Merge branch 'bk/refs-multi-update'
Give "update-refs" a "--stdin" option to read multiple update requests and perform them in an all-or-none fashion. * bk/refs-multi-update: update-ref: add test cases covering --stdin signature update-ref: support multiple simultaneous updates refs: add update_refs for multiple simultaneous updates refs: add function to repack without multiple refs refs: factor delete_ref loose ref step into a helper refs: factor update_ref steps into helpers refs: report ref type from lock_any_ref_for_update reset: rename update_refs to reset_refs
This commit is contained in:
commit
9a86b89941
@ -8,7 +8,7 @@ git-update-ref - Update the object name stored in a ref safely
|
||||
SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>])
|
||||
'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>] | --stdin [-z])
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
@ -58,6 +58,58 @@ archive by creating a symlink tree).
|
||||
With `-d` flag, it deletes the named <ref> after verifying it
|
||||
still contains <oldvalue>.
|
||||
|
||||
With `--stdin`, update-ref reads instructions from standard input and
|
||||
performs all modifications together. Specify commands of the form:
|
||||
|
||||
update SP <ref> SP <newvalue> [SP <oldvalue>] LF
|
||||
create SP <ref> SP <newvalue> LF
|
||||
delete SP <ref> [SP <oldvalue>] LF
|
||||
verify SP <ref> [SP <oldvalue>] LF
|
||||
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:
|
||||
|
||||
update SP <ref> NUL <newvalue> NUL [<oldvalue>] NUL
|
||||
create SP <ref> NUL <newvalue> NUL
|
||||
delete SP <ref> NUL [<oldvalue>] NUL
|
||||
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:
|
||||
|
||||
update::
|
||||
Set <ref> to <newvalue> after verifying <oldvalue>, if given.
|
||||
Specify a zero <newvalue> to ensure the ref does not exist
|
||||
after the update and/or a zero <oldvalue> to make sure the
|
||||
ref does not exist before the update.
|
||||
|
||||
create::
|
||||
Create <ref> with <newvalue> after verifying it does not
|
||||
exist. The given <newvalue> may not be zero.
|
||||
|
||||
delete::
|
||||
Delete <ref> after verifying it exists with <oldvalue>, if
|
||||
given. If given, <oldvalue> may not be zero.
|
||||
|
||||
verify::
|
||||
Verify <ref> against <oldvalue> but do not change it. If
|
||||
<oldvalue> zero or missing, the ref must not exist.
|
||||
|
||||
option::
|
||||
Modify behavior of the next command naming a <ref>.
|
||||
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
|
||||
<ref> is updated or deleted atomically, a concurrent reader may
|
||||
still see a subset of the modifications.
|
||||
|
||||
Logging Updates
|
||||
---------------
|
||||
|
2
branch.c
2
branch.c
@ -290,7 +290,7 @@ void create_branch(const char *head,
|
||||
hashcpy(sha1, commit->object.sha1);
|
||||
|
||||
if (!dont_change_ref) {
|
||||
lock = lock_any_ref_for_update(ref.buf, NULL, 0);
|
||||
lock = lock_any_ref_for_update(ref.buf, NULL, 0, NULL);
|
||||
if (!lock)
|
||||
die_errno(_("Failed to lock ref for update"));
|
||||
}
|
||||
|
@ -1631,7 +1631,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
|
||||
!current_head
|
||||
? NULL
|
||||
: current_head->object.sha1,
|
||||
0);
|
||||
0, NULL);
|
||||
|
||||
nl = strchr(sb.buf, '\n');
|
||||
if (nl)
|
||||
|
@ -262,7 +262,8 @@ static int s_update_ref(const char *action,
|
||||
rla = default_rla.buf;
|
||||
snprintf(msg, sizeof(msg), "%s: %s", rla, action);
|
||||
lock = lock_any_ref_for_update(ref->name,
|
||||
check_old ? ref->old_sha1 : NULL, 0);
|
||||
check_old ? ref->old_sha1 : NULL,
|
||||
0, NULL);
|
||||
if (!lock)
|
||||
return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
|
||||
STORE_REF_ERROR_OTHER;
|
||||
|
@ -526,7 +526,8 @@ static const char *update(struct command *cmd)
|
||||
return NULL; /* good */
|
||||
}
|
||||
else {
|
||||
lock = lock_any_ref_for_update(namespaced_name, old_sha1, 0);
|
||||
lock = lock_any_ref_for_update(namespaced_name, old_sha1,
|
||||
0, NULL);
|
||||
if (!lock) {
|
||||
rp_error("failed to lock %s", name);
|
||||
return "failed to lock";
|
||||
|
@ -365,7 +365,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
|
||||
* we take the lock for the ref itself to prevent it from
|
||||
* getting updated.
|
||||
*/
|
||||
lock = lock_any_ref_for_update(ref, sha1, 0);
|
||||
lock = lock_any_ref_for_update(ref, sha1, 0, NULL);
|
||||
if (!lock)
|
||||
return error("cannot lock ref '%s'", ref);
|
||||
log_file = git_pathdup("logs/%s", ref);
|
||||
|
@ -105,7 +105,7 @@ static int replace_object(const char *object_ref, const char *replace_ref,
|
||||
else if (!force)
|
||||
die("replace ref '%s' already exists", ref);
|
||||
|
||||
lock = lock_any_ref_for_update(ref, prev, 0);
|
||||
lock = lock_any_ref_for_update(ref, prev, 0, NULL);
|
||||
if (!lock)
|
||||
die("%s: cannot lock the ref", ref);
|
||||
if (write_ref_sha1(lock, repl, NULL) < 0)
|
||||
|
@ -226,7 +226,7 @@ static void parse_args(struct pathspec *pathspec,
|
||||
prefix, argv);
|
||||
}
|
||||
|
||||
static int update_refs(const char *rev, const unsigned char *sha1)
|
||||
static int reset_refs(const char *rev, const unsigned char *sha1)
|
||||
{
|
||||
int update_ref_status;
|
||||
struct strbuf msg = STRBUF_INIT;
|
||||
@ -354,7 +354,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
|
||||
if (!pathspec.nr && !unborn) {
|
||||
/* Any resets without paths update HEAD to the head being
|
||||
* switched to, saving the previous head in ORIG_HEAD before. */
|
||||
update_ref_status = update_refs(rev, sha1);
|
||||
update_ref_status = reset_refs(rev, sha1);
|
||||
|
||||
if (reset_type == HARD && !update_ref_status && !quiet)
|
||||
print_new_head_line(lookup_commit_reference(sha1));
|
||||
|
@ -574,7 +574,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
|
||||
if (annotate)
|
||||
create_tag(object, tag, &buf, &opt, prev, object);
|
||||
|
||||
lock = lock_any_ref_for_update(ref.buf, prev, 0);
|
||||
lock = lock_any_ref_for_update(ref.buf, prev, 0, NULL);
|
||||
if (!lock)
|
||||
die(_("%s: cannot lock the ref"), ref.buf);
|
||||
if (write_ref_sha1(lock, object, NULL) < 0)
|
||||
|
@ -2,23 +2,261 @@
|
||||
#include "refs.h"
|
||||
#include "builtin.h"
|
||||
#include "parse-options.h"
|
||||
#include "quote.h"
|
||||
#include "argv-array.h"
|
||||
|
||||
static const char * const git_update_ref_usage[] = {
|
||||
N_("git update-ref [options] -d <refname> [<oldval>]"),
|
||||
N_("git update-ref [options] <refname> <newval> [<oldval>]"),
|
||||
N_("git update-ref [options] --stdin [-z]"),
|
||||
NULL
|
||||
};
|
||||
|
||||
static int updates_alloc;
|
||||
static int updates_count;
|
||||
static const struct ref_update **updates;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static const char *parse_arg(const char *next, struct strbuf *arg)
|
||||
{
|
||||
/* Parse SP-terminated, possibly C-quoted argument */
|
||||
if (*next != '"')
|
||||
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 argument immediately after "command SP" */
|
||||
strbuf_reset(arg);
|
||||
if (line_termination) {
|
||||
/* Without -z, use the next argument */
|
||||
next = parse_arg(next, arg);
|
||||
} else {
|
||||
/* With -z, use rest of first NUL-terminated line */
|
||||
strbuf_addstr(arg, next);
|
||||
next = next + arg->len;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
static const char *parse_next_arg(const char *next, struct strbuf *arg)
|
||||
{
|
||||
/* Parse next SP-terminated or NUL-terminated argument, if any */
|
||||
strbuf_reset(arg);
|
||||
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);
|
||||
} 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;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
static void parse_cmd_update(const char *next)
|
||||
{
|
||||
struct strbuf ref = STRBUF_INIT;
|
||||
struct strbuf newvalue = STRBUF_INIT;
|
||||
struct strbuf oldvalue = 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("update line missing <ref>");
|
||||
|
||||
if ((next = parse_next_arg(next, &newvalue)) != NULL)
|
||||
update_store_new_sha1(update, newvalue.buf);
|
||||
else
|
||||
die("update %s missing <newvalue>", ref.buf);
|
||||
|
||||
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 && *next)
|
||||
die("update %s has extra input: %s", ref.buf, next);
|
||||
}
|
||||
|
||||
static void parse_cmd_create(const char *next)
|
||||
{
|
||||
struct strbuf ref = STRBUF_INIT;
|
||||
struct strbuf newvalue = 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("create line missing <ref>");
|
||||
|
||||
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 && *next)
|
||||
die("create %s has extra input: %s", ref.buf, next);
|
||||
}
|
||||
|
||||
static void parse_cmd_delete(const char *next)
|
||||
{
|
||||
struct strbuf ref = STRBUF_INIT;
|
||||
struct strbuf oldvalue = 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("delete line missing <ref>");
|
||||
|
||||
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 && *next)
|
||||
die("delete %s has extra input: %s", ref.buf, next);
|
||||
}
|
||||
|
||||
static void parse_cmd_verify(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"))
|
||||
update_flags |= REF_NODEREF;
|
||||
else
|
||||
die("option unknown: %s", next);
|
||||
}
|
||||
|
||||
static void update_refs_stdin(void)
|
||||
{
|
||||
struct strbuf cmd = STRBUF_INIT;
|
||||
|
||||
/* Read each line dispatch its command */
|
||||
while (strbuf_getline(&cmd, stdin, line_termination) != EOF)
|
||||
if (!cmd.buf[0])
|
||||
die("empty command in input");
|
||||
else if (isspace(*cmd.buf))
|
||||
die("whitespace before command: %s", cmd.buf);
|
||||
else if (!prefixcmp(cmd.buf, "update "))
|
||||
parse_cmd_update(cmd.buf + 7);
|
||||
else if (!prefixcmp(cmd.buf, "create "))
|
||||
parse_cmd_create(cmd.buf + 7);
|
||||
else if (!prefixcmp(cmd.buf, "delete "))
|
||||
parse_cmd_delete(cmd.buf + 7);
|
||||
else if (!prefixcmp(cmd.buf, "verify "))
|
||||
parse_cmd_verify(cmd.buf + 7);
|
||||
else if (!prefixcmp(cmd.buf, "option "))
|
||||
parse_cmd_option(cmd.buf + 7);
|
||||
else
|
||||
die("unknown command: %s", cmd.buf);
|
||||
|
||||
strbuf_release(&cmd);
|
||||
}
|
||||
|
||||
int cmd_update_ref(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
const char *refname, *oldval, *msg = NULL;
|
||||
unsigned char sha1[20], oldsha1[20];
|
||||
int delete = 0, no_deref = 0, flags = 0;
|
||||
int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0;
|
||||
struct option options[] = {
|
||||
OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")),
|
||||
OPT_BOOL('d', NULL, &delete, N_("delete the reference")),
|
||||
OPT_BOOL( 0 , "no-deref", &no_deref,
|
||||
N_("update <refname> not the one it points to")),
|
||||
OPT_BOOL('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")),
|
||||
OPT_BOOL( 0 , "stdin", &read_stdin, N_("read updates from stdin")),
|
||||
OPT_END(),
|
||||
};
|
||||
|
||||
@ -28,6 +266,18 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
|
||||
if (msg && !*msg)
|
||||
die("Refusing to perform update with empty message.");
|
||||
|
||||
if (read_stdin) {
|
||||
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);
|
||||
}
|
||||
|
||||
if (end_null)
|
||||
usage_with_options(git_update_ref_usage, options);
|
||||
|
||||
if (delete) {
|
||||
if (argc < 1 || argc > 2)
|
||||
usage_with_options(git_update_ref_usage, options);
|
||||
|
@ -1694,7 +1694,7 @@ static int update_branch(struct branch *b)
|
||||
return 0;
|
||||
if (read_ref(b->name, old_sha1))
|
||||
hashclr(old_sha1);
|
||||
lock = lock_any_ref_for_update(b->name, old_sha1, 0);
|
||||
lock = lock_any_ref_for_update(b->name, old_sha1, 0, NULL);
|
||||
if (!lock)
|
||||
return error("Unable to lock %s", b->name);
|
||||
if (!force_update && !is_null_sha1(old_sha1)) {
|
||||
|
195
refs.c
195
refs.c
@ -2121,11 +2121,12 @@ struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha
|
||||
}
|
||||
|
||||
struct ref_lock *lock_any_ref_for_update(const char *refname,
|
||||
const unsigned char *old_sha1, int flags)
|
||||
const unsigned char *old_sha1,
|
||||
int flags, int *type_p)
|
||||
{
|
||||
if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
|
||||
return NULL;
|
||||
return lock_ref_sha1_basic(refname, old_sha1, flags, NULL);
|
||||
return lock_ref_sha1_basic(refname, old_sha1, flags, type_p);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -2413,60 +2414,82 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int repack_without_ref(const char *refname)
|
||||
static int repack_without_refs(const char **refnames, int n)
|
||||
{
|
||||
struct ref_dir *packed;
|
||||
struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
|
||||
struct string_list_item *ref_to_delete;
|
||||
int i, removed = 0;
|
||||
|
||||
if (!get_packed_ref(refname))
|
||||
return 0; /* refname does not exist in packed refs */
|
||||
/* Look for a packed ref */
|
||||
for (i = 0; i < n; i++)
|
||||
if (get_packed_ref(refnames[i]))
|
||||
break;
|
||||
|
||||
/* Avoid locking if we have nothing to do */
|
||||
if (i == n)
|
||||
return 0; /* no refname exists in packed refs */
|
||||
|
||||
if (lock_packed_refs(0)) {
|
||||
unable_to_lock_error(git_path("packed-refs"), errno);
|
||||
return error("cannot delete '%s' from packed refs", refname);
|
||||
return error("cannot delete '%s' from packed refs", refnames[i]);
|
||||
}
|
||||
packed = get_packed_refs(&ref_cache);
|
||||
|
||||
/* Remove refname from the cache: */
|
||||
if (remove_entry(packed, refname) == -1) {
|
||||
/* Remove refnames from the cache */
|
||||
for (i = 0; i < n; i++)
|
||||
if (remove_entry(packed, refnames[i]) != -1)
|
||||
removed = 1;
|
||||
if (!removed) {
|
||||
/*
|
||||
* The packed entry disappeared while we were
|
||||
* All packed entries disappeared while we were
|
||||
* acquiring the lock.
|
||||
*/
|
||||
rollback_packed_refs();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Remove any other accumulated cruft: */
|
||||
/* Remove any other accumulated cruft */
|
||||
do_for_each_entry_in_dir(packed, 0, curate_packed_ref_fn, &refs_to_delete);
|
||||
for_each_string_list_item(ref_to_delete, &refs_to_delete) {
|
||||
if (remove_entry(packed, ref_to_delete->string) == -1)
|
||||
die("internal error");
|
||||
}
|
||||
|
||||
/* Write what remains: */
|
||||
/* Write what remains */
|
||||
return commit_packed_refs();
|
||||
}
|
||||
|
||||
static int repack_without_ref(const char *refname)
|
||||
{
|
||||
return repack_without_refs(&refname, 1);
|
||||
}
|
||||
|
||||
static int delete_ref_loose(struct ref_lock *lock, int flag)
|
||||
{
|
||||
if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
|
||||
/* loose */
|
||||
int err, i = strlen(lock->lk->filename) - 5; /* .lock */
|
||||
|
||||
lock->lk->filename[i] = 0;
|
||||
err = unlink_or_warn(lock->lk->filename);
|
||||
lock->lk->filename[i] = '.';
|
||||
if (err && errno != ENOENT)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
|
||||
{
|
||||
struct ref_lock *lock;
|
||||
int err, i = 0, ret = 0, flag = 0;
|
||||
int ret = 0, flag = 0;
|
||||
|
||||
lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);
|
||||
if (!lock)
|
||||
return 1;
|
||||
if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
|
||||
/* loose */
|
||||
i = strlen(lock->lk->filename) - 5; /* .lock */
|
||||
lock->lk->filename[i] = 0;
|
||||
err = unlink_or_warn(lock->lk->filename);
|
||||
if (err && errno != ENOENT)
|
||||
ret = 1;
|
||||
ret |= delete_ref_loose(lock, flag);
|
||||
|
||||
lock->lk->filename[i] = '.';
|
||||
}
|
||||
/* removing the loose one could have resurrected an earlier
|
||||
* packed one. Also, if it was not loose we need to repack
|
||||
* without it.
|
||||
@ -3169,12 +3192,13 @@ int for_each_reflog(each_ref_fn fn, void *cb_data)
|
||||
return retval;
|
||||
}
|
||||
|
||||
int update_ref(const char *action, const char *refname,
|
||||
const unsigned char *sha1, const unsigned char *oldval,
|
||||
int flags, enum action_on_err onerr)
|
||||
static struct ref_lock *update_ref_lock(const char *refname,
|
||||
const unsigned char *oldval,
|
||||
int flags, int *type_p,
|
||||
enum action_on_err onerr)
|
||||
{
|
||||
static struct ref_lock *lock;
|
||||
lock = lock_any_ref_for_update(refname, oldval, flags);
|
||||
struct ref_lock *lock;
|
||||
lock = lock_any_ref_for_update(refname, oldval, flags, type_p);
|
||||
if (!lock) {
|
||||
const char *str = "Cannot lock the ref '%s'.";
|
||||
switch (onerr) {
|
||||
@ -3182,8 +3206,14 @@ int update_ref(const char *action, const char *refname,
|
||||
case DIE_ON_ERR: die(str, refname); break;
|
||||
case QUIET_ON_ERR: break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return lock;
|
||||
}
|
||||
|
||||
static int update_ref_write(const char *action, const char *refname,
|
||||
const unsigned char *sha1, struct ref_lock *lock,
|
||||
enum action_on_err onerr)
|
||||
{
|
||||
if (write_ref_sha1(lock, sha1, action) < 0) {
|
||||
const char *str = "Cannot update the ref '%s'.";
|
||||
switch (onerr) {
|
||||
@ -3196,6 +3226,117 @@ int update_ref(const char *action, const char *refname,
|
||||
return 0;
|
||||
}
|
||||
|
||||
int update_ref(const char *action, const char *refname,
|
||||
const unsigned char *sha1, const unsigned char *oldval,
|
||||
int flags, enum action_on_err onerr)
|
||||
{
|
||||
struct ref_lock *lock;
|
||||
lock = update_ref_lock(refname, oldval, flags, 0, onerr);
|
||||
if (!lock)
|
||||
return 1;
|
||||
return update_ref_write(action, refname, sha1, lock, onerr);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static int ref_update_reject_duplicates(struct ref_update **updates, int n,
|
||||
enum action_on_err onerr)
|
||||
{
|
||||
int i;
|
||||
for (i = 1; i < n; i++)
|
||||
if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) {
|
||||
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:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int update_refs(const char *action, const struct ref_update **updates_orig,
|
||||
int n, enum action_on_err onerr)
|
||||
{
|
||||
int ret = 0, delnum = 0, i;
|
||||
struct ref_update **updates;
|
||||
int *types;
|
||||
struct ref_lock **locks;
|
||||
const char **delnames;
|
||||
|
||||
if (!updates_orig || !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)
|
||||
goto cleanup;
|
||||
|
||||
/* 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]) {
|
||||
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 */
|
||||
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]);
|
||||
}
|
||||
ret |= repack_without_refs(delnames, delnum);
|
||||
for (i = 0; i < delnum; i++)
|
||||
unlink_or_warn(git_path("logs/%s", delnames[i]));
|
||||
clear_loose_ref_cache(&ref_cache);
|
||||
|
||||
cleanup:
|
||||
for (i = 0; i < n; i++)
|
||||
if (locks[i])
|
||||
unlock_ref(locks[i]);
|
||||
free(updates);
|
||||
free(types);
|
||||
free(locks);
|
||||
free(delnames);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* generate a format suitable for scanf from a ref_rev_parse_rules
|
||||
* rule, that is replace the "%.*s" spec with a "%s" spec
|
||||
|
22
refs.h
22
refs.h
@ -10,6 +10,20 @@ 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 */
|
||||
};
|
||||
|
||||
/*
|
||||
* Bit values set in the flags argument passed to each_ref_fn():
|
||||
*/
|
||||
@ -137,7 +151,7 @@ extern struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *
|
||||
#define REF_NODEREF 0x01
|
||||
extern struct ref_lock *lock_any_ref_for_update(const char *refname,
|
||||
const unsigned char *old_sha1,
|
||||
int flags);
|
||||
int flags, int *type_p);
|
||||
|
||||
/** Close the file descriptor owned by a lock and return the status */
|
||||
extern int close_ref(struct ref_lock *lock);
|
||||
@ -214,6 +228,12 @@ 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 *);
|
||||
|
||||
|
@ -279,7 +279,8 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from,
|
||||
read_cache();
|
||||
if (checkout_fast_forward(from, to, 1))
|
||||
exit(1); /* the callee should have complained already */
|
||||
ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from, 0);
|
||||
ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from,
|
||||
0, NULL);
|
||||
strbuf_addf(&sb, "%s: fast-forward", action_name(opts));
|
||||
ret = write_ref_sha1(ref_lock, to, sb.buf);
|
||||
strbuf_release(&sb);
|
||||
|
@ -302,4 +302,636 @@ test_expect_success \
|
||||
'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \
|
||||
'test OTHER = $(git cat-file blob "master@{2005-05-26 23:42}:F")'
|
||||
|
||||
a=refs/heads/a
|
||||
b=refs/heads/b
|
||||
c=refs/heads/c
|
||||
E='""'
|
||||
F='%s\0'
|
||||
pws='path with space'
|
||||
|
||||
test_expect_success 'stdin test setup' '
|
||||
echo "$pws" >"$pws" &&
|
||||
git add -- "$pws" &&
|
||||
git commit -m "$pws"
|
||||
'
|
||||
|
||||
test_expect_success '-z fails without --stdin' '
|
||||
test_must_fail git update-ref -z $m $m $m 2>err &&
|
||||
grep "usage: git update-ref" err
|
||||
'
|
||||
|
||||
test_expect_success 'stdin works with no input' '
|
||||
>stdin &&
|
||||
git update-ref --stdin <stdin &&
|
||||
git rev-parse --verify -q $m
|
||||
'
|
||||
|
||||
test_expect_success 'stdin fails on empty line' '
|
||||
echo "" >stdin &&
|
||||
test_must_fail git update-ref --stdin <stdin 2>err &&
|
||||
grep "fatal: empty command in input" err
|
||||
'
|
||||
|
||||
test_expect_success 'stdin fails on only whitespace' '
|
||||
echo " " >stdin &&
|
||||
test_must_fail git update-ref --stdin <stdin 2>err &&
|
||||
grep "fatal: whitespace before command: " err
|
||||
'
|
||||
|
||||
test_expect_success 'stdin fails on leading whitespace' '
|
||||
echo " create $a $m" >stdin &&
|
||||
test_must_fail git update-ref --stdin <stdin 2>err &&
|
||||
grep "fatal: whitespace before command: create $a $m" err
|
||||
'
|
||||
|
||||
test_expect_success 'stdin fails on unknown command' '
|
||||
echo "unknown $a" >stdin &&
|
||||
test_must_fail git update-ref --stdin <stdin 2>err &&
|
||||
grep "fatal: unknown command: unknown $a" err
|
||||
'
|
||||
|
||||
test_expect_success 'stdin fails on badly quoted input' '
|
||||
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' '
|
||||
echo "create \"$a\"master" >stdin &&
|
||||
test_must_fail git update-ref --stdin <stdin 2>err &&
|
||||
grep "fatal: expected SP but got: 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
|
||||
'
|
||||
|
||||
test_expect_success 'stdin fails create with bad ref name' '
|
||||
echo "create ~a $m" >stdin &&
|
||||
test_must_fail git update-ref --stdin <stdin 2>err &&
|
||||
grep "fatal: invalid ref format: ~a" err
|
||||
'
|
||||
|
||||
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
|
||||
'
|
||||
|
||||
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
|
||||
'
|
||||
|
||||
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
|
||||
'
|
||||
|
||||
test_expect_success 'stdin fails update with bad ref name' '
|
||||
echo "update ~a $m" >stdin &&
|
||||
test_must_fail git update-ref --stdin <stdin 2>err &&
|
||||
grep "fatal: invalid ref format: ~a" err
|
||||
'
|
||||
|
||||
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
|
||||
'
|
||||
|
||||
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
|
||||
'
|
||||
|
||||
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
|
||||
'
|
||||
|
||||
test_expect_success 'stdin fails delete with bad ref name' '
|
||||
echo "delete ~a $m" >stdin &&
|
||||
test_must_fail git update-ref --stdin <stdin 2>err &&
|
||||
grep "fatal: invalid ref format: ~a" err
|
||||
'
|
||||
|
||||
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
|
||||
'
|
||||
|
||||
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
|
||||
'
|
||||
|
||||
test_expect_success 'stdin fails option with unknown name' '
|
||||
echo "option unknown" >stdin &&
|
||||
test_must_fail git update-ref --stdin <stdin 2>err &&
|
||||
grep "fatal: option unknown: unknown" err
|
||||
'
|
||||
|
||||
test_expect_success 'stdin fails with duplicate refs' '
|
||||
cat >stdin <<-EOF &&
|
||||
create $a $m
|
||||
create $b $m
|
||||
create $a $m
|
||||
EOF
|
||||
test_must_fail git update-ref --stdin <stdin 2>err &&
|
||||
grep "fatal: Multiple updates for ref '"'"'$a'"'"' not allowed." err
|
||||
'
|
||||
|
||||
test_expect_success 'stdin create ref works' '
|
||||
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 update ref creates with zero old value' '
|
||||
echo "update $b $m $Z" >stdin &&
|
||||
git update-ref --stdin <stdin &&
|
||||
git rev-parse $m >expect &&
|
||||
git rev-parse $b >actual &&
|
||||
test_cmp expect actual &&
|
||||
git update-ref -d $b
|
||||
'
|
||||
|
||||
test_expect_success 'stdin update ref creates with empty old value' '
|
||||
echo "update $b $m $E" >stdin &&
|
||||
git update-ref --stdin <stdin &&
|
||||
git rev-parse $m >expect &&
|
||||
git rev-parse $b >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'stdin create ref works with path with space to blob' '
|
||||
echo "create refs/blobs/pws \"$m:$pws\"" >stdin &&
|
||||
git update-ref --stdin <stdin &&
|
||||
git rev-parse "$m:$pws" >expect &&
|
||||
git rev-parse refs/blobs/pws >actual &&
|
||||
test_cmp expect actual &&
|
||||
git update-ref -d refs/blobs/pws
|
||||
'
|
||||
|
||||
test_expect_success 'stdin update ref fails with wrong old value' '
|
||||
echo "update $c $m $m~1" >stdin &&
|
||||
test_must_fail git update-ref --stdin <stdin 2>err &&
|
||||
grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
|
||||
test_must_fail git rev-parse --verify -q $c
|
||||
'
|
||||
|
||||
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 &&
|
||||
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 &&
|
||||
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 &&
|
||||
test_must_fail git rev-parse --verify -q $c
|
||||
'
|
||||
|
||||
test_expect_success 'stdin update ref works with right old value' '
|
||||
echo "update $b $m~1 $m" >stdin &&
|
||||
git update-ref --stdin <stdin &&
|
||||
git rev-parse $m~1 >expect &&
|
||||
git rev-parse $b >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'stdin delete ref fails with wrong old value' '
|
||||
echo "delete $a $m~1" >stdin &&
|
||||
test_must_fail git update-ref --stdin <stdin 2>err &&
|
||||
grep "fatal: Cannot lock the ref '"'"'$a'"'"'" err &&
|
||||
git rev-parse $m >expect &&
|
||||
git rev-parse $a >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
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 &&
|
||||
git rev-parse $m >expect &&
|
||||
git rev-parse $a >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'stdin update symref works option no-deref' '
|
||||
git symbolic-ref TESTSYMREF $b &&
|
||||
cat >stdin <<-EOF &&
|
||||
option no-deref
|
||||
update TESTSYMREF $a $b
|
||||
EOF
|
||||
git update-ref --stdin <stdin &&
|
||||
git rev-parse TESTSYMREF >expect &&
|
||||
git rev-parse $a >actual &&
|
||||
test_cmp expect actual &&
|
||||
git rev-parse $m~1 >expect &&
|
||||
git rev-parse $b >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'stdin delete symref works option no-deref' '
|
||||
git symbolic-ref TESTSYMREF $b &&
|
||||
cat >stdin <<-EOF &&
|
||||
option no-deref
|
||||
delete TESTSYMREF $b
|
||||
EOF
|
||||
git update-ref --stdin <stdin &&
|
||||
test_must_fail git rev-parse --verify -q TESTSYMREF &&
|
||||
git rev-parse $m~1 >expect &&
|
||||
git rev-parse $b >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'stdin delete ref works with right old value' '
|
||||
echo "delete $b $m~1" >stdin &&
|
||||
git update-ref --stdin <stdin &&
|
||||
test_must_fail git rev-parse --verify -q $b
|
||||
'
|
||||
|
||||
test_expect_success 'stdin update/create/verify combination works' '
|
||||
cat >stdin <<-EOF &&
|
||||
update $a $m
|
||||
create $b $m
|
||||
verify $c
|
||||
EOF
|
||||
git update-ref --stdin <stdin &&
|
||||
git rev-parse $m >expect &&
|
||||
git rev-parse $a >actual &&
|
||||
test_cmp expect actual &&
|
||||
git rev-parse $b >actual &&
|
||||
test_cmp expect actual &&
|
||||
test_must_fail git rev-parse --verify -q $c
|
||||
'
|
||||
|
||||
test_expect_success 'stdin update refs works with identity updates' '
|
||||
cat >stdin <<-EOF &&
|
||||
update $a $m $m
|
||||
update $b $m $m
|
||||
update $c $Z $E
|
||||
EOF
|
||||
git update-ref --stdin <stdin &&
|
||||
git rev-parse $m >expect &&
|
||||
git rev-parse $a >actual &&
|
||||
test_cmp expect actual &&
|
||||
git rev-parse $b >actual &&
|
||||
test_cmp expect actual &&
|
||||
test_must_fail git rev-parse --verify -q $c
|
||||
'
|
||||
|
||||
test_expect_success 'stdin update refs fails with wrong old value' '
|
||||
git update-ref $c $m &&
|
||||
cat >stdin <<-EOF &&
|
||||
update $a $m $m
|
||||
update $b $m $m
|
||||
update $c ''
|
||||
EOF
|
||||
test_must_fail git update-ref --stdin <stdin 2>err &&
|
||||
grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
|
||||
git rev-parse $m >expect &&
|
||||
git rev-parse $a >actual &&
|
||||
test_cmp expect actual &&
|
||||
git rev-parse $b >actual &&
|
||||
test_cmp expect actual &&
|
||||
git rev-parse $c >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'stdin delete refs works with packed and loose refs' '
|
||||
git pack-refs --all &&
|
||||
git update-ref $c $m~1 &&
|
||||
cat >stdin <<-EOF &&
|
||||
delete $a $m
|
||||
update $b $Z $m
|
||||
update $c $E $m~1
|
||||
EOF
|
||||
git update-ref --stdin <stdin &&
|
||||
test_must_fail git rev-parse --verify -q $a &&
|
||||
test_must_fail git rev-parse --verify -q $b &&
|
||||
test_must_fail git rev-parse --verify -q $c
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z works on empty input' '
|
||||
>stdin &&
|
||||
git update-ref -z --stdin <stdin &&
|
||||
git rev-parse --verify -q $m
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z fails on empty line' '
|
||||
echo "" >stdin &&
|
||||
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
||||
grep "fatal: whitespace before command: " err
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z fails on empty command' '
|
||||
printf $F "" >stdin &&
|
||||
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
||||
grep "fatal: empty command in input" err
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z fails on only whitespace' '
|
||||
printf $F " " >stdin &&
|
||||
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
||||
grep "fatal: whitespace before command: " err
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z fails on leading whitespace' '
|
||||
printf $F " create $a" "$m" >stdin &&
|
||||
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
||||
grep "fatal: whitespace before command: create $a" err
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z fails on unknown command' '
|
||||
printf $F "unknown $a" >stdin &&
|
||||
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
||||
grep "fatal: unknown command: unknown $a" err
|
||||
'
|
||||
|
||||
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
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z fails create with bad ref name' '
|
||||
printf $F "create ~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 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
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z fails create with too many arguments' '
|
||||
printf $F "create $a" "$m" "$m" >stdin &&
|
||||
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
||||
grep "fatal: unknown command: $m" err
|
||||
'
|
||||
|
||||
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
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z fails update with bad ref name' '
|
||||
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 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
|
||||
'
|
||||
|
||||
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
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z fails update with too many arguments' '
|
||||
printf $F "update $a" "$m" "$m" "$m" >stdin &&
|
||||
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
||||
grep "fatal: unknown command: $m" err
|
||||
'
|
||||
|
||||
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
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z fails delete with bad ref name' '
|
||||
printf $F "delete ~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 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
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z fails delete with too many arguments' '
|
||||
printf $F "delete $a" "$m" "$m" >stdin &&
|
||||
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
||||
grep "fatal: unknown command: $m" err
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z fails verify with too many arguments' '
|
||||
printf $F "verify $a" "$m" "$m" >stdin &&
|
||||
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
||||
grep "fatal: unknown command: $m" err
|
||||
'
|
||||
|
||||
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
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z fails option with unknown name' '
|
||||
printf $F "option unknown" >stdin &&
|
||||
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
||||
grep "fatal: option unknown: unknown" err
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z fails with duplicate refs' '
|
||||
printf $F "create $a" "$m" "create $b" "$m" "create $a" "$m" >stdin &&
|
||||
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
||||
grep "fatal: Multiple updates for ref '"'"'$a'"'"' not allowed." err
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z create ref works' '
|
||||
printf $F "create $a" "$m" >stdin &&
|
||||
git update-ref -z --stdin <stdin &&
|
||||
git rev-parse $m >expect &&
|
||||
git rev-parse $a >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z update ref creates with zero old value' '
|
||||
printf $F "update $b" "$m" "$Z" >stdin &&
|
||||
git update-ref -z --stdin <stdin &&
|
||||
git rev-parse $m >expect &&
|
||||
git rev-parse $b >actual &&
|
||||
test_cmp expect actual &&
|
||||
git update-ref -d $b
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z update ref creates with empty old value' '
|
||||
printf $F "update $b" "$m" "" >stdin &&
|
||||
git update-ref -z --stdin <stdin &&
|
||||
git rev-parse $m >expect &&
|
||||
git rev-parse $b >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z create ref works with path with space to blob' '
|
||||
printf $F "create refs/blobs/pws" "$m:$pws" >stdin &&
|
||||
git update-ref -z --stdin <stdin &&
|
||||
git rev-parse "$m:$pws" >expect &&
|
||||
git rev-parse refs/blobs/pws >actual &&
|
||||
test_cmp expect actual &&
|
||||
git update-ref -d refs/blobs/pws
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z update ref fails with wrong old value' '
|
||||
printf $F "update $c" "$m" "$m~1" >stdin &&
|
||||
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
||||
grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
|
||||
test_must_fail git rev-parse --verify -q $c
|
||||
'
|
||||
|
||||
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 &&
|
||||
test_must_fail git rev-parse --verify -q $c
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z create ref fails with bad new value' '
|
||||
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 &&
|
||||
test_must_fail git rev-parse --verify -q $c
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z create ref fails with zero 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 &&
|
||||
test_must_fail git rev-parse --verify -q $c
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z update ref works with right old value' '
|
||||
printf $F "update $b" "$m~1" "$m" >stdin &&
|
||||
git update-ref -z --stdin <stdin &&
|
||||
git rev-parse $m~1 >expect &&
|
||||
git rev-parse $b >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z delete ref fails with wrong old value' '
|
||||
printf $F "delete $a" "$m~1" >stdin &&
|
||||
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
||||
grep "fatal: Cannot lock the ref '"'"'$a'"'"'" err &&
|
||||
git rev-parse $m >expect &&
|
||||
git rev-parse $a >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
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 &&
|
||||
git rev-parse $m >expect &&
|
||||
git rev-parse $a >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z update symref works option no-deref' '
|
||||
git symbolic-ref TESTSYMREF $b &&
|
||||
printf $F "option no-deref" "update TESTSYMREF" "$a" "$b" >stdin &&
|
||||
git update-ref -z --stdin <stdin &&
|
||||
git rev-parse TESTSYMREF >expect &&
|
||||
git rev-parse $a >actual &&
|
||||
test_cmp expect actual &&
|
||||
git rev-parse $m~1 >expect &&
|
||||
git rev-parse $b >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z delete symref works option no-deref' '
|
||||
git symbolic-ref TESTSYMREF $b &&
|
||||
printf $F "option no-deref" "delete TESTSYMREF" "$b" >stdin &&
|
||||
git update-ref -z --stdin <stdin &&
|
||||
test_must_fail git rev-parse --verify -q TESTSYMREF &&
|
||||
git rev-parse $m~1 >expect &&
|
||||
git rev-parse $b >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z delete ref works with right old value' '
|
||||
printf $F "delete $b" "$m~1" >stdin &&
|
||||
git update-ref -z --stdin <stdin &&
|
||||
test_must_fail git rev-parse --verify -q $b
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z update/create/verify combination works' '
|
||||
printf $F "update $a" "$m" "" "create $b" "$m" "verify $c" "" >stdin &&
|
||||
git update-ref -z --stdin <stdin &&
|
||||
git rev-parse $m >expect &&
|
||||
git rev-parse $a >actual &&
|
||||
test_cmp expect actual &&
|
||||
git rev-parse $b >actual &&
|
||||
test_cmp expect actual &&
|
||||
test_must_fail git rev-parse --verify -q $c
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z update refs works with identity updates' '
|
||||
printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "$Z" "" >stdin &&
|
||||
git update-ref -z --stdin <stdin &&
|
||||
git rev-parse $m >expect &&
|
||||
git rev-parse $a >actual &&
|
||||
test_cmp expect actual &&
|
||||
git rev-parse $b >actual &&
|
||||
test_cmp expect actual &&
|
||||
test_must_fail git rev-parse --verify -q $c
|
||||
'
|
||||
|
||||
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 &&
|
||||
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
||||
grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
|
||||
git rev-parse $m >expect &&
|
||||
git rev-parse $a >actual &&
|
||||
test_cmp expect actual &&
|
||||
git rev-parse $b >actual &&
|
||||
test_cmp expect actual &&
|
||||
git rev-parse $c >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'stdin -z delete refs works with packed and loose refs' '
|
||||
git pack-refs --all &&
|
||||
git update-ref $c $m~1 &&
|
||||
printf $F "delete $a" "$m" "update $b" "$Z" "$m" "update $c" "" "$m~1" >stdin &&
|
||||
git update-ref -z --stdin <stdin &&
|
||||
test_must_fail git rev-parse --verify -q $a &&
|
||||
test_must_fail git rev-parse --verify -q $b &&
|
||||
test_must_fail git rev-parse --verify -q $c
|
||||
'
|
||||
|
||||
test_done
|
||||
|
Loading…
Reference in New Issue
Block a user