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