Skip to content

Commit 98aee92

Browse files
bradkinggitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 61cee0d commit 98aee92

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed

refs.c

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3237,6 +3237,106 @@ int update_ref(const char *action, const char *refname,
32373237
return update_ref_write(action, refname, sha1, lock, onerr);
32383238
}
32393239

3240+
static int ref_update_compare(const void *r1, const void *r2)
3241+
{
3242+
const struct ref_update * const *u1 = r1;
3243+
const struct ref_update * const *u2 = r2;
3244+
return strcmp((*u1)->ref_name, (*u2)->ref_name);
3245+
}
3246+
3247+
static int ref_update_reject_duplicates(struct ref_update **updates, int n,
3248+
enum action_on_err onerr)
3249+
{
3250+
int i;
3251+
for (i = 1; i < n; i++)
3252+
if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) {
3253+
const char *str =
3254+
"Multiple updates for ref '%s' not allowed.";
3255+
switch (onerr) {
3256+
case MSG_ON_ERR:
3257+
error(str, updates[i]->ref_name); break;
3258+
case DIE_ON_ERR:
3259+
die(str, updates[i]->ref_name); break;
3260+
case QUIET_ON_ERR:
3261+
break;
3262+
}
3263+
return 1;
3264+
}
3265+
return 0;
3266+
}
3267+
3268+
int update_refs(const char *action, const struct ref_update **updates_orig,
3269+
int n, enum action_on_err onerr)
3270+
{
3271+
int ret = 0, delnum = 0, i;
3272+
struct ref_update **updates;
3273+
int *types;
3274+
struct ref_lock **locks;
3275+
const char **delnames;
3276+
3277+
if (!updates_orig || !n)
3278+
return 0;
3279+
3280+
/* Allocate work space */
3281+
updates = xmalloc(sizeof(*updates) * n);
3282+
types = xmalloc(sizeof(*types) * n);
3283+
locks = xcalloc(n, sizeof(*locks));
3284+
delnames = xmalloc(sizeof(*delnames) * n);
3285+
3286+
/* Copy, sort, and reject duplicate refs */
3287+
memcpy(updates, updates_orig, sizeof(*updates) * n);
3288+
qsort(updates, n, sizeof(*updates), ref_update_compare);
3289+
ret = ref_update_reject_duplicates(updates, n, onerr);
3290+
if (ret)
3291+
goto cleanup;
3292+
3293+
/* Acquire all locks while verifying old values */
3294+
for (i = 0; i < n; i++) {
3295+
locks[i] = update_ref_lock(updates[i]->ref_name,
3296+
(updates[i]->have_old ?
3297+
updates[i]->old_sha1 : NULL),
3298+
updates[i]->flags,
3299+
&types[i], onerr);
3300+
if (!locks[i]) {
3301+
ret = 1;
3302+
goto cleanup;
3303+
}
3304+
}
3305+
3306+
/* Perform updates first so live commits remain referenced */
3307+
for (i = 0; i < n; i++)
3308+
if (!is_null_sha1(updates[i]->new_sha1)) {
3309+
ret = update_ref_write(action,
3310+
updates[i]->ref_name,
3311+
updates[i]->new_sha1,
3312+
locks[i], onerr);
3313+
locks[i] = NULL; /* freed by update_ref_write */
3314+
if (ret)
3315+
goto cleanup;
3316+
}
3317+
3318+
/* Perform deletes now that updates are safely completed */
3319+
for (i = 0; i < n; i++)
3320+
if (locks[i]) {
3321+
delnames[delnum++] = locks[i]->ref_name;
3322+
ret |= delete_ref_loose(locks[i], types[i]);
3323+
}
3324+
ret |= repack_without_refs(delnames, delnum);
3325+
for (i = 0; i < delnum; i++)
3326+
unlink_or_warn(git_path("logs/%s", delnames[i]));
3327+
clear_loose_ref_cache(&ref_cache);
3328+
3329+
cleanup:
3330+
for (i = 0; i < n; i++)
3331+
if (locks[i])
3332+
unlock_ref(locks[i]);
3333+
free(updates);
3334+
free(types);
3335+
free(locks);
3336+
free(delnames);
3337+
return ret;
3338+
}
3339+
32403340
struct ref *find_ref_by_name(const struct ref *list, const char *name)
32413341
{
32423342
for ( ; list; list = list->next)

refs.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,20 @@ struct ref_lock {
1010
int force_write;
1111
};
1212

13+
/**
14+
* Information needed for a single ref update. Set new_sha1 to the
15+
* new value or to zero to delete the ref. To check the old value
16+
* while locking the ref, set have_old to 1 and set old_sha1 to the
17+
* value or to zero to ensure the ref does not exist before update.
18+
*/
19+
struct ref_update {
20+
const char *ref_name;
21+
unsigned char new_sha1[20];
22+
unsigned char old_sha1[20];
23+
int flags; /* REF_NODEREF? */
24+
int have_old; /* 1 if old_sha1 is valid, 0 otherwise */
25+
};
26+
1327
/*
1428
* Bit values set in the flags argument passed to each_ref_fn():
1529
*/
@@ -214,6 +228,12 @@ int update_ref(const char *action, const char *refname,
214228
const unsigned char *sha1, const unsigned char *oldval,
215229
int flags, enum action_on_err onerr);
216230

231+
/**
232+
* Lock all refs and then perform all modifications.
233+
*/
234+
int update_refs(const char *action, const struct ref_update **updates,
235+
int n, enum action_on_err onerr);
236+
217237
extern int parse_hide_refs_config(const char *var, const char *value, const char *);
218238
extern int ref_is_hidden(const char *);
219239

0 commit comments

Comments
 (0)