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