##// END OF EJS Templates
phabricator: fix a crash when submitting binaries (issue6260)...
Matt Harbison -
r44630:5d85e9dd stable
parent child Browse files
Show More
This diff has been collapsed as it changes many lines, (1909 lines changed) Show them Hide them
@@ -0,0 +1,1909 b''
1 {
2 "version": 1,
3 "interactions": [
4 {
5 "response": {
6 "status": {
7 "message": "OK",
8 "code": 200
9 },
10 "headers": {
11 "strict-transport-security": [
12 "max-age=0; includeSubdomains; preload"
13 ],
14 "server": [
15 "Apache/2.4.10 (Debian)"
16 ],
17 "referrer-policy": [
18 "no-referrer"
19 ],
20 "x-frame-options": [
21 "Deny"
22 ],
23 "expires": [
24 "Sat, 01 Jan 2000 00:00:00 GMT"
25 ],
26 "x-content-type-options": [
27 "nosniff"
28 ],
29 "cache-control": [
30 "no-store"
31 ],
32 "content-type": [
33 "application/json"
34 ],
35 "x-xss-protection": [
36 "1; mode=block"
37 ],
38 "transfer-encoding": [
39 "chunked"
40 ],
41 "date": [
42 "Sat, 25 Jan 2020 06:01:03 GMT"
43 ]
44 },
45 "body": {
46 "string": "{\"result\":{\"data\":[{\"id\":2,\"type\":\"REPO\",\"phid\":\"PHID-REPO-bvunnehri4u2isyr7bc3\",\"fields\":{\"name\":\"Mercurial\",\"vcs\":\"hg\",\"callsign\":\"HG\",\"shortName\":null,\"status\":\"active\",\"isImporting\":false,\"almanacServicePHID\":null,\"refRules\":{\"fetchRules\":[],\"trackRules\":[],\"permanentRefRules\":[]},\"spacePHID\":null,\"dateCreated\":1498761653,\"dateModified\":1500403184,\"policy\":{\"view\":\"public\",\"edit\":\"admin\",\"diffusion.push\":\"users\"}},\"attachments\":{}}],\"maps\":{},\"query\":{\"queryKey\":null},\"cursor\":{\"limit\":100,\"after\":null,\"before\":null,\"order\":null}},\"error_code\":null,\"error_info\":null}"
47 }
48 },
49 "request": {
50 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22constraints%22%3A+%7B%22callsigns%22%3A+%5B%22HG%22%5D%7D%7D",
51 "method": "POST",
52 "headers": {
53 "content-type": [
54 "application/x-www-form-urlencoded"
55 ],
56 "user-agent": [
57 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
58 ],
59 "content-length": [
60 "183"
61 ],
62 "host": [
63 "phab.mercurial-scm.org"
64 ],
65 "accept": [
66 "application/mercurial-0.1"
67 ]
68 },
69 "uri": "https://phab.mercurial-scm.org//api/diffusion.repository.search"
70 }
71 },
72 {
73 "response": {
74 "status": {
75 "message": "OK",
76 "code": 200
77 },
78 "headers": {
79 "strict-transport-security": [
80 "max-age=0; includeSubdomains; preload"
81 ],
82 "server": [
83 "Apache/2.4.10 (Debian)"
84 ],
85 "referrer-policy": [
86 "no-referrer"
87 ],
88 "x-frame-options": [
89 "Deny"
90 ],
91 "expires": [
92 "Sat, 01 Jan 2000 00:00:00 GMT"
93 ],
94 "x-content-type-options": [
95 "nosniff"
96 ],
97 "cache-control": [
98 "no-store"
99 ],
100 "content-type": [
101 "application/json"
102 ],
103 "x-xss-protection": [
104 "1; mode=block"
105 ],
106 "transfer-encoding": [
107 "chunked"
108 ],
109 "date": [
110 "Sat, 25 Jan 2020 06:01:04 GMT"
111 ]
112 },
113 "body": {
114 "string": "{\"result\":{\"upload\":true,\"filePHID\":null},\"error_code\":null,\"error_info\":null}"
115 }
116 },
117 "request": {
118 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22contentHash%22%3A+%22022a6979e6dab7aa5ae4c3e5e45f7e977112a7e63593820dbec1ec738a24f93c%22%2C+%22contentLength%22%3A+2%2C+%22name%22%3A+%22bin%22%7D",
119 "method": "POST",
120 "headers": {
121 "content-type": [
122 "application/x-www-form-urlencoded"
123 ],
124 "user-agent": [
125 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
126 ],
127 "content-length": [
128 "269"
129 ],
130 "host": [
131 "phab.mercurial-scm.org"
132 ],
133 "accept": [
134 "application/mercurial-0.1"
135 ]
136 },
137 "uri": "https://phab.mercurial-scm.org//api/file.allocate"
138 }
139 },
140 {
141 "response": {
142 "status": {
143 "message": "OK",
144 "code": 200
145 },
146 "headers": {
147 "strict-transport-security": [
148 "max-age=0; includeSubdomains; preload"
149 ],
150 "server": [
151 "Apache/2.4.10 (Debian)"
152 ],
153 "referrer-policy": [
154 "no-referrer"
155 ],
156 "x-frame-options": [
157 "Deny"
158 ],
159 "expires": [
160 "Sat, 01 Jan 2000 00:00:00 GMT"
161 ],
162 "x-content-type-options": [
163 "nosniff"
164 ],
165 "cache-control": [
166 "no-store"
167 ],
168 "content-type": [
169 "application/json"
170 ],
171 "x-xss-protection": [
172 "1; mode=block"
173 ],
174 "transfer-encoding": [
175 "chunked"
176 ],
177 "date": [
178 "Sat, 25 Jan 2020 06:01:04 GMT"
179 ]
180 },
181 "body": {
182 "string": "{\"result\":\"PHID-FILE-6icohlowpfhuvuut4kt4\",\"error_code\":null,\"error_info\":null}"
183 }
184 },
185 "request": {
186 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data_base64%22%3A+%22AGE%3D%22%2C+%22name%22%3A+%22bin%22%7D",
187 "method": "POST",
188 "headers": {
189 "content-type": [
190 "application/x-www-form-urlencoded"
191 ],
192 "user-agent": [
193 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
194 ],
195 "content-length": [
196 "183"
197 ],
198 "host": [
199 "phab.mercurial-scm.org"
200 ],
201 "accept": [
202 "application/mercurial-0.1"
203 ]
204 },
205 "uri": "https://phab.mercurial-scm.org//api/file.upload"
206 }
207 },
208 {
209 "response": {
210 "status": {
211 "message": "OK",
212 "code": 200
213 },
214 "headers": {
215 "strict-transport-security": [
216 "max-age=0; includeSubdomains; preload"
217 ],
218 "server": [
219 "Apache/2.4.10 (Debian)"
220 ],
221 "referrer-policy": [
222 "no-referrer"
223 ],
224 "x-frame-options": [
225 "Deny"
226 ],
227 "expires": [
228 "Sat, 01 Jan 2000 00:00:00 GMT"
229 ],
230 "x-content-type-options": [
231 "nosniff"
232 ],
233 "cache-control": [
234 "no-store"
235 ],
236 "content-type": [
237 "application/json"
238 ],
239 "x-xss-protection": [
240 "1; mode=block"
241 ],
242 "transfer-encoding": [
243 "chunked"
244 ],
245 "date": [
246 "Sat, 25 Jan 2020 06:01:05 GMT"
247 ]
248 },
249 "body": {
250 "string": "{\"result\":{\"diffid\":19599,\"phid\":\"PHID-DIFF-uvk7qaq6iglk4wgk2lxb\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/differential\\/diff\\/19599\\/\"},\"error_code\":null,\"error_info\":null}"
251 }
252 },
253 "request": {
254 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22bookmark%22%3A+null%2C+%22branch%22%3A+%22default%22%2C+%22changes%22%3A+%7B%22bin%22%3A+%7B%22addLines%22%3A+0%2C+%22awayPaths%22%3A+%5B%5D%2C+%22commitHash%22%3A+null%2C+%22currentPath%22%3A+%22bin%22%2C+%22delLines%22%3A+0%2C+%22fileType%22%3A+3%2C+%22hunks%22%3A+%5B%5D%2C+%22metadata%22%3A+%7B%22new%3Abinary-phid%22%3A+%22PHID-FILE-6icohlowpfhuvuut4kt4%22%2C+%22new%3Afile%3Asize%22%3A+2%7D%2C+%22newProperties%22%3A+%7B%22unix%3Afilemode%22%3A+%22100644%22%7D%2C+%22oldPath%22%3A+null%2C+%22oldProperties%22%3A+%7B%7D%2C+%22type%22%3A+1%7D%7D%2C+%22creationMethod%22%3A+%22phabsend%22%2C+%22lintStatus%22%3A+%22none%22%2C+%22repositoryPHID%22%3A+%22PHID-REPO-bvunnehri4u2isyr7bc3%22%2C+%22sourceControlBaseRevision%22%3A+%221849d7828727a28e14c589323e4f8c9a1c8d2816%22%2C+%22sourceControlPath%22%3A+%22%2F%22%2C+%22sourceControlSystem%22%3A+%22hg%22%2C+%22sourceMachine%22%3A+%22%22%2C+%22sourcePath%22%3A+%22%2F%22%2C+%22unitStatus%22%3A+%22none%22%7D",
255 "method": "POST",
256 "headers": {
257 "content-type": [
258 "application/x-www-form-urlencoded"
259 ],
260 "user-agent": [
261 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
262 ],
263 "content-length": [
264 "1081"
265 ],
266 "host": [
267 "phab.mercurial-scm.org"
268 ],
269 "accept": [
270 "application/mercurial-0.1"
271 ]
272 },
273 "uri": "https://phab.mercurial-scm.org//api/differential.creatediff"
274 }
275 },
276 {
277 "response": {
278 "status": {
279 "message": "OK",
280 "code": 200
281 },
282 "headers": {
283 "strict-transport-security": [
284 "max-age=0; includeSubdomains; preload"
285 ],
286 "server": [
287 "Apache/2.4.10 (Debian)"
288 ],
289 "referrer-policy": [
290 "no-referrer"
291 ],
292 "x-frame-options": [
293 "Deny"
294 ],
295 "expires": [
296 "Sat, 01 Jan 2000 00:00:00 GMT"
297 ],
298 "x-content-type-options": [
299 "nosniff"
300 ],
301 "cache-control": [
302 "no-store"
303 ],
304 "content-type": [
305 "application/json"
306 ],
307 "x-xss-protection": [
308 "1; mode=block"
309 ],
310 "transfer-encoding": [
311 "chunked"
312 ],
313 "date": [
314 "Sat, 25 Jan 2020 06:01:06 GMT"
315 ]
316 },
317 "body": {
318 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
319 }
320 },
321 "request": {
322 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%22branch%5C%22%3A+%5C%22default%5C%22%2C+%5C%22date%5C%22%3A+%5C%220+0%5C%22%2C+%5C%22node%5C%22%3A+%5C%22aa24a81f55de50addfce4a824eeb919d59b19683%5C%22%2C+%5C%22parent%5C%22%3A+%5C%221849d7828727a28e14c589323e4f8c9a1c8d2816%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+19599%2C+%22name%22%3A+%22hg%3Ameta%22%7D",
323 "method": "POST",
324 "headers": {
325 "content-type": [
326 "application/x-www-form-urlencoded"
327 ],
328 "user-agent": [
329 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
330 ],
331 "content-length": [
332 "482"
333 ],
334 "host": [
335 "phab.mercurial-scm.org"
336 ],
337 "accept": [
338 "application/mercurial-0.1"
339 ]
340 },
341 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty"
342 }
343 },
344 {
345 "response": {
346 "status": {
347 "message": "OK",
348 "code": 200
349 },
350 "headers": {
351 "strict-transport-security": [
352 "max-age=0; includeSubdomains; preload"
353 ],
354 "server": [
355 "Apache/2.4.10 (Debian)"
356 ],
357 "referrer-policy": [
358 "no-referrer"
359 ],
360 "x-frame-options": [
361 "Deny"
362 ],
363 "expires": [
364 "Sat, 01 Jan 2000 00:00:00 GMT"
365 ],
366 "x-content-type-options": [
367 "nosniff"
368 ],
369 "cache-control": [
370 "no-store"
371 ],
372 "content-type": [
373 "application/json"
374 ],
375 "x-xss-protection": [
376 "1; mode=block"
377 ],
378 "transfer-encoding": [
379 "chunked"
380 ],
381 "date": [
382 "Sat, 25 Jan 2020 06:01:06 GMT"
383 ]
384 },
385 "body": {
386 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
387 }
388 },
389 "request": {
390 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%22aa24a81f55de50addfce4a824eeb919d59b19683%5C%22%3A+%7B%5C%22author%5C%22%3A+%5C%22test%5C%22%2C+%5C%22authorEmail%5C%22%3A+%5C%22test%5C%22%2C+%5C%22branch%5C%22%3A+%5C%22default%5C%22%2C+%5C%22commit%5C%22%3A+%5C%22aa24a81f55de50addfce4a824eeb919d59b19683%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%221849d7828727a28e14c589323e4f8c9a1c8d2816%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+19599%2C+%22name%22%3A+%22local%3Acommits%22%7D",
391 "method": "POST",
392 "headers": {
393 "content-type": [
394 "application/x-www-form-urlencoded"
395 ],
396 "user-agent": [
397 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
398 ],
399 "content-length": [
400 "594"
401 ],
402 "host": [
403 "phab.mercurial-scm.org"
404 ],
405 "accept": [
406 "application/mercurial-0.1"
407 ]
408 },
409 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty"
410 }
411 },
412 {
413 "response": {
414 "status": {
415 "message": "OK",
416 "code": 200
417 },
418 "headers": {
419 "strict-transport-security": [
420 "max-age=0; includeSubdomains; preload"
421 ],
422 "server": [
423 "Apache/2.4.10 (Debian)"
424 ],
425 "referrer-policy": [
426 "no-referrer"
427 ],
428 "x-frame-options": [
429 "Deny"
430 ],
431 "expires": [
432 "Sat, 01 Jan 2000 00:00:00 GMT"
433 ],
434 "x-content-type-options": [
435 "nosniff"
436 ],
437 "cache-control": [
438 "no-store"
439 ],
440 "content-type": [
441 "application/json"
442 ],
443 "x-xss-protection": [
444 "1; mode=block"
445 ],
446 "transfer-encoding": [
447 "chunked"
448 ],
449 "date": [
450 "Sat, 25 Jan 2020 06:01:07 GMT"
451 ]
452 },
453 "body": {
454 "string": "{\"result\":{\"errors\":[],\"fields\":{\"title\":\"add binary\"},\"revisionIDFieldInfo\":{\"value\":null,\"validDomain\":\"https:\\/\\/phab.mercurial-scm.org\"},\"transactions\":[{\"type\":\"title\",\"value\":\"add binary\"}]},\"error_code\":null,\"error_info\":null}"
455 }
456 },
457 "request": {
458 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22corpus%22%3A+%22add+binary%22%7D",
459 "method": "POST",
460 "headers": {
461 "content-type": [
462 "application/x-www-form-urlencoded"
463 ],
464 "user-agent": [
465 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
466 ],
467 "content-length": [
468 "155"
469 ],
470 "host": [
471 "phab.mercurial-scm.org"
472 ],
473 "accept": [
474 "application/mercurial-0.1"
475 ]
476 },
477 "uri": "https://phab.mercurial-scm.org//api/differential.parsecommitmessage"
478 }
479 },
480 {
481 "response": {
482 "status": {
483 "message": "OK",
484 "code": 200
485 },
486 "headers": {
487 "strict-transport-security": [
488 "max-age=0; includeSubdomains; preload"
489 ],
490 "server": [
491 "Apache/2.4.10 (Debian)"
492 ],
493 "referrer-policy": [
494 "no-referrer"
495 ],
496 "x-frame-options": [
497 "Deny"
498 ],
499 "expires": [
500 "Sat, 01 Jan 2000 00:00:00 GMT"
501 ],
502 "x-content-type-options": [
503 "nosniff"
504 ],
505 "cache-control": [
506 "no-store"
507 ],
508 "content-type": [
509 "application/json"
510 ],
511 "x-xss-protection": [
512 "1; mode=block"
513 ],
514 "transfer-encoding": [
515 "chunked"
516 ],
517 "date": [
518 "Sat, 25 Jan 2020 06:01:08 GMT"
519 ]
520 },
521 "body": {
522 "string": "{\"result\":{\"object\":{\"id\":8007,\"phid\":\"PHID-DREV-477vjqq7vbddmjluy2cd\"},\"transactions\":[{\"phid\":\"PHID-XACT-DREV-3mzn77j7wvia63r\"},{\"phid\":\"PHID-XACT-DREV-qs7abx4c65qud72\"},{\"phid\":\"PHID-XACT-DREV-yzrxnvdwpapxruz\"},{\"phid\":\"PHID-XACT-DREV-byrci62wwrwl262\"},{\"phid\":\"PHID-XACT-DREV-3ialrkyr7elyr72\"}]},\"error_code\":null,\"error_info\":null}"
523 }
524 },
525 "request": {
526 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22transactions%22%3A+%5B%7B%22type%22%3A+%22update%22%2C+%22value%22%3A+%22PHID-DIFF-uvk7qaq6iglk4wgk2lxb%22%7D%2C+%7B%22type%22%3A+%22title%22%2C+%22value%22%3A+%22add+binary%22%7D%5D%7D",
527 "method": "POST",
528 "headers": {
529 "content-type": [
530 "application/x-www-form-urlencoded"
531 ],
532 "user-agent": [
533 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
534 ],
535 "content-length": [
536 "308"
537 ],
538 "host": [
539 "phab.mercurial-scm.org"
540 ],
541 "accept": [
542 "application/mercurial-0.1"
543 ]
544 },
545 "uri": "https://phab.mercurial-scm.org//api/differential.revision.edit"
546 }
547 },
548 {
549 "response": {
550 "status": {
551 "message": "OK",
552 "code": 200
553 },
554 "headers": {
555 "strict-transport-security": [
556 "max-age=0; includeSubdomains; preload"
557 ],
558 "server": [
559 "Apache/2.4.10 (Debian)"
560 ],
561 "referrer-policy": [
562 "no-referrer"
563 ],
564 "x-frame-options": [
565 "Deny"
566 ],
567 "expires": [
568 "Sat, 01 Jan 2000 00:00:00 GMT"
569 ],
570 "x-content-type-options": [
571 "nosniff"
572 ],
573 "cache-control": [
574 "no-store"
575 ],
576 "content-type": [
577 "application/json"
578 ],
579 "x-xss-protection": [
580 "1; mode=block"
581 ],
582 "transfer-encoding": [
583 "chunked"
584 ],
585 "date": [
586 "Sat, 25 Jan 2020 06:01:08 GMT"
587 ]
588 },
589 "body": {
590 "string": "{\"result\":{\"upload\":true,\"filePHID\":null},\"error_code\":null,\"error_info\":null}"
591 }
592 },
593 "request": {
594 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22contentHash%22%3A+%2257eb35615d47f34ec714cacdf5fd74608a5e8e102724e80b24b287c0c27b6a31%22%2C+%22contentLength%22%3A+2%2C+%22name%22%3A+%22bin%22%7D",
595 "method": "POST",
596 "headers": {
597 "content-type": [
598 "application/x-www-form-urlencoded"
599 ],
600 "user-agent": [
601 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
602 ],
603 "content-length": [
604 "269"
605 ],
606 "host": [
607 "phab.mercurial-scm.org"
608 ],
609 "accept": [
610 "application/mercurial-0.1"
611 ]
612 },
613 "uri": "https://phab.mercurial-scm.org//api/file.allocate"
614 }
615 },
616 {
617 "response": {
618 "status": {
619 "message": "OK",
620 "code": 200
621 },
622 "headers": {
623 "strict-transport-security": [
624 "max-age=0; includeSubdomains; preload"
625 ],
626 "server": [
627 "Apache/2.4.10 (Debian)"
628 ],
629 "referrer-policy": [
630 "no-referrer"
631 ],
632 "x-frame-options": [
633 "Deny"
634 ],
635 "expires": [
636 "Sat, 01 Jan 2000 00:00:00 GMT"
637 ],
638 "x-content-type-options": [
639 "nosniff"
640 ],
641 "cache-control": [
642 "no-store"
643 ],
644 "content-type": [
645 "application/json"
646 ],
647 "x-xss-protection": [
648 "1; mode=block"
649 ],
650 "transfer-encoding": [
651 "chunked"
652 ],
653 "date": [
654 "Sat, 25 Jan 2020 06:01:09 GMT"
655 ]
656 },
657 "body": {
658 "string": "{\"result\":\"PHID-FILE-smitcgkxyyu7u3qodnmj\",\"error_code\":null,\"error_info\":null}"
659 }
660 },
661 "request": {
662 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data_base64%22%3A+%22AGI%3D%22%2C+%22name%22%3A+%22bin%22%7D",
663 "method": "POST",
664 "headers": {
665 "content-type": [
666 "application/x-www-form-urlencoded"
667 ],
668 "user-agent": [
669 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
670 ],
671 "content-length": [
672 "183"
673 ],
674 "host": [
675 "phab.mercurial-scm.org"
676 ],
677 "accept": [
678 "application/mercurial-0.1"
679 ]
680 },
681 "uri": "https://phab.mercurial-scm.org//api/file.upload"
682 }
683 },
684 {
685 "response": {
686 "status": {
687 "message": "OK",
688 "code": 200
689 },
690 "headers": {
691 "strict-transport-security": [
692 "max-age=0; includeSubdomains; preload"
693 ],
694 "server": [
695 "Apache/2.4.10 (Debian)"
696 ],
697 "referrer-policy": [
698 "no-referrer"
699 ],
700 "x-frame-options": [
701 "Deny"
702 ],
703 "expires": [
704 "Sat, 01 Jan 2000 00:00:00 GMT"
705 ],
706 "x-content-type-options": [
707 "nosniff"
708 ],
709 "cache-control": [
710 "no-store"
711 ],
712 "content-type": [
713 "application/json"
714 ],
715 "x-xss-protection": [
716 "1; mode=block"
717 ],
718 "transfer-encoding": [
719 "chunked"
720 ],
721 "date": [
722 "Sat, 25 Jan 2020 06:01:10 GMT"
723 ]
724 },
725 "body": {
726 "string": "{\"result\":{\"upload\":false,\"filePHID\":\"PHID-FILE-ulotc37zwk3yic5hh72n\"},\"error_code\":null,\"error_info\":null}"
727 }
728 },
729 "request": {
730 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22contentHash%22%3A+%22022a6979e6dab7aa5ae4c3e5e45f7e977112a7e63593820dbec1ec738a24f93c%22%2C+%22contentLength%22%3A+2%2C+%22name%22%3A+%22bin%22%7D",
731 "method": "POST",
732 "headers": {
733 "content-type": [
734 "application/x-www-form-urlencoded"
735 ],
736 "user-agent": [
737 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
738 ],
739 "content-length": [
740 "269"
741 ],
742 "host": [
743 "phab.mercurial-scm.org"
744 ],
745 "accept": [
746 "application/mercurial-0.1"
747 ]
748 },
749 "uri": "https://phab.mercurial-scm.org//api/file.allocate"
750 }
751 },
752 {
753 "response": {
754 "status": {
755 "message": "OK",
756 "code": 200
757 },
758 "headers": {
759 "strict-transport-security": [
760 "max-age=0; includeSubdomains; preload"
761 ],
762 "server": [
763 "Apache/2.4.10 (Debian)"
764 ],
765 "referrer-policy": [
766 "no-referrer"
767 ],
768 "x-frame-options": [
769 "Deny"
770 ],
771 "expires": [
772 "Sat, 01 Jan 2000 00:00:00 GMT"
773 ],
774 "x-content-type-options": [
775 "nosniff"
776 ],
777 "cache-control": [
778 "no-store"
779 ],
780 "content-type": [
781 "application/json"
782 ],
783 "x-xss-protection": [
784 "1; mode=block"
785 ],
786 "transfer-encoding": [
787 "chunked"
788 ],
789 "date": [
790 "Sat, 25 Jan 2020 06:01:10 GMT"
791 ]
792 },
793 "body": {
794 "string": "{\"result\":{\"diffid\":19600,\"phid\":\"PHID-DIFF-wdmlzkf4huoiqpl4vbsr\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/differential\\/diff\\/19600\\/\"},\"error_code\":null,\"error_info\":null}"
795 }
796 },
797 "request": {
798 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22bookmark%22%3A+null%2C+%22branch%22%3A+%22default%22%2C+%22changes%22%3A+%7B%22bin%22%3A+%7B%22addLines%22%3A+0%2C+%22awayPaths%22%3A+%5B%5D%2C+%22commitHash%22%3A+null%2C+%22currentPath%22%3A+%22bin%22%2C+%22delLines%22%3A+0%2C+%22fileType%22%3A+3%2C+%22hunks%22%3A+%5B%5D%2C+%22metadata%22%3A+%7B%22new%3Abinary-phid%22%3A+%22PHID-FILE-smitcgkxyyu7u3qodnmj%22%2C+%22new%3Afile%3Asize%22%3A+2%2C+%22old%3Abinary-phid%22%3A+%22PHID-FILE-ulotc37zwk3yic5hh72n%22%2C+%22old%3Afile%3Asize%22%3A+2%7D%2C+%22newProperties%22%3A+%7B%7D%2C+%22oldPath%22%3A+%22bin%22%2C+%22oldProperties%22%3A+%7B%7D%2C+%22type%22%3A+2%7D%7D%2C+%22creationMethod%22%3A+%22phabsend%22%2C+%22lintStatus%22%3A+%22none%22%2C+%22repositoryPHID%22%3A+%22PHID-REPO-bvunnehri4u2isyr7bc3%22%2C+%22sourceControlBaseRevision%22%3A+%22aa24a81f55de50addfce4a824eeb919d59b19683%22%2C+%22sourceControlPath%22%3A+%22%2F%22%2C+%22sourceControlSystem%22%3A+%22hg%22%2C+%22sourceMachine%22%3A+%22%22%2C+%22sourcePath%22%3A+%22%2F%22%2C+%22unitStatus%22%3A+%22none%22%7D",
799 "method": "POST",
800 "headers": {
801 "content-type": [
802 "application/x-www-form-urlencoded"
803 ],
804 "user-agent": [
805 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
806 ],
807 "content-length": [
808 "1148"
809 ],
810 "host": [
811 "phab.mercurial-scm.org"
812 ],
813 "accept": [
814 "application/mercurial-0.1"
815 ]
816 },
817 "uri": "https://phab.mercurial-scm.org//api/differential.creatediff"
818 }
819 },
820 {
821 "response": {
822 "status": {
823 "message": "OK",
824 "code": 200
825 },
826 "headers": {
827 "strict-transport-security": [
828 "max-age=0; includeSubdomains; preload"
829 ],
830 "server": [
831 "Apache/2.4.10 (Debian)"
832 ],
833 "referrer-policy": [
834 "no-referrer"
835 ],
836 "x-frame-options": [
837 "Deny"
838 ],
839 "expires": [
840 "Sat, 01 Jan 2000 00:00:00 GMT"
841 ],
842 "x-content-type-options": [
843 "nosniff"
844 ],
845 "cache-control": [
846 "no-store"
847 ],
848 "content-type": [
849 "application/json"
850 ],
851 "x-xss-protection": [
852 "1; mode=block"
853 ],
854 "transfer-encoding": [
855 "chunked"
856 ],
857 "date": [
858 "Sat, 25 Jan 2020 06:01:11 GMT"
859 ]
860 },
861 "body": {
862 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
863 }
864 },
865 "request": {
866 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%22branch%5C%22%3A+%5C%22default%5C%22%2C+%5C%22date%5C%22%3A+%5C%220+0%5C%22%2C+%5C%22node%5C%22%3A+%5C%22d8d62a881b546814f012cef7c1bd0c438cc153e2%5C%22%2C+%5C%22parent%5C%22%3A+%5C%22aa24a81f55de50addfce4a824eeb919d59b19683%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+19600%2C+%22name%22%3A+%22hg%3Ameta%22%7D",
867 "method": "POST",
868 "headers": {
869 "content-type": [
870 "application/x-www-form-urlencoded"
871 ],
872 "user-agent": [
873 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
874 ],
875 "content-length": [
876 "482"
877 ],
878 "host": [
879 "phab.mercurial-scm.org"
880 ],
881 "accept": [
882 "application/mercurial-0.1"
883 ]
884 },
885 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty"
886 }
887 },
888 {
889 "response": {
890 "status": {
891 "message": "OK",
892 "code": 200
893 },
894 "headers": {
895 "strict-transport-security": [
896 "max-age=0; includeSubdomains; preload"
897 ],
898 "server": [
899 "Apache/2.4.10 (Debian)"
900 ],
901 "referrer-policy": [
902 "no-referrer"
903 ],
904 "x-frame-options": [
905 "Deny"
906 ],
907 "expires": [
908 "Sat, 01 Jan 2000 00:00:00 GMT"
909 ],
910 "x-content-type-options": [
911 "nosniff"
912 ],
913 "cache-control": [
914 "no-store"
915 ],
916 "content-type": [
917 "application/json"
918 ],
919 "x-xss-protection": [
920 "1; mode=block"
921 ],
922 "transfer-encoding": [
923 "chunked"
924 ],
925 "date": [
926 "Sat, 25 Jan 2020 06:01:12 GMT"
927 ]
928 },
929 "body": {
930 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
931 }
932 },
933 "request": {
934 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%22d8d62a881b546814f012cef7c1bd0c438cc153e2%5C%22%3A+%7B%5C%22author%5C%22%3A+%5C%22test%5C%22%2C+%5C%22authorEmail%5C%22%3A+%5C%22test%5C%22%2C+%5C%22branch%5C%22%3A+%5C%22default%5C%22%2C+%5C%22commit%5C%22%3A+%5C%22d8d62a881b546814f012cef7c1bd0c438cc153e2%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%22aa24a81f55de50addfce4a824eeb919d59b19683%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+19600%2C+%22name%22%3A+%22local%3Acommits%22%7D",
935 "method": "POST",
936 "headers": {
937 "content-type": [
938 "application/x-www-form-urlencoded"
939 ],
940 "user-agent": [
941 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
942 ],
943 "content-length": [
944 "594"
945 ],
946 "host": [
947 "phab.mercurial-scm.org"
948 ],
949 "accept": [
950 "application/mercurial-0.1"
951 ]
952 },
953 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty"
954 }
955 },
956 {
957 "response": {
958 "status": {
959 "message": "OK",
960 "code": 200
961 },
962 "headers": {
963 "strict-transport-security": [
964 "max-age=0; includeSubdomains; preload"
965 ],
966 "server": [
967 "Apache/2.4.10 (Debian)"
968 ],
969 "referrer-policy": [
970 "no-referrer"
971 ],
972 "x-frame-options": [
973 "Deny"
974 ],
975 "expires": [
976 "Sat, 01 Jan 2000 00:00:00 GMT"
977 ],
978 "x-content-type-options": [
979 "nosniff"
980 ],
981 "cache-control": [
982 "no-store"
983 ],
984 "content-type": [
985 "application/json"
986 ],
987 "x-xss-protection": [
988 "1; mode=block"
989 ],
990 "transfer-encoding": [
991 "chunked"
992 ],
993 "date": [
994 "Sat, 25 Jan 2020 06:01:12 GMT"
995 ]
996 },
997 "body": {
998 "string": "{\"result\":{\"errors\":[],\"fields\":{\"title\":\"modify binary\"},\"revisionIDFieldInfo\":{\"value\":null,\"validDomain\":\"https:\\/\\/phab.mercurial-scm.org\"},\"transactions\":[{\"type\":\"title\",\"value\":\"modify binary\"}]},\"error_code\":null,\"error_info\":null}"
999 }
1000 },
1001 "request": {
1002 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22corpus%22%3A+%22modify+binary%22%7D",
1003 "method": "POST",
1004 "headers": {
1005 "content-type": [
1006 "application/x-www-form-urlencoded"
1007 ],
1008 "user-agent": [
1009 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
1010 ],
1011 "content-length": [
1012 "158"
1013 ],
1014 "host": [
1015 "phab.mercurial-scm.org"
1016 ],
1017 "accept": [
1018 "application/mercurial-0.1"
1019 ]
1020 },
1021 "uri": "https://phab.mercurial-scm.org//api/differential.parsecommitmessage"
1022 }
1023 },
1024 {
1025 "response": {
1026 "status": {
1027 "message": "OK",
1028 "code": 200
1029 },
1030 "headers": {
1031 "strict-transport-security": [
1032 "max-age=0; includeSubdomains; preload"
1033 ],
1034 "server": [
1035 "Apache/2.4.10 (Debian)"
1036 ],
1037 "referrer-policy": [
1038 "no-referrer"
1039 ],
1040 "x-frame-options": [
1041 "Deny"
1042 ],
1043 "expires": [
1044 "Sat, 01 Jan 2000 00:00:00 GMT"
1045 ],
1046 "x-content-type-options": [
1047 "nosniff"
1048 ],
1049 "cache-control": [
1050 "no-store"
1051 ],
1052 "content-type": [
1053 "application/json"
1054 ],
1055 "x-xss-protection": [
1056 "1; mode=block"
1057 ],
1058 "transfer-encoding": [
1059 "chunked"
1060 ],
1061 "date": [
1062 "Sat, 25 Jan 2020 06:01:13 GMT"
1063 ]
1064 },
1065 "body": {
1066 "string": "{\"result\":{\"object\":{\"id\":8008,\"phid\":\"PHID-DREV-q5cr6ap6pwfczhlhjrmv\"},\"transactions\":[{\"phid\":\"PHID-XACT-DREV-54nmhipbtei7cmf\"},{\"phid\":\"PHID-XACT-DREV-zh5tb4ay2no4lr7\"},{\"phid\":\"PHID-XACT-DREV-mi6n5eteba4pb2h\"},{\"phid\":\"PHID-XACT-DREV-zmbupfrlsvzty6c\"},{\"phid\":\"PHID-XACT-DREV-h2mxx7gkkriw5b5\"},{\"phid\":\"PHID-XACT-DREV-6zpi3cnarzr5f6i\"}]},\"error_code\":null,\"error_info\":null}"
1067 }
1068 },
1069 "request": {
1070 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22transactions%22%3A+%5B%7B%22type%22%3A+%22update%22%2C+%22value%22%3A+%22PHID-DIFF-wdmlzkf4huoiqpl4vbsr%22%7D%2C+%7B%22type%22%3A+%22parents.set%22%2C+%22value%22%3A+%5B%22PHID-DREV-477vjqq7vbddmjluy2cd%22%5D%7D%2C+%7B%22type%22%3A+%22title%22%2C+%22value%22%3A+%22modify+binary%22%7D%5D%7D",
1071 "method": "POST",
1072 "headers": {
1073 "content-type": [
1074 "application/x-www-form-urlencoded"
1075 ],
1076 "user-agent": [
1077 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
1078 ],
1079 "content-length": [
1080 "413"
1081 ],
1082 "host": [
1083 "phab.mercurial-scm.org"
1084 ],
1085 "accept": [
1086 "application/mercurial-0.1"
1087 ]
1088 },
1089 "uri": "https://phab.mercurial-scm.org//api/differential.revision.edit"
1090 }
1091 },
1092 {
1093 "response": {
1094 "status": {
1095 "message": "OK",
1096 "code": 200
1097 },
1098 "headers": {
1099 "strict-transport-security": [
1100 "max-age=0; includeSubdomains; preload"
1101 ],
1102 "server": [
1103 "Apache/2.4.10 (Debian)"
1104 ],
1105 "referrer-policy": [
1106 "no-referrer"
1107 ],
1108 "x-frame-options": [
1109 "Deny"
1110 ],
1111 "expires": [
1112 "Sat, 01 Jan 2000 00:00:00 GMT"
1113 ],
1114 "x-content-type-options": [
1115 "nosniff"
1116 ],
1117 "cache-control": [
1118 "no-store"
1119 ],
1120 "content-type": [
1121 "application/json"
1122 ],
1123 "x-xss-protection": [
1124 "1; mode=block"
1125 ],
1126 "transfer-encoding": [
1127 "chunked"
1128 ],
1129 "date": [
1130 "Sat, 25 Jan 2020 06:01:14 GMT"
1131 ]
1132 },
1133 "body": {
1134 "string": "{\"result\":{\"diffid\":19601,\"phid\":\"PHID-DIFF-burakyhnzac52zcbjn2l\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/differential\\/diff\\/19601\\/\"},\"error_code\":null,\"error_info\":null}"
1135 }
1136 },
1137 "request": {
1138 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22bookmark%22%3A+null%2C+%22branch%22%3A+%22default%22%2C+%22changes%22%3A+%7B%22bin%22%3A+%7B%22addLines%22%3A+0%2C+%22awayPaths%22%3A+%5B%5D%2C+%22commitHash%22%3A+null%2C+%22currentPath%22%3A+%22bin%22%2C+%22delLines%22%3A+0%2C+%22fileType%22%3A+1%2C+%22hunks%22%3A+%5B%5D%2C+%22metadata%22%3A+%7B%7D%2C+%22newProperties%22%3A+%7B%7D%2C+%22oldPath%22%3A+%22bin%22%2C+%22oldProperties%22%3A+%7B%22unix%3Afilemode%22%3A+%22100644%22%7D%2C+%22type%22%3A+3%7D%7D%2C+%22creationMethod%22%3A+%22phabsend%22%2C+%22lintStatus%22%3A+%22none%22%2C+%22repositoryPHID%22%3A+%22PHID-REPO-bvunnehri4u2isyr7bc3%22%2C+%22sourceControlBaseRevision%22%3A+%22d8d62a881b546814f012cef7c1bd0c438cc153e2%22%2C+%22sourceControlPath%22%3A+%22%2F%22%2C+%22sourceControlSystem%22%3A+%22hg%22%2C+%22sourceMachine%22%3A+%22%22%2C+%22sourcePath%22%3A+%22%2F%22%2C+%22unitStatus%22%3A+%22none%22%7D",
1139 "method": "POST",
1140 "headers": {
1141 "content-type": [
1142 "application/x-www-form-urlencoded"
1143 ],
1144 "user-agent": [
1145 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
1146 ],
1147 "content-length": [
1148 "991"
1149 ],
1150 "host": [
1151 "phab.mercurial-scm.org"
1152 ],
1153 "accept": [
1154 "application/mercurial-0.1"
1155 ]
1156 },
1157 "uri": "https://phab.mercurial-scm.org//api/differential.creatediff"
1158 }
1159 },
1160 {
1161 "response": {
1162 "status": {
1163 "message": "OK",
1164 "code": 200
1165 },
1166 "headers": {
1167 "strict-transport-security": [
1168 "max-age=0; includeSubdomains; preload"
1169 ],
1170 "server": [
1171 "Apache/2.4.10 (Debian)"
1172 ],
1173 "referrer-policy": [
1174 "no-referrer"
1175 ],
1176 "x-frame-options": [
1177 "Deny"
1178 ],
1179 "expires": [
1180 "Sat, 01 Jan 2000 00:00:00 GMT"
1181 ],
1182 "x-content-type-options": [
1183 "nosniff"
1184 ],
1185 "cache-control": [
1186 "no-store"
1187 ],
1188 "content-type": [
1189 "application/json"
1190 ],
1191 "x-xss-protection": [
1192 "1; mode=block"
1193 ],
1194 "transfer-encoding": [
1195 "chunked"
1196 ],
1197 "date": [
1198 "Sat, 25 Jan 2020 06:01:14 GMT"
1199 ]
1200 },
1201 "body": {
1202 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
1203 }
1204 },
1205 "request": {
1206 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%22branch%5C%22%3A+%5C%22default%5C%22%2C+%5C%22date%5C%22%3A+%5C%220+0%5C%22%2C+%5C%22node%5C%22%3A+%5C%22af55645b2e292f3168e993e74356af15dee0abdb%5C%22%2C+%5C%22parent%5C%22%3A+%5C%22d8d62a881b546814f012cef7c1bd0c438cc153e2%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+19601%2C+%22name%22%3A+%22hg%3Ameta%22%7D",
1207 "method": "POST",
1208 "headers": {
1209 "content-type": [
1210 "application/x-www-form-urlencoded"
1211 ],
1212 "user-agent": [
1213 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
1214 ],
1215 "content-length": [
1216 "482"
1217 ],
1218 "host": [
1219 "phab.mercurial-scm.org"
1220 ],
1221 "accept": [
1222 "application/mercurial-0.1"
1223 ]
1224 },
1225 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty"
1226 }
1227 },
1228 {
1229 "response": {
1230 "status": {
1231 "message": "OK",
1232 "code": 200
1233 },
1234 "headers": {
1235 "strict-transport-security": [
1236 "max-age=0; includeSubdomains; preload"
1237 ],
1238 "server": [
1239 "Apache/2.4.10 (Debian)"
1240 ],
1241 "referrer-policy": [
1242 "no-referrer"
1243 ],
1244 "x-frame-options": [
1245 "Deny"
1246 ],
1247 "expires": [
1248 "Sat, 01 Jan 2000 00:00:00 GMT"
1249 ],
1250 "x-content-type-options": [
1251 "nosniff"
1252 ],
1253 "cache-control": [
1254 "no-store"
1255 ],
1256 "content-type": [
1257 "application/json"
1258 ],
1259 "x-xss-protection": [
1260 "1; mode=block"
1261 ],
1262 "transfer-encoding": [
1263 "chunked"
1264 ],
1265 "date": [
1266 "Sat, 25 Jan 2020 06:01:15 GMT"
1267 ]
1268 },
1269 "body": {
1270 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
1271 }
1272 },
1273 "request": {
1274 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%22af55645b2e292f3168e993e74356af15dee0abdb%5C%22%3A+%7B%5C%22author%5C%22%3A+%5C%22test%5C%22%2C+%5C%22authorEmail%5C%22%3A+%5C%22test%5C%22%2C+%5C%22branch%5C%22%3A+%5C%22default%5C%22%2C+%5C%22commit%5C%22%3A+%5C%22af55645b2e292f3168e993e74356af15dee0abdb%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%22d8d62a881b546814f012cef7c1bd0c438cc153e2%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+19601%2C+%22name%22%3A+%22local%3Acommits%22%7D",
1275 "method": "POST",
1276 "headers": {
1277 "content-type": [
1278 "application/x-www-form-urlencoded"
1279 ],
1280 "user-agent": [
1281 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
1282 ],
1283 "content-length": [
1284 "594"
1285 ],
1286 "host": [
1287 "phab.mercurial-scm.org"
1288 ],
1289 "accept": [
1290 "application/mercurial-0.1"
1291 ]
1292 },
1293 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty"
1294 }
1295 },
1296 {
1297 "response": {
1298 "status": {
1299 "message": "OK",
1300 "code": 200
1301 },
1302 "headers": {
1303 "strict-transport-security": [
1304 "max-age=0; includeSubdomains; preload"
1305 ],
1306 "server": [
1307 "Apache/2.4.10 (Debian)"
1308 ],
1309 "referrer-policy": [
1310 "no-referrer"
1311 ],
1312 "x-frame-options": [
1313 "Deny"
1314 ],
1315 "expires": [
1316 "Sat, 01 Jan 2000 00:00:00 GMT"
1317 ],
1318 "x-content-type-options": [
1319 "nosniff"
1320 ],
1321 "cache-control": [
1322 "no-store"
1323 ],
1324 "content-type": [
1325 "application/json"
1326 ],
1327 "x-xss-protection": [
1328 "1; mode=block"
1329 ],
1330 "transfer-encoding": [
1331 "chunked"
1332 ],
1333 "date": [
1334 "Sat, 25 Jan 2020 06:01:15 GMT"
1335 ]
1336 },
1337 "body": {
1338 "string": "{\"result\":{\"errors\":[],\"fields\":{\"title\":\"remove binary\"},\"revisionIDFieldInfo\":{\"value\":null,\"validDomain\":\"https:\\/\\/phab.mercurial-scm.org\"},\"transactions\":[{\"type\":\"title\",\"value\":\"remove binary\"}]},\"error_code\":null,\"error_info\":null}"
1339 }
1340 },
1341 "request": {
1342 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22corpus%22%3A+%22remove+binary%22%7D",
1343 "method": "POST",
1344 "headers": {
1345 "content-type": [
1346 "application/x-www-form-urlencoded"
1347 ],
1348 "user-agent": [
1349 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
1350 ],
1351 "content-length": [
1352 "158"
1353 ],
1354 "host": [
1355 "phab.mercurial-scm.org"
1356 ],
1357 "accept": [
1358 "application/mercurial-0.1"
1359 ]
1360 },
1361 "uri": "https://phab.mercurial-scm.org//api/differential.parsecommitmessage"
1362 }
1363 },
1364 {
1365 "response": {
1366 "status": {
1367 "message": "OK",
1368 "code": 200
1369 },
1370 "headers": {
1371 "strict-transport-security": [
1372 "max-age=0; includeSubdomains; preload"
1373 ],
1374 "server": [
1375 "Apache/2.4.10 (Debian)"
1376 ],
1377 "referrer-policy": [
1378 "no-referrer"
1379 ],
1380 "x-frame-options": [
1381 "Deny"
1382 ],
1383 "expires": [
1384 "Sat, 01 Jan 2000 00:00:00 GMT"
1385 ],
1386 "x-content-type-options": [
1387 "nosniff"
1388 ],
1389 "cache-control": [
1390 "no-store"
1391 ],
1392 "content-type": [
1393 "application/json"
1394 ],
1395 "x-xss-protection": [
1396 "1; mode=block"
1397 ],
1398 "transfer-encoding": [
1399 "chunked"
1400 ],
1401 "date": [
1402 "Sat, 25 Jan 2020 06:01:16 GMT"
1403 ]
1404 },
1405 "body": {
1406 "string": "{\"result\":{\"object\":{\"id\":8009,\"phid\":\"PHID-DREV-dhu2sy4tbldt4npi6sm7\"},\"transactions\":[{\"phid\":\"PHID-XACT-DREV-bj3vveste6uhvwa\"},{\"phid\":\"PHID-XACT-DREV-nh6m26vnt2lglxa\"},{\"phid\":\"PHID-XACT-DREV-qpaic5oz5gjiyac\"},{\"phid\":\"PHID-XACT-DREV-2e4pylpz72lnq4u\"},{\"phid\":\"PHID-XACT-DREV-4ajvkrw3bwabrxb\"},{\"phid\":\"PHID-XACT-DREV-xs75m6gczvk4fbb\"}]},\"error_code\":null,\"error_info\":null}"
1407 }
1408 },
1409 "request": {
1410 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22transactions%22%3A+%5B%7B%22type%22%3A+%22update%22%2C+%22value%22%3A+%22PHID-DIFF-burakyhnzac52zcbjn2l%22%7D%2C+%7B%22type%22%3A+%22parents.set%22%2C+%22value%22%3A+%5B%22PHID-DREV-q5cr6ap6pwfczhlhjrmv%22%5D%7D%2C+%7B%22type%22%3A+%22title%22%2C+%22value%22%3A+%22remove+binary%22%7D%5D%7D",
1411 "method": "POST",
1412 "headers": {
1413 "content-type": [
1414 "application/x-www-form-urlencoded"
1415 ],
1416 "user-agent": [
1417 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
1418 ],
1419 "content-length": [
1420 "413"
1421 ],
1422 "host": [
1423 "phab.mercurial-scm.org"
1424 ],
1425 "accept": [
1426 "application/mercurial-0.1"
1427 ]
1428 },
1429 "uri": "https://phab.mercurial-scm.org//api/differential.revision.edit"
1430 }
1431 },
1432 {
1433 "response": {
1434 "status": {
1435 "message": "OK",
1436 "code": 200
1437 },
1438 "headers": {
1439 "strict-transport-security": [
1440 "max-age=0; includeSubdomains; preload"
1441 ],
1442 "server": [
1443 "Apache/2.4.10 (Debian)"
1444 ],
1445 "referrer-policy": [
1446 "no-referrer"
1447 ],
1448 "x-frame-options": [
1449 "Deny"
1450 ],
1451 "expires": [
1452 "Sat, 01 Jan 2000 00:00:00 GMT"
1453 ],
1454 "x-content-type-options": [
1455 "nosniff"
1456 ],
1457 "cache-control": [
1458 "no-store"
1459 ],
1460 "content-type": [
1461 "application/json"
1462 ],
1463 "x-xss-protection": [
1464 "1; mode=block"
1465 ],
1466 "transfer-encoding": [
1467 "chunked"
1468 ],
1469 "date": [
1470 "Sat, 25 Jan 2020 06:01:17 GMT"
1471 ]
1472 },
1473 "body": {
1474 "string": "{\"result\":[{\"id\":\"8009\",\"phid\":\"PHID-DREV-dhu2sy4tbldt4npi6sm7\",\"title\":\"remove binary\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/D8009\",\"dateCreated\":\"1579932076\",\"dateModified\":\"1579932076\",\"authorPHID\":\"PHID-USER-tzhaient733lwrlbcag5\",\"status\":\"0\",\"statusName\":\"Needs Review\",\"properties\":{\"draft.broadcast\":true,\"lines.added\":0,\"lines.removed\":0},\"branch\":\"default\",\"summary\":\"\",\"testPlan\":\"\",\"lineCount\":\"0\",\"activeDiffPHID\":\"PHID-DIFF-burakyhnzac52zcbjn2l\",\"diffs\":[\"19601\"],\"commits\":[],\"reviewers\":{\"PHID-PROJ-3dvcxzznrjru2xmmses3\":\"PHID-PROJ-3dvcxzznrjru2xmmses3\"},\"ccs\":[\"PHID-USER-q42dn7cc3donqriafhjx\"],\"hashes\":[[\"hgcm\",\"\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\"]],\"auxiliary\":{\"phabricator:projects\":[],\"phabricator:depends-on\":[\"PHID-DREV-q5cr6ap6pwfczhlhjrmv\"]},\"repositoryPHID\":\"PHID-REPO-bvunnehri4u2isyr7bc3\",\"sourcePath\":\"\\/\"},{\"id\":\"8008\",\"phid\":\"PHID-DREV-q5cr6ap6pwfczhlhjrmv\",\"title\":\"modify binary\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/D8008\",\"dateCreated\":\"1579932073\",\"dateModified\":\"1579932076\",\"authorPHID\":\"PHID-USER-tzhaient733lwrlbcag5\",\"status\":\"0\",\"statusName\":\"Needs Review\",\"properties\":{\"draft.broadcast\":true,\"lines.added\":0,\"lines.removed\":0},\"branch\":\"default\",\"summary\":\"\",\"testPlan\":\"\",\"lineCount\":\"0\",\"activeDiffPHID\":\"PHID-DIFF-wdmlzkf4huoiqpl4vbsr\",\"diffs\":[\"19600\"],\"commits\":[],\"reviewers\":{\"PHID-PROJ-3dvcxzznrjru2xmmses3\":\"PHID-PROJ-3dvcxzznrjru2xmmses3\"},\"ccs\":[\"PHID-USER-q42dn7cc3donqriafhjx\"],\"hashes\":[[\"hgcm\",\"\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\"]],\"auxiliary\":{\"phabricator:projects\":[],\"phabricator:depends-on\":[\"PHID-DREV-477vjqq7vbddmjluy2cd\"]},\"repositoryPHID\":\"PHID-REPO-bvunnehri4u2isyr7bc3\",\"sourcePath\":\"\\/\"},{\"id\":\"8007\",\"phid\":\"PHID-DREV-477vjqq7vbddmjluy2cd\",\"title\":\"add binary\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/D8007\",\"dateCreated\":\"1579932068\",\"dateModified\":\"1579932073\",\"authorPHID\":\"PHID-USER-tzhaient733lwrlbcag5\",\"status\":\"0\",\"statusName\":\"Needs Review\",\"properties\":{\"draft.broadcast\":true,\"lines.added\":0,\"lines.removed\":0},\"branch\":\"default\",\"summary\":\"\",\"testPlan\":\"\",\"lineCount\":\"0\",\"activeDiffPHID\":\"PHID-DIFF-uvk7qaq6iglk4wgk2lxb\",\"diffs\":[\"19599\"],\"commits\":[],\"reviewers\":{\"PHID-PROJ-3dvcxzznrjru2xmmses3\":\"PHID-PROJ-3dvcxzznrjru2xmmses3\"},\"ccs\":[\"PHID-USER-q42dn7cc3donqriafhjx\"],\"hashes\":[[\"hgcm\",\"\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\"]],\"auxiliary\":{\"phabricator:projects\":[],\"phabricator:depends-on\":[]},\"repositoryPHID\":\"PHID-REPO-bvunnehri4u2isyr7bc3\",\"sourcePath\":\"\\/\"}],\"error_code\":null,\"error_info\":null}"
1475 }
1476 },
1477 "request": {
1478 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22ids%22%3A+%5B8007%2C+8008%2C+8009%5D%7D",
1479 "method": "POST",
1480 "headers": {
1481 "content-type": [
1482 "application/x-www-form-urlencoded"
1483 ],
1484 "user-agent": [
1485 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
1486 ],
1487 "content-length": [
1488 "162"
1489 ],
1490 "host": [
1491 "phab.mercurial-scm.org"
1492 ],
1493 "accept": [
1494 "application/mercurial-0.1"
1495 ]
1496 },
1497 "uri": "https://phab.mercurial-scm.org//api/differential.query"
1498 }
1499 },
1500 {
1501 "response": {
1502 "status": {
1503 "message": "OK",
1504 "code": 200
1505 },
1506 "headers": {
1507 "strict-transport-security": [
1508 "max-age=0; includeSubdomains; preload"
1509 ],
1510 "server": [
1511 "Apache/2.4.10 (Debian)"
1512 ],
1513 "referrer-policy": [
1514 "no-referrer"
1515 ],
1516 "x-frame-options": [
1517 "Deny"
1518 ],
1519 "expires": [
1520 "Sat, 01 Jan 2000 00:00:00 GMT"
1521 ],
1522 "x-content-type-options": [
1523 "nosniff"
1524 ],
1525 "cache-control": [
1526 "no-store"
1527 ],
1528 "content-type": [
1529 "application/json"
1530 ],
1531 "x-xss-protection": [
1532 "1; mode=block"
1533 ],
1534 "transfer-encoding": [
1535 "chunked"
1536 ],
1537 "date": [
1538 "Sat, 25 Jan 2020 06:01:17 GMT"
1539 ]
1540 },
1541 "body": {
1542 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
1543 }
1544 },
1545 "request": {
1546 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%22branch%5C%22%3A+%5C%22default%5C%22%2C+%5C%22date%5C%22%3A+%5C%220+0%5C%22%2C+%5C%22node%5C%22%3A+%5C%22b8139fbb4a579a69dccd906a1a8bf34ba2b885a3%5C%22%2C+%5C%22parent%5C%22%3A+%5C%221849d7828727a28e14c589323e4f8c9a1c8d2816%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+19599%2C+%22name%22%3A+%22hg%3Ameta%22%7D",
1547 "method": "POST",
1548 "headers": {
1549 "content-type": [
1550 "application/x-www-form-urlencoded"
1551 ],
1552 "user-agent": [
1553 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
1554 ],
1555 "content-length": [
1556 "482"
1557 ],
1558 "host": [
1559 "phab.mercurial-scm.org"
1560 ],
1561 "accept": [
1562 "application/mercurial-0.1"
1563 ]
1564 },
1565 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty"
1566 }
1567 },
1568 {
1569 "response": {
1570 "status": {
1571 "message": "OK",
1572 "code": 200
1573 },
1574 "headers": {
1575 "strict-transport-security": [
1576 "max-age=0; includeSubdomains; preload"
1577 ],
1578 "server": [
1579 "Apache/2.4.10 (Debian)"
1580 ],
1581 "referrer-policy": [
1582 "no-referrer"
1583 ],
1584 "x-frame-options": [
1585 "Deny"
1586 ],
1587 "expires": [
1588 "Sat, 01 Jan 2000 00:00:00 GMT"
1589 ],
1590 "x-content-type-options": [
1591 "nosniff"
1592 ],
1593 "cache-control": [
1594 "no-store"
1595 ],
1596 "content-type": [
1597 "application/json"
1598 ],
1599 "x-xss-protection": [
1600 "1; mode=block"
1601 ],
1602 "transfer-encoding": [
1603 "chunked"
1604 ],
1605 "date": [
1606 "Sat, 25 Jan 2020 06:01:18 GMT"
1607 ]
1608 },
1609 "body": {
1610 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
1611 }
1612 },
1613 "request": {
1614 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%22b8139fbb4a579a69dccd906a1a8bf34ba2b885a3%5C%22%3A+%7B%5C%22author%5C%22%3A+%5C%22test%5C%22%2C+%5C%22authorEmail%5C%22%3A+%5C%22test%5C%22%2C+%5C%22branch%5C%22%3A+%5C%22default%5C%22%2C+%5C%22commit%5C%22%3A+%5C%22b8139fbb4a579a69dccd906a1a8bf34ba2b885a3%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%221849d7828727a28e14c589323e4f8c9a1c8d2816%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+19599%2C+%22name%22%3A+%22local%3Acommits%22%7D",
1615 "method": "POST",
1616 "headers": {
1617 "content-type": [
1618 "application/x-www-form-urlencoded"
1619 ],
1620 "user-agent": [
1621 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
1622 ],
1623 "content-length": [
1624 "594"
1625 ],
1626 "host": [
1627 "phab.mercurial-scm.org"
1628 ],
1629 "accept": [
1630 "application/mercurial-0.1"
1631 ]
1632 },
1633 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty"
1634 }
1635 },
1636 {
1637 "response": {
1638 "status": {
1639 "message": "OK",
1640 "code": 200
1641 },
1642 "headers": {
1643 "strict-transport-security": [
1644 "max-age=0; includeSubdomains; preload"
1645 ],
1646 "server": [
1647 "Apache/2.4.10 (Debian)"
1648 ],
1649 "referrer-policy": [
1650 "no-referrer"
1651 ],
1652 "x-frame-options": [
1653 "Deny"
1654 ],
1655 "expires": [
1656 "Sat, 01 Jan 2000 00:00:00 GMT"
1657 ],
1658 "x-content-type-options": [
1659 "nosniff"
1660 ],
1661 "cache-control": [
1662 "no-store"
1663 ],
1664 "content-type": [
1665 "application/json"
1666 ],
1667 "x-xss-protection": [
1668 "1; mode=block"
1669 ],
1670 "transfer-encoding": [
1671 "chunked"
1672 ],
1673 "date": [
1674 "Sat, 25 Jan 2020 06:01:19 GMT"
1675 ]
1676 },
1677 "body": {
1678 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
1679 }
1680 },
1681 "request": {
1682 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%22branch%5C%22%3A+%5C%22default%5C%22%2C+%5C%22date%5C%22%3A+%5C%220+0%5C%22%2C+%5C%22node%5C%22%3A+%5C%22c88ce4c2d2ad6337b772bc093398f85858196eb5%5C%22%2C+%5C%22parent%5C%22%3A+%5C%22b8139fbb4a579a69dccd906a1a8bf34ba2b885a3%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+19600%2C+%22name%22%3A+%22hg%3Ameta%22%7D",
1683 "method": "POST",
1684 "headers": {
1685 "content-type": [
1686 "application/x-www-form-urlencoded"
1687 ],
1688 "user-agent": [
1689 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
1690 ],
1691 "content-length": [
1692 "482"
1693 ],
1694 "host": [
1695 "phab.mercurial-scm.org"
1696 ],
1697 "accept": [
1698 "application/mercurial-0.1"
1699 ]
1700 },
1701 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty"
1702 }
1703 },
1704 {
1705 "response": {
1706 "status": {
1707 "message": "OK",
1708 "code": 200
1709 },
1710 "headers": {
1711 "strict-transport-security": [
1712 "max-age=0; includeSubdomains; preload"
1713 ],
1714 "server": [
1715 "Apache/2.4.10 (Debian)"
1716 ],
1717 "referrer-policy": [
1718 "no-referrer"
1719 ],
1720 "x-frame-options": [
1721 "Deny"
1722 ],
1723 "expires": [
1724 "Sat, 01 Jan 2000 00:00:00 GMT"
1725 ],
1726 "x-content-type-options": [
1727 "nosniff"
1728 ],
1729 "cache-control": [
1730 "no-store"
1731 ],
1732 "content-type": [
1733 "application/json"
1734 ],
1735 "x-xss-protection": [
1736 "1; mode=block"
1737 ],
1738 "transfer-encoding": [
1739 "chunked"
1740 ],
1741 "date": [
1742 "Sat, 25 Jan 2020 06:01:19 GMT"
1743 ]
1744 },
1745 "body": {
1746 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
1747 }
1748 },
1749 "request": {
1750 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%22c88ce4c2d2ad6337b772bc093398f85858196eb5%5C%22%3A+%7B%5C%22author%5C%22%3A+%5C%22test%5C%22%2C+%5C%22authorEmail%5C%22%3A+%5C%22test%5C%22%2C+%5C%22branch%5C%22%3A+%5C%22default%5C%22%2C+%5C%22commit%5C%22%3A+%5C%22c88ce4c2d2ad6337b772bc093398f85858196eb5%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%22b8139fbb4a579a69dccd906a1a8bf34ba2b885a3%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+19600%2C+%22name%22%3A+%22local%3Acommits%22%7D",
1751 "method": "POST",
1752 "headers": {
1753 "content-type": [
1754 "application/x-www-form-urlencoded"
1755 ],
1756 "user-agent": [
1757 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
1758 ],
1759 "content-length": [
1760 "594"
1761 ],
1762 "host": [
1763 "phab.mercurial-scm.org"
1764 ],
1765 "accept": [
1766 "application/mercurial-0.1"
1767 ]
1768 },
1769 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty"
1770 }
1771 },
1772 {
1773 "response": {
1774 "status": {
1775 "message": "OK",
1776 "code": 200
1777 },
1778 "headers": {
1779 "strict-transport-security": [
1780 "max-age=0; includeSubdomains; preload"
1781 ],
1782 "server": [
1783 "Apache/2.4.10 (Debian)"
1784 ],
1785 "referrer-policy": [
1786 "no-referrer"
1787 ],
1788 "x-frame-options": [
1789 "Deny"
1790 ],
1791 "expires": [
1792 "Sat, 01 Jan 2000 00:00:00 GMT"
1793 ],
1794 "x-content-type-options": [
1795 "nosniff"
1796 ],
1797 "cache-control": [
1798 "no-store"
1799 ],
1800 "content-type": [
1801 "application/json"
1802 ],
1803 "x-xss-protection": [
1804 "1; mode=block"
1805 ],
1806 "transfer-encoding": [
1807 "chunked"
1808 ],
1809 "date": [
1810 "Sat, 25 Jan 2020 06:01:20 GMT"
1811 ]
1812 },
1813 "body": {
1814 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
1815 }
1816 },
1817 "request": {
1818 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%22branch%5C%22%3A+%5C%22default%5C%22%2C+%5C%22date%5C%22%3A+%5C%220+0%5C%22%2C+%5C%22node%5C%22%3A+%5C%2275dbbc901145d7beb190197aa232f74540e5a9f3%5C%22%2C+%5C%22parent%5C%22%3A+%5C%22c88ce4c2d2ad6337b772bc093398f85858196eb5%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+19601%2C+%22name%22%3A+%22hg%3Ameta%22%7D",
1819 "method": "POST",
1820 "headers": {
1821 "content-type": [
1822 "application/x-www-form-urlencoded"
1823 ],
1824 "user-agent": [
1825 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
1826 ],
1827 "content-length": [
1828 "482"
1829 ],
1830 "host": [
1831 "phab.mercurial-scm.org"
1832 ],
1833 "accept": [
1834 "application/mercurial-0.1"
1835 ]
1836 },
1837 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty"
1838 }
1839 },
1840 {
1841 "response": {
1842 "status": {
1843 "message": "OK",
1844 "code": 200
1845 },
1846 "headers": {
1847 "strict-transport-security": [
1848 "max-age=0; includeSubdomains; preload"
1849 ],
1850 "server": [
1851 "Apache/2.4.10 (Debian)"
1852 ],
1853 "referrer-policy": [
1854 "no-referrer"
1855 ],
1856 "x-frame-options": [
1857 "Deny"
1858 ],
1859 "expires": [
1860 "Sat, 01 Jan 2000 00:00:00 GMT"
1861 ],
1862 "x-content-type-options": [
1863 "nosniff"
1864 ],
1865 "cache-control": [
1866 "no-store"
1867 ],
1868 "content-type": [
1869 "application/json"
1870 ],
1871 "x-xss-protection": [
1872 "1; mode=block"
1873 ],
1874 "transfer-encoding": [
1875 "chunked"
1876 ],
1877 "date": [
1878 "Sat, 25 Jan 2020 06:01:21 GMT"
1879 ]
1880 },
1881 "body": {
1882 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
1883 }
1884 },
1885 "request": {
1886 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%2275dbbc901145d7beb190197aa232f74540e5a9f3%5C%22%3A+%7B%5C%22author%5C%22%3A+%5C%22test%5C%22%2C+%5C%22authorEmail%5C%22%3A+%5C%22test%5C%22%2C+%5C%22branch%5C%22%3A+%5C%22default%5C%22%2C+%5C%22commit%5C%22%3A+%5C%2275dbbc901145d7beb190197aa232f74540e5a9f3%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%22c88ce4c2d2ad6337b772bc093398f85858196eb5%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+19601%2C+%22name%22%3A+%22local%3Acommits%22%7D",
1887 "method": "POST",
1888 "headers": {
1889 "content-type": [
1890 "application/x-www-form-urlencoded"
1891 ],
1892 "user-agent": [
1893 "mercurial/proto-1.0 (Mercurial 5.3rc1+3-624fe53ce1e7+20200124)"
1894 ],
1895 "content-length": [
1896 "594"
1897 ],
1898 "host": [
1899 "phab.mercurial-scm.org"
1900 ],
1901 "accept": [
1902 "application/mercurial-0.1"
1903 ]
1904 },
1905 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty"
1906 }
1907 }
1908 ]
1909 } No newline at end of file
@@ -1,1797 +1,1797 b''
1 1 # phabricator.py - simple Phabricator integration
2 2 #
3 3 # Copyright 2017 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 """simple Phabricator integration (EXPERIMENTAL)
8 8
9 9 This extension provides a ``phabsend`` command which sends a stack of
10 10 changesets to Phabricator, and a ``phabread`` command which prints a stack of
11 11 revisions in a format suitable for :hg:`import`, and a ``phabupdate`` command
12 12 to update statuses in batch.
13 13
14 14 A "phabstatus" view for :hg:`show` is also provided; it displays status
15 15 information of Phabricator differentials associated with unfinished
16 16 changesets.
17 17
18 18 By default, Phabricator requires ``Test Plan`` which might prevent some
19 19 changeset from being sent. The requirement could be disabled by changing
20 20 ``differential.require-test-plan-field`` config server side.
21 21
22 22 Config::
23 23
24 24 [phabricator]
25 25 # Phabricator URL
26 26 url = https://phab.example.com/
27 27
28 28 # Repo callsign. If a repo has a URL https://$HOST/diffusion/FOO, then its
29 29 # callsign is "FOO".
30 30 callsign = FOO
31 31
32 32 # curl command to use. If not set (default), use builtin HTTP library to
33 33 # communicate. If set, use the specified curl command. This could be useful
34 34 # if you need to specify advanced options that is not easily supported by
35 35 # the internal library.
36 36 curlcmd = curl --connect-timeout 2 --retry 3 --silent
37 37
38 38 [auth]
39 39 example.schemes = https
40 40 example.prefix = phab.example.com
41 41
42 42 # API token. Get it from https://$HOST/conduit/login/
43 43 example.phabtoken = cli-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
44 44 """
45 45
46 46 from __future__ import absolute_import
47 47
48 48 import base64
49 49 import contextlib
50 50 import hashlib
51 51 import itertools
52 52 import json
53 53 import mimetypes
54 54 import operator
55 55 import re
56 56
57 57 from mercurial.node import bin, nullid
58 58 from mercurial.i18n import _
59 59 from mercurial.pycompat import getattr
60 60 from mercurial.thirdparty import attr
61 61 from mercurial import (
62 62 cmdutil,
63 63 context,
64 64 encoding,
65 65 error,
66 66 exthelper,
67 67 graphmod,
68 68 httpconnection as httpconnectionmod,
69 69 localrepo,
70 70 logcmdutil,
71 71 match,
72 72 mdiff,
73 73 obsutil,
74 74 parser,
75 75 patch,
76 76 phases,
77 77 pycompat,
78 78 scmutil,
79 79 smartset,
80 80 tags,
81 81 templatefilters,
82 82 templateutil,
83 83 url as urlmod,
84 84 util,
85 85 )
86 86 from mercurial.utils import (
87 87 procutil,
88 88 stringutil,
89 89 )
90 90 from . import show
91 91
92 92
93 93 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
94 94 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
95 95 # be specifying the version(s) of Mercurial they are tested with, or
96 96 # leave the attribute unspecified.
97 97 testedwith = b'ships-with-hg-core'
98 98
99 99 eh = exthelper.exthelper()
100 100
101 101 cmdtable = eh.cmdtable
102 102 command = eh.command
103 103 configtable = eh.configtable
104 104 templatekeyword = eh.templatekeyword
105 105 uisetup = eh.finaluisetup
106 106
107 107 # developer config: phabricator.batchsize
108 108 eh.configitem(
109 109 b'phabricator', b'batchsize', default=12,
110 110 )
111 111 eh.configitem(
112 112 b'phabricator', b'callsign', default=None,
113 113 )
114 114 eh.configitem(
115 115 b'phabricator', b'curlcmd', default=None,
116 116 )
117 117 # developer config: phabricator.repophid
118 118 eh.configitem(
119 119 b'phabricator', b'repophid', default=None,
120 120 )
121 121 eh.configitem(
122 122 b'phabricator', b'url', default=None,
123 123 )
124 124 eh.configitem(
125 125 b'phabsend', b'confirm', default=False,
126 126 )
127 127
128 128 colortable = {
129 129 b'phabricator.action.created': b'green',
130 130 b'phabricator.action.skipped': b'magenta',
131 131 b'phabricator.action.updated': b'magenta',
132 132 b'phabricator.desc': b'',
133 133 b'phabricator.drev': b'bold',
134 134 b'phabricator.node': b'',
135 135 b'phabricator.status.abandoned': b'magenta dim',
136 136 b'phabricator.status.accepted': b'green bold',
137 137 b'phabricator.status.closed': b'green',
138 138 b'phabricator.status.needsreview': b'yellow',
139 139 b'phabricator.status.needsrevision': b'red',
140 140 b'phabricator.status.changesplanned': b'red',
141 141 }
142 142
143 143 _VCR_FLAGS = [
144 144 (
145 145 b'',
146 146 b'test-vcr',
147 147 b'',
148 148 _(
149 149 b'Path to a vcr file. If nonexistent, will record a new vcr transcript'
150 150 b', otherwise will mock all http requests using the specified vcr file.'
151 151 b' (ADVANCED)'
152 152 ),
153 153 ),
154 154 ]
155 155
156 156
157 157 @eh.wrapfunction(localrepo, "loadhgrc")
158 158 def _loadhgrc(orig, ui, wdirvfs, hgvfs, requirements):
159 159 """Load ``.arcconfig`` content into a ui instance on repository open.
160 160 """
161 161 result = False
162 162 arcconfig = {}
163 163
164 164 try:
165 165 # json.loads only accepts bytes from 3.6+
166 166 rawparams = encoding.unifromlocal(wdirvfs.read(b".arcconfig"))
167 167 # json.loads only returns unicode strings
168 168 arcconfig = pycompat.rapply(
169 169 lambda x: encoding.unitolocal(x)
170 170 if isinstance(x, pycompat.unicode)
171 171 else x,
172 172 pycompat.json_loads(rawparams),
173 173 )
174 174
175 175 result = True
176 176 except ValueError:
177 177 ui.warn(_(b"invalid JSON in %s\n") % wdirvfs.join(b".arcconfig"))
178 178 except IOError:
179 179 pass
180 180
181 181 cfg = util.sortdict()
182 182
183 183 if b"repository.callsign" in arcconfig:
184 184 cfg[(b"phabricator", b"callsign")] = arcconfig[b"repository.callsign"]
185 185
186 186 if b"phabricator.uri" in arcconfig:
187 187 cfg[(b"phabricator", b"url")] = arcconfig[b"phabricator.uri"]
188 188
189 189 if cfg:
190 190 ui.applyconfig(cfg, source=wdirvfs.join(b".arcconfig"))
191 191
192 192 return orig(ui, wdirvfs, hgvfs, requirements) or result # Load .hg/hgrc
193 193
194 194
195 195 def vcrcommand(name, flags, spec, helpcategory=None, optionalrepo=False):
196 196 fullflags = flags + _VCR_FLAGS
197 197
198 198 def hgmatcher(r1, r2):
199 199 if r1.uri != r2.uri or r1.method != r2.method:
200 200 return False
201 201 r1params = util.urlreq.parseqs(r1.body)
202 202 r2params = util.urlreq.parseqs(r2.body)
203 203 for key in r1params:
204 204 if key not in r2params:
205 205 return False
206 206 value = r1params[key][0]
207 207 # we want to compare json payloads without worrying about ordering
208 208 if value.startswith(b'{') and value.endswith(b'}'):
209 209 r1json = pycompat.json_loads(value)
210 210 r2json = pycompat.json_loads(r2params[key][0])
211 211 if r1json != r2json:
212 212 return False
213 213 elif r2params[key][0] != value:
214 214 return False
215 215 return True
216 216
217 217 def sanitiserequest(request):
218 218 request.body = re.sub(
219 219 br'cli-[a-z0-9]+', br'cli-hahayouwish', request.body
220 220 )
221 221 return request
222 222
223 223 def sanitiseresponse(response):
224 224 if 'set-cookie' in response['headers']:
225 225 del response['headers']['set-cookie']
226 226 return response
227 227
228 228 def decorate(fn):
229 229 def inner(*args, **kwargs):
230 230 cassette = pycompat.fsdecode(kwargs.pop('test_vcr', None))
231 231 if cassette:
232 232 import hgdemandimport
233 233
234 234 with hgdemandimport.deactivated():
235 235 import vcr as vcrmod
236 236 import vcr.stubs as stubs
237 237
238 238 vcr = vcrmod.VCR(
239 239 serializer='json',
240 240 before_record_request=sanitiserequest,
241 241 before_record_response=sanitiseresponse,
242 242 custom_patches=[
243 243 (
244 244 urlmod,
245 245 'httpconnection',
246 246 stubs.VCRHTTPConnection,
247 247 ),
248 248 (
249 249 urlmod,
250 250 'httpsconnection',
251 251 stubs.VCRHTTPSConnection,
252 252 ),
253 253 ],
254 254 )
255 255 vcr.register_matcher('hgmatcher', hgmatcher)
256 256 with vcr.use_cassette(cassette, match_on=['hgmatcher']):
257 257 return fn(*args, **kwargs)
258 258 return fn(*args, **kwargs)
259 259
260 260 inner.__name__ = fn.__name__
261 261 inner.__doc__ = fn.__doc__
262 262 return command(
263 263 name,
264 264 fullflags,
265 265 spec,
266 266 helpcategory=helpcategory,
267 267 optionalrepo=optionalrepo,
268 268 )(inner)
269 269
270 270 return decorate
271 271
272 272
273 273 def urlencodenested(params):
274 274 """like urlencode, but works with nested parameters.
275 275
276 276 For example, if params is {'a': ['b', 'c'], 'd': {'e': 'f'}}, it will be
277 277 flattened to {'a[0]': 'b', 'a[1]': 'c', 'd[e]': 'f'} and then passed to
278 278 urlencode. Note: the encoding is consistent with PHP's http_build_query.
279 279 """
280 280 flatparams = util.sortdict()
281 281
282 282 def process(prefix, obj):
283 283 if isinstance(obj, bool):
284 284 obj = {True: b'true', False: b'false'}[obj] # Python -> PHP form
285 285 lister = lambda l: [(b'%d' % k, v) for k, v in enumerate(l)]
286 286 items = {list: lister, dict: lambda x: x.items()}.get(type(obj))
287 287 if items is None:
288 288 flatparams[prefix] = obj
289 289 else:
290 290 for k, v in items(obj):
291 291 if prefix:
292 292 process(b'%s[%s]' % (prefix, k), v)
293 293 else:
294 294 process(k, v)
295 295
296 296 process(b'', params)
297 297 return util.urlreq.urlencode(flatparams)
298 298
299 299
300 300 def readurltoken(ui):
301 301 """return conduit url, token and make sure they exist
302 302
303 303 Currently read from [auth] config section. In the future, it might
304 304 make sense to read from .arcconfig and .arcrc as well.
305 305 """
306 306 url = ui.config(b'phabricator', b'url')
307 307 if not url:
308 308 raise error.Abort(
309 309 _(b'config %s.%s is required') % (b'phabricator', b'url')
310 310 )
311 311
312 312 res = httpconnectionmod.readauthforuri(ui, url, util.url(url).user)
313 313 token = None
314 314
315 315 if res:
316 316 group, auth = res
317 317
318 318 ui.debug(b"using auth.%s.* for authentication\n" % group)
319 319
320 320 token = auth.get(b'phabtoken')
321 321
322 322 if not token:
323 323 raise error.Abort(
324 324 _(b'Can\'t find conduit token associated to %s') % (url,)
325 325 )
326 326
327 327 return url, token
328 328
329 329
330 330 def callconduit(ui, name, params):
331 331 """call Conduit API, params is a dict. return json.loads result, or None"""
332 332 host, token = readurltoken(ui)
333 333 url, authinfo = util.url(b'/'.join([host, b'api', name])).authinfo()
334 334 ui.debug(b'Conduit Call: %s %s\n' % (url, pycompat.byterepr(params)))
335 335 params = params.copy()
336 336 params[b'__conduit__'] = {
337 337 b'token': token,
338 338 }
339 339 rawdata = {
340 340 b'params': templatefilters.json(params),
341 341 b'output': b'json',
342 342 b'__conduit__': 1,
343 343 }
344 344 data = urlencodenested(rawdata)
345 345 curlcmd = ui.config(b'phabricator', b'curlcmd')
346 346 if curlcmd:
347 347 sin, sout = procutil.popen2(
348 348 b'%s -d @- %s' % (curlcmd, procutil.shellquote(url))
349 349 )
350 350 sin.write(data)
351 351 sin.close()
352 352 body = sout.read()
353 353 else:
354 354 urlopener = urlmod.opener(ui, authinfo)
355 355 request = util.urlreq.request(pycompat.strurl(url), data=data)
356 356 with contextlib.closing(urlopener.open(request)) as rsp:
357 357 body = rsp.read()
358 358 ui.debug(b'Conduit Response: %s\n' % body)
359 359 parsed = pycompat.rapply(
360 360 lambda x: encoding.unitolocal(x)
361 361 if isinstance(x, pycompat.unicode)
362 362 else x,
363 363 # json.loads only accepts bytes from py3.6+
364 364 pycompat.json_loads(encoding.unifromlocal(body)),
365 365 )
366 366 if parsed.get(b'error_code'):
367 367 msg = _(b'Conduit Error (%s): %s') % (
368 368 parsed[b'error_code'],
369 369 parsed[b'error_info'],
370 370 )
371 371 raise error.Abort(msg)
372 372 return parsed[b'result']
373 373
374 374
375 375 @vcrcommand(b'debugcallconduit', [], _(b'METHOD'), optionalrepo=True)
376 376 def debugcallconduit(ui, repo, name):
377 377 """call Conduit API
378 378
379 379 Call parameters are read from stdin as a JSON blob. Result will be written
380 380 to stdout as a JSON blob.
381 381 """
382 382 # json.loads only accepts bytes from 3.6+
383 383 rawparams = encoding.unifromlocal(ui.fin.read())
384 384 # json.loads only returns unicode strings
385 385 params = pycompat.rapply(
386 386 lambda x: encoding.unitolocal(x)
387 387 if isinstance(x, pycompat.unicode)
388 388 else x,
389 389 pycompat.json_loads(rawparams),
390 390 )
391 391 # json.dumps only accepts unicode strings
392 392 result = pycompat.rapply(
393 393 lambda x: encoding.unifromlocal(x) if isinstance(x, bytes) else x,
394 394 callconduit(ui, name, params),
395 395 )
396 396 s = json.dumps(result, sort_keys=True, indent=2, separators=(u',', u': '))
397 397 ui.write(b'%s\n' % encoding.unitolocal(s))
398 398
399 399
400 400 def getrepophid(repo):
401 401 """given callsign, return repository PHID or None"""
402 402 # developer config: phabricator.repophid
403 403 repophid = repo.ui.config(b'phabricator', b'repophid')
404 404 if repophid:
405 405 return repophid
406 406 callsign = repo.ui.config(b'phabricator', b'callsign')
407 407 if not callsign:
408 408 return None
409 409 query = callconduit(
410 410 repo.ui,
411 411 b'diffusion.repository.search',
412 412 {b'constraints': {b'callsigns': [callsign]}},
413 413 )
414 414 if len(query[b'data']) == 0:
415 415 return None
416 416 repophid = query[b'data'][0][b'phid']
417 417 repo.ui.setconfig(b'phabricator', b'repophid', repophid)
418 418 return repophid
419 419
420 420
421 421 _differentialrevisiontagre = re.compile(br'\AD([1-9][0-9]*)\Z')
422 422 _differentialrevisiondescre = re.compile(
423 423 br'^Differential Revision:\s*(?P<url>(?:.*)D(?P<id>[1-9][0-9]*))$', re.M
424 424 )
425 425
426 426
427 427 def getoldnodedrevmap(repo, nodelist):
428 428 """find previous nodes that has been sent to Phabricator
429 429
430 430 return {node: (oldnode, Differential diff, Differential Revision ID)}
431 431 for node in nodelist with known previous sent versions, or associated
432 432 Differential Revision IDs. ``oldnode`` and ``Differential diff`` could
433 433 be ``None``.
434 434
435 435 Examines commit messages like "Differential Revision:" to get the
436 436 association information.
437 437
438 438 If such commit message line is not found, examines all precursors and their
439 439 tags. Tags with format like "D1234" are considered a match and the node
440 440 with that tag, and the number after "D" (ex. 1234) will be returned.
441 441
442 442 The ``old node``, if not None, is guaranteed to be the last diff of
443 443 corresponding Differential Revision, and exist in the repo.
444 444 """
445 445 unfi = repo.unfiltered()
446 446 has_node = unfi.changelog.index.has_node
447 447
448 448 result = {} # {node: (oldnode?, lastdiff?, drev)}
449 449 toconfirm = {} # {node: (force, {precnode}, drev)}
450 450 for node in nodelist:
451 451 ctx = unfi[node]
452 452 # For tags like "D123", put them into "toconfirm" to verify later
453 453 precnodes = list(obsutil.allpredecessors(unfi.obsstore, [node]))
454 454 for n in precnodes:
455 455 if has_node(n):
456 456 for tag in unfi.nodetags(n):
457 457 m = _differentialrevisiontagre.match(tag)
458 458 if m:
459 459 toconfirm[node] = (0, set(precnodes), int(m.group(1)))
460 460 break
461 461 else:
462 462 continue # move to next predecessor
463 463 break # found a tag, stop
464 464 else:
465 465 # Check commit message
466 466 m = _differentialrevisiondescre.search(ctx.description())
467 467 if m:
468 468 toconfirm[node] = (1, set(precnodes), int(m.group('id')))
469 469
470 470 # Double check if tags are genuine by collecting all old nodes from
471 471 # Phabricator, and expect precursors overlap with it.
472 472 if toconfirm:
473 473 drevs = [drev for force, precs, drev in toconfirm.values()]
474 474 alldiffs = callconduit(
475 475 unfi.ui, b'differential.querydiffs', {b'revisionIDs': drevs}
476 476 )
477 477 getnode = lambda d: bin(getdiffmeta(d).get(b'node', b'')) or None
478 478 for newnode, (force, precset, drev) in toconfirm.items():
479 479 diffs = [
480 480 d for d in alldiffs.values() if int(d[b'revisionID']) == drev
481 481 ]
482 482
483 483 # "precursors" as known by Phabricator
484 484 phprecset = set(getnode(d) for d in diffs)
485 485
486 486 # Ignore if precursors (Phabricator and local repo) do not overlap,
487 487 # and force is not set (when commit message says nothing)
488 488 if not force and not bool(phprecset & precset):
489 489 tagname = b'D%d' % drev
490 490 tags.tag(
491 491 repo,
492 492 tagname,
493 493 nullid,
494 494 message=None,
495 495 user=None,
496 496 date=None,
497 497 local=True,
498 498 )
499 499 unfi.ui.warn(
500 500 _(
501 501 b'D%d: local tag removed - does not match '
502 502 b'Differential history\n'
503 503 )
504 504 % drev
505 505 )
506 506 continue
507 507
508 508 # Find the last node using Phabricator metadata, and make sure it
509 509 # exists in the repo
510 510 oldnode = lastdiff = None
511 511 if diffs:
512 512 lastdiff = max(diffs, key=lambda d: int(d[b'id']))
513 513 oldnode = getnode(lastdiff)
514 514 if oldnode and not has_node(oldnode):
515 515 oldnode = None
516 516
517 517 result[newnode] = (oldnode, lastdiff, drev)
518 518
519 519 return result
520 520
521 521
522 522 def getdrevmap(repo, revs):
523 523 """Return a dict mapping each rev in `revs` to their Differential Revision
524 524 ID or None.
525 525 """
526 526 result = {}
527 527 for rev in revs:
528 528 result[rev] = None
529 529 ctx = repo[rev]
530 530 # Check commit message
531 531 m = _differentialrevisiondescre.search(ctx.description())
532 532 if m:
533 533 result[rev] = int(m.group('id'))
534 534 continue
535 535 # Check tags
536 536 for tag in repo.nodetags(ctx.node()):
537 537 m = _differentialrevisiontagre.match(tag)
538 538 if m:
539 539 result[rev] = int(m.group(1))
540 540 break
541 541
542 542 return result
543 543
544 544
545 545 def getdiff(ctx, diffopts):
546 546 """plain-text diff without header (user, commit message, etc)"""
547 547 output = util.stringio()
548 548 for chunk, _label in patch.diffui(
549 549 ctx.repo(), ctx.p1().node(), ctx.node(), None, opts=diffopts
550 550 ):
551 551 output.write(chunk)
552 552 return output.getvalue()
553 553
554 554
555 555 class DiffChangeType(object):
556 556 ADD = 1
557 557 CHANGE = 2
558 558 DELETE = 3
559 559 MOVE_AWAY = 4
560 560 COPY_AWAY = 5
561 561 MOVE_HERE = 6
562 562 COPY_HERE = 7
563 563 MULTICOPY = 8
564 564
565 565
566 566 class DiffFileType(object):
567 567 TEXT = 1
568 568 IMAGE = 2
569 569 BINARY = 3
570 570
571 571
572 572 @attr.s
573 573 class phabhunk(dict):
574 574 """Represents a Differential hunk, which is owned by a Differential change
575 575 """
576 576
577 577 oldOffset = attr.ib(default=0) # camelcase-required
578 578 oldLength = attr.ib(default=0) # camelcase-required
579 579 newOffset = attr.ib(default=0) # camelcase-required
580 580 newLength = attr.ib(default=0) # camelcase-required
581 581 corpus = attr.ib(default='')
582 582 # These get added to the phabchange's equivalents
583 583 addLines = attr.ib(default=0) # camelcase-required
584 584 delLines = attr.ib(default=0) # camelcase-required
585 585
586 586
587 587 @attr.s
588 588 class phabchange(object):
589 589 """Represents a Differential change, owns Differential hunks and owned by a
590 590 Differential diff. Each one represents one file in a diff.
591 591 """
592 592
593 593 currentPath = attr.ib(default=None) # camelcase-required
594 594 oldPath = attr.ib(default=None) # camelcase-required
595 595 awayPaths = attr.ib(default=attr.Factory(list)) # camelcase-required
596 596 metadata = attr.ib(default=attr.Factory(dict))
597 597 oldProperties = attr.ib(default=attr.Factory(dict)) # camelcase-required
598 598 newProperties = attr.ib(default=attr.Factory(dict)) # camelcase-required
599 599 type = attr.ib(default=DiffChangeType.CHANGE)
600 600 fileType = attr.ib(default=DiffFileType.TEXT) # camelcase-required
601 601 commitHash = attr.ib(default=None) # camelcase-required
602 602 addLines = attr.ib(default=0) # camelcase-required
603 603 delLines = attr.ib(default=0) # camelcase-required
604 604 hunks = attr.ib(default=attr.Factory(list))
605 605
606 606 def copynewmetadatatoold(self):
607 607 for key in list(self.metadata.keys()):
608 608 newkey = key.replace(b'new:', b'old:')
609 609 self.metadata[newkey] = self.metadata[key]
610 610
611 611 def addoldmode(self, value):
612 612 self.oldProperties[b'unix:filemode'] = value
613 613
614 614 def addnewmode(self, value):
615 615 self.newProperties[b'unix:filemode'] = value
616 616
617 617 def addhunk(self, hunk):
618 618 if not isinstance(hunk, phabhunk):
619 619 raise error.Abort(b'phabchange.addhunk only takes phabhunks')
620 620 self.hunks.append(pycompat.byteskwargs(attr.asdict(hunk)))
621 621 # It's useful to include these stats since the Phab web UI shows them,
622 622 # and uses them to estimate how large a change a Revision is. Also used
623 623 # in email subjects for the [+++--] bit.
624 624 self.addLines += hunk.addLines
625 625 self.delLines += hunk.delLines
626 626
627 627
628 628 @attr.s
629 629 class phabdiff(object):
630 630 """Represents a Differential diff, owns Differential changes. Corresponds
631 631 to a commit.
632 632 """
633 633
634 634 # Doesn't seem to be any reason to send this (output of uname -n)
635 635 sourceMachine = attr.ib(default=b'') # camelcase-required
636 636 sourcePath = attr.ib(default=b'/') # camelcase-required
637 637 sourceControlBaseRevision = attr.ib(default=b'0' * 40) # camelcase-required
638 638 sourceControlPath = attr.ib(default=b'/') # camelcase-required
639 639 sourceControlSystem = attr.ib(default=b'hg') # camelcase-required
640 640 branch = attr.ib(default=b'default')
641 641 bookmark = attr.ib(default=None)
642 642 creationMethod = attr.ib(default=b'phabsend') # camelcase-required
643 643 lintStatus = attr.ib(default=b'none') # camelcase-required
644 644 unitStatus = attr.ib(default=b'none') # camelcase-required
645 645 changes = attr.ib(default=attr.Factory(dict))
646 646 repositoryPHID = attr.ib(default=None) # camelcase-required
647 647
648 648 def addchange(self, change):
649 649 if not isinstance(change, phabchange):
650 650 raise error.Abort(b'phabdiff.addchange only takes phabchanges')
651 651 self.changes[change.currentPath] = pycompat.byteskwargs(
652 652 attr.asdict(change)
653 653 )
654 654
655 655
656 656 def maketext(pchange, ctx, fname):
657 657 """populate the phabchange for a text file"""
658 658 repo = ctx.repo()
659 659 fmatcher = match.exact([fname])
660 660 diffopts = mdiff.diffopts(git=True, context=32767)
661 661 _pfctx, _fctx, header, fhunks = next(
662 662 patch.diffhunks(repo, ctx.p1(), ctx, fmatcher, opts=diffopts)
663 663 )
664 664
665 665 for fhunk in fhunks:
666 666 (oldOffset, oldLength, newOffset, newLength), lines = fhunk
667 667 corpus = b''.join(lines[1:])
668 668 shunk = list(header)
669 669 shunk.extend(lines)
670 670 _mf, _mt, addLines, delLines, _hb = patch.diffstatsum(
671 671 patch.diffstatdata(util.iterlines(shunk))
672 672 )
673 673 pchange.addhunk(
674 674 phabhunk(
675 675 oldOffset,
676 676 oldLength,
677 677 newOffset,
678 678 newLength,
679 679 corpus,
680 680 addLines,
681 681 delLines,
682 682 )
683 683 )
684 684
685 685
686 686 def uploadchunks(fctx, fphid):
687 687 """upload large binary files as separate chunks.
688 688 Phab requests chunking over 8MiB, and splits into 4MiB chunks
689 689 """
690 690 ui = fctx.repo().ui
691 691 chunks = callconduit(ui, b'file.querychunks', {b'filePHID': fphid})
692 692 with ui.makeprogress(
693 693 _(b'uploading file chunks'), unit=_(b'chunks'), total=len(chunks)
694 694 ) as progress:
695 695 for chunk in chunks:
696 696 progress.increment()
697 697 if chunk[b'complete']:
698 698 continue
699 699 bstart = int(chunk[b'byteStart'])
700 700 bend = int(chunk[b'byteEnd'])
701 701 callconduit(
702 702 ui,
703 703 b'file.uploadchunk',
704 704 {
705 705 b'filePHID': fphid,
706 706 b'byteStart': bstart,
707 707 b'data': base64.b64encode(fctx.data()[bstart:bend]),
708 708 b'dataEncoding': b'base64',
709 709 },
710 710 )
711 711
712 712
713 713 def uploadfile(fctx):
714 714 """upload binary files to Phabricator"""
715 715 repo = fctx.repo()
716 716 ui = repo.ui
717 717 fname = fctx.path()
718 718 size = fctx.size()
719 719 fhash = pycompat.bytestr(hashlib.sha256(fctx.data()).hexdigest())
720 720
721 721 # an allocate call is required first to see if an upload is even required
722 722 # (Phab might already have it) and to determine if chunking is needed
723 723 allocateparams = {
724 724 b'name': fname,
725 725 b'contentLength': size,
726 726 b'contentHash': fhash,
727 727 }
728 728 filealloc = callconduit(ui, b'file.allocate', allocateparams)
729 729 fphid = filealloc[b'filePHID']
730 730
731 731 if filealloc[b'upload']:
732 732 ui.write(_(b'uploading %s\n') % bytes(fctx))
733 733 if not fphid:
734 734 uploadparams = {
735 735 b'name': fname,
736 736 b'data_base64': base64.b64encode(fctx.data()),
737 737 }
738 738 fphid = callconduit(ui, b'file.upload', uploadparams)
739 739 else:
740 740 uploadchunks(fctx, fphid)
741 741 else:
742 742 ui.debug(b'server already has %s\n' % bytes(fctx))
743 743
744 744 if not fphid:
745 745 raise error.Abort(b'Upload of %s failed.' % bytes(fctx))
746 746
747 747 return fphid
748 748
749 749
750 def addoldbinary(pchange, fctx, originalfname):
750 def addoldbinary(pchange, fctx):
751 751 """add the metadata for the previous version of a binary file to the
752 752 phabchange for the new version
753 753 """
754 oldfctx = fctx.p1()[originalfname]
754 oldfctx = fctx.p1()
755 755 if fctx.cmp(oldfctx):
756 756 # Files differ, add the old one
757 757 pchange.metadata[b'old:file:size'] = oldfctx.size()
758 758 mimeguess, _enc = mimetypes.guess_type(
759 759 encoding.unifromlocal(oldfctx.path())
760 760 )
761 761 if mimeguess:
762 762 pchange.metadata[b'old:file:mime-type'] = pycompat.bytestr(
763 763 mimeguess
764 764 )
765 765 fphid = uploadfile(oldfctx)
766 766 pchange.metadata[b'old:binary-phid'] = fphid
767 767 else:
768 768 # If it's left as IMAGE/BINARY web UI might try to display it
769 769 pchange.fileType = DiffFileType.TEXT
770 770 pchange.copynewmetadatatoold()
771 771
772 772
773 773 def makebinary(pchange, fctx):
774 774 """populate the phabchange for a binary file"""
775 775 pchange.fileType = DiffFileType.BINARY
776 776 fphid = uploadfile(fctx)
777 777 pchange.metadata[b'new:binary-phid'] = fphid
778 778 pchange.metadata[b'new:file:size'] = fctx.size()
779 779 mimeguess, _enc = mimetypes.guess_type(encoding.unifromlocal(fctx.path()))
780 780 if mimeguess:
781 781 mimeguess = pycompat.bytestr(mimeguess)
782 782 pchange.metadata[b'new:file:mime-type'] = mimeguess
783 783 if mimeguess.startswith(b'image/'):
784 784 pchange.fileType = DiffFileType.IMAGE
785 785
786 786
787 787 # Copied from mercurial/patch.py
788 788 gitmode = {b'l': b'120000', b'x': b'100755', b'': b'100644'}
789 789
790 790
791 791 def notutf8(fctx):
792 792 """detect non-UTF-8 text files since Phabricator requires them to be marked
793 793 as binary
794 794 """
795 795 try:
796 796 fctx.data().decode('utf-8')
797 797 if fctx.parents():
798 798 fctx.p1().data().decode('utf-8')
799 799 return False
800 800 except UnicodeDecodeError:
801 801 fctx.repo().ui.write(
802 802 _(b'file %s detected as non-UTF-8, marked as binary\n')
803 803 % fctx.path()
804 804 )
805 805 return True
806 806
807 807
808 808 def addremoved(pdiff, ctx, removed):
809 809 """add removed files to the phabdiff. Shouldn't include moves"""
810 810 for fname in removed:
811 811 pchange = phabchange(
812 812 currentPath=fname, oldPath=fname, type=DiffChangeType.DELETE
813 813 )
814 814 pchange.addoldmode(gitmode[ctx.p1()[fname].flags()])
815 815 fctx = ctx.p1()[fname]
816 816 if not (fctx.isbinary() or notutf8(fctx)):
817 817 maketext(pchange, ctx, fname)
818 818
819 819 pdiff.addchange(pchange)
820 820
821 821
822 822 def addmodified(pdiff, ctx, modified):
823 823 """add modified files to the phabdiff"""
824 824 for fname in modified:
825 825 fctx = ctx[fname]
826 826 pchange = phabchange(currentPath=fname, oldPath=fname)
827 827 filemode = gitmode[ctx[fname].flags()]
828 828 originalmode = gitmode[ctx.p1()[fname].flags()]
829 829 if filemode != originalmode:
830 830 pchange.addoldmode(originalmode)
831 831 pchange.addnewmode(filemode)
832 832
833 833 if fctx.isbinary() or notutf8(fctx):
834 834 makebinary(pchange, fctx)
835 addoldbinary(pchange, fctx, fname)
835 addoldbinary(pchange, fctx)
836 836 else:
837 837 maketext(pchange, ctx, fname)
838 838
839 839 pdiff.addchange(pchange)
840 840
841 841
842 842 def addadded(pdiff, ctx, added, removed):
843 843 """add file adds to the phabdiff, both new files and copies/moves"""
844 844 # Keep track of files that've been recorded as moved/copied, so if there are
845 845 # additional copies we can mark them (moves get removed from removed)
846 846 copiedchanges = {}
847 847 movedchanges = {}
848 848 for fname in added:
849 849 fctx = ctx[fname]
850 850 pchange = phabchange(currentPath=fname)
851 851
852 852 filemode = gitmode[ctx[fname].flags()]
853 853 renamed = fctx.renamed()
854 854
855 855 if renamed:
856 856 originalfname = renamed[0]
857 857 originalmode = gitmode[ctx.p1()[originalfname].flags()]
858 858 pchange.oldPath = originalfname
859 859
860 860 if originalfname in removed:
861 861 origpchange = phabchange(
862 862 currentPath=originalfname,
863 863 oldPath=originalfname,
864 864 type=DiffChangeType.MOVE_AWAY,
865 865 awayPaths=[fname],
866 866 )
867 867 movedchanges[originalfname] = origpchange
868 868 removed.remove(originalfname)
869 869 pchange.type = DiffChangeType.MOVE_HERE
870 870 elif originalfname in movedchanges:
871 871 movedchanges[originalfname].type = DiffChangeType.MULTICOPY
872 872 movedchanges[originalfname].awayPaths.append(fname)
873 873 pchange.type = DiffChangeType.COPY_HERE
874 874 else: # pure copy
875 875 if originalfname not in copiedchanges:
876 876 origpchange = phabchange(
877 877 currentPath=originalfname, type=DiffChangeType.COPY_AWAY
878 878 )
879 879 copiedchanges[originalfname] = origpchange
880 880 else:
881 881 origpchange = copiedchanges[originalfname]
882 882 origpchange.awayPaths.append(fname)
883 883 pchange.type = DiffChangeType.COPY_HERE
884 884
885 885 if filemode != originalmode:
886 886 pchange.addoldmode(originalmode)
887 887 pchange.addnewmode(filemode)
888 888 else: # Brand-new file
889 889 pchange.addnewmode(gitmode[fctx.flags()])
890 890 pchange.type = DiffChangeType.ADD
891 891
892 892 if fctx.isbinary() or notutf8(fctx):
893 893 makebinary(pchange, fctx)
894 894 if renamed:
895 895 addoldbinary(pchange, fctx, originalfname)
896 896 else:
897 897 maketext(pchange, ctx, fname)
898 898
899 899 pdiff.addchange(pchange)
900 900
901 901 for _path, copiedchange in copiedchanges.items():
902 902 pdiff.addchange(copiedchange)
903 903 for _path, movedchange in movedchanges.items():
904 904 pdiff.addchange(movedchange)
905 905
906 906
907 907 def creatediff(ctx):
908 908 """create a Differential Diff"""
909 909 repo = ctx.repo()
910 910 repophid = getrepophid(repo)
911 911 # Create a "Differential Diff" via "differential.creatediff" API
912 912 pdiff = phabdiff(
913 913 sourceControlBaseRevision=b'%s' % ctx.p1().hex(),
914 914 branch=b'%s' % ctx.branch(),
915 915 )
916 916 modified, added, removed, _d, _u, _i, _c = ctx.p1().status(ctx)
917 917 # addadded will remove moved files from removed, so addremoved won't get
918 918 # them
919 919 addadded(pdiff, ctx, added, removed)
920 920 addmodified(pdiff, ctx, modified)
921 921 addremoved(pdiff, ctx, removed)
922 922 if repophid:
923 923 pdiff.repositoryPHID = repophid
924 924 diff = callconduit(
925 925 repo.ui,
926 926 b'differential.creatediff',
927 927 pycompat.byteskwargs(attr.asdict(pdiff)),
928 928 )
929 929 if not diff:
930 930 raise error.Abort(_(b'cannot create diff for %s') % ctx)
931 931 return diff
932 932
933 933
934 934 def writediffproperties(ctx, diff):
935 935 """write metadata to diff so patches could be applied losslessly"""
936 936 # creatediff returns with a diffid but query returns with an id
937 937 diffid = diff.get(b'diffid', diff.get(b'id'))
938 938 params = {
939 939 b'diff_id': diffid,
940 940 b'name': b'hg:meta',
941 941 b'data': templatefilters.json(
942 942 {
943 943 b'user': ctx.user(),
944 944 b'date': b'%d %d' % ctx.date(),
945 945 b'branch': ctx.branch(),
946 946 b'node': ctx.hex(),
947 947 b'parent': ctx.p1().hex(),
948 948 }
949 949 ),
950 950 }
951 951 callconduit(ctx.repo().ui, b'differential.setdiffproperty', params)
952 952
953 953 params = {
954 954 b'diff_id': diffid,
955 955 b'name': b'local:commits',
956 956 b'data': templatefilters.json(
957 957 {
958 958 ctx.hex(): {
959 959 b'author': stringutil.person(ctx.user()),
960 960 b'authorEmail': stringutil.email(ctx.user()),
961 961 b'time': int(ctx.date()[0]),
962 962 b'commit': ctx.hex(),
963 963 b'parents': [ctx.p1().hex()],
964 964 b'branch': ctx.branch(),
965 965 },
966 966 }
967 967 ),
968 968 }
969 969 callconduit(ctx.repo().ui, b'differential.setdiffproperty', params)
970 970
971 971
972 972 def createdifferentialrevision(
973 973 ctx,
974 974 revid=None,
975 975 parentrevphid=None,
976 976 oldnode=None,
977 977 olddiff=None,
978 978 actions=None,
979 979 comment=None,
980 980 ):
981 981 """create or update a Differential Revision
982 982
983 983 If revid is None, create a new Differential Revision, otherwise update
984 984 revid. If parentrevphid is not None, set it as a dependency.
985 985
986 986 If oldnode is not None, check if the patch content (without commit message
987 987 and metadata) has changed before creating another diff.
988 988
989 989 If actions is not None, they will be appended to the transaction.
990 990 """
991 991 repo = ctx.repo()
992 992 if oldnode:
993 993 diffopts = mdiff.diffopts(git=True, context=32767)
994 994 oldctx = repo.unfiltered()[oldnode]
995 995 neednewdiff = getdiff(ctx, diffopts) != getdiff(oldctx, diffopts)
996 996 else:
997 997 neednewdiff = True
998 998
999 999 transactions = []
1000 1000 if neednewdiff:
1001 1001 diff = creatediff(ctx)
1002 1002 transactions.append({b'type': b'update', b'value': diff[b'phid']})
1003 1003 if comment:
1004 1004 transactions.append({b'type': b'comment', b'value': comment})
1005 1005 else:
1006 1006 # Even if we don't need to upload a new diff because the patch content
1007 1007 # does not change. We might still need to update its metadata so
1008 1008 # pushers could know the correct node metadata.
1009 1009 assert olddiff
1010 1010 diff = olddiff
1011 1011 writediffproperties(ctx, diff)
1012 1012
1013 1013 # Set the parent Revision every time, so commit re-ordering is picked-up
1014 1014 if parentrevphid:
1015 1015 transactions.append(
1016 1016 {b'type': b'parents.set', b'value': [parentrevphid]}
1017 1017 )
1018 1018
1019 1019 if actions:
1020 1020 transactions += actions
1021 1021
1022 1022 # Parse commit message and update related fields.
1023 1023 desc = ctx.description()
1024 1024 info = callconduit(
1025 1025 repo.ui, b'differential.parsecommitmessage', {b'corpus': desc}
1026 1026 )
1027 1027 for k, v in info[b'fields'].items():
1028 1028 if k in [b'title', b'summary', b'testPlan']:
1029 1029 transactions.append({b'type': k, b'value': v})
1030 1030
1031 1031 params = {b'transactions': transactions}
1032 1032 if revid is not None:
1033 1033 # Update an existing Differential Revision
1034 1034 params[b'objectIdentifier'] = revid
1035 1035
1036 1036 revision = callconduit(repo.ui, b'differential.revision.edit', params)
1037 1037 if not revision:
1038 1038 raise error.Abort(_(b'cannot create revision for %s') % ctx)
1039 1039
1040 1040 return revision, diff
1041 1041
1042 1042
1043 1043 def userphids(repo, names):
1044 1044 """convert user names to PHIDs"""
1045 1045 names = [name.lower() for name in names]
1046 1046 query = {b'constraints': {b'usernames': names}}
1047 1047 result = callconduit(repo.ui, b'user.search', query)
1048 1048 # username not found is not an error of the API. So check if we have missed
1049 1049 # some names here.
1050 1050 data = result[b'data']
1051 1051 resolved = set(entry[b'fields'][b'username'].lower() for entry in data)
1052 1052 unresolved = set(names) - resolved
1053 1053 if unresolved:
1054 1054 raise error.Abort(
1055 1055 _(b'unknown username: %s') % b' '.join(sorted(unresolved))
1056 1056 )
1057 1057 return [entry[b'phid'] for entry in data]
1058 1058
1059 1059
1060 1060 @vcrcommand(
1061 1061 b'phabsend',
1062 1062 [
1063 1063 (b'r', b'rev', [], _(b'revisions to send'), _(b'REV')),
1064 1064 (b'', b'amend', True, _(b'update commit messages')),
1065 1065 (b'', b'reviewer', [], _(b'specify reviewers')),
1066 1066 (b'', b'blocker', [], _(b'specify blocking reviewers')),
1067 1067 (
1068 1068 b'm',
1069 1069 b'comment',
1070 1070 b'',
1071 1071 _(b'add a comment to Revisions with new/updated Diffs'),
1072 1072 ),
1073 1073 (b'', b'confirm', None, _(b'ask for confirmation before sending')),
1074 1074 ],
1075 1075 _(b'REV [OPTIONS]'),
1076 1076 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1077 1077 )
1078 1078 def phabsend(ui, repo, *revs, **opts):
1079 1079 """upload changesets to Phabricator
1080 1080
1081 1081 If there are multiple revisions specified, they will be send as a stack
1082 1082 with a linear dependencies relationship using the order specified by the
1083 1083 revset.
1084 1084
1085 1085 For the first time uploading changesets, local tags will be created to
1086 1086 maintain the association. After the first time, phabsend will check
1087 1087 obsstore and tags information so it can figure out whether to update an
1088 1088 existing Differential Revision, or create a new one.
1089 1089
1090 1090 If --amend is set, update commit messages so they have the
1091 1091 ``Differential Revision`` URL, remove related tags. This is similar to what
1092 1092 arcanist will do, and is more desired in author-push workflows. Otherwise,
1093 1093 use local tags to record the ``Differential Revision`` association.
1094 1094
1095 1095 The --confirm option lets you confirm changesets before sending them. You
1096 1096 can also add following to your configuration file to make it default
1097 1097 behaviour::
1098 1098
1099 1099 [phabsend]
1100 1100 confirm = true
1101 1101
1102 1102 phabsend will check obsstore and the above association to decide whether to
1103 1103 update an existing Differential Revision, or create a new one.
1104 1104 """
1105 1105 opts = pycompat.byteskwargs(opts)
1106 1106 revs = list(revs) + opts.get(b'rev', [])
1107 1107 revs = scmutil.revrange(repo, revs)
1108 1108 revs.sort() # ascending order to preserve topological parent/child in phab
1109 1109
1110 1110 if not revs:
1111 1111 raise error.Abort(_(b'phabsend requires at least one changeset'))
1112 1112 if opts.get(b'amend'):
1113 1113 cmdutil.checkunfinished(repo)
1114 1114
1115 1115 # {newnode: (oldnode, olddiff, olddrev}
1116 1116 oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
1117 1117
1118 1118 confirm = ui.configbool(b'phabsend', b'confirm')
1119 1119 confirm |= bool(opts.get(b'confirm'))
1120 1120 if confirm:
1121 1121 confirmed = _confirmbeforesend(repo, revs, oldmap)
1122 1122 if not confirmed:
1123 1123 raise error.Abort(_(b'phabsend cancelled'))
1124 1124
1125 1125 actions = []
1126 1126 reviewers = opts.get(b'reviewer', [])
1127 1127 blockers = opts.get(b'blocker', [])
1128 1128 phids = []
1129 1129 if reviewers:
1130 1130 phids.extend(userphids(repo, reviewers))
1131 1131 if blockers:
1132 1132 phids.extend(
1133 1133 map(lambda phid: b'blocking(%s)' % phid, userphids(repo, blockers))
1134 1134 )
1135 1135 if phids:
1136 1136 actions.append({b'type': b'reviewers.add', b'value': phids})
1137 1137
1138 1138 drevids = [] # [int]
1139 1139 diffmap = {} # {newnode: diff}
1140 1140
1141 1141 # Send patches one by one so we know their Differential Revision PHIDs and
1142 1142 # can provide dependency relationship
1143 1143 lastrevphid = None
1144 1144 for rev in revs:
1145 1145 ui.debug(b'sending rev %d\n' % rev)
1146 1146 ctx = repo[rev]
1147 1147
1148 1148 # Get Differential Revision ID
1149 1149 oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None))
1150 1150 if oldnode != ctx.node() or opts.get(b'amend'):
1151 1151 # Create or update Differential Revision
1152 1152 revision, diff = createdifferentialrevision(
1153 1153 ctx,
1154 1154 revid,
1155 1155 lastrevphid,
1156 1156 oldnode,
1157 1157 olddiff,
1158 1158 actions,
1159 1159 opts.get(b'comment'),
1160 1160 )
1161 1161 diffmap[ctx.node()] = diff
1162 1162 newrevid = int(revision[b'object'][b'id'])
1163 1163 newrevphid = revision[b'object'][b'phid']
1164 1164 if revid:
1165 1165 action = b'updated'
1166 1166 else:
1167 1167 action = b'created'
1168 1168
1169 1169 # Create a local tag to note the association, if commit message
1170 1170 # does not have it already
1171 1171 m = _differentialrevisiondescre.search(ctx.description())
1172 1172 if not m or int(m.group('id')) != newrevid:
1173 1173 tagname = b'D%d' % newrevid
1174 1174 tags.tag(
1175 1175 repo,
1176 1176 tagname,
1177 1177 ctx.node(),
1178 1178 message=None,
1179 1179 user=None,
1180 1180 date=None,
1181 1181 local=True,
1182 1182 )
1183 1183 else:
1184 1184 # Nothing changed. But still set "newrevphid" so the next revision
1185 1185 # could depend on this one and "newrevid" for the summary line.
1186 1186 newrevphid = querydrev(repo, b'%d' % revid)[0][b'phid']
1187 1187 newrevid = revid
1188 1188 action = b'skipped'
1189 1189
1190 1190 actiondesc = ui.label(
1191 1191 {
1192 1192 b'created': _(b'created'),
1193 1193 b'skipped': _(b'skipped'),
1194 1194 b'updated': _(b'updated'),
1195 1195 }[action],
1196 1196 b'phabricator.action.%s' % action,
1197 1197 )
1198 1198 drevdesc = ui.label(b'D%d' % newrevid, b'phabricator.drev')
1199 1199 nodedesc = ui.label(bytes(ctx), b'phabricator.node')
1200 1200 desc = ui.label(ctx.description().split(b'\n')[0], b'phabricator.desc')
1201 1201 ui.write(
1202 1202 _(b'%s - %s - %s: %s\n') % (drevdesc, actiondesc, nodedesc, desc)
1203 1203 )
1204 1204 drevids.append(newrevid)
1205 1205 lastrevphid = newrevphid
1206 1206
1207 1207 # Update commit messages and remove tags
1208 1208 if opts.get(b'amend'):
1209 1209 unfi = repo.unfiltered()
1210 1210 drevs = callconduit(ui, b'differential.query', {b'ids': drevids})
1211 1211 with repo.wlock(), repo.lock(), repo.transaction(b'phabsend'):
1212 1212 wnode = unfi[b'.'].node()
1213 1213 mapping = {} # {oldnode: [newnode]}
1214 1214 for i, rev in enumerate(revs):
1215 1215 old = unfi[rev]
1216 1216 drevid = drevids[i]
1217 1217 drev = [d for d in drevs if int(d[b'id']) == drevid][0]
1218 1218 newdesc = getdescfromdrev(drev)
1219 1219 # Make sure commit message contain "Differential Revision"
1220 1220 if old.description() != newdesc:
1221 1221 if old.phase() == phases.public:
1222 1222 ui.warn(
1223 1223 _(b"warning: not updating public commit %s\n")
1224 1224 % scmutil.formatchangeid(old)
1225 1225 )
1226 1226 continue
1227 1227 parents = [
1228 1228 mapping.get(old.p1().node(), (old.p1(),))[0],
1229 1229 mapping.get(old.p2().node(), (old.p2(),))[0],
1230 1230 ]
1231 1231 new = context.metadataonlyctx(
1232 1232 repo,
1233 1233 old,
1234 1234 parents=parents,
1235 1235 text=newdesc,
1236 1236 user=old.user(),
1237 1237 date=old.date(),
1238 1238 extra=old.extra(),
1239 1239 )
1240 1240
1241 1241 newnode = new.commit()
1242 1242
1243 1243 mapping[old.node()] = [newnode]
1244 1244 # Update diff property
1245 1245 # If it fails just warn and keep going, otherwise the DREV
1246 1246 # associations will be lost
1247 1247 try:
1248 1248 writediffproperties(unfi[newnode], diffmap[old.node()])
1249 1249 except util.urlerr.urlerror:
1250 1250 ui.warnnoi18n(
1251 1251 b'Failed to update metadata for D%d\n' % drevid
1252 1252 )
1253 1253 # Remove local tags since it's no longer necessary
1254 1254 tagname = b'D%d' % drevid
1255 1255 if tagname in repo.tags():
1256 1256 tags.tag(
1257 1257 repo,
1258 1258 tagname,
1259 1259 nullid,
1260 1260 message=None,
1261 1261 user=None,
1262 1262 date=None,
1263 1263 local=True,
1264 1264 )
1265 1265 scmutil.cleanupnodes(repo, mapping, b'phabsend', fixphase=True)
1266 1266 if wnode in mapping:
1267 1267 unfi.setparents(mapping[wnode][0])
1268 1268
1269 1269
1270 1270 # Map from "hg:meta" keys to header understood by "hg import". The order is
1271 1271 # consistent with "hg export" output.
1272 1272 _metanamemap = util.sortdict(
1273 1273 [
1274 1274 (b'user', b'User'),
1275 1275 (b'date', b'Date'),
1276 1276 (b'branch', b'Branch'),
1277 1277 (b'node', b'Node ID'),
1278 1278 (b'parent', b'Parent '),
1279 1279 ]
1280 1280 )
1281 1281
1282 1282
1283 1283 def _confirmbeforesend(repo, revs, oldmap):
1284 1284 url, token = readurltoken(repo.ui)
1285 1285 ui = repo.ui
1286 1286 for rev in revs:
1287 1287 ctx = repo[rev]
1288 1288 desc = ctx.description().splitlines()[0]
1289 1289 oldnode, olddiff, drevid = oldmap.get(ctx.node(), (None, None, None))
1290 1290 if drevid:
1291 1291 drevdesc = ui.label(b'D%d' % drevid, b'phabricator.drev')
1292 1292 else:
1293 1293 drevdesc = ui.label(_(b'NEW'), b'phabricator.drev')
1294 1294
1295 1295 ui.write(
1296 1296 _(b'%s - %s: %s\n')
1297 1297 % (
1298 1298 drevdesc,
1299 1299 ui.label(bytes(ctx), b'phabricator.node'),
1300 1300 ui.label(desc, b'phabricator.desc'),
1301 1301 )
1302 1302 )
1303 1303
1304 1304 if ui.promptchoice(
1305 1305 _(b'Send the above changes to %s (yn)?$$ &Yes $$ &No') % url
1306 1306 ):
1307 1307 return False
1308 1308
1309 1309 return True
1310 1310
1311 1311
1312 1312 _knownstatusnames = {
1313 1313 b'accepted',
1314 1314 b'needsreview',
1315 1315 b'needsrevision',
1316 1316 b'closed',
1317 1317 b'abandoned',
1318 1318 b'changesplanned',
1319 1319 }
1320 1320
1321 1321
1322 1322 def _getstatusname(drev):
1323 1323 """get normalized status name from a Differential Revision"""
1324 1324 return drev[b'statusName'].replace(b' ', b'').lower()
1325 1325
1326 1326
1327 1327 # Small language to specify differential revisions. Support symbols: (), :X,
1328 1328 # +, and -.
1329 1329
1330 1330 _elements = {
1331 1331 # token-type: binding-strength, primary, prefix, infix, suffix
1332 1332 b'(': (12, None, (b'group', 1, b')'), None, None),
1333 1333 b':': (8, None, (b'ancestors', 8), None, None),
1334 1334 b'&': (5, None, None, (b'and_', 5), None),
1335 1335 b'+': (4, None, None, (b'add', 4), None),
1336 1336 b'-': (4, None, None, (b'sub', 4), None),
1337 1337 b')': (0, None, None, None, None),
1338 1338 b'symbol': (0, b'symbol', None, None, None),
1339 1339 b'end': (0, None, None, None, None),
1340 1340 }
1341 1341
1342 1342
1343 1343 def _tokenize(text):
1344 1344 view = memoryview(text) # zero-copy slice
1345 1345 special = b'():+-& '
1346 1346 pos = 0
1347 1347 length = len(text)
1348 1348 while pos < length:
1349 1349 symbol = b''.join(
1350 1350 itertools.takewhile(
1351 1351 lambda ch: ch not in special, pycompat.iterbytestr(view[pos:])
1352 1352 )
1353 1353 )
1354 1354 if symbol:
1355 1355 yield (b'symbol', symbol, pos)
1356 1356 pos += len(symbol)
1357 1357 else: # special char, ignore space
1358 1358 if text[pos : pos + 1] != b' ':
1359 1359 yield (text[pos : pos + 1], None, pos)
1360 1360 pos += 1
1361 1361 yield (b'end', None, pos)
1362 1362
1363 1363
1364 1364 def _parse(text):
1365 1365 tree, pos = parser.parser(_elements).parse(_tokenize(text))
1366 1366 if pos != len(text):
1367 1367 raise error.ParseError(b'invalid token', pos)
1368 1368 return tree
1369 1369
1370 1370
1371 1371 def _parsedrev(symbol):
1372 1372 """str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None"""
1373 1373 if symbol.startswith(b'D') and symbol[1:].isdigit():
1374 1374 return int(symbol[1:])
1375 1375 if symbol.isdigit():
1376 1376 return int(symbol)
1377 1377
1378 1378
1379 1379 def _prefetchdrevs(tree):
1380 1380 """return ({single-drev-id}, {ancestor-drev-id}) to prefetch"""
1381 1381 drevs = set()
1382 1382 ancestordrevs = set()
1383 1383 op = tree[0]
1384 1384 if op == b'symbol':
1385 1385 r = _parsedrev(tree[1])
1386 1386 if r:
1387 1387 drevs.add(r)
1388 1388 elif op == b'ancestors':
1389 1389 r, a = _prefetchdrevs(tree[1])
1390 1390 drevs.update(r)
1391 1391 ancestordrevs.update(r)
1392 1392 ancestordrevs.update(a)
1393 1393 else:
1394 1394 for t in tree[1:]:
1395 1395 r, a = _prefetchdrevs(t)
1396 1396 drevs.update(r)
1397 1397 ancestordrevs.update(a)
1398 1398 return drevs, ancestordrevs
1399 1399
1400 1400
1401 1401 def querydrev(repo, spec):
1402 1402 """return a list of "Differential Revision" dicts
1403 1403
1404 1404 spec is a string using a simple query language, see docstring in phabread
1405 1405 for details.
1406 1406
1407 1407 A "Differential Revision dict" looks like:
1408 1408
1409 1409 {
1410 1410 "id": "2",
1411 1411 "phid": "PHID-DREV-672qvysjcczopag46qty",
1412 1412 "title": "example",
1413 1413 "uri": "https://phab.example.com/D2",
1414 1414 "dateCreated": "1499181406",
1415 1415 "dateModified": "1499182103",
1416 1416 "authorPHID": "PHID-USER-tv3ohwc4v4jeu34otlye",
1417 1417 "status": "0",
1418 1418 "statusName": "Needs Review",
1419 1419 "properties": [],
1420 1420 "branch": null,
1421 1421 "summary": "",
1422 1422 "testPlan": "",
1423 1423 "lineCount": "2",
1424 1424 "activeDiffPHID": "PHID-DIFF-xoqnjkobbm6k4dk6hi72",
1425 1425 "diffs": [
1426 1426 "3",
1427 1427 "4",
1428 1428 ],
1429 1429 "commits": [],
1430 1430 "reviewers": [],
1431 1431 "ccs": [],
1432 1432 "hashes": [],
1433 1433 "auxiliary": {
1434 1434 "phabricator:projects": [],
1435 1435 "phabricator:depends-on": [
1436 1436 "PHID-DREV-gbapp366kutjebt7agcd"
1437 1437 ]
1438 1438 },
1439 1439 "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv",
1440 1440 "sourcePath": null
1441 1441 }
1442 1442 """
1443 1443
1444 1444 def fetch(params):
1445 1445 """params -> single drev or None"""
1446 1446 key = (params.get(b'ids') or params.get(b'phids') or [None])[0]
1447 1447 if key in prefetched:
1448 1448 return prefetched[key]
1449 1449 drevs = callconduit(repo.ui, b'differential.query', params)
1450 1450 # Fill prefetched with the result
1451 1451 for drev in drevs:
1452 1452 prefetched[drev[b'phid']] = drev
1453 1453 prefetched[int(drev[b'id'])] = drev
1454 1454 if key not in prefetched:
1455 1455 raise error.Abort(
1456 1456 _(b'cannot get Differential Revision %r') % params
1457 1457 )
1458 1458 return prefetched[key]
1459 1459
1460 1460 def getstack(topdrevids):
1461 1461 """given a top, get a stack from the bottom, [id] -> [id]"""
1462 1462 visited = set()
1463 1463 result = []
1464 1464 queue = [{b'ids': [i]} for i in topdrevids]
1465 1465 while queue:
1466 1466 params = queue.pop()
1467 1467 drev = fetch(params)
1468 1468 if drev[b'id'] in visited:
1469 1469 continue
1470 1470 visited.add(drev[b'id'])
1471 1471 result.append(int(drev[b'id']))
1472 1472 auxiliary = drev.get(b'auxiliary', {})
1473 1473 depends = auxiliary.get(b'phabricator:depends-on', [])
1474 1474 for phid in depends:
1475 1475 queue.append({b'phids': [phid]})
1476 1476 result.reverse()
1477 1477 return smartset.baseset(result)
1478 1478
1479 1479 # Initialize prefetch cache
1480 1480 prefetched = {} # {id or phid: drev}
1481 1481
1482 1482 tree = _parse(spec)
1483 1483 drevs, ancestordrevs = _prefetchdrevs(tree)
1484 1484
1485 1485 # developer config: phabricator.batchsize
1486 1486 batchsize = repo.ui.configint(b'phabricator', b'batchsize')
1487 1487
1488 1488 # Prefetch Differential Revisions in batch
1489 1489 tofetch = set(drevs)
1490 1490 for r in ancestordrevs:
1491 1491 tofetch.update(range(max(1, r - batchsize), r + 1))
1492 1492 if drevs:
1493 1493 fetch({b'ids': list(tofetch)})
1494 1494 validids = sorted(set(getstack(list(ancestordrevs))) | set(drevs))
1495 1495
1496 1496 # Walk through the tree, return smartsets
1497 1497 def walk(tree):
1498 1498 op = tree[0]
1499 1499 if op == b'symbol':
1500 1500 drev = _parsedrev(tree[1])
1501 1501 if drev:
1502 1502 return smartset.baseset([drev])
1503 1503 elif tree[1] in _knownstatusnames:
1504 1504 drevs = [
1505 1505 r
1506 1506 for r in validids
1507 1507 if _getstatusname(prefetched[r]) == tree[1]
1508 1508 ]
1509 1509 return smartset.baseset(drevs)
1510 1510 else:
1511 1511 raise error.Abort(_(b'unknown symbol: %s') % tree[1])
1512 1512 elif op in {b'and_', b'add', b'sub'}:
1513 1513 assert len(tree) == 3
1514 1514 return getattr(operator, op)(walk(tree[1]), walk(tree[2]))
1515 1515 elif op == b'group':
1516 1516 return walk(tree[1])
1517 1517 elif op == b'ancestors':
1518 1518 return getstack(walk(tree[1]))
1519 1519 else:
1520 1520 raise error.ProgrammingError(b'illegal tree: %r' % tree)
1521 1521
1522 1522 return [prefetched[r] for r in walk(tree)]
1523 1523
1524 1524
1525 1525 def getdescfromdrev(drev):
1526 1526 """get description (commit message) from "Differential Revision"
1527 1527
1528 1528 This is similar to differential.getcommitmessage API. But we only care
1529 1529 about limited fields: title, summary, test plan, and URL.
1530 1530 """
1531 1531 title = drev[b'title']
1532 1532 summary = drev[b'summary'].rstrip()
1533 1533 testplan = drev[b'testPlan'].rstrip()
1534 1534 if testplan:
1535 1535 testplan = b'Test Plan:\n%s' % testplan
1536 1536 uri = b'Differential Revision: %s' % drev[b'uri']
1537 1537 return b'\n\n'.join(filter(None, [title, summary, testplan, uri]))
1538 1538
1539 1539
1540 1540 def getdiffmeta(diff):
1541 1541 """get commit metadata (date, node, user, p1) from a diff object
1542 1542
1543 1543 The metadata could be "hg:meta", sent by phabsend, like:
1544 1544
1545 1545 "properties": {
1546 1546 "hg:meta": {
1547 1547 "date": "1499571514 25200",
1548 1548 "node": "98c08acae292b2faf60a279b4189beb6cff1414d",
1549 1549 "user": "Foo Bar <foo@example.com>",
1550 1550 "parent": "6d0abad76b30e4724a37ab8721d630394070fe16"
1551 1551 }
1552 1552 }
1553 1553
1554 1554 Or converted from "local:commits", sent by "arc", like:
1555 1555
1556 1556 "properties": {
1557 1557 "local:commits": {
1558 1558 "98c08acae292b2faf60a279b4189beb6cff1414d": {
1559 1559 "author": "Foo Bar",
1560 1560 "time": 1499546314,
1561 1561 "branch": "default",
1562 1562 "tag": "",
1563 1563 "commit": "98c08acae292b2faf60a279b4189beb6cff1414d",
1564 1564 "rev": "98c08acae292b2faf60a279b4189beb6cff1414d",
1565 1565 "local": "1000",
1566 1566 "parents": ["6d0abad76b30e4724a37ab8721d630394070fe16"],
1567 1567 "summary": "...",
1568 1568 "message": "...",
1569 1569 "authorEmail": "foo@example.com"
1570 1570 }
1571 1571 }
1572 1572 }
1573 1573
1574 1574 Note: metadata extracted from "local:commits" will lose time zone
1575 1575 information.
1576 1576 """
1577 1577 props = diff.get(b'properties') or {}
1578 1578 meta = props.get(b'hg:meta')
1579 1579 if not meta:
1580 1580 if props.get(b'local:commits'):
1581 1581 commit = sorted(props[b'local:commits'].values())[0]
1582 1582 meta = {}
1583 1583 if b'author' in commit and b'authorEmail' in commit:
1584 1584 meta[b'user'] = b'%s <%s>' % (
1585 1585 commit[b'author'],
1586 1586 commit[b'authorEmail'],
1587 1587 )
1588 1588 if b'time' in commit:
1589 1589 meta[b'date'] = b'%d 0' % int(commit[b'time'])
1590 1590 if b'branch' in commit:
1591 1591 meta[b'branch'] = commit[b'branch']
1592 1592 node = commit.get(b'commit', commit.get(b'rev'))
1593 1593 if node:
1594 1594 meta[b'node'] = node
1595 1595 if len(commit.get(b'parents', ())) >= 1:
1596 1596 meta[b'parent'] = commit[b'parents'][0]
1597 1597 else:
1598 1598 meta = {}
1599 1599 if b'date' not in meta and b'dateCreated' in diff:
1600 1600 meta[b'date'] = b'%s 0' % diff[b'dateCreated']
1601 1601 if b'branch' not in meta and diff.get(b'branch'):
1602 1602 meta[b'branch'] = diff[b'branch']
1603 1603 if b'parent' not in meta and diff.get(b'sourceControlBaseRevision'):
1604 1604 meta[b'parent'] = diff[b'sourceControlBaseRevision']
1605 1605 return meta
1606 1606
1607 1607
1608 1608 def readpatch(repo, drevs, write):
1609 1609 """generate plain-text patch readable by 'hg import'
1610 1610
1611 1611 write is usually ui.write. drevs is what "querydrev" returns, results of
1612 1612 "differential.query".
1613 1613 """
1614 1614 # Prefetch hg:meta property for all diffs
1615 1615 diffids = sorted(set(max(int(v) for v in drev[b'diffs']) for drev in drevs))
1616 1616 diffs = callconduit(repo.ui, b'differential.querydiffs', {b'ids': diffids})
1617 1617
1618 1618 # Generate patch for each drev
1619 1619 for drev in drevs:
1620 1620 repo.ui.note(_(b'reading D%s\n') % drev[b'id'])
1621 1621
1622 1622 diffid = max(int(v) for v in drev[b'diffs'])
1623 1623 body = callconduit(
1624 1624 repo.ui, b'differential.getrawdiff', {b'diffID': diffid}
1625 1625 )
1626 1626 desc = getdescfromdrev(drev)
1627 1627 header = b'# HG changeset patch\n'
1628 1628
1629 1629 # Try to preserve metadata from hg:meta property. Write hg patch
1630 1630 # headers that can be read by the "import" command. See patchheadermap
1631 1631 # and extract in mercurial/patch.py for supported headers.
1632 1632 meta = getdiffmeta(diffs[b'%d' % diffid])
1633 1633 for k in _metanamemap.keys():
1634 1634 if k in meta:
1635 1635 header += b'# %s %s\n' % (_metanamemap[k], meta[k])
1636 1636
1637 1637 content = b'%s%s\n%s' % (header, desc, body)
1638 1638 write(content)
1639 1639
1640 1640
1641 1641 @vcrcommand(
1642 1642 b'phabread',
1643 1643 [(b'', b'stack', False, _(b'read dependencies'))],
1644 1644 _(b'DREVSPEC [OPTIONS]'),
1645 1645 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1646 1646 )
1647 1647 def phabread(ui, repo, spec, **opts):
1648 1648 """print patches from Phabricator suitable for importing
1649 1649
1650 1650 DREVSPEC could be a Differential Revision identity, like ``D123``, or just
1651 1651 the number ``123``. It could also have common operators like ``+``, ``-``,
1652 1652 ``&``, ``(``, ``)`` for complex queries. Prefix ``:`` could be used to
1653 1653 select a stack.
1654 1654
1655 1655 ``abandoned``, ``accepted``, ``closed``, ``needsreview``, ``needsrevision``
1656 1656 could be used to filter patches by status. For performance reason, they
1657 1657 only represent a subset of non-status selections and cannot be used alone.
1658 1658
1659 1659 For example, ``:D6+8-(2+D4)`` selects a stack up to D6, plus D8 and exclude
1660 1660 D2 and D4. ``:D9 & needsreview`` selects "Needs Review" revisions in a
1661 1661 stack up to D9.
1662 1662
1663 1663 If --stack is given, follow dependencies information and read all patches.
1664 1664 It is equivalent to the ``:`` operator.
1665 1665 """
1666 1666 opts = pycompat.byteskwargs(opts)
1667 1667 if opts.get(b'stack'):
1668 1668 spec = b':(%s)' % spec
1669 1669 drevs = querydrev(repo, spec)
1670 1670 readpatch(repo, drevs, ui.write)
1671 1671
1672 1672
1673 1673 @vcrcommand(
1674 1674 b'phabupdate',
1675 1675 [
1676 1676 (b'', b'accept', False, _(b'accept revisions')),
1677 1677 (b'', b'reject', False, _(b'reject revisions')),
1678 1678 (b'', b'abandon', False, _(b'abandon revisions')),
1679 1679 (b'', b'reclaim', False, _(b'reclaim revisions')),
1680 1680 (b'm', b'comment', b'', _(b'comment on the last revision')),
1681 1681 ],
1682 1682 _(b'DREVSPEC [OPTIONS]'),
1683 1683 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1684 1684 )
1685 1685 def phabupdate(ui, repo, spec, **opts):
1686 1686 """update Differential Revision in batch
1687 1687
1688 1688 DREVSPEC selects revisions. See :hg:`help phabread` for its usage.
1689 1689 """
1690 1690 opts = pycompat.byteskwargs(opts)
1691 1691 flags = [n for n in b'accept reject abandon reclaim'.split() if opts.get(n)]
1692 1692 if len(flags) > 1:
1693 1693 raise error.Abort(_(b'%s cannot be used together') % b', '.join(flags))
1694 1694
1695 1695 actions = []
1696 1696 for f in flags:
1697 1697 actions.append({b'type': f, b'value': True})
1698 1698
1699 1699 drevs = querydrev(repo, spec)
1700 1700 for i, drev in enumerate(drevs):
1701 1701 if i + 1 == len(drevs) and opts.get(b'comment'):
1702 1702 actions.append({b'type': b'comment', b'value': opts[b'comment']})
1703 1703 if actions:
1704 1704 params = {
1705 1705 b'objectIdentifier': drev[b'phid'],
1706 1706 b'transactions': actions,
1707 1707 }
1708 1708 callconduit(ui, b'differential.revision.edit', params)
1709 1709
1710 1710
1711 1711 @eh.templatekeyword(b'phabreview', requires={b'ctx'})
1712 1712 def template_review(context, mapping):
1713 1713 """:phabreview: Object describing the review for this changeset.
1714 1714 Has attributes `url` and `id`.
1715 1715 """
1716 1716 ctx = context.resource(mapping, b'ctx')
1717 1717 m = _differentialrevisiondescre.search(ctx.description())
1718 1718 if m:
1719 1719 return templateutil.hybriddict(
1720 1720 {b'url': m.group('url'), b'id': b"D%s" % m.group('id'),}
1721 1721 )
1722 1722 else:
1723 1723 tags = ctx.repo().nodetags(ctx.node())
1724 1724 for t in tags:
1725 1725 if _differentialrevisiontagre.match(t):
1726 1726 url = ctx.repo().ui.config(b'phabricator', b'url')
1727 1727 if not url.endswith(b'/'):
1728 1728 url += b'/'
1729 1729 url += t
1730 1730
1731 1731 return templateutil.hybriddict({b'url': url, b'id': t,})
1732 1732 return None
1733 1733
1734 1734
1735 1735 @eh.templatekeyword(b'phabstatus', requires={b'ctx', b'repo', b'ui'})
1736 1736 def template_status(context, mapping):
1737 1737 """:phabstatus: String. Status of Phabricator differential.
1738 1738 """
1739 1739 ctx = context.resource(mapping, b'ctx')
1740 1740 repo = context.resource(mapping, b'repo')
1741 1741 ui = context.resource(mapping, b'ui')
1742 1742
1743 1743 rev = ctx.rev()
1744 1744 try:
1745 1745 drevid = getdrevmap(repo, [rev])[rev]
1746 1746 except KeyError:
1747 1747 return None
1748 1748 drevs = callconduit(ui, b'differential.query', {b'ids': [drevid]})
1749 1749 for drev in drevs:
1750 1750 if int(drev[b'id']) == drevid:
1751 1751 return templateutil.hybriddict(
1752 1752 {b'url': drev[b'uri'], b'status': drev[b'statusName'],}
1753 1753 )
1754 1754 return None
1755 1755
1756 1756
1757 1757 @show.showview(b'phabstatus', csettopic=b'work')
1758 1758 def phabstatusshowview(ui, repo, displayer):
1759 1759 """Phabricator differiential status"""
1760 1760 revs = repo.revs('sort(_underway(), topo)')
1761 1761 drevmap = getdrevmap(repo, revs)
1762 1762 unknownrevs, drevids, revsbydrevid = [], set([]), {}
1763 1763 for rev, drevid in pycompat.iteritems(drevmap):
1764 1764 if drevid is not None:
1765 1765 drevids.add(drevid)
1766 1766 revsbydrevid.setdefault(drevid, set([])).add(rev)
1767 1767 else:
1768 1768 unknownrevs.append(rev)
1769 1769
1770 1770 drevs = callconduit(ui, b'differential.query', {b'ids': list(drevids)})
1771 1771 drevsbyrev = {}
1772 1772 for drev in drevs:
1773 1773 for rev in revsbydrevid[int(drev[b'id'])]:
1774 1774 drevsbyrev[rev] = drev
1775 1775
1776 1776 def phabstatus(ctx):
1777 1777 drev = drevsbyrev[ctx.rev()]
1778 1778 status = ui.label(
1779 1779 b'%(statusName)s' % drev,
1780 1780 b'phabricator.status.%s' % _getstatusname(drev),
1781 1781 )
1782 1782 ui.write(b"\n%s %s\n" % (drev[b'uri'], status))
1783 1783
1784 1784 revs -= smartset.baseset(unknownrevs)
1785 1785 revdag = graphmod.dagwalker(repo, revs)
1786 1786
1787 1787 ui.setconfig(b'experimental', b'graphshorten', True)
1788 1788 displayer._exthook = phabstatus
1789 1789 nodelen = show.longestshortest(repo, revs)
1790 1790 logcmdutil.displaygraph(
1791 1791 ui,
1792 1792 repo,
1793 1793 revdag,
1794 1794 displayer,
1795 1795 graphmod.asciiedges,
1796 1796 props={b'nodelen': nodelen},
1797 1797 )
@@ -1,246 +1,263 b''
1 1 #require vcr
2 2 $ cat >> $HGRCPATH <<EOF
3 3 > [extensions]
4 4 > phabricator =
5 5 > EOF
6 6 $ hg init repo
7 7 $ cd repo
8 8 $ cat >> .hg/hgrc <<EOF
9 9 > [phabricator]
10 10 > url = https://phab.mercurial-scm.org/
11 11 > callsign = HG
12 12 >
13 13 > [auth]
14 14 > hgphab.schemes = https
15 15 > hgphab.prefix = phab.mercurial-scm.org
16 16 > # When working on the extension and making phabricator interaction
17 17 > # changes, edit this to be a real phabricator token. When done, edit
18 18 > # it back. The VCR transcripts will be auto-sanitised to replace your real
19 19 > # token with this value.
20 20 > hgphab.phabtoken = cli-hahayouwish
21 21 > EOF
22 22 $ VCR="$TESTDIR/phabricator"
23 23
24 24 Error is handled reasonably. We override the phabtoken here so that
25 25 when you're developing changes to phabricator.py you can edit the
26 26 above config and have a real token in the test but not have to edit
27 27 this test.
28 28 $ hg phabread --config auth.hgphab.phabtoken=cli-notavalidtoken \
29 29 > --test-vcr "$VCR/phabread-conduit-error.json" D4480 | head
30 30 abort: Conduit Error (ERR-INVALID-AUTH): API token "cli-notavalidtoken" has the wrong length. API tokens should be 32 characters long.
31 31
32 32 Basic phabread:
33 33 $ hg phabread --test-vcr "$VCR/phabread-4480.json" D4480 | head
34 34 # HG changeset patch
35 35 # Date 1536771503 0
36 36 # Parent a5de21c9e3703f8e8eb064bd7d893ff2f703c66a
37 37 exchangev2: start to implement pull with wire protocol v2
38 38
39 39 Wire protocol version 2 will take a substantially different
40 40 approach to exchange than version 1 (at least as far as pulling
41 41 is concerned).
42 42
43 43 This commit establishes a new exchangev2 module for holding
44 44
45 45 phabupdate with an accept:
46 46 $ hg phabupdate --accept D4564 \
47 47 > -m 'I think I like where this is headed. Will read rest of series later.'\
48 48 > --test-vcr "$VCR/accept-4564.json"
49 49 abort: Conduit Error (ERR-CONDUIT-CORE): Validation errors:
50 50 - You can not accept this revision because it has already been closed. Only open revisions can be accepted.
51 51 [255]
52 52 $ hg phabupdate --accept D7913 -m 'LGTM' --test-vcr "$VCR/accept-7913.json"
53 53
54 54 Create a differential diff:
55 55 $ HGENCODING=utf-8; export HGENCODING
56 56 $ echo alpha > alpha
57 57 $ hg ci --addremove -m 'create alpha for phabricator test €'
58 58 adding alpha
59 59 $ hg phabsend -r . --test-vcr "$VCR/phabsend-create-alpha.json"
60 60 D7915 - created - d386117f30e6: create alpha for phabricator test \xe2\x82\xac (esc)
61 61 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/d386117f30e6-24ffe649-phabsend.hg
62 62 $ echo more >> alpha
63 63 $ HGEDITOR=true hg ci --amend
64 64 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/347bf67801e5-3bf313e4-amend.hg
65 65 $ echo beta > beta
66 66 $ hg ci --addremove -m 'create beta for phabricator test'
67 67 adding beta
68 68 $ hg phabsend -r ".^::" --test-vcr "$VCR/phabsend-update-alpha-create-beta.json"
69 69 D7915 - updated - c44b38f24a45: create alpha for phabricator test \xe2\x82\xac (esc)
70 70 D7916 - created - 9e6901f21d5b: create beta for phabricator test
71 71 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9e6901f21d5b-1fcd4f0e-phabsend.hg
72 72 $ unset HGENCODING
73 73
74 74 The amend won't explode after posting a public commit. The local tag is left
75 75 behind to identify it.
76 76
77 77 $ echo 'public change' > beta
78 78 $ hg ci -m 'create public change for phabricator testing'
79 79 $ hg phase --public .
80 80 $ echo 'draft change' > alpha
81 81 $ hg ci -m 'create draft change for phabricator testing'
82 82 $ hg phabsend --amend -r '.^::' --test-vcr "$VCR/phabsend-create-public.json"
83 83 D7917 - created - 7b4185ab5d16: create public change for phabricator testing
84 84 D7918 - created - 251c1c333fc6: create draft change for phabricator testing
85 85 warning: not updating public commit 2:7b4185ab5d16
86 86 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/251c1c333fc6-41cb7c3b-phabsend.hg
87 87 $ hg tags -v
88 88 tip 3:3244dc4a3334
89 89 D7917 2:7b4185ab5d16 local
90 90
91 91 $ hg debugcallconduit user.search --test-vcr "$VCR/phab-conduit.json" <<EOF
92 92 > {
93 93 > "constraints": {
94 94 > "isBot": true
95 95 > }
96 96 > }
97 97 > EOF
98 98 {
99 99 "cursor": {
100 100 "after": null,
101 101 "before": null,
102 102 "limit": 100,
103 103 "order": null
104 104 },
105 105 "data": [],
106 106 "maps": {},
107 107 "query": {
108 108 "queryKey": null
109 109 }
110 110 }
111 111
112 112 Template keywords
113 113 $ hg log -T'{rev} {phabreview|json}\n'
114 114 3 {"id": "D7918", "url": "https://phab.mercurial-scm.org/D7918"}
115 115 2 {"id": "D7917", "url": "https://phab.mercurial-scm.org/D7917"}
116 116 1 {"id": "D7916", "url": "https://phab.mercurial-scm.org/D7916"}
117 117 0 {"id": "D7915", "url": "https://phab.mercurial-scm.org/D7915"}
118 118
119 119 $ hg log -T'{rev} {if(phabreview, "{phabreview.url} {phabreview.id}")}\n'
120 120 3 https://phab.mercurial-scm.org/D7918 D7918
121 121 2 https://phab.mercurial-scm.org/D7917 D7917
122 122 1 https://phab.mercurial-scm.org/D7916 D7916
123 123 0 https://phab.mercurial-scm.org/D7915 D7915
124 124
125 125 Commenting when phabsending:
126 126 $ echo comment > comment
127 127 $ hg ci --addremove -m "create comment for phabricator test"
128 128 adding comment
129 129 $ hg phabsend -r . -m "For default branch" --test-vcr "$VCR/phabsend-comment-created.json"
130 130 D7919 - created - d5dddca9023d: create comment for phabricator test
131 131 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/d5dddca9023d-adf673ba-phabsend.hg
132 132 $ echo comment2 >> comment
133 133 $ hg ci --amend
134 134 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/f7db812bbe1d-8fcded77-amend.hg
135 135 $ hg phabsend -r . -m "Address review comments" --test-vcr "$VCR/phabsend-comment-updated.json"
136 136 D7919 - updated - 1849d7828727: create comment for phabricator test
137 137
138 138 Phabsending a skipped commit:
139 139 $ hg phabsend --no-amend -r . --test-vcr "$VCR/phabsend-skipped.json"
140 140 D7919 - skipped - 1849d7828727: create comment for phabricator test
141 141
142 Phabesending a new binary, a modified binary, and a removed binary
143
144 >>> open('bin', 'wb').write(b'\0a') and None
145 $ hg ci -Am 'add binary'
146 adding bin
147 >>> open('bin', 'wb').write(b'\0b') and None
148 $ hg ci -m 'modify binary'
149 $ hg rm bin
150 $ hg ci -m 'remove binary'
151 $ hg phabsend -r .~2:: --test-vcr "$VCR/phabsend-binary.json"
152 uploading bin@aa24a81f55de
153 D8007 - created - aa24a81f55de: add binary
154 uploading bin@d8d62a881b54
155 D8008 - created - d8d62a881b54: modify binary
156 D8009 - created - af55645b2e29: remove binary
157 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/aa24a81f55de-a3a0cf24-phabsend.hg
158
142 159 Phabreading a DREV with a local:commits time as a string:
143 160 $ hg phabread --test-vcr "$VCR/phabread-str-time.json" D1285
144 161 # HG changeset patch
145 162 # User Pulkit Goyal <7895pulkit@gmail.com>
146 163 # Date 1509404054 -19800
147 164 # Node ID 44fc1c1f1774a76423b9c732af6938435099bcc5
148 165 # Parent 8feef8ef8389a3b544e0a74624f1efc3a8d85d35
149 166 repoview: add a new attribute _visibilityexceptions and related API
150 167
151 168 Currently we don't have a defined way in core to make some hidden revisions
152 169 visible in filtered repo. Extensions to achieve the purpose of unhiding some
153 170 hidden commits, wrap repoview.pinnedrevs() function.
154 171
155 172 To make the above task simple and have well defined API, this patch adds a new
156 173 attribute '_visibilityexceptions' to repoview class which will contains
157 174 the hidden revs which should be exception.
158 175 This will allow to set different exceptions for different repoview objects
159 176 backed by the same unfiltered repo.
160 177
161 178 This patch also adds API to add revs to the attribute set and get them.
162 179
163 180 Thanks to Jun for suggesting the use of repoview class instead of localrepo.
164 181
165 182 Differential Revision: https://phab.mercurial-scm.org/D1285
166 183 diff --git a/mercurial/repoview.py b/mercurial/repoview.py
167 184 --- a/mercurial/repoview.py
168 185 +++ b/mercurial/repoview.py
169 186 @@ * @@ (glob)
170 187 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
171 188 """
172 189
173 190 + # hidden revs which should be visible
174 191 + _visibilityexceptions = set()
175 192 +
176 193 def __init__(self, repo, filtername):
177 194 object.__setattr__(self, r'_unfilteredrepo', repo)
178 195 object.__setattr__(self, r'filtername', filtername)
179 196 @@ -231,6 +234,14 @@
180 197 return self
181 198 return self.unfiltered().filtered(name)
182 199
183 200 + def addvisibilityexceptions(self, revs):
184 201 + """adds hidden revs which should be visible to set of exceptions"""
185 202 + self._visibilityexceptions.update(revs)
186 203 +
187 204 + def getvisibilityexceptions(self):
188 205 + """returns the set of hidden revs which should be visible"""
189 206 + return self._visibilityexceptions
190 207 +
191 208 # everything access are forwarded to the proxied repo
192 209 def __getattr__(self, attr):
193 210 return getattr(self._unfilteredrepo, attr)
194 211 diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
195 212 --- a/mercurial/localrepo.py
196 213 +++ b/mercurial/localrepo.py
197 214 @@ -570,6 +570,14 @@
198 215 def close(self):
199 216 self._writecaches()
200 217
201 218 + def addvisibilityexceptions(self, exceptions):
202 219 + # should be called on a filtered repository
203 220 + pass
204 221 +
205 222 + def getvisibilityexceptions(self):
206 223 + # should be called on a filtered repository
207 224 + return set()
208 225 +
209 226 def _loadextensions(self):
210 227 extensions.loadall(self.ui)
211 228
212 229
213 230 A bad .arcconfig doesn't error out
214 231 $ echo 'garbage' > .arcconfig
215 232 $ hg config phabricator --debug
216 233 invalid JSON in $TESTTMP/repo/.arcconfig
217 234 read config from: */.hgrc (glob)
218 235 $TESTTMP/repo/.hg/hgrc:*: phabricator.url=https://phab.mercurial-scm.org/ (glob)
219 236 $TESTTMP/repo/.hg/hgrc:*: phabricator.callsign=HG (glob)
220 237
221 238 The .arcconfig content overrides global config
222 239 $ cat >> $HGRCPATH << EOF
223 240 > [phabricator]
224 241 > url = global
225 242 > callsign = global
226 243 > EOF
227 244 $ cp $TESTDIR/../.arcconfig .
228 245 $ mv .hg/hgrc .hg/hgrc.bak
229 246 $ hg config phabricator --debug
230 247 read config from: */.hgrc (glob)
231 248 $TESTTMP/repo/.arcconfig: phabricator.callsign=HG
232 249 $TESTTMP/repo/.arcconfig: phabricator.url=https://phab.mercurial-scm.org/
233 250
234 251 But it doesn't override local config
235 252 $ cat >> .hg/hgrc << EOF
236 253 > [phabricator]
237 254 > url = local
238 255 > callsign = local
239 256 > EOF
240 257 $ hg config phabricator --debug
241 258 read config from: */.hgrc (glob)
242 259 $TESTTMP/repo/.hg/hgrc:*: phabricator.url=local (glob)
243 260 $TESTTMP/repo/.hg/hgrc:*: phabricator.callsign=local (glob)
244 261 $ mv .hg/hgrc.bak .hg/hgrc
245 262
246 263 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now