##// END OF EJS Templates
histedit: fix unused variable warnings spotted by pyflakes
Patrick Mezard -
r17451:8e1fa8a3 default
parent child Browse files
Show More
@@ -1,661 +1,661 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 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 between(repo, old, new, keep):
198 def between(repo, old, new, keep):
199 revs = [old]
199 revs = [old]
200 current = old
200 current = old
201 while current != new:
201 while current != new:
202 ctx = repo[current]
202 ctx = repo[current]
203 if not keep and len(ctx.children()) > 1:
203 if not keep and len(ctx.children()) > 1:
204 raise util.Abort(_('cannot edit history that would orphan nodes'))
204 raise util.Abort(_('cannot edit history that would orphan nodes'))
205 if len(ctx.parents()) != 1 and ctx.parents()[1] != node.nullid:
205 if len(ctx.parents()) != 1 and ctx.parents()[1] != node.nullid:
206 raise util.Abort(_("can't edit history with merges"))
206 raise util.Abort(_("can't edit history with merges"))
207 if not ctx.children():
207 if not ctx.children():
208 current = new
208 current = new
209 else:
209 else:
210 current = ctx.children()[0].node()
210 current = ctx.children()[0].node()
211 revs.append(current)
211 revs.append(current)
212 if len(repo[current].children()) and not keep:
212 if len(repo[current].children()) and not keep:
213 raise util.Abort(_('cannot edit history that would orphan nodes'))
213 raise util.Abort(_('cannot edit history that would orphan nodes'))
214 return revs
214 return revs
215
215
216
216
217 def pick(ui, repo, ctx, ha, opts):
217 def pick(ui, repo, ctx, ha, opts):
218 oldctx = repo[ha]
218 oldctx = repo[ha]
219 if oldctx.parents()[0] == ctx:
219 if oldctx.parents()[0] == ctx:
220 ui.debug('node %s unchanged\n' % ha)
220 ui.debug('node %s unchanged\n' % ha)
221 return oldctx, [], [], []
221 return oldctx, [], [], []
222 hg.update(repo, ctx.node())
222 hg.update(repo, ctx.node())
223 try:
223 try:
224 files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
224 files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
225 if not files:
225 if not files:
226 ui.warn(_('%s: empty changeset')
226 ui.warn(_('%s: empty changeset')
227 % node.hex(ha))
227 % node.hex(ha))
228 return ctx, [], [], []
228 return ctx, [], [], []
229 except Exception:
229 except Exception:
230 raise util.Abort(_('Fix up the change and run '
230 raise util.Abort(_('Fix up the change and run '
231 'hg histedit --continue'))
231 'hg histedit --continue'))
232 n = repo.commit(text=oldctx.description(), user=oldctx.user(),
232 n = repo.commit(text=oldctx.description(), user=oldctx.user(),
233 date=oldctx.date(), extra=oldctx.extra())
233 date=oldctx.date(), extra=oldctx.extra())
234 return repo[n], [n], [oldctx.node()], []
234 return repo[n], [n], [oldctx.node()], []
235
235
236
236
237 def edit(ui, repo, ctx, ha, opts):
237 def edit(ui, repo, ctx, ha, opts):
238 oldctx = repo[ha]
238 oldctx = repo[ha]
239 hg.update(repo, ctx.node())
239 hg.update(repo, ctx.node())
240 try:
240 try:
241 files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
241 foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
242 except Exception:
242 except Exception:
243 pass
243 pass
244 raise util.Abort(_('Make changes as needed, you may commit or record as '
244 raise util.Abort(_('Make changes as needed, you may commit or record as '
245 'needed now.\nWhen you are finished, run hg'
245 'needed now.\nWhen you are finished, run hg'
246 ' histedit --continue to resume.'))
246 ' histedit --continue to resume.'))
247
247
248 def fold(ui, repo, ctx, ha, opts):
248 def fold(ui, repo, ctx, ha, opts):
249 oldctx = repo[ha]
249 oldctx = repo[ha]
250 hg.update(repo, ctx.node())
250 hg.update(repo, ctx.node())
251 try:
251 try:
252 files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
252 files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
253 if not files:
253 if not files:
254 ui.warn(_('%s: empty changeset')
254 ui.warn(_('%s: empty changeset')
255 % node.hex(ha))
255 % node.hex(ha))
256 return ctx, [], [], []
256 return ctx, [], [], []
257 except Exception:
257 except Exception:
258 raise util.Abort(_('Fix up the change and run '
258 raise util.Abort(_('Fix up the change and run '
259 'hg histedit --continue'))
259 'hg histedit --continue'))
260 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
260 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
261 date=oldctx.date(), extra=oldctx.extra())
261 date=oldctx.date(), extra=oldctx.extra())
262 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
262 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
263
263
264 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
264 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
265 parent = ctx.parents()[0].node()
265 parent = ctx.parents()[0].node()
266 hg.update(repo, parent)
266 hg.update(repo, parent)
267 files = foldchanges(ui, repo, parent, newnode, opts)
267 foldchanges(ui, repo, parent, newnode, opts)
268 newmessage = '\n***\n'.join(
268 newmessage = '\n***\n'.join(
269 [ctx.description()] +
269 [ctx.description()] +
270 [repo[r].description() for r in internalchanges] +
270 [repo[r].description() for r in internalchanges] +
271 [oldctx.description()]) + '\n'
271 [oldctx.description()]) + '\n'
272 # If the changesets are from the same author, keep it.
272 # If the changesets are from the same author, keep it.
273 if ctx.user() == oldctx.user():
273 if ctx.user() == oldctx.user():
274 username = ctx.user()
274 username = ctx.user()
275 else:
275 else:
276 username = ui.username()
276 username = ui.username()
277 newmessage = ui.edit(newmessage, username)
277 newmessage = ui.edit(newmessage, username)
278 n = repo.commit(text=newmessage, user=username,
278 n = repo.commit(text=newmessage, user=username,
279 date=max(ctx.date(), oldctx.date()), extra=oldctx.extra())
279 date=max(ctx.date(), oldctx.date()), extra=oldctx.extra())
280 return repo[n], [n], [oldctx.node(), ctx.node()], [newnode]
280 return repo[n], [n], [oldctx.node(), ctx.node()], [newnode]
281
281
282 def drop(ui, repo, ctx, ha, opts):
282 def drop(ui, repo, ctx, ha, opts):
283 return ctx, [], [repo[ha].node()], []
283 return ctx, [], [repo[ha].node()], []
284
284
285
285
286 def message(ui, repo, ctx, ha, opts):
286 def message(ui, repo, ctx, ha, opts):
287 oldctx = repo[ha]
287 oldctx = repo[ha]
288 hg.update(repo, ctx.node())
288 hg.update(repo, ctx.node())
289 try:
289 try:
290 files = foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
290 foldchanges(ui, repo, oldctx.p1().node() , ha, opts)
291 except Exception:
291 except Exception:
292 raise util.Abort(_('Fix up the change and run '
292 raise util.Abort(_('Fix up the change and run '
293 'hg histedit --continue'))
293 'hg histedit --continue'))
294 message = oldctx.description() + '\n'
294 message = oldctx.description() + '\n'
295 message = ui.edit(message, ui.username())
295 message = ui.edit(message, ui.username())
296 new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(),
296 new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(),
297 extra=oldctx.extra())
297 extra=oldctx.extra())
298 newctx = repo[new]
298 newctx = repo[new]
299 if oldctx.node() != newctx.node():
299 if oldctx.node() != newctx.node():
300 return newctx, [new], [oldctx.node()], []
300 return newctx, [new], [oldctx.node()], []
301 # We didn't make an edit, so just indicate no replaced nodes
301 # We didn't make an edit, so just indicate no replaced nodes
302 return newctx, [new], [], []
302 return newctx, [new], [], []
303
303
304
304
305 def makedesc(c):
305 def makedesc(c):
306 summary = ''
306 summary = ''
307 if c.description():
307 if c.description():
308 summary = c.description().splitlines()[0]
308 summary = c.description().splitlines()[0]
309 line = 'pick %s %d %s' % (c.hex()[:12], c.rev(), summary)
309 line = 'pick %s %d %s' % (c.hex()[:12], c.rev(), summary)
310 return line[:80] # trim to 80 chars so it's not stupidly wide in my editor
310 return line[:80] # trim to 80 chars so it's not stupidly wide in my editor
311
311
312 actiontable = {'p': pick,
312 actiontable = {'p': pick,
313 'pick': pick,
313 'pick': pick,
314 'e': edit,
314 'e': edit,
315 'edit': edit,
315 'edit': edit,
316 'f': fold,
316 'f': fold,
317 'fold': fold,
317 'fold': fold,
318 'd': drop,
318 'd': drop,
319 'drop': drop,
319 'drop': drop,
320 'm': message,
320 'm': message,
321 'mess': message,
321 'mess': message,
322 }
322 }
323
323
324 @command('histedit',
324 @command('histedit',
325 [('', 'commands', '',
325 [('', 'commands', '',
326 _('Read history edits from the specified file.')),
326 _('Read history edits from the specified file.')),
327 ('c', 'continue', False, _('continue an edit already in progress')),
327 ('c', 'continue', False, _('continue an edit already in progress')),
328 ('k', 'keep', False,
328 ('k', 'keep', False,
329 _("don't strip old nodes after edit is complete")),
329 _("don't strip old nodes after edit is complete")),
330 ('', 'abort', False, _('abort an edit in progress')),
330 ('', 'abort', False, _('abort an edit in progress')),
331 ('o', 'outgoing', False, _('changesets not found in destination')),
331 ('o', 'outgoing', False, _('changesets not found in destination')),
332 ('f', 'force', False,
332 ('f', 'force', False,
333 _('force outgoing even for unrelated repositories')),
333 _('force outgoing even for unrelated repositories')),
334 ('r', 'rev', [], _('first revision to be edited'))],
334 ('r', 'rev', [], _('first revision to be edited'))],
335 _("[PARENT]"))
335 _("[PARENT]"))
336 def histedit(ui, repo, *parent, **opts):
336 def histedit(ui, repo, *parent, **opts):
337 """interactively edit changeset history
337 """interactively edit changeset history
338 """
338 """
339 # TODO only abort if we try and histedit mq patches, not just
339 # TODO only abort if we try and histedit mq patches, not just
340 # blanket if mq patches are applied somewhere
340 # blanket if mq patches are applied somewhere
341 mq = getattr(repo, 'mq', None)
341 mq = getattr(repo, 'mq', None)
342 if mq and mq.applied:
342 if mq and mq.applied:
343 raise util.Abort(_('source has mq patches applied'))
343 raise util.Abort(_('source has mq patches applied'))
344
344
345 parent = list(parent) + opts.get('rev', [])
345 parent = list(parent) + opts.get('rev', [])
346 if opts.get('outgoing'):
346 if opts.get('outgoing'):
347 if len(parent) > 1:
347 if len(parent) > 1:
348 raise util.Abort(
348 raise util.Abort(
349 _('only one repo argument allowed with --outgoing'))
349 _('only one repo argument allowed with --outgoing'))
350 elif parent:
350 elif parent:
351 parent = parent[0]
351 parent = parent[0]
352
352
353 dest = ui.expandpath(parent or 'default-push', parent or 'default')
353 dest = ui.expandpath(parent or 'default-push', parent or 'default')
354 dest, revs = hg.parseurl(dest, None)[:2]
354 dest, revs = hg.parseurl(dest, None)[:2]
355 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
355 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
356
356
357 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
357 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
358 other = hg.peer(repo, opts, dest)
358 other = hg.peer(repo, opts, dest)
359
359
360 if revs:
360 if revs:
361 revs = [repo.lookup(rev) for rev in revs]
361 revs = [repo.lookup(rev) for rev in revs]
362
362
363 parent = discovery.findcommonoutgoing(
363 parent = discovery.findcommonoutgoing(
364 repo, other, [], force=opts.get('force')).missing[0:1]
364 repo, other, [], force=opts.get('force')).missing[0:1]
365 else:
365 else:
366 if opts.get('force'):
366 if opts.get('force'):
367 raise util.Abort(_('--force only allowed with --outgoing'))
367 raise util.Abort(_('--force only allowed with --outgoing'))
368
368
369 if opts.get('continue', False):
369 if opts.get('continue', False):
370 if len(parent) != 0:
370 if len(parent) != 0:
371 raise util.Abort(_('no arguments allowed with --continue'))
371 raise util.Abort(_('no arguments allowed with --continue'))
372 (parentctxnode, created, replaced,
372 (parentctxnode, created, replaced,
373 tmpnodes, existing, rules, keep, tip, replacemap) = readstate(repo)
373 tmpnodes, existing, rules, keep, tip, replacemap) = readstate(repo)
374 currentparent, wantnull = repo.dirstate.parents()
374 currentparent, wantnull = repo.dirstate.parents()
375 parentctx = repo[parentctxnode]
375 parentctx = repo[parentctxnode]
376 # existing is the list of revisions initially considered by
376 # existing is the list of revisions initially considered by
377 # histedit. Here we use it to list new changesets, descendants
377 # histedit. Here we use it to list new changesets, descendants
378 # of parentctx without an 'existing' changeset in-between. We
378 # of parentctx without an 'existing' changeset in-between. We
379 # also have to exclude 'existing' changesets which were
379 # also have to exclude 'existing' changesets which were
380 # previously dropped.
380 # previously dropped.
381 descendants = set(c.node() for c in
381 descendants = set(c.node() for c in
382 repo.set('(%n::) - %n', parentctxnode, parentctxnode))
382 repo.set('(%n::) - %n', parentctxnode, parentctxnode))
383 existing = set(existing)
383 existing = set(existing)
384 notdropped = set(n for n in existing if n in descendants and
384 notdropped = set(n for n in existing if n in descendants and
385 (n not in replacemap or replacemap[n] in descendants))
385 (n not in replacemap or replacemap[n] in descendants))
386 # Discover any nodes the user has added in the interim. We can
386 # Discover any nodes the user has added in the interim. We can
387 # miss changesets which were dropped and recreated the same.
387 # miss changesets which were dropped and recreated the same.
388 newchildren = list(c.node() for c in repo.set(
388 newchildren = list(c.node() for c in repo.set(
389 'sort(%ln - (%ln or %ln::))', descendants, existing, notdropped))
389 'sort(%ln - (%ln or %ln::))', descendants, existing, notdropped))
390 action, currentnode = rules.pop(0)
390 action, currentnode = rules.pop(0)
391 if action in ('f', 'fold'):
391 if action in ('f', 'fold'):
392 tmpnodes.extend(newchildren)
392 tmpnodes.extend(newchildren)
393 else:
393 else:
394 created.extend(newchildren)
394 created.extend(newchildren)
395
395
396 m, a, r, d = repo.status()[:4]
396 m, a, r, d = repo.status()[:4]
397 oldctx = repo[currentnode]
397 oldctx = repo[currentnode]
398 message = oldctx.description() + '\n'
398 message = oldctx.description() + '\n'
399 if action in ('e', 'edit', 'm', 'mess'):
399 if action in ('e', 'edit', 'm', 'mess'):
400 message = ui.edit(message, ui.username())
400 message = ui.edit(message, ui.username())
401 elif action in ('f', 'fold'):
401 elif action in ('f', 'fold'):
402 message = 'fold-temp-revision %s' % currentnode
402 message = 'fold-temp-revision %s' % currentnode
403 new = None
403 new = None
404 if m or a or r or d:
404 if m or a or r or d:
405 new = repo.commit(text=message, user=oldctx.user(),
405 new = repo.commit(text=message, user=oldctx.user(),
406 date=oldctx.date(), extra=oldctx.extra())
406 date=oldctx.date(), extra=oldctx.extra())
407
407
408 # If we're resuming a fold and we have new changes, mark the
408 # If we're resuming a fold and we have new changes, mark the
409 # replacements and finish the fold. If not, it's more like a
409 # replacements and finish the fold. If not, it's more like a
410 # drop of the changesets that disappeared, and we can skip
410 # drop of the changesets that disappeared, and we can skip
411 # this step.
411 # this step.
412 if action in ('f', 'fold') and (new or newchildren):
412 if action in ('f', 'fold') and (new or newchildren):
413 if new:
413 if new:
414 tmpnodes.append(new)
414 tmpnodes.append(new)
415 else:
415 else:
416 new = newchildren[-1]
416 new = newchildren[-1]
417 (parentctx, created_, replaced_, tmpnodes_) = finishfold(
417 (parentctx, created_, replaced_, tmpnodes_) = finishfold(
418 ui, repo, parentctx, oldctx, new, opts, newchildren)
418 ui, repo, parentctx, oldctx, new, opts, newchildren)
419 replaced.extend(replaced_)
419 replaced.extend(replaced_)
420 created.extend(created_)
420 created.extend(created_)
421 tmpnodes.extend(tmpnodes_)
421 tmpnodes.extend(tmpnodes_)
422 elif action not in ('d', 'drop'):
422 elif action not in ('d', 'drop'):
423 if new != oldctx.node():
423 if new != oldctx.node():
424 replaced.append(oldctx.node())
424 replaced.append(oldctx.node())
425 if new:
425 if new:
426 if new != oldctx.node():
426 if new != oldctx.node():
427 created.append(new)
427 created.append(new)
428 parentctx = repo[new]
428 parentctx = repo[new]
429
429
430 elif opts.get('abort', False):
430 elif opts.get('abort', False):
431 if len(parent) != 0:
431 if len(parent) != 0:
432 raise util.Abort(_('no arguments allowed with --abort'))
432 raise util.Abort(_('no arguments allowed with --abort'))
433 (parentctxnode, created, replaced, tmpnodes,
433 (parentctxnode, created, replaced, tmpnodes,
434 existing, rules, keep, tip, replacemap) = readstate(repo)
434 existing, rules, keep, tip, replacemap) = readstate(repo)
435 ui.debug('restore wc to old tip %s\n' % node.hex(tip))
435 ui.debug('restore wc to old tip %s\n' % node.hex(tip))
436 hg.clean(repo, tip)
436 hg.clean(repo, tip)
437 ui.debug('should strip created nodes %s\n' %
437 ui.debug('should strip created nodes %s\n' %
438 ', '.join([node.hex(n)[:12] for n in created]))
438 ', '.join([node.hex(n)[:12] for n in created]))
439 ui.debug('should strip temp nodes %s\n' %
439 ui.debug('should strip temp nodes %s\n' %
440 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
440 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
441 for nodes in (created, tmpnodes):
441 for nodes in (created, tmpnodes):
442 lock = None
442 lock = None
443 try:
443 try:
444 lock = repo.lock()
444 lock = repo.lock()
445 for n in reversed(nodes):
445 for n in reversed(nodes):
446 try:
446 try:
447 repair.strip(ui, repo, n)
447 repair.strip(ui, repo, n)
448 except error.LookupError:
448 except error.LookupError:
449 pass
449 pass
450 finally:
450 finally:
451 lockmod.release(lock)
451 lockmod.release(lock)
452 os.unlink(os.path.join(repo.path, 'histedit-state'))
452 os.unlink(os.path.join(repo.path, 'histedit-state'))
453 return
453 return
454 else:
454 else:
455 cmdutil.bailifchanged(repo)
455 cmdutil.bailifchanged(repo)
456 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
456 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
457 raise util.Abort(_('history edit already in progress, try '
457 raise util.Abort(_('history edit already in progress, try '
458 '--continue or --abort'))
458 '--continue or --abort'))
459
459
460 tip, empty = repo.dirstate.parents()
460 tip, empty = repo.dirstate.parents()
461
461
462
462
463 if len(parent) != 1:
463 if len(parent) != 1:
464 raise util.Abort(_('histedit requires exactly one parent revision'))
464 raise util.Abort(_('histedit requires exactly one parent revision'))
465 parent = scmutil.revsingle(repo, parent[0]).node()
465 parent = scmutil.revsingle(repo, parent[0]).node()
466
466
467 keep = opts.get('keep', False)
467 keep = opts.get('keep', False)
468 revs = between(repo, parent, tip, keep)
468 revs = between(repo, parent, tip, keep)
469
469
470 ctxs = [repo[r] for r in revs]
470 ctxs = [repo[r] for r in revs]
471 existing = [r.node() for r in ctxs]
471 existing = [r.node() for r in ctxs]
472 rules = opts.get('commands', '')
472 rules = opts.get('commands', '')
473 if not rules:
473 if not rules:
474 rules = '\n'.join([makedesc(c) for c in ctxs])
474 rules = '\n'.join([makedesc(c) for c in ctxs])
475 rules += '\n\n'
475 rules += '\n\n'
476 rules += editcomment % (node.hex(parent)[:12], node.hex(tip)[:12])
476 rules += editcomment % (node.hex(parent)[:12], node.hex(tip)[:12])
477 rules = ui.edit(rules, ui.username())
477 rules = ui.edit(rules, ui.username())
478 # Save edit rules in .hg/histedit-last-edit.txt in case
478 # Save edit rules in .hg/histedit-last-edit.txt in case
479 # the user needs to ask for help after something
479 # the user needs to ask for help after something
480 # surprising happens.
480 # surprising happens.
481 f = open(repo.join('histedit-last-edit.txt'), 'w')
481 f = open(repo.join('histedit-last-edit.txt'), 'w')
482 f.write(rules)
482 f.write(rules)
483 f.close()
483 f.close()
484 else:
484 else:
485 f = open(rules)
485 f = open(rules)
486 rules = f.read()
486 rules = f.read()
487 f.close()
487 f.close()
488 rules = [l for l in (r.strip() for r in rules.splitlines())
488 rules = [l for l in (r.strip() for r in rules.splitlines())
489 if l and not l[0] == '#']
489 if l and not l[0] == '#']
490 rules = verifyrules(rules, repo, ctxs)
490 rules = verifyrules(rules, repo, ctxs)
491
491
492 parentctx = repo[parent].parents()[0]
492 parentctx = repo[parent].parents()[0]
493 keep = opts.get('keep', False)
493 keep = opts.get('keep', False)
494 replaced = []
494 replaced = []
495 replacemap = {}
495 replacemap = {}
496 tmpnodes = []
496 tmpnodes = []
497 created = []
497 created = []
498
498
499
499
500 while rules:
500 while rules:
501 writestate(repo, parentctx.node(), created, replaced,
501 writestate(repo, parentctx.node(), created, replaced,
502 tmpnodes, existing, rules, keep, tip, replacemap)
502 tmpnodes, existing, rules, keep, tip, replacemap)
503 action, ha = rules.pop(0)
503 action, ha = rules.pop(0)
504 (parentctx, created_, replaced_, tmpnodes_) = actiontable[action](
504 (parentctx, created_, replaced_, tmpnodes_) = actiontable[action](
505 ui, repo, parentctx, ha, opts)
505 ui, repo, parentctx, ha, opts)
506
506
507 if replaced_:
507 if replaced_:
508 clen, rlen = len(created_), len(replaced_)
508 clen, rlen = len(created_), len(replaced_)
509 if clen == rlen == 1:
509 if clen == rlen == 1:
510 ui.debug('histedit: exact replacement of %s with %s\n' % (
510 ui.debug('histedit: exact replacement of %s with %s\n' % (
511 node.short(replaced_[0]), node.short(created_[0])))
511 node.short(replaced_[0]), node.short(created_[0])))
512
512
513 replacemap[replaced_[0]] = created_[0]
513 replacemap[replaced_[0]] = created_[0]
514 elif clen > rlen:
514 elif clen > rlen:
515 assert rlen == 1, ('unexpected replacement of '
515 assert rlen == 1, ('unexpected replacement of '
516 '%d changes with %d changes' % (rlen, clen))
516 '%d changes with %d changes' % (rlen, clen))
517 # made more changesets than we're replacing
517 # made more changesets than we're replacing
518 # TODO synthesize patch names for created patches
518 # TODO synthesize patch names for created patches
519 replacemap[replaced_[0]] = created_[-1]
519 replacemap[replaced_[0]] = created_[-1]
520 ui.debug('histedit: created many, assuming %s replaced by %s' %
520 ui.debug('histedit: created many, assuming %s replaced by %s' %
521 (node.short(replaced_[0]), node.short(created_[-1])))
521 (node.short(replaced_[0]), node.short(created_[-1])))
522 elif rlen > clen:
522 elif rlen > clen:
523 if not created_:
523 if not created_:
524 # This must be a drop. Try and put our metadata on
524 # This must be a drop. Try and put our metadata on
525 # the parent change.
525 # the parent change.
526 assert rlen == 1
526 assert rlen == 1
527 r = replaced_[0]
527 r = replaced_[0]
528 ui.debug('histedit: %s seems replaced with nothing, '
528 ui.debug('histedit: %s seems replaced with nothing, '
529 'finding a parent\n' % (node.short(r)))
529 'finding a parent\n' % (node.short(r)))
530 pctx = repo[r].parents()[0]
530 pctx = repo[r].parents()[0]
531 if pctx.node() in replacemap:
531 if pctx.node() in replacemap:
532 ui.debug('histedit: parent is already replaced\n')
532 ui.debug('histedit: parent is already replaced\n')
533 replacemap[r] = replacemap[pctx.node()]
533 replacemap[r] = replacemap[pctx.node()]
534 else:
534 else:
535 replacemap[r] = pctx.node()
535 replacemap[r] = pctx.node()
536 ui.debug('histedit: %s best replaced by %s\n' % (
536 ui.debug('histedit: %s best replaced by %s\n' % (
537 node.short(r), node.short(replacemap[r])))
537 node.short(r), node.short(replacemap[r])))
538 else:
538 else:
539 assert len(created_) == 1
539 assert len(created_) == 1
540 for r in replaced_:
540 for r in replaced_:
541 ui.debug('histedit: %s replaced by %s\n' % (
541 ui.debug('histedit: %s replaced by %s\n' % (
542 node.short(r), node.short(created_[0])))
542 node.short(r), node.short(created_[0])))
543 replacemap[r] = created_[0]
543 replacemap[r] = created_[0]
544 else:
544 else:
545 assert False, (
545 assert False, (
546 'Unhandled case in replacement mapping! '
546 'Unhandled case in replacement mapping! '
547 'replacing %d changes with %d changes' % (rlen, clen))
547 'replacing %d changes with %d changes' % (rlen, clen))
548 created.extend(created_)
548 created.extend(created_)
549 replaced.extend(replaced_)
549 replaced.extend(replaced_)
550 tmpnodes.extend(tmpnodes_)
550 tmpnodes.extend(tmpnodes_)
551
551
552 hg.update(repo, parentctx.node())
552 hg.update(repo, parentctx.node())
553
553
554 if not keep:
554 if not keep:
555 if replacemap:
555 if replacemap:
556 ui.note(_('histedit: Should update metadata for the following '
556 ui.note(_('histedit: Should update metadata for the following '
557 'changes:\n'))
557 'changes:\n'))
558
558
559 def copybms(old, new):
559 def copybms(old, new):
560 if old in tmpnodes or old in created:
560 if old in tmpnodes or old in created:
561 # can't have any metadata we'd want to update
561 # can't have any metadata we'd want to update
562 return
562 return
563 while new in replacemap:
563 while new in replacemap:
564 new = replacemap[new]
564 new = replacemap[new]
565 ui.note(_('histedit: %s to %s\n') % (node.short(old),
565 ui.note(_('histedit: %s to %s\n') % (node.short(old),
566 node.short(new)))
566 node.short(new)))
567 octx = repo[old]
567 octx = repo[old]
568 marks = octx.bookmarks()
568 marks = octx.bookmarks()
569 if marks:
569 if marks:
570 ui.note(_('histedit: moving bookmarks %s\n') %
570 ui.note(_('histedit: moving bookmarks %s\n') %
571 ', '.join(marks))
571 ', '.join(marks))
572 for mark in marks:
572 for mark in marks:
573 repo._bookmarks[mark] = new
573 repo._bookmarks[mark] = new
574 bookmarks.write(repo)
574 bookmarks.write(repo)
575
575
576 # We assume that bookmarks on the tip should remain
576 # We assume that bookmarks on the tip should remain
577 # tipmost, but bookmarks on non-tip changesets should go
577 # tipmost, but bookmarks on non-tip changesets should go
578 # to their most reasonable successor. As a result, find
578 # to their most reasonable successor. As a result, find
579 # the old tip and new tip and copy those bookmarks first,
579 # the old tip and new tip and copy those bookmarks first,
580 # then do the rest of the bookmark copies.
580 # then do the rest of the bookmark copies.
581 oldtip = sorted(replacemap.keys(), key=repo.changelog.rev)[-1]
581 oldtip = sorted(replacemap.keys(), key=repo.changelog.rev)[-1]
582 newtip = sorted(replacemap.values(), key=repo.changelog.rev)[-1]
582 newtip = sorted(replacemap.values(), key=repo.changelog.rev)[-1]
583 copybms(oldtip, newtip)
583 copybms(oldtip, newtip)
584
584
585 for old, new in sorted(replacemap.iteritems()):
585 for old, new in sorted(replacemap.iteritems()):
586 copybms(old, new)
586 copybms(old, new)
587 # TODO update mq state
587 # TODO update mq state
588
588
589 ui.debug('should strip replaced nodes %s\n' %
589 ui.debug('should strip replaced nodes %s\n' %
590 ', '.join([node.hex(n)[:12] for n in replaced]))
590 ', '.join([node.hex(n)[:12] for n in replaced]))
591 lock = None
591 lock = None
592 try:
592 try:
593 lock = repo.lock()
593 lock = repo.lock()
594 for n in sorted(replaced, key=lambda x: repo[x].rev()):
594 for n in sorted(replaced, key=lambda x: repo[x].rev()):
595 try:
595 try:
596 repair.strip(ui, repo, n)
596 repair.strip(ui, repo, n)
597 except error.LookupError:
597 except error.LookupError:
598 pass
598 pass
599 finally:
599 finally:
600 lockmod.release(lock)
600 lockmod.release(lock)
601
601
602 ui.debug('should strip temp nodes %s\n' %
602 ui.debug('should strip temp nodes %s\n' %
603 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
603 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
604 lock = None
604 lock = None
605 try:
605 try:
606 lock = repo.lock()
606 lock = repo.lock()
607 for n in reversed(tmpnodes):
607 for n in reversed(tmpnodes):
608 try:
608 try:
609 repair.strip(ui, repo, n)
609 repair.strip(ui, repo, n)
610 except error.LookupError:
610 except error.LookupError:
611 pass
611 pass
612 finally:
612 finally:
613 lockmod.release(lock)
613 lockmod.release(lock)
614 os.unlink(os.path.join(repo.path, 'histedit-state'))
614 os.unlink(os.path.join(repo.path, 'histedit-state'))
615 if os.path.exists(repo.sjoin('undo')):
615 if os.path.exists(repo.sjoin('undo')):
616 os.unlink(repo.sjoin('undo'))
616 os.unlink(repo.sjoin('undo'))
617
617
618
618
619 def writestate(repo, parentctxnode, created, replaced,
619 def writestate(repo, parentctxnode, created, replaced,
620 tmpnodes, existing, rules, keep, oldtip, replacemap):
620 tmpnodes, existing, rules, keep, oldtip, replacemap):
621 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
621 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
622 pickle.dump((parentctxnode, created, replaced,
622 pickle.dump((parentctxnode, created, replaced,
623 tmpnodes, existing, rules, keep, oldtip, replacemap),
623 tmpnodes, existing, rules, keep, oldtip, replacemap),
624 fp)
624 fp)
625 fp.close()
625 fp.close()
626
626
627 def readstate(repo):
627 def readstate(repo):
628 """Returns a tuple of (parentnode, created, replaced, tmp, existing, rules,
628 """Returns a tuple of (parentnode, created, replaced, tmp, existing, rules,
629 keep, oldtip, replacemap ).
629 keep, oldtip, replacemap ).
630 """
630 """
631 fp = open(os.path.join(repo.path, 'histedit-state'))
631 fp = open(os.path.join(repo.path, 'histedit-state'))
632 return pickle.load(fp)
632 return pickle.load(fp)
633
633
634
634
635 def verifyrules(rules, repo, ctxs):
635 def verifyrules(rules, repo, ctxs):
636 """Verify that there exists exactly one edit rule per given changeset.
636 """Verify that there exists exactly one edit rule per given changeset.
637
637
638 Will abort if there are to many or too few rules, a malformed rule,
638 Will abort if there are to many or too few rules, a malformed rule,
639 or a rule on a changeset outside of the user-given range.
639 or a rule on a changeset outside of the user-given range.
640 """
640 """
641 parsed = []
641 parsed = []
642 if len(rules) != len(ctxs):
642 if len(rules) != len(ctxs):
643 raise util.Abort(_('must specify a rule for each changeset once'))
643 raise util.Abort(_('must specify a rule for each changeset once'))
644 for r in rules:
644 for r in rules:
645 if ' ' not in r:
645 if ' ' not in r:
646 raise util.Abort(_('malformed line "%s"') % r)
646 raise util.Abort(_('malformed line "%s"') % r)
647 action, rest = r.split(' ', 1)
647 action, rest = r.split(' ', 1)
648 if ' ' in rest.strip():
648 if ' ' in rest.strip():
649 ha, rest = rest.split(' ', 1)
649 ha, rest = rest.split(' ', 1)
650 else:
650 else:
651 ha = r.strip()
651 ha = r.strip()
652 try:
652 try:
653 if repo[ha] not in ctxs:
653 if repo[ha] not in ctxs:
654 raise util.Abort(
654 raise util.Abort(
655 _('may not use changesets other than the ones listed'))
655 _('may not use changesets other than the ones listed'))
656 except error.RepoError:
656 except error.RepoError:
657 raise util.Abort(_('unknown changeset %s listed') % ha)
657 raise util.Abort(_('unknown changeset %s listed') % ha)
658 if action not in actiontable:
658 if action not in actiontable:
659 raise util.Abort(_('unknown action "%s"') % action)
659 raise util.Abort(_('unknown action "%s"') % action)
660 parsed.append([action, ha])
660 parsed.append([action, ha])
661 return parsed
661 return parsed
General Comments 0
You need to be logged in to leave comments. Login now