/*
* How to handle various characters in refnames:
- * This table is used by both the SIMD and non-SIMD code. It has
- * some cases that are only useful for the SIMD; these are handled
- * equivalently to the listed disposition in the non-SIMD code.
* 0: An acceptable character for refs
- * 1: @, look for a following { to reject @{ in refs (SIMD or = 0)
- * 2: \0: End-of-component and string
- * 3: /: End-of-component (SIMD or = 2)
- * 4: ., look for a preceding . to reject .. in refs
- * 5: {, look for a preceding @ to reject @{ in refs
- * 6: *, usually a bad character except, once as a wildcard (SIMD or = 7)
- * 7: A bad character except * (see check_refname_component below)
+ * 1: End-of-component
+ * 2: ., look for a preceding . to reject .. in refs
+ * 3: {, look for a preceding @ to reject @{ in refs
+ * 4: A bad character: ASCII control characters, "~", "^", ":" or SP
*/
static unsigned char refname_disposition[256] = {
- 2, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
- 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
- 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 4, 3,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 7,
- 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 7, 0,
+ 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 7, 7
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 4, 4
};
+/*
+ * Used as a flag to ref_transaction_delete when a loose ref is being
+ * pruned.
+ */
+#define REF_ISPRUNING 0x0100
/*
* Try to read one refname component from the front of refname.
* Return the length of the component found, or -1 if the component is
* - any path component of it begins with ".", or
* - it has double dots "..", or
* - it has ASCII control character, "~", "^", ":" or SP, anywhere, or
- * - it has pattern-matching notation "*", "?", "[", anywhere, or
- * - it ends with a "/", or
- * - it ends with ".lock", or
+ * - it ends with a "/".
+ * - it ends with ".lock"
* - it contains a "\" (backslash)
*/
static int check_refname_component(const char *refname, int flags)
int ch = *cp & 255;
unsigned char disp = refname_disposition[ch];
switch (disp) {
- case 2: /* fall-through */
- case 3:
+ case 1:
goto out;
- case 4:
+ case 2:
if (last == '.')
return -1; /* Refname contains "..". */
break;
- case 5:
+ case 3:
if (last == '@')
return -1; /* Refname contains "@{". */
break;
- case 6: /* fall-through */
- case 7:
+ case 4:
return -1;
}
last = ch;
return cp - refname;
}
-static int check_refname_format_bytewise(const char *refname, int flags)
+int check_refname_format(const char *refname, int flags)
{
int component_len, component_count = 0;
return 0;
}
-#if defined(__GNUC__) && defined(__x86_64__)
-#define SSE_VECTOR_BYTES 16
-
-/* Vectorized version of check_refname_format. */
-int check_refname_format(const char *refname, int flags)
-{
- const char *cp = refname;
-
- const __m128i dot = _mm_set1_epi8('.');
- const __m128i at = _mm_set1_epi8('@');
- const __m128i curly = _mm_set1_epi8('{');
- const __m128i slash = _mm_set1_epi8('/');
- const __m128i zero = _mm_set1_epi8('\000');
- const __m128i el = _mm_set1_epi8('l');
-
- /* below '*', all characters are forbidden or rare */
- const __m128i star_ub = _mm_set1_epi8('*' + 1);
-
- const __m128i colon = _mm_set1_epi8(':');
- const __m128i question = _mm_set1_epi8('?');
-
- /* '['..'^' contains 4 characters: 3 forbidden and 1 rare */
- const __m128i bracket_lb = _mm_set1_epi8('[' - 1);
- const __m128i caret_ub = _mm_set1_epi8('^' + 1);
-
- /* '~' and above are forbidden */
- const __m128i tilde_lb = _mm_set1_epi8('~' - 1);
-
- int component_count = 0;
- int orig_flags = flags;
-
- if (refname[0] == 0 || refname[0] == '/') {
- /* entirely empty ref or initial ref component */
- return -1;
- }
-
- /*
- * Initial ref component of '.'; below we look for /. so we'll
- * miss this.
- */
- if (refname[0] == '.') {
- if (refname[1] == '/' || refname[1] == '\0')
- return -1;
- if (!(flags & REFNAME_DOT_COMPONENT))
- return -1;
- }
- while(1) {
- __m128i tmp, tmp1, result;
- uint64_t mask;
-
- if ((uintptr_t) cp % PAGE_SIZE > PAGE_SIZE - SSE_VECTOR_BYTES - 1)
- /*
- * End-of-page; fall back to slow method for
- * this entire ref.
- */
- return check_refname_format_bytewise(refname, orig_flags);
-
- tmp = _mm_loadu_si128((__m128i *)cp);
- tmp1 = _mm_loadu_si128((__m128i *)(cp + 1));
-
- /*
- * This range (note the lt) contains some
- * permissible-but-rare characters (including all
- * characters >= 128), which we handle later. It also
- * includes \000.
- */
- result = _mm_cmplt_epi8(tmp, star_ub);
-
- result = _mm_or_si128(result, _mm_cmpeq_epi8(tmp, question));
- result = _mm_or_si128(result, _mm_cmpeq_epi8(tmp, colon));
-
- /* This range contains the permissible ] as bycatch */
- result = _mm_or_si128(result, _mm_and_si128(
- _mm_cmpgt_epi8(tmp, bracket_lb),
- _mm_cmplt_epi8(tmp, caret_ub)));
-
- result = _mm_or_si128(result, _mm_cmpgt_epi8(tmp, tilde_lb));
-
- /* .. */
- result = _mm_or_si128(result, _mm_and_si128(
- _mm_cmpeq_epi8(tmp, dot),
- _mm_cmpeq_epi8(tmp1, dot)));
- /* @{ */
- result = _mm_or_si128(result, _mm_and_si128(
- _mm_cmpeq_epi8(tmp, at),
- _mm_cmpeq_epi8(tmp1, curly)));
- /* // */
- result = _mm_or_si128(result, _mm_and_si128(
- _mm_cmpeq_epi8(tmp, slash),
- _mm_cmpeq_epi8(tmp1, slash)));
- /* trailing / */
- result = _mm_or_si128(result, _mm_and_si128(
- _mm_cmpeq_epi8(tmp, slash),
- _mm_cmpeq_epi8(tmp1, zero)));
- /* .l, beginning of .lock */
- result = _mm_or_si128(result, _mm_and_si128(
- _mm_cmpeq_epi8(tmp, dot),
- _mm_cmpeq_epi8(tmp1, el)));
- /*
- * Even though /. is not necessarily an error, we flag
- * it anyway. If we find it, we'll check if it's valid
- * and if so we'll advance just past it.
- */
- result = _mm_or_si128(result, _mm_and_si128(
- _mm_cmpeq_epi8(tmp, slash),
- _mm_cmpeq_epi8(tmp1, dot)));
-
- mask = _mm_movemask_epi8(result);
- if (mask) {
- /*
- * We've found either end-of-string, or some
- * probably-bad character or substring.
- */
- int i = __builtin_ctz(mask);
- switch (refname_disposition[cp[i] & 255]) {
- case 0: /* fall-through */
- case 5:
- /*
- * bycatch: a good character that's in
- * one of the ranges of mostly-forbidden
- * characters
- */
- cp += i + 1;
- break;
- case 1:
- if (cp[i + 1] == '{')
- return -1;
- cp += i + 1;
- break;
- case 2:
- if (!(flags & REFNAME_ALLOW_ONELEVEL)
- && !component_count && !strchr(refname, '/'))
- /* Refname has only one component. */
- return -1;
- return 0;
- case 3:
- component_count ++;
- /*
- * Even if leading dots are allowed, don't
- * allow "." as a component (".." is
- * prevented by case 4 below).
- */
- if (cp[i + 1] == '.') {
- if (cp[i + 2] == '\0')
- return -1;
- if (flags & REFNAME_DOT_COMPONENT) {
- /* skip to just after the /. */
- cp += i + 2;
- break;
- }
- return -1;
- } else if (cp[i + 1] == '/' || cp[i + 1] == '\0')
- return -1;
- break;
- case 4:
- if (cp[i + 1] == '.' || cp[i + 1] == '\0')
- return -1;
- /* .lock as end-of-component or end-of-string */
- if ((!strncmp(cp + i, ".lock", 5))
- && (cp[i + 5] == '/' || cp[i + 5] == 0))
- return -1;
- cp += 1;
- break;
- case 6:
- if (((cp == refname + i) || cp[i - 1] == '/')
- && (cp[i + 1] == '/' || cp[i + 1] == 0))
- if (flags & REFNAME_REFSPEC_PATTERN) {
- flags &= ~REFNAME_REFSPEC_PATTERN;
- /* restart after the * */
- cp += i + 1;
- continue;
- }
- /* fall-through */
- case 7:
- return -1;
- }
- } else
- cp += SSE_VECTOR_BYTES;
- }
-}
-
-#else
-
-int check_refname_format (const char *refname, int flags)
-{
- return check_refname_format_bytewise(refname, flags);
-}
-
-#endif
-
struct ref_entry;
/*
if (o->type == OBJ_NONE) {
int type = sha1_object_info(name, NULL);
- if (type < 0)
+ if (type < 0 || !object_as_type(o, type, 0))
return PEEL_INVALID;
- o->type = type;
}
if (o->type != OBJ_TAG)
return logs_found;
}
-/* This function should make sure errno is meaningful on error */
+/*
+ * Locks a "refs/" ref returning the lock on success and NULL on failure.
+ * On failure errno is set to something meaningful.
+ */
static struct ref_lock *lock_ref_sha1_basic(const char *refname,
const unsigned char *old_sha1,
int flags, int *type_p)
return NULL;
}
-struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1)
-{
- char refpath[PATH_MAX];
- if (check_refname_format(refname, 0))
- return NULL;
- strcpy(refpath, mkpath("refs/%s", refname));
- return lock_ref_sha1_basic(refpath, old_sha1, 0, NULL);
-}
-
struct ref_lock *lock_any_ref_for_update(const char *refname,
const unsigned char *old_sha1,
int flags, int *type_p)
/* make sure nobody touched the ref, and unlink */
static void prune_ref(struct ref_to_prune *r)
{
- struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
- if (lock) {
- unlink_or_warn(git_path("%s", r->name));
- unlock_ref(lock);
- try_remove_empty_parents(r->name);
+ if (check_refname_format(r->name, 0))
+ return;
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_delete(transaction, r->name, r->sha1,
+ REF_ISPRUNING, 1, &err) ||
+ ref_transaction_commit(transaction, NULL, &err)) {
+ ref_transaction_free(transaction);
+ error("%s", err.buf);
+ strbuf_release(&err);
+ return;
}
+ ref_transaction_free(transaction);
+ strbuf_release(&err);
+ try_remove_empty_parents(r->name);
}
static void prune_refs(struct ref_to_prune *r)
return ret;
}
-static int repack_without_ref(const char *refname)
-{
- return repack_without_refs(&refname, 1, NULL);
-}
-
static int delete_ref_loose(struct ref_lock *lock, int flag)
{
if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
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)
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_delete(transaction, refname, sha1, delopt,
+ sha1 && !is_null_sha1(sha1), &err) ||
+ ref_transaction_commit(transaction, NULL, &err)) {
+ error("%s", err.buf);
+ ref_transaction_free(transaction);
+ strbuf_release(&err);
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.
- */
- ret |= repack_without_ref(lock->ref_name);
-
- unlink_or_warn(git_path("logs/%s", lock->ref_name));
- clear_loose_ref_cache(&ref_cache);
- unlock_ref(lock);
- return ret;
+ }
+ ref_transaction_free(transaction);
+ strbuf_release(&err);
+ return 0;
}
/*
return retval;
}
-static struct ref_lock *update_ref_lock(const char *refname,
- const unsigned char *oldval,
- int flags, int *type_p,
- enum action_on_err onerr)
-{
- 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) {
- case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
- case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
- case UPDATE_REFS_QUIET_ON_ERR: break;
- }
- }
- return lock;
-}
-
-static int update_ref_write(const char *action, const char *refname,
- const unsigned char *sha1, struct ref_lock *lock,
- struct strbuf *err, enum action_on_err onerr)
-{
- if (write_ref_sha1(lock, sha1, action) < 0) {
- const char *str = "Cannot update the ref '%s'.";
- if (err)
- strbuf_addf(err, str, refname);
-
- switch (onerr) {
- case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
- case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
- case UPDATE_REFS_QUIET_ON_ERR: break;
- }
- return 1;
- }
- return 0;
-}
-
/**
* Information needed for a single ref update. Set new_sha1 to the
* new value or to zero to delete the ref. To check the old value
const char refname[FLEX_ARRAY];
};
+/*
+ * Transaction states.
+ * OPEN: The transaction is in a valid state and can accept new updates.
+ * An OPEN transaction can be committed.
+ * CLOSED: A closed transaction is no longer active and no other operations
+ * than free can be used on it in this state.
+ * A transaction can either become closed by successfully committing
+ * an active transaction or if there is a failure while building
+ * the transaction thus rendering it failed/inactive.
+ */
+enum ref_transaction_state {
+ REF_TRANSACTION_OPEN = 0,
+ REF_TRANSACTION_CLOSED = 1
+};
+
/*
* Data structure for holding a reference transaction, which can
* consist of checks and updates to multiple references, carried out
struct ref_update **updates;
size_t alloc;
size_t nr;
+ enum ref_transaction_state state;
};
-struct ref_transaction *ref_transaction_begin(void)
+struct ref_transaction *ref_transaction_begin(struct strbuf *err)
{
return xcalloc(1, sizeof(struct ref_transaction));
}
{
struct ref_update *update;
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: update called for transaction that is not open");
+
if (have_old && !old_sha1)
die("BUG: have_old is true but old_sha1 is NULL");
return 0;
}
-void ref_transaction_create(struct ref_transaction *transaction,
- const char *refname,
- const unsigned char *new_sha1,
- int flags)
+int ref_transaction_create(struct ref_transaction *transaction,
+ const char *refname,
+ const unsigned char *new_sha1,
+ int flags,
+ struct strbuf *err)
{
- struct ref_update *update = add_update(transaction, refname);
+ struct ref_update *update;
+
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: create called for transaction that is not open");
+
+ if (!new_sha1 || is_null_sha1(new_sha1))
+ die("BUG: create ref with null new_sha1");
+
+ update = add_update(transaction, refname);
- assert(!is_null_sha1(new_sha1));
hashcpy(update->new_sha1, new_sha1);
hashclr(update->old_sha1);
update->flags = flags;
update->have_old = 1;
+ return 0;
}
-void ref_transaction_delete(struct ref_transaction *transaction,
- const char *refname,
- const unsigned char *old_sha1,
- int flags, int have_old)
+int ref_transaction_delete(struct ref_transaction *transaction,
+ const char *refname,
+ const unsigned char *old_sha1,
+ int flags, int have_old,
+ struct strbuf *err)
{
- struct ref_update *update = add_update(transaction, refname);
+ struct ref_update *update;
+
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: delete called for transaction that is not open");
+
+ if (have_old && !old_sha1)
+ die("BUG: have_old is true but old_sha1 is NULL");
+ update = add_update(transaction, refname);
update->flags = flags;
update->have_old = have_old;
if (have_old) {
assert(!is_null_sha1(old_sha1));
hashcpy(update->old_sha1, old_sha1);
}
+ 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, NULL, onerr);
- if (!lock)
+ struct ref_transaction *t;
+ struct strbuf err = STRBUF_INIT;
+
+ t = ref_transaction_begin(&err);
+ if (!t ||
+ ref_transaction_update(t, refname, sha1, oldval, flags,
+ !!oldval, &err) ||
+ ref_transaction_commit(t, action, &err)) {
+ const char *str = "update_ref failed for ref '%s': %s";
+
+ ref_transaction_free(t);
+ switch (onerr) {
+ case UPDATE_REFS_MSG_ON_ERR:
+ error(str, refname, err.buf);
+ break;
+ case UPDATE_REFS_DIE_ON_ERR:
+ die(str, refname, err.buf);
+ break;
+ case UPDATE_REFS_QUIET_ON_ERR:
+ break;
+ }
+ strbuf_release(&err);
return 1;
- return update_ref_write(action, refname, sha1, lock, NULL, onerr);
+ }
+ strbuf_release(&err);
+ ref_transaction_free(t);
+ return 0;
}
static int ref_update_compare(const void *r1, const void *r2)
int n = transaction->nr;
struct ref_update **updates = transaction->updates;
- if (!n)
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: commit called for transaction that is not open");
+
+ if (!n) {
+ transaction->state = REF_TRANSACTION_CLOSED;
return 0;
+ }
/* Allocate work space */
delnames = xmalloc(sizeof(*delnames) * n);
for (i = 0; i < n; i++) {
struct ref_update *update = updates[i];
- update->lock = update_ref_lock(update->refname,
- (update->have_old ?
- update->old_sha1 : NULL),
- update->flags,
- &update->type,
- UPDATE_REFS_QUIET_ON_ERR);
+ update->lock = lock_any_ref_for_update(update->refname,
+ (update->have_old ?
+ update->old_sha1 :
+ NULL),
+ update->flags,
+ &update->type);
if (!update->lock) {
if (err)
strbuf_addf(err, "Cannot lock the ref '%s'.",
struct ref_update *update = updates[i];
if (!is_null_sha1(update->new_sha1)) {
- ret = update_ref_write(msg,
- update->refname,
- update->new_sha1,
- update->lock, err,
- UPDATE_REFS_QUIET_ON_ERR);
- update->lock = NULL; /* freed by update_ref_write */
- if (ret)
+ ret = write_ref_sha1(update->lock, update->new_sha1,
+ msg);
+ update->lock = NULL; /* freed by write_ref_sha1 */
+ if (ret) {
+ if (err)
+ strbuf_addf(err, "Cannot update the ref '%s'.",
+ update->refname);
goto cleanup;
+ }
}
}
struct ref_update *update = updates[i];
if (update->lock) {
- delnames[delnum++] = update->lock->ref_name;
ret |= delete_ref_loose(update->lock, update->type);
+ if (!(update->flags & REF_ISPRUNING))
+ delnames[delnum++] = update->lock->ref_name;
}
}
clear_loose_ref_cache(&ref_cache);
cleanup:
+ transaction->state = REF_TRANSACTION_CLOSED;
+
for (i = 0; i < n; i++)
if (updates[i]->lock)
unlock_ref(updates[i]->lock);