v3 changes
- fix incorrect ref reporting (it reported main/HEAD instead of
main-worktree/HEAD)
- other document typos
- fix strbuf_worktree_ref() producing refs that cannot be handled by
files-backend.c, e.g. worktrees/foo/refs/heads/master (one day the
ref store will, but not now)
- make sure the "HEAD" special case in reflog expire still works
with main-worktree/HEAD and worktrees/xxx/HEAD
- tests added for reflog
Elijah Newren (1):
fsck: Move fsck_head_link() to get_default_heads() to avoid some
globals
Nguyễn Thái Ngọc Duy (7):
refs.c: indent with tabs, not spaces
Add a place for (not) sharing stuff between worktrees
refs: new ref types to make per-worktree refs visible to all worktrees
revision.c: correct a parameter name
revision.c: better error reporting on ref from different worktrees
fsck: check HEAD and reflog from other worktrees
reflog expire: cover reflog from all worktrees
Documentation/git-reflog.txt | 7 ++-
Documentation/git-worktree.txt | 32 ++++++++++-
Documentation/gitrepository-layout.txt | 11 +++-
builtin/fsck.c | 68 +++++++++++++++-------
builtin/reflog.c | 46 +++++++++++++--
path.c | 2 +
refs.c | 24 +++++++-
refs.h | 8 ++-
refs/files-backend.c | 42 +++++++++++++-
revision.c | 22 ++++---
t/t0060-path-utils.sh | 2 +
t/t1410-reflog.sh | 15 +++++
t/t1415-worktree-refs.sh | 79 ++++++++++++++++++++++++++
t/t1450-fsck.sh | 35 ++++++++++++
worktree.c | 79 +++++++++++++++++++++++++-
worktree.h | 24 ++++++++
16 files changed, 449 insertions(+), 47 deletions(-)
create mode 100755 t/t1415-worktree-refs.sh
Range-diff against v2:
1: 328a4d1263 ! 1: fff4cfcc93 refs: new ref types to make per-worktree refs
visible to all worktrees
@@ -24,7 +24,7 @@
"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
+ 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
@@ -53,9 +53,9 @@
shared.
+Refs that are per working tree can still be accessed from another
-+working tree via two special paths main-worktree and worktrees. The
++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 former to all linked working trees.
++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
@@ -128,6 +128,14 @@
diff --git a/refs/files-backend.c b/refs/files-backend.c
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
+@@
+ #include "../object.h"
+ #include "../dir.h"
+ #include "../chdir-notify.h"
++#include "worktree.h"
+
+ /*
+ * This backend uses the following flags in `ref_update::flags` for
@@
return refs;
}
@@ -137,16 +145,18 @@
+ const char *refname)
+{
+ const char *real_ref;
++ const char *worktree_name;
++ int length;
+
-+ if (!skip_prefix(refname, "worktrees/", &real_ref))
-+ BUG("refname %s is not a other-worktree ref", refname);
-+ real_ref = strchr(real_ref, '/');
-+ if (!real_ref)
++ if (parse_worktree_ref(refname, &worktree_name, &length, &real_ref))
+ BUG("refname %s is not a other-worktree ref", refname);
-+ real_ref++;
+
-+ strbuf_addf(sb, "%s/%.*slogs/%s", refs->gitcommondir,
-+ (int)(real_ref - refname), refname, real_ref);
++ 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,
@@ -157,11 +167,8 @@
strbuf_addf(sb, "%s/logs/%s", refs->gitdir, refname);
break;
+ case REF_TYPE_OTHER_PSEUDOREF:
-+ return files_reflog_path_other_worktrees(refs, sb, refname);
+ case REF_TYPE_MAIN_PSEUDOREF:
-+ if (!skip_prefix(refname, "main-worktree/", &refname))
-+ BUG("ref %s is not a main pseudoref", refname);
-+ /* passthru */
++ return files_reflog_path_other_worktrees(refs, sb, refname);
case REF_TYPE_NORMAL:
strbuf_addf(sb, "%s/logs/%s", refs->gitcommondir, refname);
break;
@@ -172,7 +179,7 @@
+ case REF_TYPE_MAIN_PSEUDOREF:
+ if (!skip_prefix(refname, "main-worktree/", &refname))
+ BUG("ref %s is not a main pseudoref", refname);
-+ /* passthru */
++ /* fallthrough */
+ case REF_TYPE_OTHER_PSEUDOREF:
case REF_TYPE_NORMAL:
strbuf_addf(sb, "%s/%s", refs->gitcommondir, refname);
@@ -193,9 +200,9 @@
+
+test_expect_success 'ambiguous main-worktree/HEAD' '
+ mkdir -p .git/refs/heads/main-worktree &&
-+ test_when_finished rm .git/refs/heads/main-worktree/HEAD &&
++ 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 >/dev/null &&
++ git rev-parse main-worktree/HEAD 2>warn &&
+ grep "main-worktree/HEAD.*ambiguous" warn
+'
+
@@ -207,9 +214,9 @@
+
+test_expect_success 'ambiguous worktrees/xx/HEAD' '
+ mkdir -p .git/refs/heads/worktrees/wt1 &&
-+ test_when_finished rm .git/refs/heads/worktrees/wt1/HEAD &&
++ 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 >/dev/null &&
++ git rev-parse worktrees/wt1/HEAD 2>warn &&
+ grep "worktrees/wt1/HEAD.*ambiguous" warn
+'
+
@@ -232,3 +239,62 @@
+'
+
test_done
+
+ diff --git a/worktree.c b/worktree.c
+ --- a/worktree.c
+ +++ b/worktree.c
+@@
+ 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
+ --- a/worktree.h
+ +++ b/worktree.h
+@@
+ 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
2: ffdd30f7fc = 2: e936a0af1e revision.c: correct a parameter name
3: 6809bab191 ! 3: 382c645d73 revision.c: better error reporting on ref from
different worktrees
@@ -87,18 +87,35 @@
--- a/worktree.c
+++ b/worktree.c
@@
- return ret;
+ return -1;
}
+void strbuf_worktree_ref(const struct worktree *wt,
+ struct strbuf *sb,
+ const char *refname)
+{
-+ if (wt && !wt->is_current) {
-+ if (is_main_worktree(wt))
-+ strbuf_addstr(sb, "main/");
-+ else
-+ strbuf_addf(sb, "worktrees/%s/", wt->id);
++ switch (ref_type(refname)) {
++ case REF_TYPE_PSEUDOREF:
++ case REF_TYPE_PER_WORKTREE:
++ if (wt && !wt->is_current) {
++ if (is_main_worktree(wt))
++ strbuf_addstr(sb, "main-worktree/");
++ else
++ strbuf_addf(sb, "worktrees/%s/", wt->id);
++ }
++ break;
++
++ case REF_TYPE_MAIN_PSEUDOREF:
++ case REF_TYPE_OTHER_PSEUDOREF:
++ break;
++
++ case REF_TYPE_NORMAL:
++ /*
++ * For shared refs, don't prefix worktrees/ or
++ * main-worktree/. It's not necessary and
++ * files-backend.c can't handle it anyway.
++ */
++ break;
+ }
+ strbuf_addstr(sb, refname);
+}
@@ -141,9 +158,10 @@
--- a/worktree.h
+++ b/worktree.h
@@
- const char *fmt, ...)
- __attribute__((format (printf, 2, 3)));
-
+ */
+ int parse_worktree_ref(const char *worktree_ref, const char **name,
+ int *name_length, const char **ref);
++
+/*
+ * Return a refname suitable for access from the current ref store.
+ */
@@ -153,7 +171,7 @@
+
+/*
+ * Return a refname suitable for access from the current ref
-+ * store. The result may be destroyed at the next call.
++ * store. The result will be destroyed at the next call.
+ */
+const char *worktree_ref(const struct worktree *wt,
+ const char *refname);
4: 2e13fa8361 = 4: e2b99ef955 fsck: Move fsck_head_link() to
get_default_heads() to avoid some globals
5: 65f1547df3 ! 5: 8fbff370c3 fsck: check HEAD and reflog from other worktrees
@@ -136,7 +136,7 @@
+ echo $ZERO_OID >.git/HEAD &&
+ # avoid corrupt/broken HEAD from interfering with repo discovery
+ test_must_fail git -C wt fsck 2>out &&
-+ grep "main/HEAD: detached HEAD points" out
++ grep "main-worktree/HEAD: detached HEAD points" out
+'
+
+test_expect_success 'other worktree HEAD link pointing at a funny object'
'
6: 86326b44b5 ! 6: 2cd501f2ce reflog expire: cover reflog from all worktrees
@@ -48,12 +48,48 @@
};
/* Remember to update object flag allocation in object.h */
+@@
+ return 0;
+ }
+
++static int is_head(const char *refname)
++{
++ switch (ref_type(refname)) {
++ case REF_TYPE_OTHER_PSEUDOREF:
++ case REF_TYPE_MAIN_PSEUDOREF:
++ if (parse_worktree_ref(refname, NULL, NULL, &refname))
++ BUG("not a worktree ref: %s", refname);
++ break;
++ default:
++ break;
++ }
++ return !strcmp(refname, "HEAD");
++}
++
+ static void reflog_expiry_prepare(const char *refname,
+ const struct object_id *oid,
+ void *cb_data)
+ {
+ struct expire_reflog_policy_cb *cb = cb_data;
+
+- if (!cb->cmd.expire_unreachable || !strcmp(refname, "HEAD")) {
++ if (!cb->cmd.expire_unreachable || is_head(refname)) {
+ cb->tip_commit = NULL;
+ cb->unreachable_expire_kind = UE_HEAD;
+ } else {
@@
{
struct collected_reflog *e;
struct collect_reflog_cb *cb = cb_data;
+ struct strbuf newref = STRBUF_INIT;
+
++ /*
++ * Avoid collecting the same shared ref multiple times because
++ * they are available via all worktrees.
++ */
++ if (!cb->wt->is_current && ref_type(ref) == REF_TYPE_NORMAL)
++ return 0;
++
+ strbuf_worktree_ref(cb->wt, &newref, ref);
+ FLEX_ALLOC_STR(e, reflog, newref.buf);
+ strbuf_release(&newref);
@@ -94,9 +130,34 @@
+ if (!all_worktrees && !(*p)->is_current)
+ continue;
+ collected.wt = *p;
-+ for_each_reflog(collect_reflog, &collected);
++ refs_for_each_reflog(get_worktree_ref_store(*p),
++ collect_reflog, &collected);
+ }
+ free_worktrees(worktrees);
for (i = 0; i < collected.nr; i++) {
struct collected_reflog *e = collected.e[i];
set_reflog_expiry_param(&cb.cmd, explicit_expiry,
e->reflog);
+
+ diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh
+ --- a/t/t1410-reflog.sh
+ +++ b/t/t1410-reflog.sh
+@@
+ )
+ '
+
++test_expect_success 'expire with multiple worktrees' '
++ git init main-wt &&
++ (
++ cd main-wt &&
++ test_tick &&
++ test_commit foo &&
++ git worktree add link-wt &&
++ test_tick &&
++ test_commit -C link-wt foobar &&
++ test_tick &&
++ git reflog expire --verbose --all --expire=$test_tick &&
++ test_must_be_empty .git/worktrees/link-wt/logs/HEAD
++ )
++'
++
+ test_done
--
2.19.1.647.g708186aaf9