{"thread":{"id":"39987","subject":"[PATCH v4 1/2] worktrees: add find_shared_symref","startedAt":"2015-07-31T22:11:08Z","lastAt":"2015-08-03T18:46:23Z","messageCount":5,"participants":["David Turner","Eric Sunshine","Johan Herland"],"isPatch":true,"patchVersion":4,"patchTotal":2},"messages":[{"id":"267234","messageId":"1438380669-11012-1-git-send-email-dturner@twopensource.com","threadId":"39987","inReplyTo":null,"subject":"[PATCH v4 1/2] worktrees: add find_shared_symref","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-07-31T22:11:08Z","receivedAt":"2015-07-31T22:11:08Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Add a new function, find_shared_symref, which contains the heart of\ndie_if_checked_out, but works for all symrefs.  Refactor\ndie_if_checked_out to use the same infrastructure as\nfind_shared_symref.\n\nSoon, we will use find_shared_symref to protect notes merges in\nworktrees.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n\nThis version includes Junio's suggested comment fix, and Eric's\nsuggestions for error message improvements.\n\n---\n branch.c | 39 ++++++++++++++++++++++++++++-----------\n branch.h |  8 ++++++++\n 2 files changed, 36 insertions(+), 11 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex c85be07..9b5e3b3 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -311,21 +311,22 @@ void remove_branch_state(void)\n \tunlink(git_path(\"SQUASH_MSG\"));\n }\n \n-static void check_linked_checkout(const char *branch, const char *id)\n+static char *find_linked_symref(const char *symref, const char *branch,\n+\t\t\t\tconst char *id)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n \tstruct strbuf path = STRBUF_INIT;\n \tstruct strbuf gitdir = STRBUF_INIT;\n \n \t/*\n-\t * $GIT_COMMON_DIR/HEAD is practically outside\n-\t * $GIT_DIR so resolve_ref_unsafe() won't work (it\n-\t * uses git_path). Parse the ref ourselves.\n+\t * $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside\n+\t * $GIT_DIR so resolve_ref_unsafe() won't work (it uses\n+\t * git_path). Parse the ref ourselves.\n \t */\n \tif (id)\n-\t\tstrbuf_addf(&path, \"%s/worktrees/%s/HEAD\", get_git_common_dir(), id);\n+\t\tstrbuf_addf(&path, \"%s/worktrees/%s/%s\", get_git_common_dir(), id, symref);\n \telse\n-\t\tstrbuf_addf(&path, \"%s/HEAD\", get_git_common_dir());\n+\t\tstrbuf_addf(&path, \"%s/%s\", get_git_common_dir(), symref);\n \n \tif (!strbuf_readlink(&sb, path.buf, 0)) {\n \t\tif (!starts_with(sb.buf, \"refs/\") ||\n@@ -349,31 +350,47 @@ static void check_linked_checkout(const char *branch, const char *id)\n \t\tstrbuf_addstr(&gitdir, get_git_common_dir());\n \tskip_prefix(branch, \"refs/heads/\", &branch);\n \tstrbuf_strip_suffix(&gitdir, \".git\");\n-\tdie(_(\"'%s' is already checked out at '%s'\"), branch, gitdir.buf);\n+\treturn strbuf_detach(&gitdir, NULL);\n done:\n \tstrbuf_release(&path);\n \tstrbuf_release(&sb);\n \tstrbuf_release(&gitdir);\n+\n+\treturn NULL;\n }\n \n-void die_if_checked_out(const char *branch)\n+char *find_shared_symref(const char *symref, const char *target)\n {\n \tstruct strbuf path = STRBUF_INIT;\n \tDIR *dir;\n \tstruct dirent *d;\n+\tchar *existing;\n \n-\tcheck_linked_checkout(branch, NULL);\n+\tif ((existing = find_linked_symref(symref, target, NULL)))\n+\t\treturn existing;\n \n \tstrbuf_addf(&path, \"%s/worktrees\", get_git_common_dir());\n \tdir = opendir(path.buf);\n \tstrbuf_release(&path);\n \tif (!dir)\n-\t\treturn;\n+\t\treturn NULL;\n \n \twhile ((d = readdir(dir)) != NULL) {\n \t\tif (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n \t\t\tcontinue;\n-\t\tcheck_linked_checkout(branch, d->d_name);\n+\t\tif ((existing = find_linked_symref(symref, target, d->d_name)))\n+\t\t\treturn existing;\n \t}\n \tclosedir(dir);\n+\n+\treturn NULL;\n+}\n+\n+void die_if_checked_out(const char *branch)\n+{\n+\tchar *existing;\n+\n+\texisting = find_shared_symref(\"HEAD\", branch);\n+\tif (existing)\n+\t\tdie(_(\"'%s' is already checked out at '%s'\"), branch, existing);\n }\ndiff --git a/branch.h b/branch.h\nindex 58aa45f..0f466c7 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -59,4 +59,12 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name);\n  */\n extern void die_if_checked_out(const char *branch);\n \n+/*\n+ * Check if a per-worktree symref points to a ref in the main worktree\n+ * or any linked worktree, and return the path to the exising worktree\n+ * if it is.  Returns NULL if there is no existing ref.  The caller is\n+ * responsible for freeing the returned path.\n+ */\n+extern char *find_shared_symref(const char *symref, const char *branch);\n+\n #endif\n-- \n2.0.4.315.gad8727a-twtrsrc\n"},{"id":"267235","messageId":"1438380669-11012-2-git-send-email-dturner@twopensource.com","threadId":"39987","inReplyTo":"1438380669-11012-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v4 2/2] notes: handle multiple worktrees","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-07-31T22:11:09Z","receivedAt":"2015-07-31T22:11:09Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Before creating NOTES_MERGE_REF, check NOTES_MERGE_REF using\nfind_shared_symref and die if we find one.  This prevents simultaneous\nmerges to the same notes branch from different worktrees.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n builtin/notes.c                  |  5 +++\n t/t3320-notes-merge-worktrees.sh | 72 ++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 77 insertions(+)\n create mode 100755 t/t3320-notes-merge-worktrees.sh\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 63f95fc..e4dda79 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -19,6 +19,7 @@\n #include \"string-list.h\"\n #include \"notes-merge.h\"\n #include \"notes-utils.h\"\n+#include \"branch.h\"\n \n static const char * const git_notes_usage[] = {\n \tN_(\"git notes [--ref <notes-ref>] [list [<object>]]\"),\n@@ -825,10 +826,14 @@ static int merge(int argc, const char **argv, const char *prefix)\n \t\tupdate_ref(msg.buf, default_notes_ref(), result_sha1, NULL,\n \t\t\t   0, UPDATE_REFS_DIE_ON_ERR);\n \telse { /* Merge has unresolved conflicts */\n+\t\tchar *existing;\n \t\t/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */\n \t\tupdate_ref(msg.buf, \"NOTES_MERGE_PARTIAL\", result_sha1, NULL,\n \t\t\t   0, UPDATE_REFS_DIE_ON_ERR);\n \t\t/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */\n+\t\texisting = find_shared_symref(\"NOTES_MERGE_REF\", default_notes_ref());\n+\t\tif (existing)\n+\t\t\tdie(_(\"A notes merge on %s is already in-progress for %s\"), default_notes_ref(), existing);\n \t\tif (create_symref(\"NOTES_MERGE_REF\", default_notes_ref(), NULL))\n \t\t\tdie(\"Failed to store link to current notes ref (%s)\",\n \t\t\t    default_notes_ref());\ndiff --git a/t/t3320-notes-merge-worktrees.sh b/t/t3320-notes-merge-worktrees.sh\nnew file mode 100755\nindex 0000000..997621f\n--- /dev/null\n+++ b/t/t3320-notes-merge-worktrees.sh\n@@ -0,0 +1,72 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2015 Twitter, Inc\n+#\n+\n+test_description='Test merging of notes trees in multiple worktrees'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup commit' '\n+\ttest_commit tantrum\n+'\n+\n+commit_tantrum=$(git rev-parse tantrum^{commit})\n+\n+test_expect_success 'setup notes ref (x)' '\n+\tgit config core.notesRef refs/notes/x &&\n+\tgit notes add -m \"x notes on tantrum\" tantrum\n+'\n+\n+test_expect_success 'setup local branch (y)' '\n+\tgit update-ref refs/notes/y refs/notes/x &&\n+\tgit config core.notesRef refs/notes/y &&\n+\tgit notes remove tantrum\n+'\n+\n+test_expect_success 'setup remote branch (z)' '\n+\tgit update-ref refs/notes/z refs/notes/x &&\n+\tgit config core.notesRef refs/notes/z &&\n+\tgit notes add -f -m \"conflicting notes on tantrum\" tantrum\n+'\n+\n+test_expect_success 'modify notes ref ourselves (x)' '\n+\tgit config core.notesRef refs/notes/x &&\n+\tgit notes add -f -m \"more conflicting notes on tantrum\" tantrum\n+'\n+\n+test_expect_success 'create some new worktrees' '\n+\tgit worktree add -b newbranch worktree master &&\n+\tgit worktree add -b newbranch2 worktree2 master\n+'\n+\n+test_expect_success 'merge z into y fails and sets NOTES_MERGE_REF' '\n+\tgit config core.notesRef refs/notes/y &&\n+\ttest_must_fail git notes merge z &&\n+\techo \"ref: refs/notes/y\" > expect &&\n+\ttest_cmp .git/NOTES_MERGE_REF expect\n+'\n+\n+test_expect_success 'merge z into y while mid-merge in another workdir fails' '\n+\t(\n+\t\tcd worktree &&\n+\t\tgit config core.notesRef refs/notes/y &&\n+\t\ttest_must_fail git notes merge z 2>err &&\n+\t\tgrep \"A notes merge on refs/notes/y is already in-progress for\" err\n+\t) &&\n+\ttest_path_is_missing .git/worktrees/worktree/NOTES_MERGE_REF\n+'\n+\n+test_expect_success 'merge z into x while mid-merge on y succeeds' '\n+\t(\n+\t\tcd worktree2 &&\n+\t\tgit config core.notesRef refs/notes/x &&\n+\t\ttest_must_fail git notes merge z 2>&1 >out &&\n+\t\tgrep \"Automatic notes merge failed\" out\n+\t\tgrep -v \"A notes merge on refs/notes/x is already in-progress for\" out\n+\t) &&\n+\techo \"ref: refs/notes/x\" > expect &&\n+\ttest_cmp .git/worktrees/worktree2/NOTES_MERGE_REF expect\n+'\n+\n+test_done\n-- \n2.0.4.315.gad8727a-twtrsrc\n"},{"id":"267236","messageId":"CAPig+cRWQDAB5AMPy4GUyckKr+4eqra_DiZGbfj1L2BnUfBNrA@mail.gmail.com","threadId":"39987","inReplyTo":"1438380669-11012-1-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v4 1/2] worktrees: add find_shared_symref","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2015-07-31T22:20:34Z","receivedAt":"2015-07-31T22:20:34Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Jul 31, 2015 at 6:11 PM, David Turner <dturner@twopensource.com> wrote:\n> Add a new function, find_shared_symref, which contains the heart of\n> die_if_checked_out, but works for all symrefs.  Refactor\n\nSlightly more explanatory:\n\n    ..., but works for any symref, not just HEAD. Refactor\n\nMore below.\n\n> die_if_checked_out to use the same infrastructure as\n> find_shared_symref.\n>\n> Soon, we will use find_shared_symref to protect notes merges in\n> worktrees.\n>\n> Signed-off-by: David Turner <dturner@twopensource.com>\n> ---\n> diff --git a/branch.c b/branch.c\n> index c85be07..9b5e3b3 100644\n> --- a/branch.c\n> +++ b/branch.c\n> @@ -349,31 +350,47 @@ static void check_linked_checkout(const char *branch, const char *id)\n>                 strbuf_addstr(&gitdir, get_git_common_dir());\n>         skip_prefix(branch, \"refs/heads/\", &branch);\n>         strbuf_strip_suffix(&gitdir, \".git\");\n> -       die(_(\"'%s' is already checked out at '%s'\"), branch, gitdir.buf);\n> +       return strbuf_detach(&gitdir, NULL);\n\nBy returning here, you're now leaking 'path' and 'sb' (see the 'done'\nlabel below).\n\n>  done:\n>         strbuf_release(&path);\n>         strbuf_release(&sb);\n>         strbuf_release(&gitdir);\n> +\n> +       return NULL;\n>  }\n>\n> -void die_if_checked_out(const char *branch)\n> +char *find_shared_symref(const char *symref, const char *target)\n>  {\n>         struct strbuf path = STRBUF_INIT;\n>         DIR *dir;\n>         struct dirent *d;\n> +       char *existing;\n>\n> -       check_linked_checkout(branch, NULL);\n> +       if ((existing = find_linked_symref(symref, target, NULL)))\n> +               return existing;\n>\n>         strbuf_addf(&path, \"%s/worktrees\", get_git_common_dir());\n>         dir = opendir(path.buf);\n>         strbuf_release(&path);\n>         if (!dir)\n> -               return;\n> +               return NULL;\n>\n>         while ((d = readdir(dir)) != NULL) {\n>                 if (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n>                         continue;\n> -               check_linked_checkout(branch, d->d_name);\n> +               if ((existing = find_linked_symref(symref, target, d->d_name)))\n> +                       return existing;\n\nBy returning here, you're leaking the open 'dir'.\n\n>         }\n>         closedir(dir);\n> +\n> +       return NULL;\n> +}\n> +\n> +void die_if_checked_out(const char *branch)\n> +{\n> +       char *existing;\n> +\n> +       existing = find_shared_symref(\"HEAD\", branch);\n> +       if (existing)\n> +               die(_(\"'%s' is already checked out at '%s'\"), branch, existing);\n>  }\n> diff --git a/branch.h b/branch.h\n> index 58aa45f..0f466c7 100644\n> --- a/branch.h\n> +++ b/branch.h\n> @@ -59,4 +59,12 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name);\n>   */\n>  extern void die_if_checked_out(const char *branch);\n>\n> +/*\n> + * Check if a per-worktree symref points to a ref in the main worktree\n> + * or any linked worktree, and return the path to the exising worktree\n> + * if it is.  Returns NULL if there is no existing ref.  The caller is\n> + * responsible for freeing the returned path.\n> + */\n> +extern char *find_shared_symref(const char *symref, const char *branch);\n\nI think you meant s/branch/target/.\n\n>  #endif\n> --\n> 2.0.4.315.gad8727a-twtrsrc\n"},{"id":"267269","messageId":"CALKQrgcrpQ2j2J-65RJhN7owwmSzhqyGinGjUbicFbY=m82tNA@mail.gmail.com","threadId":"39987","inReplyTo":"1438380669-11012-2-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v4 2/2] notes: handle multiple worktrees","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2015-08-01T13:51:18Z","receivedAt":"2015-08-01T13:51:18Z","isPatch":true,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"On Sat, Aug 1, 2015 at 12:11 AM, David Turner <dturner@twopensource.com> wrote:\n> Before creating NOTES_MERGE_REF, check NOTES_MERGE_REF using\n> find_shared_symref and die if we find one.  This prevents simultaneous\n> merges to the same notes branch from different worktrees.\n>\n> Signed-off-by: David Turner <dturner@twopensource.com>\n> ---\n>  builtin/notes.c                  |  5 +++\n>  t/t3320-notes-merge-worktrees.sh | 72 ++++++++++++++++++++++++++++++++++++++++\n>  2 files changed, 77 insertions(+)\n>  create mode 100755 t/t3320-notes-merge-worktrees.sh\n>\n> diff --git a/builtin/notes.c b/builtin/notes.c\n> index 63f95fc..e4dda79 100644\n> --- a/builtin/notes.c\n> +++ b/builtin/notes.c\n> @@ -19,6 +19,7 @@\n>  #include \"string-list.h\"\n>  #include \"notes-merge.h\"\n>  #include \"notes-utils.h\"\n> +#include \"branch.h\"\n>\n>  static const char * const git_notes_usage[] = {\n>         N_(\"git notes [--ref <notes-ref>] [list [<object>]]\"),\n> @@ -825,10 +826,14 @@ static int merge(int argc, const char **argv, const char *prefix)\n>                 update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,\n>                            0, UPDATE_REFS_DIE_ON_ERR);\n>         else { /* Merge has unresolved conflicts */\n> +               char *existing;\n>                 /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */\n>                 update_ref(msg.buf, \"NOTES_MERGE_PARTIAL\", result_sha1, NULL,\n>                            0, UPDATE_REFS_DIE_ON_ERR);\n>                 /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */\n> +               existing = find_shared_symref(\"NOTES_MERGE_REF\", default_notes_ref());\n\nPlease confirm my assumption here: existing originally comes from a\nstrbuf_detach(), so it's the caller's (i.e. our) responsibility to\nfree() it, but we don't care, as we just die()d anyway. Correct?\n\n> +               if (existing)\n> +                       die(_(\"A notes merge on %s is already in-progress for %s\"), default_notes_ref(), existing);\n\nNot sure about your prepositions here. Would this maybe read better?:\n\n  A notes merge into %s is already in-progress at %s\n\n>                 if (create_symref(\"NOTES_MERGE_REF\", default_notes_ref(), NULL))\n>                         die(\"Failed to store link to current notes ref (%s)\",\n>                             default_notes_ref());\n> diff --git a/t/t3320-notes-merge-worktrees.sh b/t/t3320-notes-merge-worktrees.sh\n> new file mode 100755\n> index 0000000..997621f\n> --- /dev/null\n> +++ b/t/t3320-notes-merge-worktrees.sh\n> @@ -0,0 +1,72 @@\n> +#!/bin/sh\n> +#\n> +# Copyright (c) 2015 Twitter, Inc\n> +#\n> +\n> +test_description='Test merging of notes trees in multiple worktrees'\n> +\n> +. ./test-lib.sh\n> +\n> +test_expect_success 'setup commit' '\n> +       test_commit tantrum\n> +'\n> +\n> +commit_tantrum=$(git rev-parse tantrum^{commit})\n> +\n> +test_expect_success 'setup notes ref (x)' '\n> +       git config core.notesRef refs/notes/x &&\n> +       git notes add -m \"x notes on tantrum\" tantrum\n> +'\n> +\n> +test_expect_success 'setup local branch (y)' '\n> +       git update-ref refs/notes/y refs/notes/x &&\n> +       git config core.notesRef refs/notes/y &&\n> +       git notes remove tantrum\n> +'\n> +\n> +test_expect_success 'setup remote branch (z)' '\n> +       git update-ref refs/notes/z refs/notes/x &&\n> +       git config core.notesRef refs/notes/z &&\n> +       git notes add -f -m \"conflicting notes on tantrum\" tantrum\n> +'\n> +\n> +test_expect_success 'modify notes ref ourselves (x)' '\n> +       git config core.notesRef refs/notes/x &&\n> +       git notes add -f -m \"more conflicting notes on tantrum\" tantrum\n> +'\n> +\n> +test_expect_success 'create some new worktrees' '\n> +       git worktree add -b newbranch worktree master &&\n> +       git worktree add -b newbranch2 worktree2 master\n> +'\n> +\n> +test_expect_success 'merge z into y fails and sets NOTES_MERGE_REF' '\n> +       git config core.notesRef refs/notes/y &&\n> +       test_must_fail git notes merge z &&\n> +       echo \"ref: refs/notes/y\" > expect &&\n> +       test_cmp .git/NOTES_MERGE_REF expect\n> +'\n> +\n> +test_expect_success 'merge z into y while mid-merge in another workdir fails' '\n> +       (\n> +               cd worktree &&\n> +               git config core.notesRef refs/notes/y &&\n> +               test_must_fail git notes merge z 2>err &&\n> +               grep \"A notes merge on refs/notes/y is already in-progress for\" err\n> +       ) &&\n> +       test_path_is_missing .git/worktrees/worktree/NOTES_MERGE_REF\n> +'\n> +\n> +test_expect_success 'merge z into x while mid-merge on y succeeds' '\n> +       (\n> +               cd worktree2 &&\n> +               git config core.notesRef refs/notes/x &&\n> +               test_must_fail git notes merge z 2>&1 >out &&\n> +               grep \"Automatic notes merge failed\" out\n\nMissing &&?\n\n> +               grep -v \"A notes merge on refs/notes/x is already in-progress for\" out\n> +       ) &&\n> +       echo \"ref: refs/notes/x\" > expect &&\n> +       test_cmp .git/worktrees/worktree2/NOTES_MERGE_REF expect\n> +'\n> +\n> +test_done\n\nOtherwise, this looks good to me.\n\n\n...Johan\n\n> --\n> 2.0.4.315.gad8727a-twtrsrc\n>\n> --\n> To unsubscribe from this list: send the line \"unsubscribe git\" in\n> the body of a message to majordomo@vger.kernel.org\n> More majordomo info at  http://vger.kernel.org/majordomo-info.html\n\n\n\n-- \nJohan Herland, <johan@herland.net>\nwww.herland.net\n"},{"id":"267331","messageId":"1438627583.4439.11.camel@twopensource.com","threadId":"39987","inReplyTo":"CALKQrgcrpQ2j2J-65RJhN7owwmSzhqyGinGjUbicFbY=m82tNA@mail.gmail.com","subject":"Re: [PATCH v4 2/2] notes: handle multiple worktrees","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-08-03T18:46:23Z","receivedAt":"2015-08-03T18:46:23Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Sat, 2015-08-01 at 15:51 +0200, Johan Herland wrote:\n> On Sat, Aug 1, 2015 at 12:11 AM, David Turner <dturner@twopensource.com> wrote:\n> > Before creating NOTES_MERGE_REF, check NOTES_MERGE_REF using\n> > find_shared_symref and die if we find one.  This prevents simultaneous\n> > merges to the same notes branch from different worktrees.\n> >\n> > Signed-off-by: David Turner <dturner@twopensource.com>\n> > ---\n> >  builtin/notes.c                  |  5 +++\n> >  t/t3320-notes-merge-worktrees.sh | 72 ++++++++++++++++++++++++++++++++++++++++\n> >  2 files changed, 77 insertions(+)\n> >  create mode 100755 t/t3320-notes-merge-worktrees.sh\n> >\n> > diff --git a/builtin/notes.c b/builtin/notes.c\n> > index 63f95fc..e4dda79 100644\n> > --- a/builtin/notes.c\n> > +++ b/builtin/notes.c\n> > @@ -19,6 +19,7 @@\n> >  #include \"string-list.h\"\n> >  #include \"notes-merge.h\"\n> >  #include \"notes-utils.h\"\n> > +#include \"branch.h\"\n> >\n> >  static const char * const git_notes_usage[] = {\n> >         N_(\"git notes [--ref <notes-ref>] [list [<object>]]\"),\n> > @@ -825,10 +826,14 @@ static int merge(int argc, const char **argv, const char *prefix)\n> >                 update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,\n> >                            0, UPDATE_REFS_DIE_ON_ERR);\n> >         else { /* Merge has unresolved conflicts */\n> > +               char *existing;\n> >                 /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */\n> >                 update_ref(msg.buf, \"NOTES_MERGE_PARTIAL\", result_sha1, NULL,\n> >                            0, UPDATE_REFS_DIE_ON_ERR);\n> >                 /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */\n> > +               existing = find_shared_symref(\"NOTES_MERGE_REF\", default_notes_ref());\n> \n> Please confirm my assumption here: existing originally comes from a\n> strbuf_detach(), so it's the caller's (i.e. our) responsibility to\n> free() it, but we don't care, as we just die()d anyway. Correct?\n\nConfirmed.\n\n I will fix the other issues you reported.\n"}]}