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