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