if (dir->sorted == dir->nr)
return;
- qsort(dir->entries, dir->nr, sizeof(*dir->entries), ref_entry_cmp);
+ QSORT(dir->entries, dir->nr, ref_entry_cmp);
/* Remove any duplicates: */
for (i = 0, j = 0; j < dir->nr; j++) {
if (peel_entry(entry, 0))
return -1;
- hashcpy(peeled->hash, entry->u.value.peeled.hash);
+ oidcpy(peeled, &entry->u.value.peeled);
return 0;
}
*/
struct files_ref_store {
struct ref_store base;
+
+ /*
+ * The name of the submodule represented by this object, or
+ * NULL if it represents the main repository's reference
+ * store:
+ */
+ const char *submodule;
+
struct ref_entry *loose;
struct packed_ref_cache *packed;
};
struct files_ref_store *refs = xcalloc(1, sizeof(*refs));
struct ref_store *ref_store = (struct ref_store *)refs;
- base_ref_store_init(ref_store, &refs_be_files, submodule);
+ base_ref_store_init(ref_store, &refs_be_files);
+
+ refs->submodule = xstrdup_or_null(submodule);
return ref_store;
}
+/*
+ * Die if refs is for a submodule (i.e., not for the main repository).
+ * caller is used in any necessary error messages.
+ */
+static void files_assert_main_repository(struct files_ref_store *refs,
+ const char *caller)
+{
+ if (refs->submodule)
+ die("BUG: %s called for a submodule", caller);
+}
+
/*
* Downcast ref_store to files_ref_store. Die if ref_store is not a
* files_ref_store. If submodule_allowed is not true, then also die if
struct ref_store *ref_store, int submodule_allowed,
const char *caller)
{
+ struct files_ref_store *refs;
+
if (ref_store->be != &refs_be_files)
die("BUG: ref_store is type \"%s\" not \"files\" in %s",
ref_store->be->name, caller);
- if (!submodule_allowed)
- assert_main_repository(ref_store, caller);
+ refs = (struct files_ref_store *)ref_store;
- return (struct files_ref_store *)ref_store;
-}
-
-/*
- * Return a pointer to the reference store for the specified
- * submodule. For the main repository, use submodule==NULL; such a
- * call cannot fail. For a submodule, the submodule must exist and be
- * a nonbare repository, otherwise return NULL. Verify that the
- * reference store is a files_ref_store, and cast it to that type
- * before returning it.
- */
-static struct files_ref_store *get_files_ref_store(const char *submodule,
- const char *caller)
-{
- struct ref_store *refs = get_ref_store(submodule);
+ if (!submodule_allowed)
+ files_assert_main_repository(refs, caller);
- return refs ? files_downcast(refs, 1, caller) : NULL;
+ return refs;
}
/* The length of a peeled reference line in packed-refs, including EOL: */
{
char *packed_refs_file;
- if (*refs->base.submodule)
- packed_refs_file = git_pathdup_submodule(refs->base.submodule,
+ if (refs->submodule)
+ packed_refs_file = git_pathdup_submodule(refs->submodule,
"packed-refs");
else
packed_refs_file = git_pathdup("packed-refs");
struct strbuf refname;
struct strbuf path = STRBUF_INIT;
size_t path_baselen;
+ int err = 0;
- if (*refs->base.submodule)
- strbuf_git_path_submodule(&path, refs->base.submodule, "%s", dirname);
+ if (refs->submodule)
+ err = strbuf_git_path_submodule(&path, refs->submodule, "%s", dirname);
else
strbuf_git_path(&path, "%s", dirname);
path_baselen = path.len;
+ if (err) {
+ strbuf_release(&path);
+ return;
+ }
+
d = opendir(path.buf);
if (!d) {
strbuf_release(&path);
create_dir_entry(refs, refname.buf,
refname.len, 1));
} else {
- int read_ok;
-
- if (*refs->base.submodule) {
- hashclr(sha1);
- flag = 0;
- read_ok = !resolve_gitlink_ref(refs->base.submodule,
- refname.buf, sha1);
- } else {
- read_ok = !read_ref_full(refname.buf,
- RESOLVE_REF_READING,
- sha1, &flag);
- }
-
- if (!read_ok) {
+ if (!resolve_ref_recursively(&refs->base,
+ refname.buf,
+ RESOLVE_REF_READING,
+ sha1, &flag)) {
hashclr(sha1);
flag |= REF_ISBROKEN;
} else if (is_null_sha1(sha1)) {
return -1;
}
-int read_raw_ref(struct ref_store *ref_store,
- const char *refname, unsigned char *sha1,
- struct strbuf *referent, unsigned int *type)
+static int files_read_raw_ref(struct ref_store *ref_store,
+ const char *refname, unsigned char *sha1,
+ struct strbuf *referent, unsigned int *type)
{
struct files_ref_store *refs =
files_downcast(ref_store, 1, "read_raw_ref");
int fd;
int ret = -1;
int save_errno;
+ int remaining_retries = 3;
*type = 0;
strbuf_reset(&sb_path);
- if (*refs->base.submodule)
- strbuf_git_path_submodule(&sb_path, refs->base.submodule, "%s", refname);
+ if (refs->submodule)
+ strbuf_git_path_submodule(&sb_path, refs->submodule, "%s", refname);
else
strbuf_git_path(&sb_path, "%s", refname);
* <-> symlink) between the lstat() and reading, then
* we don't want to report that as an error but rather
* try again starting with the lstat().
+ *
+ * We'll keep a count of the retries, though, just to avoid
+ * any confusing situation sending us into an infinite loop.
*/
+ if (remaining_retries-- <= 0)
+ goto out;
+
if (lstat(path, &st) < 0) {
if (errno != ENOENT)
goto out;
ret = 0;
goto out;
}
+ /*
+ * It doesn't look like a refname; fall through to just
+ * treating it like a non-symlink, and reading whatever it
+ * points to.
+ */
}
/* Is it a directory? */
*/
fd = open(path, O_RDONLY);
if (fd < 0) {
- if (errno == ENOENT)
+ if (errno == ENOENT && !S_ISLNK(st.st_mode))
/* inconsistent with lstat; retry */
goto stat_ref;
else
* avoided, namely if we were successfully able to read the ref
* - Generate informative error messages in the case of failure
*/
-static int lock_raw_ref(const char *refname, int mustexist,
+static int lock_raw_ref(struct files_ref_store *refs,
+ const char *refname, int mustexist,
const struct string_list *extras,
const struct string_list *skip,
struct ref_lock **lock_p,
unsigned int *type,
struct strbuf *err)
{
- struct ref_store *ref_store = get_ref_store(NULL);
- struct files_ref_store *refs =
- files_downcast(ref_store, 0, "lock_raw_ref");
struct ref_lock *lock;
struct strbuf ref_file = STRBUF_INIT;
int attempts_remaining = 3;
int ret = TRANSACTION_GENERIC_ERROR;
assert(err);
+ files_assert_main_repository(refs, "lock_raw_ref");
+
*type = 0;
/* First lock the file so it can't change out from under us. */
* fear that its value will change.
*/
- if (read_raw_ref(ref_store, refname,
- lock->old_oid.hash, referent, type)) {
+ if (files_read_raw_ref(&refs->base, refname,
+ lock->old_oid.hash, referent, type)) {
if (errno == ENOENT) {
if (mustexist) {
/* Garden variety missing reference. */
return status;
}
-int peel_ref(const char *refname, unsigned char *sha1)
+static int files_peel_ref(struct ref_store *ref_store,
+ const char *refname, unsigned char *sha1)
{
- struct files_ref_store *refs = get_files_ref_store(NULL, "peel_ref");
+ struct files_ref_store *refs = files_downcast(ref_store, 0, "peel_ref");
int flag;
unsigned char base[20];
int ok;
while ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {
+ if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&
+ ref_type(iter->iter0->refname) != REF_TYPE_PER_WORKTREE)
+ continue;
+
if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
!ref_resolves_to_object(iter->iter0->refname,
iter->iter0->oid,
files_ref_iterator_abort
};
-struct ref_iterator *files_ref_iterator_begin(
- const char *submodule,
+static struct ref_iterator *files_ref_iterator_begin(
+ struct ref_store *ref_store,
const char *prefix, unsigned int flags)
{
struct files_ref_store *refs =
- get_files_ref_store(submodule, "ref_iterator_begin");
+ files_downcast(ref_store, 1, "ref_iterator_begin");
struct ref_dir *loose_dir, *packed_dir;
struct ref_iterator *loose_iter, *packed_iter;
struct files_ref_iterator *iter;
errno = save_errno;
return -1;
} else {
- hashclr(lock->old_oid.hash);
+ oidclr(&lock->old_oid);
return 0;
}
}
if (old_sha1 && hashcmp(lock->old_oid.hash, old_sha1)) {
strbuf_addf(err, "ref '%s' is at %s but expected %s",
lock->ref_name,
- sha1_to_hex(lock->old_oid.hash),
+ oid_to_hex(&lock->old_oid),
sha1_to_hex(old_sha1));
errno = EBUSY;
return -1;
return remove_dir_recursively(path, REMOVE_DIR_EMPTY_ONLY);
}
+static int create_reflock(const char *path, void *cb)
+{
+ struct lock_file *lk = cb;
+
+ return hold_lock_file_for_update(lk, path, LOCK_NO_DEREF) < 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(const char *refname,
+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)
{
- struct files_ref_store *refs =
- get_files_ref_store(NULL, "lock_ref_sha1_basic");
struct strbuf ref_file = STRBUF_INIT;
struct ref_lock *lock;
int last_errno = 0;
- int lflags = LOCK_NO_DEREF;
int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
int resolve_flags = RESOLVE_REF_NO_RECURSE;
- int attempts_remaining = 3;
int resolved;
+ files_assert_main_repository(refs, "lock_ref_sha1_basic");
assert(err);
lock = xcalloc(1, sizeof(struct ref_lock));
lock->ref_name = xstrdup(refname);
- retry:
- switch (safe_create_leading_directories_const(ref_file.buf)) {
- case SCLD_OK:
- break; /* success */
- case SCLD_VANISHED:
- if (--attempts_remaining > 0)
- goto retry;
- /* fall through */
- default:
+ if (raceproof_create_file(ref_file.buf, create_reflock, lock->lk)) {
last_errno = errno;
- strbuf_addf(err, "unable to create directory for '%s'",
- ref_file.buf);
+ unable_to_lock_message(ref_file.buf, errno, err);
goto error_return;
}
- if (hold_lock_file_for_update(lock->lk, ref_file.buf, lflags) < 0) {
- last_errno = errno;
- if (errno == ENOENT && --attempts_remaining > 0)
- /*
- * Maybe somebody just deleted one of the
- * directories leading to ref_file. Try
- * again:
- */
- goto retry;
- else {
- unable_to_lock_message(ref_file.buf, errno, err);
- goto error_return;
- }
- }
if (verify_lock(lock, old_sha1, mustexist, err)) {
last_errno = errno;
goto error_return;
static int timeout_value = 1000;
struct packed_ref_cache *packed_ref_cache;
- assert_main_repository(&refs->base, "lock_packed_refs");
+ files_assert_main_repository(refs, "lock_packed_refs");
if (!timeout_configured) {
git_config_get_int("core.packedrefstimeout", &timeout_value);
int save_errno = 0;
FILE *out;
- assert_main_repository(&refs->base, "commit_packed_refs");
+ files_assert_main_repository(refs, "commit_packed_refs");
if (!packed_ref_cache->lock)
die("internal error: packed-refs not locked");
struct packed_ref_cache *packed_ref_cache =
get_packed_ref_cache(refs);
- assert_main_repository(&refs->base, "rollback_packed_refs");
+ files_assert_main_repository(refs, "rollback_packed_refs");
if (!packed_ref_cache->lock)
die("internal error: packed-refs not locked");
return 0;
}
+enum {
+ REMOVE_EMPTY_PARENTS_REF = 0x01,
+ REMOVE_EMPTY_PARENTS_REFLOG = 0x02
+};
+
/*
- * Remove empty parents, but spare refs/ and immediate subdirs.
- * Note: munges *name.
+ * Remove empty parent directories associated with the specified
+ * reference and/or its reflog, but spare [logs/]refs/ and immediate
+ * subdirs. flags is a combination of REMOVE_EMPTY_PARENTS_REF and/or
+ * REMOVE_EMPTY_PARENTS_REFLOG.
*/
-static void try_remove_empty_parents(char *name)
+static void try_remove_empty_parents(const char *refname, unsigned int flags)
{
+ struct strbuf buf = STRBUF_INIT;
char *p, *q;
int i;
- p = name;
+
+ strbuf_addstr(&buf, refname);
+ p = buf.buf;
for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */
while (*p && *p != '/')
p++;
while (*p == '/')
p++;
}
- for (q = p; *q; q++)
- ;
- while (1) {
+ q = buf.buf + buf.len;
+ while (flags & (REMOVE_EMPTY_PARENTS_REF | REMOVE_EMPTY_PARENTS_REFLOG)) {
while (q > p && *q != '/')
q--;
while (q > p && *(q-1) == '/')
q--;
if (q == p)
break;
- *q = '\0';
- if (rmdir(git_path("%s", name)))
- break;
+ strbuf_setlen(&buf, q - buf.buf);
+ if ((flags & REMOVE_EMPTY_PARENTS_REF) &&
+ rmdir(git_path("%s", buf.buf)))
+ flags &= ~REMOVE_EMPTY_PARENTS_REF;
+ if ((flags & REMOVE_EMPTY_PARENTS_REFLOG) &&
+ rmdir(git_path("logs/%s", buf.buf)))
+ flags &= ~REMOVE_EMPTY_PARENTS_REFLOG;
}
+ strbuf_release(&buf);
}
/* make sure nobody touched the ref, and unlink */
}
ref_transaction_free(transaction);
strbuf_release(&err);
- try_remove_empty_parents(r->name);
}
static void prune_refs(struct ref_to_prune *r)
}
}
-int pack_refs(unsigned int flags)
+static int files_pack_refs(struct ref_store *ref_store, unsigned int flags)
{
struct files_ref_store *refs =
- get_files_ref_store(NULL, "pack_refs");
+ files_downcast(ref_store, 0, "pack_refs");
struct pack_refs_cb_data cbdata;
memset(&cbdata, 0, sizeof(cbdata));
*
* The refs in 'refnames' needn't be sorted. `err` must not be NULL.
*/
-static int repack_without_refs(struct string_list *refnames, struct strbuf *err)
+static int repack_without_refs(struct files_ref_store *refs,
+ struct string_list *refnames, struct strbuf *err)
{
- struct files_ref_store *refs =
- get_files_ref_store(NULL, "repack_without_refs");
struct ref_dir *packed;
struct string_list_item *refname;
int ret, needs_repacking = 0, removed = 0;
+ files_assert_main_repository(refs, "repack_without_refs");
assert(err);
/* Look for a packed ref */
return ret;
}
-static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
-{
- assert(err);
-
- if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
- /*
- * 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_refs(struct string_list *refnames, unsigned int flags)
+static int files_delete_refs(struct ref_store *ref_store,
+ struct string_list *refnames, unsigned int flags)
{
+ struct files_ref_store *refs =
+ files_downcast(ref_store, 0, "delete_refs");
struct strbuf err = STRBUF_INIT;
int i, result = 0;
if (!refnames->nr)
return 0;
- result = repack_without_refs(refnames, &err);
+ result = repack_without_refs(refs, refnames, &err);
if (result) {
/*
* If we failed to rewrite the packed-refs file, then
for (i = 0; i < refnames->nr; i++) {
const char *refname = refnames->items[i].string;
- if (delete_ref(refname, NULL, flags))
+ if (delete_ref(NULL, refname, NULL, flags))
result |= error(_("could not remove reference %s"), refname);
}
*/
#define TMP_RENAMED_LOG "logs/refs/.tmp-renamed-log"
-static int rename_tmp_log(const char *newrefname)
+static int rename_tmp_log_callback(const char *path, void *cb)
{
- int attempts_remaining = 4;
- struct strbuf path = STRBUF_INIT;
- int ret = -1;
+ int *true_errno = cb;
- retry:
- strbuf_reset(&path);
- strbuf_git_path(&path, "logs/%s", newrefname);
- switch (safe_create_leading_directories_const(path.buf)) {
- case SCLD_OK:
- break; /* success */
- case SCLD_VANISHED:
- if (--attempts_remaining > 0)
- goto retry;
- /* fall through */
- default:
- error("unable to create directory for %s", newrefname);
- goto out;
+ if (rename(git_path(TMP_RENAMED_LOG), path)) {
+ /*
+ * rename(a, b) when b is an existing directory ought
+ * to result in ISDIR, but Solaris 5.8 gives ENOTDIR.
+ * Sheesh. Record the true errno for error reporting,
+ * but report EISDIR to raceproof_create_file() so
+ * that it knows to retry.
+ */
+ *true_errno = errno;
+ if (errno == ENOTDIR)
+ errno = EISDIR;
+ return -1;
+ } else {
+ return 0;
}
+}
- if (rename(git_path(TMP_RENAMED_LOG), path.buf)) {
- if ((errno==EISDIR || errno==ENOTDIR) && --attempts_remaining > 0) {
- /*
- * rename(a, b) when b is an existing
- * directory ought to result in ISDIR, but
- * Solaris 5.8 gives ENOTDIR. Sheesh.
- */
- if (remove_empty_directories(&path)) {
- error("Directory not empty: logs/%s", newrefname);
- goto out;
- }
- goto retry;
- } else if (errno == ENOENT && --attempts_remaining > 0) {
- /*
- * Maybe another process just deleted one of
- * the directories in the path to newrefname.
- * Try again from the beginning.
- */
- goto retry;
- } else {
- error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s",
- newrefname, strerror(errno));
- goto out;
- }
+static int rename_tmp_log(const char *newrefname)
+{
+ char *path = git_pathdup("logs/%s", newrefname);
+ int ret, true_errno;
+
+ ret = raceproof_create_file(path, rename_tmp_log_callback, &true_errno);
+ if (ret) {
+ if (errno == EISDIR)
+ error("directory not empty: %s", path);
+ else
+ error("unable to move logfile %s to %s: %s",
+ git_path(TMP_RENAMED_LOG), path,
+ strerror(true_errno));
}
- ret = 0;
-out:
- strbuf_release(&path);
+
+ free(path);
return ret;
}
-int verify_refname_available(const char *newname,
- const struct string_list *extras,
- const struct string_list *skip,
- struct strbuf *err)
+static int files_verify_refname_available(struct ref_store *ref_store,
+ const char *newname,
+ const struct string_list *extras,
+ const struct string_list *skip,
+ struct strbuf *err)
{
struct files_ref_store *refs =
- get_files_ref_store(NULL, "verify_refname_available");
+ files_downcast(ref_store, 1, "verify_refname_available");
struct ref_dir *packed_refs = get_packed_refs(refs);
struct ref_dir *loose_refs = get_loose_refs(refs);
static int write_ref_to_lockfile(struct ref_lock *lock,
const unsigned char *sha1, struct strbuf *err);
-static int commit_ref_update(struct ref_lock *lock,
+static int commit_ref_update(struct files_ref_store *refs,
+ struct ref_lock *lock,
const unsigned char *sha1, const char *logmsg,
struct strbuf *err);
-int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)
+static int files_rename_ref(struct ref_store *ref_store,
+ const char *oldrefname, const char *newrefname,
+ const char *logmsg)
{
+ struct files_ref_store *refs =
+ files_downcast(ref_store, 0, "rename_ref");
unsigned char sha1[20], orig_sha1[20];
int flag = 0, logmoved = 0;
struct ref_lock *lock;
return error("unable to move logfile logs/%s to "TMP_RENAMED_LOG": %s",
oldrefname, strerror(errno));
- if (delete_ref(oldrefname, orig_sha1, REF_NODEREF)) {
+ if (delete_ref(logmsg, oldrefname, orig_sha1, REF_NODEREF)) {
error("unable to delete old %s", oldrefname);
goto rollback;
}
*/
if (!read_ref_full(newrefname, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
sha1, NULL) &&
- delete_ref(newrefname, NULL, REF_NODEREF)) {
- if (errno==EISDIR) {
+ delete_ref(NULL, newrefname, NULL, REF_NODEREF)) {
+ if (errno == EISDIR) {
struct strbuf path = STRBUF_INIT;
int result;
logmoved = log;
- lock = lock_ref_sha1_basic(newrefname, NULL, NULL, NULL, REF_NODEREF,
- NULL, &err);
+ lock = lock_ref_sha1_basic(refs, newrefname, NULL, NULL, NULL,
+ REF_NODEREF, NULL, &err);
if (!lock) {
error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf);
strbuf_release(&err);
hashcpy(lock->old_oid.hash, orig_sha1);
if (write_ref_to_lockfile(lock, orig_sha1, &err) ||
- commit_ref_update(lock, orig_sha1, logmsg, &err)) {
+ commit_ref_update(refs, lock, orig_sha1, logmsg, &err)) {
error("unable to write current sha1 into %s: %s", newrefname, err.buf);
strbuf_release(&err);
goto rollback;
return 0;
rollback:
- lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, NULL, REF_NODEREF,
- NULL, &err);
+ lock = lock_ref_sha1_basic(refs, oldrefname, NULL, NULL, NULL,
+ REF_NODEREF, NULL, &err);
if (!lock) {
error("unable to lock %s for rollback: %s", oldrefname, err.buf);
strbuf_release(&err);
}
flag = log_all_ref_updates;
- log_all_ref_updates = 0;
+ log_all_ref_updates = LOG_REFS_NONE;
if (write_ref_to_lockfile(lock, orig_sha1, &err) ||
- commit_ref_update(lock, orig_sha1, NULL, &err)) {
+ commit_ref_update(refs, lock, orig_sha1, NULL, &err)) {
error("unable to write current sha1 into %s: %s", oldrefname, err.buf);
strbuf_release(&err);
}
return 0;
}
+static int open_or_create_logfile(const char *path, void *cb)
+{
+ int *fd = cb;
+
+ *fd = open(path, O_APPEND | O_WRONLY | O_CREAT, 0666);
+ return (*fd < 0) ? -1 : 0;
+}
+
/*
- * Create a reflog for a ref. If force_create = 0, the reflog will
- * only be created for certain refs (those for which
- * should_autocreate_reflog returns non-zero. Otherwise, create it
- * regardless of the ref name. Fill in *err and return -1 on failure.
+ * Create a reflog for a ref. If force_create = 0, only create the
+ * reflog for certain refs (those for which should_autocreate_reflog
+ * returns non-zero). Otherwise, create it regardless of the reference
+ * name. If the logfile already existed or was created, return 0 and
+ * set *logfd to the file descriptor opened for appending to the file.
+ * If no logfile exists and we decided not to create one, return 0 and
+ * set *logfd to -1. On failure, fill in *err, set *logfd to -1, and
+ * return -1.
*/
-static int log_ref_setup(const char *refname, struct strbuf *logfile, struct strbuf *err, int force_create)
+static int log_ref_setup(const char *refname, int force_create,
+ int *logfd, struct strbuf *err)
{
- int logfd, oflags = O_APPEND | O_WRONLY;
+ char *logfile = git_pathdup("logs/%s", refname);
- strbuf_git_path(logfile, "logs/%s", refname);
if (force_create || should_autocreate_reflog(refname)) {
- if (safe_create_leading_directories(logfile->buf) < 0) {
- strbuf_addf(err, "unable to create directory for '%s': "
- "%s", logfile->buf, strerror(errno));
- return -1;
- }
- oflags |= O_CREAT;
- }
-
- logfd = open(logfile->buf, oflags, 0666);
- if (logfd < 0) {
- if (!(oflags & O_CREAT) && (errno == ENOENT || errno == EISDIR))
- return 0;
+ if (raceproof_create_file(logfile, open_or_create_logfile, logfd)) {
+ if (errno == ENOENT)
+ strbuf_addf(err, "unable to create directory for '%s': "
+ "%s", logfile, strerror(errno));
+ else if (errno == EISDIR)
+ strbuf_addf(err, "there are still logs under '%s'",
+ logfile);
+ else
+ strbuf_addf(err, "unable to append to '%s': %s",
+ logfile, strerror(errno));
- if (errno == EISDIR) {
- if (remove_empty_directories(logfile)) {
- strbuf_addf(err, "there are still logs under "
- "'%s'", logfile->buf);
- return -1;
- }
- logfd = open(logfile->buf, oflags, 0666);
+ goto error;
}
-
- if (logfd < 0) {
- strbuf_addf(err, "unable to append to '%s': %s",
- logfile->buf, strerror(errno));
- return -1;
+ } else {
+ *logfd = open(logfile, O_APPEND | O_WRONLY, 0666);
+ if (*logfd < 0) {
+ if (errno == ENOENT || errno == EISDIR) {
+ /*
+ * The logfile doesn't already exist,
+ * but that is not an error; it only
+ * means that we won't write log
+ * entries to it.
+ */
+ ;
+ } else {
+ strbuf_addf(err, "unable to append to '%s': %s",
+ logfile, strerror(errno));
+ goto error;
+ }
}
}
- adjust_shared_perm(logfile->buf);
- close(logfd);
+ if (*logfd >= 0)
+ adjust_shared_perm(logfile);
+
+ free(logfile);
return 0;
-}
+error:
+ free(logfile);
+ return -1;
+}
-int safe_create_reflog(const char *refname, int force_create, struct strbuf *err)
+static int files_create_reflog(struct ref_store *ref_store,
+ const char *refname, int force_create,
+ struct strbuf *err)
{
- int ret;
- struct strbuf sb = STRBUF_INIT;
+ int fd;
- ret = log_ref_setup(refname, &sb, err, force_create);
- strbuf_release(&sb);
- return ret;
+ /* Check validity (but we don't need the result): */
+ files_downcast(ref_store, 0, "create_reflog");
+
+ if (log_ref_setup(refname, force_create, &fd, err))
+ return -1;
+
+ if (fd >= 0)
+ close(fd);
+
+ return 0;
}
static int log_ref_write_fd(int fd, const unsigned char *old_sha1,
return 0;
}
-static int log_ref_write_1(const char *refname, const unsigned char *old_sha1,
- const unsigned char *new_sha1, const char *msg,
- struct strbuf *logfile, int flags,
- struct strbuf *err)
+int files_log_ref_write(const char *refname, const unsigned char *old_sha1,
+ const unsigned char *new_sha1, const char *msg,
+ int flags, struct strbuf *err)
{
- int logfd, result, oflags = O_APPEND | O_WRONLY;
+ int logfd, result;
- if (log_all_ref_updates < 0)
- log_all_ref_updates = !is_bare_repository();
+ if (log_all_ref_updates == LOG_REFS_UNSET)
+ log_all_ref_updates = is_bare_repository() ? LOG_REFS_NONE : LOG_REFS_NORMAL;
- result = log_ref_setup(refname, logfile, err, flags & REF_FORCE_CREATE_REFLOG);
+ result = log_ref_setup(refname, flags & REF_FORCE_CREATE_REFLOG,
+ &logfd, err);
if (result)
return result;
- logfd = open(logfile->buf, oflags);
if (logfd < 0)
return 0;
result = log_ref_write_fd(logfd, old_sha1, new_sha1,
git_committer_info(0), msg);
if (result) {
- strbuf_addf(err, "unable to append to '%s': %s", logfile->buf,
- strerror(errno));
+ int save_errno = errno;
+
+ strbuf_addf(err, "unable to append to '%s': %s",
+ git_path("logs/%s", refname), strerror(save_errno));
close(logfd);
return -1;
}
if (close(logfd)) {
- strbuf_addf(err, "unable to append to '%s': %s", logfile->buf,
- strerror(errno));
+ int save_errno = errno;
+
+ strbuf_addf(err, "unable to append to '%s': %s",
+ git_path("logs/%s", refname), strerror(save_errno));
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 flags, struct strbuf *err)
-{
- return files_log_ref_write(refname, old_sha1, new_sha1, msg, flags,
- err);
-}
-
-int files_log_ref_write(const char *refname, const unsigned char *old_sha1,
- const unsigned char *new_sha1, const char *msg,
- int flags, struct strbuf *err)
-{
- struct strbuf sb = STRBUF_INIT;
- int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb, flags,
- err);
- strbuf_release(&sb);
- return ret;
-}
-
/*
* Write sha1 into the open lockfile, then close the lockfile. On
* errors, rollback the lockfile, fill in *err and
* 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,
+static int commit_ref_update(struct files_ref_store *refs,
+ struct ref_lock *lock,
const unsigned char *sha1, const char *logmsg,
struct strbuf *err)
{
- struct files_ref_store *refs =
- get_files_ref_store(NULL, "commit_ref_update");
+ files_assert_main_repository(refs, "commit_ref_update");
clear_loose_ref_cache(refs);
- if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, 0, err)) {
+ if (files_log_ref_write(lock->ref_name, lock->old_oid.hash, sha1,
+ logmsg, 0, err)) {
char *old_msg = strbuf_detach(err, NULL);
strbuf_addf(err, "cannot update the ref '%s': %s",
lock->ref_name, old_msg);
if (head_ref && (head_flag & REF_ISSYMREF) &&
!strcmp(head_ref, lock->ref_name)) {
struct strbuf log_err = STRBUF_INIT;
- if (log_ref_write("HEAD", lock->old_oid.hash, sha1,
+ if (files_log_ref_write("HEAD", lock->old_oid.hash, sha1,
logmsg, 0, &log_err)) {
error("%s", log_err.buf);
strbuf_release(&log_err);
struct strbuf err = STRBUF_INIT;
unsigned char new_sha1[20];
if (logmsg && !read_ref(target, new_sha1) &&
- log_ref_write(refname, lock->old_oid.hash, new_sha1, logmsg, 0, &err)) {
+ files_log_ref_write(refname, lock->old_oid.hash, new_sha1,
+ logmsg, 0, &err)) {
error("%s", err.buf);
strbuf_release(&err);
}
return 0;
}
-int create_symref(const char *refname, const char *target, const char *logmsg)
+static int files_create_symref(struct ref_store *ref_store,
+ const char *refname, const char *target,
+ const char *logmsg)
{
+ struct files_ref_store *refs =
+ files_downcast(ref_store, 0, "create_symref");
struct strbuf err = STRBUF_INIT;
struct ref_lock *lock;
int ret;
- lock = lock_ref_sha1_basic(refname, NULL, NULL, NULL, REF_NODEREF, NULL,
+ lock = lock_ref_sha1_basic(refs, refname, NULL,
+ NULL, NULL, REF_NODEREF, NULL,
&err);
if (!lock) {
error("%s", err.buf);
return ret;
}
-int set_worktree_head_symref(const char *gitdir, const char *target)
+int set_worktree_head_symref(const char *gitdir, const char *target, const char *logmsg)
{
static struct lock_file head_lock;
struct ref_lock *lock;
lock->lk = &head_lock;
lock->ref_name = xstrdup(head_rel);
- ret = create_symref_locked(lock, head_rel, target, NULL);
+ ret = create_symref_locked(lock, head_rel, target, logmsg);
unlock_ref(lock); /* will free lock */
strbuf_release(&head_path);
return ret;
}
-int reflog_exists(const char *refname)
+static int files_reflog_exists(struct ref_store *ref_store,
+ const char *refname)
{
struct stat st;
+ /* Check validity (but we don't need the result): */
+ files_downcast(ref_store, 0, "reflog_exists");
+
return !lstat(git_path("logs/%s", refname), &st) &&
S_ISREG(st.st_mode);
}
-int delete_reflog(const char *refname)
+static int files_delete_reflog(struct ref_store *ref_store,
+ const char *refname)
{
+ /* Check validity (but we don't need the result): */
+ files_downcast(ref_store, 0, "delete_reflog");
+
return remove_path(git_path("logs/%s", refname));
}
return scan;
}
-int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data)
+static int files_for_each_reflog_ent_reverse(struct ref_store *ref_store,
+ const char *refname,
+ each_reflog_ent_fn fn,
+ void *cb_data)
{
struct strbuf sb = STRBUF_INIT;
FILE *logfp;
long pos;
int ret = 0, at_tail = 1;
+ /* Check validity (but we don't need the result): */
+ files_downcast(ref_store, 0, "for_each_reflog_ent_reverse");
+
logfp = fopen(git_path("logs/%s", refname), "r");
if (!logfp)
return -1;
return ret;
}
-int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data)
+static int files_for_each_reflog_ent(struct ref_store *ref_store,
+ const char *refname,
+ each_reflog_ent_fn fn, void *cb_data)
{
FILE *logfp;
struct strbuf sb = STRBUF_INIT;
int ret = 0;
+ /* Check validity (but we don't need the result): */
+ files_downcast(ref_store, 0, "for_each_reflog_ent");
+
logfp = fopen(git_path("logs/%s", refname), "r");
if (!logfp)
return -1;
files_reflog_iterator_abort
};
-struct ref_iterator *files_reflog_iterator_begin(void)
+static struct ref_iterator *files_reflog_iterator_begin(struct ref_store *ref_store)
{
struct files_reflog_iterator *iter = xcalloc(1, sizeof(*iter));
struct ref_iterator *ref_iterator = &iter->base;
+ /* Check validity (but we don't need the result): */
+ files_downcast(ref_store, 0, "reflog_iterator_begin");
+
base_ref_iterator_init(ref_iterator, &files_reflog_iterator_vtable);
iter->dir_iterator = dir_iterator_begin(git_path("logs"));
return ref_iterator;
}
-int for_each_reflog(each_ref_fn fn, void *cb_data)
-{
- return do_for_each_ref_iterator(files_reflog_iterator_begin(),
- fn, cb_data);
-}
-
static int ref_update_reject_duplicates(struct string_list *refnames,
struct strbuf *err)
{
* Note that the new update will itself be subject to splitting when
* the iteration gets to it.
*/
-static int split_symref_update(struct ref_update *update,
+static int split_symref_update(struct files_ref_store *refs,
+ struct ref_update *update,
const char *referent,
struct ref_transaction *transaction,
struct string_list *affected_refnames,
return update->refname;
}
+/*
+ * Check whether the REF_HAVE_OLD and old_oid values stored in update
+ * are consistent with oid, which is the reference's current value. If
+ * everything is OK, return 0; otherwise, write an error message to
+ * err and return -1.
+ */
+static int check_old_oid(struct ref_update *update, struct object_id *oid,
+ struct strbuf *err)
+{
+ if (!(update->flags & REF_HAVE_OLD) ||
+ !hashcmp(oid->hash, update->old_sha1))
+ return 0;
+
+ if (is_null_sha1(update->old_sha1))
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "reference already exists",
+ original_update_refname(update));
+ else if (is_null_oid(oid))
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "reference is missing but expected %s",
+ original_update_refname(update),
+ sha1_to_hex(update->old_sha1));
+ else
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "is at %s but expected %s",
+ original_update_refname(update),
+ oid_to_hex(oid),
+ sha1_to_hex(update->old_sha1));
+
+ return -1;
+}
+
/*
* Prepare for carrying out update:
* - Lock the reference referred to by update.
* - If it is an update of head_ref, add a corresponding REF_LOG_ONLY
* update of HEAD.
*/
-static int lock_ref_for_update(struct ref_update *update,
+static int lock_ref_for_update(struct files_ref_store *refs,
+ struct ref_update *update,
struct ref_transaction *transaction,
const char *head_ref,
struct string_list *affected_refnames,
int ret;
struct ref_lock *lock;
+ files_assert_main_repository(refs, "lock_ref_for_update");
+
if ((update->flags & REF_HAVE_NEW) && is_null_sha1(update->new_sha1))
update->flags |= REF_DELETING;
return ret;
}
- ret = lock_raw_ref(update->refname, mustexist,
+ ret = lock_raw_ref(refs, update->refname, mustexist,
affected_refnames, NULL,
- &update->lock, &referent,
+ &lock, &referent,
&update->type, err);
-
if (ret) {
char *reason;
reason = strbuf_detach(err, NULL);
strbuf_addf(err, "cannot lock ref '%s': %s",
- update->refname, reason);
+ original_update_refname(update), reason);
free(reason);
return ret;
}
- lock = update->lock;
+ update->backend_data = lock;
if (update->type & REF_ISSYMREF) {
if (update->flags & REF_NODEREF) {
* the transaction, so we have to read it here
* to record and possibly check old_sha1:
*/
- if (read_ref_full(update->refname,
- mustexist ? RESOLVE_REF_READING : 0,
+ if (read_ref_full(referent.buf, 0,
lock->old_oid.hash, NULL)) {
if (update->flags & REF_HAVE_OLD) {
strbuf_addf(err, "cannot lock ref '%s': "
- "can't resolve old value",
- update->refname);
- return TRANSACTION_GENERIC_ERROR;
- } else {
- hashclr(lock->old_oid.hash);
+ "error reading reference",
+ original_update_refname(update));
+ return -1;
}
- }
- if ((update->flags & REF_HAVE_OLD) &&
- hashcmp(lock->old_oid.hash, update->old_sha1)) {
- strbuf_addf(err, "cannot lock ref '%s': "
- "is at %s but expected %s",
- update->refname,
- sha1_to_hex(lock->old_oid.hash),
- sha1_to_hex(update->old_sha1));
+ } else if (check_old_oid(update, &lock->old_oid, err)) {
return TRANSACTION_GENERIC_ERROR;
}
-
} else {
/*
* Create a new update for the reference this
* of processing the split-off update, so we
* don't have to do it here.
*/
- ret = split_symref_update(update, referent.buf, transaction,
+ ret = split_symref_update(refs, update,
+ referent.buf, transaction,
affected_refnames, err);
if (ret)
return ret;
} else {
struct ref_update *parent_update;
+ if (check_old_oid(update, &lock->old_oid, err))
+ return TRANSACTION_GENERIC_ERROR;
+
/*
* If this update is happening indirectly because of a
* symref update, record the old SHA-1 in the parent
for (parent_update = update->parent_update;
parent_update;
parent_update = parent_update->parent_update) {
- oidcpy(&parent_update->lock->old_oid, &lock->old_oid);
- }
-
- if ((update->flags & REF_HAVE_OLD) &&
- hashcmp(lock->old_oid.hash, update->old_sha1)) {
- if (is_null_sha1(update->old_sha1))
- strbuf_addf(err, "cannot lock ref '%s': reference already exists",
- original_update_refname(update));
- else
- strbuf_addf(err, "cannot lock ref '%s': is at %s but expected %s",
- original_update_refname(update),
- sha1_to_hex(lock->old_oid.hash),
- sha1_to_hex(update->old_sha1));
-
- return TRANSACTION_GENERIC_ERROR;
+ struct ref_lock *parent_lock = parent_update->backend_data;
+ oidcpy(&parent_lock->old_oid, &lock->old_oid);
}
}
* The lock was freed upon failure of
* write_ref_to_lockfile():
*/
- update->lock = NULL;
+ update->backend_data = NULL;
strbuf_addf(err,
- "cannot update the ref '%s': %s",
+ "cannot update ref '%s': %s",
update->refname, write_err);
free(write_err);
return TRANSACTION_GENERIC_ERROR;
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
- ret = lock_ref_for_update(update, transaction, head_ref,
- &affected_refnames, err);
+ ret = lock_ref_for_update(refs, update, transaction,
+ head_ref, &affected_refnames, err);
if (ret)
goto cleanup;
}
/* Perform updates first so live commits remain referenced */
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
- struct ref_lock *lock = update->lock;
+ struct ref_lock *lock = update->backend_data;
if (update->flags & REF_NEEDS_COMMIT ||
update->flags & REF_LOG_ONLY) {
- if (log_ref_write(lock->ref_name, lock->old_oid.hash,
- update->new_sha1,
- update->msg, update->flags, err)) {
+ if (files_log_ref_write(lock->ref_name,
+ lock->old_oid.hash,
+ update->new_sha1,
+ update->msg, update->flags,
+ err)) {
char *old_msg = strbuf_detach(err, NULL);
strbuf_addf(err, "cannot update the ref '%s': %s",
lock->ref_name, old_msg);
free(old_msg);
unlock_ref(lock);
- update->lock = NULL;
+ update->backend_data = NULL;
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
}
if (commit_ref(lock)) {
strbuf_addf(err, "couldn't set '%s'", lock->ref_name);
unlock_ref(lock);
- update->lock = NULL;
+ update->backend_data = NULL;
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
}
/* Perform deletes now that updates are safely completed */
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
+ struct ref_lock *lock = update->backend_data;
if (update->flags & REF_DELETING &&
!(update->flags & REF_LOG_ONLY)) {
- if (delete_ref_loose(update->lock, update->type, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto cleanup;
+ if (!(update->type & REF_ISPACKED) ||
+ update->type & REF_ISSYMREF) {
+ /* It is a loose reference. */
+ if (unlink_or_msg(git_path("%s", lock->ref_name), err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto cleanup;
+ }
+ update->flags |= REF_DELETED_LOOSE;
}
if (!(update->flags & REF_ISPRUNING))
string_list_append(&refs_to_delete,
- update->lock->ref_name);
+ lock->ref_name);
}
}
- if (repack_without_refs(&refs_to_delete, err)) {
+ if (repack_without_refs(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));
+
+ /* Delete the reflogs of any references that were deleted: */
+ for_each_string_list_item(ref_to_delete, &refs_to_delete) {
+ if (!unlink_or_warn(git_path("logs/%s", ref_to_delete->string)))
+ try_remove_empty_parents(ref_to_delete->string,
+ REMOVE_EMPTY_PARENTS_REFLOG);
+ }
+
clear_loose_ref_cache(refs);
cleanup:
transaction->state = REF_TRANSACTION_CLOSED;
- for (i = 0; i < transaction->nr; i++)
- if (transaction->updates[i]->lock)
- unlock_ref(transaction->updates[i]->lock);
+ for (i = 0; i < transaction->nr; i++) {
+ struct ref_update *update = transaction->updates[i];
+ struct ref_lock *lock = update->backend_data;
+
+ if (lock)
+ unlock_ref(lock);
+
+ if (update->flags & REF_DELETED_LOOSE) {
+ /*
+ * The loose reference was deleted. Delete any
+ * empty parent directories. (Note that this
+ * can only work because we have already
+ * removed the lockfile.)
+ */
+ try_remove_empty_parents(update->refname,
+ REMOVE_EMPTY_PARENTS_REF);
+ }
+ }
+
string_list_clear(&refs_to_delete, 0);
free(head_ref);
string_list_clear(&affected_refnames, 0);
return string_list_has_string(affected_refnames, refname);
}
-int initial_ref_transaction_commit(struct ref_transaction *transaction,
- struct strbuf *err)
+static int files_initial_transaction_commit(struct ref_store *ref_store,
+ struct ref_transaction *transaction,
+ struct strbuf *err)
{
struct files_ref_store *refs =
- get_files_ref_store(NULL, "initial_ref_transaction_commit");
+ files_downcast(ref_store, 0, "initial_ref_transaction_commit");
int ret = 0, i;
struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
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 int files_reflog_expire(struct ref_store *ref_store,
+ 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)
{
+ struct files_ref_store *refs =
+ files_downcast(ref_store, 0, "reflog_expire");
static struct lock_file reflog_lock;
struct expire_reflog_cb cb;
struct ref_lock *lock;
* reference itself, plus we might need to update the
* reference if --updateref was specified:
*/
- lock = lock_ref_sha1_basic(refname, sha1, NULL, NULL, REF_NODEREF,
+ lock = lock_ref_sha1_basic(refs, refname, sha1,
+ NULL, NULL, REF_NODEREF,
&type, &err);
if (!lock) {
error("cannot lock ref '%s': %s", refname, err.buf);
return -1;
}
+static int files_init_db(struct ref_store *ref_store, struct strbuf *err)
+{
+ /* Check validity (but we don't need the result): */
+ files_downcast(ref_store, 0, "init_db");
+
+ /*
+ * Create .git/refs/{heads,tags}
+ */
+ safe_create_dir(git_path("refs/heads"), 1);
+ safe_create_dir(git_path("refs/tags"), 1);
+ if (get_shared_repository()) {
+ adjust_shared_perm(git_path("refs/heads"));
+ adjust_shared_perm(git_path("refs/tags"));
+ }
+ return 0;
+}
+
struct ref_storage_be refs_be_files = {
NULL,
"files",
files_ref_store_create,
- files_transaction_commit
+ files_init_db,
+ files_transaction_commit,
+ files_initial_transaction_commit,
+
+ files_pack_refs,
+ files_peel_ref,
+ files_create_symref,
+ files_delete_refs,
+ files_rename_ref,
+
+ files_ref_iterator_begin,
+ files_read_raw_ref,
+ files_verify_refname_available,
+
+ files_reflog_iterator_begin,
+ files_for_each_reflog_ent,
+ files_for_each_reflog_ent_reverse,
+ files_reflog_exists,
+ files_create_reflog,
+ files_delete_reflog,
+ files_reflog_expire
};