Skip to content

Commit e146cc9

Browse files
committed
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
2 parents 11aa560 + 14f74d5 commit e146cc9

16 files changed

+449
-47
lines changed

Documentation/git-reflog.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ depending on the subcommand:
2020
'git reflog' ['show'] [log-options] [<ref>]
2121
'git reflog expire' [--expire=<time>] [--expire-unreachable=<time>]
2222
[--rewrite] [--updateref] [--stale-fix]
23-
[--dry-run | -n] [--verbose] [--all | <refs>...]
23+
[--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]
2424
'git reflog delete' [--rewrite] [--updateref]
2525
[--dry-run | -n] [--verbose] ref@\{specifier\}...
2626
'git reflog exists' <ref>
@@ -72,6 +72,11 @@ Options for `expire`
7272
--all::
7373
Process the reflogs of all references.
7474

75+
--single-worktree::
76+
By default when `--all` is specified, reflogs from all working
77+
trees are processed. This option limits the processing to reflogs
78+
from the current working tree only.
79+
7580
--expire=<time>::
7681
Prune entries older than the specified time. If this option is
7782
not specified, the expiration time is taken from the

Documentation/git-worktree.txt

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,35 @@ working trees, it can be used to identify worktrees. For example if
204204
you only have two working trees, at "/abc/def/ghi" and "/abc/def/ggg",
205205
then "ghi" or "def/ghi" is enough to point to the former working tree.
206206

207+
REFS
208+
----
209+
In multiple working trees, some refs may be shared between all working
210+
trees, some refs are local. One example is HEAD is different for all
211+
working trees. This section is about the sharing rules and how to access
212+
refs of one working tree from another.
213+
214+
In general, all pseudo refs are per working tree and all refs starting
215+
with "refs/" are shared. Pseudo refs are ones like HEAD which are
216+
directly under GIT_DIR instead of inside GIT_DIR/refs. There are one
217+
exception to this: refs inside refs/bisect and refs/worktree is not
218+
shared.
219+
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+
232+
To access refs, it's best not to look inside GIT_DIR directly. Instead
233+
use commands such as linkgit:git-rev-parse[1] or linkgit:git-update-ref[1]
234+
which will handle refs correctly.
235+
207236
CONFIGURATION FILE
208237
------------------
209238
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
258287
`/path/other/test-next/.git/HEAD` or `/path/main/.git/HEAD`) while `git
259288
rev-parse --git-path refs/heads/master` uses
260289
$GIT_COMMON_DIR and returns `/path/main/.git/refs/heads/master`,
261-
since refs are shared across all working trees.
290+
since refs are shared across all working trees, except refs/bisect and
291+
refs/worktree.
262292

263293
See linkgit:gitrepository-layout[5] for more information. The rule of
264294
thumb is do not make any assumption about whether a path belongs to

Documentation/gitrepository-layout.txt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,10 @@ refs::
9595
References are stored in subdirectories of this
9696
directory. The 'git prune' command knows to preserve
9797
objects reachable from refs found in this directory and
98-
its subdirectories. This directory is ignored if $GIT_COMMON_DIR
99-
is set and "$GIT_COMMON_DIR/refs" will be used instead.
98+
its subdirectories.
99+
This directory is ignored (except refs/bisect and
100+
refs/worktree) if $GIT_COMMON_DIR is set and
101+
"$GIT_COMMON_DIR/refs" will be used instead.
100102

101103
refs/heads/`name`::
102104
records tip-of-the-tree commit objects of branch `name`
@@ -170,6 +172,11 @@ hooks::
170172
each hook. This directory is ignored if $GIT_COMMON_DIR is set
171173
and "$GIT_COMMON_DIR/hooks" will be used instead.
172174

175+
common::
176+
When multiple working trees are used, most of files in
177+
$GIT_DIR are per-worktree with a few known exceptions. All
178+
files under 'common' however will be shared between all
179+
working trees.
173180

174181
index::
175182
The current index file for the repository. It is

builtin/fsck.c

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "packfile.h"
2020
#include "object-store.h"
2121
#include "run-command.h"
22+
#include "worktree.h"
2223

2324
#define REACHABLE 0x0001
2425
#define SEEN 0x0002
@@ -36,8 +37,6 @@ static int check_strict;
3637
static int keep_cache_objects;
3738
static struct fsck_options fsck_walk_options = FSCK_OPTIONS_DEFAULT;
3839
static struct fsck_options fsck_obj_options = FSCK_OPTIONS_DEFAULT;
39-
static struct object_id head_oid;
40-
static const char *head_points_at;
4140
static int errors_found;
4241
static int write_lost_and_found;
4342
static int verbose;
@@ -446,7 +445,11 @@ static int fsck_handle_reflog_ent(struct object_id *ooid, struct object_id *noid
446445
static int fsck_handle_reflog(const char *logname, const struct object_id *oid,
447446
int flag, void *cb_data)
448447
{
449-
for_each_reflog_ent(logname, fsck_handle_reflog_ent, (void *)logname);
448+
struct strbuf refname = STRBUF_INIT;
449+
450+
strbuf_worktree_ref(cb_data, &refname, logname);
451+
for_each_reflog_ent(refname.buf, fsck_handle_reflog_ent, refname.buf);
452+
strbuf_release(&refname);
450453
return 0;
451454
}
452455

@@ -484,13 +487,34 @@ static int fsck_handle_ref(const char *refname, const struct object_id *oid,
484487
return 0;
485488
}
486489

490+
static int fsck_head_link(const char *head_ref_name,
491+
const char **head_points_at,
492+
struct object_id *head_oid);
493+
487494
static void get_default_heads(void)
488495
{
489-
if (head_points_at && !is_null_oid(&head_oid))
490-
fsck_handle_ref("HEAD", &head_oid, 0, NULL);
496+
struct worktree **worktrees, **p;
497+
const char *head_points_at;
498+
struct object_id head_oid;
499+
491500
for_each_rawref(fsck_handle_ref, NULL);
492-
if (include_reflogs)
493-
for_each_reflog(fsck_handle_reflog, NULL);
501+
502+
worktrees = get_worktrees(0);
503+
for (p = worktrees; *p; p++) {
504+
struct worktree *wt = *p;
505+
struct strbuf ref = STRBUF_INIT;
506+
507+
strbuf_worktree_ref(wt, &ref, "HEAD");
508+
fsck_head_link(ref.buf, &head_points_at, &head_oid);
509+
if (head_points_at && !is_null_oid(&head_oid))
510+
fsck_handle_ref(ref.buf, &head_oid, 0, NULL);
511+
strbuf_release(&ref);
512+
513+
if (include_reflogs)
514+
refs_for_each_reflog(get_worktree_ref_store(wt),
515+
fsck_handle_reflog, wt);
516+
}
517+
free_worktrees(worktrees);
494518

495519
/*
496520
* Not having any default heads isn't really fatal, but
@@ -579,33 +603,36 @@ static void fsck_object_dir(const char *path)
579603
stop_progress(&progress);
580604
}
581605

582-
static int fsck_head_link(void)
606+
static int fsck_head_link(const char *head_ref_name,
607+
const char **head_points_at,
608+
struct object_id *head_oid)
583609
{
584610
int null_is_error = 0;
585611

586612
if (verbose)
587-
fprintf(stderr, "Checking HEAD link\n");
613+
fprintf(stderr, "Checking %s link\n", head_ref_name);
588614

589-
head_points_at = resolve_ref_unsafe("HEAD", 0, &head_oid, NULL);
590-
if (!head_points_at) {
615+
*head_points_at = resolve_ref_unsafe(head_ref_name, 0, head_oid, NULL);
616+
if (!*head_points_at) {
591617
errors_found |= ERROR_REFS;
592-
return error("Invalid HEAD");
618+
return error("Invalid %s", head_ref_name);
593619
}
594-
if (!strcmp(head_points_at, "HEAD"))
620+
if (!strcmp(*head_points_at, head_ref_name))
595621
/* detached HEAD */
596622
null_is_error = 1;
597-
else if (!starts_with(head_points_at, "refs/heads/")) {
623+
else if (!starts_with(*head_points_at, "refs/heads/")) {
598624
errors_found |= ERROR_REFS;
599-
return error("HEAD points to something strange (%s)",
600-
head_points_at);
625+
return error("%s points to something strange (%s)",
626+
head_ref_name, *head_points_at);
601627
}
602-
if (is_null_oid(&head_oid)) {
628+
if (is_null_oid(head_oid)) {
603629
if (null_is_error) {
604630
errors_found |= ERROR_REFS;
605-
return error("HEAD: detached HEAD points at nothing");
631+
return error("%s: detached HEAD points at nothing",
632+
head_ref_name);
606633
}
607-
fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n",
608-
head_points_at + 11);
634+
fprintf(stderr, "notice: %s points to an unborn branch (%s)\n",
635+
head_ref_name, *head_points_at + 11);
609636
}
610637
return 0;
611638
}
@@ -720,7 +747,6 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
720747

721748
git_config(fsck_config, NULL);
722749

723-
fsck_head_link();
724750
if (connectivity_only) {
725751
for_each_loose_object(mark_loose_for_connectivity, NULL, 0);
726752
for_each_packed_object(mark_packed_for_connectivity, NULL, 0);

builtin/reflog.c

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "diff.h"
1111
#include "revision.h"
1212
#include "reachable.h"
13+
#include "worktree.h"
1314

1415
/* NEEDSWORK: switch to using parse_options */
1516
static const char reflog_expire_usage[] =
@@ -52,6 +53,7 @@ struct collect_reflog_cb {
5253
struct collected_reflog **e;
5354
int alloc;
5455
int nr;
56+
struct worktree *wt;
5557
};
5658

5759
/* 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,
330332
return 0;
331333
}
332334

335+
static int is_head(const char *refname)
336+
{
337+
switch (ref_type(refname)) {
338+
case REF_TYPE_OTHER_PSEUDOREF:
339+
case REF_TYPE_MAIN_PSEUDOREF:
340+
if (parse_worktree_ref(refname, NULL, NULL, &refname))
341+
BUG("not a worktree ref: %s", refname);
342+
break;
343+
default:
344+
break;
345+
}
346+
return !strcmp(refname, "HEAD");
347+
}
348+
333349
static void reflog_expiry_prepare(const char *refname,
334350
const struct object_id *oid,
335351
void *cb_data)
336352
{
337353
struct expire_reflog_policy_cb *cb = cb_data;
338354

339-
if (!cb->cmd.expire_unreachable || !strcmp(refname, "HEAD")) {
355+
if (!cb->cmd.expire_unreachable || is_head(refname)) {
340356
cb->tip_commit = NULL;
341357
cb->unreachable_expire_kind = UE_HEAD;
342358
} else {
@@ -388,8 +404,19 @@ static int collect_reflog(const char *ref, const struct object_id *oid, int unus
388404
{
389405
struct collected_reflog *e;
390406
struct collect_reflog_cb *cb = cb_data;
407+
struct strbuf newref = STRBUF_INIT;
408+
409+
/*
410+
* Avoid collecting the same shared ref multiple times because
411+
* they are available via all worktrees.
412+
*/
413+
if (!cb->wt->is_current && ref_type(ref) == REF_TYPE_NORMAL)
414+
return 0;
415+
416+
strbuf_worktree_ref(cb->wt, &newref, ref);
417+
FLEX_ALLOC_STR(e, reflog, newref.buf);
418+
strbuf_release(&newref);
391419

392-
FLEX_ALLOC_STR(e, reflog, ref);
393420
oidcpy(&e->oid, oid);
394421
ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc);
395422
cb->e[cb->nr++] = e;
@@ -512,7 +539,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
512539
{
513540
struct expire_reflog_policy_cb cb;
514541
timestamp_t now = time(NULL);
515-
int i, status, do_all;
542+
int i, status, do_all, all_worktrees = 1;
516543
int explicit_expiry = 0;
517544
unsigned int flags = 0;
518545

@@ -549,6 +576,8 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
549576
flags |= EXPIRE_REFLOGS_UPDATE_REF;
550577
else if (!strcmp(arg, "--all"))
551578
do_all = 1;
579+
else if (!strcmp(arg, "--single-worktree"))
580+
all_worktrees = 0;
552581
else if (!strcmp(arg, "--verbose"))
553582
flags |= EXPIRE_REFLOGS_VERBOSE;
554583
else if (!strcmp(arg, "--")) {
@@ -577,10 +606,19 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
577606

578607
if (do_all) {
579608
struct collect_reflog_cb collected;
609+
struct worktree **worktrees, **p;
580610
int i;
581611

582612
memset(&collected, 0, sizeof(collected));
583-
for_each_reflog(collect_reflog, &collected);
613+
worktrees = get_worktrees(0);
614+
for (p = worktrees; *p; p++) {
615+
if (!all_worktrees && !(*p)->is_current)
616+
continue;
617+
collected.wt = *p;
618+
refs_for_each_reflog(get_worktree_ref_store(*p),
619+
collect_reflog, &collected);
620+
}
621+
free_worktrees(worktrees);
584622
for (i = 0; i < collected.nr; i++) {
585623
struct collected_reflog *e = collected.e[i];
586624
set_reflog_expiry_param(&cb.cmd, explicit_expiry, e->reflog);

path.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ struct common_dir {
108108

109109
static struct common_dir common_list[] = {
110110
{ 0, 1, 0, "branches" },
111+
{ 0, 1, 0, "common" },
111112
{ 0, 1, 0, "hooks" },
112113
{ 0, 1, 0, "info" },
113114
{ 0, 0, 1, "info/sparse-checkout" },
@@ -118,6 +119,7 @@ static struct common_dir common_list[] = {
118119
{ 0, 1, 0, "objects" },
119120
{ 0, 1, 0, "refs" },
120121
{ 0, 1, 1, "refs/bisect" },
122+
{ 0, 1, 1, "refs/worktree" },
121123
{ 0, 1, 0, "remotes" },
122124
{ 0, 1, 0, "worktrees" },
123125
{ 0, 1, 0, "rr-cache" },

refs.c

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,7 @@ int dwim_log(const char *str, int len, struct object_id *oid, char **log)
624624
static int is_per_worktree_ref(const char *refname)
625625
{
626626
return !strcmp(refname, "HEAD") ||
627+
starts_with(refname, "refs/worktree/") ||
627628
starts_with(refname, "refs/bisect/") ||
628629
starts_with(refname, "refs/rewritten/");
629630
}
@@ -640,13 +641,34 @@ static int is_pseudoref_syntax(const char *refname)
640641
return 1;
641642
}
642643

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+
643661
enum ref_type ref_type(const char *refname)
644662
{
645663
if (is_per_worktree_ref(refname))
646664
return REF_TYPE_PER_WORKTREE;
647665
if (is_pseudoref_syntax(refname))
648666
return REF_TYPE_PSEUDOREF;
649-
return REF_TYPE_NORMAL;
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;
671+
return REF_TYPE_NORMAL;
650672
}
651673

652674
long get_files_ref_lock_timeout_ms(void)

refs.h

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

716716
enum ref_type {
717-
REF_TYPE_PER_WORKTREE,
718-
REF_TYPE_PSEUDOREF,
719-
REF_TYPE_NORMAL,
717+
REF_TYPE_PER_WORKTREE, /* refs inside refs/ but not shared */
718+
REF_TYPE_PSEUDOREF, /* refs outside refs/ in current worktree */
719+
REF_TYPE_MAIN_PSEUDOREF, /* pseudo refs from the main worktree */
720+
REF_TYPE_OTHER_PSEUDOREF, /* pseudo refs from other worktrees */
721+
REF_TYPE_NORMAL, /* normal/shared refs inside refs/ */
720722
};
721723

722724
enum ref_type ref_type(const char *refname);

0 commit comments

Comments
 (0)