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