##// END OF EJS Templates
histedit: use more specific exceptions for more detailed exit codes...
Martin von Zweigbergk -
r48868:f27a8339 default
parent child Browse files
Show More
@@ -1,2651 +1,2655
1 # histedit.py - interactive history editing for mercurial
1 # histedit.py - interactive history editing for mercurial
2 #
2 #
3 # Copyright 2009 Augie Fackler <raf@durin42.com>
3 # Copyright 2009 Augie Fackler <raf@durin42.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 """interactive history editing
7 """interactive history editing
8
8
9 With this extension installed, Mercurial gains one new command: histedit. Usage
9 With this extension installed, Mercurial gains one new command: histedit. Usage
10 is as follows, assuming the following history::
10 is as follows, assuming the following history::
11
11
12 @ 3[tip] 7c2fd3b9020c 2009-04-27 18:04 -0500 durin42
12 @ 3[tip] 7c2fd3b9020c 2009-04-27 18:04 -0500 durin42
13 | Add delta
13 | Add delta
14 |
14 |
15 o 2 030b686bedc4 2009-04-27 18:04 -0500 durin42
15 o 2 030b686bedc4 2009-04-27 18:04 -0500 durin42
16 | Add gamma
16 | Add gamma
17 |
17 |
18 o 1 c561b4e977df 2009-04-27 18:04 -0500 durin42
18 o 1 c561b4e977df 2009-04-27 18:04 -0500 durin42
19 | Add beta
19 | Add beta
20 |
20 |
21 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
21 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
22 Add alpha
22 Add alpha
23
23
24 If you were to run ``hg histedit c561b4e977df``, you would see the following
24 If you were to run ``hg histedit c561b4e977df``, you would see the following
25 file open in your editor::
25 file open in your editor::
26
26
27 pick c561b4e977df Add beta
27 pick c561b4e977df Add beta
28 pick 030b686bedc4 Add gamma
28 pick 030b686bedc4 Add gamma
29 pick 7c2fd3b9020c Add delta
29 pick 7c2fd3b9020c Add delta
30
30
31 # Edit history between c561b4e977df and 7c2fd3b9020c
31 # Edit history between c561b4e977df and 7c2fd3b9020c
32 #
32 #
33 # Commits are listed from least to most recent
33 # Commits are listed from least to most recent
34 #
34 #
35 # Commands:
35 # Commands:
36 # p, pick = use commit
36 # p, pick = use commit
37 # e, edit = use commit, but allow edits before making new commit
37 # e, edit = use commit, but allow edits before making new commit
38 # f, fold = use commit, but combine it with the one above
38 # f, fold = use commit, but combine it with the one above
39 # r, roll = like fold, but discard this commit's description and date
39 # r, roll = like fold, but discard this commit's description and date
40 # d, drop = remove commit from history
40 # d, drop = remove commit from history
41 # m, mess = edit commit message without changing commit content
41 # m, mess = edit commit message without changing commit content
42 # b, base = checkout changeset and apply further changesets from there
42 # b, base = checkout changeset and apply further changesets from there
43 #
43 #
44
44
45 In this file, lines beginning with ``#`` are ignored. You must specify a rule
45 In this file, lines beginning with ``#`` are ignored. You must specify a rule
46 for each revision in your history. For example, if you had meant to add gamma
46 for each revision in your history. For example, if you had meant to add gamma
47 before beta, and then wanted to add delta in the same revision as beta, you
47 before beta, and then wanted to add delta in the same revision as beta, you
48 would reorganize the file to look like this::
48 would reorganize the file to look like this::
49
49
50 pick 030b686bedc4 Add gamma
50 pick 030b686bedc4 Add gamma
51 pick c561b4e977df Add beta
51 pick c561b4e977df Add beta
52 fold 7c2fd3b9020c Add delta
52 fold 7c2fd3b9020c Add delta
53
53
54 # Edit history between c561b4e977df and 7c2fd3b9020c
54 # Edit history between c561b4e977df and 7c2fd3b9020c
55 #
55 #
56 # Commits are listed from least to most recent
56 # Commits are listed from least to most recent
57 #
57 #
58 # Commands:
58 # Commands:
59 # p, pick = use commit
59 # p, pick = use commit
60 # e, edit = use commit, but allow edits before making new commit
60 # e, edit = use commit, but allow edits before making new commit
61 # f, fold = use commit, but combine it with the one above
61 # f, fold = use commit, but combine it with the one above
62 # r, roll = like fold, but discard this commit's description and date
62 # r, roll = like fold, but discard this commit's description and date
63 # d, drop = remove commit from history
63 # d, drop = remove commit from history
64 # m, mess = edit commit message without changing commit content
64 # m, mess = edit commit message without changing commit content
65 # b, base = checkout changeset and apply further changesets from there
65 # b, base = checkout changeset and apply further changesets from there
66 #
66 #
67
67
68 At which point you close the editor and ``histedit`` starts working. When you
68 At which point you close the editor and ``histedit`` starts working. When you
69 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
69 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
70 those revisions together, offering you a chance to clean up the commit message::
70 those revisions together, offering you a chance to clean up the commit message::
71
71
72 Add beta
72 Add beta
73 ***
73 ***
74 Add delta
74 Add delta
75
75
76 Edit the commit message to your liking, then close the editor. The date used
76 Edit the commit message to your liking, then close the editor. The date used
77 for the commit will be the later of the two commits' dates. For this example,
77 for the commit will be the later of the two commits' dates. For this example,
78 let's assume that the commit message was changed to ``Add beta and delta.``
78 let's assume that the commit message was changed to ``Add beta and delta.``
79 After histedit has run and had a chance to remove any old or temporary
79 After histedit has run and had a chance to remove any old or temporary
80 revisions it needed, the history looks like this::
80 revisions it needed, the history looks like this::
81
81
82 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
82 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
83 | Add beta and delta.
83 | Add beta and delta.
84 |
84 |
85 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
85 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
86 | Add gamma
86 | Add gamma
87 |
87 |
88 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
88 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
89 Add alpha
89 Add alpha
90
90
91 Note that ``histedit`` does *not* remove any revisions (even its own temporary
91 Note that ``histedit`` does *not* remove any revisions (even its own temporary
92 ones) until after it has completed all the editing operations, so it will
92 ones) until after it has completed all the editing operations, so it will
93 probably perform several strip operations when it's done. For the above example,
93 probably perform several strip operations when it's done. For the above example,
94 it had to run strip twice. Strip can be slow depending on a variety of factors,
94 it had to run strip twice. Strip can be slow depending on a variety of factors,
95 so you might need to be a little patient. You can choose to keep the original
95 so you might need to be a little patient. You can choose to keep the original
96 revisions by passing the ``--keep`` flag.
96 revisions by passing the ``--keep`` flag.
97
97
98 The ``edit`` operation will drop you back to a command prompt,
98 The ``edit`` operation will drop you back to a command prompt,
99 allowing you to edit files freely, or even use ``hg record`` to commit
99 allowing you to edit files freely, or even use ``hg record`` to commit
100 some changes as a separate commit. When you're done, any remaining
100 some changes as a separate commit. When you're done, any remaining
101 uncommitted changes will be committed as well. When done, run ``hg
101 uncommitted changes will be committed as well. When done, run ``hg
102 histedit --continue`` to finish this step. If there are uncommitted
102 histedit --continue`` to finish this step. If there are uncommitted
103 changes, you'll be prompted for a new commit message, but the default
103 changes, you'll be prompted for a new commit message, but the default
104 commit message will be the original message for the ``edit`` ed
104 commit message will be the original message for the ``edit`` ed
105 revision, and the date of the original commit will be preserved.
105 revision, and the date of the original commit will be preserved.
106
106
107 The ``message`` operation will give you a chance to revise a commit
107 The ``message`` operation will give you a chance to revise a commit
108 message without changing the contents. It's a shortcut for doing
108 message without changing the contents. It's a shortcut for doing
109 ``edit`` immediately followed by `hg histedit --continue``.
109 ``edit`` immediately followed by `hg histedit --continue``.
110
110
111 If ``histedit`` encounters a conflict when moving a revision (while
111 If ``histedit`` encounters a conflict when moving a revision (while
112 handling ``pick`` or ``fold``), it'll stop in a similar manner to
112 handling ``pick`` or ``fold``), it'll stop in a similar manner to
113 ``edit`` with the difference that it won't prompt you for a commit
113 ``edit`` with the difference that it won't prompt you for a commit
114 message when done. If you decide at this point that you don't like how
114 message when done. If you decide at this point that you don't like how
115 much work it will be to rearrange history, or that you made a mistake,
115 much work it will be to rearrange history, or that you made a mistake,
116 you can use ``hg histedit --abort`` to abandon the new changes you
116 you can use ``hg histedit --abort`` to abandon the new changes you
117 have made and return to the state before you attempted to edit your
117 have made and return to the state before you attempted to edit your
118 history.
118 history.
119
119
120 If we clone the histedit-ed example repository above and add four more
120 If we clone the histedit-ed example repository above and add four more
121 changes, such that we have the following history::
121 changes, such that we have the following history::
122
122
123 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
123 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
124 | Add theta
124 | Add theta
125 |
125 |
126 o 5 140988835471 2009-04-27 18:04 -0500 stefan
126 o 5 140988835471 2009-04-27 18:04 -0500 stefan
127 | Add eta
127 | Add eta
128 |
128 |
129 o 4 122930637314 2009-04-27 18:04 -0500 stefan
129 o 4 122930637314 2009-04-27 18:04 -0500 stefan
130 | Add zeta
130 | Add zeta
131 |
131 |
132 o 3 836302820282 2009-04-27 18:04 -0500 stefan
132 o 3 836302820282 2009-04-27 18:04 -0500 stefan
133 | Add epsilon
133 | Add epsilon
134 |
134 |
135 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
135 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
136 | Add beta and delta.
136 | Add beta and delta.
137 |
137 |
138 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
138 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
139 | Add gamma
139 | Add gamma
140 |
140 |
141 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
141 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
142 Add alpha
142 Add alpha
143
143
144 If you run ``hg histedit --outgoing`` on the clone then it is the same
144 If you run ``hg histedit --outgoing`` on the clone then it is the same
145 as running ``hg histedit 836302820282``. If you need plan to push to a
145 as running ``hg histedit 836302820282``. If you need plan to push to a
146 repository that Mercurial does not detect to be related to the source
146 repository that Mercurial does not detect to be related to the source
147 repo, you can add a ``--force`` option.
147 repo, you can add a ``--force`` option.
148
148
149 Config
149 Config
150 ------
150 ------
151
151
152 Histedit rule lines are truncated to 80 characters by default. You
152 Histedit rule lines are truncated to 80 characters by default. You
153 can customize this behavior by setting a different length in your
153 can customize this behavior by setting a different length in your
154 configuration file::
154 configuration file::
155
155
156 [histedit]
156 [histedit]
157 linelen = 120 # truncate rule lines at 120 characters
157 linelen = 120 # truncate rule lines at 120 characters
158
158
159 The summary of a change can be customized as well::
159 The summary of a change can be customized as well::
160
160
161 [histedit]
161 [histedit]
162 summary-template = '{rev} {bookmarks} {desc|firstline}'
162 summary-template = '{rev} {bookmarks} {desc|firstline}'
163
163
164 The customized summary should be kept short enough that rule lines
164 The customized summary should be kept short enough that rule lines
165 will fit in the configured line length. See above if that requires
165 will fit in the configured line length. See above if that requires
166 customization.
166 customization.
167
167
168 ``hg histedit`` attempts to automatically choose an appropriate base
168 ``hg histedit`` attempts to automatically choose an appropriate base
169 revision to use. To change which base revision is used, define a
169 revision to use. To change which base revision is used, define a
170 revset in your configuration file::
170 revset in your configuration file::
171
171
172 [histedit]
172 [histedit]
173 defaultrev = only(.) & draft()
173 defaultrev = only(.) & draft()
174
174
175 By default each edited revision needs to be present in histedit commands.
175 By default each edited revision needs to be present in histedit commands.
176 To remove revision you need to use ``drop`` operation. You can configure
176 To remove revision you need to use ``drop`` operation. You can configure
177 the drop to be implicit for missing commits by adding::
177 the drop to be implicit for missing commits by adding::
178
178
179 [histedit]
179 [histedit]
180 dropmissing = True
180 dropmissing = True
181
181
182 By default, histedit will close the transaction after each action. For
182 By default, histedit will close the transaction after each action. For
183 performance purposes, you can configure histedit to use a single transaction
183 performance purposes, you can configure histedit to use a single transaction
184 across the entire histedit. WARNING: This setting introduces a significant risk
184 across the entire histedit. WARNING: This setting introduces a significant risk
185 of losing the work you've done in a histedit if the histedit aborts
185 of losing the work you've done in a histedit if the histedit aborts
186 unexpectedly::
186 unexpectedly::
187
187
188 [histedit]
188 [histedit]
189 singletransaction = True
189 singletransaction = True
190
190
191 """
191 """
192
192
193 from __future__ import absolute_import
193 from __future__ import absolute_import
194
194
195 # chistedit dependencies that are not available everywhere
195 # chistedit dependencies that are not available everywhere
196 try:
196 try:
197 import fcntl
197 import fcntl
198 import termios
198 import termios
199 except ImportError:
199 except ImportError:
200 fcntl = None
200 fcntl = None
201 termios = None
201 termios = None
202
202
203 import functools
203 import functools
204 import os
204 import os
205 import struct
205 import struct
206
206
207 from mercurial.i18n import _
207 from mercurial.i18n import _
208 from mercurial.pycompat import (
208 from mercurial.pycompat import (
209 getattr,
209 getattr,
210 open,
210 open,
211 )
211 )
212 from mercurial.node import (
212 from mercurial.node import (
213 bin,
213 bin,
214 hex,
214 hex,
215 short,
215 short,
216 )
216 )
217 from mercurial import (
217 from mercurial import (
218 bundle2,
218 bundle2,
219 cmdutil,
219 cmdutil,
220 context,
220 context,
221 copies,
221 copies,
222 destutil,
222 destutil,
223 discovery,
223 discovery,
224 encoding,
224 encoding,
225 error,
225 error,
226 exchange,
226 exchange,
227 extensions,
227 extensions,
228 hg,
228 hg,
229 logcmdutil,
229 logcmdutil,
230 merge as mergemod,
230 merge as mergemod,
231 mergestate as mergestatemod,
231 mergestate as mergestatemod,
232 mergeutil,
232 mergeutil,
233 obsolete,
233 obsolete,
234 pycompat,
234 pycompat,
235 registrar,
235 registrar,
236 repair,
236 repair,
237 rewriteutil,
237 rewriteutil,
238 scmutil,
238 scmutil,
239 state as statemod,
239 state as statemod,
240 util,
240 util,
241 )
241 )
242 from mercurial.utils import (
242 from mercurial.utils import (
243 dateutil,
243 dateutil,
244 stringutil,
244 stringutil,
245 urlutil,
245 urlutil,
246 )
246 )
247
247
248 pickle = util.pickle
248 pickle = util.pickle
249 cmdtable = {}
249 cmdtable = {}
250 command = registrar.command(cmdtable)
250 command = registrar.command(cmdtable)
251
251
252 configtable = {}
252 configtable = {}
253 configitem = registrar.configitem(configtable)
253 configitem = registrar.configitem(configtable)
254 configitem(
254 configitem(
255 b'experimental',
255 b'experimental',
256 b'histedit.autoverb',
256 b'histedit.autoverb',
257 default=False,
257 default=False,
258 )
258 )
259 configitem(
259 configitem(
260 b'histedit',
260 b'histedit',
261 b'defaultrev',
261 b'defaultrev',
262 default=None,
262 default=None,
263 )
263 )
264 configitem(
264 configitem(
265 b'histedit',
265 b'histedit',
266 b'dropmissing',
266 b'dropmissing',
267 default=False,
267 default=False,
268 )
268 )
269 configitem(
269 configitem(
270 b'histedit',
270 b'histedit',
271 b'linelen',
271 b'linelen',
272 default=80,
272 default=80,
273 )
273 )
274 configitem(
274 configitem(
275 b'histedit',
275 b'histedit',
276 b'singletransaction',
276 b'singletransaction',
277 default=False,
277 default=False,
278 )
278 )
279 configitem(
279 configitem(
280 b'ui',
280 b'ui',
281 b'interface.histedit',
281 b'interface.histedit',
282 default=None,
282 default=None,
283 )
283 )
284 configitem(b'histedit', b'summary-template', default=b'{rev} {desc|firstline}')
284 configitem(b'histedit', b'summary-template', default=b'{rev} {desc|firstline}')
285
285
286 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
286 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
287 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
287 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
288 # be specifying the version(s) of Mercurial they are tested with, or
288 # be specifying the version(s) of Mercurial they are tested with, or
289 # leave the attribute unspecified.
289 # leave the attribute unspecified.
290 testedwith = b'ships-with-hg-core'
290 testedwith = b'ships-with-hg-core'
291
291
292 actiontable = {}
292 actiontable = {}
293 primaryactions = set()
293 primaryactions = set()
294 secondaryactions = set()
294 secondaryactions = set()
295 tertiaryactions = set()
295 tertiaryactions = set()
296 internalactions = set()
296 internalactions = set()
297
297
298
298
299 def geteditcomment(ui, first, last):
299 def geteditcomment(ui, first, last):
300 """construct the editor comment
300 """construct the editor comment
301 The comment includes::
301 The comment includes::
302 - an intro
302 - an intro
303 - sorted primary commands
303 - sorted primary commands
304 - sorted short commands
304 - sorted short commands
305 - sorted long commands
305 - sorted long commands
306 - additional hints
306 - additional hints
307
307
308 Commands are only included once.
308 Commands are only included once.
309 """
309 """
310 intro = _(
310 intro = _(
311 b"""Edit history between %s and %s
311 b"""Edit history between %s and %s
312
312
313 Commits are listed from least to most recent
313 Commits are listed from least to most recent
314
314
315 You can reorder changesets by reordering the lines
315 You can reorder changesets by reordering the lines
316
316
317 Commands:
317 Commands:
318 """
318 """
319 )
319 )
320 actions = []
320 actions = []
321
321
322 def addverb(v):
322 def addverb(v):
323 a = actiontable[v]
323 a = actiontable[v]
324 lines = a.message.split(b"\n")
324 lines = a.message.split(b"\n")
325 if len(a.verbs):
325 if len(a.verbs):
326 v = b', '.join(sorted(a.verbs, key=lambda v: len(v)))
326 v = b', '.join(sorted(a.verbs, key=lambda v: len(v)))
327 actions.append(b" %s = %s" % (v, lines[0]))
327 actions.append(b" %s = %s" % (v, lines[0]))
328 actions.extend([b' %s'] * (len(lines) - 1))
328 actions.extend([b' %s'] * (len(lines) - 1))
329
329
330 for v in (
330 for v in (
331 sorted(primaryactions)
331 sorted(primaryactions)
332 + sorted(secondaryactions)
332 + sorted(secondaryactions)
333 + sorted(tertiaryactions)
333 + sorted(tertiaryactions)
334 ):
334 ):
335 addverb(v)
335 addverb(v)
336 actions.append(b'')
336 actions.append(b'')
337
337
338 hints = []
338 hints = []
339 if ui.configbool(b'histedit', b'dropmissing'):
339 if ui.configbool(b'histedit', b'dropmissing'):
340 hints.append(
340 hints.append(
341 b"Deleting a changeset from the list "
341 b"Deleting a changeset from the list "
342 b"will DISCARD it from the edited history!"
342 b"will DISCARD it from the edited history!"
343 )
343 )
344
344
345 lines = (intro % (first, last)).split(b'\n') + actions + hints
345 lines = (intro % (first, last)).split(b'\n') + actions + hints
346
346
347 return b''.join([b'# %s\n' % l if l else b'#\n' for l in lines])
347 return b''.join([b'# %s\n' % l if l else b'#\n' for l in lines])
348
348
349
349
350 class histeditstate(object):
350 class histeditstate(object):
351 def __init__(self, repo):
351 def __init__(self, repo):
352 self.repo = repo
352 self.repo = repo
353 self.actions = None
353 self.actions = None
354 self.keep = None
354 self.keep = None
355 self.topmost = None
355 self.topmost = None
356 self.parentctxnode = None
356 self.parentctxnode = None
357 self.lock = None
357 self.lock = None
358 self.wlock = None
358 self.wlock = None
359 self.backupfile = None
359 self.backupfile = None
360 self.stateobj = statemod.cmdstate(repo, b'histedit-state')
360 self.stateobj = statemod.cmdstate(repo, b'histedit-state')
361 self.replacements = []
361 self.replacements = []
362
362
363 def read(self):
363 def read(self):
364 """Load histedit state from disk and set fields appropriately."""
364 """Load histedit state from disk and set fields appropriately."""
365 if not self.stateobj.exists():
365 if not self.stateobj.exists():
366 cmdutil.wrongtooltocontinue(self.repo, _(b'histedit'))
366 cmdutil.wrongtooltocontinue(self.repo, _(b'histedit'))
367
367
368 data = self._read()
368 data = self._read()
369
369
370 self.parentctxnode = data[b'parentctxnode']
370 self.parentctxnode = data[b'parentctxnode']
371 actions = parserules(data[b'rules'], self)
371 actions = parserules(data[b'rules'], self)
372 self.actions = actions
372 self.actions = actions
373 self.keep = data[b'keep']
373 self.keep = data[b'keep']
374 self.topmost = data[b'topmost']
374 self.topmost = data[b'topmost']
375 self.replacements = data[b'replacements']
375 self.replacements = data[b'replacements']
376 self.backupfile = data[b'backupfile']
376 self.backupfile = data[b'backupfile']
377
377
378 def _read(self):
378 def _read(self):
379 fp = self.repo.vfs.read(b'histedit-state')
379 fp = self.repo.vfs.read(b'histedit-state')
380 if fp.startswith(b'v1\n'):
380 if fp.startswith(b'v1\n'):
381 data = self._load()
381 data = self._load()
382 parentctxnode, rules, keep, topmost, replacements, backupfile = data
382 parentctxnode, rules, keep, topmost, replacements, backupfile = data
383 else:
383 else:
384 data = pickle.loads(fp)
384 data = pickle.loads(fp)
385 parentctxnode, rules, keep, topmost, replacements = data
385 parentctxnode, rules, keep, topmost, replacements = data
386 backupfile = None
386 backupfile = None
387 rules = b"\n".join([b"%s %s" % (verb, rest) for [verb, rest] in rules])
387 rules = b"\n".join([b"%s %s" % (verb, rest) for [verb, rest] in rules])
388
388
389 return {
389 return {
390 b'parentctxnode': parentctxnode,
390 b'parentctxnode': parentctxnode,
391 b"rules": rules,
391 b"rules": rules,
392 b"keep": keep,
392 b"keep": keep,
393 b"topmost": topmost,
393 b"topmost": topmost,
394 b"replacements": replacements,
394 b"replacements": replacements,
395 b"backupfile": backupfile,
395 b"backupfile": backupfile,
396 }
396 }
397
397
398 def write(self, tr=None):
398 def write(self, tr=None):
399 if tr:
399 if tr:
400 tr.addfilegenerator(
400 tr.addfilegenerator(
401 b'histedit-state',
401 b'histedit-state',
402 (b'histedit-state',),
402 (b'histedit-state',),
403 self._write,
403 self._write,
404 location=b'plain',
404 location=b'plain',
405 )
405 )
406 else:
406 else:
407 with self.repo.vfs(b"histedit-state", b"w") as f:
407 with self.repo.vfs(b"histedit-state", b"w") as f:
408 self._write(f)
408 self._write(f)
409
409
410 def _write(self, fp):
410 def _write(self, fp):
411 fp.write(b'v1\n')
411 fp.write(b'v1\n')
412 fp.write(b'%s\n' % hex(self.parentctxnode))
412 fp.write(b'%s\n' % hex(self.parentctxnode))
413 fp.write(b'%s\n' % hex(self.topmost))
413 fp.write(b'%s\n' % hex(self.topmost))
414 fp.write(b'%s\n' % (b'True' if self.keep else b'False'))
414 fp.write(b'%s\n' % (b'True' if self.keep else b'False'))
415 fp.write(b'%d\n' % len(self.actions))
415 fp.write(b'%d\n' % len(self.actions))
416 for action in self.actions:
416 for action in self.actions:
417 fp.write(b'%s\n' % action.tostate())
417 fp.write(b'%s\n' % action.tostate())
418 fp.write(b'%d\n' % len(self.replacements))
418 fp.write(b'%d\n' % len(self.replacements))
419 for replacement in self.replacements:
419 for replacement in self.replacements:
420 fp.write(
420 fp.write(
421 b'%s%s\n'
421 b'%s%s\n'
422 % (
422 % (
423 hex(replacement[0]),
423 hex(replacement[0]),
424 b''.join(hex(r) for r in replacement[1]),
424 b''.join(hex(r) for r in replacement[1]),
425 )
425 )
426 )
426 )
427 backupfile = self.backupfile
427 backupfile = self.backupfile
428 if not backupfile:
428 if not backupfile:
429 backupfile = b''
429 backupfile = b''
430 fp.write(b'%s\n' % backupfile)
430 fp.write(b'%s\n' % backupfile)
431
431
432 def _load(self):
432 def _load(self):
433 fp = self.repo.vfs(b'histedit-state', b'r')
433 fp = self.repo.vfs(b'histedit-state', b'r')
434 lines = [l[:-1] for l in fp.readlines()]
434 lines = [l[:-1] for l in fp.readlines()]
435
435
436 index = 0
436 index = 0
437 lines[index] # version number
437 lines[index] # version number
438 index += 1
438 index += 1
439
439
440 parentctxnode = bin(lines[index])
440 parentctxnode = bin(lines[index])
441 index += 1
441 index += 1
442
442
443 topmost = bin(lines[index])
443 topmost = bin(lines[index])
444 index += 1
444 index += 1
445
445
446 keep = lines[index] == b'True'
446 keep = lines[index] == b'True'
447 index += 1
447 index += 1
448
448
449 # Rules
449 # Rules
450 rules = []
450 rules = []
451 rulelen = int(lines[index])
451 rulelen = int(lines[index])
452 index += 1
452 index += 1
453 for i in pycompat.xrange(rulelen):
453 for i in pycompat.xrange(rulelen):
454 ruleaction = lines[index]
454 ruleaction = lines[index]
455 index += 1
455 index += 1
456 rule = lines[index]
456 rule = lines[index]
457 index += 1
457 index += 1
458 rules.append((ruleaction, rule))
458 rules.append((ruleaction, rule))
459
459
460 # Replacements
460 # Replacements
461 replacements = []
461 replacements = []
462 replacementlen = int(lines[index])
462 replacementlen = int(lines[index])
463 index += 1
463 index += 1
464 for i in pycompat.xrange(replacementlen):
464 for i in pycompat.xrange(replacementlen):
465 replacement = lines[index]
465 replacement = lines[index]
466 original = bin(replacement[:40])
466 original = bin(replacement[:40])
467 succ = [
467 succ = [
468 bin(replacement[i : i + 40])
468 bin(replacement[i : i + 40])
469 for i in range(40, len(replacement), 40)
469 for i in range(40, len(replacement), 40)
470 ]
470 ]
471 replacements.append((original, succ))
471 replacements.append((original, succ))
472 index += 1
472 index += 1
473
473
474 backupfile = lines[index]
474 backupfile = lines[index]
475 index += 1
475 index += 1
476
476
477 fp.close()
477 fp.close()
478
478
479 return parentctxnode, rules, keep, topmost, replacements, backupfile
479 return parentctxnode, rules, keep, topmost, replacements, backupfile
480
480
481 def clear(self):
481 def clear(self):
482 if self.inprogress():
482 if self.inprogress():
483 self.repo.vfs.unlink(b'histedit-state')
483 self.repo.vfs.unlink(b'histedit-state')
484
484
485 def inprogress(self):
485 def inprogress(self):
486 return self.repo.vfs.exists(b'histedit-state')
486 return self.repo.vfs.exists(b'histedit-state')
487
487
488
488
489 class histeditaction(object):
489 class histeditaction(object):
490 def __init__(self, state, node):
490 def __init__(self, state, node):
491 self.state = state
491 self.state = state
492 self.repo = state.repo
492 self.repo = state.repo
493 self.node = node
493 self.node = node
494
494
495 @classmethod
495 @classmethod
496 def fromrule(cls, state, rule):
496 def fromrule(cls, state, rule):
497 """Parses the given rule, returning an instance of the histeditaction."""
497 """Parses the given rule, returning an instance of the histeditaction."""
498 ruleid = rule.strip().split(b' ', 1)[0]
498 ruleid = rule.strip().split(b' ', 1)[0]
499 # ruleid can be anything from rev numbers, hashes, "bookmarks" etc
499 # ruleid can be anything from rev numbers, hashes, "bookmarks" etc
500 # Check for validation of rule ids and get the rulehash
500 # Check for validation of rule ids and get the rulehash
501 try:
501 try:
502 rev = bin(ruleid)
502 rev = bin(ruleid)
503 except TypeError:
503 except TypeError:
504 try:
504 try:
505 _ctx = scmutil.revsingle(state.repo, ruleid)
505 _ctx = scmutil.revsingle(state.repo, ruleid)
506 rulehash = _ctx.hex()
506 rulehash = _ctx.hex()
507 rev = bin(rulehash)
507 rev = bin(rulehash)
508 except error.RepoLookupError:
508 except error.RepoLookupError:
509 raise error.ParseError(_(b"invalid changeset %s") % ruleid)
509 raise error.ParseError(_(b"invalid changeset %s") % ruleid)
510 return cls(state, rev)
510 return cls(state, rev)
511
511
512 def verify(self, prev, expected, seen):
512 def verify(self, prev, expected, seen):
513 """Verifies semantic correctness of the rule"""
513 """Verifies semantic correctness of the rule"""
514 repo = self.repo
514 repo = self.repo
515 ha = hex(self.node)
515 ha = hex(self.node)
516 self.node = scmutil.resolvehexnodeidprefix(repo, ha)
516 self.node = scmutil.resolvehexnodeidprefix(repo, ha)
517 if self.node is None:
517 if self.node is None:
518 raise error.ParseError(_(b'unknown changeset %s listed') % ha[:12])
518 raise error.ParseError(_(b'unknown changeset %s listed') % ha[:12])
519 self._verifynodeconstraints(prev, expected, seen)
519 self._verifynodeconstraints(prev, expected, seen)
520
520
521 def _verifynodeconstraints(self, prev, expected, seen):
521 def _verifynodeconstraints(self, prev, expected, seen):
522 # by default command need a node in the edited list
522 # by default command need a node in the edited list
523 if self.node not in expected:
523 if self.node not in expected:
524 raise error.ParseError(
524 raise error.ParseError(
525 _(b'%s "%s" changeset was not a candidate')
525 _(b'%s "%s" changeset was not a candidate')
526 % (self.verb, short(self.node)),
526 % (self.verb, short(self.node)),
527 hint=_(b'only use listed changesets'),
527 hint=_(b'only use listed changesets'),
528 )
528 )
529 # and only one command per node
529 # and only one command per node
530 if self.node in seen:
530 if self.node in seen:
531 raise error.ParseError(
531 raise error.ParseError(
532 _(b'duplicated command for changeset %s') % short(self.node)
532 _(b'duplicated command for changeset %s') % short(self.node)
533 )
533 )
534
534
535 def torule(self):
535 def torule(self):
536 """build a histedit rule line for an action
536 """build a histedit rule line for an action
537
537
538 by default lines are in the form:
538 by default lines are in the form:
539 <hash> <rev> <summary>
539 <hash> <rev> <summary>
540 """
540 """
541 ctx = self.repo[self.node]
541 ctx = self.repo[self.node]
542 ui = self.repo.ui
542 ui = self.repo.ui
543 # We don't want color codes in the commit message template, so
543 # We don't want color codes in the commit message template, so
544 # disable the label() template function while we render it.
544 # disable the label() template function while we render it.
545 with ui.configoverride(
545 with ui.configoverride(
546 {(b'templatealias', b'label(l,x)'): b"x"}, b'histedit'
546 {(b'templatealias', b'label(l,x)'): b"x"}, b'histedit'
547 ):
547 ):
548 summary = cmdutil.rendertemplate(
548 summary = cmdutil.rendertemplate(
549 ctx, ui.config(b'histedit', b'summary-template')
549 ctx, ui.config(b'histedit', b'summary-template')
550 )
550 )
551 # Handle the fact that `''.splitlines() => []`
551 # Handle the fact that `''.splitlines() => []`
552 summary = summary.splitlines()[0] if summary else b''
552 summary = summary.splitlines()[0] if summary else b''
553 line = b'%s %s %s' % (self.verb, ctx, summary)
553 line = b'%s %s %s' % (self.verb, ctx, summary)
554 # trim to 75 columns by default so it's not stupidly wide in my editor
554 # trim to 75 columns by default so it's not stupidly wide in my editor
555 # (the 5 more are left for verb)
555 # (the 5 more are left for verb)
556 maxlen = self.repo.ui.configint(b'histedit', b'linelen')
556 maxlen = self.repo.ui.configint(b'histedit', b'linelen')
557 maxlen = max(maxlen, 22) # avoid truncating hash
557 maxlen = max(maxlen, 22) # avoid truncating hash
558 return stringutil.ellipsis(line, maxlen)
558 return stringutil.ellipsis(line, maxlen)
559
559
560 def tostate(self):
560 def tostate(self):
561 """Print an action in format used by histedit state files
561 """Print an action in format used by histedit state files
562 (the first line is a verb, the remainder is the second)
562 (the first line is a verb, the remainder is the second)
563 """
563 """
564 return b"%s\n%s" % (self.verb, hex(self.node))
564 return b"%s\n%s" % (self.verb, hex(self.node))
565
565
566 def run(self):
566 def run(self):
567 """Runs the action. The default behavior is simply apply the action's
567 """Runs the action. The default behavior is simply apply the action's
568 rulectx onto the current parentctx."""
568 rulectx onto the current parentctx."""
569 self.applychange()
569 self.applychange()
570 self.continuedirty()
570 self.continuedirty()
571 return self.continueclean()
571 return self.continueclean()
572
572
573 def applychange(self):
573 def applychange(self):
574 """Applies the changes from this action's rulectx onto the current
574 """Applies the changes from this action's rulectx onto the current
575 parentctx, but does not commit them."""
575 parentctx, but does not commit them."""
576 repo = self.repo
576 repo = self.repo
577 rulectx = repo[self.node]
577 rulectx = repo[self.node]
578 with repo.ui.silent():
578 with repo.ui.silent():
579 hg.update(repo, self.state.parentctxnode, quietempty=True)
579 hg.update(repo, self.state.parentctxnode, quietempty=True)
580 stats = applychanges(repo.ui, repo, rulectx, {})
580 stats = applychanges(repo.ui, repo, rulectx, {})
581 repo.dirstate.setbranch(rulectx.branch())
581 repo.dirstate.setbranch(rulectx.branch())
582 if stats.unresolvedcount:
582 if stats.unresolvedcount:
583 raise error.InterventionRequired(
583 raise error.InterventionRequired(
584 _(b'Fix up the change (%s %s)') % (self.verb, short(self.node)),
584 _(b'Fix up the change (%s %s)') % (self.verb, short(self.node)),
585 hint=_(b'hg histedit --continue to resume'),
585 hint=_(b'hg histedit --continue to resume'),
586 )
586 )
587
587
588 def continuedirty(self):
588 def continuedirty(self):
589 """Continues the action when changes have been applied to the working
589 """Continues the action when changes have been applied to the working
590 copy. The default behavior is to commit the dirty changes."""
590 copy. The default behavior is to commit the dirty changes."""
591 repo = self.repo
591 repo = self.repo
592 rulectx = repo[self.node]
592 rulectx = repo[self.node]
593
593
594 editor = self.commiteditor()
594 editor = self.commiteditor()
595 commit = commitfuncfor(repo, rulectx)
595 commit = commitfuncfor(repo, rulectx)
596 if repo.ui.configbool(b'rewrite', b'update-timestamp'):
596 if repo.ui.configbool(b'rewrite', b'update-timestamp'):
597 date = dateutil.makedate()
597 date = dateutil.makedate()
598 else:
598 else:
599 date = rulectx.date()
599 date = rulectx.date()
600 commit(
600 commit(
601 text=rulectx.description(),
601 text=rulectx.description(),
602 user=rulectx.user(),
602 user=rulectx.user(),
603 date=date,
603 date=date,
604 extra=rulectx.extra(),
604 extra=rulectx.extra(),
605 editor=editor,
605 editor=editor,
606 )
606 )
607
607
608 def commiteditor(self):
608 def commiteditor(self):
609 """The editor to be used to edit the commit message."""
609 """The editor to be used to edit the commit message."""
610 return False
610 return False
611
611
612 def continueclean(self):
612 def continueclean(self):
613 """Continues the action when the working copy is clean. The default
613 """Continues the action when the working copy is clean. The default
614 behavior is to accept the current commit as the new version of the
614 behavior is to accept the current commit as the new version of the
615 rulectx."""
615 rulectx."""
616 ctx = self.repo[b'.']
616 ctx = self.repo[b'.']
617 if ctx.node() == self.state.parentctxnode:
617 if ctx.node() == self.state.parentctxnode:
618 self.repo.ui.warn(
618 self.repo.ui.warn(
619 _(b'%s: skipping changeset (no changes)\n') % short(self.node)
619 _(b'%s: skipping changeset (no changes)\n') % short(self.node)
620 )
620 )
621 return ctx, [(self.node, tuple())]
621 return ctx, [(self.node, tuple())]
622 if ctx.node() == self.node:
622 if ctx.node() == self.node:
623 # Nothing changed
623 # Nothing changed
624 return ctx, []
624 return ctx, []
625 return ctx, [(self.node, (ctx.node(),))]
625 return ctx, [(self.node, (ctx.node(),))]
626
626
627
627
628 def commitfuncfor(repo, src):
628 def commitfuncfor(repo, src):
629 """Build a commit function for the replacement of <src>
629 """Build a commit function for the replacement of <src>
630
630
631 This function ensure we apply the same treatment to all changesets.
631 This function ensure we apply the same treatment to all changesets.
632
632
633 - Add a 'histedit_source' entry in extra.
633 - Add a 'histedit_source' entry in extra.
634
634
635 Note that fold has its own separated logic because its handling is a bit
635 Note that fold has its own separated logic because its handling is a bit
636 different and not easily factored out of the fold method.
636 different and not easily factored out of the fold method.
637 """
637 """
638 phasemin = src.phase()
638 phasemin = src.phase()
639
639
640 def commitfunc(**kwargs):
640 def commitfunc(**kwargs):
641 overrides = {(b'phases', b'new-commit'): phasemin}
641 overrides = {(b'phases', b'new-commit'): phasemin}
642 with repo.ui.configoverride(overrides, b'histedit'):
642 with repo.ui.configoverride(overrides, b'histedit'):
643 extra = kwargs.get('extra', {}).copy()
643 extra = kwargs.get('extra', {}).copy()
644 extra[b'histedit_source'] = src.hex()
644 extra[b'histedit_source'] = src.hex()
645 kwargs['extra'] = extra
645 kwargs['extra'] = extra
646 return repo.commit(**kwargs)
646 return repo.commit(**kwargs)
647
647
648 return commitfunc
648 return commitfunc
649
649
650
650
651 def applychanges(ui, repo, ctx, opts):
651 def applychanges(ui, repo, ctx, opts):
652 """Merge changeset from ctx (only) in the current working directory"""
652 """Merge changeset from ctx (only) in the current working directory"""
653 if ctx.p1().node() == repo.dirstate.p1():
653 if ctx.p1().node() == repo.dirstate.p1():
654 # edits are "in place" we do not need to make any merge,
654 # edits are "in place" we do not need to make any merge,
655 # just applies changes on parent for editing
655 # just applies changes on parent for editing
656 with ui.silent():
656 with ui.silent():
657 cmdutil.revert(ui, repo, ctx, all=True)
657 cmdutil.revert(ui, repo, ctx, all=True)
658 stats = mergemod.updateresult(0, 0, 0, 0)
658 stats = mergemod.updateresult(0, 0, 0, 0)
659 else:
659 else:
660 try:
660 try:
661 # ui.forcemerge is an internal variable, do not document
661 # ui.forcemerge is an internal variable, do not document
662 repo.ui.setconfig(
662 repo.ui.setconfig(
663 b'ui', b'forcemerge', opts.get(b'tool', b''), b'histedit'
663 b'ui', b'forcemerge', opts.get(b'tool', b''), b'histedit'
664 )
664 )
665 stats = mergemod.graft(repo, ctx, labels=[b'local', b'histedit'])
665 stats = mergemod.graft(repo, ctx, labels=[b'local', b'histedit'])
666 finally:
666 finally:
667 repo.ui.setconfig(b'ui', b'forcemerge', b'', b'histedit')
667 repo.ui.setconfig(b'ui', b'forcemerge', b'', b'histedit')
668 return stats
668 return stats
669
669
670
670
671 def collapse(repo, firstctx, lastctx, commitopts, skipprompt=False):
671 def collapse(repo, firstctx, lastctx, commitopts, skipprompt=False):
672 """collapse the set of revisions from first to last as new one.
672 """collapse the set of revisions from first to last as new one.
673
673
674 Expected commit options are:
674 Expected commit options are:
675 - message
675 - message
676 - date
676 - date
677 - username
677 - username
678 Commit message is edited in all cases.
678 Commit message is edited in all cases.
679
679
680 This function works in memory."""
680 This function works in memory."""
681 ctxs = list(repo.set(b'%d::%d', firstctx.rev(), lastctx.rev()))
681 ctxs = list(repo.set(b'%d::%d', firstctx.rev(), lastctx.rev()))
682 if not ctxs:
682 if not ctxs:
683 return None
683 return None
684 for c in ctxs:
684 for c in ctxs:
685 if not c.mutable():
685 if not c.mutable():
686 raise error.ParseError(
686 raise error.ParseError(
687 _(b"cannot fold into public change %s") % short(c.node())
687 _(b"cannot fold into public change %s") % short(c.node())
688 )
688 )
689 base = firstctx.p1()
689 base = firstctx.p1()
690
690
691 # commit a new version of the old changeset, including the update
691 # commit a new version of the old changeset, including the update
692 # collect all files which might be affected
692 # collect all files which might be affected
693 files = set()
693 files = set()
694 for ctx in ctxs:
694 for ctx in ctxs:
695 files.update(ctx.files())
695 files.update(ctx.files())
696
696
697 # Recompute copies (avoid recording a -> b -> a)
697 # Recompute copies (avoid recording a -> b -> a)
698 copied = copies.pathcopies(base, lastctx)
698 copied = copies.pathcopies(base, lastctx)
699
699
700 # prune files which were reverted by the updates
700 # prune files which were reverted by the updates
701 files = [f for f in files if not cmdutil.samefile(f, lastctx, base)]
701 files = [f for f in files if not cmdutil.samefile(f, lastctx, base)]
702 # commit version of these files as defined by head
702 # commit version of these files as defined by head
703 headmf = lastctx.manifest()
703 headmf = lastctx.manifest()
704
704
705 def filectxfn(repo, ctx, path):
705 def filectxfn(repo, ctx, path):
706 if path in headmf:
706 if path in headmf:
707 fctx = lastctx[path]
707 fctx = lastctx[path]
708 flags = fctx.flags()
708 flags = fctx.flags()
709 mctx = context.memfilectx(
709 mctx = context.memfilectx(
710 repo,
710 repo,
711 ctx,
711 ctx,
712 fctx.path(),
712 fctx.path(),
713 fctx.data(),
713 fctx.data(),
714 islink=b'l' in flags,
714 islink=b'l' in flags,
715 isexec=b'x' in flags,
715 isexec=b'x' in flags,
716 copysource=copied.get(path),
716 copysource=copied.get(path),
717 )
717 )
718 return mctx
718 return mctx
719 return None
719 return None
720
720
721 if commitopts.get(b'message'):
721 if commitopts.get(b'message'):
722 message = commitopts[b'message']
722 message = commitopts[b'message']
723 else:
723 else:
724 message = firstctx.description()
724 message = firstctx.description()
725 user = commitopts.get(b'user')
725 user = commitopts.get(b'user')
726 date = commitopts.get(b'date')
726 date = commitopts.get(b'date')
727 extra = commitopts.get(b'extra')
727 extra = commitopts.get(b'extra')
728
728
729 parents = (firstctx.p1().node(), firstctx.p2().node())
729 parents = (firstctx.p1().node(), firstctx.p2().node())
730 editor = None
730 editor = None
731 if not skipprompt:
731 if not skipprompt:
732 editor = cmdutil.getcommiteditor(edit=True, editform=b'histedit.fold')
732 editor = cmdutil.getcommiteditor(edit=True, editform=b'histedit.fold')
733 new = context.memctx(
733 new = context.memctx(
734 repo,
734 repo,
735 parents=parents,
735 parents=parents,
736 text=message,
736 text=message,
737 files=files,
737 files=files,
738 filectxfn=filectxfn,
738 filectxfn=filectxfn,
739 user=user,
739 user=user,
740 date=date,
740 date=date,
741 extra=extra,
741 extra=extra,
742 editor=editor,
742 editor=editor,
743 )
743 )
744 return repo.commitctx(new)
744 return repo.commitctx(new)
745
745
746
746
747 def _isdirtywc(repo):
747 def _isdirtywc(repo):
748 return repo[None].dirty(missing=True)
748 return repo[None].dirty(missing=True)
749
749
750
750
751 def abortdirty():
751 def abortdirty():
752 raise error.Abort(
752 raise error.StateError(
753 _(b'working copy has pending changes'),
753 _(b'working copy has pending changes'),
754 hint=_(
754 hint=_(
755 b'amend, commit, or revert them and run histedit '
755 b'amend, commit, or revert them and run histedit '
756 b'--continue, or abort with histedit --abort'
756 b'--continue, or abort with histedit --abort'
757 ),
757 ),
758 )
758 )
759
759
760
760
761 def action(verbs, message, priority=False, internal=False):
761 def action(verbs, message, priority=False, internal=False):
762 def wrap(cls):
762 def wrap(cls):
763 assert not priority or not internal
763 assert not priority or not internal
764 verb = verbs[0]
764 verb = verbs[0]
765 if priority:
765 if priority:
766 primaryactions.add(verb)
766 primaryactions.add(verb)
767 elif internal:
767 elif internal:
768 internalactions.add(verb)
768 internalactions.add(verb)
769 elif len(verbs) > 1:
769 elif len(verbs) > 1:
770 secondaryactions.add(verb)
770 secondaryactions.add(verb)
771 else:
771 else:
772 tertiaryactions.add(verb)
772 tertiaryactions.add(verb)
773
773
774 cls.verb = verb
774 cls.verb = verb
775 cls.verbs = verbs
775 cls.verbs = verbs
776 cls.message = message
776 cls.message = message
777 for verb in verbs:
777 for verb in verbs:
778 actiontable[verb] = cls
778 actiontable[verb] = cls
779 return cls
779 return cls
780
780
781 return wrap
781 return wrap
782
782
783
783
784 @action([b'pick', b'p'], _(b'use commit'), priority=True)
784 @action([b'pick', b'p'], _(b'use commit'), priority=True)
785 class pick(histeditaction):
785 class pick(histeditaction):
786 def run(self):
786 def run(self):
787 rulectx = self.repo[self.node]
787 rulectx = self.repo[self.node]
788 if rulectx.p1().node() == self.state.parentctxnode:
788 if rulectx.p1().node() == self.state.parentctxnode:
789 self.repo.ui.debug(b'node %s unchanged\n' % short(self.node))
789 self.repo.ui.debug(b'node %s unchanged\n' % short(self.node))
790 return rulectx, []
790 return rulectx, []
791
791
792 return super(pick, self).run()
792 return super(pick, self).run()
793
793
794
794
795 @action(
795 @action(
796 [b'edit', b'e'],
796 [b'edit', b'e'],
797 _(b'use commit, but allow edits before making new commit'),
797 _(b'use commit, but allow edits before making new commit'),
798 priority=True,
798 priority=True,
799 )
799 )
800 class edit(histeditaction):
800 class edit(histeditaction):
801 def run(self):
801 def run(self):
802 repo = self.repo
802 repo = self.repo
803 rulectx = repo[self.node]
803 rulectx = repo[self.node]
804 hg.update(repo, self.state.parentctxnode, quietempty=True)
804 hg.update(repo, self.state.parentctxnode, quietempty=True)
805 applychanges(repo.ui, repo, rulectx, {})
805 applychanges(repo.ui, repo, rulectx, {})
806 hint = _(b'to edit %s, `hg histedit --continue` after making changes')
806 hint = _(b'to edit %s, `hg histedit --continue` after making changes')
807 raise error.InterventionRequired(
807 raise error.InterventionRequired(
808 _(b'Editing (%s), commit as needed now to split the change')
808 _(b'Editing (%s), commit as needed now to split the change')
809 % short(self.node),
809 % short(self.node),
810 hint=hint % short(self.node),
810 hint=hint % short(self.node),
811 )
811 )
812
812
813 def commiteditor(self):
813 def commiteditor(self):
814 return cmdutil.getcommiteditor(edit=True, editform=b'histedit.edit')
814 return cmdutil.getcommiteditor(edit=True, editform=b'histedit.edit')
815
815
816
816
817 @action([b'fold', b'f'], _(b'use commit, but combine it with the one above'))
817 @action([b'fold', b'f'], _(b'use commit, but combine it with the one above'))
818 class fold(histeditaction):
818 class fold(histeditaction):
819 def verify(self, prev, expected, seen):
819 def verify(self, prev, expected, seen):
820 """Verifies semantic correctness of the fold rule"""
820 """Verifies semantic correctness of the fold rule"""
821 super(fold, self).verify(prev, expected, seen)
821 super(fold, self).verify(prev, expected, seen)
822 repo = self.repo
822 repo = self.repo
823 if not prev:
823 if not prev:
824 c = repo[self.node].p1()
824 c = repo[self.node].p1()
825 elif not prev.verb in (b'pick', b'base'):
825 elif not prev.verb in (b'pick', b'base'):
826 return
826 return
827 else:
827 else:
828 c = repo[prev.node]
828 c = repo[prev.node]
829 if not c.mutable():
829 if not c.mutable():
830 raise error.ParseError(
830 raise error.ParseError(
831 _(b"cannot fold into public change %s") % short(c.node())
831 _(b"cannot fold into public change %s") % short(c.node())
832 )
832 )
833
833
834 def continuedirty(self):
834 def continuedirty(self):
835 repo = self.repo
835 repo = self.repo
836 rulectx = repo[self.node]
836 rulectx = repo[self.node]
837
837
838 commit = commitfuncfor(repo, rulectx)
838 commit = commitfuncfor(repo, rulectx)
839 commit(
839 commit(
840 text=b'fold-temp-revision %s' % short(self.node),
840 text=b'fold-temp-revision %s' % short(self.node),
841 user=rulectx.user(),
841 user=rulectx.user(),
842 date=rulectx.date(),
842 date=rulectx.date(),
843 extra=rulectx.extra(),
843 extra=rulectx.extra(),
844 )
844 )
845
845
846 def continueclean(self):
846 def continueclean(self):
847 repo = self.repo
847 repo = self.repo
848 ctx = repo[b'.']
848 ctx = repo[b'.']
849 rulectx = repo[self.node]
849 rulectx = repo[self.node]
850 parentctxnode = self.state.parentctxnode
850 parentctxnode = self.state.parentctxnode
851 if ctx.node() == parentctxnode:
851 if ctx.node() == parentctxnode:
852 repo.ui.warn(_(b'%s: empty changeset\n') % short(self.node))
852 repo.ui.warn(_(b'%s: empty changeset\n') % short(self.node))
853 return ctx, [(self.node, (parentctxnode,))]
853 return ctx, [(self.node, (parentctxnode,))]
854
854
855 parentctx = repo[parentctxnode]
855 parentctx = repo[parentctxnode]
856 newcommits = {
856 newcommits = {
857 c.node()
857 c.node()
858 for c in repo.set(b'(%d::. - %d)', parentctx.rev(), parentctx.rev())
858 for c in repo.set(b'(%d::. - %d)', parentctx.rev(), parentctx.rev())
859 }
859 }
860 if not newcommits:
860 if not newcommits:
861 repo.ui.warn(
861 repo.ui.warn(
862 _(
862 _(
863 b'%s: cannot fold - working copy is not a '
863 b'%s: cannot fold - working copy is not a '
864 b'descendant of previous commit %s\n'
864 b'descendant of previous commit %s\n'
865 )
865 )
866 % (short(self.node), short(parentctxnode))
866 % (short(self.node), short(parentctxnode))
867 )
867 )
868 return ctx, [(self.node, (ctx.node(),))]
868 return ctx, [(self.node, (ctx.node(),))]
869
869
870 middlecommits = newcommits.copy()
870 middlecommits = newcommits.copy()
871 middlecommits.discard(ctx.node())
871 middlecommits.discard(ctx.node())
872
872
873 return self.finishfold(
873 return self.finishfold(
874 repo.ui, repo, parentctx, rulectx, ctx.node(), middlecommits
874 repo.ui, repo, parentctx, rulectx, ctx.node(), middlecommits
875 )
875 )
876
876
877 def skipprompt(self):
877 def skipprompt(self):
878 """Returns true if the rule should skip the message editor.
878 """Returns true if the rule should skip the message editor.
879
879
880 For example, 'fold' wants to show an editor, but 'rollup'
880 For example, 'fold' wants to show an editor, but 'rollup'
881 doesn't want to.
881 doesn't want to.
882 """
882 """
883 return False
883 return False
884
884
885 def mergedescs(self):
885 def mergedescs(self):
886 """Returns true if the rule should merge messages of multiple changes.
886 """Returns true if the rule should merge messages of multiple changes.
887
887
888 This exists mainly so that 'rollup' rules can be a subclass of
888 This exists mainly so that 'rollup' rules can be a subclass of
889 'fold'.
889 'fold'.
890 """
890 """
891 return True
891 return True
892
892
893 def firstdate(self):
893 def firstdate(self):
894 """Returns true if the rule should preserve the date of the first
894 """Returns true if the rule should preserve the date of the first
895 change.
895 change.
896
896
897 This exists mainly so that 'rollup' rules can be a subclass of
897 This exists mainly so that 'rollup' rules can be a subclass of
898 'fold'.
898 'fold'.
899 """
899 """
900 return False
900 return False
901
901
902 def finishfold(self, ui, repo, ctx, oldctx, newnode, internalchanges):
902 def finishfold(self, ui, repo, ctx, oldctx, newnode, internalchanges):
903 mergemod.update(ctx.p1())
903 mergemod.update(ctx.p1())
904 ### prepare new commit data
904 ### prepare new commit data
905 commitopts = {}
905 commitopts = {}
906 commitopts[b'user'] = ctx.user()
906 commitopts[b'user'] = ctx.user()
907 # commit message
907 # commit message
908 if not self.mergedescs():
908 if not self.mergedescs():
909 newmessage = ctx.description()
909 newmessage = ctx.description()
910 else:
910 else:
911 newmessage = (
911 newmessage = (
912 b'\n***\n'.join(
912 b'\n***\n'.join(
913 [ctx.description()]
913 [ctx.description()]
914 + [repo[r].description() for r in internalchanges]
914 + [repo[r].description() for r in internalchanges]
915 + [oldctx.description()]
915 + [oldctx.description()]
916 )
916 )
917 + b'\n'
917 + b'\n'
918 )
918 )
919 commitopts[b'message'] = newmessage
919 commitopts[b'message'] = newmessage
920 # date
920 # date
921 if self.firstdate():
921 if self.firstdate():
922 commitopts[b'date'] = ctx.date()
922 commitopts[b'date'] = ctx.date()
923 else:
923 else:
924 commitopts[b'date'] = max(ctx.date(), oldctx.date())
924 commitopts[b'date'] = max(ctx.date(), oldctx.date())
925 # if date is to be updated to current
925 # if date is to be updated to current
926 if ui.configbool(b'rewrite', b'update-timestamp'):
926 if ui.configbool(b'rewrite', b'update-timestamp'):
927 commitopts[b'date'] = dateutil.makedate()
927 commitopts[b'date'] = dateutil.makedate()
928
928
929 extra = ctx.extra().copy()
929 extra = ctx.extra().copy()
930 # histedit_source
930 # histedit_source
931 # note: ctx is likely a temporary commit but that the best we can do
931 # note: ctx is likely a temporary commit but that the best we can do
932 # here. This is sufficient to solve issue3681 anyway.
932 # here. This is sufficient to solve issue3681 anyway.
933 extra[b'histedit_source'] = b'%s,%s' % (ctx.hex(), oldctx.hex())
933 extra[b'histedit_source'] = b'%s,%s' % (ctx.hex(), oldctx.hex())
934 commitopts[b'extra'] = extra
934 commitopts[b'extra'] = extra
935 phasemin = max(ctx.phase(), oldctx.phase())
935 phasemin = max(ctx.phase(), oldctx.phase())
936 overrides = {(b'phases', b'new-commit'): phasemin}
936 overrides = {(b'phases', b'new-commit'): phasemin}
937 with repo.ui.configoverride(overrides, b'histedit'):
937 with repo.ui.configoverride(overrides, b'histedit'):
938 n = collapse(
938 n = collapse(
939 repo,
939 repo,
940 ctx,
940 ctx,
941 repo[newnode],
941 repo[newnode],
942 commitopts,
942 commitopts,
943 skipprompt=self.skipprompt(),
943 skipprompt=self.skipprompt(),
944 )
944 )
945 if n is None:
945 if n is None:
946 return ctx, []
946 return ctx, []
947 mergemod.update(repo[n])
947 mergemod.update(repo[n])
948 replacements = [
948 replacements = [
949 (oldctx.node(), (newnode,)),
949 (oldctx.node(), (newnode,)),
950 (ctx.node(), (n,)),
950 (ctx.node(), (n,)),
951 (newnode, (n,)),
951 (newnode, (n,)),
952 ]
952 ]
953 for ich in internalchanges:
953 for ich in internalchanges:
954 replacements.append((ich, (n,)))
954 replacements.append((ich, (n,)))
955 return repo[n], replacements
955 return repo[n], replacements
956
956
957
957
958 @action(
958 @action(
959 [b'base', b'b'],
959 [b'base', b'b'],
960 _(b'checkout changeset and apply further changesets from there'),
960 _(b'checkout changeset and apply further changesets from there'),
961 )
961 )
962 class base(histeditaction):
962 class base(histeditaction):
963 def run(self):
963 def run(self):
964 if self.repo[b'.'].node() != self.node:
964 if self.repo[b'.'].node() != self.node:
965 mergemod.clean_update(self.repo[self.node])
965 mergemod.clean_update(self.repo[self.node])
966 return self.continueclean()
966 return self.continueclean()
967
967
968 def continuedirty(self):
968 def continuedirty(self):
969 abortdirty()
969 abortdirty()
970
970
971 def continueclean(self):
971 def continueclean(self):
972 basectx = self.repo[b'.']
972 basectx = self.repo[b'.']
973 return basectx, []
973 return basectx, []
974
974
975 def _verifynodeconstraints(self, prev, expected, seen):
975 def _verifynodeconstraints(self, prev, expected, seen):
976 # base can only be use with a node not in the edited set
976 # base can only be use with a node not in the edited set
977 if self.node in expected:
977 if self.node in expected:
978 msg = _(b'%s "%s" changeset was an edited list candidate')
978 msg = _(b'%s "%s" changeset was an edited list candidate')
979 raise error.ParseError(
979 raise error.ParseError(
980 msg % (self.verb, short(self.node)),
980 msg % (self.verb, short(self.node)),
981 hint=_(b'base must only use unlisted changesets'),
981 hint=_(b'base must only use unlisted changesets'),
982 )
982 )
983
983
984
984
985 @action(
985 @action(
986 [b'_multifold'],
986 [b'_multifold'],
987 _(
987 _(
988 """fold subclass used for when multiple folds happen in a row
988 """fold subclass used for when multiple folds happen in a row
989
989
990 We only want to fire the editor for the folded message once when
990 We only want to fire the editor for the folded message once when
991 (say) four changes are folded down into a single change. This is
991 (say) four changes are folded down into a single change. This is
992 similar to rollup, but we should preserve both messages so that
992 similar to rollup, but we should preserve both messages so that
993 when the last fold operation runs we can show the user all the
993 when the last fold operation runs we can show the user all the
994 commit messages in their editor.
994 commit messages in their editor.
995 """
995 """
996 ),
996 ),
997 internal=True,
997 internal=True,
998 )
998 )
999 class _multifold(fold):
999 class _multifold(fold):
1000 def skipprompt(self):
1000 def skipprompt(self):
1001 return True
1001 return True
1002
1002
1003
1003
1004 @action(
1004 @action(
1005 [b"roll", b"r"],
1005 [b"roll", b"r"],
1006 _(b"like fold, but discard this commit's description and date"),
1006 _(b"like fold, but discard this commit's description and date"),
1007 )
1007 )
1008 class rollup(fold):
1008 class rollup(fold):
1009 def mergedescs(self):
1009 def mergedescs(self):
1010 return False
1010 return False
1011
1011
1012 def skipprompt(self):
1012 def skipprompt(self):
1013 return True
1013 return True
1014
1014
1015 def firstdate(self):
1015 def firstdate(self):
1016 return True
1016 return True
1017
1017
1018
1018
1019 @action([b"drop", b"d"], _(b'remove commit from history'))
1019 @action([b"drop", b"d"], _(b'remove commit from history'))
1020 class drop(histeditaction):
1020 class drop(histeditaction):
1021 def run(self):
1021 def run(self):
1022 parentctx = self.repo[self.state.parentctxnode]
1022 parentctx = self.repo[self.state.parentctxnode]
1023 return parentctx, [(self.node, tuple())]
1023 return parentctx, [(self.node, tuple())]
1024
1024
1025
1025
1026 @action(
1026 @action(
1027 [b"mess", b"m"],
1027 [b"mess", b"m"],
1028 _(b'edit commit message without changing commit content'),
1028 _(b'edit commit message without changing commit content'),
1029 priority=True,
1029 priority=True,
1030 )
1030 )
1031 class message(histeditaction):
1031 class message(histeditaction):
1032 def commiteditor(self):
1032 def commiteditor(self):
1033 return cmdutil.getcommiteditor(edit=True, editform=b'histedit.mess')
1033 return cmdutil.getcommiteditor(edit=True, editform=b'histedit.mess')
1034
1034
1035
1035
1036 def findoutgoing(ui, repo, remote=None, force=False, opts=None):
1036 def findoutgoing(ui, repo, remote=None, force=False, opts=None):
1037 """utility function to find the first outgoing changeset
1037 """utility function to find the first outgoing changeset
1038
1038
1039 Used by initialization code"""
1039 Used by initialization code"""
1040 if opts is None:
1040 if opts is None:
1041 opts = {}
1041 opts = {}
1042 path = urlutil.get_unique_push_path(b'histedit', repo, ui, remote)
1042 path = urlutil.get_unique_push_path(b'histedit', repo, ui, remote)
1043 dest = path.pushloc or path.loc
1043 dest = path.pushloc or path.loc
1044
1044
1045 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(dest))
1045 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(dest))
1046
1046
1047 revs, checkout = hg.addbranchrevs(repo, repo, (path.branch, []), None)
1047 revs, checkout = hg.addbranchrevs(repo, repo, (path.branch, []), None)
1048 other = hg.peer(repo, opts, dest)
1048 other = hg.peer(repo, opts, dest)
1049
1049
1050 if revs:
1050 if revs:
1051 revs = [repo.lookup(rev) for rev in revs]
1051 revs = [repo.lookup(rev) for rev in revs]
1052
1052
1053 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
1053 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
1054 if not outgoing.missing:
1054 if not outgoing.missing:
1055 raise error.Abort(_(b'no outgoing ancestors'))
1055 raise error.StateError(_(b'no outgoing ancestors'))
1056 roots = list(repo.revs(b"roots(%ln)", outgoing.missing))
1056 roots = list(repo.revs(b"roots(%ln)", outgoing.missing))
1057 if len(roots) > 1:
1057 if len(roots) > 1:
1058 msg = _(b'there are ambiguous outgoing revisions')
1058 msg = _(b'there are ambiguous outgoing revisions')
1059 hint = _(b"see 'hg help histedit' for more detail")
1059 hint = _(b"see 'hg help histedit' for more detail")
1060 raise error.Abort(msg, hint=hint)
1060 raise error.StateError(msg, hint=hint)
1061 return repo[roots[0]].node()
1061 return repo[roots[0]].node()
1062
1062
1063
1063
1064 # Curses Support
1064 # Curses Support
1065 try:
1065 try:
1066 import curses
1066 import curses
1067 except ImportError:
1067 except ImportError:
1068 curses = None
1068 curses = None
1069
1069
1070 KEY_LIST = [b'pick', b'edit', b'fold', b'drop', b'mess', b'roll']
1070 KEY_LIST = [b'pick', b'edit', b'fold', b'drop', b'mess', b'roll']
1071 ACTION_LABELS = {
1071 ACTION_LABELS = {
1072 b'fold': b'^fold',
1072 b'fold': b'^fold',
1073 b'roll': b'^roll',
1073 b'roll': b'^roll',
1074 }
1074 }
1075
1075
1076 COLOR_HELP, COLOR_SELECTED, COLOR_OK, COLOR_WARN, COLOR_CURRENT = 1, 2, 3, 4, 5
1076 COLOR_HELP, COLOR_SELECTED, COLOR_OK, COLOR_WARN, COLOR_CURRENT = 1, 2, 3, 4, 5
1077 COLOR_DIFF_ADD_LINE, COLOR_DIFF_DEL_LINE, COLOR_DIFF_OFFSET = 6, 7, 8
1077 COLOR_DIFF_ADD_LINE, COLOR_DIFF_DEL_LINE, COLOR_DIFF_OFFSET = 6, 7, 8
1078 COLOR_ROLL, COLOR_ROLL_CURRENT, COLOR_ROLL_SELECTED = 9, 10, 11
1078 COLOR_ROLL, COLOR_ROLL_CURRENT, COLOR_ROLL_SELECTED = 9, 10, 11
1079
1079
1080 E_QUIT, E_HISTEDIT = 1, 2
1080 E_QUIT, E_HISTEDIT = 1, 2
1081 E_PAGEDOWN, E_PAGEUP, E_LINEUP, E_LINEDOWN, E_RESIZE = 3, 4, 5, 6, 7
1081 E_PAGEDOWN, E_PAGEUP, E_LINEUP, E_LINEDOWN, E_RESIZE = 3, 4, 5, 6, 7
1082 MODE_INIT, MODE_PATCH, MODE_RULES, MODE_HELP = 0, 1, 2, 3
1082 MODE_INIT, MODE_PATCH, MODE_RULES, MODE_HELP = 0, 1, 2, 3
1083
1083
1084 KEYTABLE = {
1084 KEYTABLE = {
1085 b'global': {
1085 b'global': {
1086 b'h': b'next-action',
1086 b'h': b'next-action',
1087 b'KEY_RIGHT': b'next-action',
1087 b'KEY_RIGHT': b'next-action',
1088 b'l': b'prev-action',
1088 b'l': b'prev-action',
1089 b'KEY_LEFT': b'prev-action',
1089 b'KEY_LEFT': b'prev-action',
1090 b'q': b'quit',
1090 b'q': b'quit',
1091 b'c': b'histedit',
1091 b'c': b'histedit',
1092 b'C': b'histedit',
1092 b'C': b'histedit',
1093 b'v': b'showpatch',
1093 b'v': b'showpatch',
1094 b'?': b'help',
1094 b'?': b'help',
1095 },
1095 },
1096 MODE_RULES: {
1096 MODE_RULES: {
1097 b'd': b'action-drop',
1097 b'd': b'action-drop',
1098 b'e': b'action-edit',
1098 b'e': b'action-edit',
1099 b'f': b'action-fold',
1099 b'f': b'action-fold',
1100 b'm': b'action-mess',
1100 b'm': b'action-mess',
1101 b'p': b'action-pick',
1101 b'p': b'action-pick',
1102 b'r': b'action-roll',
1102 b'r': b'action-roll',
1103 b' ': b'select',
1103 b' ': b'select',
1104 b'j': b'down',
1104 b'j': b'down',
1105 b'k': b'up',
1105 b'k': b'up',
1106 b'KEY_DOWN': b'down',
1106 b'KEY_DOWN': b'down',
1107 b'KEY_UP': b'up',
1107 b'KEY_UP': b'up',
1108 b'J': b'move-down',
1108 b'J': b'move-down',
1109 b'K': b'move-up',
1109 b'K': b'move-up',
1110 b'KEY_NPAGE': b'move-down',
1110 b'KEY_NPAGE': b'move-down',
1111 b'KEY_PPAGE': b'move-up',
1111 b'KEY_PPAGE': b'move-up',
1112 b'0': b'goto', # Used for 0..9
1112 b'0': b'goto', # Used for 0..9
1113 },
1113 },
1114 MODE_PATCH: {
1114 MODE_PATCH: {
1115 b' ': b'page-down',
1115 b' ': b'page-down',
1116 b'KEY_NPAGE': b'page-down',
1116 b'KEY_NPAGE': b'page-down',
1117 b'KEY_PPAGE': b'page-up',
1117 b'KEY_PPAGE': b'page-up',
1118 b'j': b'line-down',
1118 b'j': b'line-down',
1119 b'k': b'line-up',
1119 b'k': b'line-up',
1120 b'KEY_DOWN': b'line-down',
1120 b'KEY_DOWN': b'line-down',
1121 b'KEY_UP': b'line-up',
1121 b'KEY_UP': b'line-up',
1122 b'J': b'down',
1122 b'J': b'down',
1123 b'K': b'up',
1123 b'K': b'up',
1124 },
1124 },
1125 MODE_HELP: {},
1125 MODE_HELP: {},
1126 }
1126 }
1127
1127
1128
1128
1129 def screen_size():
1129 def screen_size():
1130 return struct.unpack(b'hh', fcntl.ioctl(1, termios.TIOCGWINSZ, b' '))
1130 return struct.unpack(b'hh', fcntl.ioctl(1, termios.TIOCGWINSZ, b' '))
1131
1131
1132
1132
1133 class histeditrule(object):
1133 class histeditrule(object):
1134 def __init__(self, ui, ctx, pos, action=b'pick'):
1134 def __init__(self, ui, ctx, pos, action=b'pick'):
1135 self.ui = ui
1135 self.ui = ui
1136 self.ctx = ctx
1136 self.ctx = ctx
1137 self.action = action
1137 self.action = action
1138 self.origpos = pos
1138 self.origpos = pos
1139 self.pos = pos
1139 self.pos = pos
1140 self.conflicts = []
1140 self.conflicts = []
1141
1141
1142 def __bytes__(self):
1142 def __bytes__(self):
1143 # Example display of several histeditrules:
1143 # Example display of several histeditrules:
1144 #
1144 #
1145 # #10 pick 316392:06a16c25c053 add option to skip tests
1145 # #10 pick 316392:06a16c25c053 add option to skip tests
1146 # #11 ^roll 316393:71313c964cc5 <RED>oops a fixup commit</RED>
1146 # #11 ^roll 316393:71313c964cc5 <RED>oops a fixup commit</RED>
1147 # #12 pick 316394:ab31f3973b0d include mfbt for mozilla-config.h
1147 # #12 pick 316394:ab31f3973b0d include mfbt for mozilla-config.h
1148 # #13 ^fold 316395:14ce5803f4c3 fix warnings
1148 # #13 ^fold 316395:14ce5803f4c3 fix warnings
1149 #
1149 #
1150 # The carets point to the changeset being folded into ("roll this
1150 # The carets point to the changeset being folded into ("roll this
1151 # changeset into the changeset above").
1151 # changeset into the changeset above").
1152 return b'%s%s' % (self.prefix, self.desc)
1152 return b'%s%s' % (self.prefix, self.desc)
1153
1153
1154 __str__ = encoding.strmethod(__bytes__)
1154 __str__ = encoding.strmethod(__bytes__)
1155
1155
1156 @property
1156 @property
1157 def prefix(self):
1157 def prefix(self):
1158 # Some actions ('fold' and 'roll') combine a patch with a
1158 # Some actions ('fold' and 'roll') combine a patch with a
1159 # previous one. Add a marker showing which patch they apply
1159 # previous one. Add a marker showing which patch they apply
1160 # to.
1160 # to.
1161 action = ACTION_LABELS.get(self.action, self.action)
1161 action = ACTION_LABELS.get(self.action, self.action)
1162
1162
1163 h = self.ctx.hex()[0:12]
1163 h = self.ctx.hex()[0:12]
1164 r = self.ctx.rev()
1164 r = self.ctx.rev()
1165
1165
1166 return b"#%s %s %d:%s " % (
1166 return b"#%s %s %d:%s " % (
1167 (b'%d' % self.origpos).ljust(2),
1167 (b'%d' % self.origpos).ljust(2),
1168 action.ljust(6),
1168 action.ljust(6),
1169 r,
1169 r,
1170 h,
1170 h,
1171 )
1171 )
1172
1172
1173 @util.propertycache
1173 @util.propertycache
1174 def desc(self):
1174 def desc(self):
1175 summary = cmdutil.rendertemplate(
1175 summary = cmdutil.rendertemplate(
1176 self.ctx, self.ui.config(b'histedit', b'summary-template')
1176 self.ctx, self.ui.config(b'histedit', b'summary-template')
1177 )
1177 )
1178 if summary:
1178 if summary:
1179 return summary
1179 return summary
1180 # This is split off from the prefix property so that we can
1180 # This is split off from the prefix property so that we can
1181 # separately make the description for 'roll' red (since it
1181 # separately make the description for 'roll' red (since it
1182 # will get discarded).
1182 # will get discarded).
1183 return self.ctx.description().splitlines()[0].strip()
1183 return self.ctx.description().splitlines()[0].strip()
1184
1184
1185 def checkconflicts(self, other):
1185 def checkconflicts(self, other):
1186 if other.pos > self.pos and other.origpos <= self.origpos:
1186 if other.pos > self.pos and other.origpos <= self.origpos:
1187 if set(other.ctx.files()) & set(self.ctx.files()) != set():
1187 if set(other.ctx.files()) & set(self.ctx.files()) != set():
1188 self.conflicts.append(other)
1188 self.conflicts.append(other)
1189 return self.conflicts
1189 return self.conflicts
1190
1190
1191 if other in self.conflicts:
1191 if other in self.conflicts:
1192 self.conflicts.remove(other)
1192 self.conflicts.remove(other)
1193 return self.conflicts
1193 return self.conflicts
1194
1194
1195
1195
1196 # ============ EVENTS ===============
1196 # ============ EVENTS ===============
1197 def movecursor(state, oldpos, newpos):
1197 def movecursor(state, oldpos, newpos):
1198 """Change the rule/changeset that the cursor is pointing to, regardless of
1198 """Change the rule/changeset that the cursor is pointing to, regardless of
1199 current mode (you can switch between patches from the view patch window)."""
1199 current mode (you can switch between patches from the view patch window)."""
1200 state[b'pos'] = newpos
1200 state[b'pos'] = newpos
1201
1201
1202 mode, _ = state[b'mode']
1202 mode, _ = state[b'mode']
1203 if mode == MODE_RULES:
1203 if mode == MODE_RULES:
1204 # Scroll through the list by updating the view for MODE_RULES, so that
1204 # Scroll through the list by updating the view for MODE_RULES, so that
1205 # even if we are not currently viewing the rules, switching back will
1205 # even if we are not currently viewing the rules, switching back will
1206 # result in the cursor's rule being visible.
1206 # result in the cursor's rule being visible.
1207 modestate = state[b'modes'][MODE_RULES]
1207 modestate = state[b'modes'][MODE_RULES]
1208 if newpos < modestate[b'line_offset']:
1208 if newpos < modestate[b'line_offset']:
1209 modestate[b'line_offset'] = newpos
1209 modestate[b'line_offset'] = newpos
1210 elif newpos > modestate[b'line_offset'] + state[b'page_height'] - 1:
1210 elif newpos > modestate[b'line_offset'] + state[b'page_height'] - 1:
1211 modestate[b'line_offset'] = newpos - state[b'page_height'] + 1
1211 modestate[b'line_offset'] = newpos - state[b'page_height'] + 1
1212
1212
1213 # Reset the patch view region to the top of the new patch.
1213 # Reset the patch view region to the top of the new patch.
1214 state[b'modes'][MODE_PATCH][b'line_offset'] = 0
1214 state[b'modes'][MODE_PATCH][b'line_offset'] = 0
1215
1215
1216
1216
1217 def changemode(state, mode):
1217 def changemode(state, mode):
1218 curmode, _ = state[b'mode']
1218 curmode, _ = state[b'mode']
1219 state[b'mode'] = (mode, curmode)
1219 state[b'mode'] = (mode, curmode)
1220 if mode == MODE_PATCH:
1220 if mode == MODE_PATCH:
1221 state[b'modes'][MODE_PATCH][b'patchcontents'] = patchcontents(state)
1221 state[b'modes'][MODE_PATCH][b'patchcontents'] = patchcontents(state)
1222
1222
1223
1223
1224 def makeselection(state, pos):
1224 def makeselection(state, pos):
1225 state[b'selected'] = pos
1225 state[b'selected'] = pos
1226
1226
1227
1227
1228 def swap(state, oldpos, newpos):
1228 def swap(state, oldpos, newpos):
1229 """Swap two positions and calculate necessary conflicts in
1229 """Swap two positions and calculate necessary conflicts in
1230 O(|newpos-oldpos|) time"""
1230 O(|newpos-oldpos|) time"""
1231
1231
1232 rules = state[b'rules']
1232 rules = state[b'rules']
1233 assert 0 <= oldpos < len(rules) and 0 <= newpos < len(rules)
1233 assert 0 <= oldpos < len(rules) and 0 <= newpos < len(rules)
1234
1234
1235 rules[oldpos], rules[newpos] = rules[newpos], rules[oldpos]
1235 rules[oldpos], rules[newpos] = rules[newpos], rules[oldpos]
1236
1236
1237 # TODO: swap should not know about histeditrule's internals
1237 # TODO: swap should not know about histeditrule's internals
1238 rules[newpos].pos = newpos
1238 rules[newpos].pos = newpos
1239 rules[oldpos].pos = oldpos
1239 rules[oldpos].pos = oldpos
1240
1240
1241 start = min(oldpos, newpos)
1241 start = min(oldpos, newpos)
1242 end = max(oldpos, newpos)
1242 end = max(oldpos, newpos)
1243 for r in pycompat.xrange(start, end + 1):
1243 for r in pycompat.xrange(start, end + 1):
1244 rules[newpos].checkconflicts(rules[r])
1244 rules[newpos].checkconflicts(rules[r])
1245 rules[oldpos].checkconflicts(rules[r])
1245 rules[oldpos].checkconflicts(rules[r])
1246
1246
1247 if state[b'selected']:
1247 if state[b'selected']:
1248 makeselection(state, newpos)
1248 makeselection(state, newpos)
1249
1249
1250
1250
1251 def changeaction(state, pos, action):
1251 def changeaction(state, pos, action):
1252 """Change the action state on the given position to the new action"""
1252 """Change the action state on the given position to the new action"""
1253 rules = state[b'rules']
1253 rules = state[b'rules']
1254 assert 0 <= pos < len(rules)
1254 assert 0 <= pos < len(rules)
1255 rules[pos].action = action
1255 rules[pos].action = action
1256
1256
1257
1257
1258 def cycleaction(state, pos, next=False):
1258 def cycleaction(state, pos, next=False):
1259 """Changes the action state the next or the previous action from
1259 """Changes the action state the next or the previous action from
1260 the action list"""
1260 the action list"""
1261 rules = state[b'rules']
1261 rules = state[b'rules']
1262 assert 0 <= pos < len(rules)
1262 assert 0 <= pos < len(rules)
1263 current = rules[pos].action
1263 current = rules[pos].action
1264
1264
1265 assert current in KEY_LIST
1265 assert current in KEY_LIST
1266
1266
1267 index = KEY_LIST.index(current)
1267 index = KEY_LIST.index(current)
1268 if next:
1268 if next:
1269 index += 1
1269 index += 1
1270 else:
1270 else:
1271 index -= 1
1271 index -= 1
1272 changeaction(state, pos, KEY_LIST[index % len(KEY_LIST)])
1272 changeaction(state, pos, KEY_LIST[index % len(KEY_LIST)])
1273
1273
1274
1274
1275 def changeview(state, delta, unit):
1275 def changeview(state, delta, unit):
1276 """Change the region of whatever is being viewed (a patch or the list of
1276 """Change the region of whatever is being viewed (a patch or the list of
1277 changesets). 'delta' is an amount (+/- 1) and 'unit' is 'page' or 'line'."""
1277 changesets). 'delta' is an amount (+/- 1) and 'unit' is 'page' or 'line'."""
1278 mode, _ = state[b'mode']
1278 mode, _ = state[b'mode']
1279 if mode != MODE_PATCH:
1279 if mode != MODE_PATCH:
1280 return
1280 return
1281 mode_state = state[b'modes'][mode]
1281 mode_state = state[b'modes'][mode]
1282 num_lines = len(mode_state[b'patchcontents'])
1282 num_lines = len(mode_state[b'patchcontents'])
1283 page_height = state[b'page_height']
1283 page_height = state[b'page_height']
1284 unit = page_height if unit == b'page' else 1
1284 unit = page_height if unit == b'page' else 1
1285 num_pages = 1 + (num_lines - 1) // page_height
1285 num_pages = 1 + (num_lines - 1) // page_height
1286 max_offset = (num_pages - 1) * page_height
1286 max_offset = (num_pages - 1) * page_height
1287 newline = mode_state[b'line_offset'] + delta * unit
1287 newline = mode_state[b'line_offset'] + delta * unit
1288 mode_state[b'line_offset'] = max(0, min(max_offset, newline))
1288 mode_state[b'line_offset'] = max(0, min(max_offset, newline))
1289
1289
1290
1290
1291 def event(state, ch):
1291 def event(state, ch):
1292 """Change state based on the current character input
1292 """Change state based on the current character input
1293
1293
1294 This takes the current state and based on the current character input from
1294 This takes the current state and based on the current character input from
1295 the user we change the state.
1295 the user we change the state.
1296 """
1296 """
1297 selected = state[b'selected']
1297 selected = state[b'selected']
1298 oldpos = state[b'pos']
1298 oldpos = state[b'pos']
1299 rules = state[b'rules']
1299 rules = state[b'rules']
1300
1300
1301 if ch in (curses.KEY_RESIZE, b"KEY_RESIZE"):
1301 if ch in (curses.KEY_RESIZE, b"KEY_RESIZE"):
1302 return E_RESIZE
1302 return E_RESIZE
1303
1303
1304 lookup_ch = ch
1304 lookup_ch = ch
1305 if ch is not None and b'0' <= ch <= b'9':
1305 if ch is not None and b'0' <= ch <= b'9':
1306 lookup_ch = b'0'
1306 lookup_ch = b'0'
1307
1307
1308 curmode, prevmode = state[b'mode']
1308 curmode, prevmode = state[b'mode']
1309 action = KEYTABLE[curmode].get(
1309 action = KEYTABLE[curmode].get(
1310 lookup_ch, KEYTABLE[b'global'].get(lookup_ch)
1310 lookup_ch, KEYTABLE[b'global'].get(lookup_ch)
1311 )
1311 )
1312 if action is None:
1312 if action is None:
1313 return
1313 return
1314 if action in (b'down', b'move-down'):
1314 if action in (b'down', b'move-down'):
1315 newpos = min(oldpos + 1, len(rules) - 1)
1315 newpos = min(oldpos + 1, len(rules) - 1)
1316 movecursor(state, oldpos, newpos)
1316 movecursor(state, oldpos, newpos)
1317 if selected is not None or action == b'move-down':
1317 if selected is not None or action == b'move-down':
1318 swap(state, oldpos, newpos)
1318 swap(state, oldpos, newpos)
1319 elif action in (b'up', b'move-up'):
1319 elif action in (b'up', b'move-up'):
1320 newpos = max(0, oldpos - 1)
1320 newpos = max(0, oldpos - 1)
1321 movecursor(state, oldpos, newpos)
1321 movecursor(state, oldpos, newpos)
1322 if selected is not None or action == b'move-up':
1322 if selected is not None or action == b'move-up':
1323 swap(state, oldpos, newpos)
1323 swap(state, oldpos, newpos)
1324 elif action == b'next-action':
1324 elif action == b'next-action':
1325 cycleaction(state, oldpos, next=True)
1325 cycleaction(state, oldpos, next=True)
1326 elif action == b'prev-action':
1326 elif action == b'prev-action':
1327 cycleaction(state, oldpos, next=False)
1327 cycleaction(state, oldpos, next=False)
1328 elif action == b'select':
1328 elif action == b'select':
1329 selected = oldpos if selected is None else None
1329 selected = oldpos if selected is None else None
1330 makeselection(state, selected)
1330 makeselection(state, selected)
1331 elif action == b'goto' and int(ch) < len(rules) and len(rules) <= 10:
1331 elif action == b'goto' and int(ch) < len(rules) and len(rules) <= 10:
1332 newrule = next((r for r in rules if r.origpos == int(ch)))
1332 newrule = next((r for r in rules if r.origpos == int(ch)))
1333 movecursor(state, oldpos, newrule.pos)
1333 movecursor(state, oldpos, newrule.pos)
1334 if selected is not None:
1334 if selected is not None:
1335 swap(state, oldpos, newrule.pos)
1335 swap(state, oldpos, newrule.pos)
1336 elif action.startswith(b'action-'):
1336 elif action.startswith(b'action-'):
1337 changeaction(state, oldpos, action[7:])
1337 changeaction(state, oldpos, action[7:])
1338 elif action == b'showpatch':
1338 elif action == b'showpatch':
1339 changemode(state, MODE_PATCH if curmode != MODE_PATCH else prevmode)
1339 changemode(state, MODE_PATCH if curmode != MODE_PATCH else prevmode)
1340 elif action == b'help':
1340 elif action == b'help':
1341 changemode(state, MODE_HELP if curmode != MODE_HELP else prevmode)
1341 changemode(state, MODE_HELP if curmode != MODE_HELP else prevmode)
1342 elif action == b'quit':
1342 elif action == b'quit':
1343 return E_QUIT
1343 return E_QUIT
1344 elif action == b'histedit':
1344 elif action == b'histedit':
1345 return E_HISTEDIT
1345 return E_HISTEDIT
1346 elif action == b'page-down':
1346 elif action == b'page-down':
1347 return E_PAGEDOWN
1347 return E_PAGEDOWN
1348 elif action == b'page-up':
1348 elif action == b'page-up':
1349 return E_PAGEUP
1349 return E_PAGEUP
1350 elif action == b'line-down':
1350 elif action == b'line-down':
1351 return E_LINEDOWN
1351 return E_LINEDOWN
1352 elif action == b'line-up':
1352 elif action == b'line-up':
1353 return E_LINEUP
1353 return E_LINEUP
1354
1354
1355
1355
1356 def makecommands(rules):
1356 def makecommands(rules):
1357 """Returns a list of commands consumable by histedit --commands based on
1357 """Returns a list of commands consumable by histedit --commands based on
1358 our list of rules"""
1358 our list of rules"""
1359 commands = []
1359 commands = []
1360 for rules in rules:
1360 for rules in rules:
1361 commands.append(b'%s %s\n' % (rules.action, rules.ctx))
1361 commands.append(b'%s %s\n' % (rules.action, rules.ctx))
1362 return commands
1362 return commands
1363
1363
1364
1364
1365 def addln(win, y, x, line, color=None):
1365 def addln(win, y, x, line, color=None):
1366 """Add a line to the given window left padding but 100% filled with
1366 """Add a line to the given window left padding but 100% filled with
1367 whitespace characters, so that the color appears on the whole line"""
1367 whitespace characters, so that the color appears on the whole line"""
1368 maxy, maxx = win.getmaxyx()
1368 maxy, maxx = win.getmaxyx()
1369 length = maxx - 1 - x
1369 length = maxx - 1 - x
1370 line = bytes(line).ljust(length)[:length]
1370 line = bytes(line).ljust(length)[:length]
1371 if y < 0:
1371 if y < 0:
1372 y = maxy + y
1372 y = maxy + y
1373 if x < 0:
1373 if x < 0:
1374 x = maxx + x
1374 x = maxx + x
1375 if color:
1375 if color:
1376 win.addstr(y, x, line, color)
1376 win.addstr(y, x, line, color)
1377 else:
1377 else:
1378 win.addstr(y, x, line)
1378 win.addstr(y, x, line)
1379
1379
1380
1380
1381 def _trunc_head(line, n):
1381 def _trunc_head(line, n):
1382 if len(line) <= n:
1382 if len(line) <= n:
1383 return line
1383 return line
1384 return b'> ' + line[-(n - 2) :]
1384 return b'> ' + line[-(n - 2) :]
1385
1385
1386
1386
1387 def _trunc_tail(line, n):
1387 def _trunc_tail(line, n):
1388 if len(line) <= n:
1388 if len(line) <= n:
1389 return line
1389 return line
1390 return line[: n - 2] + b' >'
1390 return line[: n - 2] + b' >'
1391
1391
1392
1392
1393 def patchcontents(state):
1393 def patchcontents(state):
1394 repo = state[b'repo']
1394 repo = state[b'repo']
1395 rule = state[b'rules'][state[b'pos']]
1395 rule = state[b'rules'][state[b'pos']]
1396 displayer = logcmdutil.changesetdisplayer(
1396 displayer = logcmdutil.changesetdisplayer(
1397 repo.ui, repo, {b"patch": True, b"template": b"status"}, buffered=True
1397 repo.ui, repo, {b"patch": True, b"template": b"status"}, buffered=True
1398 )
1398 )
1399 overrides = {(b'ui', b'verbose'): True}
1399 overrides = {(b'ui', b'verbose'): True}
1400 with repo.ui.configoverride(overrides, source=b'histedit'):
1400 with repo.ui.configoverride(overrides, source=b'histedit'):
1401 displayer.show(rule.ctx)
1401 displayer.show(rule.ctx)
1402 displayer.close()
1402 displayer.close()
1403 return displayer.hunk[rule.ctx.rev()].splitlines()
1403 return displayer.hunk[rule.ctx.rev()].splitlines()
1404
1404
1405
1405
1406 def _chisteditmain(repo, rules, stdscr):
1406 def _chisteditmain(repo, rules, stdscr):
1407 try:
1407 try:
1408 curses.use_default_colors()
1408 curses.use_default_colors()
1409 except curses.error:
1409 except curses.error:
1410 pass
1410 pass
1411
1411
1412 # initialize color pattern
1412 # initialize color pattern
1413 curses.init_pair(COLOR_HELP, curses.COLOR_WHITE, curses.COLOR_BLUE)
1413 curses.init_pair(COLOR_HELP, curses.COLOR_WHITE, curses.COLOR_BLUE)
1414 curses.init_pair(COLOR_SELECTED, curses.COLOR_BLACK, curses.COLOR_WHITE)
1414 curses.init_pair(COLOR_SELECTED, curses.COLOR_BLACK, curses.COLOR_WHITE)
1415 curses.init_pair(COLOR_WARN, curses.COLOR_BLACK, curses.COLOR_YELLOW)
1415 curses.init_pair(COLOR_WARN, curses.COLOR_BLACK, curses.COLOR_YELLOW)
1416 curses.init_pair(COLOR_OK, curses.COLOR_BLACK, curses.COLOR_GREEN)
1416 curses.init_pair(COLOR_OK, curses.COLOR_BLACK, curses.COLOR_GREEN)
1417 curses.init_pair(COLOR_CURRENT, curses.COLOR_WHITE, curses.COLOR_MAGENTA)
1417 curses.init_pair(COLOR_CURRENT, curses.COLOR_WHITE, curses.COLOR_MAGENTA)
1418 curses.init_pair(COLOR_DIFF_ADD_LINE, curses.COLOR_GREEN, -1)
1418 curses.init_pair(COLOR_DIFF_ADD_LINE, curses.COLOR_GREEN, -1)
1419 curses.init_pair(COLOR_DIFF_DEL_LINE, curses.COLOR_RED, -1)
1419 curses.init_pair(COLOR_DIFF_DEL_LINE, curses.COLOR_RED, -1)
1420 curses.init_pair(COLOR_DIFF_OFFSET, curses.COLOR_MAGENTA, -1)
1420 curses.init_pair(COLOR_DIFF_OFFSET, curses.COLOR_MAGENTA, -1)
1421 curses.init_pair(COLOR_ROLL, curses.COLOR_RED, -1)
1421 curses.init_pair(COLOR_ROLL, curses.COLOR_RED, -1)
1422 curses.init_pair(
1422 curses.init_pair(
1423 COLOR_ROLL_CURRENT, curses.COLOR_BLACK, curses.COLOR_MAGENTA
1423 COLOR_ROLL_CURRENT, curses.COLOR_BLACK, curses.COLOR_MAGENTA
1424 )
1424 )
1425 curses.init_pair(COLOR_ROLL_SELECTED, curses.COLOR_RED, curses.COLOR_WHITE)
1425 curses.init_pair(COLOR_ROLL_SELECTED, curses.COLOR_RED, curses.COLOR_WHITE)
1426
1426
1427 # don't display the cursor
1427 # don't display the cursor
1428 try:
1428 try:
1429 curses.curs_set(0)
1429 curses.curs_set(0)
1430 except curses.error:
1430 except curses.error:
1431 pass
1431 pass
1432
1432
1433 def rendercommit(win, state):
1433 def rendercommit(win, state):
1434 """Renders the commit window that shows the log of the current selected
1434 """Renders the commit window that shows the log of the current selected
1435 commit"""
1435 commit"""
1436 pos = state[b'pos']
1436 pos = state[b'pos']
1437 rules = state[b'rules']
1437 rules = state[b'rules']
1438 rule = rules[pos]
1438 rule = rules[pos]
1439
1439
1440 ctx = rule.ctx
1440 ctx = rule.ctx
1441 win.box()
1441 win.box()
1442
1442
1443 maxy, maxx = win.getmaxyx()
1443 maxy, maxx = win.getmaxyx()
1444 length = maxx - 3
1444 length = maxx - 3
1445
1445
1446 line = b"changeset: %d:%s" % (ctx.rev(), ctx.hex()[:12])
1446 line = b"changeset: %d:%s" % (ctx.rev(), ctx.hex()[:12])
1447 win.addstr(1, 1, line[:length])
1447 win.addstr(1, 1, line[:length])
1448
1448
1449 line = b"user: %s" % ctx.user()
1449 line = b"user: %s" % ctx.user()
1450 win.addstr(2, 1, line[:length])
1450 win.addstr(2, 1, line[:length])
1451
1451
1452 bms = repo.nodebookmarks(ctx.node())
1452 bms = repo.nodebookmarks(ctx.node())
1453 line = b"bookmark: %s" % b' '.join(bms)
1453 line = b"bookmark: %s" % b' '.join(bms)
1454 win.addstr(3, 1, line[:length])
1454 win.addstr(3, 1, line[:length])
1455
1455
1456 line = b"summary: %s" % (ctx.description().splitlines()[0])
1456 line = b"summary: %s" % (ctx.description().splitlines()[0])
1457 win.addstr(4, 1, line[:length])
1457 win.addstr(4, 1, line[:length])
1458
1458
1459 line = b"files: "
1459 line = b"files: "
1460 win.addstr(5, 1, line)
1460 win.addstr(5, 1, line)
1461 fnx = 1 + len(line)
1461 fnx = 1 + len(line)
1462 fnmaxx = length - fnx + 1
1462 fnmaxx = length - fnx + 1
1463 y = 5
1463 y = 5
1464 fnmaxn = maxy - (1 + y) - 1
1464 fnmaxn = maxy - (1 + y) - 1
1465 files = ctx.files()
1465 files = ctx.files()
1466 for i, line1 in enumerate(files):
1466 for i, line1 in enumerate(files):
1467 if len(files) > fnmaxn and i == fnmaxn - 1:
1467 if len(files) > fnmaxn and i == fnmaxn - 1:
1468 win.addstr(y, fnx, _trunc_tail(b','.join(files[i:]), fnmaxx))
1468 win.addstr(y, fnx, _trunc_tail(b','.join(files[i:]), fnmaxx))
1469 y = y + 1
1469 y = y + 1
1470 break
1470 break
1471 win.addstr(y, fnx, _trunc_head(line1, fnmaxx))
1471 win.addstr(y, fnx, _trunc_head(line1, fnmaxx))
1472 y = y + 1
1472 y = y + 1
1473
1473
1474 conflicts = rule.conflicts
1474 conflicts = rule.conflicts
1475 if len(conflicts) > 0:
1475 if len(conflicts) > 0:
1476 conflictstr = b','.join(map(lambda r: r.ctx.hex()[:12], conflicts))
1476 conflictstr = b','.join(map(lambda r: r.ctx.hex()[:12], conflicts))
1477 conflictstr = b"changed files overlap with %s" % conflictstr
1477 conflictstr = b"changed files overlap with %s" % conflictstr
1478 else:
1478 else:
1479 conflictstr = b'no overlap'
1479 conflictstr = b'no overlap'
1480
1480
1481 win.addstr(y, 1, conflictstr[:length])
1481 win.addstr(y, 1, conflictstr[:length])
1482 win.noutrefresh()
1482 win.noutrefresh()
1483
1483
1484 def helplines(mode):
1484 def helplines(mode):
1485 if mode == MODE_PATCH:
1485 if mode == MODE_PATCH:
1486 help = b"""\
1486 help = b"""\
1487 ?: help, k/up: line up, j/down: line down, v: stop viewing patch
1487 ?: help, k/up: line up, j/down: line down, v: stop viewing patch
1488 pgup: prev page, space/pgdn: next page, c: commit, q: abort
1488 pgup: prev page, space/pgdn: next page, c: commit, q: abort
1489 """
1489 """
1490 else:
1490 else:
1491 help = b"""\
1491 help = b"""\
1492 ?: help, k/up: move up, j/down: move down, space: select, v: view patch
1492 ?: help, k/up: move up, j/down: move down, space: select, v: view patch
1493 d: drop, e: edit, f: fold, m: mess, p: pick, r: roll
1493 d: drop, e: edit, f: fold, m: mess, p: pick, r: roll
1494 pgup/K: move patch up, pgdn/J: move patch down, c: commit, q: abort
1494 pgup/K: move patch up, pgdn/J: move patch down, c: commit, q: abort
1495 """
1495 """
1496 return help.splitlines()
1496 return help.splitlines()
1497
1497
1498 def renderhelp(win, state):
1498 def renderhelp(win, state):
1499 maxy, maxx = win.getmaxyx()
1499 maxy, maxx = win.getmaxyx()
1500 mode, _ = state[b'mode']
1500 mode, _ = state[b'mode']
1501 for y, line in enumerate(helplines(mode)):
1501 for y, line in enumerate(helplines(mode)):
1502 if y >= maxy:
1502 if y >= maxy:
1503 break
1503 break
1504 addln(win, y, 0, line, curses.color_pair(COLOR_HELP))
1504 addln(win, y, 0, line, curses.color_pair(COLOR_HELP))
1505 win.noutrefresh()
1505 win.noutrefresh()
1506
1506
1507 def renderrules(rulesscr, state):
1507 def renderrules(rulesscr, state):
1508 rules = state[b'rules']
1508 rules = state[b'rules']
1509 pos = state[b'pos']
1509 pos = state[b'pos']
1510 selected = state[b'selected']
1510 selected = state[b'selected']
1511 start = state[b'modes'][MODE_RULES][b'line_offset']
1511 start = state[b'modes'][MODE_RULES][b'line_offset']
1512
1512
1513 conflicts = [r.ctx for r in rules if r.conflicts]
1513 conflicts = [r.ctx for r in rules if r.conflicts]
1514 if len(conflicts) > 0:
1514 if len(conflicts) > 0:
1515 line = b"potential conflict in %s" % b','.join(
1515 line = b"potential conflict in %s" % b','.join(
1516 map(pycompat.bytestr, conflicts)
1516 map(pycompat.bytestr, conflicts)
1517 )
1517 )
1518 addln(rulesscr, -1, 0, line, curses.color_pair(COLOR_WARN))
1518 addln(rulesscr, -1, 0, line, curses.color_pair(COLOR_WARN))
1519
1519
1520 for y, rule in enumerate(rules[start:]):
1520 for y, rule in enumerate(rules[start:]):
1521 if y >= state[b'page_height']:
1521 if y >= state[b'page_height']:
1522 break
1522 break
1523 if len(rule.conflicts) > 0:
1523 if len(rule.conflicts) > 0:
1524 rulesscr.addstr(y, 0, b" ", curses.color_pair(COLOR_WARN))
1524 rulesscr.addstr(y, 0, b" ", curses.color_pair(COLOR_WARN))
1525 else:
1525 else:
1526 rulesscr.addstr(y, 0, b" ", curses.COLOR_BLACK)
1526 rulesscr.addstr(y, 0, b" ", curses.COLOR_BLACK)
1527
1527
1528 if y + start == selected:
1528 if y + start == selected:
1529 rollcolor = COLOR_ROLL_SELECTED
1529 rollcolor = COLOR_ROLL_SELECTED
1530 addln(rulesscr, y, 2, rule, curses.color_pair(COLOR_SELECTED))
1530 addln(rulesscr, y, 2, rule, curses.color_pair(COLOR_SELECTED))
1531 elif y + start == pos:
1531 elif y + start == pos:
1532 rollcolor = COLOR_ROLL_CURRENT
1532 rollcolor = COLOR_ROLL_CURRENT
1533 addln(
1533 addln(
1534 rulesscr,
1534 rulesscr,
1535 y,
1535 y,
1536 2,
1536 2,
1537 rule,
1537 rule,
1538 curses.color_pair(COLOR_CURRENT) | curses.A_BOLD,
1538 curses.color_pair(COLOR_CURRENT) | curses.A_BOLD,
1539 )
1539 )
1540 else:
1540 else:
1541 rollcolor = COLOR_ROLL
1541 rollcolor = COLOR_ROLL
1542 addln(rulesscr, y, 2, rule)
1542 addln(rulesscr, y, 2, rule)
1543
1543
1544 if rule.action == b'roll':
1544 if rule.action == b'roll':
1545 rulesscr.addstr(
1545 rulesscr.addstr(
1546 y,
1546 y,
1547 2 + len(rule.prefix),
1547 2 + len(rule.prefix),
1548 rule.desc,
1548 rule.desc,
1549 curses.color_pair(rollcolor),
1549 curses.color_pair(rollcolor),
1550 )
1550 )
1551
1551
1552 rulesscr.noutrefresh()
1552 rulesscr.noutrefresh()
1553
1553
1554 def renderstring(win, state, output, diffcolors=False):
1554 def renderstring(win, state, output, diffcolors=False):
1555 maxy, maxx = win.getmaxyx()
1555 maxy, maxx = win.getmaxyx()
1556 length = min(maxy - 1, len(output))
1556 length = min(maxy - 1, len(output))
1557 for y in range(0, length):
1557 for y in range(0, length):
1558 line = output[y]
1558 line = output[y]
1559 if diffcolors:
1559 if diffcolors:
1560 if line and line[0] == b'+':
1560 if line and line[0] == b'+':
1561 win.addstr(
1561 win.addstr(
1562 y, 0, line, curses.color_pair(COLOR_DIFF_ADD_LINE)
1562 y, 0, line, curses.color_pair(COLOR_DIFF_ADD_LINE)
1563 )
1563 )
1564 elif line and line[0] == b'-':
1564 elif line and line[0] == b'-':
1565 win.addstr(
1565 win.addstr(
1566 y, 0, line, curses.color_pair(COLOR_DIFF_DEL_LINE)
1566 y, 0, line, curses.color_pair(COLOR_DIFF_DEL_LINE)
1567 )
1567 )
1568 elif line.startswith(b'@@ '):
1568 elif line.startswith(b'@@ '):
1569 win.addstr(y, 0, line, curses.color_pair(COLOR_DIFF_OFFSET))
1569 win.addstr(y, 0, line, curses.color_pair(COLOR_DIFF_OFFSET))
1570 else:
1570 else:
1571 win.addstr(y, 0, line)
1571 win.addstr(y, 0, line)
1572 else:
1572 else:
1573 win.addstr(y, 0, line)
1573 win.addstr(y, 0, line)
1574 win.noutrefresh()
1574 win.noutrefresh()
1575
1575
1576 def renderpatch(win, state):
1576 def renderpatch(win, state):
1577 start = state[b'modes'][MODE_PATCH][b'line_offset']
1577 start = state[b'modes'][MODE_PATCH][b'line_offset']
1578 content = state[b'modes'][MODE_PATCH][b'patchcontents']
1578 content = state[b'modes'][MODE_PATCH][b'patchcontents']
1579 renderstring(win, state, content[start:], diffcolors=True)
1579 renderstring(win, state, content[start:], diffcolors=True)
1580
1580
1581 def layout(mode):
1581 def layout(mode):
1582 maxy, maxx = stdscr.getmaxyx()
1582 maxy, maxx = stdscr.getmaxyx()
1583 helplen = len(helplines(mode))
1583 helplen = len(helplines(mode))
1584 mainlen = maxy - helplen - 12
1584 mainlen = maxy - helplen - 12
1585 if mainlen < 1:
1585 if mainlen < 1:
1586 raise error.Abort(
1586 raise error.Abort(
1587 _(b"terminal dimensions %d by %d too small for curses histedit")
1587 _(b"terminal dimensions %d by %d too small for curses histedit")
1588 % (maxy, maxx),
1588 % (maxy, maxx),
1589 hint=_(
1589 hint=_(
1590 b"enlarge your terminal or use --config ui.interface=text"
1590 b"enlarge your terminal or use --config ui.interface=text"
1591 ),
1591 ),
1592 )
1592 )
1593 return {
1593 return {
1594 b'commit': (12, maxx),
1594 b'commit': (12, maxx),
1595 b'help': (helplen, maxx),
1595 b'help': (helplen, maxx),
1596 b'main': (mainlen, maxx),
1596 b'main': (mainlen, maxx),
1597 }
1597 }
1598
1598
1599 def drawvertwin(size, y, x):
1599 def drawvertwin(size, y, x):
1600 win = curses.newwin(size[0], size[1], y, x)
1600 win = curses.newwin(size[0], size[1], y, x)
1601 y += size[0]
1601 y += size[0]
1602 return win, y, x
1602 return win, y, x
1603
1603
1604 state = {
1604 state = {
1605 b'pos': 0,
1605 b'pos': 0,
1606 b'rules': rules,
1606 b'rules': rules,
1607 b'selected': None,
1607 b'selected': None,
1608 b'mode': (MODE_INIT, MODE_INIT),
1608 b'mode': (MODE_INIT, MODE_INIT),
1609 b'page_height': None,
1609 b'page_height': None,
1610 b'modes': {
1610 b'modes': {
1611 MODE_RULES: {
1611 MODE_RULES: {
1612 b'line_offset': 0,
1612 b'line_offset': 0,
1613 },
1613 },
1614 MODE_PATCH: {
1614 MODE_PATCH: {
1615 b'line_offset': 0,
1615 b'line_offset': 0,
1616 },
1616 },
1617 },
1617 },
1618 b'repo': repo,
1618 b'repo': repo,
1619 }
1619 }
1620
1620
1621 # eventloop
1621 # eventloop
1622 ch = None
1622 ch = None
1623 stdscr.clear()
1623 stdscr.clear()
1624 stdscr.refresh()
1624 stdscr.refresh()
1625 while True:
1625 while True:
1626 oldmode, unused = state[b'mode']
1626 oldmode, unused = state[b'mode']
1627 if oldmode == MODE_INIT:
1627 if oldmode == MODE_INIT:
1628 changemode(state, MODE_RULES)
1628 changemode(state, MODE_RULES)
1629 e = event(state, ch)
1629 e = event(state, ch)
1630
1630
1631 if e == E_QUIT:
1631 if e == E_QUIT:
1632 return False
1632 return False
1633 if e == E_HISTEDIT:
1633 if e == E_HISTEDIT:
1634 return state[b'rules']
1634 return state[b'rules']
1635 else:
1635 else:
1636 if e == E_RESIZE:
1636 if e == E_RESIZE:
1637 size = screen_size()
1637 size = screen_size()
1638 if size != stdscr.getmaxyx():
1638 if size != stdscr.getmaxyx():
1639 curses.resizeterm(*size)
1639 curses.resizeterm(*size)
1640
1640
1641 curmode, unused = state[b'mode']
1641 curmode, unused = state[b'mode']
1642 sizes = layout(curmode)
1642 sizes = layout(curmode)
1643 if curmode != oldmode:
1643 if curmode != oldmode:
1644 state[b'page_height'] = sizes[b'main'][0]
1644 state[b'page_height'] = sizes[b'main'][0]
1645 # Adjust the view to fit the current screen size.
1645 # Adjust the view to fit the current screen size.
1646 movecursor(state, state[b'pos'], state[b'pos'])
1646 movecursor(state, state[b'pos'], state[b'pos'])
1647
1647
1648 # Pack the windows against the top, each pane spread across the
1648 # Pack the windows against the top, each pane spread across the
1649 # full width of the screen.
1649 # full width of the screen.
1650 y, x = (0, 0)
1650 y, x = (0, 0)
1651 helpwin, y, x = drawvertwin(sizes[b'help'], y, x)
1651 helpwin, y, x = drawvertwin(sizes[b'help'], y, x)
1652 mainwin, y, x = drawvertwin(sizes[b'main'], y, x)
1652 mainwin, y, x = drawvertwin(sizes[b'main'], y, x)
1653 commitwin, y, x = drawvertwin(sizes[b'commit'], y, x)
1653 commitwin, y, x = drawvertwin(sizes[b'commit'], y, x)
1654
1654
1655 if e in (E_PAGEDOWN, E_PAGEUP, E_LINEDOWN, E_LINEUP):
1655 if e in (E_PAGEDOWN, E_PAGEUP, E_LINEDOWN, E_LINEUP):
1656 if e == E_PAGEDOWN:
1656 if e == E_PAGEDOWN:
1657 changeview(state, +1, b'page')
1657 changeview(state, +1, b'page')
1658 elif e == E_PAGEUP:
1658 elif e == E_PAGEUP:
1659 changeview(state, -1, b'page')
1659 changeview(state, -1, b'page')
1660 elif e == E_LINEDOWN:
1660 elif e == E_LINEDOWN:
1661 changeview(state, +1, b'line')
1661 changeview(state, +1, b'line')
1662 elif e == E_LINEUP:
1662 elif e == E_LINEUP:
1663 changeview(state, -1, b'line')
1663 changeview(state, -1, b'line')
1664
1664
1665 # start rendering
1665 # start rendering
1666 commitwin.erase()
1666 commitwin.erase()
1667 helpwin.erase()
1667 helpwin.erase()
1668 mainwin.erase()
1668 mainwin.erase()
1669 if curmode == MODE_PATCH:
1669 if curmode == MODE_PATCH:
1670 renderpatch(mainwin, state)
1670 renderpatch(mainwin, state)
1671 elif curmode == MODE_HELP:
1671 elif curmode == MODE_HELP:
1672 renderstring(mainwin, state, __doc__.strip().splitlines())
1672 renderstring(mainwin, state, __doc__.strip().splitlines())
1673 else:
1673 else:
1674 renderrules(mainwin, state)
1674 renderrules(mainwin, state)
1675 rendercommit(commitwin, state)
1675 rendercommit(commitwin, state)
1676 renderhelp(helpwin, state)
1676 renderhelp(helpwin, state)
1677 curses.doupdate()
1677 curses.doupdate()
1678 # done rendering
1678 # done rendering
1679 ch = encoding.strtolocal(stdscr.getkey())
1679 ch = encoding.strtolocal(stdscr.getkey())
1680
1680
1681
1681
1682 def _chistedit(ui, repo, freeargs, opts):
1682 def _chistedit(ui, repo, freeargs, opts):
1683 """interactively edit changeset history via a curses interface
1683 """interactively edit changeset history via a curses interface
1684
1684
1685 Provides a ncurses interface to histedit. Press ? in chistedit mode
1685 Provides a ncurses interface to histedit. Press ? in chistedit mode
1686 to see an extensive help. Requires python-curses to be installed."""
1686 to see an extensive help. Requires python-curses to be installed."""
1687
1687
1688 if curses is None:
1688 if curses is None:
1689 raise error.Abort(_(b"Python curses library required"))
1689 raise error.Abort(_(b"Python curses library required"))
1690
1690
1691 # disable color
1691 # disable color
1692 ui._colormode = None
1692 ui._colormode = None
1693
1693
1694 try:
1694 try:
1695 keep = opts.get(b'keep')
1695 keep = opts.get(b'keep')
1696 revs = opts.get(b'rev', [])[:]
1696 revs = opts.get(b'rev', [])[:]
1697 cmdutil.checkunfinished(repo)
1697 cmdutil.checkunfinished(repo)
1698 cmdutil.bailifchanged(repo)
1698 cmdutil.bailifchanged(repo)
1699
1699
1700 revs.extend(freeargs)
1700 revs.extend(freeargs)
1701 if not revs:
1701 if not revs:
1702 defaultrev = destutil.desthistedit(ui, repo)
1702 defaultrev = destutil.desthistedit(ui, repo)
1703 if defaultrev is not None:
1703 if defaultrev is not None:
1704 revs.append(defaultrev)
1704 revs.append(defaultrev)
1705 if len(revs) != 1:
1705 if len(revs) != 1:
1706 raise error.Abort(
1706 raise error.InputError(
1707 _(b'histedit requires exactly one ancestor revision')
1707 _(b'histedit requires exactly one ancestor revision')
1708 )
1708 )
1709
1709
1710 rr = list(repo.set(b'roots(%ld)', scmutil.revrange(repo, revs)))
1710 rr = list(repo.set(b'roots(%ld)', scmutil.revrange(repo, revs)))
1711 if len(rr) != 1:
1711 if len(rr) != 1:
1712 raise error.Abort(
1712 raise error.InputError(
1713 _(
1713 _(
1714 b'The specified revisions must have '
1714 b'The specified revisions must have '
1715 b'exactly one common root'
1715 b'exactly one common root'
1716 )
1716 )
1717 )
1717 )
1718 root = rr[0].node()
1718 root = rr[0].node()
1719
1719
1720 topmost = repo.dirstate.p1()
1720 topmost = repo.dirstate.p1()
1721 revs = between(repo, root, topmost, keep)
1721 revs = between(repo, root, topmost, keep)
1722 if not revs:
1722 if not revs:
1723 raise error.Abort(
1723 raise error.InputError(
1724 _(b'%s is not an ancestor of working directory') % short(root)
1724 _(b'%s is not an ancestor of working directory') % short(root)
1725 )
1725 )
1726
1726
1727 ctxs = []
1727 ctxs = []
1728 for i, r in enumerate(revs):
1728 for i, r in enumerate(revs):
1729 ctxs.append(histeditrule(ui, repo[r], i))
1729 ctxs.append(histeditrule(ui, repo[r], i))
1730 with util.with_lc_ctype():
1730 with util.with_lc_ctype():
1731 rc = curses.wrapper(functools.partial(_chisteditmain, repo, ctxs))
1731 rc = curses.wrapper(functools.partial(_chisteditmain, repo, ctxs))
1732 curses.echo()
1732 curses.echo()
1733 curses.endwin()
1733 curses.endwin()
1734 if rc is False:
1734 if rc is False:
1735 ui.write(_(b"histedit aborted\n"))
1735 ui.write(_(b"histedit aborted\n"))
1736 return 0
1736 return 0
1737 if type(rc) is list:
1737 if type(rc) is list:
1738 ui.status(_(b"performing changes\n"))
1738 ui.status(_(b"performing changes\n"))
1739 rules = makecommands(rc)
1739 rules = makecommands(rc)
1740 with repo.vfs(b'chistedit', b'w+') as fp:
1740 with repo.vfs(b'chistedit', b'w+') as fp:
1741 for r in rules:
1741 for r in rules:
1742 fp.write(r)
1742 fp.write(r)
1743 opts[b'commands'] = fp.name
1743 opts[b'commands'] = fp.name
1744 return _texthistedit(ui, repo, freeargs, opts)
1744 return _texthistedit(ui, repo, freeargs, opts)
1745 except KeyboardInterrupt:
1745 except KeyboardInterrupt:
1746 pass
1746 pass
1747 return -1
1747 return -1
1748
1748
1749
1749
1750 @command(
1750 @command(
1751 b'histedit',
1751 b'histedit',
1752 [
1752 [
1753 (
1753 (
1754 b'',
1754 b'',
1755 b'commands',
1755 b'commands',
1756 b'',
1756 b'',
1757 _(b'read history edits from the specified file'),
1757 _(b'read history edits from the specified file'),
1758 _(b'FILE'),
1758 _(b'FILE'),
1759 ),
1759 ),
1760 (b'c', b'continue', False, _(b'continue an edit already in progress')),
1760 (b'c', b'continue', False, _(b'continue an edit already in progress')),
1761 (b'', b'edit-plan', False, _(b'edit remaining actions list')),
1761 (b'', b'edit-plan', False, _(b'edit remaining actions list')),
1762 (
1762 (
1763 b'k',
1763 b'k',
1764 b'keep',
1764 b'keep',
1765 False,
1765 False,
1766 _(b"don't strip old nodes after edit is complete"),
1766 _(b"don't strip old nodes after edit is complete"),
1767 ),
1767 ),
1768 (b'', b'abort', False, _(b'abort an edit in progress')),
1768 (b'', b'abort', False, _(b'abort an edit in progress')),
1769 (b'o', b'outgoing', False, _(b'changesets not found in destination')),
1769 (b'o', b'outgoing', False, _(b'changesets not found in destination')),
1770 (
1770 (
1771 b'f',
1771 b'f',
1772 b'force',
1772 b'force',
1773 False,
1773 False,
1774 _(b'force outgoing even for unrelated repositories'),
1774 _(b'force outgoing even for unrelated repositories'),
1775 ),
1775 ),
1776 (b'r', b'rev', [], _(b'first revision to be edited'), _(b'REV')),
1776 (b'r', b'rev', [], _(b'first revision to be edited'), _(b'REV')),
1777 ]
1777 ]
1778 + cmdutil.formatteropts,
1778 + cmdutil.formatteropts,
1779 _(b"[OPTIONS] ([ANCESTOR] | --outgoing [URL])"),
1779 _(b"[OPTIONS] ([ANCESTOR] | --outgoing [URL])"),
1780 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
1780 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
1781 )
1781 )
1782 def histedit(ui, repo, *freeargs, **opts):
1782 def histedit(ui, repo, *freeargs, **opts):
1783 """interactively edit changeset history
1783 """interactively edit changeset history
1784
1784
1785 This command lets you edit a linear series of changesets (up to
1785 This command lets you edit a linear series of changesets (up to
1786 and including the working directory, which should be clean).
1786 and including the working directory, which should be clean).
1787 You can:
1787 You can:
1788
1788
1789 - `pick` to [re]order a changeset
1789 - `pick` to [re]order a changeset
1790
1790
1791 - `drop` to omit changeset
1791 - `drop` to omit changeset
1792
1792
1793 - `mess` to reword the changeset commit message
1793 - `mess` to reword the changeset commit message
1794
1794
1795 - `fold` to combine it with the preceding changeset (using the later date)
1795 - `fold` to combine it with the preceding changeset (using the later date)
1796
1796
1797 - `roll` like fold, but discarding this commit's description and date
1797 - `roll` like fold, but discarding this commit's description and date
1798
1798
1799 - `edit` to edit this changeset (preserving date)
1799 - `edit` to edit this changeset (preserving date)
1800
1800
1801 - `base` to checkout changeset and apply further changesets from there
1801 - `base` to checkout changeset and apply further changesets from there
1802
1802
1803 There are a number of ways to select the root changeset:
1803 There are a number of ways to select the root changeset:
1804
1804
1805 - Specify ANCESTOR directly
1805 - Specify ANCESTOR directly
1806
1806
1807 - Use --outgoing -- it will be the first linear changeset not
1807 - Use --outgoing -- it will be the first linear changeset not
1808 included in destination. (See :hg:`help config.paths.default-push`)
1808 included in destination. (See :hg:`help config.paths.default-push`)
1809
1809
1810 - Otherwise, the value from the "histedit.defaultrev" config option
1810 - Otherwise, the value from the "histedit.defaultrev" config option
1811 is used as a revset to select the base revision when ANCESTOR is not
1811 is used as a revset to select the base revision when ANCESTOR is not
1812 specified. The first revision returned by the revset is used. By
1812 specified. The first revision returned by the revset is used. By
1813 default, this selects the editable history that is unique to the
1813 default, this selects the editable history that is unique to the
1814 ancestry of the working directory.
1814 ancestry of the working directory.
1815
1815
1816 .. container:: verbose
1816 .. container:: verbose
1817
1817
1818 If you use --outgoing, this command will abort if there are ambiguous
1818 If you use --outgoing, this command will abort if there are ambiguous
1819 outgoing revisions. For example, if there are multiple branches
1819 outgoing revisions. For example, if there are multiple branches
1820 containing outgoing revisions.
1820 containing outgoing revisions.
1821
1821
1822 Use "min(outgoing() and ::.)" or similar revset specification
1822 Use "min(outgoing() and ::.)" or similar revset specification
1823 instead of --outgoing to specify edit target revision exactly in
1823 instead of --outgoing to specify edit target revision exactly in
1824 such ambiguous situation. See :hg:`help revsets` for detail about
1824 such ambiguous situation. See :hg:`help revsets` for detail about
1825 selecting revisions.
1825 selecting revisions.
1826
1826
1827 .. container:: verbose
1827 .. container:: verbose
1828
1828
1829 Examples:
1829 Examples:
1830
1830
1831 - A number of changes have been made.
1831 - A number of changes have been made.
1832 Revision 3 is no longer needed.
1832 Revision 3 is no longer needed.
1833
1833
1834 Start history editing from revision 3::
1834 Start history editing from revision 3::
1835
1835
1836 hg histedit -r 3
1836 hg histedit -r 3
1837
1837
1838 An editor opens, containing the list of revisions,
1838 An editor opens, containing the list of revisions,
1839 with specific actions specified::
1839 with specific actions specified::
1840
1840
1841 pick 5339bf82f0ca 3 Zworgle the foobar
1841 pick 5339bf82f0ca 3 Zworgle the foobar
1842 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1842 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1843 pick 0a9639fcda9d 5 Morgify the cromulancy
1843 pick 0a9639fcda9d 5 Morgify the cromulancy
1844
1844
1845 Additional information about the possible actions
1845 Additional information about the possible actions
1846 to take appears below the list of revisions.
1846 to take appears below the list of revisions.
1847
1847
1848 To remove revision 3 from the history,
1848 To remove revision 3 from the history,
1849 its action (at the beginning of the relevant line)
1849 its action (at the beginning of the relevant line)
1850 is changed to 'drop'::
1850 is changed to 'drop'::
1851
1851
1852 drop 5339bf82f0ca 3 Zworgle the foobar
1852 drop 5339bf82f0ca 3 Zworgle the foobar
1853 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1853 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1854 pick 0a9639fcda9d 5 Morgify the cromulancy
1854 pick 0a9639fcda9d 5 Morgify the cromulancy
1855
1855
1856 - A number of changes have been made.
1856 - A number of changes have been made.
1857 Revision 2 and 4 need to be swapped.
1857 Revision 2 and 4 need to be swapped.
1858
1858
1859 Start history editing from revision 2::
1859 Start history editing from revision 2::
1860
1860
1861 hg histedit -r 2
1861 hg histedit -r 2
1862
1862
1863 An editor opens, containing the list of revisions,
1863 An editor opens, containing the list of revisions,
1864 with specific actions specified::
1864 with specific actions specified::
1865
1865
1866 pick 252a1af424ad 2 Blorb a morgwazzle
1866 pick 252a1af424ad 2 Blorb a morgwazzle
1867 pick 5339bf82f0ca 3 Zworgle the foobar
1867 pick 5339bf82f0ca 3 Zworgle the foobar
1868 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1868 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1869
1869
1870 To swap revision 2 and 4, its lines are swapped
1870 To swap revision 2 and 4, its lines are swapped
1871 in the editor::
1871 in the editor::
1872
1872
1873 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1873 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1874 pick 5339bf82f0ca 3 Zworgle the foobar
1874 pick 5339bf82f0ca 3 Zworgle the foobar
1875 pick 252a1af424ad 2 Blorb a morgwazzle
1875 pick 252a1af424ad 2 Blorb a morgwazzle
1876
1876
1877 Returns 0 on success, 1 if user intervention is required (not only
1877 Returns 0 on success, 1 if user intervention is required (not only
1878 for intentional "edit" command, but also for resolving unexpected
1878 for intentional "edit" command, but also for resolving unexpected
1879 conflicts).
1879 conflicts).
1880 """
1880 """
1881 opts = pycompat.byteskwargs(opts)
1881 opts = pycompat.byteskwargs(opts)
1882
1882
1883 # kludge: _chistedit only works for starting an edit, not aborting
1883 # kludge: _chistedit only works for starting an edit, not aborting
1884 # or continuing, so fall back to regular _texthistedit for those
1884 # or continuing, so fall back to regular _texthistedit for those
1885 # operations.
1885 # operations.
1886 if ui.interface(b'histedit') == b'curses' and _getgoal(opts) == goalnew:
1886 if ui.interface(b'histedit') == b'curses' and _getgoal(opts) == goalnew:
1887 return _chistedit(ui, repo, freeargs, opts)
1887 return _chistedit(ui, repo, freeargs, opts)
1888 return _texthistedit(ui, repo, freeargs, opts)
1888 return _texthistedit(ui, repo, freeargs, opts)
1889
1889
1890
1890
1891 def _texthistedit(ui, repo, freeargs, opts):
1891 def _texthistedit(ui, repo, freeargs, opts):
1892 state = histeditstate(repo)
1892 state = histeditstate(repo)
1893 with repo.wlock() as wlock, repo.lock() as lock:
1893 with repo.wlock() as wlock, repo.lock() as lock:
1894 state.wlock = wlock
1894 state.wlock = wlock
1895 state.lock = lock
1895 state.lock = lock
1896 _histedit(ui, repo, state, freeargs, opts)
1896 _histedit(ui, repo, state, freeargs, opts)
1897
1897
1898
1898
1899 goalcontinue = b'continue'
1899 goalcontinue = b'continue'
1900 goalabort = b'abort'
1900 goalabort = b'abort'
1901 goaleditplan = b'edit-plan'
1901 goaleditplan = b'edit-plan'
1902 goalnew = b'new'
1902 goalnew = b'new'
1903
1903
1904
1904
1905 def _getgoal(opts):
1905 def _getgoal(opts):
1906 if opts.get(b'continue'):
1906 if opts.get(b'continue'):
1907 return goalcontinue
1907 return goalcontinue
1908 if opts.get(b'abort'):
1908 if opts.get(b'abort'):
1909 return goalabort
1909 return goalabort
1910 if opts.get(b'edit_plan'):
1910 if opts.get(b'edit_plan'):
1911 return goaleditplan
1911 return goaleditplan
1912 return goalnew
1912 return goalnew
1913
1913
1914
1914
1915 def _readfile(ui, path):
1915 def _readfile(ui, path):
1916 if path == b'-':
1916 if path == b'-':
1917 with ui.timeblockedsection(b'histedit'):
1917 with ui.timeblockedsection(b'histedit'):
1918 return ui.fin.read()
1918 return ui.fin.read()
1919 else:
1919 else:
1920 with open(path, b'rb') as f:
1920 with open(path, b'rb') as f:
1921 return f.read()
1921 return f.read()
1922
1922
1923
1923
1924 def _validateargs(ui, repo, freeargs, opts, goal, rules, revs):
1924 def _validateargs(ui, repo, freeargs, opts, goal, rules, revs):
1925 # TODO only abort if we try to histedit mq patches, not just
1925 # TODO only abort if we try to histedit mq patches, not just
1926 # blanket if mq patches are applied somewhere
1926 # blanket if mq patches are applied somewhere
1927 mq = getattr(repo, 'mq', None)
1927 mq = getattr(repo, 'mq', None)
1928 if mq and mq.applied:
1928 if mq and mq.applied:
1929 raise error.Abort(_(b'source has mq patches applied'))
1929 raise error.StateError(_(b'source has mq patches applied'))
1930
1930
1931 # basic argument incompatibility processing
1931 # basic argument incompatibility processing
1932 outg = opts.get(b'outgoing')
1932 outg = opts.get(b'outgoing')
1933 editplan = opts.get(b'edit_plan')
1933 editplan = opts.get(b'edit_plan')
1934 abort = opts.get(b'abort')
1934 abort = opts.get(b'abort')
1935 force = opts.get(b'force')
1935 force = opts.get(b'force')
1936 if force and not outg:
1936 if force and not outg:
1937 raise error.Abort(_(b'--force only allowed with --outgoing'))
1937 raise error.InputError(_(b'--force only allowed with --outgoing'))
1938 if goal == b'continue':
1938 if goal == b'continue':
1939 if any((outg, abort, revs, freeargs, rules, editplan)):
1939 if any((outg, abort, revs, freeargs, rules, editplan)):
1940 raise error.Abort(_(b'no arguments allowed with --continue'))
1940 raise error.InputError(_(b'no arguments allowed with --continue'))
1941 elif goal == b'abort':
1941 elif goal == b'abort':
1942 if any((outg, revs, freeargs, rules, editplan)):
1942 if any((outg, revs, freeargs, rules, editplan)):
1943 raise error.Abort(_(b'no arguments allowed with --abort'))
1943 raise error.InputError(_(b'no arguments allowed with --abort'))
1944 elif goal == b'edit-plan':
1944 elif goal == b'edit-plan':
1945 if any((outg, revs, freeargs)):
1945 if any((outg, revs, freeargs)):
1946 raise error.Abort(
1946 raise error.InputError(
1947 _(b'only --commands argument allowed with --edit-plan')
1947 _(b'only --commands argument allowed with --edit-plan')
1948 )
1948 )
1949 else:
1949 else:
1950 if outg:
1950 if outg:
1951 if revs:
1951 if revs:
1952 raise error.Abort(_(b'no revisions allowed with --outgoing'))
1952 raise error.InputError(
1953 _(b'no revisions allowed with --outgoing')
1954 )
1953 if len(freeargs) > 1:
1955 if len(freeargs) > 1:
1954 raise error.Abort(
1956 raise error.InputError(
1955 _(b'only one repo argument allowed with --outgoing')
1957 _(b'only one repo argument allowed with --outgoing')
1956 )
1958 )
1957 else:
1959 else:
1958 revs.extend(freeargs)
1960 revs.extend(freeargs)
1959 if len(revs) == 0:
1961 if len(revs) == 0:
1960 defaultrev = destutil.desthistedit(ui, repo)
1962 defaultrev = destutil.desthistedit(ui, repo)
1961 if defaultrev is not None:
1963 if defaultrev is not None:
1962 revs.append(defaultrev)
1964 revs.append(defaultrev)
1963
1965
1964 if len(revs) != 1:
1966 if len(revs) != 1:
1965 raise error.Abort(
1967 raise error.InputError(
1966 _(b'histedit requires exactly one ancestor revision')
1968 _(b'histedit requires exactly one ancestor revision')
1967 )
1969 )
1968
1970
1969
1971
1970 def _histedit(ui, repo, state, freeargs, opts):
1972 def _histedit(ui, repo, state, freeargs, opts):
1971 fm = ui.formatter(b'histedit', opts)
1973 fm = ui.formatter(b'histedit', opts)
1972 fm.startitem()
1974 fm.startitem()
1973 goal = _getgoal(opts)
1975 goal = _getgoal(opts)
1974 revs = opts.get(b'rev', [])
1976 revs = opts.get(b'rev', [])
1975 nobackup = not ui.configbool(b'rewrite', b'backup-bundle')
1977 nobackup = not ui.configbool(b'rewrite', b'backup-bundle')
1976 rules = opts.get(b'commands', b'')
1978 rules = opts.get(b'commands', b'')
1977 state.keep = opts.get(b'keep', False)
1979 state.keep = opts.get(b'keep', False)
1978
1980
1979 _validateargs(ui, repo, freeargs, opts, goal, rules, revs)
1981 _validateargs(ui, repo, freeargs, opts, goal, rules, revs)
1980
1982
1981 hastags = False
1983 hastags = False
1982 if revs:
1984 if revs:
1983 revs = scmutil.revrange(repo, revs)
1985 revs = scmutil.revrange(repo, revs)
1984 ctxs = [repo[rev] for rev in revs]
1986 ctxs = [repo[rev] for rev in revs]
1985 for ctx in ctxs:
1987 for ctx in ctxs:
1986 tags = [tag for tag in ctx.tags() if tag != b'tip']
1988 tags = [tag for tag in ctx.tags() if tag != b'tip']
1987 if not hastags:
1989 if not hastags:
1988 hastags = len(tags)
1990 hastags = len(tags)
1989 if hastags:
1991 if hastags:
1990 if ui.promptchoice(
1992 if ui.promptchoice(
1991 _(
1993 _(
1992 b'warning: tags associated with the given'
1994 b'warning: tags associated with the given'
1993 b' changeset will be lost after histedit.\n'
1995 b' changeset will be lost after histedit.\n'
1994 b'do you want to continue (yN)? $$ &Yes $$ &No'
1996 b'do you want to continue (yN)? $$ &Yes $$ &No'
1995 ),
1997 ),
1996 default=1,
1998 default=1,
1997 ):
1999 ):
1998 raise error.Abort(_(b'histedit cancelled\n'))
2000 raise error.CanceledError(_(b'histedit cancelled\n'))
1999 # rebuild state
2001 # rebuild state
2000 if goal == goalcontinue:
2002 if goal == goalcontinue:
2001 state.read()
2003 state.read()
2002 state = bootstrapcontinue(ui, state, opts)
2004 state = bootstrapcontinue(ui, state, opts)
2003 elif goal == goaleditplan:
2005 elif goal == goaleditplan:
2004 _edithisteditplan(ui, repo, state, rules)
2006 _edithisteditplan(ui, repo, state, rules)
2005 return
2007 return
2006 elif goal == goalabort:
2008 elif goal == goalabort:
2007 _aborthistedit(ui, repo, state, nobackup=nobackup)
2009 _aborthistedit(ui, repo, state, nobackup=nobackup)
2008 return
2010 return
2009 else:
2011 else:
2010 # goal == goalnew
2012 # goal == goalnew
2011 _newhistedit(ui, repo, state, revs, freeargs, opts)
2013 _newhistedit(ui, repo, state, revs, freeargs, opts)
2012
2014
2013 _continuehistedit(ui, repo, state)
2015 _continuehistedit(ui, repo, state)
2014 _finishhistedit(ui, repo, state, fm)
2016 _finishhistedit(ui, repo, state, fm)
2015 fm.end()
2017 fm.end()
2016
2018
2017
2019
2018 def _continuehistedit(ui, repo, state):
2020 def _continuehistedit(ui, repo, state):
2019 """This function runs after either:
2021 """This function runs after either:
2020 - bootstrapcontinue (if the goal is 'continue')
2022 - bootstrapcontinue (if the goal is 'continue')
2021 - _newhistedit (if the goal is 'new')
2023 - _newhistedit (if the goal is 'new')
2022 """
2024 """
2023 # preprocess rules so that we can hide inner folds from the user
2025 # preprocess rules so that we can hide inner folds from the user
2024 # and only show one editor
2026 # and only show one editor
2025 actions = state.actions[:]
2027 actions = state.actions[:]
2026 for idx, (action, nextact) in enumerate(zip(actions, actions[1:] + [None])):
2028 for idx, (action, nextact) in enumerate(zip(actions, actions[1:] + [None])):
2027 if action.verb == b'fold' and nextact and nextact.verb == b'fold':
2029 if action.verb == b'fold' and nextact and nextact.verb == b'fold':
2028 state.actions[idx].__class__ = _multifold
2030 state.actions[idx].__class__ = _multifold
2029
2031
2030 # Force an initial state file write, so the user can run --abort/continue
2032 # Force an initial state file write, so the user can run --abort/continue
2031 # even if there's an exception before the first transaction serialize.
2033 # even if there's an exception before the first transaction serialize.
2032 state.write()
2034 state.write()
2033
2035
2034 tr = None
2036 tr = None
2035 # Don't use singletransaction by default since it rolls the entire
2037 # Don't use singletransaction by default since it rolls the entire
2036 # transaction back if an unexpected exception happens (like a
2038 # transaction back if an unexpected exception happens (like a
2037 # pretxncommit hook throws, or the user aborts the commit msg editor).
2039 # pretxncommit hook throws, or the user aborts the commit msg editor).
2038 if ui.configbool(b"histedit", b"singletransaction"):
2040 if ui.configbool(b"histedit", b"singletransaction"):
2039 # Don't use a 'with' for the transaction, since actions may close
2041 # Don't use a 'with' for the transaction, since actions may close
2040 # and reopen a transaction. For example, if the action executes an
2042 # and reopen a transaction. For example, if the action executes an
2041 # external process it may choose to commit the transaction first.
2043 # external process it may choose to commit the transaction first.
2042 tr = repo.transaction(b'histedit')
2044 tr = repo.transaction(b'histedit')
2043 progress = ui.makeprogress(
2045 progress = ui.makeprogress(
2044 _(b"editing"), unit=_(b'changes'), total=len(state.actions)
2046 _(b"editing"), unit=_(b'changes'), total=len(state.actions)
2045 )
2047 )
2046 with progress, util.acceptintervention(tr):
2048 with progress, util.acceptintervention(tr):
2047 while state.actions:
2049 while state.actions:
2048 state.write(tr=tr)
2050 state.write(tr=tr)
2049 actobj = state.actions[0]
2051 actobj = state.actions[0]
2050 progress.increment(item=actobj.torule())
2052 progress.increment(item=actobj.torule())
2051 ui.debug(
2053 ui.debug(
2052 b'histedit: processing %s %s\n' % (actobj.verb, actobj.torule())
2054 b'histedit: processing %s %s\n' % (actobj.verb, actobj.torule())
2053 )
2055 )
2054 parentctx, replacement_ = actobj.run()
2056 parentctx, replacement_ = actobj.run()
2055 state.parentctxnode = parentctx.node()
2057 state.parentctxnode = parentctx.node()
2056 state.replacements.extend(replacement_)
2058 state.replacements.extend(replacement_)
2057 state.actions.pop(0)
2059 state.actions.pop(0)
2058
2060
2059 state.write()
2061 state.write()
2060
2062
2061
2063
2062 def _finishhistedit(ui, repo, state, fm):
2064 def _finishhistedit(ui, repo, state, fm):
2063 """This action runs when histedit is finishing its session"""
2065 """This action runs when histedit is finishing its session"""
2064 mergemod.update(repo[state.parentctxnode])
2066 mergemod.update(repo[state.parentctxnode])
2065
2067
2066 mapping, tmpnodes, created, ntm = processreplacement(state)
2068 mapping, tmpnodes, created, ntm = processreplacement(state)
2067 if mapping:
2069 if mapping:
2068 for prec, succs in pycompat.iteritems(mapping):
2070 for prec, succs in pycompat.iteritems(mapping):
2069 if not succs:
2071 if not succs:
2070 ui.debug(b'histedit: %s is dropped\n' % short(prec))
2072 ui.debug(b'histedit: %s is dropped\n' % short(prec))
2071 else:
2073 else:
2072 ui.debug(
2074 ui.debug(
2073 b'histedit: %s is replaced by %s\n'
2075 b'histedit: %s is replaced by %s\n'
2074 % (short(prec), short(succs[0]))
2076 % (short(prec), short(succs[0]))
2075 )
2077 )
2076 if len(succs) > 1:
2078 if len(succs) > 1:
2077 m = b'histedit: %s'
2079 m = b'histedit: %s'
2078 for n in succs[1:]:
2080 for n in succs[1:]:
2079 ui.debug(m % short(n))
2081 ui.debug(m % short(n))
2080
2082
2081 if not state.keep:
2083 if not state.keep:
2082 if mapping:
2084 if mapping:
2083 movetopmostbookmarks(repo, state.topmost, ntm)
2085 movetopmostbookmarks(repo, state.topmost, ntm)
2084 # TODO update mq state
2086 # TODO update mq state
2085 else:
2087 else:
2086 mapping = {}
2088 mapping = {}
2087
2089
2088 for n in tmpnodes:
2090 for n in tmpnodes:
2089 if n in repo:
2091 if n in repo:
2090 mapping[n] = ()
2092 mapping[n] = ()
2091
2093
2092 # remove entries about unknown nodes
2094 # remove entries about unknown nodes
2093 has_node = repo.unfiltered().changelog.index.has_node
2095 has_node = repo.unfiltered().changelog.index.has_node
2094 mapping = {
2096 mapping = {
2095 k: v
2097 k: v
2096 for k, v in mapping.items()
2098 for k, v in mapping.items()
2097 if has_node(k) and all(has_node(n) for n in v)
2099 if has_node(k) and all(has_node(n) for n in v)
2098 }
2100 }
2099 scmutil.cleanupnodes(repo, mapping, b'histedit')
2101 scmutil.cleanupnodes(repo, mapping, b'histedit')
2100 hf = fm.hexfunc
2102 hf = fm.hexfunc
2101 fl = fm.formatlist
2103 fl = fm.formatlist
2102 fd = fm.formatdict
2104 fd = fm.formatdict
2103 nodechanges = fd(
2105 nodechanges = fd(
2104 {
2106 {
2105 hf(oldn): fl([hf(n) for n in newn], name=b'node')
2107 hf(oldn): fl([hf(n) for n in newn], name=b'node')
2106 for oldn, newn in pycompat.iteritems(mapping)
2108 for oldn, newn in pycompat.iteritems(mapping)
2107 },
2109 },
2108 key=b"oldnode",
2110 key=b"oldnode",
2109 value=b"newnodes",
2111 value=b"newnodes",
2110 )
2112 )
2111 fm.data(nodechanges=nodechanges)
2113 fm.data(nodechanges=nodechanges)
2112
2114
2113 state.clear()
2115 state.clear()
2114 if os.path.exists(repo.sjoin(b'undo')):
2116 if os.path.exists(repo.sjoin(b'undo')):
2115 os.unlink(repo.sjoin(b'undo'))
2117 os.unlink(repo.sjoin(b'undo'))
2116 if repo.vfs.exists(b'histedit-last-edit.txt'):
2118 if repo.vfs.exists(b'histedit-last-edit.txt'):
2117 repo.vfs.unlink(b'histedit-last-edit.txt')
2119 repo.vfs.unlink(b'histedit-last-edit.txt')
2118
2120
2119
2121
2120 def _aborthistedit(ui, repo, state, nobackup=False):
2122 def _aborthistedit(ui, repo, state, nobackup=False):
2121 try:
2123 try:
2122 state.read()
2124 state.read()
2123 __, leafs, tmpnodes, __ = processreplacement(state)
2125 __, leafs, tmpnodes, __ = processreplacement(state)
2124 ui.debug(b'restore wc to old parent %s\n' % short(state.topmost))
2126 ui.debug(b'restore wc to old parent %s\n' % short(state.topmost))
2125
2127
2126 # Recover our old commits if necessary
2128 # Recover our old commits if necessary
2127 if not state.topmost in repo and state.backupfile:
2129 if not state.topmost in repo and state.backupfile:
2128 backupfile = repo.vfs.join(state.backupfile)
2130 backupfile = repo.vfs.join(state.backupfile)
2129 f = hg.openpath(ui, backupfile)
2131 f = hg.openpath(ui, backupfile)
2130 gen = exchange.readbundle(ui, f, backupfile)
2132 gen = exchange.readbundle(ui, f, backupfile)
2131 with repo.transaction(b'histedit.abort') as tr:
2133 with repo.transaction(b'histedit.abort') as tr:
2132 bundle2.applybundle(
2134 bundle2.applybundle(
2133 repo,
2135 repo,
2134 gen,
2136 gen,
2135 tr,
2137 tr,
2136 source=b'histedit',
2138 source=b'histedit',
2137 url=b'bundle:' + backupfile,
2139 url=b'bundle:' + backupfile,
2138 )
2140 )
2139
2141
2140 os.remove(backupfile)
2142 os.remove(backupfile)
2141
2143
2142 # check whether we should update away
2144 # check whether we should update away
2143 if repo.unfiltered().revs(
2145 if repo.unfiltered().revs(
2144 b'parents() and (%n or %ln::)',
2146 b'parents() and (%n or %ln::)',
2145 state.parentctxnode,
2147 state.parentctxnode,
2146 leafs | tmpnodes,
2148 leafs | tmpnodes,
2147 ):
2149 ):
2148 hg.clean(repo, state.topmost, show_stats=True, quietempty=True)
2150 hg.clean(repo, state.topmost, show_stats=True, quietempty=True)
2149 cleanupnode(ui, repo, tmpnodes, nobackup=nobackup)
2151 cleanupnode(ui, repo, tmpnodes, nobackup=nobackup)
2150 cleanupnode(ui, repo, leafs, nobackup=nobackup)
2152 cleanupnode(ui, repo, leafs, nobackup=nobackup)
2151 except Exception:
2153 except Exception:
2152 if state.inprogress():
2154 if state.inprogress():
2153 ui.warn(
2155 ui.warn(
2154 _(
2156 _(
2155 b'warning: encountered an exception during histedit '
2157 b'warning: encountered an exception during histedit '
2156 b'--abort; the repository may not have been completely '
2158 b'--abort; the repository may not have been completely '
2157 b'cleaned up\n'
2159 b'cleaned up\n'
2158 )
2160 )
2159 )
2161 )
2160 raise
2162 raise
2161 finally:
2163 finally:
2162 state.clear()
2164 state.clear()
2163
2165
2164
2166
2165 def hgaborthistedit(ui, repo):
2167 def hgaborthistedit(ui, repo):
2166 state = histeditstate(repo)
2168 state = histeditstate(repo)
2167 nobackup = not ui.configbool(b'rewrite', b'backup-bundle')
2169 nobackup = not ui.configbool(b'rewrite', b'backup-bundle')
2168 with repo.wlock() as wlock, repo.lock() as lock:
2170 with repo.wlock() as wlock, repo.lock() as lock:
2169 state.wlock = wlock
2171 state.wlock = wlock
2170 state.lock = lock
2172 state.lock = lock
2171 _aborthistedit(ui, repo, state, nobackup=nobackup)
2173 _aborthistedit(ui, repo, state, nobackup=nobackup)
2172
2174
2173
2175
2174 def _edithisteditplan(ui, repo, state, rules):
2176 def _edithisteditplan(ui, repo, state, rules):
2175 state.read()
2177 state.read()
2176 if not rules:
2178 if not rules:
2177 comment = geteditcomment(
2179 comment = geteditcomment(
2178 ui, short(state.parentctxnode), short(state.topmost)
2180 ui, short(state.parentctxnode), short(state.topmost)
2179 )
2181 )
2180 rules = ruleeditor(repo, ui, state.actions, comment)
2182 rules = ruleeditor(repo, ui, state.actions, comment)
2181 else:
2183 else:
2182 rules = _readfile(ui, rules)
2184 rules = _readfile(ui, rules)
2183 actions = parserules(rules, state)
2185 actions = parserules(rules, state)
2184 ctxs = [repo[act.node] for act in state.actions if act.node]
2186 ctxs = [repo[act.node] for act in state.actions if act.node]
2185 warnverifyactions(ui, repo, actions, state, ctxs)
2187 warnverifyactions(ui, repo, actions, state, ctxs)
2186 state.actions = actions
2188 state.actions = actions
2187 state.write()
2189 state.write()
2188
2190
2189
2191
2190 def _newhistedit(ui, repo, state, revs, freeargs, opts):
2192 def _newhistedit(ui, repo, state, revs, freeargs, opts):
2191 outg = opts.get(b'outgoing')
2193 outg = opts.get(b'outgoing')
2192 rules = opts.get(b'commands', b'')
2194 rules = opts.get(b'commands', b'')
2193 force = opts.get(b'force')
2195 force = opts.get(b'force')
2194
2196
2195 cmdutil.checkunfinished(repo)
2197 cmdutil.checkunfinished(repo)
2196 cmdutil.bailifchanged(repo)
2198 cmdutil.bailifchanged(repo)
2197
2199
2198 topmost = repo.dirstate.p1()
2200 topmost = repo.dirstate.p1()
2199 if outg:
2201 if outg:
2200 if freeargs:
2202 if freeargs:
2201 remote = freeargs[0]
2203 remote = freeargs[0]
2202 else:
2204 else:
2203 remote = None
2205 remote = None
2204 root = findoutgoing(ui, repo, remote, force, opts)
2206 root = findoutgoing(ui, repo, remote, force, opts)
2205 else:
2207 else:
2206 rr = list(repo.set(b'roots(%ld)', scmutil.revrange(repo, revs)))
2208 rr = list(repo.set(b'roots(%ld)', scmutil.revrange(repo, revs)))
2207 if len(rr) != 1:
2209 if len(rr) != 1:
2208 raise error.Abort(
2210 raise error.InputError(
2209 _(
2211 _(
2210 b'The specified revisions must have '
2212 b'The specified revisions must have '
2211 b'exactly one common root'
2213 b'exactly one common root'
2212 )
2214 )
2213 )
2215 )
2214 root = rr[0].node()
2216 root = rr[0].node()
2215
2217
2216 revs = between(repo, root, topmost, state.keep)
2218 revs = between(repo, root, topmost, state.keep)
2217 if not revs:
2219 if not revs:
2218 raise error.Abort(
2220 raise error.InputError(
2219 _(b'%s is not an ancestor of working directory') % short(root)
2221 _(b'%s is not an ancestor of working directory') % short(root)
2220 )
2222 )
2221
2223
2222 ctxs = [repo[r] for r in revs]
2224 ctxs = [repo[r] for r in revs]
2223
2225
2224 wctx = repo[None]
2226 wctx = repo[None]
2225 # Please don't ask me why `ancestors` is this value. I figured it
2227 # Please don't ask me why `ancestors` is this value. I figured it
2226 # out with print-debugging, not by actually understanding what the
2228 # out with print-debugging, not by actually understanding what the
2227 # merge code is doing. :(
2229 # merge code is doing. :(
2228 ancs = [repo[b'.']]
2230 ancs = [repo[b'.']]
2229 # Sniff-test to make sure we won't collide with untracked files in
2231 # Sniff-test to make sure we won't collide with untracked files in
2230 # the working directory. If we don't do this, we can get a
2232 # the working directory. If we don't do this, we can get a
2231 # collision after we've started histedit and backing out gets ugly
2233 # collision after we've started histedit and backing out gets ugly
2232 # for everyone, especially the user.
2234 # for everyone, especially the user.
2233 for c in [ctxs[0].p1()] + ctxs:
2235 for c in [ctxs[0].p1()] + ctxs:
2234 try:
2236 try:
2235 mergemod.calculateupdates(
2237 mergemod.calculateupdates(
2236 repo,
2238 repo,
2237 wctx,
2239 wctx,
2238 c,
2240 c,
2239 ancs,
2241 ancs,
2240 # These parameters were determined by print-debugging
2242 # These parameters were determined by print-debugging
2241 # what happens later on inside histedit.
2243 # what happens later on inside histedit.
2242 branchmerge=False,
2244 branchmerge=False,
2243 force=False,
2245 force=False,
2244 acceptremote=False,
2246 acceptremote=False,
2245 followcopies=False,
2247 followcopies=False,
2246 )
2248 )
2247 except error.Abort:
2249 except error.Abort:
2248 raise error.Abort(
2250 raise error.StateError(
2249 _(
2251 _(
2250 b"untracked files in working directory conflict with files in %s"
2252 b"untracked files in working directory conflict with files in %s"
2251 )
2253 )
2252 % c
2254 % c
2253 )
2255 )
2254
2256
2255 if not rules:
2257 if not rules:
2256 comment = geteditcomment(ui, short(root), short(topmost))
2258 comment = geteditcomment(ui, short(root), short(topmost))
2257 actions = [pick(state, r) for r in revs]
2259 actions = [pick(state, r) for r in revs]
2258 rules = ruleeditor(repo, ui, actions, comment)
2260 rules = ruleeditor(repo, ui, actions, comment)
2259 else:
2261 else:
2260 rules = _readfile(ui, rules)
2262 rules = _readfile(ui, rules)
2261 actions = parserules(rules, state)
2263 actions = parserules(rules, state)
2262 warnverifyactions(ui, repo, actions, state, ctxs)
2264 warnverifyactions(ui, repo, actions, state, ctxs)
2263
2265
2264 parentctxnode = repo[root].p1().node()
2266 parentctxnode = repo[root].p1().node()
2265
2267
2266 state.parentctxnode = parentctxnode
2268 state.parentctxnode = parentctxnode
2267 state.actions = actions
2269 state.actions = actions
2268 state.topmost = topmost
2270 state.topmost = topmost
2269 state.replacements = []
2271 state.replacements = []
2270
2272
2271 ui.log(
2273 ui.log(
2272 b"histedit",
2274 b"histedit",
2273 b"%d actions to histedit\n",
2275 b"%d actions to histedit\n",
2274 len(actions),
2276 len(actions),
2275 histedit_num_actions=len(actions),
2277 histedit_num_actions=len(actions),
2276 )
2278 )
2277
2279
2278 # Create a backup so we can always abort completely.
2280 # Create a backup so we can always abort completely.
2279 backupfile = None
2281 backupfile = None
2280 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2282 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2281 backupfile = repair.backupbundle(
2283 backupfile = repair.backupbundle(
2282 repo, [parentctxnode], [topmost], root, b'histedit'
2284 repo, [parentctxnode], [topmost], root, b'histedit'
2283 )
2285 )
2284 state.backupfile = backupfile
2286 state.backupfile = backupfile
2285
2287
2286
2288
2287 def _getsummary(ctx):
2289 def _getsummary(ctx):
2288 # a common pattern is to extract the summary but default to the empty
2290 # a common pattern is to extract the summary but default to the empty
2289 # string
2291 # string
2290 summary = ctx.description() or b''
2292 summary = ctx.description() or b''
2291 if summary:
2293 if summary:
2292 summary = summary.splitlines()[0]
2294 summary = summary.splitlines()[0]
2293 return summary
2295 return summary
2294
2296
2295
2297
2296 def bootstrapcontinue(ui, state, opts):
2298 def bootstrapcontinue(ui, state, opts):
2297 repo = state.repo
2299 repo = state.repo
2298
2300
2299 ms = mergestatemod.mergestate.read(repo)
2301 ms = mergestatemod.mergestate.read(repo)
2300 mergeutil.checkunresolved(ms)
2302 mergeutil.checkunresolved(ms)
2301
2303
2302 if state.actions:
2304 if state.actions:
2303 actobj = state.actions.pop(0)
2305 actobj = state.actions.pop(0)
2304
2306
2305 if _isdirtywc(repo):
2307 if _isdirtywc(repo):
2306 actobj.continuedirty()
2308 actobj.continuedirty()
2307 if _isdirtywc(repo):
2309 if _isdirtywc(repo):
2308 abortdirty()
2310 abortdirty()
2309
2311
2310 parentctx, replacements = actobj.continueclean()
2312 parentctx, replacements = actobj.continueclean()
2311
2313
2312 state.parentctxnode = parentctx.node()
2314 state.parentctxnode = parentctx.node()
2313 state.replacements.extend(replacements)
2315 state.replacements.extend(replacements)
2314
2316
2315 return state
2317 return state
2316
2318
2317
2319
2318 def between(repo, old, new, keep):
2320 def between(repo, old, new, keep):
2319 """select and validate the set of revision to edit
2321 """select and validate the set of revision to edit
2320
2322
2321 When keep is false, the specified set can't have children."""
2323 When keep is false, the specified set can't have children."""
2322 revs = repo.revs(b'%n::%n', old, new)
2324 revs = repo.revs(b'%n::%n', old, new)
2323 if revs and not keep:
2325 if revs and not keep:
2324 rewriteutil.precheck(repo, revs, b'edit')
2326 rewriteutil.precheck(repo, revs, b'edit')
2325 if repo.revs(b'(%ld) and merge()', revs):
2327 if repo.revs(b'(%ld) and merge()', revs):
2326 raise error.Abort(_(b'cannot edit history that contains merges'))
2328 raise error.StateError(
2329 _(b'cannot edit history that contains merges')
2330 )
2327 return pycompat.maplist(repo.changelog.node, revs)
2331 return pycompat.maplist(repo.changelog.node, revs)
2328
2332
2329
2333
2330 def ruleeditor(repo, ui, actions, editcomment=b""):
2334 def ruleeditor(repo, ui, actions, editcomment=b""):
2331 """open an editor to edit rules
2335 """open an editor to edit rules
2332
2336
2333 rules are in the format [ [act, ctx], ...] like in state.rules
2337 rules are in the format [ [act, ctx], ...] like in state.rules
2334 """
2338 """
2335 if repo.ui.configbool(b"experimental", b"histedit.autoverb"):
2339 if repo.ui.configbool(b"experimental", b"histedit.autoverb"):
2336 newact = util.sortdict()
2340 newact = util.sortdict()
2337 for act in actions:
2341 for act in actions:
2338 ctx = repo[act.node]
2342 ctx = repo[act.node]
2339 summary = _getsummary(ctx)
2343 summary = _getsummary(ctx)
2340 fword = summary.split(b' ', 1)[0].lower()
2344 fword = summary.split(b' ', 1)[0].lower()
2341 added = False
2345 added = False
2342
2346
2343 # if it doesn't end with the special character '!' just skip this
2347 # if it doesn't end with the special character '!' just skip this
2344 if fword.endswith(b'!'):
2348 if fword.endswith(b'!'):
2345 fword = fword[:-1]
2349 fword = fword[:-1]
2346 if fword in primaryactions | secondaryactions | tertiaryactions:
2350 if fword in primaryactions | secondaryactions | tertiaryactions:
2347 act.verb = fword
2351 act.verb = fword
2348 # get the target summary
2352 # get the target summary
2349 tsum = summary[len(fword) + 1 :].lstrip()
2353 tsum = summary[len(fword) + 1 :].lstrip()
2350 # safe but slow: reverse iterate over the actions so we
2354 # safe but slow: reverse iterate over the actions so we
2351 # don't clash on two commits having the same summary
2355 # don't clash on two commits having the same summary
2352 for na, l in reversed(list(pycompat.iteritems(newact))):
2356 for na, l in reversed(list(pycompat.iteritems(newact))):
2353 actx = repo[na.node]
2357 actx = repo[na.node]
2354 asum = _getsummary(actx)
2358 asum = _getsummary(actx)
2355 if asum == tsum:
2359 if asum == tsum:
2356 added = True
2360 added = True
2357 l.append(act)
2361 l.append(act)
2358 break
2362 break
2359
2363
2360 if not added:
2364 if not added:
2361 newact[act] = []
2365 newact[act] = []
2362
2366
2363 # copy over and flatten the new list
2367 # copy over and flatten the new list
2364 actions = []
2368 actions = []
2365 for na, l in pycompat.iteritems(newact):
2369 for na, l in pycompat.iteritems(newact):
2366 actions.append(na)
2370 actions.append(na)
2367 actions += l
2371 actions += l
2368
2372
2369 rules = b'\n'.join([act.torule() for act in actions])
2373 rules = b'\n'.join([act.torule() for act in actions])
2370 rules += b'\n\n'
2374 rules += b'\n\n'
2371 rules += editcomment
2375 rules += editcomment
2372 rules = ui.edit(
2376 rules = ui.edit(
2373 rules,
2377 rules,
2374 ui.username(),
2378 ui.username(),
2375 {b'prefix': b'histedit'},
2379 {b'prefix': b'histedit'},
2376 repopath=repo.path,
2380 repopath=repo.path,
2377 action=b'histedit',
2381 action=b'histedit',
2378 )
2382 )
2379
2383
2380 # Save edit rules in .hg/histedit-last-edit.txt in case
2384 # Save edit rules in .hg/histedit-last-edit.txt in case
2381 # the user needs to ask for help after something
2385 # the user needs to ask for help after something
2382 # surprising happens.
2386 # surprising happens.
2383 with repo.vfs(b'histedit-last-edit.txt', b'wb') as f:
2387 with repo.vfs(b'histedit-last-edit.txt', b'wb') as f:
2384 f.write(rules)
2388 f.write(rules)
2385
2389
2386 return rules
2390 return rules
2387
2391
2388
2392
2389 def parserules(rules, state):
2393 def parserules(rules, state):
2390 """Read the histedit rules string and return list of action objects"""
2394 """Read the histedit rules string and return list of action objects"""
2391 rules = [
2395 rules = [
2392 l
2396 l
2393 for l in (r.strip() for r in rules.splitlines())
2397 for l in (r.strip() for r in rules.splitlines())
2394 if l and not l.startswith(b'#')
2398 if l and not l.startswith(b'#')
2395 ]
2399 ]
2396 actions = []
2400 actions = []
2397 for r in rules:
2401 for r in rules:
2398 if b' ' not in r:
2402 if b' ' not in r:
2399 raise error.ParseError(_(b'malformed line "%s"') % r)
2403 raise error.ParseError(_(b'malformed line "%s"') % r)
2400 verb, rest = r.split(b' ', 1)
2404 verb, rest = r.split(b' ', 1)
2401
2405
2402 if verb not in actiontable:
2406 if verb not in actiontable:
2403 raise error.ParseError(_(b'unknown action "%s"') % verb)
2407 raise error.ParseError(_(b'unknown action "%s"') % verb)
2404
2408
2405 action = actiontable[verb].fromrule(state, rest)
2409 action = actiontable[verb].fromrule(state, rest)
2406 actions.append(action)
2410 actions.append(action)
2407 return actions
2411 return actions
2408
2412
2409
2413
2410 def warnverifyactions(ui, repo, actions, state, ctxs):
2414 def warnverifyactions(ui, repo, actions, state, ctxs):
2411 try:
2415 try:
2412 verifyactions(actions, state, ctxs)
2416 verifyactions(actions, state, ctxs)
2413 except error.ParseError:
2417 except error.ParseError:
2414 if repo.vfs.exists(b'histedit-last-edit.txt'):
2418 if repo.vfs.exists(b'histedit-last-edit.txt'):
2415 ui.warn(
2419 ui.warn(
2416 _(
2420 _(
2417 b'warning: histedit rules saved '
2421 b'warning: histedit rules saved '
2418 b'to: .hg/histedit-last-edit.txt\n'
2422 b'to: .hg/histedit-last-edit.txt\n'
2419 )
2423 )
2420 )
2424 )
2421 raise
2425 raise
2422
2426
2423
2427
2424 def verifyactions(actions, state, ctxs):
2428 def verifyactions(actions, state, ctxs):
2425 """Verify that there exists exactly one action per given changeset and
2429 """Verify that there exists exactly one action per given changeset and
2426 other constraints.
2430 other constraints.
2427
2431
2428 Will abort if there are to many or too few rules, a malformed rule,
2432 Will abort if there are to many or too few rules, a malformed rule,
2429 or a rule on a changeset outside of the user-given range.
2433 or a rule on a changeset outside of the user-given range.
2430 """
2434 """
2431 expected = {c.node() for c in ctxs}
2435 expected = {c.node() for c in ctxs}
2432 seen = set()
2436 seen = set()
2433 prev = None
2437 prev = None
2434
2438
2435 if actions and actions[0].verb in [b'roll', b'fold']:
2439 if actions and actions[0].verb in [b'roll', b'fold']:
2436 raise error.ParseError(
2440 raise error.ParseError(
2437 _(b'first changeset cannot use verb "%s"') % actions[0].verb
2441 _(b'first changeset cannot use verb "%s"') % actions[0].verb
2438 )
2442 )
2439
2443
2440 for action in actions:
2444 for action in actions:
2441 action.verify(prev, expected, seen)
2445 action.verify(prev, expected, seen)
2442 prev = action
2446 prev = action
2443 if action.node is not None:
2447 if action.node is not None:
2444 seen.add(action.node)
2448 seen.add(action.node)
2445 missing = sorted(expected - seen) # sort to stabilize output
2449 missing = sorted(expected - seen) # sort to stabilize output
2446
2450
2447 if state.repo.ui.configbool(b'histedit', b'dropmissing'):
2451 if state.repo.ui.configbool(b'histedit', b'dropmissing'):
2448 if len(actions) == 0:
2452 if len(actions) == 0:
2449 raise error.ParseError(
2453 raise error.ParseError(
2450 _(b'no rules provided'),
2454 _(b'no rules provided'),
2451 hint=_(b'use strip extension to remove commits'),
2455 hint=_(b'use strip extension to remove commits'),
2452 )
2456 )
2453
2457
2454 drops = [drop(state, n) for n in missing]
2458 drops = [drop(state, n) for n in missing]
2455 # put the in the beginning so they execute immediately and
2459 # put the in the beginning so they execute immediately and
2456 # don't show in the edit-plan in the future
2460 # don't show in the edit-plan in the future
2457 actions[:0] = drops
2461 actions[:0] = drops
2458 elif missing:
2462 elif missing:
2459 raise error.ParseError(
2463 raise error.ParseError(
2460 _(b'missing rules for changeset %s') % short(missing[0]),
2464 _(b'missing rules for changeset %s') % short(missing[0]),
2461 hint=_(
2465 hint=_(
2462 b'use "drop %s" to discard, see also: '
2466 b'use "drop %s" to discard, see also: '
2463 b"'hg help -e histedit.config'"
2467 b"'hg help -e histedit.config'"
2464 )
2468 )
2465 % short(missing[0]),
2469 % short(missing[0]),
2466 )
2470 )
2467
2471
2468
2472
2469 def adjustreplacementsfrommarkers(repo, oldreplacements):
2473 def adjustreplacementsfrommarkers(repo, oldreplacements):
2470 """Adjust replacements from obsolescence markers
2474 """Adjust replacements from obsolescence markers
2471
2475
2472 Replacements structure is originally generated based on
2476 Replacements structure is originally generated based on
2473 histedit's state and does not account for changes that are
2477 histedit's state and does not account for changes that are
2474 not recorded there. This function fixes that by adding
2478 not recorded there. This function fixes that by adding
2475 data read from obsolescence markers"""
2479 data read from obsolescence markers"""
2476 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2480 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2477 return oldreplacements
2481 return oldreplacements
2478
2482
2479 unfi = repo.unfiltered()
2483 unfi = repo.unfiltered()
2480 get_rev = unfi.changelog.index.get_rev
2484 get_rev = unfi.changelog.index.get_rev
2481 obsstore = repo.obsstore
2485 obsstore = repo.obsstore
2482 newreplacements = list(oldreplacements)
2486 newreplacements = list(oldreplacements)
2483 oldsuccs = [r[1] for r in oldreplacements]
2487 oldsuccs = [r[1] for r in oldreplacements]
2484 # successors that have already been added to succstocheck once
2488 # successors that have already been added to succstocheck once
2485 seensuccs = set().union(
2489 seensuccs = set().union(
2486 *oldsuccs
2490 *oldsuccs
2487 ) # create a set from an iterable of tuples
2491 ) # create a set from an iterable of tuples
2488 succstocheck = list(seensuccs)
2492 succstocheck = list(seensuccs)
2489 while succstocheck:
2493 while succstocheck:
2490 n = succstocheck.pop()
2494 n = succstocheck.pop()
2491 missing = get_rev(n) is None
2495 missing = get_rev(n) is None
2492 markers = obsstore.successors.get(n, ())
2496 markers = obsstore.successors.get(n, ())
2493 if missing and not markers:
2497 if missing and not markers:
2494 # dead end, mark it as such
2498 # dead end, mark it as such
2495 newreplacements.append((n, ()))
2499 newreplacements.append((n, ()))
2496 for marker in markers:
2500 for marker in markers:
2497 nsuccs = marker[1]
2501 nsuccs = marker[1]
2498 newreplacements.append((n, nsuccs))
2502 newreplacements.append((n, nsuccs))
2499 for nsucc in nsuccs:
2503 for nsucc in nsuccs:
2500 if nsucc not in seensuccs:
2504 if nsucc not in seensuccs:
2501 seensuccs.add(nsucc)
2505 seensuccs.add(nsucc)
2502 succstocheck.append(nsucc)
2506 succstocheck.append(nsucc)
2503
2507
2504 return newreplacements
2508 return newreplacements
2505
2509
2506
2510
2507 def processreplacement(state):
2511 def processreplacement(state):
2508 """process the list of replacements to return
2512 """process the list of replacements to return
2509
2513
2510 1) the final mapping between original and created nodes
2514 1) the final mapping between original and created nodes
2511 2) the list of temporary node created by histedit
2515 2) the list of temporary node created by histedit
2512 3) the list of new commit created by histedit"""
2516 3) the list of new commit created by histedit"""
2513 replacements = adjustreplacementsfrommarkers(state.repo, state.replacements)
2517 replacements = adjustreplacementsfrommarkers(state.repo, state.replacements)
2514 allsuccs = set()
2518 allsuccs = set()
2515 replaced = set()
2519 replaced = set()
2516 fullmapping = {}
2520 fullmapping = {}
2517 # initialize basic set
2521 # initialize basic set
2518 # fullmapping records all operations recorded in replacement
2522 # fullmapping records all operations recorded in replacement
2519 for rep in replacements:
2523 for rep in replacements:
2520 allsuccs.update(rep[1])
2524 allsuccs.update(rep[1])
2521 replaced.add(rep[0])
2525 replaced.add(rep[0])
2522 fullmapping.setdefault(rep[0], set()).update(rep[1])
2526 fullmapping.setdefault(rep[0], set()).update(rep[1])
2523 new = allsuccs - replaced
2527 new = allsuccs - replaced
2524 tmpnodes = allsuccs & replaced
2528 tmpnodes = allsuccs & replaced
2525 # Reduce content fullmapping into direct relation between original nodes
2529 # Reduce content fullmapping into direct relation between original nodes
2526 # and final node created during history edition
2530 # and final node created during history edition
2527 # Dropped changeset are replaced by an empty list
2531 # Dropped changeset are replaced by an empty list
2528 toproceed = set(fullmapping)
2532 toproceed = set(fullmapping)
2529 final = {}
2533 final = {}
2530 while toproceed:
2534 while toproceed:
2531 for x in list(toproceed):
2535 for x in list(toproceed):
2532 succs = fullmapping[x]
2536 succs = fullmapping[x]
2533 for s in list(succs):
2537 for s in list(succs):
2534 if s in toproceed:
2538 if s in toproceed:
2535 # non final node with unknown closure
2539 # non final node with unknown closure
2536 # We can't process this now
2540 # We can't process this now
2537 break
2541 break
2538 elif s in final:
2542 elif s in final:
2539 # non final node, replace with closure
2543 # non final node, replace with closure
2540 succs.remove(s)
2544 succs.remove(s)
2541 succs.update(final[s])
2545 succs.update(final[s])
2542 else:
2546 else:
2543 final[x] = succs
2547 final[x] = succs
2544 toproceed.remove(x)
2548 toproceed.remove(x)
2545 # remove tmpnodes from final mapping
2549 # remove tmpnodes from final mapping
2546 for n in tmpnodes:
2550 for n in tmpnodes:
2547 del final[n]
2551 del final[n]
2548 # we expect all changes involved in final to exist in the repo
2552 # we expect all changes involved in final to exist in the repo
2549 # turn `final` into list (topologically sorted)
2553 # turn `final` into list (topologically sorted)
2550 get_rev = state.repo.changelog.index.get_rev
2554 get_rev = state.repo.changelog.index.get_rev
2551 for prec, succs in final.items():
2555 for prec, succs in final.items():
2552 final[prec] = sorted(succs, key=get_rev)
2556 final[prec] = sorted(succs, key=get_rev)
2553
2557
2554 # computed topmost element (necessary for bookmark)
2558 # computed topmost element (necessary for bookmark)
2555 if new:
2559 if new:
2556 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
2560 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
2557 elif not final:
2561 elif not final:
2558 # Nothing rewritten at all. we won't need `newtopmost`
2562 # Nothing rewritten at all. we won't need `newtopmost`
2559 # It is the same as `oldtopmost` and `processreplacement` know it
2563 # It is the same as `oldtopmost` and `processreplacement` know it
2560 newtopmost = None
2564 newtopmost = None
2561 else:
2565 else:
2562 # every body died. The newtopmost is the parent of the root.
2566 # every body died. The newtopmost is the parent of the root.
2563 r = state.repo.changelog.rev
2567 r = state.repo.changelog.rev
2564 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
2568 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
2565
2569
2566 return final, tmpnodes, new, newtopmost
2570 return final, tmpnodes, new, newtopmost
2567
2571
2568
2572
2569 def movetopmostbookmarks(repo, oldtopmost, newtopmost):
2573 def movetopmostbookmarks(repo, oldtopmost, newtopmost):
2570 """Move bookmark from oldtopmost to newly created topmost
2574 """Move bookmark from oldtopmost to newly created topmost
2571
2575
2572 This is arguably a feature and we may only want that for the active
2576 This is arguably a feature and we may only want that for the active
2573 bookmark. But the behavior is kept compatible with the old version for now.
2577 bookmark. But the behavior is kept compatible with the old version for now.
2574 """
2578 """
2575 if not oldtopmost or not newtopmost:
2579 if not oldtopmost or not newtopmost:
2576 return
2580 return
2577 oldbmarks = repo.nodebookmarks(oldtopmost)
2581 oldbmarks = repo.nodebookmarks(oldtopmost)
2578 if oldbmarks:
2582 if oldbmarks:
2579 with repo.lock(), repo.transaction(b'histedit') as tr:
2583 with repo.lock(), repo.transaction(b'histedit') as tr:
2580 marks = repo._bookmarks
2584 marks = repo._bookmarks
2581 changes = []
2585 changes = []
2582 for name in oldbmarks:
2586 for name in oldbmarks:
2583 changes.append((name, newtopmost))
2587 changes.append((name, newtopmost))
2584 marks.applychanges(repo, tr, changes)
2588 marks.applychanges(repo, tr, changes)
2585
2589
2586
2590
2587 def cleanupnode(ui, repo, nodes, nobackup=False):
2591 def cleanupnode(ui, repo, nodes, nobackup=False):
2588 """strip a group of nodes from the repository
2592 """strip a group of nodes from the repository
2589
2593
2590 The set of node to strip may contains unknown nodes."""
2594 The set of node to strip may contains unknown nodes."""
2591 with repo.lock():
2595 with repo.lock():
2592 # do not let filtering get in the way of the cleanse
2596 # do not let filtering get in the way of the cleanse
2593 # we should probably get rid of obsolescence marker created during the
2597 # we should probably get rid of obsolescence marker created during the
2594 # histedit, but we currently do not have such information.
2598 # histedit, but we currently do not have such information.
2595 repo = repo.unfiltered()
2599 repo = repo.unfiltered()
2596 # Find all nodes that need to be stripped
2600 # Find all nodes that need to be stripped
2597 # (we use %lr instead of %ln to silently ignore unknown items)
2601 # (we use %lr instead of %ln to silently ignore unknown items)
2598 has_node = repo.changelog.index.has_node
2602 has_node = repo.changelog.index.has_node
2599 nodes = sorted(n for n in nodes if has_node(n))
2603 nodes = sorted(n for n in nodes if has_node(n))
2600 roots = [c.node() for c in repo.set(b"roots(%ln)", nodes)]
2604 roots = [c.node() for c in repo.set(b"roots(%ln)", nodes)]
2601 if roots:
2605 if roots:
2602 backup = not nobackup
2606 backup = not nobackup
2603 repair.strip(ui, repo, roots, backup=backup)
2607 repair.strip(ui, repo, roots, backup=backup)
2604
2608
2605
2609
2606 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
2610 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
2607 if isinstance(nodelist, bytes):
2611 if isinstance(nodelist, bytes):
2608 nodelist = [nodelist]
2612 nodelist = [nodelist]
2609 state = histeditstate(repo)
2613 state = histeditstate(repo)
2610 if state.inprogress():
2614 if state.inprogress():
2611 state.read()
2615 state.read()
2612 histedit_nodes = {
2616 histedit_nodes = {
2613 action.node for action in state.actions if action.node
2617 action.node for action in state.actions if action.node
2614 }
2618 }
2615 common_nodes = histedit_nodes & set(nodelist)
2619 common_nodes = histedit_nodes & set(nodelist)
2616 if common_nodes:
2620 if common_nodes:
2617 raise error.Abort(
2621 raise error.Abort(
2618 _(b"histedit in progress, can't strip %s")
2622 _(b"histedit in progress, can't strip %s")
2619 % b', '.join(short(x) for x in common_nodes)
2623 % b', '.join(short(x) for x in common_nodes)
2620 )
2624 )
2621 return orig(ui, repo, nodelist, *args, **kwargs)
2625 return orig(ui, repo, nodelist, *args, **kwargs)
2622
2626
2623
2627
2624 extensions.wrapfunction(repair, b'strip', stripwrapper)
2628 extensions.wrapfunction(repair, b'strip', stripwrapper)
2625
2629
2626
2630
2627 def summaryhook(ui, repo):
2631 def summaryhook(ui, repo):
2628 state = histeditstate(repo)
2632 state = histeditstate(repo)
2629 if not state.inprogress():
2633 if not state.inprogress():
2630 return
2634 return
2631 state.read()
2635 state.read()
2632 if state.actions:
2636 if state.actions:
2633 # i18n: column positioning for "hg summary"
2637 # i18n: column positioning for "hg summary"
2634 ui.write(
2638 ui.write(
2635 _(b'hist: %s (histedit --continue)\n')
2639 _(b'hist: %s (histedit --continue)\n')
2636 % (
2640 % (
2637 ui.label(_(b'%d remaining'), b'histedit.remaining')
2641 ui.label(_(b'%d remaining'), b'histedit.remaining')
2638 % len(state.actions)
2642 % len(state.actions)
2639 )
2643 )
2640 )
2644 )
2641
2645
2642
2646
2643 def extsetup(ui):
2647 def extsetup(ui):
2644 cmdutil.summaryhooks.add(b'histedit', summaryhook)
2648 cmdutil.summaryhooks.add(b'histedit', summaryhook)
2645 statemod.addunfinished(
2649 statemod.addunfinished(
2646 b'histedit',
2650 b'histedit',
2647 fname=b'histedit-state',
2651 fname=b'histedit-state',
2648 allowcommit=True,
2652 allowcommit=True,
2649 continueflag=True,
2653 continueflag=True,
2650 abortfunc=hgaborthistedit,
2654 abortfunc=hgaborthistedit,
2651 )
2655 )
@@ -1,605 +1,605
1 #testcases abortcommand abortflag
1 #testcases abortcommand abortflag
2
2
3 #if abortflag
3 #if abortflag
4 $ cat >> $HGRCPATH <<EOF
4 $ cat >> $HGRCPATH <<EOF
5 > [alias]
5 > [alias]
6 > abort = histedit --abort
6 > abort = histedit --abort
7 > EOF
7 > EOF
8 #endif
8 #endif
9
9
10 Test argument handling and various data parsing
10 Test argument handling and various data parsing
11 ==================================================
11 ==================================================
12
12
13
13
14 Enable extensions used by this test.
14 Enable extensions used by this test.
15 $ cat >>$HGRCPATH <<EOF
15 $ cat >>$HGRCPATH <<EOF
16 > [extensions]
16 > [extensions]
17 > histedit=
17 > histedit=
18 > EOF
18 > EOF
19
19
20 Repo setup.
20 Repo setup.
21 $ hg init foo
21 $ hg init foo
22 $ cd foo
22 $ cd foo
23 $ echo alpha >> alpha
23 $ echo alpha >> alpha
24 $ hg addr
24 $ hg addr
25 adding alpha
25 adding alpha
26 $ hg ci -m one
26 $ hg ci -m one
27 $ echo alpha >> alpha
27 $ echo alpha >> alpha
28 $ hg ci -m two
28 $ hg ci -m two
29 $ echo alpha >> alpha
29 $ echo alpha >> alpha
30 $ hg ci -m three
30 $ hg ci -m three
31 $ echo alpha >> alpha
31 $ echo alpha >> alpha
32 $ hg ci -m four
32 $ hg ci -m four
33 $ echo alpha >> alpha
33 $ echo alpha >> alpha
34 $ hg ci -m five
34 $ hg ci -m five
35
35
36 $ hg log --style compact --graph
36 $ hg log --style compact --graph
37 @ 4[tip] 08d98a8350f3 1970-01-01 00:00 +0000 test
37 @ 4[tip] 08d98a8350f3 1970-01-01 00:00 +0000 test
38 | five
38 | five
39 |
39 |
40 o 3 c8e68270e35a 1970-01-01 00:00 +0000 test
40 o 3 c8e68270e35a 1970-01-01 00:00 +0000 test
41 | four
41 | four
42 |
42 |
43 o 2 eb57da33312f 1970-01-01 00:00 +0000 test
43 o 2 eb57da33312f 1970-01-01 00:00 +0000 test
44 | three
44 | three
45 |
45 |
46 o 1 579e40513370 1970-01-01 00:00 +0000 test
46 o 1 579e40513370 1970-01-01 00:00 +0000 test
47 | two
47 | two
48 |
48 |
49 o 0 6058cbb6cfd7 1970-01-01 00:00 +0000 test
49 o 0 6058cbb6cfd7 1970-01-01 00:00 +0000 test
50 one
50 one
51
51
52
52
53 histedit --continue/--abort with no existing state
53 histedit --continue/--abort with no existing state
54 --------------------------------------------------
54 --------------------------------------------------
55
55
56 $ hg histedit --continue
56 $ hg histedit --continue
57 abort: no histedit in progress
57 abort: no histedit in progress
58 [20]
58 [20]
59 $ hg abort
59 $ hg abort
60 abort: no histedit in progress (abortflag !)
60 abort: no histedit in progress (abortflag !)
61 abort: no operation in progress (abortcommand !)
61 abort: no operation in progress (abortcommand !)
62 [20]
62 [20]
63
63
64 Run a dummy edit to make sure we get tip^^ correctly via revsingle.
64 Run a dummy edit to make sure we get tip^^ correctly via revsingle.
65 --------------------------------------------------------------------
65 --------------------------------------------------------------------
66
66
67 $ HGEDITOR=cat hg histedit "tip^^"
67 $ HGEDITOR=cat hg histedit "tip^^"
68 pick eb57da33312f 2 three
68 pick eb57da33312f 2 three
69 pick c8e68270e35a 3 four
69 pick c8e68270e35a 3 four
70 pick 08d98a8350f3 4 five
70 pick 08d98a8350f3 4 five
71
71
72 # Edit history between eb57da33312f and 08d98a8350f3
72 # Edit history between eb57da33312f and 08d98a8350f3
73 #
73 #
74 # Commits are listed from least to most recent
74 # Commits are listed from least to most recent
75 #
75 #
76 # You can reorder changesets by reordering the lines
76 # You can reorder changesets by reordering the lines
77 #
77 #
78 # Commands:
78 # Commands:
79 #
79 #
80 # e, edit = use commit, but allow edits before making new commit
80 # e, edit = use commit, but allow edits before making new commit
81 # m, mess = edit commit message without changing commit content
81 # m, mess = edit commit message without changing commit content
82 # p, pick = use commit
82 # p, pick = use commit
83 # b, base = checkout changeset and apply further changesets from there
83 # b, base = checkout changeset and apply further changesets from there
84 # d, drop = remove commit from history
84 # d, drop = remove commit from history
85 # f, fold = use commit, but combine it with the one above
85 # f, fold = use commit, but combine it with the one above
86 # r, roll = like fold, but discard this commit's description and date
86 # r, roll = like fold, but discard this commit's description and date
87 #
87 #
88
88
89 Run on a revision not ancestors of the current working directory.
89 Run on a revision not ancestors of the current working directory.
90 --------------------------------------------------------------------
90 --------------------------------------------------------------------
91
91
92 $ hg up 2
92 $ hg up 2
93 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
94 $ hg histedit -r 4
94 $ hg histedit -r 4
95 abort: 08d98a8350f3 is not an ancestor of working directory
95 abort: 08d98a8350f3 is not an ancestor of working directory
96 [255]
96 [10]
97 $ hg up --quiet
97 $ hg up --quiet
98
98
99
99
100 Test that we pick the minimum of a revrange
100 Test that we pick the minimum of a revrange
101 ---------------------------------------
101 ---------------------------------------
102
102
103 $ HGEDITOR=cat hg histedit '2::' --commands - << EOF
103 $ HGEDITOR=cat hg histedit '2::' --commands - << EOF
104 > pick eb57da33312f 2 three
104 > pick eb57da33312f 2 three
105 > pick c8e68270e35a 3 four
105 > pick c8e68270e35a 3 four
106 > pick 08d98a8350f3 4 five
106 > pick 08d98a8350f3 4 five
107 > EOF
107 > EOF
108 $ hg up --quiet
108 $ hg up --quiet
109
109
110 $ HGEDITOR=cat hg histedit 'tip:2' --commands - << EOF
110 $ HGEDITOR=cat hg histedit 'tip:2' --commands - << EOF
111 > pick eb57da33312f 2 three
111 > pick eb57da33312f 2 three
112 > pick c8e68270e35a 3 four
112 > pick c8e68270e35a 3 four
113 > pick 08d98a8350f3 4 five
113 > pick 08d98a8350f3 4 five
114 > EOF
114 > EOF
115 $ hg up --quiet
115 $ hg up --quiet
116
116
117 Test config specified default
117 Test config specified default
118 -----------------------------
118 -----------------------------
119
119
120 $ HGEDITOR=cat hg histedit --config "histedit.defaultrev=only(.) - ::eb57da33312f" --commands - << EOF
120 $ HGEDITOR=cat hg histedit --config "histedit.defaultrev=only(.) - ::eb57da33312f" --commands - << EOF
121 > pick c8e68270e35a 3 four
121 > pick c8e68270e35a 3 four
122 > pick 08d98a8350f3 4 five
122 > pick 08d98a8350f3 4 five
123 > EOF
123 > EOF
124
124
125 Test invalid config default
125 Test invalid config default
126 ---------------------------
126 ---------------------------
127
127
128 $ hg histedit --config "histedit.defaultrev="
128 $ hg histedit --config "histedit.defaultrev="
129 config error: config option histedit.defaultrev can't be empty
129 config error: config option histedit.defaultrev can't be empty
130 [30]
130 [30]
131
131
132 Run on a revision not descendants of the initial parent
132 Run on a revision not descendants of the initial parent
133 --------------------------------------------------------------------
133 --------------------------------------------------------------------
134
134
135 Test the message shown for inconsistent histedit state, which may be
135 Test the message shown for inconsistent histedit state, which may be
136 created (and forgotten) by Mercurial earlier than 2.7. This emulates
136 created (and forgotten) by Mercurial earlier than 2.7. This emulates
137 Mercurial earlier than 2.7 by renaming ".hg/histedit-state"
137 Mercurial earlier than 2.7 by renaming ".hg/histedit-state"
138 temporarily.
138 temporarily.
139
139
140 $ hg log -G -T '{rev} {shortest(node)} {desc}\n' -r 2::
140 $ hg log -G -T '{rev} {shortest(node)} {desc}\n' -r 2::
141 @ 4 08d9 five
141 @ 4 08d9 five
142 |
142 |
143 o 3 c8e6 four
143 o 3 c8e6 four
144 |
144 |
145 o 2 eb57 three
145 o 2 eb57 three
146 |
146 |
147 ~
147 ~
148 $ HGEDITOR=cat hg histedit -r 4 --commands - << EOF
148 $ HGEDITOR=cat hg histedit -r 4 --commands - << EOF
149 > edit 08d98a8350f3 4 five
149 > edit 08d98a8350f3 4 five
150 > EOF
150 > EOF
151 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
151 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
152 Editing (08d98a8350f3), commit as needed now to split the change
152 Editing (08d98a8350f3), commit as needed now to split the change
153 (to edit 08d98a8350f3, `hg histedit --continue` after making changes)
153 (to edit 08d98a8350f3, `hg histedit --continue` after making changes)
154 [240]
154 [240]
155
155
156 $ hg graft --continue
156 $ hg graft --continue
157 abort: no graft in progress
157 abort: no graft in progress
158 (continue: hg histedit --continue)
158 (continue: hg histedit --continue)
159 [20]
159 [20]
160
160
161 $ mv .hg/histedit-state .hg/histedit-state.back
161 $ mv .hg/histedit-state .hg/histedit-state.back
162 $ hg update --quiet --clean 2
162 $ hg update --quiet --clean 2
163 $ echo alpha >> alpha
163 $ echo alpha >> alpha
164 $ mv .hg/histedit-state.back .hg/histedit-state
164 $ mv .hg/histedit-state.back .hg/histedit-state
165
165
166 $ hg histedit --continue
166 $ hg histedit --continue
167 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/08d98a8350f3-02594089-histedit.hg
167 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/08d98a8350f3-02594089-histedit.hg
168 $ hg log -G -T '{rev} {shortest(node)} {desc}\n' -r 2::
168 $ hg log -G -T '{rev} {shortest(node)} {desc}\n' -r 2::
169 @ 4 f5ed five
169 @ 4 f5ed five
170 |
170 |
171 | o 3 c8e6 four
171 | o 3 c8e6 four
172 |/
172 |/
173 o 2 eb57 three
173 o 2 eb57 three
174 |
174 |
175 ~
175 ~
176
176
177 $ hg unbundle -q $TESTTMP/foo/.hg/strip-backup/08d98a8350f3-02594089-histedit.hg
177 $ hg unbundle -q $TESTTMP/foo/.hg/strip-backup/08d98a8350f3-02594089-histedit.hg
178 $ hg strip -q -r f5ed --config extensions.strip=
178 $ hg strip -q -r f5ed --config extensions.strip=
179 $ hg up -q 08d98a8350f3
179 $ hg up -q 08d98a8350f3
180
180
181 Test that missing revisions are detected
181 Test that missing revisions are detected
182 ---------------------------------------
182 ---------------------------------------
183
183
184 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
184 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
185 > pick eb57da33312f 2 three
185 > pick eb57da33312f 2 three
186 > pick 08d98a8350f3 4 five
186 > pick 08d98a8350f3 4 five
187 > EOF
187 > EOF
188 hg: parse error: missing rules for changeset c8e68270e35a
188 hg: parse error: missing rules for changeset c8e68270e35a
189 (use "drop c8e68270e35a" to discard, see also: 'hg help -e histedit.config')
189 (use "drop c8e68270e35a" to discard, see also: 'hg help -e histedit.config')
190 [10]
190 [10]
191
191
192 Test that extra revisions are detected
192 Test that extra revisions are detected
193 ---------------------------------------
193 ---------------------------------------
194
194
195 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
195 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
196 > pick 6058cbb6cfd7 0 one
196 > pick 6058cbb6cfd7 0 one
197 > pick c8e68270e35a 3 four
197 > pick c8e68270e35a 3 four
198 > pick 08d98a8350f3 4 five
198 > pick 08d98a8350f3 4 five
199 > EOF
199 > EOF
200 hg: parse error: pick "6058cbb6cfd7" changeset was not a candidate
200 hg: parse error: pick "6058cbb6cfd7" changeset was not a candidate
201 (only use listed changesets)
201 (only use listed changesets)
202 [10]
202 [10]
203
203
204 Test malformed line
204 Test malformed line
205 ---------------------------------------
205 ---------------------------------------
206
206
207 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
207 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
208 > pickeb57da33312f2three
208 > pickeb57da33312f2three
209 > pick c8e68270e35a 3 four
209 > pick c8e68270e35a 3 four
210 > pick 08d98a8350f3 4 five
210 > pick 08d98a8350f3 4 five
211 > EOF
211 > EOF
212 hg: parse error: malformed line "pickeb57da33312f2three"
212 hg: parse error: malformed line "pickeb57da33312f2three"
213 [10]
213 [10]
214
214
215 Test unknown changeset
215 Test unknown changeset
216 ---------------------------------------
216 ---------------------------------------
217
217
218 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
218 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
219 > pick 0123456789ab 2 three
219 > pick 0123456789ab 2 three
220 > pick c8e68270e35a 3 four
220 > pick c8e68270e35a 3 four
221 > pick 08d98a8350f3 4 five
221 > pick 08d98a8350f3 4 five
222 > EOF
222 > EOF
223 hg: parse error: unknown changeset 0123456789ab listed
223 hg: parse error: unknown changeset 0123456789ab listed
224 [10]
224 [10]
225
225
226 Test unknown command
226 Test unknown command
227 ---------------------------------------
227 ---------------------------------------
228
228
229 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
229 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
230 > coin eb57da33312f 2 three
230 > coin eb57da33312f 2 three
231 > pick c8e68270e35a 3 four
231 > pick c8e68270e35a 3 four
232 > pick 08d98a8350f3 4 five
232 > pick 08d98a8350f3 4 five
233 > EOF
233 > EOF
234 hg: parse error: unknown action "coin"
234 hg: parse error: unknown action "coin"
235 [10]
235 [10]
236
236
237 Test duplicated changeset
237 Test duplicated changeset
238 ---------------------------------------
238 ---------------------------------------
239
239
240 So one is missing and one appear twice.
240 So one is missing and one appear twice.
241
241
242 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
242 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
243 > pick eb57da33312f 2 three
243 > pick eb57da33312f 2 three
244 > pick eb57da33312f 2 three
244 > pick eb57da33312f 2 three
245 > pick 08d98a8350f3 4 five
245 > pick 08d98a8350f3 4 five
246 > EOF
246 > EOF
247 hg: parse error: duplicated command for changeset eb57da33312f
247 hg: parse error: duplicated command for changeset eb57da33312f
248 [10]
248 [10]
249
249
250 Test bogus rev
250 Test bogus rev
251 ---------------------------------------
251 ---------------------------------------
252
252
253 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
253 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
254 > pick eb57da33312f 2 three
254 > pick eb57da33312f 2 three
255 > pick 0u98
255 > pick 0u98
256 > pick 08d98a8350f3 4 five
256 > pick 08d98a8350f3 4 five
257 > EOF
257 > EOF
258 hg: parse error: invalid changeset 0u98
258 hg: parse error: invalid changeset 0u98
259 [10]
259 [10]
260
260
261 Test short version of command
261 Test short version of command
262 ---------------------------------------
262 ---------------------------------------
263
263
264 Note: we use varying amounts of white space between command name and changeset
264 Note: we use varying amounts of white space between command name and changeset
265 short hash. This tests issue3893.
265 short hash. This tests issue3893.
266
266
267 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
267 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
268 > pick eb57da33312f 2 three
268 > pick eb57da33312f 2 three
269 > p c8e68270e35a 3 four
269 > p c8e68270e35a 3 four
270 > f 08d98a8350f3 4 five
270 > f 08d98a8350f3 4 five
271 > EOF
271 > EOF
272 four
272 four
273 ***
273 ***
274 five
274 five
275
275
276
276
277
277
278 HG: Enter commit message. Lines beginning with 'HG:' are removed.
278 HG: Enter commit message. Lines beginning with 'HG:' are removed.
279 HG: Leave message empty to abort commit.
279 HG: Leave message empty to abort commit.
280 HG: --
280 HG: --
281 HG: user: test
281 HG: user: test
282 HG: branch 'default'
282 HG: branch 'default'
283 HG: changed alpha
283 HG: changed alpha
284 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/c8e68270e35a-63d8b8d8-histedit.hg
284 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/c8e68270e35a-63d8b8d8-histedit.hg
285
285
286 $ hg update -q 2
286 $ hg update -q 2
287 $ echo x > x
287 $ echo x > x
288 $ hg add x
288 $ hg add x
289 $ hg commit -m'x' x
289 $ hg commit -m'x' x
290 created new head
290 created new head
291 $ hg histedit -r 'heads(all())'
291 $ hg histedit -r 'heads(all())'
292 abort: The specified revisions must have exactly one common root
292 abort: The specified revisions must have exactly one common root
293 [255]
293 [10]
294
294
295 Test that trimming description using multi-byte characters
295 Test that trimming description using multi-byte characters
296 --------------------------------------------------------------------
296 --------------------------------------------------------------------
297
297
298 $ "$PYTHON" <<EOF
298 $ "$PYTHON" <<EOF
299 > fp = open('logfile', 'wb')
299 > fp = open('logfile', 'wb')
300 > fp.write(b'12345678901234567890123456789012345678901234567890' +
300 > fp.write(b'12345678901234567890123456789012345678901234567890' +
301 > b'12345') # there are 5 more columns for 80 columns
301 > b'12345') # there are 5 more columns for 80 columns
302 >
302 >
303 > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes
303 > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes
304 > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8'))
304 > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8'))
305 >
305 >
306 > fp.close()
306 > fp.close()
307 > EOF
307 > EOF
308 $ echo xx >> x
308 $ echo xx >> x
309 $ hg --encoding utf-8 commit --logfile logfile
309 $ hg --encoding utf-8 commit --logfile logfile
310
310
311 $ HGEDITOR=cat hg --encoding utf-8 histedit tip
311 $ HGEDITOR=cat hg --encoding utf-8 histedit tip
312 pick 3d3ea1f3a10b 5 1234567890123456789012345678901234567890123456789012345\xe3\x81\x82... (esc)
312 pick 3d3ea1f3a10b 5 1234567890123456789012345678901234567890123456789012345\xe3\x81\x82... (esc)
313
313
314 # Edit history between 3d3ea1f3a10b and 3d3ea1f3a10b
314 # Edit history between 3d3ea1f3a10b and 3d3ea1f3a10b
315 #
315 #
316 # Commits are listed from least to most recent
316 # Commits are listed from least to most recent
317 #
317 #
318 # You can reorder changesets by reordering the lines
318 # You can reorder changesets by reordering the lines
319 #
319 #
320 # Commands:
320 # Commands:
321 #
321 #
322 # e, edit = use commit, but allow edits before making new commit
322 # e, edit = use commit, but allow edits before making new commit
323 # m, mess = edit commit message without changing commit content
323 # m, mess = edit commit message without changing commit content
324 # p, pick = use commit
324 # p, pick = use commit
325 # b, base = checkout changeset and apply further changesets from there
325 # b, base = checkout changeset and apply further changesets from there
326 # d, drop = remove commit from history
326 # d, drop = remove commit from history
327 # f, fold = use commit, but combine it with the one above
327 # f, fold = use commit, but combine it with the one above
328 # r, roll = like fold, but discard this commit's description and date
328 # r, roll = like fold, but discard this commit's description and date
329 #
329 #
330
330
331 Test --continue with --keep
331 Test --continue with --keep
332
332
333 $ hg strip -q -r . --config extensions.strip=
333 $ hg strip -q -r . --config extensions.strip=
334 $ hg histedit '.^' -q --keep --commands - << EOF
334 $ hg histedit '.^' -q --keep --commands - << EOF
335 > edit eb57da33312f 2 three
335 > edit eb57da33312f 2 three
336 > pick f3cfcca30c44 4 x
336 > pick f3cfcca30c44 4 x
337 > EOF
337 > EOF
338 Editing (eb57da33312f), commit as needed now to split the change
338 Editing (eb57da33312f), commit as needed now to split the change
339 (to edit eb57da33312f, `hg histedit --continue` after making changes)
339 (to edit eb57da33312f, `hg histedit --continue` after making changes)
340 [240]
340 [240]
341 $ echo edit >> alpha
341 $ echo edit >> alpha
342 $ hg histedit -q --continue
342 $ hg histedit -q --continue
343 $ hg log -G -T '{rev}:{node|short} {desc}'
343 $ hg log -G -T '{rev}:{node|short} {desc}'
344 @ 6:8fda0c726bf2 x
344 @ 6:8fda0c726bf2 x
345 |
345 |
346 o 5:63379946892c three
346 o 5:63379946892c three
347 |
347 |
348 | o 4:f3cfcca30c44 x
348 | o 4:f3cfcca30c44 x
349 | |
349 | |
350 | | o 3:2a30f3cfee78 four
350 | | o 3:2a30f3cfee78 four
351 | |/ ***
351 | |/ ***
352 | | five
352 | | five
353 | o 2:eb57da33312f three
353 | o 2:eb57da33312f three
354 |/
354 |/
355 o 1:579e40513370 two
355 o 1:579e40513370 two
356 |
356 |
357 o 0:6058cbb6cfd7 one
357 o 0:6058cbb6cfd7 one
358
358
359
359
360 Test that abort fails gracefully on exception
360 Test that abort fails gracefully on exception
361 ----------------------------------------------
361 ----------------------------------------------
362 $ hg histedit . -q --commands - << EOF
362 $ hg histedit . -q --commands - << EOF
363 > edit 8fda0c726bf2 6 x
363 > edit 8fda0c726bf2 6 x
364 > EOF
364 > EOF
365 Editing (8fda0c726bf2), commit as needed now to split the change
365 Editing (8fda0c726bf2), commit as needed now to split the change
366 (to edit 8fda0c726bf2, `hg histedit --continue` after making changes)
366 (to edit 8fda0c726bf2, `hg histedit --continue` after making changes)
367 [240]
367 [240]
368 Corrupt histedit state file
368 Corrupt histedit state file
369 $ sed 's/8fda0c726bf2/123456789012/' .hg/histedit-state > ../corrupt-histedit
369 $ sed 's/8fda0c726bf2/123456789012/' .hg/histedit-state > ../corrupt-histedit
370 $ mv ../corrupt-histedit .hg/histedit-state
370 $ mv ../corrupt-histedit .hg/histedit-state
371 $ hg abort
371 $ hg abort
372 warning: encountered an exception during histedit --abort; the repository may not have been completely cleaned up
372 warning: encountered an exception during histedit --abort; the repository may not have been completely cleaned up
373 abort: $TESTTMP/foo/.hg/strip-backup/*-histedit.hg: $ENOENT$ (glob) (windows !)
373 abort: $TESTTMP/foo/.hg/strip-backup/*-histedit.hg: $ENOENT$ (glob) (windows !)
374 abort: $ENOENT$: '$TESTTMP/foo/.hg/strip-backup/*-histedit.hg' (glob) (no-windows !)
374 abort: $ENOENT$: '$TESTTMP/foo/.hg/strip-backup/*-histedit.hg' (glob) (no-windows !)
375 [255]
375 [255]
376 Histedit state has been exited
376 Histedit state has been exited
377 $ hg summary -q
377 $ hg summary -q
378 parent: 5:63379946892c
378 parent: 5:63379946892c
379 commit: 1 added, 1 unknown (new branch head)
379 commit: 1 added, 1 unknown (new branch head)
380 update: 4 new changesets (update)
380 update: 4 new changesets (update)
381
381
382 $ cd ..
382 $ cd ..
383
383
384 Set up default base revision tests
384 Set up default base revision tests
385
385
386 $ hg init defaultbase
386 $ hg init defaultbase
387 $ cd defaultbase
387 $ cd defaultbase
388 $ touch foo
388 $ touch foo
389 $ hg -q commit -A -m root
389 $ hg -q commit -A -m root
390 $ echo 1 > foo
390 $ echo 1 > foo
391 $ hg commit -m 'public 1'
391 $ hg commit -m 'public 1'
392 $ hg phase --force --public -r .
392 $ hg phase --force --public -r .
393 $ echo 2 > foo
393 $ echo 2 > foo
394 $ hg commit -m 'draft after public'
394 $ hg commit -m 'draft after public'
395 $ hg -q up -r 1
395 $ hg -q up -r 1
396 $ echo 3 > foo
396 $ echo 3 > foo
397 $ hg commit -m 'head 1 public'
397 $ hg commit -m 'head 1 public'
398 created new head
398 created new head
399 $ hg phase --force --public -r .
399 $ hg phase --force --public -r .
400 $ echo 4 > foo
400 $ echo 4 > foo
401 $ hg commit -m 'head 1 draft 1'
401 $ hg commit -m 'head 1 draft 1'
402 $ echo 5 > foo
402 $ echo 5 > foo
403 $ hg commit -m 'head 1 draft 2'
403 $ hg commit -m 'head 1 draft 2'
404 $ hg -q up -r 2
404 $ hg -q up -r 2
405 $ echo 6 > foo
405 $ echo 6 > foo
406 $ hg commit -m 'head 2 commit 1'
406 $ hg commit -m 'head 2 commit 1'
407 $ echo 7 > foo
407 $ echo 7 > foo
408 $ hg commit -m 'head 2 commit 2'
408 $ hg commit -m 'head 2 commit 2'
409 $ hg -q up -r 2
409 $ hg -q up -r 2
410 $ echo 8 > foo
410 $ echo 8 > foo
411 $ hg commit -m 'head 3'
411 $ hg commit -m 'head 3'
412 created new head
412 created new head
413 $ hg -q up -r 2
413 $ hg -q up -r 2
414 $ echo 9 > foo
414 $ echo 9 > foo
415 $ hg commit -m 'head 4'
415 $ hg commit -m 'head 4'
416 created new head
416 created new head
417 $ hg merge --tool :local -r 8
417 $ hg merge --tool :local -r 8
418 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
418 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
419 (branch merge, don't forget to commit)
419 (branch merge, don't forget to commit)
420 $ hg commit -m 'merge head 3 into head 4'
420 $ hg commit -m 'merge head 3 into head 4'
421 $ echo 11 > foo
421 $ echo 11 > foo
422 $ hg commit -m 'commit 1 after merge'
422 $ hg commit -m 'commit 1 after merge'
423 $ echo 12 > foo
423 $ echo 12 > foo
424 $ hg commit -m 'commit 2 after merge'
424 $ hg commit -m 'commit 2 after merge'
425
425
426 $ hg log -G -T '{rev}:{node|short} {phase} {desc}\n'
426 $ hg log -G -T '{rev}:{node|short} {phase} {desc}\n'
427 @ 12:8cde254db839 draft commit 2 after merge
427 @ 12:8cde254db839 draft commit 2 after merge
428 |
428 |
429 o 11:6f2f0241f119 draft commit 1 after merge
429 o 11:6f2f0241f119 draft commit 1 after merge
430 |
430 |
431 o 10:90506cc76b00 draft merge head 3 into head 4
431 o 10:90506cc76b00 draft merge head 3 into head 4
432 |\
432 |\
433 | o 9:f8607a373a97 draft head 4
433 | o 9:f8607a373a97 draft head 4
434 | |
434 | |
435 o | 8:0da92be05148 draft head 3
435 o | 8:0da92be05148 draft head 3
436 |/
436 |/
437 | o 7:4c35cdf97d5e draft head 2 commit 2
437 | o 7:4c35cdf97d5e draft head 2 commit 2
438 | |
438 | |
439 | o 6:931820154288 draft head 2 commit 1
439 | o 6:931820154288 draft head 2 commit 1
440 |/
440 |/
441 | o 5:8cdc02b9bc63 draft head 1 draft 2
441 | o 5:8cdc02b9bc63 draft head 1 draft 2
442 | |
442 | |
443 | o 4:463b8c0d2973 draft head 1 draft 1
443 | o 4:463b8c0d2973 draft head 1 draft 1
444 | |
444 | |
445 | o 3:23a0c4eefcbf public head 1 public
445 | o 3:23a0c4eefcbf public head 1 public
446 | |
446 | |
447 o | 2:4117331c3abb draft draft after public
447 o | 2:4117331c3abb draft draft after public
448 |/
448 |/
449 o 1:4426d359ea59 public public 1
449 o 1:4426d359ea59 public public 1
450 |
450 |
451 o 0:54136a8ddf32 public root
451 o 0:54136a8ddf32 public root
452
452
453
453
454 Default base revision should stop at public changesets
454 Default base revision should stop at public changesets
455
455
456 $ hg -q up 8cdc02b9bc63
456 $ hg -q up 8cdc02b9bc63
457 $ hg histedit --commands - <<EOF
457 $ hg histedit --commands - <<EOF
458 > pick 463b8c0d2973
458 > pick 463b8c0d2973
459 > pick 8cdc02b9bc63
459 > pick 8cdc02b9bc63
460 > EOF
460 > EOF
461
461
462 Default base revision should stop at branchpoint
462 Default base revision should stop at branchpoint
463
463
464 $ hg -q up 4c35cdf97d5e
464 $ hg -q up 4c35cdf97d5e
465 $ hg histedit --commands - <<EOF
465 $ hg histedit --commands - <<EOF
466 > pick 931820154288
466 > pick 931820154288
467 > pick 4c35cdf97d5e
467 > pick 4c35cdf97d5e
468 > EOF
468 > EOF
469
469
470 Default base revision should stop at merge commit
470 Default base revision should stop at merge commit
471
471
472 $ hg -q up 8cde254db839
472 $ hg -q up 8cde254db839
473 $ hg histedit --commands - <<EOF
473 $ hg histedit --commands - <<EOF
474 > pick 6f2f0241f119
474 > pick 6f2f0241f119
475 > pick 8cde254db839
475 > pick 8cde254db839
476 > EOF
476 > EOF
477
477
478 commit --amend should abort if histedit is in progress
478 commit --amend should abort if histedit is in progress
479 (issue4800) and markers are not being created.
479 (issue4800) and markers are not being created.
480 Eventually, histedit could perhaps look at `source` extra,
480 Eventually, histedit could perhaps look at `source` extra,
481 in which case this test should be revisited.
481 in which case this test should be revisited.
482
482
483 $ hg -q up 8cde254db839
483 $ hg -q up 8cde254db839
484 $ hg histedit 6f2f0241f119 --commands - <<EOF
484 $ hg histedit 6f2f0241f119 --commands - <<EOF
485 > pick 8cde254db839
485 > pick 8cde254db839
486 > edit 6f2f0241f119
486 > edit 6f2f0241f119
487 > EOF
487 > EOF
488 merging foo
488 merging foo
489 warning: conflicts while merging foo! (edit, then use 'hg resolve --mark')
489 warning: conflicts while merging foo! (edit, then use 'hg resolve --mark')
490 Fix up the change (pick 8cde254db839)
490 Fix up the change (pick 8cde254db839)
491 (hg histedit --continue to resume)
491 (hg histedit --continue to resume)
492 [240]
492 [240]
493 $ hg resolve -m --all
493 $ hg resolve -m --all
494 (no more unresolved files)
494 (no more unresolved files)
495 continue: hg histedit --continue
495 continue: hg histedit --continue
496 $ hg histedit --cont
496 $ hg histedit --cont
497 merging foo
497 merging foo
498 warning: conflicts while merging foo! (edit, then use 'hg resolve --mark')
498 warning: conflicts while merging foo! (edit, then use 'hg resolve --mark')
499 Editing (6f2f0241f119), commit as needed now to split the change
499 Editing (6f2f0241f119), commit as needed now to split the change
500 (to edit 6f2f0241f119, `hg histedit --continue` after making changes)
500 (to edit 6f2f0241f119, `hg histedit --continue` after making changes)
501 [240]
501 [240]
502 $ hg resolve -m --all
502 $ hg resolve -m --all
503 (no more unresolved files)
503 (no more unresolved files)
504 continue: hg histedit --continue
504 continue: hg histedit --continue
505 $ hg commit --amend -m 'reject this fold'
505 $ hg commit --amend -m 'reject this fold'
506 abort: histedit in progress
506 abort: histedit in progress
507 (use 'hg histedit --continue' or 'hg histedit --abort')
507 (use 'hg histedit --continue' or 'hg histedit --abort')
508 [20]
508 [20]
509
509
510 With markers enabled, histedit does not get confused, and
510 With markers enabled, histedit does not get confused, and
511 amend should not be blocked by the ongoing histedit.
511 amend should not be blocked by the ongoing histedit.
512
512
513 $ cat >>$HGRCPATH <<EOF
513 $ cat >>$HGRCPATH <<EOF
514 > [experimental]
514 > [experimental]
515 > evolution.createmarkers=True
515 > evolution.createmarkers=True
516 > evolution.allowunstable=True
516 > evolution.allowunstable=True
517 > EOF
517 > EOF
518 $ hg commit --amend -m 'allow this fold'
518 $ hg commit --amend -m 'allow this fold'
519 $ hg histedit --continue
519 $ hg histedit --continue
520
520
521 $ cd ..
521 $ cd ..
522
522
523 Test autoverb feature
523 Test autoverb feature
524
524
525 $ hg init autoverb
525 $ hg init autoverb
526 $ cd autoverb
526 $ cd autoverb
527 $ echo alpha >> alpha
527 $ echo alpha >> alpha
528 $ hg ci -qAm one
528 $ hg ci -qAm one
529 $ echo alpha >> alpha
529 $ echo alpha >> alpha
530 $ hg ci -qm two
530 $ hg ci -qm two
531 $ echo beta >> beta
531 $ echo beta >> beta
532 $ hg ci -qAm "roll! one"
532 $ hg ci -qAm "roll! one"
533
533
534 $ hg log --style compact --graph
534 $ hg log --style compact --graph
535 @ 2[tip] 4f34d0f8b5fa 1970-01-01 00:00 +0000 test
535 @ 2[tip] 4f34d0f8b5fa 1970-01-01 00:00 +0000 test
536 | roll! one
536 | roll! one
537 |
537 |
538 o 1 579e40513370 1970-01-01 00:00 +0000 test
538 o 1 579e40513370 1970-01-01 00:00 +0000 test
539 | two
539 | two
540 |
540 |
541 o 0 6058cbb6cfd7 1970-01-01 00:00 +0000 test
541 o 0 6058cbb6cfd7 1970-01-01 00:00 +0000 test
542 one
542 one
543
543
544
544
545 Check that 'roll' is selected by default
545 Check that 'roll' is selected by default
546
546
547 $ HGEDITOR=cat hg histedit 0 --config experimental.histedit.autoverb=True
547 $ HGEDITOR=cat hg histedit 0 --config experimental.histedit.autoverb=True
548 pick 6058cbb6cfd7 0 one
548 pick 6058cbb6cfd7 0 one
549 roll 4f34d0f8b5fa 2 roll! one
549 roll 4f34d0f8b5fa 2 roll! one
550 pick 579e40513370 1 two
550 pick 579e40513370 1 two
551
551
552 # Edit history between 6058cbb6cfd7 and 4f34d0f8b5fa
552 # Edit history between 6058cbb6cfd7 and 4f34d0f8b5fa
553 #
553 #
554 # Commits are listed from least to most recent
554 # Commits are listed from least to most recent
555 #
555 #
556 # You can reorder changesets by reordering the lines
556 # You can reorder changesets by reordering the lines
557 #
557 #
558 # Commands:
558 # Commands:
559 #
559 #
560 # e, edit = use commit, but allow edits before making new commit
560 # e, edit = use commit, but allow edits before making new commit
561 # m, mess = edit commit message without changing commit content
561 # m, mess = edit commit message without changing commit content
562 # p, pick = use commit
562 # p, pick = use commit
563 # b, base = checkout changeset and apply further changesets from there
563 # b, base = checkout changeset and apply further changesets from there
564 # d, drop = remove commit from history
564 # d, drop = remove commit from history
565 # f, fold = use commit, but combine it with the one above
565 # f, fold = use commit, but combine it with the one above
566 # r, roll = like fold, but discard this commit's description and date
566 # r, roll = like fold, but discard this commit's description and date
567 #
567 #
568
568
569 $ cd ..
569 $ cd ..
570
570
571 Check that histedit's commands accept revsets
571 Check that histedit's commands accept revsets
572 $ hg init bar
572 $ hg init bar
573 $ cd bar
573 $ cd bar
574 $ echo w >> a
574 $ echo w >> a
575 $ hg ci -qAm "adds a"
575 $ hg ci -qAm "adds a"
576 $ echo x >> b
576 $ echo x >> b
577 $ hg ci -qAm "adds b"
577 $ hg ci -qAm "adds b"
578 $ echo y >> c
578 $ echo y >> c
579 $ hg ci -qAm "adds c"
579 $ hg ci -qAm "adds c"
580 $ echo z >> d
580 $ echo z >> d
581 $ hg ci -qAm "adds d"
581 $ hg ci -qAm "adds d"
582 $ hg log -G -T '{rev} {desc}\n'
582 $ hg log -G -T '{rev} {desc}\n'
583 @ 3 adds d
583 @ 3 adds d
584 |
584 |
585 o 2 adds c
585 o 2 adds c
586 |
586 |
587 o 1 adds b
587 o 1 adds b
588 |
588 |
589 o 0 adds a
589 o 0 adds a
590
590
591 $ HGEDITOR=cat hg histedit "2" --commands - << EOF
591 $ HGEDITOR=cat hg histedit "2" --commands - << EOF
592 > base -4 adds c
592 > base -4 adds c
593 > pick 2 adds c
593 > pick 2 adds c
594 > pick tip adds d
594 > pick tip adds d
595 > EOF
595 > EOF
596 $ hg log -G -T '{rev} {desc}\n'
596 $ hg log -G -T '{rev} {desc}\n'
597 @ 5 adds d
597 @ 5 adds d
598 |
598 |
599 o 4 adds c
599 o 4 adds c
600 |
600 |
601 | o 1 adds b
601 | o 1 adds b
602 |/
602 |/
603 o 0 adds a
603 o 0 adds a
604
604
605
605
@@ -1,556 +1,556
1 $ . "$TESTDIR/histedit-helpers.sh"
1 $ . "$TESTDIR/histedit-helpers.sh"
2
2
3 $ cat >> $HGRCPATH <<EOF
3 $ cat >> $HGRCPATH <<EOF
4 > [extensions]
4 > [extensions]
5 > histedit=
5 > histedit=
6 > strip=
6 > strip=
7 > mockmakedate = $TESTDIR/mockmakedate.py
7 > mockmakedate = $TESTDIR/mockmakedate.py
8 > EOF
8 > EOF
9
9
10 $ initrepo ()
10 $ initrepo ()
11 > {
11 > {
12 > hg init r
12 > hg init r
13 > cd r
13 > cd r
14 > for x in a b c d e f g; do
14 > for x in a b c d e f g; do
15 > echo $x > $x
15 > echo $x > $x
16 > hg add $x
16 > hg add $x
17 > hg ci -m $x
17 > hg ci -m $x
18 > done
18 > done
19 > }
19 > }
20
20
21 $ initrepo
21 $ initrepo
22
22
23 log before edit
23 log before edit
24 $ hg log --graph
24 $ hg log --graph
25 @ changeset: 6:3c6a8ed2ebe8
25 @ changeset: 6:3c6a8ed2ebe8
26 | tag: tip
26 | tag: tip
27 | user: test
27 | user: test
28 | date: Thu Jan 01 00:00:00 1970 +0000
28 | date: Thu Jan 01 00:00:00 1970 +0000
29 | summary: g
29 | summary: g
30 |
30 |
31 o changeset: 5:652413bf663e
31 o changeset: 5:652413bf663e
32 | user: test
32 | user: test
33 | date: Thu Jan 01 00:00:00 1970 +0000
33 | date: Thu Jan 01 00:00:00 1970 +0000
34 | summary: f
34 | summary: f
35 |
35 |
36 o changeset: 4:e860deea161a
36 o changeset: 4:e860deea161a
37 | user: test
37 | user: test
38 | date: Thu Jan 01 00:00:00 1970 +0000
38 | date: Thu Jan 01 00:00:00 1970 +0000
39 | summary: e
39 | summary: e
40 |
40 |
41 o changeset: 3:055a42cdd887
41 o changeset: 3:055a42cdd887
42 | user: test
42 | user: test
43 | date: Thu Jan 01 00:00:00 1970 +0000
43 | date: Thu Jan 01 00:00:00 1970 +0000
44 | summary: d
44 | summary: d
45 |
45 |
46 o changeset: 2:177f92b77385
46 o changeset: 2:177f92b77385
47 | user: test
47 | user: test
48 | date: Thu Jan 01 00:00:00 1970 +0000
48 | date: Thu Jan 01 00:00:00 1970 +0000
49 | summary: c
49 | summary: c
50 |
50 |
51 o changeset: 1:d2ae7f538514
51 o changeset: 1:d2ae7f538514
52 | user: test
52 | user: test
53 | date: Thu Jan 01 00:00:00 1970 +0000
53 | date: Thu Jan 01 00:00:00 1970 +0000
54 | summary: b
54 | summary: b
55 |
55 |
56 o changeset: 0:cb9a9f314b8b
56 o changeset: 0:cb9a9f314b8b
57 user: test
57 user: test
58 date: Thu Jan 01 00:00:00 1970 +0000
58 date: Thu Jan 01 00:00:00 1970 +0000
59 summary: a
59 summary: a
60
60
61 dirty a file
61 dirty a file
62 $ echo a > g
62 $ echo a > g
63 $ hg histedit 177f92b77385 --commands - 2>&1 << EOF
63 $ hg histedit 177f92b77385 --commands - 2>&1 << EOF
64 > EOF
64 > EOF
65 abort: uncommitted changes
65 abort: uncommitted changes
66 [20]
66 [20]
67 $ echo g > g
67 $ echo g > g
68
68
69 edit the history
69 edit the history
70 $ hg histedit 177f92b77385 --commands - 2>&1 << EOF| fixbundle
70 $ hg histedit 177f92b77385 --commands - 2>&1 << EOF| fixbundle
71 > pick 177f92b77385 c
71 > pick 177f92b77385 c
72 > pick 055a42cdd887 d
72 > pick 055a42cdd887 d
73 > edit e860deea161a e
73 > edit e860deea161a e
74 > pick 652413bf663e f
74 > pick 652413bf663e f
75 > pick 3c6a8ed2ebe8 g
75 > pick 3c6a8ed2ebe8 g
76 > EOF
76 > EOF
77 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
77 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
78 Editing (e860deea161a), commit as needed now to split the change
78 Editing (e860deea161a), commit as needed now to split the change
79 (to edit e860deea161a, `hg histedit --continue` after making changes)
79 (to edit e860deea161a, `hg histedit --continue` after making changes)
80
80
81 try to update and get an error
81 try to update and get an error
82 $ hg update tip
82 $ hg update tip
83 abort: histedit in progress
83 abort: histedit in progress
84 (use 'hg histedit --continue' or 'hg histedit --abort')
84 (use 'hg histedit --continue' or 'hg histedit --abort')
85 [20]
85 [20]
86
86
87 edit the plan via the editor
87 edit the plan via the editor
88 $ cat >> $TESTTMP/editplan.sh <<EOF
88 $ cat >> $TESTTMP/editplan.sh <<EOF
89 > cat > \$1 <<EOF2
89 > cat > \$1 <<EOF2
90 > drop e860deea161a e
90 > drop e860deea161a e
91 > drop 652413bf663e f
91 > drop 652413bf663e f
92 > drop 3c6a8ed2ebe8 g
92 > drop 3c6a8ed2ebe8 g
93 > EOF2
93 > EOF2
94 > EOF
94 > EOF
95 $ HGEDITOR="sh $TESTTMP/editplan.sh" hg histedit --edit-plan
95 $ HGEDITOR="sh $TESTTMP/editplan.sh" hg histedit --edit-plan
96 $ cat .hg/histedit-state
96 $ cat .hg/histedit-state
97 v1
97 v1
98 055a42cdd88768532f9cf79daa407fc8d138de9b
98 055a42cdd88768532f9cf79daa407fc8d138de9b
99 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799
99 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799
100 False
100 False
101 3
101 3
102 drop
102 drop
103 e860deea161a2f77de56603b340ebbb4536308ae
103 e860deea161a2f77de56603b340ebbb4536308ae
104 drop
104 drop
105 652413bf663ef2a641cab26574e46d5f5a64a55a
105 652413bf663ef2a641cab26574e46d5f5a64a55a
106 drop
106 drop
107 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799
107 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799
108 0
108 0
109 strip-backup/177f92b77385-0ebe6a8f-histedit.hg
109 strip-backup/177f92b77385-0ebe6a8f-histedit.hg
110
110
111 edit the plan via --commands
111 edit the plan via --commands
112 $ hg histedit --edit-plan --commands - 2>&1 << EOF
112 $ hg histedit --edit-plan --commands - 2>&1 << EOF
113 > edit e860deea161a e
113 > edit e860deea161a e
114 > pick 652413bf663e f
114 > pick 652413bf663e f
115 > drop 3c6a8ed2ebe8 g
115 > drop 3c6a8ed2ebe8 g
116 > EOF
116 > EOF
117 $ cat .hg/histedit-state
117 $ cat .hg/histedit-state
118 v1
118 v1
119 055a42cdd88768532f9cf79daa407fc8d138de9b
119 055a42cdd88768532f9cf79daa407fc8d138de9b
120 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799
120 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799
121 False
121 False
122 3
122 3
123 edit
123 edit
124 e860deea161a2f77de56603b340ebbb4536308ae
124 e860deea161a2f77de56603b340ebbb4536308ae
125 pick
125 pick
126 652413bf663ef2a641cab26574e46d5f5a64a55a
126 652413bf663ef2a641cab26574e46d5f5a64a55a
127 drop
127 drop
128 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799
128 3c6a8ed2ebe862cc949d2caa30775dd6f16fb799
129 0
129 0
130 strip-backup/177f92b77385-0ebe6a8f-histedit.hg
130 strip-backup/177f92b77385-0ebe6a8f-histedit.hg
131
131
132 Go at a random point and try to continue
132 Go at a random point and try to continue
133
133
134 $ hg id -n
134 $ hg id -n
135 3+
135 3+
136 $ hg up 0
136 $ hg up 0
137 abort: histedit in progress
137 abort: histedit in progress
138 (use 'hg histedit --continue' or 'hg histedit --abort')
138 (use 'hg histedit --continue' or 'hg histedit --abort')
139 [20]
139 [20]
140
140
141 Try to delete necessary commit
141 Try to delete necessary commit
142 $ hg strip -r 652413b
142 $ hg strip -r 652413b
143 abort: histedit in progress, can't strip 652413bf663e
143 abort: histedit in progress, can't strip 652413bf663e
144 [255]
144 [255]
145
145
146 commit, then edit the revision
146 commit, then edit the revision
147 $ hg ci -m 'wat'
147 $ hg ci -m 'wat'
148 created new head
148 created new head
149 $ echo a > e
149 $ echo a > e
150
150
151 qnew should fail while we're in the middle of the edit step
151 qnew should fail while we're in the middle of the edit step
152
152
153 $ hg --config extensions.mq= qnew please-fail
153 $ hg --config extensions.mq= qnew please-fail
154 abort: histedit in progress
154 abort: histedit in progress
155 (use 'hg histedit --continue' or 'hg histedit --abort')
155 (use 'hg histedit --continue' or 'hg histedit --abort')
156 [20]
156 [20]
157 $ HGEDITOR='echo foobaz > ' hg histedit --continue 2>&1 | fixbundle
157 $ HGEDITOR='echo foobaz > ' hg histedit --continue 2>&1 | fixbundle
158
158
159 $ hg log --graph
159 $ hg log --graph
160 @ changeset: 6:b5f70786f9b0
160 @ changeset: 6:b5f70786f9b0
161 | tag: tip
161 | tag: tip
162 | user: test
162 | user: test
163 | date: Thu Jan 01 00:00:00 1970 +0000
163 | date: Thu Jan 01 00:00:00 1970 +0000
164 | summary: f
164 | summary: f
165 |
165 |
166 o changeset: 5:a5e1ba2f7afb
166 o changeset: 5:a5e1ba2f7afb
167 | user: test
167 | user: test
168 | date: Thu Jan 01 00:00:00 1970 +0000
168 | date: Thu Jan 01 00:00:00 1970 +0000
169 | summary: foobaz
169 | summary: foobaz
170 |
170 |
171 o changeset: 4:1a60820cd1f6
171 o changeset: 4:1a60820cd1f6
172 | user: test
172 | user: test
173 | date: Thu Jan 01 00:00:00 1970 +0000
173 | date: Thu Jan 01 00:00:00 1970 +0000
174 | summary: wat
174 | summary: wat
175 |
175 |
176 o changeset: 3:055a42cdd887
176 o changeset: 3:055a42cdd887
177 | user: test
177 | user: test
178 | date: Thu Jan 01 00:00:00 1970 +0000
178 | date: Thu Jan 01 00:00:00 1970 +0000
179 | summary: d
179 | summary: d
180 |
180 |
181 o changeset: 2:177f92b77385
181 o changeset: 2:177f92b77385
182 | user: test
182 | user: test
183 | date: Thu Jan 01 00:00:00 1970 +0000
183 | date: Thu Jan 01 00:00:00 1970 +0000
184 | summary: c
184 | summary: c
185 |
185 |
186 o changeset: 1:d2ae7f538514
186 o changeset: 1:d2ae7f538514
187 | user: test
187 | user: test
188 | date: Thu Jan 01 00:00:00 1970 +0000
188 | date: Thu Jan 01 00:00:00 1970 +0000
189 | summary: b
189 | summary: b
190 |
190 |
191 o changeset: 0:cb9a9f314b8b
191 o changeset: 0:cb9a9f314b8b
192 user: test
192 user: test
193 date: Thu Jan 01 00:00:00 1970 +0000
193 date: Thu Jan 01 00:00:00 1970 +0000
194 summary: a
194 summary: a
195
195
196
196
197 $ hg cat e
197 $ hg cat e
198 a
198 a
199
199
200 Stripping necessary commits should not break --abort
200 Stripping necessary commits should not break --abort
201
201
202 $ hg histedit 1a60820cd1f6 --commands - 2>&1 << EOF| fixbundle
202 $ hg histedit 1a60820cd1f6 --commands - 2>&1 << EOF| fixbundle
203 > edit 1a60820cd1f6 wat
203 > edit 1a60820cd1f6 wat
204 > pick a5e1ba2f7afb foobaz
204 > pick a5e1ba2f7afb foobaz
205 > pick b5f70786f9b0 g
205 > pick b5f70786f9b0 g
206 > EOF
206 > EOF
207 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
207 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
208 Editing (1a60820cd1f6), commit as needed now to split the change
208 Editing (1a60820cd1f6), commit as needed now to split the change
209 (to edit 1a60820cd1f6, `hg histedit --continue` after making changes)
209 (to edit 1a60820cd1f6, `hg histedit --continue` after making changes)
210
210
211 $ mv .hg/histedit-state .hg/histedit-state.bak
211 $ mv .hg/histedit-state .hg/histedit-state.bak
212 $ hg strip -q -r b5f70786f9b0
212 $ hg strip -q -r b5f70786f9b0
213 $ mv .hg/histedit-state.bak .hg/histedit-state
213 $ mv .hg/histedit-state.bak .hg/histedit-state
214 $ hg histedit --abort
214 $ hg histedit --abort
215 adding changesets
215 adding changesets
216 adding manifests
216 adding manifests
217 adding file changes
217 adding file changes
218 added 1 changesets with 1 changes to 3 files
218 added 1 changesets with 1 changes to 3 files
219 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
219 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
220 $ hg log -r .
220 $ hg log -r .
221 changeset: 6:b5f70786f9b0
221 changeset: 6:b5f70786f9b0
222 tag: tip
222 tag: tip
223 user: test
223 user: test
224 date: Thu Jan 01 00:00:00 1970 +0000
224 date: Thu Jan 01 00:00:00 1970 +0000
225 summary: f
225 summary: f
226
226
227
227
228 check histedit_source
228 check histedit_source
229
229
230 $ hg log --debug --rev 5
230 $ hg log --debug --rev 5
231 changeset: 5:a5e1ba2f7afb899ef1581cea528fd885d2fca70d
231 changeset: 5:a5e1ba2f7afb899ef1581cea528fd885d2fca70d
232 phase: draft
232 phase: draft
233 parent: 4:1a60820cd1f6004a362aa622ebc47d59bc48eb34
233 parent: 4:1a60820cd1f6004a362aa622ebc47d59bc48eb34
234 parent: -1:0000000000000000000000000000000000000000
234 parent: -1:0000000000000000000000000000000000000000
235 manifest: 5:5ad3be8791f39117565557781f5464363b918a45
235 manifest: 5:5ad3be8791f39117565557781f5464363b918a45
236 user: test
236 user: test
237 date: Thu Jan 01 00:00:00 1970 +0000
237 date: Thu Jan 01 00:00:00 1970 +0000
238 files: e
238 files: e
239 extra: branch=default
239 extra: branch=default
240 extra: histedit_source=e860deea161a2f77de56603b340ebbb4536308ae
240 extra: histedit_source=e860deea161a2f77de56603b340ebbb4536308ae
241 description:
241 description:
242 foobaz
242 foobaz
243
243
244
244
245
245
246 $ hg histedit tip --commands - 2>&1 <<EOF| fixbundle
246 $ hg histedit tip --commands - 2>&1 <<EOF| fixbundle
247 > edit b5f70786f9b0 f
247 > edit b5f70786f9b0 f
248 > EOF
248 > EOF
249 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
249 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
250 Editing (b5f70786f9b0), commit as needed now to split the change
250 Editing (b5f70786f9b0), commit as needed now to split the change
251 (to edit b5f70786f9b0, `hg histedit --continue` after making changes)
251 (to edit b5f70786f9b0, `hg histedit --continue` after making changes)
252 $ hg status
252 $ hg status
253 A f
253 A f
254
254
255 $ hg summary
255 $ hg summary
256 parent: 5:a5e1ba2f7afb
256 parent: 5:a5e1ba2f7afb
257 foobaz
257 foobaz
258 branch: default
258 branch: default
259 commit: 1 added (new branch head)
259 commit: 1 added (new branch head)
260 update: 1 new changesets (update)
260 update: 1 new changesets (update)
261 phases: 7 draft
261 phases: 7 draft
262 hist: 1 remaining (histedit --continue)
262 hist: 1 remaining (histedit --continue)
263
263
264 (test also that editor is invoked if histedit is continued for
264 (test also that editor is invoked if histedit is continued for
265 "edit" action)
265 "edit" action)
266
266
267 $ HGEDITOR='cat' hg histedit --continue
267 $ HGEDITOR='cat' hg histedit --continue
268 f
268 f
269
269
270
270
271 HG: Enter commit message. Lines beginning with 'HG:' are removed.
271 HG: Enter commit message. Lines beginning with 'HG:' are removed.
272 HG: Leave message empty to abort commit.
272 HG: Leave message empty to abort commit.
273 HG: --
273 HG: --
274 HG: user: test
274 HG: user: test
275 HG: branch 'default'
275 HG: branch 'default'
276 HG: added f
276 HG: added f
277 saved backup bundle to $TESTTMP/r/.hg/strip-backup/b5f70786f9b0-c28d9c86-histedit.hg
277 saved backup bundle to $TESTTMP/r/.hg/strip-backup/b5f70786f9b0-c28d9c86-histedit.hg
278
278
279 $ hg status
279 $ hg status
280
280
281 log after edit
281 log after edit
282 $ hg log --limit 1
282 $ hg log --limit 1
283 changeset: 6:a107ee126658
283 changeset: 6:a107ee126658
284 tag: tip
284 tag: tip
285 user: test
285 user: test
286 date: Thu Jan 01 00:00:00 1970 +0000
286 date: Thu Jan 01 00:00:00 1970 +0000
287 summary: f
287 summary: f
288
288
289
289
290 say we'll change the message, but don't.
290 say we'll change the message, but don't.
291 $ cat > ../edit.sh <<EOF
291 $ cat > ../edit.sh <<EOF
292 > cat "\$1" | sed s/pick/mess/ > tmp
292 > cat "\$1" | sed s/pick/mess/ > tmp
293 > mv tmp "\$1"
293 > mv tmp "\$1"
294 > EOF
294 > EOF
295 $ HGEDITOR="sh ../edit.sh" hg histedit tip 2>&1 | fixbundle
295 $ HGEDITOR="sh ../edit.sh" hg histedit tip 2>&1 | fixbundle
296 $ hg status
296 $ hg status
297 $ hg log --limit 1
297 $ hg log --limit 1
298 changeset: 6:1fd3b2fe7754
298 changeset: 6:1fd3b2fe7754
299 tag: tip
299 tag: tip
300 user: test
300 user: test
301 date: Thu Jan 01 00:00:00 1970 +0000
301 date: Thu Jan 01 00:00:00 1970 +0000
302 summary: f
302 summary: f
303
303
304
304
305 modify the message
305 modify the message
306
306
307 check saving last-message.txt, at first
307 check saving last-message.txt, at first
308
308
309 $ cat > $TESTTMP/commitfailure.py <<EOF
309 $ cat > $TESTTMP/commitfailure.py <<EOF
310 > from mercurial import error
310 > from mercurial import error
311 > def reposetup(ui, repo):
311 > def reposetup(ui, repo):
312 > class commitfailure(repo.__class__):
312 > class commitfailure(repo.__class__):
313 > def commit(self, *args, **kwargs):
313 > def commit(self, *args, **kwargs):
314 > raise error.Abort(b'emulating unexpected abort')
314 > raise error.Abort(b'emulating unexpected abort')
315 > repo.__class__ = commitfailure
315 > repo.__class__ = commitfailure
316 > EOF
316 > EOF
317 $ cat >> .hg/hgrc <<EOF
317 $ cat >> .hg/hgrc <<EOF
318 > [extensions]
318 > [extensions]
319 > # this failure occurs before editor invocation
319 > # this failure occurs before editor invocation
320 > commitfailure = $TESTTMP/commitfailure.py
320 > commitfailure = $TESTTMP/commitfailure.py
321 > EOF
321 > EOF
322
322
323 $ cat > $TESTTMP/editor.sh <<EOF
323 $ cat > $TESTTMP/editor.sh <<EOF
324 > echo "==== before editing"
324 > echo "==== before editing"
325 > cat \$1
325 > cat \$1
326 > echo "===="
326 > echo "===="
327 > echo "check saving last-message.txt" >> \$1
327 > echo "check saving last-message.txt" >> \$1
328 > EOF
328 > EOF
329
329
330 (test that editor is not invoked before transaction starting)
330 (test that editor is not invoked before transaction starting)
331
331
332 $ rm -f .hg/last-message.txt
332 $ rm -f .hg/last-message.txt
333 $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF | fixbundle
333 $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF | fixbundle
334 > mess 1fd3b2fe7754 f
334 > mess 1fd3b2fe7754 f
335 > EOF
335 > EOF
336 abort: emulating unexpected abort
336 abort: emulating unexpected abort
337 $ test -f .hg/last-message.txt
337 $ test -f .hg/last-message.txt
338 [1]
338 [1]
339
339
340 $ cat >> .hg/hgrc <<EOF
340 $ cat >> .hg/hgrc <<EOF
341 > [extensions]
341 > [extensions]
342 > commitfailure = !
342 > commitfailure = !
343 > EOF
343 > EOF
344 $ hg histedit --abort -q
344 $ hg histedit --abort -q
345
345
346 (test that editor is invoked and commit message is saved into
346 (test that editor is invoked and commit message is saved into
347 "last-message.txt")
347 "last-message.txt")
348
348
349 $ cat >> .hg/hgrc <<EOF
349 $ cat >> .hg/hgrc <<EOF
350 > [hooks]
350 > [hooks]
351 > # this failure occurs after editor invocation
351 > # this failure occurs after editor invocation
352 > pretxncommit.unexpectedabort = false
352 > pretxncommit.unexpectedabort = false
353 > EOF
353 > EOF
354
354
355 $ hg status --rev '1fd3b2fe7754^1' --rev 1fd3b2fe7754
355 $ hg status --rev '1fd3b2fe7754^1' --rev 1fd3b2fe7754
356 A f
356 A f
357
357
358 $ rm -f .hg/last-message.txt
358 $ rm -f .hg/last-message.txt
359 $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF
359 $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF
360 > mess 1fd3b2fe7754 f
360 > mess 1fd3b2fe7754 f
361 > EOF
361 > EOF
362 ==== before editing
362 ==== before editing
363 f
363 f
364
364
365
365
366 HG: Enter commit message. Lines beginning with 'HG:' are removed.
366 HG: Enter commit message. Lines beginning with 'HG:' are removed.
367 HG: Leave message empty to abort commit.
367 HG: Leave message empty to abort commit.
368 HG: --
368 HG: --
369 HG: user: test
369 HG: user: test
370 HG: branch 'default'
370 HG: branch 'default'
371 HG: added f
371 HG: added f
372 ====
372 ====
373 transaction abort!
373 transaction abort!
374 rollback completed
374 rollback completed
375 note: commit message saved in .hg/last-message.txt
375 note: commit message saved in .hg/last-message.txt
376 note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it
376 note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it
377 abort: pretxncommit.unexpectedabort hook exited with status 1
377 abort: pretxncommit.unexpectedabort hook exited with status 1
378 [40]
378 [40]
379 $ cat .hg/last-message.txt
379 $ cat .hg/last-message.txt
380 f
380 f
381
381
382
382
383 check saving last-message.txt
383 check saving last-message.txt
384
384
385 (test also that editor is invoked if histedit is continued for "message"
385 (test also that editor is invoked if histedit is continued for "message"
386 action)
386 action)
387
387
388 $ HGEDITOR=cat hg histedit --continue
388 $ HGEDITOR=cat hg histedit --continue
389 f
389 f
390
390
391
391
392 HG: Enter commit message. Lines beginning with 'HG:' are removed.
392 HG: Enter commit message. Lines beginning with 'HG:' are removed.
393 HG: Leave message empty to abort commit.
393 HG: Leave message empty to abort commit.
394 HG: --
394 HG: --
395 HG: user: test
395 HG: user: test
396 HG: branch 'default'
396 HG: branch 'default'
397 HG: added f
397 HG: added f
398 transaction abort!
398 transaction abort!
399 rollback completed
399 rollback completed
400 note: commit message saved in .hg/last-message.txt
400 note: commit message saved in .hg/last-message.txt
401 note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it
401 note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it
402 abort: pretxncommit.unexpectedabort hook exited with status 1
402 abort: pretxncommit.unexpectedabort hook exited with status 1
403 [40]
403 [40]
404
404
405 $ cat >> .hg/hgrc <<EOF
405 $ cat >> .hg/hgrc <<EOF
406 > [hooks]
406 > [hooks]
407 > pretxncommit.unexpectedabort =
407 > pretxncommit.unexpectedabort =
408 > EOF
408 > EOF
409 $ hg histedit --abort -q
409 $ hg histedit --abort -q
410
410
411 then, check "modify the message" itself
411 then, check "modify the message" itself
412
412
413 $ hg histedit tip --commands - 2>&1 << EOF | fixbundle
413 $ hg histedit tip --commands - 2>&1 << EOF | fixbundle
414 > mess 1fd3b2fe7754 f
414 > mess 1fd3b2fe7754 f
415 > EOF
415 > EOF
416 $ hg status
416 $ hg status
417 $ hg log --limit 1
417 $ hg log --limit 1
418 changeset: 6:62feedb1200e
418 changeset: 6:62feedb1200e
419 tag: tip
419 tag: tip
420 user: test
420 user: test
421 date: Thu Jan 01 00:00:00 1970 +0000
421 date: Thu Jan 01 00:00:00 1970 +0000
422 summary: f
422 summary: f
423
423
424
424
425 rollback should not work after a histedit
425 rollback should not work after a histedit
426 $ hg rollback
426 $ hg rollback
427 no rollback information available
427 no rollback information available
428 [1]
428 [1]
429
429
430 $ cd ..
430 $ cd ..
431 $ hg clone -qr0 r r0
431 $ hg clone -qr0 r r0
432 $ cd r0
432 $ cd r0
433 $ hg phase -fdr0
433 $ hg phase -fdr0
434 $ hg histedit --commands - 0 2>&1 << EOF
434 $ hg histedit --commands - 0 2>&1 << EOF
435 > edit cb9a9f314b8b a > $EDITED
435 > edit cb9a9f314b8b a > $EDITED
436 > EOF
436 > EOF
437 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
437 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
438 Editing (cb9a9f314b8b), commit as needed now to split the change
438 Editing (cb9a9f314b8b), commit as needed now to split the change
439 (to edit cb9a9f314b8b, `hg histedit --continue` after making changes)
439 (to edit cb9a9f314b8b, `hg histedit --continue` after making changes)
440 [240]
440 [240]
441 $ HGEDITOR=true hg histedit --continue
441 $ HGEDITOR=true hg histedit --continue
442 saved backup bundle to $TESTTMP/r0/.hg/strip-backup/cb9a9f314b8b-cc5ccb0b-histedit.hg
442 saved backup bundle to $TESTTMP/r0/.hg/strip-backup/cb9a9f314b8b-cc5ccb0b-histedit.hg
443
443
444 $ hg log -G
444 $ hg log -G
445 @ changeset: 0:0efcea34f18a
445 @ changeset: 0:0efcea34f18a
446 tag: tip
446 tag: tip
447 user: test
447 user: test
448 date: Thu Jan 01 00:00:00 1970 +0000
448 date: Thu Jan 01 00:00:00 1970 +0000
449 summary: a
449 summary: a
450
450
451 $ echo foo >> b
451 $ echo foo >> b
452 $ hg addr
452 $ hg addr
453 adding b
453 adding b
454 $ hg ci -m 'add b'
454 $ hg ci -m 'add b'
455 $ echo foo >> a
455 $ echo foo >> a
456 $ hg ci -m 'extend a'
456 $ hg ci -m 'extend a'
457 $ hg phase --public 1
457 $ hg phase --public 1
458 Attempting to fold a change into a public change should not work:
458 Attempting to fold a change into a public change should not work:
459 $ cat > ../edit.sh <<EOF
459 $ cat > ../edit.sh <<EOF
460 > cat "\$1" | sed s/pick/fold/ > tmp
460 > cat "\$1" | sed s/pick/fold/ > tmp
461 > mv tmp "\$1"
461 > mv tmp "\$1"
462 > EOF
462 > EOF
463 $ HGEDITOR="sh ../edit.sh" hg histedit 2
463 $ HGEDITOR="sh ../edit.sh" hg histedit 2
464 warning: histedit rules saved to: .hg/histedit-last-edit.txt
464 warning: histedit rules saved to: .hg/histedit-last-edit.txt
465 hg: parse error: first changeset cannot use verb "fold"
465 hg: parse error: first changeset cannot use verb "fold"
466 [10]
466 [10]
467 $ cat .hg/histedit-last-edit.txt
467 $ cat .hg/histedit-last-edit.txt
468 fold 0012be4a27ea 2 extend a
468 fold 0012be4a27ea 2 extend a
469
469
470 # Edit history between 0012be4a27ea and 0012be4a27ea
470 # Edit history between 0012be4a27ea and 0012be4a27ea
471 #
471 #
472 # Commits are listed from least to most recent
472 # Commits are listed from least to most recent
473 #
473 #
474 # You can reorder changesets by reordering the lines
474 # You can reorder changesets by reordering the lines
475 #
475 #
476 # Commands:
476 # Commands:
477 #
477 #
478 # e, edit = use commit, but allow edits before making new commit
478 # e, edit = use commit, but allow edits before making new commit
479 # m, mess = edit commit message without changing commit content
479 # m, mess = edit commit message without changing commit content
480 # p, fold = use commit
480 # p, fold = use commit
481 # b, base = checkout changeset and apply further changesets from there
481 # b, base = checkout changeset and apply further changesets from there
482 # d, drop = remove commit from history
482 # d, drop = remove commit from history
483 # f, fold = use commit, but combine it with the one above
483 # f, fold = use commit, but combine it with the one above
484 # r, roll = like fold, but discard this commit's description and date
484 # r, roll = like fold, but discard this commit's description and date
485 #
485 #
486
486
487 $ cd ..
487 $ cd ..
488
488
489 ============================================
489 ============================================
490 Test update-timestamp config option in mess|
490 Test update-timestamp config option in mess|
491 ============================================
491 ============================================
492
492
493 $ addwithdate ()
493 $ addwithdate ()
494 > {
494 > {
495 > echo $1 > $1
495 > echo $1 > $1
496 > hg add $1
496 > hg add $1
497 > hg ci -m $1 -d "$2 0"
497 > hg ci -m $1 -d "$2 0"
498 > }
498 > }
499
499
500 $ initrepo ()
500 $ initrepo ()
501 > {
501 > {
502 > hg init r2
502 > hg init r2
503 > cd r2
503 > cd r2
504 > addwithdate a 1
504 > addwithdate a 1
505 > addwithdate b 2
505 > addwithdate b 2
506 > addwithdate c 3
506 > addwithdate c 3
507 > addwithdate d 4
507 > addwithdate d 4
508 > addwithdate e 5
508 > addwithdate e 5
509 > addwithdate f 6
509 > addwithdate f 6
510 > }
510 > }
511
511
512 $ initrepo
512 $ initrepo
513
513
514 log before edit
514 log before edit
515
515
516 $ hg log --limit 1
516 $ hg log --limit 1
517 changeset: 5:178e35e0ce73
517 changeset: 5:178e35e0ce73
518 tag: tip
518 tag: tip
519 user: test
519 user: test
520 date: Thu Jan 01 00:00:06 1970 +0000
520 date: Thu Jan 01 00:00:06 1970 +0000
521 summary: f
521 summary: f
522
522
523 $ hg histedit tip --commands - 2>&1 --config rewrite.update-timestamp=True << EOF | fixbundle
523 $ hg histedit tip --commands - 2>&1 --config rewrite.update-timestamp=True << EOF | fixbundle
524 > mess 178e35e0ce73 f
524 > mess 178e35e0ce73 f
525 > EOF
525 > EOF
526
526
527 log after edit
527 log after edit
528
528
529 $ hg log --limit 1
529 $ hg log --limit 1
530 changeset: 5:98bf456d476b
530 changeset: 5:98bf456d476b
531 tag: tip
531 tag: tip
532 user: test
532 user: test
533 date: Thu Jan 01 00:00:00 1970 +0000
533 date: Thu Jan 01 00:00:00 1970 +0000
534 summary: f
534 summary: f
535
535
536
536
537 $ cd ..
537 $ cd ..
538
538
539 warn the user on editing tagged commits
539 warn the user on editing tagged commits
540
540
541 $ hg init issue4017
541 $ hg init issue4017
542 $ cd issue4017
542 $ cd issue4017
543 $ echo > a
543 $ echo > a
544 $ hg ci -Am 'add a'
544 $ hg ci -Am 'add a'
545 adding a
545 adding a
546 $ hg tag a
546 $ hg tag a
547 $ hg tags
547 $ hg tags
548 tip 1:bd7ee4f3939b
548 tip 1:bd7ee4f3939b
549 a 0:a8a82d372bb3
549 a 0:a8a82d372bb3
550 $ hg histedit
550 $ hg histedit
551 warning: tags associated with the given changeset will be lost after histedit.
551 warning: tags associated with the given changeset will be lost after histedit.
552 do you want to continue (yN)? n
552 do you want to continue (yN)? n
553 abort: histedit cancelled
553 abort: histedit cancelled
554
554
555 [255]
555 [250]
556 $ cd ..
556 $ cd ..
@@ -1,174 +1,174
1 $ . "$TESTDIR/histedit-helpers.sh"
1 $ . "$TESTDIR/histedit-helpers.sh"
2
2
3 $ cat >> $HGRCPATH <<EOF
3 $ cat >> $HGRCPATH <<EOF
4 > [extensions]
4 > [extensions]
5 > histedit=
5 > histedit=
6 > EOF
6 > EOF
7
7
8 $ initrepo ()
8 $ initrepo ()
9 > {
9 > {
10 > hg init r
10 > hg init r
11 > cd r
11 > cd r
12 > for x in a b c d e f ; do
12 > for x in a b c d e f ; do
13 > echo $x > $x
13 > echo $x > $x
14 > hg add $x
14 > hg add $x
15 > hg ci -m $x
15 > hg ci -m $x
16 > done
16 > done
17 > echo a >> e
17 > echo a >> e
18 > hg ci -m 'does not commute with e'
18 > hg ci -m 'does not commute with e'
19 > cd ..
19 > cd ..
20 > }
20 > }
21
21
22 $ initrepo
22 $ initrepo
23 $ cd r
23 $ cd r
24
24
25 log before edit
25 log before edit
26 $ hg log --graph
26 $ hg log --graph
27 @ changeset: 6:bfa474341cc9
27 @ changeset: 6:bfa474341cc9
28 | tag: tip
28 | tag: tip
29 | user: test
29 | user: test
30 | date: Thu Jan 01 00:00:00 1970 +0000
30 | date: Thu Jan 01 00:00:00 1970 +0000
31 | summary: does not commute with e
31 | summary: does not commute with e
32 |
32 |
33 o changeset: 5:652413bf663e
33 o changeset: 5:652413bf663e
34 | user: test
34 | user: test
35 | date: Thu Jan 01 00:00:00 1970 +0000
35 | date: Thu Jan 01 00:00:00 1970 +0000
36 | summary: f
36 | summary: f
37 |
37 |
38 o changeset: 4:e860deea161a
38 o changeset: 4:e860deea161a
39 | user: test
39 | user: test
40 | date: Thu Jan 01 00:00:00 1970 +0000
40 | date: Thu Jan 01 00:00:00 1970 +0000
41 | summary: e
41 | summary: e
42 |
42 |
43 o changeset: 3:055a42cdd887
43 o changeset: 3:055a42cdd887
44 | user: test
44 | user: test
45 | date: Thu Jan 01 00:00:00 1970 +0000
45 | date: Thu Jan 01 00:00:00 1970 +0000
46 | summary: d
46 | summary: d
47 |
47 |
48 o changeset: 2:177f92b77385
48 o changeset: 2:177f92b77385
49 | user: test
49 | user: test
50 | date: Thu Jan 01 00:00:00 1970 +0000
50 | date: Thu Jan 01 00:00:00 1970 +0000
51 | summary: c
51 | summary: c
52 |
52 |
53 o changeset: 1:d2ae7f538514
53 o changeset: 1:d2ae7f538514
54 | user: test
54 | user: test
55 | date: Thu Jan 01 00:00:00 1970 +0000
55 | date: Thu Jan 01 00:00:00 1970 +0000
56 | summary: b
56 | summary: b
57 |
57 |
58 o changeset: 0:cb9a9f314b8b
58 o changeset: 0:cb9a9f314b8b
59 user: test
59 user: test
60 date: Thu Jan 01 00:00:00 1970 +0000
60 date: Thu Jan 01 00:00:00 1970 +0000
61 summary: a
61 summary: a
62
62
63
63
64 edit the history
64 edit the history
65 $ hg histedit 177f92b77385 --commands - 2>&1 <<EOF | fixbundle
65 $ hg histedit 177f92b77385 --commands - 2>&1 <<EOF | fixbundle
66 > pick 177f92b77385 c
66 > pick 177f92b77385 c
67 > pick 055a42cdd887 d
67 > pick 055a42cdd887 d
68 > pick bfa474341cc9 does not commute with e
68 > pick bfa474341cc9 does not commute with e
69 > pick e860deea161a e
69 > pick e860deea161a e
70 > pick 652413bf663e f
70 > pick 652413bf663e f
71 > EOF
71 > EOF
72 merging e
72 merging e
73 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
73 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
74 Fix up the change (pick e860deea161a)
74 Fix up the change (pick e860deea161a)
75 (hg histedit --continue to resume)
75 (hg histedit --continue to resume)
76
76
77 insert unsupported advisory merge record
77 insert unsupported advisory merge record
78 $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -x
78 $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -x
79 $ hg debugmergestate
79 $ hg debugmergestate
80 local (local): 8f7551c7e4a2f2efe0bc8c741baf7f227d65d758
80 local (local): 8f7551c7e4a2f2efe0bc8c741baf7f227d65d758
81 other (histedit): e860deea161a2f77de56603b340ebbb4536308ae
81 other (histedit): e860deea161a2f77de56603b340ebbb4536308ae
82 file: e (state "u")
82 file: e (state "u")
83 local path: e (hash 58e6b3a414a1e090dfc6029add0f3555ccba127f, flags "")
83 local path: e (hash 58e6b3a414a1e090dfc6029add0f3555ccba127f, flags "")
84 ancestor path: e (node 0000000000000000000000000000000000000000)
84 ancestor path: e (node 0000000000000000000000000000000000000000)
85 other path: e (node 6b67ccefd5ce6de77e7ead4f5292843a0255329f)
85 other path: e (node 6b67ccefd5ce6de77e7ead4f5292843a0255329f)
86 extra: ancestorlinknode = 0000000000000000000000000000000000000000
86 extra: ancestorlinknode = 0000000000000000000000000000000000000000
87 extra: merged = yes
87 extra: merged = yes
88 $ hg resolve -l
88 $ hg resolve -l
89 U e
89 U e
90
90
91 insert unsupported mandatory merge record
91 insert unsupported mandatory merge record
92 $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -X
92 $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -X
93 $ hg debugmergestate
93 $ hg debugmergestate
94 abort: unsupported merge state records: X
94 abort: unsupported merge state records: X
95 (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
95 (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
96 [255]
96 [255]
97 $ hg resolve -l
97 $ hg resolve -l
98 abort: unsupported merge state records: X
98 abort: unsupported merge state records: X
99 (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
99 (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
100 [255]
100 [255]
101 $ hg resolve -ma
101 $ hg resolve -ma
102 abort: unsupported merge state records: X
102 abort: unsupported merge state records: X
103 (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
103 (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
104 [255]
104 [255]
105
105
106 abort the edit (should clear out merge state)
106 abort the edit (should clear out merge state)
107 $ hg histedit --abort 2>&1 | fixbundle
107 $ hg histedit --abort 2>&1 | fixbundle
108 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
108 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
109 $ hg debugmergestate
109 $ hg debugmergestate
110 no merge state found
110 no merge state found
111
111
112 log after abort
112 log after abort
113 $ hg resolve -l
113 $ hg resolve -l
114 $ hg log --graph
114 $ hg log --graph
115 @ changeset: 6:bfa474341cc9
115 @ changeset: 6:bfa474341cc9
116 | tag: tip
116 | tag: tip
117 | user: test
117 | user: test
118 | date: Thu Jan 01 00:00:00 1970 +0000
118 | date: Thu Jan 01 00:00:00 1970 +0000
119 | summary: does not commute with e
119 | summary: does not commute with e
120 |
120 |
121 o changeset: 5:652413bf663e
121 o changeset: 5:652413bf663e
122 | user: test
122 | user: test
123 | date: Thu Jan 01 00:00:00 1970 +0000
123 | date: Thu Jan 01 00:00:00 1970 +0000
124 | summary: f
124 | summary: f
125 |
125 |
126 o changeset: 4:e860deea161a
126 o changeset: 4:e860deea161a
127 | user: test
127 | user: test
128 | date: Thu Jan 01 00:00:00 1970 +0000
128 | date: Thu Jan 01 00:00:00 1970 +0000
129 | summary: e
129 | summary: e
130 |
130 |
131 o changeset: 3:055a42cdd887
131 o changeset: 3:055a42cdd887
132 | user: test
132 | user: test
133 | date: Thu Jan 01 00:00:00 1970 +0000
133 | date: Thu Jan 01 00:00:00 1970 +0000
134 | summary: d
134 | summary: d
135 |
135 |
136 o changeset: 2:177f92b77385
136 o changeset: 2:177f92b77385
137 | user: test
137 | user: test
138 | date: Thu Jan 01 00:00:00 1970 +0000
138 | date: Thu Jan 01 00:00:00 1970 +0000
139 | summary: c
139 | summary: c
140 |
140 |
141 o changeset: 1:d2ae7f538514
141 o changeset: 1:d2ae7f538514
142 | user: test
142 | user: test
143 | date: Thu Jan 01 00:00:00 1970 +0000
143 | date: Thu Jan 01 00:00:00 1970 +0000
144 | summary: b
144 | summary: b
145 |
145 |
146 o changeset: 0:cb9a9f314b8b
146 o changeset: 0:cb9a9f314b8b
147 user: test
147 user: test
148 date: Thu Jan 01 00:00:00 1970 +0000
148 date: Thu Jan 01 00:00:00 1970 +0000
149 summary: a
149 summary: a
150
150
151
151
152 Early tree conflict doesn't leave histedit in a wedged state. Note
152 Early tree conflict doesn't leave histedit in a wedged state. Note
153 that we don't specify --commands here: we catch the problem before we
153 that we don't specify --commands here: we catch the problem before we
154 even prompt the user for rules, sidestepping any dataloss issues.
154 even prompt the user for rules, sidestepping any dataloss issues.
155
155
156 $ hg rm c
156 $ hg rm c
157 $ hg ci -m 'remove c'
157 $ hg ci -m 'remove c'
158 $ echo collision > c
158 $ echo collision > c
159
159
160 $ hg histedit e860deea161a
160 $ hg histedit e860deea161a
161 c: untracked file differs
161 c: untracked file differs
162 abort: untracked files in working directory conflict with files in 055a42cdd887
162 abort: untracked files in working directory conflict with files in 055a42cdd887
163 [255]
163 [20]
164
164
165 We should have detected the collision early enough we're not in a
165 We should have detected the collision early enough we're not in a
166 histedit state, and p1 is unchanged.
166 histedit state, and p1 is unchanged.
167
167
168 $ hg log -r 'p1()' -T'{node}\n'
168 $ hg log -r 'p1()' -T'{node}\n'
169 1b0954ff00fccb15a37b679e4a35e9b01dfe685e
169 1b0954ff00fccb15a37b679e4a35e9b01dfe685e
170 $ hg status --config ui.tweakdefaults=yes
170 $ hg status --config ui.tweakdefaults=yes
171 ? c
171 ? c
172 ? e.orig
172 ? e.orig
173
173
174 $ cd ..
174 $ cd ..
@@ -1,591 +1,591
1 #testcases abortcommand abortflag
1 #testcases abortcommand abortflag
2
2
3 #if abortflag
3 #if abortflag
4 $ cat >> $HGRCPATH <<EOF
4 $ cat >> $HGRCPATH <<EOF
5 > [alias]
5 > [alias]
6 > abort = histedit --abort
6 > abort = histedit --abort
7 > EOF
7 > EOF
8 #endif
8 #endif
9
9
10 $ . "$TESTDIR/histedit-helpers.sh"
10 $ . "$TESTDIR/histedit-helpers.sh"
11
11
12 Enable obsolete
12 Enable obsolete
13
13
14 $ cat >> $HGRCPATH << EOF
14 $ cat >> $HGRCPATH << EOF
15 > [ui]
15 > [ui]
16 > logtemplate= {rev}:{node|short} {desc|firstline}
16 > logtemplate= {rev}:{node|short} {desc|firstline}
17 > [phases]
17 > [phases]
18 > publish=False
18 > publish=False
19 > [experimental]
19 > [experimental]
20 > evolution.createmarkers=True
20 > evolution.createmarkers=True
21 > evolution.allowunstable=True
21 > evolution.allowunstable=True
22 > [extensions]
22 > [extensions]
23 > histedit=
23 > histedit=
24 > rebase=
24 > rebase=
25 > EOF
25 > EOF
26
26
27 Test that histedit learns about obsolescence not stored in histedit state
27 Test that histedit learns about obsolescence not stored in histedit state
28 $ hg init boo
28 $ hg init boo
29 $ cd boo
29 $ cd boo
30 $ echo a > a
30 $ echo a > a
31 $ hg ci -Am a
31 $ hg ci -Am a
32 adding a
32 adding a
33 $ echo a > b
33 $ echo a > b
34 $ echo a > c
34 $ echo a > c
35 $ echo a > c
35 $ echo a > c
36 $ hg ci -Am b
36 $ hg ci -Am b
37 adding b
37 adding b
38 adding c
38 adding c
39 $ echo a > d
39 $ echo a > d
40 $ hg ci -Am c
40 $ hg ci -Am c
41 adding d
41 adding d
42 $ echo "pick `hg log -r 0 -T '{node|short}'`" > plan
42 $ echo "pick `hg log -r 0 -T '{node|short}'`" > plan
43 $ echo "pick `hg log -r 2 -T '{node|short}'`" >> plan
43 $ echo "pick `hg log -r 2 -T '{node|short}'`" >> plan
44 $ echo "edit `hg log -r 1 -T '{node|short}'`" >> plan
44 $ echo "edit `hg log -r 1 -T '{node|short}'`" >> plan
45 $ hg histedit -r 'all()' --commands plan
45 $ hg histedit -r 'all()' --commands plan
46 Editing (1b2d564fad96), commit as needed now to split the change
46 Editing (1b2d564fad96), commit as needed now to split the change
47 (to edit 1b2d564fad96, `hg histedit --continue` after making changes)
47 (to edit 1b2d564fad96, `hg histedit --continue` after making changes)
48 [240]
48 [240]
49 $ hg st
49 $ hg st
50 A b
50 A b
51 A c
51 A c
52 ? plan
52 ? plan
53 $ hg commit --amend b
53 $ hg commit --amend b
54 $ hg histedit --continue
54 $ hg histedit --continue
55 $ hg log -G
55 $ hg log -G
56 @ 5:46abc7c4d873 b
56 @ 5:46abc7c4d873 b
57 |
57 |
58 o 4:49d44ab2be1b c
58 o 4:49d44ab2be1b c
59 |
59 |
60 o 0:cb9a9f314b8b a
60 o 0:cb9a9f314b8b a
61
61
62 $ hg debugobsolete
62 $ hg debugobsolete
63 e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
63 e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
64 1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '12', 'operation': 'histedit', 'user': 'test'}
64 1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '12', 'operation': 'histedit', 'user': 'test'}
65 114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '12', 'operation': 'histedit', 'user': 'test'}
65 114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '12', 'operation': 'histedit', 'user': 'test'}
66
66
67 With some node gone missing during the edit.
67 With some node gone missing during the edit.
68
68
69 $ echo "pick `hg log -r 0 -T '{node|short}'`" > plan
69 $ echo "pick `hg log -r 0 -T '{node|short}'`" > plan
70 $ echo "pick `hg log -r 5 -T '{node|short}'`" >> plan
70 $ echo "pick `hg log -r 5 -T '{node|short}'`" >> plan
71 $ echo "edit `hg log -r 4 -T '{node|short}'`" >> plan
71 $ echo "edit `hg log -r 4 -T '{node|short}'`" >> plan
72 $ hg histedit -r 'all()' --commands plan
72 $ hg histedit -r 'all()' --commands plan
73 Editing (49d44ab2be1b), commit as needed now to split the change
73 Editing (49d44ab2be1b), commit as needed now to split the change
74 (to edit 49d44ab2be1b, `hg histedit --continue` after making changes)
74 (to edit 49d44ab2be1b, `hg histedit --continue` after making changes)
75 [240]
75 [240]
76 $ hg st
76 $ hg st
77 A b
77 A b
78 A d
78 A d
79 ? plan
79 ? plan
80 $ hg commit --amend -X . -m XXXXXX
80 $ hg commit --amend -X . -m XXXXXX
81 $ hg commit --amend -X . -m b2
81 $ hg commit --amend -X . -m b2
82 $ hg --hidden --config extensions.strip= strip 'desc(XXXXXX)' --no-backup
82 $ hg --hidden --config extensions.strip= strip 'desc(XXXXXX)' --no-backup
83 $ hg histedit --continue
83 $ hg histedit --continue
84 $ hg log -G
84 $ hg log -G
85 @ 8:273c1f3b8626 c
85 @ 8:273c1f3b8626 c
86 |
86 |
87 o 7:aba7da937030 b2
87 o 7:aba7da937030 b2
88 |
88 |
89 o 0:cb9a9f314b8b a
89 o 0:cb9a9f314b8b a
90
90
91 $ hg debugobsolete
91 $ hg debugobsolete
92 e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
92 e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
93 1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '12', 'operation': 'histedit', 'user': 'test'}
93 1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '12', 'operation': 'histedit', 'user': 'test'}
94 114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '12', 'operation': 'histedit', 'user': 'test'}
94 114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '12', 'operation': 'histedit', 'user': 'test'}
95 76f72745eac0643d16530e56e2f86e36e40631f1 2ca853e48edbd6453a0674dc0fe28a0974c51b9c 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
95 76f72745eac0643d16530e56e2f86e36e40631f1 2ca853e48edbd6453a0674dc0fe28a0974c51b9c 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
96 2ca853e48edbd6453a0674dc0fe28a0974c51b9c aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
96 2ca853e48edbd6453a0674dc0fe28a0974c51b9c aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
97 49d44ab2be1b67a79127568a67c9c99430633b48 273c1f3b86267ed3ec684bb13af1fa4d6ba56e02 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'histedit', 'user': 'test'}
97 49d44ab2be1b67a79127568a67c9c99430633b48 273c1f3b86267ed3ec684bb13af1fa4d6ba56e02 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'histedit', 'user': 'test'}
98 46abc7c4d8738e8563e577f7889e1b6db3da4199 aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '5', 'operation': 'histedit', 'user': 'test'}
98 46abc7c4d8738e8563e577f7889e1b6db3da4199 aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '5', 'operation': 'histedit', 'user': 'test'}
99 $ cd ..
99 $ cd ..
100
100
101 Base setup for the rest of the testing
101 Base setup for the rest of the testing
102 ======================================
102 ======================================
103
103
104 $ hg init base
104 $ hg init base
105 $ cd base
105 $ cd base
106
106
107 $ for x in a b c d e f ; do
107 $ for x in a b c d e f ; do
108 > echo $x > $x
108 > echo $x > $x
109 > hg add $x
109 > hg add $x
110 > hg ci -m $x
110 > hg ci -m $x
111 > done
111 > done
112
112
113 $ hg log --graph
113 $ hg log --graph
114 @ 5:652413bf663e f
114 @ 5:652413bf663e f
115 |
115 |
116 o 4:e860deea161a e
116 o 4:e860deea161a e
117 |
117 |
118 o 3:055a42cdd887 d
118 o 3:055a42cdd887 d
119 |
119 |
120 o 2:177f92b77385 c
120 o 2:177f92b77385 c
121 |
121 |
122 o 1:d2ae7f538514 b
122 o 1:d2ae7f538514 b
123 |
123 |
124 o 0:cb9a9f314b8b a
124 o 0:cb9a9f314b8b a
125
125
126
126
127 $ HGEDITOR=cat hg histedit 1
127 $ HGEDITOR=cat hg histedit 1
128 pick d2ae7f538514 1 b
128 pick d2ae7f538514 1 b
129 pick 177f92b77385 2 c
129 pick 177f92b77385 2 c
130 pick 055a42cdd887 3 d
130 pick 055a42cdd887 3 d
131 pick e860deea161a 4 e
131 pick e860deea161a 4 e
132 pick 652413bf663e 5 f
132 pick 652413bf663e 5 f
133
133
134 # Edit history between d2ae7f538514 and 652413bf663e
134 # Edit history between d2ae7f538514 and 652413bf663e
135 #
135 #
136 # Commits are listed from least to most recent
136 # Commits are listed from least to most recent
137 #
137 #
138 # You can reorder changesets by reordering the lines
138 # You can reorder changesets by reordering the lines
139 #
139 #
140 # Commands:
140 # Commands:
141 #
141 #
142 # e, edit = use commit, but allow edits before making new commit
142 # e, edit = use commit, but allow edits before making new commit
143 # m, mess = edit commit message without changing commit content
143 # m, mess = edit commit message without changing commit content
144 # p, pick = use commit
144 # p, pick = use commit
145 # b, base = checkout changeset and apply further changesets from there
145 # b, base = checkout changeset and apply further changesets from there
146 # d, drop = remove commit from history
146 # d, drop = remove commit from history
147 # f, fold = use commit, but combine it with the one above
147 # f, fold = use commit, but combine it with the one above
148 # r, roll = like fold, but discard this commit's description and date
148 # r, roll = like fold, but discard this commit's description and date
149 #
149 #
150 $ hg histedit 1 --commands - --verbose <<EOF | grep histedit
150 $ hg histedit 1 --commands - --verbose <<EOF | grep histedit
151 > pick 177f92b77385 2 c
151 > pick 177f92b77385 2 c
152 > drop d2ae7f538514 1 b
152 > drop d2ae7f538514 1 b
153 > pick 055a42cdd887 3 d
153 > pick 055a42cdd887 3 d
154 > fold e860deea161a 4 e
154 > fold e860deea161a 4 e
155 > pick 652413bf663e 5 f
155 > pick 652413bf663e 5 f
156 > EOF
156 > EOF
157 [1]
157 [1]
158 $ hg log --graph --hidden
158 $ hg log --graph --hidden
159 @ 10:cacdfd884a93 f
159 @ 10:cacdfd884a93 f
160 |
160 |
161 o 9:59d9f330561f d
161 o 9:59d9f330561f d
162 |
162 |
163 | x 8:b558abc46d09 fold-temp-revision e860deea161a
163 | x 8:b558abc46d09 fold-temp-revision e860deea161a
164 | |
164 | |
165 | x 7:96e494a2d553 d
165 | x 7:96e494a2d553 d
166 |/
166 |/
167 o 6:b346ab9a313d c
167 o 6:b346ab9a313d c
168 |
168 |
169 | x 5:652413bf663e f
169 | x 5:652413bf663e f
170 | |
170 | |
171 | x 4:e860deea161a e
171 | x 4:e860deea161a e
172 | |
172 | |
173 | x 3:055a42cdd887 d
173 | x 3:055a42cdd887 d
174 | |
174 | |
175 | x 2:177f92b77385 c
175 | x 2:177f92b77385 c
176 | |
176 | |
177 | x 1:d2ae7f538514 b
177 | x 1:d2ae7f538514 b
178 |/
178 |/
179 o 0:cb9a9f314b8b a
179 o 0:cb9a9f314b8b a
180
180
181 $ hg debugobsolete
181 $ hg debugobsolete
182 d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'histedit', 'user': 'test'}
182 d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'histedit', 'user': 'test'}
183 177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'histedit', 'user': 'test'}
183 177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'histedit', 'user': 'test'}
184 055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'histedit', 'user': 'test'}
184 055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'histedit', 'user': 'test'}
185 e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'histedit', 'user': 'test'}
185 e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'histedit', 'user': 'test'}
186 652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'histedit', 'user': 'test'}
186 652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'histedit', 'user': 'test'}
187 96e494a2d553dd05902ba1cee1d94d4cb7b8faed 0 {b346ab9a313db8537ecf96fca3ca3ca984ef3bd7} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'histedit', 'user': 'test'}
187 96e494a2d553dd05902ba1cee1d94d4cb7b8faed 0 {b346ab9a313db8537ecf96fca3ca3ca984ef3bd7} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'histedit', 'user': 'test'}
188 b558abc46d09c30f57ac31e85a8a3d64d2e906e4 0 {96e494a2d553dd05902ba1cee1d94d4cb7b8faed} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'histedit', 'user': 'test'}
188 b558abc46d09c30f57ac31e85a8a3d64d2e906e4 0 {96e494a2d553dd05902ba1cee1d94d4cb7b8faed} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'histedit', 'user': 'test'}
189
189
190
190
191 Ensure hidden revision does not prevent histedit
191 Ensure hidden revision does not prevent histedit
192 -------------------------------------------------
192 -------------------------------------------------
193
193
194 create an hidden revision
194 create an hidden revision
195
195
196 $ hg histedit 6 --commands - << EOF
196 $ hg histedit 6 --commands - << EOF
197 > pick b346ab9a313d 6 c
197 > pick b346ab9a313d 6 c
198 > drop 59d9f330561f 7 d
198 > drop 59d9f330561f 7 d
199 > pick cacdfd884a93 8 f
199 > pick cacdfd884a93 8 f
200 > EOF
200 > EOF
201 $ hg log --graph
201 $ hg log --graph
202 @ 11:c13eb81022ca f
202 @ 11:c13eb81022ca f
203 |
203 |
204 o 6:b346ab9a313d c
204 o 6:b346ab9a313d c
205 |
205 |
206 o 0:cb9a9f314b8b a
206 o 0:cb9a9f314b8b a
207
207
208 check hidden revision are ignored (6 have hidden children 7 and 8)
208 check hidden revision are ignored (6 have hidden children 7 and 8)
209
209
210 $ hg histedit 6 --commands - << EOF
210 $ hg histedit 6 --commands - << EOF
211 > pick b346ab9a313d 6 c
211 > pick b346ab9a313d 6 c
212 > pick c13eb81022ca 8 f
212 > pick c13eb81022ca 8 f
213 > EOF
213 > EOF
214
214
215
215
216
216
217 Test that rewriting leaving instability behind is allowed
217 Test that rewriting leaving instability behind is allowed
218 ---------------------------------------------------------------------
218 ---------------------------------------------------------------------
219
219
220 $ hg up '.^'
220 $ hg up '.^'
221 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
221 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
222 $ hg log -r 'children(.)'
222 $ hg log -r 'children(.)'
223 11:c13eb81022ca f (no-eol)
223 11:c13eb81022ca f (no-eol)
224 $ hg histedit -r '.' --commands - <<EOF
224 $ hg histedit -r '.' --commands - <<EOF
225 > edit b346ab9a313d 6 c
225 > edit b346ab9a313d 6 c
226 > EOF
226 > EOF
227 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
227 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
228 Editing (b346ab9a313d), commit as needed now to split the change
228 Editing (b346ab9a313d), commit as needed now to split the change
229 (to edit b346ab9a313d, `hg histedit --continue` after making changes)
229 (to edit b346ab9a313d, `hg histedit --continue` after making changes)
230 [240]
230 [240]
231 $ echo c >> c
231 $ echo c >> c
232 $ hg histedit --continue
232 $ hg histedit --continue
233 1 new orphan changesets
233 1 new orphan changesets
234
234
235 $ hg log -r 'orphan()'
235 $ hg log -r 'orphan()'
236 11:c13eb81022ca f (no-eol)
236 11:c13eb81022ca f (no-eol)
237
237
238 stabilise
238 stabilise
239
239
240 $ hg rebase -r 'orphan()' -d .
240 $ hg rebase -r 'orphan()' -d .
241 rebasing 11:c13eb81022ca "f"
241 rebasing 11:c13eb81022ca "f"
242 $ hg up tip -q
242 $ hg up tip -q
243
243
244 Test dropping of changeset on the top of the stack
244 Test dropping of changeset on the top of the stack
245 -------------------------------------------------------
245 -------------------------------------------------------
246
246
247 Nothing is rewritten below, the working directory parent must be change for the
247 Nothing is rewritten below, the working directory parent must be change for the
248 dropped changeset to be hidden.
248 dropped changeset to be hidden.
249
249
250 $ cd ..
250 $ cd ..
251 $ hg clone base droplast
251 $ hg clone base droplast
252 updating to branch default
252 updating to branch default
253 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
253 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
254 $ cd droplast
254 $ cd droplast
255 $ hg histedit -r '40db8afa467b' --commands - << EOF
255 $ hg histedit -r '40db8afa467b' --commands - << EOF
256 > pick 40db8afa467b 10 c
256 > pick 40db8afa467b 10 c
257 > drop b449568bf7fc 11 f
257 > drop b449568bf7fc 11 f
258 > EOF
258 > EOF
259 $ hg log -G
259 $ hg log -G
260 @ 12:40db8afa467b c
260 @ 12:40db8afa467b c
261 |
261 |
262 o 0:cb9a9f314b8b a
262 o 0:cb9a9f314b8b a
263
263
264
264
265 With rewritten ancestors
265 With rewritten ancestors
266
266
267 $ echo e > e
267 $ echo e > e
268 $ hg add e
268 $ hg add e
269 $ hg commit -m g
269 $ hg commit -m g
270 $ echo f > f
270 $ echo f > f
271 $ hg add f
271 $ hg add f
272 $ hg commit -m h
272 $ hg commit -m h
273 $ hg histedit -r '40db8afa467b' --commands - << EOF
273 $ hg histedit -r '40db8afa467b' --commands - << EOF
274 > pick 47a8561c0449 12 g
274 > pick 47a8561c0449 12 g
275 > pick 40db8afa467b 10 c
275 > pick 40db8afa467b 10 c
276 > drop 1b3b05f35ff0 13 h
276 > drop 1b3b05f35ff0 13 h
277 > EOF
277 > EOF
278 $ hg log -G
278 $ hg log -G
279 @ 17:ee6544123ab8 c
279 @ 17:ee6544123ab8 c
280 |
280 |
281 o 16:269e713e9eae g
281 o 16:269e713e9eae g
282 |
282 |
283 o 0:cb9a9f314b8b a
283 o 0:cb9a9f314b8b a
284
284
285 $ cd ../base
285 $ cd ../base
286
286
287
287
288
288
289 Test phases support
289 Test phases support
290 ===========================================
290 ===========================================
291
291
292 Check that histedit respect immutability
292 Check that histedit respect immutability
293 -------------------------------------------
293 -------------------------------------------
294
294
295 $ cat >> $HGRCPATH << EOF
295 $ cat >> $HGRCPATH << EOF
296 > [command-templates]
296 > [command-templates]
297 > log = {rev}:{node|short} ({phase}) {desc|firstline}\n
297 > log = {rev}:{node|short} ({phase}) {desc|firstline}\n
298 > EOF
298 > EOF
299
299
300 $ hg ph -pv '.^'
300 $ hg ph -pv '.^'
301 phase changed for 2 changesets
301 phase changed for 2 changesets
302 $ hg log -G
302 $ hg log -G
303 @ 13:b449568bf7fc (draft) f
303 @ 13:b449568bf7fc (draft) f
304 |
304 |
305 o 12:40db8afa467b (public) c
305 o 12:40db8afa467b (public) c
306 |
306 |
307 o 0:cb9a9f314b8b (public) a
307 o 0:cb9a9f314b8b (public) a
308
308
309 $ hg histedit -r '.~2'
309 $ hg histedit -r '.~2'
310 abort: cannot edit public changesets: cb9a9f314b8b, 40db8afa467b
310 abort: cannot edit public changesets: cb9a9f314b8b, 40db8afa467b
311 (see 'hg help phases' for details)
311 (see 'hg help phases' for details)
312 [10]
312 [10]
313
313
314
314
315 Prepare further testing
315 Prepare further testing
316 -------------------------------------------
316 -------------------------------------------
317
317
318 $ for x in g h i j k ; do
318 $ for x in g h i j k ; do
319 > echo $x > $x
319 > echo $x > $x
320 > hg add $x
320 > hg add $x
321 > hg ci -m $x
321 > hg ci -m $x
322 > done
322 > done
323 $ hg phase --force --secret .~2
323 $ hg phase --force --secret .~2
324 $ hg log -G
324 $ hg log -G
325 @ 18:ee118ab9fa44 (secret) k
325 @ 18:ee118ab9fa44 (secret) k
326 |
326 |
327 o 17:3a6c53ee7f3d (secret) j
327 o 17:3a6c53ee7f3d (secret) j
328 |
328 |
329 o 16:b605fb7503f2 (secret) i
329 o 16:b605fb7503f2 (secret) i
330 |
330 |
331 o 15:7395e1ff83bd (draft) h
331 o 15:7395e1ff83bd (draft) h
332 |
332 |
333 o 14:6b70183d2492 (draft) g
333 o 14:6b70183d2492 (draft) g
334 |
334 |
335 o 13:b449568bf7fc (draft) f
335 o 13:b449568bf7fc (draft) f
336 |
336 |
337 o 12:40db8afa467b (public) c
337 o 12:40db8afa467b (public) c
338 |
338 |
339 o 0:cb9a9f314b8b (public) a
339 o 0:cb9a9f314b8b (public) a
340
340
341 $ cd ..
341 $ cd ..
342
342
343 simple phase conservation
343 simple phase conservation
344 -------------------------------------------
344 -------------------------------------------
345
345
346 Resulting changeset should conserve the phase of the original one whatever the
346 Resulting changeset should conserve the phase of the original one whatever the
347 phases.new-commit option is.
347 phases.new-commit option is.
348
348
349 New-commit as draft (default)
349 New-commit as draft (default)
350
350
351 $ cp -R base simple-draft
351 $ cp -R base simple-draft
352 $ cd simple-draft
352 $ cd simple-draft
353 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
353 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
354 > edit b449568bf7fc 11 f
354 > edit b449568bf7fc 11 f
355 > pick 6b70183d2492 12 g
355 > pick 6b70183d2492 12 g
356 > pick 7395e1ff83bd 13 h
356 > pick 7395e1ff83bd 13 h
357 > pick b605fb7503f2 14 i
357 > pick b605fb7503f2 14 i
358 > pick 3a6c53ee7f3d 15 j
358 > pick 3a6c53ee7f3d 15 j
359 > pick ee118ab9fa44 16 k
359 > pick ee118ab9fa44 16 k
360 > EOF
360 > EOF
361 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
361 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
362 Editing (b449568bf7fc), commit as needed now to split the change
362 Editing (b449568bf7fc), commit as needed now to split the change
363 (to edit b449568bf7fc, `hg histedit --continue` after making changes)
363 (to edit b449568bf7fc, `hg histedit --continue` after making changes)
364 [240]
364 [240]
365 $ echo f >> f
365 $ echo f >> f
366 $ hg histedit --continue
366 $ hg histedit --continue
367 $ hg log -G
367 $ hg log -G
368 @ 24:12e89af74238 (secret) k
368 @ 24:12e89af74238 (secret) k
369 |
369 |
370 o 23:636a8687b22e (secret) j
370 o 23:636a8687b22e (secret) j
371 |
371 |
372 o 22:ccaf0a38653f (secret) i
372 o 22:ccaf0a38653f (secret) i
373 |
373 |
374 o 21:11a89d1c2613 (draft) h
374 o 21:11a89d1c2613 (draft) h
375 |
375 |
376 o 20:c1dec7ca82ea (draft) g
376 o 20:c1dec7ca82ea (draft) g
377 |
377 |
378 o 19:087281e68428 (draft) f
378 o 19:087281e68428 (draft) f
379 |
379 |
380 o 12:40db8afa467b (public) c
380 o 12:40db8afa467b (public) c
381 |
381 |
382 o 0:cb9a9f314b8b (public) a
382 o 0:cb9a9f314b8b (public) a
383
383
384 $ cd ..
384 $ cd ..
385
385
386
386
387 New-commit as secret (config)
387 New-commit as secret (config)
388
388
389 $ cp -R base simple-secret
389 $ cp -R base simple-secret
390 $ cd simple-secret
390 $ cd simple-secret
391 $ cat >> .hg/hgrc << EOF
391 $ cat >> .hg/hgrc << EOF
392 > [phases]
392 > [phases]
393 > new-commit=secret
393 > new-commit=secret
394 > EOF
394 > EOF
395 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
395 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
396 > edit b449568bf7fc 11 f
396 > edit b449568bf7fc 11 f
397 > pick 6b70183d2492 12 g
397 > pick 6b70183d2492 12 g
398 > pick 7395e1ff83bd 13 h
398 > pick 7395e1ff83bd 13 h
399 > pick b605fb7503f2 14 i
399 > pick b605fb7503f2 14 i
400 > pick 3a6c53ee7f3d 15 j
400 > pick 3a6c53ee7f3d 15 j
401 > pick ee118ab9fa44 16 k
401 > pick ee118ab9fa44 16 k
402 > EOF
402 > EOF
403 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
403 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
404 Editing (b449568bf7fc), commit as needed now to split the change
404 Editing (b449568bf7fc), commit as needed now to split the change
405 (to edit b449568bf7fc, `hg histedit --continue` after making changes)
405 (to edit b449568bf7fc, `hg histedit --continue` after making changes)
406 [240]
406 [240]
407 $ echo f >> f
407 $ echo f >> f
408 $ hg histedit --continue
408 $ hg histedit --continue
409 $ hg log -G
409 $ hg log -G
410 @ 24:12e89af74238 (secret) k
410 @ 24:12e89af74238 (secret) k
411 |
411 |
412 o 23:636a8687b22e (secret) j
412 o 23:636a8687b22e (secret) j
413 |
413 |
414 o 22:ccaf0a38653f (secret) i
414 o 22:ccaf0a38653f (secret) i
415 |
415 |
416 o 21:11a89d1c2613 (draft) h
416 o 21:11a89d1c2613 (draft) h
417 |
417 |
418 o 20:c1dec7ca82ea (draft) g
418 o 20:c1dec7ca82ea (draft) g
419 |
419 |
420 o 19:087281e68428 (draft) f
420 o 19:087281e68428 (draft) f
421 |
421 |
422 o 12:40db8afa467b (public) c
422 o 12:40db8afa467b (public) c
423 |
423 |
424 o 0:cb9a9f314b8b (public) a
424 o 0:cb9a9f314b8b (public) a
425
425
426 $ cd ..
426 $ cd ..
427
427
428
428
429 Changeset reordering
429 Changeset reordering
430 -------------------------------------------
430 -------------------------------------------
431
431
432 If a secret changeset is put before a draft one, all descendant should be secret.
432 If a secret changeset is put before a draft one, all descendant should be secret.
433 It seems more important to present the secret phase.
433 It seems more important to present the secret phase.
434
434
435 $ cp -R base reorder
435 $ cp -R base reorder
436 $ cd reorder
436 $ cd reorder
437 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
437 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
438 > pick b449568bf7fc 11 f
438 > pick b449568bf7fc 11 f
439 > pick 3a6c53ee7f3d 15 j
439 > pick 3a6c53ee7f3d 15 j
440 > pick 6b70183d2492 12 g
440 > pick 6b70183d2492 12 g
441 > pick b605fb7503f2 14 i
441 > pick b605fb7503f2 14 i
442 > pick 7395e1ff83bd 13 h
442 > pick 7395e1ff83bd 13 h
443 > pick ee118ab9fa44 16 k
443 > pick ee118ab9fa44 16 k
444 > EOF
444 > EOF
445 $ hg log -G
445 $ hg log -G
446 @ 23:558246857888 (secret) k
446 @ 23:558246857888 (secret) k
447 |
447 |
448 o 22:28bd44768535 (secret) h
448 o 22:28bd44768535 (secret) h
449 |
449 |
450 o 21:d5395202aeb9 (secret) i
450 o 21:d5395202aeb9 (secret) i
451 |
451 |
452 o 20:21edda8e341b (secret) g
452 o 20:21edda8e341b (secret) g
453 |
453 |
454 o 19:5ab64f3a4832 (secret) j
454 o 19:5ab64f3a4832 (secret) j
455 |
455 |
456 o 13:b449568bf7fc (draft) f
456 o 13:b449568bf7fc (draft) f
457 |
457 |
458 o 12:40db8afa467b (public) c
458 o 12:40db8afa467b (public) c
459 |
459 |
460 o 0:cb9a9f314b8b (public) a
460 o 0:cb9a9f314b8b (public) a
461
461
462 $ cd ..
462 $ cd ..
463
463
464 Changeset folding
464 Changeset folding
465 -------------------------------------------
465 -------------------------------------------
466
466
467 Folding a secret changeset with a draft one turn the result secret (again,
467 Folding a secret changeset with a draft one turn the result secret (again,
468 better safe than sorry). Folding between same phase changeset still works
468 better safe than sorry). Folding between same phase changeset still works
469
469
470 Note that there is a few reordering in this series for more extensive test
470 Note that there is a few reordering in this series for more extensive test
471
471
472 $ cp -R base folding
472 $ cp -R base folding
473 $ cd folding
473 $ cd folding
474 $ cat >> .hg/hgrc << EOF
474 $ cat >> .hg/hgrc << EOF
475 > [phases]
475 > [phases]
476 > new-commit=secret
476 > new-commit=secret
477 > EOF
477 > EOF
478 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
478 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
479 > pick 7395e1ff83bd 13 h
479 > pick 7395e1ff83bd 13 h
480 > fold b449568bf7fc 11 f
480 > fold b449568bf7fc 11 f
481 > pick 6b70183d2492 12 g
481 > pick 6b70183d2492 12 g
482 > fold 3a6c53ee7f3d 15 j
482 > fold 3a6c53ee7f3d 15 j
483 > pick b605fb7503f2 14 i
483 > pick b605fb7503f2 14 i
484 > fold ee118ab9fa44 16 k
484 > fold ee118ab9fa44 16 k
485 > EOF
485 > EOF
486 $ hg log -G
486 $ hg log -G
487 @ 27:f9daec13fb98 (secret) i
487 @ 27:f9daec13fb98 (secret) i
488 |
488 |
489 o 24:49807617f46a (secret) g
489 o 24:49807617f46a (secret) g
490 |
490 |
491 o 21:050280826e04 (draft) h
491 o 21:050280826e04 (draft) h
492 |
492 |
493 o 12:40db8afa467b (public) c
493 o 12:40db8afa467b (public) c
494 |
494 |
495 o 0:cb9a9f314b8b (public) a
495 o 0:cb9a9f314b8b (public) a
496
496
497 $ hg co 49807617f46a
497 $ hg co 49807617f46a
498 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
498 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
499 $ echo wat >> wat
499 $ echo wat >> wat
500 $ hg add wat
500 $ hg add wat
501 $ hg ci -m 'add wat'
501 $ hg ci -m 'add wat'
502 created new head
502 created new head
503 $ hg merge f9daec13fb98
503 $ hg merge f9daec13fb98
504 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
504 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
505 (branch merge, don't forget to commit)
505 (branch merge, don't forget to commit)
506 $ hg ci -m 'merge'
506 $ hg ci -m 'merge'
507 $ echo not wat > wat
507 $ echo not wat > wat
508 $ hg ci -m 'modify wat'
508 $ hg ci -m 'modify wat'
509 $ hg histedit 050280826e04
509 $ hg histedit 050280826e04
510 abort: cannot edit history that contains merges
510 abort: cannot edit history that contains merges
511 [255]
511 [20]
512 $ cd ..
512 $ cd ..
513
513
514 Check abort behavior
514 Check abort behavior
515 -------------------------------------------
515 -------------------------------------------
516
516
517 We checks that abort properly clean the repository so the same histedit can be
517 We checks that abort properly clean the repository so the same histedit can be
518 attempted later.
518 attempted later.
519
519
520 $ cp -R base abort
520 $ cp -R base abort
521 $ cd abort
521 $ cd abort
522 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
522 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
523 > pick b449568bf7fc 13 f
523 > pick b449568bf7fc 13 f
524 > pick 7395e1ff83bd 15 h
524 > pick 7395e1ff83bd 15 h
525 > pick 6b70183d2492 14 g
525 > pick 6b70183d2492 14 g
526 > pick b605fb7503f2 16 i
526 > pick b605fb7503f2 16 i
527 > roll 3a6c53ee7f3d 17 j
527 > roll 3a6c53ee7f3d 17 j
528 > edit ee118ab9fa44 18 k
528 > edit ee118ab9fa44 18 k
529 > EOF
529 > EOF
530 Editing (ee118ab9fa44), commit as needed now to split the change
530 Editing (ee118ab9fa44), commit as needed now to split the change
531 (to edit ee118ab9fa44, `hg histedit --continue` after making changes)
531 (to edit ee118ab9fa44, `hg histedit --continue` after making changes)
532 [240]
532 [240]
533
533
534 #if abortcommand
534 #if abortcommand
535 when in dry-run mode
535 when in dry-run mode
536 $ hg abort --dry-run
536 $ hg abort --dry-run
537 histedit in progress, will be aborted
537 histedit in progress, will be aborted
538 #endif
538 #endif
539
539
540 $ hg abort
540 $ hg abort
541 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
541 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
542 saved backup bundle to $TESTTMP/abort/.hg/strip-backup/4dc06258baa6-dff4ef05-backup.hg
542 saved backup bundle to $TESTTMP/abort/.hg/strip-backup/4dc06258baa6-dff4ef05-backup.hg
543
543
544 $ hg log -G
544 $ hg log -G
545 @ 18:ee118ab9fa44 (secret) k
545 @ 18:ee118ab9fa44 (secret) k
546 |
546 |
547 o 17:3a6c53ee7f3d (secret) j
547 o 17:3a6c53ee7f3d (secret) j
548 |
548 |
549 o 16:b605fb7503f2 (secret) i
549 o 16:b605fb7503f2 (secret) i
550 |
550 |
551 o 15:7395e1ff83bd (draft) h
551 o 15:7395e1ff83bd (draft) h
552 |
552 |
553 o 14:6b70183d2492 (draft) g
553 o 14:6b70183d2492 (draft) g
554 |
554 |
555 o 13:b449568bf7fc (draft) f
555 o 13:b449568bf7fc (draft) f
556 |
556 |
557 o 12:40db8afa467b (public) c
557 o 12:40db8afa467b (public) c
558 |
558 |
559 o 0:cb9a9f314b8b (public) a
559 o 0:cb9a9f314b8b (public) a
560
560
561 $ hg histedit -r 'b449568bf7fc' --commands - << EOF --config experimental.evolution.track-operation=1
561 $ hg histedit -r 'b449568bf7fc' --commands - << EOF --config experimental.evolution.track-operation=1
562 > pick b449568bf7fc 13 f
562 > pick b449568bf7fc 13 f
563 > pick 7395e1ff83bd 15 h
563 > pick 7395e1ff83bd 15 h
564 > pick 6b70183d2492 14 g
564 > pick 6b70183d2492 14 g
565 > pick b605fb7503f2 16 i
565 > pick b605fb7503f2 16 i
566 > pick 3a6c53ee7f3d 17 j
566 > pick 3a6c53ee7f3d 17 j
567 > edit ee118ab9fa44 18 k
567 > edit ee118ab9fa44 18 k
568 > EOF
568 > EOF
569 Editing (ee118ab9fa44), commit as needed now to split the change
569 Editing (ee118ab9fa44), commit as needed now to split the change
570 (to edit ee118ab9fa44, `hg histedit --continue` after making changes)
570 (to edit ee118ab9fa44, `hg histedit --continue` after making changes)
571 [240]
571 [240]
572 $ hg histedit --continue --config experimental.evolution.track-operation=1
572 $ hg histedit --continue --config experimental.evolution.track-operation=1
573 $ hg log -G
573 $ hg log -G
574 @ 23:175d6b286a22 (secret) k
574 @ 23:175d6b286a22 (secret) k
575 |
575 |
576 o 22:44ca09d59ae4 (secret) j
576 o 22:44ca09d59ae4 (secret) j
577 |
577 |
578 o 21:31747692a644 (secret) i
578 o 21:31747692a644 (secret) i
579 |
579 |
580 o 20:9985cd4f21fa (draft) g
580 o 20:9985cd4f21fa (draft) g
581 |
581 |
582 o 19:4dc06258baa6 (draft) h
582 o 19:4dc06258baa6 (draft) h
583 |
583 |
584 o 13:b449568bf7fc (draft) f
584 o 13:b449568bf7fc (draft) f
585 |
585 |
586 o 12:40db8afa467b (public) c
586 o 12:40db8afa467b (public) c
587 |
587 |
588 o 0:cb9a9f314b8b (public) a
588 o 0:cb9a9f314b8b (public) a
589
589
590 $ hg debugobsolete --rev .
590 $ hg debugobsolete --rev .
591 ee118ab9fa44ebb86be85996548b5517a39e5093 175d6b286a224c23f192e79a581ce83131a53fa2 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'histedit', 'user': 'test'}
591 ee118ab9fa44ebb86be85996548b5517a39e5093 175d6b286a224c23f192e79a581ce83131a53fa2 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'histedit', 'user': 'test'}
@@ -1,156 +1,156
1 $ cat >> $HGRCPATH <<EOF
1 $ cat >> $HGRCPATH <<EOF
2 > [extensions]
2 > [extensions]
3 > histedit=
3 > histedit=
4 > EOF
4 > EOF
5
5
6 $ initrepos ()
6 $ initrepos ()
7 > {
7 > {
8 > hg init r
8 > hg init r
9 > cd r
9 > cd r
10 > for x in a b c ; do
10 > for x in a b c ; do
11 > echo $x > $x
11 > echo $x > $x
12 > hg add $x
12 > hg add $x
13 > hg ci -m $x
13 > hg ci -m $x
14 > done
14 > done
15 > cd ..
15 > cd ..
16 > hg clone r r2 | grep -v updating
16 > hg clone r r2 | grep -v updating
17 > cd r2
17 > cd r2
18 > for x in d e f ; do
18 > for x in d e f ; do
19 > echo $x > $x
19 > echo $x > $x
20 > hg add $x
20 > hg add $x
21 > hg ci -m $x
21 > hg ci -m $x
22 > done
22 > done
23 > cd ..
23 > cd ..
24 > hg init r3
24 > hg init r3
25 > cd r3
25 > cd r3
26 > for x in g h i ; do
26 > for x in g h i ; do
27 > echo $x > $x
27 > echo $x > $x
28 > hg add $x
28 > hg add $x
29 > hg ci -m $x
29 > hg ci -m $x
30 > done
30 > done
31 > cd ..
31 > cd ..
32 > }
32 > }
33
33
34 $ initrepos
34 $ initrepos
35 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
35 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
36
36
37 show the edit commands offered by outgoing
37 show the edit commands offered by outgoing
38 $ cd r2
38 $ cd r2
39 $ HGEDITOR=cat hg histedit --outgoing ../r | grep -v comparing | grep -v searching
39 $ HGEDITOR=cat hg histedit --outgoing ../r | grep -v comparing | grep -v searching
40 pick 055a42cdd887 3 d
40 pick 055a42cdd887 3 d
41 pick e860deea161a 4 e
41 pick e860deea161a 4 e
42 pick 652413bf663e 5 f
42 pick 652413bf663e 5 f
43
43
44 # Edit history between 055a42cdd887 and 652413bf663e
44 # Edit history between 055a42cdd887 and 652413bf663e
45 #
45 #
46 # Commits are listed from least to most recent
46 # Commits are listed from least to most recent
47 #
47 #
48 # You can reorder changesets by reordering the lines
48 # You can reorder changesets by reordering the lines
49 #
49 #
50 # Commands:
50 # Commands:
51 #
51 #
52 # e, edit = use commit, but allow edits before making new commit
52 # e, edit = use commit, but allow edits before making new commit
53 # m, mess = edit commit message without changing commit content
53 # m, mess = edit commit message without changing commit content
54 # p, pick = use commit
54 # p, pick = use commit
55 # b, base = checkout changeset and apply further changesets from there
55 # b, base = checkout changeset and apply further changesets from there
56 # d, drop = remove commit from history
56 # d, drop = remove commit from history
57 # f, fold = use commit, but combine it with the one above
57 # f, fold = use commit, but combine it with the one above
58 # r, roll = like fold, but discard this commit's description and date
58 # r, roll = like fold, but discard this commit's description and date
59 #
59 #
60 $ cd ..
60 $ cd ..
61
61
62 show the error from unrelated repos
62 show the error from unrelated repos
63 $ cd r3
63 $ cd r3
64 $ HGEDITOR=cat hg histedit --outgoing ../r | grep -v comparing | grep -v searching
64 $ HGEDITOR=cat hg histedit --outgoing ../r | grep -v comparing | grep -v searching
65 abort: repository is unrelated
65 abort: repository is unrelated
66 [1]
66 [1]
67 $ cd ..
67 $ cd ..
68
68
69 show the error from unrelated repos
69 show the error from unrelated repos
70 $ cd r3
70 $ cd r3
71 $ HGEDITOR=cat hg histedit --force --outgoing ../r
71 $ HGEDITOR=cat hg histedit --force --outgoing ../r
72 comparing with ../r
72 comparing with ../r
73 searching for changes
73 searching for changes
74 warning: repository is unrelated
74 warning: repository is unrelated
75 pick 2a4042b45417 0 g
75 pick 2a4042b45417 0 g
76 pick 68c46b4927ce 1 h
76 pick 68c46b4927ce 1 h
77 pick 51281e65ba79 2 i
77 pick 51281e65ba79 2 i
78
78
79 # Edit history between 2a4042b45417 and 51281e65ba79
79 # Edit history between 2a4042b45417 and 51281e65ba79
80 #
80 #
81 # Commits are listed from least to most recent
81 # Commits are listed from least to most recent
82 #
82 #
83 # You can reorder changesets by reordering the lines
83 # You can reorder changesets by reordering the lines
84 #
84 #
85 # Commands:
85 # Commands:
86 #
86 #
87 # e, edit = use commit, but allow edits before making new commit
87 # e, edit = use commit, but allow edits before making new commit
88 # m, mess = edit commit message without changing commit content
88 # m, mess = edit commit message without changing commit content
89 # p, pick = use commit
89 # p, pick = use commit
90 # b, base = checkout changeset and apply further changesets from there
90 # b, base = checkout changeset and apply further changesets from there
91 # d, drop = remove commit from history
91 # d, drop = remove commit from history
92 # f, fold = use commit, but combine it with the one above
92 # f, fold = use commit, but combine it with the one above
93 # r, roll = like fold, but discard this commit's description and date
93 # r, roll = like fold, but discard this commit's description and date
94 #
94 #
95 $ cd ..
95 $ cd ..
96
96
97 test sensitivity to branch in URL:
97 test sensitivity to branch in URL:
98
98
99 $ cd r2
99 $ cd r2
100 $ hg -q update 2
100 $ hg -q update 2
101 $ hg -q branch foo
101 $ hg -q branch foo
102 $ hg commit -m 'create foo branch'
102 $ hg commit -m 'create foo branch'
103 $ HGEDITOR=cat hg histedit --outgoing '../r#foo' | grep -v comparing | grep -v searching
103 $ HGEDITOR=cat hg histedit --outgoing '../r#foo' | grep -v comparing | grep -v searching
104 pick f26599ee3441 6 create foo branch
104 pick f26599ee3441 6 create foo branch
105
105
106 # Edit history between f26599ee3441 and f26599ee3441
106 # Edit history between f26599ee3441 and f26599ee3441
107 #
107 #
108 # Commits are listed from least to most recent
108 # Commits are listed from least to most recent
109 #
109 #
110 # You can reorder changesets by reordering the lines
110 # You can reorder changesets by reordering the lines
111 #
111 #
112 # Commands:
112 # Commands:
113 #
113 #
114 # e, edit = use commit, but allow edits before making new commit
114 # e, edit = use commit, but allow edits before making new commit
115 # m, mess = edit commit message without changing commit content
115 # m, mess = edit commit message without changing commit content
116 # p, pick = use commit
116 # p, pick = use commit
117 # b, base = checkout changeset and apply further changesets from there
117 # b, base = checkout changeset and apply further changesets from there
118 # d, drop = remove commit from history
118 # d, drop = remove commit from history
119 # f, fold = use commit, but combine it with the one above
119 # f, fold = use commit, but combine it with the one above
120 # r, roll = like fold, but discard this commit's description and date
120 # r, roll = like fold, but discard this commit's description and date
121 #
121 #
122
122
123 test to check number of roots in outgoing revisions
123 test to check number of roots in outgoing revisions
124
124
125 $ hg -q outgoing -G --template '{node|short}({branch})' '../r'
125 $ hg -q outgoing -G --template '{node|short}({branch})' '../r'
126 @ f26599ee3441(foo)
126 @ f26599ee3441(foo)
127
127
128 o 652413bf663e(default)
128 o 652413bf663e(default)
129 |
129 |
130 o e860deea161a(default)
130 o e860deea161a(default)
131 |
131 |
132 o 055a42cdd887(default)
132 o 055a42cdd887(default)
133
133
134 $ HGEDITOR=cat hg -q histedit --outgoing '../r'
134 $ HGEDITOR=cat hg -q histedit --outgoing '../r'
135 abort: there are ambiguous outgoing revisions
135 abort: there are ambiguous outgoing revisions
136 (see 'hg help histedit' for more detail)
136 (see 'hg help histedit' for more detail)
137 [255]
137 [20]
138
138
139 $ hg -q update -C 2
139 $ hg -q update -C 2
140 $ echo aa >> a
140 $ echo aa >> a
141 $ hg -q commit -m 'another head on default'
141 $ hg -q commit -m 'another head on default'
142 $ hg -q outgoing -G --template '{node|short}({branch})' '../r#default'
142 $ hg -q outgoing -G --template '{node|short}({branch})' '../r#default'
143 @ 3879dc049647(default)
143 @ 3879dc049647(default)
144
144
145 o 652413bf663e(default)
145 o 652413bf663e(default)
146 |
146 |
147 o e860deea161a(default)
147 o e860deea161a(default)
148 |
148 |
149 o 055a42cdd887(default)
149 o 055a42cdd887(default)
150
150
151 $ HGEDITOR=cat hg -q histedit --outgoing '../r#default'
151 $ HGEDITOR=cat hg -q histedit --outgoing '../r#default'
152 abort: there are ambiguous outgoing revisions
152 abort: there are ambiguous outgoing revisions
153 (see 'hg help histedit' for more detail)
153 (see 'hg help histedit' for more detail)
154 [255]
154 [20]
155
155
156 $ cd ..
156 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now