summaryrefslogtreecommitdiff
path: root/refs.c
diff options
context:
space:
mode:
Diffstat (limited to 'refs.c')
-rw-r--r--refs.c1969
1 files changed, 1343 insertions, 626 deletions
diff --git a/refs.c b/refs.c
index f0bd7ac0e9..67d6745e28 100644
--- a/refs.c
+++ b/refs.c
@@ -1,13 +1,73 @@
#include "cache.h"
+#include "lockfile.h"
#include "refs.h"
#include "object.h"
#include "tag.h"
#include "dir.h"
#include "string-list.h"
+struct ref_lock {
+ char *ref_name;
+ char *orig_ref_name;
+ struct lock_file *lk;
+ unsigned char old_sha1[20];
+ int lock_fd;
+};
+
+/*
+ * 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
+};
+
+/*
+ * Flag passed to lock_ref_sha1_basic() telling it to tolerate broken
+ * refs (i.e., because the reference is about to be deleted anyway).
+ */
+#define REF_DELETING 0x02
+
+/*
+ * Used as a flag in ref_update::flags when a loose ref is being
+ * pruned.
+ */
+#define REF_ISPRUNING 0x04
+
+/*
+ * Used as a flag in ref_update::flags when the reference should be
+ * updated to new_sha1.
+ */
+#define REF_HAVE_NEW 0x08
+
+/*
+ * Used as a flag in ref_update::flags when old_sha1 should be
+ * checked.
+ */
+#define REF_HAVE_OLD 0x10
+
/*
- * Make sure "ref" is something reasonable to have under ".git/refs/";
- * We do not like it if:
+ * Used as a flag in ref_update::flags when the lockfile needs to be
+ * committed.
+ */
+#define REF_NEEDS_COMMIT 0x20
+
+/*
+ * 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,54 +76,37 @@
* - 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;
- 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 3:
+ if (last == '@')
+ return -1; /* Refname contains "@{". */
+ break;
+ case 4:
+ return -1;
+ }
last = ch;
}
+out:
if (cp == refname)
return 0; /* Component has zero length. */
- if (refname[0] == '.') {
- if (!(flags & REFNAME_DOT_COMPONENT))
- return -1; /* Component starts with '.'. */
- /*
- * Even if leading dots are allowed, don't allow "."
- * as a component (".." is prevented by a rule above).
- */
- if (refname[1] == '\0')
- return -1; /* Component equals ".". */
- }
- if (cp - refname >= 5 && !memcmp(cp - 5, ".lock", 5))
+ if (refname[0] == '.')
+ return -1; /* Component starts with '.'. */
+ 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;
}
@@ -177,8 +220,8 @@ struct ref_dir {
/*
* Bit values for ref_entry::flag. REF_ISSYMREF=0x01,
- * REF_ISPACKED=0x02, and REF_ISBROKEN=0x04 are public values; see
- * refs.h.
+ * REF_ISPACKED=0x02, REF_ISBROKEN=0x04 and REF_BAD_NAME=0x08 are
+ * public values; see refs.h.
*/
/*
@@ -186,16 +229,16 @@ struct ref_dir {
* the correct peeled value for the reference, which might be
* null_sha1 if the reference is not a tag or if it is broken.
*/
-#define REF_KNOWS_PEELED 0x08
+#define REF_KNOWS_PEELED 0x10
/* ref_entry represents a directory of references */
-#define REF_DIR 0x10
+#define REF_DIR 0x20
/*
* Entry has not yet been read from disk (used only for REF_DIR
* entries representing loose references)
*/
-#define REF_INCOMPLETE 0x20
+#define REF_INCOMPLETE 0x40
/*
* A ref_entry represents either a reference or a "subdirectory" of
@@ -226,7 +269,7 @@ struct ref_dir {
* presence of an empty subdirectory does not block the creation of a
* similarly-named reference. (The fact that reference names with the
* 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
* reference (or subdirectory) name. Space could be saved by only
@@ -264,6 +307,39 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry)
return dir;
}
+/*
+ * Check if a refname is safe.
+ * For refs that start with "refs/" we consider it safe as long they do
+ * not try to resolve to outside of refs/.
+ *
+ * For all other refs we only consider them safe iff they only contain
+ * upper case characters and '_' (like "HEAD" AND "MERGE_HEAD", and not like
+ * "config").
+ */
+static int refname_is_safe(const char *refname)
+{
+ if (starts_with(refname, "refs/")) {
+ char *buf;
+ int result;
+
+ buf = xmalloc(strlen(refname) + 1);
+ /*
+ * Does the refname try to escape refs/?
+ * For example: refs/foo/../bar is safe but refs/foo/../../bar
+ * is not.
+ */
+ result = !normalize_path_copy(buf, refname + strlen("refs/"));
+ free(buf);
+ return result;
+ }
+ while (*refname) {
+ if (!isupper(*refname) && *refname != '_')
+ return 0;
+ refname++;
+ }
+ return 1;
+}
+
static struct ref_entry *create_ref_entry(const char *refname,
const unsigned char *sha1, int flag,
int check_name)
@@ -272,8 +348,10 @@ static struct ref_entry *create_ref_entry(const char *refname,
struct ref_entry *ref;
if (check_name &&
- check_refname_format(refname, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT))
+ check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
die("Reference has invalid format: '%s'", refname);
+ if (!check_name && !refname_is_safe(refname))
+ die("Reference has invalid name: '%s'", refname);
len = strlen(refname) + 1;
ref = xmalloc(sizeof(struct ref_entry) + len);
hashcpy(ref->u.value.sha1, sha1);
@@ -768,61 +846,182 @@ 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)
-{
- for (; *refname1 && *refname1 == *refname2; refname1++, refname2++)
- ;
- return (*refname1 == '\0' && *refname2 == '/')
- || (*refname1 == '/' && *refname2 == '\0');
-}
-struct name_conflict_cb {
- const char *refname;
- const char *oldrefname;
+struct nonmatching_ref_data {
+ const struct string_list *skip;
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;
- if (data->oldrefname && !strcmp(data->oldrefname, entry->name))
+ struct nonmatching_ref_data *data = vdata;
+
+ if (data->skip && string_list_has_string(data->skip, entry->name))
return 0;
- if (names_conflict(data->refname, entry->name)) {
- data->conflicting_refname = entry->name;
- return 1;
- }
- return 0;
+
+ data->conflicting_refname = entry->name;
+ return 1;
}
/*
- * Return true iff a reference named refname could be created without
- * conflicting with the name of an existing reference in dir. If
- * oldrefname is non-NULL, ignore potential conflicts with oldrefname
- * (e.g., because oldrefname is scheduled for deletion in the same
- * operation).
+ * Return 0 if a reference named refname could be created without
+ * conflicting with the name of an existing reference in dir.
+ * Otherwise, return a negative value and write an explanation to err.
+ * If extras is non-NULL, it is a list of additional refnames with
+ * 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
+ * leading components of the other; e.g., "refs/foo/bar" conflicts
+ * with both "refs/foo" and with "refs/foo/bar/baz" but not with
+ * "refs/foo/bar" or "refs/foo/barbados".
+ *
+ * extras and skip must be sorted.
*/
-static int is_refname_available(const char *refname, const char *oldrefname,
- struct ref_dir *dir)
+static int verify_refname_available(const char *refname,
+ const struct string_list *extras,
+ const struct string_list *skip,
+ struct ref_dir *dir,
+ struct strbuf *err)
{
- struct name_conflict_cb data;
- data.refname = refname;
- data.oldrefname = oldrefname;
- data.conflicting_refname = NULL;
+ const char *slash;
+ int pos;
+ struct strbuf dirname = STRBUF_INIT;
+ int ret = -1;
- 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);
- return 0;
+ /*
+ * 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, '/')) {
+ /* 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 (e.g.,
+ * "refs/foo"; if there is a reference with that name,
+ * it is a conflict, *unless* it is in skip.
+ */
+ if (dir) {
+ pos = search_ref_dir(dir, dirname.buf, dirname.len);
+ if (pos >= 0 &&
+ (!skip || !string_list_has_string(skip, dirname.buf))) {
+ /*
+ * We found a reference whose name is
+ * 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
+ * the next component. So try to look up the
+ * 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.
+ */
+ strbuf_addch(&dirname, '/');
+ if (dir) {
+ pos = search_ref_dir(dir, dirname.buf, dirname.len);
+ if (pos < 0) {
+ /*
+ * 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]);
+ }
+ }
}
- return 1;
+
+ /*
+ * We are at the leaf of our refname (e.g., "refs/foo/bar").
+ * 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.
+ */
+ strbuf_addstr(&dirname, refname + dirname.len);
+ strbuf_addch(&dirname, '/');
+
+ 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) {
+ /*
+ * Check for entries in extras that start with
+ * "$refname/". We do that by looking for the place
+ * 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.
+ */
+ for (pos = string_list_find_insert_index(extras, dirname.buf, 0);
+ pos < extras->nr; pos++) {
+ const char *extra_refname = extras->items[pos].string;
+
+ if (!starts_with(extra_refname, dirname.buf))
+ break;
+
+ if (!skip || !string_list_has_string(skip, extra_refname)) {
+ strbuf_addf(err, "cannot process '%s' and '%s' at the same time",
+ refname, extra_refname);
+ goto cleanup;
+ }
+ }
+ }
+
+ /* No conflicts were found */
+ ret = 0;
+
+cleanup:
+ strbuf_release(&dirname);
+ return ret;
}
struct packed_ref_cache {
@@ -962,8 +1161,10 @@ static const char PACKED_REFS_HEADER[] =
* Return a pointer to the refname within the line (null-terminated),
* or NULL if there was a problem.
*/
-static const char *parse_ref_line(char *line, unsigned char *sha1)
+static const char *parse_ref_line(struct strbuf *line, unsigned char *sha1)
{
+ const char *ref;
+
/*
* 42: the answer to everything.
*
@@ -972,22 +1173,23 @@ static const char *parse_ref_line(char *line, unsigned char *sha1)
* +1 (space in between hex and name)
* +1 (newline at the end of the line)
*/
- int len = strlen(line) - 42;
-
- if (len <= 0)
+ if (line->len <= 42)
return NULL;
- if (get_sha1_hex(line, sha1) < 0)
+
+ if (get_sha1_hex(line->buf, sha1) < 0)
return NULL;
- if (!isspace(line[40]))
+ if (!isspace(line->buf[40]))
return NULL;
- line += 41;
- if (isspace(*line))
+
+ ref = line->buf + 41;
+ if (isspace(*ref))
return NULL;
- if (line[len] != '\n')
+
+ if (line->buf[line->len - 1] != '\n')
return NULL;
- line[len] = 0;
+ line->buf[--line->len] = 0;
- return line;
+ return ref;
}
/*
@@ -1020,16 +1222,15 @@ static const char *parse_ref_line(char *line, unsigned char *sha1)
static void read_packed_refs(FILE *f, struct ref_dir *dir)
{
struct ref_entry *last = NULL;
- char refline[PATH_MAX];
+ struct strbuf line = STRBUF_INIT;
enum { PEELED_NONE, PEELED_TAGS, PEELED_FULLY } peeled = PEELED_NONE;
- while (fgets(refline, sizeof(refline), f)) {
+ while (strbuf_getwholeline(&line, f, '\n') != EOF) {
unsigned char sha1[20];
const char *refname;
- static const char header[] = "# pack-refs with:";
+ const char *traits;
- if (!strncmp(refline, header, sizeof(header)-1)) {
- const char *traits = refline + sizeof(header) - 1;
+ if (skip_prefix(line.buf, "# pack-refs with:", &traits)) {
if (strstr(traits, " fully-peeled "))
peeled = PEELED_FULLY;
else if (strstr(traits, " peeled "))
@@ -1038,9 +1239,15 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
continue;
}
- refname = parse_ref_line(refline, sha1);
+ refname = parse_ref_line(&line, sha1);
if (refname) {
- last = create_ref_entry(refname, sha1, REF_ISPACKED, 1);
+ int flag = REF_ISPACKED;
+
+ if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+ hashclr(sha1);
+ flag |= REF_BAD_NAME | REF_ISBROKEN;
+ }
+ last = create_ref_entry(refname, sha1, flag, 0);
if (peeled == PEELED_FULLY ||
(peeled == PEELED_TAGS && starts_with(refname, "refs/tags/")))
last->flag |= REF_KNOWS_PEELED;
@@ -1048,10 +1255,10 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
continue;
}
if (last &&
- refline[0] == '^' &&
- strlen(refline) == PEELED_LINE_LENGTH &&
- refline[PEELED_LINE_LENGTH - 1] == '\n' &&
- !get_sha1_hex(refline + 1, sha1)) {
+ line.buf[0] == '^' &&
+ line.len == PEELED_LINE_LENGTH &&
+ line.buf[PEELED_LINE_LENGTH - 1] == '\n' &&
+ !get_sha1_hex(line.buf + 1, sha1)) {
hashcpy(last->u.value.peeled, sha1);
/*
* Regardless of what the file header said,
@@ -1061,6 +1268,8 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
last->flag |= REF_KNOWS_PEELED;
}
}
+
+ strbuf_release(&line);
}
/*
@@ -1151,7 +1360,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
@@ -1172,12 +1381,19 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)
hashclr(sha1);
flag |= REF_ISBROKEN;
}
- } else if (read_ref_full(refname.buf, sha1, 1, &flag)) {
+ } else if (read_ref_full(refname.buf,
+ RESOLVE_REF_READING,
+ sha1, &flag)) {
hashclr(sha1);
flag |= REF_ISBROKEN;
}
+ if (check_refname_format(refname.buf,
+ REFNAME_ALLOW_ONELEVEL)) {
+ hashclr(sha1);
+ flag |= REF_BAD_NAME | REF_ISBROKEN;
+ }
add_entry_to_dir(dir,
- create_ref_entry(refname.buf, sha1, flag, 1));
+ create_ref_entry(refname.buf, sha1, flag, 0));
}
strbuf_setlen(&refname, dirnamelen);
}
@@ -1296,10 +1512,10 @@ static struct ref_entry *get_packed_ref(const char *refname)
* A loose ref file doesn't exist; check for a packed ref. The
* options are forwarded from resolve_safe_unsafe().
*/
-static const char *handle_missing_loose_ref(const char *refname,
- unsigned char *sha1,
- int reading,
- int *flag)
+static int resolve_missing_loose_ref(const char *refname,
+ int resolve_flags,
+ unsigned char *sha1,
+ int *flags)
{
struct ref_entry *entry;
@@ -1310,40 +1526,61 @@ static const char *handle_missing_loose_ref(const char *refname,
entry = get_packed_ref(refname);
if (entry) {
hashcpy(sha1, entry->u.value.sha1);
- if (flag)
- *flag |= REF_ISPACKED;
- return refname;
+ if (flags)
+ *flags |= REF_ISPACKED;
+ return 0;
}
/* The reference is not a packed reference, either. */
- if (reading) {
- return NULL;
+ if (resolve_flags & RESOLVE_REF_READING) {
+ errno = ENOENT;
+ return -1;
} else {
hashclr(sha1);
- return refname;
+ return 0;
}
}
-const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)
+/* This function needs to return a meaningful errno on failure */
+const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned char *sha1, int *flags)
{
int depth = MAXDEPTH;
ssize_t len;
char buffer[256];
static char refname_buffer[256];
+ int bad_name = 0;
- if (flag)
- *flag = 0;
+ if (flags)
+ *flags = 0;
- if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
- return NULL;
+ if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+ if (flags)
+ *flags |= REF_BAD_NAME;
+ if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
+ !refname_is_safe(refname)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ /*
+ * dwim_ref() uses REF_ISBROKEN to distinguish between
+ * missing refs and refs that were present but invalid,
+ * to complain about the latter to stderr.
+ *
+ * We don't know whether the ref exists, so don't set
+ * REF_ISBROKEN yet.
+ */
+ bad_name = 1;
+ }
for (;;) {
char path[PATH_MAX];
struct stat st;
char *buf;
int fd;
- if (--depth < 0)
+ if (--depth < 0) {
+ errno = ELOOP;
return NULL;
+ }
git_snpath(path, sizeof(path), "%s", refname);
@@ -1358,11 +1595,17 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
*/
stat_ref:
if (lstat(path, &st) < 0) {
- if (errno == ENOENT)
- return handle_missing_loose_ref(refname, sha1,
- reading, flag);
- else
+ if (errno != ENOENT)
return NULL;
+ if (resolve_missing_loose_ref(refname, resolve_flags,
+ sha1, flags))
+ return NULL;
+ if (bad_name) {
+ hashclr(sha1);
+ if (flags)
+ *flags |= REF_ISBROKEN;
+ }
+ return refname;
}
/* Follow "normalized" - ie "refs/.." symlinks by hand */
@@ -1380,8 +1623,12 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
!check_refname_format(buffer, 0)) {
strcpy(refname_buffer, buffer);
refname = refname_buffer;
- if (flag)
- *flag |= REF_ISSYMREF;
+ if (flags)
+ *flags |= REF_ISSYMREF;
+ if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
+ hashclr(sha1);
+ return refname;
+ }
continue;
}
}
@@ -1405,9 +1652,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';
@@ -1422,30 +1673,45 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
*/
if (get_sha1_hex(buffer, sha1) ||
(buffer[40] != '\0' && !isspace(buffer[40]))) {
- if (flag)
- *flag |= REF_ISBROKEN;
+ if (flags)
+ *flags |= REF_ISBROKEN;
+ errno = EINVAL;
return NULL;
}
+ if (bad_name) {
+ hashclr(sha1);
+ if (flags)
+ *flags |= REF_ISBROKEN;
+ }
return refname;
}
- if (flag)
- *flag |= REF_ISSYMREF;
+ if (flags)
+ *flags |= REF_ISSYMREF;
buf = buffer + 4;
while (isspace(*buf))
buf++;
+ refname = strcpy(refname_buffer, buf);
+ if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
+ hashclr(sha1);
+ return refname;
+ }
if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
- if (flag)
- *flag |= REF_ISBROKEN;
- return NULL;
+ if (flags)
+ *flags |= REF_ISBROKEN;
+
+ if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
+ !refname_is_safe(buf)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ bad_name = 1;
}
- refname = strcpy(refname_buffer, buf);
}
}
-char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag)
+char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags)
{
- const char *ret = resolve_ref_unsafe(ref, sha1, reading, flag);
- return ret ? xstrdup(ret) : NULL;
+ return xstrdup_or_null(resolve_ref_unsafe(ref, resolve_flags, sha1, flags));
}
/* The argument to filter_refs */
@@ -1455,22 +1721,22 @@ struct ref_filter {
void *cb_data;
};
-int read_ref_full(const char *refname, unsigned char *sha1, int reading, int *flags)
+int read_ref_full(const char *refname, int resolve_flags, unsigned char *sha1, int *flags)
{
- if (resolve_ref_unsafe(refname, sha1, reading, flags))
+ if (resolve_ref_unsafe(refname, resolve_flags, sha1, flags))
return 0;
return -1;
}
int read_ref(const char *refname, unsigned char *sha1)
{
- return read_ref_full(refname, sha1, 1, NULL);
+ return read_ref_full(refname, RESOLVE_REF_READING, sha1, NULL);
}
int ref_exists(const char *refname)
{
unsigned char sha1[20];
- return !!resolve_ref_unsafe(refname, sha1, 1, NULL);
+ return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, sha1, NULL);
}
static int filter_refs(const char *refname, const unsigned char *sha1, int flags,
@@ -1583,7 +1849,7 @@ int peel_ref(const char *refname, unsigned char *sha1)
return 0;
}
- if (read_ref_full(refname, base, 1, &flag))
+ if (read_ref_full(refname, RESOLVE_REF_READING, base, &flag))
return -1;
/*
@@ -1624,7 +1890,7 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha
if (!(flags & REF_ISSYMREF))
return 0;
- resolves_to = resolve_ref_unsafe(refname, junk, 0, NULL);
+ resolves_to = resolve_ref_unsafe(refname, 0, junk, NULL);
if (!resolves_to
|| (d->refname
? strcmp(resolves_to, d->refname)
@@ -1734,6 +2000,11 @@ static int do_for_each_ref(struct ref_cache *refs, const char *base,
data.fn = fn;
data.cb_data = cb_data;
+ if (ref_paranoia < 0)
+ ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 0);
+ if (ref_paranoia)
+ data.flags |= DO_FOR_EACH_INCLUDE_BROKEN;
+
return do_for_each_entry(refs, base, do_one_ref, &data);
}
@@ -1749,7 +2020,7 @@ static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)
return 0;
}
- if (!read_ref_full("HEAD", sha1, 1, &flag))
+ if (!read_ref_full("HEAD", RESOLVE_REF_READING, sha1, &flag))
return fn("HEAD", sha1, flag, cb_data);
return 0;
@@ -1829,7 +2100,7 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data)
int flag;
strbuf_addf(&buf, "%sHEAD", get_git_namespace());
- if (!read_ref_full(buf.buf, sha1, 1, &flag))
+ if (!read_ref_full(buf.buf, RESOLVE_REF_READING, sha1, &flag))
ret = fn(buf.buf, sha1, flag, cb_data);
strbuf_release(&buf);
@@ -1920,18 +2191,34 @@ int refname_match(const char *abbrev_name, const char *full_name)
return 0;
}
+static void unlock_ref(struct ref_lock *lock)
+{
+ /* Do not free lock->lk -- atexit() still looks at them */
+ if (lock->lk)
+ rollback_lock_file(lock->lk);
+ free(lock->ref_name);
+ free(lock->orig_ref_name);
+ free(lock);
+}
+
+/* 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)) {
+ if (read_ref_full(lock->ref_name,
+ mustexist ? RESOLVE_REF_READING : 0,
+ lock->old_sha1, 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;
@@ -1944,14 +2231,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;
}
@@ -1991,7 +2280,8 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
this_result = refs_found ? sha1_from_ref : sha1;
mksnpath(fullref, sizeof(fullref), *p, len, str);
- r = resolve_ref_unsafe(fullref, this_result, 1, &flag);
+ r = resolve_ref_unsafe(fullref, RESOLVE_REF_READING,
+ this_result, &flag);
if (r) {
if (!refs_found++)
*ref = xstrdup(r);
@@ -2015,21 +2305,18 @@ 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;
mksnpath(path, sizeof(path), *p, len, str);
- ref = resolve_ref_unsafe(path, hash, 1, NULL);
+ ref = resolve_ref_unsafe(path, RESOLVE_REF_READING,
+ hash, 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;
@@ -2044,9 +2331,16 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
return logs_found;
}
+/*
+ * Locks a 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)
+ const struct string_list *extras,
+ const struct string_list *skip,
+ unsigned int flags, int *type_p,
+ struct strbuf *err)
{
char *ref_file;
const char *orig_refname = refname;
@@ -2054,13 +2348,24 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
int last_errno = 0;
int type, lflags;
int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
- int missing = 0;
+ int resolve_flags = 0;
int attempts_remaining = 3;
+ assert(err);
+
lock = xcalloc(1, sizeof(struct ref_lock));
lock->lock_fd = -1;
- refname = resolve_ref_unsafe(refname, lock->old_sha1, mustexist, &type);
+ if (mustexist)
+ resolve_flags |= RESOLVE_REF_READING;
+ if (flags & REF_DELETING) {
+ resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;
+ if (flags & REF_NODEREF)
+ resolve_flags |= RESOLVE_REF_NO_RECURSE;
+ }
+
+ refname = resolve_ref_unsafe(refname, resolve_flags,
+ lock->old_sha1, &type);
if (!refname && errno == EISDIR) {
/* we are trying to lock foo but we used to
* have foo/bar which now does not exist;
@@ -2070,27 +2375,38 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
ref_file = git_path("%s", orig_refname);
if (remove_empty_directories(ref_file)) {
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;
}
- refname = resolve_ref_unsafe(orig_refname, lock->old_sha1, mustexist, &type);
+ refname = resolve_ref_unsafe(orig_refname, resolve_flags,
+ lock->old_sha1, &type);
}
if (type_p)
*type_p = type;
if (!refname) {
last_errno = errno;
- error("unable to resolve reference %s: %s",
- orig_refname, strerror(errno));
+ if (last_errno != ENOTDIR ||
+ !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;
}
- missing = is_null_sha1(lock->old_sha1);
- /* When the ref did not exist and we are creating it,
- * make sure there is no existing ref that is packed
- * whose name begins with our refname, nor a ref whose
- * name is a proper prefix of our refname.
+ /*
+ * If the ref did not exist and we are creating it, make sure
+ * there is no existing packed ref whose name begins with our
+ * refname, nor a packed ref whose name is a proper prefix of
+ * our refname.
*/
- if (missing &&
- !is_refname_available(refname, NULL, get_packed_refs(&ref_cache))) {
+ if (is_null_sha1(lock->old_sha1) &&
+ verify_refname_available(refname, extras, skip,
+ get_packed_refs(&ref_cache), err)) {
last_errno = ENOTDIR;
goto error_return;
}
@@ -2100,15 +2416,11 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
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);
ref_file = git_path("%s", refname);
- if (missing)
- lock->force_write = 1;
- if ((flags & REF_NODEREF) && (type & REF_ISSYMREF))
- lock->force_write = 1;
retry:
switch (safe_create_leading_directories(ref_file)) {
@@ -2120,12 +2432,13 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
/* fall through */
default:
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;
}
lock->lock_fd = hold_lock_file_for_update(lock->lk, ref_file, lflags);
if (lock->lock_fd < 0) {
+ last_errno = errno;
if (errno == ENOENT && --attempts_remaining > 0)
/*
* Maybe somebody just deleted one of the
@@ -2133,8 +2446,10 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
* again:
*/
goto retry;
- else
- unable_to_lock_index_die(ref_file, errno);
+ else {
+ unable_to_lock_message(ref_file, errno, err);
+ goto error_return;
+ }
}
return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
@@ -2144,47 +2459,16 @@ 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)
-{
- if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
- return NULL;
- return lock_ref_sha1_basic(refname, old_sha1, flags, type_p);
-}
-
/*
* 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));
}
/*
@@ -2192,18 +2476,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;
@@ -2223,24 +2507,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;
}
@@ -2353,13 +2649,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, NULL, &err) ||
+ ref_transaction_commit(transaction, &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)
@@ -2390,88 +2698,35 @@ int pack_refs(unsigned int flags)
return 0;
}
-/*
- * If entry is no longer needed in packed-refs, add it to the string
- * list pointed to by cb_data. Reasons for deleting entries:
- *
- * - Entry is broken.
- * - Entry is overridden by a loose ref.
- * - Entry does not point at a valid object.
- *
- * In the first and third cases, also emit an error message because these
- * are indications of repository corruption.
- */
-static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)
-{
- struct string_list *refs_to_delete = cb_data;
-
- if (entry->flag & REF_ISBROKEN) {
- /* This shouldn't happen to packed refs. */
- error("%s is broken!", entry->name);
- string_list_append(refs_to_delete, entry->name);
- return 0;
- }
- if (!has_sha1_file(entry->u.value.sha1)) {
- unsigned char sha1[20];
- int flags;
-
- if (read_ref_full(entry->name, sha1, 0, &flags))
- /* We should at least have found the packed ref. */
- die("Internal error");
- if ((flags & REF_ISSYMREF) || !(flags & REF_ISPACKED)) {
- /*
- * This packed reference is overridden by a
- * loose reference, so it is OK that its value
- * is no longer valid; for example, it might
- * refer to an object that has been garbage
- * collected. For this purpose we don't even
- * care whether the loose reference itself is
- * invalid, broken, symbolic, etc. Silently
- * remove the packed reference.
- */
- string_list_append(refs_to_delete, entry->name);
- return 0;
- }
- /*
- * There is no overriding loose reference, so the fact
- * that this reference doesn't refer to a valid object
- * indicates some kind of repository corruption.
- * Report the problem, then omit the reference from
- * the output.
- */
- error("%s does not point to a valid object!", entry->name);
- string_list_append(refs_to_delete, entry->name);
- return 0;
- }
-
- return 0;
-}
-
-int repack_without_refs(const char **refnames, int n)
+int repack_without_refs(struct string_list *refnames, 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;
+ struct string_list_item *refname;
+ int ret, needs_repacking = 0, removed = 0;
+
+ assert(err);
/* Look for a packed ref */
- for (i = 0; i < n; i++)
- if (get_packed_ref(refnames[i]))
+ for_each_string_list_item(refname, refnames) {
+ if (get_packed_ref(refname->string)) {
+ needs_repacking = 1;
break;
+ }
+ }
/* Avoid locking if we have nothing to do */
- if (i == n)
+ if (!needs_repacking)
return 0; /* no refname exists in packed refs */
if (lock_packed_refs(0)) {
- unable_to_lock_error(git_path("packed-refs"), errno);
- return error("cannot delete '%s' from packed refs", refnames[i]);
+ unable_to_lock_message(git_path("packed-refs"), errno, err);
+ return -1;
}
packed = get_packed_refs(&ref_cache);
/* Remove refnames from the cache */
- for (i = 0; i < n; i++)
- if (remove_entry(packed, refnames[i]) != -1)
+ for_each_string_list_item(refname, refnames)
+ if (remove_entry(packed, refname->string) != -1)
removed = 1;
if (!removed) {
/*
@@ -2482,57 +2737,51 @@ int repack_without_refs(const char **refnames, int n)
return 0;
}
- /* Remove any other accumulated cruft */
- do_for_each_entry_in_dir(packed, 0, curate_packed_ref_fn, &refs_to_delete);
- for_each_string_list_item(ref_to_delete, &refs_to_delete) {
- if (remove_entry(packed, ref_to_delete->string) == -1)
- die("internal error");
- }
-
/* Write what remains */
- return commit_packed_refs();
+ ret = commit_packed_refs();
+ if (ret)
+ strbuf_addf(err, "unable to overwrite old ref-pack file: %s",
+ strerror(errno));
+ return ret;
}
-static int repack_without_ref(const char *refname)
+static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
{
- return repack_without_refs(&refname, 1);
-}
+ assert(err);
-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] = '.';
- if (err && errno != ENOENT)
+ /*
+ * loose. The loose file name is the same as the
+ * lockfile name, minus ".lock":
+ */
+ char *loose_filename = get_locked_file_path(lock->lk);
+ int res = unlink_or_msg(loose_filename, err);
+ free(loose_filename);
+ if (res)
return 1;
}
return 0;
}
-int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
+int delete_ref(const char *refname, const unsigned char *sha1, unsigned int flags)
{
- struct ref_lock *lock;
- int ret = 0, flag = 0;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
- lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);
- if (!lock)
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_delete(transaction, refname,
+ (sha1 && !is_null_sha1(sha1)) ? sha1 : NULL,
+ flags, NULL, &err) ||
+ ref_transaction_commit(transaction, &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;
}
/*
@@ -2589,6 +2838,29 @@ static int rename_tmp_log(const char *newrefname)
return 0;
}
+static int rename_ref_available(const char *oldname, const char *newname)
+{
+ struct string_list skip = STRING_LIST_INIT_NODUP;
+ struct strbuf err = STRBUF_INIT;
+ int ret;
+
+ string_list_insert(&skip, oldname);
+ ret = !verify_refname_available(newname, NULL, &skip,
+ 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);
+ strbuf_release(&err);
+ return ret;
+}
+
+static int write_ref_to_lockfile(struct ref_lock *lock, const unsigned char *sha1);
+static int commit_ref_update(struct ref_lock *lock,
+ const unsigned char *sha1, const char *logmsg);
+
int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)
{
unsigned char sha1[20], orig_sha1[20];
@@ -2597,21 +2869,20 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
struct stat loginfo;
int log = !lstat(git_path("logs/%s", oldrefname), &loginfo);
const char *symref = NULL;
+ struct strbuf err = STRBUF_INIT;
if (log && S_ISLNK(loginfo.st_mode))
return error("reflog for %s is a symlink", oldrefname);
- symref = resolve_ref_unsafe(oldrefname, orig_sha1, 1, &flag);
+ symref = resolve_ref_unsafe(oldrefname, RESOLVE_REF_READING,
+ orig_sha1, &flag);
if (flag & REF_ISSYMREF)
return error("refname %s is a symbolic ref, renaming it is not supported",
oldrefname);
if (!symref)
return error("refname %s not found", oldrefname);
- if (!is_refname_available(newrefname, oldrefname, get_packed_refs(&ref_cache)))
- return 1;
-
- if (!is_refname_available(newrefname, oldrefname, get_loose_refs(&ref_cache)))
+ if (!rename_ref_available(oldrefname, newrefname))
return 1;
if (log && rename(git_path("logs/%s", oldrefname), git_path(TMP_RENAMED_LOG)))
@@ -2623,7 +2894,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
goto rollback;
}
- if (!read_ref_full(newrefname, sha1, 1, &flag) &&
+ if (!read_ref_full(newrefname, RESOLVE_REF_READING, sha1, NULL) &&
delete_ref(newrefname, sha1, REF_NODEREF)) {
if (errno==EISDIR) {
if (remove_empty_directories(git_path("%s", newrefname))) {
@@ -2641,14 +2912,16 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
logmoved = log;
- lock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL);
+ lock = lock_ref_sha1_basic(newrefname, NULL, NULL, NULL, 0, NULL, &err);
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;
}
- lock->force_write = 1;
hashcpy(lock->old_sha1, orig_sha1);
- if (write_ref_sha1(lock, orig_sha1, logmsg)) {
+
+ if (write_ref_to_lockfile(lock, orig_sha1) ||
+ commit_ref_update(lock, orig_sha1, logmsg)) {
error("unable to write current sha1 into %s", newrefname);
goto rollback;
}
@@ -2656,16 +2929,17 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
return 0;
rollback:
- lock = lock_ref_sha1_basic(oldrefname, NULL, 0, NULL);
+ lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, NULL, 0, NULL, &err);
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;
}
- lock->force_write = 1;
flag = log_all_ref_updates;
log_all_ref_updates = 0;
- if (write_ref_sha1(lock, orig_sha1, NULL))
+ if (write_ref_to_lockfile(lock, orig_sha1) ||
+ commit_ref_update(lock, orig_sha1, NULL))
error("unable to write current sha1 into %s", oldrefname);
log_all_ref_updates = flag;
@@ -2681,7 +2955,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
return 1;
}
-int close_ref(struct ref_lock *lock)
+static int close_ref(struct ref_lock *lock)
{
if (close_lock_file(lock->lk))
return -1;
@@ -2689,7 +2963,7 @@ int close_ref(struct ref_lock *lock)
return 0;
}
-int commit_ref(struct ref_lock *lock)
+static int commit_ref(struct ref_lock *lock)
{
if (commit_lock_file(lock->lk))
return -1;
@@ -2697,16 +2971,6 @@ int commit_ref(struct ref_lock *lock)
return 0;
}
-void unlock_ref(struct ref_lock *lock)
-{
- /* Do not free lock->lk -- atexit() still looks at them */
- if (lock->lk)
- rollback_lock_file(lock->lk);
- free(lock->ref_name);
- free(lock->orig_ref_name);
- free(lock);
-}
-
/*
* copy the reflog message msg to buf, which has been allocated sufficiently
* large, while cleaning up the whitespaces. Especially, convert LF to space,
@@ -2733,6 +2997,7 @@ 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;
@@ -2743,28 +3008,38 @@ int log_ref_setup(const char *refname, char *logfile, int bufsize)
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;
}
logfd = open(logfile, oflags, 0666);
if (logfd < 0) {
- if (!(oflags & O_CREAT) && errno == ENOENT)
+ if (!(oflags & O_CREAT) && (errno == ENOENT || errno == EISDIR))
return 0;
- if ((oflags & O_CREAT) && errno == EISDIR) {
+ if (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);
@@ -2772,15 +3047,37 @@ int log_ref_setup(const char *refname, char *logfile, int bufsize)
return 0;
}
+static int log_ref_write_fd(int fd, const unsigned char *old_sha1,
+ const unsigned char *new_sha1,
+ const char *committer, const char *msg)
+{
+ int msglen, written;
+ unsigned maxlen, len;
+ char *logrec;
+
+ msglen = msg ? strlen(msg) : 0;
+ maxlen = strlen(committer) + msglen + 100;
+ logrec = xmalloc(maxlen);
+ len = sprintf(logrec, "%s %s %s\n",
+ sha1_to_hex(old_sha1),
+ sha1_to_hex(new_sha1),
+ committer);
+ if (msglen)
+ len += copy_msg(logrec + len - 1, msg) - 1;
+
+ written = len <= maxlen ? write_in_full(fd, logrec, len) : -1;
+ free(logrec);
+ if (written != len)
+ return -1;
+
+ return 0;
+}
+
static int log_ref_write(const char *refname, const unsigned char *old_sha1,
const unsigned char *new_sha1, const char *msg)
{
- int logfd, result, written, oflags = O_APPEND | O_WRONLY;
- unsigned maxlen, len;
- int msglen;
+ int logfd, result, oflags = O_APPEND | O_WRONLY;
char log_file[PATH_MAX];
- char *logrec;
- const char *committer;
if (log_all_ref_updates < 0)
log_all_ref_updates = !is_bare_repository();
@@ -2792,60 +3089,74 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,
logfd = open(log_file, oflags);
if (logfd < 0)
return 0;
- msglen = msg ? strlen(msg) : 0;
- committer = git_committer_info(0);
- maxlen = strlen(committer) + msglen + 100;
- logrec = xmalloc(maxlen);
- len = sprintf(logrec, "%s %s %s\n",
- sha1_to_hex(old_sha1),
- sha1_to_hex(new_sha1),
- committer);
- if (msglen)
- 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);
+ result = log_ref_write_fd(logfd, old_sha1, new_sha1,
+ git_committer_info(0), msg);
+ if (result) {
+ 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") || starts_with(refname, "refs/heads/");
}
-int write_ref_sha1(struct ref_lock *lock,
- const unsigned char *sha1, const char *logmsg)
+/*
+ * Write sha1 into the open lockfile, then close the lockfile. On
+ * errors, rollback the lockfile and set errno to reflect the problem.
+ */
+static int write_ref_to_lockfile(struct ref_lock *lock,
+ const unsigned char *sha1)
{
static char term = '\n';
struct object *o;
- if (!lock)
- return -1;
- if (!lock->force_write && !hashcmp(lock->old_sha1, sha1)) {
- unlock_ref(lock);
- return 0;
- }
o = parse_object(sha1);
if (!o) {
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;
}
+ return 0;
+}
+
+/*
+ * Commit a change to a loose reference that has already been written
+ * to the loose reference lockfile. Also update the reflogs if
+ * necessary, using the specified lockmsg (which can be NULL).
+ */
+static int commit_ref_update(struct ref_lock *lock,
+ const unsigned char *sha1, const char *logmsg)
+{
clear_loose_ref_cache(&ref_cache);
if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0 ||
(strcmp(lock->ref_name, lock->orig_ref_name) &&
@@ -2869,7 +3180,8 @@ int write_ref_sha1(struct ref_lock *lock,
unsigned char head_sha1[20];
int head_flag;
const char *head_ref;
- head_ref = resolve_ref_unsafe("HEAD", head_sha1, 1, &head_flag);
+ head_ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
+ head_sha1, &head_flag);
if (head_ref && (head_flag & REF_ISSYMREF) &&
!strcmp(head_ref, lock->ref_name))
log_ref_write("HEAD", lock->old_sha1, sha1, logmsg);
@@ -2946,122 +3258,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;
- 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);
+ 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 (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;
+ if (cb.found_it)
+ return 0;
+
+ for_each_reflog_ent(refname, read_ref_at_ent_oldest, &cb);
+
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];
@@ -3149,29 +3476,54 @@ int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void
bp = find_beginning_of_line(buf, scanp);
- if (*bp != '\n') {
- strbuf_splice(&sb, 0, 0, buf, endp - buf);
- if (pos)
- break; /* need to fill another block */
- scanp = buf - 1; /* leave loop */
- } else {
+ if (*bp == '\n') {
/*
- * (bp + 1) thru endp is the beginning of the
- * current line we have in sb
+ * The newline is the end of the previous line,
+ * so we know we have complete line starting
+ * at (bp + 1). Prefix it onto any prior data
+ * we collected for the line and process it.
*/
strbuf_splice(&sb, 0, 0, bp + 1, endp - (bp + 1));
scanp = bp;
endp = bp + 1;
+ ret = show_one_reflog_ent(&sb, fn, cb_data);
+ strbuf_reset(&sb);
+ if (ret)
+ break;
+ } else if (!pos) {
+ /*
+ * We are at the start of the buffer, and the
+ * start of the file; there is no previous
+ * line, and we have everything for this one.
+ * Process it, and we can end the loop.
+ */
+ strbuf_splice(&sb, 0, 0, buf, endp - buf);
+ ret = show_one_reflog_ent(&sb, fn, cb_data);
+ strbuf_reset(&sb);
+ break;
}
- ret = show_one_reflog_ent(&sb, fn, cb_data);
- strbuf_reset(&sb);
- if (ret)
+
+ if (bp == buf) {
+ /*
+ * We are at the start of the buffer, and there
+ * is more file to read backwards. Which means
+ * we are in the middle of a line. Note that we
+ * may get here even if *bp was a newline; that
+ * just means we are at the exact end of the
+ * previous line, rather than some spot in the
+ * middle.
+ *
+ * Save away what we have to be combined with
+ * the data from the next read.
+ */
+ strbuf_splice(&sb, 0, 0, buf, endp - buf);
break;
+ }
}
}
if (!ret && sb.len)
- ret = show_one_reflog_ent(&sb, fn, cb_data);
+ die("BUG: reverse reflog parser had leftover data");
fclose(logfp);
strbuf_release(&sb);
@@ -3214,7 +3566,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) {
@@ -3225,7 +3577,7 @@ static int do_for_each_reflog(struct strbuf *name, each_ref_fn fn, void *cb_data
retval = do_for_each_reflog(name, fn, cb_data);
} else {
unsigned char sha1[20];
- if (read_ref_full(name->buf, sha1, 0, NULL))
+ if (read_ref_full(name->buf, 0, sha1, NULL))
retval = error("bad ref for %s", name->buf);
else
retval = fn(name->buf, sha1, 0, cb_data);
@@ -3249,148 +3601,371 @@ 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 null_sha1 to delete the ref. To check the old value
+ * while the ref is locked, set (flags & REF_HAVE_OLD) and set
+ * old_sha1 to the old value, or to null_sha1 to ensure the ref does
+ * not exist before update.
+ */
+struct ref_update {
+ /*
+ * If (flags & REF_HAVE_NEW), set the reference to this value:
+ */
+ unsigned char new_sha1[20];
+ /*
+ * If (flags & REF_HAVE_OLD), check that the reference
+ * previously had this value:
+ */
+ unsigned char old_sha1[20];
+ /*
+ * One or more of REF_HAVE_NEW, REF_HAVE_OLD, REF_NODEREF,
+ * REF_DELETING, and REF_ISPRUNING:
+ */
+ unsigned int flags;
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;
- }
+ int type;
+ char *msg;
+ 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)
+{
+ assert(err);
+
+ return xcalloc(1, sizeof(struct ref_transaction));
+}
+
+void ref_transaction_free(struct ref_transaction *transaction)
+{
+ int i;
+
+ if (!transaction)
+ return;
+
+ for (i = 0; i < transaction->nr; i++) {
+ free(transaction->updates[i]->msg);
+ free(transaction->updates[i]);
}
- return lock;
+ free(transaction->updates);
+ free(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)
+static struct ref_update *add_update(struct ref_transaction *transaction,
+ const char *refname)
{
- 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;
+ 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,
+ unsigned int flags, const char *msg,
+ struct strbuf *err)
+{
+ struct ref_update *update;
+
+ assert(err);
+
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: update called for transaction that is not open");
+
+ if (new_sha1 && !is_null_sha1(new_sha1) &&
+ check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+ strbuf_addf(err, "refusing to update ref with bad name %s",
+ refname);
+ return -1;
+ }
+
+ update = add_update(transaction, refname);
+ if (new_sha1) {
+ hashcpy(update->new_sha1, new_sha1);
+ flags |= REF_HAVE_NEW;
}
+ if (old_sha1) {
+ hashcpy(update->old_sha1, old_sha1);
+ flags |= REF_HAVE_OLD;
+ }
+ update->flags = flags;
+ if (msg)
+ update->msg = xstrdup(msg);
return 0;
}
-int update_ref(const char *action, const char *refname,
- const unsigned char *sha1, const unsigned char *oldval,
- int flags, enum action_on_err onerr)
+int ref_transaction_create(struct ref_transaction *transaction,
+ const char *refname,
+ const unsigned char *new_sha1,
+ unsigned int flags, const char *msg,
+ struct strbuf *err)
{
- struct ref_lock *lock;
- lock = update_ref_lock(refname, oldval, flags, NULL, onerr);
- if (!lock)
- return 1;
- return update_ref_write(action, refname, sha1, lock, onerr);
+ if (!new_sha1 || is_null_sha1(new_sha1))
+ die("BUG: create called without valid new_sha1");
+ return ref_transaction_update(transaction, refname, new_sha1,
+ null_sha1, flags, msg, err);
}
-static int ref_update_compare(const void *r1, const void *r2)
+int ref_transaction_delete(struct ref_transaction *transaction,
+ const char *refname,
+ const unsigned char *old_sha1,
+ unsigned int flags, const char *msg,
+ struct strbuf *err)
{
- const struct ref_update * const *u1 = r1;
- const struct ref_update * const *u2 = r2;
- return strcmp((*u1)->ref_name, (*u2)->ref_name);
+ if (old_sha1 && is_null_sha1(old_sha1))
+ die("BUG: delete called with old_sha1 set to zeros");
+ return ref_transaction_update(transaction, refname,
+ null_sha1, old_sha1,
+ flags, msg, err);
}
-static int ref_update_reject_duplicates(struct ref_update **updates, int n,
- enum action_on_err onerr)
+int ref_transaction_verify(struct ref_transaction *transaction,
+ const char *refname,
+ const unsigned char *old_sha1,
+ unsigned int flags,
+ struct strbuf *err)
{
- int i;
+ if (!old_sha1)
+ die("BUG: verify called with old_sha1 set to NULL");
+ return ref_transaction_update(transaction, refname,
+ NULL, old_sha1,
+ flags, NULL, err);
+}
+
+int update_ref(const char *msg, const char *refname,
+ const unsigned char *new_sha1, const unsigned char *old_sha1,
+ unsigned int flags, enum action_on_err onerr)
+{
+ struct ref_transaction *t;
+ struct strbuf err = STRBUF_INIT;
+
+ t = ref_transaction_begin(&err);
+ if (!t ||
+ ref_transaction_update(t, refname, new_sha1, old_sha1,
+ flags, msg, &err) ||
+ ref_transaction_commit(t, &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;
+ }
+ strbuf_release(&err);
+ ref_transaction_free(t);
+ return 0;
+}
+
+static int ref_update_reject_duplicates(struct string_list *refnames,
+ struct strbuf *err)
+{
+ int i, n = refnames->nr;
+
+ assert(err);
+
for (i = 1; i < n; i++)
- if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) {
- 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 (!strcmp(refnames->items[i - 1].string, refnames->items[i].string)) {
+ strbuf_addf(err,
+ "Multiple updates for ref '%s' not allowed.",
+ refnames->items[i].string);
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,
+ struct strbuf *err)
{
- int ret = 0, delnum = 0, i;
- struct ref_update **updates;
- int *types;
- struct ref_lock **locks;
- const char **delnames;
+ int ret = 0, i;
+ int n = transaction->nr;
+ struct ref_update **updates = transaction->updates;
+ struct string_list refs_to_delete = STRING_LIST_INIT_NODUP;
+ struct string_list_item *ref_to_delete;
+ struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
- if (!updates_orig || !n)
- return 0;
+ assert(err);
- /* Allocate work space */
- updates = xmalloc(sizeof(*updates) * n);
- types = xmalloc(sizeof(*types) * n);
- locks = xcalloc(n, sizeof(*locks));
- delnames = xmalloc(sizeof(*delnames) * n);
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: commit called for transaction that is not open");
- /* 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);
- if (ret)
+ if (!n) {
+ transaction->state = REF_TRANSACTION_CLOSED;
+ return 0;
+ }
+
+ /* Fail if a refname appears more than once in the transaction: */
+ for (i = 0; i < n; i++)
+ 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;
goto cleanup;
+ }
- /* Acquire all locks while verifying old values */
+ /*
+ * Acquire all locks, verify old values if provided, check
+ * that new values are valid, and write new values to the
+ * lockfiles, ready to be activated. Only keep one lockfile
+ * open at a time to avoid running out of file descriptors.
+ */
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]) {
- ret = 1;
+ struct ref_update *update = updates[i];
+
+ if ((update->flags & REF_HAVE_NEW) &&
+ is_null_sha1(update->new_sha1))
+ update->flags |= REF_DELETING;
+ update->lock = lock_ref_sha1_basic(
+ update->refname,
+ ((update->flags & REF_HAVE_OLD) ?
+ update->old_sha1 : NULL),
+ &affected_refnames, NULL,
+ update->flags,
+ &update->type,
+ err);
+ if (!update->lock) {
+ char *reason;
+
+ ret = (errno == ENOTDIR)
+ ? TRANSACTION_NAME_CONFLICT
+ : TRANSACTION_GENERIC_ERROR;
+ reason = strbuf_detach(err, NULL);
+ strbuf_addf(err, "Cannot lock ref '%s': %s",
+ update->refname, reason);
+ free(reason);
goto cleanup;
}
+ if ((update->flags & REF_HAVE_NEW) &&
+ !(update->flags & REF_DELETING)) {
+ int overwriting_symref = ((update->type & REF_ISSYMREF) &&
+ (update->flags & REF_NODEREF));
+
+ if (!overwriting_symref &&
+ !hashcmp(update->lock->old_sha1, update->new_sha1)) {
+ /*
+ * The reference already has the desired
+ * value, so we don't need to write it.
+ */
+ } else if (write_ref_to_lockfile(update->lock,
+ update->new_sha1)) {
+ /*
+ * The lock was freed upon failure of
+ * write_ref_to_lockfile():
+ */
+ update->lock = NULL;
+ strbuf_addf(err, "Cannot update the ref '%s'.",
+ update->refname);
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto cleanup;
+ } else {
+ update->flags |= REF_NEEDS_COMMIT;
+ }
+ }
+ if (!(update->flags & REF_NEEDS_COMMIT)) {
+ /*
+ * We didn't have to write anything to the lockfile.
+ * Close it to free up the file descriptor:
+ */
+ if (close_ref(update->lock)) {
+ strbuf_addf(err, "Couldn't close %s.lock",
+ update->refname);
+ 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 (update->flags & REF_NEEDS_COMMIT) {
+ if (commit_ref_update(update->lock,
+ update->new_sha1, update->msg)) {
+ /* freed by commit_ref_update(): */
+ update->lock = NULL;
+ strbuf_addf(err, "Cannot update the ref '%s'.",
+ update->refname);
+ ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
+ } else {
+ /* freed by commit_ref_update(): */
+ update->lock = NULL;
+ }
}
+ }
/* 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->flags & REF_DELETING) {
+ if (delete_ref_loose(update->lock, update->type, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto cleanup;
+ }
+
+ if (!(update->flags & REF_ISPRUNING))
+ string_list_append(&refs_to_delete,
+ update->lock->ref_name);
}
- ret |= repack_without_refs(delnames, delnum);
- for (i = 0; i < delnum; i++)
- unlink_or_warn(git_path("logs/%s", delnames[i]));
+ }
+
+ if (repack_without_refs(&refs_to_delete, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto cleanup;
+ }
+ for_each_string_list_item(ref_to_delete, &refs_to_delete)
+ unlink_or_warn(git_path("logs/%s", ref_to_delete->string));
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);
- free(delnames);
+ if (updates[i]->lock)
+ unlock_ref(updates[i]->lock);
+ string_list_clear(&refs_to_delete, 0);
+ string_list_clear(&affected_refnames, 0);
return ret;
}
@@ -3529,3 +4104,145 @@ int ref_is_hidden(const char *refname)
}
return 0;
}
+
+struct expire_reflog_cb {
+ unsigned int flags;
+ reflog_expiry_should_prune_fn *should_prune_fn;
+ void *policy_cb;
+ FILE *newlog;
+ unsigned char last_kept_sha1[20];
+};
+
+static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+ const char *email, unsigned long timestamp, int tz,
+ const char *message, void *cb_data)
+{
+ struct expire_reflog_cb *cb = cb_data;
+ struct expire_reflog_policy_cb *policy_cb = cb->policy_cb;
+
+ if (cb->flags & EXPIRE_REFLOGS_REWRITE)
+ osha1 = cb->last_kept_sha1;
+
+ if ((*cb->should_prune_fn)(osha1, nsha1, email, timestamp, tz,
+ message, policy_cb)) {
+ if (!cb->newlog)
+ printf("would prune %s", message);
+ else if (cb->flags & EXPIRE_REFLOGS_VERBOSE)
+ printf("prune %s", message);
+ } else {
+ if (cb->newlog) {
+ fprintf(cb->newlog, "%s %s %s %lu %+05d\t%s",
+ sha1_to_hex(osha1), sha1_to_hex(nsha1),
+ email, timestamp, tz, message);
+ hashcpy(cb->last_kept_sha1, nsha1);
+ }
+ if (cb->flags & EXPIRE_REFLOGS_VERBOSE)
+ printf("keep %s", message);
+ }
+ return 0;
+}
+
+int reflog_expire(const char *refname, const unsigned char *sha1,
+ unsigned int flags,
+ reflog_expiry_prepare_fn prepare_fn,
+ reflog_expiry_should_prune_fn should_prune_fn,
+ reflog_expiry_cleanup_fn cleanup_fn,
+ void *policy_cb_data)
+{
+ static struct lock_file reflog_lock;
+ struct expire_reflog_cb cb;
+ struct ref_lock *lock;
+ char *log_file;
+ int status = 0;
+ int type;
+ struct strbuf err = STRBUF_INIT;
+
+ memset(&cb, 0, sizeof(cb));
+ cb.flags = flags;
+ cb.policy_cb = policy_cb_data;
+ cb.should_prune_fn = should_prune_fn;
+
+ /*
+ * The reflog file is locked by holding the lock on the
+ * reference itself, plus we might need to update the
+ * reference if --updateref was specified:
+ */
+ lock = lock_ref_sha1_basic(refname, sha1, NULL, NULL, 0, &type, &err);
+ if (!lock) {
+ error("cannot lock ref '%s': %s", refname, err.buf);
+ strbuf_release(&err);
+ return -1;
+ }
+ if (!reflog_exists(refname)) {
+ unlock_ref(lock);
+ return 0;
+ }
+
+ log_file = git_pathdup("logs/%s", refname);
+ if (!(flags & EXPIRE_REFLOGS_DRY_RUN)) {
+ /*
+ * Even though holding $GIT_DIR/logs/$reflog.lock has
+ * no locking implications, we use the lock_file
+ * machinery here anyway because it does a lot of the
+ * work we need, including cleaning up if the program
+ * exits unexpectedly.
+ */
+ if (hold_lock_file_for_update(&reflog_lock, log_file, 0) < 0) {
+ struct strbuf err = STRBUF_INIT;
+ unable_to_lock_message(log_file, errno, &err);
+ error("%s", err.buf);
+ strbuf_release(&err);
+ goto failure;
+ }
+ cb.newlog = fdopen_lock_file(&reflog_lock, "w");
+ if (!cb.newlog) {
+ error("cannot fdopen %s (%s)",
+ reflog_lock.filename.buf, strerror(errno));
+ goto failure;
+ }
+ }
+
+ (*prepare_fn)(refname, sha1, cb.policy_cb);
+ for_each_reflog_ent(refname, expire_reflog_ent, &cb);
+ (*cleanup_fn)(cb.policy_cb);
+
+ if (!(flags & EXPIRE_REFLOGS_DRY_RUN)) {
+ /*
+ * It doesn't make sense to adjust a reference pointed
+ * to by a symbolic ref based on expiring entries in
+ * the symbolic reference's reflog. Nor can we update
+ * a reference if there are no remaining reflog
+ * entries.
+ */
+ int update = (flags & EXPIRE_REFLOGS_UPDATE_REF) &&
+ !(type & REF_ISSYMREF) &&
+ !is_null_sha1(cb.last_kept_sha1);
+
+ if (close_lock_file(&reflog_lock)) {
+ status |= error("couldn't write %s: %s", log_file,
+ strerror(errno));
+ } else if (update &&
+ (write_in_full(lock->lock_fd,
+ sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
+ write_str_in_full(lock->lock_fd, "\n") != 1 ||
+ close_ref(lock) < 0)) {
+ status |= error("couldn't write %s",
+ lock->lk->filename.buf);
+ rollback_lock_file(&reflog_lock);
+ } else if (commit_lock_file(&reflog_lock)) {
+ status |= error("unable to commit reflog '%s' (%s)",
+ log_file, strerror(errno));
+ } else if (update && commit_ref(lock)) {
+ status |= error("couldn't set %s", lock->ref_name);
+ }
+ }
+ free(log_file);
+ unlock_ref(lock);
+ return status;
+
+ failure:
+ rollback_lock_file(&reflog_lock);
+ free(log_file);
+ unlock_ref(lock);
+ return -1;
+}