Skip to content

Commit e6df1ee

Browse files
calebdwgitster
authored andcommitted
worktree: add relative cli/config options to repair command
This teaches the `worktree repair` command to respect the `--[no-]relative-paths` CLI option and `worktree.useRelativePaths` config setting. If an existing worktree with an absolute path is repaired with `--relative-paths`, the links will be replaced with relative paths, even if the original path was correct. This allows a user to covert existing worktrees between absolute/relative as desired. To simplify things, both linking files are written when one of the files needs to be repaired. In some cases, this fixes the other file before it is checked, in other cases this results in a correct file being written with the same contents. Signed-off-by: Caleb White <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 298d291 commit e6df1ee

File tree

5 files changed

+76
-33
lines changed

5 files changed

+76
-33
lines changed

Documentation/git-worktree.txt

+3
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,9 @@ This can also be set up as the default behaviour by using the
220220
Link worktrees using relative paths or absolute paths (default).
221221
Overrides the `worktree.useRelativePaths` config option, see
222222
linkgit:git-config[1].
223+
+
224+
With `repair`, the linking files will be updated if there's an absolute/relative
225+
mismatch, even if the links are correct.
223226

224227
--[no-]track::
225228
When creating a new branch, if `<commit-ish>` is a branch,

builtin/worktree.c

+4-2
Original file line numberDiff line numberDiff line change
@@ -1385,15 +1385,17 @@ static int repair(int ac, const char **av, const char *prefix)
13851385
const char **p;
13861386
const char *self[] = { ".", NULL };
13871387
struct option options[] = {
1388+
OPT_BOOL(0, "relative-paths", &use_relative_paths,
1389+
N_("use relative paths for worktrees")),
13881390
OPT_END()
13891391
};
13901392
int rc = 0;
13911393

13921394
ac = parse_options(ac, av, prefix, options, git_worktree_repair_usage, 0);
13931395
p = ac > 0 ? av : self;
13941396
for (; *p; p++)
1395-
repair_worktree_at_path(*p, report_repair, &rc);
1396-
repair_worktrees(report_repair, &rc);
1397+
repair_worktree_at_path(*p, report_repair, &rc, use_relative_paths);
1398+
repair_worktrees(report_repair, &rc, use_relative_paths);
13971399
return rc;
13981400
}
13991401

t/t2406-worktree-repair.sh

+39
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,43 @@ test_expect_success 'repair copied main and linked worktrees' '
216216
test_cmp dup/linked.expect dup/linked/.git
217217
'
218218

219+
test_expect_success 'repair worktree with relative path with missing gitfile' '
220+
test_when_finished "rm -rf main wt" &&
221+
test_create_repo main &&
222+
git -C main config worktree.useRelativePaths true &&
223+
test_commit -C main init &&
224+
git -C main worktree add --detach ../wt &&
225+
rm wt/.git &&
226+
test_path_is_missing wt/.git &&
227+
git -C main worktree repair &&
228+
echo "gitdir: ../main/.git/worktrees/wt" >expect &&
229+
test_cmp expect wt/.git
230+
'
231+
232+
test_expect_success 'repair absolute worktree to use relative paths' '
233+
test_when_finished "rm -rf main side sidemoved" &&
234+
test_create_repo main &&
235+
test_commit -C main init &&
236+
git -C main worktree add --detach ../side &&
237+
echo "../../../../sidemoved/.git" >expect-gitdir &&
238+
echo "gitdir: ../main/.git/worktrees/side" >expect-gitfile &&
239+
mv side sidemoved &&
240+
git -C main worktree repair --relative-paths ../sidemoved &&
241+
test_cmp expect-gitdir main/.git/worktrees/side/gitdir &&
242+
test_cmp expect-gitfile sidemoved/.git
243+
'
244+
245+
test_expect_success 'repair relative worktree to use absolute paths' '
246+
test_when_finished "rm -rf main side sidemoved" &&
247+
test_create_repo main &&
248+
test_commit -C main init &&
249+
git -C main worktree add --relative-paths --detach ../side &&
250+
echo "$(pwd)/sidemoved/.git" >expect-gitdir &&
251+
echo "gitdir: $(pwd)/main/.git/worktrees/side" >expect-gitfile &&
252+
mv side sidemoved &&
253+
git -C main worktree repair ../sidemoved &&
254+
test_cmp expect-gitdir main/.git/worktrees/side/gitdir &&
255+
test_cmp expect-gitfile sidemoved/.git
256+
'
257+
219258
test_done

worktree.c

+27-29
Original file line numberDiff line numberDiff line change
@@ -573,12 +573,13 @@ int other_head_refs(each_ref_fn fn, void *cb_data)
573573
* pointing at <repo>/worktrees/<id>.
574574
*/
575575
static void repair_gitfile(struct worktree *wt,
576-
worktree_repair_fn fn, void *cb_data)
576+
worktree_repair_fn fn, void *cb_data,
577+
int use_relative_paths)
577578
{
578579
struct strbuf dotgit = STRBUF_INIT;
580+
struct strbuf gitdir = STRBUF_INIT;
579581
struct strbuf repo = STRBUF_INIT;
580582
struct strbuf backlink = STRBUF_INIT;
581-
struct strbuf tmp = STRBUF_INIT;
582583
char *dotgit_contents = NULL;
583584
const char *repair = NULL;
584585
int err;
@@ -594,6 +595,7 @@ static void repair_gitfile(struct worktree *wt,
594595

595596
strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1);
596597
strbuf_addf(&dotgit, "%s/.git", wt->path);
598+
strbuf_addf(&gitdir, "%s/gitdir", repo.buf);
597599
dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
598600

599601
if (dotgit_contents) {
@@ -611,18 +613,20 @@ static void repair_gitfile(struct worktree *wt,
611613
repair = _(".git file broken");
612614
else if (fspathcmp(backlink.buf, repo.buf))
613615
repair = _(".git file incorrect");
616+
else if (use_relative_paths == is_absolute_path(dotgit_contents))
617+
repair = _(".git file absolute/relative path mismatch");
614618

615619
if (repair) {
616620
fn(0, wt->path, repair, cb_data);
617-
write_file(dotgit.buf, "gitdir: %s", relative_path(repo.buf, wt->path, &tmp));
621+
write_worktree_linking_files(dotgit, gitdir, use_relative_paths);
618622
}
619623

620624
done:
621625
free(dotgit_contents);
622626
strbuf_release(&repo);
623627
strbuf_release(&dotgit);
628+
strbuf_release(&gitdir);
624629
strbuf_release(&backlink);
625-
strbuf_release(&tmp);
626630
}
627631

628632
static void repair_noop(int iserr UNUSED,
@@ -633,15 +637,15 @@ static void repair_noop(int iserr UNUSED,
633637
/* nothing */
634638
}
635639

636-
void repair_worktrees(worktree_repair_fn fn, void *cb_data)
640+
void repair_worktrees(worktree_repair_fn fn, void *cb_data, int use_relative_paths)
637641
{
638642
struct worktree **worktrees = get_worktrees_internal(1);
639643
struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
640644

641645
if (!fn)
642646
fn = repair_noop;
643647
for (; *wt; wt++)
644-
repair_gitfile(*wt, fn, cb_data);
648+
repair_gitfile(*wt, fn, cb_data, use_relative_paths);
645649
free_worktrees(worktrees);
646650
}
647651

@@ -757,16 +761,14 @@ static ssize_t infer_backlink(const char *gitfile, struct strbuf *inferred)
757761
* the worktree's path.
758762
*/
759763
void repair_worktree_at_path(const char *path,
760-
worktree_repair_fn fn, void *cb_data)
764+
worktree_repair_fn fn, void *cb_data,
765+
int use_relative_paths)
761766
{
762767
struct strbuf dotgit = STRBUF_INIT;
763-
struct strbuf realdotgit = STRBUF_INIT;
764768
struct strbuf backlink = STRBUF_INIT;
765769
struct strbuf inferred_backlink = STRBUF_INIT;
766770
struct strbuf gitdir = STRBUF_INIT;
767771
struct strbuf olddotgit = STRBUF_INIT;
768-
struct strbuf realolddotgit = STRBUF_INIT;
769-
struct strbuf tmp = STRBUF_INIT;
770772
char *dotgit_contents = NULL;
771773
const char *repair = NULL;
772774
int err;
@@ -778,25 +780,25 @@ void repair_worktree_at_path(const char *path,
778780
goto done;
779781

780782
strbuf_addf(&dotgit, "%s/.git", path);
781-
if (!strbuf_realpath(&realdotgit, dotgit.buf, 0)) {
783+
if (!strbuf_realpath(&dotgit, dotgit.buf, 0)) {
782784
fn(1, path, _("not a valid path"), cb_data);
783785
goto done;
784786
}
785787

786-
infer_backlink(realdotgit.buf, &inferred_backlink);
788+
infer_backlink(dotgit.buf, &inferred_backlink);
787789
strbuf_realpath_forgiving(&inferred_backlink, inferred_backlink.buf, 0);
788-
dotgit_contents = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err));
790+
dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
789791
if (dotgit_contents) {
790792
if (is_absolute_path(dotgit_contents)) {
791793
strbuf_addstr(&backlink, dotgit_contents);
792794
} else {
793-
strbuf_addbuf(&backlink, &realdotgit);
795+
strbuf_addbuf(&backlink, &dotgit);
794796
strbuf_strip_suffix(&backlink, ".git");
795797
strbuf_addstr(&backlink, dotgit_contents);
796798
strbuf_realpath_forgiving(&backlink, backlink.buf, 0);
797799
}
798800
} else if (err == READ_GITFILE_ERR_NOT_A_FILE) {
799-
fn(1, realdotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);
801+
fn(1, dotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);
800802
goto done;
801803
} else if (err == READ_GITFILE_ERR_NOT_A_REPO) {
802804
if (inferred_backlink.len) {
@@ -809,11 +811,11 @@ void repair_worktree_at_path(const char *path,
809811
*/
810812
strbuf_swap(&backlink, &inferred_backlink);
811813
} else {
812-
fn(1, realdotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data);
814+
fn(1, dotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data);
813815
goto done;
814816
}
815817
} else {
816-
fn(1, realdotgit.buf, _("unable to locate repository; .git file broken"), cb_data);
818+
fn(1, dotgit.buf, _("unable to locate repository; .git file broken"), cb_data);
817819
goto done;
818820
}
819821

@@ -835,39 +837,35 @@ void repair_worktree_at_path(const char *path,
835837
* in the "copy" repository. In this case, point the "copy" worktree's
836838
* .git file at the "copy" repository.
837839
*/
838-
if (inferred_backlink.len && fspathcmp(backlink.buf, inferred_backlink.buf)) {
840+
if (inferred_backlink.len && fspathcmp(backlink.buf, inferred_backlink.buf))
839841
strbuf_swap(&backlink, &inferred_backlink);
840-
}
841842

842843
strbuf_addf(&gitdir, "%s/gitdir", backlink.buf);
843844
if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0)
844845
repair = _("gitdir unreadable");
846+
else if (use_relative_paths == is_absolute_path(olddotgit.buf))
847+
repair = _("gitdir absolute/relative path mismatch");
845848
else {
846849
strbuf_rtrim(&olddotgit);
847-
if (is_absolute_path(olddotgit.buf)) {
848-
strbuf_addbuf(&realolddotgit, &olddotgit);
849-
} else {
850-
strbuf_addf(&realolddotgit, "%s/%s", backlink.buf, olddotgit.buf);
851-
strbuf_realpath_forgiving(&realolddotgit, realolddotgit.buf, 0);
850+
if (!is_absolute_path(olddotgit.buf)) {
851+
strbuf_insertf(&olddotgit, 0, "%s/", backlink.buf);
852+
strbuf_realpath_forgiving(&olddotgit, olddotgit.buf, 0);
852853
}
853-
if (fspathcmp(realolddotgit.buf, realdotgit.buf))
854+
if (fspathcmp(olddotgit.buf, dotgit.buf))
854855
repair = _("gitdir incorrect");
855856
}
856857

857858
if (repair) {
858859
fn(0, gitdir.buf, repair, cb_data);
859-
write_file(gitdir.buf, "%s", relative_path(realdotgit.buf, backlink.buf, &tmp));
860+
write_worktree_linking_files(dotgit, gitdir, use_relative_paths);
860861
}
861862
done:
862863
free(dotgit_contents);
863864
strbuf_release(&olddotgit);
864-
strbuf_release(&realolddotgit);
865865
strbuf_release(&backlink);
866866
strbuf_release(&inferred_backlink);
867867
strbuf_release(&gitdir);
868-
strbuf_release(&realdotgit);
869868
strbuf_release(&dotgit);
870-
strbuf_release(&tmp);
871869
}
872870

873871
int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, timestamp_t expire)

worktree.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ typedef void (* worktree_repair_fn)(int iserr, const char *path,
129129
* function, if non-NULL, is called with the path of the worktree and a
130130
* description of the repair or error, along with the callback user-data.
131131
*/
132-
void repair_worktrees(worktree_repair_fn, void *cb_data);
132+
void repair_worktrees(worktree_repair_fn, void *cb_data, int use_relative_paths);
133133

134134
/*
135135
* Repair the linked worktrees after the gitdir has been moved.
@@ -151,7 +151,8 @@ void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path
151151
* worktree and a description of the repair or error, along with the callback
152152
* user-data.
153153
*/
154-
void repair_worktree_at_path(const char *, worktree_repair_fn, void *cb_data);
154+
void repair_worktree_at_path(const char *, worktree_repair_fn,
155+
void *cb_data, int use_relative_paths);
155156

156157
/*
157158
* Free up the memory for a worktree.

0 commit comments

Comments
 (0)