##// END OF EJS Templates
histedit: move makedesc function near other rules related function...
Pierre-Yves David -
r17643:64e0f0cf default
parent child Browse files
Show More
@@ -1,670 +1,669
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 633536316234 and 7c2fd3b9020c
31 # Edit history between 633536316234 and 7c2fd3b9020c
32 #
32 #
33 # Commands:
33 # Commands:
34 # p, pick = use commit
34 # p, pick = use commit
35 # e, edit = use commit, but stop for amending
35 # e, edit = use commit, but stop for amending
36 # f, fold = use commit, but fold into previous commit (combines N and N-1)
36 # f, fold = use commit, but fold into previous commit (combines N and N-1)
37 # d, drop = remove commit from history
37 # d, drop = remove commit from history
38 # m, mess = edit message without changing commit content
38 # m, mess = edit message without changing commit content
39 #
39 #
40
40
41 In this file, lines beginning with ``#`` are ignored. You must specify a rule
41 In this file, lines beginning with ``#`` are ignored. You must specify a rule
42 for each revision in your history. For example, if you had meant to add gamma
42 for each revision in your history. For example, if you had meant to add gamma
43 before beta, and then wanted to add delta in the same revision as beta, you
43 before beta, and then wanted to add delta in the same revision as beta, you
44 would reorganize the file to look like this::
44 would reorganize the file to look like this::
45
45
46 pick 030b686bedc4 Add gamma
46 pick 030b686bedc4 Add gamma
47 pick c561b4e977df Add beta
47 pick c561b4e977df Add beta
48 fold 7c2fd3b9020c Add delta
48 fold 7c2fd3b9020c Add delta
49
49
50 # Edit history between 633536316234 and 7c2fd3b9020c
50 # Edit history between 633536316234 and 7c2fd3b9020c
51 #
51 #
52 # Commands:
52 # Commands:
53 # p, pick = use commit
53 # p, pick = use commit
54 # e, edit = use commit, but stop for amending
54 # e, edit = use commit, but stop for amending
55 # f, fold = use commit, but fold into previous commit (combines N and N-1)
55 # f, fold = use commit, but fold into previous commit (combines N and N-1)
56 # d, drop = remove commit from history
56 # d, drop = remove commit from history
57 # m, mess = edit message without changing commit content
57 # m, mess = edit message without changing commit content
58 #
58 #
59
59
60 At which point you close the editor and ``histedit`` starts working. When you
60 At which point you close the editor and ``histedit`` starts working. When you
61 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
61 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
62 those revisions together, offering you a chance to clean up the commit message::
62 those revisions together, offering you a chance to clean up the commit message::
63
63
64 Add beta
64 Add beta
65 ***
65 ***
66 Add delta
66 Add delta
67
67
68 Edit the commit message to your liking, then close the editor. For
68 Edit the commit message to your liking, then close the editor. For
69 this example, let's assume that the commit message was changed to
69 this example, let's assume that the commit message was changed to
70 ``Add beta and delta.`` After histedit has run and had a chance to
70 ``Add beta and delta.`` After histedit has run and had a chance to
71 remove any old or temporary revisions it needed, the history looks
71 remove any old or temporary revisions it needed, the history looks
72 like this::
72 like this::
73
73
74 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
74 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
75 | Add beta and delta.
75 | Add beta and delta.
76 |
76 |
77 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
77 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
78 | Add gamma
78 | Add gamma
79 |
79 |
80 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
80 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
81 Add alpha
81 Add alpha
82
82
83 Note that ``histedit`` does *not* remove any revisions (even its own temporary
83 Note that ``histedit`` does *not* remove any revisions (even its own temporary
84 ones) until after it has completed all the editing operations, so it will
84 ones) until after it has completed all the editing operations, so it will
85 probably perform several strip operations when it's done. For the above example,
85 probably perform several strip operations when it's done. For the above example,
86 it had to run strip twice. Strip can be slow depending on a variety of factors,
86 it had to run strip twice. Strip can be slow depending on a variety of factors,
87 so you might need to be a little patient. You can choose to keep the original
87 so you might need to be a little patient. You can choose to keep the original
88 revisions by passing the ``--keep`` flag.
88 revisions by passing the ``--keep`` flag.
89
89
90 The ``edit`` operation will drop you back to a command prompt,
90 The ``edit`` operation will drop you back to a command prompt,
91 allowing you to edit files freely, or even use ``hg record`` to commit
91 allowing you to edit files freely, or even use ``hg record`` to commit
92 some changes as a separate commit. When you're done, any remaining
92 some changes as a separate commit. When you're done, any remaining
93 uncommitted changes will be committed as well. When done, run ``hg
93 uncommitted changes will be committed as well. When done, run ``hg
94 histedit --continue`` to finish this step. You'll be prompted for a
94 histedit --continue`` to finish this step. You'll be prompted for a
95 new commit message, but the default commit message will be the
95 new commit message, but the default commit message will be the
96 original message for the ``edit`` ed revision.
96 original message for the ``edit`` ed revision.
97
97
98 The ``message`` operation will give you a chance to revise a commit
98 The ``message`` operation will give you a chance to revise a commit
99 message without changing the contents. It's a shortcut for doing
99 message without changing the contents. It's a shortcut for doing
100 ``edit`` immediately followed by `hg histedit --continue``.
100 ``edit`` immediately followed by `hg histedit --continue``.
101
101
102 If ``histedit`` encounters a conflict when moving a revision (while
102 If ``histedit`` encounters a conflict when moving a revision (while
103 handling ``pick`` or ``fold``), it'll stop in a similar manner to
103 handling ``pick`` or ``fold``), it'll stop in a similar manner to
104 ``edit`` with the difference that it won't prompt you for a commit
104 ``edit`` with the difference that it won't prompt you for a commit
105 message when done. If you decide at this point that you don't like how
105 message when done. If you decide at this point that you don't like how
106 much work it will be to rearrange history, or that you made a mistake,
106 much work it will be to rearrange history, or that you made a mistake,
107 you can use ``hg histedit --abort`` to abandon the new changes you
107 you can use ``hg histedit --abort`` to abandon the new changes you
108 have made and return to the state before you attempted to edit your
108 have made and return to the state before you attempted to edit your
109 history.
109 history.
110
110
111 If we clone the example repository above and add three more changes, such that
111 If we clone the example repository above and add three more changes, such that
112 we have the following history::
112 we have the following history::
113
113
114 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
114 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
115 | Add theta
115 | Add theta
116 |
116 |
117 o 5 140988835471 2009-04-27 18:04 -0500 stefan
117 o 5 140988835471 2009-04-27 18:04 -0500 stefan
118 | Add eta
118 | Add eta
119 |
119 |
120 o 4 122930637314 2009-04-27 18:04 -0500 stefan
120 o 4 122930637314 2009-04-27 18:04 -0500 stefan
121 | Add zeta
121 | Add zeta
122 |
122 |
123 o 3 836302820282 2009-04-27 18:04 -0500 stefan
123 o 3 836302820282 2009-04-27 18:04 -0500 stefan
124 | Add epsilon
124 | Add epsilon
125 |
125 |
126 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
126 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
127 | Add beta and delta.
127 | Add beta and delta.
128 |
128 |
129 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
129 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
130 | Add gamma
130 | Add gamma
131 |
131 |
132 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
132 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
133 Add alpha
133 Add alpha
134
134
135 If you run ``hg histedit --outgoing`` on the clone then it is the same
135 If you run ``hg histedit --outgoing`` on the clone then it is the same
136 as running ``hg histedit 836302820282``. If you need plan to push to a
136 as running ``hg histedit 836302820282``. If you need plan to push to a
137 repository that Mercurial does not detect to be related to the source
137 repository that Mercurial does not detect to be related to the source
138 repo, you can add a ``--force`` option.
138 repo, you can add a ``--force`` option.
139 """
139 """
140
140
141 try:
141 try:
142 import cPickle as pickle
142 import cPickle as pickle
143 except ImportError:
143 except ImportError:
144 import pickle
144 import pickle
145 import tempfile
145 import tempfile
146 import os
146 import os
147
147
148 from mercurial import bookmarks
148 from mercurial import bookmarks
149 from mercurial import cmdutil
149 from mercurial import cmdutil
150 from mercurial import discovery
150 from mercurial import discovery
151 from mercurial import error
151 from mercurial import error
152 from mercurial import hg
152 from mercurial import hg
153 from mercurial import lock as lockmod
153 from mercurial import lock as lockmod
154 from mercurial import node
154 from mercurial import node
155 from mercurial import patch
155 from mercurial import patch
156 from mercurial import repair
156 from mercurial import repair
157 from mercurial import scmutil
157 from mercurial import scmutil
158 from mercurial import util
158 from mercurial import util
159 from mercurial.i18n import _
159 from mercurial.i18n import _
160
160
161 cmdtable = {}
161 cmdtable = {}
162 command = cmdutil.command(cmdtable)
162 command = cmdutil.command(cmdtable)
163
163
164 testedwith = 'internal'
164 testedwith = 'internal'
165
165
166 # i18n: command names and abbreviations must remain untranslated
166 # i18n: command names and abbreviations must remain untranslated
167 editcomment = _("""# Edit history between %s and %s
167 editcomment = _("""# Edit history between %s and %s
168 #
168 #
169 # Commands:
169 # Commands:
170 # p, pick = use commit
170 # p, pick = use commit
171 # e, edit = use commit, but stop for amending
171 # e, edit = use commit, but stop for amending
172 # f, fold = use commit, but fold into previous commit (combines N and N-1)
172 # f, fold = use commit, but fold into previous commit (combines N and N-1)
173 # d, drop = remove commit from history
173 # d, drop = remove commit from history
174 # m, mess = edit message without changing commit content
174 # m, mess = edit message without changing commit content
175 #
175 #
176 """)
176 """)
177
177
178 def foldchanges(ui, repo, node1, node2, opts):
178 def foldchanges(ui, repo, node1, node2, opts):
179 """Produce a new changeset that represents the diff from node1 to node2."""
179 """Produce a new changeset that represents the diff from node1 to node2."""
180 try:
180 try:
181 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
181 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
182 fp = os.fdopen(fd, 'w')
182 fp = os.fdopen(fd, 'w')
183 diffopts = patch.diffopts(ui, opts)
183 diffopts = patch.diffopts(ui, opts)
184 diffopts.git = True
184 diffopts.git = True
185 diffopts.ignorews = False
185 diffopts.ignorews = False
186 diffopts.ignorewsamount = False
186 diffopts.ignorewsamount = False
187 diffopts.ignoreblanklines = False
187 diffopts.ignoreblanklines = False
188 gen = patch.diff(repo, node1, node2, opts=diffopts)
188 gen = patch.diff(repo, node1, node2, opts=diffopts)
189 for chunk in gen:
189 for chunk in gen:
190 fp.write(chunk)
190 fp.write(chunk)
191 fp.close()
191 fp.close()
192 files = set()
192 files = set()
193 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
193 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
194 finally:
194 finally:
195 os.unlink(patchfile)
195 os.unlink(patchfile)
196 return files
196 return files
197
197
198 def pick(ui, repo, ctx, ha, opts):
198 def pick(ui, repo, ctx, ha, opts):
199 oldctx = repo[ha]
199 oldctx = repo[ha]
200 if oldctx.parents()[0] == ctx:
200 if oldctx.parents()[0] == ctx:
201 ui.debug('node %s unchanged\n' % ha)
201 ui.debug('node %s unchanged\n' % ha)
202 return oldctx, [], [], []
202 return oldctx, [], [], []
203 hg.update(repo, ctx.node())
203 hg.update(repo, ctx.node())
204 try:
204 try:
205 files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
205 files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
206 if not files:
206 if not files:
207 ui.warn(_('%s: empty changeset')
207 ui.warn(_('%s: empty changeset')
208 % node.hex(ha))
208 % node.hex(ha))
209 return ctx, [], [], []
209 return ctx, [], [], []
210 except Exception:
210 except Exception:
211 raise util.Abort(_('Fix up the change and run '
211 raise util.Abort(_('Fix up the change and run '
212 'hg histedit --continue'))
212 'hg histedit --continue'))
213 n = repo.commit(text=oldctx.description(), user=oldctx.user(),
213 n = repo.commit(text=oldctx.description(), user=oldctx.user(),
214 date=oldctx.date(), extra=oldctx.extra())
214 date=oldctx.date(), extra=oldctx.extra())
215 return repo[n], [n], [oldctx.node()], []
215 return repo[n], [n], [oldctx.node()], []
216
216
217
217
218 def edit(ui, repo, ctx, ha, opts):
218 def edit(ui, repo, ctx, ha, opts):
219 oldctx = repo[ha]
219 oldctx = repo[ha]
220 hg.update(repo, ctx.node())
220 hg.update(repo, ctx.node())
221 try:
221 try:
222 foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
222 foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
223 except Exception:
223 except Exception:
224 pass
224 pass
225 raise util.Abort(_('Make changes as needed, you may commit or record as '
225 raise util.Abort(_('Make changes as needed, you may commit or record as '
226 'needed now.\nWhen you are finished, run hg'
226 'needed now.\nWhen you are finished, run hg'
227 ' histedit --continue to resume.'))
227 ' histedit --continue to resume.'))
228
228
229 def fold(ui, repo, ctx, ha, opts):
229 def fold(ui, repo, ctx, ha, opts):
230 oldctx = repo[ha]
230 oldctx = repo[ha]
231 hg.update(repo, ctx.node())
231 hg.update(repo, ctx.node())
232 try:
232 try:
233 files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
233 files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
234 if not files:
234 if not files:
235 ui.warn(_('%s: empty changeset')
235 ui.warn(_('%s: empty changeset')
236 % node.hex(ha))
236 % node.hex(ha))
237 return ctx, [], [], []
237 return ctx, [], [], []
238 except Exception:
238 except Exception:
239 raise util.Abort(_('Fix up the change and run '
239 raise util.Abort(_('Fix up the change and run '
240 'hg histedit --continue'))
240 'hg histedit --continue'))
241 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
241 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
242 date=oldctx.date(), extra=oldctx.extra())
242 date=oldctx.date(), extra=oldctx.extra())
243 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
243 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
244
244
245 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
245 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
246 parent = ctx.parents()[0].node()
246 parent = ctx.parents()[0].node()
247 hg.update(repo, parent)
247 hg.update(repo, parent)
248 foldchanges(ui, repo, parent, newnode, opts)
248 foldchanges(ui, repo, parent, newnode, opts)
249 newmessage = '\n***\n'.join(
249 newmessage = '\n***\n'.join(
250 [ctx.description()] +
250 [ctx.description()] +
251 [repo[r].description() for r in internalchanges] +
251 [repo[r].description() for r in internalchanges] +
252 [oldctx.description()]) + '\n'
252 [oldctx.description()]) + '\n'
253 # If the changesets are from the same author, keep it.
253 # If the changesets are from the same author, keep it.
254 if ctx.user() == oldctx.user():
254 if ctx.user() == oldctx.user():
255 username = ctx.user()
255 username = ctx.user()
256 else:
256 else:
257 username = ui.username()
257 username = ui.username()
258 newmessage = ui.edit(newmessage, username)
258 newmessage = ui.edit(newmessage, username)
259 n = repo.commit(text=newmessage, user=username,
259 n = repo.commit(text=newmessage, user=username,
260 date=max(ctx.date(), oldctx.date()), extra=oldctx.extra())
260 date=max(ctx.date(), oldctx.date()), extra=oldctx.extra())
261 return repo[n], [n], [oldctx.node(), ctx.node()], [newnode]
261 return repo[n], [n], [oldctx.node(), ctx.node()], [newnode]
262
262
263 def drop(ui, repo, ctx, ha, opts):
263 def drop(ui, repo, ctx, ha, opts):
264 return ctx, [], [repo[ha].node()], []
264 return ctx, [], [repo[ha].node()], []
265
265
266
266
267 def message(ui, repo, ctx, ha, opts):
267 def message(ui, repo, ctx, ha, opts):
268 oldctx = repo[ha]
268 oldctx = repo[ha]
269 hg.update(repo, ctx.node())
269 hg.update(repo, ctx.node())
270 try:
270 try:
271 foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
271 foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
272 except Exception:
272 except Exception:
273 raise util.Abort(_('Fix up the change and run '
273 raise util.Abort(_('Fix up the change and run '
274 'hg histedit --continue'))
274 'hg histedit --continue'))
275 message = oldctx.description() + '\n'
275 message = oldctx.description() + '\n'
276 message = ui.edit(message, ui.username())
276 message = ui.edit(message, ui.username())
277 new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(),
277 new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(),
278 extra=oldctx.extra())
278 extra=oldctx.extra())
279 newctx = repo[new]
279 newctx = repo[new]
280 if oldctx.node() != newctx.node():
280 if oldctx.node() != newctx.node():
281 return newctx, [new], [oldctx.node()], []
281 return newctx, [new], [oldctx.node()], []
282 # We didn't make an edit, so just indicate no replaced nodes
282 # We didn't make an edit, so just indicate no replaced nodes
283 return newctx, [new], [], []
283 return newctx, [new], [], []
284
284
285
286 def makedesc(c):
287 """build a initial action line for a ctx `c`
288
289 line are in the form:
290
291 pick <hash> <rev> <summary>
292 """
293 summary = ''
294 if c.description():
295 summary = c.description().splitlines()[0]
296 line = 'pick %s %d %s' % (c.hex()[:12], c.rev(), summary)
297 return line[:80] # trim to 80 chars so it's not stupidly wide in my editor
298
299 actiontable = {'p': pick,
285 actiontable = {'p': pick,
300 'pick': pick,
286 'pick': pick,
301 'e': edit,
287 'e': edit,
302 'edit': edit,
288 'edit': edit,
303 'f': fold,
289 'f': fold,
304 'fold': fold,
290 'fold': fold,
305 'd': drop,
291 'd': drop,
306 'drop': drop,
292 'drop': drop,
307 'm': message,
293 'm': message,
308 'mess': message,
294 'mess': message,
309 }
295 }
310
296
311 @command('histedit',
297 @command('histedit',
312 [('', 'commands', '',
298 [('', 'commands', '',
313 _('Read history edits from the specified file.')),
299 _('Read history edits from the specified file.')),
314 ('c', 'continue', False, _('continue an edit already in progress')),
300 ('c', 'continue', False, _('continue an edit already in progress')),
315 ('k', 'keep', False,
301 ('k', 'keep', False,
316 _("don't strip old nodes after edit is complete")),
302 _("don't strip old nodes after edit is complete")),
317 ('', 'abort', False, _('abort an edit in progress')),
303 ('', 'abort', False, _('abort an edit in progress')),
318 ('o', 'outgoing', False, _('changesets not found in destination')),
304 ('o', 'outgoing', False, _('changesets not found in destination')),
319 ('f', 'force', False,
305 ('f', 'force', False,
320 _('force outgoing even for unrelated repositories')),
306 _('force outgoing even for unrelated repositories')),
321 ('r', 'rev', [], _('first revision to be edited'))],
307 ('r', 'rev', [], _('first revision to be edited'))],
322 _("[PARENT]"))
308 _("[PARENT]"))
323 def histedit(ui, repo, *parent, **opts):
309 def histedit(ui, repo, *parent, **opts):
324 """interactively edit changeset history
310 """interactively edit changeset history
325 """
311 """
326 # TODO only abort if we try and histedit mq patches, not just
312 # TODO only abort if we try and histedit mq patches, not just
327 # blanket if mq patches are applied somewhere
313 # blanket if mq patches are applied somewhere
328 mq = getattr(repo, 'mq', None)
314 mq = getattr(repo, 'mq', None)
329 if mq and mq.applied:
315 if mq and mq.applied:
330 raise util.Abort(_('source has mq patches applied'))
316 raise util.Abort(_('source has mq patches applied'))
331
317
332 parent = list(parent) + opts.get('rev', [])
318 parent = list(parent) + opts.get('rev', [])
333 if opts.get('outgoing'):
319 if opts.get('outgoing'):
334 if len(parent) > 1:
320 if len(parent) > 1:
335 raise util.Abort(
321 raise util.Abort(
336 _('only one repo argument allowed with --outgoing'))
322 _('only one repo argument allowed with --outgoing'))
337 elif parent:
323 elif parent:
338 parent = parent[0]
324 parent = parent[0]
339
325
340 dest = ui.expandpath(parent or 'default-push', parent or 'default')
326 dest = ui.expandpath(parent or 'default-push', parent or 'default')
341 dest, revs = hg.parseurl(dest, None)[:2]
327 dest, revs = hg.parseurl(dest, None)[:2]
342 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
328 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
343
329
344 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
330 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
345 other = hg.peer(repo, opts, dest)
331 other = hg.peer(repo, opts, dest)
346
332
347 if revs:
333 if revs:
348 revs = [repo.lookup(rev) for rev in revs]
334 revs = [repo.lookup(rev) for rev in revs]
349
335
350 parent = discovery.findcommonoutgoing(
336 parent = discovery.findcommonoutgoing(
351 repo, other, [], force=opts.get('force')).missing[0:1]
337 repo, other, [], force=opts.get('force')).missing[0:1]
352 else:
338 else:
353 if opts.get('force'):
339 if opts.get('force'):
354 raise util.Abort(_('--force only allowed with --outgoing'))
340 raise util.Abort(_('--force only allowed with --outgoing'))
355
341
356 if opts.get('continue', False):
342 if opts.get('continue', False):
357 if len(parent) != 0:
343 if len(parent) != 0:
358 raise util.Abort(_('no arguments allowed with --continue'))
344 raise util.Abort(_('no arguments allowed with --continue'))
359 (parentctxnode, created, replaced,
345 (parentctxnode, created, replaced,
360 tmpnodes, existing, rules, keep, tip, replacemap) = readstate(repo)
346 tmpnodes, existing, rules, keep, tip, replacemap) = readstate(repo)
361 currentparent, wantnull = repo.dirstate.parents()
347 currentparent, wantnull = repo.dirstate.parents()
362 parentctx = repo[parentctxnode]
348 parentctx = repo[parentctxnode]
363 # existing is the list of revisions initially considered by
349 # existing is the list of revisions initially considered by
364 # histedit. Here we use it to list new changesets, descendants
350 # histedit. Here we use it to list new changesets, descendants
365 # of parentctx without an 'existing' changeset in-between. We
351 # of parentctx without an 'existing' changeset in-between. We
366 # also have to exclude 'existing' changesets which were
352 # also have to exclude 'existing' changesets which were
367 # previously dropped.
353 # previously dropped.
368 descendants = set(c.node() for c in
354 descendants = set(c.node() for c in
369 repo.set('(%n::) - %n', parentctxnode, parentctxnode))
355 repo.set('(%n::) - %n', parentctxnode, parentctxnode))
370 existing = set(existing)
356 existing = set(existing)
371 notdropped = set(n for n in existing if n in descendants and
357 notdropped = set(n for n in existing if n in descendants and
372 (n not in replacemap or replacemap[n] in descendants))
358 (n not in replacemap or replacemap[n] in descendants))
373 # Discover any nodes the user has added in the interim. We can
359 # Discover any nodes the user has added in the interim. We can
374 # miss changesets which were dropped and recreated the same.
360 # miss changesets which were dropped and recreated the same.
375 newchildren = list(c.node() for c in repo.set(
361 newchildren = list(c.node() for c in repo.set(
376 'sort(%ln - (%ln or %ln::))', descendants, existing, notdropped))
362 'sort(%ln - (%ln or %ln::))', descendants, existing, notdropped))
377 action, currentnode = rules.pop(0)
363 action, currentnode = rules.pop(0)
378 if action in ('f', 'fold'):
364 if action in ('f', 'fold'):
379 tmpnodes.extend(newchildren)
365 tmpnodes.extend(newchildren)
380 else:
366 else:
381 created.extend(newchildren)
367 created.extend(newchildren)
382
368
383 m, a, r, d = repo.status()[:4]
369 m, a, r, d = repo.status()[:4]
384 oldctx = repo[currentnode]
370 oldctx = repo[currentnode]
385 message = oldctx.description() + '\n'
371 message = oldctx.description() + '\n'
386 if action in ('e', 'edit', 'm', 'mess'):
372 if action in ('e', 'edit', 'm', 'mess'):
387 message = ui.edit(message, ui.username())
373 message = ui.edit(message, ui.username())
388 elif action in ('f', 'fold'):
374 elif action in ('f', 'fold'):
389 message = 'fold-temp-revision %s' % currentnode
375 message = 'fold-temp-revision %s' % currentnode
390 new = None
376 new = None
391 if m or a or r or d:
377 if m or a or r or d:
392 new = repo.commit(text=message, user=oldctx.user(),
378 new = repo.commit(text=message, user=oldctx.user(),
393 date=oldctx.date(), extra=oldctx.extra())
379 date=oldctx.date(), extra=oldctx.extra())
394
380
395 # If we're resuming a fold and we have new changes, mark the
381 # If we're resuming a fold and we have new changes, mark the
396 # replacements and finish the fold. If not, it's more like a
382 # replacements and finish the fold. If not, it's more like a
397 # drop of the changesets that disappeared, and we can skip
383 # drop of the changesets that disappeared, and we can skip
398 # this step.
384 # this step.
399 if action in ('f', 'fold') and (new or newchildren):
385 if action in ('f', 'fold') and (new or newchildren):
400 if new:
386 if new:
401 tmpnodes.append(new)
387 tmpnodes.append(new)
402 else:
388 else:
403 new = newchildren[-1]
389 new = newchildren[-1]
404 (parentctx, created_, replaced_, tmpnodes_) = finishfold(
390 (parentctx, created_, replaced_, tmpnodes_) = finishfold(
405 ui, repo, parentctx, oldctx, new, opts, newchildren)
391 ui, repo, parentctx, oldctx, new, opts, newchildren)
406 replaced.extend(replaced_)
392 replaced.extend(replaced_)
407 created.extend(created_)
393 created.extend(created_)
408 tmpnodes.extend(tmpnodes_)
394 tmpnodes.extend(tmpnodes_)
409 elif action not in ('d', 'drop'):
395 elif action not in ('d', 'drop'):
410 if new != oldctx.node():
396 if new != oldctx.node():
411 replaced.append(oldctx.node())
397 replaced.append(oldctx.node())
412 if new:
398 if new:
413 if new != oldctx.node():
399 if new != oldctx.node():
414 created.append(new)
400 created.append(new)
415 parentctx = repo[new]
401 parentctx = repo[new]
416
402
417 elif opts.get('abort', False):
403 elif opts.get('abort', False):
418 if len(parent) != 0:
404 if len(parent) != 0:
419 raise util.Abort(_('no arguments allowed with --abort'))
405 raise util.Abort(_('no arguments allowed with --abort'))
420 (parentctxnode, created, replaced, tmpnodes,
406 (parentctxnode, created, replaced, tmpnodes,
421 existing, rules, keep, tip, replacemap) = readstate(repo)
407 existing, rules, keep, tip, replacemap) = readstate(repo)
422 ui.debug('restore wc to old tip %s\n' % node.hex(tip))
408 ui.debug('restore wc to old tip %s\n' % node.hex(tip))
423 hg.clean(repo, tip)
409 hg.clean(repo, tip)
424 ui.debug('should strip created nodes %s\n' %
410 ui.debug('should strip created nodes %s\n' %
425 ', '.join([node.hex(n)[:12] for n in created]))
411 ', '.join([node.hex(n)[:12] for n in created]))
426 ui.debug('should strip temp nodes %s\n' %
412 ui.debug('should strip temp nodes %s\n' %
427 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
413 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
428 for nodes in (created, tmpnodes):
414 for nodes in (created, tmpnodes):
429 lock = None
415 lock = None
430 try:
416 try:
431 lock = repo.lock()
417 lock = repo.lock()
432 for n in reversed(nodes):
418 for n in reversed(nodes):
433 try:
419 try:
434 repair.strip(ui, repo, n)
420 repair.strip(ui, repo, n)
435 except error.LookupError:
421 except error.LookupError:
436 pass
422 pass
437 finally:
423 finally:
438 lockmod.release(lock)
424 lockmod.release(lock)
439 os.unlink(os.path.join(repo.path, 'histedit-state'))
425 os.unlink(os.path.join(repo.path, 'histedit-state'))
440 return
426 return
441 else:
427 else:
442 cmdutil.bailifchanged(repo)
428 cmdutil.bailifchanged(repo)
443 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
429 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
444 raise util.Abort(_('history edit already in progress, try '
430 raise util.Abort(_('history edit already in progress, try '
445 '--continue or --abort'))
431 '--continue or --abort'))
446
432
447 tip, empty = repo.dirstate.parents()
433 tip, empty = repo.dirstate.parents()
448
434
449
435
450 if len(parent) != 1:
436 if len(parent) != 1:
451 raise util.Abort(_('histedit requires exactly one parent revision'))
437 raise util.Abort(_('histedit requires exactly one parent revision'))
452 parent = scmutil.revsingle(repo, parent[0]).node()
438 parent = scmutil.revsingle(repo, parent[0]).node()
453
439
454 keep = opts.get('keep', False)
440 keep = opts.get('keep', False)
455 revs = between(repo, parent, tip, keep)
441 revs = between(repo, parent, tip, keep)
456
442
457 ctxs = [repo[r] for r in revs]
443 ctxs = [repo[r] for r in revs]
458 existing = [r.node() for r in ctxs]
444 existing = [r.node() for r in ctxs]
459 rules = opts.get('commands', '')
445 rules = opts.get('commands', '')
460 if not rules:
446 if not rules:
461 rules = '\n'.join([makedesc(c) for c in ctxs])
447 rules = '\n'.join([makedesc(c) for c in ctxs])
462 rules += '\n\n'
448 rules += '\n\n'
463 rules += editcomment % (node.hex(parent)[:12], node.hex(tip)[:12])
449 rules += editcomment % (node.hex(parent)[:12], node.hex(tip)[:12])
464 rules = ui.edit(rules, ui.username())
450 rules = ui.edit(rules, ui.username())
465 # Save edit rules in .hg/histedit-last-edit.txt in case
451 # Save edit rules in .hg/histedit-last-edit.txt in case
466 # the user needs to ask for help after something
452 # the user needs to ask for help after something
467 # surprising happens.
453 # surprising happens.
468 f = open(repo.join('histedit-last-edit.txt'), 'w')
454 f = open(repo.join('histedit-last-edit.txt'), 'w')
469 f.write(rules)
455 f.write(rules)
470 f.close()
456 f.close()
471 else:
457 else:
472 f = open(rules)
458 f = open(rules)
473 rules = f.read()
459 rules = f.read()
474 f.close()
460 f.close()
475 rules = [l for l in (r.strip() for r in rules.splitlines())
461 rules = [l for l in (r.strip() for r in rules.splitlines())
476 if l and not l[0] == '#']
462 if l and not l[0] == '#']
477 rules = verifyrules(rules, repo, ctxs)
463 rules = verifyrules(rules, repo, ctxs)
478
464
479 parentctx = repo[parent].parents()[0]
465 parentctx = repo[parent].parents()[0]
480 keep = opts.get('keep', False)
466 keep = opts.get('keep', False)
481 replaced = []
467 replaced = []
482 replacemap = {}
468 replacemap = {}
483 tmpnodes = []
469 tmpnodes = []
484 created = []
470 created = []
485
471
486
472
487 while rules:
473 while rules:
488 writestate(repo, parentctx.node(), created, replaced,
474 writestate(repo, parentctx.node(), created, replaced,
489 tmpnodes, existing, rules, keep, tip, replacemap)
475 tmpnodes, existing, rules, keep, tip, replacemap)
490 action, ha = rules.pop(0)
476 action, ha = rules.pop(0)
491 (parentctx, created_, replaced_, tmpnodes_) = actiontable[action](
477 (parentctx, created_, replaced_, tmpnodes_) = actiontable[action](
492 ui, repo, parentctx, ha, opts)
478 ui, repo, parentctx, ha, opts)
493
479
494 if replaced_:
480 if replaced_:
495 clen, rlen = len(created_), len(replaced_)
481 clen, rlen = len(created_), len(replaced_)
496 if clen == rlen == 1:
482 if clen == rlen == 1:
497 ui.debug('histedit: exact replacement of %s with %s\n' % (
483 ui.debug('histedit: exact replacement of %s with %s\n' % (
498 node.short(replaced_[0]), node.short(created_[0])))
484 node.short(replaced_[0]), node.short(created_[0])))
499
485
500 replacemap[replaced_[0]] = created_[0]
486 replacemap[replaced_[0]] = created_[0]
501 elif clen > rlen:
487 elif clen > rlen:
502 assert rlen == 1, ('unexpected replacement of '
488 assert rlen == 1, ('unexpected replacement of '
503 '%d changes with %d changes' % (rlen, clen))
489 '%d changes with %d changes' % (rlen, clen))
504 # made more changesets than we're replacing
490 # made more changesets than we're replacing
505 # TODO synthesize patch names for created patches
491 # TODO synthesize patch names for created patches
506 replacemap[replaced_[0]] = created_[-1]
492 replacemap[replaced_[0]] = created_[-1]
507 ui.debug('histedit: created many, assuming %s replaced by %s' %
493 ui.debug('histedit: created many, assuming %s replaced by %s' %
508 (node.short(replaced_[0]), node.short(created_[-1])))
494 (node.short(replaced_[0]), node.short(created_[-1])))
509 elif rlen > clen:
495 elif rlen > clen:
510 if not created_:
496 if not created_:
511 # This must be a drop. Try and put our metadata on
497 # This must be a drop. Try and put our metadata on
512 # the parent change.
498 # the parent change.
513 assert rlen == 1
499 assert rlen == 1
514 r = replaced_[0]
500 r = replaced_[0]
515 ui.debug('histedit: %s seems replaced with nothing, '
501 ui.debug('histedit: %s seems replaced with nothing, '
516 'finding a parent\n' % (node.short(r)))
502 'finding a parent\n' % (node.short(r)))
517 pctx = repo[r].parents()[0]
503 pctx = repo[r].parents()[0]
518 if pctx.node() in replacemap:
504 if pctx.node() in replacemap:
519 ui.debug('histedit: parent is already replaced\n')
505 ui.debug('histedit: parent is already replaced\n')
520 replacemap[r] = replacemap[pctx.node()]
506 replacemap[r] = replacemap[pctx.node()]
521 else:
507 else:
522 replacemap[r] = pctx.node()
508 replacemap[r] = pctx.node()
523 ui.debug('histedit: %s best replaced by %s\n' % (
509 ui.debug('histedit: %s best replaced by %s\n' % (
524 node.short(r), node.short(replacemap[r])))
510 node.short(r), node.short(replacemap[r])))
525 else:
511 else:
526 assert len(created_) == 1
512 assert len(created_) == 1
527 for r in replaced_:
513 for r in replaced_:
528 ui.debug('histedit: %s replaced by %s\n' % (
514 ui.debug('histedit: %s replaced by %s\n' % (
529 node.short(r), node.short(created_[0])))
515 node.short(r), node.short(created_[0])))
530 replacemap[r] = created_[0]
516 replacemap[r] = created_[0]
531 else:
517 else:
532 assert False, (
518 assert False, (
533 'Unhandled case in replacement mapping! '
519 'Unhandled case in replacement mapping! '
534 'replacing %d changes with %d changes' % (rlen, clen))
520 'replacing %d changes with %d changes' % (rlen, clen))
535 created.extend(created_)
521 created.extend(created_)
536 replaced.extend(replaced_)
522 replaced.extend(replaced_)
537 tmpnodes.extend(tmpnodes_)
523 tmpnodes.extend(tmpnodes_)
538
524
539 hg.update(repo, parentctx.node())
525 hg.update(repo, parentctx.node())
540
526
541 if not keep:
527 if not keep:
542 if replacemap:
528 if replacemap:
543 ui.note(_('histedit: Should update metadata for the following '
529 ui.note(_('histedit: Should update metadata for the following '
544 'changes:\n'))
530 'changes:\n'))
545
531
546 def copybms(old, new):
532 def copybms(old, new):
547 if old in tmpnodes or old in created:
533 if old in tmpnodes or old in created:
548 # can't have any metadata we'd want to update
534 # can't have any metadata we'd want to update
549 return
535 return
550 while new in replacemap:
536 while new in replacemap:
551 new = replacemap[new]
537 new = replacemap[new]
552 ui.note(_('histedit: %s to %s\n') % (node.short(old),
538 ui.note(_('histedit: %s to %s\n') % (node.short(old),
553 node.short(new)))
539 node.short(new)))
554 octx = repo[old]
540 octx = repo[old]
555 marks = octx.bookmarks()
541 marks = octx.bookmarks()
556 if marks:
542 if marks:
557 ui.note(_('histedit: moving bookmarks %s\n') %
543 ui.note(_('histedit: moving bookmarks %s\n') %
558 ', '.join(marks))
544 ', '.join(marks))
559 for mark in marks:
545 for mark in marks:
560 repo._bookmarks[mark] = new
546 repo._bookmarks[mark] = new
561 bookmarks.write(repo)
547 bookmarks.write(repo)
562
548
563 # We assume that bookmarks on the tip should remain
549 # We assume that bookmarks on the tip should remain
564 # tipmost, but bookmarks on non-tip changesets should go
550 # tipmost, but bookmarks on non-tip changesets should go
565 # to their most reasonable successor. As a result, find
551 # to their most reasonable successor. As a result, find
566 # the old tip and new tip and copy those bookmarks first,
552 # the old tip and new tip and copy those bookmarks first,
567 # then do the rest of the bookmark copies.
553 # then do the rest of the bookmark copies.
568 oldtip = sorted(replacemap.keys(), key=repo.changelog.rev)[-1]
554 oldtip = sorted(replacemap.keys(), key=repo.changelog.rev)[-1]
569 newtip = sorted(replacemap.values(), key=repo.changelog.rev)[-1]
555 newtip = sorted(replacemap.values(), key=repo.changelog.rev)[-1]
570 copybms(oldtip, newtip)
556 copybms(oldtip, newtip)
571
557
572 for old, new in sorted(replacemap.iteritems()):
558 for old, new in sorted(replacemap.iteritems()):
573 copybms(old, new)
559 copybms(old, new)
574 # TODO update mq state
560 # TODO update mq state
575
561
576 ui.debug('should strip replaced nodes %s\n' %
562 ui.debug('should strip replaced nodes %s\n' %
577 ', '.join([node.hex(n)[:12] for n in replaced]))
563 ', '.join([node.hex(n)[:12] for n in replaced]))
578 lock = None
564 lock = None
579 try:
565 try:
580 lock = repo.lock()
566 lock = repo.lock()
581 for n in sorted(replaced, key=lambda x: repo[x].rev()):
567 for n in sorted(replaced, key=lambda x: repo[x].rev()):
582 try:
568 try:
583 repair.strip(ui, repo, n)
569 repair.strip(ui, repo, n)
584 except error.LookupError:
570 except error.LookupError:
585 pass
571 pass
586 finally:
572 finally:
587 lockmod.release(lock)
573 lockmod.release(lock)
588
574
589 ui.debug('should strip temp nodes %s\n' %
575 ui.debug('should strip temp nodes %s\n' %
590 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
576 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
591 lock = None
577 lock = None
592 try:
578 try:
593 lock = repo.lock()
579 lock = repo.lock()
594 for n in reversed(tmpnodes):
580 for n in reversed(tmpnodes):
595 try:
581 try:
596 repair.strip(ui, repo, n)
582 repair.strip(ui, repo, n)
597 except error.LookupError:
583 except error.LookupError:
598 pass
584 pass
599 finally:
585 finally:
600 lockmod.release(lock)
586 lockmod.release(lock)
601 os.unlink(os.path.join(repo.path, 'histedit-state'))
587 os.unlink(os.path.join(repo.path, 'histedit-state'))
602 if os.path.exists(repo.sjoin('undo')):
588 if os.path.exists(repo.sjoin('undo')):
603 os.unlink(repo.sjoin('undo'))
589 os.unlink(repo.sjoin('undo'))
604
590
605
591
606 def between(repo, old, new, keep):
592 def between(repo, old, new, keep):
607 """select and validate the set of revision to edit
593 """select and validate the set of revision to edit
608
594
609 When keep is false, the specified set can't have children."""
595 When keep is false, the specified set can't have children."""
610 revs = [old]
596 revs = [old]
611 current = old
597 current = old
612 while current != new:
598 while current != new:
613 ctx = repo[current]
599 ctx = repo[current]
614 if not keep and len(ctx.children()) > 1:
600 if not keep and len(ctx.children()) > 1:
615 raise util.Abort(_('cannot edit history that would orphan nodes'))
601 raise util.Abort(_('cannot edit history that would orphan nodes'))
616 if len(ctx.parents()) != 1 and ctx.parents()[1] != node.nullid:
602 if len(ctx.parents()) != 1 and ctx.parents()[1] != node.nullid:
617 raise util.Abort(_("can't edit history with merges"))
603 raise util.Abort(_("can't edit history with merges"))
618 if not ctx.children():
604 if not ctx.children():
619 current = new
605 current = new
620 else:
606 else:
621 current = ctx.children()[0].node()
607 current = ctx.children()[0].node()
622 revs.append(current)
608 revs.append(current)
623 if len(repo[current].children()) and not keep:
609 if len(repo[current].children()) and not keep:
624 raise util.Abort(_('cannot edit history that would orphan nodes'))
610 raise util.Abort(_('cannot edit history that would orphan nodes'))
625 return revs
611 return revs
626
612
627
613
628 def writestate(repo, parentctxnode, created, replaced,
614 def writestate(repo, parentctxnode, created, replaced,
629 tmpnodes, existing, rules, keep, oldtip, replacemap):
615 tmpnodes, existing, rules, keep, oldtip, replacemap):
630 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
616 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
631 pickle.dump((parentctxnode, created, replaced,
617 pickle.dump((parentctxnode, created, replaced,
632 tmpnodes, existing, rules, keep, oldtip, replacemap),
618 tmpnodes, existing, rules, keep, oldtip, replacemap),
633 fp)
619 fp)
634 fp.close()
620 fp.close()
635
621
636 def readstate(repo):
622 def readstate(repo):
637 """Returns a tuple of (parentnode, created, replaced, tmp, existing, rules,
623 """Returns a tuple of (parentnode, created, replaced, tmp, existing, rules,
638 keep, oldtip, replacemap ).
624 keep, oldtip, replacemap ).
639 """
625 """
640 fp = open(os.path.join(repo.path, 'histedit-state'))
626 fp = open(os.path.join(repo.path, 'histedit-state'))
641 return pickle.load(fp)
627 return pickle.load(fp)
642
628
643
629
630 def makedesc(c):
631 """build a initial action line for a ctx `c`
632
633 line are in the form:
634
635 pick <hash> <rev> <summary>
636 """
637 summary = ''
638 if c.description():
639 summary = c.description().splitlines()[0]
640 line = 'pick %s %d %s' % (c.hex()[:12], c.rev(), summary)
641 return line[:80] # trim to 80 chars so it's not stupidly wide in my editor
642
644 def verifyrules(rules, repo, ctxs):
643 def verifyrules(rules, repo, ctxs):
645 """Verify that there exists exactly one edit rule per given changeset.
644 """Verify that there exists exactly one edit rule per given changeset.
646
645
647 Will abort if there are to many or too few rules, a malformed rule,
646 Will abort if there are to many or too few rules, a malformed rule,
648 or a rule on a changeset outside of the user-given range.
647 or a rule on a changeset outside of the user-given range.
649 """
648 """
650 parsed = []
649 parsed = []
651 if len(rules) != len(ctxs):
650 if len(rules) != len(ctxs):
652 raise util.Abort(_('must specify a rule for each changeset once'))
651 raise util.Abort(_('must specify a rule for each changeset once'))
653 for r in rules:
652 for r in rules:
654 if ' ' not in r:
653 if ' ' not in r:
655 raise util.Abort(_('malformed line "%s"') % r)
654 raise util.Abort(_('malformed line "%s"') % r)
656 action, rest = r.split(' ', 1)
655 action, rest = r.split(' ', 1)
657 if ' ' in rest.strip():
656 if ' ' in rest.strip():
658 ha, rest = rest.split(' ', 1)
657 ha, rest = rest.split(' ', 1)
659 else:
658 else:
660 ha = r.strip()
659 ha = r.strip()
661 try:
660 try:
662 if repo[ha] not in ctxs:
661 if repo[ha] not in ctxs:
663 raise util.Abort(
662 raise util.Abort(
664 _('may not use changesets other than the ones listed'))
663 _('may not use changesets other than the ones listed'))
665 except error.RepoError:
664 except error.RepoError:
666 raise util.Abort(_('unknown changeset %s listed') % ha)
665 raise util.Abort(_('unknown changeset %s listed') % ha)
667 if action not in actiontable:
666 if action not in actiontable:
668 raise util.Abort(_('unknown action "%s"') % action)
667 raise util.Abort(_('unknown action "%s"') % action)
669 parsed.append([action, ha])
668 parsed.append([action, ha])
670 return parsed
669 return parsed
General Comments 0
You need to be logged in to leave comments. Login now