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