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 <cdwhite3@pm.me> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Caleb White committed
Nov 29, 2024 at 22:23 UTC
e6df1ee2c13405ef7077256fef49424f69d61125
5 files changed
+76
-33
Documentation/git-worktree.txt
+3
@@ -220,6 +220,9 @@ This can also be set up as the default behaviour by using the
220
Link worktrees using relative paths or absolute paths (default).
221
Overrides the `worktree.useRelativePaths` config option, see
222
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.
226
227
--[no-]track::
228
When creating a new branch, if `<commit-ish>` is a branch,
builtin/worktree.c
+4
-2
@@ -1385,6 +1385,8 @@ static int repair(int ac, const char **av, const char *prefix)
1385
const char **p;
1386
const char *self[] = { ".", NULL };
1387
struct option options[] = {
1388
+ OPT_BOOL(0, "relative-paths", &use_relative_paths,
1389
+ N_("use relative paths for worktrees")),
1390
OPT_END()
1391
};
1392
int rc = 0;
@@ -1392,8 +1394,8 @@ static int repair(int ac, const char **av, const char *prefix)
1394
ac = parse_options(ac, av, prefix, options, git_worktree_repair_usage, 0);
1395
p = ac > 0 ? av : self;
1396
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);
1399
return rc;
1400
}
1401
t/t2406-worktree-repair.sh
+39
@@ -216,4 +216,43 @@ test_expect_success 'repair copied main and linked worktrees' '
216
test_cmp dup/linked.expect dup/linked/.git
217
'
218
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
+
258
test_done
worktree.c
+27
-29
@@ -573,12 +573,13 @@ int other_head_refs(each_ref_fn fn, void *cb_data)
573
* pointing at <repo>/worktrees/<id>.
574
*/
575
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)
578
{
579
struct strbuf dotgit = STRBUF_INIT;
580
+ struct strbuf gitdir = STRBUF_INIT;
581
struct strbuf repo = STRBUF_INIT;
582
struct strbuf backlink = STRBUF_INIT;
581
- struct strbuf tmp = STRBUF_INIT;
583
char *dotgit_contents = NULL;
584
const char *repair = NULL;
585
int err;
@@ -594,6 +595,7 @@ static void repair_gitfile(struct worktree *wt,
595
596
strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1);
597
strbuf_addf(&dotgit, "%s/.git", wt->path);
598
+ strbuf_addf(&gitdir, "%s/gitdir", repo.buf);
599
dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
600
601
if (dotgit_contents) {
@@ -611,18 +613,20 @@ static void repair_gitfile(struct worktree *wt,
613
repair = _(".git file broken");
614
else if (fspathcmp(backlink.buf, repo.buf))
615
repair = _(".git file incorrect");
616
+ else if (use_relative_paths == is_absolute_path(dotgit_contents))
617
+ repair = _(".git file absolute/relative path mismatch");
618
619
if (repair) {
620
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);
622
}
623
624
done:
625
free(dotgit_contents);
626
strbuf_release(&repo);
627
strbuf_release(&dotgit);
628
+ strbuf_release(&gitdir);
629
strbuf_release(&backlink);
625
- strbuf_release(&tmp);
630
}
631
632
static void repair_noop(int iserr UNUSED,
@@ -633,7 +637,7 @@ static void repair_noop(int iserr UNUSED,
637
/* nothing */
638
}
639
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)
641
{
642
struct worktree **worktrees = get_worktrees_internal(1);
643
struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
@@ -641,7 +645,7 @@ void repair_worktrees(worktree_repair_fn fn, void *cb_data)
645
if (!fn)
646
fn = repair_noop;
647
for (; *wt; wt++)
644
- repair_gitfile(*wt, fn, cb_data);
648
+ repair_gitfile(*wt, fn, cb_data, use_relative_paths);
649
free_worktrees(worktrees);
650
}
651
@@ -757,16 +761,14 @@ error:
761
* the worktree's path.
762
*/
763
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)
766
{
767
struct strbuf dotgit = STRBUF_INIT;
763
- struct strbuf realdotgit = STRBUF_INIT;
768
struct strbuf backlink = STRBUF_INIT;
769
struct strbuf inferred_backlink = STRBUF_INIT;
770
struct strbuf gitdir = STRBUF_INIT;
771
struct strbuf olddotgit = STRBUF_INIT;
768
- struct strbuf realolddotgit = STRBUF_INIT;
769
- struct strbuf tmp = STRBUF_INIT;
772
char *dotgit_contents = NULL;
773
const char *repair = NULL;
774
int err;
@@ -778,25 +780,25 @@ void repair_worktree_at_path(const char *path,
780
goto done;
781
782
strbuf_addf(&dotgit, "%s/.git", path);
781
- if (!strbuf_realpath(&realdotgit, dotgit.buf, 0)) {
783
+ if (!strbuf_realpath(&dotgit, dotgit.buf, 0)) {
784
fn(1, path, _("not a valid path"), cb_data);
785
goto done;
786
}
787
786
- infer_backlink(realdotgit.buf, &inferred_backlink);
788
+ infer_backlink(dotgit.buf, &inferred_backlink);
789
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));
791
if (dotgit_contents) {
792
if (is_absolute_path(dotgit_contents)) {
793
strbuf_addstr(&backlink, dotgit_contents);
794
} else {
793
- strbuf_addbuf(&backlink, &realdotgit);
795
+ strbuf_addbuf(&backlink, &dotgit);
796
strbuf_strip_suffix(&backlink, ".git");
797
strbuf_addstr(&backlink, dotgit_contents);
798
strbuf_realpath_forgiving(&backlink, backlink.buf, 0);
799
}
800
} 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);
802
goto done;
803
} else if (err == READ_GITFILE_ERR_NOT_A_REPO) {
804
if (inferred_backlink.len) {
@@ -809,11 +811,11 @@ void repair_worktree_at_path(const char *path,
811
*/
812
strbuf_swap(&backlink, &inferred_backlink);
813
} 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);
815
goto done;
816
}
817
} 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);
819
goto done;
820
}
821
@@ -835,39 +837,35 @@ void repair_worktree_at_path(const char *path,
837
* in the "copy" repository. In this case, point the "copy" worktree's
838
* .git file at the "copy" repository.
839
*/
838
- if (inferred_backlink.len && fspathcmp(backlink.buf, inferred_backlink.buf)) {
840
+ if (inferred_backlink.len && fspathcmp(backlink.buf, inferred_backlink.buf))
841
strbuf_swap(&backlink, &inferred_backlink);
840
- }
842
843
strbuf_addf(&gitdir, "%s/gitdir", backlink.buf);
844
if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0)
845
repair = _("gitdir unreadable");
846
+ else if (use_relative_paths == is_absolute_path(olddotgit.buf))
847
+ repair = _("gitdir absolute/relative path mismatch");
848
else {
849
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);
853
}
853
- if (fspathcmp(realolddotgit.buf, realdotgit.buf))
854
+ if (fspathcmp(olddotgit.buf, dotgit.buf))
855
repair = _("gitdir incorrect");
856
}
857
858
if (repair) {
859
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);
861
}
862
done:
863
free(dotgit_contents);
864
strbuf_release(&olddotgit);
864
- strbuf_release(&realolddotgit);
865
strbuf_release(&backlink);
866
strbuf_release(&inferred_backlink);
867
strbuf_release(&gitdir);
868
- strbuf_release(&realdotgit);
868
strbuf_release(&dotgit);
870
- strbuf_release(&tmp);
869
}
870
871
int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, timestamp_t expire)
worktree.h
+3
-2
@@ -129,7 +129,7 @@ typedef void (* worktree_repair_fn)(int iserr, const char *path,
129
* function, if non-NULL, is called with the path of the worktree and a
130
* description of the repair or error, along with the callback user-data.
131
*/
132
-void repair_worktrees(worktree_repair_fn, void *cb_data);
132
+void repair_worktrees(worktree_repair_fn, void *cb_data, int use_relative_paths);
133
134
/*
135
* 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
151
* worktree and a description of the repair or error, along with the callback
152
* user-data.
153
*/
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);
156
157
/*
158
* Free up the memory for a worktree.