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:
Junio C Hamano
2013-09-20 12:36:12 -07:00
15 changed files with 1139 additions and 41 deletions

195
refs.c
View File

@ -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