##// END OF EJS Templates
histedit: fold in memory...
Pierre-Yves David -
r17644:9ae073f1 default
parent child Browse files
Show More
@@ -1,669 +1,748 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 633536316234 and 7c2fd3b9020c
31 # Edit history between 633536316234 and 7c2fd3b9020c
32 #
32 #
33 # Commands:
33 # Commands:
34 # p, pick = use commit
34 # p, pick = use commit
35 # e, edit = use commit, but stop for amending
35 # e, edit = use commit, but stop for amending
36 # f, fold = use commit, but fold into previous commit (combines N and N-1)
36 # f, fold = use commit, but fold into previous commit (combines N and N-1)
37 # d, drop = remove commit from history
37 # d, drop = remove commit from history
38 # m, mess = edit message without changing commit content
38 # m, mess = edit message without changing commit content
39 #
39 #
40
40
41 In this file, lines beginning with ``#`` are ignored. You must specify a rule
41 In this file, lines beginning with ``#`` are ignored. You must specify a rule
42 for each revision in your history. For example, if you had meant to add gamma
42 for each revision in your history. For example, if you had meant to add gamma
43 before beta, and then wanted to add delta in the same revision as beta, you
43 before beta, and then wanted to add delta in the same revision as beta, you
44 would reorganize the file to look like this::
44 would reorganize the file to look like this::
45
45
46 pick 030b686bedc4 Add gamma
46 pick 030b686bedc4 Add gamma
47 pick c561b4e977df Add beta
47 pick c561b4e977df Add beta
48 fold 7c2fd3b9020c Add delta
48 fold 7c2fd3b9020c Add delta
49
49
50 # Edit history between 633536316234 and 7c2fd3b9020c
50 # Edit history between 633536316234 and 7c2fd3b9020c
51 #
51 #
52 # Commands:
52 # Commands:
53 # p, pick = use commit
53 # p, pick = use commit
54 # e, edit = use commit, but stop for amending
54 # e, edit = use commit, but stop for amending
55 # f, fold = use commit, but fold into previous commit (combines N and N-1)
55 # f, fold = use commit, but fold into previous commit (combines N and N-1)
56 # d, drop = remove commit from history
56 # d, drop = remove commit from history
57 # m, mess = edit message without changing commit content
57 # m, mess = edit message without changing commit content
58 #
58 #
59
59
60 At which point you close the editor and ``histedit`` starts working. When you
60 At which point you close the editor and ``histedit`` starts working. When you
61 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
61 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
62 those revisions together, offering you a chance to clean up the commit message::
62 those revisions together, offering you a chance to clean up the commit message::
63
63
64 Add beta
64 Add beta
65 ***
65 ***
66 Add delta
66 Add delta
67
67
68 Edit the commit message to your liking, then close the editor. For
68 Edit the commit message to your liking, then close the editor. For
69 this example, let's assume that the commit message was changed to
69 this example, let's assume that the commit message was changed to
70 ``Add beta and delta.`` After histedit has run and had a chance to
70 ``Add beta and delta.`` After histedit has run and had a chance to
71 remove any old or temporary revisions it needed, the history looks
71 remove any old or temporary revisions it needed, the history looks
72 like this::
72 like this::
73
73
74 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
74 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
75 | Add beta and delta.
75 | Add beta and delta.
76 |
76 |
77 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
77 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
78 | Add gamma
78 | Add gamma
79 |
79 |
80 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
80 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
81 Add alpha
81 Add alpha
82
82
83 Note that ``histedit`` does *not* remove any revisions (even its own temporary
83 Note that ``histedit`` does *not* remove any revisions (even its own temporary
84 ones) until after it has completed all the editing operations, so it will
84 ones) until after it has completed all the editing operations, so it will
85 probably perform several strip operations when it's done. For the above example,
85 probably perform several strip operations when it's done. For the above example,
86 it had to run strip twice. Strip can be slow depending on a variety of factors,
86 it had to run strip twice. Strip can be slow depending on a variety of factors,
87 so you might need to be a little patient. You can choose to keep the original
87 so you might need to be a little patient. You can choose to keep the original
88 revisions by passing the ``--keep`` flag.
88 revisions by passing the ``--keep`` flag.
89
89
90 The ``edit`` operation will drop you back to a command prompt,
90 The ``edit`` operation will drop you back to a command prompt,
91 allowing you to edit files freely, or even use ``hg record`` to commit
91 allowing you to edit files freely, or even use ``hg record`` to commit
92 some changes as a separate commit. When you're done, any remaining
92 some changes as a separate commit. When you're done, any remaining
93 uncommitted changes will be committed as well. When done, run ``hg
93 uncommitted changes will be committed as well. When done, run ``hg
94 histedit --continue`` to finish this step. You'll be prompted for a
94 histedit --continue`` to finish this step. You'll be prompted for a
95 new commit message, but the default commit message will be the
95 new commit message, but the default commit message will be the
96 original message for the ``edit`` ed revision.
96 original message for the ``edit`` ed revision.
97
97
98 The ``message`` operation will give you a chance to revise a commit
98 The ``message`` operation will give you a chance to revise a commit
99 message without changing the contents. It's a shortcut for doing
99 message without changing the contents. It's a shortcut for doing
100 ``edit`` immediately followed by `hg histedit --continue``.
100 ``edit`` immediately followed by `hg histedit --continue``.
101
101
102 If ``histedit`` encounters a conflict when moving a revision (while
102 If ``histedit`` encounters a conflict when moving a revision (while
103 handling ``pick`` or ``fold``), it'll stop in a similar manner to
103 handling ``pick`` or ``fold``), it'll stop in a similar manner to
104 ``edit`` with the difference that it won't prompt you for a commit
104 ``edit`` with the difference that it won't prompt you for a commit
105 message when done. If you decide at this point that you don't like how
105 message when done. If you decide at this point that you don't like how
106 much work it will be to rearrange history, or that you made a mistake,
106 much work it will be to rearrange history, or that you made a mistake,
107 you can use ``hg histedit --abort`` to abandon the new changes you
107 you can use ``hg histedit --abort`` to abandon the new changes you
108 have made and return to the state before you attempted to edit your
108 have made and return to the state before you attempted to edit your
109 history.
109 history.
110
110
111 If we clone the example repository above and add three more changes, such that
111 If we clone the example repository above and add three more changes, such that
112 we have the following history::
112 we have the following history::
113
113
114 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
114 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
115 | Add theta
115 | Add theta
116 |
116 |
117 o 5 140988835471 2009-04-27 18:04 -0500 stefan
117 o 5 140988835471 2009-04-27 18:04 -0500 stefan
118 | Add eta
118 | Add eta
119 |
119 |
120 o 4 122930637314 2009-04-27 18:04 -0500 stefan
120 o 4 122930637314 2009-04-27 18:04 -0500 stefan
121 | Add zeta
121 | Add zeta
122 |
122 |
123 o 3 836302820282 2009-04-27 18:04 -0500 stefan
123 o 3 836302820282 2009-04-27 18:04 -0500 stefan
124 | Add epsilon
124 | Add epsilon
125 |
125 |
126 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
126 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
127 | Add beta and delta.
127 | Add beta and delta.
128 |
128 |
129 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
129 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
130 | Add gamma
130 | Add gamma
131 |
131 |
132 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
132 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
133 Add alpha
133 Add alpha
134
134
135 If you run ``hg histedit --outgoing`` on the clone then it is the same
135 If you run ``hg histedit --outgoing`` on the clone then it is the same
136 as running ``hg histedit 836302820282``. If you need plan to push to a
136 as running ``hg histedit 836302820282``. If you need plan to push to a
137 repository that Mercurial does not detect to be related to the source
137 repository that Mercurial does not detect to be related to the source
138 repo, you can add a ``--force`` option.
138 repo, you can add a ``--force`` option.
139 """
139 """
140
140
141 try:
141 try:
142 import cPickle as pickle
142 import cPickle as pickle
143 except ImportError:
143 except ImportError:
144 import pickle
144 import pickle
145 import tempfile
145 import tempfile
146 import os
146 import os
147
147
148 from mercurial import bookmarks
148 from mercurial import bookmarks
149 from mercurial import cmdutil
149 from mercurial import cmdutil
150 from mercurial import discovery
150 from mercurial import discovery
151 from mercurial import error
151 from mercurial import error
152 from mercurial import copies
153 from mercurial import context
152 from mercurial import hg
154 from mercurial import hg
153 from mercurial import lock as lockmod
155 from mercurial import lock as lockmod
154 from mercurial import node
156 from mercurial import node
155 from mercurial import patch
157 from mercurial import patch
156 from mercurial import repair
158 from mercurial import repair
157 from mercurial import scmutil
159 from mercurial import scmutil
158 from mercurial import util
160 from mercurial import util
159 from mercurial.i18n import _
161 from mercurial.i18n import _
160
162
161 cmdtable = {}
163 cmdtable = {}
162 command = cmdutil.command(cmdtable)
164 command = cmdutil.command(cmdtable)
163
165
164 testedwith = 'internal'
166 testedwith = 'internal'
165
167
166 # i18n: command names and abbreviations must remain untranslated
168 # i18n: command names and abbreviations must remain untranslated
167 editcomment = _("""# Edit history between %s and %s
169 editcomment = _("""# Edit history between %s and %s
168 #
170 #
169 # Commands:
171 # Commands:
170 # p, pick = use commit
172 # p, pick = use commit
171 # e, edit = use commit, but stop for amending
173 # e, edit = use commit, but stop for amending
172 # f, fold = use commit, but fold into previous commit (combines N and N-1)
174 # f, fold = use commit, but fold into previous commit (combines N and N-1)
173 # d, drop = remove commit from history
175 # d, drop = remove commit from history
174 # m, mess = edit message without changing commit content
176 # m, mess = edit message without changing commit content
175 #
177 #
176 """)
178 """)
177
179
178 def foldchanges(ui, repo, node1, node2, opts):
180 def foldchanges(ui, repo, node1, node2, opts):
179 """Produce a new changeset that represents the diff from node1 to node2."""
181 """Produce a new changeset that represents the diff from node1 to node2."""
180 try:
182 try:
181 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
183 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
182 fp = os.fdopen(fd, 'w')
184 fp = os.fdopen(fd, 'w')
183 diffopts = patch.diffopts(ui, opts)
185 diffopts = patch.diffopts(ui, opts)
184 diffopts.git = True
186 diffopts.git = True
185 diffopts.ignorews = False
187 diffopts.ignorews = False
186 diffopts.ignorewsamount = False
188 diffopts.ignorewsamount = False
187 diffopts.ignoreblanklines = False
189 diffopts.ignoreblanklines = False
188 gen = patch.diff(repo, node1, node2, opts=diffopts)
190 gen = patch.diff(repo, node1, node2, opts=diffopts)
189 for chunk in gen:
191 for chunk in gen:
190 fp.write(chunk)
192 fp.write(chunk)
191 fp.close()
193 fp.close()
192 files = set()
194 files = set()
193 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
195 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
194 finally:
196 finally:
195 os.unlink(patchfile)
197 os.unlink(patchfile)
196 return files
198 return files
197
199
200 def collapse(repo, first, last, commitopts):
201 """collapse the set of revisions from first to last as new one.
202
203 Expected commit options are:
204 - message
205 - date
206 - username
207 Edition of commit message is trigered in all case.
208
209 This function works in memory."""
210 ctxs = list(repo.set('%d::%d', first, last))
211 if not ctxs:
212 return None
213 base = first.parents()[0]
214
215 # commit a new version of the old changeset, including the update
216 # collect all files which might be affected
217 files = set()
218 for ctx in ctxs:
219 files.update(ctx.files())
220
221 # Recompute copies (avoid recording a -> b -> a)
222 copied = copies.pathcopies(first, last)
223
224 # prune files which were reverted by the updates
225 def samefile(f):
226 if f in last.manifest():
227 a = last.filectx(f)
228 if f in base.manifest():
229 b = base.filectx(f)
230 return (a.data() == b.data()
231 and a.flags() == b.flags())
232 else:
233 return False
234 else:
235 return f not in base.manifest()
236 files = [f for f in files if not samefile(f)]
237 # commit version of these files as defined by head
238 headmf = last.manifest()
239 def filectxfn(repo, ctx, path):
240 if path in headmf:
241 fctx = last[path]
242 flags = fctx.flags()
243 mctx = context.memfilectx(fctx.path(), fctx.data(),
244 islink='l' in flags,
245 isexec='x' in flags,
246 copied=copied.get(path))
247 return mctx
248 raise IOError()
249
250 if commitopts.get('message'):
251 message = commitopts['message']
252 else:
253 message = first.description()
254 user = commitopts.get('user')
255 date = commitopts.get('date')
256 extra = first.extra()
257
258 parents = (first.p1().node(), first.p2().node())
259 new = context.memctx(repo,
260 parents=parents,
261 text=message,
262 files=files,
263 filectxfn=filectxfn,
264 user=user,
265 date=date,
266 extra=extra)
267 new._text = cmdutil.commitforceeditor(repo, new, [])
268 return repo.commitctx(new)
269
198 def pick(ui, repo, ctx, ha, opts):
270 def pick(ui, repo, ctx, ha, opts):
199 oldctx = repo[ha]
271 oldctx = repo[ha]
200 if oldctx.parents()[0] == ctx:
272 if oldctx.parents()[0] == ctx:
201 ui.debug('node %s unchanged\n' % ha)
273 ui.debug('node %s unchanged\n' % ha)
202 return oldctx, [], [], []
274 return oldctx, [], [], []
203 hg.update(repo, ctx.node())
275 hg.update(repo, ctx.node())
204 try:
276 try:
205 files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
277 files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
206 if not files:
278 if not files:
207 ui.warn(_('%s: empty changeset')
279 ui.warn(_('%s: empty changeset')
208 % node.hex(ha))
280 % node.hex(ha))
209 return ctx, [], [], []
281 return ctx, [], [], []
210 except Exception:
282 except Exception:
211 raise util.Abort(_('Fix up the change and run '
283 raise util.Abort(_('Fix up the change and run '
212 'hg histedit --continue'))
284 'hg histedit --continue'))
213 n = repo.commit(text=oldctx.description(), user=oldctx.user(),
285 n = repo.commit(text=oldctx.description(), user=oldctx.user(),
214 date=oldctx.date(), extra=oldctx.extra())
286 date=oldctx.date(), extra=oldctx.extra())
215 return repo[n], [n], [oldctx.node()], []
287 return repo[n], [n], [oldctx.node()], []
216
288
217
289
218 def edit(ui, repo, ctx, ha, opts):
290 def edit(ui, repo, ctx, ha, opts):
219 oldctx = repo[ha]
291 oldctx = repo[ha]
220 hg.update(repo, ctx.node())
292 hg.update(repo, ctx.node())
221 try:
293 try:
222 foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
294 foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
223 except Exception:
295 except Exception:
224 pass
296 pass
225 raise util.Abort(_('Make changes as needed, you may commit or record as '
297 raise util.Abort(_('Make changes as needed, you may commit or record as '
226 'needed now.\nWhen you are finished, run hg'
298 'needed now.\nWhen you are finished, run hg'
227 ' histedit --continue to resume.'))
299 ' histedit --continue to resume.'))
228
300
229 def fold(ui, repo, ctx, ha, opts):
301 def fold(ui, repo, ctx, ha, opts):
230 oldctx = repo[ha]
302 oldctx = repo[ha]
231 hg.update(repo, ctx.node())
303 hg.update(repo, ctx.node())
232 try:
304 try:
233 files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
305 files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
234 if not files:
306 if not files:
235 ui.warn(_('%s: empty changeset')
307 ui.warn(_('%s: empty changeset')
236 % node.hex(ha))
308 % node.hex(ha))
237 return ctx, [], [], []
309 return ctx, [], [], []
238 except Exception:
310 except Exception:
239 raise util.Abort(_('Fix up the change and run '
311 raise util.Abort(_('Fix up the change and run '
240 'hg histedit --continue'))
312 'hg histedit --continue'))
241 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
313 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
242 date=oldctx.date(), extra=oldctx.extra())
314 date=oldctx.date(), extra=oldctx.extra())
243 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
315 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
244
316
245 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
317 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
246 parent = ctx.parents()[0].node()
318 parent = ctx.parents()[0].node()
247 hg.update(repo, parent)
319 hg.update(repo, parent)
248 foldchanges(ui, repo, parent, newnode, opts)
320 ### prepare new commit data
321 commitopts = opts.copy()
322 # username
323 if ctx.user() == oldctx.user():
324 username = ctx.user()
325 else:
326 username = ui.username()
327 commitopts['user'] = username
328 # commit message
249 newmessage = '\n***\n'.join(
329 newmessage = '\n***\n'.join(
250 [ctx.description()] +
330 [ctx.description()] +
251 [repo[r].description() for r in internalchanges] +
331 [repo[r].description() for r in internalchanges] +
252 [oldctx.description()]) + '\n'
332 [oldctx.description()]) + '\n'
253 # If the changesets are from the same author, keep it.
333 commitopts['message'] = newmessage
254 if ctx.user() == oldctx.user():
334 # date
255 username = ctx.user()
335 commitopts['date'] = max(ctx.date(), oldctx.date())
256 else:
336 n = collapse(repo, ctx, repo[newnode], commitopts)
257 username = ui.username()
337 if n is None:
258 newmessage = ui.edit(newmessage, username)
338 return ctx, [], [], []
259 n = repo.commit(text=newmessage, user=username,
339 hg.update(repo, n)
260 date=max(ctx.date(), oldctx.date()), extra=oldctx.extra())
261 return repo[n], [n], [oldctx.node(), ctx.node()], [newnode]
340 return repo[n], [n], [oldctx.node(), ctx.node()], [newnode]
262
341
263 def drop(ui, repo, ctx, ha, opts):
342 def drop(ui, repo, ctx, ha, opts):
264 return ctx, [], [repo[ha].node()], []
343 return ctx, [], [repo[ha].node()], []
265
344
266
345
267 def message(ui, repo, ctx, ha, opts):
346 def message(ui, repo, ctx, ha, opts):
268 oldctx = repo[ha]
347 oldctx = repo[ha]
269 hg.update(repo, ctx.node())
348 hg.update(repo, ctx.node())
270 try:
349 try:
271 foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
350 foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
272 except Exception:
351 except Exception:
273 raise util.Abort(_('Fix up the change and run '
352 raise util.Abort(_('Fix up the change and run '
274 'hg histedit --continue'))
353 'hg histedit --continue'))
275 message = oldctx.description() + '\n'
354 message = oldctx.description() + '\n'
276 message = ui.edit(message, ui.username())
355 message = ui.edit(message, ui.username())
277 new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(),
356 new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(),
278 extra=oldctx.extra())
357 extra=oldctx.extra())
279 newctx = repo[new]
358 newctx = repo[new]
280 if oldctx.node() != newctx.node():
359 if oldctx.node() != newctx.node():
281 return newctx, [new], [oldctx.node()], []
360 return newctx, [new], [oldctx.node()], []
282 # We didn't make an edit, so just indicate no replaced nodes
361 # We didn't make an edit, so just indicate no replaced nodes
283 return newctx, [new], [], []
362 return newctx, [new], [], []
284
363
285 actiontable = {'p': pick,
364 actiontable = {'p': pick,
286 'pick': pick,
365 'pick': pick,
287 'e': edit,
366 'e': edit,
288 'edit': edit,
367 'edit': edit,
289 'f': fold,
368 'f': fold,
290 'fold': fold,
369 'fold': fold,
291 'd': drop,
370 'd': drop,
292 'drop': drop,
371 'drop': drop,
293 'm': message,
372 'm': message,
294 'mess': message,
373 'mess': message,
295 }
374 }
296
375
297 @command('histedit',
376 @command('histedit',
298 [('', 'commands', '',
377 [('', 'commands', '',
299 _('Read history edits from the specified file.')),
378 _('Read history edits from the specified file.')),
300 ('c', 'continue', False, _('continue an edit already in progress')),
379 ('c', 'continue', False, _('continue an edit already in progress')),
301 ('k', 'keep', False,
380 ('k', 'keep', False,
302 _("don't strip old nodes after edit is complete")),
381 _("don't strip old nodes after edit is complete")),
303 ('', 'abort', False, _('abort an edit in progress')),
382 ('', 'abort', False, _('abort an edit in progress')),
304 ('o', 'outgoing', False, _('changesets not found in destination')),
383 ('o', 'outgoing', False, _('changesets not found in destination')),
305 ('f', 'force', False,
384 ('f', 'force', False,
306 _('force outgoing even for unrelated repositories')),
385 _('force outgoing even for unrelated repositories')),
307 ('r', 'rev', [], _('first revision to be edited'))],
386 ('r', 'rev', [], _('first revision to be edited'))],
308 _("[PARENT]"))
387 _("[PARENT]"))
309 def histedit(ui, repo, *parent, **opts):
388 def histedit(ui, repo, *parent, **opts):
310 """interactively edit changeset history
389 """interactively edit changeset history
311 """
390 """
312 # TODO only abort if we try and histedit mq patches, not just
391 # TODO only abort if we try and histedit mq patches, not just
313 # blanket if mq patches are applied somewhere
392 # blanket if mq patches are applied somewhere
314 mq = getattr(repo, 'mq', None)
393 mq = getattr(repo, 'mq', None)
315 if mq and mq.applied:
394 if mq and mq.applied:
316 raise util.Abort(_('source has mq patches applied'))
395 raise util.Abort(_('source has mq patches applied'))
317
396
318 parent = list(parent) + opts.get('rev', [])
397 parent = list(parent) + opts.get('rev', [])
319 if opts.get('outgoing'):
398 if opts.get('outgoing'):
320 if len(parent) > 1:
399 if len(parent) > 1:
321 raise util.Abort(
400 raise util.Abort(
322 _('only one repo argument allowed with --outgoing'))
401 _('only one repo argument allowed with --outgoing'))
323 elif parent:
402 elif parent:
324 parent = parent[0]
403 parent = parent[0]
325
404
326 dest = ui.expandpath(parent or 'default-push', parent or 'default')
405 dest = ui.expandpath(parent or 'default-push', parent or 'default')
327 dest, revs = hg.parseurl(dest, None)[:2]
406 dest, revs = hg.parseurl(dest, None)[:2]
328 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
407 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
329
408
330 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
409 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
331 other = hg.peer(repo, opts, dest)
410 other = hg.peer(repo, opts, dest)
332
411
333 if revs:
412 if revs:
334 revs = [repo.lookup(rev) for rev in revs]
413 revs = [repo.lookup(rev) for rev in revs]
335
414
336 parent = discovery.findcommonoutgoing(
415 parent = discovery.findcommonoutgoing(
337 repo, other, [], force=opts.get('force')).missing[0:1]
416 repo, other, [], force=opts.get('force')).missing[0:1]
338 else:
417 else:
339 if opts.get('force'):
418 if opts.get('force'):
340 raise util.Abort(_('--force only allowed with --outgoing'))
419 raise util.Abort(_('--force only allowed with --outgoing'))
341
420
342 if opts.get('continue', False):
421 if opts.get('continue', False):
343 if len(parent) != 0:
422 if len(parent) != 0:
344 raise util.Abort(_('no arguments allowed with --continue'))
423 raise util.Abort(_('no arguments allowed with --continue'))
345 (parentctxnode, created, replaced,
424 (parentctxnode, created, replaced,
346 tmpnodes, existing, rules, keep, tip, replacemap) = readstate(repo)
425 tmpnodes, existing, rules, keep, tip, replacemap) = readstate(repo)
347 currentparent, wantnull = repo.dirstate.parents()
426 currentparent, wantnull = repo.dirstate.parents()
348 parentctx = repo[parentctxnode]
427 parentctx = repo[parentctxnode]
349 # existing is the list of revisions initially considered by
428 # existing is the list of revisions initially considered by
350 # histedit. Here we use it to list new changesets, descendants
429 # histedit. Here we use it to list new changesets, descendants
351 # of parentctx without an 'existing' changeset in-between. We
430 # of parentctx without an 'existing' changeset in-between. We
352 # also have to exclude 'existing' changesets which were
431 # also have to exclude 'existing' changesets which were
353 # previously dropped.
432 # previously dropped.
354 descendants = set(c.node() for c in
433 descendants = set(c.node() for c in
355 repo.set('(%n::) - %n', parentctxnode, parentctxnode))
434 repo.set('(%n::) - %n', parentctxnode, parentctxnode))
356 existing = set(existing)
435 existing = set(existing)
357 notdropped = set(n for n in existing if n in descendants and
436 notdropped = set(n for n in existing if n in descendants and
358 (n not in replacemap or replacemap[n] in descendants))
437 (n not in replacemap or replacemap[n] in descendants))
359 # Discover any nodes the user has added in the interim. We can
438 # Discover any nodes the user has added in the interim. We can
360 # miss changesets which were dropped and recreated the same.
439 # miss changesets which were dropped and recreated the same.
361 newchildren = list(c.node() for c in repo.set(
440 newchildren = list(c.node() for c in repo.set(
362 'sort(%ln - (%ln or %ln::))', descendants, existing, notdropped))
441 'sort(%ln - (%ln or %ln::))', descendants, existing, notdropped))
363 action, currentnode = rules.pop(0)
442 action, currentnode = rules.pop(0)
364 if action in ('f', 'fold'):
443 if action in ('f', 'fold'):
365 tmpnodes.extend(newchildren)
444 tmpnodes.extend(newchildren)
366 else:
445 else:
367 created.extend(newchildren)
446 created.extend(newchildren)
368
447
369 m, a, r, d = repo.status()[:4]
448 m, a, r, d = repo.status()[:4]
370 oldctx = repo[currentnode]
449 oldctx = repo[currentnode]
371 message = oldctx.description() + '\n'
450 message = oldctx.description() + '\n'
372 if action in ('e', 'edit', 'm', 'mess'):
451 if action in ('e', 'edit', 'm', 'mess'):
373 message = ui.edit(message, ui.username())
452 message = ui.edit(message, ui.username())
374 elif action in ('f', 'fold'):
453 elif action in ('f', 'fold'):
375 message = 'fold-temp-revision %s' % currentnode
454 message = 'fold-temp-revision %s' % currentnode
376 new = None
455 new = None
377 if m or a or r or d:
456 if m or a or r or d:
378 new = repo.commit(text=message, user=oldctx.user(),
457 new = repo.commit(text=message, user=oldctx.user(),
379 date=oldctx.date(), extra=oldctx.extra())
458 date=oldctx.date(), extra=oldctx.extra())
380
459
381 # If we're resuming a fold and we have new changes, mark the
460 # If we're resuming a fold and we have new changes, mark the
382 # replacements and finish the fold. If not, it's more like a
461 # replacements and finish the fold. If not, it's more like a
383 # drop of the changesets that disappeared, and we can skip
462 # drop of the changesets that disappeared, and we can skip
384 # this step.
463 # this step.
385 if action in ('f', 'fold') and (new or newchildren):
464 if action in ('f', 'fold') and (new or newchildren):
386 if new:
465 if new:
387 tmpnodes.append(new)
466 tmpnodes.append(new)
388 else:
467 else:
389 new = newchildren[-1]
468 new = newchildren[-1]
390 (parentctx, created_, replaced_, tmpnodes_) = finishfold(
469 (parentctx, created_, replaced_, tmpnodes_) = finishfold(
391 ui, repo, parentctx, oldctx, new, opts, newchildren)
470 ui, repo, parentctx, oldctx, new, opts, newchildren)
392 replaced.extend(replaced_)
471 replaced.extend(replaced_)
393 created.extend(created_)
472 created.extend(created_)
394 tmpnodes.extend(tmpnodes_)
473 tmpnodes.extend(tmpnodes_)
395 elif action not in ('d', 'drop'):
474 elif action not in ('d', 'drop'):
396 if new != oldctx.node():
475 if new != oldctx.node():
397 replaced.append(oldctx.node())
476 replaced.append(oldctx.node())
398 if new:
477 if new:
399 if new != oldctx.node():
478 if new != oldctx.node():
400 created.append(new)
479 created.append(new)
401 parentctx = repo[new]
480 parentctx = repo[new]
402
481
403 elif opts.get('abort', False):
482 elif opts.get('abort', False):
404 if len(parent) != 0:
483 if len(parent) != 0:
405 raise util.Abort(_('no arguments allowed with --abort'))
484 raise util.Abort(_('no arguments allowed with --abort'))
406 (parentctxnode, created, replaced, tmpnodes,
485 (parentctxnode, created, replaced, tmpnodes,
407 existing, rules, keep, tip, replacemap) = readstate(repo)
486 existing, rules, keep, tip, replacemap) = readstate(repo)
408 ui.debug('restore wc to old tip %s\n' % node.hex(tip))
487 ui.debug('restore wc to old tip %s\n' % node.hex(tip))
409 hg.clean(repo, tip)
488 hg.clean(repo, tip)
410 ui.debug('should strip created nodes %s\n' %
489 ui.debug('should strip created nodes %s\n' %
411 ', '.join([node.hex(n)[:12] for n in created]))
490 ', '.join([node.hex(n)[:12] for n in created]))
412 ui.debug('should strip temp nodes %s\n' %
491 ui.debug('should strip temp nodes %s\n' %
413 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
492 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
414 for nodes in (created, tmpnodes):
493 for nodes in (created, tmpnodes):
415 lock = None
494 lock = None
416 try:
495 try:
417 lock = repo.lock()
496 lock = repo.lock()
418 for n in reversed(nodes):
497 for n in reversed(nodes):
419 try:
498 try:
420 repair.strip(ui, repo, n)
499 repair.strip(ui, repo, n)
421 except error.LookupError:
500 except error.LookupError:
422 pass
501 pass
423 finally:
502 finally:
424 lockmod.release(lock)
503 lockmod.release(lock)
425 os.unlink(os.path.join(repo.path, 'histedit-state'))
504 os.unlink(os.path.join(repo.path, 'histedit-state'))
426 return
505 return
427 else:
506 else:
428 cmdutil.bailifchanged(repo)
507 cmdutil.bailifchanged(repo)
429 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
508 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
430 raise util.Abort(_('history edit already in progress, try '
509 raise util.Abort(_('history edit already in progress, try '
431 '--continue or --abort'))
510 '--continue or --abort'))
432
511
433 tip, empty = repo.dirstate.parents()
512 tip, empty = repo.dirstate.parents()
434
513
435
514
436 if len(parent) != 1:
515 if len(parent) != 1:
437 raise util.Abort(_('histedit requires exactly one parent revision'))
516 raise util.Abort(_('histedit requires exactly one parent revision'))
438 parent = scmutil.revsingle(repo, parent[0]).node()
517 parent = scmutil.revsingle(repo, parent[0]).node()
439
518
440 keep = opts.get('keep', False)
519 keep = opts.get('keep', False)
441 revs = between(repo, parent, tip, keep)
520 revs = between(repo, parent, tip, keep)
442
521
443 ctxs = [repo[r] for r in revs]
522 ctxs = [repo[r] for r in revs]
444 existing = [r.node() for r in ctxs]
523 existing = [r.node() for r in ctxs]
445 rules = opts.get('commands', '')
524 rules = opts.get('commands', '')
446 if not rules:
525 if not rules:
447 rules = '\n'.join([makedesc(c) for c in ctxs])
526 rules = '\n'.join([makedesc(c) for c in ctxs])
448 rules += '\n\n'
527 rules += '\n\n'
449 rules += editcomment % (node.hex(parent)[:12], node.hex(tip)[:12])
528 rules += editcomment % (node.hex(parent)[:12], node.hex(tip)[:12])
450 rules = ui.edit(rules, ui.username())
529 rules = ui.edit(rules, ui.username())
451 # Save edit rules in .hg/histedit-last-edit.txt in case
530 # Save edit rules in .hg/histedit-last-edit.txt in case
452 # the user needs to ask for help after something
531 # the user needs to ask for help after something
453 # surprising happens.
532 # surprising happens.
454 f = open(repo.join('histedit-last-edit.txt'), 'w')
533 f = open(repo.join('histedit-last-edit.txt'), 'w')
455 f.write(rules)
534 f.write(rules)
456 f.close()
535 f.close()
457 else:
536 else:
458 f = open(rules)
537 f = open(rules)
459 rules = f.read()
538 rules = f.read()
460 f.close()
539 f.close()
461 rules = [l for l in (r.strip() for r in rules.splitlines())
540 rules = [l for l in (r.strip() for r in rules.splitlines())
462 if l and not l[0] == '#']
541 if l and not l[0] == '#']
463 rules = verifyrules(rules, repo, ctxs)
542 rules = verifyrules(rules, repo, ctxs)
464
543
465 parentctx = repo[parent].parents()[0]
544 parentctx = repo[parent].parents()[0]
466 keep = opts.get('keep', False)
545 keep = opts.get('keep', False)
467 replaced = []
546 replaced = []
468 replacemap = {}
547 replacemap = {}
469 tmpnodes = []
548 tmpnodes = []
470 created = []
549 created = []
471
550
472
551
473 while rules:
552 while rules:
474 writestate(repo, parentctx.node(), created, replaced,
553 writestate(repo, parentctx.node(), created, replaced,
475 tmpnodes, existing, rules, keep, tip, replacemap)
554 tmpnodes, existing, rules, keep, tip, replacemap)
476 action, ha = rules.pop(0)
555 action, ha = rules.pop(0)
477 (parentctx, created_, replaced_, tmpnodes_) = actiontable[action](
556 (parentctx, created_, replaced_, tmpnodes_) = actiontable[action](
478 ui, repo, parentctx, ha, opts)
557 ui, repo, parentctx, ha, opts)
479
558
480 if replaced_:
559 if replaced_:
481 clen, rlen = len(created_), len(replaced_)
560 clen, rlen = len(created_), len(replaced_)
482 if clen == rlen == 1:
561 if clen == rlen == 1:
483 ui.debug('histedit: exact replacement of %s with %s\n' % (
562 ui.debug('histedit: exact replacement of %s with %s\n' % (
484 node.short(replaced_[0]), node.short(created_[0])))
563 node.short(replaced_[0]), node.short(created_[0])))
485
564
486 replacemap[replaced_[0]] = created_[0]
565 replacemap[replaced_[0]] = created_[0]
487 elif clen > rlen:
566 elif clen > rlen:
488 assert rlen == 1, ('unexpected replacement of '
567 assert rlen == 1, ('unexpected replacement of '
489 '%d changes with %d changes' % (rlen, clen))
568 '%d changes with %d changes' % (rlen, clen))
490 # made more changesets than we're replacing
569 # made more changesets than we're replacing
491 # TODO synthesize patch names for created patches
570 # TODO synthesize patch names for created patches
492 replacemap[replaced_[0]] = created_[-1]
571 replacemap[replaced_[0]] = created_[-1]
493 ui.debug('histedit: created many, assuming %s replaced by %s' %
572 ui.debug('histedit: created many, assuming %s replaced by %s' %
494 (node.short(replaced_[0]), node.short(created_[-1])))
573 (node.short(replaced_[0]), node.short(created_[-1])))
495 elif rlen > clen:
574 elif rlen > clen:
496 if not created_:
575 if not created_:
497 # This must be a drop. Try and put our metadata on
576 # This must be a drop. Try and put our metadata on
498 # the parent change.
577 # the parent change.
499 assert rlen == 1
578 assert rlen == 1
500 r = replaced_[0]
579 r = replaced_[0]
501 ui.debug('histedit: %s seems replaced with nothing, '
580 ui.debug('histedit: %s seems replaced with nothing, '
502 'finding a parent\n' % (node.short(r)))
581 'finding a parent\n' % (node.short(r)))
503 pctx = repo[r].parents()[0]
582 pctx = repo[r].parents()[0]
504 if pctx.node() in replacemap:
583 if pctx.node() in replacemap:
505 ui.debug('histedit: parent is already replaced\n')
584 ui.debug('histedit: parent is already replaced\n')
506 replacemap[r] = replacemap[pctx.node()]
585 replacemap[r] = replacemap[pctx.node()]
507 else:
586 else:
508 replacemap[r] = pctx.node()
587 replacemap[r] = pctx.node()
509 ui.debug('histedit: %s best replaced by %s\n' % (
588 ui.debug('histedit: %s best replaced by %s\n' % (
510 node.short(r), node.short(replacemap[r])))
589 node.short(r), node.short(replacemap[r])))
511 else:
590 else:
512 assert len(created_) == 1
591 assert len(created_) == 1
513 for r in replaced_:
592 for r in replaced_:
514 ui.debug('histedit: %s replaced by %s\n' % (
593 ui.debug('histedit: %s replaced by %s\n' % (
515 node.short(r), node.short(created_[0])))
594 node.short(r), node.short(created_[0])))
516 replacemap[r] = created_[0]
595 replacemap[r] = created_[0]
517 else:
596 else:
518 assert False, (
597 assert False, (
519 'Unhandled case in replacement mapping! '
598 'Unhandled case in replacement mapping! '
520 'replacing %d changes with %d changes' % (rlen, clen))
599 'replacing %d changes with %d changes' % (rlen, clen))
521 created.extend(created_)
600 created.extend(created_)
522 replaced.extend(replaced_)
601 replaced.extend(replaced_)
523 tmpnodes.extend(tmpnodes_)
602 tmpnodes.extend(tmpnodes_)
524
603
525 hg.update(repo, parentctx.node())
604 hg.update(repo, parentctx.node())
526
605
527 if not keep:
606 if not keep:
528 if replacemap:
607 if replacemap:
529 ui.note(_('histedit: Should update metadata for the following '
608 ui.note(_('histedit: Should update metadata for the following '
530 'changes:\n'))
609 'changes:\n'))
531
610
532 def copybms(old, new):
611 def copybms(old, new):
533 if old in tmpnodes or old in created:
612 if old in tmpnodes or old in created:
534 # can't have any metadata we'd want to update
613 # can't have any metadata we'd want to update
535 return
614 return
536 while new in replacemap:
615 while new in replacemap:
537 new = replacemap[new]
616 new = replacemap[new]
538 ui.note(_('histedit: %s to %s\n') % (node.short(old),
617 ui.note(_('histedit: %s to %s\n') % (node.short(old),
539 node.short(new)))
618 node.short(new)))
540 octx = repo[old]
619 octx = repo[old]
541 marks = octx.bookmarks()
620 marks = octx.bookmarks()
542 if marks:
621 if marks:
543 ui.note(_('histedit: moving bookmarks %s\n') %
622 ui.note(_('histedit: moving bookmarks %s\n') %
544 ', '.join(marks))
623 ', '.join(marks))
545 for mark in marks:
624 for mark in marks:
546 repo._bookmarks[mark] = new
625 repo._bookmarks[mark] = new
547 bookmarks.write(repo)
626 bookmarks.write(repo)
548
627
549 # We assume that bookmarks on the tip should remain
628 # We assume that bookmarks on the tip should remain
550 # tipmost, but bookmarks on non-tip changesets should go
629 # tipmost, but bookmarks on non-tip changesets should go
551 # to their most reasonable successor. As a result, find
630 # to their most reasonable successor. As a result, find
552 # the old tip and new tip and copy those bookmarks first,
631 # the old tip and new tip and copy those bookmarks first,
553 # then do the rest of the bookmark copies.
632 # then do the rest of the bookmark copies.
554 oldtip = sorted(replacemap.keys(), key=repo.changelog.rev)[-1]
633 oldtip = sorted(replacemap.keys(), key=repo.changelog.rev)[-1]
555 newtip = sorted(replacemap.values(), key=repo.changelog.rev)[-1]
634 newtip = sorted(replacemap.values(), key=repo.changelog.rev)[-1]
556 copybms(oldtip, newtip)
635 copybms(oldtip, newtip)
557
636
558 for old, new in sorted(replacemap.iteritems()):
637 for old, new in sorted(replacemap.iteritems()):
559 copybms(old, new)
638 copybms(old, new)
560 # TODO update mq state
639 # TODO update mq state
561
640
562 ui.debug('should strip replaced nodes %s\n' %
641 ui.debug('should strip replaced nodes %s\n' %
563 ', '.join([node.hex(n)[:12] for n in replaced]))
642 ', '.join([node.hex(n)[:12] for n in replaced]))
564 lock = None
643 lock = None
565 try:
644 try:
566 lock = repo.lock()
645 lock = repo.lock()
567 for n in sorted(replaced, key=lambda x: repo[x].rev()):
646 for n in sorted(replaced, key=lambda x: repo[x].rev()):
568 try:
647 try:
569 repair.strip(ui, repo, n)
648 repair.strip(ui, repo, n)
570 except error.LookupError:
649 except error.LookupError:
571 pass
650 pass
572 finally:
651 finally:
573 lockmod.release(lock)
652 lockmod.release(lock)
574
653
575 ui.debug('should strip temp nodes %s\n' %
654 ui.debug('should strip temp nodes %s\n' %
576 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
655 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
577 lock = None
656 lock = None
578 try:
657 try:
579 lock = repo.lock()
658 lock = repo.lock()
580 for n in reversed(tmpnodes):
659 for n in reversed(tmpnodes):
581 try:
660 try:
582 repair.strip(ui, repo, n)
661 repair.strip(ui, repo, n)
583 except error.LookupError:
662 except error.LookupError:
584 pass
663 pass
585 finally:
664 finally:
586 lockmod.release(lock)
665 lockmod.release(lock)
587 os.unlink(os.path.join(repo.path, 'histedit-state'))
666 os.unlink(os.path.join(repo.path, 'histedit-state'))
588 if os.path.exists(repo.sjoin('undo')):
667 if os.path.exists(repo.sjoin('undo')):
589 os.unlink(repo.sjoin('undo'))
668 os.unlink(repo.sjoin('undo'))
590
669
591
670
592 def between(repo, old, new, keep):
671 def between(repo, old, new, keep):
593 """select and validate the set of revision to edit
672 """select and validate the set of revision to edit
594
673
595 When keep is false, the specified set can't have children."""
674 When keep is false, the specified set can't have children."""
596 revs = [old]
675 revs = [old]
597 current = old
676 current = old
598 while current != new:
677 while current != new:
599 ctx = repo[current]
678 ctx = repo[current]
600 if not keep and len(ctx.children()) > 1:
679 if not keep and len(ctx.children()) > 1:
601 raise util.Abort(_('cannot edit history that would orphan nodes'))
680 raise util.Abort(_('cannot edit history that would orphan nodes'))
602 if len(ctx.parents()) != 1 and ctx.parents()[1] != node.nullid:
681 if len(ctx.parents()) != 1 and ctx.parents()[1] != node.nullid:
603 raise util.Abort(_("can't edit history with merges"))
682 raise util.Abort(_("can't edit history with merges"))
604 if not ctx.children():
683 if not ctx.children():
605 current = new
684 current = new
606 else:
685 else:
607 current = ctx.children()[0].node()
686 current = ctx.children()[0].node()
608 revs.append(current)
687 revs.append(current)
609 if len(repo[current].children()) and not keep:
688 if len(repo[current].children()) and not keep:
610 raise util.Abort(_('cannot edit history that would orphan nodes'))
689 raise util.Abort(_('cannot edit history that would orphan nodes'))
611 return revs
690 return revs
612
691
613
692
614 def writestate(repo, parentctxnode, created, replaced,
693 def writestate(repo, parentctxnode, created, replaced,
615 tmpnodes, existing, rules, keep, oldtip, replacemap):
694 tmpnodes, existing, rules, keep, oldtip, replacemap):
616 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
695 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
617 pickle.dump((parentctxnode, created, replaced,
696 pickle.dump((parentctxnode, created, replaced,
618 tmpnodes, existing, rules, keep, oldtip, replacemap),
697 tmpnodes, existing, rules, keep, oldtip, replacemap),
619 fp)
698 fp)
620 fp.close()
699 fp.close()
621
700
622 def readstate(repo):
701 def readstate(repo):
623 """Returns a tuple of (parentnode, created, replaced, tmp, existing, rules,
702 """Returns a tuple of (parentnode, created, replaced, tmp, existing, rules,
624 keep, oldtip, replacemap ).
703 keep, oldtip, replacemap ).
625 """
704 """
626 fp = open(os.path.join(repo.path, 'histedit-state'))
705 fp = open(os.path.join(repo.path, 'histedit-state'))
627 return pickle.load(fp)
706 return pickle.load(fp)
628
707
629
708
630 def makedesc(c):
709 def makedesc(c):
631 """build a initial action line for a ctx `c`
710 """build a initial action line for a ctx `c`
632
711
633 line are in the form:
712 line are in the form:
634
713
635 pick <hash> <rev> <summary>
714 pick <hash> <rev> <summary>
636 """
715 """
637 summary = ''
716 summary = ''
638 if c.description():
717 if c.description():
639 summary = c.description().splitlines()[0]
718 summary = c.description().splitlines()[0]
640 line = 'pick %s %d %s' % (c.hex()[:12], c.rev(), summary)
719 line = 'pick %s %d %s' % (c.hex()[:12], c.rev(), summary)
641 return line[:80] # trim to 80 chars so it's not stupidly wide in my editor
720 return line[:80] # trim to 80 chars so it's not stupidly wide in my editor
642
721
643 def verifyrules(rules, repo, ctxs):
722 def verifyrules(rules, repo, ctxs):
644 """Verify that there exists exactly one edit rule per given changeset.
723 """Verify that there exists exactly one edit rule per given changeset.
645
724
646 Will abort if there are to many or too few rules, a malformed rule,
725 Will abort if there are to many or too few rules, a malformed rule,
647 or a rule on a changeset outside of the user-given range.
726 or a rule on a changeset outside of the user-given range.
648 """
727 """
649 parsed = []
728 parsed = []
650 if len(rules) != len(ctxs):
729 if len(rules) != len(ctxs):
651 raise util.Abort(_('must specify a rule for each changeset once'))
730 raise util.Abort(_('must specify a rule for each changeset once'))
652 for r in rules:
731 for r in rules:
653 if ' ' not in r:
732 if ' ' not in r:
654 raise util.Abort(_('malformed line "%s"') % r)
733 raise util.Abort(_('malformed line "%s"') % r)
655 action, rest = r.split(' ', 1)
734 action, rest = r.split(' ', 1)
656 if ' ' in rest.strip():
735 if ' ' in rest.strip():
657 ha, rest = rest.split(' ', 1)
736 ha, rest = rest.split(' ', 1)
658 else:
737 else:
659 ha = r.strip()
738 ha = r.strip()
660 try:
739 try:
661 if repo[ha] not in ctxs:
740 if repo[ha] not in ctxs:
662 raise util.Abort(
741 raise util.Abort(
663 _('may not use changesets other than the ones listed'))
742 _('may not use changesets other than the ones listed'))
664 except error.RepoError:
743 except error.RepoError:
665 raise util.Abort(_('unknown changeset %s listed') % ha)
744 raise util.Abort(_('unknown changeset %s listed') % ha)
666 if action not in actiontable:
745 if action not in actiontable:
667 raise util.Abort(_('unknown action "%s"') % action)
746 raise util.Abort(_('unknown action "%s"') % action)
668 parsed.append([action, ha])
747 parsed.append([action, ha])
669 return parsed
748 return parsed
@@ -1,147 +1,158 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 > graphlog=
5 > graphlog=
6 > histedit=
6 > histedit=
7 > EOF
7 > EOF
8
8
9 $ EDITED="$TESTTMP/editedhistory"
9 $ EDITED="$TESTTMP/editedhistory"
10 $ cat > $EDITED <<EOF
10 $ cat > $EDITED <<EOF
11 > pick 177f92b77385 c
11 > pick 177f92b77385 c
12 > pick 055a42cdd887 d
12 > pick 055a42cdd887 d
13 > fold bfa474341cc9 does not commute with e
13 > fold bfa474341cc9 does not commute with e
14 > pick e860deea161a e
14 > pick e860deea161a e
15 > pick 652413bf663e f
15 > pick 652413bf663e f
16 > EOF
16 > EOF
17 $ initrepo ()
17 $ initrepo ()
18 > {
18 > {
19 > hg init $1
19 > hg init $1
20 > cd $1
20 > cd $1
21 > for x in a b c d e f ; do
21 > for x in a b c d e f ; do
22 > echo $x > $x
22 > echo $x > $x
23 > hg add $x
23 > hg add $x
24 > hg ci -m $x
24 > hg ci -m $x
25 > done
25 > done
26 > echo a >> e
26 > echo a >> e
27 > hg ci -m 'does not commute with e'
27 > hg ci -m 'does not commute with e'
28 > cd ..
28 > cd ..
29 > }
29 > }
30
30
31 $ initrepo r
31 $ initrepo r
32 $ cd r
32 $ cd r
33
33
34 log before edit
34 log before edit
35 $ hg log --graph
35 $ hg log --graph
36 @ changeset: 6:bfa474341cc9
36 @ changeset: 6:bfa474341cc9
37 | tag: tip
37 | tag: tip
38 | user: test
38 | user: test
39 | date: Thu Jan 01 00:00:00 1970 +0000
39 | date: Thu Jan 01 00:00:00 1970 +0000
40 | summary: does not commute with e
40 | summary: does not commute with e
41 |
41 |
42 o changeset: 5:652413bf663e
42 o changeset: 5:652413bf663e
43 | user: test
43 | user: test
44 | date: Thu Jan 01 00:00:00 1970 +0000
44 | date: Thu Jan 01 00:00:00 1970 +0000
45 | summary: f
45 | summary: f
46 |
46 |
47 o changeset: 4:e860deea161a
47 o changeset: 4:e860deea161a
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: e
50 | summary: e
51 |
51 |
52 o changeset: 3:055a42cdd887
52 o changeset: 3:055a42cdd887
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: d
55 | summary: d
56 |
56 |
57 o changeset: 2:177f92b77385
57 o changeset: 2:177f92b77385
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: c
60 | summary: c
61 |
61 |
62 o changeset: 1:d2ae7f538514
62 o changeset: 1:d2ae7f538514
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: b
65 | summary: b
66 |
66 |
67 o changeset: 0:cb9a9f314b8b
67 o changeset: 0:cb9a9f314b8b
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: a
70 summary: a
71
71
72
72
73 edit the history
73 edit the history
74 $ HGEDITOR="cat \"$EDITED\" > " hg histedit 177f92b77385 2>&1 | fixbundle
74 $ HGEDITOR="cat \"$EDITED\" > " hg histedit 177f92b77385 2>&1 | fixbundle
75 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
75 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
76 1 out of 1 hunks FAILED -- saving rejects to file e.rej
76 1 out of 1 hunks FAILED -- saving rejects to file e.rej
77 abort: Fix up the change and run hg histedit --continue
77 abort: Fix up the change and run hg histedit --continue
78
78
79 fix up
79 fix up
80 $ echo a > e
80 $ echo a > e
81 $ hg add e
81 $ hg add e
82 $ cat > cat.py <<EOF
82 $ cat > cat.py <<EOF
83 > import sys
83 > import sys
84 > print open(sys.argv[1]).read()
84 > print open(sys.argv[1]).read()
85 > print
85 > print
86 > print
86 > print
87 > EOF
87 > EOF
88 $ HGEDITOR="python cat.py" hg histedit --continue 2>&1 | fixbundle | grep -v '2 files removed'
88 $ HGEDITOR="python cat.py" hg histedit --continue 2>&1 | fixbundle | grep -v '2 files removed'
89 d
89 d
90 ***
90 ***
91 does not commute with e
91 does not commute with e
92
92
93
93
94
94
95 HG: Enter commit message. Lines beginning with 'HG:' are removed.
96 HG: Leave message empty to abort commit.
97 HG: --
98 HG: user: test
99 HG: branch 'default'
100 HG: changed d
101 HG: changed e
102
103
104
105 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
95 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
106 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
96 file e already exists
107 file e already exists
97 1 out of 1 hunks FAILED -- saving rejects to file e.rej
108 1 out of 1 hunks FAILED -- saving rejects to file e.rej
98 abort: Fix up the change and run hg histedit --continue
109 abort: Fix up the change and run hg histedit --continue
99
110
100 just continue this time
111 just continue this time
101 $ hg histedit --continue 2>&1 | fixbundle
112 $ hg histedit --continue 2>&1 | fixbundle
102 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
113 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
103 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
114 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
104
115
105 log after edit
116 log after edit
106 $ hg log --graph
117 $ hg log --graph
107 @ changeset: 4:f768fd60ca34
118 @ changeset: 4:f768fd60ca34
108 | tag: tip
119 | tag: tip
109 | user: test
120 | user: test
110 | date: Thu Jan 01 00:00:00 1970 +0000
121 | date: Thu Jan 01 00:00:00 1970 +0000
111 | summary: f
122 | summary: f
112 |
123 |
113 o changeset: 3:671efe372e33
124 o changeset: 3:671efe372e33
114 | user: test
125 | user: test
115 | date: Thu Jan 01 00:00:00 1970 +0000
126 | date: Thu Jan 01 00:00:00 1970 +0000
116 | summary: d
127 | summary: d
117 |
128 |
118 o changeset: 2:177f92b77385
129 o changeset: 2:177f92b77385
119 | user: test
130 | user: test
120 | date: Thu Jan 01 00:00:00 1970 +0000
131 | date: Thu Jan 01 00:00:00 1970 +0000
121 | summary: c
132 | summary: c
122 |
133 |
123 o changeset: 1:d2ae7f538514
134 o changeset: 1:d2ae7f538514
124 | user: test
135 | user: test
125 | date: Thu Jan 01 00:00:00 1970 +0000
136 | date: Thu Jan 01 00:00:00 1970 +0000
126 | summary: b
137 | summary: b
127 |
138 |
128 o changeset: 0:cb9a9f314b8b
139 o changeset: 0:cb9a9f314b8b
129 user: test
140 user: test
130 date: Thu Jan 01 00:00:00 1970 +0000
141 date: Thu Jan 01 00:00:00 1970 +0000
131 summary: a
142 summary: a
132
143
133
144
134 contents of e
145 contents of e
135 $ hg cat e
146 $ hg cat e
136 a
147 a
137
148
138 manifest
149 manifest
139 $ hg manifest
150 $ hg manifest
140 a
151 a
141 b
152 b
142 c
153 c
143 d
154 d
144 e
155 e
145 f
156 f
146
157
147 $ cd ..
158 $ cd ..
@@ -1,238 +1,249 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 > graphlog=
5 > graphlog=
6 > histedit=
6 > histedit=
7 > EOF
7 > EOF
8
8
9 $ EDITED="$TESTTMP/editedhistory"
9 $ EDITED="$TESTTMP/editedhistory"
10 $ cat > $EDITED <<EOF
10 $ cat > $EDITED <<EOF
11 > pick e860deea161a e
11 > pick e860deea161a e
12 > pick 652413bf663e f
12 > pick 652413bf663e f
13 > fold 177f92b77385 c
13 > fold 177f92b77385 c
14 > pick 055a42cdd887 d
14 > pick 055a42cdd887 d
15 > EOF
15 > EOF
16 $ initrepo ()
16 $ initrepo ()
17 > {
17 > {
18 > hg init r
18 > hg init r
19 > cd r
19 > cd r
20 > for x in a b c d e f ; do
20 > for x in a b c d e f ; do
21 > echo $x > $x
21 > echo $x > $x
22 > hg add $x
22 > hg add $x
23 > hg ci -m $x
23 > hg ci -m $x
24 > done
24 > done
25 > }
25 > }
26
26
27 $ initrepo
27 $ initrepo
28
28
29 log before edit
29 log before edit
30 $ hg log --graph
30 $ hg log --graph
31 @ changeset: 5:652413bf663e
31 @ changeset: 5:652413bf663e
32 | tag: tip
32 | tag: tip
33 | user: test
33 | user: test
34 | date: Thu Jan 01 00:00:00 1970 +0000
34 | date: Thu Jan 01 00:00:00 1970 +0000
35 | summary: f
35 | summary: f
36 |
36 |
37 o changeset: 4:e860deea161a
37 o changeset: 4:e860deea161a
38 | user: test
38 | user: test
39 | date: Thu Jan 01 00:00:00 1970 +0000
39 | date: Thu Jan 01 00:00:00 1970 +0000
40 | summary: e
40 | summary: e
41 |
41 |
42 o changeset: 3:055a42cdd887
42 o changeset: 3:055a42cdd887
43 | user: test
43 | user: test
44 | date: Thu Jan 01 00:00:00 1970 +0000
44 | date: Thu Jan 01 00:00:00 1970 +0000
45 | summary: d
45 | summary: d
46 |
46 |
47 o changeset: 2:177f92b77385
47 o changeset: 2:177f92b77385
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: c
50 | summary: c
51 |
51 |
52 o changeset: 1:d2ae7f538514
52 o changeset: 1:d2ae7f538514
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: b
55 | summary: b
56 |
56 |
57 o changeset: 0:cb9a9f314b8b
57 o changeset: 0:cb9a9f314b8b
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: a
60 summary: a
61
61
62
62
63 edit the history
63 edit the history
64 $ HGEDITOR="cat \"$EDITED\" > " hg histedit 177f92b77385 2>&1 | fixbundle
64 $ HGEDITOR="cat \"$EDITED\" > " hg histedit 177f92b77385 2>&1 | fixbundle
65 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
65 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
66 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
66 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
67 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
67 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
68 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
68 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
69 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
69 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
70 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
70 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
71 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
71
72
72 log after edit
73 log after edit
73 $ hg log --graph
74 $ hg log --graph
74 @ changeset: 4:82b0c1ff1777
75 @ changeset: 4:82b0c1ff1777
75 | tag: tip
76 | tag: tip
76 | user: test
77 | user: test
77 | date: Thu Jan 01 00:00:00 1970 +0000
78 | date: Thu Jan 01 00:00:00 1970 +0000
78 | summary: d
79 | summary: d
79 |
80 |
80 o changeset: 3:150aafb44a91
81 o changeset: 3:150aafb44a91
81 | user: test
82 | user: test
82 | date: Thu Jan 01 00:00:00 1970 +0000
83 | date: Thu Jan 01 00:00:00 1970 +0000
83 | summary: pick e860deea161a e
84 | summary: pick e860deea161a e
84 |
85 |
85 o changeset: 2:493dc0964412
86 o changeset: 2:493dc0964412
86 | user: test
87 | user: test
87 | date: Thu Jan 01 00:00:00 1970 +0000
88 | date: Thu Jan 01 00:00:00 1970 +0000
88 | summary: e
89 | summary: e
89 |
90 |
90 o changeset: 1:d2ae7f538514
91 o changeset: 1:d2ae7f538514
91 | user: test
92 | user: test
92 | date: Thu Jan 01 00:00:00 1970 +0000
93 | date: Thu Jan 01 00:00:00 1970 +0000
93 | summary: b
94 | summary: b
94 |
95 |
95 o changeset: 0:cb9a9f314b8b
96 o changeset: 0:cb9a9f314b8b
96 user: test
97 user: test
97 date: Thu Jan 01 00:00:00 1970 +0000
98 date: Thu Jan 01 00:00:00 1970 +0000
98 summary: a
99 summary: a
99
100
100
101
101 post-fold manifest
102 post-fold manifest
102 $ hg manifest
103 $ hg manifest
103 a
104 a
104 b
105 b
105 c
106 c
106 d
107 d
107 e
108 e
108 f
109 f
109
110
110 $ cd ..
111 $ cd ..
111
112
112 folding and creating no new change doesn't break:
113 folding and creating no new change doesn't break:
113 $ mkdir fold-to-empty-test
114 $ mkdir fold-to-empty-test
114 $ cd fold-to-empty-test
115 $ cd fold-to-empty-test
115 $ hg init
116 $ hg init
116 $ printf "1\n2\n3\n" > file
117 $ printf "1\n2\n3\n" > file
117 $ hg add file
118 $ hg add file
118 $ hg commit -m '1+2+3'
119 $ hg commit -m '1+2+3'
119 $ echo 4 >> file
120 $ echo 4 >> file
120 $ hg commit -m '+4'
121 $ hg commit -m '+4'
121 $ echo 5 >> file
122 $ echo 5 >> file
122 $ hg commit -m '+5'
123 $ hg commit -m '+5'
123 $ echo 6 >> file
124 $ echo 6 >> file
124 $ hg commit -m '+6'
125 $ hg commit -m '+6'
125 $ hg log --graph
126 $ hg log --graph
126 @ changeset: 3:251d831eeec5
127 @ changeset: 3:251d831eeec5
127 | tag: tip
128 | tag: tip
128 | user: test
129 | user: test
129 | date: Thu Jan 01 00:00:00 1970 +0000
130 | date: Thu Jan 01 00:00:00 1970 +0000
130 | summary: +6
131 | summary: +6
131 |
132 |
132 o changeset: 2:888f9082bf99
133 o changeset: 2:888f9082bf99
133 | user: test
134 | user: test
134 | date: Thu Jan 01 00:00:00 1970 +0000
135 | date: Thu Jan 01 00:00:00 1970 +0000
135 | summary: +5
136 | summary: +5
136 |
137 |
137 o changeset: 1:617f94f13c0f
138 o changeset: 1:617f94f13c0f
138 | user: test
139 | user: test
139 | date: Thu Jan 01 00:00:00 1970 +0000
140 | date: Thu Jan 01 00:00:00 1970 +0000
140 | summary: +4
141 | summary: +4
141 |
142 |
142 o changeset: 0:0189ba417d34
143 o changeset: 0:0189ba417d34
143 user: test
144 user: test
144 date: Thu Jan 01 00:00:00 1970 +0000
145 date: Thu Jan 01 00:00:00 1970 +0000
145 summary: 1+2+3
146 summary: 1+2+3
146
147
147
148
148 $ cat > editor.py <<EOF
149 $ cat > editor.py <<EOF
149 > import re, sys
150 > import re, sys
150 > rules = sys.argv[1]
151 > rules = sys.argv[1]
151 > data = open(rules).read()
152 > data = open(rules).read()
152 > data = re.sub(r'pick ([0-9a-f]{12} 2 \+5)', r'drop \1', data)
153 > data = re.sub(r'pick ([0-9a-f]{12} 2 \+5)', r'drop \1', data)
153 > data = re.sub(r'pick ([0-9a-f]{12} 2 \+6)', r'fold \1', data)
154 > data = re.sub(r'pick ([0-9a-f]{12} 2 \+6)', r'fold \1', data)
154 > open(rules, 'w').write(data)
155 > open(rules, 'w').write(data)
155 > EOF
156 > EOF
156
157
157 $ HGEDITOR='python editor.py' hg histedit 1
158 $ HGEDITOR='python editor.py' hg histedit 1
158 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
159 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
159 patching file file
160 patching file file
160 Hunk #1 FAILED at 2
161 Hunk #1 FAILED at 2
161 1 out of 1 hunks FAILED -- saving rejects to file file.rej
162 1 out of 1 hunks FAILED -- saving rejects to file file.rej
162 abort: Fix up the change and run hg histedit --continue
163 abort: Fix up the change and run hg histedit --continue
163 [255]
164 [255]
164 There were conflicts, but we'll continue without resolving. This
165 There were conflicts, but we'll continue without resolving. This
165 should effectively drop the changes from +6.
166 should effectively drop the changes from +6.
166 $ hg status
167 $ hg status
167 ? editor.py
168 ? editor.py
168 ? file.rej
169 ? file.rej
169 $ hg histedit --continue
170 $ hg histedit --continue
170 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
171 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
171 saved backup bundle to $TESTTMP/*-backup.hg (glob)
172 saved backup bundle to $TESTTMP/*-backup.hg (glob)
172 $ hg log --graph
173 $ hg log --graph
173 @ changeset: 1:617f94f13c0f
174 @ changeset: 1:617f94f13c0f
174 | tag: tip
175 | tag: tip
175 | user: test
176 | user: test
176 | date: Thu Jan 01 00:00:00 1970 +0000
177 | date: Thu Jan 01 00:00:00 1970 +0000
177 | summary: +4
178 | summary: +4
178 |
179 |
179 o changeset: 0:0189ba417d34
180 o changeset: 0:0189ba417d34
180 user: test
181 user: test
181 date: Thu Jan 01 00:00:00 1970 +0000
182 date: Thu Jan 01 00:00:00 1970 +0000
182 summary: 1+2+3
183 summary: 1+2+3
183
184
184
185
185 $ cd ..
186 $ cd ..
186
187
187 Test corner case where folded revision is separated from its parent by a
188 Test corner case where folded revision is separated from its parent by a
188 dropped revision.
189 dropped revision.
189
190
190
191
191 $ hg init fold-with-dropped
192 $ hg init fold-with-dropped
192 $ cd fold-with-dropped
193 $ cd fold-with-dropped
193 $ printf "1\n2\n3\n" > file
194 $ printf "1\n2\n3\n" > file
194 $ hg commit -Am '1+2+3'
195 $ hg commit -Am '1+2+3'
195 adding file
196 adding file
196 $ echo 4 >> file
197 $ echo 4 >> file
197 $ hg commit -m '+4'
198 $ hg commit -m '+4'
198 $ echo 5 >> file
199 $ echo 5 >> file
199 $ hg commit -m '+5'
200 $ hg commit -m '+5'
200 $ echo 6 >> file
201 $ echo 6 >> file
201 $ hg commit -m '+6'
202 $ hg commit -m '+6'
202 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n'
203 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n'
203 @ 3:251d831eeec5 +6
204 @ 3:251d831eeec5 +6
204 |
205 |
205 o 2:888f9082bf99 +5
206 o 2:888f9082bf99 +5
206 |
207 |
207 o 1:617f94f13c0f +4
208 o 1:617f94f13c0f +4
208 |
209 |
209 o 0:0189ba417d34 1+2+3
210 o 0:0189ba417d34 1+2+3
210
211
211 $ EDITED="$TESTTMP/editcommands"
212 $ EDITED="$TESTTMP/editcommands"
212 $ cat > $EDITED <<EOF
213 $ cat > $EDITED <<EOF
213 > pick 617f94f13c0f 1 +4
214 > pick 617f94f13c0f 1 +4
214 > drop 888f9082bf99 2 +5
215 > drop 888f9082bf99 2 +5
215 > fold 251d831eeec5 3 +6
216 > fold 251d831eeec5 3 +6
216 > EOF
217 > EOF
217 $ HGEDITOR="cat $EDITED >" hg histedit 1
218 $ HGEDITOR="cat $EDITED >" hg histedit 1
218 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
219 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
219 patching file file
220 patching file file
220 Hunk #1 FAILED at 2
221 Hunk #1 FAILED at 2
221 1 out of 1 hunks FAILED -- saving rejects to file file.rej
222 1 out of 1 hunks FAILED -- saving rejects to file file.rej
222 abort: Fix up the change and run hg histedit --continue
223 abort: Fix up the change and run hg histedit --continue
223 [255]
224 [255]
224 $ echo 5 >> file
225 $ echo 5 >> file
225 $ hg commit -m '+5.2'
226 $ hg commit -m '+5.2'
226 created new head
227 created new head
227 $ echo 6 >> file
228 $ echo 6 >> file
228 $ HGEDITOR=cat hg histedit --continue
229 $ HGEDITOR=cat hg histedit --continue
229 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
230 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
230 +4
231 +4
231 ***
232 ***
232 +5.2
233 +5.2
233 ***
234 ***
234 +6
235 +6
236
237
238
239 HG: Enter commit message. Lines beginning with 'HG:' are removed.
240 HG: Leave message empty to abort commit.
241 HG: --
242 HG: user: test
243 HG: branch 'default'
244 HG: changed file
245 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
235 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
246 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
236 saved backup bundle to $TESTTMP/fold-with-dropped/.hg/strip-backup/617f94f13c0f-backup.hg (glob)
247 saved backup bundle to $TESTTMP/fold-with-dropped/.hg/strip-backup/617f94f13c0f-backup.hg (glob)
237 $ cd ..
248 $ cd ..
238
249
General Comments 0
You need to be logged in to leave comments. Login now