Merge branch 'nd/multiple-work-trees'
A replacement for contrib/workdir/git-new-workdir that does not rely on symbolic links and make sharing of objects and refs safer by making the borrowee and borrowers aware of each other. * nd/multiple-work-trees: (41 commits) prune --worktrees: fix expire vs worktree existence condition t1501: fix test with split index t2026: fix broken &&-chain t2026 needs procondition SANITY git-checkout.txt: a note about multiple checkout support for submodules checkout: add --ignore-other-wortrees checkout: pass whole struct to parse_branchname_arg instead of individual flags git-common-dir: make "modules/" per-working-directory directory checkout: do not fail if target is an empty directory t2025: add a test to make sure grafts is working from a linked checkout checkout: don't require a work tree when checking out into a new one git_path(): keep "info/sparse-checkout" per work-tree count-objects: report unused files in $GIT_DIR/worktrees/... gc: support prune --worktrees gc: factor out gc.pruneexpire parsing code gc: style change -- no SP before closing parenthesis checkout: clean up half-prepared directories in --to mode checkout: reject if the branch is already checked out elsewhere prune: strategies for linked checkouts checkout: support checking out into a new working directory ...
This commit is contained in:
240
path.c
240
path.c
@ -4,6 +4,7 @@
|
||||
#include "cache.h"
|
||||
#include "strbuf.h"
|
||||
#include "string-list.h"
|
||||
#include "dir.h"
|
||||
|
||||
static int get_st_mode_bits(const char *path, int *mode)
|
||||
{
|
||||
@ -16,11 +17,15 @@ static int get_st_mode_bits(const char *path, int *mode)
|
||||
|
||||
static char bad_path[] = "/bad-path/";
|
||||
|
||||
static char *get_pathname(void)
|
||||
static struct strbuf *get_pathname(void)
|
||||
{
|
||||
static char pathname_array[4][PATH_MAX];
|
||||
static struct strbuf pathname_array[4] = {
|
||||
STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
|
||||
};
|
||||
static int index;
|
||||
return pathname_array[3 & ++index];
|
||||
struct strbuf *sb = &pathname_array[3 & ++index];
|
||||
strbuf_reset(sb);
|
||||
return sb;
|
||||
}
|
||||
|
||||
static char *cleanup_path(char *path)
|
||||
@ -34,6 +39,13 @@ static char *cleanup_path(char *path)
|
||||
return path;
|
||||
}
|
||||
|
||||
static void strbuf_cleanup_path(struct strbuf *sb)
|
||||
{
|
||||
char *path = cleanup_path(sb->buf);
|
||||
if (path > sb->buf)
|
||||
strbuf_remove(sb, 0, path - sb->buf);
|
||||
}
|
||||
|
||||
char *mksnpath(char *buf, size_t n, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
@ -49,85 +61,167 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
|
||||
return cleanup_path(buf);
|
||||
}
|
||||
|
||||
static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)
|
||||
static int dir_prefix(const char *buf, const char *dir)
|
||||
{
|
||||
const char *git_dir = get_git_dir();
|
||||
size_t len;
|
||||
|
||||
len = strlen(git_dir);
|
||||
if (n < len + 1)
|
||||
goto bad;
|
||||
memcpy(buf, git_dir, len);
|
||||
if (len && !is_dir_sep(git_dir[len-1]))
|
||||
buf[len++] = '/';
|
||||
len += vsnprintf(buf + len, n - len, fmt, args);
|
||||
if (len >= n)
|
||||
goto bad;
|
||||
return cleanup_path(buf);
|
||||
bad:
|
||||
strlcpy(buf, bad_path, n);
|
||||
return buf;
|
||||
int len = strlen(dir);
|
||||
return !strncmp(buf, dir, len) &&
|
||||
(is_dir_sep(buf[len]) || buf[len] == '\0');
|
||||
}
|
||||
|
||||
char *git_snpath(char *buf, size_t n, const char *fmt, ...)
|
||||
/* $buf =~ m|$dir/+$file| but without regex */
|
||||
static int is_dir_file(const char *buf, const char *dir, const char *file)
|
||||
{
|
||||
int len = strlen(dir);
|
||||
if (strncmp(buf, dir, len) || !is_dir_sep(buf[len]))
|
||||
return 0;
|
||||
while (is_dir_sep(buf[len]))
|
||||
len++;
|
||||
return !strcmp(buf + len, file);
|
||||
}
|
||||
|
||||
static void replace_dir(struct strbuf *buf, int len, const char *newdir)
|
||||
{
|
||||
int newlen = strlen(newdir);
|
||||
int need_sep = (buf->buf[len] && !is_dir_sep(buf->buf[len])) &&
|
||||
!is_dir_sep(newdir[newlen - 1]);
|
||||
if (need_sep)
|
||||
len--; /* keep one char, to be replaced with '/' */
|
||||
strbuf_splice(buf, 0, len, newdir, newlen);
|
||||
if (need_sep)
|
||||
buf->buf[newlen] = '/';
|
||||
}
|
||||
|
||||
static const char *common_list[] = {
|
||||
"/branches", "/hooks", "/info", "!/logs", "/lost-found",
|
||||
"/objects", "/refs", "/remotes", "/worktrees", "/rr-cache", "/svn",
|
||||
"config", "!gc.pid", "packed-refs", "shallow",
|
||||
NULL
|
||||
};
|
||||
|
||||
static void update_common_dir(struct strbuf *buf, int git_dir_len)
|
||||
{
|
||||
char *base = buf->buf + git_dir_len;
|
||||
const char **p;
|
||||
|
||||
if (is_dir_file(base, "logs", "HEAD") ||
|
||||
is_dir_file(base, "info", "sparse-checkout"))
|
||||
return; /* keep this in $GIT_DIR */
|
||||
for (p = common_list; *p; p++) {
|
||||
const char *path = *p;
|
||||
int is_dir = 0;
|
||||
if (*path == '!')
|
||||
path++;
|
||||
if (*path == '/') {
|
||||
path++;
|
||||
is_dir = 1;
|
||||
}
|
||||
if (is_dir && dir_prefix(base, path)) {
|
||||
replace_dir(buf, git_dir_len, get_git_common_dir());
|
||||
return;
|
||||
}
|
||||
if (!is_dir && !strcmp(base, path)) {
|
||||
replace_dir(buf, git_dir_len, get_git_common_dir());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void report_linked_checkout_garbage(void)
|
||||
{
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
const char **p;
|
||||
int len;
|
||||
|
||||
if (!git_common_dir_env)
|
||||
return;
|
||||
strbuf_addf(&sb, "%s/", get_git_dir());
|
||||
len = sb.len;
|
||||
for (p = common_list; *p; p++) {
|
||||
const char *path = *p;
|
||||
if (*path == '!')
|
||||
continue;
|
||||
strbuf_setlen(&sb, len);
|
||||
strbuf_addstr(&sb, path);
|
||||
if (file_exists(sb.buf))
|
||||
report_garbage("unused in linked checkout", sb.buf);
|
||||
}
|
||||
strbuf_release(&sb);
|
||||
}
|
||||
|
||||
static void adjust_git_path(struct strbuf *buf, int git_dir_len)
|
||||
{
|
||||
const char *base = buf->buf + git_dir_len;
|
||||
if (git_graft_env && is_dir_file(base, "info", "grafts"))
|
||||
strbuf_splice(buf, 0, buf->len,
|
||||
get_graft_file(), strlen(get_graft_file()));
|
||||
else if (git_index_env && !strcmp(base, "index"))
|
||||
strbuf_splice(buf, 0, buf->len,
|
||||
get_index_file(), strlen(get_index_file()));
|
||||
else if (git_db_env && dir_prefix(base, "objects"))
|
||||
replace_dir(buf, git_dir_len + 7, get_object_directory());
|
||||
else if (git_common_dir_env)
|
||||
update_common_dir(buf, git_dir_len);
|
||||
}
|
||||
|
||||
static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
|
||||
{
|
||||
int gitdir_len;
|
||||
strbuf_addstr(buf, get_git_dir());
|
||||
if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
|
||||
strbuf_addch(buf, '/');
|
||||
gitdir_len = buf->len;
|
||||
strbuf_vaddf(buf, fmt, args);
|
||||
adjust_git_path(buf, gitdir_len);
|
||||
strbuf_cleanup_path(buf);
|
||||
}
|
||||
|
||||
void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
|
||||
{
|
||||
char *ret;
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
ret = vsnpath(buf, n, fmt, args);
|
||||
do_git_path(sb, fmt, args);
|
||||
va_end(args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
const char *git_path(const char *fmt, ...)
|
||||
{
|
||||
struct strbuf *pathname = get_pathname();
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
do_git_path(pathname, fmt, args);
|
||||
va_end(args);
|
||||
return pathname->buf;
|
||||
}
|
||||
|
||||
char *git_pathdup(const char *fmt, ...)
|
||||
{
|
||||
char path[PATH_MAX], *ret;
|
||||
struct strbuf path = STRBUF_INIT;
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
ret = vsnpath(path, sizeof(path), fmt, args);
|
||||
do_git_path(&path, fmt, args);
|
||||
va_end(args);
|
||||
return xstrdup(ret);
|
||||
return strbuf_detach(&path, NULL);
|
||||
}
|
||||
|
||||
char *mkpathdup(const char *fmt, ...)
|
||||
{
|
||||
char *path;
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
strbuf_vaddf(&sb, fmt, args);
|
||||
va_end(args);
|
||||
path = xstrdup(cleanup_path(sb.buf));
|
||||
|
||||
strbuf_release(&sb);
|
||||
return path;
|
||||
strbuf_cleanup_path(&sb);
|
||||
return strbuf_detach(&sb, NULL);
|
||||
}
|
||||
|
||||
char *mkpath(const char *fmt, ...)
|
||||
const char *mkpath(const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
unsigned len;
|
||||
char *pathname = get_pathname();
|
||||
|
||||
struct strbuf *pathname = get_pathname();
|
||||
va_start(args, fmt);
|
||||
len = vsnprintf(pathname, PATH_MAX, fmt, args);
|
||||
strbuf_vaddf(pathname, fmt, args);
|
||||
va_end(args);
|
||||
if (len >= PATH_MAX)
|
||||
return bad_path;
|
||||
return cleanup_path(pathname);
|
||||
}
|
||||
|
||||
char *git_path(const char *fmt, ...)
|
||||
{
|
||||
char *pathname = get_pathname();
|
||||
va_list args;
|
||||
char *ret;
|
||||
|
||||
va_start(args, fmt);
|
||||
ret = vsnpath(pathname, PATH_MAX, fmt, args);
|
||||
va_end(args);
|
||||
return ret;
|
||||
return cleanup_path(pathname->buf);
|
||||
}
|
||||
|
||||
void home_config_paths(char **global, char **xdg, char *file)
|
||||
@ -158,43 +252,29 @@ void home_config_paths(char **global, char **xdg, char *file)
|
||||
free(to_free);
|
||||
}
|
||||
|
||||
char *git_path_submodule(const char *path, const char *fmt, ...)
|
||||
const char *git_path_submodule(const char *path, const char *fmt, ...)
|
||||
{
|
||||
char *pathname = get_pathname();
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
struct strbuf *buf = get_pathname();
|
||||
const char *git_dir;
|
||||
va_list args;
|
||||
unsigned len;
|
||||
|
||||
len = strlen(path);
|
||||
if (len > PATH_MAX-100)
|
||||
return bad_path;
|
||||
strbuf_addstr(buf, path);
|
||||
if (buf->len && buf->buf[buf->len - 1] != '/')
|
||||
strbuf_addch(buf, '/');
|
||||
strbuf_addstr(buf, ".git");
|
||||
|
||||
strbuf_addstr(&buf, path);
|
||||
if (len && path[len-1] != '/')
|
||||
strbuf_addch(&buf, '/');
|
||||
strbuf_addstr(&buf, ".git");
|
||||
|
||||
git_dir = read_gitfile(buf.buf);
|
||||
git_dir = read_gitfile(buf->buf);
|
||||
if (git_dir) {
|
||||
strbuf_reset(&buf);
|
||||
strbuf_addstr(&buf, git_dir);
|
||||
strbuf_reset(buf);
|
||||
strbuf_addstr(buf, git_dir);
|
||||
}
|
||||
strbuf_addch(&buf, '/');
|
||||
|
||||
if (buf.len >= PATH_MAX)
|
||||
return bad_path;
|
||||
memcpy(pathname, buf.buf, buf.len + 1);
|
||||
|
||||
strbuf_release(&buf);
|
||||
len = strlen(pathname);
|
||||
strbuf_addch(buf, '/');
|
||||
|
||||
va_start(args, fmt);
|
||||
len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
|
||||
strbuf_vaddf(buf, fmt, args);
|
||||
va_end(args);
|
||||
if (len >= PATH_MAX)
|
||||
return bad_path;
|
||||
return cleanup_path(pathname);
|
||||
strbuf_cleanup_path(buf);
|
||||
return buf->buf;
|
||||
}
|
||||
|
||||
int validate_headref(const char *path)
|
||||
|
Reference in New Issue
Block a user