From 9bbb0fa1fdc6c413b1f35c26e090515e5d0096aa Mon Sep 17 00:00:00 2001 From: Brad King Date: Fri, 30 Aug 2013 14:12:00 -0400 Subject: refs: report ref type from lock_any_ref_for_update Expose lock_ref_sha1_basic's type_p argument to callers of lock_any_ref_for_update. Update all call sites to ignore it by passing NULL for now. Signed-off-by: Brad King Signed-off-by: Junio C Hamano --- refs.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'refs.c') diff --git a/refs.c b/refs.c index 7922261580..c69fd68021 100644 --- a/refs.c +++ b/refs.c @@ -2121,11 +2121,12 @@ struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha } struct ref_lock *lock_any_ref_for_update(const char *refname, - const unsigned char *old_sha1, int flags) + const unsigned char *old_sha1, + int flags, int *type_p) { if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) return NULL; - return lock_ref_sha1_basic(refname, old_sha1, flags, NULL); + return lock_ref_sha1_basic(refname, old_sha1, flags, type_p); } /* @@ -3174,7 +3175,7 @@ int update_ref(const char *action, const char *refname, int flags, enum action_on_err onerr) { static struct ref_lock *lock; - lock = lock_any_ref_for_update(refname, oldval, flags); + lock = lock_any_ref_for_update(refname, oldval, flags, NULL); if (!lock) { const char *str = "Cannot lock the ref '%s'."; switch (onerr) { -- cgit v1.2.1 From 4738a33338b837d4896b5e8d75e777f5e99a25cb Mon Sep 17 00:00:00 2001 From: Brad King Date: Wed, 4 Sep 2013 11:22:40 -0400 Subject: refs: factor update_ref steps into helpers Factor the lock and write steps and error handling into helper functions update_ref_lock and update_ref_write to allow later use elsewhere. Expose lock_any_ref_for_update's type_p to update_ref_lock callers. While at it, drop "static" from the local "lock" variable as it is not necessary to keep across invocations. Signed-off-by: Brad King Signed-off-by: Junio C Hamano --- refs.c | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) (limited to 'refs.c') diff --git a/refs.c b/refs.c index c69fd68021..4347826dcb 100644 --- a/refs.c +++ b/refs.c @@ -3170,12 +3170,13 @@ int for_each_reflog(each_ref_fn fn, void *cb_data) return retval; } -int update_ref(const char *action, const char *refname, - const unsigned char *sha1, const unsigned char *oldval, - int flags, enum action_on_err onerr) +static struct ref_lock *update_ref_lock(const char *refname, + const unsigned char *oldval, + int flags, int *type_p, + enum action_on_err onerr) { - static struct ref_lock *lock; - lock = lock_any_ref_for_update(refname, oldval, flags, NULL); + struct ref_lock *lock; + lock = lock_any_ref_for_update(refname, oldval, flags, type_p); if (!lock) { const char *str = "Cannot lock the ref '%s'."; switch (onerr) { @@ -3183,8 +3184,14 @@ int update_ref(const char *action, const char *refname, case DIE_ON_ERR: die(str, refname); break; case QUIET_ON_ERR: break; } - return 1; } + return lock; +} + +static int update_ref_write(const char *action, const char *refname, + const unsigned char *sha1, struct ref_lock *lock, + enum action_on_err onerr) +{ if (write_ref_sha1(lock, sha1, action) < 0) { const char *str = "Cannot update the ref '%s'."; switch (onerr) { @@ -3197,6 +3204,17 @@ int update_ref(const char *action, const char *refname, return 0; } +int update_ref(const char *action, const char *refname, + const unsigned char *sha1, const unsigned char *oldval, + int flags, enum action_on_err onerr) +{ + struct ref_lock *lock; + lock = update_ref_lock(refname, oldval, flags, 0, onerr); + if (!lock) + return 1; + return update_ref_write(action, refname, sha1, lock, onerr); +} + struct ref *find_ref_by_name(const struct ref *list, const char *name) { for ( ; list; list = list->next) -- cgit v1.2.1 From 2ddb5d170ac5fa3568efecb001c4ff0af0580c0d Mon Sep 17 00:00:00 2001 From: Brad King Date: Wed, 4 Sep 2013 11:22:41 -0400 Subject: refs: factor delete_ref loose ref step into a helper Factor loose ref deletion into helper function delete_ref_loose to allow later use elsewhere. Signed-off-by: Brad King Signed-off-by: Junio C Hamano --- refs.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) (limited to 'refs.c') diff --git a/refs.c b/refs.c index 4347826dcb..ab9d22ec7a 100644 --- a/refs.c +++ b/refs.c @@ -2450,24 +2450,31 @@ static int repack_without_ref(const char *refname) return commit_packed_refs(); } +static int delete_ref_loose(struct ref_lock *lock, int flag) +{ + if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) { + /* loose */ + int err, i = strlen(lock->lk->filename) - 5; /* .lock */ + + lock->lk->filename[i] = 0; + err = unlink_or_warn(lock->lk->filename); + lock->lk->filename[i] = '.'; + if (err && errno != ENOENT) + return 1; + } + return 0; +} + int delete_ref(const char *refname, const unsigned char *sha1, int delopt) { struct ref_lock *lock; - int err, i = 0, ret = 0, flag = 0; + int ret = 0, flag = 0; lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag); if (!lock) return 1; - if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) { - /* loose */ - i = strlen(lock->lk->filename) - 5; /* .lock */ - lock->lk->filename[i] = 0; - err = unlink_or_warn(lock->lk->filename); - if (err && errno != ENOENT) - ret = 1; + ret |= delete_ref_loose(lock, flag); - lock->lk->filename[i] = '.'; - } /* removing the loose one could have resurrected an earlier * packed one. Also, if it was not loose we need to repack * without it. -- cgit v1.2.1 From 61cee0dbac84160ad4a1bcc2ecdb15761bd284fc Mon Sep 17 00:00:00 2001 From: Brad King Date: Wed, 4 Sep 2013 11:22:42 -0400 Subject: refs: add function to repack without multiple refs Generalize repack_without_ref as repack_without_refs to support a list of refs and implement the former in terms of the latter. Signed-off-by: Brad King Signed-off-by: Junio C Hamano --- refs.c | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) (limited to 'refs.c') diff --git a/refs.c b/refs.c index ab9d22ec7a..92d801c00a 100644 --- a/refs.c +++ b/refs.c @@ -2414,42 +2414,57 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data) return 0; } -static int repack_without_ref(const char *refname) +static int repack_without_refs(const char **refnames, int n) { struct ref_dir *packed; struct string_list refs_to_delete = STRING_LIST_INIT_DUP; struct string_list_item *ref_to_delete; + int i, removed = 0; + + /* Look for a packed ref */ + for (i = 0; i < n; i++) + if (get_packed_ref(refnames[i])) + break; - if (!get_packed_ref(refname)) - return 0; /* refname does not exist in packed refs */ + /* Avoid locking if we have nothing to do */ + if (i == n) + return 0; /* no refname exists in packed refs */ if (lock_packed_refs(0)) { unable_to_lock_error(git_path("packed-refs"), errno); - return error("cannot delete '%s' from packed refs", refname); + return error("cannot delete '%s' from packed refs", refnames[i]); } packed = get_packed_refs(&ref_cache); - /* Remove refname from the cache: */ - if (remove_entry(packed, refname) == -1) { + /* Remove refnames from the cache */ + for (i = 0; i < n; i++) + if (remove_entry(packed, refnames[i]) != -1) + removed = 1; + if (!removed) { /* - * The packed entry disappeared while we were + * All packed entries disappeared while we were * acquiring the lock. */ rollback_packed_refs(); return 0; } - /* Remove any other accumulated cruft: */ + /* Remove any other accumulated cruft */ do_for_each_entry_in_dir(packed, 0, curate_packed_ref_fn, &refs_to_delete); for_each_string_list_item(ref_to_delete, &refs_to_delete) { if (remove_entry(packed, ref_to_delete->string) == -1) die("internal error"); } - /* Write what remains: */ + /* Write what remains */ return commit_packed_refs(); } +static int repack_without_ref(const char *refname) +{ + return repack_without_refs(&refname, 1); +} + static int delete_ref_loose(struct ref_lock *lock, int flag) { if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) { -- cgit v1.2.1 From 98aee92d5c9e5161a8c11b7666996e4ffffe80ab Mon Sep 17 00:00:00 2001 From: Brad King Date: Wed, 4 Sep 2013 11:22:43 -0400 Subject: refs: add update_refs for multiple simultaneous updates Add 'struct ref_update' to encode the information needed to update or delete a ref (name, new sha1, optional old sha1, no-deref flag). Add function 'update_refs' accepting an array of updates to perform. First sort the input array to order locks consistently everywhere and reject multiple updates to the same ref. Then acquire locks on all refs with verified old values. Then update or delete all refs accordingly. Fail if any one lock cannot be obtained or any one old value does not match. Though the refs themselves cannot be modified together in a single atomic transaction, this function does enable some useful semantics. For example, a caller may create a new branch starting from the head of another branch and rewind the original branch at the same time. This transfers ownership of commits between branches without risk of losing commits added to the original branch by a concurrent process, or risk of a concurrent process creating the new branch first. Signed-off-by: Brad King Signed-off-by: Junio C Hamano --- refs.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) (limited to 'refs.c') diff --git a/refs.c b/refs.c index 92d801c00a..46177ad256 100644 --- a/refs.c +++ b/refs.c @@ -3237,6 +3237,106 @@ int update_ref(const char *action, const char *refname, return update_ref_write(action, refname, sha1, lock, onerr); } +static int ref_update_compare(const void *r1, const void *r2) +{ + const struct ref_update * const *u1 = r1; + const struct ref_update * const *u2 = r2; + return strcmp((*u1)->ref_name, (*u2)->ref_name); +} + +static int ref_update_reject_duplicates(struct ref_update **updates, int n, + enum action_on_err onerr) +{ + int i; + for (i = 1; i < n; i++) + if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) { + const char *str = + "Multiple updates for ref '%s' not allowed."; + switch (onerr) { + case MSG_ON_ERR: + error(str, updates[i]->ref_name); break; + case DIE_ON_ERR: + die(str, updates[i]->ref_name); break; + case QUIET_ON_ERR: + break; + } + return 1; + } + return 0; +} + +int update_refs(const char *action, const struct ref_update **updates_orig, + int n, enum action_on_err onerr) +{ + int ret = 0, delnum = 0, i; + struct ref_update **updates; + int *types; + struct ref_lock **locks; + const char **delnames; + + if (!updates_orig || !n) + return 0; + + /* Allocate work space */ + updates = xmalloc(sizeof(*updates) * n); + types = xmalloc(sizeof(*types) * n); + locks = xcalloc(n, sizeof(*locks)); + delnames = xmalloc(sizeof(*delnames) * n); + + /* Copy, sort, and reject duplicate refs */ + memcpy(updates, updates_orig, sizeof(*updates) * n); + qsort(updates, n, sizeof(*updates), ref_update_compare); + ret = ref_update_reject_duplicates(updates, n, onerr); + if (ret) + goto cleanup; + + /* Acquire all locks while verifying old values */ + for (i = 0; i < n; i++) { + locks[i] = update_ref_lock(updates[i]->ref_name, + (updates[i]->have_old ? + updates[i]->old_sha1 : NULL), + updates[i]->flags, + &types[i], onerr); + if (!locks[i]) { + ret = 1; + goto cleanup; + } + } + + /* Perform updates first so live commits remain referenced */ + for (i = 0; i < n; i++) + if (!is_null_sha1(updates[i]->new_sha1)) { + ret = update_ref_write(action, + updates[i]->ref_name, + updates[i]->new_sha1, + locks[i], onerr); + locks[i] = NULL; /* freed by update_ref_write */ + if (ret) + goto cleanup; + } + + /* Perform deletes now that updates are safely completed */ + for (i = 0; i < n; i++) + if (locks[i]) { + delnames[delnum++] = locks[i]->ref_name; + ret |= delete_ref_loose(locks[i], types[i]); + } + ret |= repack_without_refs(delnames, delnum); + for (i = 0; i < delnum; i++) + unlink_or_warn(git_path("logs/%s", delnames[i])); + clear_loose_ref_cache(&ref_cache); + +cleanup: + for (i = 0; i < n; i++) + if (locks[i]) + unlock_ref(locks[i]); + free(updates); + free(types); + free(locks); + free(delnames); + return ret; +} + struct ref *find_ref_by_name(const struct ref *list, const char *name) { for ( ; list; list = list->next) -- cgit v1.2.1