Skip to content

Commit 3a3b9d8

Browse files
pcloudsgitster
authored andcommitted
refs: new ref types to make per-worktree refs visible to all worktrees
One of the problems with multiple worktree is accessing per-worktree refs of one worktree from another worktree. This was sort of solved by multiple ref store, where the code can open the ref store of another worktree and has access to the ref space of that worktree. The problem with this is reporting. "HEAD" in another ref space is also called "HEAD" like in the current ref space. In order to differentiate them, all the code must somehow carry the ref store around and print something like "HEAD from this ref store". But that is not feasible (or possible with a _lot_ of work). With the current design, we pass a reference around as a string (so called "refname"). Extending this design to pass a string _and_ a ref store is a nightmare, especially when handling extended SHA-1 syntax. So we do it another way. Instead of entering a separate ref space, we make refs from other worktrees available in the current ref space. So "HEAD" is always HEAD of the current worktree, but then we can have "worktrees/blah/HEAD" to denote HEAD from a worktree named "blah". This syntax coincidentally matches the underlying directory structure which makes implementation a bit easier. The main worktree has to be treated specially because well... it's special from the beginning. So HEAD from the main worktree is acccessible via the name "main-worktree/HEAD" instead of "worktrees/main/HEAD" because "main" could be just another secondary worktree. This patch also makes it possible to specify refs from one worktree in another one, e.g. git log worktrees/foo/HEAD Signed-off-by: Nguyễn Thái Ngọc Duy <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 8aff1a9 commit 3a3b9d8

File tree

7 files changed

+153
-4
lines changed

7 files changed

+153
-4
lines changed

Documentation/git-worktree.txt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,14 +208,27 @@ REFS
208208
----
209209
In multiple working trees, some refs may be shared between all working
210210
trees, some refs are local. One example is HEAD is different for all
211-
working trees. This section is about the sharing rules.
211+
working trees. This section is about the sharing rules and how to access
212+
refs of one working tree from another.
212213

213214
In general, all pseudo refs are per working tree and all refs starting
214215
with "refs/" are shared. Pseudo refs are ones like HEAD which are
215216
directly under GIT_DIR instead of inside GIT_DIR/refs. There are one
216217
exception to this: refs inside refs/bisect and refs/worktree is not
217218
shared.
218219

220+
Refs that are per working tree can still be accessed from another
221+
working tree via two special paths, main-worktree and worktrees. The
222+
former gives access to per-worktree refs of the main working tree,
223+
while the latter to all linked working trees.
224+
225+
For example, main-worktree/HEAD or main-worktree/refs/bisect/good
226+
resolve to the same value as the main working tree's HEAD and
227+
refs/bisect/good respectively. Similarly, worktrees/foo/HEAD or
228+
worktrees/bar/refs/bisect/bad are the same as
229+
GIT_COMMON_DIR/worktrees/foo/HEAD and
230+
GIT_COMMON_DIR/worktrees/bar/refs/bisect/bad.
231+
219232
To access refs, it's best not to look inside GIT_DIR directly. Instead
220233
use commands such as linkgit:git-revparse[1] or linkgit:git-update-ref[1]
221234
which will handle refs correctly.

refs.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,12 +641,33 @@ static int is_pseudoref_syntax(const char *refname)
641641
return 1;
642642
}
643643

644+
static int is_main_pseudoref_syntax(const char *refname)
645+
{
646+
return skip_prefix(refname, "main-worktree/", &refname) &&
647+
*refname &&
648+
is_pseudoref_syntax(refname);
649+
}
650+
651+
static int is_other_pseudoref_syntax(const char *refname)
652+
{
653+
if (!skip_prefix(refname, "worktrees/", &refname))
654+
return 0;
655+
refname = strchr(refname, '/');
656+
if (!refname || !refname[1])
657+
return 0;
658+
return is_pseudoref_syntax(refname + 1);
659+
}
660+
644661
enum ref_type ref_type(const char *refname)
645662
{
646663
if (is_per_worktree_ref(refname))
647664
return REF_TYPE_PER_WORKTREE;
648665
if (is_pseudoref_syntax(refname))
649666
return REF_TYPE_PSEUDOREF;
667+
if (is_main_pseudoref_syntax(refname))
668+
return REF_TYPE_MAIN_PSEUDOREF;
669+
if (is_other_pseudoref_syntax(refname))
670+
return REF_TYPE_OTHER_PSEUDOREF;
650671
return REF_TYPE_NORMAL;
651672
}
652673

refs.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -704,9 +704,11 @@ int parse_hide_refs_config(const char *var, const char *value, const char *);
704704
int ref_is_hidden(const char *, const char *);
705705

706706
enum ref_type {
707-
REF_TYPE_PER_WORKTREE,
708-
REF_TYPE_PSEUDOREF,
709-
REF_TYPE_NORMAL,
707+
REF_TYPE_PER_WORKTREE, /* refs inside refs/ but not shared */
708+
REF_TYPE_PSEUDOREF, /* refs outside refs/ in current worktree */
709+
REF_TYPE_MAIN_PSEUDOREF, /* pseudo refs from the main worktree */
710+
REF_TYPE_OTHER_PSEUDOREF, /* pseudo refs from other worktrees */
711+
REF_TYPE_NORMAL, /* normal/shared refs inside refs/ */
710712
};
711713

712714
enum ref_type ref_type(const char *refname);

refs/files-backend.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "../object.h"
1111
#include "../dir.h"
1212
#include "../chdir-notify.h"
13+
#include "worktree.h"
1314

1415
/*
1516
* 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,
149150
return refs;
150151
}
151152

153+
static void files_reflog_path_other_worktrees(struct files_ref_store *refs,
154+
struct strbuf *sb,
155+
const char *refname)
156+
{
157+
const char *real_ref;
158+
const char *worktree_name;
159+
int length;
160+
161+
if (parse_worktree_ref(refname, &worktree_name, &length, &real_ref))
162+
BUG("refname %s is not a other-worktree ref", refname);
163+
164+
if (worktree_name)
165+
strbuf_addf(sb, "%s/worktrees/%.*s/logs/%s", refs->gitcommondir,
166+
length, worktree_name, real_ref);
167+
else
168+
strbuf_addf(sb, "%s/logs/%s", refs->gitcommondir,
169+
real_ref);
170+
}
171+
152172
static void files_reflog_path(struct files_ref_store *refs,
153173
struct strbuf *sb,
154174
const char *refname)
@@ -158,6 +178,9 @@ static void files_reflog_path(struct files_ref_store *refs,
158178
case REF_TYPE_PSEUDOREF:
159179
strbuf_addf(sb, "%s/logs/%s", refs->gitdir, refname);
160180
break;
181+
case REF_TYPE_OTHER_PSEUDOREF:
182+
case REF_TYPE_MAIN_PSEUDOREF:
183+
return files_reflog_path_other_worktrees(refs, sb, refname);
161184
case REF_TYPE_NORMAL:
162185
strbuf_addf(sb, "%s/logs/%s", refs->gitcommondir, refname);
163186
break;
@@ -176,6 +199,11 @@ static void files_ref_path(struct files_ref_store *refs,
176199
case REF_TYPE_PSEUDOREF:
177200
strbuf_addf(sb, "%s/%s", refs->gitdir, refname);
178201
break;
202+
case REF_TYPE_MAIN_PSEUDOREF:
203+
if (!skip_prefix(refname, "main-worktree/", &refname))
204+
BUG("ref %s is not a main pseudoref", refname);
205+
/* fallthrough */
206+
case REF_TYPE_OTHER_PSEUDOREF:
179207
case REF_TYPE_NORMAL:
180208
strbuf_addf(sb, "%s/%s", refs->gitcommondir, refname);
181209
break;

t/t1415-worktree-refs.sh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,50 @@ test_expect_success 'refs/worktree are per-worktree' '
3030
( cd wt2 && test_cmp_rev worktree/foo wt2 )
3131
'
3232

33+
test_expect_success 'resolve main-worktree/HEAD' '
34+
test_cmp_rev main-worktree/HEAD initial &&
35+
( cd wt1 && test_cmp_rev main-worktree/HEAD initial ) &&
36+
( cd wt2 && test_cmp_rev main-worktree/HEAD initial )
37+
'
38+
39+
test_expect_success 'ambiguous main-worktree/HEAD' '
40+
mkdir -p .git/refs/heads/main-worktree &&
41+
test_when_finished rm -f .git/refs/heads/main-worktree/HEAD &&
42+
cp .git/HEAD .git/refs/heads/main-worktree/HEAD &&
43+
git rev-parse main-worktree/HEAD 2>warn &&
44+
grep "main-worktree/HEAD.*ambiguous" warn
45+
'
46+
47+
test_expect_success 'resolve worktrees/xx/HEAD' '
48+
test_cmp_rev worktrees/wt1/HEAD wt1 &&
49+
( cd wt1 && test_cmp_rev worktrees/wt1/HEAD wt1 ) &&
50+
( cd wt2 && test_cmp_rev worktrees/wt1/HEAD wt1 )
51+
'
52+
53+
test_expect_success 'ambiguous worktrees/xx/HEAD' '
54+
mkdir -p .git/refs/heads/worktrees/wt1 &&
55+
test_when_finished rm -f .git/refs/heads/worktrees/wt1/HEAD &&
56+
cp .git/HEAD .git/refs/heads/worktrees/wt1/HEAD &&
57+
git rev-parse worktrees/wt1/HEAD 2>warn &&
58+
grep "worktrees/wt1/HEAD.*ambiguous" warn
59+
'
60+
61+
test_expect_success 'reflog of main-worktree/HEAD' '
62+
git reflog HEAD | sed "s/HEAD/main-worktree\/HEAD/" >expected &&
63+
git reflog main-worktree/HEAD >actual &&
64+
test_cmp expected actual &&
65+
git -C wt1 reflog main-worktree/HEAD >actual.wt1 &&
66+
test_cmp expected actual.wt1
67+
'
68+
69+
test_expect_success 'reflog of worktrees/xx/HEAD' '
70+
git -C wt2 reflog HEAD | sed "s/HEAD/worktrees\/wt2\/HEAD/" >expected &&
71+
git reflog worktrees/wt2/HEAD >actual &&
72+
test_cmp expected actual &&
73+
git -C wt1 reflog worktrees/wt2/HEAD >actual.wt1 &&
74+
test_cmp expected actual.wt1 &&
75+
git -C wt2 reflog worktrees/wt2/HEAD >actual.wt2 &&
76+
test_cmp expected actual.wt2
77+
'
78+
3379
test_done

worktree.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,36 @@ int submodule_uses_worktrees(const char *path)
487487
return ret;
488488
}
489489

490+
int parse_worktree_ref(const char *worktree_ref, const char **name,
491+
int *name_length, const char **ref)
492+
{
493+
if (skip_prefix(worktree_ref, "main-worktree/", &worktree_ref)) {
494+
if (!*worktree_ref)
495+
return -1;
496+
if (name)
497+
*name = NULL;
498+
if (name_length)
499+
*name_length = 0;
500+
if (ref)
501+
*ref = worktree_ref;
502+
return 0;
503+
}
504+
if (skip_prefix(worktree_ref, "worktrees/", &worktree_ref)) {
505+
const char *slash = strchr(worktree_ref, '/');
506+
507+
if (!slash || slash == worktree_ref || !slash[1])
508+
return -1;
509+
if (name)
510+
*name = worktree_ref;
511+
if (name_length)
512+
*name_length = slash - worktree_ref;
513+
if (ref)
514+
*ref = slash + 1;
515+
return 0;
516+
}
517+
return -1;
518+
}
519+
490520
int other_head_refs(each_ref_fn fn, void *cb_data)
491521
{
492522
struct worktree **worktrees, **p;

worktree.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,13 @@ extern const char *worktree_git_path(const struct worktree *wt,
108108
const char *fmt, ...)
109109
__attribute__((format (printf, 2, 3)));
110110

111+
/*
112+
* Parse a worktree ref (i.e. with prefix main-worktree/ or
113+
* worktrees/) and return the position of the worktree's name and
114+
* length (or NULL and zero if it's main worktree), and ref.
115+
*
116+
* All name, name_length and ref arguments could be NULL.
117+
*/
118+
int parse_worktree_ref(const char *worktree_ref, const char **name,
119+
int *name_length, const char **ref);
111120
#endif

0 commit comments

Comments
 (0)