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