summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/git-reflog.txt7
-rw-r--r--Documentation/git-worktree.txt32
-rw-r--r--Documentation/gitrepository-layout.txt11
-rw-r--r--builtin/fsck.c68
-rw-r--r--builtin/reflog.c46
-rw-r--r--path.c2
-rw-r--r--refs.c24
-rw-r--r--refs.h8
-rw-r--r--refs/files-backend.c42
-rw-r--r--revision.c22
-rwxr-xr-xt/t0060-path-utils.sh2
-rwxr-xr-xt/t1410-reflog.sh15
-rwxr-xr-xt/t1415-worktree-refs.sh79
-rwxr-xr-xt/t1450-fsck.sh35
-rw-r--r--worktree.c79
-rw-r--r--worktree.h24
16 files changed, 449 insertions, 47 deletions
diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt
index 472a6808cd..ff487ff77d 100644
--- a/Documentation/git-reflog.txt
+++ b/Documentation/git-reflog.txt
@@ -20,7 +20,7 @@ depending on the subcommand:
'git reflog' ['show'] [log-options] [<ref>]
'git reflog expire' [--expire=<time>] [--expire-unreachable=<time>]
[--rewrite] [--updateref] [--stale-fix]
- [--dry-run | -n] [--verbose] [--all | <refs>...]
+ [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]
'git reflog delete' [--rewrite] [--updateref]
[--dry-run | -n] [--verbose] ref@\{specifier\}...
'git reflog exists' <ref>
@@ -72,6 +72,11 @@ Options for `expire`
--all::
Process the reflogs of all references.
+--single-worktree::
+ By default when `--all` is specified, reflogs from all working
+ trees are processed. This option limits the processing to reflogs
+ from the current working tree only.
+
--expire=<time>::
Prune entries older than the specified time. If this option is
not specified, the expiration time is taken from the
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 5e986ce8aa..cb86318f3e 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -204,6 +204,35 @@ working trees, it can be used to identify worktrees. For example if
you only have two working trees, at "/abc/def/ghi" and "/abc/def/ggg",
then "ghi" or "def/ghi" is enough to point to the former working tree.
+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 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
+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-rev-parse[1] or linkgit:git-update-ref[1]
+which will handle refs correctly.
+
CONFIGURATION FILE
------------------
By default, the repository "config" file is shared across all working
@@ -258,7 +287,8 @@ linked working tree `git rev-parse --git-path HEAD` returns
`/path/other/test-next/.git/HEAD` or `/path/main/.git/HEAD`) while `git
rev-parse --git-path refs/heads/master` uses
$GIT_COMMON_DIR and returns `/path/main/.git/refs/heads/master`,
-since refs are shared across all working trees.
+since refs are shared across all working trees, except refs/bisect and
+refs/worktree.
See linkgit:gitrepository-layout[5] for more information. The rule of
thumb is do not make any assumption about whether a path belongs to
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index 36fcca8087..d501af9d77 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -95,8 +95,10 @@ refs::
References are stored in subdirectories of this
directory. The 'git prune' command knows to preserve
objects reachable from refs found in this directory and
- its subdirectories. This directory is ignored if $GIT_COMMON_DIR
- is set and "$GIT_COMMON_DIR/refs" will be used instead.
+ its subdirectories.
+ This directory is ignored (except refs/bisect and
+ refs/worktree) if $GIT_COMMON_DIR is set and
+ "$GIT_COMMON_DIR/refs" will be used instead.
refs/heads/`name`::
records tip-of-the-tree commit objects of branch `name`
@@ -170,6 +172,11 @@ hooks::
each hook. This directory is ignored if $GIT_COMMON_DIR is set
and "$GIT_COMMON_DIR/hooks" will be used instead.
+common::
+ When multiple working trees are used, most of files in
+ $GIT_DIR are per-worktree with a few known exceptions. All
+ files under 'common' however will be shared between all
+ working trees.
index::
The current index file for the repository. It is
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 06eb421720..3c3e0f06e7 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -19,6 +19,7 @@
#include "packfile.h"
#include "object-store.h"
#include "run-command.h"
+#include "worktree.h"
#define REACHABLE 0x0001
#define SEEN 0x0002
@@ -36,8 +37,6 @@ static int check_strict;
static int keep_cache_objects;
static struct fsck_options fsck_walk_options = FSCK_OPTIONS_DEFAULT;
static struct fsck_options fsck_obj_options = FSCK_OPTIONS_DEFAULT;
-static struct object_id head_oid;
-static const char *head_points_at;
static int errors_found;
static int write_lost_and_found;
static int verbose;
@@ -446,7 +445,11 @@ static int fsck_handle_reflog_ent(struct object_id *ooid, struct object_id *noid
static int fsck_handle_reflog(const char *logname, const struct object_id *oid,
int flag, void *cb_data)
{
- for_each_reflog_ent(logname, fsck_handle_reflog_ent, (void *)logname);
+ struct strbuf refname = STRBUF_INIT;
+
+ strbuf_worktree_ref(cb_data, &refname, logname);
+ for_each_reflog_ent(refname.buf, fsck_handle_reflog_ent, refname.buf);
+ strbuf_release(&refname);
return 0;
}
@@ -484,13 +487,34 @@ static int fsck_handle_ref(const char *refname, const struct object_id *oid,
return 0;
}
+static int fsck_head_link(const char *head_ref_name,
+ const char **head_points_at,
+ struct object_id *head_oid);
+
static void get_default_heads(void)
{
- if (head_points_at && !is_null_oid(&head_oid))
- fsck_handle_ref("HEAD", &head_oid, 0, NULL);
+ struct worktree **worktrees, **p;
+ const char *head_points_at;
+ struct object_id head_oid;
+
for_each_rawref(fsck_handle_ref, NULL);
- if (include_reflogs)
- for_each_reflog(fsck_handle_reflog, NULL);
+
+ worktrees = get_worktrees(0);
+ for (p = worktrees; *p; p++) {
+ struct worktree *wt = *p;
+ struct strbuf ref = STRBUF_INIT;
+
+ strbuf_worktree_ref(wt, &ref, "HEAD");
+ fsck_head_link(ref.buf, &head_points_at, &head_oid);
+ if (head_points_at && !is_null_oid(&head_oid))
+ fsck_handle_ref(ref.buf, &head_oid, 0, NULL);
+ strbuf_release(&ref);
+
+ if (include_reflogs)
+ refs_for_each_reflog(get_worktree_ref_store(wt),
+ fsck_handle_reflog, wt);
+ }
+ free_worktrees(worktrees);
/*
* Not having any default heads isn't really fatal, but
@@ -579,33 +603,36 @@ static void fsck_object_dir(const char *path)
stop_progress(&progress);
}
-static int fsck_head_link(void)
+static int fsck_head_link(const char *head_ref_name,
+ const char **head_points_at,
+ struct object_id *head_oid)
{
int null_is_error = 0;
if (verbose)
- fprintf(stderr, "Checking HEAD link\n");
+ fprintf(stderr, "Checking %s link\n", head_ref_name);
- head_points_at = resolve_ref_unsafe("HEAD", 0, &head_oid, NULL);
- if (!head_points_at) {
+ *head_points_at = resolve_ref_unsafe(head_ref_name, 0, head_oid, NULL);
+ if (!*head_points_at) {
errors_found |= ERROR_REFS;
- return error("Invalid HEAD");
+ return error("Invalid %s", head_ref_name);
}
- if (!strcmp(head_points_at, "HEAD"))
+ if (!strcmp(*head_points_at, head_ref_name))
/* detached HEAD */
null_is_error = 1;
- else if (!starts_with(head_points_at, "refs/heads/")) {
+ else if (!starts_with(*head_points_at, "refs/heads/")) {
errors_found |= ERROR_REFS;
- return error("HEAD points to something strange (%s)",
- head_points_at);
+ return error("%s points to something strange (%s)",
+ head_ref_name, *head_points_at);
}
- if (is_null_oid(&head_oid)) {
+ if (is_null_oid(head_oid)) {
if (null_is_error) {
errors_found |= ERROR_REFS;
- return error("HEAD: detached HEAD points at nothing");
+ return error("%s: detached HEAD points at nothing",
+ head_ref_name);
}
- fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n",
- head_points_at + 11);
+ fprintf(stderr, "notice: %s points to an unborn branch (%s)\n",
+ head_ref_name, *head_points_at + 11);
}
return 0;
}
@@ -720,7 +747,6 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
git_config(fsck_config, NULL);
- fsck_head_link();
if (connectivity_only) {
for_each_loose_object(mark_loose_for_connectivity, NULL, 0);
for_each_packed_object(mark_packed_for_connectivity, NULL, 0);
diff --git a/builtin/reflog.c b/builtin/reflog.c
index b5941c1ff3..7a85e4b164 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -10,6 +10,7 @@
#include "diff.h"
#include "revision.h"
#include "reachable.h"
+#include "worktree.h"
/* NEEDSWORK: switch to using parse_options */
static const char reflog_expire_usage[] =
@@ -52,6 +53,7 @@ struct collect_reflog_cb {
struct collected_reflog **e;
int alloc;
int nr;
+ struct worktree *wt;
};
/* Remember to update object flag allocation in object.h */
@@ -330,13 +332,27 @@ static int push_tip_to_list(const char *refname, const struct object_id *oid,
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 {
@@ -388,8 +404,19 @@ static int collect_reflog(const char *ref, const struct object_id *oid, int unus
{
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);
- FLEX_ALLOC_STR(e, reflog, ref);
oidcpy(&e->oid, oid);
ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc);
cb->e[cb->nr++] = e;
@@ -512,7 +539,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
{
struct expire_reflog_policy_cb cb;
timestamp_t now = time(NULL);
- int i, status, do_all;
+ int i, status, do_all, all_worktrees = 1;
int explicit_expiry = 0;
unsigned int flags = 0;
@@ -549,6 +576,8 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
flags |= EXPIRE_REFLOGS_UPDATE_REF;
else if (!strcmp(arg, "--all"))
do_all = 1;
+ else if (!strcmp(arg, "--single-worktree"))
+ all_worktrees = 0;
else if (!strcmp(arg, "--verbose"))
flags |= EXPIRE_REFLOGS_VERBOSE;
else if (!strcmp(arg, "--")) {
@@ -577,10 +606,19 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
if (do_all) {
struct collect_reflog_cb collected;
+ struct worktree **worktrees, **p;
int i;
memset(&collected, 0, sizeof(collected));
- for_each_reflog(collect_reflog, &collected);
+ worktrees = get_worktrees(0);
+ for (p = worktrees; *p; p++) {
+ if (!all_worktrees && !(*p)->is_current)
+ continue;
+ collected.wt = *p;
+ 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/path.c b/path.c
index ba06ec5b2d..dc3294c71e 100644
--- a/path.c
+++ b/path.c
@@ -108,6 +108,7 @@ struct common_dir {
static struct common_dir common_list[] = {
{ 0, 1, 0, "branches" },
+ { 0, 1, 0, "common" },
{ 0, 1, 0, "hooks" },
{ 0, 1, 0, "info" },
{ 0, 0, 1, "info/sparse-checkout" },
@@ -118,6 +119,7 @@ static struct common_dir common_list[] = {
{ 0, 1, 0, "objects" },
{ 0, 1, 0, "refs" },
{ 0, 1, 1, "refs/bisect" },
+ { 0, 1, 1, "refs/worktree" },
{ 0, 1, 0, "remotes" },
{ 0, 1, 0, "worktrees" },
{ 0, 1, 0, "rr-cache" },
diff --git a/refs.c b/refs.c
index bbcac921b6..17e4307f31 100644
--- a/refs.c
+++ b/refs.c
@@ -624,6 +624,7 @@ int dwim_log(const char *str, int len, struct object_id *oid, char **log)
static int is_per_worktree_ref(const char *refname)
{
return !strcmp(refname, "HEAD") ||
+ starts_with(refname, "refs/worktree/") ||
starts_with(refname, "refs/bisect/") ||
starts_with(refname, "refs/rewritten/");
}
@@ -640,13 +641,34 @@ 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;
- return REF_TYPE_NORMAL;
+ 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;
}
long get_files_ref_lock_timeout_ms(void)
diff --git a/refs.h b/refs.h
index 6cc0397679..308fa1f03b 100644
--- a/refs.h
+++ b/refs.h
@@ -714,9 +714,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 16ef9325e0..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;
@@ -269,9 +297,9 @@ static void loose_fill_ref_dir(struct ref_store *ref_store,
closedir(d);
/*
- * Manually add refs/bisect, which, being per-worktree, might
- * not appear in the directory listing for refs/ in the main
- * repo.
+ * Manually add refs/bisect and refs/worktree, which, being
+ * per-worktree, might not appear in the directory listing for
+ * refs/ in the main repo.
*/
if (!strcmp(dirname, "refs/")) {
int pos = search_ref_dir(dir, "refs/bisect/", 12);
@@ -281,6 +309,14 @@ static void loose_fill_ref_dir(struct ref_store *ref_store,
dir->cache, "refs/bisect/", 12, 1);
add_entry_to_dir(dir, child_entry);
}
+
+ pos = search_ref_dir(dir, "refs/worktree/", 11);
+
+ if (pos < 0) {
+ struct ref_entry *child_entry = create_dir_entry(
+ dir->cache, "refs/worktree/", 11, 1);
+ add_entry_to_dir(dir, child_entry);
+ }
}
}
diff --git a/revision.c b/revision.c
index 28fb2a70cd..59a3c22b8a 100644
--- a/revision.c
+++ b/revision.c
@@ -1177,7 +1177,7 @@ struct all_refs_cb {
int warned_bad_reflog;
struct rev_info *all_revs;
const char *name_for_errormsg;
- struct ref_store *refs;
+ struct worktree *wt;
};
int ref_excluded(struct string_list *ref_excludes, const char *path)
@@ -1214,7 +1214,7 @@ static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs,
cb->all_revs = revs;
cb->all_flags = flags;
revs->rev_input_given = 1;
- cb->refs = NULL;
+ cb->wt = NULL;
}
void clear_ref_exclusion(struct string_list **ref_excludes_p)
@@ -1277,14 +1277,20 @@ static int handle_one_reflog_ent(struct object_id *ooid, struct object_id *noid,
return 0;
}
-static int handle_one_reflog(const char *path, const struct object_id *oid,
+static int handle_one_reflog(const char *refname_in_wt,
+ const struct object_id *oid,
int flag, void *cb_data)
{
struct all_refs_cb *cb = cb_data;
+ struct strbuf refname = STRBUF_INIT;
+
cb->warned_bad_reflog = 0;
- cb->name_for_errormsg = path;
- refs_for_each_reflog_ent(cb->refs, path,
+ strbuf_worktree_ref(cb->wt, &refname, refname_in_wt);
+ cb->name_for_errormsg = refname.buf;
+ refs_for_each_reflog_ent(get_main_ref_store(the_repository),
+ refname.buf,
handle_one_reflog_ent, cb_data);
+ strbuf_release(&refname);
return 0;
}
@@ -1299,8 +1305,8 @@ static void add_other_reflogs_to_pending(struct all_refs_cb *cb)
if (wt->is_current)
continue;
- cb->refs = get_worktree_ref_store(wt);
- refs_for_each_reflog(cb->refs,
+ cb->wt = wt;
+ refs_for_each_reflog(get_worktree_ref_store(wt),
handle_one_reflog,
cb);
}
@@ -1313,7 +1319,7 @@ void add_reflogs_to_pending(struct rev_info *revs, unsigned flags)
cb.all_revs = revs;
cb.all_flags = flags;
- cb.refs = get_main_ref_store(revs->repo);
+ cb.wt = NULL;
for_each_reflog(handle_one_reflog, &cb);
if (!revs->single_worktree)
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index cd74c0a471..c7b53e494b 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -306,6 +306,8 @@ test_git_path GIT_COMMON_DIR=bar hooks/me bar/hooks/me
test_git_path GIT_COMMON_DIR=bar config bar/config
test_git_path GIT_COMMON_DIR=bar packed-refs bar/packed-refs
test_git_path GIT_COMMON_DIR=bar shallow bar/shallow
+test_git_path GIT_COMMON_DIR=bar common bar/common
+test_git_path GIT_COMMON_DIR=bar common/file bar/common/file
# In the tests below, $(pwd) must be used because it is a native path on
# Windows and avoids MSYS's path mangling (which simplifies "foo/../bar" and
diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh
index 388b0611d8..3e053532eb 100755
--- a/t/t1410-reflog.sh
+++ b/t/t1410-reflog.sh
@@ -368,4 +368,19 @@ test_expect_success 'continue walking past root commits' '
)
'
+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
diff --git a/t/t1415-worktree-refs.sh b/t/t1415-worktree-refs.sh
new file mode 100755
index 0000000000..b664e51250
--- /dev/null
+++ b/t/t1415-worktree-refs.sh
@@ -0,0 +1,79 @@
+#!/bin/sh
+
+test_description='per-worktree refs'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ test_commit initial &&
+ test_commit wt1 &&
+ test_commit wt2 &&
+ git worktree add wt1 wt1 &&
+ git worktree add wt2 wt2 &&
+ git checkout initial &&
+ git update-ref refs/worktree/foo HEAD &&
+ git -C wt1 update-ref refs/worktree/foo HEAD &&
+ git -C wt2 update-ref refs/worktree/foo HEAD
+'
+
+test_expect_success 'refs/worktree must not be packed' '
+ git pack-refs --all &&
+ test_path_is_missing .git/refs/tags/wt1 &&
+ test_path_is_file .git/refs/worktree/foo &&
+ test_path_is_file .git/worktrees/wt1/refs/worktree/foo &&
+ test_path_is_file .git/worktrees/wt2/refs/worktree/foo
+'
+
+test_expect_success 'refs/worktree are per-worktree' '
+ test_cmp_rev worktree/foo initial &&
+ ( cd wt1 && test_cmp_rev worktree/foo wt1 ) &&
+ ( 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/t/t1450-fsck.sh b/t/t1450-fsck.sh
index b5677d26a4..e20e8fa830 100755
--- a/t/t1450-fsck.sh
+++ b/t/t1450-fsck.sh
@@ -101,6 +101,41 @@ test_expect_success 'HEAD link pointing at a funny place' '
grep "HEAD points to something strange" out
'
+test_expect_success 'HEAD link pointing at a funny object (from different wt)' '
+ test_when_finished "mv .git/SAVED_HEAD .git/HEAD" &&
+ test_when_finished "rm -rf .git/worktrees wt" &&
+ git worktree add wt &&
+ mv .git/HEAD .git/SAVED_HEAD &&
+ 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-worktree/HEAD: detached HEAD points" out
+'
+
+test_expect_success 'other worktree HEAD link pointing at a funny object' '
+ test_when_finished "rm -rf .git/worktrees other" &&
+ git worktree add other &&
+ echo $ZERO_OID >.git/worktrees/other/HEAD &&
+ test_must_fail git fsck 2>out &&
+ grep "worktrees/other/HEAD: detached HEAD points" out
+'
+
+test_expect_success 'other worktree HEAD link pointing at missing object' '
+ test_when_finished "rm -rf .git/worktrees other" &&
+ git worktree add other &&
+ echo "Contents missing from repo" | git hash-object --stdin >.git/worktrees/other/HEAD &&
+ test_must_fail git fsck 2>out &&
+ grep "worktrees/other/HEAD: invalid sha1 pointer" out
+'
+
+test_expect_success 'other worktree HEAD link pointing at a funny place' '
+ test_when_finished "rm -rf .git/worktrees other" &&
+ git worktree add other &&
+ echo "ref: refs/funny/place" >.git/worktrees/other/HEAD &&
+ test_must_fail git fsck 2>out &&
+ grep "worktrees/other/HEAD points to something strange" out
+'
+
test_expect_success 'email without @ is okay' '
git cat-file commit HEAD >basis &&
sed "s/@/AT/" basis >okay &&
diff --git a/worktree.c b/worktree.c
index befdbe7fae..d6a0ee7f73 100644
--- a/worktree.c
+++ b/worktree.c
@@ -487,6 +487,75 @@ 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;
+}
+
+void strbuf_worktree_ref(const struct worktree *wt,
+ struct strbuf *sb,
+ const char *refname)
+{
+ 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);
+}
+
+const char *worktree_ref(const struct worktree *wt, const char *refname)
+{
+ static struct strbuf sb = STRBUF_INIT;
+
+ strbuf_reset(&sb);
+ strbuf_worktree_ref(wt, &sb, refname);
+ return sb.buf;
+}
+
int other_head_refs(each_ref_fn fn, void *cb_data)
{
struct worktree **worktrees, **p;
@@ -495,13 +564,17 @@ int other_head_refs(each_ref_fn fn, void *cb_data)
worktrees = get_worktrees(0);
for (p = worktrees; *p; p++) {
struct worktree *wt = *p;
- struct ref_store *refs;
+ struct object_id oid;
+ int flag;
if (wt->is_current)
continue;
- refs = get_worktree_ref_store(wt);
- ret = refs_head_ref(refs, fn, cb_data);
+ if (!refs_read_ref_full(get_main_ref_store(the_repository),
+ worktree_ref(wt, "HEAD"),
+ RESOLVE_REF_READING,
+ &oid, &flag))
+ ret = fn(worktree_ref(wt, "HEAD"), &oid, flag, cb_data);
if (ret)
break;
}
diff --git a/worktree.h b/worktree.h
index 55d449b6a9..9e3b0b7b6f 100644
--- a/worktree.h
+++ b/worktree.h
@@ -108,4 +108,28 @@ 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);
+
+/*
+ * Return a refname suitable for access from the current ref store.
+ */
+void strbuf_worktree_ref(const struct worktree *wt,
+ struct strbuf *sb,
+ const char *refname);
+
+/*
+ * Return a refname suitable for access from the current ref
+ * store. The result will be destroyed at the next call.
+ */
+const char *worktree_ref(const struct worktree *wt,
+ const char *refname);
+
#endif