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