##// END OF EJS Templates
histedit: move all arguments checks to the beginning of the command...
Pierre-Yves David -
r19020:12c06686 default
parent child Browse files
Show More
@@ -1,826 +1,845 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 # 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 c561b4e977df and 7c2fd3b9020c
50 # Edit history between c561b4e977df 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 histedit-ed example repository above and add four more
111 If we clone the histedit-ed example repository above and add four more
112 changes, such that we have the following history::
112 changes, such that 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 os
145 import os
146 import sys
146 import sys
147
147
148 from mercurial import cmdutil
148 from mercurial import cmdutil
149 from mercurial import discovery
149 from mercurial import discovery
150 from mercurial import error
150 from mercurial import error
151 from mercurial import copies
151 from mercurial import copies
152 from mercurial import context
152 from mercurial import context
153 from mercurial import hg
153 from mercurial import hg
154 from mercurial import lock as lockmod
154 from mercurial import lock as lockmod
155 from mercurial import node
155 from mercurial import node
156 from mercurial import repair
156 from mercurial import repair
157 from mercurial import scmutil
157 from mercurial import scmutil
158 from mercurial import util
158 from mercurial import util
159 from mercurial import obsolete
159 from mercurial import obsolete
160 from mercurial import merge as mergemod
160 from mercurial import merge as mergemod
161 from mercurial.i18n import _
161 from mercurial.i18n import _
162
162
163 cmdtable = {}
163 cmdtable = {}
164 command = cmdutil.command(cmdtable)
164 command = cmdutil.command(cmdtable)
165
165
166 testedwith = 'internal'
166 testedwith = 'internal'
167
167
168 # i18n: command names and abbreviations must remain untranslated
168 # i18n: command names and abbreviations must remain untranslated
169 editcomment = _("""# Edit history between %s and %s
169 editcomment = _("""# Edit history between %s and %s
170 #
170 #
171 # Commands:
171 # Commands:
172 # p, pick = use commit
172 # p, pick = use commit
173 # e, edit = use commit, but stop for amending
173 # e, edit = use commit, but stop for amending
174 # 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)
175 # d, drop = remove commit from history
175 # d, drop = remove commit from history
176 # m, mess = edit message without changing commit content
176 # m, mess = edit message without changing commit content
177 #
177 #
178 """)
178 """)
179
179
180 def commitfuncfor(repo, src):
180 def commitfuncfor(repo, src):
181 """Build a commit function for the replacement of <src>
181 """Build a commit function for the replacement of <src>
182
182
183 This function ensure we apply the same treatment to all changesets.
183 This function ensure we apply the same treatment to all changesets.
184
184
185 - Add a 'histedit_source' entry in extra.
185 - Add a 'histedit_source' entry in extra.
186
186
187 Note that fold have its own separated logic because its handling is a bit
187 Note that fold have its own separated logic because its handling is a bit
188 different and not easily factored out of the fold method.
188 different and not easily factored out of the fold method.
189 """
189 """
190 phasemin = src.phase()
190 phasemin = src.phase()
191 def commitfunc(**kwargs):
191 def commitfunc(**kwargs):
192 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
192 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
193 try:
193 try:
194 repo.ui.setconfig('phases', 'new-commit', phasemin)
194 repo.ui.setconfig('phases', 'new-commit', phasemin)
195 extra = kwargs.get('extra', {}).copy()
195 extra = kwargs.get('extra', {}).copy()
196 extra['histedit_source'] = src.hex()
196 extra['histedit_source'] = src.hex()
197 kwargs['extra'] = extra
197 kwargs['extra'] = extra
198 return repo.commit(**kwargs)
198 return repo.commit(**kwargs)
199 finally:
199 finally:
200 repo.ui.restoreconfig(phasebackup)
200 repo.ui.restoreconfig(phasebackup)
201 return commitfunc
201 return commitfunc
202
202
203
203
204
204
205 def applychanges(ui, repo, ctx, opts):
205 def applychanges(ui, repo, ctx, opts):
206 """Merge changeset from ctx (only) in the current working directory"""
206 """Merge changeset from ctx (only) in the current working directory"""
207 wcpar = repo.dirstate.parents()[0]
207 wcpar = repo.dirstate.parents()[0]
208 if ctx.p1().node() == wcpar:
208 if ctx.p1().node() == wcpar:
209 # edition ar "in place" we do not need to make any merge,
209 # edition ar "in place" we do not need to make any merge,
210 # just applies changes on parent for edition
210 # just applies changes on parent for edition
211 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
211 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
212 stats = None
212 stats = None
213 else:
213 else:
214 try:
214 try:
215 # ui.forcemerge is an internal variable, do not document
215 # ui.forcemerge is an internal variable, do not document
216 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
216 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
217 stats = mergemod.update(repo, ctx.node(), True, True, False,
217 stats = mergemod.update(repo, ctx.node(), True, True, False,
218 ctx.p1().node())
218 ctx.p1().node())
219 finally:
219 finally:
220 repo.ui.setconfig('ui', 'forcemerge', '')
220 repo.ui.setconfig('ui', 'forcemerge', '')
221 repo.setparents(wcpar, node.nullid)
221 repo.setparents(wcpar, node.nullid)
222 repo.dirstate.write()
222 repo.dirstate.write()
223 # fix up dirstate for copies and renames
223 # fix up dirstate for copies and renames
224 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
224 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
225 return stats
225 return stats
226
226
227 def collapse(repo, first, last, commitopts):
227 def collapse(repo, first, last, commitopts):
228 """collapse the set of revisions from first to last as new one.
228 """collapse the set of revisions from first to last as new one.
229
229
230 Expected commit options are:
230 Expected commit options are:
231 - message
231 - message
232 - date
232 - date
233 - username
233 - username
234 Commit message is edited in all cases.
234 Commit message is edited in all cases.
235
235
236 This function works in memory."""
236 This function works in memory."""
237 ctxs = list(repo.set('%d::%d', first, last))
237 ctxs = list(repo.set('%d::%d', first, last))
238 if not ctxs:
238 if not ctxs:
239 return None
239 return None
240 base = first.parents()[0]
240 base = first.parents()[0]
241
241
242 # commit a new version of the old changeset, including the update
242 # commit a new version of the old changeset, including the update
243 # collect all files which might be affected
243 # collect all files which might be affected
244 files = set()
244 files = set()
245 for ctx in ctxs:
245 for ctx in ctxs:
246 files.update(ctx.files())
246 files.update(ctx.files())
247
247
248 # Recompute copies (avoid recording a -> b -> a)
248 # Recompute copies (avoid recording a -> b -> a)
249 copied = copies.pathcopies(first, last)
249 copied = copies.pathcopies(first, last)
250
250
251 # prune files which were reverted by the updates
251 # prune files which were reverted by the updates
252 def samefile(f):
252 def samefile(f):
253 if f in last.manifest():
253 if f in last.manifest():
254 a = last.filectx(f)
254 a = last.filectx(f)
255 if f in base.manifest():
255 if f in base.manifest():
256 b = base.filectx(f)
256 b = base.filectx(f)
257 return (a.data() == b.data()
257 return (a.data() == b.data()
258 and a.flags() == b.flags())
258 and a.flags() == b.flags())
259 else:
259 else:
260 return False
260 return False
261 else:
261 else:
262 return f not in base.manifest()
262 return f not in base.manifest()
263 files = [f for f in files if not samefile(f)]
263 files = [f for f in files if not samefile(f)]
264 # commit version of these files as defined by head
264 # commit version of these files as defined by head
265 headmf = last.manifest()
265 headmf = last.manifest()
266 def filectxfn(repo, ctx, path):
266 def filectxfn(repo, ctx, path):
267 if path in headmf:
267 if path in headmf:
268 fctx = last[path]
268 fctx = last[path]
269 flags = fctx.flags()
269 flags = fctx.flags()
270 mctx = context.memfilectx(fctx.path(), fctx.data(),
270 mctx = context.memfilectx(fctx.path(), fctx.data(),
271 islink='l' in flags,
271 islink='l' in flags,
272 isexec='x' in flags,
272 isexec='x' in flags,
273 copied=copied.get(path))
273 copied=copied.get(path))
274 return mctx
274 return mctx
275 raise IOError()
275 raise IOError()
276
276
277 if commitopts.get('message'):
277 if commitopts.get('message'):
278 message = commitopts['message']
278 message = commitopts['message']
279 else:
279 else:
280 message = first.description()
280 message = first.description()
281 user = commitopts.get('user')
281 user = commitopts.get('user')
282 date = commitopts.get('date')
282 date = commitopts.get('date')
283 extra = commitopts.get('extra')
283 extra = commitopts.get('extra')
284
284
285 parents = (first.p1().node(), first.p2().node())
285 parents = (first.p1().node(), first.p2().node())
286 new = context.memctx(repo,
286 new = context.memctx(repo,
287 parents=parents,
287 parents=parents,
288 text=message,
288 text=message,
289 files=files,
289 files=files,
290 filectxfn=filectxfn,
290 filectxfn=filectxfn,
291 user=user,
291 user=user,
292 date=date,
292 date=date,
293 extra=extra)
293 extra=extra)
294 new._text = cmdutil.commitforceeditor(repo, new, [])
294 new._text = cmdutil.commitforceeditor(repo, new, [])
295 return repo.commitctx(new)
295 return repo.commitctx(new)
296
296
297 def pick(ui, repo, ctx, ha, opts):
297 def pick(ui, repo, ctx, ha, opts):
298 oldctx = repo[ha]
298 oldctx = repo[ha]
299 if oldctx.parents()[0] == ctx:
299 if oldctx.parents()[0] == ctx:
300 ui.debug('node %s unchanged\n' % ha)
300 ui.debug('node %s unchanged\n' % ha)
301 return oldctx, []
301 return oldctx, []
302 hg.update(repo, ctx.node())
302 hg.update(repo, ctx.node())
303 stats = applychanges(ui, repo, oldctx, opts)
303 stats = applychanges(ui, repo, oldctx, opts)
304 if stats and stats[3] > 0:
304 if stats and stats[3] > 0:
305 raise error.InterventionRequired(_('Fix up the change and run '
305 raise error.InterventionRequired(_('Fix up the change and run '
306 'hg histedit --continue'))
306 'hg histedit --continue'))
307 # drop the second merge parent
307 # drop the second merge parent
308 commit = commitfuncfor(repo, oldctx)
308 commit = commitfuncfor(repo, oldctx)
309 n = commit(text=oldctx.description(), user=oldctx.user(),
309 n = commit(text=oldctx.description(), user=oldctx.user(),
310 date=oldctx.date(), extra=oldctx.extra())
310 date=oldctx.date(), extra=oldctx.extra())
311 if n is None:
311 if n is None:
312 ui.warn(_('%s: empty changeset\n')
312 ui.warn(_('%s: empty changeset\n')
313 % node.hex(ha))
313 % node.hex(ha))
314 return ctx, []
314 return ctx, []
315 new = repo[n]
315 new = repo[n]
316 return new, [(oldctx.node(), (n,))]
316 return new, [(oldctx.node(), (n,))]
317
317
318
318
319 def edit(ui, repo, ctx, ha, opts):
319 def edit(ui, repo, ctx, ha, opts):
320 oldctx = repo[ha]
320 oldctx = repo[ha]
321 hg.update(repo, ctx.node())
321 hg.update(repo, ctx.node())
322 applychanges(ui, repo, oldctx, opts)
322 applychanges(ui, repo, oldctx, opts)
323 raise error.InterventionRequired(
323 raise error.InterventionRequired(
324 _('Make changes as needed, you may commit or record as needed now.\n'
324 _('Make changes as needed, you may commit or record as needed now.\n'
325 'When you are finished, run hg histedit --continue to resume.'))
325 'When you are finished, run hg histedit --continue to resume.'))
326
326
327 def fold(ui, repo, ctx, ha, opts):
327 def fold(ui, repo, ctx, ha, opts):
328 oldctx = repo[ha]
328 oldctx = repo[ha]
329 hg.update(repo, ctx.node())
329 hg.update(repo, ctx.node())
330 stats = applychanges(ui, repo, oldctx, opts)
330 stats = applychanges(ui, repo, oldctx, opts)
331 if stats and stats[3] > 0:
331 if stats and stats[3] > 0:
332 raise error.InterventionRequired(
332 raise error.InterventionRequired(
333 _('Fix up the change and run hg histedit --continue'))
333 _('Fix up the change and run hg histedit --continue'))
334 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
334 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
335 date=oldctx.date(), extra=oldctx.extra())
335 date=oldctx.date(), extra=oldctx.extra())
336 if n is None:
336 if n is None:
337 ui.warn(_('%s: empty changeset')
337 ui.warn(_('%s: empty changeset')
338 % node.hex(ha))
338 % node.hex(ha))
339 return ctx, []
339 return ctx, []
340 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
340 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
341
341
342 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
342 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
343 parent = ctx.parents()[0].node()
343 parent = ctx.parents()[0].node()
344 hg.update(repo, parent)
344 hg.update(repo, parent)
345 ### prepare new commit data
345 ### prepare new commit data
346 commitopts = opts.copy()
346 commitopts = opts.copy()
347 # username
347 # username
348 if ctx.user() == oldctx.user():
348 if ctx.user() == oldctx.user():
349 username = ctx.user()
349 username = ctx.user()
350 else:
350 else:
351 username = ui.username()
351 username = ui.username()
352 commitopts['user'] = username
352 commitopts['user'] = username
353 # commit message
353 # commit message
354 newmessage = '\n***\n'.join(
354 newmessage = '\n***\n'.join(
355 [ctx.description()] +
355 [ctx.description()] +
356 [repo[r].description() for r in internalchanges] +
356 [repo[r].description() for r in internalchanges] +
357 [oldctx.description()]) + '\n'
357 [oldctx.description()]) + '\n'
358 commitopts['message'] = newmessage
358 commitopts['message'] = newmessage
359 # date
359 # date
360 commitopts['date'] = max(ctx.date(), oldctx.date())
360 commitopts['date'] = max(ctx.date(), oldctx.date())
361 extra = ctx.extra().copy()
361 extra = ctx.extra().copy()
362 # histedit_source
362 # histedit_source
363 # note: ctx is likely a temporary commit but that the best we can do here
363 # note: ctx is likely a temporary commit but that the best we can do here
364 # This is sufficient to solve issue3681 anyway
364 # This is sufficient to solve issue3681 anyway
365 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
365 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
366 commitopts['extra'] = extra
366 commitopts['extra'] = extra
367 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
367 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
368 try:
368 try:
369 phasemin = max(ctx.phase(), oldctx.phase())
369 phasemin = max(ctx.phase(), oldctx.phase())
370 repo.ui.setconfig('phases', 'new-commit', phasemin)
370 repo.ui.setconfig('phases', 'new-commit', phasemin)
371 n = collapse(repo, ctx, repo[newnode], commitopts)
371 n = collapse(repo, ctx, repo[newnode], commitopts)
372 finally:
372 finally:
373 repo.ui.restoreconfig(phasebackup)
373 repo.ui.restoreconfig(phasebackup)
374 if n is None:
374 if n is None:
375 return ctx, []
375 return ctx, []
376 hg.update(repo, n)
376 hg.update(repo, n)
377 replacements = [(oldctx.node(), (newnode,)),
377 replacements = [(oldctx.node(), (newnode,)),
378 (ctx.node(), (n,)),
378 (ctx.node(), (n,)),
379 (newnode, (n,)),
379 (newnode, (n,)),
380 ]
380 ]
381 for ich in internalchanges:
381 for ich in internalchanges:
382 replacements.append((ich, (n,)))
382 replacements.append((ich, (n,)))
383 return repo[n], replacements
383 return repo[n], replacements
384
384
385 def drop(ui, repo, ctx, ha, opts):
385 def drop(ui, repo, ctx, ha, opts):
386 return ctx, [(repo[ha].node(), ())]
386 return ctx, [(repo[ha].node(), ())]
387
387
388
388
389 def message(ui, repo, ctx, ha, opts):
389 def message(ui, repo, ctx, ha, opts):
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 message = oldctx.description() + '\n'
396 message = oldctx.description() + '\n'
397 message = ui.edit(message, ui.username())
397 message = ui.edit(message, ui.username())
398 commit = commitfuncfor(repo, oldctx)
398 commit = commitfuncfor(repo, oldctx)
399 new = commit(text=message, user=oldctx.user(), date=oldctx.date(),
399 new = commit(text=message, user=oldctx.user(), date=oldctx.date(),
400 extra=oldctx.extra())
400 extra=oldctx.extra())
401 newctx = repo[new]
401 newctx = repo[new]
402 if oldctx.node() != newctx.node():
402 if oldctx.node() != newctx.node():
403 return newctx, [(oldctx.node(), (new,))]
403 return newctx, [(oldctx.node(), (new,))]
404 # We didn't make an edit, so just indicate no replaced nodes
404 # We didn't make an edit, so just indicate no replaced nodes
405 return newctx, []
405 return newctx, []
406
406
407 actiontable = {'p': pick,
407 actiontable = {'p': pick,
408 'pick': pick,
408 'pick': pick,
409 'e': edit,
409 'e': edit,
410 'edit': edit,
410 'edit': edit,
411 'f': fold,
411 'f': fold,
412 'fold': fold,
412 'fold': fold,
413 'd': drop,
413 'd': drop,
414 'drop': drop,
414 'drop': drop,
415 'm': message,
415 'm': message,
416 'mess': message,
416 'mess': message,
417 }
417 }
418
418
419 @command('histedit',
419 @command('histedit',
420 [('', 'commands', '',
420 [('', 'commands', '',
421 _('Read history edits from the specified file.')),
421 _('Read history edits from the specified file.')),
422 ('c', 'continue', False, _('continue an edit already in progress')),
422 ('c', 'continue', False, _('continue an edit already in progress')),
423 ('k', 'keep', False,
423 ('k', 'keep', False,
424 _("don't strip old nodes after edit is complete")),
424 _("don't strip old nodes after edit is complete")),
425 ('', 'abort', False, _('abort an edit in progress')),
425 ('', 'abort', False, _('abort an edit in progress')),
426 ('o', 'outgoing', False, _('changesets not found in destination')),
426 ('o', 'outgoing', False, _('changesets not found in destination')),
427 ('f', 'force', False,
427 ('f', 'force', False,
428 _('force outgoing even for unrelated repositories')),
428 _('force outgoing even for unrelated repositories')),
429 ('r', 'rev', [], _('first revision to be edited'))],
429 ('r', 'rev', [], _('first revision to be edited'))],
430 _("[PARENT]"))
430 _("[PARENT]"))
431 def histedit(ui, repo, *parent, **opts):
431 def histedit(ui, repo, *freeargs, **opts):
432 """interactively edit changeset history
432 """interactively edit changeset history
433 """
433 """
434 # TODO only abort if we try and histedit mq patches, not just
434 # TODO only abort if we try and histedit mq patches, not just
435 # blanket if mq patches are applied somewhere
435 # blanket if mq patches are applied somewhere
436 mq = getattr(repo, 'mq', None)
436 mq = getattr(repo, 'mq', None)
437 if mq and mq.applied:
437 if mq and mq.applied:
438 raise util.Abort(_('source has mq patches applied'))
438 raise util.Abort(_('source has mq patches applied'))
439
439
440 parent = list(parent) + opts.get('rev', [])
440 # basic argument incompatibility processing
441 if opts.get('outgoing'):
441 outg = opts.get('outgoing')
442 if len(parent) > 1:
442 cont = opts.get('continue')
443 abort = opts.get('abort')
444 force = opts.get('force')
445 rules = opts.get('commands', '')
446 revs = opts.get('rev', [])
447 goal = 'new' # This invocation goal, in new, continue, abort
448 if force and not outg:
449 raise util.Abort(_('--force only allowed with --outgoing'))
450 if cont:
451 if util.any((outg, abort, revs, freeargs, rules)):
452 raise util.Abort(_('no arguments allowed with --continue'))
453 goal = 'continue'
454 elif abort:
455 if util.any((outg, revs, freeargs, rules)):
456 raise util.Abort(_('no arguments allowed with --abort'))
457 goal = 'abort'
458 else:
459 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
460 raise util.Abort(_('history edit already in progress, try '
461 '--continue or --abort'))
462 if outg:
463 if revs:
464 raise util.Abort(_('no revisions allowed with --outgoing'))
465 if len(freeargs) > 1:
443 raise util.Abort(
466 raise util.Abort(
444 _('only one repo argument allowed with --outgoing'))
467 _('only one repo argument allowed with --outgoing'))
445 elif parent:
468 else:
446 parent = parent[0]
469 parent = list(freeargs) + opts.get('rev', [])
470 if len(parent) != 1:
471 raise util.Abort(
472 _('histedit requires exactly one parent revision'))
473
474 if opts.get('outgoing'):
475 if freeargs:
476 parent = freeargs[0]
447
477
448 dest = ui.expandpath(parent or 'default-push', parent or 'default')
478 dest = ui.expandpath(parent or 'default-push', parent or 'default')
449 dest, revs = hg.parseurl(dest, None)[:2]
479 dest, revs = hg.parseurl(dest, None)[:2]
450 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
480 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
451
481
452 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
482 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
453 other = hg.peer(repo, opts, dest)
483 other = hg.peer(repo, opts, dest)
454
484
455 if revs:
485 if revs:
456 revs = [repo.lookup(rev) for rev in revs]
486 revs = [repo.lookup(rev) for rev in revs]
457
487
458 # hexlify nodes from outgoing, because we're going to parse
488 # hexlify nodes from outgoing, because we're going to parse
459 # parent[0] using revsingle below, and if the binary hash
489 # parent[0] using revsingle below, and if the binary hash
460 # contains special revset characters like ":" the revset
490 # contains special revset characters like ":" the revset
461 # parser can choke.
491 # parser can choke.
462 parent = [node.hex(n) for n in discovery.findcommonoutgoing(
492 parent = [node.hex(n) for n in discovery.findcommonoutgoing(
463 repo, other, revs, force=opts.get('force')).missing[0:1]]
493 repo, other, revs, force=force).missing[0:1]]
464 else:
494 if not parent:
465 if opts.get('force'):
495 raise util.Abort(_('no outgoing ancestors'))
466 raise util.Abort(_('--force only allowed with --outgoing'))
467
496
468 if opts.get('continue', False):
497 if goal == 'continue':
469 if len(parent) != 0:
470 raise util.Abort(_('no arguments allowed with --continue'))
471 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
498 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
472 currentparent, wantnull = repo.dirstate.parents()
499 currentparent, wantnull = repo.dirstate.parents()
473 parentctx = repo[parentctxnode]
500 parentctx = repo[parentctxnode]
474 parentctx, repl = bootstrapcontinue(ui, repo, parentctx, rules, opts)
501 parentctx, repl = bootstrapcontinue(ui, repo, parentctx, rules, opts)
475 replacements.extend(repl)
502 replacements.extend(repl)
476 elif opts.get('abort', False):
503 elif goal == 'abort':
477 if len(parent) != 0:
478 raise util.Abort(_('no arguments allowed with --abort'))
479 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
504 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
480 mapping, tmpnodes, leafs, _ntm = processreplacement(repo, replacements)
505 mapping, tmpnodes, leafs, _ntm = processreplacement(repo, replacements)
481 ui.debug('restore wc to old parent %s\n' % node.short(topmost))
506 ui.debug('restore wc to old parent %s\n' % node.short(topmost))
482 hg.clean(repo, topmost)
507 hg.clean(repo, topmost)
483 cleanupnode(ui, repo, 'created', tmpnodes)
508 cleanupnode(ui, repo, 'created', tmpnodes)
484 cleanupnode(ui, repo, 'temp', leafs)
509 cleanupnode(ui, repo, 'temp', leafs)
485 os.unlink(os.path.join(repo.path, 'histedit-state'))
510 os.unlink(os.path.join(repo.path, 'histedit-state'))
486 return
511 return
487 else:
512 else:
488 cmdutil.bailifchanged(repo)
513 cmdutil.bailifchanged(repo)
489 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
490 raise util.Abort(_('history edit already in progress, try '
491 '--continue or --abort'))
492
514
493 topmost, empty = repo.dirstate.parents()
515 topmost, empty = repo.dirstate.parents()
494
516
495 if len(parent) != 1:
496 raise util.Abort(_('histedit requires exactly one parent revision'))
497 parent = scmutil.revsingle(repo, parent[0]).node()
517 parent = scmutil.revsingle(repo, parent[0]).node()
498
518
499 keep = opts.get('keep', False)
519 keep = opts.get('keep', False)
500 revs = between(repo, parent, topmost, keep)
520 revs = between(repo, parent, topmost, keep)
501 if not revs:
521 if not revs:
502 raise util.Abort(_('%s is not an ancestor of working directory') %
522 raise util.Abort(_('%s is not an ancestor of working directory') %
503 node.short(parent))
523 node.short(parent))
504
524
505 ctxs = [repo[r] for r in revs]
525 ctxs = [repo[r] for r in revs]
506 rules = opts.get('commands', '')
507 if not rules:
526 if not rules:
508 rules = '\n'.join([makedesc(c) for c in ctxs])
527 rules = '\n'.join([makedesc(c) for c in ctxs])
509 rules += '\n\n'
528 rules += '\n\n'
510 rules += editcomment % (node.short(parent), node.short(topmost))
529 rules += editcomment % (node.short(parent), node.short(topmost))
511 rules = ui.edit(rules, ui.username())
530 rules = ui.edit(rules, ui.username())
512 # Save edit rules in .hg/histedit-last-edit.txt in case
531 # Save edit rules in .hg/histedit-last-edit.txt in case
513 # the user needs to ask for help after something
532 # the user needs to ask for help after something
514 # surprising happens.
533 # surprising happens.
515 f = open(repo.join('histedit-last-edit.txt'), 'w')
534 f = open(repo.join('histedit-last-edit.txt'), 'w')
516 f.write(rules)
535 f.write(rules)
517 f.close()
536 f.close()
518 else:
537 else:
519 if rules == '-':
538 if rules == '-':
520 f = sys.stdin
539 f = sys.stdin
521 else:
540 else:
522 f = open(rules)
541 f = open(rules)
523 rules = f.read()
542 rules = f.read()
524 f.close()
543 f.close()
525 rules = [l for l in (r.strip() for r in rules.splitlines())
544 rules = [l for l in (r.strip() for r in rules.splitlines())
526 if l and not l[0] == '#']
545 if l and not l[0] == '#']
527 rules = verifyrules(rules, repo, ctxs)
546 rules = verifyrules(rules, repo, ctxs)
528
547
529 parentctx = repo[parent].parents()[0]
548 parentctx = repo[parent].parents()[0]
530 keep = opts.get('keep', False)
549 keep = opts.get('keep', False)
531 replacements = []
550 replacements = []
532
551
533
552
534 while rules:
553 while rules:
535 writestate(repo, parentctx.node(), rules, keep, topmost, replacements)
554 writestate(repo, parentctx.node(), rules, keep, topmost, replacements)
536 action, ha = rules.pop(0)
555 action, ha = rules.pop(0)
537 ui.debug('histedit: processing %s %s\n' % (action, ha))
556 ui.debug('histedit: processing %s %s\n' % (action, ha))
538 actfunc = actiontable[action]
557 actfunc = actiontable[action]
539 parentctx, replacement_ = actfunc(ui, repo, parentctx, ha, opts)
558 parentctx, replacement_ = actfunc(ui, repo, parentctx, ha, opts)
540 replacements.extend(replacement_)
559 replacements.extend(replacement_)
541
560
542 hg.update(repo, parentctx.node())
561 hg.update(repo, parentctx.node())
543
562
544 mapping, tmpnodes, created, ntm = processreplacement(repo, replacements)
563 mapping, tmpnodes, created, ntm = processreplacement(repo, replacements)
545 if mapping:
564 if mapping:
546 for prec, succs in mapping.iteritems():
565 for prec, succs in mapping.iteritems():
547 if not succs:
566 if not succs:
548 ui.debug('histedit: %s is dropped\n' % node.short(prec))
567 ui.debug('histedit: %s is dropped\n' % node.short(prec))
549 else:
568 else:
550 ui.debug('histedit: %s is replaced by %s\n' % (
569 ui.debug('histedit: %s is replaced by %s\n' % (
551 node.short(prec), node.short(succs[0])))
570 node.short(prec), node.short(succs[0])))
552 if len(succs) > 1:
571 if len(succs) > 1:
553 m = 'histedit: %s'
572 m = 'histedit: %s'
554 for n in succs[1:]:
573 for n in succs[1:]:
555 ui.debug(m % node.short(n))
574 ui.debug(m % node.short(n))
556
575
557 if not keep:
576 if not keep:
558 if mapping:
577 if mapping:
559 movebookmarks(ui, repo, mapping, topmost, ntm)
578 movebookmarks(ui, repo, mapping, topmost, ntm)
560 # TODO update mq state
579 # TODO update mq state
561 if obsolete._enabled:
580 if obsolete._enabled:
562 markers = []
581 markers = []
563 # sort by revision number because it sound "right"
582 # sort by revision number because it sound "right"
564 for prec in sorted(mapping, key=repo.changelog.rev):
583 for prec in sorted(mapping, key=repo.changelog.rev):
565 succs = mapping[prec]
584 succs = mapping[prec]
566 markers.append((repo[prec],
585 markers.append((repo[prec],
567 tuple(repo[s] for s in succs)))
586 tuple(repo[s] for s in succs)))
568 if markers:
587 if markers:
569 obsolete.createmarkers(repo, markers)
588 obsolete.createmarkers(repo, markers)
570 else:
589 else:
571 cleanupnode(ui, repo, 'replaced', mapping)
590 cleanupnode(ui, repo, 'replaced', mapping)
572
591
573 cleanupnode(ui, repo, 'temp', tmpnodes)
592 cleanupnode(ui, repo, 'temp', tmpnodes)
574 os.unlink(os.path.join(repo.path, 'histedit-state'))
593 os.unlink(os.path.join(repo.path, 'histedit-state'))
575 if os.path.exists(repo.sjoin('undo')):
594 if os.path.exists(repo.sjoin('undo')):
576 os.unlink(repo.sjoin('undo'))
595 os.unlink(repo.sjoin('undo'))
577
596
578
597
579 def bootstrapcontinue(ui, repo, parentctx, rules, opts):
598 def bootstrapcontinue(ui, repo, parentctx, rules, opts):
580 action, currentnode = rules.pop(0)
599 action, currentnode = rules.pop(0)
581 ctx = repo[currentnode]
600 ctx = repo[currentnode]
582 # is there any new commit between the expected parent and "."
601 # is there any new commit between the expected parent and "."
583 #
602 #
584 # note: does not take non linear new change in account (but previous
603 # note: does not take non linear new change in account (but previous
585 # implementation didn't used them anyway (issue3655)
604 # implementation didn't used them anyway (issue3655)
586 newchildren = [c.node() for c in repo.set('(%d::.)', parentctx)]
605 newchildren = [c.node() for c in repo.set('(%d::.)', parentctx)]
587 if parentctx.node() != node.nullid:
606 if parentctx.node() != node.nullid:
588 if not newchildren:
607 if not newchildren:
589 # `parentctxnode` should match but no result. This means that
608 # `parentctxnode` should match but no result. This means that
590 # currentnode is not a descendant from parentctxnode.
609 # currentnode is not a descendant from parentctxnode.
591 msg = _('%s is not an ancestor of working directory')
610 msg = _('%s is not an ancestor of working directory')
592 hint = _('update to %s or descendant and run "hg histedit '
611 hint = _('update to %s or descendant and run "hg histedit '
593 '--continue" again') % parentctx
612 '--continue" again') % parentctx
594 raise util.Abort(msg % parentctx, hint=hint)
613 raise util.Abort(msg % parentctx, hint=hint)
595 newchildren.pop(0) # remove parentctxnode
614 newchildren.pop(0) # remove parentctxnode
596 # Commit dirty working directory if necessary
615 # Commit dirty working directory if necessary
597 new = None
616 new = None
598 m, a, r, d = repo.status()[:4]
617 m, a, r, d = repo.status()[:4]
599 if m or a or r or d:
618 if m or a or r or d:
600 # prepare the message for the commit to comes
619 # prepare the message for the commit to comes
601 if action in ('f', 'fold'):
620 if action in ('f', 'fold'):
602 message = 'fold-temp-revision %s' % currentnode
621 message = 'fold-temp-revision %s' % currentnode
603 else:
622 else:
604 message = ctx.description() + '\n'
623 message = ctx.description() + '\n'
605 if action in ('e', 'edit', 'm', 'mess'):
624 if action in ('e', 'edit', 'm', 'mess'):
606 editor = cmdutil.commitforceeditor
625 editor = cmdutil.commitforceeditor
607 else:
626 else:
608 editor = False
627 editor = False
609 commit = commitfuncfor(repo, ctx)
628 commit = commitfuncfor(repo, ctx)
610 new = commit(text=message, user=ctx.user(),
629 new = commit(text=message, user=ctx.user(),
611 date=ctx.date(), extra=ctx.extra(),
630 date=ctx.date(), extra=ctx.extra(),
612 editor=editor)
631 editor=editor)
613 if new is not None:
632 if new is not None:
614 newchildren.append(new)
633 newchildren.append(new)
615
634
616 replacements = []
635 replacements = []
617 # track replacements
636 # track replacements
618 if ctx.node() not in newchildren:
637 if ctx.node() not in newchildren:
619 # note: new children may be empty when the changeset is dropped.
638 # note: new children may be empty when the changeset is dropped.
620 # this happen e.g during conflicting pick where we revert content
639 # this happen e.g during conflicting pick where we revert content
621 # to parent.
640 # to parent.
622 replacements.append((ctx.node(), tuple(newchildren)))
641 replacements.append((ctx.node(), tuple(newchildren)))
623
642
624 if action in ('f', 'fold'):
643 if action in ('f', 'fold'):
625 if newchildren:
644 if newchildren:
626 # finalize fold operation if applicable
645 # finalize fold operation if applicable
627 if new is None:
646 if new is None:
628 new = newchildren[-1]
647 new = newchildren[-1]
629 else:
648 else:
630 newchildren.pop() # remove new from internal changes
649 newchildren.pop() # remove new from internal changes
631 parentctx, repl = finishfold(ui, repo, parentctx, ctx, new, opts,
650 parentctx, repl = finishfold(ui, repo, parentctx, ctx, new, opts,
632 newchildren)
651 newchildren)
633 replacements.extend(repl)
652 replacements.extend(repl)
634 else:
653 else:
635 # newchildren is empty if the fold did not result in any commit
654 # newchildren is empty if the fold did not result in any commit
636 # this happen when all folded change are discarded during the
655 # this happen when all folded change are discarded during the
637 # merge.
656 # merge.
638 replacements.append((ctx.node(), (parentctx.node(),)))
657 replacements.append((ctx.node(), (parentctx.node(),)))
639 elif newchildren:
658 elif newchildren:
640 # otherwise update "parentctx" before proceeding to further operation
659 # otherwise update "parentctx" before proceeding to further operation
641 parentctx = repo[newchildren[-1]]
660 parentctx = repo[newchildren[-1]]
642 return parentctx, replacements
661 return parentctx, replacements
643
662
644
663
645 def between(repo, old, new, keep):
664 def between(repo, old, new, keep):
646 """select and validate the set of revision to edit
665 """select and validate the set of revision to edit
647
666
648 When keep is false, the specified set can't have children."""
667 When keep is false, the specified set can't have children."""
649 ctxs = list(repo.set('%n::%n', old, new))
668 ctxs = list(repo.set('%n::%n', old, new))
650 if ctxs and not keep:
669 if ctxs and not keep:
651 if (not obsolete._enabled and
670 if (not obsolete._enabled and
652 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
671 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
653 raise util.Abort(_('cannot edit history that would orphan nodes'))
672 raise util.Abort(_('cannot edit history that would orphan nodes'))
654 root = ctxs[0] # list is already sorted by repo.set
673 root = ctxs[0] # list is already sorted by repo.set
655 if not root.phase():
674 if not root.phase():
656 raise util.Abort(_('cannot edit immutable changeset: %s') % root)
675 raise util.Abort(_('cannot edit immutable changeset: %s') % root)
657 return [c.node() for c in ctxs]
676 return [c.node() for c in ctxs]
658
677
659
678
660 def writestate(repo, parentnode, rules, keep, topmost, replacements):
679 def writestate(repo, parentnode, rules, keep, topmost, replacements):
661 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
680 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
662 pickle.dump((parentnode, rules, keep, topmost, replacements), fp)
681 pickle.dump((parentnode, rules, keep, topmost, replacements), fp)
663 fp.close()
682 fp.close()
664
683
665 def readstate(repo):
684 def readstate(repo):
666 """Returns a tuple of (parentnode, rules, keep, topmost, replacements).
685 """Returns a tuple of (parentnode, rules, keep, topmost, replacements).
667 """
686 """
668 fp = open(os.path.join(repo.path, 'histedit-state'))
687 fp = open(os.path.join(repo.path, 'histedit-state'))
669 return pickle.load(fp)
688 return pickle.load(fp)
670
689
671
690
672 def makedesc(c):
691 def makedesc(c):
673 """build a initial action line for a ctx `c`
692 """build a initial action line for a ctx `c`
674
693
675 line are in the form:
694 line are in the form:
676
695
677 pick <hash> <rev> <summary>
696 pick <hash> <rev> <summary>
678 """
697 """
679 summary = ''
698 summary = ''
680 if c.description():
699 if c.description():
681 summary = c.description().splitlines()[0]
700 summary = c.description().splitlines()[0]
682 line = 'pick %s %d %s' % (c, c.rev(), summary)
701 line = 'pick %s %d %s' % (c, c.rev(), summary)
683 return line[:80] # trim to 80 chars so it's not stupidly wide in my editor
702 return line[:80] # trim to 80 chars so it's not stupidly wide in my editor
684
703
685 def verifyrules(rules, repo, ctxs):
704 def verifyrules(rules, repo, ctxs):
686 """Verify that there exists exactly one edit rule per given changeset.
705 """Verify that there exists exactly one edit rule per given changeset.
687
706
688 Will abort if there are to many or too few rules, a malformed rule,
707 Will abort if there are to many or too few rules, a malformed rule,
689 or a rule on a changeset outside of the user-given range.
708 or a rule on a changeset outside of the user-given range.
690 """
709 """
691 parsed = []
710 parsed = []
692 if len(rules) != len(ctxs):
711 if len(rules) != len(ctxs):
693 raise util.Abort(_('must specify a rule for each changeset once'))
712 raise util.Abort(_('must specify a rule for each changeset once'))
694 for r in rules:
713 for r in rules:
695 if ' ' not in r:
714 if ' ' not in r:
696 raise util.Abort(_('malformed line "%s"') % r)
715 raise util.Abort(_('malformed line "%s"') % r)
697 action, rest = r.split(' ', 1)
716 action, rest = r.split(' ', 1)
698 if ' ' in rest.strip():
717 if ' ' in rest.strip():
699 ha, rest = rest.split(' ', 1)
718 ha, rest = rest.split(' ', 1)
700 else:
719 else:
701 ha = r.strip()
720 ha = r.strip()
702 try:
721 try:
703 if repo[ha] not in ctxs:
722 if repo[ha] not in ctxs:
704 raise util.Abort(
723 raise util.Abort(
705 _('may not use changesets other than the ones listed'))
724 _('may not use changesets other than the ones listed'))
706 except error.RepoError:
725 except error.RepoError:
707 raise util.Abort(_('unknown changeset %s listed') % ha)
726 raise util.Abort(_('unknown changeset %s listed') % ha)
708 if action not in actiontable:
727 if action not in actiontable:
709 raise util.Abort(_('unknown action "%s"') % action)
728 raise util.Abort(_('unknown action "%s"') % action)
710 parsed.append([action, ha])
729 parsed.append([action, ha])
711 return parsed
730 return parsed
712
731
713 def processreplacement(repo, replacements):
732 def processreplacement(repo, replacements):
714 """process the list of replacements to return
733 """process the list of replacements to return
715
734
716 1) the final mapping between original and created nodes
735 1) the final mapping between original and created nodes
717 2) the list of temporary node created by histedit
736 2) the list of temporary node created by histedit
718 3) the list of new commit created by histedit"""
737 3) the list of new commit created by histedit"""
719 allsuccs = set()
738 allsuccs = set()
720 replaced = set()
739 replaced = set()
721 fullmapping = {}
740 fullmapping = {}
722 # initialise basic set
741 # initialise basic set
723 # fullmapping record all operation recorded in replacement
742 # fullmapping record all operation recorded in replacement
724 for rep in replacements:
743 for rep in replacements:
725 allsuccs.update(rep[1])
744 allsuccs.update(rep[1])
726 replaced.add(rep[0])
745 replaced.add(rep[0])
727 fullmapping.setdefault(rep[0], set()).update(rep[1])
746 fullmapping.setdefault(rep[0], set()).update(rep[1])
728 new = allsuccs - replaced
747 new = allsuccs - replaced
729 tmpnodes = allsuccs & replaced
748 tmpnodes = allsuccs & replaced
730 # Reduce content fullmapping into direct relation between original nodes
749 # Reduce content fullmapping into direct relation between original nodes
731 # and final node created during history edition
750 # and final node created during history edition
732 # Dropped changeset are replaced by an empty list
751 # Dropped changeset are replaced by an empty list
733 toproceed = set(fullmapping)
752 toproceed = set(fullmapping)
734 final = {}
753 final = {}
735 while toproceed:
754 while toproceed:
736 for x in list(toproceed):
755 for x in list(toproceed):
737 succs = fullmapping[x]
756 succs = fullmapping[x]
738 for s in list(succs):
757 for s in list(succs):
739 if s in toproceed:
758 if s in toproceed:
740 # non final node with unknown closure
759 # non final node with unknown closure
741 # We can't process this now
760 # We can't process this now
742 break
761 break
743 elif s in final:
762 elif s in final:
744 # non final node, replace with closure
763 # non final node, replace with closure
745 succs.remove(s)
764 succs.remove(s)
746 succs.update(final[s])
765 succs.update(final[s])
747 else:
766 else:
748 final[x] = succs
767 final[x] = succs
749 toproceed.remove(x)
768 toproceed.remove(x)
750 # remove tmpnodes from final mapping
769 # remove tmpnodes from final mapping
751 for n in tmpnodes:
770 for n in tmpnodes:
752 del final[n]
771 del final[n]
753 # we expect all changes involved in final to exist in the repo
772 # we expect all changes involved in final to exist in the repo
754 # turn `final` into list (topologically sorted)
773 # turn `final` into list (topologically sorted)
755 nm = repo.changelog.nodemap
774 nm = repo.changelog.nodemap
756 for prec, succs in final.items():
775 for prec, succs in final.items():
757 final[prec] = sorted(succs, key=nm.get)
776 final[prec] = sorted(succs, key=nm.get)
758
777
759 # computed topmost element (necessary for bookmark)
778 # computed topmost element (necessary for bookmark)
760 if new:
779 if new:
761 newtopmost = sorted(new, key=repo.changelog.rev)[-1]
780 newtopmost = sorted(new, key=repo.changelog.rev)[-1]
762 elif not final:
781 elif not final:
763 # Nothing rewritten at all. we won't need `newtopmost`
782 # Nothing rewritten at all. we won't need `newtopmost`
764 # It is the same as `oldtopmost` and `processreplacement` know it
783 # It is the same as `oldtopmost` and `processreplacement` know it
765 newtopmost = None
784 newtopmost = None
766 else:
785 else:
767 # every body died. The newtopmost is the parent of the root.
786 # every body died. The newtopmost is the parent of the root.
768 newtopmost = repo[sorted(final, key=repo.changelog.rev)[0]].p1().node()
787 newtopmost = repo[sorted(final, key=repo.changelog.rev)[0]].p1().node()
769
788
770 return final, tmpnodes, new, newtopmost
789 return final, tmpnodes, new, newtopmost
771
790
772 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
791 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
773 """Move bookmark from old to newly created node"""
792 """Move bookmark from old to newly created node"""
774 if not mapping:
793 if not mapping:
775 # if nothing got rewritten there is not purpose for this function
794 # if nothing got rewritten there is not purpose for this function
776 return
795 return
777 moves = []
796 moves = []
778 for bk, old in sorted(repo._bookmarks.iteritems()):
797 for bk, old in sorted(repo._bookmarks.iteritems()):
779 if old == oldtopmost:
798 if old == oldtopmost:
780 # special case ensure bookmark stay on tip.
799 # special case ensure bookmark stay on tip.
781 #
800 #
782 # This is arguably a feature and we may only want that for the
801 # This is arguably a feature and we may only want that for the
783 # active bookmark. But the behavior is kept compatible with the old
802 # active bookmark. But the behavior is kept compatible with the old
784 # version for now.
803 # version for now.
785 moves.append((bk, newtopmost))
804 moves.append((bk, newtopmost))
786 continue
805 continue
787 base = old
806 base = old
788 new = mapping.get(base, None)
807 new = mapping.get(base, None)
789 if new is None:
808 if new is None:
790 continue
809 continue
791 while not new:
810 while not new:
792 # base is killed, trying with parent
811 # base is killed, trying with parent
793 base = repo[base].p1().node()
812 base = repo[base].p1().node()
794 new = mapping.get(base, (base,))
813 new = mapping.get(base, (base,))
795 # nothing to move
814 # nothing to move
796 moves.append((bk, new[-1]))
815 moves.append((bk, new[-1]))
797 if moves:
816 if moves:
798 marks = repo._bookmarks
817 marks = repo._bookmarks
799 for mark, new in moves:
818 for mark, new in moves:
800 old = marks[mark]
819 old = marks[mark]
801 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
820 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
802 % (mark, node.short(old), node.short(new)))
821 % (mark, node.short(old), node.short(new)))
803 marks[mark] = new
822 marks[mark] = new
804 marks.write()
823 marks.write()
805
824
806 def cleanupnode(ui, repo, name, nodes):
825 def cleanupnode(ui, repo, name, nodes):
807 """strip a group of nodes from the repository
826 """strip a group of nodes from the repository
808
827
809 The set of node to strip may contains unknown nodes."""
828 The set of node to strip may contains unknown nodes."""
810 ui.debug('should strip %s nodes %s\n' %
829 ui.debug('should strip %s nodes %s\n' %
811 (name, ', '.join([node.short(n) for n in nodes])))
830 (name, ', '.join([node.short(n) for n in nodes])))
812 lock = None
831 lock = None
813 try:
832 try:
814 lock = repo.lock()
833 lock = repo.lock()
815 # Find all node that need to be stripped
834 # Find all node that need to be stripped
816 # (we hg %lr instead of %ln to silently ignore unknown item
835 # (we hg %lr instead of %ln to silently ignore unknown item
817 nm = repo.changelog.nodemap
836 nm = repo.changelog.nodemap
818 nodes = [n for n in nodes if n in nm]
837 nodes = [n for n in nodes if n in nm]
819 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
838 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
820 for c in roots:
839 for c in roots:
821 # We should process node in reverse order to strip tip most first.
840 # We should process node in reverse order to strip tip most first.
822 # but this trigger a bug in changegroup hook.
841 # but this trigger a bug in changegroup hook.
823 # This would reduce bundle overhead
842 # This would reduce bundle overhead
824 repair.strip(ui, repo, c)
843 repair.strip(ui, repo, c)
825 finally:
844 finally:
826 lockmod.release(lock)
845 lockmod.release(lock)
@@ -1,295 +1,295 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 $ initrepo ()
9 $ initrepo ()
10 > {
10 > {
11 > hg init $1
11 > hg init $1
12 > cd $1
12 > cd $1
13 > for x in a b c d e f ; do
13 > for x in a b c d e f ; do
14 > echo $x$x$x$x$x > $x
14 > echo $x$x$x$x$x > $x
15 > hg add $x
15 > hg add $x
16 > done
16 > done
17 > hg ci -m 'Initial commit'
17 > hg ci -m 'Initial commit'
18 > for x in a b c d e f ; do
18 > for x in a b c d e f ; do
19 > echo $x > $x
19 > echo $x > $x
20 > hg ci -m $x
20 > hg ci -m $x
21 > done
21 > done
22 > echo 'I can haz no commute' > e
22 > echo 'I can haz no commute' > e
23 > hg ci -m 'does not commute with e'
23 > hg ci -m 'does not commute with e'
24 > cd ..
24 > cd ..
25 > }
25 > }
26
26
27 $ initrepo r1
27 $ initrepo r1
28 $ cd r1
28 $ cd r1
29
29
30 Initial generation of the command files
30 Initial generation of the command files
31
31
32 $ EDITED="$TESTTMP/editedhistory"
32 $ EDITED="$TESTTMP/editedhistory"
33 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 3 >> $EDITED
33 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 3 >> $EDITED
34 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 4 >> $EDITED
34 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 4 >> $EDITED
35 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 7 >> $EDITED
35 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 7 >> $EDITED
36 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 5 >> $EDITED
36 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 5 >> $EDITED
37 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 6 >> $EDITED
37 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 6 >> $EDITED
38 $ cat $EDITED
38 $ cat $EDITED
39 pick 65a9a84f33fd 3 c
39 pick 65a9a84f33fd 3 c
40 pick 00f1c5383965 4 d
40 pick 00f1c5383965 4 d
41 pick 39522b764e3d 7 does not commute with e
41 pick 39522b764e3d 7 does not commute with e
42 pick 7b4e2f4b7bcd 5 e
42 pick 7b4e2f4b7bcd 5 e
43 pick 500cac37a696 6 f
43 pick 500cac37a696 6 f
44
44
45 log before edit
45 log before edit
46 $ hg log --graph
46 $ hg log --graph
47 @ changeset: 7:39522b764e3d
47 @ changeset: 7:39522b764e3d
48 | tag: tip
48 | tag: tip
49 | user: test
49 | user: test
50 | date: Thu Jan 01 00:00:00 1970 +0000
50 | date: Thu Jan 01 00:00:00 1970 +0000
51 | summary: does not commute with e
51 | summary: does not commute with e
52 |
52 |
53 o changeset: 6:500cac37a696
53 o changeset: 6:500cac37a696
54 | user: test
54 | user: test
55 | date: Thu Jan 01 00:00:00 1970 +0000
55 | date: Thu Jan 01 00:00:00 1970 +0000
56 | summary: f
56 | summary: f
57 |
57 |
58 o changeset: 5:7b4e2f4b7bcd
58 o changeset: 5:7b4e2f4b7bcd
59 | user: test
59 | user: test
60 | date: Thu Jan 01 00:00:00 1970 +0000
60 | date: Thu Jan 01 00:00:00 1970 +0000
61 | summary: e
61 | summary: e
62 |
62 |
63 o changeset: 4:00f1c5383965
63 o changeset: 4:00f1c5383965
64 | user: test
64 | user: test
65 | date: Thu Jan 01 00:00:00 1970 +0000
65 | date: Thu Jan 01 00:00:00 1970 +0000
66 | summary: d
66 | summary: d
67 |
67 |
68 o changeset: 3:65a9a84f33fd
68 o changeset: 3:65a9a84f33fd
69 | user: test
69 | user: test
70 | date: Thu Jan 01 00:00:00 1970 +0000
70 | date: Thu Jan 01 00:00:00 1970 +0000
71 | summary: c
71 | summary: c
72 |
72 |
73 o changeset: 2:da6535b52e45
73 o changeset: 2:da6535b52e45
74 | user: test
74 | user: test
75 | date: Thu Jan 01 00:00:00 1970 +0000
75 | date: Thu Jan 01 00:00:00 1970 +0000
76 | summary: b
76 | summary: b
77 |
77 |
78 o changeset: 1:c1f09da44841
78 o changeset: 1:c1f09da44841
79 | user: test
79 | user: test
80 | date: Thu Jan 01 00:00:00 1970 +0000
80 | date: Thu Jan 01 00:00:00 1970 +0000
81 | summary: a
81 | summary: a
82 |
82 |
83 o changeset: 0:1715188a53c7
83 o changeset: 0:1715188a53c7
84 user: test
84 user: test
85 date: Thu Jan 01 00:00:00 1970 +0000
85 date: Thu Jan 01 00:00:00 1970 +0000
86 summary: Initial commit
86 summary: Initial commit
87
87
88
88
89 edit the history
89 edit the history
90 $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
90 $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
91 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 merging e
92 merging e
93 warning: conflicts during merge.
93 warning: conflicts during merge.
94 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
94 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
95 Fix up the change and run hg histedit --continue
95 Fix up the change and run hg histedit --continue
96
96
97 abort the edit
97 abort the edit
98 $ hg histedit --abort 2>&1 | fixbundle
98 $ hg histedit --abort 2>&1 | fixbundle
99 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
99 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
100
100
101
101
102 second edit set
102 second edit set
103
103
104 $ hg log --graph
104 $ hg log --graph
105 @ changeset: 7:39522b764e3d
105 @ changeset: 7:39522b764e3d
106 | tag: tip
106 | tag: tip
107 | user: test
107 | user: test
108 | date: Thu Jan 01 00:00:00 1970 +0000
108 | date: Thu Jan 01 00:00:00 1970 +0000
109 | summary: does not commute with e
109 | summary: does not commute with e
110 |
110 |
111 o changeset: 6:500cac37a696
111 o changeset: 6:500cac37a696
112 | user: test
112 | user: test
113 | date: Thu Jan 01 00:00:00 1970 +0000
113 | date: Thu Jan 01 00:00:00 1970 +0000
114 | summary: f
114 | summary: f
115 |
115 |
116 o changeset: 5:7b4e2f4b7bcd
116 o changeset: 5:7b4e2f4b7bcd
117 | user: test
117 | user: test
118 | date: Thu Jan 01 00:00:00 1970 +0000
118 | date: Thu Jan 01 00:00:00 1970 +0000
119 | summary: e
119 | summary: e
120 |
120 |
121 o changeset: 4:00f1c5383965
121 o changeset: 4:00f1c5383965
122 | user: test
122 | user: test
123 | date: Thu Jan 01 00:00:00 1970 +0000
123 | date: Thu Jan 01 00:00:00 1970 +0000
124 | summary: d
124 | summary: d
125 |
125 |
126 o changeset: 3:65a9a84f33fd
126 o changeset: 3:65a9a84f33fd
127 | user: test
127 | user: test
128 | date: Thu Jan 01 00:00:00 1970 +0000
128 | date: Thu Jan 01 00:00:00 1970 +0000
129 | summary: c
129 | summary: c
130 |
130 |
131 o changeset: 2:da6535b52e45
131 o changeset: 2:da6535b52e45
132 | user: test
132 | user: test
133 | date: Thu Jan 01 00:00:00 1970 +0000
133 | date: Thu Jan 01 00:00:00 1970 +0000
134 | summary: b
134 | summary: b
135 |
135 |
136 o changeset: 1:c1f09da44841
136 o changeset: 1:c1f09da44841
137 | user: test
137 | user: test
138 | date: Thu Jan 01 00:00:00 1970 +0000
138 | date: Thu Jan 01 00:00:00 1970 +0000
139 | summary: a
139 | summary: a
140 |
140 |
141 o changeset: 0:1715188a53c7
141 o changeset: 0:1715188a53c7
142 user: test
142 user: test
143 date: Thu Jan 01 00:00:00 1970 +0000
143 date: Thu Jan 01 00:00:00 1970 +0000
144 summary: Initial commit
144 summary: Initial commit
145
145
146
146
147 edit the history
147 edit the history
148 $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
148 $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
149 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
149 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
150 merging e
150 merging e
151 warning: conflicts during merge.
151 warning: conflicts during merge.
152 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
152 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
153 Fix up the change and run hg histedit --continue
153 Fix up the change and run hg histedit --continue
154
154
155 fix up
155 fix up
156 $ echo 'I can haz no commute' > e
156 $ echo 'I can haz no commute' > e
157 $ hg resolve --mark e
157 $ hg resolve --mark e
158 $ hg histedit --continue 2>&1 | fixbundle
158 $ hg histedit --continue 2>&1 | fixbundle
159 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
159 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
160 merging e
160 merging e
161 warning: conflicts during merge.
161 warning: conflicts during merge.
162 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
162 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
163 Fix up the change and run hg histedit --continue
163 Fix up the change and run hg histedit --continue
164
164
165 This failure is caused by 7b4e2f4b7bcd "e" not rebasing the non commutative
165 This failure is caused by 7b4e2f4b7bcd "e" not rebasing the non commutative
166 former children.
166 former children.
167
167
168 just continue this time
168 just continue this time
169 $ hg revert -r 'p1()' e
169 $ hg revert -r 'p1()' e
170 $ hg resolve --mark e
170 $ hg resolve --mark e
171 $ hg histedit --continue 2>&1 | fixbundle
171 $ hg histedit --continue 2>&1 | fixbundle
172 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
172 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
173 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
173 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
174
174
175 log after edit
175 log after edit
176 $ hg log --graph
176 $ hg log --graph
177 @ changeset: 6:7efe1373e4bc
177 @ changeset: 6:7efe1373e4bc
178 | tag: tip
178 | tag: tip
179 | user: test
179 | user: test
180 | date: Thu Jan 01 00:00:00 1970 +0000
180 | date: Thu Jan 01 00:00:00 1970 +0000
181 | summary: f
181 | summary: f
182 |
182 |
183 o changeset: 5:e334d87a1e55
183 o changeset: 5:e334d87a1e55
184 | user: test
184 | user: test
185 | date: Thu Jan 01 00:00:00 1970 +0000
185 | date: Thu Jan 01 00:00:00 1970 +0000
186 | summary: does not commute with e
186 | summary: does not commute with e
187 |
187 |
188 o changeset: 4:00f1c5383965
188 o changeset: 4:00f1c5383965
189 | user: test
189 | user: test
190 | date: Thu Jan 01 00:00:00 1970 +0000
190 | date: Thu Jan 01 00:00:00 1970 +0000
191 | summary: d
191 | summary: d
192 |
192 |
193 o changeset: 3:65a9a84f33fd
193 o changeset: 3:65a9a84f33fd
194 | user: test
194 | user: test
195 | date: Thu Jan 01 00:00:00 1970 +0000
195 | date: Thu Jan 01 00:00:00 1970 +0000
196 | summary: c
196 | summary: c
197 |
197 |
198 o changeset: 2:da6535b52e45
198 o changeset: 2:da6535b52e45
199 | user: test
199 | user: test
200 | date: Thu Jan 01 00:00:00 1970 +0000
200 | date: Thu Jan 01 00:00:00 1970 +0000
201 | summary: b
201 | summary: b
202 |
202 |
203 o changeset: 1:c1f09da44841
203 o changeset: 1:c1f09da44841
204 | user: test
204 | user: test
205 | date: Thu Jan 01 00:00:00 1970 +0000
205 | date: Thu Jan 01 00:00:00 1970 +0000
206 | summary: a
206 | summary: a
207 |
207 |
208 o changeset: 0:1715188a53c7
208 o changeset: 0:1715188a53c7
209 user: test
209 user: test
210 date: Thu Jan 01 00:00:00 1970 +0000
210 date: Thu Jan 01 00:00:00 1970 +0000
211 summary: Initial commit
211 summary: Initial commit
212
212
213
213
214 start over
214 start over
215
215
216 $ cd ..
216 $ cd ..
217
217
218 $ initrepo r2
218 $ initrepo r2
219 $ cd r2
219 $ cd r2
220 $ rm $EDITED
220 $ rm $EDITED
221 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 3 >> $EDITED
221 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 3 >> $EDITED
222 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 4 >> $EDITED
222 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 4 >> $EDITED
223 $ hg log --template 'mess {node|short} {rev} {desc}\n' -r 7 >> $EDITED
223 $ hg log --template 'mess {node|short} {rev} {desc}\n' -r 7 >> $EDITED
224 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 5 >> $EDITED
224 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 5 >> $EDITED
225 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 6 >> $EDITED
225 $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 6 >> $EDITED
226 $ cat $EDITED
226 $ cat $EDITED
227 pick 65a9a84f33fd 3 c
227 pick 65a9a84f33fd 3 c
228 pick 00f1c5383965 4 d
228 pick 00f1c5383965 4 d
229 mess 39522b764e3d 7 does not commute with e
229 mess 39522b764e3d 7 does not commute with e
230 pick 7b4e2f4b7bcd 5 e
230 pick 7b4e2f4b7bcd 5 e
231 pick 500cac37a696 6 f
231 pick 500cac37a696 6 f
232
232
233 edit the history, this time with a fold action
233 edit the history, this time with a fold action
234 $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
234 $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
235 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
235 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
236 merging e
236 merging e
237 warning: conflicts during merge.
237 warning: conflicts during merge.
238 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
238 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
239 Fix up the change and run hg histedit --continue
239 Fix up the change and run hg histedit --continue
240
240
241 $ echo 'I can haz no commute' > e
241 $ echo 'I can haz no commute' > e
242 $ hg resolve --mark e
242 $ hg resolve --mark e
243 $ hg histedit --commands $EDITED --continue 2>&1 | fixbundle
243 $ hg histedit --continue 2>&1 | fixbundle
244 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
244 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
245 merging e
245 merging e
246 warning: conflicts during merge.
246 warning: conflicts during merge.
247 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
247 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
248 Fix up the change and run hg histedit --continue
248 Fix up the change and run hg histedit --continue
249 second edit also fails, but just continue
249 second edit also fails, but just continue
250 $ hg revert -r 'p1()' e
250 $ hg revert -r 'p1()' e
251 $ hg resolve --mark e
251 $ hg resolve --mark e
252 $ hg histedit --continue 2>&1 | fixbundle
252 $ hg histedit --continue 2>&1 | fixbundle
253 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
253 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
254 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
254 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
255
255
256 post message fix
256 post message fix
257 $ hg log --graph
257 $ hg log --graph
258 @ changeset: 6:7efe1373e4bc
258 @ changeset: 6:7efe1373e4bc
259 | tag: tip
259 | tag: tip
260 | user: test
260 | user: test
261 | date: Thu Jan 01 00:00:00 1970 +0000
261 | date: Thu Jan 01 00:00:00 1970 +0000
262 | summary: f
262 | summary: f
263 |
263 |
264 o changeset: 5:e334d87a1e55
264 o changeset: 5:e334d87a1e55
265 | user: test
265 | user: test
266 | date: Thu Jan 01 00:00:00 1970 +0000
266 | date: Thu Jan 01 00:00:00 1970 +0000
267 | summary: does not commute with e
267 | summary: does not commute with e
268 |
268 |
269 o changeset: 4:00f1c5383965
269 o changeset: 4:00f1c5383965
270 | user: test
270 | user: test
271 | date: Thu Jan 01 00:00:00 1970 +0000
271 | date: Thu Jan 01 00:00:00 1970 +0000
272 | summary: d
272 | summary: d
273 |
273 |
274 o changeset: 3:65a9a84f33fd
274 o changeset: 3:65a9a84f33fd
275 | user: test
275 | user: test
276 | date: Thu Jan 01 00:00:00 1970 +0000
276 | date: Thu Jan 01 00:00:00 1970 +0000
277 | summary: c
277 | summary: c
278 |
278 |
279 o changeset: 2:da6535b52e45
279 o changeset: 2:da6535b52e45
280 | user: test
280 | user: test
281 | date: Thu Jan 01 00:00:00 1970 +0000
281 | date: Thu Jan 01 00:00:00 1970 +0000
282 | summary: b
282 | summary: b
283 |
283 |
284 o changeset: 1:c1f09da44841
284 o changeset: 1:c1f09da44841
285 | user: test
285 | user: test
286 | date: Thu Jan 01 00:00:00 1970 +0000
286 | date: Thu Jan 01 00:00:00 1970 +0000
287 | summary: a
287 | summary: a
288 |
288 |
289 o changeset: 0:1715188a53c7
289 o changeset: 0:1715188a53c7
290 user: test
290 user: test
291 date: Thu Jan 01 00:00:00 1970 +0000
291 date: Thu Jan 01 00:00:00 1970 +0000
292 summary: Initial commit
292 summary: Initial commit
293
293
294
294
295 $ cd ..
295 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now