* The backend-independent part of the reference module.
*/
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "git-compat-util.h"
#include "advice.h"
#include "config.h"
#include "environment.h"
-#include "hashmap.h"
+#include "strmap.h"
#include "gettext.h"
#include "hex.h"
#include "lockfile.h"
#include "object-store-ll.h"
#include "object.h"
#include "path.h"
-#include "tag.h"
#include "submodule.h"
#include "worktree.h"
#include "strvec.h"
[REF_STORAGE_FORMAT_REFTABLE] = &refs_be_reftable,
};
-static const struct ref_storage_be *find_ref_storage_backend(unsigned int ref_storage_format)
+static const struct ref_storage_be *find_ref_storage_backend(
+ enum ref_storage_format ref_storage_format)
{
if (ref_storage_format < ARRAY_SIZE(refs_backends))
return refs_backends[ref_storage_format];
return NULL;
}
-unsigned int ref_storage_format_by_name(const char *name)
+enum ref_storage_format ref_storage_format_by_name(const char *name)
{
for (unsigned int i = 0; i < ARRAY_SIZE(refs_backends); i++)
if (refs_backends[i] && !strcmp(refs_backends[i]->name, name))
return REF_STORAGE_FORMAT_UNKNOWN;
}
-const char *ref_storage_format_to_name(unsigned int ref_storage_format)
+const char *ref_storage_format_to_name(enum ref_storage_format ref_storage_format)
{
const struct ref_storage_be *be = find_ref_storage_backend(ref_storage_format);
if (!be)
{
struct ref_namespace_info *info = &ref_namespace[namespace];
if (info->ref_updated)
- free(info->ref);
+ free((char *)info->ref);
info->ref = ref;
info->ref_updated = 1;
}
return check_or_sanitize_refname(refname, flags, NULL);
}
+int refs_fsck(struct ref_store *refs, struct fsck_options *o)
+{
+ return refs->be->fsck(refs, o);
+}
+
void sanitize_refname_component(const char *refname, struct strbuf *out)
{
if (check_or_sanitize_refname(refname, REFNAME_ALLOW_ONELEVEL, out))
NULL, NULL);
}
-static int for_each_filter_refs(const char *refname,
+static int for_each_filter_refs(const char *refname, const char *referent,
const struct object_id *oid,
int flags, void *data)
{
return 0;
if (filter->prefix)
skip_prefix(refname, filter->prefix, &refname);
- return filter->fn(refname, oid, flags, filter->cb_data);
-}
-
-enum peel_status peel_object(const struct object_id *name, struct object_id *oid)
-{
- struct object *o = lookup_unknown_object(the_repository, name);
-
- if (o->type == OBJ_NONE) {
- int type = oid_object_info(the_repository, name, NULL);
- if (type < 0 || !object_as_type(o, type, 0))
- return PEEL_INVALID;
- }
-
- if (o->type != OBJ_TAG)
- return PEEL_NON_TAG;
-
- o = deref_tag_noverify(o);
- if (!o)
- return PEEL_INVALID;
-
- oidcpy(oid, &o->oid);
- return PEEL_PEELED;
+ return filter->fn(refname, referent, oid, flags, filter->cb_data);
}
struct warn_if_dangling_data {
+ struct ref_store *refs;
FILE *fp;
const char *refname;
const struct string_list *refnames;
const char *msg_fmt;
};
-static int warn_if_dangling_symref(const char *refname,
+static int warn_if_dangling_symref(const char *refname, const char *referent UNUSED,
const struct object_id *oid UNUSED,
int flags, void *cb_data)
{
if (!(flags & REF_ISSYMREF))
return 0;
- resolves_to = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
- refname, 0, NULL, NULL);
+ resolves_to = refs_resolve_ref_unsafe(d->refs, refname, 0, NULL, NULL);
if (!resolves_to
|| (d->refname
? strcmp(resolves_to, d->refname)
return 0;
}
-void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
+void refs_warn_dangling_symref(struct ref_store *refs, FILE *fp,
+ const char *msg_fmt, const char *refname)
{
- struct warn_if_dangling_data data;
-
- data.fp = fp;
- data.refname = refname;
- data.refnames = NULL;
- data.msg_fmt = msg_fmt;
- refs_for_each_rawref(get_main_ref_store(the_repository),
- warn_if_dangling_symref, &data);
+ struct warn_if_dangling_data data = {
+ .refs = refs,
+ .fp = fp,
+ .refname = refname,
+ .msg_fmt = msg_fmt,
+ };
+ refs_for_each_rawref(refs, warn_if_dangling_symref, &data);
}
-void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_list *refnames)
+void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp,
+ const char *msg_fmt, const struct string_list *refnames)
{
- struct warn_if_dangling_data data;
-
- data.fp = fp;
- data.refname = NULL;
- data.refnames = refnames;
- data.msg_fmt = msg_fmt;
- refs_for_each_rawref(get_main_ref_store(the_repository),
- warn_if_dangling_symref, &data);
+ struct warn_if_dangling_data data = {
+ .refs = refs,
+ .fp = fp,
+ .refnames = refnames,
+ .msg_fmt = msg_fmt,
+ };
+ refs_for_each_rawref(refs, warn_if_dangling_symref, &data);
}
int refs_for_each_tag_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
strbuf_addf(&buf, "%sHEAD", get_git_namespace());
if (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag))
- ret = fn(buf.buf, &oid, flag, cb_data);
+ ret = fn(buf.buf, NULL, &oid, flag, cb_data);
strbuf_release(&buf);
return ret;
return ret;
}
-const char *git_default_branch_name(int quiet)
-{
- static char *ret;
-
- if (!ret)
- ret = repo_default_branch_name(the_repository, quiet);
-
- return ret;
-}
-
/*
* *string and *len will only be substituted, and *string returned (for
* later free()ing) if the string passed in is a magic short-hand form
return logs_found;
}
-int dwim_log(const char *str, int len, struct object_id *oid, char **log)
-{
- return repo_dwim_log(the_repository, str, len, oid, log);
-}
-
int is_per_worktree_ref(const char *refname)
{
return starts_with(refname, "refs/worktree/") ||
starts_with(refname, "refs/rewritten/");
}
-static int is_pseudoref_syntax(const char *refname)
+int is_pseudo_ref(const char *refname)
+{
+ static const char * const pseudo_refs[] = {
+ "FETCH_HEAD",
+ "MERGE_HEAD",
+ };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(pseudo_refs); i++)
+ if (!strcmp(refname, pseudo_refs[i]))
+ return 1;
+
+ return 0;
+}
+
+static int is_root_ref_syntax(const char *refname)
{
const char *c;
return 0;
}
- /*
- * HEAD is not a pseudoref, but it certainly uses the
- * pseudoref syntax.
- */
return 1;
}
-int is_pseudoref(struct ref_store *refs, const char *refname)
+int is_root_ref(const char *refname)
{
- static const char *const irregular_pseudorefs[] = {
+ static const char *const irregular_root_refs[] = {
+ "HEAD",
"AUTO_MERGE",
"BISECT_EXPECTED_REV",
"NOTES_MERGE_PARTIAL",
"NOTES_MERGE_REF",
"MERGE_AUTOSTASH",
};
- struct object_id oid;
size_t i;
- if (!is_pseudoref_syntax(refname))
+ if (!is_root_ref_syntax(refname) ||
+ is_pseudo_ref(refname))
return 0;
- if (ends_with(refname, "_HEAD")) {
- refs_resolve_ref_unsafe(refs, refname,
- RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
- &oid, NULL);
- return !is_null_oid(&oid);
- }
-
- for (i = 0; i < ARRAY_SIZE(irregular_pseudorefs); i++)
- if (!strcmp(refname, irregular_pseudorefs[i])) {
- refs_resolve_ref_unsafe(refs, refname,
- RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
- &oid, NULL);
- return !is_null_oid(&oid);
- }
-
- return 0;
-}
+ if (ends_with(refname, "_HEAD"))
+ return 1;
-int is_headref(struct ref_store *refs, const char *refname)
-{
- if (!strcmp(refname, "HEAD"))
- return refs_ref_exists(refs, refname);
+ for (i = 0; i < ARRAY_SIZE(irregular_root_refs); i++)
+ if (!strcmp(refname, irregular_root_refs[i]))
+ return 1;
return 0;
}
static int is_current_worktree_ref(const char *ref) {
- return is_pseudoref_syntax(ref) || is_per_worktree_ref(ref);
+ return is_root_ref_syntax(ref) || is_per_worktree_ref(ref);
}
enum ref_worktree_type parse_worktree_ref(const char *maybe_worktree_ref,
transaction = ref_store_transaction_begin(refs, &err);
if (!transaction ||
ref_transaction_delete(transaction, refname, old_oid,
- flags, msg, &err) ||
+ NULL, flags, msg, &err) ||
ref_transaction_commit(transaction, &err)) {
error("%s", err.buf);
ref_transaction_free(transaction);
{
assert(err);
+ if ((flags & REF_FORCE_CREATE_REFLOG) &&
+ (flags & REF_SKIP_CREATE_REFLOG)) {
+ strbuf_addstr(err, _("refusing to force and skip creation of reflog"));
+ return -1;
+ }
+
if (!(flags & REF_SKIP_REFNAME_VERIFICATION) &&
((new_oid && !is_null_oid(new_oid)) ?
check_refname_format(refname, REFNAME_ALLOW_ONELEVEL) :
return -1;
}
+ if (!(flags & REF_SKIP_REFNAME_VERIFICATION) &&
+ is_pseudo_ref(refname)) {
+ strbuf_addf(err, _("refusing to update pseudoref '%s'"),
+ refname);
+ return -1;
+ }
+
if (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS)
BUG("illegal flags 0x%x passed to ref_transaction_update()", flags);
int ref_transaction_create(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
+ const char *new_target,
unsigned int flags, const char *msg,
struct strbuf *err)
{
- if (!new_oid || is_null_oid(new_oid)) {
- strbuf_addf(err, "'%s' has a null OID", refname);
+ if (new_oid && new_target)
+ BUG("create called with both new_oid and new_target set");
+ if ((!new_oid || is_null_oid(new_oid)) && !new_target) {
+ strbuf_addf(err, "'%s' has neither a valid OID nor a target", refname);
return 1;
}
return ref_transaction_update(transaction, refname, new_oid,
- null_oid(), NULL, NULL, flags,
+ null_oid(), new_target, NULL, flags,
msg, err);
}
int ref_transaction_delete(struct ref_transaction *transaction,
const char *refname,
const struct object_id *old_oid,
- unsigned int flags, const char *msg,
+ const char *old_target,
+ unsigned int flags,
+ const char *msg,
struct strbuf *err)
{
if (old_oid && is_null_oid(old_oid))
BUG("delete called with old_oid set to zeros");
+ if (old_oid && old_target)
+ BUG("delete called with both old_oid and old_target set");
+ if (old_target && !(flags & REF_NO_DEREF))
+ BUG("delete cannot operate on symrefs with deref mode");
return ref_transaction_update(transaction, refname,
null_oid(), old_oid,
- NULL, NULL, flags,
+ NULL, old_target, flags,
msg, err);
}
int ref_transaction_verify(struct ref_transaction *transaction,
const char *refname,
const struct object_id *old_oid,
+ const char *old_target,
unsigned int flags,
struct strbuf *err)
{
- if (!old_oid)
- BUG("verify called with old_oid set to NULL");
+ if (!old_target && !old_oid)
+ BUG("verify called with old_oid and old_target set to NULL");
+ if (old_oid && old_target)
+ BUG("verify called with both old_oid and old_target set");
+ if (old_target && !(flags & REF_NO_DEREF))
+ BUG("verify cannot operate on symrefs with deref mode");
return ref_transaction_update(transaction, refname,
NULL, old_oid,
- NULL, NULL,
+ NULL, old_target,
flags, NULL, err);
}
if (refs_resolve_ref_unsafe(refs, "HEAD", RESOLVE_REF_READING,
&oid, &flag))
- return fn("HEAD", &oid, flag, cb_data);
+ return fn("HEAD", NULL, &oid, flag, cb_data);
return 0;
}
return iter;
}
-/*
- * Call fn for each reference in the specified submodule for which the
- * refname begins with prefix. If trim is non-zero, then trim that
- * many characters off the beginning of each refname before passing
- * the refname to fn. flags can be DO_FOR_EACH_INCLUDE_BROKEN to
- * include broken references in the iteration. If fn ever returns a
- * non-zero value, stop the iteration and return that value;
- * otherwise, return 0.
- */
-static int do_for_each_repo_ref(struct repository *r, const char *prefix,
- each_repo_ref_fn fn, int trim, int flags,
- void *cb_data)
-{
- struct ref_iterator *iter;
- struct ref_store *refs = get_main_ref_store(r);
-
- if (!refs)
- return 0;
-
- iter = refs_ref_iterator_begin(refs, prefix, NULL, trim, flags);
-
- return do_for_each_repo_ref_iterator(r, iter, fn, cb_data);
-}
-
-struct do_for_each_ref_help {
- each_ref_fn *fn;
- void *cb_data;
-};
-
-static int do_for_each_ref_helper(struct repository *r UNUSED,
- const char *refname,
- const struct object_id *oid,
- int flags,
- void *cb_data)
-{
- struct do_for_each_ref_help *hp = cb_data;
-
- return hp->fn(refname, oid, flags, hp->cb_data);
-}
-
static int do_for_each_ref(struct ref_store *refs, const char *prefix,
const char **exclude_patterns,
each_ref_fn fn, int trim,
enum do_for_each_ref_flags flags, void *cb_data)
{
struct ref_iterator *iter;
- struct do_for_each_ref_help hp = { fn, cb_data };
if (!refs)
return 0;
iter = refs_ref_iterator_begin(refs, prefix, exclude_patterns, trim,
flags);
- return do_for_each_repo_ref_iterator(the_repository, iter,
- do_for_each_ref_helper, &hp);
+ return do_for_each_ref_iterator(iter, fn, cb_data);
}
int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
return do_for_each_ref(refs, prefix, exclude_patterns, fn, 0, 0, cb_data);
}
-int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data)
+int refs_for_each_replace_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
{
const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
- return do_for_each_repo_ref(r, git_replace_ref_base, fn,
- strlen(git_replace_ref_base),
- DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
+ return do_for_each_ref(refs, git_replace_ref_base, NULL, fn,
+ strlen(git_replace_ref_base),
+ DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
int refs_for_each_namespaced_ref(struct ref_store *refs,
goto done;
}
- result = parse_loose_ref_contents(content.buf, oid, referent, type,
- failure_errno);
+ result = parse_loose_ref_contents(ref_store->repo->hash_algo, content.buf,
+ oid, referent, type, failure_errno);
done:
strbuf_release(&full_path);
return result;
}
-static int is_special_ref(const char *refname)
-{
- /*
- * Special references are refs that have different semantics compared
- * to "normal" refs. These refs can thus not be stored in the ref
- * backend, but must always be accessed via the filesystem. The
- * following refs are special:
- *
- * - FETCH_HEAD may contain multiple object IDs, and each one of them
- * carries additional metadata like where it came from.
- *
- * - MERGE_HEAD may contain multiple object IDs when merging multiple
- * heads.
- *
- * Reading, writing or deleting references must consistently go either
- * through the filesystem (special refs) or through the reference
- * backend (normal ones).
- */
- static const char * const special_refs[] = {
- "FETCH_HEAD",
- "MERGE_HEAD",
- };
- size_t i;
-
- for (i = 0; i < ARRAY_SIZE(special_refs); i++)
- if (!strcmp(refname, special_refs[i]))
- return 1;
-
- return 0;
-}
-
int refs_read_raw_ref(struct ref_store *ref_store, const char *refname,
struct object_id *oid, struct strbuf *referent,
unsigned int *type, int *failure_errno)
{
assert(failure_errno);
- if (is_special_ref(refname))
+ if (is_pseudo_ref(refname))
return refs_read_special_head(ref_store, refname, oid, referent,
type, failure_errno);
failure_errno != ENOTDIR)
return NULL;
- oidclr(oid);
+ oidclr(oid, refs->repo->hash_algo);
if (*flags & REF_BAD_NAME)
*flags |= REF_ISBROKEN;
return refname;
if (!(read_flags & REF_ISSYMREF)) {
if (*flags & REF_BAD_NAME) {
- oidclr(oid);
+ oidclr(oid, refs->repo->hash_algo);
*flags |= REF_ISBROKEN;
}
return refname;
refname = sb_refname.buf;
if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
- oidclr(oid);
+ oidclr(oid, refs->repo->hash_algo);
return refname;
}
if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
}
/* backend functions */
-int refs_init_db(struct ref_store *refs, int flags, struct strbuf *err)
+int ref_store_create_on_disk(struct ref_store *refs, int flags, struct strbuf *err)
{
- return refs->be->init_db(refs, flags, err);
+ return refs->be->create_on_disk(refs, flags, err);
}
-int resolve_gitlink_ref(const char *submodule, const char *refname,
- struct object_id *oid)
+int ref_store_remove_on_disk(struct ref_store *refs, struct strbuf *err)
+{
+ return refs->be->remove_on_disk(refs, err);
+}
+
+int repo_resolve_gitlink_ref(struct repository *r,
+ const char *submodule, const char *refname,
+ struct object_id *oid)
{
struct ref_store *refs;
int flags;
- refs = get_submodule_ref_store(submodule);
-
+ refs = repo_get_submodule_ref_store(r, submodule);
if (!refs)
return -1;
return 0;
}
-struct ref_store_hash_entry
-{
- struct hashmap_entry ent;
-
- struct ref_store *refs;
-
- /* NUL-terminated identifier of the ref store: */
- char name[FLEX_ARRAY];
-};
-
-static int ref_store_hash_cmp(const void *cmp_data UNUSED,
- const struct hashmap_entry *eptr,
- const struct hashmap_entry *entry_or_key,
- const void *keydata)
-{
- const struct ref_store_hash_entry *e1, *e2;
- const char *name;
-
- e1 = container_of(eptr, const struct ref_store_hash_entry, ent);
- e2 = container_of(entry_or_key, const struct ref_store_hash_entry, ent);
- name = keydata ? keydata : e2->name;
-
- return strcmp(e1->name, name);
-}
-
-static struct ref_store_hash_entry *alloc_ref_store_hash_entry(
- const char *name, struct ref_store *refs)
-{
- struct ref_store_hash_entry *entry;
-
- FLEX_ALLOC_STR(entry, name, name);
- hashmap_entry_init(&entry->ent, strhash(name));
- entry->refs = refs;
- return entry;
-}
-
-/* A hashmap of ref_stores, stored by submodule name: */
-static struct hashmap submodule_ref_stores;
-
-/* A hashmap of ref_stores, stored by worktree id: */
-static struct hashmap worktree_ref_stores;
-
/*
* Look up a ref store by name. If that ref_store hasn't been
* registered yet, return NULL.
*/
-static struct ref_store *lookup_ref_store_map(struct hashmap *map,
+static struct ref_store *lookup_ref_store_map(struct strmap *map,
const char *name)
{
- struct ref_store_hash_entry *entry;
- unsigned int hash;
+ struct strmap_entry *entry;
- if (!map->tablesize)
+ if (!map->map.tablesize)
/* It's initialized on demand in register_ref_store(). */
return NULL;
- hash = strhash(name);
- entry = hashmap_get_entry_from_hash(map, hash, name,
- struct ref_store_hash_entry, ent);
- return entry ? entry->refs : NULL;
+ entry = strmap_get_entry(map, name);
+ return entry ? entry->value : NULL;
}
/*
* Create, record, and return a ref_store instance for the specified
- * gitdir.
+ * gitdir using the given ref storage format.
*/
static struct ref_store *ref_store_init(struct repository *repo,
+ enum ref_storage_format format,
const char *gitdir,
unsigned int flags)
{
const struct ref_storage_be *be;
struct ref_store *refs;
- be = find_ref_storage_backend(repo->ref_storage_format);
+ be = find_ref_storage_backend(format);
if (!be)
BUG("reference backend is unknown");
return refs;
}
+void ref_store_release(struct ref_store *ref_store)
+{
+ ref_store->be->release(ref_store);
+ free(ref_store->gitdir);
+}
+
struct ref_store *get_main_ref_store(struct repository *r)
{
if (r->refs_private)
if (!r->gitdir)
BUG("attempting to get main_ref_store outside of repository");
- r->refs_private = ref_store_init(r, r->gitdir, REF_STORE_ALL_CAPS);
+ r->refs_private = ref_store_init(r, r->ref_storage_format,
+ r->gitdir, REF_STORE_ALL_CAPS);
r->refs_private = maybe_debug_wrap_ref_store(r->gitdir, r->refs_private);
return r->refs_private;
}
* Associate a ref store with a name. It is a fatal error to call this
* function twice for the same name.
*/
-static void register_ref_store_map(struct hashmap *map,
+static void register_ref_store_map(struct strmap *map,
const char *type,
struct ref_store *refs,
const char *name)
{
- struct ref_store_hash_entry *entry;
-
- if (!map->tablesize)
- hashmap_init(map, ref_store_hash_cmp, NULL, 0);
-
- entry = alloc_ref_store_hash_entry(name, refs);
- if (hashmap_put(map, &entry->ent))
+ if (!map->map.tablesize)
+ strmap_init(map);
+ if (strmap_put(map, name, refs))
BUG("%s ref_store '%s' initialized twice", type, name);
}
-struct ref_store *get_submodule_ref_store(const char *submodule)
+struct ref_store *repo_get_submodule_ref_store(struct repository *repo,
+ const char *submodule)
{
struct strbuf submodule_sb = STRBUF_INIT;
struct ref_store *refs;
/* We need to strip off one or more trailing slashes */
submodule = to_free = xmemdupz(submodule, len);
- refs = lookup_ref_store_map(&submodule_ref_stores, submodule);
+ refs = lookup_ref_store_map(&repo->submodule_ref_stores, submodule);
if (refs)
goto done;
goto done;
subrepo = xmalloc(sizeof(*subrepo));
- /*
- * NEEDSWORK: Make get_submodule_ref_store() work with arbitrary
- * superprojects other than the_repository. This probably should be
- * done by making it take a struct repository * parameter instead of a
- * submodule path.
- */
- if (repo_submodule_init(subrepo, the_repository, submodule,
+
+ if (repo_submodule_init(subrepo, repo, submodule,
null_oid())) {
free(subrepo);
goto done;
}
- refs = ref_store_init(subrepo, submodule_sb.buf,
+ refs = ref_store_init(subrepo, subrepo->ref_storage_format,
+ submodule_sb.buf,
REF_STORE_READ | REF_STORE_ODB);
- register_ref_store_map(&submodule_ref_stores, "submodule",
+ register_ref_store_map(&repo->submodule_ref_stores, "submodule",
refs, submodule);
done:
const char *id;
if (wt->is_current)
- return get_main_ref_store(the_repository);
+ return get_main_ref_store(wt->repo);
id = wt->id ? wt->id : "/";
- refs = lookup_ref_store_map(&worktree_ref_stores, id);
+ refs = lookup_ref_store_map(&wt->repo->worktree_ref_stores, id);
if (refs)
return refs;
- if (wt->id)
- refs = ref_store_init(the_repository,
- git_common_path("worktrees/%s", wt->id),
- REF_STORE_ALL_CAPS);
- else
- refs = ref_store_init(the_repository,
- get_git_common_dir(),
- REF_STORE_ALL_CAPS);
+ if (wt->id) {
+ struct strbuf common_path = STRBUF_INIT;
+ strbuf_git_common_path(&common_path, wt->repo,
+ "worktrees/%s", wt->id);
+ refs = ref_store_init(wt->repo, wt->repo->ref_storage_format,
+ common_path.buf, REF_STORE_ALL_CAPS);
+ strbuf_release(&common_path);
+ } else {
+ refs = ref_store_init(wt->repo, wt->repo->ref_storage_format,
+ wt->repo->commondir, REF_STORE_ALL_CAPS);
+ }
if (refs)
- register_ref_store_map(&worktree_ref_stores, "worktree",
- refs, id);
+ register_ref_store_map(&wt->repo->worktree_ref_stores,
+ "worktree", refs, id);
+
return refs;
}
return refs->be->pack_refs(refs, opts);
}
-int peel_iterated_oid(const struct object_id *base, struct object_id *peeled)
+int peel_iterated_oid(struct repository *r, const struct object_id *base, struct object_id *peeled)
{
if (current_ref_iter &&
(current_ref_iter->oid == base ||
oideq(current_ref_iter->oid, base)))
return ref_iterator_peel(current_ref_iter, peeled);
- return peel_object(base, peeled) ? -1 : 0;
+ return peel_object(r, base, peeled) ? -1 : 0;
}
int refs_update_symref(struct ref_store *refs, const char *ref,
const char *hook;
int ret = 0, i;
- hook = find_hook("reference-transaction");
+ hook = find_hook(transaction->ref_store->repo, "reference-transaction");
if (!hook)
return ret;
void *cb_data;
};
-static int do_for_each_reflog_helper(struct repository *r UNUSED,
- const char *refname,
+static int do_for_each_reflog_helper(const char *refname,
+ const char *referent UNUSED,
const struct object_id *oid UNUSED,
- int flags,
+ int flags UNUSED,
void *cb_data)
{
struct do_for_each_reflog_help *hp = cb_data;
iter = refs->be->reflog_iterator_begin(refs);
- return do_for_each_repo_ref_iterator(the_repository, iter,
- do_for_each_reflog_helper, &hp);
+ return do_for_each_ref_iterator(iter, do_for_each_reflog_helper, &hp);
}
int refs_for_each_reflog_ent_reverse(struct ref_store *refs,
for_each_string_list_item(item, refnames) {
ret = ref_transaction_delete(transaction, item->string,
- NULL, flags, msg, &err);
+ NULL, NULL, flags, msg, &err);
if (ret) {
warning(_("could not delete reference %s: %s"),
item->string, err.buf);
referent, update->old_target);
return -1;
}
+
+struct migration_data {
+ struct ref_store *old_refs;
+ struct ref_transaction *transaction;
+ struct strbuf *errbuf;
+};
+
+static int migrate_one_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
+ int flags, void *cb_data)
+{
+ struct migration_data *data = cb_data;
+ struct strbuf symref_target = STRBUF_INIT;
+ int ret;
+
+ if (flags & REF_ISSYMREF) {
+ ret = refs_read_symbolic_ref(data->old_refs, refname, &symref_target);
+ if (ret < 0)
+ goto done;
+
+ ret = ref_transaction_update(data->transaction, refname, NULL, null_oid(),
+ symref_target.buf, NULL,
+ REF_SKIP_CREATE_REFLOG | REF_NO_DEREF, NULL, data->errbuf);
+ if (ret < 0)
+ goto done;
+ } else {
+ ret = ref_transaction_create(data->transaction, refname, oid, NULL,
+ REF_SKIP_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION,
+ NULL, data->errbuf);
+ if (ret < 0)
+ goto done;
+ }
+
+done:
+ strbuf_release(&symref_target);
+ return ret;
+}
+
+static int move_files(const char *from_path, const char *to_path, struct strbuf *errbuf)
+{
+ struct strbuf from_buf = STRBUF_INIT, to_buf = STRBUF_INIT;
+ size_t from_len, to_len;
+ DIR *from_dir;
+ int ret;
+
+ from_dir = opendir(from_path);
+ if (!from_dir) {
+ strbuf_addf(errbuf, "could not open source directory '%s': %s",
+ from_path, strerror(errno));
+ ret = -1;
+ goto done;
+ }
+
+ strbuf_addstr(&from_buf, from_path);
+ strbuf_complete(&from_buf, '/');
+ from_len = from_buf.len;
+
+ strbuf_addstr(&to_buf, to_path);
+ strbuf_complete(&to_buf, '/');
+ to_len = to_buf.len;
+
+ while (1) {
+ struct dirent *ent;
+
+ errno = 0;
+ ent = readdir(from_dir);
+ if (!ent)
+ break;
+
+ if (!strcmp(ent->d_name, ".") ||
+ !strcmp(ent->d_name, ".."))
+ continue;
+
+ strbuf_setlen(&from_buf, from_len);
+ strbuf_addstr(&from_buf, ent->d_name);
+
+ strbuf_setlen(&to_buf, to_len);
+ strbuf_addstr(&to_buf, ent->d_name);
+
+ ret = rename(from_buf.buf, to_buf.buf);
+ if (ret < 0) {
+ strbuf_addf(errbuf, "could not link file '%s' to '%s': %s",
+ from_buf.buf, to_buf.buf, strerror(errno));
+ goto done;
+ }
+ }
+
+ if (errno) {
+ strbuf_addf(errbuf, "could not read entry from directory '%s': %s",
+ from_path, strerror(errno));
+ ret = -1;
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ strbuf_release(&from_buf);
+ strbuf_release(&to_buf);
+ if (from_dir)
+ closedir(from_dir);
+ return ret;
+}
+
+static int count_reflogs(const char *reflog UNUSED, void *payload)
+{
+ size_t *reflog_count = payload;
+ (*reflog_count)++;
+ return 0;
+}
+
+static int has_worktrees(void)
+{
+ struct worktree **worktrees = get_worktrees();
+ int ret = 0;
+ size_t i;
+
+ for (i = 0; worktrees[i]; i++) {
+ if (is_main_worktree(worktrees[i]))
+ continue;
+ ret = 1;
+ }
+
+ free_worktrees(worktrees);
+ return ret;
+}
+
+int repo_migrate_ref_storage_format(struct repository *repo,
+ enum ref_storage_format format,
+ unsigned int flags,
+ struct strbuf *errbuf)
+{
+ struct ref_store *old_refs = NULL, *new_refs = NULL;
+ struct ref_transaction *transaction = NULL;
+ struct strbuf new_gitdir = STRBUF_INIT;
+ struct migration_data data;
+ size_t reflog_count = 0;
+ int did_migrate_refs = 0;
+ int ret;
+
+ if (repo->ref_storage_format == format) {
+ strbuf_addstr(errbuf, "current and new ref storage format are equal");
+ ret = -1;
+ goto done;
+ }
+
+ old_refs = get_main_ref_store(repo);
+
+ /*
+ * We do not have any interfaces that would allow us to write many
+ * reflog entries. Once we have them we can remove this restriction.
+ */
+ if (refs_for_each_reflog(old_refs, count_reflogs, &reflog_count) < 0) {
+ strbuf_addstr(errbuf, "cannot count reflogs");
+ ret = -1;
+ goto done;
+ }
+ if (reflog_count) {
+ strbuf_addstr(errbuf, "migrating reflogs is not supported yet");
+ ret = -1;
+ goto done;
+ }
+
+ /*
+ * Worktrees complicate the migration because every worktree has a
+ * separate ref storage. While it should be feasible to implement, this
+ * is pushed out to a future iteration.
+ *
+ * TODO: we should really be passing the caller-provided repository to
+ * `has_worktrees()`, but our worktree subsystem doesn't yet support
+ * that.
+ */
+ if (has_worktrees()) {
+ strbuf_addstr(errbuf, "migrating repositories with worktrees is not supported yet");
+ ret = -1;
+ goto done;
+ }
+
+ /*
+ * The overall logic looks like this:
+ *
+ * 1. Set up a new temporary directory and initialize it with the new
+ * format. This is where all refs will be migrated into.
+ *
+ * 2. Enumerate all refs and write them into the new ref storage.
+ * This operation is safe as we do not yet modify the main
+ * repository.
+ *
+ * 3. If we're in dry-run mode then we are done and can hand over the
+ * directory to the caller for inspection. If not, we now start
+ * with the destructive part.
+ *
+ * 4. Delete the old ref storage from disk. As we have a copy of refs
+ * in the new ref storage it's okay(ish) if we now get interrupted
+ * as there is an equivalent copy of all refs available.
+ *
+ * 5. Move the new ref storage files into place.
+ *
+ * 6. Change the repository format to the new ref format.
+ */
+ strbuf_addf(&new_gitdir, "%s/%s", old_refs->gitdir, "ref_migration.XXXXXX");
+ if (!mkdtemp(new_gitdir.buf)) {
+ strbuf_addf(errbuf, "cannot create migration directory: %s",
+ strerror(errno));
+ ret = -1;
+ goto done;
+ }
+
+ new_refs = ref_store_init(repo, format, new_gitdir.buf,
+ REF_STORE_ALL_CAPS);
+ ret = ref_store_create_on_disk(new_refs, 0, errbuf);
+ if (ret < 0)
+ goto done;
+
+ transaction = ref_store_transaction_begin(new_refs, errbuf);
+ if (!transaction)
+ goto done;
+
+ data.old_refs = old_refs;
+ data.transaction = transaction;
+ data.errbuf = errbuf;
+
+ /*
+ * We need to use the internal `do_for_each_ref()` here so that we can
+ * also include broken refs and symrefs. These would otherwise be
+ * skipped silently.
+ *
+ * Ideally, we would do this call while locking the old ref storage
+ * such that there cannot be any concurrent modifications. We do not
+ * have the infra for that though, and the "files" backend does not
+ * allow for a central lock due to its design. It's thus on the user to
+ * ensure that there are no concurrent writes.
+ */
+ ret = do_for_each_ref(old_refs, "", NULL, migrate_one_ref, 0,
+ DO_FOR_EACH_INCLUDE_ROOT_REFS | DO_FOR_EACH_INCLUDE_BROKEN,
+ &data);
+ if (ret < 0)
+ goto done;
+
+ /*
+ * TODO: we might want to migrate to `initial_ref_transaction_commit()`
+ * here, which is more efficient for the files backend because it would
+ * write new refs into the packed-refs file directly. At this point,
+ * the files backend doesn't handle pseudo-refs and symrefs correctly
+ * though, so this requires some more work.
+ */
+ ret = ref_transaction_commit(transaction, errbuf);
+ if (ret < 0)
+ goto done;
+ did_migrate_refs = 1;
+
+ if (flags & REPO_MIGRATE_REF_STORAGE_FORMAT_DRYRUN) {
+ printf(_("Finished dry-run migration of refs, "
+ "the result can be found at '%s'\n"), new_gitdir.buf);
+ ret = 0;
+ goto done;
+ }
+
+ /*
+ * Release the new ref store such that any potentially-open files will
+ * be closed. This is required for platforms like Cygwin, where
+ * renaming an open file results in EPERM.
+ */
+ ref_store_release(new_refs);
+ FREE_AND_NULL(new_refs);
+
+ /*
+ * Until now we were in the non-destructive phase, where we only
+ * populated the new ref store. From hereon though we are about
+ * to get hands by deleting the old ref store and then moving
+ * the new one into place.
+ *
+ * Assuming that there were no concurrent writes, the new ref
+ * store should have all information. So if we fail from hereon
+ * we may be in an in-between state, but it would still be able
+ * to recover by manually moving remaining files from the
+ * temporary migration directory into place.
+ */
+ ret = ref_store_remove_on_disk(old_refs, errbuf);
+ if (ret < 0)
+ goto done;
+
+ ret = move_files(new_gitdir.buf, old_refs->gitdir, errbuf);
+ if (ret < 0)
+ goto done;
+
+ if (rmdir(new_gitdir.buf) < 0)
+ warning_errno(_("could not remove temporary migration directory '%s'"),
+ new_gitdir.buf);
+
+ /*
+ * We have migrated the repository, so we now need to adjust the
+ * repository format so that clients will use the new ref store.
+ * We also need to swap out the repository's main ref store.
+ */
+ initialize_repository_version(hash_algo_by_ptr(repo->hash_algo), format, 1);
+
+ /*
+ * Unset the old ref store and release it. `get_main_ref_store()` will
+ * make sure to lazily re-initialize the repository's ref store with
+ * the new format.
+ */
+ ref_store_release(old_refs);
+ FREE_AND_NULL(old_refs);
+ repo->refs_private = NULL;
+
+ ret = 0;
+
+done:
+ if (ret && did_migrate_refs) {
+ strbuf_complete(errbuf, '\n');
+ strbuf_addf(errbuf, _("migrated refs can be found at '%s'"),
+ new_gitdir.buf);
+ }
+
+ if (new_refs) {
+ ref_store_release(new_refs);
+ free(new_refs);
+ }
+ ref_transaction_free(transaction);
+ strbuf_release(&new_gitdir);
+ return ret;
+}
+
+int ref_update_expects_existing_old_ref(struct ref_update *update)
+{
+ return (update->flags & REF_HAVE_OLD) &&
+ (!is_null_oid(&update->old_oid) || update->old_target);
+}
+