Skip to content

Commit 79a9ff8

Browse files
committed
Merge branch 'kn/fetch-push-bulk-ref-update' into jch
* kn/fetch-push-bulk-ref-update: receive-pack: use batched reference updates send-pack: fix memory leak around duplicate refs fetch: use batched reference updates
2 parents 02e5780 + 4ea42b2 commit 79a9ff8

File tree

5 files changed

+155
-75
lines changed

5 files changed

+155
-75
lines changed

builtin/fetch.c

Lines changed: 65 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-
}
684662

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-
}
697-
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,32 @@ 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 UNUSED,
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 = (struct ref_rejection_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+
}
1668+
1669+
*data->retcode = 1;
1670+
}
1671+
16901672
static int do_fetch(struct transport *transport,
16911673
struct refspec *rs,
16921674
const struct fetch_config *config)
@@ -1807,6 +1789,20 @@ static int do_fetch(struct transport *transport,
18071789
retcode = 1;
18081790
}
18091791

1792+
/*
1793+
* If not atomic, we can still use batched updates, which would be much
1794+
* more performent. We don't initiate the transaction before pruning,
1795+
* since pruning must be an independent step, to avoid F/D conflicts.
1796+
*/
1797+
if (!transaction) {
1798+
transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
1799+
REF_TRANSACTION_ALLOW_FAILURE, &err);
1800+
if (!transaction) {
1801+
retcode = -1;
1802+
goto cleanup;
1803+
}
1804+
}
1805+
18101806
if (fetch_and_consume_refs(&display_state, transport, transaction, ref_map,
18111807
&fetch_head, config)) {
18121808
retcode = 1;
@@ -1838,16 +1834,31 @@ static int do_fetch(struct transport *transport,
18381834
free_refs(tags_ref_map);
18391835
}
18401836

1841-
if (transaction) {
1842-
if (retcode)
1843-
goto cleanup;
1837+
if (retcode)
1838+
goto cleanup;
1839+
1840+
retcode = ref_transaction_commit(transaction, &err);
1841+
if (retcode) {
1842+
/*
1843+
* Explicitly handle transaction cleanup to avoid
1844+
* aborting an already closed transaction.
1845+
*/
1846+
ref_transaction_free(transaction);
1847+
transaction = NULL;
1848+
goto cleanup;
1849+
}
18441850

1845-
retcode = ref_transaction_commit(transaction, &err);
1851+
if (!atomic_fetch) {
1852+
struct ref_rejection_data data = {
1853+
.retcode = &retcode,
1854+
.conflict_msg_shown = 0,
1855+
.remote_name = transport->remote->name,
1856+
};
1857+
1858+
ref_transaction_for_each_rejected_update(transaction,
1859+
ref_transaction_rejection_handler,
1860+
&data);
18461861
if (retcode) {
1847-
/*
1848-
* Explicitly handle transaction cleanup to avoid
1849-
* aborting an already closed transaction.
1850-
*/
18511862
ref_transaction_free(transaction);
18521863
transaction = NULL;
18531864
goto cleanup;

builtin/receive-pack.c

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1845,35 +1845,91 @@ static void BUG_if_skipped_connectivity_check(struct command *commands,
18451845
BUG_if_bug("connectivity check skipped???");
18461846
}
18471847

1848+
static void ref_transaction_rejection_handler(const char *refname,
1849+
const struct object_id *old_oid UNUSED,
1850+
const struct object_id *new_oid UNUSED,
1851+
const char *old_target UNUSED,
1852+
const char *new_target UNUSED,
1853+
enum ref_transaction_error err,
1854+
void *cb_data)
1855+
{
1856+
struct strmap *failed_refs = (struct strmap *)cb_data;
1857+
const char *reason = "";
1858+
1859+
switch (err) {
1860+
case REF_TRANSACTION_ERROR_NAME_CONFLICT:
1861+
reason = "refname conflict";
1862+
break;
1863+
case REF_TRANSACTION_ERROR_CREATE_EXISTS:
1864+
reason = "reference already exists";
1865+
break;
1866+
case REF_TRANSACTION_ERROR_NONEXISTENT_REF:
1867+
reason = "reference does not exist";
1868+
break;
1869+
case REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE:
1870+
reason = "incorrect old value provided";
1871+
break;
1872+
case REF_TRANSACTION_ERROR_INVALID_NEW_VALUE:
1873+
reason = "invalid new value provided";
1874+
break;
1875+
case REF_TRANSACTION_ERROR_EXPECTED_SYMREF:
1876+
reason = "expected symref but found regular ref";
1877+
break;
1878+
default:
1879+
reason = "unkown failure";
1880+
}
1881+
1882+
strmap_put(failed_refs, refname, xstrdup(reason));
1883+
}
1884+
18481885
static void execute_commands_non_atomic(struct command *commands,
18491886
struct shallow_info *si)
18501887
{
18511888
struct command *cmd;
18521889
struct strbuf err = STRBUF_INIT;
1890+
const char *reported_error = "";
1891+
struct strmap failed_refs = STRMAP_INIT;
1892+
1893+
transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
1894+
REF_TRANSACTION_ALLOW_FAILURE, &err);
1895+
if (!transaction) {
1896+
rp_error("%s", err.buf);
1897+
strbuf_reset(&err);
1898+
reported_error = "transaction failed to start";
1899+
goto failure;
1900+
}
18531901

18541902
for (cmd = commands; cmd; cmd = cmd->next) {
18551903
if (!should_process_cmd(cmd) || cmd->run_proc_receive)
18561904
continue;
18571905

1858-
transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
1859-
0, &err);
1860-
if (!transaction) {
1861-
rp_error("%s", err.buf);
1862-
strbuf_reset(&err);
1863-
cmd->error_string = "transaction failed to start";
1864-
continue;
1865-
}
1866-
18671906
cmd->error_string = update(cmd, si);
1907+
}
18681908

1869-
if (!cmd->error_string
1870-
&& ref_transaction_commit(transaction, &err)) {
1871-
rp_error("%s", err.buf);
1872-
strbuf_reset(&err);
1873-
cmd->error_string = "failed to update ref";
1874-
}
1875-
ref_transaction_free(transaction);
1909+
if (ref_transaction_commit(transaction, &err)) {
1910+
rp_error("%s", err.buf);
1911+
reported_error = "failed to update refs";
1912+
goto failure;
18761913
}
1914+
1915+
ref_transaction_for_each_rejected_update(transaction,
1916+
ref_transaction_rejection_handler,
1917+
&failed_refs);
1918+
1919+
if (strmap_empty(&failed_refs))
1920+
goto cleanup;
1921+
1922+
failure:
1923+
for (cmd = commands; cmd; cmd = cmd->next) {
1924+
if (strmap_empty(&failed_refs))
1925+
cmd->error_string = reported_error;
1926+
else if (strmap_contains(&failed_refs, cmd->ref_name))
1927+
cmd->error_string = xstrdup(strmap_get(&failed_refs, cmd->ref_name));
1928+
}
1929+
1930+
cleanup:
1931+
ref_transaction_free(transaction);
1932+
strmap_clear(&failed_refs, 1);
18771933
strbuf_release(&err);
18781934
}
18791935

send-pack.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,13 @@ static int receive_status(struct repository *r,
257257
refname);
258258
continue;
259259
}
260+
261+
/*
262+
* Clients sending duplicate refs can cause the same value
263+
* to be overridden, causing a memory leak.
264+
*/
265+
free(hint->remote_status);
266+
260267
if (!strcmp(head, "ng")) {
261268
hint->status = REF_STATUS_REMOTE_REJECT;
262269
if (p)

t/t1416-ref-transaction-hooks.sh

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,6 @@ test_expect_success 'interleaving hook calls succeed' '
120120
121121
cat >expect <<-EOF &&
122122
hooks/update refs/tags/PRE $ZERO_OID $PRE_OID
123-
hooks/reference-transaction prepared
124-
hooks/reference-transaction committed
125123
hooks/update refs/tags/POST $ZERO_OID $POST_OID
126124
hooks/reference-transaction prepared
127125
hooks/reference-transaction committed

t/t5408-send-pack-stdin.sh

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,23 @@ test_expect_success 'stdin mixed with cmdline' '
6969

7070
test_expect_success 'cmdline refs written in order' '
7171
clear_remote &&
72-
test_must_fail git send-pack remote.git A:foo B:foo &&
73-
verify_push A foo
72+
test_must_fail git send-pack remote.git A:foo B:foo 2>err &&
73+
test_grep "multiple updates for ref ${SQ}refs/heads/foo${SQ} not allowed" err &&
74+
test_must_fail git --git-dir=remote.git rev-parse foo
75+
'
76+
77+
test_expect_success 'cmdline refs with multiple duplicates' '
78+
clear_remote &&
79+
test_must_fail git send-pack remote.git A:foo B:foo C:foo 2>err &&
80+
test_grep "multiple updates for ref ${SQ}refs/heads/foo${SQ} not allowed" err &&
81+
test_must_fail git --git-dir=remote.git rev-parse foo
7482
'
7583

7684
test_expect_success '--stdin refs come after cmdline' '
7785
clear_remote &&
7886
echo A:foo >input &&
7987
test_must_fail git send-pack remote.git --stdin B:foo <input &&
80-
verify_push B foo
88+
test_must_fail git --git-dir=remote.git rev-parse foo
8189
'
8290

8391
test_expect_success 'refspecs and --mirror do not mix (cmdline)' '

0 commit comments

Comments
 (0)