mirror of
https://github.com/git/git.git
synced 2024-11-23 18:05:29 +08:00
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:
commit
32561f5dd3
46
branch.c
46
branch.c
@ -311,21 +311,23 @@ void remove_branch_state(void)
|
||||
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 path = STRBUF_INIT;
|
||||
struct strbuf gitdir = STRBUF_INIT;
|
||||
char *existing = NULL;
|
||||
|
||||
/*
|
||||
* $GIT_COMMON_DIR/HEAD is practically outside
|
||||
* $GIT_DIR so resolve_ref_unsafe() won't work (it
|
||||
* uses git_path). Parse the ref ourselves.
|
||||
* $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside
|
||||
* $GIT_DIR so resolve_ref_unsafe() won't work (it uses
|
||||
* git_path). Parse the ref ourselves.
|
||||
*/
|
||||
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
|
||||
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 (!starts_with(sb.buf, "refs/") ||
|
||||
@ -347,33 +349,53 @@ static void check_linked_checkout(const char *branch, const char *id)
|
||||
strbuf_rtrim(&gitdir);
|
||||
} else
|
||||
strbuf_addstr(&gitdir, get_git_common_dir());
|
||||
skip_prefix(branch, "refs/heads/", &branch);
|
||||
strbuf_strip_suffix(&gitdir, ".git");
|
||||
die(_("'%s' is already checked out at '%s'"), branch, gitdir.buf);
|
||||
|
||||
existing = strbuf_detach(&gitdir, NULL);
|
||||
done:
|
||||
strbuf_release(&path);
|
||||
strbuf_release(&sb);
|
||||
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;
|
||||
DIR *dir;
|
||||
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());
|
||||
dir = opendir(path.buf);
|
||||
strbuf_release(&path);
|
||||
if (!dir)
|
||||
return;
|
||||
return NULL;
|
||||
|
||||
while ((d = readdir(dir)) != NULL) {
|
||||
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
|
||||
continue;
|
||||
check_linked_checkout(branch, d->d_name);
|
||||
existing = find_linked_symref(symref, target, d->d_name);
|
||||
if (existing)
|
||||
goto done;
|
||||
}
|
||||
done:
|
||||
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);
|
||||
|
||||
/*
|
||||
* 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
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "string-list.h"
|
||||
#include "notes-merge.h"
|
||||
#include "notes-utils.h"
|
||||
#include "branch.h"
|
||||
|
||||
static const char * const git_notes_usage[] = {
|
||||
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,
|
||||
0, UPDATE_REFS_DIE_ON_ERR);
|
||||
else { /* Merge has unresolved conflicts */
|
||||
char *existing;
|
||||
/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
|
||||
update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL,
|
||||
0, UPDATE_REFS_DIE_ON_ERR);
|
||||
/* 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))
|
||||
die("Failed to store link to current notes ref (%s)",
|
||||
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
|
Loading…
Reference in New Issue
Block a user