diff options
Diffstat (limited to 'refs.c')
| -rw-r--r-- | refs.c | 810 |
1 files changed, 604 insertions, 206 deletions
@@ -18,7 +18,7 @@ #include "run-command.h" #include "hook.h" #include "object-name.h" -#include "object-store-ll.h" +#include "odb.h" #include "object.h" #include "path.h" #include "submodule.h" @@ -30,6 +30,8 @@ #include "date.h" #include "commit.h" #include "wildmatch.h" +#include "ident.h" +#include "fsck.h" /* * List of all available backends @@ -318,9 +320,13 @@ 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) +int refs_fsck(struct ref_store *refs, struct fsck_options *o, + struct worktree *wt) { - return refs->be->fsck(refs, o); + if (o->verbose) + fprintf_ln(stderr, _("Checking references consistency")); + + return refs->be->fsck(refs, o, wt); } void sanitize_refname_component(const char *refname, struct strbuf *out) @@ -373,7 +379,8 @@ int ref_resolves_to_object(const char *refname, { if (flags & REF_ISBROKEN) return 0; - if (!repo_has_object_file(repo, oid)) { + if (!odb_has_object(repo->objects, oid, + HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR)) { error(_("%s does not point to a valid object!"), refname); return 0; } @@ -435,9 +442,9 @@ static int for_each_filter_refs(const char *refname, const char *referent, struct warn_if_dangling_data { struct ref_store *refs; FILE *fp; - const char *refname; const struct string_list *refnames; - const char *msg_fmt; + const char *indent; + int dry_run; }; static int warn_if_dangling_symref(const char *refname, const char *referent UNUSED, @@ -445,44 +452,34 @@ static int warn_if_dangling_symref(const char *refname, const char *referent UNU int flags, void *cb_data) { struct warn_if_dangling_data *d = cb_data; - const char *resolves_to; + const char *resolves_to, *msg; if (!(flags & REF_ISSYMREF)) return 0; resolves_to = refs_resolve_ref_unsafe(d->refs, refname, 0, NULL, NULL); if (!resolves_to - || (d->refname - ? strcmp(resolves_to, d->refname) - : !string_list_has_string(d->refnames, resolves_to))) { + || !string_list_has_string(d->refnames, resolves_to)) { return 0; } - fprintf(d->fp, d->msg_fmt, refname); - fputc('\n', d->fp); + msg = d->dry_run + ? _("%s%s will become dangling after %s is deleted\n") + : _("%s%s has become dangling after %s was deleted\n"); + fprintf(d->fp, msg, d->indent, refname, resolves_to); return 0; } -void refs_warn_dangling_symref(struct ref_store *refs, FILE *fp, - const char *msg_fmt, const char *refname) -{ - 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 refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp, - const char *msg_fmt, const struct string_list *refnames) + const char *indent, int dry_run, + const struct string_list *refnames) { struct warn_if_dangling_data data = { .refs = refs, .fp = fp, .refnames = refnames, - .msg_fmt = msg_fmt, + .indent = indent, + .dry_run = dry_run, }; refs_for_each_rawref(refs, warn_if_dangling_symref, &data); } @@ -633,10 +630,12 @@ void expand_ref_prefix(struct strvec *prefixes, const char *prefix) strvec_pushf(prefixes, *p, len, prefix); } +#ifndef WITH_BREAKING_CHANGES static const char default_branch_name_advice[] = N_( "Using '%s' as the name for the initial branch. This default branch name\n" -"is subject to change. To configure the initial branch name to use in all\n" -"of your new repositories, which will suppress this warning, call:\n" +"will change to \"main\" in Git 3.0. To configure the initial branch name\n" +"to use in all of your new repositories, which will suppress this warning,\n" +"call:\n" "\n" "\tgit config --global init.defaultBranch <name>\n" "\n" @@ -645,6 +644,15 @@ static const char default_branch_name_advice[] = N_( "\n" "\tgit branch -m <name>\n" ); +#else +static const char default_branch_name_advice[] = N_( +"Using '%s' as the name for the initial branch since Git 3.0.\n" +"If you expected Git to create 'master', the just-created\n" +"branch can be renamed via this command:\n" +"\n" +"\tgit branch -m master\n" +); +#endif /* WITH_BREAKING_CHANGES */ char *repo_default_branch_name(struct repository *r, int quiet) { @@ -655,13 +663,18 @@ char *repo_default_branch_name(struct repository *r, int quiet) if (env && *env) ret = xstrdup(env); - else if (repo_config_get_string(r, config_key, &ret) < 0) + if (!ret && repo_config_get_string(r, config_key, &ret) < 0) die(_("could not retrieve `%s`"), config_display_key); if (!ret) { +#ifdef WITH_BREAKING_CHANGES + ret = xstrdup("main"); +#else ret = xstrdup("master"); +#endif /* WITH_BREAKING_CHANGES */ if (!quiet) - advise(_(default_branch_name_advice), ret); + advise_if_enabled(ADVICE_DEFAULT_BRANCH_NAME, + _(default_branch_name_advice), ret); } full_ref = xstrfmt("refs/heads/%s", ret); @@ -697,6 +710,53 @@ static char *substitute_branch_name(struct repository *r, return NULL; } +void copy_branchname(struct strbuf *sb, const char *name, unsigned allowed) +{ + int len = strlen(name); + struct interpret_branch_name_options options = { + .allowed = allowed + }; + int used = repo_interpret_branch_name(the_repository, name, len, sb, + &options); + + if (used < 0) + used = 0; + strbuf_add(sb, name + used, len - used); +} + +int check_branch_ref(struct strbuf *sb, const char *name) +{ + if (startup_info->have_repository) + copy_branchname(sb, name, INTERPRET_BRANCH_LOCAL); + else + strbuf_addstr(sb, name); + + /* + * This splice must be done even if we end up rejecting the + * name; builtin/branch.c::copy_or_rename_branch() still wants + * to see what the name expanded to so that "branch -m" can be + * used as a tool to correct earlier mistakes. + */ + strbuf_splice(sb, 0, 0, "refs/heads/", 11); + + if (*name == '-' || + !strcmp(sb->buf, "refs/heads/HEAD")) + return -1; + + return check_refname_format(sb->buf, 0); +} + +int check_tag_ref(struct strbuf *sb, const char *name) +{ + if (name[0] == '-' || !strcmp(name, "HEAD")) + return -1; + + strbuf_reset(sb); + strbuf_addf(sb, "refs/tags/%s", name); + + return check_refname_format(sb->buf, 0); +} + int repo_dwim_ref(struct repository *r, const char *str, int len, struct object_id *oid, char **ref, int nonfatal_dangling_mark) { @@ -903,7 +963,7 @@ long get_files_ref_lock_timeout_ms(void) static int timeout_ms = 100; if (!configured) { - git_config_get_int("core.filesreflocktimeout", &timeout_ms); + repo_config_get_int(the_repository, "core.filesreflocktimeout", &timeout_ms); configured = 1; } @@ -918,7 +978,7 @@ 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, NULL, flags, msg, &err) || @@ -980,7 +1040,6 @@ int is_branch(const char *refname) } struct read_ref_at_cb { - const char *refname; timestamp_t at_time; int cnt; int reccnt; @@ -1010,7 +1069,8 @@ static void set_read_ref_cutoffs(struct read_ref_at_cb *cb, *cb->cutoff_cnt = cb->reccnt; } -static int read_ref_at_ent(struct object_id *ooid, struct object_id *noid, +static int read_ref_at_ent(const char *refname, + struct object_id *ooid, struct object_id *noid, const char *email UNUSED, timestamp_t timestamp, int tz, const char *message, void *cb_data) @@ -1030,14 +1090,13 @@ static int read_ref_at_ent(struct object_id *ooid, struct object_id *noid, 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))); + refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822))); } 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))); + refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822))); cb->reccnt++; oidcpy(&cb->ooid, ooid); oidcpy(&cb->noid, noid); @@ -1052,7 +1111,8 @@ static int read_ref_at_ent(struct object_id *ooid, struct object_id *noid, return 0; } -static int read_ref_at_ent_oldest(struct object_id *ooid, struct object_id *noid, +static int read_ref_at_ent_oldest(const char *refname UNUSED, + struct object_id *ooid, struct object_id *noid, const char *email UNUSED, timestamp_t timestamp, int tz, const char *message, void *cb_data) @@ -1075,7 +1135,6 @@ int read_ref_at(struct ref_store *refs, const char *refname, struct read_ref_at_cb cb; memset(&cb, 0, sizeof(cb)); - cb.refname = refname; cb.at_time = at_time; cb.cnt = cnt; cb.msg = msg; @@ -1116,6 +1175,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; @@ -1123,6 +1183,12 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, CALLOC_ARRAY(tr, 1); tr->ref_store = refs; + tr->flags = flags; + string_list_init_dup(&tr->refnames); + + if (flags & REF_TRANSACTION_ALLOW_FAILURE) + CALLOC_ARRAY(tr->rejections, 1); + return tr; } @@ -1148,22 +1214,67 @@ void ref_transaction_free(struct ref_transaction *transaction) for (i = 0; i < transaction->nr; i++) { free(transaction->updates[i]->msg); + free(transaction->updates[i]->committer_info); free((char *)transaction->updates[i]->new_target); free((char *)transaction->updates[i]->old_target); free(transaction->updates[i]); } + + if (transaction->rejections) + free(transaction->rejections->update_indices); + free(transaction->rejections); + + string_list_clear(&transaction->refnames, 0); free(transaction->updates); free(transaction); } +int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction, + size_t update_idx, + enum ref_transaction_error err) +{ + if (update_idx >= transaction->nr) + BUG("trying to set rejection on invalid update index"); + + if (!(transaction->flags & REF_TRANSACTION_ALLOW_FAILURE)) + return 0; + + if (!transaction->rejections) + BUG("transaction not initialized with failure support"); + + /* + * Don't accept generic errors, since these errors are not user + * input related. + */ + if (err == REF_TRANSACTION_ERROR_GENERIC) + return 0; + + /* + * Rejected refnames shouldn't be considered in the availability + * checks, so remove them from the list. + */ + string_list_remove(&transaction->refnames, + transaction->updates[update_idx]->refname, 0); + + transaction->updates[update_idx]->rejection_err = err; + ALLOC_GROW(transaction->rejections->update_indices, + transaction->rejections->nr + 1, + transaction->rejections->alloc); + transaction->rejections->update_indices[transaction->rejections->nr++] = update_idx; + + return 1; +} + struct ref_update *ref_transaction_add_update( struct ref_transaction *transaction, 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 *committer_info, const char *msg) { + struct string_list_item *item; struct ref_update *update; if (transaction->state != REF_TRANSACTION_OPEN) @@ -1179,6 +1290,7 @@ struct ref_update *ref_transaction_add_update( transaction->updates[transaction->nr++] = update; update->flags = flags; + update->rejection_err = 0; update->new_target = xstrdup_or_null(new_target); update->old_target = xstrdup_or_null(old_target); @@ -1186,11 +1298,54 @@ struct ref_update *ref_transaction_add_update( oidcpy(&update->new_oid, new_oid); if ((flags & REF_HAVE_OLD) && old_oid) oidcpy(&update->old_oid, old_oid); + if (!(flags & REF_SKIP_CREATE_REFLOG)) { + update->committer_info = xstrdup_or_null(committer_info); + update->msg = normalize_reflog_message(msg); + } + + /* + * This list is generally used by the backends to avoid duplicates. + * But we do support multiple log updates for a given refname within + * a single transaction. + */ + if (!(update->flags & REF_LOG_ONLY)) { + item = string_list_append(&transaction->refnames, refname); + item->util = update; + } - update->msg = normalize_reflog_message(msg); return update; } +static int transaction_refname_valid(const char *refname, + const struct object_id *new_oid, + unsigned int flags, struct strbuf *err) +{ + if (flags & REF_SKIP_REFNAME_VERIFICATION) + return 1; + + if (is_pseudo_ref(refname)) { + const char *refusal_msg; + if (flags & REF_LOG_ONLY) + refusal_msg = _("refusing to update reflog for pseudoref '%s'"); + else + refusal_msg = _("refusing to update pseudoref '%s'"); + strbuf_addf(err, refusal_msg, refname); + return 0; + } else if ((new_oid && !is_null_oid(new_oid)) ? + check_refname_format(refname, REFNAME_ALLOW_ONELEVEL) : + !refname_is_safe(refname)) { + const char *refusal_msg; + if (flags & REF_LOG_ONLY) + refusal_msg = _("refusing to update reflog with bad name '%s'"); + else + refusal_msg = _("refusing to update ref with bad name '%s'"); + strbuf_addf(err, refusal_msg, refname); + return 0; + } + + return 1; +} + int ref_transaction_update(struct ref_transaction *transaction, const char *refname, const struct object_id *new_oid, @@ -1208,21 +1363,8 @@ int ref_transaction_update(struct ref_transaction *transaction, return -1; } - if (!(flags & REF_SKIP_REFNAME_VERIFICATION) && - ((new_oid && !is_null_oid(new_oid)) ? - check_refname_format(refname, REFNAME_ALLOW_ONELEVEL) : - !refname_is_safe(refname))) { - strbuf_addf(err, _("refusing to update ref with bad name '%s'"), - refname); - return -1; - } - - if (!(flags & REF_SKIP_REFNAME_VERIFICATION) && - is_pseudo_ref(refname)) { - strbuf_addf(err, _("refusing to update pseudoref '%s'"), - refname); + if (!transaction_refname_valid(refname, new_oid, flags, err)) return -1; - } if (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS) BUG("illegal flags 0x%x passed to ref_transaction_update()", flags); @@ -1239,7 +1381,43 @@ int ref_transaction_update(struct ref_transaction *transaction, ref_transaction_add_update(transaction, refname, flags, new_oid, old_oid, new_target, - old_target, msg); + old_target, NULL, msg); + + return 0; +} + +int ref_transaction_update_reflog(struct ref_transaction *transaction, + const char *refname, + const struct object_id *new_oid, + const struct object_id *old_oid, + const char *committer_info, + const char *msg, + uint64_t index, + struct strbuf *err) +{ + struct ref_update *update; + unsigned int flags; + + assert(err); + + flags = REF_HAVE_OLD | REF_HAVE_NEW | REF_LOG_ONLY | REF_FORCE_CREATE_REFLOG | REF_NO_DEREF | + REF_LOG_USE_PROVIDED_OIDS; + + if (!transaction_refname_valid(refname, new_oid, flags, err)) + return -1; + + update = ref_transaction_add_update(transaction, refname, flags, + new_oid, old_oid, NULL, NULL, + committer_info, msg); + update->index = index; + + /* + * Reference backends may need to know the max index to optimize + * their writes. So we store the max_index on the transaction level. + */ + if (index > transaction->max_index) + transaction->max_index = index; + return 0; } @@ -1257,7 +1435,7 @@ int ref_transaction_create(struct ref_transaction *transaction, return 1; } return ref_transaction_update(transaction, refname, new_oid, - null_oid(), new_target, NULL, flags, + null_oid(the_hash_algo), new_target, NULL, flags, msg, err); } @@ -1276,7 +1454,7 @@ int ref_transaction_delete(struct ref_transaction *transaction, 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_oid(the_hash_algo), old_oid, NULL, old_target, flags, msg, err); } @@ -1309,7 +1487,7 @@ 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, NULL, NULL, flags, msg, &err) || @@ -1535,8 +1713,6 @@ const char *find_descendant_ref(const char *dirname, const struct string_list *extras, const struct string_list *skip) { - int pos; - if (!extras) return NULL; @@ -1546,7 +1722,7 @@ const char *find_descendant_ref(const char *dirname, * with dirname (remember, dirname includes the trailing * slash) and is not in skip, then we have a conflict. */ - for (pos = string_list_find_insert_index(extras, dirname, 0); + for (size_t pos = string_list_find_insert_index(extras, dirname, NULL); pos < extras->nr; pos++) { const char *extra_refname = extras->items[pos].string; @@ -1579,6 +1755,24 @@ struct ref_iterator *refs_ref_iterator_begin( enum do_for_each_ref_flags flags) { struct ref_iterator *iter; + struct strvec normalized_exclude_patterns = STRVEC_INIT; + + if (exclude_patterns) { + for (size_t i = 0; exclude_patterns[i]; i++) { + const char *pattern = exclude_patterns[i]; + size_t len = strlen(pattern); + if (!len) + continue; + + if (pattern[len - 1] == '/') + strvec_push(&normalized_exclude_patterns, pattern); + else + strvec_pushf(&normalized_exclude_patterns, "%s/", + pattern); + } + + exclude_patterns = normalized_exclude_patterns.v; + } if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) { static int ref_paranoia = -1; @@ -1599,6 +1793,8 @@ struct ref_iterator *refs_ref_iterator_begin( if (trim) iter = prefix_ref_iterator_begin(iter, "", trim); + strvec_clear(&normalized_exclude_patterns); + return iter; } @@ -1666,7 +1862,13 @@ int refs_for_each_namespaced_ref(struct ref_store *refs, int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data) { - return do_for_each_ref(refs, "", NULL, fn, 0, + return refs_for_each_rawref_in(refs, "", fn, cb_data); +} + +int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix, + each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(refs, prefix, NULL, fn, 0, DO_FOR_EACH_INCLUDE_BROKEN, cb_data); } @@ -1788,7 +1990,7 @@ static int refs_read_special_head(struct ref_store *ref_store, } result = parse_loose_ref_contents(ref_store->repo->hash_algo, content.buf, - oid, referent, type, failure_errno); + oid, referent, type, NULL, failure_errno); done: strbuf_release(&full_path); @@ -2034,13 +2236,13 @@ struct ref_store *repo_get_submodule_ref_store(struct repository *repo, if (!is_nonbare_repository_dir(&submodule_sb)) goto done; - if (submodule_to_gitdir(&submodule_sb, submodule)) + if (submodule_to_gitdir(repo, &submodule_sb, submodule)) goto done; subrepo = xmalloc(sizeof(*subrepo)); if (repo_submodule_init(subrepo, repo, submodule, - null_oid())) { + null_oid(the_hash_algo))) { free(subrepo); goto done; } @@ -2072,8 +2274,8 @@ struct ref_store *get_worktree_ref_store(const struct worktree *wt) if (wt->id) { struct strbuf common_path = STRBUF_INIT; - strbuf_git_common_path(&common_path, wt->repo, - "worktrees/%s", wt->id); + repo_common_path_append(wt->repo, &common_path, + "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); @@ -2103,6 +2305,11 @@ int refs_pack_refs(struct ref_store *refs, struct pack_refs_opts *opts) return refs->be->pack_refs(refs, opts); } +int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts) +{ + return refs->be->optimize(refs, opts); +} + int peel_iterated_oid(struct repository *r, const struct object_id *base, struct object_id *peeled) { if (current_ref_iter && @@ -2116,19 +2323,53 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct int refs_update_symref(struct ref_store *refs, const char *ref, const char *target, const char *logmsg) { + return refs_update_symref_extended(refs, ref, target, logmsg, NULL, 0); +} + +int refs_update_symref_extended(struct ref_store *refs, const char *ref, + const char *target, const char *logmsg, + struct strbuf *referent, int create_only) +{ struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; - int ret = 0; + int ret = 0, prepret = 0; - transaction = ref_store_transaction_begin(refs, &err); - if (!transaction || - ref_transaction_update(transaction, ref, NULL, NULL, - target, NULL, REF_NO_DEREF, - logmsg, &err) || - ref_transaction_commit(transaction, &err)) { + transaction = ref_store_transaction_begin(refs, 0, &err); + if (!transaction) { + error_return: ret = error("%s", err.buf); + goto cleanup; + } + if (create_only) { + if (ref_transaction_create(transaction, ref, NULL, target, + REF_NO_DEREF, logmsg, &err)) + goto error_return; + prepret = ref_transaction_prepare(transaction, &err); + if (prepret && prepret != REF_TRANSACTION_ERROR_CREATE_EXISTS) + goto error_return; + } else { + if (ref_transaction_update(transaction, ref, NULL, NULL, + target, NULL, REF_NO_DEREF, + logmsg, &err) || + ref_transaction_prepare(transaction, &err)) + goto error_return; + } + + if (referent && refs_read_symbolic_ref(refs, ref, referent) == NOT_A_SYMREF) { + struct object_id oid; + if (!refs_read_ref(refs, ref, &oid)) { + strbuf_addstr(referent, oid_to_hex(&oid)); + ret = NOT_A_SYMREF; + } } + if (prepret == REF_TRANSACTION_ERROR_CREATE_EXISTS) + goto cleanup; + + if (ref_transaction_commit(transaction, &err)) + goto error_return; + +cleanup: strbuf_release(&err); if (transaction) ref_transaction_free(transaction); @@ -2136,8 +2377,13 @@ int refs_update_symref(struct ref_store *refs, const char *ref, return ret; } -int ref_update_reject_duplicates(struct string_list *refnames, - struct strbuf *err) +/* + * Write an error to `err` and return a nonzero value iff the same + * refname appears multiple times in `refnames`. `refnames` must be + * sorted on entry to this function. + */ +static int ref_update_reject_duplicates(struct string_list *refnames, + struct strbuf *err) { size_t i, n = refnames->nr; @@ -2165,7 +2411,7 @@ static int run_transaction_hook(struct ref_transaction *transaction, struct child_process proc = CHILD_PROCESS_INIT; struct strbuf buf = STRBUF_INIT; const char *hook; - int ret = 0, i; + int ret = 0; hook = find_hook(transaction->ref_store->repo, "reference-transaction"); if (!hook) @@ -2182,20 +2428,23 @@ static int run_transaction_hook(struct ref_transaction *transaction, sigchain_push(SIGPIPE, SIG_IGN); - for (i = 0; i < transaction->nr; i++) { + for (size_t i = 0; i < transaction->nr; i++) { struct ref_update *update = transaction->updates[i]; + if (update->flags & REF_LOG_ONLY) + continue; + strbuf_reset(&buf); if (!(update->flags & REF_HAVE_OLD)) - strbuf_addf(&buf, "%s ", oid_to_hex(null_oid())); + strbuf_addf(&buf, "%s ", oid_to_hex(null_oid(the_hash_algo))); 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())); + strbuf_addf(&buf, "%s ", oid_to_hex(null_oid(the_hash_algo))); else if (update->new_target) strbuf_addf(&buf, "ref:%s ", update->new_target); else @@ -2242,12 +2491,16 @@ int ref_transaction_prepare(struct ref_transaction *transaction, break; } - if (refs->repo->objects->odb->disable_ref_updates) { + if (refs->repo->objects->sources->disable_ref_updates) { strbuf_addstr(err, _("ref updates forbidden inside quarantine environment")); return -1; } + string_list_sort(&transaction->refnames); + if (ref_update_reject_duplicates(&transaction->refnames, err)) + return REF_TRANSACTION_ERROR_GENERIC; + ret = refs->be->transaction_prepare(refs, transaction, err); if (ret) return ret; @@ -2313,26 +2566,26 @@ 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; } -int refs_verify_refname_available(struct ref_store *refs, - const char *refname, - const struct string_list *extras, - const struct string_list *skip, - struct strbuf *err) +enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs, + const struct string_list *refnames, + const struct string_list *extras, + const struct string_list *skip, + struct ref_transaction *transaction, + unsigned int initial_transaction, + struct strbuf *err) { - const char *slash; - const char *extra_refname; struct strbuf dirname = STRBUF_INIT; struct strbuf referent = STRBUF_INIT; - struct object_id oid; - unsigned int type; - struct ref_iterator *iter; - int ok; - int ret = -1; + struct string_list_item *item; + struct ref_iterator *iter = NULL; + struct strset conflicting_dirnames; + struct strset dirnames; + int ret = REF_TRANSACTION_ERROR_NAME_CONFLICT; /* * For the sake of comments in this function, suppose that @@ -2341,80 +2594,160 @@ int refs_verify_refname_available(struct ref_store *refs, assert(err); - strbuf_grow(&dirname, strlen(refname) + 1); - for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) { - /* - * Just saying "Is a directory" when we e.g. can't - * lock some multi-level ref isn't very informative, - * the user won't be told *what* is a directory, so - * let's not use strerror() below. - */ - int ignore_errno; - /* Expand dirname to the new prefix, not including the trailing slash: */ - strbuf_add(&dirname, refname + dirname.len, slash - refname - dirname.len); + strset_init(&conflicting_dirnames); + strset_init(&dirnames); + + for_each_string_list_item(item, refnames) { + const size_t *update_idx = (size_t *)item->util; + const char *refname = item->string; + const char *extra_refname; + struct object_id oid; + unsigned int type; + const char *slash; + + strbuf_reset(&dirname); + + for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) { + /* + * Just saying "Is a directory" when we e.g. can't + * lock some multi-level ref isn't very informative, + * the user won't be told *what* is a directory, so + * let's not use strerror() below. + */ + int ignore_errno; + + /* Expand dirname to the new prefix, not including the trailing slash: */ + strbuf_add(&dirname, refname + dirname.len, slash - refname - dirname.len); + + /* + * We are still at a leading dir of the refname (e.g., + * "refs/foo"; if there is a reference with that name, + * it is a conflict, *unless* it is in skip. + */ + if (skip && string_list_has_string(skip, dirname.buf)) + continue; + + /* + * If we've already seen the directory we don't need to + * process it again. Skip it to avoid checking common + * prefixes like "refs/heads/" repeatedly. + */ + if (!strset_add(&dirnames, dirname.buf)) + continue; + + if (!initial_transaction && + (strset_contains(&conflicting_dirnames, dirname.buf) || + !refs_read_raw_ref(refs, dirname.buf, &oid, &referent, + &type, &ignore_errno))) { + if (transaction && ref_transaction_maybe_set_rejected( + transaction, *update_idx, + REF_TRANSACTION_ERROR_NAME_CONFLICT)) { + strset_remove(&dirnames, dirname.buf); + strset_add(&conflicting_dirnames, dirname.buf); + continue; + } + + strbuf_addf(err, _("'%s' exists; cannot create '%s'"), + dirname.buf, refname); + goto cleanup; + } + + if (extras && string_list_has_string(extras, dirname.buf)) { + if (transaction && ref_transaction_maybe_set_rejected( + transaction, *update_idx, + REF_TRANSACTION_ERROR_NAME_CONFLICT)) { + strset_remove(&dirnames, dirname.buf); + continue; + } + + strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"), + refname, dirname.buf); + goto cleanup; + } + } /* - * We are still at a leading dir of the refname (e.g., - * "refs/foo"; if there is a reference with that name, - * it is a conflict, *unless* it is in skip. + * We are at the leaf of our refname (e.g., "refs/foo/bar"). + * There is no point in searching for a reference with that + * name, because a refname isn't considered to conflict with + * itself. But we still need to check for references whose + * names are in the "refs/foo/bar/" namespace, because they + * *do* conflict. */ - if (skip && string_list_has_string(skip, dirname.buf)) - continue; + strbuf_addstr(&dirname, refname + dirname.len); + strbuf_addch(&dirname, '/'); + + if (!initial_transaction) { + int ok; + + if (!iter) + iter = refs_ref_iterator_begin(refs, dirname.buf, NULL, 0, + DO_FOR_EACH_INCLUDE_BROKEN); + else if (ref_iterator_seek(iter, dirname.buf, + REF_ITERATOR_SEEK_SET_PREFIX) < 0) + goto cleanup; + + while ((ok = ref_iterator_advance(iter)) == ITER_OK) { + if (skip && + string_list_has_string(skip, iter->refname)) + continue; + + if (transaction && ref_transaction_maybe_set_rejected( + transaction, *update_idx, + REF_TRANSACTION_ERROR_NAME_CONFLICT)) + continue; + + strbuf_addf(err, _("'%s' exists; cannot create '%s'"), + iter->refname, refname); + goto cleanup; + } - if (!refs_read_raw_ref(refs, dirname.buf, &oid, &referent, - &type, &ignore_errno)) { - strbuf_addf(err, _("'%s' exists; cannot create '%s'"), - dirname.buf, refname); - goto cleanup; + if (ok != ITER_DONE) + BUG("error while iterating over references"); } - if (extras && string_list_has_string(extras, dirname.buf)) { + extra_refname = find_descendant_ref(dirname.buf, extras, skip); + if (extra_refname) { + if (transaction && ref_transaction_maybe_set_rejected( + transaction, *update_idx, + REF_TRANSACTION_ERROR_NAME_CONFLICT)) + continue; + strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"), - refname, dirname.buf); + refname, extra_refname); goto cleanup; } } - /* - * We are at the leaf of our refname (e.g., "refs/foo/bar"). - * There is no point in searching for a reference with that - * name, because a refname isn't considered to conflict with - * itself. But we still need to check for references whose - * names are in the "refs/foo/bar/" namespace, because they - * *do* conflict. - */ - 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; - - 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"); - - extra_refname = find_descendant_ref(dirname.buf, extras, skip); - if (extra_refname) - strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"), - refname, extra_refname); - else - ret = 0; + ret = 0; cleanup: strbuf_release(&referent); strbuf_release(&dirname); + strset_clear(&conflicting_dirnames); + strset_clear(&dirnames); + ref_iterator_free(iter); return ret; } +enum ref_transaction_error 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) +{ + struct string_list_item item = { .string = (char *) refname }; + struct string_list refnames = { + .items = &item, + .nr = 1, + }; + + return refs_verify_refnames_available(refs, &refnames, extras, skip, + NULL, initial_transaction, err); +} + struct do_for_each_reflog_help { each_reflog_fn *fn; void *cb_data; @@ -2484,21 +2817,11 @@ int refs_reflog_expire(struct ref_store *refs, 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) { - int i; - - for (i = 0; i < transaction->nr; i++) { + for (size_t i = 0; i < transaction->nr; i++) { struct ref_update *update = transaction->updates[i]; cb(update->refname, @@ -2508,6 +2831,28 @@ void ref_transaction_for_each_queued_update(struct ref_transaction *transaction, } } +void ref_transaction_for_each_rejected_update(struct ref_transaction *transaction, + ref_transaction_for_each_rejected_update_fn cb, + void *cb_data) +{ + if (!transaction->rejections) + return; + + for (size_t i = 0; i < transaction->rejections->nr; i++) { + size_t update_index = transaction->rejections->update_indices[i]; + struct ref_update *update = transaction->updates[update_index]; + + if (!update->rejection_err) + continue; + + cb(update->refname, + (update->flags & REF_HAVE_OLD) ? &update->old_oid : NULL, + (update->flags & REF_HAVE_NEW) ? &update->new_oid : NULL, + update->old_target, update->new_target, + update->rejection_err, cb_data); + } +} + int refs_delete_refs(struct ref_store *refs, const char *logmsg, struct string_list *refnames, unsigned int flags) { @@ -2527,7 +2872,7 @@ int refs_delete_refs(struct ref_store *refs, const char *logmsg, * individual updates can't fail, so we can pack all of the * updates into a single transaction. */ - transaction = ref_store_transaction_begin(refs, &err); + transaction = ref_store_transaction_begin(refs, 0, &err); if (!transaction) { ret = error("%s", err.buf); goto out; @@ -2599,8 +2944,9 @@ 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) +enum ref_transaction_error ref_update_check_old_target(const char *referent, + struct ref_update *update, + struct strbuf *err) { if (!update->old_target) BUG("called without old_target set"); @@ -2608,23 +2954,26 @@ int ref_update_check_old_target(const char *referent, struct ref_update *update, if (!strcmp(referent, update->old_target)) return 0; - if (!strcmp(referent, "")) + 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", + return REF_TRANSACTION_ERROR_NONEXISTENT_REF; + } + + 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; + return REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE; } struct migration_data { struct ref_store *old_refs; struct ref_transaction *transaction; struct strbuf *errbuf; + struct strbuf sb, name, mail; + uint64_t index; }; static int migrate_one_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, @@ -2639,7 +2988,7 @@ static int migrate_one_ref(const char *refname, const char *referent UNUSED, con if (ret < 0) goto done; - ret = ref_transaction_update(data->transaction, refname, NULL, null_oid(), + ret = ref_transaction_update(data->transaction, refname, NULL, null_oid(the_hash_algo), symref_target.buf, NULL, REF_SKIP_CREATE_REFLOG | REF_NO_DEREF, NULL, data->errbuf); if (ret < 0) @@ -2657,6 +3006,43 @@ done: return ret; } +static int migrate_one_reflog_entry(const char *refname, + struct object_id *old_oid, + struct object_id *new_oid, + const char *committer, + timestamp_t timestamp, int tz, + const char *msg, void *cb_data) +{ + struct migration_data *data = cb_data; + struct ident_split ident; + const char *date; + int ret; + + if (split_ident_line(&ident, committer, strlen(committer)) < 0) + return -1; + + strbuf_reset(&data->name); + strbuf_add(&data->name, ident.name_begin, ident.name_end - ident.name_begin); + strbuf_reset(&data->mail); + strbuf_add(&data->mail, ident.mail_begin, ident.mail_end - ident.mail_begin); + + date = show_date(timestamp, tz, DATE_MODE(NORMAL)); + strbuf_reset(&data->sb); + strbuf_addstr(&data->sb, fmt_ident(data->name.buf, data->mail.buf, WANT_BLANK_IDENT, date, 0)); + + ret = ref_transaction_update_reflog(data->transaction, refname, + new_oid, old_oid, data->sb.buf, + msg, data->index++, data->errbuf); + return ret; +} + +static int migrate_one_reflog(const char *refname, void *cb_data) +{ + struct migration_data *migration_data = cb_data; + return refs_for_each_reflog_ent(migration_data->old_refs, refname, + migrate_one_reflog_entry, migration_data); +} + 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; @@ -2723,13 +3109,6 @@ done: 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(); @@ -2754,8 +3133,11 @@ int repo_migrate_ref_storage_format(struct repository *repo, 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; + struct migration_data data = { + .sb = STRBUF_INIT, + .name = STRBUF_INIT, + .mail = STRBUF_INIT, + }; int did_migrate_refs = 0; int ret; @@ -2768,21 +3150,6 @@ int repo_migrate_ref_storage_format(struct repository *repo, 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. @@ -2807,17 +3174,21 @@ int repo_migrate_ref_storage_format(struct repository *repo, * 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 + * 3. Enumerate all reflogs and write them into the new ref storage. + * This operation is safe as we do not yet modify the main + * repository. + * + * 4. 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 + * 5. 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. Move the new ref storage files into place. * - * 6. Change the repository format to the new ref format. + * 7. 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)) { @@ -2833,7 +3204,8 @@ int repo_migrate_ref_storage_format(struct repository *repo, if (ret < 0) goto done; - transaction = ref_store_transaction_begin(new_refs, errbuf); + transaction = ref_store_transaction_begin(new_refs, REF_TRANSACTION_FLAG_INITIAL, + errbuf); if (!transaction) goto done; @@ -2858,13 +3230,12 @@ int repo_migrate_ref_storage_format(struct repository *repo, 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. - */ + if (!(flags & REPO_MIGRATE_REF_STORAGE_FORMAT_SKIP_REFLOG)) { + ret = refs_for_each_reflog(old_refs, migrate_one_reflog, &data); + if (ret < 0) + goto done; + } + ret = ref_transaction_commit(transaction, errbuf); if (ret < 0) goto done; @@ -2940,12 +3311,39 @@ done: } ref_transaction_free(transaction); strbuf_release(&new_gitdir); + strbuf_release(&data.sb); + strbuf_release(&data.name); + strbuf_release(&data.mail); return ret; } int ref_update_expects_existing_old_ref(struct ref_update *update) { + if (update->flags & REF_LOG_ONLY) + return 0; + return (update->flags & REF_HAVE_OLD) && (!is_null_oid(&update->old_oid) || update->old_target); } +const char *ref_transaction_error_msg(enum ref_transaction_error err) +{ + switch (err) { + case REF_TRANSACTION_ERROR_NAME_CONFLICT: + return "refname conflict"; + case REF_TRANSACTION_ERROR_CREATE_EXISTS: + return "reference already exists"; + case REF_TRANSACTION_ERROR_NONEXISTENT_REF: + return "reference does not exist"; + case REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE: + return "incorrect old value provided"; + case REF_TRANSACTION_ERROR_INVALID_NEW_VALUE: + return "invalid new value provided"; + case REF_TRANSACTION_ERROR_EXPECTED_SYMREF: + return "expected symref but found regular ref"; + case REF_TRANSACTION_ERROR_CASE_CONFLICT: + return "reference conflict due to case-insensitive filesystem"; + default: + return "unknown failure"; + } +} |
