summaryrefslogtreecommitdiff
path: root/refs/files-backend.c
diff options
context:
space:
mode:
Diffstat (limited to 'refs/files-backend.c')
-rw-r--r--refs/files-backend.c78
1 files changed, 66 insertions, 12 deletions
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 1b3bf26add..bc3347d18c 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -654,6 +654,26 @@ static void unlock_ref(struct ref_lock *lock)
}
/*
+ * Check if the transaction has another update with a case-insensitive refname
+ * match.
+ *
+ * If the update is part of the transaction, we only check up to that index.
+ * Further updates are expected to call this function to match previous indices.
+ */
+static bool transaction_has_case_conflicting_update(struct ref_transaction *transaction,
+ struct ref_update *update)
+{
+ for (size_t i = 0; i < transaction->nr; i++) {
+ if (transaction->updates[i] == update)
+ break;
+
+ if (!strcasecmp(transaction->updates[i]->refname, update->refname))
+ return true;
+ }
+ return false;
+}
+
+/*
* Lock refname, without following symrefs, and set *lock_p to point
* at a newly-allocated lock object. Fill in lock->old_oid, referent,
* and type similarly to read_raw_ref().
@@ -683,16 +703,17 @@ static void unlock_ref(struct ref_lock *lock)
* - Generate informative error messages in the case of failure
*/
static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs,
- struct ref_update *update,
+ struct ref_transaction *transaction,
size_t update_idx,
int mustexist,
struct string_list *refnames_to_check,
- const struct string_list *extras,
struct ref_lock **lock_p,
struct strbuf *referent,
struct strbuf *err)
{
enum ref_transaction_error ret = REF_TRANSACTION_ERROR_GENERIC;
+ struct ref_update *update = transaction->updates[update_idx];
+ const struct string_list *extras = &transaction->refnames;
const char *refname = update->refname;
unsigned int *type = &update->type;
struct ref_lock *lock;
@@ -782,6 +803,24 @@ retry:
goto retry;
} else {
unable_to_lock_message(ref_file.buf, myerr, err);
+ if (myerr == EEXIST) {
+ if (ignore_case &&
+ transaction_has_case_conflicting_update(transaction, update)) {
+ /*
+ * In case-insensitive filesystems, ensure that conflicts within a
+ * given transaction are handled. Pre-existing refs on a
+ * case-insensitive system will be overridden without any issue.
+ */
+ ret = REF_TRANSACTION_ERROR_CASE_CONFLICT;
+ } else {
+ /*
+ * Pre-existing case-conflicting reference locks should also be
+ * specially categorized to avoid failing all batched updates.
+ */
+ ret = REF_TRANSACTION_ERROR_CREATE_EXISTS;
+ }
+ }
+
goto error_return;
}
}
@@ -837,6 +876,7 @@ retry:
goto error_return;
} else if (remove_dir_recursively(&ref_file,
REMOVE_DIR_EMPTY_ONLY)) {
+ ret = REF_TRANSACTION_ERROR_NAME_CONFLICT;
if (refs_verify_refname_available(
&refs->base, refname,
extras, NULL, 0, err)) {
@@ -844,14 +884,14 @@ retry:
* The error message set by
* verify_refname_available() is OK.
*/
- ret = REF_TRANSACTION_ERROR_NAME_CONFLICT;
goto error_return;
} else {
/*
- * We can't delete the directory,
- * but we also don't know of any
- * references that it should
- * contain.
+ * Directory conflicts can occur if there
+ * is an existing lock file in the directory
+ * or if the filesystem is case-insensitive
+ * and the directory contains a valid reference
+ * but conflicts with the update.
*/
strbuf_addf(err, "there is a non-empty directory '%s' "
"blocking reference '%s'",
@@ -873,8 +913,23 @@ retry:
* If the ref did not exist and we are creating it, we have to
* make sure there is no existing packed ref that conflicts
* with refname. This check is deferred so that we can batch it.
+ *
+ * For case-insensitive filesystems, we should also check for F/D
+ * conflicts between 'foo' and 'Foo/bar'. So let's lowercase
+ * the refname.
*/
- item = string_list_append(refnames_to_check, refname);
+ if (ignore_case) {
+ struct strbuf lower = STRBUF_INIT;
+
+ strbuf_addstr(&lower, refname);
+ strbuf_tolower(&lower);
+
+ item = string_list_append_nodup(refnames_to_check,
+ strbuf_detach(&lower, NULL));
+ } else {
+ item = string_list_append(refnames_to_check, refname);
+ }
+
item->util = xmalloc(sizeof(update_idx));
memcpy(item->util, &update_idx, sizeof(update_idx));
}
@@ -2616,9 +2671,8 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re
if (lock) {
lock->count++;
} else {
- ret = lock_raw_ref(refs, update, update_idx, mustexist,
- refnames_to_check, &transaction->refnames,
- &lock, &referent, err);
+ ret = lock_raw_ref(refs, transaction, update_idx, mustexist,
+ refnames_to_check, &lock, &referent, err);
if (ret) {
char *reason;
@@ -2858,7 +2912,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
"ref_transaction_prepare");
size_t i;
int ret = 0;
- struct string_list refnames_to_check = STRING_LIST_INIT_NODUP;
+ struct string_list refnames_to_check = STRING_LIST_INIT_DUP;
char *head_ref = NULL;
int head_type;
struct files_transaction_backend_data *backend_data;