提交 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: ...@@ -68,7 +68,12 @@ performs all modifications together. Specify commands of the form:
option SP <opt> LF option SP <opt> LF
Quote fields containing whitespace as if they were strings in C source 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 update SP <ref> NUL <newvalue> NUL [<oldvalue>] NUL
create SP <ref> NUL <newvalue> NUL create SP <ref> NUL <newvalue> NUL
...@@ -76,8 +81,12 @@ code. Alternatively, use `-z` to specify commands without quoting: ...@@ -76,8 +81,12 @@ code. Alternatively, use `-z` to specify commands without quoting:
verify SP <ref> NUL [<oldvalue>] NUL verify SP <ref> NUL [<oldvalue>] NUL
option SP <opt> NUL option SP <opt> NUL
Lines of any other format or a repeated <ref> produce an error. In this format, use 40 "0" to specify a zero value, and use the empty
Command meanings are: 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:: update::
Set <ref> to <newvalue> after verifying <oldvalue>, if given. Set <ref> to <newvalue> after verifying <oldvalue>, if given.
...@@ -102,9 +111,6 @@ option:: ...@@ -102,9 +111,6 @@ option::
The only valid option is `no-deref` to avoid dereferencing The only valid option is `no-deref` to avoid dereferencing
a symbolic ref. 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 If all <ref>s can be locked with matching <oldvalue>s
simultaneously, all modifications are performed. Otherwise, no simultaneously, all modifications are performed. Otherwise, no
modifications are performed. Note that while each individual modifications are performed. Note that while each individual
......
...@@ -624,7 +624,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts, ...@@ -624,7 +624,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
/* Nothing to do. */ /* Nothing to do. */
} else if (opts->force_detach || !new->path) { /* No longer on any branch. */ } else if (opts->force_detach || !new->path) { /* No longer on any branch. */
update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL, 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 (!opts->quiet) {
if (old->path && advice_detached_head) if (old->path && advice_detached_head)
detach_advice(new->name); detach_advice(new->name);
......
...@@ -521,7 +521,7 @@ static void write_followtags(const struct ref *refs, const char *msg) ...@@ -521,7 +521,7 @@ static void write_followtags(const struct ref *refs, const char *msg)
if (!has_sha1_file(ref->old_sha1)) if (!has_sha1_file(ref->old_sha1))
continue; continue;
update_ref(msg, ref->name, ref->old_sha1, 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, ...@@ -589,14 +589,15 @@ static void update_head(const struct ref *our, const struct ref *remote,
create_symref("HEAD", our->name, NULL); create_symref("HEAD", our->name, NULL);
if (!option_bare) { if (!option_bare) {
const char *head = skip_prefix(our->name, "refs/heads/"); 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); install_branch_config(0, head, option_origin, our->name);
} }
} else if (our) { } else if (our) {
struct commit *c = lookup_commit_reference(our->old_sha1); struct commit *c = lookup_commit_reference(our->old_sha1);
/* --branch specifies a non-branch (i.e. tags), detach HEAD */ /* --branch specifies a non-branch (i.e. tags), detach HEAD */
update_ref(msg, "HEAD", c->object.sha1, update_ref(msg, "HEAD", c->object.sha1,
NULL, REF_NODEREF, DIE_ON_ERR); NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
} else if (remote) { } else if (remote) {
/* /*
* We know remote HEAD points to a non-branch, or * 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, ...@@ -604,7 +605,7 @@ static void update_head(const struct ref *our, const struct ref *remote,
* Detach HEAD in all these cases. * Detach HEAD in all these cases.
*/ */
update_ref(msg, "HEAD", remote->old_sha1, 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, ...@@ -398,7 +398,7 @@ static void finish(struct commit *head_commit,
const char *argv_gc_auto[] = { "gc", "--auto", NULL }; const char *argv_gc_auto[] = { "gc", "--auto", NULL };
update_ref(reflog_message.buf, "HEAD", update_ref(reflog_message.buf, "HEAD",
new_head, head, 0, new_head, head, 0,
DIE_ON_ERR); UPDATE_REFS_DIE_ON_ERR);
/* /*
* We ignore errors in 'gc --auto', since the * We ignore errors in 'gc --auto', since the
* user should see them. * user should see them.
...@@ -1222,7 +1222,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) ...@@ -1222,7 +1222,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
die(_("%s - not something we can merge"), argv[0]); die(_("%s - not something we can merge"), argv[0]);
read_empty(remote_head->object.sha1, 0); read_empty(remote_head->object.sha1, 0);
update_ref("initial pull", "HEAD", remote_head->object.sha1, update_ref("initial pull", "HEAD", remote_head->object.sha1,
NULL, 0, DIE_ON_ERR); NULL, 0, UPDATE_REFS_DIE_ON_ERR);
goto done; goto done;
} else { } else {
struct strbuf merge_names = STRBUF_INIT; struct strbuf merge_names = STRBUF_INIT;
...@@ -1339,7 +1339,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) ...@@ -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, 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) if (remoteheads && !common)
; /* No common ancestors found. We need a real merge. */ ; /* No common ancestors found. We need a real merge. */
......
...@@ -717,7 +717,7 @@ static int merge_commit(struct notes_merge_options *o) ...@@ -717,7 +717,7 @@ static int merge_commit(struct notes_merge_options *o)
strbuf_insert(&msg, 0, "notes: ", 7); strbuf_insert(&msg, 0, "notes: ", 7);
update_ref(msg.buf, o->local_ref, sha1, update_ref(msg.buf, o->local_ref, sha1,
is_null_sha1(parent_sha1) ? NULL : parent_sha1, is_null_sha1(parent_sha1) ? NULL : parent_sha1,
0, DIE_ON_ERR); 0, UPDATE_REFS_DIE_ON_ERR);
free_notes(t); free_notes(t);
strbuf_release(&msg); strbuf_release(&msg);
...@@ -812,11 +812,11 @@ static int merge(int argc, const char **argv, const char *prefix) ...@@ -812,11 +812,11 @@ static int merge(int argc, const char **argv, const char *prefix)
if (result >= 0) /* Merge resulted (trivially) in result_sha1 */ if (result >= 0) /* Merge resulted (trivially) in result_sha1 */
/* Update default notes ref with new commit */ /* Update default notes ref with new commit */
update_ref(msg.buf, default_notes_ref(), result_sha1, NULL, 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 */ else { /* Merge has unresolved conflicts */
/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL, 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 */ /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
die("Failed to store link to current notes ref (%s)", 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) ...@@ -252,11 +252,13 @@ static int reset_refs(const char *rev, const unsigned char *sha1)
if (!get_sha1("HEAD", sha1_orig)) { if (!get_sha1("HEAD", sha1_orig)) {
orig = sha1_orig; orig = sha1_orig;
set_reflog_message(&msg, "updating ORIG_HEAD", NULL); 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) } else if (old_orig)
delete_ref("ORIG_HEAD", old_orig, 0); delete_ref("ORIG_HEAD", old_orig, 0);
set_reflog_message(&msg, "updating HEAD", rev); 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); strbuf_release(&msg);
return update_ref_status; return update_ref_status;
} }
......
...@@ -12,238 +12,329 @@ static const char * const git_update_ref_usage[] = { ...@@ -12,238 +12,329 @@ static const char * const git_update_ref_usage[] = {
NULL NULL
}; };
static int updates_alloc; static struct ref_transaction *transaction;
static int updates_count;
static const struct ref_update **updates;
static char line_termination = '\n'; static char line_termination = '\n';
static int update_flags; static int update_flags;
static struct ref_update *update_alloc(void) /*
{ * Parse one whitespace- or NUL-terminated, possibly C-quoted argument
struct ref_update *update; * 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
/* Allocate and zero-init a struct ref_update */ * function is only used if not -z.
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;
}
static const char *parse_arg(const char *next, struct strbuf *arg) 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)) while (*next && !isspace(*next))
strbuf_addch(arg, *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; 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" */ struct strbuf ref = STRBUF_INIT;
strbuf_reset(arg);
if (line_termination) { if (line_termination) {
/* Without -z, use the next argument */ /* Without -z, use the next argument */
next = parse_arg(next, arg); *next = parse_arg(*next, &ref);
} else { } else {
/* With -z, use rest of first NUL-terminated line */ /* With -z, use everything up to the next NUL */
strbuf_addstr(arg, next); strbuf_addstr(&ref, *next);
next = next + arg->len; *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 */ struct strbuf arg = STRBUF_INIT;
strbuf_reset(arg); int ret = 0;
if (*next == input->buf + input->len)
goto eof;
if (line_termination) { if (line_termination) {
/* Without -z, consume SP and use next argument */ /* Without -z, consume SP and use next argument */
if (!*next) if (!**next || **next == line_termination)
return NULL; return 1;
if (*next != ' ') if (**next != ' ')
die("expected SP but got: %s", next); die("%s %s: expected SP but got: %s",
next = parse_arg(next + 1, arg); 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 { } else {
/* With -z, read the next NUL-terminated line */ /* With -z, read the next NUL-terminated line */
if (*next) if (**next)
die("expected NUL but got: %s", next); die("%s %s: expected NUL but got: %s",
if (strbuf_getline(arg, stdin, '\0') == EOF) command, refname, *next);
return NULL; (*next)++;
next = arg->buf + arg->len; 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; char *refname;
struct strbuf newvalue = STRBUF_INIT; unsigned char new_sha1[20];
struct strbuf oldvalue = STRBUF_INIT; unsigned char old_sha1[20];
struct ref_update *update; 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]) if (parse_next_sha1(input, &next, new_sha1, "update", refname,
update_store_ref_name(update, ref.buf); PARSE_SHA1_ALLOW_EMPTY))
else die("update %s: missing <newvalue>", refname);
die("update line missing <ref>");
if ((next = parse_next_arg(next, &newvalue)) != NULL) have_old = !parse_next_sha1(input, &next, old_sha1, "update", refname,
update_store_new_sha1(update, newvalue.buf); PARSE_SHA1_OLD);
else
die("update %s missing <newvalue>", ref.buf);
if ((next = parse_next_arg(next, &oldvalue)) != NULL) if (*next != line_termination)
update_store_old_sha1(update, oldvalue.buf); die("update %s: extra input: %s", refname, next);
else if(!line_termination)
die("update %s missing [<oldvalue>] NUL", ref.buf);
if (next && *next) ref_transaction_update(transaction, refname, new_sha1, old_sha1,
die("update %s has extra input: %s", ref.buf, next); 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; char *refname;
struct strbuf newvalue = STRBUF_INIT; unsigned char new_sha1[20];
struct ref_update *update;
update = update_alloc(); refname = parse_refname(input, &next);
update->have_old = 1; if (!refname)
die("create: missing <ref>");
if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) if (parse_next_sha1(input, &next, new_sha1, "create", refname, 0))
update_store_ref_name(update, ref.buf); die("create %s: missing <newvalue>", refname);
else
die("create line missing <ref>");
if ((next = parse_next_arg(next, &newvalue)) != NULL) if (is_null_sha1(new_sha1))
update_store_new_sha1(update, newvalue.buf); die("create %s: zero <newvalue>", refname);
else
die("create %s missing <newvalue>", ref.buf); if (*next != line_termination)
if (is_null_sha1(update->new_sha1)) die("create %s: extra input: %s", refname, next);
die("create %s given zero new value", ref.buf);
ref_transaction_create(transaction, refname, new_sha1, update_flags);
update_flags = 0;
free(refname);
if (next && *next) return next;
die("create %s has extra input: %s", ref.buf, 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; char *refname;
struct strbuf oldvalue = STRBUF_INIT; unsigned char old_sha1[20];
struct ref_update *update; 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]) if (parse_next_sha1(input, &next, old_sha1, "delete", refname,
update_store_ref_name(update, ref.buf); PARSE_SHA1_OLD)) {
else have_old = 0;
die("delete line missing <ref>"); } else {
if (is_null_sha1(old_sha1))
die("delete %s: zero <oldvalue>", refname);
have_old = 1;
}
if ((next = parse_next_arg(next, &oldvalue)) != NULL) if (*next != line_termination)
update_store_old_sha1(update, oldvalue.buf); die("delete %s: extra input: %s", refname, next);
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);
if (next && *next) ref_transaction_delete(transaction, refname, old_sha1,
die("delete %s has extra input: %s", ref.buf, next); update_flags, have_old);
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; char *refname;
struct strbuf value = STRBUF_INIT; unsigned char new_sha1[20];
struct ref_update *update; 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]) ref_transaction_update(transaction, refname, new_sha1, old_sha1,
update_store_ref_name(update, ref.buf); update_flags, have_old);
else
die("verify line missing <ref>");
if ((next = parse_next_arg(next, &value)) != NULL) { update_flags = 0;
update_store_old_sha1(update, value.buf); free(refname);
update_store_new_sha1(update, value.buf);
} else if(!line_termination)
die("verify %s missing [<oldvalue>] NUL", ref.buf);
if (next && *next) return next;
die("verify %s has extra input: %s", ref.buf, 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; update_flags |= REF_NODEREF;
else else
die("option unknown: %s", next); die("option unknown: %s", next);
return next + 8;
} }
static void update_refs_stdin(void) 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 */ /* Read each line dispatch its command */
while (strbuf_getline(&cmd, stdin, line_termination) != EOF) while (next < input.buf + input.len) {
if (!cmd.buf[0]) if (*next == line_termination)
die("empty command in input"); die("empty command in input");
else if (isspace(*cmd.buf)) else if (isspace(*next))
die("whitespace before command: %s", cmd.buf); die("whitespace before command: %s", next);
else if (starts_with(cmd.buf, "update ")) else if (starts_with(next, "update "))
parse_cmd_update(cmd.buf + 7); next = parse_cmd_update(&input, next + 7);
else if (starts_with(cmd.buf, "create ")) else if (starts_with(next, "create "))
parse_cmd_create(cmd.buf + 7); next = parse_cmd_create(&input, next + 7);
else if (starts_with(cmd.buf, "delete ")) else if (starts_with(next, "delete "))
parse_cmd_delete(cmd.buf + 7); next = parse_cmd_delete(&input, next + 7);
else if (starts_with(cmd.buf, "verify ")) else if (starts_with(next, "verify "))
parse_cmd_verify(cmd.buf + 7); next = parse_cmd_verify(&input, next + 7);
else if (starts_with(cmd.buf, "option ")) else if (starts_with(next, "option "))
parse_cmd_option(cmd.buf + 7); next = parse_cmd_option(&input, next + 7);
else 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) 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) ...@@ -268,12 +359,17 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
die("Refusing to perform update with empty message."); die("Refusing to perform update with empty message.");
if (read_stdin) { if (read_stdin) {
int ret;
transaction = ref_transaction_begin();
if (delete || no_deref || argc > 0) if (delete || no_deref || argc > 0)
usage_with_options(git_update_ref_usage, options); usage_with_options(git_update_ref_usage, options);
if (end_null) if (end_null)
line_termination = '\0'; line_termination = '\0';
update_refs_stdin(); 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) if (end_null)
...@@ -305,5 +401,5 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) ...@@ -305,5 +401,5 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
return delete_ref(refname, oldval ? oldsha1 : NULL, flags); return delete_ref(refname, oldval ? oldsha1 : NULL, flags);
else else
return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL, 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, ...@@ -31,7 +31,8 @@ static int update_ref_env(const char *action,
rla = "(reflog update)"; rla = "(reflog update)";
if (snprintf(msg, sizeof(msg), "%s: %s", rla, action) >= sizeof(msg)) if (snprintf(msg, sizeof(msg), "%s: %s", rla, action) >= sizeof(msg))
warning("reflog message too long: %.*s...", 50, 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, static int update_local_ref(const char *name,
......
...@@ -62,7 +62,7 @@ int notes_cache_write(struct notes_cache *c) ...@@ -62,7 +62,7 @@ int notes_cache_write(struct notes_cache *c)
if (commit_tree(&msg, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0) if (commit_tree(&msg, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0)
return -1; return -1;
if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL, 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 -1;
return 0; return 0;
......
...@@ -48,7 +48,8 @@ void commit_notes(struct notes_tree *t, const char *msg) ...@@ -48,7 +48,8 @@ void commit_notes(struct notes_tree *t, const char *msg)
create_notes_commit(t, NULL, &buf, commit_sha1); create_notes_commit(t, NULL, &buf, commit_sha1);
strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */ 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); strbuf_release(&buf);
} }
......
...@@ -3243,9 +3243,9 @@ static struct ref_lock *update_ref_lock(const char *refname, ...@@ -3243,9 +3243,9 @@ static struct ref_lock *update_ref_lock(const char *refname,
if (!lock) { if (!lock) {
const char *str = "Cannot lock the ref '%s'."; const char *str = "Cannot lock the ref '%s'.";
switch (onerr) { switch (onerr) {
case MSG_ON_ERR: error(str, refname); break; case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
case DIE_ON_ERR: die(str, refname); break; case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
case QUIET_ON_ERR: break; case UPDATE_REFS_QUIET_ON_ERR: break;
} }
} }
return lock; return lock;
...@@ -3258,15 +3258,118 @@ static int update_ref_write(const char *action, const char *refname, ...@@ -3258,15 +3258,118 @@ static int update_ref_write(const char *action, const char *refname,
if (write_ref_sha1(lock, sha1, action) < 0) { if (write_ref_sha1(lock, sha1, action) < 0) {
const char *str = "Cannot update the ref '%s'."; const char *str = "Cannot update the ref '%s'.";
switch (onerr) { switch (onerr) {
case MSG_ON_ERR: error(str, refname); break; case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
case DIE_ON_ERR: die(str, refname); break; case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
case QUIET_ON_ERR: break; case UPDATE_REFS_QUIET_ON_ERR: break;
} }
return 1; return 1;
} }
return 0; 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, int update_ref(const char *action, const char *refname,
const unsigned char *sha1, const unsigned char *oldval, const unsigned char *sha1, const unsigned char *oldval,
int flags, enum action_on_err onerr) int flags, enum action_on_err onerr)
...@@ -3282,7 +3385,7 @@ static int ref_update_compare(const void *r1, const void *r2) ...@@ -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 *u1 = r1;
const struct ref_update * const *u2 = r2; 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, 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, ...@@ -3290,15 +3393,15 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,
{ {
int i; int i;
for (i = 1; i < n; 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 = const char *str =
"Multiple updates for ref '%s' not allowed."; "Multiple updates for ref '%s' not allowed.";
switch (onerr) { switch (onerr) {
case MSG_ON_ERR: case UPDATE_REFS_MSG_ON_ERR:
error(str, updates[i]->ref_name); break; error(str, updates[i]->refname); break;
case DIE_ON_ERR: case UPDATE_REFS_DIE_ON_ERR:
die(str, updates[i]->ref_name); break; die(str, updates[i]->refname); break;
case QUIET_ON_ERR: case UPDATE_REFS_QUIET_ON_ERR:
break; break;
} }
return 1; return 1;
...@@ -3306,26 +3409,21 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n, ...@@ -3306,26 +3409,21 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,
return 0; return 0;
} }
int update_refs(const char *action, const struct ref_update **updates_orig, int ref_transaction_commit(struct ref_transaction *transaction,
int n, enum action_on_err onerr) const char *msg, enum action_on_err onerr)
{ {
int ret = 0, delnum = 0, i; int ret = 0, delnum = 0, i;
struct ref_update **updates;
int *types;
struct ref_lock **locks;
const char **delnames; const char **delnames;
int n = transaction->nr;
struct ref_update **updates = transaction->updates;
if (!updates_orig || !n) if (!n)
return 0; return 0;
/* Allocate work space */ /* Allocate work space */
updates = xmalloc(sizeof(*updates) * n);
types = xmalloc(sizeof(*types) * n);
locks = xcalloc(n, sizeof(*locks));
delnames = xmalloc(sizeof(*delnames) * n); delnames = xmalloc(sizeof(*delnames) * n);
/* Copy, sort, and reject duplicate refs */ /* Copy, sort, and reject duplicate refs */
memcpy(updates, updates_orig, sizeof(*updates) * n);
qsort(updates, n, sizeof(*updates), ref_update_compare); qsort(updates, n, sizeof(*updates), ref_update_compare);
ret = ref_update_reject_duplicates(updates, n, onerr); ret = ref_update_reject_duplicates(updates, n, onerr);
if (ret) if (ret)
...@@ -3333,35 +3431,44 @@ int update_refs(const char *action, const struct ref_update **updates_orig, ...@@ -3333,35 +3431,44 @@ int update_refs(const char *action, const struct ref_update **updates_orig,
/* Acquire all locks while verifying old values */ /* Acquire all locks while verifying old values */
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
locks[i] = update_ref_lock(updates[i]->ref_name, struct ref_update *update = updates[i];
(updates[i]->have_old ?
updates[i]->old_sha1 : NULL), update->lock = update_ref_lock(update->refname,
updates[i]->flags, (update->have_old ?
&types[i], onerr); update->old_sha1 : NULL),
if (!locks[i]) { update->flags,
&update->type, onerr);
if (!update->lock) {
ret = 1; ret = 1;
goto cleanup; goto cleanup;
} }
} }
/* Perform updates first so live commits remain referenced */ /* Perform updates first so live commits remain referenced */
for (i = 0; i < n; i++) for (i = 0; i < n; i++) {
if (!is_null_sha1(updates[i]->new_sha1)) { struct ref_update *update = updates[i];
ret = update_ref_write(action,
updates[i]->ref_name, if (!is_null_sha1(update->new_sha1)) {
updates[i]->new_sha1, ret = update_ref_write(msg,
locks[i], onerr); update->refname,
locks[i] = NULL; /* freed by update_ref_write */ update->new_sha1,
update->lock, onerr);
update->lock = NULL; /* freed by update_ref_write */
if (ret) if (ret)
goto cleanup; goto cleanup;
} }
}
/* Perform deletes now that updates are safely completed */ /* Perform deletes now that updates are safely completed */
for (i = 0; i < n; i++) for (i = 0; i < n; i++) {
if (locks[i]) { struct ref_update *update = updates[i];
delnames[delnum++] = locks[i]->ref_name;
ret |= delete_ref_loose(locks[i], types[i]); if (update->lock) {
delnames[delnum++] = update->lock->ref_name;
ret |= delete_ref_loose(update->lock, update->type);
} }
}
ret |= repack_without_refs(delnames, delnum); ret |= repack_without_refs(delnames, delnum);
for (i = 0; i < delnum; i++) for (i = 0; i < delnum; i++)
unlink_or_warn(git_path("logs/%s", delnames[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, ...@@ -3369,12 +3476,10 @@ int update_refs(const char *action, const struct ref_update **updates_orig,
cleanup: cleanup:
for (i = 0; i < n; i++) for (i = 0; i < n; i++)
if (locks[i]) if (updates[i]->lock)
unlock_ref(locks[i]); unlock_ref(updates[i]->lock);
free(updates);
free(types);
free(locks);
free(delnames); free(delnames);
ref_transaction_free(transaction);
return ret; return ret;
} }
......
...@@ -10,19 +10,7 @@ struct ref_lock { ...@@ -10,19 +10,7 @@ struct ref_lock {
int force_write; int force_write;
}; };
/** struct ref_transaction;
* 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 */
};
/* /*
* Bit values set in the flags argument passed to each_ref_fn(): * Bit values set in the flags argument passed to each_ref_fn():
...@@ -166,7 +154,7 @@ extern void unlock_ref(struct ref_lock *lock); ...@@ -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); extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
/** Setup reflog before using. **/ /** 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. **/ /** Reads log for the value of ref during at_time. **/
extern int read_ref_at(const char *refname, unsigned long at_time, int cnt, 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 ...@@ -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); 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 {
enum action_on_err { MSG_ON_ERR, DIE_ON_ERR, QUIET_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, int update_ref(const char *action, const char *refname,
const unsigned char *sha1, const unsigned char *oldval, const unsigned char *sha1, const unsigned char *oldval,
int flags, enum action_on_err onerr); 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 parse_hide_refs_config(const char *var, const char *value, const char *);
extern int ref_is_hidden(const char *); extern int ref_is_hidden(const char *);
......
...@@ -350,22 +350,28 @@ test_expect_success 'stdin fails on unknown command' ' ...@@ -350,22 +350,28 @@ test_expect_success 'stdin fails on unknown command' '
grep "fatal: unknown command: unknown $a" err 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 && echo "create $a \"master" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err && test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: badly quoted argument: \\\"master" 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 && echo "create \"$a\"master" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err && 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' ' test_expect_success 'stdin fails create with no ref' '
echo "create " >stdin && echo "create " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err && 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' ' test_expect_success 'stdin fails create with bad ref name' '
...@@ -377,19 +383,19 @@ 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' ' test_expect_success 'stdin fails create with no new value' '
echo "create $a" >stdin && echo "create $a" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err && 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' ' test_expect_success 'stdin fails create with too many arguments' '
echo "create $a $m $m" >stdin && echo "create $a $m $m" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err && 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' ' test_expect_success 'stdin fails update with no ref' '
echo "update " >stdin && echo "update " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err && 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' ' test_expect_success 'stdin fails update with bad ref name' '
...@@ -401,19 +407,19 @@ 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' ' test_expect_success 'stdin fails update with no new value' '
echo "update $a" >stdin && echo "update $a" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err && 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' ' test_expect_success 'stdin fails update with too many arguments' '
echo "update $a $m $m $m" >stdin && echo "update $a $m $m $m" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err && 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' ' test_expect_success 'stdin fails delete with no ref' '
echo "delete " >stdin && echo "delete " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err && 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' ' test_expect_success 'stdin fails delete with bad ref name' '
...@@ -425,13 +431,13 @@ 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' ' test_expect_success 'stdin fails delete with too many arguments' '
echo "delete $a $m $m" >stdin && echo "delete $a $m $m" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err && 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' ' test_expect_success 'stdin fails verify with too many arguments' '
echo "verify $a $m $m" >stdin && echo "verify $a $m $m" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err && 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' ' test_expect_success 'stdin fails option with unknown name' '
...@@ -458,6 +464,24 @@ test_expect_success 'stdin create ref works' ' ...@@ -458,6 +464,24 @@ test_expect_success 'stdin create ref works' '
test_cmp expect actual 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' ' test_expect_success 'stdin update ref creates with zero old value' '
echo "update $b $m $Z" >stdin && echo "update $b $m $Z" >stdin &&
git update-ref --stdin <stdin && git update-ref --stdin <stdin &&
...@@ -494,21 +518,21 @@ test_expect_success 'stdin update ref fails with wrong old value' ' ...@@ -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' ' test_expect_success 'stdin update ref fails with bad old value' '
echo "update $c $m does-not-exist" >stdin && echo "update $c $m does-not-exist" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err && 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_must_fail git rev-parse --verify -q $c
' '
test_expect_success 'stdin create ref fails with bad new value' ' test_expect_success 'stdin create ref fails with bad new value' '
echo "create $c does-not-exist" >stdin && echo "create $c does-not-exist" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err && 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_must_fail git rev-parse --verify -q $c
' '
test_expect_success 'stdin create ref fails with zero new value' ' test_expect_success 'stdin create ref fails with zero new value' '
echo "create $c " >stdin && echo "create $c " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err && 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 test_must_fail git rev-parse --verify -q $c
' '
...@@ -532,7 +556,7 @@ test_expect_success 'stdin delete ref fails with wrong old value' ' ...@@ -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' ' test_expect_success 'stdin delete ref fails with zero old value' '
echo "delete $a " >stdin && echo "delete $a " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err && 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 $m >expect &&
git rev-parse $a >actual && git rev-parse $a >actual &&
test_cmp expect actual test_cmp expect actual
...@@ -673,7 +697,7 @@ test_expect_success 'stdin -z fails on unknown command' ' ...@@ -673,7 +697,7 @@ test_expect_success 'stdin -z fails on unknown command' '
test_expect_success 'stdin -z fails create with no ref' ' test_expect_success 'stdin -z fails create with no ref' '
printf $F "create " >stdin && printf $F "create " >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err && 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' ' 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' ' ...@@ -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' ' test_expect_success 'stdin -z fails create with no new value' '
printf $F "create $a" >stdin && printf $F "create $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err && 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' ' 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' ' ...@@ -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' ' test_expect_success 'stdin -z fails update with no ref' '
printf $F "update " >stdin && printf $F "update " >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err && 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' ' 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 && test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: invalid ref format: ~a" 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' ' test_expect_success 'stdin -z fails update with no new value' '
printf $F "update $a" >stdin && printf $F "update $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err && 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' ' test_expect_success 'stdin -z fails update with no old value' '
printf $F "update $a" "$m" >stdin && printf $F "update $a" "$m" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err && 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' ' 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' ' ...@@ -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' ' test_expect_success 'stdin -z fails delete with no ref' '
printf $F "delete " >stdin && printf $F "delete " >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err && 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' ' 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' ' ...@@ -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' ' test_expect_success 'stdin -z fails delete with no old value' '
printf $F "delete $a" >stdin && printf $F "delete $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err && 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' ' 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' ' ...@@ -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' ' test_expect_success 'stdin -z fails verify with no old value' '
printf $F "verify $a" >stdin && printf $F "verify $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err && 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' ' 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' ' ...@@ -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' ' test_expect_success 'stdin -z update ref fails with bad old value' '
printf $F "update $c" "$m" "does-not-exist" >stdin && printf $F "update $c" "$m" "does-not-exist" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err && 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 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' ' ...@@ -834,14 +872,14 @@ test_expect_success 'stdin -z create ref fails with bad new value' '
git update-ref -d "$c" && git update-ref -d "$c" &&
printf $F "create $c" "does-not-exist" >stdin && printf $F "create $c" "does-not-exist" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err && 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_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 && printf $F "create $c" "" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err && 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 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' ' ...@@ -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' ' test_expect_success 'stdin -z delete ref fails with zero old value' '
printf $F "delete $a" "$Z" >stdin && printf $F "delete $a" "$Z" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err && 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 $m >expect &&
git rev-parse $a >actual && git rev-parse $a >actual &&
test_cmp expect actual test_cmp expect actual
...@@ -923,7 +961,7 @@ test_expect_success 'stdin -z update refs works with identity updates' ' ...@@ -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' ' test_expect_success 'stdin -z update refs fails with wrong old value' '
git update-ref $c $m && 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 && test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err && grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
git rev-parse $m >expect && git rev-parse $m >expect &&
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册