From 35c340779af55f6b8d501de79988b0345b311c70 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Tue, 3 Dec 2019 15:38:29 +0100 Subject: [PATCH 001/111] worktree: mimic 'git worktree add' behavior. When adding a worktree using 'git worktree add ', if a reference named 'refs/heads/$(basename )' already exist, it is checkouted in the worktree. Mimic this behavior in libgit2. Signed-off-by: Gregory Herrero --- src/worktree.c | 26 +++++++++++++++++----- tests/worktree/worktree.c | 47 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/src/worktree.c b/src/worktree.c index ef4ebfda83c..c5edcb86bf2 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -278,7 +278,8 @@ int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *worktree, const git_worktree_add_options *opts) { - git_buf gitdir = GIT_BUF_INIT, wddir = GIT_BUF_INIT, buf = GIT_BUF_INIT; + git_buf gitdir = GIT_BUF_INIT, wddir = GIT_BUF_INIT; + git_buf buf = GIT_BUF_INIT, ref_buf = GIT_BUF_INIT; git_reference *ref = NULL, *head = NULL; git_commit *commit = NULL; git_repository *wt = NULL; @@ -365,12 +366,24 @@ int git_worktree_add(git_worktree **out, git_repository *repo, 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) + if ((err = git_buf_printf(&ref_buf, "refs/heads/%s", name)) < 0) goto out; + if (!git_reference_lookup(&ref, repo, ref_buf.ptr)) { + if (git_branch_is_checked_out(ref)) { + git_error_set(GIT_ERROR_WORKTREE, + "reference %s is already checked out", + ref_buf.ptr); + err = -1; + 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 */ @@ -392,6 +405,7 @@ int git_worktree_add(git_worktree **out, git_repository *repo, git_buf_dispose(&gitdir); git_buf_dispose(&wddir); git_buf_dispose(&buf); + git_buf_dispose(&ref_buf); git_reference_free(ref); git_reference_free(head); git_commit_free(commit); diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c index 5e99dbf616b..9ef08320163 100644 --- a/tests/worktree/worktree.c +++ b/tests/worktree/worktree.c @@ -216,6 +216,46 @@ void test_worktree_worktree__init(void) git_repository_free(repo); } +void test_worktree_worktree__add_remove_add(void) +{ + git_worktree *wt; + git_repository *repo; + git_reference *branch; + git_buf path = GIT_BUF_INIT; + + git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; + + /* Add the worktree */ + cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-add-remove-add")); + cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-add-remove-add", 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-add-remove-add/") == 0); + cl_git_pass(git_branch_lookup(&branch, repo, "worktree-add-remove-add", GIT_BRANCH_LOCAL)); + git_repository_free(repo); + + /* Prune the worktree */ + opts.flags = GIT_WORKTREE_PRUNE_VALID|GIT_WORKTREE_PRUNE_WORKING_TREE; + cl_git_pass(git_worktree_prune(wt, &opts)); + cl_assert(!git_path_exists(wt->gitdir_path)); + cl_assert(!git_path_exists(wt->gitlink_path)); + git_worktree_free(wt); + + /* Add the worktree back */ + cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-add-remove-add", 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-add-remove-add/") == 0); + cl_git_pass(git_branch_lookup(&branch, repo, "worktree-add-remove-add", GIT_BRANCH_LOCAL)); + + git_buf_dispose(&path); + git_worktree_free(wt); + git_reference_free(branch); + git_repository_free(repo); +} + void test_worktree_worktree__add_locked(void) { git_worktree *wt; @@ -250,12 +290,13 @@ void test_worktree_worktree__init_existing_branch(void) 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_branch_create(&branch, fixture.repo, "worktree-new-exist", commit, false)); - cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-new")); - cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr, NULL)); + cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-new-exist")); + cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-new-exist", path.ptr, NULL)); git_buf_dispose(&path); + git_worktree_free(wt); git_commit_free(commit); git_reference_free(head); git_reference_free(branch); From ea5028ab68584268ba9055e76eb99e5e2e6cac8e Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 24 Jan 2020 12:46:59 +0100 Subject: [PATCH 002/111] worktree: add flag to allow re-using existing branches for new worktrees In this commit's parent, we have introduced logic that will automatically re-use of existing branches if the new worktree name matches the branch name. While this is a handy feature, it changes behaviour in a backwards-incompatible way and might thus surprise users. Furthermore, it's impossible to tell whether we have created the worktree with a new or an existing reference. To fix this, introduce a new option `checkout_existing` that toggles this behaviour. Only if the flag is set will we now allow re-use of existing branches, while it's set to "off" by default. --- include/git2/worktree.h | 9 ++++---- src/worktree.c | 47 +++++++++++++-------------------------- tests/worktree/worktree.c | 32 +++++++++++++++++--------- 3 files changed, 41 insertions(+), 47 deletions(-) diff --git a/include/git2/worktree.h b/include/git2/worktree.h index 049511da121..3f1acbdb0ff 100644 --- a/include/git2/worktree.h +++ b/include/git2/worktree.h @@ -84,12 +84,13 @@ GIT_EXTERN(int) git_worktree_validate(const git_worktree *wt); typedef struct git_worktree_add_options { unsigned int version; - int lock; /**< lock newly created worktree */ - git_reference *ref; /**< reference to use for the new worktree HEAD */ + int lock; /**< lock newly created worktree */ + int checkout_existing; /**< allow checkout of existing branch matching worktree name */ + git_reference *ref; /**< reference to use for the new worktree HEAD */ } git_worktree_add_options; -#define GIT_WORKTREE_ADD_OPTIONS_VERSION 1 -#define GIT_WORKTREE_ADD_OPTIONS_INIT {GIT_WORKTREE_ADD_OPTIONS_VERSION,0,NULL} +#define GIT_WORKTREE_ADD_OPTIONS_VERSION 2 +#define GIT_WORKTREE_ADD_OPTIONS_INIT {GIT_WORKTREE_ADD_OPTIONS_VERSION,0,0,NULL} /** * Initialize git_worktree_add_options structure diff --git a/src/worktree.c b/src/worktree.c index c5edcb86bf2..5e4255b91e9 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -278,8 +278,7 @@ int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *worktree, const git_worktree_add_options *opts) { - git_buf gitdir = GIT_BUF_INIT, wddir = GIT_BUF_INIT; - git_buf buf = GIT_BUF_INIT, ref_buf = GIT_BUF_INIT; + git_buf gitdir = GIT_BUF_INIT, wddir = GIT_BUF_INIT, buf = GIT_BUF_INIT; git_reference *ref = NULL, *head = NULL; git_commit *commit = NULL; git_repository *wt = NULL; @@ -304,11 +303,21 @@ int git_worktree_add(git_worktree **out, git_repository *repo, goto out; } - if (git_branch_is_checked_out(wtopts.ref)) { - git_error_set(GIT_ERROR_WORKTREE, "reference is already checked out"); - err = -1; + if ((err = git_reference_dup(&ref, wtopts.ref)) < 0) goto out; - } + } else if (wtopts.checkout_existing && git_branch_lookup(&ref, repo, name, GIT_BRANCH_LOCAL) == 0) { + /* Do nothing */ + } else if ((err = git_repository_head(&head, repo)) < 0 || + (err = git_commit_lookup(&commit, repo, &head->target.oid)) < 0 || + (err = git_branch_create(&ref, repo, name, commit, false)) < 0) { + goto out; + } + + if (git_branch_is_checked_out(ref)) { + git_error_set(GIT_ERROR_WORKTREE, "reference %s is already checked out", + git_reference_name(ref)); + err = -1; + goto out; } /* Create gitdir directory ".git/worktrees/" */ @@ -361,31 +370,6 @@ int git_worktree_add(git_worktree **out, git_repository *repo, || (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_buf_printf(&ref_buf, "refs/heads/%s", name)) < 0) - goto out; - if (!git_reference_lookup(&ref, repo, ref_buf.ptr)) { - if (git_branch_is_checked_out(ref)) { - git_error_set(GIT_ERROR_WORKTREE, - "reference %s is already checked out", - ref_buf.ptr); - err = -1; - 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; @@ -405,7 +389,6 @@ int git_worktree_add(git_worktree **out, git_repository *repo, git_buf_dispose(&gitdir); git_buf_dispose(&wddir); git_buf_dispose(&buf); - git_buf_dispose(&ref_buf); git_reference_free(ref); git_reference_free(head); git_commit_free(commit); diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c index 9ef08320163..6a709476a1b 100644 --- a/tests/worktree/worktree.c +++ b/tests/worktree/worktree.c @@ -218,12 +218,12 @@ void test_worktree_worktree__init(void) void test_worktree_worktree__add_remove_add(void) { - git_worktree *wt; - git_repository *repo; - git_reference *branch; - git_buf path = GIT_BUF_INIT; - + git_worktree_add_options add_opts = GIT_WORKTREE_ADD_OPTIONS_INIT; git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; + git_buf path = GIT_BUF_INIT; + git_reference *branch; + git_repository *repo; + git_worktree *wt; /* Add the worktree */ cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-add-remove-add")); @@ -233,6 +233,7 @@ void test_worktree_worktree__add_remove_add(void) cl_git_pass(git_repository_open(&repo, path.ptr)); cl_assert(git__suffixcmp(git_repository_workdir(repo), "worktree-add-remove-add/") == 0); cl_git_pass(git_branch_lookup(&branch, repo, "worktree-add-remove-add", GIT_BRANCH_LOCAL)); + git_reference_free(branch); git_repository_free(repo); /* Prune the worktree */ @@ -242,18 +243,21 @@ void test_worktree_worktree__add_remove_add(void) cl_assert(!git_path_exists(wt->gitlink_path)); git_worktree_free(wt); - /* Add the worktree back */ - cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-add-remove-add", path.ptr, NULL)); + /* Add the worktree back with default options should fail. */ + cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-add-remove-add", path.ptr, &add_opts)); + /* If allowing checkout of existing branches, it should succeed. */ + add_opts.checkout_existing = 1; + cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-add-remove-add", path.ptr, &add_opts)); /* Open and verify created repo */ cl_git_pass(git_repository_open(&repo, path.ptr)); cl_assert(git__suffixcmp(git_repository_workdir(repo), "worktree-add-remove-add/") == 0); cl_git_pass(git_branch_lookup(&branch, repo, "worktree-add-remove-add", GIT_BRANCH_LOCAL)); + git_reference_free(branch); + git_repository_free(repo); git_buf_dispose(&path); git_worktree_free(wt); - git_reference_free(branch); - git_repository_free(repo); } void test_worktree_worktree__add_locked(void) @@ -283,6 +287,7 @@ void test_worktree_worktree__add_locked(void) void test_worktree_worktree__init_existing_branch(void) { + git_worktree_add_options opts = GIT_WORKTREE_ADD_OPTIONS_INIT; git_reference *head, *branch; git_commit *commit; git_worktree *wt; @@ -293,7 +298,12 @@ void test_worktree_worktree__init_existing_branch(void) cl_git_pass(git_branch_create(&branch, fixture.repo, "worktree-new-exist", commit, false)); cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-new-exist")); - cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-new-exist", path.ptr, NULL)); + + /* Add the worktree back with default options should fail. */ + cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new-exist", path.ptr, NULL)); + /* If allowing checkout of existing branches, it should succeed. */ + opts.checkout_existing = 1; + cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-new-exist", path.ptr, &opts)); git_buf_dispose(&path); git_worktree_free(wt); @@ -421,7 +431,7 @@ void test_worktree_worktree__name(void) 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); } From ed241cea1a2b1d195722d5de7e3295be9ea3a5e3 Mon Sep 17 00:00:00 2001 From: xphoniex Date: Mon, 28 Feb 2022 18:07:53 +0000 Subject: [PATCH 003/111] Swap `GIT_DIFF_LINE_(ADD|DEL)_EOFNL` to match other Diffs Signed-off-by: xphoniex --- src/libgit2/patch_parse.c | 4 ++-- tests/libgit2/diff/parse.c | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/libgit2/patch_parse.c b/src/libgit2/patch_parse.c index 78cd96252f8..be765febd0a 100644 --- a/src/libgit2/patch_parse.c +++ b/src/libgit2/patch_parse.c @@ -558,9 +558,9 @@ static int parse_hunk_header( 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; + if (origin == GIT_DIFF_LINE_DELETION) + return GIT_DIFF_LINE_ADD_EOFNL; return GIT_DIFF_LINE_CONTEXT_EOFNL; } diff --git a/tests/libgit2/diff/parse.c b/tests/libgit2/diff/parse.c index d3a0c8de6d4..48f924fe087 100644 --- a/tests/libgit2/diff/parse.c +++ b/tests/libgit2/diff/parse.c @@ -279,6 +279,31 @@ static int file_cb(const git_diff_delta *delta, float progress, void *payload) return 0; } +void test_diff_parse__eof_nl_missing(void) +{ + const char patch[] = + "diff --git a/.env b/.env\n" + "index f89e4c0..7c56eb7 100644\n" + "--- a/.env\n" + "+++ b/.env\n" + "@@ -1 +1 @@\n" + "-hello=12345\n" + "+hello=123456\n" + "\\ No newline at end of file\n"; + git_diff *diff; + git_patch *ret_patch; + git_diff_line *line; + + cl_git_pass(git_diff_from_buffer(&diff, patch, strlen(patch))); + cl_git_pass(git_patch_from_diff(&ret_patch, diff, 0)); + + cl_assert((line = git_array_get(ret_patch->lines, 2)) != NULL); + cl_assert(line->origin == GIT_DIFF_LINE_DEL_EOFNL); + + git_diff_free(diff); + git_patch_free(ret_patch); +} + void test_diff_parse__foreach_works_with_parsed_patch(void) { const char patch[] = From d9de12f88bfb025d0092eaa18f266978577c11f2 Mon Sep 17 00:00:00 2001 From: Alberto Fanjul Date: Sun, 17 Jul 2022 23:52:11 +0200 Subject: [PATCH 004/111] fix log example --- examples/log.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/log.c b/examples/log.c index 4b0a95dcd0d..e9ebe377c87 100644 --- a/examples/log.c +++ b/examples/log.c @@ -81,9 +81,11 @@ int lg2_log(git_repository *repo, int argc, char *argv[]) git_commit *commit = NULL; git_pathspec *ps = NULL; + memset(&s, 0, sizeof(s)); + /** Parse arguments and set up revwalker. */ - last_arg = parse_options(&s, &opt, argc, argv); s.repo = repo; + last_arg = parse_options(&s, &opt, argc, argv); diffopts.pathspec.strings = &argv[last_arg]; diffopts.pathspec.count = argc - last_arg; @@ -407,8 +409,6 @@ static int parse_options( struct log_state *s, struct log_options *opt, int argc, char **argv) { struct args_info args = ARGS_INFO_INIT; - - memset(s, 0, sizeof(*s)); s->sorting = GIT_SORT_TIME; memset(opt, 0, sizeof(*opt)); From 6b86332465b8740b8845043acf377f7d383f6014 Mon Sep 17 00:00:00 2001 From: Alberto Fanjul Date: Mon, 18 Jul 2022 07:57:21 +0200 Subject: [PATCH 005/111] parse arguments correctly --- examples/log.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/log.c b/examples/log.c index e9ebe377c87..84de5510985 100644 --- a/examples/log.c +++ b/examples/log.c @@ -424,7 +424,7 @@ static int parse_options( else /** Try failed revision parse as filename. */ break; - } else if (!match_arg_separator(&args)) { + } else if (match_arg_separator(&args)) { break; } else if (!strcmp(a, "--date-order")) From c97989e48deb4a68b4a3ca43af4278131dfbd288 Mon Sep 17 00:00:00 2001 From: Alberto Fanjul Date: Mon, 18 Jul 2022 08:02:41 +0200 Subject: [PATCH 006/111] Add oneline option --- examples/log.c | 48 +++++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/examples/log.c b/examples/log.c index 84de5510985..62a6eb5858f 100644 --- a/examples/log.c +++ b/examples/log.c @@ -50,6 +50,7 @@ static int add_revision(struct log_state *s, const char *revstr); /** log_options holds other command line options that affect log output */ struct log_options { int show_diff; + int show_oneline; int show_log_size; int skip, limit; int min_parents, max_parents; @@ -337,34 +338,45 @@ static void print_commit(git_commit *commit, struct log_options *opts) const char *scan, *eol; git_oid_tostr(buf, sizeof(buf), git_commit_id(commit)); - printf("commit %s\n", buf); - if (opts->show_log_size) { - printf("log size %d\n", (int)strlen(git_commit_message(commit))); - } + if (opts->show_oneline) { + printf("%s ", buf); + } else { + printf("commit %s\n", buf); - if ((count = (int)git_commit_parentcount(commit)) > 1) { - printf("Merge:"); - for (i = 0; i < count; ++i) { - git_oid_tostr(buf, 8, git_commit_parent_id(commit, i)); - printf(" %s", buf); + if (opts->show_log_size) { + printf("log size %d\n", (int)strlen(git_commit_message(commit))); + } + + if ((count = (int)git_commit_parentcount(commit)) > 1) { + printf("Merge:"); + for (i = 0; i < count; ++i) { + git_oid_tostr(buf, 8, git_commit_parent_id(commit, i)); + printf(" %s", buf); + } + printf("\n"); } - printf("\n"); - } - if ((sig = git_commit_author(commit)) != NULL) { - printf("Author: %s <%s>\n", sig->name, sig->email); - print_time(&sig->when, "Date: "); + if ((sig = git_commit_author(commit)) != NULL) { + printf("Author: %s <%s>\n", sig->name, sig->email); + print_time(&sig->when, "Date: "); + } + printf("\n"); } - printf("\n"); for (scan = git_commit_message(commit); scan && *scan; ) { for (eol = scan; *eol && *eol != '\n'; ++eol) /* find eol */; - printf(" %.*s\n", (int)(eol - scan), scan); + if (opts->show_oneline) + printf("%.*s\n", (int)(eol - scan), scan); + else + printf(" %.*s\n", (int)(eol - scan), scan); scan = *eol ? eol + 1 : NULL; + if (opts->show_oneline) + break; } - printf("\n"); + if (!opts->show_oneline) + printf("\n"); } /** Helper to find how many files in a commit changed from its nth parent. */ @@ -474,6 +486,8 @@ static int parse_options( opt->show_diff = 1; else if (!strcmp(a, "--log-size")) opt->show_log_size = 1; + else if (!strcmp(a, "--oneline")) + opt->show_oneline = 1; else usage("Unsupported argument", a); } From ca0103671f6720c06751b6770269487b9111f661 Mon Sep 17 00:00:00 2001 From: Sven Strickroth Date: Thu, 18 Aug 2022 14:05:42 +0200 Subject: [PATCH 007/111] Extend list of per worktree refs Signed-off-by: Sven Strickroth --- src/libgit2/refdb_fs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libgit2/refdb_fs.c b/src/libgit2/refdb_fs.c index 43283b3e472..586d2561ab3 100644 --- a/src/libgit2/refdb_fs.c +++ b/src/libgit2/refdb_fs.c @@ -403,7 +403,9 @@ static const char *loose_parse_symbolic(git_str *file_content) static bool is_per_worktree_ref(const char *ref_name) { return git__prefixcmp(ref_name, "refs/") != 0 || - git__prefixcmp(ref_name, "refs/bisect/") == 0; + git__prefixcmp(ref_name, "refs/bisect/") == 0 || + git__prefixcmp(ref_name, "refs/worktree/") == 0 || + git__prefixcmp(ref_name, "refs/rewritten/") == 0; } static int loose_lookup( From 4b289c190b6ba434541081d8ca2844c7a03b0384 Mon Sep 17 00:00:00 2001 From: Sven Strickroth Date: Thu, 18 Aug 2022 15:08:23 +0200 Subject: [PATCH 008/111] Make refdb_fs fully aware of per worktree refs Fixes issue isse #5492. Signed-off-by: Sven Strickroth --- src/libgit2/refdb_fs.c | 45 ++++++++++++++++++++++ tests/libgit2/worktree/refs.c | 70 ++++++++++++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/src/libgit2/refdb_fs.c b/src/libgit2/refdb_fs.c index 586d2561ab3..4628e01dc99 100644 --- a/src/libgit2/refdb_fs.c +++ b/src/libgit2/refdb_fs.c @@ -853,6 +853,8 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) git_str_truncate(&path, ref_prefix_len); git_str_puts(&path, entry->path); ref_name = git_str_cstr(&path); + if (git_repository_is_worktree(backend->repo) == 1 && is_per_worktree_ref(ref_name)) + continue; if (git__suffixcmp(ref_name, ".lock") == 0 || (iter->glob && wildmatch(iter->glob, ref_name, 0) != 0)) @@ -865,6 +867,49 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) error = git_vector_insert(&iter->loose, ref_dup); } + if (!error && git_repository_is_worktree(backend->repo) == 1) { + git_iterator_free(fsit); + git_str_clear(&path); + if ((error = git_str_puts(&path, backend->gitpath)) < 0 || + (error = git_str_put(&path, ref_prefix, ref_prefix_len)) < 0 || + !git_fs_path_exists(git_str_cstr(&path))) { + 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 (!is_per_worktree_ref(ref_name)) + continue; + + 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); diff --git a/tests/libgit2/worktree/refs.c b/tests/libgit2/worktree/refs.c index 557726aafb6..6bcf7aa9dac 100644 --- a/tests/libgit2/worktree/refs.c +++ b/tests/libgit2/worktree/refs.c @@ -20,7 +20,7 @@ void test_worktree_refs__cleanup(void) cleanup_fixture_worktree(&fixture); } -void test_worktree_refs__list(void) +void test_worktree_refs__list_no_difference_in_worktree(void) { git_strarray refs, wtrefs; unsigned i, j; @@ -61,6 +61,74 @@ void test_worktree_refs__list(void) cl_git_pass(error); } +void test_worktree_refs__list_worktree_specific(void) +{ + git_strarray refs, wtrefs; + git_reference *ref, *new_branch; + int error = 0; + git_oid oid; + + cl_git_pass(git_reference_name_to_id(&oid, fixture.repo, "refs/heads/dir")); + cl_git_fail(git_reference_lookup(&ref, fixture.repo, "refs/bisect/a-bisect-ref")); + cl_git_pass(git_reference_create( + &new_branch, fixture.worktree, "refs/bisect/a-bisect-ref", &oid, + 0, "test")); + + cl_git_fail(git_reference_lookup(&ref, fixture.repo, "refs/bisect/a-bisect-ref")); + cl_git_pass(git_reference_lookup(&ref, fixture.worktree, "refs/bisect/a-bisect-ref")); + + cl_git_pass(git_reference_list(&refs, fixture.repo)); + cl_git_pass(git_reference_list(&wtrefs, fixture.worktree)); + + if (refs.count + 1 != wtrefs.count) { + error = GIT_ERROR; + goto exit; + } + +exit: + git_reference_free(ref); + git_reference_free(new_branch); + git_strarray_dispose(&refs); + git_strarray_dispose(&wtrefs); + cl_git_pass(error); +} + +void test_worktree_refs__list_worktree_specific_hidden_in_main_repo(void) +{ + git_strarray refs, wtrefs; + git_reference *ref, *new_branch; + int error = 0; + git_oid oid; + + cl_git_pass( + git_reference_name_to_id(&oid, fixture.repo, "refs/heads/dir")); + cl_git_fail(git_reference_lookup( + &ref, fixture.worktree, "refs/bisect/a-bisect-ref")); + cl_git_pass(git_reference_create( + &new_branch, fixture.repo, "refs/bisect/a-bisect-ref", &oid, + 0, "test")); + + cl_git_fail(git_reference_lookup( + &ref, fixture.worktree, "refs/bisect/a-bisect-ref")); + cl_git_pass(git_reference_lookup( + &ref, fixture.repo, "refs/bisect/a-bisect-ref")); + + cl_git_pass(git_reference_list(&refs, fixture.repo)); + cl_git_pass(git_reference_list(&wtrefs, fixture.worktree)); + + if (refs.count != wtrefs.count + 1) { + error = GIT_ERROR; + goto exit; + } + +exit: + git_reference_free(ref); + git_reference_free(new_branch); + git_strarray_dispose(&refs); + git_strarray_dispose(&wtrefs); + cl_git_pass(error); +} + void test_worktree_refs__read_head(void) { git_reference *head; From 40556c798aa23e13e4e6f859c3e43b7e118a7ab0 Mon Sep 17 00:00:00 2001 From: Christopher Fujino Date: Sat, 9 Sep 2023 20:06:03 -0700 Subject: [PATCH 009/111] add dl to LIBGIT2_SYSTEM_LIBS --- cmake/SelectHTTPSBackend.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/SelectHTTPSBackend.cmake b/cmake/SelectHTTPSBackend.cmake index 0b3d63a790c..94a0953f891 100644 --- a/cmake/SelectHTTPSBackend.cmake +++ b/cmake/SelectHTTPSBackend.cmake @@ -54,7 +54,7 @@ if(USE_HTTPS) set(GIT_OPENSSL 1) list(APPEND LIBGIT2_SYSTEM_INCLUDES ${OPENSSL_INCLUDE_DIR}) - list(APPEND LIBGIT2_SYSTEM_LIBS ${OPENSSL_LIBRARIES}) + list(APPEND LIBGIT2_SYSTEM_LIBS ${OPENSSL_LIBRARIES} ${CMAKE_DL_LIBS}) list(APPEND LIBGIT2_PC_LIBS ${OPENSSL_LDFLAGS}) list(APPEND LIBGIT2_PC_REQUIRES "openssl") elseif(USE_HTTPS STREQUAL "mbedTLS") From 8dd5d60e807fc49f136921c7537688de77d6190e Mon Sep 17 00:00:00 2001 From: Christopher Fujino Date: Sat, 9 Sep 2023 20:50:42 -0700 Subject: [PATCH 010/111] guard --- cmake/SelectHTTPSBackend.cmake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmake/SelectHTTPSBackend.cmake b/cmake/SelectHTTPSBackend.cmake index 94a0953f891..fbc2adc147f 100644 --- a/cmake/SelectHTTPSBackend.cmake +++ b/cmake/SelectHTTPSBackend.cmake @@ -54,7 +54,10 @@ if(USE_HTTPS) set(GIT_OPENSSL 1) list(APPEND LIBGIT2_SYSTEM_INCLUDES ${OPENSSL_INCLUDE_DIR}) - list(APPEND LIBGIT2_SYSTEM_LIBS ${OPENSSL_LIBRARIES} ${CMAKE_DL_LIBS}) + list(APPEND LIBGIT2_SYSTEM_LIBS ${OPENSSL_LIBRARIES}) + if(LINK_WITH_STATIC_LIBRARIES STREQUAL ON) + list(APPEND LIBGIT2_SYSTEM_LIBS ${CMAKE_DL_LIBS}) + endif() list(APPEND LIBGIT2_PC_LIBS ${OPENSSL_LDFLAGS}) list(APPEND LIBGIT2_PC_REQUIRES "openssl") elseif(USE_HTTPS STREQUAL "mbedTLS") From e619b884d5f121d7e584f77e2187b720d4d23f01 Mon Sep 17 00:00:00 2001 From: Peter Pettersson Date: Thu, 14 Dec 2023 19:22:05 +0100 Subject: [PATCH 011/111] ctype: cast characters to unsigned when classifying characters ctype classification takes an integer in as returned from getc() if we just sign extend characters to integers 128-255 will be misclassified. (255 will become EOF) Newlib in particular doesn't like this since they uses the value as an index in a lookup table. --- src/libgit2/config.c | 2 +- src/libgit2/config_parse.c | 9 +++++---- src/libgit2/path.c | 2 +- src/libgit2/trailer.c | 10 +++++----- src/libgit2/transports/smart_pkt.c | 4 ++-- src/util/date.c | 18 +++++++++--------- src/util/str.c | 4 ++-- src/util/util.h | 2 +- tests/libgit2/repo/open.c | 2 +- 9 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index 23a8f9ffad1..5c1c00f6cb7 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -1447,7 +1447,7 @@ static int normalize_section(char *start, char *end) for (scan = start; *scan; ++scan) { if (end && scan >= end) break; - if (isalnum(*scan)) + if (isalnum((unsigned char)*scan)) *scan = (char)git__tolower(*scan); else if (*scan != '-' || scan == start) return GIT_EINVALIDSPEC; diff --git a/src/libgit2/config_parse.c b/src/libgit2/config_parse.c index 1431bed36db..9ab78cc7f60 100644 --- a/src/libgit2/config_parse.c +++ b/src/libgit2/config_parse.c @@ -25,9 +25,9 @@ static void set_parse_error(git_config_parser *reader, int col, const char *erro } -GIT_INLINE(int) config_keychar(int c) +GIT_INLINE(int) config_keychar(char c) { - return isalnum(c) || c == '-'; + return isalnum((unsigned char)c) || c == '-'; } static int strip_comments(char *line, int in_quotes) @@ -158,9 +158,10 @@ static int parse_subsection_header(git_config_parser *reader, const char *line, static int parse_section_header(git_config_parser *reader, char **section_out) { char *name, *name_end; - int name_length, c, pos; + int name_length, pos; int result; char *line; + char c; size_t line_len; git_parse_advance_ws(&reader->ctx); @@ -382,7 +383,7 @@ static int parse_multiline_variable(git_config_parser *reader, git_str *value, i GIT_INLINE(bool) is_namechar(char c) { - return isalnum(c) || c == '-'; + return isalnum((unsigned char)c) || c == '-'; } static int parse_name( diff --git a/src/libgit2/path.c b/src/libgit2/path.c index a19340efe6f..50181fdbff0 100644 --- a/src/libgit2/path.c +++ b/src/libgit2/path.c @@ -202,7 +202,7 @@ GIT_INLINE(size_t) common_prefix_icase(const char *str, size_t len, const char * { size_t count = 0; - while (len > 0 && tolower(*str) == tolower(*prefix)) { + while (len > 0 && tolower((unsigned char)*str) == tolower((unsigned char)*prefix)) { count++; str++; prefix++; diff --git a/src/libgit2/trailer.c b/src/libgit2/trailer.c index 4761c9922f2..9fb16418413 100644 --- a/src/libgit2/trailer.c +++ b/src/libgit2/trailer.c @@ -24,7 +24,7 @@ static const char *const git_generated_prefixes[] = { static int is_blank_line(const char *str) { const char *s = str; - while (*s && *s != '\n' && isspace(*s)) + while (*s && *s != '\n' && isspace((unsigned char)*s)) s++; return !*s || *s == '\n'; } @@ -93,7 +93,7 @@ static bool find_separator(size_t *out, const char *line, const char *separators return true; } - if (!whitespace_found && (isalnum(*c) || *c == '-')) + if (!whitespace_found && (isalnum((unsigned char)*c) || *c == '-')) continue; if (c != line && (*c == ' ' || *c == '\t')) { whitespace_found = 1; @@ -233,12 +233,12 @@ static size_t find_trailer_start(const char *buf, size_t len) } find_separator(&separator_pos, bol, TRAILER_SEPARATORS); - if (separator_pos >= 1 && !isspace(bol[0])) { + if (separator_pos >= 1 && !isspace((unsigned char)bol[0])) { trailer_lines++; possible_continuation_lines = 0; if (recognized_prefix) continue; - } else if (isspace(bol[0])) + } else if (isspace((unsigned char)bol[0])) possible_continuation_lines++; else { non_trailer_lines++; @@ -323,7 +323,7 @@ int git_message_trailers(git_message_trailer_array *trailer_arr, const char *mes goto ret; } - if (isalnum(*ptr) || *ptr == '-') { + if (isalnum((unsigned char)*ptr) || *ptr == '-') { /* legal key character */ NEXT(S_KEY); } diff --git a/src/libgit2/transports/smart_pkt.c b/src/libgit2/transports/smart_pkt.c index 7805f332377..08cb7fbe5c2 100644 --- a/src/libgit2/transports/smart_pkt.c +++ b/src/libgit2/transports/smart_pkt.c @@ -535,10 +535,10 @@ static int parse_len(size_t *out, const char *line, size_t linelen) num[PKT_LEN_SIZE] = '\0'; for (i = 0; i < PKT_LEN_SIZE; ++i) { - if (!isxdigit(num[i])) { + if (!isxdigit((unsigned char)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])) { + if(!isprint((unsigned char)num[k])) { num[k] = '.'; } } diff --git a/src/util/date.c b/src/util/date.c index 4d757e21a00..d54056842dd 100644 --- a/src/util/date.c +++ b/src/util/date.c @@ -129,9 +129,9 @@ static size_t match_string(const char *date, const char *str) for (i = 0; *date; date++, str++, i++) { if (*date == *str) continue; - if (toupper(*date) == toupper(*str)) + if (toupper((unsigned char)*date) == toupper((unsigned char)*str)) continue; - if (!isalnum(*date)) + if (!isalnum((unsigned char)*date)) break; return 0; } @@ -143,7 +143,7 @@ static int skip_alpha(const char *date) int i = 0; do { i++; - } while (isalpha(date[i])); + } while (isalpha((unsigned char)date[i])); return i; } @@ -251,7 +251,7 @@ static size_t match_multi_number(unsigned long num, char c, const char *date, ch num2 = strtol(end+1, &end, 10); num3 = -1; - if (*end == c && isdigit(end[1])) + if (*end == c && isdigit((unsigned char)end[1])) num3 = strtol(end+1, &end, 10); /* Time? Date? */ @@ -349,7 +349,7 @@ static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_ case '.': case '/': case '-': - if (isdigit(end[1])) { + if (isdigit((unsigned char)end[1])) { size_t match = match_multi_number(num, *end, date, end, tm); if (match) return match; @@ -364,7 +364,7 @@ static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_ n = 0; do { n++; - } while (isdigit(date[n])); + } while (isdigit((unsigned char)date[n])); /* Four-digit year or a timezone? */ if (n == 4) { @@ -518,7 +518,7 @@ static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset 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])) + else if ((c == '-' || c == '+') && isdigit((unsigned char)date[1])) match = match_tz(date, offset); if (!match) { @@ -682,7 +682,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm const char *end = date; int i; - while (isalpha(*++end)) + while (isalpha((unsigned char)*++end)) /* scan to non-alpha */; for (i = 0; i < 12; i++) { @@ -783,7 +783,7 @@ static const char *approxidate_digit(const char *date, struct tm *tm, int *num) case '.': case '/': case '-': - if (isdigit(end[1])) { + if (isdigit((unsigned char)end[1])) { size_t match = match_multi_number(number, *end, date, end, tm); if (match) return date + match; diff --git a/src/util/str.c b/src/util/str.c index 0d405bfda50..625faba06db 100644 --- a/src/util/str.c +++ b/src/util/str.c @@ -485,8 +485,8 @@ int git_str_decode_percent( 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])) { + isxdigit((unsigned char)str[str_pos + 1]) && + isxdigit((unsigned char)str[str_pos + 2])) { buf->ptr[buf->size] = (HEX_DECODE(str[str_pos + 1]) << 4) + HEX_DECODE(str[str_pos + 2]); str_pos += 2; diff --git a/src/util/util.h b/src/util/util.h index 7f178b169fe..933644fd2b9 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -89,7 +89,7 @@ GIT_INLINE(int) git__tolower(int c) return (c >= 'A' && c <= 'Z') ? (c + 32) : c; } #else -# define git__tolower(a) tolower(a) +# define git__tolower(a) tolower((unsigned char)(a)) #endif extern size_t git__linenlen(const char *buffer, size_t buffer_len); diff --git a/tests/libgit2/repo/open.c b/tests/libgit2/repo/open.c index 3d1a0620b12..219612016b9 100644 --- a/tests/libgit2/repo/open.c +++ b/tests/libgit2/repo/open.c @@ -316,7 +316,7 @@ static void unposix_path(git_str *path) src = tgt = path->ptr; /* convert "/d/..." to "d:\..." */ - if (src[0] == '/' && isalpha(src[1]) && src[2] == '/') { + if (src[0] == '/' && isalpha((unsigned char)src[1]) && src[2] == '/') { *tgt++ = src[1]; *tgt++ = ':'; *tgt++ = '\\'; From 216f698eb2e22ca5d6fbd58f60f6481a4e994934 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Mon, 15 Jan 2024 14:14:39 +0100 Subject: [PATCH 012/111] merge: fix incorrect rename detection for empty files. When merging trees containing multiple empty files, make sure a rename is not detected between each empty files. For example Ancestor has files: * foo.c with content * bar.c empty Ours has: * foo.c with content * boo.c empty Theirs has: * foo.c with other content * bar.c with content merge_trees() will incorrectly apply the content of theirs's 'bar.c' in ours's boo.c' thinking 'bar.c' has been renamed to 'boo.c'. This happens because both are empty and their sha1 are the same. Thus merge_diff_mark_similarity_exact() incorrectly mark it as renamed. Signed-off-by: Gregory Herrero --- src/libgit2/merge.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libgit2/merge.c b/src/libgit2/merge.c index 0114e4b75a0..21e5ef6a8f9 100644 --- a/src/libgit2/merge.c +++ b/src/libgit2/merge.c @@ -1225,6 +1225,13 @@ static int merge_diff_mark_similarity_exact( if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->ancestor_entry)) continue; + /* + * Ignore empty files because it has always the same blob sha1 + * and will lead to incorrect matches between all entries. + */ + if (git_oid_equal(&conflict_src->ancestor_entry.id, &git_oid__empty_blob_sha1)) + 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) From 334dd8da93cb80c353fb3b363bfb730383f09fb8 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Mon, 15 Jan 2024 15:52:44 +0100 Subject: [PATCH 013/111] tests: merge: tree: add support for different ancestor. Add a boolean option to merge_trivial() so that it can use theirs parent instead of merge base as ancestor. This will be needed in the following commit. Signed-off-by: Gregory Herrero --- tests/libgit2/merge/trees/trivial.c | 36 ++++++++++++++++------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/tests/libgit2/merge/trees/trivial.c b/tests/libgit2/merge/trees/trivial.c index 287a53cfe7a..becd618d358 100644 --- a/tests/libgit2/merge/trees/trivial.c +++ b/tests/libgit2/merge/trees/trivial.c @@ -25,7 +25,7 @@ void test_merge_trees_trivial__cleanup(void) } -static int merge_trivial(git_index **index, const char *ours, const char *theirs) +static int merge_trivial(git_index **index, const char *ours, const char *theirs, bool ancestor_use_parent) { git_commit *our_commit, *their_commit, *ancestor_commit; git_tree *our_tree, *their_tree, *ancestor_tree; @@ -42,8 +42,12 @@ static int merge_trivial(git_index **index, const char *ours, const char *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)); + if (!ancestor_use_parent) { + 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)); + } else { + cl_git_pass(git_commit_parent(&ancestor_commit, their_commit, 0)); + } cl_git_pass(git_commit_tree(&ancestor_tree, ancestor_commit)); cl_git_pass(git_commit_tree(&our_tree, our_commit)); @@ -84,7 +88,7 @@ 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_git_pass(merge_trivial(&result, "trivial-2alt", "trivial-2alt-branch", false)); cl_assert(entry = git_index_get_bypath(result, "new-in-branch.txt", 0)); cl_assert(git_index_reuc_entrycount(result) == 0); @@ -99,7 +103,7 @@ 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_git_pass(merge_trivial(&result, "trivial-3alt", "trivial-3alt-branch", false)); cl_assert(entry = git_index_get_bypath(result, "new-in-3alt.txt", 0)); cl_assert(git_index_reuc_entrycount(result) == 0); @@ -114,7 +118,7 @@ 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_git_pass(merge_trivial(&result, "trivial-4", "trivial-4-branch", false)); cl_assert((entry = git_index_get_bypath(result, "new-and-different.txt", 0)) == NULL); cl_assert(git_index_reuc_entrycount(result) == 0); @@ -132,7 +136,7 @@ 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_git_pass(merge_trivial(&result, "trivial-5alt-1", "trivial-5alt-1-branch", false)); cl_assert(entry = git_index_get_bypath(result, "new-and-same.txt", 0)); cl_assert(git_index_reuc_entrycount(result) == 0); @@ -147,7 +151,7 @@ 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_git_pass(merge_trivial(&result, "trivial-5alt-2", "trivial-5alt-2-branch", false)); cl_assert(entry = git_index_get_bypath(result, "modified-to-same.txt", 0)); cl_assert(git_index_reuc_entrycount(result) == 0); @@ -163,7 +167,7 @@ void test_merge_trees_trivial__6(void) const git_index_entry *entry; const git_index_reuc_entry *reuc; - cl_git_pass(merge_trivial(&result, "trivial-6", "trivial-6-branch")); + cl_git_pass(merge_trivial(&result, "trivial-6", "trivial-6-branch", false)); cl_assert((entry = git_index_get_bypath(result, "removed-in-both.txt", 0)) == NULL); cl_assert(git_index_reuc_entrycount(result) == 1); @@ -181,7 +185,7 @@ void test_merge_trees_trivial__8(void) const git_index_entry *entry; const git_index_reuc_entry *reuc; - cl_git_pass(merge_trivial(&result, "trivial-8", "trivial-8-branch")); + cl_git_pass(merge_trivial(&result, "trivial-8", "trivial-8-branch", false)); cl_assert((entry = git_index_get_bypath(result, "removed-in-8.txt", 0)) == NULL); @@ -199,7 +203,7 @@ 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_git_pass(merge_trivial(&result, "trivial-7", "trivial-7-branch", false)); cl_assert((entry = git_index_get_bypath(result, "removed-in-7.txt", 0)) == NULL); cl_assert(git_index_reuc_entrycount(result) == 0); @@ -218,7 +222,7 @@ void test_merge_trees_trivial__10(void) const git_index_entry *entry; const git_index_reuc_entry *reuc; - cl_git_pass(merge_trivial(&result, "trivial-10", "trivial-10-branch")); + cl_git_pass(merge_trivial(&result, "trivial-10", "trivial-10-branch", false)); cl_assert((entry = git_index_get_bypath(result, "removed-in-10-branch.txt", 0)) == NULL); @@ -236,7 +240,7 @@ 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_git_pass(merge_trivial(&result, "trivial-9", "trivial-9-branch", false)); cl_assert((entry = git_index_get_bypath(result, "removed-in-9-branch.txt", 0)) == NULL); cl_assert(git_index_reuc_entrycount(result) == 0); @@ -255,7 +259,7 @@ void test_merge_trees_trivial__13(void) const git_index_entry *entry; git_oid expected_oid; - cl_git_pass(merge_trivial(&result, "trivial-13", "trivial-13-branch")); + cl_git_pass(merge_trivial(&result, "trivial-13", "trivial-13-branch", false)); cl_assert(entry = git_index_get_bypath(result, "modified-in-13.txt", 0)); cl_git_pass(git_oid__fromstr(&expected_oid, "1cff9ec6a47a537380dedfdd17c9e76d74259a2b", GIT_OID_SHA1)); @@ -274,7 +278,7 @@ void test_merge_trees_trivial__14(void) const git_index_entry *entry; git_oid expected_oid; - cl_git_pass(merge_trivial(&result, "trivial-14", "trivial-14-branch")); + cl_git_pass(merge_trivial(&result, "trivial-14", "trivial-14-branch", false)); cl_assert(entry = git_index_get_bypath(result, "modified-in-14-branch.txt", 0)); cl_git_pass(git_oid__fromstr(&expected_oid, "26153a3ff3649b6c2bb652d3f06878c6e0a172f9", GIT_OID_SHA1)); @@ -292,7 +296,7 @@ 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_git_pass(merge_trivial(&result, "trivial-11", "trivial-11-branch", false)); cl_assert((entry = git_index_get_bypath(result, "modified-in-both.txt", 0)) == NULL); cl_assert(git_index_reuc_entrycount(result) == 0); From 16688efa412208d0ed6d90d24ae632abda9c8a4d Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Mon, 15 Jan 2024 15:46:00 +0100 Subject: [PATCH 014/111] tests: merge: tree: detect incorrect renames. This test follow "merge: fix incorrect rename detection for empty files." commit. Signed-off-by: Gregory Herrero --- tests/libgit2/merge/trees/trivial.c | 19 ++++++++++++++++++ .../.gitted/logs/refs/heads/trivial-15 | 1 + .../.gitted/logs/refs/heads/trivial-15-branch | 2 ++ .../1d/6a96dd04ac7354ae6bb4ba24c514500fd0ec89 | Bin 0 -> 279 bytes .../48/320305de02310b9c4fd744243b9b0d1d5f11af | Bin 0 -> 46 bytes .../67/06996f054c6af4fec7c77939d00e2f486dab4c | Bin 0 -> 172 bytes .../c0/d1f321977d1c18e23a28c581fed6d17d0cc013 | Bin 0 -> 273 bytes .../c4/52c5eb5aacf204fc95a55d1eb9668736fecfb6 | Bin 0 -> 163 bytes .../c5/be2acabac675af81df8bb70400235af2a9c225 | 4 ++++ .../cd/d1cd02dbd164e9d18a644515bc2ca6551f13b4 | Bin 0 -> 280 bytes .../.gitted/refs/heads/trivial-15 | 1 + .../.gitted/refs/heads/trivial-15-branch | 1 + 12 files changed, 28 insertions(+) create mode 100644 tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-15 create mode 100644 tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-15-branch create mode 100644 tests/resources/merge-resolve/.gitted/objects/1d/6a96dd04ac7354ae6bb4ba24c514500fd0ec89 create mode 100644 tests/resources/merge-resolve/.gitted/objects/48/320305de02310b9c4fd744243b9b0d1d5f11af create mode 100644 tests/resources/merge-resolve/.gitted/objects/67/06996f054c6af4fec7c77939d00e2f486dab4c create mode 100644 tests/resources/merge-resolve/.gitted/objects/c0/d1f321977d1c18e23a28c581fed6d17d0cc013 create mode 100644 tests/resources/merge-resolve/.gitted/objects/c4/52c5eb5aacf204fc95a55d1eb9668736fecfb6 create mode 100644 tests/resources/merge-resolve/.gitted/objects/c5/be2acabac675af81df8bb70400235af2a9c225 create mode 100644 tests/resources/merge-resolve/.gitted/objects/cd/d1cd02dbd164e9d18a644515bc2ca6551f13b4 create mode 100644 tests/resources/merge-resolve/.gitted/refs/heads/trivial-15 create mode 100644 tests/resources/merge-resolve/.gitted/refs/heads/trivial-15-branch diff --git a/tests/libgit2/merge/trees/trivial.c b/tests/libgit2/merge/trees/trivial.c index becd618d358..7f9d918e806 100644 --- a/tests/libgit2/merge/trees/trivial.c +++ b/tests/libgit2/merge/trees/trivial.c @@ -308,3 +308,22 @@ void test_merge_trees_trivial__11(void) git_index_free(result); } + +/* 15: ancest:remote^, head:head, remote:remote = result:no merge */ +void test_merge_trees_trivial__15(void) +{ + git_index *result; + const git_index_entry *entry; + + /* Can't use merge_trivialfalsehere because a different ancestor is used. */ + cl_git_pass(merge_trivial(&result, "trivial-15", "trivial-15-branch", true)); + + cl_assert((entry = git_index_get_bypath(result, "another-new-empty-15.txt", GIT_INDEX_STAGE_NORMAL)) == NULL); + cl_assert((entry = git_index_get_bypath(result, "another-new-empty-15.txt", GIT_INDEX_STAGE_ANCESTOR))); + cl_assert((entry = git_index_get_bypath(result, "another-new-empty-15.txt", GIT_INDEX_STAGE_OURS)) == NULL); + cl_assert((entry = git_index_get_bypath(result, "another-new-empty-15.txt", GIT_INDEX_STAGE_THEIRS))); + cl_assert(merge_trivial_conflict_entrycount(result) == 2); + + git_index_free(result); +} + diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-15 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-15 new file mode 100644 index 00000000000..c71411b5f11 --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-15 @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 c452c5eb5aacf204fc95a55d1eb9668736fecfb6 Gregory Herrero 1705326305 +0100 push diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-15-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-15-branch new file mode 100644 index 00000000000..f85a1e96dd9 --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-15-branch @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 6706996f054c6af4fec7c77939d00e2f486dab4c Gregory Herrero 1705326302 +0100 push +6706996f054c6af4fec7c77939d00e2f486dab4c c5be2acabac675af81df8bb70400235af2a9c225 Gregory Herrero 1705326412 +0100 push diff --git a/tests/resources/merge-resolve/.gitted/objects/1d/6a96dd04ac7354ae6bb4ba24c514500fd0ec89 b/tests/resources/merge-resolve/.gitted/objects/1d/6a96dd04ac7354ae6bb4ba24c514500fd0ec89 new file mode 100644 index 0000000000000000000000000000000000000000..ec618090bdf04fd36e456a413c316a3e8ab4dca1 GIT binary patch literal 279 zcmV+y0qFjC0V^p=O;s>9H)k+3FfcPQQAo_oFUd$P(#=aP*G|#Ju#>6y3}`-K3(#yyOh9hSfPPi(a@)TI=n&J9|yLsoh+rHQksRaubV7 zQi}*`$j?j5$xJTE%u5F=y`uhL(?g2@DO=Gq<_YDCljTJBx*O7ai1V#(6;Mw#=Y#jm>Q5A6JwpRT>i+fSTp_0?Qy}vHCa*HmqIm^0(}VfFxV+4 dKIH3JZ<^*#A Cyb`|v literal 0 HcmV?d00001 diff --git a/tests/resources/merge-resolve/.gitted/objects/67/06996f054c6af4fec7c77939d00e2f486dab4c b/tests/resources/merge-resolve/.gitted/objects/67/06996f054c6af4fec7c77939d00e2f486dab4c new file mode 100644 index 0000000000000000000000000000000000000000..92ddbf0be101a048e8e819dac29dfdc5a4305659 GIT binary patch literal 172 zcmV;d08{^X0j17OY6CG0hT*P#3h&Ddl08yf2%)?78n&$YflOS46Ugl|kRx>a@bd9V z+xM;eK=jc~)l01?6+$f)dD5?nDf2WDQjs8;iEzePCij<3Z`}*SX_g2nMU9cF&LK(V z#N`NC<_w_#tGImcYcv02x-@(GUo}%}{=B?lT;K5BEX!3#Y5T?F2`B~*0sSW+fIIYf a+x5RYuG{w5@^wHOishTEqk91!2~$WI3Rb58 literal 0 HcmV?d00001 diff --git a/tests/resources/merge-resolve/.gitted/objects/c0/d1f321977d1c18e23a28c581fed6d17d0cc013 b/tests/resources/merge-resolve/.gitted/objects/c0/d1f321977d1c18e23a28c581fed6d17d0cc013 new file mode 100644 index 0000000000000000000000000000000000000000..4c20a212ade60c6f8046f0f5384bb06e145ce014 GIT binary patch literal 273 zcmV+s0q*{I0V^p=O;s>9vt%$dFfcPQQAjK;$5Ep%1PBLsVHGc5;`oqI?hAM z{rdDef8NUpvTr_GLlq}yB<7{3rs!to=_VB=<|Su>F;6I8oGd50*9~fRUTV2+ zYHmSErLLhVSnad9k7jSWc+J_nTXS7`)Qv+=dV(iH)fT1Z=9ghO{Ql!l+N8 zkTGc6^*G~R_FYU3NDhy&&R8ygh_N7J5=Yr3_6e($P%su?1|;*!D$i~?K&(uV5($wnQkS}f6h@CV zaLwRG2*M`6`@N~XsN9-NRjNr)yB(hsT0JG@ww#&ae Rrt9>X@?(d7Fh7HwP+9wjOc4M8 literal 0 HcmV?d00001 diff --git a/tests/resources/merge-resolve/.gitted/objects/c5/be2acabac675af81df8bb70400235af2a9c225 b/tests/resources/merge-resolve/.gitted/objects/c5/be2acabac675af81df8bb70400235af2a9c225 new file mode 100644 index 00000000000..848f341c929 --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/objects/c5/be2acabac675af81df8bb70400235af2a9c225 @@ -0,0 +1,4 @@ +x¥ÎAj1 @Ѭ} +ïCyF–c(%»ö²,g2qn¡·o GÈö->_ú¾oÃÏ1†©úP‰3Õ +È’–ˆ¬T +žQbÀÐ*¨œ³{°é}xJ@9SƒˆBÜ°©$I)/¹èÜðL• ŠãŸ±vó_¦×nþ[ÍÔºÿ¸¾`Z_péÆrÓIúþéC‚¸Ì„@þÀ=õy;ôÝŽ¶ýn|;…x*ÆwY'÷äIS \ No newline at end of file diff --git a/tests/resources/merge-resolve/.gitted/objects/cd/d1cd02dbd164e9d18a644515bc2ca6551f13b4 b/tests/resources/merge-resolve/.gitted/objects/cd/d1cd02dbd164e9d18a644515bc2ca6551f13b4 new file mode 100644 index 0000000000000000000000000000000000000000..d2385b8f2353863209a288ba2cd3edc4ef04cfad GIT binary patch literal 280 zcmV+z0q6dB0V^p=O;s>9H)k+3FfcPQQAo_oFUd$P(#=aP*GO{^L*D ztK{>~g}6_UF=*TMIOAUST}%x~j)}3(ST29$SFD--<@UH>;hLAL e6Cd*RtT#<_EJ?4M_+j;{3r}q3#R3319++g`S&~%% literal 0 HcmV?d00001 diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-15 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-15 new file mode 100644 index 00000000000..d6149eb8131 --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-15 @@ -0,0 +1 @@ +c452c5eb5aacf204fc95a55d1eb9668736fecfb6 diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-15-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-15-branch new file mode 100644 index 00000000000..a255d1a3a95 --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-15-branch @@ -0,0 +1 @@ +c5be2acabac675af81df8bb70400235af2a9c225 From 7f1d52067a4ecb5cacdff07f3563c400f497235a Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Mon, 22 Jan 2024 20:09:26 +0100 Subject: [PATCH 015/111] config: properly rename section containing multivars Renaming a section or deleting its content removes each entry after copying it in its new place if needed. However, since each entry may be part of a multivar, deletion must only remove the exact entry that has just been copied. --- src/libgit2/config.c | 29 ++++++++++++++++++++------ tests/libgit2/config/multivar.c | 31 ++++++++++++++++++++++++++++ tests/libgit2/refs/branches/delete.c | 15 ++++++++++++++ 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index 23a8f9ffad1..d11e76d9577 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -1509,19 +1509,36 @@ static int rename_config_entries_cb( int error = 0; struct rename_data *data = (struct rename_data *)payload; size_t base_len = git_str_len(data->name); + git_str raw_value = GIT_STR_INIT, value = GIT_STR_INIT; 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); + error = git_config_set_multivar( + data->config, git_str_cstr(data->name), "^$", + entry->value); } - if (!error) - error = git_config_delete_entry(data->config, entry->name); + if (!error) { + if ((error = git_str_puts_escape_regex( + &raw_value, entry->value))) + goto cleanup; + git_str_grow(&value, git_str_len(&raw_value) + 2); + git_str_putc(&value, '^'); + git_str_puts(&value, git_str_cstr(&raw_value)); + git_str_putc(&value, '$'); + if (git_str_oom(&value)) { + error = -1; + goto cleanup; + } + error = git_config_delete_multivar( + data->config, entry->name, git_str_cstr(&value)); + } + cleanup: + git_str_truncate(data->name, base_len); + git_str_dispose(&raw_value); + git_str_dispose(&value); return error; } diff --git a/tests/libgit2/config/multivar.c b/tests/libgit2/config/multivar.c index 244e3755965..3ed846012fa 100644 --- a/tests/libgit2/config/multivar.c +++ b/tests/libgit2/config/multivar.c @@ -1,4 +1,6 @@ #include "clar_libgit2.h" +#include "config.h" +#include "config/config_helpers.h" static const char *_name = "remote.ab.url"; @@ -286,3 +288,32 @@ void test_config_multivar__delete_notfound(void) git_config_free(cfg); } + +void test_config_multivar__rename_section(void) +{ + git_repository *repo; + git_config *cfg; + int n; + + repo = cl_git_sandbox_init("testrepo"); + cl_git_pass(git_repository_config(&cfg, repo)); + + cl_git_pass(git_config_set_multivar(cfg, "branch.foo.name", "^$", "bar")); + cl_git_pass(git_config_set_multivar(cfg, "branch.foo.name", "^$", "xyzzy")); + n = 0; + cl_git_pass(git_config_get_multivar_foreach( + cfg, "branch.foo.name", NULL, cb, &n)); + cl_assert(n == 2); + + cl_git_pass( + git_config_rename_section(repo, "branch.foo", "branch.foobar")); + + assert_config_entry_existence(repo, "branch.foo.name", false); + n = 0; + cl_git_pass(git_config_get_multivar_foreach( + cfg, "branch.foobar.name", NULL, cb, &n)); + cl_assert(n == 2); + + git_config_free(cfg); + cl_git_sandbox_cleanup(); +} diff --git a/tests/libgit2/refs/branches/delete.c b/tests/libgit2/refs/branches/delete.c index 6b3d507a869..63f8c5d95c7 100644 --- a/tests/libgit2/refs/branches/delete.c +++ b/tests/libgit2/refs/branches/delete.c @@ -92,6 +92,21 @@ void test_refs_branches_delete__can_delete_a_local_branch(void) git_reference_free(branch); } +void test_refs_branches_delete__can_delete_a_local_branch_with_multivar(void) +{ + git_reference *branch; + git_config *cfg; + + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_set_multivar( + cfg, "branch.br2.gitpublishto", "^$", "example1@example.com")); + cl_git_pass(git_config_set_multivar( + cfg, "branch.br2.gitpublishto", "^$", "example2@example.com")); + 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; From c95e91a280e58c4776b8f440c1bde5b9184827c5 Mon Sep 17 00:00:00 2001 From: Sebastien Marie Date: Mon, 12 Feb 2024 10:17:02 +0100 Subject: [PATCH 016/111] GIT_RAND_GETENTROPY: do not include sys/random.h CMakeLists.txt is looking for unistd.h (where the getentropy function is present for at least Linux and OpenBSD). It isn't necessary to include sys/random.h (which is Linux only). It unbreak the build on OpenBSD. --- src/util/rand.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/util/rand.c b/src/util/rand.c index 2ed0605738b..a02853519b2 100644 --- a/src/util/rand.c +++ b/src/util/rand.c @@ -10,10 +10,6 @@ See . */ #include "rand.h" #include "runtime.h" -#if defined(GIT_RAND_GETENTROPY) -# include -#endif - #if defined(GIT_WIN32) # include #endif From 38a062ddca5d95d8e308894886567d2c49a0fac2 Mon Sep 17 00:00:00 2001 From: Parnic Date: Fri, 16 Feb 2024 09:09:32 -0600 Subject: [PATCH 017/111] Support index.skipHash true config When this is set, the checksum at the end of the index is zero. Fixes #6531 --- src/libgit2/index.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libgit2/index.c b/src/libgit2/index.c index 5074e3e1fa4..86f14fdfc41 100644 --- a/src/libgit2/index.c +++ b/src/libgit2/index.c @@ -2760,6 +2760,7 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) unsigned int i; struct index_header header = { 0 }; unsigned char checksum[GIT_HASH_MAX_SIZE]; + unsigned char zero_checksum[GIT_HASH_MAX_SIZE] = { 0 }; size_t checksum_size = git_hash_size(git_oid_algorithm(index->oid_type)); const char *last = NULL; const char *empty = ""; @@ -2849,8 +2850,9 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) /* * SHA-1 or SHA-256 (depending on the repository's object format) * over the content of the index file before this checksum. + * Note: checksum may be 0 if index.skipHash is set to true. */ - if (memcmp(checksum, buffer, checksum_size) != 0) { + if (memcmp(zero_checksum, buffer, checksum_size) != 0 && memcmp(checksum, buffer, checksum_size) != 0) { error = index_error_invalid( "calculated checksum does not match expected"); goto done; From 9ed014775ee47e0b59ce2fc3b12a79344596f05b Mon Sep 17 00:00:00 2001 From: Parnic Date: Fri, 16 Feb 2024 09:29:21 -0600 Subject: [PATCH 018/111] Add index.skipHash test --- tests/libgit2/status/worktree.c | 9 + .../status_skipHash/.gitted/COMMIT_EDITMSG | 1 + tests/resources/status_skipHash/.gitted/HEAD | 1 + .../status_skipHash/.gitted/MERGE_RR | 0 .../resources/status_skipHash/.gitted/config | 9 + .../status_skipHash/.gitted/description | 1 + .../.gitted/hooks/applypatch-msg.sample | 15 ++ .../.gitted/hooks/commit-msg.sample | 24 +++ .../.gitted/hooks/fsmonitor-watchman.sample | 174 ++++++++++++++++++ .../.gitted/hooks/post-update.sample | 8 + .../.gitted/hooks/pre-applypatch.sample | 14 ++ .../.gitted/hooks/pre-commit.sample | 49 +++++ .../.gitted/hooks/pre-merge-commit.sample | 13 ++ .../.gitted/hooks/pre-push.sample | 53 ++++++ .../.gitted/hooks/pre-rebase.sample | 169 +++++++++++++++++ .../.gitted/hooks/pre-receive.sample | 24 +++ .../.gitted/hooks/prepare-commit-msg.sample | 42 +++++ .../.gitted/hooks/push-to-checkout.sample | 78 ++++++++ .../.gitted/hooks/sendemail-validate.sample | 77 ++++++++ .../.gitted/hooks/update.sample | 128 +++++++++++++ tests/resources/status_skipHash/.gitted/index | Bin 0 -> 137 bytes .../status_skipHash/.gitted/info/exclude | 6 + .../status_skipHash/.gitted/logs/HEAD | 1 + .../.gitted/logs/refs/heads/main | 1 + .../34/f4c90b237fcb4c677772a6093f3cba239c41a5 | Bin 0 -> 510 bytes .../71/a21e67674e9717aa7380e5782ec5e070a8d7e0 | Bin 0 -> 53 bytes .../d7/c1f165e51adbbfd7760162b7a5802d4117740c | Bin 0 -> 26 bytes .../status_skipHash/.gitted/refs/heads/main | 1 + tests/resources/status_skipHash/new_file | 1 + 29 files changed, 899 insertions(+) create mode 100644 tests/resources/status_skipHash/.gitted/COMMIT_EDITMSG create mode 100644 tests/resources/status_skipHash/.gitted/HEAD create mode 100644 tests/resources/status_skipHash/.gitted/MERGE_RR create mode 100644 tests/resources/status_skipHash/.gitted/config create mode 100644 tests/resources/status_skipHash/.gitted/description create mode 100644 tests/resources/status_skipHash/.gitted/hooks/applypatch-msg.sample create mode 100644 tests/resources/status_skipHash/.gitted/hooks/commit-msg.sample create mode 100644 tests/resources/status_skipHash/.gitted/hooks/fsmonitor-watchman.sample create mode 100644 tests/resources/status_skipHash/.gitted/hooks/post-update.sample create mode 100644 tests/resources/status_skipHash/.gitted/hooks/pre-applypatch.sample create mode 100644 tests/resources/status_skipHash/.gitted/hooks/pre-commit.sample create mode 100644 tests/resources/status_skipHash/.gitted/hooks/pre-merge-commit.sample create mode 100644 tests/resources/status_skipHash/.gitted/hooks/pre-push.sample create mode 100644 tests/resources/status_skipHash/.gitted/hooks/pre-rebase.sample create mode 100644 tests/resources/status_skipHash/.gitted/hooks/pre-receive.sample create mode 100644 tests/resources/status_skipHash/.gitted/hooks/prepare-commit-msg.sample create mode 100644 tests/resources/status_skipHash/.gitted/hooks/push-to-checkout.sample create mode 100644 tests/resources/status_skipHash/.gitted/hooks/sendemail-validate.sample create mode 100644 tests/resources/status_skipHash/.gitted/hooks/update.sample create mode 100644 tests/resources/status_skipHash/.gitted/index create mode 100644 tests/resources/status_skipHash/.gitted/info/exclude create mode 100644 tests/resources/status_skipHash/.gitted/logs/HEAD create mode 100644 tests/resources/status_skipHash/.gitted/logs/refs/heads/main create mode 100644 tests/resources/status_skipHash/.gitted/objects/34/f4c90b237fcb4c677772a6093f3cba239c41a5 create mode 100644 tests/resources/status_skipHash/.gitted/objects/71/a21e67674e9717aa7380e5782ec5e070a8d7e0 create mode 100644 tests/resources/status_skipHash/.gitted/objects/d7/c1f165e51adbbfd7760162b7a5802d4117740c create mode 100644 tests/resources/status_skipHash/.gitted/refs/heads/main create mode 100644 tests/resources/status_skipHash/new_file diff --git a/tests/libgit2/status/worktree.c b/tests/libgit2/status/worktree.c index efbf597a723..bd3cdb9e7d3 100644 --- a/tests/libgit2/status/worktree.c +++ b/tests/libgit2/status/worktree.c @@ -1360,3 +1360,12 @@ void test_status_worktree__at_head_parent(void) git_tree_free(parent_tree); git_status_list_free(statuslist); } + +void test_status_worktree__skip_hash(void) +{ + git_repository *repo = cl_git_sandbox_init("status_skipHash"); + git_index *index; + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_read(index, true)); +} diff --git a/tests/resources/status_skipHash/.gitted/COMMIT_EDITMSG b/tests/resources/status_skipHash/.gitted/COMMIT_EDITMSG new file mode 100644 index 00000000000..ea450f959b9 --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/COMMIT_EDITMSG @@ -0,0 +1 @@ +New file diff --git a/tests/resources/status_skipHash/.gitted/HEAD b/tests/resources/status_skipHash/.gitted/HEAD new file mode 100644 index 00000000000..b870d82622c --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/tests/resources/status_skipHash/.gitted/MERGE_RR b/tests/resources/status_skipHash/.gitted/MERGE_RR new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/resources/status_skipHash/.gitted/config b/tests/resources/status_skipHash/.gitted/config new file mode 100644 index 00000000000..16aebb686c2 --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/config @@ -0,0 +1,9 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true +[index] + skipHash = true diff --git a/tests/resources/status_skipHash/.gitted/description b/tests/resources/status_skipHash/.gitted/description new file mode 100644 index 00000000000..498b267a8c7 --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/resources/status_skipHash/.gitted/hooks/applypatch-msg.sample b/tests/resources/status_skipHash/.gitted/hooks/applypatch-msg.sample new file mode 100644 index 00000000000..a5d7b84a673 --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/tests/resources/status_skipHash/.gitted/hooks/commit-msg.sample b/tests/resources/status_skipHash/.gitted/hooks/commit-msg.sample new file mode 100644 index 00000000000..b58d1184a9d --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/tests/resources/status_skipHash/.gitted/hooks/fsmonitor-watchman.sample b/tests/resources/status_skipHash/.gitted/hooks/fsmonitor-watchman.sample new file mode 100644 index 00000000000..23e856f5dee --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/hooks/fsmonitor-watchman.sample @@ -0,0 +1,174 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use IPC::Open2; + +# An example hook script to integrate Watchman +# (https://facebook.github.io/watchman/) with git to speed up detecting +# new and modified files. +# +# The hook is passed a version (currently 2) and last update token +# formatted as a string and outputs to stdout a new update token and +# all files that have been modified since the update token. Paths must +# be relative to the root of the working tree and separated by a single NUL. +# +# To enable this hook, rename this file to "query-watchman" and set +# 'git config core.fsmonitor .git/hooks/query-watchman' +# +my ($version, $last_update_token) = @ARGV; + +# Uncomment for debugging +# print STDERR "$0 $version $last_update_token\n"; + +# Check the hook interface version +if ($version ne 2) { + die "Unsupported query-fsmonitor hook version '$version'.\n" . + "Falling back to scanning...\n"; +} + +my $git_work_tree = get_working_dir(); + +my $retry = 1; + +my $json_pkg; +eval { + require JSON::XS; + $json_pkg = "JSON::XS"; + 1; +} or do { + require JSON::PP; + $json_pkg = "JSON::PP"; +}; + +launch_watchman(); + +sub launch_watchman { + my $o = watchman_query(); + if (is_work_tree_watched($o)) { + output_result($o->{clock}, @{$o->{files}}); + } +} + +sub output_result { + my ($clockid, @files) = @_; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # binmode $fh, ":utf8"; + # print $fh "$clockid\n@files\n"; + # close $fh; + + binmode STDOUT, ":utf8"; + print $clockid; + print "\0"; + local $, = "\0"; + print @files; +} + +sub watchman_clock { + my $response = qx/watchman clock "$git_work_tree"/; + die "Failed to get clock id on '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + + return $json_pkg->new->utf8->decode($response); +} + +sub watchman_query { + my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') + or die "open2() failed: $!\n" . + "Falling back to scanning...\n"; + + # In the query expression below we're asking for names of files that + # changed since $last_update_token but not from the .git folder. + # + # To accomplish this, we're using the "since" generator to use the + # recency index to select candidate nodes and "fields" to limit the + # output to file names only. Then we're using the "expression" term to + # further constrain the results. + my $last_update_line = ""; + if (substr($last_update_token, 0, 1) eq "c") { + $last_update_token = "\"$last_update_token\""; + $last_update_line = qq[\n"since": $last_update_token,]; + } + my $query = <<" END"; + ["query", "$git_work_tree", {$last_update_line + "fields": ["name"], + "expression": ["not", ["dirname", ".git"]] + }] + END + + # Uncomment for debugging the watchman query + # open (my $fh, ">", ".git/watchman-query.json"); + # print $fh $query; + # close $fh; + + print CHLD_IN $query; + close CHLD_IN; + my $response = do {local $/; }; + + # Uncomment for debugging the watch response + # open ($fh, ">", ".git/watchman-response.json"); + # print $fh $response; + # close $fh; + + die "Watchman: command returned no output.\n" . + "Falling back to scanning...\n" if $response eq ""; + die "Watchman: command returned invalid output: $response\n" . + "Falling back to scanning...\n" unless $response =~ /^\{/; + + return $json_pkg->new->utf8->decode($response); +} + +sub is_work_tree_watched { + my ($output) = @_; + my $error = $output->{error}; + if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { + $retry--; + my $response = qx/watchman watch "$git_work_tree"/; + die "Failed to make watchman watch '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + $output = $json_pkg->new->utf8->decode($response); + $error = $output->{error}; + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # close $fh; + + # Watchman will always return all files on the first query so + # return the fast "everything is dirty" flag to git and do the + # Watchman query just to get it over with now so we won't pay + # the cost in git to look up each individual file. + my $o = watchman_clock(); + $error = $output->{error}; + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + output_result($o->{clock}, ("/")); + $last_update_token = $o->{clock}; + + eval { launch_watchman() }; + return 0; + } + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + return 1; +} + +sub get_working_dir { + my $working_dir; + if ($^O =~ 'msys' || $^O =~ 'cygwin') { + $working_dir = Win32::GetCwd(); + $working_dir =~ tr/\\/\//; + } else { + require Cwd; + $working_dir = Cwd::cwd(); + } + + return $working_dir; +} diff --git a/tests/resources/status_skipHash/.gitted/hooks/post-update.sample b/tests/resources/status_skipHash/.gitted/hooks/post-update.sample new file mode 100644 index 00000000000..ec17ec1939b --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/tests/resources/status_skipHash/.gitted/hooks/pre-applypatch.sample b/tests/resources/status_skipHash/.gitted/hooks/pre-applypatch.sample new file mode 100644 index 00000000000..4142082bcb9 --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/tests/resources/status_skipHash/.gitted/hooks/pre-commit.sample b/tests/resources/status_skipHash/.gitted/hooks/pre-commit.sample new file mode 100644 index 00000000000..e144712c85c --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=$(git hash-object -t tree /dev/null) +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --type=bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/tests/resources/status_skipHash/.gitted/hooks/pre-merge-commit.sample b/tests/resources/status_skipHash/.gitted/hooks/pre-merge-commit.sample new file mode 100644 index 00000000000..399eab1924e --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/hooks/pre-merge-commit.sample @@ -0,0 +1,13 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git merge" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message to +# stderr if it wants to stop the merge commit. +# +# To enable this hook, rename this file to "pre-merge-commit". + +. git-sh-setup +test -x "$GIT_DIR/hooks/pre-commit" && + exec "$GIT_DIR/hooks/pre-commit" +: diff --git a/tests/resources/status_skipHash/.gitted/hooks/pre-push.sample b/tests/resources/status_skipHash/.gitted/hooks/pre-push.sample new file mode 100644 index 00000000000..4ce688d32b7 --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/tests/resources/status_skipHash/.gitted/hooks/pre-rebase.sample b/tests/resources/status_skipHash/.gitted/hooks/pre-rebase.sample new file mode 100644 index 00000000000..6cbef5c370d --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up to date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +<<\DOC_END + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/tests/resources/status_skipHash/.gitted/hooks/pre-receive.sample b/tests/resources/status_skipHash/.gitted/hooks/pre-receive.sample new file mode 100644 index 00000000000..a1fd29ec148 --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/hooks/pre-receive.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to make use of push options. +# The example simply echoes all push options that start with 'echoback=' +# and rejects all pushes when the "reject" push option is used. +# +# To enable this hook, rename this file to "pre-receive". + +if test -n "$GIT_PUSH_OPTION_COUNT" +then + i=0 + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" + do + eval "value=\$GIT_PUSH_OPTION_$i" + case "$value" in + echoback=*) + echo "echo from the pre-receive-hook: ${value#*=}" >&2 + ;; + reject) + exit 1 + esac + i=$((i + 1)) + done +fi diff --git a/tests/resources/status_skipHash/.gitted/hooks/prepare-commit-msg.sample b/tests/resources/status_skipHash/.gitted/hooks/prepare-commit-msg.sample new file mode 100644 index 00000000000..10fa14c5ab0 --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/hooks/prepare-commit-msg.sample @@ -0,0 +1,42 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first one removes the +# "# Please enter the commit message..." help message. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +COMMIT_MSG_FILE=$1 +COMMIT_SOURCE=$2 +SHA1=$3 + +/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" + +# case "$COMMIT_SOURCE,$SHA1" in +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; +# *) ;; +# esac + +# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" +# if test -z "$COMMIT_SOURCE" +# then +# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" +# fi diff --git a/tests/resources/status_skipHash/.gitted/hooks/push-to-checkout.sample b/tests/resources/status_skipHash/.gitted/hooks/push-to-checkout.sample new file mode 100644 index 00000000000..af5a0c0018b --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/hooks/push-to-checkout.sample @@ -0,0 +1,78 @@ +#!/bin/sh + +# An example hook script to update a checked-out tree on a git push. +# +# This hook is invoked by git-receive-pack(1) when it reacts to git +# push and updates reference(s) in its repository, and when the push +# tries to update the branch that is currently checked out and the +# receive.denyCurrentBranch configuration variable is set to +# updateInstead. +# +# By default, such a push is refused if the working tree and the index +# of the remote repository has any difference from the currently +# checked out commit; when both the working tree and the index match +# the current commit, they are updated to match the newly pushed tip +# of the branch. This hook is to be used to override the default +# behaviour; however the code below reimplements the default behaviour +# as a starting point for convenient modification. +# +# The hook receives the commit with which the tip of the current +# branch is going to be updated: +commit=$1 + +# It can exit with a non-zero status to refuse the push (when it does +# so, it must not modify the index or the working tree). +die () { + echo >&2 "$*" + exit 1 +} + +# Or it can make any necessary changes to the working tree and to the +# index to bring them to the desired state when the tip of the current +# branch is updated to the new commit, and exit with a zero status. +# +# For example, the hook can simply run git read-tree -u -m HEAD "$1" +# in order to emulate git fetch that is run in the reverse direction +# with git push, as the two-tree form of git read-tree -u -m is +# essentially the same as git switch or git checkout that switches +# branches while keeping the local changes in the working tree that do +# not interfere with the difference between the branches. + +# The below is a more-or-less exact translation to shell of the C code +# for the default behaviour for git's push-to-checkout hook defined in +# the push_to_deploy() function in builtin/receive-pack.c. +# +# Note that the hook will be executed from the repository directory, +# not from the working tree, so if you want to perform operations on +# the working tree, you will have to adapt your code accordingly, e.g. +# by adding "cd .." or using relative paths. + +if ! git update-index -q --ignore-submodules --refresh +then + die "Up-to-date check failed" +fi + +if ! git diff-files --quiet --ignore-submodules -- +then + die "Working directory has unstaged changes" +fi + +# This is a rough translation of: +# +# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX +if git cat-file -e HEAD 2>/dev/null +then + head=HEAD +else + head=$(git hash-object -t tree --stdin &2 + exit 1 +} + +unset GIT_DIR GIT_WORK_TREE +cd "$worktree" && + +if grep -q "^diff --git " "$1" +then + validate_patch "$1" +else + validate_cover_letter "$1" +fi && + +if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL" +then + git config --unset-all sendemail.validateWorktree && + trap 'git worktree remove -ff "$worktree"' EXIT && + validate_series +fi diff --git a/tests/resources/status_skipHash/.gitted/hooks/update.sample b/tests/resources/status_skipHash/.gitted/hooks/update.sample new file mode 100644 index 00000000000..c4d426bc6ee --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to block unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --type=bool hooks.allowunannotated) +allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) +denycreatebranch=$(git config --type=bool hooks.denycreatebranch) +allowdeletetag=$(git config --type=bool hooks.allowdeletetag) +allowmodifytag=$(git config --type=bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero=$(git hash-object --stdin &2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/tests/resources/status_skipHash/.gitted/index b/tests/resources/status_skipHash/.gitted/index new file mode 100644 index 0000000000000000000000000000000000000000..1963fe0d3d48be4a53099966cbd14d342421a38a GIT binary patch literal 137 zcmZ?q402{*U|<4b#?Ef%38=h9^9eq%+;`#$5!{AH+ D>}4ak literal 0 HcmV?d00001 diff --git a/tests/resources/status_skipHash/.gitted/info/exclude b/tests/resources/status_skipHash/.gitted/info/exclude new file mode 100644 index 00000000000..a5196d1be8f --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/resources/status_skipHash/.gitted/logs/HEAD b/tests/resources/status_skipHash/.gitted/logs/HEAD new file mode 100644 index 00000000000..35e1a747dbe --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 34f4c90b237fcb4c677772a6093f3cba239c41a5 Parnic 1708097798 -0600 commit (initial): New file diff --git a/tests/resources/status_skipHash/.gitted/logs/refs/heads/main b/tests/resources/status_skipHash/.gitted/logs/refs/heads/main new file mode 100644 index 00000000000..35e1a747dbe --- /dev/null +++ b/tests/resources/status_skipHash/.gitted/logs/refs/heads/main @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 34f4c90b237fcb4c677772a6093f3cba239c41a5 Parnic 1708097798 -0600 commit (initial): New file diff --git a/tests/resources/status_skipHash/.gitted/objects/34/f4c90b237fcb4c677772a6093f3cba239c41a5 b/tests/resources/status_skipHash/.gitted/objects/34/f4c90b237fcb4c677772a6093f3cba239c41a5 new file mode 100644 index 0000000000000000000000000000000000000000..0513158cb21d4d3d77b30a4b5d80b8a70ec19815 GIT binary patch literal 510 zcmVF?TCrKxLpRo64Xg@&*}LnPe%?Rrx4dnBKoKJasTivY{EB3Rzz6-<8uosDLv3>U+IYv{No1t@=50=A(I9*4rMPHr0CYysM zBqAvR?045{MMp=l^}1vOV!>@84W3Zloph=PFU#%2^N9@_QUxS887mGu{rKo5TeSnj>BNUy684J0e*G-`Bg-LHWExDb$c2APdZYecO%TXM=DmWF&l$rj$z8I!|NtQK$ z+kUaG)XSopSp0lgbbWZ<38G_;#tZjHWn;(g95h8(yI8roj(1ljVIGZBTrKl+$Rq%* zhUPmbY*%sLAD^``KE}$-?}<0b^NZ!S+I~d*jSl@P>7v!jGxomMP7fxC`LsOlZvb-s zXlv)X@Io9LeI14AbqaZON2ag!!P66fPwBWlf|Fsj4~n1v@m*8e4Do Am;e9( literal 0 HcmV?d00001 diff --git a/tests/resources/status_skipHash/.gitted/objects/71/a21e67674e9717aa7380e5782ec5e070a8d7e0 b/tests/resources/status_skipHash/.gitted/objects/71/a21e67674e9717aa7380e5782ec5e070a8d7e0 new file mode 100644 index 0000000000000000000000000000000000000000..7c48fa4f43b779b3bbbc8a8c39fa596877c1e24f GIT binary patch literal 53 zcmV-50LuS(0V^p=O;s>9V=y!@Ff%bx$V)AcPs_|nWw?IuW9n0>+xxGVF(z$a+Mw$w LUcv(aTUZe49NQLZ literal 0 HcmV?d00001 diff --git a/tests/resources/status_skipHash/.gitted/objects/d7/c1f165e51adbbfd7760162b7a5802d4117740c b/tests/resources/status_skipHash/.gitted/objects/d7/c1f165e51adbbfd7760162b7a5802d4117740c new file mode 100644 index 0000000000000000000000000000000000000000..c685321ce69cedebf8744674d239c0e493b89cbb GIT binary patch literal 26 icmb Date: Fri, 16 Feb 2024 09:40:29 -0600 Subject: [PATCH 019/111] Remove unnecessary files --- .../.gitted/hooks/applypatch-msg.sample | 15 -- .../.gitted/hooks/commit-msg.sample | 24 --- .../.gitted/hooks/fsmonitor-watchman.sample | 174 ------------------ .../.gitted/hooks/post-update.sample | 8 - .../.gitted/hooks/pre-applypatch.sample | 14 -- .../.gitted/hooks/pre-commit.sample | 49 ----- .../.gitted/hooks/pre-merge-commit.sample | 13 -- .../.gitted/hooks/pre-push.sample | 53 ------ .../.gitted/hooks/pre-rebase.sample | 169 ----------------- .../.gitted/hooks/pre-receive.sample | 24 --- .../.gitted/hooks/prepare-commit-msg.sample | 42 ----- .../.gitted/hooks/push-to-checkout.sample | 78 -------- .../.gitted/hooks/sendemail-validate.sample | 77 -------- .../.gitted/hooks/update.sample | 128 ------------- 14 files changed, 868 deletions(-) delete mode 100644 tests/resources/status_skipHash/.gitted/hooks/applypatch-msg.sample delete mode 100644 tests/resources/status_skipHash/.gitted/hooks/commit-msg.sample delete mode 100644 tests/resources/status_skipHash/.gitted/hooks/fsmonitor-watchman.sample delete mode 100644 tests/resources/status_skipHash/.gitted/hooks/post-update.sample delete mode 100644 tests/resources/status_skipHash/.gitted/hooks/pre-applypatch.sample delete mode 100644 tests/resources/status_skipHash/.gitted/hooks/pre-commit.sample delete mode 100644 tests/resources/status_skipHash/.gitted/hooks/pre-merge-commit.sample delete mode 100644 tests/resources/status_skipHash/.gitted/hooks/pre-push.sample delete mode 100644 tests/resources/status_skipHash/.gitted/hooks/pre-rebase.sample delete mode 100644 tests/resources/status_skipHash/.gitted/hooks/pre-receive.sample delete mode 100644 tests/resources/status_skipHash/.gitted/hooks/prepare-commit-msg.sample delete mode 100644 tests/resources/status_skipHash/.gitted/hooks/push-to-checkout.sample delete mode 100644 tests/resources/status_skipHash/.gitted/hooks/sendemail-validate.sample delete mode 100644 tests/resources/status_skipHash/.gitted/hooks/update.sample diff --git a/tests/resources/status_skipHash/.gitted/hooks/applypatch-msg.sample b/tests/resources/status_skipHash/.gitted/hooks/applypatch-msg.sample deleted file mode 100644 index a5d7b84a673..00000000000 --- a/tests/resources/status_skipHash/.gitted/hooks/applypatch-msg.sample +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -# -# An example hook script to check the commit log message taken by -# applypatch from an e-mail message. -# -# The hook should exit with non-zero status after issuing an -# appropriate message if it wants to stop the commit. The hook is -# allowed to edit the commit message file. -# -# To enable this hook, rename this file to "applypatch-msg". - -. git-sh-setup -commitmsg="$(git rev-parse --git-path hooks/commit-msg)" -test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} -: diff --git a/tests/resources/status_skipHash/.gitted/hooks/commit-msg.sample b/tests/resources/status_skipHash/.gitted/hooks/commit-msg.sample deleted file mode 100644 index b58d1184a9d..00000000000 --- a/tests/resources/status_skipHash/.gitted/hooks/commit-msg.sample +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh -# -# An example hook script to check the commit log message. -# Called by "git commit" with one argument, the name of the file -# that has the commit message. The hook should exit with non-zero -# status after issuing an appropriate message if it wants to stop the -# commit. The hook is allowed to edit the commit message file. -# -# To enable this hook, rename this file to "commit-msg". - -# Uncomment the below to add a Signed-off-by line to the message. -# Doing this in a hook is a bad idea in general, but the prepare-commit-msg -# hook is more suited to it. -# -# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') -# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" - -# This example catches duplicate Signed-off-by lines. - -test "" = "$(grep '^Signed-off-by: ' "$1" | - sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { - echo >&2 Duplicate Signed-off-by lines. - exit 1 -} diff --git a/tests/resources/status_skipHash/.gitted/hooks/fsmonitor-watchman.sample b/tests/resources/status_skipHash/.gitted/hooks/fsmonitor-watchman.sample deleted file mode 100644 index 23e856f5dee..00000000000 --- a/tests/resources/status_skipHash/.gitted/hooks/fsmonitor-watchman.sample +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; -use IPC::Open2; - -# An example hook script to integrate Watchman -# (https://facebook.github.io/watchman/) with git to speed up detecting -# new and modified files. -# -# The hook is passed a version (currently 2) and last update token -# formatted as a string and outputs to stdout a new update token and -# all files that have been modified since the update token. Paths must -# be relative to the root of the working tree and separated by a single NUL. -# -# To enable this hook, rename this file to "query-watchman" and set -# 'git config core.fsmonitor .git/hooks/query-watchman' -# -my ($version, $last_update_token) = @ARGV; - -# Uncomment for debugging -# print STDERR "$0 $version $last_update_token\n"; - -# Check the hook interface version -if ($version ne 2) { - die "Unsupported query-fsmonitor hook version '$version'.\n" . - "Falling back to scanning...\n"; -} - -my $git_work_tree = get_working_dir(); - -my $retry = 1; - -my $json_pkg; -eval { - require JSON::XS; - $json_pkg = "JSON::XS"; - 1; -} or do { - require JSON::PP; - $json_pkg = "JSON::PP"; -}; - -launch_watchman(); - -sub launch_watchman { - my $o = watchman_query(); - if (is_work_tree_watched($o)) { - output_result($o->{clock}, @{$o->{files}}); - } -} - -sub output_result { - my ($clockid, @files) = @_; - - # Uncomment for debugging watchman output - # open (my $fh, ">", ".git/watchman-output.out"); - # binmode $fh, ":utf8"; - # print $fh "$clockid\n@files\n"; - # close $fh; - - binmode STDOUT, ":utf8"; - print $clockid; - print "\0"; - local $, = "\0"; - print @files; -} - -sub watchman_clock { - my $response = qx/watchman clock "$git_work_tree"/; - die "Failed to get clock id on '$git_work_tree'.\n" . - "Falling back to scanning...\n" if $? != 0; - - return $json_pkg->new->utf8->decode($response); -} - -sub watchman_query { - my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') - or die "open2() failed: $!\n" . - "Falling back to scanning...\n"; - - # In the query expression below we're asking for names of files that - # changed since $last_update_token but not from the .git folder. - # - # To accomplish this, we're using the "since" generator to use the - # recency index to select candidate nodes and "fields" to limit the - # output to file names only. Then we're using the "expression" term to - # further constrain the results. - my $last_update_line = ""; - if (substr($last_update_token, 0, 1) eq "c") { - $last_update_token = "\"$last_update_token\""; - $last_update_line = qq[\n"since": $last_update_token,]; - } - my $query = <<" END"; - ["query", "$git_work_tree", {$last_update_line - "fields": ["name"], - "expression": ["not", ["dirname", ".git"]] - }] - END - - # Uncomment for debugging the watchman query - # open (my $fh, ">", ".git/watchman-query.json"); - # print $fh $query; - # close $fh; - - print CHLD_IN $query; - close CHLD_IN; - my $response = do {local $/; }; - - # Uncomment for debugging the watch response - # open ($fh, ">", ".git/watchman-response.json"); - # print $fh $response; - # close $fh; - - die "Watchman: command returned no output.\n" . - "Falling back to scanning...\n" if $response eq ""; - die "Watchman: command returned invalid output: $response\n" . - "Falling back to scanning...\n" unless $response =~ /^\{/; - - return $json_pkg->new->utf8->decode($response); -} - -sub is_work_tree_watched { - my ($output) = @_; - my $error = $output->{error}; - if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { - $retry--; - my $response = qx/watchman watch "$git_work_tree"/; - die "Failed to make watchman watch '$git_work_tree'.\n" . - "Falling back to scanning...\n" if $? != 0; - $output = $json_pkg->new->utf8->decode($response); - $error = $output->{error}; - die "Watchman: $error.\n" . - "Falling back to scanning...\n" if $error; - - # Uncomment for debugging watchman output - # open (my $fh, ">", ".git/watchman-output.out"); - # close $fh; - - # Watchman will always return all files on the first query so - # return the fast "everything is dirty" flag to git and do the - # Watchman query just to get it over with now so we won't pay - # the cost in git to look up each individual file. - my $o = watchman_clock(); - $error = $output->{error}; - - die "Watchman: $error.\n" . - "Falling back to scanning...\n" if $error; - - output_result($o->{clock}, ("/")); - $last_update_token = $o->{clock}; - - eval { launch_watchman() }; - return 0; - } - - die "Watchman: $error.\n" . - "Falling back to scanning...\n" if $error; - - return 1; -} - -sub get_working_dir { - my $working_dir; - if ($^O =~ 'msys' || $^O =~ 'cygwin') { - $working_dir = Win32::GetCwd(); - $working_dir =~ tr/\\/\//; - } else { - require Cwd; - $working_dir = Cwd::cwd(); - } - - return $working_dir; -} diff --git a/tests/resources/status_skipHash/.gitted/hooks/post-update.sample b/tests/resources/status_skipHash/.gitted/hooks/post-update.sample deleted file mode 100644 index ec17ec1939b..00000000000 --- a/tests/resources/status_skipHash/.gitted/hooks/post-update.sample +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -# -# An example hook script to prepare a packed repository for use over -# dumb transports. -# -# To enable this hook, rename this file to "post-update". - -exec git update-server-info diff --git a/tests/resources/status_skipHash/.gitted/hooks/pre-applypatch.sample b/tests/resources/status_skipHash/.gitted/hooks/pre-applypatch.sample deleted file mode 100644 index 4142082bcb9..00000000000 --- a/tests/resources/status_skipHash/.gitted/hooks/pre-applypatch.sample +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed -# by applypatch from an e-mail message. -# -# The hook should exit with non-zero status after issuing an -# appropriate message if it wants to stop the commit. -# -# To enable this hook, rename this file to "pre-applypatch". - -. git-sh-setup -precommit="$(git rev-parse --git-path hooks/pre-commit)" -test -x "$precommit" && exec "$precommit" ${1+"$@"} -: diff --git a/tests/resources/status_skipHash/.gitted/hooks/pre-commit.sample b/tests/resources/status_skipHash/.gitted/hooks/pre-commit.sample deleted file mode 100644 index e144712c85c..00000000000 --- a/tests/resources/status_skipHash/.gitted/hooks/pre-commit.sample +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed. -# Called by "git commit" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message if -# it wants to stop the commit. -# -# To enable this hook, rename this file to "pre-commit". - -if git rev-parse --verify HEAD >/dev/null 2>&1 -then - against=HEAD -else - # Initial commit: diff against an empty tree object - against=$(git hash-object -t tree /dev/null) -fi - -# If you want to allow non-ASCII filenames set this variable to true. -allownonascii=$(git config --type=bool hooks.allownonascii) - -# Redirect output to stderr. -exec 1>&2 - -# Cross platform projects tend to avoid non-ASCII filenames; prevent -# them from being added to the repository. We exploit the fact that the -# printable range starts at the space character and ends with tilde. -if [ "$allownonascii" != "true" ] && - # Note that the use of brackets around a tr range is ok here, (it's - # even required, for portability to Solaris 10's /usr/bin/tr), since - # the square bracket bytes happen to fall in the designated range. - test $(git diff --cached --name-only --diff-filter=A -z $against | - LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 -then - cat <<\EOF -Error: Attempt to add a non-ASCII file name. - -This can cause problems if you want to work with people on other platforms. - -To be portable it is advisable to rename the file. - -If you know what you are doing you can disable this check using: - - git config hooks.allownonascii true -EOF - exit 1 -fi - -# If there are whitespace errors, print the offending file names and fail. -exec git diff-index --check --cached $against -- diff --git a/tests/resources/status_skipHash/.gitted/hooks/pre-merge-commit.sample b/tests/resources/status_skipHash/.gitted/hooks/pre-merge-commit.sample deleted file mode 100644 index 399eab1924e..00000000000 --- a/tests/resources/status_skipHash/.gitted/hooks/pre-merge-commit.sample +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed. -# Called by "git merge" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message to -# stderr if it wants to stop the merge commit. -# -# To enable this hook, rename this file to "pre-merge-commit". - -. git-sh-setup -test -x "$GIT_DIR/hooks/pre-commit" && - exec "$GIT_DIR/hooks/pre-commit" -: diff --git a/tests/resources/status_skipHash/.gitted/hooks/pre-push.sample b/tests/resources/status_skipHash/.gitted/hooks/pre-push.sample deleted file mode 100644 index 4ce688d32b7..00000000000 --- a/tests/resources/status_skipHash/.gitted/hooks/pre-push.sample +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/sh - -# An example hook script to verify what is about to be pushed. Called by "git -# push" after it has checked the remote status, but before anything has been -# pushed. If this script exits with a non-zero status nothing will be pushed. -# -# This hook is called with the following parameters: -# -# $1 -- Name of the remote to which the push is being done -# $2 -- URL to which the push is being done -# -# If pushing without using a named remote those arguments will be equal. -# -# Information about the commits which are being pushed is supplied as lines to -# the standard input in the form: -# -# -# -# This sample shows how to prevent push of commits where the log message starts -# with "WIP" (work in progress). - -remote="$1" -url="$2" - -zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" - exit 1 - fi - fi -done - -exit 0 diff --git a/tests/resources/status_skipHash/.gitted/hooks/pre-rebase.sample b/tests/resources/status_skipHash/.gitted/hooks/pre-rebase.sample deleted file mode 100644 index 6cbef5c370d..00000000000 --- a/tests/resources/status_skipHash/.gitted/hooks/pre-rebase.sample +++ /dev/null @@ -1,169 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2006, 2008 Junio C Hamano -# -# The "pre-rebase" hook is run just before "git rebase" starts doing -# its job, and can prevent the command from running by exiting with -# non-zero status. -# -# The hook is called with the following parameters: -# -# $1 -- the upstream the series was forked from. -# $2 -- the branch being rebased (or empty when rebasing the current branch). -# -# This sample shows how to prevent topic branches that are already -# merged to 'next' branch from getting rebased, because allowing it -# would result in rebasing already published history. - -publish=next -basebranch="$1" -if test "$#" = 2 -then - topic="refs/heads/$2" -else - topic=`git symbolic-ref HEAD` || - exit 0 ;# we do not interrupt rebasing detached HEAD -fi - -case "$topic" in -refs/heads/??/*) - ;; -*) - exit 0 ;# we do not interrupt others. - ;; -esac - -# Now we are dealing with a topic branch being rebased -# on top of master. Is it OK to rebase it? - -# Does the topic really exist? -git show-ref -q "$topic" || { - echo >&2 "No such branch $topic" - exit 1 -} - -# Is topic fully merged to master? -not_in_master=`git rev-list --pretty=oneline ^master "$topic"` -if test -z "$not_in_master" -then - echo >&2 "$topic is fully merged to master; better remove it." - exit 1 ;# we could allow it, but there is no point. -fi - -# Is topic ever merged to next? If so you should not be rebasing it. -only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` -only_next_2=`git rev-list ^master ${publish} | sort` -if test "$only_next_1" = "$only_next_2" -then - not_in_topic=`git rev-list "^$topic" master` - if test -z "$not_in_topic" - then - echo >&2 "$topic is already up to date with master" - exit 1 ;# we could allow it, but there is no point. - else - exit 0 - fi -else - not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` - /usr/bin/perl -e ' - my $topic = $ARGV[0]; - my $msg = "* $topic has commits already merged to public branch:\n"; - my (%not_in_next) = map { - /^([0-9a-f]+) /; - ($1 => 1); - } split(/\n/, $ARGV[1]); - for my $elem (map { - /^([0-9a-f]+) (.*)$/; - [$1 => $2]; - } split(/\n/, $ARGV[2])) { - if (!exists $not_in_next{$elem->[0]}) { - if ($msg) { - print STDERR $msg; - undef $msg; - } - print STDERR " $elem->[1]\n"; - } - } - ' "$topic" "$not_in_next" "$not_in_master" - exit 1 -fi - -<<\DOC_END - -This sample hook safeguards topic branches that have been -published from being rewound. - -The workflow assumed here is: - - * Once a topic branch forks from "master", "master" is never - merged into it again (either directly or indirectly). - - * Once a topic branch is fully cooked and merged into "master", - it is deleted. If you need to build on top of it to correct - earlier mistakes, a new topic branch is created by forking at - the tip of the "master". This is not strictly necessary, but - it makes it easier to keep your history simple. - - * Whenever you need to test or publish your changes to topic - branches, merge them into "next" branch. - -The script, being an example, hardcodes the publish branch name -to be "next", but it is trivial to make it configurable via -$GIT_DIR/config mechanism. - -With this workflow, you would want to know: - -(1) ... if a topic branch has ever been merged to "next". Young - topic branches can have stupid mistakes you would rather - clean up before publishing, and things that have not been - merged into other branches can be easily rebased without - affecting other people. But once it is published, you would - not want to rewind it. - -(2) ... if a topic branch has been fully merged to "master". - Then you can delete it. More importantly, you should not - build on top of it -- other people may already want to - change things related to the topic as patches against your - "master", so if you need further changes, it is better to - fork the topic (perhaps with the same name) afresh from the - tip of "master". - -Let's look at this example: - - o---o---o---o---o---o---o---o---o---o "next" - / / / / - / a---a---b A / / - / / / / - / / c---c---c---c B / - / / / \ / - / / / b---b C \ / - / / / / \ / - ---o---o---o---o---o---o---o---o---o---o---o "master" - - -A, B and C are topic branches. - - * A has one fix since it was merged up to "next". - - * B has finished. It has been fully merged up to "master" and "next", - and is ready to be deleted. - - * C has not merged to "next" at all. - -We would want to allow C to be rebased, refuse A, and encourage -B to be deleted. - -To compute (1): - - git rev-list ^master ^topic next - git rev-list ^master next - - if these match, topic has not merged in next at all. - -To compute (2): - - git rev-list master..topic - - if this is empty, it is fully merged to "master". - -DOC_END diff --git a/tests/resources/status_skipHash/.gitted/hooks/pre-receive.sample b/tests/resources/status_skipHash/.gitted/hooks/pre-receive.sample deleted file mode 100644 index a1fd29ec148..00000000000 --- a/tests/resources/status_skipHash/.gitted/hooks/pre-receive.sample +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh -# -# An example hook script to make use of push options. -# The example simply echoes all push options that start with 'echoback=' -# and rejects all pushes when the "reject" push option is used. -# -# To enable this hook, rename this file to "pre-receive". - -if test -n "$GIT_PUSH_OPTION_COUNT" -then - i=0 - while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" - do - eval "value=\$GIT_PUSH_OPTION_$i" - case "$value" in - echoback=*) - echo "echo from the pre-receive-hook: ${value#*=}" >&2 - ;; - reject) - exit 1 - esac - i=$((i + 1)) - done -fi diff --git a/tests/resources/status_skipHash/.gitted/hooks/prepare-commit-msg.sample b/tests/resources/status_skipHash/.gitted/hooks/prepare-commit-msg.sample deleted file mode 100644 index 10fa14c5ab0..00000000000 --- a/tests/resources/status_skipHash/.gitted/hooks/prepare-commit-msg.sample +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh -# -# An example hook script to prepare the commit log message. -# Called by "git commit" with the name of the file that has the -# commit message, followed by the description of the commit -# message's source. The hook's purpose is to edit the commit -# message file. If the hook fails with a non-zero status, -# the commit is aborted. -# -# To enable this hook, rename this file to "prepare-commit-msg". - -# This hook includes three examples. The first one removes the -# "# Please enter the commit message..." help message. -# -# The second includes the output of "git diff --name-status -r" -# into the message, just before the "git status" output. It is -# commented because it doesn't cope with --amend or with squashed -# commits. -# -# The third example adds a Signed-off-by line to the message, that can -# still be edited. This is rarely a good idea. - -COMMIT_MSG_FILE=$1 -COMMIT_SOURCE=$2 -SHA1=$3 - -/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" - -# case "$COMMIT_SOURCE,$SHA1" in -# ,|template,) -# /usr/bin/perl -i.bak -pe ' -# print "\n" . `git diff --cached --name-status -r` -# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; -# *) ;; -# esac - -# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') -# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" -# if test -z "$COMMIT_SOURCE" -# then -# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" -# fi diff --git a/tests/resources/status_skipHash/.gitted/hooks/push-to-checkout.sample b/tests/resources/status_skipHash/.gitted/hooks/push-to-checkout.sample deleted file mode 100644 index af5a0c0018b..00000000000 --- a/tests/resources/status_skipHash/.gitted/hooks/push-to-checkout.sample +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/sh - -# An example hook script to update a checked-out tree on a git push. -# -# This hook is invoked by git-receive-pack(1) when it reacts to git -# push and updates reference(s) in its repository, and when the push -# tries to update the branch that is currently checked out and the -# receive.denyCurrentBranch configuration variable is set to -# updateInstead. -# -# By default, such a push is refused if the working tree and the index -# of the remote repository has any difference from the currently -# checked out commit; when both the working tree and the index match -# the current commit, they are updated to match the newly pushed tip -# of the branch. This hook is to be used to override the default -# behaviour; however the code below reimplements the default behaviour -# as a starting point for convenient modification. -# -# The hook receives the commit with which the tip of the current -# branch is going to be updated: -commit=$1 - -# It can exit with a non-zero status to refuse the push (when it does -# so, it must not modify the index or the working tree). -die () { - echo >&2 "$*" - exit 1 -} - -# Or it can make any necessary changes to the working tree and to the -# index to bring them to the desired state when the tip of the current -# branch is updated to the new commit, and exit with a zero status. -# -# For example, the hook can simply run git read-tree -u -m HEAD "$1" -# in order to emulate git fetch that is run in the reverse direction -# with git push, as the two-tree form of git read-tree -u -m is -# essentially the same as git switch or git checkout that switches -# branches while keeping the local changes in the working tree that do -# not interfere with the difference between the branches. - -# The below is a more-or-less exact translation to shell of the C code -# for the default behaviour for git's push-to-checkout hook defined in -# the push_to_deploy() function in builtin/receive-pack.c. -# -# Note that the hook will be executed from the repository directory, -# not from the working tree, so if you want to perform operations on -# the working tree, you will have to adapt your code accordingly, e.g. -# by adding "cd .." or using relative paths. - -if ! git update-index -q --ignore-submodules --refresh -then - die "Up-to-date check failed" -fi - -if ! git diff-files --quiet --ignore-submodules -- -then - die "Working directory has unstaged changes" -fi - -# This is a rough translation of: -# -# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX -if git cat-file -e HEAD 2>/dev/null -then - head=HEAD -else - head=$(git hash-object -t tree --stdin &2 - exit 1 -} - -unset GIT_DIR GIT_WORK_TREE -cd "$worktree" && - -if grep -q "^diff --git " "$1" -then - validate_patch "$1" -else - validate_cover_letter "$1" -fi && - -if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL" -then - git config --unset-all sendemail.validateWorktree && - trap 'git worktree remove -ff "$worktree"' EXIT && - validate_series -fi diff --git a/tests/resources/status_skipHash/.gitted/hooks/update.sample b/tests/resources/status_skipHash/.gitted/hooks/update.sample deleted file mode 100644 index c4d426bc6ee..00000000000 --- a/tests/resources/status_skipHash/.gitted/hooks/update.sample +++ /dev/null @@ -1,128 +0,0 @@ -#!/bin/sh -# -# An example hook script to block unannotated tags from entering. -# Called by "git receive-pack" with arguments: refname sha1-old sha1-new -# -# To enable this hook, rename this file to "update". -# -# Config -# ------ -# hooks.allowunannotated -# This boolean sets whether unannotated tags will be allowed into the -# repository. By default they won't be. -# hooks.allowdeletetag -# This boolean sets whether deleting tags will be allowed in the -# repository. By default they won't be. -# hooks.allowmodifytag -# This boolean sets whether a tag may be modified after creation. By default -# it won't be. -# hooks.allowdeletebranch -# This boolean sets whether deleting branches will be allowed in the -# repository. By default they won't be. -# hooks.denycreatebranch -# This boolean sets whether remotely creating branches will be denied -# in the repository. By default this is allowed. -# - -# --- Command line -refname="$1" -oldrev="$2" -newrev="$3" - -# --- Safety check -if [ -z "$GIT_DIR" ]; then - echo "Don't run this script from the command line." >&2 - echo " (if you want, you could supply GIT_DIR then run" >&2 - echo " $0 )" >&2 - exit 1 -fi - -if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then - echo "usage: $0 " >&2 - exit 1 -fi - -# --- Config -allowunannotated=$(git config --type=bool hooks.allowunannotated) -allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) -denycreatebranch=$(git config --type=bool hooks.denycreatebranch) -allowdeletetag=$(git config --type=bool hooks.allowdeletetag) -allowmodifytag=$(git config --type=bool hooks.allowmodifytag) - -# check for no description -projectdesc=$(sed -e '1q' "$GIT_DIR/description") -case "$projectdesc" in -"Unnamed repository"* | "") - echo "*** Project description file hasn't been set" >&2 - exit 1 - ;; -esac - -# --- Check types -# if $newrev is 0000...0000, it's a commit to delete a ref. -zero=$(git hash-object --stdin &2 - echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 - exit 1 - fi - ;; - refs/tags/*,delete) - # delete tag - if [ "$allowdeletetag" != "true" ]; then - echo "*** Deleting a tag is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/tags/*,tag) - # annotated tag - if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 - then - echo "*** Tag '$refname' already exists." >&2 - echo "*** Modifying a tag is not allowed in this repository." >&2 - exit 1 - fi - ;; - refs/heads/*,commit) - # branch - if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then - echo "*** Creating a branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/heads/*,delete) - # delete branch - if [ "$allowdeletebranch" != "true" ]; then - echo "*** Deleting a branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/remotes/*,commit) - # tracking branch - ;; - refs/remotes/*,delete) - # delete tracking branch - if [ "$allowdeletebranch" != "true" ]; then - echo "*** Deleting a tracking branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - *) - # Anything else (is there anything else?) - echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 - exit 1 - ;; -esac - -# --- Finished -exit 0 From d73078773e8f68dd7bceb70d0887846ef6a9e201 Mon Sep 17 00:00:00 2001 From: parnic Date: Sat, 17 Feb 2024 08:13:46 -0600 Subject: [PATCH 020/111] Update src/libgit2/index.c Co-authored-by: Edward Thomson --- src/libgit2/index.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libgit2/index.c b/src/libgit2/index.c index 86f14fdfc41..f4df9f64392 100644 --- a/src/libgit2/index.c +++ b/src/libgit2/index.c @@ -2850,7 +2850,8 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) /* * SHA-1 or SHA-256 (depending on the repository's object format) * over the content of the index file before this checksum. - * Note: checksum may be 0 if index.skipHash is set to true. + * Note: checksum may be 0 if the index was written by a client + * where index.skipHash was set to true. */ if (memcmp(zero_checksum, buffer, checksum_size) != 0 && memcmp(checksum, buffer, checksum_size) != 0) { error = index_error_invalid( From b69d927243e0137342fb093ec3c9203df6d201d5 Mon Sep 17 00:00:00 2001 From: parnic Date: Sat, 17 Feb 2024 08:13:54 -0600 Subject: [PATCH 021/111] Update src/libgit2/index.c Co-authored-by: Edward Thomson --- src/libgit2/index.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libgit2/index.c b/src/libgit2/index.c index f4df9f64392..670fbc5cf15 100644 --- a/src/libgit2/index.c +++ b/src/libgit2/index.c @@ -2853,7 +2853,8 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) * Note: checksum may be 0 if the index was written by a client * where index.skipHash was set to true. */ - if (memcmp(zero_checksum, buffer, checksum_size) != 0 && memcmp(checksum, buffer, checksum_size) != 0) { + if (memcmp(zero_checksum, buffer, checksum_size) != 0 && + memcmp(checksum, buffer, checksum_size) != 0) { error = index_error_invalid( "calculated checksum does not match expected"); goto done; From 6a218c2dc14f8989616b62e441ac161c820fa4f2 Mon Sep 17 00:00:00 2001 From: parnic Date: Sat, 17 Feb 2024 08:14:09 -0600 Subject: [PATCH 022/111] Update tests/libgit2/status/worktree.c Co-authored-by: Edward Thomson --- tests/libgit2/status/worktree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/libgit2/status/worktree.c b/tests/libgit2/status/worktree.c index bd3cdb9e7d3..bf99461871c 100644 --- a/tests/libgit2/status/worktree.c +++ b/tests/libgit2/status/worktree.c @@ -1363,7 +1363,7 @@ void test_status_worktree__at_head_parent(void) void test_status_worktree__skip_hash(void) { - git_repository *repo = cl_git_sandbox_init("status_skipHash"); + git_repository *repo = cl_git_sandbox_init("status_skiphash"); git_index *index; cl_git_pass(git_repository_index(&index, repo)); From a0dbf6044785a914b0c2c1b4f6601b54a153374e Mon Sep 17 00:00:00 2001 From: Parnic Date: Sat, 17 Feb 2024 08:15:42 -0600 Subject: [PATCH 023/111] Change capitalization per PR feedback --- .../.gitted/COMMIT_EDITMSG | 0 .../.gitted/HEAD | 0 .../.gitted/MERGE_RR | 0 .../.gitted/config | 0 .../.gitted/description | 0 .../.gitted/index | Bin .../.gitted/info/exclude | 0 .../.gitted/logs/HEAD | 0 .../.gitted/logs/refs/heads/main | 0 .../34/f4c90b237fcb4c677772a6093f3cba239c41a5 | Bin .../71/a21e67674e9717aa7380e5782ec5e070a8d7e0 | Bin .../d7/c1f165e51adbbfd7760162b7a5802d4117740c | Bin .../.gitted/refs/heads/main | 0 .../{status_skipHash => status_skiphash}/new_file | 0 14 files changed, 0 insertions(+), 0 deletions(-) rename tests/resources/{status_skipHash => status_skiphash}/.gitted/COMMIT_EDITMSG (100%) rename tests/resources/{status_skipHash => status_skiphash}/.gitted/HEAD (100%) rename tests/resources/{status_skipHash => status_skiphash}/.gitted/MERGE_RR (100%) rename tests/resources/{status_skipHash => status_skiphash}/.gitted/config (100%) rename tests/resources/{status_skipHash => status_skiphash}/.gitted/description (100%) rename tests/resources/{status_skipHash => status_skiphash}/.gitted/index (100%) rename tests/resources/{status_skipHash => status_skiphash}/.gitted/info/exclude (100%) rename tests/resources/{status_skipHash => status_skiphash}/.gitted/logs/HEAD (100%) rename tests/resources/{status_skipHash => status_skiphash}/.gitted/logs/refs/heads/main (100%) rename tests/resources/{status_skipHash => status_skiphash}/.gitted/objects/34/f4c90b237fcb4c677772a6093f3cba239c41a5 (100%) rename tests/resources/{status_skipHash => status_skiphash}/.gitted/objects/71/a21e67674e9717aa7380e5782ec5e070a8d7e0 (100%) rename tests/resources/{status_skipHash => status_skiphash}/.gitted/objects/d7/c1f165e51adbbfd7760162b7a5802d4117740c (100%) rename tests/resources/{status_skipHash => status_skiphash}/.gitted/refs/heads/main (100%) rename tests/resources/{status_skipHash => status_skiphash}/new_file (100%) diff --git a/tests/resources/status_skipHash/.gitted/COMMIT_EDITMSG b/tests/resources/status_skiphash/.gitted/COMMIT_EDITMSG similarity index 100% rename from tests/resources/status_skipHash/.gitted/COMMIT_EDITMSG rename to tests/resources/status_skiphash/.gitted/COMMIT_EDITMSG diff --git a/tests/resources/status_skipHash/.gitted/HEAD b/tests/resources/status_skiphash/.gitted/HEAD similarity index 100% rename from tests/resources/status_skipHash/.gitted/HEAD rename to tests/resources/status_skiphash/.gitted/HEAD diff --git a/tests/resources/status_skipHash/.gitted/MERGE_RR b/tests/resources/status_skiphash/.gitted/MERGE_RR similarity index 100% rename from tests/resources/status_skipHash/.gitted/MERGE_RR rename to tests/resources/status_skiphash/.gitted/MERGE_RR diff --git a/tests/resources/status_skipHash/.gitted/config b/tests/resources/status_skiphash/.gitted/config similarity index 100% rename from tests/resources/status_skipHash/.gitted/config rename to tests/resources/status_skiphash/.gitted/config diff --git a/tests/resources/status_skipHash/.gitted/description b/tests/resources/status_skiphash/.gitted/description similarity index 100% rename from tests/resources/status_skipHash/.gitted/description rename to tests/resources/status_skiphash/.gitted/description diff --git a/tests/resources/status_skipHash/.gitted/index b/tests/resources/status_skiphash/.gitted/index similarity index 100% rename from tests/resources/status_skipHash/.gitted/index rename to tests/resources/status_skiphash/.gitted/index diff --git a/tests/resources/status_skipHash/.gitted/info/exclude b/tests/resources/status_skiphash/.gitted/info/exclude similarity index 100% rename from tests/resources/status_skipHash/.gitted/info/exclude rename to tests/resources/status_skiphash/.gitted/info/exclude diff --git a/tests/resources/status_skipHash/.gitted/logs/HEAD b/tests/resources/status_skiphash/.gitted/logs/HEAD similarity index 100% rename from tests/resources/status_skipHash/.gitted/logs/HEAD rename to tests/resources/status_skiphash/.gitted/logs/HEAD diff --git a/tests/resources/status_skipHash/.gitted/logs/refs/heads/main b/tests/resources/status_skiphash/.gitted/logs/refs/heads/main similarity index 100% rename from tests/resources/status_skipHash/.gitted/logs/refs/heads/main rename to tests/resources/status_skiphash/.gitted/logs/refs/heads/main diff --git a/tests/resources/status_skipHash/.gitted/objects/34/f4c90b237fcb4c677772a6093f3cba239c41a5 b/tests/resources/status_skiphash/.gitted/objects/34/f4c90b237fcb4c677772a6093f3cba239c41a5 similarity index 100% rename from tests/resources/status_skipHash/.gitted/objects/34/f4c90b237fcb4c677772a6093f3cba239c41a5 rename to tests/resources/status_skiphash/.gitted/objects/34/f4c90b237fcb4c677772a6093f3cba239c41a5 diff --git a/tests/resources/status_skipHash/.gitted/objects/71/a21e67674e9717aa7380e5782ec5e070a8d7e0 b/tests/resources/status_skiphash/.gitted/objects/71/a21e67674e9717aa7380e5782ec5e070a8d7e0 similarity index 100% rename from tests/resources/status_skipHash/.gitted/objects/71/a21e67674e9717aa7380e5782ec5e070a8d7e0 rename to tests/resources/status_skiphash/.gitted/objects/71/a21e67674e9717aa7380e5782ec5e070a8d7e0 diff --git a/tests/resources/status_skipHash/.gitted/objects/d7/c1f165e51adbbfd7760162b7a5802d4117740c b/tests/resources/status_skiphash/.gitted/objects/d7/c1f165e51adbbfd7760162b7a5802d4117740c similarity index 100% rename from tests/resources/status_skipHash/.gitted/objects/d7/c1f165e51adbbfd7760162b7a5802d4117740c rename to tests/resources/status_skiphash/.gitted/objects/d7/c1f165e51adbbfd7760162b7a5802d4117740c diff --git a/tests/resources/status_skipHash/.gitted/refs/heads/main b/tests/resources/status_skiphash/.gitted/refs/heads/main similarity index 100% rename from tests/resources/status_skipHash/.gitted/refs/heads/main rename to tests/resources/status_skiphash/.gitted/refs/heads/main diff --git a/tests/resources/status_skipHash/new_file b/tests/resources/status_skiphash/new_file similarity index 100% rename from tests/resources/status_skipHash/new_file rename to tests/resources/status_skiphash/new_file From e696528aa849a3911b756b0e5d32abaca5370358 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 17 Feb 2024 14:27:19 +0000 Subject: [PATCH 024/111] Update cmake/SelectHTTPSBackend.cmake --- cmake/SelectHTTPSBackend.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/SelectHTTPSBackend.cmake b/cmake/SelectHTTPSBackend.cmake index fbc2adc147f..d293001f567 100644 --- a/cmake/SelectHTTPSBackend.cmake +++ b/cmake/SelectHTTPSBackend.cmake @@ -55,6 +55,7 @@ if(USE_HTTPS) set(GIT_OPENSSL 1) list(APPEND LIBGIT2_SYSTEM_INCLUDES ${OPENSSL_INCLUDE_DIR}) list(APPEND LIBGIT2_SYSTEM_LIBS ${OPENSSL_LIBRARIES}) + # Static OpenSSL (lib crypto.a) requires libdl, include it explicitly if(LINK_WITH_STATIC_LIBRARIES STREQUAL ON) list(APPEND LIBGIT2_SYSTEM_LIBS ${CMAKE_DL_LIBS}) endif() From 8422a6386ea7458799a080ce073b530b663aa0cc Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 17 Feb 2024 15:07:53 +0000 Subject: [PATCH 025/111] config: avoid unnecessary copy in rename_section We can just append the escaped regex to the value buffer, we don't need create a new value buffer and then append _that_. --- src/libgit2/config.c | 42 +++++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index d11e76d9577..04f3ec2fee2 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -1509,35 +1509,31 @@ static int rename_config_entries_cb( int error = 0; struct rename_data *data = (struct rename_data *)payload; size_t base_len = git_str_len(data->name); - git_str raw_value = GIT_STR_INIT, value = GIT_STR_INIT; - - if (base_len > 0 && - !(error = git_str_puts(data->name, entry->name + data->old_len))) - { - error = git_config_set_multivar( - data->config, git_str_cstr(data->name), "^$", - entry->value); + git_str value = GIT_STR_INIT; + + if (base_len > 0) { + if ((error = git_str_puts(data->name, + entry->name + data->old_len)) < 0 || + (error = git_config_set_multivar( + data->config, git_str_cstr(data->name), "^$", + entry->value)) < 0) + goto cleanup; } - if (!error) { - if ((error = git_str_puts_escape_regex( - &raw_value, entry->value))) - goto cleanup; - git_str_grow(&value, git_str_len(&raw_value) + 2); - git_str_putc(&value, '^'); - git_str_puts(&value, git_str_cstr(&raw_value)); - git_str_putc(&value, '$'); - if (git_str_oom(&value)) { - error = -1; - goto cleanup; - } - error = git_config_delete_multivar( - data->config, entry->name, git_str_cstr(&value)); + git_str_putc(&value, '^'); + git_str_puts_escape_regex(&value, entry->value); + git_str_putc(&value, '$'); + + if (git_str_oom(&value)) { + error = -1; + goto cleanup; } + error = git_config_delete_multivar( + data->config, entry->name, git_str_cstr(&value)); + cleanup: git_str_truncate(data->name, base_len); - git_str_dispose(&raw_value); git_str_dispose(&value); return error; } From 913d4ea251366f006a5ced6ed1804acf973a6ce6 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 17 Feb 2024 15:20:12 +0000 Subject: [PATCH 026/111] skiphash: free the index --- tests/libgit2/status/worktree.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/libgit2/status/worktree.c b/tests/libgit2/status/worktree.c index bf99461871c..8a2ea9cb674 100644 --- a/tests/libgit2/status/worktree.c +++ b/tests/libgit2/status/worktree.c @@ -1368,4 +1368,5 @@ void test_status_worktree__skip_hash(void) cl_git_pass(git_repository_index(&index, repo)); cl_git_pass(git_index_read(index, true)); + git_index_free(index); } From 928ac176f4949481b9eea4a172af5bc4a2be2779 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 18 Feb 2024 00:55:46 +0000 Subject: [PATCH 027/111] meta: add dependency tag to release.yml --- .github/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/release.yml b/.github/release.yml index 099e3803fa6..4d4e31860c2 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -27,6 +27,9 @@ changelog: - title: Git compatibility fixes labels: - git compatibility + - title: Dependency updates + labels: + - dependency - title: Other changes labels: - '*' From 49487b5e39a4895085450897e9335f60c252b811 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 19 Feb 2024 08:38:34 -0800 Subject: [PATCH 028/111] clar: canonicalize temp sandbox directory everywhere We currently only canonicalize the temp sandbox directory on macOS, which is critical since `/tmp` is really `/private/tmp`. However, we should do it everywhere, so that tests can actually expect a consistent outcome by looking at `clar_sandbox_path()`. --- tests/clar/clar/sandbox.h | 45 ++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/tests/clar/clar/sandbox.h b/tests/clar/clar/sandbox.h index 0ba1479620a..0688374f8d6 100644 --- a/tests/clar/clar/sandbox.h +++ b/tests/clar/clar/sandbox.h @@ -2,7 +2,8 @@ #include #endif -static char _clar_path[4096 + 1]; +#define CLAR_PATH_MAX 4096 +static char _clar_path[CLAR_PATH_MAX]; static int is_valid_tmp_path(const char *path) @@ -35,10 +36,9 @@ find_tmp_path(char *buffer, size_t length) continue; if (is_valid_tmp_path(env)) { -#ifdef __APPLE__ - if (length >= PATH_MAX && realpath(env, buffer) != NULL) - return 0; -#endif + if (strlen(env) + 1 > CLAR_PATH_MAX) + return -1; + strncpy(buffer, env, length - 1); buffer[length - 1] = '\0'; return 0; @@ -47,10 +47,6 @@ find_tmp_path(char *buffer, size_t length) /* 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; @@ -75,6 +71,34 @@ find_tmp_path(char *buffer, size_t length) return -1; } +static int canonicalize_tmp_path(char *buffer) +{ +#ifdef _WIN32 + char tmp[CLAR_PATH_MAX]; + DWORD ret; + + ret = GetFullPathName(buffer, CLAR_PATH_MAX, tmp, NULL); + + if (ret == 0 || ret > CLAR_PATH_MAX) + return -1; + + ret = GetLongPathName(tmp, buffer, CLAR_PATH_MAX); + + if (ret == 0 || ret > CLAR_PATH_MAX) + return -1; + + return 0; +#else + char tmp[CLAR_PATH_MAX]; + + if (realpath(buffer, tmp) == NULL) + return -1; + + strcpy(buffer, tmp); + return 0; +#endif +} + static void clar_unsandbox(void) { if (_clar_path[0] == '\0') @@ -95,7 +119,8 @@ static int build_sandbox_path(void) size_t len; - if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0) + if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0 || + canonicalize_tmp_path(_clar_path) < 0) return -1; len = strlen(_clar_path); From bec1d7b7b073758549d1d7484fab753b889acbfc Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 18 Feb 2024 00:02:47 +0000 Subject: [PATCH 029/111] path: provide a helper to compute len with trailing slashes We may need to strip multiple slashes at the end of a path; provide a method to do so. --- src/util/fs_path.c | 10 ++++++++++ src/util/fs_path.h | 6 ++++++ tests/util/path.c | 12 ++++++++++++ 3 files changed, 28 insertions(+) diff --git a/src/util/fs_path.c b/src/util/fs_path.c index ab64778c23e..9d5c99eab81 100644 --- a/src/util/fs_path.c +++ b/src/util/fs_path.c @@ -419,6 +419,16 @@ int git_fs_path_to_dir(git_str *path) return git_str_oom(path) ? -1 : 0; } +size_t git_fs_path_dirlen(const char *path) +{ + size_t len = strlen(path); + + while (len > 1 && path[len - 1] == '/') + len--; + + return len; +} + void git_fs_path_string_to_dir(char *path, size_t size) { size_t end = strlen(path); diff --git a/src/util/fs_path.h b/src/util/fs_path.h index e5ca6737818..34e491eb919 100644 --- a/src/util/fs_path.h +++ b/src/util/fs_path.h @@ -86,6 +86,12 @@ extern int git_fs_path_to_dir(git_str *path); */ extern void git_fs_path_string_to_dir(char *path, size_t size); +/** + * Provides the length of the given path string with no trailing + * slashes. + */ +size_t git_fs_path_dirlen(const char *path); + /** * Taken from git.git; returns nonzero if the given path is "." or "..". */ diff --git a/tests/util/path.c b/tests/util/path.c index 02ec42fcea2..38a59392521 100644 --- a/tests/util/path.c +++ b/tests/util/path.c @@ -766,3 +766,15 @@ void test_path__validate_current_user_ownership(void) cl_git_fail(git_fs_path_owner_is_current_user(&is_cur, "/path/does/not/exist")); #endif } + +void test_path__dirlen(void) +{ + cl_assert_equal_sz(13, git_fs_path_dirlen("/foo/bar/asdf")); + cl_assert_equal_sz(13, git_fs_path_dirlen("/foo/bar/asdf/")); + cl_assert_equal_sz(13, git_fs_path_dirlen("/foo/bar/asdf//")); + cl_assert_equal_sz(3, git_fs_path_dirlen("foo////")); + cl_assert_equal_sz(3, git_fs_path_dirlen("foo")); + cl_assert_equal_sz(1, git_fs_path_dirlen("/")); + cl_assert_equal_sz(1, git_fs_path_dirlen("////")); + cl_assert_equal_sz(0, git_fs_path_dirlen("")); +} From beea99b08234a7e0de3a3c7f739260527d38a6f1 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 18 Feb 2024 07:46:19 -0800 Subject: [PATCH 030/111] path: provide an is_root helper method We may quickly want to determine if the given path is the root path ('/') on POSIX, or the root of a drive letter (eg, 'A:/', 'C:\') on Windows. --- src/util/fs_path.h | 17 +++++++++++++++++ tests/util/path/core.c | 26 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/util/fs_path.h b/src/util/fs_path.h index 34e491eb919..43f7951adde 100644 --- a/src/util/fs_path.h +++ b/src/util/fs_path.h @@ -92,6 +92,23 @@ extern void git_fs_path_string_to_dir(char *path, size_t size); */ size_t git_fs_path_dirlen(const char *path); +/** + * Returns nonzero if the given path is a filesystem root; on Windows, this + * means a drive letter (eg `A:/`, `C:\`). On POSIX this is `/`. + */ +GIT_INLINE(int) git_fs_path_is_root(const char *name) +{ +#ifdef GIT_WIN32 + if (((name[0] >= 'A' && name[0] <= 'Z') || (name[0] >= 'a' && name[0] <= 'z')) && + name[1] == ':' && + (name[2] == '/' || name[2] == '\\') && + name[3] == '\0') + return 1; +#endif + + return (name[0] == '/' && name[1] == '\0'); +} + /** * Taken from git.git; returns nonzero if the given path is "." or "..". */ diff --git a/tests/util/path/core.c b/tests/util/path/core.c index f30f6c01b0d..41d9b02040f 100644 --- a/tests/util/path/core.c +++ b/tests/util/path/core.c @@ -341,3 +341,29 @@ void test_path_core__join_unrooted_respects_funny_windows_roots(void) test_join_unrooted("💩:/foo/bar/foobar", 13, "💩:/foo/bar/foobar", "💩:/foo/bar"); test_join_unrooted("💩:/foo/bar/foobar", 9, "💩:/foo/bar/foobar", "💩:/foo/"); } + +void test_path_core__is_root(void) +{ + cl_assert_equal_b(true, git_fs_path_is_root("/")); + cl_assert_equal_b(false, git_fs_path_is_root("//")); + cl_assert_equal_b(false, git_fs_path_is_root("foo/")); + cl_assert_equal_b(false, git_fs_path_is_root("/foo/")); + cl_assert_equal_b(false, git_fs_path_is_root("/foo")); + cl_assert_equal_b(false, git_fs_path_is_root("\\")); + +#ifdef GIT_WIN32 + cl_assert_equal_b(true, git_fs_path_is_root("A:\\")); + cl_assert_equal_b(false, git_fs_path_is_root("B:\\foo")); + cl_assert_equal_b(false, git_fs_path_is_root("B:\\foo\\")); + cl_assert_equal_b(true, git_fs_path_is_root("C:\\")); + cl_assert_equal_b(true, git_fs_path_is_root("c:\\")); + cl_assert_equal_b(true, git_fs_path_is_root("z:\\")); + cl_assert_equal_b(false, git_fs_path_is_root("z:\\\\")); + cl_assert_equal_b(false, git_fs_path_is_root("\\\\localhost")); + cl_assert_equal_b(false, git_fs_path_is_root("\\\\localhost\\")); + cl_assert_equal_b(false, git_fs_path_is_root("\\\\localhost\\c$\\")); + cl_assert_equal_b(false, git_fs_path_is_root("\\\\localhost\\c$\\Foo")); + cl_assert_equal_b(false, git_fs_path_is_root("\\\\localhost\\c$\\Foo\\")); + cl_assert_equal_b(false, git_fs_path_is_root("\\\\Volume\\12345\\Foo\\Bar.txt")); +#endif +} From f43aa1adb50666e258d2412230cf93f6a493ae05 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 19 Feb 2024 07:05:41 -0800 Subject: [PATCH 031/111] util: move path tests to path::core Clar handles multiple levels of hierarchy in a test name _but_ it does so assuming that there are not tests at a parent folder level. In other words, if you have some tests at path/core.c and path/win32.c, then you cannot have tests in path.c. If you have tests in path.c, then the things in path/*.c will be ignored. Move the tests in path.c into path/core.c. --- tests/util/path.c | 780 ----------------------------------------- tests/util/path/core.c | 778 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 778 insertions(+), 780 deletions(-) delete mode 100644 tests/util/path.c diff --git a/tests/util/path.c b/tests/util/path.c deleted file mode 100644 index 38a59392521..00000000000 --- a/tests/util/path.c +++ /dev/null @@ -1,780 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "fs_path.h" - -#ifndef GIT_WIN32 -# include -#endif - -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); -} - -void test_path__validate_current_user_ownership(void) -{ - bool is_cur; - - cl_must_pass(p_mkdir("testdir", 0777)); - cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "testdir")); - cl_assert_equal_i(is_cur, 1); - - cl_git_rewritefile("testfile", "This is a test file."); - cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "testfile")); - cl_assert_equal_i(is_cur, 1); - -#ifdef GIT_WIN32 - cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "C:\\")); - cl_assert_equal_i(is_cur, 0); - - cl_git_fail(git_fs_path_owner_is_current_user(&is_cur, "c:\\path\\does\\not\\exist")); -#else - cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "/")); - cl_assert_equal_i(is_cur, (geteuid() == 0)); - - cl_git_fail(git_fs_path_owner_is_current_user(&is_cur, "/path/does/not/exist")); -#endif -} - -void test_path__dirlen(void) -{ - cl_assert_equal_sz(13, git_fs_path_dirlen("/foo/bar/asdf")); - cl_assert_equal_sz(13, git_fs_path_dirlen("/foo/bar/asdf/")); - cl_assert_equal_sz(13, git_fs_path_dirlen("/foo/bar/asdf//")); - cl_assert_equal_sz(3, git_fs_path_dirlen("foo////")); - cl_assert_equal_sz(3, git_fs_path_dirlen("foo")); - cl_assert_equal_sz(1, git_fs_path_dirlen("/")); - cl_assert_equal_sz(1, git_fs_path_dirlen("////")); - cl_assert_equal_sz(0, git_fs_path_dirlen("")); -} diff --git a/tests/util/path/core.c b/tests/util/path/core.c index 41d9b02040f..d1935a816a9 100644 --- a/tests/util/path/core.c +++ b/tests/util/path/core.c @@ -1,6 +1,784 @@ #include "clar_libgit2.h" +#include "futils.h" #include "fs_path.h" +#ifndef GIT_WIN32 +# include +#endif + +static char *path_save; + +void test_path_core__initialize(void) +{ + path_save = cl_getenv("PATH"); +} + +void test_path_core__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_core__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_core__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_core__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_core__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_core__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_core__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_core__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_core__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_core__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_core__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_core__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_core__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_core__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_core__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_core__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_core__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); +} + +void test_path_core__validate_current_user_ownership(void) +{ + bool is_cur; + + cl_must_pass(p_mkdir("testdir", 0777)); + cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "testdir")); + cl_assert_equal_i(is_cur, 1); + + cl_git_rewritefile("testfile", "This is a test file."); + cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "testfile")); + cl_assert_equal_i(is_cur, 1); + +#ifdef GIT_WIN32 + cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "C:\\")); + cl_assert_equal_i(is_cur, 0); + + cl_git_fail(git_fs_path_owner_is_current_user(&is_cur, "c:\\path\\does\\not\\exist")); +#else + cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "/")); + cl_assert_equal_i(is_cur, (geteuid() == 0)); + + cl_git_fail(git_fs_path_owner_is_current_user(&is_cur, "/path/does/not/exist")); +#endif +} + +void test_path_core__dirlen(void) +{ + cl_assert_equal_sz(13, git_fs_path_dirlen("/foo/bar/asdf")); + cl_assert_equal_sz(13, git_fs_path_dirlen("/foo/bar/asdf/")); + cl_assert_equal_sz(13, git_fs_path_dirlen("/foo/bar/asdf//")); + cl_assert_equal_sz(3, git_fs_path_dirlen("foo////")); + cl_assert_equal_sz(3, git_fs_path_dirlen("foo")); + cl_assert_equal_sz(1, git_fs_path_dirlen("/")); + cl_assert_equal_sz(1, git_fs_path_dirlen("////")); + cl_assert_equal_sz(0, git_fs_path_dirlen("")); +} + static void test_make_relative( const char *expected_path, const char *path, From bdf4d70a1333bed0de1728e4725b7620f8bf74f8 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 19 Feb 2024 07:08:39 -0800 Subject: [PATCH 032/111] util: update win32 p_realpath to canonicalize case The POSIX `realpath` function canonicalizes relative paths, symbolic links, and case (on case-insensitive filesystems). For example, on macOS, if you create some file `/private/tmp/FOO`, and you call `realpath("/tmp/foo")`, you get _the real path_ returned of `/private/tmp/FOO`. To emulate this behavior on win32, we call `GetFullPathName` to handle the relative to absolute path conversion, then call `GetLongPathName` to handle the case canonicalization. --- src/util/win32/posix_w32.c | 34 ++++++++++++++++++++++++++++------ tests/util/path/win32.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/util/win32/posix_w32.c b/src/util/win32/posix_w32.c index 3fec469a648..ace23200f59 100644 --- a/src/util/win32/posix_w32.c +++ b/src/util/win32/posix_w32.c @@ -787,13 +787,19 @@ int p_rmdir(const char *path) char *p_realpath(const char *orig_path, char *buffer) { git_win32_path orig_path_w, buffer_w; + DWORD long_len; 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 + /* + * POSIX realpath performs two functions: first, it turns relative + * paths into absolute paths. For this, we need GetFullPathName. + * + * 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. */ + * 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; @@ -803,9 +809,26 @@ char *p_realpath(const char *orig_path, char *buffer) return NULL; } - /* The path must exist. */ - if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) { - errno = ENOENT; + /* + * Then, the path is canonicalized. eg, on macOS, + * "/TMP" -> "/private/tmp". For this, we need GetLongPathName. + */ + if ((long_len = GetLongPathNameW(buffer_w, buffer_w, GIT_WIN_PATH_UTF16)) == 0) { + DWORD error = GetLastError(); + + if (error == ERROR_FILE_NOT_FOUND || + error == ERROR_PATH_NOT_FOUND) + errno = ENOENT; + else if (error == ERROR_ACCESS_DENIED) + errno = EPERM; + else + errno = EINVAL; + + return NULL; + } + + if (long_len > GIT_WIN_PATH_UTF16) { + errno = ENAMETOOLONG; return NULL; } @@ -821,7 +844,6 @@ char *p_realpath(const char *orig_path, char *buffer) return NULL; git_fs_path_mkposix(buffer); - return buffer; } diff --git a/tests/util/path/win32.c b/tests/util/path/win32.c index 1aaf6867a26..09a5a8ba881 100644 --- a/tests/util/path/win32.c +++ b/tests/util/path/win32.c @@ -280,3 +280,32 @@ void test_path_win32__8dot3_name(void) git__free(shortname); #endif } + +void test_path_win32__realpath(void) +{ +#ifdef GIT_WIN32 + git_str expected = GIT_STR_INIT; + char result[GIT_PATH_MAX]; + + /* Ensure relative paths become absolute */ + cl_must_pass(git_str_joinpath(&expected, clar_sandbox_path(), "abcdef")); + cl_must_pass(p_mkdir("abcdef", 0777)); + cl_assert(p_realpath("./abcdef", result) != NULL); + cl_assert_equal_s(expected.ptr, result); + + /* Ensure case is canonicalized */ + git_str_clear(&expected); + cl_must_pass(git_str_joinpath(&expected, clar_sandbox_path(), "FOO")); + cl_must_pass(p_mkdir("FOO", 0777)); + cl_assert(p_realpath("foo", result) != NULL); + cl_assert_equal_s(expected.ptr, result); + + cl_assert(p_realpath("nonexistent", result) == NULL); + cl_assert_equal_i(ENOENT, errno); + + git_str_dispose(&expected); + + p_rmdir("abcdef"); + p_rmdir("FOO"); +#endif +} From 57870e9f4d21791d3b62166f5b9b0a67641de942 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 19 Feb 2024 09:04:39 -0800 Subject: [PATCH 033/111] util: clean up test resources in the sandbox Ensure that we clean up cruft that we create for testing, so that future tests don't have troubles. --- tests/util/path/win32.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/util/path/win32.c b/tests/util/path/win32.c index 09a5a8ba881..6f90b447dda 100644 --- a/tests/util/path/win32.c +++ b/tests/util/path/win32.c @@ -278,6 +278,10 @@ void test_path_win32__8dot3_name(void) cl_must_pass(p_mkdir(".bar", 0777)); cl_assert_equal_s("BAR~2", (shortname = git_win32_path_8dot3_name(".bar"))); git__free(shortname); + + p_rmdir(".foo"); + p_rmdir(".bar"); + p_unlink("bar~1"); #endif } From 19d52837e4b879f8cfd903dcfa8e1459f0f92cc6 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 18 Feb 2024 00:06:35 +0000 Subject: [PATCH 034/111] repo: trim trailing slashes from safedir error message Our error messages should provide the literal path that users should add to their safe directory allowlist, which means it should not have a trailing slash. --- src/libgit2/repository.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index b2afc4f59d2..0a39e07c084 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -705,9 +705,12 @@ static int validate_ownership(git_repository *repo) goto done; if (!is_safe) { + size_t path_len = git_fs_path_is_root(path) ? + strlen(path) : git_fs_path_dirlen(path); + git_error_set(GIT_ERROR_CONFIG, - "repository path '%s' is not owned by current user", - path); + "repository path '%.*s' is not owned by current user", + (int)min(path_len, INT_MAX), path); error = GIT_EOWNER; } From d970934b29ebad813de9b378f246015df6801da8 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 18 Feb 2024 00:39:26 +0000 Subject: [PATCH 035/111] repo: test that '%(prefix)/' safe.directory succeeds Ensure that we can support safe.directory entries that are prefixed with '%(prefix)/'. --- tests/libgit2/repo/open.c | 66 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/tests/libgit2/repo/open.c b/tests/libgit2/repo/open.c index 9c0bfde7b57..595440a68dd 100644 --- a/tests/libgit2/repo/open.c +++ b/tests/libgit2/repo/open.c @@ -779,3 +779,69 @@ void test_repo_open__can_reset_safe_directory_list(void) git_str_dispose(&config_filename); git_str_dispose(&config_data); } + +void test_repo_open__can_handle_prefixed_safe_paths(void) +{ + git_repository *repo; + git_str config_path = GIT_STR_INIT, + config_filename = GIT_STR_INIT, + config_data = GIT_STR_INIT; + + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1)); + + cl_fixture_sandbox("empty_standard_repo"); + cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git")); + + git_fs_path__set_owner(GIT_FS_PATH_OWNER_OTHER); + cl_git_fail_with(GIT_EOWNER, git_repository_open(&repo, "empty_standard_repo")); + + /* Add safe.directory options to the global configuration */ + git_str_joinpath(&config_path, clar_sandbox_path(), "__global_config"); + cl_must_pass(p_mkdir(config_path.ptr, 0777)); + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, config_path.ptr); + + git_str_joinpath(&config_filename, config_path.ptr, ".gitconfig"); + + /* + * Using "%(prefix)/" becomes "%(prefix)//tmp/foo" - so + * "%(prefix)/" is stripped and means the literal path + * follows. + */ + git_str_clear(&config_data); + git_str_printf(&config_data, + "[foo]\n" \ + "\tbar = Foobar\n" \ + "\tbaz = Baz!\n" \ + "[safe]\n" \ + "\tdirectory = %%(prefix)/%s/%s\n" \ + "[bar]\n" \ + "\tfoo = barfoo\n", + clar_sandbox_path(), "empty_standard_repo"); + cl_git_rewritefile(config_filename.ptr, config_data.ptr); + + cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); + git_repository_free(repo); + + /* + * Using "%(prefix)" becomes "%(prefix)/tmp/foo" - so it's + * actually trying to look in the git prefix, for example, + * "/usr/local/tmp/foo", which we don't actually support. + */ + git_str_clear(&config_data); + git_str_printf(&config_data, + "[foo]\n" \ + "\tbar = Foobar\n" \ + "\tbaz = Baz!\n" \ + "[safe]\n" \ + "\tdirectory = %%(prefix)%s/%s\n" \ + "[bar]\n" \ + "\tfoo = barfoo\n", + clar_sandbox_path(), "empty_standard_repo"); + cl_git_rewritefile(config_filename.ptr, config_data.ptr); + + cl_git_fail_with(GIT_EOWNER, git_repository_open(&repo, "empty_standard_repo")); + + git_str_dispose(&config_path); + git_str_dispose(&config_filename); + git_str_dispose(&config_data); +} From 71309547ec602a9bd1a2c36d0bfc2b24f3a43cd8 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 18 Feb 2024 00:40:44 +0000 Subject: [PATCH 036/111] repo: trim '%(prefix)/' from safedir on all platforms '%(prefix)/' as a leading path name is safe to strip on all platforms. It doesn't make sense to use on non-Windows, but supporting it there does allow us to easily dev/test. --- src/libgit2/repository.c | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index 0a39e07c084..372c43f5ea6 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -558,15 +558,14 @@ typedef struct { static int validate_ownership_cb(const git_config_entry *entry, void *payload) { validate_ownership_data *data = payload; + const char *test_path; if (strcmp(entry->value, "") == 0) { *data->is_safe = false; } else if (strcmp(entry->value, "*") == 0) { *data->is_safe = true; } else { - const char *test_path = entry->value; - - if (git_str_sets(&data->tmp, test_path) < 0 || + if (git_str_sets(&data->tmp, entry->value) < 0 || git_fs_path_to_dir(&data->tmp) < 0) return -1; @@ -575,20 +574,22 @@ static int validate_ownership_cb(const git_config_entry *entry, void *payload) * input path by adding a trailing backslash. * A trailing backslash on the input is not allowed. */ - if (strcmp(data->tmp.ptr, test_path) == 0) + if (strcmp(data->tmp.ptr, entry->value) == 0) return 0; -#ifdef GIT_WIN32 + test_path = data->tmp.ptr; + /* - * Git for Windows does some truly bizarre things with - * paths that start with a forward slash; and expects you - * to escape that with `%(prefix)`. This syntax generally - * means to add the prefix that Git was installed to -- eg - * `/usr/local` -- unless it's an absolute path, in which - * case the leading `%(prefix)/` is just removed. And Git - * for Windows expects you to use this syntax for absolute - * Unix-style paths (in "Git Bash" or Windows Subsystem for - * Linux). + * Git - and especially, Git for Windows - does some + * truly bizarre things with paths that start with a + * forward slash; and expects you to escape that with + * `%(prefix)`. This syntax generally means to add the + * prefix that Git was installed to (eg `/usr/local`) + * unless it's an absolute path, in which case the + * leading `%(prefix)/` is just removed. And Git for + * Windows expects you to use this syntax for absolute + * Unix-style paths (in "Git Bash" or Windows Subsystem + * for Linux). * * Worse, the behavior used to be that a leading `/` was * not absolute. It would indicate that Git for Windows @@ -603,12 +604,13 @@ static int validate_ownership_cb(const git_config_entry *entry, void *payload) */ if (strncmp(test_path, "%(prefix)//", strlen("%(prefix)//")) == 0) test_path += strlen("%(prefix)/"); +#ifdef GIT_WIN32 else if (strncmp(test_path, "//", 2) == 0 && strncmp(test_path, "//wsl.localhost/", strlen("//wsl.localhost/")) != 0) test_path++; #endif - if (strcmp(data->tmp.ptr, data->repo_path) == 0) + if (strcmp(test_path, data->repo_path) == 0) *data->is_safe = true; } From b19a6a1582c626a2253ad232b087542f10c3cb62 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 18 Feb 2024 14:40:26 +0000 Subject: [PATCH 037/111] repo: refactor safe directory tests Provide a helper method in the tests to set up the safe.directory configuration for a normal and bare repository to avoid some copy/pasta. (Some copypasta will remain, since there's customizations to the file that are not trivially abstractable.) --- tests/libgit2/repo/open.c | 260 +++++++++++++++----------------------- 1 file changed, 100 insertions(+), 160 deletions(-) diff --git a/tests/libgit2/repo/open.c b/tests/libgit2/repo/open.c index 595440a68dd..7719bbd1b82 100644 --- a/tests/libgit2/repo/open.c +++ b/tests/libgit2/repo/open.c @@ -533,15 +533,21 @@ void test_repo_open__validates_bare_repo_ownership(void) cl_git_fail_with(GIT_EOWNER, git_repository_open(&repo, "testrepo.git")); } -void test_repo_open__can_allowlist_dirs_with_problematic_ownership(void) +static int test_safe_path(const char *path) { git_repository *repo; git_str config_path = GIT_STR_INIT, config_filename = GIT_STR_INIT, config_data = GIT_STR_INIT; + int error; cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1)); + /* + * Sandbox the fixture, and ensure that when we fake an owner + * of "other" that the repository cannot be opened (and fails + * with `GIT_EOWNER`). + */ cl_fixture_sandbox("empty_standard_repo"); cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git")); @@ -555,93 +561,35 @@ void test_repo_open__can_allowlist_dirs_with_problematic_ownership(void) git_str_joinpath(&config_filename, config_path.ptr, ".gitconfig"); - /* Test with incorrect exception (slash at the end) */ - git_str_printf(&config_data, - "[foo]\n" \ - "\tbar = Foobar\n" \ - "\tbaz = Baz!\n" \ - "[safe]\n" \ - "\tdirectory = /non/existent/path\n" \ - "\tdirectory = /\n" \ - "\tdirectory = c:\\\\temp\n" \ - "\tdirectory = %s/%s/\n" \ - "\tdirectory = /tmp\n" \ - "[bar]\n" \ - "\tfoo = barfoo\n", - clar_sandbox_path(), "empty_standard_repo"); - cl_git_rewritefile(config_filename.ptr, config_data.ptr); - cl_git_fail_with(GIT_EOWNER, git_repository_open(&repo, "empty_standard_repo")); - - /* Test with correct exception */ git_str_clear(&config_data); git_str_printf(&config_data, "[foo]\n" \ "\tbar = Foobar\n" \ "\tbaz = Baz!\n" \ "[safe]\n" \ - "\tdirectory = /non/existent/path\n" \ - "\tdirectory = /\n" \ - "\tdirectory = c:\\\\temp\n" \ - "\tdirectory = %s/%s\n" \ - "\tdirectory = /tmp\n" \ + "\tdirectory = %s\n" \ "[bar]\n" \ "\tfoo = barfoo\n", - clar_sandbox_path(), "empty_standard_repo"); + path); cl_git_rewritefile(config_filename.ptr, config_data.ptr); - cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); + error = git_repository_open(&repo, "empty_standard_repo"); git_repository_free(repo); git_str_dispose(&config_path); git_str_dispose(&config_filename); git_str_dispose(&config_data); -} - -void test_repo_open__can_wildcard_allowlist_with_problematic_ownership(void) -{ - git_repository *repo; - git_str config_path = GIT_STR_INIT, config_filename = GIT_STR_INIT; - - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1)); - - cl_fixture_sandbox("empty_standard_repo"); - cl_git_pass(cl_rename( - "empty_standard_repo/.gitted", "empty_standard_repo/.git")); - - git_fs_path__set_owner(GIT_FS_PATH_OWNER_OTHER); - cl_git_fail_with( - GIT_EOWNER, git_repository_open(&repo, "empty_standard_repo")); - - /* Add safe.directory options to the global configuration */ - git_str_joinpath(&config_path, clar_sandbox_path(), "__global_config"); - cl_must_pass(p_mkdir(config_path.ptr, 0777)); - git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, - config_path.ptr); - - git_str_joinpath(&config_filename, config_path.ptr, ".gitconfig"); - - cl_git_rewritefile(config_filename.ptr, "[foo]\n" - "\tbar = Foobar\n" - "\tbaz = Baz!\n" - "[safe]\n" - "\tdirectory = *\n" - "[bar]\n" - "\tfoo = barfoo\n"); - - cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - git_repository_free(repo); - git_str_dispose(&config_path); - git_str_dispose(&config_filename); + return error; } -void test_repo_open__can_allowlist_bare_gitdir(void) +static int test_bare_safe_path(const char *path) { git_repository *repo; git_str config_path = GIT_STR_INIT, config_filename = GIT_STR_INIT, config_data = GIT_STR_INIT; + int error; cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1)); @@ -665,56 +613,114 @@ void test_repo_open__can_allowlist_bare_gitdir(void) "\tdirectory = /non/existent/path\n" \ "\tdirectory = /\n" \ "\tdirectory = c:\\\\temp\n" \ - "\tdirectory = %s/%s\n" \ + "\tdirectory = %s\n" \ "\tdirectory = /tmp\n" \ "[bar]\n" \ "\tfoo = barfoo\n", - clar_sandbox_path(), "testrepo.git"); + path); cl_git_rewritefile(config_filename.ptr, config_data.ptr); - cl_git_pass(git_repository_open(&repo, "testrepo.git")); + error = git_repository_open(&repo, "testrepo.git"); git_repository_free(repo); git_str_dispose(&config_path); git_str_dispose(&config_filename); git_str_dispose(&config_data); + + return error; } -void test_repo_open__can_wildcard_allowlist_bare_gitdir(void) +void test_repo_open__can_allowlist_dirs_with_problematic_ownership(void) { - git_repository *repo; - git_str config_path = GIT_STR_INIT, config_filename = GIT_STR_INIT; + git_str path = GIT_STR_INIT; - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1)); + cl_git_pass(git_str_printf(&path, "%s/%s", + clar_sandbox_path(), "empty_standard_repo")); + cl_git_pass(test_safe_path(path.ptr)); + git_str_dispose(&path); +} - cl_fixture_sandbox("testrepo.git"); +void test_repo_open__safe_directory_fails_with_trailing_slash(void) +{ + git_str path = GIT_STR_INIT; - git_fs_path__set_owner(GIT_FS_PATH_OWNER_OTHER); - cl_git_fail_with( - GIT_EOWNER, git_repository_open(&repo, "testrepo.git")); + /* + * "/tmp/foo/" is not permitted; safe path must be specified + * as "/tmp/foo" + */ + cl_git_pass(git_str_printf(&path, "%s/%s/", + clar_sandbox_path(), "empty_standard_repo")); + cl_git_fail_with(GIT_EOWNER, test_safe_path(path.ptr)); + git_str_dispose(&path); +} - /* Add safe.directory options to the global configuration */ - git_str_joinpath(&config_path, clar_sandbox_path(), "__global_config"); - cl_must_pass(p_mkdir(config_path.ptr, 0777)); - git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, - config_path.ptr); +void test_repo_open__can_wildcard_allowlist_with_problematic_ownership(void) +{ + cl_git_pass(test_safe_path("*")); +} - git_str_joinpath(&config_filename, config_path.ptr, ".gitconfig"); +void test_repo_open__can_allowlist_bare_gitdir(void) +{ + git_str path = GIT_STR_INIT; - cl_git_rewritefile(config_filename.ptr, "[foo]\n" - "\tbar = Foobar\n" - "\tbaz = Baz!\n" - "[safe]\n" - "\tdirectory = *\n" - "[bar]\n" - "\tfoo = barfoo\n"); + cl_git_pass(git_str_printf(&path, "%s/%s", + clar_sandbox_path(), "testrepo.git")); + cl_git_pass(test_bare_safe_path(path.ptr)); + git_str_dispose(&path); +} - cl_git_pass(git_repository_open(&repo, "testrepo.git")); - git_repository_free(repo); +void test_repo_open__can_wildcard_allowlist_bare_gitdir(void) +{ + cl_git_pass(test_bare_safe_path("*")); +} - git_str_dispose(&config_path); - git_str_dispose(&config_filename); +void test_repo_open__can_handle_prefixed_safe_paths(void) +{ +#ifndef GIT_WIN32 + git_str path = GIT_STR_INIT; + + /* + * Using "%(prefix)/" becomes "%(prefix)//tmp/foo" - so + * "%(prefix)/" is stripped and means the literal path + * follows. + */ + cl_git_pass(git_str_printf(&path, "%%(prefix)/%s/%s", + clar_sandbox_path(), "empty_standard_repo")); + cl_git_pass(test_safe_path(path.ptr)); + git_str_dispose(&path); +#endif +} + +void test_repo_open__prefixed_safe_paths_must_have_two_slashes(void) +{ + git_str path = GIT_STR_INIT; + + /* + * Using "%(prefix)" becomes "%(prefix)/tmp/foo" - so it's + * actually trying to look in the git prefix, for example, + * "/usr/local/tmp/foo", which we don't actually support. + */ + cl_git_pass(git_str_printf(&path, "%%(prefix)%s/%s", + clar_sandbox_path(), "empty_standard_repo")); + cl_git_fail_with(GIT_EOWNER, test_safe_path(path.ptr)); + git_str_dispose(&path); +} + +void test_repo_open__can_handle_win32_prefixed_safe_paths(void) +{ +#ifdef GIT_WIN32 + git_str path = GIT_STR_INIT; + + /* + * On Windows, we need %(prefix)///wsl.localhost/C:/foo/bar/... + * because we can't have nice things. + */ + cl_git_pass(git_str_printf(&path, + "%%(prefix)///localhost/%s/%s", + clar_sandbox_path(), "empty_standard_repo")); + cl_git_pass(test_safe_path(path.ptr)); + git_str_dispose(&path); +#endif } void test_repo_open__can_reset_safe_directory_list(void) @@ -779,69 +785,3 @@ void test_repo_open__can_reset_safe_directory_list(void) git_str_dispose(&config_filename); git_str_dispose(&config_data); } - -void test_repo_open__can_handle_prefixed_safe_paths(void) -{ - git_repository *repo; - git_str config_path = GIT_STR_INIT, - config_filename = GIT_STR_INIT, - config_data = GIT_STR_INIT; - - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1)); - - cl_fixture_sandbox("empty_standard_repo"); - cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git")); - - git_fs_path__set_owner(GIT_FS_PATH_OWNER_OTHER); - cl_git_fail_with(GIT_EOWNER, git_repository_open(&repo, "empty_standard_repo")); - - /* Add safe.directory options to the global configuration */ - git_str_joinpath(&config_path, clar_sandbox_path(), "__global_config"); - cl_must_pass(p_mkdir(config_path.ptr, 0777)); - git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, config_path.ptr); - - git_str_joinpath(&config_filename, config_path.ptr, ".gitconfig"); - - /* - * Using "%(prefix)/" becomes "%(prefix)//tmp/foo" - so - * "%(prefix)/" is stripped and means the literal path - * follows. - */ - git_str_clear(&config_data); - git_str_printf(&config_data, - "[foo]\n" \ - "\tbar = Foobar\n" \ - "\tbaz = Baz!\n" \ - "[safe]\n" \ - "\tdirectory = %%(prefix)/%s/%s\n" \ - "[bar]\n" \ - "\tfoo = barfoo\n", - clar_sandbox_path(), "empty_standard_repo"); - cl_git_rewritefile(config_filename.ptr, config_data.ptr); - - cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - git_repository_free(repo); - - /* - * Using "%(prefix)" becomes "%(prefix)/tmp/foo" - so it's - * actually trying to look in the git prefix, for example, - * "/usr/local/tmp/foo", which we don't actually support. - */ - git_str_clear(&config_data); - git_str_printf(&config_data, - "[foo]\n" \ - "\tbar = Foobar\n" \ - "\tbaz = Baz!\n" \ - "[safe]\n" \ - "\tdirectory = %%(prefix)%s/%s\n" \ - "[bar]\n" \ - "\tfoo = barfoo\n", - clar_sandbox_path(), "empty_standard_repo"); - cl_git_rewritefile(config_filename.ptr, config_data.ptr); - - cl_git_fail_with(GIT_EOWNER, git_repository_open(&repo, "empty_standard_repo")); - - git_str_dispose(&config_path); - git_str_dispose(&config_filename); - git_str_dispose(&config_data); -} From c1734b5ae42809a322977f9fe0dd26ccebd19a94 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 18 Feb 2024 07:04:00 -0800 Subject: [PATCH 038/111] repo: test for safe.directory with unc paths On Windows, you may open a repo with UNC path format -- for example \\share\foo\bar, or as a git-style path, //share/foo/bar. In this world, Git for Windows expects you to translate this to $(prefix)//share/foo/bar. We can test for that by using the fact that local drives are exposed as hidden shares. For example, C:\Foo is //localhost/C$/Foo/. --- tests/libgit2/repo/open.c | 50 +++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/tests/libgit2/repo/open.c b/tests/libgit2/repo/open.c index 7719bbd1b82..f859838aeb3 100644 --- a/tests/libgit2/repo/open.c +++ b/tests/libgit2/repo/open.c @@ -709,17 +709,53 @@ void test_repo_open__prefixed_safe_paths_must_have_two_slashes(void) void test_repo_open__can_handle_win32_prefixed_safe_paths(void) { #ifdef GIT_WIN32 - git_str path = GIT_STR_INIT; + git_repository *repo; + git_str unc_path = GIT_STR_INIT, + config_path = GIT_STR_INIT, + config_filename = GIT_STR_INIT, + config_data = GIT_STR_INIT; + + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1)); + + cl_fixture_sandbox("empty_standard_repo"); + cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git")); /* - * On Windows, we need %(prefix)///wsl.localhost/C:/foo/bar/... - * because we can't have nice things. + * On Windows, we can generally map a local drive to a UNC path; + * for example C:\Foo\Bar becomes //localhost/C$/Foo/bar */ - cl_git_pass(git_str_printf(&path, - "%%(prefix)///localhost/%s/%s", + cl_git_pass(git_str_printf(&unc_path, "//localhost/%s/%s", clar_sandbox_path(), "empty_standard_repo")); - cl_git_pass(test_safe_path(path.ptr)); - git_str_dispose(&path); + + if (unc_path.ptr[13] != ':' || unc_path.ptr[14] != '/') + cl_skip(); + + unc_path.ptr[13] = '$'; + + git_fs_path__set_owner(GIT_FS_PATH_OWNER_OTHER); + cl_git_fail_with(GIT_EOWNER, git_repository_open(&repo, unc_path.ptr)); + + /* Add safe.directory options to the global configuration */ + git_str_joinpath(&config_path, clar_sandbox_path(), "__global_config"); + cl_must_pass(p_mkdir(config_path.ptr, 0777)); + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, config_path.ptr); + + git_str_joinpath(&config_filename, config_path.ptr, ".gitconfig"); + + /* The blank resets our sandbox directory and opening fails */ + + git_str_printf(&config_data, + "[safe]\n\tdirectory = %%(prefix)/%s\n", + unc_path.ptr); + cl_git_rewritefile(config_filename.ptr, config_data.ptr); + + cl_git_pass(git_repository_open(&repo, unc_path.ptr)); + git_repository_free(repo); + + git_str_dispose(&config_path); + git_str_dispose(&config_filename); + git_str_dispose(&config_data); + git_str_dispose(&unc_path); #endif } From e9d55212890ed0cb695485aadac0a2cc4e89ecb3 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 18 Feb 2024 07:47:26 -0800 Subject: [PATCH 039/111] repo: handle root paths properly for safe directories When doing directory name munging to remove trailing slashes, ensure that we do not remove the trailing slash from the path root, whether that's '/' or 'C:\'. --- src/libgit2/repository.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index 372c43f5ea6..6df9e63f2bb 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -565,17 +565,18 @@ static int validate_ownership_cb(const git_config_entry *entry, void *payload) } else if (strcmp(entry->value, "*") == 0) { *data->is_safe = true; } else { - if (git_str_sets(&data->tmp, entry->value) < 0 || - git_fs_path_to_dir(&data->tmp) < 0) + if (git_str_sets(&data->tmp, entry->value) < 0) return -1; - /* - * Ensure that `git_fs_path_to_dir` mutated the - * input path by adding a trailing backslash. - * A trailing backslash on the input is not allowed. - */ - if (strcmp(data->tmp.ptr, entry->value) == 0) - return 0; + if (!git_fs_path_is_root(data->tmp.ptr)) { + /* Input must not have trailing backslash. */ + if (!data->tmp.size || + data->tmp.ptr[data->tmp.size - 1] == '/') + return 0; + + if (git_fs_path_to_dir(&data->tmp) < 0) + return -1; + } test_path = data->tmp.ptr; From 9c31bd0358acfcafe45bb6a2fe5da6ce5d0d1f01 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 19 Feb 2024 07:23:33 -0800 Subject: [PATCH 040/111] repo: allow safe.directory to be a UNC path Recent versions of Git for Windows allow a sane UNC style path to be used directly in `safe.directory` (without needing the `%(prefix)` silliness). Match that behavior. --- src/libgit2/repository.c | 5 ---- tests/libgit2/repo/open.c | 53 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index 6df9e63f2bb..2c16808c7fc 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -605,11 +605,6 @@ static int validate_ownership_cb(const git_config_entry *entry, void *payload) */ if (strncmp(test_path, "%(prefix)//", strlen("%(prefix)//")) == 0) test_path += strlen("%(prefix)/"); -#ifdef GIT_WIN32 - else if (strncmp(test_path, "//", 2) == 0 && - strncmp(test_path, "//wsl.localhost/", strlen("//wsl.localhost/")) != 0) - test_path++; -#endif if (strcmp(test_path, data->repo_path) == 0) *data->is_safe = true; diff --git a/tests/libgit2/repo/open.c b/tests/libgit2/repo/open.c index f859838aeb3..a1acfae9022 100644 --- a/tests/libgit2/repo/open.c +++ b/tests/libgit2/repo/open.c @@ -759,6 +759,59 @@ void test_repo_open__can_handle_win32_prefixed_safe_paths(void) #endif } +void test_repo_open__can_handle_win32_unc_safe_paths(void) +{ +#ifdef GIT_WIN32 + git_repository *repo; + git_str unc_path = GIT_STR_INIT, + config_path = GIT_STR_INIT, + config_filename = GIT_STR_INIT, + config_data = GIT_STR_INIT; + + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 1)); + + cl_fixture_sandbox("empty_standard_repo"); + cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git")); + + /* + * On Windows, we can generally map a local drive to a UNC path; + * for example C:\Foo\Bar becomes //localhost/C$/Foo/bar + */ + cl_git_pass(git_str_printf(&unc_path, "//localhost/%s/%s", + clar_sandbox_path(), "empty_standard_repo")); + + if (unc_path.ptr[13] != ':' || unc_path.ptr[14] != '/') + cl_skip(); + + unc_path.ptr[13] = '$'; + + git_fs_path__set_owner(GIT_FS_PATH_OWNER_OTHER); + cl_git_fail_with(GIT_EOWNER, git_repository_open(&repo, unc_path.ptr)); + + /* Add safe.directory options to the global configuration */ + git_str_joinpath(&config_path, clar_sandbox_path(), "__global_config"); + cl_must_pass(p_mkdir(config_path.ptr, 0777)); + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, config_path.ptr); + + git_str_joinpath(&config_filename, config_path.ptr, ".gitconfig"); + + /* The blank resets our sandbox directory and opening fails */ + + git_str_printf(&config_data, + "[safe]\n\tdirectory = %s\n", + unc_path.ptr); + cl_git_rewritefile(config_filename.ptr, config_data.ptr); + + cl_git_pass(git_repository_open(&repo, unc_path.ptr)); + git_repository_free(repo); + + git_str_dispose(&config_path); + git_str_dispose(&config_filename); + git_str_dispose(&config_data); + git_str_dispose(&unc_path); +#endif +} + void test_repo_open__can_reset_safe_directory_list(void) { git_repository *repo; From c63eccc3e5d2fe4481270431c9fb3377f2949595 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 20 Feb 2024 09:56:26 +0000 Subject: [PATCH 041/111] revparse: ensure bare '@' is truly bare Support a revspec of '@' to mean 'HEAD', but ensure that it's at the start of the revspec. Previously we were erroneously allowing 'foo@' to mean 'HEAD' as well. Instead, 'foo@' should be rejected. --- src/libgit2/revparse.c | 6 ++++++ tests/libgit2/refs/revparse.c | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/libgit2/revparse.c b/src/libgit2/revparse.c index 06d92f82bf2..bf8131aab79 100644 --- a/src/libgit2/revparse.c +++ b/src/libgit2/revparse.c @@ -817,6 +817,12 @@ static int revparse( base_rev = temp_object; break; } else if (spec[pos+1] == '\0') { + if (pos) { + git_error_set(GIT_ERROR_REFERENCE, "invalid revspec"); + error = GIT_EINVALIDSPEC; + goto cleanup; + } + spec = "HEAD"; identifier_len = 4; parsed = true; diff --git a/tests/libgit2/refs/revparse.c b/tests/libgit2/refs/revparse.c index d2f464840a0..3fe07811796 100644 --- a/tests/libgit2/refs/revparse.c +++ b/tests/libgit2/refs/revparse.c @@ -889,3 +889,15 @@ void test_refs_revparse__parses_at_head(void) test_id("@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", NULL, GIT_REVSPEC_SINGLE); test_id("@", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", NULL, GIT_REVSPEC_SINGLE); } + +void test_refs_revparse__rejects_bogus_at(void) +{ + git_repository *repo; + git_object *target; + + repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_fail_with(GIT_EINVALIDSPEC, git_revparse_single(&target, repo, "foo@")); + + cl_git_sandbox_cleanup(); +} From af68b86baad93fa529e6fe412c2b129c80ddb0d8 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 20 Feb 2024 09:57:18 +0000 Subject: [PATCH 042/111] revspec: correct capitalization for error message --- src/libgit2/revparse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libgit2/revparse.c b/src/libgit2/revparse.c index bf8131aab79..08237628793 100644 --- a/src/libgit2/revparse.c +++ b/src/libgit2/revparse.c @@ -941,7 +941,7 @@ int git_revparse( * allowed. */ if (!git__strcmp(spec, "..")) { - git_error_set(GIT_ERROR_INVALID, "Invalid pattern '..'"); + git_error_set(GIT_ERROR_INVALID, "invalid pattern '..'"); return GIT_EINVALIDSPEC; } From 6e8c09a46f4136ada0601a75546f5b6bfb6298d0 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 20 Feb 2024 03:16:04 -0800 Subject: [PATCH 043/111] repo: ensure we can initialize win32 paths Given a win32 path, ensure that we can initialize it. This involves a posix-style path conversion at the beginning of our initialization. --- src/libgit2/repository.c | 2 ++ tests/libgit2/repo/init.c | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index b2afc4f59d2..737b96445a4 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -2676,6 +2676,8 @@ static int repo_init_directories( if (git_str_joinpath(repo_path, given_repo, add_dotgit ? GIT_DIR : "") < 0) return -1; + git_fs_path_mkposix(repo_path->ptr); + has_dotgit = (git__suffixcmp(repo_path->ptr, "/" GIT_DIR) == 0); if (has_dotgit) opts->flags |= GIT_REPOSITORY_INIT__HAS_DOTGIT; diff --git a/tests/libgit2/repo/init.c b/tests/libgit2/repo/init.c index d78ec063cd2..446ab735e4e 100644 --- a/tests/libgit2/repo/init.c +++ b/tests/libgit2/repo/init.c @@ -755,3 +755,28 @@ void test_repo_init__longpath(void) git_str_dispose(&path); #endif } + +void test_repo_init__absolute_path_with_backslashes(void) +{ +#ifdef GIT_WIN32 + git_repository_init_options initopts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + git_str path = GIT_STR_INIT; + char *c; + + cl_set_cleanup(&cleanup_repository, "path"); + + cl_git_pass(git_str_joinpath(&path, clar_sandbox_path(), "path/to/newrepo")); + + for (c = path.ptr; *c; c++) { + if (*c == '/') + *c = '\\'; + } + + initopts.flags |= GIT_REPOSITORY_INIT_MKDIR | GIT_REPOSITORY_INIT_MKPATH; + + cl_git_pass(git_repository_init_ext(&g_repo, path.ptr, &initopts)); + git_str_dispose(&path); +#else + clar__skip(); +#endif +} From 267479dad5bfc8b028965e54a2625f3960f9d319 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 20 Feb 2024 11:31:22 +0000 Subject: [PATCH 044/111] http: support empty http.proxy config setting An empty `http.proxy` config setting should be treated like a missing `http.proxy` config setting. --- src/libgit2/transports/http.c | 2 +- src/libgit2/transports/winhttp.c | 2 +- tests/libgit2/online/fetch.c | 20 ++++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/libgit2/transports/http.c b/src/libgit2/transports/http.c index 8437674fcc9..00bfcb0b39b 100644 --- a/src/libgit2/transports/http.c +++ b/src/libgit2/transports/http.c @@ -334,7 +334,7 @@ static int lookup_proxy( return 0; } - if (!proxy || + if (!proxy || !*proxy || (error = git_net_url_parse_http(&transport->proxy.url, proxy)) < 0) goto done; diff --git a/src/libgit2/transports/winhttp.c b/src/libgit2/transports/winhttp.c index ae572c56d8e..031ff3f70b0 100644 --- a/src/libgit2/transports/winhttp.c +++ b/src/libgit2/transports/winhttp.c @@ -436,7 +436,7 @@ static int winhttp_stream_connect(winhttp_stream *s) GIT_ERROR_CHECK_ALLOC(proxy_url); } - if (proxy_url) { + if (proxy_url && *proxy_url) { git_str processed_url = GIT_STR_INIT; WINHTTP_PROXY_INFO proxy_info; wchar_t *proxy_wide; diff --git a/tests/libgit2/online/fetch.c b/tests/libgit2/online/fetch.c index d640eac1b6c..08a14c6317d 100644 --- a/tests/libgit2/online/fetch.c +++ b/tests/libgit2/online/fetch.c @@ -110,6 +110,26 @@ void test_online_fetch__fetch_twice(void) git_remote_free(remote); } +void test_online_fetch__fetch_with_empty_http_proxy(void) +{ + git_remote *remote; + git_config *config; + git_fetch_options opts = GIT_FETCH_OPTIONS_INIT; + + opts.proxy_opts.type = GIT_PROXY_AUTO; + + cl_git_pass(git_repository_config(&config, _repo)); + cl_git_pass(git_config_set_string(config, "http.proxy", "")); + + cl_git_pass(git_remote_create(&remote, _repo, "test", + "https://github.com/libgit2/TestGitRepository")); + cl_git_pass(git_remote_fetch(remote, NULL, &opts, NULL)); + + git_remote_disconnect(remote); + git_remote_free(remote); + git_config_free(config); +} + static int transferProgressCallback(const git_indexer_progress *stats, void *payload) { bool *invoked = (bool *)payload; From 4a1c2bd8ced01f4b95b924a7c0348b5684a726f7 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 20 Feb 2024 12:37:58 +0000 Subject: [PATCH 045/111] diff: fix test for SHA256 support in diff_from_buffer --- tests/libgit2/diff/parse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/libgit2/diff/parse.c b/tests/libgit2/diff/parse.c index 0e5eb2809c8..59fc0280ed6 100644 --- a/tests/libgit2/diff/parse.c +++ b/tests/libgit2/diff/parse.c @@ -294,7 +294,7 @@ void test_diff_parse__eof_nl_missing(void) git_patch *ret_patch; git_diff_line *line; - cl_git_pass(git_diff_from_buffer(&diff, patch, strlen(patch))); + cl_git_pass(diff_from_buffer(&diff, patch, strlen(patch))); cl_git_pass(git_patch_from_diff(&ret_patch, diff, 0)); cl_assert((line = git_array_get(ret_patch->lines, 2)) != NULL); From 6b542105950b0d99b1d49d25d0db8f397e7e46e9 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 20 Feb 2024 13:42:38 +0000 Subject: [PATCH 046/111] merge: refactor merge tests for empty files treated as renames Don't munge the "trivial" tests, those are specifically about the "trivial" resolutions for git's tree merge. (For example, adding a file in a new branch and making no changes in the HEAD branch is something that can be handled _trivially_.) For tests of rename functionality, put them in the trees::rename tests. --- tests/libgit2/merge/trees/renames.c | 19 ++++++ tests/libgit2/merge/trees/trivial.c | 55 +++++------------- .../.gitted/logs/refs/heads/trivial-15 | 1 - .../.gitted/logs/refs/heads/trivial-15-branch | 2 - .../1d/6a96dd04ac7354ae6bb4ba24c514500fd0ec89 | Bin 279 -> 0 bytes .../merge-resolve/.gitted/objects/29 | Bin 0 -> 75 bytes .../48/320305de02310b9c4fd744243b9b0d1d5f11af | Bin 46 -> 0 bytes .../54/c9d15687fb4f56e08252662962d6d1dbc09d9d | 3 + .../57/16ca5987cbf97d6bb54920bea6adde242d87e6 | Bin 0 -> 19 bytes .../60/b12be2d2f57977ce83d8dfd32e2394ac1ba1a2 | Bin 0 -> 38 bytes .../67/06996f054c6af4fec7c77939d00e2f486dab4c | Bin 172 -> 0 bytes .../a6/5140d5ec9f47064f614ecf8e43776baa5c0c11 | Bin 0 -> 75 bytes .../ab/347abd8cda4a0e3b8bb42bb620c0c72c7df779 | Bin 0 -> 165 bytes .../bc/114411903fd2afaa4bb9b85ed13f27e37ac375 | Bin 0 -> 74 bytes .../c0/d1f321977d1c18e23a28c581fed6d17d0cc013 | Bin 273 -> 0 bytes .../c4/52c5eb5aacf204fc95a55d1eb9668736fecfb6 | Bin 163 -> 0 bytes .../c5/be2acabac675af81df8bb70400235af2a9c225 | 4 -- .../cd/d1cd02dbd164e9d18a644515bc2ca6551f13b4 | Bin 280 -> 0 bytes .../cd/edf9760406dc79e0c6a8899ce9f180ec2a23a0 | 2 + .../de/06afe070b65f94d7d791c39a6d389c58dda60d | Bin 0 -> 75 bytes .../e5/0a49f9558d09d4d3bfc108363bb24c127ed263 | Bin 0 -> 20 bytes .../ea/789495e0a72efadcd0f86a48f4c9ed435bb8a3 | 3 + .../.gitted/refs/heads/emptyfile_renames | 1 + .../refs/heads/emptyfile_renames-branch | 1 + .../.gitted/refs/heads/trivial-15 | 1 - .../.gitted/refs/heads/trivial-15-branch | 1 - 26 files changed, 45 insertions(+), 48 deletions(-) delete mode 100644 tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-15 delete mode 100644 tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-15-branch delete mode 100644 tests/resources/merge-resolve/.gitted/objects/1d/6a96dd04ac7354ae6bb4ba24c514500fd0ec89 create mode 100644 tests/resources/merge-resolve/.gitted/objects/29 delete mode 100644 tests/resources/merge-resolve/.gitted/objects/48/320305de02310b9c4fd744243b9b0d1d5f11af create mode 100644 tests/resources/merge-resolve/.gitted/objects/54/c9d15687fb4f56e08252662962d6d1dbc09d9d create mode 100644 tests/resources/merge-resolve/.gitted/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 create mode 100644 tests/resources/merge-resolve/.gitted/objects/60/b12be2d2f57977ce83d8dfd32e2394ac1ba1a2 delete mode 100644 tests/resources/merge-resolve/.gitted/objects/67/06996f054c6af4fec7c77939d00e2f486dab4c create mode 100644 tests/resources/merge-resolve/.gitted/objects/a6/5140d5ec9f47064f614ecf8e43776baa5c0c11 create mode 100644 tests/resources/merge-resolve/.gitted/objects/ab/347abd8cda4a0e3b8bb42bb620c0c72c7df779 create mode 100644 tests/resources/merge-resolve/.gitted/objects/bc/114411903fd2afaa4bb9b85ed13f27e37ac375 delete mode 100644 tests/resources/merge-resolve/.gitted/objects/c0/d1f321977d1c18e23a28c581fed6d17d0cc013 delete mode 100644 tests/resources/merge-resolve/.gitted/objects/c4/52c5eb5aacf204fc95a55d1eb9668736fecfb6 delete mode 100644 tests/resources/merge-resolve/.gitted/objects/c5/be2acabac675af81df8bb70400235af2a9c225 delete mode 100644 tests/resources/merge-resolve/.gitted/objects/cd/d1cd02dbd164e9d18a644515bc2ca6551f13b4 create mode 100644 tests/resources/merge-resolve/.gitted/objects/cd/edf9760406dc79e0c6a8899ce9f180ec2a23a0 create mode 100644 tests/resources/merge-resolve/.gitted/objects/de/06afe070b65f94d7d791c39a6d389c58dda60d create mode 100644 tests/resources/merge-resolve/.gitted/objects/e5/0a49f9558d09d4d3bfc108363bb24c127ed263 create mode 100644 tests/resources/merge-resolve/.gitted/objects/ea/789495e0a72efadcd0f86a48f4c9ed435bb8a3 create mode 100644 tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames create mode 100644 tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames-branch delete mode 100644 tests/resources/merge-resolve/.gitted/refs/heads/trivial-15 delete mode 100644 tests/resources/merge-resolve/.gitted/refs/heads/trivial-15-branch diff --git a/tests/libgit2/merge/trees/renames.c b/tests/libgit2/merge/trees/renames.c index a27945ee071..9507b51bc9e 100644 --- a/tests/libgit2/merge/trees/renames.c +++ b/tests/libgit2/merge/trees/renames.c @@ -350,3 +350,22 @@ void test_merge_trees_renames__cache_recomputation(void) git_tree_free(our_tree); git__free(data); } + +void test_merge_trees_renames__emptyfile_renames(void) +{ + git_index *index; + git_merge_options *opts = NULL; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", 1, "bar" }, + { 0100644, "60b12be2d2f57977ce83d8dfd32e2394ac1ba1a2", 3, "bar" }, + { 0100644, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", 0, "boo" }, + { 0100644, "e50a49f9558d09d4d3bfc108363bb24c127ed263", 0, "foo" }, + }; + + cl_git_pass(merge_trees_from_branches(&index, repo, + "emptyfile_renames", "emptyfile_renames-branch", + opts)); + cl_assert(merge_test_index(index, merge_index_entries, 4)); + git_index_free(index); +} diff --git a/tests/libgit2/merge/trees/trivial.c b/tests/libgit2/merge/trees/trivial.c index 7f9d918e806..287a53cfe7a 100644 --- a/tests/libgit2/merge/trees/trivial.c +++ b/tests/libgit2/merge/trees/trivial.c @@ -25,7 +25,7 @@ void test_merge_trees_trivial__cleanup(void) } -static int merge_trivial(git_index **index, const char *ours, const char *theirs, bool ancestor_use_parent) +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; @@ -42,12 +42,8 @@ static int merge_trivial(git_index **index, const char *ours, const char *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)); - if (!ancestor_use_parent) { - 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)); - } else { - cl_git_pass(git_commit_parent(&ancestor_commit, their_commit, 0)); - } + 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)); @@ -88,7 +84,7 @@ 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", false)); + 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); @@ -103,7 +99,7 @@ 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", false)); + 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); @@ -118,7 +114,7 @@ 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", false)); + 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); @@ -136,7 +132,7 @@ 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", false)); + 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); @@ -151,7 +147,7 @@ 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", false)); + 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); @@ -167,7 +163,7 @@ void test_merge_trees_trivial__6(void) const git_index_entry *entry; const git_index_reuc_entry *reuc; - cl_git_pass(merge_trivial(&result, "trivial-6", "trivial-6-branch", false)); + 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); @@ -185,7 +181,7 @@ void test_merge_trees_trivial__8(void) const git_index_entry *entry; const git_index_reuc_entry *reuc; - cl_git_pass(merge_trivial(&result, "trivial-8", "trivial-8-branch", false)); + 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); @@ -203,7 +199,7 @@ 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", false)); + 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); @@ -222,7 +218,7 @@ void test_merge_trees_trivial__10(void) const git_index_entry *entry; const git_index_reuc_entry *reuc; - cl_git_pass(merge_trivial(&result, "trivial-10", "trivial-10-branch", false)); + 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); @@ -240,7 +236,7 @@ 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", false)); + 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); @@ -259,7 +255,7 @@ void test_merge_trees_trivial__13(void) const git_index_entry *entry; git_oid expected_oid; - cl_git_pass(merge_trivial(&result, "trivial-13", "trivial-13-branch", false)); + 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", GIT_OID_SHA1)); @@ -278,7 +274,7 @@ void test_merge_trees_trivial__14(void) const git_index_entry *entry; git_oid expected_oid; - cl_git_pass(merge_trivial(&result, "trivial-14", "trivial-14-branch", false)); + 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", GIT_OID_SHA1)); @@ -296,7 +292,7 @@ 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", false)); + 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); @@ -308,22 +304,3 @@ void test_merge_trees_trivial__11(void) git_index_free(result); } - -/* 15: ancest:remote^, head:head, remote:remote = result:no merge */ -void test_merge_trees_trivial__15(void) -{ - git_index *result; - const git_index_entry *entry; - - /* Can't use merge_trivialfalsehere because a different ancestor is used. */ - cl_git_pass(merge_trivial(&result, "trivial-15", "trivial-15-branch", true)); - - cl_assert((entry = git_index_get_bypath(result, "another-new-empty-15.txt", GIT_INDEX_STAGE_NORMAL)) == NULL); - cl_assert((entry = git_index_get_bypath(result, "another-new-empty-15.txt", GIT_INDEX_STAGE_ANCESTOR))); - cl_assert((entry = git_index_get_bypath(result, "another-new-empty-15.txt", GIT_INDEX_STAGE_OURS)) == NULL); - cl_assert((entry = git_index_get_bypath(result, "another-new-empty-15.txt", GIT_INDEX_STAGE_THEIRS))); - cl_assert(merge_trivial_conflict_entrycount(result) == 2); - - git_index_free(result); -} - diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-15 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-15 deleted file mode 100644 index c71411b5f11..00000000000 --- a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-15 +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 c452c5eb5aacf204fc95a55d1eb9668736fecfb6 Gregory Herrero 1705326305 +0100 push diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-15-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-15-branch deleted file mode 100644 index f85a1e96dd9..00000000000 --- a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-15-branch +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 6706996f054c6af4fec7c77939d00e2f486dab4c Gregory Herrero 1705326302 +0100 push -6706996f054c6af4fec7c77939d00e2f486dab4c c5be2acabac675af81df8bb70400235af2a9c225 Gregory Herrero 1705326412 +0100 push diff --git a/tests/resources/merge-resolve/.gitted/objects/1d/6a96dd04ac7354ae6bb4ba24c514500fd0ec89 b/tests/resources/merge-resolve/.gitted/objects/1d/6a96dd04ac7354ae6bb4ba24c514500fd0ec89 deleted file mode 100644 index ec618090bdf04fd36e456a413c316a3e8ab4dca1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 279 zcmV+y0qFjC0V^p=O;s>9H)k+3FfcPQQAo_oFUd$P(#=aP*G|#Ju#>6y3}`-K3(#yyOh9hSfPPi(a@)TI=n&J9|yLsoh+rHQksRaubV7 zQi}*`$j?j5$xJTE%u5F=y`uhL(?g2@DO=Gq<_YDCljTJBx*O7ai1V#(6;Mw#=Y#jm>Q5A6JwpRT>i+fSTp_0?Qy}vHCa*HmqIm^0(}VfFxV+4 dKIH3JZ<^*6VlXr?Ff%bxNJ=bXNZ6?T=+f89@^j5M?qAkZp0Y-I;UcJX hT7Eu5xY()4_R~LWv$uLG>|3_>o{Db!GXS}e9ew4xB)b3r literal 0 HcmV?d00001 diff --git a/tests/resources/merge-resolve/.gitted/objects/48/320305de02310b9c4fd744243b9b0d1d5f11af b/tests/resources/merge-resolve/.gitted/objects/48/320305de02310b9c4fd744243b9b0d1d5f11af deleted file mode 100644 index 25236a804aca5db4fbe9f17b32e88afeb1156a22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46 zcmb#A Cyb`|v diff --git a/tests/resources/merge-resolve/.gitted/objects/54/c9d15687fb4f56e08252662962d6d1dbc09d9d b/tests/resources/merge-resolve/.gitted/objects/54/c9d15687fb4f56e08252662962d6d1dbc09d9d new file mode 100644 index 00000000000..7e0555b3ff6 --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/objects/54/c9d15687fb4f56e08252662962d6d1dbc09d9d @@ -0,0 +1,3 @@ +x¥NI +1ôœWô]žì/þÀ´Žãa&#~ß(þÀ:ÕFQ\—åÖAÛ¸éMtrš8K0Y›àK&Ž¥Xk¢w—É™"è$pPwj²vÕ\RðhÑgI=ŘK*SDaMÚ*zö¹68åµ ç¹.ºÂ^†ûaGù?µãº` +­q6YØâ€î8ÛåÏÅ3­W^áBM½ùiQ† \ No newline at end of file diff --git a/tests/resources/merge-resolve/.gitted/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 b/tests/resources/merge-resolve/.gitted/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 new file mode 100644 index 0000000000000000000000000000000000000000..cfc3920fb3f273219368fbff8644d599281d31f4 GIT binary patch literal 19 acmba@bd9V z+xM;eK=jc~)l01?6+$f)dD5?nDf2WDQjs8;iEzePCij<3Z`}*SX_g2nMU9cF&LK(V z#N`NC<_w_#tGImcYcv02x-@(GUo}%}{=B?lT;K5BEX!3#Y5T?F2`B~*0sSW+fIIYf a+x5RYuG{w5@^wHOishTEqk91!2~$WI3Rb58 diff --git a/tests/resources/merge-resolve/.gitted/objects/a6/5140d5ec9f47064f614ecf8e43776baa5c0c11 b/tests/resources/merge-resolve/.gitted/objects/a6/5140d5ec9f47064f614ecf8e43776baa5c0c11 new file mode 100644 index 0000000000000000000000000000000000000000..dc6cf6493cbe7b9ca4bccd8a6e212b4f7c385fa4 GIT binary patch literal 75 zcmV-R0JQ&j0V^p=O;s>6VlXr?Ff%bxNJ=bXcsBRZ>`fQ1IeT|&t}Bnaap*};@I|3_>o{Db!GXTtE9p+*ZB1!-N literal 0 HcmV?d00001 diff --git a/tests/resources/merge-resolve/.gitted/objects/ab/347abd8cda4a0e3b8bb42bb620c0c72c7df779 b/tests/resources/merge-resolve/.gitted/objects/ab/347abd8cda4a0e3b8bb42bb620c0c72c7df779 new file mode 100644 index 0000000000000000000000000000000000000000..d743a385c215b9e2a6b0073fb81115ba027e1ccd GIT binary patch literal 165 zcmV;W09yZe0i}*jssb?(gx@-+=)EBM?Ia-L#v6EnOuFN|MJ5s>Uf(F*z*<#(1x4jn z>;C|4!{6x4KrAs8mR>ICO0?w3N0m)XNG8PW0#QOq;+8sZ1Dwr~l*8c5Vv9qTK|778 zBWglYv}746l1PuSwhm`{rcQ8KTfMgp4m|1Go%mCH(>}L)1m$J0#yR6*C#S@u^X%Xs TXGC7pc4Zh1N^9Z;oFY*Th`UZ( literal 0 HcmV?d00001 diff --git a/tests/resources/merge-resolve/.gitted/objects/bc/114411903fd2afaa4bb9b85ed13f27e37ac375 b/tests/resources/merge-resolve/.gitted/objects/bc/114411903fd2afaa4bb9b85ed13f27e37ac375 new file mode 100644 index 0000000000000000000000000000000000000000..08941ff9538a4fea381f49ebd80332d417954c84 GIT binary patch literal 74 zcmV-Q0JZ;k0V^p=O;s>6VlXr?Ff%bxNXpM=csBRZ>`fQ1IeT|&t}Bnaap*};@I{Mj?>7TXPTRj!_En9m}MYsJK0M57_@mw7s{r~^~ literal 0 HcmV?d00001 diff --git a/tests/resources/merge-resolve/.gitted/objects/c0/d1f321977d1c18e23a28c581fed6d17d0cc013 b/tests/resources/merge-resolve/.gitted/objects/c0/d1f321977d1c18e23a28c581fed6d17d0cc013 deleted file mode 100644 index 4c20a212ade60c6f8046f0f5384bb06e145ce014..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 273 zcmV+s0q*{I0V^p=O;s>9vt%$dFfcPQQAjK;$5Ep%1PBLsVHGc5;`oqI?hAM z{rdDef8NUpvTr_GLlq}yB<7{3rs!to=_VB=<|Su>F;6I8oGd50*9~fRUTV2+ zYHmSErLLhVSnad9k7jSWc+J_nTXS7`)Qv+=dV(iH)fT1Z=9ghO{Ql!l+N8 zkTGc6^*G~R_FYU3NDhy&&R8ygh_N7J5=Yr3_6e($P%su?1|;*!D$i~?K&(uV5($wnQkS}f6h@CV zaLwRG2*M`6`@N~XsN9-NRjNr)yB(hsT0JG@ww#&ae Rrt9>X@?(d7Fh7HwP+9wjOc4M8 diff --git a/tests/resources/merge-resolve/.gitted/objects/c5/be2acabac675af81df8bb70400235af2a9c225 b/tests/resources/merge-resolve/.gitted/objects/c5/be2acabac675af81df8bb70400235af2a9c225 deleted file mode 100644 index 848f341c929..00000000000 --- a/tests/resources/merge-resolve/.gitted/objects/c5/be2acabac675af81df8bb70400235af2a9c225 +++ /dev/null @@ -1,4 +0,0 @@ -x¥ÎAj1 @Ѭ} -ïCyF–c(%»ö²,g2qn¡·o GÈö->_ú¾oÃÏ1†©úP‰3Õ -È’–ˆ¬T -žQbÀÐ*¨œ³{°é}xJ@9SƒˆBÜ°©$I)/¹èÜðL• ŠãŸ±vó_¦×nþ[ÍÔºÿ¸¾`Z_péÆrÓIúþéC‚¸Ì„@þÀ=õy;ôÝŽ¶ýn|;…x*ÆwY'÷äIS \ No newline at end of file diff --git a/tests/resources/merge-resolve/.gitted/objects/cd/d1cd02dbd164e9d18a644515bc2ca6551f13b4 b/tests/resources/merge-resolve/.gitted/objects/cd/d1cd02dbd164e9d18a644515bc2ca6551f13b4 deleted file mode 100644 index d2385b8f2353863209a288ba2cd3edc4ef04cfad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 280 zcmV+z0q6dB0V^p=O;s>9H)k+3FfcPQQAo_oFUd$P(#=aP*GO{^L*D ztK{>~g}6_UF=*TMIOAUST}%x~j)}3(ST29$SFD--<@UH>;hLAL e6Cd*RtT#<_EJ?4M_+j;{3r}q3#R3319++g`S&~%% diff --git a/tests/resources/merge-resolve/.gitted/objects/cd/edf9760406dc79e0c6a8899ce9f180ec2a23a0 b/tests/resources/merge-resolve/.gitted/objects/cd/edf9760406dc79e0c6a8899ce9f180ec2a23a0 new file mode 100644 index 00000000000..011b5b3e58c --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/objects/cd/edf9760406dc79e0c6a8899ce9f180ec2a23a0 @@ -0,0 +1,2 @@ +x¥A +B1C]÷³¤µÓö "n¼¨Ó)ÀþBÕë[ŘUò ÕR¤Ã^ÛMo̽3¨“c:d Úcö™òÄhCð·i2FÅGŸkƒKzÅ–à:ײÖŽ<èÇù[üÒŽj9 zBë0XØê!5è8ïü猒EºÄ;4~Ê*uQo—$DÝ \ No newline at end of file diff --git a/tests/resources/merge-resolve/.gitted/objects/de/06afe070b65f94d7d791c39a6d389c58dda60d b/tests/resources/merge-resolve/.gitted/objects/de/06afe070b65f94d7d791c39a6d389c58dda60d new file mode 100644 index 0000000000000000000000000000000000000000..28567b624d1d3579d581e99f2fb49ea86add007c GIT binary patch literal 75 zcmV-R0JQ&j0V^p=O;s>6VlXr?Ff%bxNJ=bXNZ6?T=+f89@^j5M?qAkZp0Y-I;UcJX hT7EvmQ!dY+p}m|}F7H3cVP?I_N2u;nG61#K9B#?!BSruK literal 0 HcmV?d00001 diff --git a/tests/resources/merge-resolve/.gitted/objects/e5/0a49f9558d09d4d3bfc108363bb24c127ed263 b/tests/resources/merge-resolve/.gitted/objects/e5/0a49f9558d09d4d3bfc108363bb24c127ed263 new file mode 100644 index 0000000000000000000000000000000000000000..251c5dfb20c2b73cf789ff29345c8faa2ee433cc GIT binary patch literal 20 bcmbæÌZ;§uB[‹¡JärN9z)ÚVÄb¼ú¥!Ç.Rj +:Ü +‡$ÈŘKª:¢°!c ÝçÞ\˃Fï½·[?àK–ûbyê“{;ƒõÞ œpA-wòϵ–S[_iÀì{WOG‰Rï \ No newline at end of file diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames b/tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames new file mode 100644 index 00000000000..89b4eea8ff5 --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames @@ -0,0 +1 @@ +ea789495e0a72efadcd0f86a48f4c9ed435bb8a3 diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames-branch b/tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames-branch new file mode 100644 index 00000000000..1c6a9f4db55 --- /dev/null +++ b/tests/resources/merge-resolve/.gitted/refs/heads/emptyfile_renames-branch @@ -0,0 +1 @@ +ab347abd8cda4a0e3b8bb42bb620c0c72c7df779 diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-15 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-15 deleted file mode 100644 index d6149eb8131..00000000000 --- a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-15 +++ /dev/null @@ -1 +0,0 @@ -c452c5eb5aacf204fc95a55d1eb9668736fecfb6 diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-15-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-15-branch deleted file mode 100644 index a255d1a3a95..00000000000 --- a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-15-branch +++ /dev/null @@ -1 +0,0 @@ -c5be2acabac675af81df8bb70400235af2a9c225 From a204533ded1e30f79b50d1e681b9884043091c12 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 20 Feb 2024 14:30:17 +0000 Subject: [PATCH 047/111] test: completely ignored diff is empty --- tests/libgit2/diff/workdir.c | 57 ++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/tests/libgit2/diff/workdir.c b/tests/libgit2/diff/workdir.c index c433b75cedc..504ece6fc91 100644 --- a/tests/libgit2/diff/workdir.c +++ b/tests/libgit2/diff/workdir.c @@ -2286,42 +2286,81 @@ void test_diff_workdir__to_index_reversed_content_loads(void) diff_expects exp; int use_iterator; char *pathspec = "new_file"; - + 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_SHOW_UNTRACKED_CONTENT | GIT_DIFF_REVERSE; 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(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(0, 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]); - + cl_assert_equal_i(1, exp.hunks); - + cl_assert_equal_i(1, exp.lines); cl_assert_equal_i(0, exp.line_ctxt); cl_assert_equal_i(0, exp.line_adds); cl_assert_equal_i(1, exp.line_dels); } - + + git_diff_free(diff); +} + +void test_diff_workdir__completely_ignored_shows_empty_diff(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff; + git_patch *patch; + git_buf buf = GIT_BUF_INIT; + char *pathspec = "subdir.txt"; + + opts.pathspec.strings = &pathspec; + opts.pathspec.count = 1; + + g_repo = cl_git_sandbox_init("status"); + cl_git_rewritefile("status/subdir.txt", "Is it a bird?\n\nIs it a plane?\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/subdir.txt b/subdir.txt\nindex e8ee89e..53c8db5 100644\n--- a/subdir.txt\n+++ b/subdir.txt\n@@ -1,2 +1,3 @@\n Is it a bird?\n+\n Is it a plane?\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("", buf.ptr); + + git_buf_dispose(&buf); + git_patch_free(patch); git_diff_free(diff); } From d704046712a762a79689c00767ef7602e86d630f Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 20 Feb 2024 14:30:26 +0000 Subject: [PATCH 048/111] diff: don't print header for empty (fully ignored) diff When a diff has no content -- for example, when there are only whitespace changes, and they're being ignored -- we need to avoid printing the file header. Queue the file header update until the first hunk is printed, and display it then. --- src/libgit2/diff_print.c | 51 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/src/libgit2/diff_print.c b/src/libgit2/diff_print.c index 32c93682679..daeefca50ca 100644 --- a/src/libgit2/diff_print.c +++ b/src/libgit2/diff_print.c @@ -29,6 +29,7 @@ typedef struct { const char *new_prefix; uint32_t flags; int id_strlen; + unsigned int sent_file_header; git_oid_t oid_type; int (*strcomp)(const char *, const char *); @@ -579,6 +580,30 @@ static int diff_print_patch_file_binary( return error; } +GIT_INLINE(int) should_force_header(const git_diff_delta *delta) +{ + if (delta->old_file.mode != delta->new_file.mode) + return 1; + + if (delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_COPIED) + return 1; + + return 0; +} + +GIT_INLINE(int) flush_file_header(const git_diff_delta *delta, diff_print_info *pi) +{ + if (pi->sent_file_header) + return 0; + + 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); + pi->sent_file_header = 1; + + return pi->print_cb(delta, NULL, &pi->line, pi->payload); +} + static int diff_print_patch_file( const git_diff_delta *delta, float progress, void *data) { @@ -609,15 +634,22 @@ static int diff_print_patch_file( (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0)) return 0; + pi->sent_file_header = 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); + /* + * pi->buf now contains the file header data. Go ahead and send it + * if there's useful data in there, like similarity. Otherwise, we + * should queue it to send when we see the first hunk. This prevents + * us from sending a header when all hunks were ignored. + */ + if (should_force_header(delta) && (error = flush_file_header(delta, pi)) < 0) + return error; - return pi->print_cb(delta, NULL, &pi->line, pi->payload); + return 0; } static int diff_print_patch_binary( @@ -632,6 +664,9 @@ static int diff_print_patch_binary( pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT; int error; + if ((error = flush_file_header(delta, pi)) < 0) + return error; + git_str_clear(pi->buf); if ((error = diff_print_patch_file_binary( @@ -651,10 +686,14 @@ static int diff_print_patch_hunk( void *data) { diff_print_info *pi = data; + int error; if (S_ISDIR(d->new_file.mode)) return 0; + if ((error = flush_file_header(d, pi)) < 0) + return error; + pi->line.origin = GIT_DIFF_LINE_HUNK_HDR; pi->line.content = h->header; pi->line.content_len = h->header_len; @@ -669,10 +708,14 @@ static int diff_print_patch_line( void *data) { diff_print_info *pi = data; + int error; if (S_ISDIR(delta->new_file.mode)) return 0; + if ((error = flush_file_header(delta, pi)) < 0) + return error; + return pi->print_cb(delta, hunk, line, pi->payload); } From aa31120f7c131cbe79f0f95b9bd5e745792ae47d Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sun, 6 Feb 2022 07:22:59 -0600 Subject: [PATCH 049/111] config: introduce GIT_CONFIG_LEVEL_WORKTREE Introduce the logical concept of a worktree-level config. The new value sits between _LOCAL and _APP to allow `git_config_get_*` to 'just work'. The assumption of how `git_config_get_*` works was tested experimentally by setting _WORKTREE to some nonsense value (like -3) and watching the new test fail. --- include/git2/config.h | 6 +++++- tests/libgit2/config/configlevel.c | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/include/git2/config.h b/include/git2/config.h index 332e62036d0..63293dbdaec 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -48,9 +48,13 @@ typedef enum { */ GIT_CONFIG_LEVEL_LOCAL = 5, + /** Worktree specific configuration file; $GIT_DIR/config.worktree + */ + GIT_CONFIG_LEVEL_WORKTREE = 6, + /** Application specific configuration file; freely defined by applications */ - GIT_CONFIG_LEVEL_APP = 6, + GIT_CONFIG_LEVEL_APP = 7, /** Represents the highest level available config file (i.e. the most * specific config file available that actually is loaded) diff --git a/tests/libgit2/config/configlevel.c b/tests/libgit2/config/configlevel.c index 8422d32c944..3534fbc2c84 100644 --- a/tests/libgit2/config/configlevel.c +++ b/tests/libgit2/config/configlevel.c @@ -71,3 +71,21 @@ void test_config_configlevel__fetching_a_level_from_an_empty_compound_config_ret git_config_free(cfg); } + +void test_config_configlevel__can_override_local_with_worktree(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/config19"), + GIT_CONFIG_LEVEL_WORKTREE, NULL, 1)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"), + 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); +} From c4df10285edc3bab45cd779293800e7ec19d9bb0 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sun, 6 Feb 2022 09:26:41 -0600 Subject: [PATCH 050/111] config: load worktree config from disk Now that GIT_CONFIG_LEVEL_WORKTREE exists logically, define and load $GIT_DIR/config.worktree into that level. --- include/git2/repository.h | 1 + src/libgit2/repository.c | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/include/git2/repository.h b/include/git2/repository.h index 0ff0856510f..9ad6176f940 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -499,6 +499,7 @@ typedef enum { GIT_REPOSITORY_ITEM_PACKED_REFS, GIT_REPOSITORY_ITEM_REMOTES, GIT_REPOSITORY_ITEM_CONFIG, + GIT_REPOSITORY_ITEM_WORKTREE_CONFIG, GIT_REPOSITORY_ITEM_INFO, GIT_REPOSITORY_ITEM_HOOKS, GIT_REPOSITORY_ITEM_LOGS, diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index 4eb3449133b..2859bd4800b 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -58,6 +58,7 @@ static const struct { { 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, "config.worktree", 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 }, @@ -1291,6 +1292,12 @@ static int load_config( return error; if (repo) { + if ((error = git_repository__item_path(&config_path, repo, GIT_REPOSITORY_ITEM_WORKTREE_CONFIG)) == 0) + error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_WORKTREE, repo, 0); + + if (error && error != GIT_ENOTFOUND) + goto on_error; + 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); From 5a86dfc17298bb80ff0dabe95f8e9fbdd6ad6e7c Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sun, 6 Feb 2022 10:00:40 -0600 Subject: [PATCH 051/111] repository: support the 'worktreeConfig' extension Now that worktree-level configuration can be read from disk and manipulated in memory, we should be able to say we support 'extensions.worktreeConfig'. --- src/libgit2/repository.c | 3 ++- tests/libgit2/core/opts.c | 12 ++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index 2859bd4800b..be938b5b9d0 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -1852,7 +1852,8 @@ static int check_repositoryformatversion(int *version, git_config *config) static const char *builtin_extensions[] = { "noop", - "objectformat" + "objectformat", + "worktreeconfig", }; static git_vector user_extensions = { 0, git__strcmp_cb }; diff --git a/tests/libgit2/core/opts.c b/tests/libgit2/core/opts.c index 1aa095adf4c..cbef29f991d 100644 --- a/tests/libgit2/core/opts.c +++ b/tests/libgit2/core/opts.c @@ -34,9 +34,10 @@ void test_core_opts__extensions_query(void) cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); - cl_assert_equal_sz(out.count, 2); + cl_assert_equal_sz(out.count, 3); cl_assert_equal_s("noop", out.strings[0]); cl_assert_equal_s("objectformat", out.strings[1]); + cl_assert_equal_s("worktreeconfig", out.strings[2]); git_strarray_dispose(&out); } @@ -49,10 +50,11 @@ void test_core_opts__extensions_add(void) 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, 3); + cl_assert_equal_sz(out.count, 4); cl_assert_equal_s("foo", out.strings[0]); cl_assert_equal_s("noop", out.strings[1]); cl_assert_equal_s("objectformat", out.strings[2]); + cl_assert_equal_s("worktreeconfig", out.strings[3]); git_strarray_dispose(&out); } @@ -65,10 +67,11 @@ void test_core_opts__extensions_remove(void) 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, 3); + cl_assert_equal_sz(out.count, 4); cl_assert_equal_s("bar", out.strings[0]); cl_assert_equal_s("baz", out.strings[1]); cl_assert_equal_s("objectformat", out.strings[2]); + cl_assert_equal_s("worktreeconfig", out.strings[3]); git_strarray_dispose(&out); } @@ -81,11 +84,12 @@ void test_core_opts__extensions_uniq(void) 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, 4); + cl_assert_equal_sz(out.count, 5); cl_assert_equal_s("bar", out.strings[0]); cl_assert_equal_s("foo", out.strings[1]); cl_assert_equal_s("noop", out.strings[2]); cl_assert_equal_s("objectformat", out.strings[3]); + cl_assert_equal_s("worktreeconfig", out.strings[4]); git_strarray_dispose(&out); } From 42e7e681de2dbd37b0b1018fc78a0e3a7ff1cf68 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Tue, 20 Feb 2024 21:38:52 -0600 Subject: [PATCH 052/111] config: refactor get_backend_for_use This structure provides for cleaner diffs in upcoming commits. --- src/libgit2/config.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index 04f3ec2fee2..e67839c3622 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -594,6 +594,7 @@ static int get_backend_for_use(git_config_backend **out, { size_t i; backend_internal *backend; + int error = 0; *out = NULL; @@ -605,16 +606,20 @@ static int get_backend_for_use(git_config_backend **out, } git_vector_foreach(&cfg->backends, i, backend) { - if (!backend->backend->readonly) { - *out = backend->backend; - return 0; - } + if (backend->backend->readonly) + continue; + + *out = backend->backend; + goto cleanup; } + error = GIT_ENOTFOUND; git_error_set(GIT_ERROR_CONFIG, "cannot %s value for '%s' when all config backends are readonly", uses[use], name); - return GIT_ENOTFOUND; + + cleanup: + return error; } int git_config_delete_entry(git_config *cfg, const char *name) From ea66ab07738642a3d7dae6d249a92f5fd1ff3e89 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Tue, 20 Feb 2024 21:39:11 -0600 Subject: [PATCH 053/111] config: skip worktree config in get_backend_for_use It would seem that `get_backend_for_use` is primarily used when writing config data -- either to set keys or delete them (based on the possible values of `backend_use`). When git-config(1) is used for side-effects, it will modify only the local (repository-level) configuration unless explicitly overridden. From git-config(1): --local For writing options: write to the repository .git/config file. This is the default behavior. `get_backend_for_use` does not have the ability to specify a config level and typically is expected (it seems) to 'do the right thing'. Taking its cue from git-config(1), don't update worktree-specific config unless it's the only option. If that functionality is needed by consumers, I assume they would find the appropriate backend with `git_config_open_level` and feed that `git_config` object through to the `git_config_set_*` functions (as demonstrated in the provided test). --- src/libgit2/config.c | 13 ++++++++++++- tests/libgit2/worktree/config.c | 30 +++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index e67839c3622..732d10830ec 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -593,12 +593,14 @@ static int get_backend_for_use(git_config_backend **out, git_config *cfg, const char *name, backend_use use) { size_t i; + size_t len; backend_internal *backend; int error = 0; *out = NULL; - if (git_vector_length(&cfg->backends) == 0) { + len = git_vector_length(&cfg->backends); + if (len == 0) { git_error_set(GIT_ERROR_CONFIG, "cannot %s value for '%s' when no config backends exist", uses[use], name); @@ -609,6 +611,15 @@ static int get_backend_for_use(git_config_backend **out, if (backend->backend->readonly) continue; + /* git-config doesn't update worktree-level config + unless specifically requested; follow suit. If you + specifically want to update that level, open the + single config level with git_config_open_level and + provide that as the config. In this case, there + will only be one backend in the config. */ + if (len > 1 && backend->level == GIT_CONFIG_LEVEL_WORKTREE) + continue; + *out = backend->backend; goto cleanup; } diff --git a/tests/libgit2/worktree/config.c b/tests/libgit2/worktree/config.c index 81dcfe1fa51..c23cd044fba 100644 --- a/tests/libgit2/worktree/config.c +++ b/tests/libgit2/worktree/config.c @@ -27,7 +27,7 @@ void test_worktree_config__open(void) git_config_free(cfg); } -void test_worktree_config__set(void) +void test_worktree_config__set_level_local(void) { git_config *cfg; int32_t val; @@ -45,3 +45,31 @@ void test_worktree_config__set(void) cl_assert_equal_i(val, 5); git_config_free(cfg); } + +void test_worktree_config__set_level_worktree(void) +{ + git_config *cfg; + git_config *wtcfg; + int32_t val; + + cl_git_pass(git_repository_config(&cfg, fixture.repo)); + cl_git_pass(git_config_open_level(&wtcfg, cfg, GIT_CONFIG_LEVEL_WORKTREE)); + cl_git_pass(git_config_set_int32(wtcfg, "worktree.specific", 42)); + + cl_git_pass(git_config_get_int32(&val, cfg, "worktree.specific")); + cl_assert_equal_i(val, 42); + + /* reopen to verify config has been set */ + git_config_free(cfg); + cl_git_pass(git_repository_config(&cfg, fixture.repo)); + cl_git_pass(git_config_get_int32(&val, cfg, "worktree.specific")); + cl_assert_equal_i(val, 42); + + cl_assert(git_config_delete_entry(cfg, "worktree.specific") == GIT_ENOTFOUND); + + cl_git_pass(git_config_delete_entry(wtcfg, "worktree.specific")); + cl_assert(git_config_get_int32(&val, cfg, "worktree.specific") == GIT_ENOTFOUND); + + git_config_free(cfg); + git_config_free(wtcfg); +} From 9b21c428cfebdb5cd047d7fd6505d8c32e47f101 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Tue, 20 Feb 2024 21:46:07 -0600 Subject: [PATCH 054/111] config: return only backends that contain the key When deleting a key from a repository with multiple config backends (like a --local config and a --worktree config), it's important to return the correct backend to modify. This patch ensures that we don't return a backend that is incapable of deleting a given piece of configuration when that is the required use. --- src/libgit2/config.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index 732d10830ec..6d3cd9836d3 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -596,6 +596,7 @@ static int get_backend_for_use(git_config_backend **out, size_t len; backend_internal *backend; int error = 0; + git_config_entry *entry = NULL; *out = NULL; @@ -620,6 +621,15 @@ static int get_backend_for_use(git_config_backend **out, if (len > 1 && backend->level == GIT_CONFIG_LEVEL_WORKTREE) continue; + /* If we're trying to delete a piece of config, make + sure the backend we return actually defines it in + the first place. */ + if (use == BACKEND_USE_DELETE) { + if (backend->backend->get(backend->backend, name, &entry) < 0) + continue; + git_config_entry_free(entry); + } + *out = backend->backend; goto cleanup; } From 498f443379f3fb248bb20c9ae6dce0f956dea327 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Tue, 20 Feb 2024 21:46:16 -0600 Subject: [PATCH 055/111] config: normalize key name in get_backend_for_use Before passing the config key name to backend->get(), it needs to be normalized first. Not all current callers are performing this normalization, so perform it centrally instead. Co-authored-by: Brian Lyles --- src/libgit2/config.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index 6d3cd9836d3..6994a8e2736 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -597,6 +597,7 @@ static int get_backend_for_use(git_config_backend **out, backend_internal *backend; int error = 0; git_config_entry *entry = NULL; + char *key = NULL; *out = NULL; @@ -625,7 +626,9 @@ static int get_backend_for_use(git_config_backend **out, sure the backend we return actually defines it in the first place. */ if (use == BACKEND_USE_DELETE) { - if (backend->backend->get(backend->backend, name, &entry) < 0) + if (key == NULL && (error = git_config__normalize_name(name, &key)) < 0) + goto cleanup; + if (backend->backend->get(backend->backend, key, &entry) < 0) continue; git_config_entry_free(entry); } @@ -640,6 +643,7 @@ static int get_backend_for_use(git_config_backend **out, uses[use], name); cleanup: + git__free(key); return error; } From 47f3953111a94bcd11b9798989bc702eda2cdb44 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Wed, 21 Feb 2024 13:51:36 -0600 Subject: [PATCH 056/111] config: use appropriate backend_use value This looks like a simple copy/paste error. --- src/libgit2/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index 6994a8e2736..908bf8d390a 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -1126,7 +1126,7 @@ int git_config_set_multivar(git_config *cfg, const char *name, const char *regex { git_config_backend *backend; - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) + if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET) < 0) return GIT_ENOTFOUND; return backend->set_multivar(backend, name, regexp, value); From 4cd2d1798ce2af4bff7ce8a76b6e97d702183567 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Tue, 20 Feb 2024 21:34:29 -0600 Subject: [PATCH 057/111] config: propagate errors from get_backend_for_use Now that get_backend_for_use can return other error codes (by virtue of key-name normalization), make sure to propagate the appropriate error code when used. --- src/libgit2/config.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index 908bf8d390a..d3193c95e26 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -650,9 +650,10 @@ static int get_backend_for_use(git_config_backend **out, int git_config_delete_entry(git_config *cfg, const char *name) { git_config_backend *backend; + int error = 0; - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) - return GIT_ENOTFOUND; + if ((error = get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE)) < 0) + return error; return backend->del(backend, name); } @@ -684,8 +685,8 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value) return -1; } - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET) < 0) - return GIT_ENOTFOUND; + if ((error = get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET)) < 0) + return error; error = backend->set(backend, name, value); @@ -1125,9 +1126,10 @@ int git_config_multivar_iterator_new(git_config_iterator **out, const git_config int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value) { git_config_backend *backend; + int error = 0; - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET) < 0) - return GIT_ENOTFOUND; + if ((error = get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET)) < 0) + return error; return backend->set_multivar(backend, name, regexp, value); } @@ -1135,9 +1137,10 @@ int git_config_set_multivar(git_config *cfg, const char *name, const char *regex int git_config_delete_multivar(git_config *cfg, const char *name, const char *regexp) { git_config_backend *backend; + int error = 0; - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) - return GIT_ENOTFOUND; + if ((error = get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE)) < 0) + return error; return backend->del_multivar(backend, name, regexp); } From 88608a5e9e4c0a617bac158e762b058e16b9b600 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 22 Feb 2024 14:06:06 +0000 Subject: [PATCH 058/111] worktree: update worktree refs test Ensure that we test for the expected error code (GIT_ENOTFOUND) on not found refs. In addition, we don't need to handle the error case with an explicit free; it's okay to leak memory on test failures. --- tests/libgit2/worktree/refs.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/tests/libgit2/worktree/refs.c b/tests/libgit2/worktree/refs.c index 6bcf7aa9dac..51e7b2b9463 100644 --- a/tests/libgit2/worktree/refs.c +++ b/tests/libgit2/worktree/refs.c @@ -69,23 +69,19 @@ void test_worktree_refs__list_worktree_specific(void) git_oid oid; cl_git_pass(git_reference_name_to_id(&oid, fixture.repo, "refs/heads/dir")); - cl_git_fail(git_reference_lookup(&ref, fixture.repo, "refs/bisect/a-bisect-ref")); + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, fixture.repo, "refs/bisect/a-bisect-ref")); cl_git_pass(git_reference_create( &new_branch, fixture.worktree, "refs/bisect/a-bisect-ref", &oid, 0, "test")); - cl_git_fail(git_reference_lookup(&ref, fixture.repo, "refs/bisect/a-bisect-ref")); + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, fixture.repo, "refs/bisect/a-bisect-ref")); cl_git_pass(git_reference_lookup(&ref, fixture.worktree, "refs/bisect/a-bisect-ref")); cl_git_pass(git_reference_list(&refs, fixture.repo)); cl_git_pass(git_reference_list(&wtrefs, fixture.worktree)); - if (refs.count + 1 != wtrefs.count) { - error = GIT_ERROR; - goto exit; - } + cl_assert_equal_sz(wtrefs.count, refs.count + 1); -exit: git_reference_free(ref); git_reference_free(new_branch); git_strarray_dispose(&refs); @@ -102,13 +98,13 @@ void test_worktree_refs__list_worktree_specific_hidden_in_main_repo(void) cl_git_pass( git_reference_name_to_id(&oid, fixture.repo, "refs/heads/dir")); - cl_git_fail(git_reference_lookup( + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup( &ref, fixture.worktree, "refs/bisect/a-bisect-ref")); cl_git_pass(git_reference_create( &new_branch, fixture.repo, "refs/bisect/a-bisect-ref", &oid, 0, "test")); - cl_git_fail(git_reference_lookup( + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup( &ref, fixture.worktree, "refs/bisect/a-bisect-ref")); cl_git_pass(git_reference_lookup( &ref, fixture.repo, "refs/bisect/a-bisect-ref")); @@ -116,12 +112,8 @@ void test_worktree_refs__list_worktree_specific_hidden_in_main_repo(void) cl_git_pass(git_reference_list(&refs, fixture.repo)); cl_git_pass(git_reference_list(&wtrefs, fixture.worktree)); - if (refs.count != wtrefs.count + 1) { - error = GIT_ERROR; - goto exit; - } + cl_assert_equal_sz(refs.count, wtrefs.count + 1); -exit: git_reference_free(ref); git_reference_free(new_branch); git_strarray_dispose(&refs); From bd242a05e29d74e44e5a5d98ff6023c0f0a4750e Mon Sep 17 00:00:00 2001 From: Sven Strickroth Date: Sat, 24 Feb 2024 14:33:26 +0100 Subject: [PATCH 059/111] Fix broken links Signed-off-by: Sven Strickroth --- README.md | 2 +- examples/general.c | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 19fe7196976..f646feb197c 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ Getting Help **Getting Help** If you have questions about the library, please be sure to check out the -[API documentation](http://libgit2.github.com/libgit2/). If you still have +[API documentation](http://libgit2.github.io/libgit2/). If you still have questions, reach out to us on Slack or post a question on [StackOverflow](http://stackoverflow.com/questions/tagged/libgit2) (with the `libgit2` tag). diff --git a/examples/general.c b/examples/general.c index 7f44cd78680..a3767809230 100644 --- a/examples/general.c +++ b/examples/general.c @@ -32,7 +32,7 @@ * check out [Chapter 10][pg] of the Pro Git book. * * [lg]: http://libgit2.github.com - * [ap]: http://libgit2.github.com/libgit2 + * [ap]: http://libgit2.github.io/libgit2 * [pg]: https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain */ @@ -97,7 +97,7 @@ int lg2_general(git_repository *repo, int argc, char** argv) * * (Try running this program against tests/resources/testrepo.git.) * - * [me]: http://libgit2.github.com/libgit2/#HEAD/group/repository + * [me]: http://libgit2.github.io/libgit2/#HEAD/group/repository */ repo_path = (argc > 1) ? argv[1] : "/opt/libgit2-test/.git"; @@ -173,7 +173,7 @@ static void oid_parsing(git_oid *oid) * working with raw objects, we'll need to get this structure from the * repository. * - * [odb]: http://libgit2.github.com/libgit2/#HEAD/group/odb + * [odb]: http://libgit2.github.io/libgit2/#HEAD/group/odb */ static void object_database(git_repository *repo, git_oid *oid) { @@ -262,7 +262,7 @@ static void object_database(git_repository *repo, git_oid *oid) * of them here. You can read about the other ones in the [commit API * docs][cd]. * - * [cd]: http://libgit2.github.com/libgit2/#HEAD/group/commit + * [cd]: http://libgit2.github.io/libgit2/#HEAD/group/commit */ static void commit_writing(git_repository *repo) { @@ -347,7 +347,7 @@ static void commit_writing(git_repository *repo) * data in the commit - the author (name, email, datetime), committer * (same), tree, message, encoding and parent(s). * - * [pco]: http://libgit2.github.com/libgit2/#HEAD/group/commit + * [pco]: http://libgit2.github.io/libgit2/#HEAD/group/commit */ static void commit_parsing(git_repository *repo) { @@ -418,7 +418,7 @@ static void commit_parsing(git_repository *repo) * functions very similarly to the commit lookup, parsing and creation * methods, since the objects themselves are very similar. * - * [tm]: http://libgit2.github.com/libgit2/#HEAD/group/tag + * [tm]: http://libgit2.github.io/libgit2/#HEAD/group/tag */ static void tag_parsing(git_repository *repo) { @@ -472,7 +472,7 @@ static void tag_parsing(git_repository *repo) * object type in Git, but a useful structure for parsing and traversing * tree entries. * - * [tp]: http://libgit2.github.com/libgit2/#HEAD/group/tree + * [tp]: http://libgit2.github.io/libgit2/#HEAD/group/tree */ static void tree_parsing(git_repository *repo) { @@ -536,7 +536,7 @@ static void tree_parsing(git_repository *repo) * from disk and writing it to the db and getting the oid back so you * don't have to do all those steps yourself. * - * [ba]: http://libgit2.github.com/libgit2/#HEAD/group/blob + * [ba]: http://libgit2.github.io/libgit2/#HEAD/group/blob */ static void blob_parsing(git_repository *repo) { @@ -578,7 +578,7 @@ static void blob_parsing(git_repository *repo) * that were ancestors of (reachable from) a given starting point. This * can allow you to create `git log` type functionality. * - * [rw]: http://libgit2.github.com/libgit2/#HEAD/group/revwalk + * [rw]: http://libgit2.github.io/libgit2/#HEAD/group/revwalk */ static void revwalking(git_repository *repo) { @@ -643,7 +643,7 @@ static void revwalking(git_repository *repo) * The [index file API][gi] allows you to read, traverse, update and write * the Git index file (sometimes thought of as the staging area). * - * [gi]: http://libgit2.github.com/libgit2/#HEAD/group/index + * [gi]: http://libgit2.github.io/libgit2/#HEAD/group/index */ static void index_walking(git_repository *repo) { @@ -687,7 +687,7 @@ static void index_walking(git_repository *repo) * references such as branches, tags and remote references (everything in * the .git/refs directory). * - * [ref]: http://libgit2.github.com/libgit2/#HEAD/group/reference + * [ref]: http://libgit2.github.io/libgit2/#HEAD/group/reference */ static void reference_listing(git_repository *repo) { @@ -740,7 +740,7 @@ static void reference_listing(git_repository *repo) * The [config API][config] allows you to list and update config values * in any of the accessible config file locations (system, global, local). * - * [config]: http://libgit2.github.com/libgit2/#HEAD/group/config + * [config]: http://libgit2.github.io/libgit2/#HEAD/group/config */ static void config_files(const char *repo_path, git_repository* repo) { From f9e055468d167cb4d6bde1ecd9e293d8a7d40db5 Mon Sep 17 00:00:00 2001 From: Sven Strickroth Date: Sat, 24 Feb 2024 14:47:40 +0100 Subject: [PATCH 060/111] Fix memory leaks Signed-off-by: Sven Strickroth --- src/libgit2/repository.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index 4eb3449133b..ba182eea4bb 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -3246,14 +3246,18 @@ int git_repository_set_workdir( if (git_fs_path_prettify_dir(&path, workdir, NULL) < 0) return -1; - if (repo->workdir && strcmp(repo->workdir, path.ptr) == 0) + if (repo->workdir && strcmp(repo->workdir, path.ptr) == 0) { + git_str_dispose(&path); return 0; + } if (update_gitlink) { git_config *config; - if (git_repository_config__weakptr(&config, repo) < 0) + if (git_repository_config__weakptr(&config, repo) < 0) { + git_str_dispose(&path); return -1; + } error = repo_write_gitlink(path.ptr, git_repository_path(repo), false); @@ -3275,6 +3279,7 @@ int git_repository_set_workdir( git__free(old_workdir); } + git_str_dispose(&path); return error; } From 2f6c4f75e374e08e5e1e2de294b77cc5cd7de232 Mon Sep 17 00:00:00 2001 From: Sven Strickroth Date: Sat, 24 Feb 2024 15:18:42 +0100 Subject: [PATCH 061/111] Fix broken regexp that matches submodule names containing ".path" Signed-off-by: Sven Strickroth --- src/libgit2/submodule.c | 4 ++-- tests/libgit2/submodule/lookup.c | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/libgit2/submodule.c b/src/libgit2/submodule.c index 95ea84fc233..830d41c7d22 100644 --- a/src/libgit2/submodule.c +++ b/src/libgit2/submodule.c @@ -196,7 +196,7 @@ static void free_submodule_names(git_strmap *names) */ static int load_submodule_names(git_strmap **out, git_repository *repo, git_config *cfg) { - const char *key = "submodule\\..*\\.path"; + const char *key = "^submodule\\..*\\.path$"; git_config_iterator *iter = NULL; git_config_entry *entry; git_str buf = GIT_STR_INIT; @@ -332,7 +332,7 @@ int git_submodule__lookup_with_cache( /* 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"; + const char *pattern = "^submodule\\..*\\.path$"; git_str path = GIT_STR_INIT; fbp_data data = { NULL, NULL }; diff --git a/tests/libgit2/submodule/lookup.c b/tests/libgit2/submodule/lookup.c index febb7dfad7d..14a624badef 100644 --- a/tests/libgit2/submodule/lookup.c +++ b/tests/libgit2/submodule/lookup.c @@ -401,6 +401,24 @@ void test_submodule_lookup__prefix_name(void) git_submodule_free(sm); } +/* ".path" in name of submodule */ +void test_submodule_lookup__dotpath_in_name(void) +{ + sm_lookup_data data; + + cl_git_rewritefile( + "submod2/.gitmodules", "[submodule \"kwb.pathdict\"]\n" + " path = kwb.pathdict\n" + " url = ../Test_App\n" + "[submodule \"fakin.path.app\"]\n" + " path = fakin.path.app\n" + " url = ../Test_App\n"); + + memset(&data, 0, sizeof(data)); + cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data)); + cl_assert_equal_i(9, data.count); +} + void test_submodule_lookup__renamed(void) { const char *newpath = "sm_actually_changed"; From cc35d987b412878b6f944243658fce88e845ec94 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 29 Feb 2024 18:25:24 +0000 Subject: [PATCH 062/111] ci: drop debugging ls --- ci/test.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/ci/test.sh b/ci/test.sh index 4217568226a..5f6b3c31d23 100755 --- a/ci/test.sh +++ b/ci/test.sh @@ -207,7 +207,6 @@ if should_run "SSH_TESTS"; then echo "Starting SSH server..." SSHD_DIR=`mktemp -d ${TMPDIR}/sshd.XXXXXXXX` cp -R "${SOURCE_DIR}/tests/resources/pushoptions.git" "${SSHD_DIR}/test.git" - ls -FlasR "${SSHD_DIR}" cat >"${SSHD_DIR}/sshd_config" <<-EOF Port 2222 From 1a3d6a889323f26acd2c572466356f9591f3147c Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 29 Feb 2024 18:24:38 +0000 Subject: [PATCH 063/111] ci: allow opting out of push-options tests The push-options online tests require push options support in the git itself that's on the system. Allow callers with old git to opt out. --- .github/actions/run-build/action.yml | 1 + ci/test.sh | 15 +++++---------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/actions/run-build/action.yml b/.github/actions/run-build/action.yml index bd0f59c9a88..9afcfb11e72 100644 --- a/.github/actions/run-build/action.yml +++ b/.github/actions/run-build/action.yml @@ -40,6 +40,7 @@ runs: -e PKG_CONFIG_PATH \ -e SKIP_NEGOTIATE_TESTS \ -e SKIP_SSH_TESTS \ + -e SKIP_PUSHOPTIONS_TESTS \ -e TSAN_OPTIONS \ -e UBSAN_OPTIONS \ ${{ inputs.container-version }} \ diff --git a/ci/test.sh b/ci/test.sh index 5f6b3c31d23..98093c6ec5c 100755 --- a/ci/test.sh +++ b/ci/test.sh @@ -18,6 +18,11 @@ if [[ "$(uname -s)" == MINGW* ]]; then SKIP_NTLM_TESTS=1 fi +# older versions of git don't support push options +if [ -z "$SKIP_PUSHOPTIONS_TESTS" ]; then + export GITTEST_PUSH_OPTIONS=true +fi + SOURCE_DIR=${SOURCE_DIR:-$( cd "$( dirname "${BASH_SOURCE[0]}" )" && dirname $( pwd ) )} BUILD_DIR=$(pwd) BUILD_PATH=${BUILD_PATH:=$PATH} @@ -324,10 +329,8 @@ if should_run "GITDAEMON_TESTS"; then echo "" export GITTEST_REMOTE_URL="git://localhost/test.git" - export GITTEST_PUSH_OPTIONS=true run_test gitdaemon unset GITTEST_REMOTE_URL - unset GITTEST_PUSH_OPTIONS echo "" echo "Running gitdaemon (namespace) tests" @@ -382,12 +385,10 @@ if should_run "NTLM_TESTS"; then export GITTEST_REMOTE_URL="http://localhost:9000/ntlm/test.git" export GITTEST_REMOTE_USER="foo" export GITTEST_REMOTE_PASS="baz" - export GITTEST_PUSH_OPTIONS=true run_test auth_clone_and_push unset GITTEST_REMOTE_URL unset GITTEST_REMOTE_USER unset GITTEST_REMOTE_PASS - unset GITTEST_PUSH_OPTIONS echo "" echo "Running NTLM tests (Apache emulation)" @@ -396,12 +397,10 @@ if should_run "NTLM_TESTS"; then export GITTEST_REMOTE_URL="http://localhost:9000/broken-ntlm/test.git" export GITTEST_REMOTE_USER="foo" export GITTEST_REMOTE_PASS="baz" - export GITTEST_PUSH_OPTIONS=true run_test auth_clone_and_push unset GITTEST_REMOTE_URL unset GITTEST_REMOTE_USER unset GITTEST_REMOTE_PASS - unset GITTEST_PUSH_OPTIONS fi if should_run "NEGOTIATE_TESTS" && -n "$GITTEST_NEGOTIATE_PASSWORD" ; then @@ -451,20 +450,16 @@ if should_run "SSH_TESTS"; then echo "" export GITTEST_REMOTE_URL="ssh://localhost:2222/$SSHD_DIR/test.git" - export GITTEST_PUSH_OPTIONS=true run_test ssh unset GITTEST_REMOTE_URL - unset GITTEST_PUSH_OPTIONS echo "" echo "Running ssh tests (scp-style paths)" echo "" export GITTEST_REMOTE_URL="[localhost:2222]:$SSHD_DIR/test.git" - export GITTEST_PUSH_OPTIONS=true run_test ssh unset GITTEST_REMOTE_URL - unset GITTEST_PUSH_OPTIONS unset GITTEST_SSH_CMD From 038f078d44e00df040cd2e24e038bce89253aa79 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 29 Feb 2024 21:16:56 +0000 Subject: [PATCH 064/111] ci: disable push options tests on older systems --- .github/workflows/nightly.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 43796859762..25249e7440d 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -178,6 +178,7 @@ jobs: CMAKE_OPTIONS: -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON PKG_CONFIG_PATH: /usr/local/lib/pkgconfig SKIP_NEGOTIATE_TESTS: true + SKIP_PUSHOPTIONS_TESTS: true - name: "Linux (CentOS 7, dynamically-loaded OpenSSL)" id: centos7-dynamicopenssl os: ubuntu-latest @@ -187,6 +188,7 @@ jobs: CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL-Dynamic -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON PKG_CONFIG_PATH: /usr/local/lib/pkgconfig SKIP_NEGOTIATE_TESTS: true + SKIP_PUSHOPTIONS_TESTS: true - name: "Linux (CentOS 8, OpenSSL)" id: centos8-openssl os: ubuntu-latest @@ -218,6 +220,7 @@ jobs: CMAKE_GENERATOR: Ninja CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL-Dynamic -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON RUN_INVASIVE_TESTS: true + SKIP_PUSHOPTIONS_TESTS: true os: ubuntu-latest - name: "Linux (x86, Bionic, Clang, OpenSSL)" container: @@ -229,6 +232,7 @@ jobs: CMAKE_GENERATOR: Ninja CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON RUN_INVASIVE_TESTS: true + SKIP_PUSHOPTIONS_TESTS: true os: ubuntu-latest - name: "Linux (x86, Bionic, GCC, OpenSSL)" container: @@ -239,6 +243,7 @@ jobs: CMAKE_GENERATOR: Ninja CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON RUN_INVASIVE_TESTS: true + SKIP_PUSHOPTIONS_TESTS: true os: ubuntu-latest - name: "Linux (arm32, Bionic, GCC, OpenSSL)" container: @@ -251,6 +256,7 @@ jobs: CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_GSSAPI=ON -DUSE_SSH=ON RUN_INVASIVE_TESTS: true SKIP_PROXY_TESTS: true + SKIP_PUSHOPTIONS_TESTS: true GITTEST_FLAKY_STAT: true os: ubuntu-latest - name: "Linux (arm64, Bionic, GCC, OpenSSL)" @@ -264,6 +270,7 @@ jobs: CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_GSSAPI=ON -DUSE_SSH=ON RUN_INVASIVE_TESTS: true SKIP_PROXY_TESTS: true + SKIP_PUSHOPTIONS_TESTS: true os: ubuntu-latest # Nightly builds: ensure we fallback when missing core functionality @@ -276,6 +283,7 @@ jobs: CC: gcc CMAKE_OPTIONS: -DTHREADSAFE=OFF -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON CMAKE_GENERATOR: Ninja + SKIP_PUSHOPTIONS_TESTS: true - name: "Linux (no mmap)" id: noble-nommap os: ubuntu-latest From 6c7df67071d779208c3e5ca3155d01640c8e477b Mon Sep 17 00:00:00 2001 From: Sven Strickroth Date: Fri, 1 Mar 2024 14:45:08 +0100 Subject: [PATCH 065/111] Consistently use libgit2.org Signed-off-by: Sven Strickroth --- README.md | 2 +- examples/README.md | 4 ++-- examples/general.c | 26 +++++++++++++------------- src/libgit2/git2.rc | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index f646feb197c..f4dbc789154 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ Getting Help **Getting Help** If you have questions about the library, please be sure to check out the -[API documentation](http://libgit2.github.io/libgit2/). If you still have +[API documentation](https://libgit2.org/libgit2/). If you still have questions, reach out to us on Slack or post a question on [StackOverflow](http://stackoverflow.com/questions/tagged/libgit2) (with the `libgit2` tag). diff --git a/examples/README.md b/examples/README.md index 769c4b2678a..0f1f253877a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -15,8 +15,8 @@ so there are no restrictions on their use. For annotated HTML versions, see the "Examples" section of: - http://libgit2.github.com/libgit2 + https://libgit2.org/libgit2 such as: - http://libgit2.github.com/libgit2/ex/HEAD/general.html + https://libgit2.org/libgit2/ex/HEAD/general.html diff --git a/examples/general.c b/examples/general.c index a3767809230..0275f84a24e 100644 --- a/examples/general.c +++ b/examples/general.c @@ -31,8 +31,8 @@ * Git Internals that you will need to know to work with Git at this level, * check out [Chapter 10][pg] of the Pro Git book. * - * [lg]: http://libgit2.github.com - * [ap]: http://libgit2.github.io/libgit2 + * [lg]: https://libgit2.org + * [ap]: https://libgit2.org/libgit2 * [pg]: https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain */ @@ -97,7 +97,7 @@ int lg2_general(git_repository *repo, int argc, char** argv) * * (Try running this program against tests/resources/testrepo.git.) * - * [me]: http://libgit2.github.io/libgit2/#HEAD/group/repository + * [me]: https://libgit2.org/libgit2/#HEAD/group/repository */ repo_path = (argc > 1) ? argv[1] : "/opt/libgit2-test/.git"; @@ -173,7 +173,7 @@ static void oid_parsing(git_oid *oid) * working with raw objects, we'll need to get this structure from the * repository. * - * [odb]: http://libgit2.github.io/libgit2/#HEAD/group/odb + * [odb]: https://libgit2.org/libgit2/#HEAD/group/odb */ static void object_database(git_repository *repo, git_oid *oid) { @@ -262,7 +262,7 @@ static void object_database(git_repository *repo, git_oid *oid) * of them here. You can read about the other ones in the [commit API * docs][cd]. * - * [cd]: http://libgit2.github.io/libgit2/#HEAD/group/commit + * [cd]: https://libgit2.org/libgit2/#HEAD/group/commit */ static void commit_writing(git_repository *repo) { @@ -347,7 +347,7 @@ static void commit_writing(git_repository *repo) * data in the commit - the author (name, email, datetime), committer * (same), tree, message, encoding and parent(s). * - * [pco]: http://libgit2.github.io/libgit2/#HEAD/group/commit + * [pco]: https://libgit2.org/libgit2/#HEAD/group/commit */ static void commit_parsing(git_repository *repo) { @@ -418,7 +418,7 @@ static void commit_parsing(git_repository *repo) * functions very similarly to the commit lookup, parsing and creation * methods, since the objects themselves are very similar. * - * [tm]: http://libgit2.github.io/libgit2/#HEAD/group/tag + * [tm]: https://libgit2.org/libgit2/#HEAD/group/tag */ static void tag_parsing(git_repository *repo) { @@ -472,7 +472,7 @@ static void tag_parsing(git_repository *repo) * object type in Git, but a useful structure for parsing and traversing * tree entries. * - * [tp]: http://libgit2.github.io/libgit2/#HEAD/group/tree + * [tp]: https://libgit2.org/libgit2/#HEAD/group/tree */ static void tree_parsing(git_repository *repo) { @@ -536,7 +536,7 @@ static void tree_parsing(git_repository *repo) * from disk and writing it to the db and getting the oid back so you * don't have to do all those steps yourself. * - * [ba]: http://libgit2.github.io/libgit2/#HEAD/group/blob + * [ba]: https://libgit2.org/libgit2/#HEAD/group/blob */ static void blob_parsing(git_repository *repo) { @@ -578,7 +578,7 @@ static void blob_parsing(git_repository *repo) * that were ancestors of (reachable from) a given starting point. This * can allow you to create `git log` type functionality. * - * [rw]: http://libgit2.github.io/libgit2/#HEAD/group/revwalk + * [rw]: https://libgit2.org/libgit2/#HEAD/group/revwalk */ static void revwalking(git_repository *repo) { @@ -643,7 +643,7 @@ static void revwalking(git_repository *repo) * The [index file API][gi] allows you to read, traverse, update and write * the Git index file (sometimes thought of as the staging area). * - * [gi]: http://libgit2.github.io/libgit2/#HEAD/group/index + * [gi]: https://libgit2.org/libgit2/#HEAD/group/index */ static void index_walking(git_repository *repo) { @@ -687,7 +687,7 @@ static void index_walking(git_repository *repo) * references such as branches, tags and remote references (everything in * the .git/refs directory). * - * [ref]: http://libgit2.github.io/libgit2/#HEAD/group/reference + * [ref]: https://libgit2.org/libgit2/#HEAD/group/reference */ static void reference_listing(git_repository *repo) { @@ -740,7 +740,7 @@ static void reference_listing(git_repository *repo) * The [config API][config] allows you to list and update config values * in any of the accessible config file locations (system, global, local). * - * [config]: http://libgit2.github.io/libgit2/#HEAD/group/config + * [config]: https://libgit2.org/libgit2/#HEAD/group/config */ static void config_files(const char *repo_path, git_repository* repo) { diff --git a/src/libgit2/git2.rc b/src/libgit2/git2.rc index d273afd7066..b94ecafd774 100644 --- a/src/libgit2/git2.rc +++ b/src/libgit2/git2.rc @@ -10,7 +10,7 @@ #endif #ifndef LIBGIT2_COMMENTS -# define LIBGIT2_COMMENTS "For more information visit http://libgit2.github.com/" +# define LIBGIT2_COMMENTS "For more information visit https://libgit2.org/" #endif #ifdef __GNUC__ From 70ab8216079469cc7a20443953dfa37b94e16671 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 2 Mar 2024 09:06:46 +0000 Subject: [PATCH 066/111] config: improve documentation for config levels --- include/git2/config.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/include/git2/config.h b/include/git2/config.h index 63293dbdaec..a711e1561dc 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -22,8 +22,15 @@ GIT_BEGIN_DECL /** * Priority level of a config file. + * * These priority levels correspond to the natural escalation logic - * (from higher to lower) when searching for config entries in git.git. + * (from higher to lower) when reading or searching for config entries + * in git.git. Meaning that for the same key, the configuration in + * the local configuration is preferred over the configuration in + * the system configuration file. + * + * Callers can add their own custom configuration, beginning at the + * `GIT_CONFIG_LEVEL_APP` level. * * git_config_open_default() and git_repository_config() honor those * priority levels as well. From 78f145366e3865ff6d20d9aecbbff57decaf773f Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 2 Mar 2024 09:07:12 +0000 Subject: [PATCH 067/111] config: update tests to use `fail_with` `cl_git_fail_with` is preferred over asserting a return value; the former gives more detailed information about the mismatch. --- tests/libgit2/worktree/config.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/libgit2/worktree/config.c b/tests/libgit2/worktree/config.c index c23cd044fba..2ddec25e264 100644 --- a/tests/libgit2/worktree/config.c +++ b/tests/libgit2/worktree/config.c @@ -65,10 +65,10 @@ void test_worktree_config__set_level_worktree(void) cl_git_pass(git_config_get_int32(&val, cfg, "worktree.specific")); cl_assert_equal_i(val, 42); - cl_assert(git_config_delete_entry(cfg, "worktree.specific") == GIT_ENOTFOUND); + cl_git_fail_with(GIT_ENOTFOUND, git_config_delete_entry(cfg, "worktree.specific")); cl_git_pass(git_config_delete_entry(wtcfg, "worktree.specific")); - cl_assert(git_config_get_int32(&val, cfg, "worktree.specific") == GIT_ENOTFOUND); + cl_git_fail_with(GIT_ENOTFOUND, git_config_get_int32(&val, cfg, "worktree.specific")); git_config_free(cfg); git_config_free(wtcfg); From 88b516713d7606c115d5253c535a4d556b899dde Mon Sep 17 00:00:00 2001 From: Laurence McGlashan Date: Mon, 4 Mar 2024 10:46:45 +0000 Subject: [PATCH 068/111] trace: Re-enable tests as tracing is now enabled by default --- tests/libgit2/trace/trace.c | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/tests/libgit2/trace/trace.c b/tests/libgit2/trace/trace.c index 097208bffd6..9fea57668a2 100644 --- a/tests/libgit2/trace/trace.c +++ b/tests/libgit2/trace/trace.c @@ -32,16 +32,11 @@ void test_trace_trace__cleanup(void) 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)); @@ -51,14 +46,10 @@ void test_trace_trace__can_reset(void) 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)); @@ -67,40 +58,25 @@ void test_trace_trace__can_unset(void) 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 } From 885744a77e8a727dd725f6a5fa8d80c199668fb7 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 5 Mar 2024 09:23:03 +0000 Subject: [PATCH 069/111] worktree: keep version number at 1 We aren't upgrading options struct version numbers when we make ABI changes. This is a future (v2.0+) plan for libgit2. In the meantime, keep the version numbers at 1. --- include/git2/worktree.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/git2/worktree.h b/include/git2/worktree.h index 3f1acbdb0ff..c41bc8e7bc1 100644 --- a/include/git2/worktree.h +++ b/include/git2/worktree.h @@ -86,11 +86,12 @@ typedef struct git_worktree_add_options { int lock; /**< lock newly created worktree */ int checkout_existing; /**< allow checkout of existing branch matching worktree name */ + git_reference *ref; /**< reference to use for the new worktree HEAD */ } git_worktree_add_options; -#define GIT_WORKTREE_ADD_OPTIONS_VERSION 2 -#define GIT_WORKTREE_ADD_OPTIONS_INIT {GIT_WORKTREE_ADD_OPTIONS_VERSION,0,0,NULL} +#define GIT_WORKTREE_ADD_OPTIONS_VERSION 1 +#define GIT_WORKTREE_ADD_OPTIONS_INIT { GIT_WORKTREE_ADD_OPTIONS_VERSION } /** * Initialize git_worktree_add_options structure From 7bbb07f83f36dbdad6b2fa0b45eb7a87a1a818f9 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 8 Mar 2024 16:51:11 +0000 Subject: [PATCH 070/111] worktree: dry up the refdb_fs iterator The refdb iterator has grown a bit and has a few concerns intermixed. Refactor a bit to improve readability while trying to avoid unnecessary git_str allocations. --- src/libgit2/refdb_fs.c | 201 +++++++++++++++++++++++------------------ 1 file changed, 111 insertions(+), 90 deletions(-) diff --git a/src/libgit2/refdb_fs.c b/src/libgit2/refdb_fs.c index 4628e01dc99..14368e34775 100644 --- a/src/libgit2/refdb_fs.c +++ b/src/libgit2/refdb_fs.c @@ -794,125 +794,146 @@ static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter) git__free(iter); } -static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) +struct iter_load_context { + refdb_fs_backend *backend; + refdb_fs_iter *iter; + + /* + * If we have a glob with a prefix (eg `refs/heads/ *`) then we can + * optimize our prefix to avoid walking refs that we know won't + * match. This is that prefix. + */ + const char *ref_prefix; + size_t ref_prefix_len; + + /* Temporary variables to avoid unnecessary allocations */ + git_str ref_name; + git_str path; +}; + +static void iter_load_optimize_prefix(struct iter_load_context *ctx) { - 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); + const char *pos, *last_sep = NULL; - if (!backend->commonpath) /* do nothing if no commonpath for loose refs */ - return 0; + if (!ctx->iter->glob) + return; - 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; - } + for (pos = ctx->iter->glob; *pos; pos++) { + switch (*pos) { + case '?': + case '*': + case '[': + case '\\': break; + case '/': + last_sep = pos; + /* FALLTHROUGH */ + default: + continue; } - if (last_sep) { - ref_prefix = iter->glob; - ref_prefix_len = (last_sep - ref_prefix) + 1; - } + break; } - 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 (last_sep) { + ctx->ref_prefix = ctx->iter->glob; + ctx->ref_prefix_len = (last_sep - ctx->ref_prefix) + 1; } +} + +static int iter_load_paths( + struct iter_load_context *ctx, + const char *root_path, + bool worktree) +{ + git_iterator *fsit = NULL; + git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT; + const git_index_entry *entry; + int error = 0; + + fsit_opts.flags = ctx->backend->iterator_flags; + + git_str_clear(&ctx->path); + git_str_puts(&ctx->path, root_path); + git_str_put(&ctx->path, ctx->ref_prefix, ctx->ref_prefix_len); + + if ((error = git_iterator_for_filesystem(&fsit, ctx->path.ptr, &fsit_opts)) < 0) { + /* + * Subdirectories - either glob provided or per-worktree refs - need + * not exist. + */ + if ((worktree || ctx->iter->glob) && error == GIT_ENOTFOUND) + error = 0; - if ((error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) { - git_str_dispose(&path); - return (iter->glob && error == GIT_ENOTFOUND)? 0 : error; + goto done; } - error = git_str_sets(&path, ref_prefix); + git_str_clear(&ctx->ref_name); + git_str_put(&ctx->ref_name, ctx->ref_prefix, ctx->ref_prefix_len); - while (!error && !git_iterator_advance(&entry, fsit)) { - const char *ref_name; + while (git_iterator_advance(&entry, fsit) == 0) { char *ref_dup; - git_str_truncate(&path, ref_prefix_len); - git_str_puts(&path, entry->path); - ref_name = git_str_cstr(&path); - if (git_repository_is_worktree(backend->repo) == 1 && is_per_worktree_ref(ref_name)) + git_str_truncate(&ctx->ref_name, ctx->ref_prefix_len); + git_str_puts(&ctx->ref_name, entry->path); + + if (worktree) { + if (!is_per_worktree_ref(ctx->ref_name.ptr)) + continue; + } else { + if (git_repository_is_worktree(ctx->backend->repo) && + is_per_worktree_ref(ctx->ref_name.ptr)) + continue; + } + + if (git__suffixcmp(ctx->ref_name.ptr, ".lock") == 0) continue; - if (git__suffixcmp(ref_name, ".lock") == 0 || - (iter->glob && wildmatch(iter->glob, ref_name, 0) != 0)) + if (ctx->iter->glob && wildmatch(ctx->iter->glob, ctx->ref_name.ptr, 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); + ref_dup = git_pool_strdup(&ctx->iter->pool, ctx->ref_name.ptr); + GIT_ERROR_CHECK_ALLOC(ref_dup); + + if ((error = git_vector_insert(&ctx->iter->loose, ref_dup)) < 0) + goto done; } - if (!error && git_repository_is_worktree(backend->repo) == 1) { - git_iterator_free(fsit); - git_str_clear(&path); - if ((error = git_str_puts(&path, backend->gitpath)) < 0 || - (error = git_str_put(&path, ref_prefix, ref_prefix_len)) < 0 || - !git_fs_path_exists(git_str_cstr(&path))) { - git_str_dispose(&path); - return error; - } +done: + git_iterator_free(fsit); + 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; - } +#define iter_load_context_init(b, i) { b, i, GIT_REFS_DIR, CONST_STRLEN(GIT_REFS_DIR) } +#define iter_load_context_dispose(ctx) do { \ + git_str_dispose(&((ctx)->path)); \ + git_str_dispose(&((ctx)->ref_name)); \ +} while(0) - error = git_str_sets(&path, ref_prefix); +static int iter_load_loose_paths( + refdb_fs_backend *backend, + refdb_fs_iter *iter) +{ + struct iter_load_context ctx = iter_load_context_init(backend, iter); - while (!error && !git_iterator_advance(&entry, fsit)) { - const char *ref_name; - char *ref_dup; + int error = 0; - git_str_truncate(&path, ref_prefix_len); - git_str_puts(&path, entry->path); - ref_name = git_str_cstr(&path); + if (!backend->commonpath) + return 0; - if (!is_per_worktree_ref(ref_name)) - continue; + iter_load_optimize_prefix(&ctx); - if (git__suffixcmp(ref_name, ".lock") == 0 || - (iter->glob && - wildmatch(iter->glob, ref_name, 0) != 0)) - continue; + if ((error = iter_load_paths(&ctx, + backend->commonpath, false)) < 0) + goto done; - ref_dup = git_pool_strdup(&iter->pool, ref_name); - if (!ref_dup) - error = -1; - else - error = git_vector_insert( - &iter->loose, ref_dup); - } + if (git_repository_is_worktree(backend->repo)) { + if ((error = iter_load_paths(&ctx, + backend->gitpath, true)) < 0) + goto done; } - git_iterator_free(fsit); - git_str_dispose(&path); - +done: + iter_load_context_dispose(&ctx); return error; } From b499a3465c121c9f942648adadd56495727b38d9 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 3 Mar 2024 21:48:06 +0000 Subject: [PATCH 071/111] config: introduce a writers list Configuration read order and write order should be separated. For example: configuration readers have a worktree level that is higher priority than the local configuration _for reads_. Despite that, the worktree configuration is not written to by default. Use a new list, `writers`, to identify the write target. To do this, we need another level of indirection between backend instances (which are refcounted and shared amongst different git_config instances) and the config reader/writer list (since each of those different git_config instances can have different read/write priorities). --- include/git2/config.h | 9 + src/libgit2/config.c | 597 ++++++++++++++++++++------------------- src/libgit2/config.h | 3 +- src/libgit2/repository.c | 8 +- 4 files changed, 327 insertions(+), 290 deletions(-) diff --git a/include/git2/config.h b/include/git2/config.h index a711e1561dc..32361431326 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -32,6 +32,10 @@ GIT_BEGIN_DECL * Callers can add their own custom configuration, beginning at the * `GIT_CONFIG_LEVEL_APP` level. * + * Writes, by default, occur in the highest priority level backend + * that is writable. This ordering can be overridden with + * `git_config_set_writeorder`. + * * git_config_open_default() and git_repository_config() honor those * priority levels as well. */ @@ -307,6 +311,11 @@ GIT_EXTERN(int) git_config_open_level( */ GIT_EXTERN(int) git_config_open_global(git_config **out, git_config *config); +GIT_EXTERN(int) git_config_set_writeorder( + git_config *cfg, + git_config_level_t *levels, + size_t len); + /** * Create a snapshot of the configuration * diff --git a/src/libgit2/config.c b/src/libgit2/config.c index d3193c95e26..fdf32e9669a 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -22,6 +22,32 @@ #include +/* + * A refcounted instance of a config_backend that can be shared across + * a configuration instance, any snapshots, and individual configuration + * levels (from `git_config_open_level`). + */ +typedef struct { + git_refcount rc; + git_config_backend *backend; +} backend_instance; + +/* + * An entry in the readers or writers vector in the configuration. + * This is kept separate from the refcounted instance so that different + * views of the configuration can have different notions of levels or + * write orders. + * + * (eg, a standard configuration has a priority ordering of writers, a + * snapshot has *no* writers, and an individual level has a single + * writer.) + */ +typedef struct { + backend_instance *instance; + git_config_level_t level; + int write_order; +} backend_entry; + void git_config_entry_free(git_config_entry *entry) { if (!entry) @@ -30,75 +56,75 @@ void git_config_entry_free(git_config_entry *entry) 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) +static void backend_instance_free(backend_instance *instance) { git_config_backend *backend; - backend = internal->backend; + backend = instance->backend; backend->free(backend); - git__free(internal); + git__free(instance); } -static void config_free(git_config *cfg) +static void config_free(git_config *config) { size_t i; - backend_internal *internal; + backend_entry *entry; - for (i = 0; i < cfg->backends.length; ++i) { - internal = git_vector_get(&cfg->backends, i); - GIT_REFCOUNT_DEC(internal, backend_internal_free); + git_vector_foreach(&config->readers, i, entry) { + GIT_REFCOUNT_DEC(entry->instance, backend_instance_free); + git__free(entry); } - git_vector_free(&cfg->backends); - - git__memzero(cfg, sizeof(*cfg)); - git__free(cfg); + git_vector_free(&config->readers); + git_vector_free(&config->writers); + git__free(config); } -void git_config_free(git_config *cfg) +void git_config_free(git_config *config) { - if (cfg == NULL) + if (config == NULL) return; - GIT_REFCOUNT_DEC(cfg, config_free); + GIT_REFCOUNT_DEC(config, config_free); } -static int config_backend_cmp(const void *a, const void *b) +static int reader_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); + const backend_entry *a = _a; + const backend_entry *b = _b; - return bk_b->level - bk_a->level; + return b->level - a->level; } -int git_config_new(git_config **out) +static int writer_cmp(const void *_a, const void *_b) { - git_config *cfg; + const backend_entry *a = _a; + const backend_entry *b = _b; - cfg = git__malloc(sizeof(git_config)); - GIT_ERROR_CHECK_ALLOC(cfg); + return b->write_order - a->write_order; +} - memset(cfg, 0x0, sizeof(git_config)); +int git_config_new(git_config **out) +{ + git_config *config; - if (git_vector_init(&cfg->backends, 3, config_backend_cmp) < 0) { - git__free(cfg); + config = git__calloc(1, sizeof(git_config)); + GIT_ERROR_CHECK_ALLOC(config); + + if (git_vector_init(&config->readers, 8, reader_cmp) < 0 || + git_vector_init(&config->writers, 8, writer_cmp) < 0) { + config_free(config); return -1; } - *out = cfg; - GIT_REFCOUNT_INC(cfg); + GIT_REFCOUNT_INC(config); + + *out = config; return 0; } int git_config_add_file_ondisk( - git_config *cfg, + git_config *config, const char *path, git_config_level_t level, const git_repository *repo, @@ -108,7 +134,7 @@ int git_config_add_file_ondisk( struct stat st; int res; - GIT_ASSERT_ARG(cfg); + GIT_ASSERT_ARG(config); GIT_ASSERT_ARG(path); res = p_stat(path, &st); @@ -120,7 +146,7 @@ int git_config_add_file_ondisk( if (git_config_backend_from_file(&file, path) < 0) return -1; - if ((res = git_config_add_backend(cfg, file, level, repo, force)) < 0) { + if ((res = git_config_add_backend(config, file, level, repo, force)) < 0) { /* * free manually; the file is not owned by the config * instance yet and will not be freed on cleanup @@ -154,7 +180,7 @@ int git_config_snapshot(git_config **out, git_config *in) { int error = 0; size_t i; - backend_internal *internal; + backend_entry *entry; git_config *config; *out = NULL; @@ -162,18 +188,20 @@ int git_config_snapshot(git_config **out, git_config *in) if (git_config_new(&config) < 0) return -1; - git_vector_foreach(&in->backends, i, internal) { + git_vector_foreach(&in->readers, i, entry) { git_config_backend *b; - if ((error = internal->backend->snapshot(&b, internal->backend)) < 0) + if ((error = entry->instance->backend->snapshot(&b, entry->instance->backend)) < 0) break; - if ((error = git_config_add_backend(config, b, internal->level, NULL, 0)) < 0) { + if ((error = git_config_add_backend(config, b, entry->level, NULL, 0)) < 0) { b->free(b); break; } } + git_config_set_writeorder(config, NULL, 0); + if (error < 0) git_config_free(config); else @@ -183,141 +211,162 @@ int git_config_snapshot(git_config **out, git_config *in) } static int find_backend_by_level( - backend_internal **out, - const git_config *cfg, + backend_instance **out, + const git_config *config, git_config_level_t level) { - int pos = -1; - backend_internal *internal; + backend_entry *entry, *found = NULL; 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. + /* + * 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; + found = git_vector_get(&config->readers, 0); } else { - git_vector_foreach(&cfg->backends, i, internal) { - if (internal->level == level) - pos = (int)i; + git_vector_foreach(&config->readers, i, entry) { + if (entry->level == level) { + found = entry; + break; + } } } - if (pos == -1) { + if (!found) { git_error_set(GIT_ERROR_CONFIG, - "no configuration exists for the given level '%i'", (int)level); + "no configuration exists for the given level '%d'", level); return GIT_ENOTFOUND; } - *out = git_vector_get(&cfg->backends, pos); - + *out = entry->instance; return 0; } -static int duplicate_level(void **old_raw, void *new_raw) +static int duplicate_level(void **_old, void *_new) { - backend_internal **old = (backend_internal **)old_raw; + backend_entry **old = (backend_entry **)_old; - GIT_UNUSED(new_raw); + GIT_UNUSED(_new); - git_error_set(GIT_ERROR_CONFIG, "there already exists a configuration for the given level (%i)", (int)(*old)->level); + git_error_set(GIT_ERROR_CONFIG, "configuration at level %d already exists", (*old)->level); return GIT_EEXISTS; } static void try_remove_existing_backend( - git_config *cfg, + git_config *config, git_config_level_t level) { - int pos = -1; - backend_internal *internal; + backend_entry *entry, *found = NULL; size_t i; - git_vector_foreach(&cfg->backends, i, internal) { - if (internal->level == level) - pos = (int)i; + git_vector_foreach(&config->readers, i, entry) { + if (entry->level == level) { + git_vector_remove(&config->readers, i); + found = entry; + break; + } } - if (pos == -1) + if (!found) return; - internal = git_vector_get(&cfg->backends, pos); - - if (git_vector_remove(&cfg->backends, pos) < 0) - return; + git_vector_foreach(&config->writers, i, entry) { + if (entry->level == level) { + git_vector_remove(&config->writers, i); + break; + } + } - GIT_REFCOUNT_DEC(internal, backend_internal_free); + GIT_REFCOUNT_DEC(found->instance, backend_instance_free); + git__free(found); } -static int git_config__add_internal( - git_config *cfg, - backend_internal *internal, +static int git_config__add_instance( + git_config *config, + backend_instance *instance, git_config_level_t level, int force) { + backend_entry *entry; int result; /* delete existing config backend for level if it exists */ if (force) - try_remove_existing_backend(cfg, level); + try_remove_existing_backend(config, level); - if ((result = git_vector_insert_sorted(&cfg->backends, - internal, &duplicate_level)) < 0) - return result; + entry = git__malloc(sizeof(backend_entry)); + GIT_ERROR_CHECK_ALLOC(entry); - git_vector_sort(&cfg->backends); - internal->backend->cfg = cfg; + entry->instance = instance; + entry->level = level; + entry->write_order = level; - GIT_REFCOUNT_INC(internal); + if ((result = git_vector_insert_sorted(&config->readers, + entry, &duplicate_level)) < 0 || + (result = git_vector_insert_sorted(&config->writers, + entry, NULL)) < 0) { + git__free(entry); + return result; + } + + GIT_REFCOUNT_INC(entry->instance); return 0; } -int git_config_open_global(git_config **cfg_out, git_config *cfg) +int git_config_open_global(git_config **out, git_config *config) { - if (!git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_XDG)) + int error; + + error = git_config_open_level(out, config, GIT_CONFIG_LEVEL_XDG); + + if (error == 0) return 0; + else if (error != GIT_ENOTFOUND) + return error; - return git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_GLOBAL); + return git_config_open_level(out, config, GIT_CONFIG_LEVEL_GLOBAL); } int git_config_open_level( - git_config **cfg_out, - const git_config *cfg_parent, + git_config **out, + const git_config *parent, git_config_level_t level) { - git_config *cfg; - backend_internal *internal; + git_config *config; + backend_instance *instance; int res; - if ((res = find_backend_by_level(&internal, cfg_parent, level)) < 0) + if ((res = find_backend_by_level(&instance, parent, level)) < 0) return res; - if ((res = git_config_new(&cfg)) < 0) + if ((res = git_config_new(&config)) < 0) return res; - if ((res = git_config__add_internal(cfg, internal, level, true)) < 0) { - git_config_free(cfg); + if ((res = git_config__add_instance(config, instance, level, true)) < 0) { + git_config_free(config); return res; } - *cfg_out = cfg; + *out = config; return 0; } int git_config_add_backend( - git_config *cfg, + git_config *config, git_config_backend *backend, git_config_level_t level, const git_repository *repo, int force) { - backend_internal *internal; + backend_instance *instance; int result; - GIT_ASSERT_ARG(cfg); + GIT_ASSERT_ARG(config); GIT_ASSERT_ARG(backend); GIT_ERROR_CHECK_VERSION(backend, GIT_CONFIG_BACKEND_VERSION, "git_config_backend"); @@ -325,22 +374,50 @@ int git_config_add_backend( if ((result = backend->open(backend, level, repo)) < 0) return result; - internal = git__malloc(sizeof(backend_internal)); - GIT_ERROR_CHECK_ALLOC(internal); + instance = git__calloc(1, sizeof(backend_instance)); + GIT_ERROR_CHECK_ALLOC(instance); - memset(internal, 0x0, sizeof(backend_internal)); + instance->backend = backend; + instance->backend->cfg = config; - internal->backend = backend; - internal->level = level; - - if ((result = git_config__add_internal(cfg, internal, level, force)) < 0) { - git__free(internal); + if ((result = git_config__add_instance(config, instance, level, force)) < 0) { + git__free(instance); return result; } return 0; } +int git_config_set_writeorder( + git_config *config, + git_config_level_t *levels, + size_t len) +{ + backend_entry *entry; + size_t i, j; + + GIT_ASSERT(len < INT_MAX); + + git_vector_foreach(&config->readers, i, entry) { + bool found = false; + + for (j = 0; j < len; j++) { + if (levels[j] == entry->level) { + entry->write_order = (int)j; + found = true; + break; + } + } + + if (!found) + entry->write_order = -1; + } + + git_vector_sort(&config->writers); + + return 0; +} + /* * Loop over all the variables */ @@ -348,19 +425,18 @@ int git_config_add_backend( typedef struct { git_config_iterator parent; git_config_iterator *current; - const git_config *cfg; + const git_config *config; git_regexp regex; size_t i; } all_iter; -static int find_next_backend(size_t *out, const git_config *cfg, size_t i) +static int find_next_backend(size_t *out, const git_config *config, size_t i) { - backend_internal *internal; + backend_entry *entry; for (; i > 0; --i) { - internal = git_vector_get(&cfg->backends, i - 1); - if (!internal || !internal->backend) - continue; + entry = git_vector_get(&config->readers, i - 1); + GIT_ASSERT(entry && entry->instance && entry->instance->backend); *out = i; return 0; @@ -369,16 +445,16 @@ static int find_next_backend(size_t *out, const git_config *cfg, size_t i) return -1; } -static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter) +static int all_iter_next(git_config_entry **out, git_config_iterator *_iter) { all_iter *iter = (all_iter *) _iter; - backend_internal *internal; + backend_entry *entry; git_config_backend *backend; size_t i; int error = 0; if (iter->current != NULL && - (error = iter->current->next(entry, iter->current)) == 0) { + (error = iter->current->next(out, iter->current)) == 0) { return 0; } @@ -386,11 +462,11 @@ static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter) return error; do { - if (find_next_backend(&i, iter->cfg, iter->i) < 0) + if (find_next_backend(&i, iter->config, iter->i) < 0) return GIT_ITEROVER; - internal = git_vector_get(&iter->cfg->backends, i - 1); - backend = internal->backend; + entry = git_vector_get(&iter->config->readers, i - 1); + backend = entry->instance->backend; iter->i = i - 1; if (iter->current) @@ -404,7 +480,7 @@ static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter) if (error < 0) return error; - error = iter->current->next(entry, iter->current); + error = iter->current->next(out, iter->current); /* If this backend is empty, then keep going */ if (error == GIT_ITEROVER) continue; @@ -423,7 +499,7 @@ static int all_iter_glob_next(git_config_entry **entry, git_config_iterator *_it /* * We use the "normal" function to grab the next one across - * backends and then apply the regex + * readers and then apply the regex */ while ((error = all_iter_next(entry, _iter)) == 0) { /* skip non-matching keys if regexp was provided */ @@ -455,7 +531,7 @@ static void all_iter_glob_free(git_config_iterator *_iter) all_iter_free(_iter); } -int git_config_iterator_new(git_config_iterator **out, const git_config *cfg) +int git_config_iterator_new(git_config_iterator **out, const git_config *config) { all_iter *iter; @@ -465,21 +541,21 @@ int git_config_iterator_new(git_config_iterator **out, const git_config *cfg) iter->parent.free = all_iter_free; iter->parent.next = all_iter_next; - iter->i = cfg->backends.length; - iter->cfg = cfg; + iter->i = config->readers.length; + iter->config = config; *out = (git_config_iterator *) iter; return 0; } -int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cfg, const char *regexp) +int git_config_iterator_glob_new(git_config_iterator **out, const git_config *config, const char *regexp) { all_iter *iter; int result; if (regexp == NULL) - return git_config_iterator_new(out, cfg); + return git_config_iterator_new(out, config); iter = git__calloc(1, sizeof(all_iter)); GIT_ERROR_CHECK_ALLOC(iter); @@ -491,8 +567,8 @@ int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cf iter->parent.next = all_iter_glob_next; iter->parent.free = all_iter_glob_free; - iter->i = cfg->backends.length; - iter->cfg = cfg; + iter->i = config->readers.length; + iter->config = config; *out = (git_config_iterator *) iter; @@ -500,9 +576,9 @@ int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cf } int git_config_foreach( - const git_config *cfg, git_config_foreach_cb cb, void *payload) + const git_config *config, git_config_foreach_cb cb, void *payload) { - return git_config_foreach_match(cfg, NULL, cb, payload); + return git_config_foreach_match(config, NULL, cb, payload); } int git_config_backend_foreach_match( @@ -548,7 +624,7 @@ int git_config_backend_foreach_match( } int git_config_foreach_match( - const git_config *cfg, + const git_config *config, const char *regexp, git_config_foreach_cb cb, void *payload) @@ -557,7 +633,7 @@ int git_config_foreach_match( git_config_iterator *iter; git_config_entry *entry; - if ((error = git_config_iterator_glob_new(&iter, cfg, regexp)) < 0) + if ((error = git_config_iterator_glob_new(&iter, config, regexp)) < 0) return error; while (!(error = git_config_next(&entry, iter))) { @@ -579,103 +655,52 @@ int git_config_foreach_match( * 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) +static git_config_backend *get_writer(git_config *config) { + backend_entry *entry; size_t i; - size_t len; - backend_internal *backend; - int error = 0; - git_config_entry *entry = NULL; - char *key = NULL; - *out = NULL; - - len = git_vector_length(&cfg->backends); - if (len == 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) + git_vector_foreach(&config->writers, i, entry) { + if (entry->instance->backend->readonly) continue; - /* git-config doesn't update worktree-level config - unless specifically requested; follow suit. If you - specifically want to update that level, open the - single config level with git_config_open_level and - provide that as the config. In this case, there - will only be one backend in the config. */ - if (len > 1 && backend->level == GIT_CONFIG_LEVEL_WORKTREE) + if (entry->write_order < 0) continue; - /* If we're trying to delete a piece of config, make - sure the backend we return actually defines it in - the first place. */ - if (use == BACKEND_USE_DELETE) { - if (key == NULL && (error = git_config__normalize_name(name, &key)) < 0) - goto cleanup; - if (backend->backend->get(backend->backend, key, &entry) < 0) - continue; - git_config_entry_free(entry); - } - - *out = backend->backend; - goto cleanup; + return entry->instance->backend; } - error = GIT_ENOTFOUND; - git_error_set(GIT_ERROR_CONFIG, - "cannot %s value for '%s' when all config backends are readonly", - uses[use], name); - - cleanup: - git__free(key); - return error; + return NULL; } -int git_config_delete_entry(git_config *cfg, const char *name) +int git_config_delete_entry(git_config *config, const char *name) { git_config_backend *backend; - int error = 0; - if ((error = get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE)) < 0) - return error; + if ((backend = get_writer(config)) == NULL) + return GIT_ENOTFOUND; return backend->del(backend, name); } -int git_config_set_int64(git_config *cfg, const char *name, int64_t value) +int git_config_set_int64(git_config *config, 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); + return git_config_set_string(config, name, str_value); } -int git_config_set_int32(git_config *cfg, const char *name, int32_t value) +int git_config_set_int32(git_config *config, const char *name, int32_t value) { - return git_config_set_int64(cfg, name, (int64_t)value); + return git_config_set_int64(config, name, (int64_t)value); } -int git_config_set_bool(git_config *cfg, const char *name, int value) +int git_config_set_bool(git_config *config, const char *name, int value) { - return git_config_set_string(cfg, name, value ? "true" : "false"); + return git_config_set_string(config, name, value ? "true" : "false"); } -int git_config_set_string(git_config *cfg, const char *name, const char *value) +int git_config_set_string(git_config *config, const char *name, const char *value) { int error; git_config_backend *backend; @@ -685,13 +710,15 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value) return -1; } - if ((error = get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET)) < 0) - return error; + if ((backend = get_writer(config)) == NULL) { + git_error_set(GIT_ERROR_CONFIG, "cannot set '%s': the configuration is read-only", name); + 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)); + if (!error && GIT_REFCOUNT_OWNER(config) != NULL) + git_repository__configmap_lookup_cache_clear(GIT_REFCOUNT_OWNER(config)); return error; } @@ -745,16 +772,17 @@ enum { static int get_entry( git_config_entry **out, - const git_config *cfg, + const git_config *config, const char *name, bool normalize_name, int want_errors) { + backend_entry *entry; + git_config_backend *backend; int res = GIT_ENOTFOUND; const char *key = name; char *normalized = NULL; size_t i; - backend_internal *internal; *out = NULL; @@ -765,11 +793,12 @@ static int get_entry( } res = GIT_ENOTFOUND; - git_vector_foreach(&cfg->backends, i, internal) { - if (!internal || !internal->backend) - continue; + git_vector_foreach(&config->readers, i, entry) { + GIT_ASSERT(entry->instance && entry->instance->backend); + + backend = entry->instance->backend; + res = backend->get(backend, key, out); - res = internal->backend->get(internal->backend, key, out); if (res != GIT_ENOTFOUND) break; } @@ -777,9 +806,9 @@ static int get_entry( git__free(normalized); cleanup: - if (res == GIT_ENOTFOUND) + if (res == GIT_ENOTFOUND) { res = (want_errors > GET_ALL_ERRORS) ? 0 : config_error_notfound(name); - else if (res && (want_errors == GET_NO_ERRORS)) { + } else if (res && (want_errors == GET_NO_ERRORS)) { git_error_clear(); res = 0; } @@ -788,24 +817,24 @@ static int get_entry( } int git_config_get_entry( - git_config_entry **out, const git_config *cfg, const char *name) + git_config_entry **out, const git_config *config, const char *name) { - return get_entry(out, cfg, name, true, GET_ALL_ERRORS); + return get_entry(out, config, name, true, GET_ALL_ERRORS); } int git_config__lookup_entry( git_config_entry **out, - const git_config *cfg, + const git_config *config, const char *key, bool no_errors) { return get_entry( - out, cfg, key, false, no_errors ? GET_NO_ERRORS : GET_NO_MISSING); + out, config, key, false, no_errors ? GET_NO_ERRORS : GET_NO_MISSING); } int git_config_get_mapped( int *out, - const git_config *cfg, + const git_config *config, const char *name, const git_configmap *maps, size_t map_n) @@ -813,7 +842,7 @@ int git_config_get_mapped( git_config_entry *entry; int ret; - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + if ((ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0) return ret; ret = git_config_lookup_map_value(out, maps, map_n, entry->value); @@ -822,12 +851,12 @@ int git_config_get_mapped( return ret; } -int git_config_get_int64(int64_t *out, const git_config *cfg, const char *name) +int git_config_get_int64(int64_t *out, const git_config *config, const char *name) { git_config_entry *entry; int ret; - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + if ((ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0) return ret; ret = git_config_parse_int64(out, entry->value); @@ -836,12 +865,12 @@ int git_config_get_int64(int64_t *out, const git_config *cfg, const char *name) return ret; } -int git_config_get_int32(int32_t *out, const git_config *cfg, const char *name) +int git_config_get_int32(int32_t *out, const git_config *config, const char *name) { git_config_entry *entry; int ret; - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + if ((ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0) return ret; ret = git_config_parse_int32(out, entry->value); @@ -850,12 +879,12 @@ int git_config_get_int32(int32_t *out, const git_config *cfg, const char *name) return ret; } -int git_config_get_bool(int *out, const git_config *cfg, const char *name) +int git_config_get_bool(int *out, const git_config *config, const char *name) { git_config_entry *entry; int ret; - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + if ((ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0) return ret; ret = git_config_parse_bool(out, entry->value); @@ -864,16 +893,15 @@ int git_config_get_bool(int *out, const git_config *cfg, const char *name) return ret; } -static int is_readonly(const git_config *cfg) +static int is_readonly(const git_config *config) { + backend_entry *entry; size_t i; - backend_internal *internal; - git_vector_foreach(&cfg->backends, i, internal) { - if (!internal || !internal->backend) - continue; + git_vector_foreach(&config->writers, i, entry) { + GIT_ASSERT(entry->instance && entry->instance->backend); - if (!internal->backend->readonly) + if (!entry->instance->backend->readonly) return 0; } @@ -904,21 +932,21 @@ int git_config_parse_path(git_buf *out, const char *value) int git_config_get_path( git_buf *out, - const git_config *cfg, + const git_config *config, const char *name) { - GIT_BUF_WRAP_PRIVATE(out, git_config__get_path, cfg, name); + GIT_BUF_WRAP_PRIVATE(out, git_config__get_path, config, name); } int git_config__get_path( git_str *out, - const git_config *cfg, + const git_config *config, const char *name) { git_config_entry *entry; int error; - if ((error = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + if ((error = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0) return error; error = git_config__parse_path(out, entry->value); @@ -928,17 +956,17 @@ int git_config__get_path( } int git_config_get_string( - const char **out, const git_config *cfg, const char *name) + const char **out, const git_config *config, const char *name) { git_config_entry *entry; int ret; - if (!is_readonly(cfg)) { + if (!is_readonly(config)) { 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); + ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS); *out = !ret ? (entry->value ? entry->value : "") : NULL; git_config_entry_free(entry); @@ -947,22 +975,22 @@ int git_config_get_string( } int git_config_get_string_buf( - git_buf *out, const git_config *cfg, const char *name) + git_buf *out, const git_config *config, const char *name) { - GIT_BUF_WRAP_PRIVATE(out, git_config__get_string_buf, cfg, name); + GIT_BUF_WRAP_PRIVATE(out, git_config__get_string_buf, config, name); } int git_config__get_string_buf( - git_str *out, const git_config *cfg, const char *name) + git_str *out, const git_config *config, const char *name) { git_config_entry *entry; int ret; const char *str; GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(cfg); + GIT_ASSERT_ARG(config); - ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS); + ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS); str = !ret ? (entry->value ? entry->value : "") : NULL; if (str) @@ -974,12 +1002,12 @@ int git_config__get_string_buf( } char *git_config__get_string_force( - const git_config *cfg, const char *key, const char *fallback_value) + const git_config *config, const char *key, const char *fallback_value) { git_config_entry *entry; char *ret; - get_entry(&entry, cfg, key, false, GET_NO_ERRORS); + get_entry(&entry, config, 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); @@ -987,12 +1015,12 @@ char *git_config__get_string_force( } int git_config__get_bool_force( - const git_config *cfg, const char *key, int fallback_value) + const git_config *config, const char *key, int fallback_value) { int val = fallback_value; git_config_entry *entry; - get_entry(&entry, cfg, key, false, GET_NO_ERRORS); + get_entry(&entry, config, key, false, GET_NO_ERRORS); if (entry && git_config_parse_bool(&val, entry->value) < 0) git_error_clear(); @@ -1002,12 +1030,12 @@ int git_config__get_bool_force( } int git_config__get_int_force( - const git_config *cfg, const char *key, int fallback_value) + const git_config *config, 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); + get_entry(&entry, config, key, false, GET_NO_ERRORS); if (entry && git_config_parse_int32(&val, entry->value) < 0) git_error_clear(); @@ -1017,14 +1045,14 @@ int git_config__get_int_force( } int git_config_get_multivar_foreach( - const git_config *cfg, const char *name, const char *regexp, + const git_config *config, 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) + if ((err = git_config_multivar_iterator_new(&iter, config, name, regexp)) < 0) return err; found = 0; @@ -1086,13 +1114,13 @@ static void multivar_iter_free(git_config_iterator *_iter) git__free(iter); } -int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp) +int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *config, 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) + if ((error = git_config_iterator_new(&inner, config)) < 0) return error; iter = git__calloc(1, sizeof(multivar_iter)); @@ -1123,24 +1151,24 @@ int git_config_multivar_iterator_new(git_config_iterator **out, const git_config return error; } -int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value) +int git_config_set_multivar(git_config *config, const char *name, const char *regexp, const char *value) { git_config_backend *backend; - int error = 0; - if ((error = get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET)) < 0) - return error; + if ((backend = get_writer(config)) == NULL) { + git_error_set(GIT_ERROR_CONFIG, "cannot set '%s': the configuration is read-only", name); + 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) +int git_config_delete_multivar(git_config *config, const char *name, const char *regexp) { git_config_backend *backend; - int error = 0; - if ((error = get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE)) < 0) - return error; + if ((backend = get_writer(config)) == NULL) + return GIT_ENOTFOUND; return backend->del_multivar(backend, name, regexp); } @@ -1251,78 +1279,71 @@ int git_config__global_location(git_str *buf) int git_config_open_default(git_config **out) { int error; - git_config *cfg = NULL; + git_config *config = NULL; git_str buf = GIT_STR_INIT; - if ((error = git_config_new(&cfg)) < 0) + if ((error = git_config_new(&config)) < 0) return error; if (!git_config__find_global(&buf) || !git_config__global_location(&buf)) { - error = git_config_add_file_ondisk(cfg, buf.ptr, + error = git_config_add_file_ondisk(config, buf.ptr, GIT_CONFIG_LEVEL_GLOBAL, NULL, 0); } if (!error && !git_config__find_xdg(&buf)) - error = git_config_add_file_ondisk(cfg, buf.ptr, + error = git_config_add_file_ondisk(config, buf.ptr, GIT_CONFIG_LEVEL_XDG, NULL, 0); if (!error && !git_config__find_system(&buf)) - error = git_config_add_file_ondisk(cfg, buf.ptr, + error = git_config_add_file_ondisk(config, buf.ptr, GIT_CONFIG_LEVEL_SYSTEM, NULL, 0); if (!error && !git_config__find_programdata(&buf)) - error = git_config_add_file_ondisk(cfg, buf.ptr, + error = git_config_add_file_ondisk(config, buf.ptr, GIT_CONFIG_LEVEL_PROGRAMDATA, NULL, 0); git_str_dispose(&buf); if (error) { - git_config_free(cfg); - cfg = NULL; + git_config_free(config); + config = NULL; } - *out = cfg; + *out = config; return error; } -int git_config_lock(git_transaction **out, git_config *cfg) +int git_config_lock(git_transaction **out, git_config *config) { - int error; git_config_backend *backend; - backend_internal *internal; + int error; - GIT_ASSERT_ARG(cfg); + GIT_ASSERT_ARG(config); - 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; + if ((backend = get_writer(config)) == NULL) { + git_error_set(GIT_ERROR_CONFIG, "cannot lock: the configuration is read-only"); + return GIT_ENOTFOUND; } - backend = internal->backend; if ((error = backend->lock(backend)) < 0) return error; - return git_transaction_config_new(out, cfg); + return git_transaction_config_new(out, config); } -int git_config_unlock(git_config *cfg, int commit) +int git_config_unlock(git_config *config, int commit) { git_config_backend *backend; - backend_internal *internal; - GIT_ASSERT_ARG(cfg); + GIT_ASSERT_ARG(config); - 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; + if ((backend = get_writer(config)) == NULL) { + git_error_set(GIT_ERROR_CONFIG, "cannot unlock: the configuration is read-only"); + return GIT_ENOTFOUND; } - backend = internal->backend; - return backend->unlock(backend, commit); } diff --git a/src/libgit2/config.h b/src/libgit2/config.h index 01b84b157f7..3119f7d748b 100644 --- a/src/libgit2/config.h +++ b/src/libgit2/config.h @@ -24,7 +24,8 @@ struct git_config { git_refcount rc; - git_vector backends; + git_vector readers; + git_vector writers; }; extern int git_config__global_location(git_str *buf); diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index be938b5b9d0..97671fe40b1 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -1282,9 +1282,10 @@ static int load_config( const char *system_config_path, const char *programdata_path) { - int error; git_str config_path = GIT_STR_INIT; git_config *cfg = NULL; + git_config_level_t write_order; + int error; GIT_ASSERT_ARG(out); @@ -1333,6 +1334,11 @@ static int load_config( git_error_clear(); /* clear any lingering ENOTFOUND errors */ + write_order = GIT_CONFIG_LEVEL_LOCAL; + + if ((error = git_config_set_writeorder(cfg, &write_order, 1)) < 0) + goto on_error; + *out = cfg; return 0; From fb187bd00367463587ea88f351b71822d3f6057c Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 4 Mar 2024 13:28:21 +0000 Subject: [PATCH 072/111] config: return GIT_EREADONLY on read-only configs Introduce `GIT_EREADONLY` and return it when a read-only configuration is attempted to be mutated. This is preferred over the prior `GIT_ENOTFOUND` which is not accurate. --- include/git2/errors.h | 3 ++- src/libgit2/config.c | 12 ++++++------ tests/libgit2/config/readonly.c | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/include/git2/errors.h b/include/git2/errors.h index bdace8c43dd..5dda9fa0362 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -60,7 +60,8 @@ typedef enum { GIT_EAPPLYFAIL = -35, /**< Patch application failed */ GIT_EOWNER = -36, /**< The object is not owned by the current user */ GIT_TIMEOUT = -37, /**< The operation timed out */ - GIT_EUNCHANGED = -38 /**< There were no changes */ + GIT_EUNCHANGED = -38, /**< There were no changes */ + GIT_EREADONLY = -39 /**< The subject is read-only */ } git_error_code; /** diff --git a/src/libgit2/config.c b/src/libgit2/config.c index fdf32e9669a..7e38d86936f 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -678,7 +678,7 @@ int git_config_delete_entry(git_config *config, const char *name) git_config_backend *backend; if ((backend = get_writer(config)) == NULL) - return GIT_ENOTFOUND; + return GIT_EREADONLY; return backend->del(backend, name); } @@ -712,7 +712,7 @@ int git_config_set_string(git_config *config, const char *name, const char *valu if ((backend = get_writer(config)) == NULL) { git_error_set(GIT_ERROR_CONFIG, "cannot set '%s': the configuration is read-only", name); - return GIT_ENOTFOUND; + return GIT_EREADONLY; } error = backend->set(backend, name, value); @@ -1157,7 +1157,7 @@ int git_config_set_multivar(git_config *config, const char *name, const char *re if ((backend = get_writer(config)) == NULL) { git_error_set(GIT_ERROR_CONFIG, "cannot set '%s': the configuration is read-only", name); - return GIT_ENOTFOUND; + return GIT_EREADONLY; } return backend->set_multivar(backend, name, regexp, value); @@ -1168,7 +1168,7 @@ int git_config_delete_multivar(git_config *config, const char *name, const char git_config_backend *backend; if ((backend = get_writer(config)) == NULL) - return GIT_ENOTFOUND; + return GIT_EREADONLY; return backend->del_multivar(backend, name, regexp); } @@ -1324,7 +1324,7 @@ int git_config_lock(git_transaction **out, git_config *config) if ((backend = get_writer(config)) == NULL) { git_error_set(GIT_ERROR_CONFIG, "cannot lock: the configuration is read-only"); - return GIT_ENOTFOUND; + return GIT_EREADONLY; } if ((error = backend->lock(backend)) < 0) @@ -1341,7 +1341,7 @@ int git_config_unlock(git_config *config, int commit) if ((backend = get_writer(config)) == NULL) { git_error_set(GIT_ERROR_CONFIG, "cannot unlock: the configuration is read-only"); - return GIT_ENOTFOUND; + return GIT_EREADONLY; } return backend->unlock(backend, commit); diff --git a/tests/libgit2/config/readonly.c b/tests/libgit2/config/readonly.c index a8901e394c0..483f83a85fd 100644 --- a/tests/libgit2/config/readonly.c +++ b/tests/libgit2/config/readonly.c @@ -24,7 +24,7 @@ void test_config_readonly__writing_to_readonly_fails(void) 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_git_fail_with(GIT_EREADONLY, git_config_set_string(cfg, "foo.bar", "baz")); cl_assert(!git_fs_path_exists("global")); } From 5f85714e1f80a16f95c646fbb2124d8f963c0a2c Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 4 Mar 2024 14:55:40 +0000 Subject: [PATCH 073/111] config: don't git_config_free an aborted transaction When a configuration transaction is freed with `git_transaction_free` - without first committing it - we should not `git_config_free`. We never increased the refcount, so we certainly shouldn't decrease it. Also, add a test around aborting a transaction without committing it. --- tests/libgit2/config/write.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/libgit2/config/write.c b/tests/libgit2/config/write.c index 9d8c3fe9495..c71d4f6dc86 100644 --- a/tests/libgit2/config/write.c +++ b/tests/libgit2/config/write.c @@ -696,6 +696,36 @@ void test_config_write__locking(void) git_config_free(cfg); } +void test_config_write__abort_lock(void) +{ + git_config *cfg; + 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")); + + git_transaction_free(tx); + + /* Now that we've unlocked it, we should see no changes */ + 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")); + + git_config_free(cfg); +} + void test_config_write__repeated(void) { const char *filename = "config-repeated"; From d287e1a4fe020253f2b194e2f81e199145f3646d Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 4 Mar 2024 15:11:54 +0000 Subject: [PATCH 074/111] config: store the backend in a transaction When we `git_config_unlock`, we shouldn't _unlock the thing that we locked_ instead of assuming that the highest-priority target _remains_ the highest-priority target. --- src/libgit2/config.c | 17 +++++++---------- src/libgit2/config.h | 10 +++++++--- src/libgit2/transaction.c | 17 +++++++++++------ src/libgit2/transaction.h | 5 ++++- 4 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index 7e38d86936f..26b6fd677cd 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -1330,19 +1330,16 @@ int git_config_lock(git_transaction **out, git_config *config) if ((error = backend->lock(backend)) < 0) return error; - return git_transaction_config_new(out, config); + return git_transaction_config_new(out, config, backend); } -int git_config_unlock(git_config *config, int commit) +int git_config_unlock( + git_config *config, + git_config_backend *backend, + int commit) { - git_config_backend *backend; - - GIT_ASSERT_ARG(config); - - if ((backend = get_writer(config)) == NULL) { - git_error_set(GIT_ERROR_CONFIG, "cannot unlock: the configuration is read-only"); - return GIT_EREADONLY; - } + GIT_ASSERT_ARG(config && backend); + GIT_UNUSED(config); return backend->unlock(backend, commit); } diff --git a/src/libgit2/config.h b/src/libgit2/config.h index 3119f7d748b..d3428e7da4e 100644 --- a/src/libgit2/config.h +++ b/src/libgit2/config.h @@ -95,17 +95,21 @@ int git_config_lookup_map_enum(git_configmap_t *type_out, size_t map_n, int enum_val); /** - * Unlock the backend with the highest priority + * Unlock the given backend that was previously locked. * * 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 config the config instance + * @param backend the config backend * @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); +GIT_EXTERN(int) git_config_unlock( + git_config *config, + git_config_backend *backend, + int commit); #endif diff --git a/src/libgit2/transaction.c b/src/libgit2/transaction.c index ccffa9984cc..47bdb03a2f9 100644 --- a/src/libgit2/transaction.c +++ b/src/libgit2/transaction.c @@ -49,12 +49,16 @@ struct git_transaction { git_repository *repo; git_refdb *db; git_config *cfg; + git_config_backend *backend; git_strmap *locks; git_pool pool; }; -int git_transaction_config_new(git_transaction **out, git_config *cfg) +int git_transaction_config_new( + git_transaction **out, + git_config *cfg, + git_config_backend *backend) { git_transaction *tx; @@ -66,6 +70,8 @@ int git_transaction_config_new(git_transaction **out, git_config *cfg) tx->type = TRANSACTION_CONFIG; tx->cfg = cfg; + tx->backend = backend; + *out = tx; return 0; } @@ -333,8 +339,9 @@ int git_transaction_commit(git_transaction *tx) GIT_ASSERT_ARG(tx); if (tx->type == TRANSACTION_CONFIG) { - error = git_config_unlock(tx->cfg, true); + error = git_config_unlock(tx->cfg, tx->backend, true); tx->cfg = NULL; + tx->backend = NULL; return error; } @@ -369,10 +376,8 @@ void git_transaction_free(git_transaction *tx) return; if (tx->type == TRANSACTION_CONFIG) { - if (tx->cfg) { - git_config_unlock(tx->cfg, false); - git_config_free(tx->cfg); - } + if (tx->cfg) + git_config_unlock(tx->cfg, tx->backend, false); git__free(tx); return; diff --git a/src/libgit2/transaction.h b/src/libgit2/transaction.h index 780c068303e..961c8c3c844 100644 --- a/src/libgit2/transaction.h +++ b/src/libgit2/transaction.h @@ -9,6 +9,9 @@ #include "common.h" -int git_transaction_config_new(git_transaction **out, git_config *cfg); +int git_transaction_config_new( + git_transaction **out, + git_config *cfg, + git_config_backend *backend); #endif From e8910c175dd4418505962687050193cb08e3c3e4 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 4 Mar 2024 16:42:59 +0000 Subject: [PATCH 075/111] config: refcount the backend when we start a transaction When we start a transaction, we should refcount the backend so that we don't lose it. This prevents the case where we have a transaction open and a configuration entry locked and somebody forces a new backend at the same level and the backend is freed. Now a user can gently wind down their transaction even when the backend has been removed from the live configuration object. --- src/libgit2/config.c | 35 +++++++++++++++++++++++++---------- src/libgit2/config.h | 4 ++-- src/libgit2/transaction.c | 12 ++++++------ src/libgit2/transaction.h | 2 +- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index 26b6fd677cd..21b9666db33 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -655,8 +655,8 @@ int git_config_foreach_match( * Setters **************/ -static git_config_backend *get_writer(git_config *config) -{ + static backend_instance *get_writer_instance(git_config *config) + { backend_entry *entry; size_t i; @@ -667,10 +667,17 @@ static git_config_backend *get_writer(git_config *config) if (entry->write_order < 0) continue; - return entry->instance->backend; + return entry->instance; } return NULL; + } + +static git_config_backend *get_writer(git_config *config) +{ + backend_instance *instance = get_writer_instance(config); + + return instance ? instance->backend : NULL; } int git_config_delete_entry(git_config *config, const char *name) @@ -1317,31 +1324,39 @@ int git_config_open_default(git_config **out) int git_config_lock(git_transaction **out, git_config *config) { - git_config_backend *backend; + backend_instance *instance; int error; GIT_ASSERT_ARG(config); - if ((backend = get_writer(config)) == NULL) { + if ((instance = get_writer_instance(config)) == NULL) { git_error_set(GIT_ERROR_CONFIG, "cannot lock: the configuration is read-only"); return GIT_EREADONLY; } - if ((error = backend->lock(backend)) < 0) + if ((error = instance->backend->lock(instance->backend)) < 0 || + (error = git_transaction_config_new(out, config, instance)) < 0) return error; - return git_transaction_config_new(out, config, backend); + GIT_REFCOUNT_INC(instance); + return 0; } int git_config_unlock( git_config *config, - git_config_backend *backend, + void *data, int commit) { - GIT_ASSERT_ARG(config && backend); + backend_instance *instance = data; + int error; + + GIT_ASSERT_ARG(config && data); GIT_UNUSED(config); - return backend->unlock(backend, commit); + error = instance->backend->unlock(instance->backend, commit); + GIT_REFCOUNT_DEC(instance, backend_instance_free); + + return error; } /*********** diff --git a/src/libgit2/config.h b/src/libgit2/config.h index d3428e7da4e..5003cbf9c1f 100644 --- a/src/libgit2/config.h +++ b/src/libgit2/config.h @@ -102,14 +102,14 @@ int git_config_lookup_map_enum(git_configmap_t *type_out, * applied to the configuration. * * @param config the config instance - * @param backend the config backend + * @param data the config data passed to git_transaction_new * @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 *config, - git_config_backend *backend, + void *data, int commit); #endif diff --git a/src/libgit2/transaction.c b/src/libgit2/transaction.c index 47bdb03a2f9..963416196b4 100644 --- a/src/libgit2/transaction.c +++ b/src/libgit2/transaction.c @@ -49,7 +49,7 @@ struct git_transaction { git_repository *repo; git_refdb *db; git_config *cfg; - git_config_backend *backend; + void *cfg_data; git_strmap *locks; git_pool pool; @@ -58,7 +58,7 @@ struct git_transaction { int git_transaction_config_new( git_transaction **out, git_config *cfg, - git_config_backend *backend) + void *data) { git_transaction *tx; @@ -70,7 +70,7 @@ int git_transaction_config_new( tx->type = TRANSACTION_CONFIG; tx->cfg = cfg; - tx->backend = backend; + tx->cfg_data = data; *out = tx; return 0; @@ -339,9 +339,9 @@ int git_transaction_commit(git_transaction *tx) GIT_ASSERT_ARG(tx); if (tx->type == TRANSACTION_CONFIG) { - error = git_config_unlock(tx->cfg, tx->backend, true); + error = git_config_unlock(tx->cfg, tx->cfg_data, true); tx->cfg = NULL; - tx->backend = NULL; + tx->cfg_data = NULL; return error; } @@ -377,7 +377,7 @@ void git_transaction_free(git_transaction *tx) if (tx->type == TRANSACTION_CONFIG) { if (tx->cfg) - git_config_unlock(tx->cfg, tx->backend, false); + git_config_unlock(tx->cfg, tx->cfg_data, false); git__free(tx); return; diff --git a/src/libgit2/transaction.h b/src/libgit2/transaction.h index 961c8c3c844..cb26017ae9f 100644 --- a/src/libgit2/transaction.h +++ b/src/libgit2/transaction.h @@ -12,6 +12,6 @@ int git_transaction_config_new( git_transaction **out, git_config *cfg, - git_config_backend *backend); + void *data); #endif From e890ca35aa321cf128078e628ea05e79bd2c9607 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 11 Mar 2024 15:20:02 +0000 Subject: [PATCH 076/111] config: only read worktree config if extension is set Only read the worktree configuration when `extensions.worktreeconfig` is set to true. --- src/libgit2/repository.c | 29 +++++++++++++++++++++++++---- tests/libgit2/worktree/config.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index 97671fe40b1..124ec3672ab 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -1274,6 +1274,24 @@ int git_repository_discover( return error; } +static int has_config_worktree(bool *out, git_config *cfg) +{ + int worktreeconfig = 0, error; + + *out = false; + + error = git_config_get_bool(&worktreeconfig, cfg, "extensions.worktreeconfig"); + + if (error == 0) + *out = worktreeconfig; + else if (error == GIT_ENOTFOUND) + *out = false; + else + return error; + + return 0; +} + static int load_config( git_config **out, git_repository *repo, @@ -1285,6 +1303,7 @@ static int load_config( git_str config_path = GIT_STR_INIT; git_config *cfg = NULL; git_config_level_t write_order; + bool has_worktree; int error; GIT_ASSERT_ARG(out); @@ -1293,14 +1312,16 @@ static int load_config( return error; if (repo) { - if ((error = git_repository__item_path(&config_path, repo, GIT_REPOSITORY_ITEM_WORKTREE_CONFIG)) == 0) - error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_WORKTREE, repo, 0); + 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; - 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 = has_config_worktree(&has_worktree, cfg)) == 0 && + has_worktree && + (error = git_repository__item_path(&config_path, repo, GIT_REPOSITORY_ITEM_WORKTREE_CONFIG)) == 0) + error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_WORKTREE, repo, 0); if (error && error != GIT_ENOTFOUND) goto on_error; diff --git a/tests/libgit2/worktree/config.c b/tests/libgit2/worktree/config.c index 2ddec25e264..dee8e0c06d6 100644 --- a/tests/libgit2/worktree/config.c +++ b/tests/libgit2/worktree/config.c @@ -6,15 +6,19 @@ static worktree_fixture fixture = WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); +static worktree_fixture submodule = + WORKTREE_FIXTURE_INIT("submodules", "submodules-worktree-parent"); void test_worktree_config__initialize(void) { setup_fixture_worktree(&fixture); + setup_fixture_worktree(&submodule); } void test_worktree_config__cleanup(void) { cleanup_fixture_worktree(&fixture); + cleanup_fixture_worktree(&submodule); } void test_worktree_config__open(void) @@ -46,6 +50,31 @@ void test_worktree_config__set_level_local(void) git_config_free(cfg); } +void test_worktree_config__requires_extension(void) +{ + git_config *cfg; + git_config *wtcfg; + int extension = 0; + + /* + * the "submodules" repo does not have extensions.worktreeconfig + * set, the worktree configuration should not be available. + */ + cl_git_pass(git_repository_config(&cfg, submodule.repo)); + cl_git_fail_with(GIT_ENOTFOUND, git_config_get_bool(&extension, cfg, "extensions.worktreeconfig")); + cl_assert_equal_i(0, extension); + cl_git_fail_with(GIT_ENOTFOUND, git_config_open_level(&wtcfg, cfg, GIT_CONFIG_LEVEL_WORKTREE)); + git_config_free(cfg); + + /* the "testrepo" repo does have the configuration set. */ + cl_git_pass(git_repository_config(&cfg, fixture.repo)); + cl_git_pass(git_config_get_bool(&extension, cfg, "extensions.worktreeconfig")); + cl_assert_equal_i(1, extension); + cl_git_pass(git_config_open_level(&wtcfg, cfg, GIT_CONFIG_LEVEL_WORKTREE)); + git_config_free(wtcfg); + git_config_free(cfg); +} + void test_worktree_config__set_level_worktree(void) { git_config *cfg; From 3baa6372fd5957ebc8601ee7223118fa19f00ce4 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 11 Mar 2024 15:21:12 +0000 Subject: [PATCH 077/111] config: read the individual worktree config Instead of reading the worktree configuration from the commondir, read it from the gitdir. This ensures that each worktree gets its own configuration. --- src/libgit2/repository.c | 2 +- tests/libgit2/worktree/config.c | 22 +++++++++++++++++++ tests/resources/testrepo/.gitted/config | 2 ++ .../testrepo/.gitted/config.worktree | 2 ++ .../testrepo-worktree/config.worktree | 2 ++ 5 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tests/resources/testrepo/.gitted/config.worktree create mode 100644 tests/resources/testrepo/.gitted/worktrees/testrepo-worktree/config.worktree diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index 124ec3672ab..09b74df4a6e 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -58,7 +58,7 @@ static const struct { { 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, "config.worktree", false }, + { GIT_REPOSITORY_ITEM_GITDIR, GIT_REPOSITORY_ITEM_GITDIR, "config.worktree", 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 }, diff --git a/tests/libgit2/worktree/config.c b/tests/libgit2/worktree/config.c index dee8e0c06d6..1fd1f75b47b 100644 --- a/tests/libgit2/worktree/config.c +++ b/tests/libgit2/worktree/config.c @@ -75,6 +75,28 @@ void test_worktree_config__requires_extension(void) git_config_free(cfg); } +void test_worktree_config__exists(void) +{ + git_config *cfg, *wtcfg, *snap; + const char *str; + + cl_git_pass(git_repository_config(&cfg, fixture.repo)); + cl_git_pass(git_repository_config(&wtcfg, fixture.worktree)); + + cl_git_pass(git_config_snapshot(&snap, cfg)); + cl_git_pass(git_config_get_string(&str, snap, "worktreetest.config")); + cl_assert_equal_s("mainrepo", str); + git_config_free(snap); + + cl_git_pass(git_config_snapshot(&snap, wtcfg)); + cl_git_pass(git_config_get_string(&str, snap, "worktreetest.config")); + cl_assert_equal_s("worktreerepo", str); + git_config_free(snap); + + git_config_free(cfg); + git_config_free(wtcfg); +} + void test_worktree_config__set_level_worktree(void) { git_config *cfg; diff --git a/tests/resources/testrepo/.gitted/config b/tests/resources/testrepo/.gitted/config index d0114012f98..04d750a93bc 100644 --- a/tests/resources/testrepo/.gitted/config +++ b/tests/resources/testrepo/.gitted/config @@ -3,6 +3,8 @@ filemode = true bare = false logallrefupdates = true +[extensions] + worktreeconfig = true [remote "test"] url = git://github.com/libgit2/libgit2 fetch = +refs/heads/*:refs/remotes/test/* diff --git a/tests/resources/testrepo/.gitted/config.worktree b/tests/resources/testrepo/.gitted/config.worktree new file mode 100644 index 00000000000..df9f0caf650 --- /dev/null +++ b/tests/resources/testrepo/.gitted/config.worktree @@ -0,0 +1,2 @@ +[worktreetest] + config = mainrepo diff --git a/tests/resources/testrepo/.gitted/worktrees/testrepo-worktree/config.worktree b/tests/resources/testrepo/.gitted/worktrees/testrepo-worktree/config.worktree new file mode 100644 index 00000000000..7a130a7aed7 --- /dev/null +++ b/tests/resources/testrepo/.gitted/worktrees/testrepo-worktree/config.worktree @@ -0,0 +1,2 @@ +[worktreetest] + config = worktreerepo From b454eba9a717919bb5b43d3b2b70bcd54ca5f3e6 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 11 Mar 2024 21:07:21 +0000 Subject: [PATCH 078/111] errors: introduce GIT_ENOTSUPPORTED libgit2 lacks many of the things that git supports. Give a reasonable error code for these cases. --- include/git2/errors.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/git2/errors.h b/include/git2/errors.h index bdace8c43dd..2e007ecb55e 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -60,7 +60,8 @@ typedef enum { GIT_EAPPLYFAIL = -35, /**< Patch application failed */ GIT_EOWNER = -36, /**< The object is not owned by the current user */ GIT_TIMEOUT = -37, /**< The operation timed out */ - GIT_EUNCHANGED = -38 /**< There were no changes */ + GIT_EUNCHANGED = -38, /**< There were no changes */ + GIT_ENOTSUPPORTED = -39 /**< An option is not supported */ } git_error_code; /** From b8f3dae325fb257f14404979b837aef374a8a497 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 11 Mar 2024 21:07:46 +0000 Subject: [PATCH 079/111] fetch: fail on depth for local transport We don't support shallow clones for the local transport, currently. Fail, instead of silently ignoring the depth option. --- src/libgit2/transports/local.c | 5 +++++ tests/libgit2/clone/local.c | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/libgit2/transports/local.c b/src/libgit2/transports/local.c index 64c21afbd0d..fe59bcab0c1 100644 --- a/src/libgit2/transports/local.c +++ b/src/libgit2/transports/local.c @@ -303,6 +303,11 @@ static int local_negotiate_fetch( GIT_UNUSED(wants); + if (wants->depth) { + git_error_set(GIT_ERROR_NET, "shallow fetch is not supported by the local transport"); + return GIT_ENOTSUPPORTED; + } + /* Fill in the loids */ git_vector_foreach(&t->refs, i, rhead) { git_object *obj; diff --git a/tests/libgit2/clone/local.c b/tests/libgit2/clone/local.c index e0bd74df78a..a89b2343759 100644 --- a/tests/libgit2/clone/local.c +++ b/tests/libgit2/clone/local.c @@ -210,3 +210,14 @@ void test_clone_local__git_style_unc_paths(void) cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES)); #endif } + +void test_clone_local__shallow_fails(void) +{ + git_repository *repo; + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + + opts.fetch_opts.depth = 4; + + cl_git_fail_with(GIT_ENOTSUPPORTED, git_clone(&repo, cl_fixture("testrepo.git"), "./clone.git", &opts)); + git_repository_free(repo); +} From 2625ed24b9a4b0cd0dedbb3327fddf773893a486 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Wed, 13 Mar 2024 17:20:11 -0400 Subject: [PATCH 080/111] Initial implementation. --- src/libgit2/streams/mbedtls.c | 36 ++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/libgit2/streams/mbedtls.c b/src/libgit2/streams/mbedtls.c index 49aa76c3ed8..c875a2f50e8 100644 --- a/src/libgit2/streams/mbedtls.c +++ b/src/libgit2/streams/mbedtls.c @@ -32,7 +32,6 @@ # endif #endif -#include #include #include #include @@ -54,10 +53,17 @@ static mbedtls_entropy_context *mbedtls_entropy; 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); + #if MBEDTLS_VERSION_MAJOR >= 3 + mbedtls_x509_crt_free(git__ssl_conf->private_ca_chain); + git__free(git__ssl_conf->private_ca_chain); + mbedtls_ctr_drbg_free(git__ssl_conf->private_p_rng); + git__free(git__ssl_conf->private_p_rng); + #else + 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); + #endif mbedtls_ssl_config_free(git__ssl_conf); git__free(git__ssl_conf); git__ssl_conf = NULL; @@ -94,7 +100,10 @@ int git_mbedtls_stream_global_init(void) } /* configure TLSv1 */ - mbedtls_ssl_conf_min_version(git__ssl_conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_0); + #if MBEDTLS_VERSION_MAJOR < 3 + /* SSLv3 is not included in mbedtls3 */ + mbedtls_ssl_conf_min_version(git__ssl_conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_0); + #endif /* verify_server_cert is responsible for making the check. * OPTIONAL because REQUIRED drops the certificate as soon as the check @@ -192,7 +201,11 @@ static int ssl_set_error(mbedtls_ssl_context *ssl, int 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); + #ifdef MBEDTLS_VERSION_MAJOR >= 3 + git_error_set(GIT_ERROR_SSL, "SSL error: %#04x - %s", error, errbuf); + #else + git_error_set(GIT_ERROR_SSL, "SSL error: %#04x [%x] - %s", error, ssl->session_negotiate->verify_result, errbuf); + #endif ret = GIT_ECERTIFICATE; break; @@ -462,8 +475,13 @@ int git_mbedtls__set_cert_location(const char *file, const char *path) return -1; } - mbedtls_x509_crt_free(git__ssl_conf->ca_chain); - git__free(git__ssl_conf->ca_chain); + #if MBEDTLS_VERSION_MAJOR >= 3 + mbedtls_x509_crt_free(git__ssl_conf->private_ca_chain); + git__free(git__ssl_conf->private_ca_chain); + #else + mbedtls_x509_crt_free(git__ssl_conf->ca_chain); + git__free(git__ssl_conf->ca_chain); + #endif mbedtls_ssl_conf_ca_chain(git__ssl_conf, cacert, NULL); return 0; From 5934779ae0aa1c66def5153c8a4628c65e5b0fc0 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Wed, 13 Mar 2024 17:35:39 -0400 Subject: [PATCH 081/111] Restored verify result report. --- src/libgit2/streams/mbedtls.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libgit2/streams/mbedtls.c b/src/libgit2/streams/mbedtls.c index c875a2f50e8..f2ce411abd7 100644 --- a/src/libgit2/streams/mbedtls.c +++ b/src/libgit2/streams/mbedtls.c @@ -201,8 +201,8 @@ static int ssl_set_error(mbedtls_ssl_context *ssl, int error) break; case MBEDTLS_ERR_X509_CERT_VERIFY_FAILED: - #ifdef MBEDTLS_VERSION_MAJOR >= 3 - git_error_set(GIT_ERROR_SSL, "SSL error: %#04x - %s", error, errbuf); + #if MBEDTLS_VERSION_MAJOR >= 3 + git_error_set(GIT_ERROR_SSL, "SSL error: %#04x [%x] - %s", error, mbedtls_ssl_get_verify_result(ssl), errbuf); #else git_error_set(GIT_ERROR_SSL, "SSL error: %#04x [%x] - %s", error, ssl->session_negotiate->verify_result, errbuf); #endif From fdaf373c0e50555c16e97f5ae41db8571fcc3ba6 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 14 Mar 2024 11:27:36 +0000 Subject: [PATCH 082/111] trailer: test actual array equivalence We never test the lengths of the two arrays, so a short return from `git_message_trailers` will match erroneously. --- tests/libgit2/message/trailer.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/libgit2/message/trailer.c b/tests/libgit2/message/trailer.c index 919e10a499c..3705794fbdb 100644 --- a/tests/libgit2/message/trailer.c +++ b/tests/libgit2/message/trailer.c @@ -3,19 +3,22 @@ static void assert_trailers(const char *message, git_message_trailer *trailers) { git_message_trailer_array arr; - size_t i; + size_t i, count; int rc = git_message_trailers(&arr, message); cl_assert_equal_i(0, rc); - for(i=0; i Date: Thu, 14 Mar 2024 11:28:20 +0000 Subject: [PATCH 083/111] trailer: ensure we identify patch lines like git Git looks explicitly for `---` followed by whitespace (`isspace()`) to determine when a patch line begins in a commit message. Match that behavior. This ensures that we don't treat (say) `----` as a patch line incorrectly. --- src/libgit2/trailer.c | 2 +- tests/libgit2/message/trailer.c | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/libgit2/trailer.c b/src/libgit2/trailer.c index 4761c9922f2..52e655914b0 100644 --- a/src/libgit2/trailer.c +++ b/src/libgit2/trailer.c @@ -158,7 +158,7 @@ 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) + if (git__prefixcmp(s, "---") == 0 && git__isspace(s[3])) return s - str; } diff --git a/tests/libgit2/message/trailer.c b/tests/libgit2/message/trailer.c index 3705794fbdb..09e8f6115a7 100644 --- a/tests/libgit2/message/trailer.c +++ b/tests/libgit2/message/trailer.c @@ -165,3 +165,23 @@ void test_message_trailer__invalid(void) "Another: trailer\n" , trailers); } + +void test_message_trailer__ignores_dashes(void) +{ + git_message_trailer trailers[] = { + { "Signed-off-by", "some@one.com" }, + { "Another", "trailer" }, + { NULL, NULL }, + }; + + assert_trailers( + "Message\n" + "\n" + "Markdown header\n" + "---------------\n" + "Lorem ipsum\n" + "\n" + "Signed-off-by: some@one.com\n" + "Another: trailer\n" + , trailers); +} From 4c9353227b039bc4e7532df60031bbd5900b8352 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 14 Mar 2024 19:20:18 +0000 Subject: [PATCH 084/111] tests: don't free an unininitialized repo --- tests/libgit2/clone/local.c | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/libgit2/clone/local.c b/tests/libgit2/clone/local.c index a89b2343759..d35fe86b27e 100644 --- a/tests/libgit2/clone/local.c +++ b/tests/libgit2/clone/local.c @@ -219,5 +219,4 @@ void test_clone_local__shallow_fails(void) opts.fetch_opts.depth = 4; cl_git_fail_with(GIT_ENOTSUPPORTED, git_clone(&repo, cl_fixture("testrepo.git"), "./clone.git", &opts)); - git_repository_free(repo); } From 53978e678455e0cdcd7f77b242b34450d2169706 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 16 Mar 2024 11:04:36 +0000 Subject: [PATCH 085/111] ci: reduce ASLR randomization for TSAN Linux updated its ASLR randomization in a way that is incompatible with TSAN. See https://github.com/google/sanitizers/issues/1716 Reducing the randomness for ASLR allows the sanitizers to cope. --- .github/workflows/main.yml | 10 +++++++--- .github/workflows/nightly.yml | 9 ++++++--- ci/setup-sanitizer-build.sh | 7 +++++++ 3 files changed, 20 insertions(+), 6 deletions(-) create mode 100755 ci/setup-sanitizer-build.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index accb042e463..284b40358f1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -65,6 +65,7 @@ jobs: - name: "macOS" id: macos os: macos-12 + setup-script: osx env: CC: clang CMAKE_OPTIONS: -DREGEX_BACKEND=regcomp_l -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=leaks -DUSE_GSSAPI=ON @@ -72,7 +73,6 @@ jobs: PKG_CONFIG_PATH: /usr/local/opt/openssl/lib/pkgconfig SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true - setup-script: osx - name: "Windows (amd64, Visual Studio, Schannel)" id: windows-amd64-vs os: windows-2019 @@ -125,6 +125,8 @@ jobs: # All builds: sanitizers - name: "Sanitizer (Memory)" id: sanitizer-memory + os: ubuntu-latest + setup-script: sanitizer container: name: noble env: @@ -136,9 +138,10 @@ jobs: SKIP_NEGOTIATE_TESTS: true ASAN_SYMBOLIZER_PATH: /usr/bin/llvm-symbolizer-10 UBSAN_OPTIONS: print_stacktrace=1 - os: ubuntu-latest - name: "Sanitizer (Address)" id: sanitizer-address + os: ubuntu-latest + setup-script: sanitizer container: name: noble env: @@ -150,10 +153,10 @@ jobs: SKIP_NEGOTIATE_TESTS: true ASAN_SYMBOLIZER_PATH: /usr/bin/llvm-symbolizer-10 UBSAN_OPTIONS: print_stacktrace=1 - os: ubuntu-latest - name: "Sanitizer (UndefinedBehavior)" id: sanitizer-ub os: ubuntu-latest + setup-script: sanitizer container: name: noble env: @@ -168,6 +171,7 @@ jobs: - name: "Sanitizer (Thread)" id: sanitizer-thread os: ubuntu-latest + setup-script: sanitizer container: name: noble env: diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 25249e7440d..779f66f586e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -66,6 +66,7 @@ jobs: - name: "macOS" id: macos os: macos-12 + setup-script: osx env: CC: clang CMAKE_OPTIONS: -DREGEX_BACKEND=regcomp_l -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=leaks -DUSE_GSSAPI=ON @@ -73,7 +74,6 @@ jobs: PKG_CONFIG_PATH: /usr/local/opt/openssl/lib/pkgconfig SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true - setup-script: osx - name: "Windows (amd64, Visual Studio, Schannel)" id: windows-amd64-vs os: windows-2019 @@ -126,6 +126,8 @@ jobs: # All builds: sanitizers - name: "Sanitizer (Memory)" id: memorysanitizer + os: ubuntu-latest + setup-script: sanitizer container: name: noble env: @@ -137,10 +139,10 @@ jobs: SKIP_NEGOTIATE_TESTS: true ASAN_SYMBOLIZER_PATH: /usr/bin/llvm-symbolizer-10 UBSAN_OPTIONS: print_stacktrace=1 - os: ubuntu-latest - name: "Sanitizer (UndefinedBehavior)" id: ubsanitizer os: ubuntu-latest + setup-script: sanitizer container: name: noble env: @@ -155,6 +157,7 @@ jobs: - name: "Sanitizer (Thread)" id: threadsanitizer os: ubuntu-latest + setup-script: sanitizer container: name: noble env: @@ -330,13 +333,13 @@ jobs: - name: "macOS (SHA256)" id: macos os: macos-12 + setup-script: osx env: CC: clang CMAKE_OPTIONS: -DREGEX_BACKEND=regcomp_l -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=leaks -DUSE_GSSAPI=ON -DEXPERIMENTAL_SHA256=ON PKG_CONFIG_PATH: /usr/local/opt/openssl/lib/pkgconfig SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true - setup-script: osx - name: "Windows (SHA256, amd64, Visual Studio)" id: windows-amd64-vs os: windows-2019 diff --git a/ci/setup-sanitizer-build.sh b/ci/setup-sanitizer-build.sh new file mode 100755 index 00000000000..e4591f85bec --- /dev/null +++ b/ci/setup-sanitizer-build.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +set -ex + +# Linux updated its ASLR randomization in a way that is incompatible with +# TSAN. See https://github.com/google/sanitizers/issues/1716 +sudo sysctl vm.mmap_rnd_bits=28 From b70dd1270614fef64b11bda4e55d71c96a687a88 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 14 Mar 2024 11:07:31 +0000 Subject: [PATCH 086/111] packbuilder: adjust nondeterministic tests The packbuilder tests should be deterministic. This comment suggests that they are: ``` /* * 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 * */ ``` but it is disappointingly incorrect. As is evidenced by the fact that -- at least on _my_ machine -- that command does not actually produce that output any longer. Variations in things like the which compression library is actually used, and its defaults, mean that we cannot accurately predict or enforce the bytes in the packfile from one system to another. Adjust the tests so that they do not believe that they should enforce the bytes in the packfile. This allows broader compatibility with zlib-compatible compression libraries, or other compression levels. --- tests/libgit2/pack/packbuilder.c | 70 ++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/tests/libgit2/pack/packbuilder.c b/tests/libgit2/pack/packbuilder.c index ff3dc1f68aa..7da3877e451 100644 --- a/tests/libgit2/pack/packbuilder.c +++ b/tests/libgit2/pack/packbuilder.c @@ -98,9 +98,6 @@ 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(); @@ -114,33 +111,13 @@ void test_pack_packbuilder__create_pack(void) 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_assert(git_fs_path_exists(path.ptr)); 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); + cl_assert(buf.size > 256); 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) @@ -148,22 +125,49 @@ 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)); + cl_assert(git_packbuilder_name(_packbuilder) != NULL); +} + +static void get_packfile_path(git_str *out, git_packbuilder *pb) +{ + git_str_puts(out, "pack-"); + git_str_puts(out, git_packbuilder_name(pb)); + git_str_puts(out, ".pack"); +} + +static void get_index_path(git_str *out, git_packbuilder *pb) +{ + git_str_puts(out, "pack-"); + git_str_puts(out, git_packbuilder_name(pb)); + git_str_puts(out, ".idx"); } void test_pack_packbuilder__write_default_path(void) { + git_str idx = GIT_STR_INIT, pack = GIT_STR_INIT; + 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")); + + git_str_puts(&idx, "objects/pack/"); + get_index_path(&idx, _packbuilder); + + git_str_puts(&pack, "objects/pack/"); + get_packfile_path(&pack, _packbuilder); + + cl_assert(git_fs_path_exists(idx.ptr)); + cl_assert(git_fs_path_exists(pack.ptr)); + + git_str_dispose(&idx); + git_str_dispose(&pack); } static void test_write_pack_permission(mode_t given, mode_t expected) { struct stat statbuf; mode_t mask, os_mask; + git_str idx = GIT_STR_INIT, pack = GIT_STR_INIT; seed_packbuilder(); @@ -181,11 +185,17 @@ static void test_write_pack_permission(mode_t given, mode_t expected) mask = p_umask(0); p_umask(mask); - cl_git_pass(p_stat("pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.idx", &statbuf)); + get_index_path(&idx, _packbuilder); + get_packfile_path(&pack, _packbuilder); + + cl_git_pass(p_stat(idx.ptr, &statbuf)); cl_assert_equal_i(statbuf.st_mode & os_mask, (expected & ~mask) & os_mask); - cl_git_pass(p_stat("pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.pack", &statbuf)); + cl_git_pass(p_stat(pack.ptr, &statbuf)); cl_assert_equal_i(statbuf.st_mode & os_mask, (expected & ~mask) & os_mask); + + git_str_dispose(&idx); + git_str_dispose(&pack); } void test_pack_packbuilder__permissions_standard(void) From 594063935b3bd3a419b66ae24c99d041eb95f3a6 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 16 Mar 2024 14:31:55 +0000 Subject: [PATCH 087/111] cli: use libgit2 system includes libgit2 may adjust the system includes - for example, to locate a dependency installed in a funny location. Ensure that the cli understands those include paths as well. --- src/cli/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 84b6c190151..97797e33bd9 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -4,7 +4,8 @@ set(CLI_INCLUDES "${libgit2_SOURCE_DIR}/src/util" "${libgit2_SOURCE_DIR}/src/cli" "${libgit2_SOURCE_DIR}/include" - "${LIBGIT2_DEPENDENCY_INCLUDES}") + "${LIBGIT2_DEPENDENCY_INCLUDES}" + "${LIBGIT2_SYSTEM_INCLUDES}") if(WIN32 AND NOT CYGWIN) file(GLOB CLI_SRC_OS win32/*.c) From 079861729b55a43366a59cb6922bee6f6975d982 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 16 Mar 2024 14:32:54 +0000 Subject: [PATCH 088/111] build: update ntlmclient dependency The ntlmclient dependency now bundles an MD4 implementation for compatibility with newer mbedTLS. Include that so that we can support mbedTLS 3. --- deps/ntlmclient/CMakeLists.txt | 2 +- deps/ntlmclient/crypt_builtin_md4.c | 311 ++++++++++++++++++++++++++++ deps/ntlmclient/crypt_mbedtls.c | 20 -- 3 files changed, 312 insertions(+), 21 deletions(-) create mode 100644 deps/ntlmclient/crypt_builtin_md4.c diff --git a/deps/ntlmclient/CMakeLists.txt b/deps/ntlmclient/CMakeLists.txt index 8356d472367..f1f5de162a0 100644 --- a/deps/ntlmclient/CMakeLists.txt +++ b/deps/ntlmclient/CMakeLists.txt @@ -31,7 +31,7 @@ elseif(USE_HTTPS STREQUAL "OpenSSL-Dynamic") elseif(USE_HTTPS STREQUAL "mbedTLS") add_definitions(-DCRYPT_MBEDTLS) include_directories(${MBEDTLS_INCLUDE_DIR}) - set(SRC_NTLMCLIENT_CRYPTO "crypt_mbedtls.c" "crypt_mbedtls.h") + set(SRC_NTLMCLIENT_CRYPTO "crypt_mbedtls.c" "crypt_mbedtls.h" "crypt_builtin_md4.c") else() message(FATAL_ERROR "Unable to use libgit2's HTTPS backend (${USE_HTTPS}) for NTLM crypto") endif() diff --git a/deps/ntlmclient/crypt_builtin_md4.c b/deps/ntlmclient/crypt_builtin_md4.c new file mode 100644 index 00000000000..de9a85cafaa --- /dev/null +++ b/deps/ntlmclient/crypt_builtin_md4.c @@ -0,0 +1,311 @@ +/* + * Copyright (c) Edward Thomson. All rights reserved. + * + * This file is part of ntlmclient, distributed under the MIT license. + * For full terms and copyright information, and for third-party + * copyright information, see the included LICENSE.txt file. + */ + +#include +#include + +#include "ntlm.h" +#include "crypt.h" + +/* + * Below is the MD4 code from RFC 1320, with minor modifications + * to make it compile on a modern compiler. It is included since + * many system crypto libraries lack MD4, sensibly. + */ + +/* MD4C.C - RSA Data Security, Inc., MD4 message-digest algorithm + */ + +/* Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved. + + License to copy and use this software is granted provided that it + is identified as the "RSA Data Security, Inc. MD4 Message-Digest + Algorithm" in all material mentioning or referencing this software + or this function. + + License is also granted to make and use derivative works provided + that such works are identified as "derived from the RSA Data + Security, Inc. MD4 Message-Digest Algorithm" in all material + mentioning or referencing the derived work. + + RSA Data Security, Inc. makes no representations concerning either + the merchantability of this software or the suitability of this + software for any particular purpose. It is provided "as is" + without express or implied warranty of any kind. + + These notices must be retained in any copies of any part of this + documentation and/or software. + */ + +/* POINTER defines a generic pointer type */ +typedef unsigned char *POINTER; + +/* UINT2 defines a two byte word */ +typedef uint16_t UINT2; + +/* UINT4 defines a four byte word */ +typedef uint32_t UINT4; + +#define MD4_memcpy memcpy +#define MD4_memset memset + +/* MD4 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD4_CTX; + +/* Constants for MD4Transform routine. + */ +#define S11 3 +#define S12 7 +#define S13 11 +#define S14 19 +#define S21 3 +#define S22 5 +#define S23 9 +#define S24 13 +#define S31 3 +#define S32 9 +#define S33 11 +#define S34 15 + +static void MD4Transform(UINT4 [4], const unsigned char [64]); +static void Encode (unsigned char *, UINT4 *, unsigned int); +static void Decode (UINT4 *, const unsigned char *, unsigned int); + +static unsigned char 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 +}; + +/* F, G and H are basic MD4 functions. + */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) + +/* ROTATE_LEFT rotates x left n bits. + */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG and HH are transformations for rounds 1, 2 and 3 */ +/* Rotation is separate from addition to prevent recomputation */ +#define FF(a, b, c, d, x, s) { \ + (a) += F ((b), (c), (d)) + (x); \ + (a) = ROTATE_LEFT ((a), (s)); \ + } +#define GG(a, b, c, d, x, s) { \ + (a) += G ((b), (c), (d)) + (x) + (UINT4)0x5a827999; \ + (a) = ROTATE_LEFT ((a), (s)); \ + } +#define HH(a, b, c, d, x, s) { \ + (a) += H ((b), (c), (d)) + (x) + (UINT4)0x6ed9eba1; \ + (a) = ROTATE_LEFT ((a), (s)); \ + } + +/* MD4 initialization. Begins an MD4 operation, writing a new context. + */ +static void MD4Init (MD4_CTX *context) +{ + context->count[0] = context->count[1] = 0; + + /* Load magic initialization constants. + */ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* MD4 block update operation. Continues an MD4 message-digest + operation, processing another message block, and updating the + context. + */ +static void MD4Update (MD4_CTX *context, const unsigned char *input, unsigned int inputLen) +{ + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3)) + < ((UINT4)inputLen << 3)) + context->count[1]++; + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible. + */ + if (inputLen >= partLen) { + MD4_memcpy + ((POINTER)&context->buffer[index], (POINTER)input, partLen); + MD4Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD4Transform (context->state, &input[i]); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + MD4_memcpy + ((POINTER)&context->buffer[index], (POINTER)&input[i], + inputLen-i); +} + +/* MD4 finalization. Ends an MD4 message-digest operation, writing the + the message digest and zeroizing the context. + */ +static void MD4Final (unsigned char digest[16], MD4_CTX *context) +{ + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64. + */ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD4Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD4Update (context, bits, 8); + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information. + */ + MD4_memset ((POINTER)context, 0, sizeof (*context)); +} + +/* MD4 basic transformation. Transforms state based on block. + */ +static void MD4Transform (UINT4 state[4], const unsigned char block[64]) +{ + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11); /* 1 */ + FF (d, a, b, c, x[ 1], S12); /* 2 */ + FF (c, d, a, b, x[ 2], S13); /* 3 */ + FF (b, c, d, a, x[ 3], S14); /* 4 */ + FF (a, b, c, d, x[ 4], S11); /* 5 */ + FF (d, a, b, c, x[ 5], S12); /* 6 */ + FF (c, d, a, b, x[ 6], S13); /* 7 */ + FF (b, c, d, a, x[ 7], S14); /* 8 */ + FF (a, b, c, d, x[ 8], S11); /* 9 */ + FF (d, a, b, c, x[ 9], S12); /* 10 */ + FF (c, d, a, b, x[10], S13); /* 11 */ + FF (b, c, d, a, x[11], S14); /* 12 */ + FF (a, b, c, d, x[12], S11); /* 13 */ + FF (d, a, b, c, x[13], S12); /* 14 */ + FF (c, d, a, b, x[14], S13); /* 15 */ + FF (b, c, d, a, x[15], S14); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 0], S21); /* 17 */ + GG (d, a, b, c, x[ 4], S22); /* 18 */ + GG (c, d, a, b, x[ 8], S23); /* 19 */ + GG (b, c, d, a, x[12], S24); /* 20 */ + GG (a, b, c, d, x[ 1], S21); /* 21 */ + GG (d, a, b, c, x[ 5], S22); /* 22 */ + GG (c, d, a, b, x[ 9], S23); /* 23 */ + GG (b, c, d, a, x[13], S24); /* 24 */ + GG (a, b, c, d, x[ 2], S21); /* 25 */ + GG (d, a, b, c, x[ 6], S22); /* 26 */ + GG (c, d, a, b, x[10], S23); /* 27 */ + GG (b, c, d, a, x[14], S24); /* 28 */ + GG (a, b, c, d, x[ 3], S21); /* 29 */ + GG (d, a, b, c, x[ 7], S22); /* 30 */ + GG (c, d, a, b, x[11], S23); /* 31 */ + GG (b, c, d, a, x[15], S24); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 0], S31); /* 33 */ + HH (d, a, b, c, x[ 8], S32); /* 34 */ + HH (c, d, a, b, x[ 4], S33); /* 35 */ + HH (b, c, d, a, x[12], S34); /* 36 */ + HH (a, b, c, d, x[ 2], S31); /* 37 */ + HH (d, a, b, c, x[10], S32); /* 38 */ + HH (c, d, a, b, x[ 6], S33); /* 39 */ + HH (b, c, d, a, x[14], S34); /* 40 */ + HH (a, b, c, d, x[ 1], S31); /* 41 */ + HH (d, a, b, c, x[ 9], S32); /* 42 */ + HH (c, d, a, b, x[ 5], S33); /* 43 */ + HH (b, c, d, a, x[13], S34); /* 44 */ + HH (a, b, c, d, x[ 3], S31); /* 45 */ + HH (d, a, b, c, x[11], S32); /* 46 */ + HH (c, d, a, b, x[ 7], S33); /* 47 */ + HH (b, c, d, a, x[15], S34); /* 48 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information. + */ + MD4_memset ((POINTER)x, 0, sizeof (x)); +} + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is + a multiple of 4. + */ +static void Encode (unsigned char *output, UINT4 *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is + a multiple of 4. + */ +static void Decode (UINT4 *output, const unsigned char *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | + (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); +} + +bool ntlm_md4_digest( + unsigned char out[CRYPT_MD4_DIGESTSIZE], + ntlm_client *ntlm, + const unsigned char *in, + size_t in_len) +{ + MD4_CTX ctx; + + NTLM_UNUSED(ntlm); + + if (in_len > UINT_MAX) + return false; + + MD4Init(&ctx); + MD4Update(&ctx, in, (unsigned int)in_len); + MD4Final (out, &ctx); + + return true; +} diff --git a/deps/ntlmclient/crypt_mbedtls.c b/deps/ntlmclient/crypt_mbedtls.c index 6283c3eec08..4bbb878015d 100644 --- a/deps/ntlmclient/crypt_mbedtls.c +++ b/deps/ntlmclient/crypt_mbedtls.c @@ -12,7 +12,6 @@ #include "mbedtls/ctr_drbg.h" #include "mbedtls/des.h" #include "mbedtls/entropy.h" -#include "mbedtls/md4.h" #include "ntlm.h" #include "crypt.h" @@ -88,25 +87,6 @@ bool ntlm_des_encrypt( return success; } -bool ntlm_md4_digest( - unsigned char out[CRYPT_MD4_DIGESTSIZE], - ntlm_client *ntlm, - const unsigned char *in, - size_t in_len) -{ - mbedtls_md4_context ctx; - - NTLM_UNUSED(ntlm); - - mbedtls_md4_init(&ctx); - mbedtls_md4_starts(&ctx); - mbedtls_md4_update(&ctx, in, in_len); - mbedtls_md4_finish(&ctx, out); - mbedtls_md4_free(&ctx); - - return true; -} - bool ntlm_hmac_md5_init( ntlm_client *ntlm, const unsigned char *key, From 3266fc4938cce5f4251b7a055cbb4ff099d090a3 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 16 Mar 2024 14:57:06 +0000 Subject: [PATCH 089/111] mbedtls: keep track of what we allocate, then free it Instead of trying to allocate something, hand it to mbedTLS, and then peer into its private data structures to try to free that thing... we could just keep track of it ourselves. Once we've done that, we needn't do an allocation _anyway_, we can just keep it on the stack. --- src/libgit2/streams/mbedtls.c | 128 ++++++++++++++-------------------- 1 file changed, 52 insertions(+), 76 deletions(-) diff --git a/src/libgit2/streams/mbedtls.c b/src/libgit2/streams/mbedtls.c index f2ce411abd7..1b2780706c6 100644 --- a/src/libgit2/streams/mbedtls.c +++ b/src/libgit2/streams/mbedtls.c @@ -42,9 +42,15 @@ #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; + +static bool initialized = false; +static mbedtls_ssl_config mbedtls_config; +static mbedtls_ctr_drbg_context mbedtls_rng; +static mbedtls_entropy_context mbedtls_entropy; + +static bool has_ca_chain = false; +static mbedtls_x509_crt mbedtls_ca_chain; /** * This function aims to clean-up the SSL context which @@ -52,26 +58,16 @@ static mbedtls_entropy_context *mbedtls_entropy; */ static void shutdown_ssl(void) { - if (git__ssl_conf) { - #if MBEDTLS_VERSION_MAJOR >= 3 - mbedtls_x509_crt_free(git__ssl_conf->private_ca_chain); - git__free(git__ssl_conf->private_ca_chain); - mbedtls_ctr_drbg_free(git__ssl_conf->private_p_rng); - git__free(git__ssl_conf->private_p_rng); - #else - 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); - #endif - mbedtls_ssl_config_free(git__ssl_conf); - git__free(git__ssl_conf); - git__ssl_conf = NULL; + if (has_ca_chain) { + mbedtls_x509_crt_free(&mbedtls_ca_chain); + has_ca_chain = false; } - if (mbedtls_entropy) { - mbedtls_entropy_free(mbedtls_entropy); - git__free(mbedtls_entropy); - mbedtls_entropy = NULL; + + if (initialized) { + mbedtls_ctr_drbg_free(&mbedtls_rng); + mbedtls_ssl_config_free(&mbedtls_config); + mbedtls_entropy_free(&mbedtls_entropy); + initialized = false; } } @@ -80,35 +76,33 @@ 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(&mbedtls_config); + mbedtls_entropy_init(&mbedtls_entropy); + mbedtls_ctr_drbg_init(&mbedtls_rng); - 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) { + if (mbedtls_ssl_config_defaults(&mbedtls_config, + 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 */ - #if MBEDTLS_VERSION_MAJOR < 3 - /* SSLv3 is not included in mbedtls3 */ - mbedtls_ssl_conf_min_version(git__ssl_conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_0); - #endif + /* configure TLSv1.1 */ +#ifdef MBEDTLS_SSL_MINOR_VERSION_2 + mbedtls_ssl_conf_min_version(&mbedtls_config, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_2); +#endif /* 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); + mbedtls_ssl_conf_authmode(&mbedtls_config, MBEDTLS_SSL_VERIFY_OPTIONAL); /* set the list of allowed ciphersuites */ ciphers_known = 0; @@ -132,42 +126,33 @@ int git_mbedtls_stream_global_init(void) git_error_set(GIT_ERROR_SSL, "no cipher could be enabled"); goto cleanup; } - mbedtls_ssl_conf_ciphersuites(git__ssl_conf, ciphers_list); + mbedtls_ssl_conf_ciphersuites(&mbedtls_config, 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) { + if (mbedtls_ctr_drbg_seed(&mbedtls_rng, 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); + mbedtls_ssl_conf_rng(&mbedtls_config, mbedtls_ctr_drbg_random, &mbedtls_rng); /* 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); + initialized = true; + 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; + mbedtls_ctr_drbg_free(&mbedtls_rng); + mbedtls_ssl_config_free(&mbedtls_config); + mbedtls_entropy_free(&mbedtls_entropy); return -1; } @@ -201,11 +186,7 @@ static int ssl_set_error(mbedtls_ssl_context *ssl, int error) break; case MBEDTLS_ERR_X509_CERT_VERIFY_FAILED: - #if MBEDTLS_VERSION_MAJOR >= 3 - git_error_set(GIT_ERROR_SSL, "SSL error: %#04x [%x] - %s", error, mbedtls_ssl_get_verify_result(ssl), errbuf); - #else - git_error_set(GIT_ERROR_SSL, "SSL error: %#04x [%x] - %s", error, ssl->session_negotiate->verify_result, errbuf); - #endif + git_error_set(GIT_ERROR_SSL, "SSL error: %#04x [%x] - %s", error, mbedtls_ssl_get_verify_result(ssl), errbuf); ret = GIT_ECERTIFICATE; break; @@ -387,7 +368,7 @@ static int mbedtls_stream_wrap( 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)) { + if (mbedtls_ssl_setup(st->ssl, &mbedtls_config)) { git_error_set(GIT_ERROR_SSL, "failed to create ssl object"); error = -1; goto out_err; @@ -454,35 +435,30 @@ 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); + if (has_ca_chain) + mbedtls_x509_crt_free(&mbedtls_ca_chain); + + mbedtls_x509_crt_init(&mbedtls_ca_chain); - mbedtls_x509_crt_init(cacert); if (file) - ret = mbedtls_x509_crt_parse_file(cacert, file); + ret = mbedtls_x509_crt_parse_file(&mbedtls_ca_chain, file); + if (ret >= 0 && path) - ret = mbedtls_x509_crt_parse_path(cacert, path); + ret = mbedtls_x509_crt_parse_path(&mbedtls_ca_chain, 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_x509_crt_free(&mbedtls_ca_chain); mbedtls_strerror( ret, errbuf, 512 ); git_error_set(GIT_ERROR_SSL, "failed to load CA certificates: %#04x - %s", ret, errbuf); return -1; } - #if MBEDTLS_VERSION_MAJOR >= 3 - mbedtls_x509_crt_free(git__ssl_conf->private_ca_chain); - git__free(git__ssl_conf->private_ca_chain); - #else - mbedtls_x509_crt_free(git__ssl_conf->ca_chain); - git__free(git__ssl_conf->ca_chain); - #endif - mbedtls_ssl_conf_ca_chain(git__ssl_conf, cacert, NULL); + mbedtls_ssl_conf_ca_chain(&mbedtls_config, &mbedtls_ca_chain, NULL); + has_ca_chain = true; return 0; } From 58dfe647b7cb474661f5f5a6f072efc929cecf03 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 16 Mar 2024 16:38:44 +0000 Subject: [PATCH 090/111] build: update to latest actions versions --- .github/workflows/benchmark.yml | 6 +++--- .github/workflows/build-containers.yml | 2 +- .github/workflows/main.yml | 14 +++++++------- .github/workflows/nightly.yml | 14 +++++++------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 831ffbb8235..51a5fc1c083 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -54,7 +54,7 @@ jobs: runs-on: ${{ matrix.platform.os }} steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: source fetch-depth: 0 @@ -79,7 +79,7 @@ jobs: ../source/tests/benchmarks/benchmark.sh --baseline-cli "git" --cli "${GIT2_CLI}" --name libgit2 --json benchmarks.json --zip benchmarks.zip shell: bash - name: Upload results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: benchmark-${{ matrix.platform.id }} path: benchmark @@ -93,7 +93,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out benchmark repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: libgit2/benchmarks path: site diff --git a/.github/workflows/build-containers.yml b/.github/workflows/build-containers.yml index 56ac66a9016..5518548c6c3 100644 --- a/.github/workflows/build-containers.yml +++ b/.github/workflows/build-containers.yml @@ -44,7 +44,7 @@ jobs: name: "Create container: ${{ matrix.container.name }}" steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: source fetch-depth: 0 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 284b40358f1..236a7d13bb6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -187,7 +187,7 @@ jobs: # All builds: experimental SHA256 support - name: "Linux (SHA256, Xenial, Clang, OpenSSL)" - id: xenial-clang-openssl + id: linux-sha256 os: ubuntu-latest container: name: xenial @@ -196,7 +196,7 @@ jobs: CMAKE_GENERATOR: Ninja CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON -DEXPERIMENTAL_SHA256=ON - name: "macOS (SHA256)" - id: macos + id: macos-sha256 os: macos-12 setup-script: osx env: @@ -207,7 +207,7 @@ jobs: SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true - name: "Windows (SHA256, amd64, Visual Studio)" - id: windows-amd64-vs + id: windows-sha256 os: windows-2019 env: ARCH: amd64 @@ -221,7 +221,7 @@ jobs: name: "Build: ${{ matrix.platform.name }}" steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: source fetch-depth: 0 @@ -258,7 +258,7 @@ jobs: container-version: ${{ env.docker-registry-container-sha }} shell: ${{ matrix.platform.shell }} - name: Upload test results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: success() || failure() with: name: test-results-${{ matrix.platform.id }} @@ -289,7 +289,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: source fetch-depth: 0 @@ -316,7 +316,7 @@ jobs: cm doc api.docurium git checkout gh-pages zip --exclude .git/\* --exclude .gitignore --exclude .gitattributes -r api-documentation.zip . - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 name: Upload artifact with: name: api-documentation diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 779f66f586e..570b9aa6ab6 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -322,7 +322,7 @@ jobs: # All builds: experimental SHA256 support - name: "Linux (SHA256, Xenial, Clang, OpenSSL)" - id: xenial-clang-openssl + id: linux-sha256 container: name: xenial env: @@ -331,7 +331,7 @@ jobs: CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON os: ubuntu-latest - name: "macOS (SHA256)" - id: macos + id: macos-sha256 os: macos-12 setup-script: osx env: @@ -341,7 +341,7 @@ jobs: SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true - name: "Windows (SHA256, amd64, Visual Studio)" - id: windows-amd64-vs + id: windows-sha256 os: windows-2019 env: ARCH: amd64 @@ -355,7 +355,7 @@ jobs: name: "Build ${{ matrix.platform.name }}" steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: source fetch-depth: 0 @@ -392,7 +392,7 @@ jobs: container-version: ${{ env.docker-registry-container-sha }} shell: ${{ matrix.platform.shell }} - name: Upload test results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: success() || failure() with: name: test-results-${{ matrix.platform.id }} @@ -420,7 +420,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: source fetch-depth: 0 @@ -451,7 +451,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 From cc7764f6a69c4b01c6e5e0b19e4177afb93d0620 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 17 Mar 2024 14:11:40 +0000 Subject: [PATCH 091/111] config: correct fetching the HIGHEST_LEVEL config Also, add a test for fetching the `GIT_CONFIG_HIGHEST_LEVEL`. --- src/libgit2/config.c | 26 ++++++-------------------- tests/libgit2/config/configlevel.c | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index 21b9666db33..67ce6573801 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -241,7 +241,7 @@ static int find_backend_by_level( return GIT_ENOTFOUND; } - *out = entry->instance; + *out = found->instance; return 0; } @@ -430,27 +430,11 @@ typedef struct { size_t i; } all_iter; -static int find_next_backend(size_t *out, const git_config *config, size_t i) -{ - backend_entry *entry; - - for (; i > 0; --i) { - entry = git_vector_get(&config->readers, i - 1); - GIT_ASSERT(entry && entry->instance && entry->instance->backend); - - *out = i; - return 0; - } - - return -1; -} - static int all_iter_next(git_config_entry **out, git_config_iterator *_iter) { all_iter *iter = (all_iter *) _iter; backend_entry *entry; git_config_backend *backend; - size_t i; int error = 0; if (iter->current != NULL && @@ -462,12 +446,14 @@ static int all_iter_next(git_config_entry **out, git_config_iterator *_iter) return error; do { - if (find_next_backend(&i, iter->config, iter->i) < 0) + if (iter->i == 0) return GIT_ITEROVER; - entry = git_vector_get(&iter->config->readers, i - 1); + entry = git_vector_get(&iter->config->readers, iter->i - 1); + GIT_ASSERT(entry && entry->instance && entry->instance->backend); + backend = entry->instance->backend; - iter->i = i - 1; + iter->i--; if (iter->current) iter->current->free(iter->current); diff --git a/tests/libgit2/config/configlevel.c b/tests/libgit2/config/configlevel.c index 3534fbc2c84..0e31268b0c6 100644 --- a/tests/libgit2/config/configlevel.c +++ b/tests/libgit2/config/configlevel.c @@ -72,6 +72,29 @@ void test_config_configlevel__fetching_a_level_from_an_empty_compound_config_ret git_config_free(cfg); } +void test_config_configlevel__can_fetch_highest_level(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_HIGHEST_LEVEL)); + + 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__can_override_local_with_worktree(void) { git_config *cfg; From 2eb3fecd03376be3f72a2c41716ee89b2fccdcaa Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 17 Mar 2024 21:18:06 +0000 Subject: [PATCH 092/111] fetch: avoid API breaking-changes from v1.7 Update `git_fetch_options` to break out the fetch options into individual options. This prevents creating an API breaking change from v1.7.0. `git_remote_update_tips` retains the `update_flags` to also avoid an API breaking change. --- include/git2/remote.h | 9 +++++---- src/libgit2/clone.c | 2 +- src/libgit2/remote.c | 7 ++++++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/include/git2/remote.h b/include/git2/remote.h index 7ad820ad3c3..7067c88b0b1 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -744,10 +744,10 @@ typedef struct { git_fetch_prune_t prune; /** - * How to handle reference updates; a combination of - * `git_remote_update_flags`. + * How to handle reference updates; see `git_remote_update_flags`. */ - unsigned int update_flags; + unsigned int update_fetchhead : 1, + report_unchanged : 1; /** * Determines how to behave regarding tags on the remote, such @@ -790,7 +790,8 @@ typedef struct { GIT_FETCH_OPTIONS_VERSION, \ GIT_REMOTE_CALLBACKS_INIT, \ GIT_FETCH_PRUNE_UNSPECIFIED, \ - GIT_REMOTE_UPDATE_FETCHHEAD, \ + 1, \ + 0, \ GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED, \ GIT_PROXY_OPTIONS_INIT } diff --git a/src/libgit2/clone.c b/src/libgit2/clone.c index db9a898e34e..d62c77ac554 100644 --- a/src/libgit2/clone.c +++ b/src/libgit2/clone.c @@ -431,7 +431,7 @@ static int clone_into( return error; memcpy(&fetch_opts, opts, sizeof(git_fetch_options)); - fetch_opts.update_flags = ~GIT_REMOTE_UPDATE_FETCHHEAD; + fetch_opts.update_fetchhead = 0; if (!opts->depth) fetch_opts.download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL; diff --git a/src/libgit2/remote.c b/src/libgit2/remote.c index 9eb4bac8b2f..8b07c83184a 100644 --- a/src/libgit2/remote.c +++ b/src/libgit2/remote.c @@ -1376,7 +1376,12 @@ int git_remote_fetch( return error; if (opts) { - update_flags = opts->update_flags; + if (opts->update_fetchhead) + update_flags |= GIT_REMOTE_UPDATE_FETCHHEAD; + + if (opts->report_unchanged) + update_flags |= GIT_REMOTE_UPDATE_REPORT_UNCHANGED; + tagopt = opts->download_tags; } From dd35af37d8ef1fbed24511ba8258ef77e6b8a0cc Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 17 Mar 2024 21:28:17 +0000 Subject: [PATCH 093/111] repository: rearrange `git_repository_item_t` values Update the ordering of `GIT_REPOSITORY_ITEM_WORKTREE_CONFIG` to avoid breaking the ABI unnecessarily. --- include/git2/repository.h | 2 +- src/libgit2/repository.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/git2/repository.h b/include/git2/repository.h index 9ad6176f940..0afda72d402 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -499,12 +499,12 @@ typedef enum { GIT_REPOSITORY_ITEM_PACKED_REFS, GIT_REPOSITORY_ITEM_REMOTES, GIT_REPOSITORY_ITEM_CONFIG, - GIT_REPOSITORY_ITEM_WORKTREE_CONFIG, GIT_REPOSITORY_ITEM_INFO, GIT_REPOSITORY_ITEM_HOOKS, GIT_REPOSITORY_ITEM_LOGS, GIT_REPOSITORY_ITEM_MODULES, GIT_REPOSITORY_ITEM_WORKTREES, + GIT_REPOSITORY_ITEM_WORKTREE_CONFIG, GIT_REPOSITORY_ITEM__LAST } git_repository_item_t; diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index 06c653ea921..8e449a6df09 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -58,12 +58,12 @@ static const struct { { 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_GITDIR, GIT_REPOSITORY_ITEM_GITDIR, "config.worktree", 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 } + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "worktrees", true }, + { GIT_REPOSITORY_ITEM_GITDIR, GIT_REPOSITORY_ITEM_GITDIR, "config.worktree", false } }; static int check_repositoryformatversion(int *version, git_config *config); From 4504f2c5cf7ca6ca8b7af3b748b0ba09df7801dd Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 18 Mar 2024 10:33:27 +0000 Subject: [PATCH 094/111] valgrind: suppress OpenSSL warnings --- script/valgrind.supp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/script/valgrind.supp b/script/valgrind.supp index 8c4549f62be..12b6634d5fe 100644 --- a/script/valgrind.supp +++ b/script/valgrind.supp @@ -191,6 +191,16 @@ ... } +{ + ignore-openssl-undefined-in-connect + Memcheck:Cond + ... + obj:*libcrypto.so* + ... + fun:openssl_connect + ... +} + { ignore-libssh2-rsa-sha1-sign Memcheck:Leak From 647f8eb9879eb4fe0459658756fcf2d23982f40a Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 19 Dec 2023 16:56:30 +0000 Subject: [PATCH 095/111] ctype: only use custom functions on Windows The Microsoft C runtime (MSVCRT) may take a heavy lock on the locale in order to figure out how the `ctype` functions work. This is deeply slow. Provide our own to avoid that. On POSIX, provide emulation for that functionality using the ctype functions, but compress the return value into a `bool`, and cast the value to an `unsigned char`. --- src/util/ctype_compat.h | 52 +++++++++++++++++++++++++++++++++++++++++ src/util/git2_util.h | 1 + src/util/util.h | 34 --------------------------- 3 files changed, 53 insertions(+), 34 deletions(-) create mode 100644 src/util/ctype_compat.h diff --git a/src/util/ctype_compat.h b/src/util/ctype_compat.h new file mode 100644 index 00000000000..5b04a8dd42c --- /dev/null +++ b/src/util/ctype_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_ctype_compat_h__ +#define INCLUDE_ctype_compat_h__ + +/* + * The Microsoft C runtime (MSVCRT) may take a heavy lock on the + * locale in order to figure out how the `ctype` functions work. + * This is deeply slow. Provide our own to avoid that. + */ + +#ifdef GIT_WIN32 + +GIT_INLINE(int) git__tolower(int c) +{ + return (c >= 'A' && c <= 'Z') ? (c + 32) : c; +} + +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__isxdigit(int c) +{ + return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); +} + +#else +# define git__tolower(a) tolower((unsigned char)(a)) + +# define git__isalpha(a) (!!isalpha((unsigned char)(a))) +# define git__isdigit(a) (!!isdigit((unsigned char)(a))) +# define git__isspace(a) (!!isspace((unsigned char)(a))) +# define git__isxdigit(a) (!!isxdigit((unsigned char)(a))) +#endif + +#endif diff --git a/src/util/git2_util.h b/src/util/git2_util.h index 5ec9429344a..5bf09819956 100644 --- a/src/util/git2_util.h +++ b/src/util/git2_util.h @@ -165,5 +165,6 @@ typedef struct git_str git_str; if (GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize)) { return -1; } #include "util.h" +#include "ctype_compat.h" #endif diff --git a/src/util/util.h b/src/util/util.h index 933644fd2b9..2ed005110ef 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -83,15 +83,6 @@ 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((unsigned char)(a)) -#endif - extern size_t git__linenlen(const char *buffer, size_t buffer_len); GIT_INLINE(const char *) git__next_line(const char *s) @@ -249,26 +240,6 @@ 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'); @@ -279,11 +250,6 @@ 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. * From 8e6beb3d16286f9647880ab6488ca7c2485d46cd Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 19 Dec 2023 17:09:16 +0000 Subject: [PATCH 096/111] ctype: switch to `git__` ctype functions Use the `git__` ctype functions (`git__isdigit` and friends) instead of explicitly casting. --- src/libgit2/config.c | 2 +- src/libgit2/config_parse.c | 4 ++-- src/libgit2/path.c | 2 +- src/libgit2/trailer.c | 10 +++++----- src/libgit2/transports/smart_pkt.c | 4 ++-- src/util/ctype_compat.h | 20 +++++++++++++++++++- src/util/date.c | 26 +++++++++++++------------- src/util/str.c | 4 ++-- tests/libgit2/repo/open.c | 2 +- 9 files changed, 46 insertions(+), 28 deletions(-) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index 5c1c00f6cb7..cfc3a4b2e26 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -1447,7 +1447,7 @@ static int normalize_section(char *start, char *end) for (scan = start; *scan; ++scan) { if (end && scan >= end) break; - if (isalnum((unsigned char)*scan)) + if (git__isalnum(*scan)) *scan = (char)git__tolower(*scan); else if (*scan != '-' || scan == start) return GIT_EINVALIDSPEC; diff --git a/src/libgit2/config_parse.c b/src/libgit2/config_parse.c index 9ab78cc7f60..7f933db4885 100644 --- a/src/libgit2/config_parse.c +++ b/src/libgit2/config_parse.c @@ -27,7 +27,7 @@ static void set_parse_error(git_config_parser *reader, int col, const char *erro GIT_INLINE(int) config_keychar(char c) { - return isalnum((unsigned char)c) || c == '-'; + return git__isalnum(c) || c == '-'; } static int strip_comments(char *line, int in_quotes) @@ -383,7 +383,7 @@ static int parse_multiline_variable(git_config_parser *reader, git_str *value, i GIT_INLINE(bool) is_namechar(char c) { - return isalnum((unsigned char)c) || c == '-'; + return git__isalnum(c) || c == '-'; } static int parse_name( diff --git a/src/libgit2/path.c b/src/libgit2/path.c index 50181fdbff0..4b584fb8056 100644 --- a/src/libgit2/path.c +++ b/src/libgit2/path.c @@ -202,7 +202,7 @@ GIT_INLINE(size_t) common_prefix_icase(const char *str, size_t len, const char * { size_t count = 0; - while (len > 0 && tolower((unsigned char)*str) == tolower((unsigned char)*prefix)) { + while (len > 0 && git__tolower(*str) == git__tolower(*prefix)) { count++; str++; prefix++; diff --git a/src/libgit2/trailer.c b/src/libgit2/trailer.c index 9fb16418413..6e37cfdcf2d 100644 --- a/src/libgit2/trailer.c +++ b/src/libgit2/trailer.c @@ -24,7 +24,7 @@ static const char *const git_generated_prefixes[] = { static int is_blank_line(const char *str) { const char *s = str; - while (*s && *s != '\n' && isspace((unsigned char)*s)) + while (*s && *s != '\n' && git__isspace(*s)) s++; return !*s || *s == '\n'; } @@ -93,7 +93,7 @@ static bool find_separator(size_t *out, const char *line, const char *separators return true; } - if (!whitespace_found && (isalnum((unsigned char)*c) || *c == '-')) + if (!whitespace_found && (git__isalnum(*c) || *c == '-')) continue; if (c != line && (*c == ' ' || *c == '\t')) { whitespace_found = 1; @@ -233,12 +233,12 @@ static size_t find_trailer_start(const char *buf, size_t len) } find_separator(&separator_pos, bol, TRAILER_SEPARATORS); - if (separator_pos >= 1 && !isspace((unsigned char)bol[0])) { + if (separator_pos >= 1 && !git__isspace(bol[0])) { trailer_lines++; possible_continuation_lines = 0; if (recognized_prefix) continue; - } else if (isspace((unsigned char)bol[0])) + } else if (git__isspace(bol[0])) possible_continuation_lines++; else { non_trailer_lines++; @@ -323,7 +323,7 @@ int git_message_trailers(git_message_trailer_array *trailer_arr, const char *mes goto ret; } - if (isalnum((unsigned char)*ptr) || *ptr == '-') { + if (git__isalnum(*ptr) || *ptr == '-') { /* legal key character */ NEXT(S_KEY); } diff --git a/src/libgit2/transports/smart_pkt.c b/src/libgit2/transports/smart_pkt.c index 08cb7fbe5c2..6a3f903df15 100644 --- a/src/libgit2/transports/smart_pkt.c +++ b/src/libgit2/transports/smart_pkt.c @@ -535,10 +535,10 @@ static int parse_len(size_t *out, const char *line, size_t linelen) num[PKT_LEN_SIZE] = '\0'; for (i = 0; i < PKT_LEN_SIZE; ++i) { - if (!isxdigit((unsigned char)num[i])) { + if (!git__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((unsigned char)num[k])) { + if(!git__isprint(num[k])) { num[k] = '.'; } } diff --git a/src/util/ctype_compat.h b/src/util/ctype_compat.h index 5b04a8dd42c..462c8a17f17 100644 --- a/src/util/ctype_compat.h +++ b/src/util/ctype_compat.h @@ -20,6 +20,11 @@ GIT_INLINE(int) git__tolower(int c) return (c >= 'A' && c <= 'Z') ? (c + 32) : c; } +GIT_INLINE(int) git__toupper(int c) +{ + return (c >= 'a' && c <= 'z') ? (c - 32) : c; +} + GIT_INLINE(bool) git__isalpha(int c) { return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); @@ -29,7 +34,12 @@ GIT_INLINE(bool) git__isdigit(int c) { return (c >= '0' && c <= '9'); } -- + +GIT_INLINE(bool) git__isalnum(int c) +{ + return git__isalpha(c) || git__isdigit(c); +} + GIT_INLINE(bool) git__isspace(int c) { return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v'); @@ -40,13 +50,21 @@ GIT_INLINE(bool) git__isxdigit(int c) return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); } +GIT_INLINE(bool) git__isprint(int c) +{ + return (c >= ' ' && c <= '~'); +} + #else # define git__tolower(a) tolower((unsigned char)(a)) +# define git__toupper(a) toupper((unsigned char)(a)) # define git__isalpha(a) (!!isalpha((unsigned char)(a))) # define git__isdigit(a) (!!isdigit((unsigned char)(a))) +# define git__isalnum(a) (!!isalnum((unsigned char)(a))) # define git__isspace(a) (!!isspace((unsigned char)(a))) # define git__isxdigit(a) (!!isxdigit((unsigned char)(a))) +# define git__isprint(a) (!!isprint((unsigned char)(a))) #endif #endif diff --git a/src/util/date.c b/src/util/date.c index d54056842dd..872cb81f33c 100644 --- a/src/util/date.c +++ b/src/util/date.c @@ -129,9 +129,9 @@ static size_t match_string(const char *date, const char *str) for (i = 0; *date; date++, str++, i++) { if (*date == *str) continue; - if (toupper((unsigned char)*date) == toupper((unsigned char)*str)) + if (git__toupper(*date) == git__toupper(*str)) continue; - if (!isalnum((unsigned char)*date)) + if (!git__isalnum(*date)) break; return 0; } @@ -143,7 +143,7 @@ static int skip_alpha(const char *date) int i = 0; do { i++; - } while (isalpha((unsigned char)date[i])); + } while (git__isalpha(date[i])); return i; } @@ -251,7 +251,7 @@ static size_t match_multi_number(unsigned long num, char c, const char *date, ch num2 = strtol(end+1, &end, 10); num3 = -1; - if (*end == c && isdigit((unsigned char)end[1])) + if (*end == c && git__isdigit(end[1])) num3 = strtol(end+1, &end, 10); /* Time? Date? */ @@ -349,7 +349,7 @@ static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_ case '.': case '/': case '-': - if (isdigit((unsigned char)end[1])) { + if (git__isdigit(end[1])) { size_t match = match_multi_number(num, *end, date, end, tm); if (match) return match; @@ -364,7 +364,7 @@ static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_ n = 0; do { n++; - } while (isdigit((unsigned char)date[n])); + } while (git__isdigit(date[n])); /* Four-digit year or a timezone? */ if (n == 4) { @@ -514,11 +514,11 @@ static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset if (!c || c == '\n') break; - if (isalpha(c)) + if (git__isalpha(c)) match = match_alpha(date, &tm, offset); - else if (isdigit(c)) + else if (git__isdigit(c)) match = match_digit(date, &tm, offset, &tm_gmt); - else if ((c == '-' || c == '+') && isdigit((unsigned char)date[1])) + else if ((c == '-' || c == '+') && git__isdigit(date[1])) match = match_tz(date, offset); if (!match) { @@ -682,7 +682,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm const char *end = date; int i; - while (isalpha((unsigned char)*++end)) + while (git__isalpha(*++end)) /* scan to non-alpha */; for (i = 0; i < 12; i++) { @@ -783,7 +783,7 @@ static const char *approxidate_digit(const char *date, struct tm *tm, int *num) case '.': case '/': case '-': - if (isdigit((unsigned char)end[1])) { + if (git__isdigit(end[1])) { size_t match = match_multi_number(number, *end, date, end, tm); if (match) return date + match; @@ -843,13 +843,13 @@ static git_time_t approxidate_str(const char *date, if (!c) break; date++; - if (isdigit(c)) { + if (git__isdigit(c)) { pending_number(&tm, &number); date = approxidate_digit(date-1, &tm, &number); touched = 1; continue; } - if (isalpha(c)) + if (git__isalpha(c)) date = approxidate_alpha(date-1, &tm, &now, &number, &touched); } pending_number(&tm, &number); diff --git a/src/util/str.c b/src/util/str.c index 625faba06db..0b07c814702 100644 --- a/src/util/str.c +++ b/src/util/str.c @@ -485,8 +485,8 @@ int git_str_decode_percent( for (str_pos = 0; str_pos < str_len; buf->size++, str_pos++) { if (str[str_pos] == '%' && str_len > str_pos + 2 && - isxdigit((unsigned char)str[str_pos + 1]) && - isxdigit((unsigned char)str[str_pos + 2])) { + git__isxdigit(str[str_pos + 1]) && + git__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; diff --git a/tests/libgit2/repo/open.c b/tests/libgit2/repo/open.c index 219612016b9..9eef7f5a7e8 100644 --- a/tests/libgit2/repo/open.c +++ b/tests/libgit2/repo/open.c @@ -316,7 +316,7 @@ static void unposix_path(git_str *path) src = tgt = path->ptr; /* convert "/d/..." to "d:\..." */ - if (src[0] == '/' && isalpha((unsigned char)src[1]) && src[2] == '/') { + if (src[0] == '/' && git__isalpha(src[1]) && src[2] == '/') { *tgt++ = src[1]; *tgt++ = ':'; *tgt++ = '\\'; From 467556993fa9d249294858c967f0b4a6359b9a3b Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 18 Mar 2024 10:14:35 +0000 Subject: [PATCH 097/111] ntlmclient: use unsigned char for ctype functions --- deps/ntlmclient/ntlm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deps/ntlmclient/ntlm.c b/deps/ntlmclient/ntlm.c index ad4de5de56e..6094a4a3484 100644 --- a/deps/ntlmclient/ntlm.c +++ b/deps/ntlmclient/ntlm.c @@ -988,9 +988,9 @@ static inline bool generate_lm_hash( keystr2_len = (password_len > 7) ? MIN(14, password_len) - 7 : 0; for (i = 0; i < keystr1_len; i++) - keystr1[i] = (unsigned char)toupper(password[i]); + keystr1[i] = (unsigned char)toupper((unsigned char)password[i]); for (i = 0; i < keystr2_len; i++) - keystr2[i] = (unsigned char)toupper(password[i+7]); + keystr2[i] = (unsigned char)toupper((unsigned char)password[i+7]); /* DES encrypt the LM constant using the password as the key */ des_key_from_password(&key1, keystr1, keystr1_len); From 9288436e38b64ec86795f90710b79ccf8ceba1a6 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 19 Mar 2024 07:01:01 +0000 Subject: [PATCH 098/111] ci: split SHA256 builds out into their own workflow Split the SHA256 builds into their own workflow; since they're experimental (and have proven to be flaky) they shouldn't be used as signal that there's a problem with a PR. --- .github/workflows/experimental.yml | 118 +++++++++++++++++++++++++++++ .github/workflows/main.yml | 31 -------- 2 files changed, 118 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/experimental.yml diff --git a/.github/workflows/experimental.yml b/.github/workflows/experimental.yml new file mode 100644 index 00000000000..5bfea2c0028 --- /dev/null +++ b/.github/workflows/experimental.yml @@ -0,0 +1,118 @@ +# Validation builds for experimental features; these shouldn't be +# required for pull request approval. +name: Experimental Features + +on: + push: + branches: [ main, maint/* ] + pull_request: + branches: [ main, maint/* ] + workflow_dispatch: + +env: + docker-registry: ghcr.io + docker-config-path: ci/docker + +permissions: + contents: write + packages: write + +jobs: + # Run our CI/CD builds. We build a matrix with the various build targets + # and their details. Then we build either in a docker container (Linux) + # or on the actual hosts (macOS, Windows). + build: + strategy: + matrix: + platform: + # All builds: experimental SHA256 support + - name: "Linux (SHA256, Xenial, Clang, OpenSSL)" + id: linux-sha256 + os: ubuntu-latest + container: + name: xenial + env: + CC: clang + CMAKE_GENERATOR: Ninja + CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON -DEXPERIMENTAL_SHA256=ON + - name: "macOS (SHA256)" + id: macos-sha256 + os: macos-12 + setup-script: osx + env: + CC: clang + CMAKE_OPTIONS: -DREGEX_BACKEND=regcomp_l -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=leaks -DUSE_GSSAPI=ON -DEXPERIMENTAL_SHA256=ON + CMAKE_GENERATOR: Ninja + PKG_CONFIG_PATH: /usr/local/opt/openssl/lib/pkgconfig + SKIP_SSH_TESTS: true + SKIP_NEGOTIATE_TESTS: true + - name: "Windows (SHA256, amd64, Visual Studio)" + id: windows-sha256 + os: windows-2019 + env: + ARCH: amd64 + CMAKE_GENERATOR: Visual Studio 16 2019 + CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DEXPERIMENTAL_SHA256=ON + SKIP_SSH_TESTS: true + SKIP_NEGOTIATE_TESTS: true + fail-fast: false + env: ${{ matrix.platform.env }} + runs-on: ${{ matrix.platform.os }} + name: "Build: ${{ matrix.platform.name }}" + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + path: source + fetch-depth: 0 + - name: Set up build environment + run: source/ci/setup-${{ matrix.platform.setup-script }}-build.sh + shell: bash + if: matrix.platform.setup-script != '' + - name: Setup QEMU + run: docker run --rm --privileged multiarch/qemu-user-static:register --reset + if: matrix.platform.container.qemu == true + - name: Set up container + uses: ./source/.github/actions/download-or-build-container + with: + registry: ${{ env.docker-registry }} + config-path: ${{ env.docker-config-path }} + container: ${{ matrix.platform.container.name }} + github_token: ${{ secrets.github_token }} + dockerfile: ${{ matrix.platform.container.dockerfile }} + if: matrix.platform.container.name != '' + - name: Prepare build + run: mkdir build + - name: Build + uses: ./source/.github/actions/run-build + with: + command: cd ${BUILD_WORKSPACE:-.}/build && ../source/ci/build.sh + container: ${{ matrix.platform.container.name }} + container-version: ${{ env.docker-registry-container-sha }} + shell: ${{ matrix.platform.shell }} + - name: Test + uses: ./source/.github/actions/run-build + with: + command: cd ${BUILD_WORKSPACE:-.}/build && ../source/ci/test.sh + container: ${{ matrix.platform.container.name }} + container-version: ${{ env.docker-registry-container-sha }} + shell: ${{ matrix.platform.shell }} + - name: Upload test results + uses: actions/upload-artifact@v4 + if: success() || failure() + with: + name: test-results-${{ matrix.platform.id }} + path: build/results_*.xml + + test_results: + name: Test results + needs: [ build ] + if: always() + runs-on: ubuntu-latest + steps: + - name: Download test results + uses: actions/download-artifact@v3 + - name: Generate test summary + uses: test-summary/action@v2 + with: + paths: 'test-results-*/*.xml' diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 236a7d13bb6..87e834f10db 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -184,37 +184,6 @@ jobs: ASAN_SYMBOLIZER_PATH: /usr/bin/llvm-symbolizer-10 UBSAN_OPTIONS: print_stacktrace=1 TSAN_OPTIONS: suppressions=/home/libgit2/source/script/thread-sanitizer.supp second_deadlock_stack=1 - - # All builds: experimental SHA256 support - - name: "Linux (SHA256, Xenial, Clang, OpenSSL)" - id: linux-sha256 - os: ubuntu-latest - container: - name: xenial - env: - CC: clang - CMAKE_GENERATOR: Ninja - CMAKE_OPTIONS: -DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON -DUSE_SSH=ON -DEXPERIMENTAL_SHA256=ON - - name: "macOS (SHA256)" - id: macos-sha256 - os: macos-12 - setup-script: osx - env: - CC: clang - CMAKE_OPTIONS: -DREGEX_BACKEND=regcomp_l -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=leaks -DUSE_GSSAPI=ON -DEXPERIMENTAL_SHA256=ON - CMAKE_GENERATOR: Ninja - PKG_CONFIG_PATH: /usr/local/opt/openssl/lib/pkgconfig - SKIP_SSH_TESTS: true - SKIP_NEGOTIATE_TESTS: true - - name: "Windows (SHA256, amd64, Visual Studio)" - id: windows-sha256 - os: windows-2019 - env: - ARCH: amd64 - CMAKE_GENERATOR: Visual Studio 16 2019 - CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DEXPERIMENTAL_SHA256=ON - SKIP_SSH_TESTS: true - SKIP_NEGOTIATE_TESTS: true fail-fast: false env: ${{ matrix.platform.env }} runs-on: ${{ matrix.platform.os }} From 7940f094a54765f277d5ce9bcb9d6ce6ea9d24e5 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 16 Mar 2024 21:41:59 +0000 Subject: [PATCH 099/111] v1.8: update COPYING file Include the ntlmclient dependency's license file. --- COPYING | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) diff --git a/COPYING b/COPYING index ff5e76857e3..bc94b9df9fa 100644 --- a/COPYING +++ b/COPYING @@ -1214,3 +1214,172 @@ 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 bundled ntlmclient code is licensed under the MIT license: + +Copyright (c) Edward Thomson. All rights reserved. + +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. + +---------------------------------------------------------------------- + +Portions of this software derived from Team Explorer Everywhere: + +Copyright (c) Microsoft Corporation + +All rights reserved. + +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. + +--------------------------------------------------------------------------- + +Portions of this software derived from the LLVM Compiler Infrastructure: + +Copyright (c) 2003-2016 University of Illinois at Urbana-Champaign. +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +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: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +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 +CONTRIBUTORS 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 WITH THE +SOFTWARE. + +--------------------------------------------------------------------------- + +Portions of this software derived from Unicode, Inc: + +Copyright 2001-2004 Unicode, Inc. + +Disclaimer + +This source code is provided as is by Unicode, Inc. No claims are +made as to fitness for any particular purpose. No warranties of any +kind are expressed or implied. The recipient agrees to determine +applicability of information provided. If this file has been +purchased on magnetic or optical media from Unicode, Inc., the +sole remedy for any claim will be exchange of defective media +within 90 days of receipt. + +Limitations on Rights to Redistribute This Code + +Unicode, Inc. hereby grants the right to freely use the information +supplied in this file in the creation of products supporting the +Unicode Standard, and to make copies of this file in any form +for internal or external distribution as long as this notice +remains attached. + +--------------------------------------------------------------------------- + +Portions of this software derived from sheredom/utf8.h: + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +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 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. + +For more information, please refer to + +--------------------------------------------------------------------------- + +Portions of this software derived from RFC 1320: + +Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD4 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD4 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. From 5aa3ce722538bc898af09be332e8ccaa5d04232c Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 16 Mar 2024 20:07:30 +0000 Subject: [PATCH 100/111] v1.8: update version numbers --- README.md | 2 +- include/git2/version.h | 6 +++--- package.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f4dbc789154..77efdd4a688 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ libgit2 - the Git linkable library | Build Status | | | ------------ | - | | **main** branch CI builds | [![CI Build](https://github.com/libgit2/libgit2/workflows/CI%20Build/badge.svg?event=push)](https://github.com/libgit2/libgit2/actions?query=workflow%3A%22CI+Build%22+event%3Apush) | +| **v1.8 branch** CI builds | [![CI Build](https://github.com/libgit2/libgit2/workflows/CI%20Build/badge.svg?branch=maint%2Fv1.8&event=push)](https://github.com/libgit2/libgit2/actions?query=workflow%3A%22CI+Build%22+event%3Apush+branch%3Amaint%2Fv1.8) | | **v1.7 branch** CI builds | [![CI Build](https://github.com/libgit2/libgit2/workflows/CI%20Build/badge.svg?branch=maint%2Fv1.7&event=push)](https://github.com/libgit2/libgit2/actions?query=workflow%3A%22CI+Build%22+event%3Apush+branch%3Amaint%2Fv1.7) | -| **v1.6 branch** CI builds | [![CI Build](https://github.com/libgit2/libgit2/workflows/CI%20Build/badge.svg?branch=maint%2Fv1.6&event=push)](https://github.com/libgit2/libgit2/actions?query=workflow%3A%22CI+Build%22+event%3Apush+branch%3Amaint%2Fv1.6) | | **Nightly** builds | [![Nightly Build](https://github.com/libgit2/libgit2/workflows/Nightly%20Build/badge.svg)](https://github.com/libgit2/libgit2/actions?query=workflow%3A%22Nightly+Build%22) [![Coverity Scan Status](https://scan.coverity.com/projects/639/badge.svg)](https://scan.coverity.com/projects/639) | `libgit2` is a portable, pure C implementation of the Git core methods diff --git a/include/git2/version.h b/include/git2/version.h index 76cb0026fc2..010d4a224d9 100644 --- a/include/git2/version.h +++ b/include/git2/version.h @@ -11,7 +11,7 @@ * The version string for libgit2. This string follows semantic * versioning (v2) guidelines. */ -#define LIBGIT2_VERSION "1.8.0-alpha" +#define LIBGIT2_VERSION "1.8.0" /** The major version number for this version of libgit2. */ #define LIBGIT2_VER_MAJOR 1 @@ -31,13 +31,13 @@ * a prerelease name like "beta" or "rc1". For final releases, this will * be `NULL`. */ -#define LIBGIT2_VER_PRERELEASE "alpha" +#define LIBGIT2_VER_PRERELEASE NULL /** * The library ABI soversion for this version of libgit2. This should * only be changed when the library has a breaking ABI change, and so * may trail the library's version number. */ -#define LIBGIT2_SOVERSION "1.7" +#define LIBGIT2_SOVERSION "1.8" #endif diff --git a/package.json b/package.json index 203e99b9bc6..2306e721853 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libgit2", - "version": "1.8.0-alpha", + "version": "1.8.0", "repo": "https://github.com/libgit2/libgit2", "description": " A cross-platform, linkable library implementation of Git that you can use in your application.", "install": "mkdir build && cd build && cmake .. && cmake --build ." From 69f257711014e738819feb13280d9428d0539339 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 17 Mar 2024 13:55:52 +0000 Subject: [PATCH 101/111] v1.8: update changelog --- docs/changelog.md | 317 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 317 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index f733102356a..7b118c798be 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,320 @@ +v1.8 +---- + +This is release v1.8.0, "Das Fliegende Klassenzimmer". This release +includes optional, experimental support for invoking OpenSSH to fetch +and push, an easier mechanism to perform the default behavior of +`git commit`, and has many improvements for worktrees. This release +also includes many other new features and bugfixes. + +## Major changes + +* **Executable SSH (OpenSSH) support** + libgit2 can now invoke the command-line OpenSSH to fetch from and push + to remotes over SSH. This support takes the place of libssh2 support. + To use it, configure libgit2 with `cmake -DUSE_SSH=exec`, and please + report any problems that you discover. By @ethomson in + https://github.com/libgit2/libgit2/pull/6617 + +* **Simplified commit creation** + The `git_commit_create_from_stage` API was introduced to allow users to + better emulate the behavior of `git commit` without needing to provide + unnecessary information. The current state of the index is committed to + the current branch. By @ethomson in + https://github.com/libgit2/libgit2/pull/6716 + +* **Worktree improvements** + A number of worktree improvements have been made for better + compatibility with core git. First, libgit2 now understands per-worktree + references, thanks to @csware in + https://github.com/libgit2/libgit2/pull/6387. Worktree-specific + configuration is now supported, thanks to @vermiculus in + https://github.com/libgit2/libgit2/pull/6202. And improved compatibility + with `git worktree add` is now supported, thanks to @herrerog in + https://github.com/libgit2/libgit2/pull/5319. + +## Breaking changes + +* **Adding `WORKTREE` configuration level** (ABI breaking change) + To support worktree configurations at the appropriate level (higher + priority than local configuration, but lower priority than app-specific + configuration), the `GIT_CONFIG_LEVEL_WORKTREE` level was introduced at + priority 6. `GIT_CONFIG_LEVEL_APP` now begins at priority 7. + +* **Changes to `git_config_entry`** (ABI breaking change) + The `git_config_entry` structure now contains information about the + `backend_type` and `origin_path`. The unused `payload` value has been + removed. + +* **`git_push_options` includes remote push options** (ABI breaking change) + The `git_push_options` structure now contains a value for remote push + options. + +## Other changes + +### New features + +* config: provide an "origin" for config entries by @ethomson in + https://github.com/libgit2/libgit2/pull/6615 +* cli: add a `git config` command by @ethomson in + https://github.com/libgit2/libgit2/pull/6616 +* Add OpenSSH support by @ethomson in + https://github.com/libgit2/libgit2/pull/6617 +* remote: optionally report unchanged tips by @ethomson in + https://github.com/libgit2/libgit2/pull/6645 +* Support setting oid type for in-memory repositories by @kcsaul in + https://github.com/libgit2/libgit2/pull/6671 +* cli: add `index-pack` command by @ethomson in + https://github.com/libgit2/libgit2/pull/6681 +* Add `git_repository_commit_parents` to identify the parents of the next + commit given the repository state by @ethomson in + https://github.com/libgit2/libgit2/pull/6707 +* commit: introduce `git_commit_create_from_stage` by @ethomson in + https://github.com/libgit2/libgit2/pull/6716 +* set SSH timeout by @vafada in + https://github.com/libgit2/libgit2/pull/6721 +* Implement push options on push by @russell in + https://github.com/libgit2/libgit2/pull/6439 +* Support index.skipHash true config by @parnic in + https://github.com/libgit2/libgit2/pull/6738 +* worktree: mimic 'git worktree add' behavior. by @herrerog in + https://github.com/libgit2/libgit2/pull/5319 +* Support the extension for worktree-specific config by @vermiculus in + https://github.com/libgit2/libgit2/pull/6202 +* Separate config reader and writer backend priorities (for worktree + configs) by @ethomson in https://github.com/libgit2/libgit2/pull/6756 +* fetch: enable deepening/shortening shallow clones by @kempniu in + https://github.com/libgit2/libgit2/pull/6662 + +### Bug fixes + +* repository: make cleanup safe for re-use with grafts by @carlosmn in + https://github.com/libgit2/libgit2/pull/6600 +* fix: Add missing include for `oidarray`. by @dvzrv in + https://github.com/libgit2/libgit2/pull/6608 +* ssh: fix `known_hosts` leak in `_git_ssh_setup_conn` by @steven9724 in + https://github.com/libgit2/libgit2/pull/6599 +* proxy: Return an error for invalid proxy URLs instead of crashing by + @lrm29 in https://github.com/libgit2/libgit2/pull/6597 +* errors: refactoring - never return `NULL` in `git_error_last()` by + @ethomson in https://github.com/libgit2/libgit2/pull/6625 +* Reject potential option injections over ssh by @carlosmn in + https://github.com/libgit2/libgit2/pull/6636 +* remote: fix memory leak in `git_remote_download()` by @7Ji in + https://github.com/libgit2/libgit2/pull/6651 +* git2: Fix crash when called w/o parameters by @csware in + https://github.com/libgit2/libgit2/pull/6673 +* Avoid macro redefinition of `ENABLE_INTSAFE_SIGNED_FUNCTIONS` by @csware + in https://github.com/libgit2/libgit2/pull/6666 +* util: suppress some uninitialized variable warnings by @boretrk in + https://github.com/libgit2/libgit2/pull/6659 +* push: set generic error in `push_negotiation` cb by @ethomson in + https://github.com/libgit2/libgit2/pull/6675 +* process: test `/usr/bin/false` on BSDs by @ethomson in + https://github.com/libgit2/libgit2/pull/6677 +* clone: don't mix up "http://url" with "http:/url" when figuring out if we + should do a local clone by @boretrk in + https://github.com/libgit2/libgit2/pull/6361 +* Several compatibility fixes by @ethomson in + https://github.com/libgit2/libgit2/pull/6678 +* Git blame buffer gives the wrong result in many cases where there are + by @thosey in https://github.com/libgit2/libgit2/pull/6572 +* Fix 'path cannot exist in repository' during diff for in-memory repository + by @kcsaul in https://github.com/libgit2/libgit2/pull/6683 +* process: don't try to close the status by @ethomson in + https://github.com/libgit2/libgit2/pull/6693 +* Minor bug fixes by @ethomson in + https://github.com/libgit2/libgit2/pull/6695 +* Bypass shallow clone support for in-memory repositories by @kcsaul in + https://github.com/libgit2/libgit2/pull/6684 +* examples: use `unsigned` int for bitfields by @ethomson in + https://github.com/libgit2/libgit2/pull/6699 +* Fix some bugs caught by UBscan by @ethomson in + https://github.com/libgit2/libgit2/pull/6700 +* `git_diff_find_similar` doesn't always remove unmodified deltas by @yori + in https://github.com/libgit2/libgit2/pull/6642 +* httpclient: clear `client->parser.data` after use by @ethomson in + https://github.com/libgit2/libgit2/pull/6705 +* Do not normalize `safe.directory` paths by @csware in + https://github.com/libgit2/libgit2/pull/6668 +* clone: don't swallow error in `should_checkout` by @ethomson in + https://github.com/libgit2/libgit2/pull/6727 +* Correct index add directory/file conflict detection by @ethomson in + https://github.com/libgit2/libgit2/pull/6729 +* Correct `git_revparse_single` and add revparse fuzzing by @ethomson in + https://github.com/libgit2/libgit2/pull/6730 +* config: properly delete or rename section containing multivars by + @samueltardieu in https://github.com/libgit2/libgit2/pull/6723 +* revparse: ensure bare '@' is truly bare by @ethomson in + https://github.com/libgit2/libgit2/pull/6742 +* repo: ensure we can initialize win32 paths by @ethomson in + https://github.com/libgit2/libgit2/pull/6743 +* Swap `GIT_DIFF_LINE_(ADD|DEL)_EOFNL` to match other Diffs by @xphoniex in + https://github.com/libgit2/libgit2/pull/6240 +* diff: fix test for SHA256 support in `diff_from_buffer` by @ethomson in + https://github.com/libgit2/libgit2/pull/6745 +* http: support empty http.proxy config setting by @ethomson in + https://github.com/libgit2/libgit2/pull/6744 +* More `safe.directory` improvements by @ethomson in + https://github.com/libgit2/libgit2/pull/6739 +* Ensure that completely ignored diff is empty by @ethomson in + https://github.com/libgit2/libgit2/pull/5893 +* Fix broken regexp that matches submodule names containing ".path" by + @csware in https://github.com/libgit2/libgit2/pull/6749 +* Fix memory leaks by @csware in + https://github.com/libgit2/libgit2/pull/6748 +* Make `refdb_fs` (hopefully) fully aware of per worktree refs by @csware in + https://github.com/libgit2/libgit2/pull/6387 +* fix log example by @albfan in https://github.com/libgit2/libgit2/pull/6359 +* fetch: fail on depth for local transport by @ethomson in + https://github.com/libgit2/libgit2/pull/6757 +* Fix message trailer parsing by @ethomson in + https://github.com/libgit2/libgit2/pull/6761 +* config: correct fetching the `HIGHEST_LEVEL` config by @ethomson in + https://github.com/libgit2/libgit2/pull/6766 +* Avoid some API breaking changes in v1.8 by @ethomson in + https://github.com/libgit2/libgit2/pull/6768 + +### Build and CI improvements + +* meta: update version numbers to v1.8 by @ethomson in + https://github.com/libgit2/libgit2/pull/6596 +* Revert "CMake: Search for ssh2 instead of libssh2." by @ethomson in + https://github.com/libgit2/libgit2/pull/6619 +* cmake: fix openssl build on win32 by @lazka in + https://github.com/libgit2/libgit2/pull/6626 +* ci: retry flaky online tests by @ethomson in + https://github.com/libgit2/libgit2/pull/6628 +* ci: update to macOS 12 by @ethomson in + https://github.com/libgit2/libgit2/pull/6629 +* Use `#!/bin/bash` for script with bash-specific commands by @roehling in + https://github.com/libgit2/libgit2/pull/6581 +* ci: overwrite nonsense in `/usr/local` during macOS setup by @ethomson in + https://github.com/libgit2/libgit2/pull/6664 +* release: add a compatibility label by @ethomson in + https://github.com/libgit2/libgit2/pull/6676 +* actions: set permissions by @ethomson in + https://github.com/libgit2/libgit2/pull/6680 +* cmake: rename FindIconv to avoid collision with cmake by @ethomson in + https://github.com/libgit2/libgit2/pull/6682 +* ci: allow workflows to read and write packages by @ethomson in + https://github.com/libgit2/libgit2/pull/6687 +* ci: allow workflows to push changes by @ethomson in + https://github.com/libgit2/libgit2/pull/6688 +* tests: remove test for strcasecmp by @boretrk in + https://github.com/libgit2/libgit2/pull/6691 +* CI fixes by @ethomson in + https://github.com/libgit2/libgit2/pull/6694 +* ci: improvements to prepare for Cygwin support by @ethomson in + https://github.com/libgit2/libgit2/pull/6696 +* Yet more CI improvements by @ethomson in + https://github.com/libgit2/libgit2/pull/6697 +* Fix nightly builds by @ethomson in + https://github.com/libgit2/libgit2/pull/6709 +* Benchmarks: add a site to view results by @ethomson in + https://github.com/libgit2/libgit2/pull/6715 +* `GIT_RAND_GETENTROPY`: do not include `sys/random.h` by @semarie in + https://github.com/libgit2/libgit2/pull/6736 +* add dl to `LIBGIT2_SYSTEM_LIBS` by @christopherfujino in + https://github.com/libgit2/libgit2/pull/6631 +* meta: add dependency tag to release.yml by @ethomson in + https://github.com/libgit2/libgit2/pull/6740 +* CI: fix our nightlies by @ethomson in + https://github.com/libgit2/libgit2/pull/6751 +* trace: Re-enable tests as tracing is now enabled by default by @lrm29 in + https://github.com/libgit2/libgit2/pull/6752 +* tests: don't free an unininitialized repo by @ethomson in + https://github.com/libgit2/libgit2/pull/6763 +* ci: reduce ASLR randomization for TSAN by @ethomson in + https://github.com/libgit2/libgit2/pull/6764 +* packbuilder: adjust nondeterministic tests by @ethomson in + https://github.com/libgit2/libgit2/pull/6762 +* Allow libgit2 to be compiled with mbedtls3. by @adamharrison in + https://github.com/libgit2/libgit2/pull/6759 +* build: update to latest actions versions by @ethomson in + https://github.com/libgit2/libgit2/pull/6765 +* ctype: cast characters to unsigned when classifying characters by + @boretrk in https://github.com/libgit2/libgit2/pull/6679 and + @ethomson in https://github.com/libgit2/libgit2/pull/6770 +* valgrind: suppress OpenSSL warnings by @ethomson in https://github.com/libgit2/libgit2/pull/6769 + +### Documentation improvements + +* README.md: Fix link to conan packages by @lrm29 in + https://github.com/libgit2/libgit2/pull/6621 +* README: replace gmaster with GitButler by @ethomson in + https://github.com/libgit2/libgit2/pull/6692 +* blame example: Fix support for line range in CLI by @wetneb in + https://github.com/libgit2/libgit2/pull/6638 +* Support authentication in push example by @pluehne in + https://github.com/libgit2/libgit2/pull/5904 +* docs: fix mistake in attr.h by @DavHau in + https://github.com/libgit2/libgit2/pull/6714 +* Fix broken links by @csware in + https://github.com/libgit2/libgit2/pull/6747 + +### Platform compatibility fixes + +* stransport: macOS: replace `errSSLNetworkTimeout`, with hard-coded + value by @mascguy in https://github.com/libgit2/libgit2/pull/6610 + +### Git compatibility fixes + +* Do not trim dots from usernames by @georgthegreat in + https://github.com/libgit2/libgit2/pull/6657 +* merge: fix incorrect rename detection for empty files by @herrerog in + https://github.com/libgit2/libgit2/pull/6717 + +### Dependency updates + +* zlib: upgrade bundled zlib to v1.3 by @ethomson in + https://github.com/libgit2/libgit2/pull/6698 +* ntlmclient: update to latest upstream ntlmclient by @ethomson in + https://github.com/libgit2/libgit2/pull/6704 + +## New Contributors + +* @dvzrv made their first contribution in + https://github.com/libgit2/libgit2/pull/6608 +* @mascguy made their first contribution in + https://github.com/libgit2/libgit2/pull/6610 +* @steven9724 made their first contribution in + https://github.com/libgit2/libgit2/pull/6599 +* @lazka made their first contribution in + https://github.com/libgit2/libgit2/pull/6626 +* @roehling made their first contribution in + https://github.com/libgit2/libgit2/pull/6581 +* @7Ji made their first contribution in + https://github.com/libgit2/libgit2/pull/6651 +* @kempniu made their first contribution in + https://github.com/libgit2/libgit2/pull/6662 +* @thosey made their first contribution in + https://github.com/libgit2/libgit2/pull/6572 +* @wetneb made their first contribution in + https://github.com/libgit2/libgit2/pull/6638 +* @yori made their first contribution in + https://github.com/libgit2/libgit2/pull/6642 +* @pluehne made their first contribution in + https://github.com/libgit2/libgit2/pull/5904 +* @DavHau made their first contribution in + https://github.com/libgit2/libgit2/pull/6714 +* @vafada made their first contribution in + https://github.com/libgit2/libgit2/pull/6721 +* @semarie made their first contribution in + https://github.com/libgit2/libgit2/pull/6736 +* @christopherfujino made their first contribution in + https://github.com/libgit2/libgit2/pull/6631 +* @parnic made their first contribution in + https://github.com/libgit2/libgit2/pull/6738 +* @samueltardieu made their first contribution in + https://github.com/libgit2/libgit2/pull/6723 +* @xphoniex made their first contribution in + https://github.com/libgit2/libgit2/pull/6240 +* @adamharrison made their first contribution in + https://github.com/libgit2/libgit2/pull/6759 + +**Full Changelog**: https://github.com/libgit2/libgit2/compare/v1.7.0...v1.8.0 + v1.7 ---- From 9b29a5d30d1ea993498b9f9c91855ed0d56eea6a Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 20 Mar 2024 09:25:14 +0000 Subject: [PATCH 102/111] ci: update nightly workflows Update the nightly and benchmark workflows to only run steps in libgit2/libgit2 by default. Also update the benchmark workflow to use the latest download-artifact version. --- .github/workflows/benchmark.yml | 4 ++-- .github/workflows/nightly.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 51a5fc1c083..6ee492ac443 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -89,7 +89,7 @@ jobs: publish: name: Publish results needs: [ build ] - if: always() + if: ${{ always() && github.repository == 'libgit2/libgit2' }} runs-on: ubuntu-latest steps: - name: Check out benchmark repository @@ -100,7 +100,7 @@ jobs: fetch-depth: 0 ssh-key: ${{ secrets.BENCHMARKS_PUBLISH_KEY }} - name: Download test results - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 - name: Publish API run: | # Move today's benchmark run into the right place diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 570b9aa6ab6..33155c9e447 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -401,7 +401,7 @@ jobs: test_results: name: Test results needs: [ build ] - if: always() + if: ${{ always() && github.repository == 'libgit2/libgit2' }} runs-on: ubuntu-latest steps: - name: Download test results From 34073bf2e53eedc83f301ded79361ec0e506ca20 Mon Sep 17 00:00:00 2001 From: Florian Pircher Date: Sun, 24 Mar 2024 23:11:41 +0100 Subject: [PATCH 103/111] commit: Fix git_commit_create_from_stage without author and committer --- src/libgit2/commit.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libgit2/commit.c b/src/libgit2/commit.c index 5582a65aadf..47f6fed892f 100644 --- a/src/libgit2/commit.c +++ b/src/libgit2/commit.c @@ -1107,8 +1107,10 @@ int git_commit_create_from_stage( if (given_opts) memcpy(&opts, given_opts, sizeof(git_commit_create_options)); - if ((author = opts.author) == NULL || - (committer = opts.committer) == NULL) { + author = opts.author; + committer = opts.committer; + + if (!author || !committer) { if (git_signature_default(&default_signature, repo) < 0) goto done; From dd79fbb5abee81fc232e92cc4bbd1b11647ec092 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 26 Mar 2024 20:56:07 +0000 Subject: [PATCH 104/111] ci: give all nightly builds a unique id The new upload-artifact action fails on conflicting names; ensure that we give each artifact a unique name (keyed off the id). --- .github/workflows/nightly.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 33155c9e447..1888e5e174e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -215,6 +215,7 @@ jobs: ARCH: x86 - name: "Linux (Bionic, GCC, dynamically-loaded OpenSSL)" + id: bionic-gcc-dynamicopenssl container: name: bionic dockerfile: bionic @@ -226,6 +227,7 @@ jobs: SKIP_PUSHOPTIONS_TESTS: true os: ubuntu-latest - name: "Linux (x86, Bionic, Clang, OpenSSL)" + id: bionic-x86-clang-openssl container: name: bionic-x86 dockerfile: bionic @@ -238,6 +240,7 @@ jobs: SKIP_PUSHOPTIONS_TESTS: true os: ubuntu-latest - name: "Linux (x86, Bionic, GCC, OpenSSL)" + id: bionic-x86-gcc-openssl container: name: bionic-x86 dockerfile: bionic @@ -249,6 +252,7 @@ jobs: SKIP_PUSHOPTIONS_TESTS: true os: ubuntu-latest - name: "Linux (arm32, Bionic, GCC, OpenSSL)" + id: bionic-arm32-gcc-openssl container: name: bionic-arm32 dockerfile: bionic @@ -263,6 +267,7 @@ jobs: GITTEST_FLAKY_STAT: true os: ubuntu-latest - name: "Linux (arm64, Bionic, GCC, OpenSSL)" + id: bionic-arm64-gcc-openssl container: name: bionic-arm64 dockerfile: bionic @@ -300,6 +305,7 @@ jobs: SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true - name: "Windows (no mmap)" + id: windows-nommap os: windows-2019 env: ARCH: amd64 From 4d19e8c9c5113fc68dde130af3f6e66c2bd00f65 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 27 Mar 2024 09:59:48 +0000 Subject: [PATCH 105/111] settings: pull settings out into its own file --- src/libgit2/libgit2.c | 392 +------------------------------ src/libgit2/libgit2.h | 15 -- src/libgit2/settings.c | 408 +++++++++++++++++++++++++++++++++ src/libgit2/settings.h | 7 +- src/libgit2/streams/openssl.c | 4 +- src/libgit2/transports/http.h | 2 +- tests/libgit2/core/useragent.c | 4 +- 7 files changed, 422 insertions(+), 410 deletions(-) delete mode 100644 src/libgit2/libgit2.h create mode 100644 src/libgit2/settings.c diff --git a/src/libgit2/libgit2.c b/src/libgit2/libgit2.c index 777dcbbb558..1b6f1a1f846 100644 --- a/src/libgit2/libgit2.c +++ b/src/libgit2/libgit2.c @@ -5,25 +5,19 @@ * 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 "grafts.h" #include "hash.h" -#include "index.h" #include "merge_driver.h" #include "pool.h" #include "mwindow.h" -#include "object.h" -#include "odb.h" +#include "oid.h" #include "rand.h" -#include "refs.h" #include "runtime.h" +#include "settings.h" #include "sysdir.h" #include "thread.h" #include "git2/global.h" @@ -31,40 +25,12 @@ #include "streams/mbedtls.h" #include "streams/openssl.h" #include "streams/socket.h" -#include "transports/smart.h" -#include "transports/http.h" #include "transports/ssh_libssh2.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; -extern int git_socket_stream__connect_timeout; -extern int git_socket_stream__timeout; - -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[] = { @@ -87,17 +53,12 @@ int git_libgit2_init(void) git_mbedtls_stream_global_init, git_mwindow_global_init, git_pool_global_init, - git_libgit2_settings_global_init + git_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(); @@ -134,350 +95,3 @@ int git_libgit2_features(void) #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; - - case GIT_OPT_GET_OWNER_VALIDATION: - *(va_arg(ap, int *)) = git_repository__validate_ownership; - break; - - case GIT_OPT_SET_OWNER_VALIDATION: - git_repository__validate_ownership = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_GET_HOMEDIR: - { - 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_HOME)) < 0 || - (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0) - break; - - error = git_buf_fromstr(out, &str); - } - break; - - case GIT_OPT_SET_HOMEDIR: - error = git_sysdir_set(GIT_SYSDIR_HOME, va_arg(ap, const char *)); - break; - - case GIT_OPT_GET_SERVER_CONNECT_TIMEOUT: - *(va_arg(ap, int *)) = git_socket_stream__connect_timeout; - break; - - case GIT_OPT_SET_SERVER_CONNECT_TIMEOUT: - { - int timeout = va_arg(ap, int); - - if (timeout < 0) { - git_error_set(GIT_ERROR_INVALID, "invalid connect timeout"); - error = -1; - } else { - git_socket_stream__connect_timeout = timeout; - } - } - break; - - case GIT_OPT_GET_SERVER_TIMEOUT: - *(va_arg(ap, int *)) = git_socket_stream__timeout; - break; - - case GIT_OPT_SET_SERVER_TIMEOUT: - { - int timeout = va_arg(ap, int); - - if (timeout < 0) { - git_error_set(GIT_ERROR_INVALID, "invalid timeout"); - error = -1; - } else { - git_socket_stream__timeout = timeout; - } - } - 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 deleted file mode 100644 index a898367ae37..00000000000 --- a/src/libgit2/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/settings.c b/src/libgit2/settings.c new file mode 100644 index 00000000000..96a910d70cd --- /dev/null +++ b/src/libgit2/settings.c @@ -0,0 +1,408 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "settings.h" + +#include +#include "alloc.h" +#include "buf.h" +#include "cache.h" +#include "common.h" +#include "filter.h" +#include "grafts.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 "git2/global.h" +#include "streams/registry.h" +#include "streams/mbedtls.h" +#include "streams/openssl.h" +#include "streams/socket.h" +#include "transports/smart.h" +#include "transports/http.h" +#include "transports/ssh_libssh2.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; +extern int git_socket_stream__connect_timeout; +extern int git_socket_stream__timeout; + +char *git__user_agent; +char *git__ssl_ciphers; + +static void settings_global_shutdown(void) +{ + git__free(git__user_agent); + git__free(git__ssl_ciphers); + git_repository__free_extensions(); +} + +int git_settings_global_init(void) +{ + return git_runtime_shutdown_register(settings_global_shutdown); +} + +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_settings__user_agent(void) +{ + return git__user_agent; +} + +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; + + case GIT_OPT_GET_OWNER_VALIDATION: + *(va_arg(ap, int *)) = git_repository__validate_ownership; + break; + + case GIT_OPT_SET_OWNER_VALIDATION: + git_repository__validate_ownership = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_GET_HOMEDIR: + { + 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_HOME)) < 0 || + (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0) + break; + + error = git_buf_fromstr(out, &str); + } + break; + + case GIT_OPT_SET_HOMEDIR: + error = git_sysdir_set(GIT_SYSDIR_HOME, va_arg(ap, const char *)); + break; + + case GIT_OPT_GET_SERVER_CONNECT_TIMEOUT: + *(va_arg(ap, int *)) = git_socket_stream__connect_timeout; + break; + + case GIT_OPT_SET_SERVER_CONNECT_TIMEOUT: + { + int timeout = va_arg(ap, int); + + if (timeout < 0) { + git_error_set(GIT_ERROR_INVALID, "invalid connect timeout"); + error = -1; + } else { + git_socket_stream__connect_timeout = timeout; + } + } + break; + + case GIT_OPT_GET_SERVER_TIMEOUT: + *(va_arg(ap, int *)) = git_socket_stream__timeout; + break; + + case GIT_OPT_SET_SERVER_TIMEOUT: + { + int timeout = va_arg(ap, int); + + if (timeout < 0) { + git_error_set(GIT_ERROR_INVALID, "invalid timeout"); + error = -1; + } else { + git_socket_stream__timeout = timeout; + } + } + break; + + default: + git_error_set(GIT_ERROR_INVALID, "invalid option key"); + error = -1; + } + + va_end(ap); + + return error; +} diff --git a/src/libgit2/settings.h b/src/libgit2/settings.h index dc42ce93952..18ee5280ac7 100644 --- a/src/libgit2/settings.h +++ b/src/libgit2/settings.h @@ -4,8 +4,11 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ +#ifndef INCLUDE_settings_h__ +#define INCLUDE_settings_h__ extern int git_settings_global_init(void); -extern const char *git_libgit2__user_agent(void); -extern const char *git_libgit2__ssl_ciphers(void); +extern const char *git_settings__user_agent(void); + +#endif diff --git a/src/libgit2/streams/openssl.c b/src/libgit2/streams/openssl.c index 9db911e39b3..7cb8f7f927c 100644 --- a/src/libgit2/streams/openssl.c +++ b/src/libgit2/streams/openssl.c @@ -36,6 +36,8 @@ # include #endif +extern char *git__ssl_ciphers; + 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" @@ -105,7 +107,7 @@ static void git_openssl_free(void *mem) static int openssl_init(void) { long ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; - const char *ciphers = git_libgit2__ssl_ciphers(); + const char *ciphers = git__ssl_ciphers; #ifdef VALGRIND static bool allocators_initialized = false; #endif diff --git a/src/libgit2/transports/http.h b/src/libgit2/transports/http.h index 8e8e7226ed2..3d73237dd0f 100644 --- a/src/libgit2/transports/http.h +++ b/src/libgit2/transports/http.h @@ -17,7 +17,7 @@ extern bool git_http__expect_continue; GIT_INLINE(int) git_http__user_agent(git_str *buf) { - const char *ua = git_libgit2__user_agent(); + const char *ua = git_settings__user_agent(); if (!ua) ua = "libgit2 " LIBGIT2_VERSION; diff --git a/tests/libgit2/core/useragent.c b/tests/libgit2/core/useragent.c index a4ece902fd9..697f7440db6 100644 --- a/tests/libgit2/core/useragent.c +++ b/tests/libgit2/core/useragent.c @@ -6,9 +6,9 @@ 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_assert_equal_p(NULL, git_settings__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_assert_equal_s(custom_name, git_settings__user_agent()); cl_git_pass(git_libgit2_opts(GIT_OPT_GET_USER_AGENT, &buf)); cl_assert_equal_s(custom_name, buf.ptr); From 4839f4fbfc3bfd9e19ac140ed9b5654861b97596 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 27 Mar 2024 11:56:45 +0000 Subject: [PATCH 106/111] http: allow users more control over user-agent Users can now override the "product" portion of the user-agent (via GIT_OPT_SET_USER_AGENT_PRODUCT). This continues to default to "git/2.0", but users may define their own string, or may opt out of sending a user-agent entirely (by passing an empty string). Similarly, users may now also opt-out of sending any additional "comment" information by setting the GIT_OPT_SET_USER_AGENT value to an empty string. --- include/git2/common.h | 41 ++++++++++---- src/libgit2/settings.c | 86 ++++++++++++++++++++++------- src/libgit2/settings.h | 1 + src/libgit2/transports/http.h | 10 ---- src/libgit2/transports/httpclient.c | 32 +++++++++-- src/libgit2/transports/winhttp.c | 34 +++++++++++- tests/libgit2/core/useragent.c | 53 +++++++++++++++--- 7 files changed, 200 insertions(+), 57 deletions(-) diff --git a/include/git2/common.h b/include/git2/common.h index 0f42c34f683..b7cf20b31c9 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -228,7 +228,9 @@ typedef enum { GIT_OPT_SET_SERVER_CONNECT_TIMEOUT, GIT_OPT_GET_SERVER_CONNECT_TIMEOUT, GIT_OPT_SET_SERVER_TIMEOUT, - GIT_OPT_GET_SERVER_TIMEOUT + GIT_OPT_GET_SERVER_TIMEOUT, + GIT_OPT_SET_USER_AGENT_PRODUCT, + GIT_OPT_GET_USER_AGENT_PRODUCT } git_libgit2_opt_t; /** @@ -337,11 +339,35 @@ typedef enum { * * * opts(GIT_OPT_SET_USER_AGENT, const char *user_agent) * - * > Set the value of the User-Agent header. This value will be - * > appended to "git/1.0", for compatibility with other git clients. + * > Set the value of the comment section of the User-Agent header. + * > This can be information about your product and its version. + * > By default this is "libgit2" followed by the libgit2 version. * > - * > - `user_agent` is the value that will be delivered as the - * > User-Agent header on HTTP requests. + * > This value will be appended to User-Agent _product_, which + * > is typically set to "git/2.0". + * > + * > Set to the empty string ("") to not send any information in the + * > comment section, or set to NULL to restore the default. + * + * * opts(GIT_OPT_GET_USER_AGENT, git_buf *out) + * + * > Get the value of the User-Agent header. + * > The User-Agent is written to the `out` buffer. + * + * * opts(GIT_OPT_SET_USER_AGENT_PRODUCT, const char *user_agent_product) + * + * > Set the value of the product portion of the User-Agent header. + * > This defaults to "git/2.0", for compatibility with other git + * > clients. It is recommended to keep this as git/ for + * > compatibility with servers that do user-agent detection. + * > + * > Set to the empty string ("") to not send any user-agent string, + * > or set to NULL to restore the default. + * + * * opts(GIT_OPT_GET_USER_AGENT_PRODUCT, git_buf *out) + * + * > Get the value of the User-Agent product header. + * > The User-Agent product is written to the `out` buffer. * * * opts(GIT_OPT_SET_WINDOWS_SHAREMODE, unsigned long value) * @@ -377,11 +403,6 @@ typedef enum { * > * > - `ciphers` is the list of ciphers that are eanbled. * - * * opts(GIT_OPT_GET_USER_AGENT, git_buf *out) - * - * > Get the value of the User-Agent header. - * > The User-Agent is written to the `out` buffer. - * * * opts(GIT_OPT_ENABLE_OFS_DELTA, int enabled) * * > Enable or disable the use of "offset deltas" when creating packfiles, diff --git a/src/libgit2/settings.c b/src/libgit2/settings.c index 96a910d70cd..4a41830b8fd 100644 --- a/src/libgit2/settings.c +++ b/src/libgit2/settings.c @@ -51,11 +51,14 @@ extern int git_socket_stream__connect_timeout; extern int git_socket_stream__timeout; char *git__user_agent; +char *git__user_agent_product; char *git__ssl_ciphers; static void settings_global_shutdown(void) { git__free(git__user_agent); + git__free(git__user_agent_product); + git__free(git__ssl_ciphers); git_repository__free_extensions(); } @@ -89,9 +92,16 @@ static int config_level_to_sysdir(int *out, int config_level) return -1; } +const char *git_settings__user_agent_product(void) +{ + return git__user_agent_product ? git__user_agent_product : + "git/2.0"; +} + const char *git_settings__user_agent(void) { - return git__user_agent; + return git__user_agent ? git__user_agent : + "libgit2 " LIBGIT2_VERSION; } int git_libgit2_opts(int key, ...) @@ -211,14 +221,65 @@ int git_libgit2_opts(int key, ...) 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; + { + const char *new_agent = va_arg(ap, const char *); + + git__free(git__user_agent); + + if (new_agent) { + git__user_agent= git__strdup(new_agent); + + if (!git__user_agent) + error = -1; + } else { + git__user_agent = NULL; + } + } + 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_settings__user_agent())) < 0) + break; + + error = git_buf_fromstr(out, &str); + } + break; + + case GIT_OPT_SET_USER_AGENT_PRODUCT: + { + const char *new_agent = va_arg(ap, const char *); + + git__free(git__user_agent_product); + + if (new_agent) { + git__user_agent_product = git__strdup(new_agent); + + if (!git__user_agent_product) + error = -1; + } else { + git__user_agent_product = NULL; + } } + break; + + case GIT_OPT_GET_USER_AGENT_PRODUCT: + { + 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_settings__user_agent_product())) < 0) + break; + + error = git_buf_fromstr(out, &str); + } break; case GIT_OPT_ENABLE_STRICT_OBJECT_CREATION: @@ -245,19 +306,6 @@ int git_libgit2_opts(int key, ...) #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; diff --git a/src/libgit2/settings.h b/src/libgit2/settings.h index 18ee5280ac7..292936676aa 100644 --- a/src/libgit2/settings.h +++ b/src/libgit2/settings.h @@ -10,5 +10,6 @@ extern int git_settings_global_init(void); extern const char *git_settings__user_agent(void); +extern const char *git_settings__user_agent_product(void); #endif diff --git a/src/libgit2/transports/http.h b/src/libgit2/transports/http.h index 3d73237dd0f..7410202a820 100644 --- a/src/libgit2/transports/http.h +++ b/src/libgit2/transports/http.h @@ -15,14 +15,4 @@ extern bool git_http__expect_continue; -GIT_INLINE(int) git_http__user_agent(git_str *buf) -{ - const char *ua = git_settings__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 index e22a07ba1a0..2c2e36859a7 100644 --- a/src/libgit2/transports/httpclient.c +++ b/src/libgit2/transports/httpclient.c @@ -651,6 +651,30 @@ static int puts_host_and_port(git_str *buf, git_net_url *url, bool force_port) return git_str_oom(buf) ? -1 : 0; } +static int append_user_agent(git_str *buf) +{ + const char *product = git_settings__user_agent_product(); + const char *comment = git_settings__user_agent(); + + GIT_ASSERT(product && comment); + + if (!*product) + return 0; + + git_str_puts(buf, "User-Agent: "); + git_str_puts(buf, product); + + if (*comment) { + git_str_puts(buf, " ("); + git_str_puts(buf, comment); + git_str_puts(buf, ")"); + } + + git_str_puts(buf, "\r\n"); + + return git_str_oom(buf) ? -1 : 0; +} + static int generate_connect_request( git_http_client *client, git_http_request *request) @@ -665,9 +689,7 @@ static int generate_connect_request( 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"); + append_user_agent(buf); git_str_puts(buf, "Host: "); puts_host_and_port(buf, &client->server.url, true); @@ -711,9 +733,7 @@ static int generate_request( 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"); + append_user_agent(buf); git_str_puts(buf, "Host: "); puts_host_and_port(buf, request->url, false); diff --git a/src/libgit2/transports/winhttp.c b/src/libgit2/transports/winhttp.c index 031ff3f70b0..7eca4b7443b 100644 --- a/src/libgit2/transports/winhttp.c +++ b/src/libgit2/transports/winhttp.c @@ -746,6 +746,33 @@ static void CALLBACK winhttp_status( } } +static int user_agent(bool *exists, git_str *out) +{ + const char *product = git_settings__user_agent_product(); + const char *comment = git_settings__user_agent(); + + GIT_ASSERT(product && comment); + + if (!*product) { + *exists = false; + return 0; + } + + git_str_puts(out, product); + + if (*comment) { + git_str_puts(out, " ("); + git_str_puts(out, comment); + git_str_puts(out, ")"); + } + + if (git_str_oom(out)) + return -1; + + *exists = true; + return 0; +} + static int winhttp_connect( winhttp_subtransport *t) { @@ -757,6 +784,7 @@ static int winhttp_connect( int error = -1; int default_timeout = TIMEOUT_INFINITE; int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT; + bool has_ua = true; DWORD protocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | @@ -787,11 +815,11 @@ static int winhttp_connect( goto on_error; } - - if (git_http__user_agent(&ua) < 0) + if (user_agent(&has_ua, &ua) < 0) goto on_error; - if (git_utf8_to_16_alloc(&wide_ua, git_str_cstr(&ua)) < 0) { + if (has_ua && + 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; } diff --git a/tests/libgit2/core/useragent.c b/tests/libgit2/core/useragent.c index 697f7440db6..2e119de4490 100644 --- a/tests/libgit2/core/useragent.c +++ b/tests/libgit2/core/useragent.c @@ -1,17 +1,52 @@ #include "clar_libgit2.h" #include "settings.h" -void test_core_useragent__get(void) +static git_buf default_ua = GIT_BUF_INIT; +static git_buf default_product = GIT_BUF_INIT; + +void test_core_useragent__initialize(void) +{ + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_USER_AGENT, &default_ua)); + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_USER_AGENT_PRODUCT, &default_product)); +} + +void test_core_useragent__cleanup(void) +{ + git_libgit2_opts(GIT_OPT_SET_USER_AGENT, NULL); + git_libgit2_opts(GIT_OPT_SET_USER_AGENT_PRODUCT, NULL); + + git_buf_dispose(&default_ua); + git_buf_dispose(&default_product); +} + +void test_core_useragent__get_default(void) +{ + cl_assert(default_ua.size); + cl_assert(default_ua.ptr); + cl_assert(git__prefixcmp(default_ua.ptr, "libgit2 ") == 0); + + cl_assert(default_product.size); + cl_assert(default_product.ptr); + cl_assert(git__prefixcmp(default_product.ptr, "git/") == 0); +} + +void test_core_useragent__set(void) { - const char *custom_name = "super duper git"; - git_str buf = GIT_STR_INIT; + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, "foo bar 4.24")); + cl_assert_equal_s("foo bar 4.24", git_settings__user_agent()); + cl_assert_equal_s(default_product.ptr, git_settings__user_agent_product()); - cl_assert_equal_p(NULL, git_settings__user_agent()); - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, custom_name)); - cl_assert_equal_s(custom_name, git_settings__user_agent()); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT_PRODUCT, "baz/2.2.3")); + cl_assert_equal_s("foo bar 4.24", git_settings__user_agent()); + cl_assert_equal_s("baz/2.2.3", git_settings__user_agent_product()); - cl_git_pass(git_libgit2_opts(GIT_OPT_GET_USER_AGENT, &buf)); - cl_assert_equal_s(custom_name, buf.ptr); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, "")); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT_PRODUCT, "")); + cl_assert_equal_s("", git_settings__user_agent()); + cl_assert_equal_s("", git_settings__user_agent_product()); - git_str_dispose(&buf); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, NULL)); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT_PRODUCT, NULL)); + cl_assert_equal_s(default_ua.ptr, git_settings__user_agent()); + cl_assert_equal_s(default_product.ptr, git_settings__user_agent_product()); } From 6bed71e05d77566423756060032e4cdd467085f8 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 2 Apr 2024 22:08:50 +0100 Subject: [PATCH 107/111] docs: update includes Update our headers so that they can include the necessary definitions. Docs generators (in particular, `clang -Xclang -ast-dump`) were unable to see the necessary definitions. --- include/git2/email.h | 1 + include/git2/sys/email.h | 5 +++++ include/git2/worktree.h | 1 + 3 files changed, 7 insertions(+) diff --git a/include/git2/email.h b/include/git2/email.h index 20393653e9f..3389353e796 100644 --- a/include/git2/email.h +++ b/include/git2/email.h @@ -8,6 +8,7 @@ #define INCLUDE_git_email_h__ #include "common.h" +#include "diff.h" /** * @file git2/email.h diff --git a/include/git2/sys/email.h b/include/git2/sys/email.h index 6f4a2866209..5029f9a532c 100644 --- a/include/git2/sys/email.h +++ b/include/git2/sys/email.h @@ -7,6 +7,11 @@ #ifndef INCLUDE_sys_git_email_h__ #define INCLUDE_sys_git_email_h__ +#include "git2/common.h" +#include "git2/diff.h" +#include "git2/email.h" +#include "git2/types.h" + /** * @file git2/sys/email.h * @brief Advanced git email creation routines diff --git a/include/git2/worktree.h b/include/git2/worktree.h index 1af4c038573..a6e5d17c4b1 100644 --- a/include/git2/worktree.h +++ b/include/git2/worktree.h @@ -11,6 +11,7 @@ #include "buffer.h" #include "types.h" #include "strarray.h" +#include "checkout.h" /** * @file git2/worktrees.h From 6122f008c6f247a4729d246d9c469c2f1d3bf685 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 2 Apr 2024 22:13:24 +0100 Subject: [PATCH 108/111] docs: it's _return_ not _returns_ --- include/git2/refspec.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/git2/refspec.h b/include/git2/refspec.h index eaf7747465c..e7087132b04 100644 --- a/include/git2/refspec.h +++ b/include/git2/refspec.h @@ -58,7 +58,7 @@ GIT_EXTERN(const char *) git_refspec_dst(const git_refspec *refspec); * Get the refspec's string * * @param refspec the refspec - * @returns the refspec's original string + * @return the refspec's original string */ GIT_EXTERN(const char *) git_refspec_string(const git_refspec *refspec); From cc2a01524d7ff0cbab60b4c68908a364ea960360 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 2 Apr 2024 22:13:53 +0100 Subject: [PATCH 109/111] docs: document `git_remote_capability_t` --- include/git2/sys/remote.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/git2/sys/remote.h b/include/git2/sys/remote.h index 07309ab09b6..58950e1ec77 100644 --- a/include/git2/sys/remote.h +++ b/include/git2/sys/remote.h @@ -20,6 +20,9 @@ GIT_BEGIN_DECL +/** + * A remote's capabilities. + */ typedef enum { /** Remote supports fetching an advertised object by ID. */ GIT_REMOTE_CAPABILITY_TIP_OID = (1 << 0), From 4b043541ab480aaa083707c134c23d17be5baafc Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Mon, 8 Apr 2024 03:46:17 +0800 Subject: [PATCH 110/111] process.c: fix environ for macOS --- src/util/unix/process.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/util/unix/process.c b/src/util/unix/process.c index 15092cb217f..68c0384a4c4 100644 --- a/src/util/unix/process.c +++ b/src/util/unix/process.c @@ -15,7 +15,12 @@ #include "process.h" #include "strlist.h" -extern char **environ; +#ifdef __APPLE__ + #include + #define environ (*_NSGetEnviron()) +#else + extern char **environ; +#endif struct git_process { char **args; From 387d01c18633175adb05aaad1f0428dcf6a5c254 Mon Sep 17 00:00:00 2001 From: Jason Haslam Date: Wed, 10 Apr 2024 21:29:54 -0600 Subject: [PATCH 111/111] cmake: remove workaround that isn't compatible with Windows on ARM --- src/libgit2/CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/libgit2/CMakeLists.txt b/src/libgit2/CMakeLists.txt index 876a703e857..bc7cb5b3597 100644 --- a/src/libgit2/CMakeLists.txt +++ b/src/libgit2/CMakeLists.txt @@ -65,12 +65,6 @@ set_target_properties(libgit2package PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJE 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(libgit2package PROPERTIES STATIC_LIBRARY_FLAGS "/MACHINE:x64") -endif() - ide_split_sources(libgit2package) if(SONAME)