Merge branch 'nd/per-worktree-ref-iteration'
The code to traverse objects for reachability, used to decide what objects are unreferenced and expendable, have been taught to also consider per-worktree refs of other worktrees as starting points to prevent data loss. * nd/per-worktree-ref-iteration: git-worktree.txt: correct linkgit command name reflog expire: cover reflog from all worktrees fsck: check HEAD and reflog from other worktrees fsck: move fsck_head_link() to get_default_heads() to avoid some globals revision.c: better error reporting on ref from different worktrees revision.c: correct a parameter name refs: new ref types to make per-worktree refs visible to all worktrees Add a place for (not) sharing stuff between worktrees refs.c: indent with tabs, not spaces
This commit is contained in:
commit
e146cc97be
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
2
path.c
2
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" },
|
||||
|
24
refs.c
24
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)
|
||||
|
8
refs.h
8
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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
22
revision.c
22
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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
79
t/t1415-worktree-refs.sh
Executable file
79
t/t1415-worktree-refs.sh
Executable file
@ -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
|
@ -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 &&
|
||||
|
79
worktree.c
79
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;
|
||||
}
|
||||
|
24
worktree.h
24
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
|
||||
|
Loading…
Reference in New Issue
Block a user