Merge branch 'kn/non-transactional-batch-updates'

Updating multiple references have only been possible in all-or-none
fashion with transactions, but it can be more efficient to batch
multiple updates even when some of them are allowed to fail in a
best-effort manner.  A new "best effort batches of updates" mode
has been introduced.

* kn/non-transactional-batch-updates:
  update-ref: add --batch-updates flag for stdin mode
  refs: support rejection in batch updates during F/D checks
  refs: implement batch reference update support
  refs: introduce enum-based transaction error types
  refs/reftable: extract code from the transaction preparation
  refs/files: remove duplicate duplicates check
  refs: move duplicate refname update check to generic layer
  refs/files: remove redundant check in split_symref_update()
This commit is contained in:
Junio C Hamano
2025-04-16 13:54:19 -07:00
10 changed files with 969 additions and 523 deletions

View File

@@ -5,6 +5,7 @@
#include "config.h"
#include "gettext.h"
#include "hash.h"
#include "hex.h"
#include "refs.h"
#include "object-name.h"
#include "parse-options.h"
@@ -13,7 +14,7 @@
static const char * const git_update_ref_usage[] = {
N_("git update-ref [<options>] -d <refname> [<old-oid>]"),
N_("git update-ref [<options>] <refname> <new-oid> [<old-oid>]"),
N_("git update-ref [<options>] --stdin [-z]"),
N_("git update-ref [<options>] --stdin [-z] [--batch-updates]"),
NULL
};
@@ -565,6 +566,49 @@ 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,
void *cb_data UNUSED)
{
struct strbuf sb = STRBUF_INIT;
const char *reason = "";
switch (err) {
case REF_TRANSACTION_ERROR_NAME_CONFLICT:
reason = "refname conflict";
break;
case REF_TRANSACTION_ERROR_CREATE_EXISTS:
reason = "reference already exists";
break;
case REF_TRANSACTION_ERROR_NONEXISTENT_REF:
reason = "reference does not exist";
break;
case REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE:
reason = "incorrect old value provided";
break;
case REF_TRANSACTION_ERROR_INVALID_NEW_VALUE:
reason = "invalid new value provided";
break;
case REF_TRANSACTION_ERROR_EXPECTED_SYMREF:
reason = "expected symref but found regular ref";
break;
default:
reason = "unkown failure";
}
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,
reason);
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)
{
@@ -573,6 +617,10 @@ static void parse_cmd_commit(struct ref_transaction *transaction,
die("commit: extra input: %s", next);
if (ref_transaction_commit(transaction, &error))
die("commit: %s", error.buf);
ref_transaction_for_each_rejected_update(transaction,
print_rejected_refs, NULL);
report_ok("commit");
ref_transaction_free(transaction);
}
@@ -609,7 +657,7 @@ static const struct parse_cmd {
{ "commit", parse_cmd_commit, 0, UPDATE_REFS_CLOSED },
};
static void update_refs_stdin(void)
static void update_refs_stdin(unsigned int flags)
{
struct strbuf input = STRBUF_INIT, err = STRBUF_INIT;
enum update_refs_state state = UPDATE_REFS_OPEN;
@@ -617,7 +665,7 @@ static void update_refs_stdin(void)
int i, j;
transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
0, &err);
flags, &err);
if (!transaction)
die("%s", err.buf);
@@ -685,7 +733,7 @@ static void update_refs_stdin(void)
*/
state = cmd->state;
transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
0, &err);
flags, &err);
if (!transaction)
die("%s", err.buf);
@@ -701,6 +749,8 @@ static void update_refs_stdin(void)
/* Commit by default if no transaction was requested. */
if (ref_transaction_commit(transaction, &err))
die("%s", err.buf);
ref_transaction_for_each_rejected_update(transaction,
print_rejected_refs, NULL);
ref_transaction_free(transaction);
break;
case UPDATE_REFS_STARTED:
@@ -727,6 +777,8 @@ int cmd_update_ref(int argc,
struct object_id oid, oldoid;
int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0;
int create_reflog = 0;
unsigned int flags = 0;
struct option options[] = {
OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")),
OPT_BOOL('d', NULL, &delete, N_("delete the reference")),
@@ -735,6 +787,8 @@ int cmd_update_ref(int argc,
OPT_BOOL('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")),
OPT_BOOL( 0 , "stdin", &read_stdin, N_("read updates from stdin")),
OPT_BOOL( 0 , "create-reflog", &create_reflog, N_("create a reflog")),
OPT_BIT('0', "batch-updates", &flags, N_("batch reference updates"),
REF_TRANSACTION_ALLOW_FAILURE),
OPT_END(),
};
@@ -756,8 +810,10 @@ int cmd_update_ref(int argc,
usage_with_options(git_update_ref_usage, options);
if (end_null)
line_termination = '\0';
update_refs_stdin();
update_refs_stdin(flags);
return 0;
} else if (flags & REF_TRANSACTION_ALLOW_FAILURE) {
die("--batch-updates can only be used with --stdin");
}
if (end_null)