##// END OF EJS Templates
histedit: use stable iteration order for processing bookmarks...
Mads Kiilerich -
r17084:69dae798 default
parent child Browse files
Show More
@@ -1,564 +1,564 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 Inspired by git rebase --interactive.
9 Inspired by git rebase --interactive.
10 """
10 """
11 try:
11 try:
12 import cPickle as pickle
12 import cPickle as pickle
13 except ImportError:
13 except ImportError:
14 import pickle
14 import pickle
15 import tempfile
15 import tempfile
16 import os
16 import os
17
17
18 from mercurial import bookmarks
18 from mercurial import bookmarks
19 from mercurial import cmdutil
19 from mercurial import cmdutil
20 from mercurial import discovery
20 from mercurial import discovery
21 from mercurial import error
21 from mercurial import error
22 from mercurial import hg
22 from mercurial import hg
23 from mercurial import node
23 from mercurial import node
24 from mercurial import patch
24 from mercurial import patch
25 from mercurial import repair
25 from mercurial import repair
26 from mercurial import scmutil
26 from mercurial import scmutil
27 from mercurial import util
27 from mercurial import util
28 from mercurial.i18n import _
28 from mercurial.i18n import _
29
29
30 testedwith = 'internal'
30 testedwith = 'internal'
31
31
32 editcomment = """
32 editcomment = """
33
33
34 # Edit history between %s and %s
34 # Edit history between %s and %s
35 #
35 #
36 # Commands:
36 # Commands:
37 # p, pick = use commit
37 # p, pick = use commit
38 # e, edit = use commit, but stop for amending
38 # e, edit = use commit, but stop for amending
39 # f, fold = use commit, but fold into previous commit (combines N and N-1)
39 # f, fold = use commit, but fold into previous commit (combines N and N-1)
40 # d, drop = remove commit from history
40 # d, drop = remove commit from history
41 # m, mess = edit message without changing commit content
41 # m, mess = edit message without changing commit content
42 #
42 #
43 """
43 """
44
44
45 def between(repo, old, new, keep):
45 def between(repo, old, new, keep):
46 revs = [old]
46 revs = [old]
47 current = old
47 current = old
48 while current != new:
48 while current != new:
49 ctx = repo[current]
49 ctx = repo[current]
50 if not keep and len(ctx.children()) > 1:
50 if not keep and len(ctx.children()) > 1:
51 raise util.Abort(_('cannot edit history that would orphan nodes'))
51 raise util.Abort(_('cannot edit history that would orphan nodes'))
52 if len(ctx.parents()) != 1 and ctx.parents()[1] != node.nullid:
52 if len(ctx.parents()) != 1 and ctx.parents()[1] != node.nullid:
53 raise util.Abort(_("can't edit history with merges"))
53 raise util.Abort(_("can't edit history with merges"))
54 if not ctx.children():
54 if not ctx.children():
55 current = new
55 current = new
56 else:
56 else:
57 current = ctx.children()[0].node()
57 current = ctx.children()[0].node()
58 revs.append(current)
58 revs.append(current)
59 if len(repo[current].children()) and not keep:
59 if len(repo[current].children()) and not keep:
60 raise util.Abort(_('cannot edit history that would orphan nodes'))
60 raise util.Abort(_('cannot edit history that would orphan nodes'))
61 return revs
61 return revs
62
62
63
63
64 def pick(ui, repo, ctx, ha, opts):
64 def pick(ui, repo, ctx, ha, opts):
65 oldctx = repo[ha]
65 oldctx = repo[ha]
66 if oldctx.parents()[0] == ctx:
66 if oldctx.parents()[0] == ctx:
67 ui.debug('node %s unchanged\n' % ha)
67 ui.debug('node %s unchanged\n' % ha)
68 return oldctx, [], [], []
68 return oldctx, [], [], []
69 hg.update(repo, ctx.node())
69 hg.update(repo, ctx.node())
70 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
70 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
71 fp = os.fdopen(fd, 'w')
71 fp = os.fdopen(fd, 'w')
72 diffopts = patch.diffopts(ui, opts)
72 diffopts = patch.diffopts(ui, opts)
73 diffopts.git = True
73 diffopts.git = True
74 diffopts.ignorews = False
74 diffopts.ignorews = False
75 diffopts.ignorewsamount = False
75 diffopts.ignorewsamount = False
76 diffopts.ignoreblanklines = False
76 diffopts.ignoreblanklines = False
77 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
77 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
78 for chunk in gen:
78 for chunk in gen:
79 fp.write(chunk)
79 fp.write(chunk)
80 fp.close()
80 fp.close()
81 try:
81 try:
82 files = set()
82 files = set()
83 try:
83 try:
84 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
84 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
85 if not files:
85 if not files:
86 ui.warn(_('%s: empty changeset')
86 ui.warn(_('%s: empty changeset')
87 % node.hex(ha))
87 % node.hex(ha))
88 return ctx, [], [], []
88 return ctx, [], [], []
89 finally:
89 finally:
90 os.unlink(patchfile)
90 os.unlink(patchfile)
91 except Exception:
91 except Exception:
92 raise util.Abort(_('Fix up the change and run '
92 raise util.Abort(_('Fix up the change and run '
93 'hg histedit --continue'))
93 'hg histedit --continue'))
94 n = repo.commit(text=oldctx.description(), user=oldctx.user(),
94 n = repo.commit(text=oldctx.description(), user=oldctx.user(),
95 date=oldctx.date(), extra=oldctx.extra())
95 date=oldctx.date(), extra=oldctx.extra())
96 return repo[n], [n], [oldctx.node()], []
96 return repo[n], [n], [oldctx.node()], []
97
97
98
98
99 def edit(ui, repo, ctx, ha, opts):
99 def edit(ui, repo, ctx, ha, opts):
100 oldctx = repo[ha]
100 oldctx = repo[ha]
101 hg.update(repo, ctx.node())
101 hg.update(repo, ctx.node())
102 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
102 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
103 fp = os.fdopen(fd, 'w')
103 fp = os.fdopen(fd, 'w')
104 diffopts = patch.diffopts(ui, opts)
104 diffopts = patch.diffopts(ui, opts)
105 diffopts.git = True
105 diffopts.git = True
106 diffopts.ignorews = False
106 diffopts.ignorews = False
107 diffopts.ignorewsamount = False
107 diffopts.ignorewsamount = False
108 diffopts.ignoreblanklines = False
108 diffopts.ignoreblanklines = False
109 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
109 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
110 for chunk in gen:
110 for chunk in gen:
111 fp.write(chunk)
111 fp.write(chunk)
112 fp.close()
112 fp.close()
113 try:
113 try:
114 files = set()
114 files = set()
115 try:
115 try:
116 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
116 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
117 finally:
117 finally:
118 os.unlink(patchfile)
118 os.unlink(patchfile)
119 except Exception:
119 except Exception:
120 pass
120 pass
121 raise util.Abort(_('Make changes as needed, you may commit or record as '
121 raise util.Abort(_('Make changes as needed, you may commit or record as '
122 'needed now.\nWhen you are finished, run hg'
122 'needed now.\nWhen you are finished, run hg'
123 ' histedit --continue to resume.'))
123 ' histedit --continue to resume.'))
124
124
125 def fold(ui, repo, ctx, ha, opts):
125 def fold(ui, repo, ctx, ha, opts):
126 oldctx = repo[ha]
126 oldctx = repo[ha]
127 hg.update(repo, ctx.node())
127 hg.update(repo, ctx.node())
128 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
128 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
129 fp = os.fdopen(fd, 'w')
129 fp = os.fdopen(fd, 'w')
130 diffopts = patch.diffopts(ui, opts)
130 diffopts = patch.diffopts(ui, opts)
131 diffopts.git = True
131 diffopts.git = True
132 diffopts.ignorews = False
132 diffopts.ignorews = False
133 diffopts.ignorewsamount = False
133 diffopts.ignorewsamount = False
134 diffopts.ignoreblanklines = False
134 diffopts.ignoreblanklines = False
135 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
135 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
136 for chunk in gen:
136 for chunk in gen:
137 fp.write(chunk)
137 fp.write(chunk)
138 fp.close()
138 fp.close()
139 try:
139 try:
140 files = set()
140 files = set()
141 try:
141 try:
142 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
142 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
143 if not files:
143 if not files:
144 ui.warn(_('%s: empty changeset')
144 ui.warn(_('%s: empty changeset')
145 % node.hex(ha))
145 % node.hex(ha))
146 return ctx, [], [], []
146 return ctx, [], [], []
147 finally:
147 finally:
148 os.unlink(patchfile)
148 os.unlink(patchfile)
149 except Exception:
149 except Exception:
150 raise util.Abort(_('Fix up the change and run '
150 raise util.Abort(_('Fix up the change and run '
151 'hg histedit --continue'))
151 'hg histedit --continue'))
152 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
152 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
153 date=oldctx.date(), extra=oldctx.extra())
153 date=oldctx.date(), extra=oldctx.extra())
154 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
154 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
155
155
156 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
156 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
157 parent = ctx.parents()[0].node()
157 parent = ctx.parents()[0].node()
158 hg.update(repo, parent)
158 hg.update(repo, parent)
159 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
159 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
160 fp = os.fdopen(fd, 'w')
160 fp = os.fdopen(fd, 'w')
161 diffopts = patch.diffopts(ui, opts)
161 diffopts = patch.diffopts(ui, opts)
162 diffopts.git = True
162 diffopts.git = True
163 diffopts.ignorews = False
163 diffopts.ignorews = False
164 diffopts.ignorewsamount = False
164 diffopts.ignorewsamount = False
165 diffopts.ignoreblanklines = False
165 diffopts.ignoreblanklines = False
166 gen = patch.diff(repo, parent, newnode, opts=diffopts)
166 gen = patch.diff(repo, parent, newnode, opts=diffopts)
167 for chunk in gen:
167 for chunk in gen:
168 fp.write(chunk)
168 fp.write(chunk)
169 fp.close()
169 fp.close()
170 files = set()
170 files = set()
171 try:
171 try:
172 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
172 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
173 finally:
173 finally:
174 os.unlink(patchfile)
174 os.unlink(patchfile)
175 newmessage = '\n***\n'.join(
175 newmessage = '\n***\n'.join(
176 [ctx.description()] +
176 [ctx.description()] +
177 [repo[r].description() for r in internalchanges] +
177 [repo[r].description() for r in internalchanges] +
178 [oldctx.description()])
178 [oldctx.description()])
179 # If the changesets are from the same author, keep it.
179 # If the changesets are from the same author, keep it.
180 if ctx.user() == oldctx.user():
180 if ctx.user() == oldctx.user():
181 username = ctx.user()
181 username = ctx.user()
182 else:
182 else:
183 username = ui.username()
183 username = ui.username()
184 newmessage = ui.edit(newmessage, username)
184 newmessage = ui.edit(newmessage, username)
185 n = repo.commit(text=newmessage, user=username,
185 n = repo.commit(text=newmessage, user=username,
186 date=max(ctx.date(), oldctx.date()), extra=oldctx.extra())
186 date=max(ctx.date(), oldctx.date()), extra=oldctx.extra())
187 return repo[n], [n], [oldctx.node(), ctx.node()], [newnode]
187 return repo[n], [n], [oldctx.node(), ctx.node()], [newnode]
188
188
189 def drop(ui, repo, ctx, ha, opts):
189 def drop(ui, repo, ctx, ha, opts):
190 return ctx, [], [repo[ha].node()], []
190 return ctx, [], [repo[ha].node()], []
191
191
192
192
193 def message(ui, repo, ctx, ha, opts):
193 def message(ui, repo, ctx, ha, opts):
194 oldctx = repo[ha]
194 oldctx = repo[ha]
195 hg.update(repo, ctx.node())
195 hg.update(repo, ctx.node())
196 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
196 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
197 fp = os.fdopen(fd, 'w')
197 fp = os.fdopen(fd, 'w')
198 diffopts = patch.diffopts(ui, opts)
198 diffopts = patch.diffopts(ui, opts)
199 diffopts.git = True
199 diffopts.git = True
200 diffopts.ignorews = False
200 diffopts.ignorews = False
201 diffopts.ignorewsamount = False
201 diffopts.ignorewsamount = False
202 diffopts.ignoreblanklines = False
202 diffopts.ignoreblanklines = False
203 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
203 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
204 for chunk in gen:
204 for chunk in gen:
205 fp.write(chunk)
205 fp.write(chunk)
206 fp.close()
206 fp.close()
207 try:
207 try:
208 files = set()
208 files = set()
209 try:
209 try:
210 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
210 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
211 finally:
211 finally:
212 os.unlink(patchfile)
212 os.unlink(patchfile)
213 except Exception:
213 except Exception:
214 raise util.Abort(_('Fix up the change and run '
214 raise util.Abort(_('Fix up the change and run '
215 'hg histedit --continue'))
215 'hg histedit --continue'))
216 message = oldctx.description()
216 message = oldctx.description()
217 message = ui.edit(message, ui.username())
217 message = ui.edit(message, ui.username())
218 new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(),
218 new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(),
219 extra=oldctx.extra())
219 extra=oldctx.extra())
220 newctx = repo[new]
220 newctx = repo[new]
221 if oldctx.node() != newctx.node():
221 if oldctx.node() != newctx.node():
222 return newctx, [new], [oldctx.node()], []
222 return newctx, [new], [oldctx.node()], []
223 # We didn't make an edit, so just indicate no replaced nodes
223 # We didn't make an edit, so just indicate no replaced nodes
224 return newctx, [new], [], []
224 return newctx, [new], [], []
225
225
226
226
227 def makedesc(c):
227 def makedesc(c):
228 summary = ''
228 summary = ''
229 if c.description():
229 if c.description():
230 summary = c.description().splitlines()[0]
230 summary = c.description().splitlines()[0]
231 line = 'pick %s %d %s' % (c.hex()[:12], c.rev(), summary)
231 line = 'pick %s %d %s' % (c.hex()[:12], c.rev(), summary)
232 return line[:80] # trim to 80 chars so it's not stupidly wide in my editor
232 return line[:80] # trim to 80 chars so it's not stupidly wide in my editor
233
233
234 actiontable = {'p': pick,
234 actiontable = {'p': pick,
235 'pick': pick,
235 'pick': pick,
236 'e': edit,
236 'e': edit,
237 'edit': edit,
237 'edit': edit,
238 'f': fold,
238 'f': fold,
239 'fold': fold,
239 'fold': fold,
240 'd': drop,
240 'd': drop,
241 'drop': drop,
241 'drop': drop,
242 'm': message,
242 'm': message,
243 'mess': message,
243 'mess': message,
244 }
244 }
245 def histedit(ui, repo, *parent, **opts):
245 def histedit(ui, repo, *parent, **opts):
246 """hg histedit <parent>
246 """hg histedit <parent>
247 """
247 """
248 # TODO only abort if we try and histedit mq patches, not just
248 # TODO only abort if we try and histedit mq patches, not just
249 # blanket if mq patches are applied somewhere
249 # blanket if mq patches are applied somewhere
250 mq = getattr(repo, 'mq', None)
250 mq = getattr(repo, 'mq', None)
251 if mq and mq.applied:
251 if mq and mq.applied:
252 raise util.Abort(_('source has mq patches applied'))
252 raise util.Abort(_('source has mq patches applied'))
253
253
254 parent = list(parent) + opts.get('rev', [])
254 parent = list(parent) + opts.get('rev', [])
255 if opts.get('outgoing'):
255 if opts.get('outgoing'):
256 if len(parent) > 1:
256 if len(parent) > 1:
257 raise util.Abort(
257 raise util.Abort(
258 _('only one repo argument allowed with --outgoing'))
258 _('only one repo argument allowed with --outgoing'))
259 elif parent:
259 elif parent:
260 parent = parent[0]
260 parent = parent[0]
261
261
262 dest = ui.expandpath(parent or 'default-push', parent or 'default')
262 dest = ui.expandpath(parent or 'default-push', parent or 'default')
263 dest, revs = hg.parseurl(dest, None)[:2]
263 dest, revs = hg.parseurl(dest, None)[:2]
264 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
264 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
265
265
266 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
266 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
267 other = hg.repository(hg.remoteui(repo, opts), dest)
267 other = hg.repository(hg.remoteui(repo, opts), dest)
268
268
269 if revs:
269 if revs:
270 revs = [repo.lookup(rev) for rev in revs]
270 revs = [repo.lookup(rev) for rev in revs]
271
271
272 parent = discovery.findcommonoutgoing(
272 parent = discovery.findcommonoutgoing(
273 repo, other, [], force=opts.get('force')).missing[0:1]
273 repo, other, [], force=opts.get('force')).missing[0:1]
274 else:
274 else:
275 if opts.get('force'):
275 if opts.get('force'):
276 raise util.Abort(_('--force only allowed with --outgoing'))
276 raise util.Abort(_('--force only allowed with --outgoing'))
277
277
278 if opts.get('continue', False):
278 if opts.get('continue', False):
279 if len(parent) != 0:
279 if len(parent) != 0:
280 raise util.Abort(_('no arguments allowed with --continue'))
280 raise util.Abort(_('no arguments allowed with --continue'))
281 (parentctxnode, created, replaced,
281 (parentctxnode, created, replaced,
282 tmpnodes, existing, rules, keep, tip, replacemap) = readstate(repo)
282 tmpnodes, existing, rules, keep, tip, replacemap) = readstate(repo)
283 currentparent, wantnull = repo.dirstate.parents()
283 currentparent, wantnull = repo.dirstate.parents()
284 parentctx = repo[parentctxnode]
284 parentctx = repo[parentctxnode]
285 # discover any nodes the user has added in the interim
285 # discover any nodes the user has added in the interim
286 newchildren = [c for c in parentctx.children()
286 newchildren = [c for c in parentctx.children()
287 if c.node() not in existing]
287 if c.node() not in existing]
288 action, currentnode = rules.pop(0)
288 action, currentnode = rules.pop(0)
289 while newchildren:
289 while newchildren:
290 if action in ('f', 'fold'):
290 if action in ('f', 'fold'):
291 tmpnodes.extend([n.node() for n in newchildren])
291 tmpnodes.extend([n.node() for n in newchildren])
292 else:
292 else:
293 created.extend([n.node() for n in newchildren])
293 created.extend([n.node() for n in newchildren])
294 filtered = []
294 filtered = []
295 for r in newchildren:
295 for r in newchildren:
296 filtered += [c for c in r.children() if c.node not in existing]
296 filtered += [c for c in r.children() if c.node not in existing]
297 newchildren = filtered
297 newchildren = filtered
298 m, a, r, d = repo.status()[:4]
298 m, a, r, d = repo.status()[:4]
299 oldctx = repo[currentnode]
299 oldctx = repo[currentnode]
300 message = oldctx.description()
300 message = oldctx.description()
301 if action in ('e', 'edit', 'm', 'mess'):
301 if action in ('e', 'edit', 'm', 'mess'):
302 message = ui.edit(message, ui.username())
302 message = ui.edit(message, ui.username())
303 elif action in ('f', 'fold'):
303 elif action in ('f', 'fold'):
304 message = 'fold-temp-revision %s' % currentnode
304 message = 'fold-temp-revision %s' % currentnode
305 new = None
305 new = None
306 if m or a or r or d:
306 if m or a or r or d:
307 new = repo.commit(text=message, user=oldctx.user(),
307 new = repo.commit(text=message, user=oldctx.user(),
308 date=oldctx.date(), extra=oldctx.extra())
308 date=oldctx.date(), extra=oldctx.extra())
309
309
310 if action in ('f', 'fold'):
310 if action in ('f', 'fold'):
311 if new:
311 if new:
312 tmpnodes.append(new)
312 tmpnodes.append(new)
313 else:
313 else:
314 new = newchildren[-1]
314 new = newchildren[-1]
315 (parentctx, created_, replaced_, tmpnodes_) = finishfold(
315 (parentctx, created_, replaced_, tmpnodes_) = finishfold(
316 ui, repo, parentctx, oldctx, new, opts, newchildren)
316 ui, repo, parentctx, oldctx, new, opts, newchildren)
317 replaced.extend(replaced_)
317 replaced.extend(replaced_)
318 created.extend(created_)
318 created.extend(created_)
319 tmpnodes.extend(tmpnodes_)
319 tmpnodes.extend(tmpnodes_)
320 elif action not in ('d', 'drop'):
320 elif action not in ('d', 'drop'):
321 if new != oldctx.node():
321 if new != oldctx.node():
322 replaced.append(oldctx.node())
322 replaced.append(oldctx.node())
323 if new:
323 if new:
324 if new != oldctx.node():
324 if new != oldctx.node():
325 created.append(new)
325 created.append(new)
326 parentctx = repo[new]
326 parentctx = repo[new]
327
327
328 elif opts.get('abort', False):
328 elif opts.get('abort', False):
329 if len(parent) != 0:
329 if len(parent) != 0:
330 raise util.Abort(_('no arguments allowed with --abort'))
330 raise util.Abort(_('no arguments allowed with --abort'))
331 (parentctxnode, created, replaced, tmpnodes,
331 (parentctxnode, created, replaced, tmpnodes,
332 existing, rules, keep, tip, replacemap) = readstate(repo)
332 existing, rules, keep, tip, replacemap) = readstate(repo)
333 ui.debug('restore wc to old tip %s\n' % node.hex(tip))
333 ui.debug('restore wc to old tip %s\n' % node.hex(tip))
334 hg.clean(repo, tip)
334 hg.clean(repo, tip)
335 ui.debug('should strip created nodes %s\n' %
335 ui.debug('should strip created nodes %s\n' %
336 ', '.join([node.hex(n)[:12] for n in created]))
336 ', '.join([node.hex(n)[:12] for n in created]))
337 ui.debug('should strip temp nodes %s\n' %
337 ui.debug('should strip temp nodes %s\n' %
338 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
338 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
339 for nodes in (created, tmpnodes):
339 for nodes in (created, tmpnodes):
340 for n in reversed(nodes):
340 for n in reversed(nodes):
341 try:
341 try:
342 repair.strip(ui, repo, n)
342 repair.strip(ui, repo, n)
343 except error.LookupError:
343 except error.LookupError:
344 pass
344 pass
345 os.unlink(os.path.join(repo.path, 'histedit-state'))
345 os.unlink(os.path.join(repo.path, 'histedit-state'))
346 return
346 return
347 else:
347 else:
348 cmdutil.bailifchanged(repo)
348 cmdutil.bailifchanged(repo)
349 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
349 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
350 raise util.Abort(_('history edit already in progress, try '
350 raise util.Abort(_('history edit already in progress, try '
351 '--continue or --abort'))
351 '--continue or --abort'))
352
352
353 tip, empty = repo.dirstate.parents()
353 tip, empty = repo.dirstate.parents()
354
354
355
355
356 if len(parent) != 1:
356 if len(parent) != 1:
357 raise util.Abort(_('histedit requires exactly one parent revision'))
357 raise util.Abort(_('histedit requires exactly one parent revision'))
358 parent = scmutil.revsingle(repo, parent[0]).node()
358 parent = scmutil.revsingle(repo, parent[0]).node()
359
359
360 keep = opts.get('keep', False)
360 keep = opts.get('keep', False)
361 revs = between(repo, parent, tip, keep)
361 revs = between(repo, parent, tip, keep)
362
362
363 ctxs = [repo[r] for r in revs]
363 ctxs = [repo[r] for r in revs]
364 existing = [r.node() for r in ctxs]
364 existing = [r.node() for r in ctxs]
365 rules = opts.get('commands', '')
365 rules = opts.get('commands', '')
366 if not rules:
366 if not rules:
367 rules = '\n'.join([makedesc(c) for c in ctxs])
367 rules = '\n'.join([makedesc(c) for c in ctxs])
368 rules += editcomment % (node.hex(parent)[:12], node.hex(tip)[:12])
368 rules += editcomment % (node.hex(parent)[:12], node.hex(tip)[:12])
369 rules = ui.edit(rules, ui.username())
369 rules = ui.edit(rules, ui.username())
370 # Save edit rules in .hg/histedit-last-edit.txt in case
370 # Save edit rules in .hg/histedit-last-edit.txt in case
371 # the user needs to ask for help after something
371 # the user needs to ask for help after something
372 # surprising happens.
372 # surprising happens.
373 f = open(repo.join('histedit-last-edit.txt'), 'w')
373 f = open(repo.join('histedit-last-edit.txt'), 'w')
374 f.write(rules)
374 f.write(rules)
375 f.close()
375 f.close()
376 else:
376 else:
377 f = open(rules)
377 f = open(rules)
378 rules = f.read()
378 rules = f.read()
379 f.close()
379 f.close()
380 rules = [l for l in (r.strip() for r in rules.splitlines())
380 rules = [l for l in (r.strip() for r in rules.splitlines())
381 if l and not l[0] == '#']
381 if l and not l[0] == '#']
382 rules = verifyrules(rules, repo, ctxs)
382 rules = verifyrules(rules, repo, ctxs)
383
383
384 parentctx = repo[parent].parents()[0]
384 parentctx = repo[parent].parents()[0]
385 keep = opts.get('keep', False)
385 keep = opts.get('keep', False)
386 replaced = []
386 replaced = []
387 replacemap = {}
387 replacemap = {}
388 tmpnodes = []
388 tmpnodes = []
389 created = []
389 created = []
390
390
391
391
392 while rules:
392 while rules:
393 writestate(repo, parentctx.node(), created, replaced,
393 writestate(repo, parentctx.node(), created, replaced,
394 tmpnodes, existing, rules, keep, tip, replacemap)
394 tmpnodes, existing, rules, keep, tip, replacemap)
395 action, ha = rules.pop(0)
395 action, ha = rules.pop(0)
396 (parentctx, created_, replaced_, tmpnodes_) = actiontable[action](
396 (parentctx, created_, replaced_, tmpnodes_) = actiontable[action](
397 ui, repo, parentctx, ha, opts)
397 ui, repo, parentctx, ha, opts)
398
398
399 hexshort = lambda x: node.hex(x)[:12]
399 hexshort = lambda x: node.hex(x)[:12]
400
400
401 if replaced_:
401 if replaced_:
402 clen, rlen = len(created_), len(replaced_)
402 clen, rlen = len(created_), len(replaced_)
403 if clen == rlen == 1:
403 if clen == rlen == 1:
404 ui.debug('histedit: exact replacement of %s with %s\n' % (
404 ui.debug('histedit: exact replacement of %s with %s\n' % (
405 hexshort(replaced_[0]), hexshort(created_[0])))
405 hexshort(replaced_[0]), hexshort(created_[0])))
406
406
407 replacemap[replaced_[0]] = created_[0]
407 replacemap[replaced_[0]] = created_[0]
408 elif clen > rlen:
408 elif clen > rlen:
409 assert rlen == 1, ('unexpected replacement of '
409 assert rlen == 1, ('unexpected replacement of '
410 '%d changes with %d changes' % (rlen, clen))
410 '%d changes with %d changes' % (rlen, clen))
411 # made more changesets than we're replacing
411 # made more changesets than we're replacing
412 # TODO synthesize patch names for created patches
412 # TODO synthesize patch names for created patches
413 replacemap[replaced_[0]] = created_[-1]
413 replacemap[replaced_[0]] = created_[-1]
414 ui.debug('histedit: created many, assuming %s replaced by %s' %
414 ui.debug('histedit: created many, assuming %s replaced by %s' %
415 (hexshort(replaced_[0]), hexshort(created_[-1])))
415 (hexshort(replaced_[0]), hexshort(created_[-1])))
416 elif rlen > clen:
416 elif rlen > clen:
417 if not created_:
417 if not created_:
418 # This must be a drop. Try and put our metadata on
418 # This must be a drop. Try and put our metadata on
419 # the parent change.
419 # the parent change.
420 assert rlen == 1
420 assert rlen == 1
421 r = replaced_[0]
421 r = replaced_[0]
422 ui.debug('histedit: %s seems replaced with nothing, '
422 ui.debug('histedit: %s seems replaced with nothing, '
423 'finding a parent\n' % (hexshort(r)))
423 'finding a parent\n' % (hexshort(r)))
424 pctx = repo[r].parents()[0]
424 pctx = repo[r].parents()[0]
425 if pctx.node() in replacemap:
425 if pctx.node() in replacemap:
426 ui.debug('histedit: parent is already replaced\n')
426 ui.debug('histedit: parent is already replaced\n')
427 replacemap[r] = replacemap[pctx.node()]
427 replacemap[r] = replacemap[pctx.node()]
428 else:
428 else:
429 replacemap[r] = pctx.node()
429 replacemap[r] = pctx.node()
430 ui.debug('histedit: %s best replaced by %s\n' % (
430 ui.debug('histedit: %s best replaced by %s\n' % (
431 hexshort(r), hexshort(replacemap[r])))
431 hexshort(r), hexshort(replacemap[r])))
432 else:
432 else:
433 assert len(created_) == 1
433 assert len(created_) == 1
434 for r in replaced_:
434 for r in replaced_:
435 ui.debug('histedit: %s replaced by %s\n' % (
435 ui.debug('histedit: %s replaced by %s\n' % (
436 hexshort(r), hexshort(created_[0])))
436 hexshort(r), hexshort(created_[0])))
437 replacemap[r] = created_[0]
437 replacemap[r] = created_[0]
438 else:
438 else:
439 assert False, (
439 assert False, (
440 'Unhandled case in replacement mapping! '
440 'Unhandled case in replacement mapping! '
441 'replacing %d changes with %d changes' % (rlen, clen))
441 'replacing %d changes with %d changes' % (rlen, clen))
442 created.extend(created_)
442 created.extend(created_)
443 replaced.extend(replaced_)
443 replaced.extend(replaced_)
444 tmpnodes.extend(tmpnodes_)
444 tmpnodes.extend(tmpnodes_)
445
445
446 hg.update(repo, parentctx.node())
446 hg.update(repo, parentctx.node())
447
447
448 if not keep:
448 if not keep:
449 if replacemap:
449 if replacemap:
450 ui.note(_('histedit: Should update metadata for the following '
450 ui.note(_('histedit: Should update metadata for the following '
451 'changes:\n'))
451 'changes:\n'))
452
452
453 def copybms(old, new):
453 def copybms(old, new):
454 if old in tmpnodes or old in created:
454 if old in tmpnodes or old in created:
455 # can't have any metadata we'd want to update
455 # can't have any metadata we'd want to update
456 return
456 return
457 while new in replacemap:
457 while new in replacemap:
458 new = replacemap[new]
458 new = replacemap[new]
459 ui.note(_('histedit: %s to %s\n') % (hexshort(old),
459 ui.note(_('histedit: %s to %s\n') % (hexshort(old),
460 hexshort(new)))
460 hexshort(new)))
461 octx = repo[old]
461 octx = repo[old]
462 marks = octx.bookmarks()
462 marks = octx.bookmarks()
463 if marks:
463 if marks:
464 ui.note(_('histedit: moving bookmarks %s\n') %
464 ui.note(_('histedit: moving bookmarks %s\n') %
465 ', '.join(marks))
465 ', '.join(marks))
466 for mark in marks:
466 for mark in marks:
467 repo._bookmarks[mark] = new
467 repo._bookmarks[mark] = new
468 bookmarks.write(repo)
468 bookmarks.write(repo)
469
469
470 # We assume that bookmarks on the tip should remain
470 # We assume that bookmarks on the tip should remain
471 # tipmost, but bookmarks on non-tip changesets should go
471 # tipmost, but bookmarks on non-tip changesets should go
472 # to their most reasonable successor. As a result, find
472 # to their most reasonable successor. As a result, find
473 # the old tip and new tip and copy those bookmarks first,
473 # the old tip and new tip and copy those bookmarks first,
474 # then do the rest of the bookmark copies.
474 # then do the rest of the bookmark copies.
475 oldtip = sorted(replacemap.keys(), key=repo.changelog.rev)[-1]
475 oldtip = sorted(replacemap.keys(), key=repo.changelog.rev)[-1]
476 newtip = sorted(replacemap.values(), key=repo.changelog.rev)[-1]
476 newtip = sorted(replacemap.values(), key=repo.changelog.rev)[-1]
477 copybms(oldtip, newtip)
477 copybms(oldtip, newtip)
478
478
479 for old, new in replacemap.iteritems():
479 for old, new in sorted(replacemap.iteritems()):
480 copybms(old, new)
480 copybms(old, new)
481 # TODO update mq state
481 # TODO update mq state
482
482
483 ui.debug('should strip replaced nodes %s\n' %
483 ui.debug('should strip replaced nodes %s\n' %
484 ', '.join([node.hex(n)[:12] for n in replaced]))
484 ', '.join([node.hex(n)[:12] for n in replaced]))
485 for n in sorted(replaced, key=lambda x: repo[x].rev()):
485 for n in sorted(replaced, key=lambda x: repo[x].rev()):
486 try:
486 try:
487 repair.strip(ui, repo, n)
487 repair.strip(ui, repo, n)
488 except error.LookupError:
488 except error.LookupError:
489 pass
489 pass
490
490
491 ui.debug('should strip temp nodes %s\n' %
491 ui.debug('should strip temp nodes %s\n' %
492 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
492 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
493 for n in reversed(tmpnodes):
493 for n in reversed(tmpnodes):
494 try:
494 try:
495 repair.strip(ui, repo, n)
495 repair.strip(ui, repo, n)
496 except error.LookupError:
496 except error.LookupError:
497 pass
497 pass
498 os.unlink(os.path.join(repo.path, 'histedit-state'))
498 os.unlink(os.path.join(repo.path, 'histedit-state'))
499 if os.path.exists(repo.sjoin('undo')):
499 if os.path.exists(repo.sjoin('undo')):
500 os.unlink(repo.sjoin('undo'))
500 os.unlink(repo.sjoin('undo'))
501
501
502
502
503 def writestate(repo, parentctxnode, created, replaced,
503 def writestate(repo, parentctxnode, created, replaced,
504 tmpnodes, existing, rules, keep, oldtip, replacemap):
504 tmpnodes, existing, rules, keep, oldtip, replacemap):
505 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
505 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
506 pickle.dump((parentctxnode, created, replaced,
506 pickle.dump((parentctxnode, created, replaced,
507 tmpnodes, existing, rules, keep, oldtip, replacemap),
507 tmpnodes, existing, rules, keep, oldtip, replacemap),
508 fp)
508 fp)
509 fp.close()
509 fp.close()
510
510
511 def readstate(repo):
511 def readstate(repo):
512 """Returns a tuple of (parentnode, created, replaced, tmp, existing, rules,
512 """Returns a tuple of (parentnode, created, replaced, tmp, existing, rules,
513 keep, oldtip, replacemap ).
513 keep, oldtip, replacemap ).
514 """
514 """
515 fp = open(os.path.join(repo.path, 'histedit-state'))
515 fp = open(os.path.join(repo.path, 'histedit-state'))
516 return pickle.load(fp)
516 return pickle.load(fp)
517
517
518
518
519 def verifyrules(rules, repo, ctxs):
519 def verifyrules(rules, repo, ctxs):
520 """Verify that there exists exactly one edit rule per given changeset.
520 """Verify that there exists exactly one edit rule per given changeset.
521
521
522 Will abort if there are to many or too few rules, a malformed rule,
522 Will abort if there are to many or too few rules, a malformed rule,
523 or a rule on a changeset outside of the user-given range.
523 or a rule on a changeset outside of the user-given range.
524 """
524 """
525 parsed = []
525 parsed = []
526 if len(rules) != len(ctxs):
526 if len(rules) != len(ctxs):
527 raise util.Abort(_('must specify a rule for each changeset once'))
527 raise util.Abort(_('must specify a rule for each changeset once'))
528 for r in rules:
528 for r in rules:
529 if ' ' not in r:
529 if ' ' not in r:
530 raise util.Abort(_('malformed line "%s"') % r)
530 raise util.Abort(_('malformed line "%s"') % r)
531 action, rest = r.split(' ', 1)
531 action, rest = r.split(' ', 1)
532 if ' ' in rest.strip():
532 if ' ' in rest.strip():
533 ha, rest = rest.split(' ', 1)
533 ha, rest = rest.split(' ', 1)
534 else:
534 else:
535 ha = r.strip()
535 ha = r.strip()
536 try:
536 try:
537 if repo[ha] not in ctxs:
537 if repo[ha] not in ctxs:
538 raise util.Abort(
538 raise util.Abort(
539 _('may not use changesets other than the ones listed'))
539 _('may not use changesets other than the ones listed'))
540 except error.RepoError:
540 except error.RepoError:
541 raise util.Abort(_('unknown changeset %s listed') % ha)
541 raise util.Abort(_('unknown changeset %s listed') % ha)
542 if action not in actiontable:
542 if action not in actiontable:
543 raise util.Abort(_('unknown action "%s"') % action)
543 raise util.Abort(_('unknown action "%s"') % action)
544 parsed.append([action, ha])
544 parsed.append([action, ha])
545 return parsed
545 return parsed
546
546
547
547
548 cmdtable = {
548 cmdtable = {
549 "histedit":
549 "histedit":
550 (histedit,
550 (histedit,
551 [('', 'commands', '', _(
551 [('', 'commands', '', _(
552 'Read history edits from the specified file.')),
552 'Read history edits from the specified file.')),
553 ('c', 'continue', False, _('continue an edit already in progress')),
553 ('c', 'continue', False, _('continue an edit already in progress')),
554 ('k', 'keep', False, _(
554 ('k', 'keep', False, _(
555 "don't strip old nodes after edit is complete")),
555 "don't strip old nodes after edit is complete")),
556 ('', 'abort', False, _('abort an edit in progress')),
556 ('', 'abort', False, _('abort an edit in progress')),
557 ('o', 'outgoing', False, _('changesets not found in destination')),
557 ('o', 'outgoing', False, _('changesets not found in destination')),
558 ('f', 'force', False, _(
558 ('f', 'force', False, _(
559 'force outgoing even for unrelated repositories')),
559 'force outgoing even for unrelated repositories')),
560 ('r', 'rev', [], _('first revision to be edited')),
560 ('r', 'rev', [], _('first revision to be edited')),
561 ],
561 ],
562 __doc__,
562 __doc__,
563 ),
563 ),
564 }
564 }
@@ -1,187 +1,187 b''
1 $ . "$TESTDIR/histedit-helpers.sh"
1 $ . "$TESTDIR/histedit-helpers.sh"
2
2
3 $ cat >> $HGRCPATH <<EOF
3 $ cat >> $HGRCPATH <<EOF
4 > [extensions]
4 > [extensions]
5 > graphlog=
5 > graphlog=
6 > histedit=
6 > histedit=
7 > EOF
7 > EOF
8
8
9 $ hg init r
9 $ hg init r
10 $ cd r
10 $ cd r
11
11
12 $ for x in a b c d e f ; do
12 $ for x in a b c d e f ; do
13 > echo $x > $x
13 > echo $x > $x
14 > hg add $x
14 > hg add $x
15 > hg ci -m $x
15 > hg ci -m $x
16 > done
16 > done
17
17
18 $ hg book -r 1 will-move-backwards
18 $ hg book -r 1 will-move-backwards
19 $ hg book -r 2 two
19 $ hg book -r 2 two
20 $ hg book -r 2 also-two
20 $ hg book -r 2 also-two
21 $ hg book -r 3 three
21 $ hg book -r 3 three
22 $ hg book -r 4 four
22 $ hg book -r 4 four
23 $ hg book -r tip five
23 $ hg book -r tip five
24 $ hg log --graph
24 $ hg log --graph
25 @ changeset: 5:652413bf663e
25 @ changeset: 5:652413bf663e
26 | bookmark: five
26 | bookmark: five
27 | tag: tip
27 | tag: tip
28 | user: test
28 | user: test
29 | date: Thu Jan 01 00:00:00 1970 +0000
29 | date: Thu Jan 01 00:00:00 1970 +0000
30 | summary: f
30 | summary: f
31 |
31 |
32 o changeset: 4:e860deea161a
32 o changeset: 4:e860deea161a
33 | bookmark: four
33 | bookmark: four
34 | user: test
34 | user: test
35 | date: Thu Jan 01 00:00:00 1970 +0000
35 | date: Thu Jan 01 00:00:00 1970 +0000
36 | summary: e
36 | summary: e
37 |
37 |
38 o changeset: 3:055a42cdd887
38 o changeset: 3:055a42cdd887
39 | bookmark: three
39 | bookmark: three
40 | user: test
40 | user: test
41 | date: Thu Jan 01 00:00:00 1970 +0000
41 | date: Thu Jan 01 00:00:00 1970 +0000
42 | summary: d
42 | summary: d
43 |
43 |
44 o changeset: 2:177f92b77385
44 o changeset: 2:177f92b77385
45 | bookmark: also-two
45 | bookmark: also-two
46 | bookmark: two
46 | bookmark: two
47 | user: test
47 | user: test
48 | date: Thu Jan 01 00:00:00 1970 +0000
48 | date: Thu Jan 01 00:00:00 1970 +0000
49 | summary: c
49 | summary: c
50 |
50 |
51 o changeset: 1:d2ae7f538514
51 o changeset: 1:d2ae7f538514
52 | bookmark: will-move-backwards
52 | bookmark: will-move-backwards
53 | user: test
53 | user: test
54 | date: Thu Jan 01 00:00:00 1970 +0000
54 | date: Thu Jan 01 00:00:00 1970 +0000
55 | summary: b
55 | summary: b
56 |
56 |
57 o changeset: 0:cb9a9f314b8b
57 o changeset: 0:cb9a9f314b8b
58 user: test
58 user: test
59 date: Thu Jan 01 00:00:00 1970 +0000
59 date: Thu Jan 01 00:00:00 1970 +0000
60 summary: a
60 summary: a
61
61
62 $ HGEDITOR=cat hg histedit 1
62 $ HGEDITOR=cat hg histedit 1
63 pick d2ae7f538514 1 b
63 pick d2ae7f538514 1 b
64 pick 177f92b77385 2 c
64 pick 177f92b77385 2 c
65 pick 055a42cdd887 3 d
65 pick 055a42cdd887 3 d
66 pick e860deea161a 4 e
66 pick e860deea161a 4 e
67 pick 652413bf663e 5 f
67 pick 652413bf663e 5 f
68
68
69 # Edit history between d2ae7f538514 and 652413bf663e
69 # Edit history between d2ae7f538514 and 652413bf663e
70 #
70 #
71 # Commands:
71 # Commands:
72 # p, pick = use commit
72 # p, pick = use commit
73 # e, edit = use commit, but stop for amending
73 # e, edit = use commit, but stop for amending
74 # f, fold = use commit, but fold into previous commit (combines N and N-1)
74 # f, fold = use commit, but fold into previous commit (combines N and N-1)
75 # d, drop = remove commit from history
75 # d, drop = remove commit from history
76 # m, mess = edit message without changing commit content
76 # m, mess = edit message without changing commit content
77 #
77 #
78 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
78 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
79 $ cat >> commands.txt <<EOF
79 $ cat >> commands.txt <<EOF
80 > pick 177f92b77385 2 c
80 > pick 177f92b77385 2 c
81 > drop d2ae7f538514 1 b
81 > drop d2ae7f538514 1 b
82 > pick 055a42cdd887 3 d
82 > pick 055a42cdd887 3 d
83 > fold e860deea161a 4 e
83 > fold e860deea161a 4 e
84 > pick 652413bf663e 5 f
84 > pick 652413bf663e 5 f
85 > EOF
85 > EOF
86 $ hg histedit 1 --commands commands.txt --verbose | grep histedit
86 $ hg histedit 1 --commands commands.txt --verbose | grep histedit
87 histedit: Should update metadata for the following changes:
87 histedit: Should update metadata for the following changes:
88 histedit: 055a42cdd887 to ae467701c500
88 histedit: 055a42cdd887 to ae467701c500
89 histedit: moving bookmarks three
89 histedit: moving bookmarks three
90 histedit: 177f92b77385 to d36c0562f908
91 histedit: moving bookmarks also-two, two
90 histedit: 652413bf663e to 0efacef7cb48
92 histedit: 652413bf663e to 0efacef7cb48
91 histedit: moving bookmarks five
93 histedit: moving bookmarks five
92 histedit: d2ae7f538514 to cb9a9f314b8b
94 histedit: d2ae7f538514 to cb9a9f314b8b
93 histedit: moving bookmarks will-move-backwards
95 histedit: moving bookmarks will-move-backwards
94 histedit: e860deea161a to ae467701c500
96 histedit: e860deea161a to ae467701c500
95 histedit: moving bookmarks four
97 histedit: moving bookmarks four
96 histedit: 177f92b77385 to d36c0562f908
97 histedit: moving bookmarks also-two, two
98 saved backup bundle to $TESTTMP/r/.hg/strip-backup/d2ae7f538514-backup.hg
98 saved backup bundle to $TESTTMP/r/.hg/strip-backup/d2ae7f538514-backup.hg
99 saved backup bundle to $TESTTMP/r/.hg/strip-backup/34a9919932c1-backup.hg
99 saved backup bundle to $TESTTMP/r/.hg/strip-backup/34a9919932c1-backup.hg
100 $ hg log --graph
100 $ hg log --graph
101 @ changeset: 3:0efacef7cb48
101 @ changeset: 3:0efacef7cb48
102 | bookmark: five
102 | bookmark: five
103 | tag: tip
103 | tag: tip
104 | user: test
104 | user: test
105 | date: Thu Jan 01 00:00:00 1970 +0000
105 | date: Thu Jan 01 00:00:00 1970 +0000
106 | summary: f
106 | summary: f
107 |
107 |
108 o changeset: 2:ae467701c500
108 o changeset: 2:ae467701c500
109 | bookmark: four
109 | bookmark: four
110 | bookmark: three
110 | bookmark: three
111 | user: test
111 | user: test
112 | date: Thu Jan 01 00:00:00 1970 +0000
112 | date: Thu Jan 01 00:00:00 1970 +0000
113 | summary: d
113 | summary: d
114 |
114 |
115 o changeset: 1:d36c0562f908
115 o changeset: 1:d36c0562f908
116 | bookmark: also-two
116 | bookmark: also-two
117 | bookmark: two
117 | bookmark: two
118 | user: test
118 | user: test
119 | date: Thu Jan 01 00:00:00 1970 +0000
119 | date: Thu Jan 01 00:00:00 1970 +0000
120 | summary: c
120 | summary: c
121 |
121 |
122 o changeset: 0:cb9a9f314b8b
122 o changeset: 0:cb9a9f314b8b
123 bookmark: will-move-backwards
123 bookmark: will-move-backwards
124 user: test
124 user: test
125 date: Thu Jan 01 00:00:00 1970 +0000
125 date: Thu Jan 01 00:00:00 1970 +0000
126 summary: a
126 summary: a
127
127
128 $ HGEDITOR=cat hg histedit 1
128 $ HGEDITOR=cat hg histedit 1
129 pick d36c0562f908 1 c
129 pick d36c0562f908 1 c
130 pick ae467701c500 2 d
130 pick ae467701c500 2 d
131 pick 0efacef7cb48 3 f
131 pick 0efacef7cb48 3 f
132
132
133 # Edit history between d36c0562f908 and 0efacef7cb48
133 # Edit history between d36c0562f908 and 0efacef7cb48
134 #
134 #
135 # Commands:
135 # Commands:
136 # p, pick = use commit
136 # p, pick = use commit
137 # e, edit = use commit, but stop for amending
137 # e, edit = use commit, but stop for amending
138 # f, fold = use commit, but fold into previous commit (combines N and N-1)
138 # f, fold = use commit, but fold into previous commit (combines N and N-1)
139 # d, drop = remove commit from history
139 # d, drop = remove commit from history
140 # m, mess = edit message without changing commit content
140 # m, mess = edit message without changing commit content
141 #
141 #
142 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
142 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
143 $ cat > commands.txt << EOF
143 $ cat > commands.txt << EOF
144 > pick d36c0562f908 1 c
144 > pick d36c0562f908 1 c
145 > pick 0efacef7cb48 3 f
145 > pick 0efacef7cb48 3 f
146 > pick ae467701c500 2 d
146 > pick ae467701c500 2 d
147 > EOF
147 > EOF
148 $ hg histedit 1 --commands commands.txt --verbose | grep histedit
148 $ hg histedit 1 --commands commands.txt --verbose | grep histedit
149 histedit: Should update metadata for the following changes:
149 histedit: Should update metadata for the following changes:
150 histedit: 0efacef7cb48 to 1be9c35b4cb2
150 histedit: 0efacef7cb48 to 1be9c35b4cb2
151 histedit: moving bookmarks five
151 histedit: moving bookmarks five
152 histedit: 0efacef7cb48 to 7c044e3e33a9
152 histedit: ae467701c500 to 1be9c35b4cb2
153 histedit: ae467701c500 to 1be9c35b4cb2
153 histedit: moving bookmarks four, three
154 histedit: moving bookmarks four, three
154 histedit: 0efacef7cb48 to 7c044e3e33a9
155 saved backup bundle to $TESTTMP/r/.hg/strip-backup/ae467701c500-backup.hg
155 saved backup bundle to $TESTTMP/r/.hg/strip-backup/ae467701c500-backup.hg
156
156
157 We expect 'five' to stay at tip, since the tipmost bookmark is most
157 We expect 'five' to stay at tip, since the tipmost bookmark is most
158 likely the useful signal.
158 likely the useful signal.
159
159
160 $ hg log --graph
160 $ hg log --graph
161 @ changeset: 3:1be9c35b4cb2
161 @ changeset: 3:1be9c35b4cb2
162 | bookmark: five
162 | bookmark: five
163 | bookmark: four
163 | bookmark: four
164 | bookmark: three
164 | bookmark: three
165 | tag: tip
165 | tag: tip
166 | user: test
166 | user: test
167 | date: Thu Jan 01 00:00:00 1970 +0000
167 | date: Thu Jan 01 00:00:00 1970 +0000
168 | summary: d
168 | summary: d
169 |
169 |
170 o changeset: 2:7c044e3e33a9
170 o changeset: 2:7c044e3e33a9
171 | user: test
171 | user: test
172 | date: Thu Jan 01 00:00:00 1970 +0000
172 | date: Thu Jan 01 00:00:00 1970 +0000
173 | summary: f
173 | summary: f
174 |
174 |
175 o changeset: 1:d36c0562f908
175 o changeset: 1:d36c0562f908
176 | bookmark: also-two
176 | bookmark: also-two
177 | bookmark: two
177 | bookmark: two
178 | user: test
178 | user: test
179 | date: Thu Jan 01 00:00:00 1970 +0000
179 | date: Thu Jan 01 00:00:00 1970 +0000
180 | summary: c
180 | summary: c
181 |
181 |
182 o changeset: 0:cb9a9f314b8b
182 o changeset: 0:cb9a9f314b8b
183 bookmark: will-move-backwards
183 bookmark: will-move-backwards
184 user: test
184 user: test
185 date: Thu Jan 01 00:00:00 1970 +0000
185 date: Thu Jan 01 00:00:00 1970 +0000
186 summary: a
186 summary: a
187
187
General Comments 0
You need to be logged in to leave comments. Login now