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