#include "../refs.h"
#include "refs-internal.h"
#include "ref-cache.h"
+#include "packed-backend.h"
#include "../iterator.h"
#include "../dir-iterator.h"
#include "../lockfile.h"
#include "../object.h"
#include "../dir.h"
+#include "../chdir-notify.h"
-struct ref_lock {
- char *ref_name;
- struct lock_file *lk;
- struct object_id old_oid;
-};
+/*
+ * This backend uses the following flags in `ref_update::flags` for
+ * internal bookkeeping purposes. Their numerical values must not
+ * conflict with REF_NO_DEREF, REF_FORCE_CREATE_REFLOG, REF_HAVE_NEW,
+ * REF_HAVE_OLD, or REF_IS_PRUNING, which are also stored in
+ * `ref_update::flags`.
+ */
/*
- * Return true if refname, which has the specified oid and flags, can
- * be resolved to an object in the database. If the referred-to object
- * does not exist, emit a warning and return false.
+ * Used as a flag in ref_update::flags when a loose ref is being
+ * pruned. This flag must only be used when REF_NO_DEREF is set.
*/
-static int ref_resolves_to_object(const char *refname,
- const struct object_id *oid,
- unsigned int flags)
-{
- if (flags & REF_ISBROKEN)
- return 0;
- if (!has_sha1_file(oid->hash)) {
- error("%s does not point to a valid object!", refname);
- return 0;
- }
- return 1;
-}
+#define REF_IS_PRUNING (1 << 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 (1 << 5)
-struct packed_ref_cache {
- struct ref_cache *cache;
+/*
+ * Used as a flag in ref_update::flags when the lockfile needs to be
+ * committed.
+ */
+#define REF_NEEDS_COMMIT (1 << 6)
- /*
- * Count of references to the data structure in this instance,
- * including the pointer from files_ref_store::packed if any.
- * The data will not be freed as long as the reference count
- * is nonzero.
- */
- unsigned int referrers;
+/*
+ * Used as a flag in ref_update::flags when we want to log a ref
+ * update but not actually perform it. This is used when a symbolic
+ * ref update is split up.
+ */
+#define REF_LOG_ONLY (1 << 7)
- /* The metadata from when this packed-refs cache was read */
- struct stat_validity validity;
-};
+/*
+ * Used as a flag in ref_update::flags when the ref_update was via an
+ * update to HEAD.
+ */
+#define REF_UPDATE_VIA_HEAD (1 << 8)
/*
- * Future: need to be in "struct repository"
- * when doing a full libification.
+ * Used as a flag in ref_update::flags when the loose reference has
+ * been deleted.
*/
+#define REF_DELETED_LOOSE (1 << 9)
+
+struct ref_lock {
+ char *ref_name;
+ struct lock_file lk;
+ struct object_id old_oid;
+};
+
struct files_ref_store {
struct ref_store base;
unsigned int store_flags;
char *gitdir;
char *gitcommondir;
- char *packed_refs_path;
struct ref_cache *loose;
- struct packed_ref_cache *packed;
- /*
- * Lock used for the "packed-refs" file. Note that this (and
- * thus the enclosing `files_ref_store`) must not be freed.
- */
- struct lock_file packed_refs_lock;
+ struct ref_store *packed_ref_store;
};
-/*
- * Increment the reference count of *packed_refs.
- */
-static void acquire_packed_ref_cache(struct packed_ref_cache *packed_refs)
-{
- packed_refs->referrers++;
-}
-
-/*
- * Decrease the reference count of *packed_refs. If it goes to zero,
- * free *packed_refs and return true; otherwise return false.
- */
-static int release_packed_ref_cache(struct packed_ref_cache *packed_refs)
-{
- if (!--packed_refs->referrers) {
- free_ref_cache(packed_refs->cache);
- stat_validity_clear(&packed_refs->validity);
- free(packed_refs);
- return 1;
- } else {
- return 0;
- }
-}
-
-static void clear_packed_ref_cache(struct files_ref_store *refs)
-{
- if (refs->packed) {
- struct packed_ref_cache *packed_refs = refs->packed;
-
- if (is_lock_file_locked(&refs->packed_refs_lock))
- die("BUG: packed-ref cache cleared while locked");
- refs->packed = NULL;
- release_packed_ref_cache(packed_refs);
- }
-}
-
static void clear_loose_ref_cache(struct files_ref_store *refs)
{
if (refs->loose) {
get_common_dir_noenv(&sb, gitdir);
refs->gitcommondir = strbuf_detach(&sb, NULL);
strbuf_addf(&sb, "%s/packed-refs", refs->gitcommondir);
- refs->packed_refs_path = strbuf_detach(&sb, NULL);
+ refs->packed_ref_store = packed_ref_store_create(sb.buf, flags);
+ strbuf_release(&sb);
+
+ chdir_notify_reparent("files-backend $GIT_DIR",
+ &refs->gitdir);
+ chdir_notify_reparent("files-backend $GIT_COMMONDIR",
+ &refs->gitcommondir);
return ref_store;
}
if (refs->store_flags & REF_STORE_MAIN)
return;
- die("BUG: operation %s only allowed for main ref store", caller);
+ BUG("operation %s only allowed for main ref store", caller);
}
/*
struct files_ref_store *refs;
if (ref_store->be != &refs_be_files)
- die("BUG: ref_store is type \"%s\" not \"files\" in %s",
+ BUG("ref_store is type \"%s\" not \"files\" in %s",
ref_store->be->name, caller);
refs = (struct files_ref_store *)ref_store;
if ((refs->store_flags & required_flags) != required_flags)
- die("BUG: operation %s requires abilities 0x%x, but only have 0x%x",
+ BUG("operation %s requires abilities 0x%x, but only have 0x%x",
caller, required_flags, refs->store_flags);
return refs;
}
-/* The length of a peeled reference line in packed-refs, including EOL: */
-#define PEELED_LINE_LENGTH 42
-
-/*
- * The packed-refs header line that we write out. Perhaps other
- * traits will be added later. The trailing space is required.
- */
-static const char PACKED_REFS_HEADER[] =
- "# pack-refs with: peeled fully-peeled \n";
-
-/*
- * Parse one line from a packed-refs file. Write the SHA1 to sha1.
- * Return a pointer to the refname within the line (null-terminated),
- * or NULL if there was a problem.
- */
-static const char *parse_ref_line(struct strbuf *line, struct object_id *oid)
-{
- const char *ref;
-
- if (parse_oid_hex(line->buf, oid, &ref) < 0)
- return NULL;
- if (!isspace(*ref++))
- return NULL;
-
- if (isspace(*ref))
- return NULL;
-
- if (line->buf[line->len - 1] != '\n')
- return NULL;
- line->buf[--line->len] = 0;
-
- return ref;
-}
-
-/*
- * Read from `packed_refs_file` into a newly-allocated
- * `packed_ref_cache` and return it. The return value will already
- * have its reference count incremented.
- *
- * A comment line of the form "# pack-refs with: " may contain zero or
- * more traits. We interpret the traits as follows:
- *
- * No traits:
- *
- * Probably no references are peeled. But if the file contains a
- * peeled value for a reference, we will use it.
- *
- * peeled:
- *
- * References under "refs/tags/", if they *can* be peeled, *are*
- * peeled in this file. References outside of "refs/tags/" are
- * probably not peeled even if they could have been, but if we find
- * a peeled value for such a reference we will use it.
- *
- * fully-peeled:
- *
- * All references in the file that can be peeled are peeled.
- * Inversely (and this is more important), any references in the
- * file for which no peeled value is recorded is not peelable. This
- * trait should typically be written alongside "peeled" for
- * compatibility with older clients, but we do not require it
- * (i.e., "peeled" is a no-op if "fully-peeled" is set).
- */
-static struct packed_ref_cache *read_packed_refs(const char *packed_refs_file)
-{
- FILE *f;
- struct packed_ref_cache *packed_refs = xcalloc(1, sizeof(*packed_refs));
- struct ref_entry *last = NULL;
- struct strbuf line = STRBUF_INIT;
- enum { PEELED_NONE, PEELED_TAGS, PEELED_FULLY } peeled = PEELED_NONE;
- struct ref_dir *dir;
-
- acquire_packed_ref_cache(packed_refs);
- packed_refs->cache = create_ref_cache(NULL, NULL);
- packed_refs->cache->root->flag &= ~REF_INCOMPLETE;
-
- f = fopen(packed_refs_file, "r");
- if (!f) {
- if (errno == ENOENT) {
- /*
- * This is OK; it just means that no
- * "packed-refs" file has been written yet,
- * which is equivalent to it being empty.
- */
- return packed_refs;
- } else {
- die_errno("couldn't read %s", packed_refs_file);
- }
- }
-
- stat_validity_update(&packed_refs->validity, fileno(f));
-
- dir = get_ref_dir(packed_refs->cache->root);
- while (strbuf_getwholeline(&line, f, '\n') != EOF) {
- struct object_id oid;
- const char *refname;
- const char *traits;
-
- if (skip_prefix(line.buf, "# pack-refs with:", &traits)) {
- if (strstr(traits, " fully-peeled "))
- peeled = PEELED_FULLY;
- else if (strstr(traits, " peeled "))
- peeled = PEELED_TAGS;
- /* perhaps other traits later as well */
- continue;
- }
-
- refname = parse_ref_line(&line, &oid);
- if (refname) {
- int flag = REF_ISPACKED;
-
- if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
- if (!refname_is_safe(refname))
- die("packed refname is dangerous: %s", refname);
- oidclr(&oid);
- flag |= REF_BAD_NAME | REF_ISBROKEN;
- }
- last = create_ref_entry(refname, &oid, flag);
- if (peeled == PEELED_FULLY ||
- (peeled == PEELED_TAGS && starts_with(refname, "refs/tags/")))
- last->flag |= REF_KNOWS_PEELED;
- add_ref_entry(dir, last);
- continue;
- }
- if (last &&
- line.buf[0] == '^' &&
- line.len == PEELED_LINE_LENGTH &&
- line.buf[PEELED_LINE_LENGTH - 1] == '\n' &&
- !get_oid_hex(line.buf + 1, &oid)) {
- oidcpy(&last->u.value.peeled, &oid);
- /*
- * Regardless of what the file header said,
- * we definitely know the value of *this*
- * reference:
- */
- last->flag |= REF_KNOWS_PEELED;
- }
- }
-
- fclose(f);
- strbuf_release(&line);
-
- return packed_refs;
-}
-
-static const char *files_packed_refs_path(struct files_ref_store *refs)
-{
- return refs->packed_refs_path;
-}
-
static void files_reflog_path(struct files_ref_store *refs,
struct strbuf *sb,
const char *refname)
{
- if (!refname) {
- /*
- * FIXME: of course this is wrong in multi worktree
- * setting. To be fixed real soon.
- */
- strbuf_addf(sb, "%s/logs", refs->gitcommondir);
- return;
- }
-
switch (ref_type(refname)) {
case REF_TYPE_PER_WORKTREE:
case REF_TYPE_PSEUDOREF:
strbuf_addf(sb, "%s/logs/%s", refs->gitcommondir, refname);
break;
default:
- die("BUG: unknown ref type %d of ref %s",
+ BUG("unknown ref type %d of ref %s",
ref_type(refname), refname);
}
}
strbuf_addf(sb, "%s/%s", refs->gitcommondir, refname);
break;
default:
- die("BUG: unknown ref type %d of ref %s",
+ BUG("unknown ref type %d of ref %s",
ref_type(refname), refname);
}
}
-/*
- * Check that the packed refs cache (if any) still reflects the
- * contents of the file. If not, clear the cache.
- */
-static void validate_packed_ref_cache(struct files_ref_store *refs)
-{
- if (refs->packed &&
- !stat_validity_check(&refs->packed->validity,
- files_packed_refs_path(refs)))
- clear_packed_ref_cache(refs);
-}
-
-/*
- * Get the packed_ref_cache for the specified files_ref_store,
- * creating and populating it if it hasn't been read before or if the
- * file has been changed (according to its `validity` field) since it
- * was last read. On the other hand, if we hold the lock, then assume
- * that the file hasn't been changed out from under us, so skip the
- * extra `stat()` call in `stat_validity_check()`.
- */
-static struct packed_ref_cache *get_packed_ref_cache(struct files_ref_store *refs)
-{
- const char *packed_refs_file = files_packed_refs_path(refs);
-
- if (!is_lock_file_locked(&refs->packed_refs_lock))
- validate_packed_ref_cache(refs);
-
- if (!refs->packed)
- refs->packed = read_packed_refs(packed_refs_file);
-
- return refs->packed;
-}
-
-static struct ref_dir *get_packed_ref_dir(struct packed_ref_cache *packed_ref_cache)
-{
- return get_ref_dir(packed_ref_cache->cache->root);
-}
-
-static struct ref_dir *get_packed_refs(struct files_ref_store *refs)
-{
- return get_packed_ref_dir(get_packed_ref_cache(refs));
-}
-
-/*
- * Add a reference to the in-memory packed reference cache. This may
- * only be called while the packed-refs file is locked (see
- * lock_packed_refs()). To actually write the packed-refs file, call
- * commit_packed_refs().
- */
-static void add_packed_ref(struct files_ref_store *refs,
- const char *refname, const struct object_id *oid)
-{
- struct packed_ref_cache *packed_ref_cache = get_packed_ref_cache(refs);
-
- if (!is_lock_file_locked(&refs->packed_refs_lock))
- die("BUG: packed refs not locked");
-
- if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
- die("Reference has invalid format: '%s'", refname);
-
- add_ref_entry(get_packed_ref_dir(packed_ref_cache),
- create_ref_entry(refname, oid, REF_ISPACKED));
-}
-
/*
* Read the loose references from the namespace dirname into dir
* (without recursing). dirname must end with '/'. dir must be the
if (!refs_resolve_ref_unsafe(&refs->base,
refname.buf,
RESOLVE_REF_READING,
- oid.hash, &flag)) {
+ &oid, &flag)) {
oidclr(&oid);
flag |= REF_ISBROKEN;
} else if (is_null_oid(&oid)) {
/*
* It is so astronomically unlikely
- * that NULL_SHA1 is the SHA-1 of an
+ * that null_oid is the OID of an
* actual object that we consider its
* appearance in a loose reference
* file to be repo corruption
return refs->loose;
}
-/*
- * Return the ref_entry for the given refname from the packed
- * references. If it does not exist, return NULL.
- */
-static struct ref_entry *get_packed_ref(struct files_ref_store *refs,
- const char *refname)
-{
- return find_ref_entry(get_packed_refs(refs), refname);
-}
-
-/*
- * A loose ref file doesn't exist; check for a packed ref.
- */
-static int resolve_packed_ref(struct files_ref_store *refs,
- const char *refname,
- unsigned char *sha1, unsigned int *flags)
-{
- struct ref_entry *entry;
-
- /*
- * The loose reference file does not exist; check for a packed
- * reference.
- */
- entry = get_packed_ref(refs, refname);
- if (entry) {
- hashcpy(sha1, entry->u.value.oid.hash);
- *flags |= REF_ISPACKED;
- return 0;
- }
- /* refname is not a packed reference. */
- return -1;
-}
-
static int files_read_raw_ref(struct ref_store *ref_store,
- const char *refname, unsigned char *sha1,
+ const char *refname, struct object_id *oid,
struct strbuf *referent, unsigned int *type)
{
struct files_ref_store *refs =
struct strbuf sb_path = STRBUF_INIT;
const char *path;
const char *buf;
+ const char *p;
struct stat st;
int fd;
int ret = -1;
if (lstat(path, &st) < 0) {
if (errno != ENOENT)
goto out;
- if (resolve_packed_ref(refs, refname, sha1, type)) {
+ if (refs_read_raw_ref(refs->packed_ref_store, refname,
+ oid, referent, type)) {
errno = ENOENT;
goto out;
}
* ref is supposed to be, there could still be a
* packed ref:
*/
- if (resolve_packed_ref(refs, refname, sha1, type)) {
+ if (refs_read_raw_ref(refs->packed_ref_store, refname,
+ oid, referent, type)) {
errno = EISDIR;
goto out;
}
* Please note that FETCH_HEAD has additional
* data after the sha.
*/
- if (get_sha1_hex(buf, sha1) ||
- (buf[40] != '\0' && !isspace(buf[40]))) {
+ if (parse_oid_hex(buf, oid, &p) ||
+ (*p != '\0' && !isspace(*p))) {
*type |= REF_ISBROKEN;
errno = EINVAL;
goto out;
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);
+ rollback_lock_file(&lock->lk);
free(lock->ref_name);
free(lock);
}
* are passed to refs_verify_refname_available() for this check.
*
* If mustexist is not set and the reference is not found or is
- * broken, lock the reference anyway but clear sha1.
+ * broken, lock the reference anyway but clear old_oid.
*
* Return 0 on success. On failure, write an error message to err and
* return TRANSACTION_NAME_CONFLICT or TRANSACTION_GENERIC_ERROR.
goto error_return;
}
- if (!lock->lk)
- lock->lk = xcalloc(1, sizeof(struct lock_file));
-
- if (hold_lock_file_for_update(lock->lk, ref_file.buf, LOCK_NO_DEREF) < 0) {
+ if (hold_lock_file_for_update_timeout(
+ &lock->lk, ref_file.buf, LOCK_NO_DEREF,
+ get_files_ref_lock_timeout_ms()) < 0) {
if (errno == ENOENT && --attempts_remaining > 0) {
/*
* Maybe somebody just deleted one of the
*/
if (files_read_raw_ref(&refs->base, refname,
- lock->old_oid.hash, referent, type)) {
+ &lock->old_oid, referent, type)) {
if (errno == ENOENT) {
if (mustexist) {
/* Garden variety missing reference. */
/*
* If the ref did not exist and we are creating it,
- * make sure there is no existing ref that conflicts
- * with refname:
+ * make sure there is no existing packed ref that
+ * conflicts with refname:
*/
if (refs_verify_refname_available(
- &refs->base, refname,
+ refs->packed_ref_store, refname,
extras, skip, err))
goto error_return;
}
return ret;
}
-static int files_peel_ref(struct ref_store *ref_store,
- const char *refname, unsigned char *sha1)
-{
- struct files_ref_store *refs =
- files_downcast(ref_store, REF_STORE_READ | REF_STORE_ODB,
- "peel_ref");
- int flag;
- unsigned char base[20];
-
- if (current_ref_iter && current_ref_iter->refname == refname) {
- struct object_id peeled;
-
- if (ref_iterator_peel(current_ref_iter, &peeled))
- return -1;
- hashcpy(sha1, peeled.hash);
- return 0;
- }
-
- if (refs_read_ref_full(ref_store, refname,
- RESOLVE_REF_READING, base, &flag))
- return -1;
-
- /*
- * If the reference is packed, read its ref_entry from the
- * cache in the hope that we already know its peeled value.
- * We only try this optimization on packed references because
- * (a) forcing the filling of the loose reference cache could
- * be expensive and (b) loose references anyway usually do not
- * have REF_KNOWS_PEELED.
- */
- if (flag & REF_ISPACKED) {
- struct ref_entry *r = get_packed_ref(refs, refname);
- if (r) {
- if (peel_entry(r, 0))
- return -1;
- hashcpy(sha1, r->u.value.peeled.hash);
- return 0;
- }
- }
-
- return peel_object(base, sha1);
-}
-
struct files_ref_iterator {
struct ref_iterator base;
- struct packed_ref_cache *packed_ref_cache;
struct ref_iterator *iter0;
unsigned int flags;
};
if (iter->iter0)
ok = ref_iterator_abort(iter->iter0);
- release_packed_ref_cache(iter->packed_ref_cache);
base_ref_iterator_free(ref_iterator);
return ok;
}
const char *prefix, unsigned int flags)
{
struct files_ref_store *refs;
- struct ref_iterator *loose_iter, *packed_iter;
+ struct ref_iterator *loose_iter, *packed_iter, *overlay_iter;
struct files_ref_iterator *iter;
struct ref_iterator *ref_iterator;
unsigned int required_flags = REF_STORE_READ;
refs = files_downcast(ref_store, required_flags, "ref_iterator_begin");
- iter = xcalloc(1, sizeof(*iter));
- ref_iterator = &iter->base;
- base_ref_iterator_init(ref_iterator, &files_ref_iterator_vtable);
-
/*
* We must make sure that all loose refs are read before
* accessing the packed-refs file; this avoids a race
* (If they've already been read, that's OK; we only need to
* guarantee that they're read before the packed refs, not
* *how much* before.) After that, we call
- * get_packed_ref_cache(), which internally checks whether the
- * packed-ref cache is up to date with what is on disk, and
- * re-reads it if not.
+ * packed_ref_iterator_begin(), which internally checks
+ * whether the packed-ref cache is up to date with what is on
+ * disk, and re-reads it if not.
*/
loose_iter = cache_ref_iterator_begin(get_loose_ref_cache(refs),
prefix, 1);
- iter->packed_ref_cache = get_packed_ref_cache(refs);
- acquire_packed_ref_cache(iter->packed_ref_cache);
- packed_iter = cache_ref_iterator_begin(iter->packed_ref_cache->cache,
- prefix, 0);
+ /*
+ * The packed-refs file might contain broken references, for
+ * example an old version of a reference that points at an
+ * object that has since been garbage-collected. This is OK as
+ * long as there is a corresponding loose reference that
+ * overrides it, and we don't want to emit an error message in
+ * this case. So ask the packed_ref_store for all of its
+ * references, and (if needed) do our own check for broken
+ * ones in files_ref_iterator_advance(), after we have merged
+ * the packed and loose references.
+ */
+ packed_iter = refs_ref_iterator_begin(
+ refs->packed_ref_store, prefix, 0,
+ DO_FOR_EACH_INCLUDE_BROKEN);
+
+ overlay_iter = overlay_ref_iterator_begin(loose_iter, packed_iter);
- iter->iter0 = overlay_ref_iterator_begin(loose_iter, packed_iter);
+ iter = xcalloc(1, sizeof(*iter));
+ ref_iterator = &iter->base;
+ base_ref_iterator_init(ref_iterator, &files_ref_iterator_vtable,
+ overlay_iter->ordered);
+ iter->iter0 = overlay_iter;
iter->flags = flags;
return ref_iterator;
}
/*
- * Verify that the reference locked by lock has the value old_sha1.
- * Fail if the reference doesn't exist and mustexist is set. Return 0
- * on success. On error, write an error message to err, set errno, and
- * return a negative value.
+ * Verify that the reference locked by lock has the value old_oid
+ * (unless it is NULL). Fail if the reference doesn't exist and
+ * mustexist is set. Return 0 on success. On error, write an error
+ * message to err, set errno, and return a negative value.
*/
static int verify_lock(struct ref_store *ref_store, struct ref_lock *lock,
- const unsigned char *old_sha1, int mustexist,
+ const struct object_id *old_oid, int mustexist,
struct strbuf *err)
{
assert(err);
if (refs_read_ref_full(ref_store, lock->ref_name,
mustexist ? RESOLVE_REF_READING : 0,
- lock->old_oid.hash, NULL)) {
- if (old_sha1) {
+ &lock->old_oid, NULL)) {
+ if (old_oid) {
int save_errno = errno;
strbuf_addf(err, "can't verify ref '%s'", lock->ref_name);
errno = save_errno;
return 0;
}
}
- if (old_sha1 && hashcmp(lock->old_oid.hash, old_sha1)) {
+ if (old_oid && oidcmp(&lock->old_oid, old_oid)) {
strbuf_addf(err, "ref '%s' is at %s but expected %s",
lock->ref_name,
oid_to_hex(&lock->old_oid),
- sha1_to_hex(old_sha1));
+ oid_to_hex(old_oid));
errno = EBUSY;
return -1;
}
{
struct lock_file *lk = cb;
- return hold_lock_file_for_update(lk, path, LOCK_NO_DEREF) < 0 ? -1 : 0;
+ return hold_lock_file_for_update_timeout(
+ lk, path, LOCK_NO_DEREF,
+ get_files_ref_lock_timeout_ms()) < 0 ? -1 : 0;
}
/*
* 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(struct files_ref_store *refs,
- const char *refname,
- const unsigned char *old_sha1,
- const struct string_list *extras,
- const struct string_list *skip,
- unsigned int flags, int *type,
- struct strbuf *err)
+static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs,
+ const char *refname,
+ const struct object_id *old_oid,
+ const struct string_list *extras,
+ const struct string_list *skip,
+ unsigned int flags, int *type,
+ struct strbuf *err)
{
struct strbuf ref_file = STRBUF_INIT;
struct ref_lock *lock;
int last_errno = 0;
- int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
+ int mustexist = (old_oid && !is_null_oid(old_oid));
int resolve_flags = RESOLVE_REF_NO_RECURSE;
int resolved;
- files_assert_main_repository(refs, "lock_ref_sha1_basic");
+ files_assert_main_repository(refs, "lock_ref_oid_basic");
assert(err);
lock = xcalloc(1, sizeof(struct ref_lock));
files_ref_path(refs, &ref_file, refname);
resolved = !!refs_resolve_ref_unsafe(&refs->base,
refname, resolve_flags,
- lock->old_oid.hash, type);
+ &lock->old_oid, type);
if (!resolved && errno == EISDIR) {
/*
* we are trying to lock foo but we used to
}
resolved = !!refs_resolve_ref_unsafe(&refs->base,
refname, resolve_flags,
- lock->old_oid.hash, type);
+ &lock->old_oid, type);
}
if (!resolved) {
last_errno = errno;
* our refname.
*/
if (is_null_oid(&lock->old_oid) &&
- refs_verify_refname_available(&refs->base, refname,
+ refs_verify_refname_available(refs->packed_ref_store, refname,
extras, skip, err)) {
last_errno = ENOTDIR;
goto error_return;
}
- lock->lk = xcalloc(1, sizeof(struct lock_file));
-
lock->ref_name = xstrdup(refname);
- if (raceproof_create_file(ref_file.buf, create_reflock, lock->lk)) {
+ if (raceproof_create_file(ref_file.buf, create_reflock, &lock->lk)) {
last_errno = errno;
unable_to_lock_message(ref_file.buf, errno, err);
goto error_return;
}
- if (verify_lock(&refs->base, lock, old_sha1, mustexist, err)) {
+ if (verify_lock(&refs->base, lock, old_oid, mustexist, err)) {
last_errno = errno;
goto error_return;
}
return lock;
}
-/*
- * 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(FILE *fh, const char *refname,
- const unsigned char *sha1,
- const unsigned char *peeled)
-{
- fprintf_or_die(fh, "%s %s\n", sha1_to_hex(sha1), refname);
- if (peeled)
- fprintf_or_die(fh, "^%s\n", sha1_to_hex(peeled));
-}
-
-/*
- * Lock the packed-refs file for writing. Flags is passed to
- * hold_lock_file_for_update(). Return 0 on success. On errors, set
- * errno appropriately and return a nonzero value.
- */
-static int lock_packed_refs(struct files_ref_store *refs, int flags)
-{
- static int timeout_configured = 0;
- static int timeout_value = 1000;
- struct packed_ref_cache *packed_ref_cache;
-
- files_assert_main_repository(refs, "lock_packed_refs");
-
- if (!timeout_configured) {
- git_config_get_int("core.packedrefstimeout", &timeout_value);
- timeout_configured = 1;
- }
-
- if (hold_lock_file_for_update_timeout(
- &refs->packed_refs_lock, files_packed_refs_path(refs),
- flags, timeout_value) < 0)
- return -1;
-
- /*
- * Now that we hold the `packed-refs` lock, make sure that our
- * cache matches the current version of the file. Normally
- * `get_packed_ref_cache()` does that for us, but that
- * function assumes that when the file is locked, any existing
- * cache is still valid. We've just locked the file, but it
- * might have changed the moment *before* we locked it.
- */
- validate_packed_ref_cache(refs);
-
- packed_ref_cache = get_packed_ref_cache(refs);
- /* Increment the reference count to prevent it from being freed: */
- acquire_packed_ref_cache(packed_ref_cache);
- return 0;
-}
-
-/*
- * Write the current version of the packed refs cache from memory to
- * disk. The packed-refs file must already be locked for writing (see
- * lock_packed_refs()). Return zero on success. On errors, set errno
- * and return a nonzero value
- */
-static int commit_packed_refs(struct files_ref_store *refs)
-{
- struct packed_ref_cache *packed_ref_cache =
- get_packed_ref_cache(refs);
- int ok, error = 0;
- int save_errno = 0;
- FILE *out;
- struct ref_iterator *iter;
-
- files_assert_main_repository(refs, "commit_packed_refs");
-
- if (!is_lock_file_locked(&refs->packed_refs_lock))
- die("BUG: packed-refs not locked");
-
- out = fdopen_lock_file(&refs->packed_refs_lock, "w");
- if (!out)
- die_errno("unable to fdopen packed-refs descriptor");
-
- fprintf_or_die(out, "%s", PACKED_REFS_HEADER);
-
- iter = cache_ref_iterator_begin(packed_ref_cache->cache, NULL, 0);
- while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
- struct object_id peeled;
- int peel_error = ref_iterator_peel(iter, &peeled);
-
- write_packed_entry(out, iter->refname, iter->oid->hash,
- peel_error ? NULL : peeled.hash);
- }
-
- if (ok != ITER_DONE)
- die("error while iterating over references");
-
- if (commit_lock_file(&refs->packed_refs_lock)) {
- save_errno = errno;
- error = -1;
- }
- release_packed_ref_cache(packed_ref_cache);
- errno = save_errno;
- return error;
-}
-
-/*
- * Rollback the lockfile for the packed-refs file, and discard the
- * in-memory packed reference cache. (The packed-refs file will be
- * read anew if it is needed again after this function is called.)
- */
-static void rollback_packed_refs(struct files_ref_store *refs)
-{
- struct packed_ref_cache *packed_ref_cache =
- get_packed_ref_cache(refs);
-
- files_assert_main_repository(refs, "rollback_packed_refs");
-
- if (!is_lock_file_locked(&refs->packed_refs_lock))
- die("BUG: packed-refs not locked");
- rollback_lock_file(&refs->packed_refs_lock);
- release_packed_ref_cache(packed_ref_cache);
- clear_packed_ref_cache(refs);
-}
-
struct ref_to_prune {
struct ref_to_prune *next;
- unsigned char sha1[20];
+ struct object_id oid;
char name[FLEX_ARRAY];
};
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
+ int ret = -1;
if (check_refname_format(r->name, 0))
return;
transaction = ref_store_transaction_begin(&refs->base, &err);
- if (!transaction ||
- ref_transaction_delete(transaction, r->name, r->sha1,
- REF_ISPRUNING | REF_NODEREF, NULL, &err) ||
- ref_transaction_commit(transaction, &err)) {
- ref_transaction_free(transaction);
+ if (!transaction)
+ goto cleanup;
+ ref_transaction_add_update(
+ transaction, r->name,
+ REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,
+ &null_oid, &r->oid, NULL);
+ if (ref_transaction_commit(transaction, &err))
+ goto cleanup;
+
+ ret = 0;
+
+cleanup:
+ if (ret)
error("%s", err.buf);
- strbuf_release(&err);
- return;
- }
- ref_transaction_free(transaction);
strbuf_release(&err);
+ ref_transaction_free(transaction);
+ return;
}
-static void prune_refs(struct files_ref_store *refs, struct ref_to_prune *r)
+/*
+ * Prune the loose versions of the references in the linked list
+ * `*refs_to_prune`, freeing the entries in the list as we go.
+ */
+static void prune_refs(struct files_ref_store *refs, struct ref_to_prune **refs_to_prune)
{
- while (r) {
+ while (*refs_to_prune) {
+ struct ref_to_prune *r = *refs_to_prune;
+ *refs_to_prune = r->next;
prune_ref(refs, r);
- r = r->next;
+ free(r);
}
}
files_downcast(ref_store, REF_STORE_WRITE | REF_STORE_ODB,
"pack_refs");
struct ref_iterator *iter;
- struct ref_dir *packed_refs;
int ok;
struct ref_to_prune *refs_to_prune = NULL;
+ struct strbuf err = STRBUF_INIT;
+ struct ref_transaction *transaction;
+
+ transaction = ref_store_transaction_begin(refs->packed_ref_store, &err);
+ if (!transaction)
+ return -1;
- lock_packed_refs(refs, LOCK_DIE_ON_ERROR);
- packed_refs = get_packed_refs(refs);
+ packed_refs_lock(refs->packed_ref_store, LOCK_DIE_ON_ERROR, &err);
iter = cache_ref_iterator_begin(get_loose_ref_cache(refs), NULL, 0);
while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
* in the packed ref cache. If the reference should be
* pruned, also add it to refs_to_prune.
*/
- struct ref_entry *packed_entry;
-
if (!should_pack_ref(iter->refname, iter->oid, iter->flags,
flags))
continue;
/*
- * Create an entry in the packed-refs cache equivalent
- * to the one from the loose ref cache, except that
- * we don't copy the peeled status, because we want it
- * to be re-peeled.
+ * Add a reference creation for this reference to the
+ * packed-refs transaction:
*/
- packed_entry = find_ref_entry(packed_refs, iter->refname);
- if (packed_entry) {
- /* Overwrite existing packed entry with info from loose entry */
- packed_entry->flag = REF_ISPACKED;
- oidcpy(&packed_entry->u.value.oid, iter->oid);
- } else {
- packed_entry = create_ref_entry(iter->refname, iter->oid,
- REF_ISPACKED);
- add_ref_entry(packed_refs, packed_entry);
- }
- oidclr(&packed_entry->u.value.peeled);
+ if (ref_transaction_update(transaction, iter->refname,
+ iter->oid, NULL,
+ REF_NO_DEREF, NULL, &err))
+ die("failure preparing to create packed reference %s: %s",
+ iter->refname, err.buf);
/* Schedule the loose reference for pruning if requested. */
if ((flags & PACK_REFS_PRUNE)) {
struct ref_to_prune *n;
FLEX_ALLOC_STR(n, name, iter->refname);
- hashcpy(n->sha1, iter->oid->hash);
+ oidcpy(&n->oid, iter->oid);
n->next = refs_to_prune;
refs_to_prune = n;
}
if (ok != ITER_DONE)
die("error while iterating over references");
- if (commit_packed_refs(refs))
- die_errno("unable to overwrite old ref-pack file");
-
- prune_refs(refs, refs_to_prune);
- return 0;
-}
-
-/*
- * Rewrite the packed-refs file, omitting any refs listed in
- * 'refnames'. On error, leave packed-refs unchanged, write an error
- * message to 'err', and return a nonzero value.
- *
- * The refs in 'refnames' needn't be sorted. `err` must not be NULL.
- */
-static int repack_without_refs(struct files_ref_store *refs,
- struct string_list *refnames, struct strbuf *err)
-{
- struct ref_dir *packed;
- struct string_list_item *refname;
- int ret, needs_repacking = 0, removed = 0;
+ if (ref_transaction_commit(transaction, &err))
+ die("unable to write new packed-refs: %s", err.buf);
- files_assert_main_repository(refs, "repack_without_refs");
- assert(err);
-
- /* Look for a packed ref */
- for_each_string_list_item(refname, refnames) {
- if (get_packed_ref(refs, refname->string)) {
- needs_repacking = 1;
- break;
- }
- }
-
- /* Avoid locking if we have nothing to do */
- if (!needs_repacking)
- return 0; /* no refname exists in packed refs */
-
- if (lock_packed_refs(refs, 0)) {
- unable_to_lock_message(files_packed_refs_path(refs), errno, err);
- return -1;
- }
- packed = get_packed_refs(refs);
+ ref_transaction_free(transaction);
- /* Remove refnames from the cache */
- for_each_string_list_item(refname, refnames)
- if (remove_entry_from_dir(packed, refname->string) != -1)
- removed = 1;
- if (!removed) {
- /*
- * All packed entries disappeared while we were
- * acquiring the lock.
- */
- rollback_packed_refs(refs);
- return 0;
- }
+ packed_refs_unlock(refs->packed_ref_store);
- /* Write what remains */
- ret = commit_packed_refs(refs);
- if (ret)
- strbuf_addf(err, "unable to overwrite old ref-pack file: %s",
- strerror(errno));
- return ret;
+ prune_refs(refs, &refs_to_prune);
+ strbuf_release(&err);
+ return 0;
}
static int files_delete_refs(struct ref_store *ref_store, const char *msg,
if (!refnames->nr)
return 0;
- result = repack_without_refs(refs, refnames, &err);
- if (result) {
- /*
- * If we failed to rewrite the packed-refs file, then
- * it is unsafe to try to remove loose refs, because
- * doing so might expose an obsolete packed value for
- * a reference that might even point at an object that
- * has been garbage collected.
- */
- if (refnames->nr == 1)
- error(_("could not delete reference %s: %s"),
- refnames->items[0].string, err.buf);
- else
- error(_("could not delete references: %s"), err.buf);
+ if (packed_refs_lock(refs->packed_ref_store, 0, &err))
+ goto error;
- goto out;
+ if (refs_delete_refs(refs->packed_ref_store, msg, refnames, flags)) {
+ packed_refs_unlock(refs->packed_ref_store);
+ goto error;
}
+ packed_refs_unlock(refs->packed_ref_store);
+
for (i = 0; i < refnames->nr; i++) {
const char *refname = refnames->items[i].string;
result |= error(_("could not remove reference %s"), refname);
}
-out:
strbuf_release(&err);
return result;
+
+error:
+ /*
+ * If we failed to rewrite the packed-refs file, then it is
+ * unsafe to try to remove loose refs, because doing so might
+ * expose an obsolete packed value for a reference that might
+ * even point at an object that has been garbage collected.
+ */
+ if (refnames->nr == 1)
+ error(_("could not delete reference %s: %s"),
+ refnames->items[0].string, err.buf);
+ else
+ error(_("could not delete references: %s"), err.buf);
+
+ strbuf_release(&err);
+ return -1;
}
/*
const struct object_id *oid, const char *logmsg,
struct strbuf *err);
-static int files_rename_ref(struct ref_store *ref_store,
+static int files_copy_or_rename_ref(struct ref_store *ref_store,
const char *oldrefname, const char *newrefname,
- const char *logmsg)
+ const char *logmsg, int copy)
{
struct files_ref_store *refs =
files_downcast(ref_store, REF_STORE_WRITE, "rename_ref");
if (!refs_resolve_ref_unsafe(&refs->base, oldrefname,
RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
- orig_oid.hash, &flag)) {
+ &orig_oid, &flag)) {
ret = error("refname %s not found", oldrefname);
goto out;
}
if (flag & REF_ISSYMREF) {
- ret = error("refname %s is a symbolic ref, renaming it is not supported",
- oldrefname);
+ if (copy)
+ ret = error("refname %s is a symbolic ref, copying it is not supported",
+ oldrefname);
+ else
+ ret = error("refname %s is a symbolic ref, renaming it is not supported",
+ oldrefname);
goto out;
}
if (!refs_rename_ref_available(&refs->base, oldrefname, newrefname)) {
goto out;
}
- if (log && rename(sb_oldref.buf, tmp_renamed_log.buf)) {
+ if (!copy && log && rename(sb_oldref.buf, tmp_renamed_log.buf)) {
ret = error("unable to move logfile logs/%s to logs/"TMP_RENAMED_LOG": %s",
oldrefname, strerror(errno));
goto out;
}
- if (refs_delete_ref(&refs->base, logmsg, oldrefname,
- orig_oid.hash, REF_NODEREF)) {
+ if (copy && log && copy_file(tmp_renamed_log.buf, sb_oldref.buf, 0644)) {
+ ret = error("unable to copy logfile logs/%s to logs/"TMP_RENAMED_LOG": %s",
+ oldrefname, strerror(errno));
+ goto out;
+ }
+
+ if (!copy && refs_delete_ref(&refs->base, logmsg, oldrefname,
+ &orig_oid, REF_NO_DEREF)) {
error("unable to delete old %s", oldrefname);
goto rollback;
}
* the safety anyway; we want to delete the reference whatever
* its current value.
*/
- if (!refs_read_ref_full(&refs->base, newrefname,
+ if (!copy && !refs_read_ref_full(&refs->base, newrefname,
RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
- oid.hash, NULL) &&
+ &oid, NULL) &&
refs_delete_ref(&refs->base, NULL, newrefname,
- NULL, REF_NODEREF)) {
+ NULL, REF_NO_DEREF)) {
if (errno == EISDIR) {
struct strbuf path = STRBUF_INIT;
int result;
logmoved = log;
- lock = lock_ref_sha1_basic(refs, newrefname, NULL, NULL, NULL,
- REF_NODEREF, NULL, &err);
+ lock = lock_ref_oid_basic(refs, newrefname, NULL, NULL, NULL,
+ REF_NO_DEREF, NULL, &err);
if (!lock) {
- error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf);
+ if (copy)
+ error("unable to copy '%s' to '%s': %s", oldrefname, newrefname, err.buf);
+ else
+ error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf);
strbuf_release(&err);
goto rollback;
}
goto out;
rollback:
- lock = lock_ref_sha1_basic(refs, oldrefname, NULL, NULL, NULL,
- REF_NODEREF, NULL, &err);
+ lock = lock_ref_oid_basic(refs, oldrefname, NULL, NULL, NULL,
+ REF_NO_DEREF, NULL, &err);
if (!lock) {
error("unable to lock %s for rollback: %s", oldrefname, err.buf);
strbuf_release(&err);
return ret;
}
-static int close_ref(struct ref_lock *lock)
+static int files_rename_ref(struct ref_store *ref_store,
+ const char *oldrefname, const char *newrefname,
+ const char *logmsg)
+{
+ return files_copy_or_rename_ref(ref_store, oldrefname,
+ newrefname, logmsg, 0);
+}
+
+static int files_copy_ref(struct ref_store *ref_store,
+ const char *oldrefname, const char *newrefname,
+ const char *logmsg)
+{
+ return files_copy_or_rename_ref(ref_store, oldrefname,
+ newrefname, logmsg, 1);
+}
+
+static int close_ref_gently(struct ref_lock *lock)
{
- if (close_lock_file(lock->lk))
+ if (close_lock_file_gently(&lock->lk))
return -1;
return 0;
}
static int commit_ref(struct ref_lock *lock)
{
- char *path = get_locked_file_path(lock->lk);
+ char *path = get_locked_file_path(&lock->lk);
struct stat st;
if (!lstat(path, &st) && S_ISDIR(st.st_mode)) {
free(path);
}
- if (commit_lock_file(lock->lk))
+ if (commit_lock_file(&lock->lk))
return -1;
return 0;
}
}
/*
- * Write sha1 into the open lockfile, then close the lockfile. On
- * errors, rollback the lockfile, fill in *err and
- * return -1.
+ * Write oid into the open lockfile, then close the lockfile. On
+ * errors, rollback the lockfile, fill in *err and return -1.
*/
static int write_ref_to_lockfile(struct ref_lock *lock,
const struct object_id *oid, struct strbuf *err)
unlock_ref(lock);
return -1;
}
- fd = get_lock_file_fd(lock->lk);
+ fd = get_lock_file_fd(&lock->lk);
if (write_in_full(fd, oid_to_hex(oid), GIT_SHA1_HEXSZ) < 0 ||
write_in_full(fd, &term, 1) < 0 ||
- close_ref(lock) < 0) {
+ close_ref_gently(lock) < 0) {
strbuf_addf(err,
- "couldn't write '%s'", get_lock_file_path(lock->lk));
+ "couldn't write '%s'", get_lock_file_path(&lock->lk));
unlock_ref(lock);
return -1;
}
* check with HEAD only which should cover 99% of all usage
* scenarios (even 100% of the default ones).
*/
- struct object_id head_oid;
int head_flag;
const char *head_ref;
head_ref = refs_resolve_ref_unsafe(&refs->base, "HEAD",
RESOLVE_REF_READING,
- head_oid.hash, &head_flag);
+ NULL, &head_flag);
if (head_ref && (head_flag & REF_ISSYMREF) &&
!strcmp(head_ref, lock->ref_name)) {
struct strbuf log_err = STRBUF_INIT;
{
int ret = -1;
#ifndef NO_SYMLINK_HEAD
- char *ref_path = get_locked_file_path(lock->lk);
+ char *ref_path = get_locked_file_path(&lock->lk);
unlink(ref_path);
ret = symlink(target, ref_path);
free(ref_path);
struct object_id new_oid;
if (logmsg &&
!refs_read_ref_full(&refs->base, target,
- RESOLVE_REF_READING, new_oid.hash, NULL) &&
+ RESOLVE_REF_READING, &new_oid, NULL) &&
files_log_ref_write(refs, refname, &lock->old_oid,
&new_oid, logmsg, 0, &err)) {
error("%s", err.buf);
return 0;
}
- if (!fdopen_lock_file(lock->lk, "w"))
+ if (!fdopen_lock_file(&lock->lk, "w"))
return error("unable to fdopen %s: %s",
- lock->lk->tempfile.filename.buf, strerror(errno));
+ lock->lk.tempfile->filename.buf, strerror(errno));
update_symref_reflog(refs, lock, refname, target, logmsg);
/* no error check; commit_ref will check ferror */
- fprintf(lock->lk->tempfile.fp, "ref: %s\n", target);
+ fprintf(lock->lk.tempfile->fp, "ref: %s\n", target);
if (commit_ref(lock) < 0)
return error("unable to write symref for %s: %s", refname,
strerror(errno));
struct ref_lock *lock;
int ret;
- lock = lock_ref_sha1_basic(refs, refname, NULL,
- NULL, NULL, REF_NODEREF, NULL,
- &err);
+ lock = lock_ref_oid_basic(refs, refname, NULL,
+ NULL, NULL, REF_NO_DEREF, NULL,
+ &err);
if (!lock) {
error("%s", err.buf);
strbuf_release(&err);
}
if (!ret && sb.len)
- die("BUG: reverse reflog parser had leftover data");
+ BUG("reverse reflog parser had leftover data");
fclose(logfp);
strbuf_release(&sb);
if (refs_read_ref_full(iter->ref_store,
diter->relative_path, 0,
- iter->oid.hash, &flags)) {
+ &iter->oid, &flags)) {
error("bad ref for %s", diter->path.buf);
continue;
}
static int files_reflog_iterator_peel(struct ref_iterator *ref_iterator,
struct object_id *peeled)
{
- die("BUG: ref_iterator_peel() called for reflog_iterator");
+ BUG("ref_iterator_peel() called for reflog_iterator");
}
static int files_reflog_iterator_abort(struct ref_iterator *ref_iterator)
files_reflog_iterator_abort
};
-static struct ref_iterator *files_reflog_iterator_begin(struct ref_store *ref_store)
+static struct ref_iterator *reflog_iterator_begin(struct ref_store *ref_store,
+ const char *gitdir)
{
- struct files_ref_store *refs =
- files_downcast(ref_store, REF_STORE_READ,
- "reflog_iterator_begin");
struct files_reflog_iterator *iter = xcalloc(1, sizeof(*iter));
struct ref_iterator *ref_iterator = &iter->base;
struct strbuf sb = STRBUF_INIT;
- base_ref_iterator_init(ref_iterator, &files_reflog_iterator_vtable);
- files_reflog_path(refs, &sb, NULL);
+ base_ref_iterator_init(ref_iterator, &files_reflog_iterator_vtable, 0);
+ strbuf_addf(&sb, "%s/logs", gitdir);
iter->dir_iterator = dir_iterator_begin(sb.buf);
iter->ref_store = ref_store;
strbuf_release(&sb);
+
return ref_iterator;
}
+static enum iterator_selection reflog_iterator_select(
+ struct ref_iterator *iter_worktree,
+ struct ref_iterator *iter_common,
+ void *cb_data)
+{
+ if (iter_worktree) {
+ /*
+ * We're a bit loose here. We probably should ignore
+ * common refs if they are accidentally added as
+ * per-worktree refs.
+ */
+ return ITER_SELECT_0;
+ } else if (iter_common) {
+ if (ref_type(iter_common->refname) == REF_TYPE_NORMAL)
+ return ITER_SELECT_1;
+
+ /*
+ * The main ref store may contain main worktree's
+ * per-worktree refs, which should be ignored
+ */
+ return ITER_SKIP_1;
+ } else
+ return ITER_DONE;
+}
+
+static struct ref_iterator *files_reflog_iterator_begin(struct ref_store *ref_store)
+{
+ struct files_ref_store *refs =
+ files_downcast(ref_store, REF_STORE_READ,
+ "reflog_iterator_begin");
+
+ if (!strcmp(refs->gitdir, refs->gitcommondir)) {
+ return reflog_iterator_begin(ref_store, refs->gitcommondir);
+ } else {
+ return merge_ref_iterator_begin(
+ 0,
+ reflog_iterator_begin(ref_store, refs->gitdir),
+ reflog_iterator_begin(ref_store, refs->gitcommondir),
+ reflog_iterator_select, refs);
+ }
+}
+
/*
* If update is a direct update of head_ref (the reference pointed to
* by HEAD), then add an extra REF_LOG_ONLY update for HEAD.
struct ref_update *new_update;
if ((update->flags & REF_LOG_ONLY) ||
- (update->flags & REF_ISPRUNING) ||
+ (update->flags & REF_IS_PRUNING) ||
(update->flags & REF_UPDATE_VIA_HEAD))
return 0;
/*
* First make sure that HEAD is not already in the
- * transaction. This insertion is O(N) in the transaction
+ * transaction. This check is O(lg N) in the transaction
* size, but it happens at most once per transaction.
*/
- item = string_list_insert(affected_refnames, "HEAD");
- if (item->util) {
+ if (string_list_has_string(affected_refnames, "HEAD")) {
/* An entry already existed */
strbuf_addf(err,
"multiple updates for 'HEAD' (including one "
new_update = ref_transaction_add_update(
transaction, "HEAD",
- update->flags | REF_LOG_ONLY | REF_NODEREF,
- update->new_oid.hash, update->old_oid.hash,
+ update->flags | REF_LOG_ONLY | REF_NO_DEREF,
+ &update->new_oid, &update->old_oid,
update->msg);
+ /*
+ * Add "HEAD". This insertion is O(N) in the transaction
+ * size, but it happens at most once per transaction.
+ * Add new_update->refname instead of a literal "HEAD".
+ */
+ if (strcmp(new_update->refname, "HEAD"))
+ BUG("%s unexpectedly not 'HEAD'", new_update->refname);
+ item = string_list_insert(affected_refnames, new_update->refname);
item->util = new_update;
return 0;
/*
* update is for a symref that points at referent and doesn't have
- * REF_NODEREF set. Split it into two updates:
- * - The original update, but with REF_LOG_ONLY and REF_NODEREF set
+ * REF_NO_DEREF set. Split it into two updates:
+ * - The original update, but with REF_LOG_ONLY and REF_NO_DEREF set
* - A new, separate update for the referent reference
* Note that the new update will itself be subject to splitting when
* the iteration gets to it.
/*
* First make sure that referent is not already in the
- * transaction. This insertion is O(N) in the transaction
+ * transaction. This check is O(lg N) in the transaction
* size, but it happens at most once per symref in a
* transaction.
*/
- item = string_list_insert(affected_refnames, referent);
- if (item->util) {
- /* An entry already existed */
+ if (string_list_has_string(affected_refnames, referent)) {
+ /* An entry already exists */
strbuf_addf(err,
"multiple updates for '%s' (including one "
"via symref '%s') are not allowed",
new_update = ref_transaction_add_update(
transaction, referent, new_flags,
- update->new_oid.hash, update->old_oid.hash,
+ &update->new_oid, &update->old_oid,
update->msg);
new_update->parent_update = update;
/*
* Change the symbolic ref update to log only. Also, it
- * doesn't need to check its old SHA-1 value, as that will be
+ * doesn't need to check its old OID value, as that will be
* done when new_update is processed.
*/
- update->flags |= REF_LOG_ONLY | REF_NODEREF;
+ update->flags |= REF_LOG_ONLY | REF_NO_DEREF;
update->flags &= ~REF_HAVE_OLD;
+ /*
+ * Add the referent. This insertion is O(N) in the transaction
+ * size, but it happens at most once per symref in a
+ * transaction. Make sure to add new_update->refname, which will
+ * be valid as long as affected_refnames is in use, and NOT
+ * referent, which might soon be freed by our caller.
+ */
+ item = string_list_insert(affected_refnames, new_update->refname);
+ if (item->util)
+ BUG("%s unexpectedly found in affected_refnames",
+ new_update->refname);
item->util = new_update;
return 0;
* Prepare for carrying out update:
* - Lock the reference referred to by update.
* - Read the reference under lock.
- * - Check that its old SHA-1 value (if specified) is correct, and in
+ * - Check that its old OID value (if specified) is correct, and in
* any case record it in update->lock->old_oid for later use when
* writing the reflog.
- * - If it is a symref update without REF_NODEREF, split it up into a
+ * - If it is a symref update without REF_NO_DEREF, split it up into a
* REF_LOG_ONLY update of the symref and add a separate update for
* the referent to transaction.
* - If it is an update of head_ref, add a corresponding REF_LOG_ONLY
struct strbuf referent = STRBUF_INIT;
int mustexist = (update->flags & REF_HAVE_OLD) &&
!is_null_oid(&update->old_oid);
- int ret;
+ int ret = 0;
struct ref_lock *lock;
files_assert_main_repository(refs, "lock_ref_for_update");
ret = split_head_update(update, transaction, head_ref,
affected_refnames, err);
if (ret)
- return ret;
+ goto out;
}
ret = lock_raw_ref(refs, update->refname, mustexist,
strbuf_addf(err, "cannot lock ref '%s': %s",
original_update_refname(update), reason);
free(reason);
- return ret;
+ goto out;
}
update->backend_data = lock;
if (update->type & REF_ISSYMREF) {
- if (update->flags & REF_NODEREF) {
+ if (update->flags & REF_NO_DEREF) {
/*
* We won't be reading the referent as part of
* the transaction, so we have to read it here
- * to record and possibly check old_sha1:
+ * to record and possibly check old_oid:
*/
if (refs_read_ref_full(&refs->base,
referent.buf, 0,
- lock->old_oid.hash, NULL)) {
+ &lock->old_oid, NULL)) {
if (update->flags & REF_HAVE_OLD) {
strbuf_addf(err, "cannot lock ref '%s': "
"error reading reference",
original_update_refname(update));
- return -1;
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
}
} else if (check_old_oid(update, &lock->old_oid, err)) {
- return TRANSACTION_GENERIC_ERROR;
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
}
} else {
/*
* Create a new update for the reference this
* symref is pointing at. Also, we will record
- * and verify old_sha1 for this update as part
+ * and verify old_oid for this update as part
* of processing the split-off update, so we
* don't have to do it here.
*/
referent.buf, transaction,
affected_refnames, err);
if (ret)
- return ret;
+ goto out;
}
} else {
struct ref_update *parent_update;
- if (check_old_oid(update, &lock->old_oid, err))
- return TRANSACTION_GENERIC_ERROR;
+ if (check_old_oid(update, &lock->old_oid, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
/*
* If this update is happening indirectly because of a
- * symref update, record the old SHA-1 in the parent
+ * symref update, record the old OID in the parent
* update:
*/
for (parent_update = update->parent_update;
"cannot update ref '%s': %s",
update->refname, write_err);
free(write_err);
- return TRANSACTION_GENERIC_ERROR;
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
} else {
update->flags |= REF_NEEDS_COMMIT;
}
* the lockfile is still open. Close it to
* free up the file descriptor:
*/
- if (close_ref(lock)) {
+ if (close_ref_gently(lock)) {
strbuf_addf(err, "couldn't close '%s.lock'",
update->refname);
- return TRANSACTION_GENERIC_ERROR;
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
}
}
- return 0;
+
+out:
+ strbuf_release(&referent);
+ return ret;
}
+struct files_transaction_backend_data {
+ struct ref_transaction *packed_transaction;
+ int packed_refs_locked;
+};
+
/*
* Unlock any references in `transaction` that are still locked, and
* mark the transaction closed.
*/
-static void files_transaction_cleanup(struct ref_transaction *transaction)
+static void files_transaction_cleanup(struct files_ref_store *refs,
+ struct ref_transaction *transaction)
{
size_t i;
+ struct files_transaction_backend_data *backend_data =
+ transaction->backend_data;
+ struct strbuf err = STRBUF_INIT;
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
}
}
+ if (backend_data->packed_transaction &&
+ ref_transaction_abort(backend_data->packed_transaction, &err)) {
+ error("error aborting transaction: %s", err.buf);
+ strbuf_release(&err);
+ }
+
+ if (backend_data->packed_refs_locked)
+ packed_refs_unlock(refs->packed_ref_store);
+
+ free(backend_data);
+
transaction->state = REF_TRANSACTION_CLOSED;
}
struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
char *head_ref = NULL;
int head_type;
- struct object_id head_oid;
+ struct files_transaction_backend_data *backend_data;
+ struct ref_transaction *packed_transaction = NULL;
assert(err);
if (!transaction->nr)
goto cleanup;
+ backend_data = xcalloc(1, sizeof(*backend_data));
+ transaction->backend_data = backend_data;
+
/*
* Fail if a refname appears more than once in the
* transaction. (If we end up splitting up any updates using
* split_symref_update() or split_head_update(), those
* functions will check that the new updates don't have the
- * same refname as any existing ones.)
+ * same refname as any existing ones.) Also fail if any of the
+ * updates use REF_IS_PRUNING without REF_NO_DEREF.
*/
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
struct string_list_item *item =
string_list_append(&affected_refnames, update->refname);
+ if ((update->flags & REF_IS_PRUNING) &&
+ !(update->flags & REF_NO_DEREF))
+ BUG("REF_IS_PRUNING set without REF_NO_DEREF");
+
/*
* We store a pointer to update in item->util, but at
* the moment we never use the value of this field
*/
head_ref = refs_resolve_refdup(ref_store, "HEAD",
RESOLVE_REF_NO_RECURSE,
- head_oid.hash, &head_type);
+ NULL, &head_type);
if (head_ref && !(head_type & REF_ISSYMREF)) {
FREE_AND_NULL(head_ref);
ret = lock_ref_for_update(refs, update, transaction,
head_ref, &affected_refnames, err);
if (ret)
- break;
+ goto cleanup;
+
+ if (update->flags & REF_DELETING &&
+ !(update->flags & REF_LOG_ONLY) &&
+ !(update->flags & REF_IS_PRUNING)) {
+ /*
+ * This reference has to be deleted from
+ * packed-refs if it exists there.
+ */
+ if (!packed_transaction) {
+ packed_transaction = ref_store_transaction_begin(
+ refs->packed_ref_store, err);
+ if (!packed_transaction) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto cleanup;
+ }
+
+ backend_data->packed_transaction =
+ packed_transaction;
+ }
+
+ ref_transaction_add_update(
+ packed_transaction, update->refname,
+ REF_HAVE_NEW | REF_NO_DEREF,
+ &update->new_oid, NULL,
+ NULL);
+ }
+ }
+
+ if (packed_transaction) {
+ if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto cleanup;
+ }
+ backend_data->packed_refs_locked = 1;
+
+ if (is_packed_transaction_needed(refs->packed_ref_store,
+ packed_transaction)) {
+ ret = ref_transaction_prepare(packed_transaction, err);
+ } else {
+ /*
+ * We can skip rewriting the `packed-refs`
+ * file. But we do need to leave it locked, so
+ * that somebody else doesn't pack a reference
+ * that we are trying to delete.
+ */
+ if (ref_transaction_abort(packed_transaction, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto cleanup;
+ }
+ backend_data->packed_transaction = NULL;
+ }
}
cleanup:
string_list_clear(&affected_refnames, 0);
if (ret)
- files_transaction_cleanup(transaction);
+ files_transaction_cleanup(refs, transaction);
else
transaction->state = REF_TRANSACTION_PREPARED;
files_downcast(ref_store, 0, "ref_transaction_finish");
size_t i;
int ret = 0;
- struct string_list refs_to_delete = STRING_LIST_INIT_NODUP;
- struct string_list_item *ref_to_delete;
struct strbuf sb = STRBUF_INIT;
+ struct files_transaction_backend_data *backend_data;
+ struct ref_transaction *packed_transaction;
+
assert(err);
return 0;
}
+ backend_data = transaction->backend_data;
+ packed_transaction = backend_data->packed_transaction;
+
/* Perform updates first so live commits remain referenced */
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
}
}
}
- /* Perform deletes now that updates are safely completed */
+
+ /*
+ * Now that updates are safely completed, we can perform
+ * deletes. First delete the reflogs of any references that
+ * will be deleted, since (in the unexpected event of an
+ * error) leaving a reference without a reflog is less bad
+ * than leaving a reflog without a reference (the latter is a
+ * mildly invalid repository state):
+ */
+ for (i = 0; i < transaction->nr; i++) {
+ struct ref_update *update = transaction->updates[i];
+ if (update->flags & REF_DELETING &&
+ !(update->flags & REF_LOG_ONLY) &&
+ !(update->flags & REF_IS_PRUNING)) {
+ strbuf_reset(&sb);
+ files_reflog_path(refs, &sb, update->refname);
+ if (!unlink_or_warn(sb.buf))
+ try_remove_empty_parents(refs, update->refname,
+ REMOVE_EMPTY_PARENTS_REFLOG);
+ }
+ }
+
+ /*
+ * Perform deletes now that updates are safely completed.
+ *
+ * First delete any packed versions of the references, while
+ * retaining the packed-refs lock:
+ */
+ if (packed_transaction) {
+ ret = ref_transaction_commit(packed_transaction, err);
+ ref_transaction_free(packed_transaction);
+ packed_transaction = NULL;
+ backend_data->packed_transaction = NULL;
+ if (ret)
+ goto cleanup;
+ }
+
+ /* Now delete the loose versions of the references: */
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
struct ref_lock *lock = update->backend_data;
}
update->flags |= REF_DELETED_LOOSE;
}
-
- if (!(update->flags & REF_ISPRUNING))
- string_list_append(&refs_to_delete,
- lock->ref_name);
}
}
- if (repack_without_refs(refs, &refs_to_delete, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto cleanup;
- }
-
- /* Delete the reflogs of any references that were deleted: */
- for_each_string_list_item(ref_to_delete, &refs_to_delete) {
- strbuf_reset(&sb);
- files_reflog_path(refs, &sb, ref_to_delete->string);
- if (!unlink_or_warn(sb.buf))
- try_remove_empty_parents(refs, ref_to_delete->string,
- REMOVE_EMPTY_PARENTS_REFLOG);
- }
-
clear_loose_ref_cache(refs);
cleanup:
- files_transaction_cleanup(transaction);
+ files_transaction_cleanup(refs, transaction);
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
}
strbuf_release(&sb);
- string_list_clear(&refs_to_delete, 0);
return ret;
}
struct ref_transaction *transaction,
struct strbuf *err)
{
- files_transaction_cleanup(transaction);
+ struct files_ref_store *refs =
+ files_downcast(ref_store, 0, "ref_transaction_abort");
+
+ files_transaction_cleanup(refs, transaction);
return 0;
}
size_t i;
int ret = 0;
struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
+ struct ref_transaction *packed_transaction = NULL;
assert(err);
if (transaction->state != REF_TRANSACTION_OPEN)
- die("BUG: commit called for transaction that is not open");
+ BUG("commit called for transaction that is not open");
/* Fail if a refname appears more than once in the transaction: */
for (i = 0; i < transaction->nr; i++)
*/
if (refs_for_each_rawref(&refs->base, ref_present,
&affected_refnames))
- die("BUG: initial ref transaction called with existing refs");
+ BUG("initial ref transaction called with existing refs");
+
+ packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, err);
+ if (!packed_transaction) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto cleanup;
+ }
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
if ((update->flags & REF_HAVE_OLD) &&
!is_null_oid(&update->old_oid))
- die("BUG: initial ref transaction with old_sha1 set");
+ BUG("initial ref transaction with old_sha1 set");
if (refs_verify_refname_available(&refs->base, update->refname,
&affected_refnames, NULL,
err)) {
ret = TRANSACTION_NAME_CONFLICT;
goto cleanup;
}
+
+ /*
+ * Add a reference creation for this reference to the
+ * packed-refs transaction:
+ */
+ ref_transaction_add_update(packed_transaction, update->refname,
+ update->flags & ~REF_HAVE_OLD,
+ &update->new_oid, &update->old_oid,
+ NULL);
}
- if (lock_packed_refs(refs, 0)) {
- strbuf_addf(err, "unable to lock packed-refs file: %s",
- strerror(errno));
+ if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
}
- for (i = 0; i < transaction->nr; i++) {
- struct ref_update *update = transaction->updates[i];
-
- if ((update->flags & REF_HAVE_NEW) &&
- !is_null_oid(&update->new_oid))
- add_packed_ref(refs, update->refname,
- &update->new_oid);
- }
-
- if (commit_packed_refs(refs)) {
- strbuf_addf(err, "unable to commit packed-refs file: %s",
- strerror(errno));
+ if (initial_ref_transaction_commit(packed_transaction, err)) {
ret = TRANSACTION_GENERIC_ERROR;
- goto cleanup;
}
+ packed_refs_unlock(refs->packed_ref_store);
cleanup:
+ if (packed_transaction)
+ ref_transaction_free(packed_transaction);
transaction->state = REF_TRANSACTION_CLOSED;
string_list_clear(&affected_refnames, 0);
return ret;
}
static int files_reflog_expire(struct ref_store *ref_store,
- const char *refname, const unsigned char *sha1,
+ const char *refname, const struct object_id *oid,
unsigned int flags,
reflog_expiry_prepare_fn prepare_fn,
reflog_expiry_should_prune_fn should_prune_fn,
{
struct files_ref_store *refs =
files_downcast(ref_store, REF_STORE_WRITE, "reflog_expire");
- static struct lock_file reflog_lock;
+ struct lock_file reflog_lock = LOCK_INIT;
struct expire_reflog_cb cb;
struct ref_lock *lock;
struct strbuf log_file_sb = STRBUF_INIT;
int status = 0;
int type;
struct strbuf err = STRBUF_INIT;
- struct object_id oid;
memset(&cb, 0, sizeof(cb));
cb.flags = flags;
* reference itself, plus we might need to update the
* reference if --updateref was specified:
*/
- lock = lock_ref_sha1_basic(refs, refname, sha1,
- NULL, NULL, REF_NODEREF,
- &type, &err);
+ lock = lock_ref_oid_basic(refs, refname, oid,
+ NULL, NULL, REF_NO_DEREF,
+ &type, &err);
if (!lock) {
error("cannot lock ref '%s': %s", refname, err.buf);
strbuf_release(&err);
}
}
- hashcpy(oid.hash, sha1);
-
- (*prepare_fn)(refname, &oid, cb.policy_cb);
+ (*prepare_fn)(refname, oid, cb.policy_cb);
refs_for_each_reflog_ent(ref_store, refname, expire_reflog_ent, &cb);
(*cleanup_fn)(cb.policy_cb);
!(type & REF_ISSYMREF) &&
!is_null_oid(&cb.last_kept_oid);
- if (close_lock_file(&reflog_lock)) {
+ if (close_lock_file_gently(&reflog_lock)) {
status |= error("couldn't write %s: %s", log_file,
strerror(errno));
+ rollback_lock_file(&reflog_lock);
} else if (update &&
- (write_in_full(get_lock_file_fd(lock->lk),
+ (write_in_full(get_lock_file_fd(&lock->lk),
oid_to_hex(&cb.last_kept_oid), GIT_SHA1_HEXSZ) < 0 ||
- write_str_in_full(get_lock_file_fd(lock->lk), "\n") < 0 ||
- close_ref(lock) < 0)) {
+ write_str_in_full(get_lock_file_fd(&lock->lk), "\n") < 0 ||
+ close_ref_gently(lock) < 0)) {
status |= error("couldn't write %s",
- get_lock_file_path(lock->lk));
+ get_lock_file_path(&lock->lk));
rollback_lock_file(&reflog_lock);
} else if (commit_lock_file(&reflog_lock)) {
status |= error("unable to write reflog '%s' (%s)",
files_initial_transaction_commit,
files_pack_refs,
- files_peel_ref,
files_create_symref,
files_delete_refs,
files_rename_ref,
+ files_copy_ref,
files_ref_iterator_begin,
files_read_raw_ref,