From 35c340779af55f6b8d501de79988b0345b311c70 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Tue, 3 Dec 2019 15:38:29 +0100 Subject: [PATCH 1/3] 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 ef4ebfda8..c5edcb86b 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 @@ out: 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 5e99dbf61..9ef083201 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 2/3] 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 049511da1..3f1acbdb0 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 c5edcb86b..5e4255b91 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 @@ out: 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 9ef083201..6a709476a 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 885744a77e8a727dd725f6a5fa8d80c199668fb7 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 5 Mar 2024 09:23:03 +0000 Subject: [PATCH 3/3] 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 3f1acbdb0..c41bc8e7b 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