##// END OF EJS Templates
merge with stable
Matt Mackall -
r17341:b131e24e merge default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

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