Skip to content

Commit 30e9e41

Browse files
calebdwttaylorr
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. Signed-off-by: Caleb White <[email protected]> Signed-off-by: Taylor Blau <[email protected]>
1 parent 34401d6 commit 30e9e41

File tree

5 files changed

+65
-33
lines changed

5 files changed

+65
-33
lines changed

Documentation/git-worktree.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,9 @@ This can also be set up as the default behaviour by using the
219219
--[no-]relative-paths::
220220
Overrides the `worktree.useRelativePaths` config option, see
221221
linkgit:git-config[1].
222+
+
223+
With `repair`, the linking files will be updated if there's an absolute/relative
224+
mismatch, even if the links are correct.
222225

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

builtin/worktree.c

Lines changed: 4 additions & 2 deletions
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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,30 @@ 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 absolute worktree to use relative paths' '
220+
test_when_finished "rm -rf main side sidemoved" &&
221+
test_create_repo main &&
222+
test_commit -C main init &&
223+
git -C main worktree add --detach ../side &&
224+
echo "../../../../sidemoved/.git" >expect-gitdir &&
225+
echo "gitdir: ../main/.git/worktrees/side" >expect-gitfile &&
226+
mv side sidemoved &&
227+
git -C main worktree repair --relative-paths ../sidemoved &&
228+
test_cmp expect-gitdir main/.git/worktrees/side/gitdir &&
229+
test_cmp expect-gitfile sidemoved/.git
230+
'
231+
232+
test_expect_success 'repair relative worktree to use absolute 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 --relative-paths --detach ../side &&
237+
echo "$(pwd)/sidemoved/.git" >expect-gitdir &&
238+
echo "gitdir: $(pwd)/main/.git/worktrees/side" >expect-gitfile &&
239+
mv side sidemoved &&
240+
git -C main worktree repair ../sidemoved &&
241+
test_cmp expect-gitdir main/.git/worktrees/side/gitdir &&
242+
test_cmp expect-gitfile sidemoved/.git
243+
'
244+
219245
test_done

worktree.c

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -574,12 +574,14 @@ int other_head_refs(each_ref_fn fn, void *cb_data)
574574
* pointing at <repo>/worktrees/<id>.
575575
*/
576576
static void repair_gitfile(struct worktree *wt,
577-
worktree_repair_fn fn, void *cb_data)
577+
worktree_repair_fn fn,
578+
void *cb_data,
579+
int use_relative_paths)
578580
{
579581
struct strbuf dotgit = STRBUF_INIT;
582+
struct strbuf gitdir = STRBUF_INIT;
580583
struct strbuf repo = STRBUF_INIT;
581584
struct strbuf backlink = STRBUF_INIT;
582-
struct strbuf tmp = STRBUF_INIT;
583585
char *dotgit_contents = NULL;
584586
const char *repair = NULL;
585587
int err;
@@ -595,6 +597,7 @@ static void repair_gitfile(struct worktree *wt,
595597

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

600603
if (dotgit_contents) {
@@ -612,18 +615,20 @@ static void repair_gitfile(struct worktree *wt,
612615
repair = _(".git file broken");
613616
else if (fspathcmp(backlink.buf, repo.buf))
614617
repair = _(".git file incorrect");
618+
else if (use_relative_paths == is_absolute_path(dotgit_contents))
619+
repair = _(".git file absolute/relative path mismatch");
615620

616621
if (repair) {
617622
fn(0, wt->path, repair, cb_data);
618-
write_file(dotgit.buf, "gitdir: %s", relative_path(repo.buf, wt->path, &tmp));
623+
write_worktree_linking_files(dotgit, gitdir, use_relative_paths);
619624
}
620625

621626
done:
622627
free(dotgit_contents);
623628
strbuf_release(&repo);
624629
strbuf_release(&dotgit);
630+
strbuf_release(&gitdir);
625631
strbuf_release(&backlink);
626-
strbuf_release(&tmp);
627632
}
628633

629634
static void repair_noop(int iserr UNUSED,
@@ -634,15 +639,15 @@ static void repair_noop(int iserr UNUSED,
634639
/* nothing */
635640
}
636641

637-
void repair_worktrees(worktree_repair_fn fn, void *cb_data)
642+
void repair_worktrees(worktree_repair_fn fn, void *cb_data, int use_relative_paths)
638643
{
639644
struct worktree **worktrees = get_worktrees_internal(1);
640645
struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
641646

642647
if (!fn)
643648
fn = repair_noop;
644649
for (; *wt; wt++)
645-
repair_gitfile(*wt, fn, cb_data);
650+
repair_gitfile(*wt, fn, cb_data, use_relative_paths);
646651
free_worktrees(worktrees);
647652
}
648653

@@ -758,16 +763,15 @@ static ssize_t infer_backlink(const char *gitfile, struct strbuf *inferred)
758763
* the worktree's path.
759764
*/
760765
void repair_worktree_at_path(const char *path,
761-
worktree_repair_fn fn, void *cb_data)
766+
worktree_repair_fn fn,
767+
void *cb_data,
768+
int use_relative_paths)
762769
{
763770
struct strbuf dotgit = STRBUF_INIT;
764-
struct strbuf realdotgit = STRBUF_INIT;
765771
struct strbuf backlink = STRBUF_INIT;
766772
struct strbuf inferred_backlink = STRBUF_INIT;
767773
struct strbuf gitdir = STRBUF_INIT;
768774
struct strbuf olddotgit = STRBUF_INIT;
769-
struct strbuf realolddotgit = STRBUF_INIT;
770-
struct strbuf tmp = STRBUF_INIT;
771775
char *dotgit_contents = NULL;
772776
const char *repair = NULL;
773777
int err;
@@ -779,25 +783,25 @@ void repair_worktree_at_path(const char *path,
779783
goto done;
780784

781785
strbuf_addf(&dotgit, "%s/.git", path);
782-
if (!strbuf_realpath(&realdotgit, dotgit.buf, 0)) {
786+
if (!strbuf_realpath(&dotgit, dotgit.buf, 0)) {
783787
fn(1, path, _("not a valid path"), cb_data);
784788
goto done;
785789
}
786790

787-
infer_backlink(realdotgit.buf, &inferred_backlink);
791+
infer_backlink(dotgit.buf, &inferred_backlink);
788792
strbuf_realpath_forgiving(&inferred_backlink, inferred_backlink.buf, 0);
789-
dotgit_contents = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err));
793+
dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
790794
if (dotgit_contents) {
791795
if (is_absolute_path(dotgit_contents)) {
792796
strbuf_addstr(&backlink, dotgit_contents);
793797
} else {
794-
strbuf_addbuf(&backlink, &realdotgit);
798+
strbuf_addbuf(&backlink, &dotgit);
795799
strbuf_strip_suffix(&backlink, ".git");
796800
strbuf_addstr(&backlink, dotgit_contents);
797801
strbuf_realpath_forgiving(&backlink, backlink.buf, 0);
798802
}
799803
} else if (err == READ_GITFILE_ERR_NOT_A_FILE) {
800-
fn(1, realdotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);
804+
fn(1, dotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);
801805
goto done;
802806
} else if (err == READ_GITFILE_ERR_NOT_A_REPO) {
803807
if (inferred_backlink.len) {
@@ -810,11 +814,11 @@ void repair_worktree_at_path(const char *path,
810814
*/
811815
strbuf_swap(&backlink, &inferred_backlink);
812816
} else {
813-
fn(1, realdotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data);
817+
fn(1, dotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data);
814818
goto done;
815819
}
816820
} else {
817-
fn(1, realdotgit.buf, _("unable to locate repository; .git file broken"), cb_data);
821+
fn(1, dotgit.buf, _("unable to locate repository; .git file broken"), cb_data);
818822
goto done;
819823
}
820824

@@ -836,39 +840,35 @@ void repair_worktree_at_path(const char *path,
836840
* in the "copy" repository. In this case, point the "copy" worktree's
837841
* .git file at the "copy" repository.
838842
*/
839-
if (inferred_backlink.len && fspathcmp(backlink.buf, inferred_backlink.buf)) {
843+
if (inferred_backlink.len && fspathcmp(backlink.buf, inferred_backlink.buf))
840844
strbuf_swap(&backlink, &inferred_backlink);
841-
}
842845

843846
strbuf_addf(&gitdir, "%s/gitdir", backlink.buf);
844847
if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0)
845848
repair = _("gitdir unreadable");
849+
else if (use_relative_paths == is_absolute_path(olddotgit.buf))
850+
repair = _("gitdir absolute/relative path mismatch");
846851
else {
847852
strbuf_rtrim(&olddotgit);
848-
if (is_absolute_path(olddotgit.buf)) {
849-
strbuf_addbuf(&realolddotgit, &olddotgit);
850-
} else {
851-
strbuf_addf(&realolddotgit, "%s/%s", backlink.buf, olddotgit.buf);
852-
strbuf_realpath_forgiving(&realolddotgit, realolddotgit.buf, 0);
853+
if (!is_absolute_path(olddotgit.buf)) {
854+
strbuf_insertf(&olddotgit, 0, "%s/", backlink.buf);
855+
strbuf_realpath_forgiving(&olddotgit, olddotgit.buf, 0);
853856
}
854-
if (fspathcmp(realolddotgit.buf, realdotgit.buf))
857+
if (fspathcmp(olddotgit.buf, dotgit.buf))
855858
repair = _("gitdir incorrect");
856859
}
857860

858861
if (repair) {
859862
fn(0, gitdir.buf, repair, cb_data);
860-
write_file(gitdir.buf, "%s", relative_path(realdotgit.buf, backlink.buf, &tmp));
863+
write_worktree_linking_files(dotgit, gitdir, use_relative_paths);
861864
}
862865
done:
863866
free(dotgit_contents);
864867
strbuf_release(&olddotgit);
865-
strbuf_release(&realolddotgit);
866868
strbuf_release(&backlink);
867869
strbuf_release(&inferred_backlink);
868870
strbuf_release(&gitdir);
869-
strbuf_release(&realdotgit);
870871
strbuf_release(&dotgit);
871-
strbuf_release(&tmp);
872872
}
873873

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

worktree.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ typedef void (* worktree_repair_fn)(int iserr, const char *path,
130130
* function, if non-NULL, is called with the path of the worktree and a
131131
* description of the repair or error, along with the callback user-data.
132132
*/
133-
void repair_worktrees(worktree_repair_fn, void *cb_data);
133+
void repair_worktrees(worktree_repair_fn, void *cb_data, int use_relative_paths);
134134

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

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

0 commit comments

Comments
 (0)