Browse Source

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
maint
Junio C Hamano 11 years ago
parent
commit
9a86b89941
  1. 54
      Documentation/git-update-ref.txt
  2. 2
      branch.c
  3. 2
      builtin/commit.c
  4. 3
      builtin/fetch.c
  5. 3
      builtin/receive-pack.c
  6. 2
      builtin/reflog.c
  7. 2
      builtin/replace.c
  8. 4
      builtin/reset.c
  9. 2
      builtin/tag.c
  10. 252
      builtin/update-ref.c
  11. 2
      fast-import.c
  12. 195
      refs.c
  13. 22
      refs.h
  14. 3
      sequencer.c
  15. 632
      t/t1400-update-ref.sh

54
Documentation/git-update-ref.txt

@ -8,7 +8,7 @@ git-update-ref - Update the object name stored in a ref safely @@ -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). @@ -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

@ -290,7 +290,7 @@ void create_branch(const char *head, @@ -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"));
}

2
builtin/commit.c

@ -1631,7 +1631,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) @@ -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)

3
builtin/fetch.c

@ -262,7 +262,8 @@ static int s_update_ref(const char *action, @@ -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;

3
builtin/receive-pack.c

@ -526,7 +526,8 @@ static const char *update(struct command *cmd) @@ -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";

2
builtin/reflog.c

@ -365,7 +365,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, @@ -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);

2
builtin/replace.c

@ -105,7 +105,7 @@ static int replace_object(const char *object_ref, const char *replace_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)

4
builtin/reset.c

@ -226,7 +226,7 @@ static void parse_args(struct pathspec *pathspec, @@ -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) @@ -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));

2
builtin/tag.c

@ -574,7 +574,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) @@ -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)

252
builtin/update-ref.c

@ -2,23 +2,261 @@ @@ -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) @@ -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);

2
fast-import.c

@ -1694,7 +1694,7 @@ static int update_branch(struct branch *b) @@ -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

@ -2121,11 +2121,12 @@ struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha @@ -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) @@ -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;

/* Look for a packed ref */
for (i = 0; i < n; i++)
if (get_packed_ref(refnames[i]))
break;

if (!get_packed_ref(refname))
return 0; /* refname does not exist in packed refs */
/* 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();
}

int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
static int repack_without_ref(const char *refname)
{
struct ref_lock *lock;
int err, i = 0, ret = 0, flag = 0;
return repack_without_refs(&refname, 1);
}

lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);
if (!lock)
return 1;
static int delete_ref_loose(struct ref_lock *lock, int flag)
{
if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
/* loose */
i = strlen(lock->lk->filename) - 5; /* .lock */
int err, 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;

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 ret = 0, flag = 0;

lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);
if (!lock)
return 1;
ret |= delete_ref_loose(lock, flag);

/* 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) @@ -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, @@ -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, @@ -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

@ -10,6 +10,20 @@ struct ref_lock { @@ -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 * @@ -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, @@ -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 *);


3
sequencer.c

@ -279,7 +279,8 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from, @@ -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);

632
t/t1400-update-ref.sh

@ -302,4 +302,636 @@ test_expect_success \ @@ -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…
Cancel
Save