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