Merge branch 'ps/worktree-refdb-initialization'

Instead of manually creating refs/ hierarchy on disk upon a
creation of a secondary worktree, which is only usable via the
files backend, use the refs API to populate it.

* ps/worktree-refdb-initialization:
  builtin/worktree: create refdb via ref backend
  worktree: expose interface to look up worktree by name
  builtin/worktree: move setup of commondir file earlier
  refs/files: skip creation of "refs/{heads,tags}" for worktrees
  setup: move creation of "refs/" into the files backend
  refs: prepare `refs_init_db()` for initializing worktree refs
This commit is contained in:
Junio C Hamano
2024-01-26 08:54:46 -08:00
10 changed files with 96 additions and 69 deletions

View File

@ -416,7 +416,6 @@ static int add_worktree(const char *path, const char *refname,
struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
struct strbuf sb = STRBUF_INIT, realpath = STRBUF_INIT; struct strbuf sb = STRBUF_INIT, realpath = STRBUF_INIT;
const char *name; const char *name;
struct child_process cp = CHILD_PROCESS_INIT;
struct strvec child_env = STRVEC_INIT; struct strvec child_env = STRVEC_INIT;
unsigned int counter = 0; unsigned int counter = 0;
int len, ret; int len, ret;
@ -424,7 +423,8 @@ static int add_worktree(const char *path, const char *refname,
struct commit *commit = NULL; struct commit *commit = NULL;
int is_branch = 0; int is_branch = 0;
struct strbuf sb_name = STRBUF_INIT; struct strbuf sb_name = STRBUF_INIT;
struct worktree **worktrees; struct worktree **worktrees, *wt = NULL;
struct ref_store *wt_refs;
worktrees = get_worktrees(); worktrees = get_worktrees();
check_candidate_path(path, opts->force, worktrees, "add"); check_candidate_path(path, opts->force, worktrees, "add");
@ -495,20 +495,32 @@ static int add_worktree(const char *path, const char *refname,
strbuf_realpath(&realpath, get_git_common_dir(), 1); strbuf_realpath(&realpath, get_git_common_dir(), 1);
write_file(sb_git.buf, "gitdir: %s/worktrees/%s", write_file(sb_git.buf, "gitdir: %s/worktrees/%s",
realpath.buf, name); realpath.buf, name);
/*
* This is to keep resolve_ref() happy. We need a valid HEAD
* or is_git_directory() will reject the directory. Any value which
* looks like an object ID will do since it will be immediately
* replaced by the symbolic-ref or update-ref invocation in the new
* worktree.
*/
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
write_file(sb.buf, "%s", oid_to_hex(null_oid()));
strbuf_reset(&sb); strbuf_reset(&sb);
strbuf_addf(&sb, "%s/commondir", sb_repo.buf); strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
write_file(sb.buf, "../.."); write_file(sb.buf, "../..");
/*
* Set up the ref store of the worktree and create the HEAD reference.
*/
wt = get_linked_worktree(name, 1);
if (!wt) {
ret = error(_("could not find created worktree '%s'"), name);
goto done;
}
wt_refs = get_worktree_ref_store(wt);
ret = refs_init_db(wt_refs, REFS_INIT_DB_IS_WORKTREE, &sb);
if (ret)
goto done;
if (!is_branch && commit)
ret = refs_update_ref(wt_refs, NULL, "HEAD", &commit->object.oid,
NULL, 0, UPDATE_REFS_MSG_ON_ERR);
else
ret = refs_create_symref(wt_refs, "HEAD", symref.buf, NULL);
if (ret)
goto done;
/* /*
* If the current worktree has sparse-checkout enabled, then copy * If the current worktree has sparse-checkout enabled, then copy
* the sparse-checkout patterns from the current worktree. * the sparse-checkout patterns from the current worktree.
@ -526,22 +538,6 @@ static int add_worktree(const char *path, const char *refname,
strvec_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf); strvec_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf);
strvec_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path); strvec_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
cp.git_cmd = 1;
if (!is_branch && commit) {
strvec_pushl(&cp.args, "update-ref", "HEAD",
oid_to_hex(&commit->object.oid), NULL);
} else {
strvec_pushl(&cp.args, "symbolic-ref", "HEAD",
symref.buf, NULL);
if (opts->quiet)
strvec_push(&cp.args, "--quiet");
}
strvec_pushv(&cp.env, child_env.v);
ret = run_command(&cp);
if (ret)
goto done;
if (opts->orphan && if (opts->orphan &&
(ret = make_worktree_orphan(refname, opts, &child_env))) (ret = make_worktree_orphan(refname, opts, &child_env)))
@ -587,6 +583,7 @@ done:
strbuf_release(&sb_git); strbuf_release(&sb_git);
strbuf_release(&sb_name); strbuf_release(&sb_name);
strbuf_release(&realpath); strbuf_release(&realpath);
free_worktree(wt);
return ret; return ret;
} }

6
refs.c
View File

@ -1997,11 +1997,9 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
} }
/* backend functions */ /* backend functions */
int refs_init_db(struct strbuf *err) int refs_init_db(struct ref_store *refs, int flags, struct strbuf *err)
{ {
struct ref_store *refs = get_main_ref_store(the_repository); return refs->be->init_db(refs, flags, err);
return refs->be->init_db(refs, err);
} }
const char *resolve_ref_unsafe(const char *refname, int resolve_flags, const char *resolve_ref_unsafe(const char *refname, int resolve_flags,

4
refs.h
View File

@ -126,7 +126,9 @@ int should_autocreate_reflog(const char *refname);
int is_branch(const char *refname); int is_branch(const char *refname);
int refs_init_db(struct strbuf *err); #define REFS_INIT_DB_IS_WORKTREE (1 << 0)
int refs_init_db(struct ref_store *refs, int flags, struct strbuf *err);
/* /*
* Return the peeled value of the oid currently being iterated via * Return the peeled value of the oid currently being iterated via

View File

@ -33,10 +33,10 @@ struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_stor
return (struct ref_store *)res; return (struct ref_store *)res;
} }
static int debug_init_db(struct ref_store *refs, struct strbuf *err) static int debug_init_db(struct ref_store *refs, int flags, struct strbuf *err)
{ {
struct debug_ref_store *drefs = (struct debug_ref_store *)refs; struct debug_ref_store *drefs = (struct debug_ref_store *)refs;
int res = drefs->refs->be->init_db(drefs->refs, err); int res = drefs->refs->be->init_db(drefs->refs, flags, err);
trace_printf_key(&trace_refs, "init_db: %d\n", res); trace_printf_key(&trace_refs, "init_db: %d\n", res);
return res; return res;
} }

View File

@ -3218,21 +3218,46 @@ static int files_reflog_expire(struct ref_store *ref_store,
return -1; return -1;
} }
static int files_init_db(struct ref_store *ref_store, struct strbuf *err UNUSED) static int files_init_db(struct ref_store *ref_store,
int flags,
struct strbuf *err UNUSED)
{ {
struct files_ref_store *refs = struct files_ref_store *refs =
files_downcast(ref_store, REF_STORE_WRITE, "init_db"); files_downcast(ref_store, REF_STORE_WRITE, "init_db");
struct strbuf sb = STRBUF_INIT; struct strbuf sb = STRBUF_INIT;
/* /*
* Create .git/refs/{heads,tags} * We need to create a "refs" dir in any case so that older versions of
* Git can tell that this is a repository. This serves two main purposes:
*
* - Clients will know to stop walking the parent-directory chain when
* detecting the Git repository. Otherwise they may end up detecting
* a Git repository in a parent directory instead.
*
* - Instead of failing to detect a repository with unknown reference
* format altogether, old clients will print an error saying that
* they do not understand the reference format extension.
*/ */
files_ref_path(refs, &sb, "refs/heads"); strbuf_addf(&sb, "%s/refs", ref_store->gitdir);
safe_create_dir(sb.buf, 1); safe_create_dir(sb.buf, 1);
adjust_shared_perm(sb.buf);
strbuf_reset(&sb); /*
files_ref_path(refs, &sb, "refs/tags"); * There is no need to create directories for common refs when creating
safe_create_dir(sb.buf, 1); * a worktree ref store.
*/
if (!(flags & REFS_INIT_DB_IS_WORKTREE)) {
/*
* Create .git/refs/{heads,tags}
*/
strbuf_reset(&sb);
files_ref_path(refs, &sb, "refs/heads");
safe_create_dir(sb.buf, 1);
strbuf_reset(&sb);
files_ref_path(refs, &sb, "refs/tags");
safe_create_dir(sb.buf, 1);
}
strbuf_release(&sb); strbuf_release(&sb);
return 0; return 0;

View File

@ -1245,6 +1245,7 @@ static const char PACKED_REFS_HEADER[] =
"# pack-refs with: peeled fully-peeled sorted \n"; "# pack-refs with: peeled fully-peeled sorted \n";
static int packed_init_db(struct ref_store *ref_store UNUSED, static int packed_init_db(struct ref_store *ref_store UNUSED,
int flags UNUSED,
struct strbuf *err UNUSED) struct strbuf *err UNUSED)
{ {
/* Nothing to do. */ /* Nothing to do. */

View File

@ -529,7 +529,9 @@ typedef struct ref_store *ref_store_init_fn(struct repository *repo,
const char *gitdir, const char *gitdir,
unsigned int flags); unsigned int flags);
typedef int ref_init_db_fn(struct ref_store *refs, struct strbuf *err); typedef int ref_init_db_fn(struct ref_store *refs,
int flags,
struct strbuf *err);
typedef int ref_transaction_prepare_fn(struct ref_store *refs, typedef int ref_transaction_prepare_fn(struct ref_store *refs,
struct ref_transaction *transaction, struct ref_transaction *transaction,

17
setup.c
View File

@ -1926,23 +1926,8 @@ void create_reference_database(unsigned int ref_storage_format,
struct strbuf err = STRBUF_INIT; struct strbuf err = STRBUF_INIT;
int reinit = is_reinit(); int reinit = is_reinit();
/*
* We need to create a "refs" dir in any case so that older versions of
* Git can tell that this is a repository. This serves two main purposes:
*
* - Clients will know to stop walking the parent-directory chain when
* detecting the Git repository. Otherwise they may end up detecting
* a Git repository in a parent directory instead.
*
* - Instead of failing to detect a repository with unknown reference
* format altogether, old clients will print an error saying that
* they do not understand the reference format extension.
*/
safe_create_dir(git_path("refs"), 1);
adjust_shared_perm(git_path("refs"));
repo_set_ref_storage_format(the_repository, ref_storage_format); repo_set_ref_storage_format(the_repository, ref_storage_format);
if (refs_init_db(&err)) if (refs_init_db(get_main_ref_store(the_repository), 0, &err))
die("failed to set up refs db: %s", err.buf); die("failed to set up refs db: %s", err.buf);
/* /*

View File

@ -12,18 +12,23 @@
#include "wt-status.h" #include "wt-status.h"
#include "config.h" #include "config.h"
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);
}
void free_worktrees(struct worktree **worktrees) void free_worktrees(struct worktree **worktrees)
{ {
int i = 0; int i = 0;
for (i = 0; worktrees[i]; i++)
for (i = 0; worktrees[i]; i++) { free_worktree(worktrees[i]);
free(worktrees[i]->path);
free(worktrees[i]->id);
free(worktrees[i]->head_ref);
free(worktrees[i]->lock_reason);
free(worktrees[i]->prune_reason);
free(worktrees[i]);
}
free (worktrees); free (worktrees);
} }
@ -75,8 +80,8 @@ static struct worktree *get_main_worktree(int skip_reading_head)
return worktree; return worktree;
} }
static struct worktree *get_linked_worktree(const char *id, struct worktree *get_linked_worktree(const char *id,
int skip_reading_head) int skip_reading_head)
{ {
struct worktree *worktree = NULL; struct worktree *worktree = NULL;
struct strbuf path = STRBUF_INIT; struct strbuf path = STRBUF_INIT;

View File

@ -57,6 +57,13 @@ struct worktree *find_worktree(struct worktree **list,
const char *prefix, const char *prefix,
const char *arg); const char *arg);
/*
* Look up the worktree corresponding to `id`, or NULL of no such worktree
* exists.
*/
struct worktree *get_linked_worktree(const char *id,
int skip_reading_head);
/* /*
* Return the worktree corresponding to `path`, or NULL if no such worktree * Return the worktree corresponding to `path`, or NULL if no such worktree
* exists. * exists.
@ -134,6 +141,11 @@ void repair_worktrees(worktree_repair_fn, void *cb_data);
*/ */
void repair_worktree_at_path(const char *, worktree_repair_fn, void *cb_data); void repair_worktree_at_path(const char *, worktree_repair_fn, void *cb_data);
/*
* Free up the memory for a worktree.
*/
void free_worktree(struct worktree *);
/* /*
* Free up the memory for worktree(s) * Free up the memory for worktree(s)
*/ */