зеркало из https://github.com/microsoft/git.git
Merge branch 'mh/ref-directory-file'
The ref API did not handle cases where 'refs/heads/xyzzy/frotz' is removed at the same time as 'refs/heads/xyzzy' is added (or vice versa) very well. * mh/ref-directory-file: reflog_expire(): integrate lock_ref_sha1_basic() errors into ours ref_transaction_commit(): delete extra "the" from error message ref_transaction_commit(): provide better error messages rename_ref(): integrate lock_ref_sha1_basic() errors into ours lock_ref_sha1_basic(): improve diagnostics for ref D/F conflicts lock_ref_sha1_basic(): report errors via a "struct strbuf *err" verify_refname_available(): report errors via a "struct strbuf *err" verify_refname_available(): rename function refs: check for D/F conflicts among refs created in a transaction ref_transaction_commit(): use a string_list for detecting duplicates is_refname_available(): use dirname in first loop struct nonmatching_ref_data: store a refname instead of a ref_entry report_refname_conflict(): inline function entry_matches(): inline function is_refname_available(): convert local variable "dirname" to strbuf is_refname_available(): avoid shadowing "dir" variable is_refname_available(): revamp the comments t1404: new tests of ref D/F conflicts within transactions
This commit is contained in:
Коммит
faa4b2ecbb
309
refs.c
309
refs.c
|
@ -268,7 +268,7 @@ struct ref_dir {
|
||||||
* presence of an empty subdirectory does not block the creation of a
|
* presence of an empty subdirectory does not block the creation of a
|
||||||
* similarly-named reference. (The fact that reference names with the
|
* similarly-named reference. (The fact that reference names with the
|
||||||
* same leading components can conflict *with each other* is a
|
* same leading components can conflict *with each other* is a
|
||||||
* separate issue that is regulated by is_refname_available().)
|
* separate issue that is regulated by verify_refname_available().)
|
||||||
*
|
*
|
||||||
* Please note that the name field contains the fully-qualified
|
* Please note that the name field contains the fully-qualified
|
||||||
* reference (or subdirectory) name. Space could be saved by only
|
* reference (or subdirectory) name. Space could be saved by only
|
||||||
|
@ -844,121 +844,181 @@ static void prime_ref_dir(struct ref_dir *dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int entry_matches(struct ref_entry *entry, const struct string_list *list)
|
|
||||||
{
|
|
||||||
return list && string_list_has_string(list, entry->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct nonmatching_ref_data {
|
struct nonmatching_ref_data {
|
||||||
const struct string_list *skip;
|
const struct string_list *skip;
|
||||||
struct ref_entry *found;
|
const char *conflicting_refname;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int nonmatching_ref_fn(struct ref_entry *entry, void *vdata)
|
static int nonmatching_ref_fn(struct ref_entry *entry, void *vdata)
|
||||||
{
|
{
|
||||||
struct nonmatching_ref_data *data = vdata;
|
struct nonmatching_ref_data *data = vdata;
|
||||||
|
|
||||||
if (entry_matches(entry, data->skip))
|
if (data->skip && string_list_has_string(data->skip, entry->name))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
data->found = entry;
|
data->conflicting_refname = entry->name;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void report_refname_conflict(struct ref_entry *entry,
|
|
||||||
const char *refname)
|
|
||||||
{
|
|
||||||
error("'%s' exists; cannot create '%s'", entry->name, refname);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Return true iff a reference named refname could be created without
|
* Return 0 if 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.
|
||||||
* skip is non-NULL, ignore potential conflicts with refs in skip
|
* Otherwise, return a negative value and write an explanation to err.
|
||||||
* (e.g., because they are scheduled for deletion in the same
|
* If extras is non-NULL, it is a list of additional refnames with
|
||||||
* operation).
|
* which refname is not allowed to conflict. If skip is non-NULL,
|
||||||
|
* ignore potential conflicts with refs in skip (e.g., because they
|
||||||
|
* are scheduled for deletion in the same operation). Behavior is
|
||||||
|
* undefined if the same name is listed in both extras and skip.
|
||||||
*
|
*
|
||||||
* 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., "refs/foo/bar" conflicts
|
||||||
* both "foo" and with "foo/bar/baz" but not with "foo/bar" or
|
* with both "refs/foo" and with "refs/foo/bar/baz" but not with
|
||||||
* "foo/barbados".
|
* "refs/foo/bar" or "refs/foo/barbados".
|
||||||
*
|
*
|
||||||
* skip must be sorted.
|
* extras and skip must be sorted.
|
||||||
*/
|
*/
|
||||||
static int is_refname_available(const char *refname,
|
static int verify_refname_available(const char *refname,
|
||||||
const struct string_list *skip,
|
const struct string_list *extras,
|
||||||
struct ref_dir *dir)
|
const struct string_list *skip,
|
||||||
|
struct ref_dir *dir,
|
||||||
|
struct strbuf *err)
|
||||||
{
|
{
|
||||||
const char *slash;
|
const char *slash;
|
||||||
size_t len;
|
|
||||||
int pos;
|
int pos;
|
||||||
char *dirname;
|
struct strbuf dirname = STRBUF_INIT;
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For the sake of comments in this function, suppose that
|
||||||
|
* refname is "refs/foo/bar".
|
||||||
|
*/
|
||||||
|
|
||||||
|
assert(err);
|
||||||
|
|
||||||
|
strbuf_grow(&dirname, strlen(refname) + 1);
|
||||||
for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) {
|
for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) {
|
||||||
|
/* Expand dirname to the new prefix, not including the trailing slash: */
|
||||||
|
strbuf_add(&dirname, refname + dirname.len, slash - refname - dirname.len);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We are still at a leading dir of the refname; we are
|
* We are still at a leading dir of the refname (e.g.,
|
||||||
* looking for a conflict with a leaf entry.
|
* "refs/foo"; if there is a reference with that name,
|
||||||
*
|
* it is a conflict, *unless* it is in skip.
|
||||||
* If we find one, we still must make sure it is
|
|
||||||
* not in "skip".
|
|
||||||
*/
|
*/
|
||||||
pos = search_ref_dir(dir, refname, slash - refname);
|
if (dir) {
|
||||||
if (pos >= 0) {
|
pos = search_ref_dir(dir, dirname.buf, dirname.len);
|
||||||
struct ref_entry *entry = dir->entries[pos];
|
if (pos >= 0 &&
|
||||||
if (entry_matches(entry, skip))
|
(!skip || !string_list_has_string(skip, dirname.buf))) {
|
||||||
return 1;
|
/*
|
||||||
report_refname_conflict(entry, refname);
|
* We found a reference whose name is
|
||||||
return 0;
|
* a proper prefix of refname; e.g.,
|
||||||
|
* "refs/foo", and is not in skip.
|
||||||
|
*/
|
||||||
|
strbuf_addf(err, "'%s' exists; cannot create '%s'",
|
||||||
|
dirname.buf, refname);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (extras && string_list_has_string(extras, dirname.buf) &&
|
||||||
|
(!skip || !string_list_has_string(skip, dirname.buf))) {
|
||||||
|
strbuf_addf(err, "cannot process '%s' and '%s' at the same time",
|
||||||
|
refname, dirname.buf);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Otherwise, we can try to continue our search with
|
* Otherwise, we can try to continue our search with
|
||||||
* the next component; if we come up empty, we know
|
* the next component. So try to look up the
|
||||||
* there is nothing under this whole prefix.
|
* directory, e.g., "refs/foo/". If we come up empty,
|
||||||
|
* we know there is nothing under this whole prefix,
|
||||||
|
* but even in that case we still have to continue the
|
||||||
|
* search for conflicts with extras.
|
||||||
*/
|
*/
|
||||||
pos = search_ref_dir(dir, refname, slash + 1 - refname);
|
strbuf_addch(&dirname, '/');
|
||||||
if (pos < 0)
|
if (dir) {
|
||||||
return 1;
|
pos = search_ref_dir(dir, dirname.buf, dirname.len);
|
||||||
|
if (pos < 0) {
|
||||||
dir = get_ref_dir(dir->entries[pos]);
|
/*
|
||||||
|
* There was no directory "refs/foo/",
|
||||||
|
* so there is nothing under this
|
||||||
|
* whole prefix. So there is no need
|
||||||
|
* to continue looking for conflicting
|
||||||
|
* references. But we need to continue
|
||||||
|
* looking for conflicting extras.
|
||||||
|
*/
|
||||||
|
dir = NULL;
|
||||||
|
} else {
|
||||||
|
dir = get_ref_dir(dir->entries[pos]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We are at the leaf of our refname; we want to
|
* We are at the leaf of our refname (e.g., "refs/foo/bar").
|
||||||
* make sure there are no directories which match it.
|
* There is no point in searching for a reference with that
|
||||||
|
* name, because a refname isn't considered to conflict with
|
||||||
|
* itself. But we still need to check for references whose
|
||||||
|
* names are in the "refs/foo/bar/" namespace, because they
|
||||||
|
* *do* conflict.
|
||||||
*/
|
*/
|
||||||
len = strlen(refname);
|
strbuf_addstr(&dirname, refname + dirname.len);
|
||||||
dirname = xmallocz(len + 1);
|
strbuf_addch(&dirname, '/');
|
||||||
sprintf(dirname, "%s/", refname);
|
|
||||||
pos = search_ref_dir(dir, dirname, len + 1);
|
|
||||||
free(dirname);
|
|
||||||
|
|
||||||
if (pos >= 0) {
|
if (dir) {
|
||||||
|
pos = search_ref_dir(dir, dirname.buf, dirname.len);
|
||||||
|
|
||||||
|
if (pos >= 0) {
|
||||||
|
/*
|
||||||
|
* We found a directory named "$refname/"
|
||||||
|
* (e.g., "refs/foo/bar/"). It is a problem
|
||||||
|
* iff it contains any ref that is not in
|
||||||
|
* "skip".
|
||||||
|
*/
|
||||||
|
struct nonmatching_ref_data data;
|
||||||
|
|
||||||
|
data.skip = skip;
|
||||||
|
data.conflicting_refname = NULL;
|
||||||
|
dir = get_ref_dir(dir->entries[pos]);
|
||||||
|
sort_ref_dir(dir);
|
||||||
|
if (do_for_each_entry_in_dir(dir, 0, nonmatching_ref_fn, &data)) {
|
||||||
|
strbuf_addf(err, "'%s' exists; cannot create '%s'",
|
||||||
|
data.conflicting_refname, refname);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extras) {
|
||||||
/*
|
/*
|
||||||
* We found a directory named "refname". It is a
|
* Check for entries in extras that start with
|
||||||
* problem iff it contains any ref that is not
|
* "$refname/". We do that by looking for the place
|
||||||
* in "skip".
|
* where "$refname/" would be inserted in extras. If
|
||||||
|
* there is an entry at that position that starts with
|
||||||
|
* "$refname/" and is not in skip, then we have a
|
||||||
|
* conflict.
|
||||||
*/
|
*/
|
||||||
struct ref_entry *entry = dir->entries[pos];
|
for (pos = string_list_find_insert_index(extras, dirname.buf, 0);
|
||||||
struct ref_dir *dir = get_ref_dir(entry);
|
pos < extras->nr; pos++) {
|
||||||
struct nonmatching_ref_data data;
|
const char *extra_refname = extras->items[pos].string;
|
||||||
|
|
||||||
data.skip = skip;
|
if (!starts_with(extra_refname, dirname.buf))
|
||||||
sort_ref_dir(dir);
|
break;
|
||||||
if (!do_for_each_entry_in_dir(dir, 0, nonmatching_ref_fn, &data))
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
report_refname_conflict(data.found, refname);
|
if (!skip || !string_list_has_string(skip, extra_refname)) {
|
||||||
return 0;
|
strbuf_addf(err, "cannot process '%s' and '%s' at the same time",
|
||||||
|
refname, extra_refname);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/* No conflicts were found */
|
||||||
* There is no point in searching for another leaf
|
ret = 0;
|
||||||
* node which matches it; such an entry would be the
|
|
||||||
* ref we are looking for, not a conflict.
|
cleanup:
|
||||||
*/
|
strbuf_release(&dirname);
|
||||||
return 1;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct packed_ref_cache {
|
struct packed_ref_cache {
|
||||||
|
@ -2294,8 +2354,10 @@ 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 *extras,
|
||||||
const struct string_list *skip,
|
const struct string_list *skip,
|
||||||
unsigned int flags, int *type_p)
|
unsigned int flags, int *type_p,
|
||||||
|
struct strbuf *err)
|
||||||
{
|
{
|
||||||
const char *ref_file;
|
const char *ref_file;
|
||||||
const char *orig_refname = refname;
|
const char *orig_refname = refname;
|
||||||
|
@ -2306,6 +2368,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
|
||||||
int resolve_flags = 0;
|
int resolve_flags = 0;
|
||||||
int attempts_remaining = 3;
|
int attempts_remaining = 3;
|
||||||
|
|
||||||
|
assert(err);
|
||||||
|
|
||||||
lock = xcalloc(1, sizeof(struct ref_lock));
|
lock = xcalloc(1, sizeof(struct ref_lock));
|
||||||
|
|
||||||
if (mustexist)
|
if (mustexist)
|
||||||
|
@ -2327,7 +2391,12 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
|
||||||
ref_file = git_path("%s", orig_refname);
|
ref_file = git_path("%s", orig_refname);
|
||||||
if (remove_empty_directories(ref_file)) {
|
if (remove_empty_directories(ref_file)) {
|
||||||
last_errno = errno;
|
last_errno = errno;
|
||||||
error("there are still refs under '%s'", orig_refname);
|
|
||||||
|
if (!verify_refname_available(orig_refname, extras, skip,
|
||||||
|
get_loose_refs(&ref_cache), err))
|
||||||
|
strbuf_addf(err, "there are still refs under '%s'",
|
||||||
|
orig_refname);
|
||||||
|
|
||||||
goto error_return;
|
goto error_return;
|
||||||
}
|
}
|
||||||
refname = resolve_ref_unsafe(orig_refname, resolve_flags,
|
refname = resolve_ref_unsafe(orig_refname, resolve_flags,
|
||||||
|
@ -2337,8 +2406,12 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
|
||||||
*type_p = type;
|
*type_p = type;
|
||||||
if (!refname) {
|
if (!refname) {
|
||||||
last_errno = errno;
|
last_errno = errno;
|
||||||
error("unable to resolve reference %s: %s",
|
if (last_errno != ENOTDIR ||
|
||||||
orig_refname, strerror(errno));
|
!verify_refname_available(orig_refname, extras, skip,
|
||||||
|
get_loose_refs(&ref_cache), err))
|
||||||
|
strbuf_addf(err, "unable to resolve reference %s: %s",
|
||||||
|
orig_refname, strerror(last_errno));
|
||||||
|
|
||||||
goto error_return;
|
goto error_return;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
|
@ -2348,7 +2421,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
|
||||||
* our refname.
|
* our refname.
|
||||||
*/
|
*/
|
||||||
if (is_null_sha1(lock->old_sha1) &&
|
if (is_null_sha1(lock->old_sha1) &&
|
||||||
!is_refname_available(refname, skip, get_packed_refs(&ref_cache))) {
|
verify_refname_available(refname, extras, skip,
|
||||||
|
get_packed_refs(&ref_cache), err)) {
|
||||||
last_errno = ENOTDIR;
|
last_errno = ENOTDIR;
|
||||||
goto error_return;
|
goto error_return;
|
||||||
}
|
}
|
||||||
|
@ -2374,7 +2448,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
|
||||||
/* fall through */
|
/* fall through */
|
||||||
default:
|
default:
|
||||||
last_errno = errno;
|
last_errno = errno;
|
||||||
error("unable to create directory for %s", ref_file);
|
strbuf_addf(err, "unable to create directory for %s", ref_file);
|
||||||
goto error_return;
|
goto error_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2388,10 +2462,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
|
||||||
*/
|
*/
|
||||||
goto retry;
|
goto retry;
|
||||||
else {
|
else {
|
||||||
struct strbuf err = STRBUF_INIT;
|
unable_to_lock_message(ref_file, errno, err);
|
||||||
unable_to_lock_message(ref_file, errno, &err);
|
|
||||||
error("%s", err.buf);
|
|
||||||
strbuf_release(&err);
|
|
||||||
goto error_return;
|
goto error_return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2785,12 +2856,19 @@ static int rename_tmp_log(const char *newrefname)
|
||||||
static int rename_ref_available(const char *oldname, const char *newname)
|
static int rename_ref_available(const char *oldname, const char *newname)
|
||||||
{
|
{
|
||||||
struct string_list skip = STRING_LIST_INIT_NODUP;
|
struct string_list skip = STRING_LIST_INIT_NODUP;
|
||||||
|
struct strbuf err = STRBUF_INIT;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
string_list_insert(&skip, oldname);
|
string_list_insert(&skip, oldname);
|
||||||
ret = is_refname_available(newname, &skip, get_packed_refs(&ref_cache))
|
ret = !verify_refname_available(newname, NULL, &skip,
|
||||||
&& is_refname_available(newname, &skip, get_loose_refs(&ref_cache));
|
get_packed_refs(&ref_cache), &err)
|
||||||
|
&& !verify_refname_available(newname, NULL, &skip,
|
||||||
|
get_loose_refs(&ref_cache), &err);
|
||||||
|
if (!ret)
|
||||||
|
error("%s", err.buf);
|
||||||
|
|
||||||
string_list_clear(&skip, 0);
|
string_list_clear(&skip, 0);
|
||||||
|
strbuf_release(&err);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2806,6 +2884,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
|
||||||
struct stat loginfo;
|
struct stat loginfo;
|
||||||
int log = !lstat(git_path("logs/%s", oldrefname), &loginfo);
|
int log = !lstat(git_path("logs/%s", oldrefname), &loginfo);
|
||||||
const char *symref = NULL;
|
const char *symref = NULL;
|
||||||
|
struct strbuf err = STRBUF_INIT;
|
||||||
|
|
||||||
if (log && S_ISLNK(loginfo.st_mode))
|
if (log && S_ISLNK(loginfo.st_mode))
|
||||||
return error("reflog for %s is a symlink", oldrefname);
|
return error("reflog for %s is a symlink", oldrefname);
|
||||||
|
@ -2848,9 +2927,10 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
|
||||||
|
|
||||||
logmoved = log;
|
logmoved = log;
|
||||||
|
|
||||||
lock = lock_ref_sha1_basic(newrefname, NULL, NULL, 0, NULL);
|
lock = lock_ref_sha1_basic(newrefname, NULL, NULL, NULL, 0, NULL, &err);
|
||||||
if (!lock) {
|
if (!lock) {
|
||||||
error("unable to lock %s for update", newrefname);
|
error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf);
|
||||||
|
strbuf_release(&err);
|
||||||
goto rollback;
|
goto rollback;
|
||||||
}
|
}
|
||||||
hashcpy(lock->old_sha1, orig_sha1);
|
hashcpy(lock->old_sha1, orig_sha1);
|
||||||
|
@ -2864,9 +2944,10 @@ 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, NULL, 0, NULL);
|
lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, NULL, 0, NULL, &err);
|
||||||
if (!lock) {
|
if (!lock) {
|
||||||
error("unable to lock %s for rollback", oldrefname);
|
error("unable to lock %s for rollback: %s", oldrefname, err.buf);
|
||||||
|
strbuf_release(&err);
|
||||||
goto rollbacklog;
|
goto rollbacklog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3746,25 +3827,18 @@ int update_ref(const char *msg, const char *refname,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ref_update_compare(const void *r1, const void *r2)
|
static int ref_update_reject_duplicates(struct string_list *refnames,
|
||||||
{
|
|
||||||
const struct ref_update * const *u1 = r1;
|
|
||||||
const struct ref_update * const *u2 = r2;
|
|
||||||
return strcmp((*u1)->refname, (*u2)->refname);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ref_update_reject_duplicates(struct ref_update **updates, int n,
|
|
||||||
struct strbuf *err)
|
struct strbuf *err)
|
||||||
{
|
{
|
||||||
int i;
|
int i, n = refnames->nr;
|
||||||
|
|
||||||
assert(err);
|
assert(err);
|
||||||
|
|
||||||
for (i = 1; i < n; i++)
|
for (i = 1; i < n; i++)
|
||||||
if (!strcmp(updates[i - 1]->refname, updates[i]->refname)) {
|
if (!strcmp(refnames->items[i - 1].string, refnames->items[i].string)) {
|
||||||
strbuf_addf(err,
|
strbuf_addf(err,
|
||||||
"Multiple updates for ref '%s' not allowed.",
|
"Multiple updates for ref '%s' not allowed.",
|
||||||
updates[i]->refname);
|
refnames->items[i].string);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -3778,6 +3852,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,
|
||||||
struct ref_update **updates = transaction->updates;
|
struct ref_update **updates = transaction->updates;
|
||||||
struct string_list refs_to_delete = STRING_LIST_INIT_NODUP;
|
struct string_list refs_to_delete = STRING_LIST_INIT_NODUP;
|
||||||
struct string_list_item *ref_to_delete;
|
struct string_list_item *ref_to_delete;
|
||||||
|
struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
|
||||||
|
|
||||||
assert(err);
|
assert(err);
|
||||||
|
|
||||||
|
@ -3789,9 +3864,11 @@ int ref_transaction_commit(struct ref_transaction *transaction,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Copy, sort, and reject duplicate refs */
|
/* Fail if a refname appears more than once in the transaction: */
|
||||||
qsort(updates, n, sizeof(*updates), ref_update_compare);
|
for (i = 0; i < n; i++)
|
||||||
if (ref_update_reject_duplicates(updates, n, err)) {
|
string_list_append(&affected_refnames, updates[i]->refname);
|
||||||
|
string_list_sort(&affected_refnames);
|
||||||
|
if (ref_update_reject_duplicates(&affected_refnames, err)) {
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
ret = TRANSACTION_GENERIC_ERROR;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
@ -3812,15 +3889,20 @@ int ref_transaction_commit(struct ref_transaction *transaction,
|
||||||
update->refname,
|
update->refname,
|
||||||
((update->flags & REF_HAVE_OLD) ?
|
((update->flags & REF_HAVE_OLD) ?
|
||||||
update->old_sha1 : NULL),
|
update->old_sha1 : NULL),
|
||||||
NULL,
|
&affected_refnames, NULL,
|
||||||
update->flags,
|
update->flags,
|
||||||
&update->type);
|
&update->type,
|
||||||
|
err);
|
||||||
if (!update->lock) {
|
if (!update->lock) {
|
||||||
|
char *reason;
|
||||||
|
|
||||||
ret = (errno == ENOTDIR)
|
ret = (errno == ENOTDIR)
|
||||||
? TRANSACTION_NAME_CONFLICT
|
? TRANSACTION_NAME_CONFLICT
|
||||||
: TRANSACTION_GENERIC_ERROR;
|
: TRANSACTION_GENERIC_ERROR;
|
||||||
strbuf_addf(err, "Cannot lock the ref '%s'.",
|
reason = strbuf_detach(err, NULL);
|
||||||
update->refname);
|
strbuf_addf(err, "Cannot lock ref '%s': %s",
|
||||||
|
update->refname, reason);
|
||||||
|
free(reason);
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
if ((update->flags & REF_HAVE_NEW) &&
|
if ((update->flags & REF_HAVE_NEW) &&
|
||||||
|
@ -3913,6 +3995,7 @@ cleanup:
|
||||||
if (updates[i]->lock)
|
if (updates[i]->lock)
|
||||||
unlock_ref(updates[i]->lock);
|
unlock_ref(updates[i]->lock);
|
||||||
string_list_clear(&refs_to_delete, 0);
|
string_list_clear(&refs_to_delete, 0);
|
||||||
|
string_list_clear(&affected_refnames, 0);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4102,6 +4185,7 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
|
||||||
char *log_file;
|
char *log_file;
|
||||||
int status = 0;
|
int status = 0;
|
||||||
int type;
|
int type;
|
||||||
|
struct strbuf err = STRBUF_INIT;
|
||||||
|
|
||||||
memset(&cb, 0, sizeof(cb));
|
memset(&cb, 0, sizeof(cb));
|
||||||
cb.flags = flags;
|
cb.flags = flags;
|
||||||
|
@ -4113,9 +4197,12 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
|
||||||
* reference itself, plus we might need to update the
|
* reference itself, plus we might need to update the
|
||||||
* reference if --updateref was specified:
|
* reference if --updateref was specified:
|
||||||
*/
|
*/
|
||||||
lock = lock_ref_sha1_basic(refname, sha1, NULL, 0, &type);
|
lock = lock_ref_sha1_basic(refname, sha1, NULL, NULL, 0, &type, &err);
|
||||||
if (!lock)
|
if (!lock) {
|
||||||
return error("cannot lock ref '%s'", refname);
|
error("cannot lock ref '%s': %s", refname, err.buf);
|
||||||
|
strbuf_release(&err);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
if (!reflog_exists(refname)) {
|
if (!reflog_exists(refname)) {
|
||||||
unlock_ref(lock);
|
unlock_ref(lock);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -519,7 +519,7 @@ test_expect_success 'stdin create ref works with path with space to blob' '
|
||||||
test_expect_success 'stdin update ref fails with wrong old value' '
|
test_expect_success 'stdin update ref fails with wrong old value' '
|
||||||
echo "update $c $m $m~1" >stdin &&
|
echo "update $c $m $m~1" >stdin &&
|
||||||
test_must_fail git update-ref --stdin <stdin 2>err &&
|
test_must_fail git update-ref --stdin <stdin 2>err &&
|
||||||
grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
|
grep "fatal: Cannot lock ref '"'"'$c'"'"'" err &&
|
||||||
test_must_fail git rev-parse --verify -q $c
|
test_must_fail git rev-parse --verify -q $c
|
||||||
'
|
'
|
||||||
|
|
||||||
|
@ -555,7 +555,7 @@ test_expect_success 'stdin update ref works with right old value' '
|
||||||
test_expect_success 'stdin delete ref fails with wrong old value' '
|
test_expect_success 'stdin delete ref fails with wrong old value' '
|
||||||
echo "delete $a $m~1" >stdin &&
|
echo "delete $a $m~1" >stdin &&
|
||||||
test_must_fail git update-ref --stdin <stdin 2>err &&
|
test_must_fail git update-ref --stdin <stdin 2>err &&
|
||||||
grep "fatal: Cannot lock the ref '"'"'$a'"'"'" err &&
|
grep "fatal: Cannot lock ref '"'"'$a'"'"'" err &&
|
||||||
git rev-parse $m >expect &&
|
git rev-parse $m >expect &&
|
||||||
git rev-parse $a >actual &&
|
git rev-parse $a >actual &&
|
||||||
test_cmp expect actual
|
test_cmp expect actual
|
||||||
|
@ -688,7 +688,7 @@ test_expect_success 'stdin update refs fails with wrong old value' '
|
||||||
update $c ''
|
update $c ''
|
||||||
EOF
|
EOF
|
||||||
test_must_fail git update-ref --stdin <stdin 2>err &&
|
test_must_fail git update-ref --stdin <stdin 2>err &&
|
||||||
grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
|
grep "fatal: Cannot lock ref '"'"'$c'"'"'" err &&
|
||||||
git rev-parse $m >expect &&
|
git rev-parse $m >expect &&
|
||||||
git rev-parse $a >actual &&
|
git rev-parse $a >actual &&
|
||||||
test_cmp expect actual &&
|
test_cmp expect actual &&
|
||||||
|
@ -883,7 +883,7 @@ test_expect_success 'stdin -z create ref works with path with space to blob' '
|
||||||
test_expect_success 'stdin -z update ref fails with wrong old value' '
|
test_expect_success 'stdin -z update ref fails with wrong old value' '
|
||||||
printf $F "update $c" "$m" "$m~1" >stdin &&
|
printf $F "update $c" "$m" "$m~1" >stdin &&
|
||||||
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
||||||
grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
|
grep "fatal: Cannot lock ref '"'"'$c'"'"'" err &&
|
||||||
test_must_fail git rev-parse --verify -q $c
|
test_must_fail git rev-parse --verify -q $c
|
||||||
'
|
'
|
||||||
|
|
||||||
|
@ -899,7 +899,7 @@ test_expect_success 'stdin -z create ref fails when ref exists' '
|
||||||
git rev-parse "$c" >expect &&
|
git rev-parse "$c" >expect &&
|
||||||
printf $F "create $c" "$m~1" >stdin &&
|
printf $F "create $c" "$m~1" >stdin &&
|
||||||
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
||||||
grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
|
grep "fatal: Cannot lock ref '"'"'$c'"'"'" err &&
|
||||||
git rev-parse "$c" >actual &&
|
git rev-parse "$c" >actual &&
|
||||||
test_cmp expect actual
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
@ -930,7 +930,7 @@ test_expect_success 'stdin -z update ref works with right old value' '
|
||||||
test_expect_success 'stdin -z delete ref fails with wrong old value' '
|
test_expect_success 'stdin -z delete ref fails with wrong old value' '
|
||||||
printf $F "delete $a" "$m~1" >stdin &&
|
printf $F "delete $a" "$m~1" >stdin &&
|
||||||
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
||||||
grep "fatal: Cannot lock the ref '"'"'$a'"'"'" err &&
|
grep "fatal: Cannot lock ref '"'"'$a'"'"'" err &&
|
||||||
git rev-parse $m >expect &&
|
git rev-parse $m >expect &&
|
||||||
git rev-parse $a >actual &&
|
git rev-parse $a >actual &&
|
||||||
test_cmp expect actual
|
test_cmp expect actual
|
||||||
|
@ -1045,7 +1045,7 @@ test_expect_success 'stdin -z update refs fails with wrong old value' '
|
||||||
git update-ref $c $m &&
|
git update-ref $c $m &&
|
||||||
printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "$m" "$Z" >stdin &&
|
printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "$m" "$Z" >stdin &&
|
||||||
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
test_must_fail git update-ref -z --stdin <stdin 2>err &&
|
||||||
grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
|
grep "fatal: Cannot lock ref '"'"'$c'"'"'" err &&
|
||||||
git rev-parse $m >expect &&
|
git rev-parse $m >expect &&
|
||||||
git rev-parse $a >actual &&
|
git rev-parse $a >actual &&
|
||||||
test_cmp expect actual &&
|
test_cmp expect actual &&
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='Test git update-ref with D/F conflicts'
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
test_update_rejected () {
|
||||||
|
prefix="$1" &&
|
||||||
|
before="$2" &&
|
||||||
|
pack="$3" &&
|
||||||
|
create="$4" &&
|
||||||
|
error="$5" &&
|
||||||
|
printf "create $prefix/%s $C\n" $before |
|
||||||
|
git update-ref --stdin &&
|
||||||
|
git for-each-ref $prefix >unchanged &&
|
||||||
|
if $pack
|
||||||
|
then
|
||||||
|
git pack-refs --all
|
||||||
|
fi &&
|
||||||
|
printf "create $prefix/%s $C\n" $create >input &&
|
||||||
|
test_must_fail git update-ref --stdin <input 2>output.err &&
|
||||||
|
grep -F "$error" output.err &&
|
||||||
|
git for-each-ref $prefix >actual &&
|
||||||
|
test_cmp unchanged actual
|
||||||
|
}
|
||||||
|
|
||||||
|
Q="'"
|
||||||
|
|
||||||
|
test_expect_success 'setup' '
|
||||||
|
|
||||||
|
git commit --allow-empty -m Initial &&
|
||||||
|
C=$(git rev-parse HEAD)
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'existing loose ref is a simple prefix of new' '
|
||||||
|
|
||||||
|
prefix=refs/1l &&
|
||||||
|
test_update_rejected $prefix "a c e" false "b c/x d" \
|
||||||
|
"$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x$Q"
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'existing packed ref is a simple prefix of new' '
|
||||||
|
|
||||||
|
prefix=refs/1p &&
|
||||||
|
test_update_rejected $prefix "a c e" true "b c/x d" \
|
||||||
|
"$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x$Q"
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'existing loose ref is a deeper prefix of new' '
|
||||||
|
|
||||||
|
prefix=refs/2l &&
|
||||||
|
test_update_rejected $prefix "a c e" false "b c/x/y d" \
|
||||||
|
"$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x/y$Q"
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'existing packed ref is a deeper prefix of new' '
|
||||||
|
|
||||||
|
prefix=refs/2p &&
|
||||||
|
test_update_rejected $prefix "a c e" true "b c/x/y d" \
|
||||||
|
"$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x/y$Q"
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'new ref is a simple prefix of existing loose' '
|
||||||
|
|
||||||
|
prefix=refs/3l &&
|
||||||
|
test_update_rejected $prefix "a c/x e" false "b c d" \
|
||||||
|
"$Q$prefix/c/x$Q exists; cannot create $Q$prefix/c$Q"
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'new ref is a simple prefix of existing packed' '
|
||||||
|
|
||||||
|
prefix=refs/3p &&
|
||||||
|
test_update_rejected $prefix "a c/x e" true "b c d" \
|
||||||
|
"$Q$prefix/c/x$Q exists; cannot create $Q$prefix/c$Q"
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'new ref is a deeper prefix of existing loose' '
|
||||||
|
|
||||||
|
prefix=refs/4l &&
|
||||||
|
test_update_rejected $prefix "a c/x/y e" false "b c d" \
|
||||||
|
"$Q$prefix/c/x/y$Q exists; cannot create $Q$prefix/c$Q"
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'new ref is a deeper prefix of existing packed' '
|
||||||
|
|
||||||
|
prefix=refs/4p &&
|
||||||
|
test_update_rejected $prefix "a c/x/y e" true "b c d" \
|
||||||
|
"$Q$prefix/c/x/y$Q exists; cannot create $Q$prefix/c$Q"
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'one new ref is a simple prefix of another' '
|
||||||
|
|
||||||
|
prefix=refs/5 &&
|
||||||
|
test_update_rejected $prefix "a e" false "b c c/x d" \
|
||||||
|
"cannot process $Q$prefix/c$Q and $Q$prefix/c/x$Q at the same time"
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
Загрузка…
Ссылка в новой задаче