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