##// END OF EJS Templates
histedit: mark temporary commits as obsolete when allowed to...
Laurent Charignon -
r25809:ebb5bb9b default
parent child Browse files
Show More
@@ -1,1160 +1,1167
1 # histedit.py - interactive history editing for mercurial
1 # histedit.py - interactive history editing for mercurial
2 #
2 #
3 # Copyright 2009 Augie Fackler <raf@durin42.com>
3 # Copyright 2009 Augie Fackler <raf@durin42.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 """interactive history editing
7 """interactive history editing
8
8
9 With this extension installed, Mercurial gains one new command: histedit. Usage
9 With this extension installed, Mercurial gains one new command: histedit. Usage
10 is as follows, assuming the following history::
10 is as follows, assuming the following history::
11
11
12 @ 3[tip] 7c2fd3b9020c 2009-04-27 18:04 -0500 durin42
12 @ 3[tip] 7c2fd3b9020c 2009-04-27 18:04 -0500 durin42
13 | Add delta
13 | Add delta
14 |
14 |
15 o 2 030b686bedc4 2009-04-27 18:04 -0500 durin42
15 o 2 030b686bedc4 2009-04-27 18:04 -0500 durin42
16 | Add gamma
16 | Add gamma
17 |
17 |
18 o 1 c561b4e977df 2009-04-27 18:04 -0500 durin42
18 o 1 c561b4e977df 2009-04-27 18:04 -0500 durin42
19 | Add beta
19 | Add beta
20 |
20 |
21 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
21 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
22 Add alpha
22 Add alpha
23
23
24 If you were to run ``hg histedit c561b4e977df``, you would see the following
24 If you were to run ``hg histedit c561b4e977df``, you would see the following
25 file open in your editor::
25 file open in your editor::
26
26
27 pick c561b4e977df Add beta
27 pick c561b4e977df Add beta
28 pick 030b686bedc4 Add gamma
28 pick 030b686bedc4 Add gamma
29 pick 7c2fd3b9020c Add delta
29 pick 7c2fd3b9020c Add delta
30
30
31 # Edit history between c561b4e977df and 7c2fd3b9020c
31 # Edit history between c561b4e977df and 7c2fd3b9020c
32 #
32 #
33 # Commits are listed from least to most recent
33 # Commits are listed from least to most recent
34 #
34 #
35 # Commands:
35 # Commands:
36 # p, pick = use commit
36 # p, pick = use commit
37 # e, edit = use commit, but stop for amending
37 # e, edit = use commit, but stop for amending
38 # f, fold = use commit, but combine it with the one above
38 # f, fold = use commit, but combine it with the one above
39 # r, roll = like fold, but discard this commit's description
39 # r, roll = like fold, but discard this commit's description
40 # d, drop = remove commit from history
40 # d, drop = remove commit from history
41 # m, mess = edit message without changing commit content
41 # m, mess = edit message without changing commit content
42 #
42 #
43
43
44 In this file, lines beginning with ``#`` are ignored. You must specify a rule
44 In this file, lines beginning with ``#`` are ignored. You must specify a rule
45 for each revision in your history. For example, if you had meant to add gamma
45 for each revision in your history. For example, if you had meant to add gamma
46 before beta, and then wanted to add delta in the same revision as beta, you
46 before beta, and then wanted to add delta in the same revision as beta, you
47 would reorganize the file to look like this::
47 would reorganize the file to look like this::
48
48
49 pick 030b686bedc4 Add gamma
49 pick 030b686bedc4 Add gamma
50 pick c561b4e977df Add beta
50 pick c561b4e977df Add beta
51 fold 7c2fd3b9020c Add delta
51 fold 7c2fd3b9020c Add delta
52
52
53 # Edit history between c561b4e977df and 7c2fd3b9020c
53 # Edit history between c561b4e977df and 7c2fd3b9020c
54 #
54 #
55 # Commits are listed from least to most recent
55 # Commits are listed from least to most recent
56 #
56 #
57 # Commands:
57 # Commands:
58 # p, pick = use commit
58 # p, pick = use commit
59 # e, edit = use commit, but stop for amending
59 # e, edit = use commit, but stop for amending
60 # f, fold = use commit, but combine it with the one above
60 # f, fold = use commit, but combine it with the one above
61 # r, roll = like fold, but discard this commit's description
61 # r, roll = like fold, but discard this commit's description
62 # d, drop = remove commit from history
62 # d, drop = remove commit from history
63 # m, mess = edit message without changing commit content
63 # m, mess = edit message without changing commit content
64 #
64 #
65
65
66 At which point you close the editor and ``histedit`` starts working. When you
66 At which point you close the editor and ``histedit`` starts working. When you
67 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
67 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
68 those revisions together, offering you a chance to clean up the commit message::
68 those revisions together, offering you a chance to clean up the commit message::
69
69
70 Add beta
70 Add beta
71 ***
71 ***
72 Add delta
72 Add delta
73
73
74 Edit the commit message to your liking, then close the editor. For
74 Edit the commit message to your liking, then close the editor. For
75 this example, let's assume that the commit message was changed to
75 this example, let's assume that the commit message was changed to
76 ``Add beta and delta.`` After histedit has run and had a chance to
76 ``Add beta and delta.`` After histedit has run and had a chance to
77 remove any old or temporary revisions it needed, the history looks
77 remove any old or temporary revisions it needed, the history looks
78 like this::
78 like this::
79
79
80 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
80 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
81 | Add beta and delta.
81 | Add beta and delta.
82 |
82 |
83 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
83 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
84 | Add gamma
84 | Add gamma
85 |
85 |
86 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
86 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
87 Add alpha
87 Add alpha
88
88
89 Note that ``histedit`` does *not* remove any revisions (even its own temporary
89 Note that ``histedit`` does *not* remove any revisions (even its own temporary
90 ones) until after it has completed all the editing operations, so it will
90 ones) until after it has completed all the editing operations, so it will
91 probably perform several strip operations when it's done. For the above example,
91 probably perform several strip operations when it's done. For the above example,
92 it had to run strip twice. Strip can be slow depending on a variety of factors,
92 it had to run strip twice. Strip can be slow depending on a variety of factors,
93 so you might need to be a little patient. You can choose to keep the original
93 so you might need to be a little patient. You can choose to keep the original
94 revisions by passing the ``--keep`` flag.
94 revisions by passing the ``--keep`` flag.
95
95
96 The ``edit`` operation will drop you back to a command prompt,
96 The ``edit`` operation will drop you back to a command prompt,
97 allowing you to edit files freely, or even use ``hg record`` to commit
97 allowing you to edit files freely, or even use ``hg record`` to commit
98 some changes as a separate commit. When you're done, any remaining
98 some changes as a separate commit. When you're done, any remaining
99 uncommitted changes will be committed as well. When done, run ``hg
99 uncommitted changes will be committed as well. When done, run ``hg
100 histedit --continue`` to finish this step. You'll be prompted for a
100 histedit --continue`` to finish this step. You'll be prompted for a
101 new commit message, but the default commit message will be the
101 new commit message, but the default commit message will be the
102 original message for the ``edit`` ed revision.
102 original message for the ``edit`` ed revision.
103
103
104 The ``message`` operation will give you a chance to revise a commit
104 The ``message`` operation will give you a chance to revise a commit
105 message without changing the contents. It's a shortcut for doing
105 message without changing the contents. It's a shortcut for doing
106 ``edit`` immediately followed by `hg histedit --continue``.
106 ``edit`` immediately followed by `hg histedit --continue``.
107
107
108 If ``histedit`` encounters a conflict when moving a revision (while
108 If ``histedit`` encounters a conflict when moving a revision (while
109 handling ``pick`` or ``fold``), it'll stop in a similar manner to
109 handling ``pick`` or ``fold``), it'll stop in a similar manner to
110 ``edit`` with the difference that it won't prompt you for a commit
110 ``edit`` with the difference that it won't prompt you for a commit
111 message when done. If you decide at this point that you don't like how
111 message when done. If you decide at this point that you don't like how
112 much work it will be to rearrange history, or that you made a mistake,
112 much work it will be to rearrange history, or that you made a mistake,
113 you can use ``hg histedit --abort`` to abandon the new changes you
113 you can use ``hg histedit --abort`` to abandon the new changes you
114 have made and return to the state before you attempted to edit your
114 have made and return to the state before you attempted to edit your
115 history.
115 history.
116
116
117 If we clone the histedit-ed example repository above and add four more
117 If we clone the histedit-ed example repository above and add four more
118 changes, such that we have the following history::
118 changes, such that we have the following history::
119
119
120 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
120 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
121 | Add theta
121 | Add theta
122 |
122 |
123 o 5 140988835471 2009-04-27 18:04 -0500 stefan
123 o 5 140988835471 2009-04-27 18:04 -0500 stefan
124 | Add eta
124 | Add eta
125 |
125 |
126 o 4 122930637314 2009-04-27 18:04 -0500 stefan
126 o 4 122930637314 2009-04-27 18:04 -0500 stefan
127 | Add zeta
127 | Add zeta
128 |
128 |
129 o 3 836302820282 2009-04-27 18:04 -0500 stefan
129 o 3 836302820282 2009-04-27 18:04 -0500 stefan
130 | Add epsilon
130 | Add epsilon
131 |
131 |
132 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
132 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
133 | Add beta and delta.
133 | Add beta and delta.
134 |
134 |
135 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
135 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
136 | Add gamma
136 | Add gamma
137 |
137 |
138 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
138 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
139 Add alpha
139 Add alpha
140
140
141 If you run ``hg histedit --outgoing`` on the clone then it is the same
141 If you run ``hg histedit --outgoing`` on the clone then it is the same
142 as running ``hg histedit 836302820282``. If you need plan to push to a
142 as running ``hg histedit 836302820282``. If you need plan to push to a
143 repository that Mercurial does not detect to be related to the source
143 repository that Mercurial does not detect to be related to the source
144 repo, you can add a ``--force`` option.
144 repo, you can add a ``--force`` option.
145
145
146 Histedit rule lines are truncated to 80 characters by default. You
146 Histedit rule lines are truncated to 80 characters by default. You
147 can customise this behaviour by setting a different length in your
147 can customise this behaviour by setting a different length in your
148 configuration file::
148 configuration file::
149
149
150 [histedit]
150 [histedit]
151 linelen = 120 # truncate rule lines at 120 characters
151 linelen = 120 # truncate rule lines at 120 characters
152 """
152 """
153
153
154 try:
154 try:
155 import cPickle as pickle
155 import cPickle as pickle
156 pickle.dump # import now
156 pickle.dump # import now
157 except ImportError:
157 except ImportError:
158 import pickle
158 import pickle
159 import errno
159 import errno
160 import os
160 import os
161 import sys
161 import sys
162
162
163 from mercurial import cmdutil
163 from mercurial import cmdutil
164 from mercurial import discovery
164 from mercurial import discovery
165 from mercurial import error
165 from mercurial import error
166 from mercurial import changegroup
166 from mercurial import changegroup
167 from mercurial import copies
167 from mercurial import copies
168 from mercurial import context
168 from mercurial import context
169 from mercurial import exchange
169 from mercurial import exchange
170 from mercurial import extensions
170 from mercurial import extensions
171 from mercurial import hg
171 from mercurial import hg
172 from mercurial import node
172 from mercurial import node
173 from mercurial import repair
173 from mercurial import repair
174 from mercurial import scmutil
174 from mercurial import scmutil
175 from mercurial import util
175 from mercurial import util
176 from mercurial import obsolete
176 from mercurial import obsolete
177 from mercurial import merge as mergemod
177 from mercurial import merge as mergemod
178 from mercurial.lock import release
178 from mercurial.lock import release
179 from mercurial.i18n import _
179 from mercurial.i18n import _
180
180
181 cmdtable = {}
181 cmdtable = {}
182 command = cmdutil.command(cmdtable)
182 command = cmdutil.command(cmdtable)
183
183
184 # Note for extension authors: ONLY specify testedwith = 'internal' for
184 # Note for extension authors: ONLY specify testedwith = 'internal' for
185 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
185 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
186 # be specifying the version(s) of Mercurial they are tested with, or
186 # be specifying the version(s) of Mercurial they are tested with, or
187 # leave the attribute unspecified.
187 # leave the attribute unspecified.
188 testedwith = 'internal'
188 testedwith = 'internal'
189
189
190 # i18n: command names and abbreviations must remain untranslated
190 # i18n: command names and abbreviations must remain untranslated
191 editcomment = _("""# Edit history between %s and %s
191 editcomment = _("""# Edit history between %s and %s
192 #
192 #
193 # Commits are listed from least to most recent
193 # Commits are listed from least to most recent
194 #
194 #
195 # Commands:
195 # Commands:
196 # p, pick = use commit
196 # p, pick = use commit
197 # e, edit = use commit, but stop for amending
197 # e, edit = use commit, but stop for amending
198 # f, fold = use commit, but combine it with the one above
198 # f, fold = use commit, but combine it with the one above
199 # r, roll = like fold, but discard this commit's description
199 # r, roll = like fold, but discard this commit's description
200 # d, drop = remove commit from history
200 # d, drop = remove commit from history
201 # m, mess = edit message without changing commit content
201 # m, mess = edit message without changing commit content
202 #
202 #
203 """)
203 """)
204
204
205 class histeditstate(object):
205 class histeditstate(object):
206 def __init__(self, repo, parentctxnode=None, rules=None, keep=None,
206 def __init__(self, repo, parentctxnode=None, rules=None, keep=None,
207 topmost=None, replacements=None, lock=None, wlock=None):
207 topmost=None, replacements=None, lock=None, wlock=None):
208 self.repo = repo
208 self.repo = repo
209 self.rules = rules
209 self.rules = rules
210 self.keep = keep
210 self.keep = keep
211 self.topmost = topmost
211 self.topmost = topmost
212 self.parentctxnode = parentctxnode
212 self.parentctxnode = parentctxnode
213 self.lock = lock
213 self.lock = lock
214 self.wlock = wlock
214 self.wlock = wlock
215 self.backupfile = None
215 self.backupfile = None
216 if replacements is None:
216 if replacements is None:
217 self.replacements = []
217 self.replacements = []
218 else:
218 else:
219 self.replacements = replacements
219 self.replacements = replacements
220
220
221 def read(self):
221 def read(self):
222 """Load histedit state from disk and set fields appropriately."""
222 """Load histedit state from disk and set fields appropriately."""
223 try:
223 try:
224 fp = self.repo.vfs('histedit-state', 'r')
224 fp = self.repo.vfs('histedit-state', 'r')
225 except IOError as err:
225 except IOError as err:
226 if err.errno != errno.ENOENT:
226 if err.errno != errno.ENOENT:
227 raise
227 raise
228 raise util.Abort(_('no histedit in progress'))
228 raise util.Abort(_('no histedit in progress'))
229
229
230 try:
230 try:
231 data = pickle.load(fp)
231 data = pickle.load(fp)
232 parentctxnode, rules, keep, topmost, replacements = data
232 parentctxnode, rules, keep, topmost, replacements = data
233 backupfile = None
233 backupfile = None
234 except pickle.UnpicklingError:
234 except pickle.UnpicklingError:
235 data = self._load()
235 data = self._load()
236 parentctxnode, rules, keep, topmost, replacements, backupfile = data
236 parentctxnode, rules, keep, topmost, replacements, backupfile = data
237
237
238 self.parentctxnode = parentctxnode
238 self.parentctxnode = parentctxnode
239 self.rules = rules
239 self.rules = rules
240 self.keep = keep
240 self.keep = keep
241 self.topmost = topmost
241 self.topmost = topmost
242 self.replacements = replacements
242 self.replacements = replacements
243 self.backupfile = backupfile
243 self.backupfile = backupfile
244
244
245 def write(self):
245 def write(self):
246 fp = self.repo.vfs('histedit-state', 'w')
246 fp = self.repo.vfs('histedit-state', 'w')
247 fp.write('v1\n')
247 fp.write('v1\n')
248 fp.write('%s\n' % node.hex(self.parentctxnode))
248 fp.write('%s\n' % node.hex(self.parentctxnode))
249 fp.write('%s\n' % node.hex(self.topmost))
249 fp.write('%s\n' % node.hex(self.topmost))
250 fp.write('%s\n' % self.keep)
250 fp.write('%s\n' % self.keep)
251 fp.write('%d\n' % len(self.rules))
251 fp.write('%d\n' % len(self.rules))
252 for rule in self.rules:
252 for rule in self.rules:
253 fp.write('%s\n' % rule[0]) # action
253 fp.write('%s\n' % rule[0]) # action
254 fp.write('%s\n' % rule[1]) # remainder
254 fp.write('%s\n' % rule[1]) # remainder
255 fp.write('%d\n' % len(self.replacements))
255 fp.write('%d\n' % len(self.replacements))
256 for replacement in self.replacements:
256 for replacement in self.replacements:
257 fp.write('%s%s\n' % (node.hex(replacement[0]), ''.join(node.hex(r)
257 fp.write('%s%s\n' % (node.hex(replacement[0]), ''.join(node.hex(r)
258 for r in replacement[1])))
258 for r in replacement[1])))
259 backupfile = self.backupfile
259 backupfile = self.backupfile
260 if not backupfile:
260 if not backupfile:
261 backupfile = ''
261 backupfile = ''
262 fp.write('%s\n' % backupfile)
262 fp.write('%s\n' % backupfile)
263 fp.close()
263 fp.close()
264
264
265 def _load(self):
265 def _load(self):
266 fp = self.repo.vfs('histedit-state', 'r')
266 fp = self.repo.vfs('histedit-state', 'r')
267 lines = [l[:-1] for l in fp.readlines()]
267 lines = [l[:-1] for l in fp.readlines()]
268
268
269 index = 0
269 index = 0
270 lines[index] # version number
270 lines[index] # version number
271 index += 1
271 index += 1
272
272
273 parentctxnode = node.bin(lines[index])
273 parentctxnode = node.bin(lines[index])
274 index += 1
274 index += 1
275
275
276 topmost = node.bin(lines[index])
276 topmost = node.bin(lines[index])
277 index += 1
277 index += 1
278
278
279 keep = lines[index] == 'True'
279 keep = lines[index] == 'True'
280 index += 1
280 index += 1
281
281
282 # Rules
282 # Rules
283 rules = []
283 rules = []
284 rulelen = int(lines[index])
284 rulelen = int(lines[index])
285 index += 1
285 index += 1
286 for i in xrange(rulelen):
286 for i in xrange(rulelen):
287 ruleaction = lines[index]
287 ruleaction = lines[index]
288 index += 1
288 index += 1
289 rule = lines[index]
289 rule = lines[index]
290 index += 1
290 index += 1
291 rules.append((ruleaction, rule))
291 rules.append((ruleaction, rule))
292
292
293 # Replacements
293 # Replacements
294 replacements = []
294 replacements = []
295 replacementlen = int(lines[index])
295 replacementlen = int(lines[index])
296 index += 1
296 index += 1
297 for i in xrange(replacementlen):
297 for i in xrange(replacementlen):
298 replacement = lines[index]
298 replacement = lines[index]
299 original = node.bin(replacement[:40])
299 original = node.bin(replacement[:40])
300 succ = [node.bin(replacement[i:i + 40]) for i in
300 succ = [node.bin(replacement[i:i + 40]) for i in
301 range(40, len(replacement), 40)]
301 range(40, len(replacement), 40)]
302 replacements.append((original, succ))
302 replacements.append((original, succ))
303 index += 1
303 index += 1
304
304
305 backupfile = lines[index]
305 backupfile = lines[index]
306 index += 1
306 index += 1
307
307
308 fp.close()
308 fp.close()
309
309
310 return parentctxnode, rules, keep, topmost, replacements, backupfile
310 return parentctxnode, rules, keep, topmost, replacements, backupfile
311
311
312 def clear(self):
312 def clear(self):
313 self.repo.vfs.unlink('histedit-state')
313 self.repo.vfs.unlink('histedit-state')
314
314
315 class histeditaction(object):
315 class histeditaction(object):
316 def __init__(self, state, node):
316 def __init__(self, state, node):
317 self.state = state
317 self.state = state
318 self.repo = state.repo
318 self.repo = state.repo
319 self.node = node
319 self.node = node
320
320
321 @classmethod
321 @classmethod
322 def fromrule(cls, state, rule):
322 def fromrule(cls, state, rule):
323 """Parses the given rule, returning an instance of the histeditaction.
323 """Parses the given rule, returning an instance of the histeditaction.
324 """
324 """
325 repo = state.repo
325 repo = state.repo
326 rulehash = rule.strip().split(' ', 1)[0]
326 rulehash = rule.strip().split(' ', 1)[0]
327 try:
327 try:
328 node = repo[rulehash].node()
328 node = repo[rulehash].node()
329 except error.RepoError:
329 except error.RepoError:
330 raise util.Abort(_('unknown changeset %s listed') % rulehash[:12])
330 raise util.Abort(_('unknown changeset %s listed') % rulehash[:12])
331 return cls(state, node)
331 return cls(state, node)
332
332
333 def run(self):
333 def run(self):
334 """Runs the action. The default behavior is simply apply the action's
334 """Runs the action. The default behavior is simply apply the action's
335 rulectx onto the current parentctx."""
335 rulectx onto the current parentctx."""
336 self.applychange()
336 self.applychange()
337 self.continuedirty()
337 self.continuedirty()
338 return self.continueclean()
338 return self.continueclean()
339
339
340 def applychange(self):
340 def applychange(self):
341 """Applies the changes from this action's rulectx onto the current
341 """Applies the changes from this action's rulectx onto the current
342 parentctx, but does not commit them."""
342 parentctx, but does not commit them."""
343 repo = self.repo
343 repo = self.repo
344 rulectx = repo[self.node]
344 rulectx = repo[self.node]
345 hg.update(repo, self.state.parentctxnode)
345 hg.update(repo, self.state.parentctxnode)
346 stats = applychanges(repo.ui, repo, rulectx, {})
346 stats = applychanges(repo.ui, repo, rulectx, {})
347 if stats and stats[3] > 0:
347 if stats and stats[3] > 0:
348 raise error.InterventionRequired(_('Fix up the change and run '
348 raise error.InterventionRequired(_('Fix up the change and run '
349 'hg histedit --continue'))
349 'hg histedit --continue'))
350
350
351 def continuedirty(self):
351 def continuedirty(self):
352 """Continues the action when changes have been applied to the working
352 """Continues the action when changes have been applied to the working
353 copy. The default behavior is to commit the dirty changes."""
353 copy. The default behavior is to commit the dirty changes."""
354 repo = self.repo
354 repo = self.repo
355 rulectx = repo[self.node]
355 rulectx = repo[self.node]
356
356
357 editor = self.commiteditor()
357 editor = self.commiteditor()
358 commit = commitfuncfor(repo, rulectx)
358 commit = commitfuncfor(repo, rulectx)
359
359
360 commit(text=rulectx.description(), user=rulectx.user(),
360 commit(text=rulectx.description(), user=rulectx.user(),
361 date=rulectx.date(), extra=rulectx.extra(), editor=editor)
361 date=rulectx.date(), extra=rulectx.extra(), editor=editor)
362
362
363 def commiteditor(self):
363 def commiteditor(self):
364 """The editor to be used to edit the commit message."""
364 """The editor to be used to edit the commit message."""
365 return False
365 return False
366
366
367 def continueclean(self):
367 def continueclean(self):
368 """Continues the action when the working copy is clean. The default
368 """Continues the action when the working copy is clean. The default
369 behavior is to accept the current commit as the new version of the
369 behavior is to accept the current commit as the new version of the
370 rulectx."""
370 rulectx."""
371 ctx = self.repo['.']
371 ctx = self.repo['.']
372 if ctx.node() == self.state.parentctxnode:
372 if ctx.node() == self.state.parentctxnode:
373 self.repo.ui.warn(_('%s: empty changeset\n') %
373 self.repo.ui.warn(_('%s: empty changeset\n') %
374 node.short(self.node))
374 node.short(self.node))
375 return ctx, [(self.node, tuple())]
375 return ctx, [(self.node, tuple())]
376 if ctx.node() == self.node:
376 if ctx.node() == self.node:
377 # Nothing changed
377 # Nothing changed
378 return ctx, []
378 return ctx, []
379 return ctx, [(self.node, (ctx.node(),))]
379 return ctx, [(self.node, (ctx.node(),))]
380
380
381 def commitfuncfor(repo, src):
381 def commitfuncfor(repo, src):
382 """Build a commit function for the replacement of <src>
382 """Build a commit function for the replacement of <src>
383
383
384 This function ensure we apply the same treatment to all changesets.
384 This function ensure we apply the same treatment to all changesets.
385
385
386 - Add a 'histedit_source' entry in extra.
386 - Add a 'histedit_source' entry in extra.
387
387
388 Note that fold has its own separated logic because its handling is a bit
388 Note that fold has its own separated logic because its handling is a bit
389 different and not easily factored out of the fold method.
389 different and not easily factored out of the fold method.
390 """
390 """
391 phasemin = src.phase()
391 phasemin = src.phase()
392 def commitfunc(**kwargs):
392 def commitfunc(**kwargs):
393 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
393 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
394 try:
394 try:
395 repo.ui.setconfig('phases', 'new-commit', phasemin,
395 repo.ui.setconfig('phases', 'new-commit', phasemin,
396 'histedit')
396 'histedit')
397 extra = kwargs.get('extra', {}).copy()
397 extra = kwargs.get('extra', {}).copy()
398 extra['histedit_source'] = src.hex()
398 extra['histedit_source'] = src.hex()
399 kwargs['extra'] = extra
399 kwargs['extra'] = extra
400 return repo.commit(**kwargs)
400 return repo.commit(**kwargs)
401 finally:
401 finally:
402 repo.ui.restoreconfig(phasebackup)
402 repo.ui.restoreconfig(phasebackup)
403 return commitfunc
403 return commitfunc
404
404
405 def applychanges(ui, repo, ctx, opts):
405 def applychanges(ui, repo, ctx, opts):
406 """Merge changeset from ctx (only) in the current working directory"""
406 """Merge changeset from ctx (only) in the current working directory"""
407 wcpar = repo.dirstate.parents()[0]
407 wcpar = repo.dirstate.parents()[0]
408 if ctx.p1().node() == wcpar:
408 if ctx.p1().node() == wcpar:
409 # edition ar "in place" we do not need to make any merge,
409 # edition ar "in place" we do not need to make any merge,
410 # just applies changes on parent for edition
410 # just applies changes on parent for edition
411 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
411 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
412 stats = None
412 stats = None
413 else:
413 else:
414 try:
414 try:
415 # ui.forcemerge is an internal variable, do not document
415 # ui.forcemerge is an internal variable, do not document
416 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
416 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
417 'histedit')
417 'histedit')
418 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit'])
418 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit'])
419 finally:
419 finally:
420 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit')
420 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit')
421 return stats
421 return stats
422
422
423 def collapse(repo, first, last, commitopts, skipprompt=False):
423 def collapse(repo, first, last, commitopts, skipprompt=False):
424 """collapse the set of revisions from first to last as new one.
424 """collapse the set of revisions from first to last as new one.
425
425
426 Expected commit options are:
426 Expected commit options are:
427 - message
427 - message
428 - date
428 - date
429 - username
429 - username
430 Commit message is edited in all cases.
430 Commit message is edited in all cases.
431
431
432 This function works in memory."""
432 This function works in memory."""
433 ctxs = list(repo.set('%d::%d', first, last))
433 ctxs = list(repo.set('%d::%d', first, last))
434 if not ctxs:
434 if not ctxs:
435 return None
435 return None
436 for c in ctxs:
436 for c in ctxs:
437 if not c.mutable():
437 if not c.mutable():
438 raise util.Abort(
438 raise util.Abort(
439 _("cannot fold into public change %s") % node.short(c.node()))
439 _("cannot fold into public change %s") % node.short(c.node()))
440 base = first.parents()[0]
440 base = first.parents()[0]
441
441
442 # commit a new version of the old changeset, including the update
442 # commit a new version of the old changeset, including the update
443 # collect all files which might be affected
443 # collect all files which might be affected
444 files = set()
444 files = set()
445 for ctx in ctxs:
445 for ctx in ctxs:
446 files.update(ctx.files())
446 files.update(ctx.files())
447
447
448 # Recompute copies (avoid recording a -> b -> a)
448 # Recompute copies (avoid recording a -> b -> a)
449 copied = copies.pathcopies(base, last)
449 copied = copies.pathcopies(base, last)
450
450
451 # prune files which were reverted by the updates
451 # prune files which were reverted by the updates
452 def samefile(f):
452 def samefile(f):
453 if f in last.manifest():
453 if f in last.manifest():
454 a = last.filectx(f)
454 a = last.filectx(f)
455 if f in base.manifest():
455 if f in base.manifest():
456 b = base.filectx(f)
456 b = base.filectx(f)
457 return (a.data() == b.data()
457 return (a.data() == b.data()
458 and a.flags() == b.flags())
458 and a.flags() == b.flags())
459 else:
459 else:
460 return False
460 return False
461 else:
461 else:
462 return f not in base.manifest()
462 return f not in base.manifest()
463 files = [f for f in files if not samefile(f)]
463 files = [f for f in files if not samefile(f)]
464 # commit version of these files as defined by head
464 # commit version of these files as defined by head
465 headmf = last.manifest()
465 headmf = last.manifest()
466 def filectxfn(repo, ctx, path):
466 def filectxfn(repo, ctx, path):
467 if path in headmf:
467 if path in headmf:
468 fctx = last[path]
468 fctx = last[path]
469 flags = fctx.flags()
469 flags = fctx.flags()
470 mctx = context.memfilectx(repo,
470 mctx = context.memfilectx(repo,
471 fctx.path(), fctx.data(),
471 fctx.path(), fctx.data(),
472 islink='l' in flags,
472 islink='l' in flags,
473 isexec='x' in flags,
473 isexec='x' in flags,
474 copied=copied.get(path))
474 copied=copied.get(path))
475 return mctx
475 return mctx
476 return None
476 return None
477
477
478 if commitopts.get('message'):
478 if commitopts.get('message'):
479 message = commitopts['message']
479 message = commitopts['message']
480 else:
480 else:
481 message = first.description()
481 message = first.description()
482 user = commitopts.get('user')
482 user = commitopts.get('user')
483 date = commitopts.get('date')
483 date = commitopts.get('date')
484 extra = commitopts.get('extra')
484 extra = commitopts.get('extra')
485
485
486 parents = (first.p1().node(), first.p2().node())
486 parents = (first.p1().node(), first.p2().node())
487 editor = None
487 editor = None
488 if not skipprompt:
488 if not skipprompt:
489 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.fold')
489 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.fold')
490 new = context.memctx(repo,
490 new = context.memctx(repo,
491 parents=parents,
491 parents=parents,
492 text=message,
492 text=message,
493 files=files,
493 files=files,
494 filectxfn=filectxfn,
494 filectxfn=filectxfn,
495 user=user,
495 user=user,
496 date=date,
496 date=date,
497 extra=extra,
497 extra=extra,
498 editor=editor)
498 editor=editor)
499 return repo.commitctx(new)
499 return repo.commitctx(new)
500
500
501 class pick(histeditaction):
501 class pick(histeditaction):
502 def run(self):
502 def run(self):
503 rulectx = self.repo[self.node]
503 rulectx = self.repo[self.node]
504 if rulectx.parents()[0].node() == self.state.parentctxnode:
504 if rulectx.parents()[0].node() == self.state.parentctxnode:
505 self.repo.ui.debug('node %s unchanged\n' % node.short(self.node))
505 self.repo.ui.debug('node %s unchanged\n' % node.short(self.node))
506 return rulectx, []
506 return rulectx, []
507
507
508 return super(pick, self).run()
508 return super(pick, self).run()
509
509
510 class edit(histeditaction):
510 class edit(histeditaction):
511 def run(self):
511 def run(self):
512 repo = self.repo
512 repo = self.repo
513 rulectx = repo[self.node]
513 rulectx = repo[self.node]
514 hg.update(repo, self.state.parentctxnode)
514 hg.update(repo, self.state.parentctxnode)
515 applychanges(repo.ui, repo, rulectx, {})
515 applychanges(repo.ui, repo, rulectx, {})
516 raise error.InterventionRequired(
516 raise error.InterventionRequired(
517 _('Make changes as needed, you may commit or record as needed '
517 _('Make changes as needed, you may commit or record as needed '
518 'now.\nWhen you are finished, run hg histedit --continue to '
518 'now.\nWhen you are finished, run hg histedit --continue to '
519 'resume.'))
519 'resume.'))
520
520
521 def commiteditor(self):
521 def commiteditor(self):
522 return cmdutil.getcommiteditor(edit=True, editform='histedit.edit')
522 return cmdutil.getcommiteditor(edit=True, editform='histedit.edit')
523
523
524 class fold(histeditaction):
524 class fold(histeditaction):
525 def continuedirty(self):
525 def continuedirty(self):
526 repo = self.repo
526 repo = self.repo
527 rulectx = repo[self.node]
527 rulectx = repo[self.node]
528
528
529 commit = commitfuncfor(repo, rulectx)
529 commit = commitfuncfor(repo, rulectx)
530 commit(text='fold-temp-revision %s' % node.short(self.node),
530 commit(text='fold-temp-revision %s' % node.short(self.node),
531 user=rulectx.user(), date=rulectx.date(),
531 user=rulectx.user(), date=rulectx.date(),
532 extra=rulectx.extra())
532 extra=rulectx.extra())
533
533
534 def continueclean(self):
534 def continueclean(self):
535 repo = self.repo
535 repo = self.repo
536 ctx = repo['.']
536 ctx = repo['.']
537 rulectx = repo[self.node]
537 rulectx = repo[self.node]
538 parentctxnode = self.state.parentctxnode
538 parentctxnode = self.state.parentctxnode
539 if ctx.node() == parentctxnode:
539 if ctx.node() == parentctxnode:
540 repo.ui.warn(_('%s: empty changeset\n') %
540 repo.ui.warn(_('%s: empty changeset\n') %
541 node.short(self.node))
541 node.short(self.node))
542 return ctx, [(self.node, (parentctxnode,))]
542 return ctx, [(self.node, (parentctxnode,))]
543
543
544 parentctx = repo[parentctxnode]
544 parentctx = repo[parentctxnode]
545 newcommits = set(c.node() for c in repo.set('(%d::. - %d)', parentctx,
545 newcommits = set(c.node() for c in repo.set('(%d::. - %d)', parentctx,
546 parentctx))
546 parentctx))
547 if not newcommits:
547 if not newcommits:
548 repo.ui.warn(_('%s: cannot fold - working copy is not a '
548 repo.ui.warn(_('%s: cannot fold - working copy is not a '
549 'descendant of previous commit %s\n') %
549 'descendant of previous commit %s\n') %
550 (node.short(self.node), node.short(parentctxnode)))
550 (node.short(self.node), node.short(parentctxnode)))
551 return ctx, [(self.node, (ctx.node(),))]
551 return ctx, [(self.node, (ctx.node(),))]
552
552
553 middlecommits = newcommits.copy()
553 middlecommits = newcommits.copy()
554 middlecommits.discard(ctx.node())
554 middlecommits.discard(ctx.node())
555
555
556 return self.finishfold(repo.ui, repo, parentctx, rulectx, ctx.node(),
556 return self.finishfold(repo.ui, repo, parentctx, rulectx, ctx.node(),
557 middlecommits)
557 middlecommits)
558
558
559 def skipprompt(self):
559 def skipprompt(self):
560 return False
560 return False
561
561
562 def finishfold(self, ui, repo, ctx, oldctx, newnode, internalchanges):
562 def finishfold(self, ui, repo, ctx, oldctx, newnode, internalchanges):
563 parent = ctx.parents()[0].node()
563 parent = ctx.parents()[0].node()
564 hg.update(repo, parent)
564 hg.update(repo, parent)
565 ### prepare new commit data
565 ### prepare new commit data
566 commitopts = {}
566 commitopts = {}
567 commitopts['user'] = ctx.user()
567 commitopts['user'] = ctx.user()
568 # commit message
568 # commit message
569 if self.skipprompt():
569 if self.skipprompt():
570 newmessage = ctx.description()
570 newmessage = ctx.description()
571 else:
571 else:
572 newmessage = '\n***\n'.join(
572 newmessage = '\n***\n'.join(
573 [ctx.description()] +
573 [ctx.description()] +
574 [repo[r].description() for r in internalchanges] +
574 [repo[r].description() for r in internalchanges] +
575 [oldctx.description()]) + '\n'
575 [oldctx.description()]) + '\n'
576 commitopts['message'] = newmessage
576 commitopts['message'] = newmessage
577 # date
577 # date
578 commitopts['date'] = max(ctx.date(), oldctx.date())
578 commitopts['date'] = max(ctx.date(), oldctx.date())
579 extra = ctx.extra().copy()
579 extra = ctx.extra().copy()
580 # histedit_source
580 # histedit_source
581 # note: ctx is likely a temporary commit but that the best we can do
581 # note: ctx is likely a temporary commit but that the best we can do
582 # here. This is sufficient to solve issue3681 anyway.
582 # here. This is sufficient to solve issue3681 anyway.
583 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
583 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
584 commitopts['extra'] = extra
584 commitopts['extra'] = extra
585 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
585 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
586 try:
586 try:
587 phasemin = max(ctx.phase(), oldctx.phase())
587 phasemin = max(ctx.phase(), oldctx.phase())
588 repo.ui.setconfig('phases', 'new-commit', phasemin, 'histedit')
588 repo.ui.setconfig('phases', 'new-commit', phasemin, 'histedit')
589 n = collapse(repo, ctx, repo[newnode], commitopts,
589 n = collapse(repo, ctx, repo[newnode], commitopts,
590 skipprompt=self.skipprompt())
590 skipprompt=self.skipprompt())
591 finally:
591 finally:
592 repo.ui.restoreconfig(phasebackup)
592 repo.ui.restoreconfig(phasebackup)
593 if n is None:
593 if n is None:
594 return ctx, []
594 return ctx, []
595 hg.update(repo, n)
595 hg.update(repo, n)
596 replacements = [(oldctx.node(), (newnode,)),
596 replacements = [(oldctx.node(), (newnode,)),
597 (ctx.node(), (n,)),
597 (ctx.node(), (n,)),
598 (newnode, (n,)),
598 (newnode, (n,)),
599 ]
599 ]
600 for ich in internalchanges:
600 for ich in internalchanges:
601 replacements.append((ich, (n,)))
601 replacements.append((ich, (n,)))
602 return repo[n], replacements
602 return repo[n], replacements
603
603
604 class rollup(fold):
604 class rollup(fold):
605 def skipprompt(self):
605 def skipprompt(self):
606 return True
606 return True
607
607
608 class drop(histeditaction):
608 class drop(histeditaction):
609 def run(self):
609 def run(self):
610 parentctx = self.repo[self.state.parentctxnode]
610 parentctx = self.repo[self.state.parentctxnode]
611 return parentctx, [(self.node, tuple())]
611 return parentctx, [(self.node, tuple())]
612
612
613 class message(histeditaction):
613 class message(histeditaction):
614 def commiteditor(self):
614 def commiteditor(self):
615 return cmdutil.getcommiteditor(edit=True, editform='histedit.mess')
615 return cmdutil.getcommiteditor(edit=True, editform='histedit.mess')
616
616
617 def findoutgoing(ui, repo, remote=None, force=False, opts={}):
617 def findoutgoing(ui, repo, remote=None, force=False, opts={}):
618 """utility function to find the first outgoing changeset
618 """utility function to find the first outgoing changeset
619
619
620 Used by initialisation code"""
620 Used by initialisation code"""
621 dest = ui.expandpath(remote or 'default-push', remote or 'default')
621 dest = ui.expandpath(remote or 'default-push', remote or 'default')
622 dest, revs = hg.parseurl(dest, None)[:2]
622 dest, revs = hg.parseurl(dest, None)[:2]
623 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
623 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
624
624
625 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
625 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
626 other = hg.peer(repo, opts, dest)
626 other = hg.peer(repo, opts, dest)
627
627
628 if revs:
628 if revs:
629 revs = [repo.lookup(rev) for rev in revs]
629 revs = [repo.lookup(rev) for rev in revs]
630
630
631 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
631 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
632 if not outgoing.missing:
632 if not outgoing.missing:
633 raise util.Abort(_('no outgoing ancestors'))
633 raise util.Abort(_('no outgoing ancestors'))
634 roots = list(repo.revs("roots(%ln)", outgoing.missing))
634 roots = list(repo.revs("roots(%ln)", outgoing.missing))
635 if 1 < len(roots):
635 if 1 < len(roots):
636 msg = _('there are ambiguous outgoing revisions')
636 msg = _('there are ambiguous outgoing revisions')
637 hint = _('see "hg help histedit" for more detail')
637 hint = _('see "hg help histedit" for more detail')
638 raise util.Abort(msg, hint=hint)
638 raise util.Abort(msg, hint=hint)
639 return repo.lookup(roots[0])
639 return repo.lookup(roots[0])
640
640
641 actiontable = {'p': pick,
641 actiontable = {'p': pick,
642 'pick': pick,
642 'pick': pick,
643 'e': edit,
643 'e': edit,
644 'edit': edit,
644 'edit': edit,
645 'f': fold,
645 'f': fold,
646 'fold': fold,
646 'fold': fold,
647 'r': rollup,
647 'r': rollup,
648 'roll': rollup,
648 'roll': rollup,
649 'd': drop,
649 'd': drop,
650 'drop': drop,
650 'drop': drop,
651 'm': message,
651 'm': message,
652 'mess': message,
652 'mess': message,
653 }
653 }
654
654
655 @command('histedit',
655 @command('histedit',
656 [('', 'commands', '',
656 [('', 'commands', '',
657 _('read history edits from the specified file'), _('FILE')),
657 _('read history edits from the specified file'), _('FILE')),
658 ('c', 'continue', False, _('continue an edit already in progress')),
658 ('c', 'continue', False, _('continue an edit already in progress')),
659 ('', 'edit-plan', False, _('edit remaining actions list')),
659 ('', 'edit-plan', False, _('edit remaining actions list')),
660 ('k', 'keep', False,
660 ('k', 'keep', False,
661 _("don't strip old nodes after edit is complete")),
661 _("don't strip old nodes after edit is complete")),
662 ('', 'abort', False, _('abort an edit in progress')),
662 ('', 'abort', False, _('abort an edit in progress')),
663 ('o', 'outgoing', False, _('changesets not found in destination')),
663 ('o', 'outgoing', False, _('changesets not found in destination')),
664 ('f', 'force', False,
664 ('f', 'force', False,
665 _('force outgoing even for unrelated repositories')),
665 _('force outgoing even for unrelated repositories')),
666 ('r', 'rev', [], _('first revision to be edited'), _('REV'))],
666 ('r', 'rev', [], _('first revision to be edited'), _('REV'))],
667 _("ANCESTOR | --outgoing [URL]"))
667 _("ANCESTOR | --outgoing [URL]"))
668 def histedit(ui, repo, *freeargs, **opts):
668 def histedit(ui, repo, *freeargs, **opts):
669 """interactively edit changeset history
669 """interactively edit changeset history
670
670
671 This command edits changesets between ANCESTOR and the parent of
671 This command edits changesets between ANCESTOR and the parent of
672 the working directory.
672 the working directory.
673
673
674 With --outgoing, this edits changesets not found in the
674 With --outgoing, this edits changesets not found in the
675 destination repository. If URL of the destination is omitted, the
675 destination repository. If URL of the destination is omitted, the
676 'default-push' (or 'default') path will be used.
676 'default-push' (or 'default') path will be used.
677
677
678 For safety, this command is aborted, also if there are ambiguous
678 For safety, this command is aborted, also if there are ambiguous
679 outgoing revisions which may confuse users: for example, there are
679 outgoing revisions which may confuse users: for example, there are
680 multiple branches containing outgoing revisions.
680 multiple branches containing outgoing revisions.
681
681
682 Use "min(outgoing() and ::.)" or similar revset specification
682 Use "min(outgoing() and ::.)" or similar revset specification
683 instead of --outgoing to specify edit target revision exactly in
683 instead of --outgoing to specify edit target revision exactly in
684 such ambiguous situation. See :hg:`help revsets` for detail about
684 such ambiguous situation. See :hg:`help revsets` for detail about
685 selecting revisions.
685 selecting revisions.
686
686
687 Returns 0 on success, 1 if user intervention is required (not only
687 Returns 0 on success, 1 if user intervention is required (not only
688 for intentional "edit" command, but also for resolving unexpected
688 for intentional "edit" command, but also for resolving unexpected
689 conflicts).
689 conflicts).
690 """
690 """
691 state = histeditstate(repo)
691 state = histeditstate(repo)
692 try:
692 try:
693 state.wlock = repo.wlock()
693 state.wlock = repo.wlock()
694 state.lock = repo.lock()
694 state.lock = repo.lock()
695 _histedit(ui, repo, state, *freeargs, **opts)
695 _histedit(ui, repo, state, *freeargs, **opts)
696 finally:
696 finally:
697 release(state.lock, state.wlock)
697 release(state.lock, state.wlock)
698
698
699 def _histedit(ui, repo, state, *freeargs, **opts):
699 def _histedit(ui, repo, state, *freeargs, **opts):
700 # TODO only abort if we try and histedit mq patches, not just
700 # TODO only abort if we try and histedit mq patches, not just
701 # blanket if mq patches are applied somewhere
701 # blanket if mq patches are applied somewhere
702 mq = getattr(repo, 'mq', None)
702 mq = getattr(repo, 'mq', None)
703 if mq and mq.applied:
703 if mq and mq.applied:
704 raise util.Abort(_('source has mq patches applied'))
704 raise util.Abort(_('source has mq patches applied'))
705
705
706 # basic argument incompatibility processing
706 # basic argument incompatibility processing
707 outg = opts.get('outgoing')
707 outg = opts.get('outgoing')
708 cont = opts.get('continue')
708 cont = opts.get('continue')
709 editplan = opts.get('edit_plan')
709 editplan = opts.get('edit_plan')
710 abort = opts.get('abort')
710 abort = opts.get('abort')
711 force = opts.get('force')
711 force = opts.get('force')
712 rules = opts.get('commands', '')
712 rules = opts.get('commands', '')
713 revs = opts.get('rev', [])
713 revs = opts.get('rev', [])
714 goal = 'new' # This invocation goal, in new, continue, abort
714 goal = 'new' # This invocation goal, in new, continue, abort
715 if force and not outg:
715 if force and not outg:
716 raise util.Abort(_('--force only allowed with --outgoing'))
716 raise util.Abort(_('--force only allowed with --outgoing'))
717 if cont:
717 if cont:
718 if any((outg, abort, revs, freeargs, rules, editplan)):
718 if any((outg, abort, revs, freeargs, rules, editplan)):
719 raise util.Abort(_('no arguments allowed with --continue'))
719 raise util.Abort(_('no arguments allowed with --continue'))
720 goal = 'continue'
720 goal = 'continue'
721 elif abort:
721 elif abort:
722 if any((outg, revs, freeargs, rules, editplan)):
722 if any((outg, revs, freeargs, rules, editplan)):
723 raise util.Abort(_('no arguments allowed with --abort'))
723 raise util.Abort(_('no arguments allowed with --abort'))
724 goal = 'abort'
724 goal = 'abort'
725 elif editplan:
725 elif editplan:
726 if any((outg, revs, freeargs)):
726 if any((outg, revs, freeargs)):
727 raise util.Abort(_('only --commands argument allowed with '
727 raise util.Abort(_('only --commands argument allowed with '
728 '--edit-plan'))
728 '--edit-plan'))
729 goal = 'edit-plan'
729 goal = 'edit-plan'
730 else:
730 else:
731 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
731 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
732 raise util.Abort(_('history edit already in progress, try '
732 raise util.Abort(_('history edit already in progress, try '
733 '--continue or --abort'))
733 '--continue or --abort'))
734 if outg:
734 if outg:
735 if revs:
735 if revs:
736 raise util.Abort(_('no revisions allowed with --outgoing'))
736 raise util.Abort(_('no revisions allowed with --outgoing'))
737 if len(freeargs) > 1:
737 if len(freeargs) > 1:
738 raise util.Abort(
738 raise util.Abort(
739 _('only one repo argument allowed with --outgoing'))
739 _('only one repo argument allowed with --outgoing'))
740 else:
740 else:
741 revs.extend(freeargs)
741 revs.extend(freeargs)
742 if len(revs) == 0:
742 if len(revs) == 0:
743 histeditdefault = ui.config('histedit', 'defaultrev')
743 histeditdefault = ui.config('histedit', 'defaultrev')
744 if histeditdefault:
744 if histeditdefault:
745 revs.append(histeditdefault)
745 revs.append(histeditdefault)
746 if len(revs) != 1:
746 if len(revs) != 1:
747 raise util.Abort(
747 raise util.Abort(
748 _('histedit requires exactly one ancestor revision'))
748 _('histedit requires exactly one ancestor revision'))
749
749
750
750
751 replacements = []
751 replacements = []
752 state.keep = opts.get('keep', False)
752 state.keep = opts.get('keep', False)
753 supportsmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
753 supportsmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
754
754
755 # rebuild state
755 # rebuild state
756 if goal == 'continue':
756 if goal == 'continue':
757 state.read()
757 state.read()
758 state = bootstrapcontinue(ui, state, opts)
758 state = bootstrapcontinue(ui, state, opts)
759 elif goal == 'edit-plan':
759 elif goal == 'edit-plan':
760 state.read()
760 state.read()
761 if not rules:
761 if not rules:
762 comment = editcomment % (node.short(state.parentctxnode),
762 comment = editcomment % (node.short(state.parentctxnode),
763 node.short(state.topmost))
763 node.short(state.topmost))
764 rules = ruleeditor(repo, ui, state.rules, comment)
764 rules = ruleeditor(repo, ui, state.rules, comment)
765 else:
765 else:
766 if rules == '-':
766 if rules == '-':
767 f = sys.stdin
767 f = sys.stdin
768 else:
768 else:
769 f = open(rules)
769 f = open(rules)
770 rules = f.read()
770 rules = f.read()
771 f.close()
771 f.close()
772 rules = [l for l in (r.strip() for r in rules.splitlines())
772 rules = [l for l in (r.strip() for r in rules.splitlines())
773 if l and not l.startswith('#')]
773 if l and not l.startswith('#')]
774 rules = verifyrules(rules, repo, [repo[c] for [_a, c] in state.rules])
774 rules = verifyrules(rules, repo, [repo[c] for [_a, c] in state.rules])
775 state.rules = rules
775 state.rules = rules
776 state.write()
776 state.write()
777 return
777 return
778 elif goal == 'abort':
778 elif goal == 'abort':
779 state.read()
779 state.read()
780 mapping, tmpnodes, leafs, _ntm = processreplacement(state)
780 mapping, tmpnodes, leafs, _ntm = processreplacement(state)
781 ui.debug('restore wc to old parent %s\n' % node.short(state.topmost))
781 ui.debug('restore wc to old parent %s\n' % node.short(state.topmost))
782
782
783 # Recover our old commits if necessary
783 # Recover our old commits if necessary
784 if not state.topmost in repo and state.backupfile:
784 if not state.topmost in repo and state.backupfile:
785 backupfile = repo.join(state.backupfile)
785 backupfile = repo.join(state.backupfile)
786 f = hg.openpath(ui, backupfile)
786 f = hg.openpath(ui, backupfile)
787 gen = exchange.readbundle(ui, f, backupfile)
787 gen = exchange.readbundle(ui, f, backupfile)
788 changegroup.addchangegroup(repo, gen, 'histedit',
788 changegroup.addchangegroup(repo, gen, 'histedit',
789 'bundle:' + backupfile)
789 'bundle:' + backupfile)
790 os.remove(backupfile)
790 os.remove(backupfile)
791
791
792 # check whether we should update away
792 # check whether we should update away
793 parentnodes = [c.node() for c in repo[None].parents()]
793 parentnodes = [c.node() for c in repo[None].parents()]
794 for n in leafs | set([state.parentctxnode]):
794 for n in leafs | set([state.parentctxnode]):
795 if n in parentnodes:
795 if n in parentnodes:
796 hg.clean(repo, state.topmost)
796 hg.clean(repo, state.topmost)
797 break
797 break
798 else:
798 else:
799 pass
799 pass
800 if supportsmarkers:
801 obsolete.createmarkers(repo,
802 ((repo[t],()) for t in sorted(tmpnodes)))
803 obsolete.createmarkers(repo, ((repo[t],()) for t in sorted(leafs)))
804 else:
800 cleanupnode(ui, repo, 'created', tmpnodes)
805 cleanupnode(ui, repo, 'created', tmpnodes)
801 cleanupnode(ui, repo, 'temp', leafs)
806 cleanupnode(ui, repo, 'temp', leafs)
802 state.clear()
807 state.clear()
803 return
808 return
804 else:
809 else:
805 cmdutil.checkunfinished(repo)
810 cmdutil.checkunfinished(repo)
806 cmdutil.bailifchanged(repo)
811 cmdutil.bailifchanged(repo)
807
812
808 topmost, empty = repo.dirstate.parents()
813 topmost, empty = repo.dirstate.parents()
809 if outg:
814 if outg:
810 if freeargs:
815 if freeargs:
811 remote = freeargs[0]
816 remote = freeargs[0]
812 else:
817 else:
813 remote = None
818 remote = None
814 root = findoutgoing(ui, repo, remote, force, opts)
819 root = findoutgoing(ui, repo, remote, force, opts)
815 else:
820 else:
816 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
821 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
817 if len(rr) != 1:
822 if len(rr) != 1:
818 raise util.Abort(_('The specified revisions must have '
823 raise util.Abort(_('The specified revisions must have '
819 'exactly one common root'))
824 'exactly one common root'))
820 root = rr[0].node()
825 root = rr[0].node()
821
826
822 revs = between(repo, root, topmost, state.keep)
827 revs = between(repo, root, topmost, state.keep)
823 if not revs:
828 if not revs:
824 raise util.Abort(_('%s is not an ancestor of working directory') %
829 raise util.Abort(_('%s is not an ancestor of working directory') %
825 node.short(root))
830 node.short(root))
826
831
827 ctxs = [repo[r] for r in revs]
832 ctxs = [repo[r] for r in revs]
828 if not rules:
833 if not rules:
829 comment = editcomment % (node.short(root), node.short(topmost))
834 comment = editcomment % (node.short(root), node.short(topmost))
830 rules = ruleeditor(repo, ui, [['pick', c] for c in ctxs], comment)
835 rules = ruleeditor(repo, ui, [['pick', c] for c in ctxs], comment)
831 else:
836 else:
832 if rules == '-':
837 if rules == '-':
833 f = sys.stdin
838 f = sys.stdin
834 else:
839 else:
835 f = open(rules)
840 f = open(rules)
836 rules = f.read()
841 rules = f.read()
837 f.close()
842 f.close()
838 rules = [l for l in (r.strip() for r in rules.splitlines())
843 rules = [l for l in (r.strip() for r in rules.splitlines())
839 if l and not l.startswith('#')]
844 if l and not l.startswith('#')]
840 rules = verifyrules(rules, repo, ctxs)
845 rules = verifyrules(rules, repo, ctxs)
841
846
842 parentctxnode = repo[root].parents()[0].node()
847 parentctxnode = repo[root].parents()[0].node()
843
848
844 state.parentctxnode = parentctxnode
849 state.parentctxnode = parentctxnode
845 state.rules = rules
850 state.rules = rules
846 state.topmost = topmost
851 state.topmost = topmost
847 state.replacements = replacements
852 state.replacements = replacements
848
853
849 # Create a backup so we can always abort completely.
854 # Create a backup so we can always abort completely.
850 backupfile = None
855 backupfile = None
851 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
856 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
852 backupfile = repair._bundle(repo, [parentctxnode], [topmost], root,
857 backupfile = repair._bundle(repo, [parentctxnode], [topmost], root,
853 'histedit')
858 'histedit')
854 state.backupfile = backupfile
859 state.backupfile = backupfile
855
860
856 while state.rules:
861 while state.rules:
857 state.write()
862 state.write()
858 action, ha = state.rules.pop(0)
863 action, ha = state.rules.pop(0)
859 ui.debug('histedit: processing %s %s\n' % (action, ha[:12]))
864 ui.debug('histedit: processing %s %s\n' % (action, ha[:12]))
860 actobj = actiontable[action].fromrule(state, ha)
865 actobj = actiontable[action].fromrule(state, ha)
861 parentctx, replacement_ = actobj.run()
866 parentctx, replacement_ = actobj.run()
862 state.parentctxnode = parentctx.node()
867 state.parentctxnode = parentctx.node()
863 state.replacements.extend(replacement_)
868 state.replacements.extend(replacement_)
864 state.write()
869 state.write()
865
870
866 hg.update(repo, state.parentctxnode)
871 hg.update(repo, state.parentctxnode)
867
872
868 mapping, tmpnodes, created, ntm = processreplacement(state)
873 mapping, tmpnodes, created, ntm = processreplacement(state)
869 if mapping:
874 if mapping:
870 for prec, succs in mapping.iteritems():
875 for prec, succs in mapping.iteritems():
871 if not succs:
876 if not succs:
872 ui.debug('histedit: %s is dropped\n' % node.short(prec))
877 ui.debug('histedit: %s is dropped\n' % node.short(prec))
873 else:
878 else:
874 ui.debug('histedit: %s is replaced by %s\n' % (
879 ui.debug('histedit: %s is replaced by %s\n' % (
875 node.short(prec), node.short(succs[0])))
880 node.short(prec), node.short(succs[0])))
876 if len(succs) > 1:
881 if len(succs) > 1:
877 m = 'histedit: %s'
882 m = 'histedit: %s'
878 for n in succs[1:]:
883 for n in succs[1:]:
879 ui.debug(m % node.short(n))
884 ui.debug(m % node.short(n))
880
885
881 if not state.keep:
886 if not state.keep:
882 if mapping:
887 if mapping:
883 movebookmarks(ui, repo, mapping, state.topmost, ntm)
888 movebookmarks(ui, repo, mapping, state.topmost, ntm)
884 # TODO update mq state
889 # TODO update mq state
885 if supportsmarkers:
890 if supportsmarkers:
886 markers = []
891 markers = []
887 # sort by revision number because it sound "right"
892 # sort by revision number because it sound "right"
888 for prec in sorted(mapping, key=repo.changelog.rev):
893 for prec in sorted(mapping, key=repo.changelog.rev):
889 succs = mapping[prec]
894 succs = mapping[prec]
890 markers.append((repo[prec],
895 markers.append((repo[prec],
891 tuple(repo[s] for s in succs)))
896 tuple(repo[s] for s in succs)))
892 if markers:
897 if markers:
893 obsolete.createmarkers(repo, markers)
898 obsolete.createmarkers(repo, markers)
894 else:
899 else:
895 cleanupnode(ui, repo, 'replaced', mapping)
900 cleanupnode(ui, repo, 'replaced', mapping)
896
901 if supportsmarkers:
902 obsolete.createmarkers(repo, ((repo[t],()) for t in sorted(tmpnodes)))
903 else:
897 cleanupnode(ui, repo, 'temp', tmpnodes)
904 cleanupnode(ui, repo, 'temp', tmpnodes)
898 state.clear()
905 state.clear()
899 if os.path.exists(repo.sjoin('undo')):
906 if os.path.exists(repo.sjoin('undo')):
900 os.unlink(repo.sjoin('undo'))
907 os.unlink(repo.sjoin('undo'))
901
908
902 def bootstrapcontinue(ui, state, opts):
909 def bootstrapcontinue(ui, state, opts):
903 repo = state.repo
910 repo = state.repo
904 if state.rules:
911 if state.rules:
905 action, currentnode = state.rules.pop(0)
912 action, currentnode = state.rules.pop(0)
906
913
907 actobj = actiontable[action].fromrule(state, currentnode)
914 actobj = actiontable[action].fromrule(state, currentnode)
908
915
909 s = repo.status()
916 s = repo.status()
910 if s.modified or s.added or s.removed or s.deleted:
917 if s.modified or s.added or s.removed or s.deleted:
911 actobj.continuedirty()
918 actobj.continuedirty()
912 s = repo.status()
919 s = repo.status()
913 if s.modified or s.added or s.removed or s.deleted:
920 if s.modified or s.added or s.removed or s.deleted:
914 raise util.Abort(_("working copy still dirty"))
921 raise util.Abort(_("working copy still dirty"))
915
922
916 parentctx, replacements = actobj.continueclean()
923 parentctx, replacements = actobj.continueclean()
917
924
918 state.parentctxnode = parentctx.node()
925 state.parentctxnode = parentctx.node()
919 state.replacements.extend(replacements)
926 state.replacements.extend(replacements)
920
927
921 return state
928 return state
922
929
923 def between(repo, old, new, keep):
930 def between(repo, old, new, keep):
924 """select and validate the set of revision to edit
931 """select and validate the set of revision to edit
925
932
926 When keep is false, the specified set can't have children."""
933 When keep is false, the specified set can't have children."""
927 ctxs = list(repo.set('%n::%n', old, new))
934 ctxs = list(repo.set('%n::%n', old, new))
928 if ctxs and not keep:
935 if ctxs and not keep:
929 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
936 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
930 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
937 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
931 raise util.Abort(_('cannot edit history that would orphan nodes'))
938 raise util.Abort(_('cannot edit history that would orphan nodes'))
932 if repo.revs('(%ld) and merge()', ctxs):
939 if repo.revs('(%ld) and merge()', ctxs):
933 raise util.Abort(_('cannot edit history that contains merges'))
940 raise util.Abort(_('cannot edit history that contains merges'))
934 root = ctxs[0] # list is already sorted by repo.set
941 root = ctxs[0] # list is already sorted by repo.set
935 if not root.mutable():
942 if not root.mutable():
936 raise util.Abort(_('cannot edit public changeset: %s') % root,
943 raise util.Abort(_('cannot edit public changeset: %s') % root,
937 hint=_('see "hg help phases" for details'))
944 hint=_('see "hg help phases" for details'))
938 return [c.node() for c in ctxs]
945 return [c.node() for c in ctxs]
939
946
940 def makedesc(repo, action, rev):
947 def makedesc(repo, action, rev):
941 """build a initial action line for a ctx
948 """build a initial action line for a ctx
942
949
943 line are in the form:
950 line are in the form:
944
951
945 <action> <hash> <rev> <summary>
952 <action> <hash> <rev> <summary>
946 """
953 """
947 ctx = repo[rev]
954 ctx = repo[rev]
948 summary = ''
955 summary = ''
949 if ctx.description():
956 if ctx.description():
950 summary = ctx.description().splitlines()[0]
957 summary = ctx.description().splitlines()[0]
951 line = '%s %s %d %s' % (action, ctx, ctx.rev(), summary)
958 line = '%s %s %d %s' % (action, ctx, ctx.rev(), summary)
952 # trim to 80 columns so it's not stupidly wide in my editor
959 # trim to 80 columns so it's not stupidly wide in my editor
953 maxlen = repo.ui.configint('histedit', 'linelen', default=80)
960 maxlen = repo.ui.configint('histedit', 'linelen', default=80)
954 maxlen = max(maxlen, 22) # avoid truncating hash
961 maxlen = max(maxlen, 22) # avoid truncating hash
955 return util.ellipsis(line, maxlen)
962 return util.ellipsis(line, maxlen)
956
963
957 def ruleeditor(repo, ui, rules, editcomment=""):
964 def ruleeditor(repo, ui, rules, editcomment=""):
958 """open an editor to edit rules
965 """open an editor to edit rules
959
966
960 rules are in the format [ [act, ctx], ...] like in state.rules
967 rules are in the format [ [act, ctx], ...] like in state.rules
961 """
968 """
962 rules = '\n'.join([makedesc(repo, act, rev) for [act, rev] in rules])
969 rules = '\n'.join([makedesc(repo, act, rev) for [act, rev] in rules])
963 rules += '\n\n'
970 rules += '\n\n'
964 rules += editcomment
971 rules += editcomment
965 rules = ui.edit(rules, ui.username())
972 rules = ui.edit(rules, ui.username())
966
973
967 # Save edit rules in .hg/histedit-last-edit.txt in case
974 # Save edit rules in .hg/histedit-last-edit.txt in case
968 # the user needs to ask for help after something
975 # the user needs to ask for help after something
969 # surprising happens.
976 # surprising happens.
970 f = open(repo.join('histedit-last-edit.txt'), 'w')
977 f = open(repo.join('histedit-last-edit.txt'), 'w')
971 f.write(rules)
978 f.write(rules)
972 f.close()
979 f.close()
973
980
974 return rules
981 return rules
975
982
976 def verifyrules(rules, repo, ctxs):
983 def verifyrules(rules, repo, ctxs):
977 """Verify that there exists exactly one edit rule per given changeset.
984 """Verify that there exists exactly one edit rule per given changeset.
978
985
979 Will abort if there are to many or too few rules, a malformed rule,
986 Will abort if there are to many or too few rules, a malformed rule,
980 or a rule on a changeset outside of the user-given range.
987 or a rule on a changeset outside of the user-given range.
981 """
988 """
982 parsed = []
989 parsed = []
983 expected = set(c.hex() for c in ctxs)
990 expected = set(c.hex() for c in ctxs)
984 seen = set()
991 seen = set()
985 for r in rules:
992 for r in rules:
986 if ' ' not in r:
993 if ' ' not in r:
987 raise util.Abort(_('malformed line "%s"') % r)
994 raise util.Abort(_('malformed line "%s"') % r)
988 action, rest = r.split(' ', 1)
995 action, rest = r.split(' ', 1)
989 ha = rest.strip().split(' ', 1)[0]
996 ha = rest.strip().split(' ', 1)[0]
990 try:
997 try:
991 ha = repo[ha].hex()
998 ha = repo[ha].hex()
992 except error.RepoError:
999 except error.RepoError:
993 raise util.Abort(_('unknown changeset %s listed') % ha[:12])
1000 raise util.Abort(_('unknown changeset %s listed') % ha[:12])
994 if ha not in expected:
1001 if ha not in expected:
995 raise util.Abort(
1002 raise util.Abort(
996 _('may not use changesets other than the ones listed'))
1003 _('may not use changesets other than the ones listed'))
997 if ha in seen:
1004 if ha in seen:
998 raise util.Abort(_('duplicated command for changeset %s') %
1005 raise util.Abort(_('duplicated command for changeset %s') %
999 ha[:12])
1006 ha[:12])
1000 seen.add(ha)
1007 seen.add(ha)
1001 if action not in actiontable:
1008 if action not in actiontable:
1002 raise util.Abort(_('unknown action "%s"') % action)
1009 raise util.Abort(_('unknown action "%s"') % action)
1003 parsed.append([action, ha])
1010 parsed.append([action, ha])
1004 missing = sorted(expected - seen) # sort to stabilize output
1011 missing = sorted(expected - seen) # sort to stabilize output
1005 if missing:
1012 if missing:
1006 raise util.Abort(_('missing rules for changeset %s') %
1013 raise util.Abort(_('missing rules for changeset %s') %
1007 missing[0][:12],
1014 missing[0][:12],
1008 hint=_('do you want to use the drop action?'))
1015 hint=_('do you want to use the drop action?'))
1009 return parsed
1016 return parsed
1010
1017
1011 def processreplacement(state):
1018 def processreplacement(state):
1012 """process the list of replacements to return
1019 """process the list of replacements to return
1013
1020
1014 1) the final mapping between original and created nodes
1021 1) the final mapping between original and created nodes
1015 2) the list of temporary node created by histedit
1022 2) the list of temporary node created by histedit
1016 3) the list of new commit created by histedit"""
1023 3) the list of new commit created by histedit"""
1017 replacements = state.replacements
1024 replacements = state.replacements
1018 allsuccs = set()
1025 allsuccs = set()
1019 replaced = set()
1026 replaced = set()
1020 fullmapping = {}
1027 fullmapping = {}
1021 # initialise basic set
1028 # initialise basic set
1022 # fullmapping record all operation recorded in replacement
1029 # fullmapping record all operation recorded in replacement
1023 for rep in replacements:
1030 for rep in replacements:
1024 allsuccs.update(rep[1])
1031 allsuccs.update(rep[1])
1025 replaced.add(rep[0])
1032 replaced.add(rep[0])
1026 fullmapping.setdefault(rep[0], set()).update(rep[1])
1033 fullmapping.setdefault(rep[0], set()).update(rep[1])
1027 new = allsuccs - replaced
1034 new = allsuccs - replaced
1028 tmpnodes = allsuccs & replaced
1035 tmpnodes = allsuccs & replaced
1029 # Reduce content fullmapping into direct relation between original nodes
1036 # Reduce content fullmapping into direct relation between original nodes
1030 # and final node created during history edition
1037 # and final node created during history edition
1031 # Dropped changeset are replaced by an empty list
1038 # Dropped changeset are replaced by an empty list
1032 toproceed = set(fullmapping)
1039 toproceed = set(fullmapping)
1033 final = {}
1040 final = {}
1034 while toproceed:
1041 while toproceed:
1035 for x in list(toproceed):
1042 for x in list(toproceed):
1036 succs = fullmapping[x]
1043 succs = fullmapping[x]
1037 for s in list(succs):
1044 for s in list(succs):
1038 if s in toproceed:
1045 if s in toproceed:
1039 # non final node with unknown closure
1046 # non final node with unknown closure
1040 # We can't process this now
1047 # We can't process this now
1041 break
1048 break
1042 elif s in final:
1049 elif s in final:
1043 # non final node, replace with closure
1050 # non final node, replace with closure
1044 succs.remove(s)
1051 succs.remove(s)
1045 succs.update(final[s])
1052 succs.update(final[s])
1046 else:
1053 else:
1047 final[x] = succs
1054 final[x] = succs
1048 toproceed.remove(x)
1055 toproceed.remove(x)
1049 # remove tmpnodes from final mapping
1056 # remove tmpnodes from final mapping
1050 for n in tmpnodes:
1057 for n in tmpnodes:
1051 del final[n]
1058 del final[n]
1052 # we expect all changes involved in final to exist in the repo
1059 # we expect all changes involved in final to exist in the repo
1053 # turn `final` into list (topologically sorted)
1060 # turn `final` into list (topologically sorted)
1054 nm = state.repo.changelog.nodemap
1061 nm = state.repo.changelog.nodemap
1055 for prec, succs in final.items():
1062 for prec, succs in final.items():
1056 final[prec] = sorted(succs, key=nm.get)
1063 final[prec] = sorted(succs, key=nm.get)
1057
1064
1058 # computed topmost element (necessary for bookmark)
1065 # computed topmost element (necessary for bookmark)
1059 if new:
1066 if new:
1060 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
1067 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
1061 elif not final:
1068 elif not final:
1062 # Nothing rewritten at all. we won't need `newtopmost`
1069 # Nothing rewritten at all. we won't need `newtopmost`
1063 # It is the same as `oldtopmost` and `processreplacement` know it
1070 # It is the same as `oldtopmost` and `processreplacement` know it
1064 newtopmost = None
1071 newtopmost = None
1065 else:
1072 else:
1066 # every body died. The newtopmost is the parent of the root.
1073 # every body died. The newtopmost is the parent of the root.
1067 r = state.repo.changelog.rev
1074 r = state.repo.changelog.rev
1068 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
1075 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
1069
1076
1070 return final, tmpnodes, new, newtopmost
1077 return final, tmpnodes, new, newtopmost
1071
1078
1072 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
1079 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
1073 """Move bookmark from old to newly created node"""
1080 """Move bookmark from old to newly created node"""
1074 if not mapping:
1081 if not mapping:
1075 # if nothing got rewritten there is not purpose for this function
1082 # if nothing got rewritten there is not purpose for this function
1076 return
1083 return
1077 moves = []
1084 moves = []
1078 for bk, old in sorted(repo._bookmarks.iteritems()):
1085 for bk, old in sorted(repo._bookmarks.iteritems()):
1079 if old == oldtopmost:
1086 if old == oldtopmost:
1080 # special case ensure bookmark stay on tip.
1087 # special case ensure bookmark stay on tip.
1081 #
1088 #
1082 # This is arguably a feature and we may only want that for the
1089 # This is arguably a feature and we may only want that for the
1083 # active bookmark. But the behavior is kept compatible with the old
1090 # active bookmark. But the behavior is kept compatible with the old
1084 # version for now.
1091 # version for now.
1085 moves.append((bk, newtopmost))
1092 moves.append((bk, newtopmost))
1086 continue
1093 continue
1087 base = old
1094 base = old
1088 new = mapping.get(base, None)
1095 new = mapping.get(base, None)
1089 if new is None:
1096 if new is None:
1090 continue
1097 continue
1091 while not new:
1098 while not new:
1092 # base is killed, trying with parent
1099 # base is killed, trying with parent
1093 base = repo[base].p1().node()
1100 base = repo[base].p1().node()
1094 new = mapping.get(base, (base,))
1101 new = mapping.get(base, (base,))
1095 # nothing to move
1102 # nothing to move
1096 moves.append((bk, new[-1]))
1103 moves.append((bk, new[-1]))
1097 if moves:
1104 if moves:
1098 marks = repo._bookmarks
1105 marks = repo._bookmarks
1099 for mark, new in moves:
1106 for mark, new in moves:
1100 old = marks[mark]
1107 old = marks[mark]
1101 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
1108 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
1102 % (mark, node.short(old), node.short(new)))
1109 % (mark, node.short(old), node.short(new)))
1103 marks[mark] = new
1110 marks[mark] = new
1104 marks.write()
1111 marks.write()
1105
1112
1106 def cleanupnode(ui, repo, name, nodes):
1113 def cleanupnode(ui, repo, name, nodes):
1107 """strip a group of nodes from the repository
1114 """strip a group of nodes from the repository
1108
1115
1109 The set of node to strip may contains unknown nodes."""
1116 The set of node to strip may contains unknown nodes."""
1110 ui.debug('should strip %s nodes %s\n' %
1117 ui.debug('should strip %s nodes %s\n' %
1111 (name, ', '.join([node.short(n) for n in nodes])))
1118 (name, ', '.join([node.short(n) for n in nodes])))
1112 lock = None
1119 lock = None
1113 try:
1120 try:
1114 lock = repo.lock()
1121 lock = repo.lock()
1115 # Find all node that need to be stripped
1122 # Find all node that need to be stripped
1116 # (we hg %lr instead of %ln to silently ignore unknown item
1123 # (we hg %lr instead of %ln to silently ignore unknown item
1117 nm = repo.changelog.nodemap
1124 nm = repo.changelog.nodemap
1118 nodes = sorted(n for n in nodes if n in nm)
1125 nodes = sorted(n for n in nodes if n in nm)
1119 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
1126 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
1120 for c in roots:
1127 for c in roots:
1121 # We should process node in reverse order to strip tip most first.
1128 # We should process node in reverse order to strip tip most first.
1122 # but this trigger a bug in changegroup hook.
1129 # but this trigger a bug in changegroup hook.
1123 # This would reduce bundle overhead
1130 # This would reduce bundle overhead
1124 repair.strip(ui, repo, c)
1131 repair.strip(ui, repo, c)
1125 finally:
1132 finally:
1126 release(lock)
1133 release(lock)
1127
1134
1128 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
1135 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
1129 if isinstance(nodelist, str):
1136 if isinstance(nodelist, str):
1130 nodelist = [nodelist]
1137 nodelist = [nodelist]
1131 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1138 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
1132 state = histeditstate(repo)
1139 state = histeditstate(repo)
1133 state.read()
1140 state.read()
1134 histedit_nodes = set([repo[rulehash].node() for (action, rulehash)
1141 histedit_nodes = set([repo[rulehash].node() for (action, rulehash)
1135 in state.rules if rulehash in repo])
1142 in state.rules if rulehash in repo])
1136 strip_nodes = set([repo[n].node() for n in nodelist])
1143 strip_nodes = set([repo[n].node() for n in nodelist])
1137 common_nodes = histedit_nodes & strip_nodes
1144 common_nodes = histedit_nodes & strip_nodes
1138 if common_nodes:
1145 if common_nodes:
1139 raise util.Abort(_("histedit in progress, can't strip %s")
1146 raise util.Abort(_("histedit in progress, can't strip %s")
1140 % ', '.join(node.short(x) for x in common_nodes))
1147 % ', '.join(node.short(x) for x in common_nodes))
1141 return orig(ui, repo, nodelist, *args, **kwargs)
1148 return orig(ui, repo, nodelist, *args, **kwargs)
1142
1149
1143 extensions.wrapfunction(repair, 'strip', stripwrapper)
1150 extensions.wrapfunction(repair, 'strip', stripwrapper)
1144
1151
1145 def summaryhook(ui, repo):
1152 def summaryhook(ui, repo):
1146 if not os.path.exists(repo.join('histedit-state')):
1153 if not os.path.exists(repo.join('histedit-state')):
1147 return
1154 return
1148 state = histeditstate(repo)
1155 state = histeditstate(repo)
1149 state.read()
1156 state.read()
1150 if state.rules:
1157 if state.rules:
1151 # i18n: column positioning for "hg summary"
1158 # i18n: column positioning for "hg summary"
1152 ui.write(_('hist: %s (histedit --continue)\n') %
1159 ui.write(_('hist: %s (histedit --continue)\n') %
1153 (ui.label(_('%d remaining'), 'histedit.remaining') %
1160 (ui.label(_('%d remaining'), 'histedit.remaining') %
1154 len(state.rules)))
1161 len(state.rules)))
1155
1162
1156 def extsetup(ui):
1163 def extsetup(ui):
1157 cmdutil.summaryhooks.add('histedit', summaryhook)
1164 cmdutil.summaryhooks.add('histedit', summaryhook)
1158 cmdutil.unfinishedstates.append(
1165 cmdutil.unfinishedstates.append(
1159 ['histedit-state', False, True, _('histedit in progress'),
1166 ['histedit-state', False, True, _('histedit in progress'),
1160 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
1167 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
@@ -1,460 +1,463
1 $ . "$TESTDIR/histedit-helpers.sh"
1 $ . "$TESTDIR/histedit-helpers.sh"
2
2
3 Enable obsolete
3 Enable obsolete
4
4
5 $ cat >> $HGRCPATH << EOF
5 $ cat >> $HGRCPATH << EOF
6 > [ui]
6 > [ui]
7 > logtemplate= {rev}:{node|short} {desc|firstline}
7 > logtemplate= {rev}:{node|short} {desc|firstline}
8 > [phases]
8 > [phases]
9 > publish=False
9 > publish=False
10 > [experimental]
10 > [experimental]
11 > evolution=createmarkers,allowunstable
11 > evolution=createmarkers,allowunstable
12 > [extensions]
12 > [extensions]
13 > histedit=
13 > histedit=
14 > rebase=
14 > rebase=
15 > EOF
15 > EOF
16
16
17 $ hg init base
17 $ hg init base
18 $ cd base
18 $ cd base
19
19
20 $ for x in a b c d e f ; do
20 $ for x in a b c d e f ; do
21 > echo $x > $x
21 > echo $x > $x
22 > hg add $x
22 > hg add $x
23 > hg ci -m $x
23 > hg ci -m $x
24 > done
24 > done
25
25
26 $ hg log --graph
26 $ hg log --graph
27 @ 5:652413bf663e f
27 @ 5:652413bf663e f
28 |
28 |
29 o 4:e860deea161a e
29 o 4:e860deea161a e
30 |
30 |
31 o 3:055a42cdd887 d
31 o 3:055a42cdd887 d
32 |
32 |
33 o 2:177f92b77385 c
33 o 2:177f92b77385 c
34 |
34 |
35 o 1:d2ae7f538514 b
35 o 1:d2ae7f538514 b
36 |
36 |
37 o 0:cb9a9f314b8b a
37 o 0:cb9a9f314b8b a
38
38
39
39
40 $ HGEDITOR=cat hg histedit 1
40 $ HGEDITOR=cat hg histedit 1
41 pick d2ae7f538514 1 b
41 pick d2ae7f538514 1 b
42 pick 177f92b77385 2 c
42 pick 177f92b77385 2 c
43 pick 055a42cdd887 3 d
43 pick 055a42cdd887 3 d
44 pick e860deea161a 4 e
44 pick e860deea161a 4 e
45 pick 652413bf663e 5 f
45 pick 652413bf663e 5 f
46
46
47 # Edit history between d2ae7f538514 and 652413bf663e
47 # Edit history between d2ae7f538514 and 652413bf663e
48 #
48 #
49 # Commits are listed from least to most recent
49 # Commits are listed from least to most recent
50 #
50 #
51 # Commands:
51 # Commands:
52 # p, pick = use commit
52 # p, pick = use commit
53 # e, edit = use commit, but stop for amending
53 # e, edit = use commit, but stop for amending
54 # f, fold = use commit, but combine it with the one above
54 # f, fold = use commit, but combine it with the one above
55 # r, roll = like fold, but discard this commit's description
55 # r, roll = like fold, but discard this commit's description
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 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
60 $ hg histedit 1 --commands - --verbose <<EOF | grep histedit
60 $ hg histedit 1 --commands - --verbose <<EOF | grep histedit
61 > pick 177f92b77385 2 c
61 > pick 177f92b77385 2 c
62 > drop d2ae7f538514 1 b
62 > drop d2ae7f538514 1 b
63 > pick 055a42cdd887 3 d
63 > pick 055a42cdd887 3 d
64 > fold e860deea161a 4 e
64 > fold e860deea161a 4 e
65 > pick 652413bf663e 5 f
65 > pick 652413bf663e 5 f
66 > EOF
66 > EOF
67 saved backup bundle to $TESTTMP/base/.hg/strip-backup/96e494a2d553-3c6c5d92-backup.hg (glob)
67 [1]
68 $ hg log --graph --hidden
68 $ hg log --graph --hidden
69 @ 8:cacdfd884a93 f
69 @ 10:cacdfd884a93 f
70 |
71 o 9:59d9f330561f d
70 |
72 |
71 o 7:59d9f330561f d
73 | x 8:b558abc46d09 fold-temp-revision e860deea161a
72 |
74 | |
75 | x 7:96e494a2d553 d
76 |/
73 o 6:b346ab9a313d c
77 o 6:b346ab9a313d c
74 |
78 |
75 | x 5:652413bf663e f
79 | x 5:652413bf663e f
76 | |
80 | |
77 | x 4:e860deea161a e
81 | x 4:e860deea161a e
78 | |
82 | |
79 | x 3:055a42cdd887 d
83 | x 3:055a42cdd887 d
80 | |
84 | |
81 | x 2:177f92b77385 c
85 | x 2:177f92b77385 c
82 | |
86 | |
83 | x 1:d2ae7f538514 b
87 | x 1:d2ae7f538514 b
84 |/
88 |/
85 o 0:cb9a9f314b8b a
89 o 0:cb9a9f314b8b a
86
90
87 $ hg debugobsolete
91 $ hg debugobsolete
88 d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b} (*) {'user': 'test'} (glob)
92 d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b} (*) {'user': 'test'} (glob)
89 177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (*) {'user': 'test'} (glob)
93 177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (*) {'user': 'test'} (glob)
90 055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
94 055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
91 e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
95 e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
92 652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (*) {'user': 'test'} (glob)
96 652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (*) {'user': 'test'} (glob)
97 96e494a2d553dd05902ba1cee1d94d4cb7b8faed 0 {b346ab9a313db8537ecf96fca3ca3ca984ef3bd7} (*) {'user': 'test'} (glob)
98 b558abc46d09c30f57ac31e85a8a3d64d2e906e4 0 {96e494a2d553dd05902ba1cee1d94d4cb7b8faed} (*) {'user': 'test'} (glob)
93
99
94
100
95 Ensure hidden revision does not prevent histedit
101 Ensure hidden revision does not prevent histedit
96 -------------------------------------------------
102 -------------------------------------------------
97
103
98 create an hidden revision
104 create an hidden revision
99
105
100 $ hg histedit 6 --commands - << EOF
106 $ hg histedit 6 --commands - << EOF
101 > pick b346ab9a313d 6 c
107 > pick b346ab9a313d 6 c
102 > drop 59d9f330561f 7 d
108 > drop 59d9f330561f 7 d
103 > pick cacdfd884a93 8 f
109 > pick cacdfd884a93 8 f
104 > EOF
110 > EOF
105 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
111 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
106 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
112 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
107 $ hg log --graph
113 $ hg log --graph
108 @ 9:c13eb81022ca f
114 @ 11:c13eb81022ca f
109 |
115 |
110 o 6:b346ab9a313d c
116 o 6:b346ab9a313d c
111 |
117 |
112 o 0:cb9a9f314b8b a
118 o 0:cb9a9f314b8b a
113
119
114 check hidden revision are ignored (6 have hidden children 7 and 8)
120 check hidden revision are ignored (6 have hidden children 7 and 8)
115
121
116 $ hg histedit 6 --commands - << EOF
122 $ hg histedit 6 --commands - << EOF
117 > pick b346ab9a313d 6 c
123 > pick b346ab9a313d 6 c
118 > pick c13eb81022ca 8 f
124 > pick c13eb81022ca 8 f
119 > EOF
125 > EOF
120 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
126 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
121
127
122
128
123
129
124 Test that rewriting leaving instability behind is allowed
130 Test that rewriting leaving instability behind is allowed
125 ---------------------------------------------------------------------
131 ---------------------------------------------------------------------
126
132
127 $ hg up '.^'
133 $ hg up '.^'
128 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
134 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
129 $ hg log -r 'children(.)'
135 $ hg log -r 'children(.)'
130 9:c13eb81022ca f (no-eol)
136 11:c13eb81022ca f (no-eol)
131 $ hg histedit -r '.' --commands - <<EOF
137 $ hg histedit -r '.' --commands - <<EOF
132 > edit b346ab9a313d 6 c
138 > edit b346ab9a313d 6 c
133 > EOF
139 > EOF
134 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
135 adding c
141 adding c
136 Make changes as needed, you may commit or record as needed now.
142 Make changes as needed, you may commit or record as needed now.
137 When you are finished, run hg histedit --continue to resume.
143 When you are finished, run hg histedit --continue to resume.
138 [1]
144 [1]
139 $ echo c >> c
145 $ echo c >> c
140 $ hg histedit --continue
146 $ hg histedit --continue
141 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
142
148
143 $ hg log -r 'unstable()'
149 $ hg log -r 'unstable()'
144 9:c13eb81022ca f (no-eol)
150 11:c13eb81022ca f (no-eol)
145
151
146 stabilise
152 stabilise
147
153
148 $ hg rebase -r 'unstable()' -d .
154 $ hg rebase -r 'unstable()' -d .
149 rebasing 9:c13eb81022ca "f"
155 rebasing 11:c13eb81022ca "f"
150 $ hg up tip -q
156 $ hg up tip -q
151
157
152 Test dropping of changeset on the top of the stack
158 Test dropping of changeset on the top of the stack
153 -------------------------------------------------------
159 -------------------------------------------------------
154
160
155 Nothing is rewritten below, the working directory parent must be change for the
161 Nothing is rewritten below, the working directory parent must be change for the
156 dropped changeset to be hidden.
162 dropped changeset to be hidden.
157
163
158 $ cd ..
164 $ cd ..
159 $ hg clone base droplast
165 $ hg clone base droplast
160 updating to branch default
166 updating to branch default
161 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
167 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
162 $ cd droplast
168 $ cd droplast
163 $ hg histedit -r '40db8afa467b' --commands - << EOF
169 $ hg histedit -r '40db8afa467b' --commands - << EOF
164 > pick 40db8afa467b 10 c
170 > pick 40db8afa467b 10 c
165 > drop b449568bf7fc 11 f
171 > drop b449568bf7fc 11 f
166 > EOF
172 > EOF
167 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
173 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
168 $ hg log -G
174 $ hg log -G
169 @ 10:40db8afa467b c
175 @ 12:40db8afa467b c
170 |
176 |
171 o 0:cb9a9f314b8b a
177 o 0:cb9a9f314b8b a
172
178
173
179
174 With rewritten ancestors
180 With rewritten ancestors
175
181
176 $ echo e > e
182 $ echo e > e
177 $ hg add e
183 $ hg add e
178 $ hg commit -m g
184 $ hg commit -m g
179 $ echo f > f
185 $ echo f > f
180 $ hg add f
186 $ hg add f
181 $ hg commit -m h
187 $ hg commit -m h
182 $ hg histedit -r '40db8afa467b' --commands - << EOF
188 $ hg histedit -r '40db8afa467b' --commands - << EOF
183 > pick 47a8561c0449 12 g
189 > pick 47a8561c0449 12 g
184 > pick 40db8afa467b 10 c
190 > pick 40db8afa467b 10 c
185 > drop 1b3b05f35ff0 13 h
191 > drop 1b3b05f35ff0 13 h
186 > EOF
192 > EOF
187 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
193 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
188 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
194 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
189 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
195 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
190 $ hg log -G
196 $ hg log -G
191 @ 15:ee6544123ab8 c
197 @ 17:ee6544123ab8 c
192 |
198 |
193 o 14:269e713e9eae g
199 o 16:269e713e9eae g
194 |
200 |
195 o 0:cb9a9f314b8b a
201 o 0:cb9a9f314b8b a
196
202
197 $ cd ../base
203 $ cd ../base
198
204
199
205
200
206
201 Test phases support
207 Test phases support
202 ===========================================
208 ===========================================
203
209
204 Check that histedit respect immutability
210 Check that histedit respect immutability
205 -------------------------------------------
211 -------------------------------------------
206
212
207 $ cat >> $HGRCPATH << EOF
213 $ cat >> $HGRCPATH << EOF
208 > [ui]
214 > [ui]
209 > logtemplate= {rev}:{node|short} ({phase}) {desc|firstline}\n
215 > logtemplate= {rev}:{node|short} ({phase}) {desc|firstline}\n
210 > EOF
216 > EOF
211
217
212 $ hg ph -pv '.^'
218 $ hg ph -pv '.^'
213 phase changed for 2 changesets
219 phase changed for 2 changesets
214 $ hg log -G
220 $ hg log -G
215 @ 11:b449568bf7fc (draft) f
221 @ 13:b449568bf7fc (draft) f
216 |
222 |
217 o 10:40db8afa467b (public) c
223 o 12:40db8afa467b (public) c
218 |
224 |
219 o 0:cb9a9f314b8b (public) a
225 o 0:cb9a9f314b8b (public) a
220
226
221 $ hg histedit -r '.~2'
227 $ hg histedit -r '.~2'
222 abort: cannot edit public changeset: cb9a9f314b8b
228 abort: cannot edit public changeset: cb9a9f314b8b
223 (see "hg help phases" for details)
229 (see "hg help phases" for details)
224 [255]
230 [255]
225
231
226
232
227 Prepare further testing
233 Prepare further testing
228 -------------------------------------------
234 -------------------------------------------
229
235
230 $ for x in g h i j k ; do
236 $ for x in g h i j k ; do
231 > echo $x > $x
237 > echo $x > $x
232 > hg add $x
238 > hg add $x
233 > hg ci -m $x
239 > hg ci -m $x
234 > done
240 > done
235 $ hg phase --force --secret .~2
241 $ hg phase --force --secret .~2
236 $ hg log -G
242 $ hg log -G
237 @ 16:ee118ab9fa44 (secret) k
243 @ 18:ee118ab9fa44 (secret) k
238 |
244 |
239 o 15:3a6c53ee7f3d (secret) j
245 o 17:3a6c53ee7f3d (secret) j
240 |
246 |
241 o 14:b605fb7503f2 (secret) i
247 o 16:b605fb7503f2 (secret) i
242 |
248 |
243 o 13:7395e1ff83bd (draft) h
249 o 15:7395e1ff83bd (draft) h
244 |
250 |
245 o 12:6b70183d2492 (draft) g
251 o 14:6b70183d2492 (draft) g
246 |
252 |
247 o 11:b449568bf7fc (draft) f
253 o 13:b449568bf7fc (draft) f
248 |
254 |
249 o 10:40db8afa467b (public) c
255 o 12:40db8afa467b (public) c
250 |
256 |
251 o 0:cb9a9f314b8b (public) a
257 o 0:cb9a9f314b8b (public) a
252
258
253 $ cd ..
259 $ cd ..
254
260
255 simple phase conservation
261 simple phase conservation
256 -------------------------------------------
262 -------------------------------------------
257
263
258 Resulting changeset should conserve the phase of the original one whatever the
264 Resulting changeset should conserve the phase of the original one whatever the
259 phases.new-commit option is.
265 phases.new-commit option is.
260
266
261 New-commit as draft (default)
267 New-commit as draft (default)
262
268
263 $ cp -r base simple-draft
269 $ cp -r base simple-draft
264 $ cd simple-draft
270 $ cd simple-draft
265 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
271 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
266 > edit b449568bf7fc 11 f
272 > edit b449568bf7fc 11 f
267 > pick 6b70183d2492 12 g
273 > pick 6b70183d2492 12 g
268 > pick 7395e1ff83bd 13 h
274 > pick 7395e1ff83bd 13 h
269 > pick b605fb7503f2 14 i
275 > pick b605fb7503f2 14 i
270 > pick 3a6c53ee7f3d 15 j
276 > pick 3a6c53ee7f3d 15 j
271 > pick ee118ab9fa44 16 k
277 > pick ee118ab9fa44 16 k
272 > EOF
278 > EOF
273 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
279 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
274 adding f
280 adding f
275 Make changes as needed, you may commit or record as needed now.
281 Make changes as needed, you may commit or record as needed now.
276 When you are finished, run hg histedit --continue to resume.
282 When you are finished, run hg histedit --continue to resume.
277 [1]
283 [1]
278 $ echo f >> f
284 $ echo f >> f
279 $ hg histedit --continue
285 $ hg histedit --continue
280 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
286 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
281 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
282 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
283 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
284 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
285 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
286 $ hg log -G
292 $ hg log -G
287 @ 22:12e89af74238 (secret) k
293 @ 24:12e89af74238 (secret) k
288 |
294 |
289 o 21:636a8687b22e (secret) j
295 o 23:636a8687b22e (secret) j
290 |
296 |
291 o 20:ccaf0a38653f (secret) i
297 o 22:ccaf0a38653f (secret) i
292 |
298 |
293 o 19:11a89d1c2613 (draft) h
299 o 21:11a89d1c2613 (draft) h
294 |
300 |
295 o 18:c1dec7ca82ea (draft) g
301 o 20:c1dec7ca82ea (draft) g
296 |
302 |
297 o 17:087281e68428 (draft) f
303 o 19:087281e68428 (draft) f
298 |
304 |
299 o 10:40db8afa467b (public) c
305 o 12:40db8afa467b (public) c
300 |
306 |
301 o 0:cb9a9f314b8b (public) a
307 o 0:cb9a9f314b8b (public) a
302
308
303 $ cd ..
309 $ cd ..
304
310
305
311
306 New-commit as draft (default)
312 New-commit as draft (default)
307
313
308 $ cp -r base simple-secret
314 $ cp -r base simple-secret
309 $ cd simple-secret
315 $ cd simple-secret
310 $ cat >> .hg/hgrc << EOF
316 $ cat >> .hg/hgrc << EOF
311 > [phases]
317 > [phases]
312 > new-commit=secret
318 > new-commit=secret
313 > EOF
319 > EOF
314 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
320 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
315 > edit b449568bf7fc 11 f
321 > edit b449568bf7fc 11 f
316 > pick 6b70183d2492 12 g
322 > pick 6b70183d2492 12 g
317 > pick 7395e1ff83bd 13 h
323 > pick 7395e1ff83bd 13 h
318 > pick b605fb7503f2 14 i
324 > pick b605fb7503f2 14 i
319 > pick 3a6c53ee7f3d 15 j
325 > pick 3a6c53ee7f3d 15 j
320 > pick ee118ab9fa44 16 k
326 > pick ee118ab9fa44 16 k
321 > EOF
327 > EOF
322 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
328 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
323 adding f
329 adding f
324 Make changes as needed, you may commit or record as needed now.
330 Make changes as needed, you may commit or record as needed now.
325 When you are finished, run hg histedit --continue to resume.
331 When you are finished, run hg histedit --continue to resume.
326 [1]
332 [1]
327 $ echo f >> f
333 $ echo f >> f
328 $ hg histedit --continue
334 $ hg histedit --continue
329 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
335 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
330 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
336 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
337 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
338 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
339 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
340 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
335 $ hg log -G
341 $ hg log -G
336 @ 22:12e89af74238 (secret) k
342 @ 24:12e89af74238 (secret) k
337 |
343 |
338 o 21:636a8687b22e (secret) j
344 o 23:636a8687b22e (secret) j
339 |
345 |
340 o 20:ccaf0a38653f (secret) i
346 o 22:ccaf0a38653f (secret) i
341 |
347 |
342 o 19:11a89d1c2613 (draft) h
348 o 21:11a89d1c2613 (draft) h
343 |
349 |
344 o 18:c1dec7ca82ea (draft) g
350 o 20:c1dec7ca82ea (draft) g
345 |
351 |
346 o 17:087281e68428 (draft) f
352 o 19:087281e68428 (draft) f
347 |
353 |
348 o 10:40db8afa467b (public) c
354 o 12:40db8afa467b (public) c
349 |
355 |
350 o 0:cb9a9f314b8b (public) a
356 o 0:cb9a9f314b8b (public) a
351
357
352 $ cd ..
358 $ cd ..
353
359
354
360
355 Changeset reordering
361 Changeset reordering
356 -------------------------------------------
362 -------------------------------------------
357
363
358 If a secret changeset is put before a draft one, all descendant should be secret.
364 If a secret changeset is put before a draft one, all descendant should be secret.
359 It seems more important to present the secret phase.
365 It seems more important to present the secret phase.
360
366
361 $ cp -r base reorder
367 $ cp -r base reorder
362 $ cd reorder
368 $ cd reorder
363 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
369 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
364 > pick b449568bf7fc 11 f
370 > pick b449568bf7fc 11 f
365 > pick 3a6c53ee7f3d 15 j
371 > pick 3a6c53ee7f3d 15 j
366 > pick 6b70183d2492 12 g
372 > pick 6b70183d2492 12 g
367 > pick b605fb7503f2 14 i
373 > pick b605fb7503f2 14 i
368 > pick 7395e1ff83bd 13 h
374 > pick 7395e1ff83bd 13 h
369 > pick ee118ab9fa44 16 k
375 > pick ee118ab9fa44 16 k
370 > EOF
376 > EOF
371 0 files updated, 0 files merged, 5 files removed, 0 files unresolved
377 0 files updated, 0 files merged, 5 files removed, 0 files unresolved
372 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
378 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
373 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
379 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
374 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
380 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
375 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
381 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
376 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
377 $ hg log -G
383 $ hg log -G
378 @ 21:558246857888 (secret) k
384 @ 23:558246857888 (secret) k
379 |
385 |
380 o 20:28bd44768535 (secret) h
386 o 22:28bd44768535 (secret) h
381 |
387 |
382 o 19:d5395202aeb9 (secret) i
388 o 21:d5395202aeb9 (secret) i
383 |
389 |
384 o 18:21edda8e341b (secret) g
390 o 20:21edda8e341b (secret) g
385 |
391 |
386 o 17:5ab64f3a4832 (secret) j
392 o 19:5ab64f3a4832 (secret) j
387 |
393 |
388 o 11:b449568bf7fc (draft) f
394 o 13:b449568bf7fc (draft) f
389 |
395 |
390 o 10:40db8afa467b (public) c
396 o 12:40db8afa467b (public) c
391 |
397 |
392 o 0:cb9a9f314b8b (public) a
398 o 0:cb9a9f314b8b (public) a
393
399
394 $ cd ..
400 $ cd ..
395
401
396 Changeset folding
402 Changeset folding
397 -------------------------------------------
403 -------------------------------------------
398
404
399 Folding a secret changeset with a draft one turn the result secret (again,
405 Folding a secret changeset with a draft one turn the result secret (again,
400 better safe than sorry). Folding between same phase changeset still works
406 better safe than sorry). Folding between same phase changeset still works
401
407
402 Note that there is a few reordering in this series for more extensive test
408 Note that there is a few reordering in this series for more extensive test
403
409
404 $ cp -r base folding
410 $ cp -r base folding
405 $ cd folding
411 $ cd folding
406 $ cat >> .hg/hgrc << EOF
412 $ cat >> .hg/hgrc << EOF
407 > [phases]
413 > [phases]
408 > new-commit=secret
414 > new-commit=secret
409 > EOF
415 > EOF
410 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
416 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
411 > pick 7395e1ff83bd 13 h
417 > pick 7395e1ff83bd 13 h
412 > fold b449568bf7fc 11 f
418 > fold b449568bf7fc 11 f
413 > pick 6b70183d2492 12 g
419 > pick 6b70183d2492 12 g
414 > fold 3a6c53ee7f3d 15 j
420 > fold 3a6c53ee7f3d 15 j
415 > pick b605fb7503f2 14 i
421 > pick b605fb7503f2 14 i
416 > fold ee118ab9fa44 16 k
422 > fold ee118ab9fa44 16 k
417 > EOF
423 > EOF
418 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
424 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
419 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
425 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
420 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
426 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
421 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
427 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
422 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
428 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
423 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
429 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
424 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
430 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
425 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
431 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
426 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
432 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
427 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
433 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
428 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
434 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
429 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
435 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
430 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
436 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
431 saved backup bundle to $TESTTMP/folding/.hg/strip-backup/58019c66f35f-96092fce-backup.hg (glob)
432 saved backup bundle to $TESTTMP/folding/.hg/strip-backup/83d1858e070b-f3469cf8-backup.hg (glob)
433 saved backup bundle to $TESTTMP/folding/.hg/strip-backup/859969f5ed7e-d89a19d7-backup.hg (glob)
434 $ hg log -G
437 $ hg log -G
435 @ 19:f9daec13fb98 (secret) i
438 @ 27:f9daec13fb98 (secret) i
436 |
439 |
437 o 18:49807617f46a (secret) g
440 o 24:49807617f46a (secret) g
438 |
441 |
439 o 17:050280826e04 (draft) h
442 o 21:050280826e04 (draft) h
440 |
443 |
441 o 10:40db8afa467b (public) c
444 o 12:40db8afa467b (public) c
442 |
445 |
443 o 0:cb9a9f314b8b (public) a
446 o 0:cb9a9f314b8b (public) a
444
447
445 $ hg co 18
448 $ hg co 24
446 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
449 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
447 $ echo wat >> wat
450 $ echo wat >> wat
448 $ hg add wat
451 $ hg add wat
449 $ hg ci -m 'add wat'
452 $ hg ci -m 'add wat'
450 created new head
453 created new head
451 $ hg merge 19
454 $ hg merge 27
452 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
455 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
453 (branch merge, don't forget to commit)
456 (branch merge, don't forget to commit)
454 $ hg ci -m 'merge'
457 $ hg ci -m 'merge'
455 $ echo not wat > wat
458 $ echo not wat > wat
456 $ hg ci -m 'modify wat'
459 $ hg ci -m 'modify wat'
457 $ hg histedit 17
460 $ hg histedit 21
458 abort: cannot edit history that contains merges
461 abort: cannot edit history that contains merges
459 [255]
462 [255]
460 $ cd ..
463 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now