##// END OF EJS Templates
phabricator: avoid creating unstable children within the review stack...
Matt Harbison -
r45597:0680b8a1 default
parent child Browse files
Show More
This diff has been collapsed as it changes many lines, (617 lines changed) Show them Hide them
@@ -0,0 +1,617 b''
1 {
2 "version": 1,
3 "interactions": [
4 {
5 "response": {
6 "headers": {
7 "referrer-policy": [
8 "no-referrer"
9 ],
10 "x-frame-options": [
11 "Deny"
12 ],
13 "date": [
14 "Wed, 15 Apr 2020 23:43:39 GMT"
15 ],
16 "server": [
17 "Apache/2.4.10 (Debian)"
18 ],
19 "content-type": [
20 "application/json"
21 ],
22 "expires": [
23 "Sat, 01 Jan 2000 00:00:00 GMT"
24 ],
25 "transfer-encoding": [
26 "chunked"
27 ],
28 "x-xss-protection": [
29 "1; mode=block"
30 ],
31 "strict-transport-security": [
32 "max-age=0; includeSubdomains; preload"
33 ],
34 "cache-control": [
35 "no-store"
36 ],
37 "x-content-type-options": [
38 "nosniff"
39 ]
40 },
41 "status": {
42 "code": 200,
43 "message": "OK"
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 "headers": {
51 "content-length": [
52 "183"
53 ],
54 "content-type": [
55 "application/x-www-form-urlencoded"
56 ],
57 "user-agent": [
58 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
59 ],
60 "accept": [
61 "application/mercurial-0.1"
62 ],
63 "host": [
64 "phab.mercurial-scm.org"
65 ]
66 },
67 "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",
68 "uri": "https://phab.mercurial-scm.org//api/diffusion.repository.search",
69 "method": "POST"
70 }
71 },
72 {
73 "response": {
74 "headers": {
75 "referrer-policy": [
76 "no-referrer"
77 ],
78 "x-frame-options": [
79 "Deny"
80 ],
81 "date": [
82 "Wed, 15 Apr 2020 23:43:39 GMT"
83 ],
84 "server": [
85 "Apache/2.4.10 (Debian)"
86 ],
87 "content-type": [
88 "application/json"
89 ],
90 "expires": [
91 "Sat, 01 Jan 2000 00:00:00 GMT"
92 ],
93 "transfer-encoding": [
94 "chunked"
95 ],
96 "x-xss-protection": [
97 "1; mode=block"
98 ],
99 "strict-transport-security": [
100 "max-age=0; includeSubdomains; preload"
101 ],
102 "cache-control": [
103 "no-store"
104 ],
105 "x-content-type-options": [
106 "nosniff"
107 ]
108 },
109 "status": {
110 "code": 200,
111 "message": "OK"
112 },
113 "body": {
114 "string": "{\"result\":{\"diffid\":21110,\"phid\":\"PHID-DIFF-g25jdc5b5khduwpp3p3b\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/differential\\/diff\\/21110\\/\"},\"error_code\":null,\"error_info\":null}"
115 }
116 },
117 "request": {
118 "headers": {
119 "content-length": [
120 "1162"
121 ],
122 "content-type": [
123 "application/x-www-form-urlencoded"
124 ],
125 "user-agent": [
126 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
127 ],
128 "accept": [
129 "application/mercurial-0.1"
130 ],
131 "host": [
132 "phab.mercurial-scm.org"
133 ]
134 },
135 "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%22file1.txt%22%3A+%7B%22addLines%22%3A+1%2C+%22awayPaths%22%3A+%5B%5D%2C+%22commitHash%22%3A+null%2C+%22currentPath%22%3A+%22file1.txt%22%2C+%22delLines%22%3A+1%2C+%22fileType%22%3A+1%2C+%22hunks%22%3A+%5B%7B%22addLines%22%3A+1%2C+%22corpus%22%3A+%22-mod1%5Cn%2Bmod2%5Cn%22%2C+%22delLines%22%3A+1%2C+%22newLength%22%3A+1%2C+%22newOffset%22%3A+1%2C+%22oldLength%22%3A+1%2C+%22oldOffset%22%3A+1%7D%5D%2C+%22metadata%22%3A+%7B%7D%2C+%22newProperties%22%3A+%7B%7D%2C+%22oldPath%22%3A+%22file1.txt%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+%22d549263bcb2db54042adf048047b368f1ed246df%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",
136 "uri": "https://phab.mercurial-scm.org//api/differential.creatediff",
137 "method": "POST"
138 }
139 },
140 {
141 "response": {
142 "headers": {
143 "referrer-policy": [
144 "no-referrer"
145 ],
146 "x-frame-options": [
147 "Deny"
148 ],
149 "date": [
150 "Wed, 15 Apr 2020 23:43:39 GMT"
151 ],
152 "server": [
153 "Apache/2.4.10 (Debian)"
154 ],
155 "content-type": [
156 "application/json"
157 ],
158 "expires": [
159 "Sat, 01 Jan 2000 00:00:00 GMT"
160 ],
161 "transfer-encoding": [
162 "chunked"
163 ],
164 "x-xss-protection": [
165 "1; mode=block"
166 ],
167 "strict-transport-security": [
168 "max-age=0; includeSubdomains; preload"
169 ],
170 "cache-control": [
171 "no-store"
172 ],
173 "x-content-type-options": [
174 "nosniff"
175 ]
176 },
177 "status": {
178 "code": 200,
179 "message": "OK"
180 },
181 "body": {
182 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
183 }
184 },
185 "request": {
186 "headers": {
187 "content-length": [
188 "482"
189 ],
190 "content-type": [
191 "application/x-www-form-urlencoded"
192 ],
193 "user-agent": [
194 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
195 ],
196 "accept": [
197 "application/mercurial-0.1"
198 ],
199 "host": [
200 "phab.mercurial-scm.org"
201 ]
202 },
203 "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%225d3959e20d1d71d9e906e562f891582b4072570f%5C%22%2C+%5C%22parent%5C%22%3A+%5C%22d549263bcb2db54042adf048047b368f1ed246df%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+21110%2C+%22name%22%3A+%22hg%3Ameta%22%7D",
204 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty",
205 "method": "POST"
206 }
207 },
208 {
209 "response": {
210 "headers": {
211 "referrer-policy": [
212 "no-referrer"
213 ],
214 "x-frame-options": [
215 "Deny"
216 ],
217 "date": [
218 "Wed, 15 Apr 2020 23:43:40 GMT"
219 ],
220 "server": [
221 "Apache/2.4.10 (Debian)"
222 ],
223 "content-type": [
224 "application/json"
225 ],
226 "expires": [
227 "Sat, 01 Jan 2000 00:00:00 GMT"
228 ],
229 "transfer-encoding": [
230 "chunked"
231 ],
232 "x-xss-protection": [
233 "1; mode=block"
234 ],
235 "strict-transport-security": [
236 "max-age=0; includeSubdomains; preload"
237 ],
238 "cache-control": [
239 "no-store"
240 ],
241 "x-content-type-options": [
242 "nosniff"
243 ]
244 },
245 "status": {
246 "code": 200,
247 "message": "OK"
248 },
249 "body": {
250 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
251 }
252 },
253 "request": {
254 "headers": {
255 "content-length": [
256 "594"
257 ],
258 "content-type": [
259 "application/x-www-form-urlencoded"
260 ],
261 "user-agent": [
262 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
263 ],
264 "accept": [
265 "application/mercurial-0.1"
266 ],
267 "host": [
268 "phab.mercurial-scm.org"
269 ]
270 },
271 "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%225d3959e20d1d71d9e906e562f891582b4072570f%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%225d3959e20d1d71d9e906e562f891582b4072570f%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%22d549263bcb2db54042adf048047b368f1ed246df%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+21110%2C+%22name%22%3A+%22local%3Acommits%22%7D",
272 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty",
273 "method": "POST"
274 }
275 },
276 {
277 "response": {
278 "headers": {
279 "referrer-policy": [
280 "no-referrer"
281 ],
282 "x-frame-options": [
283 "Deny"
284 ],
285 "date": [
286 "Wed, 15 Apr 2020 23:43:40 GMT"
287 ],
288 "server": [
289 "Apache/2.4.10 (Debian)"
290 ],
291 "content-type": [
292 "application/json"
293 ],
294 "expires": [
295 "Sat, 01 Jan 2000 00:00:00 GMT"
296 ],
297 "transfer-encoding": [
298 "chunked"
299 ],
300 "x-xss-protection": [
301 "1; mode=block"
302 ],
303 "strict-transport-security": [
304 "max-age=0; includeSubdomains; preload"
305 ],
306 "cache-control": [
307 "no-store"
308 ],
309 "x-content-type-options": [
310 "nosniff"
311 ]
312 },
313 "status": {
314 "code": 200,
315 "message": "OK"
316 },
317 "body": {
318 "string": "{\"result\":{\"errors\":[],\"fields\":{\"title\":\"modified 2\"},\"revisionIDFieldInfo\":{\"value\":null,\"validDomain\":\"https:\\/\\/phab.mercurial-scm.org\"},\"transactions\":[{\"type\":\"title\",\"value\":\"modified 2\"}]},\"error_code\":null,\"error_info\":null}"
319 }
320 },
321 "request": {
322 "headers": {
323 "content-length": [
324 "155"
325 ],
326 "content-type": [
327 "application/x-www-form-urlencoded"
328 ],
329 "user-agent": [
330 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
331 ],
332 "accept": [
333 "application/mercurial-0.1"
334 ],
335 "host": [
336 "phab.mercurial-scm.org"
337 ]
338 },
339 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22corpus%22%3A+%22modified+2%22%7D",
340 "uri": "https://phab.mercurial-scm.org//api/differential.parsecommitmessage",
341 "method": "POST"
342 }
343 },
344 {
345 "response": {
346 "headers": {
347 "referrer-policy": [
348 "no-referrer"
349 ],
350 "x-frame-options": [
351 "Deny"
352 ],
353 "date": [
354 "Wed, 15 Apr 2020 23:43:41 GMT"
355 ],
356 "server": [
357 "Apache/2.4.10 (Debian)"
358 ],
359 "content-type": [
360 "application/json"
361 ],
362 "expires": [
363 "Sat, 01 Jan 2000 00:00:00 GMT"
364 ],
365 "transfer-encoding": [
366 "chunked"
367 ],
368 "x-xss-protection": [
369 "1; mode=block"
370 ],
371 "strict-transport-security": [
372 "max-age=0; includeSubdomains; preload"
373 ],
374 "cache-control": [
375 "no-store"
376 ],
377 "x-content-type-options": [
378 "nosniff"
379 ]
380 },
381 "status": {
382 "code": 200,
383 "message": "OK"
384 },
385 "body": {
386 "string": "{\"result\":{\"object\":{\"id\":8433,\"phid\":\"PHID-DREV-kpkwhtylyxrzikfspl5r\"},\"transactions\":[{\"phid\":\"PHID-XACT-DREV-txmvtexe4kw2pjn\"},{\"phid\":\"PHID-XACT-DREV-l2em7ff7nlcb2fu\"},{\"phid\":\"PHID-XACT-DREV-ttcilh74gwwixze\"},{\"phid\":\"PHID-XACT-DREV-ytvu3llz6pqwg46\"},{\"phid\":\"PHID-XACT-DREV-jeqenvlffsbdv5y\"}]},\"error_code\":null,\"error_info\":null}"
387 }
388 },
389 "request": {
390 "headers": {
391 "content-length": [
392 "308"
393 ],
394 "content-type": [
395 "application/x-www-form-urlencoded"
396 ],
397 "user-agent": [
398 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
399 ],
400 "accept": [
401 "application/mercurial-0.1"
402 ],
403 "host": [
404 "phab.mercurial-scm.org"
405 ]
406 },
407 "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-g25jdc5b5khduwpp3p3b%22%7D%2C+%7B%22type%22%3A+%22title%22%2C+%22value%22%3A+%22modified+2%22%7D%5D%7D",
408 "uri": "https://phab.mercurial-scm.org//api/differential.revision.edit",
409 "method": "POST"
410 }
411 },
412 {
413 "response": {
414 "headers": {
415 "referrer-policy": [
416 "no-referrer"
417 ],
418 "x-frame-options": [
419 "Deny"
420 ],
421 "date": [
422 "Wed, 15 Apr 2020 23:43:41 GMT"
423 ],
424 "server": [
425 "Apache/2.4.10 (Debian)"
426 ],
427 "content-type": [
428 "application/json"
429 ],
430 "expires": [
431 "Sat, 01 Jan 2000 00:00:00 GMT"
432 ],
433 "transfer-encoding": [
434 "chunked"
435 ],
436 "x-xss-protection": [
437 "1; mode=block"
438 ],
439 "strict-transport-security": [
440 "max-age=0; includeSubdomains; preload"
441 ],
442 "cache-control": [
443 "no-store"
444 ],
445 "x-content-type-options": [
446 "nosniff"
447 ]
448 },
449 "status": {
450 "code": 200,
451 "message": "OK"
452 },
453 "body": {
454 "string": "{\"result\":[{\"id\":\"8433\",\"phid\":\"PHID-DREV-kpkwhtylyxrzikfspl5r\",\"title\":\"modified 2\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/D8433\",\"dateCreated\":\"1586994221\",\"dateModified\":\"1586994221\",\"authorPHID\":\"PHID-USER-tzhaient733lwrlbcag5\",\"status\":\"0\",\"statusName\":\"Needs Review\",\"properties\":{\"draft.broadcast\":true,\"lines.added\":1,\"lines.removed\":1},\"branch\":\"default\",\"summary\":\"\",\"testPlan\":\"\",\"lineCount\":\"2\",\"activeDiffPHID\":\"PHID-DIFF-g25jdc5b5khduwpp3p3b\",\"diffs\":[\"21110\"],\"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}"
455 }
456 },
457 "request": {
458 "headers": {
459 "content-length": [
460 "146"
461 ],
462 "content-type": [
463 "application/x-www-form-urlencoded"
464 ],
465 "user-agent": [
466 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
467 ],
468 "accept": [
469 "application/mercurial-0.1"
470 ],
471 "host": [
472 "phab.mercurial-scm.org"
473 ]
474 },
475 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22ids%22%3A+%5B8433%5D%7D",
476 "uri": "https://phab.mercurial-scm.org//api/differential.query",
477 "method": "POST"
478 }
479 },
480 {
481 "response": {
482 "headers": {
483 "referrer-policy": [
484 "no-referrer"
485 ],
486 "x-frame-options": [
487 "Deny"
488 ],
489 "date": [
490 "Wed, 15 Apr 2020 23:43:42 GMT"
491 ],
492 "server": [
493 "Apache/2.4.10 (Debian)"
494 ],
495 "content-type": [
496 "application/json"
497 ],
498 "expires": [
499 "Sat, 01 Jan 2000 00:00:00 GMT"
500 ],
501 "transfer-encoding": [
502 "chunked"
503 ],
504 "x-xss-protection": [
505 "1; mode=block"
506 ],
507 "strict-transport-security": [
508 "max-age=0; includeSubdomains; preload"
509 ],
510 "cache-control": [
511 "no-store"
512 ],
513 "x-content-type-options": [
514 "nosniff"
515 ]
516 },
517 "status": {
518 "code": 200,
519 "message": "OK"
520 },
521 "body": {
522 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
523 }
524 },
525 "request": {
526 "headers": {
527 "content-length": [
528 "482"
529 ],
530 "content-type": [
531 "application/x-www-form-urlencoded"
532 ],
533 "user-agent": [
534 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
535 ],
536 "accept": [
537 "application/mercurial-0.1"
538 ],
539 "host": [
540 "phab.mercurial-scm.org"
541 ]
542 },
543 "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%222b4aa8a88d617ca3ba0afd40e0e840b30588f126%5C%22%2C+%5C%22parent%5C%22%3A+%5C%22d549263bcb2db54042adf048047b368f1ed246df%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+21110%2C+%22name%22%3A+%22hg%3Ameta%22%7D",
544 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty",
545 "method": "POST"
546 }
547 },
548 {
549 "response": {
550 "headers": {
551 "referrer-policy": [
552 "no-referrer"
553 ],
554 "x-frame-options": [
555 "Deny"
556 ],
557 "date": [
558 "Wed, 15 Apr 2020 23:43:42 GMT"
559 ],
560 "server": [
561 "Apache/2.4.10 (Debian)"
562 ],
563 "content-type": [
564 "application/json"
565 ],
566 "expires": [
567 "Sat, 01 Jan 2000 00:00:00 GMT"
568 ],
569 "transfer-encoding": [
570 "chunked"
571 ],
572 "x-xss-protection": [
573 "1; mode=block"
574 ],
575 "strict-transport-security": [
576 "max-age=0; includeSubdomains; preload"
577 ],
578 "cache-control": [
579 "no-store"
580 ],
581 "x-content-type-options": [
582 "nosniff"
583 ]
584 },
585 "status": {
586 "code": 200,
587 "message": "OK"
588 },
589 "body": {
590 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
591 }
592 },
593 "request": {
594 "headers": {
595 "content-length": [
596 "594"
597 ],
598 "content-type": [
599 "application/x-www-form-urlencoded"
600 ],
601 "user-agent": [
602 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
603 ],
604 "accept": [
605 "application/mercurial-0.1"
606 ],
607 "host": [
608 "phab.mercurial-scm.org"
609 ]
610 },
611 "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%222b4aa8a88d617ca3ba0afd40e0e840b30588f126%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%222b4aa8a88d617ca3ba0afd40e0e840b30588f126%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%22d549263bcb2db54042adf048047b368f1ed246df%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+21110%2C+%22name%22%3A+%22local%3Acommits%22%7D",
612 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty",
613 "method": "POST"
614 }
615 }
616 ]
617 } No newline at end of file
This diff has been collapsed as it changes many lines, (1093 lines changed) Show them Hide them
@@ -0,0 +1,1093 b''
1 {
2 "version": 1,
3 "interactions": [
4 {
5 "response": {
6 "headers": {
7 "referrer-policy": [
8 "no-referrer"
9 ],
10 "x-frame-options": [
11 "Deny"
12 ],
13 "date": [
14 "Wed, 15 Apr 2020 23:43:44 GMT"
15 ],
16 "server": [
17 "Apache/2.4.10 (Debian)"
18 ],
19 "content-type": [
20 "application/json"
21 ],
22 "expires": [
23 "Sat, 01 Jan 2000 00:00:00 GMT"
24 ],
25 "transfer-encoding": [
26 "chunked"
27 ],
28 "x-xss-protection": [
29 "1; mode=block"
30 ],
31 "strict-transport-security": [
32 "max-age=0; includeSubdomains; preload"
33 ],
34 "cache-control": [
35 "no-store"
36 ],
37 "x-content-type-options": [
38 "nosniff"
39 ]
40 },
41 "status": {
42 "code": 200,
43 "message": "OK"
44 },
45 "body": {
46 "string": "{\"result\":{\"21110\":{\"id\":\"21110\",\"revisionID\":\"8433\",\"dateCreated\":\"1586994219\",\"dateModified\":\"1586994221\",\"sourceControlBaseRevision\":\"d549263bcb2db54042adf048047b368f1ed246df\",\"sourceControlPath\":\"\\/\",\"sourceControlSystem\":\"hg\",\"branch\":\"default\",\"bookmark\":null,\"creationMethod\":\"phabsend\",\"description\":null,\"unitStatus\":\"0\",\"lintStatus\":\"0\",\"changes\":[{\"id\":\"57078\",\"metadata\":{\"line:first\":1,\"hash.effect\":\"ftEQkHimiyJo\"},\"oldPath\":\"file1.txt\",\"currentPath\":\"file1.txt\",\"awayPaths\":[],\"oldProperties\":[],\"newProperties\":[],\"type\":\"2\",\"fileType\":\"1\",\"commitHash\":null,\"addLines\":\"1\",\"delLines\":\"1\",\"hunks\":[{\"oldOffset\":\"1\",\"newOffset\":\"1\",\"oldLength\":\"1\",\"newLength\":\"1\",\"addLines\":null,\"delLines\":null,\"isMissingOldNewline\":null,\"isMissingNewNewline\":null,\"corpus\":\"-mod1\\n+mod2\\n\"}]}],\"properties\":{\"hg:meta\":{\"branch\":\"default\",\"date\":\"0 0\",\"node\":\"2b4aa8a88d617ca3ba0afd40e0e840b30588f126\",\"parent\":\"d549263bcb2db54042adf048047b368f1ed246df\",\"user\":\"test\"},\"local:commits\":{\"2b4aa8a88d617ca3ba0afd40e0e840b30588f126\":{\"author\":\"test\",\"authorEmail\":\"test\",\"branch\":\"default\",\"commit\":\"2b4aa8a88d617ca3ba0afd40e0e840b30588f126\",\"parents\":[\"d549263bcb2db54042adf048047b368f1ed246df\"],\"time\":0}}},\"authorName\":\"test\",\"authorEmail\":\"test\"}},\"error_code\":null,\"error_info\":null}"
47 }
48 },
49 "request": {
50 "headers": {
51 "content-length": [
52 "154"
53 ],
54 "content-type": [
55 "application/x-www-form-urlencoded"
56 ],
57 "user-agent": [
58 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
59 ],
60 "accept": [
61 "application/mercurial-0.1"
62 ],
63 "host": [
64 "phab.mercurial-scm.org"
65 ]
66 },
67 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22revisionIDs%22%3A+%5B8433%5D%7D",
68 "uri": "https://phab.mercurial-scm.org//api/differential.querydiffs",
69 "method": "POST"
70 }
71 },
72 {
73 "response": {
74 "headers": {
75 "referrer-policy": [
76 "no-referrer"
77 ],
78 "x-frame-options": [
79 "Deny"
80 ],
81 "date": [
82 "Wed, 15 Apr 2020 23:43:45 GMT"
83 ],
84 "server": [
85 "Apache/2.4.10 (Debian)"
86 ],
87 "content-type": [
88 "application/json"
89 ],
90 "expires": [
91 "Sat, 01 Jan 2000 00:00:00 GMT"
92 ],
93 "transfer-encoding": [
94 "chunked"
95 ],
96 "x-xss-protection": [
97 "1; mode=block"
98 ],
99 "strict-transport-security": [
100 "max-age=0; includeSubdomains; preload"
101 ],
102 "cache-control": [
103 "no-store"
104 ],
105 "x-content-type-options": [
106 "nosniff"
107 ]
108 },
109 "status": {
110 "code": 200,
111 "message": "OK"
112 },
113 "body": {
114 "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}"
115 }
116 },
117 "request": {
118 "headers": {
119 "content-length": [
120 "183"
121 ],
122 "content-type": [
123 "application/x-www-form-urlencoded"
124 ],
125 "user-agent": [
126 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
127 ],
128 "accept": [
129 "application/mercurial-0.1"
130 ],
131 "host": [
132 "phab.mercurial-scm.org"
133 ]
134 },
135 "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",
136 "uri": "https://phab.mercurial-scm.org//api/diffusion.repository.search",
137 "method": "POST"
138 }
139 },
140 {
141 "response": {
142 "headers": {
143 "referrer-policy": [
144 "no-referrer"
145 ],
146 "x-frame-options": [
147 "Deny"
148 ],
149 "date": [
150 "Wed, 15 Apr 2020 23:43:45 GMT"
151 ],
152 "server": [
153 "Apache/2.4.10 (Debian)"
154 ],
155 "content-type": [
156 "application/json"
157 ],
158 "expires": [
159 "Sat, 01 Jan 2000 00:00:00 GMT"
160 ],
161 "transfer-encoding": [
162 "chunked"
163 ],
164 "x-xss-protection": [
165 "1; mode=block"
166 ],
167 "strict-transport-security": [
168 "max-age=0; includeSubdomains; preload"
169 ],
170 "cache-control": [
171 "no-store"
172 ],
173 "x-content-type-options": [
174 "nosniff"
175 ]
176 },
177 "status": {
178 "code": 200,
179 "message": "OK"
180 },
181 "body": {
182 "string": "{\"result\":{\"diffid\":21111,\"phid\":\"PHID-DIFF-qat4sqpqqvytzhf7rpti\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/differential\\/diff\\/21111\\/\"},\"error_code\":null,\"error_info\":null}"
183 }
184 },
185 "request": {
186 "headers": {
187 "content-length": [
188 "1161"
189 ],
190 "content-type": [
191 "application/x-www-form-urlencoded"
192 ],
193 "user-agent": [
194 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
195 ],
196 "accept": [
197 "application/mercurial-0.1"
198 ],
199 "host": [
200 "phab.mercurial-scm.org"
201 ]
202 },
203 "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%22file1.txt%22%3A+%7B%22addLines%22%3A+1%2C+%22awayPaths%22%3A+%5B%5D%2C+%22commitHash%22%3A+null%2C+%22currentPath%22%3A+%22file1.txt%22%2C+%22delLines%22%3A+1%2C+%22fileType%22%3A+1%2C+%22hunks%22%3A+%5B%7B%22addLines%22%3A+1%2C+%22corpus%22%3A+%22-add%5Cn%2Bmod1%5Cn%22%2C+%22delLines%22%3A+1%2C+%22newLength%22%3A+1%2C+%22newOffset%22%3A+1%2C+%22oldLength%22%3A+1%2C+%22oldOffset%22%3A+1%7D%5D%2C+%22metadata%22%3A+%7B%7D%2C+%22newProperties%22%3A+%7B%7D%2C+%22oldPath%22%3A+%22file1.txt%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+%225cbade24e0fae40d67c568e86a978a2a946b9aed%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",
204 "uri": "https://phab.mercurial-scm.org//api/differential.creatediff",
205 "method": "POST"
206 }
207 },
208 {
209 "response": {
210 "headers": {
211 "referrer-policy": [
212 "no-referrer"
213 ],
214 "x-frame-options": [
215 "Deny"
216 ],
217 "date": [
218 "Wed, 15 Apr 2020 23:43:46 GMT"
219 ],
220 "server": [
221 "Apache/2.4.10 (Debian)"
222 ],
223 "content-type": [
224 "application/json"
225 ],
226 "expires": [
227 "Sat, 01 Jan 2000 00:00:00 GMT"
228 ],
229 "transfer-encoding": [
230 "chunked"
231 ],
232 "x-xss-protection": [
233 "1; mode=block"
234 ],
235 "strict-transport-security": [
236 "max-age=0; includeSubdomains; preload"
237 ],
238 "cache-control": [
239 "no-store"
240 ],
241 "x-content-type-options": [
242 "nosniff"
243 ]
244 },
245 "status": {
246 "code": 200,
247 "message": "OK"
248 },
249 "body": {
250 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
251 }
252 },
253 "request": {
254 "headers": {
255 "content-length": [
256 "482"
257 ],
258 "content-type": [
259 "application/x-www-form-urlencoded"
260 ],
261 "user-agent": [
262 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
263 ],
264 "accept": [
265 "application/mercurial-0.1"
266 ],
267 "host": [
268 "phab.mercurial-scm.org"
269 ]
270 },
271 "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%22d549263bcb2db54042adf048047b368f1ed246df%5C%22%2C+%5C%22parent%5C%22%3A+%5C%225cbade24e0fae40d67c568e86a978a2a946b9aed%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+21111%2C+%22name%22%3A+%22hg%3Ameta%22%7D",
272 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty",
273 "method": "POST"
274 }
275 },
276 {
277 "response": {
278 "headers": {
279 "referrer-policy": [
280 "no-referrer"
281 ],
282 "x-frame-options": [
283 "Deny"
284 ],
285 "date": [
286 "Wed, 15 Apr 2020 23:43:46 GMT"
287 ],
288 "server": [
289 "Apache/2.4.10 (Debian)"
290 ],
291 "content-type": [
292 "application/json"
293 ],
294 "expires": [
295 "Sat, 01 Jan 2000 00:00:00 GMT"
296 ],
297 "transfer-encoding": [
298 "chunked"
299 ],
300 "x-xss-protection": [
301 "1; mode=block"
302 ],
303 "strict-transport-security": [
304 "max-age=0; includeSubdomains; preload"
305 ],
306 "cache-control": [
307 "no-store"
308 ],
309 "x-content-type-options": [
310 "nosniff"
311 ]
312 },
313 "status": {
314 "code": 200,
315 "message": "OK"
316 },
317 "body": {
318 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
319 }
320 },
321 "request": {
322 "headers": {
323 "content-length": [
324 "594"
325 ],
326 "content-type": [
327 "application/x-www-form-urlencoded"
328 ],
329 "user-agent": [
330 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
331 ],
332 "accept": [
333 "application/mercurial-0.1"
334 ],
335 "host": [
336 "phab.mercurial-scm.org"
337 ]
338 },
339 "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%22d549263bcb2db54042adf048047b368f1ed246df%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%22d549263bcb2db54042adf048047b368f1ed246df%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%225cbade24e0fae40d67c568e86a978a2a946b9aed%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+21111%2C+%22name%22%3A+%22local%3Acommits%22%7D",
340 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty",
341 "method": "POST"
342 }
343 },
344 {
345 "response": {
346 "headers": {
347 "referrer-policy": [
348 "no-referrer"
349 ],
350 "x-frame-options": [
351 "Deny"
352 ],
353 "date": [
354 "Wed, 15 Apr 2020 23:43:47 GMT"
355 ],
356 "server": [
357 "Apache/2.4.10 (Debian)"
358 ],
359 "content-type": [
360 "application/json"
361 ],
362 "expires": [
363 "Sat, 01 Jan 2000 00:00:00 GMT"
364 ],
365 "transfer-encoding": [
366 "chunked"
367 ],
368 "x-xss-protection": [
369 "1; mode=block"
370 ],
371 "strict-transport-security": [
372 "max-age=0; includeSubdomains; preload"
373 ],
374 "cache-control": [
375 "no-store"
376 ],
377 "x-content-type-options": [
378 "nosniff"
379 ]
380 },
381 "status": {
382 "code": 200,
383 "message": "OK"
384 },
385 "body": {
386 "string": "{\"result\":{\"errors\":[],\"fields\":{\"title\":\"modified 1\"},\"revisionIDFieldInfo\":{\"value\":null,\"validDomain\":\"https:\\/\\/phab.mercurial-scm.org\"},\"transactions\":[{\"type\":\"title\",\"value\":\"modified 1\"}]},\"error_code\":null,\"error_info\":null}"
387 }
388 },
389 "request": {
390 "headers": {
391 "content-length": [
392 "155"
393 ],
394 "content-type": [
395 "application/x-www-form-urlencoded"
396 ],
397 "user-agent": [
398 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
399 ],
400 "accept": [
401 "application/mercurial-0.1"
402 ],
403 "host": [
404 "phab.mercurial-scm.org"
405 ]
406 },
407 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22corpus%22%3A+%22modified+1%22%7D",
408 "uri": "https://phab.mercurial-scm.org//api/differential.parsecommitmessage",
409 "method": "POST"
410 }
411 },
412 {
413 "response": {
414 "headers": {
415 "referrer-policy": [
416 "no-referrer"
417 ],
418 "x-frame-options": [
419 "Deny"
420 ],
421 "date": [
422 "Wed, 15 Apr 2020 23:43:47 GMT"
423 ],
424 "server": [
425 "Apache/2.4.10 (Debian)"
426 ],
427 "content-type": [
428 "application/json"
429 ],
430 "expires": [
431 "Sat, 01 Jan 2000 00:00:00 GMT"
432 ],
433 "transfer-encoding": [
434 "chunked"
435 ],
436 "x-xss-protection": [
437 "1; mode=block"
438 ],
439 "strict-transport-security": [
440 "max-age=0; includeSubdomains; preload"
441 ],
442 "cache-control": [
443 "no-store"
444 ],
445 "x-content-type-options": [
446 "nosniff"
447 ]
448 },
449 "status": {
450 "code": 200,
451 "message": "OK"
452 },
453 "body": {
454 "string": "{\"result\":{\"object\":{\"id\":8434,\"phid\":\"PHID-DREV-l5ocnglddqa4hwbdzcky\"},\"transactions\":[{\"phid\":\"PHID-XACT-DREV-ov2izjdfoe74m42\"},{\"phid\":\"PHID-XACT-DREV-yqeh2if4ea4pio4\"},{\"phid\":\"PHID-XACT-DREV-a74civfucvpjkpl\"},{\"phid\":\"PHID-XACT-DREV-s3ikmvejpazw2cd\"},{\"phid\":\"PHID-XACT-DREV-2wojx4fyghzgkgw\"}]},\"error_code\":null,\"error_info\":null}"
455 }
456 },
457 "request": {
458 "headers": {
459 "content-length": [
460 "308"
461 ],
462 "content-type": [
463 "application/x-www-form-urlencoded"
464 ],
465 "user-agent": [
466 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
467 ],
468 "accept": [
469 "application/mercurial-0.1"
470 ],
471 "host": [
472 "phab.mercurial-scm.org"
473 ]
474 },
475 "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-qat4sqpqqvytzhf7rpti%22%7D%2C+%7B%22type%22%3A+%22title%22%2C+%22value%22%3A+%22modified+1%22%7D%5D%7D",
476 "uri": "https://phab.mercurial-scm.org//api/differential.revision.edit",
477 "method": "POST"
478 }
479 },
480 {
481 "response": {
482 "headers": {
483 "referrer-policy": [
484 "no-referrer"
485 ],
486 "x-frame-options": [
487 "Deny"
488 ],
489 "date": [
490 "Wed, 15 Apr 2020 23:43:48 GMT"
491 ],
492 "server": [
493 "Apache/2.4.10 (Debian)"
494 ],
495 "content-type": [
496 "application/json"
497 ],
498 "expires": [
499 "Sat, 01 Jan 2000 00:00:00 GMT"
500 ],
501 "transfer-encoding": [
502 "chunked"
503 ],
504 "x-xss-protection": [
505 "1; mode=block"
506 ],
507 "strict-transport-security": [
508 "max-age=0; includeSubdomains; preload"
509 ],
510 "cache-control": [
511 "no-store"
512 ],
513 "x-content-type-options": [
514 "nosniff"
515 ]
516 },
517 "status": {
518 "code": 200,
519 "message": "OK"
520 },
521 "body": {
522 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
523 }
524 },
525 "request": {
526 "headers": {
527 "content-length": [
528 "488"
529 ],
530 "content-type": [
531 "application/x-www-form-urlencoded"
532 ],
533 "user-agent": [
534 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
535 ],
536 "accept": [
537 "application/mercurial-0.1"
538 ],
539 "host": [
540 "phab.mercurial-scm.org"
541 ]
542 },
543 "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%222b4aa8a88d617ca3ba0afd40e0e840b30588f126%5C%22%2C+%5C%22parent%5C%22%3A+%5C%22d549263bcb2db54042adf048047b368f1ed246df%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+%2221110%22%2C+%22name%22%3A+%22hg%3Ameta%22%7D",
544 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty",
545 "method": "POST"
546 }
547 },
548 {
549 "response": {
550 "headers": {
551 "referrer-policy": [
552 "no-referrer"
553 ],
554 "x-frame-options": [
555 "Deny"
556 ],
557 "date": [
558 "Wed, 15 Apr 2020 23:43:48 GMT"
559 ],
560 "server": [
561 "Apache/2.4.10 (Debian)"
562 ],
563 "content-type": [
564 "application/json"
565 ],
566 "expires": [
567 "Sat, 01 Jan 2000 00:00:00 GMT"
568 ],
569 "transfer-encoding": [
570 "chunked"
571 ],
572 "x-xss-protection": [
573 "1; mode=block"
574 ],
575 "strict-transport-security": [
576 "max-age=0; includeSubdomains; preload"
577 ],
578 "cache-control": [
579 "no-store"
580 ],
581 "x-content-type-options": [
582 "nosniff"
583 ]
584 },
585 "status": {
586 "code": 200,
587 "message": "OK"
588 },
589 "body": {
590 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
591 }
592 },
593 "request": {
594 "headers": {
595 "content-length": [
596 "600"
597 ],
598 "content-type": [
599 "application/x-www-form-urlencoded"
600 ],
601 "user-agent": [
602 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
603 ],
604 "accept": [
605 "application/mercurial-0.1"
606 ],
607 "host": [
608 "phab.mercurial-scm.org"
609 ]
610 },
611 "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%222b4aa8a88d617ca3ba0afd40e0e840b30588f126%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%222b4aa8a88d617ca3ba0afd40e0e840b30588f126%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%22d549263bcb2db54042adf048047b368f1ed246df%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+%2221110%22%2C+%22name%22%3A+%22local%3Acommits%22%7D",
612 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty",
613 "method": "POST"
614 }
615 },
616 {
617 "response": {
618 "headers": {
619 "referrer-policy": [
620 "no-referrer"
621 ],
622 "x-frame-options": [
623 "Deny"
624 ],
625 "date": [
626 "Wed, 15 Apr 2020 23:43:49 GMT"
627 ],
628 "server": [
629 "Apache/2.4.10 (Debian)"
630 ],
631 "content-type": [
632 "application/json"
633 ],
634 "expires": [
635 "Sat, 01 Jan 2000 00:00:00 GMT"
636 ],
637 "transfer-encoding": [
638 "chunked"
639 ],
640 "x-xss-protection": [
641 "1; mode=block"
642 ],
643 "strict-transport-security": [
644 "max-age=0; includeSubdomains; preload"
645 ],
646 "cache-control": [
647 "no-store"
648 ],
649 "x-content-type-options": [
650 "nosniff"
651 ]
652 },
653 "status": {
654 "code": 200,
655 "message": "OK"
656 },
657 "body": {
658 "string": "{\"result\":{\"errors\":[],\"fields\":{\"title\":\"modified 2\",\"revisionID\":8433},\"revisionIDFieldInfo\":{\"value\":8433,\"validDomain\":\"https:\\/\\/phab.mercurial-scm.org\"},\"transactions\":[{\"type\":\"title\",\"value\":\"modified 2\"}]},\"error_code\":null,\"error_info\":null}"
659 }
660 },
661 "request": {
662 "headers": {
663 "content-length": [
664 "232"
665 ],
666 "content-type": [
667 "application/x-www-form-urlencoded"
668 ],
669 "user-agent": [
670 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
671 ],
672 "accept": [
673 "application/mercurial-0.1"
674 ],
675 "host": [
676 "phab.mercurial-scm.org"
677 ]
678 },
679 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22corpus%22%3A+%22modified+2%5Cn%5CnDifferential+Revision%3A+https%3A%2F%2Fphab.mercurial-scm.org%2FD8433%22%7D",
680 "uri": "https://phab.mercurial-scm.org//api/differential.parsecommitmessage",
681 "method": "POST"
682 }
683 },
684 {
685 "response": {
686 "headers": {
687 "referrer-policy": [
688 "no-referrer"
689 ],
690 "x-frame-options": [
691 "Deny"
692 ],
693 "date": [
694 "Wed, 15 Apr 2020 23:43:49 GMT"
695 ],
696 "server": [
697 "Apache/2.4.10 (Debian)"
698 ],
699 "content-type": [
700 "application/json"
701 ],
702 "expires": [
703 "Sat, 01 Jan 2000 00:00:00 GMT"
704 ],
705 "transfer-encoding": [
706 "chunked"
707 ],
708 "x-xss-protection": [
709 "1; mode=block"
710 ],
711 "strict-transport-security": [
712 "max-age=0; includeSubdomains; preload"
713 ],
714 "cache-control": [
715 "no-store"
716 ],
717 "x-content-type-options": [
718 "nosniff"
719 ]
720 },
721 "status": {
722 "code": 200,
723 "message": "OK"
724 },
725 "body": {
726 "string": "{\"result\":{\"object\":{\"id\":8433,\"phid\":\"PHID-DREV-kpkwhtylyxrzikfspl5r\"},\"transactions\":[{\"phid\":\"PHID-XACT-DREV-vhgtprwan3k3k6l\"}]},\"error_code\":null,\"error_info\":null}"
727 }
728 },
729 "request": {
730 "headers": {
731 "content-length": [
732 "353"
733 ],
734 "content-type": [
735 "application/x-www-form-urlencoded"
736 ],
737 "user-agent": [
738 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
739 ],
740 "accept": [
741 "application/mercurial-0.1"
742 ],
743 "host": [
744 "phab.mercurial-scm.org"
745 ]
746 },
747 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22objectIdentifier%22%3A+8433%2C+%22transactions%22%3A+%5B%7B%22type%22%3A+%22parents.set%22%2C+%22value%22%3A+%5B%22PHID-DREV-l5ocnglddqa4hwbdzcky%22%5D%7D%2C+%7B%22type%22%3A+%22title%22%2C+%22value%22%3A+%22modified+2%22%7D%5D%7D",
748 "uri": "https://phab.mercurial-scm.org//api/differential.revision.edit",
749 "method": "POST"
750 }
751 },
752 {
753 "response": {
754 "headers": {
755 "referrer-policy": [
756 "no-referrer"
757 ],
758 "x-frame-options": [
759 "Deny"
760 ],
761 "date": [
762 "Wed, 15 Apr 2020 23:43:50 GMT"
763 ],
764 "server": [
765 "Apache/2.4.10 (Debian)"
766 ],
767 "content-type": [
768 "application/json"
769 ],
770 "expires": [
771 "Sat, 01 Jan 2000 00:00:00 GMT"
772 ],
773 "transfer-encoding": [
774 "chunked"
775 ],
776 "x-xss-protection": [
777 "1; mode=block"
778 ],
779 "strict-transport-security": [
780 "max-age=0; includeSubdomains; preload"
781 ],
782 "cache-control": [
783 "no-store"
784 ],
785 "x-content-type-options": [
786 "nosniff"
787 ]
788 },
789 "status": {
790 "code": 200,
791 "message": "OK"
792 },
793 "body": {
794 "string": "{\"result\":[{\"id\":\"8434\",\"phid\":\"PHID-DREV-l5ocnglddqa4hwbdzcky\",\"title\":\"modified 1\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/D8434\",\"dateCreated\":\"1586994227\",\"dateModified\":\"1586994229\",\"authorPHID\":\"PHID-USER-tzhaient733lwrlbcag5\",\"status\":\"0\",\"statusName\":\"Needs Review\",\"properties\":{\"draft.broadcast\":true,\"lines.added\":1,\"lines.removed\":1},\"branch\":\"default\",\"summary\":\"\",\"testPlan\":\"\",\"lineCount\":\"2\",\"activeDiffPHID\":\"PHID-DIFF-qat4sqpqqvytzhf7rpti\",\"diffs\":[\"21111\"],\"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\":\"\\/\"},{\"id\":\"8433\",\"phid\":\"PHID-DREV-kpkwhtylyxrzikfspl5r\",\"title\":\"modified 2\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/D8433\",\"dateCreated\":\"1586994221\",\"dateModified\":\"1586994229\",\"authorPHID\":\"PHID-USER-tzhaient733lwrlbcag5\",\"status\":\"0\",\"statusName\":\"Needs Review\",\"properties\":{\"draft.broadcast\":true,\"lines.added\":1,\"lines.removed\":1},\"branch\":\"default\",\"summary\":\"\",\"testPlan\":\"\",\"lineCount\":\"2\",\"activeDiffPHID\":\"PHID-DIFF-g25jdc5b5khduwpp3p3b\",\"diffs\":[\"21110\"],\"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-l5ocnglddqa4hwbdzcky\"]},\"repositoryPHID\":\"PHID-REPO-bvunnehri4u2isyr7bc3\",\"sourcePath\":\"\\/\"}],\"error_code\":null,\"error_info\":null}"
795 }
796 },
797 "request": {
798 "headers": {
799 "content-length": [
800 "154"
801 ],
802 "content-type": [
803 "application/x-www-form-urlencoded"
804 ],
805 "user-agent": [
806 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
807 ],
808 "accept": [
809 "application/mercurial-0.1"
810 ],
811 "host": [
812 "phab.mercurial-scm.org"
813 ]
814 },
815 "body": "output=json&__conduit__=1&params=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22ids%22%3A+%5B8434%2C+8433%5D%7D",
816 "uri": "https://phab.mercurial-scm.org//api/differential.query",
817 "method": "POST"
818 }
819 },
820 {
821 "response": {
822 "headers": {
823 "referrer-policy": [
824 "no-referrer"
825 ],
826 "x-frame-options": [
827 "Deny"
828 ],
829 "date": [
830 "Wed, 15 Apr 2020 23:43:50 GMT"
831 ],
832 "server": [
833 "Apache/2.4.10 (Debian)"
834 ],
835 "content-type": [
836 "application/json"
837 ],
838 "expires": [
839 "Sat, 01 Jan 2000 00:00:00 GMT"
840 ],
841 "transfer-encoding": [
842 "chunked"
843 ],
844 "x-xss-protection": [
845 "1; mode=block"
846 ],
847 "strict-transport-security": [
848 "max-age=0; includeSubdomains; preload"
849 ],
850 "cache-control": [
851 "no-store"
852 ],
853 "x-content-type-options": [
854 "nosniff"
855 ]
856 },
857 "status": {
858 "code": 200,
859 "message": "OK"
860 },
861 "body": {
862 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
863 }
864 },
865 "request": {
866 "headers": {
867 "content-length": [
868 "482"
869 ],
870 "content-type": [
871 "application/x-www-form-urlencoded"
872 ],
873 "user-agent": [
874 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
875 ],
876 "accept": [
877 "application/mercurial-0.1"
878 ],
879 "host": [
880 "phab.mercurial-scm.org"
881 ]
882 },
883 "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%22876a60d024de493e35a1c6f963f2604056cdc0b9%5C%22%2C+%5C%22parent%5C%22%3A+%5C%225cbade24e0fae40d67c568e86a978a2a946b9aed%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+21111%2C+%22name%22%3A+%22hg%3Ameta%22%7D",
884 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty",
885 "method": "POST"
886 }
887 },
888 {
889 "response": {
890 "headers": {
891 "referrer-policy": [
892 "no-referrer"
893 ],
894 "x-frame-options": [
895 "Deny"
896 ],
897 "date": [
898 "Wed, 15 Apr 2020 23:43:51 GMT"
899 ],
900 "server": [
901 "Apache/2.4.10 (Debian)"
902 ],
903 "content-type": [
904 "application/json"
905 ],
906 "expires": [
907 "Sat, 01 Jan 2000 00:00:00 GMT"
908 ],
909 "transfer-encoding": [
910 "chunked"
911 ],
912 "x-xss-protection": [
913 "1; mode=block"
914 ],
915 "strict-transport-security": [
916 "max-age=0; includeSubdomains; preload"
917 ],
918 "cache-control": [
919 "no-store"
920 ],
921 "x-content-type-options": [
922 "nosniff"
923 ]
924 },
925 "status": {
926 "code": 200,
927 "message": "OK"
928 },
929 "body": {
930 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
931 }
932 },
933 "request": {
934 "headers": {
935 "content-length": [
936 "594"
937 ],
938 "content-type": [
939 "application/x-www-form-urlencoded"
940 ],
941 "user-agent": [
942 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
943 ],
944 "accept": [
945 "application/mercurial-0.1"
946 ],
947 "host": [
948 "phab.mercurial-scm.org"
949 ]
950 },
951 "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%22876a60d024de493e35a1c6f963f2604056cdc0b9%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%22876a60d024de493e35a1c6f963f2604056cdc0b9%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%225cbade24e0fae40d67c568e86a978a2a946b9aed%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+21111%2C+%22name%22%3A+%22local%3Acommits%22%7D",
952 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty",
953 "method": "POST"
954 }
955 },
956 {
957 "response": {
958 "headers": {
959 "referrer-policy": [
960 "no-referrer"
961 ],
962 "x-frame-options": [
963 "Deny"
964 ],
965 "date": [
966 "Wed, 15 Apr 2020 23:43:51 GMT"
967 ],
968 "server": [
969 "Apache/2.4.10 (Debian)"
970 ],
971 "content-type": [
972 "application/json"
973 ],
974 "expires": [
975 "Sat, 01 Jan 2000 00:00:00 GMT"
976 ],
977 "transfer-encoding": [
978 "chunked"
979 ],
980 "x-xss-protection": [
981 "1; mode=block"
982 ],
983 "strict-transport-security": [
984 "max-age=0; includeSubdomains; preload"
985 ],
986 "cache-control": [
987 "no-store"
988 ],
989 "x-content-type-options": [
990 "nosniff"
991 ]
992 },
993 "status": {
994 "code": 200,
995 "message": "OK"
996 },
997 "body": {
998 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
999 }
1000 },
1001 "request": {
1002 "headers": {
1003 "content-length": [
1004 "488"
1005 ],
1006 "content-type": [
1007 "application/x-www-form-urlencoded"
1008 ],
1009 "user-agent": [
1010 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
1011 ],
1012 "accept": [
1013 "application/mercurial-0.1"
1014 ],
1015 "host": [
1016 "phab.mercurial-scm.org"
1017 ]
1018 },
1019 "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%220c6523cb1d0f560a958bcc0f4f938c91cb1141dc%5C%22%2C+%5C%22parent%5C%22%3A+%5C%22876a60d024de493e35a1c6f963f2604056cdc0b9%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+%2221110%22%2C+%22name%22%3A+%22hg%3Ameta%22%7D",
1020 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty",
1021 "method": "POST"
1022 }
1023 },
1024 {
1025 "response": {
1026 "headers": {
1027 "referrer-policy": [
1028 "no-referrer"
1029 ],
1030 "x-frame-options": [
1031 "Deny"
1032 ],
1033 "date": [
1034 "Wed, 15 Apr 2020 23:43:52 GMT"
1035 ],
1036 "server": [
1037 "Apache/2.4.10 (Debian)"
1038 ],
1039 "content-type": [
1040 "application/json"
1041 ],
1042 "expires": [
1043 "Sat, 01 Jan 2000 00:00:00 GMT"
1044 ],
1045 "transfer-encoding": [
1046 "chunked"
1047 ],
1048 "x-xss-protection": [
1049 "1; mode=block"
1050 ],
1051 "strict-transport-security": [
1052 "max-age=0; includeSubdomains; preload"
1053 ],
1054 "cache-control": [
1055 "no-store"
1056 ],
1057 "x-content-type-options": [
1058 "nosniff"
1059 ]
1060 },
1061 "status": {
1062 "code": 200,
1063 "message": "OK"
1064 },
1065 "body": {
1066 "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
1067 }
1068 },
1069 "request": {
1070 "headers": {
1071 "content-length": [
1072 "600"
1073 ],
1074 "content-type": [
1075 "application/x-www-form-urlencoded"
1076 ],
1077 "user-agent": [
1078 "mercurial/proto-1.0 (Mercurial 5.3.2+497-112d1ad30f88)"
1079 ],
1080 "accept": [
1081 "application/mercurial-0.1"
1082 ],
1083 "host": [
1084 "phab.mercurial-scm.org"
1085 ]
1086 },
1087 "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%220c6523cb1d0f560a958bcc0f4f938c91cb1141dc%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%220c6523cb1d0f560a958bcc0f4f938c91cb1141dc%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%22876a60d024de493e35a1c6f963f2604056cdc0b9%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+%2221110%22%2C+%22name%22%3A+%22local%3Acommits%22%7D",
1088 "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty",
1089 "method": "POST"
1090 }
1091 }
1092 ]
1093 } No newline at end of file
@@ -1,2219 +1,2223 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, short
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 copies,
65 65 encoding,
66 66 error,
67 67 exthelper,
68 68 graphmod,
69 69 httpconnection as httpconnectionmod,
70 70 localrepo,
71 71 logcmdutil,
72 72 match,
73 73 mdiff,
74 74 obsutil,
75 75 parser,
76 76 patch,
77 77 phases,
78 78 pycompat,
79 79 scmutil,
80 80 smartset,
81 81 tags,
82 82 templatefilters,
83 83 templateutil,
84 84 url as urlmod,
85 85 util,
86 86 )
87 87 from mercurial.utils import (
88 88 procutil,
89 89 stringutil,
90 90 )
91 91 from . import show
92 92
93 93
94 94 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
95 95 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
96 96 # be specifying the version(s) of Mercurial they are tested with, or
97 97 # leave the attribute unspecified.
98 98 testedwith = b'ships-with-hg-core'
99 99
100 100 eh = exthelper.exthelper()
101 101
102 102 cmdtable = eh.cmdtable
103 103 command = eh.command
104 104 configtable = eh.configtable
105 105 templatekeyword = eh.templatekeyword
106 106 uisetup = eh.finaluisetup
107 107
108 108 # developer config: phabricator.batchsize
109 109 eh.configitem(
110 110 b'phabricator', b'batchsize', default=12,
111 111 )
112 112 eh.configitem(
113 113 b'phabricator', b'callsign', default=None,
114 114 )
115 115 eh.configitem(
116 116 b'phabricator', b'curlcmd', default=None,
117 117 )
118 118 # developer config: phabricator.debug
119 119 eh.configitem(
120 120 b'phabricator', b'debug', default=False,
121 121 )
122 122 # developer config: phabricator.repophid
123 123 eh.configitem(
124 124 b'phabricator', b'repophid', default=None,
125 125 )
126 126 eh.configitem(
127 127 b'phabricator', b'url', default=None,
128 128 )
129 129 eh.configitem(
130 130 b'phabsend', b'confirm', default=False,
131 131 )
132 132 eh.configitem(
133 133 b'phabimport', b'secret', default=False,
134 134 )
135 135 eh.configitem(
136 136 b'phabimport', b'obsolete', default=False,
137 137 )
138 138
139 139 colortable = {
140 140 b'phabricator.action.created': b'green',
141 141 b'phabricator.action.skipped': b'magenta',
142 142 b'phabricator.action.updated': b'magenta',
143 143 b'phabricator.desc': b'',
144 144 b'phabricator.drev': b'bold',
145 145 b'phabricator.node': b'',
146 146 b'phabricator.status.abandoned': b'magenta dim',
147 147 b'phabricator.status.accepted': b'green bold',
148 148 b'phabricator.status.closed': b'green',
149 149 b'phabricator.status.needsreview': b'yellow',
150 150 b'phabricator.status.needsrevision': b'red',
151 151 b'phabricator.status.changesplanned': b'red',
152 152 }
153 153
154 154 _VCR_FLAGS = [
155 155 (
156 156 b'',
157 157 b'test-vcr',
158 158 b'',
159 159 _(
160 160 b'Path to a vcr file. If nonexistent, will record a new vcr transcript'
161 161 b', otherwise will mock all http requests using the specified vcr file.'
162 162 b' (ADVANCED)'
163 163 ),
164 164 ),
165 165 ]
166 166
167 167
168 168 @eh.wrapfunction(localrepo, "loadhgrc")
169 169 def _loadhgrc(orig, ui, wdirvfs, hgvfs, requirements):
170 170 """Load ``.arcconfig`` content into a ui instance on repository open.
171 171 """
172 172 result = False
173 173 arcconfig = {}
174 174
175 175 try:
176 176 # json.loads only accepts bytes from 3.6+
177 177 rawparams = encoding.unifromlocal(wdirvfs.read(b".arcconfig"))
178 178 # json.loads only returns unicode strings
179 179 arcconfig = pycompat.rapply(
180 180 lambda x: encoding.unitolocal(x)
181 181 if isinstance(x, pycompat.unicode)
182 182 else x,
183 183 pycompat.json_loads(rawparams),
184 184 )
185 185
186 186 result = True
187 187 except ValueError:
188 188 ui.warn(_(b"invalid JSON in %s\n") % wdirvfs.join(b".arcconfig"))
189 189 except IOError:
190 190 pass
191 191
192 192 cfg = util.sortdict()
193 193
194 194 if b"repository.callsign" in arcconfig:
195 195 cfg[(b"phabricator", b"callsign")] = arcconfig[b"repository.callsign"]
196 196
197 197 if b"phabricator.uri" in arcconfig:
198 198 cfg[(b"phabricator", b"url")] = arcconfig[b"phabricator.uri"]
199 199
200 200 if cfg:
201 201 ui.applyconfig(cfg, source=wdirvfs.join(b".arcconfig"))
202 202
203 203 return orig(ui, wdirvfs, hgvfs, requirements) or result # Load .hg/hgrc
204 204
205 205
206 206 def vcrcommand(name, flags, spec, helpcategory=None, optionalrepo=False):
207 207 fullflags = flags + _VCR_FLAGS
208 208
209 209 def hgmatcher(r1, r2):
210 210 if r1.uri != r2.uri or r1.method != r2.method:
211 211 return False
212 212 r1params = util.urlreq.parseqs(r1.body)
213 213 r2params = util.urlreq.parseqs(r2.body)
214 214 for key in r1params:
215 215 if key not in r2params:
216 216 return False
217 217 value = r1params[key][0]
218 218 # we want to compare json payloads without worrying about ordering
219 219 if value.startswith(b'{') and value.endswith(b'}'):
220 220 r1json = pycompat.json_loads(value)
221 221 r2json = pycompat.json_loads(r2params[key][0])
222 222 if r1json != r2json:
223 223 return False
224 224 elif r2params[key][0] != value:
225 225 return False
226 226 return True
227 227
228 228 def sanitiserequest(request):
229 229 request.body = re.sub(
230 230 br'cli-[a-z0-9]+', br'cli-hahayouwish', request.body
231 231 )
232 232 return request
233 233
234 234 def sanitiseresponse(response):
235 235 if 'set-cookie' in response['headers']:
236 236 del response['headers']['set-cookie']
237 237 return response
238 238
239 239 def decorate(fn):
240 240 def inner(*args, **kwargs):
241 241 cassette = pycompat.fsdecode(kwargs.pop('test_vcr', None))
242 242 if cassette:
243 243 import hgdemandimport
244 244
245 245 with hgdemandimport.deactivated():
246 246 import vcr as vcrmod
247 247 import vcr.stubs as stubs
248 248
249 249 vcr = vcrmod.VCR(
250 250 serializer='json',
251 251 before_record_request=sanitiserequest,
252 252 before_record_response=sanitiseresponse,
253 253 custom_patches=[
254 254 (
255 255 urlmod,
256 256 'httpconnection',
257 257 stubs.VCRHTTPConnection,
258 258 ),
259 259 (
260 260 urlmod,
261 261 'httpsconnection',
262 262 stubs.VCRHTTPSConnection,
263 263 ),
264 264 ],
265 265 )
266 266 vcr.register_matcher('hgmatcher', hgmatcher)
267 267 with vcr.use_cassette(cassette, match_on=['hgmatcher']):
268 268 return fn(*args, **kwargs)
269 269 return fn(*args, **kwargs)
270 270
271 271 cmd = util.checksignature(inner, depth=2)
272 272 cmd.__name__ = fn.__name__
273 273 cmd.__doc__ = fn.__doc__
274 274
275 275 return command(
276 276 name,
277 277 fullflags,
278 278 spec,
279 279 helpcategory=helpcategory,
280 280 optionalrepo=optionalrepo,
281 281 )(cmd)
282 282
283 283 return decorate
284 284
285 285
286 286 def _debug(ui, *msg, **opts):
287 287 """write debug output for Phabricator if ``phabricator.debug`` is set
288 288
289 289 Specifically, this avoids dumping Conduit and HTTP auth chatter that is
290 290 printed with the --debug argument.
291 291 """
292 292 if ui.configbool(b"phabricator", b"debug"):
293 293 flag = ui.debugflag
294 294 try:
295 295 ui.debugflag = True
296 296 ui.write(*msg, **opts)
297 297 finally:
298 298 ui.debugflag = flag
299 299
300 300
301 301 def urlencodenested(params):
302 302 """like urlencode, but works with nested parameters.
303 303
304 304 For example, if params is {'a': ['b', 'c'], 'd': {'e': 'f'}}, it will be
305 305 flattened to {'a[0]': 'b', 'a[1]': 'c', 'd[e]': 'f'} and then passed to
306 306 urlencode. Note: the encoding is consistent with PHP's http_build_query.
307 307 """
308 308 flatparams = util.sortdict()
309 309
310 310 def process(prefix, obj):
311 311 if isinstance(obj, bool):
312 312 obj = {True: b'true', False: b'false'}[obj] # Python -> PHP form
313 313 lister = lambda l: [(b'%d' % k, v) for k, v in enumerate(l)]
314 314 items = {list: lister, dict: lambda x: x.items()}.get(type(obj))
315 315 if items is None:
316 316 flatparams[prefix] = obj
317 317 else:
318 318 for k, v in items(obj):
319 319 if prefix:
320 320 process(b'%s[%s]' % (prefix, k), v)
321 321 else:
322 322 process(k, v)
323 323
324 324 process(b'', params)
325 325 return util.urlreq.urlencode(flatparams)
326 326
327 327
328 328 def readurltoken(ui):
329 329 """return conduit url, token and make sure they exist
330 330
331 331 Currently read from [auth] config section. In the future, it might
332 332 make sense to read from .arcconfig and .arcrc as well.
333 333 """
334 334 url = ui.config(b'phabricator', b'url')
335 335 if not url:
336 336 raise error.Abort(
337 337 _(b'config %s.%s is required') % (b'phabricator', b'url')
338 338 )
339 339
340 340 res = httpconnectionmod.readauthforuri(ui, url, util.url(url).user)
341 341 token = None
342 342
343 343 if res:
344 344 group, auth = res
345 345
346 346 ui.debug(b"using auth.%s.* for authentication\n" % group)
347 347
348 348 token = auth.get(b'phabtoken')
349 349
350 350 if not token:
351 351 raise error.Abort(
352 352 _(b'Can\'t find conduit token associated to %s') % (url,)
353 353 )
354 354
355 355 return url, token
356 356
357 357
358 358 def callconduit(ui, name, params):
359 359 """call Conduit API, params is a dict. return json.loads result, or None"""
360 360 host, token = readurltoken(ui)
361 361 url, authinfo = util.url(b'/'.join([host, b'api', name])).authinfo()
362 362 ui.debug(b'Conduit Call: %s %s\n' % (url, pycompat.byterepr(params)))
363 363 params = params.copy()
364 364 params[b'__conduit__'] = {
365 365 b'token': token,
366 366 }
367 367 rawdata = {
368 368 b'params': templatefilters.json(params),
369 369 b'output': b'json',
370 370 b'__conduit__': 1,
371 371 }
372 372 data = urlencodenested(rawdata)
373 373 curlcmd = ui.config(b'phabricator', b'curlcmd')
374 374 if curlcmd:
375 375 sin, sout = procutil.popen2(
376 376 b'%s -d @- %s' % (curlcmd, procutil.shellquote(url))
377 377 )
378 378 sin.write(data)
379 379 sin.close()
380 380 body = sout.read()
381 381 else:
382 382 urlopener = urlmod.opener(ui, authinfo)
383 383 request = util.urlreq.request(pycompat.strurl(url), data=data)
384 384 with contextlib.closing(urlopener.open(request)) as rsp:
385 385 body = rsp.read()
386 386 ui.debug(b'Conduit Response: %s\n' % body)
387 387 parsed = pycompat.rapply(
388 388 lambda x: encoding.unitolocal(x)
389 389 if isinstance(x, pycompat.unicode)
390 390 else x,
391 391 # json.loads only accepts bytes from py3.6+
392 392 pycompat.json_loads(encoding.unifromlocal(body)),
393 393 )
394 394 if parsed.get(b'error_code'):
395 395 msg = _(b'Conduit Error (%s): %s') % (
396 396 parsed[b'error_code'],
397 397 parsed[b'error_info'],
398 398 )
399 399 raise error.Abort(msg)
400 400 return parsed[b'result']
401 401
402 402
403 403 @vcrcommand(b'debugcallconduit', [], _(b'METHOD'), optionalrepo=True)
404 404 def debugcallconduit(ui, repo, name):
405 405 """call Conduit API
406 406
407 407 Call parameters are read from stdin as a JSON blob. Result will be written
408 408 to stdout as a JSON blob.
409 409 """
410 410 # json.loads only accepts bytes from 3.6+
411 411 rawparams = encoding.unifromlocal(ui.fin.read())
412 412 # json.loads only returns unicode strings
413 413 params = pycompat.rapply(
414 414 lambda x: encoding.unitolocal(x)
415 415 if isinstance(x, pycompat.unicode)
416 416 else x,
417 417 pycompat.json_loads(rawparams),
418 418 )
419 419 # json.dumps only accepts unicode strings
420 420 result = pycompat.rapply(
421 421 lambda x: encoding.unifromlocal(x) if isinstance(x, bytes) else x,
422 422 callconduit(ui, name, params),
423 423 )
424 424 s = json.dumps(result, sort_keys=True, indent=2, separators=(u',', u': '))
425 425 ui.write(b'%s\n' % encoding.unitolocal(s))
426 426
427 427
428 428 def getrepophid(repo):
429 429 """given callsign, return repository PHID or None"""
430 430 # developer config: phabricator.repophid
431 431 repophid = repo.ui.config(b'phabricator', b'repophid')
432 432 if repophid:
433 433 return repophid
434 434 callsign = repo.ui.config(b'phabricator', b'callsign')
435 435 if not callsign:
436 436 return None
437 437 query = callconduit(
438 438 repo.ui,
439 439 b'diffusion.repository.search',
440 440 {b'constraints': {b'callsigns': [callsign]}},
441 441 )
442 442 if len(query[b'data']) == 0:
443 443 return None
444 444 repophid = query[b'data'][0][b'phid']
445 445 repo.ui.setconfig(b'phabricator', b'repophid', repophid)
446 446 return repophid
447 447
448 448
449 449 _differentialrevisiontagre = re.compile(br'\AD([1-9][0-9]*)\Z')
450 450 _differentialrevisiondescre = re.compile(
451 451 br'^Differential Revision:\s*(?P<url>(?:.*)D(?P<id>[1-9][0-9]*))$', re.M
452 452 )
453 453
454 454
455 455 def getoldnodedrevmap(repo, nodelist):
456 456 """find previous nodes that has been sent to Phabricator
457 457
458 458 return {node: (oldnode, Differential diff, Differential Revision ID)}
459 459 for node in nodelist with known previous sent versions, or associated
460 460 Differential Revision IDs. ``oldnode`` and ``Differential diff`` could
461 461 be ``None``.
462 462
463 463 Examines commit messages like "Differential Revision:" to get the
464 464 association information.
465 465
466 466 If such commit message line is not found, examines all precursors and their
467 467 tags. Tags with format like "D1234" are considered a match and the node
468 468 with that tag, and the number after "D" (ex. 1234) will be returned.
469 469
470 470 The ``old node``, if not None, is guaranteed to be the last diff of
471 471 corresponding Differential Revision, and exist in the repo.
472 472 """
473 473 unfi = repo.unfiltered()
474 474 has_node = unfi.changelog.index.has_node
475 475
476 476 result = {} # {node: (oldnode?, lastdiff?, drev)}
477 477 # ordered for test stability when printing new -> old mapping below
478 478 toconfirm = util.sortdict() # {node: (force, {precnode}, drev)}
479 479 for node in nodelist:
480 480 ctx = unfi[node]
481 481 # For tags like "D123", put them into "toconfirm" to verify later
482 482 precnodes = list(obsutil.allpredecessors(unfi.obsstore, [node]))
483 483 for n in precnodes:
484 484 if has_node(n):
485 485 for tag in unfi.nodetags(n):
486 486 m = _differentialrevisiontagre.match(tag)
487 487 if m:
488 488 toconfirm[node] = (0, set(precnodes), int(m.group(1)))
489 489 break
490 490 else:
491 491 continue # move to next predecessor
492 492 break # found a tag, stop
493 493 else:
494 494 # Check commit message
495 495 m = _differentialrevisiondescre.search(ctx.description())
496 496 if m:
497 497 toconfirm[node] = (1, set(precnodes), int(m.group('id')))
498 498
499 499 # Double check if tags are genuine by collecting all old nodes from
500 500 # Phabricator, and expect precursors overlap with it.
501 501 if toconfirm:
502 502 drevs = [drev for force, precs, drev in toconfirm.values()]
503 503 alldiffs = callconduit(
504 504 unfi.ui, b'differential.querydiffs', {b'revisionIDs': drevs}
505 505 )
506 506
507 507 def getnodes(d, precset):
508 508 # Ignore other nodes that were combined into the Differential
509 509 # that aren't predecessors of the current local node.
510 510 return [n for n in getlocalcommits(d) if n in precset]
511 511
512 512 for newnode, (force, precset, drev) in toconfirm.items():
513 513 diffs = [
514 514 d for d in alldiffs.values() if int(d[b'revisionID']) == drev
515 515 ]
516 516
517 517 # local predecessors known by Phabricator
518 518 phprecset = {n for d in diffs for n in getnodes(d, precset)}
519 519
520 520 # Ignore if precursors (Phabricator and local repo) do not overlap,
521 521 # and force is not set (when commit message says nothing)
522 522 if not force and not phprecset:
523 523 tagname = b'D%d' % drev
524 524 tags.tag(
525 525 repo,
526 526 tagname,
527 527 nullid,
528 528 message=None,
529 529 user=None,
530 530 date=None,
531 531 local=True,
532 532 )
533 533 unfi.ui.warn(
534 534 _(
535 535 b'D%d: local tag removed - does not match '
536 536 b'Differential history\n'
537 537 )
538 538 % drev
539 539 )
540 540 continue
541 541
542 542 # Find the last node using Phabricator metadata, and make sure it
543 543 # exists in the repo
544 544 oldnode = lastdiff = None
545 545 if diffs:
546 546 lastdiff = max(diffs, key=lambda d: int(d[b'id']))
547 547 oldnodes = getnodes(lastdiff, precset)
548 548
549 549 _debug(
550 550 unfi.ui,
551 551 b"%s mapped to old nodes %s\n"
552 552 % (
553 553 short(newnode),
554 554 stringutil.pprint([short(n) for n in sorted(oldnodes)]),
555 555 ),
556 556 )
557 557
558 558 # If this commit was the result of `hg fold` after submission,
559 559 # and now resubmitted with --fold, the easiest thing to do is
560 560 # to leave the node clear. This only results in creating a new
561 561 # diff for the _same_ Differential Revision if this commit is
562 562 # the first or last in the selected range. If we picked a node
563 563 # from the list instead, it would have to be the lowest if at
564 564 # the beginning of the --fold range, or the highest at the end.
565 565 # Otherwise, one or more of the nodes wouldn't be considered in
566 566 # the diff, and the Differential wouldn't be properly updated.
567 567 # If this commit is the result of `hg split` in the same
568 568 # scenario, there is a single oldnode here (and multiple
569 569 # newnodes mapped to it). That makes it the same as the normal
570 570 # case, as the edges of the newnode range cleanly maps to one
571 571 # oldnode each.
572 572 if len(oldnodes) == 1:
573 573 oldnode = oldnodes[0]
574 574 if oldnode and not has_node(oldnode):
575 575 oldnode = None
576 576
577 577 result[newnode] = (oldnode, lastdiff, drev)
578 578
579 579 return result
580 580
581 581
582 582 def getdrevmap(repo, revs):
583 583 """Return a dict mapping each rev in `revs` to their Differential Revision
584 584 ID or None.
585 585 """
586 586 result = {}
587 587 for rev in revs:
588 588 result[rev] = None
589 589 ctx = repo[rev]
590 590 # Check commit message
591 591 m = _differentialrevisiondescre.search(ctx.description())
592 592 if m:
593 593 result[rev] = int(m.group('id'))
594 594 continue
595 595 # Check tags
596 596 for tag in repo.nodetags(ctx.node()):
597 597 m = _differentialrevisiontagre.match(tag)
598 598 if m:
599 599 result[rev] = int(m.group(1))
600 600 break
601 601
602 602 return result
603 603
604 604
605 605 def getdiff(basectx, ctx, diffopts):
606 606 """plain-text diff without header (user, commit message, etc)"""
607 607 output = util.stringio()
608 608 for chunk, _label in patch.diffui(
609 609 ctx.repo(), basectx.p1().node(), ctx.node(), None, opts=diffopts
610 610 ):
611 611 output.write(chunk)
612 612 return output.getvalue()
613 613
614 614
615 615 class DiffChangeType(object):
616 616 ADD = 1
617 617 CHANGE = 2
618 618 DELETE = 3
619 619 MOVE_AWAY = 4
620 620 COPY_AWAY = 5
621 621 MOVE_HERE = 6
622 622 COPY_HERE = 7
623 623 MULTICOPY = 8
624 624
625 625
626 626 class DiffFileType(object):
627 627 TEXT = 1
628 628 IMAGE = 2
629 629 BINARY = 3
630 630
631 631
632 632 @attr.s
633 633 class phabhunk(dict):
634 634 """Represents a Differential hunk, which is owned by a Differential change
635 635 """
636 636
637 637 oldOffset = attr.ib(default=0) # camelcase-required
638 638 oldLength = attr.ib(default=0) # camelcase-required
639 639 newOffset = attr.ib(default=0) # camelcase-required
640 640 newLength = attr.ib(default=0) # camelcase-required
641 641 corpus = attr.ib(default='')
642 642 # These get added to the phabchange's equivalents
643 643 addLines = attr.ib(default=0) # camelcase-required
644 644 delLines = attr.ib(default=0) # camelcase-required
645 645
646 646
647 647 @attr.s
648 648 class phabchange(object):
649 649 """Represents a Differential change, owns Differential hunks and owned by a
650 650 Differential diff. Each one represents one file in a diff.
651 651 """
652 652
653 653 currentPath = attr.ib(default=None) # camelcase-required
654 654 oldPath = attr.ib(default=None) # camelcase-required
655 655 awayPaths = attr.ib(default=attr.Factory(list)) # camelcase-required
656 656 metadata = attr.ib(default=attr.Factory(dict))
657 657 oldProperties = attr.ib(default=attr.Factory(dict)) # camelcase-required
658 658 newProperties = attr.ib(default=attr.Factory(dict)) # camelcase-required
659 659 type = attr.ib(default=DiffChangeType.CHANGE)
660 660 fileType = attr.ib(default=DiffFileType.TEXT) # camelcase-required
661 661 commitHash = attr.ib(default=None) # camelcase-required
662 662 addLines = attr.ib(default=0) # camelcase-required
663 663 delLines = attr.ib(default=0) # camelcase-required
664 664 hunks = attr.ib(default=attr.Factory(list))
665 665
666 666 def copynewmetadatatoold(self):
667 667 for key in list(self.metadata.keys()):
668 668 newkey = key.replace(b'new:', b'old:')
669 669 self.metadata[newkey] = self.metadata[key]
670 670
671 671 def addoldmode(self, value):
672 672 self.oldProperties[b'unix:filemode'] = value
673 673
674 674 def addnewmode(self, value):
675 675 self.newProperties[b'unix:filemode'] = value
676 676
677 677 def addhunk(self, hunk):
678 678 if not isinstance(hunk, phabhunk):
679 679 raise error.Abort(b'phabchange.addhunk only takes phabhunks')
680 680 self.hunks.append(pycompat.byteskwargs(attr.asdict(hunk)))
681 681 # It's useful to include these stats since the Phab web UI shows them,
682 682 # and uses them to estimate how large a change a Revision is. Also used
683 683 # in email subjects for the [+++--] bit.
684 684 self.addLines += hunk.addLines
685 685 self.delLines += hunk.delLines
686 686
687 687
688 688 @attr.s
689 689 class phabdiff(object):
690 690 """Represents a Differential diff, owns Differential changes. Corresponds
691 691 to a commit.
692 692 """
693 693
694 694 # Doesn't seem to be any reason to send this (output of uname -n)
695 695 sourceMachine = attr.ib(default=b'') # camelcase-required
696 696 sourcePath = attr.ib(default=b'/') # camelcase-required
697 697 sourceControlBaseRevision = attr.ib(default=b'0' * 40) # camelcase-required
698 698 sourceControlPath = attr.ib(default=b'/') # camelcase-required
699 699 sourceControlSystem = attr.ib(default=b'hg') # camelcase-required
700 700 branch = attr.ib(default=b'default')
701 701 bookmark = attr.ib(default=None)
702 702 creationMethod = attr.ib(default=b'phabsend') # camelcase-required
703 703 lintStatus = attr.ib(default=b'none') # camelcase-required
704 704 unitStatus = attr.ib(default=b'none') # camelcase-required
705 705 changes = attr.ib(default=attr.Factory(dict))
706 706 repositoryPHID = attr.ib(default=None) # camelcase-required
707 707
708 708 def addchange(self, change):
709 709 if not isinstance(change, phabchange):
710 710 raise error.Abort(b'phabdiff.addchange only takes phabchanges')
711 711 self.changes[change.currentPath] = pycompat.byteskwargs(
712 712 attr.asdict(change)
713 713 )
714 714
715 715
716 716 def maketext(pchange, basectx, ctx, fname):
717 717 """populate the phabchange for a text file"""
718 718 repo = ctx.repo()
719 719 fmatcher = match.exact([fname])
720 720 diffopts = mdiff.diffopts(git=True, context=32767)
721 721 _pfctx, _fctx, header, fhunks = next(
722 722 patch.diffhunks(repo, basectx.p1(), ctx, fmatcher, opts=diffopts)
723 723 )
724 724
725 725 for fhunk in fhunks:
726 726 (oldOffset, oldLength, newOffset, newLength), lines = fhunk
727 727 corpus = b''.join(lines[1:])
728 728 shunk = list(header)
729 729 shunk.extend(lines)
730 730 _mf, _mt, addLines, delLines, _hb = patch.diffstatsum(
731 731 patch.diffstatdata(util.iterlines(shunk))
732 732 )
733 733 pchange.addhunk(
734 734 phabhunk(
735 735 oldOffset,
736 736 oldLength,
737 737 newOffset,
738 738 newLength,
739 739 corpus,
740 740 addLines,
741 741 delLines,
742 742 )
743 743 )
744 744
745 745
746 746 def uploadchunks(fctx, fphid):
747 747 """upload large binary files as separate chunks.
748 748 Phab requests chunking over 8MiB, and splits into 4MiB chunks
749 749 """
750 750 ui = fctx.repo().ui
751 751 chunks = callconduit(ui, b'file.querychunks', {b'filePHID': fphid})
752 752 with ui.makeprogress(
753 753 _(b'uploading file chunks'), unit=_(b'chunks'), total=len(chunks)
754 754 ) as progress:
755 755 for chunk in chunks:
756 756 progress.increment()
757 757 if chunk[b'complete']:
758 758 continue
759 759 bstart = int(chunk[b'byteStart'])
760 760 bend = int(chunk[b'byteEnd'])
761 761 callconduit(
762 762 ui,
763 763 b'file.uploadchunk',
764 764 {
765 765 b'filePHID': fphid,
766 766 b'byteStart': bstart,
767 767 b'data': base64.b64encode(fctx.data()[bstart:bend]),
768 768 b'dataEncoding': b'base64',
769 769 },
770 770 )
771 771
772 772
773 773 def uploadfile(fctx):
774 774 """upload binary files to Phabricator"""
775 775 repo = fctx.repo()
776 776 ui = repo.ui
777 777 fname = fctx.path()
778 778 size = fctx.size()
779 779 fhash = pycompat.bytestr(hashlib.sha256(fctx.data()).hexdigest())
780 780
781 781 # an allocate call is required first to see if an upload is even required
782 782 # (Phab might already have it) and to determine if chunking is needed
783 783 allocateparams = {
784 784 b'name': fname,
785 785 b'contentLength': size,
786 786 b'contentHash': fhash,
787 787 }
788 788 filealloc = callconduit(ui, b'file.allocate', allocateparams)
789 789 fphid = filealloc[b'filePHID']
790 790
791 791 if filealloc[b'upload']:
792 792 ui.write(_(b'uploading %s\n') % bytes(fctx))
793 793 if not fphid:
794 794 uploadparams = {
795 795 b'name': fname,
796 796 b'data_base64': base64.b64encode(fctx.data()),
797 797 }
798 798 fphid = callconduit(ui, b'file.upload', uploadparams)
799 799 else:
800 800 uploadchunks(fctx, fphid)
801 801 else:
802 802 ui.debug(b'server already has %s\n' % bytes(fctx))
803 803
804 804 if not fphid:
805 805 raise error.Abort(b'Upload of %s failed.' % bytes(fctx))
806 806
807 807 return fphid
808 808
809 809
810 810 def addoldbinary(pchange, oldfctx, fctx):
811 811 """add the metadata for the previous version of a binary file to the
812 812 phabchange for the new version
813 813
814 814 ``oldfctx`` is the previous version of the file; ``fctx`` is the new
815 815 version of the file, or None if the file is being removed.
816 816 """
817 817 if not fctx or fctx.cmp(oldfctx):
818 818 # Files differ, add the old one
819 819 pchange.metadata[b'old:file:size'] = oldfctx.size()
820 820 mimeguess, _enc = mimetypes.guess_type(
821 821 encoding.unifromlocal(oldfctx.path())
822 822 )
823 823 if mimeguess:
824 824 pchange.metadata[b'old:file:mime-type'] = pycompat.bytestr(
825 825 mimeguess
826 826 )
827 827 fphid = uploadfile(oldfctx)
828 828 pchange.metadata[b'old:binary-phid'] = fphid
829 829 else:
830 830 # If it's left as IMAGE/BINARY web UI might try to display it
831 831 pchange.fileType = DiffFileType.TEXT
832 832 pchange.copynewmetadatatoold()
833 833
834 834
835 835 def makebinary(pchange, fctx):
836 836 """populate the phabchange for a binary file"""
837 837 pchange.fileType = DiffFileType.BINARY
838 838 fphid = uploadfile(fctx)
839 839 pchange.metadata[b'new:binary-phid'] = fphid
840 840 pchange.metadata[b'new:file:size'] = fctx.size()
841 841 mimeguess, _enc = mimetypes.guess_type(encoding.unifromlocal(fctx.path()))
842 842 if mimeguess:
843 843 mimeguess = pycompat.bytestr(mimeguess)
844 844 pchange.metadata[b'new:file:mime-type'] = mimeguess
845 845 if mimeguess.startswith(b'image/'):
846 846 pchange.fileType = DiffFileType.IMAGE
847 847
848 848
849 849 # Copied from mercurial/patch.py
850 850 gitmode = {b'l': b'120000', b'x': b'100755', b'': b'100644'}
851 851
852 852
853 853 def notutf8(fctx):
854 854 """detect non-UTF-8 text files since Phabricator requires them to be marked
855 855 as binary
856 856 """
857 857 try:
858 858 fctx.data().decode('utf-8')
859 859 return False
860 860 except UnicodeDecodeError:
861 861 fctx.repo().ui.write(
862 862 _(b'file %s detected as non-UTF-8, marked as binary\n')
863 863 % fctx.path()
864 864 )
865 865 return True
866 866
867 867
868 868 def addremoved(pdiff, basectx, ctx, removed):
869 869 """add removed files to the phabdiff. Shouldn't include moves"""
870 870 for fname in removed:
871 871 pchange = phabchange(
872 872 currentPath=fname, oldPath=fname, type=DiffChangeType.DELETE
873 873 )
874 874 oldfctx = basectx.p1()[fname]
875 875 pchange.addoldmode(gitmode[oldfctx.flags()])
876 876 if not (oldfctx.isbinary() or notutf8(oldfctx)):
877 877 maketext(pchange, basectx, ctx, fname)
878 878
879 879 pdiff.addchange(pchange)
880 880
881 881
882 882 def addmodified(pdiff, basectx, ctx, modified):
883 883 """add modified files to the phabdiff"""
884 884 for fname in modified:
885 885 fctx = ctx[fname]
886 886 oldfctx = basectx.p1()[fname]
887 887 pchange = phabchange(currentPath=fname, oldPath=fname)
888 888 filemode = gitmode[fctx.flags()]
889 889 originalmode = gitmode[oldfctx.flags()]
890 890 if filemode != originalmode:
891 891 pchange.addoldmode(originalmode)
892 892 pchange.addnewmode(filemode)
893 893
894 894 if (
895 895 fctx.isbinary()
896 896 or notutf8(fctx)
897 897 or oldfctx.isbinary()
898 898 or notutf8(oldfctx)
899 899 ):
900 900 makebinary(pchange, fctx)
901 901 addoldbinary(pchange, oldfctx, fctx)
902 902 else:
903 903 maketext(pchange, basectx, ctx, fname)
904 904
905 905 pdiff.addchange(pchange)
906 906
907 907
908 908 def addadded(pdiff, basectx, ctx, added, removed):
909 909 """add file adds to the phabdiff, both new files and copies/moves"""
910 910 # Keep track of files that've been recorded as moved/copied, so if there are
911 911 # additional copies we can mark them (moves get removed from removed)
912 912 copiedchanges = {}
913 913 movedchanges = {}
914 914
915 915 copy = {}
916 916 if basectx != ctx:
917 917 copy = copies.pathcopies(basectx.p1(), ctx)
918 918
919 919 for fname in added:
920 920 fctx = ctx[fname]
921 921 oldfctx = None
922 922 pchange = phabchange(currentPath=fname)
923 923
924 924 filemode = gitmode[fctx.flags()]
925 925
926 926 if copy:
927 927 originalfname = copy.get(fname, fname)
928 928 else:
929 929 originalfname = fname
930 930 if fctx.renamed():
931 931 originalfname = fctx.renamed()[0]
932 932
933 933 renamed = fname != originalfname
934 934
935 935 if renamed:
936 936 oldfctx = basectx.p1()[originalfname]
937 937 originalmode = gitmode[oldfctx.flags()]
938 938 pchange.oldPath = originalfname
939 939
940 940 if originalfname in removed:
941 941 origpchange = phabchange(
942 942 currentPath=originalfname,
943 943 oldPath=originalfname,
944 944 type=DiffChangeType.MOVE_AWAY,
945 945 awayPaths=[fname],
946 946 )
947 947 movedchanges[originalfname] = origpchange
948 948 removed.remove(originalfname)
949 949 pchange.type = DiffChangeType.MOVE_HERE
950 950 elif originalfname in movedchanges:
951 951 movedchanges[originalfname].type = DiffChangeType.MULTICOPY
952 952 movedchanges[originalfname].awayPaths.append(fname)
953 953 pchange.type = DiffChangeType.COPY_HERE
954 954 else: # pure copy
955 955 if originalfname not in copiedchanges:
956 956 origpchange = phabchange(
957 957 currentPath=originalfname, type=DiffChangeType.COPY_AWAY
958 958 )
959 959 copiedchanges[originalfname] = origpchange
960 960 else:
961 961 origpchange = copiedchanges[originalfname]
962 962 origpchange.awayPaths.append(fname)
963 963 pchange.type = DiffChangeType.COPY_HERE
964 964
965 965 if filemode != originalmode:
966 966 pchange.addoldmode(originalmode)
967 967 pchange.addnewmode(filemode)
968 968 else: # Brand-new file
969 969 pchange.addnewmode(gitmode[fctx.flags()])
970 970 pchange.type = DiffChangeType.ADD
971 971
972 972 if (
973 973 fctx.isbinary()
974 974 or notutf8(fctx)
975 975 or (oldfctx and (oldfctx.isbinary() or notutf8(oldfctx)))
976 976 ):
977 977 makebinary(pchange, fctx)
978 978 if renamed:
979 979 addoldbinary(pchange, oldfctx, fctx)
980 980 else:
981 981 maketext(pchange, basectx, ctx, fname)
982 982
983 983 pdiff.addchange(pchange)
984 984
985 985 for _path, copiedchange in copiedchanges.items():
986 986 pdiff.addchange(copiedchange)
987 987 for _path, movedchange in movedchanges.items():
988 988 pdiff.addchange(movedchange)
989 989
990 990
991 991 def creatediff(basectx, ctx):
992 992 """create a Differential Diff"""
993 993 repo = ctx.repo()
994 994 repophid = getrepophid(repo)
995 995 # Create a "Differential Diff" via "differential.creatediff" API
996 996 pdiff = phabdiff(
997 997 sourceControlBaseRevision=b'%s' % basectx.p1().hex(),
998 998 branch=b'%s' % ctx.branch(),
999 999 )
1000 1000 modified, added, removed, _d, _u, _i, _c = basectx.p1().status(ctx)
1001 1001 # addadded will remove moved files from removed, so addremoved won't get
1002 1002 # them
1003 1003 addadded(pdiff, basectx, ctx, added, removed)
1004 1004 addmodified(pdiff, basectx, ctx, modified)
1005 1005 addremoved(pdiff, basectx, ctx, removed)
1006 1006 if repophid:
1007 1007 pdiff.repositoryPHID = repophid
1008 1008 diff = callconduit(
1009 1009 repo.ui,
1010 1010 b'differential.creatediff',
1011 1011 pycompat.byteskwargs(attr.asdict(pdiff)),
1012 1012 )
1013 1013 if not diff:
1014 1014 if basectx != ctx:
1015 1015 msg = _(b'cannot create diff for %s::%s') % (basectx, ctx)
1016 1016 else:
1017 1017 msg = _(b'cannot create diff for %s') % ctx
1018 1018 raise error.Abort(msg)
1019 1019 return diff
1020 1020
1021 1021
1022 1022 def writediffproperties(ctxs, diff):
1023 1023 """write metadata to diff so patches could be applied losslessly
1024 1024
1025 1025 ``ctxs`` is the list of commits that created the diff, in ascending order.
1026 1026 The list is generally a single commit, but may be several when using
1027 1027 ``phabsend --fold``.
1028 1028 """
1029 1029 # creatediff returns with a diffid but query returns with an id
1030 1030 diffid = diff.get(b'diffid', diff.get(b'id'))
1031 1031 basectx = ctxs[0]
1032 1032 tipctx = ctxs[-1]
1033 1033
1034 1034 params = {
1035 1035 b'diff_id': diffid,
1036 1036 b'name': b'hg:meta',
1037 1037 b'data': templatefilters.json(
1038 1038 {
1039 1039 b'user': tipctx.user(),
1040 1040 b'date': b'%d %d' % tipctx.date(),
1041 1041 b'branch': tipctx.branch(),
1042 1042 b'node': tipctx.hex(),
1043 1043 b'parent': basectx.p1().hex(),
1044 1044 }
1045 1045 ),
1046 1046 }
1047 1047 callconduit(basectx.repo().ui, b'differential.setdiffproperty', params)
1048 1048
1049 1049 commits = {}
1050 1050 for ctx in ctxs:
1051 1051 commits[ctx.hex()] = {
1052 1052 b'author': stringutil.person(ctx.user()),
1053 1053 b'authorEmail': stringutil.email(ctx.user()),
1054 1054 b'time': int(ctx.date()[0]),
1055 1055 b'commit': ctx.hex(),
1056 1056 b'parents': [ctx.p1().hex()],
1057 1057 b'branch': ctx.branch(),
1058 1058 }
1059 1059 params = {
1060 1060 b'diff_id': diffid,
1061 1061 b'name': b'local:commits',
1062 1062 b'data': templatefilters.json(commits),
1063 1063 }
1064 1064 callconduit(basectx.repo().ui, b'differential.setdiffproperty', params)
1065 1065
1066 1066
1067 1067 def createdifferentialrevision(
1068 1068 ctxs,
1069 1069 revid=None,
1070 1070 parentrevphid=None,
1071 1071 oldbasenode=None,
1072 1072 oldnode=None,
1073 1073 olddiff=None,
1074 1074 actions=None,
1075 1075 comment=None,
1076 1076 ):
1077 1077 """create or update a Differential Revision
1078 1078
1079 1079 If revid is None, create a new Differential Revision, otherwise update
1080 1080 revid. If parentrevphid is not None, set it as a dependency.
1081 1081
1082 1082 If there is a single commit for the new Differential Revision, ``ctxs`` will
1083 1083 be a list of that single context. Otherwise, it is a list that covers the
1084 1084 range of changes for the differential, where ``ctxs[0]`` is the first change
1085 1085 to include and ``ctxs[-1]`` is the last.
1086 1086
1087 1087 If oldnode is not None, check if the patch content (without commit message
1088 1088 and metadata) has changed before creating another diff. For a Revision with
1089 1089 a single commit, ``oldbasenode`` and ``oldnode`` have the same value. For a
1090 1090 Revision covering multiple commits, ``oldbasenode`` corresponds to
1091 1091 ``ctxs[0]`` the previous time this Revision was posted, and ``oldnode``
1092 1092 corresponds to ``ctxs[-1]``.
1093 1093
1094 1094 If actions is not None, they will be appended to the transaction.
1095 1095 """
1096 1096 ctx = ctxs[-1]
1097 1097 basectx = ctxs[0]
1098 1098
1099 1099 repo = ctx.repo()
1100 1100 if oldnode:
1101 1101 diffopts = mdiff.diffopts(git=True, context=32767)
1102 1102 unfi = repo.unfiltered()
1103 1103 oldctx = unfi[oldnode]
1104 1104 oldbasectx = unfi[oldbasenode]
1105 1105 neednewdiff = getdiff(basectx, ctx, diffopts) != getdiff(
1106 1106 oldbasectx, oldctx, diffopts
1107 1107 )
1108 1108 else:
1109 1109 neednewdiff = True
1110 1110
1111 1111 transactions = []
1112 1112 if neednewdiff:
1113 1113 diff = creatediff(basectx, ctx)
1114 1114 transactions.append({b'type': b'update', b'value': diff[b'phid']})
1115 1115 if comment:
1116 1116 transactions.append({b'type': b'comment', b'value': comment})
1117 1117 else:
1118 1118 # Even if we don't need to upload a new diff because the patch content
1119 1119 # does not change. We might still need to update its metadata so
1120 1120 # pushers could know the correct node metadata.
1121 1121 assert olddiff
1122 1122 diff = olddiff
1123 1123 writediffproperties(ctxs, diff)
1124 1124
1125 1125 # Set the parent Revision every time, so commit re-ordering is picked-up
1126 1126 if parentrevphid:
1127 1127 transactions.append(
1128 1128 {b'type': b'parents.set', b'value': [parentrevphid]}
1129 1129 )
1130 1130
1131 1131 if actions:
1132 1132 transactions += actions
1133 1133
1134 1134 # When folding multiple local commits into a single review, arcanist will
1135 1135 # take the summary line of the first commit as the title, and then
1136 1136 # concatenate the rest of the remaining messages (including each of their
1137 1137 # first lines) to the rest of the first commit message (each separated by
1138 1138 # an empty line), and use that as the summary field. Do the same here.
1139 1139 # For commits with only a one line message, there is no summary field, as
1140 1140 # this gets assigned to the title.
1141 1141 fields = util.sortdict() # sorted for stable wire protocol in tests
1142 1142
1143 1143 for i, _ctx in enumerate(ctxs):
1144 1144 # Parse commit message and update related fields.
1145 1145 desc = _ctx.description()
1146 1146 info = callconduit(
1147 1147 repo.ui, b'differential.parsecommitmessage', {b'corpus': desc}
1148 1148 )
1149 1149
1150 1150 for k in [b'title', b'summary', b'testPlan']:
1151 1151 v = info[b'fields'].get(k)
1152 1152 if not v:
1153 1153 continue
1154 1154
1155 1155 if i == 0:
1156 1156 # Title, summary and test plan (if present) are taken verbatim
1157 1157 # for the first commit.
1158 1158 fields[k] = v.rstrip()
1159 1159 continue
1160 1160 elif k == b'title':
1161 1161 # Add subsequent titles (i.e. the first line of the commit
1162 1162 # message) back to the summary.
1163 1163 k = b'summary'
1164 1164
1165 1165 # Append any current field to the existing composite field
1166 1166 fields[k] = b'\n\n'.join(filter(None, [fields.get(k), v.rstrip()]))
1167 1167
1168 1168 for k, v in fields.items():
1169 1169 transactions.append({b'type': k, b'value': v})
1170 1170
1171 1171 params = {b'transactions': transactions}
1172 1172 if revid is not None:
1173 1173 # Update an existing Differential Revision
1174 1174 params[b'objectIdentifier'] = revid
1175 1175
1176 1176 revision = callconduit(repo.ui, b'differential.revision.edit', params)
1177 1177 if not revision:
1178 1178 if len(ctxs) == 1:
1179 1179 msg = _(b'cannot create revision for %s') % ctx
1180 1180 else:
1181 1181 msg = _(b'cannot create revision for %s::%s') % (basectx, ctx)
1182 1182 raise error.Abort(msg)
1183 1183
1184 1184 return revision, diff
1185 1185
1186 1186
1187 1187 def userphids(ui, names):
1188 1188 """convert user names to PHIDs"""
1189 1189 names = [name.lower() for name in names]
1190 1190 query = {b'constraints': {b'usernames': names}}
1191 1191 result = callconduit(ui, b'user.search', query)
1192 1192 # username not found is not an error of the API. So check if we have missed
1193 1193 # some names here.
1194 1194 data = result[b'data']
1195 1195 resolved = {entry[b'fields'][b'username'].lower() for entry in data}
1196 1196 unresolved = set(names) - resolved
1197 1197 if unresolved:
1198 1198 raise error.Abort(
1199 1199 _(b'unknown username: %s') % b' '.join(sorted(unresolved))
1200 1200 )
1201 1201 return [entry[b'phid'] for entry in data]
1202 1202
1203 1203
1204 1204 def _print_phabsend_action(ui, ctx, newrevid, action):
1205 1205 """print the ``action`` that occurred when posting ``ctx`` for review
1206 1206
1207 1207 This is a utility function for the sending phase of ``phabsend``, which
1208 1208 makes it easier to show a status for all local commits with `--fold``.
1209 1209 """
1210 1210 actiondesc = ui.label(
1211 1211 {
1212 1212 b'created': _(b'created'),
1213 1213 b'skipped': _(b'skipped'),
1214 1214 b'updated': _(b'updated'),
1215 1215 }[action],
1216 1216 b'phabricator.action.%s' % action,
1217 1217 )
1218 1218 drevdesc = ui.label(b'D%d' % newrevid, b'phabricator.drev')
1219 1219 nodedesc = ui.label(bytes(ctx), b'phabricator.node')
1220 1220 desc = ui.label(ctx.description().split(b'\n')[0], b'phabricator.desc')
1221 1221 ui.write(_(b'%s - %s - %s: %s\n') % (drevdesc, actiondesc, nodedesc, desc))
1222 1222
1223 1223
1224 1224 def _amend_diff_properties(unfi, drevid, newnodes, diff):
1225 1225 """update the local commit list for the ``diff`` associated with ``drevid``
1226 1226
1227 1227 This is a utility function for the amend phase of ``phabsend``, which
1228 1228 converts failures to warning messages.
1229 1229 """
1230 1230 _debug(
1231 1231 unfi.ui,
1232 1232 b"new commits: %s\n" % stringutil.pprint([short(n) for n in newnodes]),
1233 1233 )
1234 1234
1235 1235 try:
1236 1236 writediffproperties([unfi[newnode] for newnode in newnodes], diff)
1237 1237 except util.urlerr.urlerror:
1238 1238 # If it fails just warn and keep going, otherwise the DREV
1239 1239 # associations will be lost
1240 1240 unfi.ui.warnnoi18n(b'Failed to update metadata for D%d\n' % drevid)
1241 1241
1242 1242
1243 1243 @vcrcommand(
1244 1244 b'phabsend',
1245 1245 [
1246 1246 (b'r', b'rev', [], _(b'revisions to send'), _(b'REV')),
1247 1247 (b'', b'amend', True, _(b'update commit messages')),
1248 1248 (b'', b'reviewer', [], _(b'specify reviewers')),
1249 1249 (b'', b'blocker', [], _(b'specify blocking reviewers')),
1250 1250 (
1251 1251 b'm',
1252 1252 b'comment',
1253 1253 b'',
1254 1254 _(b'add a comment to Revisions with new/updated Diffs'),
1255 1255 ),
1256 1256 (b'', b'confirm', None, _(b'ask for confirmation before sending')),
1257 1257 (b'', b'fold', False, _(b'combine the revisions into one review')),
1258 1258 ],
1259 1259 _(b'REV [OPTIONS]'),
1260 1260 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1261 1261 )
1262 1262 def phabsend(ui, repo, *revs, **opts):
1263 1263 """upload changesets to Phabricator
1264 1264
1265 1265 If there are multiple revisions specified, they will be send as a stack
1266 1266 with a linear dependencies relationship using the order specified by the
1267 1267 revset.
1268 1268
1269 1269 For the first time uploading changesets, local tags will be created to
1270 1270 maintain the association. After the first time, phabsend will check
1271 1271 obsstore and tags information so it can figure out whether to update an
1272 1272 existing Differential Revision, or create a new one.
1273 1273
1274 1274 If --amend is set, update commit messages so they have the
1275 1275 ``Differential Revision`` URL, remove related tags. This is similar to what
1276 1276 arcanist will do, and is more desired in author-push workflows. Otherwise,
1277 1277 use local tags to record the ``Differential Revision`` association.
1278 1278
1279 1279 The --confirm option lets you confirm changesets before sending them. You
1280 1280 can also add following to your configuration file to make it default
1281 1281 behaviour::
1282 1282
1283 1283 [phabsend]
1284 1284 confirm = true
1285 1285
1286 1286 By default, a separate review will be created for each commit that is
1287 1287 selected, and will have the same parent/child relationship in Phabricator.
1288 1288 If ``--fold`` is set, multiple commits are rolled up into a single review
1289 1289 as if diffed from the parent of the first revision to the last. The commit
1290 1290 messages are concatenated in the summary field on Phabricator.
1291 1291
1292 1292 phabsend will check obsstore and the above association to decide whether to
1293 1293 update an existing Differential Revision, or create a new one.
1294 1294 """
1295 1295 opts = pycompat.byteskwargs(opts)
1296 1296 revs = list(revs) + opts.get(b'rev', [])
1297 1297 revs = scmutil.revrange(repo, revs)
1298 1298 revs.sort() # ascending order to preserve topological parent/child in phab
1299 1299
1300 1300 if not revs:
1301 1301 raise error.Abort(_(b'phabsend requires at least one changeset'))
1302 1302 if opts.get(b'amend'):
1303 1303 cmdutil.checkunfinished(repo)
1304 1304
1305 1305 ctxs = [repo[rev] for rev in revs]
1306 1306
1307 1307 fold = opts.get(b'fold')
1308 1308 if fold:
1309 1309 if len(revs) == 1:
1310 1310 # TODO: just switch to --no-fold instead?
1311 1311 raise error.Abort(_(b"cannot fold a single revision"))
1312 1312
1313 1313 # There's no clear way to manage multiple commits with a Dxxx tag, so
1314 1314 # require the amend option. (We could append "_nnn", but then it
1315 1315 # becomes jumbled if earlier commits are added to an update.) It should
1316 1316 # lock the repo and ensure that the range is editable, but that would
1317 1317 # make the code pretty convoluted. The default behavior of `arc` is to
1318 1318 # create a new review anyway.
1319 1319 if not opts.get(b"amend"):
1320 1320 raise error.Abort(_(b"cannot fold with --no-amend"))
1321 1321
1322 1322 # Ensure the local commits are an unbroken range
1323 1323 revrange = repo.revs(b'(first(%ld)::last(%ld))', revs, revs)
1324 1324 if any(r for r in revs if r not in revrange) or any(
1325 1325 r for r in revrange if r not in revs
1326 1326 ):
1327 1327 raise error.Abort(_(b"cannot fold non-linear revisions"))
1328 1328
1329 1329 # It might be possible to bucketize the revisions by the DREV value, and
1330 1330 # iterate over those groups when posting, and then again when amending.
1331 1331 # But for simplicity, require all selected revisions to be for the same
1332 1332 # DREV (if present). Adding local revisions to an existing DREV is
1333 1333 # acceptable.
1334 1334 drevmatchers = [
1335 1335 _differentialrevisiondescre.search(ctx.description())
1336 1336 for ctx in ctxs
1337 1337 ]
1338 1338 if len({m.group('url') for m in drevmatchers if m}) > 1:
1339 1339 raise error.Abort(
1340 1340 _(b"cannot fold revisions with different DREV values")
1341 1341 )
1342 1342
1343 1343 # {newnode: (oldnode, olddiff, olddrev}
1344 1344 oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
1345 1345
1346 1346 confirm = ui.configbool(b'phabsend', b'confirm')
1347 1347 confirm |= bool(opts.get(b'confirm'))
1348 1348 if confirm:
1349 1349 confirmed = _confirmbeforesend(repo, revs, oldmap)
1350 1350 if not confirmed:
1351 1351 raise error.Abort(_(b'phabsend cancelled'))
1352 1352
1353 1353 actions = []
1354 1354 reviewers = opts.get(b'reviewer', [])
1355 1355 blockers = opts.get(b'blocker', [])
1356 1356 phids = []
1357 1357 if reviewers:
1358 1358 phids.extend(userphids(repo.ui, reviewers))
1359 1359 if blockers:
1360 1360 phids.extend(
1361 1361 map(
1362 1362 lambda phid: b'blocking(%s)' % phid,
1363 1363 userphids(repo.ui, blockers),
1364 1364 )
1365 1365 )
1366 1366 if phids:
1367 1367 actions.append({b'type': b'reviewers.add', b'value': phids})
1368 1368
1369 1369 drevids = [] # [int]
1370 1370 diffmap = {} # {newnode: diff}
1371 1371
1372 1372 # Send patches one by one so we know their Differential Revision PHIDs and
1373 1373 # can provide dependency relationship
1374 1374 lastrevphid = None
1375 1375 for ctx in ctxs:
1376 1376 if fold:
1377 1377 ui.debug(b'sending rev %d::%d\n' % (ctx.rev(), ctxs[-1].rev()))
1378 1378 else:
1379 1379 ui.debug(b'sending rev %d\n' % ctx.rev())
1380 1380
1381 1381 # Get Differential Revision ID
1382 1382 oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None))
1383 1383 oldbasenode, oldbasediff, oldbaserevid = oldnode, olddiff, revid
1384 1384
1385 1385 if fold:
1386 1386 oldbasenode, oldbasediff, oldbaserevid = oldmap.get(
1387 1387 ctxs[-1].node(), (None, None, None)
1388 1388 )
1389 1389
1390 1390 if oldnode != ctx.node() or opts.get(b'amend'):
1391 1391 # Create or update Differential Revision
1392 1392 revision, diff = createdifferentialrevision(
1393 1393 ctxs if fold else [ctx],
1394 1394 revid,
1395 1395 lastrevphid,
1396 1396 oldbasenode,
1397 1397 oldnode,
1398 1398 olddiff,
1399 1399 actions,
1400 1400 opts.get(b'comment'),
1401 1401 )
1402 1402
1403 1403 if fold:
1404 1404 for ctx in ctxs:
1405 1405 diffmap[ctx.node()] = diff
1406 1406 else:
1407 1407 diffmap[ctx.node()] = diff
1408 1408
1409 1409 newrevid = int(revision[b'object'][b'id'])
1410 1410 newrevphid = revision[b'object'][b'phid']
1411 1411 if revid:
1412 1412 action = b'updated'
1413 1413 else:
1414 1414 action = b'created'
1415 1415
1416 1416 # Create a local tag to note the association, if commit message
1417 1417 # does not have it already
1418 1418 if not fold:
1419 1419 m = _differentialrevisiondescre.search(ctx.description())
1420 1420 if not m or int(m.group('id')) != newrevid:
1421 1421 tagname = b'D%d' % newrevid
1422 1422 tags.tag(
1423 1423 repo,
1424 1424 tagname,
1425 1425 ctx.node(),
1426 1426 message=None,
1427 1427 user=None,
1428 1428 date=None,
1429 1429 local=True,
1430 1430 )
1431 1431 else:
1432 1432 # Nothing changed. But still set "newrevphid" so the next revision
1433 1433 # could depend on this one and "newrevid" for the summary line.
1434 1434 newrevphid = querydrev(repo.ui, b'%d' % revid)[0][b'phid']
1435 1435 newrevid = revid
1436 1436 action = b'skipped'
1437 1437
1438 1438 drevids.append(newrevid)
1439 1439 lastrevphid = newrevphid
1440 1440
1441 1441 if fold:
1442 1442 for c in ctxs:
1443 1443 if oldmap.get(c.node(), (None, None, None))[2]:
1444 1444 action = b'updated'
1445 1445 else:
1446 1446 action = b'created'
1447 1447 _print_phabsend_action(ui, c, newrevid, action)
1448 1448 break
1449 1449
1450 1450 _print_phabsend_action(ui, ctx, newrevid, action)
1451 1451
1452 1452 # Update commit messages and remove tags
1453 1453 if opts.get(b'amend'):
1454 1454 unfi = repo.unfiltered()
1455 1455 drevs = callconduit(ui, b'differential.query', {b'ids': drevids})
1456 1456 with repo.wlock(), repo.lock(), repo.transaction(b'phabsend'):
1457 1457 wnode = unfi[b'.'].node()
1458 1458 mapping = {} # {oldnode: [newnode]}
1459 1459 newnodes = []
1460 1460
1461 1461 drevid = drevids[0]
1462 1462
1463 1463 for i, rev in enumerate(revs):
1464 1464 old = unfi[rev]
1465 1465 if not fold:
1466 1466 drevid = drevids[i]
1467 1467 drev = [d for d in drevs if int(d[b'id']) == drevid][0]
1468 1468
1469 1469 newdesc = get_amended_desc(drev, old, fold)
1470 1470 # Make sure commit message contain "Differential Revision"
1471 if old.description() != newdesc:
1471 if (
1472 old.description() != newdesc
1473 or old.p1().node() in mapping
1474 or old.p2().node() in mapping
1475 ):
1472 1476 if old.phase() == phases.public:
1473 1477 ui.warn(
1474 1478 _(b"warning: not updating public commit %s\n")
1475 1479 % scmutil.formatchangeid(old)
1476 1480 )
1477 1481 continue
1478 1482 parents = [
1479 1483 mapping.get(old.p1().node(), (old.p1(),))[0],
1480 1484 mapping.get(old.p2().node(), (old.p2(),))[0],
1481 1485 ]
1482 1486 new = context.metadataonlyctx(
1483 1487 repo,
1484 1488 old,
1485 1489 parents=parents,
1486 1490 text=newdesc,
1487 1491 user=old.user(),
1488 1492 date=old.date(),
1489 1493 extra=old.extra(),
1490 1494 )
1491 1495
1492 1496 newnode = new.commit()
1493 1497
1494 1498 mapping[old.node()] = [newnode]
1495 1499
1496 1500 if fold:
1497 1501 # Defer updating the (single) Diff until all nodes are
1498 1502 # collected. No tags were created, so none need to be
1499 1503 # removed.
1500 1504 newnodes.append(newnode)
1501 1505 continue
1502 1506
1503 1507 _amend_diff_properties(
1504 1508 unfi, drevid, [newnode], diffmap[old.node()]
1505 1509 )
1506 1510
1507 1511 # Remove local tags since it's no longer necessary
1508 1512 tagname = b'D%d' % drevid
1509 1513 if tagname in repo.tags():
1510 1514 tags.tag(
1511 1515 repo,
1512 1516 tagname,
1513 1517 nullid,
1514 1518 message=None,
1515 1519 user=None,
1516 1520 date=None,
1517 1521 local=True,
1518 1522 )
1519 1523 elif fold:
1520 1524 # When folding multiple commits into one review with
1521 1525 # --fold, track even the commits that weren't amended, so
1522 1526 # that their association isn't lost if the properties are
1523 1527 # rewritten below.
1524 1528 newnodes.append(old.node())
1525 1529
1526 1530 # If the submitted commits are public, no amend takes place so
1527 1531 # there are no newnodes and therefore no diff update to do.
1528 1532 if fold and newnodes:
1529 1533 diff = diffmap[old.node()]
1530 1534
1531 1535 # The diff object in diffmap doesn't have the local commits
1532 1536 # because that could be returned from differential.creatediff,
1533 1537 # not differential.querydiffs. So use the queried diff (if
1534 1538 # present), or force the amend (a new revision is being posted.)
1535 1539 if not olddiff or set(newnodes) != getlocalcommits(olddiff):
1536 1540 _debug(ui, b"updating local commit list for D%d\n" % drevid)
1537 1541 _amend_diff_properties(unfi, drevid, newnodes, diff)
1538 1542 else:
1539 1543 _debug(
1540 1544 ui,
1541 1545 b"local commit list for D%d is already up-to-date\n"
1542 1546 % drevid,
1543 1547 )
1544 1548 elif fold:
1545 1549 _debug(ui, b"no newnodes to update\n")
1546 1550
1547 1551 scmutil.cleanupnodes(repo, mapping, b'phabsend', fixphase=True)
1548 1552 if wnode in mapping:
1549 1553 unfi.setparents(mapping[wnode][0])
1550 1554
1551 1555
1552 1556 # Map from "hg:meta" keys to header understood by "hg import". The order is
1553 1557 # consistent with "hg export" output.
1554 1558 _metanamemap = util.sortdict(
1555 1559 [
1556 1560 (b'user', b'User'),
1557 1561 (b'date', b'Date'),
1558 1562 (b'branch', b'Branch'),
1559 1563 (b'node', b'Node ID'),
1560 1564 (b'parent', b'Parent '),
1561 1565 ]
1562 1566 )
1563 1567
1564 1568
1565 1569 def _confirmbeforesend(repo, revs, oldmap):
1566 1570 url, token = readurltoken(repo.ui)
1567 1571 ui = repo.ui
1568 1572 for rev in revs:
1569 1573 ctx = repo[rev]
1570 1574 desc = ctx.description().splitlines()[0]
1571 1575 oldnode, olddiff, drevid = oldmap.get(ctx.node(), (None, None, None))
1572 1576 if drevid:
1573 1577 drevdesc = ui.label(b'D%d' % drevid, b'phabricator.drev')
1574 1578 else:
1575 1579 drevdesc = ui.label(_(b'NEW'), b'phabricator.drev')
1576 1580
1577 1581 ui.write(
1578 1582 _(b'%s - %s: %s\n')
1579 1583 % (
1580 1584 drevdesc,
1581 1585 ui.label(bytes(ctx), b'phabricator.node'),
1582 1586 ui.label(desc, b'phabricator.desc'),
1583 1587 )
1584 1588 )
1585 1589
1586 1590 if ui.promptchoice(
1587 1591 _(b'Send the above changes to %s (yn)?$$ &Yes $$ &No') % url
1588 1592 ):
1589 1593 return False
1590 1594
1591 1595 return True
1592 1596
1593 1597
1594 1598 _knownstatusnames = {
1595 1599 b'accepted',
1596 1600 b'needsreview',
1597 1601 b'needsrevision',
1598 1602 b'closed',
1599 1603 b'abandoned',
1600 1604 b'changesplanned',
1601 1605 }
1602 1606
1603 1607
1604 1608 def _getstatusname(drev):
1605 1609 """get normalized status name from a Differential Revision"""
1606 1610 return drev[b'statusName'].replace(b' ', b'').lower()
1607 1611
1608 1612
1609 1613 # Small language to specify differential revisions. Support symbols: (), :X,
1610 1614 # +, and -.
1611 1615
1612 1616 _elements = {
1613 1617 # token-type: binding-strength, primary, prefix, infix, suffix
1614 1618 b'(': (12, None, (b'group', 1, b')'), None, None),
1615 1619 b':': (8, None, (b'ancestors', 8), None, None),
1616 1620 b'&': (5, None, None, (b'and_', 5), None),
1617 1621 b'+': (4, None, None, (b'add', 4), None),
1618 1622 b'-': (4, None, None, (b'sub', 4), None),
1619 1623 b')': (0, None, None, None, None),
1620 1624 b'symbol': (0, b'symbol', None, None, None),
1621 1625 b'end': (0, None, None, None, None),
1622 1626 }
1623 1627
1624 1628
1625 1629 def _tokenize(text):
1626 1630 view = memoryview(text) # zero-copy slice
1627 1631 special = b'():+-& '
1628 1632 pos = 0
1629 1633 length = len(text)
1630 1634 while pos < length:
1631 1635 symbol = b''.join(
1632 1636 itertools.takewhile(
1633 1637 lambda ch: ch not in special, pycompat.iterbytestr(view[pos:])
1634 1638 )
1635 1639 )
1636 1640 if symbol:
1637 1641 yield (b'symbol', symbol, pos)
1638 1642 pos += len(symbol)
1639 1643 else: # special char, ignore space
1640 1644 if text[pos : pos + 1] != b' ':
1641 1645 yield (text[pos : pos + 1], None, pos)
1642 1646 pos += 1
1643 1647 yield (b'end', None, pos)
1644 1648
1645 1649
1646 1650 def _parse(text):
1647 1651 tree, pos = parser.parser(_elements).parse(_tokenize(text))
1648 1652 if pos != len(text):
1649 1653 raise error.ParseError(b'invalid token', pos)
1650 1654 return tree
1651 1655
1652 1656
1653 1657 def _parsedrev(symbol):
1654 1658 """str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None"""
1655 1659 if symbol.startswith(b'D') and symbol[1:].isdigit():
1656 1660 return int(symbol[1:])
1657 1661 if symbol.isdigit():
1658 1662 return int(symbol)
1659 1663
1660 1664
1661 1665 def _prefetchdrevs(tree):
1662 1666 """return ({single-drev-id}, {ancestor-drev-id}) to prefetch"""
1663 1667 drevs = set()
1664 1668 ancestordrevs = set()
1665 1669 op = tree[0]
1666 1670 if op == b'symbol':
1667 1671 r = _parsedrev(tree[1])
1668 1672 if r:
1669 1673 drevs.add(r)
1670 1674 elif op == b'ancestors':
1671 1675 r, a = _prefetchdrevs(tree[1])
1672 1676 drevs.update(r)
1673 1677 ancestordrevs.update(r)
1674 1678 ancestordrevs.update(a)
1675 1679 else:
1676 1680 for t in tree[1:]:
1677 1681 r, a = _prefetchdrevs(t)
1678 1682 drevs.update(r)
1679 1683 ancestordrevs.update(a)
1680 1684 return drevs, ancestordrevs
1681 1685
1682 1686
1683 1687 def querydrev(ui, spec):
1684 1688 """return a list of "Differential Revision" dicts
1685 1689
1686 1690 spec is a string using a simple query language, see docstring in phabread
1687 1691 for details.
1688 1692
1689 1693 A "Differential Revision dict" looks like:
1690 1694
1691 1695 {
1692 1696 "activeDiffPHID": "PHID-DIFF-xoqnjkobbm6k4dk6hi72",
1693 1697 "authorPHID": "PHID-USER-tv3ohwc4v4jeu34otlye",
1694 1698 "auxiliary": {
1695 1699 "phabricator:depends-on": [
1696 1700 "PHID-DREV-gbapp366kutjebt7agcd"
1697 1701 ]
1698 1702 "phabricator:projects": [],
1699 1703 },
1700 1704 "branch": "default",
1701 1705 "ccs": [],
1702 1706 "commits": [],
1703 1707 "dateCreated": "1499181406",
1704 1708 "dateModified": "1499182103",
1705 1709 "diffs": [
1706 1710 "3",
1707 1711 "4",
1708 1712 ],
1709 1713 "hashes": [],
1710 1714 "id": "2",
1711 1715 "lineCount": "2",
1712 1716 "phid": "PHID-DREV-672qvysjcczopag46qty",
1713 1717 "properties": {},
1714 1718 "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv",
1715 1719 "reviewers": [],
1716 1720 "sourcePath": null
1717 1721 "status": "0",
1718 1722 "statusName": "Needs Review",
1719 1723 "summary": "",
1720 1724 "testPlan": "",
1721 1725 "title": "example",
1722 1726 "uri": "https://phab.example.com/D2",
1723 1727 }
1724 1728 """
1725 1729 # TODO: replace differential.query and differential.querydiffs with
1726 1730 # differential.diff.search because the former (and their output) are
1727 1731 # frozen, and planned to be deprecated and removed.
1728 1732
1729 1733 def fetch(params):
1730 1734 """params -> single drev or None"""
1731 1735 key = (params.get(b'ids') or params.get(b'phids') or [None])[0]
1732 1736 if key in prefetched:
1733 1737 return prefetched[key]
1734 1738 drevs = callconduit(ui, b'differential.query', params)
1735 1739 # Fill prefetched with the result
1736 1740 for drev in drevs:
1737 1741 prefetched[drev[b'phid']] = drev
1738 1742 prefetched[int(drev[b'id'])] = drev
1739 1743 if key not in prefetched:
1740 1744 raise error.Abort(
1741 1745 _(b'cannot get Differential Revision %r') % params
1742 1746 )
1743 1747 return prefetched[key]
1744 1748
1745 1749 def getstack(topdrevids):
1746 1750 """given a top, get a stack from the bottom, [id] -> [id]"""
1747 1751 visited = set()
1748 1752 result = []
1749 1753 queue = [{b'ids': [i]} for i in topdrevids]
1750 1754 while queue:
1751 1755 params = queue.pop()
1752 1756 drev = fetch(params)
1753 1757 if drev[b'id'] in visited:
1754 1758 continue
1755 1759 visited.add(drev[b'id'])
1756 1760 result.append(int(drev[b'id']))
1757 1761 auxiliary = drev.get(b'auxiliary', {})
1758 1762 depends = auxiliary.get(b'phabricator:depends-on', [])
1759 1763 for phid in depends:
1760 1764 queue.append({b'phids': [phid]})
1761 1765 result.reverse()
1762 1766 return smartset.baseset(result)
1763 1767
1764 1768 # Initialize prefetch cache
1765 1769 prefetched = {} # {id or phid: drev}
1766 1770
1767 1771 tree = _parse(spec)
1768 1772 drevs, ancestordrevs = _prefetchdrevs(tree)
1769 1773
1770 1774 # developer config: phabricator.batchsize
1771 1775 batchsize = ui.configint(b'phabricator', b'batchsize')
1772 1776
1773 1777 # Prefetch Differential Revisions in batch
1774 1778 tofetch = set(drevs)
1775 1779 for r in ancestordrevs:
1776 1780 tofetch.update(range(max(1, r - batchsize), r + 1))
1777 1781 if drevs:
1778 1782 fetch({b'ids': list(tofetch)})
1779 1783 validids = sorted(set(getstack(list(ancestordrevs))) | set(drevs))
1780 1784
1781 1785 # Walk through the tree, return smartsets
1782 1786 def walk(tree):
1783 1787 op = tree[0]
1784 1788 if op == b'symbol':
1785 1789 drev = _parsedrev(tree[1])
1786 1790 if drev:
1787 1791 return smartset.baseset([drev])
1788 1792 elif tree[1] in _knownstatusnames:
1789 1793 drevs = [
1790 1794 r
1791 1795 for r in validids
1792 1796 if _getstatusname(prefetched[r]) == tree[1]
1793 1797 ]
1794 1798 return smartset.baseset(drevs)
1795 1799 else:
1796 1800 raise error.Abort(_(b'unknown symbol: %s') % tree[1])
1797 1801 elif op in {b'and_', b'add', b'sub'}:
1798 1802 assert len(tree) == 3
1799 1803 return getattr(operator, op)(walk(tree[1]), walk(tree[2]))
1800 1804 elif op == b'group':
1801 1805 return walk(tree[1])
1802 1806 elif op == b'ancestors':
1803 1807 return getstack(walk(tree[1]))
1804 1808 else:
1805 1809 raise error.ProgrammingError(b'illegal tree: %r' % tree)
1806 1810
1807 1811 return [prefetched[r] for r in walk(tree)]
1808 1812
1809 1813
1810 1814 def getdescfromdrev(drev):
1811 1815 """get description (commit message) from "Differential Revision"
1812 1816
1813 1817 This is similar to differential.getcommitmessage API. But we only care
1814 1818 about limited fields: title, summary, test plan, and URL.
1815 1819 """
1816 1820 title = drev[b'title']
1817 1821 summary = drev[b'summary'].rstrip()
1818 1822 testplan = drev[b'testPlan'].rstrip()
1819 1823 if testplan:
1820 1824 testplan = b'Test Plan:\n%s' % testplan
1821 1825 uri = b'Differential Revision: %s' % drev[b'uri']
1822 1826 return b'\n\n'.join(filter(None, [title, summary, testplan, uri]))
1823 1827
1824 1828
1825 1829 def get_amended_desc(drev, ctx, folded):
1826 1830 """similar to ``getdescfromdrev``, but supports a folded series of commits
1827 1831
1828 1832 This is used when determining if an individual commit needs to have its
1829 1833 message amended after posting it for review. The determination is made for
1830 1834 each individual commit, even when they were folded into one review.
1831 1835 """
1832 1836 if not folded:
1833 1837 return getdescfromdrev(drev)
1834 1838
1835 1839 uri = b'Differential Revision: %s' % drev[b'uri']
1836 1840
1837 1841 # Since the commit messages were combined when posting multiple commits
1838 1842 # with --fold, the fields can't be read from Phabricator here, or *all*
1839 1843 # affected local revisions will end up with the same commit message after
1840 1844 # the URI is amended in. Append in the DREV line, or update it if it
1841 1845 # exists. At worst, this means commit message or test plan updates on
1842 1846 # Phabricator aren't propagated back to the repository, but that seems
1843 1847 # reasonable for the case where local commits are effectively combined
1844 1848 # in Phabricator.
1845 1849 m = _differentialrevisiondescre.search(ctx.description())
1846 1850 if not m:
1847 1851 return b'\n\n'.join([ctx.description(), uri])
1848 1852
1849 1853 return _differentialrevisiondescre.sub(uri, ctx.description())
1850 1854
1851 1855
1852 1856 def getlocalcommits(diff):
1853 1857 """get the set of local commits from a diff object
1854 1858
1855 1859 See ``getdiffmeta()`` for an example diff object.
1856 1860 """
1857 1861 props = diff.get(b'properties') or {}
1858 1862 commits = props.get(b'local:commits') or {}
1859 1863 if len(commits) > 1:
1860 1864 return {bin(c) for c in commits.keys()}
1861 1865
1862 1866 # Storing the diff metadata predates storing `local:commits`, so continue
1863 1867 # to use that in the --no-fold case.
1864 1868 return {bin(getdiffmeta(diff).get(b'node', b'')) or None}
1865 1869
1866 1870
1867 1871 def getdiffmeta(diff):
1868 1872 """get commit metadata (date, node, user, p1) from a diff object
1869 1873
1870 1874 The metadata could be "hg:meta", sent by phabsend, like:
1871 1875
1872 1876 "properties": {
1873 1877 "hg:meta": {
1874 1878 "branch": "default",
1875 1879 "date": "1499571514 25200",
1876 1880 "node": "98c08acae292b2faf60a279b4189beb6cff1414d",
1877 1881 "user": "Foo Bar <foo@example.com>",
1878 1882 "parent": "6d0abad76b30e4724a37ab8721d630394070fe16"
1879 1883 }
1880 1884 }
1881 1885
1882 1886 Or converted from "local:commits", sent by "arc", like:
1883 1887
1884 1888 "properties": {
1885 1889 "local:commits": {
1886 1890 "98c08acae292b2faf60a279b4189beb6cff1414d": {
1887 1891 "author": "Foo Bar",
1888 1892 "authorEmail": "foo@example.com"
1889 1893 "branch": "default",
1890 1894 "commit": "98c08acae292b2faf60a279b4189beb6cff1414d",
1891 1895 "local": "1000",
1892 1896 "message": "...",
1893 1897 "parents": ["6d0abad76b30e4724a37ab8721d630394070fe16"],
1894 1898 "rev": "98c08acae292b2faf60a279b4189beb6cff1414d",
1895 1899 "summary": "...",
1896 1900 "tag": "",
1897 1901 "time": 1499546314,
1898 1902 }
1899 1903 }
1900 1904 }
1901 1905
1902 1906 Note: metadata extracted from "local:commits" will lose time zone
1903 1907 information.
1904 1908 """
1905 1909 props = diff.get(b'properties') or {}
1906 1910 meta = props.get(b'hg:meta')
1907 1911 if not meta:
1908 1912 if props.get(b'local:commits'):
1909 1913 commit = sorted(props[b'local:commits'].values())[0]
1910 1914 meta = {}
1911 1915 if b'author' in commit and b'authorEmail' in commit:
1912 1916 meta[b'user'] = b'%s <%s>' % (
1913 1917 commit[b'author'],
1914 1918 commit[b'authorEmail'],
1915 1919 )
1916 1920 if b'time' in commit:
1917 1921 meta[b'date'] = b'%d 0' % int(commit[b'time'])
1918 1922 if b'branch' in commit:
1919 1923 meta[b'branch'] = commit[b'branch']
1920 1924 node = commit.get(b'commit', commit.get(b'rev'))
1921 1925 if node:
1922 1926 meta[b'node'] = node
1923 1927 if len(commit.get(b'parents', ())) >= 1:
1924 1928 meta[b'parent'] = commit[b'parents'][0]
1925 1929 else:
1926 1930 meta = {}
1927 1931 if b'date' not in meta and b'dateCreated' in diff:
1928 1932 meta[b'date'] = b'%s 0' % diff[b'dateCreated']
1929 1933 if b'branch' not in meta and diff.get(b'branch'):
1930 1934 meta[b'branch'] = diff[b'branch']
1931 1935 if b'parent' not in meta and diff.get(b'sourceControlBaseRevision'):
1932 1936 meta[b'parent'] = diff[b'sourceControlBaseRevision']
1933 1937 return meta
1934 1938
1935 1939
1936 1940 def _getdrevs(ui, stack, specs):
1937 1941 """convert user supplied DREVSPECs into "Differential Revision" dicts
1938 1942
1939 1943 See ``hg help phabread`` for how to specify each DREVSPEC.
1940 1944 """
1941 1945 if len(specs) > 0:
1942 1946
1943 1947 def _formatspec(s):
1944 1948 if stack:
1945 1949 s = b':(%s)' % s
1946 1950 return b'(%s)' % s
1947 1951
1948 1952 spec = b'+'.join(pycompat.maplist(_formatspec, specs))
1949 1953
1950 1954 drevs = querydrev(ui, spec)
1951 1955 if drevs:
1952 1956 return drevs
1953 1957
1954 1958 raise error.Abort(_(b"empty DREVSPEC set"))
1955 1959
1956 1960
1957 1961 def readpatch(ui, drevs, write):
1958 1962 """generate plain-text patch readable by 'hg import'
1959 1963
1960 1964 write takes a list of (DREV, bytes), where DREV is the differential number
1961 1965 (as bytes, without the "D" prefix) and the bytes are the text of a patch
1962 1966 to be imported. drevs is what "querydrev" returns, results of
1963 1967 "differential.query".
1964 1968 """
1965 1969 # Prefetch hg:meta property for all diffs
1966 1970 diffids = sorted({max(int(v) for v in drev[b'diffs']) for drev in drevs})
1967 1971 diffs = callconduit(ui, b'differential.querydiffs', {b'ids': diffids})
1968 1972
1969 1973 patches = []
1970 1974
1971 1975 # Generate patch for each drev
1972 1976 for drev in drevs:
1973 1977 ui.note(_(b'reading D%s\n') % drev[b'id'])
1974 1978
1975 1979 diffid = max(int(v) for v in drev[b'diffs'])
1976 1980 body = callconduit(ui, b'differential.getrawdiff', {b'diffID': diffid})
1977 1981 desc = getdescfromdrev(drev)
1978 1982 header = b'# HG changeset patch\n'
1979 1983
1980 1984 # Try to preserve metadata from hg:meta property. Write hg patch
1981 1985 # headers that can be read by the "import" command. See patchheadermap
1982 1986 # and extract in mercurial/patch.py for supported headers.
1983 1987 meta = getdiffmeta(diffs[b'%d' % diffid])
1984 1988 for k in _metanamemap.keys():
1985 1989 if k in meta:
1986 1990 header += b'# %s %s\n' % (_metanamemap[k], meta[k])
1987 1991
1988 1992 content = b'%s%s\n%s' % (header, desc, body)
1989 1993 patches.append((drev[b'id'], content))
1990 1994
1991 1995 # Write patches to the supplied callback
1992 1996 write(patches)
1993 1997
1994 1998
1995 1999 @vcrcommand(
1996 2000 b'phabread',
1997 2001 [(b'', b'stack', False, _(b'read dependencies'))],
1998 2002 _(b'DREVSPEC... [OPTIONS]'),
1999 2003 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2000 2004 optionalrepo=True,
2001 2005 )
2002 2006 def phabread(ui, repo, *specs, **opts):
2003 2007 """print patches from Phabricator suitable for importing
2004 2008
2005 2009 DREVSPEC could be a Differential Revision identity, like ``D123``, or just
2006 2010 the number ``123``. It could also have common operators like ``+``, ``-``,
2007 2011 ``&``, ``(``, ``)`` for complex queries. Prefix ``:`` could be used to
2008 2012 select a stack. If multiple DREVSPEC values are given, the result is the
2009 2013 union of each individually evaluated value. No attempt is currently made
2010 2014 to reorder the values to run from parent to child.
2011 2015
2012 2016 ``abandoned``, ``accepted``, ``closed``, ``needsreview``, ``needsrevision``
2013 2017 could be used to filter patches by status. For performance reason, they
2014 2018 only represent a subset of non-status selections and cannot be used alone.
2015 2019
2016 2020 For example, ``:D6+8-(2+D4)`` selects a stack up to D6, plus D8 and exclude
2017 2021 D2 and D4. ``:D9 & needsreview`` selects "Needs Review" revisions in a
2018 2022 stack up to D9.
2019 2023
2020 2024 If --stack is given, follow dependencies information and read all patches.
2021 2025 It is equivalent to the ``:`` operator.
2022 2026 """
2023 2027 opts = pycompat.byteskwargs(opts)
2024 2028 drevs = _getdrevs(ui, opts.get(b'stack'), specs)
2025 2029
2026 2030 def _write(patches):
2027 2031 for drev, content in patches:
2028 2032 ui.write(content)
2029 2033
2030 2034 readpatch(ui, drevs, _write)
2031 2035
2032 2036
2033 2037 @vcrcommand(
2034 2038 b'phabimport',
2035 2039 [(b'', b'stack', False, _(b'import dependencies as well'))],
2036 2040 _(b'DREVSPEC... [OPTIONS]'),
2037 2041 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2038 2042 )
2039 2043 def phabimport(ui, repo, *specs, **opts):
2040 2044 """import patches from Phabricator for the specified Differential Revisions
2041 2045
2042 2046 The patches are read and applied starting at the parent of the working
2043 2047 directory.
2044 2048
2045 2049 See ``hg help phabread`` for how to specify DREVSPEC.
2046 2050 """
2047 2051 opts = pycompat.byteskwargs(opts)
2048 2052
2049 2053 # --bypass avoids losing exec and symlink bits when importing on Windows,
2050 2054 # and allows importing with a dirty wdir. It also aborts instead of leaving
2051 2055 # rejects.
2052 2056 opts[b'bypass'] = True
2053 2057
2054 2058 # Mandatory default values, synced with commands.import
2055 2059 opts[b'strip'] = 1
2056 2060 opts[b'prefix'] = b''
2057 2061 # Evolve 9.3.0 assumes this key is present in cmdutil.tryimportone()
2058 2062 opts[b'obsolete'] = False
2059 2063
2060 2064 if ui.configbool(b'phabimport', b'secret'):
2061 2065 opts[b'secret'] = True
2062 2066 if ui.configbool(b'phabimport', b'obsolete'):
2063 2067 opts[b'obsolete'] = True # Handled by evolve wrapping tryimportone()
2064 2068
2065 2069 def _write(patches):
2066 2070 parents = repo[None].parents()
2067 2071
2068 2072 with repo.wlock(), repo.lock(), repo.transaction(b'phabimport'):
2069 2073 for drev, contents in patches:
2070 2074 ui.status(_(b'applying patch from D%s\n') % drev)
2071 2075
2072 2076 with patch.extract(ui, pycompat.bytesio(contents)) as patchdata:
2073 2077 msg, node, rej = cmdutil.tryimportone(
2074 2078 ui,
2075 2079 repo,
2076 2080 patchdata,
2077 2081 parents,
2078 2082 opts,
2079 2083 [],
2080 2084 None, # Never update wdir to another revision
2081 2085 )
2082 2086
2083 2087 if not node:
2084 2088 raise error.Abort(_(b'D%s: no diffs found') % drev)
2085 2089
2086 2090 ui.note(msg + b'\n')
2087 2091 parents = [repo[node]]
2088 2092
2089 2093 drevs = _getdrevs(ui, opts.get(b'stack'), specs)
2090 2094
2091 2095 readpatch(repo.ui, drevs, _write)
2092 2096
2093 2097
2094 2098 @vcrcommand(
2095 2099 b'phabupdate',
2096 2100 [
2097 2101 (b'', b'accept', False, _(b'accept revisions')),
2098 2102 (b'', b'reject', False, _(b'reject revisions')),
2099 2103 (b'', b'abandon', False, _(b'abandon revisions')),
2100 2104 (b'', b'reclaim', False, _(b'reclaim revisions')),
2101 2105 (b'm', b'comment', b'', _(b'comment on the last revision')),
2102 2106 ],
2103 2107 _(b'DREVSPEC... [OPTIONS]'),
2104 2108 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2105 2109 optionalrepo=True,
2106 2110 )
2107 2111 def phabupdate(ui, repo, *specs, **opts):
2108 2112 """update Differential Revision in batch
2109 2113
2110 2114 DREVSPEC selects revisions. See :hg:`help phabread` for its usage.
2111 2115 """
2112 2116 opts = pycompat.byteskwargs(opts)
2113 2117 flags = [n for n in b'accept reject abandon reclaim'.split() if opts.get(n)]
2114 2118 if len(flags) > 1:
2115 2119 raise error.Abort(_(b'%s cannot be used together') % b', '.join(flags))
2116 2120
2117 2121 actions = []
2118 2122 for f in flags:
2119 2123 actions.append({b'type': f, b'value': True})
2120 2124
2121 2125 drevs = _getdrevs(ui, opts.get(b'stack'), specs)
2122 2126 for i, drev in enumerate(drevs):
2123 2127 if i + 1 == len(drevs) and opts.get(b'comment'):
2124 2128 actions.append({b'type': b'comment', b'value': opts[b'comment']})
2125 2129 if actions:
2126 2130 params = {
2127 2131 b'objectIdentifier': drev[b'phid'],
2128 2132 b'transactions': actions,
2129 2133 }
2130 2134 callconduit(ui, b'differential.revision.edit', params)
2131 2135
2132 2136
2133 2137 @eh.templatekeyword(b'phabreview', requires={b'ctx'})
2134 2138 def template_review(context, mapping):
2135 2139 """:phabreview: Object describing the review for this changeset.
2136 2140 Has attributes `url` and `id`.
2137 2141 """
2138 2142 ctx = context.resource(mapping, b'ctx')
2139 2143 m = _differentialrevisiondescre.search(ctx.description())
2140 2144 if m:
2141 2145 return templateutil.hybriddict(
2142 2146 {b'url': m.group('url'), b'id': b"D%s" % m.group('id'),}
2143 2147 )
2144 2148 else:
2145 2149 tags = ctx.repo().nodetags(ctx.node())
2146 2150 for t in tags:
2147 2151 if _differentialrevisiontagre.match(t):
2148 2152 url = ctx.repo().ui.config(b'phabricator', b'url')
2149 2153 if not url.endswith(b'/'):
2150 2154 url += b'/'
2151 2155 url += t
2152 2156
2153 2157 return templateutil.hybriddict({b'url': url, b'id': t,})
2154 2158 return None
2155 2159
2156 2160
2157 2161 @eh.templatekeyword(b'phabstatus', requires={b'ctx', b'repo', b'ui'})
2158 2162 def template_status(context, mapping):
2159 2163 """:phabstatus: String. Status of Phabricator differential.
2160 2164 """
2161 2165 ctx = context.resource(mapping, b'ctx')
2162 2166 repo = context.resource(mapping, b'repo')
2163 2167 ui = context.resource(mapping, b'ui')
2164 2168
2165 2169 rev = ctx.rev()
2166 2170 try:
2167 2171 drevid = getdrevmap(repo, [rev])[rev]
2168 2172 except KeyError:
2169 2173 return None
2170 2174 drevs = callconduit(ui, b'differential.query', {b'ids': [drevid]})
2171 2175 for drev in drevs:
2172 2176 if int(drev[b'id']) == drevid:
2173 2177 return templateutil.hybriddict(
2174 2178 {b'url': drev[b'uri'], b'status': drev[b'statusName'],}
2175 2179 )
2176 2180 return None
2177 2181
2178 2182
2179 2183 @show.showview(b'phabstatus', csettopic=b'work')
2180 2184 def phabstatusshowview(ui, repo, displayer):
2181 2185 """Phabricator differiential status"""
2182 2186 revs = repo.revs('sort(_underway(), topo)')
2183 2187 drevmap = getdrevmap(repo, revs)
2184 2188 unknownrevs, drevids, revsbydrevid = [], set(), {}
2185 2189 for rev, drevid in pycompat.iteritems(drevmap):
2186 2190 if drevid is not None:
2187 2191 drevids.add(drevid)
2188 2192 revsbydrevid.setdefault(drevid, set()).add(rev)
2189 2193 else:
2190 2194 unknownrevs.append(rev)
2191 2195
2192 2196 drevs = callconduit(ui, b'differential.query', {b'ids': list(drevids)})
2193 2197 drevsbyrev = {}
2194 2198 for drev in drevs:
2195 2199 for rev in revsbydrevid[int(drev[b'id'])]:
2196 2200 drevsbyrev[rev] = drev
2197 2201
2198 2202 def phabstatus(ctx):
2199 2203 drev = drevsbyrev[ctx.rev()]
2200 2204 status = ui.label(
2201 2205 b'%(statusName)s' % drev,
2202 2206 b'phabricator.status.%s' % _getstatusname(drev),
2203 2207 )
2204 2208 ui.write(b"\n%s %s\n" % (drev[b'uri'], status))
2205 2209
2206 2210 revs -= smartset.baseset(unknownrevs)
2207 2211 revdag = graphmod.dagwalker(repo, revs)
2208 2212
2209 2213 ui.setconfig(b'experimental', b'graphshorten', True)
2210 2214 displayer._exthook = phabstatus
2211 2215 nodelen = show.longestshortest(repo, revs)
2212 2216 logcmdutil.displaygraph(
2213 2217 ui,
2214 2218 repo,
2215 2219 revdag,
2216 2220 displayer,
2217 2221 graphmod.asciiedges,
2218 2222 props={b'nodelen': nodelen},
2219 2223 )
@@ -1,788 +1,838 b''
1 1 #require vcr
2 2 $ cat >> $HGRCPATH <<EOF
3 3 > [extensions]
4 4 > phabricator =
5 5 >
6 6 > [auth]
7 7 > hgphab.schemes = https
8 8 > hgphab.prefix = phab.mercurial-scm.org
9 9 > # When working on the extension and making phabricator interaction
10 10 > # changes, edit this to be a real phabricator token. When done, edit
11 11 > # it back. The VCR transcripts will be auto-sanitised to replace your real
12 12 > # token with this value.
13 13 > hgphab.phabtoken = cli-hahayouwish
14 14 >
15 15 > [phabricator]
16 16 > debug = True
17 17 > EOF
18 18 $ hg init repo
19 19 $ cd repo
20 20 $ cat >> .hg/hgrc <<EOF
21 21 > [phabricator]
22 22 > url = https://phab.mercurial-scm.org/
23 23 > callsign = HG
24 24 > EOF
25 25 $ VCR="$TESTDIR/phabricator"
26 26
27 27 Error is handled reasonably. We override the phabtoken here so that
28 28 when you're developing changes to phabricator.py you can edit the
29 29 above config and have a real token in the test but not have to edit
30 30 this test.
31 31 $ hg phabread --config auth.hgphab.phabtoken=cli-notavalidtoken \
32 32 > --test-vcr "$VCR/phabread-conduit-error.json" D4480 | head
33 33 abort: Conduit Error (ERR-INVALID-AUTH): API token "cli-notavalidtoken" has the wrong length. API tokens should be 32 characters long.
34 34
35 35 Missing arguments don't crash, and may print the command help
36 36
37 37 $ hg debugcallconduit
38 38 hg debugcallconduit: invalid arguments
39 39 hg debugcallconduit METHOD
40 40
41 41 call Conduit API
42 42
43 43 options:
44 44
45 45 (use 'hg debugcallconduit -h' to show more help)
46 46 [255]
47 47 $ hg phabread
48 48 abort: empty DREVSPEC set
49 49 [255]
50 50
51 51 Basic phabread:
52 52 $ hg phabread --test-vcr "$VCR/phabread-4480.json" D4480 | head
53 53 # HG changeset patch
54 54 # Date 1536771503 0
55 55 # Parent a5de21c9e3703f8e8eb064bd7d893ff2f703c66a
56 56 exchangev2: start to implement pull with wire protocol v2
57 57
58 58 Wire protocol version 2 will take a substantially different
59 59 approach to exchange than version 1 (at least as far as pulling
60 60 is concerned).
61 61
62 62 This commit establishes a new exchangev2 module for holding
63 63
64 64 Phabread with multiple DREVSPEC
65 65
66 66 TODO: attempt to order related revisions like --stack?
67 67 $ hg phabread --test-vcr "$VCR/phabread-multi-drev.json" D8205 8206 D8207 \
68 68 > | grep '^Differential Revision'
69 69 Differential Revision: https://phab.mercurial-scm.org/D8205
70 70 Differential Revision: https://phab.mercurial-scm.org/D8206
71 71 Differential Revision: https://phab.mercurial-scm.org/D8207
72 72
73 73 Empty DREVSPECs don't crash
74 74
75 75 $ hg phabread --test-vcr "$VCR/phabread-empty-drev.json" D7917-D7917
76 76 abort: empty DREVSPEC set
77 77 [255]
78 78
79 79
80 80 phabupdate with an accept:
81 81 $ hg phabupdate --accept D4564 \
82 82 > -m 'I think I like where this is headed. Will read rest of series later.'\
83 83 > --test-vcr "$VCR/accept-4564.json"
84 84 abort: Conduit Error (ERR-CONDUIT-CORE): Validation errors:
85 85 - You can not accept this revision because it has already been closed. Only open revisions can be accepted.
86 86 [255]
87 87 $ hg phabupdate --accept D7913 -m 'LGTM' --test-vcr "$VCR/accept-7913.json"
88 88
89 89 Create a differential diff:
90 90 $ HGENCODING=utf-8; export HGENCODING
91 91 $ echo alpha > alpha
92 92 $ hg ci --addremove -m 'create alpha for phabricator test €'
93 93 adding alpha
94 94 $ hg phabsend -r . --test-vcr "$VCR/phabsend-create-alpha.json"
95 95 D7915 - created - d386117f30e6: create alpha for phabricator test \xe2\x82\xac (esc)
96 96 new commits: ['347bf67801e5']
97 97 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/d386117f30e6-24ffe649-phabsend.hg
98 98 $ echo more >> alpha
99 99 $ HGEDITOR=true hg ci --amend
100 100 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/347bf67801e5-3bf313e4-amend.hg
101 101 $ echo beta > beta
102 102 $ hg ci --addremove -m 'create beta for phabricator test'
103 103 adding beta
104 104 $ hg phabsend -r ".^::" --test-vcr "$VCR/phabsend-update-alpha-create-beta.json"
105 105 c44b38f24a45 mapped to old nodes []
106 106 D7915 - updated - c44b38f24a45: create alpha for phabricator test \xe2\x82\xac (esc)
107 107 D7916 - created - 9e6901f21d5b: create beta for phabricator test
108 108 new commits: ['a692622e6937']
109 109 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9e6901f21d5b-1fcd4f0e-phabsend.hg
110 110 $ unset HGENCODING
111 111
112 112 The amend won't explode after posting a public commit. The local tag is left
113 113 behind to identify it.
114 114
115 115 $ echo 'public change' > beta
116 116 $ hg ci -m 'create public change for phabricator testing'
117 117 $ hg phase --public .
118 118 $ echo 'draft change' > alpha
119 119 $ hg ci -m 'create draft change for phabricator testing'
120 120 $ hg phabsend --amend -r '.^::' --test-vcr "$VCR/phabsend-create-public.json"
121 121 D7917 - created - 7b4185ab5d16: create public change for phabricator testing
122 122 D7918 - created - 251c1c333fc6: create draft change for phabricator testing
123 123 warning: not updating public commit 2:7b4185ab5d16
124 124 new commits: ['3244dc4a3334']
125 125 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/251c1c333fc6-41cb7c3b-phabsend.hg
126 126 $ hg tags -v
127 127 tip 3:3244dc4a3334
128 128 D7917 2:7b4185ab5d16 local
129 129
130 130 $ hg debugcallconduit user.search --test-vcr "$VCR/phab-conduit.json" <<EOF
131 131 > {
132 132 > "constraints": {
133 133 > "isBot": true
134 134 > }
135 135 > }
136 136 > EOF
137 137 {
138 138 "cursor": {
139 139 "after": null,
140 140 "before": null,
141 141 "limit": 100,
142 142 "order": null
143 143 },
144 144 "data": [],
145 145 "maps": {},
146 146 "query": {
147 147 "queryKey": null
148 148 }
149 149 }
150 150
151 151 Template keywords
152 152 $ hg log -T'{rev} {phabreview|json}\n'
153 153 3 {"id": "D7918", "url": "https://phab.mercurial-scm.org/D7918"}
154 154 2 {"id": "D7917", "url": "https://phab.mercurial-scm.org/D7917"}
155 155 1 {"id": "D7916", "url": "https://phab.mercurial-scm.org/D7916"}
156 156 0 {"id": "D7915", "url": "https://phab.mercurial-scm.org/D7915"}
157 157
158 158 $ hg log -T'{rev} {if(phabreview, "{phabreview.url} {phabreview.id}")}\n'
159 159 3 https://phab.mercurial-scm.org/D7918 D7918
160 160 2 https://phab.mercurial-scm.org/D7917 D7917
161 161 1 https://phab.mercurial-scm.org/D7916 D7916
162 162 0 https://phab.mercurial-scm.org/D7915 D7915
163 163
164 164 Commenting when phabsending:
165 165 $ echo comment > comment
166 166 $ hg ci --addremove -m "create comment for phabricator test"
167 167 adding comment
168 168 $ hg phabsend -r . -m "For default branch" --test-vcr "$VCR/phabsend-comment-created.json"
169 169 D7919 - created - d5dddca9023d: create comment for phabricator test
170 170 new commits: ['f7db812bbe1d']
171 171 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/d5dddca9023d-adf673ba-phabsend.hg
172 172 $ echo comment2 >> comment
173 173 $ hg ci --amend
174 174 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/f7db812bbe1d-8fcded77-amend.hg
175 175 $ hg phabsend -r . -m "Address review comments" --test-vcr "$VCR/phabsend-comment-updated.json"
176 176 1849d7828727 mapped to old nodes []
177 177 D7919 - updated - 1849d7828727: create comment for phabricator test
178 178
179 179 Phabsending a skipped commit:
180 180 $ hg phabsend --no-amend -r . --test-vcr "$VCR/phabsend-skipped.json"
181 181 1849d7828727 mapped to old nodes ['1849d7828727']
182 182 D7919 - skipped - 1849d7828727: create comment for phabricator test
183 183
184 Phabsend doesn't create an instability when rebasing existing revisions on top
185 of new revisions.
186
187 $ hg init reorder
188 $ cd reorder
189 $ cat >> .hg/hgrc <<EOF
190 > [phabricator]
191 > url = https://phab.mercurial-scm.org/
192 > callsign = HG
193 > [experimental]
194 > evolution = all
195 > EOF
196
197 $ echo "add" > file1.txt
198 $ hg ci -Aqm 'added'
199 $ echo "mod1" > file1.txt
200 $ hg ci -m 'modified 1'
201 $ echo "mod2" > file1.txt
202 $ hg ci -m 'modified 2'
203 $ hg phabsend -r . --test-vcr "$VCR/phabsend-add-parent-setup.json"
204 D8433 - created - 5d3959e20d1d: modified 2
205 new commits: ['2b4aa8a88d61']
206 $ hg log -G -T compact
207 @ 3[tip]:1 2b4aa8a88d61 1970-01-01 00:00 +0000 test
208 | modified 2
209 |
210 o 1 d549263bcb2d 1970-01-01 00:00 +0000 test
211 | modified 1
212 |
213 o 0 5cbade24e0fa 1970-01-01 00:00 +0000 test
214 added
215
216 $ hg phabsend -r ".^ + ." --test-vcr "$VCR/phabsend-add-parent.json"
217 2b4aa8a88d61 mapped to old nodes ['2b4aa8a88d61']
218 D8434 - created - d549263bcb2d: modified 1
219 D8433 - updated - 2b4aa8a88d61: modified 2
220 new commits: ['876a60d024de']
221 new commits: ['0c6523cb1d0f']
222 $ hg log -G -T compact
223 @ 5[tip] 1dff6b051abf 1970-01-01 00:00 +0000 test
224 | modified 2
225 |
226 o 4:0 eb3752621d45 1970-01-01 00:00 +0000 test
227 | modified 1
228 |
229 o 0 5cbade24e0fa 1970-01-01 00:00 +0000 test
230 added
231
232 $ cd ..
233
184 234 Phabesending a new binary, a modified binary, and a removed binary
185 235
186 236 >>> open('bin', 'wb').write(b'\0a') and None
187 237 $ hg ci -Am 'add binary'
188 238 adding bin
189 239 >>> open('bin', 'wb').write(b'\0b') and None
190 240 $ hg ci -m 'modify binary'
191 241 $ hg rm bin
192 242 $ hg ci -m 'remove binary'
193 243 $ hg phabsend -r .~2:: --test-vcr "$VCR/phabsend-binary.json"
194 244 uploading bin@aa24a81f55de
195 245 D8007 - created - aa24a81f55de: add binary
196 246 uploading bin@d8d62a881b54
197 247 D8008 - created - d8d62a881b54: modify binary
198 248 D8009 - created - af55645b2e29: remove binary
199 249 new commits: ['b8139fbb4a57']
200 250 new commits: ['c88ce4c2d2ad']
201 251 new commits: ['75dbbc901145']
202 252 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/aa24a81f55de-a3a0cf24-phabsend.hg
203 253
204 254 Phabsend a renamed binary and a copied binary, with and without content changes
205 255 to src and dest
206 256
207 257 >>> open('bin2', 'wb').write(b'\0c') and None
208 258 $ hg ci -Am 'add another binary'
209 259 adding bin2
210 260
211 261 TODO: "bin2" can't be viewed in this commit (left or right side), and the URL
212 262 looks much different than when viewing "bin2_moved". No idea if this is a phab
213 263 bug, or phabsend bug. The patch (as printed by phabread) look reasonable
214 264 though.
215 265
216 266 $ hg mv bin2 bin2_moved
217 267 $ hg ci -m "moved binary"
218 268
219 269 Note: "bin2_moved" is also not viewable in phabricator with this review
220 270
221 271 $ hg cp bin2_moved bin2_copied
222 272 $ hg ci -m "copied binary"
223 273
224 274 Note: "bin2_moved_again" is marked binary in phabricator, and both sides of it
225 275 are viewable in their proper state. "bin2_copied" is not viewable, and not
226 276 listed as binary in phabricator.
227 277
228 278 >>> open('bin2_copied', 'wb').write(b'\0move+mod') and None
229 279 $ hg mv bin2_copied bin2_moved_again
230 280 $ hg ci -m "move+mod copied binary"
231 281
232 282 Note: "bin2_moved" and "bin2_moved_copy" are both marked binary, and both
233 283 viewable on each side.
234 284
235 285 >>> open('bin2_moved', 'wb').write(b'\0precopy mod') and None
236 286 $ hg cp bin2_moved bin2_moved_copied
237 287 >>> open('bin2_moved', 'wb').write(b'\0copy src+mod') and None
238 288 $ hg ci -m "copy+mod moved binary"
239 289
240 290 $ hg phabsend -r .~4:: --test-vcr "$VCR/phabsend-binary-renames.json"
241 291 uploading bin2@f42f9195e00c
242 292 D8128 - created - f42f9195e00c: add another binary
243 293 D8129 - created - 834ab31d80ae: moved binary
244 294 D8130 - created - 494b750e5194: copied binary
245 295 uploading bin2_moved_again@25f766b50cc2
246 296 D8131 - created - 25f766b50cc2: move+mod copied binary
247 297 uploading bin2_moved_copied@1b87b363a5e4
248 298 uploading bin2_moved@1b87b363a5e4
249 299 D8132 - created - 1b87b363a5e4: copy+mod moved binary
250 300 new commits: ['90437c20312a']
251 301 new commits: ['f391f4da4c61']
252 302 new commits: ['da86a9f3268c']
253 303 new commits: ['003ffc16ba66']
254 304 new commits: ['13bd750c36fa']
255 305 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/f42f9195e00c-e82a0769-phabsend.hg
256 306
257 307 Phabreading a DREV with a local:commits time as a string:
258 308 $ hg phabread --test-vcr "$VCR/phabread-str-time.json" D1285
259 309 # HG changeset patch
260 310 # User Pulkit Goyal <7895pulkit@gmail.com>
261 311 # Date 1509404054 -19800
262 312 # Node ID 44fc1c1f1774a76423b9c732af6938435099bcc5
263 313 # Parent 8feef8ef8389a3b544e0a74624f1efc3a8d85d35
264 314 repoview: add a new attribute _visibilityexceptions and related API
265 315
266 316 Currently we don't have a defined way in core to make some hidden revisions
267 317 visible in filtered repo. Extensions to achieve the purpose of unhiding some
268 318 hidden commits, wrap repoview.pinnedrevs() function.
269 319
270 320 To make the above task simple and have well defined API, this patch adds a new
271 321 attribute '_visibilityexceptions' to repoview class which will contains
272 322 the hidden revs which should be exception.
273 323 This will allow to set different exceptions for different repoview objects
274 324 backed by the same unfiltered repo.
275 325
276 326 This patch also adds API to add revs to the attribute set and get them.
277 327
278 328 Thanks to Jun for suggesting the use of repoview class instead of localrepo.
279 329
280 330 Differential Revision: https://phab.mercurial-scm.org/D1285
281 331 diff --git a/mercurial/repoview.py b/mercurial/repoview.py
282 332 --- a/mercurial/repoview.py
283 333 +++ b/mercurial/repoview.py
284 334 @@ * @@ (glob)
285 335 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
286 336 """
287 337
288 338 + # hidden revs which should be visible
289 339 + _visibilityexceptions = set()
290 340 +
291 341 def __init__(self, repo, filtername):
292 342 object.__setattr__(self, r'_unfilteredrepo', repo)
293 343 object.__setattr__(self, r'filtername', filtername)
294 344 @@ -231,6 +234,14 @@
295 345 return self
296 346 return self.unfiltered().filtered(name)
297 347
298 348 + def addvisibilityexceptions(self, revs):
299 349 + """adds hidden revs which should be visible to set of exceptions"""
300 350 + self._visibilityexceptions.update(revs)
301 351 +
302 352 + def getvisibilityexceptions(self):
303 353 + """returns the set of hidden revs which should be visible"""
304 354 + return self._visibilityexceptions
305 355 +
306 356 # everything access are forwarded to the proxied repo
307 357 def __getattr__(self, attr):
308 358 return getattr(self._unfilteredrepo, attr)
309 359 diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
310 360 --- a/mercurial/localrepo.py
311 361 +++ b/mercurial/localrepo.py
312 362 @@ -570,6 +570,14 @@
313 363 def close(self):
314 364 self._writecaches()
315 365
316 366 + def addvisibilityexceptions(self, exceptions):
317 367 + # should be called on a filtered repository
318 368 + pass
319 369 +
320 370 + def getvisibilityexceptions(self):
321 371 + # should be called on a filtered repository
322 372 + return set()
323 373 +
324 374 def _loadextensions(self):
325 375 extensions.loadall(self.ui)
326 376
327 377
328 378 A bad .arcconfig doesn't error out
329 379 $ echo 'garbage' > .arcconfig
330 380 $ hg config phabricator --debug
331 381 invalid JSON in $TESTTMP/repo/.arcconfig
332 382 read config from: */.hgrc (glob)
333 383 */.hgrc:*: phabricator.debug=True (glob)
334 384 $TESTTMP/repo/.hg/hgrc:*: phabricator.url=https://phab.mercurial-scm.org/ (glob)
335 385 $TESTTMP/repo/.hg/hgrc:*: phabricator.callsign=HG (glob)
336 386
337 387 The .arcconfig content overrides global config
338 388 $ cat >> $HGRCPATH << EOF
339 389 > [phabricator]
340 390 > url = global
341 391 > callsign = global
342 392 > EOF
343 393 $ cp $TESTDIR/../.arcconfig .
344 394 $ mv .hg/hgrc .hg/hgrc.bak
345 395 $ hg config phabricator --debug
346 396 read config from: */.hgrc (glob)
347 397 */.hgrc:*: phabricator.debug=True (glob)
348 398 $TESTTMP/repo/.arcconfig: phabricator.callsign=HG
349 399 $TESTTMP/repo/.arcconfig: phabricator.url=https://phab.mercurial-scm.org/
350 400
351 401 But it doesn't override local config
352 402 $ cat >> .hg/hgrc << EOF
353 403 > [phabricator]
354 404 > url = local
355 405 > callsign = local
356 406 > EOF
357 407 $ hg config phabricator --debug
358 408 read config from: */.hgrc (glob)
359 409 */.hgrc:*: phabricator.debug=True (glob)
360 410 $TESTTMP/repo/.hg/hgrc:*: phabricator.url=local (glob)
361 411 $TESTTMP/repo/.hg/hgrc:*: phabricator.callsign=local (glob)
362 412 $ mv .hg/hgrc.bak .hg/hgrc
363 413
364 414 Phabimport works with a stack
365 415
366 416 $ cd ..
367 417 $ hg clone repo repo2 -qr 1
368 418 $ cp repo/.hg/hgrc repo2/.hg/
369 419 $ cd repo2
370 420 $ hg phabimport --stack 'D7918' --test-vcr "$VCR/phabimport-stack.json"
371 421 applying patch from D7917
372 422 applying patch from D7918
373 423 $ hg log -r .: -G -Tcompact
374 424 o 3[tip] aaef04066140 1970-01-01 00:00 +0000 test
375 425 | create draft change for phabricator testing
376 426 |
377 427 o 2 8de3712202d1 1970-01-01 00:00 +0000 test
378 428 | create public change for phabricator testing
379 429 |
380 430 @ 1 a692622e6937 1970-01-01 00:00 +0000 test
381 431 | create beta for phabricator test
382 432 ~
383 433 Phabimport can create secret commits
384 434
385 435 $ hg rollback --config ui.rollback=True
386 436 repository tip rolled back to revision 1 (undo phabimport)
387 437 $ hg phabimport --stack 'D7918' --test-vcr "$VCR/phabimport-stack.json" \
388 438 > --config phabimport.secret=True
389 439 applying patch from D7917
390 440 applying patch from D7918
391 441 $ hg log -r 'reverse(.:)' -T phases
392 442 changeset: 3:aaef04066140
393 443 tag: tip
394 444 phase: secret
395 445 user: test
396 446 date: Thu Jan 01 00:00:00 1970 +0000
397 447 summary: create draft change for phabricator testing
398 448
399 449 changeset: 2:8de3712202d1
400 450 phase: secret
401 451 user: test
402 452 date: Thu Jan 01 00:00:00 1970 +0000
403 453 summary: create public change for phabricator testing
404 454
405 455 changeset: 1:a692622e6937
406 456 phase: public
407 457 user: test
408 458 date: Thu Jan 01 00:00:00 1970 +0000
409 459 summary: create beta for phabricator test
410 460
411 461 Phabimport accepts multiple DREVSPECs
412 462
413 463 $ hg rollback --config ui.rollback=True
414 464 repository tip rolled back to revision 1 (undo phabimport)
415 465 $ hg phabimport --no-stack D7917 D7918 --test-vcr "$VCR/phabimport-multi-drev.json"
416 466 applying patch from D7917
417 467 applying patch from D7918
418 468
419 469 Validate arguments with --fold
420 470
421 471 $ hg phabsend --fold -r 1
422 472 abort: cannot fold a single revision
423 473 [255]
424 474 $ hg phabsend --fold --no-amend -r 1::
425 475 abort: cannot fold with --no-amend
426 476 [255]
427 477 $ hg phabsend --fold -r 0+3
428 478 abort: cannot fold non-linear revisions
429 479 [255]
430 480 $ hg phabsend --fold -r 1::
431 481 abort: cannot fold revisions with different DREV values
432 482 [255]
433 483
434 484 Setup a series of commits to be folded, and include the Test Plan field multiple
435 485 times to test the concatenation logic. No Test Plan field in the last one to
436 486 ensure missing fields are skipped.
437 487
438 488 $ hg init ../folded
439 489 $ cd ../folded
440 490 $ cat >> .hg/hgrc <<EOF
441 491 > [phabricator]
442 492 > url = https://phab.mercurial-scm.org/
443 493 > callsign = HG
444 494 > EOF
445 495
446 496 $ echo 'added' > file.txt
447 497 $ hg ci -Aqm 'added file'
448 498
449 499 $ cat > log.txt <<EOF
450 500 > one: first commit to review
451 501 >
452 502 > This file was modified with 'mod1' as its contents.
453 503 >
454 504 > Test Plan:
455 505 > LOL! What testing?!
456 506 > EOF
457 507 $ echo mod1 > file.txt
458 508 $ hg ci -l log.txt
459 509
460 510 $ cat > log.txt <<EOF
461 511 > two: second commit to review
462 512 >
463 513 > This file was modified with 'mod2' as its contents.
464 514 >
465 515 > Test Plan:
466 516 > Haha! yeah, right.
467 517 >
468 518 > EOF
469 519 $ echo mod2 > file.txt
470 520 $ hg ci -l log.txt
471 521
472 522 $ echo mod3 > file.txt
473 523 $ hg ci -m '3: a commit with no detailed message'
474 524
475 525 The folding of immutable commits works...
476 526
477 527 $ hg phase -r tip --public
478 528 $ hg phabsend --fold -r 1:: --test-vcr "$VCR/phabsend-fold-immutable.json"
479 529 D8386 - created - a959a3f69d8d: one: first commit to review
480 530 D8386 - created - 24a4438154ba: two: second commit to review
481 531 D8386 - created - d235829e802c: 3: a commit with no detailed message
482 532 warning: not updating public commit 1:a959a3f69d8d
483 533 warning: not updating public commit 2:24a4438154ba
484 534 warning: not updating public commit 3:d235829e802c
485 535 no newnodes to update
486 536
487 537 $ hg phase -r 0 --draft --force
488 538
489 539 ... as does the initial mutable fold...
490 540
491 541 $ echo y | hg phabsend --fold --confirm -r 1:: \
492 542 > --test-vcr "$VCR/phabsend-fold-initial.json"
493 543 NEW - a959a3f69d8d: one: first commit to review
494 544 NEW - 24a4438154ba: two: second commit to review
495 545 NEW - d235829e802c: 3: a commit with no detailed message
496 546 Send the above changes to https://phab.mercurial-scm.org/ (yn)? y
497 547 D8387 - created - a959a3f69d8d: one: first commit to review
498 548 D8387 - created - 24a4438154ba: two: second commit to review
499 549 D8387 - created - d235829e802c: 3: a commit with no detailed message
500 550 updating local commit list for D8387
501 551 new commits: ['602c4e738243', '832553266fe8', '921f8265efbd']
502 552 saved backup bundle to $TESTTMP/folded/.hg/strip-backup/a959a3f69d8d-a4a24136-phabsend.hg
503 553
504 554 ... and doesn't mangle the local commits.
505 555
506 556 $ hg log -T '{rev}:{node|short}\n{indent(desc, " ")}\n'
507 557 3:921f8265efbd
508 558 3: a commit with no detailed message
509 559
510 560 Differential Revision: https://phab.mercurial-scm.org/D8387
511 561 2:832553266fe8
512 562 two: second commit to review
513 563
514 564 This file was modified with 'mod2' as its contents.
515 565
516 566 Test Plan:
517 567 Haha! yeah, right.
518 568
519 569 Differential Revision: https://phab.mercurial-scm.org/D8387
520 570 1:602c4e738243
521 571 one: first commit to review
522 572
523 573 This file was modified with 'mod1' as its contents.
524 574
525 575 Test Plan:
526 576 LOL! What testing?!
527 577
528 578 Differential Revision: https://phab.mercurial-scm.org/D8387
529 579 0:98d480e0d494
530 580 added file
531 581
532 582 Setup some obsmarkers by adding a file to the middle commit. This stress tests
533 583 getoldnodedrevmap() in later phabsends.
534 584
535 585 $ hg up '.^'
536 586 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
537 587 $ echo 'modified' > file2.txt
538 588 $ hg add file2.txt
539 589 $ hg amend --config experimental.evolution=all --config extensions.amend=
540 590 1 new orphan changesets
541 591 $ hg up 3
542 592 obsolete feature not enabled but 1 markers found!
543 593 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
544 594 $ hg rebase --config experimental.evolution=all --config extensions.rebase=
545 595 note: not rebasing 2:832553266fe8 "two: second commit to review", already in destination as 4:0124e5474c88 "two: second commit to review" (tip)
546 596 rebasing 3:921f8265efbd "3: a commit with no detailed message"
547 597
548 598 When commits have changed locally, the local commit list on Phabricator is
549 599 updated.
550 600
551 601 $ echo y | hg phabsend --fold --confirm -r 1:: \
552 602 > --test-vcr "$VCR/phabsend-fold-updated.json"
553 603 obsolete feature not enabled but 2 markers found!
554 604 602c4e738243 mapped to old nodes ['602c4e738243']
555 605 0124e5474c88 mapped to old nodes ['832553266fe8']
556 606 e4edb1fe3565 mapped to old nodes ['921f8265efbd']
557 607 D8387 - 602c4e738243: one: first commit to review
558 608 D8387 - 0124e5474c88: two: second commit to review
559 609 D8387 - e4edb1fe3565: 3: a commit with no detailed message
560 610 Send the above changes to https://phab.mercurial-scm.org/ (yn)? y
561 611 D8387 - updated - 602c4e738243: one: first commit to review
562 612 D8387 - updated - 0124e5474c88: two: second commit to review
563 613 D8387 - updated - e4edb1fe3565: 3: a commit with no detailed message
564 614 obsolete feature not enabled but 2 markers found! (?)
565 615 updating local commit list for D8387
566 616 new commits: ['602c4e738243', '0124e5474c88', 'e4edb1fe3565']
567 617 $ hg log -Tcompact
568 618 obsolete feature not enabled but 2 markers found!
569 619 5[tip] e4edb1fe3565 1970-01-01 00:00 +0000 test
570 620 3: a commit with no detailed message
571 621
572 622 4:1 0124e5474c88 1970-01-01 00:00 +0000 test
573 623 two: second commit to review
574 624
575 625 1 602c4e738243 1970-01-01 00:00 +0000 test
576 626 one: first commit to review
577 627
578 628 0 98d480e0d494 1970-01-01 00:00 +0000 test
579 629 added file
580 630
581 631 When nothing has changed locally since the last phabsend, the commit list isn't
582 632 updated, and nothing is changed locally afterward.
583 633
584 634 $ hg phabsend --fold -r 1:: --test-vcr "$VCR/phabsend-fold-no-changes.json"
585 635 obsolete feature not enabled but 2 markers found!
586 636 602c4e738243 mapped to old nodes ['602c4e738243']
587 637 0124e5474c88 mapped to old nodes ['0124e5474c88']
588 638 e4edb1fe3565 mapped to old nodes ['e4edb1fe3565']
589 639 D8387 - updated - 602c4e738243: one: first commit to review
590 640 D8387 - updated - 0124e5474c88: two: second commit to review
591 641 D8387 - updated - e4edb1fe3565: 3: a commit with no detailed message
592 642 obsolete feature not enabled but 2 markers found! (?)
593 643 local commit list for D8387 is already up-to-date
594 644 $ hg log -Tcompact
595 645 obsolete feature not enabled but 2 markers found!
596 646 5[tip] e4edb1fe3565 1970-01-01 00:00 +0000 test
597 647 3: a commit with no detailed message
598 648
599 649 4:1 0124e5474c88 1970-01-01 00:00 +0000 test
600 650 two: second commit to review
601 651
602 652 1 602c4e738243 1970-01-01 00:00 +0000 test
603 653 one: first commit to review
604 654
605 655 0 98d480e0d494 1970-01-01 00:00 +0000 test
606 656 added file
607 657
608 658 Fold will accept new revisions at the end...
609 659
610 660 $ echo 'another mod' > file2.txt
611 661 $ hg ci -m 'four: extend the fold range'
612 662 obsolete feature not enabled but 2 markers found!
613 663 $ hg phabsend --fold -r 1:: --test-vcr "$VCR/phabsend-fold-extend-end.json" \
614 664 > --config experimental.evolution=all
615 665 602c4e738243 mapped to old nodes ['602c4e738243']
616 666 0124e5474c88 mapped to old nodes ['0124e5474c88']
617 667 e4edb1fe3565 mapped to old nodes ['e4edb1fe3565']
618 668 D8387 - updated - 602c4e738243: one: first commit to review
619 669 D8387 - updated - 0124e5474c88: two: second commit to review
620 670 D8387 - updated - e4edb1fe3565: 3: a commit with no detailed message
621 671 D8387 - created - 94aaae213b23: four: extend the fold range
622 672 updating local commit list for D8387
623 673 new commits: ['602c4e738243', '0124e5474c88', 'e4edb1fe3565', '51a04fea8707']
624 674 $ hg log -r . -T '{desc}\n'
625 675 four: extend the fold range
626 676
627 677 Differential Revision: https://phab.mercurial-scm.org/D8387
628 678 $ hg log -T'{rev} {if(phabreview, "{phabreview.url} {phabreview.id}")}\n' -r 1::
629 679 obsolete feature not enabled but 3 markers found!
630 680 1 https://phab.mercurial-scm.org/D8387 D8387
631 681 4 https://phab.mercurial-scm.org/D8387 D8387
632 682 5 https://phab.mercurial-scm.org/D8387 D8387
633 683 7 https://phab.mercurial-scm.org/D8387 D8387
634 684
635 685 ... and also accepts new revisions at the beginning of the range
636 686
637 687 It's a bit unfortunate that not having a Differential URL on the first commit
638 688 causes a new Differential Revision to be created, though it isn't *entirely*
639 689 unreasonable. At least this updates the subsequent commits.
640 690
641 691 TODO: See if it can reuse the existing Differential.
642 692
643 693 $ hg phabsend --fold -r 0:: --test-vcr "$VCR/phabsend-fold-extend-front.json" \
644 694 > --config experimental.evolution=all
645 695 602c4e738243 mapped to old nodes ['602c4e738243']
646 696 0124e5474c88 mapped to old nodes ['0124e5474c88']
647 697 e4edb1fe3565 mapped to old nodes ['e4edb1fe3565']
648 698 51a04fea8707 mapped to old nodes ['51a04fea8707']
649 699 D8388 - created - 98d480e0d494: added file
650 700 D8388 - updated - 602c4e738243: one: first commit to review
651 701 D8388 - updated - 0124e5474c88: two: second commit to review
652 702 D8388 - updated - e4edb1fe3565: 3: a commit with no detailed message
653 703 D8388 - updated - 51a04fea8707: four: extend the fold range
654 704 updating local commit list for D8388
655 705 new commits: ['15e9b14b4b4c', '6320b7d714cf', '3ee132d41dbc', '30682b960804', 'ac7db67f0991']
656 706
657 707 $ hg log -T '{rev}:{node|short}\n{indent(desc, " ")}\n'
658 708 obsolete feature not enabled but 8 markers found!
659 709 12:ac7db67f0991
660 710 four: extend the fold range
661 711
662 712 Differential Revision: https://phab.mercurial-scm.org/D8388
663 713 11:30682b960804
664 714 3: a commit with no detailed message
665 715
666 716 Differential Revision: https://phab.mercurial-scm.org/D8388
667 717 10:3ee132d41dbc
668 718 two: second commit to review
669 719
670 720 This file was modified with 'mod2' as its contents.
671 721
672 722 Test Plan:
673 723 Haha! yeah, right.
674 724
675 725 Differential Revision: https://phab.mercurial-scm.org/D8388
676 726 9:6320b7d714cf
677 727 one: first commit to review
678 728
679 729 This file was modified with 'mod1' as its contents.
680 730
681 731 Test Plan:
682 732 LOL! What testing?!
683 733
684 734 Differential Revision: https://phab.mercurial-scm.org/D8388
685 735 8:15e9b14b4b4c
686 736 added file
687 737
688 738 Differential Revision: https://phab.mercurial-scm.org/D8388
689 739
690 740 Test phabsend --fold with an `hg split` at the end of the range
691 741
692 742 $ echo foo > file3.txt
693 743 $ hg add file3.txt
694 744
695 745 $ hg log -r . -T '{desc}' > log.txt
696 746 $ echo 'amended mod' > file2.txt
697 747 $ hg ci --amend -l log.txt --config experimental.evolution=all
698 748
699 749 $ cat <<EOF | hg --config extensions.split= --config ui.interactive=True \
700 750 > --config experimental.evolution=all split -r .
701 751 > n
702 752 > y
703 753 > y
704 754 > y
705 755 > y
706 756 > EOF
707 757 diff --git a/file2.txt b/file2.txt
708 758 1 hunks, 1 lines changed
709 759 examine changes to 'file2.txt'?
710 760 (enter ? for help) [Ynesfdaq?] n
711 761
712 762 diff --git a/file3.txt b/file3.txt
713 763 new file mode 100644
714 764 examine changes to 'file3.txt'?
715 765 (enter ? for help) [Ynesfdaq?] y
716 766
717 767 @@ -0,0 +1,1 @@
718 768 +foo
719 769 record change 2/2 to 'file3.txt'?
720 770 (enter ? for help) [Ynesfdaq?] y
721 771
722 772 created new head
723 773 diff --git a/file2.txt b/file2.txt
724 774 1 hunks, 1 lines changed
725 775 examine changes to 'file2.txt'?
726 776 (enter ? for help) [Ynesfdaq?] y
727 777
728 778 @@ -1,1 +1,1 @@
729 779 -modified
730 780 +amended mod
731 781 record this change to 'file2.txt'?
732 782 (enter ? for help) [Ynesfdaq?] y
733 783
734 784 $ hg phabsend --fold -r 8:: --test-vcr "$VCR/phabsend-fold-split-end.json" \
735 785 > --config experimental.evolution=all
736 786 15e9b14b4b4c mapped to old nodes ['15e9b14b4b4c']
737 787 6320b7d714cf mapped to old nodes ['6320b7d714cf']
738 788 3ee132d41dbc mapped to old nodes ['3ee132d41dbc']
739 789 30682b960804 mapped to old nodes ['30682b960804']
740 790 6bc15dc99efd mapped to old nodes ['ac7db67f0991']
741 791 b50946d5e490 mapped to old nodes ['ac7db67f0991']
742 792 D8388 - updated - 15e9b14b4b4c: added file
743 793 D8388 - updated - 6320b7d714cf: one: first commit to review
744 794 D8388 - updated - 3ee132d41dbc: two: second commit to review
745 795 D8388 - updated - 30682b960804: 3: a commit with no detailed message
746 796 D8388 - updated - 6bc15dc99efd: four: extend the fold range
747 797 D8388 - updated - b50946d5e490: four: extend the fold range
748 798 updating local commit list for D8388
749 799 new commits: ['15e9b14b4b4c', '6320b7d714cf', '3ee132d41dbc', '30682b960804', '6bc15dc99efd', 'b50946d5e490']
750 800
751 801 Test phabsend --fold with an `hg fold` at the end of the range
752 802
753 803 $ hg --config experimental.evolution=all --config extensions.rebase= \
754 804 > rebase -r '.^' -r . -d '.^^' --collapse -l log.txt
755 805 rebasing 14:6bc15dc99efd "four: extend the fold range"
756 806 rebasing 15:b50946d5e490 "four: extend the fold range" (tip)
757 807
758 808 $ hg phabsend --fold -r 8:: --test-vcr "$VCR/phabsend-fold-fold-end.json" \
759 809 > --config experimental.evolution=all
760 810 15e9b14b4b4c mapped to old nodes ['15e9b14b4b4c']
761 811 6320b7d714cf mapped to old nodes ['6320b7d714cf']
762 812 3ee132d41dbc mapped to old nodes ['3ee132d41dbc']
763 813 30682b960804 mapped to old nodes ['30682b960804']
764 814 e919cdf3d4fe mapped to old nodes ['6bc15dc99efd', 'b50946d5e490']
765 815 D8388 - updated - 15e9b14b4b4c: added file
766 816 D8388 - updated - 6320b7d714cf: one: first commit to review
767 817 D8388 - updated - 3ee132d41dbc: two: second commit to review
768 818 D8388 - updated - 30682b960804: 3: a commit with no detailed message
769 819 D8388 - updated - e919cdf3d4fe: four: extend the fold range
770 820 updating local commit list for D8388
771 821 new commits: ['15e9b14b4b4c', '6320b7d714cf', '3ee132d41dbc', '30682b960804', 'e919cdf3d4fe']
772 822
773 823 $ hg log -r tip -v
774 824 obsolete feature not enabled but 12 markers found!
775 825 changeset: 16:e919cdf3d4fe
776 826 tag: tip
777 827 parent: 11:30682b960804
778 828 user: test
779 829 date: Thu Jan 01 00:00:00 1970 +0000
780 830 files: file2.txt file3.txt
781 831 description:
782 832 four: extend the fold range
783 833
784 834 Differential Revision: https://phab.mercurial-scm.org/D8388
785 835
786 836
787 837
788 838 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now