##// END OF EJS Templates
histedit: fix style of new error message...
Matt Mackall -
r24196:c3d13202 default
parent child Browse files
Show More
@@ -1,1051 +1,1050 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 message without changing commit content
41 # m, mess = edit 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 message without changing commit content
63 # m, mess = edit 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
146
147 try:
147 try:
148 import cPickle as pickle
148 import cPickle as pickle
149 pickle.dump # import now
149 pickle.dump # import now
150 except ImportError:
150 except ImportError:
151 import pickle
151 import pickle
152 import errno
152 import errno
153 import os
153 import os
154 import sys
154 import sys
155
155
156 from mercurial import cmdutil
156 from mercurial import cmdutil
157 from mercurial import discovery
157 from mercurial import discovery
158 from mercurial import error
158 from mercurial import error
159 from mercurial import copies
159 from mercurial import copies
160 from mercurial import context
160 from mercurial import context
161 from mercurial import extensions
161 from mercurial import extensions
162 from mercurial import hg
162 from mercurial import hg
163 from mercurial import node
163 from mercurial import node
164 from mercurial import repair
164 from mercurial import repair
165 from mercurial import scmutil
165 from mercurial import scmutil
166 from mercurial import util
166 from mercurial import util
167 from mercurial import obsolete
167 from mercurial import obsolete
168 from mercurial import merge as mergemod
168 from mercurial import merge as mergemod
169 from mercurial.lock import release
169 from mercurial.lock import release
170 from mercurial.i18n import _
170 from mercurial.i18n import _
171
171
172 cmdtable = {}
172 cmdtable = {}
173 command = cmdutil.command(cmdtable)
173 command = cmdutil.command(cmdtable)
174
174
175 testedwith = 'internal'
175 testedwith = 'internal'
176
176
177 # i18n: command names and abbreviations must remain untranslated
177 # i18n: command names and abbreviations must remain untranslated
178 editcomment = _("""# Edit history between %s and %s
178 editcomment = _("""# Edit history between %s and %s
179 #
179 #
180 # Commits are listed from least to most recent
180 # Commits are listed from least to most recent
181 #
181 #
182 # Commands:
182 # Commands:
183 # p, pick = use commit
183 # p, pick = use commit
184 # e, edit = use commit, but stop for amending
184 # e, edit = use commit, but stop for amending
185 # f, fold = use commit, but combine it with the one above
185 # f, fold = use commit, but combine it with the one above
186 # r, roll = like fold, but discard this commit's description
186 # r, roll = like fold, but discard this commit's description
187 # d, drop = remove commit from history
187 # d, drop = remove commit from history
188 # m, mess = edit message without changing commit content
188 # m, mess = edit message without changing commit content
189 #
189 #
190 """)
190 """)
191
191
192 class histeditstate(object):
192 class histeditstate(object):
193 def __init__(self, repo, parentctxnode=None, rules=None, keep=None,
193 def __init__(self, repo, parentctxnode=None, rules=None, keep=None,
194 topmost=None, replacements=None, lock=None, wlock=None):
194 topmost=None, replacements=None, lock=None, wlock=None):
195 self.repo = repo
195 self.repo = repo
196 self.rules = rules
196 self.rules = rules
197 self.keep = keep
197 self.keep = keep
198 self.topmost = topmost
198 self.topmost = topmost
199 self.parentctxnode = parentctxnode
199 self.parentctxnode = parentctxnode
200 self.lock = lock
200 self.lock = lock
201 self.wlock = wlock
201 self.wlock = wlock
202 if replacements is None:
202 if replacements is None:
203 self.replacements = []
203 self.replacements = []
204 else:
204 else:
205 self.replacements = replacements
205 self.replacements = replacements
206
206
207 def read(self):
207 def read(self):
208 """Load histedit state from disk and set fields appropriately."""
208 """Load histedit state from disk and set fields appropriately."""
209 try:
209 try:
210 fp = self.repo.vfs('histedit-state', 'r')
210 fp = self.repo.vfs('histedit-state', 'r')
211 except IOError, err:
211 except IOError, err:
212 if err.errno != errno.ENOENT:
212 if err.errno != errno.ENOENT:
213 raise
213 raise
214 raise util.Abort(_('no histedit in progress'))
214 raise util.Abort(_('no histedit in progress'))
215
215
216 parentctxnode, rules, keep, topmost, replacements = pickle.load(fp)
216 parentctxnode, rules, keep, topmost, replacements = pickle.load(fp)
217
217
218 self.parentctxnode = parentctxnode
218 self.parentctxnode = parentctxnode
219 self.rules = rules
219 self.rules = rules
220 self.keep = keep
220 self.keep = keep
221 self.topmost = topmost
221 self.topmost = topmost
222 self.replacements = replacements
222 self.replacements = replacements
223
223
224 def write(self):
224 def write(self):
225 fp = self.repo.vfs('histedit-state', 'w')
225 fp = self.repo.vfs('histedit-state', 'w')
226 pickle.dump((self.parentctxnode, self.rules, self.keep,
226 pickle.dump((self.parentctxnode, self.rules, self.keep,
227 self.topmost, self.replacements), fp)
227 self.topmost, self.replacements), fp)
228 fp.close()
228 fp.close()
229
229
230 def clear(self):
230 def clear(self):
231 self.repo.vfs.unlink('histedit-state')
231 self.repo.vfs.unlink('histedit-state')
232
232
233 def commitfuncfor(repo, src):
233 def commitfuncfor(repo, src):
234 """Build a commit function for the replacement of <src>
234 """Build a commit function for the replacement of <src>
235
235
236 This function ensure we apply the same treatment to all changesets.
236 This function ensure we apply the same treatment to all changesets.
237
237
238 - Add a 'histedit_source' entry in extra.
238 - Add a 'histedit_source' entry in extra.
239
239
240 Note that fold have its own separated logic because its handling is a bit
240 Note that fold have its own separated logic because its handling is a bit
241 different and not easily factored out of the fold method.
241 different and not easily factored out of the fold method.
242 """
242 """
243 phasemin = src.phase()
243 phasemin = src.phase()
244 def commitfunc(**kwargs):
244 def commitfunc(**kwargs):
245 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
245 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
246 try:
246 try:
247 repo.ui.setconfig('phases', 'new-commit', phasemin,
247 repo.ui.setconfig('phases', 'new-commit', phasemin,
248 'histedit')
248 'histedit')
249 extra = kwargs.get('extra', {}).copy()
249 extra = kwargs.get('extra', {}).copy()
250 extra['histedit_source'] = src.hex()
250 extra['histedit_source'] = src.hex()
251 kwargs['extra'] = extra
251 kwargs['extra'] = extra
252 return repo.commit(**kwargs)
252 return repo.commit(**kwargs)
253 finally:
253 finally:
254 repo.ui.restoreconfig(phasebackup)
254 repo.ui.restoreconfig(phasebackup)
255 return commitfunc
255 return commitfunc
256
256
257 def applychanges(ui, repo, ctx, opts):
257 def applychanges(ui, repo, ctx, opts):
258 """Merge changeset from ctx (only) in the current working directory"""
258 """Merge changeset from ctx (only) in the current working directory"""
259 wcpar = repo.dirstate.parents()[0]
259 wcpar = repo.dirstate.parents()[0]
260 if ctx.p1().node() == wcpar:
260 if ctx.p1().node() == wcpar:
261 # edition ar "in place" we do not need to make any merge,
261 # edition ar "in place" we do not need to make any merge,
262 # just applies changes on parent for edition
262 # just applies changes on parent for edition
263 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
263 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
264 stats = None
264 stats = None
265 else:
265 else:
266 try:
266 try:
267 # ui.forcemerge is an internal variable, do not document
267 # ui.forcemerge is an internal variable, do not document
268 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
268 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
269 'histedit')
269 'histedit')
270 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit'])
270 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit'])
271 finally:
271 finally:
272 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit')
272 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit')
273 return stats
273 return stats
274
274
275 def collapse(repo, first, last, commitopts):
275 def collapse(repo, first, last, commitopts):
276 """collapse the set of revisions from first to last as new one.
276 """collapse the set of revisions from first to last as new one.
277
277
278 Expected commit options are:
278 Expected commit options are:
279 - message
279 - message
280 - date
280 - date
281 - username
281 - username
282 Commit message is edited in all cases.
282 Commit message is edited in all cases.
283
283
284 This function works in memory."""
284 This function works in memory."""
285 ctxs = list(repo.set('%d::%d', first, last))
285 ctxs = list(repo.set('%d::%d', first, last))
286 if not ctxs:
286 if not ctxs:
287 return None
287 return None
288 base = first.parents()[0]
288 base = first.parents()[0]
289
289
290 # commit a new version of the old changeset, including the update
290 # commit a new version of the old changeset, including the update
291 # collect all files which might be affected
291 # collect all files which might be affected
292 files = set()
292 files = set()
293 for ctx in ctxs:
293 for ctx in ctxs:
294 files.update(ctx.files())
294 files.update(ctx.files())
295
295
296 # Recompute copies (avoid recording a -> b -> a)
296 # Recompute copies (avoid recording a -> b -> a)
297 copied = copies.pathcopies(base, last)
297 copied = copies.pathcopies(base, last)
298
298
299 # prune files which were reverted by the updates
299 # prune files which were reverted by the updates
300 def samefile(f):
300 def samefile(f):
301 if f in last.manifest():
301 if f in last.manifest():
302 a = last.filectx(f)
302 a = last.filectx(f)
303 if f in base.manifest():
303 if f in base.manifest():
304 b = base.filectx(f)
304 b = base.filectx(f)
305 return (a.data() == b.data()
305 return (a.data() == b.data()
306 and a.flags() == b.flags())
306 and a.flags() == b.flags())
307 else:
307 else:
308 return False
308 return False
309 else:
309 else:
310 return f not in base.manifest()
310 return f not in base.manifest()
311 files = [f for f in files if not samefile(f)]
311 files = [f for f in files if not samefile(f)]
312 # commit version of these files as defined by head
312 # commit version of these files as defined by head
313 headmf = last.manifest()
313 headmf = last.manifest()
314 def filectxfn(repo, ctx, path):
314 def filectxfn(repo, ctx, path):
315 if path in headmf:
315 if path in headmf:
316 fctx = last[path]
316 fctx = last[path]
317 flags = fctx.flags()
317 flags = fctx.flags()
318 mctx = context.memfilectx(repo,
318 mctx = context.memfilectx(repo,
319 fctx.path(), fctx.data(),
319 fctx.path(), fctx.data(),
320 islink='l' in flags,
320 islink='l' in flags,
321 isexec='x' in flags,
321 isexec='x' in flags,
322 copied=copied.get(path))
322 copied=copied.get(path))
323 return mctx
323 return mctx
324 return None
324 return None
325
325
326 if commitopts.get('message'):
326 if commitopts.get('message'):
327 message = commitopts['message']
327 message = commitopts['message']
328 else:
328 else:
329 message = first.description()
329 message = first.description()
330 user = commitopts.get('user')
330 user = commitopts.get('user')
331 date = commitopts.get('date')
331 date = commitopts.get('date')
332 extra = commitopts.get('extra')
332 extra = commitopts.get('extra')
333
333
334 parents = (first.p1().node(), first.p2().node())
334 parents = (first.p1().node(), first.p2().node())
335 editor = None
335 editor = None
336 if not commitopts.get('rollup'):
336 if not commitopts.get('rollup'):
337 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.fold')
337 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.fold')
338 new = context.memctx(repo,
338 new = context.memctx(repo,
339 parents=parents,
339 parents=parents,
340 text=message,
340 text=message,
341 files=files,
341 files=files,
342 filectxfn=filectxfn,
342 filectxfn=filectxfn,
343 user=user,
343 user=user,
344 date=date,
344 date=date,
345 extra=extra,
345 extra=extra,
346 editor=editor)
346 editor=editor)
347 return repo.commitctx(new)
347 return repo.commitctx(new)
348
348
349 def pick(ui, state, ha, opts):
349 def pick(ui, state, ha, opts):
350 repo, ctxnode = state.repo, state.parentctxnode
350 repo, ctxnode = state.repo, state.parentctxnode
351 ctx = repo[ctxnode]
351 ctx = repo[ctxnode]
352 oldctx = repo[ha]
352 oldctx = repo[ha]
353 if oldctx.parents()[0] == ctx:
353 if oldctx.parents()[0] == ctx:
354 ui.debug('node %s unchanged\n' % ha[:12])
354 ui.debug('node %s unchanged\n' % ha[:12])
355 return oldctx, []
355 return oldctx, []
356 hg.update(repo, ctx.node())
356 hg.update(repo, ctx.node())
357 stats = applychanges(ui, repo, oldctx, opts)
357 stats = applychanges(ui, repo, oldctx, opts)
358 if stats and stats[3] > 0:
358 if stats and stats[3] > 0:
359 raise error.InterventionRequired(_('Fix up the change and run '
359 raise error.InterventionRequired(_('Fix up the change and run '
360 'hg histedit --continue'))
360 'hg histedit --continue'))
361 # drop the second merge parent
361 # drop the second merge parent
362 commit = commitfuncfor(repo, oldctx)
362 commit = commitfuncfor(repo, oldctx)
363 n = commit(text=oldctx.description(), user=oldctx.user(),
363 n = commit(text=oldctx.description(), user=oldctx.user(),
364 date=oldctx.date(), extra=oldctx.extra())
364 date=oldctx.date(), extra=oldctx.extra())
365 if n is None:
365 if n is None:
366 ui.warn(_('%s: empty changeset\n') % ha[:12])
366 ui.warn(_('%s: empty changeset\n') % ha[:12])
367 return ctx, []
367 return ctx, []
368 new = repo[n]
368 new = repo[n]
369 return new, [(oldctx.node(), (n,))]
369 return new, [(oldctx.node(), (n,))]
370
370
371
371
372 def edit(ui, state, ha, opts):
372 def edit(ui, state, ha, opts):
373 repo, ctxnode = state.repo, state.parentctxnode
373 repo, ctxnode = state.repo, state.parentctxnode
374 ctx = repo[ctxnode]
374 ctx = repo[ctxnode]
375 oldctx = repo[ha]
375 oldctx = repo[ha]
376 hg.update(repo, ctx.node())
376 hg.update(repo, ctx.node())
377 applychanges(ui, repo, oldctx, opts)
377 applychanges(ui, repo, oldctx, opts)
378 raise error.InterventionRequired(
378 raise error.InterventionRequired(
379 _('Make changes as needed, you may commit or record as needed now.\n'
379 _('Make changes as needed, you may commit or record as needed now.\n'
380 'When you are finished, run hg histedit --continue to resume.'))
380 'When you are finished, run hg histedit --continue to resume.'))
381
381
382 def rollup(ui, state, ha, opts):
382 def rollup(ui, state, ha, opts):
383 rollupopts = opts.copy()
383 rollupopts = opts.copy()
384 rollupopts['rollup'] = True
384 rollupopts['rollup'] = True
385 return fold(ui, state, ha, rollupopts)
385 return fold(ui, state, ha, rollupopts)
386
386
387 def fold(ui, state, ha, opts):
387 def fold(ui, state, ha, opts):
388 repo, ctxnode = state.repo, state.parentctxnode
388 repo, ctxnode = state.repo, state.parentctxnode
389 ctx = repo[ctxnode]
389 ctx = repo[ctxnode]
390 oldctx = repo[ha]
390 oldctx = repo[ha]
391 hg.update(repo, ctx.node())
391 hg.update(repo, ctx.node())
392 stats = applychanges(ui, repo, oldctx, opts)
392 stats = applychanges(ui, repo, oldctx, opts)
393 if stats and stats[3] > 0:
393 if stats and stats[3] > 0:
394 raise error.InterventionRequired(
394 raise error.InterventionRequired(
395 _('Fix up the change and run hg histedit --continue'))
395 _('Fix up the change and run hg histedit --continue'))
396 n = repo.commit(text='fold-temp-revision %s' % ha[:12], user=oldctx.user(),
396 n = repo.commit(text='fold-temp-revision %s' % ha[:12], user=oldctx.user(),
397 date=oldctx.date(), extra=oldctx.extra())
397 date=oldctx.date(), extra=oldctx.extra())
398 if n is None:
398 if n is None:
399 ui.warn(_('%s: empty changeset') % ha[:12])
399 ui.warn(_('%s: empty changeset') % ha[:12])
400 return ctx, []
400 return ctx, []
401 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
401 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
402
402
403 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
403 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
404 parent = ctx.parents()[0].node()
404 parent = ctx.parents()[0].node()
405 hg.update(repo, parent)
405 hg.update(repo, parent)
406 ### prepare new commit data
406 ### prepare new commit data
407 commitopts = opts.copy()
407 commitopts = opts.copy()
408 commitopts['user'] = ctx.user()
408 commitopts['user'] = ctx.user()
409 # commit message
409 # commit message
410 if opts.get('rollup'):
410 if opts.get('rollup'):
411 newmessage = ctx.description()
411 newmessage = ctx.description()
412 else:
412 else:
413 newmessage = '\n***\n'.join(
413 newmessage = '\n***\n'.join(
414 [ctx.description()] +
414 [ctx.description()] +
415 [repo[r].description() for r in internalchanges] +
415 [repo[r].description() for r in internalchanges] +
416 [oldctx.description()]) + '\n'
416 [oldctx.description()]) + '\n'
417 commitopts['message'] = newmessage
417 commitopts['message'] = newmessage
418 # date
418 # date
419 commitopts['date'] = max(ctx.date(), oldctx.date())
419 commitopts['date'] = max(ctx.date(), oldctx.date())
420 extra = ctx.extra().copy()
420 extra = ctx.extra().copy()
421 # histedit_source
421 # histedit_source
422 # note: ctx is likely a temporary commit but that the best we can do here
422 # note: ctx is likely a temporary commit but that the best we can do here
423 # This is sufficient to solve issue3681 anyway
423 # This is sufficient to solve issue3681 anyway
424 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
424 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
425 commitopts['extra'] = extra
425 commitopts['extra'] = extra
426 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
426 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
427 try:
427 try:
428 phasemin = max(ctx.phase(), oldctx.phase())
428 phasemin = max(ctx.phase(), oldctx.phase())
429 repo.ui.setconfig('phases', 'new-commit', phasemin, 'histedit')
429 repo.ui.setconfig('phases', 'new-commit', phasemin, 'histedit')
430 n = collapse(repo, ctx, repo[newnode], commitopts)
430 n = collapse(repo, ctx, repo[newnode], commitopts)
431 finally:
431 finally:
432 repo.ui.restoreconfig(phasebackup)
432 repo.ui.restoreconfig(phasebackup)
433 if n is None:
433 if n is None:
434 return ctx, []
434 return ctx, []
435 hg.update(repo, n)
435 hg.update(repo, n)
436 replacements = [(oldctx.node(), (newnode,)),
436 replacements = [(oldctx.node(), (newnode,)),
437 (ctx.node(), (n,)),
437 (ctx.node(), (n,)),
438 (newnode, (n,)),
438 (newnode, (n,)),
439 ]
439 ]
440 for ich in internalchanges:
440 for ich in internalchanges:
441 replacements.append((ich, (n,)))
441 replacements.append((ich, (n,)))
442 return repo[n], replacements
442 return repo[n], replacements
443
443
444 def drop(ui, state, ha, opts):
444 def drop(ui, state, ha, opts):
445 repo, ctxnode = state.repo, state.parentctxnode
445 repo, ctxnode = state.repo, state.parentctxnode
446 ctx = repo[ctxnode]
446 ctx = repo[ctxnode]
447 return ctx, [(repo[ha].node(), ())]
447 return ctx, [(repo[ha].node(), ())]
448
448
449
449
450 def message(ui, state, ha, opts):
450 def message(ui, state, ha, opts):
451 repo, ctxnode = state.repo, state.parentctxnode
451 repo, ctxnode = state.repo, state.parentctxnode
452 ctx = repo[ctxnode]
452 ctx = repo[ctxnode]
453 oldctx = repo[ha]
453 oldctx = repo[ha]
454 hg.update(repo, ctx.node())
454 hg.update(repo, ctx.node())
455 stats = applychanges(ui, repo, oldctx, opts)
455 stats = applychanges(ui, repo, oldctx, opts)
456 if stats and stats[3] > 0:
456 if stats and stats[3] > 0:
457 raise error.InterventionRequired(
457 raise error.InterventionRequired(
458 _('Fix up the change and run hg histedit --continue'))
458 _('Fix up the change and run hg histedit --continue'))
459 message = oldctx.description()
459 message = oldctx.description()
460 commit = commitfuncfor(repo, oldctx)
460 commit = commitfuncfor(repo, oldctx)
461 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.mess')
461 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.mess')
462 new = commit(text=message, user=oldctx.user(), date=oldctx.date(),
462 new = commit(text=message, user=oldctx.user(), date=oldctx.date(),
463 extra=oldctx.extra(), editor=editor)
463 extra=oldctx.extra(), editor=editor)
464 newctx = repo[new]
464 newctx = repo[new]
465 if oldctx.node() != newctx.node():
465 if oldctx.node() != newctx.node():
466 return newctx, [(oldctx.node(), (new,))]
466 return newctx, [(oldctx.node(), (new,))]
467 # We didn't make an edit, so just indicate no replaced nodes
467 # We didn't make an edit, so just indicate no replaced nodes
468 return newctx, []
468 return newctx, []
469
469
470 def findoutgoing(ui, repo, remote=None, force=False, opts={}):
470 def findoutgoing(ui, repo, remote=None, force=False, opts={}):
471 """utility function to find the first outgoing changeset
471 """utility function to find the first outgoing changeset
472
472
473 Used by initialisation code"""
473 Used by initialisation code"""
474 dest = ui.expandpath(remote or 'default-push', remote or 'default')
474 dest = ui.expandpath(remote or 'default-push', remote or 'default')
475 dest, revs = hg.parseurl(dest, None)[:2]
475 dest, revs = hg.parseurl(dest, None)[:2]
476 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
476 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
477
477
478 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
478 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
479 other = hg.peer(repo, opts, dest)
479 other = hg.peer(repo, opts, dest)
480
480
481 if revs:
481 if revs:
482 revs = [repo.lookup(rev) for rev in revs]
482 revs = [repo.lookup(rev) for rev in revs]
483
483
484 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
484 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
485 if not outgoing.missing:
485 if not outgoing.missing:
486 raise util.Abort(_('no outgoing ancestors'))
486 raise util.Abort(_('no outgoing ancestors'))
487 roots = list(repo.revs("roots(%ln)", outgoing.missing))
487 roots = list(repo.revs("roots(%ln)", outgoing.missing))
488 if 1 < len(roots):
488 if 1 < len(roots):
489 msg = _('there are ambiguous outgoing revisions')
489 msg = _('there are ambiguous outgoing revisions')
490 hint = _('see "hg help histedit" for more detail')
490 hint = _('see "hg help histedit" for more detail')
491 raise util.Abort(msg, hint=hint)
491 raise util.Abort(msg, hint=hint)
492 return repo.lookup(roots[0])
492 return repo.lookup(roots[0])
493
493
494 actiontable = {'p': pick,
494 actiontable = {'p': pick,
495 'pick': pick,
495 'pick': pick,
496 'e': edit,
496 'e': edit,
497 'edit': edit,
497 'edit': edit,
498 'f': fold,
498 'f': fold,
499 'fold': fold,
499 'fold': fold,
500 'r': rollup,
500 'r': rollup,
501 'roll': rollup,
501 'roll': rollup,
502 'd': drop,
502 'd': drop,
503 'drop': drop,
503 'drop': drop,
504 'm': message,
504 'm': message,
505 'mess': message,
505 'mess': message,
506 }
506 }
507
507
508 @command('histedit',
508 @command('histedit',
509 [('', 'commands', '',
509 [('', 'commands', '',
510 _('Read history edits from the specified file.')),
510 _('Read history edits from the specified file.')),
511 ('c', 'continue', False, _('continue an edit already in progress')),
511 ('c', 'continue', False, _('continue an edit already in progress')),
512 ('', 'edit-plan', False, _('edit remaining actions list')),
512 ('', 'edit-plan', False, _('edit remaining actions list')),
513 ('k', 'keep', False,
513 ('k', 'keep', False,
514 _("don't strip old nodes after edit is complete")),
514 _("don't strip old nodes after edit is complete")),
515 ('', 'abort', False, _('abort an edit in progress')),
515 ('', 'abort', False, _('abort an edit in progress')),
516 ('o', 'outgoing', False, _('changesets not found in destination')),
516 ('o', 'outgoing', False, _('changesets not found in destination')),
517 ('f', 'force', False,
517 ('f', 'force', False,
518 _('force outgoing even for unrelated repositories')),
518 _('force outgoing even for unrelated repositories')),
519 ('r', 'rev', [], _('first revision to be edited'))],
519 ('r', 'rev', [], _('first revision to be edited'))],
520 _("ANCESTOR | --outgoing [URL]"))
520 _("ANCESTOR | --outgoing [URL]"))
521 def histedit(ui, repo, *freeargs, **opts):
521 def histedit(ui, repo, *freeargs, **opts):
522 """interactively edit changeset history
522 """interactively edit changeset history
523
523
524 This command edits changesets between ANCESTOR and the parent of
524 This command edits changesets between ANCESTOR and the parent of
525 the working directory.
525 the working directory.
526
526
527 With --outgoing, this edits changesets not found in the
527 With --outgoing, this edits changesets not found in the
528 destination repository. If URL of the destination is omitted, the
528 destination repository. If URL of the destination is omitted, the
529 'default-push' (or 'default') path will be used.
529 'default-push' (or 'default') path will be used.
530
530
531 For safety, this command is aborted, also if there are ambiguous
531 For safety, this command is aborted, also if there are ambiguous
532 outgoing revisions which may confuse users: for example, there are
532 outgoing revisions which may confuse users: for example, there are
533 multiple branches containing outgoing revisions.
533 multiple branches containing outgoing revisions.
534
534
535 Use "min(outgoing() and ::.)" or similar revset specification
535 Use "min(outgoing() and ::.)" or similar revset specification
536 instead of --outgoing to specify edit target revision exactly in
536 instead of --outgoing to specify edit target revision exactly in
537 such ambiguous situation. See :hg:`help revsets` for detail about
537 such ambiguous situation. See :hg:`help revsets` for detail about
538 selecting revisions.
538 selecting revisions.
539
539
540 Returns 0 on success, 1 if user intervention is required (not only
540 Returns 0 on success, 1 if user intervention is required (not only
541 for intentional "edit" command, but also for resolving unexpected
541 for intentional "edit" command, but also for resolving unexpected
542 conflicts).
542 conflicts).
543 """
543 """
544 state = histeditstate(repo)
544 state = histeditstate(repo)
545 try:
545 try:
546 state.wlock = repo.wlock()
546 state.wlock = repo.wlock()
547 state.lock = repo.lock()
547 state.lock = repo.lock()
548 _histedit(ui, repo, state, *freeargs, **opts)
548 _histedit(ui, repo, state, *freeargs, **opts)
549 finally:
549 finally:
550 release(state.lock, state.wlock)
550 release(state.lock, state.wlock)
551
551
552 def _histedit(ui, repo, state, *freeargs, **opts):
552 def _histedit(ui, repo, state, *freeargs, **opts):
553 # TODO only abort if we try and histedit mq patches, not just
553 # TODO only abort if we try and histedit mq patches, not just
554 # blanket if mq patches are applied somewhere
554 # blanket if mq patches are applied somewhere
555 mq = getattr(repo, 'mq', None)
555 mq = getattr(repo, 'mq', None)
556 if mq and mq.applied:
556 if mq and mq.applied:
557 raise util.Abort(_('source has mq patches applied'))
557 raise util.Abort(_('source has mq patches applied'))
558
558
559 # basic argument incompatibility processing
559 # basic argument incompatibility processing
560 outg = opts.get('outgoing')
560 outg = opts.get('outgoing')
561 cont = opts.get('continue')
561 cont = opts.get('continue')
562 editplan = opts.get('edit_plan')
562 editplan = opts.get('edit_plan')
563 abort = opts.get('abort')
563 abort = opts.get('abort')
564 force = opts.get('force')
564 force = opts.get('force')
565 rules = opts.get('commands', '')
565 rules = opts.get('commands', '')
566 revs = opts.get('rev', [])
566 revs = opts.get('rev', [])
567 goal = 'new' # This invocation goal, in new, continue, abort
567 goal = 'new' # This invocation goal, in new, continue, abort
568 if force and not outg:
568 if force and not outg:
569 raise util.Abort(_('--force only allowed with --outgoing'))
569 raise util.Abort(_('--force only allowed with --outgoing'))
570 if cont:
570 if cont:
571 if util.any((outg, abort, revs, freeargs, rules, editplan)):
571 if util.any((outg, abort, revs, freeargs, rules, editplan)):
572 raise util.Abort(_('no arguments allowed with --continue'))
572 raise util.Abort(_('no arguments allowed with --continue'))
573 goal = 'continue'
573 goal = 'continue'
574 elif abort:
574 elif abort:
575 if util.any((outg, revs, freeargs, rules, editplan)):
575 if util.any((outg, revs, freeargs, rules, editplan)):
576 raise util.Abort(_('no arguments allowed with --abort'))
576 raise util.Abort(_('no arguments allowed with --abort'))
577 goal = 'abort'
577 goal = 'abort'
578 elif editplan:
578 elif editplan:
579 if util.any((outg, revs, freeargs)):
579 if util.any((outg, revs, freeargs)):
580 raise util.Abort(_('only --commands argument allowed with'
580 raise util.Abort(_('only --commands argument allowed with'
581 '--edit-plan'))
581 '--edit-plan'))
582 goal = 'edit-plan'
582 goal = 'edit-plan'
583 else:
583 else:
584 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
584 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
585 raise util.Abort(_('history edit already in progress, try '
585 raise util.Abort(_('history edit already in progress, try '
586 '--continue or --abort'))
586 '--continue or --abort'))
587 if outg:
587 if outg:
588 if revs:
588 if revs:
589 raise util.Abort(_('no revisions allowed with --outgoing'))
589 raise util.Abort(_('no revisions allowed with --outgoing'))
590 if len(freeargs) > 1:
590 if len(freeargs) > 1:
591 raise util.Abort(
591 raise util.Abort(
592 _('only one repo argument allowed with --outgoing'))
592 _('only one repo argument allowed with --outgoing'))
593 else:
593 else:
594 revs.extend(freeargs)
594 revs.extend(freeargs)
595 if len(revs) == 0:
595 if len(revs) == 0:
596 histeditdefault = ui.config('histedit', 'defaultrev')
596 histeditdefault = ui.config('histedit', 'defaultrev')
597 if histeditdefault:
597 if histeditdefault:
598 revs.append(histeditdefault)
598 revs.append(histeditdefault)
599 if len(revs) != 1:
599 if len(revs) != 1:
600 raise util.Abort(
600 raise util.Abort(
601 _('histedit requires exactly one ancestor revision'))
601 _('histedit requires exactly one ancestor revision'))
602
602
603
603
604 replacements = []
604 replacements = []
605 keep = opts.get('keep', False)
605 keep = opts.get('keep', False)
606
606
607 # rebuild state
607 # rebuild state
608 if goal == 'continue':
608 if goal == 'continue':
609 state.read()
609 state.read()
610 state = bootstrapcontinue(ui, state, opts)
610 state = bootstrapcontinue(ui, state, opts)
611 elif goal == 'edit-plan':
611 elif goal == 'edit-plan':
612 state = histeditstate(repo)
612 state = histeditstate(repo)
613 state.read()
613 state.read()
614 if not rules:
614 if not rules:
615 comment = editcomment % (state.parentctx, node.short(state.topmost))
615 comment = editcomment % (state.parentctx, node.short(state.topmost))
616 rules = ruleeditor(repo, ui, state.rules, comment)
616 rules = ruleeditor(repo, ui, state.rules, comment)
617 else:
617 else:
618 if rules == '-':
618 if rules == '-':
619 f = sys.stdin
619 f = sys.stdin
620 else:
620 else:
621 f = open(rules)
621 f = open(rules)
622 rules = f.read()
622 rules = f.read()
623 f.close()
623 f.close()
624 rules = [l for l in (r.strip() for r in rules.splitlines())
624 rules = [l for l in (r.strip() for r in rules.splitlines())
625 if l and not l.startswith('#')]
625 if l and not l.startswith('#')]
626 rules = verifyrules(rules, repo, [repo[c] for [_a, c] in state.rules])
626 rules = verifyrules(rules, repo, [repo[c] for [_a, c] in state.rules])
627 state.rules = rules
627 state.rules = rules
628 state.write()
628 state.write()
629 return
629 return
630 elif goal == 'abort':
630 elif goal == 'abort':
631 state.read()
631 state.read()
632 mapping, tmpnodes, leafs, _ntm = processreplacement(state)
632 mapping, tmpnodes, leafs, _ntm = processreplacement(state)
633 ui.debug('restore wc to old parent %s\n' % node.short(state.topmost))
633 ui.debug('restore wc to old parent %s\n' % node.short(state.topmost))
634 # check whether we should update away
634 # check whether we should update away
635 parentnodes = [c.node() for c in repo[None].parents()]
635 parentnodes = [c.node() for c in repo[None].parents()]
636 for n in leafs | set([state.parentctxnode]):
636 for n in leafs | set([state.parentctxnode]):
637 if n in parentnodes:
637 if n in parentnodes:
638 hg.clean(repo, state.topmost)
638 hg.clean(repo, state.topmost)
639 break
639 break
640 else:
640 else:
641 pass
641 pass
642 cleanupnode(ui, repo, 'created', tmpnodes)
642 cleanupnode(ui, repo, 'created', tmpnodes)
643 cleanupnode(ui, repo, 'temp', leafs)
643 cleanupnode(ui, repo, 'temp', leafs)
644 state.clear()
644 state.clear()
645 return
645 return
646 else:
646 else:
647 cmdutil.checkunfinished(repo)
647 cmdutil.checkunfinished(repo)
648 cmdutil.bailifchanged(repo)
648 cmdutil.bailifchanged(repo)
649
649
650 topmost, empty = repo.dirstate.parents()
650 topmost, empty = repo.dirstate.parents()
651 if outg:
651 if outg:
652 if freeargs:
652 if freeargs:
653 remote = freeargs[0]
653 remote = freeargs[0]
654 else:
654 else:
655 remote = None
655 remote = None
656 root = findoutgoing(ui, repo, remote, force, opts)
656 root = findoutgoing(ui, repo, remote, force, opts)
657 else:
657 else:
658 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
658 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
659 if len(rr) != 1:
659 if len(rr) != 1:
660 raise util.Abort(_('The specified revisions must have '
660 raise util.Abort(_('The specified revisions must have '
661 'exactly one common root'))
661 'exactly one common root'))
662 root = rr[0].node()
662 root = rr[0].node()
663
663
664 revs = between(repo, root, topmost, keep)
664 revs = between(repo, root, topmost, keep)
665 if not revs:
665 if not revs:
666 raise util.Abort(_('%s is not an ancestor of working directory') %
666 raise util.Abort(_('%s is not an ancestor of working directory') %
667 node.short(root))
667 node.short(root))
668
668
669 ctxs = [repo[r] for r in revs]
669 ctxs = [repo[r] for r in revs]
670 if not rules:
670 if not rules:
671 comment = editcomment % (node.short(root), node.short(topmost))
671 comment = editcomment % (node.short(root), node.short(topmost))
672 rules = ruleeditor(repo, ui, [['pick', c] for c in ctxs], comment)
672 rules = ruleeditor(repo, ui, [['pick', c] for c in ctxs], comment)
673 else:
673 else:
674 if rules == '-':
674 if rules == '-':
675 f = sys.stdin
675 f = sys.stdin
676 else:
676 else:
677 f = open(rules)
677 f = open(rules)
678 rules = f.read()
678 rules = f.read()
679 f.close()
679 f.close()
680 rules = [l for l in (r.strip() for r in rules.splitlines())
680 rules = [l for l in (r.strip() for r in rules.splitlines())
681 if l and not l.startswith('#')]
681 if l and not l.startswith('#')]
682 rules = verifyrules(rules, repo, ctxs)
682 rules = verifyrules(rules, repo, ctxs)
683
683
684 parentctxnode = repo[root].parents()[0].node()
684 parentctxnode = repo[root].parents()[0].node()
685
685
686 state.parentctxnode = parentctxnode
686 state.parentctxnode = parentctxnode
687 state.rules = rules
687 state.rules = rules
688 state.keep = keep
688 state.keep = keep
689 state.topmost = topmost
689 state.topmost = topmost
690 state.replacements = replacements
690 state.replacements = replacements
691
691
692 while state.rules:
692 while state.rules:
693 state.write()
693 state.write()
694 action, ha = state.rules.pop(0)
694 action, ha = state.rules.pop(0)
695 ui.debug('histedit: processing %s %s\n' % (action, ha[:12]))
695 ui.debug('histedit: processing %s %s\n' % (action, ha[:12]))
696 actfunc = actiontable[action]
696 actfunc = actiontable[action]
697 parentctx, replacement_ = actfunc(ui, state, ha, opts)
697 parentctx, replacement_ = actfunc(ui, state, ha, opts)
698 state.parentctxnode = parentctx.node()
698 state.parentctxnode = parentctx.node()
699 state.replacements.extend(replacement_)
699 state.replacements.extend(replacement_)
700 state.write()
700 state.write()
701
701
702 hg.update(repo, state.parentctxnode)
702 hg.update(repo, state.parentctxnode)
703
703
704 mapping, tmpnodes, created, ntm = processreplacement(state)
704 mapping, tmpnodes, created, ntm = processreplacement(state)
705 if mapping:
705 if mapping:
706 for prec, succs in mapping.iteritems():
706 for prec, succs in mapping.iteritems():
707 if not succs:
707 if not succs:
708 ui.debug('histedit: %s is dropped\n' % node.short(prec))
708 ui.debug('histedit: %s is dropped\n' % node.short(prec))
709 else:
709 else:
710 ui.debug('histedit: %s is replaced by %s\n' % (
710 ui.debug('histedit: %s is replaced by %s\n' % (
711 node.short(prec), node.short(succs[0])))
711 node.short(prec), node.short(succs[0])))
712 if len(succs) > 1:
712 if len(succs) > 1:
713 m = 'histedit: %s'
713 m = 'histedit: %s'
714 for n in succs[1:]:
714 for n in succs[1:]:
715 ui.debug(m % node.short(n))
715 ui.debug(m % node.short(n))
716
716
717 if not keep:
717 if not keep:
718 if mapping:
718 if mapping:
719 movebookmarks(ui, repo, mapping, state.topmost, ntm)
719 movebookmarks(ui, repo, mapping, state.topmost, ntm)
720 # TODO update mq state
720 # TODO update mq state
721 if obsolete.isenabled(repo, obsolete.createmarkersopt):
721 if obsolete.isenabled(repo, obsolete.createmarkersopt):
722 markers = []
722 markers = []
723 # sort by revision number because it sound "right"
723 # sort by revision number because it sound "right"
724 for prec in sorted(mapping, key=repo.changelog.rev):
724 for prec in sorted(mapping, key=repo.changelog.rev):
725 succs = mapping[prec]
725 succs = mapping[prec]
726 markers.append((repo[prec],
726 markers.append((repo[prec],
727 tuple(repo[s] for s in succs)))
727 tuple(repo[s] for s in succs)))
728 if markers:
728 if markers:
729 obsolete.createmarkers(repo, markers)
729 obsolete.createmarkers(repo, markers)
730 else:
730 else:
731 cleanupnode(ui, repo, 'replaced', mapping)
731 cleanupnode(ui, repo, 'replaced', mapping)
732
732
733 cleanupnode(ui, repo, 'temp', tmpnodes)
733 cleanupnode(ui, repo, 'temp', tmpnodes)
734 state.clear()
734 state.clear()
735 if os.path.exists(repo.sjoin('undo')):
735 if os.path.exists(repo.sjoin('undo')):
736 os.unlink(repo.sjoin('undo'))
736 os.unlink(repo.sjoin('undo'))
737
737
738 def gatherchildren(repo, ctx):
738 def gatherchildren(repo, ctx):
739 # is there any new commit between the expected parent and "."
739 # is there any new commit between the expected parent and "."
740 #
740 #
741 # note: does not take non linear new change in account (but previous
741 # note: does not take non linear new change in account (but previous
742 # implementation didn't used them anyway (issue3655)
742 # implementation didn't used them anyway (issue3655)
743 newchildren = [c.node() for c in repo.set('(%d::.)', ctx)]
743 newchildren = [c.node() for c in repo.set('(%d::.)', ctx)]
744 if ctx.node() != node.nullid:
744 if ctx.node() != node.nullid:
745 if not newchildren:
745 if not newchildren:
746 # `ctx` should match but no result. This means that
746 # `ctx` should match but no result. This means that
747 # currentnode is not a descendant from ctx.
747 # currentnode is not a descendant from ctx.
748 msg = _('%s is not an ancestor of working directory')
748 msg = _('%s is not an ancestor of working directory')
749 hint = _('use "histedit --abort" to clear broken state')
749 hint = _('use "histedit --abort" to clear broken state')
750 raise util.Abort(msg % ctx, hint=hint)
750 raise util.Abort(msg % ctx, hint=hint)
751 newchildren.pop(0) # remove ctx
751 newchildren.pop(0) # remove ctx
752 return newchildren
752 return newchildren
753
753
754 def bootstrapcontinue(ui, state, opts):
754 def bootstrapcontinue(ui, state, opts):
755 repo, parentctxnode = state.repo, state.parentctxnode
755 repo, parentctxnode = state.repo, state.parentctxnode
756 parentctx = repo[parentctxnode]
756 parentctx = repo[parentctxnode]
757 action, currentnode = state.rules.pop(0)
757 action, currentnode = state.rules.pop(0)
758 ctx = repo[currentnode]
758 ctx = repo[currentnode]
759
759
760 newchildren = gatherchildren(repo, parentctx)
760 newchildren = gatherchildren(repo, parentctx)
761
761
762 # Commit dirty working directory if necessary
762 # Commit dirty working directory if necessary
763 new = None
763 new = None
764 s = repo.status()
764 s = repo.status()
765 if s.modified or s.added or s.removed or s.deleted:
765 if s.modified or s.added or s.removed or s.deleted:
766 # prepare the message for the commit to comes
766 # prepare the message for the commit to comes
767 if action in ('f', 'fold', 'r', 'roll'):
767 if action in ('f', 'fold', 'r', 'roll'):
768 message = 'fold-temp-revision %s' % currentnode[:12]
768 message = 'fold-temp-revision %s' % currentnode[:12]
769 else:
769 else:
770 message = ctx.description()
770 message = ctx.description()
771 editopt = action in ('e', 'edit', 'm', 'mess')
771 editopt = action in ('e', 'edit', 'm', 'mess')
772 canonaction = {'e': 'edit', 'm': 'mess', 'p': 'pick'}
772 canonaction = {'e': 'edit', 'm': 'mess', 'p': 'pick'}
773 editform = 'histedit.%s' % canonaction.get(action, action)
773 editform = 'histedit.%s' % canonaction.get(action, action)
774 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
774 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
775 commit = commitfuncfor(repo, ctx)
775 commit = commitfuncfor(repo, ctx)
776 new = commit(text=message, user=ctx.user(), date=ctx.date(),
776 new = commit(text=message, user=ctx.user(), date=ctx.date(),
777 extra=ctx.extra(), editor=editor)
777 extra=ctx.extra(), editor=editor)
778 if new is not None:
778 if new is not None:
779 newchildren.append(new)
779 newchildren.append(new)
780
780
781 replacements = []
781 replacements = []
782 # track replacements
782 # track replacements
783 if ctx.node() not in newchildren:
783 if ctx.node() not in newchildren:
784 # note: new children may be empty when the changeset is dropped.
784 # note: new children may be empty when the changeset is dropped.
785 # this happen e.g during conflicting pick where we revert content
785 # this happen e.g during conflicting pick where we revert content
786 # to parent.
786 # to parent.
787 replacements.append((ctx.node(), tuple(newchildren)))
787 replacements.append((ctx.node(), tuple(newchildren)))
788
788
789 if action in ('f', 'fold', 'r', 'roll'):
789 if action in ('f', 'fold', 'r', 'roll'):
790 if newchildren:
790 if newchildren:
791 # finalize fold operation if applicable
791 # finalize fold operation if applicable
792 if new is None:
792 if new is None:
793 new = newchildren[-1]
793 new = newchildren[-1]
794 else:
794 else:
795 newchildren.pop() # remove new from internal changes
795 newchildren.pop() # remove new from internal changes
796 foldopts = opts
796 foldopts = opts
797 if action in ('r', 'roll'):
797 if action in ('r', 'roll'):
798 foldopts = foldopts.copy()
798 foldopts = foldopts.copy()
799 foldopts['rollup'] = True
799 foldopts['rollup'] = True
800 parentctx, repl = finishfold(ui, repo, parentctx, ctx, new,
800 parentctx, repl = finishfold(ui, repo, parentctx, ctx, new,
801 foldopts, newchildren)
801 foldopts, newchildren)
802 replacements.extend(repl)
802 replacements.extend(repl)
803 else:
803 else:
804 # newchildren is empty if the fold did not result in any commit
804 # newchildren is empty if the fold did not result in any commit
805 # this happen when all folded change are discarded during the
805 # this happen when all folded change are discarded during the
806 # merge.
806 # merge.
807 replacements.append((ctx.node(), (parentctx.node(),)))
807 replacements.append((ctx.node(), (parentctx.node(),)))
808 elif newchildren:
808 elif newchildren:
809 # otherwise update "parentctx" before proceeding to further operation
809 # otherwise update "parentctx" before proceeding to further operation
810 parentctx = repo[newchildren[-1]]
810 parentctx = repo[newchildren[-1]]
811
811
812 state.parentctxnode = parentctx.node()
812 state.parentctxnode = parentctx.node()
813 state.replacements.extend(replacements)
813 state.replacements.extend(replacements)
814
814
815 return state
815 return state
816
816
817 def between(repo, old, new, keep):
817 def between(repo, old, new, keep):
818 """select and validate the set of revision to edit
818 """select and validate the set of revision to edit
819
819
820 When keep is false, the specified set can't have children."""
820 When keep is false, the specified set can't have children."""
821 ctxs = list(repo.set('%n::%n', old, new))
821 ctxs = list(repo.set('%n::%n', old, new))
822 if ctxs and not keep:
822 if ctxs and not keep:
823 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
823 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
824 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
824 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
825 raise util.Abort(_('cannot edit history that would orphan nodes'))
825 raise util.Abort(_('cannot edit history that would orphan nodes'))
826 if repo.revs('(%ld) and merge()', ctxs):
826 if repo.revs('(%ld) and merge()', ctxs):
827 raise util.Abort(_('cannot edit history that contains merges'))
827 raise util.Abort(_('cannot edit history that contains merges'))
828 root = ctxs[0] # list is already sorted by repo.set
828 root = ctxs[0] # list is already sorted by repo.set
829 if not root.mutable():
829 if not root.mutable():
830 raise util.Abort(_('cannot edit immutable changeset: %s') % root)
830 raise util.Abort(_('cannot edit immutable changeset: %s') % root)
831 return [c.node() for c in ctxs]
831 return [c.node() for c in ctxs]
832
832
833 def makedesc(repo, action, rev):
833 def makedesc(repo, action, rev):
834 """build a initial action line for a ctx
834 """build a initial action line for a ctx
835
835
836 line are in the form:
836 line are in the form:
837
837
838 <action> <hash> <rev> <summary>
838 <action> <hash> <rev> <summary>
839 """
839 """
840 ctx = repo[rev]
840 ctx = repo[rev]
841 summary = ''
841 summary = ''
842 if ctx.description():
842 if ctx.description():
843 summary = ctx.description().splitlines()[0]
843 summary = ctx.description().splitlines()[0]
844 line = '%s %s %d %s' % (action, ctx, ctx.rev(), summary)
844 line = '%s %s %d %s' % (action, ctx, ctx.rev(), summary)
845 # trim to 80 columns so it's not stupidly wide in my editor
845 # trim to 80 columns so it's not stupidly wide in my editor
846 return util.ellipsis(line, 80)
846 return util.ellipsis(line, 80)
847
847
848 def ruleeditor(repo, ui, rules, editcomment=""):
848 def ruleeditor(repo, ui, rules, editcomment=""):
849 """open an editor to edit rules
849 """open an editor to edit rules
850
850
851 rules are in the format [ [act, ctx], ...] like in state.rules
851 rules are in the format [ [act, ctx], ...] like in state.rules
852 """
852 """
853 rules = '\n'.join([makedesc(repo, act, rev) for [act, rev] in rules])
853 rules = '\n'.join([makedesc(repo, act, rev) for [act, rev] in rules])
854 rules += '\n\n'
854 rules += '\n\n'
855 rules += editcomment
855 rules += editcomment
856 rules = ui.edit(rules, ui.username())
856 rules = ui.edit(rules, ui.username())
857
857
858 # Save edit rules in .hg/histedit-last-edit.txt in case
858 # Save edit rules in .hg/histedit-last-edit.txt in case
859 # the user needs to ask for help after something
859 # the user needs to ask for help after something
860 # surprising happens.
860 # surprising happens.
861 f = open(repo.join('histedit-last-edit.txt'), 'w')
861 f = open(repo.join('histedit-last-edit.txt'), 'w')
862 f.write(rules)
862 f.write(rules)
863 f.close()
863 f.close()
864
864
865 return rules
865 return rules
866
866
867 def verifyrules(rules, repo, ctxs):
867 def verifyrules(rules, repo, ctxs):
868 """Verify that there exists exactly one edit rule per given changeset.
868 """Verify that there exists exactly one edit rule per given changeset.
869
869
870 Will abort if there are to many or too few rules, a malformed rule,
870 Will abort if there are to many or too few rules, a malformed rule,
871 or a rule on a changeset outside of the user-given range.
871 or a rule on a changeset outside of the user-given range.
872 """
872 """
873 parsed = []
873 parsed = []
874 expected = set(c.hex() for c in ctxs)
874 expected = set(c.hex() for c in ctxs)
875 seen = set()
875 seen = set()
876 for r in rules:
876 for r in rules:
877 if ' ' not in r:
877 if ' ' not in r:
878 raise util.Abort(_('malformed line "%s"') % r)
878 raise util.Abort(_('malformed line "%s"') % r)
879 action, rest = r.split(' ', 1)
879 action, rest = r.split(' ', 1)
880 ha = rest.strip().split(' ', 1)[0]
880 ha = rest.strip().split(' ', 1)[0]
881 try:
881 try:
882 ha = repo[ha].hex()
882 ha = repo[ha].hex()
883 except error.RepoError:
883 except error.RepoError:
884 raise util.Abort(_('unknown changeset %s listed') % ha[:12])
884 raise util.Abort(_('unknown changeset %s listed') % ha[:12])
885 if ha not in expected:
885 if ha not in expected:
886 raise util.Abort(
886 raise util.Abort(
887 _('may not use changesets other than the ones listed'))
887 _('may not use changesets other than the ones listed'))
888 if ha in seen:
888 if ha in seen:
889 raise util.Abort(_('duplicated command for changeset %s') %
889 raise util.Abort(_('duplicated command for changeset %s') %
890 ha[:12])
890 ha[:12])
891 seen.add(ha)
891 seen.add(ha)
892 if action not in actiontable:
892 if action not in actiontable:
893 raise util.Abort(_('unknown action "%s"') % action)
893 raise util.Abort(_('unknown action "%s"') % action)
894 parsed.append([action, ha])
894 parsed.append([action, ha])
895 missing = sorted(expected - seen) # sort to stabilize output
895 missing = sorted(expected - seen) # sort to stabilize output
896 if missing:
896 if missing:
897 raise util.Abort(_('missing rules for changeset %s') %
897 raise util.Abort(_('missing rules for changeset %s') %
898 missing[0][:12],
898 missing[0][:12],
899 hint=_('do you want to use the drop action?'))
899 hint=_('do you want to use the drop action?'))
900 return parsed
900 return parsed
901
901
902 def processreplacement(state):
902 def processreplacement(state):
903 """process the list of replacements to return
903 """process the list of replacements to return
904
904
905 1) the final mapping between original and created nodes
905 1) the final mapping between original and created nodes
906 2) the list of temporary node created by histedit
906 2) the list of temporary node created by histedit
907 3) the list of new commit created by histedit"""
907 3) the list of new commit created by histedit"""
908 replacements = state.replacements
908 replacements = state.replacements
909 allsuccs = set()
909 allsuccs = set()
910 replaced = set()
910 replaced = set()
911 fullmapping = {}
911 fullmapping = {}
912 # initialise basic set
912 # initialise basic set
913 # fullmapping record all operation recorded in replacement
913 # fullmapping record all operation recorded in replacement
914 for rep in replacements:
914 for rep in replacements:
915 allsuccs.update(rep[1])
915 allsuccs.update(rep[1])
916 replaced.add(rep[0])
916 replaced.add(rep[0])
917 fullmapping.setdefault(rep[0], set()).update(rep[1])
917 fullmapping.setdefault(rep[0], set()).update(rep[1])
918 new = allsuccs - replaced
918 new = allsuccs - replaced
919 tmpnodes = allsuccs & replaced
919 tmpnodes = allsuccs & replaced
920 # Reduce content fullmapping into direct relation between original nodes
920 # Reduce content fullmapping into direct relation between original nodes
921 # and final node created during history edition
921 # and final node created during history edition
922 # Dropped changeset are replaced by an empty list
922 # Dropped changeset are replaced by an empty list
923 toproceed = set(fullmapping)
923 toproceed = set(fullmapping)
924 final = {}
924 final = {}
925 while toproceed:
925 while toproceed:
926 for x in list(toproceed):
926 for x in list(toproceed):
927 succs = fullmapping[x]
927 succs = fullmapping[x]
928 for s in list(succs):
928 for s in list(succs):
929 if s in toproceed:
929 if s in toproceed:
930 # non final node with unknown closure
930 # non final node with unknown closure
931 # We can't process this now
931 # We can't process this now
932 break
932 break
933 elif s in final:
933 elif s in final:
934 # non final node, replace with closure
934 # non final node, replace with closure
935 succs.remove(s)
935 succs.remove(s)
936 succs.update(final[s])
936 succs.update(final[s])
937 else:
937 else:
938 final[x] = succs
938 final[x] = succs
939 toproceed.remove(x)
939 toproceed.remove(x)
940 # remove tmpnodes from final mapping
940 # remove tmpnodes from final mapping
941 for n in tmpnodes:
941 for n in tmpnodes:
942 del final[n]
942 del final[n]
943 # we expect all changes involved in final to exist in the repo
943 # we expect all changes involved in final to exist in the repo
944 # turn `final` into list (topologically sorted)
944 # turn `final` into list (topologically sorted)
945 nm = state.repo.changelog.nodemap
945 nm = state.repo.changelog.nodemap
946 for prec, succs in final.items():
946 for prec, succs in final.items():
947 final[prec] = sorted(succs, key=nm.get)
947 final[prec] = sorted(succs, key=nm.get)
948
948
949 # computed topmost element (necessary for bookmark)
949 # computed topmost element (necessary for bookmark)
950 if new:
950 if new:
951 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
951 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
952 elif not final:
952 elif not final:
953 # Nothing rewritten at all. we won't need `newtopmost`
953 # Nothing rewritten at all. we won't need `newtopmost`
954 # It is the same as `oldtopmost` and `processreplacement` know it
954 # It is the same as `oldtopmost` and `processreplacement` know it
955 newtopmost = None
955 newtopmost = None
956 else:
956 else:
957 # every body died. The newtopmost is the parent of the root.
957 # every body died. The newtopmost is the parent of the root.
958 r = state.repo.changelog.rev
958 r = state.repo.changelog.rev
959 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
959 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
960
960
961 return final, tmpnodes, new, newtopmost
961 return final, tmpnodes, new, newtopmost
962
962
963 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
963 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
964 """Move bookmark from old to newly created node"""
964 """Move bookmark from old to newly created node"""
965 if not mapping:
965 if not mapping:
966 # if nothing got rewritten there is not purpose for this function
966 # if nothing got rewritten there is not purpose for this function
967 return
967 return
968 moves = []
968 moves = []
969 for bk, old in sorted(repo._bookmarks.iteritems()):
969 for bk, old in sorted(repo._bookmarks.iteritems()):
970 if old == oldtopmost:
970 if old == oldtopmost:
971 # special case ensure bookmark stay on tip.
971 # special case ensure bookmark stay on tip.
972 #
972 #
973 # This is arguably a feature and we may only want that for the
973 # This is arguably a feature and we may only want that for the
974 # active bookmark. But the behavior is kept compatible with the old
974 # active bookmark. But the behavior is kept compatible with the old
975 # version for now.
975 # version for now.
976 moves.append((bk, newtopmost))
976 moves.append((bk, newtopmost))
977 continue
977 continue
978 base = old
978 base = old
979 new = mapping.get(base, None)
979 new = mapping.get(base, None)
980 if new is None:
980 if new is None:
981 continue
981 continue
982 while not new:
982 while not new:
983 # base is killed, trying with parent
983 # base is killed, trying with parent
984 base = repo[base].p1().node()
984 base = repo[base].p1().node()
985 new = mapping.get(base, (base,))
985 new = mapping.get(base, (base,))
986 # nothing to move
986 # nothing to move
987 moves.append((bk, new[-1]))
987 moves.append((bk, new[-1]))
988 if moves:
988 if moves:
989 marks = repo._bookmarks
989 marks = repo._bookmarks
990 for mark, new in moves:
990 for mark, new in moves:
991 old = marks[mark]
991 old = marks[mark]
992 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
992 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
993 % (mark, node.short(old), node.short(new)))
993 % (mark, node.short(old), node.short(new)))
994 marks[mark] = new
994 marks[mark] = new
995 marks.write()
995 marks.write()
996
996
997 def cleanupnode(ui, repo, name, nodes):
997 def cleanupnode(ui, repo, name, nodes):
998 """strip a group of nodes from the repository
998 """strip a group of nodes from the repository
999
999
1000 The set of node to strip may contains unknown nodes."""
1000 The set of node to strip may contains unknown nodes."""
1001 ui.debug('should strip %s nodes %s\n' %
1001 ui.debug('should strip %s nodes %s\n' %
1002 (name, ', '.join([node.short(n) for n in nodes])))
1002 (name, ', '.join([node.short(n) for n in nodes])))
1003 lock = None
1003 lock = None
1004 try:
1004 try:
1005 lock = repo.lock()
1005 lock = repo.lock()
1006 # Find all node that need to be stripped
1006 # Find all node that need to be stripped
1007 # (we hg %lr instead of %ln to silently ignore unknown item
1007 # (we hg %lr instead of %ln to silently ignore unknown item
1008 nm = repo.changelog.nodemap
1008 nm = repo.changelog.nodemap
1009 nodes = sorted(n for n in nodes if n in nm)
1009 nodes = sorted(n for n in nodes if n in nm)
1010 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
1010 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
1011 for c in roots:
1011 for c in roots:
1012 # We should process node in reverse order to strip tip most first.
1012 # We should process node in reverse order to strip tip most first.
1013 # but this trigger a bug in changegroup hook.
1013 # but this trigger a bug in changegroup hook.
1014 # This would reduce bundle overhead
1014 # This would reduce bundle overhead
1015 repair.strip(ui, repo, c)
1015 repair.strip(ui, repo, c)
1016 finally:
1016 finally:
1017 release(lock)
1017 release(lock)
1018
1018
1019 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
1019 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
1020 if isinstance(nodelist, str):
1020 if isinstance(nodelist, str):
1021 nodelist = [nodelist]
1021 nodelist = [nodelist]
1022 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1022 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1023 state = histeditstate(repo)
1023 state = histeditstate(repo)
1024 state.read()
1024 state.read()
1025 histedit_nodes = set([ctx for (action, ctx) in state.rules])
1025 histedit_nodes = set([ctx for (action, ctx) in state.rules])
1026 strip_nodes = set([repo[n].hex() for n in nodelist])
1026 strip_nodes = set([repo[n].hex() for n in nodelist])
1027 common_nodes = histedit_nodes & strip_nodes
1027 common_nodes = histedit_nodes & strip_nodes
1028 if common_nodes:
1028 if common_nodes:
1029 raise util.Abort(_('unable to strip %s. Nodes are '
1029 raise util.Abort(_("histedit in progress, can't strip %s")
1030 'used by history edit in progress.')
1030 % ', '.join(node.short(x) for x in common_nodes))
1031 % ', '.join(common_nodes))
1032 return orig(ui, repo, nodelist, *args, **kwargs)
1031 return orig(ui, repo, nodelist, *args, **kwargs)
1033
1032
1034 extensions.wrapfunction(repair, 'strip', stripwrapper)
1033 extensions.wrapfunction(repair, 'strip', stripwrapper)
1035
1034
1036 def summaryhook(ui, repo):
1035 def summaryhook(ui, repo):
1037 if not os.path.exists(repo.join('histedit-state')):
1036 if not os.path.exists(repo.join('histedit-state')):
1038 return
1037 return
1039 state = histeditstate(repo)
1038 state = histeditstate(repo)
1040 state.read()
1039 state.read()
1041 if state.rules:
1040 if state.rules:
1042 # i18n: column positioning for "hg summary"
1041 # i18n: column positioning for "hg summary"
1043 ui.write(_('hist: %s (histedit --continue)\n') %
1042 ui.write(_('hist: %s (histedit --continue)\n') %
1044 (ui.label(_('%d remaining'), 'histedit.remaining') %
1043 (ui.label(_('%d remaining'), 'histedit.remaining') %
1045 len(state.rules)))
1044 len(state.rules)))
1046
1045
1047 def extsetup(ui):
1046 def extsetup(ui):
1048 cmdutil.summaryhooks.add('histedit', summaryhook)
1047 cmdutil.summaryhooks.add('histedit', summaryhook)
1049 cmdutil.unfinishedstates.append(
1048 cmdutil.unfinishedstates.append(
1050 ['histedit-state', False, True, _('histedit in progress'),
1049 ['histedit-state', False, True, _('histedit in progress'),
1051 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
1050 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
@@ -1,364 +1,364 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 > strip=
6 > strip=
7 > EOF
7 > EOF
8
8
9 $ initrepo ()
9 $ initrepo ()
10 > {
10 > {
11 > hg init r
11 > hg init r
12 > cd r
12 > cd r
13 > for x in a b c d e f g; do
13 > for x in a b c d e f g; do
14 > echo $x > $x
14 > echo $x > $x
15 > hg add $x
15 > hg add $x
16 > hg ci -m $x
16 > hg ci -m $x
17 > done
17 > done
18 > }
18 > }
19
19
20 $ initrepo
20 $ initrepo
21
21
22 log before edit
22 log before edit
23 $ hg log --graph
23 $ hg log --graph
24 @ changeset: 6:3c6a8ed2ebe8
24 @ changeset: 6:3c6a8ed2ebe8
25 | tag: tip
25 | tag: tip
26 | user: test
26 | user: test
27 | date: Thu Jan 01 00:00:00 1970 +0000
27 | date: Thu Jan 01 00:00:00 1970 +0000
28 | summary: g
28 | summary: g
29 |
29 |
30 o changeset: 5:652413bf663e
30 o changeset: 5:652413bf663e
31 | user: test
31 | user: test
32 | date: Thu Jan 01 00:00:00 1970 +0000
32 | date: Thu Jan 01 00:00:00 1970 +0000
33 | summary: f
33 | summary: f
34 |
34 |
35 o changeset: 4:e860deea161a
35 o changeset: 4:e860deea161a
36 | user: test
36 | user: test
37 | date: Thu Jan 01 00:00:00 1970 +0000
37 | date: Thu Jan 01 00:00:00 1970 +0000
38 | summary: e
38 | summary: e
39 |
39 |
40 o changeset: 3:055a42cdd887
40 o changeset: 3:055a42cdd887
41 | user: test
41 | user: test
42 | date: Thu Jan 01 00:00:00 1970 +0000
42 | date: Thu Jan 01 00:00:00 1970 +0000
43 | summary: d
43 | summary: d
44 |
44 |
45 o changeset: 2:177f92b77385
45 o changeset: 2:177f92b77385
46 | user: test
46 | user: test
47 | date: Thu Jan 01 00:00:00 1970 +0000
47 | date: Thu Jan 01 00:00:00 1970 +0000
48 | summary: c
48 | summary: c
49 |
49 |
50 o changeset: 1:d2ae7f538514
50 o changeset: 1:d2ae7f538514
51 | user: test
51 | user: test
52 | date: Thu Jan 01 00:00:00 1970 +0000
52 | date: Thu Jan 01 00:00:00 1970 +0000
53 | summary: b
53 | summary: b
54 |
54 |
55 o changeset: 0:cb9a9f314b8b
55 o changeset: 0:cb9a9f314b8b
56 user: test
56 user: test
57 date: Thu Jan 01 00:00:00 1970 +0000
57 date: Thu Jan 01 00:00:00 1970 +0000
58 summary: a
58 summary: a
59
59
60
60
61 edit the history
61 edit the history
62 $ hg histedit 177f92b77385 --commands - 2>&1 << EOF| fixbundle
62 $ hg histedit 177f92b77385 --commands - 2>&1 << EOF| fixbundle
63 > pick 177f92b77385 c
63 > pick 177f92b77385 c
64 > pick 055a42cdd887 d
64 > pick 055a42cdd887 d
65 > edit e860deea161a e
65 > edit e860deea161a e
66 > pick 652413bf663e f
66 > pick 652413bf663e f
67 > pick 3c6a8ed2ebe8 g
67 > pick 3c6a8ed2ebe8 g
68 > EOF
68 > EOF
69 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
69 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
70 Make changes as needed, you may commit or record as needed now.
70 Make changes as needed, you may commit or record as needed now.
71 When you are finished, run hg histedit --continue to resume.
71 When you are finished, run hg histedit --continue to resume.
72
72
73 edit the plan
73 edit the plan
74 $ hg histedit --edit-plan --commands - 2>&1 << EOF
74 $ hg histedit --edit-plan --commands - 2>&1 << EOF
75 > edit e860deea161a e
75 > edit e860deea161a e
76 > pick 652413bf663e f
76 > pick 652413bf663e f
77 > drop 3c6a8ed2ebe8 g
77 > drop 3c6a8ed2ebe8 g
78 > EOF
78 > EOF
79
79
80 Go at a random point and try to continue
80 Go at a random point and try to continue
81
81
82 $ hg id -n
82 $ hg id -n
83 3+
83 3+
84 $ hg up 0
84 $ hg up 0
85 abort: histedit in progress
85 abort: histedit in progress
86 (use 'hg histedit --continue' or 'hg histedit --abort')
86 (use 'hg histedit --continue' or 'hg histedit --abort')
87 [255]
87 [255]
88
88
89 Try to delete necessary commit
89 Try to delete necessary commit
90 $ hg strip -r 652413bf663e
90 $ hg strip -r 652413bf663e
91 abort: unable to strip 652413bf663ef2a641cab26574e46d5f5a64a55a. Nodes are used by history edit in progress.
91 abort: histedit in progress, can't strip 363532343133
92 [255]
92 [255]
93
93
94 commit, then edit the revision
94 commit, then edit the revision
95 $ hg ci -m 'wat'
95 $ hg ci -m 'wat'
96 created new head
96 created new head
97 $ echo a > e
97 $ echo a > e
98 $ HGEDITOR='echo foobaz > ' hg histedit --continue 2>&1 | fixbundle
98 $ HGEDITOR='echo foobaz > ' hg histedit --continue 2>&1 | fixbundle
99 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
99 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
100 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
100 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
101
101
102 $ hg log --graph
102 $ hg log --graph
103 @ changeset: 6:b5f70786f9b0
103 @ changeset: 6:b5f70786f9b0
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: f
107 | summary: f
108 |
108 |
109 o changeset: 5:a5e1ba2f7afb
109 o changeset: 5:a5e1ba2f7afb
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: foobaz
112 | summary: foobaz
113 |
113 |
114 o changeset: 4:1a60820cd1f6
114 o changeset: 4:1a60820cd1f6
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: wat
117 | summary: wat
118 |
118 |
119 o changeset: 3:055a42cdd887
119 o changeset: 3:055a42cdd887
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: 2:177f92b77385
124 o changeset: 2:177f92b77385
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: 1:d2ae7f538514
129 o changeset: 1:d2ae7f538514
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: 0:cb9a9f314b8b
134 o changeset: 0:cb9a9f314b8b
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
139
140 $ hg cat e
140 $ hg cat e
141 a
141 a
142
142
143 check histedit_source
143 check histedit_source
144
144
145 $ hg log --debug --rev 5
145 $ hg log --debug --rev 5
146 changeset: 5:a5e1ba2f7afb899ef1581cea528fd885d2fca70d
146 changeset: 5:a5e1ba2f7afb899ef1581cea528fd885d2fca70d
147 phase: draft
147 phase: draft
148 parent: 4:1a60820cd1f6004a362aa622ebc47d59bc48eb34
148 parent: 4:1a60820cd1f6004a362aa622ebc47d59bc48eb34
149 parent: -1:0000000000000000000000000000000000000000
149 parent: -1:0000000000000000000000000000000000000000
150 manifest: 5:5ad3be8791f39117565557781f5464363b918a45
150 manifest: 5:5ad3be8791f39117565557781f5464363b918a45
151 user: test
151 user: test
152 date: Thu Jan 01 00:00:00 1970 +0000
152 date: Thu Jan 01 00:00:00 1970 +0000
153 files: e
153 files: e
154 extra: branch=default
154 extra: branch=default
155 extra: histedit_source=e860deea161a2f77de56603b340ebbb4536308ae
155 extra: histedit_source=e860deea161a2f77de56603b340ebbb4536308ae
156 description:
156 description:
157 foobaz
157 foobaz
158
158
159
159
160
160
161 $ hg histedit tip --commands - 2>&1 <<EOF| fixbundle
161 $ hg histedit tip --commands - 2>&1 <<EOF| fixbundle
162 > edit b5f70786f9b0 f
162 > edit b5f70786f9b0 f
163 > EOF
163 > EOF
164 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
164 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
165 Make changes as needed, you may commit or record as needed now.
165 Make changes as needed, you may commit or record as needed now.
166 When you are finished, run hg histedit --continue to resume.
166 When you are finished, run hg histedit --continue to resume.
167 $ hg status
167 $ hg status
168 A f
168 A f
169
169
170 $ hg summary
170 $ hg summary
171 parent: 5:a5e1ba2f7afb
171 parent: 5:a5e1ba2f7afb
172 foobaz
172 foobaz
173 branch: default
173 branch: default
174 commit: 1 added (new branch head)
174 commit: 1 added (new branch head)
175 update: 1 new changesets (update)
175 update: 1 new changesets (update)
176 hist: 1 remaining (histedit --continue)
176 hist: 1 remaining (histedit --continue)
177
177
178 (test also that editor is invoked if histedit is continued for
178 (test also that editor is invoked if histedit is continued for
179 "edit" action)
179 "edit" action)
180
180
181 $ HGEDITOR='cat' hg histedit --continue
181 $ HGEDITOR='cat' hg histedit --continue
182 f
182 f
183
183
184
184
185 HG: Enter commit message. Lines beginning with 'HG:' are removed.
185 HG: Enter commit message. Lines beginning with 'HG:' are removed.
186 HG: Leave message empty to abort commit.
186 HG: Leave message empty to abort commit.
187 HG: --
187 HG: --
188 HG: user: test
188 HG: user: test
189 HG: branch 'default'
189 HG: branch 'default'
190 HG: added f
190 HG: added f
191 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
191 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
192 saved backup bundle to $TESTTMP/r/.hg/strip-backup/b5f70786f9b0-c28d9c86-backup.hg (glob)
192 saved backup bundle to $TESTTMP/r/.hg/strip-backup/b5f70786f9b0-c28d9c86-backup.hg (glob)
193
193
194 $ hg status
194 $ hg status
195
195
196 log after edit
196 log after edit
197 $ hg log --limit 1
197 $ hg log --limit 1
198 changeset: 6:a107ee126658
198 changeset: 6:a107ee126658
199 tag: tip
199 tag: tip
200 user: test
200 user: test
201 date: Thu Jan 01 00:00:00 1970 +0000
201 date: Thu Jan 01 00:00:00 1970 +0000
202 summary: f
202 summary: f
203
203
204
204
205 say we'll change the message, but don't.
205 say we'll change the message, but don't.
206 $ cat > ../edit.sh <<EOF
206 $ cat > ../edit.sh <<EOF
207 > cat "\$1" | sed s/pick/mess/ > tmp
207 > cat "\$1" | sed s/pick/mess/ > tmp
208 > mv tmp "\$1"
208 > mv tmp "\$1"
209 > EOF
209 > EOF
210 $ HGEDITOR="sh ../edit.sh" hg histedit tip 2>&1 | fixbundle
210 $ HGEDITOR="sh ../edit.sh" hg histedit tip 2>&1 | fixbundle
211 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
211 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
212 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
212 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
213 $ hg status
213 $ hg status
214 $ hg log --limit 1
214 $ hg log --limit 1
215 changeset: 6:1fd3b2fe7754
215 changeset: 6:1fd3b2fe7754
216 tag: tip
216 tag: tip
217 user: test
217 user: test
218 date: Thu Jan 01 00:00:00 1970 +0000
218 date: Thu Jan 01 00:00:00 1970 +0000
219 summary: f
219 summary: f
220
220
221
221
222 modify the message
222 modify the message
223
223
224 check saving last-message.txt, at first
224 check saving last-message.txt, at first
225
225
226 $ cat > $TESTTMP/commitfailure.py <<EOF
226 $ cat > $TESTTMP/commitfailure.py <<EOF
227 > from mercurial import util
227 > from mercurial import util
228 > def reposetup(ui, repo):
228 > def reposetup(ui, repo):
229 > class commitfailure(repo.__class__):
229 > class commitfailure(repo.__class__):
230 > def commit(self, *args, **kwargs):
230 > def commit(self, *args, **kwargs):
231 > raise util.Abort('emulating unexpected abort')
231 > raise util.Abort('emulating unexpected abort')
232 > repo.__class__ = commitfailure
232 > repo.__class__ = commitfailure
233 > EOF
233 > EOF
234 $ cat >> .hg/hgrc <<EOF
234 $ cat >> .hg/hgrc <<EOF
235 > [extensions]
235 > [extensions]
236 > # this failure occurs before editor invocation
236 > # this failure occurs before editor invocation
237 > commitfailure = $TESTTMP/commitfailure.py
237 > commitfailure = $TESTTMP/commitfailure.py
238 > EOF
238 > EOF
239
239
240 $ cat > $TESTTMP/editor.sh <<EOF
240 $ cat > $TESTTMP/editor.sh <<EOF
241 > echo "==== before editing"
241 > echo "==== before editing"
242 > cat \$1
242 > cat \$1
243 > echo "===="
243 > echo "===="
244 > echo "check saving last-message.txt" >> \$1
244 > echo "check saving last-message.txt" >> \$1
245 > EOF
245 > EOF
246
246
247 (test that editor is not invoked before transaction starting)
247 (test that editor is not invoked before transaction starting)
248
248
249 $ rm -f .hg/last-message.txt
249 $ rm -f .hg/last-message.txt
250 $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF | fixbundle
250 $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF | fixbundle
251 > mess 1fd3b2fe7754 f
251 > mess 1fd3b2fe7754 f
252 > EOF
252 > EOF
253 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
253 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
254 abort: emulating unexpected abort
254 abort: emulating unexpected abort
255 $ test -f .hg/last-message.txt
255 $ test -f .hg/last-message.txt
256 [1]
256 [1]
257
257
258 $ cat >> .hg/hgrc <<EOF
258 $ cat >> .hg/hgrc <<EOF
259 > [extensions]
259 > [extensions]
260 > commitfailure = !
260 > commitfailure = !
261 > EOF
261 > EOF
262 $ hg histedit --abort -q
262 $ hg histedit --abort -q
263
263
264 (test that editor is invoked and commit message is saved into
264 (test that editor is invoked and commit message is saved into
265 "last-message.txt")
265 "last-message.txt")
266
266
267 $ cat >> .hg/hgrc <<EOF
267 $ cat >> .hg/hgrc <<EOF
268 > [hooks]
268 > [hooks]
269 > # this failure occurs after editor invocation
269 > # this failure occurs after editor invocation
270 > pretxncommit.unexpectedabort = false
270 > pretxncommit.unexpectedabort = false
271 > EOF
271 > EOF
272
272
273 $ hg status --rev '1fd3b2fe7754^1' --rev 1fd3b2fe7754
273 $ hg status --rev '1fd3b2fe7754^1' --rev 1fd3b2fe7754
274 A f
274 A f
275
275
276 $ rm -f .hg/last-message.txt
276 $ rm -f .hg/last-message.txt
277 $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF
277 $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit tip --commands - 2>&1 << EOF
278 > mess 1fd3b2fe7754 f
278 > mess 1fd3b2fe7754 f
279 > EOF
279 > EOF
280 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
280 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
281 adding f
281 adding f
282 ==== before editing
282 ==== before editing
283 f
283 f
284
284
285
285
286 HG: Enter commit message. Lines beginning with 'HG:' are removed.
286 HG: Enter commit message. Lines beginning with 'HG:' are removed.
287 HG: Leave message empty to abort commit.
287 HG: Leave message empty to abort commit.
288 HG: --
288 HG: --
289 HG: user: test
289 HG: user: test
290 HG: branch 'default'
290 HG: branch 'default'
291 HG: added f
291 HG: added f
292 ====
292 ====
293 transaction abort!
293 transaction abort!
294 rollback completed
294 rollback completed
295 note: commit message saved in .hg/last-message.txt
295 note: commit message saved in .hg/last-message.txt
296 abort: pretxncommit.unexpectedabort hook exited with status 1
296 abort: pretxncommit.unexpectedabort hook exited with status 1
297 [255]
297 [255]
298 $ cat .hg/last-message.txt
298 $ cat .hg/last-message.txt
299 f
299 f
300
300
301
301
302 check saving last-message.txt
302 check saving last-message.txt
303
303
304 (test also that editor is invoked if histedit is continued for "message"
304 (test also that editor is invoked if histedit is continued for "message"
305 action)
305 action)
306
306
307 $ HGEDITOR=cat hg histedit --continue
307 $ HGEDITOR=cat hg histedit --continue
308 f
308 f
309
309
310
310
311 HG: Enter commit message. Lines beginning with 'HG:' are removed.
311 HG: Enter commit message. Lines beginning with 'HG:' are removed.
312 HG: Leave message empty to abort commit.
312 HG: Leave message empty to abort commit.
313 HG: --
313 HG: --
314 HG: user: test
314 HG: user: test
315 HG: branch 'default'
315 HG: branch 'default'
316 HG: added f
316 HG: added f
317 transaction abort!
317 transaction abort!
318 rollback completed
318 rollback completed
319 note: commit message saved in .hg/last-message.txt
319 note: commit message saved in .hg/last-message.txt
320 abort: pretxncommit.unexpectedabort hook exited with status 1
320 abort: pretxncommit.unexpectedabort hook exited with status 1
321 [255]
321 [255]
322
322
323 $ cat >> .hg/hgrc <<EOF
323 $ cat >> .hg/hgrc <<EOF
324 > [hooks]
324 > [hooks]
325 > pretxncommit.unexpectedabort =
325 > pretxncommit.unexpectedabort =
326 > EOF
326 > EOF
327 $ hg histedit --abort -q
327 $ hg histedit --abort -q
328
328
329 then, check "modify the message" itself
329 then, check "modify the message" itself
330
330
331 $ hg histedit tip --commands - 2>&1 << EOF | fixbundle
331 $ hg histedit tip --commands - 2>&1 << EOF | fixbundle
332 > mess 1fd3b2fe7754 f
332 > mess 1fd3b2fe7754 f
333 > EOF
333 > EOF
334 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
334 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
335 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
335 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
336 $ hg status
336 $ hg status
337 $ hg log --limit 1
337 $ hg log --limit 1
338 changeset: 6:62feedb1200e
338 changeset: 6:62feedb1200e
339 tag: tip
339 tag: tip
340 user: test
340 user: test
341 date: Thu Jan 01 00:00:00 1970 +0000
341 date: Thu Jan 01 00:00:00 1970 +0000
342 summary: f
342 summary: f
343
343
344
344
345 rollback should not work after a histedit
345 rollback should not work after a histedit
346 $ hg rollback
346 $ hg rollback
347 no rollback information available
347 no rollback information available
348 [1]
348 [1]
349
349
350 $ cd ..
350 $ cd ..
351 $ hg clone -qr0 r r0
351 $ hg clone -qr0 r r0
352 $ cd r0
352 $ cd r0
353 $ hg phase -fdr0
353 $ hg phase -fdr0
354 $ hg histedit --commands - 0 2>&1 << EOF
354 $ hg histedit --commands - 0 2>&1 << EOF
355 > edit cb9a9f314b8b a > $EDITED
355 > edit cb9a9f314b8b a > $EDITED
356 > EOF
356 > EOF
357 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
357 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
358 adding a
358 adding a
359 Make changes as needed, you may commit or record as needed now.
359 Make changes as needed, you may commit or record as needed now.
360 When you are finished, run hg histedit --continue to resume.
360 When you are finished, run hg histedit --continue to resume.
361 [1]
361 [1]
362 $ HGEDITOR=true hg histedit --continue
362 $ HGEDITOR=true hg histedit --continue
363 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
363 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
364 saved backup bundle to $TESTTMP/r0/.hg/strip-backup/cb9a9f314b8b-cc5ccb0b-backup.hg (glob)
364 saved backup bundle to $TESTTMP/r0/.hg/strip-backup/cb9a9f314b8b-cc5ccb0b-backup.hg (glob)
General Comments 0
You need to be logged in to leave comments. Login now