diff options
Diffstat (limited to 'refs.c')
-rw-r--r-- | refs.c | 1554 |
1 files changed, 1038 insertions, 516 deletions
@@ -1,10 +1,19 @@ #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 @@ -25,6 +34,36 @@ static unsigned char refname_disposition[256] = { }; /* + * 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 + +/* + * 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 @@ -64,17 +103,10 @@ static int check_refname_component(const char *refname, int flags) 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; } @@ -188,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. */ /* @@ -197,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 @@ -237,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 @@ -275,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) @@ -283,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); @@ -779,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 { @@ -973,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. * @@ -983,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; } /* @@ -1031,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 ")) @@ -1049,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; @@ -1059,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, @@ -1072,6 +1268,8 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir) last->flag |= REF_KNOWS_PEELED; } } + + strbuf_release(&line); } /* @@ -1183,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); } @@ -1307,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; @@ -1321,35 +1526,51 @@ 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; } } /* 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) +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)) { - errno = EINVAL; - return NULL; - } + 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; @@ -1374,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 */ @@ -1396,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; } } @@ -1442,32 +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; - errno = EINVAL; - 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 */ @@ -1477,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, @@ -1605,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; /* @@ -1646,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) @@ -1756,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); } @@ -1771,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; @@ -1851,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); @@ -1942,11 +2191,23 @@ 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); @@ -2019,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); @@ -2048,7 +2310,8 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) 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 (reflog_exists(path)) @@ -2068,10 +2331,16 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) return logs_found; } -/* This function should make sure errno is meaningful on error */ +/* + * 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; @@ -2079,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; @@ -2095,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; } @@ -2125,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)) { @@ -2145,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 @@ -2158,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; @@ -2169,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)); } /* @@ -2217,13 +2476,12 @@ 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; @@ -2259,15 +2517,19 @@ int commit_packed_refs(void) 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); + 0, write_packed_entry_fn, out); + if (commit_lock_file(packed_ref_cache->lock)) { save_errno = errno; error = -1; @@ -2387,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) @@ -2424,93 +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, struct strbuf *err) +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, ret, 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)) { - 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]); + 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) { /* @@ -2521,61 +2737,51 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err) 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 */ ret = commit_packed_refs(); - if (ret && err) + 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, NULL); -} + 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; } /* @@ -2632,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]; @@ -2640,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))) @@ -2666,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))) { @@ -2684,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; } @@ -2699,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; @@ -2724,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; @@ -2732,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; @@ -2740,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, @@ -2798,10 +3019,10 @@ int log_ref_setup(const char *refname, char *logfile, int bufsize) 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)) { int save_errno = errno; error("There are still logs under '%s'", @@ -2826,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(); @@ -2846,19 +3089,9 @@ 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 (written != len) { + 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); @@ -2879,21 +3112,16 @@ int is_branch(const char *refname) 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) +/* + * 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) { - errno = EINVAL; - 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", @@ -2913,11 +3141,22 @@ int write_ref_sha1(struct ref_lock *lock, 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); + 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) && @@ -2941,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); @@ -3104,7 +3344,7 @@ static int read_ref_at_ent_oldest(unsigned char *osha1, unsigned char *nsha1, 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) { @@ -3122,8 +3362,12 @@ int read_ref_at(const char *refname, unsigned long at_time, int cnt, for_each_reflog_ent_reverse(refname, read_ref_at_ent, &cb); - if (!cb.reccnt) - die("Log for %s is empty.", refname); + if (!cb.reccnt) { + if (flags & GET_SHA1_QUIETLY) + exit(128); + else + die("Log for %s is empty.", refname); + } if (cb.found_it) return 0; @@ -3232,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); @@ -3308,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); @@ -3332,60 +3601,50 @@ 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) -{ - 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 UPDATE_REFS_MSG_ON_ERR: error(str, refname); break; - case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break; - case UPDATE_REFS_QUIET_ON_ERR: break; - } - } - return lock; -} - -static int update_ref_write(const char *action, const char *refname, - const unsigned char *sha1, struct ref_lock *lock, - struct strbuf *err, enum action_on_err onerr) -{ - if (write_ref_sha1(lock, sha1, action) < 0) { - const char *str = "Cannot update the ref '%s'."; - if (err) - strbuf_addf(err, str, refname); - - switch (onerr) { - case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break; - case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break; - case UPDATE_REFS_QUIET_ON_ERR: break; - } - return 1; - } - return 0; -} - /** - * 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. + * 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]; - int flags; /* REF_NODEREF? */ - int have_old; /* 1 if old_sha1 is valid, 0 otherwise */ + /* + * One or more of REF_HAVE_NEW, REF_HAVE_OLD, REF_NODEREF, + * REF_DELETING, and REF_ISPRUNING: + */ + unsigned int flags; struct ref_lock *lock; 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. @@ -3394,10 +3653,13 @@ struct ref_transaction { struct ref_update **updates; size_t alloc; size_t nr; + enum ref_transaction_state state; }; -struct ref_transaction *ref_transaction_begin(void) +struct ref_transaction *ref_transaction_begin(struct strbuf *err) { + assert(err); + return xcalloc(1, sizeof(struct ref_transaction)); } @@ -3408,9 +3670,10 @@ void ref_transaction_free(struct ref_transaction *transaction) if (!transaction) return; - for (i = 0; i < transaction->nr; i++) + for (i = 0; i < transaction->nr; i++) { + free(transaction->updates[i]->msg); free(transaction->updates[i]); - + } free(transaction->updates); free(transaction); } @@ -3431,138 +3694,243 @@ 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, + unsigned int flags, const char *msg, struct strbuf *err) { struct ref_update *update; - if (have_old && !old_sha1) - die("BUG: have_old is true but old_sha1 is NULL"); + 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); - hashcpy(update->new_sha1, new_sha1); - update->flags = flags; - update->have_old = have_old; - if (have_old) + 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; } -void ref_transaction_create(struct ref_transaction *transaction, - const char *refname, - const unsigned char *new_sha1, - int flags) +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_update *update = add_update(transaction, refname); - - assert(!is_null_sha1(new_sha1)); - hashcpy(update->new_sha1, new_sha1); - hashclr(update->old_sha1); - update->flags = flags; - update->have_old = 1; + 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); } -void ref_transaction_delete(struct ref_transaction *transaction, - const char *refname, - const unsigned char *old_sha1, - int flags, int have_old) +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) { - struct ref_update *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); - } + 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); } -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_verify(struct ref_transaction *transaction, + const char *refname, + const unsigned char *old_sha1, + unsigned int flags, + 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, NULL, onerr); + 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); } -static int ref_update_compare(const void *r1, const void *r2) +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) { - const struct ref_update * const *u1 = r1; - const struct ref_update * const *u2 = r2; - return strcmp((*u1)->refname, (*u2)->refname); + 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 ref_update **updates, int n, +static int ref_update_reject_duplicates(struct string_list *refnames, struct strbuf *err) { - int i; - for (i = 1; i < n; i++) - if (!strcmp(updates[i - 1]->refname, updates[i]->refname)) { - const char *str = - "Multiple updates for ref '%s' not allowed."; - if (err) - strbuf_addf(err, str, updates[i]->refname); + int i, n = refnames->nr; + + assert(err); + for (i = 1; i < n; i++) + 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 ref_transaction_commit(struct ref_transaction *transaction, - const char *msg, struct strbuf *err) + struct strbuf *err) { - int ret = 0, delnum = 0, i; - 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 (!n) - return 0; + assert(err); - /* Allocate work space */ - 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 */ - qsort(updates, n, sizeof(*updates), ref_update_compare); - ret = ref_update_reject_duplicates(updates, n, err); - 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++) { struct ref_update *update = updates[i]; - update->lock = update_ref_lock(update->refname, - (update->have_old ? - update->old_sha1 : NULL), - update->flags, - &update->type, - UPDATE_REFS_QUIET_ON_ERR); + 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) { - if (err) - strbuf_addf(err, "Cannot lock the ref '%s'.", - update->refname); - ret = 1; + 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++) { struct ref_update *update = updates[i]; - if (!is_null_sha1(update->new_sha1)) { - ret = update_ref_write(msg, - update->refname, - update->new_sha1, - update->lock, err, - UPDATE_REFS_QUIET_ON_ERR); - update->lock = NULL; /* freed by update_ref_write */ - if (ret) + 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; + } } } @@ -3570,22 +3938,34 @@ int ref_transaction_commit(struct ref_transaction *transaction, for (i = 0; i < n; i++) { struct ref_update *update = updates[i]; - if (update->lock) { - delnames[delnum++] = update->lock->ref_name; - ret |= delete_ref_loose(update->lock, update->type); + 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, err); - 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 (updates[i]->lock) unlock_ref(updates[i]->lock); - free(delnames); + string_list_clear(&refs_to_delete, 0); + string_list_clear(&affected_refnames, 0); return ret; } @@ -3724,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; +} |