refs.c: pass a list of names to skip to is_refname_available

Change is_refname_available to take a list of strings to exclude when
checking for conflicts instead of just one single name. We can already
exclude a single name for the sake of renames. This generalizes that support.

ref_transaction_commit already tracks a set of refs that are being deleted
in an array.  This array is then used to exclude refs from being written to
the packed-refs file.  At some stage we will want to change this array to a
struct string_list and then we can pass it to is_refname_available via the
call to lock_ref_sha1_basic.  That will allow us to perform transactions
that perform multiple renames as long as there are no conflicts within the
starting or ending state.

For example, that would allow a single transaction that contains two
renames that are both individually conflicting:

   m -> n/n
   n -> m/m

No functional change intended yet.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Ronnie Sahlberg 2014-05-01 11:16:07 -07:00 committed by Junio C Hamano
parent 5d94a1b033
commit 5fe7d825da

50
refs.c
View File

@ -787,13 +787,13 @@ static void prime_ref_dir(struct ref_dir *dir)
} }
} }
static int entry_matches(struct ref_entry *entry, const char *refname) static int entry_matches(struct ref_entry *entry, const struct string_list *list)
{ {
return refname && !strcmp(entry->name, refname); return list && string_list_has_string(list, entry->name);
} }
struct nonmatching_ref_data { struct nonmatching_ref_data {
const char *skip; const struct string_list *skip;
struct ref_entry *found; struct ref_entry *found;
}; };
@ -817,16 +817,19 @@ static void report_refname_conflict(struct ref_entry *entry,
/* /*
* Return true iff a reference named refname could be created without * Return true iff a reference named refname could be created without
* conflicting with the name of an existing reference in dir. If * conflicting with the name of an existing reference in dir. If
* oldrefname is non-NULL, ignore potential conflicts with oldrefname * skip is non-NULL, ignore potential conflicts with refs in skip
* (e.g., because oldrefname is scheduled for deletion in the same * (e.g., because they are scheduled for deletion in the same
* operation). * operation).
* *
* Two reference names conflict if one of them exactly matches the * Two reference names conflict if one of them exactly matches the
* leading components of the other; e.g., "foo/bar" conflicts with * leading components of the other; e.g., "foo/bar" conflicts with
* both "foo" and with "foo/bar/baz" but not with "foo/bar" or * both "foo" and with "foo/bar/baz" but not with "foo/bar" or
* "foo/barbados". * "foo/barbados".
*
* skip must be sorted.
*/ */
static int is_refname_available(const char *refname, const char *oldrefname, static int is_refname_available(const char *refname,
const struct string_list *skip,
struct ref_dir *dir) struct ref_dir *dir)
{ {
const char *slash; const char *slash;
@ -840,12 +843,12 @@ static int is_refname_available(const char *refname, const char *oldrefname,
* looking for a conflict with a leaf entry. * looking for a conflict with a leaf entry.
* *
* If we find one, we still must make sure it is * If we find one, we still must make sure it is
* not "oldrefname". * not in "skip".
*/ */
pos = search_ref_dir(dir, refname, slash - refname); pos = search_ref_dir(dir, refname, slash - refname);
if (pos >= 0) { if (pos >= 0) {
struct ref_entry *entry = dir->entries[pos]; struct ref_entry *entry = dir->entries[pos];
if (entry_matches(entry, oldrefname)) if (entry_matches(entry, skip))
return 1; return 1;
report_refname_conflict(entry, refname); report_refname_conflict(entry, refname);
return 0; return 0;
@ -878,13 +881,13 @@ static int is_refname_available(const char *refname, const char *oldrefname,
/* /*
* We found a directory named "refname". It is a * We found a directory named "refname". It is a
* problem iff it contains any ref that is not * problem iff it contains any ref that is not
* "oldrefname". * in "skip".
*/ */
struct ref_entry *entry = dir->entries[pos]; struct ref_entry *entry = dir->entries[pos];
struct ref_dir *dir = get_ref_dir(entry); struct ref_dir *dir = get_ref_dir(entry);
struct nonmatching_ref_data data; struct nonmatching_ref_data data;
data.skip = oldrefname; data.skip = skip;
sort_ref_dir(dir); sort_ref_dir(dir);
if (!do_for_each_entry_in_dir(dir, 0, nonmatching_ref_fn, &data)) if (!do_for_each_entry_in_dir(dir, 0, nonmatching_ref_fn, &data))
return 1; return 1;
@ -2139,6 +2142,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
*/ */
static struct ref_lock *lock_ref_sha1_basic(const char *refname, static struct ref_lock *lock_ref_sha1_basic(const char *refname,
const unsigned char *old_sha1, const unsigned char *old_sha1,
const struct string_list *skip,
int flags, int *type_p) int flags, int *type_p)
{ {
char *ref_file; char *ref_file;
@ -2188,7 +2192,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
* name is a proper prefix of our refname. * name is a proper prefix of our refname.
*/ */
if (missing && if (missing &&
!is_refname_available(refname, NULL, get_packed_refs(&ref_cache))) { !is_refname_available(refname, skip, get_packed_refs(&ref_cache))) {
last_errno = ENOTDIR; last_errno = ENOTDIR;
goto error_return; goto error_return;
} }
@ -2246,7 +2250,7 @@ struct ref_lock *lock_any_ref_for_update(const char *refname,
const unsigned char *old_sha1, const unsigned char *old_sha1,
int flags, int *type_p) int flags, int *type_p)
{ {
return lock_ref_sha1_basic(refname, old_sha1, flags, type_p); return lock_ref_sha1_basic(refname, old_sha1, NULL, flags, type_p);
} }
/* /*
@ -2690,6 +2694,18 @@ static int rename_tmp_log(const char *newrefname)
return 0; return 0;
} }
static int rename_ref_available(const char *oldname, const char *newname)
{
struct string_list skip = STRING_LIST_INIT_NODUP;
int ret;
string_list_insert(&skip, oldname);
ret = is_refname_available(newname, &skip, get_packed_refs(&ref_cache))
&& is_refname_available(newname, &skip, get_loose_refs(&ref_cache));
string_list_clear(&skip, 0);
return ret;
}
int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg) int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)
{ {
unsigned char sha1[20], orig_sha1[20]; unsigned char sha1[20], orig_sha1[20];
@ -2709,10 +2725,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
if (!symref) if (!symref)
return error("refname %s not found", oldrefname); return error("refname %s not found", oldrefname);
if (!is_refname_available(newrefname, oldrefname, get_packed_refs(&ref_cache))) if (!rename_ref_available(oldrefname, newrefname))
return 1;
if (!is_refname_available(newrefname, oldrefname, get_loose_refs(&ref_cache)))
return 1; return 1;
if (log && rename(git_path("logs/%s", oldrefname), git_path(TMP_RENAMED_LOG))) if (log && rename(git_path("logs/%s", oldrefname), git_path(TMP_RENAMED_LOG)))
@ -2742,7 +2755,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
logmoved = log; logmoved = log;
lock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL); lock = lock_ref_sha1_basic(newrefname, NULL, NULL, 0, NULL);
if (!lock) { if (!lock) {
error("unable to lock %s for update", newrefname); error("unable to lock %s for update", newrefname);
goto rollback; goto rollback;
@ -2757,7 +2770,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
return 0; return 0;
rollback: rollback:
lock = lock_ref_sha1_basic(oldrefname, NULL, 0, NULL); lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, 0, NULL);
if (!lock) { if (!lock) {
error("unable to lock %s for rollback", oldrefname); error("unable to lock %s for rollback", oldrefname);
goto rollbacklog; goto rollbacklog;
@ -3636,6 +3649,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,
(update->have_old ? (update->have_old ?
update->old_sha1 : update->old_sha1 :
NULL), NULL),
NULL,
update->flags, update->flags,
&update->type); &update->type);
if (!update->lock) { if (!update->lock) {