##// END OF EJS Templates
histedit: delete to drop...
Mateusz Kwapich -
r27414:6602a7b9 default
parent child Browse files
Show More
@@ -1,1414 +1,1432
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 stop for amending
37 # e, edit = use commit, but stop for amending
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
39 # r, roll = like fold, but discard this commit's description
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 #
42 #
43
43
44 In this file, lines beginning with ``#`` are ignored. You must specify a rule
44 In this file, lines beginning with ``#`` are ignored. You must specify a rule
45 for each revision in your history. For example, if you had meant to add gamma
45 for each revision in your history. For example, if you had meant to add gamma
46 before beta, and then wanted to add delta in the same revision as beta, you
46 before beta, and then wanted to add delta in the same revision as beta, you
47 would reorganize the file to look like this::
47 would reorganize the file to look like this::
48
48
49 pick 030b686bedc4 Add gamma
49 pick 030b686bedc4 Add gamma
50 pick c561b4e977df Add beta
50 pick c561b4e977df Add beta
51 fold 7c2fd3b9020c Add delta
51 fold 7c2fd3b9020c Add delta
52
52
53 # Edit history between c561b4e977df and 7c2fd3b9020c
53 # Edit history between c561b4e977df and 7c2fd3b9020c
54 #
54 #
55 # Commits are listed from least to most recent
55 # Commits are listed from least to most recent
56 #
56 #
57 # Commands:
57 # Commands:
58 # p, pick = use commit
58 # p, pick = use commit
59 # e, edit = use commit, but stop for amending
59 # e, edit = use commit, but stop for amending
60 # f, fold = use commit, but combine it with the one above
60 # f, fold = use commit, but combine it with the one above
61 # r, roll = like fold, but discard this commit's description
61 # r, roll = like fold, but discard this commit's description
62 # d, drop = remove commit from history
62 # d, drop = remove commit from history
63 # m, mess = edit commit message without changing commit content
63 # m, mess = edit commit message without changing commit content
64 #
64 #
65
65
66 At which point you close the editor and ``histedit`` starts working. When you
66 At which point you close the editor and ``histedit`` starts working. When you
67 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
67 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
68 those revisions together, offering you a chance to clean up the commit message::
68 those revisions together, offering you a chance to clean up the commit message::
69
69
70 Add beta
70 Add beta
71 ***
71 ***
72 Add delta
72 Add delta
73
73
74 Edit the commit message to your liking, then close the editor. For
74 Edit the commit message to your liking, then close the editor. For
75 this example, let's assume that the commit message was changed to
75 this example, let's assume that the commit message was changed to
76 ``Add beta and delta.`` After histedit has run and had a chance to
76 ``Add beta and delta.`` After histedit has run and had a chance to
77 remove any old or temporary revisions it needed, the history looks
77 remove any old or temporary revisions it needed, the history looks
78 like this::
78 like this::
79
79
80 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
80 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
81 | Add beta and delta.
81 | Add beta and delta.
82 |
82 |
83 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
83 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
84 | Add gamma
84 | Add gamma
85 |
85 |
86 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
86 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
87 Add alpha
87 Add alpha
88
88
89 Note that ``histedit`` does *not* remove any revisions (even its own temporary
89 Note that ``histedit`` does *not* remove any revisions (even its own temporary
90 ones) until after it has completed all the editing operations, so it will
90 ones) until after it has completed all the editing operations, so it will
91 probably perform several strip operations when it's done. For the above example,
91 probably perform several strip operations when it's done. For the above example,
92 it had to run strip twice. Strip can be slow depending on a variety of factors,
92 it had to run strip twice. Strip can be slow depending on a variety of factors,
93 so you might need to be a little patient. You can choose to keep the original
93 so you might need to be a little patient. You can choose to keep the original
94 revisions by passing the ``--keep`` flag.
94 revisions by passing the ``--keep`` flag.
95
95
96 The ``edit`` operation will drop you back to a command prompt,
96 The ``edit`` operation will drop you back to a command prompt,
97 allowing you to edit files freely, or even use ``hg record`` to commit
97 allowing you to edit files freely, or even use ``hg record`` to commit
98 some changes as a separate commit. When you're done, any remaining
98 some changes as a separate commit. When you're done, any remaining
99 uncommitted changes will be committed as well. When done, run ``hg
99 uncommitted changes will be committed as well. When done, run ``hg
100 histedit --continue`` to finish this step. You'll be prompted for a
100 histedit --continue`` to finish this step. You'll be prompted for a
101 new commit message, but the default commit message will be the
101 new commit message, but the default commit message will be the
102 original message for the ``edit`` ed revision.
102 original message for the ``edit`` ed revision.
103
103
104 The ``message`` operation will give you a chance to revise a commit
104 The ``message`` operation will give you a chance to revise a commit
105 message without changing the contents. It's a shortcut for doing
105 message without changing the contents. It's a shortcut for doing
106 ``edit`` immediately followed by `hg histedit --continue``.
106 ``edit`` immediately followed by `hg histedit --continue``.
107
107
108 If ``histedit`` encounters a conflict when moving a revision (while
108 If ``histedit`` encounters a conflict when moving a revision (while
109 handling ``pick`` or ``fold``), it'll stop in a similar manner to
109 handling ``pick`` or ``fold``), it'll stop in a similar manner to
110 ``edit`` with the difference that it won't prompt you for a commit
110 ``edit`` with the difference that it won't prompt you for a commit
111 message when done. If you decide at this point that you don't like how
111 message when done. If you decide at this point that you don't like how
112 much work it will be to rearrange history, or that you made a mistake,
112 much work it will be to rearrange history, or that you made a mistake,
113 you can use ``hg histedit --abort`` to abandon the new changes you
113 you can use ``hg histedit --abort`` to abandon the new changes you
114 have made and return to the state before you attempted to edit your
114 have made and return to the state before you attempted to edit your
115 history.
115 history.
116
116
117 If we clone the histedit-ed example repository above and add four more
117 If we clone the histedit-ed example repository above and add four more
118 changes, such that we have the following history::
118 changes, such that we have the following history::
119
119
120 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
120 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
121 | Add theta
121 | Add theta
122 |
122 |
123 o 5 140988835471 2009-04-27 18:04 -0500 stefan
123 o 5 140988835471 2009-04-27 18:04 -0500 stefan
124 | Add eta
124 | Add eta
125 |
125 |
126 o 4 122930637314 2009-04-27 18:04 -0500 stefan
126 o 4 122930637314 2009-04-27 18:04 -0500 stefan
127 | Add zeta
127 | Add zeta
128 |
128 |
129 o 3 836302820282 2009-04-27 18:04 -0500 stefan
129 o 3 836302820282 2009-04-27 18:04 -0500 stefan
130 | Add epsilon
130 | Add epsilon
131 |
131 |
132 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
132 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
133 | Add beta and delta.
133 | Add beta and delta.
134 |
134 |
135 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
135 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
136 | Add gamma
136 | Add gamma
137 |
137 |
138 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
138 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
139 Add alpha
139 Add alpha
140
140
141 If you run ``hg histedit --outgoing`` on the clone then it is the same
141 If you run ``hg histedit --outgoing`` on the clone then it is the same
142 as running ``hg histedit 836302820282``. If you need plan to push to a
142 as running ``hg histedit 836302820282``. If you need plan to push to a
143 repository that Mercurial does not detect to be related to the source
143 repository that Mercurial does not detect to be related to the source
144 repo, you can add a ``--force`` option.
144 repo, you can add a ``--force`` option.
145
145
146 Config
147 ------
148
146 Histedit rule lines are truncated to 80 characters by default. You
149 Histedit rule lines are truncated to 80 characters by default. You
147 can customize this behavior by setting a different length in your
150 can customize this behavior by setting a different length in your
148 configuration file::
151 configuration file::
149
152
150 [histedit]
153 [histedit]
151 linelen = 120 # truncate rule lines at 120 characters
154 linelen = 120 # truncate rule lines at 120 characters
152
155
153 ``hg histedit`` attempts to automatically choose an appropriate base
156 ``hg histedit`` attempts to automatically choose an appropriate base
154 revision to use. To change which base revision is used, define a
157 revision to use. To change which base revision is used, define a
155 revset in your configuration file::
158 revset in your configuration file::
156
159
157 [histedit]
160 [histedit]
158 defaultrev = only(.) & draft()
161 defaultrev = only(.) & draft()
162
163 By default each edited revision needs to be present in histedit commands.
164 To remove revision you need to use ``drop`` operation. You can configure
165 the drop to be implicit for missing commits by adding:
166
167 [histedit]
168 dropmissing = True
169
159 """
170 """
160
171
161 try:
172 try:
162 import cPickle as pickle
173 import cPickle as pickle
163 pickle.dump # import now
174 pickle.dump # import now
164 except ImportError:
175 except ImportError:
165 import pickle
176 import pickle
166 import errno
177 import errno
167 import os
178 import os
168 import sys
179 import sys
169
180
170 from mercurial import bundle2
181 from mercurial import bundle2
171 from mercurial import cmdutil
182 from mercurial import cmdutil
172 from mercurial import discovery
183 from mercurial import discovery
173 from mercurial import error
184 from mercurial import error
174 from mercurial import copies
185 from mercurial import copies
175 from mercurial import context
186 from mercurial import context
176 from mercurial import destutil
187 from mercurial import destutil
177 from mercurial import exchange
188 from mercurial import exchange
178 from mercurial import extensions
189 from mercurial import extensions
179 from mercurial import hg
190 from mercurial import hg
180 from mercurial import node
191 from mercurial import node
181 from mercurial import repair
192 from mercurial import repair
182 from mercurial import scmutil
193 from mercurial import scmutil
183 from mercurial import util
194 from mercurial import util
184 from mercurial import obsolete
195 from mercurial import obsolete
185 from mercurial import merge as mergemod
196 from mercurial import merge as mergemod
186 from mercurial.lock import release
197 from mercurial.lock import release
187 from mercurial.i18n import _
198 from mercurial.i18n import _
188
199
189 cmdtable = {}
200 cmdtable = {}
190 command = cmdutil.command(cmdtable)
201 command = cmdutil.command(cmdtable)
191
202
192 class _constraints(object):
203 class _constraints(object):
193 # aborts if there are multiple rules for one node
204 # aborts if there are multiple rules for one node
194 noduplicates = 'noduplicates'
205 noduplicates = 'noduplicates'
195 # abort if the node does belong to edited stack
206 # abort if the node does belong to edited stack
196 forceother = 'forceother'
207 forceother = 'forceother'
197 # abort if the node doesn't belong to edited stack
208 # abort if the node doesn't belong to edited stack
198 noother = 'noother'
209 noother = 'noother'
199
210
200 @classmethod
211 @classmethod
201 def known(cls):
212 def known(cls):
202 return set([v for k, v in cls.__dict__.items() if k[0] != '_'])
213 return set([v for k, v in cls.__dict__.items() if k[0] != '_'])
203
214
204 # Note for extension authors: ONLY specify testedwith = 'internal' for
215 # Note for extension authors: ONLY specify testedwith = 'internal' for
205 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
216 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
206 # be specifying the version(s) of Mercurial they are tested with, or
217 # be specifying the version(s) of Mercurial they are tested with, or
207 # leave the attribute unspecified.
218 # leave the attribute unspecified.
208 testedwith = 'internal'
219 testedwith = 'internal'
209
220
210 # i18n: command names and abbreviations must remain untranslated
221 # i18n: command names and abbreviations must remain untranslated
211 editcomment = _("""# Edit history between %s and %s
222 editcomment = _("""# Edit history between %s and %s
212 #
223 #
213 # Commits are listed from least to most recent
224 # Commits are listed from least to most recent
214 #
225 #
215 # Commands:
226 # Commands:
216 # p, pick = use commit
227 # p, pick = use commit
217 # e, edit = use commit, but stop for amending
228 # e, edit = use commit, but stop for amending
218 # f, fold = use commit, but combine it with the one above
229 # f, fold = use commit, but combine it with the one above
219 # r, roll = like fold, but discard this commit's description
230 # r, roll = like fold, but discard this commit's description
220 # d, drop = remove commit from history
231 # d, drop = remove commit from history
221 # m, mess = edit commit message without changing commit content
232 # m, mess = edit commit message without changing commit content
222 #
233 #
223 """)
234 """)
224
235
225 class histeditstate(object):
236 class histeditstate(object):
226 def __init__(self, repo, parentctxnode=None, actions=None, keep=None,
237 def __init__(self, repo, parentctxnode=None, actions=None, keep=None,
227 topmost=None, replacements=None, lock=None, wlock=None):
238 topmost=None, replacements=None, lock=None, wlock=None):
228 self.repo = repo
239 self.repo = repo
229 self.actions = actions
240 self.actions = actions
230 self.keep = keep
241 self.keep = keep
231 self.topmost = topmost
242 self.topmost = topmost
232 self.parentctxnode = parentctxnode
243 self.parentctxnode = parentctxnode
233 self.lock = lock
244 self.lock = lock
234 self.wlock = wlock
245 self.wlock = wlock
235 self.backupfile = None
246 self.backupfile = None
236 if replacements is None:
247 if replacements is None:
237 self.replacements = []
248 self.replacements = []
238 else:
249 else:
239 self.replacements = replacements
250 self.replacements = replacements
240
251
241 def read(self):
252 def read(self):
242 """Load histedit state from disk and set fields appropriately."""
253 """Load histedit state from disk and set fields appropriately."""
243 try:
254 try:
244 fp = self.repo.vfs('histedit-state', 'r')
255 fp = self.repo.vfs('histedit-state', 'r')
245 except IOError as err:
256 except IOError as err:
246 if err.errno != errno.ENOENT:
257 if err.errno != errno.ENOENT:
247 raise
258 raise
248 raise error.Abort(_('no histedit in progress'))
259 raise error.Abort(_('no histedit in progress'))
249
260
250 try:
261 try:
251 data = pickle.load(fp)
262 data = pickle.load(fp)
252 parentctxnode, rules, keep, topmost, replacements = data
263 parentctxnode, rules, keep, topmost, replacements = data
253 backupfile = None
264 backupfile = None
254 except pickle.UnpicklingError:
265 except pickle.UnpicklingError:
255 data = self._load()
266 data = self._load()
256 parentctxnode, rules, keep, topmost, replacements, backupfile = data
267 parentctxnode, rules, keep, topmost, replacements, backupfile = data
257
268
258 self.parentctxnode = parentctxnode
269 self.parentctxnode = parentctxnode
259 rules = "\n".join(["%s %s" % (verb, rest) for [verb, rest] in rules])
270 rules = "\n".join(["%s %s" % (verb, rest) for [verb, rest] in rules])
260 actions = parserules(rules, self)
271 actions = parserules(rules, self)
261 self.actions = actions
272 self.actions = actions
262 self.keep = keep
273 self.keep = keep
263 self.topmost = topmost
274 self.topmost = topmost
264 self.replacements = replacements
275 self.replacements = replacements
265 self.backupfile = backupfile
276 self.backupfile = backupfile
266
277
267 def write(self):
278 def write(self):
268 fp = self.repo.vfs('histedit-state', 'w')
279 fp = self.repo.vfs('histedit-state', 'w')
269 fp.write('v1\n')
280 fp.write('v1\n')
270 fp.write('%s\n' % node.hex(self.parentctxnode))
281 fp.write('%s\n' % node.hex(self.parentctxnode))
271 fp.write('%s\n' % node.hex(self.topmost))
282 fp.write('%s\n' % node.hex(self.topmost))
272 fp.write('%s\n' % self.keep)
283 fp.write('%s\n' % self.keep)
273 fp.write('%d\n' % len(self.actions))
284 fp.write('%d\n' % len(self.actions))
274 for action in self.actions:
285 for action in self.actions:
275 fp.write('%s\n' % action.tostate())
286 fp.write('%s\n' % action.tostate())
276 fp.write('%d\n' % len(self.replacements))
287 fp.write('%d\n' % len(self.replacements))
277 for replacement in self.replacements:
288 for replacement in self.replacements:
278 fp.write('%s%s\n' % (node.hex(replacement[0]), ''.join(node.hex(r)
289 fp.write('%s%s\n' % (node.hex(replacement[0]), ''.join(node.hex(r)
279 for r in replacement[1])))
290 for r in replacement[1])))
280 backupfile = self.backupfile
291 backupfile = self.backupfile
281 if not backupfile:
292 if not backupfile:
282 backupfile = ''
293 backupfile = ''
283 fp.write('%s\n' % backupfile)
294 fp.write('%s\n' % backupfile)
284 fp.close()
295 fp.close()
285
296
286 def _load(self):
297 def _load(self):
287 fp = self.repo.vfs('histedit-state', 'r')
298 fp = self.repo.vfs('histedit-state', 'r')
288 lines = [l[:-1] for l in fp.readlines()]
299 lines = [l[:-1] for l in fp.readlines()]
289
300
290 index = 0
301 index = 0
291 lines[index] # version number
302 lines[index] # version number
292 index += 1
303 index += 1
293
304
294 parentctxnode = node.bin(lines[index])
305 parentctxnode = node.bin(lines[index])
295 index += 1
306 index += 1
296
307
297 topmost = node.bin(lines[index])
308 topmost = node.bin(lines[index])
298 index += 1
309 index += 1
299
310
300 keep = lines[index] == 'True'
311 keep = lines[index] == 'True'
301 index += 1
312 index += 1
302
313
303 # Rules
314 # Rules
304 rules = []
315 rules = []
305 rulelen = int(lines[index])
316 rulelen = int(lines[index])
306 index += 1
317 index += 1
307 for i in xrange(rulelen):
318 for i in xrange(rulelen):
308 ruleaction = lines[index]
319 ruleaction = lines[index]
309 index += 1
320 index += 1
310 rule = lines[index]
321 rule = lines[index]
311 index += 1
322 index += 1
312 rules.append((ruleaction, rule))
323 rules.append((ruleaction, rule))
313
324
314 # Replacements
325 # Replacements
315 replacements = []
326 replacements = []
316 replacementlen = int(lines[index])
327 replacementlen = int(lines[index])
317 index += 1
328 index += 1
318 for i in xrange(replacementlen):
329 for i in xrange(replacementlen):
319 replacement = lines[index]
330 replacement = lines[index]
320 original = node.bin(replacement[:40])
331 original = node.bin(replacement[:40])
321 succ = [node.bin(replacement[i:i + 40]) for i in
332 succ = [node.bin(replacement[i:i + 40]) for i in
322 range(40, len(replacement), 40)]
333 range(40, len(replacement), 40)]
323 replacements.append((original, succ))
334 replacements.append((original, succ))
324 index += 1
335 index += 1
325
336
326 backupfile = lines[index]
337 backupfile = lines[index]
327 index += 1
338 index += 1
328
339
329 fp.close()
340 fp.close()
330
341
331 return parentctxnode, rules, keep, topmost, replacements, backupfile
342 return parentctxnode, rules, keep, topmost, replacements, backupfile
332
343
333 def clear(self):
344 def clear(self):
334 if self.inprogress():
345 if self.inprogress():
335 self.repo.vfs.unlink('histedit-state')
346 self.repo.vfs.unlink('histedit-state')
336
347
337 def inprogress(self):
348 def inprogress(self):
338 return self.repo.vfs.exists('histedit-state')
349 return self.repo.vfs.exists('histedit-state')
339
350
340
351
341 class histeditaction(object):
352 class histeditaction(object):
342 def __init__(self, state, node):
353 def __init__(self, state, node):
343 self.state = state
354 self.state = state
344 self.repo = state.repo
355 self.repo = state.repo
345 self.node = node
356 self.node = node
346
357
347 @classmethod
358 @classmethod
348 def fromrule(cls, state, rule):
359 def fromrule(cls, state, rule):
349 """Parses the given rule, returning an instance of the histeditaction.
360 """Parses the given rule, returning an instance of the histeditaction.
350 """
361 """
351 rulehash = rule.strip().split(' ', 1)[0]
362 rulehash = rule.strip().split(' ', 1)[0]
352 return cls(state, node.bin(rulehash))
363 return cls(state, node.bin(rulehash))
353
364
354 def verify(self):
365 def verify(self):
355 """ Verifies semantic correctness of the rule"""
366 """ Verifies semantic correctness of the rule"""
356 repo = self.repo
367 repo = self.repo
357 ha = node.hex(self.node)
368 ha = node.hex(self.node)
358 try:
369 try:
359 self.node = repo[ha].node()
370 self.node = repo[ha].node()
360 except error.RepoError:
371 except error.RepoError:
361 raise error.Abort(_('unknown changeset %s listed')
372 raise error.Abort(_('unknown changeset %s listed')
362 % ha[:12])
373 % ha[:12])
363
374
364 def torule(self):
375 def torule(self):
365 """build a histedit rule line for an action
376 """build a histedit rule line for an action
366
377
367 by default lines are in the form:
378 by default lines are in the form:
368 <hash> <rev> <summary>
379 <hash> <rev> <summary>
369 """
380 """
370 ctx = self.repo[self.node]
381 ctx = self.repo[self.node]
371 summary = ''
382 summary = ''
372 if ctx.description():
383 if ctx.description():
373 summary = ctx.description().splitlines()[0]
384 summary = ctx.description().splitlines()[0]
374 line = '%s %s %d %s' % (self.verb, ctx, ctx.rev(), summary)
385 line = '%s %s %d %s' % (self.verb, ctx, ctx.rev(), summary)
375 # trim to 75 columns by default so it's not stupidly wide in my editor
386 # trim to 75 columns by default so it's not stupidly wide in my editor
376 # (the 5 more are left for verb)
387 # (the 5 more are left for verb)
377 maxlen = self.repo.ui.configint('histedit', 'linelen', default=80)
388 maxlen = self.repo.ui.configint('histedit', 'linelen', default=80)
378 maxlen = max(maxlen, 22) # avoid truncating hash
389 maxlen = max(maxlen, 22) # avoid truncating hash
379 return util.ellipsis(line, maxlen)
390 return util.ellipsis(line, maxlen)
380
391
381 def tostate(self):
392 def tostate(self):
382 """Print an action in format used by histedit state files
393 """Print an action in format used by histedit state files
383 (the first line is a verb, the remainder is the second)
394 (the first line is a verb, the remainder is the second)
384 """
395 """
385 return "%s\n%s" % (self.verb, node.hex(self.node))
396 return "%s\n%s" % (self.verb, node.hex(self.node))
386
397
387 def constraints(self):
398 def constraints(self):
388 """Return a set of constrains that this action should be verified for
399 """Return a set of constrains that this action should be verified for
389 """
400 """
390 return set([_constraints.noduplicates, _constraints.noother])
401 return set([_constraints.noduplicates, _constraints.noother])
391
402
392 def nodetoverify(self):
403 def nodetoverify(self):
393 """Returns a node associated with the action that will be used for
404 """Returns a node associated with the action that will be used for
394 verification purposes.
405 verification purposes.
395
406
396 If the action doesn't correspond to node it should return None
407 If the action doesn't correspond to node it should return None
397 """
408 """
398 return self.node
409 return self.node
399
410
400 def run(self):
411 def run(self):
401 """Runs the action. The default behavior is simply apply the action's
412 """Runs the action. The default behavior is simply apply the action's
402 rulectx onto the current parentctx."""
413 rulectx onto the current parentctx."""
403 self.applychange()
414 self.applychange()
404 self.continuedirty()
415 self.continuedirty()
405 return self.continueclean()
416 return self.continueclean()
406
417
407 def applychange(self):
418 def applychange(self):
408 """Applies the changes from this action's rulectx onto the current
419 """Applies the changes from this action's rulectx onto the current
409 parentctx, but does not commit them."""
420 parentctx, but does not commit them."""
410 repo = self.repo
421 repo = self.repo
411 rulectx = repo[self.node]
422 rulectx = repo[self.node]
412 hg.update(repo, self.state.parentctxnode, quietempty=True)
423 hg.update(repo, self.state.parentctxnode, quietempty=True)
413 stats = applychanges(repo.ui, repo, rulectx, {})
424 stats = applychanges(repo.ui, repo, rulectx, {})
414 if stats and stats[3] > 0:
425 if stats and stats[3] > 0:
415 raise error.InterventionRequired(_('Fix up the change and run '
426 raise error.InterventionRequired(_('Fix up the change and run '
416 'hg histedit --continue'))
427 'hg histedit --continue'))
417
428
418 def continuedirty(self):
429 def continuedirty(self):
419 """Continues the action when changes have been applied to the working
430 """Continues the action when changes have been applied to the working
420 copy. The default behavior is to commit the dirty changes."""
431 copy. The default behavior is to commit the dirty changes."""
421 repo = self.repo
432 repo = self.repo
422 rulectx = repo[self.node]
433 rulectx = repo[self.node]
423
434
424 editor = self.commiteditor()
435 editor = self.commiteditor()
425 commit = commitfuncfor(repo, rulectx)
436 commit = commitfuncfor(repo, rulectx)
426
437
427 commit(text=rulectx.description(), user=rulectx.user(),
438 commit(text=rulectx.description(), user=rulectx.user(),
428 date=rulectx.date(), extra=rulectx.extra(), editor=editor)
439 date=rulectx.date(), extra=rulectx.extra(), editor=editor)
429
440
430 def commiteditor(self):
441 def commiteditor(self):
431 """The editor to be used to edit the commit message."""
442 """The editor to be used to edit the commit message."""
432 return False
443 return False
433
444
434 def continueclean(self):
445 def continueclean(self):
435 """Continues the action when the working copy is clean. The default
446 """Continues the action when the working copy is clean. The default
436 behavior is to accept the current commit as the new version of the
447 behavior is to accept the current commit as the new version of the
437 rulectx."""
448 rulectx."""
438 ctx = self.repo['.']
449 ctx = self.repo['.']
439 if ctx.node() == self.state.parentctxnode:
450 if ctx.node() == self.state.parentctxnode:
440 self.repo.ui.warn(_('%s: empty changeset\n') %
451 self.repo.ui.warn(_('%s: empty changeset\n') %
441 node.short(self.node))
452 node.short(self.node))
442 return ctx, [(self.node, tuple())]
453 return ctx, [(self.node, tuple())]
443 if ctx.node() == self.node:
454 if ctx.node() == self.node:
444 # Nothing changed
455 # Nothing changed
445 return ctx, []
456 return ctx, []
446 return ctx, [(self.node, (ctx.node(),))]
457 return ctx, [(self.node, (ctx.node(),))]
447
458
448 def commitfuncfor(repo, src):
459 def commitfuncfor(repo, src):
449 """Build a commit function for the replacement of <src>
460 """Build a commit function for the replacement of <src>
450
461
451 This function ensure we apply the same treatment to all changesets.
462 This function ensure we apply the same treatment to all changesets.
452
463
453 - Add a 'histedit_source' entry in extra.
464 - Add a 'histedit_source' entry in extra.
454
465
455 Note that fold has its own separated logic because its handling is a bit
466 Note that fold has its own separated logic because its handling is a bit
456 different and not easily factored out of the fold method.
467 different and not easily factored out of the fold method.
457 """
468 """
458 phasemin = src.phase()
469 phasemin = src.phase()
459 def commitfunc(**kwargs):
470 def commitfunc(**kwargs):
460 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
471 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
461 try:
472 try:
462 repo.ui.setconfig('phases', 'new-commit', phasemin,
473 repo.ui.setconfig('phases', 'new-commit', phasemin,
463 'histedit')
474 'histedit')
464 extra = kwargs.get('extra', {}).copy()
475 extra = kwargs.get('extra', {}).copy()
465 extra['histedit_source'] = src.hex()
476 extra['histedit_source'] = src.hex()
466 kwargs['extra'] = extra
477 kwargs['extra'] = extra
467 return repo.commit(**kwargs)
478 return repo.commit(**kwargs)
468 finally:
479 finally:
469 repo.ui.restoreconfig(phasebackup)
480 repo.ui.restoreconfig(phasebackup)
470 return commitfunc
481 return commitfunc
471
482
472 def applychanges(ui, repo, ctx, opts):
483 def applychanges(ui, repo, ctx, opts):
473 """Merge changeset from ctx (only) in the current working directory"""
484 """Merge changeset from ctx (only) in the current working directory"""
474 wcpar = repo.dirstate.parents()[0]
485 wcpar = repo.dirstate.parents()[0]
475 if ctx.p1().node() == wcpar:
486 if ctx.p1().node() == wcpar:
476 # edits are "in place" we do not need to make any merge,
487 # edits are "in place" we do not need to make any merge,
477 # just applies changes on parent for edition
488 # just applies changes on parent for edition
478 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
489 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
479 stats = None
490 stats = None
480 else:
491 else:
481 try:
492 try:
482 # ui.forcemerge is an internal variable, do not document
493 # ui.forcemerge is an internal variable, do not document
483 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
494 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
484 'histedit')
495 'histedit')
485 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit'])
496 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit'])
486 finally:
497 finally:
487 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit')
498 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit')
488 return stats
499 return stats
489
500
490 def collapse(repo, first, last, commitopts, skipprompt=False):
501 def collapse(repo, first, last, commitopts, skipprompt=False):
491 """collapse the set of revisions from first to last as new one.
502 """collapse the set of revisions from first to last as new one.
492
503
493 Expected commit options are:
504 Expected commit options are:
494 - message
505 - message
495 - date
506 - date
496 - username
507 - username
497 Commit message is edited in all cases.
508 Commit message is edited in all cases.
498
509
499 This function works in memory."""
510 This function works in memory."""
500 ctxs = list(repo.set('%d::%d', first, last))
511 ctxs = list(repo.set('%d::%d', first, last))
501 if not ctxs:
512 if not ctxs:
502 return None
513 return None
503 for c in ctxs:
514 for c in ctxs:
504 if not c.mutable():
515 if not c.mutable():
505 raise error.Abort(
516 raise error.Abort(
506 _("cannot fold into public change %s") % node.short(c.node()))
517 _("cannot fold into public change %s") % node.short(c.node()))
507 base = first.parents()[0]
518 base = first.parents()[0]
508
519
509 # commit a new version of the old changeset, including the update
520 # commit a new version of the old changeset, including the update
510 # collect all files which might be affected
521 # collect all files which might be affected
511 files = set()
522 files = set()
512 for ctx in ctxs:
523 for ctx in ctxs:
513 files.update(ctx.files())
524 files.update(ctx.files())
514
525
515 # Recompute copies (avoid recording a -> b -> a)
526 # Recompute copies (avoid recording a -> b -> a)
516 copied = copies.pathcopies(base, last)
527 copied = copies.pathcopies(base, last)
517
528
518 # prune files which were reverted by the updates
529 # prune files which were reverted by the updates
519 def samefile(f):
530 def samefile(f):
520 if f in last.manifest():
531 if f in last.manifest():
521 a = last.filectx(f)
532 a = last.filectx(f)
522 if f in base.manifest():
533 if f in base.manifest():
523 b = base.filectx(f)
534 b = base.filectx(f)
524 return (a.data() == b.data()
535 return (a.data() == b.data()
525 and a.flags() == b.flags())
536 and a.flags() == b.flags())
526 else:
537 else:
527 return False
538 return False
528 else:
539 else:
529 return f not in base.manifest()
540 return f not in base.manifest()
530 files = [f for f in files if not samefile(f)]
541 files = [f for f in files if not samefile(f)]
531 # commit version of these files as defined by head
542 # commit version of these files as defined by head
532 headmf = last.manifest()
543 headmf = last.manifest()
533 def filectxfn(repo, ctx, path):
544 def filectxfn(repo, ctx, path):
534 if path in headmf:
545 if path in headmf:
535 fctx = last[path]
546 fctx = last[path]
536 flags = fctx.flags()
547 flags = fctx.flags()
537 mctx = context.memfilectx(repo,
548 mctx = context.memfilectx(repo,
538 fctx.path(), fctx.data(),
549 fctx.path(), fctx.data(),
539 islink='l' in flags,
550 islink='l' in flags,
540 isexec='x' in flags,
551 isexec='x' in flags,
541 copied=copied.get(path))
552 copied=copied.get(path))
542 return mctx
553 return mctx
543 return None
554 return None
544
555
545 if commitopts.get('message'):
556 if commitopts.get('message'):
546 message = commitopts['message']
557 message = commitopts['message']
547 else:
558 else:
548 message = first.description()
559 message = first.description()
549 user = commitopts.get('user')
560 user = commitopts.get('user')
550 date = commitopts.get('date')
561 date = commitopts.get('date')
551 extra = commitopts.get('extra')
562 extra = commitopts.get('extra')
552
563
553 parents = (first.p1().node(), first.p2().node())
564 parents = (first.p1().node(), first.p2().node())
554 editor = None
565 editor = None
555 if not skipprompt:
566 if not skipprompt:
556 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.fold')
567 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.fold')
557 new = context.memctx(repo,
568 new = context.memctx(repo,
558 parents=parents,
569 parents=parents,
559 text=message,
570 text=message,
560 files=files,
571 files=files,
561 filectxfn=filectxfn,
572 filectxfn=filectxfn,
562 user=user,
573 user=user,
563 date=date,
574 date=date,
564 extra=extra,
575 extra=extra,
565 editor=editor)
576 editor=editor)
566 return repo.commitctx(new)
577 return repo.commitctx(new)
567
578
568 def _isdirtywc(repo):
579 def _isdirtywc(repo):
569 return repo[None].dirty(missing=True)
580 return repo[None].dirty(missing=True)
570
581
571 def abortdirty():
582 def abortdirty():
572 raise error.Abort(_('working copy has pending changes'),
583 raise error.Abort(_('working copy has pending changes'),
573 hint=_('amend, commit, or revert them and run histedit '
584 hint=_('amend, commit, or revert them and run histedit '
574 '--continue, or abort with histedit --abort'))
585 '--continue, or abort with histedit --abort'))
575
586
576
587
577 actiontable = {}
588 actiontable = {}
578 actionlist = []
589 actionlist = []
579
590
580 def addhisteditaction(verbs):
591 def addhisteditaction(verbs):
581 def wrap(cls):
592 def wrap(cls):
582 cls.verb = verbs[0]
593 cls.verb = verbs[0]
583 for verb in verbs:
594 for verb in verbs:
584 actiontable[verb] = cls
595 actiontable[verb] = cls
585 actionlist.append(cls)
596 actionlist.append(cls)
586 return cls
597 return cls
587 return wrap
598 return wrap
588
599
589
600
590 @addhisteditaction(['pick', 'p'])
601 @addhisteditaction(['pick', 'p'])
591 class pick(histeditaction):
602 class pick(histeditaction):
592 def run(self):
603 def run(self):
593 rulectx = self.repo[self.node]
604 rulectx = self.repo[self.node]
594 if rulectx.parents()[0].node() == self.state.parentctxnode:
605 if rulectx.parents()[0].node() == self.state.parentctxnode:
595 self.repo.ui.debug('node %s unchanged\n' % node.short(self.node))
606 self.repo.ui.debug('node %s unchanged\n' % node.short(self.node))
596 return rulectx, []
607 return rulectx, []
597
608
598 return super(pick, self).run()
609 return super(pick, self).run()
599
610
600 @addhisteditaction(['edit', 'e'])
611 @addhisteditaction(['edit', 'e'])
601 class edit(histeditaction):
612 class edit(histeditaction):
602 def run(self):
613 def run(self):
603 repo = self.repo
614 repo = self.repo
604 rulectx = repo[self.node]
615 rulectx = repo[self.node]
605 hg.update(repo, self.state.parentctxnode, quietempty=True)
616 hg.update(repo, self.state.parentctxnode, quietempty=True)
606 applychanges(repo.ui, repo, rulectx, {})
617 applychanges(repo.ui, repo, rulectx, {})
607 raise error.InterventionRequired(
618 raise error.InterventionRequired(
608 _('Make changes as needed, you may commit or record as needed '
619 _('Make changes as needed, you may commit or record as needed '
609 'now.\nWhen you are finished, run hg histedit --continue to '
620 'now.\nWhen you are finished, run hg histedit --continue to '
610 'resume.'))
621 'resume.'))
611
622
612 def commiteditor(self):
623 def commiteditor(self):
613 return cmdutil.getcommiteditor(edit=True, editform='histedit.edit')
624 return cmdutil.getcommiteditor(edit=True, editform='histedit.edit')
614
625
615 @addhisteditaction(['fold', 'f'])
626 @addhisteditaction(['fold', 'f'])
616 class fold(histeditaction):
627 class fold(histeditaction):
617 def continuedirty(self):
628 def continuedirty(self):
618 repo = self.repo
629 repo = self.repo
619 rulectx = repo[self.node]
630 rulectx = repo[self.node]
620
631
621 commit = commitfuncfor(repo, rulectx)
632 commit = commitfuncfor(repo, rulectx)
622 commit(text='fold-temp-revision %s' % node.short(self.node),
633 commit(text='fold-temp-revision %s' % node.short(self.node),
623 user=rulectx.user(), date=rulectx.date(),
634 user=rulectx.user(), date=rulectx.date(),
624 extra=rulectx.extra())
635 extra=rulectx.extra())
625
636
626 def continueclean(self):
637 def continueclean(self):
627 repo = self.repo
638 repo = self.repo
628 ctx = repo['.']
639 ctx = repo['.']
629 rulectx = repo[self.node]
640 rulectx = repo[self.node]
630 parentctxnode = self.state.parentctxnode
641 parentctxnode = self.state.parentctxnode
631 if ctx.node() == parentctxnode:
642 if ctx.node() == parentctxnode:
632 repo.ui.warn(_('%s: empty changeset\n') %
643 repo.ui.warn(_('%s: empty changeset\n') %
633 node.short(self.node))
644 node.short(self.node))
634 return ctx, [(self.node, (parentctxnode,))]
645 return ctx, [(self.node, (parentctxnode,))]
635
646
636 parentctx = repo[parentctxnode]
647 parentctx = repo[parentctxnode]
637 newcommits = set(c.node() for c in repo.set('(%d::. - %d)', parentctx,
648 newcommits = set(c.node() for c in repo.set('(%d::. - %d)', parentctx,
638 parentctx))
649 parentctx))
639 if not newcommits:
650 if not newcommits:
640 repo.ui.warn(_('%s: cannot fold - working copy is not a '
651 repo.ui.warn(_('%s: cannot fold - working copy is not a '
641 'descendant of previous commit %s\n') %
652 'descendant of previous commit %s\n') %
642 (node.short(self.node), node.short(parentctxnode)))
653 (node.short(self.node), node.short(parentctxnode)))
643 return ctx, [(self.node, (ctx.node(),))]
654 return ctx, [(self.node, (ctx.node(),))]
644
655
645 middlecommits = newcommits.copy()
656 middlecommits = newcommits.copy()
646 middlecommits.discard(ctx.node())
657 middlecommits.discard(ctx.node())
647
658
648 return self.finishfold(repo.ui, repo, parentctx, rulectx, ctx.node(),
659 return self.finishfold(repo.ui, repo, parentctx, rulectx, ctx.node(),
649 middlecommits)
660 middlecommits)
650
661
651 def skipprompt(self):
662 def skipprompt(self):
652 """Returns true if the rule should skip the message editor.
663 """Returns true if the rule should skip the message editor.
653
664
654 For example, 'fold' wants to show an editor, but 'rollup'
665 For example, 'fold' wants to show an editor, but 'rollup'
655 doesn't want to.
666 doesn't want to.
656 """
667 """
657 return False
668 return False
658
669
659 def mergedescs(self):
670 def mergedescs(self):
660 """Returns true if the rule should merge messages of multiple changes.
671 """Returns true if the rule should merge messages of multiple changes.
661
672
662 This exists mainly so that 'rollup' rules can be a subclass of
673 This exists mainly so that 'rollup' rules can be a subclass of
663 'fold'.
674 'fold'.
664 """
675 """
665 return True
676 return True
666
677
667 def finishfold(self, ui, repo, ctx, oldctx, newnode, internalchanges):
678 def finishfold(self, ui, repo, ctx, oldctx, newnode, internalchanges):
668 parent = ctx.parents()[0].node()
679 parent = ctx.parents()[0].node()
669 hg.update(repo, parent)
680 hg.update(repo, parent)
670 ### prepare new commit data
681 ### prepare new commit data
671 commitopts = {}
682 commitopts = {}
672 commitopts['user'] = ctx.user()
683 commitopts['user'] = ctx.user()
673 # commit message
684 # commit message
674 if not self.mergedescs():
685 if not self.mergedescs():
675 newmessage = ctx.description()
686 newmessage = ctx.description()
676 else:
687 else:
677 newmessage = '\n***\n'.join(
688 newmessage = '\n***\n'.join(
678 [ctx.description()] +
689 [ctx.description()] +
679 [repo[r].description() for r in internalchanges] +
690 [repo[r].description() for r in internalchanges] +
680 [oldctx.description()]) + '\n'
691 [oldctx.description()]) + '\n'
681 commitopts['message'] = newmessage
692 commitopts['message'] = newmessage
682 # date
693 # date
683 commitopts['date'] = max(ctx.date(), oldctx.date())
694 commitopts['date'] = max(ctx.date(), oldctx.date())
684 extra = ctx.extra().copy()
695 extra = ctx.extra().copy()
685 # histedit_source
696 # histedit_source
686 # note: ctx is likely a temporary commit but that the best we can do
697 # note: ctx is likely a temporary commit but that the best we can do
687 # here. This is sufficient to solve issue3681 anyway.
698 # here. This is sufficient to solve issue3681 anyway.
688 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
699 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
689 commitopts['extra'] = extra
700 commitopts['extra'] = extra
690 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
701 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
691 try:
702 try:
692 phasemin = max(ctx.phase(), oldctx.phase())
703 phasemin = max(ctx.phase(), oldctx.phase())
693 repo.ui.setconfig('phases', 'new-commit', phasemin, 'histedit')
704 repo.ui.setconfig('phases', 'new-commit', phasemin, 'histedit')
694 n = collapse(repo, ctx, repo[newnode], commitopts,
705 n = collapse(repo, ctx, repo[newnode], commitopts,
695 skipprompt=self.skipprompt())
706 skipprompt=self.skipprompt())
696 finally:
707 finally:
697 repo.ui.restoreconfig(phasebackup)
708 repo.ui.restoreconfig(phasebackup)
698 if n is None:
709 if n is None:
699 return ctx, []
710 return ctx, []
700 hg.update(repo, n)
711 hg.update(repo, n)
701 replacements = [(oldctx.node(), (newnode,)),
712 replacements = [(oldctx.node(), (newnode,)),
702 (ctx.node(), (n,)),
713 (ctx.node(), (n,)),
703 (newnode, (n,)),
714 (newnode, (n,)),
704 ]
715 ]
705 for ich in internalchanges:
716 for ich in internalchanges:
706 replacements.append((ich, (n,)))
717 replacements.append((ich, (n,)))
707 return repo[n], replacements
718 return repo[n], replacements
708
719
709 class base(histeditaction):
720 class base(histeditaction):
710 def constraints(self):
721 def constraints(self):
711 return set([_constraints.forceother])
722 return set([_constraints.forceother])
712
723
713 def run(self):
724 def run(self):
714 if self.repo['.'].node() != self.node:
725 if self.repo['.'].node() != self.node:
715 mergemod.update(self.repo, self.node, False, True)
726 mergemod.update(self.repo, self.node, False, True)
716 # branchmerge, force)
727 # branchmerge, force)
717 return self.continueclean()
728 return self.continueclean()
718
729
719 def continuedirty(self):
730 def continuedirty(self):
720 abortdirty()
731 abortdirty()
721
732
722 def continueclean(self):
733 def continueclean(self):
723 basectx = self.repo['.']
734 basectx = self.repo['.']
724 return basectx, []
735 return basectx, []
725
736
726 @addhisteditaction(['_multifold'])
737 @addhisteditaction(['_multifold'])
727 class _multifold(fold):
738 class _multifold(fold):
728 """fold subclass used for when multiple folds happen in a row
739 """fold subclass used for when multiple folds happen in a row
729
740
730 We only want to fire the editor for the folded message once when
741 We only want to fire the editor for the folded message once when
731 (say) four changes are folded down into a single change. This is
742 (say) four changes are folded down into a single change. This is
732 similar to rollup, but we should preserve both messages so that
743 similar to rollup, but we should preserve both messages so that
733 when the last fold operation runs we can show the user all the
744 when the last fold operation runs we can show the user all the
734 commit messages in their editor.
745 commit messages in their editor.
735 """
746 """
736 def skipprompt(self):
747 def skipprompt(self):
737 return True
748 return True
738
749
739 @addhisteditaction(["roll", "r"])
750 @addhisteditaction(["roll", "r"])
740 class rollup(fold):
751 class rollup(fold):
741 def mergedescs(self):
752 def mergedescs(self):
742 return False
753 return False
743
754
744 def skipprompt(self):
755 def skipprompt(self):
745 return True
756 return True
746
757
747 @addhisteditaction(["drop", "d"])
758 @addhisteditaction(["drop", "d"])
748 class drop(histeditaction):
759 class drop(histeditaction):
749 def run(self):
760 def run(self):
750 parentctx = self.repo[self.state.parentctxnode]
761 parentctx = self.repo[self.state.parentctxnode]
751 return parentctx, [(self.node, tuple())]
762 return parentctx, [(self.node, tuple())]
752
763
753 @addhisteditaction(["mess", "m"])
764 @addhisteditaction(["mess", "m"])
754 class message(histeditaction):
765 class message(histeditaction):
755 def commiteditor(self):
766 def commiteditor(self):
756 return cmdutil.getcommiteditor(edit=True, editform='histedit.mess')
767 return cmdutil.getcommiteditor(edit=True, editform='histedit.mess')
757
768
758 def findoutgoing(ui, repo, remote=None, force=False, opts=None):
769 def findoutgoing(ui, repo, remote=None, force=False, opts=None):
759 """utility function to find the first outgoing changeset
770 """utility function to find the first outgoing changeset
760
771
761 Used by initialization code"""
772 Used by initialization code"""
762 if opts is None:
773 if opts is None:
763 opts = {}
774 opts = {}
764 dest = ui.expandpath(remote or 'default-push', remote or 'default')
775 dest = ui.expandpath(remote or 'default-push', remote or 'default')
765 dest, revs = hg.parseurl(dest, None)[:2]
776 dest, revs = hg.parseurl(dest, None)[:2]
766 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
777 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
767
778
768 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
779 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
769 other = hg.peer(repo, opts, dest)
780 other = hg.peer(repo, opts, dest)
770
781
771 if revs:
782 if revs:
772 revs = [repo.lookup(rev) for rev in revs]
783 revs = [repo.lookup(rev) for rev in revs]
773
784
774 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
785 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
775 if not outgoing.missing:
786 if not outgoing.missing:
776 raise error.Abort(_('no outgoing ancestors'))
787 raise error.Abort(_('no outgoing ancestors'))
777 roots = list(repo.revs("roots(%ln)", outgoing.missing))
788 roots = list(repo.revs("roots(%ln)", outgoing.missing))
778 if 1 < len(roots):
789 if 1 < len(roots):
779 msg = _('there are ambiguous outgoing revisions')
790 msg = _('there are ambiguous outgoing revisions')
780 hint = _('see "hg help histedit" for more detail')
791 hint = _('see "hg help histedit" for more detail')
781 raise error.Abort(msg, hint=hint)
792 raise error.Abort(msg, hint=hint)
782 return repo.lookup(roots[0])
793 return repo.lookup(roots[0])
783
794
784
795
785 @command('histedit',
796 @command('histedit',
786 [('', 'commands', '',
797 [('', 'commands', '',
787 _('read history edits from the specified file'), _('FILE')),
798 _('read history edits from the specified file'), _('FILE')),
788 ('c', 'continue', False, _('continue an edit already in progress')),
799 ('c', 'continue', False, _('continue an edit already in progress')),
789 ('', 'edit-plan', False, _('edit remaining actions list')),
800 ('', 'edit-plan', False, _('edit remaining actions list')),
790 ('k', 'keep', False,
801 ('k', 'keep', False,
791 _("don't strip old nodes after edit is complete")),
802 _("don't strip old nodes after edit is complete")),
792 ('', 'abort', False, _('abort an edit in progress')),
803 ('', 'abort', False, _('abort an edit in progress')),
793 ('o', 'outgoing', False, _('changesets not found in destination')),
804 ('o', 'outgoing', False, _('changesets not found in destination')),
794 ('f', 'force', False,
805 ('f', 'force', False,
795 _('force outgoing even for unrelated repositories')),
806 _('force outgoing even for unrelated repositories')),
796 ('r', 'rev', [], _('first revision to be edited'), _('REV'))],
807 ('r', 'rev', [], _('first revision to be edited'), _('REV'))],
797 _("[ANCESTOR] | --outgoing [URL]"))
808 _("[ANCESTOR] | --outgoing [URL]"))
798 def histedit(ui, repo, *freeargs, **opts):
809 def histedit(ui, repo, *freeargs, **opts):
799 """interactively edit changeset history
810 """interactively edit changeset history
800
811
801 This command edits changesets between an ANCESTOR and the parent of
812 This command edits changesets between an ANCESTOR and the parent of
802 the working directory.
813 the working directory.
803
814
804 The value from the "histedit.defaultrev" config option is used as a
815 The value from the "histedit.defaultrev" config option is used as a
805 revset to select the base revision when ANCESTOR is not specified.
816 revset to select the base revision when ANCESTOR is not specified.
806 The first revision returned by the revset is used. By default, this
817 The first revision returned by the revset is used. By default, this
807 selects the editable history that is unique to the ancestry of the
818 selects the editable history that is unique to the ancestry of the
808 working directory.
819 working directory.
809
820
810 With --outgoing, this edits changesets not found in the
821 With --outgoing, this edits changesets not found in the
811 destination repository. If URL of the destination is omitted, the
822 destination repository. If URL of the destination is omitted, the
812 'default-push' (or 'default') path will be used.
823 'default-push' (or 'default') path will be used.
813
824
814 For safety, this command is also aborted if there are ambiguous
825 For safety, this command is also aborted if there are ambiguous
815 outgoing revisions which may confuse users: for example, if there
826 outgoing revisions which may confuse users: for example, if there
816 are multiple branches containing outgoing revisions.
827 are multiple branches containing outgoing revisions.
817
828
818 Use "min(outgoing() and ::.)" or similar revset specification
829 Use "min(outgoing() and ::.)" or similar revset specification
819 instead of --outgoing to specify edit target revision exactly in
830 instead of --outgoing to specify edit target revision exactly in
820 such ambiguous situation. See :hg:`help revsets` for detail about
831 such ambiguous situation. See :hg:`help revsets` for detail about
821 selecting revisions.
832 selecting revisions.
822
833
823 .. container:: verbose
834 .. container:: verbose
824
835
825 Examples:
836 Examples:
826
837
827 - A number of changes have been made.
838 - A number of changes have been made.
828 Revision 3 is no longer needed.
839 Revision 3 is no longer needed.
829
840
830 Start history editing from revision 3::
841 Start history editing from revision 3::
831
842
832 hg histedit -r 3
843 hg histedit -r 3
833
844
834 An editor opens, containing the list of revisions,
845 An editor opens, containing the list of revisions,
835 with specific actions specified::
846 with specific actions specified::
836
847
837 pick 5339bf82f0ca 3 Zworgle the foobar
848 pick 5339bf82f0ca 3 Zworgle the foobar
838 pick 8ef592ce7cc4 4 Bedazzle the zerlog
849 pick 8ef592ce7cc4 4 Bedazzle the zerlog
839 pick 0a9639fcda9d 5 Morgify the cromulancy
850 pick 0a9639fcda9d 5 Morgify the cromulancy
840
851
841 Additional information about the possible actions
852 Additional information about the possible actions
842 to take appears below the list of revisions.
853 to take appears below the list of revisions.
843
854
844 To remove revision 3 from the history,
855 To remove revision 3 from the history,
845 its action (at the beginning of the relevant line)
856 its action (at the beginning of the relevant line)
846 is changed to 'drop'::
857 is changed to 'drop'::
847
858
848 drop 5339bf82f0ca 3 Zworgle the foobar
859 drop 5339bf82f0ca 3 Zworgle the foobar
849 pick 8ef592ce7cc4 4 Bedazzle the zerlog
860 pick 8ef592ce7cc4 4 Bedazzle the zerlog
850 pick 0a9639fcda9d 5 Morgify the cromulancy
861 pick 0a9639fcda9d 5 Morgify the cromulancy
851
862
852 - A number of changes have been made.
863 - A number of changes have been made.
853 Revision 2 and 4 need to be swapped.
864 Revision 2 and 4 need to be swapped.
854
865
855 Start history editing from revision 2::
866 Start history editing from revision 2::
856
867
857 hg histedit -r 2
868 hg histedit -r 2
858
869
859 An editor opens, containing the list of revisions,
870 An editor opens, containing the list of revisions,
860 with specific actions specified::
871 with specific actions specified::
861
872
862 pick 252a1af424ad 2 Blorb a morgwazzle
873 pick 252a1af424ad 2 Blorb a morgwazzle
863 pick 5339bf82f0ca 3 Zworgle the foobar
874 pick 5339bf82f0ca 3 Zworgle the foobar
864 pick 8ef592ce7cc4 4 Bedazzle the zerlog
875 pick 8ef592ce7cc4 4 Bedazzle the zerlog
865
876
866 To swap revision 2 and 4, its lines are swapped
877 To swap revision 2 and 4, its lines are swapped
867 in the editor::
878 in the editor::
868
879
869 pick 8ef592ce7cc4 4 Bedazzle the zerlog
880 pick 8ef592ce7cc4 4 Bedazzle the zerlog
870 pick 5339bf82f0ca 3 Zworgle the foobar
881 pick 5339bf82f0ca 3 Zworgle the foobar
871 pick 252a1af424ad 2 Blorb a morgwazzle
882 pick 252a1af424ad 2 Blorb a morgwazzle
872
883
873 Returns 0 on success, 1 if user intervention is required (not only
884 Returns 0 on success, 1 if user intervention is required (not only
874 for intentional "edit" command, but also for resolving unexpected
885 for intentional "edit" command, but also for resolving unexpected
875 conflicts).
886 conflicts).
876 """
887 """
877 state = histeditstate(repo)
888 state = histeditstate(repo)
878 try:
889 try:
879 state.wlock = repo.wlock()
890 state.wlock = repo.wlock()
880 state.lock = repo.lock()
891 state.lock = repo.lock()
881 _histedit(ui, repo, state, *freeargs, **opts)
892 _histedit(ui, repo, state, *freeargs, **opts)
882 except error.Abort:
893 except error.Abort:
883 if repo.vfs.exists('histedit-last-edit.txt'):
894 if repo.vfs.exists('histedit-last-edit.txt'):
884 ui.warn(_('warning: histedit rules saved '
895 ui.warn(_('warning: histedit rules saved '
885 'to: .hg/histedit-last-edit.txt\n'))
896 'to: .hg/histedit-last-edit.txt\n'))
886 raise
897 raise
887 finally:
898 finally:
888 release(state.lock, state.wlock)
899 release(state.lock, state.wlock)
889
900
890 def _histedit(ui, repo, state, *freeargs, **opts):
901 def _histedit(ui, repo, state, *freeargs, **opts):
891 # TODO only abort if we try to histedit mq patches, not just
902 # TODO only abort if we try to histedit mq patches, not just
892 # blanket if mq patches are applied somewhere
903 # blanket if mq patches are applied somewhere
893 mq = getattr(repo, 'mq', None)
904 mq = getattr(repo, 'mq', None)
894 if mq and mq.applied:
905 if mq and mq.applied:
895 raise error.Abort(_('source has mq patches applied'))
906 raise error.Abort(_('source has mq patches applied'))
896
907
897 # basic argument incompatibility processing
908 # basic argument incompatibility processing
898 outg = opts.get('outgoing')
909 outg = opts.get('outgoing')
899 cont = opts.get('continue')
910 cont = opts.get('continue')
900 editplan = opts.get('edit_plan')
911 editplan = opts.get('edit_plan')
901 abort = opts.get('abort')
912 abort = opts.get('abort')
902 force = opts.get('force')
913 force = opts.get('force')
903 rules = opts.get('commands', '')
914 rules = opts.get('commands', '')
904 revs = opts.get('rev', [])
915 revs = opts.get('rev', [])
905 goal = 'new' # This invocation goal, in new, continue, abort
916 goal = 'new' # This invocation goal, in new, continue, abort
906 if force and not outg:
917 if force and not outg:
907 raise error.Abort(_('--force only allowed with --outgoing'))
918 raise error.Abort(_('--force only allowed with --outgoing'))
908 if cont:
919 if cont:
909 if any((outg, abort, revs, freeargs, rules, editplan)):
920 if any((outg, abort, revs, freeargs, rules, editplan)):
910 raise error.Abort(_('no arguments allowed with --continue'))
921 raise error.Abort(_('no arguments allowed with --continue'))
911 goal = 'continue'
922 goal = 'continue'
912 elif abort:
923 elif abort:
913 if any((outg, revs, freeargs, rules, editplan)):
924 if any((outg, revs, freeargs, rules, editplan)):
914 raise error.Abort(_('no arguments allowed with --abort'))
925 raise error.Abort(_('no arguments allowed with --abort'))
915 goal = 'abort'
926 goal = 'abort'
916 elif editplan:
927 elif editplan:
917 if any((outg, revs, freeargs)):
928 if any((outg, revs, freeargs)):
918 raise error.Abort(_('only --commands argument allowed with '
929 raise error.Abort(_('only --commands argument allowed with '
919 '--edit-plan'))
930 '--edit-plan'))
920 goal = 'edit-plan'
931 goal = 'edit-plan'
921 else:
932 else:
922 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
933 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
923 raise error.Abort(_('history edit already in progress, try '
934 raise error.Abort(_('history edit already in progress, try '
924 '--continue or --abort'))
935 '--continue or --abort'))
925 if outg:
936 if outg:
926 if revs:
937 if revs:
927 raise error.Abort(_('no revisions allowed with --outgoing'))
938 raise error.Abort(_('no revisions allowed with --outgoing'))
928 if len(freeargs) > 1:
939 if len(freeargs) > 1:
929 raise error.Abort(
940 raise error.Abort(
930 _('only one repo argument allowed with --outgoing'))
941 _('only one repo argument allowed with --outgoing'))
931 else:
942 else:
932 revs.extend(freeargs)
943 revs.extend(freeargs)
933 if len(revs) == 0:
944 if len(revs) == 0:
934 defaultrev = destutil.desthistedit(ui, repo)
945 defaultrev = destutil.desthistedit(ui, repo)
935 if defaultrev is not None:
946 if defaultrev is not None:
936 revs.append(defaultrev)
947 revs.append(defaultrev)
937
948
938 if len(revs) != 1:
949 if len(revs) != 1:
939 raise error.Abort(
950 raise error.Abort(
940 _('histedit requires exactly one ancestor revision'))
951 _('histedit requires exactly one ancestor revision'))
941
952
942
953
943 replacements = []
954 replacements = []
944 state.keep = opts.get('keep', False)
955 state.keep = opts.get('keep', False)
945 supportsmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
956 supportsmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
946
957
947 # rebuild state
958 # rebuild state
948 if goal == 'continue':
959 if goal == 'continue':
949 state.read()
960 state.read()
950 state = bootstrapcontinue(ui, state, opts)
961 state = bootstrapcontinue(ui, state, opts)
951 elif goal == 'edit-plan':
962 elif goal == 'edit-plan':
952 state.read()
963 state.read()
953 if not rules:
964 if not rules:
954 comment = editcomment % (node.short(state.parentctxnode),
965 comment = editcomment % (node.short(state.parentctxnode),
955 node.short(state.topmost))
966 node.short(state.topmost))
956 rules = ruleeditor(repo, ui, state.actions, comment)
967 rules = ruleeditor(repo, ui, state.actions, comment)
957 else:
968 else:
958 if rules == '-':
969 if rules == '-':
959 f = sys.stdin
970 f = sys.stdin
960 else:
971 else:
961 f = open(rules)
972 f = open(rules)
962 rules = f.read()
973 rules = f.read()
963 f.close()
974 f.close()
964 actions = parserules(rules, state)
975 actions = parserules(rules, state)
965 ctxs = [repo[act.nodetoverify()] \
976 ctxs = [repo[act.nodetoverify()] \
966 for act in state.actions if act.nodetoverify()]
977 for act in state.actions if act.nodetoverify()]
967 verifyactions(actions, state, ctxs)
978 verifyactions(actions, state, ctxs)
968 state.actions = actions
979 state.actions = actions
969 state.write()
980 state.write()
970 return
981 return
971 elif goal == 'abort':
982 elif goal == 'abort':
972 try:
983 try:
973 state.read()
984 state.read()
974 tmpnodes, leafs = newnodestoabort(state)
985 tmpnodes, leafs = newnodestoabort(state)
975 ui.debug('restore wc to old parent %s\n'
986 ui.debug('restore wc to old parent %s\n'
976 % node.short(state.topmost))
987 % node.short(state.topmost))
977
988
978 # Recover our old commits if necessary
989 # Recover our old commits if necessary
979 if not state.topmost in repo and state.backupfile:
990 if not state.topmost in repo and state.backupfile:
980 backupfile = repo.join(state.backupfile)
991 backupfile = repo.join(state.backupfile)
981 f = hg.openpath(ui, backupfile)
992 f = hg.openpath(ui, backupfile)
982 gen = exchange.readbundle(ui, f, backupfile)
993 gen = exchange.readbundle(ui, f, backupfile)
983 tr = repo.transaction('histedit.abort')
994 tr = repo.transaction('histedit.abort')
984 try:
995 try:
985 if not isinstance(gen, bundle2.unbundle20):
996 if not isinstance(gen, bundle2.unbundle20):
986 gen.apply(repo, 'histedit', 'bundle:' + backupfile)
997 gen.apply(repo, 'histedit', 'bundle:' + backupfile)
987 if isinstance(gen, bundle2.unbundle20):
998 if isinstance(gen, bundle2.unbundle20):
988 bundle2.applybundle(repo, gen, tr,
999 bundle2.applybundle(repo, gen, tr,
989 source='histedit',
1000 source='histedit',
990 url='bundle:' + backupfile)
1001 url='bundle:' + backupfile)
991 tr.close()
1002 tr.close()
992 finally:
1003 finally:
993 tr.release()
1004 tr.release()
994
1005
995 os.remove(backupfile)
1006 os.remove(backupfile)
996
1007
997 # check whether we should update away
1008 # check whether we should update away
998 if repo.unfiltered().revs('parents() and (%n or %ln::)',
1009 if repo.unfiltered().revs('parents() and (%n or %ln::)',
999 state.parentctxnode, leafs | tmpnodes):
1010 state.parentctxnode, leafs | tmpnodes):
1000 hg.clean(repo, state.topmost, show_stats=True, quietempty=True)
1011 hg.clean(repo, state.topmost, show_stats=True, quietempty=True)
1001 cleanupnode(ui, repo, 'created', tmpnodes)
1012 cleanupnode(ui, repo, 'created', tmpnodes)
1002 cleanupnode(ui, repo, 'temp', leafs)
1013 cleanupnode(ui, repo, 'temp', leafs)
1003 except Exception:
1014 except Exception:
1004 if state.inprogress():
1015 if state.inprogress():
1005 ui.warn(_('warning: encountered an exception during histedit '
1016 ui.warn(_('warning: encountered an exception during histedit '
1006 '--abort; the repository may not have been completely '
1017 '--abort; the repository may not have been completely '
1007 'cleaned up\n'))
1018 'cleaned up\n'))
1008 raise
1019 raise
1009 finally:
1020 finally:
1010 state.clear()
1021 state.clear()
1011 return
1022 return
1012 else:
1023 else:
1013 cmdutil.checkunfinished(repo)
1024 cmdutil.checkunfinished(repo)
1014 cmdutil.bailifchanged(repo)
1025 cmdutil.bailifchanged(repo)
1015
1026
1016 if repo.vfs.exists('histedit-last-edit.txt'):
1027 if repo.vfs.exists('histedit-last-edit.txt'):
1017 repo.vfs.unlink('histedit-last-edit.txt')
1028 repo.vfs.unlink('histedit-last-edit.txt')
1018 topmost, empty = repo.dirstate.parents()
1029 topmost, empty = repo.dirstate.parents()
1019 if outg:
1030 if outg:
1020 if freeargs:
1031 if freeargs:
1021 remote = freeargs[0]
1032 remote = freeargs[0]
1022 else:
1033 else:
1023 remote = None
1034 remote = None
1024 root = findoutgoing(ui, repo, remote, force, opts)
1035 root = findoutgoing(ui, repo, remote, force, opts)
1025 else:
1036 else:
1026 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
1037 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
1027 if len(rr) != 1:
1038 if len(rr) != 1:
1028 raise error.Abort(_('The specified revisions must have '
1039 raise error.Abort(_('The specified revisions must have '
1029 'exactly one common root'))
1040 'exactly one common root'))
1030 root = rr[0].node()
1041 root = rr[0].node()
1031
1042
1032 revs = between(repo, root, topmost, state.keep)
1043 revs = between(repo, root, topmost, state.keep)
1033 if not revs:
1044 if not revs:
1034 raise error.Abort(_('%s is not an ancestor of working directory') %
1045 raise error.Abort(_('%s is not an ancestor of working directory') %
1035 node.short(root))
1046 node.short(root))
1036
1047
1037 ctxs = [repo[r] for r in revs]
1048 ctxs = [repo[r] for r in revs]
1038 if not rules:
1049 if not rules:
1039 comment = editcomment % (node.short(root), node.short(topmost))
1050 comment = editcomment % (node.short(root), node.short(topmost))
1040 actions = [pick(state, r) for r in revs]
1051 actions = [pick(state, r) for r in revs]
1041 rules = ruleeditor(repo, ui, actions, comment)
1052 rules = ruleeditor(repo, ui, actions, comment)
1042 else:
1053 else:
1043 if rules == '-':
1054 if rules == '-':
1044 f = sys.stdin
1055 f = sys.stdin
1045 else:
1056 else:
1046 f = open(rules)
1057 f = open(rules)
1047 rules = f.read()
1058 rules = f.read()
1048 f.close()
1059 f.close()
1049 actions = parserules(rules, state)
1060 actions = parserules(rules, state)
1050 verifyactions(actions, state, ctxs)
1061 verifyactions(actions, state, ctxs)
1051
1062
1052 parentctxnode = repo[root].parents()[0].node()
1063 parentctxnode = repo[root].parents()[0].node()
1053
1064
1054 state.parentctxnode = parentctxnode
1065 state.parentctxnode = parentctxnode
1055 state.actions = actions
1066 state.actions = actions
1056 state.topmost = topmost
1067 state.topmost = topmost
1057 state.replacements = replacements
1068 state.replacements = replacements
1058
1069
1059 # Create a backup so we can always abort completely.
1070 # Create a backup so we can always abort completely.
1060 backupfile = None
1071 backupfile = None
1061 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1072 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1062 backupfile = repair._bundle(repo, [parentctxnode], [topmost], root,
1073 backupfile = repair._bundle(repo, [parentctxnode], [topmost], root,
1063 'histedit')
1074 'histedit')
1064 state.backupfile = backupfile
1075 state.backupfile = backupfile
1065
1076
1066 # preprocess rules so that we can hide inner folds from the user
1077 # preprocess rules so that we can hide inner folds from the user
1067 # and only show one editor
1078 # and only show one editor
1068 actions = state.actions[:]
1079 actions = state.actions[:]
1069 for idx, (action, nextact) in enumerate(
1080 for idx, (action, nextact) in enumerate(
1070 zip(actions, actions[1:] + [None])):
1081 zip(actions, actions[1:] + [None])):
1071 if action.verb == 'fold' and nextact and nextact.verb == 'fold':
1082 if action.verb == 'fold' and nextact and nextact.verb == 'fold':
1072 state.actions[idx].__class__ = _multifold
1083 state.actions[idx].__class__ = _multifold
1073
1084
1074 while state.actions:
1085 while state.actions:
1075 state.write()
1086 state.write()
1076 actobj = state.actions.pop(0)
1087 actobj = state.actions.pop(0)
1077 ui.debug('histedit: processing %s %s\n' % (actobj.verb,\
1088 ui.debug('histedit: processing %s %s\n' % (actobj.verb,\
1078 actobj.torule()))
1089 actobj.torule()))
1079 parentctx, replacement_ = actobj.run()
1090 parentctx, replacement_ = actobj.run()
1080 state.parentctxnode = parentctx.node()
1091 state.parentctxnode = parentctx.node()
1081 state.replacements.extend(replacement_)
1092 state.replacements.extend(replacement_)
1082 state.write()
1093 state.write()
1083
1094
1084 hg.update(repo, state.parentctxnode, quietempty=True)
1095 hg.update(repo, state.parentctxnode, quietempty=True)
1085
1096
1086 mapping, tmpnodes, created, ntm = processreplacement(state)
1097 mapping, tmpnodes, created, ntm = processreplacement(state)
1087 if mapping:
1098 if mapping:
1088 for prec, succs in mapping.iteritems():
1099 for prec, succs in mapping.iteritems():
1089 if not succs:
1100 if not succs:
1090 ui.debug('histedit: %s is dropped\n' % node.short(prec))
1101 ui.debug('histedit: %s is dropped\n' % node.short(prec))
1091 else:
1102 else:
1092 ui.debug('histedit: %s is replaced by %s\n' % (
1103 ui.debug('histedit: %s is replaced by %s\n' % (
1093 node.short(prec), node.short(succs[0])))
1104 node.short(prec), node.short(succs[0])))
1094 if len(succs) > 1:
1105 if len(succs) > 1:
1095 m = 'histedit: %s'
1106 m = 'histedit: %s'
1096 for n in succs[1:]:
1107 for n in succs[1:]:
1097 ui.debug(m % node.short(n))
1108 ui.debug(m % node.short(n))
1098
1109
1099 if supportsmarkers:
1110 if supportsmarkers:
1100 # Only create markers if the temp nodes weren't already removed.
1111 # Only create markers if the temp nodes weren't already removed.
1101 obsolete.createmarkers(repo, ((repo[t],()) for t in sorted(tmpnodes)
1112 obsolete.createmarkers(repo, ((repo[t],()) for t in sorted(tmpnodes)
1102 if t in repo))
1113 if t in repo))
1103 else:
1114 else:
1104 cleanupnode(ui, repo, 'temp', tmpnodes)
1115 cleanupnode(ui, repo, 'temp', tmpnodes)
1105
1116
1106 if not state.keep:
1117 if not state.keep:
1107 if mapping:
1118 if mapping:
1108 movebookmarks(ui, repo, mapping, state.topmost, ntm)
1119 movebookmarks(ui, repo, mapping, state.topmost, ntm)
1109 # TODO update mq state
1120 # TODO update mq state
1110 if supportsmarkers:
1121 if supportsmarkers:
1111 markers = []
1122 markers = []
1112 # sort by revision number because it sound "right"
1123 # sort by revision number because it sound "right"
1113 for prec in sorted(mapping, key=repo.changelog.rev):
1124 for prec in sorted(mapping, key=repo.changelog.rev):
1114 succs = mapping[prec]
1125 succs = mapping[prec]
1115 markers.append((repo[prec],
1126 markers.append((repo[prec],
1116 tuple(repo[s] for s in succs)))
1127 tuple(repo[s] for s in succs)))
1117 if markers:
1128 if markers:
1118 obsolete.createmarkers(repo, markers)
1129 obsolete.createmarkers(repo, markers)
1119 else:
1130 else:
1120 cleanupnode(ui, repo, 'replaced', mapping)
1131 cleanupnode(ui, repo, 'replaced', mapping)
1121
1132
1122 state.clear()
1133 state.clear()
1123 if os.path.exists(repo.sjoin('undo')):
1134 if os.path.exists(repo.sjoin('undo')):
1124 os.unlink(repo.sjoin('undo'))
1135 os.unlink(repo.sjoin('undo'))
1125
1136
1126 def bootstrapcontinue(ui, state, opts):
1137 def bootstrapcontinue(ui, state, opts):
1127 repo = state.repo
1138 repo = state.repo
1128 if state.actions:
1139 if state.actions:
1129 actobj = state.actions.pop(0)
1140 actobj = state.actions.pop(0)
1130
1141
1131 if _isdirtywc(repo):
1142 if _isdirtywc(repo):
1132 actobj.continuedirty()
1143 actobj.continuedirty()
1133 if _isdirtywc(repo):
1144 if _isdirtywc(repo):
1134 abortdirty()
1145 abortdirty()
1135
1146
1136 parentctx, replacements = actobj.continueclean()
1147 parentctx, replacements = actobj.continueclean()
1137
1148
1138 state.parentctxnode = parentctx.node()
1149 state.parentctxnode = parentctx.node()
1139 state.replacements.extend(replacements)
1150 state.replacements.extend(replacements)
1140
1151
1141 return state
1152 return state
1142
1153
1143 def between(repo, old, new, keep):
1154 def between(repo, old, new, keep):
1144 """select and validate the set of revision to edit
1155 """select and validate the set of revision to edit
1145
1156
1146 When keep is false, the specified set can't have children."""
1157 When keep is false, the specified set can't have children."""
1147 ctxs = list(repo.set('%n::%n', old, new))
1158 ctxs = list(repo.set('%n::%n', old, new))
1148 if ctxs and not keep:
1159 if ctxs and not keep:
1149 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
1160 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
1150 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
1161 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
1151 raise error.Abort(_('cannot edit history that would orphan nodes'))
1162 raise error.Abort(_('cannot edit history that would orphan nodes'))
1152 if repo.revs('(%ld) and merge()', ctxs):
1163 if repo.revs('(%ld) and merge()', ctxs):
1153 raise error.Abort(_('cannot edit history that contains merges'))
1164 raise error.Abort(_('cannot edit history that contains merges'))
1154 root = ctxs[0] # list is already sorted by repo.set
1165 root = ctxs[0] # list is already sorted by repo.set
1155 if not root.mutable():
1166 if not root.mutable():
1156 raise error.Abort(_('cannot edit public changeset: %s') % root,
1167 raise error.Abort(_('cannot edit public changeset: %s') % root,
1157 hint=_('see "hg help phases" for details'))
1168 hint=_('see "hg help phases" for details'))
1158 return [c.node() for c in ctxs]
1169 return [c.node() for c in ctxs]
1159
1170
1160 def ruleeditor(repo, ui, actions, editcomment=""):
1171 def ruleeditor(repo, ui, actions, editcomment=""):
1161 """open an editor to edit rules
1172 """open an editor to edit rules
1162
1173
1163 rules are in the format [ [act, ctx], ...] like in state.rules
1174 rules are in the format [ [act, ctx], ...] like in state.rules
1164 """
1175 """
1165 rules = '\n'.join([act.torule() for act in actions])
1176 rules = '\n'.join([act.torule() for act in actions])
1166 rules += '\n\n'
1177 rules += '\n\n'
1167 rules += editcomment
1178 rules += editcomment
1168 rules = ui.edit(rules, ui.username(), {'prefix': 'histedit'})
1179 rules = ui.edit(rules, ui.username(), {'prefix': 'histedit'})
1169
1180
1170 # Save edit rules in .hg/histedit-last-edit.txt in case
1181 # Save edit rules in .hg/histedit-last-edit.txt in case
1171 # the user needs to ask for help after something
1182 # the user needs to ask for help after something
1172 # surprising happens.
1183 # surprising happens.
1173 f = open(repo.join('histedit-last-edit.txt'), 'w')
1184 f = open(repo.join('histedit-last-edit.txt'), 'w')
1174 f.write(rules)
1185 f.write(rules)
1175 f.close()
1186 f.close()
1176
1187
1177 return rules
1188 return rules
1178
1189
1179 def parserules(rules, state):
1190 def parserules(rules, state):
1180 """Read the histedit rules string and return list of action objects """
1191 """Read the histedit rules string and return list of action objects """
1181 rules = [l for l in (r.strip() for r in rules.splitlines())
1192 rules = [l for l in (r.strip() for r in rules.splitlines())
1182 if l and not l.startswith('#')]
1193 if l and not l.startswith('#')]
1183 actions = []
1194 actions = []
1184 for r in rules:
1195 for r in rules:
1185 if ' ' not in r:
1196 if ' ' not in r:
1186 raise error.Abort(_('malformed line "%s"') % r)
1197 raise error.Abort(_('malformed line "%s"') % r)
1187 verb, rest = r.split(' ', 1)
1198 verb, rest = r.split(' ', 1)
1188
1199
1189 if verb not in actiontable:
1200 if verb not in actiontable:
1190 raise error.Abort(_('unknown action "%s"') % verb)
1201 raise error.Abort(_('unknown action "%s"') % verb)
1191
1202
1192 action = actiontable[verb].fromrule(state, rest)
1203 action = actiontable[verb].fromrule(state, rest)
1193 actions.append(action)
1204 actions.append(action)
1194 return actions
1205 return actions
1195
1206
1196 def verifyactions(actions, state, ctxs):
1207 def verifyactions(actions, state, ctxs):
1197 """Verify that there exists exactly one action per given changeset and
1208 """Verify that there exists exactly one action per given changeset and
1198 other constraints.
1209 other constraints.
1199
1210
1200 Will abort if there are to many or too few rules, a malformed rule,
1211 Will abort if there are to many or too few rules, a malformed rule,
1201 or a rule on a changeset outside of the user-given range.
1212 or a rule on a changeset outside of the user-given range.
1202 """
1213 """
1203 expected = set(c.hex() for c in ctxs)
1214 expected = set(c.hex() for c in ctxs)
1204 seen = set()
1215 seen = set()
1205 for action in actions:
1216 for action in actions:
1206 action.verify()
1217 action.verify()
1207 constraints = action.constraints()
1218 constraints = action.constraints()
1208 for constraint in constraints:
1219 for constraint in constraints:
1209 if constraint not in _constraints.known():
1220 if constraint not in _constraints.known():
1210 raise error.Abort(_('unknown constraint "%s"') % constraint)
1221 raise error.Abort(_('unknown constraint "%s"') % constraint)
1211
1222
1212 nodetoverify = action.nodetoverify()
1223 nodetoverify = action.nodetoverify()
1213 if nodetoverify is not None:
1224 if nodetoverify is not None:
1214 ha = node.hex(nodetoverify)
1225 ha = node.hex(nodetoverify)
1215 if _constraints.noother in constraints and ha not in expected:
1226 if _constraints.noother in constraints and ha not in expected:
1216 raise error.Abort(
1227 raise error.Abort(
1217 _('may not use "%s" with changesets '
1228 _('may not use "%s" with changesets '
1218 'other than the ones listed') % action.verb)
1229 'other than the ones listed') % action.verb)
1219 if _constraints.forceother in constraints and ha in expected:
1230 if _constraints.forceother in constraints and ha in expected:
1220 raise error.Abort(
1231 raise error.Abort(
1221 _('may not use "%s" with changesets '
1232 _('may not use "%s" with changesets '
1222 'within the edited list') % action.verb)
1233 'within the edited list') % action.verb)
1223 if _constraints.noduplicates in constraints and ha in seen:
1234 if _constraints.noduplicates in constraints and ha in seen:
1224 raise error.Abort(_('duplicated command for changeset %s') %
1235 raise error.Abort(_('duplicated command for changeset %s') %
1225 ha[:12])
1236 ha[:12])
1226 seen.add(ha)
1237 seen.add(ha)
1227 missing = sorted(expected - seen) # sort to stabilize output
1238 missing = sorted(expected - seen) # sort to stabilize output
1228 if missing:
1239
1240 if state.repo.ui.configbool('histedit', 'dropmissing'):
1241 drops = [drop(state, node.bin(n)) for n in missing]
1242 # put the in the beginning so they execute immediately and
1243 # don't show in the edit-plan in the future
1244 actions[:0] = drops
1245 elif missing:
1229 raise error.Abort(_('missing rules for changeset %s') %
1246 raise error.Abort(_('missing rules for changeset %s') %
1230 missing[0][:12],
1247 missing[0][:12],
1231 hint=_('use "drop %s" to discard the change') % missing[0][:12])
1248 hint=_('use "drop %s" to discard, see also: '
1249 '"hg help -e histedit.config"') % missing[0][:12])
1232
1250
1233 def newnodestoabort(state):
1251 def newnodestoabort(state):
1234 """process the list of replacements to return
1252 """process the list of replacements to return
1235
1253
1236 1) the list of final node
1254 1) the list of final node
1237 2) the list of temporary node
1255 2) the list of temporary node
1238
1256
1239 This meant to be used on abort as less data are required in this case.
1257 This meant to be used on abort as less data are required in this case.
1240 """
1258 """
1241 replacements = state.replacements
1259 replacements = state.replacements
1242 allsuccs = set()
1260 allsuccs = set()
1243 replaced = set()
1261 replaced = set()
1244 for rep in replacements:
1262 for rep in replacements:
1245 allsuccs.update(rep[1])
1263 allsuccs.update(rep[1])
1246 replaced.add(rep[0])
1264 replaced.add(rep[0])
1247 newnodes = allsuccs - replaced
1265 newnodes = allsuccs - replaced
1248 tmpnodes = allsuccs & replaced
1266 tmpnodes = allsuccs & replaced
1249 return newnodes, tmpnodes
1267 return newnodes, tmpnodes
1250
1268
1251
1269
1252 def processreplacement(state):
1270 def processreplacement(state):
1253 """process the list of replacements to return
1271 """process the list of replacements to return
1254
1272
1255 1) the final mapping between original and created nodes
1273 1) the final mapping between original and created nodes
1256 2) the list of temporary node created by histedit
1274 2) the list of temporary node created by histedit
1257 3) the list of new commit created by histedit"""
1275 3) the list of new commit created by histedit"""
1258 replacements = state.replacements
1276 replacements = state.replacements
1259 allsuccs = set()
1277 allsuccs = set()
1260 replaced = set()
1278 replaced = set()
1261 fullmapping = {}
1279 fullmapping = {}
1262 # initialize basic set
1280 # initialize basic set
1263 # fullmapping records all operations recorded in replacement
1281 # fullmapping records all operations recorded in replacement
1264 for rep in replacements:
1282 for rep in replacements:
1265 allsuccs.update(rep[1])
1283 allsuccs.update(rep[1])
1266 replaced.add(rep[0])
1284 replaced.add(rep[0])
1267 fullmapping.setdefault(rep[0], set()).update(rep[1])
1285 fullmapping.setdefault(rep[0], set()).update(rep[1])
1268 new = allsuccs - replaced
1286 new = allsuccs - replaced
1269 tmpnodes = allsuccs & replaced
1287 tmpnodes = allsuccs & replaced
1270 # Reduce content fullmapping into direct relation between original nodes
1288 # Reduce content fullmapping into direct relation between original nodes
1271 # and final node created during history edition
1289 # and final node created during history edition
1272 # Dropped changeset are replaced by an empty list
1290 # Dropped changeset are replaced by an empty list
1273 toproceed = set(fullmapping)
1291 toproceed = set(fullmapping)
1274 final = {}
1292 final = {}
1275 while toproceed:
1293 while toproceed:
1276 for x in list(toproceed):
1294 for x in list(toproceed):
1277 succs = fullmapping[x]
1295 succs = fullmapping[x]
1278 for s in list(succs):
1296 for s in list(succs):
1279 if s in toproceed:
1297 if s in toproceed:
1280 # non final node with unknown closure
1298 # non final node with unknown closure
1281 # We can't process this now
1299 # We can't process this now
1282 break
1300 break
1283 elif s in final:
1301 elif s in final:
1284 # non final node, replace with closure
1302 # non final node, replace with closure
1285 succs.remove(s)
1303 succs.remove(s)
1286 succs.update(final[s])
1304 succs.update(final[s])
1287 else:
1305 else:
1288 final[x] = succs
1306 final[x] = succs
1289 toproceed.remove(x)
1307 toproceed.remove(x)
1290 # remove tmpnodes from final mapping
1308 # remove tmpnodes from final mapping
1291 for n in tmpnodes:
1309 for n in tmpnodes:
1292 del final[n]
1310 del final[n]
1293 # we expect all changes involved in final to exist in the repo
1311 # we expect all changes involved in final to exist in the repo
1294 # turn `final` into list (topologically sorted)
1312 # turn `final` into list (topologically sorted)
1295 nm = state.repo.changelog.nodemap
1313 nm = state.repo.changelog.nodemap
1296 for prec, succs in final.items():
1314 for prec, succs in final.items():
1297 final[prec] = sorted(succs, key=nm.get)
1315 final[prec] = sorted(succs, key=nm.get)
1298
1316
1299 # computed topmost element (necessary for bookmark)
1317 # computed topmost element (necessary for bookmark)
1300 if new:
1318 if new:
1301 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
1319 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
1302 elif not final:
1320 elif not final:
1303 # Nothing rewritten at all. we won't need `newtopmost`
1321 # Nothing rewritten at all. we won't need `newtopmost`
1304 # It is the same as `oldtopmost` and `processreplacement` know it
1322 # It is the same as `oldtopmost` and `processreplacement` know it
1305 newtopmost = None
1323 newtopmost = None
1306 else:
1324 else:
1307 # every body died. The newtopmost is the parent of the root.
1325 # every body died. The newtopmost is the parent of the root.
1308 r = state.repo.changelog.rev
1326 r = state.repo.changelog.rev
1309 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
1327 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
1310
1328
1311 return final, tmpnodes, new, newtopmost
1329 return final, tmpnodes, new, newtopmost
1312
1330
1313 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
1331 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
1314 """Move bookmark from old to newly created node"""
1332 """Move bookmark from old to newly created node"""
1315 if not mapping:
1333 if not mapping:
1316 # if nothing got rewritten there is not purpose for this function
1334 # if nothing got rewritten there is not purpose for this function
1317 return
1335 return
1318 moves = []
1336 moves = []
1319 for bk, old in sorted(repo._bookmarks.iteritems()):
1337 for bk, old in sorted(repo._bookmarks.iteritems()):
1320 if old == oldtopmost:
1338 if old == oldtopmost:
1321 # special case ensure bookmark stay on tip.
1339 # special case ensure bookmark stay on tip.
1322 #
1340 #
1323 # This is arguably a feature and we may only want that for the
1341 # This is arguably a feature and we may only want that for the
1324 # active bookmark. But the behavior is kept compatible with the old
1342 # active bookmark. But the behavior is kept compatible with the old
1325 # version for now.
1343 # version for now.
1326 moves.append((bk, newtopmost))
1344 moves.append((bk, newtopmost))
1327 continue
1345 continue
1328 base = old
1346 base = old
1329 new = mapping.get(base, None)
1347 new = mapping.get(base, None)
1330 if new is None:
1348 if new is None:
1331 continue
1349 continue
1332 while not new:
1350 while not new:
1333 # base is killed, trying with parent
1351 # base is killed, trying with parent
1334 base = repo[base].p1().node()
1352 base = repo[base].p1().node()
1335 new = mapping.get(base, (base,))
1353 new = mapping.get(base, (base,))
1336 # nothing to move
1354 # nothing to move
1337 moves.append((bk, new[-1]))
1355 moves.append((bk, new[-1]))
1338 if moves:
1356 if moves:
1339 lock = tr = None
1357 lock = tr = None
1340 try:
1358 try:
1341 lock = repo.lock()
1359 lock = repo.lock()
1342 tr = repo.transaction('histedit')
1360 tr = repo.transaction('histedit')
1343 marks = repo._bookmarks
1361 marks = repo._bookmarks
1344 for mark, new in moves:
1362 for mark, new in moves:
1345 old = marks[mark]
1363 old = marks[mark]
1346 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
1364 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
1347 % (mark, node.short(old), node.short(new)))
1365 % (mark, node.short(old), node.short(new)))
1348 marks[mark] = new
1366 marks[mark] = new
1349 marks.recordchange(tr)
1367 marks.recordchange(tr)
1350 tr.close()
1368 tr.close()
1351 finally:
1369 finally:
1352 release(tr, lock)
1370 release(tr, lock)
1353
1371
1354 def cleanupnode(ui, repo, name, nodes):
1372 def cleanupnode(ui, repo, name, nodes):
1355 """strip a group of nodes from the repository
1373 """strip a group of nodes from the repository
1356
1374
1357 The set of node to strip may contains unknown nodes."""
1375 The set of node to strip may contains unknown nodes."""
1358 ui.debug('should strip %s nodes %s\n' %
1376 ui.debug('should strip %s nodes %s\n' %
1359 (name, ', '.join([node.short(n) for n in nodes])))
1377 (name, ', '.join([node.short(n) for n in nodes])))
1360 lock = None
1378 lock = None
1361 try:
1379 try:
1362 lock = repo.lock()
1380 lock = repo.lock()
1363 # do not let filtering get in the way of the cleanse
1381 # do not let filtering get in the way of the cleanse
1364 # we should probably get rid of obsolescence marker created during the
1382 # we should probably get rid of obsolescence marker created during the
1365 # histedit, but we currently do not have such information.
1383 # histedit, but we currently do not have such information.
1366 repo = repo.unfiltered()
1384 repo = repo.unfiltered()
1367 # Find all nodes that need to be stripped
1385 # Find all nodes that need to be stripped
1368 # (we use %lr instead of %ln to silently ignore unknown items)
1386 # (we use %lr instead of %ln to silently ignore unknown items)
1369 nm = repo.changelog.nodemap
1387 nm = repo.changelog.nodemap
1370 nodes = sorted(n for n in nodes if n in nm)
1388 nodes = sorted(n for n in nodes if n in nm)
1371 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
1389 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
1372 for c in roots:
1390 for c in roots:
1373 # We should process node in reverse order to strip tip most first.
1391 # We should process node in reverse order to strip tip most first.
1374 # but this trigger a bug in changegroup hook.
1392 # but this trigger a bug in changegroup hook.
1375 # This would reduce bundle overhead
1393 # This would reduce bundle overhead
1376 repair.strip(ui, repo, c)
1394 repair.strip(ui, repo, c)
1377 finally:
1395 finally:
1378 release(lock)
1396 release(lock)
1379
1397
1380 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
1398 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
1381 if isinstance(nodelist, str):
1399 if isinstance(nodelist, str):
1382 nodelist = [nodelist]
1400 nodelist = [nodelist]
1383 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1401 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1384 state = histeditstate(repo)
1402 state = histeditstate(repo)
1385 state.read()
1403 state.read()
1386 histedit_nodes = set([action.nodetoverify() for action
1404 histedit_nodes = set([action.nodetoverify() for action
1387 in state.actions if action.nodetoverify()])
1405 in state.actions if action.nodetoverify()])
1388 strip_nodes = set([repo[n].node() for n in nodelist])
1406 strip_nodes = set([repo[n].node() for n in nodelist])
1389 common_nodes = histedit_nodes & strip_nodes
1407 common_nodes = histedit_nodes & strip_nodes
1390 if common_nodes:
1408 if common_nodes:
1391 raise error.Abort(_("histedit in progress, can't strip %s")
1409 raise error.Abort(_("histedit in progress, can't strip %s")
1392 % ', '.join(node.short(x) for x in common_nodes))
1410 % ', '.join(node.short(x) for x in common_nodes))
1393 return orig(ui, repo, nodelist, *args, **kwargs)
1411 return orig(ui, repo, nodelist, *args, **kwargs)
1394
1412
1395 extensions.wrapfunction(repair, 'strip', stripwrapper)
1413 extensions.wrapfunction(repair, 'strip', stripwrapper)
1396
1414
1397 def summaryhook(ui, repo):
1415 def summaryhook(ui, repo):
1398 if not os.path.exists(repo.join('histedit-state')):
1416 if not os.path.exists(repo.join('histedit-state')):
1399 return
1417 return
1400 state = histeditstate(repo)
1418 state = histeditstate(repo)
1401 state.read()
1419 state.read()
1402 if state.actions:
1420 if state.actions:
1403 # i18n: column positioning for "hg summary"
1421 # i18n: column positioning for "hg summary"
1404 ui.write(_('hist: %s (histedit --continue)\n') %
1422 ui.write(_('hist: %s (histedit --continue)\n') %
1405 (ui.label(_('%d remaining'), 'histedit.remaining') %
1423 (ui.label(_('%d remaining'), 'histedit.remaining') %
1406 len(state.actions)))
1424 len(state.actions)))
1407
1425
1408 def extsetup(ui):
1426 def extsetup(ui):
1409 cmdutil.summaryhooks.add('histedit', summaryhook)
1427 cmdutil.summaryhooks.add('histedit', summaryhook)
1410 cmdutil.unfinishedstates.append(
1428 cmdutil.unfinishedstates.append(
1411 ['histedit-state', False, True, _('histedit in progress'),
1429 ['histedit-state', False, True, _('histedit in progress'),
1412 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
1430 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
1413 if ui.configbool("experimental", "histeditng"):
1431 if ui.configbool("experimental", "histeditng"):
1414 globals()['base'] = addhisteditaction(['base', 'b'])(base)
1432 globals()['base'] = addhisteditaction(['base', 'b'])(base)
@@ -1,437 +1,437
1 Test argument handling and various data parsing
1 Test argument handling and various data parsing
2 ==================================================
2 ==================================================
3
3
4
4
5 Enable extensions used by this test.
5 Enable extensions used by this test.
6 $ cat >>$HGRCPATH <<EOF
6 $ cat >>$HGRCPATH <<EOF
7 > [extensions]
7 > [extensions]
8 > histedit=
8 > histedit=
9 > EOF
9 > EOF
10
10
11 Repo setup.
11 Repo setup.
12 $ hg init foo
12 $ hg init foo
13 $ cd foo
13 $ cd foo
14 $ echo alpha >> alpha
14 $ echo alpha >> alpha
15 $ hg addr
15 $ hg addr
16 adding alpha
16 adding alpha
17 $ hg ci -m one
17 $ hg ci -m one
18 $ echo alpha >> alpha
18 $ echo alpha >> alpha
19 $ hg ci -m two
19 $ hg ci -m two
20 $ echo alpha >> alpha
20 $ echo alpha >> alpha
21 $ hg ci -m three
21 $ hg ci -m three
22 $ echo alpha >> alpha
22 $ echo alpha >> alpha
23 $ hg ci -m four
23 $ hg ci -m four
24 $ echo alpha >> alpha
24 $ echo alpha >> alpha
25 $ hg ci -m five
25 $ hg ci -m five
26
26
27 $ hg log --style compact --graph
27 $ hg log --style compact --graph
28 @ 4[tip] 08d98a8350f3 1970-01-01 00:00 +0000 test
28 @ 4[tip] 08d98a8350f3 1970-01-01 00:00 +0000 test
29 | five
29 | five
30 |
30 |
31 o 3 c8e68270e35a 1970-01-01 00:00 +0000 test
31 o 3 c8e68270e35a 1970-01-01 00:00 +0000 test
32 | four
32 | four
33 |
33 |
34 o 2 eb57da33312f 1970-01-01 00:00 +0000 test
34 o 2 eb57da33312f 1970-01-01 00:00 +0000 test
35 | three
35 | three
36 |
36 |
37 o 1 579e40513370 1970-01-01 00:00 +0000 test
37 o 1 579e40513370 1970-01-01 00:00 +0000 test
38 | two
38 | two
39 |
39 |
40 o 0 6058cbb6cfd7 1970-01-01 00:00 +0000 test
40 o 0 6058cbb6cfd7 1970-01-01 00:00 +0000 test
41 one
41 one
42
42
43
43
44 histedit --continue/--abort with no existing state
44 histedit --continue/--abort with no existing state
45 --------------------------------------------------
45 --------------------------------------------------
46
46
47 $ hg histedit --continue
47 $ hg histedit --continue
48 abort: no histedit in progress
48 abort: no histedit in progress
49 [255]
49 [255]
50 $ hg histedit --abort
50 $ hg histedit --abort
51 abort: no histedit in progress
51 abort: no histedit in progress
52 [255]
52 [255]
53
53
54 Run a dummy edit to make sure we get tip^^ correctly via revsingle.
54 Run a dummy edit to make sure we get tip^^ correctly via revsingle.
55 --------------------------------------------------------------------
55 --------------------------------------------------------------------
56
56
57 $ HGEDITOR=cat hg histedit "tip^^"
57 $ HGEDITOR=cat hg histedit "tip^^"
58 pick eb57da33312f 2 three
58 pick eb57da33312f 2 three
59 pick c8e68270e35a 3 four
59 pick c8e68270e35a 3 four
60 pick 08d98a8350f3 4 five
60 pick 08d98a8350f3 4 five
61
61
62 # Edit history between eb57da33312f and 08d98a8350f3
62 # Edit history between eb57da33312f and 08d98a8350f3
63 #
63 #
64 # Commits are listed from least to most recent
64 # Commits are listed from least to most recent
65 #
65 #
66 # Commands:
66 # Commands:
67 # p, pick = use commit
67 # p, pick = use commit
68 # e, edit = use commit, but stop for amending
68 # e, edit = use commit, but stop for amending
69 # f, fold = use commit, but combine it with the one above
69 # f, fold = use commit, but combine it with the one above
70 # r, roll = like fold, but discard this commit's description
70 # r, roll = like fold, but discard this commit's description
71 # d, drop = remove commit from history
71 # d, drop = remove commit from history
72 # m, mess = edit commit message without changing commit content
72 # m, mess = edit commit message without changing commit content
73 #
73 #
74
74
75 Run on a revision not ancestors of the current working directory.
75 Run on a revision not ancestors of the current working directory.
76 --------------------------------------------------------------------
76 --------------------------------------------------------------------
77
77
78 $ hg up 2
78 $ hg up 2
79 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
79 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
80 $ hg histedit -r 4
80 $ hg histedit -r 4
81 abort: 08d98a8350f3 is not an ancestor of working directory
81 abort: 08d98a8350f3 is not an ancestor of working directory
82 [255]
82 [255]
83 $ hg up --quiet
83 $ hg up --quiet
84
84
85
85
86 Test that we pick the minimum of a revrange
86 Test that we pick the minimum of a revrange
87 ---------------------------------------
87 ---------------------------------------
88
88
89 $ HGEDITOR=cat hg histedit '2::' --commands - << EOF
89 $ HGEDITOR=cat hg histedit '2::' --commands - << EOF
90 > pick eb57da33312f 2 three
90 > pick eb57da33312f 2 three
91 > pick c8e68270e35a 3 four
91 > pick c8e68270e35a 3 four
92 > pick 08d98a8350f3 4 five
92 > pick 08d98a8350f3 4 five
93 > EOF
93 > EOF
94 $ hg up --quiet
94 $ hg up --quiet
95
95
96 $ HGEDITOR=cat hg histedit 'tip:2' --commands - << EOF
96 $ HGEDITOR=cat hg histedit 'tip:2' --commands - << EOF
97 > pick eb57da33312f 2 three
97 > pick eb57da33312f 2 three
98 > pick c8e68270e35a 3 four
98 > pick c8e68270e35a 3 four
99 > pick 08d98a8350f3 4 five
99 > pick 08d98a8350f3 4 five
100 > EOF
100 > EOF
101 $ hg up --quiet
101 $ hg up --quiet
102
102
103 Test config specified default
103 Test config specified default
104 -----------------------------
104 -----------------------------
105
105
106 $ HGEDITOR=cat hg histedit --config "histedit.defaultrev=only(.) - ::eb57da33312f" --commands - << EOF
106 $ HGEDITOR=cat hg histedit --config "histedit.defaultrev=only(.) - ::eb57da33312f" --commands - << EOF
107 > pick c8e68270e35a 3 four
107 > pick c8e68270e35a 3 four
108 > pick 08d98a8350f3 4 five
108 > pick 08d98a8350f3 4 five
109 > EOF
109 > EOF
110
110
111 Run on a revision not descendants of the initial parent
111 Run on a revision not descendants of the initial parent
112 --------------------------------------------------------------------
112 --------------------------------------------------------------------
113
113
114 Test the message shown for inconsistent histedit state, which may be
114 Test the message shown for inconsistent histedit state, which may be
115 created (and forgotten) by Mercurial earlier than 2.7. This emulates
115 created (and forgotten) by Mercurial earlier than 2.7. This emulates
116 Mercurial earlier than 2.7 by renaming ".hg/histedit-state"
116 Mercurial earlier than 2.7 by renaming ".hg/histedit-state"
117 temporarily.
117 temporarily.
118
118
119 $ hg log -G -T '{rev} {shortest(node)} {desc}\n' -r 2::
119 $ hg log -G -T '{rev} {shortest(node)} {desc}\n' -r 2::
120 @ 4 08d9 five
120 @ 4 08d9 five
121 |
121 |
122 o 3 c8e6 four
122 o 3 c8e6 four
123 |
123 |
124 o 2 eb57 three
124 o 2 eb57 three
125 |
125 |
126 $ HGEDITOR=cat hg histedit -r 4 --commands - << EOF
126 $ HGEDITOR=cat hg histedit -r 4 --commands - << EOF
127 > edit 08d98a8350f3 4 five
127 > edit 08d98a8350f3 4 five
128 > EOF
128 > EOF
129 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
129 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
130 reverting alpha
130 reverting alpha
131 Make changes as needed, you may commit or record as needed now.
131 Make changes as needed, you may commit or record as needed now.
132 When you are finished, run hg histedit --continue to resume.
132 When you are finished, run hg histedit --continue to resume.
133 [1]
133 [1]
134
134
135 $ mv .hg/histedit-state .hg/histedit-state.back
135 $ mv .hg/histedit-state .hg/histedit-state.back
136 $ hg update --quiet --clean 2
136 $ hg update --quiet --clean 2
137 $ echo alpha >> alpha
137 $ echo alpha >> alpha
138 $ mv .hg/histedit-state.back .hg/histedit-state
138 $ mv .hg/histedit-state.back .hg/histedit-state
139
139
140 $ hg histedit --continue
140 $ hg histedit --continue
141 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/08d98a8350f3-02594089-backup.hg (glob)
141 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/08d98a8350f3-02594089-backup.hg (glob)
142 $ hg log -G -T '{rev} {shortest(node)} {desc}\n' -r 2::
142 $ hg log -G -T '{rev} {shortest(node)} {desc}\n' -r 2::
143 @ 4 f5ed five
143 @ 4 f5ed five
144 |
144 |
145 | o 3 c8e6 four
145 | o 3 c8e6 four
146 |/
146 |/
147 o 2 eb57 three
147 o 2 eb57 three
148 |
148 |
149
149
150 $ hg unbundle -q $TESTTMP/foo/.hg/strip-backup/08d98a8350f3-02594089-backup.hg
150 $ hg unbundle -q $TESTTMP/foo/.hg/strip-backup/08d98a8350f3-02594089-backup.hg
151 $ hg strip -q -r f5ed --config extensions.strip=
151 $ hg strip -q -r f5ed --config extensions.strip=
152 $ hg up -q 08d98a8350f3
152 $ hg up -q 08d98a8350f3
153
153
154 Test that missing revisions are detected
154 Test that missing revisions are detected
155 ---------------------------------------
155 ---------------------------------------
156
156
157 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
157 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
158 > pick eb57da33312f 2 three
158 > pick eb57da33312f 2 three
159 > pick 08d98a8350f3 4 five
159 > pick 08d98a8350f3 4 five
160 > EOF
160 > EOF
161 abort: missing rules for changeset c8e68270e35a
161 abort: missing rules for changeset c8e68270e35a
162 (use "drop c8e68270e35a" to discard the change)
162 (use "drop c8e68270e35a" to discard, see also: "hg help -e histedit.config")
163 [255]
163 [255]
164
164
165 Test that extra revisions are detected
165 Test that extra revisions are detected
166 ---------------------------------------
166 ---------------------------------------
167
167
168 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
168 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
169 > pick 6058cbb6cfd7 0 one
169 > pick 6058cbb6cfd7 0 one
170 > pick c8e68270e35a 3 four
170 > pick c8e68270e35a 3 four
171 > pick 08d98a8350f3 4 five
171 > pick 08d98a8350f3 4 five
172 > EOF
172 > EOF
173 abort: may not use "pick" with changesets other than the ones listed
173 abort: may not use "pick" with changesets other than the ones listed
174 [255]
174 [255]
175
175
176 Test malformed line
176 Test malformed line
177 ---------------------------------------
177 ---------------------------------------
178
178
179 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
179 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
180 > pickeb57da33312f2three
180 > pickeb57da33312f2three
181 > pick c8e68270e35a 3 four
181 > pick c8e68270e35a 3 four
182 > pick 08d98a8350f3 4 five
182 > pick 08d98a8350f3 4 five
183 > EOF
183 > EOF
184 abort: malformed line "pickeb57da33312f2three"
184 abort: malformed line "pickeb57da33312f2three"
185 [255]
185 [255]
186
186
187 Test unknown changeset
187 Test unknown changeset
188 ---------------------------------------
188 ---------------------------------------
189
189
190 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
190 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
191 > pick 0123456789ab 2 three
191 > pick 0123456789ab 2 three
192 > pick c8e68270e35a 3 four
192 > pick c8e68270e35a 3 four
193 > pick 08d98a8350f3 4 five
193 > pick 08d98a8350f3 4 five
194 > EOF
194 > EOF
195 abort: unknown changeset 0123456789ab listed
195 abort: unknown changeset 0123456789ab listed
196 [255]
196 [255]
197
197
198 Test unknown command
198 Test unknown command
199 ---------------------------------------
199 ---------------------------------------
200
200
201 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
201 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
202 > coin eb57da33312f 2 three
202 > coin eb57da33312f 2 three
203 > pick c8e68270e35a 3 four
203 > pick c8e68270e35a 3 four
204 > pick 08d98a8350f3 4 five
204 > pick 08d98a8350f3 4 five
205 > EOF
205 > EOF
206 abort: unknown action "coin"
206 abort: unknown action "coin"
207 [255]
207 [255]
208
208
209 Test duplicated changeset
209 Test duplicated changeset
210 ---------------------------------------
210 ---------------------------------------
211
211
212 So one is missing and one appear twice.
212 So one is missing and one appear twice.
213
213
214 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
214 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
215 > pick eb57da33312f 2 three
215 > pick eb57da33312f 2 three
216 > pick eb57da33312f 2 three
216 > pick eb57da33312f 2 three
217 > pick 08d98a8350f3 4 five
217 > pick 08d98a8350f3 4 five
218 > EOF
218 > EOF
219 abort: duplicated command for changeset eb57da33312f
219 abort: duplicated command for changeset eb57da33312f
220 [255]
220 [255]
221
221
222 Test short version of command
222 Test short version of command
223 ---------------------------------------
223 ---------------------------------------
224
224
225 Note: we use varying amounts of white space between command name and changeset
225 Note: we use varying amounts of white space between command name and changeset
226 short hash. This tests issue3893.
226 short hash. This tests issue3893.
227
227
228 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
228 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
229 > pick eb57da33312f 2 three
229 > pick eb57da33312f 2 three
230 > p c8e68270e35a 3 four
230 > p c8e68270e35a 3 four
231 > f 08d98a8350f3 4 five
231 > f 08d98a8350f3 4 five
232 > EOF
232 > EOF
233 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
233 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
234 reverting alpha
234 reverting alpha
235 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
235 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
236 four
236 four
237 ***
237 ***
238 five
238 five
239
239
240
240
241
241
242 HG: Enter commit message. Lines beginning with 'HG:' are removed.
242 HG: Enter commit message. Lines beginning with 'HG:' are removed.
243 HG: Leave message empty to abort commit.
243 HG: Leave message empty to abort commit.
244 HG: --
244 HG: --
245 HG: user: test
245 HG: user: test
246 HG: branch 'default'
246 HG: branch 'default'
247 HG: changed alpha
247 HG: changed alpha
248 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
248 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
249 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/*-backup.hg (glob)
249 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/*-backup.hg (glob)
250 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/*-backup.hg (glob)
250 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/*-backup.hg (glob)
251
251
252 $ hg update -q 2
252 $ hg update -q 2
253 $ echo x > x
253 $ echo x > x
254 $ hg add x
254 $ hg add x
255 $ hg commit -m'x' x
255 $ hg commit -m'x' x
256 created new head
256 created new head
257 $ hg histedit -r 'heads(all())'
257 $ hg histedit -r 'heads(all())'
258 abort: The specified revisions must have exactly one common root
258 abort: The specified revisions must have exactly one common root
259 [255]
259 [255]
260
260
261 Test that trimming description using multi-byte characters
261 Test that trimming description using multi-byte characters
262 --------------------------------------------------------------------
262 --------------------------------------------------------------------
263
263
264 $ python <<EOF
264 $ python <<EOF
265 > fp = open('logfile', 'w')
265 > fp = open('logfile', 'w')
266 > fp.write('12345678901234567890123456789012345678901234567890' +
266 > fp.write('12345678901234567890123456789012345678901234567890' +
267 > '12345') # there are 5 more columns for 80 columns
267 > '12345') # there are 5 more columns for 80 columns
268 >
268 >
269 > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes
269 > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes
270 > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8'))
270 > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8'))
271 >
271 >
272 > fp.close()
272 > fp.close()
273 > EOF
273 > EOF
274 $ echo xx >> x
274 $ echo xx >> x
275 $ hg --encoding utf-8 commit --logfile logfile
275 $ hg --encoding utf-8 commit --logfile logfile
276
276
277 $ HGEDITOR=cat hg --encoding utf-8 histedit tip
277 $ HGEDITOR=cat hg --encoding utf-8 histedit tip
278 pick 3d3ea1f3a10b 5 1234567890123456789012345678901234567890123456789012345\xe3\x81\x82... (esc)
278 pick 3d3ea1f3a10b 5 1234567890123456789012345678901234567890123456789012345\xe3\x81\x82... (esc)
279
279
280 # Edit history between 3d3ea1f3a10b and 3d3ea1f3a10b
280 # Edit history between 3d3ea1f3a10b and 3d3ea1f3a10b
281 #
281 #
282 # Commits are listed from least to most recent
282 # Commits are listed from least to most recent
283 #
283 #
284 # Commands:
284 # Commands:
285 # p, pick = use commit
285 # p, pick = use commit
286 # e, edit = use commit, but stop for amending
286 # e, edit = use commit, but stop for amending
287 # f, fold = use commit, but combine it with the one above
287 # f, fold = use commit, but combine it with the one above
288 # r, roll = like fold, but discard this commit's description
288 # r, roll = like fold, but discard this commit's description
289 # d, drop = remove commit from history
289 # d, drop = remove commit from history
290 # m, mess = edit commit message without changing commit content
290 # m, mess = edit commit message without changing commit content
291 #
291 #
292
292
293 Test --continue with --keep
293 Test --continue with --keep
294
294
295 $ hg strip -q -r . --config extensions.strip=
295 $ hg strip -q -r . --config extensions.strip=
296 $ hg histedit '.^' -q --keep --commands - << EOF
296 $ hg histedit '.^' -q --keep --commands - << EOF
297 > edit eb57da33312f 2 three
297 > edit eb57da33312f 2 three
298 > pick f3cfcca30c44 4 x
298 > pick f3cfcca30c44 4 x
299 > EOF
299 > EOF
300 Make changes as needed, you may commit or record as needed now.
300 Make changes as needed, you may commit or record as needed now.
301 When you are finished, run hg histedit --continue to resume.
301 When you are finished, run hg histedit --continue to resume.
302 [1]
302 [1]
303 $ echo edit >> alpha
303 $ echo edit >> alpha
304 $ hg histedit -q --continue
304 $ hg histedit -q --continue
305 $ hg log -G -T '{rev}:{node|short} {desc}'
305 $ hg log -G -T '{rev}:{node|short} {desc}'
306 @ 6:8fda0c726bf2 x
306 @ 6:8fda0c726bf2 x
307 |
307 |
308 o 5:63379946892c three
308 o 5:63379946892c three
309 |
309 |
310 | o 4:f3cfcca30c44 x
310 | o 4:f3cfcca30c44 x
311 | |
311 | |
312 | | o 3:2a30f3cfee78 four
312 | | o 3:2a30f3cfee78 four
313 | |/ ***
313 | |/ ***
314 | | five
314 | | five
315 | o 2:eb57da33312f three
315 | o 2:eb57da33312f three
316 |/
316 |/
317 o 1:579e40513370 two
317 o 1:579e40513370 two
318 |
318 |
319 o 0:6058cbb6cfd7 one
319 o 0:6058cbb6cfd7 one
320
320
321
321
322 Test that abort fails gracefully on exception
322 Test that abort fails gracefully on exception
323 ----------------------------------------------
323 ----------------------------------------------
324 $ hg histedit . -q --commands - << EOF
324 $ hg histedit . -q --commands - << EOF
325 > edit 8fda0c726bf2 6 x
325 > edit 8fda0c726bf2 6 x
326 > EOF
326 > EOF
327 Make changes as needed, you may commit or record as needed now.
327 Make changes as needed, you may commit or record as needed now.
328 When you are finished, run hg histedit --continue to resume.
328 When you are finished, run hg histedit --continue to resume.
329 [1]
329 [1]
330 Corrupt histedit state file
330 Corrupt histedit state file
331 $ sed 's/8fda0c726bf2/123456789012/' .hg/histedit-state > ../corrupt-histedit
331 $ sed 's/8fda0c726bf2/123456789012/' .hg/histedit-state > ../corrupt-histedit
332 $ mv ../corrupt-histedit .hg/histedit-state
332 $ mv ../corrupt-histedit .hg/histedit-state
333 $ hg histedit --abort
333 $ hg histedit --abort
334 warning: encountered an exception during histedit --abort; the repository may not have been completely cleaned up
334 warning: encountered an exception during histedit --abort; the repository may not have been completely cleaned up
335 abort: .*(No such file or directory:|The system cannot find the file specified).* (re)
335 abort: .*(No such file or directory:|The system cannot find the file specified).* (re)
336 [255]
336 [255]
337 Histedit state has been exited
337 Histedit state has been exited
338 $ hg summary -q
338 $ hg summary -q
339 parent: 5:63379946892c
339 parent: 5:63379946892c
340 commit: 1 added, 1 unknown (new branch head)
340 commit: 1 added, 1 unknown (new branch head)
341 update: 4 new changesets (update)
341 update: 4 new changesets (update)
342
342
343 $ cd ..
343 $ cd ..
344
344
345 Set up default base revision tests
345 Set up default base revision tests
346
346
347 $ hg init defaultbase
347 $ hg init defaultbase
348 $ cd defaultbase
348 $ cd defaultbase
349 $ touch foo
349 $ touch foo
350 $ hg -q commit -A -m root
350 $ hg -q commit -A -m root
351 $ echo 1 > foo
351 $ echo 1 > foo
352 $ hg commit -m 'public 1'
352 $ hg commit -m 'public 1'
353 $ hg phase --force --public -r .
353 $ hg phase --force --public -r .
354 $ echo 2 > foo
354 $ echo 2 > foo
355 $ hg commit -m 'draft after public'
355 $ hg commit -m 'draft after public'
356 $ hg -q up -r 1
356 $ hg -q up -r 1
357 $ echo 3 > foo
357 $ echo 3 > foo
358 $ hg commit -m 'head 1 public'
358 $ hg commit -m 'head 1 public'
359 created new head
359 created new head
360 $ hg phase --force --public -r .
360 $ hg phase --force --public -r .
361 $ echo 4 > foo
361 $ echo 4 > foo
362 $ hg commit -m 'head 1 draft 1'
362 $ hg commit -m 'head 1 draft 1'
363 $ echo 5 > foo
363 $ echo 5 > foo
364 $ hg commit -m 'head 1 draft 2'
364 $ hg commit -m 'head 1 draft 2'
365 $ hg -q up -r 2
365 $ hg -q up -r 2
366 $ echo 6 > foo
366 $ echo 6 > foo
367 $ hg commit -m 'head 2 commit 1'
367 $ hg commit -m 'head 2 commit 1'
368 $ echo 7 > foo
368 $ echo 7 > foo
369 $ hg commit -m 'head 2 commit 2'
369 $ hg commit -m 'head 2 commit 2'
370 $ hg -q up -r 2
370 $ hg -q up -r 2
371 $ echo 8 > foo
371 $ echo 8 > foo
372 $ hg commit -m 'head 3'
372 $ hg commit -m 'head 3'
373 created new head
373 created new head
374 $ hg -q up -r 2
374 $ hg -q up -r 2
375 $ echo 9 > foo
375 $ echo 9 > foo
376 $ hg commit -m 'head 4'
376 $ hg commit -m 'head 4'
377 created new head
377 created new head
378 $ hg merge --tool :local -r 8
378 $ hg merge --tool :local -r 8
379 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
379 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
380 (branch merge, don't forget to commit)
380 (branch merge, don't forget to commit)
381 $ hg commit -m 'merge head 3 into head 4'
381 $ hg commit -m 'merge head 3 into head 4'
382 $ echo 11 > foo
382 $ echo 11 > foo
383 $ hg commit -m 'commit 1 after merge'
383 $ hg commit -m 'commit 1 after merge'
384 $ echo 12 > foo
384 $ echo 12 > foo
385 $ hg commit -m 'commit 2 after merge'
385 $ hg commit -m 'commit 2 after merge'
386
386
387 $ hg log -G -T '{rev}:{node|short} {phase} {desc}\n'
387 $ hg log -G -T '{rev}:{node|short} {phase} {desc}\n'
388 @ 12:8cde254db839 draft commit 2 after merge
388 @ 12:8cde254db839 draft commit 2 after merge
389 |
389 |
390 o 11:6f2f0241f119 draft commit 1 after merge
390 o 11:6f2f0241f119 draft commit 1 after merge
391 |
391 |
392 o 10:90506cc76b00 draft merge head 3 into head 4
392 o 10:90506cc76b00 draft merge head 3 into head 4
393 |\
393 |\
394 | o 9:f8607a373a97 draft head 4
394 | o 9:f8607a373a97 draft head 4
395 | |
395 | |
396 o | 8:0da92be05148 draft head 3
396 o | 8:0da92be05148 draft head 3
397 |/
397 |/
398 | o 7:4c35cdf97d5e draft head 2 commit 2
398 | o 7:4c35cdf97d5e draft head 2 commit 2
399 | |
399 | |
400 | o 6:931820154288 draft head 2 commit 1
400 | o 6:931820154288 draft head 2 commit 1
401 |/
401 |/
402 | o 5:8cdc02b9bc63 draft head 1 draft 2
402 | o 5:8cdc02b9bc63 draft head 1 draft 2
403 | |
403 | |
404 | o 4:463b8c0d2973 draft head 1 draft 1
404 | o 4:463b8c0d2973 draft head 1 draft 1
405 | |
405 | |
406 | o 3:23a0c4eefcbf public head 1 public
406 | o 3:23a0c4eefcbf public head 1 public
407 | |
407 | |
408 o | 2:4117331c3abb draft draft after public
408 o | 2:4117331c3abb draft draft after public
409 |/
409 |/
410 o 1:4426d359ea59 public public 1
410 o 1:4426d359ea59 public public 1
411 |
411 |
412 o 0:54136a8ddf32 public root
412 o 0:54136a8ddf32 public root
413
413
414
414
415 Default base revision should stop at public changesets
415 Default base revision should stop at public changesets
416
416
417 $ hg -q up 8cdc02b9bc63
417 $ hg -q up 8cdc02b9bc63
418 $ hg histedit --commands - <<EOF
418 $ hg histedit --commands - <<EOF
419 > pick 463b8c0d2973
419 > pick 463b8c0d2973
420 > pick 8cdc02b9bc63
420 > pick 8cdc02b9bc63
421 > EOF
421 > EOF
422
422
423 Default base revision should stop at branchpoint
423 Default base revision should stop at branchpoint
424
424
425 $ hg -q up 4c35cdf97d5e
425 $ hg -q up 4c35cdf97d5e
426 $ hg histedit --commands - <<EOF
426 $ hg histedit --commands - <<EOF
427 > pick 931820154288
427 > pick 931820154288
428 > pick 4c35cdf97d5e
428 > pick 4c35cdf97d5e
429 > EOF
429 > EOF
430
430
431 Default base revision should stop at merge commit
431 Default base revision should stop at merge commit
432
432
433 $ hg -q up 8cde254db839
433 $ hg -q up 8cde254db839
434 $ hg histedit --commands - <<EOF
434 $ hg histedit --commands - <<EOF
435 > pick 6f2f0241f119
435 > pick 6f2f0241f119
436 > pick 8cde254db839
436 > pick 8cde254db839
437 > EOF
437 > EOF
@@ -1,151 +1,173
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 > }
17 > }
18
18
19 $ initrepo
19 $ initrepo
20
20
21 log before edit
21 log before edit
22 $ hg log --graph
22 $ hg log --graph
23 @ changeset: 5:652413bf663e
23 @ changeset: 5:652413bf663e
24 | tag: tip
24 | tag: tip
25 | user: test
25 | user: test
26 | date: Thu Jan 01 00:00:00 1970 +0000
26 | date: Thu Jan 01 00:00:00 1970 +0000
27 | summary: f
27 | summary: f
28 |
28 |
29 o changeset: 4:e860deea161a
29 o changeset: 4:e860deea161a
30 | user: test
30 | user: test
31 | date: Thu Jan 01 00:00:00 1970 +0000
31 | date: Thu Jan 01 00:00:00 1970 +0000
32 | summary: e
32 | summary: e
33 |
33 |
34 o changeset: 3:055a42cdd887
34 o changeset: 3:055a42cdd887
35 | user: test
35 | user: test
36 | date: Thu Jan 01 00:00:00 1970 +0000
36 | date: Thu Jan 01 00:00:00 1970 +0000
37 | summary: d
37 | summary: d
38 |
38 |
39 o changeset: 2:177f92b77385
39 o changeset: 2:177f92b77385
40 | user: test
40 | user: test
41 | date: Thu Jan 01 00:00:00 1970 +0000
41 | date: Thu Jan 01 00:00:00 1970 +0000
42 | summary: c
42 | summary: c
43 |
43 |
44 o changeset: 1:d2ae7f538514
44 o changeset: 1:d2ae7f538514
45 | user: test
45 | user: test
46 | date: Thu Jan 01 00:00:00 1970 +0000
46 | date: Thu Jan 01 00:00:00 1970 +0000
47 | summary: b
47 | summary: b
48 |
48 |
49 o changeset: 0:cb9a9f314b8b
49 o changeset: 0:cb9a9f314b8b
50 user: test
50 user: test
51 date: Thu Jan 01 00:00:00 1970 +0000
51 date: Thu Jan 01 00:00:00 1970 +0000
52 summary: a
52 summary: a
53
53
54
54
55 edit the history
55 edit the history
56 $ hg histedit 177f92b77385 --commands - 2>&1 << EOF | fixbundle
56 $ hg histedit 177f92b77385 --commands - 2>&1 << EOF | fixbundle
57 > drop 177f92b77385 c
57 > drop 177f92b77385 c
58 > pick e860deea161a e
58 > pick e860deea161a e
59 > pick 652413bf663e f
59 > pick 652413bf663e f
60 > pick 055a42cdd887 d
60 > pick 055a42cdd887 d
61 > EOF
61 > EOF
62 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
62 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
63
63
64 log after edit
64 log after edit
65 $ hg log --graph
65 $ hg log --graph
66 @ changeset: 4:f518305ce889
66 @ changeset: 4:f518305ce889
67 | tag: tip
67 | tag: tip
68 | user: test
68 | user: test
69 | date: Thu Jan 01 00:00:00 1970 +0000
69 | date: Thu Jan 01 00:00:00 1970 +0000
70 | summary: d
70 | summary: d
71 |
71 |
72 o changeset: 3:a4f7421b80f7
72 o changeset: 3:a4f7421b80f7
73 | user: test
73 | user: test
74 | date: Thu Jan 01 00:00:00 1970 +0000
74 | date: Thu Jan 01 00:00:00 1970 +0000
75 | summary: f
75 | summary: f
76 |
76 |
77 o changeset: 2:ee283cb5f2d5
77 o changeset: 2:ee283cb5f2d5
78 | user: test
78 | user: test
79 | date: Thu Jan 01 00:00:00 1970 +0000
79 | date: Thu Jan 01 00:00:00 1970 +0000
80 | summary: e
80 | summary: e
81 |
81 |
82 o changeset: 1:d2ae7f538514
82 o changeset: 1:d2ae7f538514
83 | user: test
83 | user: test
84 | date: Thu Jan 01 00:00:00 1970 +0000
84 | date: Thu Jan 01 00:00:00 1970 +0000
85 | summary: b
85 | summary: b
86 |
86 |
87 o changeset: 0:cb9a9f314b8b
87 o changeset: 0:cb9a9f314b8b
88 user: test
88 user: test
89 date: Thu Jan 01 00:00:00 1970 +0000
89 date: Thu Jan 01 00:00:00 1970 +0000
90 summary: a
90 summary: a
91
91
92
92
93 Check histedit_source
93 Check histedit_source
94
94
95 $ hg log --debug --rev f518305ce889
95 $ hg log --debug --rev f518305ce889
96 changeset: 4:f518305ce889c07cb5bd05522176d75590ef3324
96 changeset: 4:f518305ce889c07cb5bd05522176d75590ef3324
97 tag: tip
97 tag: tip
98 phase: draft
98 phase: draft
99 parent: 3:a4f7421b80f79fcc59fff01bcbf4a53d127dd6d3
99 parent: 3:a4f7421b80f79fcc59fff01bcbf4a53d127dd6d3
100 parent: -1:0000000000000000000000000000000000000000
100 parent: -1:0000000000000000000000000000000000000000
101 manifest: 4:d3d4f51c157ff242c32ff745d4799aaa26ccda44
101 manifest: 4:d3d4f51c157ff242c32ff745d4799aaa26ccda44
102 user: test
102 user: test
103 date: Thu Jan 01 00:00:00 1970 +0000
103 date: Thu Jan 01 00:00:00 1970 +0000
104 files+: d
104 files+: d
105 extra: branch=default
105 extra: branch=default
106 extra: histedit_source=055a42cdd88768532f9cf79daa407fc8d138de9b
106 extra: histedit_source=055a42cdd88768532f9cf79daa407fc8d138de9b
107 description:
107 description:
108 d
108 d
109
109
110
110
111
111
112 manifest after edit
112 manifest after edit
113 $ hg manifest
113 $ hg manifest
114 a
114 a
115 b
115 b
116 d
116 d
117 e
117 e
118 f
118 f
119
119
120 Drop the last changeset
120 Drop the last changeset
121
121
122 $ hg histedit ee283cb5f2d5 --commands - 2>&1 << EOF | fixbundle
122 $ hg histedit ee283cb5f2d5 --commands - 2>&1 << EOF | fixbundle
123 > pick ee283cb5f2d5 e
123 > pick ee283cb5f2d5 e
124 > pick a4f7421b80f7 f
124 > pick a4f7421b80f7 f
125 > drop f518305ce889 d
125 > drop f518305ce889 d
126 > EOF
126 > EOF
127 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
127 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
128 $ hg log --graph
128 $ hg log --graph
129 @ changeset: 3:a4f7421b80f7
129 @ changeset: 3:a4f7421b80f7
130 | tag: tip
130 | tag: tip
131 | user: test
131 | user: test
132 | date: Thu Jan 01 00:00:00 1970 +0000
132 | date: Thu Jan 01 00:00:00 1970 +0000
133 | summary: f
133 | summary: f
134 |
134 |
135 o changeset: 2:ee283cb5f2d5
135 o changeset: 2:ee283cb5f2d5
136 | user: test
136 | user: test
137 | date: Thu Jan 01 00:00:00 1970 +0000
137 | date: Thu Jan 01 00:00:00 1970 +0000
138 | summary: e
138 | summary: e
139 |
139 |
140 o changeset: 1:d2ae7f538514
140 o changeset: 1:d2ae7f538514
141 | user: test
141 | user: test
142 | date: Thu Jan 01 00:00:00 1970 +0000
142 | date: Thu Jan 01 00:00:00 1970 +0000
143 | summary: b
143 | summary: b
144 |
144 |
145 o changeset: 0:cb9a9f314b8b
145 o changeset: 0:cb9a9f314b8b
146 user: test
146 user: test
147 date: Thu Jan 01 00:00:00 1970 +0000
147 date: Thu Jan 01 00:00:00 1970 +0000
148 summary: a
148 summary: a
149
149
150
150
151 $ cd ..
151 $ hg histedit cb9a9f314b8b --commands - 2>&1 << EOF | fixbundle
152 > pick cb9a9f314b8b a
153 > pick ee283cb5f2d5 e
154 > EOF
155 abort: missing rules for changeset a4f7421b80f7
156 (use "drop a4f7421b80f7" to discard, see also: "hg help -e histedit.config")
157 $ hg --config histedit.dropmissing=True histedit cb9a9f314b8b --commands - 2>&1 << EOF | fixbundle
158 > pick cb9a9f314b8b a
159 > pick ee283cb5f2d5 e
160 > EOF
161 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
162 $ hg log --graph
163 @ changeset: 1:e99c679bf03e
164 | tag: tip
165 | user: test
166 | date: Thu Jan 01 00:00:00 1970 +0000
167 | summary: e
168 |
169 o changeset: 0:cb9a9f314b8b
170 user: test
171 date: Thu Jan 01 00:00:00 1970 +0000
172 summary: a
173
General Comments 0
You need to be logged in to leave comments. Login now