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