Show More
The requested changes are too big and content was truncated. Show full diff
This diff has been collapsed as it changes many lines, (3411 lines changed) Show them Hide them | |||||
@@ -0,0 +1,3411 b'' | |||||
|
1 | { | |||
|
2 | "version": 1, | |||
|
3 | "interactions": [ | |||
|
4 | { | |||
|
5 | "response": { | |||
|
6 | "headers": { | |||
|
7 | "date": [ | |||
|
8 | "Tue, 18 Feb 2020 18:43:11 GMT" | |||
|
9 | ], | |||
|
10 | "strict-transport-security": [ | |||
|
11 | "max-age=0; includeSubdomains; preload" | |||
|
12 | ], | |||
|
13 | "x-frame-options": [ | |||
|
14 | "Deny" | |||
|
15 | ], | |||
|
16 | "content-type": [ | |||
|
17 | "application/json" | |||
|
18 | ], | |||
|
19 | "transfer-encoding": [ | |||
|
20 | "chunked" | |||
|
21 | ], | |||
|
22 | "x-xss-protection": [ | |||
|
23 | "1; mode=block" | |||
|
24 | ], | |||
|
25 | "expires": [ | |||
|
26 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
27 | ], | |||
|
28 | "x-content-type-options": [ | |||
|
29 | "nosniff" | |||
|
30 | ], | |||
|
31 | "referrer-policy": [ | |||
|
32 | "no-referrer" | |||
|
33 | ], | |||
|
34 | "server": [ | |||
|
35 | "Apache/2.4.10 (Debian)" | |||
|
36 | ], | |||
|
37 | "cache-control": [ | |||
|
38 | "no-store" | |||
|
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 | "host": [ | |||
|
55 | "phab.mercurial-scm.org" | |||
|
56 | ], | |||
|
57 | "content-type": [ | |||
|
58 | "application/x-www-form-urlencoded" | |||
|
59 | ], | |||
|
60 | "user-agent": [ | |||
|
61 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
62 | ], | |||
|
63 | "accept": [ | |||
|
64 | "application/mercurial-0.1" | |||
|
65 | ] | |||
|
66 | }, | |||
|
67 | "uri": "https://phab.mercurial-scm.org//api/diffusion.repository.search", | |||
|
68 | "method": "POST", | |||
|
69 | "body": "output=json&__conduit__=1¶ms=%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" | |||
|
70 | } | |||
|
71 | }, | |||
|
72 | { | |||
|
73 | "response": { | |||
|
74 | "headers": { | |||
|
75 | "date": [ | |||
|
76 | "Tue, 18 Feb 2020 18:43:11 GMT" | |||
|
77 | ], | |||
|
78 | "strict-transport-security": [ | |||
|
79 | "max-age=0; includeSubdomains; preload" | |||
|
80 | ], | |||
|
81 | "x-frame-options": [ | |||
|
82 | "Deny" | |||
|
83 | ], | |||
|
84 | "content-type": [ | |||
|
85 | "application/json" | |||
|
86 | ], | |||
|
87 | "transfer-encoding": [ | |||
|
88 | "chunked" | |||
|
89 | ], | |||
|
90 | "x-xss-protection": [ | |||
|
91 | "1; mode=block" | |||
|
92 | ], | |||
|
93 | "expires": [ | |||
|
94 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
95 | ], | |||
|
96 | "x-content-type-options": [ | |||
|
97 | "nosniff" | |||
|
98 | ], | |||
|
99 | "referrer-policy": [ | |||
|
100 | "no-referrer" | |||
|
101 | ], | |||
|
102 | "server": [ | |||
|
103 | "Apache/2.4.10 (Debian)" | |||
|
104 | ], | |||
|
105 | "cache-control": [ | |||
|
106 | "no-store" | |||
|
107 | ] | |||
|
108 | }, | |||
|
109 | "status": { | |||
|
110 | "code": 200, | |||
|
111 | "message": "OK" | |||
|
112 | }, | |||
|
113 | "body": { | |||
|
114 | "string": "{\"result\":{\"upload\":true,\"filePHID\":null},\"error_code\":null,\"error_info\":null}" | |||
|
115 | } | |||
|
116 | }, | |||
|
117 | "request": { | |||
|
118 | "headers": { | |||
|
119 | "content-length": [ | |||
|
120 | "270" | |||
|
121 | ], | |||
|
122 | "host": [ | |||
|
123 | "phab.mercurial-scm.org" | |||
|
124 | ], | |||
|
125 | "content-type": [ | |||
|
126 | "application/x-www-form-urlencoded" | |||
|
127 | ], | |||
|
128 | "user-agent": [ | |||
|
129 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
130 | ], | |||
|
131 | "accept": [ | |||
|
132 | "application/mercurial-0.1" | |||
|
133 | ] | |||
|
134 | }, | |||
|
135 | "uri": "https://phab.mercurial-scm.org//api/file.allocate", | |||
|
136 | "method": "POST", | |||
|
137 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22contentHash%22%3A+%22597fcb31282d34654c200d3418fca5705c648ebf326ec73d8ddef11841f876d8%22%2C+%22contentLength%22%3A+2%2C+%22name%22%3A+%22bin2%22%7D" | |||
|
138 | } | |||
|
139 | }, | |||
|
140 | { | |||
|
141 | "response": { | |||
|
142 | "headers": { | |||
|
143 | "date": [ | |||
|
144 | "Tue, 18 Feb 2020 18:43:12 GMT" | |||
|
145 | ], | |||
|
146 | "strict-transport-security": [ | |||
|
147 | "max-age=0; includeSubdomains; preload" | |||
|
148 | ], | |||
|
149 | "x-frame-options": [ | |||
|
150 | "Deny" | |||
|
151 | ], | |||
|
152 | "content-type": [ | |||
|
153 | "application/json" | |||
|
154 | ], | |||
|
155 | "transfer-encoding": [ | |||
|
156 | "chunked" | |||
|
157 | ], | |||
|
158 | "x-xss-protection": [ | |||
|
159 | "1; mode=block" | |||
|
160 | ], | |||
|
161 | "expires": [ | |||
|
162 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
163 | ], | |||
|
164 | "x-content-type-options": [ | |||
|
165 | "nosniff" | |||
|
166 | ], | |||
|
167 | "referrer-policy": [ | |||
|
168 | "no-referrer" | |||
|
169 | ], | |||
|
170 | "server": [ | |||
|
171 | "Apache/2.4.10 (Debian)" | |||
|
172 | ], | |||
|
173 | "cache-control": [ | |||
|
174 | "no-store" | |||
|
175 | ] | |||
|
176 | }, | |||
|
177 | "status": { | |||
|
178 | "code": 200, | |||
|
179 | "message": "OK" | |||
|
180 | }, | |||
|
181 | "body": { | |||
|
182 | "string": "{\"result\":\"PHID-FILE-6c77dv6moq5rt5hkiauh\",\"error_code\":null,\"error_info\":null}" | |||
|
183 | } | |||
|
184 | }, | |||
|
185 | "request": { | |||
|
186 | "headers": { | |||
|
187 | "content-length": [ | |||
|
188 | "184" | |||
|
189 | ], | |||
|
190 | "host": [ | |||
|
191 | "phab.mercurial-scm.org" | |||
|
192 | ], | |||
|
193 | "content-type": [ | |||
|
194 | "application/x-www-form-urlencoded" | |||
|
195 | ], | |||
|
196 | "user-agent": [ | |||
|
197 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
198 | ], | |||
|
199 | "accept": [ | |||
|
200 | "application/mercurial-0.1" | |||
|
201 | ] | |||
|
202 | }, | |||
|
203 | "uri": "https://phab.mercurial-scm.org//api/file.upload", | |||
|
204 | "method": "POST", | |||
|
205 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data_base64%22%3A+%22AGM%3D%22%2C+%22name%22%3A+%22bin2%22%7D" | |||
|
206 | } | |||
|
207 | }, | |||
|
208 | { | |||
|
209 | "response": { | |||
|
210 | "headers": { | |||
|
211 | "date": [ | |||
|
212 | "Tue, 18 Feb 2020 18:43:12 GMT" | |||
|
213 | ], | |||
|
214 | "strict-transport-security": [ | |||
|
215 | "max-age=0; includeSubdomains; preload" | |||
|
216 | ], | |||
|
217 | "x-frame-options": [ | |||
|
218 | "Deny" | |||
|
219 | ], | |||
|
220 | "content-type": [ | |||
|
221 | "application/json" | |||
|
222 | ], | |||
|
223 | "transfer-encoding": [ | |||
|
224 | "chunked" | |||
|
225 | ], | |||
|
226 | "x-xss-protection": [ | |||
|
227 | "1; mode=block" | |||
|
228 | ], | |||
|
229 | "connection": [ | |||
|
230 | "close" | |||
|
231 | ], | |||
|
232 | "expires": [ | |||
|
233 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
234 | ], | |||
|
235 | "x-content-type-options": [ | |||
|
236 | "nosniff" | |||
|
237 | ], | |||
|
238 | "referrer-policy": [ | |||
|
239 | "no-referrer" | |||
|
240 | ], | |||
|
241 | "server": [ | |||
|
242 | "Apache/2.4.10 (Debian)" | |||
|
243 | ], | |||
|
244 | "cache-control": [ | |||
|
245 | "no-store" | |||
|
246 | ] | |||
|
247 | }, | |||
|
248 | "status": { | |||
|
249 | "code": 200, | |||
|
250 | "message": "OK" | |||
|
251 | }, | |||
|
252 | "body": { | |||
|
253 | "string": "{\"result\":{\"diffid\":20252,\"phid\":\"PHID-DIFF-haue3qqytoovnmb7orw6\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/differential\\/diff\\/20252\\/\"},\"error_code\":null,\"error_info\":null}" | |||
|
254 | } | |||
|
255 | }, | |||
|
256 | "request": { | |||
|
257 | "headers": { | |||
|
258 | "content-length": [ | |||
|
259 | "1083" | |||
|
260 | ], | |||
|
261 | "host": [ | |||
|
262 | "phab.mercurial-scm.org" | |||
|
263 | ], | |||
|
264 | "content-type": [ | |||
|
265 | "application/x-www-form-urlencoded" | |||
|
266 | ], | |||
|
267 | "user-agent": [ | |||
|
268 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
269 | ], | |||
|
270 | "accept": [ | |||
|
271 | "application/mercurial-0.1" | |||
|
272 | ] | |||
|
273 | }, | |||
|
274 | "uri": "https://phab.mercurial-scm.org//api/differential.creatediff", | |||
|
275 | "method": "POST", | |||
|
276 | "body": "output=json&__conduit__=1¶ms=%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%22bin2%22%3A+%7B%22addLines%22%3A+0%2C+%22awayPaths%22%3A+%5B%5D%2C+%22commitHash%22%3A+null%2C+%22currentPath%22%3A+%22bin2%22%2C+%22delLines%22%3A+0%2C+%22fileType%22%3A+3%2C+%22hunks%22%3A+%5B%5D%2C+%22metadata%22%3A+%7B%22new%3Abinary-phid%22%3A+%22PHID-FILE-6c77dv6moq5rt5hkiauh%22%2C+%22new%3Afile%3Asize%22%3A+2%7D%2C+%22newProperties%22%3A+%7B%22unix%3Afilemode%22%3A+%22100644%22%7D%2C+%22oldPath%22%3A+null%2C+%22oldProperties%22%3A+%7B%7D%2C+%22type%22%3A+1%7D%7D%2C+%22creationMethod%22%3A+%22phabsend%22%2C+%22lintStatus%22%3A+%22none%22%2C+%22repositoryPHID%22%3A+%22PHID-REPO-bvunnehri4u2isyr7bc3%22%2C+%22sourceControlBaseRevision%22%3A+%2275dbbc901145d7beb190197aa232f74540e5a9f3%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" | |||
|
277 | } | |||
|
278 | }, | |||
|
279 | { | |||
|
280 | "response": { | |||
|
281 | "headers": { | |||
|
282 | "date": [ | |||
|
283 | "Tue, 18 Feb 2020 18:43:13 GMT" | |||
|
284 | ], | |||
|
285 | "strict-transport-security": [ | |||
|
286 | "max-age=0; includeSubdomains; preload" | |||
|
287 | ], | |||
|
288 | "x-frame-options": [ | |||
|
289 | "Deny" | |||
|
290 | ], | |||
|
291 | "content-type": [ | |||
|
292 | "application/json" | |||
|
293 | ], | |||
|
294 | "transfer-encoding": [ | |||
|
295 | "chunked" | |||
|
296 | ], | |||
|
297 | "x-xss-protection": [ | |||
|
298 | "1; mode=block" | |||
|
299 | ], | |||
|
300 | "expires": [ | |||
|
301 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
302 | ], | |||
|
303 | "x-content-type-options": [ | |||
|
304 | "nosniff" | |||
|
305 | ], | |||
|
306 | "referrer-policy": [ | |||
|
307 | "no-referrer" | |||
|
308 | ], | |||
|
309 | "server": [ | |||
|
310 | "Apache/2.4.10 (Debian)" | |||
|
311 | ], | |||
|
312 | "cache-control": [ | |||
|
313 | "no-store" | |||
|
314 | ] | |||
|
315 | }, | |||
|
316 | "status": { | |||
|
317 | "code": 200, | |||
|
318 | "message": "OK" | |||
|
319 | }, | |||
|
320 | "body": { | |||
|
321 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
322 | } | |||
|
323 | }, | |||
|
324 | "request": { | |||
|
325 | "headers": { | |||
|
326 | "content-length": [ | |||
|
327 | "482" | |||
|
328 | ], | |||
|
329 | "host": [ | |||
|
330 | "phab.mercurial-scm.org" | |||
|
331 | ], | |||
|
332 | "content-type": [ | |||
|
333 | "application/x-www-form-urlencoded" | |||
|
334 | ], | |||
|
335 | "user-agent": [ | |||
|
336 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
337 | ], | |||
|
338 | "accept": [ | |||
|
339 | "application/mercurial-0.1" | |||
|
340 | ] | |||
|
341 | }, | |||
|
342 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
343 | "method": "POST", | |||
|
344 | "body": "output=json&__conduit__=1¶ms=%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%22f42f9195e00ce1b8e9513ea704652a6787b57ce6%5C%22%2C+%5C%22parent%5C%22%3A+%5C%2275dbbc901145d7beb190197aa232f74540e5a9f3%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+20252%2C+%22name%22%3A+%22hg%3Ameta%22%7D" | |||
|
345 | } | |||
|
346 | }, | |||
|
347 | { | |||
|
348 | "response": { | |||
|
349 | "headers": { | |||
|
350 | "date": [ | |||
|
351 | "Tue, 18 Feb 2020 18:43:13 GMT" | |||
|
352 | ], | |||
|
353 | "strict-transport-security": [ | |||
|
354 | "max-age=0; includeSubdomains; preload" | |||
|
355 | ], | |||
|
356 | "x-frame-options": [ | |||
|
357 | "Deny" | |||
|
358 | ], | |||
|
359 | "content-type": [ | |||
|
360 | "application/json" | |||
|
361 | ], | |||
|
362 | "transfer-encoding": [ | |||
|
363 | "chunked" | |||
|
364 | ], | |||
|
365 | "x-xss-protection": [ | |||
|
366 | "1; mode=block" | |||
|
367 | ], | |||
|
368 | "expires": [ | |||
|
369 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
370 | ], | |||
|
371 | "x-content-type-options": [ | |||
|
372 | "nosniff" | |||
|
373 | ], | |||
|
374 | "referrer-policy": [ | |||
|
375 | "no-referrer" | |||
|
376 | ], | |||
|
377 | "server": [ | |||
|
378 | "Apache/2.4.10 (Debian)" | |||
|
379 | ], | |||
|
380 | "cache-control": [ | |||
|
381 | "no-store" | |||
|
382 | ] | |||
|
383 | }, | |||
|
384 | "status": { | |||
|
385 | "code": 200, | |||
|
386 | "message": "OK" | |||
|
387 | }, | |||
|
388 | "body": { | |||
|
389 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
390 | } | |||
|
391 | }, | |||
|
392 | "request": { | |||
|
393 | "headers": { | |||
|
394 | "content-length": [ | |||
|
395 | "594" | |||
|
396 | ], | |||
|
397 | "host": [ | |||
|
398 | "phab.mercurial-scm.org" | |||
|
399 | ], | |||
|
400 | "content-type": [ | |||
|
401 | "application/x-www-form-urlencoded" | |||
|
402 | ], | |||
|
403 | "user-agent": [ | |||
|
404 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
405 | ], | |||
|
406 | "accept": [ | |||
|
407 | "application/mercurial-0.1" | |||
|
408 | ] | |||
|
409 | }, | |||
|
410 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
411 | "method": "POST", | |||
|
412 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%22f42f9195e00ce1b8e9513ea704652a6787b57ce6%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%22f42f9195e00ce1b8e9513ea704652a6787b57ce6%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%2275dbbc901145d7beb190197aa232f74540e5a9f3%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+20252%2C+%22name%22%3A+%22local%3Acommits%22%7D" | |||
|
413 | } | |||
|
414 | }, | |||
|
415 | { | |||
|
416 | "response": { | |||
|
417 | "headers": { | |||
|
418 | "date": [ | |||
|
419 | "Tue, 18 Feb 2020 18:43:14 GMT" | |||
|
420 | ], | |||
|
421 | "strict-transport-security": [ | |||
|
422 | "max-age=0; includeSubdomains; preload" | |||
|
423 | ], | |||
|
424 | "x-frame-options": [ | |||
|
425 | "Deny" | |||
|
426 | ], | |||
|
427 | "content-type": [ | |||
|
428 | "application/json" | |||
|
429 | ], | |||
|
430 | "transfer-encoding": [ | |||
|
431 | "chunked" | |||
|
432 | ], | |||
|
433 | "x-xss-protection": [ | |||
|
434 | "1; mode=block" | |||
|
435 | ], | |||
|
436 | "expires": [ | |||
|
437 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
438 | ], | |||
|
439 | "x-content-type-options": [ | |||
|
440 | "nosniff" | |||
|
441 | ], | |||
|
442 | "referrer-policy": [ | |||
|
443 | "no-referrer" | |||
|
444 | ], | |||
|
445 | "server": [ | |||
|
446 | "Apache/2.4.10 (Debian)" | |||
|
447 | ], | |||
|
448 | "cache-control": [ | |||
|
449 | "no-store" | |||
|
450 | ] | |||
|
451 | }, | |||
|
452 | "status": { | |||
|
453 | "code": 200, | |||
|
454 | "message": "OK" | |||
|
455 | }, | |||
|
456 | "body": { | |||
|
457 | "string": "{\"result\":{\"errors\":[],\"fields\":{\"title\":\"add another binary\"},\"revisionIDFieldInfo\":{\"value\":null,\"validDomain\":\"https:\\/\\/phab.mercurial-scm.org\"},\"transactions\":[{\"type\":\"title\",\"value\":\"add another binary\"}]},\"error_code\":null,\"error_info\":null}" | |||
|
458 | } | |||
|
459 | }, | |||
|
460 | "request": { | |||
|
461 | "headers": { | |||
|
462 | "content-length": [ | |||
|
463 | "163" | |||
|
464 | ], | |||
|
465 | "host": [ | |||
|
466 | "phab.mercurial-scm.org" | |||
|
467 | ], | |||
|
468 | "content-type": [ | |||
|
469 | "application/x-www-form-urlencoded" | |||
|
470 | ], | |||
|
471 | "user-agent": [ | |||
|
472 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
473 | ], | |||
|
474 | "accept": [ | |||
|
475 | "application/mercurial-0.1" | |||
|
476 | ] | |||
|
477 | }, | |||
|
478 | "uri": "https://phab.mercurial-scm.org//api/differential.parsecommitmessage", | |||
|
479 | "method": "POST", | |||
|
480 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22corpus%22%3A+%22add+another+binary%22%7D" | |||
|
481 | } | |||
|
482 | }, | |||
|
483 | { | |||
|
484 | "response": { | |||
|
485 | "headers": { | |||
|
486 | "date": [ | |||
|
487 | "Tue, 18 Feb 2020 18:43:14 GMT" | |||
|
488 | ], | |||
|
489 | "strict-transport-security": [ | |||
|
490 | "max-age=0; includeSubdomains; preload" | |||
|
491 | ], | |||
|
492 | "x-frame-options": [ | |||
|
493 | "Deny" | |||
|
494 | ], | |||
|
495 | "content-type": [ | |||
|
496 | "application/json" | |||
|
497 | ], | |||
|
498 | "transfer-encoding": [ | |||
|
499 | "chunked" | |||
|
500 | ], | |||
|
501 | "x-xss-protection": [ | |||
|
502 | "1; mode=block" | |||
|
503 | ], | |||
|
504 | "expires": [ | |||
|
505 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
506 | ], | |||
|
507 | "x-content-type-options": [ | |||
|
508 | "nosniff" | |||
|
509 | ], | |||
|
510 | "referrer-policy": [ | |||
|
511 | "no-referrer" | |||
|
512 | ], | |||
|
513 | "server": [ | |||
|
514 | "Apache/2.4.10 (Debian)" | |||
|
515 | ], | |||
|
516 | "cache-control": [ | |||
|
517 | "no-store" | |||
|
518 | ] | |||
|
519 | }, | |||
|
520 | "status": { | |||
|
521 | "code": 200, | |||
|
522 | "message": "OK" | |||
|
523 | }, | |||
|
524 | "body": { | |||
|
525 | "string": "{\"result\":{\"object\":{\"id\":8128,\"phid\":\"PHID-DREV-ebpbxa27h5ibeudclsse\"},\"transactions\":[{\"phid\":\"PHID-XACT-DREV-m6rt6hdyxzkl6nv\"},{\"phid\":\"PHID-XACT-DREV-degiee33gjgtg23\"},{\"phid\":\"PHID-XACT-DREV-6oz5qcrlwerhhxw\"},{\"phid\":\"PHID-XACT-DREV-pm4rroqrqehsnau\"},{\"phid\":\"PHID-XACT-DREV-ncnb2jnrhtearld\"}]},\"error_code\":null,\"error_info\":null}" | |||
|
526 | } | |||
|
527 | }, | |||
|
528 | "request": { | |||
|
529 | "headers": { | |||
|
530 | "content-length": [ | |||
|
531 | "316" | |||
|
532 | ], | |||
|
533 | "host": [ | |||
|
534 | "phab.mercurial-scm.org" | |||
|
535 | ], | |||
|
536 | "content-type": [ | |||
|
537 | "application/x-www-form-urlencoded" | |||
|
538 | ], | |||
|
539 | "user-agent": [ | |||
|
540 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
541 | ], | |||
|
542 | "accept": [ | |||
|
543 | "application/mercurial-0.1" | |||
|
544 | ] | |||
|
545 | }, | |||
|
546 | "uri": "https://phab.mercurial-scm.org//api/differential.revision.edit", | |||
|
547 | "method": "POST", | |||
|
548 | "body": "output=json&__conduit__=1¶ms=%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-haue3qqytoovnmb7orw6%22%7D%2C+%7B%22type%22%3A+%22title%22%2C+%22value%22%3A+%22add+another+binary%22%7D%5D%7D" | |||
|
549 | } | |||
|
550 | }, | |||
|
551 | { | |||
|
552 | "response": { | |||
|
553 | "headers": { | |||
|
554 | "date": [ | |||
|
555 | "Tue, 18 Feb 2020 18:43:15 GMT" | |||
|
556 | ], | |||
|
557 | "strict-transport-security": [ | |||
|
558 | "max-age=0; includeSubdomains; preload" | |||
|
559 | ], | |||
|
560 | "x-frame-options": [ | |||
|
561 | "Deny" | |||
|
562 | ], | |||
|
563 | "content-type": [ | |||
|
564 | "application/json" | |||
|
565 | ], | |||
|
566 | "transfer-encoding": [ | |||
|
567 | "chunked" | |||
|
568 | ], | |||
|
569 | "x-xss-protection": [ | |||
|
570 | "1; mode=block" | |||
|
571 | ], | |||
|
572 | "expires": [ | |||
|
573 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
574 | ], | |||
|
575 | "x-content-type-options": [ | |||
|
576 | "nosniff" | |||
|
577 | ], | |||
|
578 | "referrer-policy": [ | |||
|
579 | "no-referrer" | |||
|
580 | ], | |||
|
581 | "server": [ | |||
|
582 | "Apache/2.4.10 (Debian)" | |||
|
583 | ], | |||
|
584 | "cache-control": [ | |||
|
585 | "no-store" | |||
|
586 | ] | |||
|
587 | }, | |||
|
588 | "status": { | |||
|
589 | "code": 200, | |||
|
590 | "message": "OK" | |||
|
591 | }, | |||
|
592 | "body": { | |||
|
593 | "string": "{\"result\":{\"upload\":false,\"filePHID\":\"PHID-FILE-wybozuu6uch7gezkw6pe\"},\"error_code\":null,\"error_info\":null}" | |||
|
594 | } | |||
|
595 | }, | |||
|
596 | "request": { | |||
|
597 | "headers": { | |||
|
598 | "content-length": [ | |||
|
599 | "276" | |||
|
600 | ], | |||
|
601 | "host": [ | |||
|
602 | "phab.mercurial-scm.org" | |||
|
603 | ], | |||
|
604 | "content-type": [ | |||
|
605 | "application/x-www-form-urlencoded" | |||
|
606 | ], | |||
|
607 | "user-agent": [ | |||
|
608 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
609 | ], | |||
|
610 | "accept": [ | |||
|
611 | "application/mercurial-0.1" | |||
|
612 | ] | |||
|
613 | }, | |||
|
614 | "uri": "https://phab.mercurial-scm.org//api/file.allocate", | |||
|
615 | "method": "POST", | |||
|
616 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22contentHash%22%3A+%22597fcb31282d34654c200d3418fca5705c648ebf326ec73d8ddef11841f876d8%22%2C+%22contentLength%22%3A+2%2C+%22name%22%3A+%22bin2_moved%22%7D" | |||
|
617 | } | |||
|
618 | }, | |||
|
619 | { | |||
|
620 | "response": { | |||
|
621 | "headers": { | |||
|
622 | "date": [ | |||
|
623 | "Tue, 18 Feb 2020 18:43:15 GMT" | |||
|
624 | ], | |||
|
625 | "strict-transport-security": [ | |||
|
626 | "max-age=0; includeSubdomains; preload" | |||
|
627 | ], | |||
|
628 | "x-frame-options": [ | |||
|
629 | "Deny" | |||
|
630 | ], | |||
|
631 | "content-type": [ | |||
|
632 | "application/json" | |||
|
633 | ], | |||
|
634 | "transfer-encoding": [ | |||
|
635 | "chunked" | |||
|
636 | ], | |||
|
637 | "x-xss-protection": [ | |||
|
638 | "1; mode=block" | |||
|
639 | ], | |||
|
640 | "expires": [ | |||
|
641 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
642 | ], | |||
|
643 | "x-content-type-options": [ | |||
|
644 | "nosniff" | |||
|
645 | ], | |||
|
646 | "referrer-policy": [ | |||
|
647 | "no-referrer" | |||
|
648 | ], | |||
|
649 | "server": [ | |||
|
650 | "Apache/2.4.10 (Debian)" | |||
|
651 | ], | |||
|
652 | "cache-control": [ | |||
|
653 | "no-store" | |||
|
654 | ] | |||
|
655 | }, | |||
|
656 | "status": { | |||
|
657 | "code": 200, | |||
|
658 | "message": "OK" | |||
|
659 | }, | |||
|
660 | "body": { | |||
|
661 | "string": "{\"result\":{\"diffid\":20253,\"phid\":\"PHID-DIFF-svria2kxhgr63qwpdzzb\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/differential\\/diff\\/20253\\/\"},\"error_code\":null,\"error_info\":null}" | |||
|
662 | } | |||
|
663 | }, | |||
|
664 | "request": { | |||
|
665 | "headers": { | |||
|
666 | "content-length": [ | |||
|
667 | "1529" | |||
|
668 | ], | |||
|
669 | "host": [ | |||
|
670 | "phab.mercurial-scm.org" | |||
|
671 | ], | |||
|
672 | "content-type": [ | |||
|
673 | "application/x-www-form-urlencoded" | |||
|
674 | ], | |||
|
675 | "user-agent": [ | |||
|
676 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
677 | ], | |||
|
678 | "accept": [ | |||
|
679 | "application/mercurial-0.1" | |||
|
680 | ] | |||
|
681 | }, | |||
|
682 | "uri": "https://phab.mercurial-scm.org//api/differential.creatediff", | |||
|
683 | "method": "POST", | |||
|
684 | "body": "output=json&__conduit__=1¶ms=%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%22bin2%22%3A+%7B%22addLines%22%3A+0%2C+%22awayPaths%22%3A+%5B%22bin2_moved%22%5D%2C+%22commitHash%22%3A+null%2C+%22currentPath%22%3A+%22bin2%22%2C+%22delLines%22%3A+0%2C+%22fileType%22%3A+1%2C+%22hunks%22%3A+%5B%5D%2C+%22metadata%22%3A+%7B%7D%2C+%22newProperties%22%3A+%7B%7D%2C+%22oldPath%22%3A+%22bin2%22%2C+%22oldProperties%22%3A+%7B%7D%2C+%22type%22%3A+4%7D%2C+%22bin2_moved%22%3A+%7B%22addLines%22%3A+0%2C+%22awayPaths%22%3A+%5B%5D%2C+%22commitHash%22%3A+null%2C+%22currentPath%22%3A+%22bin2_moved%22%2C+%22delLines%22%3A+0%2C+%22fileType%22%3A+1%2C+%22hunks%22%3A+%5B%5D%2C+%22metadata%22%3A+%7B%22new%3Abinary-phid%22%3A+%22PHID-FILE-wybozuu6uch7gezkw6pe%22%2C+%22new%3Afile%3Asize%22%3A+2%2C+%22old%3Abinary-phid%22%3A+%22PHID-FILE-wybozuu6uch7gezkw6pe%22%2C+%22old%3Afile%3Asize%22%3A+2%7D%2C+%22newProperties%22%3A+%7B%7D%2C+%22oldPath%22%3A+%22bin2%22%2C+%22oldProperties%22%3A+%7B%7D%2C+%22type%22%3A+6%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+%22f42f9195e00ce1b8e9513ea704652a6787b57ce6%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" | |||
|
685 | } | |||
|
686 | }, | |||
|
687 | { | |||
|
688 | "response": { | |||
|
689 | "headers": { | |||
|
690 | "date": [ | |||
|
691 | "Tue, 18 Feb 2020 18:43:16 GMT" | |||
|
692 | ], | |||
|
693 | "strict-transport-security": [ | |||
|
694 | "max-age=0; includeSubdomains; preload" | |||
|
695 | ], | |||
|
696 | "x-frame-options": [ | |||
|
697 | "Deny" | |||
|
698 | ], | |||
|
699 | "content-type": [ | |||
|
700 | "application/json" | |||
|
701 | ], | |||
|
702 | "transfer-encoding": [ | |||
|
703 | "chunked" | |||
|
704 | ], | |||
|
705 | "x-xss-protection": [ | |||
|
706 | "1; mode=block" | |||
|
707 | ], | |||
|
708 | "expires": [ | |||
|
709 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
710 | ], | |||
|
711 | "x-content-type-options": [ | |||
|
712 | "nosniff" | |||
|
713 | ], | |||
|
714 | "referrer-policy": [ | |||
|
715 | "no-referrer" | |||
|
716 | ], | |||
|
717 | "server": [ | |||
|
718 | "Apache/2.4.10 (Debian)" | |||
|
719 | ], | |||
|
720 | "cache-control": [ | |||
|
721 | "no-store" | |||
|
722 | ] | |||
|
723 | }, | |||
|
724 | "status": { | |||
|
725 | "code": 200, | |||
|
726 | "message": "OK" | |||
|
727 | }, | |||
|
728 | "body": { | |||
|
729 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
730 | } | |||
|
731 | }, | |||
|
732 | "request": { | |||
|
733 | "headers": { | |||
|
734 | "content-length": [ | |||
|
735 | "482" | |||
|
736 | ], | |||
|
737 | "host": [ | |||
|
738 | "phab.mercurial-scm.org" | |||
|
739 | ], | |||
|
740 | "content-type": [ | |||
|
741 | "application/x-www-form-urlencoded" | |||
|
742 | ], | |||
|
743 | "user-agent": [ | |||
|
744 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
745 | ], | |||
|
746 | "accept": [ | |||
|
747 | "application/mercurial-0.1" | |||
|
748 | ] | |||
|
749 | }, | |||
|
750 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
751 | "method": "POST", | |||
|
752 | "body": "output=json&__conduit__=1¶ms=%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%22834ab31d80aede3f92435c26366095c3518dc5af%5C%22%2C+%5C%22parent%5C%22%3A+%5C%22f42f9195e00ce1b8e9513ea704652a6787b57ce6%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+20253%2C+%22name%22%3A+%22hg%3Ameta%22%7D" | |||
|
753 | } | |||
|
754 | }, | |||
|
755 | { | |||
|
756 | "response": { | |||
|
757 | "headers": { | |||
|
758 | "date": [ | |||
|
759 | "Tue, 18 Feb 2020 18:43:16 GMT" | |||
|
760 | ], | |||
|
761 | "strict-transport-security": [ | |||
|
762 | "max-age=0; includeSubdomains; preload" | |||
|
763 | ], | |||
|
764 | "x-frame-options": [ | |||
|
765 | "Deny" | |||
|
766 | ], | |||
|
767 | "content-type": [ | |||
|
768 | "application/json" | |||
|
769 | ], | |||
|
770 | "transfer-encoding": [ | |||
|
771 | "chunked" | |||
|
772 | ], | |||
|
773 | "x-xss-protection": [ | |||
|
774 | "1; mode=block" | |||
|
775 | ], | |||
|
776 | "expires": [ | |||
|
777 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
778 | ], | |||
|
779 | "x-content-type-options": [ | |||
|
780 | "nosniff" | |||
|
781 | ], | |||
|
782 | "referrer-policy": [ | |||
|
783 | "no-referrer" | |||
|
784 | ], | |||
|
785 | "server": [ | |||
|
786 | "Apache/2.4.10 (Debian)" | |||
|
787 | ], | |||
|
788 | "cache-control": [ | |||
|
789 | "no-store" | |||
|
790 | ] | |||
|
791 | }, | |||
|
792 | "status": { | |||
|
793 | "code": 200, | |||
|
794 | "message": "OK" | |||
|
795 | }, | |||
|
796 | "body": { | |||
|
797 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
798 | } | |||
|
799 | }, | |||
|
800 | "request": { | |||
|
801 | "headers": { | |||
|
802 | "content-length": [ | |||
|
803 | "594" | |||
|
804 | ], | |||
|
805 | "host": [ | |||
|
806 | "phab.mercurial-scm.org" | |||
|
807 | ], | |||
|
808 | "content-type": [ | |||
|
809 | "application/x-www-form-urlencoded" | |||
|
810 | ], | |||
|
811 | "user-agent": [ | |||
|
812 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
813 | ], | |||
|
814 | "accept": [ | |||
|
815 | "application/mercurial-0.1" | |||
|
816 | ] | |||
|
817 | }, | |||
|
818 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
819 | "method": "POST", | |||
|
820 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%22834ab31d80aede3f92435c26366095c3518dc5af%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%22834ab31d80aede3f92435c26366095c3518dc5af%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%22f42f9195e00ce1b8e9513ea704652a6787b57ce6%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+20253%2C+%22name%22%3A+%22local%3Acommits%22%7D" | |||
|
821 | } | |||
|
822 | }, | |||
|
823 | { | |||
|
824 | "response": { | |||
|
825 | "headers": { | |||
|
826 | "date": [ | |||
|
827 | "Tue, 18 Feb 2020 18:43:17 GMT" | |||
|
828 | ], | |||
|
829 | "strict-transport-security": [ | |||
|
830 | "max-age=0; includeSubdomains; preload" | |||
|
831 | ], | |||
|
832 | "x-frame-options": [ | |||
|
833 | "Deny" | |||
|
834 | ], | |||
|
835 | "content-type": [ | |||
|
836 | "application/json" | |||
|
837 | ], | |||
|
838 | "transfer-encoding": [ | |||
|
839 | "chunked" | |||
|
840 | ], | |||
|
841 | "x-xss-protection": [ | |||
|
842 | "1; mode=block" | |||
|
843 | ], | |||
|
844 | "expires": [ | |||
|
845 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
846 | ], | |||
|
847 | "x-content-type-options": [ | |||
|
848 | "nosniff" | |||
|
849 | ], | |||
|
850 | "referrer-policy": [ | |||
|
851 | "no-referrer" | |||
|
852 | ], | |||
|
853 | "server": [ | |||
|
854 | "Apache/2.4.10 (Debian)" | |||
|
855 | ], | |||
|
856 | "cache-control": [ | |||
|
857 | "no-store" | |||
|
858 | ] | |||
|
859 | }, | |||
|
860 | "status": { | |||
|
861 | "code": 200, | |||
|
862 | "message": "OK" | |||
|
863 | }, | |||
|
864 | "body": { | |||
|
865 | "string": "{\"result\":{\"errors\":[],\"fields\":{\"title\":\"moved binary\"},\"revisionIDFieldInfo\":{\"value\":null,\"validDomain\":\"https:\\/\\/phab.mercurial-scm.org\"},\"transactions\":[{\"type\":\"title\",\"value\":\"moved binary\"}]},\"error_code\":null,\"error_info\":null}" | |||
|
866 | } | |||
|
867 | }, | |||
|
868 | "request": { | |||
|
869 | "headers": { | |||
|
870 | "content-length": [ | |||
|
871 | "157" | |||
|
872 | ], | |||
|
873 | "host": [ | |||
|
874 | "phab.mercurial-scm.org" | |||
|
875 | ], | |||
|
876 | "content-type": [ | |||
|
877 | "application/x-www-form-urlencoded" | |||
|
878 | ], | |||
|
879 | "user-agent": [ | |||
|
880 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
881 | ], | |||
|
882 | "accept": [ | |||
|
883 | "application/mercurial-0.1" | |||
|
884 | ] | |||
|
885 | }, | |||
|
886 | "uri": "https://phab.mercurial-scm.org//api/differential.parsecommitmessage", | |||
|
887 | "method": "POST", | |||
|
888 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22corpus%22%3A+%22moved+binary%22%7D" | |||
|
889 | } | |||
|
890 | }, | |||
|
891 | { | |||
|
892 | "response": { | |||
|
893 | "headers": { | |||
|
894 | "date": [ | |||
|
895 | "Tue, 18 Feb 2020 18:43:17 GMT" | |||
|
896 | ], | |||
|
897 | "strict-transport-security": [ | |||
|
898 | "max-age=0; includeSubdomains; preload" | |||
|
899 | ], | |||
|
900 | "x-frame-options": [ | |||
|
901 | "Deny" | |||
|
902 | ], | |||
|
903 | "content-type": [ | |||
|
904 | "application/json" | |||
|
905 | ], | |||
|
906 | "transfer-encoding": [ | |||
|
907 | "chunked" | |||
|
908 | ], | |||
|
909 | "x-xss-protection": [ | |||
|
910 | "1; mode=block" | |||
|
911 | ], | |||
|
912 | "expires": [ | |||
|
913 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
914 | ], | |||
|
915 | "x-content-type-options": [ | |||
|
916 | "nosniff" | |||
|
917 | ], | |||
|
918 | "referrer-policy": [ | |||
|
919 | "no-referrer" | |||
|
920 | ], | |||
|
921 | "server": [ | |||
|
922 | "Apache/2.4.10 (Debian)" | |||
|
923 | ], | |||
|
924 | "cache-control": [ | |||
|
925 | "no-store" | |||
|
926 | ] | |||
|
927 | }, | |||
|
928 | "status": { | |||
|
929 | "code": 200, | |||
|
930 | "message": "OK" | |||
|
931 | }, | |||
|
932 | "body": { | |||
|
933 | "string": "{\"result\":{\"object\":{\"id\":8129,\"phid\":\"PHID-DREV-s74qwcmdszxugly5fjru\"},\"transactions\":[{\"phid\":\"PHID-XACT-DREV-a36lldjdtmtyks2\"},{\"phid\":\"PHID-XACT-DREV-xq32hvqubr4vi6u\"},{\"phid\":\"PHID-XACT-DREV-t7xt2kxu2u3ld2s\"},{\"phid\":\"PHID-XACT-DREV-kzygpxqiccpph4u\"},{\"phid\":\"PHID-XACT-DREV-vhr6fdqah3gnmnn\"},{\"phid\":\"PHID-XACT-DREV-ghlyvkgloeskuqg\"}]},\"error_code\":null,\"error_info\":null}" | |||
|
934 | } | |||
|
935 | }, | |||
|
936 | "request": { | |||
|
937 | "headers": { | |||
|
938 | "content-length": [ | |||
|
939 | "412" | |||
|
940 | ], | |||
|
941 | "host": [ | |||
|
942 | "phab.mercurial-scm.org" | |||
|
943 | ], | |||
|
944 | "content-type": [ | |||
|
945 | "application/x-www-form-urlencoded" | |||
|
946 | ], | |||
|
947 | "user-agent": [ | |||
|
948 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
949 | ], | |||
|
950 | "accept": [ | |||
|
951 | "application/mercurial-0.1" | |||
|
952 | ] | |||
|
953 | }, | |||
|
954 | "uri": "https://phab.mercurial-scm.org//api/differential.revision.edit", | |||
|
955 | "method": "POST", | |||
|
956 | "body": "output=json&__conduit__=1¶ms=%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-svria2kxhgr63qwpdzzb%22%7D%2C+%7B%22type%22%3A+%22parents.set%22%2C+%22value%22%3A+%5B%22PHID-DREV-ebpbxa27h5ibeudclsse%22%5D%7D%2C+%7B%22type%22%3A+%22title%22%2C+%22value%22%3A+%22moved+binary%22%7D%5D%7D" | |||
|
957 | } | |||
|
958 | }, | |||
|
959 | { | |||
|
960 | "response": { | |||
|
961 | "headers": { | |||
|
962 | "date": [ | |||
|
963 | "Tue, 18 Feb 2020 18:43:18 GMT" | |||
|
964 | ], | |||
|
965 | "strict-transport-security": [ | |||
|
966 | "max-age=0; includeSubdomains; preload" | |||
|
967 | ], | |||
|
968 | "x-frame-options": [ | |||
|
969 | "Deny" | |||
|
970 | ], | |||
|
971 | "content-type": [ | |||
|
972 | "application/json" | |||
|
973 | ], | |||
|
974 | "transfer-encoding": [ | |||
|
975 | "chunked" | |||
|
976 | ], | |||
|
977 | "x-xss-protection": [ | |||
|
978 | "1; mode=block" | |||
|
979 | ], | |||
|
980 | "expires": [ | |||
|
981 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
982 | ], | |||
|
983 | "x-content-type-options": [ | |||
|
984 | "nosniff" | |||
|
985 | ], | |||
|
986 | "referrer-policy": [ | |||
|
987 | "no-referrer" | |||
|
988 | ], | |||
|
989 | "server": [ | |||
|
990 | "Apache/2.4.10 (Debian)" | |||
|
991 | ], | |||
|
992 | "cache-control": [ | |||
|
993 | "no-store" | |||
|
994 | ] | |||
|
995 | }, | |||
|
996 | "status": { | |||
|
997 | "code": 200, | |||
|
998 | "message": "OK" | |||
|
999 | }, | |||
|
1000 | "body": { | |||
|
1001 | "string": "{\"result\":{\"upload\":false,\"filePHID\":\"PHID-FILE-wodobo7epta7trv3vdz2\"},\"error_code\":null,\"error_info\":null}" | |||
|
1002 | } | |||
|
1003 | }, | |||
|
1004 | "request": { | |||
|
1005 | "headers": { | |||
|
1006 | "content-length": [ | |||
|
1007 | "277" | |||
|
1008 | ], | |||
|
1009 | "host": [ | |||
|
1010 | "phab.mercurial-scm.org" | |||
|
1011 | ], | |||
|
1012 | "content-type": [ | |||
|
1013 | "application/x-www-form-urlencoded" | |||
|
1014 | ], | |||
|
1015 | "user-agent": [ | |||
|
1016 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
1017 | ], | |||
|
1018 | "accept": [ | |||
|
1019 | "application/mercurial-0.1" | |||
|
1020 | ] | |||
|
1021 | }, | |||
|
1022 | "uri": "https://phab.mercurial-scm.org//api/file.allocate", | |||
|
1023 | "method": "POST", | |||
|
1024 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22contentHash%22%3A+%22597fcb31282d34654c200d3418fca5705c648ebf326ec73d8ddef11841f876d8%22%2C+%22contentLength%22%3A+2%2C+%22name%22%3A+%22bin2_copied%22%7D" | |||
|
1025 | } | |||
|
1026 | }, | |||
|
1027 | { | |||
|
1028 | "response": { | |||
|
1029 | "headers": { | |||
|
1030 | "date": [ | |||
|
1031 | "Tue, 18 Feb 2020 18:43:18 GMT" | |||
|
1032 | ], | |||
|
1033 | "strict-transport-security": [ | |||
|
1034 | "max-age=0; includeSubdomains; preload" | |||
|
1035 | ], | |||
|
1036 | "x-frame-options": [ | |||
|
1037 | "Deny" | |||
|
1038 | ], | |||
|
1039 | "content-type": [ | |||
|
1040 | "application/json" | |||
|
1041 | ], | |||
|
1042 | "transfer-encoding": [ | |||
|
1043 | "chunked" | |||
|
1044 | ], | |||
|
1045 | "x-xss-protection": [ | |||
|
1046 | "1; mode=block" | |||
|
1047 | ], | |||
|
1048 | "expires": [ | |||
|
1049 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
1050 | ], | |||
|
1051 | "x-content-type-options": [ | |||
|
1052 | "nosniff" | |||
|
1053 | ], | |||
|
1054 | "referrer-policy": [ | |||
|
1055 | "no-referrer" | |||
|
1056 | ], | |||
|
1057 | "server": [ | |||
|
1058 | "Apache/2.4.10 (Debian)" | |||
|
1059 | ], | |||
|
1060 | "cache-control": [ | |||
|
1061 | "no-store" | |||
|
1062 | ] | |||
|
1063 | }, | |||
|
1064 | "status": { | |||
|
1065 | "code": 200, | |||
|
1066 | "message": "OK" | |||
|
1067 | }, | |||
|
1068 | "body": { | |||
|
1069 | "string": "{\"result\":{\"diffid\":20254,\"phid\":\"PHID-DIFF-g7zeghdg2dzimfldfk5b\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/differential\\/diff\\/20254\\/\"},\"error_code\":null,\"error_info\":null}" | |||
|
1070 | } | |||
|
1071 | }, | |||
|
1072 | "request": { | |||
|
1073 | "headers": { | |||
|
1074 | "content-length": [ | |||
|
1075 | "1544" | |||
|
1076 | ], | |||
|
1077 | "host": [ | |||
|
1078 | "phab.mercurial-scm.org" | |||
|
1079 | ], | |||
|
1080 | "content-type": [ | |||
|
1081 | "application/x-www-form-urlencoded" | |||
|
1082 | ], | |||
|
1083 | "user-agent": [ | |||
|
1084 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
1085 | ], | |||
|
1086 | "accept": [ | |||
|
1087 | "application/mercurial-0.1" | |||
|
1088 | ] | |||
|
1089 | }, | |||
|
1090 | "uri": "https://phab.mercurial-scm.org//api/differential.creatediff", | |||
|
1091 | "method": "POST", | |||
|
1092 | "body": "output=json&__conduit__=1¶ms=%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%22bin2_copied%22%3A+%7B%22addLines%22%3A+0%2C+%22awayPaths%22%3A+%5B%5D%2C+%22commitHash%22%3A+null%2C+%22currentPath%22%3A+%22bin2_copied%22%2C+%22delLines%22%3A+0%2C+%22fileType%22%3A+1%2C+%22hunks%22%3A+%5B%5D%2C+%22metadata%22%3A+%7B%22new%3Abinary-phid%22%3A+%22PHID-FILE-wodobo7epta7trv3vdz2%22%2C+%22new%3Afile%3Asize%22%3A+2%2C+%22old%3Abinary-phid%22%3A+%22PHID-FILE-wodobo7epta7trv3vdz2%22%2C+%22old%3Afile%3Asize%22%3A+2%7D%2C+%22newProperties%22%3A+%7B%7D%2C+%22oldPath%22%3A+%22bin2_moved%22%2C+%22oldProperties%22%3A+%7B%7D%2C+%22type%22%3A+7%7D%2C+%22bin2_moved%22%3A+%7B%22addLines%22%3A+0%2C+%22awayPaths%22%3A+%5B%22bin2_copied%22%5D%2C+%22commitHash%22%3A+null%2C+%22currentPath%22%3A+%22bin2_moved%22%2C+%22delLines%22%3A+0%2C+%22fileType%22%3A+1%2C+%22hunks%22%3A+%5B%5D%2C+%22metadata%22%3A+%7B%7D%2C+%22newProperties%22%3A+%7B%7D%2C+%22oldPath%22%3A+null%2C+%22oldProperties%22%3A+%7B%7D%2C+%22type%22%3A+5%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+%22834ab31d80aede3f92435c26366095c3518dc5af%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" | |||
|
1093 | } | |||
|
1094 | }, | |||
|
1095 | { | |||
|
1096 | "response": { | |||
|
1097 | "headers": { | |||
|
1098 | "date": [ | |||
|
1099 | "Tue, 18 Feb 2020 18:43:19 GMT" | |||
|
1100 | ], | |||
|
1101 | "strict-transport-security": [ | |||
|
1102 | "max-age=0; includeSubdomains; preload" | |||
|
1103 | ], | |||
|
1104 | "x-frame-options": [ | |||
|
1105 | "Deny" | |||
|
1106 | ], | |||
|
1107 | "content-type": [ | |||
|
1108 | "application/json" | |||
|
1109 | ], | |||
|
1110 | "transfer-encoding": [ | |||
|
1111 | "chunked" | |||
|
1112 | ], | |||
|
1113 | "x-xss-protection": [ | |||
|
1114 | "1; mode=block" | |||
|
1115 | ], | |||
|
1116 | "expires": [ | |||
|
1117 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
1118 | ], | |||
|
1119 | "x-content-type-options": [ | |||
|
1120 | "nosniff" | |||
|
1121 | ], | |||
|
1122 | "referrer-policy": [ | |||
|
1123 | "no-referrer" | |||
|
1124 | ], | |||
|
1125 | "server": [ | |||
|
1126 | "Apache/2.4.10 (Debian)" | |||
|
1127 | ], | |||
|
1128 | "cache-control": [ | |||
|
1129 | "no-store" | |||
|
1130 | ] | |||
|
1131 | }, | |||
|
1132 | "status": { | |||
|
1133 | "code": 200, | |||
|
1134 | "message": "OK" | |||
|
1135 | }, | |||
|
1136 | "body": { | |||
|
1137 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
1138 | } | |||
|
1139 | }, | |||
|
1140 | "request": { | |||
|
1141 | "headers": { | |||
|
1142 | "content-length": [ | |||
|
1143 | "482" | |||
|
1144 | ], | |||
|
1145 | "host": [ | |||
|
1146 | "phab.mercurial-scm.org" | |||
|
1147 | ], | |||
|
1148 | "content-type": [ | |||
|
1149 | "application/x-www-form-urlencoded" | |||
|
1150 | ], | |||
|
1151 | "user-agent": [ | |||
|
1152 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
1153 | ], | |||
|
1154 | "accept": [ | |||
|
1155 | "application/mercurial-0.1" | |||
|
1156 | ] | |||
|
1157 | }, | |||
|
1158 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
1159 | "method": "POST", | |||
|
1160 | "body": "output=json&__conduit__=1¶ms=%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%22494b750e5194bb08c1de04d6b292e28273afa18c%5C%22%2C+%5C%22parent%5C%22%3A+%5C%22834ab31d80aede3f92435c26366095c3518dc5af%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+20254%2C+%22name%22%3A+%22hg%3Ameta%22%7D" | |||
|
1161 | } | |||
|
1162 | }, | |||
|
1163 | { | |||
|
1164 | "response": { | |||
|
1165 | "headers": { | |||
|
1166 | "date": [ | |||
|
1167 | "Tue, 18 Feb 2020 18:43:19 GMT" | |||
|
1168 | ], | |||
|
1169 | "strict-transport-security": [ | |||
|
1170 | "max-age=0; includeSubdomains; preload" | |||
|
1171 | ], | |||
|
1172 | "x-frame-options": [ | |||
|
1173 | "Deny" | |||
|
1174 | ], | |||
|
1175 | "content-type": [ | |||
|
1176 | "application/json" | |||
|
1177 | ], | |||
|
1178 | "transfer-encoding": [ | |||
|
1179 | "chunked" | |||
|
1180 | ], | |||
|
1181 | "x-xss-protection": [ | |||
|
1182 | "1; mode=block" | |||
|
1183 | ], | |||
|
1184 | "expires": [ | |||
|
1185 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
1186 | ], | |||
|
1187 | "x-content-type-options": [ | |||
|
1188 | "nosniff" | |||
|
1189 | ], | |||
|
1190 | "referrer-policy": [ | |||
|
1191 | "no-referrer" | |||
|
1192 | ], | |||
|
1193 | "server": [ | |||
|
1194 | "Apache/2.4.10 (Debian)" | |||
|
1195 | ], | |||
|
1196 | "cache-control": [ | |||
|
1197 | "no-store" | |||
|
1198 | ] | |||
|
1199 | }, | |||
|
1200 | "status": { | |||
|
1201 | "code": 200, | |||
|
1202 | "message": "OK" | |||
|
1203 | }, | |||
|
1204 | "body": { | |||
|
1205 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
1206 | } | |||
|
1207 | }, | |||
|
1208 | "request": { | |||
|
1209 | "headers": { | |||
|
1210 | "content-length": [ | |||
|
1211 | "594" | |||
|
1212 | ], | |||
|
1213 | "host": [ | |||
|
1214 | "phab.mercurial-scm.org" | |||
|
1215 | ], | |||
|
1216 | "content-type": [ | |||
|
1217 | "application/x-www-form-urlencoded" | |||
|
1218 | ], | |||
|
1219 | "user-agent": [ | |||
|
1220 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
1221 | ], | |||
|
1222 | "accept": [ | |||
|
1223 | "application/mercurial-0.1" | |||
|
1224 | ] | |||
|
1225 | }, | |||
|
1226 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
1227 | "method": "POST", | |||
|
1228 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%22494b750e5194bb08c1de04d6b292e28273afa18c%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%22494b750e5194bb08c1de04d6b292e28273afa18c%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%22834ab31d80aede3f92435c26366095c3518dc5af%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+20254%2C+%22name%22%3A+%22local%3Acommits%22%7D" | |||
|
1229 | } | |||
|
1230 | }, | |||
|
1231 | { | |||
|
1232 | "response": { | |||
|
1233 | "headers": { | |||
|
1234 | "date": [ | |||
|
1235 | "Tue, 18 Feb 2020 18:43:19 GMT" | |||
|
1236 | ], | |||
|
1237 | "strict-transport-security": [ | |||
|
1238 | "max-age=0; includeSubdomains; preload" | |||
|
1239 | ], | |||
|
1240 | "x-frame-options": [ | |||
|
1241 | "Deny" | |||
|
1242 | ], | |||
|
1243 | "content-type": [ | |||
|
1244 | "application/json" | |||
|
1245 | ], | |||
|
1246 | "transfer-encoding": [ | |||
|
1247 | "chunked" | |||
|
1248 | ], | |||
|
1249 | "x-xss-protection": [ | |||
|
1250 | "1; mode=block" | |||
|
1251 | ], | |||
|
1252 | "expires": [ | |||
|
1253 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
1254 | ], | |||
|
1255 | "x-content-type-options": [ | |||
|
1256 | "nosniff" | |||
|
1257 | ], | |||
|
1258 | "referrer-policy": [ | |||
|
1259 | "no-referrer" | |||
|
1260 | ], | |||
|
1261 | "server": [ | |||
|
1262 | "Apache/2.4.10 (Debian)" | |||
|
1263 | ], | |||
|
1264 | "cache-control": [ | |||
|
1265 | "no-store" | |||
|
1266 | ] | |||
|
1267 | }, | |||
|
1268 | "status": { | |||
|
1269 | "code": 200, | |||
|
1270 | "message": "OK" | |||
|
1271 | }, | |||
|
1272 | "body": { | |||
|
1273 | "string": "{\"result\":{\"errors\":[],\"fields\":{\"title\":\"copied binary\"},\"revisionIDFieldInfo\":{\"value\":null,\"validDomain\":\"https:\\/\\/phab.mercurial-scm.org\"},\"transactions\":[{\"type\":\"title\",\"value\":\"copied binary\"}]},\"error_code\":null,\"error_info\":null}" | |||
|
1274 | } | |||
|
1275 | }, | |||
|
1276 | "request": { | |||
|
1277 | "headers": { | |||
|
1278 | "content-length": [ | |||
|
1279 | "158" | |||
|
1280 | ], | |||
|
1281 | "host": [ | |||
|
1282 | "phab.mercurial-scm.org" | |||
|
1283 | ], | |||
|
1284 | "content-type": [ | |||
|
1285 | "application/x-www-form-urlencoded" | |||
|
1286 | ], | |||
|
1287 | "user-agent": [ | |||
|
1288 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
1289 | ], | |||
|
1290 | "accept": [ | |||
|
1291 | "application/mercurial-0.1" | |||
|
1292 | ] | |||
|
1293 | }, | |||
|
1294 | "uri": "https://phab.mercurial-scm.org//api/differential.parsecommitmessage", | |||
|
1295 | "method": "POST", | |||
|
1296 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22corpus%22%3A+%22copied+binary%22%7D" | |||
|
1297 | } | |||
|
1298 | }, | |||
|
1299 | { | |||
|
1300 | "response": { | |||
|
1301 | "headers": { | |||
|
1302 | "date": [ | |||
|
1303 | "Tue, 18 Feb 2020 18:43:20 GMT" | |||
|
1304 | ], | |||
|
1305 | "strict-transport-security": [ | |||
|
1306 | "max-age=0; includeSubdomains; preload" | |||
|
1307 | ], | |||
|
1308 | "x-frame-options": [ | |||
|
1309 | "Deny" | |||
|
1310 | ], | |||
|
1311 | "content-type": [ | |||
|
1312 | "application/json" | |||
|
1313 | ], | |||
|
1314 | "transfer-encoding": [ | |||
|
1315 | "chunked" | |||
|
1316 | ], | |||
|
1317 | "x-xss-protection": [ | |||
|
1318 | "1; mode=block" | |||
|
1319 | ], | |||
|
1320 | "expires": [ | |||
|
1321 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
1322 | ], | |||
|
1323 | "x-content-type-options": [ | |||
|
1324 | "nosniff" | |||
|
1325 | ], | |||
|
1326 | "referrer-policy": [ | |||
|
1327 | "no-referrer" | |||
|
1328 | ], | |||
|
1329 | "server": [ | |||
|
1330 | "Apache/2.4.10 (Debian)" | |||
|
1331 | ], | |||
|
1332 | "cache-control": [ | |||
|
1333 | "no-store" | |||
|
1334 | ] | |||
|
1335 | }, | |||
|
1336 | "status": { | |||
|
1337 | "code": 200, | |||
|
1338 | "message": "OK" | |||
|
1339 | }, | |||
|
1340 | "body": { | |||
|
1341 | "string": "{\"result\":{\"object\":{\"id\":8130,\"phid\":\"PHID-DREV-fqgsuuwbzvaodvaeengd\"},\"transactions\":[{\"phid\":\"PHID-XACT-DREV-5vmkafcunj3k74h\"},{\"phid\":\"PHID-XACT-DREV-fqkifg45n7tfcwm\"},{\"phid\":\"PHID-XACT-DREV-xuxqxmsrs5l4ppy\"},{\"phid\":\"PHID-XACT-DREV-brzpr5bfvs7r6jr\"},{\"phid\":\"PHID-XACT-DREV-3jplzixdyccpkpf\"},{\"phid\":\"PHID-XACT-DREV-sqm2qjussz6bsta\"}]},\"error_code\":null,\"error_info\":null}" | |||
|
1342 | } | |||
|
1343 | }, | |||
|
1344 | "request": { | |||
|
1345 | "headers": { | |||
|
1346 | "content-length": [ | |||
|
1347 | "413" | |||
|
1348 | ], | |||
|
1349 | "host": [ | |||
|
1350 | "phab.mercurial-scm.org" | |||
|
1351 | ], | |||
|
1352 | "content-type": [ | |||
|
1353 | "application/x-www-form-urlencoded" | |||
|
1354 | ], | |||
|
1355 | "user-agent": [ | |||
|
1356 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
1357 | ], | |||
|
1358 | "accept": [ | |||
|
1359 | "application/mercurial-0.1" | |||
|
1360 | ] | |||
|
1361 | }, | |||
|
1362 | "uri": "https://phab.mercurial-scm.org//api/differential.revision.edit", | |||
|
1363 | "method": "POST", | |||
|
1364 | "body": "output=json&__conduit__=1¶ms=%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-g7zeghdg2dzimfldfk5b%22%7D%2C+%7B%22type%22%3A+%22parents.set%22%2C+%22value%22%3A+%5B%22PHID-DREV-s74qwcmdszxugly5fjru%22%5D%7D%2C+%7B%22type%22%3A+%22title%22%2C+%22value%22%3A+%22copied+binary%22%7D%5D%7D" | |||
|
1365 | } | |||
|
1366 | }, | |||
|
1367 | { | |||
|
1368 | "response": { | |||
|
1369 | "headers": { | |||
|
1370 | "date": [ | |||
|
1371 | "Tue, 18 Feb 2020 18:43:21 GMT" | |||
|
1372 | ], | |||
|
1373 | "strict-transport-security": [ | |||
|
1374 | "max-age=0; includeSubdomains; preload" | |||
|
1375 | ], | |||
|
1376 | "x-frame-options": [ | |||
|
1377 | "Deny" | |||
|
1378 | ], | |||
|
1379 | "content-type": [ | |||
|
1380 | "application/json" | |||
|
1381 | ], | |||
|
1382 | "transfer-encoding": [ | |||
|
1383 | "chunked" | |||
|
1384 | ], | |||
|
1385 | "x-xss-protection": [ | |||
|
1386 | "1; mode=block" | |||
|
1387 | ], | |||
|
1388 | "expires": [ | |||
|
1389 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
1390 | ], | |||
|
1391 | "x-content-type-options": [ | |||
|
1392 | "nosniff" | |||
|
1393 | ], | |||
|
1394 | "referrer-policy": [ | |||
|
1395 | "no-referrer" | |||
|
1396 | ], | |||
|
1397 | "server": [ | |||
|
1398 | "Apache/2.4.10 (Debian)" | |||
|
1399 | ], | |||
|
1400 | "cache-control": [ | |||
|
1401 | "no-store" | |||
|
1402 | ] | |||
|
1403 | }, | |||
|
1404 | "status": { | |||
|
1405 | "code": 200, | |||
|
1406 | "message": "OK" | |||
|
1407 | }, | |||
|
1408 | "body": { | |||
|
1409 | "string": "{\"result\":{\"upload\":true,\"filePHID\":null},\"error_code\":null,\"error_info\":null}" | |||
|
1410 | } | |||
|
1411 | }, | |||
|
1412 | "request": { | |||
|
1413 | "headers": { | |||
|
1414 | "content-length": [ | |||
|
1415 | "282" | |||
|
1416 | ], | |||
|
1417 | "host": [ | |||
|
1418 | "phab.mercurial-scm.org" | |||
|
1419 | ], | |||
|
1420 | "content-type": [ | |||
|
1421 | "application/x-www-form-urlencoded" | |||
|
1422 | ], | |||
|
1423 | "user-agent": [ | |||
|
1424 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
1425 | ], | |||
|
1426 | "accept": [ | |||
|
1427 | "application/mercurial-0.1" | |||
|
1428 | ] | |||
|
1429 | }, | |||
|
1430 | "uri": "https://phab.mercurial-scm.org//api/file.allocate", | |||
|
1431 | "method": "POST", | |||
|
1432 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22contentHash%22%3A+%22c95c6d5f8642ef05301e41e9f07b12b1ba6ab75ec0892e125c9ea87ac380c396%22%2C+%22contentLength%22%3A+9%2C+%22name%22%3A+%22bin2_moved_again%22%7D" | |||
|
1433 | } | |||
|
1434 | }, | |||
|
1435 | { | |||
|
1436 | "response": { | |||
|
1437 | "headers": { | |||
|
1438 | "date": [ | |||
|
1439 | "Tue, 18 Feb 2020 18:43:21 GMT" | |||
|
1440 | ], | |||
|
1441 | "strict-transport-security": [ | |||
|
1442 | "max-age=0; includeSubdomains; preload" | |||
|
1443 | ], | |||
|
1444 | "x-frame-options": [ | |||
|
1445 | "Deny" | |||
|
1446 | ], | |||
|
1447 | "content-type": [ | |||
|
1448 | "application/json" | |||
|
1449 | ], | |||
|
1450 | "transfer-encoding": [ | |||
|
1451 | "chunked" | |||
|
1452 | ], | |||
|
1453 | "x-xss-protection": [ | |||
|
1454 | "1; mode=block" | |||
|
1455 | ], | |||
|
1456 | "expires": [ | |||
|
1457 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
1458 | ], | |||
|
1459 | "x-content-type-options": [ | |||
|
1460 | "nosniff" | |||
|
1461 | ], | |||
|
1462 | "referrer-policy": [ | |||
|
1463 | "no-referrer" | |||
|
1464 | ], | |||
|
1465 | "server": [ | |||
|
1466 | "Apache/2.4.10 (Debian)" | |||
|
1467 | ], | |||
|
1468 | "cache-control": [ | |||
|
1469 | "no-store" | |||
|
1470 | ] | |||
|
1471 | }, | |||
|
1472 | "status": { | |||
|
1473 | "code": 200, | |||
|
1474 | "message": "OK" | |||
|
1475 | }, | |||
|
1476 | "body": { | |||
|
1477 | "string": "{\"result\":\"PHID-FILE-2jcxh7p4n5p7gqhpyvv6\",\"error_code\":null,\"error_info\":null}" | |||
|
1478 | } | |||
|
1479 | }, | |||
|
1480 | "request": { | |||
|
1481 | "headers": { | |||
|
1482 | "content-length": [ | |||
|
1483 | "202" | |||
|
1484 | ], | |||
|
1485 | "host": [ | |||
|
1486 | "phab.mercurial-scm.org" | |||
|
1487 | ], | |||
|
1488 | "content-type": [ | |||
|
1489 | "application/x-www-form-urlencoded" | |||
|
1490 | ], | |||
|
1491 | "user-agent": [ | |||
|
1492 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
1493 | ], | |||
|
1494 | "accept": [ | |||
|
1495 | "application/mercurial-0.1" | |||
|
1496 | ] | |||
|
1497 | }, | |||
|
1498 | "uri": "https://phab.mercurial-scm.org//api/file.upload", | |||
|
1499 | "method": "POST", | |||
|
1500 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data_base64%22%3A+%22AG1vdmUrbW9k%22%2C+%22name%22%3A+%22bin2_moved_again%22%7D" | |||
|
1501 | } | |||
|
1502 | }, | |||
|
1503 | { | |||
|
1504 | "response": { | |||
|
1505 | "headers": { | |||
|
1506 | "date": [ | |||
|
1507 | "Tue, 18 Feb 2020 18:43:22 GMT" | |||
|
1508 | ], | |||
|
1509 | "strict-transport-security": [ | |||
|
1510 | "max-age=0; includeSubdomains; preload" | |||
|
1511 | ], | |||
|
1512 | "x-frame-options": [ | |||
|
1513 | "Deny" | |||
|
1514 | ], | |||
|
1515 | "content-type": [ | |||
|
1516 | "application/json" | |||
|
1517 | ], | |||
|
1518 | "transfer-encoding": [ | |||
|
1519 | "chunked" | |||
|
1520 | ], | |||
|
1521 | "x-xss-protection": [ | |||
|
1522 | "1; mode=block" | |||
|
1523 | ], | |||
|
1524 | "expires": [ | |||
|
1525 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
1526 | ], | |||
|
1527 | "x-content-type-options": [ | |||
|
1528 | "nosniff" | |||
|
1529 | ], | |||
|
1530 | "referrer-policy": [ | |||
|
1531 | "no-referrer" | |||
|
1532 | ], | |||
|
1533 | "server": [ | |||
|
1534 | "Apache/2.4.10 (Debian)" | |||
|
1535 | ], | |||
|
1536 | "cache-control": [ | |||
|
1537 | "no-store" | |||
|
1538 | ] | |||
|
1539 | }, | |||
|
1540 | "status": { | |||
|
1541 | "code": 200, | |||
|
1542 | "message": "OK" | |||
|
1543 | }, | |||
|
1544 | "body": { | |||
|
1545 | "string": "{\"result\":{\"upload\":false,\"filePHID\":\"PHID-FILE-kjwtw4t6foc3cxtpcot2\"},\"error_code\":null,\"error_info\":null}" | |||
|
1546 | } | |||
|
1547 | }, | |||
|
1548 | "request": { | |||
|
1549 | "headers": { | |||
|
1550 | "content-length": [ | |||
|
1551 | "277" | |||
|
1552 | ], | |||
|
1553 | "host": [ | |||
|
1554 | "phab.mercurial-scm.org" | |||
|
1555 | ], | |||
|
1556 | "content-type": [ | |||
|
1557 | "application/x-www-form-urlencoded" | |||
|
1558 | ], | |||
|
1559 | "user-agent": [ | |||
|
1560 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
1561 | ], | |||
|
1562 | "accept": [ | |||
|
1563 | "application/mercurial-0.1" | |||
|
1564 | ] | |||
|
1565 | }, | |||
|
1566 | "uri": "https://phab.mercurial-scm.org//api/file.allocate", | |||
|
1567 | "method": "POST", | |||
|
1568 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22contentHash%22%3A+%22597fcb31282d34654c200d3418fca5705c648ebf326ec73d8ddef11841f876d8%22%2C+%22contentLength%22%3A+2%2C+%22name%22%3A+%22bin2_copied%22%7D" | |||
|
1569 | } | |||
|
1570 | }, | |||
|
1571 | { | |||
|
1572 | "response": { | |||
|
1573 | "headers": { | |||
|
1574 | "date": [ | |||
|
1575 | "Tue, 18 Feb 2020 18:43:22 GMT" | |||
|
1576 | ], | |||
|
1577 | "strict-transport-security": [ | |||
|
1578 | "max-age=0; includeSubdomains; preload" | |||
|
1579 | ], | |||
|
1580 | "x-frame-options": [ | |||
|
1581 | "Deny" | |||
|
1582 | ], | |||
|
1583 | "content-type": [ | |||
|
1584 | "application/json" | |||
|
1585 | ], | |||
|
1586 | "transfer-encoding": [ | |||
|
1587 | "chunked" | |||
|
1588 | ], | |||
|
1589 | "x-xss-protection": [ | |||
|
1590 | "1; mode=block" | |||
|
1591 | ], | |||
|
1592 | "expires": [ | |||
|
1593 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
1594 | ], | |||
|
1595 | "x-content-type-options": [ | |||
|
1596 | "nosniff" | |||
|
1597 | ], | |||
|
1598 | "referrer-policy": [ | |||
|
1599 | "no-referrer" | |||
|
1600 | ], | |||
|
1601 | "server": [ | |||
|
1602 | "Apache/2.4.10 (Debian)" | |||
|
1603 | ], | |||
|
1604 | "cache-control": [ | |||
|
1605 | "no-store" | |||
|
1606 | ] | |||
|
1607 | }, | |||
|
1608 | "status": { | |||
|
1609 | "code": 200, | |||
|
1610 | "message": "OK" | |||
|
1611 | }, | |||
|
1612 | "body": { | |||
|
1613 | "string": "{\"result\":{\"diffid\":20255,\"phid\":\"PHID-DIFF-euvajw4uojheyjmhqmav\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/differential\\/diff\\/20255\\/\"},\"error_code\":null,\"error_info\":null}" | |||
|
1614 | } | |||
|
1615 | }, | |||
|
1616 | "request": { | |||
|
1617 | "headers": { | |||
|
1618 | "content-length": [ | |||
|
1619 | "1575" | |||
|
1620 | ], | |||
|
1621 | "host": [ | |||
|
1622 | "phab.mercurial-scm.org" | |||
|
1623 | ], | |||
|
1624 | "content-type": [ | |||
|
1625 | "application/x-www-form-urlencoded" | |||
|
1626 | ], | |||
|
1627 | "user-agent": [ | |||
|
1628 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
1629 | ], | |||
|
1630 | "accept": [ | |||
|
1631 | "application/mercurial-0.1" | |||
|
1632 | ] | |||
|
1633 | }, | |||
|
1634 | "uri": "https://phab.mercurial-scm.org//api/differential.creatediff", | |||
|
1635 | "method": "POST", | |||
|
1636 | "body": "output=json&__conduit__=1¶ms=%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%22bin2_copied%22%3A+%7B%22addLines%22%3A+0%2C+%22awayPaths%22%3A+%5B%22bin2_moved_again%22%5D%2C+%22commitHash%22%3A+null%2C+%22currentPath%22%3A+%22bin2_copied%22%2C+%22delLines%22%3A+0%2C+%22fileType%22%3A+1%2C+%22hunks%22%3A+%5B%5D%2C+%22metadata%22%3A+%7B%7D%2C+%22newProperties%22%3A+%7B%7D%2C+%22oldPath%22%3A+%22bin2_copied%22%2C+%22oldProperties%22%3A+%7B%7D%2C+%22type%22%3A+4%7D%2C+%22bin2_moved_again%22%3A+%7B%22addLines%22%3A+0%2C+%22awayPaths%22%3A+%5B%5D%2C+%22commitHash%22%3A+null%2C+%22currentPath%22%3A+%22bin2_moved_again%22%2C+%22delLines%22%3A+0%2C+%22fileType%22%3A+3%2C+%22hunks%22%3A+%5B%5D%2C+%22metadata%22%3A+%7B%22new%3Abinary-phid%22%3A+%22PHID-FILE-2jcxh7p4n5p7gqhpyvv6%22%2C+%22new%3Afile%3Asize%22%3A+9%2C+%22old%3Abinary-phid%22%3A+%22PHID-FILE-kjwtw4t6foc3cxtpcot2%22%2C+%22old%3Afile%3Asize%22%3A+2%7D%2C+%22newProperties%22%3A+%7B%7D%2C+%22oldPath%22%3A+%22bin2_copied%22%2C+%22oldProperties%22%3A+%7B%7D%2C+%22type%22%3A+6%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+%22494b750e5194bb08c1de04d6b292e28273afa18c%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" | |||
|
1637 | } | |||
|
1638 | }, | |||
|
1639 | { | |||
|
1640 | "response": { | |||
|
1641 | "headers": { | |||
|
1642 | "date": [ | |||
|
1643 | "Tue, 18 Feb 2020 18:43:22 GMT" | |||
|
1644 | ], | |||
|
1645 | "strict-transport-security": [ | |||
|
1646 | "max-age=0; includeSubdomains; preload" | |||
|
1647 | ], | |||
|
1648 | "x-frame-options": [ | |||
|
1649 | "Deny" | |||
|
1650 | ], | |||
|
1651 | "content-type": [ | |||
|
1652 | "application/json" | |||
|
1653 | ], | |||
|
1654 | "transfer-encoding": [ | |||
|
1655 | "chunked" | |||
|
1656 | ], | |||
|
1657 | "x-xss-protection": [ | |||
|
1658 | "1; mode=block" | |||
|
1659 | ], | |||
|
1660 | "expires": [ | |||
|
1661 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
1662 | ], | |||
|
1663 | "x-content-type-options": [ | |||
|
1664 | "nosniff" | |||
|
1665 | ], | |||
|
1666 | "referrer-policy": [ | |||
|
1667 | "no-referrer" | |||
|
1668 | ], | |||
|
1669 | "server": [ | |||
|
1670 | "Apache/2.4.10 (Debian)" | |||
|
1671 | ], | |||
|
1672 | "cache-control": [ | |||
|
1673 | "no-store" | |||
|
1674 | ] | |||
|
1675 | }, | |||
|
1676 | "status": { | |||
|
1677 | "code": 200, | |||
|
1678 | "message": "OK" | |||
|
1679 | }, | |||
|
1680 | "body": { | |||
|
1681 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
1682 | } | |||
|
1683 | }, | |||
|
1684 | "request": { | |||
|
1685 | "headers": { | |||
|
1686 | "content-length": [ | |||
|
1687 | "482" | |||
|
1688 | ], | |||
|
1689 | "host": [ | |||
|
1690 | "phab.mercurial-scm.org" | |||
|
1691 | ], | |||
|
1692 | "content-type": [ | |||
|
1693 | "application/x-www-form-urlencoded" | |||
|
1694 | ], | |||
|
1695 | "user-agent": [ | |||
|
1696 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
1697 | ], | |||
|
1698 | "accept": [ | |||
|
1699 | "application/mercurial-0.1" | |||
|
1700 | ] | |||
|
1701 | }, | |||
|
1702 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
1703 | "method": "POST", | |||
|
1704 | "body": "output=json&__conduit__=1¶ms=%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%2225f766b50cc23ba2f44ed3bc707b429cdf0186e8%5C%22%2C+%5C%22parent%5C%22%3A+%5C%22494b750e5194bb08c1de04d6b292e28273afa18c%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+20255%2C+%22name%22%3A+%22hg%3Ameta%22%7D" | |||
|
1705 | } | |||
|
1706 | }, | |||
|
1707 | { | |||
|
1708 | "response": { | |||
|
1709 | "headers": { | |||
|
1710 | "date": [ | |||
|
1711 | "Tue, 18 Feb 2020 18:43:23 GMT" | |||
|
1712 | ], | |||
|
1713 | "strict-transport-security": [ | |||
|
1714 | "max-age=0; includeSubdomains; preload" | |||
|
1715 | ], | |||
|
1716 | "x-frame-options": [ | |||
|
1717 | "Deny" | |||
|
1718 | ], | |||
|
1719 | "content-type": [ | |||
|
1720 | "application/json" | |||
|
1721 | ], | |||
|
1722 | "transfer-encoding": [ | |||
|
1723 | "chunked" | |||
|
1724 | ], | |||
|
1725 | "x-xss-protection": [ | |||
|
1726 | "1; mode=block" | |||
|
1727 | ], | |||
|
1728 | "expires": [ | |||
|
1729 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
1730 | ], | |||
|
1731 | "x-content-type-options": [ | |||
|
1732 | "nosniff" | |||
|
1733 | ], | |||
|
1734 | "referrer-policy": [ | |||
|
1735 | "no-referrer" | |||
|
1736 | ], | |||
|
1737 | "server": [ | |||
|
1738 | "Apache/2.4.10 (Debian)" | |||
|
1739 | ], | |||
|
1740 | "cache-control": [ | |||
|
1741 | "no-store" | |||
|
1742 | ] | |||
|
1743 | }, | |||
|
1744 | "status": { | |||
|
1745 | "code": 200, | |||
|
1746 | "message": "OK" | |||
|
1747 | }, | |||
|
1748 | "body": { | |||
|
1749 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
1750 | } | |||
|
1751 | }, | |||
|
1752 | "request": { | |||
|
1753 | "headers": { | |||
|
1754 | "content-length": [ | |||
|
1755 | "594" | |||
|
1756 | ], | |||
|
1757 | "host": [ | |||
|
1758 | "phab.mercurial-scm.org" | |||
|
1759 | ], | |||
|
1760 | "content-type": [ | |||
|
1761 | "application/x-www-form-urlencoded" | |||
|
1762 | ], | |||
|
1763 | "user-agent": [ | |||
|
1764 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
1765 | ], | |||
|
1766 | "accept": [ | |||
|
1767 | "application/mercurial-0.1" | |||
|
1768 | ] | |||
|
1769 | }, | |||
|
1770 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
1771 | "method": "POST", | |||
|
1772 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%2225f766b50cc23ba2f44ed3bc707b429cdf0186e8%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%2225f766b50cc23ba2f44ed3bc707b429cdf0186e8%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%22494b750e5194bb08c1de04d6b292e28273afa18c%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+20255%2C+%22name%22%3A+%22local%3Acommits%22%7D" | |||
|
1773 | } | |||
|
1774 | }, | |||
|
1775 | { | |||
|
1776 | "response": { | |||
|
1777 | "headers": { | |||
|
1778 | "date": [ | |||
|
1779 | "Tue, 18 Feb 2020 18:43:23 GMT" | |||
|
1780 | ], | |||
|
1781 | "strict-transport-security": [ | |||
|
1782 | "max-age=0; includeSubdomains; preload" | |||
|
1783 | ], | |||
|
1784 | "x-frame-options": [ | |||
|
1785 | "Deny" | |||
|
1786 | ], | |||
|
1787 | "content-type": [ | |||
|
1788 | "application/json" | |||
|
1789 | ], | |||
|
1790 | "transfer-encoding": [ | |||
|
1791 | "chunked" | |||
|
1792 | ], | |||
|
1793 | "x-xss-protection": [ | |||
|
1794 | "1; mode=block" | |||
|
1795 | ], | |||
|
1796 | "expires": [ | |||
|
1797 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
1798 | ], | |||
|
1799 | "x-content-type-options": [ | |||
|
1800 | "nosniff" | |||
|
1801 | ], | |||
|
1802 | "referrer-policy": [ | |||
|
1803 | "no-referrer" | |||
|
1804 | ], | |||
|
1805 | "server": [ | |||
|
1806 | "Apache/2.4.10 (Debian)" | |||
|
1807 | ], | |||
|
1808 | "cache-control": [ | |||
|
1809 | "no-store" | |||
|
1810 | ] | |||
|
1811 | }, | |||
|
1812 | "status": { | |||
|
1813 | "code": 200, | |||
|
1814 | "message": "OK" | |||
|
1815 | }, | |||
|
1816 | "body": { | |||
|
1817 | "string": "{\"result\":{\"errors\":[],\"fields\":{\"title\":\"move+mod copied binary\"},\"revisionIDFieldInfo\":{\"value\":null,\"validDomain\":\"https:\\/\\/phab.mercurial-scm.org\"},\"transactions\":[{\"type\":\"title\",\"value\":\"move+mod copied binary\"}]},\"error_code\":null,\"error_info\":null}" | |||
|
1818 | } | |||
|
1819 | }, | |||
|
1820 | "request": { | |||
|
1821 | "headers": { | |||
|
1822 | "content-length": [ | |||
|
1823 | "169" | |||
|
1824 | ], | |||
|
1825 | "host": [ | |||
|
1826 | "phab.mercurial-scm.org" | |||
|
1827 | ], | |||
|
1828 | "content-type": [ | |||
|
1829 | "application/x-www-form-urlencoded" | |||
|
1830 | ], | |||
|
1831 | "user-agent": [ | |||
|
1832 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
1833 | ], | |||
|
1834 | "accept": [ | |||
|
1835 | "application/mercurial-0.1" | |||
|
1836 | ] | |||
|
1837 | }, | |||
|
1838 | "uri": "https://phab.mercurial-scm.org//api/differential.parsecommitmessage", | |||
|
1839 | "method": "POST", | |||
|
1840 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22corpus%22%3A+%22move%2Bmod+copied+binary%22%7D" | |||
|
1841 | } | |||
|
1842 | }, | |||
|
1843 | { | |||
|
1844 | "response": { | |||
|
1845 | "headers": { | |||
|
1846 | "date": [ | |||
|
1847 | "Tue, 18 Feb 2020 18:43:24 GMT" | |||
|
1848 | ], | |||
|
1849 | "strict-transport-security": [ | |||
|
1850 | "max-age=0; includeSubdomains; preload" | |||
|
1851 | ], | |||
|
1852 | "x-frame-options": [ | |||
|
1853 | "Deny" | |||
|
1854 | ], | |||
|
1855 | "content-type": [ | |||
|
1856 | "application/json" | |||
|
1857 | ], | |||
|
1858 | "transfer-encoding": [ | |||
|
1859 | "chunked" | |||
|
1860 | ], | |||
|
1861 | "x-xss-protection": [ | |||
|
1862 | "1; mode=block" | |||
|
1863 | ], | |||
|
1864 | "expires": [ | |||
|
1865 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
1866 | ], | |||
|
1867 | "x-content-type-options": [ | |||
|
1868 | "nosniff" | |||
|
1869 | ], | |||
|
1870 | "referrer-policy": [ | |||
|
1871 | "no-referrer" | |||
|
1872 | ], | |||
|
1873 | "server": [ | |||
|
1874 | "Apache/2.4.10 (Debian)" | |||
|
1875 | ], | |||
|
1876 | "cache-control": [ | |||
|
1877 | "no-store" | |||
|
1878 | ] | |||
|
1879 | }, | |||
|
1880 | "status": { | |||
|
1881 | "code": 200, | |||
|
1882 | "message": "OK" | |||
|
1883 | }, | |||
|
1884 | "body": { | |||
|
1885 | "string": "{\"result\":{\"object\":{\"id\":8131,\"phid\":\"PHID-DREV-hcbzjqg4xhb6thfuhkgp\"},\"transactions\":[{\"phid\":\"PHID-XACT-DREV-ui3eapxx7g4iur2\"},{\"phid\":\"PHID-XACT-DREV-wgr7u5ka7qru33i\"},{\"phid\":\"PHID-XACT-DREV-2yozrzratdvxnl7\"},{\"phid\":\"PHID-XACT-DREV-ikamh5ifb64cnr5\"},{\"phid\":\"PHID-XACT-DREV-cu2qhhytni4ruip\"},{\"phid\":\"PHID-XACT-DREV-n3zs6drpigwdrtw\"}]},\"error_code\":null,\"error_info\":null}" | |||
|
1886 | } | |||
|
1887 | }, | |||
|
1888 | "request": { | |||
|
1889 | "headers": { | |||
|
1890 | "content-length": [ | |||
|
1891 | "424" | |||
|
1892 | ], | |||
|
1893 | "host": [ | |||
|
1894 | "phab.mercurial-scm.org" | |||
|
1895 | ], | |||
|
1896 | "content-type": [ | |||
|
1897 | "application/x-www-form-urlencoded" | |||
|
1898 | ], | |||
|
1899 | "user-agent": [ | |||
|
1900 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
1901 | ], | |||
|
1902 | "accept": [ | |||
|
1903 | "application/mercurial-0.1" | |||
|
1904 | ] | |||
|
1905 | }, | |||
|
1906 | "uri": "https://phab.mercurial-scm.org//api/differential.revision.edit", | |||
|
1907 | "method": "POST", | |||
|
1908 | "body": "output=json&__conduit__=1¶ms=%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-euvajw4uojheyjmhqmav%22%7D%2C+%7B%22type%22%3A+%22parents.set%22%2C+%22value%22%3A+%5B%22PHID-DREV-fqgsuuwbzvaodvaeengd%22%5D%7D%2C+%7B%22type%22%3A+%22title%22%2C+%22value%22%3A+%22move%2Bmod+copied+binary%22%7D%5D%7D" | |||
|
1909 | } | |||
|
1910 | }, | |||
|
1911 | { | |||
|
1912 | "response": { | |||
|
1913 | "headers": { | |||
|
1914 | "date": [ | |||
|
1915 | "Tue, 18 Feb 2020 18:43:24 GMT" | |||
|
1916 | ], | |||
|
1917 | "strict-transport-security": [ | |||
|
1918 | "max-age=0; includeSubdomains; preload" | |||
|
1919 | ], | |||
|
1920 | "x-frame-options": [ | |||
|
1921 | "Deny" | |||
|
1922 | ], | |||
|
1923 | "content-type": [ | |||
|
1924 | "application/json" | |||
|
1925 | ], | |||
|
1926 | "transfer-encoding": [ | |||
|
1927 | "chunked" | |||
|
1928 | ], | |||
|
1929 | "x-xss-protection": [ | |||
|
1930 | "1; mode=block" | |||
|
1931 | ], | |||
|
1932 | "expires": [ | |||
|
1933 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
1934 | ], | |||
|
1935 | "x-content-type-options": [ | |||
|
1936 | "nosniff" | |||
|
1937 | ], | |||
|
1938 | "referrer-policy": [ | |||
|
1939 | "no-referrer" | |||
|
1940 | ], | |||
|
1941 | "server": [ | |||
|
1942 | "Apache/2.4.10 (Debian)" | |||
|
1943 | ], | |||
|
1944 | "cache-control": [ | |||
|
1945 | "no-store" | |||
|
1946 | ] | |||
|
1947 | }, | |||
|
1948 | "status": { | |||
|
1949 | "code": 200, | |||
|
1950 | "message": "OK" | |||
|
1951 | }, | |||
|
1952 | "body": { | |||
|
1953 | "string": "{\"result\":{\"upload\":true,\"filePHID\":null},\"error_code\":null,\"error_info\":null}" | |||
|
1954 | } | |||
|
1955 | }, | |||
|
1956 | "request": { | |||
|
1957 | "headers": { | |||
|
1958 | "content-length": [ | |||
|
1959 | "284" | |||
|
1960 | ], | |||
|
1961 | "host": [ | |||
|
1962 | "phab.mercurial-scm.org" | |||
|
1963 | ], | |||
|
1964 | "content-type": [ | |||
|
1965 | "application/x-www-form-urlencoded" | |||
|
1966 | ], | |||
|
1967 | "user-agent": [ | |||
|
1968 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
1969 | ], | |||
|
1970 | "accept": [ | |||
|
1971 | "application/mercurial-0.1" | |||
|
1972 | ] | |||
|
1973 | }, | |||
|
1974 | "uri": "https://phab.mercurial-scm.org//api/file.allocate", | |||
|
1975 | "method": "POST", | |||
|
1976 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22contentHash%22%3A+%226aa3b10b091f1381d0720c4e96baee2ac03f355a10b0bc1afebf082cab1a9589%22%2C+%22contentLength%22%3A+12%2C+%22name%22%3A+%22bin2_moved_copied%22%7D" | |||
|
1977 | } | |||
|
1978 | }, | |||
|
1979 | { | |||
|
1980 | "response": { | |||
|
1981 | "headers": { | |||
|
1982 | "date": [ | |||
|
1983 | "Tue, 18 Feb 2020 18:43:25 GMT" | |||
|
1984 | ], | |||
|
1985 | "strict-transport-security": [ | |||
|
1986 | "max-age=0; includeSubdomains; preload" | |||
|
1987 | ], | |||
|
1988 | "x-frame-options": [ | |||
|
1989 | "Deny" | |||
|
1990 | ], | |||
|
1991 | "content-type": [ | |||
|
1992 | "application/json" | |||
|
1993 | ], | |||
|
1994 | "transfer-encoding": [ | |||
|
1995 | "chunked" | |||
|
1996 | ], | |||
|
1997 | "x-xss-protection": [ | |||
|
1998 | "1; mode=block" | |||
|
1999 | ], | |||
|
2000 | "expires": [ | |||
|
2001 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
2002 | ], | |||
|
2003 | "x-content-type-options": [ | |||
|
2004 | "nosniff" | |||
|
2005 | ], | |||
|
2006 | "referrer-policy": [ | |||
|
2007 | "no-referrer" | |||
|
2008 | ], | |||
|
2009 | "server": [ | |||
|
2010 | "Apache/2.4.10 (Debian)" | |||
|
2011 | ], | |||
|
2012 | "cache-control": [ | |||
|
2013 | "no-store" | |||
|
2014 | ] | |||
|
2015 | }, | |||
|
2016 | "status": { | |||
|
2017 | "code": 200, | |||
|
2018 | "message": "OK" | |||
|
2019 | }, | |||
|
2020 | "body": { | |||
|
2021 | "string": "{\"result\":\"PHID-FILE-2kkudzwg35maqlp4mkdz\",\"error_code\":null,\"error_info\":null}" | |||
|
2022 | } | |||
|
2023 | }, | |||
|
2024 | "request": { | |||
|
2025 | "headers": { | |||
|
2026 | "content-length": [ | |||
|
2027 | "207" | |||
|
2028 | ], | |||
|
2029 | "host": [ | |||
|
2030 | "phab.mercurial-scm.org" | |||
|
2031 | ], | |||
|
2032 | "content-type": [ | |||
|
2033 | "application/x-www-form-urlencoded" | |||
|
2034 | ], | |||
|
2035 | "user-agent": [ | |||
|
2036 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
2037 | ], | |||
|
2038 | "accept": [ | |||
|
2039 | "application/mercurial-0.1" | |||
|
2040 | ] | |||
|
2041 | }, | |||
|
2042 | "uri": "https://phab.mercurial-scm.org//api/file.upload", | |||
|
2043 | "method": "POST", | |||
|
2044 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data_base64%22%3A+%22AHByZWNvcHkgbW9k%22%2C+%22name%22%3A+%22bin2_moved_copied%22%7D" | |||
|
2045 | } | |||
|
2046 | }, | |||
|
2047 | { | |||
|
2048 | "response": { | |||
|
2049 | "headers": { | |||
|
2050 | "date": [ | |||
|
2051 | "Tue, 18 Feb 2020 18:43:25 GMT" | |||
|
2052 | ], | |||
|
2053 | "strict-transport-security": [ | |||
|
2054 | "max-age=0; includeSubdomains; preload" | |||
|
2055 | ], | |||
|
2056 | "x-frame-options": [ | |||
|
2057 | "Deny" | |||
|
2058 | ], | |||
|
2059 | "content-type": [ | |||
|
2060 | "application/json" | |||
|
2061 | ], | |||
|
2062 | "transfer-encoding": [ | |||
|
2063 | "chunked" | |||
|
2064 | ], | |||
|
2065 | "x-xss-protection": [ | |||
|
2066 | "1; mode=block" | |||
|
2067 | ], | |||
|
2068 | "expires": [ | |||
|
2069 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
2070 | ], | |||
|
2071 | "x-content-type-options": [ | |||
|
2072 | "nosniff" | |||
|
2073 | ], | |||
|
2074 | "referrer-policy": [ | |||
|
2075 | "no-referrer" | |||
|
2076 | ], | |||
|
2077 | "server": [ | |||
|
2078 | "Apache/2.4.10 (Debian)" | |||
|
2079 | ], | |||
|
2080 | "cache-control": [ | |||
|
2081 | "no-store" | |||
|
2082 | ] | |||
|
2083 | }, | |||
|
2084 | "status": { | |||
|
2085 | "code": 200, | |||
|
2086 | "message": "OK" | |||
|
2087 | }, | |||
|
2088 | "body": { | |||
|
2089 | "string": "{\"result\":{\"upload\":false,\"filePHID\":\"PHID-FILE-cj76tzcs23ob2jmkoscw\"},\"error_code\":null,\"error_info\":null}" | |||
|
2090 | } | |||
|
2091 | }, | |||
|
2092 | "request": { | |||
|
2093 | "headers": { | |||
|
2094 | "content-length": [ | |||
|
2095 | "276" | |||
|
2096 | ], | |||
|
2097 | "host": [ | |||
|
2098 | "phab.mercurial-scm.org" | |||
|
2099 | ], | |||
|
2100 | "content-type": [ | |||
|
2101 | "application/x-www-form-urlencoded" | |||
|
2102 | ], | |||
|
2103 | "user-agent": [ | |||
|
2104 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
2105 | ], | |||
|
2106 | "accept": [ | |||
|
2107 | "application/mercurial-0.1" | |||
|
2108 | ] | |||
|
2109 | }, | |||
|
2110 | "uri": "https://phab.mercurial-scm.org//api/file.allocate", | |||
|
2111 | "method": "POST", | |||
|
2112 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22contentHash%22%3A+%22597fcb31282d34654c200d3418fca5705c648ebf326ec73d8ddef11841f876d8%22%2C+%22contentLength%22%3A+2%2C+%22name%22%3A+%22bin2_moved%22%7D" | |||
|
2113 | } | |||
|
2114 | }, | |||
|
2115 | { | |||
|
2116 | "response": { | |||
|
2117 | "headers": { | |||
|
2118 | "date": [ | |||
|
2119 | "Tue, 18 Feb 2020 18:43:26 GMT" | |||
|
2120 | ], | |||
|
2121 | "strict-transport-security": [ | |||
|
2122 | "max-age=0; includeSubdomains; preload" | |||
|
2123 | ], | |||
|
2124 | "x-frame-options": [ | |||
|
2125 | "Deny" | |||
|
2126 | ], | |||
|
2127 | "content-type": [ | |||
|
2128 | "application/json" | |||
|
2129 | ], | |||
|
2130 | "transfer-encoding": [ | |||
|
2131 | "chunked" | |||
|
2132 | ], | |||
|
2133 | "x-xss-protection": [ | |||
|
2134 | "1; mode=block" | |||
|
2135 | ], | |||
|
2136 | "expires": [ | |||
|
2137 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
2138 | ], | |||
|
2139 | "x-content-type-options": [ | |||
|
2140 | "nosniff" | |||
|
2141 | ], | |||
|
2142 | "referrer-policy": [ | |||
|
2143 | "no-referrer" | |||
|
2144 | ], | |||
|
2145 | "server": [ | |||
|
2146 | "Apache/2.4.10 (Debian)" | |||
|
2147 | ], | |||
|
2148 | "cache-control": [ | |||
|
2149 | "no-store" | |||
|
2150 | ] | |||
|
2151 | }, | |||
|
2152 | "status": { | |||
|
2153 | "code": 200, | |||
|
2154 | "message": "OK" | |||
|
2155 | }, | |||
|
2156 | "body": { | |||
|
2157 | "string": "{\"result\":{\"upload\":true,\"filePHID\":null},\"error_code\":null,\"error_info\":null}" | |||
|
2158 | } | |||
|
2159 | }, | |||
|
2160 | "request": { | |||
|
2161 | "headers": { | |||
|
2162 | "content-length": [ | |||
|
2163 | "277" | |||
|
2164 | ], | |||
|
2165 | "host": [ | |||
|
2166 | "phab.mercurial-scm.org" | |||
|
2167 | ], | |||
|
2168 | "content-type": [ | |||
|
2169 | "application/x-www-form-urlencoded" | |||
|
2170 | ], | |||
|
2171 | "user-agent": [ | |||
|
2172 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
2173 | ], | |||
|
2174 | "accept": [ | |||
|
2175 | "application/mercurial-0.1" | |||
|
2176 | ] | |||
|
2177 | }, | |||
|
2178 | "uri": "https://phab.mercurial-scm.org//api/file.allocate", | |||
|
2179 | "method": "POST", | |||
|
2180 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22contentHash%22%3A+%22abade6cd35ec23966dc37ae41b0ef87cbe6e7f735965176eb5f04d90c1034492%22%2C+%22contentLength%22%3A+13%2C+%22name%22%3A+%22bin2_moved%22%7D" | |||
|
2181 | } | |||
|
2182 | }, | |||
|
2183 | { | |||
|
2184 | "response": { | |||
|
2185 | "headers": { | |||
|
2186 | "date": [ | |||
|
2187 | "Tue, 18 Feb 2020 18:43:26 GMT" | |||
|
2188 | ], | |||
|
2189 | "strict-transport-security": [ | |||
|
2190 | "max-age=0; includeSubdomains; preload" | |||
|
2191 | ], | |||
|
2192 | "x-frame-options": [ | |||
|
2193 | "Deny" | |||
|
2194 | ], | |||
|
2195 | "content-type": [ | |||
|
2196 | "application/json" | |||
|
2197 | ], | |||
|
2198 | "transfer-encoding": [ | |||
|
2199 | "chunked" | |||
|
2200 | ], | |||
|
2201 | "x-xss-protection": [ | |||
|
2202 | "1; mode=block" | |||
|
2203 | ], | |||
|
2204 | "expires": [ | |||
|
2205 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
2206 | ], | |||
|
2207 | "x-content-type-options": [ | |||
|
2208 | "nosniff" | |||
|
2209 | ], | |||
|
2210 | "referrer-policy": [ | |||
|
2211 | "no-referrer" | |||
|
2212 | ], | |||
|
2213 | "server": [ | |||
|
2214 | "Apache/2.4.10 (Debian)" | |||
|
2215 | ], | |||
|
2216 | "cache-control": [ | |||
|
2217 | "no-store" | |||
|
2218 | ] | |||
|
2219 | }, | |||
|
2220 | "status": { | |||
|
2221 | "code": 200, | |||
|
2222 | "message": "OK" | |||
|
2223 | }, | |||
|
2224 | "body": { | |||
|
2225 | "string": "{\"result\":\"PHID-FILE-p7kxwaunxwqcsfp2mcai\",\"error_code\":null,\"error_info\":null}" | |||
|
2226 | } | |||
|
2227 | }, | |||
|
2228 | "request": { | |||
|
2229 | "headers": { | |||
|
2230 | "content-length": [ | |||
|
2231 | "208" | |||
|
2232 | ], | |||
|
2233 | "host": [ | |||
|
2234 | "phab.mercurial-scm.org" | |||
|
2235 | ], | |||
|
2236 | "content-type": [ | |||
|
2237 | "application/x-www-form-urlencoded" | |||
|
2238 | ], | |||
|
2239 | "user-agent": [ | |||
|
2240 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
2241 | ], | |||
|
2242 | "accept": [ | |||
|
2243 | "application/mercurial-0.1" | |||
|
2244 | ] | |||
|
2245 | }, | |||
|
2246 | "uri": "https://phab.mercurial-scm.org//api/file.upload", | |||
|
2247 | "method": "POST", | |||
|
2248 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data_base64%22%3A+%22AGNvcHkgc3JjK21vZA%3D%3D%22%2C+%22name%22%3A+%22bin2_moved%22%7D" | |||
|
2249 | } | |||
|
2250 | }, | |||
|
2251 | { | |||
|
2252 | "response": { | |||
|
2253 | "headers": { | |||
|
2254 | "date": [ | |||
|
2255 | "Tue, 18 Feb 2020 18:43:27 GMT" | |||
|
2256 | ], | |||
|
2257 | "strict-transport-security": [ | |||
|
2258 | "max-age=0; includeSubdomains; preload" | |||
|
2259 | ], | |||
|
2260 | "x-frame-options": [ | |||
|
2261 | "Deny" | |||
|
2262 | ], | |||
|
2263 | "content-type": [ | |||
|
2264 | "application/json" | |||
|
2265 | ], | |||
|
2266 | "transfer-encoding": [ | |||
|
2267 | "chunked" | |||
|
2268 | ], | |||
|
2269 | "x-xss-protection": [ | |||
|
2270 | "1; mode=block" | |||
|
2271 | ], | |||
|
2272 | "expires": [ | |||
|
2273 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
2274 | ], | |||
|
2275 | "x-content-type-options": [ | |||
|
2276 | "nosniff" | |||
|
2277 | ], | |||
|
2278 | "referrer-policy": [ | |||
|
2279 | "no-referrer" | |||
|
2280 | ], | |||
|
2281 | "server": [ | |||
|
2282 | "Apache/2.4.10 (Debian)" | |||
|
2283 | ], | |||
|
2284 | "cache-control": [ | |||
|
2285 | "no-store" | |||
|
2286 | ] | |||
|
2287 | }, | |||
|
2288 | "status": { | |||
|
2289 | "code": 200, | |||
|
2290 | "message": "OK" | |||
|
2291 | }, | |||
|
2292 | "body": { | |||
|
2293 | "string": "{\"result\":{\"upload\":false,\"filePHID\":\"PHID-FILE-erukut7iaaxa6anzphgx\"},\"error_code\":null,\"error_info\":null}" | |||
|
2294 | } | |||
|
2295 | }, | |||
|
2296 | "request": { | |||
|
2297 | "headers": { | |||
|
2298 | "content-length": [ | |||
|
2299 | "276" | |||
|
2300 | ], | |||
|
2301 | "host": [ | |||
|
2302 | "phab.mercurial-scm.org" | |||
|
2303 | ], | |||
|
2304 | "content-type": [ | |||
|
2305 | "application/x-www-form-urlencoded" | |||
|
2306 | ], | |||
|
2307 | "user-agent": [ | |||
|
2308 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
2309 | ], | |||
|
2310 | "accept": [ | |||
|
2311 | "application/mercurial-0.1" | |||
|
2312 | ] | |||
|
2313 | }, | |||
|
2314 | "uri": "https://phab.mercurial-scm.org//api/file.allocate", | |||
|
2315 | "method": "POST", | |||
|
2316 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22contentHash%22%3A+%22597fcb31282d34654c200d3418fca5705c648ebf326ec73d8ddef11841f876d8%22%2C+%22contentLength%22%3A+2%2C+%22name%22%3A+%22bin2_moved%22%7D" | |||
|
2317 | } | |||
|
2318 | }, | |||
|
2319 | { | |||
|
2320 | "response": { | |||
|
2321 | "headers": { | |||
|
2322 | "date": [ | |||
|
2323 | "Tue, 18 Feb 2020 18:43:27 GMT" | |||
|
2324 | ], | |||
|
2325 | "strict-transport-security": [ | |||
|
2326 | "max-age=0; includeSubdomains; preload" | |||
|
2327 | ], | |||
|
2328 | "x-frame-options": [ | |||
|
2329 | "Deny" | |||
|
2330 | ], | |||
|
2331 | "content-type": [ | |||
|
2332 | "application/json" | |||
|
2333 | ], | |||
|
2334 | "transfer-encoding": [ | |||
|
2335 | "chunked" | |||
|
2336 | ], | |||
|
2337 | "x-xss-protection": [ | |||
|
2338 | "1; mode=block" | |||
|
2339 | ], | |||
|
2340 | "expires": [ | |||
|
2341 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
2342 | ], | |||
|
2343 | "x-content-type-options": [ | |||
|
2344 | "nosniff" | |||
|
2345 | ], | |||
|
2346 | "referrer-policy": [ | |||
|
2347 | "no-referrer" | |||
|
2348 | ], | |||
|
2349 | "server": [ | |||
|
2350 | "Apache/2.4.10 (Debian)" | |||
|
2351 | ], | |||
|
2352 | "cache-control": [ | |||
|
2353 | "no-store" | |||
|
2354 | ] | |||
|
2355 | }, | |||
|
2356 | "status": { | |||
|
2357 | "code": 200, | |||
|
2358 | "message": "OK" | |||
|
2359 | }, | |||
|
2360 | "body": { | |||
|
2361 | "string": "{\"result\":{\"diffid\":20256,\"phid\":\"PHID-DIFF-mmdclcfxghe4byjqhaaa\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/differential\\/diff\\/20256\\/\"},\"error_code\":null,\"error_info\":null}" | |||
|
2362 | } | |||
|
2363 | }, | |||
|
2364 | "request": { | |||
|
2365 | "headers": { | |||
|
2366 | "content-length": [ | |||
|
2367 | "1747" | |||
|
2368 | ], | |||
|
2369 | "host": [ | |||
|
2370 | "phab.mercurial-scm.org" | |||
|
2371 | ], | |||
|
2372 | "content-type": [ | |||
|
2373 | "application/x-www-form-urlencoded" | |||
|
2374 | ], | |||
|
2375 | "user-agent": [ | |||
|
2376 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
2377 | ], | |||
|
2378 | "accept": [ | |||
|
2379 | "application/mercurial-0.1" | |||
|
2380 | ] | |||
|
2381 | }, | |||
|
2382 | "uri": "https://phab.mercurial-scm.org//api/differential.creatediff", | |||
|
2383 | "method": "POST", | |||
|
2384 | "body": "output=json&__conduit__=1¶ms=%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%22bin2_moved%22%3A+%7B%22addLines%22%3A+0%2C+%22awayPaths%22%3A+%5B%5D%2C+%22commitHash%22%3A+null%2C+%22currentPath%22%3A+%22bin2_moved%22%2C+%22delLines%22%3A+0%2C+%22fileType%22%3A+3%2C+%22hunks%22%3A+%5B%5D%2C+%22metadata%22%3A+%7B%22new%3Abinary-phid%22%3A+%22PHID-FILE-p7kxwaunxwqcsfp2mcai%22%2C+%22new%3Afile%3Asize%22%3A+13%2C+%22old%3Abinary-phid%22%3A+%22PHID-FILE-erukut7iaaxa6anzphgx%22%2C+%22old%3Afile%3Asize%22%3A+2%7D%2C+%22newProperties%22%3A+%7B%7D%2C+%22oldPath%22%3A+%22bin2_moved%22%2C+%22oldProperties%22%3A+%7B%7D%2C+%22type%22%3A+2%7D%2C+%22bin2_moved_copied%22%3A+%7B%22addLines%22%3A+0%2C+%22awayPaths%22%3A+%5B%5D%2C+%22commitHash%22%3A+null%2C+%22currentPath%22%3A+%22bin2_moved_copied%22%2C+%22delLines%22%3A+0%2C+%22fileType%22%3A+3%2C+%22hunks%22%3A+%5B%5D%2C+%22metadata%22%3A+%7B%22new%3Abinary-phid%22%3A+%22PHID-FILE-2kkudzwg35maqlp4mkdz%22%2C+%22new%3Afile%3Asize%22%3A+12%2C+%22old%3Abinary-phid%22%3A+%22PHID-FILE-cj76tzcs23ob2jmkoscw%22%2C+%22old%3Afile%3Asize%22%3A+2%7D%2C+%22newProperties%22%3A+%7B%7D%2C+%22oldPath%22%3A+%22bin2_moved%22%2C+%22oldProperties%22%3A+%7B%7D%2C+%22type%22%3A+7%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+%2225f766b50cc23ba2f44ed3bc707b429cdf0186e8%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" | |||
|
2385 | } | |||
|
2386 | }, | |||
|
2387 | { | |||
|
2388 | "response": { | |||
|
2389 | "headers": { | |||
|
2390 | "date": [ | |||
|
2391 | "Tue, 18 Feb 2020 18:43:28 GMT" | |||
|
2392 | ], | |||
|
2393 | "strict-transport-security": [ | |||
|
2394 | "max-age=0; includeSubdomains; preload" | |||
|
2395 | ], | |||
|
2396 | "x-frame-options": [ | |||
|
2397 | "Deny" | |||
|
2398 | ], | |||
|
2399 | "content-type": [ | |||
|
2400 | "application/json" | |||
|
2401 | ], | |||
|
2402 | "transfer-encoding": [ | |||
|
2403 | "chunked" | |||
|
2404 | ], | |||
|
2405 | "x-xss-protection": [ | |||
|
2406 | "1; mode=block" | |||
|
2407 | ], | |||
|
2408 | "expires": [ | |||
|
2409 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
2410 | ], | |||
|
2411 | "x-content-type-options": [ | |||
|
2412 | "nosniff" | |||
|
2413 | ], | |||
|
2414 | "referrer-policy": [ | |||
|
2415 | "no-referrer" | |||
|
2416 | ], | |||
|
2417 | "server": [ | |||
|
2418 | "Apache/2.4.10 (Debian)" | |||
|
2419 | ], | |||
|
2420 | "cache-control": [ | |||
|
2421 | "no-store" | |||
|
2422 | ] | |||
|
2423 | }, | |||
|
2424 | "status": { | |||
|
2425 | "code": 200, | |||
|
2426 | "message": "OK" | |||
|
2427 | }, | |||
|
2428 | "body": { | |||
|
2429 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
2430 | } | |||
|
2431 | }, | |||
|
2432 | "request": { | |||
|
2433 | "headers": { | |||
|
2434 | "content-length": [ | |||
|
2435 | "482" | |||
|
2436 | ], | |||
|
2437 | "host": [ | |||
|
2438 | "phab.mercurial-scm.org" | |||
|
2439 | ], | |||
|
2440 | "content-type": [ | |||
|
2441 | "application/x-www-form-urlencoded" | |||
|
2442 | ], | |||
|
2443 | "user-agent": [ | |||
|
2444 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
2445 | ], | |||
|
2446 | "accept": [ | |||
|
2447 | "application/mercurial-0.1" | |||
|
2448 | ] | |||
|
2449 | }, | |||
|
2450 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
2451 | "method": "POST", | |||
|
2452 | "body": "output=json&__conduit__=1¶ms=%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%221b87b363a5e4ce4cd1f05d8518eee1f55cf31919%5C%22%2C+%5C%22parent%5C%22%3A+%5C%2225f766b50cc23ba2f44ed3bc707b429cdf0186e8%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+20256%2C+%22name%22%3A+%22hg%3Ameta%22%7D" | |||
|
2453 | } | |||
|
2454 | }, | |||
|
2455 | { | |||
|
2456 | "response": { | |||
|
2457 | "headers": { | |||
|
2458 | "date": [ | |||
|
2459 | "Tue, 18 Feb 2020 18:43:28 GMT" | |||
|
2460 | ], | |||
|
2461 | "strict-transport-security": [ | |||
|
2462 | "max-age=0; includeSubdomains; preload" | |||
|
2463 | ], | |||
|
2464 | "x-frame-options": [ | |||
|
2465 | "Deny" | |||
|
2466 | ], | |||
|
2467 | "content-type": [ | |||
|
2468 | "application/json" | |||
|
2469 | ], | |||
|
2470 | "transfer-encoding": [ | |||
|
2471 | "chunked" | |||
|
2472 | ], | |||
|
2473 | "x-xss-protection": [ | |||
|
2474 | "1; mode=block" | |||
|
2475 | ], | |||
|
2476 | "expires": [ | |||
|
2477 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
2478 | ], | |||
|
2479 | "x-content-type-options": [ | |||
|
2480 | "nosniff" | |||
|
2481 | ], | |||
|
2482 | "referrer-policy": [ | |||
|
2483 | "no-referrer" | |||
|
2484 | ], | |||
|
2485 | "server": [ | |||
|
2486 | "Apache/2.4.10 (Debian)" | |||
|
2487 | ], | |||
|
2488 | "cache-control": [ | |||
|
2489 | "no-store" | |||
|
2490 | ] | |||
|
2491 | }, | |||
|
2492 | "status": { | |||
|
2493 | "code": 200, | |||
|
2494 | "message": "OK" | |||
|
2495 | }, | |||
|
2496 | "body": { | |||
|
2497 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
2498 | } | |||
|
2499 | }, | |||
|
2500 | "request": { | |||
|
2501 | "headers": { | |||
|
2502 | "content-length": [ | |||
|
2503 | "594" | |||
|
2504 | ], | |||
|
2505 | "host": [ | |||
|
2506 | "phab.mercurial-scm.org" | |||
|
2507 | ], | |||
|
2508 | "content-type": [ | |||
|
2509 | "application/x-www-form-urlencoded" | |||
|
2510 | ], | |||
|
2511 | "user-agent": [ | |||
|
2512 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
2513 | ], | |||
|
2514 | "accept": [ | |||
|
2515 | "application/mercurial-0.1" | |||
|
2516 | ] | |||
|
2517 | }, | |||
|
2518 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
2519 | "method": "POST", | |||
|
2520 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%221b87b363a5e4ce4cd1f05d8518eee1f55cf31919%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%221b87b363a5e4ce4cd1f05d8518eee1f55cf31919%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%2225f766b50cc23ba2f44ed3bc707b429cdf0186e8%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+20256%2C+%22name%22%3A+%22local%3Acommits%22%7D" | |||
|
2521 | } | |||
|
2522 | }, | |||
|
2523 | { | |||
|
2524 | "response": { | |||
|
2525 | "headers": { | |||
|
2526 | "date": [ | |||
|
2527 | "Tue, 18 Feb 2020 18:43:28 GMT" | |||
|
2528 | ], | |||
|
2529 | "strict-transport-security": [ | |||
|
2530 | "max-age=0; includeSubdomains; preload" | |||
|
2531 | ], | |||
|
2532 | "x-frame-options": [ | |||
|
2533 | "Deny" | |||
|
2534 | ], | |||
|
2535 | "content-type": [ | |||
|
2536 | "application/json" | |||
|
2537 | ], | |||
|
2538 | "transfer-encoding": [ | |||
|
2539 | "chunked" | |||
|
2540 | ], | |||
|
2541 | "x-xss-protection": [ | |||
|
2542 | "1; mode=block" | |||
|
2543 | ], | |||
|
2544 | "expires": [ | |||
|
2545 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
2546 | ], | |||
|
2547 | "x-content-type-options": [ | |||
|
2548 | "nosniff" | |||
|
2549 | ], | |||
|
2550 | "referrer-policy": [ | |||
|
2551 | "no-referrer" | |||
|
2552 | ], | |||
|
2553 | "server": [ | |||
|
2554 | "Apache/2.4.10 (Debian)" | |||
|
2555 | ], | |||
|
2556 | "cache-control": [ | |||
|
2557 | "no-store" | |||
|
2558 | ] | |||
|
2559 | }, | |||
|
2560 | "status": { | |||
|
2561 | "code": 200, | |||
|
2562 | "message": "OK" | |||
|
2563 | }, | |||
|
2564 | "body": { | |||
|
2565 | "string": "{\"result\":{\"errors\":[],\"fields\":{\"title\":\"copy+mod moved binary\"},\"revisionIDFieldInfo\":{\"value\":null,\"validDomain\":\"https:\\/\\/phab.mercurial-scm.org\"},\"transactions\":[{\"type\":\"title\",\"value\":\"copy+mod moved binary\"}]},\"error_code\":null,\"error_info\":null}" | |||
|
2566 | } | |||
|
2567 | }, | |||
|
2568 | "request": { | |||
|
2569 | "headers": { | |||
|
2570 | "content-length": [ | |||
|
2571 | "168" | |||
|
2572 | ], | |||
|
2573 | "host": [ | |||
|
2574 | "phab.mercurial-scm.org" | |||
|
2575 | ], | |||
|
2576 | "content-type": [ | |||
|
2577 | "application/x-www-form-urlencoded" | |||
|
2578 | ], | |||
|
2579 | "user-agent": [ | |||
|
2580 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
2581 | ], | |||
|
2582 | "accept": [ | |||
|
2583 | "application/mercurial-0.1" | |||
|
2584 | ] | |||
|
2585 | }, | |||
|
2586 | "uri": "https://phab.mercurial-scm.org//api/differential.parsecommitmessage", | |||
|
2587 | "method": "POST", | |||
|
2588 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22corpus%22%3A+%22copy%2Bmod+moved+binary%22%7D" | |||
|
2589 | } | |||
|
2590 | }, | |||
|
2591 | { | |||
|
2592 | "response": { | |||
|
2593 | "headers": { | |||
|
2594 | "date": [ | |||
|
2595 | "Tue, 18 Feb 2020 18:43:29 GMT" | |||
|
2596 | ], | |||
|
2597 | "strict-transport-security": [ | |||
|
2598 | "max-age=0; includeSubdomains; preload" | |||
|
2599 | ], | |||
|
2600 | "x-frame-options": [ | |||
|
2601 | "Deny" | |||
|
2602 | ], | |||
|
2603 | "content-type": [ | |||
|
2604 | "application/json" | |||
|
2605 | ], | |||
|
2606 | "transfer-encoding": [ | |||
|
2607 | "chunked" | |||
|
2608 | ], | |||
|
2609 | "x-xss-protection": [ | |||
|
2610 | "1; mode=block" | |||
|
2611 | ], | |||
|
2612 | "expires": [ | |||
|
2613 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
2614 | ], | |||
|
2615 | "x-content-type-options": [ | |||
|
2616 | "nosniff" | |||
|
2617 | ], | |||
|
2618 | "referrer-policy": [ | |||
|
2619 | "no-referrer" | |||
|
2620 | ], | |||
|
2621 | "server": [ | |||
|
2622 | "Apache/2.4.10 (Debian)" | |||
|
2623 | ], | |||
|
2624 | "cache-control": [ | |||
|
2625 | "no-store" | |||
|
2626 | ] | |||
|
2627 | }, | |||
|
2628 | "status": { | |||
|
2629 | "code": 200, | |||
|
2630 | "message": "OK" | |||
|
2631 | }, | |||
|
2632 | "body": { | |||
|
2633 | "string": "{\"result\":{\"object\":{\"id\":8132,\"phid\":\"PHID-DREV-mdf64bhsna5jfl6nothr\"},\"transactions\":[{\"phid\":\"PHID-XACT-DREV-4svgehxywd53uj7\"},{\"phid\":\"PHID-XACT-DREV-px4562fvi7lu6p5\"},{\"phid\":\"PHID-XACT-DREV-jambzs7kliwaca4\"},{\"phid\":\"PHID-XACT-DREV-6znwvwbrhzqepz3\"},{\"phid\":\"PHID-XACT-DREV-v52eykrda6sbtzo\"},{\"phid\":\"PHID-XACT-DREV-bvqyk7zylod5us6\"}]},\"error_code\":null,\"error_info\":null}" | |||
|
2634 | } | |||
|
2635 | }, | |||
|
2636 | "request": { | |||
|
2637 | "headers": { | |||
|
2638 | "content-length": [ | |||
|
2639 | "423" | |||
|
2640 | ], | |||
|
2641 | "host": [ | |||
|
2642 | "phab.mercurial-scm.org" | |||
|
2643 | ], | |||
|
2644 | "content-type": [ | |||
|
2645 | "application/x-www-form-urlencoded" | |||
|
2646 | ], | |||
|
2647 | "user-agent": [ | |||
|
2648 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
2649 | ], | |||
|
2650 | "accept": [ | |||
|
2651 | "application/mercurial-0.1" | |||
|
2652 | ] | |||
|
2653 | }, | |||
|
2654 | "uri": "https://phab.mercurial-scm.org//api/differential.revision.edit", | |||
|
2655 | "method": "POST", | |||
|
2656 | "body": "output=json&__conduit__=1¶ms=%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-mmdclcfxghe4byjqhaaa%22%7D%2C+%7B%22type%22%3A+%22parents.set%22%2C+%22value%22%3A+%5B%22PHID-DREV-hcbzjqg4xhb6thfuhkgp%22%5D%7D%2C+%7B%22type%22%3A+%22title%22%2C+%22value%22%3A+%22copy%2Bmod+moved+binary%22%7D%5D%7D" | |||
|
2657 | } | |||
|
2658 | }, | |||
|
2659 | { | |||
|
2660 | "response": { | |||
|
2661 | "headers": { | |||
|
2662 | "date": [ | |||
|
2663 | "Tue, 18 Feb 2020 18:43:30 GMT" | |||
|
2664 | ], | |||
|
2665 | "strict-transport-security": [ | |||
|
2666 | "max-age=0; includeSubdomains; preload" | |||
|
2667 | ], | |||
|
2668 | "x-frame-options": [ | |||
|
2669 | "Deny" | |||
|
2670 | ], | |||
|
2671 | "content-type": [ | |||
|
2672 | "application/json" | |||
|
2673 | ], | |||
|
2674 | "transfer-encoding": [ | |||
|
2675 | "chunked" | |||
|
2676 | ], | |||
|
2677 | "x-xss-protection": [ | |||
|
2678 | "1; mode=block" | |||
|
2679 | ], | |||
|
2680 | "connection": [ | |||
|
2681 | "close" | |||
|
2682 | ], | |||
|
2683 | "expires": [ | |||
|
2684 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
2685 | ], | |||
|
2686 | "x-content-type-options": [ | |||
|
2687 | "nosniff" | |||
|
2688 | ], | |||
|
2689 | "referrer-policy": [ | |||
|
2690 | "no-referrer" | |||
|
2691 | ], | |||
|
2692 | "server": [ | |||
|
2693 | "Apache/2.4.10 (Debian)" | |||
|
2694 | ], | |||
|
2695 | "cache-control": [ | |||
|
2696 | "no-store" | |||
|
2697 | ] | |||
|
2698 | }, | |||
|
2699 | "status": { | |||
|
2700 | "code": 200, | |||
|
2701 | "message": "OK" | |||
|
2702 | }, | |||
|
2703 | "body": { | |||
|
2704 | "string": "{\"result\":[{\"id\":\"8132\",\"phid\":\"PHID-DREV-mdf64bhsna5jfl6nothr\",\"title\":\"copy+mod moved binary\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/D8132\",\"dateCreated\":\"1582051409\",\"dateModified\":\"1582051409\",\"authorPHID\":\"PHID-USER-tzhaient733lwrlbcag5\",\"status\":\"0\",\"statusName\":\"Needs Review\",\"properties\":{\"draft.broadcast\":true,\"lines.added\":0,\"lines.removed\":0},\"branch\":\"default\",\"summary\":\"\",\"testPlan\":\"\",\"lineCount\":\"0\",\"activeDiffPHID\":\"PHID-DIFF-mmdclcfxghe4byjqhaaa\",\"diffs\":[\"20256\"],\"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-hcbzjqg4xhb6thfuhkgp\"]},\"repositoryPHID\":\"PHID-REPO-bvunnehri4u2isyr7bc3\",\"sourcePath\":\"\\/\"},{\"id\":\"8131\",\"phid\":\"PHID-DREV-hcbzjqg4xhb6thfuhkgp\",\"title\":\"move+mod copied binary\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/D8131\",\"dateCreated\":\"1582051404\",\"dateModified\":\"1582051409\",\"authorPHID\":\"PHID-USER-tzhaient733lwrlbcag5\",\"status\":\"0\",\"statusName\":\"Needs Review\",\"properties\":{\"draft.broadcast\":true,\"lines.added\":0,\"lines.removed\":0},\"branch\":\"default\",\"summary\":\"\",\"testPlan\":\"\",\"lineCount\":\"0\",\"activeDiffPHID\":\"PHID-DIFF-euvajw4uojheyjmhqmav\",\"diffs\":[\"20255\"],\"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-fqgsuuwbzvaodvaeengd\"]},\"repositoryPHID\":\"PHID-REPO-bvunnehri4u2isyr7bc3\",\"sourcePath\":\"\\/\"},{\"id\":\"8130\",\"phid\":\"PHID-DREV-fqgsuuwbzvaodvaeengd\",\"title\":\"copied binary\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/D8130\",\"dateCreated\":\"1582051400\",\"dateModified\":\"1582051404\",\"authorPHID\":\"PHID-USER-tzhaient733lwrlbcag5\",\"status\":\"0\",\"statusName\":\"Needs Review\",\"properties\":{\"draft.broadcast\":true,\"lines.added\":0,\"lines.removed\":0},\"branch\":\"default\",\"summary\":\"\",\"testPlan\":\"\",\"lineCount\":\"0\",\"activeDiffPHID\":\"PHID-DIFF-g7zeghdg2dzimfldfk5b\",\"diffs\":[\"20254\"],\"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-s74qwcmdszxugly5fjru\"]},\"repositoryPHID\":\"PHID-REPO-bvunnehri4u2isyr7bc3\",\"sourcePath\":\"\\/\"},{\"id\":\"8129\",\"phid\":\"PHID-DREV-s74qwcmdszxugly5fjru\",\"title\":\"moved binary\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/D8129\",\"dateCreated\":\"1582051397\",\"dateModified\":\"1582051400\",\"authorPHID\":\"PHID-USER-tzhaient733lwrlbcag5\",\"status\":\"0\",\"statusName\":\"Needs Review\",\"properties\":{\"draft.broadcast\":true,\"lines.added\":0,\"lines.removed\":0},\"branch\":\"default\",\"summary\":\"\",\"testPlan\":\"\",\"lineCount\":\"0\",\"activeDiffPHID\":\"PHID-DIFF-svria2kxhgr63qwpdzzb\",\"diffs\":[\"20253\"],\"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-ebpbxa27h5ibeudclsse\"]},\"repositoryPHID\":\"PHID-REPO-bvunnehri4u2isyr7bc3\",\"sourcePath\":\"\\/\"},{\"id\":\"8128\",\"phid\":\"PHID-DREV-ebpbxa27h5ibeudclsse\",\"title\":\"add another binary\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/D8128\",\"dateCreated\":\"1582051394\",\"dateModified\":\"1582051397\",\"authorPHID\":\"PHID-USER-tzhaient733lwrlbcag5\",\"status\":\"0\",\"statusName\":\"Needs Review\",\"properties\":{\"draft.broadcast\":true,\"lines.added\":0,\"lines.removed\":0},\"branch\":\"default\",\"summary\":\"\",\"testPlan\":\"\",\"lineCount\":\"0\",\"activeDiffPHID\":\"PHID-DIFF-haue3qqytoovnmb7orw6\",\"diffs\":[\"20252\"],\"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}" | |||
|
2705 | } | |||
|
2706 | }, | |||
|
2707 | "request": { | |||
|
2708 | "headers": { | |||
|
2709 | "content-length": [ | |||
|
2710 | "178" | |||
|
2711 | ], | |||
|
2712 | "host": [ | |||
|
2713 | "phab.mercurial-scm.org" | |||
|
2714 | ], | |||
|
2715 | "content-type": [ | |||
|
2716 | "application/x-www-form-urlencoded" | |||
|
2717 | ], | |||
|
2718 | "user-agent": [ | |||
|
2719 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
2720 | ], | |||
|
2721 | "accept": [ | |||
|
2722 | "application/mercurial-0.1" | |||
|
2723 | ] | |||
|
2724 | }, | |||
|
2725 | "uri": "https://phab.mercurial-scm.org//api/differential.query", | |||
|
2726 | "method": "POST", | |||
|
2727 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22ids%22%3A+%5B8128%2C+8129%2C+8130%2C+8131%2C+8132%5D%7D" | |||
|
2728 | } | |||
|
2729 | }, | |||
|
2730 | { | |||
|
2731 | "response": { | |||
|
2732 | "headers": { | |||
|
2733 | "date": [ | |||
|
2734 | "Tue, 18 Feb 2020 18:43:30 GMT" | |||
|
2735 | ], | |||
|
2736 | "strict-transport-security": [ | |||
|
2737 | "max-age=0; includeSubdomains; preload" | |||
|
2738 | ], | |||
|
2739 | "x-frame-options": [ | |||
|
2740 | "Deny" | |||
|
2741 | ], | |||
|
2742 | "content-type": [ | |||
|
2743 | "application/json" | |||
|
2744 | ], | |||
|
2745 | "transfer-encoding": [ | |||
|
2746 | "chunked" | |||
|
2747 | ], | |||
|
2748 | "x-xss-protection": [ | |||
|
2749 | "1; mode=block" | |||
|
2750 | ], | |||
|
2751 | "expires": [ | |||
|
2752 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
2753 | ], | |||
|
2754 | "x-content-type-options": [ | |||
|
2755 | "nosniff" | |||
|
2756 | ], | |||
|
2757 | "referrer-policy": [ | |||
|
2758 | "no-referrer" | |||
|
2759 | ], | |||
|
2760 | "server": [ | |||
|
2761 | "Apache/2.4.10 (Debian)" | |||
|
2762 | ], | |||
|
2763 | "cache-control": [ | |||
|
2764 | "no-store" | |||
|
2765 | ] | |||
|
2766 | }, | |||
|
2767 | "status": { | |||
|
2768 | "code": 200, | |||
|
2769 | "message": "OK" | |||
|
2770 | }, | |||
|
2771 | "body": { | |||
|
2772 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
2773 | } | |||
|
2774 | }, | |||
|
2775 | "request": { | |||
|
2776 | "headers": { | |||
|
2777 | "content-length": [ | |||
|
2778 | "482" | |||
|
2779 | ], | |||
|
2780 | "host": [ | |||
|
2781 | "phab.mercurial-scm.org" | |||
|
2782 | ], | |||
|
2783 | "content-type": [ | |||
|
2784 | "application/x-www-form-urlencoded" | |||
|
2785 | ], | |||
|
2786 | "user-agent": [ | |||
|
2787 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
2788 | ], | |||
|
2789 | "accept": [ | |||
|
2790 | "application/mercurial-0.1" | |||
|
2791 | ] | |||
|
2792 | }, | |||
|
2793 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
2794 | "method": "POST", | |||
|
2795 | "body": "output=json&__conduit__=1¶ms=%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%2290437c20312a3ff63f2da1c52a0f1b18da2212c1%5C%22%2C+%5C%22parent%5C%22%3A+%5C%2275dbbc901145d7beb190197aa232f74540e5a9f3%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+20252%2C+%22name%22%3A+%22hg%3Ameta%22%7D" | |||
|
2796 | } | |||
|
2797 | }, | |||
|
2798 | { | |||
|
2799 | "response": { | |||
|
2800 | "headers": { | |||
|
2801 | "date": [ | |||
|
2802 | "Tue, 18 Feb 2020 18:43:30 GMT" | |||
|
2803 | ], | |||
|
2804 | "strict-transport-security": [ | |||
|
2805 | "max-age=0; includeSubdomains; preload" | |||
|
2806 | ], | |||
|
2807 | "x-frame-options": [ | |||
|
2808 | "Deny" | |||
|
2809 | ], | |||
|
2810 | "content-type": [ | |||
|
2811 | "application/json" | |||
|
2812 | ], | |||
|
2813 | "transfer-encoding": [ | |||
|
2814 | "chunked" | |||
|
2815 | ], | |||
|
2816 | "x-xss-protection": [ | |||
|
2817 | "1; mode=block" | |||
|
2818 | ], | |||
|
2819 | "expires": [ | |||
|
2820 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
2821 | ], | |||
|
2822 | "x-content-type-options": [ | |||
|
2823 | "nosniff" | |||
|
2824 | ], | |||
|
2825 | "referrer-policy": [ | |||
|
2826 | "no-referrer" | |||
|
2827 | ], | |||
|
2828 | "server": [ | |||
|
2829 | "Apache/2.4.10 (Debian)" | |||
|
2830 | ], | |||
|
2831 | "cache-control": [ | |||
|
2832 | "no-store" | |||
|
2833 | ] | |||
|
2834 | }, | |||
|
2835 | "status": { | |||
|
2836 | "code": 200, | |||
|
2837 | "message": "OK" | |||
|
2838 | }, | |||
|
2839 | "body": { | |||
|
2840 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
2841 | } | |||
|
2842 | }, | |||
|
2843 | "request": { | |||
|
2844 | "headers": { | |||
|
2845 | "content-length": [ | |||
|
2846 | "594" | |||
|
2847 | ], | |||
|
2848 | "host": [ | |||
|
2849 | "phab.mercurial-scm.org" | |||
|
2850 | ], | |||
|
2851 | "content-type": [ | |||
|
2852 | "application/x-www-form-urlencoded" | |||
|
2853 | ], | |||
|
2854 | "user-agent": [ | |||
|
2855 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
2856 | ], | |||
|
2857 | "accept": [ | |||
|
2858 | "application/mercurial-0.1" | |||
|
2859 | ] | |||
|
2860 | }, | |||
|
2861 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
2862 | "method": "POST", | |||
|
2863 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%2290437c20312a3ff63f2da1c52a0f1b18da2212c1%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%2290437c20312a3ff63f2da1c52a0f1b18da2212c1%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%2275dbbc901145d7beb190197aa232f74540e5a9f3%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+20252%2C+%22name%22%3A+%22local%3Acommits%22%7D" | |||
|
2864 | } | |||
|
2865 | }, | |||
|
2866 | { | |||
|
2867 | "response": { | |||
|
2868 | "headers": { | |||
|
2869 | "date": [ | |||
|
2870 | "Tue, 18 Feb 2020 18:43:31 GMT" | |||
|
2871 | ], | |||
|
2872 | "strict-transport-security": [ | |||
|
2873 | "max-age=0; includeSubdomains; preload" | |||
|
2874 | ], | |||
|
2875 | "x-frame-options": [ | |||
|
2876 | "Deny" | |||
|
2877 | ], | |||
|
2878 | "content-type": [ | |||
|
2879 | "application/json" | |||
|
2880 | ], | |||
|
2881 | "transfer-encoding": [ | |||
|
2882 | "chunked" | |||
|
2883 | ], | |||
|
2884 | "x-xss-protection": [ | |||
|
2885 | "1; mode=block" | |||
|
2886 | ], | |||
|
2887 | "expires": [ | |||
|
2888 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
2889 | ], | |||
|
2890 | "x-content-type-options": [ | |||
|
2891 | "nosniff" | |||
|
2892 | ], | |||
|
2893 | "referrer-policy": [ | |||
|
2894 | "no-referrer" | |||
|
2895 | ], | |||
|
2896 | "server": [ | |||
|
2897 | "Apache/2.4.10 (Debian)" | |||
|
2898 | ], | |||
|
2899 | "cache-control": [ | |||
|
2900 | "no-store" | |||
|
2901 | ] | |||
|
2902 | }, | |||
|
2903 | "status": { | |||
|
2904 | "code": 200, | |||
|
2905 | "message": "OK" | |||
|
2906 | }, | |||
|
2907 | "body": { | |||
|
2908 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
2909 | } | |||
|
2910 | }, | |||
|
2911 | "request": { | |||
|
2912 | "headers": { | |||
|
2913 | "content-length": [ | |||
|
2914 | "482" | |||
|
2915 | ], | |||
|
2916 | "host": [ | |||
|
2917 | "phab.mercurial-scm.org" | |||
|
2918 | ], | |||
|
2919 | "content-type": [ | |||
|
2920 | "application/x-www-form-urlencoded" | |||
|
2921 | ], | |||
|
2922 | "user-agent": [ | |||
|
2923 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
2924 | ], | |||
|
2925 | "accept": [ | |||
|
2926 | "application/mercurial-0.1" | |||
|
2927 | ] | |||
|
2928 | }, | |||
|
2929 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
2930 | "method": "POST", | |||
|
2931 | "body": "output=json&__conduit__=1¶ms=%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%22f391f4da4c61a54562f5e3dce83ea7f7bd38be31%5C%22%2C+%5C%22parent%5C%22%3A+%5C%2290437c20312a3ff63f2da1c52a0f1b18da2212c1%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+20253%2C+%22name%22%3A+%22hg%3Ameta%22%7D" | |||
|
2932 | } | |||
|
2933 | }, | |||
|
2934 | { | |||
|
2935 | "response": { | |||
|
2936 | "headers": { | |||
|
2937 | "date": [ | |||
|
2938 | "Tue, 18 Feb 2020 18:43:31 GMT" | |||
|
2939 | ], | |||
|
2940 | "strict-transport-security": [ | |||
|
2941 | "max-age=0; includeSubdomains; preload" | |||
|
2942 | ], | |||
|
2943 | "x-frame-options": [ | |||
|
2944 | "Deny" | |||
|
2945 | ], | |||
|
2946 | "content-type": [ | |||
|
2947 | "application/json" | |||
|
2948 | ], | |||
|
2949 | "transfer-encoding": [ | |||
|
2950 | "chunked" | |||
|
2951 | ], | |||
|
2952 | "x-xss-protection": [ | |||
|
2953 | "1; mode=block" | |||
|
2954 | ], | |||
|
2955 | "expires": [ | |||
|
2956 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
2957 | ], | |||
|
2958 | "x-content-type-options": [ | |||
|
2959 | "nosniff" | |||
|
2960 | ], | |||
|
2961 | "referrer-policy": [ | |||
|
2962 | "no-referrer" | |||
|
2963 | ], | |||
|
2964 | "server": [ | |||
|
2965 | "Apache/2.4.10 (Debian)" | |||
|
2966 | ], | |||
|
2967 | "cache-control": [ | |||
|
2968 | "no-store" | |||
|
2969 | ] | |||
|
2970 | }, | |||
|
2971 | "status": { | |||
|
2972 | "code": 200, | |||
|
2973 | "message": "OK" | |||
|
2974 | }, | |||
|
2975 | "body": { | |||
|
2976 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
2977 | } | |||
|
2978 | }, | |||
|
2979 | "request": { | |||
|
2980 | "headers": { | |||
|
2981 | "content-length": [ | |||
|
2982 | "594" | |||
|
2983 | ], | |||
|
2984 | "host": [ | |||
|
2985 | "phab.mercurial-scm.org" | |||
|
2986 | ], | |||
|
2987 | "content-type": [ | |||
|
2988 | "application/x-www-form-urlencoded" | |||
|
2989 | ], | |||
|
2990 | "user-agent": [ | |||
|
2991 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
2992 | ], | |||
|
2993 | "accept": [ | |||
|
2994 | "application/mercurial-0.1" | |||
|
2995 | ] | |||
|
2996 | }, | |||
|
2997 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
2998 | "method": "POST", | |||
|
2999 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%22f391f4da4c61a54562f5e3dce83ea7f7bd38be31%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%22f391f4da4c61a54562f5e3dce83ea7f7bd38be31%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%2290437c20312a3ff63f2da1c52a0f1b18da2212c1%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+20253%2C+%22name%22%3A+%22local%3Acommits%22%7D" | |||
|
3000 | } | |||
|
3001 | }, | |||
|
3002 | { | |||
|
3003 | "response": { | |||
|
3004 | "headers": { | |||
|
3005 | "date": [ | |||
|
3006 | "Tue, 18 Feb 2020 18:43:32 GMT" | |||
|
3007 | ], | |||
|
3008 | "strict-transport-security": [ | |||
|
3009 | "max-age=0; includeSubdomains; preload" | |||
|
3010 | ], | |||
|
3011 | "x-frame-options": [ | |||
|
3012 | "Deny" | |||
|
3013 | ], | |||
|
3014 | "content-type": [ | |||
|
3015 | "application/json" | |||
|
3016 | ], | |||
|
3017 | "transfer-encoding": [ | |||
|
3018 | "chunked" | |||
|
3019 | ], | |||
|
3020 | "x-xss-protection": [ | |||
|
3021 | "1; mode=block" | |||
|
3022 | ], | |||
|
3023 | "expires": [ | |||
|
3024 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
3025 | ], | |||
|
3026 | "x-content-type-options": [ | |||
|
3027 | "nosniff" | |||
|
3028 | ], | |||
|
3029 | "referrer-policy": [ | |||
|
3030 | "no-referrer" | |||
|
3031 | ], | |||
|
3032 | "server": [ | |||
|
3033 | "Apache/2.4.10 (Debian)" | |||
|
3034 | ], | |||
|
3035 | "cache-control": [ | |||
|
3036 | "no-store" | |||
|
3037 | ] | |||
|
3038 | }, | |||
|
3039 | "status": { | |||
|
3040 | "code": 200, | |||
|
3041 | "message": "OK" | |||
|
3042 | }, | |||
|
3043 | "body": { | |||
|
3044 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
3045 | } | |||
|
3046 | }, | |||
|
3047 | "request": { | |||
|
3048 | "headers": { | |||
|
3049 | "content-length": [ | |||
|
3050 | "482" | |||
|
3051 | ], | |||
|
3052 | "host": [ | |||
|
3053 | "phab.mercurial-scm.org" | |||
|
3054 | ], | |||
|
3055 | "content-type": [ | |||
|
3056 | "application/x-www-form-urlencoded" | |||
|
3057 | ], | |||
|
3058 | "user-agent": [ | |||
|
3059 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
3060 | ], | |||
|
3061 | "accept": [ | |||
|
3062 | "application/mercurial-0.1" | |||
|
3063 | ] | |||
|
3064 | }, | |||
|
3065 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
3066 | "method": "POST", | |||
|
3067 | "body": "output=json&__conduit__=1¶ms=%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%22da86a9f3268c2bcd03a0efc5ef8f2171e3e741fc%5C%22%2C+%5C%22parent%5C%22%3A+%5C%22f391f4da4c61a54562f5e3dce83ea7f7bd38be31%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+20254%2C+%22name%22%3A+%22hg%3Ameta%22%7D" | |||
|
3068 | } | |||
|
3069 | }, | |||
|
3070 | { | |||
|
3071 | "response": { | |||
|
3072 | "headers": { | |||
|
3073 | "date": [ | |||
|
3074 | "Tue, 18 Feb 2020 18:43:32 GMT" | |||
|
3075 | ], | |||
|
3076 | "strict-transport-security": [ | |||
|
3077 | "max-age=0; includeSubdomains; preload" | |||
|
3078 | ], | |||
|
3079 | "x-frame-options": [ | |||
|
3080 | "Deny" | |||
|
3081 | ], | |||
|
3082 | "content-type": [ | |||
|
3083 | "application/json" | |||
|
3084 | ], | |||
|
3085 | "transfer-encoding": [ | |||
|
3086 | "chunked" | |||
|
3087 | ], | |||
|
3088 | "x-xss-protection": [ | |||
|
3089 | "1; mode=block" | |||
|
3090 | ], | |||
|
3091 | "expires": [ | |||
|
3092 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
3093 | ], | |||
|
3094 | "x-content-type-options": [ | |||
|
3095 | "nosniff" | |||
|
3096 | ], | |||
|
3097 | "referrer-policy": [ | |||
|
3098 | "no-referrer" | |||
|
3099 | ], | |||
|
3100 | "server": [ | |||
|
3101 | "Apache/2.4.10 (Debian)" | |||
|
3102 | ], | |||
|
3103 | "cache-control": [ | |||
|
3104 | "no-store" | |||
|
3105 | ] | |||
|
3106 | }, | |||
|
3107 | "status": { | |||
|
3108 | "code": 200, | |||
|
3109 | "message": "OK" | |||
|
3110 | }, | |||
|
3111 | "body": { | |||
|
3112 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
3113 | } | |||
|
3114 | }, | |||
|
3115 | "request": { | |||
|
3116 | "headers": { | |||
|
3117 | "content-length": [ | |||
|
3118 | "594" | |||
|
3119 | ], | |||
|
3120 | "host": [ | |||
|
3121 | "phab.mercurial-scm.org" | |||
|
3122 | ], | |||
|
3123 | "content-type": [ | |||
|
3124 | "application/x-www-form-urlencoded" | |||
|
3125 | ], | |||
|
3126 | "user-agent": [ | |||
|
3127 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
3128 | ], | |||
|
3129 | "accept": [ | |||
|
3130 | "application/mercurial-0.1" | |||
|
3131 | ] | |||
|
3132 | }, | |||
|
3133 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
3134 | "method": "POST", | |||
|
3135 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%22da86a9f3268c2bcd03a0efc5ef8f2171e3e741fc%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%22da86a9f3268c2bcd03a0efc5ef8f2171e3e741fc%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%22f391f4da4c61a54562f5e3dce83ea7f7bd38be31%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+20254%2C+%22name%22%3A+%22local%3Acommits%22%7D" | |||
|
3136 | } | |||
|
3137 | }, | |||
|
3138 | { | |||
|
3139 | "response": { | |||
|
3140 | "headers": { | |||
|
3141 | "date": [ | |||
|
3142 | "Tue, 18 Feb 2020 18:43:33 GMT" | |||
|
3143 | ], | |||
|
3144 | "strict-transport-security": [ | |||
|
3145 | "max-age=0; includeSubdomains; preload" | |||
|
3146 | ], | |||
|
3147 | "x-frame-options": [ | |||
|
3148 | "Deny" | |||
|
3149 | ], | |||
|
3150 | "content-type": [ | |||
|
3151 | "application/json" | |||
|
3152 | ], | |||
|
3153 | "transfer-encoding": [ | |||
|
3154 | "chunked" | |||
|
3155 | ], | |||
|
3156 | "x-xss-protection": [ | |||
|
3157 | "1; mode=block" | |||
|
3158 | ], | |||
|
3159 | "expires": [ | |||
|
3160 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
3161 | ], | |||
|
3162 | "x-content-type-options": [ | |||
|
3163 | "nosniff" | |||
|
3164 | ], | |||
|
3165 | "referrer-policy": [ | |||
|
3166 | "no-referrer" | |||
|
3167 | ], | |||
|
3168 | "server": [ | |||
|
3169 | "Apache/2.4.10 (Debian)" | |||
|
3170 | ], | |||
|
3171 | "cache-control": [ | |||
|
3172 | "no-store" | |||
|
3173 | ] | |||
|
3174 | }, | |||
|
3175 | "status": { | |||
|
3176 | "code": 200, | |||
|
3177 | "message": "OK" | |||
|
3178 | }, | |||
|
3179 | "body": { | |||
|
3180 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
3181 | } | |||
|
3182 | }, | |||
|
3183 | "request": { | |||
|
3184 | "headers": { | |||
|
3185 | "content-length": [ | |||
|
3186 | "482" | |||
|
3187 | ], | |||
|
3188 | "host": [ | |||
|
3189 | "phab.mercurial-scm.org" | |||
|
3190 | ], | |||
|
3191 | "content-type": [ | |||
|
3192 | "application/x-www-form-urlencoded" | |||
|
3193 | ], | |||
|
3194 | "user-agent": [ | |||
|
3195 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
3196 | ], | |||
|
3197 | "accept": [ | |||
|
3198 | "application/mercurial-0.1" | |||
|
3199 | ] | |||
|
3200 | }, | |||
|
3201 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
3202 | "method": "POST", | |||
|
3203 | "body": "output=json&__conduit__=1¶ms=%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%22003ffc16ba6612afd72ba30ad5054ee96316e149%5C%22%2C+%5C%22parent%5C%22%3A+%5C%22da86a9f3268c2bcd03a0efc5ef8f2171e3e741fc%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+20255%2C+%22name%22%3A+%22hg%3Ameta%22%7D" | |||
|
3204 | } | |||
|
3205 | }, | |||
|
3206 | { | |||
|
3207 | "response": { | |||
|
3208 | "headers": { | |||
|
3209 | "date": [ | |||
|
3210 | "Tue, 18 Feb 2020 18:43:33 GMT" | |||
|
3211 | ], | |||
|
3212 | "strict-transport-security": [ | |||
|
3213 | "max-age=0; includeSubdomains; preload" | |||
|
3214 | ], | |||
|
3215 | "x-frame-options": [ | |||
|
3216 | "Deny" | |||
|
3217 | ], | |||
|
3218 | "content-type": [ | |||
|
3219 | "application/json" | |||
|
3220 | ], | |||
|
3221 | "transfer-encoding": [ | |||
|
3222 | "chunked" | |||
|
3223 | ], | |||
|
3224 | "x-xss-protection": [ | |||
|
3225 | "1; mode=block" | |||
|
3226 | ], | |||
|
3227 | "expires": [ | |||
|
3228 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
3229 | ], | |||
|
3230 | "x-content-type-options": [ | |||
|
3231 | "nosniff" | |||
|
3232 | ], | |||
|
3233 | "referrer-policy": [ | |||
|
3234 | "no-referrer" | |||
|
3235 | ], | |||
|
3236 | "server": [ | |||
|
3237 | "Apache/2.4.10 (Debian)" | |||
|
3238 | ], | |||
|
3239 | "cache-control": [ | |||
|
3240 | "no-store" | |||
|
3241 | ] | |||
|
3242 | }, | |||
|
3243 | "status": { | |||
|
3244 | "code": 200, | |||
|
3245 | "message": "OK" | |||
|
3246 | }, | |||
|
3247 | "body": { | |||
|
3248 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
3249 | } | |||
|
3250 | }, | |||
|
3251 | "request": { | |||
|
3252 | "headers": { | |||
|
3253 | "content-length": [ | |||
|
3254 | "594" | |||
|
3255 | ], | |||
|
3256 | "host": [ | |||
|
3257 | "phab.mercurial-scm.org" | |||
|
3258 | ], | |||
|
3259 | "content-type": [ | |||
|
3260 | "application/x-www-form-urlencoded" | |||
|
3261 | ], | |||
|
3262 | "user-agent": [ | |||
|
3263 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
3264 | ], | |||
|
3265 | "accept": [ | |||
|
3266 | "application/mercurial-0.1" | |||
|
3267 | ] | |||
|
3268 | }, | |||
|
3269 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
3270 | "method": "POST", | |||
|
3271 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%22003ffc16ba6612afd72ba30ad5054ee96316e149%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%22003ffc16ba6612afd72ba30ad5054ee96316e149%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%22da86a9f3268c2bcd03a0efc5ef8f2171e3e741fc%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+20255%2C+%22name%22%3A+%22local%3Acommits%22%7D" | |||
|
3272 | } | |||
|
3273 | }, | |||
|
3274 | { | |||
|
3275 | "response": { | |||
|
3276 | "headers": { | |||
|
3277 | "date": [ | |||
|
3278 | "Tue, 18 Feb 2020 18:43:33 GMT" | |||
|
3279 | ], | |||
|
3280 | "strict-transport-security": [ | |||
|
3281 | "max-age=0; includeSubdomains; preload" | |||
|
3282 | ], | |||
|
3283 | "x-frame-options": [ | |||
|
3284 | "Deny" | |||
|
3285 | ], | |||
|
3286 | "content-type": [ | |||
|
3287 | "application/json" | |||
|
3288 | ], | |||
|
3289 | "transfer-encoding": [ | |||
|
3290 | "chunked" | |||
|
3291 | ], | |||
|
3292 | "x-xss-protection": [ | |||
|
3293 | "1; mode=block" | |||
|
3294 | ], | |||
|
3295 | "expires": [ | |||
|
3296 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
3297 | ], | |||
|
3298 | "x-content-type-options": [ | |||
|
3299 | "nosniff" | |||
|
3300 | ], | |||
|
3301 | "referrer-policy": [ | |||
|
3302 | "no-referrer" | |||
|
3303 | ], | |||
|
3304 | "server": [ | |||
|
3305 | "Apache/2.4.10 (Debian)" | |||
|
3306 | ], | |||
|
3307 | "cache-control": [ | |||
|
3308 | "no-store" | |||
|
3309 | ] | |||
|
3310 | }, | |||
|
3311 | "status": { | |||
|
3312 | "code": 200, | |||
|
3313 | "message": "OK" | |||
|
3314 | }, | |||
|
3315 | "body": { | |||
|
3316 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
3317 | } | |||
|
3318 | }, | |||
|
3319 | "request": { | |||
|
3320 | "headers": { | |||
|
3321 | "content-length": [ | |||
|
3322 | "482" | |||
|
3323 | ], | |||
|
3324 | "host": [ | |||
|
3325 | "phab.mercurial-scm.org" | |||
|
3326 | ], | |||
|
3327 | "content-type": [ | |||
|
3328 | "application/x-www-form-urlencoded" | |||
|
3329 | ], | |||
|
3330 | "user-agent": [ | |||
|
3331 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
3332 | ], | |||
|
3333 | "accept": [ | |||
|
3334 | "application/mercurial-0.1" | |||
|
3335 | ] | |||
|
3336 | }, | |||
|
3337 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
3338 | "method": "POST", | |||
|
3339 | "body": "output=json&__conduit__=1¶ms=%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%2213bd750c36fadfe118f04a1f9639300393791443%5C%22%2C+%5C%22parent%5C%22%3A+%5C%22003ffc16ba6612afd72ba30ad5054ee96316e149%5C%22%2C+%5C%22user%5C%22%3A+%5C%22test%5C%22%7D%22%2C+%22diff_id%22%3A+20256%2C+%22name%22%3A+%22hg%3Ameta%22%7D" | |||
|
3340 | } | |||
|
3341 | }, | |||
|
3342 | { | |||
|
3343 | "response": { | |||
|
3344 | "headers": { | |||
|
3345 | "date": [ | |||
|
3346 | "Tue, 18 Feb 2020 18:43:34 GMT" | |||
|
3347 | ], | |||
|
3348 | "strict-transport-security": [ | |||
|
3349 | "max-age=0; includeSubdomains; preload" | |||
|
3350 | ], | |||
|
3351 | "x-frame-options": [ | |||
|
3352 | "Deny" | |||
|
3353 | ], | |||
|
3354 | "content-type": [ | |||
|
3355 | "application/json" | |||
|
3356 | ], | |||
|
3357 | "transfer-encoding": [ | |||
|
3358 | "chunked" | |||
|
3359 | ], | |||
|
3360 | "x-xss-protection": [ | |||
|
3361 | "1; mode=block" | |||
|
3362 | ], | |||
|
3363 | "expires": [ | |||
|
3364 | "Sat, 01 Jan 2000 00:00:00 GMT" | |||
|
3365 | ], | |||
|
3366 | "x-content-type-options": [ | |||
|
3367 | "nosniff" | |||
|
3368 | ], | |||
|
3369 | "referrer-policy": [ | |||
|
3370 | "no-referrer" | |||
|
3371 | ], | |||
|
3372 | "server": [ | |||
|
3373 | "Apache/2.4.10 (Debian)" | |||
|
3374 | ], | |||
|
3375 | "cache-control": [ | |||
|
3376 | "no-store" | |||
|
3377 | ] | |||
|
3378 | }, | |||
|
3379 | "status": { | |||
|
3380 | "code": 200, | |||
|
3381 | "message": "OK" | |||
|
3382 | }, | |||
|
3383 | "body": { | |||
|
3384 | "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}" | |||
|
3385 | } | |||
|
3386 | }, | |||
|
3387 | "request": { | |||
|
3388 | "headers": { | |||
|
3389 | "content-length": [ | |||
|
3390 | "594" | |||
|
3391 | ], | |||
|
3392 | "host": [ | |||
|
3393 | "phab.mercurial-scm.org" | |||
|
3394 | ], | |||
|
3395 | "content-type": [ | |||
|
3396 | "application/x-www-form-urlencoded" | |||
|
3397 | ], | |||
|
3398 | "user-agent": [ | |||
|
3399 | "mercurial/proto-1.0 (Mercurial 5.3+213-eda4eceb98c0+20200218)" | |||
|
3400 | ], | |||
|
3401 | "accept": [ | |||
|
3402 | "application/mercurial-0.1" | |||
|
3403 | ] | |||
|
3404 | }, | |||
|
3405 | "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", | |||
|
3406 | "method": "POST", | |||
|
3407 | "body": "output=json&__conduit__=1¶ms=%7B%22__conduit__%22%3A+%7B%22token%22%3A+%22cli-hahayouwish%22%7D%2C+%22data%22%3A+%22%7B%5C%2213bd750c36fadfe118f04a1f9639300393791443%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%2213bd750c36fadfe118f04a1f9639300393791443%5C%22%2C+%5C%22parents%5C%22%3A+%5B%5C%22003ffc16ba6612afd72ba30ad5054ee96316e149%5C%22%5D%2C+%5C%22time%5C%22%3A+0%7D%7D%22%2C+%22diff_id%22%3A+20256%2C+%22name%22%3A+%22local%3Acommits%22%7D" | |||
|
3408 | } | |||
|
3409 | } | |||
|
3410 | ] | |||
|
3411 | } No newline at end of file |
@@ -1,1797 +1,1797 b'' | |||||
1 | # phabricator.py - simple Phabricator integration |
|
1 | # phabricator.py - simple Phabricator integration | |
2 | # |
|
2 | # | |
3 | # Copyright 2017 Facebook, Inc. |
|
3 | # Copyright 2017 Facebook, Inc. | |
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2 or any later version. |
|
6 | # GNU General Public License version 2 or any later version. | |
7 | """simple Phabricator integration (EXPERIMENTAL) |
|
7 | """simple Phabricator integration (EXPERIMENTAL) | |
8 |
|
8 | |||
9 | This extension provides a ``phabsend`` command which sends a stack of |
|
9 | This extension provides a ``phabsend`` command which sends a stack of | |
10 | changesets to Phabricator, and a ``phabread`` command which prints a stack of |
|
10 | changesets to Phabricator, and a ``phabread`` command which prints a stack of | |
11 | revisions in a format suitable for :hg:`import`, and a ``phabupdate`` command |
|
11 | revisions in a format suitable for :hg:`import`, and a ``phabupdate`` command | |
12 | to update statuses in batch. |
|
12 | to update statuses in batch. | |
13 |
|
13 | |||
14 | A "phabstatus" view for :hg:`show` is also provided; it displays status |
|
14 | A "phabstatus" view for :hg:`show` is also provided; it displays status | |
15 | information of Phabricator differentials associated with unfinished |
|
15 | information of Phabricator differentials associated with unfinished | |
16 | changesets. |
|
16 | changesets. | |
17 |
|
17 | |||
18 | By default, Phabricator requires ``Test Plan`` which might prevent some |
|
18 | By default, Phabricator requires ``Test Plan`` which might prevent some | |
19 | changeset from being sent. The requirement could be disabled by changing |
|
19 | changeset from being sent. The requirement could be disabled by changing | |
20 | ``differential.require-test-plan-field`` config server side. |
|
20 | ``differential.require-test-plan-field`` config server side. | |
21 |
|
21 | |||
22 | Config:: |
|
22 | Config:: | |
23 |
|
23 | |||
24 | [phabricator] |
|
24 | [phabricator] | |
25 | # Phabricator URL |
|
25 | # Phabricator URL | |
26 | url = https://phab.example.com/ |
|
26 | url = https://phab.example.com/ | |
27 |
|
27 | |||
28 | # Repo callsign. If a repo has a URL https://$HOST/diffusion/FOO, then its |
|
28 | # Repo callsign. If a repo has a URL https://$HOST/diffusion/FOO, then its | |
29 | # callsign is "FOO". |
|
29 | # callsign is "FOO". | |
30 | callsign = FOO |
|
30 | callsign = FOO | |
31 |
|
31 | |||
32 | # curl command to use. If not set (default), use builtin HTTP library to |
|
32 | # curl command to use. If not set (default), use builtin HTTP library to | |
33 | # communicate. If set, use the specified curl command. This could be useful |
|
33 | # communicate. If set, use the specified curl command. This could be useful | |
34 | # if you need to specify advanced options that is not easily supported by |
|
34 | # if you need to specify advanced options that is not easily supported by | |
35 | # the internal library. |
|
35 | # the internal library. | |
36 | curlcmd = curl --connect-timeout 2 --retry 3 --silent |
|
36 | curlcmd = curl --connect-timeout 2 --retry 3 --silent | |
37 |
|
37 | |||
38 | [auth] |
|
38 | [auth] | |
39 | example.schemes = https |
|
39 | example.schemes = https | |
40 | example.prefix = phab.example.com |
|
40 | example.prefix = phab.example.com | |
41 |
|
41 | |||
42 | # API token. Get it from https://$HOST/conduit/login/ |
|
42 | # API token. Get it from https://$HOST/conduit/login/ | |
43 | example.phabtoken = cli-xxxxxxxxxxxxxxxxxxxxxxxxxxxx |
|
43 | example.phabtoken = cli-xxxxxxxxxxxxxxxxxxxxxxxxxxxx | |
44 | """ |
|
44 | """ | |
45 |
|
45 | |||
46 | from __future__ import absolute_import |
|
46 | from __future__ import absolute_import | |
47 |
|
47 | |||
48 | import base64 |
|
48 | import base64 | |
49 | import contextlib |
|
49 | import contextlib | |
50 | import hashlib |
|
50 | import hashlib | |
51 | import itertools |
|
51 | import itertools | |
52 | import json |
|
52 | import json | |
53 | import mimetypes |
|
53 | import mimetypes | |
54 | import operator |
|
54 | import operator | |
55 | import re |
|
55 | import re | |
56 |
|
56 | |||
57 | from mercurial.node import bin, nullid |
|
57 | from mercurial.node import bin, nullid | |
58 | from mercurial.i18n import _ |
|
58 | from mercurial.i18n import _ | |
59 | from mercurial.pycompat import getattr |
|
59 | from mercurial.pycompat import getattr | |
60 | from mercurial.thirdparty import attr |
|
60 | from mercurial.thirdparty import attr | |
61 | from mercurial import ( |
|
61 | from mercurial import ( | |
62 | cmdutil, |
|
62 | cmdutil, | |
63 | context, |
|
63 | context, | |
64 | encoding, |
|
64 | encoding, | |
65 | error, |
|
65 | error, | |
66 | exthelper, |
|
66 | exthelper, | |
67 | graphmod, |
|
67 | graphmod, | |
68 | httpconnection as httpconnectionmod, |
|
68 | httpconnection as httpconnectionmod, | |
69 | localrepo, |
|
69 | localrepo, | |
70 | logcmdutil, |
|
70 | logcmdutil, | |
71 | match, |
|
71 | match, | |
72 | mdiff, |
|
72 | mdiff, | |
73 | obsutil, |
|
73 | obsutil, | |
74 | parser, |
|
74 | parser, | |
75 | patch, |
|
75 | patch, | |
76 | phases, |
|
76 | phases, | |
77 | pycompat, |
|
77 | pycompat, | |
78 | scmutil, |
|
78 | scmutil, | |
79 | smartset, |
|
79 | smartset, | |
80 | tags, |
|
80 | tags, | |
81 | templatefilters, |
|
81 | templatefilters, | |
82 | templateutil, |
|
82 | templateutil, | |
83 | url as urlmod, |
|
83 | url as urlmod, | |
84 | util, |
|
84 | util, | |
85 | ) |
|
85 | ) | |
86 | from mercurial.utils import ( |
|
86 | from mercurial.utils import ( | |
87 | procutil, |
|
87 | procutil, | |
88 | stringutil, |
|
88 | stringutil, | |
89 | ) |
|
89 | ) | |
90 | from . import show |
|
90 | from . import show | |
91 |
|
91 | |||
92 |
|
92 | |||
93 | # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for |
|
93 | # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for | |
94 | # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should |
|
94 | # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should | |
95 | # be specifying the version(s) of Mercurial they are tested with, or |
|
95 | # be specifying the version(s) of Mercurial they are tested with, or | |
96 | # leave the attribute unspecified. |
|
96 | # leave the attribute unspecified. | |
97 | testedwith = b'ships-with-hg-core' |
|
97 | testedwith = b'ships-with-hg-core' | |
98 |
|
98 | |||
99 | eh = exthelper.exthelper() |
|
99 | eh = exthelper.exthelper() | |
100 |
|
100 | |||
101 | cmdtable = eh.cmdtable |
|
101 | cmdtable = eh.cmdtable | |
102 | command = eh.command |
|
102 | command = eh.command | |
103 | configtable = eh.configtable |
|
103 | configtable = eh.configtable | |
104 | templatekeyword = eh.templatekeyword |
|
104 | templatekeyword = eh.templatekeyword | |
105 | uisetup = eh.finaluisetup |
|
105 | uisetup = eh.finaluisetup | |
106 |
|
106 | |||
107 | # developer config: phabricator.batchsize |
|
107 | # developer config: phabricator.batchsize | |
108 | eh.configitem( |
|
108 | eh.configitem( | |
109 | b'phabricator', b'batchsize', default=12, |
|
109 | b'phabricator', b'batchsize', default=12, | |
110 | ) |
|
110 | ) | |
111 | eh.configitem( |
|
111 | eh.configitem( | |
112 | b'phabricator', b'callsign', default=None, |
|
112 | b'phabricator', b'callsign', default=None, | |
113 | ) |
|
113 | ) | |
114 | eh.configitem( |
|
114 | eh.configitem( | |
115 | b'phabricator', b'curlcmd', default=None, |
|
115 | b'phabricator', b'curlcmd', default=None, | |
116 | ) |
|
116 | ) | |
117 | # developer config: phabricator.repophid |
|
117 | # developer config: phabricator.repophid | |
118 | eh.configitem( |
|
118 | eh.configitem( | |
119 | b'phabricator', b'repophid', default=None, |
|
119 | b'phabricator', b'repophid', default=None, | |
120 | ) |
|
120 | ) | |
121 | eh.configitem( |
|
121 | eh.configitem( | |
122 | b'phabricator', b'url', default=None, |
|
122 | b'phabricator', b'url', default=None, | |
123 | ) |
|
123 | ) | |
124 | eh.configitem( |
|
124 | eh.configitem( | |
125 | b'phabsend', b'confirm', default=False, |
|
125 | b'phabsend', b'confirm', default=False, | |
126 | ) |
|
126 | ) | |
127 |
|
127 | |||
128 | colortable = { |
|
128 | colortable = { | |
129 | b'phabricator.action.created': b'green', |
|
129 | b'phabricator.action.created': b'green', | |
130 | b'phabricator.action.skipped': b'magenta', |
|
130 | b'phabricator.action.skipped': b'magenta', | |
131 | b'phabricator.action.updated': b'magenta', |
|
131 | b'phabricator.action.updated': b'magenta', | |
132 | b'phabricator.desc': b'', |
|
132 | b'phabricator.desc': b'', | |
133 | b'phabricator.drev': b'bold', |
|
133 | b'phabricator.drev': b'bold', | |
134 | b'phabricator.node': b'', |
|
134 | b'phabricator.node': b'', | |
135 | b'phabricator.status.abandoned': b'magenta dim', |
|
135 | b'phabricator.status.abandoned': b'magenta dim', | |
136 | b'phabricator.status.accepted': b'green bold', |
|
136 | b'phabricator.status.accepted': b'green bold', | |
137 | b'phabricator.status.closed': b'green', |
|
137 | b'phabricator.status.closed': b'green', | |
138 | b'phabricator.status.needsreview': b'yellow', |
|
138 | b'phabricator.status.needsreview': b'yellow', | |
139 | b'phabricator.status.needsrevision': b'red', |
|
139 | b'phabricator.status.needsrevision': b'red', | |
140 | b'phabricator.status.changesplanned': b'red', |
|
140 | b'phabricator.status.changesplanned': b'red', | |
141 | } |
|
141 | } | |
142 |
|
142 | |||
143 | _VCR_FLAGS = [ |
|
143 | _VCR_FLAGS = [ | |
144 | ( |
|
144 | ( | |
145 | b'', |
|
145 | b'', | |
146 | b'test-vcr', |
|
146 | b'test-vcr', | |
147 | b'', |
|
147 | b'', | |
148 | _( |
|
148 | _( | |
149 | b'Path to a vcr file. If nonexistent, will record a new vcr transcript' |
|
149 | b'Path to a vcr file. If nonexistent, will record a new vcr transcript' | |
150 | b', otherwise will mock all http requests using the specified vcr file.' |
|
150 | b', otherwise will mock all http requests using the specified vcr file.' | |
151 | b' (ADVANCED)' |
|
151 | b' (ADVANCED)' | |
152 | ), |
|
152 | ), | |
153 | ), |
|
153 | ), | |
154 | ] |
|
154 | ] | |
155 |
|
155 | |||
156 |
|
156 | |||
157 | @eh.wrapfunction(localrepo, "loadhgrc") |
|
157 | @eh.wrapfunction(localrepo, "loadhgrc") | |
158 | def _loadhgrc(orig, ui, wdirvfs, hgvfs, requirements): |
|
158 | def _loadhgrc(orig, ui, wdirvfs, hgvfs, requirements): | |
159 | """Load ``.arcconfig`` content into a ui instance on repository open. |
|
159 | """Load ``.arcconfig`` content into a ui instance on repository open. | |
160 | """ |
|
160 | """ | |
161 | result = False |
|
161 | result = False | |
162 | arcconfig = {} |
|
162 | arcconfig = {} | |
163 |
|
163 | |||
164 | try: |
|
164 | try: | |
165 | # json.loads only accepts bytes from 3.6+ |
|
165 | # json.loads only accepts bytes from 3.6+ | |
166 | rawparams = encoding.unifromlocal(wdirvfs.read(b".arcconfig")) |
|
166 | rawparams = encoding.unifromlocal(wdirvfs.read(b".arcconfig")) | |
167 | # json.loads only returns unicode strings |
|
167 | # json.loads only returns unicode strings | |
168 | arcconfig = pycompat.rapply( |
|
168 | arcconfig = pycompat.rapply( | |
169 | lambda x: encoding.unitolocal(x) |
|
169 | lambda x: encoding.unitolocal(x) | |
170 | if isinstance(x, pycompat.unicode) |
|
170 | if isinstance(x, pycompat.unicode) | |
171 | else x, |
|
171 | else x, | |
172 | pycompat.json_loads(rawparams), |
|
172 | pycompat.json_loads(rawparams), | |
173 | ) |
|
173 | ) | |
174 |
|
174 | |||
175 | result = True |
|
175 | result = True | |
176 | except ValueError: |
|
176 | except ValueError: | |
177 | ui.warn(_(b"invalid JSON in %s\n") % wdirvfs.join(b".arcconfig")) |
|
177 | ui.warn(_(b"invalid JSON in %s\n") % wdirvfs.join(b".arcconfig")) | |
178 | except IOError: |
|
178 | except IOError: | |
179 | pass |
|
179 | pass | |
180 |
|
180 | |||
181 | cfg = util.sortdict() |
|
181 | cfg = util.sortdict() | |
182 |
|
182 | |||
183 | if b"repository.callsign" in arcconfig: |
|
183 | if b"repository.callsign" in arcconfig: | |
184 | cfg[(b"phabricator", b"callsign")] = arcconfig[b"repository.callsign"] |
|
184 | cfg[(b"phabricator", b"callsign")] = arcconfig[b"repository.callsign"] | |
185 |
|
185 | |||
186 | if b"phabricator.uri" in arcconfig: |
|
186 | if b"phabricator.uri" in arcconfig: | |
187 | cfg[(b"phabricator", b"url")] = arcconfig[b"phabricator.uri"] |
|
187 | cfg[(b"phabricator", b"url")] = arcconfig[b"phabricator.uri"] | |
188 |
|
188 | |||
189 | if cfg: |
|
189 | if cfg: | |
190 | ui.applyconfig(cfg, source=wdirvfs.join(b".arcconfig")) |
|
190 | ui.applyconfig(cfg, source=wdirvfs.join(b".arcconfig")) | |
191 |
|
191 | |||
192 | return orig(ui, wdirvfs, hgvfs, requirements) or result # Load .hg/hgrc |
|
192 | return orig(ui, wdirvfs, hgvfs, requirements) or result # Load .hg/hgrc | |
193 |
|
193 | |||
194 |
|
194 | |||
195 | def vcrcommand(name, flags, spec, helpcategory=None, optionalrepo=False): |
|
195 | def vcrcommand(name, flags, spec, helpcategory=None, optionalrepo=False): | |
196 | fullflags = flags + _VCR_FLAGS |
|
196 | fullflags = flags + _VCR_FLAGS | |
197 |
|
197 | |||
198 | def hgmatcher(r1, r2): |
|
198 | def hgmatcher(r1, r2): | |
199 | if r1.uri != r2.uri or r1.method != r2.method: |
|
199 | if r1.uri != r2.uri or r1.method != r2.method: | |
200 | return False |
|
200 | return False | |
201 | r1params = util.urlreq.parseqs(r1.body) |
|
201 | r1params = util.urlreq.parseqs(r1.body) | |
202 | r2params = util.urlreq.parseqs(r2.body) |
|
202 | r2params = util.urlreq.parseqs(r2.body) | |
203 | for key in r1params: |
|
203 | for key in r1params: | |
204 | if key not in r2params: |
|
204 | if key not in r2params: | |
205 | return False |
|
205 | return False | |
206 | value = r1params[key][0] |
|
206 | value = r1params[key][0] | |
207 | # we want to compare json payloads without worrying about ordering |
|
207 | # we want to compare json payloads without worrying about ordering | |
208 | if value.startswith(b'{') and value.endswith(b'}'): |
|
208 | if value.startswith(b'{') and value.endswith(b'}'): | |
209 | r1json = pycompat.json_loads(value) |
|
209 | r1json = pycompat.json_loads(value) | |
210 | r2json = pycompat.json_loads(r2params[key][0]) |
|
210 | r2json = pycompat.json_loads(r2params[key][0]) | |
211 | if r1json != r2json: |
|
211 | if r1json != r2json: | |
212 | return False |
|
212 | return False | |
213 | elif r2params[key][0] != value: |
|
213 | elif r2params[key][0] != value: | |
214 | return False |
|
214 | return False | |
215 | return True |
|
215 | return True | |
216 |
|
216 | |||
217 | def sanitiserequest(request): |
|
217 | def sanitiserequest(request): | |
218 | request.body = re.sub( |
|
218 | request.body = re.sub( | |
219 | br'cli-[a-z0-9]+', br'cli-hahayouwish', request.body |
|
219 | br'cli-[a-z0-9]+', br'cli-hahayouwish', request.body | |
220 | ) |
|
220 | ) | |
221 | return request |
|
221 | return request | |
222 |
|
222 | |||
223 | def sanitiseresponse(response): |
|
223 | def sanitiseresponse(response): | |
224 | if 'set-cookie' in response['headers']: |
|
224 | if 'set-cookie' in response['headers']: | |
225 | del response['headers']['set-cookie'] |
|
225 | del response['headers']['set-cookie'] | |
226 | return response |
|
226 | return response | |
227 |
|
227 | |||
228 | def decorate(fn): |
|
228 | def decorate(fn): | |
229 | def inner(*args, **kwargs): |
|
229 | def inner(*args, **kwargs): | |
230 | cassette = pycompat.fsdecode(kwargs.pop('test_vcr', None)) |
|
230 | cassette = pycompat.fsdecode(kwargs.pop('test_vcr', None)) | |
231 | if cassette: |
|
231 | if cassette: | |
232 | import hgdemandimport |
|
232 | import hgdemandimport | |
233 |
|
233 | |||
234 | with hgdemandimport.deactivated(): |
|
234 | with hgdemandimport.deactivated(): | |
235 | import vcr as vcrmod |
|
235 | import vcr as vcrmod | |
236 | import vcr.stubs as stubs |
|
236 | import vcr.stubs as stubs | |
237 |
|
237 | |||
238 | vcr = vcrmod.VCR( |
|
238 | vcr = vcrmod.VCR( | |
239 | serializer='json', |
|
239 | serializer='json', | |
240 | before_record_request=sanitiserequest, |
|
240 | before_record_request=sanitiserequest, | |
241 | before_record_response=sanitiseresponse, |
|
241 | before_record_response=sanitiseresponse, | |
242 | custom_patches=[ |
|
242 | custom_patches=[ | |
243 | ( |
|
243 | ( | |
244 | urlmod, |
|
244 | urlmod, | |
245 | 'httpconnection', |
|
245 | 'httpconnection', | |
246 | stubs.VCRHTTPConnection, |
|
246 | stubs.VCRHTTPConnection, | |
247 | ), |
|
247 | ), | |
248 | ( |
|
248 | ( | |
249 | urlmod, |
|
249 | urlmod, | |
250 | 'httpsconnection', |
|
250 | 'httpsconnection', | |
251 | stubs.VCRHTTPSConnection, |
|
251 | stubs.VCRHTTPSConnection, | |
252 | ), |
|
252 | ), | |
253 | ], |
|
253 | ], | |
254 | ) |
|
254 | ) | |
255 | vcr.register_matcher('hgmatcher', hgmatcher) |
|
255 | vcr.register_matcher('hgmatcher', hgmatcher) | |
256 | with vcr.use_cassette(cassette, match_on=['hgmatcher']): |
|
256 | with vcr.use_cassette(cassette, match_on=['hgmatcher']): | |
257 | return fn(*args, **kwargs) |
|
257 | return fn(*args, **kwargs) | |
258 | return fn(*args, **kwargs) |
|
258 | return fn(*args, **kwargs) | |
259 |
|
259 | |||
260 | inner.__name__ = fn.__name__ |
|
260 | inner.__name__ = fn.__name__ | |
261 | inner.__doc__ = fn.__doc__ |
|
261 | inner.__doc__ = fn.__doc__ | |
262 | return command( |
|
262 | return command( | |
263 | name, |
|
263 | name, | |
264 | fullflags, |
|
264 | fullflags, | |
265 | spec, |
|
265 | spec, | |
266 | helpcategory=helpcategory, |
|
266 | helpcategory=helpcategory, | |
267 | optionalrepo=optionalrepo, |
|
267 | optionalrepo=optionalrepo, | |
268 | )(inner) |
|
268 | )(inner) | |
269 |
|
269 | |||
270 | return decorate |
|
270 | return decorate | |
271 |
|
271 | |||
272 |
|
272 | |||
273 | def urlencodenested(params): |
|
273 | def urlencodenested(params): | |
274 | """like urlencode, but works with nested parameters. |
|
274 | """like urlencode, but works with nested parameters. | |
275 |
|
275 | |||
276 | For example, if params is {'a': ['b', 'c'], 'd': {'e': 'f'}}, it will be |
|
276 | For example, if params is {'a': ['b', 'c'], 'd': {'e': 'f'}}, it will be | |
277 | flattened to {'a[0]': 'b', 'a[1]': 'c', 'd[e]': 'f'} and then passed to |
|
277 | flattened to {'a[0]': 'b', 'a[1]': 'c', 'd[e]': 'f'} and then passed to | |
278 | urlencode. Note: the encoding is consistent with PHP's http_build_query. |
|
278 | urlencode. Note: the encoding is consistent with PHP's http_build_query. | |
279 | """ |
|
279 | """ | |
280 | flatparams = util.sortdict() |
|
280 | flatparams = util.sortdict() | |
281 |
|
281 | |||
282 | def process(prefix, obj): |
|
282 | def process(prefix, obj): | |
283 | if isinstance(obj, bool): |
|
283 | if isinstance(obj, bool): | |
284 | obj = {True: b'true', False: b'false'}[obj] # Python -> PHP form |
|
284 | obj = {True: b'true', False: b'false'}[obj] # Python -> PHP form | |
285 | lister = lambda l: [(b'%d' % k, v) for k, v in enumerate(l)] |
|
285 | lister = lambda l: [(b'%d' % k, v) for k, v in enumerate(l)] | |
286 | items = {list: lister, dict: lambda x: x.items()}.get(type(obj)) |
|
286 | items = {list: lister, dict: lambda x: x.items()}.get(type(obj)) | |
287 | if items is None: |
|
287 | if items is None: | |
288 | flatparams[prefix] = obj |
|
288 | flatparams[prefix] = obj | |
289 | else: |
|
289 | else: | |
290 | for k, v in items(obj): |
|
290 | for k, v in items(obj): | |
291 | if prefix: |
|
291 | if prefix: | |
292 | process(b'%s[%s]' % (prefix, k), v) |
|
292 | process(b'%s[%s]' % (prefix, k), v) | |
293 | else: |
|
293 | else: | |
294 | process(k, v) |
|
294 | process(k, v) | |
295 |
|
295 | |||
296 | process(b'', params) |
|
296 | process(b'', params) | |
297 | return util.urlreq.urlencode(flatparams) |
|
297 | return util.urlreq.urlencode(flatparams) | |
298 |
|
298 | |||
299 |
|
299 | |||
300 | def readurltoken(ui): |
|
300 | def readurltoken(ui): | |
301 | """return conduit url, token and make sure they exist |
|
301 | """return conduit url, token and make sure they exist | |
302 |
|
302 | |||
303 | Currently read from [auth] config section. In the future, it might |
|
303 | Currently read from [auth] config section. In the future, it might | |
304 | make sense to read from .arcconfig and .arcrc as well. |
|
304 | make sense to read from .arcconfig and .arcrc as well. | |
305 | """ |
|
305 | """ | |
306 | url = ui.config(b'phabricator', b'url') |
|
306 | url = ui.config(b'phabricator', b'url') | |
307 | if not url: |
|
307 | if not url: | |
308 | raise error.Abort( |
|
308 | raise error.Abort( | |
309 | _(b'config %s.%s is required') % (b'phabricator', b'url') |
|
309 | _(b'config %s.%s is required') % (b'phabricator', b'url') | |
310 | ) |
|
310 | ) | |
311 |
|
311 | |||
312 | res = httpconnectionmod.readauthforuri(ui, url, util.url(url).user) |
|
312 | res = httpconnectionmod.readauthforuri(ui, url, util.url(url).user) | |
313 | token = None |
|
313 | token = None | |
314 |
|
314 | |||
315 | if res: |
|
315 | if res: | |
316 | group, auth = res |
|
316 | group, auth = res | |
317 |
|
317 | |||
318 | ui.debug(b"using auth.%s.* for authentication\n" % group) |
|
318 | ui.debug(b"using auth.%s.* for authentication\n" % group) | |
319 |
|
319 | |||
320 | token = auth.get(b'phabtoken') |
|
320 | token = auth.get(b'phabtoken') | |
321 |
|
321 | |||
322 | if not token: |
|
322 | if not token: | |
323 | raise error.Abort( |
|
323 | raise error.Abort( | |
324 | _(b'Can\'t find conduit token associated to %s') % (url,) |
|
324 | _(b'Can\'t find conduit token associated to %s') % (url,) | |
325 | ) |
|
325 | ) | |
326 |
|
326 | |||
327 | return url, token |
|
327 | return url, token | |
328 |
|
328 | |||
329 |
|
329 | |||
330 | def callconduit(ui, name, params): |
|
330 | def callconduit(ui, name, params): | |
331 | """call Conduit API, params is a dict. return json.loads result, or None""" |
|
331 | """call Conduit API, params is a dict. return json.loads result, or None""" | |
332 | host, token = readurltoken(ui) |
|
332 | host, token = readurltoken(ui) | |
333 | url, authinfo = util.url(b'/'.join([host, b'api', name])).authinfo() |
|
333 | url, authinfo = util.url(b'/'.join([host, b'api', name])).authinfo() | |
334 | ui.debug(b'Conduit Call: %s %s\n' % (url, pycompat.byterepr(params))) |
|
334 | ui.debug(b'Conduit Call: %s %s\n' % (url, pycompat.byterepr(params))) | |
335 | params = params.copy() |
|
335 | params = params.copy() | |
336 | params[b'__conduit__'] = { |
|
336 | params[b'__conduit__'] = { | |
337 | b'token': token, |
|
337 | b'token': token, | |
338 | } |
|
338 | } | |
339 | rawdata = { |
|
339 | rawdata = { | |
340 | b'params': templatefilters.json(params), |
|
340 | b'params': templatefilters.json(params), | |
341 | b'output': b'json', |
|
341 | b'output': b'json', | |
342 | b'__conduit__': 1, |
|
342 | b'__conduit__': 1, | |
343 | } |
|
343 | } | |
344 | data = urlencodenested(rawdata) |
|
344 | data = urlencodenested(rawdata) | |
345 | curlcmd = ui.config(b'phabricator', b'curlcmd') |
|
345 | curlcmd = ui.config(b'phabricator', b'curlcmd') | |
346 | if curlcmd: |
|
346 | if curlcmd: | |
347 | sin, sout = procutil.popen2( |
|
347 | sin, sout = procutil.popen2( | |
348 | b'%s -d @- %s' % (curlcmd, procutil.shellquote(url)) |
|
348 | b'%s -d @- %s' % (curlcmd, procutil.shellquote(url)) | |
349 | ) |
|
349 | ) | |
350 | sin.write(data) |
|
350 | sin.write(data) | |
351 | sin.close() |
|
351 | sin.close() | |
352 | body = sout.read() |
|
352 | body = sout.read() | |
353 | else: |
|
353 | else: | |
354 | urlopener = urlmod.opener(ui, authinfo) |
|
354 | urlopener = urlmod.opener(ui, authinfo) | |
355 | request = util.urlreq.request(pycompat.strurl(url), data=data) |
|
355 | request = util.urlreq.request(pycompat.strurl(url), data=data) | |
356 | with contextlib.closing(urlopener.open(request)) as rsp: |
|
356 | with contextlib.closing(urlopener.open(request)) as rsp: | |
357 | body = rsp.read() |
|
357 | body = rsp.read() | |
358 | ui.debug(b'Conduit Response: %s\n' % body) |
|
358 | ui.debug(b'Conduit Response: %s\n' % body) | |
359 | parsed = pycompat.rapply( |
|
359 | parsed = pycompat.rapply( | |
360 | lambda x: encoding.unitolocal(x) |
|
360 | lambda x: encoding.unitolocal(x) | |
361 | if isinstance(x, pycompat.unicode) |
|
361 | if isinstance(x, pycompat.unicode) | |
362 | else x, |
|
362 | else x, | |
363 | # json.loads only accepts bytes from py3.6+ |
|
363 | # json.loads only accepts bytes from py3.6+ | |
364 | pycompat.json_loads(encoding.unifromlocal(body)), |
|
364 | pycompat.json_loads(encoding.unifromlocal(body)), | |
365 | ) |
|
365 | ) | |
366 | if parsed.get(b'error_code'): |
|
366 | if parsed.get(b'error_code'): | |
367 | msg = _(b'Conduit Error (%s): %s') % ( |
|
367 | msg = _(b'Conduit Error (%s): %s') % ( | |
368 | parsed[b'error_code'], |
|
368 | parsed[b'error_code'], | |
369 | parsed[b'error_info'], |
|
369 | parsed[b'error_info'], | |
370 | ) |
|
370 | ) | |
371 | raise error.Abort(msg) |
|
371 | raise error.Abort(msg) | |
372 | return parsed[b'result'] |
|
372 | return parsed[b'result'] | |
373 |
|
373 | |||
374 |
|
374 | |||
375 | @vcrcommand(b'debugcallconduit', [], _(b'METHOD'), optionalrepo=True) |
|
375 | @vcrcommand(b'debugcallconduit', [], _(b'METHOD'), optionalrepo=True) | |
376 | def debugcallconduit(ui, repo, name): |
|
376 | def debugcallconduit(ui, repo, name): | |
377 | """call Conduit API |
|
377 | """call Conduit API | |
378 |
|
378 | |||
379 | Call parameters are read from stdin as a JSON blob. Result will be written |
|
379 | Call parameters are read from stdin as a JSON blob. Result will be written | |
380 | to stdout as a JSON blob. |
|
380 | to stdout as a JSON blob. | |
381 | """ |
|
381 | """ | |
382 | # json.loads only accepts bytes from 3.6+ |
|
382 | # json.loads only accepts bytes from 3.6+ | |
383 | rawparams = encoding.unifromlocal(ui.fin.read()) |
|
383 | rawparams = encoding.unifromlocal(ui.fin.read()) | |
384 | # json.loads only returns unicode strings |
|
384 | # json.loads only returns unicode strings | |
385 | params = pycompat.rapply( |
|
385 | params = pycompat.rapply( | |
386 | lambda x: encoding.unitolocal(x) |
|
386 | lambda x: encoding.unitolocal(x) | |
387 | if isinstance(x, pycompat.unicode) |
|
387 | if isinstance(x, pycompat.unicode) | |
388 | else x, |
|
388 | else x, | |
389 | pycompat.json_loads(rawparams), |
|
389 | pycompat.json_loads(rawparams), | |
390 | ) |
|
390 | ) | |
391 | # json.dumps only accepts unicode strings |
|
391 | # json.dumps only accepts unicode strings | |
392 | result = pycompat.rapply( |
|
392 | result = pycompat.rapply( | |
393 | lambda x: encoding.unifromlocal(x) if isinstance(x, bytes) else x, |
|
393 | lambda x: encoding.unifromlocal(x) if isinstance(x, bytes) else x, | |
394 | callconduit(ui, name, params), |
|
394 | callconduit(ui, name, params), | |
395 | ) |
|
395 | ) | |
396 | s = json.dumps(result, sort_keys=True, indent=2, separators=(u',', u': ')) |
|
396 | s = json.dumps(result, sort_keys=True, indent=2, separators=(u',', u': ')) | |
397 | ui.write(b'%s\n' % encoding.unitolocal(s)) |
|
397 | ui.write(b'%s\n' % encoding.unitolocal(s)) | |
398 |
|
398 | |||
399 |
|
399 | |||
400 | def getrepophid(repo): |
|
400 | def getrepophid(repo): | |
401 | """given callsign, return repository PHID or None""" |
|
401 | """given callsign, return repository PHID or None""" | |
402 | # developer config: phabricator.repophid |
|
402 | # developer config: phabricator.repophid | |
403 | repophid = repo.ui.config(b'phabricator', b'repophid') |
|
403 | repophid = repo.ui.config(b'phabricator', b'repophid') | |
404 | if repophid: |
|
404 | if repophid: | |
405 | return repophid |
|
405 | return repophid | |
406 | callsign = repo.ui.config(b'phabricator', b'callsign') |
|
406 | callsign = repo.ui.config(b'phabricator', b'callsign') | |
407 | if not callsign: |
|
407 | if not callsign: | |
408 | return None |
|
408 | return None | |
409 | query = callconduit( |
|
409 | query = callconduit( | |
410 | repo.ui, |
|
410 | repo.ui, | |
411 | b'diffusion.repository.search', |
|
411 | b'diffusion.repository.search', | |
412 | {b'constraints': {b'callsigns': [callsign]}}, |
|
412 | {b'constraints': {b'callsigns': [callsign]}}, | |
413 | ) |
|
413 | ) | |
414 | if len(query[b'data']) == 0: |
|
414 | if len(query[b'data']) == 0: | |
415 | return None |
|
415 | return None | |
416 | repophid = query[b'data'][0][b'phid'] |
|
416 | repophid = query[b'data'][0][b'phid'] | |
417 | repo.ui.setconfig(b'phabricator', b'repophid', repophid) |
|
417 | repo.ui.setconfig(b'phabricator', b'repophid', repophid) | |
418 | return repophid |
|
418 | return repophid | |
419 |
|
419 | |||
420 |
|
420 | |||
421 | _differentialrevisiontagre = re.compile(br'\AD([1-9][0-9]*)\Z') |
|
421 | _differentialrevisiontagre = re.compile(br'\AD([1-9][0-9]*)\Z') | |
422 | _differentialrevisiondescre = re.compile( |
|
422 | _differentialrevisiondescre = re.compile( | |
423 | br'^Differential Revision:\s*(?P<url>(?:.*)D(?P<id>[1-9][0-9]*))$', re.M |
|
423 | br'^Differential Revision:\s*(?P<url>(?:.*)D(?P<id>[1-9][0-9]*))$', re.M | |
424 | ) |
|
424 | ) | |
425 |
|
425 | |||
426 |
|
426 | |||
427 | def getoldnodedrevmap(repo, nodelist): |
|
427 | def getoldnodedrevmap(repo, nodelist): | |
428 | """find previous nodes that has been sent to Phabricator |
|
428 | """find previous nodes that has been sent to Phabricator | |
429 |
|
429 | |||
430 | return {node: (oldnode, Differential diff, Differential Revision ID)} |
|
430 | return {node: (oldnode, Differential diff, Differential Revision ID)} | |
431 | for node in nodelist with known previous sent versions, or associated |
|
431 | for node in nodelist with known previous sent versions, or associated | |
432 | Differential Revision IDs. ``oldnode`` and ``Differential diff`` could |
|
432 | Differential Revision IDs. ``oldnode`` and ``Differential diff`` could | |
433 | be ``None``. |
|
433 | be ``None``. | |
434 |
|
434 | |||
435 | Examines commit messages like "Differential Revision:" to get the |
|
435 | Examines commit messages like "Differential Revision:" to get the | |
436 | association information. |
|
436 | association information. | |
437 |
|
437 | |||
438 | If such commit message line is not found, examines all precursors and their |
|
438 | If such commit message line is not found, examines all precursors and their | |
439 | tags. Tags with format like "D1234" are considered a match and the node |
|
439 | tags. Tags with format like "D1234" are considered a match and the node | |
440 | with that tag, and the number after "D" (ex. 1234) will be returned. |
|
440 | with that tag, and the number after "D" (ex. 1234) will be returned. | |
441 |
|
441 | |||
442 | The ``old node``, if not None, is guaranteed to be the last diff of |
|
442 | The ``old node``, if not None, is guaranteed to be the last diff of | |
443 | corresponding Differential Revision, and exist in the repo. |
|
443 | corresponding Differential Revision, and exist in the repo. | |
444 | """ |
|
444 | """ | |
445 | unfi = repo.unfiltered() |
|
445 | unfi = repo.unfiltered() | |
446 | has_node = unfi.changelog.index.has_node |
|
446 | has_node = unfi.changelog.index.has_node | |
447 |
|
447 | |||
448 | result = {} # {node: (oldnode?, lastdiff?, drev)} |
|
448 | result = {} # {node: (oldnode?, lastdiff?, drev)} | |
449 | toconfirm = {} # {node: (force, {precnode}, drev)} |
|
449 | toconfirm = {} # {node: (force, {precnode}, drev)} | |
450 | for node in nodelist: |
|
450 | for node in nodelist: | |
451 | ctx = unfi[node] |
|
451 | ctx = unfi[node] | |
452 | # For tags like "D123", put them into "toconfirm" to verify later |
|
452 | # For tags like "D123", put them into "toconfirm" to verify later | |
453 | precnodes = list(obsutil.allpredecessors(unfi.obsstore, [node])) |
|
453 | precnodes = list(obsutil.allpredecessors(unfi.obsstore, [node])) | |
454 | for n in precnodes: |
|
454 | for n in precnodes: | |
455 | if has_node(n): |
|
455 | if has_node(n): | |
456 | for tag in unfi.nodetags(n): |
|
456 | for tag in unfi.nodetags(n): | |
457 | m = _differentialrevisiontagre.match(tag) |
|
457 | m = _differentialrevisiontagre.match(tag) | |
458 | if m: |
|
458 | if m: | |
459 | toconfirm[node] = (0, set(precnodes), int(m.group(1))) |
|
459 | toconfirm[node] = (0, set(precnodes), int(m.group(1))) | |
460 | break |
|
460 | break | |
461 | else: |
|
461 | else: | |
462 | continue # move to next predecessor |
|
462 | continue # move to next predecessor | |
463 | break # found a tag, stop |
|
463 | break # found a tag, stop | |
464 | else: |
|
464 | else: | |
465 | # Check commit message |
|
465 | # Check commit message | |
466 | m = _differentialrevisiondescre.search(ctx.description()) |
|
466 | m = _differentialrevisiondescre.search(ctx.description()) | |
467 | if m: |
|
467 | if m: | |
468 | toconfirm[node] = (1, set(precnodes), int(m.group('id'))) |
|
468 | toconfirm[node] = (1, set(precnodes), int(m.group('id'))) | |
469 |
|
469 | |||
470 | # Double check if tags are genuine by collecting all old nodes from |
|
470 | # Double check if tags are genuine by collecting all old nodes from | |
471 | # Phabricator, and expect precursors overlap with it. |
|
471 | # Phabricator, and expect precursors overlap with it. | |
472 | if toconfirm: |
|
472 | if toconfirm: | |
473 | drevs = [drev for force, precs, drev in toconfirm.values()] |
|
473 | drevs = [drev for force, precs, drev in toconfirm.values()] | |
474 | alldiffs = callconduit( |
|
474 | alldiffs = callconduit( | |
475 | unfi.ui, b'differential.querydiffs', {b'revisionIDs': drevs} |
|
475 | unfi.ui, b'differential.querydiffs', {b'revisionIDs': drevs} | |
476 | ) |
|
476 | ) | |
477 | getnode = lambda d: bin(getdiffmeta(d).get(b'node', b'')) or None |
|
477 | getnode = lambda d: bin(getdiffmeta(d).get(b'node', b'')) or None | |
478 | for newnode, (force, precset, drev) in toconfirm.items(): |
|
478 | for newnode, (force, precset, drev) in toconfirm.items(): | |
479 | diffs = [ |
|
479 | diffs = [ | |
480 | d for d in alldiffs.values() if int(d[b'revisionID']) == drev |
|
480 | d for d in alldiffs.values() if int(d[b'revisionID']) == drev | |
481 | ] |
|
481 | ] | |
482 |
|
482 | |||
483 | # "precursors" as known by Phabricator |
|
483 | # "precursors" as known by Phabricator | |
484 | phprecset = set(getnode(d) for d in diffs) |
|
484 | phprecset = set(getnode(d) for d in diffs) | |
485 |
|
485 | |||
486 | # Ignore if precursors (Phabricator and local repo) do not overlap, |
|
486 | # Ignore if precursors (Phabricator and local repo) do not overlap, | |
487 | # and force is not set (when commit message says nothing) |
|
487 | # and force is not set (when commit message says nothing) | |
488 | if not force and not bool(phprecset & precset): |
|
488 | if not force and not bool(phprecset & precset): | |
489 | tagname = b'D%d' % drev |
|
489 | tagname = b'D%d' % drev | |
490 | tags.tag( |
|
490 | tags.tag( | |
491 | repo, |
|
491 | repo, | |
492 | tagname, |
|
492 | tagname, | |
493 | nullid, |
|
493 | nullid, | |
494 | message=None, |
|
494 | message=None, | |
495 | user=None, |
|
495 | user=None, | |
496 | date=None, |
|
496 | date=None, | |
497 | local=True, |
|
497 | local=True, | |
498 | ) |
|
498 | ) | |
499 | unfi.ui.warn( |
|
499 | unfi.ui.warn( | |
500 | _( |
|
500 | _( | |
501 | b'D%d: local tag removed - does not match ' |
|
501 | b'D%d: local tag removed - does not match ' | |
502 | b'Differential history\n' |
|
502 | b'Differential history\n' | |
503 | ) |
|
503 | ) | |
504 | % drev |
|
504 | % drev | |
505 | ) |
|
505 | ) | |
506 | continue |
|
506 | continue | |
507 |
|
507 | |||
508 | # Find the last node using Phabricator metadata, and make sure it |
|
508 | # Find the last node using Phabricator metadata, and make sure it | |
509 | # exists in the repo |
|
509 | # exists in the repo | |
510 | oldnode = lastdiff = None |
|
510 | oldnode = lastdiff = None | |
511 | if diffs: |
|
511 | if diffs: | |
512 | lastdiff = max(diffs, key=lambda d: int(d[b'id'])) |
|
512 | lastdiff = max(diffs, key=lambda d: int(d[b'id'])) | |
513 | oldnode = getnode(lastdiff) |
|
513 | oldnode = getnode(lastdiff) | |
514 | if oldnode and not has_node(oldnode): |
|
514 | if oldnode and not has_node(oldnode): | |
515 | oldnode = None |
|
515 | oldnode = None | |
516 |
|
516 | |||
517 | result[newnode] = (oldnode, lastdiff, drev) |
|
517 | result[newnode] = (oldnode, lastdiff, drev) | |
518 |
|
518 | |||
519 | return result |
|
519 | return result | |
520 |
|
520 | |||
521 |
|
521 | |||
522 | def getdrevmap(repo, revs): |
|
522 | def getdrevmap(repo, revs): | |
523 | """Return a dict mapping each rev in `revs` to their Differential Revision |
|
523 | """Return a dict mapping each rev in `revs` to their Differential Revision | |
524 | ID or None. |
|
524 | ID or None. | |
525 | """ |
|
525 | """ | |
526 | result = {} |
|
526 | result = {} | |
527 | for rev in revs: |
|
527 | for rev in revs: | |
528 | result[rev] = None |
|
528 | result[rev] = None | |
529 | ctx = repo[rev] |
|
529 | ctx = repo[rev] | |
530 | # Check commit message |
|
530 | # Check commit message | |
531 | m = _differentialrevisiondescre.search(ctx.description()) |
|
531 | m = _differentialrevisiondescre.search(ctx.description()) | |
532 | if m: |
|
532 | if m: | |
533 | result[rev] = int(m.group('id')) |
|
533 | result[rev] = int(m.group('id')) | |
534 | continue |
|
534 | continue | |
535 | # Check tags |
|
535 | # Check tags | |
536 | for tag in repo.nodetags(ctx.node()): |
|
536 | for tag in repo.nodetags(ctx.node()): | |
537 | m = _differentialrevisiontagre.match(tag) |
|
537 | m = _differentialrevisiontagre.match(tag) | |
538 | if m: |
|
538 | if m: | |
539 | result[rev] = int(m.group(1)) |
|
539 | result[rev] = int(m.group(1)) | |
540 | break |
|
540 | break | |
541 |
|
541 | |||
542 | return result |
|
542 | return result | |
543 |
|
543 | |||
544 |
|
544 | |||
545 | def getdiff(ctx, diffopts): |
|
545 | def getdiff(ctx, diffopts): | |
546 | """plain-text diff without header (user, commit message, etc)""" |
|
546 | """plain-text diff without header (user, commit message, etc)""" | |
547 | output = util.stringio() |
|
547 | output = util.stringio() | |
548 | for chunk, _label in patch.diffui( |
|
548 | for chunk, _label in patch.diffui( | |
549 | ctx.repo(), ctx.p1().node(), ctx.node(), None, opts=diffopts |
|
549 | ctx.repo(), ctx.p1().node(), ctx.node(), None, opts=diffopts | |
550 | ): |
|
550 | ): | |
551 | output.write(chunk) |
|
551 | output.write(chunk) | |
552 | return output.getvalue() |
|
552 | return output.getvalue() | |
553 |
|
553 | |||
554 |
|
554 | |||
555 | class DiffChangeType(object): |
|
555 | class DiffChangeType(object): | |
556 | ADD = 1 |
|
556 | ADD = 1 | |
557 | CHANGE = 2 |
|
557 | CHANGE = 2 | |
558 | DELETE = 3 |
|
558 | DELETE = 3 | |
559 | MOVE_AWAY = 4 |
|
559 | MOVE_AWAY = 4 | |
560 | COPY_AWAY = 5 |
|
560 | COPY_AWAY = 5 | |
561 | MOVE_HERE = 6 |
|
561 | MOVE_HERE = 6 | |
562 | COPY_HERE = 7 |
|
562 | COPY_HERE = 7 | |
563 | MULTICOPY = 8 |
|
563 | MULTICOPY = 8 | |
564 |
|
564 | |||
565 |
|
565 | |||
566 | class DiffFileType(object): |
|
566 | class DiffFileType(object): | |
567 | TEXT = 1 |
|
567 | TEXT = 1 | |
568 | IMAGE = 2 |
|
568 | IMAGE = 2 | |
569 | BINARY = 3 |
|
569 | BINARY = 3 | |
570 |
|
570 | |||
571 |
|
571 | |||
572 | @attr.s |
|
572 | @attr.s | |
573 | class phabhunk(dict): |
|
573 | class phabhunk(dict): | |
574 | """Represents a Differential hunk, which is owned by a Differential change |
|
574 | """Represents a Differential hunk, which is owned by a Differential change | |
575 | """ |
|
575 | """ | |
576 |
|
576 | |||
577 | oldOffset = attr.ib(default=0) # camelcase-required |
|
577 | oldOffset = attr.ib(default=0) # camelcase-required | |
578 | oldLength = attr.ib(default=0) # camelcase-required |
|
578 | oldLength = attr.ib(default=0) # camelcase-required | |
579 | newOffset = attr.ib(default=0) # camelcase-required |
|
579 | newOffset = attr.ib(default=0) # camelcase-required | |
580 | newLength = attr.ib(default=0) # camelcase-required |
|
580 | newLength = attr.ib(default=0) # camelcase-required | |
581 | corpus = attr.ib(default='') |
|
581 | corpus = attr.ib(default='') | |
582 | # These get added to the phabchange's equivalents |
|
582 | # These get added to the phabchange's equivalents | |
583 | addLines = attr.ib(default=0) # camelcase-required |
|
583 | addLines = attr.ib(default=0) # camelcase-required | |
584 | delLines = attr.ib(default=0) # camelcase-required |
|
584 | delLines = attr.ib(default=0) # camelcase-required | |
585 |
|
585 | |||
586 |
|
586 | |||
587 | @attr.s |
|
587 | @attr.s | |
588 | class phabchange(object): |
|
588 | class phabchange(object): | |
589 | """Represents a Differential change, owns Differential hunks and owned by a |
|
589 | """Represents a Differential change, owns Differential hunks and owned by a | |
590 | Differential diff. Each one represents one file in a diff. |
|
590 | Differential diff. Each one represents one file in a diff. | |
591 | """ |
|
591 | """ | |
592 |
|
592 | |||
593 | currentPath = attr.ib(default=None) # camelcase-required |
|
593 | currentPath = attr.ib(default=None) # camelcase-required | |
594 | oldPath = attr.ib(default=None) # camelcase-required |
|
594 | oldPath = attr.ib(default=None) # camelcase-required | |
595 | awayPaths = attr.ib(default=attr.Factory(list)) # camelcase-required |
|
595 | awayPaths = attr.ib(default=attr.Factory(list)) # camelcase-required | |
596 | metadata = attr.ib(default=attr.Factory(dict)) |
|
596 | metadata = attr.ib(default=attr.Factory(dict)) | |
597 | oldProperties = attr.ib(default=attr.Factory(dict)) # camelcase-required |
|
597 | oldProperties = attr.ib(default=attr.Factory(dict)) # camelcase-required | |
598 | newProperties = attr.ib(default=attr.Factory(dict)) # camelcase-required |
|
598 | newProperties = attr.ib(default=attr.Factory(dict)) # camelcase-required | |
599 | type = attr.ib(default=DiffChangeType.CHANGE) |
|
599 | type = attr.ib(default=DiffChangeType.CHANGE) | |
600 | fileType = attr.ib(default=DiffFileType.TEXT) # camelcase-required |
|
600 | fileType = attr.ib(default=DiffFileType.TEXT) # camelcase-required | |
601 | commitHash = attr.ib(default=None) # camelcase-required |
|
601 | commitHash = attr.ib(default=None) # camelcase-required | |
602 | addLines = attr.ib(default=0) # camelcase-required |
|
602 | addLines = attr.ib(default=0) # camelcase-required | |
603 | delLines = attr.ib(default=0) # camelcase-required |
|
603 | delLines = attr.ib(default=0) # camelcase-required | |
604 | hunks = attr.ib(default=attr.Factory(list)) |
|
604 | hunks = attr.ib(default=attr.Factory(list)) | |
605 |
|
605 | |||
606 | def copynewmetadatatoold(self): |
|
606 | def copynewmetadatatoold(self): | |
607 | for key in list(self.metadata.keys()): |
|
607 | for key in list(self.metadata.keys()): | |
608 | newkey = key.replace(b'new:', b'old:') |
|
608 | newkey = key.replace(b'new:', b'old:') | |
609 | self.metadata[newkey] = self.metadata[key] |
|
609 | self.metadata[newkey] = self.metadata[key] | |
610 |
|
610 | |||
611 | def addoldmode(self, value): |
|
611 | def addoldmode(self, value): | |
612 | self.oldProperties[b'unix:filemode'] = value |
|
612 | self.oldProperties[b'unix:filemode'] = value | |
613 |
|
613 | |||
614 | def addnewmode(self, value): |
|
614 | def addnewmode(self, value): | |
615 | self.newProperties[b'unix:filemode'] = value |
|
615 | self.newProperties[b'unix:filemode'] = value | |
616 |
|
616 | |||
617 | def addhunk(self, hunk): |
|
617 | def addhunk(self, hunk): | |
618 | if not isinstance(hunk, phabhunk): |
|
618 | if not isinstance(hunk, phabhunk): | |
619 | raise error.Abort(b'phabchange.addhunk only takes phabhunks') |
|
619 | raise error.Abort(b'phabchange.addhunk only takes phabhunks') | |
620 | self.hunks.append(pycompat.byteskwargs(attr.asdict(hunk))) |
|
620 | self.hunks.append(pycompat.byteskwargs(attr.asdict(hunk))) | |
621 | # It's useful to include these stats since the Phab web UI shows them, |
|
621 | # It's useful to include these stats since the Phab web UI shows them, | |
622 | # and uses them to estimate how large a change a Revision is. Also used |
|
622 | # and uses them to estimate how large a change a Revision is. Also used | |
623 | # in email subjects for the [+++--] bit. |
|
623 | # in email subjects for the [+++--] bit. | |
624 | self.addLines += hunk.addLines |
|
624 | self.addLines += hunk.addLines | |
625 | self.delLines += hunk.delLines |
|
625 | self.delLines += hunk.delLines | |
626 |
|
626 | |||
627 |
|
627 | |||
628 | @attr.s |
|
628 | @attr.s | |
629 | class phabdiff(object): |
|
629 | class phabdiff(object): | |
630 | """Represents a Differential diff, owns Differential changes. Corresponds |
|
630 | """Represents a Differential diff, owns Differential changes. Corresponds | |
631 | to a commit. |
|
631 | to a commit. | |
632 | """ |
|
632 | """ | |
633 |
|
633 | |||
634 | # Doesn't seem to be any reason to send this (output of uname -n) |
|
634 | # Doesn't seem to be any reason to send this (output of uname -n) | |
635 | sourceMachine = attr.ib(default=b'') # camelcase-required |
|
635 | sourceMachine = attr.ib(default=b'') # camelcase-required | |
636 | sourcePath = attr.ib(default=b'/') # camelcase-required |
|
636 | sourcePath = attr.ib(default=b'/') # camelcase-required | |
637 | sourceControlBaseRevision = attr.ib(default=b'0' * 40) # camelcase-required |
|
637 | sourceControlBaseRevision = attr.ib(default=b'0' * 40) # camelcase-required | |
638 | sourceControlPath = attr.ib(default=b'/') # camelcase-required |
|
638 | sourceControlPath = attr.ib(default=b'/') # camelcase-required | |
639 | sourceControlSystem = attr.ib(default=b'hg') # camelcase-required |
|
639 | sourceControlSystem = attr.ib(default=b'hg') # camelcase-required | |
640 | branch = attr.ib(default=b'default') |
|
640 | branch = attr.ib(default=b'default') | |
641 | bookmark = attr.ib(default=None) |
|
641 | bookmark = attr.ib(default=None) | |
642 | creationMethod = attr.ib(default=b'phabsend') # camelcase-required |
|
642 | creationMethod = attr.ib(default=b'phabsend') # camelcase-required | |
643 | lintStatus = attr.ib(default=b'none') # camelcase-required |
|
643 | lintStatus = attr.ib(default=b'none') # camelcase-required | |
644 | unitStatus = attr.ib(default=b'none') # camelcase-required |
|
644 | unitStatus = attr.ib(default=b'none') # camelcase-required | |
645 | changes = attr.ib(default=attr.Factory(dict)) |
|
645 | changes = attr.ib(default=attr.Factory(dict)) | |
646 | repositoryPHID = attr.ib(default=None) # camelcase-required |
|
646 | repositoryPHID = attr.ib(default=None) # camelcase-required | |
647 |
|
647 | |||
648 | def addchange(self, change): |
|
648 | def addchange(self, change): | |
649 | if not isinstance(change, phabchange): |
|
649 | if not isinstance(change, phabchange): | |
650 | raise error.Abort(b'phabdiff.addchange only takes phabchanges') |
|
650 | raise error.Abort(b'phabdiff.addchange only takes phabchanges') | |
651 | self.changes[change.currentPath] = pycompat.byteskwargs( |
|
651 | self.changes[change.currentPath] = pycompat.byteskwargs( | |
652 | attr.asdict(change) |
|
652 | attr.asdict(change) | |
653 | ) |
|
653 | ) | |
654 |
|
654 | |||
655 |
|
655 | |||
656 | def maketext(pchange, ctx, fname): |
|
656 | def maketext(pchange, ctx, fname): | |
657 | """populate the phabchange for a text file""" |
|
657 | """populate the phabchange for a text file""" | |
658 | repo = ctx.repo() |
|
658 | repo = ctx.repo() | |
659 | fmatcher = match.exact([fname]) |
|
659 | fmatcher = match.exact([fname]) | |
660 | diffopts = mdiff.diffopts(git=True, context=32767) |
|
660 | diffopts = mdiff.diffopts(git=True, context=32767) | |
661 | _pfctx, _fctx, header, fhunks = next( |
|
661 | _pfctx, _fctx, header, fhunks = next( | |
662 | patch.diffhunks(repo, ctx.p1(), ctx, fmatcher, opts=diffopts) |
|
662 | patch.diffhunks(repo, ctx.p1(), ctx, fmatcher, opts=diffopts) | |
663 | ) |
|
663 | ) | |
664 |
|
664 | |||
665 | for fhunk in fhunks: |
|
665 | for fhunk in fhunks: | |
666 | (oldOffset, oldLength, newOffset, newLength), lines = fhunk |
|
666 | (oldOffset, oldLength, newOffset, newLength), lines = fhunk | |
667 | corpus = b''.join(lines[1:]) |
|
667 | corpus = b''.join(lines[1:]) | |
668 | shunk = list(header) |
|
668 | shunk = list(header) | |
669 | shunk.extend(lines) |
|
669 | shunk.extend(lines) | |
670 | _mf, _mt, addLines, delLines, _hb = patch.diffstatsum( |
|
670 | _mf, _mt, addLines, delLines, _hb = patch.diffstatsum( | |
671 | patch.diffstatdata(util.iterlines(shunk)) |
|
671 | patch.diffstatdata(util.iterlines(shunk)) | |
672 | ) |
|
672 | ) | |
673 | pchange.addhunk( |
|
673 | pchange.addhunk( | |
674 | phabhunk( |
|
674 | phabhunk( | |
675 | oldOffset, |
|
675 | oldOffset, | |
676 | oldLength, |
|
676 | oldLength, | |
677 | newOffset, |
|
677 | newOffset, | |
678 | newLength, |
|
678 | newLength, | |
679 | corpus, |
|
679 | corpus, | |
680 | addLines, |
|
680 | addLines, | |
681 | delLines, |
|
681 | delLines, | |
682 | ) |
|
682 | ) | |
683 | ) |
|
683 | ) | |
684 |
|
684 | |||
685 |
|
685 | |||
686 | def uploadchunks(fctx, fphid): |
|
686 | def uploadchunks(fctx, fphid): | |
687 | """upload large binary files as separate chunks. |
|
687 | """upload large binary files as separate chunks. | |
688 | Phab requests chunking over 8MiB, and splits into 4MiB chunks |
|
688 | Phab requests chunking over 8MiB, and splits into 4MiB chunks | |
689 | """ |
|
689 | """ | |
690 | ui = fctx.repo().ui |
|
690 | ui = fctx.repo().ui | |
691 | chunks = callconduit(ui, b'file.querychunks', {b'filePHID': fphid}) |
|
691 | chunks = callconduit(ui, b'file.querychunks', {b'filePHID': fphid}) | |
692 | with ui.makeprogress( |
|
692 | with ui.makeprogress( | |
693 | _(b'uploading file chunks'), unit=_(b'chunks'), total=len(chunks) |
|
693 | _(b'uploading file chunks'), unit=_(b'chunks'), total=len(chunks) | |
694 | ) as progress: |
|
694 | ) as progress: | |
695 | for chunk in chunks: |
|
695 | for chunk in chunks: | |
696 | progress.increment() |
|
696 | progress.increment() | |
697 | if chunk[b'complete']: |
|
697 | if chunk[b'complete']: | |
698 | continue |
|
698 | continue | |
699 | bstart = int(chunk[b'byteStart']) |
|
699 | bstart = int(chunk[b'byteStart']) | |
700 | bend = int(chunk[b'byteEnd']) |
|
700 | bend = int(chunk[b'byteEnd']) | |
701 | callconduit( |
|
701 | callconduit( | |
702 | ui, |
|
702 | ui, | |
703 | b'file.uploadchunk', |
|
703 | b'file.uploadchunk', | |
704 | { |
|
704 | { | |
705 | b'filePHID': fphid, |
|
705 | b'filePHID': fphid, | |
706 | b'byteStart': bstart, |
|
706 | b'byteStart': bstart, | |
707 | b'data': base64.b64encode(fctx.data()[bstart:bend]), |
|
707 | b'data': base64.b64encode(fctx.data()[bstart:bend]), | |
708 | b'dataEncoding': b'base64', |
|
708 | b'dataEncoding': b'base64', | |
709 | }, |
|
709 | }, | |
710 | ) |
|
710 | ) | |
711 |
|
711 | |||
712 |
|
712 | |||
713 | def uploadfile(fctx): |
|
713 | def uploadfile(fctx): | |
714 | """upload binary files to Phabricator""" |
|
714 | """upload binary files to Phabricator""" | |
715 | repo = fctx.repo() |
|
715 | repo = fctx.repo() | |
716 | ui = repo.ui |
|
716 | ui = repo.ui | |
717 | fname = fctx.path() |
|
717 | fname = fctx.path() | |
718 | size = fctx.size() |
|
718 | size = fctx.size() | |
719 | fhash = pycompat.bytestr(hashlib.sha256(fctx.data()).hexdigest()) |
|
719 | fhash = pycompat.bytestr(hashlib.sha256(fctx.data()).hexdigest()) | |
720 |
|
720 | |||
721 | # an allocate call is required first to see if an upload is even required |
|
721 | # an allocate call is required first to see if an upload is even required | |
722 | # (Phab might already have it) and to determine if chunking is needed |
|
722 | # (Phab might already have it) and to determine if chunking is needed | |
723 | allocateparams = { |
|
723 | allocateparams = { | |
724 | b'name': fname, |
|
724 | b'name': fname, | |
725 | b'contentLength': size, |
|
725 | b'contentLength': size, | |
726 | b'contentHash': fhash, |
|
726 | b'contentHash': fhash, | |
727 | } |
|
727 | } | |
728 | filealloc = callconduit(ui, b'file.allocate', allocateparams) |
|
728 | filealloc = callconduit(ui, b'file.allocate', allocateparams) | |
729 | fphid = filealloc[b'filePHID'] |
|
729 | fphid = filealloc[b'filePHID'] | |
730 |
|
730 | |||
731 | if filealloc[b'upload']: |
|
731 | if filealloc[b'upload']: | |
732 | ui.write(_(b'uploading %s\n') % bytes(fctx)) |
|
732 | ui.write(_(b'uploading %s\n') % bytes(fctx)) | |
733 | if not fphid: |
|
733 | if not fphid: | |
734 | uploadparams = { |
|
734 | uploadparams = { | |
735 | b'name': fname, |
|
735 | b'name': fname, | |
736 | b'data_base64': base64.b64encode(fctx.data()), |
|
736 | b'data_base64': base64.b64encode(fctx.data()), | |
737 | } |
|
737 | } | |
738 | fphid = callconduit(ui, b'file.upload', uploadparams) |
|
738 | fphid = callconduit(ui, b'file.upload', uploadparams) | |
739 | else: |
|
739 | else: | |
740 | uploadchunks(fctx, fphid) |
|
740 | uploadchunks(fctx, fphid) | |
741 | else: |
|
741 | else: | |
742 | ui.debug(b'server already has %s\n' % bytes(fctx)) |
|
742 | ui.debug(b'server already has %s\n' % bytes(fctx)) | |
743 |
|
743 | |||
744 | if not fphid: |
|
744 | if not fphid: | |
745 | raise error.Abort(b'Upload of %s failed.' % bytes(fctx)) |
|
745 | raise error.Abort(b'Upload of %s failed.' % bytes(fctx)) | |
746 |
|
746 | |||
747 | return fphid |
|
747 | return fphid | |
748 |
|
748 | |||
749 |
|
749 | |||
750 | def addoldbinary(pchange, fctx): |
|
750 | def addoldbinary(pchange, fctx): | |
751 | """add the metadata for the previous version of a binary file to the |
|
751 | """add the metadata for the previous version of a binary file to the | |
752 | phabchange for the new version |
|
752 | phabchange for the new version | |
753 | """ |
|
753 | """ | |
754 | oldfctx = fctx.p1() |
|
754 | oldfctx = fctx.p1() | |
755 | if fctx.cmp(oldfctx): |
|
755 | if fctx.cmp(oldfctx): | |
756 | # Files differ, add the old one |
|
756 | # Files differ, add the old one | |
757 | pchange.metadata[b'old:file:size'] = oldfctx.size() |
|
757 | pchange.metadata[b'old:file:size'] = oldfctx.size() | |
758 | mimeguess, _enc = mimetypes.guess_type( |
|
758 | mimeguess, _enc = mimetypes.guess_type( | |
759 | encoding.unifromlocal(oldfctx.path()) |
|
759 | encoding.unifromlocal(oldfctx.path()) | |
760 | ) |
|
760 | ) | |
761 | if mimeguess: |
|
761 | if mimeguess: | |
762 | pchange.metadata[b'old:file:mime-type'] = pycompat.bytestr( |
|
762 | pchange.metadata[b'old:file:mime-type'] = pycompat.bytestr( | |
763 | mimeguess |
|
763 | mimeguess | |
764 | ) |
|
764 | ) | |
765 | fphid = uploadfile(oldfctx) |
|
765 | fphid = uploadfile(oldfctx) | |
766 | pchange.metadata[b'old:binary-phid'] = fphid |
|
766 | pchange.metadata[b'old:binary-phid'] = fphid | |
767 | else: |
|
767 | else: | |
768 | # If it's left as IMAGE/BINARY web UI might try to display it |
|
768 | # If it's left as IMAGE/BINARY web UI might try to display it | |
769 | pchange.fileType = DiffFileType.TEXT |
|
769 | pchange.fileType = DiffFileType.TEXT | |
770 | pchange.copynewmetadatatoold() |
|
770 | pchange.copynewmetadatatoold() | |
771 |
|
771 | |||
772 |
|
772 | |||
773 | def makebinary(pchange, fctx): |
|
773 | def makebinary(pchange, fctx): | |
774 | """populate the phabchange for a binary file""" |
|
774 | """populate the phabchange for a binary file""" | |
775 | pchange.fileType = DiffFileType.BINARY |
|
775 | pchange.fileType = DiffFileType.BINARY | |
776 | fphid = uploadfile(fctx) |
|
776 | fphid = uploadfile(fctx) | |
777 | pchange.metadata[b'new:binary-phid'] = fphid |
|
777 | pchange.metadata[b'new:binary-phid'] = fphid | |
778 | pchange.metadata[b'new:file:size'] = fctx.size() |
|
778 | pchange.metadata[b'new:file:size'] = fctx.size() | |
779 | mimeguess, _enc = mimetypes.guess_type(encoding.unifromlocal(fctx.path())) |
|
779 | mimeguess, _enc = mimetypes.guess_type(encoding.unifromlocal(fctx.path())) | |
780 | if mimeguess: |
|
780 | if mimeguess: | |
781 | mimeguess = pycompat.bytestr(mimeguess) |
|
781 | mimeguess = pycompat.bytestr(mimeguess) | |
782 | pchange.metadata[b'new:file:mime-type'] = mimeguess |
|
782 | pchange.metadata[b'new:file:mime-type'] = mimeguess | |
783 | if mimeguess.startswith(b'image/'): |
|
783 | if mimeguess.startswith(b'image/'): | |
784 | pchange.fileType = DiffFileType.IMAGE |
|
784 | pchange.fileType = DiffFileType.IMAGE | |
785 |
|
785 | |||
786 |
|
786 | |||
787 | # Copied from mercurial/patch.py |
|
787 | # Copied from mercurial/patch.py | |
788 | gitmode = {b'l': b'120000', b'x': b'100755', b'': b'100644'} |
|
788 | gitmode = {b'l': b'120000', b'x': b'100755', b'': b'100644'} | |
789 |
|
789 | |||
790 |
|
790 | |||
791 | def notutf8(fctx): |
|
791 | def notutf8(fctx): | |
792 | """detect non-UTF-8 text files since Phabricator requires them to be marked |
|
792 | """detect non-UTF-8 text files since Phabricator requires them to be marked | |
793 | as binary |
|
793 | as binary | |
794 | """ |
|
794 | """ | |
795 | try: |
|
795 | try: | |
796 | fctx.data().decode('utf-8') |
|
796 | fctx.data().decode('utf-8') | |
797 | if fctx.parents(): |
|
797 | if fctx.parents(): | |
798 | fctx.p1().data().decode('utf-8') |
|
798 | fctx.p1().data().decode('utf-8') | |
799 | return False |
|
799 | return False | |
800 | except UnicodeDecodeError: |
|
800 | except UnicodeDecodeError: | |
801 | fctx.repo().ui.write( |
|
801 | fctx.repo().ui.write( | |
802 | _(b'file %s detected as non-UTF-8, marked as binary\n') |
|
802 | _(b'file %s detected as non-UTF-8, marked as binary\n') | |
803 | % fctx.path() |
|
803 | % fctx.path() | |
804 | ) |
|
804 | ) | |
805 | return True |
|
805 | return True | |
806 |
|
806 | |||
807 |
|
807 | |||
808 | def addremoved(pdiff, ctx, removed): |
|
808 | def addremoved(pdiff, ctx, removed): | |
809 | """add removed files to the phabdiff. Shouldn't include moves""" |
|
809 | """add removed files to the phabdiff. Shouldn't include moves""" | |
810 | for fname in removed: |
|
810 | for fname in removed: | |
811 | pchange = phabchange( |
|
811 | pchange = phabchange( | |
812 | currentPath=fname, oldPath=fname, type=DiffChangeType.DELETE |
|
812 | currentPath=fname, oldPath=fname, type=DiffChangeType.DELETE | |
813 | ) |
|
813 | ) | |
814 | pchange.addoldmode(gitmode[ctx.p1()[fname].flags()]) |
|
814 | pchange.addoldmode(gitmode[ctx.p1()[fname].flags()]) | |
815 | fctx = ctx.p1()[fname] |
|
815 | fctx = ctx.p1()[fname] | |
816 | if not (fctx.isbinary() or notutf8(fctx)): |
|
816 | if not (fctx.isbinary() or notutf8(fctx)): | |
817 | maketext(pchange, ctx, fname) |
|
817 | maketext(pchange, ctx, fname) | |
818 |
|
818 | |||
819 | pdiff.addchange(pchange) |
|
819 | pdiff.addchange(pchange) | |
820 |
|
820 | |||
821 |
|
821 | |||
822 | def addmodified(pdiff, ctx, modified): |
|
822 | def addmodified(pdiff, ctx, modified): | |
823 | """add modified files to the phabdiff""" |
|
823 | """add modified files to the phabdiff""" | |
824 | for fname in modified: |
|
824 | for fname in modified: | |
825 | fctx = ctx[fname] |
|
825 | fctx = ctx[fname] | |
826 | pchange = phabchange(currentPath=fname, oldPath=fname) |
|
826 | pchange = phabchange(currentPath=fname, oldPath=fname) | |
827 | filemode = gitmode[ctx[fname].flags()] |
|
827 | filemode = gitmode[ctx[fname].flags()] | |
828 | originalmode = gitmode[ctx.p1()[fname].flags()] |
|
828 | originalmode = gitmode[ctx.p1()[fname].flags()] | |
829 | if filemode != originalmode: |
|
829 | if filemode != originalmode: | |
830 | pchange.addoldmode(originalmode) |
|
830 | pchange.addoldmode(originalmode) | |
831 | pchange.addnewmode(filemode) |
|
831 | pchange.addnewmode(filemode) | |
832 |
|
832 | |||
833 | if fctx.isbinary() or notutf8(fctx): |
|
833 | if fctx.isbinary() or notutf8(fctx): | |
834 | makebinary(pchange, fctx) |
|
834 | makebinary(pchange, fctx) | |
835 | addoldbinary(pchange, fctx) |
|
835 | addoldbinary(pchange, fctx) | |
836 | else: |
|
836 | else: | |
837 | maketext(pchange, ctx, fname) |
|
837 | maketext(pchange, ctx, fname) | |
838 |
|
838 | |||
839 | pdiff.addchange(pchange) |
|
839 | pdiff.addchange(pchange) | |
840 |
|
840 | |||
841 |
|
841 | |||
842 | def addadded(pdiff, ctx, added, removed): |
|
842 | def addadded(pdiff, ctx, added, removed): | |
843 | """add file adds to the phabdiff, both new files and copies/moves""" |
|
843 | """add file adds to the phabdiff, both new files and copies/moves""" | |
844 | # Keep track of files that've been recorded as moved/copied, so if there are |
|
844 | # Keep track of files that've been recorded as moved/copied, so if there are | |
845 | # additional copies we can mark them (moves get removed from removed) |
|
845 | # additional copies we can mark them (moves get removed from removed) | |
846 | copiedchanges = {} |
|
846 | copiedchanges = {} | |
847 | movedchanges = {} |
|
847 | movedchanges = {} | |
848 | for fname in added: |
|
848 | for fname in added: | |
849 | fctx = ctx[fname] |
|
849 | fctx = ctx[fname] | |
850 | pchange = phabchange(currentPath=fname) |
|
850 | pchange = phabchange(currentPath=fname) | |
851 |
|
851 | |||
852 | filemode = gitmode[ctx[fname].flags()] |
|
852 | filemode = gitmode[ctx[fname].flags()] | |
853 | renamed = fctx.renamed() |
|
853 | renamed = fctx.renamed() | |
854 |
|
854 | |||
855 | if renamed: |
|
855 | if renamed: | |
856 | originalfname = renamed[0] |
|
856 | originalfname = renamed[0] | |
857 | originalmode = gitmode[ctx.p1()[originalfname].flags()] |
|
857 | originalmode = gitmode[ctx.p1()[originalfname].flags()] | |
858 | pchange.oldPath = originalfname |
|
858 | pchange.oldPath = originalfname | |
859 |
|
859 | |||
860 | if originalfname in removed: |
|
860 | if originalfname in removed: | |
861 | origpchange = phabchange( |
|
861 | origpchange = phabchange( | |
862 | currentPath=originalfname, |
|
862 | currentPath=originalfname, | |
863 | oldPath=originalfname, |
|
863 | oldPath=originalfname, | |
864 | type=DiffChangeType.MOVE_AWAY, |
|
864 | type=DiffChangeType.MOVE_AWAY, | |
865 | awayPaths=[fname], |
|
865 | awayPaths=[fname], | |
866 | ) |
|
866 | ) | |
867 | movedchanges[originalfname] = origpchange |
|
867 | movedchanges[originalfname] = origpchange | |
868 | removed.remove(originalfname) |
|
868 | removed.remove(originalfname) | |
869 | pchange.type = DiffChangeType.MOVE_HERE |
|
869 | pchange.type = DiffChangeType.MOVE_HERE | |
870 | elif originalfname in movedchanges: |
|
870 | elif originalfname in movedchanges: | |
871 | movedchanges[originalfname].type = DiffChangeType.MULTICOPY |
|
871 | movedchanges[originalfname].type = DiffChangeType.MULTICOPY | |
872 | movedchanges[originalfname].awayPaths.append(fname) |
|
872 | movedchanges[originalfname].awayPaths.append(fname) | |
873 | pchange.type = DiffChangeType.COPY_HERE |
|
873 | pchange.type = DiffChangeType.COPY_HERE | |
874 | else: # pure copy |
|
874 | else: # pure copy | |
875 | if originalfname not in copiedchanges: |
|
875 | if originalfname not in copiedchanges: | |
876 | origpchange = phabchange( |
|
876 | origpchange = phabchange( | |
877 | currentPath=originalfname, type=DiffChangeType.COPY_AWAY |
|
877 | currentPath=originalfname, type=DiffChangeType.COPY_AWAY | |
878 | ) |
|
878 | ) | |
879 | copiedchanges[originalfname] = origpchange |
|
879 | copiedchanges[originalfname] = origpchange | |
880 | else: |
|
880 | else: | |
881 | origpchange = copiedchanges[originalfname] |
|
881 | origpchange = copiedchanges[originalfname] | |
882 | origpchange.awayPaths.append(fname) |
|
882 | origpchange.awayPaths.append(fname) | |
883 | pchange.type = DiffChangeType.COPY_HERE |
|
883 | pchange.type = DiffChangeType.COPY_HERE | |
884 |
|
884 | |||
885 | if filemode != originalmode: |
|
885 | if filemode != originalmode: | |
886 | pchange.addoldmode(originalmode) |
|
886 | pchange.addoldmode(originalmode) | |
887 | pchange.addnewmode(filemode) |
|
887 | pchange.addnewmode(filemode) | |
888 | else: # Brand-new file |
|
888 | else: # Brand-new file | |
889 | pchange.addnewmode(gitmode[fctx.flags()]) |
|
889 | pchange.addnewmode(gitmode[fctx.flags()]) | |
890 | pchange.type = DiffChangeType.ADD |
|
890 | pchange.type = DiffChangeType.ADD | |
891 |
|
891 | |||
892 | if fctx.isbinary() or notutf8(fctx): |
|
892 | if fctx.isbinary() or notutf8(fctx): | |
893 | makebinary(pchange, fctx) |
|
893 | makebinary(pchange, fctx) | |
894 | if renamed: |
|
894 | if renamed: | |
895 |
addoldbinary(pchange, fctx |
|
895 | addoldbinary(pchange, fctx) | |
896 | else: |
|
896 | else: | |
897 | maketext(pchange, ctx, fname) |
|
897 | maketext(pchange, ctx, fname) | |
898 |
|
898 | |||
899 | pdiff.addchange(pchange) |
|
899 | pdiff.addchange(pchange) | |
900 |
|
900 | |||
901 | for _path, copiedchange in copiedchanges.items(): |
|
901 | for _path, copiedchange in copiedchanges.items(): | |
902 | pdiff.addchange(copiedchange) |
|
902 | pdiff.addchange(copiedchange) | |
903 | for _path, movedchange in movedchanges.items(): |
|
903 | for _path, movedchange in movedchanges.items(): | |
904 | pdiff.addchange(movedchange) |
|
904 | pdiff.addchange(movedchange) | |
905 |
|
905 | |||
906 |
|
906 | |||
907 | def creatediff(ctx): |
|
907 | def creatediff(ctx): | |
908 | """create a Differential Diff""" |
|
908 | """create a Differential Diff""" | |
909 | repo = ctx.repo() |
|
909 | repo = ctx.repo() | |
910 | repophid = getrepophid(repo) |
|
910 | repophid = getrepophid(repo) | |
911 | # Create a "Differential Diff" via "differential.creatediff" API |
|
911 | # Create a "Differential Diff" via "differential.creatediff" API | |
912 | pdiff = phabdiff( |
|
912 | pdiff = phabdiff( | |
913 | sourceControlBaseRevision=b'%s' % ctx.p1().hex(), |
|
913 | sourceControlBaseRevision=b'%s' % ctx.p1().hex(), | |
914 | branch=b'%s' % ctx.branch(), |
|
914 | branch=b'%s' % ctx.branch(), | |
915 | ) |
|
915 | ) | |
916 | modified, added, removed, _d, _u, _i, _c = ctx.p1().status(ctx) |
|
916 | modified, added, removed, _d, _u, _i, _c = ctx.p1().status(ctx) | |
917 | # addadded will remove moved files from removed, so addremoved won't get |
|
917 | # addadded will remove moved files from removed, so addremoved won't get | |
918 | # them |
|
918 | # them | |
919 | addadded(pdiff, ctx, added, removed) |
|
919 | addadded(pdiff, ctx, added, removed) | |
920 | addmodified(pdiff, ctx, modified) |
|
920 | addmodified(pdiff, ctx, modified) | |
921 | addremoved(pdiff, ctx, removed) |
|
921 | addremoved(pdiff, ctx, removed) | |
922 | if repophid: |
|
922 | if repophid: | |
923 | pdiff.repositoryPHID = repophid |
|
923 | pdiff.repositoryPHID = repophid | |
924 | diff = callconduit( |
|
924 | diff = callconduit( | |
925 | repo.ui, |
|
925 | repo.ui, | |
926 | b'differential.creatediff', |
|
926 | b'differential.creatediff', | |
927 | pycompat.byteskwargs(attr.asdict(pdiff)), |
|
927 | pycompat.byteskwargs(attr.asdict(pdiff)), | |
928 | ) |
|
928 | ) | |
929 | if not diff: |
|
929 | if not diff: | |
930 | raise error.Abort(_(b'cannot create diff for %s') % ctx) |
|
930 | raise error.Abort(_(b'cannot create diff for %s') % ctx) | |
931 | return diff |
|
931 | return diff | |
932 |
|
932 | |||
933 |
|
933 | |||
934 | def writediffproperties(ctx, diff): |
|
934 | def writediffproperties(ctx, diff): | |
935 | """write metadata to diff so patches could be applied losslessly""" |
|
935 | """write metadata to diff so patches could be applied losslessly""" | |
936 | # creatediff returns with a diffid but query returns with an id |
|
936 | # creatediff returns with a diffid but query returns with an id | |
937 | diffid = diff.get(b'diffid', diff.get(b'id')) |
|
937 | diffid = diff.get(b'diffid', diff.get(b'id')) | |
938 | params = { |
|
938 | params = { | |
939 | b'diff_id': diffid, |
|
939 | b'diff_id': diffid, | |
940 | b'name': b'hg:meta', |
|
940 | b'name': b'hg:meta', | |
941 | b'data': templatefilters.json( |
|
941 | b'data': templatefilters.json( | |
942 | { |
|
942 | { | |
943 | b'user': ctx.user(), |
|
943 | b'user': ctx.user(), | |
944 | b'date': b'%d %d' % ctx.date(), |
|
944 | b'date': b'%d %d' % ctx.date(), | |
945 | b'branch': ctx.branch(), |
|
945 | b'branch': ctx.branch(), | |
946 | b'node': ctx.hex(), |
|
946 | b'node': ctx.hex(), | |
947 | b'parent': ctx.p1().hex(), |
|
947 | b'parent': ctx.p1().hex(), | |
948 | } |
|
948 | } | |
949 | ), |
|
949 | ), | |
950 | } |
|
950 | } | |
951 | callconduit(ctx.repo().ui, b'differential.setdiffproperty', params) |
|
951 | callconduit(ctx.repo().ui, b'differential.setdiffproperty', params) | |
952 |
|
952 | |||
953 | params = { |
|
953 | params = { | |
954 | b'diff_id': diffid, |
|
954 | b'diff_id': diffid, | |
955 | b'name': b'local:commits', |
|
955 | b'name': b'local:commits', | |
956 | b'data': templatefilters.json( |
|
956 | b'data': templatefilters.json( | |
957 | { |
|
957 | { | |
958 | ctx.hex(): { |
|
958 | ctx.hex(): { | |
959 | b'author': stringutil.person(ctx.user()), |
|
959 | b'author': stringutil.person(ctx.user()), | |
960 | b'authorEmail': stringutil.email(ctx.user()), |
|
960 | b'authorEmail': stringutil.email(ctx.user()), | |
961 | b'time': int(ctx.date()[0]), |
|
961 | b'time': int(ctx.date()[0]), | |
962 | b'commit': ctx.hex(), |
|
962 | b'commit': ctx.hex(), | |
963 | b'parents': [ctx.p1().hex()], |
|
963 | b'parents': [ctx.p1().hex()], | |
964 | b'branch': ctx.branch(), |
|
964 | b'branch': ctx.branch(), | |
965 | }, |
|
965 | }, | |
966 | } |
|
966 | } | |
967 | ), |
|
967 | ), | |
968 | } |
|
968 | } | |
969 | callconduit(ctx.repo().ui, b'differential.setdiffproperty', params) |
|
969 | callconduit(ctx.repo().ui, b'differential.setdiffproperty', params) | |
970 |
|
970 | |||
971 |
|
971 | |||
972 | def createdifferentialrevision( |
|
972 | def createdifferentialrevision( | |
973 | ctx, |
|
973 | ctx, | |
974 | revid=None, |
|
974 | revid=None, | |
975 | parentrevphid=None, |
|
975 | parentrevphid=None, | |
976 | oldnode=None, |
|
976 | oldnode=None, | |
977 | olddiff=None, |
|
977 | olddiff=None, | |
978 | actions=None, |
|
978 | actions=None, | |
979 | comment=None, |
|
979 | comment=None, | |
980 | ): |
|
980 | ): | |
981 | """create or update a Differential Revision |
|
981 | """create or update a Differential Revision | |
982 |
|
982 | |||
983 | If revid is None, create a new Differential Revision, otherwise update |
|
983 | If revid is None, create a new Differential Revision, otherwise update | |
984 | revid. If parentrevphid is not None, set it as a dependency. |
|
984 | revid. If parentrevphid is not None, set it as a dependency. | |
985 |
|
985 | |||
986 | If oldnode is not None, check if the patch content (without commit message |
|
986 | If oldnode is not None, check if the patch content (without commit message | |
987 | and metadata) has changed before creating another diff. |
|
987 | and metadata) has changed before creating another diff. | |
988 |
|
988 | |||
989 | If actions is not None, they will be appended to the transaction. |
|
989 | If actions is not None, they will be appended to the transaction. | |
990 | """ |
|
990 | """ | |
991 | repo = ctx.repo() |
|
991 | repo = ctx.repo() | |
992 | if oldnode: |
|
992 | if oldnode: | |
993 | diffopts = mdiff.diffopts(git=True, context=32767) |
|
993 | diffopts = mdiff.diffopts(git=True, context=32767) | |
994 | oldctx = repo.unfiltered()[oldnode] |
|
994 | oldctx = repo.unfiltered()[oldnode] | |
995 | neednewdiff = getdiff(ctx, diffopts) != getdiff(oldctx, diffopts) |
|
995 | neednewdiff = getdiff(ctx, diffopts) != getdiff(oldctx, diffopts) | |
996 | else: |
|
996 | else: | |
997 | neednewdiff = True |
|
997 | neednewdiff = True | |
998 |
|
998 | |||
999 | transactions = [] |
|
999 | transactions = [] | |
1000 | if neednewdiff: |
|
1000 | if neednewdiff: | |
1001 | diff = creatediff(ctx) |
|
1001 | diff = creatediff(ctx) | |
1002 | transactions.append({b'type': b'update', b'value': diff[b'phid']}) |
|
1002 | transactions.append({b'type': b'update', b'value': diff[b'phid']}) | |
1003 | if comment: |
|
1003 | if comment: | |
1004 | transactions.append({b'type': b'comment', b'value': comment}) |
|
1004 | transactions.append({b'type': b'comment', b'value': comment}) | |
1005 | else: |
|
1005 | else: | |
1006 | # Even if we don't need to upload a new diff because the patch content |
|
1006 | # Even if we don't need to upload a new diff because the patch content | |
1007 | # does not change. We might still need to update its metadata so |
|
1007 | # does not change. We might still need to update its metadata so | |
1008 | # pushers could know the correct node metadata. |
|
1008 | # pushers could know the correct node metadata. | |
1009 | assert olddiff |
|
1009 | assert olddiff | |
1010 | diff = olddiff |
|
1010 | diff = olddiff | |
1011 | writediffproperties(ctx, diff) |
|
1011 | writediffproperties(ctx, diff) | |
1012 |
|
1012 | |||
1013 | # Set the parent Revision every time, so commit re-ordering is picked-up |
|
1013 | # Set the parent Revision every time, so commit re-ordering is picked-up | |
1014 | if parentrevphid: |
|
1014 | if parentrevphid: | |
1015 | transactions.append( |
|
1015 | transactions.append( | |
1016 | {b'type': b'parents.set', b'value': [parentrevphid]} |
|
1016 | {b'type': b'parents.set', b'value': [parentrevphid]} | |
1017 | ) |
|
1017 | ) | |
1018 |
|
1018 | |||
1019 | if actions: |
|
1019 | if actions: | |
1020 | transactions += actions |
|
1020 | transactions += actions | |
1021 |
|
1021 | |||
1022 | # Parse commit message and update related fields. |
|
1022 | # Parse commit message and update related fields. | |
1023 | desc = ctx.description() |
|
1023 | desc = ctx.description() | |
1024 | info = callconduit( |
|
1024 | info = callconduit( | |
1025 | repo.ui, b'differential.parsecommitmessage', {b'corpus': desc} |
|
1025 | repo.ui, b'differential.parsecommitmessage', {b'corpus': desc} | |
1026 | ) |
|
1026 | ) | |
1027 | for k, v in info[b'fields'].items(): |
|
1027 | for k, v in info[b'fields'].items(): | |
1028 | if k in [b'title', b'summary', b'testPlan']: |
|
1028 | if k in [b'title', b'summary', b'testPlan']: | |
1029 | transactions.append({b'type': k, b'value': v}) |
|
1029 | transactions.append({b'type': k, b'value': v}) | |
1030 |
|
1030 | |||
1031 | params = {b'transactions': transactions} |
|
1031 | params = {b'transactions': transactions} | |
1032 | if revid is not None: |
|
1032 | if revid is not None: | |
1033 | # Update an existing Differential Revision |
|
1033 | # Update an existing Differential Revision | |
1034 | params[b'objectIdentifier'] = revid |
|
1034 | params[b'objectIdentifier'] = revid | |
1035 |
|
1035 | |||
1036 | revision = callconduit(repo.ui, b'differential.revision.edit', params) |
|
1036 | revision = callconduit(repo.ui, b'differential.revision.edit', params) | |
1037 | if not revision: |
|
1037 | if not revision: | |
1038 | raise error.Abort(_(b'cannot create revision for %s') % ctx) |
|
1038 | raise error.Abort(_(b'cannot create revision for %s') % ctx) | |
1039 |
|
1039 | |||
1040 | return revision, diff |
|
1040 | return revision, diff | |
1041 |
|
1041 | |||
1042 |
|
1042 | |||
1043 | def userphids(repo, names): |
|
1043 | def userphids(repo, names): | |
1044 | """convert user names to PHIDs""" |
|
1044 | """convert user names to PHIDs""" | |
1045 | names = [name.lower() for name in names] |
|
1045 | names = [name.lower() for name in names] | |
1046 | query = {b'constraints': {b'usernames': names}} |
|
1046 | query = {b'constraints': {b'usernames': names}} | |
1047 | result = callconduit(repo.ui, b'user.search', query) |
|
1047 | result = callconduit(repo.ui, b'user.search', query) | |
1048 | # username not found is not an error of the API. So check if we have missed |
|
1048 | # username not found is not an error of the API. So check if we have missed | |
1049 | # some names here. |
|
1049 | # some names here. | |
1050 | data = result[b'data'] |
|
1050 | data = result[b'data'] | |
1051 | resolved = set(entry[b'fields'][b'username'].lower() for entry in data) |
|
1051 | resolved = set(entry[b'fields'][b'username'].lower() for entry in data) | |
1052 | unresolved = set(names) - resolved |
|
1052 | unresolved = set(names) - resolved | |
1053 | if unresolved: |
|
1053 | if unresolved: | |
1054 | raise error.Abort( |
|
1054 | raise error.Abort( | |
1055 | _(b'unknown username: %s') % b' '.join(sorted(unresolved)) |
|
1055 | _(b'unknown username: %s') % b' '.join(sorted(unresolved)) | |
1056 | ) |
|
1056 | ) | |
1057 | return [entry[b'phid'] for entry in data] |
|
1057 | return [entry[b'phid'] for entry in data] | |
1058 |
|
1058 | |||
1059 |
|
1059 | |||
1060 | @vcrcommand( |
|
1060 | @vcrcommand( | |
1061 | b'phabsend', |
|
1061 | b'phabsend', | |
1062 | [ |
|
1062 | [ | |
1063 | (b'r', b'rev', [], _(b'revisions to send'), _(b'REV')), |
|
1063 | (b'r', b'rev', [], _(b'revisions to send'), _(b'REV')), | |
1064 | (b'', b'amend', True, _(b'update commit messages')), |
|
1064 | (b'', b'amend', True, _(b'update commit messages')), | |
1065 | (b'', b'reviewer', [], _(b'specify reviewers')), |
|
1065 | (b'', b'reviewer', [], _(b'specify reviewers')), | |
1066 | (b'', b'blocker', [], _(b'specify blocking reviewers')), |
|
1066 | (b'', b'blocker', [], _(b'specify blocking reviewers')), | |
1067 | ( |
|
1067 | ( | |
1068 | b'm', |
|
1068 | b'm', | |
1069 | b'comment', |
|
1069 | b'comment', | |
1070 | b'', |
|
1070 | b'', | |
1071 | _(b'add a comment to Revisions with new/updated Diffs'), |
|
1071 | _(b'add a comment to Revisions with new/updated Diffs'), | |
1072 | ), |
|
1072 | ), | |
1073 | (b'', b'confirm', None, _(b'ask for confirmation before sending')), |
|
1073 | (b'', b'confirm', None, _(b'ask for confirmation before sending')), | |
1074 | ], |
|
1074 | ], | |
1075 | _(b'REV [OPTIONS]'), |
|
1075 | _(b'REV [OPTIONS]'), | |
1076 | helpcategory=command.CATEGORY_IMPORT_EXPORT, |
|
1076 | helpcategory=command.CATEGORY_IMPORT_EXPORT, | |
1077 | ) |
|
1077 | ) | |
1078 | def phabsend(ui, repo, *revs, **opts): |
|
1078 | def phabsend(ui, repo, *revs, **opts): | |
1079 | """upload changesets to Phabricator |
|
1079 | """upload changesets to Phabricator | |
1080 |
|
1080 | |||
1081 | If there are multiple revisions specified, they will be send as a stack |
|
1081 | If there are multiple revisions specified, they will be send as a stack | |
1082 | with a linear dependencies relationship using the order specified by the |
|
1082 | with a linear dependencies relationship using the order specified by the | |
1083 | revset. |
|
1083 | revset. | |
1084 |
|
1084 | |||
1085 | For the first time uploading changesets, local tags will be created to |
|
1085 | For the first time uploading changesets, local tags will be created to | |
1086 | maintain the association. After the first time, phabsend will check |
|
1086 | maintain the association. After the first time, phabsend will check | |
1087 | obsstore and tags information so it can figure out whether to update an |
|
1087 | obsstore and tags information so it can figure out whether to update an | |
1088 | existing Differential Revision, or create a new one. |
|
1088 | existing Differential Revision, or create a new one. | |
1089 |
|
1089 | |||
1090 | If --amend is set, update commit messages so they have the |
|
1090 | If --amend is set, update commit messages so they have the | |
1091 | ``Differential Revision`` URL, remove related tags. This is similar to what |
|
1091 | ``Differential Revision`` URL, remove related tags. This is similar to what | |
1092 | arcanist will do, and is more desired in author-push workflows. Otherwise, |
|
1092 | arcanist will do, and is more desired in author-push workflows. Otherwise, | |
1093 | use local tags to record the ``Differential Revision`` association. |
|
1093 | use local tags to record the ``Differential Revision`` association. | |
1094 |
|
1094 | |||
1095 | The --confirm option lets you confirm changesets before sending them. You |
|
1095 | The --confirm option lets you confirm changesets before sending them. You | |
1096 | can also add following to your configuration file to make it default |
|
1096 | can also add following to your configuration file to make it default | |
1097 | behaviour:: |
|
1097 | behaviour:: | |
1098 |
|
1098 | |||
1099 | [phabsend] |
|
1099 | [phabsend] | |
1100 | confirm = true |
|
1100 | confirm = true | |
1101 |
|
1101 | |||
1102 | phabsend will check obsstore and the above association to decide whether to |
|
1102 | phabsend will check obsstore and the above association to decide whether to | |
1103 | update an existing Differential Revision, or create a new one. |
|
1103 | update an existing Differential Revision, or create a new one. | |
1104 | """ |
|
1104 | """ | |
1105 | opts = pycompat.byteskwargs(opts) |
|
1105 | opts = pycompat.byteskwargs(opts) | |
1106 | revs = list(revs) + opts.get(b'rev', []) |
|
1106 | revs = list(revs) + opts.get(b'rev', []) | |
1107 | revs = scmutil.revrange(repo, revs) |
|
1107 | revs = scmutil.revrange(repo, revs) | |
1108 | revs.sort() # ascending order to preserve topological parent/child in phab |
|
1108 | revs.sort() # ascending order to preserve topological parent/child in phab | |
1109 |
|
1109 | |||
1110 | if not revs: |
|
1110 | if not revs: | |
1111 | raise error.Abort(_(b'phabsend requires at least one changeset')) |
|
1111 | raise error.Abort(_(b'phabsend requires at least one changeset')) | |
1112 | if opts.get(b'amend'): |
|
1112 | if opts.get(b'amend'): | |
1113 | cmdutil.checkunfinished(repo) |
|
1113 | cmdutil.checkunfinished(repo) | |
1114 |
|
1114 | |||
1115 | # {newnode: (oldnode, olddiff, olddrev} |
|
1115 | # {newnode: (oldnode, olddiff, olddrev} | |
1116 | oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs]) |
|
1116 | oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs]) | |
1117 |
|
1117 | |||
1118 | confirm = ui.configbool(b'phabsend', b'confirm') |
|
1118 | confirm = ui.configbool(b'phabsend', b'confirm') | |
1119 | confirm |= bool(opts.get(b'confirm')) |
|
1119 | confirm |= bool(opts.get(b'confirm')) | |
1120 | if confirm: |
|
1120 | if confirm: | |
1121 | confirmed = _confirmbeforesend(repo, revs, oldmap) |
|
1121 | confirmed = _confirmbeforesend(repo, revs, oldmap) | |
1122 | if not confirmed: |
|
1122 | if not confirmed: | |
1123 | raise error.Abort(_(b'phabsend cancelled')) |
|
1123 | raise error.Abort(_(b'phabsend cancelled')) | |
1124 |
|
1124 | |||
1125 | actions = [] |
|
1125 | actions = [] | |
1126 | reviewers = opts.get(b'reviewer', []) |
|
1126 | reviewers = opts.get(b'reviewer', []) | |
1127 | blockers = opts.get(b'blocker', []) |
|
1127 | blockers = opts.get(b'blocker', []) | |
1128 | phids = [] |
|
1128 | phids = [] | |
1129 | if reviewers: |
|
1129 | if reviewers: | |
1130 | phids.extend(userphids(repo, reviewers)) |
|
1130 | phids.extend(userphids(repo, reviewers)) | |
1131 | if blockers: |
|
1131 | if blockers: | |
1132 | phids.extend( |
|
1132 | phids.extend( | |
1133 | map(lambda phid: b'blocking(%s)' % phid, userphids(repo, blockers)) |
|
1133 | map(lambda phid: b'blocking(%s)' % phid, userphids(repo, blockers)) | |
1134 | ) |
|
1134 | ) | |
1135 | if phids: |
|
1135 | if phids: | |
1136 | actions.append({b'type': b'reviewers.add', b'value': phids}) |
|
1136 | actions.append({b'type': b'reviewers.add', b'value': phids}) | |
1137 |
|
1137 | |||
1138 | drevids = [] # [int] |
|
1138 | drevids = [] # [int] | |
1139 | diffmap = {} # {newnode: diff} |
|
1139 | diffmap = {} # {newnode: diff} | |
1140 |
|
1140 | |||
1141 | # Send patches one by one so we know their Differential Revision PHIDs and |
|
1141 | # Send patches one by one so we know their Differential Revision PHIDs and | |
1142 | # can provide dependency relationship |
|
1142 | # can provide dependency relationship | |
1143 | lastrevphid = None |
|
1143 | lastrevphid = None | |
1144 | for rev in revs: |
|
1144 | for rev in revs: | |
1145 | ui.debug(b'sending rev %d\n' % rev) |
|
1145 | ui.debug(b'sending rev %d\n' % rev) | |
1146 | ctx = repo[rev] |
|
1146 | ctx = repo[rev] | |
1147 |
|
1147 | |||
1148 | # Get Differential Revision ID |
|
1148 | # Get Differential Revision ID | |
1149 | oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None)) |
|
1149 | oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None)) | |
1150 | if oldnode != ctx.node() or opts.get(b'amend'): |
|
1150 | if oldnode != ctx.node() or opts.get(b'amend'): | |
1151 | # Create or update Differential Revision |
|
1151 | # Create or update Differential Revision | |
1152 | revision, diff = createdifferentialrevision( |
|
1152 | revision, diff = createdifferentialrevision( | |
1153 | ctx, |
|
1153 | ctx, | |
1154 | revid, |
|
1154 | revid, | |
1155 | lastrevphid, |
|
1155 | lastrevphid, | |
1156 | oldnode, |
|
1156 | oldnode, | |
1157 | olddiff, |
|
1157 | olddiff, | |
1158 | actions, |
|
1158 | actions, | |
1159 | opts.get(b'comment'), |
|
1159 | opts.get(b'comment'), | |
1160 | ) |
|
1160 | ) | |
1161 | diffmap[ctx.node()] = diff |
|
1161 | diffmap[ctx.node()] = diff | |
1162 | newrevid = int(revision[b'object'][b'id']) |
|
1162 | newrevid = int(revision[b'object'][b'id']) | |
1163 | newrevphid = revision[b'object'][b'phid'] |
|
1163 | newrevphid = revision[b'object'][b'phid'] | |
1164 | if revid: |
|
1164 | if revid: | |
1165 | action = b'updated' |
|
1165 | action = b'updated' | |
1166 | else: |
|
1166 | else: | |
1167 | action = b'created' |
|
1167 | action = b'created' | |
1168 |
|
1168 | |||
1169 | # Create a local tag to note the association, if commit message |
|
1169 | # Create a local tag to note the association, if commit message | |
1170 | # does not have it already |
|
1170 | # does not have it already | |
1171 | m = _differentialrevisiondescre.search(ctx.description()) |
|
1171 | m = _differentialrevisiondescre.search(ctx.description()) | |
1172 | if not m or int(m.group('id')) != newrevid: |
|
1172 | if not m or int(m.group('id')) != newrevid: | |
1173 | tagname = b'D%d' % newrevid |
|
1173 | tagname = b'D%d' % newrevid | |
1174 | tags.tag( |
|
1174 | tags.tag( | |
1175 | repo, |
|
1175 | repo, | |
1176 | tagname, |
|
1176 | tagname, | |
1177 | ctx.node(), |
|
1177 | ctx.node(), | |
1178 | message=None, |
|
1178 | message=None, | |
1179 | user=None, |
|
1179 | user=None, | |
1180 | date=None, |
|
1180 | date=None, | |
1181 | local=True, |
|
1181 | local=True, | |
1182 | ) |
|
1182 | ) | |
1183 | else: |
|
1183 | else: | |
1184 | # Nothing changed. But still set "newrevphid" so the next revision |
|
1184 | # Nothing changed. But still set "newrevphid" so the next revision | |
1185 | # could depend on this one and "newrevid" for the summary line. |
|
1185 | # could depend on this one and "newrevid" for the summary line. | |
1186 | newrevphid = querydrev(repo, b'%d' % revid)[0][b'phid'] |
|
1186 | newrevphid = querydrev(repo, b'%d' % revid)[0][b'phid'] | |
1187 | newrevid = revid |
|
1187 | newrevid = revid | |
1188 | action = b'skipped' |
|
1188 | action = b'skipped' | |
1189 |
|
1189 | |||
1190 | actiondesc = ui.label( |
|
1190 | actiondesc = ui.label( | |
1191 | { |
|
1191 | { | |
1192 | b'created': _(b'created'), |
|
1192 | b'created': _(b'created'), | |
1193 | b'skipped': _(b'skipped'), |
|
1193 | b'skipped': _(b'skipped'), | |
1194 | b'updated': _(b'updated'), |
|
1194 | b'updated': _(b'updated'), | |
1195 | }[action], |
|
1195 | }[action], | |
1196 | b'phabricator.action.%s' % action, |
|
1196 | b'phabricator.action.%s' % action, | |
1197 | ) |
|
1197 | ) | |
1198 | drevdesc = ui.label(b'D%d' % newrevid, b'phabricator.drev') |
|
1198 | drevdesc = ui.label(b'D%d' % newrevid, b'phabricator.drev') | |
1199 | nodedesc = ui.label(bytes(ctx), b'phabricator.node') |
|
1199 | nodedesc = ui.label(bytes(ctx), b'phabricator.node') | |
1200 | desc = ui.label(ctx.description().split(b'\n')[0], b'phabricator.desc') |
|
1200 | desc = ui.label(ctx.description().split(b'\n')[0], b'phabricator.desc') | |
1201 | ui.write( |
|
1201 | ui.write( | |
1202 | _(b'%s - %s - %s: %s\n') % (drevdesc, actiondesc, nodedesc, desc) |
|
1202 | _(b'%s - %s - %s: %s\n') % (drevdesc, actiondesc, nodedesc, desc) | |
1203 | ) |
|
1203 | ) | |
1204 | drevids.append(newrevid) |
|
1204 | drevids.append(newrevid) | |
1205 | lastrevphid = newrevphid |
|
1205 | lastrevphid = newrevphid | |
1206 |
|
1206 | |||
1207 | # Update commit messages and remove tags |
|
1207 | # Update commit messages and remove tags | |
1208 | if opts.get(b'amend'): |
|
1208 | if opts.get(b'amend'): | |
1209 | unfi = repo.unfiltered() |
|
1209 | unfi = repo.unfiltered() | |
1210 | drevs = callconduit(ui, b'differential.query', {b'ids': drevids}) |
|
1210 | drevs = callconduit(ui, b'differential.query', {b'ids': drevids}) | |
1211 | with repo.wlock(), repo.lock(), repo.transaction(b'phabsend'): |
|
1211 | with repo.wlock(), repo.lock(), repo.transaction(b'phabsend'): | |
1212 | wnode = unfi[b'.'].node() |
|
1212 | wnode = unfi[b'.'].node() | |
1213 | mapping = {} # {oldnode: [newnode]} |
|
1213 | mapping = {} # {oldnode: [newnode]} | |
1214 | for i, rev in enumerate(revs): |
|
1214 | for i, rev in enumerate(revs): | |
1215 | old = unfi[rev] |
|
1215 | old = unfi[rev] | |
1216 | drevid = drevids[i] |
|
1216 | drevid = drevids[i] | |
1217 | drev = [d for d in drevs if int(d[b'id']) == drevid][0] |
|
1217 | drev = [d for d in drevs if int(d[b'id']) == drevid][0] | |
1218 | newdesc = getdescfromdrev(drev) |
|
1218 | newdesc = getdescfromdrev(drev) | |
1219 | # Make sure commit message contain "Differential Revision" |
|
1219 | # Make sure commit message contain "Differential Revision" | |
1220 | if old.description() != newdesc: |
|
1220 | if old.description() != newdesc: | |
1221 | if old.phase() == phases.public: |
|
1221 | if old.phase() == phases.public: | |
1222 | ui.warn( |
|
1222 | ui.warn( | |
1223 | _(b"warning: not updating public commit %s\n") |
|
1223 | _(b"warning: not updating public commit %s\n") | |
1224 | % scmutil.formatchangeid(old) |
|
1224 | % scmutil.formatchangeid(old) | |
1225 | ) |
|
1225 | ) | |
1226 | continue |
|
1226 | continue | |
1227 | parents = [ |
|
1227 | parents = [ | |
1228 | mapping.get(old.p1().node(), (old.p1(),))[0], |
|
1228 | mapping.get(old.p1().node(), (old.p1(),))[0], | |
1229 | mapping.get(old.p2().node(), (old.p2(),))[0], |
|
1229 | mapping.get(old.p2().node(), (old.p2(),))[0], | |
1230 | ] |
|
1230 | ] | |
1231 | new = context.metadataonlyctx( |
|
1231 | new = context.metadataonlyctx( | |
1232 | repo, |
|
1232 | repo, | |
1233 | old, |
|
1233 | old, | |
1234 | parents=parents, |
|
1234 | parents=parents, | |
1235 | text=newdesc, |
|
1235 | text=newdesc, | |
1236 | user=old.user(), |
|
1236 | user=old.user(), | |
1237 | date=old.date(), |
|
1237 | date=old.date(), | |
1238 | extra=old.extra(), |
|
1238 | extra=old.extra(), | |
1239 | ) |
|
1239 | ) | |
1240 |
|
1240 | |||
1241 | newnode = new.commit() |
|
1241 | newnode = new.commit() | |
1242 |
|
1242 | |||
1243 | mapping[old.node()] = [newnode] |
|
1243 | mapping[old.node()] = [newnode] | |
1244 | # Update diff property |
|
1244 | # Update diff property | |
1245 | # If it fails just warn and keep going, otherwise the DREV |
|
1245 | # If it fails just warn and keep going, otherwise the DREV | |
1246 | # associations will be lost |
|
1246 | # associations will be lost | |
1247 | try: |
|
1247 | try: | |
1248 | writediffproperties(unfi[newnode], diffmap[old.node()]) |
|
1248 | writediffproperties(unfi[newnode], diffmap[old.node()]) | |
1249 | except util.urlerr.urlerror: |
|
1249 | except util.urlerr.urlerror: | |
1250 | ui.warnnoi18n( |
|
1250 | ui.warnnoi18n( | |
1251 | b'Failed to update metadata for D%d\n' % drevid |
|
1251 | b'Failed to update metadata for D%d\n' % drevid | |
1252 | ) |
|
1252 | ) | |
1253 | # Remove local tags since it's no longer necessary |
|
1253 | # Remove local tags since it's no longer necessary | |
1254 | tagname = b'D%d' % drevid |
|
1254 | tagname = b'D%d' % drevid | |
1255 | if tagname in repo.tags(): |
|
1255 | if tagname in repo.tags(): | |
1256 | tags.tag( |
|
1256 | tags.tag( | |
1257 | repo, |
|
1257 | repo, | |
1258 | tagname, |
|
1258 | tagname, | |
1259 | nullid, |
|
1259 | nullid, | |
1260 | message=None, |
|
1260 | message=None, | |
1261 | user=None, |
|
1261 | user=None, | |
1262 | date=None, |
|
1262 | date=None, | |
1263 | local=True, |
|
1263 | local=True, | |
1264 | ) |
|
1264 | ) | |
1265 | scmutil.cleanupnodes(repo, mapping, b'phabsend', fixphase=True) |
|
1265 | scmutil.cleanupnodes(repo, mapping, b'phabsend', fixphase=True) | |
1266 | if wnode in mapping: |
|
1266 | if wnode in mapping: | |
1267 | unfi.setparents(mapping[wnode][0]) |
|
1267 | unfi.setparents(mapping[wnode][0]) | |
1268 |
|
1268 | |||
1269 |
|
1269 | |||
1270 | # Map from "hg:meta" keys to header understood by "hg import". The order is |
|
1270 | # Map from "hg:meta" keys to header understood by "hg import". The order is | |
1271 | # consistent with "hg export" output. |
|
1271 | # consistent with "hg export" output. | |
1272 | _metanamemap = util.sortdict( |
|
1272 | _metanamemap = util.sortdict( | |
1273 | [ |
|
1273 | [ | |
1274 | (b'user', b'User'), |
|
1274 | (b'user', b'User'), | |
1275 | (b'date', b'Date'), |
|
1275 | (b'date', b'Date'), | |
1276 | (b'branch', b'Branch'), |
|
1276 | (b'branch', b'Branch'), | |
1277 | (b'node', b'Node ID'), |
|
1277 | (b'node', b'Node ID'), | |
1278 | (b'parent', b'Parent '), |
|
1278 | (b'parent', b'Parent '), | |
1279 | ] |
|
1279 | ] | |
1280 | ) |
|
1280 | ) | |
1281 |
|
1281 | |||
1282 |
|
1282 | |||
1283 | def _confirmbeforesend(repo, revs, oldmap): |
|
1283 | def _confirmbeforesend(repo, revs, oldmap): | |
1284 | url, token = readurltoken(repo.ui) |
|
1284 | url, token = readurltoken(repo.ui) | |
1285 | ui = repo.ui |
|
1285 | ui = repo.ui | |
1286 | for rev in revs: |
|
1286 | for rev in revs: | |
1287 | ctx = repo[rev] |
|
1287 | ctx = repo[rev] | |
1288 | desc = ctx.description().splitlines()[0] |
|
1288 | desc = ctx.description().splitlines()[0] | |
1289 | oldnode, olddiff, drevid = oldmap.get(ctx.node(), (None, None, None)) |
|
1289 | oldnode, olddiff, drevid = oldmap.get(ctx.node(), (None, None, None)) | |
1290 | if drevid: |
|
1290 | if drevid: | |
1291 | drevdesc = ui.label(b'D%d' % drevid, b'phabricator.drev') |
|
1291 | drevdesc = ui.label(b'D%d' % drevid, b'phabricator.drev') | |
1292 | else: |
|
1292 | else: | |
1293 | drevdesc = ui.label(_(b'NEW'), b'phabricator.drev') |
|
1293 | drevdesc = ui.label(_(b'NEW'), b'phabricator.drev') | |
1294 |
|
1294 | |||
1295 | ui.write( |
|
1295 | ui.write( | |
1296 | _(b'%s - %s: %s\n') |
|
1296 | _(b'%s - %s: %s\n') | |
1297 | % ( |
|
1297 | % ( | |
1298 | drevdesc, |
|
1298 | drevdesc, | |
1299 | ui.label(bytes(ctx), b'phabricator.node'), |
|
1299 | ui.label(bytes(ctx), b'phabricator.node'), | |
1300 | ui.label(desc, b'phabricator.desc'), |
|
1300 | ui.label(desc, b'phabricator.desc'), | |
1301 | ) |
|
1301 | ) | |
1302 | ) |
|
1302 | ) | |
1303 |
|
1303 | |||
1304 | if ui.promptchoice( |
|
1304 | if ui.promptchoice( | |
1305 | _(b'Send the above changes to %s (yn)?$$ &Yes $$ &No') % url |
|
1305 | _(b'Send the above changes to %s (yn)?$$ &Yes $$ &No') % url | |
1306 | ): |
|
1306 | ): | |
1307 | return False |
|
1307 | return False | |
1308 |
|
1308 | |||
1309 | return True |
|
1309 | return True | |
1310 |
|
1310 | |||
1311 |
|
1311 | |||
1312 | _knownstatusnames = { |
|
1312 | _knownstatusnames = { | |
1313 | b'accepted', |
|
1313 | b'accepted', | |
1314 | b'needsreview', |
|
1314 | b'needsreview', | |
1315 | b'needsrevision', |
|
1315 | b'needsrevision', | |
1316 | b'closed', |
|
1316 | b'closed', | |
1317 | b'abandoned', |
|
1317 | b'abandoned', | |
1318 | b'changesplanned', |
|
1318 | b'changesplanned', | |
1319 | } |
|
1319 | } | |
1320 |
|
1320 | |||
1321 |
|
1321 | |||
1322 | def _getstatusname(drev): |
|
1322 | def _getstatusname(drev): | |
1323 | """get normalized status name from a Differential Revision""" |
|
1323 | """get normalized status name from a Differential Revision""" | |
1324 | return drev[b'statusName'].replace(b' ', b'').lower() |
|
1324 | return drev[b'statusName'].replace(b' ', b'').lower() | |
1325 |
|
1325 | |||
1326 |
|
1326 | |||
1327 | # Small language to specify differential revisions. Support symbols: (), :X, |
|
1327 | # Small language to specify differential revisions. Support symbols: (), :X, | |
1328 | # +, and -. |
|
1328 | # +, and -. | |
1329 |
|
1329 | |||
1330 | _elements = { |
|
1330 | _elements = { | |
1331 | # token-type: binding-strength, primary, prefix, infix, suffix |
|
1331 | # token-type: binding-strength, primary, prefix, infix, suffix | |
1332 | b'(': (12, None, (b'group', 1, b')'), None, None), |
|
1332 | b'(': (12, None, (b'group', 1, b')'), None, None), | |
1333 | b':': (8, None, (b'ancestors', 8), None, None), |
|
1333 | b':': (8, None, (b'ancestors', 8), None, None), | |
1334 | b'&': (5, None, None, (b'and_', 5), None), |
|
1334 | b'&': (5, None, None, (b'and_', 5), None), | |
1335 | b'+': (4, None, None, (b'add', 4), None), |
|
1335 | b'+': (4, None, None, (b'add', 4), None), | |
1336 | b'-': (4, None, None, (b'sub', 4), None), |
|
1336 | b'-': (4, None, None, (b'sub', 4), None), | |
1337 | b')': (0, None, None, None, None), |
|
1337 | b')': (0, None, None, None, None), | |
1338 | b'symbol': (0, b'symbol', None, None, None), |
|
1338 | b'symbol': (0, b'symbol', None, None, None), | |
1339 | b'end': (0, None, None, None, None), |
|
1339 | b'end': (0, None, None, None, None), | |
1340 | } |
|
1340 | } | |
1341 |
|
1341 | |||
1342 |
|
1342 | |||
1343 | def _tokenize(text): |
|
1343 | def _tokenize(text): | |
1344 | view = memoryview(text) # zero-copy slice |
|
1344 | view = memoryview(text) # zero-copy slice | |
1345 | special = b'():+-& ' |
|
1345 | special = b'():+-& ' | |
1346 | pos = 0 |
|
1346 | pos = 0 | |
1347 | length = len(text) |
|
1347 | length = len(text) | |
1348 | while pos < length: |
|
1348 | while pos < length: | |
1349 | symbol = b''.join( |
|
1349 | symbol = b''.join( | |
1350 | itertools.takewhile( |
|
1350 | itertools.takewhile( | |
1351 | lambda ch: ch not in special, pycompat.iterbytestr(view[pos:]) |
|
1351 | lambda ch: ch not in special, pycompat.iterbytestr(view[pos:]) | |
1352 | ) |
|
1352 | ) | |
1353 | ) |
|
1353 | ) | |
1354 | if symbol: |
|
1354 | if symbol: | |
1355 | yield (b'symbol', symbol, pos) |
|
1355 | yield (b'symbol', symbol, pos) | |
1356 | pos += len(symbol) |
|
1356 | pos += len(symbol) | |
1357 | else: # special char, ignore space |
|
1357 | else: # special char, ignore space | |
1358 | if text[pos : pos + 1] != b' ': |
|
1358 | if text[pos : pos + 1] != b' ': | |
1359 | yield (text[pos : pos + 1], None, pos) |
|
1359 | yield (text[pos : pos + 1], None, pos) | |
1360 | pos += 1 |
|
1360 | pos += 1 | |
1361 | yield (b'end', None, pos) |
|
1361 | yield (b'end', None, pos) | |
1362 |
|
1362 | |||
1363 |
|
1363 | |||
1364 | def _parse(text): |
|
1364 | def _parse(text): | |
1365 | tree, pos = parser.parser(_elements).parse(_tokenize(text)) |
|
1365 | tree, pos = parser.parser(_elements).parse(_tokenize(text)) | |
1366 | if pos != len(text): |
|
1366 | if pos != len(text): | |
1367 | raise error.ParseError(b'invalid token', pos) |
|
1367 | raise error.ParseError(b'invalid token', pos) | |
1368 | return tree |
|
1368 | return tree | |
1369 |
|
1369 | |||
1370 |
|
1370 | |||
1371 | def _parsedrev(symbol): |
|
1371 | def _parsedrev(symbol): | |
1372 | """str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None""" |
|
1372 | """str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None""" | |
1373 | if symbol.startswith(b'D') and symbol[1:].isdigit(): |
|
1373 | if symbol.startswith(b'D') and symbol[1:].isdigit(): | |
1374 | return int(symbol[1:]) |
|
1374 | return int(symbol[1:]) | |
1375 | if symbol.isdigit(): |
|
1375 | if symbol.isdigit(): | |
1376 | return int(symbol) |
|
1376 | return int(symbol) | |
1377 |
|
1377 | |||
1378 |
|
1378 | |||
1379 | def _prefetchdrevs(tree): |
|
1379 | def _prefetchdrevs(tree): | |
1380 | """return ({single-drev-id}, {ancestor-drev-id}) to prefetch""" |
|
1380 | """return ({single-drev-id}, {ancestor-drev-id}) to prefetch""" | |
1381 | drevs = set() |
|
1381 | drevs = set() | |
1382 | ancestordrevs = set() |
|
1382 | ancestordrevs = set() | |
1383 | op = tree[0] |
|
1383 | op = tree[0] | |
1384 | if op == b'symbol': |
|
1384 | if op == b'symbol': | |
1385 | r = _parsedrev(tree[1]) |
|
1385 | r = _parsedrev(tree[1]) | |
1386 | if r: |
|
1386 | if r: | |
1387 | drevs.add(r) |
|
1387 | drevs.add(r) | |
1388 | elif op == b'ancestors': |
|
1388 | elif op == b'ancestors': | |
1389 | r, a = _prefetchdrevs(tree[1]) |
|
1389 | r, a = _prefetchdrevs(tree[1]) | |
1390 | drevs.update(r) |
|
1390 | drevs.update(r) | |
1391 | ancestordrevs.update(r) |
|
1391 | ancestordrevs.update(r) | |
1392 | ancestordrevs.update(a) |
|
1392 | ancestordrevs.update(a) | |
1393 | else: |
|
1393 | else: | |
1394 | for t in tree[1:]: |
|
1394 | for t in tree[1:]: | |
1395 | r, a = _prefetchdrevs(t) |
|
1395 | r, a = _prefetchdrevs(t) | |
1396 | drevs.update(r) |
|
1396 | drevs.update(r) | |
1397 | ancestordrevs.update(a) |
|
1397 | ancestordrevs.update(a) | |
1398 | return drevs, ancestordrevs |
|
1398 | return drevs, ancestordrevs | |
1399 |
|
1399 | |||
1400 |
|
1400 | |||
1401 | def querydrev(repo, spec): |
|
1401 | def querydrev(repo, spec): | |
1402 | """return a list of "Differential Revision" dicts |
|
1402 | """return a list of "Differential Revision" dicts | |
1403 |
|
1403 | |||
1404 | spec is a string using a simple query language, see docstring in phabread |
|
1404 | spec is a string using a simple query language, see docstring in phabread | |
1405 | for details. |
|
1405 | for details. | |
1406 |
|
1406 | |||
1407 | A "Differential Revision dict" looks like: |
|
1407 | A "Differential Revision dict" looks like: | |
1408 |
|
1408 | |||
1409 | { |
|
1409 | { | |
1410 | "id": "2", |
|
1410 | "id": "2", | |
1411 | "phid": "PHID-DREV-672qvysjcczopag46qty", |
|
1411 | "phid": "PHID-DREV-672qvysjcczopag46qty", | |
1412 | "title": "example", |
|
1412 | "title": "example", | |
1413 | "uri": "https://phab.example.com/D2", |
|
1413 | "uri": "https://phab.example.com/D2", | |
1414 | "dateCreated": "1499181406", |
|
1414 | "dateCreated": "1499181406", | |
1415 | "dateModified": "1499182103", |
|
1415 | "dateModified": "1499182103", | |
1416 | "authorPHID": "PHID-USER-tv3ohwc4v4jeu34otlye", |
|
1416 | "authorPHID": "PHID-USER-tv3ohwc4v4jeu34otlye", | |
1417 | "status": "0", |
|
1417 | "status": "0", | |
1418 | "statusName": "Needs Review", |
|
1418 | "statusName": "Needs Review", | |
1419 | "properties": [], |
|
1419 | "properties": [], | |
1420 | "branch": null, |
|
1420 | "branch": null, | |
1421 | "summary": "", |
|
1421 | "summary": "", | |
1422 | "testPlan": "", |
|
1422 | "testPlan": "", | |
1423 | "lineCount": "2", |
|
1423 | "lineCount": "2", | |
1424 | "activeDiffPHID": "PHID-DIFF-xoqnjkobbm6k4dk6hi72", |
|
1424 | "activeDiffPHID": "PHID-DIFF-xoqnjkobbm6k4dk6hi72", | |
1425 | "diffs": [ |
|
1425 | "diffs": [ | |
1426 | "3", |
|
1426 | "3", | |
1427 | "4", |
|
1427 | "4", | |
1428 | ], |
|
1428 | ], | |
1429 | "commits": [], |
|
1429 | "commits": [], | |
1430 | "reviewers": [], |
|
1430 | "reviewers": [], | |
1431 | "ccs": [], |
|
1431 | "ccs": [], | |
1432 | "hashes": [], |
|
1432 | "hashes": [], | |
1433 | "auxiliary": { |
|
1433 | "auxiliary": { | |
1434 | "phabricator:projects": [], |
|
1434 | "phabricator:projects": [], | |
1435 | "phabricator:depends-on": [ |
|
1435 | "phabricator:depends-on": [ | |
1436 | "PHID-DREV-gbapp366kutjebt7agcd" |
|
1436 | "PHID-DREV-gbapp366kutjebt7agcd" | |
1437 | ] |
|
1437 | ] | |
1438 | }, |
|
1438 | }, | |
1439 | "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv", |
|
1439 | "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv", | |
1440 | "sourcePath": null |
|
1440 | "sourcePath": null | |
1441 | } |
|
1441 | } | |
1442 | """ |
|
1442 | """ | |
1443 |
|
1443 | |||
1444 | def fetch(params): |
|
1444 | def fetch(params): | |
1445 | """params -> single drev or None""" |
|
1445 | """params -> single drev or None""" | |
1446 | key = (params.get(b'ids') or params.get(b'phids') or [None])[0] |
|
1446 | key = (params.get(b'ids') or params.get(b'phids') or [None])[0] | |
1447 | if key in prefetched: |
|
1447 | if key in prefetched: | |
1448 | return prefetched[key] |
|
1448 | return prefetched[key] | |
1449 | drevs = callconduit(repo.ui, b'differential.query', params) |
|
1449 | drevs = callconduit(repo.ui, b'differential.query', params) | |
1450 | # Fill prefetched with the result |
|
1450 | # Fill prefetched with the result | |
1451 | for drev in drevs: |
|
1451 | for drev in drevs: | |
1452 | prefetched[drev[b'phid']] = drev |
|
1452 | prefetched[drev[b'phid']] = drev | |
1453 | prefetched[int(drev[b'id'])] = drev |
|
1453 | prefetched[int(drev[b'id'])] = drev | |
1454 | if key not in prefetched: |
|
1454 | if key not in prefetched: | |
1455 | raise error.Abort( |
|
1455 | raise error.Abort( | |
1456 | _(b'cannot get Differential Revision %r') % params |
|
1456 | _(b'cannot get Differential Revision %r') % params | |
1457 | ) |
|
1457 | ) | |
1458 | return prefetched[key] |
|
1458 | return prefetched[key] | |
1459 |
|
1459 | |||
1460 | def getstack(topdrevids): |
|
1460 | def getstack(topdrevids): | |
1461 | """given a top, get a stack from the bottom, [id] -> [id]""" |
|
1461 | """given a top, get a stack from the bottom, [id] -> [id]""" | |
1462 | visited = set() |
|
1462 | visited = set() | |
1463 | result = [] |
|
1463 | result = [] | |
1464 | queue = [{b'ids': [i]} for i in topdrevids] |
|
1464 | queue = [{b'ids': [i]} for i in topdrevids] | |
1465 | while queue: |
|
1465 | while queue: | |
1466 | params = queue.pop() |
|
1466 | params = queue.pop() | |
1467 | drev = fetch(params) |
|
1467 | drev = fetch(params) | |
1468 | if drev[b'id'] in visited: |
|
1468 | if drev[b'id'] in visited: | |
1469 | continue |
|
1469 | continue | |
1470 | visited.add(drev[b'id']) |
|
1470 | visited.add(drev[b'id']) | |
1471 | result.append(int(drev[b'id'])) |
|
1471 | result.append(int(drev[b'id'])) | |
1472 | auxiliary = drev.get(b'auxiliary', {}) |
|
1472 | auxiliary = drev.get(b'auxiliary', {}) | |
1473 | depends = auxiliary.get(b'phabricator:depends-on', []) |
|
1473 | depends = auxiliary.get(b'phabricator:depends-on', []) | |
1474 | for phid in depends: |
|
1474 | for phid in depends: | |
1475 | queue.append({b'phids': [phid]}) |
|
1475 | queue.append({b'phids': [phid]}) | |
1476 | result.reverse() |
|
1476 | result.reverse() | |
1477 | return smartset.baseset(result) |
|
1477 | return smartset.baseset(result) | |
1478 |
|
1478 | |||
1479 | # Initialize prefetch cache |
|
1479 | # Initialize prefetch cache | |
1480 | prefetched = {} # {id or phid: drev} |
|
1480 | prefetched = {} # {id or phid: drev} | |
1481 |
|
1481 | |||
1482 | tree = _parse(spec) |
|
1482 | tree = _parse(spec) | |
1483 | drevs, ancestordrevs = _prefetchdrevs(tree) |
|
1483 | drevs, ancestordrevs = _prefetchdrevs(tree) | |
1484 |
|
1484 | |||
1485 | # developer config: phabricator.batchsize |
|
1485 | # developer config: phabricator.batchsize | |
1486 | batchsize = repo.ui.configint(b'phabricator', b'batchsize') |
|
1486 | batchsize = repo.ui.configint(b'phabricator', b'batchsize') | |
1487 |
|
1487 | |||
1488 | # Prefetch Differential Revisions in batch |
|
1488 | # Prefetch Differential Revisions in batch | |
1489 | tofetch = set(drevs) |
|
1489 | tofetch = set(drevs) | |
1490 | for r in ancestordrevs: |
|
1490 | for r in ancestordrevs: | |
1491 | tofetch.update(range(max(1, r - batchsize), r + 1)) |
|
1491 | tofetch.update(range(max(1, r - batchsize), r + 1)) | |
1492 | if drevs: |
|
1492 | if drevs: | |
1493 | fetch({b'ids': list(tofetch)}) |
|
1493 | fetch({b'ids': list(tofetch)}) | |
1494 | validids = sorted(set(getstack(list(ancestordrevs))) | set(drevs)) |
|
1494 | validids = sorted(set(getstack(list(ancestordrevs))) | set(drevs)) | |
1495 |
|
1495 | |||
1496 | # Walk through the tree, return smartsets |
|
1496 | # Walk through the tree, return smartsets | |
1497 | def walk(tree): |
|
1497 | def walk(tree): | |
1498 | op = tree[0] |
|
1498 | op = tree[0] | |
1499 | if op == b'symbol': |
|
1499 | if op == b'symbol': | |
1500 | drev = _parsedrev(tree[1]) |
|
1500 | drev = _parsedrev(tree[1]) | |
1501 | if drev: |
|
1501 | if drev: | |
1502 | return smartset.baseset([drev]) |
|
1502 | return smartset.baseset([drev]) | |
1503 | elif tree[1] in _knownstatusnames: |
|
1503 | elif tree[1] in _knownstatusnames: | |
1504 | drevs = [ |
|
1504 | drevs = [ | |
1505 | r |
|
1505 | r | |
1506 | for r in validids |
|
1506 | for r in validids | |
1507 | if _getstatusname(prefetched[r]) == tree[1] |
|
1507 | if _getstatusname(prefetched[r]) == tree[1] | |
1508 | ] |
|
1508 | ] | |
1509 | return smartset.baseset(drevs) |
|
1509 | return smartset.baseset(drevs) | |
1510 | else: |
|
1510 | else: | |
1511 | raise error.Abort(_(b'unknown symbol: %s') % tree[1]) |
|
1511 | raise error.Abort(_(b'unknown symbol: %s') % tree[1]) | |
1512 | elif op in {b'and_', b'add', b'sub'}: |
|
1512 | elif op in {b'and_', b'add', b'sub'}: | |
1513 | assert len(tree) == 3 |
|
1513 | assert len(tree) == 3 | |
1514 | return getattr(operator, op)(walk(tree[1]), walk(tree[2])) |
|
1514 | return getattr(operator, op)(walk(tree[1]), walk(tree[2])) | |
1515 | elif op == b'group': |
|
1515 | elif op == b'group': | |
1516 | return walk(tree[1]) |
|
1516 | return walk(tree[1]) | |
1517 | elif op == b'ancestors': |
|
1517 | elif op == b'ancestors': | |
1518 | return getstack(walk(tree[1])) |
|
1518 | return getstack(walk(tree[1])) | |
1519 | else: |
|
1519 | else: | |
1520 | raise error.ProgrammingError(b'illegal tree: %r' % tree) |
|
1520 | raise error.ProgrammingError(b'illegal tree: %r' % tree) | |
1521 |
|
1521 | |||
1522 | return [prefetched[r] for r in walk(tree)] |
|
1522 | return [prefetched[r] for r in walk(tree)] | |
1523 |
|
1523 | |||
1524 |
|
1524 | |||
1525 | def getdescfromdrev(drev): |
|
1525 | def getdescfromdrev(drev): | |
1526 | """get description (commit message) from "Differential Revision" |
|
1526 | """get description (commit message) from "Differential Revision" | |
1527 |
|
1527 | |||
1528 | This is similar to differential.getcommitmessage API. But we only care |
|
1528 | This is similar to differential.getcommitmessage API. But we only care | |
1529 | about limited fields: title, summary, test plan, and URL. |
|
1529 | about limited fields: title, summary, test plan, and URL. | |
1530 | """ |
|
1530 | """ | |
1531 | title = drev[b'title'] |
|
1531 | title = drev[b'title'] | |
1532 | summary = drev[b'summary'].rstrip() |
|
1532 | summary = drev[b'summary'].rstrip() | |
1533 | testplan = drev[b'testPlan'].rstrip() |
|
1533 | testplan = drev[b'testPlan'].rstrip() | |
1534 | if testplan: |
|
1534 | if testplan: | |
1535 | testplan = b'Test Plan:\n%s' % testplan |
|
1535 | testplan = b'Test Plan:\n%s' % testplan | |
1536 | uri = b'Differential Revision: %s' % drev[b'uri'] |
|
1536 | uri = b'Differential Revision: %s' % drev[b'uri'] | |
1537 | return b'\n\n'.join(filter(None, [title, summary, testplan, uri])) |
|
1537 | return b'\n\n'.join(filter(None, [title, summary, testplan, uri])) | |
1538 |
|
1538 | |||
1539 |
|
1539 | |||
1540 | def getdiffmeta(diff): |
|
1540 | def getdiffmeta(diff): | |
1541 | """get commit metadata (date, node, user, p1) from a diff object |
|
1541 | """get commit metadata (date, node, user, p1) from a diff object | |
1542 |
|
1542 | |||
1543 | The metadata could be "hg:meta", sent by phabsend, like: |
|
1543 | The metadata could be "hg:meta", sent by phabsend, like: | |
1544 |
|
1544 | |||
1545 | "properties": { |
|
1545 | "properties": { | |
1546 | "hg:meta": { |
|
1546 | "hg:meta": { | |
1547 | "date": "1499571514 25200", |
|
1547 | "date": "1499571514 25200", | |
1548 | "node": "98c08acae292b2faf60a279b4189beb6cff1414d", |
|
1548 | "node": "98c08acae292b2faf60a279b4189beb6cff1414d", | |
1549 | "user": "Foo Bar <foo@example.com>", |
|
1549 | "user": "Foo Bar <foo@example.com>", | |
1550 | "parent": "6d0abad76b30e4724a37ab8721d630394070fe16" |
|
1550 | "parent": "6d0abad76b30e4724a37ab8721d630394070fe16" | |
1551 | } |
|
1551 | } | |
1552 | } |
|
1552 | } | |
1553 |
|
1553 | |||
1554 | Or converted from "local:commits", sent by "arc", like: |
|
1554 | Or converted from "local:commits", sent by "arc", like: | |
1555 |
|
1555 | |||
1556 | "properties": { |
|
1556 | "properties": { | |
1557 | "local:commits": { |
|
1557 | "local:commits": { | |
1558 | "98c08acae292b2faf60a279b4189beb6cff1414d": { |
|
1558 | "98c08acae292b2faf60a279b4189beb6cff1414d": { | |
1559 | "author": "Foo Bar", |
|
1559 | "author": "Foo Bar", | |
1560 | "time": 1499546314, |
|
1560 | "time": 1499546314, | |
1561 | "branch": "default", |
|
1561 | "branch": "default", | |
1562 | "tag": "", |
|
1562 | "tag": "", | |
1563 | "commit": "98c08acae292b2faf60a279b4189beb6cff1414d", |
|
1563 | "commit": "98c08acae292b2faf60a279b4189beb6cff1414d", | |
1564 | "rev": "98c08acae292b2faf60a279b4189beb6cff1414d", |
|
1564 | "rev": "98c08acae292b2faf60a279b4189beb6cff1414d", | |
1565 | "local": "1000", |
|
1565 | "local": "1000", | |
1566 | "parents": ["6d0abad76b30e4724a37ab8721d630394070fe16"], |
|
1566 | "parents": ["6d0abad76b30e4724a37ab8721d630394070fe16"], | |
1567 | "summary": "...", |
|
1567 | "summary": "...", | |
1568 | "message": "...", |
|
1568 | "message": "...", | |
1569 | "authorEmail": "foo@example.com" |
|
1569 | "authorEmail": "foo@example.com" | |
1570 | } |
|
1570 | } | |
1571 | } |
|
1571 | } | |
1572 | } |
|
1572 | } | |
1573 |
|
1573 | |||
1574 | Note: metadata extracted from "local:commits" will lose time zone |
|
1574 | Note: metadata extracted from "local:commits" will lose time zone | |
1575 | information. |
|
1575 | information. | |
1576 | """ |
|
1576 | """ | |
1577 | props = diff.get(b'properties') or {} |
|
1577 | props = diff.get(b'properties') or {} | |
1578 | meta = props.get(b'hg:meta') |
|
1578 | meta = props.get(b'hg:meta') | |
1579 | if not meta: |
|
1579 | if not meta: | |
1580 | if props.get(b'local:commits'): |
|
1580 | if props.get(b'local:commits'): | |
1581 | commit = sorted(props[b'local:commits'].values())[0] |
|
1581 | commit = sorted(props[b'local:commits'].values())[0] | |
1582 | meta = {} |
|
1582 | meta = {} | |
1583 | if b'author' in commit and b'authorEmail' in commit: |
|
1583 | if b'author' in commit and b'authorEmail' in commit: | |
1584 | meta[b'user'] = b'%s <%s>' % ( |
|
1584 | meta[b'user'] = b'%s <%s>' % ( | |
1585 | commit[b'author'], |
|
1585 | commit[b'author'], | |
1586 | commit[b'authorEmail'], |
|
1586 | commit[b'authorEmail'], | |
1587 | ) |
|
1587 | ) | |
1588 | if b'time' in commit: |
|
1588 | if b'time' in commit: | |
1589 | meta[b'date'] = b'%d 0' % int(commit[b'time']) |
|
1589 | meta[b'date'] = b'%d 0' % int(commit[b'time']) | |
1590 | if b'branch' in commit: |
|
1590 | if b'branch' in commit: | |
1591 | meta[b'branch'] = commit[b'branch'] |
|
1591 | meta[b'branch'] = commit[b'branch'] | |
1592 | node = commit.get(b'commit', commit.get(b'rev')) |
|
1592 | node = commit.get(b'commit', commit.get(b'rev')) | |
1593 | if node: |
|
1593 | if node: | |
1594 | meta[b'node'] = node |
|
1594 | meta[b'node'] = node | |
1595 | if len(commit.get(b'parents', ())) >= 1: |
|
1595 | if len(commit.get(b'parents', ())) >= 1: | |
1596 | meta[b'parent'] = commit[b'parents'][0] |
|
1596 | meta[b'parent'] = commit[b'parents'][0] | |
1597 | else: |
|
1597 | else: | |
1598 | meta = {} |
|
1598 | meta = {} | |
1599 | if b'date' not in meta and b'dateCreated' in diff: |
|
1599 | if b'date' not in meta and b'dateCreated' in diff: | |
1600 | meta[b'date'] = b'%s 0' % diff[b'dateCreated'] |
|
1600 | meta[b'date'] = b'%s 0' % diff[b'dateCreated'] | |
1601 | if b'branch' not in meta and diff.get(b'branch'): |
|
1601 | if b'branch' not in meta and diff.get(b'branch'): | |
1602 | meta[b'branch'] = diff[b'branch'] |
|
1602 | meta[b'branch'] = diff[b'branch'] | |
1603 | if b'parent' not in meta and diff.get(b'sourceControlBaseRevision'): |
|
1603 | if b'parent' not in meta and diff.get(b'sourceControlBaseRevision'): | |
1604 | meta[b'parent'] = diff[b'sourceControlBaseRevision'] |
|
1604 | meta[b'parent'] = diff[b'sourceControlBaseRevision'] | |
1605 | return meta |
|
1605 | return meta | |
1606 |
|
1606 | |||
1607 |
|
1607 | |||
1608 | def readpatch(repo, drevs, write): |
|
1608 | def readpatch(repo, drevs, write): | |
1609 | """generate plain-text patch readable by 'hg import' |
|
1609 | """generate plain-text patch readable by 'hg import' | |
1610 |
|
1610 | |||
1611 | write is usually ui.write. drevs is what "querydrev" returns, results of |
|
1611 | write is usually ui.write. drevs is what "querydrev" returns, results of | |
1612 | "differential.query". |
|
1612 | "differential.query". | |
1613 | """ |
|
1613 | """ | |
1614 | # Prefetch hg:meta property for all diffs |
|
1614 | # Prefetch hg:meta property for all diffs | |
1615 | diffids = sorted(set(max(int(v) for v in drev[b'diffs']) for drev in drevs)) |
|
1615 | diffids = sorted(set(max(int(v) for v in drev[b'diffs']) for drev in drevs)) | |
1616 | diffs = callconduit(repo.ui, b'differential.querydiffs', {b'ids': diffids}) |
|
1616 | diffs = callconduit(repo.ui, b'differential.querydiffs', {b'ids': diffids}) | |
1617 |
|
1617 | |||
1618 | # Generate patch for each drev |
|
1618 | # Generate patch for each drev | |
1619 | for drev in drevs: |
|
1619 | for drev in drevs: | |
1620 | repo.ui.note(_(b'reading D%s\n') % drev[b'id']) |
|
1620 | repo.ui.note(_(b'reading D%s\n') % drev[b'id']) | |
1621 |
|
1621 | |||
1622 | diffid = max(int(v) for v in drev[b'diffs']) |
|
1622 | diffid = max(int(v) for v in drev[b'diffs']) | |
1623 | body = callconduit( |
|
1623 | body = callconduit( | |
1624 | repo.ui, b'differential.getrawdiff', {b'diffID': diffid} |
|
1624 | repo.ui, b'differential.getrawdiff', {b'diffID': diffid} | |
1625 | ) |
|
1625 | ) | |
1626 | desc = getdescfromdrev(drev) |
|
1626 | desc = getdescfromdrev(drev) | |
1627 | header = b'# HG changeset patch\n' |
|
1627 | header = b'# HG changeset patch\n' | |
1628 |
|
1628 | |||
1629 | # Try to preserve metadata from hg:meta property. Write hg patch |
|
1629 | # Try to preserve metadata from hg:meta property. Write hg patch | |
1630 | # headers that can be read by the "import" command. See patchheadermap |
|
1630 | # headers that can be read by the "import" command. See patchheadermap | |
1631 | # and extract in mercurial/patch.py for supported headers. |
|
1631 | # and extract in mercurial/patch.py for supported headers. | |
1632 | meta = getdiffmeta(diffs[b'%d' % diffid]) |
|
1632 | meta = getdiffmeta(diffs[b'%d' % diffid]) | |
1633 | for k in _metanamemap.keys(): |
|
1633 | for k in _metanamemap.keys(): | |
1634 | if k in meta: |
|
1634 | if k in meta: | |
1635 | header += b'# %s %s\n' % (_metanamemap[k], meta[k]) |
|
1635 | header += b'# %s %s\n' % (_metanamemap[k], meta[k]) | |
1636 |
|
1636 | |||
1637 | content = b'%s%s\n%s' % (header, desc, body) |
|
1637 | content = b'%s%s\n%s' % (header, desc, body) | |
1638 | write(content) |
|
1638 | write(content) | |
1639 |
|
1639 | |||
1640 |
|
1640 | |||
1641 | @vcrcommand( |
|
1641 | @vcrcommand( | |
1642 | b'phabread', |
|
1642 | b'phabread', | |
1643 | [(b'', b'stack', False, _(b'read dependencies'))], |
|
1643 | [(b'', b'stack', False, _(b'read dependencies'))], | |
1644 | _(b'DREVSPEC [OPTIONS]'), |
|
1644 | _(b'DREVSPEC [OPTIONS]'), | |
1645 | helpcategory=command.CATEGORY_IMPORT_EXPORT, |
|
1645 | helpcategory=command.CATEGORY_IMPORT_EXPORT, | |
1646 | ) |
|
1646 | ) | |
1647 | def phabread(ui, repo, spec, **opts): |
|
1647 | def phabread(ui, repo, spec, **opts): | |
1648 | """print patches from Phabricator suitable for importing |
|
1648 | """print patches from Phabricator suitable for importing | |
1649 |
|
1649 | |||
1650 | DREVSPEC could be a Differential Revision identity, like ``D123``, or just |
|
1650 | DREVSPEC could be a Differential Revision identity, like ``D123``, or just | |
1651 | the number ``123``. It could also have common operators like ``+``, ``-``, |
|
1651 | the number ``123``. It could also have common operators like ``+``, ``-``, | |
1652 | ``&``, ``(``, ``)`` for complex queries. Prefix ``:`` could be used to |
|
1652 | ``&``, ``(``, ``)`` for complex queries. Prefix ``:`` could be used to | |
1653 | select a stack. |
|
1653 | select a stack. | |
1654 |
|
1654 | |||
1655 | ``abandoned``, ``accepted``, ``closed``, ``needsreview``, ``needsrevision`` |
|
1655 | ``abandoned``, ``accepted``, ``closed``, ``needsreview``, ``needsrevision`` | |
1656 | could be used to filter patches by status. For performance reason, they |
|
1656 | could be used to filter patches by status. For performance reason, they | |
1657 | only represent a subset of non-status selections and cannot be used alone. |
|
1657 | only represent a subset of non-status selections and cannot be used alone. | |
1658 |
|
1658 | |||
1659 | For example, ``:D6+8-(2+D4)`` selects a stack up to D6, plus D8 and exclude |
|
1659 | For example, ``:D6+8-(2+D4)`` selects a stack up to D6, plus D8 and exclude | |
1660 | D2 and D4. ``:D9 & needsreview`` selects "Needs Review" revisions in a |
|
1660 | D2 and D4. ``:D9 & needsreview`` selects "Needs Review" revisions in a | |
1661 | stack up to D9. |
|
1661 | stack up to D9. | |
1662 |
|
1662 | |||
1663 | If --stack is given, follow dependencies information and read all patches. |
|
1663 | If --stack is given, follow dependencies information and read all patches. | |
1664 | It is equivalent to the ``:`` operator. |
|
1664 | It is equivalent to the ``:`` operator. | |
1665 | """ |
|
1665 | """ | |
1666 | opts = pycompat.byteskwargs(opts) |
|
1666 | opts = pycompat.byteskwargs(opts) | |
1667 | if opts.get(b'stack'): |
|
1667 | if opts.get(b'stack'): | |
1668 | spec = b':(%s)' % spec |
|
1668 | spec = b':(%s)' % spec | |
1669 | drevs = querydrev(repo, spec) |
|
1669 | drevs = querydrev(repo, spec) | |
1670 | readpatch(repo, drevs, ui.write) |
|
1670 | readpatch(repo, drevs, ui.write) | |
1671 |
|
1671 | |||
1672 |
|
1672 | |||
1673 | @vcrcommand( |
|
1673 | @vcrcommand( | |
1674 | b'phabupdate', |
|
1674 | b'phabupdate', | |
1675 | [ |
|
1675 | [ | |
1676 | (b'', b'accept', False, _(b'accept revisions')), |
|
1676 | (b'', b'accept', False, _(b'accept revisions')), | |
1677 | (b'', b'reject', False, _(b'reject revisions')), |
|
1677 | (b'', b'reject', False, _(b'reject revisions')), | |
1678 | (b'', b'abandon', False, _(b'abandon revisions')), |
|
1678 | (b'', b'abandon', False, _(b'abandon revisions')), | |
1679 | (b'', b'reclaim', False, _(b'reclaim revisions')), |
|
1679 | (b'', b'reclaim', False, _(b'reclaim revisions')), | |
1680 | (b'm', b'comment', b'', _(b'comment on the last revision')), |
|
1680 | (b'm', b'comment', b'', _(b'comment on the last revision')), | |
1681 | ], |
|
1681 | ], | |
1682 | _(b'DREVSPEC [OPTIONS]'), |
|
1682 | _(b'DREVSPEC [OPTIONS]'), | |
1683 | helpcategory=command.CATEGORY_IMPORT_EXPORT, |
|
1683 | helpcategory=command.CATEGORY_IMPORT_EXPORT, | |
1684 | ) |
|
1684 | ) | |
1685 | def phabupdate(ui, repo, spec, **opts): |
|
1685 | def phabupdate(ui, repo, spec, **opts): | |
1686 | """update Differential Revision in batch |
|
1686 | """update Differential Revision in batch | |
1687 |
|
1687 | |||
1688 | DREVSPEC selects revisions. See :hg:`help phabread` for its usage. |
|
1688 | DREVSPEC selects revisions. See :hg:`help phabread` for its usage. | |
1689 | """ |
|
1689 | """ | |
1690 | opts = pycompat.byteskwargs(opts) |
|
1690 | opts = pycompat.byteskwargs(opts) | |
1691 | flags = [n for n in b'accept reject abandon reclaim'.split() if opts.get(n)] |
|
1691 | flags = [n for n in b'accept reject abandon reclaim'.split() if opts.get(n)] | |
1692 | if len(flags) > 1: |
|
1692 | if len(flags) > 1: | |
1693 | raise error.Abort(_(b'%s cannot be used together') % b', '.join(flags)) |
|
1693 | raise error.Abort(_(b'%s cannot be used together') % b', '.join(flags)) | |
1694 |
|
1694 | |||
1695 | actions = [] |
|
1695 | actions = [] | |
1696 | for f in flags: |
|
1696 | for f in flags: | |
1697 | actions.append({b'type': f, b'value': True}) |
|
1697 | actions.append({b'type': f, b'value': True}) | |
1698 |
|
1698 | |||
1699 | drevs = querydrev(repo, spec) |
|
1699 | drevs = querydrev(repo, spec) | |
1700 | for i, drev in enumerate(drevs): |
|
1700 | for i, drev in enumerate(drevs): | |
1701 | if i + 1 == len(drevs) and opts.get(b'comment'): |
|
1701 | if i + 1 == len(drevs) and opts.get(b'comment'): | |
1702 | actions.append({b'type': b'comment', b'value': opts[b'comment']}) |
|
1702 | actions.append({b'type': b'comment', b'value': opts[b'comment']}) | |
1703 | if actions: |
|
1703 | if actions: | |
1704 | params = { |
|
1704 | params = { | |
1705 | b'objectIdentifier': drev[b'phid'], |
|
1705 | b'objectIdentifier': drev[b'phid'], | |
1706 | b'transactions': actions, |
|
1706 | b'transactions': actions, | |
1707 | } |
|
1707 | } | |
1708 | callconduit(ui, b'differential.revision.edit', params) |
|
1708 | callconduit(ui, b'differential.revision.edit', params) | |
1709 |
|
1709 | |||
1710 |
|
1710 | |||
1711 | @eh.templatekeyword(b'phabreview', requires={b'ctx'}) |
|
1711 | @eh.templatekeyword(b'phabreview', requires={b'ctx'}) | |
1712 | def template_review(context, mapping): |
|
1712 | def template_review(context, mapping): | |
1713 | """:phabreview: Object describing the review for this changeset. |
|
1713 | """:phabreview: Object describing the review for this changeset. | |
1714 | Has attributes `url` and `id`. |
|
1714 | Has attributes `url` and `id`. | |
1715 | """ |
|
1715 | """ | |
1716 | ctx = context.resource(mapping, b'ctx') |
|
1716 | ctx = context.resource(mapping, b'ctx') | |
1717 | m = _differentialrevisiondescre.search(ctx.description()) |
|
1717 | m = _differentialrevisiondescre.search(ctx.description()) | |
1718 | if m: |
|
1718 | if m: | |
1719 | return templateutil.hybriddict( |
|
1719 | return templateutil.hybriddict( | |
1720 | {b'url': m.group('url'), b'id': b"D%s" % m.group('id'),} |
|
1720 | {b'url': m.group('url'), b'id': b"D%s" % m.group('id'),} | |
1721 | ) |
|
1721 | ) | |
1722 | else: |
|
1722 | else: | |
1723 | tags = ctx.repo().nodetags(ctx.node()) |
|
1723 | tags = ctx.repo().nodetags(ctx.node()) | |
1724 | for t in tags: |
|
1724 | for t in tags: | |
1725 | if _differentialrevisiontagre.match(t): |
|
1725 | if _differentialrevisiontagre.match(t): | |
1726 | url = ctx.repo().ui.config(b'phabricator', b'url') |
|
1726 | url = ctx.repo().ui.config(b'phabricator', b'url') | |
1727 | if not url.endswith(b'/'): |
|
1727 | if not url.endswith(b'/'): | |
1728 | url += b'/' |
|
1728 | url += b'/' | |
1729 | url += t |
|
1729 | url += t | |
1730 |
|
1730 | |||
1731 | return templateutil.hybriddict({b'url': url, b'id': t,}) |
|
1731 | return templateutil.hybriddict({b'url': url, b'id': t,}) | |
1732 | return None |
|
1732 | return None | |
1733 |
|
1733 | |||
1734 |
|
1734 | |||
1735 | @eh.templatekeyword(b'phabstatus', requires={b'ctx', b'repo', b'ui'}) |
|
1735 | @eh.templatekeyword(b'phabstatus', requires={b'ctx', b'repo', b'ui'}) | |
1736 | def template_status(context, mapping): |
|
1736 | def template_status(context, mapping): | |
1737 | """:phabstatus: String. Status of Phabricator differential. |
|
1737 | """:phabstatus: String. Status of Phabricator differential. | |
1738 | """ |
|
1738 | """ | |
1739 | ctx = context.resource(mapping, b'ctx') |
|
1739 | ctx = context.resource(mapping, b'ctx') | |
1740 | repo = context.resource(mapping, b'repo') |
|
1740 | repo = context.resource(mapping, b'repo') | |
1741 | ui = context.resource(mapping, b'ui') |
|
1741 | ui = context.resource(mapping, b'ui') | |
1742 |
|
1742 | |||
1743 | rev = ctx.rev() |
|
1743 | rev = ctx.rev() | |
1744 | try: |
|
1744 | try: | |
1745 | drevid = getdrevmap(repo, [rev])[rev] |
|
1745 | drevid = getdrevmap(repo, [rev])[rev] | |
1746 | except KeyError: |
|
1746 | except KeyError: | |
1747 | return None |
|
1747 | return None | |
1748 | drevs = callconduit(ui, b'differential.query', {b'ids': [drevid]}) |
|
1748 | drevs = callconduit(ui, b'differential.query', {b'ids': [drevid]}) | |
1749 | for drev in drevs: |
|
1749 | for drev in drevs: | |
1750 | if int(drev[b'id']) == drevid: |
|
1750 | if int(drev[b'id']) == drevid: | |
1751 | return templateutil.hybriddict( |
|
1751 | return templateutil.hybriddict( | |
1752 | {b'url': drev[b'uri'], b'status': drev[b'statusName'],} |
|
1752 | {b'url': drev[b'uri'], b'status': drev[b'statusName'],} | |
1753 | ) |
|
1753 | ) | |
1754 | return None |
|
1754 | return None | |
1755 |
|
1755 | |||
1756 |
|
1756 | |||
1757 | @show.showview(b'phabstatus', csettopic=b'work') |
|
1757 | @show.showview(b'phabstatus', csettopic=b'work') | |
1758 | def phabstatusshowview(ui, repo, displayer): |
|
1758 | def phabstatusshowview(ui, repo, displayer): | |
1759 | """Phabricator differiential status""" |
|
1759 | """Phabricator differiential status""" | |
1760 | revs = repo.revs('sort(_underway(), topo)') |
|
1760 | revs = repo.revs('sort(_underway(), topo)') | |
1761 | drevmap = getdrevmap(repo, revs) |
|
1761 | drevmap = getdrevmap(repo, revs) | |
1762 | unknownrevs, drevids, revsbydrevid = [], set([]), {} |
|
1762 | unknownrevs, drevids, revsbydrevid = [], set([]), {} | |
1763 | for rev, drevid in pycompat.iteritems(drevmap): |
|
1763 | for rev, drevid in pycompat.iteritems(drevmap): | |
1764 | if drevid is not None: |
|
1764 | if drevid is not None: | |
1765 | drevids.add(drevid) |
|
1765 | drevids.add(drevid) | |
1766 | revsbydrevid.setdefault(drevid, set([])).add(rev) |
|
1766 | revsbydrevid.setdefault(drevid, set([])).add(rev) | |
1767 | else: |
|
1767 | else: | |
1768 | unknownrevs.append(rev) |
|
1768 | unknownrevs.append(rev) | |
1769 |
|
1769 | |||
1770 | drevs = callconduit(ui, b'differential.query', {b'ids': list(drevids)}) |
|
1770 | drevs = callconduit(ui, b'differential.query', {b'ids': list(drevids)}) | |
1771 | drevsbyrev = {} |
|
1771 | drevsbyrev = {} | |
1772 | for drev in drevs: |
|
1772 | for drev in drevs: | |
1773 | for rev in revsbydrevid[int(drev[b'id'])]: |
|
1773 | for rev in revsbydrevid[int(drev[b'id'])]: | |
1774 | drevsbyrev[rev] = drev |
|
1774 | drevsbyrev[rev] = drev | |
1775 |
|
1775 | |||
1776 | def phabstatus(ctx): |
|
1776 | def phabstatus(ctx): | |
1777 | drev = drevsbyrev[ctx.rev()] |
|
1777 | drev = drevsbyrev[ctx.rev()] | |
1778 | status = ui.label( |
|
1778 | status = ui.label( | |
1779 | b'%(statusName)s' % drev, |
|
1779 | b'%(statusName)s' % drev, | |
1780 | b'phabricator.status.%s' % _getstatusname(drev), |
|
1780 | b'phabricator.status.%s' % _getstatusname(drev), | |
1781 | ) |
|
1781 | ) | |
1782 | ui.write(b"\n%s %s\n" % (drev[b'uri'], status)) |
|
1782 | ui.write(b"\n%s %s\n" % (drev[b'uri'], status)) | |
1783 |
|
1783 | |||
1784 | revs -= smartset.baseset(unknownrevs) |
|
1784 | revs -= smartset.baseset(unknownrevs) | |
1785 | revdag = graphmod.dagwalker(repo, revs) |
|
1785 | revdag = graphmod.dagwalker(repo, revs) | |
1786 |
|
1786 | |||
1787 | ui.setconfig(b'experimental', b'graphshorten', True) |
|
1787 | ui.setconfig(b'experimental', b'graphshorten', True) | |
1788 | displayer._exthook = phabstatus |
|
1788 | displayer._exthook = phabstatus | |
1789 | nodelen = show.longestshortest(repo, revs) |
|
1789 | nodelen = show.longestshortest(repo, revs) | |
1790 | logcmdutil.displaygraph( |
|
1790 | logcmdutil.displaygraph( | |
1791 | ui, |
|
1791 | ui, | |
1792 | repo, |
|
1792 | repo, | |
1793 | revdag, |
|
1793 | revdag, | |
1794 | displayer, |
|
1794 | displayer, | |
1795 | graphmod.asciiedges, |
|
1795 | graphmod.asciiedges, | |
1796 | props={b'nodelen': nodelen}, |
|
1796 | props={b'nodelen': nodelen}, | |
1797 | ) |
|
1797 | ) |
@@ -1,4185 +1,4190 b'' | |||||
1 | # cmdutil.py - help for command processing in mercurial |
|
1 | # cmdutil.py - help for command processing in mercurial | |
2 | # |
|
2 | # | |
3 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> |
|
3 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | |
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2 or any later version. |
|
6 | # GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | from __future__ import absolute_import |
|
8 | from __future__ import absolute_import | |
9 |
|
9 | |||
10 | import copy as copymod |
|
10 | import copy as copymod | |
11 | import errno |
|
11 | import errno | |
12 | import os |
|
12 | import os | |
13 | import re |
|
13 | import re | |
14 |
|
14 | |||
15 | from .i18n import _ |
|
15 | from .i18n import _ | |
16 | from .node import ( |
|
16 | from .node import ( | |
17 | hex, |
|
17 | hex, | |
18 | nullid, |
|
18 | nullid, | |
19 | nullrev, |
|
19 | nullrev, | |
20 | short, |
|
20 | short, | |
21 | ) |
|
21 | ) | |
22 | from .pycompat import ( |
|
22 | from .pycompat import ( | |
23 | getattr, |
|
23 | getattr, | |
24 | open, |
|
24 | open, | |
25 | setattr, |
|
25 | setattr, | |
26 | ) |
|
26 | ) | |
27 | from .thirdparty import attr |
|
27 | from .thirdparty import attr | |
28 |
|
28 | |||
29 | from . import ( |
|
29 | from . import ( | |
30 | bookmarks, |
|
30 | bookmarks, | |
31 | changelog, |
|
31 | changelog, | |
32 | copies, |
|
32 | copies, | |
33 | crecord as crecordmod, |
|
33 | crecord as crecordmod, | |
34 | dirstateguard, |
|
34 | dirstateguard, | |
35 | encoding, |
|
35 | encoding, | |
36 | error, |
|
36 | error, | |
37 | formatter, |
|
37 | formatter, | |
38 | logcmdutil, |
|
38 | logcmdutil, | |
39 | match as matchmod, |
|
39 | match as matchmod, | |
40 | merge as mergemod, |
|
40 | merge as mergemod, | |
41 | mergeutil, |
|
41 | mergeutil, | |
42 | obsolete, |
|
42 | obsolete, | |
43 | patch, |
|
43 | patch, | |
44 | pathutil, |
|
44 | pathutil, | |
45 | phases, |
|
45 | phases, | |
46 | pycompat, |
|
46 | pycompat, | |
47 | repair, |
|
47 | repair, | |
48 | revlog, |
|
48 | revlog, | |
49 | rewriteutil, |
|
49 | rewriteutil, | |
50 | scmutil, |
|
50 | scmutil, | |
51 | smartset, |
|
51 | smartset, | |
52 | state as statemod, |
|
52 | state as statemod, | |
53 | subrepoutil, |
|
53 | subrepoutil, | |
54 | templatekw, |
|
54 | templatekw, | |
55 | templater, |
|
55 | templater, | |
56 | util, |
|
56 | util, | |
57 | vfs as vfsmod, |
|
57 | vfs as vfsmod, | |
58 | ) |
|
58 | ) | |
59 |
|
59 | |||
60 | from .utils import ( |
|
60 | from .utils import ( | |
61 | dateutil, |
|
61 | dateutil, | |
62 | stringutil, |
|
62 | stringutil, | |
63 | ) |
|
63 | ) | |
64 |
|
64 | |||
65 | if pycompat.TYPE_CHECKING: |
|
65 | if pycompat.TYPE_CHECKING: | |
66 | from typing import ( |
|
66 | from typing import ( | |
67 | Any, |
|
67 | Any, | |
68 | Dict, |
|
68 | Dict, | |
69 | ) |
|
69 | ) | |
70 |
|
70 | |||
71 | for t in (Any, Dict): |
|
71 | for t in (Any, Dict): | |
72 | assert t |
|
72 | assert t | |
73 |
|
73 | |||
74 | stringio = util.stringio |
|
74 | stringio = util.stringio | |
75 |
|
75 | |||
76 | # templates of common command options |
|
76 | # templates of common command options | |
77 |
|
77 | |||
78 | dryrunopts = [ |
|
78 | dryrunopts = [ | |
79 | (b'n', b'dry-run', None, _(b'do not perform actions, just print output')), |
|
79 | (b'n', b'dry-run', None, _(b'do not perform actions, just print output')), | |
80 | ] |
|
80 | ] | |
81 |
|
81 | |||
82 | confirmopts = [ |
|
82 | confirmopts = [ | |
83 | (b'', b'confirm', None, _(b'ask before applying actions')), |
|
83 | (b'', b'confirm', None, _(b'ask before applying actions')), | |
84 | ] |
|
84 | ] | |
85 |
|
85 | |||
86 | remoteopts = [ |
|
86 | remoteopts = [ | |
87 | (b'e', b'ssh', b'', _(b'specify ssh command to use'), _(b'CMD')), |
|
87 | (b'e', b'ssh', b'', _(b'specify ssh command to use'), _(b'CMD')), | |
88 | ( |
|
88 | ( | |
89 | b'', |
|
89 | b'', | |
90 | b'remotecmd', |
|
90 | b'remotecmd', | |
91 | b'', |
|
91 | b'', | |
92 | _(b'specify hg command to run on the remote side'), |
|
92 | _(b'specify hg command to run on the remote side'), | |
93 | _(b'CMD'), |
|
93 | _(b'CMD'), | |
94 | ), |
|
94 | ), | |
95 | ( |
|
95 | ( | |
96 | b'', |
|
96 | b'', | |
97 | b'insecure', |
|
97 | b'insecure', | |
98 | None, |
|
98 | None, | |
99 | _(b'do not verify server certificate (ignoring web.cacerts config)'), |
|
99 | _(b'do not verify server certificate (ignoring web.cacerts config)'), | |
100 | ), |
|
100 | ), | |
101 | ] |
|
101 | ] | |
102 |
|
102 | |||
103 | walkopts = [ |
|
103 | walkopts = [ | |
104 | ( |
|
104 | ( | |
105 | b'I', |
|
105 | b'I', | |
106 | b'include', |
|
106 | b'include', | |
107 | [], |
|
107 | [], | |
108 | _(b'include names matching the given patterns'), |
|
108 | _(b'include names matching the given patterns'), | |
109 | _(b'PATTERN'), |
|
109 | _(b'PATTERN'), | |
110 | ), |
|
110 | ), | |
111 | ( |
|
111 | ( | |
112 | b'X', |
|
112 | b'X', | |
113 | b'exclude', |
|
113 | b'exclude', | |
114 | [], |
|
114 | [], | |
115 | _(b'exclude names matching the given patterns'), |
|
115 | _(b'exclude names matching the given patterns'), | |
116 | _(b'PATTERN'), |
|
116 | _(b'PATTERN'), | |
117 | ), |
|
117 | ), | |
118 | ] |
|
118 | ] | |
119 |
|
119 | |||
120 | commitopts = [ |
|
120 | commitopts = [ | |
121 | (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')), |
|
121 | (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')), | |
122 | (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')), |
|
122 | (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')), | |
123 | ] |
|
123 | ] | |
124 |
|
124 | |||
125 | commitopts2 = [ |
|
125 | commitopts2 = [ | |
126 | ( |
|
126 | ( | |
127 | b'd', |
|
127 | b'd', | |
128 | b'date', |
|
128 | b'date', | |
129 | b'', |
|
129 | b'', | |
130 | _(b'record the specified date as commit date'), |
|
130 | _(b'record the specified date as commit date'), | |
131 | _(b'DATE'), |
|
131 | _(b'DATE'), | |
132 | ), |
|
132 | ), | |
133 | ( |
|
133 | ( | |
134 | b'u', |
|
134 | b'u', | |
135 | b'user', |
|
135 | b'user', | |
136 | b'', |
|
136 | b'', | |
137 | _(b'record the specified user as committer'), |
|
137 | _(b'record the specified user as committer'), | |
138 | _(b'USER'), |
|
138 | _(b'USER'), | |
139 | ), |
|
139 | ), | |
140 | ] |
|
140 | ] | |
141 |
|
141 | |||
142 | commitopts3 = [ |
|
142 | commitopts3 = [ | |
143 | (b'D', b'currentdate', None, _(b'record the current date as commit date')), |
|
143 | (b'D', b'currentdate', None, _(b'record the current date as commit date')), | |
144 | (b'U', b'currentuser', None, _(b'record the current user as committer')), |
|
144 | (b'U', b'currentuser', None, _(b'record the current user as committer')), | |
145 | ] |
|
145 | ] | |
146 |
|
146 | |||
147 | formatteropts = [ |
|
147 | formatteropts = [ | |
148 | (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')), |
|
148 | (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')), | |
149 | ] |
|
149 | ] | |
150 |
|
150 | |||
151 | templateopts = [ |
|
151 | templateopts = [ | |
152 | ( |
|
152 | ( | |
153 | b'', |
|
153 | b'', | |
154 | b'style', |
|
154 | b'style', | |
155 | b'', |
|
155 | b'', | |
156 | _(b'display using template map file (DEPRECATED)'), |
|
156 | _(b'display using template map file (DEPRECATED)'), | |
157 | _(b'STYLE'), |
|
157 | _(b'STYLE'), | |
158 | ), |
|
158 | ), | |
159 | (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')), |
|
159 | (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')), | |
160 | ] |
|
160 | ] | |
161 |
|
161 | |||
162 | logopts = [ |
|
162 | logopts = [ | |
163 | (b'p', b'patch', None, _(b'show patch')), |
|
163 | (b'p', b'patch', None, _(b'show patch')), | |
164 | (b'g', b'git', None, _(b'use git extended diff format')), |
|
164 | (b'g', b'git', None, _(b'use git extended diff format')), | |
165 | (b'l', b'limit', b'', _(b'limit number of changes displayed'), _(b'NUM')), |
|
165 | (b'l', b'limit', b'', _(b'limit number of changes displayed'), _(b'NUM')), | |
166 | (b'M', b'no-merges', None, _(b'do not show merges')), |
|
166 | (b'M', b'no-merges', None, _(b'do not show merges')), | |
167 | (b'', b'stat', None, _(b'output diffstat-style summary of changes')), |
|
167 | (b'', b'stat', None, _(b'output diffstat-style summary of changes')), | |
168 | (b'G', b'graph', None, _(b"show the revision DAG")), |
|
168 | (b'G', b'graph', None, _(b"show the revision DAG")), | |
169 | ] + templateopts |
|
169 | ] + templateopts | |
170 |
|
170 | |||
171 | diffopts = [ |
|
171 | diffopts = [ | |
172 | (b'a', b'text', None, _(b'treat all files as text')), |
|
172 | (b'a', b'text', None, _(b'treat all files as text')), | |
173 | ( |
|
173 | ( | |
174 | b'g', |
|
174 | b'g', | |
175 | b'git', |
|
175 | b'git', | |
176 | None, |
|
176 | None, | |
177 | _(b'use git extended diff format (DEFAULT: diff.git)'), |
|
177 | _(b'use git extended diff format (DEFAULT: diff.git)'), | |
178 | ), |
|
178 | ), | |
179 | (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')), |
|
179 | (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')), | |
180 | (b'', b'nodates', None, _(b'omit dates from diff headers')), |
|
180 | (b'', b'nodates', None, _(b'omit dates from diff headers')), | |
181 | ] |
|
181 | ] | |
182 |
|
182 | |||
183 | diffwsopts = [ |
|
183 | diffwsopts = [ | |
184 | ( |
|
184 | ( | |
185 | b'w', |
|
185 | b'w', | |
186 | b'ignore-all-space', |
|
186 | b'ignore-all-space', | |
187 | None, |
|
187 | None, | |
188 | _(b'ignore white space when comparing lines'), |
|
188 | _(b'ignore white space when comparing lines'), | |
189 | ), |
|
189 | ), | |
190 | ( |
|
190 | ( | |
191 | b'b', |
|
191 | b'b', | |
192 | b'ignore-space-change', |
|
192 | b'ignore-space-change', | |
193 | None, |
|
193 | None, | |
194 | _(b'ignore changes in the amount of white space'), |
|
194 | _(b'ignore changes in the amount of white space'), | |
195 | ), |
|
195 | ), | |
196 | ( |
|
196 | ( | |
197 | b'B', |
|
197 | b'B', | |
198 | b'ignore-blank-lines', |
|
198 | b'ignore-blank-lines', | |
199 | None, |
|
199 | None, | |
200 | _(b'ignore changes whose lines are all blank'), |
|
200 | _(b'ignore changes whose lines are all blank'), | |
201 | ), |
|
201 | ), | |
202 | ( |
|
202 | ( | |
203 | b'Z', |
|
203 | b'Z', | |
204 | b'ignore-space-at-eol', |
|
204 | b'ignore-space-at-eol', | |
205 | None, |
|
205 | None, | |
206 | _(b'ignore changes in whitespace at EOL'), |
|
206 | _(b'ignore changes in whitespace at EOL'), | |
207 | ), |
|
207 | ), | |
208 | ] |
|
208 | ] | |
209 |
|
209 | |||
210 | diffopts2 = ( |
|
210 | diffopts2 = ( | |
211 | [ |
|
211 | [ | |
212 | (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')), |
|
212 | (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')), | |
213 | ( |
|
213 | ( | |
214 | b'p', |
|
214 | b'p', | |
215 | b'show-function', |
|
215 | b'show-function', | |
216 | None, |
|
216 | None, | |
217 | _( |
|
217 | _( | |
218 | b'show which function each change is in (DEFAULT: diff.showfunc)' |
|
218 | b'show which function each change is in (DEFAULT: diff.showfunc)' | |
219 | ), |
|
219 | ), | |
220 | ), |
|
220 | ), | |
221 | (b'', b'reverse', None, _(b'produce a diff that undoes the changes')), |
|
221 | (b'', b'reverse', None, _(b'produce a diff that undoes the changes')), | |
222 | ] |
|
222 | ] | |
223 | + diffwsopts |
|
223 | + diffwsopts | |
224 | + [ |
|
224 | + [ | |
225 | ( |
|
225 | ( | |
226 | b'U', |
|
226 | b'U', | |
227 | b'unified', |
|
227 | b'unified', | |
228 | b'', |
|
228 | b'', | |
229 | _(b'number of lines of context to show'), |
|
229 | _(b'number of lines of context to show'), | |
230 | _(b'NUM'), |
|
230 | _(b'NUM'), | |
231 | ), |
|
231 | ), | |
232 | (b'', b'stat', None, _(b'output diffstat-style summary of changes')), |
|
232 | (b'', b'stat', None, _(b'output diffstat-style summary of changes')), | |
233 | ( |
|
233 | ( | |
234 | b'', |
|
234 | b'', | |
235 | b'root', |
|
235 | b'root', | |
236 | b'', |
|
236 | b'', | |
237 | _(b'produce diffs relative to subdirectory'), |
|
237 | _(b'produce diffs relative to subdirectory'), | |
238 | _(b'DIR'), |
|
238 | _(b'DIR'), | |
239 | ), |
|
239 | ), | |
240 | ] |
|
240 | ] | |
241 | ) |
|
241 | ) | |
242 |
|
242 | |||
243 | mergetoolopts = [ |
|
243 | mergetoolopts = [ | |
244 | (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')), |
|
244 | (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')), | |
245 | ] |
|
245 | ] | |
246 |
|
246 | |||
247 | similarityopts = [ |
|
247 | similarityopts = [ | |
248 | ( |
|
248 | ( | |
249 | b's', |
|
249 | b's', | |
250 | b'similarity', |
|
250 | b'similarity', | |
251 | b'', |
|
251 | b'', | |
252 | _(b'guess renamed files by similarity (0<=s<=100)'), |
|
252 | _(b'guess renamed files by similarity (0<=s<=100)'), | |
253 | _(b'SIMILARITY'), |
|
253 | _(b'SIMILARITY'), | |
254 | ) |
|
254 | ) | |
255 | ] |
|
255 | ] | |
256 |
|
256 | |||
257 | subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))] |
|
257 | subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))] | |
258 |
|
258 | |||
259 | debugrevlogopts = [ |
|
259 | debugrevlogopts = [ | |
260 | (b'c', b'changelog', False, _(b'open changelog')), |
|
260 | (b'c', b'changelog', False, _(b'open changelog')), | |
261 | (b'm', b'manifest', False, _(b'open manifest')), |
|
261 | (b'm', b'manifest', False, _(b'open manifest')), | |
262 | (b'', b'dir', b'', _(b'open directory manifest')), |
|
262 | (b'', b'dir', b'', _(b'open directory manifest')), | |
263 | ] |
|
263 | ] | |
264 |
|
264 | |||
265 | # special string such that everything below this line will be ingored in the |
|
265 | # special string such that everything below this line will be ingored in the | |
266 | # editor text |
|
266 | # editor text | |
267 | _linebelow = b"^HG: ------------------------ >8 ------------------------$" |
|
267 | _linebelow = b"^HG: ------------------------ >8 ------------------------$" | |
268 |
|
268 | |||
269 |
|
269 | |||
270 | def check_at_most_one_arg(opts, *args): |
|
270 | def check_at_most_one_arg(opts, *args): | |
271 | """abort if more than one of the arguments are in opts |
|
271 | """abort if more than one of the arguments are in opts | |
272 |
|
272 | |||
273 | Returns the unique argument or None if none of them were specified. |
|
273 | Returns the unique argument or None if none of them were specified. | |
274 | """ |
|
274 | """ | |
275 |
|
275 | |||
276 | def to_display(name): |
|
276 | def to_display(name): | |
277 | return pycompat.sysbytes(name).replace(b'_', b'-') |
|
277 | return pycompat.sysbytes(name).replace(b'_', b'-') | |
278 |
|
278 | |||
279 | previous = None |
|
279 | previous = None | |
280 | for x in args: |
|
280 | for x in args: | |
281 | if opts.get(x): |
|
281 | if opts.get(x): | |
282 | if previous: |
|
282 | if previous: | |
283 | raise error.Abort( |
|
283 | raise error.Abort( | |
284 | _(b'cannot specify both --%s and --%s') |
|
284 | _(b'cannot specify both --%s and --%s') | |
285 | % (to_display(previous), to_display(x)) |
|
285 | % (to_display(previous), to_display(x)) | |
286 | ) |
|
286 | ) | |
287 | previous = x |
|
287 | previous = x | |
288 | return previous |
|
288 | return previous | |
289 |
|
289 | |||
290 |
|
290 | |||
291 | def check_incompatible_arguments(opts, first, others): |
|
291 | def check_incompatible_arguments(opts, first, others): | |
292 | """abort if the first argument is given along with any of the others |
|
292 | """abort if the first argument is given along with any of the others | |
293 |
|
293 | |||
294 | Unlike check_at_most_one_arg(), `others` are not mutually exclusive |
|
294 | Unlike check_at_most_one_arg(), `others` are not mutually exclusive | |
295 | among themselves, and they're passed as a single collection. |
|
295 | among themselves, and they're passed as a single collection. | |
296 | """ |
|
296 | """ | |
297 | for other in others: |
|
297 | for other in others: | |
298 | check_at_most_one_arg(opts, first, other) |
|
298 | check_at_most_one_arg(opts, first, other) | |
299 |
|
299 | |||
300 |
|
300 | |||
301 | def resolvecommitoptions(ui, opts): |
|
301 | def resolvecommitoptions(ui, opts): | |
302 | """modify commit options dict to handle related options |
|
302 | """modify commit options dict to handle related options | |
303 |
|
303 | |||
304 | The return value indicates that ``rewrite.update-timestamp`` is the reason |
|
304 | The return value indicates that ``rewrite.update-timestamp`` is the reason | |
305 | the ``date`` option is set. |
|
305 | the ``date`` option is set. | |
306 | """ |
|
306 | """ | |
307 | check_at_most_one_arg(opts, b'date', b'currentdate') |
|
307 | check_at_most_one_arg(opts, b'date', b'currentdate') | |
308 | check_at_most_one_arg(opts, b'user', b'currentuser') |
|
308 | check_at_most_one_arg(opts, b'user', b'currentuser') | |
309 |
|
309 | |||
310 | datemaydiffer = False # date-only change should be ignored? |
|
310 | datemaydiffer = False # date-only change should be ignored? | |
311 |
|
311 | |||
312 | if opts.get(b'currentdate'): |
|
312 | if opts.get(b'currentdate'): | |
313 | opts[b'date'] = b'%d %d' % dateutil.makedate() |
|
313 | opts[b'date'] = b'%d %d' % dateutil.makedate() | |
314 | elif ( |
|
314 | elif ( | |
315 | not opts.get(b'date') |
|
315 | not opts.get(b'date') | |
316 | and ui.configbool(b'rewrite', b'update-timestamp') |
|
316 | and ui.configbool(b'rewrite', b'update-timestamp') | |
317 | and opts.get(b'currentdate') is None |
|
317 | and opts.get(b'currentdate') is None | |
318 | ): |
|
318 | ): | |
319 | opts[b'date'] = b'%d %d' % dateutil.makedate() |
|
319 | opts[b'date'] = b'%d %d' % dateutil.makedate() | |
320 | datemaydiffer = True |
|
320 | datemaydiffer = True | |
321 |
|
321 | |||
322 | if opts.get(b'currentuser'): |
|
322 | if opts.get(b'currentuser'): | |
323 | opts[b'user'] = ui.username() |
|
323 | opts[b'user'] = ui.username() | |
324 |
|
324 | |||
325 | return datemaydiffer |
|
325 | return datemaydiffer | |
326 |
|
326 | |||
327 |
|
327 | |||
328 | def checknotesize(ui, opts): |
|
328 | def checknotesize(ui, opts): | |
329 | """ make sure note is of valid format """ |
|
329 | """ make sure note is of valid format """ | |
330 |
|
330 | |||
331 | note = opts.get(b'note') |
|
331 | note = opts.get(b'note') | |
332 | if not note: |
|
332 | if not note: | |
333 | return |
|
333 | return | |
334 |
|
334 | |||
335 | if len(note) > 255: |
|
335 | if len(note) > 255: | |
336 | raise error.Abort(_(b"cannot store a note of more than 255 bytes")) |
|
336 | raise error.Abort(_(b"cannot store a note of more than 255 bytes")) | |
337 | if b'\n' in note: |
|
337 | if b'\n' in note: | |
338 | raise error.Abort(_(b"note cannot contain a newline")) |
|
338 | raise error.Abort(_(b"note cannot contain a newline")) | |
339 |
|
339 | |||
340 |
|
340 | |||
341 | def ishunk(x): |
|
341 | def ishunk(x): | |
342 | hunkclasses = (crecordmod.uihunk, patch.recordhunk) |
|
342 | hunkclasses = (crecordmod.uihunk, patch.recordhunk) | |
343 | return isinstance(x, hunkclasses) |
|
343 | return isinstance(x, hunkclasses) | |
344 |
|
344 | |||
345 |
|
345 | |||
346 | def newandmodified(chunks, originalchunks): |
|
346 | def newandmodified(chunks, originalchunks): | |
347 | newlyaddedandmodifiedfiles = set() |
|
347 | newlyaddedandmodifiedfiles = set() | |
348 | alsorestore = set() |
|
348 | alsorestore = set() | |
349 | for chunk in chunks: |
|
349 | for chunk in chunks: | |
350 | if ( |
|
350 | if ( | |
351 | ishunk(chunk) |
|
351 | ishunk(chunk) | |
352 | and chunk.header.isnewfile() |
|
352 | and chunk.header.isnewfile() | |
353 | and chunk not in originalchunks |
|
353 | and chunk not in originalchunks | |
354 | ): |
|
354 | ): | |
355 | newlyaddedandmodifiedfiles.add(chunk.header.filename()) |
|
355 | newlyaddedandmodifiedfiles.add(chunk.header.filename()) | |
356 | alsorestore.update( |
|
356 | alsorestore.update( | |
357 | set(chunk.header.files()) - {chunk.header.filename()} |
|
357 | set(chunk.header.files()) - {chunk.header.filename()} | |
358 | ) |
|
358 | ) | |
359 | return newlyaddedandmodifiedfiles, alsorestore |
|
359 | return newlyaddedandmodifiedfiles, alsorestore | |
360 |
|
360 | |||
361 |
|
361 | |||
362 | def parsealiases(cmd): |
|
362 | def parsealiases(cmd): | |
363 | return cmd.split(b"|") |
|
363 | return cmd.split(b"|") | |
364 |
|
364 | |||
365 |
|
365 | |||
366 | def setupwrapcolorwrite(ui): |
|
366 | def setupwrapcolorwrite(ui): | |
367 | # wrap ui.write so diff output can be labeled/colorized |
|
367 | # wrap ui.write so diff output can be labeled/colorized | |
368 | def wrapwrite(orig, *args, **kw): |
|
368 | def wrapwrite(orig, *args, **kw): | |
369 | label = kw.pop('label', b'') |
|
369 | label = kw.pop('label', b'') | |
370 | for chunk, l in patch.difflabel(lambda: args): |
|
370 | for chunk, l in patch.difflabel(lambda: args): | |
371 | orig(chunk, label=label + l) |
|
371 | orig(chunk, label=label + l) | |
372 |
|
372 | |||
373 | oldwrite = ui.write |
|
373 | oldwrite = ui.write | |
374 |
|
374 | |||
375 | def wrap(*args, **kwargs): |
|
375 | def wrap(*args, **kwargs): | |
376 | return wrapwrite(oldwrite, *args, **kwargs) |
|
376 | return wrapwrite(oldwrite, *args, **kwargs) | |
377 |
|
377 | |||
378 | setattr(ui, 'write', wrap) |
|
378 | setattr(ui, 'write', wrap) | |
379 | return oldwrite |
|
379 | return oldwrite | |
380 |
|
380 | |||
381 |
|
381 | |||
382 | def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None): |
|
382 | def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None): | |
383 | try: |
|
383 | try: | |
384 | if usecurses: |
|
384 | if usecurses: | |
385 | if testfile: |
|
385 | if testfile: | |
386 | recordfn = crecordmod.testdecorator( |
|
386 | recordfn = crecordmod.testdecorator( | |
387 | testfile, crecordmod.testchunkselector |
|
387 | testfile, crecordmod.testchunkselector | |
388 | ) |
|
388 | ) | |
389 | else: |
|
389 | else: | |
390 | recordfn = crecordmod.chunkselector |
|
390 | recordfn = crecordmod.chunkselector | |
391 |
|
391 | |||
392 | return crecordmod.filterpatch( |
|
392 | return crecordmod.filterpatch( | |
393 | ui, originalhunks, recordfn, operation |
|
393 | ui, originalhunks, recordfn, operation | |
394 | ) |
|
394 | ) | |
395 | except crecordmod.fallbackerror as e: |
|
395 | except crecordmod.fallbackerror as e: | |
396 | ui.warn(b'%s\n' % e) |
|
396 | ui.warn(b'%s\n' % e) | |
397 | ui.warn(_(b'falling back to text mode\n')) |
|
397 | ui.warn(_(b'falling back to text mode\n')) | |
398 |
|
398 | |||
399 | return patch.filterpatch(ui, originalhunks, match, operation) |
|
399 | return patch.filterpatch(ui, originalhunks, match, operation) | |
400 |
|
400 | |||
401 |
|
401 | |||
402 | def recordfilter(ui, originalhunks, match, operation=None): |
|
402 | def recordfilter(ui, originalhunks, match, operation=None): | |
403 | """ Prompts the user to filter the originalhunks and return a list of |
|
403 | """ Prompts the user to filter the originalhunks and return a list of | |
404 | selected hunks. |
|
404 | selected hunks. | |
405 | *operation* is used for to build ui messages to indicate the user what |
|
405 | *operation* is used for to build ui messages to indicate the user what | |
406 | kind of filtering they are doing: reverting, committing, shelving, etc. |
|
406 | kind of filtering they are doing: reverting, committing, shelving, etc. | |
407 | (see patch.filterpatch). |
|
407 | (see patch.filterpatch). | |
408 | """ |
|
408 | """ | |
409 | usecurses = crecordmod.checkcurses(ui) |
|
409 | usecurses = crecordmod.checkcurses(ui) | |
410 | testfile = ui.config(b'experimental', b'crecordtest') |
|
410 | testfile = ui.config(b'experimental', b'crecordtest') | |
411 | oldwrite = setupwrapcolorwrite(ui) |
|
411 | oldwrite = setupwrapcolorwrite(ui) | |
412 | try: |
|
412 | try: | |
413 | newchunks, newopts = filterchunks( |
|
413 | newchunks, newopts = filterchunks( | |
414 | ui, originalhunks, usecurses, testfile, match, operation |
|
414 | ui, originalhunks, usecurses, testfile, match, operation | |
415 | ) |
|
415 | ) | |
416 | finally: |
|
416 | finally: | |
417 | ui.write = oldwrite |
|
417 | ui.write = oldwrite | |
418 | return newchunks, newopts |
|
418 | return newchunks, newopts | |
419 |
|
419 | |||
420 |
|
420 | |||
421 | def dorecord( |
|
421 | def dorecord( | |
422 | ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts |
|
422 | ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts | |
423 | ): |
|
423 | ): | |
424 | opts = pycompat.byteskwargs(opts) |
|
424 | opts = pycompat.byteskwargs(opts) | |
425 | if not ui.interactive(): |
|
425 | if not ui.interactive(): | |
426 | if cmdsuggest: |
|
426 | if cmdsuggest: | |
427 | msg = _(b'running non-interactively, use %s instead') % cmdsuggest |
|
427 | msg = _(b'running non-interactively, use %s instead') % cmdsuggest | |
428 | else: |
|
428 | else: | |
429 | msg = _(b'running non-interactively') |
|
429 | msg = _(b'running non-interactively') | |
430 | raise error.Abort(msg) |
|
430 | raise error.Abort(msg) | |
431 |
|
431 | |||
432 | # make sure username is set before going interactive |
|
432 | # make sure username is set before going interactive | |
433 | if not opts.get(b'user'): |
|
433 | if not opts.get(b'user'): | |
434 | ui.username() # raise exception, username not provided |
|
434 | ui.username() # raise exception, username not provided | |
435 |
|
435 | |||
436 | def recordfunc(ui, repo, message, match, opts): |
|
436 | def recordfunc(ui, repo, message, match, opts): | |
437 | """This is generic record driver. |
|
437 | """This is generic record driver. | |
438 |
|
438 | |||
439 | Its job is to interactively filter local changes, and |
|
439 | Its job is to interactively filter local changes, and | |
440 | accordingly prepare working directory into a state in which the |
|
440 | accordingly prepare working directory into a state in which the | |
441 | job can be delegated to a non-interactive commit command such as |
|
441 | job can be delegated to a non-interactive commit command such as | |
442 | 'commit' or 'qrefresh'. |
|
442 | 'commit' or 'qrefresh'. | |
443 |
|
443 | |||
444 | After the actual job is done by non-interactive command, the |
|
444 | After the actual job is done by non-interactive command, the | |
445 | working directory is restored to its original state. |
|
445 | working directory is restored to its original state. | |
446 |
|
446 | |||
447 | In the end we'll record interesting changes, and everything else |
|
447 | In the end we'll record interesting changes, and everything else | |
448 | will be left in place, so the user can continue working. |
|
448 | will be left in place, so the user can continue working. | |
449 | """ |
|
449 | """ | |
450 | if not opts.get(b'interactive-unshelve'): |
|
450 | if not opts.get(b'interactive-unshelve'): | |
451 | checkunfinished(repo, commit=True) |
|
451 | checkunfinished(repo, commit=True) | |
452 | wctx = repo[None] |
|
452 | wctx = repo[None] | |
453 | merge = len(wctx.parents()) > 1 |
|
453 | merge = len(wctx.parents()) > 1 | |
454 | if merge: |
|
454 | if merge: | |
455 | raise error.Abort( |
|
455 | raise error.Abort( | |
456 | _( |
|
456 | _( | |
457 | b'cannot partially commit a merge ' |
|
457 | b'cannot partially commit a merge ' | |
458 | b'(use "hg commit" instead)' |
|
458 | b'(use "hg commit" instead)' | |
459 | ) |
|
459 | ) | |
460 | ) |
|
460 | ) | |
461 |
|
461 | |||
462 | def fail(f, msg): |
|
462 | def fail(f, msg): | |
463 | raise error.Abort(b'%s: %s' % (f, msg)) |
|
463 | raise error.Abort(b'%s: %s' % (f, msg)) | |
464 |
|
464 | |||
465 | force = opts.get(b'force') |
|
465 | force = opts.get(b'force') | |
466 | if not force: |
|
466 | if not force: | |
467 | match = matchmod.badmatch(match, fail) |
|
467 | match = matchmod.badmatch(match, fail) | |
468 |
|
468 | |||
469 | status = repo.status(match=match) |
|
469 | status = repo.status(match=match) | |
470 |
|
470 | |||
471 | overrides = {(b'ui', b'commitsubrepos'): True} |
|
471 | overrides = {(b'ui', b'commitsubrepos'): True} | |
472 |
|
472 | |||
473 | with repo.ui.configoverride(overrides, b'record'): |
|
473 | with repo.ui.configoverride(overrides, b'record'): | |
474 | # subrepoutil.precommit() modifies the status |
|
474 | # subrepoutil.precommit() modifies the status | |
475 | tmpstatus = scmutil.status( |
|
475 | tmpstatus = scmutil.status( | |
476 | copymod.copy(status.modified), |
|
476 | copymod.copy(status.modified), | |
477 | copymod.copy(status.added), |
|
477 | copymod.copy(status.added), | |
478 | copymod.copy(status.removed), |
|
478 | copymod.copy(status.removed), | |
479 | copymod.copy(status.deleted), |
|
479 | copymod.copy(status.deleted), | |
480 | copymod.copy(status.unknown), |
|
480 | copymod.copy(status.unknown), | |
481 | copymod.copy(status.ignored), |
|
481 | copymod.copy(status.ignored), | |
482 | copymod.copy(status.clean), # pytype: disable=wrong-arg-count |
|
482 | copymod.copy(status.clean), # pytype: disable=wrong-arg-count | |
483 | ) |
|
483 | ) | |
484 |
|
484 | |||
485 | # Force allows -X subrepo to skip the subrepo. |
|
485 | # Force allows -X subrepo to skip the subrepo. | |
486 | subs, commitsubs, newstate = subrepoutil.precommit( |
|
486 | subs, commitsubs, newstate = subrepoutil.precommit( | |
487 | repo.ui, wctx, tmpstatus, match, force=True |
|
487 | repo.ui, wctx, tmpstatus, match, force=True | |
488 | ) |
|
488 | ) | |
489 | for s in subs: |
|
489 | for s in subs: | |
490 | if s in commitsubs: |
|
490 | if s in commitsubs: | |
491 | dirtyreason = wctx.sub(s).dirtyreason(True) |
|
491 | dirtyreason = wctx.sub(s).dirtyreason(True) | |
492 | raise error.Abort(dirtyreason) |
|
492 | raise error.Abort(dirtyreason) | |
493 |
|
493 | |||
494 | if not force: |
|
494 | if not force: | |
495 | repo.checkcommitpatterns(wctx, match, status, fail) |
|
495 | repo.checkcommitpatterns(wctx, match, status, fail) | |
496 | diffopts = patch.difffeatureopts( |
|
496 | diffopts = patch.difffeatureopts( | |
497 | ui, |
|
497 | ui, | |
498 | opts=opts, |
|
498 | opts=opts, | |
499 | whitespace=True, |
|
499 | whitespace=True, | |
500 | section=b'commands', |
|
500 | section=b'commands', | |
501 | configprefix=b'commit.interactive.', |
|
501 | configprefix=b'commit.interactive.', | |
502 | ) |
|
502 | ) | |
503 | diffopts.nodates = True |
|
503 | diffopts.nodates = True | |
504 | diffopts.git = True |
|
504 | diffopts.git = True | |
505 | diffopts.showfunc = True |
|
505 | diffopts.showfunc = True | |
506 | originaldiff = patch.diff(repo, changes=status, opts=diffopts) |
|
506 | originaldiff = patch.diff(repo, changes=status, opts=diffopts) | |
507 | originalchunks = patch.parsepatch(originaldiff) |
|
507 | originalchunks = patch.parsepatch(originaldiff) | |
508 | match = scmutil.match(repo[None], pats) |
|
508 | match = scmutil.match(repo[None], pats) | |
509 |
|
509 | |||
510 | # 1. filter patch, since we are intending to apply subset of it |
|
510 | # 1. filter patch, since we are intending to apply subset of it | |
511 | try: |
|
511 | try: | |
512 | chunks, newopts = filterfn(ui, originalchunks, match) |
|
512 | chunks, newopts = filterfn(ui, originalchunks, match) | |
513 | except error.PatchError as err: |
|
513 | except error.PatchError as err: | |
514 | raise error.Abort(_(b'error parsing patch: %s') % err) |
|
514 | raise error.Abort(_(b'error parsing patch: %s') % err) | |
515 | opts.update(newopts) |
|
515 | opts.update(newopts) | |
516 |
|
516 | |||
517 | # We need to keep a backup of files that have been newly added and |
|
517 | # We need to keep a backup of files that have been newly added and | |
518 | # modified during the recording process because there is a previous |
|
518 | # modified during the recording process because there is a previous | |
519 | # version without the edit in the workdir. We also will need to restore |
|
519 | # version without the edit in the workdir. We also will need to restore | |
520 | # files that were the sources of renames so that the patch application |
|
520 | # files that were the sources of renames so that the patch application | |
521 | # works. |
|
521 | # works. | |
522 | newlyaddedandmodifiedfiles, alsorestore = newandmodified( |
|
522 | newlyaddedandmodifiedfiles, alsorestore = newandmodified( | |
523 | chunks, originalchunks |
|
523 | chunks, originalchunks | |
524 | ) |
|
524 | ) | |
525 | contenders = set() |
|
525 | contenders = set() | |
526 | for h in chunks: |
|
526 | for h in chunks: | |
527 | try: |
|
527 | try: | |
528 | contenders.update(set(h.files())) |
|
528 | contenders.update(set(h.files())) | |
529 | except AttributeError: |
|
529 | except AttributeError: | |
530 | pass |
|
530 | pass | |
531 |
|
531 | |||
532 | changed = status.modified + status.added + status.removed |
|
532 | changed = status.modified + status.added + status.removed | |
533 | newfiles = [f for f in changed if f in contenders] |
|
533 | newfiles = [f for f in changed if f in contenders] | |
534 | if not newfiles: |
|
534 | if not newfiles: | |
535 | ui.status(_(b'no changes to record\n')) |
|
535 | ui.status(_(b'no changes to record\n')) | |
536 | return 0 |
|
536 | return 0 | |
537 |
|
537 | |||
538 | modified = set(status.modified) |
|
538 | modified = set(status.modified) | |
539 |
|
539 | |||
540 | # 2. backup changed files, so we can restore them in the end |
|
540 | # 2. backup changed files, so we can restore them in the end | |
541 |
|
541 | |||
542 | if backupall: |
|
542 | if backupall: | |
543 | tobackup = changed |
|
543 | tobackup = changed | |
544 | else: |
|
544 | else: | |
545 | tobackup = [ |
|
545 | tobackup = [ | |
546 | f |
|
546 | f | |
547 | for f in newfiles |
|
547 | for f in newfiles | |
548 | if f in modified or f in newlyaddedandmodifiedfiles |
|
548 | if f in modified or f in newlyaddedandmodifiedfiles | |
549 | ] |
|
549 | ] | |
550 | backups = {} |
|
550 | backups = {} | |
551 | if tobackup: |
|
551 | if tobackup: | |
552 | backupdir = repo.vfs.join(b'record-backups') |
|
552 | backupdir = repo.vfs.join(b'record-backups') | |
553 | try: |
|
553 | try: | |
554 | os.mkdir(backupdir) |
|
554 | os.mkdir(backupdir) | |
555 | except OSError as err: |
|
555 | except OSError as err: | |
556 | if err.errno != errno.EEXIST: |
|
556 | if err.errno != errno.EEXIST: | |
557 | raise |
|
557 | raise | |
558 | try: |
|
558 | try: | |
559 | # backup continues |
|
559 | # backup continues | |
560 | for f in tobackup: |
|
560 | for f in tobackup: | |
561 | fd, tmpname = pycompat.mkstemp( |
|
561 | fd, tmpname = pycompat.mkstemp( | |
562 | prefix=f.replace(b'/', b'_') + b'.', dir=backupdir |
|
562 | prefix=f.replace(b'/', b'_') + b'.', dir=backupdir | |
563 | ) |
|
563 | ) | |
564 | os.close(fd) |
|
564 | os.close(fd) | |
565 | ui.debug(b'backup %r as %r\n' % (f, tmpname)) |
|
565 | ui.debug(b'backup %r as %r\n' % (f, tmpname)) | |
566 | util.copyfile(repo.wjoin(f), tmpname, copystat=True) |
|
566 | util.copyfile(repo.wjoin(f), tmpname, copystat=True) | |
567 | backups[f] = tmpname |
|
567 | backups[f] = tmpname | |
568 |
|
568 | |||
569 | fp = stringio() |
|
569 | fp = stringio() | |
570 | for c in chunks: |
|
570 | for c in chunks: | |
571 | fname = c.filename() |
|
571 | fname = c.filename() | |
572 | if fname in backups: |
|
572 | if fname in backups: | |
573 | c.write(fp) |
|
573 | c.write(fp) | |
574 | dopatch = fp.tell() |
|
574 | dopatch = fp.tell() | |
575 | fp.seek(0) |
|
575 | fp.seek(0) | |
576 |
|
576 | |||
577 | # 2.5 optionally review / modify patch in text editor |
|
577 | # 2.5 optionally review / modify patch in text editor | |
578 | if opts.get(b'review', False): |
|
578 | if opts.get(b'review', False): | |
579 | patchtext = ( |
|
579 | patchtext = ( | |
580 | crecordmod.diffhelptext |
|
580 | crecordmod.diffhelptext | |
581 | + crecordmod.patchhelptext |
|
581 | + crecordmod.patchhelptext | |
582 | + fp.read() |
|
582 | + fp.read() | |
583 | ) |
|
583 | ) | |
584 | reviewedpatch = ui.edit( |
|
584 | reviewedpatch = ui.edit( | |
585 | patchtext, b"", action=b"diff", repopath=repo.path |
|
585 | patchtext, b"", action=b"diff", repopath=repo.path | |
586 | ) |
|
586 | ) | |
587 | fp.truncate(0) |
|
587 | fp.truncate(0) | |
588 | fp.write(reviewedpatch) |
|
588 | fp.write(reviewedpatch) | |
589 | fp.seek(0) |
|
589 | fp.seek(0) | |
590 |
|
590 | |||
591 | [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles] |
|
591 | [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles] | |
592 | # 3a. apply filtered patch to clean repo (clean) |
|
592 | # 3a. apply filtered patch to clean repo (clean) | |
593 | if backups: |
|
593 | if backups: | |
594 | m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore) |
|
594 | m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore) | |
595 | mergemod.revert_to(repo[b'.'], matcher=m) |
|
595 | mergemod.revert_to(repo[b'.'], matcher=m) | |
596 |
|
596 | |||
597 | # 3b. (apply) |
|
597 | # 3b. (apply) | |
598 | if dopatch: |
|
598 | if dopatch: | |
599 | try: |
|
599 | try: | |
600 | ui.debug(b'applying patch\n') |
|
600 | ui.debug(b'applying patch\n') | |
601 | ui.debug(fp.getvalue()) |
|
601 | ui.debug(fp.getvalue()) | |
602 | patch.internalpatch(ui, repo, fp, 1, eolmode=None) |
|
602 | patch.internalpatch(ui, repo, fp, 1, eolmode=None) | |
603 | except error.PatchError as err: |
|
603 | except error.PatchError as err: | |
604 | raise error.Abort(pycompat.bytestr(err)) |
|
604 | raise error.Abort(pycompat.bytestr(err)) | |
605 | del fp |
|
605 | del fp | |
606 |
|
606 | |||
607 | # 4. We prepared working directory according to filtered |
|
607 | # 4. We prepared working directory according to filtered | |
608 | # patch. Now is the time to delegate the job to |
|
608 | # patch. Now is the time to delegate the job to | |
609 | # commit/qrefresh or the like! |
|
609 | # commit/qrefresh or the like! | |
610 |
|
610 | |||
611 | # Make all of the pathnames absolute. |
|
611 | # Make all of the pathnames absolute. | |
612 | newfiles = [repo.wjoin(nf) for nf in newfiles] |
|
612 | newfiles = [repo.wjoin(nf) for nf in newfiles] | |
613 | return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts)) |
|
613 | return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts)) | |
614 | finally: |
|
614 | finally: | |
615 | # 5. finally restore backed-up files |
|
615 | # 5. finally restore backed-up files | |
616 | try: |
|
616 | try: | |
617 | dirstate = repo.dirstate |
|
617 | dirstate = repo.dirstate | |
618 | for realname, tmpname in pycompat.iteritems(backups): |
|
618 | for realname, tmpname in pycompat.iteritems(backups): | |
619 | ui.debug(b'restoring %r to %r\n' % (tmpname, realname)) |
|
619 | ui.debug(b'restoring %r to %r\n' % (tmpname, realname)) | |
620 |
|
620 | |||
621 | if dirstate[realname] == b'n': |
|
621 | if dirstate[realname] == b'n': | |
622 | # without normallookup, restoring timestamp |
|
622 | # without normallookup, restoring timestamp | |
623 | # may cause partially committed files |
|
623 | # may cause partially committed files | |
624 | # to be treated as unmodified |
|
624 | # to be treated as unmodified | |
625 | dirstate.normallookup(realname) |
|
625 | dirstate.normallookup(realname) | |
626 |
|
626 | |||
627 | # copystat=True here and above are a hack to trick any |
|
627 | # copystat=True here and above are a hack to trick any | |
628 | # editors that have f open that we haven't modified them. |
|
628 | # editors that have f open that we haven't modified them. | |
629 | # |
|
629 | # | |
630 | # Also note that this racy as an editor could notice the |
|
630 | # Also note that this racy as an editor could notice the | |
631 | # file's mtime before we've finished writing it. |
|
631 | # file's mtime before we've finished writing it. | |
632 | util.copyfile(tmpname, repo.wjoin(realname), copystat=True) |
|
632 | util.copyfile(tmpname, repo.wjoin(realname), copystat=True) | |
633 | os.unlink(tmpname) |
|
633 | os.unlink(tmpname) | |
634 | if tobackup: |
|
634 | if tobackup: | |
635 | os.rmdir(backupdir) |
|
635 | os.rmdir(backupdir) | |
636 | except OSError: |
|
636 | except OSError: | |
637 | pass |
|
637 | pass | |
638 |
|
638 | |||
639 | def recordinwlock(ui, repo, message, match, opts): |
|
639 | def recordinwlock(ui, repo, message, match, opts): | |
640 | with repo.wlock(): |
|
640 | with repo.wlock(): | |
641 | return recordfunc(ui, repo, message, match, opts) |
|
641 | return recordfunc(ui, repo, message, match, opts) | |
642 |
|
642 | |||
643 | return commit(ui, repo, recordinwlock, pats, opts) |
|
643 | return commit(ui, repo, recordinwlock, pats, opts) | |
644 |
|
644 | |||
645 |
|
645 | |||
646 | class dirnode(object): |
|
646 | class dirnode(object): | |
647 | """ |
|
647 | """ | |
648 | Represent a directory in user working copy with information required for |
|
648 | Represent a directory in user working copy with information required for | |
649 | the purpose of tersing its status. |
|
649 | the purpose of tersing its status. | |
650 |
|
650 | |||
651 | path is the path to the directory, without a trailing '/' |
|
651 | path is the path to the directory, without a trailing '/' | |
652 |
|
652 | |||
653 | statuses is a set of statuses of all files in this directory (this includes |
|
653 | statuses is a set of statuses of all files in this directory (this includes | |
654 | all the files in all the subdirectories too) |
|
654 | all the files in all the subdirectories too) | |
655 |
|
655 | |||
656 | files is a list of files which are direct child of this directory |
|
656 | files is a list of files which are direct child of this directory | |
657 |
|
657 | |||
658 | subdirs is a dictionary of sub-directory name as the key and it's own |
|
658 | subdirs is a dictionary of sub-directory name as the key and it's own | |
659 | dirnode object as the value |
|
659 | dirnode object as the value | |
660 | """ |
|
660 | """ | |
661 |
|
661 | |||
662 | def __init__(self, dirpath): |
|
662 | def __init__(self, dirpath): | |
663 | self.path = dirpath |
|
663 | self.path = dirpath | |
664 | self.statuses = set() |
|
664 | self.statuses = set() | |
665 | self.files = [] |
|
665 | self.files = [] | |
666 | self.subdirs = {} |
|
666 | self.subdirs = {} | |
667 |
|
667 | |||
668 | def _addfileindir(self, filename, status): |
|
668 | def _addfileindir(self, filename, status): | |
669 | """Add a file in this directory as a direct child.""" |
|
669 | """Add a file in this directory as a direct child.""" | |
670 | self.files.append((filename, status)) |
|
670 | self.files.append((filename, status)) | |
671 |
|
671 | |||
672 | def addfile(self, filename, status): |
|
672 | def addfile(self, filename, status): | |
673 | """ |
|
673 | """ | |
674 | Add a file to this directory or to its direct parent directory. |
|
674 | Add a file to this directory or to its direct parent directory. | |
675 |
|
675 | |||
676 | If the file is not direct child of this directory, we traverse to the |
|
676 | If the file is not direct child of this directory, we traverse to the | |
677 | directory of which this file is a direct child of and add the file |
|
677 | directory of which this file is a direct child of and add the file | |
678 | there. |
|
678 | there. | |
679 | """ |
|
679 | """ | |
680 |
|
680 | |||
681 | # the filename contains a path separator, it means it's not the direct |
|
681 | # the filename contains a path separator, it means it's not the direct | |
682 | # child of this directory |
|
682 | # child of this directory | |
683 | if b'/' in filename: |
|
683 | if b'/' in filename: | |
684 | subdir, filep = filename.split(b'/', 1) |
|
684 | subdir, filep = filename.split(b'/', 1) | |
685 |
|
685 | |||
686 | # does the dirnode object for subdir exists |
|
686 | # does the dirnode object for subdir exists | |
687 | if subdir not in self.subdirs: |
|
687 | if subdir not in self.subdirs: | |
688 | subdirpath = pathutil.join(self.path, subdir) |
|
688 | subdirpath = pathutil.join(self.path, subdir) | |
689 | self.subdirs[subdir] = dirnode(subdirpath) |
|
689 | self.subdirs[subdir] = dirnode(subdirpath) | |
690 |
|
690 | |||
691 | # try adding the file in subdir |
|
691 | # try adding the file in subdir | |
692 | self.subdirs[subdir].addfile(filep, status) |
|
692 | self.subdirs[subdir].addfile(filep, status) | |
693 |
|
693 | |||
694 | else: |
|
694 | else: | |
695 | self._addfileindir(filename, status) |
|
695 | self._addfileindir(filename, status) | |
696 |
|
696 | |||
697 | if status not in self.statuses: |
|
697 | if status not in self.statuses: | |
698 | self.statuses.add(status) |
|
698 | self.statuses.add(status) | |
699 |
|
699 | |||
700 | def iterfilepaths(self): |
|
700 | def iterfilepaths(self): | |
701 | """Yield (status, path) for files directly under this directory.""" |
|
701 | """Yield (status, path) for files directly under this directory.""" | |
702 | for f, st in self.files: |
|
702 | for f, st in self.files: | |
703 | yield st, pathutil.join(self.path, f) |
|
703 | yield st, pathutil.join(self.path, f) | |
704 |
|
704 | |||
705 | def tersewalk(self, terseargs): |
|
705 | def tersewalk(self, terseargs): | |
706 | """ |
|
706 | """ | |
707 | Yield (status, path) obtained by processing the status of this |
|
707 | Yield (status, path) obtained by processing the status of this | |
708 | dirnode. |
|
708 | dirnode. | |
709 |
|
709 | |||
710 | terseargs is the string of arguments passed by the user with `--terse` |
|
710 | terseargs is the string of arguments passed by the user with `--terse` | |
711 | flag. |
|
711 | flag. | |
712 |
|
712 | |||
713 | Following are the cases which can happen: |
|
713 | Following are the cases which can happen: | |
714 |
|
714 | |||
715 | 1) All the files in the directory (including all the files in its |
|
715 | 1) All the files in the directory (including all the files in its | |
716 | subdirectories) share the same status and the user has asked us to terse |
|
716 | subdirectories) share the same status and the user has asked us to terse | |
717 | that status. -> yield (status, dirpath). dirpath will end in '/'. |
|
717 | that status. -> yield (status, dirpath). dirpath will end in '/'. | |
718 |
|
718 | |||
719 | 2) Otherwise, we do following: |
|
719 | 2) Otherwise, we do following: | |
720 |
|
720 | |||
721 | a) Yield (status, filepath) for all the files which are in this |
|
721 | a) Yield (status, filepath) for all the files which are in this | |
722 | directory (only the ones in this directory, not the subdirs) |
|
722 | directory (only the ones in this directory, not the subdirs) | |
723 |
|
723 | |||
724 | b) Recurse the function on all the subdirectories of this |
|
724 | b) Recurse the function on all the subdirectories of this | |
725 | directory |
|
725 | directory | |
726 | """ |
|
726 | """ | |
727 |
|
727 | |||
728 | if len(self.statuses) == 1: |
|
728 | if len(self.statuses) == 1: | |
729 | onlyst = self.statuses.pop() |
|
729 | onlyst = self.statuses.pop() | |
730 |
|
730 | |||
731 | # Making sure we terse only when the status abbreviation is |
|
731 | # Making sure we terse only when the status abbreviation is | |
732 | # passed as terse argument |
|
732 | # passed as terse argument | |
733 | if onlyst in terseargs: |
|
733 | if onlyst in terseargs: | |
734 | yield onlyst, self.path + b'/' |
|
734 | yield onlyst, self.path + b'/' | |
735 | return |
|
735 | return | |
736 |
|
736 | |||
737 | # add the files to status list |
|
737 | # add the files to status list | |
738 | for st, fpath in self.iterfilepaths(): |
|
738 | for st, fpath in self.iterfilepaths(): | |
739 | yield st, fpath |
|
739 | yield st, fpath | |
740 |
|
740 | |||
741 | # recurse on the subdirs |
|
741 | # recurse on the subdirs | |
742 | for dirobj in self.subdirs.values(): |
|
742 | for dirobj in self.subdirs.values(): | |
743 | for st, fpath in dirobj.tersewalk(terseargs): |
|
743 | for st, fpath in dirobj.tersewalk(terseargs): | |
744 | yield st, fpath |
|
744 | yield st, fpath | |
745 |
|
745 | |||
746 |
|
746 | |||
747 | def tersedir(statuslist, terseargs): |
|
747 | def tersedir(statuslist, terseargs): | |
748 | """ |
|
748 | """ | |
749 | Terse the status if all the files in a directory shares the same status. |
|
749 | Terse the status if all the files in a directory shares the same status. | |
750 |
|
750 | |||
751 | statuslist is scmutil.status() object which contains a list of files for |
|
751 | statuslist is scmutil.status() object which contains a list of files for | |
752 | each status. |
|
752 | each status. | |
753 | terseargs is string which is passed by the user as the argument to `--terse` |
|
753 | terseargs is string which is passed by the user as the argument to `--terse` | |
754 | flag. |
|
754 | flag. | |
755 |
|
755 | |||
756 | The function makes a tree of objects of dirnode class, and at each node it |
|
756 | The function makes a tree of objects of dirnode class, and at each node it | |
757 | stores the information required to know whether we can terse a certain |
|
757 | stores the information required to know whether we can terse a certain | |
758 | directory or not. |
|
758 | directory or not. | |
759 | """ |
|
759 | """ | |
760 | # the order matters here as that is used to produce final list |
|
760 | # the order matters here as that is used to produce final list | |
761 | allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c') |
|
761 | allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c') | |
762 |
|
762 | |||
763 | # checking the argument validity |
|
763 | # checking the argument validity | |
764 | for s in pycompat.bytestr(terseargs): |
|
764 | for s in pycompat.bytestr(terseargs): | |
765 | if s not in allst: |
|
765 | if s not in allst: | |
766 | raise error.Abort(_(b"'%s' not recognized") % s) |
|
766 | raise error.Abort(_(b"'%s' not recognized") % s) | |
767 |
|
767 | |||
768 | # creating a dirnode object for the root of the repo |
|
768 | # creating a dirnode object for the root of the repo | |
769 | rootobj = dirnode(b'') |
|
769 | rootobj = dirnode(b'') | |
770 | pstatus = ( |
|
770 | pstatus = ( | |
771 | b'modified', |
|
771 | b'modified', | |
772 | b'added', |
|
772 | b'added', | |
773 | b'deleted', |
|
773 | b'deleted', | |
774 | b'clean', |
|
774 | b'clean', | |
775 | b'unknown', |
|
775 | b'unknown', | |
776 | b'ignored', |
|
776 | b'ignored', | |
777 | b'removed', |
|
777 | b'removed', | |
778 | ) |
|
778 | ) | |
779 |
|
779 | |||
780 | tersedict = {} |
|
780 | tersedict = {} | |
781 | for attrname in pstatus: |
|
781 | for attrname in pstatus: | |
782 | statuschar = attrname[0:1] |
|
782 | statuschar = attrname[0:1] | |
783 | for f in getattr(statuslist, attrname): |
|
783 | for f in getattr(statuslist, attrname): | |
784 | rootobj.addfile(f, statuschar) |
|
784 | rootobj.addfile(f, statuschar) | |
785 | tersedict[statuschar] = [] |
|
785 | tersedict[statuschar] = [] | |
786 |
|
786 | |||
787 | # we won't be tersing the root dir, so add files in it |
|
787 | # we won't be tersing the root dir, so add files in it | |
788 | for st, fpath in rootobj.iterfilepaths(): |
|
788 | for st, fpath in rootobj.iterfilepaths(): | |
789 | tersedict[st].append(fpath) |
|
789 | tersedict[st].append(fpath) | |
790 |
|
790 | |||
791 | # process each sub-directory and build tersedict |
|
791 | # process each sub-directory and build tersedict | |
792 | for subdir in rootobj.subdirs.values(): |
|
792 | for subdir in rootobj.subdirs.values(): | |
793 | for st, f in subdir.tersewalk(terseargs): |
|
793 | for st, f in subdir.tersewalk(terseargs): | |
794 | tersedict[st].append(f) |
|
794 | tersedict[st].append(f) | |
795 |
|
795 | |||
796 | tersedlist = [] |
|
796 | tersedlist = [] | |
797 | for st in allst: |
|
797 | for st in allst: | |
798 | tersedict[st].sort() |
|
798 | tersedict[st].sort() | |
799 | tersedlist.append(tersedict[st]) |
|
799 | tersedlist.append(tersedict[st]) | |
800 |
|
800 | |||
801 | return scmutil.status(*tersedlist) |
|
801 | return scmutil.status(*tersedlist) | |
802 |
|
802 | |||
803 |
|
803 | |||
804 | def _commentlines(raw): |
|
804 | def _commentlines(raw): | |
805 | '''Surround lineswith a comment char and a new line''' |
|
805 | '''Surround lineswith a comment char and a new line''' | |
806 | lines = raw.splitlines() |
|
806 | lines = raw.splitlines() | |
807 | commentedlines = [b'# %s' % line for line in lines] |
|
807 | commentedlines = [b'# %s' % line for line in lines] | |
808 | return b'\n'.join(commentedlines) + b'\n' |
|
808 | return b'\n'.join(commentedlines) + b'\n' | |
809 |
|
809 | |||
810 |
|
810 | |||
811 | @attr.s(frozen=True) |
|
811 | @attr.s(frozen=True) | |
812 | class morestatus(object): |
|
812 | class morestatus(object): | |
813 | reporoot = attr.ib() |
|
813 | reporoot = attr.ib() | |
814 | unfinishedop = attr.ib() |
|
814 | unfinishedop = attr.ib() | |
815 | unfinishedmsg = attr.ib() |
|
815 | unfinishedmsg = attr.ib() | |
816 | activemerge = attr.ib() |
|
816 | activemerge = attr.ib() | |
817 | unresolvedpaths = attr.ib() |
|
817 | unresolvedpaths = attr.ib() | |
818 | _formattedpaths = attr.ib(init=False, default=set()) |
|
818 | _formattedpaths = attr.ib(init=False, default=set()) | |
819 | _label = b'status.morestatus' |
|
819 | _label = b'status.morestatus' | |
820 |
|
820 | |||
821 | def formatfile(self, path, fm): |
|
821 | def formatfile(self, path, fm): | |
822 | self._formattedpaths.add(path) |
|
822 | self._formattedpaths.add(path) | |
823 | if self.activemerge and path in self.unresolvedpaths: |
|
823 | if self.activemerge and path in self.unresolvedpaths: | |
824 | fm.data(unresolved=True) |
|
824 | fm.data(unresolved=True) | |
825 |
|
825 | |||
826 | def formatfooter(self, fm): |
|
826 | def formatfooter(self, fm): | |
827 | if self.unfinishedop or self.unfinishedmsg: |
|
827 | if self.unfinishedop or self.unfinishedmsg: | |
828 | fm.startitem() |
|
828 | fm.startitem() | |
829 | fm.data(itemtype=b'morestatus') |
|
829 | fm.data(itemtype=b'morestatus') | |
830 |
|
830 | |||
831 | if self.unfinishedop: |
|
831 | if self.unfinishedop: | |
832 | fm.data(unfinished=self.unfinishedop) |
|
832 | fm.data(unfinished=self.unfinishedop) | |
833 | statemsg = ( |
|
833 | statemsg = ( | |
834 | _(b'The repository is in an unfinished *%s* state.') |
|
834 | _(b'The repository is in an unfinished *%s* state.') | |
835 | % self.unfinishedop |
|
835 | % self.unfinishedop | |
836 | ) |
|
836 | ) | |
837 | fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label) |
|
837 | fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label) | |
838 | if self.unfinishedmsg: |
|
838 | if self.unfinishedmsg: | |
839 | fm.data(unfinishedmsg=self.unfinishedmsg) |
|
839 | fm.data(unfinishedmsg=self.unfinishedmsg) | |
840 |
|
840 | |||
841 | # May also start new data items. |
|
841 | # May also start new data items. | |
842 | self._formatconflicts(fm) |
|
842 | self._formatconflicts(fm) | |
843 |
|
843 | |||
844 | if self.unfinishedmsg: |
|
844 | if self.unfinishedmsg: | |
845 | fm.plain( |
|
845 | fm.plain( | |
846 | b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label |
|
846 | b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label | |
847 | ) |
|
847 | ) | |
848 |
|
848 | |||
849 | def _formatconflicts(self, fm): |
|
849 | def _formatconflicts(self, fm): | |
850 | if not self.activemerge: |
|
850 | if not self.activemerge: | |
851 | return |
|
851 | return | |
852 |
|
852 | |||
853 | if self.unresolvedpaths: |
|
853 | if self.unresolvedpaths: | |
854 | mergeliststr = b'\n'.join( |
|
854 | mergeliststr = b'\n'.join( | |
855 | [ |
|
855 | [ | |
856 | b' %s' |
|
856 | b' %s' | |
857 | % util.pathto(self.reporoot, encoding.getcwd(), path) |
|
857 | % util.pathto(self.reporoot, encoding.getcwd(), path) | |
858 | for path in self.unresolvedpaths |
|
858 | for path in self.unresolvedpaths | |
859 | ] |
|
859 | ] | |
860 | ) |
|
860 | ) | |
861 | msg = ( |
|
861 | msg = ( | |
862 | _( |
|
862 | _( | |
863 | '''Unresolved merge conflicts: |
|
863 | '''Unresolved merge conflicts: | |
864 |
|
864 | |||
865 | %s |
|
865 | %s | |
866 |
|
866 | |||
867 | To mark files as resolved: hg resolve --mark FILE''' |
|
867 | To mark files as resolved: hg resolve --mark FILE''' | |
868 | ) |
|
868 | ) | |
869 | % mergeliststr |
|
869 | % mergeliststr | |
870 | ) |
|
870 | ) | |
871 |
|
871 | |||
872 | # If any paths with unresolved conflicts were not previously |
|
872 | # If any paths with unresolved conflicts were not previously | |
873 | # formatted, output them now. |
|
873 | # formatted, output them now. | |
874 | for f in self.unresolvedpaths: |
|
874 | for f in self.unresolvedpaths: | |
875 | if f in self._formattedpaths: |
|
875 | if f in self._formattedpaths: | |
876 | # Already output. |
|
876 | # Already output. | |
877 | continue |
|
877 | continue | |
878 | fm.startitem() |
|
878 | fm.startitem() | |
879 | # We can't claim to know the status of the file - it may just |
|
879 | # We can't claim to know the status of the file - it may just | |
880 | # have been in one of the states that were not requested for |
|
880 | # have been in one of the states that were not requested for | |
881 | # display, so it could be anything. |
|
881 | # display, so it could be anything. | |
882 | fm.data(itemtype=b'file', path=f, unresolved=True) |
|
882 | fm.data(itemtype=b'file', path=f, unresolved=True) | |
883 |
|
883 | |||
884 | else: |
|
884 | else: | |
885 | msg = _(b'No unresolved merge conflicts.') |
|
885 | msg = _(b'No unresolved merge conflicts.') | |
886 |
|
886 | |||
887 | fm.plain(b'%s\n' % _commentlines(msg), label=self._label) |
|
887 | fm.plain(b'%s\n' % _commentlines(msg), label=self._label) | |
888 |
|
888 | |||
889 |
|
889 | |||
890 | def readmorestatus(repo): |
|
890 | def readmorestatus(repo): | |
891 | """Returns a morestatus object if the repo has unfinished state.""" |
|
891 | """Returns a morestatus object if the repo has unfinished state.""" | |
892 | statetuple = statemod.getrepostate(repo) |
|
892 | statetuple = statemod.getrepostate(repo) | |
893 | mergestate = mergemod.mergestate.read(repo) |
|
893 | mergestate = mergemod.mergestate.read(repo) | |
894 | activemerge = mergestate.active() |
|
894 | activemerge = mergestate.active() | |
895 | if not statetuple and not activemerge: |
|
895 | if not statetuple and not activemerge: | |
896 | return None |
|
896 | return None | |
897 |
|
897 | |||
898 | unfinishedop = unfinishedmsg = unresolved = None |
|
898 | unfinishedop = unfinishedmsg = unresolved = None | |
899 | if statetuple: |
|
899 | if statetuple: | |
900 | unfinishedop, unfinishedmsg = statetuple |
|
900 | unfinishedop, unfinishedmsg = statetuple | |
901 | if activemerge: |
|
901 | if activemerge: | |
902 | unresolved = sorted(mergestate.unresolved()) |
|
902 | unresolved = sorted(mergestate.unresolved()) | |
903 | return morestatus( |
|
903 | return morestatus( | |
904 | repo.root, unfinishedop, unfinishedmsg, activemerge, unresolved |
|
904 | repo.root, unfinishedop, unfinishedmsg, activemerge, unresolved | |
905 | ) |
|
905 | ) | |
906 |
|
906 | |||
907 |
|
907 | |||
908 | def findpossible(cmd, table, strict=False): |
|
908 | def findpossible(cmd, table, strict=False): | |
909 | """ |
|
909 | """ | |
910 | Return cmd -> (aliases, command table entry) |
|
910 | Return cmd -> (aliases, command table entry) | |
911 | for each matching command. |
|
911 | for each matching command. | |
912 | Return debug commands (or their aliases) only if no normal command matches. |
|
912 | Return debug commands (or their aliases) only if no normal command matches. | |
913 | """ |
|
913 | """ | |
914 | choice = {} |
|
914 | choice = {} | |
915 | debugchoice = {} |
|
915 | debugchoice = {} | |
916 |
|
916 | |||
917 | if cmd in table: |
|
917 | if cmd in table: | |
918 | # short-circuit exact matches, "log" alias beats "log|history" |
|
918 | # short-circuit exact matches, "log" alias beats "log|history" | |
919 | keys = [cmd] |
|
919 | keys = [cmd] | |
920 | else: |
|
920 | else: | |
921 | keys = table.keys() |
|
921 | keys = table.keys() | |
922 |
|
922 | |||
923 | allcmds = [] |
|
923 | allcmds = [] | |
924 | for e in keys: |
|
924 | for e in keys: | |
925 | aliases = parsealiases(e) |
|
925 | aliases = parsealiases(e) | |
926 | allcmds.extend(aliases) |
|
926 | allcmds.extend(aliases) | |
927 | found = None |
|
927 | found = None | |
928 | if cmd in aliases: |
|
928 | if cmd in aliases: | |
929 | found = cmd |
|
929 | found = cmd | |
930 | elif not strict: |
|
930 | elif not strict: | |
931 | for a in aliases: |
|
931 | for a in aliases: | |
932 | if a.startswith(cmd): |
|
932 | if a.startswith(cmd): | |
933 | found = a |
|
933 | found = a | |
934 | break |
|
934 | break | |
935 | if found is not None: |
|
935 | if found is not None: | |
936 | if aliases[0].startswith(b"debug") or found.startswith(b"debug"): |
|
936 | if aliases[0].startswith(b"debug") or found.startswith(b"debug"): | |
937 | debugchoice[found] = (aliases, table[e]) |
|
937 | debugchoice[found] = (aliases, table[e]) | |
938 | else: |
|
938 | else: | |
939 | choice[found] = (aliases, table[e]) |
|
939 | choice[found] = (aliases, table[e]) | |
940 |
|
940 | |||
941 | if not choice and debugchoice: |
|
941 | if not choice and debugchoice: | |
942 | choice = debugchoice |
|
942 | choice = debugchoice | |
943 |
|
943 | |||
944 | return choice, allcmds |
|
944 | return choice, allcmds | |
945 |
|
945 | |||
946 |
|
946 | |||
947 | def findcmd(cmd, table, strict=True): |
|
947 | def findcmd(cmd, table, strict=True): | |
948 | """Return (aliases, command table entry) for command string.""" |
|
948 | """Return (aliases, command table entry) for command string.""" | |
949 | choice, allcmds = findpossible(cmd, table, strict) |
|
949 | choice, allcmds = findpossible(cmd, table, strict) | |
950 |
|
950 | |||
951 | if cmd in choice: |
|
951 | if cmd in choice: | |
952 | return choice[cmd] |
|
952 | return choice[cmd] | |
953 |
|
953 | |||
954 | if len(choice) > 1: |
|
954 | if len(choice) > 1: | |
955 | clist = sorted(choice) |
|
955 | clist = sorted(choice) | |
956 | raise error.AmbiguousCommand(cmd, clist) |
|
956 | raise error.AmbiguousCommand(cmd, clist) | |
957 |
|
957 | |||
958 | if choice: |
|
958 | if choice: | |
959 | return list(choice.values())[0] |
|
959 | return list(choice.values())[0] | |
960 |
|
960 | |||
961 | raise error.UnknownCommand(cmd, allcmds) |
|
961 | raise error.UnknownCommand(cmd, allcmds) | |
962 |
|
962 | |||
963 |
|
963 | |||
964 | def changebranch(ui, repo, revs, label): |
|
964 | def changebranch(ui, repo, revs, label): | |
965 | """ Change the branch name of given revs to label """ |
|
965 | """ Change the branch name of given revs to label """ | |
966 |
|
966 | |||
967 | with repo.wlock(), repo.lock(), repo.transaction(b'branches'): |
|
967 | with repo.wlock(), repo.lock(), repo.transaction(b'branches'): | |
968 | # abort in case of uncommitted merge or dirty wdir |
|
968 | # abort in case of uncommitted merge or dirty wdir | |
969 | bailifchanged(repo) |
|
969 | bailifchanged(repo) | |
970 | revs = scmutil.revrange(repo, revs) |
|
970 | revs = scmutil.revrange(repo, revs) | |
971 | if not revs: |
|
971 | if not revs: | |
972 | raise error.Abort(b"empty revision set") |
|
972 | raise error.Abort(b"empty revision set") | |
973 | roots = repo.revs(b'roots(%ld)', revs) |
|
973 | roots = repo.revs(b'roots(%ld)', revs) | |
974 | if len(roots) > 1: |
|
974 | if len(roots) > 1: | |
975 | raise error.Abort( |
|
975 | raise error.Abort( | |
976 | _(b"cannot change branch of non-linear revisions") |
|
976 | _(b"cannot change branch of non-linear revisions") | |
977 | ) |
|
977 | ) | |
978 | rewriteutil.precheck(repo, revs, b'change branch of') |
|
978 | rewriteutil.precheck(repo, revs, b'change branch of') | |
979 |
|
979 | |||
980 | root = repo[roots.first()] |
|
980 | root = repo[roots.first()] | |
981 | rpb = {parent.branch() for parent in root.parents()} |
|
981 | rpb = {parent.branch() for parent in root.parents()} | |
982 | if label not in rpb and label in repo.branchmap(): |
|
982 | if label not in rpb and label in repo.branchmap(): | |
983 | raise error.Abort(_(b"a branch of the same name already exists")) |
|
983 | raise error.Abort(_(b"a branch of the same name already exists")) | |
984 |
|
984 | |||
985 | if repo.revs(b'obsolete() and %ld', revs): |
|
985 | if repo.revs(b'obsolete() and %ld', revs): | |
986 | raise error.Abort( |
|
986 | raise error.Abort( | |
987 | _(b"cannot change branch of a obsolete changeset") |
|
987 | _(b"cannot change branch of a obsolete changeset") | |
988 | ) |
|
988 | ) | |
989 |
|
989 | |||
990 | # make sure only topological heads |
|
990 | # make sure only topological heads | |
991 | if repo.revs(b'heads(%ld) - head()', revs): |
|
991 | if repo.revs(b'heads(%ld) - head()', revs): | |
992 | raise error.Abort(_(b"cannot change branch in middle of a stack")) |
|
992 | raise error.Abort(_(b"cannot change branch in middle of a stack")) | |
993 |
|
993 | |||
994 | replacements = {} |
|
994 | replacements = {} | |
995 | # avoid import cycle mercurial.cmdutil -> mercurial.context -> |
|
995 | # avoid import cycle mercurial.cmdutil -> mercurial.context -> | |
996 | # mercurial.subrepo -> mercurial.cmdutil |
|
996 | # mercurial.subrepo -> mercurial.cmdutil | |
997 | from . import context |
|
997 | from . import context | |
998 |
|
998 | |||
999 | for rev in revs: |
|
999 | for rev in revs: | |
1000 | ctx = repo[rev] |
|
1000 | ctx = repo[rev] | |
1001 | oldbranch = ctx.branch() |
|
1001 | oldbranch = ctx.branch() | |
1002 | # check if ctx has same branch |
|
1002 | # check if ctx has same branch | |
1003 | if oldbranch == label: |
|
1003 | if oldbranch == label: | |
1004 | continue |
|
1004 | continue | |
1005 |
|
1005 | |||
1006 | def filectxfn(repo, newctx, path): |
|
1006 | def filectxfn(repo, newctx, path): | |
1007 | try: |
|
1007 | try: | |
1008 | return ctx[path] |
|
1008 | return ctx[path] | |
1009 | except error.ManifestLookupError: |
|
1009 | except error.ManifestLookupError: | |
1010 | return None |
|
1010 | return None | |
1011 |
|
1011 | |||
1012 | ui.debug( |
|
1012 | ui.debug( | |
1013 | b"changing branch of '%s' from '%s' to '%s'\n" |
|
1013 | b"changing branch of '%s' from '%s' to '%s'\n" | |
1014 | % (hex(ctx.node()), oldbranch, label) |
|
1014 | % (hex(ctx.node()), oldbranch, label) | |
1015 | ) |
|
1015 | ) | |
1016 | extra = ctx.extra() |
|
1016 | extra = ctx.extra() | |
1017 | extra[b'branch_change'] = hex(ctx.node()) |
|
1017 | extra[b'branch_change'] = hex(ctx.node()) | |
1018 | # While changing branch of set of linear commits, make sure that |
|
1018 | # While changing branch of set of linear commits, make sure that | |
1019 | # we base our commits on new parent rather than old parent which |
|
1019 | # we base our commits on new parent rather than old parent which | |
1020 | # was obsoleted while changing the branch |
|
1020 | # was obsoleted while changing the branch | |
1021 | p1 = ctx.p1().node() |
|
1021 | p1 = ctx.p1().node() | |
1022 | p2 = ctx.p2().node() |
|
1022 | p2 = ctx.p2().node() | |
1023 | if p1 in replacements: |
|
1023 | if p1 in replacements: | |
1024 | p1 = replacements[p1][0] |
|
1024 | p1 = replacements[p1][0] | |
1025 | if p2 in replacements: |
|
1025 | if p2 in replacements: | |
1026 | p2 = replacements[p2][0] |
|
1026 | p2 = replacements[p2][0] | |
1027 |
|
1027 | |||
1028 | mc = context.memctx( |
|
1028 | mc = context.memctx( | |
1029 | repo, |
|
1029 | repo, | |
1030 | (p1, p2), |
|
1030 | (p1, p2), | |
1031 | ctx.description(), |
|
1031 | ctx.description(), | |
1032 | ctx.files(), |
|
1032 | ctx.files(), | |
1033 | filectxfn, |
|
1033 | filectxfn, | |
1034 | user=ctx.user(), |
|
1034 | user=ctx.user(), | |
1035 | date=ctx.date(), |
|
1035 | date=ctx.date(), | |
1036 | extra=extra, |
|
1036 | extra=extra, | |
1037 | branch=label, |
|
1037 | branch=label, | |
1038 | ) |
|
1038 | ) | |
1039 |
|
1039 | |||
1040 | newnode = repo.commitctx(mc) |
|
1040 | newnode = repo.commitctx(mc) | |
1041 | replacements[ctx.node()] = (newnode,) |
|
1041 | replacements[ctx.node()] = (newnode,) | |
1042 | ui.debug(b'new node id is %s\n' % hex(newnode)) |
|
1042 | ui.debug(b'new node id is %s\n' % hex(newnode)) | |
1043 |
|
1043 | |||
1044 | # create obsmarkers and move bookmarks |
|
1044 | # create obsmarkers and move bookmarks | |
1045 | scmutil.cleanupnodes( |
|
1045 | scmutil.cleanupnodes( | |
1046 | repo, replacements, b'branch-change', fixphase=True |
|
1046 | repo, replacements, b'branch-change', fixphase=True | |
1047 | ) |
|
1047 | ) | |
1048 |
|
1048 | |||
1049 | # move the working copy too |
|
1049 | # move the working copy too | |
1050 | wctx = repo[None] |
|
1050 | wctx = repo[None] | |
1051 | # in-progress merge is a bit too complex for now. |
|
1051 | # in-progress merge is a bit too complex for now. | |
1052 | if len(wctx.parents()) == 1: |
|
1052 | if len(wctx.parents()) == 1: | |
1053 | newid = replacements.get(wctx.p1().node()) |
|
1053 | newid = replacements.get(wctx.p1().node()) | |
1054 | if newid is not None: |
|
1054 | if newid is not None: | |
1055 | # avoid import cycle mercurial.cmdutil -> mercurial.hg -> |
|
1055 | # avoid import cycle mercurial.cmdutil -> mercurial.hg -> | |
1056 | # mercurial.cmdutil |
|
1056 | # mercurial.cmdutil | |
1057 | from . import hg |
|
1057 | from . import hg | |
1058 |
|
1058 | |||
1059 | hg.update(repo, newid[0], quietempty=True) |
|
1059 | hg.update(repo, newid[0], quietempty=True) | |
1060 |
|
1060 | |||
1061 | ui.status(_(b"changed branch on %d changesets\n") % len(replacements)) |
|
1061 | ui.status(_(b"changed branch on %d changesets\n") % len(replacements)) | |
1062 |
|
1062 | |||
1063 |
|
1063 | |||
1064 | def findrepo(p): |
|
1064 | def findrepo(p): | |
1065 | while not os.path.isdir(os.path.join(p, b".hg")): |
|
1065 | while not os.path.isdir(os.path.join(p, b".hg")): | |
1066 | oldp, p = p, os.path.dirname(p) |
|
1066 | oldp, p = p, os.path.dirname(p) | |
1067 | if p == oldp: |
|
1067 | if p == oldp: | |
1068 | return None |
|
1068 | return None | |
1069 |
|
1069 | |||
1070 | return p |
|
1070 | return p | |
1071 |
|
1071 | |||
1072 |
|
1072 | |||
1073 | def bailifchanged(repo, merge=True, hint=None): |
|
1073 | def bailifchanged(repo, merge=True, hint=None): | |
1074 | """ enforce the precondition that working directory must be clean. |
|
1074 | """ enforce the precondition that working directory must be clean. | |
1075 |
|
1075 | |||
1076 | 'merge' can be set to false if a pending uncommitted merge should be |
|
1076 | 'merge' can be set to false if a pending uncommitted merge should be | |
1077 | ignored (such as when 'update --check' runs). |
|
1077 | ignored (such as when 'update --check' runs). | |
1078 |
|
1078 | |||
1079 | 'hint' is the usual hint given to Abort exception. |
|
1079 | 'hint' is the usual hint given to Abort exception. | |
1080 | """ |
|
1080 | """ | |
1081 |
|
1081 | |||
1082 | if merge and repo.dirstate.p2() != nullid: |
|
1082 | if merge and repo.dirstate.p2() != nullid: | |
1083 | raise error.Abort(_(b'outstanding uncommitted merge'), hint=hint) |
|
1083 | raise error.Abort(_(b'outstanding uncommitted merge'), hint=hint) | |
1084 | st = repo.status() |
|
1084 | st = repo.status() | |
1085 | if st.modified or st.added or st.removed or st.deleted: |
|
1085 | if st.modified or st.added or st.removed or st.deleted: | |
1086 | raise error.Abort(_(b'uncommitted changes'), hint=hint) |
|
1086 | raise error.Abort(_(b'uncommitted changes'), hint=hint) | |
1087 | ctx = repo[None] |
|
1087 | ctx = repo[None] | |
1088 | for s in sorted(ctx.substate): |
|
1088 | for s in sorted(ctx.substate): | |
1089 | ctx.sub(s).bailifchanged(hint=hint) |
|
1089 | ctx.sub(s).bailifchanged(hint=hint) | |
1090 |
|
1090 | |||
1091 |
|
1091 | |||
1092 | def logmessage(ui, opts): |
|
1092 | def logmessage(ui, opts): | |
1093 | """ get the log message according to -m and -l option """ |
|
1093 | """ get the log message according to -m and -l option """ | |
1094 |
|
1094 | |||
1095 | check_at_most_one_arg(opts, b'message', b'logfile') |
|
1095 | check_at_most_one_arg(opts, b'message', b'logfile') | |
1096 |
|
1096 | |||
1097 | message = opts.get(b'message') |
|
1097 | message = opts.get(b'message') | |
1098 | logfile = opts.get(b'logfile') |
|
1098 | logfile = opts.get(b'logfile') | |
1099 |
|
1099 | |||
1100 | if not message and logfile: |
|
1100 | if not message and logfile: | |
1101 | try: |
|
1101 | try: | |
1102 | if isstdiofilename(logfile): |
|
1102 | if isstdiofilename(logfile): | |
1103 | message = ui.fin.read() |
|
1103 | message = ui.fin.read() | |
1104 | else: |
|
1104 | else: | |
1105 | message = b'\n'.join(util.readfile(logfile).splitlines()) |
|
1105 | message = b'\n'.join(util.readfile(logfile).splitlines()) | |
1106 | except IOError as inst: |
|
1106 | except IOError as inst: | |
1107 | raise error.Abort( |
|
1107 | raise error.Abort( | |
1108 | _(b"can't read commit message '%s': %s") |
|
1108 | _(b"can't read commit message '%s': %s") | |
1109 | % (logfile, encoding.strtolocal(inst.strerror)) |
|
1109 | % (logfile, encoding.strtolocal(inst.strerror)) | |
1110 | ) |
|
1110 | ) | |
1111 | return message |
|
1111 | return message | |
1112 |
|
1112 | |||
1113 |
|
1113 | |||
1114 | def mergeeditform(ctxorbool, baseformname): |
|
1114 | def mergeeditform(ctxorbool, baseformname): | |
1115 | """return appropriate editform name (referencing a committemplate) |
|
1115 | """return appropriate editform name (referencing a committemplate) | |
1116 |
|
1116 | |||
1117 | 'ctxorbool' is either a ctx to be committed, or a bool indicating whether |
|
1117 | 'ctxorbool' is either a ctx to be committed, or a bool indicating whether | |
1118 | merging is committed. |
|
1118 | merging is committed. | |
1119 |
|
1119 | |||
1120 | This returns baseformname with '.merge' appended if it is a merge, |
|
1120 | This returns baseformname with '.merge' appended if it is a merge, | |
1121 | otherwise '.normal' is appended. |
|
1121 | otherwise '.normal' is appended. | |
1122 | """ |
|
1122 | """ | |
1123 | if isinstance(ctxorbool, bool): |
|
1123 | if isinstance(ctxorbool, bool): | |
1124 | if ctxorbool: |
|
1124 | if ctxorbool: | |
1125 | return baseformname + b".merge" |
|
1125 | return baseformname + b".merge" | |
1126 | elif len(ctxorbool.parents()) > 1: |
|
1126 | elif len(ctxorbool.parents()) > 1: | |
1127 | return baseformname + b".merge" |
|
1127 | return baseformname + b".merge" | |
1128 |
|
1128 | |||
1129 | return baseformname + b".normal" |
|
1129 | return baseformname + b".normal" | |
1130 |
|
1130 | |||
1131 |
|
1131 | |||
1132 | def getcommiteditor( |
|
1132 | def getcommiteditor( | |
1133 | edit=False, finishdesc=None, extramsg=None, editform=b'', **opts |
|
1133 | edit=False, finishdesc=None, extramsg=None, editform=b'', **opts | |
1134 | ): |
|
1134 | ): | |
1135 | """get appropriate commit message editor according to '--edit' option |
|
1135 | """get appropriate commit message editor according to '--edit' option | |
1136 |
|
1136 | |||
1137 | 'finishdesc' is a function to be called with edited commit message |
|
1137 | 'finishdesc' is a function to be called with edited commit message | |
1138 | (= 'description' of the new changeset) just after editing, but |
|
1138 | (= 'description' of the new changeset) just after editing, but | |
1139 | before checking empty-ness. It should return actual text to be |
|
1139 | before checking empty-ness. It should return actual text to be | |
1140 | stored into history. This allows to change description before |
|
1140 | stored into history. This allows to change description before | |
1141 | storing. |
|
1141 | storing. | |
1142 |
|
1142 | |||
1143 | 'extramsg' is a extra message to be shown in the editor instead of |
|
1143 | 'extramsg' is a extra message to be shown in the editor instead of | |
1144 | 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL |
|
1144 | 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL | |
1145 | is automatically added. |
|
1145 | is automatically added. | |
1146 |
|
1146 | |||
1147 | 'editform' is a dot-separated list of names, to distinguish |
|
1147 | 'editform' is a dot-separated list of names, to distinguish | |
1148 | the purpose of commit text editing. |
|
1148 | the purpose of commit text editing. | |
1149 |
|
1149 | |||
1150 | 'getcommiteditor' returns 'commitforceeditor' regardless of |
|
1150 | 'getcommiteditor' returns 'commitforceeditor' regardless of | |
1151 | 'edit', if one of 'finishdesc' or 'extramsg' is specified, because |
|
1151 | 'edit', if one of 'finishdesc' or 'extramsg' is specified, because | |
1152 | they are specific for usage in MQ. |
|
1152 | they are specific for usage in MQ. | |
1153 | """ |
|
1153 | """ | |
1154 | if edit or finishdesc or extramsg: |
|
1154 | if edit or finishdesc or extramsg: | |
1155 | return lambda r, c, s: commitforceeditor( |
|
1155 | return lambda r, c, s: commitforceeditor( | |
1156 | r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform |
|
1156 | r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform | |
1157 | ) |
|
1157 | ) | |
1158 | elif editform: |
|
1158 | elif editform: | |
1159 | return lambda r, c, s: commiteditor(r, c, s, editform=editform) |
|
1159 | return lambda r, c, s: commiteditor(r, c, s, editform=editform) | |
1160 | else: |
|
1160 | else: | |
1161 | return commiteditor |
|
1161 | return commiteditor | |
1162 |
|
1162 | |||
1163 |
|
1163 | |||
1164 | def _escapecommandtemplate(tmpl): |
|
1164 | def _escapecommandtemplate(tmpl): | |
1165 | parts = [] |
|
1165 | parts = [] | |
1166 | for typ, start, end in templater.scantemplate(tmpl, raw=True): |
|
1166 | for typ, start, end in templater.scantemplate(tmpl, raw=True): | |
1167 | if typ == b'string': |
|
1167 | if typ == b'string': | |
1168 | parts.append(stringutil.escapestr(tmpl[start:end])) |
|
1168 | parts.append(stringutil.escapestr(tmpl[start:end])) | |
1169 | else: |
|
1169 | else: | |
1170 | parts.append(tmpl[start:end]) |
|
1170 | parts.append(tmpl[start:end]) | |
1171 | return b''.join(parts) |
|
1171 | return b''.join(parts) | |
1172 |
|
1172 | |||
1173 |
|
1173 | |||
1174 | def rendercommandtemplate(ui, tmpl, props): |
|
1174 | def rendercommandtemplate(ui, tmpl, props): | |
1175 | r"""Expand a literal template 'tmpl' in a way suitable for command line |
|
1175 | r"""Expand a literal template 'tmpl' in a way suitable for command line | |
1176 |
|
1176 | |||
1177 | '\' in outermost string is not taken as an escape character because it |
|
1177 | '\' in outermost string is not taken as an escape character because it | |
1178 | is a directory separator on Windows. |
|
1178 | is a directory separator on Windows. | |
1179 |
|
1179 | |||
1180 | >>> from . import ui as uimod |
|
1180 | >>> from . import ui as uimod | |
1181 | >>> ui = uimod.ui() |
|
1181 | >>> ui = uimod.ui() | |
1182 | >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'}) |
|
1182 | >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'}) | |
1183 | 'c:\\foo' |
|
1183 | 'c:\\foo' | |
1184 | >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'}) |
|
1184 | >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'}) | |
1185 | 'c:{path}' |
|
1185 | 'c:{path}' | |
1186 | """ |
|
1186 | """ | |
1187 | if not tmpl: |
|
1187 | if not tmpl: | |
1188 | return tmpl |
|
1188 | return tmpl | |
1189 | t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl)) |
|
1189 | t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl)) | |
1190 | return t.renderdefault(props) |
|
1190 | return t.renderdefault(props) | |
1191 |
|
1191 | |||
1192 |
|
1192 | |||
1193 | def rendertemplate(ctx, tmpl, props=None): |
|
1193 | def rendertemplate(ctx, tmpl, props=None): | |
1194 | """Expand a literal template 'tmpl' byte-string against one changeset |
|
1194 | """Expand a literal template 'tmpl' byte-string against one changeset | |
1195 |
|
1195 | |||
1196 | Each props item must be a stringify-able value or a callable returning |
|
1196 | Each props item must be a stringify-able value or a callable returning | |
1197 | such value, i.e. no bare list nor dict should be passed. |
|
1197 | such value, i.e. no bare list nor dict should be passed. | |
1198 | """ |
|
1198 | """ | |
1199 | repo = ctx.repo() |
|
1199 | repo = ctx.repo() | |
1200 | tres = formatter.templateresources(repo.ui, repo) |
|
1200 | tres = formatter.templateresources(repo.ui, repo) | |
1201 | t = formatter.maketemplater( |
|
1201 | t = formatter.maketemplater( | |
1202 | repo.ui, tmpl, defaults=templatekw.keywords, resources=tres |
|
1202 | repo.ui, tmpl, defaults=templatekw.keywords, resources=tres | |
1203 | ) |
|
1203 | ) | |
1204 | mapping = {b'ctx': ctx} |
|
1204 | mapping = {b'ctx': ctx} | |
1205 | if props: |
|
1205 | if props: | |
1206 | mapping.update(props) |
|
1206 | mapping.update(props) | |
1207 | return t.renderdefault(mapping) |
|
1207 | return t.renderdefault(mapping) | |
1208 |
|
1208 | |||
1209 |
|
1209 | |||
1210 | def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None): |
|
1210 | def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None): | |
1211 | r"""Convert old-style filename format string to template string |
|
1211 | r"""Convert old-style filename format string to template string | |
1212 |
|
1212 | |||
1213 | >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0) |
|
1213 | >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0) | |
1214 | 'foo-{reporoot|basename}-{seqno}.patch' |
|
1214 | 'foo-{reporoot|basename}-{seqno}.patch' | |
1215 | >>> _buildfntemplate(b'%R{tags % "{tag}"}%H') |
|
1215 | >>> _buildfntemplate(b'%R{tags % "{tag}"}%H') | |
1216 | '{rev}{tags % "{tag}"}{node}' |
|
1216 | '{rev}{tags % "{tag}"}{node}' | |
1217 |
|
1217 | |||
1218 | '\' in outermost strings has to be escaped because it is a directory |
|
1218 | '\' in outermost strings has to be escaped because it is a directory | |
1219 | separator on Windows: |
|
1219 | separator on Windows: | |
1220 |
|
1220 | |||
1221 | >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0) |
|
1221 | >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0) | |
1222 | 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch' |
|
1222 | 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch' | |
1223 | >>> _buildfntemplate(b'\\\\foo\\bar.patch') |
|
1223 | >>> _buildfntemplate(b'\\\\foo\\bar.patch') | |
1224 | '\\\\\\\\foo\\\\bar.patch' |
|
1224 | '\\\\\\\\foo\\\\bar.patch' | |
1225 | >>> _buildfntemplate(b'\\{tags % "{tag}"}') |
|
1225 | >>> _buildfntemplate(b'\\{tags % "{tag}"}') | |
1226 | '\\\\{tags % "{tag}"}' |
|
1226 | '\\\\{tags % "{tag}"}' | |
1227 |
|
1227 | |||
1228 | but inner strings follow the template rules (i.e. '\' is taken as an |
|
1228 | but inner strings follow the template rules (i.e. '\' is taken as an | |
1229 | escape character): |
|
1229 | escape character): | |
1230 |
|
1230 | |||
1231 | >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0) |
|
1231 | >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0) | |
1232 | '{"c:\\tmp"}' |
|
1232 | '{"c:\\tmp"}' | |
1233 | """ |
|
1233 | """ | |
1234 | expander = { |
|
1234 | expander = { | |
1235 | b'H': b'{node}', |
|
1235 | b'H': b'{node}', | |
1236 | b'R': b'{rev}', |
|
1236 | b'R': b'{rev}', | |
1237 | b'h': b'{node|short}', |
|
1237 | b'h': b'{node|short}', | |
1238 | b'm': br'{sub(r"[^\w]", "_", desc|firstline)}', |
|
1238 | b'm': br'{sub(r"[^\w]", "_", desc|firstline)}', | |
1239 | b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}', |
|
1239 | b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}', | |
1240 | b'%': b'%', |
|
1240 | b'%': b'%', | |
1241 | b'b': b'{reporoot|basename}', |
|
1241 | b'b': b'{reporoot|basename}', | |
1242 | } |
|
1242 | } | |
1243 | if total is not None: |
|
1243 | if total is not None: | |
1244 | expander[b'N'] = b'{total}' |
|
1244 | expander[b'N'] = b'{total}' | |
1245 | if seqno is not None: |
|
1245 | if seqno is not None: | |
1246 | expander[b'n'] = b'{seqno}' |
|
1246 | expander[b'n'] = b'{seqno}' | |
1247 | if total is not None and seqno is not None: |
|
1247 | if total is not None and seqno is not None: | |
1248 | expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}' |
|
1248 | expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}' | |
1249 | if pathname is not None: |
|
1249 | if pathname is not None: | |
1250 | expander[b's'] = b'{pathname|basename}' |
|
1250 | expander[b's'] = b'{pathname|basename}' | |
1251 | expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}' |
|
1251 | expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}' | |
1252 | expander[b'p'] = b'{pathname}' |
|
1252 | expander[b'p'] = b'{pathname}' | |
1253 |
|
1253 | |||
1254 | newname = [] |
|
1254 | newname = [] | |
1255 | for typ, start, end in templater.scantemplate(pat, raw=True): |
|
1255 | for typ, start, end in templater.scantemplate(pat, raw=True): | |
1256 | if typ != b'string': |
|
1256 | if typ != b'string': | |
1257 | newname.append(pat[start:end]) |
|
1257 | newname.append(pat[start:end]) | |
1258 | continue |
|
1258 | continue | |
1259 | i = start |
|
1259 | i = start | |
1260 | while i < end: |
|
1260 | while i < end: | |
1261 | n = pat.find(b'%', i, end) |
|
1261 | n = pat.find(b'%', i, end) | |
1262 | if n < 0: |
|
1262 | if n < 0: | |
1263 | newname.append(stringutil.escapestr(pat[i:end])) |
|
1263 | newname.append(stringutil.escapestr(pat[i:end])) | |
1264 | break |
|
1264 | break | |
1265 | newname.append(stringutil.escapestr(pat[i:n])) |
|
1265 | newname.append(stringutil.escapestr(pat[i:n])) | |
1266 | if n + 2 > end: |
|
1266 | if n + 2 > end: | |
1267 | raise error.Abort( |
|
1267 | raise error.Abort( | |
1268 | _(b"incomplete format spec in output filename") |
|
1268 | _(b"incomplete format spec in output filename") | |
1269 | ) |
|
1269 | ) | |
1270 | c = pat[n + 1 : n + 2] |
|
1270 | c = pat[n + 1 : n + 2] | |
1271 | i = n + 2 |
|
1271 | i = n + 2 | |
1272 | try: |
|
1272 | try: | |
1273 | newname.append(expander[c]) |
|
1273 | newname.append(expander[c]) | |
1274 | except KeyError: |
|
1274 | except KeyError: | |
1275 | raise error.Abort( |
|
1275 | raise error.Abort( | |
1276 | _(b"invalid format spec '%%%s' in output filename") % c |
|
1276 | _(b"invalid format spec '%%%s' in output filename") % c | |
1277 | ) |
|
1277 | ) | |
1278 | return b''.join(newname) |
|
1278 | return b''.join(newname) | |
1279 |
|
1279 | |||
1280 |
|
1280 | |||
1281 | def makefilename(ctx, pat, **props): |
|
1281 | def makefilename(ctx, pat, **props): | |
1282 | if not pat: |
|
1282 | if not pat: | |
1283 | return pat |
|
1283 | return pat | |
1284 | tmpl = _buildfntemplate(pat, **props) |
|
1284 | tmpl = _buildfntemplate(pat, **props) | |
1285 | # BUG: alias expansion shouldn't be made against template fragments |
|
1285 | # BUG: alias expansion shouldn't be made against template fragments | |
1286 | # rewritten from %-format strings, but we have no easy way to partially |
|
1286 | # rewritten from %-format strings, but we have no easy way to partially | |
1287 | # disable the expansion. |
|
1287 | # disable the expansion. | |
1288 | return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props)) |
|
1288 | return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props)) | |
1289 |
|
1289 | |||
1290 |
|
1290 | |||
1291 | def isstdiofilename(pat): |
|
1291 | def isstdiofilename(pat): | |
1292 | """True if the given pat looks like a filename denoting stdin/stdout""" |
|
1292 | """True if the given pat looks like a filename denoting stdin/stdout""" | |
1293 | return not pat or pat == b'-' |
|
1293 | return not pat or pat == b'-' | |
1294 |
|
1294 | |||
1295 |
|
1295 | |||
1296 | class _unclosablefile(object): |
|
1296 | class _unclosablefile(object): | |
1297 | def __init__(self, fp): |
|
1297 | def __init__(self, fp): | |
1298 | self._fp = fp |
|
1298 | self._fp = fp | |
1299 |
|
1299 | |||
1300 | def close(self): |
|
1300 | def close(self): | |
1301 | pass |
|
1301 | pass | |
1302 |
|
1302 | |||
1303 | def __iter__(self): |
|
1303 | def __iter__(self): | |
1304 | return iter(self._fp) |
|
1304 | return iter(self._fp) | |
1305 |
|
1305 | |||
1306 | def __getattr__(self, attr): |
|
1306 | def __getattr__(self, attr): | |
1307 | return getattr(self._fp, attr) |
|
1307 | return getattr(self._fp, attr) | |
1308 |
|
1308 | |||
1309 | def __enter__(self): |
|
1309 | def __enter__(self): | |
1310 | return self |
|
1310 | return self | |
1311 |
|
1311 | |||
1312 | def __exit__(self, exc_type, exc_value, exc_tb): |
|
1312 | def __exit__(self, exc_type, exc_value, exc_tb): | |
1313 | pass |
|
1313 | pass | |
1314 |
|
1314 | |||
1315 |
|
1315 | |||
1316 | def makefileobj(ctx, pat, mode=b'wb', **props): |
|
1316 | def makefileobj(ctx, pat, mode=b'wb', **props): | |
1317 | writable = mode not in (b'r', b'rb') |
|
1317 | writable = mode not in (b'r', b'rb') | |
1318 |
|
1318 | |||
1319 | if isstdiofilename(pat): |
|
1319 | if isstdiofilename(pat): | |
1320 | repo = ctx.repo() |
|
1320 | repo = ctx.repo() | |
1321 | if writable: |
|
1321 | if writable: | |
1322 | fp = repo.ui.fout |
|
1322 | fp = repo.ui.fout | |
1323 | else: |
|
1323 | else: | |
1324 | fp = repo.ui.fin |
|
1324 | fp = repo.ui.fin | |
1325 | return _unclosablefile(fp) |
|
1325 | return _unclosablefile(fp) | |
1326 | fn = makefilename(ctx, pat, **props) |
|
1326 | fn = makefilename(ctx, pat, **props) | |
1327 | return open(fn, mode) |
|
1327 | return open(fn, mode) | |
1328 |
|
1328 | |||
1329 |
|
1329 | |||
1330 | def openstorage(repo, cmd, file_, opts, returnrevlog=False): |
|
1330 | def openstorage(repo, cmd, file_, opts, returnrevlog=False): | |
1331 | """opens the changelog, manifest, a filelog or a given revlog""" |
|
1331 | """opens the changelog, manifest, a filelog or a given revlog""" | |
1332 | cl = opts[b'changelog'] |
|
1332 | cl = opts[b'changelog'] | |
1333 | mf = opts[b'manifest'] |
|
1333 | mf = opts[b'manifest'] | |
1334 | dir = opts[b'dir'] |
|
1334 | dir = opts[b'dir'] | |
1335 | msg = None |
|
1335 | msg = None | |
1336 | if cl and mf: |
|
1336 | if cl and mf: | |
1337 | msg = _(b'cannot specify --changelog and --manifest at the same time') |
|
1337 | msg = _(b'cannot specify --changelog and --manifest at the same time') | |
1338 | elif cl and dir: |
|
1338 | elif cl and dir: | |
1339 | msg = _(b'cannot specify --changelog and --dir at the same time') |
|
1339 | msg = _(b'cannot specify --changelog and --dir at the same time') | |
1340 | elif cl or mf or dir: |
|
1340 | elif cl or mf or dir: | |
1341 | if file_: |
|
1341 | if file_: | |
1342 | msg = _(b'cannot specify filename with --changelog or --manifest') |
|
1342 | msg = _(b'cannot specify filename with --changelog or --manifest') | |
1343 | elif not repo: |
|
1343 | elif not repo: | |
1344 | msg = _( |
|
1344 | msg = _( | |
1345 | b'cannot specify --changelog or --manifest or --dir ' |
|
1345 | b'cannot specify --changelog or --manifest or --dir ' | |
1346 | b'without a repository' |
|
1346 | b'without a repository' | |
1347 | ) |
|
1347 | ) | |
1348 | if msg: |
|
1348 | if msg: | |
1349 | raise error.Abort(msg) |
|
1349 | raise error.Abort(msg) | |
1350 |
|
1350 | |||
1351 | r = None |
|
1351 | r = None | |
1352 | if repo: |
|
1352 | if repo: | |
1353 | if cl: |
|
1353 | if cl: | |
1354 | r = repo.unfiltered().changelog |
|
1354 | r = repo.unfiltered().changelog | |
1355 | elif dir: |
|
1355 | elif dir: | |
1356 | if b'treemanifest' not in repo.requirements: |
|
1356 | if b'treemanifest' not in repo.requirements: | |
1357 | raise error.Abort( |
|
1357 | raise error.Abort( | |
1358 | _( |
|
1358 | _( | |
1359 | b"--dir can only be used on repos with " |
|
1359 | b"--dir can only be used on repos with " | |
1360 | b"treemanifest enabled" |
|
1360 | b"treemanifest enabled" | |
1361 | ) |
|
1361 | ) | |
1362 | ) |
|
1362 | ) | |
1363 | if not dir.endswith(b'/'): |
|
1363 | if not dir.endswith(b'/'): | |
1364 | dir = dir + b'/' |
|
1364 | dir = dir + b'/' | |
1365 | dirlog = repo.manifestlog.getstorage(dir) |
|
1365 | dirlog = repo.manifestlog.getstorage(dir) | |
1366 | if len(dirlog): |
|
1366 | if len(dirlog): | |
1367 | r = dirlog |
|
1367 | r = dirlog | |
1368 | elif mf: |
|
1368 | elif mf: | |
1369 | r = repo.manifestlog.getstorage(b'') |
|
1369 | r = repo.manifestlog.getstorage(b'') | |
1370 | elif file_: |
|
1370 | elif file_: | |
1371 | filelog = repo.file(file_) |
|
1371 | filelog = repo.file(file_) | |
1372 | if len(filelog): |
|
1372 | if len(filelog): | |
1373 | r = filelog |
|
1373 | r = filelog | |
1374 |
|
1374 | |||
1375 | # Not all storage may be revlogs. If requested, try to return an actual |
|
1375 | # Not all storage may be revlogs. If requested, try to return an actual | |
1376 | # revlog instance. |
|
1376 | # revlog instance. | |
1377 | if returnrevlog: |
|
1377 | if returnrevlog: | |
1378 | if isinstance(r, revlog.revlog): |
|
1378 | if isinstance(r, revlog.revlog): | |
1379 | pass |
|
1379 | pass | |
1380 | elif util.safehasattr(r, b'_revlog'): |
|
1380 | elif util.safehasattr(r, b'_revlog'): | |
1381 | r = r._revlog # pytype: disable=attribute-error |
|
1381 | r = r._revlog # pytype: disable=attribute-error | |
1382 | elif r is not None: |
|
1382 | elif r is not None: | |
1383 | raise error.Abort(_(b'%r does not appear to be a revlog') % r) |
|
1383 | raise error.Abort(_(b'%r does not appear to be a revlog') % r) | |
1384 |
|
1384 | |||
1385 | if not r: |
|
1385 | if not r: | |
1386 | if not returnrevlog: |
|
1386 | if not returnrevlog: | |
1387 | raise error.Abort(_(b'cannot give path to non-revlog')) |
|
1387 | raise error.Abort(_(b'cannot give path to non-revlog')) | |
1388 |
|
1388 | |||
1389 | if not file_: |
|
1389 | if not file_: | |
1390 | raise error.CommandError(cmd, _(b'invalid arguments')) |
|
1390 | raise error.CommandError(cmd, _(b'invalid arguments')) | |
1391 | if not os.path.isfile(file_): |
|
1391 | if not os.path.isfile(file_): | |
1392 | raise error.Abort(_(b"revlog '%s' not found") % file_) |
|
1392 | raise error.Abort(_(b"revlog '%s' not found") % file_) | |
1393 | r = revlog.revlog( |
|
1393 | r = revlog.revlog( | |
1394 | vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i" |
|
1394 | vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i" | |
1395 | ) |
|
1395 | ) | |
1396 | return r |
|
1396 | return r | |
1397 |
|
1397 | |||
1398 |
|
1398 | |||
1399 | def openrevlog(repo, cmd, file_, opts): |
|
1399 | def openrevlog(repo, cmd, file_, opts): | |
1400 | """Obtain a revlog backing storage of an item. |
|
1400 | """Obtain a revlog backing storage of an item. | |
1401 |
|
1401 | |||
1402 | This is similar to ``openstorage()`` except it always returns a revlog. |
|
1402 | This is similar to ``openstorage()`` except it always returns a revlog. | |
1403 |
|
1403 | |||
1404 | In most cases, a caller cares about the main storage object - not the |
|
1404 | In most cases, a caller cares about the main storage object - not the | |
1405 | revlog backing it. Therefore, this function should only be used by code |
|
1405 | revlog backing it. Therefore, this function should only be used by code | |
1406 | that needs to examine low-level revlog implementation details. e.g. debug |
|
1406 | that needs to examine low-level revlog implementation details. e.g. debug | |
1407 | commands. |
|
1407 | commands. | |
1408 | """ |
|
1408 | """ | |
1409 | return openstorage(repo, cmd, file_, opts, returnrevlog=True) |
|
1409 | return openstorage(repo, cmd, file_, opts, returnrevlog=True) | |
1410 |
|
1410 | |||
1411 |
|
1411 | |||
1412 | def copy(ui, repo, pats, opts, rename=False): |
|
1412 | def copy(ui, repo, pats, opts, rename=False): | |
1413 | check_incompatible_arguments(opts, b'forget', [b'dry_run']) |
|
1413 | check_incompatible_arguments(opts, b'forget', [b'dry_run']) | |
1414 |
|
1414 | |||
1415 | # called with the repo lock held |
|
1415 | # called with the repo lock held | |
1416 | # |
|
1416 | # | |
1417 | # hgsep => pathname that uses "/" to separate directories |
|
1417 | # hgsep => pathname that uses "/" to separate directories | |
1418 | # ossep => pathname that uses os.sep to separate directories |
|
1418 | # ossep => pathname that uses os.sep to separate directories | |
1419 | cwd = repo.getcwd() |
|
1419 | cwd = repo.getcwd() | |
1420 | targets = {} |
|
1420 | targets = {} | |
1421 | forget = opts.get(b"forget") |
|
1421 | forget = opts.get(b"forget") | |
1422 | after = opts.get(b"after") |
|
1422 | after = opts.get(b"after") | |
1423 | dryrun = opts.get(b"dry_run") |
|
1423 | dryrun = opts.get(b"dry_run") | |
1424 | rev = opts.get(b'at_rev') |
|
1424 | rev = opts.get(b'at_rev') | |
1425 | if rev: |
|
1425 | if rev: | |
1426 | if not forget and not after: |
|
1426 | if not forget and not after: | |
1427 | # TODO: Remove this restriction and make it also create the copy |
|
1427 | # TODO: Remove this restriction and make it also create the copy | |
1428 | # targets (and remove the rename source if rename==True). |
|
1428 | # targets (and remove the rename source if rename==True). | |
1429 | raise error.Abort(_(b'--at-rev requires --after')) |
|
1429 | raise error.Abort(_(b'--at-rev requires --after')) | |
1430 | ctx = scmutil.revsingle(repo, rev) |
|
1430 | ctx = scmutil.revsingle(repo, rev) | |
1431 | if len(ctx.parents()) > 1: |
|
1431 | if len(ctx.parents()) > 1: | |
1432 | raise error.Abort(_(b'cannot mark/unmark copy in merge commit')) |
|
1432 | raise error.Abort(_(b'cannot mark/unmark copy in merge commit')) | |
1433 | else: |
|
1433 | else: | |
1434 | ctx = repo[None] |
|
1434 | ctx = repo[None] | |
1435 |
|
1435 | |||
1436 | pctx = ctx.p1() |
|
1436 | pctx = ctx.p1() | |
1437 |
|
1437 | |||
1438 | uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True) |
|
1438 | uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True) | |
1439 |
|
1439 | |||
1440 | if forget: |
|
1440 | if forget: | |
1441 | if ctx.rev() is None: |
|
1441 | if ctx.rev() is None: | |
1442 | new_ctx = ctx |
|
1442 | new_ctx = ctx | |
1443 | else: |
|
1443 | else: | |
1444 | if len(ctx.parents()) > 1: |
|
1444 | if len(ctx.parents()) > 1: | |
1445 | raise error.Abort(_(b'cannot unmark copy in merge commit')) |
|
1445 | raise error.Abort(_(b'cannot unmark copy in merge commit')) | |
1446 | # avoid cycle context -> subrepo -> cmdutil |
|
1446 | # avoid cycle context -> subrepo -> cmdutil | |
1447 | from . import context |
|
1447 | from . import context | |
1448 |
|
1448 | |||
1449 | rewriteutil.precheck(repo, [ctx.rev()], b'uncopy') |
|
1449 | rewriteutil.precheck(repo, [ctx.rev()], b'uncopy') | |
1450 | new_ctx = context.overlayworkingctx(repo) |
|
1450 | new_ctx = context.overlayworkingctx(repo) | |
1451 | new_ctx.setbase(ctx.p1()) |
|
1451 | new_ctx.setbase(ctx.p1()) | |
1452 | mergemod.graft(repo, ctx, wctx=new_ctx) |
|
1452 | mergemod.graft(repo, ctx, wctx=new_ctx) | |
1453 |
|
1453 | |||
1454 | match = scmutil.match(ctx, pats, opts) |
|
1454 | match = scmutil.match(ctx, pats, opts) | |
1455 |
|
1455 | |||
1456 | current_copies = ctx.p1copies() |
|
1456 | current_copies = ctx.p1copies() | |
1457 | current_copies.update(ctx.p2copies()) |
|
1457 | current_copies.update(ctx.p2copies()) | |
1458 |
|
1458 | |||
1459 | uipathfn = scmutil.getuipathfn(repo) |
|
1459 | uipathfn = scmutil.getuipathfn(repo) | |
1460 | for f in ctx.walk(match): |
|
1460 | for f in ctx.walk(match): | |
1461 | if f in current_copies: |
|
1461 | if f in current_copies: | |
1462 | new_ctx[f].markcopied(None) |
|
1462 | new_ctx[f].markcopied(None) | |
1463 | elif match.exact(f): |
|
1463 | elif match.exact(f): | |
1464 | ui.warn( |
|
1464 | ui.warn( | |
1465 | _( |
|
1465 | _( | |
1466 | b'%s: not unmarking as copy - file is not marked as copied\n' |
|
1466 | b'%s: not unmarking as copy - file is not marked as copied\n' | |
1467 | ) |
|
1467 | ) | |
1468 | % uipathfn(f) |
|
1468 | % uipathfn(f) | |
1469 | ) |
|
1469 | ) | |
1470 |
|
1470 | |||
1471 | if ctx.rev() is not None: |
|
1471 | if ctx.rev() is not None: | |
1472 | with repo.lock(): |
|
1472 | with repo.lock(): | |
1473 | mem_ctx = new_ctx.tomemctx_for_amend(ctx) |
|
1473 | mem_ctx = new_ctx.tomemctx_for_amend(ctx) | |
1474 | new_node = mem_ctx.commit() |
|
1474 | new_node = mem_ctx.commit() | |
1475 |
|
1475 | |||
1476 | if repo.dirstate.p1() == ctx.node(): |
|
1476 | if repo.dirstate.p1() == ctx.node(): | |
1477 | with repo.dirstate.parentchange(): |
|
1477 | with repo.dirstate.parentchange(): | |
1478 | scmutil.movedirstate(repo, repo[new_node]) |
|
1478 | scmutil.movedirstate(repo, repo[new_node]) | |
1479 | replacements = {ctx.node(): [new_node]} |
|
1479 | replacements = {ctx.node(): [new_node]} | |
1480 | scmutil.cleanupnodes( |
|
1480 | scmutil.cleanupnodes( | |
1481 | repo, replacements, b'uncopy', fixphase=True |
|
1481 | repo, replacements, b'uncopy', fixphase=True | |
1482 | ) |
|
1482 | ) | |
1483 |
|
1483 | |||
1484 | return |
|
1484 | return | |
1485 |
|
1485 | |||
1486 | pats = scmutil.expandpats(pats) |
|
1486 | pats = scmutil.expandpats(pats) | |
1487 | if not pats: |
|
1487 | if not pats: | |
1488 | raise error.Abort(_(b'no source or destination specified')) |
|
1488 | raise error.Abort(_(b'no source or destination specified')) | |
1489 | if len(pats) == 1: |
|
1489 | if len(pats) == 1: | |
1490 | raise error.Abort(_(b'no destination specified')) |
|
1490 | raise error.Abort(_(b'no destination specified')) | |
1491 | dest = pats.pop() |
|
1491 | dest = pats.pop() | |
1492 |
|
1492 | |||
1493 | def walkpat(pat): |
|
1493 | def walkpat(pat): | |
1494 | srcs = [] |
|
1494 | srcs = [] | |
1495 | m = scmutil.match(ctx, [pat], opts, globbed=True) |
|
1495 | m = scmutil.match(ctx, [pat], opts, globbed=True) | |
1496 | for abs in ctx.walk(m): |
|
1496 | for abs in ctx.walk(m): | |
1497 | rel = uipathfn(abs) |
|
1497 | rel = uipathfn(abs) | |
1498 | exact = m.exact(abs) |
|
1498 | exact = m.exact(abs) | |
1499 | if abs not in ctx: |
|
1499 | if abs not in ctx: | |
1500 | if abs in pctx: |
|
1500 | if abs in pctx: | |
1501 | if not after: |
|
1501 | if not after: | |
1502 | if exact: |
|
1502 | if exact: | |
1503 | ui.warn( |
|
1503 | ui.warn( | |
1504 | _( |
|
1504 | _( | |
1505 | b'%s: not copying - file has been marked ' |
|
1505 | b'%s: not copying - file has been marked ' | |
1506 | b'for remove\n' |
|
1506 | b'for remove\n' | |
1507 | ) |
|
1507 | ) | |
1508 | % rel |
|
1508 | % rel | |
1509 | ) |
|
1509 | ) | |
1510 | continue |
|
1510 | continue | |
1511 | else: |
|
1511 | else: | |
1512 | if exact: |
|
1512 | if exact: | |
1513 | ui.warn( |
|
1513 | ui.warn( | |
1514 | _(b'%s: not copying - file is not managed\n') % rel |
|
1514 | _(b'%s: not copying - file is not managed\n') % rel | |
1515 | ) |
|
1515 | ) | |
1516 | continue |
|
1516 | continue | |
1517 |
|
1517 | |||
1518 | # abs: hgsep |
|
1518 | # abs: hgsep | |
1519 | # rel: ossep |
|
1519 | # rel: ossep | |
1520 | srcs.append((abs, rel, exact)) |
|
1520 | srcs.append((abs, rel, exact)) | |
1521 | return srcs |
|
1521 | return srcs | |
1522 |
|
1522 | |||
1523 | if ctx.rev() is not None: |
|
1523 | if ctx.rev() is not None: | |
1524 | rewriteutil.precheck(repo, [ctx.rev()], b'uncopy') |
|
1524 | rewriteutil.precheck(repo, [ctx.rev()], b'uncopy') | |
1525 | absdest = pathutil.canonpath(repo.root, cwd, dest) |
|
1525 | absdest = pathutil.canonpath(repo.root, cwd, dest) | |
1526 | if ctx.hasdir(absdest): |
|
1526 | if ctx.hasdir(absdest): | |
1527 | raise error.Abort( |
|
1527 | raise error.Abort( | |
1528 | _(b'%s: --at-rev does not support a directory as destination') |
|
1528 | _(b'%s: --at-rev does not support a directory as destination') | |
1529 | % uipathfn(absdest) |
|
1529 | % uipathfn(absdest) | |
1530 | ) |
|
1530 | ) | |
1531 | if absdest not in ctx: |
|
1531 | if absdest not in ctx: | |
1532 | raise error.Abort( |
|
1532 | raise error.Abort( | |
1533 | _(b'%s: copy destination does not exist in %s') |
|
1533 | _(b'%s: copy destination does not exist in %s') | |
1534 | % (uipathfn(absdest), ctx) |
|
1534 | % (uipathfn(absdest), ctx) | |
1535 | ) |
|
1535 | ) | |
1536 |
|
1536 | |||
1537 | # avoid cycle context -> subrepo -> cmdutil |
|
1537 | # avoid cycle context -> subrepo -> cmdutil | |
1538 | from . import context |
|
1538 | from . import context | |
1539 |
|
1539 | |||
1540 | copylist = [] |
|
1540 | copylist = [] | |
1541 | for pat in pats: |
|
1541 | for pat in pats: | |
1542 | srcs = walkpat(pat) |
|
1542 | srcs = walkpat(pat) | |
1543 | if not srcs: |
|
1543 | if not srcs: | |
1544 | continue |
|
1544 | continue | |
1545 | for abs, rel, exact in srcs: |
|
1545 | for abs, rel, exact in srcs: | |
1546 | copylist.append(abs) |
|
1546 | copylist.append(abs) | |
1547 |
|
1547 | |||
1548 | # TODO: Add support for `hg cp --at-rev . foo bar dir` and |
|
1548 | # TODO: Add support for `hg cp --at-rev . foo bar dir` and | |
1549 | # `hg cp --at-rev . dir1 dir2`, preferably unifying the code with the |
|
1549 | # `hg cp --at-rev . dir1 dir2`, preferably unifying the code with the | |
1550 | # existing functions below. |
|
1550 | # existing functions below. | |
1551 | if len(copylist) != 1: |
|
1551 | if len(copylist) != 1: | |
1552 | raise error.Abort(_(b'--at-rev requires a single source')) |
|
1552 | raise error.Abort(_(b'--at-rev requires a single source')) | |
1553 |
|
1553 | |||
1554 | new_ctx = context.overlayworkingctx(repo) |
|
1554 | new_ctx = context.overlayworkingctx(repo) | |
1555 | new_ctx.setbase(ctx.p1()) |
|
1555 | new_ctx.setbase(ctx.p1()) | |
1556 | mergemod.graft(repo, ctx, wctx=new_ctx) |
|
1556 | mergemod.graft(repo, ctx, wctx=new_ctx) | |
1557 |
|
1557 | |||
1558 | new_ctx.markcopied(absdest, copylist[0]) |
|
1558 | new_ctx.markcopied(absdest, copylist[0]) | |
1559 |
|
1559 | |||
1560 | with repo.lock(): |
|
1560 | with repo.lock(): | |
1561 | mem_ctx = new_ctx.tomemctx_for_amend(ctx) |
|
1561 | mem_ctx = new_ctx.tomemctx_for_amend(ctx) | |
1562 | new_node = mem_ctx.commit() |
|
1562 | new_node = mem_ctx.commit() | |
1563 |
|
1563 | |||
1564 | if repo.dirstate.p1() == ctx.node(): |
|
1564 | if repo.dirstate.p1() == ctx.node(): | |
1565 | with repo.dirstate.parentchange(): |
|
1565 | with repo.dirstate.parentchange(): | |
1566 | scmutil.movedirstate(repo, repo[new_node]) |
|
1566 | scmutil.movedirstate(repo, repo[new_node]) | |
1567 | replacements = {ctx.node(): [new_node]} |
|
1567 | replacements = {ctx.node(): [new_node]} | |
1568 | scmutil.cleanupnodes(repo, replacements, b'copy', fixphase=True) |
|
1568 | scmutil.cleanupnodes(repo, replacements, b'copy', fixphase=True) | |
1569 |
|
1569 | |||
1570 | return |
|
1570 | return | |
1571 |
|
1571 | |||
1572 | # abssrc: hgsep |
|
1572 | # abssrc: hgsep | |
1573 | # relsrc: ossep |
|
1573 | # relsrc: ossep | |
1574 | # otarget: ossep |
|
1574 | # otarget: ossep | |
1575 | def copyfile(abssrc, relsrc, otarget, exact): |
|
1575 | def copyfile(abssrc, relsrc, otarget, exact): | |
1576 | abstarget = pathutil.canonpath(repo.root, cwd, otarget) |
|
1576 | abstarget = pathutil.canonpath(repo.root, cwd, otarget) | |
1577 | if b'/' in abstarget: |
|
1577 | if b'/' in abstarget: | |
1578 | # We cannot normalize abstarget itself, this would prevent |
|
1578 | # We cannot normalize abstarget itself, this would prevent | |
1579 | # case only renames, like a => A. |
|
1579 | # case only renames, like a => A. | |
1580 | abspath, absname = abstarget.rsplit(b'/', 1) |
|
1580 | abspath, absname = abstarget.rsplit(b'/', 1) | |
1581 | abstarget = repo.dirstate.normalize(abspath) + b'/' + absname |
|
1581 | abstarget = repo.dirstate.normalize(abspath) + b'/' + absname | |
1582 | reltarget = repo.pathto(abstarget, cwd) |
|
1582 | reltarget = repo.pathto(abstarget, cwd) | |
1583 | target = repo.wjoin(abstarget) |
|
1583 | target = repo.wjoin(abstarget) | |
1584 | src = repo.wjoin(abssrc) |
|
1584 | src = repo.wjoin(abssrc) | |
1585 | state = repo.dirstate[abstarget] |
|
1585 | state = repo.dirstate[abstarget] | |
1586 |
|
1586 | |||
1587 | scmutil.checkportable(ui, abstarget) |
|
1587 | scmutil.checkportable(ui, abstarget) | |
1588 |
|
1588 | |||
1589 | # check for collisions |
|
1589 | # check for collisions | |
1590 | prevsrc = targets.get(abstarget) |
|
1590 | prevsrc = targets.get(abstarget) | |
1591 | if prevsrc is not None: |
|
1591 | if prevsrc is not None: | |
1592 | ui.warn( |
|
1592 | ui.warn( | |
1593 | _(b'%s: not overwriting - %s collides with %s\n') |
|
1593 | _(b'%s: not overwriting - %s collides with %s\n') | |
1594 | % ( |
|
1594 | % ( | |
1595 | reltarget, |
|
1595 | reltarget, | |
1596 | repo.pathto(abssrc, cwd), |
|
1596 | repo.pathto(abssrc, cwd), | |
1597 | repo.pathto(prevsrc, cwd), |
|
1597 | repo.pathto(prevsrc, cwd), | |
1598 | ) |
|
1598 | ) | |
1599 | ) |
|
1599 | ) | |
1600 | return True # report a failure |
|
1600 | return True # report a failure | |
1601 |
|
1601 | |||
1602 | # check for overwrites |
|
1602 | # check for overwrites | |
1603 | exists = os.path.lexists(target) |
|
1603 | exists = os.path.lexists(target) | |
1604 | samefile = False |
|
1604 | samefile = False | |
1605 | if exists and abssrc != abstarget: |
|
1605 | if exists and abssrc != abstarget: | |
1606 | if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize( |
|
1606 | if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize( | |
1607 | abstarget |
|
1607 | abstarget | |
1608 | ): |
|
1608 | ): | |
1609 | if not rename: |
|
1609 | if not rename: | |
1610 | ui.warn(_(b"%s: can't copy - same file\n") % reltarget) |
|
1610 | ui.warn(_(b"%s: can't copy - same file\n") % reltarget) | |
1611 | return True # report a failure |
|
1611 | return True # report a failure | |
1612 | exists = False |
|
1612 | exists = False | |
1613 | samefile = True |
|
1613 | samefile = True | |
1614 |
|
1614 | |||
1615 | if not after and exists or after and state in b'mn': |
|
1615 | if not after and exists or after and state in b'mn': | |
1616 | if not opts[b'force']: |
|
1616 | if not opts[b'force']: | |
1617 | if state in b'mn': |
|
1617 | if state in b'mn': | |
1618 | msg = _(b'%s: not overwriting - file already committed\n') |
|
1618 | msg = _(b'%s: not overwriting - file already committed\n') | |
1619 | if after: |
|
1619 | if after: | |
1620 | flags = b'--after --force' |
|
1620 | flags = b'--after --force' | |
1621 | else: |
|
1621 | else: | |
1622 | flags = b'--force' |
|
1622 | flags = b'--force' | |
1623 | if rename: |
|
1623 | if rename: | |
1624 | hint = ( |
|
1624 | hint = ( | |
1625 | _( |
|
1625 | _( | |
1626 | b"('hg rename %s' to replace the file by " |
|
1626 | b"('hg rename %s' to replace the file by " | |
1627 | b'recording a rename)\n' |
|
1627 | b'recording a rename)\n' | |
1628 | ) |
|
1628 | ) | |
1629 | % flags |
|
1629 | % flags | |
1630 | ) |
|
1630 | ) | |
1631 | else: |
|
1631 | else: | |
1632 | hint = ( |
|
1632 | hint = ( | |
1633 | _( |
|
1633 | _( | |
1634 | b"('hg copy %s' to replace the file by " |
|
1634 | b"('hg copy %s' to replace the file by " | |
1635 | b'recording a copy)\n' |
|
1635 | b'recording a copy)\n' | |
1636 | ) |
|
1636 | ) | |
1637 | % flags |
|
1637 | % flags | |
1638 | ) |
|
1638 | ) | |
1639 | else: |
|
1639 | else: | |
1640 | msg = _(b'%s: not overwriting - file exists\n') |
|
1640 | msg = _(b'%s: not overwriting - file exists\n') | |
1641 | if rename: |
|
1641 | if rename: | |
1642 | hint = _( |
|
1642 | hint = _( | |
1643 | b"('hg rename --after' to record the rename)\n" |
|
1643 | b"('hg rename --after' to record the rename)\n" | |
1644 | ) |
|
1644 | ) | |
1645 | else: |
|
1645 | else: | |
1646 | hint = _(b"('hg copy --after' to record the copy)\n") |
|
1646 | hint = _(b"('hg copy --after' to record the copy)\n") | |
1647 | ui.warn(msg % reltarget) |
|
1647 | ui.warn(msg % reltarget) | |
1648 | ui.warn(hint) |
|
1648 | ui.warn(hint) | |
1649 | return True # report a failure |
|
1649 | return True # report a failure | |
1650 |
|
1650 | |||
1651 | if after: |
|
1651 | if after: | |
1652 | if not exists: |
|
1652 | if not exists: | |
1653 | if rename: |
|
1653 | if rename: | |
1654 | ui.warn( |
|
1654 | ui.warn( | |
1655 | _(b'%s: not recording move - %s does not exist\n') |
|
1655 | _(b'%s: not recording move - %s does not exist\n') | |
1656 | % (relsrc, reltarget) |
|
1656 | % (relsrc, reltarget) | |
1657 | ) |
|
1657 | ) | |
1658 | else: |
|
1658 | else: | |
1659 | ui.warn( |
|
1659 | ui.warn( | |
1660 | _(b'%s: not recording copy - %s does not exist\n') |
|
1660 | _(b'%s: not recording copy - %s does not exist\n') | |
1661 | % (relsrc, reltarget) |
|
1661 | % (relsrc, reltarget) | |
1662 | ) |
|
1662 | ) | |
1663 | return True # report a failure |
|
1663 | return True # report a failure | |
1664 | elif not dryrun: |
|
1664 | elif not dryrun: | |
1665 | try: |
|
1665 | try: | |
1666 | if exists: |
|
1666 | if exists: | |
1667 | os.unlink(target) |
|
1667 | os.unlink(target) | |
1668 | targetdir = os.path.dirname(target) or b'.' |
|
1668 | targetdir = os.path.dirname(target) or b'.' | |
1669 | if not os.path.isdir(targetdir): |
|
1669 | if not os.path.isdir(targetdir): | |
1670 | os.makedirs(targetdir) |
|
1670 | os.makedirs(targetdir) | |
1671 | if samefile: |
|
1671 | if samefile: | |
1672 | tmp = target + b"~hgrename" |
|
1672 | tmp = target + b"~hgrename" | |
1673 | os.rename(src, tmp) |
|
1673 | os.rename(src, tmp) | |
1674 | os.rename(tmp, target) |
|
1674 | os.rename(tmp, target) | |
1675 | else: |
|
1675 | else: | |
1676 | # Preserve stat info on renames, not on copies; this matches |
|
1676 | # Preserve stat info on renames, not on copies; this matches | |
1677 | # Linux CLI behavior. |
|
1677 | # Linux CLI behavior. | |
1678 | util.copyfile(src, target, copystat=rename) |
|
1678 | util.copyfile(src, target, copystat=rename) | |
1679 | srcexists = True |
|
1679 | srcexists = True | |
1680 | except IOError as inst: |
|
1680 | except IOError as inst: | |
1681 | if inst.errno == errno.ENOENT: |
|
1681 | if inst.errno == errno.ENOENT: | |
1682 | ui.warn(_(b'%s: deleted in working directory\n') % relsrc) |
|
1682 | ui.warn(_(b'%s: deleted in working directory\n') % relsrc) | |
1683 | srcexists = False |
|
1683 | srcexists = False | |
1684 | else: |
|
1684 | else: | |
1685 | ui.warn( |
|
1685 | ui.warn( | |
1686 | _(b'%s: cannot copy - %s\n') |
|
1686 | _(b'%s: cannot copy - %s\n') | |
1687 | % (relsrc, encoding.strtolocal(inst.strerror)) |
|
1687 | % (relsrc, encoding.strtolocal(inst.strerror)) | |
1688 | ) |
|
1688 | ) | |
1689 | return True # report a failure |
|
1689 | return True # report a failure | |
1690 |
|
1690 | |||
1691 | if ui.verbose or not exact: |
|
1691 | if ui.verbose or not exact: | |
1692 | if rename: |
|
1692 | if rename: | |
1693 | ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget)) |
|
1693 | ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget)) | |
1694 | else: |
|
1694 | else: | |
1695 | ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget)) |
|
1695 | ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget)) | |
1696 |
|
1696 | |||
1697 | targets[abstarget] = abssrc |
|
1697 | targets[abstarget] = abssrc | |
1698 |
|
1698 | |||
1699 | # fix up dirstate |
|
1699 | # fix up dirstate | |
1700 | scmutil.dirstatecopy( |
|
1700 | scmutil.dirstatecopy( | |
1701 | ui, repo, ctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd |
|
1701 | ui, repo, ctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd | |
1702 | ) |
|
1702 | ) | |
1703 | if rename and not dryrun: |
|
1703 | if rename and not dryrun: | |
1704 | if not after and srcexists and not samefile: |
|
1704 | if not after and srcexists and not samefile: | |
1705 | rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs') |
|
1705 | rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs') | |
1706 | repo.wvfs.unlinkpath(abssrc, rmdir=rmdir) |
|
1706 | repo.wvfs.unlinkpath(abssrc, rmdir=rmdir) | |
1707 | ctx.forget([abssrc]) |
|
1707 | ctx.forget([abssrc]) | |
1708 |
|
1708 | |||
1709 | # pat: ossep |
|
1709 | # pat: ossep | |
1710 | # dest ossep |
|
1710 | # dest ossep | |
1711 | # srcs: list of (hgsep, hgsep, ossep, bool) |
|
1711 | # srcs: list of (hgsep, hgsep, ossep, bool) | |
1712 | # return: function that takes hgsep and returns ossep |
|
1712 | # return: function that takes hgsep and returns ossep | |
1713 | def targetpathfn(pat, dest, srcs): |
|
1713 | def targetpathfn(pat, dest, srcs): | |
1714 | if os.path.isdir(pat): |
|
1714 | if os.path.isdir(pat): | |
1715 | abspfx = pathutil.canonpath(repo.root, cwd, pat) |
|
1715 | abspfx = pathutil.canonpath(repo.root, cwd, pat) | |
1716 | abspfx = util.localpath(abspfx) |
|
1716 | abspfx = util.localpath(abspfx) | |
1717 | if destdirexists: |
|
1717 | if destdirexists: | |
1718 | striplen = len(os.path.split(abspfx)[0]) |
|
1718 | striplen = len(os.path.split(abspfx)[0]) | |
1719 | else: |
|
1719 | else: | |
1720 | striplen = len(abspfx) |
|
1720 | striplen = len(abspfx) | |
1721 | if striplen: |
|
1721 | if striplen: | |
1722 | striplen += len(pycompat.ossep) |
|
1722 | striplen += len(pycompat.ossep) | |
1723 | res = lambda p: os.path.join(dest, util.localpath(p)[striplen:]) |
|
1723 | res = lambda p: os.path.join(dest, util.localpath(p)[striplen:]) | |
1724 | elif destdirexists: |
|
1724 | elif destdirexists: | |
1725 | res = lambda p: os.path.join( |
|
1725 | res = lambda p: os.path.join( | |
1726 | dest, os.path.basename(util.localpath(p)) |
|
1726 | dest, os.path.basename(util.localpath(p)) | |
1727 | ) |
|
1727 | ) | |
1728 | else: |
|
1728 | else: | |
1729 | res = lambda p: dest |
|
1729 | res = lambda p: dest | |
1730 | return res |
|
1730 | return res | |
1731 |
|
1731 | |||
1732 | # pat: ossep |
|
1732 | # pat: ossep | |
1733 | # dest ossep |
|
1733 | # dest ossep | |
1734 | # srcs: list of (hgsep, hgsep, ossep, bool) |
|
1734 | # srcs: list of (hgsep, hgsep, ossep, bool) | |
1735 | # return: function that takes hgsep and returns ossep |
|
1735 | # return: function that takes hgsep and returns ossep | |
1736 | def targetpathafterfn(pat, dest, srcs): |
|
1736 | def targetpathafterfn(pat, dest, srcs): | |
1737 | if matchmod.patkind(pat): |
|
1737 | if matchmod.patkind(pat): | |
1738 | # a mercurial pattern |
|
1738 | # a mercurial pattern | |
1739 | res = lambda p: os.path.join( |
|
1739 | res = lambda p: os.path.join( | |
1740 | dest, os.path.basename(util.localpath(p)) |
|
1740 | dest, os.path.basename(util.localpath(p)) | |
1741 | ) |
|
1741 | ) | |
1742 | else: |
|
1742 | else: | |
1743 | abspfx = pathutil.canonpath(repo.root, cwd, pat) |
|
1743 | abspfx = pathutil.canonpath(repo.root, cwd, pat) | |
1744 | if len(abspfx) < len(srcs[0][0]): |
|
1744 | if len(abspfx) < len(srcs[0][0]): | |
1745 | # A directory. Either the target path contains the last |
|
1745 | # A directory. Either the target path contains the last | |
1746 | # component of the source path or it does not. |
|
1746 | # component of the source path or it does not. | |
1747 | def evalpath(striplen): |
|
1747 | def evalpath(striplen): | |
1748 | score = 0 |
|
1748 | score = 0 | |
1749 | for s in srcs: |
|
1749 | for s in srcs: | |
1750 | t = os.path.join(dest, util.localpath(s[0])[striplen:]) |
|
1750 | t = os.path.join(dest, util.localpath(s[0])[striplen:]) | |
1751 | if os.path.lexists(t): |
|
1751 | if os.path.lexists(t): | |
1752 | score += 1 |
|
1752 | score += 1 | |
1753 | return score |
|
1753 | return score | |
1754 |
|
1754 | |||
1755 | abspfx = util.localpath(abspfx) |
|
1755 | abspfx = util.localpath(abspfx) | |
1756 | striplen = len(abspfx) |
|
1756 | striplen = len(abspfx) | |
1757 | if striplen: |
|
1757 | if striplen: | |
1758 | striplen += len(pycompat.ossep) |
|
1758 | striplen += len(pycompat.ossep) | |
1759 | if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])): |
|
1759 | if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])): | |
1760 | score = evalpath(striplen) |
|
1760 | score = evalpath(striplen) | |
1761 | striplen1 = len(os.path.split(abspfx)[0]) |
|
1761 | striplen1 = len(os.path.split(abspfx)[0]) | |
1762 | if striplen1: |
|
1762 | if striplen1: | |
1763 | striplen1 += len(pycompat.ossep) |
|
1763 | striplen1 += len(pycompat.ossep) | |
1764 | if evalpath(striplen1) > score: |
|
1764 | if evalpath(striplen1) > score: | |
1765 | striplen = striplen1 |
|
1765 | striplen = striplen1 | |
1766 | res = lambda p: os.path.join(dest, util.localpath(p)[striplen:]) |
|
1766 | res = lambda p: os.path.join(dest, util.localpath(p)[striplen:]) | |
1767 | else: |
|
1767 | else: | |
1768 | # a file |
|
1768 | # a file | |
1769 | if destdirexists: |
|
1769 | if destdirexists: | |
1770 | res = lambda p: os.path.join( |
|
1770 | res = lambda p: os.path.join( | |
1771 | dest, os.path.basename(util.localpath(p)) |
|
1771 | dest, os.path.basename(util.localpath(p)) | |
1772 | ) |
|
1772 | ) | |
1773 | else: |
|
1773 | else: | |
1774 | res = lambda p: dest |
|
1774 | res = lambda p: dest | |
1775 | return res |
|
1775 | return res | |
1776 |
|
1776 | |||
1777 | destdirexists = os.path.isdir(dest) and not os.path.islink(dest) |
|
1777 | destdirexists = os.path.isdir(dest) and not os.path.islink(dest) | |
1778 | if not destdirexists: |
|
1778 | if not destdirexists: | |
1779 | if len(pats) > 1 or matchmod.patkind(pats[0]): |
|
1779 | if len(pats) > 1 or matchmod.patkind(pats[0]): | |
1780 | raise error.Abort( |
|
1780 | raise error.Abort( | |
1781 | _( |
|
1781 | _( | |
1782 | b'with multiple sources, destination must be an ' |
|
1782 | b'with multiple sources, destination must be an ' | |
1783 | b'existing directory' |
|
1783 | b'existing directory' | |
1784 | ) |
|
1784 | ) | |
1785 | ) |
|
1785 | ) | |
1786 | if util.endswithsep(dest): |
|
1786 | if util.endswithsep(dest): | |
1787 | raise error.Abort(_(b'destination %s is not a directory') % dest) |
|
1787 | raise error.Abort(_(b'destination %s is not a directory') % dest) | |
1788 |
|
1788 | |||
1789 | tfn = targetpathfn |
|
1789 | tfn = targetpathfn | |
1790 | if after: |
|
1790 | if after: | |
1791 | tfn = targetpathafterfn |
|
1791 | tfn = targetpathafterfn | |
1792 | copylist = [] |
|
1792 | copylist = [] | |
1793 | for pat in pats: |
|
1793 | for pat in pats: | |
1794 | srcs = walkpat(pat) |
|
1794 | srcs = walkpat(pat) | |
1795 | if not srcs: |
|
1795 | if not srcs: | |
1796 | continue |
|
1796 | continue | |
1797 | copylist.append((tfn(pat, dest, srcs), srcs)) |
|
1797 | copylist.append((tfn(pat, dest, srcs), srcs)) | |
1798 | if not copylist: |
|
1798 | if not copylist: | |
1799 | raise error.Abort(_(b'no files to copy')) |
|
1799 | raise error.Abort(_(b'no files to copy')) | |
1800 |
|
1800 | |||
1801 | errors = 0 |
|
1801 | errors = 0 | |
1802 | for targetpath, srcs in copylist: |
|
1802 | for targetpath, srcs in copylist: | |
1803 | for abssrc, relsrc, exact in srcs: |
|
1803 | for abssrc, relsrc, exact in srcs: | |
1804 | if copyfile(abssrc, relsrc, targetpath(abssrc), exact): |
|
1804 | if copyfile(abssrc, relsrc, targetpath(abssrc), exact): | |
1805 | errors += 1 |
|
1805 | errors += 1 | |
1806 |
|
1806 | |||
1807 | return errors != 0 |
|
1807 | return errors != 0 | |
1808 |
|
1808 | |||
1809 |
|
1809 | |||
1810 | ## facility to let extension process additional data into an import patch |
|
1810 | ## facility to let extension process additional data into an import patch | |
1811 | # list of identifier to be executed in order |
|
1811 | # list of identifier to be executed in order | |
1812 | extrapreimport = [] # run before commit |
|
1812 | extrapreimport = [] # run before commit | |
1813 | extrapostimport = [] # run after commit |
|
1813 | extrapostimport = [] # run after commit | |
1814 | # mapping from identifier to actual import function |
|
1814 | # mapping from identifier to actual import function | |
1815 | # |
|
1815 | # | |
1816 | # 'preimport' are run before the commit is made and are provided the following |
|
1816 | # 'preimport' are run before the commit is made and are provided the following | |
1817 | # arguments: |
|
1817 | # arguments: | |
1818 | # - repo: the localrepository instance, |
|
1818 | # - repo: the localrepository instance, | |
1819 | # - patchdata: data extracted from patch header (cf m.patch.patchheadermap), |
|
1819 | # - patchdata: data extracted from patch header (cf m.patch.patchheadermap), | |
1820 | # - extra: the future extra dictionary of the changeset, please mutate it, |
|
1820 | # - extra: the future extra dictionary of the changeset, please mutate it, | |
1821 | # - opts: the import options. |
|
1821 | # - opts: the import options. | |
1822 | # XXX ideally, we would just pass an ctx ready to be computed, that would allow |
|
1822 | # XXX ideally, we would just pass an ctx ready to be computed, that would allow | |
1823 | # mutation of in memory commit and more. Feel free to rework the code to get |
|
1823 | # mutation of in memory commit and more. Feel free to rework the code to get | |
1824 | # there. |
|
1824 | # there. | |
1825 | extrapreimportmap = {} |
|
1825 | extrapreimportmap = {} | |
1826 | # 'postimport' are run after the commit is made and are provided the following |
|
1826 | # 'postimport' are run after the commit is made and are provided the following | |
1827 | # argument: |
|
1827 | # argument: | |
1828 | # - ctx: the changectx created by import. |
|
1828 | # - ctx: the changectx created by import. | |
1829 | extrapostimportmap = {} |
|
1829 | extrapostimportmap = {} | |
1830 |
|
1830 | |||
1831 |
|
1831 | |||
1832 | def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc): |
|
1832 | def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc): | |
1833 | """Utility function used by commands.import to import a single patch |
|
1833 | """Utility function used by commands.import to import a single patch | |
1834 |
|
1834 | |||
1835 | This function is explicitly defined here to help the evolve extension to |
|
1835 | This function is explicitly defined here to help the evolve extension to | |
1836 | wrap this part of the import logic. |
|
1836 | wrap this part of the import logic. | |
1837 |
|
1837 | |||
1838 | The API is currently a bit ugly because it a simple code translation from |
|
1838 | The API is currently a bit ugly because it a simple code translation from | |
1839 | the import command. Feel free to make it better. |
|
1839 | the import command. Feel free to make it better. | |
1840 |
|
1840 | |||
1841 | :patchdata: a dictionary containing parsed patch data (such as from |
|
1841 | :patchdata: a dictionary containing parsed patch data (such as from | |
1842 | ``patch.extract()``) |
|
1842 | ``patch.extract()``) | |
1843 | :parents: nodes that will be parent of the created commit |
|
1843 | :parents: nodes that will be parent of the created commit | |
1844 | :opts: the full dict of option passed to the import command |
|
1844 | :opts: the full dict of option passed to the import command | |
1845 | :msgs: list to save commit message to. |
|
1845 | :msgs: list to save commit message to. | |
1846 | (used in case we need to save it when failing) |
|
1846 | (used in case we need to save it when failing) | |
1847 | :updatefunc: a function that update a repo to a given node |
|
1847 | :updatefunc: a function that update a repo to a given node | |
1848 | updatefunc(<repo>, <node>) |
|
1848 | updatefunc(<repo>, <node>) | |
1849 | """ |
|
1849 | """ | |
1850 | # avoid cycle context -> subrepo -> cmdutil |
|
1850 | # avoid cycle context -> subrepo -> cmdutil | |
1851 | from . import context |
|
1851 | from . import context | |
1852 |
|
1852 | |||
1853 | tmpname = patchdata.get(b'filename') |
|
1853 | tmpname = patchdata.get(b'filename') | |
1854 | message = patchdata.get(b'message') |
|
1854 | message = patchdata.get(b'message') | |
1855 | user = opts.get(b'user') or patchdata.get(b'user') |
|
1855 | user = opts.get(b'user') or patchdata.get(b'user') | |
1856 | date = opts.get(b'date') or patchdata.get(b'date') |
|
1856 | date = opts.get(b'date') or patchdata.get(b'date') | |
1857 | branch = patchdata.get(b'branch') |
|
1857 | branch = patchdata.get(b'branch') | |
1858 | nodeid = patchdata.get(b'nodeid') |
|
1858 | nodeid = patchdata.get(b'nodeid') | |
1859 | p1 = patchdata.get(b'p1') |
|
1859 | p1 = patchdata.get(b'p1') | |
1860 | p2 = patchdata.get(b'p2') |
|
1860 | p2 = patchdata.get(b'p2') | |
1861 |
|
1861 | |||
1862 | nocommit = opts.get(b'no_commit') |
|
1862 | nocommit = opts.get(b'no_commit') | |
1863 | importbranch = opts.get(b'import_branch') |
|
1863 | importbranch = opts.get(b'import_branch') | |
1864 | update = not opts.get(b'bypass') |
|
1864 | update = not opts.get(b'bypass') | |
1865 | strip = opts[b"strip"] |
|
1865 | strip = opts[b"strip"] | |
1866 | prefix = opts[b"prefix"] |
|
1866 | prefix = opts[b"prefix"] | |
1867 | sim = float(opts.get(b'similarity') or 0) |
|
1867 | sim = float(opts.get(b'similarity') or 0) | |
1868 |
|
1868 | |||
1869 | if not tmpname: |
|
1869 | if not tmpname: | |
1870 | return None, None, False |
|
1870 | return None, None, False | |
1871 |
|
1871 | |||
1872 | rejects = False |
|
1872 | rejects = False | |
1873 |
|
1873 | |||
1874 | cmdline_message = logmessage(ui, opts) |
|
1874 | cmdline_message = logmessage(ui, opts) | |
1875 | if cmdline_message: |
|
1875 | if cmdline_message: | |
1876 | # pickup the cmdline msg |
|
1876 | # pickup the cmdline msg | |
1877 | message = cmdline_message |
|
1877 | message = cmdline_message | |
1878 | elif message: |
|
1878 | elif message: | |
1879 | # pickup the patch msg |
|
1879 | # pickup the patch msg | |
1880 | message = message.strip() |
|
1880 | message = message.strip() | |
1881 | else: |
|
1881 | else: | |
1882 | # launch the editor |
|
1882 | # launch the editor | |
1883 | message = None |
|
1883 | message = None | |
1884 | ui.debug(b'message:\n%s\n' % (message or b'')) |
|
1884 | ui.debug(b'message:\n%s\n' % (message or b'')) | |
1885 |
|
1885 | |||
1886 | if len(parents) == 1: |
|
1886 | if len(parents) == 1: | |
1887 | parents.append(repo[nullid]) |
|
1887 | parents.append(repo[nullid]) | |
1888 | if opts.get(b'exact'): |
|
1888 | if opts.get(b'exact'): | |
1889 | if not nodeid or not p1: |
|
1889 | if not nodeid or not p1: | |
1890 | raise error.Abort(_(b'not a Mercurial patch')) |
|
1890 | raise error.Abort(_(b'not a Mercurial patch')) | |
1891 | p1 = repo[p1] |
|
1891 | p1 = repo[p1] | |
1892 | p2 = repo[p2 or nullid] |
|
1892 | p2 = repo[p2 or nullid] | |
1893 | elif p2: |
|
1893 | elif p2: | |
1894 | try: |
|
1894 | try: | |
1895 | p1 = repo[p1] |
|
1895 | p1 = repo[p1] | |
1896 | p2 = repo[p2] |
|
1896 | p2 = repo[p2] | |
1897 | # Without any options, consider p2 only if the |
|
1897 | # Without any options, consider p2 only if the | |
1898 | # patch is being applied on top of the recorded |
|
1898 | # patch is being applied on top of the recorded | |
1899 | # first parent. |
|
1899 | # first parent. | |
1900 | if p1 != parents[0]: |
|
1900 | if p1 != parents[0]: | |
1901 | p1 = parents[0] |
|
1901 | p1 = parents[0] | |
1902 | p2 = repo[nullid] |
|
1902 | p2 = repo[nullid] | |
1903 | except error.RepoError: |
|
1903 | except error.RepoError: | |
1904 | p1, p2 = parents |
|
1904 | p1, p2 = parents | |
1905 | if p2.node() == nullid: |
|
1905 | if p2.node() == nullid: | |
1906 | ui.warn( |
|
1906 | ui.warn( | |
1907 | _( |
|
1907 | _( | |
1908 | b"warning: import the patch as a normal revision\n" |
|
1908 | b"warning: import the patch as a normal revision\n" | |
1909 | b"(use --exact to import the patch as a merge)\n" |
|
1909 | b"(use --exact to import the patch as a merge)\n" | |
1910 | ) |
|
1910 | ) | |
1911 | ) |
|
1911 | ) | |
1912 | else: |
|
1912 | else: | |
1913 | p1, p2 = parents |
|
1913 | p1, p2 = parents | |
1914 |
|
1914 | |||
1915 | n = None |
|
1915 | n = None | |
1916 | if update: |
|
1916 | if update: | |
1917 | if p1 != parents[0]: |
|
1917 | if p1 != parents[0]: | |
1918 | updatefunc(repo, p1.node()) |
|
1918 | updatefunc(repo, p1.node()) | |
1919 | if p2 != parents[1]: |
|
1919 | if p2 != parents[1]: | |
1920 | repo.setparents(p1.node(), p2.node()) |
|
1920 | repo.setparents(p1.node(), p2.node()) | |
1921 |
|
1921 | |||
1922 | if opts.get(b'exact') or importbranch: |
|
1922 | if opts.get(b'exact') or importbranch: | |
1923 | repo.dirstate.setbranch(branch or b'default') |
|
1923 | repo.dirstate.setbranch(branch or b'default') | |
1924 |
|
1924 | |||
1925 | partial = opts.get(b'partial', False) |
|
1925 | partial = opts.get(b'partial', False) | |
1926 | files = set() |
|
1926 | files = set() | |
1927 | try: |
|
1927 | try: | |
1928 | patch.patch( |
|
1928 | patch.patch( | |
1929 | ui, |
|
1929 | ui, | |
1930 | repo, |
|
1930 | repo, | |
1931 | tmpname, |
|
1931 | tmpname, | |
1932 | strip=strip, |
|
1932 | strip=strip, | |
1933 | prefix=prefix, |
|
1933 | prefix=prefix, | |
1934 | files=files, |
|
1934 | files=files, | |
1935 | eolmode=None, |
|
1935 | eolmode=None, | |
1936 | similarity=sim / 100.0, |
|
1936 | similarity=sim / 100.0, | |
1937 | ) |
|
1937 | ) | |
1938 | except error.PatchError as e: |
|
1938 | except error.PatchError as e: | |
1939 | if not partial: |
|
1939 | if not partial: | |
1940 | raise error.Abort(pycompat.bytestr(e)) |
|
1940 | raise error.Abort(pycompat.bytestr(e)) | |
1941 | if partial: |
|
1941 | if partial: | |
1942 | rejects = True |
|
1942 | rejects = True | |
1943 |
|
1943 | |||
1944 | files = list(files) |
|
1944 | files = list(files) | |
1945 | if nocommit: |
|
1945 | if nocommit: | |
1946 | if message: |
|
1946 | if message: | |
1947 | msgs.append(message) |
|
1947 | msgs.append(message) | |
1948 | else: |
|
1948 | else: | |
1949 | if opts.get(b'exact') or p2: |
|
1949 | if opts.get(b'exact') or p2: | |
1950 | # If you got here, you either use --force and know what |
|
1950 | # If you got here, you either use --force and know what | |
1951 | # you are doing or used --exact or a merge patch while |
|
1951 | # you are doing or used --exact or a merge patch while | |
1952 | # being updated to its first parent. |
|
1952 | # being updated to its first parent. | |
1953 | m = None |
|
1953 | m = None | |
1954 | else: |
|
1954 | else: | |
1955 | m = scmutil.matchfiles(repo, files or []) |
|
1955 | m = scmutil.matchfiles(repo, files or []) | |
1956 | editform = mergeeditform(repo[None], b'import.normal') |
|
1956 | editform = mergeeditform(repo[None], b'import.normal') | |
1957 | if opts.get(b'exact'): |
|
1957 | if opts.get(b'exact'): | |
1958 | editor = None |
|
1958 | editor = None | |
1959 | else: |
|
1959 | else: | |
1960 | editor = getcommiteditor( |
|
1960 | editor = getcommiteditor( | |
1961 | editform=editform, **pycompat.strkwargs(opts) |
|
1961 | editform=editform, **pycompat.strkwargs(opts) | |
1962 | ) |
|
1962 | ) | |
1963 | extra = {} |
|
1963 | extra = {} | |
1964 | for idfunc in extrapreimport: |
|
1964 | for idfunc in extrapreimport: | |
1965 | extrapreimportmap[idfunc](repo, patchdata, extra, opts) |
|
1965 | extrapreimportmap[idfunc](repo, patchdata, extra, opts) | |
1966 | overrides = {} |
|
1966 | overrides = {} | |
1967 | if partial: |
|
1967 | if partial: | |
1968 | overrides[(b'ui', b'allowemptycommit')] = True |
|
1968 | overrides[(b'ui', b'allowemptycommit')] = True | |
1969 | if opts.get(b'secret'): |
|
1969 | if opts.get(b'secret'): | |
1970 | overrides[(b'phases', b'new-commit')] = b'secret' |
|
1970 | overrides[(b'phases', b'new-commit')] = b'secret' | |
1971 | with repo.ui.configoverride(overrides, b'import'): |
|
1971 | with repo.ui.configoverride(overrides, b'import'): | |
1972 | n = repo.commit( |
|
1972 | n = repo.commit( | |
1973 | message, user, date, match=m, editor=editor, extra=extra |
|
1973 | message, user, date, match=m, editor=editor, extra=extra | |
1974 | ) |
|
1974 | ) | |
1975 | for idfunc in extrapostimport: |
|
1975 | for idfunc in extrapostimport: | |
1976 | extrapostimportmap[idfunc](repo[n]) |
|
1976 | extrapostimportmap[idfunc](repo[n]) | |
1977 | else: |
|
1977 | else: | |
1978 | if opts.get(b'exact') or importbranch: |
|
1978 | if opts.get(b'exact') or importbranch: | |
1979 | branch = branch or b'default' |
|
1979 | branch = branch or b'default' | |
1980 | else: |
|
1980 | else: | |
1981 | branch = p1.branch() |
|
1981 | branch = p1.branch() | |
1982 | store = patch.filestore() |
|
1982 | store = patch.filestore() | |
1983 | try: |
|
1983 | try: | |
1984 | files = set() |
|
1984 | files = set() | |
1985 | try: |
|
1985 | try: | |
1986 | patch.patchrepo( |
|
1986 | patch.patchrepo( | |
1987 | ui, |
|
1987 | ui, | |
1988 | repo, |
|
1988 | repo, | |
1989 | p1, |
|
1989 | p1, | |
1990 | store, |
|
1990 | store, | |
1991 | tmpname, |
|
1991 | tmpname, | |
1992 | strip, |
|
1992 | strip, | |
1993 | prefix, |
|
1993 | prefix, | |
1994 | files, |
|
1994 | files, | |
1995 | eolmode=None, |
|
1995 | eolmode=None, | |
1996 | ) |
|
1996 | ) | |
1997 | except error.PatchError as e: |
|
1997 | except error.PatchError as e: | |
1998 | raise error.Abort(stringutil.forcebytestr(e)) |
|
1998 | raise error.Abort(stringutil.forcebytestr(e)) | |
1999 | if opts.get(b'exact'): |
|
1999 | if opts.get(b'exact'): | |
2000 | editor = None |
|
2000 | editor = None | |
2001 | else: |
|
2001 | else: | |
2002 | editor = getcommiteditor(editform=b'import.bypass') |
|
2002 | editor = getcommiteditor(editform=b'import.bypass') | |
2003 | memctx = context.memctx( |
|
2003 | memctx = context.memctx( | |
2004 | repo, |
|
2004 | repo, | |
2005 | (p1.node(), p2.node()), |
|
2005 | (p1.node(), p2.node()), | |
2006 | message, |
|
2006 | message, | |
2007 | files=files, |
|
2007 | files=files, | |
2008 | filectxfn=store, |
|
2008 | filectxfn=store, | |
2009 | user=user, |
|
2009 | user=user, | |
2010 | date=date, |
|
2010 | date=date, | |
2011 | branch=branch, |
|
2011 | branch=branch, | |
2012 | editor=editor, |
|
2012 | editor=editor, | |
2013 | ) |
|
2013 | ) | |
2014 | n = memctx.commit() |
|
2014 | ||
|
2015 | overrides = {} | |||
|
2016 | if opts.get(b'secret'): | |||
|
2017 | overrides[(b'phases', b'new-commit')] = b'secret' | |||
|
2018 | with repo.ui.configoverride(overrides, b'import'): | |||
|
2019 | n = memctx.commit() | |||
2015 | finally: |
|
2020 | finally: | |
2016 | store.close() |
|
2021 | store.close() | |
2017 | if opts.get(b'exact') and nocommit: |
|
2022 | if opts.get(b'exact') and nocommit: | |
2018 | # --exact with --no-commit is still useful in that it does merge |
|
2023 | # --exact with --no-commit is still useful in that it does merge | |
2019 | # and branch bits |
|
2024 | # and branch bits | |
2020 | ui.warn(_(b"warning: can't check exact import with --no-commit\n")) |
|
2025 | ui.warn(_(b"warning: can't check exact import with --no-commit\n")) | |
2021 | elif opts.get(b'exact') and (not n or hex(n) != nodeid): |
|
2026 | elif opts.get(b'exact') and (not n or hex(n) != nodeid): | |
2022 | raise error.Abort(_(b'patch is damaged or loses information')) |
|
2027 | raise error.Abort(_(b'patch is damaged or loses information')) | |
2023 | msg = _(b'applied to working directory') |
|
2028 | msg = _(b'applied to working directory') | |
2024 | if n: |
|
2029 | if n: | |
2025 | # i18n: refers to a short changeset id |
|
2030 | # i18n: refers to a short changeset id | |
2026 | msg = _(b'created %s') % short(n) |
|
2031 | msg = _(b'created %s') % short(n) | |
2027 | return msg, n, rejects |
|
2032 | return msg, n, rejects | |
2028 |
|
2033 | |||
2029 |
|
2034 | |||
2030 | # facility to let extensions include additional data in an exported patch |
|
2035 | # facility to let extensions include additional data in an exported patch | |
2031 | # list of identifiers to be executed in order |
|
2036 | # list of identifiers to be executed in order | |
2032 | extraexport = [] |
|
2037 | extraexport = [] | |
2033 | # mapping from identifier to actual export function |
|
2038 | # mapping from identifier to actual export function | |
2034 | # function as to return a string to be added to the header or None |
|
2039 | # function as to return a string to be added to the header or None | |
2035 | # it is given two arguments (sequencenumber, changectx) |
|
2040 | # it is given two arguments (sequencenumber, changectx) | |
2036 | extraexportmap = {} |
|
2041 | extraexportmap = {} | |
2037 |
|
2042 | |||
2038 |
|
2043 | |||
2039 | def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts): |
|
2044 | def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts): | |
2040 | node = scmutil.binnode(ctx) |
|
2045 | node = scmutil.binnode(ctx) | |
2041 | parents = [p.node() for p in ctx.parents() if p] |
|
2046 | parents = [p.node() for p in ctx.parents() if p] | |
2042 | branch = ctx.branch() |
|
2047 | branch = ctx.branch() | |
2043 | if switch_parent: |
|
2048 | if switch_parent: | |
2044 | parents.reverse() |
|
2049 | parents.reverse() | |
2045 |
|
2050 | |||
2046 | if parents: |
|
2051 | if parents: | |
2047 | prev = parents[0] |
|
2052 | prev = parents[0] | |
2048 | else: |
|
2053 | else: | |
2049 | prev = nullid |
|
2054 | prev = nullid | |
2050 |
|
2055 | |||
2051 | fm.context(ctx=ctx) |
|
2056 | fm.context(ctx=ctx) | |
2052 | fm.plain(b'# HG changeset patch\n') |
|
2057 | fm.plain(b'# HG changeset patch\n') | |
2053 | fm.write(b'user', b'# User %s\n', ctx.user()) |
|
2058 | fm.write(b'user', b'# User %s\n', ctx.user()) | |
2054 | fm.plain(b'# Date %d %d\n' % ctx.date()) |
|
2059 | fm.plain(b'# Date %d %d\n' % ctx.date()) | |
2055 | fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date())) |
|
2060 | fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date())) | |
2056 | fm.condwrite( |
|
2061 | fm.condwrite( | |
2057 | branch and branch != b'default', b'branch', b'# Branch %s\n', branch |
|
2062 | branch and branch != b'default', b'branch', b'# Branch %s\n', branch | |
2058 | ) |
|
2063 | ) | |
2059 | fm.write(b'node', b'# Node ID %s\n', hex(node)) |
|
2064 | fm.write(b'node', b'# Node ID %s\n', hex(node)) | |
2060 | fm.plain(b'# Parent %s\n' % hex(prev)) |
|
2065 | fm.plain(b'# Parent %s\n' % hex(prev)) | |
2061 | if len(parents) > 1: |
|
2066 | if len(parents) > 1: | |
2062 | fm.plain(b'# Parent %s\n' % hex(parents[1])) |
|
2067 | fm.plain(b'# Parent %s\n' % hex(parents[1])) | |
2063 | fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node')) |
|
2068 | fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node')) | |
2064 |
|
2069 | |||
2065 | # TODO: redesign extraexportmap function to support formatter |
|
2070 | # TODO: redesign extraexportmap function to support formatter | |
2066 | for headerid in extraexport: |
|
2071 | for headerid in extraexport: | |
2067 | header = extraexportmap[headerid](seqno, ctx) |
|
2072 | header = extraexportmap[headerid](seqno, ctx) | |
2068 | if header is not None: |
|
2073 | if header is not None: | |
2069 | fm.plain(b'# %s\n' % header) |
|
2074 | fm.plain(b'# %s\n' % header) | |
2070 |
|
2075 | |||
2071 | fm.write(b'desc', b'%s\n', ctx.description().rstrip()) |
|
2076 | fm.write(b'desc', b'%s\n', ctx.description().rstrip()) | |
2072 | fm.plain(b'\n') |
|
2077 | fm.plain(b'\n') | |
2073 |
|
2078 | |||
2074 | if fm.isplain(): |
|
2079 | if fm.isplain(): | |
2075 | chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts) |
|
2080 | chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts) | |
2076 | for chunk, label in chunkiter: |
|
2081 | for chunk, label in chunkiter: | |
2077 | fm.plain(chunk, label=label) |
|
2082 | fm.plain(chunk, label=label) | |
2078 | else: |
|
2083 | else: | |
2079 | chunkiter = patch.diff(repo, prev, node, match, opts=diffopts) |
|
2084 | chunkiter = patch.diff(repo, prev, node, match, opts=diffopts) | |
2080 | # TODO: make it structured? |
|
2085 | # TODO: make it structured? | |
2081 | fm.data(diff=b''.join(chunkiter)) |
|
2086 | fm.data(diff=b''.join(chunkiter)) | |
2082 |
|
2087 | |||
2083 |
|
2088 | |||
2084 | def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match): |
|
2089 | def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match): | |
2085 | """Export changesets to stdout or a single file""" |
|
2090 | """Export changesets to stdout or a single file""" | |
2086 | for seqno, rev in enumerate(revs, 1): |
|
2091 | for seqno, rev in enumerate(revs, 1): | |
2087 | ctx = repo[rev] |
|
2092 | ctx = repo[rev] | |
2088 | if not dest.startswith(b'<'): |
|
2093 | if not dest.startswith(b'<'): | |
2089 | repo.ui.note(b"%s\n" % dest) |
|
2094 | repo.ui.note(b"%s\n" % dest) | |
2090 | fm.startitem() |
|
2095 | fm.startitem() | |
2091 | _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts) |
|
2096 | _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts) | |
2092 |
|
2097 | |||
2093 |
|
2098 | |||
2094 | def _exportfntemplate( |
|
2099 | def _exportfntemplate( | |
2095 | repo, revs, basefm, fntemplate, switch_parent, diffopts, match |
|
2100 | repo, revs, basefm, fntemplate, switch_parent, diffopts, match | |
2096 | ): |
|
2101 | ): | |
2097 | """Export changesets to possibly multiple files""" |
|
2102 | """Export changesets to possibly multiple files""" | |
2098 | total = len(revs) |
|
2103 | total = len(revs) | |
2099 | revwidth = max(len(str(rev)) for rev in revs) |
|
2104 | revwidth = max(len(str(rev)) for rev in revs) | |
2100 | filemap = util.sortdict() # filename: [(seqno, rev), ...] |
|
2105 | filemap = util.sortdict() # filename: [(seqno, rev), ...] | |
2101 |
|
2106 | |||
2102 | for seqno, rev in enumerate(revs, 1): |
|
2107 | for seqno, rev in enumerate(revs, 1): | |
2103 | ctx = repo[rev] |
|
2108 | ctx = repo[rev] | |
2104 | dest = makefilename( |
|
2109 | dest = makefilename( | |
2105 | ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth |
|
2110 | ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth | |
2106 | ) |
|
2111 | ) | |
2107 | filemap.setdefault(dest, []).append((seqno, rev)) |
|
2112 | filemap.setdefault(dest, []).append((seqno, rev)) | |
2108 |
|
2113 | |||
2109 | for dest in filemap: |
|
2114 | for dest in filemap: | |
2110 | with formatter.maybereopen(basefm, dest) as fm: |
|
2115 | with formatter.maybereopen(basefm, dest) as fm: | |
2111 | repo.ui.note(b"%s\n" % dest) |
|
2116 | repo.ui.note(b"%s\n" % dest) | |
2112 | for seqno, rev in filemap[dest]: |
|
2117 | for seqno, rev in filemap[dest]: | |
2113 | fm.startitem() |
|
2118 | fm.startitem() | |
2114 | ctx = repo[rev] |
|
2119 | ctx = repo[rev] | |
2115 | _exportsingle( |
|
2120 | _exportsingle( | |
2116 | repo, ctx, fm, match, switch_parent, seqno, diffopts |
|
2121 | repo, ctx, fm, match, switch_parent, seqno, diffopts | |
2117 | ) |
|
2122 | ) | |
2118 |
|
2123 | |||
2119 |
|
2124 | |||
2120 | def _prefetchchangedfiles(repo, revs, match): |
|
2125 | def _prefetchchangedfiles(repo, revs, match): | |
2121 | allfiles = set() |
|
2126 | allfiles = set() | |
2122 | for rev in revs: |
|
2127 | for rev in revs: | |
2123 | for file in repo[rev].files(): |
|
2128 | for file in repo[rev].files(): | |
2124 | if not match or match(file): |
|
2129 | if not match or match(file): | |
2125 | allfiles.add(file) |
|
2130 | allfiles.add(file) | |
2126 | scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles)) |
|
2131 | scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles)) | |
2127 |
|
2132 | |||
2128 |
|
2133 | |||
2129 | def export( |
|
2134 | def export( | |
2130 | repo, |
|
2135 | repo, | |
2131 | revs, |
|
2136 | revs, | |
2132 | basefm, |
|
2137 | basefm, | |
2133 | fntemplate=b'hg-%h.patch', |
|
2138 | fntemplate=b'hg-%h.patch', | |
2134 | switch_parent=False, |
|
2139 | switch_parent=False, | |
2135 | opts=None, |
|
2140 | opts=None, | |
2136 | match=None, |
|
2141 | match=None, | |
2137 | ): |
|
2142 | ): | |
2138 | '''export changesets as hg patches |
|
2143 | '''export changesets as hg patches | |
2139 |
|
2144 | |||
2140 | Args: |
|
2145 | Args: | |
2141 | repo: The repository from which we're exporting revisions. |
|
2146 | repo: The repository from which we're exporting revisions. | |
2142 | revs: A list of revisions to export as revision numbers. |
|
2147 | revs: A list of revisions to export as revision numbers. | |
2143 | basefm: A formatter to which patches should be written. |
|
2148 | basefm: A formatter to which patches should be written. | |
2144 | fntemplate: An optional string to use for generating patch file names. |
|
2149 | fntemplate: An optional string to use for generating patch file names. | |
2145 | switch_parent: If True, show diffs against second parent when not nullid. |
|
2150 | switch_parent: If True, show diffs against second parent when not nullid. | |
2146 | Default is false, which always shows diff against p1. |
|
2151 | Default is false, which always shows diff against p1. | |
2147 | opts: diff options to use for generating the patch. |
|
2152 | opts: diff options to use for generating the patch. | |
2148 | match: If specified, only export changes to files matching this matcher. |
|
2153 | match: If specified, only export changes to files matching this matcher. | |
2149 |
|
2154 | |||
2150 | Returns: |
|
2155 | Returns: | |
2151 | Nothing. |
|
2156 | Nothing. | |
2152 |
|
2157 | |||
2153 | Side Effect: |
|
2158 | Side Effect: | |
2154 | "HG Changeset Patch" data is emitted to one of the following |
|
2159 | "HG Changeset Patch" data is emitted to one of the following | |
2155 | destinations: |
|
2160 | destinations: | |
2156 | fntemplate specified: Each rev is written to a unique file named using |
|
2161 | fntemplate specified: Each rev is written to a unique file named using | |
2157 | the given template. |
|
2162 | the given template. | |
2158 | Otherwise: All revs will be written to basefm. |
|
2163 | Otherwise: All revs will be written to basefm. | |
2159 | ''' |
|
2164 | ''' | |
2160 | _prefetchchangedfiles(repo, revs, match) |
|
2165 | _prefetchchangedfiles(repo, revs, match) | |
2161 |
|
2166 | |||
2162 | if not fntemplate: |
|
2167 | if not fntemplate: | |
2163 | _exportfile( |
|
2168 | _exportfile( | |
2164 | repo, revs, basefm, b'<unnamed>', switch_parent, opts, match |
|
2169 | repo, revs, basefm, b'<unnamed>', switch_parent, opts, match | |
2165 | ) |
|
2170 | ) | |
2166 | else: |
|
2171 | else: | |
2167 | _exportfntemplate( |
|
2172 | _exportfntemplate( | |
2168 | repo, revs, basefm, fntemplate, switch_parent, opts, match |
|
2173 | repo, revs, basefm, fntemplate, switch_parent, opts, match | |
2169 | ) |
|
2174 | ) | |
2170 |
|
2175 | |||
2171 |
|
2176 | |||
2172 | def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None): |
|
2177 | def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None): | |
2173 | """Export changesets to the given file stream""" |
|
2178 | """Export changesets to the given file stream""" | |
2174 | _prefetchchangedfiles(repo, revs, match) |
|
2179 | _prefetchchangedfiles(repo, revs, match) | |
2175 |
|
2180 | |||
2176 | dest = getattr(fp, 'name', b'<unnamed>') |
|
2181 | dest = getattr(fp, 'name', b'<unnamed>') | |
2177 | with formatter.formatter(repo.ui, fp, b'export', {}) as fm: |
|
2182 | with formatter.formatter(repo.ui, fp, b'export', {}) as fm: | |
2178 | _exportfile(repo, revs, fm, dest, switch_parent, opts, match) |
|
2183 | _exportfile(repo, revs, fm, dest, switch_parent, opts, match) | |
2179 |
|
2184 | |||
2180 |
|
2185 | |||
2181 | def showmarker(fm, marker, index=None): |
|
2186 | def showmarker(fm, marker, index=None): | |
2182 | """utility function to display obsolescence marker in a readable way |
|
2187 | """utility function to display obsolescence marker in a readable way | |
2183 |
|
2188 | |||
2184 | To be used by debug function.""" |
|
2189 | To be used by debug function.""" | |
2185 | if index is not None: |
|
2190 | if index is not None: | |
2186 | fm.write(b'index', b'%i ', index) |
|
2191 | fm.write(b'index', b'%i ', index) | |
2187 | fm.write(b'prednode', b'%s ', hex(marker.prednode())) |
|
2192 | fm.write(b'prednode', b'%s ', hex(marker.prednode())) | |
2188 | succs = marker.succnodes() |
|
2193 | succs = marker.succnodes() | |
2189 | fm.condwrite( |
|
2194 | fm.condwrite( | |
2190 | succs, |
|
2195 | succs, | |
2191 | b'succnodes', |
|
2196 | b'succnodes', | |
2192 | b'%s ', |
|
2197 | b'%s ', | |
2193 | fm.formatlist(map(hex, succs), name=b'node'), |
|
2198 | fm.formatlist(map(hex, succs), name=b'node'), | |
2194 | ) |
|
2199 | ) | |
2195 | fm.write(b'flag', b'%X ', marker.flags()) |
|
2200 | fm.write(b'flag', b'%X ', marker.flags()) | |
2196 | parents = marker.parentnodes() |
|
2201 | parents = marker.parentnodes() | |
2197 | if parents is not None: |
|
2202 | if parents is not None: | |
2198 | fm.write( |
|
2203 | fm.write( | |
2199 | b'parentnodes', |
|
2204 | b'parentnodes', | |
2200 | b'{%s} ', |
|
2205 | b'{%s} ', | |
2201 | fm.formatlist(map(hex, parents), name=b'node', sep=b', '), |
|
2206 | fm.formatlist(map(hex, parents), name=b'node', sep=b', '), | |
2202 | ) |
|
2207 | ) | |
2203 | fm.write(b'date', b'(%s) ', fm.formatdate(marker.date())) |
|
2208 | fm.write(b'date', b'(%s) ', fm.formatdate(marker.date())) | |
2204 | meta = marker.metadata().copy() |
|
2209 | meta = marker.metadata().copy() | |
2205 | meta.pop(b'date', None) |
|
2210 | meta.pop(b'date', None) | |
2206 | smeta = pycompat.rapply(pycompat.maybebytestr, meta) |
|
2211 | smeta = pycompat.rapply(pycompat.maybebytestr, meta) | |
2207 | fm.write( |
|
2212 | fm.write( | |
2208 | b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ') |
|
2213 | b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ') | |
2209 | ) |
|
2214 | ) | |
2210 | fm.plain(b'\n') |
|
2215 | fm.plain(b'\n') | |
2211 |
|
2216 | |||
2212 |
|
2217 | |||
2213 | def finddate(ui, repo, date): |
|
2218 | def finddate(ui, repo, date): | |
2214 | """Find the tipmost changeset that matches the given date spec""" |
|
2219 | """Find the tipmost changeset that matches the given date spec""" | |
2215 |
|
2220 | |||
2216 | df = dateutil.matchdate(date) |
|
2221 | df = dateutil.matchdate(date) | |
2217 | m = scmutil.matchall(repo) |
|
2222 | m = scmutil.matchall(repo) | |
2218 | results = {} |
|
2223 | results = {} | |
2219 |
|
2224 | |||
2220 | def prep(ctx, fns): |
|
2225 | def prep(ctx, fns): | |
2221 | d = ctx.date() |
|
2226 | d = ctx.date() | |
2222 | if df(d[0]): |
|
2227 | if df(d[0]): | |
2223 | results[ctx.rev()] = d |
|
2228 | results[ctx.rev()] = d | |
2224 |
|
2229 | |||
2225 | for ctx in walkchangerevs(repo, m, {b'rev': None}, prep): |
|
2230 | for ctx in walkchangerevs(repo, m, {b'rev': None}, prep): | |
2226 | rev = ctx.rev() |
|
2231 | rev = ctx.rev() | |
2227 | if rev in results: |
|
2232 | if rev in results: | |
2228 | ui.status( |
|
2233 | ui.status( | |
2229 | _(b"found revision %d from %s\n") |
|
2234 | _(b"found revision %d from %s\n") | |
2230 | % (rev, dateutil.datestr(results[rev])) |
|
2235 | % (rev, dateutil.datestr(results[rev])) | |
2231 | ) |
|
2236 | ) | |
2232 | return b'%d' % rev |
|
2237 | return b'%d' % rev | |
2233 |
|
2238 | |||
2234 | raise error.Abort(_(b"revision matching date not found")) |
|
2239 | raise error.Abort(_(b"revision matching date not found")) | |
2235 |
|
2240 | |||
2236 |
|
2241 | |||
2237 | def increasingwindows(windowsize=8, sizelimit=512): |
|
2242 | def increasingwindows(windowsize=8, sizelimit=512): | |
2238 | while True: |
|
2243 | while True: | |
2239 | yield windowsize |
|
2244 | yield windowsize | |
2240 | if windowsize < sizelimit: |
|
2245 | if windowsize < sizelimit: | |
2241 | windowsize *= 2 |
|
2246 | windowsize *= 2 | |
2242 |
|
2247 | |||
2243 |
|
2248 | |||
2244 | def _walkrevs(repo, opts): |
|
2249 | def _walkrevs(repo, opts): | |
2245 | # Default --rev value depends on --follow but --follow behavior |
|
2250 | # Default --rev value depends on --follow but --follow behavior | |
2246 | # depends on revisions resolved from --rev... |
|
2251 | # depends on revisions resolved from --rev... | |
2247 | follow = opts.get(b'follow') or opts.get(b'follow_first') |
|
2252 | follow = opts.get(b'follow') or opts.get(b'follow_first') | |
2248 | if opts.get(b'rev'): |
|
2253 | if opts.get(b'rev'): | |
2249 | revs = scmutil.revrange(repo, opts[b'rev']) |
|
2254 | revs = scmutil.revrange(repo, opts[b'rev']) | |
2250 | elif follow and repo.dirstate.p1() == nullid: |
|
2255 | elif follow and repo.dirstate.p1() == nullid: | |
2251 | revs = smartset.baseset() |
|
2256 | revs = smartset.baseset() | |
2252 | elif follow: |
|
2257 | elif follow: | |
2253 | revs = repo.revs(b'reverse(:.)') |
|
2258 | revs = repo.revs(b'reverse(:.)') | |
2254 | else: |
|
2259 | else: | |
2255 | revs = smartset.spanset(repo) |
|
2260 | revs = smartset.spanset(repo) | |
2256 | revs.reverse() |
|
2261 | revs.reverse() | |
2257 | return revs |
|
2262 | return revs | |
2258 |
|
2263 | |||
2259 |
|
2264 | |||
2260 | class FileWalkError(Exception): |
|
2265 | class FileWalkError(Exception): | |
2261 | pass |
|
2266 | pass | |
2262 |
|
2267 | |||
2263 |
|
2268 | |||
2264 | def walkfilerevs(repo, match, follow, revs, fncache): |
|
2269 | def walkfilerevs(repo, match, follow, revs, fncache): | |
2265 | '''Walks the file history for the matched files. |
|
2270 | '''Walks the file history for the matched files. | |
2266 |
|
2271 | |||
2267 | Returns the changeset revs that are involved in the file history. |
|
2272 | Returns the changeset revs that are involved in the file history. | |
2268 |
|
2273 | |||
2269 | Throws FileWalkError if the file history can't be walked using |
|
2274 | Throws FileWalkError if the file history can't be walked using | |
2270 | filelogs alone. |
|
2275 | filelogs alone. | |
2271 | ''' |
|
2276 | ''' | |
2272 | wanted = set() |
|
2277 | wanted = set() | |
2273 | copies = [] |
|
2278 | copies = [] | |
2274 | minrev, maxrev = min(revs), max(revs) |
|
2279 | minrev, maxrev = min(revs), max(revs) | |
2275 |
|
2280 | |||
2276 | def filerevs(filelog, last): |
|
2281 | def filerevs(filelog, last): | |
2277 | """ |
|
2282 | """ | |
2278 | Only files, no patterns. Check the history of each file. |
|
2283 | Only files, no patterns. Check the history of each file. | |
2279 |
|
2284 | |||
2280 | Examines filelog entries within minrev, maxrev linkrev range |
|
2285 | Examines filelog entries within minrev, maxrev linkrev range | |
2281 | Returns an iterator yielding (linkrev, parentlinkrevs, copied) |
|
2286 | Returns an iterator yielding (linkrev, parentlinkrevs, copied) | |
2282 | tuples in backwards order |
|
2287 | tuples in backwards order | |
2283 | """ |
|
2288 | """ | |
2284 | cl_count = len(repo) |
|
2289 | cl_count = len(repo) | |
2285 | revs = [] |
|
2290 | revs = [] | |
2286 | for j in pycompat.xrange(0, last + 1): |
|
2291 | for j in pycompat.xrange(0, last + 1): | |
2287 | linkrev = filelog.linkrev(j) |
|
2292 | linkrev = filelog.linkrev(j) | |
2288 | if linkrev < minrev: |
|
2293 | if linkrev < minrev: | |
2289 | continue |
|
2294 | continue | |
2290 | # only yield rev for which we have the changelog, it can |
|
2295 | # only yield rev for which we have the changelog, it can | |
2291 | # happen while doing "hg log" during a pull or commit |
|
2296 | # happen while doing "hg log" during a pull or commit | |
2292 | if linkrev >= cl_count: |
|
2297 | if linkrev >= cl_count: | |
2293 | break |
|
2298 | break | |
2294 |
|
2299 | |||
2295 | parentlinkrevs = [] |
|
2300 | parentlinkrevs = [] | |
2296 | for p in filelog.parentrevs(j): |
|
2301 | for p in filelog.parentrevs(j): | |
2297 | if p != nullrev: |
|
2302 | if p != nullrev: | |
2298 | parentlinkrevs.append(filelog.linkrev(p)) |
|
2303 | parentlinkrevs.append(filelog.linkrev(p)) | |
2299 | n = filelog.node(j) |
|
2304 | n = filelog.node(j) | |
2300 | revs.append( |
|
2305 | revs.append( | |
2301 | (linkrev, parentlinkrevs, follow and filelog.renamed(n)) |
|
2306 | (linkrev, parentlinkrevs, follow and filelog.renamed(n)) | |
2302 | ) |
|
2307 | ) | |
2303 |
|
2308 | |||
2304 | return reversed(revs) |
|
2309 | return reversed(revs) | |
2305 |
|
2310 | |||
2306 | def iterfiles(): |
|
2311 | def iterfiles(): | |
2307 | pctx = repo[b'.'] |
|
2312 | pctx = repo[b'.'] | |
2308 | for filename in match.files(): |
|
2313 | for filename in match.files(): | |
2309 | if follow: |
|
2314 | if follow: | |
2310 | if filename not in pctx: |
|
2315 | if filename not in pctx: | |
2311 | raise error.Abort( |
|
2316 | raise error.Abort( | |
2312 | _( |
|
2317 | _( | |
2313 | b'cannot follow file not in parent ' |
|
2318 | b'cannot follow file not in parent ' | |
2314 | b'revision: "%s"' |
|
2319 | b'revision: "%s"' | |
2315 | ) |
|
2320 | ) | |
2316 | % filename |
|
2321 | % filename | |
2317 | ) |
|
2322 | ) | |
2318 | yield filename, pctx[filename].filenode() |
|
2323 | yield filename, pctx[filename].filenode() | |
2319 | else: |
|
2324 | else: | |
2320 | yield filename, None |
|
2325 | yield filename, None | |
2321 | for filename_node in copies: |
|
2326 | for filename_node in copies: | |
2322 | yield filename_node |
|
2327 | yield filename_node | |
2323 |
|
2328 | |||
2324 | for file_, node in iterfiles(): |
|
2329 | for file_, node in iterfiles(): | |
2325 | filelog = repo.file(file_) |
|
2330 | filelog = repo.file(file_) | |
2326 | if not len(filelog): |
|
2331 | if not len(filelog): | |
2327 | if node is None: |
|
2332 | if node is None: | |
2328 | # A zero count may be a directory or deleted file, so |
|
2333 | # A zero count may be a directory or deleted file, so | |
2329 | # try to find matching entries on the slow path. |
|
2334 | # try to find matching entries on the slow path. | |
2330 | if follow: |
|
2335 | if follow: | |
2331 | raise error.Abort( |
|
2336 | raise error.Abort( | |
2332 | _(b'cannot follow nonexistent file: "%s"') % file_ |
|
2337 | _(b'cannot follow nonexistent file: "%s"') % file_ | |
2333 | ) |
|
2338 | ) | |
2334 | raise FileWalkError(b"Cannot walk via filelog") |
|
2339 | raise FileWalkError(b"Cannot walk via filelog") | |
2335 | else: |
|
2340 | else: | |
2336 | continue |
|
2341 | continue | |
2337 |
|
2342 | |||
2338 | if node is None: |
|
2343 | if node is None: | |
2339 | last = len(filelog) - 1 |
|
2344 | last = len(filelog) - 1 | |
2340 | else: |
|
2345 | else: | |
2341 | last = filelog.rev(node) |
|
2346 | last = filelog.rev(node) | |
2342 |
|
2347 | |||
2343 | # keep track of all ancestors of the file |
|
2348 | # keep track of all ancestors of the file | |
2344 | ancestors = {filelog.linkrev(last)} |
|
2349 | ancestors = {filelog.linkrev(last)} | |
2345 |
|
2350 | |||
2346 | # iterate from latest to oldest revision |
|
2351 | # iterate from latest to oldest revision | |
2347 | for rev, flparentlinkrevs, copied in filerevs(filelog, last): |
|
2352 | for rev, flparentlinkrevs, copied in filerevs(filelog, last): | |
2348 | if not follow: |
|
2353 | if not follow: | |
2349 | if rev > maxrev: |
|
2354 | if rev > maxrev: | |
2350 | continue |
|
2355 | continue | |
2351 | else: |
|
2356 | else: | |
2352 | # Note that last might not be the first interesting |
|
2357 | # Note that last might not be the first interesting | |
2353 | # rev to us: |
|
2358 | # rev to us: | |
2354 | # if the file has been changed after maxrev, we'll |
|
2359 | # if the file has been changed after maxrev, we'll | |
2355 | # have linkrev(last) > maxrev, and we still need |
|
2360 | # have linkrev(last) > maxrev, and we still need | |
2356 | # to explore the file graph |
|
2361 | # to explore the file graph | |
2357 | if rev not in ancestors: |
|
2362 | if rev not in ancestors: | |
2358 | continue |
|
2363 | continue | |
2359 | # XXX insert 1327 fix here |
|
2364 | # XXX insert 1327 fix here | |
2360 | if flparentlinkrevs: |
|
2365 | if flparentlinkrevs: | |
2361 | ancestors.update(flparentlinkrevs) |
|
2366 | ancestors.update(flparentlinkrevs) | |
2362 |
|
2367 | |||
2363 | fncache.setdefault(rev, []).append(file_) |
|
2368 | fncache.setdefault(rev, []).append(file_) | |
2364 | wanted.add(rev) |
|
2369 | wanted.add(rev) | |
2365 | if copied: |
|
2370 | if copied: | |
2366 | copies.append(copied) |
|
2371 | copies.append(copied) | |
2367 |
|
2372 | |||
2368 | return wanted |
|
2373 | return wanted | |
2369 |
|
2374 | |||
2370 |
|
2375 | |||
2371 | class _followfilter(object): |
|
2376 | class _followfilter(object): | |
2372 | def __init__(self, repo, onlyfirst=False): |
|
2377 | def __init__(self, repo, onlyfirst=False): | |
2373 | self.repo = repo |
|
2378 | self.repo = repo | |
2374 | self.startrev = nullrev |
|
2379 | self.startrev = nullrev | |
2375 | self.roots = set() |
|
2380 | self.roots = set() | |
2376 | self.onlyfirst = onlyfirst |
|
2381 | self.onlyfirst = onlyfirst | |
2377 |
|
2382 | |||
2378 | def match(self, rev): |
|
2383 | def match(self, rev): | |
2379 | def realparents(rev): |
|
2384 | def realparents(rev): | |
2380 | if self.onlyfirst: |
|
2385 | if self.onlyfirst: | |
2381 | return self.repo.changelog.parentrevs(rev)[0:1] |
|
2386 | return self.repo.changelog.parentrevs(rev)[0:1] | |
2382 | else: |
|
2387 | else: | |
2383 | return filter( |
|
2388 | return filter( | |
2384 | lambda x: x != nullrev, self.repo.changelog.parentrevs(rev) |
|
2389 | lambda x: x != nullrev, self.repo.changelog.parentrevs(rev) | |
2385 | ) |
|
2390 | ) | |
2386 |
|
2391 | |||
2387 | if self.startrev == nullrev: |
|
2392 | if self.startrev == nullrev: | |
2388 | self.startrev = rev |
|
2393 | self.startrev = rev | |
2389 | return True |
|
2394 | return True | |
2390 |
|
2395 | |||
2391 | if rev > self.startrev: |
|
2396 | if rev > self.startrev: | |
2392 | # forward: all descendants |
|
2397 | # forward: all descendants | |
2393 | if not self.roots: |
|
2398 | if not self.roots: | |
2394 | self.roots.add(self.startrev) |
|
2399 | self.roots.add(self.startrev) | |
2395 | for parent in realparents(rev): |
|
2400 | for parent in realparents(rev): | |
2396 | if parent in self.roots: |
|
2401 | if parent in self.roots: | |
2397 | self.roots.add(rev) |
|
2402 | self.roots.add(rev) | |
2398 | return True |
|
2403 | return True | |
2399 | else: |
|
2404 | else: | |
2400 | # backwards: all parents |
|
2405 | # backwards: all parents | |
2401 | if not self.roots: |
|
2406 | if not self.roots: | |
2402 | self.roots.update(realparents(self.startrev)) |
|
2407 | self.roots.update(realparents(self.startrev)) | |
2403 | if rev in self.roots: |
|
2408 | if rev in self.roots: | |
2404 | self.roots.remove(rev) |
|
2409 | self.roots.remove(rev) | |
2405 | self.roots.update(realparents(rev)) |
|
2410 | self.roots.update(realparents(rev)) | |
2406 | return True |
|
2411 | return True | |
2407 |
|
2412 | |||
2408 | return False |
|
2413 | return False | |
2409 |
|
2414 | |||
2410 |
|
2415 | |||
2411 | def walkchangerevs(repo, match, opts, prepare): |
|
2416 | def walkchangerevs(repo, match, opts, prepare): | |
2412 | '''Iterate over files and the revs in which they changed. |
|
2417 | '''Iterate over files and the revs in which they changed. | |
2413 |
|
2418 | |||
2414 | Callers most commonly need to iterate backwards over the history |
|
2419 | Callers most commonly need to iterate backwards over the history | |
2415 | in which they are interested. Doing so has awful (quadratic-looking) |
|
2420 | in which they are interested. Doing so has awful (quadratic-looking) | |
2416 | performance, so we use iterators in a "windowed" way. |
|
2421 | performance, so we use iterators in a "windowed" way. | |
2417 |
|
2422 | |||
2418 | We walk a window of revisions in the desired order. Within the |
|
2423 | We walk a window of revisions in the desired order. Within the | |
2419 | window, we first walk forwards to gather data, then in the desired |
|
2424 | window, we first walk forwards to gather data, then in the desired | |
2420 | order (usually backwards) to display it. |
|
2425 | order (usually backwards) to display it. | |
2421 |
|
2426 | |||
2422 | This function returns an iterator yielding contexts. Before |
|
2427 | This function returns an iterator yielding contexts. Before | |
2423 | yielding each context, the iterator will first call the prepare |
|
2428 | yielding each context, the iterator will first call the prepare | |
2424 | function on each context in the window in forward order.''' |
|
2429 | function on each context in the window in forward order.''' | |
2425 |
|
2430 | |||
2426 | allfiles = opts.get(b'all_files') |
|
2431 | allfiles = opts.get(b'all_files') | |
2427 | follow = opts.get(b'follow') or opts.get(b'follow_first') |
|
2432 | follow = opts.get(b'follow') or opts.get(b'follow_first') | |
2428 | revs = _walkrevs(repo, opts) |
|
2433 | revs = _walkrevs(repo, opts) | |
2429 | if not revs: |
|
2434 | if not revs: | |
2430 | return [] |
|
2435 | return [] | |
2431 | wanted = set() |
|
2436 | wanted = set() | |
2432 | slowpath = match.anypats() or (not match.always() and opts.get(b'removed')) |
|
2437 | slowpath = match.anypats() or (not match.always() and opts.get(b'removed')) | |
2433 | fncache = {} |
|
2438 | fncache = {} | |
2434 | change = repo.__getitem__ |
|
2439 | change = repo.__getitem__ | |
2435 |
|
2440 | |||
2436 | # First step is to fill wanted, the set of revisions that we want to yield. |
|
2441 | # First step is to fill wanted, the set of revisions that we want to yield. | |
2437 | # When it does not induce extra cost, we also fill fncache for revisions in |
|
2442 | # When it does not induce extra cost, we also fill fncache for revisions in | |
2438 | # wanted: a cache of filenames that were changed (ctx.files()) and that |
|
2443 | # wanted: a cache of filenames that were changed (ctx.files()) and that | |
2439 | # match the file filtering conditions. |
|
2444 | # match the file filtering conditions. | |
2440 |
|
2445 | |||
2441 | if match.always() or allfiles: |
|
2446 | if match.always() or allfiles: | |
2442 | # No files, no patterns. Display all revs. |
|
2447 | # No files, no patterns. Display all revs. | |
2443 | wanted = revs |
|
2448 | wanted = revs | |
2444 | elif not slowpath: |
|
2449 | elif not slowpath: | |
2445 | # We only have to read through the filelog to find wanted revisions |
|
2450 | # We only have to read through the filelog to find wanted revisions | |
2446 |
|
2451 | |||
2447 | try: |
|
2452 | try: | |
2448 | wanted = walkfilerevs(repo, match, follow, revs, fncache) |
|
2453 | wanted = walkfilerevs(repo, match, follow, revs, fncache) | |
2449 | except FileWalkError: |
|
2454 | except FileWalkError: | |
2450 | slowpath = True |
|
2455 | slowpath = True | |
2451 |
|
2456 | |||
2452 | # We decided to fall back to the slowpath because at least one |
|
2457 | # We decided to fall back to the slowpath because at least one | |
2453 | # of the paths was not a file. Check to see if at least one of them |
|
2458 | # of the paths was not a file. Check to see if at least one of them | |
2454 | # existed in history, otherwise simply return |
|
2459 | # existed in history, otherwise simply return | |
2455 | for path in match.files(): |
|
2460 | for path in match.files(): | |
2456 | if path == b'.' or path in repo.store: |
|
2461 | if path == b'.' or path in repo.store: | |
2457 | break |
|
2462 | break | |
2458 | else: |
|
2463 | else: | |
2459 | return [] |
|
2464 | return [] | |
2460 |
|
2465 | |||
2461 | if slowpath: |
|
2466 | if slowpath: | |
2462 | # We have to read the changelog to match filenames against |
|
2467 | # We have to read the changelog to match filenames against | |
2463 | # changed files |
|
2468 | # changed files | |
2464 |
|
2469 | |||
2465 | if follow: |
|
2470 | if follow: | |
2466 | raise error.Abort( |
|
2471 | raise error.Abort( | |
2467 | _(b'can only follow copies/renames for explicit filenames') |
|
2472 | _(b'can only follow copies/renames for explicit filenames') | |
2468 | ) |
|
2473 | ) | |
2469 |
|
2474 | |||
2470 | # The slow path checks files modified in every changeset. |
|
2475 | # The slow path checks files modified in every changeset. | |
2471 | # This is really slow on large repos, so compute the set lazily. |
|
2476 | # This is really slow on large repos, so compute the set lazily. | |
2472 | class lazywantedset(object): |
|
2477 | class lazywantedset(object): | |
2473 | def __init__(self): |
|
2478 | def __init__(self): | |
2474 | self.set = set() |
|
2479 | self.set = set() | |
2475 | self.revs = set(revs) |
|
2480 | self.revs = set(revs) | |
2476 |
|
2481 | |||
2477 | # No need to worry about locality here because it will be accessed |
|
2482 | # No need to worry about locality here because it will be accessed | |
2478 | # in the same order as the increasing window below. |
|
2483 | # in the same order as the increasing window below. | |
2479 | def __contains__(self, value): |
|
2484 | def __contains__(self, value): | |
2480 | if value in self.set: |
|
2485 | if value in self.set: | |
2481 | return True |
|
2486 | return True | |
2482 | elif not value in self.revs: |
|
2487 | elif not value in self.revs: | |
2483 | return False |
|
2488 | return False | |
2484 | else: |
|
2489 | else: | |
2485 | self.revs.discard(value) |
|
2490 | self.revs.discard(value) | |
2486 | ctx = change(value) |
|
2491 | ctx = change(value) | |
2487 | if allfiles: |
|
2492 | if allfiles: | |
2488 | matches = list(ctx.manifest().walk(match)) |
|
2493 | matches = list(ctx.manifest().walk(match)) | |
2489 | else: |
|
2494 | else: | |
2490 | matches = [f for f in ctx.files() if match(f)] |
|
2495 | matches = [f for f in ctx.files() if match(f)] | |
2491 | if matches: |
|
2496 | if matches: | |
2492 | fncache[value] = matches |
|
2497 | fncache[value] = matches | |
2493 | self.set.add(value) |
|
2498 | self.set.add(value) | |
2494 | return True |
|
2499 | return True | |
2495 | return False |
|
2500 | return False | |
2496 |
|
2501 | |||
2497 | def discard(self, value): |
|
2502 | def discard(self, value): | |
2498 | self.revs.discard(value) |
|
2503 | self.revs.discard(value) | |
2499 | self.set.discard(value) |
|
2504 | self.set.discard(value) | |
2500 |
|
2505 | |||
2501 | wanted = lazywantedset() |
|
2506 | wanted = lazywantedset() | |
2502 |
|
2507 | |||
2503 | # it might be worthwhile to do this in the iterator if the rev range |
|
2508 | # it might be worthwhile to do this in the iterator if the rev range | |
2504 | # is descending and the prune args are all within that range |
|
2509 | # is descending and the prune args are all within that range | |
2505 | for rev in opts.get(b'prune', ()): |
|
2510 | for rev in opts.get(b'prune', ()): | |
2506 | rev = repo[rev].rev() |
|
2511 | rev = repo[rev].rev() | |
2507 | ff = _followfilter(repo) |
|
2512 | ff = _followfilter(repo) | |
2508 | stop = min(revs[0], revs[-1]) |
|
2513 | stop = min(revs[0], revs[-1]) | |
2509 | for x in pycompat.xrange(rev, stop - 1, -1): |
|
2514 | for x in pycompat.xrange(rev, stop - 1, -1): | |
2510 | if ff.match(x): |
|
2515 | if ff.match(x): | |
2511 | wanted = wanted - [x] |
|
2516 | wanted = wanted - [x] | |
2512 |
|
2517 | |||
2513 | # Now that wanted is correctly initialized, we can iterate over the |
|
2518 | # Now that wanted is correctly initialized, we can iterate over the | |
2514 | # revision range, yielding only revisions in wanted. |
|
2519 | # revision range, yielding only revisions in wanted. | |
2515 | def iterate(): |
|
2520 | def iterate(): | |
2516 | if follow and match.always(): |
|
2521 | if follow and match.always(): | |
2517 | ff = _followfilter(repo, onlyfirst=opts.get(b'follow_first')) |
|
2522 | ff = _followfilter(repo, onlyfirst=opts.get(b'follow_first')) | |
2518 |
|
2523 | |||
2519 | def want(rev): |
|
2524 | def want(rev): | |
2520 | return ff.match(rev) and rev in wanted |
|
2525 | return ff.match(rev) and rev in wanted | |
2521 |
|
2526 | |||
2522 | else: |
|
2527 | else: | |
2523 |
|
2528 | |||
2524 | def want(rev): |
|
2529 | def want(rev): | |
2525 | return rev in wanted |
|
2530 | return rev in wanted | |
2526 |
|
2531 | |||
2527 | it = iter(revs) |
|
2532 | it = iter(revs) | |
2528 | stopiteration = False |
|
2533 | stopiteration = False | |
2529 | for windowsize in increasingwindows(): |
|
2534 | for windowsize in increasingwindows(): | |
2530 | nrevs = [] |
|
2535 | nrevs = [] | |
2531 | for i in pycompat.xrange(windowsize): |
|
2536 | for i in pycompat.xrange(windowsize): | |
2532 | rev = next(it, None) |
|
2537 | rev = next(it, None) | |
2533 | if rev is None: |
|
2538 | if rev is None: | |
2534 | stopiteration = True |
|
2539 | stopiteration = True | |
2535 | break |
|
2540 | break | |
2536 | elif want(rev): |
|
2541 | elif want(rev): | |
2537 | nrevs.append(rev) |
|
2542 | nrevs.append(rev) | |
2538 | for rev in sorted(nrevs): |
|
2543 | for rev in sorted(nrevs): | |
2539 | fns = fncache.get(rev) |
|
2544 | fns = fncache.get(rev) | |
2540 | ctx = change(rev) |
|
2545 | ctx = change(rev) | |
2541 | if not fns: |
|
2546 | if not fns: | |
2542 |
|
2547 | |||
2543 | def fns_generator(): |
|
2548 | def fns_generator(): | |
2544 | if allfiles: |
|
2549 | if allfiles: | |
2545 |
|
2550 | |||
2546 | def bad(f, msg): |
|
2551 | def bad(f, msg): | |
2547 | pass |
|
2552 | pass | |
2548 |
|
2553 | |||
2549 | for f in ctx.matches(matchmod.badmatch(match, bad)): |
|
2554 | for f in ctx.matches(matchmod.badmatch(match, bad)): | |
2550 | yield f |
|
2555 | yield f | |
2551 | else: |
|
2556 | else: | |
2552 | for f in ctx.files(): |
|
2557 | for f in ctx.files(): | |
2553 | if match(f): |
|
2558 | if match(f): | |
2554 | yield f |
|
2559 | yield f | |
2555 |
|
2560 | |||
2556 | fns = fns_generator() |
|
2561 | fns = fns_generator() | |
2557 | prepare(ctx, fns) |
|
2562 | prepare(ctx, fns) | |
2558 | for rev in nrevs: |
|
2563 | for rev in nrevs: | |
2559 | yield change(rev) |
|
2564 | yield change(rev) | |
2560 |
|
2565 | |||
2561 | if stopiteration: |
|
2566 | if stopiteration: | |
2562 | break |
|
2567 | break | |
2563 |
|
2568 | |||
2564 | return iterate() |
|
2569 | return iterate() | |
2565 |
|
2570 | |||
2566 |
|
2571 | |||
2567 | def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts): |
|
2572 | def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts): | |
2568 | bad = [] |
|
2573 | bad = [] | |
2569 |
|
2574 | |||
2570 | badfn = lambda x, y: bad.append(x) or match.bad(x, y) |
|
2575 | badfn = lambda x, y: bad.append(x) or match.bad(x, y) | |
2571 | names = [] |
|
2576 | names = [] | |
2572 | wctx = repo[None] |
|
2577 | wctx = repo[None] | |
2573 | cca = None |
|
2578 | cca = None | |
2574 | abort, warn = scmutil.checkportabilityalert(ui) |
|
2579 | abort, warn = scmutil.checkportabilityalert(ui) | |
2575 | if abort or warn: |
|
2580 | if abort or warn: | |
2576 | cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate) |
|
2581 | cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate) | |
2577 |
|
2582 | |||
2578 | match = repo.narrowmatch(match, includeexact=True) |
|
2583 | match = repo.narrowmatch(match, includeexact=True) | |
2579 | badmatch = matchmod.badmatch(match, badfn) |
|
2584 | badmatch = matchmod.badmatch(match, badfn) | |
2580 | dirstate = repo.dirstate |
|
2585 | dirstate = repo.dirstate | |
2581 | # We don't want to just call wctx.walk here, since it would return a lot of |
|
2586 | # We don't want to just call wctx.walk here, since it would return a lot of | |
2582 | # clean files, which we aren't interested in and takes time. |
|
2587 | # clean files, which we aren't interested in and takes time. | |
2583 | for f in sorted( |
|
2588 | for f in sorted( | |
2584 | dirstate.walk( |
|
2589 | dirstate.walk( | |
2585 | badmatch, |
|
2590 | badmatch, | |
2586 | subrepos=sorted(wctx.substate), |
|
2591 | subrepos=sorted(wctx.substate), | |
2587 | unknown=True, |
|
2592 | unknown=True, | |
2588 | ignored=False, |
|
2593 | ignored=False, | |
2589 | full=False, |
|
2594 | full=False, | |
2590 | ) |
|
2595 | ) | |
2591 | ): |
|
2596 | ): | |
2592 | exact = match.exact(f) |
|
2597 | exact = match.exact(f) | |
2593 | if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f): |
|
2598 | if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f): | |
2594 | if cca: |
|
2599 | if cca: | |
2595 | cca(f) |
|
2600 | cca(f) | |
2596 | names.append(f) |
|
2601 | names.append(f) | |
2597 | if ui.verbose or not exact: |
|
2602 | if ui.verbose or not exact: | |
2598 | ui.status( |
|
2603 | ui.status( | |
2599 | _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added' |
|
2604 | _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added' | |
2600 | ) |
|
2605 | ) | |
2601 |
|
2606 | |||
2602 | for subpath in sorted(wctx.substate): |
|
2607 | for subpath in sorted(wctx.substate): | |
2603 | sub = wctx.sub(subpath) |
|
2608 | sub = wctx.sub(subpath) | |
2604 | try: |
|
2609 | try: | |
2605 | submatch = matchmod.subdirmatcher(subpath, match) |
|
2610 | submatch = matchmod.subdirmatcher(subpath, match) | |
2606 | subprefix = repo.wvfs.reljoin(prefix, subpath) |
|
2611 | subprefix = repo.wvfs.reljoin(prefix, subpath) | |
2607 | subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) |
|
2612 | subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) | |
2608 | if opts.get('subrepos'): |
|
2613 | if opts.get('subrepos'): | |
2609 | bad.extend( |
|
2614 | bad.extend( | |
2610 | sub.add(ui, submatch, subprefix, subuipathfn, False, **opts) |
|
2615 | sub.add(ui, submatch, subprefix, subuipathfn, False, **opts) | |
2611 | ) |
|
2616 | ) | |
2612 | else: |
|
2617 | else: | |
2613 | bad.extend( |
|
2618 | bad.extend( | |
2614 | sub.add(ui, submatch, subprefix, subuipathfn, True, **opts) |
|
2619 | sub.add(ui, submatch, subprefix, subuipathfn, True, **opts) | |
2615 | ) |
|
2620 | ) | |
2616 | except error.LookupError: |
|
2621 | except error.LookupError: | |
2617 | ui.status( |
|
2622 | ui.status( | |
2618 | _(b"skipping missing subrepository: %s\n") % uipathfn(subpath) |
|
2623 | _(b"skipping missing subrepository: %s\n") % uipathfn(subpath) | |
2619 | ) |
|
2624 | ) | |
2620 |
|
2625 | |||
2621 | if not opts.get('dry_run'): |
|
2626 | if not opts.get('dry_run'): | |
2622 | rejected = wctx.add(names, prefix) |
|
2627 | rejected = wctx.add(names, prefix) | |
2623 | bad.extend(f for f in rejected if f in match.files()) |
|
2628 | bad.extend(f for f in rejected if f in match.files()) | |
2624 | return bad |
|
2629 | return bad | |
2625 |
|
2630 | |||
2626 |
|
2631 | |||
2627 | def addwebdirpath(repo, serverpath, webconf): |
|
2632 | def addwebdirpath(repo, serverpath, webconf): | |
2628 | webconf[serverpath] = repo.root |
|
2633 | webconf[serverpath] = repo.root | |
2629 | repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root)) |
|
2634 | repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root)) | |
2630 |
|
2635 | |||
2631 | for r in repo.revs(b'filelog("path:.hgsub")'): |
|
2636 | for r in repo.revs(b'filelog("path:.hgsub")'): | |
2632 | ctx = repo[r] |
|
2637 | ctx = repo[r] | |
2633 | for subpath in ctx.substate: |
|
2638 | for subpath in ctx.substate: | |
2634 | ctx.sub(subpath).addwebdirpath(serverpath, webconf) |
|
2639 | ctx.sub(subpath).addwebdirpath(serverpath, webconf) | |
2635 |
|
2640 | |||
2636 |
|
2641 | |||
2637 | def forget( |
|
2642 | def forget( | |
2638 | ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive |
|
2643 | ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive | |
2639 | ): |
|
2644 | ): | |
2640 | if dryrun and interactive: |
|
2645 | if dryrun and interactive: | |
2641 | raise error.Abort(_(b"cannot specify both --dry-run and --interactive")) |
|
2646 | raise error.Abort(_(b"cannot specify both --dry-run and --interactive")) | |
2642 | bad = [] |
|
2647 | bad = [] | |
2643 | badfn = lambda x, y: bad.append(x) or match.bad(x, y) |
|
2648 | badfn = lambda x, y: bad.append(x) or match.bad(x, y) | |
2644 | wctx = repo[None] |
|
2649 | wctx = repo[None] | |
2645 | forgot = [] |
|
2650 | forgot = [] | |
2646 |
|
2651 | |||
2647 | s = repo.status(match=matchmod.badmatch(match, badfn), clean=True) |
|
2652 | s = repo.status(match=matchmod.badmatch(match, badfn), clean=True) | |
2648 | forget = sorted(s.modified + s.added + s.deleted + s.clean) |
|
2653 | forget = sorted(s.modified + s.added + s.deleted + s.clean) | |
2649 | if explicitonly: |
|
2654 | if explicitonly: | |
2650 | forget = [f for f in forget if match.exact(f)] |
|
2655 | forget = [f for f in forget if match.exact(f)] | |
2651 |
|
2656 | |||
2652 | for subpath in sorted(wctx.substate): |
|
2657 | for subpath in sorted(wctx.substate): | |
2653 | sub = wctx.sub(subpath) |
|
2658 | sub = wctx.sub(subpath) | |
2654 | submatch = matchmod.subdirmatcher(subpath, match) |
|
2659 | submatch = matchmod.subdirmatcher(subpath, match) | |
2655 | subprefix = repo.wvfs.reljoin(prefix, subpath) |
|
2660 | subprefix = repo.wvfs.reljoin(prefix, subpath) | |
2656 | subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) |
|
2661 | subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) | |
2657 | try: |
|
2662 | try: | |
2658 | subbad, subforgot = sub.forget( |
|
2663 | subbad, subforgot = sub.forget( | |
2659 | submatch, |
|
2664 | submatch, | |
2660 | subprefix, |
|
2665 | subprefix, | |
2661 | subuipathfn, |
|
2666 | subuipathfn, | |
2662 | dryrun=dryrun, |
|
2667 | dryrun=dryrun, | |
2663 | interactive=interactive, |
|
2668 | interactive=interactive, | |
2664 | ) |
|
2669 | ) | |
2665 | bad.extend([subpath + b'/' + f for f in subbad]) |
|
2670 | bad.extend([subpath + b'/' + f for f in subbad]) | |
2666 | forgot.extend([subpath + b'/' + f for f in subforgot]) |
|
2671 | forgot.extend([subpath + b'/' + f for f in subforgot]) | |
2667 | except error.LookupError: |
|
2672 | except error.LookupError: | |
2668 | ui.status( |
|
2673 | ui.status( | |
2669 | _(b"skipping missing subrepository: %s\n") % uipathfn(subpath) |
|
2674 | _(b"skipping missing subrepository: %s\n") % uipathfn(subpath) | |
2670 | ) |
|
2675 | ) | |
2671 |
|
2676 | |||
2672 | if not explicitonly: |
|
2677 | if not explicitonly: | |
2673 | for f in match.files(): |
|
2678 | for f in match.files(): | |
2674 | if f not in repo.dirstate and not repo.wvfs.isdir(f): |
|
2679 | if f not in repo.dirstate and not repo.wvfs.isdir(f): | |
2675 | if f not in forgot: |
|
2680 | if f not in forgot: | |
2676 | if repo.wvfs.exists(f): |
|
2681 | if repo.wvfs.exists(f): | |
2677 | # Don't complain if the exact case match wasn't given. |
|
2682 | # Don't complain if the exact case match wasn't given. | |
2678 | # But don't do this until after checking 'forgot', so |
|
2683 | # But don't do this until after checking 'forgot', so | |
2679 | # that subrepo files aren't normalized, and this op is |
|
2684 | # that subrepo files aren't normalized, and this op is | |
2680 | # purely from data cached by the status walk above. |
|
2685 | # purely from data cached by the status walk above. | |
2681 | if repo.dirstate.normalize(f) in repo.dirstate: |
|
2686 | if repo.dirstate.normalize(f) in repo.dirstate: | |
2682 | continue |
|
2687 | continue | |
2683 | ui.warn( |
|
2688 | ui.warn( | |
2684 | _( |
|
2689 | _( | |
2685 | b'not removing %s: ' |
|
2690 | b'not removing %s: ' | |
2686 | b'file is already untracked\n' |
|
2691 | b'file is already untracked\n' | |
2687 | ) |
|
2692 | ) | |
2688 | % uipathfn(f) |
|
2693 | % uipathfn(f) | |
2689 | ) |
|
2694 | ) | |
2690 | bad.append(f) |
|
2695 | bad.append(f) | |
2691 |
|
2696 | |||
2692 | if interactive: |
|
2697 | if interactive: | |
2693 | responses = _( |
|
2698 | responses = _( | |
2694 | b'[Ynsa?]' |
|
2699 | b'[Ynsa?]' | |
2695 | b'$$ &Yes, forget this file' |
|
2700 | b'$$ &Yes, forget this file' | |
2696 | b'$$ &No, skip this file' |
|
2701 | b'$$ &No, skip this file' | |
2697 | b'$$ &Skip remaining files' |
|
2702 | b'$$ &Skip remaining files' | |
2698 | b'$$ Include &all remaining files' |
|
2703 | b'$$ Include &all remaining files' | |
2699 | b'$$ &? (display help)' |
|
2704 | b'$$ &? (display help)' | |
2700 | ) |
|
2705 | ) | |
2701 | for filename in forget[:]: |
|
2706 | for filename in forget[:]: | |
2702 | r = ui.promptchoice( |
|
2707 | r = ui.promptchoice( | |
2703 | _(b'forget %s %s') % (uipathfn(filename), responses) |
|
2708 | _(b'forget %s %s') % (uipathfn(filename), responses) | |
2704 | ) |
|
2709 | ) | |
2705 | if r == 4: # ? |
|
2710 | if r == 4: # ? | |
2706 | while r == 4: |
|
2711 | while r == 4: | |
2707 | for c, t in ui.extractchoices(responses)[1]: |
|
2712 | for c, t in ui.extractchoices(responses)[1]: | |
2708 | ui.write(b'%s - %s\n' % (c, encoding.lower(t))) |
|
2713 | ui.write(b'%s - %s\n' % (c, encoding.lower(t))) | |
2709 | r = ui.promptchoice( |
|
2714 | r = ui.promptchoice( | |
2710 | _(b'forget %s %s') % (uipathfn(filename), responses) |
|
2715 | _(b'forget %s %s') % (uipathfn(filename), responses) | |
2711 | ) |
|
2716 | ) | |
2712 | if r == 0: # yes |
|
2717 | if r == 0: # yes | |
2713 | continue |
|
2718 | continue | |
2714 | elif r == 1: # no |
|
2719 | elif r == 1: # no | |
2715 | forget.remove(filename) |
|
2720 | forget.remove(filename) | |
2716 | elif r == 2: # Skip |
|
2721 | elif r == 2: # Skip | |
2717 | fnindex = forget.index(filename) |
|
2722 | fnindex = forget.index(filename) | |
2718 | del forget[fnindex:] |
|
2723 | del forget[fnindex:] | |
2719 | break |
|
2724 | break | |
2720 | elif r == 3: # All |
|
2725 | elif r == 3: # All | |
2721 | break |
|
2726 | break | |
2722 |
|
2727 | |||
2723 | for f in forget: |
|
2728 | for f in forget: | |
2724 | if ui.verbose or not match.exact(f) or interactive: |
|
2729 | if ui.verbose or not match.exact(f) or interactive: | |
2725 | ui.status( |
|
2730 | ui.status( | |
2726 | _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed' |
|
2731 | _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed' | |
2727 | ) |
|
2732 | ) | |
2728 |
|
2733 | |||
2729 | if not dryrun: |
|
2734 | if not dryrun: | |
2730 | rejected = wctx.forget(forget, prefix) |
|
2735 | rejected = wctx.forget(forget, prefix) | |
2731 | bad.extend(f for f in rejected if f in match.files()) |
|
2736 | bad.extend(f for f in rejected if f in match.files()) | |
2732 | forgot.extend(f for f in forget if f not in rejected) |
|
2737 | forgot.extend(f for f in forget if f not in rejected) | |
2733 | return bad, forgot |
|
2738 | return bad, forgot | |
2734 |
|
2739 | |||
2735 |
|
2740 | |||
2736 | def files(ui, ctx, m, uipathfn, fm, fmt, subrepos): |
|
2741 | def files(ui, ctx, m, uipathfn, fm, fmt, subrepos): | |
2737 | ret = 1 |
|
2742 | ret = 1 | |
2738 |
|
2743 | |||
2739 | needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint() |
|
2744 | needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint() | |
2740 | for f in ctx.matches(m): |
|
2745 | for f in ctx.matches(m): | |
2741 | fm.startitem() |
|
2746 | fm.startitem() | |
2742 | fm.context(ctx=ctx) |
|
2747 | fm.context(ctx=ctx) | |
2743 | if needsfctx: |
|
2748 | if needsfctx: | |
2744 | fc = ctx[f] |
|
2749 | fc = ctx[f] | |
2745 | fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags()) |
|
2750 | fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags()) | |
2746 | fm.data(path=f) |
|
2751 | fm.data(path=f) | |
2747 | fm.plain(fmt % uipathfn(f)) |
|
2752 | fm.plain(fmt % uipathfn(f)) | |
2748 | ret = 0 |
|
2753 | ret = 0 | |
2749 |
|
2754 | |||
2750 | for subpath in sorted(ctx.substate): |
|
2755 | for subpath in sorted(ctx.substate): | |
2751 | submatch = matchmod.subdirmatcher(subpath, m) |
|
2756 | submatch = matchmod.subdirmatcher(subpath, m) | |
2752 | subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) |
|
2757 | subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) | |
2753 | if subrepos or m.exact(subpath) or any(submatch.files()): |
|
2758 | if subrepos or m.exact(subpath) or any(submatch.files()): | |
2754 | sub = ctx.sub(subpath) |
|
2759 | sub = ctx.sub(subpath) | |
2755 | try: |
|
2760 | try: | |
2756 | recurse = m.exact(subpath) or subrepos |
|
2761 | recurse = m.exact(subpath) or subrepos | |
2757 | if ( |
|
2762 | if ( | |
2758 | sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse) |
|
2763 | sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse) | |
2759 | == 0 |
|
2764 | == 0 | |
2760 | ): |
|
2765 | ): | |
2761 | ret = 0 |
|
2766 | ret = 0 | |
2762 | except error.LookupError: |
|
2767 | except error.LookupError: | |
2763 | ui.status( |
|
2768 | ui.status( | |
2764 | _(b"skipping missing subrepository: %s\n") |
|
2769 | _(b"skipping missing subrepository: %s\n") | |
2765 | % uipathfn(subpath) |
|
2770 | % uipathfn(subpath) | |
2766 | ) |
|
2771 | ) | |
2767 |
|
2772 | |||
2768 | return ret |
|
2773 | return ret | |
2769 |
|
2774 | |||
2770 |
|
2775 | |||
2771 | def remove( |
|
2776 | def remove( | |
2772 | ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None |
|
2777 | ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None | |
2773 | ): |
|
2778 | ): | |
2774 | ret = 0 |
|
2779 | ret = 0 | |
2775 | s = repo.status(match=m, clean=True) |
|
2780 | s = repo.status(match=m, clean=True) | |
2776 | modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean |
|
2781 | modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean | |
2777 |
|
2782 | |||
2778 | wctx = repo[None] |
|
2783 | wctx = repo[None] | |
2779 |
|
2784 | |||
2780 | if warnings is None: |
|
2785 | if warnings is None: | |
2781 | warnings = [] |
|
2786 | warnings = [] | |
2782 | warn = True |
|
2787 | warn = True | |
2783 | else: |
|
2788 | else: | |
2784 | warn = False |
|
2789 | warn = False | |
2785 |
|
2790 | |||
2786 | subs = sorted(wctx.substate) |
|
2791 | subs = sorted(wctx.substate) | |
2787 | progress = ui.makeprogress( |
|
2792 | progress = ui.makeprogress( | |
2788 | _(b'searching'), total=len(subs), unit=_(b'subrepos') |
|
2793 | _(b'searching'), total=len(subs), unit=_(b'subrepos') | |
2789 | ) |
|
2794 | ) | |
2790 | for subpath in subs: |
|
2795 | for subpath in subs: | |
2791 | submatch = matchmod.subdirmatcher(subpath, m) |
|
2796 | submatch = matchmod.subdirmatcher(subpath, m) | |
2792 | subprefix = repo.wvfs.reljoin(prefix, subpath) |
|
2797 | subprefix = repo.wvfs.reljoin(prefix, subpath) | |
2793 | subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) |
|
2798 | subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) | |
2794 | if subrepos or m.exact(subpath) or any(submatch.files()): |
|
2799 | if subrepos or m.exact(subpath) or any(submatch.files()): | |
2795 | progress.increment() |
|
2800 | progress.increment() | |
2796 | sub = wctx.sub(subpath) |
|
2801 | sub = wctx.sub(subpath) | |
2797 | try: |
|
2802 | try: | |
2798 | if sub.removefiles( |
|
2803 | if sub.removefiles( | |
2799 | submatch, |
|
2804 | submatch, | |
2800 | subprefix, |
|
2805 | subprefix, | |
2801 | subuipathfn, |
|
2806 | subuipathfn, | |
2802 | after, |
|
2807 | after, | |
2803 | force, |
|
2808 | force, | |
2804 | subrepos, |
|
2809 | subrepos, | |
2805 | dryrun, |
|
2810 | dryrun, | |
2806 | warnings, |
|
2811 | warnings, | |
2807 | ): |
|
2812 | ): | |
2808 | ret = 1 |
|
2813 | ret = 1 | |
2809 | except error.LookupError: |
|
2814 | except error.LookupError: | |
2810 | warnings.append( |
|
2815 | warnings.append( | |
2811 | _(b"skipping missing subrepository: %s\n") |
|
2816 | _(b"skipping missing subrepository: %s\n") | |
2812 | % uipathfn(subpath) |
|
2817 | % uipathfn(subpath) | |
2813 | ) |
|
2818 | ) | |
2814 | progress.complete() |
|
2819 | progress.complete() | |
2815 |
|
2820 | |||
2816 | # warn about failure to delete explicit files/dirs |
|
2821 | # warn about failure to delete explicit files/dirs | |
2817 | deleteddirs = pathutil.dirs(deleted) |
|
2822 | deleteddirs = pathutil.dirs(deleted) | |
2818 | files = m.files() |
|
2823 | files = m.files() | |
2819 | progress = ui.makeprogress( |
|
2824 | progress = ui.makeprogress( | |
2820 | _(b'deleting'), total=len(files), unit=_(b'files') |
|
2825 | _(b'deleting'), total=len(files), unit=_(b'files') | |
2821 | ) |
|
2826 | ) | |
2822 | for f in files: |
|
2827 | for f in files: | |
2823 |
|
2828 | |||
2824 | def insubrepo(): |
|
2829 | def insubrepo(): | |
2825 | for subpath in wctx.substate: |
|
2830 | for subpath in wctx.substate: | |
2826 | if f.startswith(subpath + b'/'): |
|
2831 | if f.startswith(subpath + b'/'): | |
2827 | return True |
|
2832 | return True | |
2828 | return False |
|
2833 | return False | |
2829 |
|
2834 | |||
2830 | progress.increment() |
|
2835 | progress.increment() | |
2831 | isdir = f in deleteddirs or wctx.hasdir(f) |
|
2836 | isdir = f in deleteddirs or wctx.hasdir(f) | |
2832 | if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs: |
|
2837 | if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs: | |
2833 | continue |
|
2838 | continue | |
2834 |
|
2839 | |||
2835 | if repo.wvfs.exists(f): |
|
2840 | if repo.wvfs.exists(f): | |
2836 | if repo.wvfs.isdir(f): |
|
2841 | if repo.wvfs.isdir(f): | |
2837 | warnings.append( |
|
2842 | warnings.append( | |
2838 | _(b'not removing %s: no tracked files\n') % uipathfn(f) |
|
2843 | _(b'not removing %s: no tracked files\n') % uipathfn(f) | |
2839 | ) |
|
2844 | ) | |
2840 | else: |
|
2845 | else: | |
2841 | warnings.append( |
|
2846 | warnings.append( | |
2842 | _(b'not removing %s: file is untracked\n') % uipathfn(f) |
|
2847 | _(b'not removing %s: file is untracked\n') % uipathfn(f) | |
2843 | ) |
|
2848 | ) | |
2844 | # missing files will generate a warning elsewhere |
|
2849 | # missing files will generate a warning elsewhere | |
2845 | ret = 1 |
|
2850 | ret = 1 | |
2846 | progress.complete() |
|
2851 | progress.complete() | |
2847 |
|
2852 | |||
2848 | if force: |
|
2853 | if force: | |
2849 | list = modified + deleted + clean + added |
|
2854 | list = modified + deleted + clean + added | |
2850 | elif after: |
|
2855 | elif after: | |
2851 | list = deleted |
|
2856 | list = deleted | |
2852 | remaining = modified + added + clean |
|
2857 | remaining = modified + added + clean | |
2853 | progress = ui.makeprogress( |
|
2858 | progress = ui.makeprogress( | |
2854 | _(b'skipping'), total=len(remaining), unit=_(b'files') |
|
2859 | _(b'skipping'), total=len(remaining), unit=_(b'files') | |
2855 | ) |
|
2860 | ) | |
2856 | for f in remaining: |
|
2861 | for f in remaining: | |
2857 | progress.increment() |
|
2862 | progress.increment() | |
2858 | if ui.verbose or (f in files): |
|
2863 | if ui.verbose or (f in files): | |
2859 | warnings.append( |
|
2864 | warnings.append( | |
2860 | _(b'not removing %s: file still exists\n') % uipathfn(f) |
|
2865 | _(b'not removing %s: file still exists\n') % uipathfn(f) | |
2861 | ) |
|
2866 | ) | |
2862 | ret = 1 |
|
2867 | ret = 1 | |
2863 | progress.complete() |
|
2868 | progress.complete() | |
2864 | else: |
|
2869 | else: | |
2865 | list = deleted + clean |
|
2870 | list = deleted + clean | |
2866 | progress = ui.makeprogress( |
|
2871 | progress = ui.makeprogress( | |
2867 | _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files') |
|
2872 | _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files') | |
2868 | ) |
|
2873 | ) | |
2869 | for f in modified: |
|
2874 | for f in modified: | |
2870 | progress.increment() |
|
2875 | progress.increment() | |
2871 | warnings.append( |
|
2876 | warnings.append( | |
2872 | _( |
|
2877 | _( | |
2873 | b'not removing %s: file is modified (use -f' |
|
2878 | b'not removing %s: file is modified (use -f' | |
2874 | b' to force removal)\n' |
|
2879 | b' to force removal)\n' | |
2875 | ) |
|
2880 | ) | |
2876 | % uipathfn(f) |
|
2881 | % uipathfn(f) | |
2877 | ) |
|
2882 | ) | |
2878 | ret = 1 |
|
2883 | ret = 1 | |
2879 | for f in added: |
|
2884 | for f in added: | |
2880 | progress.increment() |
|
2885 | progress.increment() | |
2881 | warnings.append( |
|
2886 | warnings.append( | |
2882 | _( |
|
2887 | _( | |
2883 | b"not removing %s: file has been marked for add" |
|
2888 | b"not removing %s: file has been marked for add" | |
2884 | b" (use 'hg forget' to undo add)\n" |
|
2889 | b" (use 'hg forget' to undo add)\n" | |
2885 | ) |
|
2890 | ) | |
2886 | % uipathfn(f) |
|
2891 | % uipathfn(f) | |
2887 | ) |
|
2892 | ) | |
2888 | ret = 1 |
|
2893 | ret = 1 | |
2889 | progress.complete() |
|
2894 | progress.complete() | |
2890 |
|
2895 | |||
2891 | list = sorted(list) |
|
2896 | list = sorted(list) | |
2892 | progress = ui.makeprogress( |
|
2897 | progress = ui.makeprogress( | |
2893 | _(b'deleting'), total=len(list), unit=_(b'files') |
|
2898 | _(b'deleting'), total=len(list), unit=_(b'files') | |
2894 | ) |
|
2899 | ) | |
2895 | for f in list: |
|
2900 | for f in list: | |
2896 | if ui.verbose or not m.exact(f): |
|
2901 | if ui.verbose or not m.exact(f): | |
2897 | progress.increment() |
|
2902 | progress.increment() | |
2898 | ui.status( |
|
2903 | ui.status( | |
2899 | _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed' |
|
2904 | _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed' | |
2900 | ) |
|
2905 | ) | |
2901 | progress.complete() |
|
2906 | progress.complete() | |
2902 |
|
2907 | |||
2903 | if not dryrun: |
|
2908 | if not dryrun: | |
2904 | with repo.wlock(): |
|
2909 | with repo.wlock(): | |
2905 | if not after: |
|
2910 | if not after: | |
2906 | for f in list: |
|
2911 | for f in list: | |
2907 | if f in added: |
|
2912 | if f in added: | |
2908 | continue # we never unlink added files on remove |
|
2913 | continue # we never unlink added files on remove | |
2909 | rmdir = repo.ui.configbool( |
|
2914 | rmdir = repo.ui.configbool( | |
2910 | b'experimental', b'removeemptydirs' |
|
2915 | b'experimental', b'removeemptydirs' | |
2911 | ) |
|
2916 | ) | |
2912 | repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir) |
|
2917 | repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir) | |
2913 | repo[None].forget(list) |
|
2918 | repo[None].forget(list) | |
2914 |
|
2919 | |||
2915 | if warn: |
|
2920 | if warn: | |
2916 | for warning in warnings: |
|
2921 | for warning in warnings: | |
2917 | ui.warn(warning) |
|
2922 | ui.warn(warning) | |
2918 |
|
2923 | |||
2919 | return ret |
|
2924 | return ret | |
2920 |
|
2925 | |||
2921 |
|
2926 | |||
2922 | def _catfmtneedsdata(fm): |
|
2927 | def _catfmtneedsdata(fm): | |
2923 | return not fm.datahint() or b'data' in fm.datahint() |
|
2928 | return not fm.datahint() or b'data' in fm.datahint() | |
2924 |
|
2929 | |||
2925 |
|
2930 | |||
2926 | def _updatecatformatter(fm, ctx, matcher, path, decode): |
|
2931 | def _updatecatformatter(fm, ctx, matcher, path, decode): | |
2927 | """Hook for adding data to the formatter used by ``hg cat``. |
|
2932 | """Hook for adding data to the formatter used by ``hg cat``. | |
2928 |
|
2933 | |||
2929 | Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call |
|
2934 | Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call | |
2930 | this method first.""" |
|
2935 | this method first.""" | |
2931 |
|
2936 | |||
2932 | # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it |
|
2937 | # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it | |
2933 | # wasn't requested. |
|
2938 | # wasn't requested. | |
2934 | data = b'' |
|
2939 | data = b'' | |
2935 | if _catfmtneedsdata(fm): |
|
2940 | if _catfmtneedsdata(fm): | |
2936 | data = ctx[path].data() |
|
2941 | data = ctx[path].data() | |
2937 | if decode: |
|
2942 | if decode: | |
2938 | data = ctx.repo().wwritedata(path, data) |
|
2943 | data = ctx.repo().wwritedata(path, data) | |
2939 | fm.startitem() |
|
2944 | fm.startitem() | |
2940 | fm.context(ctx=ctx) |
|
2945 | fm.context(ctx=ctx) | |
2941 | fm.write(b'data', b'%s', data) |
|
2946 | fm.write(b'data', b'%s', data) | |
2942 | fm.data(path=path) |
|
2947 | fm.data(path=path) | |
2943 |
|
2948 | |||
2944 |
|
2949 | |||
2945 | def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts): |
|
2950 | def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts): | |
2946 | err = 1 |
|
2951 | err = 1 | |
2947 | opts = pycompat.byteskwargs(opts) |
|
2952 | opts = pycompat.byteskwargs(opts) | |
2948 |
|
2953 | |||
2949 | def write(path): |
|
2954 | def write(path): | |
2950 | filename = None |
|
2955 | filename = None | |
2951 | if fntemplate: |
|
2956 | if fntemplate: | |
2952 | filename = makefilename( |
|
2957 | filename = makefilename( | |
2953 | ctx, fntemplate, pathname=os.path.join(prefix, path) |
|
2958 | ctx, fntemplate, pathname=os.path.join(prefix, path) | |
2954 | ) |
|
2959 | ) | |
2955 | # attempt to create the directory if it does not already exist |
|
2960 | # attempt to create the directory if it does not already exist | |
2956 | try: |
|
2961 | try: | |
2957 | os.makedirs(os.path.dirname(filename)) |
|
2962 | os.makedirs(os.path.dirname(filename)) | |
2958 | except OSError: |
|
2963 | except OSError: | |
2959 | pass |
|
2964 | pass | |
2960 | with formatter.maybereopen(basefm, filename) as fm: |
|
2965 | with formatter.maybereopen(basefm, filename) as fm: | |
2961 | _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode')) |
|
2966 | _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode')) | |
2962 |
|
2967 | |||
2963 | # Automation often uses hg cat on single files, so special case it |
|
2968 | # Automation often uses hg cat on single files, so special case it | |
2964 | # for performance to avoid the cost of parsing the manifest. |
|
2969 | # for performance to avoid the cost of parsing the manifest. | |
2965 | if len(matcher.files()) == 1 and not matcher.anypats(): |
|
2970 | if len(matcher.files()) == 1 and not matcher.anypats(): | |
2966 | file = matcher.files()[0] |
|
2971 | file = matcher.files()[0] | |
2967 | mfl = repo.manifestlog |
|
2972 | mfl = repo.manifestlog | |
2968 | mfnode = ctx.manifestnode() |
|
2973 | mfnode = ctx.manifestnode() | |
2969 | try: |
|
2974 | try: | |
2970 | if mfnode and mfl[mfnode].find(file)[0]: |
|
2975 | if mfnode and mfl[mfnode].find(file)[0]: | |
2971 | if _catfmtneedsdata(basefm): |
|
2976 | if _catfmtneedsdata(basefm): | |
2972 | scmutil.prefetchfiles(repo, [ctx.rev()], matcher) |
|
2977 | scmutil.prefetchfiles(repo, [ctx.rev()], matcher) | |
2973 | write(file) |
|
2978 | write(file) | |
2974 | return 0 |
|
2979 | return 0 | |
2975 | except KeyError: |
|
2980 | except KeyError: | |
2976 | pass |
|
2981 | pass | |
2977 |
|
2982 | |||
2978 | if _catfmtneedsdata(basefm): |
|
2983 | if _catfmtneedsdata(basefm): | |
2979 | scmutil.prefetchfiles(repo, [ctx.rev()], matcher) |
|
2984 | scmutil.prefetchfiles(repo, [ctx.rev()], matcher) | |
2980 |
|
2985 | |||
2981 | for abs in ctx.walk(matcher): |
|
2986 | for abs in ctx.walk(matcher): | |
2982 | write(abs) |
|
2987 | write(abs) | |
2983 | err = 0 |
|
2988 | err = 0 | |
2984 |
|
2989 | |||
2985 | uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True) |
|
2990 | uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True) | |
2986 | for subpath in sorted(ctx.substate): |
|
2991 | for subpath in sorted(ctx.substate): | |
2987 | sub = ctx.sub(subpath) |
|
2992 | sub = ctx.sub(subpath) | |
2988 | try: |
|
2993 | try: | |
2989 | submatch = matchmod.subdirmatcher(subpath, matcher) |
|
2994 | submatch = matchmod.subdirmatcher(subpath, matcher) | |
2990 | subprefix = os.path.join(prefix, subpath) |
|
2995 | subprefix = os.path.join(prefix, subpath) | |
2991 | if not sub.cat( |
|
2996 | if not sub.cat( | |
2992 | submatch, |
|
2997 | submatch, | |
2993 | basefm, |
|
2998 | basefm, | |
2994 | fntemplate, |
|
2999 | fntemplate, | |
2995 | subprefix, |
|
3000 | subprefix, | |
2996 | **pycompat.strkwargs(opts) |
|
3001 | **pycompat.strkwargs(opts) | |
2997 | ): |
|
3002 | ): | |
2998 | err = 0 |
|
3003 | err = 0 | |
2999 | except error.RepoLookupError: |
|
3004 | except error.RepoLookupError: | |
3000 | ui.status( |
|
3005 | ui.status( | |
3001 | _(b"skipping missing subrepository: %s\n") % uipathfn(subpath) |
|
3006 | _(b"skipping missing subrepository: %s\n") % uipathfn(subpath) | |
3002 | ) |
|
3007 | ) | |
3003 |
|
3008 | |||
3004 | return err |
|
3009 | return err | |
3005 |
|
3010 | |||
3006 |
|
3011 | |||
3007 | def commit(ui, repo, commitfunc, pats, opts): |
|
3012 | def commit(ui, repo, commitfunc, pats, opts): | |
3008 | '''commit the specified files or all outstanding changes''' |
|
3013 | '''commit the specified files or all outstanding changes''' | |
3009 | date = opts.get(b'date') |
|
3014 | date = opts.get(b'date') | |
3010 | if date: |
|
3015 | if date: | |
3011 | opts[b'date'] = dateutil.parsedate(date) |
|
3016 | opts[b'date'] = dateutil.parsedate(date) | |
3012 | message = logmessage(ui, opts) |
|
3017 | message = logmessage(ui, opts) | |
3013 | matcher = scmutil.match(repo[None], pats, opts) |
|
3018 | matcher = scmutil.match(repo[None], pats, opts) | |
3014 |
|
3019 | |||
3015 | dsguard = None |
|
3020 | dsguard = None | |
3016 | # extract addremove carefully -- this function can be called from a command |
|
3021 | # extract addremove carefully -- this function can be called from a command | |
3017 | # that doesn't support addremove |
|
3022 | # that doesn't support addremove | |
3018 | if opts.get(b'addremove'): |
|
3023 | if opts.get(b'addremove'): | |
3019 | dsguard = dirstateguard.dirstateguard(repo, b'commit') |
|
3024 | dsguard = dirstateguard.dirstateguard(repo, b'commit') | |
3020 | with dsguard or util.nullcontextmanager(): |
|
3025 | with dsguard or util.nullcontextmanager(): | |
3021 | if dsguard: |
|
3026 | if dsguard: | |
3022 | relative = scmutil.anypats(pats, opts) |
|
3027 | relative = scmutil.anypats(pats, opts) | |
3023 | uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative) |
|
3028 | uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative) | |
3024 | if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0: |
|
3029 | if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0: | |
3025 | raise error.Abort( |
|
3030 | raise error.Abort( | |
3026 | _(b"failed to mark all new/missing files as added/removed") |
|
3031 | _(b"failed to mark all new/missing files as added/removed") | |
3027 | ) |
|
3032 | ) | |
3028 |
|
3033 | |||
3029 | return commitfunc(ui, repo, message, matcher, opts) |
|
3034 | return commitfunc(ui, repo, message, matcher, opts) | |
3030 |
|
3035 | |||
3031 |
|
3036 | |||
3032 | def samefile(f, ctx1, ctx2): |
|
3037 | def samefile(f, ctx1, ctx2): | |
3033 | if f in ctx1.manifest(): |
|
3038 | if f in ctx1.manifest(): | |
3034 | a = ctx1.filectx(f) |
|
3039 | a = ctx1.filectx(f) | |
3035 | if f in ctx2.manifest(): |
|
3040 | if f in ctx2.manifest(): | |
3036 | b = ctx2.filectx(f) |
|
3041 | b = ctx2.filectx(f) | |
3037 | return not a.cmp(b) and a.flags() == b.flags() |
|
3042 | return not a.cmp(b) and a.flags() == b.flags() | |
3038 | else: |
|
3043 | else: | |
3039 | return False |
|
3044 | return False | |
3040 | else: |
|
3045 | else: | |
3041 | return f not in ctx2.manifest() |
|
3046 | return f not in ctx2.manifest() | |
3042 |
|
3047 | |||
3043 |
|
3048 | |||
3044 | def amend(ui, repo, old, extra, pats, opts): |
|
3049 | def amend(ui, repo, old, extra, pats, opts): | |
3045 | # avoid cycle context -> subrepo -> cmdutil |
|
3050 | # avoid cycle context -> subrepo -> cmdutil | |
3046 | from . import context |
|
3051 | from . import context | |
3047 |
|
3052 | |||
3048 | # amend will reuse the existing user if not specified, but the obsolete |
|
3053 | # amend will reuse the existing user if not specified, but the obsolete | |
3049 | # marker creation requires that the current user's name is specified. |
|
3054 | # marker creation requires that the current user's name is specified. | |
3050 | if obsolete.isenabled(repo, obsolete.createmarkersopt): |
|
3055 | if obsolete.isenabled(repo, obsolete.createmarkersopt): | |
3051 | ui.username() # raise exception if username not set |
|
3056 | ui.username() # raise exception if username not set | |
3052 |
|
3057 | |||
3053 | ui.note(_(b'amending changeset %s\n') % old) |
|
3058 | ui.note(_(b'amending changeset %s\n') % old) | |
3054 | base = old.p1() |
|
3059 | base = old.p1() | |
3055 |
|
3060 | |||
3056 | with repo.wlock(), repo.lock(), repo.transaction(b'amend'): |
|
3061 | with repo.wlock(), repo.lock(), repo.transaction(b'amend'): | |
3057 | # Participating changesets: |
|
3062 | # Participating changesets: | |
3058 | # |
|
3063 | # | |
3059 | # wctx o - workingctx that contains changes from working copy |
|
3064 | # wctx o - workingctx that contains changes from working copy | |
3060 | # | to go into amending commit |
|
3065 | # | to go into amending commit | |
3061 | # | |
|
3066 | # | | |
3062 | # old o - changeset to amend |
|
3067 | # old o - changeset to amend | |
3063 | # | |
|
3068 | # | | |
3064 | # base o - first parent of the changeset to amend |
|
3069 | # base o - first parent of the changeset to amend | |
3065 | wctx = repo[None] |
|
3070 | wctx = repo[None] | |
3066 |
|
3071 | |||
3067 | # Copy to avoid mutating input |
|
3072 | # Copy to avoid mutating input | |
3068 | extra = extra.copy() |
|
3073 | extra = extra.copy() | |
3069 | # Update extra dict from amended commit (e.g. to preserve graft |
|
3074 | # Update extra dict from amended commit (e.g. to preserve graft | |
3070 | # source) |
|
3075 | # source) | |
3071 | extra.update(old.extra()) |
|
3076 | extra.update(old.extra()) | |
3072 |
|
3077 | |||
3073 | # Also update it from the from the wctx |
|
3078 | # Also update it from the from the wctx | |
3074 | extra.update(wctx.extra()) |
|
3079 | extra.update(wctx.extra()) | |
3075 |
|
3080 | |||
3076 | # date-only change should be ignored? |
|
3081 | # date-only change should be ignored? | |
3077 | datemaydiffer = resolvecommitoptions(ui, opts) |
|
3082 | datemaydiffer = resolvecommitoptions(ui, opts) | |
3078 |
|
3083 | |||
3079 | date = old.date() |
|
3084 | date = old.date() | |
3080 | if opts.get(b'date'): |
|
3085 | if opts.get(b'date'): | |
3081 | date = dateutil.parsedate(opts.get(b'date')) |
|
3086 | date = dateutil.parsedate(opts.get(b'date')) | |
3082 | user = opts.get(b'user') or old.user() |
|
3087 | user = opts.get(b'user') or old.user() | |
3083 |
|
3088 | |||
3084 | if len(old.parents()) > 1: |
|
3089 | if len(old.parents()) > 1: | |
3085 | # ctx.files() isn't reliable for merges, so fall back to the |
|
3090 | # ctx.files() isn't reliable for merges, so fall back to the | |
3086 | # slower repo.status() method |
|
3091 | # slower repo.status() method | |
3087 | st = base.status(old) |
|
3092 | st = base.status(old) | |
3088 | files = set(st.modified) | set(st.added) | set(st.removed) |
|
3093 | files = set(st.modified) | set(st.added) | set(st.removed) | |
3089 | else: |
|
3094 | else: | |
3090 | files = set(old.files()) |
|
3095 | files = set(old.files()) | |
3091 |
|
3096 | |||
3092 | # add/remove the files to the working copy if the "addremove" option |
|
3097 | # add/remove the files to the working copy if the "addremove" option | |
3093 | # was specified. |
|
3098 | # was specified. | |
3094 | matcher = scmutil.match(wctx, pats, opts) |
|
3099 | matcher = scmutil.match(wctx, pats, opts) | |
3095 | relative = scmutil.anypats(pats, opts) |
|
3100 | relative = scmutil.anypats(pats, opts) | |
3096 | uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative) |
|
3101 | uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative) | |
3097 | if opts.get(b'addremove') and scmutil.addremove( |
|
3102 | if opts.get(b'addremove') and scmutil.addremove( | |
3098 | repo, matcher, b"", uipathfn, opts |
|
3103 | repo, matcher, b"", uipathfn, opts | |
3099 | ): |
|
3104 | ): | |
3100 | raise error.Abort( |
|
3105 | raise error.Abort( | |
3101 | _(b"failed to mark all new/missing files as added/removed") |
|
3106 | _(b"failed to mark all new/missing files as added/removed") | |
3102 | ) |
|
3107 | ) | |
3103 |
|
3108 | |||
3104 | # Check subrepos. This depends on in-place wctx._status update in |
|
3109 | # Check subrepos. This depends on in-place wctx._status update in | |
3105 | # subrepo.precommit(). To minimize the risk of this hack, we do |
|
3110 | # subrepo.precommit(). To minimize the risk of this hack, we do | |
3106 | # nothing if .hgsub does not exist. |
|
3111 | # nothing if .hgsub does not exist. | |
3107 | if b'.hgsub' in wctx or b'.hgsub' in old: |
|
3112 | if b'.hgsub' in wctx or b'.hgsub' in old: | |
3108 | subs, commitsubs, newsubstate = subrepoutil.precommit( |
|
3113 | subs, commitsubs, newsubstate = subrepoutil.precommit( | |
3109 | ui, wctx, wctx._status, matcher |
|
3114 | ui, wctx, wctx._status, matcher | |
3110 | ) |
|
3115 | ) | |
3111 | # amend should abort if commitsubrepos is enabled |
|
3116 | # amend should abort if commitsubrepos is enabled | |
3112 | assert not commitsubs |
|
3117 | assert not commitsubs | |
3113 | if subs: |
|
3118 | if subs: | |
3114 | subrepoutil.writestate(repo, newsubstate) |
|
3119 | subrepoutil.writestate(repo, newsubstate) | |
3115 |
|
3120 | |||
3116 | ms = mergemod.mergestate.read(repo) |
|
3121 | ms = mergemod.mergestate.read(repo) | |
3117 | mergeutil.checkunresolved(ms) |
|
3122 | mergeutil.checkunresolved(ms) | |
3118 |
|
3123 | |||
3119 | filestoamend = set(f for f in wctx.files() if matcher(f)) |
|
3124 | filestoamend = set(f for f in wctx.files() if matcher(f)) | |
3120 |
|
3125 | |||
3121 | changes = len(filestoamend) > 0 |
|
3126 | changes = len(filestoamend) > 0 | |
3122 | if changes: |
|
3127 | if changes: | |
3123 | # Recompute copies (avoid recording a -> b -> a) |
|
3128 | # Recompute copies (avoid recording a -> b -> a) | |
3124 | copied = copies.pathcopies(base, wctx, matcher) |
|
3129 | copied = copies.pathcopies(base, wctx, matcher) | |
3125 | if old.p2: |
|
3130 | if old.p2: | |
3126 | copied.update(copies.pathcopies(old.p2(), wctx, matcher)) |
|
3131 | copied.update(copies.pathcopies(old.p2(), wctx, matcher)) | |
3127 |
|
3132 | |||
3128 | # Prune files which were reverted by the updates: if old |
|
3133 | # Prune files which were reverted by the updates: if old | |
3129 | # introduced file X and the file was renamed in the working |
|
3134 | # introduced file X and the file was renamed in the working | |
3130 | # copy, then those two files are the same and |
|
3135 | # copy, then those two files are the same and | |
3131 | # we can discard X from our list of files. Likewise if X |
|
3136 | # we can discard X from our list of files. Likewise if X | |
3132 | # was removed, it's no longer relevant. If X is missing (aka |
|
3137 | # was removed, it's no longer relevant. If X is missing (aka | |
3133 | # deleted), old X must be preserved. |
|
3138 | # deleted), old X must be preserved. | |
3134 | files.update(filestoamend) |
|
3139 | files.update(filestoamend) | |
3135 | files = [ |
|
3140 | files = [ | |
3136 | f |
|
3141 | f | |
3137 | for f in files |
|
3142 | for f in files | |
3138 | if (f not in filestoamend or not samefile(f, wctx, base)) |
|
3143 | if (f not in filestoamend or not samefile(f, wctx, base)) | |
3139 | ] |
|
3144 | ] | |
3140 |
|
3145 | |||
3141 | def filectxfn(repo, ctx_, path): |
|
3146 | def filectxfn(repo, ctx_, path): | |
3142 | try: |
|
3147 | try: | |
3143 | # If the file being considered is not amongst the files |
|
3148 | # If the file being considered is not amongst the files | |
3144 | # to be amended, we should return the file context from the |
|
3149 | # to be amended, we should return the file context from the | |
3145 | # old changeset. This avoids issues when only some files in |
|
3150 | # old changeset. This avoids issues when only some files in | |
3146 | # the working copy are being amended but there are also |
|
3151 | # the working copy are being amended but there are also | |
3147 | # changes to other files from the old changeset. |
|
3152 | # changes to other files from the old changeset. | |
3148 | if path not in filestoamend: |
|
3153 | if path not in filestoamend: | |
3149 | return old.filectx(path) |
|
3154 | return old.filectx(path) | |
3150 |
|
3155 | |||
3151 | # Return None for removed files. |
|
3156 | # Return None for removed files. | |
3152 | if path in wctx.removed(): |
|
3157 | if path in wctx.removed(): | |
3153 | return None |
|
3158 | return None | |
3154 |
|
3159 | |||
3155 | fctx = wctx[path] |
|
3160 | fctx = wctx[path] | |
3156 | flags = fctx.flags() |
|
3161 | flags = fctx.flags() | |
3157 | mctx = context.memfilectx( |
|
3162 | mctx = context.memfilectx( | |
3158 | repo, |
|
3163 | repo, | |
3159 | ctx_, |
|
3164 | ctx_, | |
3160 | fctx.path(), |
|
3165 | fctx.path(), | |
3161 | fctx.data(), |
|
3166 | fctx.data(), | |
3162 | islink=b'l' in flags, |
|
3167 | islink=b'l' in flags, | |
3163 | isexec=b'x' in flags, |
|
3168 | isexec=b'x' in flags, | |
3164 | copysource=copied.get(path), |
|
3169 | copysource=copied.get(path), | |
3165 | ) |
|
3170 | ) | |
3166 | return mctx |
|
3171 | return mctx | |
3167 | except KeyError: |
|
3172 | except KeyError: | |
3168 | return None |
|
3173 | return None | |
3169 |
|
3174 | |||
3170 | else: |
|
3175 | else: | |
3171 | ui.note(_(b'copying changeset %s to %s\n') % (old, base)) |
|
3176 | ui.note(_(b'copying changeset %s to %s\n') % (old, base)) | |
3172 |
|
3177 | |||
3173 | # Use version of files as in the old cset |
|
3178 | # Use version of files as in the old cset | |
3174 | def filectxfn(repo, ctx_, path): |
|
3179 | def filectxfn(repo, ctx_, path): | |
3175 | try: |
|
3180 | try: | |
3176 | return old.filectx(path) |
|
3181 | return old.filectx(path) | |
3177 | except KeyError: |
|
3182 | except KeyError: | |
3178 | return None |
|
3183 | return None | |
3179 |
|
3184 | |||
3180 | # See if we got a message from -m or -l, if not, open the editor with |
|
3185 | # See if we got a message from -m or -l, if not, open the editor with | |
3181 | # the message of the changeset to amend. |
|
3186 | # the message of the changeset to amend. | |
3182 | message = logmessage(ui, opts) |
|
3187 | message = logmessage(ui, opts) | |
3183 |
|
3188 | |||
3184 | editform = mergeeditform(old, b'commit.amend') |
|
3189 | editform = mergeeditform(old, b'commit.amend') | |
3185 |
|
3190 | |||
3186 | if not message: |
|
3191 | if not message: | |
3187 | message = old.description() |
|
3192 | message = old.description() | |
3188 | # Default if message isn't provided and --edit is not passed is to |
|
3193 | # Default if message isn't provided and --edit is not passed is to | |
3189 | # invoke editor, but allow --no-edit. If somehow we don't have any |
|
3194 | # invoke editor, but allow --no-edit. If somehow we don't have any | |
3190 | # description, let's always start the editor. |
|
3195 | # description, let's always start the editor. | |
3191 | doedit = not message or opts.get(b'edit') in [True, None] |
|
3196 | doedit = not message or opts.get(b'edit') in [True, None] | |
3192 | else: |
|
3197 | else: | |
3193 | # Default if message is provided is to not invoke editor, but allow |
|
3198 | # Default if message is provided is to not invoke editor, but allow | |
3194 | # --edit. |
|
3199 | # --edit. | |
3195 | doedit = opts.get(b'edit') is True |
|
3200 | doedit = opts.get(b'edit') is True | |
3196 | editor = getcommiteditor(edit=doedit, editform=editform) |
|
3201 | editor = getcommiteditor(edit=doedit, editform=editform) | |
3197 |
|
3202 | |||
3198 | pureextra = extra.copy() |
|
3203 | pureextra = extra.copy() | |
3199 | extra[b'amend_source'] = old.hex() |
|
3204 | extra[b'amend_source'] = old.hex() | |
3200 |
|
3205 | |||
3201 | new = context.memctx( |
|
3206 | new = context.memctx( | |
3202 | repo, |
|
3207 | repo, | |
3203 | parents=[base.node(), old.p2().node()], |
|
3208 | parents=[base.node(), old.p2().node()], | |
3204 | text=message, |
|
3209 | text=message, | |
3205 | files=files, |
|
3210 | files=files, | |
3206 | filectxfn=filectxfn, |
|
3211 | filectxfn=filectxfn, | |
3207 | user=user, |
|
3212 | user=user, | |
3208 | date=date, |
|
3213 | date=date, | |
3209 | extra=extra, |
|
3214 | extra=extra, | |
3210 | editor=editor, |
|
3215 | editor=editor, | |
3211 | ) |
|
3216 | ) | |
3212 |
|
3217 | |||
3213 | newdesc = changelog.stripdesc(new.description()) |
|
3218 | newdesc = changelog.stripdesc(new.description()) | |
3214 | if ( |
|
3219 | if ( | |
3215 | (not changes) |
|
3220 | (not changes) | |
3216 | and newdesc == old.description() |
|
3221 | and newdesc == old.description() | |
3217 | and user == old.user() |
|
3222 | and user == old.user() | |
3218 | and (date == old.date() or datemaydiffer) |
|
3223 | and (date == old.date() or datemaydiffer) | |
3219 | and pureextra == old.extra() |
|
3224 | and pureextra == old.extra() | |
3220 | ): |
|
3225 | ): | |
3221 | # nothing changed. continuing here would create a new node |
|
3226 | # nothing changed. continuing here would create a new node | |
3222 | # anyway because of the amend_source noise. |
|
3227 | # anyway because of the amend_source noise. | |
3223 | # |
|
3228 | # | |
3224 | # This not what we expect from amend. |
|
3229 | # This not what we expect from amend. | |
3225 | return old.node() |
|
3230 | return old.node() | |
3226 |
|
3231 | |||
3227 | commitphase = None |
|
3232 | commitphase = None | |
3228 | if opts.get(b'secret'): |
|
3233 | if opts.get(b'secret'): | |
3229 | commitphase = phases.secret |
|
3234 | commitphase = phases.secret | |
3230 | newid = repo.commitctx(new) |
|
3235 | newid = repo.commitctx(new) | |
3231 |
|
3236 | |||
3232 | # Reroute the working copy parent to the new changeset |
|
3237 | # Reroute the working copy parent to the new changeset | |
3233 | repo.setparents(newid, nullid) |
|
3238 | repo.setparents(newid, nullid) | |
3234 | mapping = {old.node(): (newid,)} |
|
3239 | mapping = {old.node(): (newid,)} | |
3235 | obsmetadata = None |
|
3240 | obsmetadata = None | |
3236 | if opts.get(b'note'): |
|
3241 | if opts.get(b'note'): | |
3237 | obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])} |
|
3242 | obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])} | |
3238 | backup = ui.configbool(b'rewrite', b'backup-bundle') |
|
3243 | backup = ui.configbool(b'rewrite', b'backup-bundle') | |
3239 | scmutil.cleanupnodes( |
|
3244 | scmutil.cleanupnodes( | |
3240 | repo, |
|
3245 | repo, | |
3241 | mapping, |
|
3246 | mapping, | |
3242 | b'amend', |
|
3247 | b'amend', | |
3243 | metadata=obsmetadata, |
|
3248 | metadata=obsmetadata, | |
3244 | fixphase=True, |
|
3249 | fixphase=True, | |
3245 | targetphase=commitphase, |
|
3250 | targetphase=commitphase, | |
3246 | backup=backup, |
|
3251 | backup=backup, | |
3247 | ) |
|
3252 | ) | |
3248 |
|
3253 | |||
3249 | # Fixing the dirstate because localrepo.commitctx does not update |
|
3254 | # Fixing the dirstate because localrepo.commitctx does not update | |
3250 | # it. This is rather convenient because we did not need to update |
|
3255 | # it. This is rather convenient because we did not need to update | |
3251 | # the dirstate for all the files in the new commit which commitctx |
|
3256 | # the dirstate for all the files in the new commit which commitctx | |
3252 | # could have done if it updated the dirstate. Now, we can |
|
3257 | # could have done if it updated the dirstate. Now, we can | |
3253 | # selectively update the dirstate only for the amended files. |
|
3258 | # selectively update the dirstate only for the amended files. | |
3254 | dirstate = repo.dirstate |
|
3259 | dirstate = repo.dirstate | |
3255 |
|
3260 | |||
3256 | # Update the state of the files which were added and modified in the |
|
3261 | # Update the state of the files which were added and modified in the | |
3257 | # amend to "normal" in the dirstate. We need to use "normallookup" since |
|
3262 | # amend to "normal" in the dirstate. We need to use "normallookup" since | |
3258 | # the files may have changed since the command started; using "normal" |
|
3263 | # the files may have changed since the command started; using "normal" | |
3259 | # would mark them as clean but with uncommitted contents. |
|
3264 | # would mark them as clean but with uncommitted contents. | |
3260 | normalfiles = set(wctx.modified() + wctx.added()) & filestoamend |
|
3265 | normalfiles = set(wctx.modified() + wctx.added()) & filestoamend | |
3261 | for f in normalfiles: |
|
3266 | for f in normalfiles: | |
3262 | dirstate.normallookup(f) |
|
3267 | dirstate.normallookup(f) | |
3263 |
|
3268 | |||
3264 | # Update the state of files which were removed in the amend |
|
3269 | # Update the state of files which were removed in the amend | |
3265 | # to "removed" in the dirstate. |
|
3270 | # to "removed" in the dirstate. | |
3266 | removedfiles = set(wctx.removed()) & filestoamend |
|
3271 | removedfiles = set(wctx.removed()) & filestoamend | |
3267 | for f in removedfiles: |
|
3272 | for f in removedfiles: | |
3268 | dirstate.drop(f) |
|
3273 | dirstate.drop(f) | |
3269 |
|
3274 | |||
3270 | return newid |
|
3275 | return newid | |
3271 |
|
3276 | |||
3272 |
|
3277 | |||
3273 | def commiteditor(repo, ctx, subs, editform=b''): |
|
3278 | def commiteditor(repo, ctx, subs, editform=b''): | |
3274 | if ctx.description(): |
|
3279 | if ctx.description(): | |
3275 | return ctx.description() |
|
3280 | return ctx.description() | |
3276 | return commitforceeditor( |
|
3281 | return commitforceeditor( | |
3277 | repo, ctx, subs, editform=editform, unchangedmessagedetection=True |
|
3282 | repo, ctx, subs, editform=editform, unchangedmessagedetection=True | |
3278 | ) |
|
3283 | ) | |
3279 |
|
3284 | |||
3280 |
|
3285 | |||
3281 | def commitforceeditor( |
|
3286 | def commitforceeditor( | |
3282 | repo, |
|
3287 | repo, | |
3283 | ctx, |
|
3288 | ctx, | |
3284 | subs, |
|
3289 | subs, | |
3285 | finishdesc=None, |
|
3290 | finishdesc=None, | |
3286 | extramsg=None, |
|
3291 | extramsg=None, | |
3287 | editform=b'', |
|
3292 | editform=b'', | |
3288 | unchangedmessagedetection=False, |
|
3293 | unchangedmessagedetection=False, | |
3289 | ): |
|
3294 | ): | |
3290 | if not extramsg: |
|
3295 | if not extramsg: | |
3291 | extramsg = _(b"Leave message empty to abort commit.") |
|
3296 | extramsg = _(b"Leave message empty to abort commit.") | |
3292 |
|
3297 | |||
3293 | forms = [e for e in editform.split(b'.') if e] |
|
3298 | forms = [e for e in editform.split(b'.') if e] | |
3294 | forms.insert(0, b'changeset') |
|
3299 | forms.insert(0, b'changeset') | |
3295 | templatetext = None |
|
3300 | templatetext = None | |
3296 | while forms: |
|
3301 | while forms: | |
3297 | ref = b'.'.join(forms) |
|
3302 | ref = b'.'.join(forms) | |
3298 | if repo.ui.config(b'committemplate', ref): |
|
3303 | if repo.ui.config(b'committemplate', ref): | |
3299 | templatetext = committext = buildcommittemplate( |
|
3304 | templatetext = committext = buildcommittemplate( | |
3300 | repo, ctx, subs, extramsg, ref |
|
3305 | repo, ctx, subs, extramsg, ref | |
3301 | ) |
|
3306 | ) | |
3302 | break |
|
3307 | break | |
3303 | forms.pop() |
|
3308 | forms.pop() | |
3304 | else: |
|
3309 | else: | |
3305 | committext = buildcommittext(repo, ctx, subs, extramsg) |
|
3310 | committext = buildcommittext(repo, ctx, subs, extramsg) | |
3306 |
|
3311 | |||
3307 | # run editor in the repository root |
|
3312 | # run editor in the repository root | |
3308 | olddir = encoding.getcwd() |
|
3313 | olddir = encoding.getcwd() | |
3309 | os.chdir(repo.root) |
|
3314 | os.chdir(repo.root) | |
3310 |
|
3315 | |||
3311 | # make in-memory changes visible to external process |
|
3316 | # make in-memory changes visible to external process | |
3312 | tr = repo.currenttransaction() |
|
3317 | tr = repo.currenttransaction() | |
3313 | repo.dirstate.write(tr) |
|
3318 | repo.dirstate.write(tr) | |
3314 | pending = tr and tr.writepending() and repo.root |
|
3319 | pending = tr and tr.writepending() and repo.root | |
3315 |
|
3320 | |||
3316 | editortext = repo.ui.edit( |
|
3321 | editortext = repo.ui.edit( | |
3317 | committext, |
|
3322 | committext, | |
3318 | ctx.user(), |
|
3323 | ctx.user(), | |
3319 | ctx.extra(), |
|
3324 | ctx.extra(), | |
3320 | editform=editform, |
|
3325 | editform=editform, | |
3321 | pending=pending, |
|
3326 | pending=pending, | |
3322 | repopath=repo.path, |
|
3327 | repopath=repo.path, | |
3323 | action=b'commit', |
|
3328 | action=b'commit', | |
3324 | ) |
|
3329 | ) | |
3325 | text = editortext |
|
3330 | text = editortext | |
3326 |
|
3331 | |||
3327 | # strip away anything below this special string (used for editors that want |
|
3332 | # strip away anything below this special string (used for editors that want | |
3328 | # to display the diff) |
|
3333 | # to display the diff) | |
3329 | stripbelow = re.search(_linebelow, text, flags=re.MULTILINE) |
|
3334 | stripbelow = re.search(_linebelow, text, flags=re.MULTILINE) | |
3330 | if stripbelow: |
|
3335 | if stripbelow: | |
3331 | text = text[: stripbelow.start()] |
|
3336 | text = text[: stripbelow.start()] | |
3332 |
|
3337 | |||
3333 | text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text) |
|
3338 | text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text) | |
3334 | os.chdir(olddir) |
|
3339 | os.chdir(olddir) | |
3335 |
|
3340 | |||
3336 | if finishdesc: |
|
3341 | if finishdesc: | |
3337 | text = finishdesc(text) |
|
3342 | text = finishdesc(text) | |
3338 | if not text.strip(): |
|
3343 | if not text.strip(): | |
3339 | raise error.Abort(_(b"empty commit message")) |
|
3344 | raise error.Abort(_(b"empty commit message")) | |
3340 | if unchangedmessagedetection and editortext == templatetext: |
|
3345 | if unchangedmessagedetection and editortext == templatetext: | |
3341 | raise error.Abort(_(b"commit message unchanged")) |
|
3346 | raise error.Abort(_(b"commit message unchanged")) | |
3342 |
|
3347 | |||
3343 | return text |
|
3348 | return text | |
3344 |
|
3349 | |||
3345 |
|
3350 | |||
3346 | def buildcommittemplate(repo, ctx, subs, extramsg, ref): |
|
3351 | def buildcommittemplate(repo, ctx, subs, extramsg, ref): | |
3347 | ui = repo.ui |
|
3352 | ui = repo.ui | |
3348 | spec = formatter.templatespec(ref, None, None) |
|
3353 | spec = formatter.templatespec(ref, None, None) | |
3349 | t = logcmdutil.changesettemplater(ui, repo, spec) |
|
3354 | t = logcmdutil.changesettemplater(ui, repo, spec) | |
3350 | t.t.cache.update( |
|
3355 | t.t.cache.update( | |
3351 | (k, templater.unquotestring(v)) |
|
3356 | (k, templater.unquotestring(v)) | |
3352 | for k, v in repo.ui.configitems(b'committemplate') |
|
3357 | for k, v in repo.ui.configitems(b'committemplate') | |
3353 | ) |
|
3358 | ) | |
3354 |
|
3359 | |||
3355 | if not extramsg: |
|
3360 | if not extramsg: | |
3356 | extramsg = b'' # ensure that extramsg is string |
|
3361 | extramsg = b'' # ensure that extramsg is string | |
3357 |
|
3362 | |||
3358 | ui.pushbuffer() |
|
3363 | ui.pushbuffer() | |
3359 | t.show(ctx, extramsg=extramsg) |
|
3364 | t.show(ctx, extramsg=extramsg) | |
3360 | return ui.popbuffer() |
|
3365 | return ui.popbuffer() | |
3361 |
|
3366 | |||
3362 |
|
3367 | |||
3363 | def hgprefix(msg): |
|
3368 | def hgprefix(msg): | |
3364 | return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a]) |
|
3369 | return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a]) | |
3365 |
|
3370 | |||
3366 |
|
3371 | |||
3367 | def buildcommittext(repo, ctx, subs, extramsg): |
|
3372 | def buildcommittext(repo, ctx, subs, extramsg): | |
3368 | edittext = [] |
|
3373 | edittext = [] | |
3369 | modified, added, removed = ctx.modified(), ctx.added(), ctx.removed() |
|
3374 | modified, added, removed = ctx.modified(), ctx.added(), ctx.removed() | |
3370 | if ctx.description(): |
|
3375 | if ctx.description(): | |
3371 | edittext.append(ctx.description()) |
|
3376 | edittext.append(ctx.description()) | |
3372 | edittext.append(b"") |
|
3377 | edittext.append(b"") | |
3373 | edittext.append(b"") # Empty line between message and comments. |
|
3378 | edittext.append(b"") # Empty line between message and comments. | |
3374 | edittext.append( |
|
3379 | edittext.append( | |
3375 | hgprefix( |
|
3380 | hgprefix( | |
3376 | _( |
|
3381 | _( | |
3377 | b"Enter commit message." |
|
3382 | b"Enter commit message." | |
3378 | b" Lines beginning with 'HG:' are removed." |
|
3383 | b" Lines beginning with 'HG:' are removed." | |
3379 | ) |
|
3384 | ) | |
3380 | ) |
|
3385 | ) | |
3381 | ) |
|
3386 | ) | |
3382 | edittext.append(hgprefix(extramsg)) |
|
3387 | edittext.append(hgprefix(extramsg)) | |
3383 | edittext.append(b"HG: --") |
|
3388 | edittext.append(b"HG: --") | |
3384 | edittext.append(hgprefix(_(b"user: %s") % ctx.user())) |
|
3389 | edittext.append(hgprefix(_(b"user: %s") % ctx.user())) | |
3385 | if ctx.p2(): |
|
3390 | if ctx.p2(): | |
3386 | edittext.append(hgprefix(_(b"branch merge"))) |
|
3391 | edittext.append(hgprefix(_(b"branch merge"))) | |
3387 | if ctx.branch(): |
|
3392 | if ctx.branch(): | |
3388 | edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch())) |
|
3393 | edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch())) | |
3389 | if bookmarks.isactivewdirparent(repo): |
|
3394 | if bookmarks.isactivewdirparent(repo): | |
3390 | edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark)) |
|
3395 | edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark)) | |
3391 | edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs]) |
|
3396 | edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs]) | |
3392 | edittext.extend([hgprefix(_(b"added %s") % f) for f in added]) |
|
3397 | edittext.extend([hgprefix(_(b"added %s") % f) for f in added]) | |
3393 | edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified]) |
|
3398 | edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified]) | |
3394 | edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed]) |
|
3399 | edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed]) | |
3395 | if not added and not modified and not removed: |
|
3400 | if not added and not modified and not removed: | |
3396 | edittext.append(hgprefix(_(b"no files changed"))) |
|
3401 | edittext.append(hgprefix(_(b"no files changed"))) | |
3397 | edittext.append(b"") |
|
3402 | edittext.append(b"") | |
3398 |
|
3403 | |||
3399 | return b"\n".join(edittext) |
|
3404 | return b"\n".join(edittext) | |
3400 |
|
3405 | |||
3401 |
|
3406 | |||
3402 | def commitstatus(repo, node, branch, bheads=None, opts=None): |
|
3407 | def commitstatus(repo, node, branch, bheads=None, opts=None): | |
3403 | if opts is None: |
|
3408 | if opts is None: | |
3404 | opts = {} |
|
3409 | opts = {} | |
3405 | ctx = repo[node] |
|
3410 | ctx = repo[node] | |
3406 | parents = ctx.parents() |
|
3411 | parents = ctx.parents() | |
3407 |
|
3412 | |||
3408 | if ( |
|
3413 | if ( | |
3409 | not opts.get(b'amend') |
|
3414 | not opts.get(b'amend') | |
3410 | and bheads |
|
3415 | and bheads | |
3411 | and node not in bheads |
|
3416 | and node not in bheads | |
3412 | and not [ |
|
3417 | and not [ | |
3413 | x for x in parents if x.node() in bheads and x.branch() == branch |
|
3418 | x for x in parents if x.node() in bheads and x.branch() == branch | |
3414 | ] |
|
3419 | ] | |
3415 | ): |
|
3420 | ): | |
3416 | repo.ui.status(_(b'created new head\n')) |
|
3421 | repo.ui.status(_(b'created new head\n')) | |
3417 | # The message is not printed for initial roots. For the other |
|
3422 | # The message is not printed for initial roots. For the other | |
3418 | # changesets, it is printed in the following situations: |
|
3423 | # changesets, it is printed in the following situations: | |
3419 | # |
|
3424 | # | |
3420 | # Par column: for the 2 parents with ... |
|
3425 | # Par column: for the 2 parents with ... | |
3421 | # N: null or no parent |
|
3426 | # N: null or no parent | |
3422 | # B: parent is on another named branch |
|
3427 | # B: parent is on another named branch | |
3423 | # C: parent is a regular non head changeset |
|
3428 | # C: parent is a regular non head changeset | |
3424 | # H: parent was a branch head of the current branch |
|
3429 | # H: parent was a branch head of the current branch | |
3425 | # Msg column: whether we print "created new head" message |
|
3430 | # Msg column: whether we print "created new head" message | |
3426 | # In the following, it is assumed that there already exists some |
|
3431 | # In the following, it is assumed that there already exists some | |
3427 | # initial branch heads of the current branch, otherwise nothing is |
|
3432 | # initial branch heads of the current branch, otherwise nothing is | |
3428 | # printed anyway. |
|
3433 | # printed anyway. | |
3429 | # |
|
3434 | # | |
3430 | # Par Msg Comment |
|
3435 | # Par Msg Comment | |
3431 | # N N y additional topo root |
|
3436 | # N N y additional topo root | |
3432 | # |
|
3437 | # | |
3433 | # B N y additional branch root |
|
3438 | # B N y additional branch root | |
3434 | # C N y additional topo head |
|
3439 | # C N y additional topo head | |
3435 | # H N n usual case |
|
3440 | # H N n usual case | |
3436 | # |
|
3441 | # | |
3437 | # B B y weird additional branch root |
|
3442 | # B B y weird additional branch root | |
3438 | # C B y branch merge |
|
3443 | # C B y branch merge | |
3439 | # H B n merge with named branch |
|
3444 | # H B n merge with named branch | |
3440 | # |
|
3445 | # | |
3441 | # C C y additional head from merge |
|
3446 | # C C y additional head from merge | |
3442 | # C H n merge with a head |
|
3447 | # C H n merge with a head | |
3443 | # |
|
3448 | # | |
3444 | # H H n head merge: head count decreases |
|
3449 | # H H n head merge: head count decreases | |
3445 |
|
3450 | |||
3446 | if not opts.get(b'close_branch'): |
|
3451 | if not opts.get(b'close_branch'): | |
3447 | for r in parents: |
|
3452 | for r in parents: | |
3448 | if r.closesbranch() and r.branch() == branch: |
|
3453 | if r.closesbranch() and r.branch() == branch: | |
3449 | repo.ui.status( |
|
3454 | repo.ui.status( | |
3450 | _(b'reopening closed branch head %d\n') % r.rev() |
|
3455 | _(b'reopening closed branch head %d\n') % r.rev() | |
3451 | ) |
|
3456 | ) | |
3452 |
|
3457 | |||
3453 | if repo.ui.debugflag: |
|
3458 | if repo.ui.debugflag: | |
3454 | repo.ui.write( |
|
3459 | repo.ui.write( | |
3455 | _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()) |
|
3460 | _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()) | |
3456 | ) |
|
3461 | ) | |
3457 | elif repo.ui.verbose: |
|
3462 | elif repo.ui.verbose: | |
3458 | repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx)) |
|
3463 | repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx)) | |
3459 |
|
3464 | |||
3460 |
|
3465 | |||
3461 | def postcommitstatus(repo, pats, opts): |
|
3466 | def postcommitstatus(repo, pats, opts): | |
3462 | return repo.status(match=scmutil.match(repo[None], pats, opts)) |
|
3467 | return repo.status(match=scmutil.match(repo[None], pats, opts)) | |
3463 |
|
3468 | |||
3464 |
|
3469 | |||
3465 | def revert(ui, repo, ctx, parents, *pats, **opts): |
|
3470 | def revert(ui, repo, ctx, parents, *pats, **opts): | |
3466 | opts = pycompat.byteskwargs(opts) |
|
3471 | opts = pycompat.byteskwargs(opts) | |
3467 | parent, p2 = parents |
|
3472 | parent, p2 = parents | |
3468 | node = ctx.node() |
|
3473 | node = ctx.node() | |
3469 |
|
3474 | |||
3470 | mf = ctx.manifest() |
|
3475 | mf = ctx.manifest() | |
3471 | if node == p2: |
|
3476 | if node == p2: | |
3472 | parent = p2 |
|
3477 | parent = p2 | |
3473 |
|
3478 | |||
3474 | # need all matching names in dirstate and manifest of target rev, |
|
3479 | # need all matching names in dirstate and manifest of target rev, | |
3475 | # so have to walk both. do not print errors if files exist in one |
|
3480 | # so have to walk both. do not print errors if files exist in one | |
3476 | # but not other. in both cases, filesets should be evaluated against |
|
3481 | # but not other. in both cases, filesets should be evaluated against | |
3477 | # workingctx to get consistent result (issue4497). this means 'set:**' |
|
3482 | # workingctx to get consistent result (issue4497). this means 'set:**' | |
3478 | # cannot be used to select missing files from target rev. |
|
3483 | # cannot be used to select missing files from target rev. | |
3479 |
|
3484 | |||
3480 | # `names` is a mapping for all elements in working copy and target revision |
|
3485 | # `names` is a mapping for all elements in working copy and target revision | |
3481 | # The mapping is in the form: |
|
3486 | # The mapping is in the form: | |
3482 | # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>) |
|
3487 | # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>) | |
3483 | names = {} |
|
3488 | names = {} | |
3484 | uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True) |
|
3489 | uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True) | |
3485 |
|
3490 | |||
3486 | with repo.wlock(): |
|
3491 | with repo.wlock(): | |
3487 | ## filling of the `names` mapping |
|
3492 | ## filling of the `names` mapping | |
3488 | # walk dirstate to fill `names` |
|
3493 | # walk dirstate to fill `names` | |
3489 |
|
3494 | |||
3490 | interactive = opts.get(b'interactive', False) |
|
3495 | interactive = opts.get(b'interactive', False) | |
3491 | wctx = repo[None] |
|
3496 | wctx = repo[None] | |
3492 | m = scmutil.match(wctx, pats, opts) |
|
3497 | m = scmutil.match(wctx, pats, opts) | |
3493 |
|
3498 | |||
3494 | # we'll need this later |
|
3499 | # we'll need this later | |
3495 | targetsubs = sorted(s for s in wctx.substate if m(s)) |
|
3500 | targetsubs = sorted(s for s in wctx.substate if m(s)) | |
3496 |
|
3501 | |||
3497 | if not m.always(): |
|
3502 | if not m.always(): | |
3498 | matcher = matchmod.badmatch(m, lambda x, y: False) |
|
3503 | matcher = matchmod.badmatch(m, lambda x, y: False) | |
3499 | for abs in wctx.walk(matcher): |
|
3504 | for abs in wctx.walk(matcher): | |
3500 | names[abs] = m.exact(abs) |
|
3505 | names[abs] = m.exact(abs) | |
3501 |
|
3506 | |||
3502 | # walk target manifest to fill `names` |
|
3507 | # walk target manifest to fill `names` | |
3503 |
|
3508 | |||
3504 | def badfn(path, msg): |
|
3509 | def badfn(path, msg): | |
3505 | if path in names: |
|
3510 | if path in names: | |
3506 | return |
|
3511 | return | |
3507 | if path in ctx.substate: |
|
3512 | if path in ctx.substate: | |
3508 | return |
|
3513 | return | |
3509 | path_ = path + b'/' |
|
3514 | path_ = path + b'/' | |
3510 | for f in names: |
|
3515 | for f in names: | |
3511 | if f.startswith(path_): |
|
3516 | if f.startswith(path_): | |
3512 | return |
|
3517 | return | |
3513 | ui.warn(b"%s: %s\n" % (uipathfn(path), msg)) |
|
3518 | ui.warn(b"%s: %s\n" % (uipathfn(path), msg)) | |
3514 |
|
3519 | |||
3515 | for abs in ctx.walk(matchmod.badmatch(m, badfn)): |
|
3520 | for abs in ctx.walk(matchmod.badmatch(m, badfn)): | |
3516 | if abs not in names: |
|
3521 | if abs not in names: | |
3517 | names[abs] = m.exact(abs) |
|
3522 | names[abs] = m.exact(abs) | |
3518 |
|
3523 | |||
3519 | # Find status of all file in `names`. |
|
3524 | # Find status of all file in `names`. | |
3520 | m = scmutil.matchfiles(repo, names) |
|
3525 | m = scmutil.matchfiles(repo, names) | |
3521 |
|
3526 | |||
3522 | changes = repo.status( |
|
3527 | changes = repo.status( | |
3523 | node1=node, match=m, unknown=True, ignored=True, clean=True |
|
3528 | node1=node, match=m, unknown=True, ignored=True, clean=True | |
3524 | ) |
|
3529 | ) | |
3525 | else: |
|
3530 | else: | |
3526 | changes = repo.status(node1=node, match=m) |
|
3531 | changes = repo.status(node1=node, match=m) | |
3527 | for kind in changes: |
|
3532 | for kind in changes: | |
3528 | for abs in kind: |
|
3533 | for abs in kind: | |
3529 | names[abs] = m.exact(abs) |
|
3534 | names[abs] = m.exact(abs) | |
3530 |
|
3535 | |||
3531 | m = scmutil.matchfiles(repo, names) |
|
3536 | m = scmutil.matchfiles(repo, names) | |
3532 |
|
3537 | |||
3533 | modified = set(changes.modified) |
|
3538 | modified = set(changes.modified) | |
3534 | added = set(changes.added) |
|
3539 | added = set(changes.added) | |
3535 | removed = set(changes.removed) |
|
3540 | removed = set(changes.removed) | |
3536 | _deleted = set(changes.deleted) |
|
3541 | _deleted = set(changes.deleted) | |
3537 | unknown = set(changes.unknown) |
|
3542 | unknown = set(changes.unknown) | |
3538 | unknown.update(changes.ignored) |
|
3543 | unknown.update(changes.ignored) | |
3539 | clean = set(changes.clean) |
|
3544 | clean = set(changes.clean) | |
3540 | modadded = set() |
|
3545 | modadded = set() | |
3541 |
|
3546 | |||
3542 | # We need to account for the state of the file in the dirstate, |
|
3547 | # We need to account for the state of the file in the dirstate, | |
3543 | # even when we revert against something else than parent. This will |
|
3548 | # even when we revert against something else than parent. This will | |
3544 | # slightly alter the behavior of revert (doing back up or not, delete |
|
3549 | # slightly alter the behavior of revert (doing back up or not, delete | |
3545 | # or just forget etc). |
|
3550 | # or just forget etc). | |
3546 | if parent == node: |
|
3551 | if parent == node: | |
3547 | dsmodified = modified |
|
3552 | dsmodified = modified | |
3548 | dsadded = added |
|
3553 | dsadded = added | |
3549 | dsremoved = removed |
|
3554 | dsremoved = removed | |
3550 | # store all local modifications, useful later for rename detection |
|
3555 | # store all local modifications, useful later for rename detection | |
3551 | localchanges = dsmodified | dsadded |
|
3556 | localchanges = dsmodified | dsadded | |
3552 | modified, added, removed = set(), set(), set() |
|
3557 | modified, added, removed = set(), set(), set() | |
3553 | else: |
|
3558 | else: | |
3554 | changes = repo.status(node1=parent, match=m) |
|
3559 | changes = repo.status(node1=parent, match=m) | |
3555 | dsmodified = set(changes.modified) |
|
3560 | dsmodified = set(changes.modified) | |
3556 | dsadded = set(changes.added) |
|
3561 | dsadded = set(changes.added) | |
3557 | dsremoved = set(changes.removed) |
|
3562 | dsremoved = set(changes.removed) | |
3558 | # store all local modifications, useful later for rename detection |
|
3563 | # store all local modifications, useful later for rename detection | |
3559 | localchanges = dsmodified | dsadded |
|
3564 | localchanges = dsmodified | dsadded | |
3560 |
|
3565 | |||
3561 | # only take into account for removes between wc and target |
|
3566 | # only take into account for removes between wc and target | |
3562 | clean |= dsremoved - removed |
|
3567 | clean |= dsremoved - removed | |
3563 | dsremoved &= removed |
|
3568 | dsremoved &= removed | |
3564 | # distinct between dirstate remove and other |
|
3569 | # distinct between dirstate remove and other | |
3565 | removed -= dsremoved |
|
3570 | removed -= dsremoved | |
3566 |
|
3571 | |||
3567 | modadded = added & dsmodified |
|
3572 | modadded = added & dsmodified | |
3568 | added -= modadded |
|
3573 | added -= modadded | |
3569 |
|
3574 | |||
3570 | # tell newly modified apart. |
|
3575 | # tell newly modified apart. | |
3571 | dsmodified &= modified |
|
3576 | dsmodified &= modified | |
3572 | dsmodified |= modified & dsadded # dirstate added may need backup |
|
3577 | dsmodified |= modified & dsadded # dirstate added may need backup | |
3573 | modified -= dsmodified |
|
3578 | modified -= dsmodified | |
3574 |
|
3579 | |||
3575 | # We need to wait for some post-processing to update this set |
|
3580 | # We need to wait for some post-processing to update this set | |
3576 | # before making the distinction. The dirstate will be used for |
|
3581 | # before making the distinction. The dirstate will be used for | |
3577 | # that purpose. |
|
3582 | # that purpose. | |
3578 | dsadded = added |
|
3583 | dsadded = added | |
3579 |
|
3584 | |||
3580 | # in case of merge, files that are actually added can be reported as |
|
3585 | # in case of merge, files that are actually added can be reported as | |
3581 | # modified, we need to post process the result |
|
3586 | # modified, we need to post process the result | |
3582 | if p2 != nullid: |
|
3587 | if p2 != nullid: | |
3583 | mergeadd = set(dsmodified) |
|
3588 | mergeadd = set(dsmodified) | |
3584 | for path in dsmodified: |
|
3589 | for path in dsmodified: | |
3585 | if path in mf: |
|
3590 | if path in mf: | |
3586 | mergeadd.remove(path) |
|
3591 | mergeadd.remove(path) | |
3587 | dsadded |= mergeadd |
|
3592 | dsadded |= mergeadd | |
3588 | dsmodified -= mergeadd |
|
3593 | dsmodified -= mergeadd | |
3589 |
|
3594 | |||
3590 | # if f is a rename, update `names` to also revert the source |
|
3595 | # if f is a rename, update `names` to also revert the source | |
3591 | for f in localchanges: |
|
3596 | for f in localchanges: | |
3592 | src = repo.dirstate.copied(f) |
|
3597 | src = repo.dirstate.copied(f) | |
3593 | # XXX should we check for rename down to target node? |
|
3598 | # XXX should we check for rename down to target node? | |
3594 | if src and src not in names and repo.dirstate[src] == b'r': |
|
3599 | if src and src not in names and repo.dirstate[src] == b'r': | |
3595 | dsremoved.add(src) |
|
3600 | dsremoved.add(src) | |
3596 | names[src] = True |
|
3601 | names[src] = True | |
3597 |
|
3602 | |||
3598 | # determine the exact nature of the deleted changesets |
|
3603 | # determine the exact nature of the deleted changesets | |
3599 | deladded = set(_deleted) |
|
3604 | deladded = set(_deleted) | |
3600 | for path in _deleted: |
|
3605 | for path in _deleted: | |
3601 | if path in mf: |
|
3606 | if path in mf: | |
3602 | deladded.remove(path) |
|
3607 | deladded.remove(path) | |
3603 | deleted = _deleted - deladded |
|
3608 | deleted = _deleted - deladded | |
3604 |
|
3609 | |||
3605 | # distinguish between file to forget and the other |
|
3610 | # distinguish between file to forget and the other | |
3606 | added = set() |
|
3611 | added = set() | |
3607 | for abs in dsadded: |
|
3612 | for abs in dsadded: | |
3608 | if repo.dirstate[abs] != b'a': |
|
3613 | if repo.dirstate[abs] != b'a': | |
3609 | added.add(abs) |
|
3614 | added.add(abs) | |
3610 | dsadded -= added |
|
3615 | dsadded -= added | |
3611 |
|
3616 | |||
3612 | for abs in deladded: |
|
3617 | for abs in deladded: | |
3613 | if repo.dirstate[abs] == b'a': |
|
3618 | if repo.dirstate[abs] == b'a': | |
3614 | dsadded.add(abs) |
|
3619 | dsadded.add(abs) | |
3615 | deladded -= dsadded |
|
3620 | deladded -= dsadded | |
3616 |
|
3621 | |||
3617 | # For files marked as removed, we check if an unknown file is present at |
|
3622 | # For files marked as removed, we check if an unknown file is present at | |
3618 | # the same path. If a such file exists it may need to be backed up. |
|
3623 | # the same path. If a such file exists it may need to be backed up. | |
3619 | # Making the distinction at this stage helps have simpler backup |
|
3624 | # Making the distinction at this stage helps have simpler backup | |
3620 | # logic. |
|
3625 | # logic. | |
3621 | removunk = set() |
|
3626 | removunk = set() | |
3622 | for abs in removed: |
|
3627 | for abs in removed: | |
3623 | target = repo.wjoin(abs) |
|
3628 | target = repo.wjoin(abs) | |
3624 | if os.path.lexists(target): |
|
3629 | if os.path.lexists(target): | |
3625 | removunk.add(abs) |
|
3630 | removunk.add(abs) | |
3626 | removed -= removunk |
|
3631 | removed -= removunk | |
3627 |
|
3632 | |||
3628 | dsremovunk = set() |
|
3633 | dsremovunk = set() | |
3629 | for abs in dsremoved: |
|
3634 | for abs in dsremoved: | |
3630 | target = repo.wjoin(abs) |
|
3635 | target = repo.wjoin(abs) | |
3631 | if os.path.lexists(target): |
|
3636 | if os.path.lexists(target): | |
3632 | dsremovunk.add(abs) |
|
3637 | dsremovunk.add(abs) | |
3633 | dsremoved -= dsremovunk |
|
3638 | dsremoved -= dsremovunk | |
3634 |
|
3639 | |||
3635 | # action to be actually performed by revert |
|
3640 | # action to be actually performed by revert | |
3636 | # (<list of file>, message>) tuple |
|
3641 | # (<list of file>, message>) tuple | |
3637 | actions = { |
|
3642 | actions = { | |
3638 | b'revert': ([], _(b'reverting %s\n')), |
|
3643 | b'revert': ([], _(b'reverting %s\n')), | |
3639 | b'add': ([], _(b'adding %s\n')), |
|
3644 | b'add': ([], _(b'adding %s\n')), | |
3640 | b'remove': ([], _(b'removing %s\n')), |
|
3645 | b'remove': ([], _(b'removing %s\n')), | |
3641 | b'drop': ([], _(b'removing %s\n')), |
|
3646 | b'drop': ([], _(b'removing %s\n')), | |
3642 | b'forget': ([], _(b'forgetting %s\n')), |
|
3647 | b'forget': ([], _(b'forgetting %s\n')), | |
3643 | b'undelete': ([], _(b'undeleting %s\n')), |
|
3648 | b'undelete': ([], _(b'undeleting %s\n')), | |
3644 | b'noop': (None, _(b'no changes needed to %s\n')), |
|
3649 | b'noop': (None, _(b'no changes needed to %s\n')), | |
3645 | b'unknown': (None, _(b'file not managed: %s\n')), |
|
3650 | b'unknown': (None, _(b'file not managed: %s\n')), | |
3646 | } |
|
3651 | } | |
3647 |
|
3652 | |||
3648 | # "constant" that convey the backup strategy. |
|
3653 | # "constant" that convey the backup strategy. | |
3649 | # All set to `discard` if `no-backup` is set do avoid checking |
|
3654 | # All set to `discard` if `no-backup` is set do avoid checking | |
3650 | # no_backup lower in the code. |
|
3655 | # no_backup lower in the code. | |
3651 | # These values are ordered for comparison purposes |
|
3656 | # These values are ordered for comparison purposes | |
3652 | backupinteractive = 3 # do backup if interactively modified |
|
3657 | backupinteractive = 3 # do backup if interactively modified | |
3653 | backup = 2 # unconditionally do backup |
|
3658 | backup = 2 # unconditionally do backup | |
3654 | check = 1 # check if the existing file differs from target |
|
3659 | check = 1 # check if the existing file differs from target | |
3655 | discard = 0 # never do backup |
|
3660 | discard = 0 # never do backup | |
3656 | if opts.get(b'no_backup'): |
|
3661 | if opts.get(b'no_backup'): | |
3657 | backupinteractive = backup = check = discard |
|
3662 | backupinteractive = backup = check = discard | |
3658 | if interactive: |
|
3663 | if interactive: | |
3659 | dsmodifiedbackup = backupinteractive |
|
3664 | dsmodifiedbackup = backupinteractive | |
3660 | else: |
|
3665 | else: | |
3661 | dsmodifiedbackup = backup |
|
3666 | dsmodifiedbackup = backup | |
3662 | tobackup = set() |
|
3667 | tobackup = set() | |
3663 |
|
3668 | |||
3664 | backupanddel = actions[b'remove'] |
|
3669 | backupanddel = actions[b'remove'] | |
3665 | if not opts.get(b'no_backup'): |
|
3670 | if not opts.get(b'no_backup'): | |
3666 | backupanddel = actions[b'drop'] |
|
3671 | backupanddel = actions[b'drop'] | |
3667 |
|
3672 | |||
3668 | disptable = ( |
|
3673 | disptable = ( | |
3669 | # dispatch table: |
|
3674 | # dispatch table: | |
3670 | # file state |
|
3675 | # file state | |
3671 | # action |
|
3676 | # action | |
3672 | # make backup |
|
3677 | # make backup | |
3673 | ## Sets that results that will change file on disk |
|
3678 | ## Sets that results that will change file on disk | |
3674 | # Modified compared to target, no local change |
|
3679 | # Modified compared to target, no local change | |
3675 | (modified, actions[b'revert'], discard), |
|
3680 | (modified, actions[b'revert'], discard), | |
3676 | # Modified compared to target, but local file is deleted |
|
3681 | # Modified compared to target, but local file is deleted | |
3677 | (deleted, actions[b'revert'], discard), |
|
3682 | (deleted, actions[b'revert'], discard), | |
3678 | # Modified compared to target, local change |
|
3683 | # Modified compared to target, local change | |
3679 | (dsmodified, actions[b'revert'], dsmodifiedbackup), |
|
3684 | (dsmodified, actions[b'revert'], dsmodifiedbackup), | |
3680 | # Added since target |
|
3685 | # Added since target | |
3681 | (added, actions[b'remove'], discard), |
|
3686 | (added, actions[b'remove'], discard), | |
3682 | # Added in working directory |
|
3687 | # Added in working directory | |
3683 | (dsadded, actions[b'forget'], discard), |
|
3688 | (dsadded, actions[b'forget'], discard), | |
3684 | # Added since target, have local modification |
|
3689 | # Added since target, have local modification | |
3685 | (modadded, backupanddel, backup), |
|
3690 | (modadded, backupanddel, backup), | |
3686 | # Added since target but file is missing in working directory |
|
3691 | # Added since target but file is missing in working directory | |
3687 | (deladded, actions[b'drop'], discard), |
|
3692 | (deladded, actions[b'drop'], discard), | |
3688 | # Removed since target, before working copy parent |
|
3693 | # Removed since target, before working copy parent | |
3689 | (removed, actions[b'add'], discard), |
|
3694 | (removed, actions[b'add'], discard), | |
3690 | # Same as `removed` but an unknown file exists at the same path |
|
3695 | # Same as `removed` but an unknown file exists at the same path | |
3691 | (removunk, actions[b'add'], check), |
|
3696 | (removunk, actions[b'add'], check), | |
3692 | # Removed since targe, marked as such in working copy parent |
|
3697 | # Removed since targe, marked as such in working copy parent | |
3693 | (dsremoved, actions[b'undelete'], discard), |
|
3698 | (dsremoved, actions[b'undelete'], discard), | |
3694 | # Same as `dsremoved` but an unknown file exists at the same path |
|
3699 | # Same as `dsremoved` but an unknown file exists at the same path | |
3695 | (dsremovunk, actions[b'undelete'], check), |
|
3700 | (dsremovunk, actions[b'undelete'], check), | |
3696 | ## the following sets does not result in any file changes |
|
3701 | ## the following sets does not result in any file changes | |
3697 | # File with no modification |
|
3702 | # File with no modification | |
3698 | (clean, actions[b'noop'], discard), |
|
3703 | (clean, actions[b'noop'], discard), | |
3699 | # Existing file, not tracked anywhere |
|
3704 | # Existing file, not tracked anywhere | |
3700 | (unknown, actions[b'unknown'], discard), |
|
3705 | (unknown, actions[b'unknown'], discard), | |
3701 | ) |
|
3706 | ) | |
3702 |
|
3707 | |||
3703 | for abs, exact in sorted(names.items()): |
|
3708 | for abs, exact in sorted(names.items()): | |
3704 | # target file to be touch on disk (relative to cwd) |
|
3709 | # target file to be touch on disk (relative to cwd) | |
3705 | target = repo.wjoin(abs) |
|
3710 | target = repo.wjoin(abs) | |
3706 | # search the entry in the dispatch table. |
|
3711 | # search the entry in the dispatch table. | |
3707 | # if the file is in any of these sets, it was touched in the working |
|
3712 | # if the file is in any of these sets, it was touched in the working | |
3708 | # directory parent and we are sure it needs to be reverted. |
|
3713 | # directory parent and we are sure it needs to be reverted. | |
3709 | for table, (xlist, msg), dobackup in disptable: |
|
3714 | for table, (xlist, msg), dobackup in disptable: | |
3710 | if abs not in table: |
|
3715 | if abs not in table: | |
3711 | continue |
|
3716 | continue | |
3712 | if xlist is not None: |
|
3717 | if xlist is not None: | |
3713 | xlist.append(abs) |
|
3718 | xlist.append(abs) | |
3714 | if dobackup: |
|
3719 | if dobackup: | |
3715 | # If in interactive mode, don't automatically create |
|
3720 | # If in interactive mode, don't automatically create | |
3716 | # .orig files (issue4793) |
|
3721 | # .orig files (issue4793) | |
3717 | if dobackup == backupinteractive: |
|
3722 | if dobackup == backupinteractive: | |
3718 | tobackup.add(abs) |
|
3723 | tobackup.add(abs) | |
3719 | elif backup <= dobackup or wctx[abs].cmp(ctx[abs]): |
|
3724 | elif backup <= dobackup or wctx[abs].cmp(ctx[abs]): | |
3720 | absbakname = scmutil.backuppath(ui, repo, abs) |
|
3725 | absbakname = scmutil.backuppath(ui, repo, abs) | |
3721 | bakname = os.path.relpath( |
|
3726 | bakname = os.path.relpath( | |
3722 | absbakname, start=repo.root |
|
3727 | absbakname, start=repo.root | |
3723 | ) |
|
3728 | ) | |
3724 | ui.note( |
|
3729 | ui.note( | |
3725 | _(b'saving current version of %s as %s\n') |
|
3730 | _(b'saving current version of %s as %s\n') | |
3726 | % (uipathfn(abs), uipathfn(bakname)) |
|
3731 | % (uipathfn(abs), uipathfn(bakname)) | |
3727 | ) |
|
3732 | ) | |
3728 | if not opts.get(b'dry_run'): |
|
3733 | if not opts.get(b'dry_run'): | |
3729 | if interactive: |
|
3734 | if interactive: | |
3730 | util.copyfile(target, absbakname) |
|
3735 | util.copyfile(target, absbakname) | |
3731 | else: |
|
3736 | else: | |
3732 | util.rename(target, absbakname) |
|
3737 | util.rename(target, absbakname) | |
3733 | if opts.get(b'dry_run'): |
|
3738 | if opts.get(b'dry_run'): | |
3734 | if ui.verbose or not exact: |
|
3739 | if ui.verbose or not exact: | |
3735 | ui.status(msg % uipathfn(abs)) |
|
3740 | ui.status(msg % uipathfn(abs)) | |
3736 | elif exact: |
|
3741 | elif exact: | |
3737 | ui.warn(msg % uipathfn(abs)) |
|
3742 | ui.warn(msg % uipathfn(abs)) | |
3738 | break |
|
3743 | break | |
3739 |
|
3744 | |||
3740 | if not opts.get(b'dry_run'): |
|
3745 | if not opts.get(b'dry_run'): | |
3741 | needdata = (b'revert', b'add', b'undelete') |
|
3746 | needdata = (b'revert', b'add', b'undelete') | |
3742 | oplist = [actions[name][0] for name in needdata] |
|
3747 | oplist = [actions[name][0] for name in needdata] | |
3743 | prefetch = scmutil.prefetchfiles |
|
3748 | prefetch = scmutil.prefetchfiles | |
3744 | matchfiles = scmutil.matchfiles |
|
3749 | matchfiles = scmutil.matchfiles | |
3745 | prefetch( |
|
3750 | prefetch( | |
3746 | repo, |
|
3751 | repo, | |
3747 | [ctx.rev()], |
|
3752 | [ctx.rev()], | |
3748 | matchfiles(repo, [f for sublist in oplist for f in sublist]), |
|
3753 | matchfiles(repo, [f for sublist in oplist for f in sublist]), | |
3749 | ) |
|
3754 | ) | |
3750 | match = scmutil.match(repo[None], pats) |
|
3755 | match = scmutil.match(repo[None], pats) | |
3751 | _performrevert( |
|
3756 | _performrevert( | |
3752 | repo, |
|
3757 | repo, | |
3753 | parents, |
|
3758 | parents, | |
3754 | ctx, |
|
3759 | ctx, | |
3755 | names, |
|
3760 | names, | |
3756 | uipathfn, |
|
3761 | uipathfn, | |
3757 | actions, |
|
3762 | actions, | |
3758 | match, |
|
3763 | match, | |
3759 | interactive, |
|
3764 | interactive, | |
3760 | tobackup, |
|
3765 | tobackup, | |
3761 | ) |
|
3766 | ) | |
3762 |
|
3767 | |||
3763 | if targetsubs: |
|
3768 | if targetsubs: | |
3764 | # Revert the subrepos on the revert list |
|
3769 | # Revert the subrepos on the revert list | |
3765 | for sub in targetsubs: |
|
3770 | for sub in targetsubs: | |
3766 | try: |
|
3771 | try: | |
3767 | wctx.sub(sub).revert( |
|
3772 | wctx.sub(sub).revert( | |
3768 | ctx.substate[sub], *pats, **pycompat.strkwargs(opts) |
|
3773 | ctx.substate[sub], *pats, **pycompat.strkwargs(opts) | |
3769 | ) |
|
3774 | ) | |
3770 | except KeyError: |
|
3775 | except KeyError: | |
3771 | raise error.Abort( |
|
3776 | raise error.Abort( | |
3772 | b"subrepository '%s' does not exist in %s!" |
|
3777 | b"subrepository '%s' does not exist in %s!" | |
3773 | % (sub, short(ctx.node())) |
|
3778 | % (sub, short(ctx.node())) | |
3774 | ) |
|
3779 | ) | |
3775 |
|
3780 | |||
3776 |
|
3781 | |||
3777 | def _performrevert( |
|
3782 | def _performrevert( | |
3778 | repo, |
|
3783 | repo, | |
3779 | parents, |
|
3784 | parents, | |
3780 | ctx, |
|
3785 | ctx, | |
3781 | names, |
|
3786 | names, | |
3782 | uipathfn, |
|
3787 | uipathfn, | |
3783 | actions, |
|
3788 | actions, | |
3784 | match, |
|
3789 | match, | |
3785 | interactive=False, |
|
3790 | interactive=False, | |
3786 | tobackup=None, |
|
3791 | tobackup=None, | |
3787 | ): |
|
3792 | ): | |
3788 | """function that actually perform all the actions computed for revert |
|
3793 | """function that actually perform all the actions computed for revert | |
3789 |
|
3794 | |||
3790 | This is an independent function to let extension to plug in and react to |
|
3795 | This is an independent function to let extension to plug in and react to | |
3791 | the imminent revert. |
|
3796 | the imminent revert. | |
3792 |
|
3797 | |||
3793 | Make sure you have the working directory locked when calling this function. |
|
3798 | Make sure you have the working directory locked when calling this function. | |
3794 | """ |
|
3799 | """ | |
3795 | parent, p2 = parents |
|
3800 | parent, p2 = parents | |
3796 | node = ctx.node() |
|
3801 | node = ctx.node() | |
3797 | excluded_files = [] |
|
3802 | excluded_files = [] | |
3798 |
|
3803 | |||
3799 | def checkout(f): |
|
3804 | def checkout(f): | |
3800 | fc = ctx[f] |
|
3805 | fc = ctx[f] | |
3801 | repo.wwrite(f, fc.data(), fc.flags()) |
|
3806 | repo.wwrite(f, fc.data(), fc.flags()) | |
3802 |
|
3807 | |||
3803 | def doremove(f): |
|
3808 | def doremove(f): | |
3804 | try: |
|
3809 | try: | |
3805 | rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs') |
|
3810 | rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs') | |
3806 | repo.wvfs.unlinkpath(f, rmdir=rmdir) |
|
3811 | repo.wvfs.unlinkpath(f, rmdir=rmdir) | |
3807 | except OSError: |
|
3812 | except OSError: | |
3808 | pass |
|
3813 | pass | |
3809 | repo.dirstate.remove(f) |
|
3814 | repo.dirstate.remove(f) | |
3810 |
|
3815 | |||
3811 | def prntstatusmsg(action, f): |
|
3816 | def prntstatusmsg(action, f): | |
3812 | exact = names[f] |
|
3817 | exact = names[f] | |
3813 | if repo.ui.verbose or not exact: |
|
3818 | if repo.ui.verbose or not exact: | |
3814 | repo.ui.status(actions[action][1] % uipathfn(f)) |
|
3819 | repo.ui.status(actions[action][1] % uipathfn(f)) | |
3815 |
|
3820 | |||
3816 | audit_path = pathutil.pathauditor(repo.root, cached=True) |
|
3821 | audit_path = pathutil.pathauditor(repo.root, cached=True) | |
3817 | for f in actions[b'forget'][0]: |
|
3822 | for f in actions[b'forget'][0]: | |
3818 | if interactive: |
|
3823 | if interactive: | |
3819 | choice = repo.ui.promptchoice( |
|
3824 | choice = repo.ui.promptchoice( | |
3820 | _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f) |
|
3825 | _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f) | |
3821 | ) |
|
3826 | ) | |
3822 | if choice == 0: |
|
3827 | if choice == 0: | |
3823 | prntstatusmsg(b'forget', f) |
|
3828 | prntstatusmsg(b'forget', f) | |
3824 | repo.dirstate.drop(f) |
|
3829 | repo.dirstate.drop(f) | |
3825 | else: |
|
3830 | else: | |
3826 | excluded_files.append(f) |
|
3831 | excluded_files.append(f) | |
3827 | else: |
|
3832 | else: | |
3828 | prntstatusmsg(b'forget', f) |
|
3833 | prntstatusmsg(b'forget', f) | |
3829 | repo.dirstate.drop(f) |
|
3834 | repo.dirstate.drop(f) | |
3830 | for f in actions[b'remove'][0]: |
|
3835 | for f in actions[b'remove'][0]: | |
3831 | audit_path(f) |
|
3836 | audit_path(f) | |
3832 | if interactive: |
|
3837 | if interactive: | |
3833 | choice = repo.ui.promptchoice( |
|
3838 | choice = repo.ui.promptchoice( | |
3834 | _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f) |
|
3839 | _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f) | |
3835 | ) |
|
3840 | ) | |
3836 | if choice == 0: |
|
3841 | if choice == 0: | |
3837 | prntstatusmsg(b'remove', f) |
|
3842 | prntstatusmsg(b'remove', f) | |
3838 | doremove(f) |
|
3843 | doremove(f) | |
3839 | else: |
|
3844 | else: | |
3840 | excluded_files.append(f) |
|
3845 | excluded_files.append(f) | |
3841 | else: |
|
3846 | else: | |
3842 | prntstatusmsg(b'remove', f) |
|
3847 | prntstatusmsg(b'remove', f) | |
3843 | doremove(f) |
|
3848 | doremove(f) | |
3844 | for f in actions[b'drop'][0]: |
|
3849 | for f in actions[b'drop'][0]: | |
3845 | audit_path(f) |
|
3850 | audit_path(f) | |
3846 | prntstatusmsg(b'drop', f) |
|
3851 | prntstatusmsg(b'drop', f) | |
3847 | repo.dirstate.remove(f) |
|
3852 | repo.dirstate.remove(f) | |
3848 |
|
3853 | |||
3849 | normal = None |
|
3854 | normal = None | |
3850 | if node == parent: |
|
3855 | if node == parent: | |
3851 | # We're reverting to our parent. If possible, we'd like status |
|
3856 | # We're reverting to our parent. If possible, we'd like status | |
3852 | # to report the file as clean. We have to use normallookup for |
|
3857 | # to report the file as clean. We have to use normallookup for | |
3853 | # merges to avoid losing information about merged/dirty files. |
|
3858 | # merges to avoid losing information about merged/dirty files. | |
3854 | if p2 != nullid: |
|
3859 | if p2 != nullid: | |
3855 | normal = repo.dirstate.normallookup |
|
3860 | normal = repo.dirstate.normallookup | |
3856 | else: |
|
3861 | else: | |
3857 | normal = repo.dirstate.normal |
|
3862 | normal = repo.dirstate.normal | |
3858 |
|
3863 | |||
3859 | newlyaddedandmodifiedfiles = set() |
|
3864 | newlyaddedandmodifiedfiles = set() | |
3860 | if interactive: |
|
3865 | if interactive: | |
3861 | # Prompt the user for changes to revert |
|
3866 | # Prompt the user for changes to revert | |
3862 | torevert = [f for f in actions[b'revert'][0] if f not in excluded_files] |
|
3867 | torevert = [f for f in actions[b'revert'][0] if f not in excluded_files] | |
3863 | m = scmutil.matchfiles(repo, torevert) |
|
3868 | m = scmutil.matchfiles(repo, torevert) | |
3864 | diffopts = patch.difffeatureopts( |
|
3869 | diffopts = patch.difffeatureopts( | |
3865 | repo.ui, |
|
3870 | repo.ui, | |
3866 | whitespace=True, |
|
3871 | whitespace=True, | |
3867 | section=b'commands', |
|
3872 | section=b'commands', | |
3868 | configprefix=b'revert.interactive.', |
|
3873 | configprefix=b'revert.interactive.', | |
3869 | ) |
|
3874 | ) | |
3870 | diffopts.nodates = True |
|
3875 | diffopts.nodates = True | |
3871 | diffopts.git = True |
|
3876 | diffopts.git = True | |
3872 | operation = b'apply' |
|
3877 | operation = b'apply' | |
3873 | if node == parent: |
|
3878 | if node == parent: | |
3874 | if repo.ui.configbool( |
|
3879 | if repo.ui.configbool( | |
3875 | b'experimental', b'revert.interactive.select-to-keep' |
|
3880 | b'experimental', b'revert.interactive.select-to-keep' | |
3876 | ): |
|
3881 | ): | |
3877 | operation = b'keep' |
|
3882 | operation = b'keep' | |
3878 | else: |
|
3883 | else: | |
3879 | operation = b'discard' |
|
3884 | operation = b'discard' | |
3880 |
|
3885 | |||
3881 | if operation == b'apply': |
|
3886 | if operation == b'apply': | |
3882 | diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts) |
|
3887 | diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts) | |
3883 | else: |
|
3888 | else: | |
3884 | diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts) |
|
3889 | diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts) | |
3885 | originalchunks = patch.parsepatch(diff) |
|
3890 | originalchunks = patch.parsepatch(diff) | |
3886 |
|
3891 | |||
3887 | try: |
|
3892 | try: | |
3888 |
|
3893 | |||
3889 | chunks, opts = recordfilter( |
|
3894 | chunks, opts = recordfilter( | |
3890 | repo.ui, originalchunks, match, operation=operation |
|
3895 | repo.ui, originalchunks, match, operation=operation | |
3891 | ) |
|
3896 | ) | |
3892 | if operation == b'discard': |
|
3897 | if operation == b'discard': | |
3893 | chunks = patch.reversehunks(chunks) |
|
3898 | chunks = patch.reversehunks(chunks) | |
3894 |
|
3899 | |||
3895 | except error.PatchError as err: |
|
3900 | except error.PatchError as err: | |
3896 | raise error.Abort(_(b'error parsing patch: %s') % err) |
|
3901 | raise error.Abort(_(b'error parsing patch: %s') % err) | |
3897 |
|
3902 | |||
3898 | # FIXME: when doing an interactive revert of a copy, there's no way of |
|
3903 | # FIXME: when doing an interactive revert of a copy, there's no way of | |
3899 | # performing a partial revert of the added file, the only option is |
|
3904 | # performing a partial revert of the added file, the only option is | |
3900 | # "remove added file <name> (Yn)?", so we don't need to worry about the |
|
3905 | # "remove added file <name> (Yn)?", so we don't need to worry about the | |
3901 | # alsorestore value. Ideally we'd be able to partially revert |
|
3906 | # alsorestore value. Ideally we'd be able to partially revert | |
3902 | # copied/renamed files. |
|
3907 | # copied/renamed files. | |
3903 | newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified( |
|
3908 | newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified( | |
3904 | chunks, originalchunks |
|
3909 | chunks, originalchunks | |
3905 | ) |
|
3910 | ) | |
3906 | if tobackup is None: |
|
3911 | if tobackup is None: | |
3907 | tobackup = set() |
|
3912 | tobackup = set() | |
3908 | # Apply changes |
|
3913 | # Apply changes | |
3909 | fp = stringio() |
|
3914 | fp = stringio() | |
3910 | # chunks are serialized per file, but files aren't sorted |
|
3915 | # chunks are serialized per file, but files aren't sorted | |
3911 | for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))): |
|
3916 | for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))): | |
3912 | prntstatusmsg(b'revert', f) |
|
3917 | prntstatusmsg(b'revert', f) | |
3913 | files = set() |
|
3918 | files = set() | |
3914 | for c in chunks: |
|
3919 | for c in chunks: | |
3915 | if ishunk(c): |
|
3920 | if ishunk(c): | |
3916 | abs = c.header.filename() |
|
3921 | abs = c.header.filename() | |
3917 | # Create a backup file only if this hunk should be backed up |
|
3922 | # Create a backup file only if this hunk should be backed up | |
3918 | if c.header.filename() in tobackup: |
|
3923 | if c.header.filename() in tobackup: | |
3919 | target = repo.wjoin(abs) |
|
3924 | target = repo.wjoin(abs) | |
3920 | bakname = scmutil.backuppath(repo.ui, repo, abs) |
|
3925 | bakname = scmutil.backuppath(repo.ui, repo, abs) | |
3921 | util.copyfile(target, bakname) |
|
3926 | util.copyfile(target, bakname) | |
3922 | tobackup.remove(abs) |
|
3927 | tobackup.remove(abs) | |
3923 | if abs not in files: |
|
3928 | if abs not in files: | |
3924 | files.add(abs) |
|
3929 | files.add(abs) | |
3925 | if operation == b'keep': |
|
3930 | if operation == b'keep': | |
3926 | checkout(abs) |
|
3931 | checkout(abs) | |
3927 | c.write(fp) |
|
3932 | c.write(fp) | |
3928 | dopatch = fp.tell() |
|
3933 | dopatch = fp.tell() | |
3929 | fp.seek(0) |
|
3934 | fp.seek(0) | |
3930 | if dopatch: |
|
3935 | if dopatch: | |
3931 | try: |
|
3936 | try: | |
3932 | patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None) |
|
3937 | patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None) | |
3933 | except error.PatchError as err: |
|
3938 | except error.PatchError as err: | |
3934 | raise error.Abort(pycompat.bytestr(err)) |
|
3939 | raise error.Abort(pycompat.bytestr(err)) | |
3935 | del fp |
|
3940 | del fp | |
3936 | else: |
|
3941 | else: | |
3937 | for f in actions[b'revert'][0]: |
|
3942 | for f in actions[b'revert'][0]: | |
3938 | prntstatusmsg(b'revert', f) |
|
3943 | prntstatusmsg(b'revert', f) | |
3939 | checkout(f) |
|
3944 | checkout(f) | |
3940 | if normal: |
|
3945 | if normal: | |
3941 | normal(f) |
|
3946 | normal(f) | |
3942 |
|
3947 | |||
3943 | for f in actions[b'add'][0]: |
|
3948 | for f in actions[b'add'][0]: | |
3944 | # Don't checkout modified files, they are already created by the diff |
|
3949 | # Don't checkout modified files, they are already created by the diff | |
3945 | if f not in newlyaddedandmodifiedfiles: |
|
3950 | if f not in newlyaddedandmodifiedfiles: | |
3946 | prntstatusmsg(b'add', f) |
|
3951 | prntstatusmsg(b'add', f) | |
3947 | checkout(f) |
|
3952 | checkout(f) | |
3948 | repo.dirstate.add(f) |
|
3953 | repo.dirstate.add(f) | |
3949 |
|
3954 | |||
3950 | normal = repo.dirstate.normallookup |
|
3955 | normal = repo.dirstate.normallookup | |
3951 | if node == parent and p2 == nullid: |
|
3956 | if node == parent and p2 == nullid: | |
3952 | normal = repo.dirstate.normal |
|
3957 | normal = repo.dirstate.normal | |
3953 | for f in actions[b'undelete'][0]: |
|
3958 | for f in actions[b'undelete'][0]: | |
3954 | if interactive: |
|
3959 | if interactive: | |
3955 | choice = repo.ui.promptchoice( |
|
3960 | choice = repo.ui.promptchoice( | |
3956 | _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f |
|
3961 | _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f | |
3957 | ) |
|
3962 | ) | |
3958 | if choice == 0: |
|
3963 | if choice == 0: | |
3959 | prntstatusmsg(b'undelete', f) |
|
3964 | prntstatusmsg(b'undelete', f) | |
3960 | checkout(f) |
|
3965 | checkout(f) | |
3961 | normal(f) |
|
3966 | normal(f) | |
3962 | else: |
|
3967 | else: | |
3963 | excluded_files.append(f) |
|
3968 | excluded_files.append(f) | |
3964 | else: |
|
3969 | else: | |
3965 | prntstatusmsg(b'undelete', f) |
|
3970 | prntstatusmsg(b'undelete', f) | |
3966 | checkout(f) |
|
3971 | checkout(f) | |
3967 | normal(f) |
|
3972 | normal(f) | |
3968 |
|
3973 | |||
3969 | copied = copies.pathcopies(repo[parent], ctx) |
|
3974 | copied = copies.pathcopies(repo[parent], ctx) | |
3970 |
|
3975 | |||
3971 | for f in ( |
|
3976 | for f in ( | |
3972 | actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0] |
|
3977 | actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0] | |
3973 | ): |
|
3978 | ): | |
3974 | if f in copied: |
|
3979 | if f in copied: | |
3975 | repo.dirstate.copy(copied[f], f) |
|
3980 | repo.dirstate.copy(copied[f], f) | |
3976 |
|
3981 | |||
3977 |
|
3982 | |||
3978 | # a list of (ui, repo, otherpeer, opts, missing) functions called by |
|
3983 | # a list of (ui, repo, otherpeer, opts, missing) functions called by | |
3979 | # commands.outgoing. "missing" is "missing" of the result of |
|
3984 | # commands.outgoing. "missing" is "missing" of the result of | |
3980 | # "findcommonoutgoing()" |
|
3985 | # "findcommonoutgoing()" | |
3981 | outgoinghooks = util.hooks() |
|
3986 | outgoinghooks = util.hooks() | |
3982 |
|
3987 | |||
3983 | # a list of (ui, repo) functions called by commands.summary |
|
3988 | # a list of (ui, repo) functions called by commands.summary | |
3984 | summaryhooks = util.hooks() |
|
3989 | summaryhooks = util.hooks() | |
3985 |
|
3990 | |||
3986 | # a list of (ui, repo, opts, changes) functions called by commands.summary. |
|
3991 | # a list of (ui, repo, opts, changes) functions called by commands.summary. | |
3987 | # |
|
3992 | # | |
3988 | # functions should return tuple of booleans below, if 'changes' is None: |
|
3993 | # functions should return tuple of booleans below, if 'changes' is None: | |
3989 | # (whether-incomings-are-needed, whether-outgoings-are-needed) |
|
3994 | # (whether-incomings-are-needed, whether-outgoings-are-needed) | |
3990 | # |
|
3995 | # | |
3991 | # otherwise, 'changes' is a tuple of tuples below: |
|
3996 | # otherwise, 'changes' is a tuple of tuples below: | |
3992 | # - (sourceurl, sourcebranch, sourcepeer, incoming) |
|
3997 | # - (sourceurl, sourcebranch, sourcepeer, incoming) | |
3993 | # - (desturl, destbranch, destpeer, outgoing) |
|
3998 | # - (desturl, destbranch, destpeer, outgoing) | |
3994 | summaryremotehooks = util.hooks() |
|
3999 | summaryremotehooks = util.hooks() | |
3995 |
|
4000 | |||
3996 |
|
4001 | |||
3997 | def checkunfinished(repo, commit=False, skipmerge=False): |
|
4002 | def checkunfinished(repo, commit=False, skipmerge=False): | |
3998 | '''Look for an unfinished multistep operation, like graft, and abort |
|
4003 | '''Look for an unfinished multistep operation, like graft, and abort | |
3999 | if found. It's probably good to check this right before |
|
4004 | if found. It's probably good to check this right before | |
4000 | bailifchanged(). |
|
4005 | bailifchanged(). | |
4001 | ''' |
|
4006 | ''' | |
4002 | # Check for non-clearable states first, so things like rebase will take |
|
4007 | # Check for non-clearable states first, so things like rebase will take | |
4003 | # precedence over update. |
|
4008 | # precedence over update. | |
4004 | for state in statemod._unfinishedstates: |
|
4009 | for state in statemod._unfinishedstates: | |
4005 | if ( |
|
4010 | if ( | |
4006 | state._clearable |
|
4011 | state._clearable | |
4007 | or (commit and state._allowcommit) |
|
4012 | or (commit and state._allowcommit) | |
4008 | or state._reportonly |
|
4013 | or state._reportonly | |
4009 | ): |
|
4014 | ): | |
4010 | continue |
|
4015 | continue | |
4011 | if state.isunfinished(repo): |
|
4016 | if state.isunfinished(repo): | |
4012 | raise error.Abort(state.msg(), hint=state.hint()) |
|
4017 | raise error.Abort(state.msg(), hint=state.hint()) | |
4013 |
|
4018 | |||
4014 | for s in statemod._unfinishedstates: |
|
4019 | for s in statemod._unfinishedstates: | |
4015 | if ( |
|
4020 | if ( | |
4016 | not s._clearable |
|
4021 | not s._clearable | |
4017 | or (commit and s._allowcommit) |
|
4022 | or (commit and s._allowcommit) | |
4018 | or (s._opname == b'merge' and skipmerge) |
|
4023 | or (s._opname == b'merge' and skipmerge) | |
4019 | or s._reportonly |
|
4024 | or s._reportonly | |
4020 | ): |
|
4025 | ): | |
4021 | continue |
|
4026 | continue | |
4022 | if s.isunfinished(repo): |
|
4027 | if s.isunfinished(repo): | |
4023 | raise error.Abort(s.msg(), hint=s.hint()) |
|
4028 | raise error.Abort(s.msg(), hint=s.hint()) | |
4024 |
|
4029 | |||
4025 |
|
4030 | |||
4026 | def clearunfinished(repo): |
|
4031 | def clearunfinished(repo): | |
4027 | '''Check for unfinished operations (as above), and clear the ones |
|
4032 | '''Check for unfinished operations (as above), and clear the ones | |
4028 | that are clearable. |
|
4033 | that are clearable. | |
4029 | ''' |
|
4034 | ''' | |
4030 | for state in statemod._unfinishedstates: |
|
4035 | for state in statemod._unfinishedstates: | |
4031 | if state._reportonly: |
|
4036 | if state._reportonly: | |
4032 | continue |
|
4037 | continue | |
4033 | if not state._clearable and state.isunfinished(repo): |
|
4038 | if not state._clearable and state.isunfinished(repo): | |
4034 | raise error.Abort(state.msg(), hint=state.hint()) |
|
4039 | raise error.Abort(state.msg(), hint=state.hint()) | |
4035 |
|
4040 | |||
4036 | for s in statemod._unfinishedstates: |
|
4041 | for s in statemod._unfinishedstates: | |
4037 | if s._opname == b'merge' or state._reportonly: |
|
4042 | if s._opname == b'merge' or state._reportonly: | |
4038 | continue |
|
4043 | continue | |
4039 | if s._clearable and s.isunfinished(repo): |
|
4044 | if s._clearable and s.isunfinished(repo): | |
4040 | util.unlink(repo.vfs.join(s._fname)) |
|
4045 | util.unlink(repo.vfs.join(s._fname)) | |
4041 |
|
4046 | |||
4042 |
|
4047 | |||
4043 | def getunfinishedstate(repo): |
|
4048 | def getunfinishedstate(repo): | |
4044 | ''' Checks for unfinished operations and returns statecheck object |
|
4049 | ''' Checks for unfinished operations and returns statecheck object | |
4045 | for it''' |
|
4050 | for it''' | |
4046 | for state in statemod._unfinishedstates: |
|
4051 | for state in statemod._unfinishedstates: | |
4047 | if state.isunfinished(repo): |
|
4052 | if state.isunfinished(repo): | |
4048 | return state |
|
4053 | return state | |
4049 | return None |
|
4054 | return None | |
4050 |
|
4055 | |||
4051 |
|
4056 | |||
4052 | def howtocontinue(repo): |
|
4057 | def howtocontinue(repo): | |
4053 | '''Check for an unfinished operation and return the command to finish |
|
4058 | '''Check for an unfinished operation and return the command to finish | |
4054 | it. |
|
4059 | it. | |
4055 |
|
4060 | |||
4056 | statemod._unfinishedstates list is checked for an unfinished operation |
|
4061 | statemod._unfinishedstates list is checked for an unfinished operation | |
4057 | and the corresponding message to finish it is generated if a method to |
|
4062 | and the corresponding message to finish it is generated if a method to | |
4058 | continue is supported by the operation. |
|
4063 | continue is supported by the operation. | |
4059 |
|
4064 | |||
4060 | Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is |
|
4065 | Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is | |
4061 | a boolean. |
|
4066 | a boolean. | |
4062 | ''' |
|
4067 | ''' | |
4063 | contmsg = _(b"continue: %s") |
|
4068 | contmsg = _(b"continue: %s") | |
4064 | for state in statemod._unfinishedstates: |
|
4069 | for state in statemod._unfinishedstates: | |
4065 | if not state._continueflag: |
|
4070 | if not state._continueflag: | |
4066 | continue |
|
4071 | continue | |
4067 | if state.isunfinished(repo): |
|
4072 | if state.isunfinished(repo): | |
4068 | return contmsg % state.continuemsg(), True |
|
4073 | return contmsg % state.continuemsg(), True | |
4069 | if repo[None].dirty(missing=True, merge=False, branch=False): |
|
4074 | if repo[None].dirty(missing=True, merge=False, branch=False): | |
4070 | return contmsg % _(b"hg commit"), False |
|
4075 | return contmsg % _(b"hg commit"), False | |
4071 | return None, None |
|
4076 | return None, None | |
4072 |
|
4077 | |||
4073 |
|
4078 | |||
4074 | def checkafterresolved(repo): |
|
4079 | def checkafterresolved(repo): | |
4075 | '''Inform the user about the next action after completing hg resolve |
|
4080 | '''Inform the user about the next action after completing hg resolve | |
4076 |
|
4081 | |||
4077 | If there's a an unfinished operation that supports continue flag, |
|
4082 | If there's a an unfinished operation that supports continue flag, | |
4078 | howtocontinue will yield repo.ui.warn as the reporter. |
|
4083 | howtocontinue will yield repo.ui.warn as the reporter. | |
4079 |
|
4084 | |||
4080 | Otherwise, it will yield repo.ui.note. |
|
4085 | Otherwise, it will yield repo.ui.note. | |
4081 | ''' |
|
4086 | ''' | |
4082 | msg, warning = howtocontinue(repo) |
|
4087 | msg, warning = howtocontinue(repo) | |
4083 | if msg is not None: |
|
4088 | if msg is not None: | |
4084 | if warning: |
|
4089 | if warning: | |
4085 | repo.ui.warn(b"%s\n" % msg) |
|
4090 | repo.ui.warn(b"%s\n" % msg) | |
4086 | else: |
|
4091 | else: | |
4087 | repo.ui.note(b"%s\n" % msg) |
|
4092 | repo.ui.note(b"%s\n" % msg) | |
4088 |
|
4093 | |||
4089 |
|
4094 | |||
4090 | def wrongtooltocontinue(repo, task): |
|
4095 | def wrongtooltocontinue(repo, task): | |
4091 | '''Raise an abort suggesting how to properly continue if there is an |
|
4096 | '''Raise an abort suggesting how to properly continue if there is an | |
4092 | active task. |
|
4097 | active task. | |
4093 |
|
4098 | |||
4094 | Uses howtocontinue() to find the active task. |
|
4099 | Uses howtocontinue() to find the active task. | |
4095 |
|
4100 | |||
4096 | If there's no task (repo.ui.note for 'hg commit'), it does not offer |
|
4101 | If there's no task (repo.ui.note for 'hg commit'), it does not offer | |
4097 | a hint. |
|
4102 | a hint. | |
4098 | ''' |
|
4103 | ''' | |
4099 | after = howtocontinue(repo) |
|
4104 | after = howtocontinue(repo) | |
4100 | hint = None |
|
4105 | hint = None | |
4101 | if after[1]: |
|
4106 | if after[1]: | |
4102 | hint = after[0] |
|
4107 | hint = after[0] | |
4103 | raise error.Abort(_(b'no %s in progress') % task, hint=hint) |
|
4108 | raise error.Abort(_(b'no %s in progress') % task, hint=hint) | |
4104 |
|
4109 | |||
4105 |
|
4110 | |||
4106 | def abortgraft(ui, repo, graftstate): |
|
4111 | def abortgraft(ui, repo, graftstate): | |
4107 | """abort the interrupted graft and rollbacks to the state before interrupted |
|
4112 | """abort the interrupted graft and rollbacks to the state before interrupted | |
4108 | graft""" |
|
4113 | graft""" | |
4109 | if not graftstate.exists(): |
|
4114 | if not graftstate.exists(): | |
4110 | raise error.Abort(_(b"no interrupted graft to abort")) |
|
4115 | raise error.Abort(_(b"no interrupted graft to abort")) | |
4111 | statedata = readgraftstate(repo, graftstate) |
|
4116 | statedata = readgraftstate(repo, graftstate) | |
4112 | newnodes = statedata.get(b'newnodes') |
|
4117 | newnodes = statedata.get(b'newnodes') | |
4113 | if newnodes is None: |
|
4118 | if newnodes is None: | |
4114 | # and old graft state which does not have all the data required to abort |
|
4119 | # and old graft state which does not have all the data required to abort | |
4115 | # the graft |
|
4120 | # the graft | |
4116 | raise error.Abort(_(b"cannot abort using an old graftstate")) |
|
4121 | raise error.Abort(_(b"cannot abort using an old graftstate")) | |
4117 |
|
4122 | |||
4118 | # changeset from which graft operation was started |
|
4123 | # changeset from which graft operation was started | |
4119 | if len(newnodes) > 0: |
|
4124 | if len(newnodes) > 0: | |
4120 | startctx = repo[newnodes[0]].p1() |
|
4125 | startctx = repo[newnodes[0]].p1() | |
4121 | else: |
|
4126 | else: | |
4122 | startctx = repo[b'.'] |
|
4127 | startctx = repo[b'.'] | |
4123 | # whether to strip or not |
|
4128 | # whether to strip or not | |
4124 | cleanup = False |
|
4129 | cleanup = False | |
4125 | from . import hg |
|
4130 | from . import hg | |
4126 |
|
4131 | |||
4127 | if newnodes: |
|
4132 | if newnodes: | |
4128 | newnodes = [repo[r].rev() for r in newnodes] |
|
4133 | newnodes = [repo[r].rev() for r in newnodes] | |
4129 | cleanup = True |
|
4134 | cleanup = True | |
4130 | # checking that none of the newnodes turned public or is public |
|
4135 | # checking that none of the newnodes turned public or is public | |
4131 | immutable = [c for c in newnodes if not repo[c].mutable()] |
|
4136 | immutable = [c for c in newnodes if not repo[c].mutable()] | |
4132 | if immutable: |
|
4137 | if immutable: | |
4133 | repo.ui.warn( |
|
4138 | repo.ui.warn( | |
4134 | _(b"cannot clean up public changesets %s\n") |
|
4139 | _(b"cannot clean up public changesets %s\n") | |
4135 | % b', '.join(bytes(repo[r]) for r in immutable), |
|
4140 | % b', '.join(bytes(repo[r]) for r in immutable), | |
4136 | hint=_(b"see 'hg help phases' for details"), |
|
4141 | hint=_(b"see 'hg help phases' for details"), | |
4137 | ) |
|
4142 | ) | |
4138 | cleanup = False |
|
4143 | cleanup = False | |
4139 |
|
4144 | |||
4140 | # checking that no new nodes are created on top of grafted revs |
|
4145 | # checking that no new nodes are created on top of grafted revs | |
4141 | desc = set(repo.changelog.descendants(newnodes)) |
|
4146 | desc = set(repo.changelog.descendants(newnodes)) | |
4142 | if desc - set(newnodes): |
|
4147 | if desc - set(newnodes): | |
4143 | repo.ui.warn( |
|
4148 | repo.ui.warn( | |
4144 | _( |
|
4149 | _( | |
4145 | b"new changesets detected on destination " |
|
4150 | b"new changesets detected on destination " | |
4146 | b"branch, can't strip\n" |
|
4151 | b"branch, can't strip\n" | |
4147 | ) |
|
4152 | ) | |
4148 | ) |
|
4153 | ) | |
4149 | cleanup = False |
|
4154 | cleanup = False | |
4150 |
|
4155 | |||
4151 | if cleanup: |
|
4156 | if cleanup: | |
4152 | with repo.wlock(), repo.lock(): |
|
4157 | with repo.wlock(), repo.lock(): | |
4153 | hg.updaterepo(repo, startctx.node(), overwrite=True) |
|
4158 | hg.updaterepo(repo, startctx.node(), overwrite=True) | |
4154 | # stripping the new nodes created |
|
4159 | # stripping the new nodes created | |
4155 | strippoints = [ |
|
4160 | strippoints = [ | |
4156 | c.node() for c in repo.set(b"roots(%ld)", newnodes) |
|
4161 | c.node() for c in repo.set(b"roots(%ld)", newnodes) | |
4157 | ] |
|
4162 | ] | |
4158 | repair.strip(repo.ui, repo, strippoints, backup=False) |
|
4163 | repair.strip(repo.ui, repo, strippoints, backup=False) | |
4159 |
|
4164 | |||
4160 | if not cleanup: |
|
4165 | if not cleanup: | |
4161 | # we don't update to the startnode if we can't strip |
|
4166 | # we don't update to the startnode if we can't strip | |
4162 | startctx = repo[b'.'] |
|
4167 | startctx = repo[b'.'] | |
4163 | hg.updaterepo(repo, startctx.node(), overwrite=True) |
|
4168 | hg.updaterepo(repo, startctx.node(), overwrite=True) | |
4164 |
|
4169 | |||
4165 | ui.status(_(b"graft aborted\n")) |
|
4170 | ui.status(_(b"graft aborted\n")) | |
4166 | ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12]) |
|
4171 | ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12]) | |
4167 | graftstate.delete() |
|
4172 | graftstate.delete() | |
4168 | return 0 |
|
4173 | return 0 | |
4169 |
|
4174 | |||
4170 |
|
4175 | |||
4171 | def readgraftstate(repo, graftstate): |
|
4176 | def readgraftstate(repo, graftstate): | |
4172 | # type: (Any, statemod.cmdstate) -> Dict[bytes, Any] |
|
4177 | # type: (Any, statemod.cmdstate) -> Dict[bytes, Any] | |
4173 | """read the graft state file and return a dict of the data stored in it""" |
|
4178 | """read the graft state file and return a dict of the data stored in it""" | |
4174 | try: |
|
4179 | try: | |
4175 | return graftstate.read() |
|
4180 | return graftstate.read() | |
4176 | except error.CorruptedState: |
|
4181 | except error.CorruptedState: | |
4177 | nodes = repo.vfs.read(b'graftstate').splitlines() |
|
4182 | nodes = repo.vfs.read(b'graftstate').splitlines() | |
4178 | return {b'nodes': nodes} |
|
4183 | return {b'nodes': nodes} | |
4179 |
|
4184 | |||
4180 |
|
4185 | |||
4181 | def hgabortgraft(ui, repo): |
|
4186 | def hgabortgraft(ui, repo): | |
4182 | """ abort logic for aborting graft using 'hg abort'""" |
|
4187 | """ abort logic for aborting graft using 'hg abort'""" | |
4183 | with repo.wlock(): |
|
4188 | with repo.wlock(): | |
4184 | graftstate = statemod.cmdstate(repo, b'graftstate') |
|
4189 | graftstate = statemod.cmdstate(repo, b'graftstate') | |
4185 | return abortgraft(ui, repo, graftstate) |
|
4190 | return abortgraft(ui, repo, graftstate) |
@@ -1,731 +1,731 b'' | |||||
1 | # commandserver.py - communicate with Mercurial's API over a pipe |
|
1 | # commandserver.py - communicate with Mercurial's API over a pipe | |
2 | # |
|
2 | # | |
3 | # Copyright Matt Mackall <mpm@selenic.com> |
|
3 | # Copyright Matt Mackall <mpm@selenic.com> | |
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2 or any later version. |
|
6 | # GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | from __future__ import absolute_import |
|
8 | from __future__ import absolute_import | |
9 |
|
9 | |||
10 | import errno |
|
10 | import errno | |
11 | import gc |
|
11 | import gc | |
12 | import os |
|
12 | import os | |
13 | import random |
|
13 | import random | |
14 | import signal |
|
14 | import signal | |
15 | import socket |
|
15 | import socket | |
16 | import struct |
|
16 | import struct | |
17 | import traceback |
|
17 | import traceback | |
18 |
|
18 | |||
19 | try: |
|
19 | try: | |
20 | import selectors |
|
20 | import selectors | |
21 |
|
21 | |||
22 | selectors.BaseSelector |
|
22 | selectors.BaseSelector | |
23 | except ImportError: |
|
23 | except ImportError: | |
24 | from .thirdparty import selectors2 as selectors |
|
24 | from .thirdparty import selectors2 as selectors | |
25 |
|
25 | |||
26 | from .i18n import _ |
|
26 | from .i18n import _ | |
27 | from .pycompat import getattr |
|
27 | from .pycompat import getattr | |
28 | from . import ( |
|
28 | from . import ( | |
29 | encoding, |
|
29 | encoding, | |
30 | error, |
|
30 | error, | |
31 | loggingutil, |
|
31 | loggingutil, | |
32 | pycompat, |
|
32 | pycompat, | |
33 | repocache, |
|
33 | repocache, | |
34 | util, |
|
34 | util, | |
35 | vfs as vfsmod, |
|
35 | vfs as vfsmod, | |
36 | ) |
|
36 | ) | |
37 | from .utils import ( |
|
37 | from .utils import ( | |
38 | cborutil, |
|
38 | cborutil, | |
39 | procutil, |
|
39 | procutil, | |
40 | ) |
|
40 | ) | |
41 |
|
41 | |||
42 |
|
42 | |||
43 | class channeledoutput(object): |
|
43 | class channeledoutput(object): | |
44 | """ |
|
44 | """ | |
45 | Write data to out in the following format: |
|
45 | Write data to out in the following format: | |
46 |
|
46 | |||
47 | data length (unsigned int), |
|
47 | data length (unsigned int), | |
48 | data |
|
48 | data | |
49 | """ |
|
49 | """ | |
50 |
|
50 | |||
51 | def __init__(self, out, channel): |
|
51 | def __init__(self, out, channel): | |
52 | self.out = out |
|
52 | self.out = out | |
53 | self.channel = channel |
|
53 | self.channel = channel | |
54 |
|
54 | |||
55 | @property |
|
55 | @property | |
56 | def name(self): |
|
56 | def name(self): | |
57 | return b'<%c-channel>' % self.channel |
|
57 | return b'<%c-channel>' % self.channel | |
58 |
|
58 | |||
59 | def write(self, data): |
|
59 | def write(self, data): | |
60 | if not data: |
|
60 | if not data: | |
61 | return |
|
61 | return | |
62 | # single write() to guarantee the same atomicity as the underlying file |
|
62 | # single write() to guarantee the same atomicity as the underlying file | |
63 | self.out.write(struct.pack(b'>cI', self.channel, len(data)) + data) |
|
63 | self.out.write(struct.pack(b'>cI', self.channel, len(data)) + data) | |
64 | self.out.flush() |
|
64 | self.out.flush() | |
65 |
|
65 | |||
66 | def __getattr__(self, attr): |
|
66 | def __getattr__(self, attr): | |
67 | if attr in ('isatty', 'fileno', 'tell', 'seek'): |
|
67 | if attr in ('isatty', 'fileno', 'tell', 'seek'): | |
68 | raise AttributeError(attr) |
|
68 | raise AttributeError(attr) | |
69 | return getattr(self.out, attr) |
|
69 | return getattr(self.out, attr) | |
70 |
|
70 | |||
71 |
|
71 | |||
72 | class channeledmessage(object): |
|
72 | class channeledmessage(object): | |
73 | """ |
|
73 | """ | |
74 | Write encoded message and metadata to out in the following format: |
|
74 | Write encoded message and metadata to out in the following format: | |
75 |
|
75 | |||
76 | data length (unsigned int), |
|
76 | data length (unsigned int), | |
77 | encoded message and metadata, as a flat key-value dict. |
|
77 | encoded message and metadata, as a flat key-value dict. | |
78 |
|
78 | |||
79 | Each message should have 'type' attribute. Messages of unknown type |
|
79 | Each message should have 'type' attribute. Messages of unknown type | |
80 | should be ignored. |
|
80 | should be ignored. | |
81 | """ |
|
81 | """ | |
82 |
|
82 | |||
83 | # teach ui that write() can take **opts |
|
83 | # teach ui that write() can take **opts | |
84 | structured = True |
|
84 | structured = True | |
85 |
|
85 | |||
86 | def __init__(self, out, channel, encodename, encodefn): |
|
86 | def __init__(self, out, channel, encodename, encodefn): | |
87 | self._cout = channeledoutput(out, channel) |
|
87 | self._cout = channeledoutput(out, channel) | |
88 | self.encoding = encodename |
|
88 | self.encoding = encodename | |
89 | self._encodefn = encodefn |
|
89 | self._encodefn = encodefn | |
90 |
|
90 | |||
91 | def write(self, data, **opts): |
|
91 | def write(self, data, **opts): | |
92 | opts = pycompat.byteskwargs(opts) |
|
92 | opts = pycompat.byteskwargs(opts) | |
93 | if data is not None: |
|
93 | if data is not None: | |
94 | opts[b'data'] = data |
|
94 | opts[b'data'] = data | |
95 | self._cout.write(self._encodefn(opts)) |
|
95 | self._cout.write(self._encodefn(opts)) | |
96 |
|
96 | |||
97 | def __getattr__(self, attr): |
|
97 | def __getattr__(self, attr): | |
98 | return getattr(self._cout, attr) |
|
98 | return getattr(self._cout, attr) | |
99 |
|
99 | |||
100 |
|
100 | |||
101 | class channeledinput(object): |
|
101 | class channeledinput(object): | |
102 | """ |
|
102 | """ | |
103 | Read data from in_. |
|
103 | Read data from in_. | |
104 |
|
104 | |||
105 | Requests for input are written to out in the following format: |
|
105 | Requests for input are written to out in the following format: | |
106 | channel identifier - 'I' for plain input, 'L' line based (1 byte) |
|
106 | channel identifier - 'I' for plain input, 'L' line based (1 byte) | |
107 | how many bytes to send at most (unsigned int), |
|
107 | how many bytes to send at most (unsigned int), | |
108 |
|
108 | |||
109 | The client replies with: |
|
109 | The client replies with: | |
110 | data length (unsigned int), 0 meaning EOF |
|
110 | data length (unsigned int), 0 meaning EOF | |
111 | data |
|
111 | data | |
112 | """ |
|
112 | """ | |
113 |
|
113 | |||
114 | maxchunksize = 4 * 1024 |
|
114 | maxchunksize = 4 * 1024 | |
115 |
|
115 | |||
116 | def __init__(self, in_, out, channel): |
|
116 | def __init__(self, in_, out, channel): | |
117 | self.in_ = in_ |
|
117 | self.in_ = in_ | |
118 | self.out = out |
|
118 | self.out = out | |
119 | self.channel = channel |
|
119 | self.channel = channel | |
120 |
|
120 | |||
121 | @property |
|
121 | @property | |
122 | def name(self): |
|
122 | def name(self): | |
123 | return b'<%c-channel>' % self.channel |
|
123 | return b'<%c-channel>' % self.channel | |
124 |
|
124 | |||
125 | def read(self, size=-1): |
|
125 | def read(self, size=-1): | |
126 | if size < 0: |
|
126 | if size < 0: | |
127 | # if we need to consume all the clients input, ask for 4k chunks |
|
127 | # if we need to consume all the clients input, ask for 4k chunks | |
128 | # so the pipe doesn't fill up risking a deadlock |
|
128 | # so the pipe doesn't fill up risking a deadlock | |
129 | size = self.maxchunksize |
|
129 | size = self.maxchunksize | |
130 | s = self._read(size, self.channel) |
|
130 | s = self._read(size, self.channel) | |
131 | buf = s |
|
131 | buf = s | |
132 | while s: |
|
132 | while s: | |
133 | s = self._read(size, self.channel) |
|
133 | s = self._read(size, self.channel) | |
134 | buf += s |
|
134 | buf += s | |
135 |
|
135 | |||
136 | return buf |
|
136 | return buf | |
137 | else: |
|
137 | else: | |
138 | return self._read(size, self.channel) |
|
138 | return self._read(size, self.channel) | |
139 |
|
139 | |||
140 | def _read(self, size, channel): |
|
140 | def _read(self, size, channel): | |
141 | if not size: |
|
141 | if not size: | |
142 | return b'' |
|
142 | return b'' | |
143 | assert size > 0 |
|
143 | assert size > 0 | |
144 |
|
144 | |||
145 | # tell the client we need at most size bytes |
|
145 | # tell the client we need at most size bytes | |
146 | self.out.write(struct.pack(b'>cI', channel, size)) |
|
146 | self.out.write(struct.pack(b'>cI', channel, size)) | |
147 | self.out.flush() |
|
147 | self.out.flush() | |
148 |
|
148 | |||
149 | length = self.in_.read(4) |
|
149 | length = self.in_.read(4) | |
150 | length = struct.unpack(b'>I', length)[0] |
|
150 | length = struct.unpack(b'>I', length)[0] | |
151 | if not length: |
|
151 | if not length: | |
152 | return b'' |
|
152 | return b'' | |
153 | else: |
|
153 | else: | |
154 | return self.in_.read(length) |
|
154 | return self.in_.read(length) | |
155 |
|
155 | |||
156 | def readline(self, size=-1): |
|
156 | def readline(self, size=-1): | |
157 | if size < 0: |
|
157 | if size < 0: | |
158 | size = self.maxchunksize |
|
158 | size = self.maxchunksize | |
159 | s = self._read(size, b'L') |
|
159 | s = self._read(size, b'L') | |
160 | buf = s |
|
160 | buf = s | |
161 | # keep asking for more until there's either no more or |
|
161 | # keep asking for more until there's either no more or | |
162 | # we got a full line |
|
162 | # we got a full line | |
163 |
while s and s |
|
163 | while s and not s.endswith(b'\n'): | |
164 | s = self._read(size, b'L') |
|
164 | s = self._read(size, b'L') | |
165 | buf += s |
|
165 | buf += s | |
166 |
|
166 | |||
167 | return buf |
|
167 | return buf | |
168 | else: |
|
168 | else: | |
169 | return self._read(size, b'L') |
|
169 | return self._read(size, b'L') | |
170 |
|
170 | |||
171 | def __iter__(self): |
|
171 | def __iter__(self): | |
172 | return self |
|
172 | return self | |
173 |
|
173 | |||
174 | def next(self): |
|
174 | def next(self): | |
175 | l = self.readline() |
|
175 | l = self.readline() | |
176 | if not l: |
|
176 | if not l: | |
177 | raise StopIteration |
|
177 | raise StopIteration | |
178 | return l |
|
178 | return l | |
179 |
|
179 | |||
180 | __next__ = next |
|
180 | __next__ = next | |
181 |
|
181 | |||
182 | def __getattr__(self, attr): |
|
182 | def __getattr__(self, attr): | |
183 | if attr in ('isatty', 'fileno', 'tell', 'seek'): |
|
183 | if attr in ('isatty', 'fileno', 'tell', 'seek'): | |
184 | raise AttributeError(attr) |
|
184 | raise AttributeError(attr) | |
185 | return getattr(self.in_, attr) |
|
185 | return getattr(self.in_, attr) | |
186 |
|
186 | |||
187 |
|
187 | |||
188 | _messageencoders = { |
|
188 | _messageencoders = { | |
189 | b'cbor': lambda v: b''.join(cborutil.streamencode(v)), |
|
189 | b'cbor': lambda v: b''.join(cborutil.streamencode(v)), | |
190 | } |
|
190 | } | |
191 |
|
191 | |||
192 |
|
192 | |||
193 | def _selectmessageencoder(ui): |
|
193 | def _selectmessageencoder(ui): | |
194 | # experimental config: cmdserver.message-encodings |
|
194 | # experimental config: cmdserver.message-encodings | |
195 | encnames = ui.configlist(b'cmdserver', b'message-encodings') |
|
195 | encnames = ui.configlist(b'cmdserver', b'message-encodings') | |
196 | for n in encnames: |
|
196 | for n in encnames: | |
197 | f = _messageencoders.get(n) |
|
197 | f = _messageencoders.get(n) | |
198 | if f: |
|
198 | if f: | |
199 | return n, f |
|
199 | return n, f | |
200 | raise error.Abort( |
|
200 | raise error.Abort( | |
201 | b'no supported message encodings: %s' % b' '.join(encnames) |
|
201 | b'no supported message encodings: %s' % b' '.join(encnames) | |
202 | ) |
|
202 | ) | |
203 |
|
203 | |||
204 |
|
204 | |||
205 | class server(object): |
|
205 | class server(object): | |
206 | """ |
|
206 | """ | |
207 | Listens for commands on fin, runs them and writes the output on a channel |
|
207 | Listens for commands on fin, runs them and writes the output on a channel | |
208 | based stream to fout. |
|
208 | based stream to fout. | |
209 | """ |
|
209 | """ | |
210 |
|
210 | |||
211 | def __init__(self, ui, repo, fin, fout, prereposetups=None): |
|
211 | def __init__(self, ui, repo, fin, fout, prereposetups=None): | |
212 | self.cwd = encoding.getcwd() |
|
212 | self.cwd = encoding.getcwd() | |
213 |
|
213 | |||
214 | if repo: |
|
214 | if repo: | |
215 | # the ui here is really the repo ui so take its baseui so we don't |
|
215 | # the ui here is really the repo ui so take its baseui so we don't | |
216 | # end up with its local configuration |
|
216 | # end up with its local configuration | |
217 | self.ui = repo.baseui |
|
217 | self.ui = repo.baseui | |
218 | self.repo = repo |
|
218 | self.repo = repo | |
219 | self.repoui = repo.ui |
|
219 | self.repoui = repo.ui | |
220 | else: |
|
220 | else: | |
221 | self.ui = ui |
|
221 | self.ui = ui | |
222 | self.repo = self.repoui = None |
|
222 | self.repo = self.repoui = None | |
223 | self._prereposetups = prereposetups |
|
223 | self._prereposetups = prereposetups | |
224 |
|
224 | |||
225 | self.cdebug = channeledoutput(fout, b'd') |
|
225 | self.cdebug = channeledoutput(fout, b'd') | |
226 | self.cerr = channeledoutput(fout, b'e') |
|
226 | self.cerr = channeledoutput(fout, b'e') | |
227 | self.cout = channeledoutput(fout, b'o') |
|
227 | self.cout = channeledoutput(fout, b'o') | |
228 | self.cin = channeledinput(fin, fout, b'I') |
|
228 | self.cin = channeledinput(fin, fout, b'I') | |
229 | self.cresult = channeledoutput(fout, b'r') |
|
229 | self.cresult = channeledoutput(fout, b'r') | |
230 |
|
230 | |||
231 | if self.ui.config(b'cmdserver', b'log') == b'-': |
|
231 | if self.ui.config(b'cmdserver', b'log') == b'-': | |
232 | # switch log stream of server's ui to the 'd' (debug) channel |
|
232 | # switch log stream of server's ui to the 'd' (debug) channel | |
233 | # (don't touch repo.ui as its lifetime is longer than the server) |
|
233 | # (don't touch repo.ui as its lifetime is longer than the server) | |
234 | self.ui = self.ui.copy() |
|
234 | self.ui = self.ui.copy() | |
235 | setuplogging(self.ui, repo=None, fp=self.cdebug) |
|
235 | setuplogging(self.ui, repo=None, fp=self.cdebug) | |
236 |
|
236 | |||
237 | # TODO: add this to help/config.txt when stabilized |
|
237 | # TODO: add this to help/config.txt when stabilized | |
238 | # ``channel`` |
|
238 | # ``channel`` | |
239 | # Use separate channel for structured output. (Command-server only) |
|
239 | # Use separate channel for structured output. (Command-server only) | |
240 | self.cmsg = None |
|
240 | self.cmsg = None | |
241 | if ui.config(b'ui', b'message-output') == b'channel': |
|
241 | if ui.config(b'ui', b'message-output') == b'channel': | |
242 | encname, encfn = _selectmessageencoder(ui) |
|
242 | encname, encfn = _selectmessageencoder(ui) | |
243 | self.cmsg = channeledmessage(fout, b'm', encname, encfn) |
|
243 | self.cmsg = channeledmessage(fout, b'm', encname, encfn) | |
244 |
|
244 | |||
245 | self.client = fin |
|
245 | self.client = fin | |
246 |
|
246 | |||
247 | def cleanup(self): |
|
247 | def cleanup(self): | |
248 | """release and restore resources taken during server session""" |
|
248 | """release and restore resources taken during server session""" | |
249 |
|
249 | |||
250 | def _read(self, size): |
|
250 | def _read(self, size): | |
251 | if not size: |
|
251 | if not size: | |
252 | return b'' |
|
252 | return b'' | |
253 |
|
253 | |||
254 | data = self.client.read(size) |
|
254 | data = self.client.read(size) | |
255 |
|
255 | |||
256 | # is the other end closed? |
|
256 | # is the other end closed? | |
257 | if not data: |
|
257 | if not data: | |
258 | raise EOFError |
|
258 | raise EOFError | |
259 |
|
259 | |||
260 | return data |
|
260 | return data | |
261 |
|
261 | |||
262 | def _readstr(self): |
|
262 | def _readstr(self): | |
263 | """read a string from the channel |
|
263 | """read a string from the channel | |
264 |
|
264 | |||
265 | format: |
|
265 | format: | |
266 | data length (uint32), data |
|
266 | data length (uint32), data | |
267 | """ |
|
267 | """ | |
268 | length = struct.unpack(b'>I', self._read(4))[0] |
|
268 | length = struct.unpack(b'>I', self._read(4))[0] | |
269 | if not length: |
|
269 | if not length: | |
270 | return b'' |
|
270 | return b'' | |
271 | return self._read(length) |
|
271 | return self._read(length) | |
272 |
|
272 | |||
273 | def _readlist(self): |
|
273 | def _readlist(self): | |
274 | """read a list of NULL separated strings from the channel""" |
|
274 | """read a list of NULL separated strings from the channel""" | |
275 | s = self._readstr() |
|
275 | s = self._readstr() | |
276 | if s: |
|
276 | if s: | |
277 | return s.split(b'\0') |
|
277 | return s.split(b'\0') | |
278 | else: |
|
278 | else: | |
279 | return [] |
|
279 | return [] | |
280 |
|
280 | |||
281 | def runcommand(self): |
|
281 | def runcommand(self): | |
282 | """ reads a list of \0 terminated arguments, executes |
|
282 | """ reads a list of \0 terminated arguments, executes | |
283 | and writes the return code to the result channel """ |
|
283 | and writes the return code to the result channel """ | |
284 | from . import dispatch # avoid cycle |
|
284 | from . import dispatch # avoid cycle | |
285 |
|
285 | |||
286 | args = self._readlist() |
|
286 | args = self._readlist() | |
287 |
|
287 | |||
288 | # copy the uis so changes (e.g. --config or --verbose) don't |
|
288 | # copy the uis so changes (e.g. --config or --verbose) don't | |
289 | # persist between requests |
|
289 | # persist between requests | |
290 | copiedui = self.ui.copy() |
|
290 | copiedui = self.ui.copy() | |
291 | uis = [copiedui] |
|
291 | uis = [copiedui] | |
292 | if self.repo: |
|
292 | if self.repo: | |
293 | self.repo.baseui = copiedui |
|
293 | self.repo.baseui = copiedui | |
294 | # clone ui without using ui.copy because this is protected |
|
294 | # clone ui without using ui.copy because this is protected | |
295 | repoui = self.repoui.__class__(self.repoui) |
|
295 | repoui = self.repoui.__class__(self.repoui) | |
296 | repoui.copy = copiedui.copy # redo copy protection |
|
296 | repoui.copy = copiedui.copy # redo copy protection | |
297 | uis.append(repoui) |
|
297 | uis.append(repoui) | |
298 | self.repo.ui = self.repo.dirstate._ui = repoui |
|
298 | self.repo.ui = self.repo.dirstate._ui = repoui | |
299 | self.repo.invalidateall() |
|
299 | self.repo.invalidateall() | |
300 |
|
300 | |||
301 | for ui in uis: |
|
301 | for ui in uis: | |
302 | ui.resetstate() |
|
302 | ui.resetstate() | |
303 | # any kind of interaction must use server channels, but chg may |
|
303 | # any kind of interaction must use server channels, but chg may | |
304 | # replace channels by fully functional tty files. so nontty is |
|
304 | # replace channels by fully functional tty files. so nontty is | |
305 | # enforced only if cin is a channel. |
|
305 | # enforced only if cin is a channel. | |
306 | if not util.safehasattr(self.cin, b'fileno'): |
|
306 | if not util.safehasattr(self.cin, b'fileno'): | |
307 | ui.setconfig(b'ui', b'nontty', b'true', b'commandserver') |
|
307 | ui.setconfig(b'ui', b'nontty', b'true', b'commandserver') | |
308 |
|
308 | |||
309 | req = dispatch.request( |
|
309 | req = dispatch.request( | |
310 | args[:], |
|
310 | args[:], | |
311 | copiedui, |
|
311 | copiedui, | |
312 | self.repo, |
|
312 | self.repo, | |
313 | self.cin, |
|
313 | self.cin, | |
314 | self.cout, |
|
314 | self.cout, | |
315 | self.cerr, |
|
315 | self.cerr, | |
316 | self.cmsg, |
|
316 | self.cmsg, | |
317 | prereposetups=self._prereposetups, |
|
317 | prereposetups=self._prereposetups, | |
318 | ) |
|
318 | ) | |
319 |
|
319 | |||
320 | try: |
|
320 | try: | |
321 | ret = dispatch.dispatch(req) & 255 |
|
321 | ret = dispatch.dispatch(req) & 255 | |
322 | self.cresult.write(struct.pack(b'>i', int(ret))) |
|
322 | self.cresult.write(struct.pack(b'>i', int(ret))) | |
323 | finally: |
|
323 | finally: | |
324 | # restore old cwd |
|
324 | # restore old cwd | |
325 | if b'--cwd' in args: |
|
325 | if b'--cwd' in args: | |
326 | os.chdir(self.cwd) |
|
326 | os.chdir(self.cwd) | |
327 |
|
327 | |||
328 | def getencoding(self): |
|
328 | def getencoding(self): | |
329 | """ writes the current encoding to the result channel """ |
|
329 | """ writes the current encoding to the result channel """ | |
330 | self.cresult.write(encoding.encoding) |
|
330 | self.cresult.write(encoding.encoding) | |
331 |
|
331 | |||
332 | def serveone(self): |
|
332 | def serveone(self): | |
333 | cmd = self.client.readline()[:-1] |
|
333 | cmd = self.client.readline()[:-1] | |
334 | if cmd: |
|
334 | if cmd: | |
335 | handler = self.capabilities.get(cmd) |
|
335 | handler = self.capabilities.get(cmd) | |
336 | if handler: |
|
336 | if handler: | |
337 | handler(self) |
|
337 | handler(self) | |
338 | else: |
|
338 | else: | |
339 | # clients are expected to check what commands are supported by |
|
339 | # clients are expected to check what commands are supported by | |
340 | # looking at the servers capabilities |
|
340 | # looking at the servers capabilities | |
341 | raise error.Abort(_(b'unknown command %s') % cmd) |
|
341 | raise error.Abort(_(b'unknown command %s') % cmd) | |
342 |
|
342 | |||
343 | return cmd != b'' |
|
343 | return cmd != b'' | |
344 |
|
344 | |||
345 | capabilities = {b'runcommand': runcommand, b'getencoding': getencoding} |
|
345 | capabilities = {b'runcommand': runcommand, b'getencoding': getencoding} | |
346 |
|
346 | |||
347 | def serve(self): |
|
347 | def serve(self): | |
348 | hellomsg = b'capabilities: ' + b' '.join(sorted(self.capabilities)) |
|
348 | hellomsg = b'capabilities: ' + b' '.join(sorted(self.capabilities)) | |
349 | hellomsg += b'\n' |
|
349 | hellomsg += b'\n' | |
350 | hellomsg += b'encoding: ' + encoding.encoding |
|
350 | hellomsg += b'encoding: ' + encoding.encoding | |
351 | hellomsg += b'\n' |
|
351 | hellomsg += b'\n' | |
352 | if self.cmsg: |
|
352 | if self.cmsg: | |
353 | hellomsg += b'message-encoding: %s\n' % self.cmsg.encoding |
|
353 | hellomsg += b'message-encoding: %s\n' % self.cmsg.encoding | |
354 | hellomsg += b'pid: %d' % procutil.getpid() |
|
354 | hellomsg += b'pid: %d' % procutil.getpid() | |
355 | if util.safehasattr(os, b'getpgid'): |
|
355 | if util.safehasattr(os, b'getpgid'): | |
356 | hellomsg += b'\n' |
|
356 | hellomsg += b'\n' | |
357 | hellomsg += b'pgid: %d' % os.getpgid(0) |
|
357 | hellomsg += b'pgid: %d' % os.getpgid(0) | |
358 |
|
358 | |||
359 | # write the hello msg in -one- chunk |
|
359 | # write the hello msg in -one- chunk | |
360 | self.cout.write(hellomsg) |
|
360 | self.cout.write(hellomsg) | |
361 |
|
361 | |||
362 | try: |
|
362 | try: | |
363 | while self.serveone(): |
|
363 | while self.serveone(): | |
364 | pass |
|
364 | pass | |
365 | except EOFError: |
|
365 | except EOFError: | |
366 | # we'll get here if the client disconnected while we were reading |
|
366 | # we'll get here if the client disconnected while we were reading | |
367 | # its request |
|
367 | # its request | |
368 | return 1 |
|
368 | return 1 | |
369 |
|
369 | |||
370 | return 0 |
|
370 | return 0 | |
371 |
|
371 | |||
372 |
|
372 | |||
373 | def setuplogging(ui, repo=None, fp=None): |
|
373 | def setuplogging(ui, repo=None, fp=None): | |
374 | """Set up server logging facility |
|
374 | """Set up server logging facility | |
375 |
|
375 | |||
376 | If cmdserver.log is '-', log messages will be sent to the given fp. |
|
376 | If cmdserver.log is '-', log messages will be sent to the given fp. | |
377 | It should be the 'd' channel while a client is connected, and otherwise |
|
377 | It should be the 'd' channel while a client is connected, and otherwise | |
378 | is the stderr of the server process. |
|
378 | is the stderr of the server process. | |
379 | """ |
|
379 | """ | |
380 | # developer config: cmdserver.log |
|
380 | # developer config: cmdserver.log | |
381 | logpath = ui.config(b'cmdserver', b'log') |
|
381 | logpath = ui.config(b'cmdserver', b'log') | |
382 | if not logpath: |
|
382 | if not logpath: | |
383 | return |
|
383 | return | |
384 | # developer config: cmdserver.track-log |
|
384 | # developer config: cmdserver.track-log | |
385 | tracked = set(ui.configlist(b'cmdserver', b'track-log')) |
|
385 | tracked = set(ui.configlist(b'cmdserver', b'track-log')) | |
386 |
|
386 | |||
387 | if logpath == b'-' and fp: |
|
387 | if logpath == b'-' and fp: | |
388 | logger = loggingutil.fileobjectlogger(fp, tracked) |
|
388 | logger = loggingutil.fileobjectlogger(fp, tracked) | |
389 | elif logpath == b'-': |
|
389 | elif logpath == b'-': | |
390 | logger = loggingutil.fileobjectlogger(ui.ferr, tracked) |
|
390 | logger = loggingutil.fileobjectlogger(ui.ferr, tracked) | |
391 | else: |
|
391 | else: | |
392 | logpath = os.path.abspath(util.expandpath(logpath)) |
|
392 | logpath = os.path.abspath(util.expandpath(logpath)) | |
393 | # developer config: cmdserver.max-log-files |
|
393 | # developer config: cmdserver.max-log-files | |
394 | maxfiles = ui.configint(b'cmdserver', b'max-log-files') |
|
394 | maxfiles = ui.configint(b'cmdserver', b'max-log-files') | |
395 | # developer config: cmdserver.max-log-size |
|
395 | # developer config: cmdserver.max-log-size | |
396 | maxsize = ui.configbytes(b'cmdserver', b'max-log-size') |
|
396 | maxsize = ui.configbytes(b'cmdserver', b'max-log-size') | |
397 | vfs = vfsmod.vfs(os.path.dirname(logpath)) |
|
397 | vfs = vfsmod.vfs(os.path.dirname(logpath)) | |
398 | logger = loggingutil.filelogger( |
|
398 | logger = loggingutil.filelogger( | |
399 | vfs, |
|
399 | vfs, | |
400 | os.path.basename(logpath), |
|
400 | os.path.basename(logpath), | |
401 | tracked, |
|
401 | tracked, | |
402 | maxfiles=maxfiles, |
|
402 | maxfiles=maxfiles, | |
403 | maxsize=maxsize, |
|
403 | maxsize=maxsize, | |
404 | ) |
|
404 | ) | |
405 |
|
405 | |||
406 | targetuis = {ui} |
|
406 | targetuis = {ui} | |
407 | if repo: |
|
407 | if repo: | |
408 | targetuis.add(repo.baseui) |
|
408 | targetuis.add(repo.baseui) | |
409 | targetuis.add(repo.ui) |
|
409 | targetuis.add(repo.ui) | |
410 | for u in targetuis: |
|
410 | for u in targetuis: | |
411 | u.setlogger(b'cmdserver', logger) |
|
411 | u.setlogger(b'cmdserver', logger) | |
412 |
|
412 | |||
413 |
|
413 | |||
414 | class pipeservice(object): |
|
414 | class pipeservice(object): | |
415 | def __init__(self, ui, repo, opts): |
|
415 | def __init__(self, ui, repo, opts): | |
416 | self.ui = ui |
|
416 | self.ui = ui | |
417 | self.repo = repo |
|
417 | self.repo = repo | |
418 |
|
418 | |||
419 | def init(self): |
|
419 | def init(self): | |
420 | pass |
|
420 | pass | |
421 |
|
421 | |||
422 | def run(self): |
|
422 | def run(self): | |
423 | ui = self.ui |
|
423 | ui = self.ui | |
424 | # redirect stdio to null device so that broken extensions or in-process |
|
424 | # redirect stdio to null device so that broken extensions or in-process | |
425 | # hooks will never cause corruption of channel protocol. |
|
425 | # hooks will never cause corruption of channel protocol. | |
426 | with ui.protectedfinout() as (fin, fout): |
|
426 | with ui.protectedfinout() as (fin, fout): | |
427 | sv = server(ui, self.repo, fin, fout) |
|
427 | sv = server(ui, self.repo, fin, fout) | |
428 | try: |
|
428 | try: | |
429 | return sv.serve() |
|
429 | return sv.serve() | |
430 | finally: |
|
430 | finally: | |
431 | sv.cleanup() |
|
431 | sv.cleanup() | |
432 |
|
432 | |||
433 |
|
433 | |||
434 | def _initworkerprocess(): |
|
434 | def _initworkerprocess(): | |
435 | # use a different process group from the master process, in order to: |
|
435 | # use a different process group from the master process, in order to: | |
436 | # 1. make the current process group no longer "orphaned" (because the |
|
436 | # 1. make the current process group no longer "orphaned" (because the | |
437 | # parent of this process is in a different process group while |
|
437 | # parent of this process is in a different process group while | |
438 | # remains in a same session) |
|
438 | # remains in a same session) | |
439 | # according to POSIX 2.2.2.52, orphaned process group will ignore |
|
439 | # according to POSIX 2.2.2.52, orphaned process group will ignore | |
440 | # terminal-generated stop signals like SIGTSTP (Ctrl+Z), which will |
|
440 | # terminal-generated stop signals like SIGTSTP (Ctrl+Z), which will | |
441 | # cause trouble for things like ncurses. |
|
441 | # cause trouble for things like ncurses. | |
442 | # 2. the client can use kill(-pgid, sig) to simulate terminal-generated |
|
442 | # 2. the client can use kill(-pgid, sig) to simulate terminal-generated | |
443 | # SIGINT (Ctrl+C) and process-exit-generated SIGHUP. our child |
|
443 | # SIGINT (Ctrl+C) and process-exit-generated SIGHUP. our child | |
444 | # processes like ssh will be killed properly, without affecting |
|
444 | # processes like ssh will be killed properly, without affecting | |
445 | # unrelated processes. |
|
445 | # unrelated processes. | |
446 | os.setpgid(0, 0) |
|
446 | os.setpgid(0, 0) | |
447 | # change random state otherwise forked request handlers would have a |
|
447 | # change random state otherwise forked request handlers would have a | |
448 | # same state inherited from parent. |
|
448 | # same state inherited from parent. | |
449 | random.seed() |
|
449 | random.seed() | |
450 |
|
450 | |||
451 |
|
451 | |||
452 | def _serverequest(ui, repo, conn, createcmdserver, prereposetups): |
|
452 | def _serverequest(ui, repo, conn, createcmdserver, prereposetups): | |
453 | fin = conn.makefile('rb') |
|
453 | fin = conn.makefile('rb') | |
454 | fout = conn.makefile('wb') |
|
454 | fout = conn.makefile('wb') | |
455 | sv = None |
|
455 | sv = None | |
456 | try: |
|
456 | try: | |
457 | sv = createcmdserver(repo, conn, fin, fout, prereposetups) |
|
457 | sv = createcmdserver(repo, conn, fin, fout, prereposetups) | |
458 | try: |
|
458 | try: | |
459 | sv.serve() |
|
459 | sv.serve() | |
460 | # handle exceptions that may be raised by command server. most of |
|
460 | # handle exceptions that may be raised by command server. most of | |
461 | # known exceptions are caught by dispatch. |
|
461 | # known exceptions are caught by dispatch. | |
462 | except error.Abort as inst: |
|
462 | except error.Abort as inst: | |
463 | ui.error(_(b'abort: %s\n') % inst) |
|
463 | ui.error(_(b'abort: %s\n') % inst) | |
464 | except IOError as inst: |
|
464 | except IOError as inst: | |
465 | if inst.errno != errno.EPIPE: |
|
465 | if inst.errno != errno.EPIPE: | |
466 | raise |
|
466 | raise | |
467 | except KeyboardInterrupt: |
|
467 | except KeyboardInterrupt: | |
468 | pass |
|
468 | pass | |
469 | finally: |
|
469 | finally: | |
470 | sv.cleanup() |
|
470 | sv.cleanup() | |
471 | except: # re-raises |
|
471 | except: # re-raises | |
472 | # also write traceback to error channel. otherwise client cannot |
|
472 | # also write traceback to error channel. otherwise client cannot | |
473 | # see it because it is written to server's stderr by default. |
|
473 | # see it because it is written to server's stderr by default. | |
474 | if sv: |
|
474 | if sv: | |
475 | cerr = sv.cerr |
|
475 | cerr = sv.cerr | |
476 | else: |
|
476 | else: | |
477 | cerr = channeledoutput(fout, b'e') |
|
477 | cerr = channeledoutput(fout, b'e') | |
478 | cerr.write(encoding.strtolocal(traceback.format_exc())) |
|
478 | cerr.write(encoding.strtolocal(traceback.format_exc())) | |
479 | raise |
|
479 | raise | |
480 | finally: |
|
480 | finally: | |
481 | fin.close() |
|
481 | fin.close() | |
482 | try: |
|
482 | try: | |
483 | fout.close() # implicit flush() may cause another EPIPE |
|
483 | fout.close() # implicit flush() may cause another EPIPE | |
484 | except IOError as inst: |
|
484 | except IOError as inst: | |
485 | if inst.errno != errno.EPIPE: |
|
485 | if inst.errno != errno.EPIPE: | |
486 | raise |
|
486 | raise | |
487 |
|
487 | |||
488 |
|
488 | |||
489 | class unixservicehandler(object): |
|
489 | class unixservicehandler(object): | |
490 | """Set of pluggable operations for unix-mode services |
|
490 | """Set of pluggable operations for unix-mode services | |
491 |
|
491 | |||
492 | Almost all methods except for createcmdserver() are called in the main |
|
492 | Almost all methods except for createcmdserver() are called in the main | |
493 | process. You can't pass mutable resource back from createcmdserver(). |
|
493 | process. You can't pass mutable resource back from createcmdserver(). | |
494 | """ |
|
494 | """ | |
495 |
|
495 | |||
496 | pollinterval = None |
|
496 | pollinterval = None | |
497 |
|
497 | |||
498 | def __init__(self, ui): |
|
498 | def __init__(self, ui): | |
499 | self.ui = ui |
|
499 | self.ui = ui | |
500 |
|
500 | |||
501 | def bindsocket(self, sock, address): |
|
501 | def bindsocket(self, sock, address): | |
502 | util.bindunixsocket(sock, address) |
|
502 | util.bindunixsocket(sock, address) | |
503 | sock.listen(socket.SOMAXCONN) |
|
503 | sock.listen(socket.SOMAXCONN) | |
504 | self.ui.status(_(b'listening at %s\n') % address) |
|
504 | self.ui.status(_(b'listening at %s\n') % address) | |
505 | self.ui.flush() # avoid buffering of status message |
|
505 | self.ui.flush() # avoid buffering of status message | |
506 |
|
506 | |||
507 | def unlinksocket(self, address): |
|
507 | def unlinksocket(self, address): | |
508 | os.unlink(address) |
|
508 | os.unlink(address) | |
509 |
|
509 | |||
510 | def shouldexit(self): |
|
510 | def shouldexit(self): | |
511 | """True if server should shut down; checked per pollinterval""" |
|
511 | """True if server should shut down; checked per pollinterval""" | |
512 | return False |
|
512 | return False | |
513 |
|
513 | |||
514 | def newconnection(self): |
|
514 | def newconnection(self): | |
515 | """Called when main process notices new connection""" |
|
515 | """Called when main process notices new connection""" | |
516 |
|
516 | |||
517 | def createcmdserver(self, repo, conn, fin, fout, prereposetups): |
|
517 | def createcmdserver(self, repo, conn, fin, fout, prereposetups): | |
518 | """Create new command server instance; called in the process that |
|
518 | """Create new command server instance; called in the process that | |
519 | serves for the current connection""" |
|
519 | serves for the current connection""" | |
520 | return server(self.ui, repo, fin, fout, prereposetups) |
|
520 | return server(self.ui, repo, fin, fout, prereposetups) | |
521 |
|
521 | |||
522 |
|
522 | |||
523 | class unixforkingservice(object): |
|
523 | class unixforkingservice(object): | |
524 | """ |
|
524 | """ | |
525 | Listens on unix domain socket and forks server per connection |
|
525 | Listens on unix domain socket and forks server per connection | |
526 | """ |
|
526 | """ | |
527 |
|
527 | |||
528 | def __init__(self, ui, repo, opts, handler=None): |
|
528 | def __init__(self, ui, repo, opts, handler=None): | |
529 | self.ui = ui |
|
529 | self.ui = ui | |
530 | self.repo = repo |
|
530 | self.repo = repo | |
531 | self.address = opts[b'address'] |
|
531 | self.address = opts[b'address'] | |
532 | if not util.safehasattr(socket, b'AF_UNIX'): |
|
532 | if not util.safehasattr(socket, b'AF_UNIX'): | |
533 | raise error.Abort(_(b'unsupported platform')) |
|
533 | raise error.Abort(_(b'unsupported platform')) | |
534 | if not self.address: |
|
534 | if not self.address: | |
535 | raise error.Abort(_(b'no socket path specified with --address')) |
|
535 | raise error.Abort(_(b'no socket path specified with --address')) | |
536 | self._servicehandler = handler or unixservicehandler(ui) |
|
536 | self._servicehandler = handler or unixservicehandler(ui) | |
537 | self._sock = None |
|
537 | self._sock = None | |
538 | self._mainipc = None |
|
538 | self._mainipc = None | |
539 | self._workeripc = None |
|
539 | self._workeripc = None | |
540 | self._oldsigchldhandler = None |
|
540 | self._oldsigchldhandler = None | |
541 | self._workerpids = set() # updated by signal handler; do not iterate |
|
541 | self._workerpids = set() # updated by signal handler; do not iterate | |
542 | self._socketunlinked = None |
|
542 | self._socketunlinked = None | |
543 | # experimental config: cmdserver.max-repo-cache |
|
543 | # experimental config: cmdserver.max-repo-cache | |
544 | maxlen = ui.configint(b'cmdserver', b'max-repo-cache') |
|
544 | maxlen = ui.configint(b'cmdserver', b'max-repo-cache') | |
545 | if maxlen < 0: |
|
545 | if maxlen < 0: | |
546 | raise error.Abort(_(b'negative max-repo-cache size not allowed')) |
|
546 | raise error.Abort(_(b'negative max-repo-cache size not allowed')) | |
547 | self._repoloader = repocache.repoloader(ui, maxlen) |
|
547 | self._repoloader = repocache.repoloader(ui, maxlen) | |
548 | # attempt to avoid crash in CoreFoundation when using chg after fix in |
|
548 | # attempt to avoid crash in CoreFoundation when using chg after fix in | |
549 | # a89381e04c58 |
|
549 | # a89381e04c58 | |
550 | if pycompat.isdarwin: |
|
550 | if pycompat.isdarwin: | |
551 | procutil.gui() |
|
551 | procutil.gui() | |
552 |
|
552 | |||
553 | def init(self): |
|
553 | def init(self): | |
554 | self._sock = socket.socket(socket.AF_UNIX) |
|
554 | self._sock = socket.socket(socket.AF_UNIX) | |
555 | # IPC channel from many workers to one main process; this is actually |
|
555 | # IPC channel from many workers to one main process; this is actually | |
556 | # a uni-directional pipe, but is backed by a DGRAM socket so each |
|
556 | # a uni-directional pipe, but is backed by a DGRAM socket so each | |
557 | # message can be easily separated. |
|
557 | # message can be easily separated. | |
558 | o = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM) |
|
558 | o = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM) | |
559 | self._mainipc, self._workeripc = o |
|
559 | self._mainipc, self._workeripc = o | |
560 | self._servicehandler.bindsocket(self._sock, self.address) |
|
560 | self._servicehandler.bindsocket(self._sock, self.address) | |
561 | if util.safehasattr(procutil, b'unblocksignal'): |
|
561 | if util.safehasattr(procutil, b'unblocksignal'): | |
562 | procutil.unblocksignal(signal.SIGCHLD) |
|
562 | procutil.unblocksignal(signal.SIGCHLD) | |
563 | o = signal.signal(signal.SIGCHLD, self._sigchldhandler) |
|
563 | o = signal.signal(signal.SIGCHLD, self._sigchldhandler) | |
564 | self._oldsigchldhandler = o |
|
564 | self._oldsigchldhandler = o | |
565 | self._socketunlinked = False |
|
565 | self._socketunlinked = False | |
566 | self._repoloader.start() |
|
566 | self._repoloader.start() | |
567 |
|
567 | |||
568 | def _unlinksocket(self): |
|
568 | def _unlinksocket(self): | |
569 | if not self._socketunlinked: |
|
569 | if not self._socketunlinked: | |
570 | self._servicehandler.unlinksocket(self.address) |
|
570 | self._servicehandler.unlinksocket(self.address) | |
571 | self._socketunlinked = True |
|
571 | self._socketunlinked = True | |
572 |
|
572 | |||
573 | def _cleanup(self): |
|
573 | def _cleanup(self): | |
574 | signal.signal(signal.SIGCHLD, self._oldsigchldhandler) |
|
574 | signal.signal(signal.SIGCHLD, self._oldsigchldhandler) | |
575 | self._sock.close() |
|
575 | self._sock.close() | |
576 | self._mainipc.close() |
|
576 | self._mainipc.close() | |
577 | self._workeripc.close() |
|
577 | self._workeripc.close() | |
578 | self._unlinksocket() |
|
578 | self._unlinksocket() | |
579 | self._repoloader.stop() |
|
579 | self._repoloader.stop() | |
580 | # don't kill child processes as they have active clients, just wait |
|
580 | # don't kill child processes as they have active clients, just wait | |
581 | self._reapworkers(0) |
|
581 | self._reapworkers(0) | |
582 |
|
582 | |||
583 | def run(self): |
|
583 | def run(self): | |
584 | try: |
|
584 | try: | |
585 | self._mainloop() |
|
585 | self._mainloop() | |
586 | finally: |
|
586 | finally: | |
587 | self._cleanup() |
|
587 | self._cleanup() | |
588 |
|
588 | |||
589 | def _mainloop(self): |
|
589 | def _mainloop(self): | |
590 | exiting = False |
|
590 | exiting = False | |
591 | h = self._servicehandler |
|
591 | h = self._servicehandler | |
592 | selector = selectors.DefaultSelector() |
|
592 | selector = selectors.DefaultSelector() | |
593 | selector.register( |
|
593 | selector.register( | |
594 | self._sock, selectors.EVENT_READ, self._acceptnewconnection |
|
594 | self._sock, selectors.EVENT_READ, self._acceptnewconnection | |
595 | ) |
|
595 | ) | |
596 | selector.register( |
|
596 | selector.register( | |
597 | self._mainipc, selectors.EVENT_READ, self._handlemainipc |
|
597 | self._mainipc, selectors.EVENT_READ, self._handlemainipc | |
598 | ) |
|
598 | ) | |
599 | while True: |
|
599 | while True: | |
600 | if not exiting and h.shouldexit(): |
|
600 | if not exiting and h.shouldexit(): | |
601 | # clients can no longer connect() to the domain socket, so |
|
601 | # clients can no longer connect() to the domain socket, so | |
602 | # we stop queuing new requests. |
|
602 | # we stop queuing new requests. | |
603 | # for requests that are queued (connect()-ed, but haven't been |
|
603 | # for requests that are queued (connect()-ed, but haven't been | |
604 | # accept()-ed), handle them before exit. otherwise, clients |
|
604 | # accept()-ed), handle them before exit. otherwise, clients | |
605 | # waiting for recv() will receive ECONNRESET. |
|
605 | # waiting for recv() will receive ECONNRESET. | |
606 | self._unlinksocket() |
|
606 | self._unlinksocket() | |
607 | exiting = True |
|
607 | exiting = True | |
608 | try: |
|
608 | try: | |
609 | events = selector.select(timeout=h.pollinterval) |
|
609 | events = selector.select(timeout=h.pollinterval) | |
610 | except OSError as inst: |
|
610 | except OSError as inst: | |
611 | # selectors2 raises ETIMEDOUT if timeout exceeded while |
|
611 | # selectors2 raises ETIMEDOUT if timeout exceeded while | |
612 | # handling signal interrupt. That's probably wrong, but |
|
612 | # handling signal interrupt. That's probably wrong, but | |
613 | # we can easily get around it. |
|
613 | # we can easily get around it. | |
614 | if inst.errno != errno.ETIMEDOUT: |
|
614 | if inst.errno != errno.ETIMEDOUT: | |
615 | raise |
|
615 | raise | |
616 | events = [] |
|
616 | events = [] | |
617 | if not events: |
|
617 | if not events: | |
618 | # only exit if we completed all queued requests |
|
618 | # only exit if we completed all queued requests | |
619 | if exiting: |
|
619 | if exiting: | |
620 | break |
|
620 | break | |
621 | continue |
|
621 | continue | |
622 | for key, _mask in events: |
|
622 | for key, _mask in events: | |
623 | key.data(key.fileobj, selector) |
|
623 | key.data(key.fileobj, selector) | |
624 | selector.close() |
|
624 | selector.close() | |
625 |
|
625 | |||
626 | def _acceptnewconnection(self, sock, selector): |
|
626 | def _acceptnewconnection(self, sock, selector): | |
627 | h = self._servicehandler |
|
627 | h = self._servicehandler | |
628 | try: |
|
628 | try: | |
629 | conn, _addr = sock.accept() |
|
629 | conn, _addr = sock.accept() | |
630 | except socket.error as inst: |
|
630 | except socket.error as inst: | |
631 | if inst.args[0] == errno.EINTR: |
|
631 | if inst.args[0] == errno.EINTR: | |
632 | return |
|
632 | return | |
633 | raise |
|
633 | raise | |
634 |
|
634 | |||
635 | # Future improvement: On Python 3.7, maybe gc.freeze() can be used |
|
635 | # Future improvement: On Python 3.7, maybe gc.freeze() can be used | |
636 | # to prevent COW memory from being touched by GC. |
|
636 | # to prevent COW memory from being touched by GC. | |
637 | # https://instagram-engineering.com/ |
|
637 | # https://instagram-engineering.com/ | |
638 | # copy-on-write-friendly-python-garbage-collection-ad6ed5233ddf |
|
638 | # copy-on-write-friendly-python-garbage-collection-ad6ed5233ddf | |
639 | pid = os.fork() |
|
639 | pid = os.fork() | |
640 | if pid: |
|
640 | if pid: | |
641 | try: |
|
641 | try: | |
642 | self.ui.log( |
|
642 | self.ui.log( | |
643 | b'cmdserver', b'forked worker process (pid=%d)\n', pid |
|
643 | b'cmdserver', b'forked worker process (pid=%d)\n', pid | |
644 | ) |
|
644 | ) | |
645 | self._workerpids.add(pid) |
|
645 | self._workerpids.add(pid) | |
646 | h.newconnection() |
|
646 | h.newconnection() | |
647 | finally: |
|
647 | finally: | |
648 | conn.close() # release handle in parent process |
|
648 | conn.close() # release handle in parent process | |
649 | else: |
|
649 | else: | |
650 | try: |
|
650 | try: | |
651 | selector.close() |
|
651 | selector.close() | |
652 | sock.close() |
|
652 | sock.close() | |
653 | self._mainipc.close() |
|
653 | self._mainipc.close() | |
654 | self._runworker(conn) |
|
654 | self._runworker(conn) | |
655 | conn.close() |
|
655 | conn.close() | |
656 | self._workeripc.close() |
|
656 | self._workeripc.close() | |
657 | os._exit(0) |
|
657 | os._exit(0) | |
658 | except: # never return, hence no re-raises |
|
658 | except: # never return, hence no re-raises | |
659 | try: |
|
659 | try: | |
660 | self.ui.traceback(force=True) |
|
660 | self.ui.traceback(force=True) | |
661 | finally: |
|
661 | finally: | |
662 | os._exit(255) |
|
662 | os._exit(255) | |
663 |
|
663 | |||
664 | def _handlemainipc(self, sock, selector): |
|
664 | def _handlemainipc(self, sock, selector): | |
665 | """Process messages sent from a worker""" |
|
665 | """Process messages sent from a worker""" | |
666 | try: |
|
666 | try: | |
667 | path = sock.recv(32768) # large enough to receive path |
|
667 | path = sock.recv(32768) # large enough to receive path | |
668 | except socket.error as inst: |
|
668 | except socket.error as inst: | |
669 | if inst.args[0] == errno.EINTR: |
|
669 | if inst.args[0] == errno.EINTR: | |
670 | return |
|
670 | return | |
671 | raise |
|
671 | raise | |
672 | self._repoloader.load(path) |
|
672 | self._repoloader.load(path) | |
673 |
|
673 | |||
674 | def _sigchldhandler(self, signal, frame): |
|
674 | def _sigchldhandler(self, signal, frame): | |
675 | self._reapworkers(os.WNOHANG) |
|
675 | self._reapworkers(os.WNOHANG) | |
676 |
|
676 | |||
677 | def _reapworkers(self, options): |
|
677 | def _reapworkers(self, options): | |
678 | while self._workerpids: |
|
678 | while self._workerpids: | |
679 | try: |
|
679 | try: | |
680 | pid, _status = os.waitpid(-1, options) |
|
680 | pid, _status = os.waitpid(-1, options) | |
681 | except OSError as inst: |
|
681 | except OSError as inst: | |
682 | if inst.errno == errno.EINTR: |
|
682 | if inst.errno == errno.EINTR: | |
683 | continue |
|
683 | continue | |
684 | if inst.errno != errno.ECHILD: |
|
684 | if inst.errno != errno.ECHILD: | |
685 | raise |
|
685 | raise | |
686 | # no child processes at all (reaped by other waitpid()?) |
|
686 | # no child processes at all (reaped by other waitpid()?) | |
687 | self._workerpids.clear() |
|
687 | self._workerpids.clear() | |
688 | return |
|
688 | return | |
689 | if pid == 0: |
|
689 | if pid == 0: | |
690 | # no waitable child processes |
|
690 | # no waitable child processes | |
691 | return |
|
691 | return | |
692 | self.ui.log(b'cmdserver', b'worker process exited (pid=%d)\n', pid) |
|
692 | self.ui.log(b'cmdserver', b'worker process exited (pid=%d)\n', pid) | |
693 | self._workerpids.discard(pid) |
|
693 | self._workerpids.discard(pid) | |
694 |
|
694 | |||
695 | def _runworker(self, conn): |
|
695 | def _runworker(self, conn): | |
696 | signal.signal(signal.SIGCHLD, self._oldsigchldhandler) |
|
696 | signal.signal(signal.SIGCHLD, self._oldsigchldhandler) | |
697 | _initworkerprocess() |
|
697 | _initworkerprocess() | |
698 | h = self._servicehandler |
|
698 | h = self._servicehandler | |
699 | try: |
|
699 | try: | |
700 | _serverequest( |
|
700 | _serverequest( | |
701 | self.ui, |
|
701 | self.ui, | |
702 | self.repo, |
|
702 | self.repo, | |
703 | conn, |
|
703 | conn, | |
704 | h.createcmdserver, |
|
704 | h.createcmdserver, | |
705 | prereposetups=[self._reposetup], |
|
705 | prereposetups=[self._reposetup], | |
706 | ) |
|
706 | ) | |
707 | finally: |
|
707 | finally: | |
708 | gc.collect() # trigger __del__ since worker process uses os._exit |
|
708 | gc.collect() # trigger __del__ since worker process uses os._exit | |
709 |
|
709 | |||
710 | def _reposetup(self, ui, repo): |
|
710 | def _reposetup(self, ui, repo): | |
711 | if not repo.local(): |
|
711 | if not repo.local(): | |
712 | return |
|
712 | return | |
713 |
|
713 | |||
714 | class unixcmdserverrepo(repo.__class__): |
|
714 | class unixcmdserverrepo(repo.__class__): | |
715 | def close(self): |
|
715 | def close(self): | |
716 | super(unixcmdserverrepo, self).close() |
|
716 | super(unixcmdserverrepo, self).close() | |
717 | try: |
|
717 | try: | |
718 | self._cmdserveripc.send(self.root) |
|
718 | self._cmdserveripc.send(self.root) | |
719 | except socket.error: |
|
719 | except socket.error: | |
720 | self.ui.log( |
|
720 | self.ui.log( | |
721 | b'cmdserver', b'failed to send repo root to master\n' |
|
721 | b'cmdserver', b'failed to send repo root to master\n' | |
722 | ) |
|
722 | ) | |
723 |
|
723 | |||
724 | repo.__class__ = unixcmdserverrepo |
|
724 | repo.__class__ = unixcmdserverrepo | |
725 | repo._cmdserveripc = self._workeripc |
|
725 | repo._cmdserveripc = self._workeripc | |
726 |
|
726 | |||
727 | cachedrepo = self._repoloader.get(repo.root) |
|
727 | cachedrepo = self._repoloader.get(repo.root) | |
728 | if cachedrepo is None: |
|
728 | if cachedrepo is None: | |
729 | return |
|
729 | return | |
730 | repo.ui.log(b'repocache', b'repo from cache: %s\n', repo.root) |
|
730 | repo.ui.log(b'repocache', b'repo from cache: %s\n', repo.root) | |
731 | repocache.copycache(cachedrepo, repo) |
|
731 | repocache.copycache(cachedrepo, repo) |
@@ -1,1182 +1,1182 b'' | |||||
1 | # shelve.py - save/restore working directory state |
|
1 | # shelve.py - save/restore working directory state | |
2 | # |
|
2 | # | |
3 | # Copyright 2013 Facebook, Inc. |
|
3 | # Copyright 2013 Facebook, Inc. | |
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2 or any later version. |
|
6 | # GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | """save and restore changes to the working directory |
|
8 | """save and restore changes to the working directory | |
9 |
|
9 | |||
10 | The "hg shelve" command saves changes made to the working directory |
|
10 | The "hg shelve" command saves changes made to the working directory | |
11 | and reverts those changes, resetting the working directory to a clean |
|
11 | and reverts those changes, resetting the working directory to a clean | |
12 | state. |
|
12 | state. | |
13 |
|
13 | |||
14 | Later on, the "hg unshelve" command restores the changes saved by "hg |
|
14 | Later on, the "hg unshelve" command restores the changes saved by "hg | |
15 | shelve". Changes can be restored even after updating to a different |
|
15 | shelve". Changes can be restored even after updating to a different | |
16 | parent, in which case Mercurial's merge machinery will resolve any |
|
16 | parent, in which case Mercurial's merge machinery will resolve any | |
17 | conflicts if necessary. |
|
17 | conflicts if necessary. | |
18 |
|
18 | |||
19 | You can have more than one shelved change outstanding at a time; each |
|
19 | You can have more than one shelved change outstanding at a time; each | |
20 | shelved change has a distinct name. For details, see the help for "hg |
|
20 | shelved change has a distinct name. For details, see the help for "hg | |
21 | shelve". |
|
21 | shelve". | |
22 | """ |
|
22 | """ | |
23 | from __future__ import absolute_import |
|
23 | from __future__ import absolute_import | |
24 |
|
24 | |||
25 | import collections |
|
25 | import collections | |
26 | import errno |
|
26 | import errno | |
27 | import itertools |
|
27 | import itertools | |
28 | import stat |
|
28 | import stat | |
29 |
|
29 | |||
30 | from .i18n import _ |
|
30 | from .i18n import _ | |
31 | from .pycompat import open |
|
31 | from .pycompat import open | |
32 | from . import ( |
|
32 | from . import ( | |
33 | bookmarks, |
|
33 | bookmarks, | |
34 | bundle2, |
|
34 | bundle2, | |
35 | bundlerepo, |
|
35 | bundlerepo, | |
36 | changegroup, |
|
36 | changegroup, | |
37 | cmdutil, |
|
37 | cmdutil, | |
38 | discovery, |
|
38 | discovery, | |
39 | error, |
|
39 | error, | |
40 | exchange, |
|
40 | exchange, | |
41 | hg, |
|
41 | hg, | |
42 | lock as lockmod, |
|
42 | lock as lockmod, | |
43 | mdiff, |
|
43 | mdiff, | |
44 | merge, |
|
44 | merge, | |
45 | node as nodemod, |
|
45 | node as nodemod, | |
46 | patch, |
|
46 | patch, | |
47 | phases, |
|
47 | phases, | |
48 | pycompat, |
|
48 | pycompat, | |
49 | repair, |
|
49 | repair, | |
50 | scmutil, |
|
50 | scmutil, | |
51 | templatefilters, |
|
51 | templatefilters, | |
52 | util, |
|
52 | util, | |
53 | vfs as vfsmod, |
|
53 | vfs as vfsmod, | |
54 | ) |
|
54 | ) | |
55 | from .utils import ( |
|
55 | from .utils import ( | |
56 | dateutil, |
|
56 | dateutil, | |
57 | stringutil, |
|
57 | stringutil, | |
58 | ) |
|
58 | ) | |
59 |
|
59 | |||
60 | backupdir = b'shelve-backup' |
|
60 | backupdir = b'shelve-backup' | |
61 | shelvedir = b'shelved' |
|
61 | shelvedir = b'shelved' | |
62 | shelvefileextensions = [b'hg', b'patch', b'shelve'] |
|
62 | shelvefileextensions = [b'hg', b'patch', b'shelve'] | |
63 | # universal extension is present in all types of shelves |
|
63 | # universal extension is present in all types of shelves | |
64 | patchextension = b'patch' |
|
64 | patchextension = b'patch' | |
65 |
|
65 | |||
66 | # we never need the user, so we use a |
|
66 | # we never need the user, so we use a | |
67 | # generic user for all shelve operations |
|
67 | # generic user for all shelve operations | |
68 | shelveuser = b'shelve@localhost' |
|
68 | shelveuser = b'shelve@localhost' | |
69 |
|
69 | |||
70 |
|
70 | |||
71 | class shelvedfile(object): |
|
71 | class shelvedfile(object): | |
72 | """Helper for the file storing a single shelve |
|
72 | """Helper for the file storing a single shelve | |
73 |
|
73 | |||
74 | Handles common functions on shelve files (.hg/.patch) using |
|
74 | Handles common functions on shelve files (.hg/.patch) using | |
75 | the vfs layer""" |
|
75 | the vfs layer""" | |
76 |
|
76 | |||
77 | def __init__(self, repo, name, filetype=None): |
|
77 | def __init__(self, repo, name, filetype=None): | |
78 | self.repo = repo |
|
78 | self.repo = repo | |
79 | self.name = name |
|
79 | self.name = name | |
80 | self.vfs = vfsmod.vfs(repo.vfs.join(shelvedir)) |
|
80 | self.vfs = vfsmod.vfs(repo.vfs.join(shelvedir)) | |
81 | self.backupvfs = vfsmod.vfs(repo.vfs.join(backupdir)) |
|
81 | self.backupvfs = vfsmod.vfs(repo.vfs.join(backupdir)) | |
82 | self.ui = self.repo.ui |
|
82 | self.ui = self.repo.ui | |
83 | if filetype: |
|
83 | if filetype: | |
84 | self.fname = name + b'.' + filetype |
|
84 | self.fname = name + b'.' + filetype | |
85 | else: |
|
85 | else: | |
86 | self.fname = name |
|
86 | self.fname = name | |
87 |
|
87 | |||
88 | def exists(self): |
|
88 | def exists(self): | |
89 | return self.vfs.exists(self.fname) |
|
89 | return self.vfs.exists(self.fname) | |
90 |
|
90 | |||
91 | def filename(self): |
|
91 | def filename(self): | |
92 | return self.vfs.join(self.fname) |
|
92 | return self.vfs.join(self.fname) | |
93 |
|
93 | |||
94 | def backupfilename(self): |
|
94 | def backupfilename(self): | |
95 | def gennames(base): |
|
95 | def gennames(base): | |
96 | yield base |
|
96 | yield base | |
97 | base, ext = base.rsplit(b'.', 1) |
|
97 | base, ext = base.rsplit(b'.', 1) | |
98 | for i in itertools.count(1): |
|
98 | for i in itertools.count(1): | |
99 | yield b'%s-%d.%s' % (base, i, ext) |
|
99 | yield b'%s-%d.%s' % (base, i, ext) | |
100 |
|
100 | |||
101 | name = self.backupvfs.join(self.fname) |
|
101 | name = self.backupvfs.join(self.fname) | |
102 | for n in gennames(name): |
|
102 | for n in gennames(name): | |
103 | if not self.backupvfs.exists(n): |
|
103 | if not self.backupvfs.exists(n): | |
104 | return n |
|
104 | return n | |
105 |
|
105 | |||
106 | def movetobackup(self): |
|
106 | def movetobackup(self): | |
107 | if not self.backupvfs.isdir(): |
|
107 | if not self.backupvfs.isdir(): | |
108 | self.backupvfs.makedir() |
|
108 | self.backupvfs.makedir() | |
109 | util.rename(self.filename(), self.backupfilename()) |
|
109 | util.rename(self.filename(), self.backupfilename()) | |
110 |
|
110 | |||
111 | def stat(self): |
|
111 | def stat(self): | |
112 | return self.vfs.stat(self.fname) |
|
112 | return self.vfs.stat(self.fname) | |
113 |
|
113 | |||
114 | def opener(self, mode=b'rb'): |
|
114 | def opener(self, mode=b'rb'): | |
115 | try: |
|
115 | try: | |
116 | return self.vfs(self.fname, mode) |
|
116 | return self.vfs(self.fname, mode) | |
117 | except IOError as err: |
|
117 | except IOError as err: | |
118 | if err.errno != errno.ENOENT: |
|
118 | if err.errno != errno.ENOENT: | |
119 | raise |
|
119 | raise | |
120 | raise error.Abort(_(b"shelved change '%s' not found") % self.name) |
|
120 | raise error.Abort(_(b"shelved change '%s' not found") % self.name) | |
121 |
|
121 | |||
122 | def applybundle(self, tr): |
|
122 | def applybundle(self, tr): | |
123 | fp = self.opener() |
|
123 | fp = self.opener() | |
124 | try: |
|
124 | try: | |
125 | targetphase = phases.internal |
|
125 | targetphase = phases.internal | |
126 | if not phases.supportinternal(self.repo): |
|
126 | if not phases.supportinternal(self.repo): | |
127 | targetphase = phases.secret |
|
127 | targetphase = phases.secret | |
128 | gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs) |
|
128 | gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs) | |
129 | pretip = self.repo[b'tip'] |
|
129 | pretip = self.repo[b'tip'] | |
130 | bundle2.applybundle( |
|
130 | bundle2.applybundle( | |
131 | self.repo, |
|
131 | self.repo, | |
132 | gen, |
|
132 | gen, | |
133 | tr, |
|
133 | tr, | |
134 | source=b'unshelve', |
|
134 | source=b'unshelve', | |
135 | url=b'bundle:' + self.vfs.join(self.fname), |
|
135 | url=b'bundle:' + self.vfs.join(self.fname), | |
136 | targetphase=targetphase, |
|
136 | targetphase=targetphase, | |
137 | ) |
|
137 | ) | |
138 | shelvectx = self.repo[b'tip'] |
|
138 | shelvectx = self.repo[b'tip'] | |
139 | if pretip == shelvectx: |
|
139 | if pretip == shelvectx: | |
140 | shelverev = tr.changes[b'revduplicates'][-1] |
|
140 | shelverev = tr.changes[b'revduplicates'][-1] | |
141 | shelvectx = self.repo[shelverev] |
|
141 | shelvectx = self.repo[shelverev] | |
142 | return shelvectx |
|
142 | return shelvectx | |
143 | finally: |
|
143 | finally: | |
144 | fp.close() |
|
144 | fp.close() | |
145 |
|
145 | |||
146 | def bundlerepo(self): |
|
146 | def bundlerepo(self): | |
147 | path = self.vfs.join(self.fname) |
|
147 | path = self.vfs.join(self.fname) | |
148 | return bundlerepo.instance( |
|
148 | return bundlerepo.instance( | |
149 | self.repo.baseui, b'bundle://%s+%s' % (self.repo.root, path), False |
|
149 | self.repo.baseui, b'bundle://%s+%s' % (self.repo.root, path), False | |
150 | ) |
|
150 | ) | |
151 |
|
151 | |||
152 | def writebundle(self, bases, node): |
|
152 | def writebundle(self, bases, node): | |
153 | cgversion = changegroup.safeversion(self.repo) |
|
153 | cgversion = changegroup.safeversion(self.repo) | |
154 | if cgversion == b'01': |
|
154 | if cgversion == b'01': | |
155 | btype = b'HG10BZ' |
|
155 | btype = b'HG10BZ' | |
156 | compression = None |
|
156 | compression = None | |
157 | else: |
|
157 | else: | |
158 | btype = b'HG20' |
|
158 | btype = b'HG20' | |
159 | compression = b'BZ' |
|
159 | compression = b'BZ' | |
160 |
|
160 | |||
161 | repo = self.repo.unfiltered() |
|
161 | repo = self.repo.unfiltered() | |
162 |
|
162 | |||
163 | outgoing = discovery.outgoing( |
|
163 | outgoing = discovery.outgoing( | |
164 | repo, missingroots=bases, missingheads=[node] |
|
164 | repo, missingroots=bases, missingheads=[node] | |
165 | ) |
|
165 | ) | |
166 | cg = changegroup.makechangegroup(repo, outgoing, cgversion, b'shelve') |
|
166 | cg = changegroup.makechangegroup(repo, outgoing, cgversion, b'shelve') | |
167 |
|
167 | |||
168 | bundle2.writebundle( |
|
168 | bundle2.writebundle( | |
169 | self.ui, cg, self.fname, btype, self.vfs, compression=compression |
|
169 | self.ui, cg, self.fname, btype, self.vfs, compression=compression | |
170 | ) |
|
170 | ) | |
171 |
|
171 | |||
172 | def writeinfo(self, info): |
|
172 | def writeinfo(self, info): | |
173 | scmutil.simplekeyvaluefile(self.vfs, self.fname).write(info) |
|
173 | scmutil.simplekeyvaluefile(self.vfs, self.fname).write(info) | |
174 |
|
174 | |||
175 | def readinfo(self): |
|
175 | def readinfo(self): | |
176 | return scmutil.simplekeyvaluefile(self.vfs, self.fname).read() |
|
176 | return scmutil.simplekeyvaluefile(self.vfs, self.fname).read() | |
177 |
|
177 | |||
178 |
|
178 | |||
179 | class shelvedstate(object): |
|
179 | class shelvedstate(object): | |
180 | """Handle persistence during unshelving operations. |
|
180 | """Handle persistence during unshelving operations. | |
181 |
|
181 | |||
182 | Handles saving and restoring a shelved state. Ensures that different |
|
182 | Handles saving and restoring a shelved state. Ensures that different | |
183 | versions of a shelved state are possible and handles them appropriately. |
|
183 | versions of a shelved state are possible and handles them appropriately. | |
184 | """ |
|
184 | """ | |
185 |
|
185 | |||
186 | _version = 2 |
|
186 | _version = 2 | |
187 | _filename = b'shelvedstate' |
|
187 | _filename = b'shelvedstate' | |
188 | _keep = b'keep' |
|
188 | _keep = b'keep' | |
189 | _nokeep = b'nokeep' |
|
189 | _nokeep = b'nokeep' | |
190 | # colon is essential to differentiate from a real bookmark name |
|
190 | # colon is essential to differentiate from a real bookmark name | |
191 | _noactivebook = b':no-active-bookmark' |
|
191 | _noactivebook = b':no-active-bookmark' | |
192 | _interactive = b'interactive' |
|
192 | _interactive = b'interactive' | |
193 |
|
193 | |||
194 | @classmethod |
|
194 | @classmethod | |
195 | def _verifyandtransform(cls, d): |
|
195 | def _verifyandtransform(cls, d): | |
196 | """Some basic shelvestate syntactic verification and transformation""" |
|
196 | """Some basic shelvestate syntactic verification and transformation""" | |
197 | try: |
|
197 | try: | |
198 | d[b'originalwctx'] = nodemod.bin(d[b'originalwctx']) |
|
198 | d[b'originalwctx'] = nodemod.bin(d[b'originalwctx']) | |
199 | d[b'pendingctx'] = nodemod.bin(d[b'pendingctx']) |
|
199 | d[b'pendingctx'] = nodemod.bin(d[b'pendingctx']) | |
200 | d[b'parents'] = [nodemod.bin(h) for h in d[b'parents'].split(b' ')] |
|
200 | d[b'parents'] = [nodemod.bin(h) for h in d[b'parents'].split(b' ')] | |
201 | d[b'nodestoremove'] = [ |
|
201 | d[b'nodestoremove'] = [ | |
202 | nodemod.bin(h) for h in d[b'nodestoremove'].split(b' ') |
|
202 | nodemod.bin(h) for h in d[b'nodestoremove'].split(b' ') | |
203 | ] |
|
203 | ] | |
204 | except (ValueError, TypeError, KeyError) as err: |
|
204 | except (ValueError, TypeError, KeyError) as err: | |
205 | raise error.CorruptedState(pycompat.bytestr(err)) |
|
205 | raise error.CorruptedState(pycompat.bytestr(err)) | |
206 |
|
206 | |||
207 | @classmethod |
|
207 | @classmethod | |
208 | def _getversion(cls, repo): |
|
208 | def _getversion(cls, repo): | |
209 | """Read version information from shelvestate file""" |
|
209 | """Read version information from shelvestate file""" | |
210 | fp = repo.vfs(cls._filename) |
|
210 | fp = repo.vfs(cls._filename) | |
211 | try: |
|
211 | try: | |
212 | version = int(fp.readline().strip()) |
|
212 | version = int(fp.readline().strip()) | |
213 | except ValueError as err: |
|
213 | except ValueError as err: | |
214 | raise error.CorruptedState(pycompat.bytestr(err)) |
|
214 | raise error.CorruptedState(pycompat.bytestr(err)) | |
215 | finally: |
|
215 | finally: | |
216 | fp.close() |
|
216 | fp.close() | |
217 | return version |
|
217 | return version | |
218 |
|
218 | |||
219 | @classmethod |
|
219 | @classmethod | |
220 | def _readold(cls, repo): |
|
220 | def _readold(cls, repo): | |
221 | """Read the old position-based version of a shelvestate file""" |
|
221 | """Read the old position-based version of a shelvestate file""" | |
222 | # Order is important, because old shelvestate file uses it |
|
222 | # Order is important, because old shelvestate file uses it | |
223 | # to detemine values of fields (i.g. name is on the second line, |
|
223 | # to detemine values of fields (i.g. name is on the second line, | |
224 | # originalwctx is on the third and so forth). Please do not change. |
|
224 | # originalwctx is on the third and so forth). Please do not change. | |
225 | keys = [ |
|
225 | keys = [ | |
226 | b'version', |
|
226 | b'version', | |
227 | b'name', |
|
227 | b'name', | |
228 | b'originalwctx', |
|
228 | b'originalwctx', | |
229 | b'pendingctx', |
|
229 | b'pendingctx', | |
230 | b'parents', |
|
230 | b'parents', | |
231 | b'nodestoremove', |
|
231 | b'nodestoremove', | |
232 | b'branchtorestore', |
|
232 | b'branchtorestore', | |
233 | b'keep', |
|
233 | b'keep', | |
234 | b'activebook', |
|
234 | b'activebook', | |
235 | ] |
|
235 | ] | |
236 | # this is executed only seldomly, so it is not a big deal |
|
236 | # this is executed only seldomly, so it is not a big deal | |
237 | # that we open this file twice |
|
237 | # that we open this file twice | |
238 | fp = repo.vfs(cls._filename) |
|
238 | fp = repo.vfs(cls._filename) | |
239 | d = {} |
|
239 | d = {} | |
240 | try: |
|
240 | try: | |
241 | for key in keys: |
|
241 | for key in keys: | |
242 | d[key] = fp.readline().strip() |
|
242 | d[key] = fp.readline().strip() | |
243 | finally: |
|
243 | finally: | |
244 | fp.close() |
|
244 | fp.close() | |
245 | return d |
|
245 | return d | |
246 |
|
246 | |||
247 | @classmethod |
|
247 | @classmethod | |
248 | def load(cls, repo): |
|
248 | def load(cls, repo): | |
249 | version = cls._getversion(repo) |
|
249 | version = cls._getversion(repo) | |
250 | if version < cls._version: |
|
250 | if version < cls._version: | |
251 | d = cls._readold(repo) |
|
251 | d = cls._readold(repo) | |
252 | elif version == cls._version: |
|
252 | elif version == cls._version: | |
253 | d = scmutil.simplekeyvaluefile(repo.vfs, cls._filename).read( |
|
253 | d = scmutil.simplekeyvaluefile(repo.vfs, cls._filename).read( | |
254 | firstlinenonkeyval=True |
|
254 | firstlinenonkeyval=True | |
255 | ) |
|
255 | ) | |
256 | else: |
|
256 | else: | |
257 | raise error.Abort( |
|
257 | raise error.Abort( | |
258 | _( |
|
258 | _( | |
259 | b'this version of shelve is incompatible ' |
|
259 | b'this version of shelve is incompatible ' | |
260 | b'with the version used in this repo' |
|
260 | b'with the version used in this repo' | |
261 | ) |
|
261 | ) | |
262 | ) |
|
262 | ) | |
263 |
|
263 | |||
264 | cls._verifyandtransform(d) |
|
264 | cls._verifyandtransform(d) | |
265 | try: |
|
265 | try: | |
266 | obj = cls() |
|
266 | obj = cls() | |
267 | obj.name = d[b'name'] |
|
267 | obj.name = d[b'name'] | |
268 | obj.wctx = repo[d[b'originalwctx']] |
|
268 | obj.wctx = repo[d[b'originalwctx']] | |
269 | obj.pendingctx = repo[d[b'pendingctx']] |
|
269 | obj.pendingctx = repo[d[b'pendingctx']] | |
270 | obj.parents = d[b'parents'] |
|
270 | obj.parents = d[b'parents'] | |
271 | obj.nodestoremove = d[b'nodestoremove'] |
|
271 | obj.nodestoremove = d[b'nodestoremove'] | |
272 | obj.branchtorestore = d.get(b'branchtorestore', b'') |
|
272 | obj.branchtorestore = d.get(b'branchtorestore', b'') | |
273 | obj.keep = d.get(b'keep') == cls._keep |
|
273 | obj.keep = d.get(b'keep') == cls._keep | |
274 | obj.activebookmark = b'' |
|
274 | obj.activebookmark = b'' | |
275 | if d.get(b'activebook', b'') != cls._noactivebook: |
|
275 | if d.get(b'activebook', b'') != cls._noactivebook: | |
276 | obj.activebookmark = d.get(b'activebook', b'') |
|
276 | obj.activebookmark = d.get(b'activebook', b'') | |
277 | obj.interactive = d.get(b'interactive') == cls._interactive |
|
277 | obj.interactive = d.get(b'interactive') == cls._interactive | |
278 | except (error.RepoLookupError, KeyError) as err: |
|
278 | except (error.RepoLookupError, KeyError) as err: | |
279 | raise error.CorruptedState(pycompat.bytestr(err)) |
|
279 | raise error.CorruptedState(pycompat.bytestr(err)) | |
280 |
|
280 | |||
281 | return obj |
|
281 | return obj | |
282 |
|
282 | |||
283 | @classmethod |
|
283 | @classmethod | |
284 | def save( |
|
284 | def save( | |
285 | cls, |
|
285 | cls, | |
286 | repo, |
|
286 | repo, | |
287 | name, |
|
287 | name, | |
288 | originalwctx, |
|
288 | originalwctx, | |
289 | pendingctx, |
|
289 | pendingctx, | |
290 | nodestoremove, |
|
290 | nodestoremove, | |
291 | branchtorestore, |
|
291 | branchtorestore, | |
292 | keep=False, |
|
292 | keep=False, | |
293 | activebook=b'', |
|
293 | activebook=b'', | |
294 | interactive=False, |
|
294 | interactive=False, | |
295 | ): |
|
295 | ): | |
296 | info = { |
|
296 | info = { | |
297 | b"name": name, |
|
297 | b"name": name, | |
298 | b"originalwctx": nodemod.hex(originalwctx.node()), |
|
298 | b"originalwctx": nodemod.hex(originalwctx.node()), | |
299 | b"pendingctx": nodemod.hex(pendingctx.node()), |
|
299 | b"pendingctx": nodemod.hex(pendingctx.node()), | |
300 | b"parents": b' '.join( |
|
300 | b"parents": b' '.join( | |
301 | [nodemod.hex(p) for p in repo.dirstate.parents()] |
|
301 | [nodemod.hex(p) for p in repo.dirstate.parents()] | |
302 | ), |
|
302 | ), | |
303 | b"nodestoremove": b' '.join( |
|
303 | b"nodestoremove": b' '.join( | |
304 | [nodemod.hex(n) for n in nodestoremove] |
|
304 | [nodemod.hex(n) for n in nodestoremove] | |
305 | ), |
|
305 | ), | |
306 | b"branchtorestore": branchtorestore, |
|
306 | b"branchtorestore": branchtorestore, | |
307 | b"keep": cls._keep if keep else cls._nokeep, |
|
307 | b"keep": cls._keep if keep else cls._nokeep, | |
308 | b"activebook": activebook or cls._noactivebook, |
|
308 | b"activebook": activebook or cls._noactivebook, | |
309 | } |
|
309 | } | |
310 | if interactive: |
|
310 | if interactive: | |
311 | info[b'interactive'] = cls._interactive |
|
311 | info[b'interactive'] = cls._interactive | |
312 | scmutil.simplekeyvaluefile(repo.vfs, cls._filename).write( |
|
312 | scmutil.simplekeyvaluefile(repo.vfs, cls._filename).write( | |
313 | info, firstline=(b"%d" % cls._version) |
|
313 | info, firstline=(b"%d" % cls._version) | |
314 | ) |
|
314 | ) | |
315 |
|
315 | |||
316 | @classmethod |
|
316 | @classmethod | |
317 | def clear(cls, repo): |
|
317 | def clear(cls, repo): | |
318 | repo.vfs.unlinkpath(cls._filename, ignoremissing=True) |
|
318 | repo.vfs.unlinkpath(cls._filename, ignoremissing=True) | |
319 |
|
319 | |||
320 |
|
320 | |||
321 | def cleanupoldbackups(repo): |
|
321 | def cleanupoldbackups(repo): | |
322 | vfs = vfsmod.vfs(repo.vfs.join(backupdir)) |
|
322 | vfs = vfsmod.vfs(repo.vfs.join(backupdir)) | |
323 | maxbackups = repo.ui.configint(b'shelve', b'maxbackups') |
|
323 | maxbackups = repo.ui.configint(b'shelve', b'maxbackups') | |
324 | hgfiles = [f for f in vfs.listdir() if f.endswith(b'.' + patchextension)] |
|
324 | hgfiles = [f for f in vfs.listdir() if f.endswith(b'.' + patchextension)] | |
325 | hgfiles = sorted([(vfs.stat(f)[stat.ST_MTIME], f) for f in hgfiles]) |
|
325 | hgfiles = sorted([(vfs.stat(f)[stat.ST_MTIME], f) for f in hgfiles]) | |
326 | if maxbackups > 0 and maxbackups < len(hgfiles): |
|
326 | if maxbackups > 0 and maxbackups < len(hgfiles): | |
327 | bordermtime = hgfiles[-maxbackups][0] |
|
327 | bordermtime = hgfiles[-maxbackups][0] | |
328 | else: |
|
328 | else: | |
329 | bordermtime = None |
|
329 | bordermtime = None | |
330 | for mtime, f in hgfiles[: len(hgfiles) - maxbackups]: |
|
330 | for mtime, f in hgfiles[: len(hgfiles) - maxbackups]: | |
331 | if mtime == bordermtime: |
|
331 | if mtime == bordermtime: | |
332 | # keep it, because timestamp can't decide exact order of backups |
|
332 | # keep it, because timestamp can't decide exact order of backups | |
333 | continue |
|
333 | continue | |
334 | base = f[: -(1 + len(patchextension))] |
|
334 | base = f[: -(1 + len(patchextension))] | |
335 | for ext in shelvefileextensions: |
|
335 | for ext in shelvefileextensions: | |
336 | vfs.tryunlink(base + b'.' + ext) |
|
336 | vfs.tryunlink(base + b'.' + ext) | |
337 |
|
337 | |||
338 |
|
338 | |||
339 | def _backupactivebookmark(repo): |
|
339 | def _backupactivebookmark(repo): | |
340 | activebookmark = repo._activebookmark |
|
340 | activebookmark = repo._activebookmark | |
341 | if activebookmark: |
|
341 | if activebookmark: | |
342 | bookmarks.deactivate(repo) |
|
342 | bookmarks.deactivate(repo) | |
343 | return activebookmark |
|
343 | return activebookmark | |
344 |
|
344 | |||
345 |
|
345 | |||
346 | def _restoreactivebookmark(repo, mark): |
|
346 | def _restoreactivebookmark(repo, mark): | |
347 | if mark: |
|
347 | if mark: | |
348 | bookmarks.activate(repo, mark) |
|
348 | bookmarks.activate(repo, mark) | |
349 |
|
349 | |||
350 |
|
350 | |||
351 | def _aborttransaction(repo, tr): |
|
351 | def _aborttransaction(repo, tr): | |
352 | '''Abort current transaction for shelve/unshelve, but keep dirstate |
|
352 | '''Abort current transaction for shelve/unshelve, but keep dirstate | |
353 | ''' |
|
353 | ''' | |
354 | dirstatebackupname = b'dirstate.shelve' |
|
354 | dirstatebackupname = b'dirstate.shelve' | |
355 | repo.dirstate.savebackup(tr, dirstatebackupname) |
|
355 | repo.dirstate.savebackup(tr, dirstatebackupname) | |
356 | tr.abort() |
|
356 | tr.abort() | |
357 | repo.dirstate.restorebackup(None, dirstatebackupname) |
|
357 | repo.dirstate.restorebackup(None, dirstatebackupname) | |
358 |
|
358 | |||
359 |
|
359 | |||
360 | def getshelvename(repo, parent, opts): |
|
360 | def getshelvename(repo, parent, opts): | |
361 | """Decide on the name this shelve is going to have""" |
|
361 | """Decide on the name this shelve is going to have""" | |
362 |
|
362 | |||
363 | def gennames(): |
|
363 | def gennames(): | |
364 | yield label |
|
364 | yield label | |
365 | for i in itertools.count(1): |
|
365 | for i in itertools.count(1): | |
366 | yield b'%s-%02d' % (label, i) |
|
366 | yield b'%s-%02d' % (label, i) | |
367 |
|
367 | |||
368 | name = opts.get(b'name') |
|
368 | name = opts.get(b'name') | |
369 | label = repo._activebookmark or parent.branch() or b'default' |
|
369 | label = repo._activebookmark or parent.branch() or b'default' | |
370 | # slashes aren't allowed in filenames, therefore we rename it |
|
370 | # slashes aren't allowed in filenames, therefore we rename it | |
371 | label = label.replace(b'/', b'_') |
|
371 | label = label.replace(b'/', b'_') | |
372 | label = label.replace(b'\\', b'_') |
|
372 | label = label.replace(b'\\', b'_') | |
373 | # filenames must not start with '.' as it should not be hidden |
|
373 | # filenames must not start with '.' as it should not be hidden | |
374 | if label.startswith(b'.'): |
|
374 | if label.startswith(b'.'): | |
375 | label = label.replace(b'.', b'_', 1) |
|
375 | label = label.replace(b'.', b'_', 1) | |
376 |
|
376 | |||
377 | if name: |
|
377 | if name: | |
378 | if shelvedfile(repo, name, patchextension).exists(): |
|
378 | if shelvedfile(repo, name, patchextension).exists(): | |
379 | e = _(b"a shelved change named '%s' already exists") % name |
|
379 | e = _(b"a shelved change named '%s' already exists") % name | |
380 | raise error.Abort(e) |
|
380 | raise error.Abort(e) | |
381 |
|
381 | |||
382 | # ensure we are not creating a subdirectory or a hidden file |
|
382 | # ensure we are not creating a subdirectory or a hidden file | |
383 | if b'/' in name or b'\\' in name: |
|
383 | if b'/' in name or b'\\' in name: | |
384 | raise error.Abort( |
|
384 | raise error.Abort( | |
385 | _(b'shelved change names can not contain slashes') |
|
385 | _(b'shelved change names can not contain slashes') | |
386 | ) |
|
386 | ) | |
387 | if name.startswith(b'.'): |
|
387 | if name.startswith(b'.'): | |
388 | raise error.Abort(_(b"shelved change names can not start with '.'")) |
|
388 | raise error.Abort(_(b"shelved change names can not start with '.'")) | |
389 |
|
389 | |||
390 | else: |
|
390 | else: | |
391 | for n in gennames(): |
|
391 | for n in gennames(): | |
392 | if not shelvedfile(repo, n, patchextension).exists(): |
|
392 | if not shelvedfile(repo, n, patchextension).exists(): | |
393 | name = n |
|
393 | name = n | |
394 | break |
|
394 | break | |
395 |
|
395 | |||
396 | return name |
|
396 | return name | |
397 |
|
397 | |||
398 |
|
398 | |||
399 | def mutableancestors(ctx): |
|
399 | def mutableancestors(ctx): | |
400 | """return all mutable ancestors for ctx (included) |
|
400 | """return all mutable ancestors for ctx (included) | |
401 |
|
401 | |||
402 | Much faster than the revset ancestors(ctx) & draft()""" |
|
402 | Much faster than the revset ancestors(ctx) & draft()""" | |
403 | seen = {nodemod.nullrev} |
|
403 | seen = {nodemod.nullrev} | |
404 | visit = collections.deque() |
|
404 | visit = collections.deque() | |
405 | visit.append(ctx) |
|
405 | visit.append(ctx) | |
406 | while visit: |
|
406 | while visit: | |
407 | ctx = visit.popleft() |
|
407 | ctx = visit.popleft() | |
408 | yield ctx.node() |
|
408 | yield ctx.node() | |
409 | for parent in ctx.parents(): |
|
409 | for parent in ctx.parents(): | |
410 | rev = parent.rev() |
|
410 | rev = parent.rev() | |
411 | if rev not in seen: |
|
411 | if rev not in seen: | |
412 | seen.add(rev) |
|
412 | seen.add(rev) | |
413 | if parent.mutable(): |
|
413 | if parent.mutable(): | |
414 | visit.append(parent) |
|
414 | visit.append(parent) | |
415 |
|
415 | |||
416 |
|
416 | |||
417 | def getcommitfunc(extra, interactive, editor=False): |
|
417 | def getcommitfunc(extra, interactive, editor=False): | |
418 | def commitfunc(ui, repo, message, match, opts): |
|
418 | def commitfunc(ui, repo, message, match, opts): | |
419 | hasmq = util.safehasattr(repo, b'mq') |
|
419 | hasmq = util.safehasattr(repo, b'mq') | |
420 | if hasmq: |
|
420 | if hasmq: | |
421 | saved, repo.mq.checkapplied = repo.mq.checkapplied, False |
|
421 | saved, repo.mq.checkapplied = repo.mq.checkapplied, False | |
422 |
|
422 | |||
423 | targetphase = phases.internal |
|
423 | targetphase = phases.internal | |
424 | if not phases.supportinternal(repo): |
|
424 | if not phases.supportinternal(repo): | |
425 | targetphase = phases.secret |
|
425 | targetphase = phases.secret | |
426 | overrides = {(b'phases', b'new-commit'): targetphase} |
|
426 | overrides = {(b'phases', b'new-commit'): targetphase} | |
427 | try: |
|
427 | try: | |
428 | editor_ = False |
|
428 | editor_ = False | |
429 | if editor: |
|
429 | if editor: | |
430 | editor_ = cmdutil.getcommiteditor( |
|
430 | editor_ = cmdutil.getcommiteditor( | |
431 | editform=b'shelve.shelve', **pycompat.strkwargs(opts) |
|
431 | editform=b'shelve.shelve', **pycompat.strkwargs(opts) | |
432 | ) |
|
432 | ) | |
433 | with repo.ui.configoverride(overrides): |
|
433 | with repo.ui.configoverride(overrides): | |
434 | return repo.commit( |
|
434 | return repo.commit( | |
435 | message, |
|
435 | message, | |
436 | shelveuser, |
|
436 | shelveuser, | |
437 | opts.get(b'date'), |
|
437 | opts.get(b'date'), | |
438 | match, |
|
438 | match, | |
439 | editor=editor_, |
|
439 | editor=editor_, | |
440 | extra=extra, |
|
440 | extra=extra, | |
441 | ) |
|
441 | ) | |
442 | finally: |
|
442 | finally: | |
443 | if hasmq: |
|
443 | if hasmq: | |
444 | repo.mq.checkapplied = saved |
|
444 | repo.mq.checkapplied = saved | |
445 |
|
445 | |||
446 | def interactivecommitfunc(ui, repo, *pats, **opts): |
|
446 | def interactivecommitfunc(ui, repo, *pats, **opts): | |
447 | opts = pycompat.byteskwargs(opts) |
|
447 | opts = pycompat.byteskwargs(opts) | |
448 | match = scmutil.match(repo[b'.'], pats, {}) |
|
448 | match = scmutil.match(repo[b'.'], pats, {}) | |
449 | message = opts[b'message'] |
|
449 | message = opts[b'message'] | |
450 | return commitfunc(ui, repo, message, match, opts) |
|
450 | return commitfunc(ui, repo, message, match, opts) | |
451 |
|
451 | |||
452 | return interactivecommitfunc if interactive else commitfunc |
|
452 | return interactivecommitfunc if interactive else commitfunc | |
453 |
|
453 | |||
454 |
|
454 | |||
455 | def _nothingtoshelvemessaging(ui, repo, pats, opts): |
|
455 | def _nothingtoshelvemessaging(ui, repo, pats, opts): | |
456 | stat = repo.status(match=scmutil.match(repo[None], pats, opts)) |
|
456 | stat = repo.status(match=scmutil.match(repo[None], pats, opts)) | |
457 | if stat.deleted: |
|
457 | if stat.deleted: | |
458 | ui.status( |
|
458 | ui.status( | |
459 | _(b"nothing changed (%d missing files, see 'hg status')\n") |
|
459 | _(b"nothing changed (%d missing files, see 'hg status')\n") | |
460 | % len(stat.deleted) |
|
460 | % len(stat.deleted) | |
461 | ) |
|
461 | ) | |
462 | else: |
|
462 | else: | |
463 | ui.status(_(b"nothing changed\n")) |
|
463 | ui.status(_(b"nothing changed\n")) | |
464 |
|
464 | |||
465 |
|
465 | |||
466 | def _shelvecreatedcommit(repo, node, name, match): |
|
466 | def _shelvecreatedcommit(repo, node, name, match): | |
467 | info = {b'node': nodemod.hex(node)} |
|
467 | info = {b'node': nodemod.hex(node)} | |
468 | shelvedfile(repo, name, b'shelve').writeinfo(info) |
|
468 | shelvedfile(repo, name, b'shelve').writeinfo(info) | |
469 | bases = list(mutableancestors(repo[node])) |
|
469 | bases = list(mutableancestors(repo[node])) | |
470 | shelvedfile(repo, name, b'hg').writebundle(bases, node) |
|
470 | shelvedfile(repo, name, b'hg').writebundle(bases, node) | |
471 | with shelvedfile(repo, name, patchextension).opener(b'wb') as fp: |
|
471 | with shelvedfile(repo, name, patchextension).opener(b'wb') as fp: | |
472 | cmdutil.exportfile( |
|
472 | cmdutil.exportfile( | |
473 | repo, [node], fp, opts=mdiff.diffopts(git=True), match=match |
|
473 | repo, [node], fp, opts=mdiff.diffopts(git=True), match=match | |
474 | ) |
|
474 | ) | |
475 |
|
475 | |||
476 |
|
476 | |||
477 | def _includeunknownfiles(repo, pats, opts, extra): |
|
477 | def _includeunknownfiles(repo, pats, opts, extra): | |
478 | s = repo.status(match=scmutil.match(repo[None], pats, opts), unknown=True) |
|
478 | s = repo.status(match=scmutil.match(repo[None], pats, opts), unknown=True) | |
479 | if s.unknown: |
|
479 | if s.unknown: | |
480 | extra[b'shelve_unknown'] = b'\0'.join(s.unknown) |
|
480 | extra[b'shelve_unknown'] = b'\0'.join(s.unknown) | |
481 | repo[None].add(s.unknown) |
|
481 | repo[None].add(s.unknown) | |
482 |
|
482 | |||
483 |
|
483 | |||
484 | def _finishshelve(repo, tr): |
|
484 | def _finishshelve(repo, tr): | |
485 | if phases.supportinternal(repo): |
|
485 | if phases.supportinternal(repo): | |
486 | tr.close() |
|
486 | tr.close() | |
487 | else: |
|
487 | else: | |
488 | _aborttransaction(repo, tr) |
|
488 | _aborttransaction(repo, tr) | |
489 |
|
489 | |||
490 |
|
490 | |||
491 | def createcmd(ui, repo, pats, opts): |
|
491 | def createcmd(ui, repo, pats, opts): | |
492 | """subcommand that creates a new shelve""" |
|
492 | """subcommand that creates a new shelve""" | |
493 | with repo.wlock(): |
|
493 | with repo.wlock(): | |
494 | cmdutil.checkunfinished(repo) |
|
494 | cmdutil.checkunfinished(repo) | |
495 | return _docreatecmd(ui, repo, pats, opts) |
|
495 | return _docreatecmd(ui, repo, pats, opts) | |
496 |
|
496 | |||
497 |
|
497 | |||
498 | def _docreatecmd(ui, repo, pats, opts): |
|
498 | def _docreatecmd(ui, repo, pats, opts): | |
499 | wctx = repo[None] |
|
499 | wctx = repo[None] | |
500 | parents = wctx.parents() |
|
500 | parents = wctx.parents() | |
501 | parent = parents[0] |
|
501 | parent = parents[0] | |
502 | origbranch = wctx.branch() |
|
502 | origbranch = wctx.branch() | |
503 |
|
503 | |||
504 | if parent.node() != nodemod.nullid: |
|
504 | if parent.node() != nodemod.nullid: | |
505 | desc = b"changes to: %s" % parent.description().split(b'\n', 1)[0] |
|
505 | desc = b"changes to: %s" % parent.description().split(b'\n', 1)[0] | |
506 | else: |
|
506 | else: | |
507 | desc = b'(changes in empty repository)' |
|
507 | desc = b'(changes in empty repository)' | |
508 |
|
508 | |||
509 | if not opts.get(b'message'): |
|
509 | if not opts.get(b'message'): | |
510 | opts[b'message'] = desc |
|
510 | opts[b'message'] = desc | |
511 |
|
511 | |||
512 | lock = tr = activebookmark = None |
|
512 | lock = tr = activebookmark = None | |
513 | try: |
|
513 | try: | |
514 | lock = repo.lock() |
|
514 | lock = repo.lock() | |
515 |
|
515 | |||
516 | # use an uncommitted transaction to generate the bundle to avoid |
|
516 | # use an uncommitted transaction to generate the bundle to avoid | |
517 | # pull races. ensure we don't print the abort message to stderr. |
|
517 | # pull races. ensure we don't print the abort message to stderr. | |
518 | tr = repo.transaction(b'shelve', report=lambda x: None) |
|
518 | tr = repo.transaction(b'shelve', report=lambda x: None) | |
519 |
|
519 | |||
520 | interactive = opts.get(b'interactive', False) |
|
520 | interactive = opts.get(b'interactive', False) | |
521 | includeunknown = opts.get(b'unknown', False) and not opts.get( |
|
521 | includeunknown = opts.get(b'unknown', False) and not opts.get( | |
522 | b'addremove', False |
|
522 | b'addremove', False | |
523 | ) |
|
523 | ) | |
524 |
|
524 | |||
525 | name = getshelvename(repo, parent, opts) |
|
525 | name = getshelvename(repo, parent, opts) | |
526 | activebookmark = _backupactivebookmark(repo) |
|
526 | activebookmark = _backupactivebookmark(repo) | |
527 | extra = {b'internal': b'shelve'} |
|
527 | extra = {b'internal': b'shelve'} | |
528 | if includeunknown: |
|
528 | if includeunknown: | |
529 | _includeunknownfiles(repo, pats, opts, extra) |
|
529 | _includeunknownfiles(repo, pats, opts, extra) | |
530 |
|
530 | |||
531 | if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts): |
|
531 | if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts): | |
532 | # In non-bare shelve we don't store newly created branch |
|
532 | # In non-bare shelve we don't store newly created branch | |
533 | # at bundled commit |
|
533 | # at bundled commit | |
534 | repo.dirstate.setbranch(repo[b'.'].branch()) |
|
534 | repo.dirstate.setbranch(repo[b'.'].branch()) | |
535 |
|
535 | |||
536 | commitfunc = getcommitfunc(extra, interactive, editor=True) |
|
536 | commitfunc = getcommitfunc(extra, interactive, editor=True) | |
537 | if not interactive: |
|
537 | if not interactive: | |
538 | node = cmdutil.commit(ui, repo, commitfunc, pats, opts) |
|
538 | node = cmdutil.commit(ui, repo, commitfunc, pats, opts) | |
539 | else: |
|
539 | else: | |
540 | node = cmdutil.dorecord( |
|
540 | node = cmdutil.dorecord( | |
541 | ui, |
|
541 | ui, | |
542 | repo, |
|
542 | repo, | |
543 | commitfunc, |
|
543 | commitfunc, | |
544 | None, |
|
544 | None, | |
545 | False, |
|
545 | False, | |
546 | cmdutil.recordfilter, |
|
546 | cmdutil.recordfilter, | |
547 | *pats, |
|
547 | *pats, | |
548 | **pycompat.strkwargs(opts) |
|
548 | **pycompat.strkwargs(opts) | |
549 | ) |
|
549 | ) | |
550 | if not node: |
|
550 | if not node: | |
551 | _nothingtoshelvemessaging(ui, repo, pats, opts) |
|
551 | _nothingtoshelvemessaging(ui, repo, pats, opts) | |
552 | return 1 |
|
552 | return 1 | |
553 |
|
553 | |||
554 | # Create a matcher so that prefetch doesn't attempt to fetch |
|
554 | # Create a matcher so that prefetch doesn't attempt to fetch | |
555 | # the entire repository pointlessly, and as an optimisation |
|
555 | # the entire repository pointlessly, and as an optimisation | |
556 | # for movedirstate, if needed. |
|
556 | # for movedirstate, if needed. | |
557 | match = scmutil.matchfiles(repo, repo[node].files()) |
|
557 | match = scmutil.matchfiles(repo, repo[node].files()) | |
558 | _shelvecreatedcommit(repo, node, name, match) |
|
558 | _shelvecreatedcommit(repo, node, name, match) | |
559 |
|
559 | |||
560 | ui.status(_(b'shelved as %s\n') % name) |
|
560 | ui.status(_(b'shelved as %s\n') % name) | |
561 | if opts[b'keep']: |
|
561 | if opts[b'keep']: | |
562 | with repo.dirstate.parentchange(): |
|
562 | with repo.dirstate.parentchange(): | |
563 | scmutil.movedirstate(repo, parent, match) |
|
563 | scmutil.movedirstate(repo, parent, match) | |
564 | else: |
|
564 | else: | |
565 | hg.update(repo, parent.node()) |
|
565 | hg.update(repo, parent.node()) | |
566 | if origbranch != repo[b'.'].branch() and not _isbareshelve(pats, opts): |
|
566 | if origbranch != repo[b'.'].branch() and not _isbareshelve(pats, opts): | |
567 | repo.dirstate.setbranch(origbranch) |
|
567 | repo.dirstate.setbranch(origbranch) | |
568 |
|
568 | |||
569 | _finishshelve(repo, tr) |
|
569 | _finishshelve(repo, tr) | |
570 | finally: |
|
570 | finally: | |
571 | _restoreactivebookmark(repo, activebookmark) |
|
571 | _restoreactivebookmark(repo, activebookmark) | |
572 | lockmod.release(tr, lock) |
|
572 | lockmod.release(tr, lock) | |
573 |
|
573 | |||
574 |
|
574 | |||
575 | def _isbareshelve(pats, opts): |
|
575 | def _isbareshelve(pats, opts): | |
576 | return ( |
|
576 | return ( | |
577 | not pats |
|
577 | not pats | |
578 | and not opts.get(b'interactive', False) |
|
578 | and not opts.get(b'interactive', False) | |
579 | and not opts.get(b'include', False) |
|
579 | and not opts.get(b'include', False) | |
580 | and not opts.get(b'exclude', False) |
|
580 | and not opts.get(b'exclude', False) | |
581 | ) |
|
581 | ) | |
582 |
|
582 | |||
583 |
|
583 | |||
584 | def _iswctxonnewbranch(repo): |
|
584 | def _iswctxonnewbranch(repo): | |
585 | return repo[None].branch() != repo[b'.'].branch() |
|
585 | return repo[None].branch() != repo[b'.'].branch() | |
586 |
|
586 | |||
587 |
|
587 | |||
588 | def cleanupcmd(ui, repo): |
|
588 | def cleanupcmd(ui, repo): | |
589 | """subcommand that deletes all shelves""" |
|
589 | """subcommand that deletes all shelves""" | |
590 |
|
590 | |||
591 | with repo.wlock(): |
|
591 | with repo.wlock(): | |
592 | for (name, _type) in repo.vfs.readdir(shelvedir): |
|
592 | for (name, _type) in repo.vfs.readdir(shelvedir): | |
593 | suffix = name.rsplit(b'.', 1)[-1] |
|
593 | suffix = name.rsplit(b'.', 1)[-1] | |
594 | if suffix in shelvefileextensions: |
|
594 | if suffix in shelvefileextensions: | |
595 | shelvedfile(repo, name).movetobackup() |
|
595 | shelvedfile(repo, name).movetobackup() | |
596 | cleanupoldbackups(repo) |
|
596 | cleanupoldbackups(repo) | |
597 |
|
597 | |||
598 |
|
598 | |||
599 | def deletecmd(ui, repo, pats): |
|
599 | def deletecmd(ui, repo, pats): | |
600 | """subcommand that deletes a specific shelve""" |
|
600 | """subcommand that deletes a specific shelve""" | |
601 | if not pats: |
|
601 | if not pats: | |
602 | raise error.Abort(_(b'no shelved changes specified!')) |
|
602 | raise error.Abort(_(b'no shelved changes specified!')) | |
603 | with repo.wlock(): |
|
603 | with repo.wlock(): | |
604 | for name in pats: |
|
604 | for name in pats: | |
605 | try: |
|
605 | try: | |
606 | for suffix in shelvefileextensions: |
|
606 | for suffix in shelvefileextensions: | |
607 | shfile = shelvedfile(repo, name, suffix) |
|
607 | shfile = shelvedfile(repo, name, suffix) | |
608 | # patch file is necessary, as it should |
|
608 | # patch file is necessary, as it should | |
609 | # be present for any kind of shelve, |
|
609 | # be present for any kind of shelve, | |
610 | # but the .hg file is optional as in future we |
|
610 | # but the .hg file is optional as in future we | |
611 | # will add obsolete shelve with does not create a |
|
611 | # will add obsolete shelve with does not create a | |
612 | # bundle |
|
612 | # bundle | |
613 | if shfile.exists() or suffix == patchextension: |
|
613 | if shfile.exists() or suffix == patchextension: | |
614 | shfile.movetobackup() |
|
614 | shfile.movetobackup() | |
615 | except OSError as err: |
|
615 | except OSError as err: | |
616 | if err.errno != errno.ENOENT: |
|
616 | if err.errno != errno.ENOENT: | |
617 | raise |
|
617 | raise | |
618 | raise error.Abort(_(b"shelved change '%s' not found") % name) |
|
618 | raise error.Abort(_(b"shelved change '%s' not found") % name) | |
619 | cleanupoldbackups(repo) |
|
619 | cleanupoldbackups(repo) | |
620 |
|
620 | |||
621 |
|
621 | |||
622 | def listshelves(repo): |
|
622 | def listshelves(repo): | |
623 | """return all shelves in repo as list of (time, filename)""" |
|
623 | """return all shelves in repo as list of (time, filename)""" | |
624 | try: |
|
624 | try: | |
625 | names = repo.vfs.readdir(shelvedir) |
|
625 | names = repo.vfs.readdir(shelvedir) | |
626 | except OSError as err: |
|
626 | except OSError as err: | |
627 | if err.errno != errno.ENOENT: |
|
627 | if err.errno != errno.ENOENT: | |
628 | raise |
|
628 | raise | |
629 | return [] |
|
629 | return [] | |
630 | info = [] |
|
630 | info = [] | |
631 | for (name, _type) in names: |
|
631 | for (name, _type) in names: | |
632 | pfx, sfx = name.rsplit(b'.', 1) |
|
632 | pfx, sfx = name.rsplit(b'.', 1) | |
633 | if not pfx or sfx != patchextension: |
|
633 | if not pfx or sfx != patchextension: | |
634 | continue |
|
634 | continue | |
635 | st = shelvedfile(repo, name).stat() |
|
635 | st = shelvedfile(repo, name).stat() | |
636 | info.append((st[stat.ST_MTIME], shelvedfile(repo, pfx).filename())) |
|
636 | info.append((st[stat.ST_MTIME], shelvedfile(repo, pfx).filename())) | |
637 | return sorted(info, reverse=True) |
|
637 | return sorted(info, reverse=True) | |
638 |
|
638 | |||
639 |
|
639 | |||
640 | def listcmd(ui, repo, pats, opts): |
|
640 | def listcmd(ui, repo, pats, opts): | |
641 | """subcommand that displays the list of shelves""" |
|
641 | """subcommand that displays the list of shelves""" | |
642 | pats = set(pats) |
|
642 | pats = set(pats) | |
643 | width = 80 |
|
643 | width = 80 | |
644 | if not ui.plain(): |
|
644 | if not ui.plain(): | |
645 | width = ui.termwidth() |
|
645 | width = ui.termwidth() | |
646 | namelabel = b'shelve.newest' |
|
646 | namelabel = b'shelve.newest' | |
647 | ui.pager(b'shelve') |
|
647 | ui.pager(b'shelve') | |
648 | for mtime, name in listshelves(repo): |
|
648 | for mtime, name in listshelves(repo): | |
649 | sname = util.split(name)[1] |
|
649 | sname = util.split(name)[1] | |
650 | if pats and sname not in pats: |
|
650 | if pats and sname not in pats: | |
651 | continue |
|
651 | continue | |
652 | ui.write(sname, label=namelabel) |
|
652 | ui.write(sname, label=namelabel) | |
653 | namelabel = b'shelve.name' |
|
653 | namelabel = b'shelve.name' | |
654 | if ui.quiet: |
|
654 | if ui.quiet: | |
655 | ui.write(b'\n') |
|
655 | ui.write(b'\n') | |
656 | continue |
|
656 | continue | |
657 | ui.write(b' ' * (16 - len(sname))) |
|
657 | ui.write(b' ' * (16 - len(sname))) | |
658 | used = 16 |
|
658 | used = 16 | |
659 | date = dateutil.makedate(mtime) |
|
659 | date = dateutil.makedate(mtime) | |
660 | age = b'(%s)' % templatefilters.age(date, abbrev=True) |
|
660 | age = b'(%s)' % templatefilters.age(date, abbrev=True) | |
661 | ui.write(age, label=b'shelve.age') |
|
661 | ui.write(age, label=b'shelve.age') | |
662 | ui.write(b' ' * (12 - len(age))) |
|
662 | ui.write(b' ' * (12 - len(age))) | |
663 | used += 12 |
|
663 | used += 12 | |
664 | with open(name + b'.' + patchextension, b'rb') as fp: |
|
664 | with open(name + b'.' + patchextension, b'rb') as fp: | |
665 | while True: |
|
665 | while True: | |
666 | line = fp.readline() |
|
666 | line = fp.readline() | |
667 | if not line: |
|
667 | if not line: | |
668 | break |
|
668 | break | |
669 | if not line.startswith(b'#'): |
|
669 | if not line.startswith(b'#'): | |
670 | desc = line.rstrip() |
|
670 | desc = line.rstrip() | |
671 | if ui.formatted(): |
|
671 | if ui.formatted(): | |
672 | desc = stringutil.ellipsis(desc, width - used) |
|
672 | desc = stringutil.ellipsis(desc, width - used) | |
673 | ui.write(desc) |
|
673 | ui.write(desc) | |
674 | break |
|
674 | break | |
675 | ui.write(b'\n') |
|
675 | ui.write(b'\n') | |
676 | if not (opts[b'patch'] or opts[b'stat']): |
|
676 | if not (opts[b'patch'] or opts[b'stat']): | |
677 | continue |
|
677 | continue | |
678 | difflines = fp.readlines() |
|
678 | difflines = fp.readlines() | |
679 | if opts[b'patch']: |
|
679 | if opts[b'patch']: | |
680 | for chunk, label in patch.difflabel(iter, difflines): |
|
680 | for chunk, label in patch.difflabel(iter, difflines): | |
681 | ui.write(chunk, label=label) |
|
681 | ui.write(chunk, label=label) | |
682 | if opts[b'stat']: |
|
682 | if opts[b'stat']: | |
683 | for chunk, label in patch.diffstatui(difflines, width=width): |
|
683 | for chunk, label in patch.diffstatui(difflines, width=width): | |
684 | ui.write(chunk, label=label) |
|
684 | ui.write(chunk, label=label) | |
685 |
|
685 | |||
686 |
|
686 | |||
687 | def patchcmds(ui, repo, pats, opts): |
|
687 | def patchcmds(ui, repo, pats, opts): | |
688 | """subcommand that displays shelves""" |
|
688 | """subcommand that displays shelves""" | |
689 | if len(pats) == 0: |
|
689 | if len(pats) == 0: | |
690 | shelves = listshelves(repo) |
|
690 | shelves = listshelves(repo) | |
691 | if not shelves: |
|
691 | if not shelves: | |
692 | raise error.Abort(_(b"there are no shelves to show")) |
|
692 | raise error.Abort(_(b"there are no shelves to show")) | |
693 | mtime, name = shelves[0] |
|
693 | mtime, name = shelves[0] | |
694 | sname = util.split(name)[1] |
|
694 | sname = util.split(name)[1] | |
695 | pats = [sname] |
|
695 | pats = [sname] | |
696 |
|
696 | |||
697 | for shelfname in pats: |
|
697 | for shelfname in pats: | |
698 | if not shelvedfile(repo, shelfname, patchextension).exists(): |
|
698 | if not shelvedfile(repo, shelfname, patchextension).exists(): | |
699 | raise error.Abort(_(b"cannot find shelf %s") % shelfname) |
|
699 | raise error.Abort(_(b"cannot find shelf %s") % shelfname) | |
700 |
|
700 | |||
701 | listcmd(ui, repo, pats, opts) |
|
701 | listcmd(ui, repo, pats, opts) | |
702 |
|
702 | |||
703 |
|
703 | |||
704 | def checkparents(repo, state): |
|
704 | def checkparents(repo, state): | |
705 | """check parent while resuming an unshelve""" |
|
705 | """check parent while resuming an unshelve""" | |
706 | if state.parents != repo.dirstate.parents(): |
|
706 | if state.parents != repo.dirstate.parents(): | |
707 | raise error.Abort( |
|
707 | raise error.Abort( | |
708 | _(b'working directory parents do not match unshelve state') |
|
708 | _(b'working directory parents do not match unshelve state') | |
709 | ) |
|
709 | ) | |
710 |
|
710 | |||
711 |
|
711 | |||
712 | def _loadshelvedstate(ui, repo, opts): |
|
712 | def _loadshelvedstate(ui, repo, opts): | |
713 | try: |
|
713 | try: | |
714 | state = shelvedstate.load(repo) |
|
714 | state = shelvedstate.load(repo) | |
715 | if opts.get(b'keep') is None: |
|
715 | if opts.get(b'keep') is None: | |
716 | opts[b'keep'] = state.keep |
|
716 | opts[b'keep'] = state.keep | |
717 | except IOError as err: |
|
717 | except IOError as err: | |
718 | if err.errno != errno.ENOENT: |
|
718 | if err.errno != errno.ENOENT: | |
719 | raise |
|
719 | raise | |
720 | cmdutil.wrongtooltocontinue(repo, _(b'unshelve')) |
|
720 | cmdutil.wrongtooltocontinue(repo, _(b'unshelve')) | |
721 | except error.CorruptedState as err: |
|
721 | except error.CorruptedState as err: | |
722 | ui.debug(pycompat.bytestr(err) + b'\n') |
|
722 | ui.debug(pycompat.bytestr(err) + b'\n') | |
723 | if opts.get(b'continue'): |
|
723 | if opts.get(b'continue'): | |
724 | msg = _(b'corrupted shelved state file') |
|
724 | msg = _(b'corrupted shelved state file') | |
725 | hint = _( |
|
725 | hint = _( | |
726 | b'please run hg unshelve --abort to abort unshelve ' |
|
726 | b'please run hg unshelve --abort to abort unshelve ' | |
727 | b'operation' |
|
727 | b'operation' | |
728 | ) |
|
728 | ) | |
729 | raise error.Abort(msg, hint=hint) |
|
729 | raise error.Abort(msg, hint=hint) | |
730 | elif opts.get(b'abort'): |
|
730 | elif opts.get(b'abort'): | |
731 | shelvedstate.clear(repo) |
|
731 | shelvedstate.clear(repo) | |
732 | raise error.Abort( |
|
732 | raise error.Abort( | |
733 | _( |
|
733 | _( | |
734 | b'could not read shelved state file, your ' |
|
734 | b'could not read shelved state file, your ' | |
735 | b'working copy may be in an unexpected state\n' |
|
735 | b'working copy may be in an unexpected state\n' | |
736 | b'please update to some commit\n' |
|
736 | b'please update to some commit\n' | |
737 | ) |
|
737 | ) | |
738 | ) |
|
738 | ) | |
739 | return state |
|
739 | return state | |
740 |
|
740 | |||
741 |
|
741 | |||
742 | def unshelveabort(ui, repo, state): |
|
742 | def unshelveabort(ui, repo, state): | |
743 | """subcommand that abort an in-progress unshelve""" |
|
743 | """subcommand that abort an in-progress unshelve""" | |
744 | with repo.lock(): |
|
744 | with repo.lock(): | |
745 | try: |
|
745 | try: | |
746 | checkparents(repo, state) |
|
746 | checkparents(repo, state) | |
747 |
|
747 | |||
748 | merge.clean_update(state.pendingctx) |
|
748 | merge.clean_update(state.pendingctx) | |
749 | if state.activebookmark and state.activebookmark in repo._bookmarks: |
|
749 | if state.activebookmark and state.activebookmark in repo._bookmarks: | |
750 | bookmarks.activate(repo, state.activebookmark) |
|
750 | bookmarks.activate(repo, state.activebookmark) | |
751 | mergefiles(ui, repo, state.wctx, state.pendingctx) |
|
751 | mergefiles(ui, repo, state.wctx, state.pendingctx) | |
752 | if not phases.supportinternal(repo): |
|
752 | if not phases.supportinternal(repo): | |
753 | repair.strip( |
|
753 | repair.strip( | |
754 | ui, repo, state.nodestoremove, backup=False, topic=b'shelve' |
|
754 | ui, repo, state.nodestoremove, backup=False, topic=b'shelve' | |
755 | ) |
|
755 | ) | |
756 | finally: |
|
756 | finally: | |
757 | shelvedstate.clear(repo) |
|
757 | shelvedstate.clear(repo) | |
758 | ui.warn(_(b"unshelve of '%s' aborted\n") % state.name) |
|
758 | ui.warn(_(b"unshelve of '%s' aborted\n") % state.name) | |
759 |
|
759 | |||
760 |
|
760 | |||
761 | def hgabortunshelve(ui, repo): |
|
761 | def hgabortunshelve(ui, repo): | |
762 | """logic to abort unshelve using 'hg abort""" |
|
762 | """logic to abort unshelve using 'hg abort""" | |
763 | with repo.wlock(): |
|
763 | with repo.wlock(): | |
764 | state = _loadshelvedstate(ui, repo, {b'abort': True}) |
|
764 | state = _loadshelvedstate(ui, repo, {b'abort': True}) | |
765 | return unshelveabort(ui, repo, state) |
|
765 | return unshelveabort(ui, repo, state) | |
766 |
|
766 | |||
767 |
|
767 | |||
768 | def mergefiles(ui, repo, wctx, shelvectx): |
|
768 | def mergefiles(ui, repo, wctx, shelvectx): | |
769 | """updates to wctx and merges the changes from shelvectx into the |
|
769 | """updates to wctx and merges the changes from shelvectx into the | |
770 | dirstate.""" |
|
770 | dirstate.""" | |
771 | with ui.configoverride({(b'ui', b'quiet'): True}): |
|
771 | with ui.configoverride({(b'ui', b'quiet'): True}): | |
772 | hg.update(repo, wctx.node()) |
|
772 | hg.update(repo, wctx.node()) | |
773 | ui.pushbuffer(True) |
|
773 | ui.pushbuffer(True) | |
774 | cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents()) |
|
774 | cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents()) | |
775 | ui.popbuffer() |
|
775 | ui.popbuffer() | |
776 |
|
776 | |||
777 |
|
777 | |||
778 | def restorebranch(ui, repo, branchtorestore): |
|
778 | def restorebranch(ui, repo, branchtorestore): | |
779 | if branchtorestore and branchtorestore != repo.dirstate.branch(): |
|
779 | if branchtorestore and branchtorestore != repo.dirstate.branch(): | |
780 | repo.dirstate.setbranch(branchtorestore) |
|
780 | repo.dirstate.setbranch(branchtorestore) | |
781 | ui.status( |
|
781 | ui.status( | |
782 | _(b'marked working directory as branch %s\n') % branchtorestore |
|
782 | _(b'marked working directory as branch %s\n') % branchtorestore | |
783 | ) |
|
783 | ) | |
784 |
|
784 | |||
785 |
|
785 | |||
786 | def unshelvecleanup(ui, repo, name, opts): |
|
786 | def unshelvecleanup(ui, repo, name, opts): | |
787 | """remove related files after an unshelve""" |
|
787 | """remove related files after an unshelve""" | |
788 | if not opts.get(b'keep'): |
|
788 | if not opts.get(b'keep'): | |
789 | for filetype in shelvefileextensions: |
|
789 | for filetype in shelvefileextensions: | |
790 | shfile = shelvedfile(repo, name, filetype) |
|
790 | shfile = shelvedfile(repo, name, filetype) | |
791 | if shfile.exists(): |
|
791 | if shfile.exists(): | |
792 | shfile.movetobackup() |
|
792 | shfile.movetobackup() | |
793 | cleanupoldbackups(repo) |
|
793 | cleanupoldbackups(repo) | |
794 |
|
794 | |||
795 |
|
795 | |||
796 | def unshelvecontinue(ui, repo, state, opts): |
|
796 | def unshelvecontinue(ui, repo, state, opts): | |
797 | """subcommand to continue an in-progress unshelve""" |
|
797 | """subcommand to continue an in-progress unshelve""" | |
798 | # We're finishing off a merge. First parent is our original |
|
798 | # We're finishing off a merge. First parent is our original | |
799 | # parent, second is the temporary "fake" commit we're unshelving. |
|
799 | # parent, second is the temporary "fake" commit we're unshelving. | |
800 | interactive = state.interactive |
|
800 | interactive = state.interactive | |
801 | basename = state.name |
|
801 | basename = state.name | |
802 | with repo.lock(): |
|
802 | with repo.lock(): | |
803 | checkparents(repo, state) |
|
803 | checkparents(repo, state) | |
804 | ms = merge.mergestate.read(repo) |
|
804 | ms = merge.mergestate.read(repo) | |
805 | if list(ms.unresolved()): |
|
805 | if list(ms.unresolved()): | |
806 | raise error.Abort( |
|
806 | raise error.Abort( | |
807 | _(b"unresolved conflicts, can't continue"), |
|
807 | _(b"unresolved conflicts, can't continue"), | |
808 | hint=_(b"see 'hg resolve', then 'hg unshelve --continue'"), |
|
808 | hint=_(b"see 'hg resolve', then 'hg unshelve --continue'"), | |
809 | ) |
|
809 | ) | |
810 |
|
810 | |||
811 | shelvectx = repo[state.parents[1]] |
|
811 | shelvectx = repo[state.parents[1]] | |
812 | pendingctx = state.pendingctx |
|
812 | pendingctx = state.pendingctx | |
813 |
|
813 | |||
814 | with repo.dirstate.parentchange(): |
|
814 | with repo.dirstate.parentchange(): | |
815 | repo.setparents(state.pendingctx.node(), nodemod.nullid) |
|
815 | repo.setparents(state.pendingctx.node(), nodemod.nullid) | |
816 | repo.dirstate.write(repo.currenttransaction()) |
|
816 | repo.dirstate.write(repo.currenttransaction()) | |
817 |
|
817 | |||
818 | targetphase = phases.internal |
|
818 | targetphase = phases.internal | |
819 | if not phases.supportinternal(repo): |
|
819 | if not phases.supportinternal(repo): | |
820 | targetphase = phases.secret |
|
820 | targetphase = phases.secret | |
821 | overrides = {(b'phases', b'new-commit'): targetphase} |
|
821 | overrides = {(b'phases', b'new-commit'): targetphase} | |
822 | with repo.ui.configoverride(overrides, b'unshelve'): |
|
822 | with repo.ui.configoverride(overrides, b'unshelve'): | |
823 | with repo.dirstate.parentchange(): |
|
823 | with repo.dirstate.parentchange(): | |
824 | repo.setparents(state.parents[0], nodemod.nullid) |
|
824 | repo.setparents(state.parents[0], nodemod.nullid) | |
825 | newnode, ispartialunshelve = _createunshelvectx( |
|
825 | newnode, ispartialunshelve = _createunshelvectx( | |
826 | ui, repo, shelvectx, basename, interactive, opts |
|
826 | ui, repo, shelvectx, basename, interactive, opts | |
827 | ) |
|
827 | ) | |
828 |
|
828 | |||
829 | if newnode is None: |
|
829 | if newnode is None: | |
830 | # If it ended up being a no-op commit, then the normal |
|
830 | # If it ended up being a no-op commit, then the normal | |
831 | # merge state clean-up path doesn't happen, so do it |
|
831 | # merge state clean-up path doesn't happen, so do it | |
832 | # here. Fix issue5494 |
|
832 | # here. Fix issue5494 | |
833 | merge.mergestate.clean(repo) |
|
833 | merge.mergestate.clean(repo) | |
834 | shelvectx = state.pendingctx |
|
834 | shelvectx = state.pendingctx | |
835 | msg = _( |
|
835 | msg = _( | |
836 | b'note: unshelved changes already existed ' |
|
836 | b'note: unshelved changes already existed ' | |
837 | b'in the working copy\n' |
|
837 | b'in the working copy\n' | |
838 | ) |
|
838 | ) | |
839 | ui.status(msg) |
|
839 | ui.status(msg) | |
840 | else: |
|
840 | else: | |
841 | # only strip the shelvectx if we produced one |
|
841 | # only strip the shelvectx if we produced one | |
842 | state.nodestoremove.append(newnode) |
|
842 | state.nodestoremove.append(newnode) | |
843 | shelvectx = repo[newnode] |
|
843 | shelvectx = repo[newnode] | |
844 |
|
844 | |||
845 | hg.updaterepo(repo, pendingctx.node(), overwrite=False) |
|
845 | hg.updaterepo(repo, pendingctx.node(), overwrite=False) | |
846 | mergefiles(ui, repo, state.wctx, shelvectx) |
|
846 | mergefiles(ui, repo, state.wctx, shelvectx) | |
847 | restorebranch(ui, repo, state.branchtorestore) |
|
847 | restorebranch(ui, repo, state.branchtorestore) | |
848 |
|
848 | |||
849 | if not phases.supportinternal(repo): |
|
849 | if not phases.supportinternal(repo): | |
850 | repair.strip( |
|
850 | repair.strip( | |
851 | ui, repo, state.nodestoremove, backup=False, topic=b'shelve' |
|
851 | ui, repo, state.nodestoremove, backup=False, topic=b'shelve' | |
852 | ) |
|
852 | ) | |
853 | shelvedstate.clear(repo) |
|
853 | shelvedstate.clear(repo) | |
854 | if not ispartialunshelve: |
|
854 | if not ispartialunshelve: | |
855 | unshelvecleanup(ui, repo, state.name, opts) |
|
855 | unshelvecleanup(ui, repo, state.name, opts) | |
856 | _restoreactivebookmark(repo, state.activebookmark) |
|
856 | _restoreactivebookmark(repo, state.activebookmark) | |
857 | ui.status(_(b"unshelve of '%s' complete\n") % state.name) |
|
857 | ui.status(_(b"unshelve of '%s' complete\n") % state.name) | |
858 |
|
858 | |||
859 |
|
859 | |||
860 | def hgcontinueunshelve(ui, repo): |
|
860 | def hgcontinueunshelve(ui, repo): | |
861 | """logic to resume unshelve using 'hg continue'""" |
|
861 | """logic to resume unshelve using 'hg continue'""" | |
862 | with repo.wlock(): |
|
862 | with repo.wlock(): | |
863 | state = _loadshelvedstate(ui, repo, {b'continue': True}) |
|
863 | state = _loadshelvedstate(ui, repo, {b'continue': True}) | |
864 | return unshelvecontinue(ui, repo, state, {b'keep': state.keep}) |
|
864 | return unshelvecontinue(ui, repo, state, {b'keep': state.keep}) | |
865 |
|
865 | |||
866 |
|
866 | |||
867 | def _commitworkingcopychanges(ui, repo, opts, tmpwctx): |
|
867 | def _commitworkingcopychanges(ui, repo, opts, tmpwctx): | |
868 | """Temporarily commit working copy changes before moving unshelve commit""" |
|
868 | """Temporarily commit working copy changes before moving unshelve commit""" | |
869 | # Store pending changes in a commit and remember added in case a shelve |
|
869 | # Store pending changes in a commit and remember added in case a shelve | |
870 | # contains unknown files that are part of the pending change |
|
870 | # contains unknown files that are part of the pending change | |
871 | s = repo.status() |
|
871 | s = repo.status() | |
872 | addedbefore = frozenset(s.added) |
|
872 | addedbefore = frozenset(s.added) | |
873 | if not (s.modified or s.added or s.removed): |
|
873 | if not (s.modified or s.added or s.removed): | |
874 | return tmpwctx, addedbefore |
|
874 | return tmpwctx, addedbefore | |
875 | ui.status( |
|
875 | ui.status( | |
876 | _( |
|
876 | _( | |
877 | b"temporarily committing pending changes " |
|
877 | b"temporarily committing pending changes " | |
878 | b"(restore with 'hg unshelve --abort')\n" |
|
878 | b"(restore with 'hg unshelve --abort')\n" | |
879 | ) |
|
879 | ) | |
880 | ) |
|
880 | ) | |
881 | extra = {b'internal': b'shelve'} |
|
881 | extra = {b'internal': b'shelve'} | |
882 | commitfunc = getcommitfunc(extra=extra, interactive=False, editor=False) |
|
882 | commitfunc = getcommitfunc(extra=extra, interactive=False, editor=False) | |
883 | tempopts = {} |
|
883 | tempopts = {} | |
884 | tempopts[b'message'] = b"pending changes temporary commit" |
|
884 | tempopts[b'message'] = b"pending changes temporary commit" | |
885 | tempopts[b'date'] = opts.get(b'date') |
|
885 | tempopts[b'date'] = opts.get(b'date') | |
886 | with ui.configoverride({(b'ui', b'quiet'): True}): |
|
886 | with ui.configoverride({(b'ui', b'quiet'): True}): | |
887 | node = cmdutil.commit(ui, repo, commitfunc, [], tempopts) |
|
887 | node = cmdutil.commit(ui, repo, commitfunc, [], tempopts) | |
888 | tmpwctx = repo[node] |
|
888 | tmpwctx = repo[node] | |
889 | return tmpwctx, addedbefore |
|
889 | return tmpwctx, addedbefore | |
890 |
|
890 | |||
891 |
|
891 | |||
892 | def _unshelverestorecommit(ui, repo, tr, basename): |
|
892 | def _unshelverestorecommit(ui, repo, tr, basename): | |
893 | """Recreate commit in the repository during the unshelve""" |
|
893 | """Recreate commit in the repository during the unshelve""" | |
894 | repo = repo.unfiltered() |
|
894 | repo = repo.unfiltered() | |
895 | node = None |
|
895 | node = None | |
896 | if shelvedfile(repo, basename, b'shelve').exists(): |
|
896 | if shelvedfile(repo, basename, b'shelve').exists(): | |
897 | node = shelvedfile(repo, basename, b'shelve').readinfo()[b'node'] |
|
897 | node = shelvedfile(repo, basename, b'shelve').readinfo()[b'node'] | |
898 | if node is None or node not in repo: |
|
898 | if node is None or node not in repo: | |
899 | with ui.configoverride({(b'ui', b'quiet'): True}): |
|
899 | with ui.configoverride({(b'ui', b'quiet'): True}): | |
900 | shelvectx = shelvedfile(repo, basename, b'hg').applybundle(tr) |
|
900 | shelvectx = shelvedfile(repo, basename, b'hg').applybundle(tr) | |
901 | # We might not strip the unbundled changeset, so we should keep track of |
|
901 | # We might not strip the unbundled changeset, so we should keep track of | |
902 | # the unshelve node in case we need to reuse it (eg: unshelve --keep) |
|
902 | # the unshelve node in case we need to reuse it (eg: unshelve --keep) | |
903 | if node is None: |
|
903 | if node is None: | |
904 | info = {b'node': nodemod.hex(shelvectx.node())} |
|
904 | info = {b'node': nodemod.hex(shelvectx.node())} | |
905 | shelvedfile(repo, basename, b'shelve').writeinfo(info) |
|
905 | shelvedfile(repo, basename, b'shelve').writeinfo(info) | |
906 | else: |
|
906 | else: | |
907 | shelvectx = repo[node] |
|
907 | shelvectx = repo[node] | |
908 |
|
908 | |||
909 | return repo, shelvectx |
|
909 | return repo, shelvectx | |
910 |
|
910 | |||
911 |
|
911 | |||
912 | def _createunshelvectx(ui, repo, shelvectx, basename, interactive, opts): |
|
912 | def _createunshelvectx(ui, repo, shelvectx, basename, interactive, opts): | |
913 | """Handles the creation of unshelve commit and updates the shelve if it |
|
913 | """Handles the creation of unshelve commit and updates the shelve if it | |
914 | was partially unshelved. |
|
914 | was partially unshelved. | |
915 |
|
915 | |||
916 | If interactive is: |
|
916 | If interactive is: | |
917 |
|
917 | |||
918 | * False: Commits all the changes in the working directory. |
|
918 | * False: Commits all the changes in the working directory. | |
919 | * True: Prompts the user to select changes to unshelve and commit them. |
|
919 | * True: Prompts the user to select changes to unshelve and commit them. | |
920 | Update the shelve with remaining changes. |
|
920 | Update the shelve with remaining changes. | |
921 |
|
921 | |||
922 | Returns the node of the new commit formed and a bool indicating whether |
|
922 | Returns the node of the new commit formed and a bool indicating whether | |
923 | the shelve was partially unshelved.Creates a commit ctx to unshelve |
|
923 | the shelve was partially unshelved.Creates a commit ctx to unshelve | |
924 | interactively or non-interactively. |
|
924 | interactively or non-interactively. | |
925 |
|
925 | |||
926 | The user might want to unshelve certain changes only from the stored |
|
926 | The user might want to unshelve certain changes only from the stored | |
927 | shelve in interactive. So, we would create two commits. One with requested |
|
927 | shelve in interactive. So, we would create two commits. One with requested | |
928 | changes to unshelve at that time and the latter is shelved for future. |
|
928 | changes to unshelve at that time and the latter is shelved for future. | |
929 |
|
929 | |||
930 | Here, we return both the newnode which is created interactively and a |
|
930 | Here, we return both the newnode which is created interactively and a | |
931 | bool to know whether the shelve is partly done or completely done. |
|
931 | bool to know whether the shelve is partly done or completely done. | |
932 | """ |
|
932 | """ | |
933 | opts[b'message'] = shelvectx.description() |
|
933 | opts[b'message'] = shelvectx.description() | |
934 | opts[b'interactive-unshelve'] = True |
|
934 | opts[b'interactive-unshelve'] = True | |
935 | pats = [] |
|
935 | pats = [] | |
936 | if not interactive: |
|
936 | if not interactive: | |
937 | newnode = repo.commit( |
|
937 | newnode = repo.commit( | |
938 | text=shelvectx.description(), |
|
938 | text=shelvectx.description(), | |
939 | extra=shelvectx.extra(), |
|
939 | extra=shelvectx.extra(), | |
940 | user=shelvectx.user(), |
|
940 | user=shelvectx.user(), | |
941 | date=shelvectx.date(), |
|
941 | date=shelvectx.date(), | |
942 | ) |
|
942 | ) | |
943 | return newnode, False |
|
943 | return newnode, False | |
944 |
|
944 | |||
945 | commitfunc = getcommitfunc(shelvectx.extra(), interactive=True, editor=True) |
|
945 | commitfunc = getcommitfunc(shelvectx.extra(), interactive=True, editor=True) | |
946 | newnode = cmdutil.dorecord( |
|
946 | newnode = cmdutil.dorecord( | |
947 | ui, |
|
947 | ui, | |
948 | repo, |
|
948 | repo, | |
949 | commitfunc, |
|
949 | commitfunc, | |
950 | None, |
|
950 | None, | |
951 | False, |
|
951 | False, | |
952 | cmdutil.recordfilter, |
|
952 | cmdutil.recordfilter, | |
953 | *pats, |
|
953 | *pats, | |
954 | **pycompat.strkwargs(opts) |
|
954 | **pycompat.strkwargs(opts) | |
955 | ) |
|
955 | ) | |
956 | snode = repo.commit( |
|
956 | snode = repo.commit( | |
957 | text=shelvectx.description(), |
|
957 | text=shelvectx.description(), | |
958 | extra=shelvectx.extra(), |
|
958 | extra=shelvectx.extra(), | |
959 | user=shelvectx.user(), |
|
959 | user=shelvectx.user(), | |
960 | ) |
|
960 | ) | |
961 | if snode: |
|
961 | if snode: | |
962 | m = scmutil.matchfiles(repo, repo[snode].files()) |
|
962 | m = scmutil.matchfiles(repo, repo[snode].files()) | |
963 | _shelvecreatedcommit(repo, snode, basename, m) |
|
963 | _shelvecreatedcommit(repo, snode, basename, m) | |
964 |
|
964 | |||
965 | return newnode, bool(snode) |
|
965 | return newnode, bool(snode) | |
966 |
|
966 | |||
967 |
|
967 | |||
968 | def _rebaserestoredcommit( |
|
968 | def _rebaserestoredcommit( | |
969 | ui, |
|
969 | ui, | |
970 | repo, |
|
970 | repo, | |
971 | opts, |
|
971 | opts, | |
972 | tr, |
|
972 | tr, | |
973 | oldtiprev, |
|
973 | oldtiprev, | |
974 | basename, |
|
974 | basename, | |
975 | pctx, |
|
975 | pctx, | |
976 | tmpwctx, |
|
976 | tmpwctx, | |
977 | shelvectx, |
|
977 | shelvectx, | |
978 | branchtorestore, |
|
978 | branchtorestore, | |
979 | activebookmark, |
|
979 | activebookmark, | |
980 | ): |
|
980 | ): | |
981 | """Rebase restored commit from its original location to a destination""" |
|
981 | """Rebase restored commit from its original location to a destination""" | |
982 | # If the shelve is not immediately on top of the commit |
|
982 | # If the shelve is not immediately on top of the commit | |
983 | # we'll be merging with, rebase it to be on top. |
|
983 | # we'll be merging with, rebase it to be on top. | |
984 | interactive = opts.get(b'interactive') |
|
984 | interactive = opts.get(b'interactive') | |
985 | if tmpwctx.node() == shelvectx.p1().node() and not interactive: |
|
985 | if tmpwctx.node() == shelvectx.p1().node() and not interactive: | |
986 | # We won't skip on interactive mode because, the user might want to |
|
986 | # We won't skip on interactive mode because, the user might want to | |
987 | # unshelve certain changes only. |
|
987 | # unshelve certain changes only. | |
988 | return shelvectx, False |
|
988 | return shelvectx, False | |
989 |
|
989 | |||
990 | overrides = { |
|
990 | overrides = { | |
991 | (b'ui', b'forcemerge'): opts.get(b'tool', b''), |
|
991 | (b'ui', b'forcemerge'): opts.get(b'tool', b''), | |
992 | (b'phases', b'new-commit'): phases.secret, |
|
992 | (b'phases', b'new-commit'): phases.secret, | |
993 | } |
|
993 | } | |
994 | with repo.ui.configoverride(overrides, b'unshelve'): |
|
994 | with repo.ui.configoverride(overrides, b'unshelve'): | |
995 | ui.status(_(b'rebasing shelved changes\n')) |
|
995 | ui.status(_(b'rebasing shelved changes\n')) | |
996 | stats = merge.graft( |
|
996 | stats = merge.graft( | |
997 | repo, |
|
997 | repo, | |
998 | shelvectx, |
|
998 | shelvectx, | |
999 |
labels=[b' |
|
999 | labels=[b'working-copy', b'shelve'], | |
1000 | keepconflictparent=True, |
|
1000 | keepconflictparent=True, | |
1001 | ) |
|
1001 | ) | |
1002 | if stats.unresolvedcount: |
|
1002 | if stats.unresolvedcount: | |
1003 | tr.close() |
|
1003 | tr.close() | |
1004 |
|
1004 | |||
1005 | nodestoremove = [ |
|
1005 | nodestoremove = [ | |
1006 | repo.changelog.node(rev) |
|
1006 | repo.changelog.node(rev) | |
1007 | for rev in pycompat.xrange(oldtiprev, len(repo)) |
|
1007 | for rev in pycompat.xrange(oldtiprev, len(repo)) | |
1008 | ] |
|
1008 | ] | |
1009 | shelvedstate.save( |
|
1009 | shelvedstate.save( | |
1010 | repo, |
|
1010 | repo, | |
1011 | basename, |
|
1011 | basename, | |
1012 | pctx, |
|
1012 | pctx, | |
1013 | tmpwctx, |
|
1013 | tmpwctx, | |
1014 | nodestoremove, |
|
1014 | nodestoremove, | |
1015 | branchtorestore, |
|
1015 | branchtorestore, | |
1016 | opts.get(b'keep'), |
|
1016 | opts.get(b'keep'), | |
1017 | activebookmark, |
|
1017 | activebookmark, | |
1018 | interactive, |
|
1018 | interactive, | |
1019 | ) |
|
1019 | ) | |
1020 | raise error.InterventionRequired( |
|
1020 | raise error.InterventionRequired( | |
1021 | _( |
|
1021 | _( | |
1022 | b"unresolved conflicts (see 'hg resolve', then " |
|
1022 | b"unresolved conflicts (see 'hg resolve', then " | |
1023 | b"'hg unshelve --continue')" |
|
1023 | b"'hg unshelve --continue')" | |
1024 | ) |
|
1024 | ) | |
1025 | ) |
|
1025 | ) | |
1026 |
|
1026 | |||
1027 | with repo.dirstate.parentchange(): |
|
1027 | with repo.dirstate.parentchange(): | |
1028 | repo.setparents(tmpwctx.node(), nodemod.nullid) |
|
1028 | repo.setparents(tmpwctx.node(), nodemod.nullid) | |
1029 | newnode, ispartialunshelve = _createunshelvectx( |
|
1029 | newnode, ispartialunshelve = _createunshelvectx( | |
1030 | ui, repo, shelvectx, basename, interactive, opts |
|
1030 | ui, repo, shelvectx, basename, interactive, opts | |
1031 | ) |
|
1031 | ) | |
1032 |
|
1032 | |||
1033 | if newnode is None: |
|
1033 | if newnode is None: | |
1034 | # If it ended up being a no-op commit, then the normal |
|
1034 | # If it ended up being a no-op commit, then the normal | |
1035 | # merge state clean-up path doesn't happen, so do it |
|
1035 | # merge state clean-up path doesn't happen, so do it | |
1036 | # here. Fix issue5494 |
|
1036 | # here. Fix issue5494 | |
1037 | merge.mergestate.clean(repo) |
|
1037 | merge.mergestate.clean(repo) | |
1038 | shelvectx = tmpwctx |
|
1038 | shelvectx = tmpwctx | |
1039 | msg = _( |
|
1039 | msg = _( | |
1040 | b'note: unshelved changes already existed ' |
|
1040 | b'note: unshelved changes already existed ' | |
1041 | b'in the working copy\n' |
|
1041 | b'in the working copy\n' | |
1042 | ) |
|
1042 | ) | |
1043 | ui.status(msg) |
|
1043 | ui.status(msg) | |
1044 | else: |
|
1044 | else: | |
1045 | shelvectx = repo[newnode] |
|
1045 | shelvectx = repo[newnode] | |
1046 | hg.updaterepo(repo, tmpwctx.node(), False) |
|
1046 | hg.updaterepo(repo, tmpwctx.node(), False) | |
1047 |
|
1047 | |||
1048 | return shelvectx, ispartialunshelve |
|
1048 | return shelvectx, ispartialunshelve | |
1049 |
|
1049 | |||
1050 |
|
1050 | |||
1051 | def _forgetunknownfiles(repo, shelvectx, addedbefore): |
|
1051 | def _forgetunknownfiles(repo, shelvectx, addedbefore): | |
1052 | # Forget any files that were unknown before the shelve, unknown before |
|
1052 | # Forget any files that were unknown before the shelve, unknown before | |
1053 | # unshelve started, but are now added. |
|
1053 | # unshelve started, but are now added. | |
1054 | shelveunknown = shelvectx.extra().get(b'shelve_unknown') |
|
1054 | shelveunknown = shelvectx.extra().get(b'shelve_unknown') | |
1055 | if not shelveunknown: |
|
1055 | if not shelveunknown: | |
1056 | return |
|
1056 | return | |
1057 | shelveunknown = frozenset(shelveunknown.split(b'\0')) |
|
1057 | shelveunknown = frozenset(shelveunknown.split(b'\0')) | |
1058 | addedafter = frozenset(repo.status().added) |
|
1058 | addedafter = frozenset(repo.status().added) | |
1059 | toforget = (addedafter & shelveunknown) - addedbefore |
|
1059 | toforget = (addedafter & shelveunknown) - addedbefore | |
1060 | repo[None].forget(toforget) |
|
1060 | repo[None].forget(toforget) | |
1061 |
|
1061 | |||
1062 |
|
1062 | |||
1063 | def _finishunshelve(repo, oldtiprev, tr, activebookmark): |
|
1063 | def _finishunshelve(repo, oldtiprev, tr, activebookmark): | |
1064 | _restoreactivebookmark(repo, activebookmark) |
|
1064 | _restoreactivebookmark(repo, activebookmark) | |
1065 | # The transaction aborting will strip all the commits for us, |
|
1065 | # The transaction aborting will strip all the commits for us, | |
1066 | # but it doesn't update the inmemory structures, so addchangegroup |
|
1066 | # but it doesn't update the inmemory structures, so addchangegroup | |
1067 | # hooks still fire and try to operate on the missing commits. |
|
1067 | # hooks still fire and try to operate on the missing commits. | |
1068 | # Clean up manually to prevent this. |
|
1068 | # Clean up manually to prevent this. | |
1069 | repo.unfiltered().changelog.strip(oldtiprev, tr) |
|
1069 | repo.unfiltered().changelog.strip(oldtiprev, tr) | |
1070 | _aborttransaction(repo, tr) |
|
1070 | _aborttransaction(repo, tr) | |
1071 |
|
1071 | |||
1072 |
|
1072 | |||
1073 | def _checkunshelveuntrackedproblems(ui, repo, shelvectx): |
|
1073 | def _checkunshelveuntrackedproblems(ui, repo, shelvectx): | |
1074 | """Check potential problems which may result from working |
|
1074 | """Check potential problems which may result from working | |
1075 | copy having untracked changes.""" |
|
1075 | copy having untracked changes.""" | |
1076 | wcdeleted = set(repo.status().deleted) |
|
1076 | wcdeleted = set(repo.status().deleted) | |
1077 | shelvetouched = set(shelvectx.files()) |
|
1077 | shelvetouched = set(shelvectx.files()) | |
1078 | intersection = wcdeleted.intersection(shelvetouched) |
|
1078 | intersection = wcdeleted.intersection(shelvetouched) | |
1079 | if intersection: |
|
1079 | if intersection: | |
1080 | m = _(b"shelved change touches missing files") |
|
1080 | m = _(b"shelved change touches missing files") | |
1081 | hint = _(b"run hg status to see which files are missing") |
|
1081 | hint = _(b"run hg status to see which files are missing") | |
1082 | raise error.Abort(m, hint=hint) |
|
1082 | raise error.Abort(m, hint=hint) | |
1083 |
|
1083 | |||
1084 |
|
1084 | |||
1085 | def dounshelve(ui, repo, *shelved, **opts): |
|
1085 | def dounshelve(ui, repo, *shelved, **opts): | |
1086 | opts = pycompat.byteskwargs(opts) |
|
1086 | opts = pycompat.byteskwargs(opts) | |
1087 | abortf = opts.get(b'abort') |
|
1087 | abortf = opts.get(b'abort') | |
1088 | continuef = opts.get(b'continue') |
|
1088 | continuef = opts.get(b'continue') | |
1089 | interactive = opts.get(b'interactive') |
|
1089 | interactive = opts.get(b'interactive') | |
1090 | if not abortf and not continuef: |
|
1090 | if not abortf and not continuef: | |
1091 | cmdutil.checkunfinished(repo) |
|
1091 | cmdutil.checkunfinished(repo) | |
1092 | shelved = list(shelved) |
|
1092 | shelved = list(shelved) | |
1093 | if opts.get(b"name"): |
|
1093 | if opts.get(b"name"): | |
1094 | shelved.append(opts[b"name"]) |
|
1094 | shelved.append(opts[b"name"]) | |
1095 |
|
1095 | |||
1096 | if interactive and opts.get(b'keep'): |
|
1096 | if interactive and opts.get(b'keep'): | |
1097 | raise error.Abort(_(b'--keep on --interactive is not yet supported')) |
|
1097 | raise error.Abort(_(b'--keep on --interactive is not yet supported')) | |
1098 | if abortf or continuef: |
|
1098 | if abortf or continuef: | |
1099 | if abortf and continuef: |
|
1099 | if abortf and continuef: | |
1100 | raise error.Abort(_(b'cannot use both abort and continue')) |
|
1100 | raise error.Abort(_(b'cannot use both abort and continue')) | |
1101 | if shelved: |
|
1101 | if shelved: | |
1102 | raise error.Abort( |
|
1102 | raise error.Abort( | |
1103 | _( |
|
1103 | _( | |
1104 | b'cannot combine abort/continue with ' |
|
1104 | b'cannot combine abort/continue with ' | |
1105 | b'naming a shelved change' |
|
1105 | b'naming a shelved change' | |
1106 | ) |
|
1106 | ) | |
1107 | ) |
|
1107 | ) | |
1108 | if abortf and opts.get(b'tool', False): |
|
1108 | if abortf and opts.get(b'tool', False): | |
1109 | ui.warn(_(b'tool option will be ignored\n')) |
|
1109 | ui.warn(_(b'tool option will be ignored\n')) | |
1110 |
|
1110 | |||
1111 | state = _loadshelvedstate(ui, repo, opts) |
|
1111 | state = _loadshelvedstate(ui, repo, opts) | |
1112 | if abortf: |
|
1112 | if abortf: | |
1113 | return unshelveabort(ui, repo, state) |
|
1113 | return unshelveabort(ui, repo, state) | |
1114 | elif continuef and interactive: |
|
1114 | elif continuef and interactive: | |
1115 | raise error.Abort(_(b'cannot use both continue and interactive')) |
|
1115 | raise error.Abort(_(b'cannot use both continue and interactive')) | |
1116 | elif continuef: |
|
1116 | elif continuef: | |
1117 | return unshelvecontinue(ui, repo, state, opts) |
|
1117 | return unshelvecontinue(ui, repo, state, opts) | |
1118 | elif len(shelved) > 1: |
|
1118 | elif len(shelved) > 1: | |
1119 | raise error.Abort(_(b'can only unshelve one change at a time')) |
|
1119 | raise error.Abort(_(b'can only unshelve one change at a time')) | |
1120 | elif not shelved: |
|
1120 | elif not shelved: | |
1121 | shelved = listshelves(repo) |
|
1121 | shelved = listshelves(repo) | |
1122 | if not shelved: |
|
1122 | if not shelved: | |
1123 | raise error.Abort(_(b'no shelved changes to apply!')) |
|
1123 | raise error.Abort(_(b'no shelved changes to apply!')) | |
1124 | basename = util.split(shelved[0][1])[1] |
|
1124 | basename = util.split(shelved[0][1])[1] | |
1125 | ui.status(_(b"unshelving change '%s'\n") % basename) |
|
1125 | ui.status(_(b"unshelving change '%s'\n") % basename) | |
1126 | else: |
|
1126 | else: | |
1127 | basename = shelved[0] |
|
1127 | basename = shelved[0] | |
1128 |
|
1128 | |||
1129 | if not shelvedfile(repo, basename, patchextension).exists(): |
|
1129 | if not shelvedfile(repo, basename, patchextension).exists(): | |
1130 | raise error.Abort(_(b"shelved change '%s' not found") % basename) |
|
1130 | raise error.Abort(_(b"shelved change '%s' not found") % basename) | |
1131 |
|
1131 | |||
1132 | repo = repo.unfiltered() |
|
1132 | repo = repo.unfiltered() | |
1133 | lock = tr = None |
|
1133 | lock = tr = None | |
1134 | try: |
|
1134 | try: | |
1135 | lock = repo.lock() |
|
1135 | lock = repo.lock() | |
1136 | tr = repo.transaction(b'unshelve', report=lambda x: None) |
|
1136 | tr = repo.transaction(b'unshelve', report=lambda x: None) | |
1137 | oldtiprev = len(repo) |
|
1137 | oldtiprev = len(repo) | |
1138 |
|
1138 | |||
1139 | pctx = repo[b'.'] |
|
1139 | pctx = repo[b'.'] | |
1140 | tmpwctx = pctx |
|
1140 | tmpwctx = pctx | |
1141 | # The goal is to have a commit structure like so: |
|
1141 | # The goal is to have a commit structure like so: | |
1142 | # ...-> pctx -> tmpwctx -> shelvectx |
|
1142 | # ...-> pctx -> tmpwctx -> shelvectx | |
1143 | # where tmpwctx is an optional commit with the user's pending changes |
|
1143 | # where tmpwctx is an optional commit with the user's pending changes | |
1144 | # and shelvectx is the unshelved changes. Then we merge it all down |
|
1144 | # and shelvectx is the unshelved changes. Then we merge it all down | |
1145 | # to the original pctx. |
|
1145 | # to the original pctx. | |
1146 |
|
1146 | |||
1147 | activebookmark = _backupactivebookmark(repo) |
|
1147 | activebookmark = _backupactivebookmark(repo) | |
1148 | tmpwctx, addedbefore = _commitworkingcopychanges( |
|
1148 | tmpwctx, addedbefore = _commitworkingcopychanges( | |
1149 | ui, repo, opts, tmpwctx |
|
1149 | ui, repo, opts, tmpwctx | |
1150 | ) |
|
1150 | ) | |
1151 | repo, shelvectx = _unshelverestorecommit(ui, repo, tr, basename) |
|
1151 | repo, shelvectx = _unshelverestorecommit(ui, repo, tr, basename) | |
1152 | _checkunshelveuntrackedproblems(ui, repo, shelvectx) |
|
1152 | _checkunshelveuntrackedproblems(ui, repo, shelvectx) | |
1153 | branchtorestore = b'' |
|
1153 | branchtorestore = b'' | |
1154 | if shelvectx.branch() != shelvectx.p1().branch(): |
|
1154 | if shelvectx.branch() != shelvectx.p1().branch(): | |
1155 | branchtorestore = shelvectx.branch() |
|
1155 | branchtorestore = shelvectx.branch() | |
1156 |
|
1156 | |||
1157 | shelvectx, ispartialunshelve = _rebaserestoredcommit( |
|
1157 | shelvectx, ispartialunshelve = _rebaserestoredcommit( | |
1158 | ui, |
|
1158 | ui, | |
1159 | repo, |
|
1159 | repo, | |
1160 | opts, |
|
1160 | opts, | |
1161 | tr, |
|
1161 | tr, | |
1162 | oldtiprev, |
|
1162 | oldtiprev, | |
1163 | basename, |
|
1163 | basename, | |
1164 | pctx, |
|
1164 | pctx, | |
1165 | tmpwctx, |
|
1165 | tmpwctx, | |
1166 | shelvectx, |
|
1166 | shelvectx, | |
1167 | branchtorestore, |
|
1167 | branchtorestore, | |
1168 | activebookmark, |
|
1168 | activebookmark, | |
1169 | ) |
|
1169 | ) | |
1170 | overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')} |
|
1170 | overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')} | |
1171 | with ui.configoverride(overrides, b'unshelve'): |
|
1171 | with ui.configoverride(overrides, b'unshelve'): | |
1172 | mergefiles(ui, repo, pctx, shelvectx) |
|
1172 | mergefiles(ui, repo, pctx, shelvectx) | |
1173 | restorebranch(ui, repo, branchtorestore) |
|
1173 | restorebranch(ui, repo, branchtorestore) | |
1174 | shelvedstate.clear(repo) |
|
1174 | shelvedstate.clear(repo) | |
1175 | _finishunshelve(repo, oldtiprev, tr, activebookmark) |
|
1175 | _finishunshelve(repo, oldtiprev, tr, activebookmark) | |
1176 | _forgetunknownfiles(repo, shelvectx, addedbefore) |
|
1176 | _forgetunknownfiles(repo, shelvectx, addedbefore) | |
1177 | if not ispartialunshelve: |
|
1177 | if not ispartialunshelve: | |
1178 | unshelvecleanup(ui, repo, basename, opts) |
|
1178 | unshelvecleanup(ui, repo, basename, opts) | |
1179 | finally: |
|
1179 | finally: | |
1180 | if tr: |
|
1180 | if tr: | |
1181 | tr.release() |
|
1181 | tr.release() | |
1182 | lockmod.release(lock) |
|
1182 | lockmod.release(lock) |
@@ -1,1106 +1,1111 b'' | |||||
1 | #if windows |
|
1 | #if windows | |
2 | $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH" |
|
2 | $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH" | |
3 | #else |
|
3 | #else | |
4 | $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH" |
|
4 | $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH" | |
5 | #endif |
|
5 | #endif | |
6 | $ export PYTHONPATH |
|
6 | $ export PYTHONPATH | |
7 |
|
7 | |||
8 | typical client does not want echo-back messages, so test without it: |
|
8 | typical client does not want echo-back messages, so test without it: | |
9 |
|
9 | |||
10 | $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new |
|
10 | $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new | |
11 | $ mv $HGRCPATH.new $HGRCPATH |
|
11 | $ mv $HGRCPATH.new $HGRCPATH | |
12 |
|
12 | |||
13 | $ hg init repo |
|
13 | $ hg init repo | |
14 | $ cd repo |
|
14 | $ cd repo | |
15 |
|
15 | |||
16 | >>> from __future__ import absolute_import |
|
16 | >>> from __future__ import absolute_import | |
17 | >>> import os |
|
17 | >>> import os | |
18 | >>> import sys |
|
18 | >>> import sys | |
19 | >>> from hgclient import bprint, check, readchannel, runcommand |
|
19 | >>> from hgclient import bprint, check, readchannel, runcommand | |
20 | >>> @check |
|
20 | >>> @check | |
21 | ... def hellomessage(server): |
|
21 | ... def hellomessage(server): | |
22 | ... ch, data = readchannel(server) |
|
22 | ... ch, data = readchannel(server) | |
23 | ... bprint(b'%c, %r' % (ch, data)) |
|
23 | ... bprint(b'%c, %r' % (ch, data)) | |
24 | ... # run an arbitrary command to make sure the next thing the server |
|
24 | ... # run an arbitrary command to make sure the next thing the server | |
25 | ... # sends isn't part of the hello message |
|
25 | ... # sends isn't part of the hello message | |
26 | ... runcommand(server, [b'id']) |
|
26 | ... runcommand(server, [b'id']) | |
27 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) |
|
27 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) | |
28 | *** runcommand id |
|
28 | *** runcommand id | |
29 | 000000000000 tip |
|
29 | 000000000000 tip | |
30 |
|
30 | |||
31 | >>> from hgclient import check |
|
31 | >>> from hgclient import check | |
32 | >>> @check |
|
32 | >>> @check | |
33 | ... def unknowncommand(server): |
|
33 | ... def unknowncommand(server): | |
34 | ... server.stdin.write(b'unknowncommand\n') |
|
34 | ... server.stdin.write(b'unknowncommand\n') | |
35 | abort: unknown command unknowncommand |
|
35 | abort: unknown command unknowncommand | |
36 |
|
36 | |||
37 | >>> from hgclient import check, readchannel, runcommand |
|
37 | >>> from hgclient import check, readchannel, runcommand | |
38 | >>> @check |
|
38 | >>> @check | |
39 | ... def checkruncommand(server): |
|
39 | ... def checkruncommand(server): | |
40 | ... # hello block |
|
40 | ... # hello block | |
41 | ... readchannel(server) |
|
41 | ... readchannel(server) | |
42 | ... |
|
42 | ... | |
43 | ... # no args |
|
43 | ... # no args | |
44 | ... runcommand(server, []) |
|
44 | ... runcommand(server, []) | |
45 | ... |
|
45 | ... | |
46 | ... # global options |
|
46 | ... # global options | |
47 | ... runcommand(server, [b'id', b'--quiet']) |
|
47 | ... runcommand(server, [b'id', b'--quiet']) | |
48 | ... |
|
48 | ... | |
49 | ... # make sure global options don't stick through requests |
|
49 | ... # make sure global options don't stick through requests | |
50 | ... runcommand(server, [b'id']) |
|
50 | ... runcommand(server, [b'id']) | |
51 | ... |
|
51 | ... | |
52 | ... # --config |
|
52 | ... # --config | |
53 | ... runcommand(server, [b'id', b'--config', b'ui.quiet=True']) |
|
53 | ... runcommand(server, [b'id', b'--config', b'ui.quiet=True']) | |
54 | ... |
|
54 | ... | |
55 | ... # make sure --config doesn't stick |
|
55 | ... # make sure --config doesn't stick | |
56 | ... runcommand(server, [b'id']) |
|
56 | ... runcommand(server, [b'id']) | |
57 | ... |
|
57 | ... | |
58 | ... # negative return code should be masked |
|
58 | ... # negative return code should be masked | |
59 | ... runcommand(server, [b'id', b'-runknown']) |
|
59 | ... runcommand(server, [b'id', b'-runknown']) | |
60 | *** runcommand |
|
60 | *** runcommand | |
61 | Mercurial Distributed SCM |
|
61 | Mercurial Distributed SCM | |
62 |
|
62 | |||
63 | basic commands: |
|
63 | basic commands: | |
64 |
|
64 | |||
65 | add add the specified files on the next commit |
|
65 | add add the specified files on the next commit | |
66 | annotate show changeset information by line for each file |
|
66 | annotate show changeset information by line for each file | |
67 | clone make a copy of an existing repository |
|
67 | clone make a copy of an existing repository | |
68 | commit commit the specified files or all outstanding changes |
|
68 | commit commit the specified files or all outstanding changes | |
69 | diff diff repository (or selected files) |
|
69 | diff diff repository (or selected files) | |
70 | export dump the header and diffs for one or more changesets |
|
70 | export dump the header and diffs for one or more changesets | |
71 | forget forget the specified files on the next commit |
|
71 | forget forget the specified files on the next commit | |
72 | init create a new repository in the given directory |
|
72 | init create a new repository in the given directory | |
73 | log show revision history of entire repository or files |
|
73 | log show revision history of entire repository or files | |
74 | merge merge another revision into working directory |
|
74 | merge merge another revision into working directory | |
75 | pull pull changes from the specified source |
|
75 | pull pull changes from the specified source | |
76 | push push changes to the specified destination |
|
76 | push push changes to the specified destination | |
77 | remove remove the specified files on the next commit |
|
77 | remove remove the specified files on the next commit | |
78 | serve start stand-alone webserver |
|
78 | serve start stand-alone webserver | |
79 | status show changed files in the working directory |
|
79 | status show changed files in the working directory | |
80 | summary summarize working directory state |
|
80 | summary summarize working directory state | |
81 | update update working directory (or switch revisions) |
|
81 | update update working directory (or switch revisions) | |
82 |
|
82 | |||
83 | (use 'hg help' for the full list of commands or 'hg -v' for details) |
|
83 | (use 'hg help' for the full list of commands or 'hg -v' for details) | |
84 | *** runcommand id --quiet |
|
84 | *** runcommand id --quiet | |
85 | 000000000000 |
|
85 | 000000000000 | |
86 | *** runcommand id |
|
86 | *** runcommand id | |
87 | 000000000000 tip |
|
87 | 000000000000 tip | |
88 | *** runcommand id --config ui.quiet=True |
|
88 | *** runcommand id --config ui.quiet=True | |
89 | 000000000000 |
|
89 | 000000000000 | |
90 | *** runcommand id |
|
90 | *** runcommand id | |
91 | 000000000000 tip |
|
91 | 000000000000 tip | |
92 | *** runcommand id -runknown |
|
92 | *** runcommand id -runknown | |
93 | abort: unknown revision 'unknown'! |
|
93 | abort: unknown revision 'unknown'! | |
94 | [255] |
|
94 | [255] | |
95 |
|
95 | |||
96 | >>> from hgclient import bprint, check, readchannel |
|
96 | >>> from hgclient import bprint, check, readchannel | |
97 | >>> @check |
|
97 | >>> @check | |
98 | ... def inputeof(server): |
|
98 | ... def inputeof(server): | |
99 | ... readchannel(server) |
|
99 | ... readchannel(server) | |
100 | ... server.stdin.write(b'runcommand\n') |
|
100 | ... server.stdin.write(b'runcommand\n') | |
101 | ... # close stdin while server is waiting for input |
|
101 | ... # close stdin while server is waiting for input | |
102 | ... server.stdin.close() |
|
102 | ... server.stdin.close() | |
103 | ... |
|
103 | ... | |
104 | ... # server exits with 1 if the pipe closed while reading the command |
|
104 | ... # server exits with 1 if the pipe closed while reading the command | |
105 | ... bprint(b'server exit code =', b'%d' % server.wait()) |
|
105 | ... bprint(b'server exit code =', b'%d' % server.wait()) | |
106 | server exit code = 1 |
|
106 | server exit code = 1 | |
107 |
|
107 | |||
108 | >>> from hgclient import check, readchannel, runcommand, stringio |
|
108 | >>> from hgclient import check, readchannel, runcommand, stringio | |
109 | >>> @check |
|
109 | >>> @check | |
110 | ... def serverinput(server): |
|
110 | ... def serverinput(server): | |
111 | ... readchannel(server) |
|
111 | ... readchannel(server) | |
112 | ... |
|
112 | ... | |
113 | ... patch = b""" |
|
113 | ... patch = b""" | |
114 | ... # HG changeset patch |
|
114 | ... # HG changeset patch | |
115 | ... # User test |
|
115 | ... # User test | |
116 | ... # Date 0 0 |
|
116 | ... # Date 0 0 | |
117 | ... # Node ID c103a3dec114d882c98382d684d8af798d09d857 |
|
117 | ... # Node ID c103a3dec114d882c98382d684d8af798d09d857 | |
118 | ... # Parent 0000000000000000000000000000000000000000 |
|
118 | ... # Parent 0000000000000000000000000000000000000000 | |
119 | ... 1 |
|
119 | ... 1 | |
120 | ... |
|
120 | ... | |
121 | ... diff -r 000000000000 -r c103a3dec114 a |
|
121 | ... diff -r 000000000000 -r c103a3dec114 a | |
122 | ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000 |
|
122 | ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000 | |
123 | ... +++ b/a Thu Jan 01 00:00:00 1970 +0000 |
|
123 | ... +++ b/a Thu Jan 01 00:00:00 1970 +0000 | |
124 | ... @@ -0,0 +1,1 @@ |
|
124 | ... @@ -0,0 +1,1 @@ | |
125 | ... +1 |
|
125 | ... +1 | |
126 | ... """ |
|
126 | ... """ | |
127 | ... |
|
127 | ... | |
128 | ... runcommand(server, [b'import', b'-'], input=stringio(patch)) |
|
128 | ... runcommand(server, [b'import', b'-'], input=stringio(patch)) | |
129 | ... runcommand(server, [b'log']) |
|
129 | ... runcommand(server, [b'log']) | |
130 | *** runcommand import - |
|
130 | *** runcommand import - | |
131 | applying patch from stdin |
|
131 | applying patch from stdin | |
132 | *** runcommand log |
|
132 | *** runcommand log | |
133 | changeset: 0:eff892de26ec |
|
133 | changeset: 0:eff892de26ec | |
134 | tag: tip |
|
134 | tag: tip | |
135 | user: test |
|
135 | user: test | |
136 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
136 | date: Thu Jan 01 00:00:00 1970 +0000 | |
137 | summary: 1 |
|
137 | summary: 1 | |
138 |
|
138 | |||
139 |
|
139 | |||
140 | check strict parsing of early options: |
|
140 | check strict parsing of early options: | |
141 |
|
141 | |||
142 | >>> import os |
|
142 | >>> import os | |
143 | >>> from hgclient import check, readchannel, runcommand |
|
143 | >>> from hgclient import check, readchannel, runcommand | |
144 | >>> os.environ['HGPLAIN'] = '+strictflags' |
|
144 | >>> os.environ['HGPLAIN'] = '+strictflags' | |
145 | >>> @check |
|
145 | >>> @check | |
146 | ... def cwd(server): |
|
146 | ... def cwd(server): | |
147 | ... readchannel(server) |
|
147 | ... readchannel(server) | |
148 | ... runcommand(server, [b'log', b'-b', b'--config=alias.log=!echo pwned', |
|
148 | ... runcommand(server, [b'log', b'-b', b'--config=alias.log=!echo pwned', | |
149 | ... b'default']) |
|
149 | ... b'default']) | |
150 | *** runcommand log -b --config=alias.log=!echo pwned default |
|
150 | *** runcommand log -b --config=alias.log=!echo pwned default | |
151 | abort: unknown revision '--config=alias.log=!echo pwned'! |
|
151 | abort: unknown revision '--config=alias.log=!echo pwned'! | |
152 | [255] |
|
152 | [255] | |
153 |
|
153 | |||
154 | check that "histedit --commands=-" can read rules from the input channel: |
|
154 | check that "histedit --commands=-" can read rules from the input channel: | |
155 |
|
155 | |||
156 | >>> from hgclient import check, readchannel, runcommand, stringio |
|
156 | >>> from hgclient import check, readchannel, runcommand, stringio | |
157 | >>> @check |
|
157 | >>> @check | |
158 | ... def serverinput(server): |
|
158 | ... def serverinput(server): | |
159 | ... readchannel(server) |
|
159 | ... readchannel(server) | |
160 | ... rules = b'pick eff892de26ec\n' |
|
160 | ... rules = b'pick eff892de26ec\n' | |
161 | ... runcommand(server, [b'histedit', b'0', b'--commands=-', |
|
161 | ... runcommand(server, [b'histedit', b'0', b'--commands=-', | |
162 | ... b'--config', b'extensions.histedit='], |
|
162 | ... b'--config', b'extensions.histedit='], | |
163 | ... input=stringio(rules)) |
|
163 | ... input=stringio(rules)) | |
164 | *** runcommand histedit 0 --commands=- --config extensions.histedit= |
|
164 | *** runcommand histedit 0 --commands=- --config extensions.histedit= | |
165 |
|
165 | |||
166 | check that --cwd doesn't persist between requests: |
|
166 | check that --cwd doesn't persist between requests: | |
167 |
|
167 | |||
168 | $ mkdir foo |
|
168 | $ mkdir foo | |
169 | $ touch foo/bar |
|
169 | $ touch foo/bar | |
170 | >>> from hgclient import check, readchannel, runcommand |
|
170 | >>> from hgclient import check, readchannel, runcommand | |
171 | >>> @check |
|
171 | >>> @check | |
172 | ... def cwd(server): |
|
172 | ... def cwd(server): | |
173 | ... readchannel(server) |
|
173 | ... readchannel(server) | |
174 | ... runcommand(server, [b'--cwd', b'foo', b'st', b'bar']) |
|
174 | ... runcommand(server, [b'--cwd', b'foo', b'st', b'bar']) | |
175 | ... runcommand(server, [b'st', b'foo/bar']) |
|
175 | ... runcommand(server, [b'st', b'foo/bar']) | |
176 | *** runcommand --cwd foo st bar |
|
176 | *** runcommand --cwd foo st bar | |
177 | ? bar |
|
177 | ? bar | |
178 | *** runcommand st foo/bar |
|
178 | *** runcommand st foo/bar | |
179 | ? foo/bar |
|
179 | ? foo/bar | |
180 |
|
180 | |||
181 | $ rm foo/bar |
|
181 | $ rm foo/bar | |
182 |
|
182 | |||
183 |
|
183 | |||
184 | check that local configs for the cached repo aren't inherited when -R is used: |
|
184 | check that local configs for the cached repo aren't inherited when -R is used: | |
185 |
|
185 | |||
186 | $ cat <<EOF >> .hg/hgrc |
|
186 | $ cat <<EOF >> .hg/hgrc | |
187 | > [ui] |
|
187 | > [ui] | |
188 | > foo = bar |
|
188 | > foo = bar | |
189 | > EOF |
|
189 | > EOF | |
190 |
|
190 | |||
191 | #if no-extraextensions |
|
191 | #if no-extraextensions | |
192 |
|
192 | |||
193 | >>> from hgclient import check, readchannel, runcommand, sep |
|
193 | >>> from hgclient import check, readchannel, runcommand, sep | |
194 | >>> @check |
|
194 | >>> @check | |
195 | ... def localhgrc(server): |
|
195 | ... def localhgrc(server): | |
196 | ... readchannel(server) |
|
196 | ... readchannel(server) | |
197 | ... |
|
197 | ... | |
198 | ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should |
|
198 | ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should | |
199 | ... # show it |
|
199 | ... # show it | |
200 | ... runcommand(server, [b'showconfig'], outfilter=sep) |
|
200 | ... runcommand(server, [b'showconfig'], outfilter=sep) | |
201 | ... |
|
201 | ... | |
202 | ... # but not for this repo |
|
202 | ... # but not for this repo | |
203 | ... runcommand(server, [b'init', b'foo']) |
|
203 | ... runcommand(server, [b'init', b'foo']) | |
204 | ... runcommand(server, [b'-R', b'foo', b'showconfig', b'ui', b'defaults']) |
|
204 | ... runcommand(server, [b'-R', b'foo', b'showconfig', b'ui', b'defaults']) | |
205 | *** runcommand showconfig |
|
205 | *** runcommand showconfig | |
206 | bundle.mainreporoot=$TESTTMP/repo |
|
206 | bundle.mainreporoot=$TESTTMP/repo | |
207 | devel.all-warnings=true |
|
207 | devel.all-warnings=true | |
208 | devel.default-date=0 0 |
|
208 | devel.default-date=0 0 | |
209 | extensions.fsmonitor= (fsmonitor !) |
|
209 | extensions.fsmonitor= (fsmonitor !) | |
210 | largefiles.usercache=$TESTTMP/.cache/largefiles |
|
210 | largefiles.usercache=$TESTTMP/.cache/largefiles | |
211 | lfs.usercache=$TESTTMP/.cache/lfs |
|
211 | lfs.usercache=$TESTTMP/.cache/lfs | |
212 | ui.slash=True |
|
212 | ui.slash=True | |
213 | ui.interactive=False |
|
213 | ui.interactive=False | |
214 | ui.merge=internal:merge |
|
214 | ui.merge=internal:merge | |
215 | ui.mergemarkers=detailed |
|
215 | ui.mergemarkers=detailed | |
216 | ui.foo=bar |
|
216 | ui.foo=bar | |
217 | ui.nontty=true |
|
217 | ui.nontty=true | |
218 | web.address=localhost |
|
218 | web.address=localhost | |
219 | web\.ipv6=(?:True|False) (re) |
|
219 | web\.ipv6=(?:True|False) (re) | |
220 | web.server-header=testing stub value |
|
220 | web.server-header=testing stub value | |
221 | *** runcommand init foo |
|
221 | *** runcommand init foo | |
222 | *** runcommand -R foo showconfig ui defaults |
|
222 | *** runcommand -R foo showconfig ui defaults | |
223 | ui.slash=True |
|
223 | ui.slash=True | |
224 | ui.interactive=False |
|
224 | ui.interactive=False | |
225 | ui.merge=internal:merge |
|
225 | ui.merge=internal:merge | |
226 | ui.mergemarkers=detailed |
|
226 | ui.mergemarkers=detailed | |
227 | ui.nontty=true |
|
227 | ui.nontty=true | |
228 | #endif |
|
228 | #endif | |
229 |
|
229 | |||
230 | $ rm -R foo |
|
230 | $ rm -R foo | |
231 |
|
231 | |||
232 | #if windows |
|
232 | #if windows | |
233 | $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH" |
|
233 | $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH" | |
234 | #else |
|
234 | #else | |
235 | $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH" |
|
235 | $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH" | |
236 | #endif |
|
236 | #endif | |
237 |
|
237 | |||
238 | $ cat <<EOF > hook.py |
|
238 | $ cat <<EOF > hook.py | |
239 | > import sys |
|
239 | > import sys | |
240 | > from hgclient import bprint |
|
240 | > from hgclient import bprint | |
241 | > def hook(**args): |
|
241 | > def hook(**args): | |
242 | > bprint(b'hook talking') |
|
242 | > bprint(b'hook talking') | |
243 | > bprint(b'now try to read something: %r' % sys.stdin.read()) |
|
243 | > bprint(b'now try to read something: %r' % sys.stdin.read()) | |
244 | > EOF |
|
244 | > EOF | |
245 |
|
245 | |||
246 | >>> from hgclient import check, readchannel, runcommand, stringio |
|
246 | >>> from hgclient import check, readchannel, runcommand, stringio | |
247 | >>> @check |
|
247 | >>> @check | |
248 | ... def hookoutput(server): |
|
248 | ... def hookoutput(server): | |
249 | ... readchannel(server) |
|
249 | ... readchannel(server) | |
250 | ... runcommand(server, [b'--config', |
|
250 | ... runcommand(server, [b'--config', | |
251 | ... b'hooks.pre-identify=python:hook.hook', |
|
251 | ... b'hooks.pre-identify=python:hook.hook', | |
252 | ... b'id'], |
|
252 | ... b'id'], | |
253 | ... input=stringio(b'some input')) |
|
253 | ... input=stringio(b'some input')) | |
254 | *** runcommand --config hooks.pre-identify=python:hook.hook id |
|
254 | *** runcommand --config hooks.pre-identify=python:hook.hook id | |
255 | eff892de26ec tip |
|
255 | eff892de26ec tip | |
256 | hook talking |
|
256 | hook talking | |
257 | now try to read something: '' |
|
257 | now try to read something: '' | |
258 |
|
258 | |||
259 | Clean hook cached version |
|
259 | Clean hook cached version | |
260 | $ rm hook.py* |
|
260 | $ rm hook.py* | |
261 | $ rm -Rf __pycache__ |
|
261 | $ rm -Rf __pycache__ | |
262 |
|
262 | |||
263 | $ echo a >> a |
|
263 | $ echo a >> a | |
264 | >>> import os |
|
264 | >>> import os | |
265 | >>> from hgclient import check, readchannel, runcommand |
|
265 | >>> from hgclient import check, readchannel, runcommand | |
266 | >>> @check |
|
266 | >>> @check | |
267 | ... def outsidechanges(server): |
|
267 | ... def outsidechanges(server): | |
268 | ... readchannel(server) |
|
268 | ... readchannel(server) | |
269 | ... runcommand(server, [b'status']) |
|
269 | ... runcommand(server, [b'status']) | |
270 | ... os.system('hg ci -Am2') |
|
270 | ... os.system('hg ci -Am2') | |
271 | ... runcommand(server, [b'tip']) |
|
271 | ... runcommand(server, [b'tip']) | |
272 | ... runcommand(server, [b'status']) |
|
272 | ... runcommand(server, [b'status']) | |
273 | *** runcommand status |
|
273 | *** runcommand status | |
274 | M a |
|
274 | M a | |
275 | *** runcommand tip |
|
275 | *** runcommand tip | |
276 | changeset: 1:d3a0a68be6de |
|
276 | changeset: 1:d3a0a68be6de | |
277 | tag: tip |
|
277 | tag: tip | |
278 | user: test |
|
278 | user: test | |
279 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
279 | date: Thu Jan 01 00:00:00 1970 +0000 | |
280 | summary: 2 |
|
280 | summary: 2 | |
281 |
|
281 | |||
282 | *** runcommand status |
|
282 | *** runcommand status | |
283 |
|
283 | |||
284 | >>> import os |
|
284 | >>> import os | |
285 | >>> from hgclient import bprint, check, readchannel, runcommand |
|
285 | >>> from hgclient import bprint, check, readchannel, runcommand | |
286 | >>> @check |
|
286 | >>> @check | |
287 | ... def bookmarks(server): |
|
287 | ... def bookmarks(server): | |
288 | ... readchannel(server) |
|
288 | ... readchannel(server) | |
289 | ... runcommand(server, [b'bookmarks']) |
|
289 | ... runcommand(server, [b'bookmarks']) | |
290 | ... |
|
290 | ... | |
291 | ... # changes .hg/bookmarks |
|
291 | ... # changes .hg/bookmarks | |
292 | ... os.system('hg bookmark -i bm1') |
|
292 | ... os.system('hg bookmark -i bm1') | |
293 | ... os.system('hg bookmark -i bm2') |
|
293 | ... os.system('hg bookmark -i bm2') | |
294 | ... runcommand(server, [b'bookmarks']) |
|
294 | ... runcommand(server, [b'bookmarks']) | |
295 | ... |
|
295 | ... | |
296 | ... # changes .hg/bookmarks.current |
|
296 | ... # changes .hg/bookmarks.current | |
297 | ... os.system('hg upd bm1 -q') |
|
297 | ... os.system('hg upd bm1 -q') | |
298 | ... runcommand(server, [b'bookmarks']) |
|
298 | ... runcommand(server, [b'bookmarks']) | |
299 | ... |
|
299 | ... | |
300 | ... runcommand(server, [b'bookmarks', b'bm3']) |
|
300 | ... runcommand(server, [b'bookmarks', b'bm3']) | |
301 | ... f = open('a', 'ab') |
|
301 | ... f = open('a', 'ab') | |
302 | ... f.write(b'a\n') and None |
|
302 | ... f.write(b'a\n') and None | |
303 | ... f.close() |
|
303 | ... f.close() | |
304 | ... runcommand(server, [b'commit', b'-Amm']) |
|
304 | ... runcommand(server, [b'commit', b'-Amm']) | |
305 | ... runcommand(server, [b'bookmarks']) |
|
305 | ... runcommand(server, [b'bookmarks']) | |
306 | ... bprint(b'') |
|
306 | ... bprint(b'') | |
307 | *** runcommand bookmarks |
|
307 | *** runcommand bookmarks | |
308 | no bookmarks set |
|
308 | no bookmarks set | |
309 | *** runcommand bookmarks |
|
309 | *** runcommand bookmarks | |
310 | bm1 1:d3a0a68be6de |
|
310 | bm1 1:d3a0a68be6de | |
311 | bm2 1:d3a0a68be6de |
|
311 | bm2 1:d3a0a68be6de | |
312 | *** runcommand bookmarks |
|
312 | *** runcommand bookmarks | |
313 | * bm1 1:d3a0a68be6de |
|
313 | * bm1 1:d3a0a68be6de | |
314 | bm2 1:d3a0a68be6de |
|
314 | bm2 1:d3a0a68be6de | |
315 | *** runcommand bookmarks bm3 |
|
315 | *** runcommand bookmarks bm3 | |
316 | *** runcommand commit -Amm |
|
316 | *** runcommand commit -Amm | |
317 | *** runcommand bookmarks |
|
317 | *** runcommand bookmarks | |
318 | bm1 1:d3a0a68be6de |
|
318 | bm1 1:d3a0a68be6de | |
319 | bm2 1:d3a0a68be6de |
|
319 | bm2 1:d3a0a68be6de | |
320 | * bm3 2:aef17e88f5f0 |
|
320 | * bm3 2:aef17e88f5f0 | |
321 |
|
321 | |||
322 |
|
322 | |||
323 | >>> import os |
|
323 | >>> import os | |
324 | >>> from hgclient import check, readchannel, runcommand |
|
324 | >>> from hgclient import check, readchannel, runcommand | |
325 | >>> @check |
|
325 | >>> @check | |
326 | ... def tagscache(server): |
|
326 | ... def tagscache(server): | |
327 | ... readchannel(server) |
|
327 | ... readchannel(server) | |
328 | ... runcommand(server, [b'id', b'-t', b'-r', b'0']) |
|
328 | ... runcommand(server, [b'id', b'-t', b'-r', b'0']) | |
329 | ... os.system('hg tag -r 0 foo') |
|
329 | ... os.system('hg tag -r 0 foo') | |
330 | ... runcommand(server, [b'id', b'-t', b'-r', b'0']) |
|
330 | ... runcommand(server, [b'id', b'-t', b'-r', b'0']) | |
331 | *** runcommand id -t -r 0 |
|
331 | *** runcommand id -t -r 0 | |
332 |
|
332 | |||
333 | *** runcommand id -t -r 0 |
|
333 | *** runcommand id -t -r 0 | |
334 | foo |
|
334 | foo | |
335 |
|
335 | |||
336 | >>> import os |
|
336 | >>> import os | |
337 | >>> from hgclient import check, readchannel, runcommand |
|
337 | >>> from hgclient import check, readchannel, runcommand | |
338 | >>> @check |
|
338 | >>> @check | |
339 | ... def setphase(server): |
|
339 | ... def setphase(server): | |
340 | ... readchannel(server) |
|
340 | ... readchannel(server) | |
341 | ... runcommand(server, [b'phase', b'-r', b'.']) |
|
341 | ... runcommand(server, [b'phase', b'-r', b'.']) | |
342 | ... os.system('hg phase -r . -p') |
|
342 | ... os.system('hg phase -r . -p') | |
343 | ... runcommand(server, [b'phase', b'-r', b'.']) |
|
343 | ... runcommand(server, [b'phase', b'-r', b'.']) | |
344 | *** runcommand phase -r . |
|
344 | *** runcommand phase -r . | |
345 | 3: draft |
|
345 | 3: draft | |
346 | *** runcommand phase -r . |
|
346 | *** runcommand phase -r . | |
347 | 3: public |
|
347 | 3: public | |
348 |
|
348 | |||
349 | $ echo a >> a |
|
349 | $ echo a >> a | |
350 | >>> from hgclient import bprint, check, readchannel, runcommand |
|
350 | >>> from hgclient import bprint, check, readchannel, runcommand | |
351 | >>> @check |
|
351 | >>> @check | |
352 | ... def rollback(server): |
|
352 | ... def rollback(server): | |
353 | ... readchannel(server) |
|
353 | ... readchannel(server) | |
354 | ... runcommand(server, [b'phase', b'-r', b'.', b'-p']) |
|
354 | ... runcommand(server, [b'phase', b'-r', b'.', b'-p']) | |
355 | ... runcommand(server, [b'commit', b'-Am.']) |
|
355 | ... runcommand(server, [b'commit', b'-Am.']) | |
356 | ... runcommand(server, [b'rollback']) |
|
356 | ... runcommand(server, [b'rollback']) | |
357 | ... runcommand(server, [b'phase', b'-r', b'.']) |
|
357 | ... runcommand(server, [b'phase', b'-r', b'.']) | |
358 | ... bprint(b'') |
|
358 | ... bprint(b'') | |
359 | *** runcommand phase -r . -p |
|
359 | *** runcommand phase -r . -p | |
360 | no phases changed |
|
360 | no phases changed | |
361 | *** runcommand commit -Am. |
|
361 | *** runcommand commit -Am. | |
362 | *** runcommand rollback |
|
362 | *** runcommand rollback | |
363 | repository tip rolled back to revision 3 (undo commit) |
|
363 | repository tip rolled back to revision 3 (undo commit) | |
364 | working directory now based on revision 3 |
|
364 | working directory now based on revision 3 | |
365 | *** runcommand phase -r . |
|
365 | *** runcommand phase -r . | |
366 | 3: public |
|
366 | 3: public | |
367 |
|
367 | |||
368 |
|
368 | |||
369 | >>> import os |
|
369 | >>> import os | |
370 | >>> from hgclient import check, readchannel, runcommand |
|
370 | >>> from hgclient import check, readchannel, runcommand | |
371 | >>> @check |
|
371 | >>> @check | |
372 | ... def branch(server): |
|
372 | ... def branch(server): | |
373 | ... readchannel(server) |
|
373 | ... readchannel(server) | |
374 | ... runcommand(server, [b'branch']) |
|
374 | ... runcommand(server, [b'branch']) | |
375 | ... os.system('hg branch foo') |
|
375 | ... os.system('hg branch foo') | |
376 | ... runcommand(server, [b'branch']) |
|
376 | ... runcommand(server, [b'branch']) | |
377 | ... os.system('hg branch default') |
|
377 | ... os.system('hg branch default') | |
378 | *** runcommand branch |
|
378 | *** runcommand branch | |
379 | default |
|
379 | default | |
380 | marked working directory as branch foo |
|
380 | marked working directory as branch foo | |
381 | (branches are permanent and global, did you want a bookmark?) |
|
381 | (branches are permanent and global, did you want a bookmark?) | |
382 | *** runcommand branch |
|
382 | *** runcommand branch | |
383 | foo |
|
383 | foo | |
384 | marked working directory as branch default |
|
384 | marked working directory as branch default | |
385 | (branches are permanent and global, did you want a bookmark?) |
|
385 | (branches are permanent and global, did you want a bookmark?) | |
386 |
|
386 | |||
387 | $ touch .hgignore |
|
387 | $ touch .hgignore | |
388 | >>> import os |
|
388 | >>> import os | |
389 | >>> from hgclient import bprint, check, readchannel, runcommand |
|
389 | >>> from hgclient import bprint, check, readchannel, runcommand | |
390 | >>> @check |
|
390 | >>> @check | |
391 | ... def hgignore(server): |
|
391 | ... def hgignore(server): | |
392 | ... readchannel(server) |
|
392 | ... readchannel(server) | |
393 | ... runcommand(server, [b'commit', b'-Am.']) |
|
393 | ... runcommand(server, [b'commit', b'-Am.']) | |
394 | ... f = open('ignored-file', 'ab') |
|
394 | ... f = open('ignored-file', 'ab') | |
395 | ... f.write(b'') and None |
|
395 | ... f.write(b'') and None | |
396 | ... f.close() |
|
396 | ... f.close() | |
397 | ... f = open('.hgignore', 'ab') |
|
397 | ... f = open('.hgignore', 'ab') | |
398 | ... f.write(b'ignored-file') |
|
398 | ... f.write(b'ignored-file') | |
399 | ... f.close() |
|
399 | ... f.close() | |
400 | ... runcommand(server, [b'status', b'-i', b'-u']) |
|
400 | ... runcommand(server, [b'status', b'-i', b'-u']) | |
401 | ... bprint(b'') |
|
401 | ... bprint(b'') | |
402 | *** runcommand commit -Am. |
|
402 | *** runcommand commit -Am. | |
403 | adding .hgignore |
|
403 | adding .hgignore | |
404 | *** runcommand status -i -u |
|
404 | *** runcommand status -i -u | |
405 | I ignored-file |
|
405 | I ignored-file | |
406 |
|
406 | |||
407 |
|
407 | |||
408 | cache of non-public revisions should be invalidated on repository change |
|
408 | cache of non-public revisions should be invalidated on repository change | |
409 | (issue4855): |
|
409 | (issue4855): | |
410 |
|
410 | |||
411 | >>> import os |
|
411 | >>> import os | |
412 | >>> from hgclient import bprint, check, readchannel, runcommand |
|
412 | >>> from hgclient import bprint, check, readchannel, runcommand | |
413 | >>> @check |
|
413 | >>> @check | |
414 | ... def phasesetscacheaftercommit(server): |
|
414 | ... def phasesetscacheaftercommit(server): | |
415 | ... readchannel(server) |
|
415 | ... readchannel(server) | |
416 | ... # load _phasecache._phaserevs and _phasesets |
|
416 | ... # load _phasecache._phaserevs and _phasesets | |
417 | ... runcommand(server, [b'log', b'-qr', b'draft()']) |
|
417 | ... runcommand(server, [b'log', b'-qr', b'draft()']) | |
418 | ... # create draft commits by another process |
|
418 | ... # create draft commits by another process | |
419 | ... for i in range(5, 7): |
|
419 | ... for i in range(5, 7): | |
420 | ... f = open('a', 'ab') |
|
420 | ... f = open('a', 'ab') | |
421 | ... f.seek(0, os.SEEK_END) |
|
421 | ... f.seek(0, os.SEEK_END) | |
422 | ... f.write(b'a\n') and None |
|
422 | ... f.write(b'a\n') and None | |
423 | ... f.close() |
|
423 | ... f.close() | |
424 | ... os.system('hg commit -Aqm%d' % i) |
|
424 | ... os.system('hg commit -Aqm%d' % i) | |
425 | ... # new commits should be listed as draft revisions |
|
425 | ... # new commits should be listed as draft revisions | |
426 | ... runcommand(server, [b'log', b'-qr', b'draft()']) |
|
426 | ... runcommand(server, [b'log', b'-qr', b'draft()']) | |
427 | ... bprint(b'') |
|
427 | ... bprint(b'') | |
428 | *** runcommand log -qr draft() |
|
428 | *** runcommand log -qr draft() | |
429 | 4:7966c8e3734d |
|
429 | 4:7966c8e3734d | |
430 | *** runcommand log -qr draft() |
|
430 | *** runcommand log -qr draft() | |
431 | 4:7966c8e3734d |
|
431 | 4:7966c8e3734d | |
432 | 5:41f6602d1c4f |
|
432 | 5:41f6602d1c4f | |
433 | 6:10501e202c35 |
|
433 | 6:10501e202c35 | |
434 |
|
434 | |||
435 |
|
435 | |||
436 | >>> import os |
|
436 | >>> import os | |
437 | >>> from hgclient import bprint, check, readchannel, runcommand |
|
437 | >>> from hgclient import bprint, check, readchannel, runcommand | |
438 | >>> @check |
|
438 | >>> @check | |
439 | ... def phasesetscacheafterstrip(server): |
|
439 | ... def phasesetscacheafterstrip(server): | |
440 | ... readchannel(server) |
|
440 | ... readchannel(server) | |
441 | ... # load _phasecache._phaserevs and _phasesets |
|
441 | ... # load _phasecache._phaserevs and _phasesets | |
442 | ... runcommand(server, [b'log', b'-qr', b'draft()']) |
|
442 | ... runcommand(server, [b'log', b'-qr', b'draft()']) | |
443 | ... # strip cached revisions by another process |
|
443 | ... # strip cached revisions by another process | |
444 | ... os.system('hg --config extensions.strip= strip -q 5') |
|
444 | ... os.system('hg --config extensions.strip= strip -q 5') | |
445 | ... # shouldn't abort by "unknown revision '6'" |
|
445 | ... # shouldn't abort by "unknown revision '6'" | |
446 | ... runcommand(server, [b'log', b'-qr', b'draft()']) |
|
446 | ... runcommand(server, [b'log', b'-qr', b'draft()']) | |
447 | ... bprint(b'') |
|
447 | ... bprint(b'') | |
448 | *** runcommand log -qr draft() |
|
448 | *** runcommand log -qr draft() | |
449 | 4:7966c8e3734d |
|
449 | 4:7966c8e3734d | |
450 | 5:41f6602d1c4f |
|
450 | 5:41f6602d1c4f | |
451 | 6:10501e202c35 |
|
451 | 6:10501e202c35 | |
452 | *** runcommand log -qr draft() |
|
452 | *** runcommand log -qr draft() | |
453 | 4:7966c8e3734d |
|
453 | 4:7966c8e3734d | |
454 |
|
454 | |||
455 |
|
455 | |||
456 | cache of phase roots should be invalidated on strip (issue3827): |
|
456 | cache of phase roots should be invalidated on strip (issue3827): | |
457 |
|
457 | |||
458 | >>> import os |
|
458 | >>> import os | |
459 | >>> from hgclient import check, readchannel, runcommand, sep |
|
459 | >>> from hgclient import check, readchannel, runcommand, sep | |
460 | >>> @check |
|
460 | >>> @check | |
461 | ... def phasecacheafterstrip(server): |
|
461 | ... def phasecacheafterstrip(server): | |
462 | ... readchannel(server) |
|
462 | ... readchannel(server) | |
463 | ... |
|
463 | ... | |
464 | ... # create new head, 5:731265503d86 |
|
464 | ... # create new head, 5:731265503d86 | |
465 | ... runcommand(server, [b'update', b'-C', b'0']) |
|
465 | ... runcommand(server, [b'update', b'-C', b'0']) | |
466 | ... f = open('a', 'ab') |
|
466 | ... f = open('a', 'ab') | |
467 | ... f.write(b'a\n') and None |
|
467 | ... f.write(b'a\n') and None | |
468 | ... f.close() |
|
468 | ... f.close() | |
469 | ... runcommand(server, [b'commit', b'-Am.', b'a']) |
|
469 | ... runcommand(server, [b'commit', b'-Am.', b'a']) | |
470 | ... runcommand(server, [b'log', b'-Gq']) |
|
470 | ... runcommand(server, [b'log', b'-Gq']) | |
471 | ... |
|
471 | ... | |
472 | ... # make it public; draft marker moves to 4:7966c8e3734d |
|
472 | ... # make it public; draft marker moves to 4:7966c8e3734d | |
473 | ... runcommand(server, [b'phase', b'-p', b'.']) |
|
473 | ... runcommand(server, [b'phase', b'-p', b'.']) | |
474 | ... # load _phasecache.phaseroots |
|
474 | ... # load _phasecache.phaseroots | |
475 | ... runcommand(server, [b'phase', b'.'], outfilter=sep) |
|
475 | ... runcommand(server, [b'phase', b'.'], outfilter=sep) | |
476 | ... |
|
476 | ... | |
477 | ... # strip 1::4 outside server |
|
477 | ... # strip 1::4 outside server | |
478 | ... os.system('hg -q --config extensions.mq= strip 1') |
|
478 | ... os.system('hg -q --config extensions.mq= strip 1') | |
479 | ... |
|
479 | ... | |
480 | ... # shouldn't raise "7966c8e3734d: no node!" |
|
480 | ... # shouldn't raise "7966c8e3734d: no node!" | |
481 | ... runcommand(server, [b'branches']) |
|
481 | ... runcommand(server, [b'branches']) | |
482 | *** runcommand update -C 0 |
|
482 | *** runcommand update -C 0 | |
483 | 1 files updated, 0 files merged, 2 files removed, 0 files unresolved |
|
483 | 1 files updated, 0 files merged, 2 files removed, 0 files unresolved | |
484 | (leaving bookmark bm3) |
|
484 | (leaving bookmark bm3) | |
485 | *** runcommand commit -Am. a |
|
485 | *** runcommand commit -Am. a | |
486 | created new head |
|
486 | created new head | |
487 | *** runcommand log -Gq |
|
487 | *** runcommand log -Gq | |
488 | @ 5:731265503d86 |
|
488 | @ 5:731265503d86 | |
489 | | |
|
489 | | | |
490 | | o 4:7966c8e3734d |
|
490 | | o 4:7966c8e3734d | |
491 | | | |
|
491 | | | | |
492 | | o 3:b9b85890c400 |
|
492 | | o 3:b9b85890c400 | |
493 | | | |
|
493 | | | | |
494 | | o 2:aef17e88f5f0 |
|
494 | | o 2:aef17e88f5f0 | |
495 | | | |
|
495 | | | | |
496 | | o 1:d3a0a68be6de |
|
496 | | o 1:d3a0a68be6de | |
497 | |/ |
|
497 | |/ | |
498 | o 0:eff892de26ec |
|
498 | o 0:eff892de26ec | |
499 |
|
499 | |||
500 | *** runcommand phase -p . |
|
500 | *** runcommand phase -p . | |
501 | *** runcommand phase . |
|
501 | *** runcommand phase . | |
502 | 5: public |
|
502 | 5: public | |
503 | *** runcommand branches |
|
503 | *** runcommand branches | |
504 | default 1:731265503d86 |
|
504 | default 1:731265503d86 | |
505 |
|
505 | |||
506 | in-memory cache must be reloaded if transaction is aborted. otherwise |
|
506 | in-memory cache must be reloaded if transaction is aborted. otherwise | |
507 | changelog and manifest would have invalid node: |
|
507 | changelog and manifest would have invalid node: | |
508 |
|
508 | |||
509 | $ echo a >> a |
|
509 | $ echo a >> a | |
510 | >>> from hgclient import check, readchannel, runcommand |
|
510 | >>> from hgclient import check, readchannel, runcommand | |
511 | >>> @check |
|
511 | >>> @check | |
512 | ... def txabort(server): |
|
512 | ... def txabort(server): | |
513 | ... readchannel(server) |
|
513 | ... readchannel(server) | |
514 | ... runcommand(server, [b'commit', b'--config', b'hooks.pretxncommit=false', |
|
514 | ... runcommand(server, [b'commit', b'--config', b'hooks.pretxncommit=false', | |
515 | ... b'-mfoo']) |
|
515 | ... b'-mfoo']) | |
516 | ... runcommand(server, [b'verify']) |
|
516 | ... runcommand(server, [b'verify']) | |
517 | *** runcommand commit --config hooks.pretxncommit=false -mfoo |
|
517 | *** runcommand commit --config hooks.pretxncommit=false -mfoo | |
518 | transaction abort! |
|
518 | transaction abort! | |
519 | rollback completed |
|
519 | rollback completed | |
520 | abort: pretxncommit hook exited with status 1 |
|
520 | abort: pretxncommit hook exited with status 1 | |
521 | [255] |
|
521 | [255] | |
522 | *** runcommand verify |
|
522 | *** runcommand verify | |
523 | checking changesets |
|
523 | checking changesets | |
524 | checking manifests |
|
524 | checking manifests | |
525 | crosschecking files in changesets and manifests |
|
525 | crosschecking files in changesets and manifests | |
526 | checking files |
|
526 | checking files | |
527 | checked 2 changesets with 2 changes to 1 files |
|
527 | checked 2 changesets with 2 changes to 1 files | |
528 | $ hg revert --no-backup -aq |
|
528 | $ hg revert --no-backup -aq | |
529 |
|
529 | |||
530 | $ cat >> .hg/hgrc << EOF |
|
530 | $ cat >> .hg/hgrc << EOF | |
531 | > [experimental] |
|
531 | > [experimental] | |
532 | > evolution.createmarkers=True |
|
532 | > evolution.createmarkers=True | |
533 | > EOF |
|
533 | > EOF | |
534 |
|
534 | |||
535 | >>> import os |
|
535 | >>> import os | |
536 | >>> from hgclient import check, readchannel, runcommand |
|
536 | >>> from hgclient import check, readchannel, runcommand | |
537 | >>> @check |
|
537 | >>> @check | |
538 | ... def obsolete(server): |
|
538 | ... def obsolete(server): | |
539 | ... readchannel(server) |
|
539 | ... readchannel(server) | |
540 | ... |
|
540 | ... | |
541 | ... runcommand(server, [b'up', b'null']) |
|
541 | ... runcommand(server, [b'up', b'null']) | |
542 | ... runcommand(server, [b'phase', b'-df', b'tip']) |
|
542 | ... runcommand(server, [b'phase', b'-df', b'tip']) | |
543 | ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`' |
|
543 | ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`' | |
544 | ... if os.name == 'nt': |
|
544 | ... if os.name == 'nt': | |
545 | ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe |
|
545 | ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe | |
546 | ... os.system(cmd) |
|
546 | ... os.system(cmd) | |
547 | ... runcommand(server, [b'log', b'--hidden']) |
|
547 | ... runcommand(server, [b'log', b'--hidden']) | |
548 | ... runcommand(server, [b'log']) |
|
548 | ... runcommand(server, [b'log']) | |
549 | *** runcommand up null |
|
549 | *** runcommand up null | |
550 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved |
|
550 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved | |
551 | *** runcommand phase -df tip |
|
551 | *** runcommand phase -df tip | |
552 | 1 new obsolescence markers |
|
552 | 1 new obsolescence markers | |
553 | obsoleted 1 changesets |
|
553 | obsoleted 1 changesets | |
554 | *** runcommand log --hidden |
|
554 | *** runcommand log --hidden | |
555 | changeset: 1:731265503d86 |
|
555 | changeset: 1:731265503d86 | |
556 | tag: tip |
|
556 | tag: tip | |
557 | user: test |
|
557 | user: test | |
558 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
558 | date: Thu Jan 01 00:00:00 1970 +0000 | |
559 | obsolete: pruned |
|
559 | obsolete: pruned | |
560 | summary: . |
|
560 | summary: . | |
561 |
|
561 | |||
562 | changeset: 0:eff892de26ec |
|
562 | changeset: 0:eff892de26ec | |
563 | bookmark: bm1 |
|
563 | bookmark: bm1 | |
564 | bookmark: bm2 |
|
564 | bookmark: bm2 | |
565 | bookmark: bm3 |
|
565 | bookmark: bm3 | |
566 | user: test |
|
566 | user: test | |
567 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
567 | date: Thu Jan 01 00:00:00 1970 +0000 | |
568 | summary: 1 |
|
568 | summary: 1 | |
569 |
|
569 | |||
570 | *** runcommand log |
|
570 | *** runcommand log | |
571 | changeset: 0:eff892de26ec |
|
571 | changeset: 0:eff892de26ec | |
572 | bookmark: bm1 |
|
572 | bookmark: bm1 | |
573 | bookmark: bm2 |
|
573 | bookmark: bm2 | |
574 | bookmark: bm3 |
|
574 | bookmark: bm3 | |
575 | tag: tip |
|
575 | tag: tip | |
576 | user: test |
|
576 | user: test | |
577 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
577 | date: Thu Jan 01 00:00:00 1970 +0000 | |
578 | summary: 1 |
|
578 | summary: 1 | |
579 |
|
579 | |||
580 |
|
580 | |||
581 | $ cat <<EOF >> .hg/hgrc |
|
581 | $ cat <<EOF >> .hg/hgrc | |
582 | > [extensions] |
|
582 | > [extensions] | |
583 | > mq = |
|
583 | > mq = | |
584 | > EOF |
|
584 | > EOF | |
585 |
|
585 | |||
586 | >>> import os |
|
586 | >>> import os | |
587 | >>> from hgclient import check, readchannel, runcommand |
|
587 | >>> from hgclient import check, readchannel, runcommand | |
588 | >>> @check |
|
588 | >>> @check | |
589 | ... def mqoutsidechanges(server): |
|
589 | ... def mqoutsidechanges(server): | |
590 | ... readchannel(server) |
|
590 | ... readchannel(server) | |
591 | ... |
|
591 | ... | |
592 | ... # load repo.mq |
|
592 | ... # load repo.mq | |
593 | ... runcommand(server, [b'qapplied']) |
|
593 | ... runcommand(server, [b'qapplied']) | |
594 | ... os.system('hg qnew 0.diff') |
|
594 | ... os.system('hg qnew 0.diff') | |
595 | ... # repo.mq should be invalidated |
|
595 | ... # repo.mq should be invalidated | |
596 | ... runcommand(server, [b'qapplied']) |
|
596 | ... runcommand(server, [b'qapplied']) | |
597 | ... |
|
597 | ... | |
598 | ... runcommand(server, [b'qpop', b'--all']) |
|
598 | ... runcommand(server, [b'qpop', b'--all']) | |
599 | ... os.system('hg qqueue --create foo') |
|
599 | ... os.system('hg qqueue --create foo') | |
600 | ... # repo.mq should be recreated to point to new queue |
|
600 | ... # repo.mq should be recreated to point to new queue | |
601 | ... runcommand(server, [b'qqueue', b'--active']) |
|
601 | ... runcommand(server, [b'qqueue', b'--active']) | |
602 | *** runcommand qapplied |
|
602 | *** runcommand qapplied | |
603 | *** runcommand qapplied |
|
603 | *** runcommand qapplied | |
604 | 0.diff |
|
604 | 0.diff | |
605 | *** runcommand qpop --all |
|
605 | *** runcommand qpop --all | |
606 | popping 0.diff |
|
606 | popping 0.diff | |
607 | patch queue now empty |
|
607 | patch queue now empty | |
608 | *** runcommand qqueue --active |
|
608 | *** runcommand qqueue --active | |
609 | foo |
|
609 | foo | |
610 |
|
610 | |||
611 | $ cat <<'EOF' > ../dbgui.py |
|
611 | $ cat <<'EOF' > ../dbgui.py | |
612 | > import os |
|
612 | > import os | |
613 | > import sys |
|
613 | > import sys | |
614 | > from mercurial import commands, registrar |
|
614 | > from mercurial import commands, registrar | |
615 | > cmdtable = {} |
|
615 | > cmdtable = {} | |
616 | > command = registrar.command(cmdtable) |
|
616 | > command = registrar.command(cmdtable) | |
617 | > @command(b"debuggetpass", norepo=True) |
|
617 | > @command(b"debuggetpass", norepo=True) | |
618 | > def debuggetpass(ui): |
|
618 | > def debuggetpass(ui): | |
619 | > ui.write(b"%s\n" % ui.getpass()) |
|
619 | > ui.write(b"%s\n" % ui.getpass()) | |
620 | > @command(b"debugprompt", norepo=True) |
|
620 | > @command(b"debugprompt", norepo=True) | |
621 | > def debugprompt(ui): |
|
621 | > def debugprompt(ui): | |
622 | > ui.write(b"%s\n" % ui.prompt(b"prompt:")) |
|
622 | > ui.write(b"%s\n" % ui.prompt(b"prompt:")) | |
623 | > @command(b"debugpromptchoice", norepo=True) |
|
623 | > @command(b"debugpromptchoice", norepo=True) | |
624 | > def debugpromptchoice(ui): |
|
624 | > def debugpromptchoice(ui): | |
625 | > msg = b"promptchoice (y/n)? $$ &Yes $$ &No" |
|
625 | > msg = b"promptchoice (y/n)? $$ &Yes $$ &No" | |
626 | > ui.write(b"%d\n" % ui.promptchoice(msg)) |
|
626 | > ui.write(b"%d\n" % ui.promptchoice(msg)) | |
627 | > @command(b"debugreadstdin", norepo=True) |
|
627 | > @command(b"debugreadstdin", norepo=True) | |
628 | > def debugreadstdin(ui): |
|
628 | > def debugreadstdin(ui): | |
629 | > ui.write(b"read: %r\n" % sys.stdin.read(1)) |
|
629 | > ui.write(b"read: %r\n" % sys.stdin.read(1)) | |
630 | > @command(b"debugwritestdout", norepo=True) |
|
630 | > @command(b"debugwritestdout", norepo=True) | |
631 | > def debugwritestdout(ui): |
|
631 | > def debugwritestdout(ui): | |
632 | > os.write(1, b"low-level stdout fd and\n") |
|
632 | > os.write(1, b"low-level stdout fd and\n") | |
633 | > sys.stdout.write("stdout should be redirected to stderr\n") |
|
633 | > sys.stdout.write("stdout should be redirected to stderr\n") | |
634 | > sys.stdout.flush() |
|
634 | > sys.stdout.flush() | |
635 | > EOF |
|
635 | > EOF | |
636 | $ cat <<EOF >> .hg/hgrc |
|
636 | $ cat <<EOF >> .hg/hgrc | |
637 | > [extensions] |
|
637 | > [extensions] | |
638 | > dbgui = ../dbgui.py |
|
638 | > dbgui = ../dbgui.py | |
639 | > EOF |
|
639 | > EOF | |
640 |
|
640 | |||
641 | >>> from hgclient import check, readchannel, runcommand, stringio |
|
641 | >>> from hgclient import check, readchannel, runcommand, stringio | |
642 | >>> @check |
|
642 | >>> @check | |
643 | ... def getpass(server): |
|
643 | ... def getpass(server): | |
644 | ... readchannel(server) |
|
644 | ... readchannel(server) | |
645 | ... runcommand(server, [b'debuggetpass', b'--config', |
|
645 | ... runcommand(server, [b'debuggetpass', b'--config', | |
646 | ... b'ui.interactive=True'], |
|
646 | ... b'ui.interactive=True'], | |
647 | ... input=stringio(b'1234\n')) |
|
647 | ... input=stringio(b'1234\n')) | |
648 | ... runcommand(server, [b'debuggetpass', b'--config', |
|
648 | ... runcommand(server, [b'debuggetpass', b'--config', | |
649 | ... b'ui.interactive=True'], |
|
649 | ... b'ui.interactive=True'], | |
650 | ... input=stringio(b'\n')) |
|
650 | ... input=stringio(b'\n')) | |
651 | ... runcommand(server, [b'debuggetpass', b'--config', |
|
651 | ... runcommand(server, [b'debuggetpass', b'--config', | |
652 | ... b'ui.interactive=True'], |
|
652 | ... b'ui.interactive=True'], | |
653 | ... input=stringio(b'')) |
|
653 | ... input=stringio(b'')) | |
654 | ... runcommand(server, [b'debugprompt', b'--config', |
|
654 | ... runcommand(server, [b'debugprompt', b'--config', | |
655 | ... b'ui.interactive=True'], |
|
655 | ... b'ui.interactive=True'], | |
656 | ... input=stringio(b'5678\n')) |
|
656 | ... input=stringio(b'5678\n')) | |
|
657 | ... runcommand(server, [b'debugprompt', b'--config', | |||
|
658 | ... b'ui.interactive=True'], | |||
|
659 | ... input=stringio(b'\nremainder\nshould\nnot\nbe\nread\n')) | |||
657 | ... runcommand(server, [b'debugreadstdin']) |
|
660 | ... runcommand(server, [b'debugreadstdin']) | |
658 | ... runcommand(server, [b'debugwritestdout']) |
|
661 | ... runcommand(server, [b'debugwritestdout']) | |
659 | *** runcommand debuggetpass --config ui.interactive=True |
|
662 | *** runcommand debuggetpass --config ui.interactive=True | |
660 | password: 1234 |
|
663 | password: 1234 | |
661 | *** runcommand debuggetpass --config ui.interactive=True |
|
664 | *** runcommand debuggetpass --config ui.interactive=True | |
662 | password: |
|
665 | password: | |
663 | *** runcommand debuggetpass --config ui.interactive=True |
|
666 | *** runcommand debuggetpass --config ui.interactive=True | |
664 | password: abort: response expected |
|
667 | password: abort: response expected | |
665 | [255] |
|
668 | [255] | |
666 | *** runcommand debugprompt --config ui.interactive=True |
|
669 | *** runcommand debugprompt --config ui.interactive=True | |
667 | prompt: 5678 |
|
670 | prompt: 5678 | |
|
671 | *** runcommand debugprompt --config ui.interactive=True | |||
|
672 | prompt: y | |||
668 | *** runcommand debugreadstdin |
|
673 | *** runcommand debugreadstdin | |
669 | read: '' |
|
674 | read: '' | |
670 | *** runcommand debugwritestdout |
|
675 | *** runcommand debugwritestdout | |
671 | low-level stdout fd and |
|
676 | low-level stdout fd and | |
672 | stdout should be redirected to stderr |
|
677 | stdout should be redirected to stderr | |
673 |
|
678 | |||
674 |
|
679 | |||
675 | run commandserver in commandserver, which is silly but should work: |
|
680 | run commandserver in commandserver, which is silly but should work: | |
676 |
|
681 | |||
677 | >>> from hgclient import bprint, check, readchannel, runcommand, stringio |
|
682 | >>> from hgclient import bprint, check, readchannel, runcommand, stringio | |
678 | >>> @check |
|
683 | >>> @check | |
679 | ... def nested(server): |
|
684 | ... def nested(server): | |
680 | ... bprint(b'%c, %r' % readchannel(server)) |
|
685 | ... bprint(b'%c, %r' % readchannel(server)) | |
681 | ... class nestedserver(object): |
|
686 | ... class nestedserver(object): | |
682 | ... stdin = stringio(b'getencoding\n') |
|
687 | ... stdin = stringio(b'getencoding\n') | |
683 | ... stdout = stringio() |
|
688 | ... stdout = stringio() | |
684 | ... runcommand(server, [b'serve', b'--cmdserver', b'pipe'], |
|
689 | ... runcommand(server, [b'serve', b'--cmdserver', b'pipe'], | |
685 | ... output=nestedserver.stdout, input=nestedserver.stdin) |
|
690 | ... output=nestedserver.stdout, input=nestedserver.stdin) | |
686 | ... nestedserver.stdout.seek(0) |
|
691 | ... nestedserver.stdout.seek(0) | |
687 | ... bprint(b'%c, %r' % readchannel(nestedserver)) # hello |
|
692 | ... bprint(b'%c, %r' % readchannel(nestedserver)) # hello | |
688 | ... bprint(b'%c, %r' % readchannel(nestedserver)) # getencoding |
|
693 | ... bprint(b'%c, %r' % readchannel(nestedserver)) # getencoding | |
689 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) |
|
694 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) | |
690 | *** runcommand serve --cmdserver pipe |
|
695 | *** runcommand serve --cmdserver pipe | |
691 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) |
|
696 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) | |
692 | r, '*' (glob) |
|
697 | r, '*' (glob) | |
693 |
|
698 | |||
694 |
|
699 | |||
695 | start without repository: |
|
700 | start without repository: | |
696 |
|
701 | |||
697 | $ cd .. |
|
702 | $ cd .. | |
698 |
|
703 | |||
699 | >>> from hgclient import bprint, check, readchannel, runcommand |
|
704 | >>> from hgclient import bprint, check, readchannel, runcommand | |
700 | >>> @check |
|
705 | >>> @check | |
701 | ... def hellomessage(server): |
|
706 | ... def hellomessage(server): | |
702 | ... ch, data = readchannel(server) |
|
707 | ... ch, data = readchannel(server) | |
703 | ... bprint(b'%c, %r' % (ch, data)) |
|
708 | ... bprint(b'%c, %r' % (ch, data)) | |
704 | ... # run an arbitrary command to make sure the next thing the server |
|
709 | ... # run an arbitrary command to make sure the next thing the server | |
705 | ... # sends isn't part of the hello message |
|
710 | ... # sends isn't part of the hello message | |
706 | ... runcommand(server, [b'id']) |
|
711 | ... runcommand(server, [b'id']) | |
707 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) |
|
712 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) | |
708 | *** runcommand id |
|
713 | *** runcommand id | |
709 | abort: there is no Mercurial repository here (.hg not found) |
|
714 | abort: there is no Mercurial repository here (.hg not found) | |
710 | [255] |
|
715 | [255] | |
711 |
|
716 | |||
712 | >>> from hgclient import check, readchannel, runcommand |
|
717 | >>> from hgclient import check, readchannel, runcommand | |
713 | >>> @check |
|
718 | >>> @check | |
714 | ... def startwithoutrepo(server): |
|
719 | ... def startwithoutrepo(server): | |
715 | ... readchannel(server) |
|
720 | ... readchannel(server) | |
716 | ... runcommand(server, [b'init', b'repo2']) |
|
721 | ... runcommand(server, [b'init', b'repo2']) | |
717 | ... runcommand(server, [b'id', b'-R', b'repo2']) |
|
722 | ... runcommand(server, [b'id', b'-R', b'repo2']) | |
718 | *** runcommand init repo2 |
|
723 | *** runcommand init repo2 | |
719 | *** runcommand id -R repo2 |
|
724 | *** runcommand id -R repo2 | |
720 | 000000000000 tip |
|
725 | 000000000000 tip | |
721 |
|
726 | |||
722 |
|
727 | |||
723 | don't fall back to cwd if invalid -R path is specified (issue4805): |
|
728 | don't fall back to cwd if invalid -R path is specified (issue4805): | |
724 |
|
729 | |||
725 | $ cd repo |
|
730 | $ cd repo | |
726 | $ hg serve --cmdserver pipe -R ../nonexistent |
|
731 | $ hg serve --cmdserver pipe -R ../nonexistent | |
727 | abort: repository ../nonexistent not found! |
|
732 | abort: repository ../nonexistent not found! | |
728 | [255] |
|
733 | [255] | |
729 | $ cd .. |
|
734 | $ cd .. | |
730 |
|
735 | |||
731 |
|
736 | |||
732 | structured message channel: |
|
737 | structured message channel: | |
733 |
|
738 | |||
734 | $ cat <<'EOF' >> repo2/.hg/hgrc |
|
739 | $ cat <<'EOF' >> repo2/.hg/hgrc | |
735 | > [ui] |
|
740 | > [ui] | |
736 | > # server --config should precede repository option |
|
741 | > # server --config should precede repository option | |
737 | > message-output = stdio |
|
742 | > message-output = stdio | |
738 | > EOF |
|
743 | > EOF | |
739 |
|
744 | |||
740 | >>> from hgclient import bprint, checkwith, readchannel, runcommand |
|
745 | >>> from hgclient import bprint, checkwith, readchannel, runcommand | |
741 | >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel', |
|
746 | >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel', | |
742 | ... b'--config', b'cmdserver.message-encodings=foo cbor']) |
|
747 | ... b'--config', b'cmdserver.message-encodings=foo cbor']) | |
743 | ... def verify(server): |
|
748 | ... def verify(server): | |
744 | ... _ch, data = readchannel(server) |
|
749 | ... _ch, data = readchannel(server) | |
745 | ... bprint(data) |
|
750 | ... bprint(data) | |
746 | ... runcommand(server, [b'-R', b'repo2', b'verify']) |
|
751 | ... runcommand(server, [b'-R', b'repo2', b'verify']) | |
747 | capabilities: getencoding runcommand |
|
752 | capabilities: getencoding runcommand | |
748 | encoding: ascii |
|
753 | encoding: ascii | |
749 | message-encoding: cbor |
|
754 | message-encoding: cbor | |
750 | pid: * (glob) |
|
755 | pid: * (glob) | |
751 | pgid: * (glob) (no-windows !) |
|
756 | pgid: * (glob) (no-windows !) | |
752 | *** runcommand -R repo2 verify |
|
757 | *** runcommand -R repo2 verify | |
753 | message: '\xa2DdataTchecking changesets\nDtypeFstatus' |
|
758 | message: '\xa2DdataTchecking changesets\nDtypeFstatus' | |
754 | message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@' |
|
759 | message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@' | |
755 | message: '\xa2DdataSchecking manifests\nDtypeFstatus' |
|
760 | message: '\xa2DdataSchecking manifests\nDtypeFstatus' | |
756 | message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@' |
|
761 | message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@' | |
757 | message: '\xa2DdataX0crosschecking files in changesets and manifests\nDtypeFstatus' |
|
762 | message: '\xa2DdataX0crosschecking files in changesets and manifests\nDtypeFstatus' | |
758 | message: '\xa6Ditem@Cpos\xf6EtopicMcrosscheckingEtotal\xf6DtypeHprogressDunit@' |
|
763 | message: '\xa6Ditem@Cpos\xf6EtopicMcrosscheckingEtotal\xf6DtypeHprogressDunit@' | |
759 | message: '\xa2DdataOchecking files\nDtypeFstatus' |
|
764 | message: '\xa2DdataOchecking files\nDtypeFstatus' | |
760 | message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@' |
|
765 | message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@' | |
761 | message: '\xa2DdataX/checked 0 changesets with 0 changes to 0 files\nDtypeFstatus' |
|
766 | message: '\xa2DdataX/checked 0 changesets with 0 changes to 0 files\nDtypeFstatus' | |
762 |
|
767 | |||
763 | >>> from hgclient import checkwith, readchannel, runcommand, stringio |
|
768 | >>> from hgclient import checkwith, readchannel, runcommand, stringio | |
764 | >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel', |
|
769 | >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel', | |
765 | ... b'--config', b'cmdserver.message-encodings=cbor', |
|
770 | ... b'--config', b'cmdserver.message-encodings=cbor', | |
766 | ... b'--config', b'extensions.dbgui=dbgui.py']) |
|
771 | ... b'--config', b'extensions.dbgui=dbgui.py']) | |
767 | ... def prompt(server): |
|
772 | ... def prompt(server): | |
768 | ... readchannel(server) |
|
773 | ... readchannel(server) | |
769 | ... interactive = [b'--config', b'ui.interactive=True'] |
|
774 | ... interactive = [b'--config', b'ui.interactive=True'] | |
770 | ... runcommand(server, [b'debuggetpass'] + interactive, |
|
775 | ... runcommand(server, [b'debuggetpass'] + interactive, | |
771 | ... input=stringio(b'1234\n')) |
|
776 | ... input=stringio(b'1234\n')) | |
772 | ... runcommand(server, [b'debugprompt'] + interactive, |
|
777 | ... runcommand(server, [b'debugprompt'] + interactive, | |
773 | ... input=stringio(b'5678\n')) |
|
778 | ... input=stringio(b'5678\n')) | |
774 | ... runcommand(server, [b'debugpromptchoice'] + interactive, |
|
779 | ... runcommand(server, [b'debugpromptchoice'] + interactive, | |
775 | ... input=stringio(b'n\n')) |
|
780 | ... input=stringio(b'n\n')) | |
776 | *** runcommand debuggetpass --config ui.interactive=True |
|
781 | *** runcommand debuggetpass --config ui.interactive=True | |
777 | message: '\xa3DdataJpassword: Hpassword\xf5DtypeFprompt' |
|
782 | message: '\xa3DdataJpassword: Hpassword\xf5DtypeFprompt' | |
778 | 1234 |
|
783 | 1234 | |
779 | *** runcommand debugprompt --config ui.interactive=True |
|
784 | *** runcommand debugprompt --config ui.interactive=True | |
780 | message: '\xa3DdataGprompt:GdefaultAyDtypeFprompt' |
|
785 | message: '\xa3DdataGprompt:GdefaultAyDtypeFprompt' | |
781 | 5678 |
|
786 | 5678 | |
782 | *** runcommand debugpromptchoice --config ui.interactive=True |
|
787 | *** runcommand debugpromptchoice --config ui.interactive=True | |
783 | message: '\xa4Gchoices\x82\x82AyCYes\x82AnBNoDdataTpromptchoice (y/n)? GdefaultAyDtypeFprompt' |
|
788 | message: '\xa4Gchoices\x82\x82AyCYes\x82AnBNoDdataTpromptchoice (y/n)? GdefaultAyDtypeFprompt' | |
784 | 1 |
|
789 | 1 | |
785 |
|
790 | |||
786 | bad message encoding: |
|
791 | bad message encoding: | |
787 |
|
792 | |||
788 | $ hg serve --cmdserver pipe --config ui.message-output=channel |
|
793 | $ hg serve --cmdserver pipe --config ui.message-output=channel | |
789 | abort: no supported message encodings: |
|
794 | abort: no supported message encodings: | |
790 | [255] |
|
795 | [255] | |
791 | $ hg serve --cmdserver pipe --config ui.message-output=channel \ |
|
796 | $ hg serve --cmdserver pipe --config ui.message-output=channel \ | |
792 | > --config cmdserver.message-encodings='foo bar' |
|
797 | > --config cmdserver.message-encodings='foo bar' | |
793 | abort: no supported message encodings: foo bar |
|
798 | abort: no supported message encodings: foo bar | |
794 | [255] |
|
799 | [255] | |
795 |
|
800 | |||
796 | unix domain socket: |
|
801 | unix domain socket: | |
797 |
|
802 | |||
798 | $ cd repo |
|
803 | $ cd repo | |
799 | $ hg update -q |
|
804 | $ hg update -q | |
800 |
|
805 | |||
801 | #if unix-socket unix-permissions |
|
806 | #if unix-socket unix-permissions | |
802 |
|
807 | |||
803 | >>> from hgclient import bprint, check, readchannel, runcommand, stringio, unixserver |
|
808 | >>> from hgclient import bprint, check, readchannel, runcommand, stringio, unixserver | |
804 | >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log') |
|
809 | >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log') | |
805 | >>> def hellomessage(conn): |
|
810 | >>> def hellomessage(conn): | |
806 | ... ch, data = readchannel(conn) |
|
811 | ... ch, data = readchannel(conn) | |
807 | ... bprint(b'%c, %r' % (ch, data)) |
|
812 | ... bprint(b'%c, %r' % (ch, data)) | |
808 | ... runcommand(conn, [b'id']) |
|
813 | ... runcommand(conn, [b'id']) | |
809 | >>> check(hellomessage, server.connect) |
|
814 | >>> check(hellomessage, server.connect) | |
810 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) |
|
815 | o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob) | |
811 | *** runcommand id |
|
816 | *** runcommand id | |
812 | eff892de26ec tip bm1/bm2/bm3 |
|
817 | eff892de26ec tip bm1/bm2/bm3 | |
813 | >>> def unknowncommand(conn): |
|
818 | >>> def unknowncommand(conn): | |
814 | ... readchannel(conn) |
|
819 | ... readchannel(conn) | |
815 | ... conn.stdin.write(b'unknowncommand\n') |
|
820 | ... conn.stdin.write(b'unknowncommand\n') | |
816 | >>> check(unknowncommand, server.connect) # error sent to server.log |
|
821 | >>> check(unknowncommand, server.connect) # error sent to server.log | |
817 | >>> def serverinput(conn): |
|
822 | >>> def serverinput(conn): | |
818 | ... readchannel(conn) |
|
823 | ... readchannel(conn) | |
819 | ... patch = b""" |
|
824 | ... patch = b""" | |
820 | ... # HG changeset patch |
|
825 | ... # HG changeset patch | |
821 | ... # User test |
|
826 | ... # User test | |
822 | ... # Date 0 0 |
|
827 | ... # Date 0 0 | |
823 | ... 2 |
|
828 | ... 2 | |
824 | ... |
|
829 | ... | |
825 | ... diff -r eff892de26ec -r 1ed24be7e7a0 a |
|
830 | ... diff -r eff892de26ec -r 1ed24be7e7a0 a | |
826 | ... --- a/a |
|
831 | ... --- a/a | |
827 | ... +++ b/a |
|
832 | ... +++ b/a | |
828 | ... @@ -1,1 +1,2 @@ |
|
833 | ... @@ -1,1 +1,2 @@ | |
829 | ... 1 |
|
834 | ... 1 | |
830 | ... +2 |
|
835 | ... +2 | |
831 | ... """ |
|
836 | ... """ | |
832 | ... runcommand(conn, [b'import', b'-'], input=stringio(patch)) |
|
837 | ... runcommand(conn, [b'import', b'-'], input=stringio(patch)) | |
833 | ... runcommand(conn, [b'log', b'-rtip', b'-q']) |
|
838 | ... runcommand(conn, [b'log', b'-rtip', b'-q']) | |
834 | >>> check(serverinput, server.connect) |
|
839 | >>> check(serverinput, server.connect) | |
835 | *** runcommand import - |
|
840 | *** runcommand import - | |
836 | applying patch from stdin |
|
841 | applying patch from stdin | |
837 | *** runcommand log -rtip -q |
|
842 | *** runcommand log -rtip -q | |
838 | 2:1ed24be7e7a0 |
|
843 | 2:1ed24be7e7a0 | |
839 | >>> server.shutdown() |
|
844 | >>> server.shutdown() | |
840 |
|
845 | |||
841 | $ cat .hg/server.log |
|
846 | $ cat .hg/server.log | |
842 | listening at .hg/server.sock |
|
847 | listening at .hg/server.sock | |
843 | abort: unknown command unknowncommand |
|
848 | abort: unknown command unknowncommand | |
844 | killed! |
|
849 | killed! | |
845 | $ rm .hg/server.log |
|
850 | $ rm .hg/server.log | |
846 |
|
851 | |||
847 | if server crashed before hello, traceback will be sent to 'e' channel as |
|
852 | if server crashed before hello, traceback will be sent to 'e' channel as | |
848 | last ditch: |
|
853 | last ditch: | |
849 |
|
854 | |||
850 | $ cat <<'EOF' > ../earlycrasher.py |
|
855 | $ cat <<'EOF' > ../earlycrasher.py | |
851 | > from mercurial import commandserver, extensions |
|
856 | > from mercurial import commandserver, extensions | |
852 | > def _serverequest(orig, ui, repo, conn, createcmdserver, prereposetups): |
|
857 | > def _serverequest(orig, ui, repo, conn, createcmdserver, prereposetups): | |
853 | > def createcmdserver(*args, **kwargs): |
|
858 | > def createcmdserver(*args, **kwargs): | |
854 | > raise Exception('crash') |
|
859 | > raise Exception('crash') | |
855 | > return orig(ui, repo, conn, createcmdserver, prereposetups) |
|
860 | > return orig(ui, repo, conn, createcmdserver, prereposetups) | |
856 | > def extsetup(ui): |
|
861 | > def extsetup(ui): | |
857 | > extensions.wrapfunction(commandserver, b'_serverequest', _serverequest) |
|
862 | > extensions.wrapfunction(commandserver, b'_serverequest', _serverequest) | |
858 | > EOF |
|
863 | > EOF | |
859 | $ cat <<EOF >> .hg/hgrc |
|
864 | $ cat <<EOF >> .hg/hgrc | |
860 | > [extensions] |
|
865 | > [extensions] | |
861 | > earlycrasher = ../earlycrasher.py |
|
866 | > earlycrasher = ../earlycrasher.py | |
862 | > EOF |
|
867 | > EOF | |
863 | >>> from hgclient import bprint, check, readchannel, unixserver |
|
868 | >>> from hgclient import bprint, check, readchannel, unixserver | |
864 | >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log') |
|
869 | >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log') | |
865 | >>> def earlycrash(conn): |
|
870 | >>> def earlycrash(conn): | |
866 | ... while True: |
|
871 | ... while True: | |
867 | ... try: |
|
872 | ... try: | |
868 | ... ch, data = readchannel(conn) |
|
873 | ... ch, data = readchannel(conn) | |
869 | ... for l in data.splitlines(True): |
|
874 | ... for l in data.splitlines(True): | |
870 | ... if not l.startswith(b' '): |
|
875 | ... if not l.startswith(b' '): | |
871 | ... bprint(b'%c, %r' % (ch, l)) |
|
876 | ... bprint(b'%c, %r' % (ch, l)) | |
872 | ... except EOFError: |
|
877 | ... except EOFError: | |
873 | ... break |
|
878 | ... break | |
874 | >>> check(earlycrash, server.connect) |
|
879 | >>> check(earlycrash, server.connect) | |
875 | e, 'Traceback (most recent call last):\n' |
|
880 | e, 'Traceback (most recent call last):\n' | |
876 | e, 'Exception: crash\n' |
|
881 | e, 'Exception: crash\n' | |
877 | >>> server.shutdown() |
|
882 | >>> server.shutdown() | |
878 |
|
883 | |||
879 | $ cat .hg/server.log | grep -v '^ ' |
|
884 | $ cat .hg/server.log | grep -v '^ ' | |
880 | listening at .hg/server.sock |
|
885 | listening at .hg/server.sock | |
881 | Traceback (most recent call last): |
|
886 | Traceback (most recent call last): | |
882 | Exception: crash |
|
887 | Exception: crash | |
883 | killed! |
|
888 | killed! | |
884 | #endif |
|
889 | #endif | |
885 | #if no-unix-socket |
|
890 | #if no-unix-socket | |
886 |
|
891 | |||
887 | $ hg serve --cmdserver unix -a .hg/server.sock |
|
892 | $ hg serve --cmdserver unix -a .hg/server.sock | |
888 | abort: unsupported platform |
|
893 | abort: unsupported platform | |
889 | [255] |
|
894 | [255] | |
890 |
|
895 | |||
891 | #endif |
|
896 | #endif | |
892 |
|
897 | |||
893 | $ cd .. |
|
898 | $ cd .. | |
894 |
|
899 | |||
895 | Test that accessing to invalid changelog cache is avoided at |
|
900 | Test that accessing to invalid changelog cache is avoided at | |
896 | subsequent operations even if repo object is reused even after failure |
|
901 | subsequent operations even if repo object is reused even after failure | |
897 | of transaction (see 0a7610758c42 also) |
|
902 | of transaction (see 0a7610758c42 also) | |
898 |
|
903 | |||
899 | "hg log" after failure of transaction is needed to detect invalid |
|
904 | "hg log" after failure of transaction is needed to detect invalid | |
900 | cache in repoview: this can't detect by "hg verify" only. |
|
905 | cache in repoview: this can't detect by "hg verify" only. | |
901 |
|
906 | |||
902 | Combination of "finalization" and "empty-ness of changelog" (2 x 2 = |
|
907 | Combination of "finalization" and "empty-ness of changelog" (2 x 2 = | |
903 | 4) are tested, because '00changelog.i' are differently changed in each |
|
908 | 4) are tested, because '00changelog.i' are differently changed in each | |
904 | cases. |
|
909 | cases. | |
905 |
|
910 | |||
906 | $ cat > $TESTTMP/failafterfinalize.py <<EOF |
|
911 | $ cat > $TESTTMP/failafterfinalize.py <<EOF | |
907 | > # extension to abort transaction after finalization forcibly |
|
912 | > # extension to abort transaction after finalization forcibly | |
908 | > from mercurial import commands, error, extensions, lock as lockmod |
|
913 | > from mercurial import commands, error, extensions, lock as lockmod | |
909 | > from mercurial import registrar |
|
914 | > from mercurial import registrar | |
910 | > cmdtable = {} |
|
915 | > cmdtable = {} | |
911 | > command = registrar.command(cmdtable) |
|
916 | > command = registrar.command(cmdtable) | |
912 | > configtable = {} |
|
917 | > configtable = {} | |
913 | > configitem = registrar.configitem(configtable) |
|
918 | > configitem = registrar.configitem(configtable) | |
914 | > configitem(b'failafterfinalize', b'fail', |
|
919 | > configitem(b'failafterfinalize', b'fail', | |
915 | > default=None, |
|
920 | > default=None, | |
916 | > ) |
|
921 | > ) | |
917 | > def fail(tr): |
|
922 | > def fail(tr): | |
918 | > raise error.Abort(b'fail after finalization') |
|
923 | > raise error.Abort(b'fail after finalization') | |
919 | > def reposetup(ui, repo): |
|
924 | > def reposetup(ui, repo): | |
920 | > class failrepo(repo.__class__): |
|
925 | > class failrepo(repo.__class__): | |
921 | > def commitctx(self, ctx, error=False, origctx=None): |
|
926 | > def commitctx(self, ctx, error=False, origctx=None): | |
922 | > if self.ui.configbool(b'failafterfinalize', b'fail'): |
|
927 | > if self.ui.configbool(b'failafterfinalize', b'fail'): | |
923 | > # 'sorted()' by ASCII code on category names causes |
|
928 | > # 'sorted()' by ASCII code on category names causes | |
924 | > # invoking 'fail' after finalization of changelog |
|
929 | > # invoking 'fail' after finalization of changelog | |
925 | > # using "'cl-%i' % id(self)" as category name |
|
930 | > # using "'cl-%i' % id(self)" as category name | |
926 | > self.currenttransaction().addfinalize(b'zzzzzzzz', fail) |
|
931 | > self.currenttransaction().addfinalize(b'zzzzzzzz', fail) | |
927 | > return super(failrepo, self).commitctx(ctx, error, origctx) |
|
932 | > return super(failrepo, self).commitctx(ctx, error, origctx) | |
928 | > repo.__class__ = failrepo |
|
933 | > repo.__class__ = failrepo | |
929 | > EOF |
|
934 | > EOF | |
930 |
|
935 | |||
931 | $ hg init repo3 |
|
936 | $ hg init repo3 | |
932 | $ cd repo3 |
|
937 | $ cd repo3 | |
933 |
|
938 | |||
934 | $ cat <<EOF >> $HGRCPATH |
|
939 | $ cat <<EOF >> $HGRCPATH | |
935 | > [ui] |
|
940 | > [ui] | |
936 | > logtemplate = {rev} {desc|firstline} ({files})\n |
|
941 | > logtemplate = {rev} {desc|firstline} ({files})\n | |
937 | > |
|
942 | > | |
938 | > [extensions] |
|
943 | > [extensions] | |
939 | > failafterfinalize = $TESTTMP/failafterfinalize.py |
|
944 | > failafterfinalize = $TESTTMP/failafterfinalize.py | |
940 | > EOF |
|
945 | > EOF | |
941 |
|
946 | |||
942 | - test failure with "empty changelog" |
|
947 | - test failure with "empty changelog" | |
943 |
|
948 | |||
944 | $ echo foo > foo |
|
949 | $ echo foo > foo | |
945 | $ hg add foo |
|
950 | $ hg add foo | |
946 |
|
951 | |||
947 | (failure before finalization) |
|
952 | (failure before finalization) | |
948 |
|
953 | |||
949 | >>> from hgclient import check, readchannel, runcommand |
|
954 | >>> from hgclient import check, readchannel, runcommand | |
950 | >>> @check |
|
955 | >>> @check | |
951 | ... def abort(server): |
|
956 | ... def abort(server): | |
952 | ... readchannel(server) |
|
957 | ... readchannel(server) | |
953 | ... runcommand(server, [b'commit', |
|
958 | ... runcommand(server, [b'commit', | |
954 | ... b'--config', b'hooks.pretxncommit=false', |
|
959 | ... b'--config', b'hooks.pretxncommit=false', | |
955 | ... b'-mfoo']) |
|
960 | ... b'-mfoo']) | |
956 | ... runcommand(server, [b'log']) |
|
961 | ... runcommand(server, [b'log']) | |
957 | ... runcommand(server, [b'verify', b'-q']) |
|
962 | ... runcommand(server, [b'verify', b'-q']) | |
958 | *** runcommand commit --config hooks.pretxncommit=false -mfoo |
|
963 | *** runcommand commit --config hooks.pretxncommit=false -mfoo | |
959 | transaction abort! |
|
964 | transaction abort! | |
960 | rollback completed |
|
965 | rollback completed | |
961 | abort: pretxncommit hook exited with status 1 |
|
966 | abort: pretxncommit hook exited with status 1 | |
962 | [255] |
|
967 | [255] | |
963 | *** runcommand log |
|
968 | *** runcommand log | |
964 | *** runcommand verify -q |
|
969 | *** runcommand verify -q | |
965 |
|
970 | |||
966 | (failure after finalization) |
|
971 | (failure after finalization) | |
967 |
|
972 | |||
968 | >>> from hgclient import check, readchannel, runcommand |
|
973 | >>> from hgclient import check, readchannel, runcommand | |
969 | >>> @check |
|
974 | >>> @check | |
970 | ... def abort(server): |
|
975 | ... def abort(server): | |
971 | ... readchannel(server) |
|
976 | ... readchannel(server) | |
972 | ... runcommand(server, [b'commit', |
|
977 | ... runcommand(server, [b'commit', | |
973 | ... b'--config', b'failafterfinalize.fail=true', |
|
978 | ... b'--config', b'failafterfinalize.fail=true', | |
974 | ... b'-mfoo']) |
|
979 | ... b'-mfoo']) | |
975 | ... runcommand(server, [b'log']) |
|
980 | ... runcommand(server, [b'log']) | |
976 | ... runcommand(server, [b'verify', b'-q']) |
|
981 | ... runcommand(server, [b'verify', b'-q']) | |
977 | *** runcommand commit --config failafterfinalize.fail=true -mfoo |
|
982 | *** runcommand commit --config failafterfinalize.fail=true -mfoo | |
978 | transaction abort! |
|
983 | transaction abort! | |
979 | rollback completed |
|
984 | rollback completed | |
980 | abort: fail after finalization |
|
985 | abort: fail after finalization | |
981 | [255] |
|
986 | [255] | |
982 | *** runcommand log |
|
987 | *** runcommand log | |
983 | *** runcommand verify -q |
|
988 | *** runcommand verify -q | |
984 |
|
989 | |||
985 | - test failure with "not-empty changelog" |
|
990 | - test failure with "not-empty changelog" | |
986 |
|
991 | |||
987 | $ echo bar > bar |
|
992 | $ echo bar > bar | |
988 | $ hg add bar |
|
993 | $ hg add bar | |
989 | $ hg commit -mbar bar |
|
994 | $ hg commit -mbar bar | |
990 |
|
995 | |||
991 | (failure before finalization) |
|
996 | (failure before finalization) | |
992 |
|
997 | |||
993 | >>> from hgclient import check, readchannel, runcommand |
|
998 | >>> from hgclient import check, readchannel, runcommand | |
994 | >>> @check |
|
999 | >>> @check | |
995 | ... def abort(server): |
|
1000 | ... def abort(server): | |
996 | ... readchannel(server) |
|
1001 | ... readchannel(server) | |
997 | ... runcommand(server, [b'commit', |
|
1002 | ... runcommand(server, [b'commit', | |
998 | ... b'--config', b'hooks.pretxncommit=false', |
|
1003 | ... b'--config', b'hooks.pretxncommit=false', | |
999 | ... b'-mfoo', b'foo']) |
|
1004 | ... b'-mfoo', b'foo']) | |
1000 | ... runcommand(server, [b'log']) |
|
1005 | ... runcommand(server, [b'log']) | |
1001 | ... runcommand(server, [b'verify', b'-q']) |
|
1006 | ... runcommand(server, [b'verify', b'-q']) | |
1002 | *** runcommand commit --config hooks.pretxncommit=false -mfoo foo |
|
1007 | *** runcommand commit --config hooks.pretxncommit=false -mfoo foo | |
1003 | transaction abort! |
|
1008 | transaction abort! | |
1004 | rollback completed |
|
1009 | rollback completed | |
1005 | abort: pretxncommit hook exited with status 1 |
|
1010 | abort: pretxncommit hook exited with status 1 | |
1006 | [255] |
|
1011 | [255] | |
1007 | *** runcommand log |
|
1012 | *** runcommand log | |
1008 | 0 bar (bar) |
|
1013 | 0 bar (bar) | |
1009 | *** runcommand verify -q |
|
1014 | *** runcommand verify -q | |
1010 |
|
1015 | |||
1011 | (failure after finalization) |
|
1016 | (failure after finalization) | |
1012 |
|
1017 | |||
1013 | >>> from hgclient import check, readchannel, runcommand |
|
1018 | >>> from hgclient import check, readchannel, runcommand | |
1014 | >>> @check |
|
1019 | >>> @check | |
1015 | ... def abort(server): |
|
1020 | ... def abort(server): | |
1016 | ... readchannel(server) |
|
1021 | ... readchannel(server) | |
1017 | ... runcommand(server, [b'commit', |
|
1022 | ... runcommand(server, [b'commit', | |
1018 | ... b'--config', b'failafterfinalize.fail=true', |
|
1023 | ... b'--config', b'failafterfinalize.fail=true', | |
1019 | ... b'-mfoo', b'foo']) |
|
1024 | ... b'-mfoo', b'foo']) | |
1020 | ... runcommand(server, [b'log']) |
|
1025 | ... runcommand(server, [b'log']) | |
1021 | ... runcommand(server, [b'verify', b'-q']) |
|
1026 | ... runcommand(server, [b'verify', b'-q']) | |
1022 | *** runcommand commit --config failafterfinalize.fail=true -mfoo foo |
|
1027 | *** runcommand commit --config failafterfinalize.fail=true -mfoo foo | |
1023 | transaction abort! |
|
1028 | transaction abort! | |
1024 | rollback completed |
|
1029 | rollback completed | |
1025 | abort: fail after finalization |
|
1030 | abort: fail after finalization | |
1026 | [255] |
|
1031 | [255] | |
1027 | *** runcommand log |
|
1032 | *** runcommand log | |
1028 | 0 bar (bar) |
|
1033 | 0 bar (bar) | |
1029 | *** runcommand verify -q |
|
1034 | *** runcommand verify -q | |
1030 |
|
1035 | |||
1031 | $ cd .. |
|
1036 | $ cd .. | |
1032 |
|
1037 | |||
1033 | Test symlink traversal over cached audited paths: |
|
1038 | Test symlink traversal over cached audited paths: | |
1034 | ------------------------------------------------- |
|
1039 | ------------------------------------------------- | |
1035 |
|
1040 | |||
1036 | #if symlink |
|
1041 | #if symlink | |
1037 |
|
1042 | |||
1038 | set up symlink hell |
|
1043 | set up symlink hell | |
1039 |
|
1044 | |||
1040 | $ mkdir merge-symlink-out |
|
1045 | $ mkdir merge-symlink-out | |
1041 | $ hg init merge-symlink |
|
1046 | $ hg init merge-symlink | |
1042 | $ cd merge-symlink |
|
1047 | $ cd merge-symlink | |
1043 | $ touch base |
|
1048 | $ touch base | |
1044 | $ hg commit -qAm base |
|
1049 | $ hg commit -qAm base | |
1045 | $ ln -s ../merge-symlink-out a |
|
1050 | $ ln -s ../merge-symlink-out a | |
1046 | $ hg commit -qAm 'symlink a -> ../merge-symlink-out' |
|
1051 | $ hg commit -qAm 'symlink a -> ../merge-symlink-out' | |
1047 | $ hg up -q 0 |
|
1052 | $ hg up -q 0 | |
1048 | $ mkdir a |
|
1053 | $ mkdir a | |
1049 | $ touch a/poisoned |
|
1054 | $ touch a/poisoned | |
1050 | $ hg commit -qAm 'file a/poisoned' |
|
1055 | $ hg commit -qAm 'file a/poisoned' | |
1051 | $ hg log -G -T '{rev}: {desc}\n' |
|
1056 | $ hg log -G -T '{rev}: {desc}\n' | |
1052 | @ 2: file a/poisoned |
|
1057 | @ 2: file a/poisoned | |
1053 | | |
|
1058 | | | |
1054 | | o 1: symlink a -> ../merge-symlink-out |
|
1059 | | o 1: symlink a -> ../merge-symlink-out | |
1055 | |/ |
|
1060 | |/ | |
1056 | o 0: base |
|
1061 | o 0: base | |
1057 |
|
1062 | |||
1058 |
|
1063 | |||
1059 | try trivial merge after update: cache of audited paths should be discarded, |
|
1064 | try trivial merge after update: cache of audited paths should be discarded, | |
1060 | and the merge should fail (issue5628) |
|
1065 | and the merge should fail (issue5628) | |
1061 |
|
1066 | |||
1062 | $ hg up -q null |
|
1067 | $ hg up -q null | |
1063 | >>> from hgclient import check, readchannel, runcommand |
|
1068 | >>> from hgclient import check, readchannel, runcommand | |
1064 | >>> @check |
|
1069 | >>> @check | |
1065 | ... def merge(server): |
|
1070 | ... def merge(server): | |
1066 | ... readchannel(server) |
|
1071 | ... readchannel(server) | |
1067 | ... # audit a/poisoned as a good path |
|
1072 | ... # audit a/poisoned as a good path | |
1068 | ... runcommand(server, [b'up', b'-qC', b'2']) |
|
1073 | ... runcommand(server, [b'up', b'-qC', b'2']) | |
1069 | ... runcommand(server, [b'up', b'-qC', b'1']) |
|
1074 | ... runcommand(server, [b'up', b'-qC', b'1']) | |
1070 | ... # here a is a symlink, so a/poisoned is bad |
|
1075 | ... # here a is a symlink, so a/poisoned is bad | |
1071 | ... runcommand(server, [b'merge', b'2']) |
|
1076 | ... runcommand(server, [b'merge', b'2']) | |
1072 | *** runcommand up -qC 2 |
|
1077 | *** runcommand up -qC 2 | |
1073 | *** runcommand up -qC 1 |
|
1078 | *** runcommand up -qC 1 | |
1074 | *** runcommand merge 2 |
|
1079 | *** runcommand merge 2 | |
1075 | abort: path 'a/poisoned' traverses symbolic link 'a' |
|
1080 | abort: path 'a/poisoned' traverses symbolic link 'a' | |
1076 | [255] |
|
1081 | [255] | |
1077 | $ ls ../merge-symlink-out |
|
1082 | $ ls ../merge-symlink-out | |
1078 |
|
1083 | |||
1079 | cache of repo.auditor should be discarded, so matcher would never traverse |
|
1084 | cache of repo.auditor should be discarded, so matcher would never traverse | |
1080 | symlinks: |
|
1085 | symlinks: | |
1081 |
|
1086 | |||
1082 | $ hg up -qC 0 |
|
1087 | $ hg up -qC 0 | |
1083 | $ touch ../merge-symlink-out/poisoned |
|
1088 | $ touch ../merge-symlink-out/poisoned | |
1084 | >>> from hgclient import check, readchannel, runcommand |
|
1089 | >>> from hgclient import check, readchannel, runcommand | |
1085 | >>> @check |
|
1090 | >>> @check | |
1086 | ... def files(server): |
|
1091 | ... def files(server): | |
1087 | ... readchannel(server) |
|
1092 | ... readchannel(server) | |
1088 | ... runcommand(server, [b'up', b'-qC', b'2']) |
|
1093 | ... runcommand(server, [b'up', b'-qC', b'2']) | |
1089 | ... # audit a/poisoned as a good path |
|
1094 | ... # audit a/poisoned as a good path | |
1090 | ... runcommand(server, [b'files', b'a/poisoned']) |
|
1095 | ... runcommand(server, [b'files', b'a/poisoned']) | |
1091 | ... runcommand(server, [b'up', b'-qC', b'0']) |
|
1096 | ... runcommand(server, [b'up', b'-qC', b'0']) | |
1092 | ... runcommand(server, [b'up', b'-qC', b'1']) |
|
1097 | ... runcommand(server, [b'up', b'-qC', b'1']) | |
1093 | ... # here 'a' is a symlink, so a/poisoned should be warned |
|
1098 | ... # here 'a' is a symlink, so a/poisoned should be warned | |
1094 | ... runcommand(server, [b'files', b'a/poisoned']) |
|
1099 | ... runcommand(server, [b'files', b'a/poisoned']) | |
1095 | *** runcommand up -qC 2 |
|
1100 | *** runcommand up -qC 2 | |
1096 | *** runcommand files a/poisoned |
|
1101 | *** runcommand files a/poisoned | |
1097 | a/poisoned |
|
1102 | a/poisoned | |
1098 | *** runcommand up -qC 0 |
|
1103 | *** runcommand up -qC 0 | |
1099 | *** runcommand up -qC 1 |
|
1104 | *** runcommand up -qC 1 | |
1100 | *** runcommand files a/poisoned |
|
1105 | *** runcommand files a/poisoned | |
1101 | abort: path 'a/poisoned' traverses symbolic link 'a' |
|
1106 | abort: path 'a/poisoned' traverses symbolic link 'a' | |
1102 | [255] |
|
1107 | [255] | |
1103 |
|
1108 | |||
1104 | $ cd .. |
|
1109 | $ cd .. | |
1105 |
|
1110 | |||
1106 | #endif |
|
1111 | #endif |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
General Comments 0
You need to be logged in to leave comments.
Login now