Skip to content

Commit f623cf8

Browse files
authored
Merge pull request libgit2#4163 from pks-t/pks/submodules-with-worktrees
Worktree fixes
2 parents 6fd6c67 + b0c9bc9 commit f623cf8

File tree

10 files changed

+300
-135
lines changed

10 files changed

+300
-135
lines changed

include/git2/worktree.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ GIT_EXTERN(int) git_worktree_list(git_strarray *out, git_repository *repo);
4343
*/
4444
GIT_EXTERN(int) git_worktree_lookup(git_worktree **out, git_repository *repo, const char *name);
4545

46+
/**
47+
* Open a worktree of a given repository
48+
*
49+
* If a repository is not the main tree but a worktree, this
50+
* function will look up the worktree inside the parent
51+
* repository and create a new `git_worktree` structure.
52+
*
53+
* @param out Out-pointer for the newly allocated worktree
54+
* @param repo Repository to look up worktree for
55+
*/
56+
GIT_EXTERN(int) git_worktree_open_from_repository(git_worktree **out, git_repository *repo);
57+
4658
/**
4759
* Free a previously allocated worktree
4860
*

src/refdb_fs.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,7 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *
739739
{
740740
int error, filebuf_flags;
741741
git_buf ref_path = GIT_BUF_INIT;
742+
const char *basedir;
742743

743744
assert(file && backend && name);
744745

@@ -747,13 +748,18 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *
747748
return GIT_EINVALIDSPEC;
748749
}
749750

751+
if (is_per_worktree_ref(name))
752+
basedir = backend->gitpath;
753+
else
754+
basedir = backend->commonpath;
755+
750756
/* Remove a possibly existing empty directory hierarchy
751757
* which name would collide with the reference name
752758
*/
753-
if ((error = git_futils_rmdir_r(name, backend->gitpath, GIT_RMDIR_SKIP_NONEMPTY)) < 0)
759+
if ((error = git_futils_rmdir_r(name, basedir, GIT_RMDIR_SKIP_NONEMPTY)) < 0)
754760
return error;
755761

756-
if (git_buf_joinpath(&ref_path, backend->gitpath, name) < 0)
762+
if (git_buf_joinpath(&ref_path, basedir, name) < 0)
757763
return -1;
758764

759765
filebuf_flags = GIT_FILEBUF_FORCE;

src/submodule.c

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "iterator.h"
2323
#include "path.h"
2424
#include "index.h"
25+
#include "worktree.h"
2526

2627
#define GIT_MODULES_FILE ".gitmodules"
2728

@@ -2038,17 +2039,28 @@ static int lookup_default_remote(git_remote **remote, git_repository *repo)
20382039
static int get_url_base(git_buf *url, git_repository *repo)
20392040
{
20402041
int error;
2042+
git_worktree *wt = NULL;
20412043
git_remote *remote = NULL;
20422044

2043-
if (!(error = lookup_default_remote(&remote, repo))) {
2045+
if ((error = lookup_default_remote(&remote, repo)) == 0) {
20442046
error = git_buf_sets(url, git_remote_url(remote));
2045-
git_remote_free(remote);
2046-
}
2047-
else if (error == GIT_ENOTFOUND) {
2048-
/* if repository does not have a default remote, use workdir instead */
2047+
goto out;
2048+
} else if (error != GIT_ENOTFOUND)
2049+
goto out;
2050+
else
20492051
giterr_clear();
2052+
2053+
/* if repository does not have a default remote, use workdir instead */
2054+
if (git_repository_is_worktree(repo)) {
2055+
if ((error = git_worktree_open_from_repository(&wt, repo)) < 0)
2056+
goto out;
2057+
error = git_buf_sets(url, wt->parent_path);
2058+
} else
20502059
error = git_buf_sets(url, git_repository_workdir(repo));
2051-
}
2060+
2061+
out:
2062+
git_remote_free(remote);
2063+
git_worktree_free(wt);
20522064

20532065
return error;
20542066
}

src/worktree.c

Lines changed: 108 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,20 @@
1414
#include "repository.h"
1515
#include "worktree.h"
1616

17-
static bool is_worktree_dir(git_buf *dir)
17+
static bool is_worktree_dir(const char *dir)
1818
{
19-
return git_path_contains_file(dir, "commondir")
20-
&& git_path_contains_file(dir, "gitdir")
21-
&& git_path_contains_file(dir, "HEAD");
19+
git_buf buf = GIT_BUF_INIT;
20+
int error;
21+
22+
if (git_buf_sets(&buf, dir) < 0)
23+
return -1;
24+
25+
error = git_path_contains_file(&buf, "commondir")
26+
&& git_path_contains_file(&buf, "gitdir")
27+
&& git_path_contains_file(&buf, "HEAD");
28+
29+
git_buf_free(&buf);
30+
return error;
2231
}
2332

2433
int git_worktree_list(git_strarray *wts, git_repository *repo)
@@ -47,7 +56,7 @@ int git_worktree_list(git_strarray *wts, git_repository *repo)
4756
git_buf_truncate(&path, len);
4857
git_buf_puts(&path, worktree);
4958

50-
if (!is_worktree_dir(&path)) {
59+
if (!is_worktree_dir(path.ptr)) {
5160
git_vector_remove(&worktrees, i);
5261
git__free(worktree);
5362
}
@@ -112,6 +121,46 @@ static int write_wtfile(const char *base, const char *file, const git_buf *buf)
112121
return err;
113122
}
114123

124+
static int open_worktree_dir(git_worktree **out, const char *parent, const char *dir, const char *name)
125+
{
126+
git_buf gitdir = GIT_BUF_INIT;
127+
git_worktree *wt = NULL;
128+
int error = 0;
129+
130+
if (!is_worktree_dir(dir)) {
131+
error = -1;
132+
goto out;
133+
}
134+
135+
if ((wt = git__calloc(1, sizeof(struct git_repository))) == NULL) {
136+
error = -1;
137+
goto out;
138+
}
139+
140+
if ((wt->name = git__strdup(name)) == NULL
141+
|| (wt->commondir_path = git_worktree__read_link(dir, "commondir")) == NULL
142+
|| (wt->gitlink_path = git_worktree__read_link(dir, "gitdir")) == NULL
143+
|| (wt->parent_path = git__strdup(parent)) == NULL) {
144+
error = -1;
145+
goto out;
146+
}
147+
148+
if ((error = git_path_prettify_dir(&gitdir, dir, NULL)) < 0)
149+
goto out;
150+
wt->gitdir_path = git_buf_detach(&gitdir);
151+
152+
wt->locked = !!git_worktree_is_locked(NULL, wt);
153+
154+
*out = wt;
155+
156+
out:
157+
if (error)
158+
git_worktree_free(wt);
159+
git_buf_free(&gitdir);
160+
161+
return error;
162+
}
163+
115164
int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *name)
116165
{
117166
git_buf path = GIT_BUF_INIT;
@@ -125,33 +174,47 @@ int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *na
125174
if ((error = git_buf_printf(&path, "%s/worktrees/%s", repo->commondir, name)) < 0)
126175
goto out;
127176

128-
if (!is_worktree_dir(&path)) {
129-
error = -1;
177+
if ((error = (open_worktree_dir(out, git_repository_workdir(repo), path.ptr, name))) < 0)
130178
goto out;
131-
}
132179

133-
if ((wt = git__malloc(sizeof(struct git_repository))) == NULL) {
180+
out:
181+
git_buf_free(&path);
182+
183+
if (error)
184+
git_worktree_free(wt);
185+
186+
return error;
187+
}
188+
189+
int git_worktree_open_from_repository(git_worktree **out, git_repository *repo)
190+
{
191+
git_buf parent = GIT_BUF_INIT;
192+
const char *gitdir, *commondir;
193+
char *name = NULL;
194+
int error = 0;
195+
196+
if (!git_repository_is_worktree(repo)) {
197+
giterr_set(GITERR_WORKTREE, "cannot open worktree of a non-worktree repo");
134198
error = -1;
135199
goto out;
136200
}
137201

138-
if ((wt->name = git__strdup(name)) == NULL
139-
|| (wt->commondir_path = git_worktree__read_link(path.ptr, "commondir")) == NULL
140-
|| (wt->gitlink_path = git_worktree__read_link(path.ptr, "gitdir")) == NULL
141-
|| (wt->parent_path = git__strdup(git_repository_path(repo))) == NULL) {
142-
error = -1;
202+
gitdir = git_repository_path(repo);
203+
commondir = git_repository_commondir(repo);
204+
205+
if ((error = git_path_prettify_dir(&parent, "..", commondir)) < 0)
143206
goto out;
144-
}
145-
wt->gitdir_path = git_buf_detach(&path);
146-
wt->locked = !!git_worktree_is_locked(NULL, wt);
147207

148-
(*out) = wt;
208+
/* The name is defined by the last component in '.git/worktree/%s' */
209+
name = git_path_basename(gitdir);
149210

150-
out:
151-
git_buf_free(&path);
211+
if ((error = open_worktree_dir(out, parent.ptr, gitdir, name)) < 0)
212+
goto out;
152213

214+
out:
153215
if (error)
154-
git_worktree_free(wt);
216+
free(name);
217+
git_buf_free(&parent);
155218

156219
return error;
157220
}
@@ -177,7 +240,7 @@ int git_worktree_validate(const git_worktree *wt)
177240
assert(wt);
178241

179242
git_buf_puts(&buf, wt->gitdir_path);
180-
if (!is_worktree_dir(&buf)) {
243+
if (!is_worktree_dir(buf.ptr)) {
181244
giterr_set(GITERR_WORKTREE,
182245
"Worktree gitdir ('%s') is not valid",
183246
wt->gitlink_path);
@@ -209,7 +272,7 @@ int git_worktree_validate(const git_worktree *wt)
209272

210273
int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *worktree)
211274
{
212-
git_buf path = GIT_BUF_INIT, buf = GIT_BUF_INIT;
275+
git_buf gitdir = GIT_BUF_INIT, wddir = GIT_BUF_INIT, buf = GIT_BUF_INIT;
213276
git_reference *ref = NULL, *head = NULL;
214277
git_commit *commit = NULL;
215278
git_repository *wt = NULL;
@@ -220,35 +283,39 @@ int git_worktree_add(git_worktree **out, git_repository *repo, const char *name,
220283

221284
*out = NULL;
222285

223-
/* Create worktree related files in commondir */
224-
if ((err = git_buf_joinpath(&path, repo->commondir, "worktrees")) < 0)
286+
/* Create gitdir directory ".git/worktrees/<name>" */
287+
if ((err = git_buf_joinpath(&gitdir, repo->commondir, "worktrees")) < 0)
225288
goto out;
226-
if (!git_path_exists(path.ptr))
227-
if ((err = git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_EXCL)) < 0)
289+
if (!git_path_exists(gitdir.ptr))
290+
if ((err = git_futils_mkdir(gitdir.ptr, 0755, GIT_MKDIR_EXCL)) < 0)
228291
goto out;
229-
if ((err = git_buf_joinpath(&path, path.ptr, name)) < 0)
292+
if ((err = git_buf_joinpath(&gitdir, gitdir.ptr, name)) < 0)
293+
goto out;
294+
if ((err = git_futils_mkdir(gitdir.ptr, 0755, GIT_MKDIR_EXCL)) < 0)
230295
goto out;
231-
if ((err = git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_EXCL)) < 0)
296+
if ((err = git_path_prettify_dir(&gitdir, gitdir.ptr, NULL)) < 0)
232297
goto out;
233298

234299
/* Create worktree work dir */
235300
if ((err = git_futils_mkdir(worktree, 0755, GIT_MKDIR_EXCL)) < 0)
236301
goto out;
302+
if ((err = git_path_prettify_dir(&wddir, worktree, NULL)) < 0)
303+
goto out;
237304

238305
/* Create worktree .git file */
239-
if ((err = git_buf_printf(&buf, "gitdir: %s\n", path.ptr)) < 0)
306+
if ((err = git_buf_printf(&buf, "gitdir: %s\n", gitdir.ptr)) < 0)
240307
goto out;
241-
if ((err = write_wtfile(worktree, ".git", &buf)) < 0)
308+
if ((err = write_wtfile(wddir.ptr, ".git", &buf)) < 0)
242309
goto out;
243310

244-
/* Create commondir files */
245-
if ((err = git_buf_sets(&buf, repo->commondir)) < 0
311+
/* Create gitdir files */
312+
if ((err = git_path_prettify_dir(&buf, repo->commondir, NULL) < 0)
246313
|| (err = git_buf_putc(&buf, '\n')) < 0
247-
|| (err = write_wtfile(path.ptr, "commondir", &buf)) < 0)
314+
|| (err = write_wtfile(gitdir.ptr, "commondir", &buf)) < 0)
248315
goto out;
249-
if ((err = git_buf_joinpath(&buf, worktree, ".git")) < 0
316+
if ((err = git_buf_joinpath(&buf, wddir.ptr, ".git")) < 0
250317
|| (err = git_buf_putc(&buf, '\n')) < 0
251-
|| (err = write_wtfile(path.ptr, "gitdir", &buf)) < 0)
318+
|| (err = write_wtfile(gitdir.ptr, "gitdir", &buf)) < 0)
252319
goto out;
253320

254321
/* Create new branch */
@@ -260,9 +327,9 @@ int git_worktree_add(git_worktree **out, git_repository *repo, const char *name,
260327
goto out;
261328

262329
/* Set worktree's HEAD */
263-
if ((err = git_repository_create_head(path.ptr, name)) < 0)
330+
if ((err = git_repository_create_head(gitdir.ptr, git_reference_name(ref))) < 0)
264331
goto out;
265-
if ((err = git_repository_open(&wt, worktree)) < 0)
332+
if ((err = git_repository_open(&wt, wddir.ptr)) < 0)
266333
goto out;
267334

268335
/* Checkout worktree's HEAD */
@@ -275,7 +342,8 @@ int git_worktree_add(git_worktree **out, git_repository *repo, const char *name,
275342
goto out;
276343

277344
out:
278-
git_buf_free(&path);
345+
git_buf_free(&gitdir);
346+
git_buf_free(&wddir);
279347
git_buf_free(&buf);
280348
git_reference_free(ref);
281349
git_reference_free(head);
@@ -394,7 +462,7 @@ int git_worktree_prune(git_worktree *wt, unsigned flags)
394462
}
395463

396464
/* Delete gitdir in parent repository */
397-
if ((err = git_buf_printf(&path, "%s/worktrees/%s", wt->parent_path, wt->name)) < 0)
465+
if ((err = git_buf_printf(&path, "%s/worktrees/%s", wt->commondir_path, wt->name)) < 0)
398466
goto out;
399467
if (!git_path_exists(path.ptr))
400468
{

src/worktree.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ struct git_worktree {
2424
/* Path to the common directory contained in the parent
2525
* repository */
2626
char *commondir_path;
27-
/* Path to the parent's .git directory */
27+
/* Path to the parent's working directory */
2828
char *parent_path;
2929

3030
int locked:1;

tests/resources/submodules/testrepo/.gitted/config

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44
bare = false
55
logallrefupdates = true
66
ignorecase = true
7-
[remote "origin"]
8-
fetch = +refs/heads/*:refs/remotes/origin/*
9-
url = /Users/rb/src/libgit2/tests/resources/testrepo.git
107
[branch "master"]
118
remote = origin
129
merge = refs/heads/master

0 commit comments

Comments
 (0)