提交 2cc70cef 编写于 作者: J Junio C Hamano

Merge branch 'mh/ref-transaction'

Update "update-ref --stdin [-z]" and then introduce a transactional
support for (multi-)reference updates.

* mh/ref-transaction: (27 commits)
  ref_transaction_commit(): work with transaction->updates in place
  struct ref_update: add a type field
  struct ref_update: add a lock field
  ref_transaction_commit(): simplify code using temporary variables
  struct ref_update: store refname as a FLEX_ARRAY
  struct ref_update: rename field "ref_name" to "refname"
  refs: remove API function update_refs()
  update-ref --stdin: reimplement using reference transactions
  refs: add a concept of a reference transaction
  update-ref --stdin: harmonize error messages
  update-ref --stdin: improve the error message for unexpected EOF
  t1400: test one mistake at a time
  update-ref --stdin -z: deprecate interpreting the empty string as zeros
  update-ref.c: extract a new function, parse_next_sha1()
  t1400: test that stdin -z update treats empty <newvalue> as zeros
  update-ref --stdin: simplify error messages for missing oldvalues
  update-ref --stdin: make error messages more consistent
  update-ref --stdin: improve error messages for invalid values
  update-ref.c: extract a new function, parse_refname()
  parse_cmd_verify(): copy old_sha1 instead of evaluating <oldvalue> twice
  ...
......@@ -68,7 +68,12 @@ performs all modifications together. Specify commands of the form:
option SP <opt> LF
Quote fields containing whitespace as if they were strings in C source
code. Alternatively, use `-z` to specify commands without quoting:
code; i.e., surrounded by double-quotes and with backslash escapes.
Use 40 "0" characters or the empty string to specify a zero value. To
specify a missing value, omit the value and its preceding SP entirely.
Alternatively, use `-z` to specify in NUL-terminated format, without
quoting:
update SP <ref> NUL <newvalue> NUL [<oldvalue>] NUL
create SP <ref> NUL <newvalue> NUL
......@@ -76,8 +81,12 @@ code. Alternatively, use `-z` to specify commands without quoting:
verify SP <ref> NUL [<oldvalue>] NUL
option SP <opt> NUL
Lines of any other format or a repeated <ref> produce an error.
Command meanings are:
In this format, use 40 "0" to specify a zero value, and use the empty
string to specify a missing value.
In either format, values can be specified in any form that Git
recognizes as an object name. Commands in any other format or a
repeated <ref> produce an error. Command meanings are:
update::
Set <ref> to <newvalue> after verifying <oldvalue>, if given.
......@@ -102,9 +111,6 @@ option::
The only valid option is `no-deref` to avoid dereferencing
a symbolic ref.
Use 40 "0" or the empty string to specify a zero value, except that
with `-z` an empty <oldvalue> is considered missing.
If all <ref>s can be locked with matching <oldvalue>s
simultaneously, all modifications are performed. Otherwise, no
modifications are performed. Note that while each individual
......
......@@ -624,7 +624,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
/* Nothing to do. */
} else if (opts->force_detach || !new->path) { /* No longer on any branch. */
update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
REF_NODEREF, DIE_ON_ERR);
REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
if (!opts->quiet) {
if (old->path && advice_detached_head)
detach_advice(new->name);
......
......@@ -521,7 +521,7 @@ static void write_followtags(const struct ref *refs, const char *msg)
if (!has_sha1_file(ref->old_sha1))
continue;
update_ref(msg, ref->name, ref->old_sha1,
NULL, 0, DIE_ON_ERR);
NULL, 0, UPDATE_REFS_DIE_ON_ERR);
}
}
......@@ -589,14 +589,15 @@ static void update_head(const struct ref *our, const struct ref *remote,
create_symref("HEAD", our->name, NULL);
if (!option_bare) {
const char *head = skip_prefix(our->name, "refs/heads/");
update_ref(msg, "HEAD", our->old_sha1, NULL, 0, DIE_ON_ERR);
update_ref(msg, "HEAD", our->old_sha1, NULL, 0,
UPDATE_REFS_DIE_ON_ERR);
install_branch_config(0, head, option_origin, our->name);
}
} else if (our) {
struct commit *c = lookup_commit_reference(our->old_sha1);
/* --branch specifies a non-branch (i.e. tags), detach HEAD */
update_ref(msg, "HEAD", c->object.sha1,
NULL, REF_NODEREF, DIE_ON_ERR);
NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
} else if (remote) {
/*
* We know remote HEAD points to a non-branch, or
......@@ -604,7 +605,7 @@ static void update_head(const struct ref *our, const struct ref *remote,
* Detach HEAD in all these cases.
*/
update_ref(msg, "HEAD", remote->old_sha1,
NULL, REF_NODEREF, DIE_ON_ERR);
NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
}
}
......
......@@ -398,7 +398,7 @@ static void finish(struct commit *head_commit,
const char *argv_gc_auto[] = { "gc", "--auto", NULL };
update_ref(reflog_message.buf, "HEAD",
new_head, head, 0,
DIE_ON_ERR);
UPDATE_REFS_DIE_ON_ERR);
/*
* We ignore errors in 'gc --auto', since the
* user should see them.
......@@ -1222,7 +1222,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
die(_("%s - not something we can merge"), argv[0]);
read_empty(remote_head->object.sha1, 0);
update_ref("initial pull", "HEAD", remote_head->object.sha1,
NULL, 0, DIE_ON_ERR);
NULL, 0, UPDATE_REFS_DIE_ON_ERR);
goto done;
} else {
struct strbuf merge_names = STRBUF_INIT;
......@@ -1339,7 +1339,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
}
update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1,
NULL, 0, DIE_ON_ERR);
NULL, 0, UPDATE_REFS_DIE_ON_ERR);
if (remoteheads && !common)
; /* No common ancestors found. We need a real merge. */
......
......@@ -717,7 +717,7 @@ static int merge_commit(struct notes_merge_options *o)
strbuf_insert(&msg, 0, "notes: ", 7);
update_ref(msg.buf, o->local_ref, sha1,
is_null_sha1(parent_sha1) ? NULL : parent_sha1,
0, DIE_ON_ERR);
0, UPDATE_REFS_DIE_ON_ERR);
free_notes(t);
strbuf_release(&msg);
......@@ -812,11 +812,11 @@ static int merge(int argc, const char **argv, const char *prefix)
if (result >= 0) /* Merge resulted (trivially) in result_sha1 */
/* Update default notes ref with new commit */
update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,
0, DIE_ON_ERR);
0, UPDATE_REFS_DIE_ON_ERR);
else { /* Merge has unresolved conflicts */
/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL,
0, DIE_ON_ERR);
0, UPDATE_REFS_DIE_ON_ERR);
/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
die("Failed to store link to current notes ref (%s)",
......
......@@ -252,11 +252,13 @@ static int reset_refs(const char *rev, const unsigned char *sha1)
if (!get_sha1("HEAD", sha1_orig)) {
orig = sha1_orig;
set_reflog_message(&msg, "updating ORIG_HEAD", NULL);
update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR);
update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0,
UPDATE_REFS_MSG_ON_ERR);
} else if (old_orig)
delete_ref("ORIG_HEAD", old_orig, 0);
set_reflog_message(&msg, "updating HEAD", rev);
update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, MSG_ON_ERR);
update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0,
UPDATE_REFS_MSG_ON_ERR);
strbuf_release(&msg);
return update_ref_status;
}
......
......@@ -12,238 +12,329 @@ static const char * const git_update_ref_usage[] = {
NULL
};
static int updates_alloc;
static int updates_count;
static const struct ref_update **updates;
static struct ref_transaction *transaction;
static char line_termination = '\n';
static int update_flags;
static struct ref_update *update_alloc(void)
{
struct ref_update *update;
/* Allocate and zero-init a struct ref_update */
update = xcalloc(1, sizeof(*update));
ALLOC_GROW(updates, updates_count + 1, updates_alloc);
updates[updates_count++] = update;
/* Store and reset accumulated options */
update->flags = update_flags;
update_flags = 0;
return update;
}
static void update_store_ref_name(struct ref_update *update,
const char *ref_name)
{
if (check_refname_format(ref_name, REFNAME_ALLOW_ONELEVEL))
die("invalid ref format: %s", ref_name);
update->ref_name = xstrdup(ref_name);
}
static void update_store_new_sha1(struct ref_update *update,
const char *newvalue)
{
if (*newvalue && get_sha1(newvalue, update->new_sha1))
die("invalid new value for ref %s: %s",
update->ref_name, newvalue);
}
static void update_store_old_sha1(struct ref_update *update,
const char *oldvalue)
{
if (*oldvalue && get_sha1(oldvalue, update->old_sha1))
die("invalid old value for ref %s: %s",
update->ref_name, oldvalue);
/* We have an old value if non-empty, or if empty without -z */
update->have_old = *oldvalue || line_termination;
}
/*
* Parse one whitespace- or NUL-terminated, possibly C-quoted argument
* and append the result to arg. Return a pointer to the terminator.
* Die if there is an error in how the argument is C-quoted. This
* function is only used if not -z.
*/
static const char *parse_arg(const char *next, struct strbuf *arg)
{
/* Parse SP-terminated, possibly C-quoted argument */
if (*next != '"')
if (*next == '"') {
const char *orig = next;
if (unquote_c_style(arg, next, &next))
die("badly quoted argument: %s", orig);
if (*next && !isspace(*next))
die("unexpected character after quoted argument: %s", orig);
} else {
while (*next && !isspace(*next))
strbuf_addch(arg, *next++);
else if (unquote_c_style(arg, next, &next))
die("badly quoted argument: %s", next);
}
/* Return position after the argument */
return next;
}
static const char *parse_first_arg(const char *next, struct strbuf *arg)
/*
* Parse the reference name immediately after "command SP". If not
* -z, then handle C-quoting. Return a pointer to a newly allocated
* string containing the name of the reference, or NULL if there was
* an error. Update *next to point at the character that terminates
* the argument. Die if C-quoting is malformed or the reference name
* is invalid.
*/
static char *parse_refname(struct strbuf *input, const char **next)
{
/* Parse argument immediately after "command SP" */
strbuf_reset(arg);
struct strbuf ref = STRBUF_INIT;
if (line_termination) {
/* Without -z, use the next argument */
next = parse_arg(next, arg);
*next = parse_arg(*next, &ref);
} else {
/* With -z, use rest of first NUL-terminated line */
strbuf_addstr(arg, next);
next = next + arg->len;
/* With -z, use everything up to the next NUL */
strbuf_addstr(&ref, *next);
*next += ref.len;
}
return next;
if (!ref.len) {
strbuf_release(&ref);
return NULL;
}
if (check_refname_format(ref.buf, REFNAME_ALLOW_ONELEVEL))
die("invalid ref format: %s", ref.buf);
return strbuf_detach(&ref, NULL);
}
static const char *parse_next_arg(const char *next, struct strbuf *arg)
/*
* The value being parsed is <oldvalue> (as opposed to <newvalue>; the
* difference affects which error messages are generated):
*/
#define PARSE_SHA1_OLD 0x01
/*
* For backwards compatibility, accept an empty string for update's
* <newvalue> in binary mode to be equivalent to specifying zeros.
*/
#define PARSE_SHA1_ALLOW_EMPTY 0x02
/*
* Parse an argument separator followed by the next argument, if any.
* If there is an argument, convert it to a SHA-1, write it to sha1,
* set *next to point at the character terminating the argument, and
* return 0. If there is no argument at all (not even the empty
* string), return 1 and leave *next unchanged. If the value is
* provided but cannot be converted to a SHA-1, die. flags can
* include PARSE_SHA1_OLD and/or PARSE_SHA1_ALLOW_EMPTY.
*/
static int parse_next_sha1(struct strbuf *input, const char **next,
unsigned char *sha1,
const char *command, const char *refname,
int flags)
{
/* Parse next SP-terminated or NUL-terminated argument, if any */
strbuf_reset(arg);
struct strbuf arg = STRBUF_INIT;
int ret = 0;
if (*next == input->buf + input->len)
goto eof;
if (line_termination) {
/* Without -z, consume SP and use next argument */
if (!*next)
return NULL;
if (*next != ' ')
die("expected SP but got: %s", next);
next = parse_arg(next + 1, arg);
if (!**next || **next == line_termination)
return 1;
if (**next != ' ')
die("%s %s: expected SP but got: %s",
command, refname, *next);
(*next)++;
*next = parse_arg(*next, &arg);
if (arg.len) {
if (get_sha1(arg.buf, sha1))
goto invalid;
} else {
/* Without -z, an empty value means all zeros: */
hashclr(sha1);
}
} else {
/* With -z, read the next NUL-terminated line */
if (*next)
die("expected NUL but got: %s", next);
if (strbuf_getline(arg, stdin, '\0') == EOF)
return NULL;
next = arg->buf + arg->len;
if (**next)
die("%s %s: expected NUL but got: %s",
command, refname, *next);
(*next)++;
if (*next == input->buf + input->len)
goto eof;
strbuf_addstr(&arg, *next);
*next += arg.len;
if (arg.len) {
if (get_sha1(arg.buf, sha1))
goto invalid;
} else if (flags & PARSE_SHA1_ALLOW_EMPTY) {
/* With -z, treat an empty value as all zeros: */
warning("%s %s: missing <newvalue>, treating as zero",
command, refname);
hashclr(sha1);
} else {
/*
* With -z, an empty non-required value means
* unspecified:
*/
ret = 1;
}
}
return next;
strbuf_release(&arg);
return ret;
invalid:
die(flags & PARSE_SHA1_OLD ?
"%s %s: invalid <oldvalue>: %s" :
"%s %s: invalid <newvalue>: %s",
command, refname, arg.buf);
eof:
die(flags & PARSE_SHA1_OLD ?
"%s %s: unexpected end of input when reading <oldvalue>" :
"%s %s: unexpected end of input when reading <newvalue>",
command, refname);
}
static void parse_cmd_update(const char *next)
/*
* The following five parse_cmd_*() functions parse the corresponding
* command. In each case, next points at the character following the
* command name and the following space. They each return a pointer
* to the character terminating the command, and die with an
* explanatory message if there are any parsing problems. All of
* these functions handle either text or binary format input,
* depending on how line_termination is set.
*/
static const char *parse_cmd_update(struct strbuf *input, const char *next)
{
struct strbuf ref = STRBUF_INIT;
struct strbuf newvalue = STRBUF_INIT;
struct strbuf oldvalue = STRBUF_INIT;
struct ref_update *update;
char *refname;
unsigned char new_sha1[20];
unsigned char old_sha1[20];
int have_old;
update = update_alloc();
refname = parse_refname(input, &next);
if (!refname)
die("update: missing <ref>");
if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
update_store_ref_name(update, ref.buf);
else
die("update line missing <ref>");
if (parse_next_sha1(input, &next, new_sha1, "update", refname,
PARSE_SHA1_ALLOW_EMPTY))
die("update %s: missing <newvalue>", refname);
if ((next = parse_next_arg(next, &newvalue)) != NULL)
update_store_new_sha1(update, newvalue.buf);
else
die("update %s missing <newvalue>", ref.buf);
have_old = !parse_next_sha1(input, &next, old_sha1, "update", refname,
PARSE_SHA1_OLD);
if ((next = parse_next_arg(next, &oldvalue)) != NULL)
update_store_old_sha1(update, oldvalue.buf);
else if(!line_termination)
die("update %s missing [<oldvalue>] NUL", ref.buf);
if (*next != line_termination)
die("update %s: extra input: %s", refname, next);
if (next && *next)
die("update %s has extra input: %s", ref.buf, next);
ref_transaction_update(transaction, refname, new_sha1, old_sha1,
update_flags, have_old);
update_flags = 0;
free(refname);
return next;
}
static void parse_cmd_create(const char *next)
static const char *parse_cmd_create(struct strbuf *input, const char *next)
{
struct strbuf ref = STRBUF_INIT;
struct strbuf newvalue = STRBUF_INIT;
struct ref_update *update;
char *refname;
unsigned char new_sha1[20];
update = update_alloc();
update->have_old = 1;
refname = parse_refname(input, &next);
if (!refname)
die("create: missing <ref>");
if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
update_store_ref_name(update, ref.buf);
else
die("create line missing <ref>");
if (parse_next_sha1(input, &next, new_sha1, "create", refname, 0))
die("create %s: missing <newvalue>", refname);
if ((next = parse_next_arg(next, &newvalue)) != NULL)
update_store_new_sha1(update, newvalue.buf);
else
die("create %s missing <newvalue>", ref.buf);
if (is_null_sha1(update->new_sha1))
die("create %s given zero new value", ref.buf);
if (is_null_sha1(new_sha1))
die("create %s: zero <newvalue>", refname);
if (*next != line_termination)
die("create %s: extra input: %s", refname, next);
if (next && *next)
die("create %s has extra input: %s", ref.buf, next);
ref_transaction_create(transaction, refname, new_sha1, update_flags);
update_flags = 0;
free(refname);
return next;
}
static void parse_cmd_delete(const char *next)
static const char *parse_cmd_delete(struct strbuf *input, const char *next)
{
struct strbuf ref = STRBUF_INIT;
struct strbuf oldvalue = STRBUF_INIT;
struct ref_update *update;
char *refname;
unsigned char old_sha1[20];
int have_old;
update = update_alloc();
refname = parse_refname(input, &next);
if (!refname)
die("delete: missing <ref>");
if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
update_store_ref_name(update, ref.buf);
else
die("delete line missing <ref>");
if (parse_next_sha1(input, &next, old_sha1, "delete", refname,
PARSE_SHA1_OLD)) {
have_old = 0;
} else {
if (is_null_sha1(old_sha1))
die("delete %s: zero <oldvalue>", refname);
have_old = 1;
}
if (*next != line_termination)
die("delete %s: extra input: %s", refname, next);
if ((next = parse_next_arg(next, &oldvalue)) != NULL)
update_store_old_sha1(update, oldvalue.buf);
else if(!line_termination)
die("delete %s missing [<oldvalue>] NUL", ref.buf);
if (update->have_old && is_null_sha1(update->old_sha1))
die("delete %s given zero old value", ref.buf);
ref_transaction_delete(transaction, refname, old_sha1,
update_flags, have_old);
if (next && *next)
die("delete %s has extra input: %s", ref.buf, next);
update_flags = 0;
free(refname);
return next;
}
static void parse_cmd_verify(const char *next)
static const char *parse_cmd_verify(struct strbuf *input, const char *next)
{
struct strbuf ref = STRBUF_INIT;
struct strbuf value = STRBUF_INIT;
struct ref_update *update;
char *refname;
unsigned char new_sha1[20];
unsigned char old_sha1[20];
int have_old;
refname = parse_refname(input, &next);
if (!refname)
die("verify: missing <ref>");
if (parse_next_sha1(input, &next, old_sha1, "verify", refname,
PARSE_SHA1_OLD)) {
hashclr(new_sha1);
have_old = 0;
} else {
hashcpy(new_sha1, old_sha1);
have_old = 1;
}
update = update_alloc();
if (*next != line_termination)
die("verify %s: extra input: %s", refname, next);
if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
update_store_ref_name(update, ref.buf);
else
die("verify line missing <ref>");
ref_transaction_update(transaction, refname, new_sha1, old_sha1,
update_flags, have_old);
if ((next = parse_next_arg(next, &value)) != NULL) {
update_store_old_sha1(update, value.buf);
update_store_new_sha1(update, value.buf);
} else if(!line_termination)
die("verify %s missing [<oldvalue>] NUL", ref.buf);
update_flags = 0;
free(refname);
if (next && *next)
die("verify %s has extra input: %s", ref.buf, next);
return next;
}
static void parse_cmd_option(const char *next)
static const char *parse_cmd_option(struct strbuf *input, const char *next)
{
if (!strcmp(next, "no-deref"))
if (!strncmp(next, "no-deref", 8) && next[8] == line_termination)
update_flags |= REF_NODEREF;
else
die("option unknown: %s", next);
return next + 8;
}
static void update_refs_stdin(void)
{
struct strbuf cmd = STRBUF_INIT;
struct strbuf input = STRBUF_INIT;
const char *next;
if (strbuf_read(&input, 0, 1000) < 0)
die_errno("could not read from stdin");
next = input.buf;
/* Read each line dispatch its command */
while (strbuf_getline(&cmd, stdin, line_termination) != EOF)
if (!cmd.buf[0])
while (next < input.buf + input.len) {
if (*next == line_termination)
die("empty command in input");
else if (isspace(*cmd.buf))
die("whitespace before command: %s", cmd.buf);
else if (starts_with(cmd.buf, "update "))
parse_cmd_update(cmd.buf + 7);
else if (starts_with(cmd.buf, "create "))
parse_cmd_create(cmd.buf + 7);
else if (starts_with(cmd.buf, "delete "))
parse_cmd_delete(cmd.buf + 7);
else if (starts_with(cmd.buf, "verify "))
parse_cmd_verify(cmd.buf + 7);
else if (starts_with(cmd.buf, "option "))
parse_cmd_option(cmd.buf + 7);
else if (isspace(*next))
die("whitespace before command: %s", next);
else if (starts_with(next, "update "))
next = parse_cmd_update(&input, next + 7);
else if (starts_with(next, "create "))
next = parse_cmd_create(&input, next + 7);
else if (starts_with(next, "delete "))
next = parse_cmd_delete(&input, next + 7);
else if (starts_with(next, "verify "))
next = parse_cmd_verify(&input, next + 7);
else if (starts_with(next, "option "))
next = parse_cmd_option(&input, next + 7);
else
die("unknown command: %s", cmd.buf);
die("unknown command: %s", next);
next++;
}
strbuf_release(&cmd);
strbuf_release(&input);
}
int cmd_update_ref(int argc, const char **argv, const char *prefix)
......@@ -268,12 +359,17 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
die("Refusing to perform update with empty message.");
if (read_stdin) {
int ret;
transaction = ref_transaction_begin();
if (delete || no_deref || argc > 0)
usage_with_options(git_update_ref_usage, options);
if (end_null)
line_termination = '\0';
update_refs_stdin();
return update_refs(msg, updates, updates_count, DIE_ON_ERR);
ret = ref_transaction_commit(transaction, msg,
UPDATE_REFS_DIE_ON_ERR);
return ret;
}
if (end_null)
......@@ -305,5 +401,5 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
return delete_ref(refname, oldval ? oldsha1 : NULL, flags);
else
return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
flags, DIE_ON_ERR);
flags, UPDATE_REFS_DIE_ON_ERR);
}
......@@ -31,7 +31,8 @@ static int update_ref_env(const char *action,
rla = "(reflog update)";
if (snprintf(msg, sizeof(msg), "%s: %s", rla, action) >= sizeof(msg))
warning("reflog message too long: %.*s...", 50, msg);
return update_ref(msg, refname, sha1, oldval, 0, QUIET_ON_ERR);
return update_ref(msg, refname, sha1, oldval, 0,
UPDATE_REFS_QUIET_ON_ERR);
}
static int update_local_ref(const char *name,
......
......@@ -62,7 +62,7 @@ int notes_cache_write(struct notes_cache *c)
if (commit_tree(&msg, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0)
return -1;
if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL,
0, QUIET_ON_ERR) < 0)
0, UPDATE_REFS_QUIET_ON_ERR) < 0)
return -1;
return 0;
......
......@@ -48,7 +48,8 @@ void commit_notes(struct notes_tree *t, const char *msg)
create_notes_commit(t, NULL, &buf, commit_sha1);
strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */
update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR);
update_ref(buf.buf, t->ref, commit_sha1, NULL, 0,
UPDATE_REFS_DIE_ON_ERR);
strbuf_release(&buf);
}
......
......@@ -3243,9 +3243,9 @@ static struct ref_lock *update_ref_lock(const char *refname,
if (!lock) {
const char *str = "Cannot lock the ref '%s'.";
switch (onerr) {
case MSG_ON_ERR: error(str, refname); break;
case DIE_ON_ERR: die(str, refname); break;
case QUIET_ON_ERR: break;
case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
case UPDATE_REFS_QUIET_ON_ERR: break;
}
}
return lock;
......@@ -3258,15 +3258,118 @@ static int update_ref_write(const char *action, const char *refname,
if (write_ref_sha1(lock, sha1, action) < 0) {
const char *str = "Cannot update the ref '%s'.";
switch (onerr) {
case MSG_ON_ERR: error(str, refname); break;
case DIE_ON_ERR: die(str, refname); break;
case QUIET_ON_ERR: break;
case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
case UPDATE_REFS_QUIET_ON_ERR: break;
}
return 1;
}
return 0;
}
/**
* Information needed for a single ref update. Set new_sha1 to the
* new value or to zero to delete the ref. To check the old value
* while locking the ref, set have_old to 1 and set old_sha1 to the
* value or to zero to ensure the ref does not exist before update.
*/
struct ref_update {
unsigned char new_sha1[20];
unsigned char old_sha1[20];
int flags; /* REF_NODEREF? */
int have_old; /* 1 if old_sha1 is valid, 0 otherwise */
struct ref_lock *lock;
int type;
const char refname[FLEX_ARRAY];
};
/*
* Data structure for holding a reference transaction, which can
* consist of checks and updates to multiple references, carried out
* as atomically as possible. This structure is opaque to callers.
*/
struct ref_transaction {
struct ref_update **updates;
size_t alloc;
size_t nr;
};
struct ref_transaction *ref_transaction_begin(void)
{
return xcalloc(1, sizeof(struct ref_transaction));
}
static void ref_transaction_free(struct ref_transaction *transaction)
{
int i;
for (i = 0; i < transaction->nr; i++)
free(transaction->updates[i]);
free(transaction->updates);
free(transaction);
}
void ref_transaction_rollback(struct ref_transaction *transaction)
{
ref_transaction_free(transaction);
}
static struct ref_update *add_update(struct ref_transaction *transaction,
const char *refname)
{
size_t len = strlen(refname);
struct ref_update *update = xcalloc(1, sizeof(*update) + len + 1);
strcpy((char *)update->refname, refname);
ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
transaction->updates[transaction->nr++] = update;
return update;
}
void ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
unsigned char *new_sha1, unsigned char *old_sha1,
int flags, int have_old)
{
struct ref_update *update = add_update(transaction, refname);
hashcpy(update->new_sha1, new_sha1);
update->flags = flags;
update->have_old = have_old;
if (have_old)
hashcpy(update->old_sha1, old_sha1);
}
void ref_transaction_create(struct ref_transaction *transaction,
const char *refname,
unsigned char *new_sha1,
int flags)
{
struct ref_update *update = add_update(transaction, refname);
assert(!is_null_sha1(new_sha1));
hashcpy(update->new_sha1, new_sha1);
hashclr(update->old_sha1);
update->flags = flags;
update->have_old = 1;
}
void ref_transaction_delete(struct ref_transaction *transaction,
const char *refname,
unsigned char *old_sha1,
int flags, int have_old)
{
struct ref_update *update = add_update(transaction, refname);
update->flags = flags;
update->have_old = have_old;
if (have_old) {
assert(!is_null_sha1(old_sha1));
hashcpy(update->old_sha1, old_sha1);
}
}
int update_ref(const char *action, const char *refname,
const unsigned char *sha1, const unsigned char *oldval,
int flags, enum action_on_err onerr)
......@@ -3282,7 +3385,7 @@ static int ref_update_compare(const void *r1, const void *r2)
{
const struct ref_update * const *u1 = r1;
const struct ref_update * const *u2 = r2;
return strcmp((*u1)->ref_name, (*u2)->ref_name);
return strcmp((*u1)->refname, (*u2)->refname);
}
static int ref_update_reject_duplicates(struct ref_update **updates, int n,
......@@ -3290,15 +3393,15 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,
{
int i;
for (i = 1; i < n; i++)
if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) {
if (!strcmp(updates[i - 1]->refname, updates[i]->refname)) {
const char *str =
"Multiple updates for ref '%s' not allowed.";
switch (onerr) {
case MSG_ON_ERR:
error(str, updates[i]->ref_name); break;
case DIE_ON_ERR:
die(str, updates[i]->ref_name); break;
case QUIET_ON_ERR:
case UPDATE_REFS_MSG_ON_ERR:
error(str, updates[i]->refname); break;
case UPDATE_REFS_DIE_ON_ERR:
die(str, updates[i]->refname); break;
case UPDATE_REFS_QUIET_ON_ERR:
break;
}
return 1;
......@@ -3306,26 +3409,21 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,
return 0;
}
int update_refs(const char *action, const struct ref_update **updates_orig,
int n, enum action_on_err onerr)
int ref_transaction_commit(struct ref_transaction *transaction,
const char *msg, enum action_on_err onerr)
{
int ret = 0, delnum = 0, i;
struct ref_update **updates;
int *types;
struct ref_lock **locks;
const char **delnames;
int n = transaction->nr;
struct ref_update **updates = transaction->updates;
if (!updates_orig || !n)
if (!n)
return 0;
/* Allocate work space */
updates = xmalloc(sizeof(*updates) * n);
types = xmalloc(sizeof(*types) * n);
locks = xcalloc(n, sizeof(*locks));
delnames = xmalloc(sizeof(*delnames) * n);
/* Copy, sort, and reject duplicate refs */
memcpy(updates, updates_orig, sizeof(*updates) * n);
qsort(updates, n, sizeof(*updates), ref_update_compare);
ret = ref_update_reject_duplicates(updates, n, onerr);
if (ret)
......@@ -3333,35 +3431,44 @@ int update_refs(const char *action, const struct ref_update **updates_orig,
/* Acquire all locks while verifying old values */
for (i = 0; i < n; i++) {
locks[i] = update_ref_lock(updates[i]->ref_name,
(updates[i]->have_old ?
updates[i]->old_sha1 : NULL),
updates[i]->flags,
&types[i], onerr);
if (!locks[i]) {
struct ref_update *update = updates[i];
update->lock = update_ref_lock(update->refname,
(update->have_old ?
update->old_sha1 : NULL),
update->flags,
&update->type, onerr);
if (!update->lock) {
ret = 1;
goto cleanup;
}
}
/* Perform updates first so live commits remain referenced */
for (i = 0; i < n; i++)
if (!is_null_sha1(updates[i]->new_sha1)) {
ret = update_ref_write(action,
updates[i]->ref_name,
updates[i]->new_sha1,
locks[i], onerr);
locks[i] = NULL; /* freed by update_ref_write */
for (i = 0; i < n; i++) {
struct ref_update *update = updates[i];
if (!is_null_sha1(update->new_sha1)) {
ret = update_ref_write(msg,
update->refname,
update->new_sha1,
update->lock, onerr);
update->lock = NULL; /* freed by update_ref_write */
if (ret)
goto cleanup;
}
}
/* Perform deletes now that updates are safely completed */
for (i = 0; i < n; i++)
if (locks[i]) {
delnames[delnum++] = locks[i]->ref_name;
ret |= delete_ref_loose(locks[i], types[i]);
for (i = 0; i < n; i++) {
struct ref_update *update = updates[i];
if (update->lock) {
delnames[delnum++] = update->lock->ref_name;
ret |= delete_ref_loose(update->lock, update->type);
}
}
ret |= repack_without_refs(delnames, delnum);
for (i = 0; i < delnum; i++)
unlink_or_warn(git_path("logs/%s", delnames[i]));
......@@ -3369,12 +3476,10 @@ int update_refs(const char *action, const struct ref_update **updates_orig,
cleanup:
for (i = 0; i < n; i++)
if (locks[i])
unlock_ref(locks[i]);
free(updates);
free(types);
free(locks);
if (updates[i]->lock)
unlock_ref(updates[i]->lock);
free(delnames);
ref_transaction_free(transaction);
return ret;
}
......
......@@ -10,19 +10,7 @@ struct ref_lock {
int force_write;
};
/**
* Information needed for a single ref update. Set new_sha1 to the
* new value or to zero to delete the ref. To check the old value
* while locking the ref, set have_old to 1 and set old_sha1 to the
* value or to zero to ensure the ref does not exist before update.
*/
struct ref_update {
const char *ref_name;
unsigned char new_sha1[20];
unsigned char old_sha1[20];
int flags; /* REF_NODEREF? */
int have_old; /* 1 if old_sha1 is valid, 0 otherwise */
};
struct ref_transaction;
/*
* Bit values set in the flags argument passed to each_ref_fn():
......@@ -166,7 +154,7 @@ extern void unlock_ref(struct ref_lock *lock);
extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
/** Setup reflog before using. **/
int log_ref_setup(const char *ref_name, char *logfile, int bufsize);
int log_ref_setup(const char *refname, char *logfile, int bufsize);
/** Reads log for the value of ref during at_time. **/
extern int read_ref_at(const char *refname, unsigned long at_time, int cnt,
......@@ -214,18 +202,80 @@ extern int rename_ref(const char *oldref, const char *newref, const char *logmsg
*/
extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1);
/** lock a ref and then write its file */
enum action_on_err { MSG_ON_ERR, DIE_ON_ERR, QUIET_ON_ERR };
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
UPDATE_REFS_DIE_ON_ERR,
UPDATE_REFS_QUIET_ON_ERR
};
/*
* Begin a reference transaction. The reference transaction must
* eventually be commited using ref_transaction_commit() or rolled
* back using ref_transaction_rollback().
*/
struct ref_transaction *ref_transaction_begin(void);
/*
* Roll back a ref_transaction and free all associated data.
*/
void ref_transaction_rollback(struct ref_transaction *transaction);
/*
* The following functions add a reference check or update to a
* ref_transaction. In all of them, refname is the name of the
* reference to be affected. The functions make internal copies of
* refname, so the caller retains ownership of the parameter. flags
* can be REF_NODEREF; it is passed to update_ref_lock().
*/
/*
* Add a reference update to transaction. new_sha1 is the value that
* the reference should have after the update, or zeros if it should
* be deleted. If have_old is true, then old_sha1 holds the value
* that the reference should have had before the update, or zeros if
* it must not have existed beforehand.
*/
void ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
unsigned char *new_sha1, unsigned char *old_sha1,
int flags, int have_old);
/*
* Add a reference creation to transaction. new_sha1 is the value
* that the reference should have after the update; it must not be the
* null SHA-1. It is verified that the reference does not exist
* already.
*/
void ref_transaction_create(struct ref_transaction *transaction,
const char *refname,
unsigned char *new_sha1,
int flags);
/*
* Add a reference deletion to transaction. If have_old is true, then
* old_sha1 holds the value that the reference should have had before
* the update (which must not be the null SHA-1).
*/
void ref_transaction_delete(struct ref_transaction *transaction,
const char *refname,
unsigned char *old_sha1,
int flags, int have_old);
/*
* Commit all of the changes that have been queued in transaction, as
* atomically as possible. Return a nonzero value if there is a
* problem. The ref_transaction is freed by this function.
*/
int ref_transaction_commit(struct ref_transaction *transaction,
const char *msg, enum action_on_err onerr);
/** Lock a ref and then write its file */
int update_ref(const char *action, const char *refname,
const unsigned char *sha1, const unsigned char *oldval,
int flags, enum action_on_err onerr);
/**
* Lock all refs and then perform all modifications.
*/
int update_refs(const char *action, const struct ref_update **updates,
int n, enum action_on_err onerr);
extern int parse_hide_refs_config(const char *var, const char *value, const char *);
extern int ref_is_hidden(const char *);
......
......@@ -350,22 +350,28 @@ test_expect_success 'stdin fails on unknown command' '
grep "fatal: unknown command: unknown $a" err
'
test_expect_success 'stdin fails on badly quoted input' '
test_expect_success 'stdin fails on unbalanced quotes' '
echo "create $a \"master" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: badly quoted argument: \\\"master" err
'
test_expect_success 'stdin fails on arguments not separated by space' '
test_expect_success 'stdin fails on invalid escape' '
echo "create $a \"ma\zter\"" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: badly quoted argument: \\\"ma\\\\zter\\\"" err
'
test_expect_success 'stdin fails on junk after quoted argument' '
echo "create \"$a\"master" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: expected SP but got: master" err
grep "fatal: unexpected character after quoted argument: \\\"$a\\\"master" err
'
test_expect_success 'stdin fails create with no ref' '
echo "create " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: create line missing <ref>" err
grep "fatal: create: missing <ref>" err
'
test_expect_success 'stdin fails create with bad ref name' '
......@@ -377,19 +383,19 @@ test_expect_success 'stdin fails create with bad ref name' '
test_expect_success 'stdin fails create with no new value' '
echo "create $a" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: create $a missing <newvalue>" err
grep "fatal: create $a: missing <newvalue>" err
'
test_expect_success 'stdin fails create with too many arguments' '
echo "create $a $m $m" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: create $a has extra input: $m" err
grep "fatal: create $a: extra input: $m" err
'
test_expect_success 'stdin fails update with no ref' '
echo "update " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: update line missing <ref>" err
grep "fatal: update: missing <ref>" err
'
test_expect_success 'stdin fails update with bad ref name' '
......@@ -401,19 +407,19 @@ test_expect_success 'stdin fails update with bad ref name' '
test_expect_success 'stdin fails update with no new value' '
echo "update $a" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: update $a missing <newvalue>" err
grep "fatal: update $a: missing <newvalue>" err
'
test_expect_success 'stdin fails update with too many arguments' '
echo "update $a $m $m $m" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: update $a has extra input: $m" err
grep "fatal: update $a: extra input: $m" err
'
test_expect_success 'stdin fails delete with no ref' '
echo "delete " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: delete line missing <ref>" err
grep "fatal: delete: missing <ref>" err
'
test_expect_success 'stdin fails delete with bad ref name' '
......@@ -425,13 +431,13 @@ test_expect_success 'stdin fails delete with bad ref name' '
test_expect_success 'stdin fails delete with too many arguments' '
echo "delete $a $m $m" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: delete $a has extra input: $m" err
grep "fatal: delete $a: extra input: $m" err
'
test_expect_success 'stdin fails verify with too many arguments' '
echo "verify $a $m $m" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: verify $a has extra input: $m" err
grep "fatal: verify $a: extra input: $m" err
'
test_expect_success 'stdin fails option with unknown name' '
......@@ -458,6 +464,24 @@ test_expect_success 'stdin create ref works' '
test_cmp expect actual
'
test_expect_success 'stdin succeeds with quoted argument' '
git update-ref -d $a &&
echo "create $a \"$m\"" >stdin &&
git update-ref --stdin <stdin &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual
'
test_expect_success 'stdin succeeds with escaped character' '
git update-ref -d $a &&
echo "create $a \"ma\\163ter\"" >stdin &&
git update-ref --stdin <stdin &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual
'
test_expect_success 'stdin update ref creates with zero old value' '
echo "update $b $m $Z" >stdin &&
git update-ref --stdin <stdin &&
......@@ -494,21 +518,21 @@ test_expect_success 'stdin update ref fails with wrong old value' '
test_expect_success 'stdin update ref fails with bad old value' '
echo "update $c $m does-not-exist" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: invalid old value for ref $c: does-not-exist" err &&
grep "fatal: update $c: invalid <oldvalue>: does-not-exist" err &&
test_must_fail git rev-parse --verify -q $c
'
test_expect_success 'stdin create ref fails with bad new value' '
echo "create $c does-not-exist" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: invalid new value for ref $c: does-not-exist" err &&
grep "fatal: create $c: invalid <newvalue>: does-not-exist" err &&
test_must_fail git rev-parse --verify -q $c
'
test_expect_success 'stdin create ref fails with zero new value' '
echo "create $c " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: create $c given zero new value" err &&
grep "fatal: create $c: zero <newvalue>" err &&
test_must_fail git rev-parse --verify -q $c
'
......@@ -532,7 +556,7 @@ test_expect_success 'stdin delete ref fails with wrong old value' '
test_expect_success 'stdin delete ref fails with zero old value' '
echo "delete $a " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: delete $a given zero old value" err &&
grep "fatal: delete $a: zero <oldvalue>" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual
......@@ -673,7 +697,7 @@ test_expect_success 'stdin -z fails on unknown command' '
test_expect_success 'stdin -z fails create with no ref' '
printf $F "create " >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: create line missing <ref>" err
grep "fatal: create: missing <ref>" err
'
test_expect_success 'stdin -z fails create with bad ref name' '
......@@ -685,7 +709,7 @@ test_expect_success 'stdin -z fails create with bad ref name' '
test_expect_success 'stdin -z fails create with no new value' '
printf $F "create $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: create $a missing <newvalue>" err
grep "fatal: create $a: unexpected end of input when reading <newvalue>" err
'
test_expect_success 'stdin -z fails create with too many arguments' '
......@@ -697,25 +721,39 @@ test_expect_success 'stdin -z fails create with too many arguments' '
test_expect_success 'stdin -z fails update with no ref' '
printf $F "update " >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: update line missing <ref>" err
grep "fatal: update: missing <ref>" err
'
test_expect_success 'stdin -z fails update with too few args' '
printf $F "update $a" "$m" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: update $a: unexpected end of input when reading <oldvalue>" err
'
test_expect_success 'stdin -z fails update with bad ref name' '
printf $F "update ~a" "$m" >stdin &&
printf $F "update ~a" "$m" "" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: invalid ref format: ~a" err
'
test_expect_success 'stdin -z emits warning with empty new value' '
git update-ref $a $m &&
printf $F "update $a" "" "" >stdin &&
git update-ref -z --stdin <stdin 2>err &&
grep "warning: update $a: missing <newvalue>, treating as zero" err &&
test_must_fail git rev-parse --verify -q $a
'
test_expect_success 'stdin -z fails update with no new value' '
printf $F "update $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: update $a missing <newvalue>" err
grep "fatal: update $a: unexpected end of input when reading <newvalue>" err
'
test_expect_success 'stdin -z fails update with no old value' '
printf $F "update $a" "$m" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: update $a missing \\[<oldvalue>\\] NUL" err
grep "fatal: update $a: unexpected end of input when reading <oldvalue>" err
'
test_expect_success 'stdin -z fails update with too many arguments' '
......@@ -727,7 +765,7 @@ test_expect_success 'stdin -z fails update with too many arguments' '
test_expect_success 'stdin -z fails delete with no ref' '
printf $F "delete " >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: delete line missing <ref>" err
grep "fatal: delete: missing <ref>" err
'
test_expect_success 'stdin -z fails delete with bad ref name' '
......@@ -739,7 +777,7 @@ test_expect_success 'stdin -z fails delete with bad ref name' '
test_expect_success 'stdin -z fails delete with no old value' '
printf $F "delete $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: delete $a missing \\[<oldvalue>\\] NUL" err
grep "fatal: delete $a: unexpected end of input when reading <oldvalue>" err
'
test_expect_success 'stdin -z fails delete with too many arguments' '
......@@ -757,7 +795,7 @@ test_expect_success 'stdin -z fails verify with too many arguments' '
test_expect_success 'stdin -z fails verify with no old value' '
printf $F "verify $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: verify $a missing \\[<oldvalue>\\] NUL" err
grep "fatal: verify $a: unexpected end of input when reading <oldvalue>" err
'
test_expect_success 'stdin -z fails option with unknown name' '
......@@ -816,7 +854,7 @@ test_expect_success 'stdin -z update ref fails with wrong old value' '
test_expect_success 'stdin -z update ref fails with bad old value' '
printf $F "update $c" "$m" "does-not-exist" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: invalid old value for ref $c: does-not-exist" err &&
grep "fatal: update $c: invalid <oldvalue>: does-not-exist" err &&
test_must_fail git rev-parse --verify -q $c
'
......@@ -834,14 +872,14 @@ test_expect_success 'stdin -z create ref fails with bad new value' '
git update-ref -d "$c" &&
printf $F "create $c" "does-not-exist" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: invalid new value for ref $c: does-not-exist" err &&
grep "fatal: create $c: invalid <newvalue>: does-not-exist" err &&
test_must_fail git rev-parse --verify -q $c
'
test_expect_success 'stdin -z create ref fails with zero new value' '
test_expect_success 'stdin -z create ref fails with empty new value' '
printf $F "create $c" "" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: create $c given zero new value" err &&
grep "fatal: create $c: missing <newvalue>" err &&
test_must_fail git rev-parse --verify -q $c
'
......@@ -865,7 +903,7 @@ test_expect_success 'stdin -z delete ref fails with wrong old value' '
test_expect_success 'stdin -z delete ref fails with zero old value' '
printf $F "delete $a" "$Z" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: delete $a given zero old value" err &&
grep "fatal: delete $a: zero <oldvalue>" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual
......@@ -923,7 +961,7 @@ test_expect_success 'stdin -z update refs works with identity updates' '
test_expect_success 'stdin -z update refs fails with wrong old value' '
git update-ref $c $m &&
printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "" "$Z" >stdin &&
printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "$m" "$Z" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
git rev-parse $m >expect &&
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册