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