diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index f0771590a7..c7b2818f20 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1642,8 +1642,8 @@ static const char *update(struct command *cmd, struct shallow_info *si) ret = NULL; /* good */ } strbuf_release(&err); - } - else { + } else { + enum ref_transaction_error tx_err; struct strbuf err = STRBUF_INIT; if (shallow_update && si->shallow_ref[cmd->index] && update_shallow_ref(cmd, si)) { @@ -1651,14 +1651,18 @@ static const char *update(struct command *cmd, struct shallow_info *si) goto out; } - if (ref_transaction_update(transaction, - namespaced_name, - new_oid, old_oid, - NULL, NULL, - 0, "push", - &err)) { + tx_err = ref_transaction_update(transaction, + namespaced_name, + new_oid, old_oid, + NULL, NULL, + 0, "push", + &err); + if (tx_err) { rp_error("%s", err.buf); - ret = "failed to update ref"; + if (tx_err == REF_TRANSACTION_ERROR_GENERIC) + ret = "failed to update ref"; + else + ret = ref_transaction_error_msg(tx_err); } else { ret = NULL; /* good */ } diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 2d68c40ecb..6355c3dd3e 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -25,6 +25,15 @@ static unsigned int default_flags; static unsigned create_reflog_flag; static const char *msg; +struct command_options { + /* + * Individual updates are allowed to fail without causing + * update-ref to exit. This is set when using the + * '--batch-updates' flag. + */ + bool allow_update_failures; +}; + /* * Parse one whitespace- or NUL-terminated, possibly C-quoted argument * and append the result to arg. Return a pointer to the terminator. @@ -234,6 +243,53 @@ static int parse_next_oid(const char **next, const char *end, command, refname); } +static void print_rejected_refs(const char *refname, + const struct object_id *old_oid, + const struct object_id *new_oid, + const char *old_target, + const char *new_target, + enum ref_transaction_error err, + const char *details, + void *cb_data UNUSED) +{ + struct strbuf sb = STRBUF_INIT; + + if (details && *details) + error("%s", details); + + strbuf_addf(&sb, "rejected %s %s %s %s\n", refname, + new_oid ? oid_to_hex(new_oid) : new_target, + old_oid ? oid_to_hex(old_oid) : old_target, + ref_transaction_error_msg(err)); + + fwrite(sb.buf, sb.len, 1, stdout); + strbuf_release(&sb); +} + +/* + * Handle transaction errors. If we're using batches updates, we want to only + * die for generic errors and print the remaining to the user. + */ +static void handle_ref_transaction_error(const char *refname, + struct object_id *new_oid, + struct object_id *old_oid, + const char *new_target, + const char *old_target, + enum ref_transaction_error tx_err, + struct strbuf *err, + struct command_options *opts) +{ + if (!tx_err) + return; + + if (tx_err != REF_TRANSACTION_ERROR_GENERIC && opts->allow_update_failures) { + print_rejected_refs(refname, old_oid, new_oid, old_target, + new_target, tx_err, err->buf, NULL); + return; + } + + die("%s", err->buf); +} /* * The following five parse_cmd_*() functions parse the corresponding @@ -246,11 +302,13 @@ static int parse_next_oid(const char **next, const char *end, */ static void parse_cmd_update(struct ref_transaction *transaction, - const char *next, const char *end) + const char *next, const char *end, + struct command_options *opts) { struct strbuf err = STRBUF_INIT; char *refname; struct object_id new_oid, old_oid; + enum ref_transaction_error tx_err; int have_old; refname = parse_refname(&next); @@ -267,12 +325,14 @@ static void parse_cmd_update(struct ref_transaction *transaction, if (*next != line_termination) die("update %s: extra input: %s", refname, next); - if (ref_transaction_update(transaction, refname, - &new_oid, have_old ? &old_oid : NULL, - NULL, NULL, - update_flags | create_reflog_flag, - msg, &err)) - die("%s", err.buf); + tx_err = ref_transaction_update(transaction, refname, + &new_oid, have_old ? &old_oid : NULL, + NULL, NULL, + update_flags | create_reflog_flag, + msg, &err); + handle_ref_transaction_error(refname, &new_oid, have_old ? &old_oid : NULL, + NULL, NULL, tx_err, &err, opts); + update_flags = default_flags; free(refname); @@ -280,9 +340,11 @@ static void parse_cmd_update(struct ref_transaction *transaction, } static void parse_cmd_symref_update(struct ref_transaction *transaction, - const char *next, const char *end UNUSED) + const char *next, const char *end UNUSED, + struct command_options *opts) { char *refname, *new_target, *old_arg; + enum ref_transaction_error tx_err; char *old_target = NULL; struct strbuf err = STRBUF_INIT; struct object_id old_oid; @@ -319,13 +381,15 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction, if (*next != line_termination) die("symref-update %s: extra input: %s", refname, next); - if (ref_transaction_update(transaction, refname, NULL, - have_old_oid ? &old_oid : NULL, - new_target, - have_old_oid ? NULL : old_target, - update_flags | create_reflog_flag, - msg, &err)) - die("%s", err.buf); + tx_err = ref_transaction_update(transaction, refname, NULL, + have_old_oid ? &old_oid : NULL, + new_target, + have_old_oid ? NULL : old_target, + update_flags | create_reflog_flag, + msg, &err); + handle_ref_transaction_error(refname, NULL, have_old_oid ? &old_oid : NULL, + new_target, have_old_oid ? NULL : old_target, + tx_err, &err, opts); update_flags = default_flags; free(refname); @@ -336,11 +400,13 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction, } static void parse_cmd_create(struct ref_transaction *transaction, - const char *next, const char *end) + const char *next, const char *end, + struct command_options *opts) { struct strbuf err = STRBUF_INIT; char *refname; struct object_id new_oid; + enum ref_transaction_error tx_err; refname = parse_refname(&next); if (!refname) @@ -355,22 +421,24 @@ static void parse_cmd_create(struct ref_transaction *transaction, if (*next != line_termination) die("create %s: extra input: %s", refname, next); - if (ref_transaction_create(transaction, refname, &new_oid, NULL, - update_flags | create_reflog_flag, - msg, &err)) - die("%s", err.buf); + tx_err = ref_transaction_create(transaction, refname, &new_oid, NULL, + update_flags | create_reflog_flag, + msg, &err); + handle_ref_transaction_error(refname, &new_oid, NULL, NULL, NULL, tx_err, + &err, opts); update_flags = default_flags; free(refname); strbuf_release(&err); } - static void parse_cmd_symref_create(struct ref_transaction *transaction, - const char *next, const char *end UNUSED) + const char *next, const char *end UNUSED, + struct command_options *opts) { struct strbuf err = STRBUF_INIT; char *refname, *new_target; + enum ref_transaction_error tx_err; refname = parse_refname(&next); if (!refname) @@ -383,10 +451,11 @@ static void parse_cmd_symref_create(struct ref_transaction *transaction, if (*next != line_termination) die("symref-create %s: extra input: %s", refname, next); - if (ref_transaction_create(transaction, refname, NULL, new_target, - update_flags | create_reflog_flag, - msg, &err)) - die("%s", err.buf); + tx_err = ref_transaction_create(transaction, refname, NULL, new_target, + update_flags | create_reflog_flag, + msg, &err); + handle_ref_transaction_error(refname, NULL, NULL, new_target, NULL, + tx_err, &err, opts); update_flags = default_flags; free(refname); @@ -395,7 +464,8 @@ static void parse_cmd_symref_create(struct ref_transaction *transaction, } static void parse_cmd_delete(struct ref_transaction *transaction, - const char *next, const char *end) + const char *next, const char *end, + struct command_options *opts UNUSED) { struct strbuf err = STRBUF_INIT; char *refname; @@ -428,9 +498,9 @@ static void parse_cmd_delete(struct ref_transaction *transaction, strbuf_release(&err); } - static void parse_cmd_symref_delete(struct ref_transaction *transaction, - const char *next, const char *end UNUSED) + const char *next, const char *end UNUSED, + struct command_options *opts UNUSED) { struct strbuf err = STRBUF_INIT; char *refname, *old_target; @@ -457,9 +527,9 @@ static void parse_cmd_symref_delete(struct ref_transaction *transaction, strbuf_release(&err); } - static void parse_cmd_verify(struct ref_transaction *transaction, - const char *next, const char *end) + const char *next, const char *end, + struct command_options *opts UNUSED) { struct strbuf err = STRBUF_INIT; char *refname; @@ -486,7 +556,8 @@ static void parse_cmd_verify(struct ref_transaction *transaction, } static void parse_cmd_symref_verify(struct ref_transaction *transaction, - const char *next, const char *end UNUSED) + const char *next, const char *end UNUSED, + struct command_options *opts UNUSED) { struct strbuf err = STRBUF_INIT; struct object_id old_oid; @@ -528,7 +599,8 @@ static void report_ok(const char *command) } static void parse_cmd_option(struct ref_transaction *transaction UNUSED, - const char *next, const char *end UNUSED) + const char *next, const char *end UNUSED, + struct command_options *opts UNUSED) { const char *rest; if (skip_prefix(next, "no-deref", &rest) && *rest == line_termination) @@ -538,7 +610,8 @@ static void parse_cmd_option(struct ref_transaction *transaction UNUSED, } static void parse_cmd_start(struct ref_transaction *transaction UNUSED, - const char *next, const char *end UNUSED) + const char *next, const char *end UNUSED, + struct command_options *opts UNUSED) { if (*next != line_termination) die("start: extra input: %s", next); @@ -546,7 +619,8 @@ static void parse_cmd_start(struct ref_transaction *transaction UNUSED, } static void parse_cmd_prepare(struct ref_transaction *transaction, - const char *next, const char *end UNUSED) + const char *next, const char *end UNUSED, + struct command_options *opts UNUSED) { struct strbuf error = STRBUF_INIT; if (*next != line_termination) @@ -557,7 +631,8 @@ static void parse_cmd_prepare(struct ref_transaction *transaction, } static void parse_cmd_abort(struct ref_transaction *transaction, - const char *next, const char *end UNUSED) + const char *next, const char *end UNUSED, + struct command_options *opts UNUSED) { struct strbuf error = STRBUF_INIT; if (*next != line_termination) @@ -567,31 +642,9 @@ static void parse_cmd_abort(struct ref_transaction *transaction, report_ok("abort"); } -static void print_rejected_refs(const char *refname, - const struct object_id *old_oid, - const struct object_id *new_oid, - const char *old_target, - const char *new_target, - enum ref_transaction_error err, - const char *details, - void *cb_data UNUSED) -{ - struct strbuf sb = STRBUF_INIT; - - if (details && *details) - error("%s", details); - - strbuf_addf(&sb, "rejected %s %s %s %s\n", refname, - new_oid ? oid_to_hex(new_oid) : new_target, - old_oid ? oid_to_hex(old_oid) : old_target, - ref_transaction_error_msg(err)); - - fwrite(sb.buf, sb.len, 1, stdout); - strbuf_release(&sb); -} - static void parse_cmd_commit(struct ref_transaction *transaction, - const char *next, const char *end UNUSED) + const char *next, const char *end UNUSED, + struct command_options *opts UNUSED) { struct strbuf error = STRBUF_INIT; if (*next != line_termination) @@ -619,7 +672,8 @@ enum update_refs_state { static const struct parse_cmd { const char *prefix; - void (*fn)(struct ref_transaction *, const char *, const char *); + void (*fn)(struct ref_transaction *, const char *, const char *, + struct command_options *); unsigned args; enum update_refs_state state; } command[] = { @@ -645,6 +699,10 @@ static void update_refs_stdin(unsigned int flags) struct ref_transaction *transaction; int i, j; + struct command_options opts = { + .allow_update_failures = flags & REF_TRANSACTION_ALLOW_FAILURE, + }; + transaction = ref_store_transaction_begin(get_main_ref_store(the_repository), flags, &err); if (!transaction) @@ -722,7 +780,7 @@ static void update_refs_stdin(unsigned int flags) } cmd->fn(transaction, input.buf + strlen(cmd->prefix) + !!cmd->args, - input.buf + input.len); + input.buf + input.len, &opts); } switch (state) { diff --git a/refs.c b/refs.c index 844785219d..c0aa8a43dd 100644 --- a/refs.c +++ b/refs.c @@ -1307,6 +1307,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 struct object_id *peeled, const char *new_target, const char *old_target, const char *committer_info, const char *msg) @@ -1339,6 +1340,8 @@ struct ref_update *ref_transaction_add_update( update->committer_info = xstrdup_or_null(committer_info); update->msg = normalize_reflog_message(msg); } + if (flags & REF_HAVE_PEELED) + oidcpy(&update->peeled, peeled); /* * This list is generally used by the backends to avoid duplicates. @@ -1383,25 +1386,27 @@ static int transaction_refname_valid(const char *refname, return 1; } -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) +enum ref_transaction_error 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) { + struct object_id peeled; + 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; + return REF_TRANSACTION_ERROR_GENERIC; } if (!transaction_refname_valid(refname, new_oid, flags, err)) - return -1; + return REF_TRANSACTION_ERROR_GENERIC; if (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS) BUG("illegal flags 0x%x passed to ref_transaction_update()", flags); @@ -1416,8 +1421,32 @@ int ref_transaction_update(struct ref_transaction *transaction, 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); + if ((flags & REF_HAVE_NEW) && !new_target && !is_null_oid(new_oid) && + !(flags & REF_SKIP_OID_VERIFICATION) && !(flags & REF_LOG_ONLY)) { + struct object *o = parse_object(transaction->ref_store->repo, new_oid); + + if (!o) { + strbuf_addf(err, + _("trying to write ref '%s' with nonexistent object %s"), + refname, oid_to_hex(new_oid)); + return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE; + } + + if (o->type != OBJ_COMMIT && is_branch(refname)) { + strbuf_addf(err, _("trying to write non-commit object %s to branch '%s'"), + oid_to_hex(new_oid), refname); + return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE; + } + + if (o->type == OBJ_TAG) { + if (!peel_object(transaction->ref_store->repo, new_oid, &peeled, + PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE)) + flags |= REF_HAVE_PEELED; + } + } + ref_transaction_add_update(transaction, refname, flags, - new_oid, old_oid, new_target, + new_oid, old_oid, &peeled, new_target, old_target, NULL, msg); return 0; @@ -1444,7 +1473,7 @@ int ref_transaction_update_reflog(struct ref_transaction *transaction, return -1; update = ref_transaction_add_update(transaction, refname, flags, - new_oid, old_oid, NULL, NULL, + new_oid, old_oid, NULL, NULL, NULL, committer_info, msg); update->index = index; @@ -2295,6 +2324,10 @@ static struct ref_store *ref_store_init(struct repository *repo, { const struct ref_storage_be *be; struct ref_store *refs; + struct ref_store_init_options opts = { + .access_flags = flags, + .log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo), + }; be = find_ref_storage_backend(format); if (!be) @@ -2304,7 +2337,8 @@ static struct ref_store *ref_store_init(struct repository *repo, * TODO Send in a 'struct worktree' instead of a 'gitdir', and * allow the backend to handle how it wants to deal with worktrees. */ - refs = be->init(repo, repo->ref_storage_payload, gitdir, flags); + refs = be->init(repo, repo->ref_storage_payload, gitdir, &opts); + return refs; } diff --git a/refs.h b/refs.h index d65de6ab5f..71d5c186d0 100644 --- a/refs.h +++ b/refs.h @@ -905,14 +905,14 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, * See the above comment "Reference transaction updates" for more * information. */ -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); +enum ref_transaction_error 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); /* * Similar to `ref_transaction_update`, but this function is only for adding diff --git a/refs/files-backend.c b/refs/files-backend.c index bae3790845..55c76879f4 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -19,7 +19,6 @@ #include "../iterator.h" #include "../dir-iterator.h" #include "../lockfile.h" -#include "../object.h" #include "../path.h" #include "../dir.h" #include "../chdir-notify.h" @@ -108,7 +107,7 @@ static void clear_loose_ref_cache(struct files_ref_store *refs) static struct ref_store *files_ref_store_init(struct repository *repo, const char *payload, const char *gitdir, - unsigned int flags) + const struct ref_store_init_options *opts) { struct files_ref_store *refs = xcalloc(1, sizeof(*refs)); struct ref_store *ref_store = (struct ref_store *)refs; @@ -120,11 +119,13 @@ static struct ref_store *files_ref_store_init(struct repository *repo, &ref_common_dir); base_ref_store_init(ref_store, repo, refdir.buf, &refs_be_files); - refs->store_flags = flags; + refs->gitcommondir = strbuf_detach(&ref_common_dir, NULL); refs->packed_ref_store = - packed_ref_store_init(repo, NULL, refs->gitcommondir, flags); - refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo); + packed_ref_store_init(repo, NULL, refs->gitcommondir, opts); + refs->store_flags = opts->access_flags; + refs->log_all_ref_updates = opts->log_all_ref_updates; + repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs); chdir_notify_reparent("files-backend $GIT_DIR", &refs->base.gitdir); @@ -1331,7 +1332,8 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) ref_transaction_add_update( transaction, r->name, REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING, - null_oid(the_hash_algo), &r->oid, NULL, NULL, NULL, NULL); + null_oid(the_hash_algo), &r->oid, NULL, NULL, NULL, + NULL, NULL); if (ref_transaction_commit(transaction, &err)) goto cleanup; @@ -1594,7 +1596,6 @@ static int rename_tmp_log(struct files_ref_store *refs, const char *newrefname) static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs, struct ref_lock *lock, const struct object_id *oid, - int skip_oid_verification, struct strbuf *err); static int commit_ref_update(struct files_ref_store *refs, struct ref_lock *lock, @@ -1742,7 +1743,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store, } oidcpy(&lock->old_oid, &orig_oid); - if (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) || + if (write_ref_to_lockfile(refs, lock, &orig_oid, &err) || commit_ref_update(refs, lock, &orig_oid, logmsg, 0, &err)) { error("unable to write current sha1 into %s: %s", newrefname, err.buf); strbuf_release(&err); @@ -1760,7 +1761,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store, goto rollbacklog; } - if (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) || + if (write_ref_to_lockfile(refs, lock, &orig_oid, &err) || commit_ref_update(refs, lock, &orig_oid, NULL, REF_SKIP_CREATE_REFLOG, &err)) { error("unable to write current sha1 into %s: %s", oldrefname, err.buf); strbuf_release(&err); @@ -2004,32 +2005,11 @@ static int files_log_ref_write(struct files_ref_store *refs, static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs, struct ref_lock *lock, const struct object_id *oid, - int skip_oid_verification, struct strbuf *err) { static char term = '\n'; - struct object *o; int fd; - if (!skip_oid_verification) { - o = parse_object(refs->base.repo, oid); - if (!o) { - strbuf_addf( - err, - "trying to write ref '%s' with nonexistent object %s", - lock->ref_name, oid_to_hex(oid)); - unlock_ref(lock); - return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE; - } - if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) { - strbuf_addf( - err, - "trying to write non-commit object %s to branch '%s'", - oid_to_hex(oid), lock->ref_name); - unlock_ref(lock); - return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE; - } - } fd = get_lock_file_fd(&lock->lk); if (write_in_full(fd, oid_to_hex(oid), refs->base.repo->hash_algo->hexsz) < 0 || write_in_full(fd, &term, 1) < 0 || @@ -2496,7 +2476,7 @@ static enum ref_transaction_error split_head_update(struct ref_update *update, new_update = ref_transaction_add_update( transaction, "HEAD", update->flags | REF_LOG_ONLY | REF_NO_DEREF | REF_LOG_VIA_SPLIT, - &update->new_oid, &update->old_oid, + &update->new_oid, &update->old_oid, &update->peeled, NULL, NULL, update->committer_info, update->msg); new_update->parent_update = update; @@ -2558,8 +2538,8 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update, transaction, referent, new_flags, update->new_target ? NULL : &update->new_oid, update->old_target ? NULL : &update->old_oid, - update->new_target, update->old_target, NULL, - update->msg); + &update->peeled, update->new_target, update->old_target, + NULL, update->msg); new_update->parent_update = update; @@ -2833,7 +2813,6 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re } else { ret = write_ref_to_lockfile( refs, lock, &update->new_oid, - update->flags & REF_SKIP_OID_VERIFICATION, err); if (ret) { char *write_err = strbuf_detach(err, NULL); @@ -3023,7 +3002,7 @@ static int files_transaction_prepare(struct ref_store *ref_store, ref_transaction_add_update( packed_transaction, update->refname, REF_HAVE_NEW | REF_NO_DEREF, - &update->new_oid, NULL, + &update->new_oid, NULL, NULL, NULL, NULL, NULL, NULL); } } @@ -3229,19 +3208,22 @@ static int files_transaction_finish_initial(struct files_ref_store *refs, if (update->flags & REF_LOG_ONLY) ref_transaction_add_update(loose_transaction, update->refname, update->flags, &update->new_oid, - &update->old_oid, NULL, NULL, + &update->old_oid, &update->peeled, + NULL, NULL, update->committer_info, update->msg); else ref_transaction_add_update(loose_transaction, update->refname, update->flags & ~REF_HAVE_OLD, update->new_target ? NULL : &update->new_oid, NULL, - update->new_target, NULL, update->committer_info, + &update->peeled, update->new_target, + NULL, update->committer_info, NULL); } else { ref_transaction_add_update(packed_transaction, update->refname, update->flags & ~REF_HAVE_OLD, &update->new_oid, &update->old_oid, - NULL, NULL, update->committer_info, NULL); + &update->peeled, NULL, NULL, + update->committer_info, NULL); } } diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 23ed62984b..0acde48c45 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -218,14 +218,14 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot) struct ref_store *packed_ref_store_init(struct repository *repo, const char *payload UNUSED, const char *gitdir, - unsigned int store_flags) + const struct ref_store_init_options *opts) { struct packed_ref_store *refs = xcalloc(1, sizeof(*refs)); struct ref_store *ref_store = (struct ref_store *)refs; struct strbuf sb = STRBUF_INIT; base_ref_store_init(ref_store, repo, gitdir, &refs_be_packed); - refs->store_flags = store_flags; + refs->store_flags = opts->access_flags; strbuf_addf(&sb, "%s/packed-refs", gitdir); refs->path = strbuf_detach(&sb, NULL); @@ -1531,13 +1531,11 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re */ i++; } else { - struct object_id peeled; - int peel_error = peel_object(refs->base.repo, &update->new_oid, - &peeled, PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE); + bool peeled = update->flags & REF_HAVE_PEELED; if (write_packed_entry(out, update->refname, &update->new_oid, - peel_error ? NULL : &peeled)) + peeled ? &update->peeled : NULL)) goto write_error; i++; diff --git a/refs/packed-backend.h b/refs/packed-backend.h index 2c2377a356..1db48e801d 100644 --- a/refs/packed-backend.h +++ b/refs/packed-backend.h @@ -3,6 +3,7 @@ struct repository; struct ref_transaction; +struct ref_store_init_options; /* * Support for storing references in a `packed-refs` file. @@ -16,7 +17,7 @@ struct ref_transaction; struct ref_store *packed_ref_store_init(struct repository *repo, const char *payload, const char *gitdir, - unsigned int store_flags); + const struct ref_store_init_options *options); /* * Lock the packed-refs file for writing. Flags is passed to diff --git a/refs/refs-internal.h b/refs/refs-internal.h index e4cfd9e19e..a08d58900e 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -39,6 +39,13 @@ struct ref_transaction; */ #define REF_LOG_ONLY (1 << 7) +/* + * The reference contains a peeled object ID. This is used when the + * new_oid is pointing to a tag object and the reference backend + * wants to also store the peeled value for optimized retrieval. + */ +#define REF_HAVE_PEELED (1 << 15) + /* * Return the length of time to retry acquiring a loose reference lock * before giving up, in milliseconds: @@ -92,6 +99,12 @@ struct ref_update { */ struct object_id old_oid; + /* + * If the new_oid points to a tag object, set this to the peeled + * object ID for optimized retrieval without needed to hit the odb. + */ + struct object_id peeled; + /* * If set, point the reference to this value. This can also be * used to convert regular references to become symbolic refs. @@ -169,6 +182,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 struct object_id *peeled, const char *new_target, const char *old_target, const char *committer_info, const char *msg); @@ -385,6 +399,21 @@ struct ref_store; REF_STORE_ODB | \ REF_STORE_MAIN) +/* + * Options for initializing the ref backend. All backend-agnostic information + * which backends required will be held here. + */ +struct ref_store_init_options { + /* The kind of operations that the ref_store is allowed to perform. */ + unsigned int access_flags; + + /* + * Denotes under what conditions reflogs should be created when updating + * references. + */ + enum log_refs_config log_all_ref_updates; +}; + /* * Initialize the ref_store for the specified gitdir. These functions * should call base_ref_store_init() to initialize the shared part of @@ -393,7 +422,7 @@ struct ref_store; typedef struct ref_store *ref_store_init_fn(struct repository *repo, const char *payload, const char *gitdir, - unsigned int flags); + const struct ref_store_init_options *opts); /* * Release all memory and resources associated with the ref store. */ @@ -421,10 +450,6 @@ typedef int ref_transaction_abort_fn(struct ref_store *refs, struct ref_transaction *transaction, struct strbuf *err); -typedef int ref_transaction_commit_fn(struct ref_store *refs, - struct ref_transaction *transaction, - struct strbuf *err); - typedef int optimize_fn(struct ref_store *ref_store, struct refs_optimize_opts *opts); diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 4921b456ff..4ae22922de 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -12,7 +12,6 @@ #include "../hex.h" #include "../ident.h" #include "../iterator.h" -#include "../object.h" #include "../parse.h" #include "../path.h" #include "../refs.h" @@ -369,7 +368,7 @@ static int reftable_be_config(const char *var, const char *value, static struct ref_store *reftable_be_init(struct repository *repo, const char *payload, const char *gitdir, - unsigned int store_flags) + const struct ref_store_init_options *opts) { struct reftable_ref_store *refs = xcalloc(1, sizeof(*refs)); struct strbuf ref_common_dir = STRBUF_INIT; @@ -386,8 +385,8 @@ static struct ref_store *reftable_be_init(struct repository *repo, base_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable); strmap_init(&refs->worktree_backends); - refs->store_flags = store_flags; - refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo); + refs->log_all_ref_updates = opts->log_all_ref_updates; + refs->store_flags = opts->access_flags; switch (repo->hash_algo->format_id) { case GIT_SHA1_FORMAT_ID: @@ -1081,25 +1080,6 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor return 0; } - /* Verify that the new object ID is valid. */ - if ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) && - !(u->flags & REF_SKIP_OID_VERIFICATION) && - !(u->flags & REF_LOG_ONLY)) { - struct object *o = parse_object(refs->base.repo, &u->new_oid); - if (!o) { - strbuf_addf(err, - _("trying to write ref '%s' with nonexistent object %s"), - u->refname, oid_to_hex(&u->new_oid)); - return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE; - } - - if (o->type != OBJ_COMMIT && is_branch(u->refname)) { - strbuf_addf(err, _("trying to write non-commit object %s to branch '%s'"), - oid_to_hex(&u->new_oid), u->refname); - return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE; - } - } - /* * When we update the reference that HEAD points to we enqueue * a second log-only update for HEAD so that its reflog is @@ -1126,8 +1106,8 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor ref_transaction_add_update( transaction, "HEAD", u->flags | REF_LOG_ONLY | REF_NO_DEREF, - &u->new_oid, &u->old_oid, NULL, NULL, NULL, - u->msg); + &u->new_oid, &u->old_oid, &u->peeled, NULL, NULL, + NULL, u->msg); } ret = reftable_backend_read_ref(be, rewritten_ref, @@ -1213,7 +1193,7 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor transaction, referent->buf, new_flags, u->new_target ? NULL : &u->new_oid, u->old_target ? NULL : &u->old_oid, - u->new_target, u->old_target, + &u->peeled, u->new_target, u->old_target, u->committer_info, u->msg); new_update->parent_update = u; @@ -1603,17 +1583,13 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data goto done; } else if (u->flags & REF_HAVE_NEW) { struct reftable_ref_record ref = {0}; - struct object_id peeled; - int peel_error; ref.refname = (char *)u->refname; ref.update_index = ts; - peel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled, - PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE); - if (!peel_error) { + if (u->flags & REF_HAVE_PEELED) { ref.value_type = REFTABLE_REF_VAL2; - memcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ); + memcpy(ref.value.val2.target_value, u->peeled.hash, GIT_MAX_RAWSZ); memcpy(ref.value.val2.value, u->new_oid.hash, GIT_MAX_RAWSZ); } else if (!is_null_oid(&u->new_oid)) { ref.value_type = REFTABLE_REF_VAL1; diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index b2858a9061..1015f335e3 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -1196,6 +1196,20 @@ test_expect_success 'stdin -z create ref fails with empty new value' ' test_must_fail git rev-parse --verify -q $c ' +test_expect_success 'stdin -z create ref fails with non commit object' ' + printf $F "create $c" "$(test_oid 001)" >stdin && + test_must_fail git update-ref -z --stdin err && + grep "fatal: trying to write ref ${SQ}$c${SQ} with nonexistent object" err && + test_must_fail git rev-parse --verify -q $c +' + +test_expect_success 'stdin -z update ref fails with non commit object' ' + printf $F "update $b" "$(test_oid 001)" "" >stdin && + test_must_fail git update-ref -z --stdin err && + grep "fatal: trying to write ref ${SQ}$b${SQ} with nonexistent object" err && + test_must_fail git rev-parse --verify -q $c +' + test_expect_success 'stdin -z update ref works with right old value' ' printf $F "update $b" "$m~1" "$m" >stdin && git update-ref -z --stdin