@matheusbsbs / matheus-bagliano / commits / 63518a574a
Fork 0

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 <gitster@pobox.com> Suggested-by: Jeff King <peff@peff.net> Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jiang Xin committed Aug 27, 2020 at 11:45 UTC 63518a574a3a4b8c6f152b29cb1b6d1f66f6e6c7
14 files changed +472 -100
builtin/receive-pack.c
+52 -2
@@ -56,6 +56,7 @@ static int advertise_push_options;
56 static int unpack_limit = 100;
57 static off_t max_input_size;
58 static int report_status;
59 +static int report_status_v2;
60 static int use_sideband;
61 static int use_atomic;
62 static int use_push_options;
@@ -239,7 +240,7 @@ static void show_ref(const char *path, const struct object_id *oid)
240 struct strbuf cap = STRBUF_INIT;
241
242 strbuf_addstr(&cap,
242 - "report-status delete-refs side-band-64k quiet");
243 + "report-status report-status-v2 delete-refs side-band-64k quiet");
244 if (advertise_atomic_push)
245 strbuf_addstr(&cap, " atomic");
246 if (prefer_ofs_delta)
@@ -1937,6 +1938,8 @@ static struct command *read_head_info(struct packet_reader *reader,
1938 const char *feature_list = reader->line + linelen + 1;
1939 if (parse_feature_request(feature_list, "report-status"))
1940 report_status = 1;
1941 + if (parse_feature_request(feature_list, "report-status-v2"))
1942 + report_status_v2 = 1;
1943 if (parse_feature_request(feature_list, "side-band-64k"))
1944 use_sideband = LARGE_PACKET_MAX;
1945 if (parse_feature_request(feature_list, "quiet"))
@@ -2248,6 +2251,51 @@ static void report(struct command *commands, const char *unpack_status)
2251 strbuf_release(&buf);
2252 }
2253
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 +
2299 static int delete_only(struct command *commands)
2300 {
2301 struct command *cmd;
@@ -2356,7 +2404,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
2404 &push_options);
2405 if (pack_lockfile)
2406 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)
2410 report(commands, unpack_status);
2411 run_receive_hook(commands, "post-receive", 1,
2412 &push_options);
builtin/send-pack.c
+19
@@ -29,10 +29,12 @@ static struct send_pack_args args;
29 static void print_helper_status(struct ref *ref)
30 {
31 struct strbuf buf = STRBUF_INIT;
32 + struct ref_push_report *report;
33
34 for (; ref; ref = ref->next) {
35 const char *msg = NULL;
36 const char *res;
37 + int count = 0;
38
39 switch(ref->status) {
40 case REF_STATUS_NONE:
@@ -94,6 +96,23 @@ static void print_helper_status(struct ref *ref)
96 }
97 strbuf_addch(&buf, '\n');
98
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 + }
116 write_or_die(1, buf.buf, buf.len);
117 }
118 strbuf_release(&buf);
remote.h
+1
@@ -148,6 +148,7 @@ struct ref {
148 REF_STATUS_ATOMIC_PUSH_FAILED
149 } status;
150 char *remote_status;
151 + struct ref_push_report *report;
152 struct ref *peer_ref; /* when renaming */
153 char name[FLEX_ARRAY]; /* more */
154 };
send-pack.c
+82 -19
@@ -153,25 +153,79 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)
153 {
154 struct ref *hint;
155 int ret;
156 + struct ref_push_report *report = NULL;
157 + int new_report = 0;
158 + int once = 0;
159
160 hint = NULL;
161 ret = receive_unpack_status(reader);
162 while (1) {
163 + struct object_id old_oid, new_oid;
164 + const char *head;
165 const char *refname;
161 - char *msg;
166 + char *p;
167 if (packet_reader_read(reader) != PACKET_READ_NORMAL)
168 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);
173 ret = -1;
174 break;
175 }
176 + *p++ = '\0';
177
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;
180
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';
229 /* first try searching at our hint, falling back to all refs */
230 if (hint)
231 hint = find_ref_by_name(hint, refname);
@@ -179,22 +233,27 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)
233 hint = find_ref_by_name(refs, refname);
234 if (!hint) {
235 warning("remote reported status on unknown ref: %s",
182 - refname);
236 + refname);
237 continue;
238 }
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) {
242 warning("remote reported status on unexpected ref: %s",
187 - refname);
243 + refname);
244 continue;
245 }
190 -
191 - if (reader->line[0] == 'o' && reader->line[1] == 'k')
192 - hint->status = REF_STATUS_OK;
193 - else
246 + if (!strcmp(head, "ng")) {
247 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 + }
257 }
258 return ret;
259 }
@@ -369,7 +428,9 @@ int send_pack(struct send_pack_args *args,
428 struct packet_reader reader;
429
430 /* 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"))
434 status_report = 1;
435 if (server_supports("delete-refs"))
436 allow_deleting_refs = 1;
@@ -418,8 +479,10 @@ int send_pack(struct send_pack_args *args,
479
480 use_push_options = push_options_supported && args->push_options;
481
421 - if (status_report)
482 + if (status_report == 1)
483 strbuf_addstr(&cap_buf, " report-status");
484 + else if (status_report == 2)
485 + strbuf_addstr(&cap_buf, " report-status-v2");
486 if (use_sideband)
487 strbuf_addstr(&cap_buf, " side-band-64k");
488 if (quiet_supported && (args->quiet || !args->progress))
t/t5411-proc-receive-hook.sh
+9
@@ -78,6 +78,15 @@ run_proc_receive_hook_test() {
78 # Initialize the upstream repository and local workbench.
79 setup_upstream_and_workbench
80
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 +
90 # Run test cases for 'proc-receive' hook on local file protocol.
91 run_proc_receive_hook_test local
92
t/t5411/once-0010-report-status-v1.sh new
+90
@@ -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 +'
t/t5411/test-0032-report-with-options.sh
+7 -7
@@ -56,7 +56,7 @@ test_expect_success "proc-receive: report option refname ($PROTOCOL)" '
56 remote: # post-receive hook
57 remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
58 To <URL/of/upstream.git>
59 - * [new reference] HEAD -> refs/for/master/topic
59 + * [new reference] HEAD -> refs/pull/123/head
60 EOF
61 test_cmp expect actual
62 '
@@ -89,7 +89,7 @@ test_expect_success "proc-receive: report option refname and forced-update ($PRO
89 remote: # post-receive hook
90 remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
91 To <URL/of/upstream.git>
92 - * [new reference] HEAD -> refs/for/master/topic
92 + * [new reference] HEAD -> refs/pull/123/head
93 EOF
94 test_cmp expect actual
95 '
@@ -123,7 +123,7 @@ test_expect_success "proc-receive: report option refname and old-oid ($PROTOCOL)
123 remote: # post-receive hook
124 remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head
125 To <URL/of/upstream.git>
126 - * [new reference] HEAD -> refs/for/master/topic
126 + <OID-B>..<OID-A> HEAD -> refs/pull/123/head
127 EOF
128 test_cmp expect actual
129 '
@@ -155,7 +155,7 @@ test_expect_success "proc-receive: report option old-oid ($PROTOCOL)" '
155 remote: # post-receive hook
156 remote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic
157 To <URL/of/upstream.git>
158 - * [new reference] HEAD -> refs/for/master/topic
158 + <OID-B>..<OID-A> HEAD -> refs/for/master/topic
159 EOF
160 test_cmp expect actual
161 '
@@ -189,7 +189,7 @@ test_expect_success "proc-receive: report option old-oid and new-oid ($PROTOCOL)
189 remote: # post-receive hook
190 remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
191 To <URL/of/upstream.git>
192 - * [new reference] HEAD -> refs/for/master/topic
192 + <OID-A>..<OID-B> HEAD -> refs/for/master/topic
193 EOF
194 test_cmp expect actual
195 '
@@ -241,9 +241,9 @@ test_expect_success "proc-receive: report with multiple rewrites ($PROTOCOL)" '
241 remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
242 remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head
243 To <URL/of/upstream.git>
244 - * [new reference] HEAD -> refs/for/next/topic
244 + * [new reference] HEAD -> refs/pull/123/head
245 * [new reference] HEAD -> refs/for/a/b/c/topic
246 - * [new reference] HEAD -> refs/for/master/topic
246 + + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)
247 EOF
248 test_cmp expect actual &&
249
t/t5411/test-0033-report-with-options--porcelain.sh
+7 -7
@@ -57,7 +57,7 @@ test_expect_success "proc-receive: report option refname ($PROTOCOL/porcelain)"
57 remote: # post-receive hook
58 remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
59 To <URL/of/upstream.git>
60 - * HEAD:refs/for/master/topic [new reference]
60 + * HEAD:refs/pull/123/head [new reference]
61 Done
62 EOF
63 test_cmp expect actual
@@ -92,7 +92,7 @@ test_expect_success "proc-receive: report option refname and forced-update ($PRO
92 remote: # post-receive hook
93 remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
94 To <URL/of/upstream.git>
95 - * HEAD:refs/for/master/topic [new reference]
95 + * HEAD:refs/pull/123/head [new reference]
96 Done
97 EOF
98 test_cmp expect actual
@@ -127,7 +127,7 @@ test_expect_success "proc-receive: report option refname and old-oid ($PROTOCOL/
127 remote: # post-receive hook
128 remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head
129 To <URL/of/upstream.git>
130 - * HEAD:refs/for/master/topic [new reference]
130 + HEAD:refs/pull/123/head <OID-B>..<OID-A>
131 Done
132 EOF
133 test_cmp expect actual
@@ -160,7 +160,7 @@ test_expect_success "proc-receive: report option old-oid ($PROTOCOL/porcelain)"
160 remote: # post-receive hook
161 remote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic
162 To <URL/of/upstream.git>
163 - * HEAD:refs/for/master/topic [new reference]
163 + HEAD:refs/for/master/topic <OID-B>..<OID-A>
164 Done
165 EOF
166 test_cmp expect actual
@@ -195,7 +195,7 @@ test_expect_success "proc-receive: report option old-oid and new-oid ($PROTOCOL/
195 remote: # post-receive hook
196 remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
197 To <URL/of/upstream.git>
198 - * HEAD:refs/for/master/topic [new reference]
198 + HEAD:refs/for/master/topic <OID-A>..<OID-B>
199 Done
200 EOF
201 test_cmp expect actual
@@ -249,9 +249,9 @@ test_expect_success "proc-receive: report with multiple rewrites ($PROTOCOL/porc
249 remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
250 remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head
251 To <URL/of/upstream.git>
252 - * HEAD:refs/for/next/topic [new reference]
252 + * HEAD:refs/pull/123/head [new reference]
253 * HEAD:refs/for/a/b/c/topic [new reference]
254 - * HEAD:refs/for/master/topic [new reference]
254 + + HEAD:refs/pull/124/head <OID-B>...<OID-A> (forced update)
255 Done
256 EOF
257 test_cmp expect actual &&
t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh
+8 -3
@@ -45,7 +45,9 @@ test_expect_success "proc-receive: multiple rewrite for one ref, no refname for
45 remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1
46 remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1
47 To <URL/of/upstream.git>
48 - * [new reference] HEAD -> refs/for/master/topic
48 + <OID-A>..<OID-B> HEAD -> refs/for/master/topic
49 + * [new reference] HEAD -> refs/changes/24/124/1
50 + <OID-A>..<OID-B> HEAD -> refs/changes/25/125/1
51 EOF
52 test_cmp expect actual &&
53 git -C "$upstream" show-ref >out &&
@@ -105,7 +107,9 @@ test_expect_success "proc-receive: multiple rewrites for one ref, no refname for
107 remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
108 remote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1
109 To <URL/of/upstream.git>
108 - * [new reference] HEAD -> refs/for/master/topic
110 + * [new reference] HEAD -> refs/changes/24/124/1
111 + <OID-A>..<OID-B> HEAD -> refs/for/master/topic
112 + + <OID-B>...<OID-A> HEAD -> refs/changes/25/125/1 (forced update)
113 EOF
114 test_cmp expect actual &&
115 git -C "$upstream" show-ref >out &&
@@ -152,7 +156,8 @@ test_expect_success "proc-receive: multiple rewrites for one ref ($PROTOCOL)" '
156 remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1
157 remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2
158 To <URL/of/upstream.git>
155 - * [new reference] HEAD -> refs/for/master/topic
159 + * [new reference] HEAD -> refs/changes/23/123/1
160 + <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2
161 EOF
162 test_cmp expect actual &&
163 git -C "$upstream" show-ref >out &&
t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh
+8 -3
@@ -45,7 +45,9 @@ test_expect_success "proc-receive: multiple rewrite for one ref, no refname for
45 remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1
46 remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1
47 To <URL/of/upstream.git>
48 - * HEAD:refs/for/master/topic [new reference]
48 + HEAD:refs/for/master/topic <OID-A>..<OID-B>
49 + * HEAD:refs/changes/24/124/1 [new reference]
50 + HEAD:refs/changes/25/125/1 <OID-A>..<OID-B>
51 Done
52 EOF
53 test_cmp expect actual &&
@@ -106,7 +108,9 @@ test_expect_success "proc-receive: multiple rewrites for one ref, no refname for
108 remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
109 remote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1
110 To <URL/of/upstream.git>
109 - * HEAD:refs/for/master/topic [new reference]
111 + * HEAD:refs/changes/24/124/1 [new reference]
112 + HEAD:refs/for/master/topic <OID-A>..<OID-B>
113 + + HEAD:refs/changes/25/125/1 <OID-B>...<OID-A> (forced update)
114 Done
115 EOF
116 test_cmp expect actual &&
@@ -154,7 +158,8 @@ test_expect_success "proc-receive: multiple rewrites for one ref ($PROTOCOL/porc
158 remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1
159 remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2
160 To <URL/of/upstream.git>
157 - * HEAD:refs/for/master/topic [new reference]
161 + * HEAD:refs/changes/23/123/1 [new reference]
162 + HEAD:refs/changes/24/124/2 <OID-A>..<OID-B>
163 Done
164 EOF
165 test_cmp expect actual &&
t/t5411/test-0038-report-mixed-refs.sh
+1 -1
@@ -60,7 +60,7 @@ test_expect_success "proc-receive: report update of mixed refs ($PROTOCOL)" '
60 * [new branch] HEAD -> baz
61 * [new reference] HEAD -> refs/for/next/topic2
62 * [new branch] HEAD -> foo
63 - * [new reference] HEAD -> refs/for/master/topic
63 + <OID-A>..<OID-B> HEAD -> refs/for/master/topic
64 ! [remote rejected] HEAD -> refs/for/next/topic1 (fail to call Web API)
65 ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status)
66 EOF
t/t5411/test-0039-report-mixed-refs--porcelain.sh
+1 -1
@@ -60,7 +60,7 @@ test_expect_success "proc-receive: report update of mixed refs ($PROTOCOL/porcel
60 * HEAD:refs/heads/baz [new branch]
61 * HEAD:refs/for/next/topic2 [new reference]
62 * HEAD:refs/heads/foo [new branch]
63 - * HEAD:refs/for/master/topic [new reference]
63 + HEAD:refs/for/master/topic <OID-A>..<OID-B>
64 ! HEAD:refs/for/next/topic1 [remote rejected] (fail to call Web API)
65 ! HEAD:refs/for/next/topic3 [remote rejected] (proc-receive failed to report status)
66 Done
transport-helper.c
+99 -29
@@ -719,13 +719,61 @@ static int fetch(struct transport *transport,
719 return -1;
720 }
721
722 +struct push_update_ref_state {
723 + struct ref *hint;
724 + struct ref_push_report *report;
725 + int new_report;
726 +};
727 +
728 static int push_update_ref_status(struct strbuf *buf,
723 - struct ref **ref,
729 + struct push_update_ref_state *state,
730 struct ref *remote_refs)
731 {
732 char *refname, *msg;
733 int status, forced = 0;
734
735 + if (starts_with(buf->buf, "option ")) {
736 + struct object_id old_oid, new_oid;
737 + const char *key, *val;
738 + char *p;
739 +
740 + if (!state->hint || !(state->report || state->new_report))
741 + die(_("'option' without a matching 'ok/error' directive"));
742 + if (state->new_report) {
743 + if (!state->hint->report) {
744 + state->hint->report = xcalloc(1, sizeof(struct ref_push_report));
745 + state->report = state->hint->report;
746 + } else {
747 + state->report = state->hint->report;
748 + while (state->report->next)
749 + state->report = state->report->next;
750 + state->report->next = xcalloc(1, sizeof(struct ref_push_report));
751 + state->report = state->report->next;
752 + }
753 + state->new_report = 0;
754 + }
755 + key = buf->buf + 7;
756 + p = strchr(key, ' ');
757 + if (p)
758 + *p++ = '\0';
759 + val = p;
760 + if (!strcmp(key, "refname"))
761 + state->report->ref_name = xstrdup_or_null(val);
762 + else if (!strcmp(key, "old-oid") && val &&
763 + !parse_oid_hex(val, &old_oid, &val))
764 + state->report->old_oid = oiddup(&old_oid);
765 + else if (!strcmp(key, "new-oid") && val &&
766 + !parse_oid_hex(val, &new_oid, &val))
767 + state->report->new_oid = oiddup(&new_oid);
768 + else if (!strcmp(key, "forced-update"))
769 + state->report->forced_update = 1;
770 + /* Not update remote namespace again. */
771 + return 1;
772 + }
773 +
774 + state->report = NULL;
775 + state->new_report = 0;
776 +
777 if (starts_with(buf->buf, "ok ")) {
778 status = REF_STATUS_OK;
779 refname = buf->buf + 3;
@@ -781,16 +829,16 @@ static int push_update_ref_status(struct strbuf *buf,
829 }
830 }
831
784 - if (*ref)
785 - *ref = find_ref_by_name(*ref, refname);
786 - if (!*ref)
787 - *ref = find_ref_by_name(remote_refs, refname);
788 - if (!*ref) {
832 + if (state->hint)
833 + state->hint = find_ref_by_name(state->hint, refname);
834 + if (!state->hint)
835 + state->hint = find_ref_by_name(remote_refs, refname);
836 + if (!state->hint) {
837 warning(_("helper reported unexpected status of %s"), refname);
838 return 1;
839 }
840
793 - if ((*ref)->status != REF_STATUS_NONE) {
841 + if (state->hint->status != REF_STATUS_NONE) {
842 /*
843 * Earlier, the ref was marked not to be pushed, so ignore the ref
844 * status reported by the remote helper if the latter is 'no match'.
@@ -799,9 +847,11 @@ static int push_update_ref_status(struct strbuf *buf,
847 return 1;
848 }
849
802 - (*ref)->status = status;
803 - (*ref)->forced_update |= forced;
804 - (*ref)->remote_status = msg;
850 + if (status == REF_STATUS_OK)
851 + state->new_report = 1;
852 + state->hint->status = status;
853 + state->hint->forced_update |= forced;
854 + state->hint->remote_status = msg;
855 return !(status == REF_STATUS_OK);
856 }
857
@@ -809,37 +859,57 @@ static int push_update_refs_status(struct helper_data *data,
859 struct ref *remote_refs,
860 int flags)
861 {
862 + struct ref *ref;
863 + struct ref_push_report *report;
864 struct strbuf buf = STRBUF_INIT;
813 - struct ref *ref = remote_refs;
814 - int ret = 0;
865 + struct push_update_ref_state state = { remote_refs, NULL, 0 };
866
867 for (;;) {
817 - char *private;
818 -
868 if (recvline(data, &buf)) {
820 - ret = 1;
821 - break;
869 + strbuf_release(&buf);
870 + return 1;
871 }
823 -
872 if (!buf.len)
873 break;
874 + push_update_ref_status(&buf, &state, remote_refs);
875 + }
876 + strbuf_release(&buf);
877
827 - if (push_update_ref_status(&buf, &ref, remote_refs))
828 - continue;
878 + if (flags & TRANSPORT_PUSH_DRY_RUN || !data->rs.nr || data->no_private_update)
879 + return 0;
880
830 - if (flags & TRANSPORT_PUSH_DRY_RUN || !data->rs.nr || data->no_private_update)
831 - continue;
881 + /* propagate back the update to the remote namespace */
882 + for (ref = remote_refs; ref; ref = ref->next) {
883 + char *private;
884
833 - /* propagate back the update to the remote namespace */
834 - private = apply_refspecs(&data->rs, ref->name);
835 - if (!private)
885 + if (ref->status != REF_STATUS_OK)
886 continue;
837 - update_ref("update by helper", private, &ref->new_oid, NULL,
838 - 0, 0);
839 - free(private);
887 +
888 + if (!ref->report) {
889 + private = apply_refspecs(&data->rs, ref->name);
890 + if (!private)
891 + continue;
892 + update_ref("update by helper", private, &(ref->new_oid),
893 + NULL, 0, 0);
894 + free(private);
895 + } else {
896 + for (report = ref->report; report; report = report->next) {
897 + private = apply_refspecs(&data->rs,
898 + report->ref_name
899 + ? report->ref_name
900 + : ref->name);
901 + if (!private)
902 + continue;
903 + update_ref("update by helper", private,
904 + report->new_oid
905 + ? report->new_oid
906 + : &(ref->new_oid),
907 + NULL, 0, 0);
908 + free(private);
909 + }
910 + }
911 }
841 - strbuf_release(&buf);
842 - return ret;
912 + return 0;
913 }
914
915 static void set_common_push_options(struct transport *transport,
transport.c
+88 -28
@@ -461,13 +461,21 @@ void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int v
461
462 static void print_ref_status(char flag, const char *summary,
463 struct ref *to, struct ref *from, const char *msg,
464 + struct ref_push_report *report,
465 int porcelain, int summary_width)
466 {
467 + const char *to_name;
468 +
469 + if (report && report->ref_name)
470 + to_name = report->ref_name;
471 + else
472 + to_name = to->name;
473 +
474 if (porcelain) {
475 if (from)
468 - fprintf(stdout, "%c\t%s:%s\t", flag, from->name, to->name);
476 + fprintf(stdout, "%c\t%s:%s\t", flag, from->name, to_name);
477 else
470 - fprintf(stdout, "%c\t:%s\t", flag, to->name);
478 + fprintf(stdout, "%c\t:%s\t", flag, to_name);
479 if (msg)
480 fprintf(stdout, "%s (%s)\n", summary, msg);
481 else
@@ -481,9 +489,11 @@ static void print_ref_status(char flag, const char *summary,
489 fprintf(stderr, " %s%c %-*s%s ", red, flag, summary_width,
490 summary, reset);
491 if (from)
484 - fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name));
492 + fprintf(stderr, "%s -> %s",
493 + prettify_refname(from->name),
494 + prettify_refname(to_name));
495 else
486 - fputs(prettify_refname(to->name), stderr);
496 + fputs(prettify_refname(to_name), stderr);
497 if (msg) {
498 fputs(" (", stderr);
499 fputs(msg, stderr);
@@ -493,27 +503,52 @@ static void print_ref_status(char flag, const char *summary,
503 }
504 }
505
496 -static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width)
506 +static void print_ok_ref_status(struct ref *ref,
507 + struct ref_push_report *report,
508 + int porcelain, int summary_width)
509 {
510 + struct object_id *old_oid;
511 + struct object_id *new_oid;
512 + const char *ref_name;
513 + int forced_update;
514 +
515 + if (report && report->old_oid)
516 + old_oid = report->old_oid;
517 + else
518 + old_oid = &ref->old_oid;
519 + if (report && report->new_oid)
520 + new_oid = report->new_oid;
521 + else
522 + new_oid = &ref->new_oid;
523 + if (report && report->forced_update)
524 + forced_update = report->forced_update;
525 + else
526 + forced_update = ref->forced_update;
527 + if (report && report->ref_name)
528 + ref_name = report->ref_name;
529 + else
530 + ref_name = ref->name;
531 +
532 if (ref->deletion)
533 print_ref_status('-', "[deleted]", ref, NULL, NULL,
500 - porcelain, summary_width);
501 - else if (is_null_oid(&ref->old_oid))
534 + report, porcelain, summary_width);
535 + else if (is_null_oid(old_oid))
536 print_ref_status('*',
503 - (starts_with(ref->name, "refs/tags/")
537 + (starts_with(ref_name, "refs/tags/")
538 ? "[new tag]"
505 - : (starts_with(ref->name, "refs/heads/")
539 + : (starts_with(ref_name, "refs/heads/")
540 ? "[new branch]"
541 : "[new reference]")),
508 - ref, ref->peer_ref, NULL, porcelain, summary_width);
542 + ref, ref->peer_ref, NULL,
543 + report, porcelain, summary_width);
544 else {
545 struct strbuf quickref = STRBUF_INIT;
546 char type;
547 const char *msg;
548
514 - strbuf_add_unique_abbrev(&quickref, &ref->old_oid,
549 + strbuf_add_unique_abbrev(&quickref, old_oid,
550 DEFAULT_ABBREV);
516 - if (ref->forced_update) {
551 + if (forced_update) {
552 strbuf_addstr(&quickref, "...");
553 type = '+';
554 msg = "forced update";
@@ -522,16 +557,17 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt
557 type = ' ';
558 msg = NULL;
559 }
525 - strbuf_add_unique_abbrev(&quickref, &ref->new_oid,
560 + strbuf_add_unique_abbrev(&quickref, new_oid,
561 DEFAULT_ABBREV);
562
563 print_ref_status(type, quickref.buf, ref, ref->peer_ref, msg,
529 - porcelain, summary_width);
564 + report, porcelain, summary_width);
565 strbuf_release(&quickref);
566 }
567 }
568
534 -static int print_one_push_status(struct ref *ref, const char *dest, int count,
569 +static int print_one_push_report(struct ref *ref, const char *dest, int count,
570 + struct ref_push_report *report,
571 int porcelain, int summary_width)
572 {
573 if (!count) {
@@ -543,65 +579,89 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count,
579 switch(ref->status) {
580 case REF_STATUS_NONE:
581 print_ref_status('X', "[no match]", ref, NULL, NULL,
546 - porcelain, summary_width);
582 + report, porcelain, summary_width);
583 break;
584 case REF_STATUS_REJECT_NODELETE:
585 print_ref_status('!', "[rejected]", ref, NULL,
586 "remote does not support deleting refs",
551 - porcelain, summary_width);
587 + report, porcelain, summary_width);
588 break;
589 case REF_STATUS_UPTODATE:
590 print_ref_status('=', "[up to date]", ref,
555 - ref->peer_ref, NULL, porcelain, summary_width);
591 + ref->peer_ref, NULL,
592 + report, porcelain, summary_width);
593 break;
594 case REF_STATUS_REJECT_NONFASTFORWARD:
595 print_ref_status('!', "[rejected]", ref, ref->peer_ref,
559 - "non-fast-forward", porcelain, summary_width);
596 + "non-fast-forward",
597 + report, porcelain, summary_width);
598 break;
599 case REF_STATUS_REJECT_ALREADY_EXISTS:
600 print_ref_status('!', "[rejected]", ref, ref->peer_ref,
563 - "already exists", porcelain, summary_width);
601 + "already exists",
602 + report, porcelain, summary_width);
603 break;
604 case REF_STATUS_REJECT_FETCH_FIRST:
605 print_ref_status('!', "[rejected]", ref, ref->peer_ref,
567 - "fetch first", porcelain, summary_width);
606 + "fetch first",
607 + report, porcelain, summary_width);
608 break;
609 case REF_STATUS_REJECT_NEEDS_FORCE:
610 print_ref_status('!', "[rejected]", ref, ref->peer_ref,
571 - "needs force", porcelain, summary_width);
611 + "needs force",
612 + report, porcelain, summary_width);
613 break;
614 case REF_STATUS_REJECT_STALE:
615 print_ref_status('!', "[rejected]", ref, ref->peer_ref,
575 - "stale info", porcelain, summary_width);
616 + "stale info",
617 + report, porcelain, summary_width);
618 break;
619 case REF_STATUS_REJECT_SHALLOW:
620 print_ref_status('!', "[rejected]", ref, ref->peer_ref,
621 "new shallow roots not allowed",
580 - porcelain, summary_width);
622 + report, porcelain, summary_width);
623 break;
624 case REF_STATUS_REMOTE_REJECT:
625 print_ref_status('!', "[remote rejected]", ref,
626 ref->deletion ? NULL : ref->peer_ref,
585 - ref->remote_status, porcelain, summary_width);
627 + ref->remote_status,
628 + report, porcelain, summary_width);
629 break;
630 case REF_STATUS_EXPECTING_REPORT:
631 print_ref_status('!', "[remote failure]", ref,
632 ref->deletion ? NULL : ref->peer_ref,
633 "remote failed to report status",
591 - porcelain, summary_width);
634 + report, porcelain, summary_width);
635 break;
636 case REF_STATUS_ATOMIC_PUSH_FAILED:
637 print_ref_status('!', "[rejected]", ref, ref->peer_ref,
595 - "atomic push failed", porcelain, summary_width);
638 + "atomic push failed",
639 + report, porcelain, summary_width);
640 break;
641 case REF_STATUS_OK:
598 - print_ok_ref_status(ref, porcelain, summary_width);
642 + print_ok_ref_status(ref, report, porcelain, summary_width);
643 break;
644 }
645
646 return 1;
647 }
648
649 +static int print_one_push_status(struct ref *ref, const char *dest, int count,
650 + int porcelain, int summary_width)
651 +{
652 + struct ref_push_report *report;
653 + int n = 0;
654 +
655 + if (!ref->report)
656 + return print_one_push_report(ref, dest, count,
657 + NULL, porcelain, summary_width);
658 +
659 + for (report = ref->report; report; report = report->next)
660 + print_one_push_report(ref, dest, count + n++,
661 + report, porcelain, summary_width);
662 + return n;
663 +}
664 +
665 static int measure_abbrev(const struct object_id *oid, int sofar)
666 {
667 char hex[GIT_MAX_HEXSZ + 1];