When we are updating more than one single ref, i.e. not a commit, then write the updated refs directly to the packed refs file instead of writing them as loose refs.
Change clone to use a transaction instead of using the pacekd refs api.
Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
---
builtin/clone.c | 16 +++++++++----
refs.c | 74 +++++++++++++++++++++++++++++++++++++++++----------------
2 files changed, 66 insertions(+), 24 deletions(-)
Show changes to 2 files +66 −24
builtin/clone.c, refs.c
diff --git a/builtin/clone.c b/builtin/clone.c
index b12989d..000a236 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -497,17 +497,25 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
static void write_remote_refs(const struct ref *local_refs)
{
const struct ref *r;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
- lock_packed_refs(LOCK_DIE_ON_ERROR);
+ transaction = transaction_begin(&err);
+ if (!transaction)
+ die("%s", err.buf);
for (r = local_refs; r; r = r->next) {
if (!r->peer_ref)
continue;
- add_packed_ref(r->peer_ref->name, r->old_sha1);
+ if (transaction_update_sha1(transaction, r->peer_ref->name,
+ r->old_sha1, NULL, 0, 0, NULL,
+ &err))
+ die("%s", err.buf);
}
- if (commit_packed_refs())
- die_errno("unable to overwrite old ref-pack file");
+ if (transaction_commit(transaction, &err))
+ die("%s", err.buf);
+ transaction_free(transaction);
}
static void write_followtags(const struct ref *refs, const char *msg)
diff --git a/refs.c b/refs.c
index 4281011..6aa64dd 100644
--- a/refs.c
+++ b/refs.c
@@ -2481,31 +2481,18 @@ static int repack_without_refs(const char **refnames, int n, struct strbuf *err)
struct ref_dir *packed;
struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
struct string_list_item *ref_to_delete;
- int i, ret, removed = 0;
+ int i, ret;
/* Look for a packed ref */
for (i = 0; i < n; i++)
if (get_packed_ref(refnames[i]))
break;
- /* Avoid processing if we have nothing to do */
- if (i == n)
- return 0; /* no refname exists in packed refs */
-
packed = get_packed_refs(&ref_cache);
/* Remove refnames from the cache */
for (i = 0; i < n; i++)
- if (remove_entry(packed, refnames[i]) != -1)
- removed = 1;
- if (!removed) {
- /*
- * All packed entries disappeared while we were
- * acquiring the lock.
- */
- rollback_packed_refs();
- return 0;
- }
+ remove_entry(packed, refnames[i]);
/* Remove any other accumulated cruft */
do_for_each_entry_in_dir(packed, 0, curate_packed_ref_fn, &refs_to_delete);
@@ -3614,6 +3601,7 @@ int transaction_commit(struct ref_transaction *transaction,
struct strbuf *err)
{
int ret = 0, delnum = 0, i, df_conflict = 0, need_repack = 0;
+ int num_updates = 0;
const char **delnames;
int n = transaction->nr;
struct packed_ref_cache *packed_ref_cache;
@@ -3647,18 +3635,38 @@ int transaction_commit(struct ref_transaction *transaction,
goto cleanup;
}
- /* any loose refs are to be deleted are first copied to packed refs */
+ /* count how many refs we are updating (not deleting) */
for (i = 0; i < n; i++) {
struct ref_update *update = updates[i];
unsigned char sha1[20];
if (update->update_type != UPDATE_SHA1)
continue;
- if (!is_null_sha1(update->new_sha1))
+ if (is_null_sha1(update->new_sha1))
+ continue;
+
+ num_updates++;
+ }
+
+ /*
+ * Always copy loose refs that are to be deleted to the packed refs.
+ * If we are updating multiple refs then copy all non symref refs
+ * to the packed refs too.
+ */
+ for (i = 0; i < n; i++) {
+ struct ref_update *update = updates[i];
+ unsigned char sha1[20];
+ int flag;
+
+ if (update->update_type != UPDATE_SHA1)
+ continue;
+ if (num_updates < 2 && !is_null_sha1(update->new_sha1))
continue;
if (get_packed_ref(update->refname))
continue;
- if (!resolve_ref_unsafe(update->refname, sha1, 1, NULL))
+ if (!resolve_ref_unsafe(update->refname, sha1, 1, &flag))
+ continue;
+ if (flag & REF_ISSYMREF)
continue;
add_packed_ref(update->refname, sha1);
@@ -3682,11 +3690,14 @@ int transaction_commit(struct ref_transaction *transaction,
goto cleanup;
}
}
+ need_repack = 0;
/*
* At this stage any refs that are to be deleted have been moved to the
* packed refs file anf the packed refs file is deleted. We can now
* safely delete these loose refs.
+ * If we are updating multiple normal refs, then those will also be in
+ * the packed refs file so we can delete them too.
*/
/* Unlink any loose refs scheduled for deletion */
@@ -3724,7 +3735,10 @@ int transaction_commit(struct ref_transaction *transaction,
update->lock = NULL;
}
- /* Acquire all ref locks for updates while verifying old values */
+ /*
+ * Acquire all ref locks for updates while verifying old values.
+ * If we are multi-updating then update them in packed refs.
+ */
for (i = 0; i < n; i++) {
struct ref_update *update = updates[i];
@@ -3748,6 +3762,22 @@ int transaction_commit(struct ref_transaction *transaction,
ret = -1;
goto cleanup;
}
+ if (num_updates < 2 || update->type & REF_ISSYMREF)
+ continue;
+
+ if (delete_ref_loose(update->lock, update->type, err)) {
+ ret = -1;
+ goto cleanup;
+ }
+ unlock_ref(update->lock);
+ update->lock = NULL;
+
+ packed = get_packed_refs(&ref_cache);;
+ remove_entry(packed, update->refname);
+ add_packed_ref(update->refname, update->new_sha1);
+ need_repack = 1;
+
+ try_remove_empty_parents((char *)update->refname);
}
/* delete reflog for all deleted refs */
@@ -3795,7 +3825,7 @@ int transaction_commit(struct ref_transaction *transaction,
if (update->update_type != UPDATE_SHA1)
continue;
- if (!is_null_sha1(update->new_sha1)) {
+ if (update->lock && !is_null_sha1(update->new_sha1)) {
ret = write_ref_sha1(update->lock, update->new_sha1,
update->msg);
update->lock = NULL; /* freed by write_ref_sha1 */
@@ -3860,6 +3890,10 @@ int transaction_commit(struct ref_transaction *transaction,
}
}
+ if (need_repack) {
+ packed = get_packed_refs(&ref_cache);
+ sort_ref_dir(packed);
+ }
if (repack_without_refs(delnames, delnum, err))
ret = -1;
clear_loose_ref_cache(&ref_cache);