Skip to content

Commit cdb7872

Browse files
committed
Merge branch 'kn/fetch-push-bulk-ref-update'
"git push" and "git fetch" are taught to update refs in batches to gain performance. * kn/fetch-push-bulk-ref-update: receive-pack: handle reference deletions separately refs/files: skip updates with errors in batched updates receive-pack: use batched reference updates send-pack: fix memory leak around duplicate refs fetch: use batched reference updates refs: add function to translate errors to strings
2 parents 0ba1a58 + 5c697f0 commit cdb7872

11 files changed

+265
-103
lines changed

builtin/fetch.c

Lines changed: 73 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -640,17 +640,13 @@ static struct ref *get_ref_map(struct remote *remote,
640640
return ref_map;
641641
}
642642

643-
#define STORE_REF_ERROR_OTHER 1
644-
#define STORE_REF_ERROR_DF_CONFLICT 2
645-
646643
static int s_update_ref(const char *action,
647644
struct ref *ref,
648645
struct ref_transaction *transaction,
649646
int check_old)
650647
{
651648
char *msg;
652649
char *rla = getenv("GIT_REFLOG_ACTION");
653-
struct ref_transaction *our_transaction = NULL;
654650
struct strbuf err = STRBUF_INIT;
655651
int ret;
656652

@@ -660,43 +656,10 @@ static int s_update_ref(const char *action,
660656
rla = default_rla.buf;
661657
msg = xstrfmt("%s: %s", rla, action);
662658

663-
/*
664-
* If no transaction was passed to us, we manage the transaction
665-
* ourselves. Otherwise, we trust the caller to handle the transaction
666-
* lifecycle.
667-
*/
668-
if (!transaction) {
669-
transaction = our_transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
670-
0, &err);
671-
if (!transaction) {
672-
ret = STORE_REF_ERROR_OTHER;
673-
goto out;
674-
}
675-
}
676-
677659
ret = ref_transaction_update(transaction, ref->name, &ref->new_oid,
678660
check_old ? &ref->old_oid : NULL,
679661
NULL, NULL, 0, msg, &err);
680-
if (ret) {
681-
ret = STORE_REF_ERROR_OTHER;
682-
goto out;
683-
}
684-
685-
if (our_transaction) {
686-
switch (ref_transaction_commit(our_transaction, &err)) {
687-
case 0:
688-
break;
689-
case REF_TRANSACTION_ERROR_NAME_CONFLICT:
690-
ret = STORE_REF_ERROR_DF_CONFLICT;
691-
goto out;
692-
default:
693-
ret = STORE_REF_ERROR_OTHER;
694-
goto out;
695-
}
696-
}
697662

698-
out:
699-
ref_transaction_free(our_transaction);
700663
if (ret)
701664
error("%s", err.buf);
702665
strbuf_release(&err);
@@ -1139,7 +1102,6 @@ N_("it took %.2f seconds to check forced updates; you can use\n"
11391102
"to avoid this check\n");
11401103

11411104
static int store_updated_refs(struct display_state *display_state,
1142-
const char *remote_name,
11431105
int connectivity_checked,
11441106
struct ref_transaction *transaction, struct ref *ref_map,
11451107
struct fetch_head *fetch_head,
@@ -1277,11 +1239,6 @@ static int store_updated_refs(struct display_state *display_state,
12771239
}
12781240
}
12791241

1280-
if (rc & STORE_REF_ERROR_DF_CONFLICT)
1281-
error(_("some local refs could not be updated; try running\n"
1282-
" 'git remote prune %s' to remove any old, conflicting "
1283-
"branches"), remote_name);
1284-
12851242
if (advice_enabled(ADVICE_FETCH_SHOW_FORCED_UPDATES)) {
12861243
if (!config->show_forced_updates) {
12871244
warning(_(warn_show_forced_updates));
@@ -1365,9 +1322,8 @@ static int fetch_and_consume_refs(struct display_state *display_state,
13651322
}
13661323

13671324
trace2_region_enter("fetch", "consume_refs", the_repository);
1368-
ret = store_updated_refs(display_state, transport->remote->name,
1369-
connectivity_checked, transaction, ref_map,
1370-
fetch_head, config);
1325+
ret = store_updated_refs(display_state, connectivity_checked,
1326+
transaction, ref_map, fetch_head, config);
13711327
trace2_region_leave("fetch", "consume_refs", the_repository);
13721328

13731329
out:
@@ -1687,6 +1643,36 @@ static int set_head(const struct ref *remote_refs, struct remote *remote)
16871643
return result;
16881644
}
16891645

1646+
struct ref_rejection_data {
1647+
int *retcode;
1648+
int conflict_msg_shown;
1649+
const char *remote_name;
1650+
};
1651+
1652+
static void ref_transaction_rejection_handler(const char *refname,
1653+
const struct object_id *old_oid UNUSED,
1654+
const struct object_id *new_oid UNUSED,
1655+
const char *old_target UNUSED,
1656+
const char *new_target UNUSED,
1657+
enum ref_transaction_error err,
1658+
void *cb_data)
1659+
{
1660+
struct ref_rejection_data *data = cb_data;
1661+
1662+
if (err == REF_TRANSACTION_ERROR_NAME_CONFLICT && !data->conflict_msg_shown) {
1663+
error(_("some local refs could not be updated; try running\n"
1664+
" 'git remote prune %s' to remove any old, conflicting "
1665+
"branches"), data->remote_name);
1666+
data->conflict_msg_shown = 1;
1667+
} else {
1668+
const char *reason = ref_transaction_error_msg(err);
1669+
1670+
error(_("fetching ref %s failed: %s"), refname, reason);
1671+
}
1672+
1673+
*data->retcode = 1;
1674+
}
1675+
16901676
static int do_fetch(struct transport *transport,
16911677
struct refspec *rs,
16921678
const struct fetch_config *config)
@@ -1807,6 +1793,24 @@ static int do_fetch(struct transport *transport,
18071793
retcode = 1;
18081794
}
18091795

1796+
/*
1797+
* If not atomic, we can still use batched updates, which would be much
1798+
* more performant. We don't initiate the transaction before pruning,
1799+
* since pruning must be an independent step, to avoid F/D conflicts.
1800+
*
1801+
* TODO: if reference transactions gain logical conflict resolution, we
1802+
* can delete and create refs (with F/D conflicts) in the same transaction
1803+
* and this can be moved above the 'prune_refs()' block.
1804+
*/
1805+
if (!transaction) {
1806+
transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
1807+
REF_TRANSACTION_ALLOW_FAILURE, &err);
1808+
if (!transaction) {
1809+
retcode = -1;
1810+
goto cleanup;
1811+
}
1812+
}
1813+
18101814
if (fetch_and_consume_refs(&display_state, transport, transaction, ref_map,
18111815
&fetch_head, config)) {
18121816
retcode = 1;
@@ -1838,16 +1842,31 @@ static int do_fetch(struct transport *transport,
18381842
free_refs(tags_ref_map);
18391843
}
18401844

1841-
if (transaction) {
1842-
if (retcode)
1843-
goto cleanup;
1845+
if (retcode)
1846+
goto cleanup;
18441847

1845-
retcode = ref_transaction_commit(transaction, &err);
1848+
retcode = ref_transaction_commit(transaction, &err);
1849+
if (retcode) {
1850+
/*
1851+
* Explicitly handle transaction cleanup to avoid
1852+
* aborting an already closed transaction.
1853+
*/
1854+
ref_transaction_free(transaction);
1855+
transaction = NULL;
1856+
goto cleanup;
1857+
}
1858+
1859+
if (!atomic_fetch) {
1860+
struct ref_rejection_data data = {
1861+
.retcode = &retcode,
1862+
.conflict_msg_shown = 0,
1863+
.remote_name = transport->remote->name,
1864+
};
1865+
1866+
ref_transaction_for_each_rejected_update(transaction,
1867+
ref_transaction_rejection_handler,
1868+
&data);
18461869
if (retcode) {
1847-
/*
1848-
* Explicitly handle transaction cleanup to avoid
1849-
* aborting an already closed transaction.
1850-
*/
18511870
ref_transaction_free(transaction);
18521871
transaction = NULL;
18531872
goto cleanup;

builtin/receive-pack.c

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1846,36 +1846,102 @@ static void BUG_if_skipped_connectivity_check(struct command *commands,
18461846
BUG_if_bug("connectivity check skipped???");
18471847
}
18481848

1849+
static void ref_transaction_rejection_handler(const char *refname,
1850+
const struct object_id *old_oid UNUSED,
1851+
const struct object_id *new_oid UNUSED,
1852+
const char *old_target UNUSED,
1853+
const char *new_target UNUSED,
1854+
enum ref_transaction_error err,
1855+
void *cb_data)
1856+
{
1857+
struct strmap *failed_refs = cb_data;
1858+
1859+
strmap_put(failed_refs, refname, (char *)ref_transaction_error_msg(err));
1860+
}
1861+
18491862
static void execute_commands_non_atomic(struct command *commands,
18501863
struct shallow_info *si)
18511864
{
18521865
struct command *cmd;
18531866
struct strbuf err = STRBUF_INIT;
1867+
const char *reported_error = NULL;
1868+
struct strmap failed_refs = STRMAP_INIT;
18541869

1855-
for (cmd = commands; cmd; cmd = cmd->next) {
1856-
if (!should_process_cmd(cmd) || cmd->run_proc_receive)
1857-
continue;
1870+
/*
1871+
* Reference updates, where D/F conflicts shouldn't arise due to
1872+
* one reference being deleted, while the other being created
1873+
* are treated as conflicts in batched updates. This is because
1874+
* we don't do conflict resolution inside a transaction. To
1875+
* mitigate this, delete references in a separate batch.
1876+
*
1877+
* NEEDSWORK: Add conflict resolution between deletion and creation
1878+
* of reference updates within a transaction. With that, we can
1879+
* combine the two phases.
1880+
*/
1881+
enum processing_phase {
1882+
PHASE_DELETIONS,
1883+
PHASE_OTHERS
1884+
};
18581885

1859-
transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
1860-
0, &err);
1861-
if (!transaction) {
1862-
rp_error("%s", err.buf);
1863-
strbuf_reset(&err);
1864-
cmd->error_string = "transaction failed to start";
1865-
continue;
1886+
for (enum processing_phase phase = PHASE_DELETIONS; phase <= PHASE_OTHERS; phase++) {
1887+
for (cmd = commands; cmd; cmd = cmd->next) {
1888+
if (!should_process_cmd(cmd) || cmd->run_proc_receive)
1889+
continue;
1890+
1891+
if (phase == PHASE_DELETIONS && !is_null_oid(&cmd->new_oid))
1892+
continue;
1893+
else if (phase == PHASE_OTHERS && is_null_oid(&cmd->new_oid))
1894+
continue;
1895+
1896+
/*
1897+
* Lazily create a transaction only when we know there are
1898+
* updates to be added.
1899+
*/
1900+
if (!transaction) {
1901+
transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
1902+
REF_TRANSACTION_ALLOW_FAILURE, &err);
1903+
if (!transaction) {
1904+
rp_error("%s", err.buf);
1905+
strbuf_reset(&err);
1906+
reported_error = "transaction failed to start";
1907+
goto failure;
1908+
}
1909+
}
1910+
1911+
cmd->error_string = update(cmd, si);
18661912
}
18671913

1868-
cmd->error_string = update(cmd, si);
1914+
/* No transaction, so nothing to commit */
1915+
if (!transaction)
1916+
goto cleanup;
18691917

1870-
if (!cmd->error_string
1871-
&& ref_transaction_commit(transaction, &err)) {
1918+
if (ref_transaction_commit(transaction, &err)) {
18721919
rp_error("%s", err.buf);
1873-
strbuf_reset(&err);
1874-
cmd->error_string = "failed to update ref";
1920+
reported_error = "failed to update refs";
1921+
goto failure;
18751922
}
1923+
1924+
ref_transaction_for_each_rejected_update(transaction,
1925+
ref_transaction_rejection_handler,
1926+
&failed_refs);
1927+
1928+
if (strmap_empty(&failed_refs))
1929+
goto cleanup;
1930+
1931+
failure:
1932+
for (cmd = commands; cmd; cmd = cmd->next) {
1933+
if (reported_error)
1934+
cmd->error_string = reported_error;
1935+
else if (strmap_contains(&failed_refs, cmd->ref_name))
1936+
cmd->error_string = strmap_get(&failed_refs, cmd->ref_name);
1937+
}
1938+
1939+
cleanup:
18761940
ref_transaction_free(transaction);
1941+
transaction = NULL;
1942+
strmap_clear(&failed_refs, 0);
1943+
strbuf_release(&err);
18771944
}
1878-
strbuf_release(&err);
18791945
}
18801946

18811947
static void execute_commands_atomic(struct command *commands,

builtin/update-ref.c

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -575,30 +575,7 @@ static void print_rejected_refs(const char *refname,
575575
void *cb_data UNUSED)
576576
{
577577
struct strbuf sb = STRBUF_INIT;
578-
const char *reason = "";
579-
580-
switch (err) {
581-
case REF_TRANSACTION_ERROR_NAME_CONFLICT:
582-
reason = "refname conflict";
583-
break;
584-
case REF_TRANSACTION_ERROR_CREATE_EXISTS:
585-
reason = "reference already exists";
586-
break;
587-
case REF_TRANSACTION_ERROR_NONEXISTENT_REF:
588-
reason = "reference does not exist";
589-
break;
590-
case REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE:
591-
reason = "incorrect old value provided";
592-
break;
593-
case REF_TRANSACTION_ERROR_INVALID_NEW_VALUE:
594-
reason = "invalid new value provided";
595-
break;
596-
case REF_TRANSACTION_ERROR_EXPECTED_SYMREF:
597-
reason = "expected symref but found regular ref";
598-
break;
599-
default:
600-
reason = "unkown failure";
601-
}
578+
const char *reason = ref_transaction_error_msg(err);
602579

603580
strbuf_addf(&sb, "rejected %s %s %s %s\n", refname,
604581
new_oid ? oid_to_hex(new_oid) : new_target,

refs.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3314,3 +3314,23 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)
33143314
return (update->flags & REF_HAVE_OLD) &&
33153315
(!is_null_oid(&update->old_oid) || update->old_target);
33163316
}
3317+
3318+
const char *ref_transaction_error_msg(enum ref_transaction_error err)
3319+
{
3320+
switch (err) {
3321+
case REF_TRANSACTION_ERROR_NAME_CONFLICT:
3322+
return "refname conflict";
3323+
case REF_TRANSACTION_ERROR_CREATE_EXISTS:
3324+
return "reference already exists";
3325+
case REF_TRANSACTION_ERROR_NONEXISTENT_REF:
3326+
return "reference does not exist";
3327+
case REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE:
3328+
return "incorrect old value provided";
3329+
case REF_TRANSACTION_ERROR_INVALID_NEW_VALUE:
3330+
return "invalid new value provided";
3331+
case REF_TRANSACTION_ERROR_EXPECTED_SYMREF:
3332+
return "expected symref but found regular ref";
3333+
default:
3334+
return "unknown failure";
3335+
}
3336+
}

refs.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,11 @@ void ref_transaction_for_each_rejected_update(struct ref_transaction *transactio
907907
ref_transaction_for_each_rejected_update_fn cb,
908908
void *cb_data);
909909

910+
/*
911+
* Translate errors to human readable error messages.
912+
*/
913+
const char *ref_transaction_error_msg(enum ref_transaction_error err);
914+
910915
/*
911916
* Free `*transaction` and all associated data.
912917
*/

0 commit comments

Comments
 (0)