diff options
-rw-r--r-- | Documentation/git-worktree.txt | 15 | ||||
-rw-r--r-- | refs.c | 21 | ||||
-rw-r--r-- | refs.h | 8 | ||||
-rw-r--r-- | refs/files-backend.c | 28 | ||||
-rwxr-xr-x | t/t1415-worktree-refs.sh | 46 | ||||
-rw-r--r-- | worktree.c | 30 | ||||
-rw-r--r-- | worktree.h | 9 |
7 files changed, 153 insertions, 4 deletions
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index a50fbf8094..9117e4fb50 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -208,7 +208,8 @@ REFS ---- In multiple working trees, some refs may be shared between all working trees, some refs are local. One example is HEAD is different for all -working trees. This section is about the sharing rules. +working trees. This section is about the sharing rules and how to access +refs of one working tree from another. In general, all pseudo refs are per working tree and all refs starting with "refs/" are shared. Pseudo refs are ones like HEAD which are @@ -216,6 +217,18 @@ directly under GIT_DIR instead of inside GIT_DIR/refs. There are one exception to this: refs inside refs/bisect and refs/worktree is not shared. +Refs that are per working tree can still be accessed from another +working tree via two special paths, main-worktree and worktrees. The +former gives access to per-worktree refs of the main working tree, +while the latter to all linked working trees. + +For example, main-worktree/HEAD or main-worktree/refs/bisect/good +resolve to the same value as the main working tree's HEAD and +refs/bisect/good respectively. Similarly, worktrees/foo/HEAD or +worktrees/bar/refs/bisect/bad are the same as +GIT_COMMON_DIR/worktrees/foo/HEAD and +GIT_COMMON_DIR/worktrees/bar/refs/bisect/bad. + To access refs, it's best not to look inside GIT_DIR directly. Instead use commands such as linkgit:git-revparse[1] or linkgit:git-update-ref[1] which will handle refs correctly. @@ -641,12 +641,33 @@ static int is_pseudoref_syntax(const char *refname) return 1; } +static int is_main_pseudoref_syntax(const char *refname) +{ + return skip_prefix(refname, "main-worktree/", &refname) && + *refname && + is_pseudoref_syntax(refname); +} + +static int is_other_pseudoref_syntax(const char *refname) +{ + if (!skip_prefix(refname, "worktrees/", &refname)) + return 0; + refname = strchr(refname, '/'); + if (!refname || !refname[1]) + return 0; + return is_pseudoref_syntax(refname + 1); +} + enum ref_type ref_type(const char *refname) { if (is_per_worktree_ref(refname)) return REF_TYPE_PER_WORKTREE; if (is_pseudoref_syntax(refname)) return REF_TYPE_PSEUDOREF; + if (is_main_pseudoref_syntax(refname)) + return REF_TYPE_MAIN_PSEUDOREF; + if (is_other_pseudoref_syntax(refname)) + return REF_TYPE_OTHER_PSEUDOREF; return REF_TYPE_NORMAL; } @@ -704,9 +704,11 @@ int parse_hide_refs_config(const char *var, const char *value, const char *); int ref_is_hidden(const char *, const char *); enum ref_type { - REF_TYPE_PER_WORKTREE, - REF_TYPE_PSEUDOREF, - REF_TYPE_NORMAL, + REF_TYPE_PER_WORKTREE, /* refs inside refs/ but not shared */ + REF_TYPE_PSEUDOREF, /* refs outside refs/ in current worktree */ + REF_TYPE_MAIN_PSEUDOREF, /* pseudo refs from the main worktree */ + REF_TYPE_OTHER_PSEUDOREF, /* pseudo refs from other worktrees */ + REF_TYPE_NORMAL, /* normal/shared refs inside refs/ */ }; enum ref_type ref_type(const char *refname); diff --git a/refs/files-backend.c b/refs/files-backend.c index 2dd77f9485..9183875dad 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -10,6 +10,7 @@ #include "../object.h" #include "../dir.h" #include "../chdir-notify.h" +#include "worktree.h" /* * This backend uses the following flags in `ref_update::flags` for @@ -149,6 +150,25 @@ static struct files_ref_store *files_downcast(struct ref_store *ref_store, return refs; } +static void files_reflog_path_other_worktrees(struct files_ref_store *refs, + struct strbuf *sb, + const char *refname) +{ + const char *real_ref; + const char *worktree_name; + int length; + + if (parse_worktree_ref(refname, &worktree_name, &length, &real_ref)) + BUG("refname %s is not a other-worktree ref", refname); + + if (worktree_name) + strbuf_addf(sb, "%s/worktrees/%.*s/logs/%s", refs->gitcommondir, + length, worktree_name, real_ref); + else + strbuf_addf(sb, "%s/logs/%s", refs->gitcommondir, + real_ref); +} + static void files_reflog_path(struct files_ref_store *refs, struct strbuf *sb, const char *refname) @@ -158,6 +178,9 @@ static void files_reflog_path(struct files_ref_store *refs, case REF_TYPE_PSEUDOREF: strbuf_addf(sb, "%s/logs/%s", refs->gitdir, refname); break; + case REF_TYPE_OTHER_PSEUDOREF: + case REF_TYPE_MAIN_PSEUDOREF: + return files_reflog_path_other_worktrees(refs, sb, refname); case REF_TYPE_NORMAL: strbuf_addf(sb, "%s/logs/%s", refs->gitcommondir, refname); break; @@ -176,6 +199,11 @@ static void files_ref_path(struct files_ref_store *refs, case REF_TYPE_PSEUDOREF: strbuf_addf(sb, "%s/%s", refs->gitdir, refname); break; + case REF_TYPE_MAIN_PSEUDOREF: + if (!skip_prefix(refname, "main-worktree/", &refname)) + BUG("ref %s is not a main pseudoref", refname); + /* fallthrough */ + case REF_TYPE_OTHER_PSEUDOREF: case REF_TYPE_NORMAL: strbuf_addf(sb, "%s/%s", refs->gitcommondir, refname); break; diff --git a/t/t1415-worktree-refs.sh b/t/t1415-worktree-refs.sh index 16c91bef57..b664e51250 100755 --- a/t/t1415-worktree-refs.sh +++ b/t/t1415-worktree-refs.sh @@ -30,4 +30,50 @@ test_expect_success 'refs/worktree are per-worktree' ' ( cd wt2 && test_cmp_rev worktree/foo wt2 ) ' +test_expect_success 'resolve main-worktree/HEAD' ' + test_cmp_rev main-worktree/HEAD initial && + ( cd wt1 && test_cmp_rev main-worktree/HEAD initial ) && + ( cd wt2 && test_cmp_rev main-worktree/HEAD initial ) +' + +test_expect_success 'ambiguous main-worktree/HEAD' ' + mkdir -p .git/refs/heads/main-worktree && + test_when_finished rm -f .git/refs/heads/main-worktree/HEAD && + cp .git/HEAD .git/refs/heads/main-worktree/HEAD && + git rev-parse main-worktree/HEAD 2>warn && + grep "main-worktree/HEAD.*ambiguous" warn +' + +test_expect_success 'resolve worktrees/xx/HEAD' ' + test_cmp_rev worktrees/wt1/HEAD wt1 && + ( cd wt1 && test_cmp_rev worktrees/wt1/HEAD wt1 ) && + ( cd wt2 && test_cmp_rev worktrees/wt1/HEAD wt1 ) +' + +test_expect_success 'ambiguous worktrees/xx/HEAD' ' + mkdir -p .git/refs/heads/worktrees/wt1 && + test_when_finished rm -f .git/refs/heads/worktrees/wt1/HEAD && + cp .git/HEAD .git/refs/heads/worktrees/wt1/HEAD && + git rev-parse worktrees/wt1/HEAD 2>warn && + grep "worktrees/wt1/HEAD.*ambiguous" warn +' + +test_expect_success 'reflog of main-worktree/HEAD' ' + git reflog HEAD | sed "s/HEAD/main-worktree\/HEAD/" >expected && + git reflog main-worktree/HEAD >actual && + test_cmp expected actual && + git -C wt1 reflog main-worktree/HEAD >actual.wt1 && + test_cmp expected actual.wt1 +' + +test_expect_success 'reflog of worktrees/xx/HEAD' ' + git -C wt2 reflog HEAD | sed "s/HEAD/worktrees\/wt2\/HEAD/" >expected && + git reflog worktrees/wt2/HEAD >actual && + test_cmp expected actual && + git -C wt1 reflog worktrees/wt2/HEAD >actual.wt1 && + test_cmp expected actual.wt1 && + git -C wt2 reflog worktrees/wt2/HEAD >actual.wt2 && + test_cmp expected actual.wt2 +' + test_done diff --git a/worktree.c b/worktree.c index b0d0b5426d..4193496f2a 100644 --- a/worktree.c +++ b/worktree.c @@ -487,6 +487,36 @@ int submodule_uses_worktrees(const char *path) return ret; } +int parse_worktree_ref(const char *worktree_ref, const char **name, + int *name_length, const char **ref) +{ + if (skip_prefix(worktree_ref, "main-worktree/", &worktree_ref)) { + if (!*worktree_ref) + return -1; + if (name) + *name = NULL; + if (name_length) + *name_length = 0; + if (ref) + *ref = worktree_ref; + return 0; + } + if (skip_prefix(worktree_ref, "worktrees/", &worktree_ref)) { + const char *slash = strchr(worktree_ref, '/'); + + if (!slash || slash == worktree_ref || !slash[1]) + return -1; + if (name) + *name = worktree_ref; + if (name_length) + *name_length = slash - worktree_ref; + if (ref) + *ref = slash + 1; + return 0; + } + return -1; +} + int other_head_refs(each_ref_fn fn, void *cb_data) { struct worktree **worktrees, **p; diff --git a/worktree.h b/worktree.h index df3fc30f73..440bb219dd 100644 --- a/worktree.h +++ b/worktree.h @@ -108,4 +108,13 @@ extern const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...) __attribute__((format (printf, 2, 3))); +/* + * Parse a worktree ref (i.e. with prefix main-worktree/ or + * worktrees/) and return the position of the worktree's name and + * length (or NULL and zero if it's main worktree), and ref. + * + * All name, name_length and ref arguments could be NULL. + */ +int parse_worktree_ref(const char *worktree_ref, const char **name, + int *name_length, const char **ref); #endif |