summaryrefslogtreecommitdiff
path: root/refs.c
diff options
context:
space:
mode:
Diffstat (limited to 'refs.c')
-rw-r--r--refs.c1166
1 files changed, 747 insertions, 419 deletions
diff --git a/refs.c b/refs.c
index 5e5a3824b9..a77458f2f6 100644
--- a/refs.c
+++ b/refs.c
@@ -1,4 +1,5 @@
#include "cache.h"
+#include "lockfile.h"
#include "refs.h"
#include "object.h"
#include "tag.h"
@@ -6,8 +7,34 @@
#include "string-list.h"
/*
- * Make sure "ref" is something reasonable to have under ".git/refs/";
- * We do not like it if:
+ * How to handle various characters in refnames:
+ * 0: An acceptable character for refs
+ * 1: End-of-component
+ * 2: ., look for a preceding . to reject .. in refs
+ * 3: {, look for a preceding @ to reject @{ in refs
+ * 4: A bad character: ASCII control characters, "~", "^", ":" or SP
+ */
+static unsigned char refname_disposition[256] = {
+ 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 4, 4
+};
+
+/*
+ * Used as a flag to ref_transaction_delete when a loose ref is being
+ * pruned.
+ */
+#define REF_ISPRUNING 0x0100
+/*
+ * Try to read one refname component from the front of refname.
+ * Return the length of the component found, or -1 if the component is
+ * not legal. It is legal if it is something reasonable to have under
+ * ".git/refs/"; We do not like it if:
*
* - any path component of it begins with ".", or
* - it has double dots "..", or
@@ -16,41 +43,31 @@
* - it ends with ".lock"
* - it contains a "\" (backslash)
*/
-
-/* Return true iff ch is not allowed in reference names. */
-static inline int bad_ref_char(int ch)
-{
- if (((unsigned) ch) <= ' ' || ch == 0x7f ||
- ch == '~' || ch == '^' || ch == ':' || ch == '\\')
- return 1;
- /* 2.13 Pattern Matching Notation */
- if (ch == '*' || ch == '?' || ch == '[') /* Unsupported */
- return 1;
- return 0;
-}
-
-/*
- * Try to read one refname component from the front of refname. Return
- * the length of the component found, or -1 if the component is not
- * legal.
- */
static int check_refname_component(const char *refname, int flags)
{
const char *cp;
char last = '\0';
for (cp = refname; ; cp++) {
- char ch = *cp;
- if (ch == '\0' || ch == '/')
+ int ch = *cp & 255;
+ unsigned char disp = refname_disposition[ch];
+ switch (disp) {
+ case 1:
+ goto out;
+ case 2:
+ if (last == '.')
+ return -1; /* Refname contains "..". */
+ break;
+ case 3:
+ if (last == '@')
+ return -1; /* Refname contains "@{". */
break;
- if (bad_ref_char(ch))
- return -1; /* Illegal character in refname. */
- if (last == '.' && ch == '.')
- return -1; /* Refname contains "..". */
- if (last == '@' && ch == '{')
- return -1; /* Refname contains "@{". */
+ case 4:
+ return -1;
+ }
last = ch;
}
+out:
if (cp == refname)
return 0; /* Component has zero length. */
if (refname[0] == '.') {
@@ -63,7 +80,8 @@ static int check_refname_component(const char *refname, int flags)
if (refname[1] == '\0')
return -1; /* Component equals ".". */
}
- if (cp - refname >= 5 && !memcmp(cp - 5, ".lock", 5))
+ if (cp - refname >= LOCK_SUFFIX_LEN &&
+ !memcmp(cp - LOCK_SUFFIX_LEN, LOCK_SUFFIX, LOCK_SUFFIX_LEN))
return -1; /* Refname ends with ".lock". */
return cp - refname;
}
@@ -637,7 +655,7 @@ static int do_one_ref(struct ref_entry *entry, void *cb_data)
struct ref_entry *old_current_ref;
int retval;
- if (prefixcmp(entry->name, data->base))
+ if (!starts_with(entry->name, data->base))
return 0;
if (!(data->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
@@ -768,37 +786,32 @@ static void prime_ref_dir(struct ref_dir *dir)
prime_ref_dir(get_ref_dir(entry));
}
}
-/*
- * Return true iff refname1 and refname2 conflict with each other.
- * 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)
+
+static int entry_matches(struct ref_entry *entry, const char *refname)
{
- for (; *refname1 && *refname1 == *refname2; refname1++, refname2++)
- ;
- return (*refname1 == '\0' && *refname2 == '/')
- || (*refname1 == '/' && *refname2 == '\0');
+ return refname && !strcmp(entry->name, refname);
}
-struct name_conflict_cb {
- const char *refname;
- const char *oldrefname;
- const char *conflicting_refname;
+struct nonmatching_ref_data {
+ const char *skip;
+ struct ref_entry *found;
};
-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;
- if (data->oldrefname && !strcmp(data->oldrefname, entry->name))
+ struct nonmatching_ref_data *data = vdata;
+
+ if (entry_matches(entry, data->skip))
return 0;
- if (names_conflict(data->refname, entry->name)) {
- data->conflicting_refname = entry->name;
- return 1;
- }
- return 0;
+
+ data->found = entry;
+ return 1;
+}
+
+static void report_refname_conflict(struct ref_entry *entry,
+ const char *refname)
+{
+ error("'%s' exists; cannot create '%s'", entry->name, refname);
}
/*
@@ -807,21 +820,84 @@ static int name_conflict_fn(struct ref_entry *entry, void *cb_data)
* oldrefname is non-NULL, ignore potential conflicts with oldrefname
* (e.g., because oldrefname is scheduled for deletion in the same
* 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,
struct ref_dir *dir)
{
- struct name_conflict_cb data;
- data.refname = refname;
- data.oldrefname = oldrefname;
- data.conflicting_refname = NULL;
+ const char *slash;
+ size_t len;
+ int pos;
+ char *dirname;
- sort_ref_dir(dir);
- if (do_for_each_entry_in_dir(dir, 0, name_conflict_fn, &data)) {
- error("'%s' exists; cannot create '%s'",
- data.conflicting_refname, refname);
+ for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) {
+ /*
+ * We are still at a leading dir of the refname; we are
+ * 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;
+ }
+
+
+ /*
+ * 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;
}
@@ -1042,7 +1118,7 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
if (refname) {
last = create_ref_entry(refname, sha1, REF_ISPACKED, 1);
if (peeled == PEELED_FULLY ||
- (peeled == PEELED_TAGS && !prefixcmp(refname, "refs/tags/")))
+ (peeled == PEELED_TAGS && starts_with(refname, "refs/tags/")))
last->flag |= REF_KNOWS_PEELED;
add_ref(dir, last);
continue;
@@ -1151,7 +1227,7 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)
if (de->d_name[0] == '.')
continue;
- if (has_extension(de->d_name, ".lock"))
+ if (ends_with(de->d_name, ".lock"))
continue;
strbuf_addstr(&refname, de->d_name);
refdir = *refs->name
@@ -1222,7 +1298,7 @@ static int resolve_gitlink_packed_ref(struct ref_cache *refs,
if (ref == NULL)
return -1;
- memcpy(sha1, ref->u.value.sha1, 20);
+ hashcpy(sha1, ref->u.value.sha1);
return 0;
}
@@ -1323,6 +1399,7 @@ static const char *handle_missing_loose_ref(const char *refname,
}
}
+/* This function needs to return a meaningful errno on failure */
const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)
{
int depth = MAXDEPTH;
@@ -1333,8 +1410,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
if (flag)
*flag = 0;
- if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
+ if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+ errno = EINVAL;
return NULL;
+ }
for (;;) {
char path[PATH_MAX];
@@ -1342,8 +1421,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
char *buf;
int fd;
- if (--depth < 0)
+ if (--depth < 0) {
+ errno = ELOOP;
return NULL;
+ }
git_snpath(path, sizeof(path), "%s", refname);
@@ -1376,7 +1457,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
return NULL;
}
buffer[len] = 0;
- if (!prefixcmp(buffer, "refs/") &&
+ if (starts_with(buffer, "refs/") &&
!check_refname_format(buffer, 0)) {
strcpy(refname_buffer, buffer);
refname = refname_buffer;
@@ -1405,9 +1486,13 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
return NULL;
}
len = read_in_full(fd, buffer, sizeof(buffer)-1);
- close(fd);
- if (len < 0)
+ if (len < 0) {
+ int save_errno = errno;
+ close(fd);
+ errno = save_errno;
return NULL;
+ }
+ close(fd);
while (len && isspace(buffer[len-1]))
len--;
buffer[len] = '\0';
@@ -1415,7 +1500,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
/*
* Is it a symbolic ref?
*/
- if (prefixcmp(buffer, "ref:")) {
+ if (!starts_with(buffer, "ref:")) {
/*
* Please note that FETCH_HEAD has a second
* line containing other data.
@@ -1424,6 +1509,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
(buffer[40] != '\0' && !isspace(buffer[40]))) {
if (flag)
*flag |= REF_ISBROKEN;
+ errno = EINVAL;
return NULL;
}
return refname;
@@ -1436,6 +1522,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
if (flag)
*flag |= REF_ISBROKEN;
+ errno = EINVAL;
return NULL;
}
refname = strcpy(refname_buffer, buf);
@@ -1477,7 +1564,7 @@ static int filter_refs(const char *refname, const unsigned char *sha1, int flags
void *data)
{
struct ref_filter *filter = (struct ref_filter *)data;
- if (fnmatch(filter->pattern, refname, 0))
+ if (wildmatch(filter->pattern, refname, 0, NULL))
return 0;
return filter->fn(refname, sha1, flags, filter->cb_data);
}
@@ -1520,9 +1607,8 @@ static enum peel_status peel_object(const unsigned char *name, unsigned char *sh
if (o->type == OBJ_NONE) {
int type = sha1_object_info(name, NULL);
- if (type < 0)
+ if (type < 0 || !object_as_type(o, type, 0))
return PEEL_INVALID;
- o->type = type;
}
if (o->type != OBJ_TAG)
@@ -1611,6 +1697,7 @@ int peel_ref(const char *refname, unsigned char *sha1)
struct warn_if_dangling_data {
FILE *fp;
const char *refname;
+ const struct string_list *refnames;
const char *msg_fmt;
};
@@ -1625,8 +1712,12 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha
return 0;
resolves_to = resolve_ref_unsafe(refname, junk, 0, NULL);
- if (!resolves_to || strcmp(resolves_to, d->refname))
+ if (!resolves_to
+ || (d->refname
+ ? strcmp(resolves_to, d->refname)
+ : !string_list_has_string(d->refnames, resolves_to))) {
return 0;
+ }
fprintf(d->fp, d->msg_fmt, refname);
fputc('\n', d->fp);
@@ -1639,6 +1730,18 @@ void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
data.fp = fp;
data.refname = refname;
+ data.refnames = NULL;
+ data.msg_fmt = msg_fmt;
+ for_each_rawref(warn_if_dangling_symref, &data);
+}
+
+void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_list *refnames)
+{
+ struct warn_if_dangling_data data;
+
+ data.fp = fp;
+ data.refname = NULL;
+ data.refnames = refnames;
data.msg_fmt = msg_fmt;
for_each_rawref(warn_if_dangling_symref, &data);
}
@@ -1837,7 +1940,7 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
struct ref_filter filter;
int ret;
- if (!prefix && prefixcmp(pattern, "refs/"))
+ if (!prefix && !starts_with(pattern, "refs/"))
strbuf_addstr(&real_pattern, "refs/");
else if (prefix)
strbuf_addstr(&real_pattern, prefix);
@@ -1874,13 +1977,13 @@ int for_each_rawref(each_ref_fn fn, void *cb_data)
const char *prettify_refname(const char *name)
{
return name + (
- !prefixcmp(name, "refs/heads/") ? 11 :
- !prefixcmp(name, "refs/tags/") ? 10 :
- !prefixcmp(name, "refs/remotes/") ? 13 :
+ starts_with(name, "refs/heads/") ? 11 :
+ starts_with(name, "refs/tags/") ? 10 :
+ starts_with(name, "refs/remotes/") ? 13 :
0);
}
-const char *ref_rev_parse_rules[] = {
+static const char *ref_rev_parse_rules[] = {
"%.*s",
"refs/%.*s",
"refs/tags/%.*s",
@@ -1890,12 +1993,12 @@ const char *ref_rev_parse_rules[] = {
NULL
};
-int refname_match(const char *abbrev_name, const char *full_name, const char **rules)
+int refname_match(const char *abbrev_name, const char *full_name)
{
const char **p;
const int abbrev_name_len = strlen(abbrev_name);
- for (p = rules; *p; p++) {
+ for (p = ref_rev_parse_rules; *p; p++) {
if (!strcmp(full_name, mkpath(*p, abbrev_name_len, abbrev_name))) {
return 1;
}
@@ -1904,18 +2007,22 @@ int refname_match(const char *abbrev_name, const char *full_name, const char **r
return 0;
}
+/* This function should make sure errno is meaningful on error */
static struct ref_lock *verify_lock(struct ref_lock *lock,
const unsigned char *old_sha1, int mustexist)
{
if (read_ref_full(lock->ref_name, lock->old_sha1, mustexist, NULL)) {
+ int save_errno = errno;
error("Can't verify ref %s", lock->ref_name);
unlock_ref(lock);
+ errno = save_errno;
return NULL;
}
if (hashcmp(lock->old_sha1, old_sha1)) {
error("Ref %s is at %s but expected %s", lock->ref_name,
sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1));
unlock_ref(lock);
+ errno = EBUSY;
return NULL;
}
return lock;
@@ -1928,14 +2035,16 @@ static int remove_empty_directories(const char *file)
* only empty directories), remove them.
*/
struct strbuf path;
- int result;
+ int result, save_errno;
strbuf_init(&path, 20);
strbuf_addstr(&path, file);
result = remove_dir_recursively(&path, REMOVE_DIR_EMPTY_ONLY);
+ save_errno = errno;
strbuf_release(&path);
+ errno = save_errno;
return result;
}
@@ -1999,7 +2108,6 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
*log = NULL;
for (p = ref_rev_parse_rules; *p; p++) {
- struct stat st;
unsigned char hash[20];
char path[PATH_MAX];
const char *ref, *it;
@@ -2008,12 +2116,9 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
ref = resolve_ref_unsafe(path, hash, 1, NULL);
if (!ref)
continue;
- if (!stat(git_path("logs/%s", path), &st) &&
- S_ISREG(st.st_mode))
+ if (reflog_exists(path))
it = path;
- else if (strcmp(ref, path) &&
- !stat(git_path("logs/%s", ref), &st) &&
- S_ISREG(st.st_mode))
+ else if (strcmp(ref, path) && reflog_exists(ref))
it = ref;
else
continue;
@@ -2028,6 +2133,10 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
return logs_found;
}
+/*
+ * Locks a "refs/" ref returning the lock on success and NULL on failure.
+ * On failure errno is set to something meaningful.
+ */
static struct ref_lock *lock_ref_sha1_basic(const char *refname,
const unsigned char *old_sha1,
int flags, int *type_p)
@@ -2039,6 +2148,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
int type, lflags;
int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
int missing = 0;
+ int attempts_remaining = 3;
lock = xcalloc(1, sizeof(struct ref_lock));
lock->lock_fd = -1;
@@ -2080,10 +2190,10 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
lock->lk = xcalloc(1, sizeof(struct lock_file));
- lflags = LOCK_DIE_ON_ERROR;
+ lflags = 0;
if (flags & REF_NODEREF) {
refname = orig_refname;
- lflags |= LOCK_NODEREF;
+ lflags |= LOCK_NO_DEREF;
}
lock->ref_name = xstrdup(refname);
lock->orig_ref_name = xstrdup(orig_refname);
@@ -2093,13 +2203,32 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
if ((flags & REF_NODEREF) && (type & REF_ISSYMREF))
lock->force_write = 1;
- if (safe_create_leading_directories(ref_file)) {
+ retry:
+ switch (safe_create_leading_directories(ref_file)) {
+ case SCLD_OK:
+ break; /* success */
+ case SCLD_VANISHED:
+ if (--attempts_remaining > 0)
+ goto retry;
+ /* fall through */
+ default:
last_errno = errno;
error("unable to create directory for %s", ref_file);
goto error_return;
}
lock->lock_fd = hold_lock_file_for_update(lock->lk, ref_file, lflags);
+ if (lock->lock_fd < 0) {
+ if (errno == ENOENT && --attempts_remaining > 0)
+ /*
+ * Maybe somebody just deleted one of the
+ * directories leading to ref_file. Try
+ * again:
+ */
+ goto retry;
+ else
+ unable_to_lock_die(ref_file, errno);
+ }
return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
error_return:
@@ -2108,15 +2237,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
return NULL;
}
-struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1)
-{
- char refpath[PATH_MAX];
- if (check_refname_format(refname, 0))
- return NULL;
- strcpy(refpath, mkpath("refs/%s", refname));
- return lock_ref_sha1_basic(refpath, old_sha1, 0, NULL);
-}
-
struct ref_lock *lock_any_ref_for_update(const char *refname,
const unsigned char *old_sha1,
int flags, int *type_p)
@@ -2130,25 +2250,12 @@ struct ref_lock *lock_any_ref_for_update(const char *refname,
* Write an entry to the packed-refs file for the specified refname.
* If peeled is non-NULL, write it as the entry's peeled value.
*/
-static void write_packed_entry(int fd, char *refname, unsigned char *sha1,
+static void write_packed_entry(FILE *fh, char *refname, unsigned char *sha1,
unsigned char *peeled)
{
- char line[PATH_MAX + 100];
- int len;
-
- len = snprintf(line, sizeof(line), "%s %s\n",
- sha1_to_hex(sha1), refname);
- /* this should not happen but just being defensive */
- if (len > sizeof(line))
- die("too long a refname '%s'", refname);
- write_or_die(fd, line, len);
-
- if (peeled) {
- if (snprintf(line, sizeof(line), "^%s\n",
- sha1_to_hex(peeled)) != PEELED_LINE_LENGTH)
- die("internal error");
- write_or_die(fd, line, PEELED_LINE_LENGTH);
- }
+ fprintf_or_die(fh, "%s %s\n", sha1_to_hex(sha1), refname);
+ if (peeled)
+ fprintf_or_die(fh, "^%s\n", sha1_to_hex(peeled));
}
/*
@@ -2156,18 +2263,18 @@ static void write_packed_entry(int fd, char *refname, unsigned char *sha1,
*/
static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data)
{
- int *fd = cb_data;
enum peel_status peel_status = peel_entry(entry, 0);
if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG)
error("internal error: %s is not a valid packed reference!",
entry->name);
- write_packed_entry(*fd, entry->name, entry->u.value.sha1,
+ write_packed_entry(cb_data, entry->name, entry->u.value.sha1,
peel_status == PEEL_PEELED ?
entry->u.value.peeled : NULL);
return 0;
}
+/* This should return a meaningful errno on failure */
int lock_packed_refs(int flags)
{
struct packed_ref_cache *packed_ref_cache;
@@ -2187,24 +2294,36 @@ int lock_packed_refs(int flags)
return 0;
}
+/*
+ * Commit the packed refs changes.
+ * On error we must make sure that errno contains a meaningful value.
+ */
int commit_packed_refs(void)
{
struct packed_ref_cache *packed_ref_cache =
get_packed_ref_cache(&ref_cache);
int error = 0;
+ int save_errno = 0;
+ FILE *out;
if (!packed_ref_cache->lock)
die("internal error: packed-refs not locked");
- write_or_die(packed_ref_cache->lock->fd,
- PACKED_REFS_HEADER, strlen(PACKED_REFS_HEADER));
+ out = fdopen_lock_file(packed_ref_cache->lock, "w");
+ if (!out)
+ die_errno("unable to fdopen packed-refs descriptor");
+
+ fprintf_or_die(out, "%s", PACKED_REFS_HEADER);
do_for_each_entry_in_dir(get_packed_ref_dir(packed_ref_cache),
- 0, write_packed_entry_fn,
- &packed_ref_cache->lock->fd);
- if (commit_lock_file(packed_ref_cache->lock))
+ 0, write_packed_entry_fn, out);
+
+ if (commit_lock_file(packed_ref_cache->lock)) {
+ save_errno = errno;
error = -1;
+ }
packed_ref_cache->lock = NULL;
release_packed_ref_cache(packed_ref_cache);
+ errno = save_errno;
return error;
}
@@ -2244,7 +2363,7 @@ static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data)
struct pack_refs_cb_data *cb = cb_data;
enum peel_status peel_status;
struct ref_entry *packed_entry;
- int is_tag_ref = !prefixcmp(entry->name, "refs/tags/");
+ int is_tag_ref = starts_with(entry->name, "refs/tags/");
/* ALWAYS pack tags */
if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref)
@@ -2317,13 +2436,25 @@ static void try_remove_empty_parents(char *name)
/* make sure nobody touched the ref, and unlink */
static void prune_ref(struct ref_to_prune *r)
{
- struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
- if (lock) {
- unlink_or_warn(git_path("%s", r->name));
- unlock_ref(lock);
- try_remove_empty_parents(r->name);
+ if (check_refname_format(r->name, 0))
+ return;
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_delete(transaction, r->name, r->sha1,
+ REF_ISPRUNING, 1, &err) ||
+ ref_transaction_commit(transaction, NULL, &err)) {
+ ref_transaction_free(transaction);
+ error("%s", err.buf);
+ strbuf_release(&err);
+ return;
}
+ ref_transaction_free(transaction);
+ strbuf_release(&err);
+ try_remove_empty_parents(r->name);
}
static void prune_refs(struct ref_to_prune *r)
@@ -2411,12 +2542,12 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)
return 0;
}
-static int repack_without_refs(const char **refnames, int n)
+int repack_without_refs(const char **refnames, int n, struct strbuf *err)
{
struct ref_dir *packed;
struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
struct string_list_item *ref_to_delete;
- int i, removed = 0;
+ int i, ret, removed = 0;
/* Look for a packed ref */
for (i = 0; i < n; i++)
@@ -2428,6 +2559,11 @@ static int repack_without_refs(const char **refnames, int n)
return 0; /* no refname exists in packed refs */
if (lock_packed_refs(0)) {
+ if (err) {
+ unable_to_lock_message(git_path("packed-refs"), errno,
+ err);
+ return -1;
+ }
unable_to_lock_error(git_path("packed-refs"), errno);
return error("cannot delete '%s' from packed refs", refnames[i]);
}
@@ -2454,23 +2590,23 @@ static int repack_without_refs(const char **refnames, int n)
}
/* Write what remains */
- return commit_packed_refs();
-}
-
-static int repack_without_ref(const char *refname)
-{
- return repack_without_refs(&refname, 1);
+ ret = commit_packed_refs();
+ if (ret && err)
+ strbuf_addf(err, "unable to overwrite old ref-pack file: %s",
+ strerror(errno));
+ return ret;
}
static int delete_ref_loose(struct ref_lock *lock, int flag)
{
if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
- /* loose */
- int err, i = strlen(lock->lk->filename) - 5; /* .lock */
-
- lock->lk->filename[i] = 0;
- err = unlink_or_warn(lock->lk->filename);
- lock->lk->filename[i] = '.';
+ /*
+ * loose. The loose file name is the same as the
+ * lockfile name, minus ".lock":
+ */
+ char *loose_filename = get_locked_file_path(lock->lk);
+ int err = unlink_or_warn(loose_filename);
+ free(loose_filename);
if (err && errno != ENOENT)
return 1;
}
@@ -2479,24 +2615,22 @@ static int delete_ref_loose(struct ref_lock *lock, int flag)
int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
{
- struct ref_lock *lock;
- int ret = 0, flag = 0;
-
- lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);
- if (!lock)
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_delete(transaction, refname, sha1, delopt,
+ sha1 && !is_null_sha1(sha1), &err) ||
+ ref_transaction_commit(transaction, NULL, &err)) {
+ error("%s", err.buf);
+ ref_transaction_free(transaction);
+ strbuf_release(&err);
return 1;
- ret |= delete_ref_loose(lock, flag);
-
- /* removing the loose one could have resurrected an earlier
- * packed one. Also, if it was not loose we need to repack
- * without it.
- */
- ret |= repack_without_ref(lock->ref_name);
-
- unlink_or_warn(git_path("logs/%s", lock->ref_name));
- clear_loose_ref_cache(&ref_cache);
- unlock_ref(lock);
- return ret;
+ }
+ ref_transaction_free(transaction);
+ strbuf_release(&err);
+ return 0;
}
/*
@@ -2508,6 +2642,51 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
*/
#define TMP_RENAMED_LOG "logs/refs/.tmp-renamed-log"
+static int rename_tmp_log(const char *newrefname)
+{
+ int attempts_remaining = 4;
+
+ retry:
+ switch (safe_create_leading_directories(git_path("logs/%s", newrefname))) {
+ case SCLD_OK:
+ break; /* success */
+ case SCLD_VANISHED:
+ if (--attempts_remaining > 0)
+ goto retry;
+ /* fall through */
+ default:
+ error("unable to create directory for %s", newrefname);
+ return -1;
+ }
+
+ if (rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newrefname))) {
+ if ((errno==EISDIR || errno==ENOTDIR) && --attempts_remaining > 0) {
+ /*
+ * rename(a, b) when b is an existing
+ * directory ought to result in ISDIR, but
+ * Solaris 5.8 gives ENOTDIR. Sheesh.
+ */
+ if (remove_empty_directories(git_path("logs/%s", newrefname))) {
+ error("Directory not empty: logs/%s", newrefname);
+ return -1;
+ }
+ goto retry;
+ } else if (errno == ENOENT && --attempts_remaining > 0) {
+ /*
+ * Maybe another process just deleted one of
+ * the directories in the path to newrefname.
+ * Try again from the beginning.
+ */
+ goto retry;
+ } else {
+ error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s",
+ newrefname, strerror(errno));
+ return -1;
+ }
+ }
+ return 0;
+}
+
int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)
{
unsigned char sha1[20], orig_sha1[20];
@@ -2555,30 +2734,9 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
}
}
- if (log && safe_create_leading_directories(git_path("logs/%s", newrefname))) {
- error("unable to create directory for %s", newrefname);
+ if (log && rename_tmp_log(newrefname))
goto rollback;
- }
- retry:
- if (log && rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newrefname))) {
- if (errno==EISDIR || errno==ENOTDIR) {
- /*
- * rename(a, b) when b is an existing
- * directory ought to result in ISDIR, but
- * Solaris 5.8 gives ENOTDIR. Sheesh.
- */
- if (remove_empty_directories(git_path("logs/%s", newrefname))) {
- error("Directory not empty: logs/%s", newrefname);
- goto rollback;
- }
- goto retry;
- } else {
- error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s",
- newrefname, strerror(errno));
- goto rollback;
- }
- }
logmoved = log;
lock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL);
@@ -2673,19 +2831,23 @@ static int copy_msg(char *buf, const char *msg)
return cp - buf;
}
+/* This function must set a meaningful errno on failure */
int log_ref_setup(const char *refname, char *logfile, int bufsize)
{
int logfd, oflags = O_APPEND | O_WRONLY;
git_snpath(logfile, bufsize, "logs/%s", refname);
if (log_all_ref_updates &&
- (!prefixcmp(refname, "refs/heads/") ||
- !prefixcmp(refname, "refs/remotes/") ||
- !prefixcmp(refname, "refs/notes/") ||
+ (starts_with(refname, "refs/heads/") ||
+ starts_with(refname, "refs/remotes/") ||
+ starts_with(refname, "refs/notes/") ||
!strcmp(refname, "HEAD"))) {
- if (safe_create_leading_directories(logfile) < 0)
- return error("unable to create directory for %s",
- logfile);
+ if (safe_create_leading_directories(logfile) < 0) {
+ int save_errno = errno;
+ error("unable to create directory for %s", logfile);
+ errno = save_errno;
+ return -1;
+ }
oflags |= O_CREAT;
}
@@ -2696,15 +2858,22 @@ int log_ref_setup(const char *refname, char *logfile, int bufsize)
if ((oflags & O_CREAT) && errno == EISDIR) {
if (remove_empty_directories(logfile)) {
- return error("There are still logs under '%s'",
- logfile);
+ int save_errno = errno;
+ error("There are still logs under '%s'",
+ logfile);
+ errno = save_errno;
+ return -1;
}
logfd = open(logfile, oflags, 0666);
}
- if (logfd < 0)
- return error("Unable to append to %s: %s",
- logfile, strerror(errno));
+ if (logfd < 0) {
+ int save_errno = errno;
+ error("Unable to append to %s: %s", logfile,
+ strerror(errno));
+ errno = save_errno;
+ return -1;
+ }
}
adjust_shared_perm(logfile);
@@ -2744,24 +2913,38 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,
len += copy_msg(logrec + len - 1, msg) - 1;
written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1;
free(logrec);
- if (close(logfd) != 0 || written != len)
- return error("Unable to append to %s", log_file);
+ if (written != len) {
+ int save_errno = errno;
+ close(logfd);
+ error("Unable to append to %s", log_file);
+ errno = save_errno;
+ return -1;
+ }
+ if (close(logfd)) {
+ int save_errno = errno;
+ error("Unable to append to %s", log_file);
+ errno = save_errno;
+ return -1;
+ }
return 0;
}
-static int is_branch(const char *refname)
+int is_branch(const char *refname)
{
- return !strcmp(refname, "HEAD") || !prefixcmp(refname, "refs/heads/");
+ return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/");
}
+/* This function must return a meaningful errno */
int write_ref_sha1(struct ref_lock *lock,
const unsigned char *sha1, const char *logmsg)
{
static char term = '\n';
struct object *o;
- if (!lock)
+ if (!lock) {
+ errno = EINVAL;
return -1;
+ }
if (!lock->force_write && !hashcmp(lock->old_sha1, sha1)) {
unlock_ref(lock);
return 0;
@@ -2771,19 +2954,23 @@ int write_ref_sha1(struct ref_lock *lock,
error("Trying to write ref %s with nonexistent object %s",
lock->ref_name, sha1_to_hex(sha1));
unlock_ref(lock);
+ errno = EINVAL;
return -1;
}
if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) {
error("Trying to write non-commit object %s to branch %s",
sha1_to_hex(sha1), lock->ref_name);
unlock_ref(lock);
+ errno = EINVAL;
return -1;
}
if (write_in_full(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||
- write_in_full(lock->lock_fd, &term, 1) != 1
- || close_ref(lock) < 0) {
- error("Couldn't write %s", lock->lk->filename);
+ write_in_full(lock->lock_fd, &term, 1) != 1 ||
+ close_ref(lock) < 0) {
+ int save_errno = errno;
+ error("Couldn't write %s", lock->lk->filename.buf);
unlock_ref(lock);
+ errno = save_errno;
return -1;
}
clear_loose_ref_cache(&ref_cache);
@@ -2886,122 +3073,137 @@ int create_symref(const char *ref_target, const char *refs_heads_master,
return 0;
}
-static char *ref_msg(const char *line, const char *endp)
-{
- const char *ep;
- line += 82;
- ep = memchr(line, '\n', endp - line);
- if (!ep)
- ep = endp;
- return xmemdupz(line, ep - line);
+struct read_ref_at_cb {
+ const char *refname;
+ unsigned long at_time;
+ int cnt;
+ int reccnt;
+ unsigned char *sha1;
+ int found_it;
+
+ unsigned char osha1[20];
+ unsigned char nsha1[20];
+ int tz;
+ unsigned long date;
+ char **msg;
+ unsigned long *cutoff_time;
+ int *cutoff_tz;
+ int *cutoff_cnt;
+};
+
+static int read_ref_at_ent(unsigned char *osha1, unsigned char *nsha1,
+ const char *email, unsigned long timestamp, int tz,
+ const char *message, void *cb_data)
+{
+ struct read_ref_at_cb *cb = cb_data;
+
+ cb->reccnt++;
+ cb->tz = tz;
+ cb->date = timestamp;
+
+ if (timestamp <= cb->at_time || cb->cnt == 0) {
+ if (cb->msg)
+ *cb->msg = xstrdup(message);
+ if (cb->cutoff_time)
+ *cb->cutoff_time = timestamp;
+ if (cb->cutoff_tz)
+ *cb->cutoff_tz = tz;
+ if (cb->cutoff_cnt)
+ *cb->cutoff_cnt = cb->reccnt - 1;
+ /*
+ * we have not yet updated cb->[n|o]sha1 so they still
+ * hold the values for the previous record.
+ */
+ if (!is_null_sha1(cb->osha1)) {
+ hashcpy(cb->sha1, nsha1);
+ if (hashcmp(cb->osha1, nsha1))
+ warning("Log for ref %s has gap after %s.",
+ cb->refname, show_date(cb->date, cb->tz, DATE_RFC2822));
+ }
+ else if (cb->date == cb->at_time)
+ hashcpy(cb->sha1, nsha1);
+ else if (hashcmp(nsha1, cb->sha1))
+ warning("Log for ref %s unexpectedly ended on %s.",
+ cb->refname, show_date(cb->date, cb->tz,
+ DATE_RFC2822));
+ hashcpy(cb->osha1, osha1);
+ hashcpy(cb->nsha1, nsha1);
+ cb->found_it = 1;
+ return 1;
+ }
+ hashcpy(cb->osha1, osha1);
+ hashcpy(cb->nsha1, nsha1);
+ if (cb->cnt > 0)
+ cb->cnt--;
+ return 0;
+}
+
+static int read_ref_at_ent_oldest(unsigned char *osha1, unsigned char *nsha1,
+ const char *email, unsigned long timestamp,
+ int tz, const char *message, void *cb_data)
+{
+ struct read_ref_at_cb *cb = cb_data;
+
+ if (cb->msg)
+ *cb->msg = xstrdup(message);
+ if (cb->cutoff_time)
+ *cb->cutoff_time = timestamp;
+ if (cb->cutoff_tz)
+ *cb->cutoff_tz = tz;
+ if (cb->cutoff_cnt)
+ *cb->cutoff_cnt = cb->reccnt;
+ hashcpy(cb->sha1, osha1);
+ if (is_null_sha1(cb->sha1))
+ hashcpy(cb->sha1, nsha1);
+ /* We just want the first entry */
+ return 1;
}
-int read_ref_at(const char *refname, unsigned long at_time, int cnt,
+int read_ref_at(const char *refname, unsigned int flags, unsigned long at_time, int cnt,
unsigned char *sha1, char **msg,
unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
{
- const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
- char *tz_c;
- int logfd, tz, reccnt = 0;
- struct stat st;
- unsigned long date;
- unsigned char logged_sha1[20];
- void *log_mapped;
- size_t mapsz;
+ struct read_ref_at_cb cb;
- logfile = git_path("logs/%s", refname);
- logfd = open(logfile, O_RDONLY, 0);
- if (logfd < 0)
- die_errno("Unable to read log '%s'", logfile);
- fstat(logfd, &st);
- if (!st.st_size)
- die("Log %s is empty.", logfile);
- mapsz = xsize_t(st.st_size);
- log_mapped = xmmap(NULL, mapsz, PROT_READ, MAP_PRIVATE, logfd, 0);
- logdata = log_mapped;
- close(logfd);
+ memset(&cb, 0, sizeof(cb));
+ cb.refname = refname;
+ cb.at_time = at_time;
+ cb.cnt = cnt;
+ cb.msg = msg;
+ cb.cutoff_time = cutoff_time;
+ cb.cutoff_tz = cutoff_tz;
+ cb.cutoff_cnt = cutoff_cnt;
+ cb.sha1 = sha1;
+
+ for_each_reflog_ent_reverse(refname, read_ref_at_ent, &cb);
+
+ if (!cb.reccnt) {
+ if (flags & GET_SHA1_QUIETLY)
+ exit(128);
+ else
+ die("Log for %s is empty.", refname);
+ }
+ if (cb.found_it)
+ return 0;
+
+ for_each_reflog_ent(refname, read_ref_at_ent_oldest, &cb);
- lastrec = NULL;
- rec = logend = logdata + st.st_size;
- while (logdata < rec) {
- reccnt++;
- if (logdata < rec && *(rec-1) == '\n')
- rec--;
- lastgt = NULL;
- while (logdata < rec && *(rec-1) != '\n') {
- rec--;
- if (*rec == '>')
- lastgt = rec;
- }
- if (!lastgt)
- die("Log %s is corrupt.", logfile);
- date = strtoul(lastgt + 1, &tz_c, 10);
- if (date <= at_time || cnt == 0) {
- tz = strtoul(tz_c, NULL, 10);
- if (msg)
- *msg = ref_msg(rec, logend);
- if (cutoff_time)
- *cutoff_time = date;
- if (cutoff_tz)
- *cutoff_tz = tz;
- if (cutoff_cnt)
- *cutoff_cnt = reccnt - 1;
- if (lastrec) {
- if (get_sha1_hex(lastrec, logged_sha1))
- die("Log %s is corrupt.", logfile);
- if (get_sha1_hex(rec + 41, sha1))
- die("Log %s is corrupt.", logfile);
- if (hashcmp(logged_sha1, sha1)) {
- warning("Log %s has gap after %s.",
- logfile, show_date(date, tz, DATE_RFC2822));
- }
- }
- else if (date == at_time) {
- if (get_sha1_hex(rec + 41, sha1))
- die("Log %s is corrupt.", logfile);
- }
- else {
- if (get_sha1_hex(rec + 41, logged_sha1))
- die("Log %s is corrupt.", logfile);
- if (hashcmp(logged_sha1, sha1)) {
- warning("Log %s unexpectedly ended on %s.",
- logfile, show_date(date, tz, DATE_RFC2822));
- }
- }
- munmap(log_mapped, mapsz);
- return 0;
- }
- lastrec = rec;
- if (cnt > 0)
- cnt--;
- }
-
- rec = logdata;
- while (rec < logend && *rec != '>' && *rec != '\n')
- rec++;
- if (rec == logend || *rec == '\n')
- die("Log %s is corrupt.", logfile);
- date = strtoul(rec + 1, &tz_c, 10);
- tz = strtoul(tz_c, NULL, 10);
- if (get_sha1_hex(logdata, sha1))
- die("Log %s is corrupt.", logfile);
- if (is_null_sha1(sha1)) {
- if (get_sha1_hex(logdata + 41, sha1))
- die("Log %s is corrupt.", logfile);
- }
- if (msg)
- *msg = ref_msg(logdata, logend);
- munmap(log_mapped, mapsz);
-
- if (cutoff_time)
- *cutoff_time = date;
- if (cutoff_tz)
- *cutoff_tz = tz;
- if (cutoff_cnt)
- *cutoff_cnt = reccnt;
return 1;
}
+int reflog_exists(const char *refname)
+{
+ struct stat st;
+
+ return !lstat(git_path("logs/%s", refname), &st) &&
+ S_ISREG(st.st_mode);
+}
+
+int delete_reflog(const char *refname)
+{
+ return remove_path(git_path("logs/%s", refname));
+}
+
static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *cb_data)
{
unsigned char osha1[20], nsha1[20];
@@ -3154,7 +3356,7 @@ static int do_for_each_reflog(struct strbuf *name, each_ref_fn fn, void *cb_data
if (de->d_name[0] == '.')
continue;
- if (has_extension(de->d_name, ".lock"))
+ if (ends_with(de->d_name, ".lock"))
continue;
strbuf_addstr(name, de->d_name);
if (stat(git_path("logs/%s", name->buf), &st) < 0) {
@@ -3189,36 +3391,147 @@ int for_each_reflog(each_ref_fn fn, void *cb_data)
return retval;
}
-static struct ref_lock *update_ref_lock(const char *refname,
- const unsigned char *oldval,
- int flags, int *type_p,
- enum action_on_err onerr)
-{
+/**
+ * Information needed for a single ref update. Set new_sha1 to the
+ * new value or to zero to delete the ref. To check the old value
+ * while locking the ref, set have_old to 1 and set old_sha1 to the
+ * value or to zero to ensure the ref does not exist before update.
+ */
+struct ref_update {
+ unsigned char new_sha1[20];
+ unsigned char old_sha1[20];
+ int flags; /* REF_NODEREF? */
+ int have_old; /* 1 if old_sha1 is valid, 0 otherwise */
struct ref_lock *lock;
- lock = lock_any_ref_for_update(refname, oldval, flags, type_p);
- if (!lock) {
- const char *str = "Cannot lock the ref '%s'.";
- switch (onerr) {
- case MSG_ON_ERR: error(str, refname); break;
- case DIE_ON_ERR: die(str, refname); break;
- case QUIET_ON_ERR: break;
- }
- }
- return lock;
+ int type;
+ const char refname[FLEX_ARRAY];
+};
+
+/*
+ * Transaction states.
+ * OPEN: The transaction is in a valid state and can accept new updates.
+ * An OPEN transaction can be committed.
+ * CLOSED: A closed transaction is no longer active and no other operations
+ * than free can be used on it in this state.
+ * A transaction can either become closed by successfully committing
+ * an active transaction or if there is a failure while building
+ * the transaction thus rendering it failed/inactive.
+ */
+enum ref_transaction_state {
+ REF_TRANSACTION_OPEN = 0,
+ REF_TRANSACTION_CLOSED = 1
+};
+
+/*
+ * Data structure for holding a reference transaction, which can
+ * consist of checks and updates to multiple references, carried out
+ * as atomically as possible. This structure is opaque to callers.
+ */
+struct ref_transaction {
+ struct ref_update **updates;
+ size_t alloc;
+ size_t nr;
+ enum ref_transaction_state state;
+};
+
+struct ref_transaction *ref_transaction_begin(struct strbuf *err)
+{
+ return xcalloc(1, sizeof(struct ref_transaction));
}
-static int update_ref_write(const char *action, const char *refname,
- const unsigned char *sha1, struct ref_lock *lock,
- enum action_on_err onerr)
+void ref_transaction_free(struct ref_transaction *transaction)
{
- if (write_ref_sha1(lock, sha1, action) < 0) {
- const char *str = "Cannot update the ref '%s'.";
- switch (onerr) {
- case MSG_ON_ERR: error(str, refname); break;
- case DIE_ON_ERR: die(str, refname); break;
- case QUIET_ON_ERR: break;
- }
- return 1;
+ int i;
+
+ if (!transaction)
+ return;
+
+ for (i = 0; i < transaction->nr; i++)
+ free(transaction->updates[i]);
+
+ free(transaction->updates);
+ free(transaction);
+}
+
+static struct ref_update *add_update(struct ref_transaction *transaction,
+ const char *refname)
+{
+ size_t len = strlen(refname);
+ struct ref_update *update = xcalloc(1, sizeof(*update) + len + 1);
+
+ strcpy((char *)update->refname, refname);
+ ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
+ transaction->updates[transaction->nr++] = update;
+ return update;
+}
+
+int ref_transaction_update(struct ref_transaction *transaction,
+ const char *refname,
+ const unsigned char *new_sha1,
+ const unsigned char *old_sha1,
+ int flags, int have_old,
+ struct strbuf *err)
+{
+ struct ref_update *update;
+
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: update called for transaction that is not open");
+
+ if (have_old && !old_sha1)
+ die("BUG: have_old is true but old_sha1 is NULL");
+
+ update = add_update(transaction, refname);
+ hashcpy(update->new_sha1, new_sha1);
+ update->flags = flags;
+ update->have_old = have_old;
+ if (have_old)
+ hashcpy(update->old_sha1, old_sha1);
+ return 0;
+}
+
+int ref_transaction_create(struct ref_transaction *transaction,
+ const char *refname,
+ const unsigned char *new_sha1,
+ int flags,
+ struct strbuf *err)
+{
+ struct ref_update *update;
+
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: create called for transaction that is not open");
+
+ if (!new_sha1 || is_null_sha1(new_sha1))
+ die("BUG: create ref with null new_sha1");
+
+ update = add_update(transaction, refname);
+
+ hashcpy(update->new_sha1, new_sha1);
+ hashclr(update->old_sha1);
+ update->flags = flags;
+ update->have_old = 1;
+ return 0;
+}
+
+int ref_transaction_delete(struct ref_transaction *transaction,
+ const char *refname,
+ const unsigned char *old_sha1,
+ int flags, int have_old,
+ struct strbuf *err)
+{
+ struct ref_update *update;
+
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: delete called for transaction that is not open");
+
+ if (have_old && !old_sha1)
+ die("BUG: have_old is true but old_sha1 is NULL");
+
+ update = add_update(transaction, refname);
+ update->flags = flags;
+ update->have_old = have_old;
+ if (have_old) {
+ assert(!is_null_sha1(old_sha1));
+ hashcpy(update->old_sha1, old_sha1);
}
return 0;
}
@@ -3227,136 +3540,145 @@ int update_ref(const char *action, const char *refname,
const unsigned char *sha1, const unsigned char *oldval,
int flags, enum action_on_err onerr)
{
- struct ref_lock *lock;
- lock = update_ref_lock(refname, oldval, flags, NULL, onerr);
- if (!lock)
+ struct ref_transaction *t;
+ struct strbuf err = STRBUF_INIT;
+
+ t = ref_transaction_begin(&err);
+ if (!t ||
+ ref_transaction_update(t, refname, sha1, oldval, flags,
+ !!oldval, &err) ||
+ ref_transaction_commit(t, action, &err)) {
+ const char *str = "update_ref failed for ref '%s': %s";
+
+ ref_transaction_free(t);
+ switch (onerr) {
+ case UPDATE_REFS_MSG_ON_ERR:
+ error(str, refname, err.buf);
+ break;
+ case UPDATE_REFS_DIE_ON_ERR:
+ die(str, refname, err.buf);
+ break;
+ case UPDATE_REFS_QUIET_ON_ERR:
+ break;
+ }
+ strbuf_release(&err);
return 1;
- return update_ref_write(action, refname, sha1, lock, onerr);
+ }
+ strbuf_release(&err);
+ ref_transaction_free(t);
+ return 0;
}
static int ref_update_compare(const void *r1, const void *r2)
{
const struct ref_update * const *u1 = r1;
const struct ref_update * const *u2 = r2;
- return strcmp((*u1)->ref_name, (*u2)->ref_name);
+ return strcmp((*u1)->refname, (*u2)->refname);
}
static int ref_update_reject_duplicates(struct ref_update **updates, int n,
- enum action_on_err onerr)
+ struct strbuf *err)
{
int i;
for (i = 1; i < n; i++)
- if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) {
+ if (!strcmp(updates[i - 1]->refname, updates[i]->refname)) {
const char *str =
"Multiple updates for ref '%s' not allowed.";
- switch (onerr) {
- case MSG_ON_ERR:
- error(str, updates[i]->ref_name); break;
- case DIE_ON_ERR:
- die(str, updates[i]->ref_name); break;
- case QUIET_ON_ERR:
- break;
- }
+ if (err)
+ strbuf_addf(err, str, updates[i]->refname);
+
return 1;
}
return 0;
}
-int update_refs(const char *action, const struct ref_update **updates_orig,
- int n, enum action_on_err onerr)
+int ref_transaction_commit(struct ref_transaction *transaction,
+ const char *msg, struct strbuf *err)
{
int ret = 0, delnum = 0, i;
- struct ref_update **updates;
- int *types;
- struct ref_lock **locks;
const char **delnames;
+ int n = transaction->nr;
+ struct ref_update **updates = transaction->updates;
+
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: commit called for transaction that is not open");
- if (!updates_orig || !n)
+ if (!n) {
+ transaction->state = REF_TRANSACTION_CLOSED;
return 0;
+ }
/* Allocate work space */
- updates = xmalloc(sizeof(*updates) * n);
- types = xmalloc(sizeof(*types) * n);
- locks = xcalloc(n, sizeof(*locks));
delnames = xmalloc(sizeof(*delnames) * n);
/* Copy, sort, and reject duplicate refs */
- memcpy(updates, updates_orig, sizeof(*updates) * n);
qsort(updates, n, sizeof(*updates), ref_update_compare);
- ret = ref_update_reject_duplicates(updates, n, onerr);
+ ret = ref_update_reject_duplicates(updates, n, err);
if (ret)
goto cleanup;
/* Acquire all locks while verifying old values */
for (i = 0; i < n; i++) {
- locks[i] = update_ref_lock(updates[i]->ref_name,
- (updates[i]->have_old ?
- updates[i]->old_sha1 : NULL),
- updates[i]->flags,
- &types[i], onerr);
- if (!locks[i]) {
+ struct ref_update *update = updates[i];
+
+ update->lock = lock_any_ref_for_update(update->refname,
+ (update->have_old ?
+ update->old_sha1 :
+ NULL),
+ update->flags,
+ &update->type);
+ if (!update->lock) {
+ if (err)
+ strbuf_addf(err, "Cannot lock the ref '%s'.",
+ update->refname);
ret = 1;
goto cleanup;
}
}
/* Perform updates first so live commits remain referenced */
- for (i = 0; i < n; i++)
- if (!is_null_sha1(updates[i]->new_sha1)) {
- ret = update_ref_write(action,
- updates[i]->ref_name,
- updates[i]->new_sha1,
- locks[i], onerr);
- locks[i] = NULL; /* freed by update_ref_write */
- if (ret)
+ for (i = 0; i < n; i++) {
+ struct ref_update *update = updates[i];
+
+ if (!is_null_sha1(update->new_sha1)) {
+ ret = write_ref_sha1(update->lock, update->new_sha1,
+ msg);
+ update->lock = NULL; /* freed by write_ref_sha1 */
+ if (ret) {
+ if (err)
+ strbuf_addf(err, "Cannot update the ref '%s'.",
+ update->refname);
goto cleanup;
+ }
}
+ }
/* Perform deletes now that updates are safely completed */
- for (i = 0; i < n; i++)
- if (locks[i]) {
- delnames[delnum++] = locks[i]->ref_name;
- ret |= delete_ref_loose(locks[i], types[i]);
+ for (i = 0; i < n; i++) {
+ struct ref_update *update = updates[i];
+
+ if (update->lock) {
+ ret |= delete_ref_loose(update->lock, update->type);
+ if (!(update->flags & REF_ISPRUNING))
+ delnames[delnum++] = update->lock->ref_name;
}
- ret |= repack_without_refs(delnames, delnum);
+ }
+
+ ret |= repack_without_refs(delnames, delnum, err);
for (i = 0; i < delnum; i++)
unlink_or_warn(git_path("logs/%s", delnames[i]));
clear_loose_ref_cache(&ref_cache);
cleanup:
+ transaction->state = REF_TRANSACTION_CLOSED;
+
for (i = 0; i < n; i++)
- if (locks[i])
- unlock_ref(locks[i]);
- free(updates);
- free(types);
- free(locks);
+ if (updates[i]->lock)
+ unlock_ref(updates[i]->lock);
free(delnames);
return ret;
}
-/*
- * generate a format suitable for scanf from a ref_rev_parse_rules
- * rule, that is replace the "%.*s" spec with a "%s" spec
- */
-static void gen_scanf_fmt(char *scanf_fmt, const char *rule)
-{
- char *spec;
-
- spec = strstr(rule, "%.*s");
- if (!spec || strstr(spec + 4, "%.*s"))
- die("invalid rule in ref_rev_parse_rules: %s", rule);
-
- /* copy all until spec */
- strncpy(scanf_fmt, rule, spec - rule);
- scanf_fmt[spec - rule] = '\0';
- /* copy new spec */
- strcat(scanf_fmt, "%s");
- /* copy remaining rule */
- strcat(scanf_fmt, spec + 4);
-
- return;
-}
-
char *shorten_unambiguous_ref(const char *refname, int strict)
{
int i;
@@ -3364,23 +3686,29 @@ char *shorten_unambiguous_ref(const char *refname, int strict)
static int nr_rules;
char *short_name;
- /* pre generate scanf formats from ref_rev_parse_rules[] */
if (!nr_rules) {
+ /*
+ * Pre-generate scanf formats from ref_rev_parse_rules[].
+ * Generate a format suitable for scanf from a
+ * ref_rev_parse_rules rule by interpolating "%s" at the
+ * location of the "%.*s".
+ */
size_t total_len = 0;
+ size_t offset = 0;
/* the rule list is NULL terminated, count them first */
for (nr_rules = 0; ref_rev_parse_rules[nr_rules]; nr_rules++)
- /* no +1 because strlen("%s") < strlen("%.*s") */
- total_len += strlen(ref_rev_parse_rules[nr_rules]);
+ /* -2 for strlen("%.*s") - strlen("%s"); +1 for NUL */
+ total_len += strlen(ref_rev_parse_rules[nr_rules]) - 2 + 1;
scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len);
- total_len = 0;
+ offset = 0;
for (i = 0; i < nr_rules; i++) {
- scanf_fmts[i] = (char *)&scanf_fmts[nr_rules]
- + total_len;
- gen_scanf_fmt(scanf_fmts[i], ref_rev_parse_rules[i]);
- total_len += strlen(ref_rev_parse_rules[i]);
+ assert(offset < total_len);
+ scanf_fmts[i] = (char *)&scanf_fmts[nr_rules] + offset;
+ offset += snprintf(scanf_fmts[i], total_len - offset,
+ ref_rev_parse_rules[i], 2, "%s") + 1;
}
}
@@ -3450,7 +3778,7 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti
{
if (!strcmp("transfer.hiderefs", var) ||
/* NEEDSWORK: use parse_config_key() once both are merged */
- (!prefixcmp(var, section) && var[strlen(section)] == '.' &&
+ (starts_with(var, section) && var[strlen(section)] == '.' &&
!strcmp(var + strlen(section), ".hiderefs"))) {
char *ref;
int len;
@@ -3478,7 +3806,7 @@ int ref_is_hidden(const char *refname)
return 0;
for_each_string_list_item(item, hide_refs) {
int len;
- if (prefixcmp(refname, item->string))
+ if (!starts_with(refname, item->string))
continue;
len = strlen(item->string);
if (!refname[len] || refname[len] == '/')