##// END OF EJS Templates
absorb: add a pycompat.bytestr() to fix --edit-lines functionality on Python 3...
Augie Fackler -
r41296:c146651a default
parent child Browse files
Show More
@@ -1,670 +1,671
1 1 test-abort-checkin.t
2 test-absorb-edit-lines.t
2 3 test-absorb-filefixupstate.py
3 4 test-absorb-phase.t
4 5 test-absorb-rename.t
5 6 test-absorb-strip.t
6 7 test-absorb.t
7 8 test-add.t
8 9 test-addremove-similar.t
9 10 test-addremove.t
10 11 test-alias.t
11 12 test-amend-subrepo.t
12 13 test-amend.t
13 14 test-ancestor.py
14 15 test-annotate.py
15 16 test-annotate.t
16 17 test-archive-symlinks.t
17 18 test-archive.t
18 19 test-atomictempfile.py
19 20 test-audit-path.t
20 21 test-audit-subrepo.t
21 22 test-automv.t
22 23 test-backout.t
23 24 test-backwards-remove.t
24 25 test-bad-extension.t
25 26 test-bad-pull.t
26 27 test-basic.t
27 28 test-bdiff.py
28 29 test-bheads.t
29 30 test-bisect.t
30 31 test-bisect2.t
31 32 test-bisect3.t
32 33 test-blackbox.t
33 34 test-bookflow.t
34 35 test-bookmarks-current.t
35 36 test-bookmarks-merge.t
36 37 test-bookmarks-pushpull.t
37 38 test-bookmarks-rebase.t
38 39 test-bookmarks-strip.t
39 40 test-bookmarks.t
40 41 test-branch-change.t
41 42 test-branch-option.t
42 43 test-branch-tag-confict.t
43 44 test-branches.t
44 45 test-bundle-phases.t
45 46 test-bundle-r.t
46 47 test-bundle-type.t
47 48 test-bundle-vs-outgoing.t
48 49 test-bundle.t
49 50 test-bundle2-exchange.t
50 51 test-bundle2-format.t
51 52 test-bundle2-multiple-changegroups.t
52 53 test-bundle2-pushback.t
53 54 test-bundle2-remote-changegroup.t
54 55 test-cache-abuse.t
55 56 test-cappedreader.py
56 57 test-casecollision.t
57 58 test-cat.t
58 59 test-cbor.py
59 60 test-censor.t
60 61 test-changelog-exec.t
61 62 test-check-code.t
62 63 test-check-commit.t
63 64 test-check-config.py
64 65 test-check-config.t
65 66 test-check-execute.t
66 67 test-check-help.t
67 68 test-check-interfaces.py
68 69 test-check-module-imports.t
69 70 test-check-py3-compat.t
70 71 test-check-pyflakes.t
71 72 test-check-pylint.t
72 73 test-check-shbang.t
73 74 test-children.t
74 75 test-churn.t
75 76 test-clone-cgi.t
76 77 test-clone-pull-corruption.t
77 78 test-clone-r.t
78 79 test-clone-uncompressed.t
79 80 test-clone-update-order.t
80 81 test-clone.t
81 82 test-clonebundles.t
82 83 test-close-head.t
83 84 test-commandserver.t
84 85 test-commit-amend.t
85 86 test-commit-interactive.t
86 87 test-commit-multiple.t
87 88 test-commit-unresolved.t
88 89 test-commit.t
89 90 test-committer.t
90 91 test-completion.t
91 92 test-config-env.py
92 93 test-config.t
93 94 test-conflict.t
94 95 test-confused-revert.t
95 96 test-context-metadata.t
96 97 test-context.py
97 98 test-contrib-check-code.t
98 99 test-contrib-check-commit.t
99 100 test-contrib-dumprevlog.t
100 101 test-contrib-perf.t
101 102 test-contrib-relnotes.t
102 103 test-contrib-testparseutil.t
103 104 test-contrib.t
104 105 test-convert-authormap.t
105 106 test-convert-clonebranches.t
106 107 test-convert-cvs-branch.t
107 108 test-convert-cvs-detectmerge.t
108 109 test-convert-cvs-synthetic.t
109 110 test-convert-cvs.t
110 111 test-convert-cvsnt-mergepoints.t
111 112 test-convert-datesort.t
112 113 test-convert-filemap.t
113 114 test-convert-hg-sink.t
114 115 test-convert-hg-source.t
115 116 test-convert-hg-startrev.t
116 117 test-convert-splicemap.t
117 118 test-convert-tagsbranch-topology.t
118 119 test-copy-move-merge.t
119 120 test-copy.t
120 121 test-copytrace-heuristics.t
121 122 test-custom-filters.t
122 123 test-debugbuilddag.t
123 124 test-debugbundle.t
124 125 test-debugcommands.t
125 126 test-debugextensions.t
126 127 test-debugindexdot.t
127 128 test-debugrename.t
128 129 test-default-push.t
129 130 test-diff-antipatience.t
130 131 test-diff-binary-file.t
131 132 test-diff-change.t
132 133 test-diff-color.t
133 134 test-diff-copy-depth.t
134 135 test-diff-hashes.t
135 136 test-diff-ignore-whitespace.t
136 137 test-diff-indent-heuristic.t
137 138 test-diff-issue2761.t
138 139 test-diff-newlines.t
139 140 test-diff-reverse.t
140 141 test-diff-subdir.t
141 142 test-diff-unified.t
142 143 test-diff-upgrade.t
143 144 test-diffdir.t
144 145 test-diffstat.t
145 146 test-directaccess.t
146 147 test-dirstate-backup.t
147 148 test-dirstate-nonnormalset.t
148 149 test-dirstate-race.t
149 150 test-dirstate.t
150 151 test-dispatch.py
151 152 test-doctest.py
152 153 test-double-merge.t
153 154 test-drawdag.t
154 155 test-duplicateoptions.py
155 156 test-editor-filename.t
156 157 test-empty-dir.t
157 158 test-empty-file.t
158 159 test-empty-group.t
159 160 test-empty.t
160 161 test-encode.t
161 162 test-encoding-func.py
162 163 test-encoding-textwrap.t
163 164 test-encoding.t
164 165 test-eol-add.t
165 166 test-eol-clone.t
166 167 test-eol-hook.t
167 168 test-eol-patch.t
168 169 test-eol-tag.t
169 170 test-eol-update.t
170 171 test-eol.t
171 172 test-eolfilename.t
172 173 test-excessive-merge.t
173 174 test-exchange-obsmarkers-case-A1.t
174 175 test-exchange-obsmarkers-case-A2.t
175 176 test-exchange-obsmarkers-case-A3.t
176 177 test-exchange-obsmarkers-case-A4.t
177 178 test-exchange-obsmarkers-case-A5.t
178 179 test-exchange-obsmarkers-case-A6.t
179 180 test-exchange-obsmarkers-case-A7.t
180 181 test-exchange-obsmarkers-case-B1.t
181 182 test-exchange-obsmarkers-case-B2.t
182 183 test-exchange-obsmarkers-case-B3.t
183 184 test-exchange-obsmarkers-case-B4.t
184 185 test-exchange-obsmarkers-case-B5.t
185 186 test-exchange-obsmarkers-case-B6.t
186 187 test-exchange-obsmarkers-case-B7.t
187 188 test-exchange-obsmarkers-case-C1.t
188 189 test-exchange-obsmarkers-case-C2.t
189 190 test-exchange-obsmarkers-case-C3.t
190 191 test-exchange-obsmarkers-case-C4.t
191 192 test-exchange-obsmarkers-case-D1.t
192 193 test-exchange-obsmarkers-case-D2.t
193 194 test-exchange-obsmarkers-case-D3.t
194 195 test-exchange-obsmarkers-case-D4.t
195 196 test-execute-bit.t
196 197 test-export.t
197 198 test-extdata.t
198 199 test-extdiff.t
199 200 test-extension-timing.t
200 201 test-extensions-afterloaded.t
201 202 test-extensions-wrapfunction.py
202 203 test-extra-filelog-entry.t
203 204 test-fastannotate-revmap.py
204 205 test-fetch.t
205 206 test-filebranch.t
206 207 test-filecache.py
207 208 test-filelog.py
208 209 test-fileset-generated.t
209 210 test-fileset.t
210 211 test-fix-topology.t
211 212 test-fix.t
212 213 test-flags.t
213 214 test-fncache.t
214 215 test-generaldelta.t
215 216 test-getbundle.t
216 217 test-git-export.t
217 218 test-globalopts.t
218 219 test-glog-beautifygraph.t
219 220 test-glog-topological.t
220 221 test-glog.t
221 222 test-gpg.t
222 223 test-graft.t
223 224 test-grep.t
224 225 test-hardlinks.t
225 226 test-help-hide.t
226 227 test-help.t
227 228 test-hg-parseurl.py
228 229 test-hghave.t
229 230 test-hgignore.t
230 231 test-hgk.t
231 232 test-hgrc.t
232 233 test-hgweb-annotate-whitespace.t
233 234 test-hgweb-bundle.t
234 235 test-hgweb-csp.t
235 236 test-hgweb-descend-empties.t
236 237 test-hgweb-diffs.t
237 238 test-hgweb-empty.t
238 239 test-hgweb-filelog.t
239 240 test-hgweb-non-interactive.t
240 241 test-hgweb-raw.t
241 242 test-hgweb-removed.t
242 243 test-hgweb.t
243 244 test-hgwebdir-paths.py
244 245 test-hgwebdirsym.t
245 246 test-histedit-arguments.t
246 247 test-histedit-base.t
247 248 test-histedit-bookmark-motion.t
248 249 test-histedit-commute.t
249 250 test-histedit-drop.t
250 251 test-histedit-edit.t
251 252 test-histedit-fold-non-commute.t
252 253 test-histedit-fold.t
253 254 test-histedit-no-backup.t
254 255 test-histedit-no-change.t
255 256 test-histedit-non-commute-abort.t
256 257 test-histedit-non-commute.t
257 258 test-histedit-obsolete.t
258 259 test-histedit-outgoing.t
259 260 test-histedit-templates.t
260 261 test-http-branchmap.t
261 262 test-http-bundle1.t
262 263 test-http-clone-r.t
263 264 test-http-permissions.t
264 265 test-http.t
265 266 test-hybridencode.py
266 267 test-i18n.t
267 268 test-identify.t
268 269 test-impexp-branch.t
269 270 test-import-bypass.t
270 271 test-import-context.t
271 272 test-import-eol.t
272 273 test-import-merge.t
273 274 test-import-unknown.t
274 275 test-import.t
275 276 test-imports-checker.t
276 277 test-incoming-outgoing.t
277 278 test-infinitepush-bundlestore.t
278 279 test-infinitepush-ci.t
279 280 test-infinitepush.t
280 281 test-inherit-mode.t
281 282 test-init.t
282 283 test-issue1089.t
283 284 test-issue1102.t
284 285 test-issue1175.t
285 286 test-issue1306.t
286 287 test-issue1438.t
287 288 test-issue1502.t
288 289 test-issue1802.t
289 290 test-issue1877.t
290 291 test-issue1993.t
291 292 test-issue2137.t
292 293 test-issue3084.t
293 294 test-issue4074.t
294 295 test-issue522.t
295 296 test-issue586.t
296 297 test-issue5979.t
297 298 test-issue612.t
298 299 test-issue619.t
299 300 test-issue660.t
300 301 test-issue672.t
301 302 test-issue842.t
302 303 test-journal-exists.t
303 304 test-journal-share.t
304 305 test-journal.t
305 306 test-keyword.t
306 307 test-known.t
307 308 test-largefiles-cache.t
308 309 test-largefiles-misc.t
309 310 test-largefiles-small-disk.t
310 311 test-largefiles-update.t
311 312 test-largefiles-wireproto.t
312 313 test-largefiles.t
313 314 test-lfconvert.t
314 315 test-lfs-bundle.t
315 316 test-lfs-largefiles.t
316 317 test-lfs-pointer.py
317 318 test-lfs.t
318 319 test-linelog.py
319 320 test-linerange.py
320 321 test-locate.t
321 322 test-lock-badness.t
322 323 test-log-exthook.t
323 324 test-log-linerange.t
324 325 test-log.t
325 326 test-logexchange.t
326 327 test-logtoprocess.t
327 328 test-lrucachedict.py
328 329 test-mactext.t
329 330 test-mailmap.t
330 331 test-manifest-merging.t
331 332 test-manifest.py
332 333 test-manifest.t
333 334 test-match.py
334 335 test-mdiff.py
335 336 test-merge-changedelete.t
336 337 test-merge-closedheads.t
337 338 test-merge-commit.t
338 339 test-merge-criss-cross.t
339 340 test-merge-default.t
340 341 test-merge-force.t
341 342 test-merge-halt.t
342 343 test-merge-internal-tools-pattern.t
343 344 test-merge-local.t
344 345 test-merge-no-file-change.t
345 346 test-merge-remove.t
346 347 test-merge-revert.t
347 348 test-merge-revert2.t
348 349 test-merge-subrepos.t
349 350 test-merge-symlinks.t
350 351 test-merge-tools.t
351 352 test-merge-types.t
352 353 test-merge1.t
353 354 test-merge10.t
354 355 test-merge2.t
355 356 test-merge4.t
356 357 test-merge5.t
357 358 test-merge6.t
358 359 test-merge7.t
359 360 test-merge8.t
360 361 test-merge9.t
361 362 test-minifileset.py
362 363 test-minirst.py
363 364 test-mq-git.t
364 365 test-mq-guards.t
365 366 test-mq-header-date.t
366 367 test-mq-header-from.t
367 368 test-mq-merge.t
368 369 test-mq-pull-from-bundle.t
369 370 test-mq-qclone-http.t
370 371 test-mq-qdelete.t
371 372 test-mq-qdiff.t
372 373 test-mq-qfold.t
373 374 test-mq-qgoto.t
374 375 test-mq-qimport-fail-cleanup.t
375 376 test-mq-qnew.t
376 377 test-mq-qpush-exact.t
377 378 test-mq-qpush-fail.t
378 379 test-mq-qqueue.t
379 380 test-mq-qrefresh-interactive.t
380 381 test-mq-qrefresh-replace-log-message.t
381 382 test-mq-qrefresh.t
382 383 test-mq-qrename.t
383 384 test-mq-qsave.t
384 385 test-mq-safety.t
385 386 test-mq-subrepo.t
386 387 test-mq-symlinks.t
387 388 test-mq.t
388 389 test-mv-cp-st-diff.t
389 390 test-narrow-acl.t
390 391 test-narrow-archive.t
391 392 test-narrow-clone-no-ellipsis.t
392 393 test-narrow-clone-non-narrow-server.t
393 394 test-narrow-clone-nonlinear.t
394 395 test-narrow-clone-stream.t
395 396 test-narrow-clone.t
396 397 test-narrow-commit.t
397 398 test-narrow-copies.t
398 399 test-narrow-debugcommands.t
399 400 test-narrow-debugrebuilddirstate.t
400 401 test-narrow-exchange-merges.t
401 402 test-narrow-exchange.t
402 403 test-narrow-expanddirstate.t
403 404 test-narrow-merge.t
404 405 test-narrow-patch.t
405 406 test-narrow-patterns.t
406 407 test-narrow-pull.t
407 408 test-narrow-rebase.t
408 409 test-narrow-shallow-merges.t
409 410 test-narrow-shallow.t
410 411 test-narrow-share.t
411 412 test-narrow-sparse.t
412 413 test-narrow-strip.t
413 414 test-narrow-trackedcmd.t
414 415 test-narrow-update.t
415 416 test-narrow-widen-no-ellipsis.t
416 417 test-narrow-widen.t
417 418 test-narrow.t
418 419 test-nested-repo.t
419 420 test-newbranch.t
420 421 test-newercgi.t
421 422 test-nointerrupt.t
422 423 test-obshistory.t
423 424 test-obsmarker-template.t
424 425 test-obsmarkers-effectflag.t
425 426 test-obsolete-bounds-checking.t
426 427 test-obsolete-bundle-strip.t
427 428 test-obsolete-changeset-exchange.t
428 429 test-obsolete-checkheads.t
429 430 test-obsolete-distributed.t
430 431 test-obsolete-divergent.t
431 432 test-obsolete-tag-cache.t
432 433 test-obsolete.t
433 434 test-origbackup-conflict.t
434 435 test-pager-legacy.t
435 436 test-pager.t
436 437 test-parents.t
437 438 test-parse-date.t
438 439 test-parseindex2.py
439 440 test-patch-offset.t
440 441 test-patch.t
441 442 test-patchbomb-bookmark.t
442 443 test-patchbomb-tls.t
443 444 test-patchbomb.t
444 445 test-pathconflicts-basic.t
445 446 test-pathconflicts-merge.t
446 447 test-pathconflicts-update.t
447 448 test-pathencode.py
448 449 test-pending.t
449 450 test-permissions.t
450 451 test-phases-exchange.t
451 452 test-phases.t
452 453 test-profile.t
453 454 test-progress.t
454 455 test-propertycache.py
455 456 test-pull-branch.t
456 457 test-pull-http.t
457 458 test-pull-permission.t
458 459 test-pull-pull-corruption.t
459 460 test-pull-r.t
460 461 test-pull-update.t
461 462 test-pull.t
462 463 test-purge.t
463 464 test-push-cgi.t
464 465 test-push-checkheads-partial-C1.t
465 466 test-push-checkheads-partial-C2.t
466 467 test-push-checkheads-partial-C3.t
467 468 test-push-checkheads-partial-C4.t
468 469 test-push-checkheads-pruned-B1.t
469 470 test-push-checkheads-pruned-B2.t
470 471 test-push-checkheads-pruned-B3.t
471 472 test-push-checkheads-pruned-B4.t
472 473 test-push-checkheads-pruned-B5.t
473 474 test-push-checkheads-pruned-B6.t
474 475 test-push-checkheads-pruned-B7.t
475 476 test-push-checkheads-pruned-B8.t
476 477 test-push-checkheads-superceed-A1.t
477 478 test-push-checkheads-superceed-A2.t
478 479 test-push-checkheads-superceed-A3.t
479 480 test-push-checkheads-superceed-A4.t
480 481 test-push-checkheads-superceed-A5.t
481 482 test-push-checkheads-superceed-A6.t
482 483 test-push-checkheads-superceed-A7.t
483 484 test-push-checkheads-superceed-A8.t
484 485 test-push-checkheads-unpushed-D1.t
485 486 test-push-checkheads-unpushed-D2.t
486 487 test-push-checkheads-unpushed-D3.t
487 488 test-push-checkheads-unpushed-D4.t
488 489 test-push-checkheads-unpushed-D5.t
489 490 test-push-checkheads-unpushed-D6.t
490 491 test-push-checkheads-unpushed-D7.t
491 492 test-push-http.t
492 493 test-push-race.t
493 494 test-push-warn.t
494 495 test-push.t
495 496 test-pushvars.t
496 497 test-qrecord.t
497 498 test-rebase-abort.t
498 499 test-rebase-backup.t
499 500 test-rebase-base-flag.t
500 501 test-rebase-bookmarks.t
501 502 test-rebase-brute-force.t
502 503 test-rebase-cache.t
503 504 test-rebase-check-restore.t
504 505 test-rebase-collapse.t
505 506 test-rebase-conflicts.t
506 507 test-rebase-dest.t
507 508 test-rebase-detach.t
508 509 test-rebase-emptycommit.t
509 510 test-rebase-inmemory.t
510 511 test-rebase-interruptions.t
511 512 test-rebase-issue-noparam-single-rev.t
512 513 test-rebase-legacy.t
513 514 test-rebase-mq-skip.t
514 515 test-rebase-mq.t
515 516 test-rebase-named-branches.t
516 517 test-rebase-newancestor.t
517 518 test-rebase-obsolete.t
518 519 test-rebase-parameters.t
519 520 test-rebase-partial.t
520 521 test-rebase-pull.t
521 522 test-rebase-rename.t
522 523 test-rebase-scenario-global.t
523 524 test-rebase-templates.t
524 525 test-rebase-transaction.t
525 526 test-rebuildstate.t
526 527 test-record.t
527 528 test-releasenotes-formatting.t
528 529 test-releasenotes-merging.t
529 530 test-releasenotes-parsing.t
530 531 test-relink.t
531 532 test-remotefilelog-bad-configs.t
532 533 test-remotefilelog-blame.t
533 534 test-remotefilelog-clone-tree.t
534 535 test-remotefilelog-clone.t
535 536 test-remotefilelog-keepset.t
536 537 test-remotefilelog-partial-shallow.t
537 538 test-remotefilelog-prefetch.t
538 539 test-remotefilelog-pull-noshallow.t
539 540 test-remotefilelog-share.t
540 541 test-remotefilelog-tags.t
541 542 test-remove.t
542 543 test-removeemptydirs.t
543 544 test-rename-after-merge.t
544 545 test-rename-dir-merge.t
545 546 test-rename-merge1.t
546 547 test-rename-merge2.t
547 548 test-rename.t
548 549 test-repair-strip.t
549 550 test-repo-compengines.t
550 551 test-requires.t
551 552 test-resolve.t
552 553 test-revert-flags.t
553 554 test-revert-interactive.t
554 555 test-revert-unknown.t
555 556 test-revert.t
556 557 test-revisions.t
557 558 test-revlog-ancestry.py
558 559 test-revlog-group-emptyiter.t
559 560 test-revlog-mmapindex.t
560 561 test-revlog-packentry.t
561 562 test-revlog-raw.py
562 563 test-revlog-v2.t
563 564 test-revlog.t
564 565 test-revset-dirstate-parents.t
565 566 test-revset-legacy-lookup.t
566 567 test-revset-outgoing.t
567 568 test-rollback.t
568 569 test-run-tests.py
569 570 test-run-tests.t
570 571 test-rust-ancestor.py
571 572 test-schemes.t
572 573 test-serve.t
573 574 test-setdiscovery.t
574 575 test-share.t
575 576 test-shelve.t
576 577 test-shelve2.t
577 578 test-show-stack.t
578 579 test-show-work.t
579 580 test-show.t
580 581 test-simple-update.t
581 582 test-simplekeyvaluefile.py
582 583 test-simplemerge.py
583 584 test-single-head.t
584 585 test-sparse-clear.t
585 586 test-sparse-clone.t
586 587 test-sparse-import.t
587 588 test-sparse-merges.t
588 589 test-sparse-profiles.t
589 590 test-sparse-requirement.t
590 591 test-sparse-verbose-json.t
591 592 test-sparse.t
592 593 test-split.t
593 594 test-ssh-bundle1.t
594 595 test-ssh-clone-r.t
595 596 test-ssh-proto-unbundle.t
596 597 test-ssh-proto.t
597 598 test-ssh-repoerror.t
598 599 test-ssh.t
599 600 test-sshserver.py
600 601 test-stack.t
601 602 test-status-color.t
602 603 test-status-inprocess.py
603 604 test-status-rev.t
604 605 test-status-terse.t
605 606 test-status.t
606 607 test-storage.py
607 608 test-stream-bundle-v2.t
608 609 test-strict.t
609 610 test-strip-cross.t
610 611 test-strip.t
611 612 test-subrepo-deep-nested-change.t
612 613 test-subrepo-missing.t
613 614 test-subrepo-paths.t
614 615 test-subrepo-recursion.t
615 616 test-subrepo-relative-path.t
616 617 test-subrepo.t
617 618 test-symlink-os-yes-fs-no.py
618 619 test-symlink-placeholder.t
619 620 test-symlinks.t
620 621 test-tag.t
621 622 test-tags.t
622 623 test-template-basic.t
623 624 test-template-functions.t
624 625 test-template-keywords.t
625 626 test-template-map.t
626 627 test-tools.t
627 628 test-transplant.t
628 629 test-treemanifest.t
629 630 test-ui-color.py
630 631 test-ui-config.py
631 632 test-ui-verbosity.py
632 633 test-unamend.t
633 634 test-unbundlehash.t
634 635 test-uncommit.t
635 636 test-unified-test.t
636 637 test-unionrepo.t
637 638 test-unrelated-pull.t
638 639 test-up-local-change.t
639 640 test-update-branches.t
640 641 test-update-dest.t
641 642 test-update-issue1456.t
642 643 test-update-names.t
643 644 test-update-reverse.t
644 645 test-upgrade-repo.t
645 646 test-url-download.t
646 647 test-url-rev.t
647 648 test-url.py
648 649 test-username-newline.t
649 650 test-util.py
650 651 test-verify.t
651 652 test-walk.t
652 653 test-walkrepo.py
653 654 test-websub.t
654 655 test-win32text.t
655 656 test-wireproto-clientreactor.py
656 657 test-wireproto-command-branchmap.t
657 658 test-wireproto-command-changesetdata.t
658 659 test-wireproto-command-filedata.t
659 660 test-wireproto-command-filesdata.t
660 661 test-wireproto-command-heads.t
661 662 test-wireproto-command-listkeys.t
662 663 test-wireproto-command-lookup.t
663 664 test-wireproto-command-manifestdata.t
664 665 test-wireproto-command-pushkey.t
665 666 test-wireproto-command-rawstorefiledata.t
666 667 test-wireproto-framing.py
667 668 test-wireproto-serverreactor.py
668 669 test-wireproto.py
669 670 test-wsgirequest.py
670 671 test-xdg.t
@@ -1,1027 +1,1028
1 1 # absorb.py
2 2 #
3 3 # Copyright 2016 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """apply working directory changes to changesets (EXPERIMENTAL)
9 9
10 10 The absorb extension provides a command to use annotate information to
11 11 amend modified chunks into the corresponding non-public changesets.
12 12
13 13 ::
14 14
15 15 [absorb]
16 16 # only check 50 recent non-public changesets at most
17 17 max-stack-size = 50
18 18 # whether to add noise to new commits to avoid obsolescence cycle
19 19 add-noise = 1
20 20 # make `amend --correlated` a shortcut to the main command
21 21 amend-flag = correlated
22 22
23 23 [color]
24 24 absorb.description = yellow
25 25 absorb.node = blue bold
26 26 absorb.path = bold
27 27 """
28 28
29 29 # TODO:
30 30 # * Rename config items to [commands] namespace
31 31 # * Converge getdraftstack() with other code in core
32 32 # * move many attributes on fixupstate to be private
33 33
34 34 from __future__ import absolute_import
35 35
36 36 import collections
37 37
38 38 from mercurial.i18n import _
39 39 from mercurial import (
40 40 cmdutil,
41 41 commands,
42 42 context,
43 43 crecord,
44 44 error,
45 45 linelog,
46 46 mdiff,
47 47 node,
48 48 obsolete,
49 49 patch,
50 50 phases,
51 51 pycompat,
52 52 registrar,
53 53 repair,
54 54 scmutil,
55 55 util,
56 56 )
57 57 from mercurial.utils import (
58 58 stringutil,
59 59 )
60 60
61 61 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
62 62 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
63 63 # be specifying the version(s) of Mercurial they are tested with, or
64 64 # leave the attribute unspecified.
65 65 testedwith = 'ships-with-hg-core'
66 66
67 67 cmdtable = {}
68 68 command = registrar.command(cmdtable)
69 69
70 70 configtable = {}
71 71 configitem = registrar.configitem(configtable)
72 72
73 73 configitem('absorb', 'add-noise', default=True)
74 74 configitem('absorb', 'amend-flag', default=None)
75 75 configitem('absorb', 'max-stack-size', default=50)
76 76
77 77 colortable = {
78 78 'absorb.description': 'yellow',
79 79 'absorb.node': 'blue bold',
80 80 'absorb.path': 'bold',
81 81 }
82 82
83 83 defaultdict = collections.defaultdict
84 84
85 85 class nullui(object):
86 86 """blank ui object doing nothing"""
87 87 debugflag = False
88 88 verbose = False
89 89 quiet = True
90 90
91 91 def __getitem__(name):
92 92 def nullfunc(*args, **kwds):
93 93 return
94 94 return nullfunc
95 95
96 96 class emptyfilecontext(object):
97 97 """minimal filecontext representing an empty file"""
98 98 def data(self):
99 99 return ''
100 100
101 101 def node(self):
102 102 return node.nullid
103 103
104 104 def uniq(lst):
105 105 """list -> list. remove duplicated items without changing the order"""
106 106 seen = set()
107 107 result = []
108 108 for x in lst:
109 109 if x not in seen:
110 110 seen.add(x)
111 111 result.append(x)
112 112 return result
113 113
114 114 def getdraftstack(headctx, limit=None):
115 115 """(ctx, int?) -> [ctx]. get a linear stack of non-public changesets.
116 116
117 117 changesets are sorted in topo order, oldest first.
118 118 return at most limit items, if limit is a positive number.
119 119
120 120 merges are considered as non-draft as well. i.e. every commit
121 121 returned has and only has 1 parent.
122 122 """
123 123 ctx = headctx
124 124 result = []
125 125 while ctx.phase() != phases.public:
126 126 if limit and len(result) >= limit:
127 127 break
128 128 parents = ctx.parents()
129 129 if len(parents) != 1:
130 130 break
131 131 result.append(ctx)
132 132 ctx = parents[0]
133 133 result.reverse()
134 134 return result
135 135
136 136 def getfilestack(stack, path, seenfctxs=None):
137 137 """([ctx], str, set) -> [fctx], {ctx: fctx}
138 138
139 139 stack is a list of contexts, from old to new. usually they are what
140 140 "getdraftstack" returns.
141 141
142 142 follows renames, but not copies.
143 143
144 144 seenfctxs is a set of filecontexts that will be considered "immutable".
145 145 they are usually what this function returned in earlier calls, useful
146 146 to avoid issues that a file was "moved" to multiple places and was then
147 147 modified differently, like: "a" was copied to "b", "a" was also copied to
148 148 "c" and then "a" was deleted, then both "b" and "c" were "moved" from "a"
149 149 and we enforce only one of them to be able to affect "a"'s content.
150 150
151 151 return an empty list and an empty dict, if the specified path does not
152 152 exist in stack[-1] (the top of the stack).
153 153
154 154 otherwise, return a list of de-duplicated filecontexts, and the map to
155 155 convert ctx in the stack to fctx, for possible mutable fctxs. the first item
156 156 of the list would be outside the stack and should be considered immutable.
157 157 the remaining items are within the stack.
158 158
159 159 for example, given the following changelog and corresponding filelog
160 160 revisions:
161 161
162 162 changelog: 3----4----5----6----7
163 163 filelog: x 0----1----1----2 (x: no such file yet)
164 164
165 165 - if stack = [5, 6, 7], returns ([0, 1, 2], {5: 1, 6: 1, 7: 2})
166 166 - if stack = [3, 4, 5], returns ([e, 0, 1], {4: 0, 5: 1}), where "e" is a
167 167 dummy empty filecontext.
168 168 - if stack = [2], returns ([], {})
169 169 - if stack = [7], returns ([1, 2], {7: 2})
170 170 - if stack = [6, 7], returns ([1, 2], {6: 1, 7: 2}), although {6: 1} can be
171 171 removed, since 1 is immutable.
172 172 """
173 173 if seenfctxs is None:
174 174 seenfctxs = set()
175 175 assert stack
176 176
177 177 if path not in stack[-1]:
178 178 return [], {}
179 179
180 180 fctxs = []
181 181 fctxmap = {}
182 182
183 183 pctx = stack[0].p1() # the public (immutable) ctx we stop at
184 184 for ctx in reversed(stack):
185 185 if path not in ctx: # the file is added in the next commit
186 186 pctx = ctx
187 187 break
188 188 fctx = ctx[path]
189 189 fctxs.append(fctx)
190 190 if fctx in seenfctxs: # treat fctx as the immutable one
191 191 pctx = None # do not add another immutable fctx
192 192 break
193 193 fctxmap[ctx] = fctx # only for mutable fctxs
194 194 renamed = fctx.renamed()
195 195 if renamed:
196 196 path = renamed[0] # follow rename
197 197 if path in ctx: # but do not follow copy
198 198 pctx = ctx.p1()
199 199 break
200 200
201 201 if pctx is not None: # need an extra immutable fctx
202 202 if path in pctx:
203 203 fctxs.append(pctx[path])
204 204 else:
205 205 fctxs.append(emptyfilecontext())
206 206
207 207 fctxs.reverse()
208 208 # note: we rely on a property of hg: filerev is not reused for linear
209 209 # history. i.e. it's impossible to have:
210 210 # changelog: 4----5----6 (linear, no merges)
211 211 # filelog: 1----2----1
212 212 # ^ reuse filerev (impossible)
213 213 # because parents are part of the hash. if that's not true, we need to
214 214 # remove uniq and find a different way to identify fctxs.
215 215 return uniq(fctxs), fctxmap
216 216
217 217 class overlaystore(patch.filestore):
218 218 """read-only, hybrid store based on a dict and ctx.
219 219 memworkingcopy: {path: content}, overrides file contents.
220 220 """
221 221 def __init__(self, basectx, memworkingcopy):
222 222 self.basectx = basectx
223 223 self.memworkingcopy = memworkingcopy
224 224
225 225 def getfile(self, path):
226 226 """comply with mercurial.patch.filestore.getfile"""
227 227 if path not in self.basectx:
228 228 return None, None, None
229 229 fctx = self.basectx[path]
230 230 if path in self.memworkingcopy:
231 231 content = self.memworkingcopy[path]
232 232 else:
233 233 content = fctx.data()
234 234 mode = (fctx.islink(), fctx.isexec())
235 235 renamed = fctx.renamed() # False or (path, node)
236 236 return content, mode, (renamed and renamed[0])
237 237
238 238 def overlaycontext(memworkingcopy, ctx, parents=None, extra=None):
239 239 """({path: content}, ctx, (p1node, p2node)?, {}?) -> memctx
240 240 memworkingcopy overrides file contents.
241 241 """
242 242 # parents must contain 2 items: (node1, node2)
243 243 if parents is None:
244 244 parents = ctx.repo().changelog.parents(ctx.node())
245 245 if extra is None:
246 246 extra = ctx.extra()
247 247 date = ctx.date()
248 248 desc = ctx.description()
249 249 user = ctx.user()
250 250 files = set(ctx.files()).union(memworkingcopy)
251 251 store = overlaystore(ctx, memworkingcopy)
252 252 return context.memctx(
253 253 repo=ctx.repo(), parents=parents, text=desc,
254 254 files=files, filectxfn=store, user=user, date=date,
255 255 branch=None, extra=extra)
256 256
257 257 class filefixupstate(object):
258 258 """state needed to apply fixups to a single file
259 259
260 260 internally, it keeps file contents of several revisions and a linelog.
261 261
262 262 the linelog uses odd revision numbers for original contents (fctxs passed
263 263 to __init__), and even revision numbers for fixups, like:
264 264
265 265 linelog rev 1: self.fctxs[0] (from an immutable "public" changeset)
266 266 linelog rev 2: fixups made to self.fctxs[0]
267 267 linelog rev 3: self.fctxs[1] (a child of fctxs[0])
268 268 linelog rev 4: fixups made to self.fctxs[1]
269 269 ...
270 270
271 271 a typical use is like:
272 272
273 273 1. call diffwith, to calculate self.fixups
274 274 2. (optionally), present self.fixups to the user, or change it
275 275 3. call apply, to apply changes
276 276 4. read results from "finalcontents", or call getfinalcontent
277 277 """
278 278
279 279 def __init__(self, fctxs, path, ui=None, opts=None):
280 280 """([fctx], ui or None) -> None
281 281
282 282 fctxs should be linear, and sorted by topo order - oldest first.
283 283 fctxs[0] will be considered as "immutable" and will not be changed.
284 284 """
285 285 self.fctxs = fctxs
286 286 self.path = path
287 287 self.ui = ui or nullui()
288 288 self.opts = opts or {}
289 289
290 290 # following fields are built from fctxs. they exist for perf reason
291 291 self.contents = [f.data() for f in fctxs]
292 292 self.contentlines = pycompat.maplist(mdiff.splitnewlines, self.contents)
293 293 self.linelog = self._buildlinelog()
294 294 if self.ui.debugflag:
295 295 assert self._checkoutlinelog() == self.contents
296 296
297 297 # following fields will be filled later
298 298 self.chunkstats = [0, 0] # [adopted, total : int]
299 299 self.targetlines = [] # [str]
300 300 self.fixups = [] # [(linelog rev, a1, a2, b1, b2)]
301 301 self.finalcontents = [] # [str]
302 302 self.ctxaffected = set()
303 303
304 304 def diffwith(self, targetfctx, fm=None):
305 305 """calculate fixups needed by examining the differences between
306 306 self.fctxs[-1] and targetfctx, chunk by chunk.
307 307
308 308 targetfctx is the target state we move towards. we may or may not be
309 309 able to get there because not all modified chunks can be amended into
310 310 a non-public fctx unambiguously.
311 311
312 312 call this only once, before apply().
313 313
314 314 update self.fixups, self.chunkstats, and self.targetlines.
315 315 """
316 316 a = self.contents[-1]
317 317 alines = self.contentlines[-1]
318 318 b = targetfctx.data()
319 319 blines = mdiff.splitnewlines(b)
320 320 self.targetlines = blines
321 321
322 322 self.linelog.annotate(self.linelog.maxrev)
323 323 annotated = self.linelog.annotateresult # [(linelog rev, linenum)]
324 324 assert len(annotated) == len(alines)
325 325 # add a dummy end line to make insertion at the end easier
326 326 if annotated:
327 327 dummyendline = (annotated[-1][0], annotated[-1][1] + 1)
328 328 annotated.append(dummyendline)
329 329
330 330 # analyse diff blocks
331 331 for chunk in self._alldiffchunks(a, b, alines, blines):
332 332 newfixups = self._analysediffchunk(chunk, annotated)
333 333 self.chunkstats[0] += bool(newfixups) # 1 or 0
334 334 self.chunkstats[1] += 1
335 335 self.fixups += newfixups
336 336 if fm is not None:
337 337 self._showchanges(fm, alines, blines, chunk, newfixups)
338 338
339 339 def apply(self):
340 340 """apply self.fixups. update self.linelog, self.finalcontents.
341 341
342 342 call this only once, before getfinalcontent(), after diffwith().
343 343 """
344 344 # the following is unnecessary, as it's done by "diffwith":
345 345 # self.linelog.annotate(self.linelog.maxrev)
346 346 for rev, a1, a2, b1, b2 in reversed(self.fixups):
347 347 blines = self.targetlines[b1:b2]
348 348 if self.ui.debugflag:
349 349 idx = (max(rev - 1, 0)) // 2
350 350 self.ui.write(_('%s: chunk %d:%d -> %d lines\n')
351 351 % (node.short(self.fctxs[idx].node()),
352 352 a1, a2, len(blines)))
353 353 self.linelog.replacelines(rev, a1, a2, b1, b2)
354 354 if self.opts.get('edit_lines', False):
355 355 self.finalcontents = self._checkoutlinelogwithedits()
356 356 else:
357 357 self.finalcontents = self._checkoutlinelog()
358 358
359 359 def getfinalcontent(self, fctx):
360 360 """(fctx) -> str. get modified file content for a given filecontext"""
361 361 idx = self.fctxs.index(fctx)
362 362 return self.finalcontents[idx]
363 363
364 364 def _analysediffchunk(self, chunk, annotated):
365 365 """analyse a different chunk and return new fixups found
366 366
367 367 return [] if no lines from the chunk can be safely applied.
368 368
369 369 the chunk (or lines) cannot be safely applied, if, for example:
370 370 - the modified (deleted) lines belong to a public changeset
371 371 (self.fctxs[0])
372 372 - the chunk is a pure insertion and the adjacent lines (at most 2
373 373 lines) belong to different non-public changesets, or do not belong
374 374 to any non-public changesets.
375 375 - the chunk is modifying lines from different changesets.
376 376 in this case, if the number of lines deleted equals to the number
377 377 of lines added, assume it's a simple 1:1 map (could be wrong).
378 378 otherwise, give up.
379 379 - the chunk is modifying lines from a single non-public changeset,
380 380 but other revisions touch the area as well. i.e. the lines are
381 381 not continuous as seen from the linelog.
382 382 """
383 383 a1, a2, b1, b2 = chunk
384 384 # find involved indexes from annotate result
385 385 involved = annotated[a1:a2]
386 386 if not involved and annotated: # a1 == a2 and a is not empty
387 387 # pure insertion, check nearby lines. ignore lines belong
388 388 # to the public (first) changeset (i.e. annotated[i][0] == 1)
389 389 nearbylinenums = {a2, max(0, a1 - 1)}
390 390 involved = [annotated[i]
391 391 for i in nearbylinenums if annotated[i][0] != 1]
392 392 involvedrevs = list(set(r for r, l in involved))
393 393 newfixups = []
394 394 if len(involvedrevs) == 1 and self._iscontinuous(a1, a2 - 1, True):
395 395 # chunk belongs to a single revision
396 396 rev = involvedrevs[0]
397 397 if rev > 1:
398 398 fixuprev = rev + 1
399 399 newfixups.append((fixuprev, a1, a2, b1, b2))
400 400 elif a2 - a1 == b2 - b1 or b1 == b2:
401 401 # 1:1 line mapping, or chunk was deleted
402 402 for i in pycompat.xrange(a1, a2):
403 403 rev, linenum = annotated[i]
404 404 if rev > 1:
405 405 if b1 == b2: # deletion, simply remove that single line
406 406 nb1 = nb2 = 0
407 407 else: # 1:1 line mapping, change the corresponding rev
408 408 nb1 = b1 + i - a1
409 409 nb2 = nb1 + 1
410 410 fixuprev = rev + 1
411 411 newfixups.append((fixuprev, i, i + 1, nb1, nb2))
412 412 return self._optimizefixups(newfixups)
413 413
414 414 @staticmethod
415 415 def _alldiffchunks(a, b, alines, blines):
416 416 """like mdiff.allblocks, but only care about differences"""
417 417 blocks = mdiff.allblocks(a, b, lines1=alines, lines2=blines)
418 418 for chunk, btype in blocks:
419 419 if btype != '!':
420 420 continue
421 421 yield chunk
422 422
423 423 def _buildlinelog(self):
424 424 """calculate the initial linelog based on self.content{,line}s.
425 425 this is similar to running a partial "annotate".
426 426 """
427 427 llog = linelog.linelog()
428 428 a, alines = '', []
429 429 for i in pycompat.xrange(len(self.contents)):
430 430 b, blines = self.contents[i], self.contentlines[i]
431 431 llrev = i * 2 + 1
432 432 chunks = self._alldiffchunks(a, b, alines, blines)
433 433 for a1, a2, b1, b2 in reversed(list(chunks)):
434 434 llog.replacelines(llrev, a1, a2, b1, b2)
435 435 a, alines = b, blines
436 436 return llog
437 437
438 438 def _checkoutlinelog(self):
439 439 """() -> [str]. check out file contents from linelog"""
440 440 contents = []
441 441 for i in pycompat.xrange(len(self.contents)):
442 442 rev = (i + 1) * 2
443 443 self.linelog.annotate(rev)
444 444 content = ''.join(map(self._getline, self.linelog.annotateresult))
445 445 contents.append(content)
446 446 return contents
447 447
448 448 def _checkoutlinelogwithedits(self):
449 449 """() -> [str]. prompt all lines for edit"""
450 450 alllines = self.linelog.getalllines()
451 451 # header
452 452 editortext = (_('HG: editing %s\nHG: "y" means the line to the right '
453 453 'exists in the changeset to the top\nHG:\n')
454 454 % self.fctxs[-1].path())
455 455 # [(idx, fctx)]. hide the dummy emptyfilecontext
456 456 visiblefctxs = [(i, f)
457 457 for i, f in enumerate(self.fctxs)
458 458 if not isinstance(f, emptyfilecontext)]
459 459 for i, (j, f) in enumerate(visiblefctxs):
460 460 editortext += (_('HG: %s/%s %s %s\n') %
461 461 ('|' * i, '-' * (len(visiblefctxs) - i + 1),
462 462 node.short(f.node()),
463 463 f.description().split('\n',1)[0]))
464 464 editortext += _('HG: %s\n') % ('|' * len(visiblefctxs))
465 465 # figure out the lifetime of a line, this is relatively inefficient,
466 466 # but probably fine
467 467 lineset = defaultdict(lambda: set()) # {(llrev, linenum): {llrev}}
468 468 for i, f in visiblefctxs:
469 469 self.linelog.annotate((i + 1) * 2)
470 470 for l in self.linelog.annotateresult:
471 471 lineset[l].add(i)
472 472 # append lines
473 473 for l in alllines:
474 474 editortext += (' %s : %s' %
475 475 (''.join([('y' if i in lineset[l] else ' ')
476 476 for i, _f in visiblefctxs]),
477 477 self._getline(l)))
478 478 # run editor
479 479 editedtext = self.ui.edit(editortext, '', action='absorb')
480 480 if not editedtext:
481 481 raise error.Abort(_('empty editor text'))
482 482 # parse edited result
483 483 contents = ['' for i in self.fctxs]
484 484 leftpadpos = 4
485 485 colonpos = leftpadpos + len(visiblefctxs) + 1
486 486 for l in mdiff.splitnewlines(editedtext):
487 487 if l.startswith('HG:'):
488 488 continue
489 489 if l[colonpos - 1:colonpos + 2] != ' : ':
490 490 raise error.Abort(_('malformed line: %s') % l)
491 491 linecontent = l[colonpos + 2:]
492 for i, ch in enumerate(l[leftpadpos:colonpos - 1]):
492 for i, ch in enumerate(
493 pycompat.bytestr(l[leftpadpos:colonpos - 1])):
493 494 if ch == 'y':
494 495 contents[visiblefctxs[i][0]] += linecontent
495 496 # chunkstats is hard to calculate if anything changes, therefore
496 497 # set them to just a simple value (1, 1).
497 498 if editedtext != editortext:
498 499 self.chunkstats = [1, 1]
499 500 return contents
500 501
501 502 def _getline(self, lineinfo):
502 503 """((rev, linenum)) -> str. convert rev+line number to line content"""
503 504 rev, linenum = lineinfo
504 505 if rev & 1: # odd: original line taken from fctxs
505 506 return self.contentlines[rev // 2][linenum]
506 507 else: # even: fixup line from targetfctx
507 508 return self.targetlines[linenum]
508 509
509 510 def _iscontinuous(self, a1, a2, closedinterval=False):
510 511 """(a1, a2 : int) -> bool
511 512
512 513 check if these lines are continuous. i.e. no other insertions or
513 514 deletions (from other revisions) among these lines.
514 515
515 516 closedinterval decides whether a2 should be included or not. i.e. is
516 517 it [a1, a2), or [a1, a2] ?
517 518 """
518 519 if a1 >= a2:
519 520 return True
520 521 llog = self.linelog
521 522 offset1 = llog.getoffset(a1)
522 523 offset2 = llog.getoffset(a2) + int(closedinterval)
523 524 linesinbetween = llog.getalllines(offset1, offset2)
524 525 return len(linesinbetween) == a2 - a1 + int(closedinterval)
525 526
526 527 def _optimizefixups(self, fixups):
527 528 """[(rev, a1, a2, b1, b2)] -> [(rev, a1, a2, b1, b2)].
528 529 merge adjacent fixups to make them less fragmented.
529 530 """
530 531 result = []
531 532 pcurrentchunk = [[-1, -1, -1, -1, -1]]
532 533
533 534 def pushchunk():
534 535 if pcurrentchunk[0][0] != -1:
535 536 result.append(tuple(pcurrentchunk[0]))
536 537
537 538 for i, chunk in enumerate(fixups):
538 539 rev, a1, a2, b1, b2 = chunk
539 540 lastrev = pcurrentchunk[0][0]
540 541 lasta2 = pcurrentchunk[0][2]
541 542 lastb2 = pcurrentchunk[0][4]
542 543 if (a1 == lasta2 and b1 == lastb2 and rev == lastrev and
543 544 self._iscontinuous(max(a1 - 1, 0), a1)):
544 545 # merge into currentchunk
545 546 pcurrentchunk[0][2] = a2
546 547 pcurrentchunk[0][4] = b2
547 548 else:
548 549 pushchunk()
549 550 pcurrentchunk[0] = list(chunk)
550 551 pushchunk()
551 552 return result
552 553
553 554 def _showchanges(self, fm, alines, blines, chunk, fixups):
554 555
555 556 def trim(line):
556 557 if line.endswith('\n'):
557 558 line = line[:-1]
558 559 return line
559 560
560 561 # this is not optimized for perf but _showchanges only gets executed
561 562 # with an extra command-line flag.
562 563 a1, a2, b1, b2 = chunk
563 564 aidxs, bidxs = [0] * (a2 - a1), [0] * (b2 - b1)
564 565 for idx, fa1, fa2, fb1, fb2 in fixups:
565 566 for i in pycompat.xrange(fa1, fa2):
566 567 aidxs[i - a1] = (max(idx, 1) - 1) // 2
567 568 for i in pycompat.xrange(fb1, fb2):
568 569 bidxs[i - b1] = (max(idx, 1) - 1) // 2
569 570
570 571 fm.startitem()
571 572 fm.write('hunk', ' %s\n',
572 573 '@@ -%d,%d +%d,%d @@'
573 574 % (a1, a2 - a1, b1, b2 - b1), label='diff.hunk')
574 575 fm.data(path=self.path, linetype='hunk')
575 576
576 577 def writeline(idx, diffchar, line, linetype, linelabel):
577 578 fm.startitem()
578 579 node = ''
579 580 if idx:
580 581 ctx = self.fctxs[idx]
581 582 fm.context(fctx=ctx)
582 583 node = ctx.hex()
583 584 self.ctxaffected.add(ctx.changectx())
584 585 fm.write('node', '%-7.7s ', node, label='absorb.node')
585 586 fm.write('diffchar ' + linetype, '%s%s\n', diffchar, line,
586 587 label=linelabel)
587 588 fm.data(path=self.path, linetype=linetype)
588 589
589 590 for i in pycompat.xrange(a1, a2):
590 591 writeline(aidxs[i - a1], '-', trim(alines[i]), 'deleted',
591 592 'diff.deleted')
592 593 for i in pycompat.xrange(b1, b2):
593 594 writeline(bidxs[i - b1], '+', trim(blines[i]), 'inserted',
594 595 'diff.inserted')
595 596
596 597 class fixupstate(object):
597 598 """state needed to run absorb
598 599
599 600 internally, it keeps paths and filefixupstates.
600 601
601 602 a typical use is like filefixupstates:
602 603
603 604 1. call diffwith, to calculate fixups
604 605 2. (optionally), present fixups to the user, or edit fixups
605 606 3. call apply, to apply changes to memory
606 607 4. call commit, to commit changes to hg database
607 608 """
608 609
609 610 def __init__(self, stack, ui=None, opts=None):
610 611 """([ctx], ui or None) -> None
611 612
612 613 stack: should be linear, and sorted by topo order - oldest first.
613 614 all commits in stack are considered mutable.
614 615 """
615 616 assert stack
616 617 self.ui = ui or nullui()
617 618 self.opts = opts or {}
618 619 self.stack = stack
619 620 self.repo = stack[-1].repo().unfiltered()
620 621
621 622 # following fields will be filled later
622 623 self.paths = [] # [str]
623 624 self.status = None # ctx.status output
624 625 self.fctxmap = {} # {path: {ctx: fctx}}
625 626 self.fixupmap = {} # {path: filefixupstate}
626 627 self.replacemap = {} # {oldnode: newnode or None}
627 628 self.finalnode = None # head after all fixups
628 629 self.ctxaffected = set() # ctx that will be absorbed into
629 630
630 631 def diffwith(self, targetctx, match=None, fm=None):
631 632 """diff and prepare fixups. update self.fixupmap, self.paths"""
632 633 # only care about modified files
633 634 self.status = self.stack[-1].status(targetctx, match)
634 635 self.paths = []
635 636 # but if --edit-lines is used, the user may want to edit files
636 637 # even if they are not modified
637 638 editopt = self.opts.get('edit_lines')
638 639 if not self.status.modified and editopt and match:
639 640 interestingpaths = match.files()
640 641 else:
641 642 interestingpaths = self.status.modified
642 643 # prepare the filefixupstate
643 644 seenfctxs = set()
644 645 # sorting is necessary to eliminate ambiguity for the "double move"
645 646 # case: "hg cp A B; hg cp A C; hg rm A", then only "B" can affect "A".
646 647 for path in sorted(interestingpaths):
647 648 self.ui.debug('calculating fixups for %s\n' % path)
648 649 targetfctx = targetctx[path]
649 650 fctxs, ctx2fctx = getfilestack(self.stack, path, seenfctxs)
650 651 # ignore symbolic links or binary, or unchanged files
651 652 if any(f.islink() or stringutil.binary(f.data())
652 653 for f in [targetfctx] + fctxs
653 654 if not isinstance(f, emptyfilecontext)):
654 655 continue
655 656 if targetfctx.data() == fctxs[-1].data() and not editopt:
656 657 continue
657 658 seenfctxs.update(fctxs[1:])
658 659 self.fctxmap[path] = ctx2fctx
659 660 fstate = filefixupstate(fctxs, path, ui=self.ui, opts=self.opts)
660 661 if fm is not None:
661 662 fm.startitem()
662 663 fm.plain('showing changes for ')
663 664 fm.write('path', '%s\n', path, label='absorb.path')
664 665 fm.data(linetype='path')
665 666 fstate.diffwith(targetfctx, fm)
666 667 self.fixupmap[path] = fstate
667 668 self.paths.append(path)
668 669 self.ctxaffected.update(fstate.ctxaffected)
669 670
670 671 def apply(self):
671 672 """apply fixups to individual filefixupstates"""
672 673 for path, state in self.fixupmap.iteritems():
673 674 if self.ui.debugflag:
674 675 self.ui.write(_('applying fixups to %s\n') % path)
675 676 state.apply()
676 677
677 678 @property
678 679 def chunkstats(self):
679 680 """-> {path: chunkstats}. collect chunkstats from filefixupstates"""
680 681 return dict((path, state.chunkstats)
681 682 for path, state in self.fixupmap.iteritems())
682 683
683 684 def commit(self):
684 685 """commit changes. update self.finalnode, self.replacemap"""
685 686 with self.repo.wlock(), self.repo.lock():
686 687 with self.repo.transaction('absorb') as tr:
687 688 self._commitstack()
688 689 self._movebookmarks(tr)
689 690 if self.repo['.'].node() in self.replacemap:
690 691 self._moveworkingdirectoryparent()
691 692 if self._useobsolete:
692 693 self._obsoleteoldcommits()
693 694 if not self._useobsolete: # strip must be outside transactions
694 695 self._stripoldcommits()
695 696 return self.finalnode
696 697
697 698 def printchunkstats(self):
698 699 """print things like '1 of 2 chunk(s) applied'"""
699 700 ui = self.ui
700 701 chunkstats = self.chunkstats
701 702 if ui.verbose:
702 703 # chunkstats for each file
703 704 for path, stat in chunkstats.iteritems():
704 705 if stat[0]:
705 706 ui.write(_('%s: %d of %d chunk(s) applied\n')
706 707 % (path, stat[0], stat[1]))
707 708 elif not ui.quiet:
708 709 # a summary for all files
709 710 stats = chunkstats.values()
710 711 applied, total = (sum(s[i] for s in stats) for i in (0, 1))
711 712 ui.write(_('%d of %d chunk(s) applied\n') % (applied, total))
712 713
713 714 def _commitstack(self):
714 715 """make new commits. update self.finalnode, self.replacemap.
715 716 it is splitted from "commit" to avoid too much indentation.
716 717 """
717 718 # last node (20-char) committed by us
718 719 lastcommitted = None
719 720 # p1 which overrides the parent of the next commit, "None" means use
720 721 # the original parent unchanged
721 722 nextp1 = None
722 723 for ctx in self.stack:
723 724 memworkingcopy = self._getnewfilecontents(ctx)
724 725 if not memworkingcopy and not lastcommitted:
725 726 # nothing changed, nothing commited
726 727 nextp1 = ctx
727 728 continue
728 729 msg = ''
729 730 if self._willbecomenoop(memworkingcopy, ctx, nextp1):
730 731 # changeset is no longer necessary
731 732 self.replacemap[ctx.node()] = None
732 733 msg = _('became empty and was dropped')
733 734 else:
734 735 # changeset needs re-commit
735 736 nodestr = self._commitsingle(memworkingcopy, ctx, p1=nextp1)
736 737 lastcommitted = self.repo[nodestr]
737 738 nextp1 = lastcommitted
738 739 self.replacemap[ctx.node()] = lastcommitted.node()
739 740 if memworkingcopy:
740 741 msg = _('%d file(s) changed, became %s') % (
741 742 len(memworkingcopy), self._ctx2str(lastcommitted))
742 743 else:
743 744 msg = _('became %s') % self._ctx2str(lastcommitted)
744 745 if self.ui.verbose and msg:
745 746 self.ui.write(_('%s: %s\n') % (self._ctx2str(ctx), msg))
746 747 self.finalnode = lastcommitted and lastcommitted.node()
747 748
748 749 def _ctx2str(self, ctx):
749 750 if self.ui.debugflag:
750 751 return '%d:%s' % (ctx.rev(), ctx.hex())
751 752 else:
752 753 return '%d:%s' % (ctx.rev(), node.short(ctx.node()))
753 754
754 755 def _getnewfilecontents(self, ctx):
755 756 """(ctx) -> {path: str}
756 757
757 758 fetch file contents from filefixupstates.
758 759 return the working copy overrides - files different from ctx.
759 760 """
760 761 result = {}
761 762 for path in self.paths:
762 763 ctx2fctx = self.fctxmap[path] # {ctx: fctx}
763 764 if ctx not in ctx2fctx:
764 765 continue
765 766 fctx = ctx2fctx[ctx]
766 767 content = fctx.data()
767 768 newcontent = self.fixupmap[path].getfinalcontent(fctx)
768 769 if content != newcontent:
769 770 result[fctx.path()] = newcontent
770 771 return result
771 772
772 773 def _movebookmarks(self, tr):
773 774 repo = self.repo
774 775 needupdate = [(name, self.replacemap[hsh])
775 776 for name, hsh in repo._bookmarks.iteritems()
776 777 if hsh in self.replacemap]
777 778 changes = []
778 779 for name, hsh in needupdate:
779 780 if hsh:
780 781 changes.append((name, hsh))
781 782 if self.ui.verbose:
782 783 self.ui.write(_('moving bookmark %s to %s\n')
783 784 % (name, node.hex(hsh)))
784 785 else:
785 786 changes.append((name, None))
786 787 if self.ui.verbose:
787 788 self.ui.write(_('deleting bookmark %s\n') % name)
788 789 repo._bookmarks.applychanges(repo, tr, changes)
789 790
790 791 def _moveworkingdirectoryparent(self):
791 792 if not self.finalnode:
792 793 # Find the latest not-{obsoleted,stripped} parent.
793 794 revs = self.repo.revs('max(::. - %ln)', self.replacemap.keys())
794 795 ctx = self.repo[revs.first()]
795 796 self.finalnode = ctx.node()
796 797 else:
797 798 ctx = self.repo[self.finalnode]
798 799
799 800 dirstate = self.repo.dirstate
800 801 # dirstate.rebuild invalidates fsmonitorstate, causing "hg status" to
801 802 # be slow. in absorb's case, no need to invalidate fsmonitorstate.
802 803 noop = lambda: 0
803 804 restore = noop
804 805 if util.safehasattr(dirstate, '_fsmonitorstate'):
805 806 bak = dirstate._fsmonitorstate.invalidate
806 807 def restore():
807 808 dirstate._fsmonitorstate.invalidate = bak
808 809 dirstate._fsmonitorstate.invalidate = noop
809 810 try:
810 811 with dirstate.parentchange():
811 812 dirstate.rebuild(ctx.node(), ctx.manifest(), self.paths)
812 813 finally:
813 814 restore()
814 815
815 816 @staticmethod
816 817 def _willbecomenoop(memworkingcopy, ctx, pctx=None):
817 818 """({path: content}, ctx, ctx) -> bool. test if a commit will be noop
818 819
819 820 if it will become an empty commit (does not change anything, after the
820 821 memworkingcopy overrides), return True. otherwise return False.
821 822 """
822 823 if not pctx:
823 824 parents = ctx.parents()
824 825 if len(parents) != 1:
825 826 return False
826 827 pctx = parents[0]
827 828 # ctx changes more files (not a subset of memworkingcopy)
828 829 if not set(ctx.files()).issubset(set(memworkingcopy)):
829 830 return False
830 831 for path, content in memworkingcopy.iteritems():
831 832 if path not in pctx or path not in ctx:
832 833 return False
833 834 fctx = ctx[path]
834 835 pfctx = pctx[path]
835 836 if pfctx.flags() != fctx.flags():
836 837 return False
837 838 if pfctx.data() != content:
838 839 return False
839 840 return True
840 841
841 842 def _commitsingle(self, memworkingcopy, ctx, p1=None):
842 843 """(ctx, {path: content}, node) -> node. make a single commit
843 844
844 845 the commit is a clone from ctx, with a (optionally) different p1, and
845 846 different file contents replaced by memworkingcopy.
846 847 """
847 848 parents = p1 and (p1, node.nullid)
848 849 extra = ctx.extra()
849 850 if self._useobsolete and self.ui.configbool('absorb', 'add-noise'):
850 851 extra['absorb_source'] = ctx.hex()
851 852 mctx = overlaycontext(memworkingcopy, ctx, parents, extra=extra)
852 853 # preserve phase
853 854 with mctx.repo().ui.configoverride({
854 855 ('phases', 'new-commit'): ctx.phase()}):
855 856 return mctx.commit()
856 857
857 858 @util.propertycache
858 859 def _useobsolete(self):
859 860 """() -> bool"""
860 861 return obsolete.isenabled(self.repo, obsolete.createmarkersopt)
861 862
862 863 def _obsoleteoldcommits(self):
863 864 relations = [(self.repo[k], v and (self.repo[v],) or ())
864 865 for k, v in self.replacemap.iteritems()]
865 866 if relations:
866 867 obsolete.createmarkers(self.repo, relations)
867 868
868 869 def _stripoldcommits(self):
869 870 nodelist = self.replacemap.keys()
870 871 # make sure we don't strip innocent children
871 872 revs = self.repo.revs('%ln - (::(heads(%ln::)-%ln))', nodelist,
872 873 nodelist, nodelist)
873 874 tonode = self.repo.changelog.node
874 875 nodelist = [tonode(r) for r in revs]
875 876 if nodelist:
876 877 repair.strip(self.repo.ui, self.repo, nodelist)
877 878
878 879 def _parsechunk(hunk):
879 880 """(crecord.uihunk or patch.recordhunk) -> (path, (a1, a2, [bline]))"""
880 881 if type(hunk) not in (crecord.uihunk, patch.recordhunk):
881 882 return None, None
882 883 path = hunk.header.filename()
883 884 a1 = hunk.fromline + len(hunk.before) - 1
884 885 # remove before and after context
885 886 hunk.before = hunk.after = []
886 887 buf = util.stringio()
887 888 hunk.write(buf)
888 889 patchlines = mdiff.splitnewlines(buf.getvalue())
889 890 # hunk.prettystr() will update hunk.removed
890 891 a2 = a1 + hunk.removed
891 892 blines = [l[1:] for l in patchlines[1:] if l[0] != '-']
892 893 return path, (a1, a2, blines)
893 894
894 895 def overlaydiffcontext(ctx, chunks):
895 896 """(ctx, [crecord.uihunk]) -> memctx
896 897
897 898 return a memctx with some [1] patches (chunks) applied to ctx.
898 899 [1]: modifications are handled. renames, mode changes, etc. are ignored.
899 900 """
900 901 # sadly the applying-patch logic is hardly reusable, and messy:
901 902 # 1. the core logic "_applydiff" is too heavy - it writes .rej files, it
902 903 # needs a file stream of a patch and will re-parse it, while we have
903 904 # structured hunk objects at hand.
904 905 # 2. a lot of different implementations about "chunk" (patch.hunk,
905 906 # patch.recordhunk, crecord.uihunk)
906 907 # as we only care about applying changes to modified files, no mode
907 908 # change, no binary diff, and no renames, it's probably okay to
908 909 # re-invent the logic using much simpler code here.
909 910 memworkingcopy = {} # {path: content}
910 911 patchmap = defaultdict(lambda: []) # {path: [(a1, a2, [bline])]}
911 912 for path, info in map(_parsechunk, chunks):
912 913 if not path or not info:
913 914 continue
914 915 patchmap[path].append(info)
915 916 for path, patches in patchmap.iteritems():
916 917 if path not in ctx or not patches:
917 918 continue
918 919 patches.sort(reverse=True)
919 920 lines = mdiff.splitnewlines(ctx[path].data())
920 921 for a1, a2, blines in patches:
921 922 lines[a1:a2] = blines
922 923 memworkingcopy[path] = ''.join(lines)
923 924 return overlaycontext(memworkingcopy, ctx)
924 925
925 926 def absorb(ui, repo, stack=None, targetctx=None, pats=None, opts=None):
926 927 """pick fixup chunks from targetctx, apply them to stack.
927 928
928 929 if targetctx is None, the working copy context will be used.
929 930 if stack is None, the current draft stack will be used.
930 931 return fixupstate.
931 932 """
932 933 if stack is None:
933 934 limit = ui.configint('absorb', 'max-stack-size')
934 935 stack = getdraftstack(repo['.'], limit)
935 936 if limit and len(stack) >= limit:
936 937 ui.warn(_('absorb: only the recent %d changesets will '
937 938 'be analysed\n')
938 939 % limit)
939 940 if not stack:
940 941 raise error.Abort(_('no mutable changeset to change'))
941 942 if targetctx is None: # default to working copy
942 943 targetctx = repo[None]
943 944 if pats is None:
944 945 pats = ()
945 946 if opts is None:
946 947 opts = {}
947 948 state = fixupstate(stack, ui=ui, opts=opts)
948 949 matcher = scmutil.match(targetctx, pats, opts)
949 950 if opts.get('interactive'):
950 951 diff = patch.diff(repo, stack[-1].node(), targetctx.node(), matcher)
951 952 origchunks = patch.parsepatch(diff)
952 953 chunks = cmdutil.recordfilter(ui, origchunks)[0]
953 954 targetctx = overlaydiffcontext(stack[-1], chunks)
954 955 fm = None
955 956 if opts.get('print_changes') or not opts.get('apply_changes'):
956 957 fm = ui.formatter('absorb', opts)
957 958 state.diffwith(targetctx, matcher, fm)
958 959 if fm is not None:
959 960 fm.startitem()
960 961 fm.write("count", "\n%d changesets affected\n", len(state.ctxaffected))
961 962 fm.data(linetype='summary')
962 963 for ctx in reversed(stack):
963 964 if ctx not in state.ctxaffected:
964 965 continue
965 966 fm.startitem()
966 967 fm.context(ctx=ctx)
967 968 fm.data(linetype='changeset')
968 969 fm.write('node', '%-7.7s ', ctx.hex(), label='absorb.node')
969 970 descfirstline = ctx.description().splitlines()[0]
970 971 fm.write('descfirstline', '%s\n', descfirstline,
971 972 label='absorb.description')
972 973 fm.end()
973 974 if not opts.get('dry_run'):
974 975 if (not opts.get('apply_changes') and
975 976 state.ctxaffected and
976 977 ui.promptchoice("apply changes (yn)? $$ &Yes $$ &No", default=1)):
977 978 raise error.Abort(_('absorb cancelled\n'))
978 979
979 980 state.apply()
980 981 if state.commit():
981 982 state.printchunkstats()
982 983 elif not ui.quiet:
983 984 ui.write(_('nothing applied\n'))
984 985 return state
985 986
986 987 @command('absorb',
987 988 [('a', 'apply-changes', None,
988 989 _('apply changes without prompting for confirmation')),
989 990 ('p', 'print-changes', None,
990 991 _('always print which changesets are modified by which changes')),
991 992 ('i', 'interactive', None,
992 993 _('interactively select which chunks to apply (EXPERIMENTAL)')),
993 994 ('e', 'edit-lines', None,
994 995 _('edit what lines belong to which changesets before commit '
995 996 '(EXPERIMENTAL)')),
996 997 ] + commands.dryrunopts + commands.templateopts + commands.walkopts,
997 998 _('hg absorb [OPTION] [FILE]...'),
998 999 helpcategory=command.CATEGORY_COMMITTING,
999 1000 helpbasic=True)
1000 1001 def absorbcmd(ui, repo, *pats, **opts):
1001 1002 """incorporate corrections into the stack of draft changesets
1002 1003
1003 1004 absorb analyzes each change in your working directory and attempts to
1004 1005 amend the changed lines into the changesets in your stack that first
1005 1006 introduced those lines.
1006 1007
1007 1008 If absorb cannot find an unambiguous changeset to amend for a change,
1008 1009 that change will be left in the working directory, untouched. They can be
1009 1010 observed by :hg:`status` or :hg:`diff` afterwards. In other words,
1010 1011 absorb does not write to the working directory.
1011 1012
1012 1013 Changesets outside the revset `::. and not public() and not merge()` will
1013 1014 not be changed.
1014 1015
1015 1016 Changesets that become empty after applying the changes will be deleted.
1016 1017
1017 1018 By default, absorb will show what it plans to do and prompt for
1018 1019 confirmation. If you are confident that the changes will be absorbed
1019 1020 to the correct place, run :hg:`absorb -a` to apply the changes
1020 1021 immediately.
1021 1022
1022 1023 Returns 0 on success, 1 if all chunks were ignored and nothing amended.
1023 1024 """
1024 1025 opts = pycompat.byteskwargs(opts)
1025 1026 state = absorb(ui, repo, pats=pats, opts=opts)
1026 1027 if sum(s[0] for s in state.chunkstats.values()) == 0:
1027 1028 return 1
General Comments 0
You need to be logged in to leave comments. Login now