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 ...
This commit is contained in:
commit
2cc70cefdd
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
char *refname;
|
||||
unsigned char new_sha1[20];
|
||||
unsigned char old_sha1[20];
|
||||
int have_old;
|
||||
|
||||
refname = parse_refname(input, &next);
|
||||
if (!refname)
|
||||
die("update: missing <ref>");
|
||||
|
||||
if (parse_next_sha1(input, &next, new_sha1, "update", refname,
|
||||
PARSE_SHA1_ALLOW_EMPTY))
|
||||
die("update %s: missing <newvalue>", refname);
|
||||
|
||||
have_old = !parse_next_sha1(input, &next, old_sha1, "update", refname,
|
||||
PARSE_SHA1_OLD);
|
||||
|
||||
if (*next != line_termination)
|
||||
die("update %s: extra input: %s", refname, 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_update(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 strbuf oldvalue = STRBUF_INIT;
|
||||
struct ref_update *update;
|
||||
char *refname;
|
||||
unsigned char new_sha1[20];
|
||||
|
||||
update = update_alloc();
|
||||
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("update 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("update %s missing <newvalue>", ref.buf);
|
||||
if (is_null_sha1(new_sha1))
|
||||
die("create %s: zero <newvalue>", refname);
|
||||
|
||||
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("create %s: extra input: %s", refname, next);
|
||||
|
||||
if (next && *next)
|
||||
die("update %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_create(const char *next)
|
||||
static const char *parse_cmd_delete(struct strbuf *input, const char *next)
|
||||
{
|
||||
struct strbuf ref = STRBUF_INIT;
|
||||
struct strbuf newvalue = STRBUF_INIT;
|
||||
struct ref_update *update;
|
||||
char *refname;
|
||||
unsigned char old_sha1[20];
|
||||
int have_old;
|
||||
|
||||
update = update_alloc();
|
||||
update->have_old = 1;
|
||||
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("create 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 = 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 (*next != line_termination)
|
||||
die("delete %s: extra input: %s", refname, next);
|
||||
|
||||
if (next && *next)
|
||||
die("create %s has extra input: %s", ref.buf, next);
|
||||
ref_transaction_delete(transaction, refname, old_sha1,
|
||||
update_flags, have_old);
|
||||
|
||||
update_flags = 0;
|
||||
free(refname);
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
static void parse_cmd_delete(const char *next)
|
||||
static const char *parse_cmd_verify(struct strbuf *input, const char *next)
|
||||
{
|
||||
struct strbuf ref = 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("verify: 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, "verify", refname,
|
||||
PARSE_SHA1_OLD)) {
|
||||
hashclr(new_sha1);
|
||||
have_old = 0;
|
||||
} else {
|
||||
hashcpy(new_sha1, old_sha1);
|
||||
have_old = 1;
|
||||
}
|
||||
|
||||
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);
|
||||
if (*next != line_termination)
|
||||
die("verify %s: extra input: %s", refname, next);
|
||||
|
||||
if (next && *next)
|
||||
die("delete %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_verify(const char *next)
|
||||
static const char *parse_cmd_option(struct strbuf *input, const char *next)
|
||||
{
|
||||
struct strbuf ref = STRBUF_INIT;
|
||||
struct strbuf value = STRBUF_INIT;
|
||||
struct ref_update *update;
|
||||
|
||||
update = update_alloc();
|
||||
|
||||
if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
|
||||
update_store_ref_name(update, ref.buf);
|
||||
else
|
||||
die("verify line missing <ref>");
|
||||
|
||||
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);
|
||||
|
||||
if (next && *next)
|
||||
die("verify %s has extra input: %s", ref.buf, next);
|
||||
}
|
||||
|
||||
static void parse_cmd_option(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);
|
||||
|
||||
strbuf_release(&cmd);
|
||||
next++;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
195
refs.c
195
refs.c
@ -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;
|
||||
}
|
||||
|
||||
|
94
refs.h
94
refs.h
@ -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 &&
|
||||
|
Loading…
Reference in New Issue
Block a user