Merge branch 'dt/notes-multiple'
When linked worktree is used, simultaneous "notes merge" instances for the same ref in refs/notes/* are prevented from stomping on each other. * dt/notes-multiple: notes: handle multiple worktrees worktrees: add find_shared_symref
This commit is contained in:
46
branch.c
46
branch.c
@ -311,21 +311,23 @@ void remove_branch_state(void)
|
|||||||
unlink(git_path_squash_msg());
|
unlink(git_path_squash_msg());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void check_linked_checkout(const char *branch, const char *id)
|
static char *find_linked_symref(const char *symref, const char *branch,
|
||||||
|
const char *id)
|
||||||
{
|
{
|
||||||
struct strbuf sb = STRBUF_INIT;
|
struct strbuf sb = STRBUF_INIT;
|
||||||
struct strbuf path = STRBUF_INIT;
|
struct strbuf path = STRBUF_INIT;
|
||||||
struct strbuf gitdir = STRBUF_INIT;
|
struct strbuf gitdir = STRBUF_INIT;
|
||||||
|
char *existing = NULL;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* $GIT_COMMON_DIR/HEAD is practically outside
|
* $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside
|
||||||
* $GIT_DIR so resolve_ref_unsafe() won't work (it
|
* $GIT_DIR so resolve_ref_unsafe() won't work (it uses
|
||||||
* uses git_path). Parse the ref ourselves.
|
* git_path). Parse the ref ourselves.
|
||||||
*/
|
*/
|
||||||
if (id)
|
if (id)
|
||||||
strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
|
strbuf_addf(&path, "%s/worktrees/%s/%s", get_git_common_dir(), id, symref);
|
||||||
else
|
else
|
||||||
strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
|
strbuf_addf(&path, "%s/%s", get_git_common_dir(), symref);
|
||||||
|
|
||||||
if (!strbuf_readlink(&sb, path.buf, 0)) {
|
if (!strbuf_readlink(&sb, path.buf, 0)) {
|
||||||
if (!starts_with(sb.buf, "refs/") ||
|
if (!starts_with(sb.buf, "refs/") ||
|
||||||
@ -347,33 +349,53 @@ static void check_linked_checkout(const char *branch, const char *id)
|
|||||||
strbuf_rtrim(&gitdir);
|
strbuf_rtrim(&gitdir);
|
||||||
} else
|
} else
|
||||||
strbuf_addstr(&gitdir, get_git_common_dir());
|
strbuf_addstr(&gitdir, get_git_common_dir());
|
||||||
skip_prefix(branch, "refs/heads/", &branch);
|
|
||||||
strbuf_strip_suffix(&gitdir, ".git");
|
strbuf_strip_suffix(&gitdir, ".git");
|
||||||
die(_("'%s' is already checked out at '%s'"), branch, gitdir.buf);
|
|
||||||
|
existing = strbuf_detach(&gitdir, NULL);
|
||||||
done:
|
done:
|
||||||
strbuf_release(&path);
|
strbuf_release(&path);
|
||||||
strbuf_release(&sb);
|
strbuf_release(&sb);
|
||||||
strbuf_release(&gitdir);
|
strbuf_release(&gitdir);
|
||||||
|
|
||||||
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
void die_if_checked_out(const char *branch)
|
char *find_shared_symref(const char *symref, const char *target)
|
||||||
{
|
{
|
||||||
struct strbuf path = STRBUF_INIT;
|
struct strbuf path = STRBUF_INIT;
|
||||||
DIR *dir;
|
DIR *dir;
|
||||||
struct dirent *d;
|
struct dirent *d;
|
||||||
|
char *existing;
|
||||||
|
|
||||||
check_linked_checkout(branch, NULL);
|
if ((existing = find_linked_symref(symref, target, NULL)))
|
||||||
|
return existing;
|
||||||
|
|
||||||
strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
|
strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
|
||||||
dir = opendir(path.buf);
|
dir = opendir(path.buf);
|
||||||
strbuf_release(&path);
|
strbuf_release(&path);
|
||||||
if (!dir)
|
if (!dir)
|
||||||
return;
|
return NULL;
|
||||||
|
|
||||||
while ((d = readdir(dir)) != NULL) {
|
while ((d = readdir(dir)) != NULL) {
|
||||||
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
|
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
|
||||||
continue;
|
continue;
|
||||||
check_linked_checkout(branch, d->d_name);
|
existing = find_linked_symref(symref, target, d->d_name);
|
||||||
|
if (existing)
|
||||||
|
goto done;
|
||||||
}
|
}
|
||||||
|
done:
|
||||||
closedir(dir);
|
closedir(dir);
|
||||||
|
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
void die_if_checked_out(const char *branch)
|
||||||
|
{
|
||||||
|
char *existing;
|
||||||
|
|
||||||
|
existing = find_shared_symref("HEAD", branch);
|
||||||
|
if (existing) {
|
||||||
|
skip_prefix(branch, "refs/heads/", &branch);
|
||||||
|
die(_("'%s' is already checked out at '%s'"), branch, existing);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
8
branch.h
8
branch.h
@ -59,4 +59,12 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name);
|
|||||||
*/
|
*/
|
||||||
extern void die_if_checked_out(const char *branch);
|
extern void die_if_checked_out(const char *branch);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if a per-worktree symref points to a ref in the main worktree
|
||||||
|
* or any linked worktree, and return the path to the exising worktree
|
||||||
|
* if it is. Returns NULL if there is no existing ref. The caller is
|
||||||
|
* responsible for freeing the returned path.
|
||||||
|
*/
|
||||||
|
extern char *find_shared_symref(const char *symref, const char *target);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "string-list.h"
|
#include "string-list.h"
|
||||||
#include "notes-merge.h"
|
#include "notes-merge.h"
|
||||||
#include "notes-utils.h"
|
#include "notes-utils.h"
|
||||||
|
#include "branch.h"
|
||||||
|
|
||||||
static const char * const git_notes_usage[] = {
|
static const char * const git_notes_usage[] = {
|
||||||
N_("git notes [--ref <notes-ref>] [list [<object>]]"),
|
N_("git notes [--ref <notes-ref>] [list [<object>]]"),
|
||||||
@ -825,10 +826,15 @@ static int merge(int argc, const char **argv, const char *prefix)
|
|||||||
update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,
|
update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,
|
||||||
0, UPDATE_REFS_DIE_ON_ERR);
|
0, UPDATE_REFS_DIE_ON_ERR);
|
||||||
else { /* Merge has unresolved conflicts */
|
else { /* Merge has unresolved conflicts */
|
||||||
|
char *existing;
|
||||||
/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
|
/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
|
||||||
update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL,
|
update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL,
|
||||||
0, UPDATE_REFS_DIE_ON_ERR);
|
0, UPDATE_REFS_DIE_ON_ERR);
|
||||||
/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
|
/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
|
||||||
|
existing = find_shared_symref("NOTES_MERGE_REF", default_notes_ref());
|
||||||
|
if (existing)
|
||||||
|
die(_("A notes merge into %s is already in-progress at %s"),
|
||||||
|
default_notes_ref(), existing);
|
||||||
if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
|
if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
|
||||||
die("Failed to store link to current notes ref (%s)",
|
die("Failed to store link to current notes ref (%s)",
|
||||||
default_notes_ref());
|
default_notes_ref());
|
||||||
|
72
t/t3320-notes-merge-worktrees.sh
Executable file
72
t/t3320-notes-merge-worktrees.sh
Executable file
@ -0,0 +1,72 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Copyright (c) 2015 Twitter, Inc
|
||||||
|
#
|
||||||
|
|
||||||
|
test_description='Test merging of notes trees in multiple worktrees'
|
||||||
|
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
test_expect_success 'setup commit' '
|
||||||
|
test_commit tantrum
|
||||||
|
'
|
||||||
|
|
||||||
|
commit_tantrum=$(git rev-parse tantrum^{commit})
|
||||||
|
|
||||||
|
test_expect_success 'setup notes ref (x)' '
|
||||||
|
git config core.notesRef refs/notes/x &&
|
||||||
|
git notes add -m "x notes on tantrum" tantrum
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'setup local branch (y)' '
|
||||||
|
git update-ref refs/notes/y refs/notes/x &&
|
||||||
|
git config core.notesRef refs/notes/y &&
|
||||||
|
git notes remove tantrum
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'setup remote branch (z)' '
|
||||||
|
git update-ref refs/notes/z refs/notes/x &&
|
||||||
|
git config core.notesRef refs/notes/z &&
|
||||||
|
git notes add -f -m "conflicting notes on tantrum" tantrum
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'modify notes ref ourselves (x)' '
|
||||||
|
git config core.notesRef refs/notes/x &&
|
||||||
|
git notes add -f -m "more conflicting notes on tantrum" tantrum
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'create some new worktrees' '
|
||||||
|
git worktree add -b newbranch worktree master &&
|
||||||
|
git worktree add -b newbranch2 worktree2 master
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'merge z into y fails and sets NOTES_MERGE_REF' '
|
||||||
|
git config core.notesRef refs/notes/y &&
|
||||||
|
test_must_fail git notes merge z &&
|
||||||
|
echo "ref: refs/notes/y" >expect &&
|
||||||
|
test_cmp .git/NOTES_MERGE_REF expect
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'merge z into y while mid-merge in another workdir fails' '
|
||||||
|
(
|
||||||
|
cd worktree &&
|
||||||
|
git config core.notesRef refs/notes/y &&
|
||||||
|
test_must_fail git notes merge z 2>err &&
|
||||||
|
grep "A notes merge into refs/notes/y is already in-progress at" err
|
||||||
|
) &&
|
||||||
|
test_path_is_missing .git/worktrees/worktree/NOTES_MERGE_REF
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'merge z into x while mid-merge on y succeeds' '
|
||||||
|
(
|
||||||
|
cd worktree2 &&
|
||||||
|
git config core.notesRef refs/notes/x &&
|
||||||
|
test_must_fail git notes merge z 2>&1 >out &&
|
||||||
|
grep "Automatic notes merge failed" out &&
|
||||||
|
grep -v "A notes merge into refs/notes/x is already in-progress in" out
|
||||||
|
) &&
|
||||||
|
echo "ref: refs/notes/x" >expect &&
|
||||||
|
test_cmp .git/worktrees/worktree2/NOTES_MERGE_REF expect
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
Reference in New Issue
Block a user