Merge branch 'jk/faster-name-conflicts'
Optimize the check to see if a ref $F can be created by making sure no existing ref has $F/ as its prefix, which especially matters in a repository with a large number of existing refs. * jk/faster-name-conflicts: refs: speed up is_refname_available
This commit is contained in:
118
refs.c
118
refs.c
@ -784,37 +784,32 @@ static void prime_ref_dir(struct ref_dir *dir)
|
|||||||
prime_ref_dir(get_ref_dir(entry));
|
prime_ref_dir(get_ref_dir(entry));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
* Return true iff refname1 and refname2 conflict with each other.
|
static int entry_matches(struct ref_entry *entry, const char *refname)
|
||||||
* Two reference names conflict if one of them exactly matches the
|
|
||||||
* leading components of the other; e.g., "foo/bar" conflicts with
|
|
||||||
* both "foo" and with "foo/bar/baz" but not with "foo/bar" or
|
|
||||||
* "foo/barbados".
|
|
||||||
*/
|
|
||||||
static int names_conflict(const char *refname1, const char *refname2)
|
|
||||||
{
|
{
|
||||||
for (; *refname1 && *refname1 == *refname2; refname1++, refname2++)
|
return refname && !strcmp(entry->name, refname);
|
||||||
;
|
|
||||||
return (*refname1 == '\0' && *refname2 == '/')
|
|
||||||
|| (*refname1 == '/' && *refname2 == '\0');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct name_conflict_cb {
|
struct nonmatching_ref_data {
|
||||||
const char *refname;
|
const char *skip;
|
||||||
const char *oldrefname;
|
struct ref_entry *found;
|
||||||
const char *conflicting_refname;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static int name_conflict_fn(struct ref_entry *entry, void *cb_data)
|
static int nonmatching_ref_fn(struct ref_entry *entry, void *vdata)
|
||||||
{
|
{
|
||||||
struct name_conflict_cb *data = (struct name_conflict_cb *)cb_data;
|
struct nonmatching_ref_data *data = vdata;
|
||||||
if (data->oldrefname && !strcmp(data->oldrefname, entry->name))
|
|
||||||
|
if (entry_matches(entry, data->skip))
|
||||||
return 0;
|
return 0;
|
||||||
if (names_conflict(data->refname, entry->name)) {
|
|
||||||
data->conflicting_refname = entry->name;
|
data->found = entry;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
|
static void report_refname_conflict(struct ref_entry *entry,
|
||||||
|
const char *refname)
|
||||||
|
{
|
||||||
|
error("'%s' exists; cannot create '%s'", entry->name, refname);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -823,21 +818,84 @@ static int name_conflict_fn(struct ref_entry *entry, void *cb_data)
|
|||||||
* oldrefname is non-NULL, ignore potential conflicts with oldrefname
|
* oldrefname is non-NULL, ignore potential conflicts with oldrefname
|
||||||
* (e.g., because oldrefname is scheduled for deletion in the same
|
* (e.g., because oldrefname is scheduled for deletion in the same
|
||||||
* operation).
|
* operation).
|
||||||
|
*
|
||||||
|
* Two reference names conflict if one of them exactly matches the
|
||||||
|
* leading components of the other; e.g., "foo/bar" conflicts with
|
||||||
|
* both "foo" and with "foo/bar/baz" but not with "foo/bar" or
|
||||||
|
* "foo/barbados".
|
||||||
*/
|
*/
|
||||||
static int is_refname_available(const char *refname, const char *oldrefname,
|
static int is_refname_available(const char *refname, const char *oldrefname,
|
||||||
struct ref_dir *dir)
|
struct ref_dir *dir)
|
||||||
{
|
{
|
||||||
struct name_conflict_cb data;
|
const char *slash;
|
||||||
data.refname = refname;
|
size_t len;
|
||||||
data.oldrefname = oldrefname;
|
int pos;
|
||||||
data.conflicting_refname = NULL;
|
char *dirname;
|
||||||
|
|
||||||
sort_ref_dir(dir);
|
for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) {
|
||||||
if (do_for_each_entry_in_dir(dir, 0, name_conflict_fn, &data)) {
|
/*
|
||||||
error("'%s' exists; cannot create '%s'",
|
* We are still at a leading dir of the refname; we are
|
||||||
data.conflicting_refname, refname);
|
* looking for a conflict with a leaf entry.
|
||||||
|
*
|
||||||
|
* If we find one, we still must make sure it is
|
||||||
|
* not "oldrefname".
|
||||||
|
*/
|
||||||
|
pos = search_ref_dir(dir, refname, slash - refname);
|
||||||
|
if (pos >= 0) {
|
||||||
|
struct ref_entry *entry = dir->entries[pos];
|
||||||
|
if (entry_matches(entry, oldrefname))
|
||||||
|
return 1;
|
||||||
|
report_refname_conflict(entry, refname);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Otherwise, we can try to continue our search with
|
||||||
|
* the next component; if we come up empty, we know
|
||||||
|
* there is nothing under this whole prefix.
|
||||||
|
*/
|
||||||
|
pos = search_ref_dir(dir, refname, slash + 1 - refname);
|
||||||
|
if (pos < 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
dir = get_ref_dir(dir->entries[pos]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We are at the leaf of our refname; we want to
|
||||||
|
* make sure there are no directories which match it.
|
||||||
|
*/
|
||||||
|
len = strlen(refname);
|
||||||
|
dirname = xmallocz(len + 1);
|
||||||
|
sprintf(dirname, "%s/", refname);
|
||||||
|
pos = search_ref_dir(dir, dirname, len + 1);
|
||||||
|
free(dirname);
|
||||||
|
|
||||||
|
if (pos >= 0) {
|
||||||
|
/*
|
||||||
|
* We found a directory named "refname". It is a
|
||||||
|
* problem iff it contains any ref that is not
|
||||||
|
* "oldrefname".
|
||||||
|
*/
|
||||||
|
struct ref_entry *entry = dir->entries[pos];
|
||||||
|
struct ref_dir *dir = get_ref_dir(entry);
|
||||||
|
struct nonmatching_ref_data data;
|
||||||
|
|
||||||
|
data.skip = oldrefname;
|
||||||
|
sort_ref_dir(dir);
|
||||||
|
if (!do_for_each_entry_in_dir(dir, 0, nonmatching_ref_fn, &data))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
report_refname_conflict(data.found, refname);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* There is no point in searching for another leaf
|
||||||
|
* node which matches it; such an entry would be the
|
||||||
|
* ref we are looking for, not a conflict.
|
||||||
|
*/
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,9 @@ semantic is still the same.
|
|||||||
'
|
'
|
||||||
. ./test-lib.sh
|
. ./test-lib.sh
|
||||||
|
|
||||||
echo '[core] logallrefupdates = true' >>.git/config
|
test_expect_success 'enable reflogs' '
|
||||||
|
git config core.logallrefupdates true
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success \
|
test_expect_success \
|
||||||
'prepare a trivial repository' \
|
'prepare a trivial repository' \
|
||||||
@ -158,4 +160,31 @@ test_expect_success 'pack ref directly below refs/' '
|
|||||||
test_path_is_missing .git/refs/top
|
test_path_is_missing .git/refs/top
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'disable reflogs' '
|
||||||
|
git config core.logallrefupdates false &&
|
||||||
|
rm -rf .git/logs
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'create packed foo/bar/baz branch' '
|
||||||
|
git branch foo/bar/baz &&
|
||||||
|
git pack-refs --all --prune &&
|
||||||
|
test_path_is_missing .git/refs/heads/foo/bar/baz &&
|
||||||
|
test_path_is_missing .git/logs/refs/heads/foo/bar/baz
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'notice d/f conflict with existing directory' '
|
||||||
|
test_must_fail git branch foo &&
|
||||||
|
test_must_fail git branch foo/bar
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'existing directory reports concrete ref' '
|
||||||
|
test_must_fail git branch foo 2>stderr &&
|
||||||
|
grep refs/heads/foo/bar/baz stderr
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'notice d/f conflict with existing ref' '
|
||||||
|
test_must_fail git branch foo/bar/baz/extra &&
|
||||||
|
test_must_fail git branch foo/bar/baz/lots/of/extra/components
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
Reference in New Issue
Block a user