diff options
Diffstat (limited to 'refs.c')
-rw-r--r-- | refs.c | 1298 |
1 files changed, 799 insertions, 499 deletions
@@ -2,11 +2,13 @@ * 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" @@ -19,11 +21,10 @@ #include "object-store-ll.h" #include "object.h" #include "path.h" -#include "tag.h" #include "submodule.h" #include "worktree.h" #include "strvec.h" -#include "repository.h" +#include "repo-settings.h" #include "setup.h" #include "sigchain.h" #include "date.h" @@ -33,17 +34,35 @@ /* * List of all available backends */ -static struct ref_storage_be *refs_backends = &refs_be_files; +static const struct ref_storage_be *refs_backends[] = { + [REF_STORAGE_FORMAT_FILES] = &refs_be_files, + [REF_STORAGE_FORMAT_REFTABLE] = &refs_be_reftable, +}; -static struct ref_storage_be *find_ref_storage_backend(const char *name) +static const struct ref_storage_be *find_ref_storage_backend( + enum ref_storage_format ref_storage_format) { - struct ref_storage_be *be; - for (be = refs_backends; be; be = be->next) - if (!strcmp(be->name, name)) - return be; + if (ref_storage_format < ARRAY_SIZE(refs_backends)) + return refs_backends[ref_storage_format]; return NULL; } +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 i; + return REF_STORAGE_FORMAT_UNKNOWN; +} + +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) + return "unknown"; + return be->name; +} + /* * How to handle various characters in refnames: * 0: An acceptable character for refs @@ -142,7 +161,7 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref) { 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; } @@ -299,6 +318,12 @@ int check_refname_format(const char *refname, int flags) return check_or_sanitize_refname(refname, flags, NULL); } +int refs_fsck(struct ref_store *refs, struct fsck_options *o, + struct worktree *wt) +{ + return refs->be->fsck(refs, o, wt); +} + void sanitize_refname_component(const char *refname, struct strbuf *out) { if (check_or_sanitize_refname(refname, REFNAME_ALLOW_ONELEVEL, out)) @@ -367,14 +392,6 @@ char *refs_resolve_refdup(struct ref_store *refs, return xstrdup_or_null(result); } -char *resolve_refdup(const char *refname, int resolve_flags, - struct object_id *oid, int *flags) -{ - return refs_resolve_refdup(get_main_ref_store(the_repository), - refname, resolve_flags, - oid, flags); -} - /* The argument to for_each_filter_refs */ struct for_each_ref_filter { const char *pattern; @@ -383,19 +400,18 @@ struct for_each_ref_filter { void *cb_data; }; -int read_ref_full(const char *refname, int resolve_flags, struct object_id *oid, int *flags) +int refs_read_ref_full(struct ref_store *refs, const char *refname, + int resolve_flags, struct object_id *oid, int *flags) { - struct ref_store *refs = get_main_ref_store(the_repository); - if (refs_resolve_ref_unsafe(refs, refname, resolve_flags, oid, flags)) return 0; return -1; } -int read_ref(const char *refname, struct object_id *oid) +int refs_read_ref(struct ref_store *refs, const char *refname, struct object_id *oid) { - return read_ref_full(refname, RESOLVE_REF_READING, oid, NULL); + return refs_read_ref_full(refs, refname, RESOLVE_REF_READING, oid, NULL); } int refs_ref_exists(struct ref_store *refs, const char *refname) @@ -404,12 +420,7 @@ int refs_ref_exists(struct ref_store *refs, const char *refname) NULL, NULL); } -int ref_exists(const char *refname) -{ - return refs_ref_exists(get_main_ref_store(the_repository), refname); -} - -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) { @@ -419,38 +430,18 @@ static int for_each_filter_refs(const char *refname, 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) { @@ -460,7 +451,7 @@ static int warn_if_dangling_symref(const char *refname, if (!(flags & REF_ISSYMREF)) return 0; - resolves_to = resolve_ref_unsafe(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) @@ -473,26 +464,28 @@ static int warn_if_dangling_symref(const char *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; - for_each_rawref(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; - for_each_rawref(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) @@ -500,32 +493,17 @@ int refs_for_each_tag_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) return refs_for_each_ref_in(refs, "refs/tags/", fn, cb_data); } -int for_each_tag_ref(each_ref_fn fn, void *cb_data) -{ - return refs_for_each_tag_ref(get_main_ref_store(the_repository), fn, cb_data); -} - int refs_for_each_branch_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) { return refs_for_each_ref_in(refs, "refs/heads/", fn, cb_data); } -int for_each_branch_ref(each_ref_fn fn, void *cb_data) -{ - return refs_for_each_branch_ref(get_main_ref_store(the_repository), fn, cb_data); -} - int refs_for_each_remote_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) { return refs_for_each_ref_in(refs, "refs/remotes/", fn, cb_data); } -int for_each_remote_ref(each_ref_fn fn, void *cb_data) -{ - return refs_for_each_remote_ref(get_main_ref_store(the_repository), fn, cb_data); -} - -int head_ref_namespaced(each_ref_fn fn, void *cb_data) +int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_data) { struct strbuf buf = STRBUF_INIT; int ret = 0; @@ -533,8 +511,8 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data) int flag; strbuf_addf(&buf, "%sHEAD", get_git_namespace()); - if (!read_ref_full(buf.buf, RESOLVE_REF_READING, &oid, &flag)) - ret = fn(buf.buf, &oid, flag, cb_data); + if (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag)) + ret = fn(buf.buf, NULL, &oid, flag, cb_data); strbuf_release(&buf); return ret; @@ -566,8 +544,8 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix, strbuf_release(&normalized_pattern); } -int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, - const char *prefix, void *cb_data) +int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn, + const char *pattern, const char *prefix, void *cb_data) { struct strbuf real_pattern = STRBUF_INIT; struct for_each_ref_filter filter; @@ -590,15 +568,16 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, filter.prefix = prefix; filter.fn = fn; filter.cb_data = cb_data; - ret = for_each_ref(for_each_filter_refs, &filter); + ret = refs_for_each_ref(refs, for_each_filter_refs, &filter); strbuf_release(&real_pattern); return ret; } -int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data) +int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn, + const char *pattern, void *cb_data) { - return for_each_glob_ref_in(fn, pattern, NULL, cb_data); + return refs_for_each_glob_ref_in(refs, fn, pattern, NULL, cb_data); } const char *prettify_refname(const char *name) @@ -694,16 +673,6 @@ char *repo_default_branch_name(struct repository *r, int quiet) 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 @@ -762,7 +731,7 @@ int expand_ref(struct repository *repo, const char *str, int len, if (r) { if (!refs_found++) *ref = xstrdup(r); - if (!warn_ambiguous_refs) + if (!repo_settings_get_warn_ambiguous_refs(repo)) break; } else if ((flag & REF_ISSYMREF) && strcmp(fullref.buf, "HEAD")) { warning(_("ignoring dangling symref %s"), fullref.buf); @@ -807,7 +776,7 @@ int repo_dwim_log(struct repository *r, const char *str, int len, if (oid) oidcpy(oid, &hash); } - if (!warn_ambiguous_refs) + if (!repo_settings_get_warn_ambiguous_refs(r)) break; } strbuf_release(&path); @@ -815,11 +784,6 @@ int repo_dwim_log(struct repository *r, const char *str, int len, 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/") || @@ -827,7 +791,22 @@ int is_per_worktree_ref(const char *refname) 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; @@ -836,15 +815,37 @@ static int is_pseudoref_syntax(const char *refname) return 0; } - /* - * HEAD is not a pseudoref, but it certainly uses the - * pseudoref syntax. - */ return 1; } +int is_root_ref(const char *refname) +{ + static const char *const irregular_root_refs[] = { + "HEAD", + "AUTO_MERGE", + "BISECT_EXPECTED_REV", + "NOTES_MERGE_PARTIAL", + "NOTES_MERGE_REF", + "MERGE_AUTOSTASH", + }; + size_t i; + + if (!is_root_ref_syntax(refname) || + is_pseudo_ref(refname)) + return 0; + + if (ends_with(refname, "_HEAD")) + return 1; + + 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, @@ -918,10 +919,10 @@ int refs_delete_ref(struct ref_store *refs, const char *msg, struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; - transaction = ref_store_transaction_begin(refs, &err); + transaction = ref_store_transaction_begin(refs, 0, &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); @@ -933,13 +934,6 @@ int refs_delete_ref(struct ref_store *refs, const char *msg, return 0; } -int delete_ref(const char *msg, const char *refname, - const struct object_id *old_oid, unsigned int flags) -{ - return refs_delete_ref(get_main_ref_store(the_repository), msg, refname, - old_oid, flags); -} - static void copy_reflog_msg(struct strbuf *sb, const char *msg) { char c; @@ -965,7 +959,8 @@ static char *normalize_reflog_message(const char *msg) return strbuf_detach(&sb, NULL); } -int should_autocreate_reflog(const char *refname) +int should_autocreate_reflog(enum log_refs_config log_all_ref_updates, + const char *refname) { switch (log_all_ref_updates) { case LOG_REFS_ALWAYS: @@ -1022,55 +1017,40 @@ static int read_ref_at_ent(struct object_id *ooid, struct object_id *noid, const char *message, void *cb_data) { struct read_ref_at_cb *cb = cb_data; - int reached_count; cb->tz = tz; cb->date = timestamp; - /* - * It is not possible for cb->cnt == 0 on the first iteration because - * that special case is handled in read_ref_at(). - */ - if (cb->cnt > 0) - cb->cnt--; - reached_count = cb->cnt == 0 && !is_null_oid(ooid); - if (timestamp <= cb->at_time || reached_count) { + if (timestamp <= cb->at_time || cb->cnt == 0) { set_read_ref_cutoffs(cb, timestamp, tz, message); /* * we have not yet updated cb->[n|o]oid so they still * hold the values for the previous record. */ - if (!is_null_oid(&cb->ooid) && !oideq(&cb->ooid, noid)) - warning(_("log for ref %s has gap after %s"), + if (!is_null_oid(&cb->ooid)) { + oidcpy(cb->oid, noid); + if (!oideq(&cb->ooid, noid)) + warning(_("log for ref %s has gap after %s"), cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822))); - if (reached_count) - oidcpy(cb->oid, ooid); - else if (!is_null_oid(&cb->ooid) || cb->date == cb->at_time) + } + else if (cb->date == cb->at_time) oidcpy(cb->oid, noid); else if (!oideq(noid, cb->oid)) warning(_("log for ref %s unexpectedly ended on %s"), cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822))); + cb->reccnt++; + oidcpy(&cb->ooid, ooid); + oidcpy(&cb->noid, noid); cb->found_it = 1; + return 1; } cb->reccnt++; oidcpy(&cb->ooid, ooid); oidcpy(&cb->noid, noid); - return cb->found_it; -} - -static int read_ref_at_ent_newest(struct object_id *ooid UNUSED, - struct object_id *noid, - const char *email UNUSED, - timestamp_t timestamp, int tz, - const char *message, void *cb_data) -{ - struct read_ref_at_cb *cb = cb_data; - - set_read_ref_cutoffs(cb, timestamp, tz, message); - oidcpy(cb->oid, noid); - /* We just want the first entry */ - return 1; + if (cb->cnt > 0) + cb->cnt--; + return 0; } static int read_ref_at_ent_oldest(struct object_id *ooid, struct object_id *noid, @@ -1082,7 +1062,7 @@ static int read_ref_at_ent_oldest(struct object_id *ooid, struct object_id *noid set_read_ref_cutoffs(cb, timestamp, tz, message); oidcpy(cb->oid, ooid); - if (is_null_oid(cb->oid)) + if (cb->at_time && is_null_oid(cb->oid)) oidcpy(cb->oid, noid); /* We just want the first entry */ return 1; @@ -1105,14 +1085,24 @@ int read_ref_at(struct ref_store *refs, const char *refname, cb.cutoff_cnt = cutoff_cnt; cb.oid = oid; - if (cb.cnt == 0) { - refs_for_each_reflog_ent_reverse(refs, refname, read_ref_at_ent_newest, &cb); - return 0; - } - refs_for_each_reflog_ent_reverse(refs, refname, read_ref_at_ent, &cb); if (!cb.reccnt) { + if (cnt == 0) { + /* + * The caller asked for ref@{0}, and we had no entries. + * It's a bit subtle, but in practice all callers have + * prepped the "oid" field with the current value of + * the ref, which is the most reasonable fallback. + * + * We'll put dummy values into the out-parameters (so + * they're not just uninitialized garbage), and the + * caller can take our return value as a hint that + * we did not find any such reflog. + */ + set_read_ref_cutoffs(&cb, 0, 0, "empty reflog"); + return 1; + } if (flags & GET_OID_QUIETLY) exit(128); else @@ -1127,6 +1117,7 @@ int read_ref_at(struct ref_store *refs, const char *refname, } struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, + unsigned int flags, struct strbuf *err) { struct ref_transaction *tr; @@ -1134,14 +1125,10 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, CALLOC_ARRAY(tr, 1); tr->ref_store = refs; + tr->flags = flags; return tr; } -struct ref_transaction *ref_transaction_begin(struct strbuf *err) -{ - return ref_store_transaction_begin(get_main_ref_store(the_repository), err); -} - void ref_transaction_free(struct ref_transaction *transaction) { size_t i; @@ -1164,6 +1151,8 @@ void ref_transaction_free(struct ref_transaction *transaction) for (i = 0; i < transaction->nr; i++) { free(transaction->updates[i]->msg); + free((char *)transaction->updates[i]->new_target); + free((char *)transaction->updates[i]->old_target); free(transaction->updates[i]); } free(transaction->updates); @@ -1175,6 +1164,7 @@ struct ref_update *ref_transaction_add_update( const char *refname, unsigned int flags, const struct object_id *new_oid, const struct object_id *old_oid, + const char *new_target, const char *old_target, const char *msg) { struct ref_update *update; @@ -1182,17 +1172,26 @@ struct ref_update *ref_transaction_add_update( if (transaction->state != REF_TRANSACTION_OPEN) BUG("update called for transaction that is not open"); + if (old_oid && old_target) + BUG("only one of old_oid and old_target should be non NULL"); + if (new_oid && new_target) + BUG("only one of new_oid and new_target should be non NULL"); + FLEX_ALLOC_STR(update, refname, refname); ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc); transaction->updates[transaction->nr++] = update; update->flags = flags; - if (flags & REF_HAVE_NEW) + update->new_target = xstrdup_or_null(new_target); + update->old_target = xstrdup_or_null(old_target); + if ((flags & REF_HAVE_NEW) && new_oid) oidcpy(&update->new_oid, new_oid); - if (flags & REF_HAVE_OLD) + if ((flags & REF_HAVE_OLD) && old_oid) oidcpy(&update->old_oid, old_oid); - update->msg = normalize_reflog_message(msg); + if (!(flags & REF_SKIP_CREATE_REFLOG)) + update->msg = normalize_reflog_message(msg); + return update; } @@ -1200,11 +1199,19 @@ int ref_transaction_update(struct ref_transaction *transaction, const char *refname, const struct object_id *new_oid, const struct object_id *old_oid, + const char *new_target, + const char *old_target, unsigned int flags, const char *msg, struct strbuf *err) { 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) : @@ -1214,6 +1221,13 @@ int ref_transaction_update(struct ref_transaction *transaction, 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); @@ -1225,49 +1239,68 @@ int ref_transaction_update(struct ref_transaction *transaction, flags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS; flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0); + flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0); ref_transaction_add_update(transaction, refname, flags, - new_oid, old_oid, msg); + new_oid, old_oid, new_target, + old_target, msg); return 0; } 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(), flags, msg, err); + 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, - flags, msg, err); + 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, old_target, flags, NULL, err); } @@ -1280,10 +1313,10 @@ int refs_update_ref(struct ref_store *refs, const char *msg, struct strbuf err = STRBUF_INIT; int ret = 0; - t = ref_store_transaction_begin(refs, &err); + t = ref_store_transaction_begin(refs, 0, &err); if (!t || - ref_transaction_update(t, refname, new_oid, old_oid, flags, msg, - &err) || + ref_transaction_update(t, refname, new_oid, old_oid, NULL, NULL, + flags, msg, &err) || ref_transaction_commit(t, &err)) { ret = 1; ref_transaction_free(t); @@ -1310,15 +1343,6 @@ int refs_update_ref(struct ref_store *refs, const char *msg, return 0; } -int update_ref(const char *msg, const char *refname, - const struct object_id *new_oid, - const struct object_id *old_oid, - unsigned int flags, enum action_on_err onerr) -{ - return refs_update_ref(get_main_ref_store(the_repository), msg, refname, new_oid, - old_oid, flags, onerr); -} - /* * Check that the string refname matches a rule of the form * "{prefix}%.*s{suffix}". So "foo/bar/baz" would match the rule @@ -1420,12 +1444,6 @@ char *refs_shorten_unambiguous_ref(struct ref_store *refs, return xstrdup(refname); } -char *shorten_unambiguous_ref(const char *refname, int strict) -{ - return refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), - refname, strict); -} - int parse_hide_refs_config(const char *var, const char *value, const char *section, struct strvec *hide_refs) { @@ -1504,6 +1522,19 @@ const char **hidden_refs_to_excludes(const struct strvec *hide_refs) return hide_refs->v; } +const char **get_namespaced_exclude_patterns(const char **exclude_patterns, + const char *namespace, + struct strvec *out) +{ + if (!namespace || !*namespace || !exclude_patterns || !*exclude_patterns) + return exclude_patterns; + + for (size_t i = 0; exclude_patterns[i]; i++) + strvec_pushf(out, "%s%s", namespace, exclude_patterns[i]); + + return out->v; +} + const char *find_descendant_ref(const char *dirname, const struct string_list *extras, const struct string_list *skip) @@ -1539,16 +1570,11 @@ int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) 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; } -int head_ref(each_ref_fn fn, void *cb_data) -{ - return refs_head_ref(get_main_ref_store(the_repository), fn, cb_data); -} - struct ref_iterator *refs_ref_iterator_begin( struct ref_store *refs, const char *prefix, @@ -1577,60 +1603,15 @@ struct ref_iterator *refs_ref_iterator_begin( if (trim) iter = prefix_ref_iterator_begin(iter, "", trim); - /* Sanity check for subclasses: */ - if (!iter->ordered) - BUG("reference iterator is not ordered"); - 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; @@ -1638,8 +1619,7 @@ static int do_for_each_ref(struct ref_store *refs, const char *prefix, 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) @@ -1647,28 +1627,12 @@ int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) return do_for_each_ref(refs, "", NULL, fn, 0, 0, cb_data); } -int for_each_ref(each_ref_fn fn, void *cb_data) -{ - return refs_for_each_ref(get_main_ref_store(the_repository), fn, cb_data); -} - int refs_for_each_ref_in(struct ref_store *refs, const char *prefix, each_ref_fn fn, void *cb_data) { return do_for_each_ref(refs, prefix, NULL, fn, strlen(prefix), 0, cb_data); } -int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data) -{ - return refs_for_each_ref_in(get_main_ref_store(the_repository), prefix, fn, cb_data); -} - -int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data) -{ - return do_for_each_ref(get_main_ref_store(the_repository), - prefix, NULL, fn, 0, 0, cb_data); -} - int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix, const char **exclude_patterns, each_ref_fn fn, void *cb_data) @@ -1676,23 +1640,31 @@ int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix, 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 for_each_namespaced_ref(const char **exclude_patterns, - each_ref_fn fn, void *cb_data) +int refs_for_each_namespaced_ref(struct ref_store *refs, + const char **exclude_patterns, + each_ref_fn fn, void *cb_data) { - struct strbuf buf = STRBUF_INIT; + struct strvec namespaced_exclude_patterns = STRVEC_INIT; + struct strbuf prefix = STRBUF_INIT; int ret; - strbuf_addf(&buf, "%srefs/", get_git_namespace()); - ret = do_for_each_ref(get_main_ref_store(the_repository), - buf.buf, exclude_patterns, fn, 0, 0, cb_data); - strbuf_release(&buf); + + exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns, + get_git_namespace(), + &namespaced_exclude_patterns); + + strbuf_addf(&prefix, "%srefs/", get_git_namespace()); + ret = do_for_each_ref(refs, prefix.buf, exclude_patterns, fn, 0, 0, cb_data); + + strvec_clear(&namespaced_exclude_patterns); + strbuf_release(&prefix); return ret; } @@ -1702,9 +1674,11 @@ int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data) DO_FOR_EACH_INCLUDE_BROKEN, cb_data); } -int for_each_rawref(each_ref_fn fn, void *cb_data) +int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn, + void *cb_data) { - return refs_for_each_rawref(get_main_ref_store(the_repository), fn, cb_data); + return do_for_each_ref(refs, "", NULL, fn, 0, + DO_FOR_EACH_INCLUDE_ROOT_REFS, cb_data); } static int qsort_strcmp(const void *va, const void *vb) @@ -1771,6 +1745,7 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store, const char **exclude_patterns, each_ref_fn fn, void *cb_data) { + struct strvec namespaced_exclude_patterns = STRVEC_INIT; struct string_list prefixes = STRING_LIST_INIT_DUP; struct string_list_item *prefix; struct strbuf buf = STRBUF_INIT; @@ -1782,6 +1757,10 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store, strbuf_addstr(&buf, namespace); namespace_len = buf.len; + exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns, + namespace, + &namespaced_exclude_patterns); + for_each_string_list_item(prefix, &prefixes) { strbuf_addstr(&buf, prefix->string); ret = refs_for_each_fullref_in(ref_store, buf.buf, @@ -1791,6 +1770,7 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store, strbuf_setlen(&buf, namespace_len); } + strvec_clear(&namespaced_exclude_patterns); string_list_clear(&prefixes, 0); strbuf_release(&buf); return ret; @@ -1806,11 +1786,13 @@ static int refs_read_special_head(struct ref_store *ref_store, int result = -1; strbuf_addf(&full_path, "%s/%s", ref_store->gitdir, refname); - if (strbuf_read_file(&content, full_path.buf, 0) < 0) + if (strbuf_read_file(&content, full_path.buf, 0) < 0) { + *failure_errno = errno; 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, NULL, failure_errno); done: strbuf_release(&full_path); @@ -1823,10 +1805,9 @@ int refs_read_raw_ref(struct ref_store *ref_store, const char *refname, unsigned int *type, int *failure_errno) { assert(failure_errno); - if (!strcmp(refname, "FETCH_HEAD") || !strcmp(refname, "MERGE_HEAD")) { + if (is_pseudo_ref(refname)) return refs_read_special_head(ref_store, refname, oid, referent, type, failure_errno); - } return ref_store->be->read_raw_ref(ref_store, refname, oid, referent, type, failure_errno); @@ -1894,7 +1875,7 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs, failure_errno != ENOTDIR) return NULL; - oidclr(oid); + oidclr(oid, refs->repo->hash_algo); if (*flags & REF_BAD_NAME) *flags |= REF_ISBROKEN; return refname; @@ -1904,7 +1885,7 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs, if (!(read_flags & REF_ISSYMREF)) { if (*flags & REF_BAD_NAME) { - oidclr(oid); + oidclr(oid, refs->repo->hash_algo); *flags |= REF_ISBROKEN; } return refname; @@ -1912,7 +1893,7 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs, 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)) { @@ -1928,28 +1909,24 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs, } /* backend functions */ -int refs_init_db(struct strbuf *err) +int ref_store_create_on_disk(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, err); + return refs->be->create_on_disk(refs, flags, err); } -const char *resolve_ref_unsafe(const char *refname, int resolve_flags, - struct object_id *oid, int *flags) +int ref_store_remove_on_disk(struct ref_store *refs, struct strbuf *err) { - return refs_resolve_ref_unsafe(get_main_ref_store(the_repository), refname, - resolve_flags, oid, flags); + return refs->be->remove_on_disk(refs, err); } -int resolve_gitlink_ref(const char *submodule, const char *refname, - struct object_id *oid) +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; @@ -1959,87 +1936,49 @@ int resolve_gitlink_ref(const char *submodule, const char *refname, 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 char *be_name = "files"; - struct ref_storage_be *be = find_ref_storage_backend(be_name); + const struct ref_storage_be *be; struct ref_store *refs; + be = find_ref_storage_backend(format); if (!be) - BUG("reference backend %s is unknown", be_name); + BUG("reference backend is unknown"); refs = be->init(repo, gitdir, flags); 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) @@ -2048,7 +1987,8 @@ struct ref_store *get_main_ref_store(struct repository *r) 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; } @@ -2057,22 +1997,19 @@ struct ref_store *get_main_ref_store(struct repository *r) * 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; @@ -2093,7 +2030,7 @@ struct ref_store *get_submodule_ref_store(const char *submodule) /* 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; @@ -2105,20 +2042,16 @@ struct ref_store *get_submodule_ref_store(const char *submodule) 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: @@ -2134,25 +2067,29 @@ struct ref_store *get_worktree_ref_store(const struct worktree *wt) 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; } @@ -2170,36 +2107,37 @@ int refs_pack_refs(struct ref_store *refs, struct pack_refs_opts *opts) 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_create_symref(struct ref_store *refs, - const char *ref_target, - const char *refs_heads_master, - const char *logmsg) +int refs_update_symref(struct ref_store *refs, const char *ref, + const char *target, const char *logmsg) { - char *msg; - int retval; + struct ref_transaction *transaction; + struct strbuf err = STRBUF_INIT; + int ret = 0; - msg = normalize_reflog_message(logmsg); - retval = refs->be->create_symref(refs, ref_target, refs_heads_master, - msg); - free(msg); - return retval; -} + transaction = ref_store_transaction_begin(refs, 0, &err); + if (!transaction || + ref_transaction_update(transaction, ref, NULL, NULL, + target, NULL, REF_NO_DEREF, + logmsg, &err) || + ref_transaction_commit(transaction, &err)) { + ret = error("%s", err.buf); + } -int create_symref(const char *ref_target, const char *refs_heads_master, - const char *logmsg) -{ - return refs_create_symref(get_main_ref_store(the_repository), ref_target, - refs_heads_master, logmsg); + strbuf_release(&err); + if (transaction) + ref_transaction_free(transaction); + + return ret; } int ref_update_reject_duplicates(struct string_list *refnames, @@ -2233,7 +2171,7 @@ static int run_transaction_hook(struct ref_transaction *transaction, 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; @@ -2251,11 +2189,26 @@ static int run_transaction_hook(struct ref_transaction *transaction, for (i = 0; i < transaction->nr; i++) { struct ref_update *update = transaction->updates[i]; + if (update->flags & REF_LOG_ONLY) + continue; + strbuf_reset(&buf); - strbuf_addf(&buf, "%s %s %s\n", - oid_to_hex(&update->old_oid), - oid_to_hex(&update->new_oid), - update->refname); + + if (!(update->flags & REF_HAVE_OLD)) + strbuf_addf(&buf, "%s ", oid_to_hex(null_oid())); + else if (update->old_target) + strbuf_addf(&buf, "ref:%s ", update->old_target); + else + strbuf_addf(&buf, "%s ", oid_to_hex(&update->old_oid)); + + if (!(update->flags & REF_HAVE_NEW)) + strbuf_addf(&buf, "%s ", oid_to_hex(null_oid())); + else if (update->new_target) + strbuf_addf(&buf, "ref:%s ", update->new_target); + else + strbuf_addf(&buf, "%s ", oid_to_hex(&update->new_oid)); + + strbuf_addf(&buf, "%s\n", update->refname); if (write_in_full(proc.in, buf.buf, buf.len) < 0) { if (errno != EPIPE) { @@ -2367,7 +2320,7 @@ int ref_transaction_commit(struct ref_transaction *transaction, } ret = refs->be->transaction_finish(refs, transaction, err); - if (!ret) + if (!ret && !(transaction->flags & REF_TRANSACTION_FLAG_INITIAL)) run_transaction_hook(transaction, "committed"); return ret; } @@ -2376,6 +2329,7 @@ int refs_verify_refname_available(struct ref_store *refs, const char *refname, const struct string_list *extras, const struct string_list *skip, + unsigned int initial_transaction, struct strbuf *err) { const char *slash; @@ -2384,8 +2338,6 @@ int refs_verify_refname_available(struct ref_store *refs, struct strbuf referent = STRBUF_INIT; struct object_id oid; unsigned int type; - struct ref_iterator *iter; - int ok; int ret = -1; /* @@ -2415,7 +2367,8 @@ int refs_verify_refname_available(struct ref_store *refs, if (skip && string_list_has_string(skip, dirname.buf)) continue; - if (!refs_read_raw_ref(refs, dirname.buf, &oid, &referent, + if (!initial_transaction && + !refs_read_raw_ref(refs, dirname.buf, &oid, &referent, &type, &ignore_errno)) { strbuf_addf(err, _("'%s' exists; cannot create '%s'"), dirname.buf, refname); @@ -2440,21 +2393,26 @@ int refs_verify_refname_available(struct ref_store *refs, strbuf_addstr(&dirname, refname + dirname.len); strbuf_addch(&dirname, '/'); - iter = refs_ref_iterator_begin(refs, dirname.buf, NULL, 0, - DO_FOR_EACH_INCLUDE_BROKEN); - while ((ok = ref_iterator_advance(iter)) == ITER_OK) { - if (skip && - string_list_has_string(skip, iter->refname)) - continue; + if (!initial_transaction) { + struct ref_iterator *iter; + int ok; - strbuf_addf(err, _("'%s' exists; cannot create '%s'"), - iter->refname, refname); - ref_iterator_abort(iter); - goto cleanup; - } + iter = refs_ref_iterator_begin(refs, dirname.buf, NULL, 0, + DO_FOR_EACH_INCLUDE_BROKEN); + while ((ok = ref_iterator_advance(iter)) == ITER_OK) { + if (skip && + string_list_has_string(skip, iter->refname)) + continue; + + strbuf_addf(err, _("'%s' exists; cannot create '%s'"), + iter->refname, refname); + ref_iterator_abort(iter); + goto cleanup; + } - if (ok != ITER_DONE) - BUG("error while iterating over references"); + if (ok != ITER_DONE) + BUG("error while iterating over references"); + } extra_refname = find_descendant_ref(dirname.buf, extras, skip); if (extra_refname) @@ -2469,20 +2427,29 @@ cleanup: return ret; } -int refs_for_each_reflog(struct ref_store *refs, each_ref_fn fn, void *cb_data) +struct do_for_each_reflog_help { + each_reflog_fn *fn; + void *cb_data; +}; + +static int do_for_each_reflog_helper(const char *refname, + const char *referent UNUSED, + const struct object_id *oid UNUSED, + int flags UNUSED, + void *cb_data) +{ + struct do_for_each_reflog_help *hp = cb_data; + return hp->fn(refname, hp->cb_data); +} + +int refs_for_each_reflog(struct ref_store *refs, each_reflog_fn fn, void *cb_data) { struct ref_iterator *iter; - struct do_for_each_ref_help hp = { fn, cb_data }; + struct do_for_each_reflog_help hp = { fn, cb_data }; iter = refs->be->reflog_iterator_begin(refs); - return do_for_each_repo_ref_iterator(the_repository, iter, - do_for_each_ref_helper, &hp); -} - -int for_each_reflog(each_ref_fn fn, void *cb_data) -{ - return refs_for_each_reflog(get_main_ref_store(the_repository), fn, cb_data); + return do_for_each_ref_iterator(iter, do_for_each_reflog_helper, &hp); } int refs_for_each_reflog_ent_reverse(struct ref_store *refs, @@ -2494,58 +2461,28 @@ int refs_for_each_reflog_ent_reverse(struct ref_store *refs, fn, cb_data); } -int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, - void *cb_data) -{ - return refs_for_each_reflog_ent_reverse(get_main_ref_store(the_repository), - refname, fn, cb_data); -} - int refs_for_each_reflog_ent(struct ref_store *refs, const char *refname, each_reflog_ent_fn fn, void *cb_data) { return refs->be->for_each_reflog_ent(refs, refname, fn, cb_data); } -int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, - void *cb_data) -{ - return refs_for_each_reflog_ent(get_main_ref_store(the_repository), refname, - fn, cb_data); -} - int refs_reflog_exists(struct ref_store *refs, const char *refname) { return refs->be->reflog_exists(refs, refname); } -int reflog_exists(const char *refname) -{ - return refs_reflog_exists(get_main_ref_store(the_repository), refname); -} - int refs_create_reflog(struct ref_store *refs, const char *refname, struct strbuf *err) { return refs->be->create_reflog(refs, refname, err); } -int safe_create_reflog(const char *refname, struct strbuf *err) -{ - return refs_create_reflog(get_main_ref_store(the_repository), refname, - err); -} - int refs_delete_reflog(struct ref_store *refs, const char *refname) { return refs->be->delete_reflog(refs, refname); } -int delete_reflog(const char *refname) -{ - return refs_delete_reflog(get_main_ref_store(the_repository), refname); -} - int refs_reflog_expire(struct ref_store *refs, const char *refname, unsigned int flags, @@ -2559,27 +2496,6 @@ int refs_reflog_expire(struct ref_store *refs, cleanup_fn, policy_cb_data); } -int reflog_expire(const char *refname, - unsigned int flags, - reflog_expiry_prepare_fn prepare_fn, - reflog_expiry_should_prune_fn should_prune_fn, - reflog_expiry_cleanup_fn cleanup_fn, - void *policy_cb_data) -{ - return refs_reflog_expire(get_main_ref_store(the_repository), - refname, flags, - prepare_fn, should_prune_fn, - cleanup_fn, policy_cb_data); -} - -int initial_ref_transaction_commit(struct ref_transaction *transaction, - struct strbuf *err) -{ - struct ref_store *refs = transaction->ref_store; - - return refs->be->initial_transaction_commit(refs, transaction, err); -} - void ref_transaction_for_each_queued_update(struct ref_transaction *transaction, ref_transaction_for_each_queued_update_fn cb, void *cb_data) @@ -2599,19 +2515,55 @@ void ref_transaction_for_each_queued_update(struct ref_transaction *transaction, int refs_delete_refs(struct ref_store *refs, const char *logmsg, struct string_list *refnames, unsigned int flags) { + struct ref_transaction *transaction; + struct strbuf err = STRBUF_INIT; + struct string_list_item *item; + int ret = 0, failures = 0; char *msg; - int retval; + + if (!refnames->nr) + return 0; msg = normalize_reflog_message(logmsg); - retval = refs->be->delete_refs(refs, msg, refnames, flags); - free(msg); - return retval; -} -int delete_refs(const char *msg, struct string_list *refnames, - unsigned int flags) -{ - return refs_delete_refs(get_main_ref_store(the_repository), msg, refnames, flags); + /* + * Since we don't check the references' old_oids, the + * individual updates can't fail, so we can pack all of the + * updates into a single transaction. + */ + transaction = ref_store_transaction_begin(refs, 0, &err); + if (!transaction) { + ret = error("%s", err.buf); + goto out; + } + + for_each_string_list_item(item, refnames) { + ret = ref_transaction_delete(transaction, item->string, + NULL, NULL, flags, msg, &err); + if (ret) { + warning(_("could not delete reference %s: %s"), + item->string, err.buf); + strbuf_reset(&err); + failures = 1; + } + } + + ret = ref_transaction_commit(transaction, &err); + if (ret) { + if (refnames->nr == 1) + error(_("could not delete reference %s: %s"), + refnames->items[0].string, err.buf); + else + error(_("could not delete references: %s"), err.buf); + } + +out: + if (!ret && failures) + ret = -1; + ref_transaction_free(transaction); + strbuf_release(&err); + free(msg); + return ret; } int refs_rename_ref(struct ref_store *refs, const char *oldref, @@ -2626,11 +2578,6 @@ int refs_rename_ref(struct ref_store *refs, const char *oldref, return retval; } -int rename_ref(const char *oldref, const char *newref, const char *logmsg) -{ - return refs_rename_ref(get_main_ref_store(the_repository), oldref, newref, logmsg); -} - int refs_copy_existing_ref(struct ref_store *refs, const char *oldref, const char *newref, const char *logmsg) { @@ -2643,7 +2590,360 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref, return retval; } -int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg) +const char *ref_update_original_update_refname(struct ref_update *update) +{ + while (update->parent_update) + update = update->parent_update; + + return update->refname; +} + +int ref_update_has_null_new_value(struct ref_update *update) +{ + return !update->new_target && is_null_oid(&update->new_oid); +} + +int ref_update_check_old_target(const char *referent, struct ref_update *update, + struct strbuf *err) { - return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg); + if (!update->old_target) + BUG("called without old_target set"); + + if (!strcmp(referent, update->old_target)) + return 0; + + if (!strcmp(referent, "")) + strbuf_addf(err, "verifying symref target: '%s': " + "reference is missing but expected %s", + ref_update_original_update_refname(update), + update->old_target); + else + strbuf_addf(err, "verifying symref target: '%s': " + "is at %s but expected %s", + ref_update_original_update_refname(update), + 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, REF_TRANSACTION_FLAG_INITIAL, + 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; + + 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); +} + |