return 1;
}
+struct packed_ref_cache {
+ struct ref_entry *root;
+
+ /*
+ * Count of references to the data structure in this instance,
+ * including the pointer from ref_cache::packed if any. The
+ * data will not be freed as long as the reference count is
+ * nonzero.
+ */
+ unsigned int referrers;
+
+ /*
+ * Iff the packed-refs file associated with this instance is
+ * currently locked for writing, this points at the associated
+ * lock (which is owned by somebody else). The referrer count
+ * is also incremented when the file is locked and decremented
+ * when it is unlocked.
+ */
+ struct lock_file *lock;
+};
+
/*
* Future: need to be in "struct repository"
* when doing a full libification.
static struct ref_cache {
struct ref_cache *next;
struct ref_entry *loose;
- struct ref_entry *packed;
+ struct packed_ref_cache *packed;
/*
* The submodule name, or "" for the main repo. We allocate
* length 1 rather than FLEX_ARRAY so that the main ref_cache
char name[1];
} ref_cache, *submodule_ref_caches;
+/* Lock used for the main packed-refs file: */
+static struct lock_file packlock;
+
+/*
+ * 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_entry(packed_refs->root);
+ free(packed_refs);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
static void clear_packed_ref_cache(struct ref_cache *refs)
{
if (refs->packed) {
- free_ref_entry(refs->packed);
+ struct packed_ref_cache *packed_refs = refs->packed;
+
+ if (packed_refs->lock)
+ die("internal error: packed-ref cache cleared while locked");
refs->packed = NULL;
+ release_packed_ref_cache(packed_refs);
}
}
}
}
-static struct ref_dir *get_packed_refs(struct ref_cache *refs)
+/*
+ * Get the packed_ref_cache for the specified ref_cache, creating it
+ * if necessary.
+ */
+static struct packed_ref_cache *get_packed_ref_cache(struct ref_cache *refs)
{
if (!refs->packed) {
const char *packed_refs_file;
FILE *f;
- refs->packed = create_dir_entry(refs, "", 0, 0);
+ refs->packed = xcalloc(1, sizeof(*refs->packed));
+ acquire_packed_ref_cache(refs->packed);
+ refs->packed->root = create_dir_entry(refs, "", 0, 0);
if (*refs->name)
packed_refs_file = git_path_submodule(refs->name, "packed-refs");
else
packed_refs_file = git_path("packed-refs");
f = fopen(packed_refs_file, "r");
if (f) {
- read_packed_refs(f, get_ref_dir(refs->packed));
+ read_packed_refs(f, get_ref_dir(refs->packed->root));
fclose(f);
}
}
- return get_ref_dir(refs->packed);
+ 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->root);
+}
+
+static struct ref_dir *get_packed_refs(struct ref_cache *refs)
+{
+ return get_packed_ref_dir(get_packed_ref_cache(refs));
}
void add_packed_ref(const char *refname, const unsigned char *sha1)
{
- add_ref(get_packed_refs(&ref_cache),
+ struct packed_ref_cache *packed_ref_cache =
+ get_packed_ref_cache(&ref_cache);
+
+ if (!packed_ref_cache->lock)
+ die("internal error: packed refs not locked");
+ add_ref(get_packed_ref_dir(packed_ref_cache),
create_ref_entry(refname, sha1, REF_ISPACKED, 1));
}
static int do_for_each_entry(struct ref_cache *refs, const char *base,
each_ref_entry_fn fn, void *cb_data)
{
- struct ref_dir *packed_dir = get_packed_refs(refs);
+ struct packed_ref_cache *packed_ref_cache = get_packed_ref_cache(refs);
+ struct ref_dir *packed_dir = get_packed_ref_dir(packed_ref_cache);
struct ref_dir *loose_dir = get_loose_refs(refs);
int retval = 0;
+ acquire_packed_ref_cache(packed_ref_cache);
if (base && *base) {
packed_dir = find_containing_dir(packed_dir, base, 0);
loose_dir = find_containing_dir(loose_dir, base, 0);
loose_dir, 0, fn, cb_data);
}
+ release_packed_ref_cache(packed_ref_cache);
return retval;
}
return 0;
}
+int lock_packed_refs(int flags)
+{
+ struct packed_ref_cache *packed_ref_cache;
+
+ /* Discard the old cache because it might be invalid: */
+ clear_packed_ref_cache(&ref_cache);
+ if (hold_lock_file_for_update(&packlock, git_path("packed-refs"), flags) < 0)
+ return -1;
+ /* Read the current packed-refs while holding the lock: */
+ packed_ref_cache = get_packed_ref_cache(&ref_cache);
+ packed_ref_cache->lock = &packlock;
+ /* Increment the reference count to prevent it from being freed: */
+ acquire_packed_ref_cache(packed_ref_cache);
+ return 0;
+}
+
+int commit_packed_refs(void)
+{
+ struct packed_ref_cache *packed_ref_cache =
+ get_packed_ref_cache(&ref_cache);
+ int error = 0;
+
+ if (!packed_ref_cache->lock)
+ die("internal error: packed-refs not locked");
+ write_or_die(packed_ref_cache->lock->fd,
+ PACKED_REFS_HEADER, strlen(PACKED_REFS_HEADER));
+
+ do_for_each_entry_in_dir(get_packed_ref_dir(packed_ref_cache),
+ 0, write_packed_entry_fn,
+ &packed_ref_cache->lock->fd);
+ if (commit_lock_file(packed_ref_cache->lock))
+ error = -1;
+ packed_ref_cache->lock = NULL;
+ release_packed_ref_cache(packed_ref_cache);
+ return error;
+}
+
+void rollback_packed_refs(void)
+{
+ struct packed_ref_cache *packed_ref_cache =
+ get_packed_ref_cache(&ref_cache);
+
+ if (!packed_ref_cache->lock)
+ die("internal error: packed-refs not locked");
+ rollback_lock_file(packed_ref_cache->lock);
+ packed_ref_cache->lock = NULL;
+ release_packed_ref_cache(packed_ref_cache);
+ clear_packed_ref_cache(&ref_cache);
+}
+
struct ref_to_prune {
struct ref_to_prune *next;
unsigned char sha1[20];
struct pack_refs_cb_data {
unsigned int flags;
+ struct ref_dir *packed_refs;
struct ref_to_prune *ref_to_prune;
- int fd;
};
-static int pack_one_ref(struct ref_entry *entry, void *cb_data)
+/*
+ * An each_ref_entry_fn that is run over loose references only. If
+ * the loose reference can be packed, add an entry in the packed ref
+ * cache. If the reference should be pruned, also add it to
+ * ref_to_prune in the pack_refs_cb_data.
+ */
+static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data)
{
struct pack_refs_cb_data *cb = cb_data;
enum peel_status peel_status;
+ struct ref_entry *packed_entry;
int is_tag_ref = !prefixcmp(entry->name, "refs/tags/");
- /* ALWAYS pack refs that were already packed or are tags */
- if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref &&
- !(entry->flag & REF_ISPACKED))
+ /* ALWAYS pack tags */
+ if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref)
return 0;
/* Do not pack symbolic or broken refs: */
if ((entry->flag & REF_ISSYMREF) || !ref_resolves_to_object(entry))
return 0;
+ /* Add a packed ref cache entry equivalent to the loose entry. */
peel_status = peel_entry(entry, 1);
if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG)
die("internal error peeling reference %s (%s)",
entry->name, sha1_to_hex(entry->u.value.sha1));
- write_packed_entry(cb->fd, entry->name, entry->u.value.sha1,
- peel_status == PEEL_PEELED ?
- entry->u.value.peeled : NULL);
+ packed_entry = find_ref(cb->packed_refs, entry->name);
+ if (packed_entry) {
+ /* Overwrite existing packed entry with info from loose entry */
+ packed_entry->flag = REF_ISPACKED | REF_KNOWS_PEELED;
+ hashcpy(packed_entry->u.value.sha1, entry->u.value.sha1);
+ } else {
+ packed_entry = create_ref_entry(entry->name, entry->u.value.sha1,
+ REF_ISPACKED | REF_KNOWS_PEELED, 0);
+ add_ref(cb->packed_refs, packed_entry);
+ }
+ hashcpy(packed_entry->u.value.peeled, entry->u.value.peeled);
- /* If the ref was already packed, there is no need to prune it. */
- if ((cb->flags & PACK_REFS_PRUNE) && !(entry->flag & REF_ISPACKED)) {
+ /* Schedule the loose reference for pruning if requested. */
+ if ((cb->flags & PACK_REFS_PRUNE)) {
int namelen = strlen(entry->name) + 1;
struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
hashcpy(n->sha1, entry->u.value.sha1);
}
}
-static struct lock_file packlock;
-
int pack_refs(unsigned int flags)
{
struct pack_refs_cb_data cbdata;
memset(&cbdata, 0, sizeof(cbdata));
cbdata.flags = flags;
- cbdata.fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"),
- LOCK_DIE_ON_ERROR);
+ lock_packed_refs(LOCK_DIE_ON_ERROR);
+ cbdata.packed_refs = get_packed_refs(&ref_cache);
- write_or_die(cbdata.fd, PACKED_REFS_HEADER, strlen(PACKED_REFS_HEADER));
+ do_for_each_entry_in_dir(get_loose_refs(&ref_cache), 0,
+ pack_if_possible_fn, &cbdata);
- do_for_each_entry(&ref_cache, "", pack_one_ref, &cbdata);
- if (commit_lock_file(&packlock) < 0)
+ if (commit_packed_refs())
die_errno("unable to overwrite old ref-pack file");
+
prune_refs(cbdata.ref_to_prune);
return 0;
}
static int repack_without_ref(const char *refname)
{
- int fd;
struct ref_dir *packed;
struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
struct string_list_item *ref_to_delete;
if (!get_packed_ref(refname))
return 0; /* refname does not exist in packed refs */
- fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
- if (fd < 0) {
+ if (lock_packed_refs(0)) {
unable_to_lock_error(git_path("packed-refs"), errno);
return error("cannot delete '%s' from packed refs", refname);
}
- clear_packed_ref_cache(&ref_cache);
packed = get_packed_refs(&ref_cache);
/* Remove refname from the cache: */
* The packed entry disappeared while we were
* acquiring the lock.
*/
- rollback_lock_file(&packlock);
+ rollback_packed_refs();
return 0;
}
}
/* Write what remains: */
- write_or_die(fd, PACKED_REFS_HEADER, strlen(PACKED_REFS_HEADER));
- do_for_each_entry_in_dir(packed, 0, write_packed_entry_fn, &fd);
- return commit_lock_file(&packlock);
+ return commit_packed_refs();
}
int delete_ref(const char *refname, const unsigned char *sha1, int delopt)