diff options
Diffstat (limited to 'refs/files-backend.c')
| -rw-r--r-- | refs/files-backend.c | 198 | 
1 files changed, 171 insertions, 27 deletions
diff --git a/refs/files-backend.c b/refs/files-backend.c index 088b52c740..bb2bec3807 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -68,6 +68,12 @@   */  #define REF_DELETED_RMDIR (1 << 9) +/* + * Used to indicate that the reflog-only update has been created via + * `split_head_update()`. + */ +#define REF_LOG_VIA_SPLIT (1 << 14) +  struct ref_lock {  	char *ref_name;  	struct lock_file lk; @@ -648,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(). @@ -677,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; @@ -776,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;  		}  	} @@ -831,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)) { @@ -838,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'", @@ -867,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));  	} @@ -1467,6 +1528,15 @@ static int files_pack_refs(struct ref_store *ref_store,  	return 0;  } +static int files_optimize(struct ref_store *ref_store, struct pack_refs_opts *opts) +{ +	/* +	 * For the "files" backend, "optimizing" is the same as "packing". +	 * So, we just call the existing worker function for packing. +	 */ +	return files_pack_refs(ref_store, opts); +} +  /*   * People using contrib's git-new-workdir have .git/logs/refs ->   * /some/other/path/.git/logs/refs, and that may live on another device. @@ -2109,7 +2179,9 @@ static int files_delete_reflog(struct ref_store *ref_store,  	return ret;  } -static int show_one_reflog_ent(struct files_ref_store *refs, struct strbuf *sb, +static int show_one_reflog_ent(struct files_ref_store *refs, +			       const char *refname, +			       struct strbuf *sb,  			       each_reflog_ent_fn fn, void *cb_data)  {  	struct object_id ooid, noid; @@ -2136,7 +2208,7 @@ static int show_one_reflog_ent(struct files_ref_store *refs, struct strbuf *sb,  		message += 6;  	else  		message += 7; -	return fn(&ooid, &noid, p, timestamp, tz, message, cb_data); +	return fn(refname, &ooid, &noid, p, timestamp, tz, message, cb_data);  }  static char *find_beginning_of_line(char *bob, char *scan) @@ -2220,7 +2292,7 @@ static int files_for_each_reflog_ent_reverse(struct ref_store *ref_store,  				strbuf_splice(&sb, 0, 0, bp + 1, endp - (bp + 1));  				scanp = bp;  				endp = bp + 1; -				ret = show_one_reflog_ent(refs, &sb, fn, cb_data); +				ret = show_one_reflog_ent(refs, refname, &sb, fn, cb_data);  				strbuf_reset(&sb);  				if (ret)  					break; @@ -2232,7 +2304,7 @@ static int files_for_each_reflog_ent_reverse(struct ref_store *ref_store,  				 * Process it, and we can end the loop.  				 */  				strbuf_splice(&sb, 0, 0, buf, endp - buf); -				ret = show_one_reflog_ent(refs, &sb, fn, cb_data); +				ret = show_one_reflog_ent(refs, refname, &sb, fn, cb_data);  				strbuf_reset(&sb);  				break;  			} @@ -2282,7 +2354,7 @@ static int files_for_each_reflog_ent(struct ref_store *ref_store,  		return -1;  	while (!ret && !strbuf_getwholeline(&sb, logfp, '\n')) -		ret = show_one_reflog_ent(refs, &sb, fn, cb_data); +		ret = show_one_reflog_ent(refs, refname, &sb, fn, cb_data);  	fclose(logfp);  	strbuf_release(&sb);  	return ret; @@ -2421,9 +2493,10 @@ static enum ref_transaction_error split_head_update(struct ref_update *update,  	new_update = ref_transaction_add_update(  			transaction, "HEAD", -			update->flags | REF_LOG_ONLY | REF_NO_DEREF, +			update->flags | REF_LOG_ONLY | REF_NO_DEREF | REF_LOG_VIA_SPLIT,  			&update->new_oid, &update->old_oid,  			NULL, NULL, update->committer_info, update->msg); +	new_update->parent_update = update;  	/*  	 * Add "HEAD". This insertion is O(N) in the transaction @@ -2494,7 +2567,6 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update,  	 * done when new_update is processed.  	 */  	update->flags |= REF_LOG_ONLY | REF_NO_DEREF; -	update->flags &= ~REF_HAVE_OLD;  	return 0;  } @@ -2507,11 +2579,36 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update,   */  static enum ref_transaction_error check_old_oid(struct ref_update *update,  						struct object_id *oid, +						struct strbuf *referent,  						struct strbuf *err)  { -	if (!(update->flags & REF_HAVE_OLD) || -		   oideq(oid, &update->old_oid)) +	if (update->flags & REF_LOG_ONLY || +	    !(update->flags & REF_HAVE_OLD)) +		return 0; + +	if (oideq(oid, &update->old_oid)) { +		/* +		 * Normally matching the expected old oid is enough. Either we +		 * found the ref at the expected state, or we are creating and +		 * expect the null oid (and likewise found nothing). +		 * +		 * But there is one exception for the null oid: if we found a +		 * symref pointing to nothing we'll also get the null oid. In +		 * regular recursive mode, that's good (we'll write to what the +		 * symref points to, which doesn't exist). But in no-deref +		 * mode, it means we'll clobber the symref, even though the +		 * caller asked for this to be a creation event. So flag +		 * that case to preserve the dangling symref. +		 */ +		if ((update->flags & REF_NO_DEREF) && referent->len && +		    is_null_oid(oid)) { +			strbuf_addf(err, "cannot lock ref '%s': " +				    "dangling symref already exists", +				    ref_update_original_update_refname(update)); +			return REF_TRANSACTION_ERROR_CREATE_EXISTS; +		}  		return 0; +	}  	if (is_null_oid(&update->old_oid)) {  		strbuf_addf(err, "cannot lock ref '%s': " @@ -2583,9 +2680,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; @@ -2601,7 +2697,36 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re  	update->backend_data = lock; -	if (update->type & REF_ISSYMREF) { +	if (update->flags & REF_LOG_VIA_SPLIT) { +		struct ref_lock *parent_lock; + +		if (!update->parent_update) +			BUG("split update without a parent"); + +		parent_lock = update->parent_update->backend_data; + +		/* +		 * Check that "HEAD" didn't racily change since we have looked +		 * it up. If it did we must refuse to write the reflog entry. +		 * +		 * Note that this does not catch all races: if "HEAD" was +		 * racily changed to point to one of the refs part of the +		 * transaction then we would miss writing the split reflog +		 * entry for "HEAD". +		 */ +		if (!(update->type & REF_ISSYMREF) || +		    strcmp(update->parent_update->refname, referent.buf)) { +			strbuf_addstr(err, "HEAD has been racily updated"); +			ret = REF_TRANSACTION_ERROR_GENERIC; +			goto out; +		} + +		if (update->flags & REF_HAVE_OLD) { +			oidcpy(&lock->old_oid, &update->old_oid); +		} else { +			oidcpy(&lock->old_oid, &parent_lock->old_oid); +		} +	} else if (update->type & REF_ISSYMREF) {  		if (update->flags & REF_NO_DEREF) {  			/*  			 * We won't be reading the referent as part of @@ -2623,7 +2748,8 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re  			if (update->old_target)  				ret = ref_update_check_old_target(referent.buf, update, err);  			else -				ret = check_old_oid(update, &lock->old_oid, err); +				ret = check_old_oid(update, &lock->old_oid, +						    &referent, err);  			if (ret)  				goto out;  		} else { @@ -2655,7 +2781,8 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re  			ret = REF_TRANSACTION_ERROR_EXPECTED_SYMREF;  			goto out;  		} else { -			ret = check_old_oid(update, &lock->old_oid, err); +			ret = check_old_oid(update, &lock->old_oid, +					    &referent, err);  			if  (ret) {  				goto out;  			} @@ -2794,7 +2921,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; @@ -2977,6 +3104,20 @@ static int parse_and_write_reflog(struct files_ref_store *refs,  				  struct ref_lock *lock,  				  struct strbuf *err)  { +	struct object_id *old_oid = &lock->old_oid; + +	if (update->flags & REF_LOG_USE_PROVIDED_OIDS) { +		if (!(update->flags & REF_HAVE_OLD) || +		    !(update->flags & REF_HAVE_NEW) || +		    !(update->flags & REF_LOG_ONLY)) { +			strbuf_addf(err, _("trying to write reflog for '%s'" +					   "with incomplete values"), update->refname); +			return REF_TRANSACTION_ERROR_GENERIC; +		} + +		old_oid = &update->old_oid; +	} +  	if (update->new_target) {  		/*  		 * We want to get the resolved OID for the target, to ensure @@ -2994,7 +3135,7 @@ static int parse_and_write_reflog(struct files_ref_store *refs,  		}  	} -	if (files_log_ref_write(refs, lock->ref_name, &lock->old_oid, +	if (files_log_ref_write(refs, lock->ref_name, old_oid,  				&update->new_oid, update->committer_info,  				update->msg, update->flags, err)) {  		char *old_msg = strbuf_detach(err, NULL); @@ -3062,7 +3203,8 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,  	for (i = 0; i < transaction->nr; i++) {  		struct ref_update *update = transaction->updates[i]; -		if ((update->flags & REF_HAVE_OLD) && +		if (!(update->flags & REF_LOG_ONLY) && +		    (update->flags & REF_HAVE_OLD) &&  		    !is_null_oid(&update->old_oid))  			BUG("initial ref transaction with old_sha1 set"); @@ -3309,7 +3451,8 @@ struct expire_reflog_cb {  		     dry_run:1;  }; -static int expire_reflog_ent(struct object_id *ooid, struct object_id *noid, +static int expire_reflog_ent(const char *refname UNUSED, +			     struct object_id *ooid, struct object_id *noid,  			     const char *email, timestamp_t timestamp, int tz,  			     const char *message, void *cb_data)  { @@ -3855,6 +3998,7 @@ struct ref_storage_be refs_be_files = {  	.transaction_abort = files_transaction_abort,  	.pack_refs = files_pack_refs, +	.optimize = files_optimize,  	.rename_ref = files_rename_ref,  	.copy_ref = files_copy_ref,  | 
