2023-03-21 06:26:06 +00:00
|
|
|
#include "git-compat-util.h"
|
2023-03-21 06:25:58 +00:00
|
|
|
#include "abspath.h"
|
2023-03-21 06:26:03 +00:00
|
|
|
#include "environment.h"
|
2023-03-21 06:25:54 +00:00
|
|
|
#include "gettext.h"
|
2023-05-16 06:33:59 +00:00
|
|
|
#include "path.h"
|
2017-06-22 18:43:37 +00:00
|
|
|
#include "repository.h"
|
2015-10-02 11:55:31 +00:00
|
|
|
#include "refs.h"
|
2023-03-21 06:26:05 +00:00
|
|
|
#include "setup.h"
|
2015-10-02 11:55:31 +00:00
|
|
|
#include "strbuf.h"
|
|
|
|
#include "worktree.h"
|
2016-04-22 13:01:28 +00:00
|
|
|
#include "dir.h"
|
2016-04-22 13:01:33 +00:00
|
|
|
#include "wt-status.h"
|
2022-02-07 21:32:59 +00:00
|
|
|
#include "config.h"
|
2015-10-02 11:55:31 +00:00
|
|
|
|
2024-01-08 10:05:43 +00:00
|
|
|
void free_worktree(struct worktree *worktree)
|
|
|
|
{
|
|
|
|
if (!worktree)
|
|
|
|
return;
|
|
|
|
free(worktree->path);
|
|
|
|
free(worktree->id);
|
|
|
|
free(worktree->head_ref);
|
|
|
|
free(worktree->lock_reason);
|
|
|
|
free(worktree->prune_reason);
|
|
|
|
free(worktree);
|
|
|
|
}
|
|
|
|
|
2015-10-08 17:01:03 +00:00
|
|
|
void free_worktrees(struct worktree **worktrees)
|
|
|
|
{
|
|
|
|
int i = 0;
|
2024-01-08 10:05:43 +00:00
|
|
|
for (i = 0; worktrees[i]; i++)
|
|
|
|
free_worktree(worktrees[i]);
|
2015-10-08 17:01:03 +00:00
|
|
|
free (worktrees);
|
|
|
|
}
|
|
|
|
|
2015-10-08 17:01:04 +00:00
|
|
|
/**
|
2020-09-27 13:15:45 +00:00
|
|
|
* Update head_oid, head_ref and is_detached of the given worktree
|
2015-10-08 17:01:04 +00:00
|
|
|
*/
|
2017-04-24 10:01:23 +00:00
|
|
|
static void add_head_info(struct worktree *wt)
|
2015-10-08 17:01:04 +00:00
|
|
|
{
|
2017-04-24 10:01:23 +00:00
|
|
|
int flags;
|
|
|
|
const char *target;
|
|
|
|
|
2021-10-16 09:39:27 +00:00
|
|
|
target = refs_resolve_ref_unsafe(get_worktree_ref_store(wt),
|
2017-04-24 10:01:23 +00:00
|
|
|
"HEAD",
|
branch: fix branch renaming not updating HEADs correctly
There are two bugs that sort of work together and cause
problems. Let's start with one in replace_each_worktree_head_symref.
Before fa099d2322 (worktree.c: kill parse_ref() in favor of
refs_resolve_ref_unsafe() - 2017-04-24), this code looks like this:
if (strcmp(oldref, worktrees[i]->head_ref))
continue;
set_worktree_head_symref(...);
After fa099d2322, it is possible that head_ref can be NULL. However,
the updated code takes the wrong exit. In the error case (NULL
head_ref), we should "continue;" to the next worktree. The updated
code makes us _skip_ "continue;" and update HEAD anyway.
The NULL head_ref is triggered by the second bug in add_head_info (in
the same commit). With the flag RESOLVE_REF_READING, resolve_ref_unsafe()
will abort if it cannot resolve the target ref. For orphan checkouts,
HEAD always points to an unborned branch, resolving target ref will
always fail. Now we have NULL head_ref. Now we always update HEAD.
Correct the logic in replace_ function so that we don't accidentally
update HEAD on error. As it turns out, correcting the logic bug above
breaks branch renaming completely, thanks to the second bug.
"git branch -[Mm]" does two steps (on a normal checkout, no orphan!):
- rename the branch on disk (e.g. refs/heads/abc to refs/heads/def)
- update HEAD if it points to the branch being renamed.
At the second step, since the branch pointed to by HEAD (e.g. "abc") no
longer exists on disk, we run into a temporary orphan checkout situation
that has been just corrected to _not_ update HEAD. But we need to update
HEAD since it's not actually an orphan checkout. We need to update HEAD
to move out of that orphan state.
Correct add_head_info(), remove RESOLVE_REF_READING flag. With the flag
gone, we should always return good "head_ref" in orphan checkouts (either
temporary or permanent). With good head_ref, things start to work again.
Noticed-by: Nish Aravamudan <nish.aravamudan@canonical.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-08-24 10:41:24 +00:00
|
|
|
0,
|
2022-01-26 14:37:01 +00:00
|
|
|
&wt->head_oid, &flags);
|
2017-04-24 10:01:23 +00:00
|
|
|
if (!target)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (flags & REF_ISSYMREF)
|
|
|
|
wt->head_ref = xstrdup(target);
|
|
|
|
else
|
|
|
|
wt->is_detached = 1;
|
2015-10-08 17:01:04 +00:00
|
|
|
}
|
|
|
|
|
2015-10-08 17:01:03 +00:00
|
|
|
/**
|
|
|
|
* get the main worktree
|
|
|
|
*/
|
2023-12-29 07:26:30 +00:00
|
|
|
static struct worktree *get_main_worktree(int skip_reading_head)
|
2015-10-08 17:01:02 +00:00
|
|
|
{
|
2015-10-08 17:01:03 +00:00
|
|
|
struct worktree *worktree = NULL;
|
|
|
|
struct strbuf worktree_path = STRBUF_INIT;
|
2015-10-08 17:01:02 +00:00
|
|
|
|
worktree: retire special-case normalization of main worktree path
In order for "git-worktree list" to present consistent results,
get_main_worktree() performs manual normalization on the repository
path (returned by get_common_dir()) after passing it through
strbuf_add_absolute_path(). In particular, it cleans up the path for
three distinct cases when the current working directory is (1) the main
worktree, (2) the .git/ subdirectory, or (3) a bare repository.
The need for such special-cases is a direct consequence of employing
strbuf_add_absolute_path() which, for the sake of efficiency, doesn't
bother normalizing the path (such as folding out redundant path
components) after making it absolute. Lack of normalization is not
typically a problem since redundant path elements make no difference
when working with paths at the filesystem level. However, when preparing
paths for presentation, possible redundant path components make it
difficult to ensure consistency.
Eliminate the need for these special cases by instead making the path
absolute via strbuf_add_real_path() which normalizes the path for us.
Once normalized, the only case we need to handle manually is converting
it to the path of the main worktree by stripping the "/.git" suffix.
This stripping of the "/.git" suffix is a regular idiom in
worktree-related code; for instance, it is employed by
get_linked_worktree(), as well.
Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-07-31 23:32:14 +00:00
|
|
|
strbuf_add_real_path(&worktree_path, get_git_common_dir());
|
|
|
|
strbuf_strip_suffix(&worktree_path, "/.git");
|
2015-10-08 17:01:03 +00:00
|
|
|
|
2021-03-13 16:17:22 +00:00
|
|
|
CALLOC_ARRAY(worktree, 1);
|
2015-10-08 17:01:04 +00:00
|
|
|
worktree->path = strbuf_detach(&worktree_path, NULL);
|
worktree: update is_bare heuristics
When "git branch -D <name>" is run, Git usually first checks if that
branch is currently checked out. But this check is not performed if the
Git directory of that repository is not at "<repo>/.git", which is the
case if that repository is a submodule that has its Git directory stored
as "super/.git/modules/<repo>", for example. This results in the branch
being deleted even though it is checked out.
This is because get_main_worktree() in worktree.c sets is_bare on a
worktree only using the heuristic that a repo is bare if the worktree's
path does not end in "/.git", and not bare otherwise. This is_bare code
was introduced in 92718b7438 ("worktree: add details to the worktree
struct", 2015-10-08), following a pre-core.bare heuristic. This patch
does 2 things:
- Teach get_main_worktree() to use is_bare_repository() instead,
introduced in 7d1864ce67 ("Introduce is_bare_repository() and
core.bare configuration variable", 2007-01-07) and updated in
e90fdc39b6 ("Clean up work-tree handling", 2007-08-01). This solves
the "git branch -D <name>" problem described above. However...
- If a repository has core.bare=1 but the "git" command is being run
from one of its secondary worktrees, is_bare_repository() returns
false (which is fine, since there is a worktree available). However,
treating the main worktree as non-bare when it is bare causes issues:
for example, failure to delete a branch from a secondary worktree
that is referred to by a main worktree's HEAD, even if that main
worktree is bare.
In order to avoid that, also check core.bare when setting is_bare. If
core.bare=1, trust it, and otherwise, use is_bare_repository().
Signed-off-by: Jonathan Tan <jonathantanmy@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-19 17:21:28 +00:00
|
|
|
/*
|
|
|
|
* NEEDSWORK: If this function is called from a secondary worktree and
|
|
|
|
* config.worktree is present, is_bare_repository_cfg will reflect the
|
|
|
|
* contents of config.worktree, not the contents of the main worktree.
|
|
|
|
* This means that worktree->is_bare may be set to 0 even if the main
|
|
|
|
* worktree is configured to be bare.
|
|
|
|
*/
|
|
|
|
worktree->is_bare = (is_bare_repository_cfg == 1) ||
|
|
|
|
is_bare_repository();
|
2023-12-29 07:26:30 +00:00
|
|
|
if (!skip_reading_head)
|
|
|
|
add_head_info(worktree);
|
2015-10-08 17:01:03 +00:00
|
|
|
return worktree;
|
2015-10-08 17:01:02 +00:00
|
|
|
}
|
|
|
|
|
2024-01-08 10:05:43 +00:00
|
|
|
struct worktree *get_linked_worktree(const char *id,
|
|
|
|
int skip_reading_head)
|
2015-10-02 11:55:31 +00:00
|
|
|
{
|
2015-10-08 17:01:03 +00:00
|
|
|
struct worktree *worktree = NULL;
|
2015-10-02 11:55:31 +00:00
|
|
|
struct strbuf path = STRBUF_INIT;
|
2015-10-08 17:01:03 +00:00
|
|
|
struct strbuf worktree_path = STRBUF_INIT;
|
2015-10-02 11:55:31 +00:00
|
|
|
|
2015-10-08 17:01:02 +00:00
|
|
|
if (!id)
|
|
|
|
die("Missing linked worktree name");
|
2015-10-02 11:55:31 +00:00
|
|
|
|
2017-06-22 18:43:37 +00:00
|
|
|
strbuf_git_common_path(&path, the_repository, "worktrees/%s/gitdir", id);
|
2015-10-08 17:01:03 +00:00
|
|
|
if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)
|
|
|
|
/* invalid gitdir file */
|
2015-10-02 11:55:31 +00:00
|
|
|
goto done;
|
2015-10-08 17:01:03 +00:00
|
|
|
strbuf_rtrim(&worktree_path);
|
worktree: drop bogus and unnecessary path munging
The content of .git/worktrees/<id>/gitdir must be a path of the form
"/path/to/worktree/.git". Any other content would be indicative of a
corrupt "gitdir" file. To determine the path of the worktree itself one
merely strips the "/.git" suffix, and this is indeed how the worktree
path was determined from inception.
However, 5193490442 (worktree: add a function to get worktree details,
2015-10-08) extended the path manipulation in a mysterious way. If it is
unable to strip "/.git" from the path, then it instead reports the
current working directory as the linked worktree's path:
if (!strbuf_strip_suffix(&worktree_path, "/.git")) {
strbuf_reset(&worktree_path);
strbuf_add_absolute_path(&worktree_path, ".");
strbuf_strip_suffix(&worktree_path, "/.");
}
This logic is clearly bogus; it can never be generally correct behavior.
It materialized out of thin air in 5193490442 with neither explanation
nor tests to illustrate a case in which it would be desirable.
It's possible that this logic was introduced to somehow deal with a
corrupt "gitdir" file, so that it returns _some_ sort of meaningful
value, but returning the current working directory is not helpful. In
fact, it is quite misleading (except in the one specific case when the
current directory is the worktree whose "gitdir" entry is corrupt).
Moreover, reporting the corrupt value to the user, rather than fibbing
about it and hiding it outright, is more helpful since it may aid in
diagnosing the problem.
Therefore, drop this bogus path munging and restore the logic to the
original behavior of merely stripping "/.git".
Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-07-31 23:32:13 +00:00
|
|
|
strbuf_strip_suffix(&worktree_path, "/.git");
|
2015-10-08 17:01:03 +00:00
|
|
|
|
2021-03-13 16:17:22 +00:00
|
|
|
CALLOC_ARRAY(worktree, 1);
|
2015-10-08 17:01:04 +00:00
|
|
|
worktree->path = strbuf_detach(&worktree_path, NULL);
|
2016-04-22 13:01:26 +00:00
|
|
|
worktree->id = xstrdup(id);
|
2023-12-29 07:26:30 +00:00
|
|
|
if (!skip_reading_head)
|
|
|
|
add_head_info(worktree);
|
2015-10-02 11:55:31 +00:00
|
|
|
|
|
|
|
done:
|
|
|
|
strbuf_release(&path);
|
2015-10-08 17:01:03 +00:00
|
|
|
strbuf_release(&worktree_path);
|
|
|
|
return worktree;
|
2015-10-02 11:55:31 +00:00
|
|
|
}
|
|
|
|
|
2016-04-22 13:01:28 +00:00
|
|
|
static void mark_current_worktree(struct worktree **worktrees)
|
|
|
|
{
|
2017-01-26 17:54:23 +00:00
|
|
|
char *git_dir = absolute_pathdup(get_git_dir());
|
2016-04-22 13:01:28 +00:00
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; worktrees[i]; i++) {
|
|
|
|
struct worktree *wt = worktrees[i];
|
2016-05-22 09:33:52 +00:00
|
|
|
const char *wt_git_dir = get_worktree_git_dir(wt);
|
|
|
|
|
|
|
|
if (!fspathcmp(git_dir, absolute_path(wt_git_dir))) {
|
|
|
|
wt->is_current = 1;
|
2016-04-22 13:01:28 +00:00
|
|
|
break;
|
2016-05-22 09:33:52 +00:00
|
|
|
}
|
2016-04-22 13:01:28 +00:00
|
|
|
}
|
2016-05-22 09:33:52 +00:00
|
|
|
free(git_dir);
|
2016-04-22 13:01:28 +00:00
|
|
|
}
|
|
|
|
|
2023-12-29 07:26:30 +00:00
|
|
|
/*
|
|
|
|
* NEEDSWORK: This function exists so that we can look up metadata of a
|
|
|
|
* worktree without trying to access any of its internals like the refdb. It
|
|
|
|
* would be preferable to instead have a corruption-tolerant function for
|
|
|
|
* retrieving worktree metadata that could be used when the worktree is known
|
|
|
|
* to not be in a healthy state, e.g. when creating or repairing it.
|
|
|
|
*/
|
|
|
|
static struct worktree **get_worktrees_internal(int skip_reading_head)
|
2015-10-02 11:55:31 +00:00
|
|
|
{
|
2015-10-08 17:01:03 +00:00
|
|
|
struct worktree **list = NULL;
|
2015-10-02 11:55:31 +00:00
|
|
|
struct strbuf path = STRBUF_INIT;
|
|
|
|
DIR *dir;
|
|
|
|
struct dirent *d;
|
2015-10-08 17:01:03 +00:00
|
|
|
int counter = 0, alloc = 2;
|
|
|
|
|
2017-02-25 10:30:03 +00:00
|
|
|
ALLOC_ARRAY(list, alloc);
|
2015-10-02 11:55:31 +00:00
|
|
|
|
2023-12-29 07:26:30 +00:00
|
|
|
list[counter++] = get_main_worktree(skip_reading_head);
|
2015-10-02 11:55:31 +00:00
|
|
|
|
|
|
|
strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
|
|
|
|
dir = opendir(path.buf);
|
|
|
|
strbuf_release(&path);
|
2015-10-08 17:01:03 +00:00
|
|
|
if (dir) {
|
2021-05-12 17:28:22 +00:00
|
|
|
while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) {
|
2015-10-08 17:01:03 +00:00
|
|
|
struct worktree *linked = NULL;
|
2015-10-02 11:55:31 +00:00
|
|
|
|
2023-12-29 07:26:30 +00:00
|
|
|
if ((linked = get_linked_worktree(d->d_name, skip_reading_head))) {
|
2016-01-18 11:21:29 +00:00
|
|
|
ALLOC_GROW(list, counter + 1, alloc);
|
|
|
|
list[counter++] = linked;
|
|
|
|
}
|
2015-10-08 17:01:03 +00:00
|
|
|
}
|
|
|
|
closedir(dir);
|
|
|
|
}
|
|
|
|
ALLOC_GROW(list, counter + 1, alloc);
|
|
|
|
list[counter] = NULL;
|
2016-04-22 13:01:28 +00:00
|
|
|
|
|
|
|
mark_current_worktree(list);
|
2015-10-08 17:01:03 +00:00
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
2023-12-29 07:26:30 +00:00
|
|
|
struct worktree **get_worktrees(void)
|
|
|
|
{
|
|
|
|
return get_worktrees_internal(0);
|
|
|
|
}
|
|
|
|
|
2016-04-22 13:01:26 +00:00
|
|
|
const char *get_worktree_git_dir(const struct worktree *wt)
|
|
|
|
{
|
|
|
|
if (!wt)
|
|
|
|
return get_git_dir();
|
|
|
|
else if (!wt->id)
|
|
|
|
return get_git_common_dir();
|
|
|
|
else
|
|
|
|
return git_common_path("worktrees/%s", wt->id);
|
|
|
|
}
|
|
|
|
|
2016-06-13 12:18:26 +00:00
|
|
|
static struct worktree *find_worktree_by_suffix(struct worktree **list,
|
|
|
|
const char *suffix)
|
|
|
|
{
|
|
|
|
struct worktree *found = NULL;
|
|
|
|
int nr_found = 0, suffixlen;
|
|
|
|
|
|
|
|
suffixlen = strlen(suffix);
|
|
|
|
if (!suffixlen)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
for (; *list && nr_found < 2; list++) {
|
|
|
|
const char *path = (*list)->path;
|
|
|
|
int pathlen = strlen(path);
|
|
|
|
int start = pathlen - suffixlen;
|
|
|
|
|
|
|
|
/* suffix must start at directory boundary */
|
|
|
|
if ((!start || (start > 0 && is_dir_sep(path[start - 1]))) &&
|
|
|
|
!fspathcmp(suffix, path + start)) {
|
|
|
|
found = *list;
|
|
|
|
nr_found++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nr_found == 1 ? found : NULL;
|
|
|
|
}
|
|
|
|
|
2016-06-03 12:19:39 +00:00
|
|
|
struct worktree *find_worktree(struct worktree **list,
|
|
|
|
const char *prefix,
|
|
|
|
const char *arg)
|
|
|
|
{
|
2016-06-13 12:18:26 +00:00
|
|
|
struct worktree *wt;
|
2017-03-21 01:28:49 +00:00
|
|
|
char *to_free = NULL;
|
2016-06-03 12:19:39 +00:00
|
|
|
|
2016-06-13 12:18:26 +00:00
|
|
|
if ((wt = find_worktree_by_suffix(list, arg)))
|
|
|
|
return wt;
|
|
|
|
|
2017-03-21 01:28:49 +00:00
|
|
|
if (prefix)
|
|
|
|
arg = to_free = prefix_filename(prefix, arg);
|
2020-02-24 09:08:47 +00:00
|
|
|
wt = find_worktree_by_path(list, arg);
|
|
|
|
free(to_free);
|
|
|
|
return wt;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct worktree *find_worktree_by_path(struct worktree **list, const char *p)
|
|
|
|
{
|
2020-03-10 13:11:23 +00:00
|
|
|
struct strbuf wt_path = STRBUF_INIT;
|
2020-02-24 09:08:47 +00:00
|
|
|
char *path = real_pathdup(p, 0);
|
|
|
|
|
|
|
|
if (!path)
|
2018-08-28 21:20:18 +00:00
|
|
|
return NULL;
|
2019-05-13 10:49:44 +00:00
|
|
|
for (; *list; list++) {
|
2020-03-10 13:11:23 +00:00
|
|
|
if (!strbuf_realpath(&wt_path, (*list)->path, 0))
|
|
|
|
continue;
|
2019-05-13 10:49:44 +00:00
|
|
|
|
2020-03-10 13:11:23 +00:00
|
|
|
if (!fspathcmp(path, wt_path.buf))
|
2016-06-03 12:19:39 +00:00
|
|
|
break;
|
2019-05-13 10:49:44 +00:00
|
|
|
}
|
2016-06-03 12:19:39 +00:00
|
|
|
free(path);
|
2020-03-10 13:11:23 +00:00
|
|
|
strbuf_release(&wt_path);
|
2016-06-03 12:19:39 +00:00
|
|
|
return *list;
|
|
|
|
}
|
|
|
|
|
2016-06-03 12:19:40 +00:00
|
|
|
int is_main_worktree(const struct worktree *wt)
|
|
|
|
{
|
|
|
|
return !wt->id;
|
|
|
|
}
|
|
|
|
|
2018-10-30 06:24:09 +00:00
|
|
|
const char *worktree_lock_reason(struct worktree *wt)
|
2016-06-13 12:18:23 +00:00
|
|
|
{
|
2021-01-19 21:27:35 +00:00
|
|
|
if (is_main_worktree(wt))
|
|
|
|
return NULL;
|
2016-06-13 12:18:23 +00:00
|
|
|
|
|
|
|
if (!wt->lock_reason_valid) {
|
|
|
|
struct strbuf path = STRBUF_INIT;
|
|
|
|
|
|
|
|
strbuf_addstr(&path, worktree_git_path(wt, "locked"));
|
|
|
|
if (file_exists(path.buf)) {
|
|
|
|
struct strbuf lock_reason = STRBUF_INIT;
|
|
|
|
if (strbuf_read_file(&lock_reason, path.buf, 0) < 0)
|
|
|
|
die_errno(_("failed to read '%s'"), path.buf);
|
|
|
|
strbuf_trim(&lock_reason);
|
|
|
|
wt->lock_reason = strbuf_detach(&lock_reason, NULL);
|
|
|
|
} else
|
|
|
|
wt->lock_reason = NULL;
|
|
|
|
wt->lock_reason_valid = 1;
|
|
|
|
strbuf_release(&path);
|
|
|
|
}
|
|
|
|
|
|
|
|
return wt->lock_reason;
|
|
|
|
}
|
|
|
|
|
2021-01-19 21:27:34 +00:00
|
|
|
const char *worktree_prune_reason(struct worktree *wt, timestamp_t expire)
|
|
|
|
{
|
|
|
|
struct strbuf reason = STRBUF_INIT;
|
|
|
|
char *path = NULL;
|
|
|
|
|
|
|
|
if (is_main_worktree(wt))
|
|
|
|
return NULL;
|
|
|
|
if (wt->prune_reason_valid)
|
|
|
|
return wt->prune_reason;
|
|
|
|
|
|
|
|
if (should_prune_worktree(wt->id, &reason, &path, expire))
|
|
|
|
wt->prune_reason = strbuf_detach(&reason, NULL);
|
|
|
|
wt->prune_reason_valid = 1;
|
|
|
|
|
|
|
|
strbuf_release(&reason);
|
|
|
|
free(path);
|
|
|
|
return wt->prune_reason;
|
|
|
|
}
|
|
|
|
|
2018-01-24 09:53:51 +00:00
|
|
|
/* convenient wrapper to deal with NULL strbuf */
|
2021-07-13 08:05:18 +00:00
|
|
|
__attribute__((format (printf, 2, 3)))
|
2018-01-24 09:53:51 +00:00
|
|
|
static void strbuf_addf_gently(struct strbuf *buf, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list params;
|
|
|
|
|
|
|
|
if (!buf)
|
|
|
|
return;
|
|
|
|
|
|
|
|
va_start(params, fmt);
|
|
|
|
strbuf_vaddf(buf, fmt, params);
|
|
|
|
va_end(params);
|
|
|
|
}
|
|
|
|
|
2018-02-12 09:49:40 +00:00
|
|
|
int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,
|
|
|
|
unsigned flags)
|
2018-01-24 09:53:51 +00:00
|
|
|
{
|
|
|
|
struct strbuf wt_path = STRBUF_INIT;
|
2020-03-10 13:11:22 +00:00
|
|
|
struct strbuf realpath = STRBUF_INIT;
|
2018-01-24 09:53:51 +00:00
|
|
|
char *path = NULL;
|
|
|
|
int err, ret = -1;
|
|
|
|
|
|
|
|
strbuf_addf(&wt_path, "%s/.git", wt->path);
|
|
|
|
|
|
|
|
if (is_main_worktree(wt)) {
|
|
|
|
if (is_directory(wt_path.buf)) {
|
|
|
|
ret = 0;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Main worktree using .git file to point to the
|
|
|
|
* repository would make it impossible to know where
|
|
|
|
* the actual worktree is if this function is executed
|
|
|
|
* from another worktree. No .git file support for now.
|
|
|
|
*/
|
|
|
|
strbuf_addf_gently(errmsg,
|
|
|
|
_("'%s' at main working tree is not the repository directory"),
|
|
|
|
wt_path.buf);
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Make sure "gitdir" file points to a real .git file and that
|
|
|
|
* file points back here.
|
|
|
|
*/
|
|
|
|
if (!is_absolute_path(wt->path)) {
|
|
|
|
strbuf_addf_gently(errmsg,
|
|
|
|
_("'%s' file does not contain absolute path to the working tree location"),
|
|
|
|
git_common_path("worktrees/%s/gitdir", wt->id));
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
2018-02-12 09:49:40 +00:00
|
|
|
if (flags & WT_VALIDATE_WORKTREE_MISSING_OK &&
|
|
|
|
!file_exists(wt->path)) {
|
|
|
|
ret = 0;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
2018-01-24 09:53:51 +00:00
|
|
|
if (!file_exists(wt_path.buf)) {
|
|
|
|
strbuf_addf_gently(errmsg, _("'%s' does not exist"), wt_path.buf);
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
path = xstrdup_or_null(read_gitfile_gently(wt_path.buf, &err));
|
|
|
|
if (!path) {
|
|
|
|
strbuf_addf_gently(errmsg, _("'%s' is not a .git file, error code %d"),
|
|
|
|
wt_path.buf, err);
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
2020-03-10 13:11:22 +00:00
|
|
|
strbuf_realpath(&realpath, git_common_path("worktrees/%s", wt->id), 1);
|
|
|
|
ret = fspathcmp(path, realpath.buf);
|
2018-01-24 09:53:51 +00:00
|
|
|
|
|
|
|
if (ret)
|
|
|
|
strbuf_addf_gently(errmsg, _("'%s' does not point back to '%s'"),
|
|
|
|
wt->path, git_common_path("worktrees/%s", wt->id));
|
|
|
|
done:
|
|
|
|
free(path);
|
|
|
|
strbuf_release(&wt_path);
|
2020-03-10 13:11:22 +00:00
|
|
|
strbuf_release(&realpath);
|
2018-01-24 09:53:51 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-02-12 09:49:35 +00:00
|
|
|
void update_worktree_location(struct worktree *wt, const char *path_)
|
|
|
|
{
|
|
|
|
struct strbuf path = STRBUF_INIT;
|
|
|
|
|
|
|
|
if (is_main_worktree(wt))
|
2018-05-02 09:38:39 +00:00
|
|
|
BUG("can't relocate main worktree");
|
2018-02-12 09:49:35 +00:00
|
|
|
|
|
|
|
strbuf_realpath(&path, path_, 1);
|
|
|
|
if (fspathcmp(wt->path, path.buf)) {
|
|
|
|
write_file(git_common_path("worktrees/%s/gitdir", wt->id),
|
|
|
|
"%s/.git", path.buf);
|
|
|
|
free(wt->path);
|
|
|
|
wt->path = strbuf_detach(&path, NULL);
|
|
|
|
}
|
|
|
|
strbuf_release(&path);
|
|
|
|
}
|
|
|
|
|
2016-04-22 13:01:36 +00:00
|
|
|
int is_worktree_being_rebased(const struct worktree *wt,
|
|
|
|
const char *target)
|
2016-04-22 13:01:33 +00:00
|
|
|
{
|
|
|
|
struct wt_status_state state;
|
|
|
|
int found_rebase;
|
|
|
|
|
|
|
|
memset(&state, 0, sizeof(state));
|
|
|
|
found_rebase = wt_status_check_rebase(wt, &state) &&
|
2020-09-27 13:15:47 +00:00
|
|
|
(state.rebase_in_progress ||
|
|
|
|
state.rebase_interactive_in_progress) &&
|
|
|
|
state.branch &&
|
|
|
|
skip_prefix(target, "refs/heads/", &target) &&
|
|
|
|
!strcmp(state.branch, target);
|
wt-status: introduce wt_status_state_free_buffers()
When we have a `struct wt_status_state`, we manually free its `branch`,
`onto` and `detached_from`, or sometimes just one or two of them.
Provide a function `wt_status_state_free_buffers()` which does the
freeing.
The callers are still aware of these fields, e.g., they check whether
`branch` was populated or not. But this way, they don't need to know
about *all* of them, and if `struct wt_status_state` gets more fields,
they will not need to learn to free them.
Users of `struct wt_status` (which contains a `wt_status_state`) already
have `wt_status_collect_free_buffers()` (corresponding to
`wt_status_collect()`) which we can also teach to use this new helper.
Finally, note that we're currently leaving dangling pointers behind.
Some callers work on a stack-allocated struct, where this is obviously
ok. But for the users of `run_status()` in builtin/commit.c, there are
ample opportunities for someone to mistakenly use those dangling
pointers. We seem to be ok for now, but it's a use-after-free waiting to
happen. Let's leave NULL-pointers behind instead.
Signed-off-by: Martin Ågren <martin.agren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-09-27 13:15:43 +00:00
|
|
|
wt_status_state_free_buffers(&state);
|
2016-04-22 13:01:33 +00:00
|
|
|
return found_rebase;
|
|
|
|
}
|
|
|
|
|
2016-04-22 13:01:36 +00:00
|
|
|
int is_worktree_being_bisected(const struct worktree *wt,
|
|
|
|
const char *target)
|
2015-10-08 17:01:03 +00:00
|
|
|
{
|
2016-04-22 13:01:35 +00:00
|
|
|
struct wt_status_state state;
|
2020-09-27 13:15:46 +00:00
|
|
|
int found_bisect;
|
2016-04-22 13:01:35 +00:00
|
|
|
|
|
|
|
memset(&state, 0, sizeof(state));
|
2020-09-27 13:15:46 +00:00
|
|
|
found_bisect = wt_status_check_bisect(wt, &state) &&
|
status: fix branch shown when not only bisecting
In 83c750acde (wt-status.*: better advice for git status added,
2012-06-05), git-status received new informative messages to describe
the ongoing work in a worktree.
These messages were enhanced in 0722c805d6 (status: show the branch name
if possible in in-progress info, 2013-02-03), to show, if possible, the
branch where the operation was initiated.
Since then, we show incorrect information when several operations are in
progress and one of them is bisect:
$ git checkout -b foo
$ GIT_SEQUENCE_EDITOR='echo break >' git rebase -i HEAD~
$ git checkout -b bar
$ git bisect start
$ git status
...
You are currently editing a commit while rebasing branch 'bar' on '...'.
You are currently bisecting, started from branch 'bar'.
...
Note that we erroneously say "while rebasing branch 'bar'" when we
should be referring to "foo".
This must have gone unnoticed for so long because it must be unusual to
start a bisection while another operation is in progress. And even less
usual to involve different branches.
It caught my attention reviewing a leak introduced in 8b87cfd000
(wt-status: move strbuf into read_and_strip_branch(), 2013-03-16).
A simple change to deal with this situation can be to record in struct
wt_status_state, the branch where the bisect starts separately from the
branch related to other operations.
Let's do it and so we'll be able to display correct information and
we'll avoid the leak as well.
Signed-off-by: Rubén Justo <rjusto@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-09-09 20:12:47 +00:00
|
|
|
state.bisecting_from &&
|
2020-09-27 13:15:47 +00:00
|
|
|
skip_prefix(target, "refs/heads/", &target) &&
|
status: fix branch shown when not only bisecting
In 83c750acde (wt-status.*: better advice for git status added,
2012-06-05), git-status received new informative messages to describe
the ongoing work in a worktree.
These messages were enhanced in 0722c805d6 (status: show the branch name
if possible in in-progress info, 2013-02-03), to show, if possible, the
branch where the operation was initiated.
Since then, we show incorrect information when several operations are in
progress and one of them is bisect:
$ git checkout -b foo
$ GIT_SEQUENCE_EDITOR='echo break >' git rebase -i HEAD~
$ git checkout -b bar
$ git bisect start
$ git status
...
You are currently editing a commit while rebasing branch 'bar' on '...'.
You are currently bisecting, started from branch 'bar'.
...
Note that we erroneously say "while rebasing branch 'bar'" when we
should be referring to "foo".
This must have gone unnoticed for so long because it must be unusual to
start a bisection while another operation is in progress. And even less
usual to involve different branches.
It caught my attention reviewing a leak introduced in 8b87cfd000
(wt-status: move strbuf into read_and_strip_branch(), 2013-03-16).
A simple change to deal with this situation can be to record in struct
wt_status_state, the branch where the bisect starts separately from the
branch related to other operations.
Let's do it and so we'll be able to display correct information and
we'll avoid the leak as well.
Signed-off-by: Rubén Justo <rjusto@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-09-09 20:12:47 +00:00
|
|
|
!strcmp(state.bisecting_from, target);
|
wt-status: introduce wt_status_state_free_buffers()
When we have a `struct wt_status_state`, we manually free its `branch`,
`onto` and `detached_from`, or sometimes just one or two of them.
Provide a function `wt_status_state_free_buffers()` which does the
freeing.
The callers are still aware of these fields, e.g., they check whether
`branch` was populated or not. But this way, they don't need to know
about *all* of them, and if `struct wt_status_state` gets more fields,
they will not need to learn to free them.
Users of `struct wt_status` (which contains a `wt_status_state`) already
have `wt_status_collect_free_buffers()` (corresponding to
`wt_status_collect()`) which we can also teach to use this new helper.
Finally, note that we're currently leaving dangling pointers behind.
Some callers work on a stack-allocated struct, where this is obviously
ok. But for the users of `run_status()` in builtin/commit.c, there are
ample opportunities for someone to mistakenly use those dangling
pointers. We seem to be ok for now, but it's a use-after-free waiting to
happen. Let's leave NULL-pointers behind instead.
Signed-off-by: Martin Ågren <martin.agren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-09-27 13:15:43 +00:00
|
|
|
wt_status_state_free_buffers(&state);
|
2020-09-27 13:15:46 +00:00
|
|
|
return found_bisect;
|
2016-04-22 13:01:35 +00:00
|
|
|
}
|
|
|
|
|
2016-04-22 13:01:33 +00:00
|
|
|
/*
|
|
|
|
* note: this function should be able to detect shared symref even if
|
|
|
|
* HEAD is temporarily detached (e.g. in the middle of rebase or
|
|
|
|
* bisect). New commands that do similar things should update this
|
|
|
|
* function as well.
|
|
|
|
*/
|
2023-02-25 14:21:51 +00:00
|
|
|
int is_shared_symref(const struct worktree *wt, const char *symref,
|
|
|
|
const char *target)
|
2015-10-08 17:01:03 +00:00
|
|
|
{
|
2023-02-25 14:21:51 +00:00
|
|
|
const char *symref_target;
|
|
|
|
struct ref_store *refs;
|
|
|
|
int flags;
|
2015-10-08 17:01:03 +00:00
|
|
|
|
2023-02-25 14:21:51 +00:00
|
|
|
if (wt->is_bare)
|
|
|
|
return 0;
|
2017-04-24 10:01:23 +00:00
|
|
|
|
2023-02-25 14:21:51 +00:00
|
|
|
if (wt->is_detached && !strcmp(symref, "HEAD")) {
|
|
|
|
if (is_worktree_being_rebased(wt, target))
|
|
|
|
return 1;
|
|
|
|
if (is_worktree_being_bisected(wt, target))
|
|
|
|
return 1;
|
|
|
|
}
|
2016-04-22 13:01:32 +00:00
|
|
|
|
2023-02-25 14:21:51 +00:00
|
|
|
refs = get_worktree_ref_store(wt);
|
|
|
|
symref_target = refs_resolve_ref_unsafe(refs, symref, 0,
|
|
|
|
NULL, &flags);
|
|
|
|
if ((flags & REF_ISSYMREF) &&
|
|
|
|
symref_target && !strcmp(symref_target, target))
|
|
|
|
return 1;
|
2016-04-22 13:01:33 +00:00
|
|
|
|
2023-02-25 14:21:51 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
const struct worktree *find_shared_symref(struct worktree **worktrees,
|
|
|
|
const char *symref,
|
|
|
|
const char *target)
|
|
|
|
{
|
|
|
|
|
2023-02-25 14:22:02 +00:00
|
|
|
for (int i = 0; worktrees[i]; i++)
|
2023-02-25 14:21:51 +00:00
|
|
|
if (is_shared_symref(worktrees[i], symref, target))
|
|
|
|
return worktrees[i];
|
2015-10-08 17:01:03 +00:00
|
|
|
|
2023-02-25 14:21:51 +00:00
|
|
|
return NULL;
|
2015-10-02 11:55:31 +00:00
|
|
|
}
|
2016-12-12 19:04:33 +00:00
|
|
|
|
|
|
|
int submodule_uses_worktrees(const char *path)
|
|
|
|
{
|
|
|
|
char *submodule_gitdir;
|
2020-02-22 20:17:41 +00:00
|
|
|
struct strbuf sb = STRBUF_INIT, err = STRBUF_INIT;
|
2016-12-12 19:04:33 +00:00
|
|
|
DIR *dir;
|
|
|
|
struct dirent *d;
|
2016-12-27 17:50:13 +00:00
|
|
|
int ret = 0;
|
setup: fix memory leaks with `struct repository_format`
After we set up a `struct repository_format`, it owns various pieces of
allocated memory. We then either use those members, because we decide we
want to use the "candidate" repository format, or we discard the
candidate / scratch space. In the first case, we transfer ownership of
the memory to a few global variables. In the latter case, we just
silently drop the struct and end up leaking memory.
Introduce an initialization macro `REPOSITORY_FORMAT_INIT` and a
function `clear_repository_format()`, to be used on each side of
`read_repository_format()`. To have a clear and simple memory ownership,
let all users of `struct repository_format` duplicate the strings that
they take from it, rather than stealing the pointers.
Call `clear_...()` at the start of `read_...()` instead of just zeroing
the struct, since we sometimes enter the function multiple times. Thus,
it is important to initialize the struct before calling `read_...()`, so
document that. It's also important because we might not even call
`read_...()` before we call `clear_...()`, see, e.g., builtin/init-db.c.
Teach `read_...()` to clear the struct on error, so that it is reset to
a safe state, and document this. (In `setup_git_directory_gently()`, we
look at `repo_fmt.hash_algo` even if `repo_fmt.version` is -1, which we
weren't actually supposed to do per the API. After this commit, that's
ok.)
We inherit the existing code's combining "error" and "no version found".
Both are signalled through `version == -1` and now both cause us to
clear any partial configuration we have picked up. For "extensions.*",
that's fine, since they require a positive version number. For
"core.bare" and "core.worktree", we're already verifying that we have a
non-negative version number before using them.
Signed-off-by: Martin Ågren <martin.agren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-02-28 20:36:28 +00:00
|
|
|
struct repository_format format = REPOSITORY_FORMAT_INIT;
|
2016-12-12 19:04:33 +00:00
|
|
|
|
|
|
|
submodule_gitdir = git_pathdup_submodule(path, "%s", "");
|
|
|
|
if (!submodule_gitdir)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* The env would be set for the superproject. */
|
|
|
|
get_common_dir_noenv(&sb, submodule_gitdir);
|
2017-05-04 13:59:19 +00:00
|
|
|
free(submodule_gitdir);
|
2016-12-12 19:04:33 +00:00
|
|
|
|
|
|
|
strbuf_addstr(&sb, "/config");
|
|
|
|
read_repository_format(&format, sb.buf);
|
2020-02-22 20:17:41 +00:00
|
|
|
if (verify_repository_format(&format, &err)) {
|
|
|
|
strbuf_release(&err);
|
2016-12-12 19:04:33 +00:00
|
|
|
strbuf_release(&sb);
|
setup: fix memory leaks with `struct repository_format`
After we set up a `struct repository_format`, it owns various pieces of
allocated memory. We then either use those members, because we decide we
want to use the "candidate" repository format, or we discard the
candidate / scratch space. In the first case, we transfer ownership of
the memory to a few global variables. In the latter case, we just
silently drop the struct and end up leaking memory.
Introduce an initialization macro `REPOSITORY_FORMAT_INIT` and a
function `clear_repository_format()`, to be used on each side of
`read_repository_format()`. To have a clear and simple memory ownership,
let all users of `struct repository_format` duplicate the strings that
they take from it, rather than stealing the pointers.
Call `clear_...()` at the start of `read_...()` instead of just zeroing
the struct, since we sometimes enter the function multiple times. Thus,
it is important to initialize the struct before calling `read_...()`, so
document that. It's also important because we might not even call
`read_...()` before we call `clear_...()`, see, e.g., builtin/init-db.c.
Teach `read_...()` to clear the struct on error, so that it is reset to
a safe state, and document this. (In `setup_git_directory_gently()`, we
look at `repo_fmt.hash_algo` even if `repo_fmt.version` is -1, which we
weren't actually supposed to do per the API. After this commit, that's
ok.)
We inherit the existing code's combining "error" and "no version found".
Both are signalled through `version == -1` and now both cause us to
clear any partial configuration we have picked up. For "extensions.*",
that's fine, since they require a positive version number. For
"core.bare" and "core.worktree", we're already verifying that we have a
non-negative version number before using them.
Signed-off-by: Martin Ågren <martin.agren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-02-28 20:36:28 +00:00
|
|
|
clear_repository_format(&format);
|
2016-12-12 19:04:33 +00:00
|
|
|
return 1;
|
|
|
|
}
|
setup: fix memory leaks with `struct repository_format`
After we set up a `struct repository_format`, it owns various pieces of
allocated memory. We then either use those members, because we decide we
want to use the "candidate" repository format, or we discard the
candidate / scratch space. In the first case, we transfer ownership of
the memory to a few global variables. In the latter case, we just
silently drop the struct and end up leaking memory.
Introduce an initialization macro `REPOSITORY_FORMAT_INIT` and a
function `clear_repository_format()`, to be used on each side of
`read_repository_format()`. To have a clear and simple memory ownership,
let all users of `struct repository_format` duplicate the strings that
they take from it, rather than stealing the pointers.
Call `clear_...()` at the start of `read_...()` instead of just zeroing
the struct, since we sometimes enter the function multiple times. Thus,
it is important to initialize the struct before calling `read_...()`, so
document that. It's also important because we might not even call
`read_...()` before we call `clear_...()`, see, e.g., builtin/init-db.c.
Teach `read_...()` to clear the struct on error, so that it is reset to
a safe state, and document this. (In `setup_git_directory_gently()`, we
look at `repo_fmt.hash_algo` even if `repo_fmt.version` is -1, which we
weren't actually supposed to do per the API. After this commit, that's
ok.)
We inherit the existing code's combining "error" and "no version found".
Both are signalled through `version == -1` and now both cause us to
clear any partial configuration we have picked up. For "extensions.*",
that's fine, since they require a positive version number. For
"core.bare" and "core.worktree", we're already verifying that we have a
non-negative version number before using them.
Signed-off-by: Martin Ågren <martin.agren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-02-28 20:36:28 +00:00
|
|
|
clear_repository_format(&format);
|
2020-02-22 20:17:41 +00:00
|
|
|
strbuf_release(&err);
|
2016-12-12 19:04:33 +00:00
|
|
|
|
|
|
|
/* Replace config by worktrees. */
|
|
|
|
strbuf_setlen(&sb, sb.len - strlen("config"));
|
|
|
|
strbuf_addstr(&sb, "worktrees");
|
|
|
|
|
|
|
|
/* See if there is any file inside the worktrees directory. */
|
|
|
|
dir = opendir(sb.buf);
|
|
|
|
strbuf_release(&sb);
|
|
|
|
|
|
|
|
if (!dir)
|
|
|
|
return 0;
|
|
|
|
|
2021-05-12 17:28:22 +00:00
|
|
|
d = readdir_skip_dot_and_dotdot(dir);
|
2022-05-02 16:50:37 +00:00
|
|
|
if (d)
|
2016-12-12 19:04:33 +00:00
|
|
|
ret = 1;
|
|
|
|
closedir(dir);
|
|
|
|
return ret;
|
|
|
|
}
|
2017-08-23 12:36:59 +00:00
|
|
|
|
2018-10-21 08:08:56 +00:00
|
|
|
void strbuf_worktree_ref(const struct worktree *wt,
|
|
|
|
struct strbuf *sb,
|
|
|
|
const char *refname)
|
|
|
|
{
|
2022-09-19 16:34:50 +00:00
|
|
|
if (parse_worktree_ref(refname, NULL, NULL, NULL) ==
|
|
|
|
REF_WORKTREE_CURRENT &&
|
|
|
|
wt && !wt->is_current) {
|
|
|
|
if (is_main_worktree(wt))
|
|
|
|
strbuf_addstr(sb, "main-worktree/");
|
|
|
|
else
|
|
|
|
strbuf_addf(sb, "worktrees/%s/", wt->id);
|
2018-10-21 08:08:56 +00:00
|
|
|
}
|
|
|
|
strbuf_addstr(sb, refname);
|
|
|
|
}
|
|
|
|
|
2017-08-23 12:36:59 +00:00
|
|
|
int other_head_refs(each_ref_fn fn, void *cb_data)
|
|
|
|
{
|
|
|
|
struct worktree **worktrees, **p;
|
2020-09-27 13:15:44 +00:00
|
|
|
struct strbuf refname = STRBUF_INIT;
|
2017-08-23 12:36:59 +00:00
|
|
|
int ret = 0;
|
|
|
|
|
2020-06-19 23:35:44 +00:00
|
|
|
worktrees = get_worktrees();
|
2017-08-23 12:36:59 +00:00
|
|
|
for (p = worktrees; *p; p++) {
|
|
|
|
struct worktree *wt = *p;
|
2018-10-21 08:08:56 +00:00
|
|
|
struct object_id oid;
|
|
|
|
int flag;
|
2017-08-23 12:36:59 +00:00
|
|
|
|
|
|
|
if (wt->is_current)
|
|
|
|
continue;
|
|
|
|
|
2020-09-27 13:15:44 +00:00
|
|
|
strbuf_reset(&refname);
|
|
|
|
strbuf_worktree_ref(wt, &refname, "HEAD");
|
2021-10-16 09:39:27 +00:00
|
|
|
if (refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
|
2021-10-16 09:39:14 +00:00
|
|
|
refname.buf,
|
|
|
|
RESOLVE_REF_READING,
|
2022-01-26 14:37:01 +00:00
|
|
|
&oid, &flag))
|
2020-09-27 13:15:44 +00:00
|
|
|
ret = fn(refname.buf, &oid, flag, cb_data);
|
2017-08-23 12:36:59 +00:00
|
|
|
if (ret)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
free_worktrees(worktrees);
|
2020-09-27 13:15:44 +00:00
|
|
|
strbuf_release(&refname);
|
2017-08-23 12:36:59 +00:00
|
|
|
return ret;
|
|
|
|
}
|
2020-08-31 06:57:57 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Repair worktree's /path/to/worktree/.git file if missing, corrupt, or not
|
|
|
|
* pointing at <repo>/worktrees/<id>.
|
|
|
|
*/
|
|
|
|
static void repair_gitfile(struct worktree *wt,
|
|
|
|
worktree_repair_fn fn, void *cb_data)
|
|
|
|
{
|
|
|
|
struct strbuf dotgit = STRBUF_INIT;
|
|
|
|
struct strbuf repo = STRBUF_INIT;
|
|
|
|
char *backlink;
|
|
|
|
const char *repair = NULL;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
/* missing worktree can't be repaired */
|
|
|
|
if (!file_exists(wt->path))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!is_directory(wt->path)) {
|
|
|
|
fn(1, wt->path, _("not a directory"), cb_data);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1);
|
|
|
|
strbuf_addf(&dotgit, "%s/.git", wt->path);
|
|
|
|
backlink = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
|
|
|
|
|
|
|
|
if (err == READ_GITFILE_ERR_NOT_A_FILE)
|
|
|
|
fn(1, wt->path, _(".git is not a file"), cb_data);
|
|
|
|
else if (err)
|
|
|
|
repair = _(".git file broken");
|
|
|
|
else if (fspathcmp(backlink, repo.buf))
|
|
|
|
repair = _(".git file incorrect");
|
|
|
|
|
|
|
|
if (repair) {
|
|
|
|
fn(0, wt->path, repair, cb_data);
|
|
|
|
write_file(dotgit.buf, "gitdir: %s", repo.buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(backlink);
|
|
|
|
strbuf_release(&repo);
|
|
|
|
strbuf_release(&dotgit);
|
|
|
|
}
|
|
|
|
|
2023-08-29 23:45:31 +00:00
|
|
|
static void repair_noop(int iserr UNUSED,
|
|
|
|
const char *path UNUSED,
|
|
|
|
const char *msg UNUSED,
|
|
|
|
void *cb_data UNUSED)
|
2020-08-31 06:57:57 +00:00
|
|
|
{
|
|
|
|
/* nothing */
|
|
|
|
}
|
|
|
|
|
|
|
|
void repair_worktrees(worktree_repair_fn fn, void *cb_data)
|
|
|
|
{
|
2023-12-29 07:26:30 +00:00
|
|
|
struct worktree **worktrees = get_worktrees_internal(1);
|
2020-08-31 06:57:57 +00:00
|
|
|
struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
|
|
|
|
|
|
|
|
if (!fn)
|
|
|
|
fn = repair_noop;
|
|
|
|
for (; *wt; wt++)
|
|
|
|
repair_gitfile(*wt, fn, cb_data);
|
|
|
|
free_worktrees(worktrees);
|
|
|
|
}
|
2020-08-31 06:57:58 +00:00
|
|
|
|
|
|
|
static int is_main_worktree_path(const char *path)
|
|
|
|
{
|
|
|
|
struct strbuf target = STRBUF_INIT;
|
|
|
|
struct strbuf maindir = STRBUF_INIT;
|
|
|
|
int cmp;
|
|
|
|
|
|
|
|
strbuf_add_real_path(&target, path);
|
|
|
|
strbuf_strip_suffix(&target, "/.git");
|
|
|
|
strbuf_add_real_path(&maindir, get_git_common_dir());
|
|
|
|
strbuf_strip_suffix(&maindir, "/.git");
|
|
|
|
cmp = fspathcmp(maindir.buf, target.buf);
|
|
|
|
|
|
|
|
strbuf_release(&maindir);
|
|
|
|
strbuf_release(&target);
|
|
|
|
return !cmp;
|
|
|
|
}
|
|
|
|
|
worktree: teach `repair` to fix multi-directional breakage
`git worktree repair` knows how to repair the two-way links between the
repository and a worktree as long as a link in one or the other
direction is sound. For instance, if a linked worktree is moved (without
using `git worktree move`), repair is possible because the worktree
still knows the location of the repository even though the repository no
longer knows where the worktree is. Similarly, if the repository is
moved, repair is possible since the repository still knows the locations
of the worktrees even though the worktrees no longer know where the
repository is.
However, if both the repository and the worktrees are moved, then links
are severed in both directions, and no repair is possible. This is the
case even when the new worktree locations are specified as arguments to
`git worktree repair`. The reason for this limitation is twofold. First,
when `repair` consults the worktree's gitfile (/path/to/worktree/.git)
to determine the corresponding <repo>/worktrees/<id>/gitdir file to fix,
<repo> is the old path to the repository, thus it is unable to fix the
`gitdir` file at its new location since it doesn't know where it is.
Second, when `repair` consults <repo>/worktrees/<id>/gitdir to find the
location of the worktree's gitfile (/path/to/worktree/.git), the path
recorded in `gitdir` is the old location of the worktree's gitfile, thus
it is unable to repair the gitfile since it doesn't know where it is.
Fix these shortcomings by teaching `repair` to attempt to infer the new
location of the <repo>/worktrees/<id>/gitdir file when the location
recorded in the worktree's gitfile has become stale but the file is
otherwise well-formed. The inference is intentionally simple-minded.
For each worktree path specified as an argument, `git worktree repair`
manually reads the ".git" gitfile at that location and, if it is
well-formed, extracts the <id>. It then searches for a corresponding
<id> in <repo>/worktrees/ and, if found, concludes that there is a
reasonable match and updates <repo>/worktrees/<id>/gitdir to point at
the specified worktree path. In order for <repo> to be known, `git
worktree repair` must be run in the main worktree or bare repository.
`git worktree repair` first attempts to repair each incoming
/path/to/worktree/.git gitfile to point at the repository, and then
attempts to repair outgoing <repo>/worktrees/<id>/gitdir files to point
at the worktrees. This sequence was chosen arbitrarily when originally
implemented since the order of fixes is immaterial as long as one side
of the two-way link between the repository and a worktree is sound.
However, for this new repair technique to work, the order must be
reversed. This is because the new inference mechanism, when it is
successful, allows the outgoing <repo>/worktrees/<id>/gitdir file to be
repaired, thus fixing one side of the two-way link. Once that side is
fixed, the other side can be fixed by the existing repair mechanism,
hence the order of repairs is now significant.
Two safeguards are employed to avoid hijacking a worktree from a
different repository if the user accidentally specifies a foreign
worktree as an argument. The first, as described above, is that it
requires an <id> match between the repository and the worktree. That
itself is not foolproof for preventing hijack, so the second safeguard
is that the inference will only kick in if the worktree's
/path/to/worktree/.git gitfile does not point at a repository.
Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-12-21 08:16:01 +00:00
|
|
|
/*
|
|
|
|
* If both the main worktree and linked worktree have been moved, then the
|
|
|
|
* gitfile /path/to/worktree/.git won't point into the repository, thus we
|
|
|
|
* won't know which <repo>/worktrees/<id>/gitdir to repair. However, we may
|
|
|
|
* be able to infer the gitdir by manually reading /path/to/worktree/.git,
|
|
|
|
* extracting the <id>, and checking if <repo>/worktrees/<id> exists.
|
|
|
|
*/
|
|
|
|
static char *infer_backlink(const char *gitfile)
|
|
|
|
{
|
|
|
|
struct strbuf actual = STRBUF_INIT;
|
|
|
|
struct strbuf inferred = STRBUF_INIT;
|
|
|
|
const char *id;
|
|
|
|
|
|
|
|
if (strbuf_read_file(&actual, gitfile, 0) < 0)
|
|
|
|
goto error;
|
|
|
|
if (!starts_with(actual.buf, "gitdir:"))
|
|
|
|
goto error;
|
|
|
|
if (!(id = find_last_dir_sep(actual.buf)))
|
|
|
|
goto error;
|
|
|
|
strbuf_trim(&actual);
|
|
|
|
id++; /* advance past '/' to point at <id> */
|
|
|
|
if (!*id)
|
|
|
|
goto error;
|
|
|
|
strbuf_git_common_path(&inferred, the_repository, "worktrees/%s", id);
|
|
|
|
if (!is_directory(inferred.buf))
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
strbuf_release(&actual);
|
|
|
|
return strbuf_detach(&inferred, NULL);
|
|
|
|
|
|
|
|
error:
|
|
|
|
strbuf_release(&actual);
|
|
|
|
strbuf_release(&inferred);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2020-08-31 06:57:58 +00:00
|
|
|
/*
|
|
|
|
* Repair <repo>/worktrees/<id>/gitdir if missing, corrupt, or not pointing at
|
|
|
|
* the worktree's path.
|
|
|
|
*/
|
|
|
|
void repair_worktree_at_path(const char *path,
|
|
|
|
worktree_repair_fn fn, void *cb_data)
|
|
|
|
{
|
|
|
|
struct strbuf dotgit = STRBUF_INIT;
|
|
|
|
struct strbuf realdotgit = STRBUF_INIT;
|
|
|
|
struct strbuf gitdir = STRBUF_INIT;
|
|
|
|
struct strbuf olddotgit = STRBUF_INIT;
|
|
|
|
char *backlink = NULL;
|
|
|
|
const char *repair = NULL;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (!fn)
|
|
|
|
fn = repair_noop;
|
|
|
|
|
|
|
|
if (is_main_worktree_path(path))
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
strbuf_addf(&dotgit, "%s/.git", path);
|
|
|
|
if (!strbuf_realpath(&realdotgit, dotgit.buf, 0)) {
|
|
|
|
fn(1, path, _("not a valid path"), cb_data);
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
backlink = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err));
|
|
|
|
if (err == READ_GITFILE_ERR_NOT_A_FILE) {
|
|
|
|
fn(1, realdotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);
|
|
|
|
goto done;
|
worktree: teach `repair` to fix multi-directional breakage
`git worktree repair` knows how to repair the two-way links between the
repository and a worktree as long as a link in one or the other
direction is sound. For instance, if a linked worktree is moved (without
using `git worktree move`), repair is possible because the worktree
still knows the location of the repository even though the repository no
longer knows where the worktree is. Similarly, if the repository is
moved, repair is possible since the repository still knows the locations
of the worktrees even though the worktrees no longer know where the
repository is.
However, if both the repository and the worktrees are moved, then links
are severed in both directions, and no repair is possible. This is the
case even when the new worktree locations are specified as arguments to
`git worktree repair`. The reason for this limitation is twofold. First,
when `repair` consults the worktree's gitfile (/path/to/worktree/.git)
to determine the corresponding <repo>/worktrees/<id>/gitdir file to fix,
<repo> is the old path to the repository, thus it is unable to fix the
`gitdir` file at its new location since it doesn't know where it is.
Second, when `repair` consults <repo>/worktrees/<id>/gitdir to find the
location of the worktree's gitfile (/path/to/worktree/.git), the path
recorded in `gitdir` is the old location of the worktree's gitfile, thus
it is unable to repair the gitfile since it doesn't know where it is.
Fix these shortcomings by teaching `repair` to attempt to infer the new
location of the <repo>/worktrees/<id>/gitdir file when the location
recorded in the worktree's gitfile has become stale but the file is
otherwise well-formed. The inference is intentionally simple-minded.
For each worktree path specified as an argument, `git worktree repair`
manually reads the ".git" gitfile at that location and, if it is
well-formed, extracts the <id>. It then searches for a corresponding
<id> in <repo>/worktrees/ and, if found, concludes that there is a
reasonable match and updates <repo>/worktrees/<id>/gitdir to point at
the specified worktree path. In order for <repo> to be known, `git
worktree repair` must be run in the main worktree or bare repository.
`git worktree repair` first attempts to repair each incoming
/path/to/worktree/.git gitfile to point at the repository, and then
attempts to repair outgoing <repo>/worktrees/<id>/gitdir files to point
at the worktrees. This sequence was chosen arbitrarily when originally
implemented since the order of fixes is immaterial as long as one side
of the two-way link between the repository and a worktree is sound.
However, for this new repair technique to work, the order must be
reversed. This is because the new inference mechanism, when it is
successful, allows the outgoing <repo>/worktrees/<id>/gitdir file to be
repaired, thus fixing one side of the two-way link. Once that side is
fixed, the other side can be fixed by the existing repair mechanism,
hence the order of repairs is now significant.
Two safeguards are employed to avoid hijacking a worktree from a
different repository if the user accidentally specifies a foreign
worktree as an argument. The first, as described above, is that it
requires an <id> match between the repository and the worktree. That
itself is not foolproof for preventing hijack, so the second safeguard
is that the inference will only kick in if the worktree's
/path/to/worktree/.git gitfile does not point at a repository.
Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-12-21 08:16:01 +00:00
|
|
|
} else if (err == READ_GITFILE_ERR_NOT_A_REPO) {
|
|
|
|
if (!(backlink = infer_backlink(realdotgit.buf))) {
|
|
|
|
fn(1, realdotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data);
|
|
|
|
goto done;
|
|
|
|
}
|
2020-08-31 06:57:58 +00:00
|
|
|
} else if (err) {
|
|
|
|
fn(1, realdotgit.buf, _("unable to locate repository; .git file broken"), cb_data);
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
strbuf_addf(&gitdir, "%s/gitdir", backlink);
|
|
|
|
if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0)
|
|
|
|
repair = _("gitdir unreadable");
|
|
|
|
else {
|
|
|
|
strbuf_rtrim(&olddotgit);
|
|
|
|
if (fspathcmp(olddotgit.buf, realdotgit.buf))
|
|
|
|
repair = _("gitdir incorrect");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (repair) {
|
|
|
|
fn(0, gitdir.buf, repair, cb_data);
|
|
|
|
write_file(gitdir.buf, "%s", realdotgit.buf);
|
|
|
|
}
|
|
|
|
done:
|
|
|
|
free(backlink);
|
|
|
|
strbuf_release(&olddotgit);
|
|
|
|
strbuf_release(&gitdir);
|
|
|
|
strbuf_release(&realdotgit);
|
|
|
|
strbuf_release(&dotgit);
|
|
|
|
}
|
2021-01-19 21:27:33 +00:00
|
|
|
|
|
|
|
int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, timestamp_t expire)
|
|
|
|
{
|
|
|
|
struct stat st;
|
|
|
|
char *path;
|
|
|
|
int fd;
|
|
|
|
size_t len;
|
|
|
|
ssize_t read_result;
|
|
|
|
|
|
|
|
*wtpath = NULL;
|
|
|
|
if (!is_directory(git_path("worktrees/%s", id))) {
|
|
|
|
strbuf_addstr(reason, _("not a valid directory"));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (file_exists(git_path("worktrees/%s/locked", id)))
|
|
|
|
return 0;
|
|
|
|
if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
|
|
|
|
strbuf_addstr(reason, _("gitdir file does not exist"));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
|
|
|
|
if (fd < 0) {
|
|
|
|
strbuf_addf(reason, _("unable to read gitdir file (%s)"),
|
|
|
|
strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
len = xsize_t(st.st_size);
|
|
|
|
path = xmallocz(len);
|
|
|
|
|
|
|
|
read_result = read_in_full(fd, path, len);
|
|
|
|
if (read_result < 0) {
|
|
|
|
strbuf_addf(reason, _("unable to read gitdir file (%s)"),
|
|
|
|
strerror(errno));
|
|
|
|
close(fd);
|
|
|
|
free(path);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
close(fd);
|
|
|
|
|
|
|
|
if (read_result != len) {
|
|
|
|
strbuf_addf(reason,
|
|
|
|
_("short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"),
|
|
|
|
(uintmax_t)len, (uintmax_t)read_result);
|
|
|
|
free(path);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
|
|
|
|
len--;
|
|
|
|
if (!len) {
|
|
|
|
strbuf_addstr(reason, _("invalid gitdir file"));
|
|
|
|
free(path);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
path[len] = '\0';
|
|
|
|
if (!file_exists(path)) {
|
|
|
|
if (stat(git_path("worktrees/%s/index", id), &st) ||
|
|
|
|
st.st_mtime <= expire) {
|
|
|
|
strbuf_addstr(reason, _("gitdir file points to non-existent location"));
|
|
|
|
free(path);
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
*wtpath = path;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*wtpath = path;
|
|
|
|
return 0;
|
|
|
|
}
|
2022-02-07 21:32:59 +00:00
|
|
|
|
|
|
|
static int move_config_setting(const char *key, const char *value,
|
|
|
|
const char *from_file, const char *to_file)
|
|
|
|
{
|
2024-03-12 21:47:00 +00:00
|
|
|
if (git_config_set_in_file_gently(to_file, key, NULL, value))
|
2022-02-07 21:32:59 +00:00
|
|
|
return error(_("unable to set %s in '%s'"), key, to_file);
|
2024-03-12 21:47:00 +00:00
|
|
|
if (git_config_set_in_file_gently(from_file, key, NULL, NULL))
|
2022-02-07 21:32:59 +00:00
|
|
|
return error(_("unable to unset %s in '%s'"), key, from_file);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int init_worktree_config(struct repository *r)
|
|
|
|
{
|
|
|
|
int res = 0;
|
|
|
|
int bare = 0;
|
|
|
|
struct config_set cs = { { 0 } };
|
|
|
|
const char *core_worktree;
|
|
|
|
char *common_config_file;
|
|
|
|
char *main_worktree_file;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the extension is already enabled, then we can skip the
|
|
|
|
* upgrade process.
|
|
|
|
*/
|
2023-05-26 01:33:00 +00:00
|
|
|
if (r->repository_format_worktree_config)
|
2022-02-07 21:32:59 +00:00
|
|
|
return 0;
|
|
|
|
if ((res = git_config_set_gently("extensions.worktreeConfig", "true")))
|
|
|
|
return error(_("failed to set extensions.worktreeConfig setting"));
|
|
|
|
|
|
|
|
common_config_file = xstrfmt("%s/config", r->commondir);
|
|
|
|
main_worktree_file = xstrfmt("%s/config.worktree", r->commondir);
|
|
|
|
|
|
|
|
git_configset_init(&cs);
|
|
|
|
git_configset_add_file(&cs, common_config_file);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If core.bare is true in the common config file, then we need to
|
|
|
|
* move it to the main worktree's config file or it will break all
|
|
|
|
* worktrees. If it is false, then leave it in place because it
|
|
|
|
* _could_ be negating a global core.bare=true.
|
|
|
|
*/
|
|
|
|
if (!git_configset_get_bool(&cs, "core.bare", &bare) && bare) {
|
|
|
|
if ((res = move_config_setting("core.bare", "true",
|
|
|
|
common_config_file,
|
|
|
|
main_worktree_file)))
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* If core.worktree is set, then the main worktree is located
|
|
|
|
* somewhere different than the parent of the common Git dir.
|
|
|
|
* Relocate that value to avoid breaking all worktrees with this
|
|
|
|
* upgrade to worktree config.
|
|
|
|
*/
|
config: pass kvi to die_bad_number()
Plumb "struct key_value_info" through all code paths that end in
die_bad_number(), which lets us remove the helper functions that read
analogous values from "struct config_reader". As a result, nothing reads
config_reader.config_kvi any more, so remove that too.
In config.c, this requires changing the signature of
git_configset_get_value() to 'return' "kvi" in an out parameter so that
git_configset_get_<type>() can pass it to git_config_<type>(). Only
numeric types will use "kvi", so for non-numeric types (e.g.
git_configset_get_string()), pass NULL to indicate that the out
parameter isn't needed.
Outside of config.c, config callbacks now need to pass "ctx->kvi" to any
of the git_config_<type>() functions that parse a config string into a
number type. Included is a .cocci patch to make that refactor.
The only exceptional case is builtin/config.c, where git_config_<type>()
is called outside of a config callback (namely, on user-provided input),
so config source information has never been available. In this case,
die_bad_number() defaults to a generic, but perfectly descriptive
message. Let's provide a safe, non-NULL for "kvi" anyway, but make sure
not to change the message.
Signed-off-by: Glen Choo <chooglen@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-06-28 19:26:27 +00:00
|
|
|
if (!git_configset_get_value(&cs, "core.worktree", &core_worktree, NULL)) {
|
2022-02-07 21:32:59 +00:00
|
|
|
if ((res = move_config_setting("core.worktree", core_worktree,
|
|
|
|
common_config_file,
|
|
|
|
main_worktree_file)))
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Ensure that we use worktree config for the remaining lifetime
|
|
|
|
* of the current process.
|
|
|
|
*/
|
2023-05-26 01:33:00 +00:00
|
|
|
r->repository_format_worktree_config = 1;
|
2022-02-07 21:32:59 +00:00
|
|
|
|
|
|
|
cleanup:
|
|
|
|
git_configset_clear(&cs);
|
|
|
|
free(common_config_file);
|
|
|
|
free(main_worktree_file);
|
|
|
|
return res;
|
|
|
|
}
|