Skip to content

Commit 63518a5

Browse files
jiangxingitster
authored andcommitted
New capability "report-status-v2" for git-push
The new introduced "proc-receive" hook may handle a command for a pseudo-reference with a zero-old as its old-oid, while the hook may create or update a reference with different name, different new-oid, and different old-oid (the reference may exist already with a non-zero old-oid). Current "report-status" protocol cannot report the status for such reference rewrite. Add new capability "report-status-v2" and new report protocol which is not backward compatible for report of git-push. If a user pushes to a pseudo-reference "refs/for/master/topic", and "receive-pack" creates two new references "refs/changes/23/123/1" and "refs/changes/24/124/1", for client without the knowledge of "report-status-v2", "receive-pack" will only send "ok/ng" directives in the report, such as: ok ref/for/master/topic But for client which has the knowledge of "report-status-v2", "receive-pack" will use "option" directives to report more attributes for the reference given by the above "ok/ng" directive. ok refs/for/master/topic option refname refs/changes/23/123/1 option new-oid <new-oid> ok refs/for/master/topic option refname refs/changes/24/124/1 option new-oid <new-oid> The client will report two new created references to the end user. Suggested-by: Junio C Hamano <[email protected]> Suggested-by: Jeff King <[email protected]> Signed-off-by: Jiang Xin <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 195d6ea commit 63518a5

14 files changed

+472
-100
lines changed

builtin/receive-pack.c

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ static int advertise_push_options;
5656
static int unpack_limit = 100;
5757
static off_t max_input_size;
5858
static int report_status;
59+
static int report_status_v2;
5960
static int use_sideband;
6061
static int use_atomic;
6162
static int use_push_options;
@@ -239,7 +240,7 @@ static void show_ref(const char *path, const struct object_id *oid)
239240
struct strbuf cap = STRBUF_INIT;
240241

241242
strbuf_addstr(&cap,
242-
"report-status delete-refs side-band-64k quiet");
243+
"report-status report-status-v2 delete-refs side-band-64k quiet");
243244
if (advertise_atomic_push)
244245
strbuf_addstr(&cap, " atomic");
245246
if (prefer_ofs_delta)
@@ -1937,6 +1938,8 @@ static struct command *read_head_info(struct packet_reader *reader,
19371938
const char *feature_list = reader->line + linelen + 1;
19381939
if (parse_feature_request(feature_list, "report-status"))
19391940
report_status = 1;
1941+
if (parse_feature_request(feature_list, "report-status-v2"))
1942+
report_status_v2 = 1;
19401943
if (parse_feature_request(feature_list, "side-band-64k"))
19411944
use_sideband = LARGE_PACKET_MAX;
19421945
if (parse_feature_request(feature_list, "quiet"))
@@ -2248,6 +2251,51 @@ static void report(struct command *commands, const char *unpack_status)
22482251
strbuf_release(&buf);
22492252
}
22502253

2254+
static void report_v2(struct command *commands, const char *unpack_status)
2255+
{
2256+
struct command *cmd;
2257+
struct strbuf buf = STRBUF_INIT;
2258+
struct ref_push_report *report;
2259+
2260+
packet_buf_write(&buf, "unpack %s\n",
2261+
unpack_status ? unpack_status : "ok");
2262+
for (cmd = commands; cmd; cmd = cmd->next) {
2263+
int count = 0;
2264+
2265+
if (cmd->error_string) {
2266+
packet_buf_write(&buf, "ng %s %s\n",
2267+
cmd->ref_name,
2268+
cmd->error_string);
2269+
continue;
2270+
}
2271+
packet_buf_write(&buf, "ok %s\n",
2272+
cmd->ref_name);
2273+
for (report = cmd->report; report; report = report->next) {
2274+
if (count++ > 0)
2275+
packet_buf_write(&buf, "ok %s\n",
2276+
cmd->ref_name);
2277+
if (report->ref_name)
2278+
packet_buf_write(&buf, "option refname %s\n",
2279+
report->ref_name);
2280+
if (report->old_oid)
2281+
packet_buf_write(&buf, "option old-oid %s\n",
2282+
oid_to_hex(report->old_oid));
2283+
if (report->new_oid)
2284+
packet_buf_write(&buf, "option new-oid %s\n",
2285+
oid_to_hex(report->new_oid));
2286+
if (report->forced_update)
2287+
packet_buf_write(&buf, "option forced-update\n");
2288+
}
2289+
}
2290+
packet_buf_flush(&buf);
2291+
2292+
if (use_sideband)
2293+
send_sideband(1, 1, buf.buf, buf.len, use_sideband);
2294+
else
2295+
write_or_die(1, buf.buf, buf.len);
2296+
strbuf_release(&buf);
2297+
}
2298+
22512299
static int delete_only(struct command *commands)
22522300
{
22532301
struct command *cmd;
@@ -2356,7 +2404,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
23562404
&push_options);
23572405
if (pack_lockfile)
23582406
unlink_or_warn(pack_lockfile);
2359-
if (report_status)
2407+
if (report_status_v2)
2408+
report_v2(commands, unpack_status);
2409+
else if (report_status)
23602410
report(commands, unpack_status);
23612411
run_receive_hook(commands, "post-receive", 1,
23622412
&push_options);

builtin/send-pack.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@ static struct send_pack_args args;
2929
static void print_helper_status(struct ref *ref)
3030
{
3131
struct strbuf buf = STRBUF_INIT;
32+
struct ref_push_report *report;
3233

3334
for (; ref; ref = ref->next) {
3435
const char *msg = NULL;
3536
const char *res;
37+
int count = 0;
3638

3739
switch(ref->status) {
3840
case REF_STATUS_NONE:
@@ -94,6 +96,23 @@ static void print_helper_status(struct ref *ref)
9496
}
9597
strbuf_addch(&buf, '\n');
9698

99+
if (ref->status == REF_STATUS_OK) {
100+
for (report = ref->report; report; report = report->next) {
101+
if (count++ > 0)
102+
strbuf_addf(&buf, "ok %s\n", ref->name);
103+
if (report->ref_name)
104+
strbuf_addf(&buf, "option refname %s\n",
105+
report->ref_name);
106+
if (report->old_oid)
107+
strbuf_addf(&buf, "option old-oid %s\n",
108+
oid_to_hex(report->old_oid));
109+
if (report->new_oid)
110+
strbuf_addf(&buf, "option new-oid %s\n",
111+
oid_to_hex(report->new_oid));
112+
if (report->forced_update)
113+
strbuf_addstr(&buf, "option forced-update\n");
114+
}
115+
}
97116
write_or_die(1, buf.buf, buf.len);
98117
}
99118
strbuf_release(&buf);

remote.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ struct ref {
148148
REF_STATUS_ATOMIC_PUSH_FAILED
149149
} status;
150150
char *remote_status;
151+
struct ref_push_report *report;
151152
struct ref *peer_ref; /* when renaming */
152153
char name[FLEX_ARRAY]; /* more */
153154
};

send-pack.c

Lines changed: 82 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -153,48 +153,107 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)
153153
{
154154
struct ref *hint;
155155
int ret;
156+
struct ref_push_report *report = NULL;
157+
int new_report = 0;
158+
int once = 0;
156159

157160
hint = NULL;
158161
ret = receive_unpack_status(reader);
159162
while (1) {
163+
struct object_id old_oid, new_oid;
164+
const char *head;
160165
const char *refname;
161-
char *msg;
166+
char *p;
162167
if (packet_reader_read(reader) != PACKET_READ_NORMAL)
163168
break;
164-
if (!starts_with(reader->line, "ok ") && !starts_with(reader->line, "ng ")) {
165-
error("invalid ref status from remote: %s", reader->line);
169+
head = reader->line;
170+
p = strchr(head, ' ');
171+
if (!p) {
172+
error("invalid status line from remote: %s", reader->line);
166173
ret = -1;
167174
break;
168175
}
176+
*p++ = '\0';
169177

170-
refname = reader->line + 3;
171-
msg = strchr(refname, ' ');
172-
if (msg)
173-
*msg++ = '\0';
178+
if (!strcmp(head, "option")) {
179+
const char *key, *val;
174180

181+
if (!hint || !(report || new_report)) {
182+
if (!once++)
183+
error("'option' without a matching 'ok/ng' directive");
184+
ret = -1;
185+
continue;
186+
}
187+
if (new_report) {
188+
if (!hint->report) {
189+
hint->report = xcalloc(1, sizeof(struct ref_push_report));
190+
report = hint->report;
191+
} else {
192+
report = hint->report;
193+
while (report->next)
194+
report = report->next;
195+
report->next = xcalloc(1, sizeof(struct ref_push_report));
196+
report = report->next;
197+
}
198+
new_report = 0;
199+
}
200+
key = p;
201+
p = strchr(key, ' ');
202+
if (p)
203+
*p++ = '\0';
204+
val = p;
205+
if (!strcmp(key, "refname"))
206+
report->ref_name = xstrdup_or_null(val);
207+
else if (!strcmp(key, "old-oid") && val &&
208+
!parse_oid_hex(val, &old_oid, &val))
209+
report->old_oid = oiddup(&old_oid);
210+
else if (!strcmp(key, "new-oid") && val &&
211+
!parse_oid_hex(val, &new_oid, &val))
212+
report->new_oid = oiddup(&new_oid);
213+
else if (!strcmp(key, "forced-update"))
214+
report->forced_update = 1;
215+
continue;
216+
}
217+
218+
report = NULL;
219+
new_report = 0;
220+
if (strcmp(head, "ok") && strcmp(head, "ng")) {
221+
error("invalid ref status from remote: %s", head);
222+
ret = -1;
223+
break;
224+
}
225+
refname = p;
226+
p = strchr(refname, ' ');
227+
if (p)
228+
*p++ = '\0';
175229
/* first try searching at our hint, falling back to all refs */
176230
if (hint)
177231
hint = find_ref_by_name(hint, refname);
178232
if (!hint)
179233
hint = find_ref_by_name(refs, refname);
180234
if (!hint) {
181235
warning("remote reported status on unknown ref: %s",
182-
refname);
236+
refname);
183237
continue;
184238
}
185-
if (hint->status != REF_STATUS_EXPECTING_REPORT) {
239+
if (hint->status != REF_STATUS_EXPECTING_REPORT &&
240+
hint->status != REF_STATUS_OK &&
241+
hint->status != REF_STATUS_REMOTE_REJECT) {
186242
warning("remote reported status on unexpected ref: %s",
187-
refname);
243+
refname);
188244
continue;
189245
}
190-
191-
if (reader->line[0] == 'o' && reader->line[1] == 'k')
192-
hint->status = REF_STATUS_OK;
193-
else
246+
if (!strcmp(head, "ng")) {
194247
hint->status = REF_STATUS_REMOTE_REJECT;
195-
hint->remote_status = xstrdup_or_null(msg);
196-
/* start our next search from the next ref */
197-
hint = hint->next;
248+
if (p)
249+
hint->remote_status = xstrdup(p);
250+
else
251+
hint->remote_status = "failed";
252+
} else {
253+
hint->status = REF_STATUS_OK;
254+
hint->remote_status = xstrdup_or_null(p);
255+
new_report = 1;
256+
}
198257
}
199258
return ret;
200259
}
@@ -369,7 +428,9 @@ int send_pack(struct send_pack_args *args,
369428
struct packet_reader reader;
370429

371430
/* Does the other end support the reporting? */
372-
if (server_supports("report-status"))
431+
if (server_supports("report-status-v2"))
432+
status_report = 2;
433+
else if (server_supports("report-status"))
373434
status_report = 1;
374435
if (server_supports("delete-refs"))
375436
allow_deleting_refs = 1;
@@ -418,8 +479,10 @@ int send_pack(struct send_pack_args *args,
418479

419480
use_push_options = push_options_supported && args->push_options;
420481

421-
if (status_report)
482+
if (status_report == 1)
422483
strbuf_addstr(&cap_buf, " report-status");
484+
else if (status_report == 2)
485+
strbuf_addstr(&cap_buf, " report-status-v2");
423486
if (use_sideband)
424487
strbuf_addstr(&cap_buf, " side-band-64k");
425488
if (quiet_supported && (args->quiet || !args->progress))

t/t5411-proc-receive-hook.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,15 @@ run_proc_receive_hook_test() {
7878
# Initialize the upstream repository and local workbench.
7979
setup_upstream_and_workbench
8080

81+
# Load test cases that only need to be executed once.
82+
for t in "$TEST_DIRECTORY"/t5411/once-*.sh
83+
do
84+
. "$t"
85+
done
86+
87+
# Initialize the upstream repository and local workbench.
88+
setup_upstream_and_workbench
89+
8190
# Run test cases for 'proc-receive' hook on local file protocol.
8291
run_proc_receive_hook_test local
8392

t/t5411/once-0010-report-status-v1.sh

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
test_expect_success "setup proc-receive hook" '
2+
write_script "$upstream/hooks/proc-receive" <<-EOF
3+
printf >&2 "# proc-receive hook\n"
4+
test-tool proc-receive -v \
5+
-r "ok refs/for/master/topic1" \
6+
-r "option fall-through" \
7+
-r "ok refs/for/master/topic2" \
8+
-r "option refname refs/for/changes/23/123/1" \
9+
-r "option new-oid $A" \
10+
-r "ok refs/for/master/topic2" \
11+
-r "option refname refs/for/changes/24/124/2" \
12+
-r "option old-oid $B" \
13+
-r "option new-oid $A" \
14+
-r "option forced-update" \
15+
-r "ng refs/for/next/topic target branch not exist"
16+
EOF
17+
'
18+
19+
# Refs of upstream : master(A)
20+
# Refs of workbench: master(A) tags/v123
21+
# git push : (B) refs/for/master/topic1(A) foo(A) refs/for/next/topic(A) refs/for/master/topic2(A)
22+
test_expect_success "proc-receive: report status v1" '
23+
{
24+
if test -z "$GIT_DEFAULT_HASH" || test "$GIT_DEFAULT_HASH" = "sha1"
25+
then
26+
printf "%s %s refs/heads/master\0report-status\n" \
27+
$A $B | packetize
28+
else
29+
printf "%s %s refs/heads/master\0report-status object-format=$GIT_DEFAULT_HASH\n" \
30+
$A $B | packetize
31+
fi &&
32+
printf "%s %s refs/for/master/topic1\n" \
33+
$ZERO_OID $A | packetize &&
34+
printf "%s %s refs/heads/foo\n" \
35+
$ZERO_OID $A | packetize &&
36+
printf "%s %s refs/for/next/topic\n" \
37+
$ZERO_OID $A | packetize &&
38+
printf "%s %s refs/for/master/topic2\n" \
39+
$ZERO_OID $A | packetize &&
40+
printf 0000 &&
41+
printf "" | git -C "$upstream" pack-objects --stdout
42+
} | git receive-pack "$upstream" --stateless-rpc \
43+
>out 2>&1 &&
44+
make_user_friendly_and_stable_output <out >actual &&
45+
cat >expect <<-EOF &&
46+
# pre-receive hook
47+
pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
48+
pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1
49+
pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
50+
pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
51+
pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2
52+
# proc-receive hook
53+
proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1
54+
proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
55+
proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2
56+
proc-receive> ok refs/for/master/topic1
57+
proc-receive> option fall-through
58+
proc-receive> ok refs/for/master/topic2
59+
proc-receive> option refname refs/for/changes/23/123/1
60+
proc-receive> option new-oid <COMMIT-A>
61+
proc-receive> ok refs/for/master/topic2
62+
proc-receive> option refname refs/for/changes/24/124/2
63+
proc-receive> option old-oid <COMMIT-B>
64+
proc-receive> option new-oid <COMMIT-A>
65+
proc-receive> option forced-update
66+
proc-receive> ng refs/for/next/topic target branch not exist
67+
000eunpack ok
68+
0019ok refs/heads/master
69+
001eok refs/for/master/topic1
70+
0016ok refs/heads/foo
71+
0033ng refs/for/next/topic target branch not exist
72+
001eok refs/for/master/topic2
73+
0000# post-receive hook
74+
post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
75+
post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1
76+
post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
77+
post-receive< <ZERO-OID> <COMMIT-A> refs/for/changes/23/123/1
78+
post-receive< <COMMIT-B> <COMMIT-A> refs/for/changes/24/124/2
79+
EOF
80+
test_cmp expect actual &&
81+
82+
git -C "$upstream" show-ref >out &&
83+
make_user_friendly_and_stable_output <out >actual &&
84+
cat >expect <<-EOF &&
85+
<COMMIT-A> refs/for/master/topic1
86+
<COMMIT-A> refs/heads/foo
87+
<COMMIT-B> refs/heads/master
88+
EOF
89+
test_cmp expect actual
90+
'

0 commit comments

Comments
 (0)