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