##// END OF EJS Templates
rebase: don't preserve most extra fields...
Siddharth Agarwal -
r27977:b698abf9 3.7.1 stable
parent child Browse files
Show More
@@ -1,1275 +1,1272
1 # rebase.py - rebasing feature for mercurial
1 # rebase.py - rebasing feature for mercurial
2 #
2 #
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot 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
7
8 '''command to move sets of revisions to a different ancestor
8 '''command to move sets of revisions to a different ancestor
9
9
10 This extension lets you rebase changesets in an existing Mercurial
10 This extension lets you rebase changesets in an existing Mercurial
11 repository.
11 repository.
12
12
13 For more information:
13 For more information:
14 https://mercurial-scm.org/wiki/RebaseExtension
14 https://mercurial-scm.org/wiki/RebaseExtension
15 '''
15 '''
16
16
17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
18 from mercurial import extensions, patch, scmutil, phases, obsolete, error
18 from mercurial import extensions, patch, scmutil, phases, obsolete, error
19 from mercurial import copies, repoview, revset
19 from mercurial import copies, repoview, revset
20 from mercurial.commands import templateopts
20 from mercurial.commands import templateopts
21 from mercurial.node import nullrev, nullid, hex, short
21 from mercurial.node import nullrev, nullid, hex, short
22 from mercurial.lock import release
22 from mercurial.lock import release
23 from mercurial.i18n import _
23 from mercurial.i18n import _
24 import os, errno
24 import os, errno
25
25
26 # The following constants are used throughout the rebase module. The ordering of
26 # The following constants are used throughout the rebase module. The ordering of
27 # their values must be maintained.
27 # their values must be maintained.
28
28
29 # Indicates that a revision needs to be rebased
29 # Indicates that a revision needs to be rebased
30 revtodo = -1
30 revtodo = -1
31 nullmerge = -2
31 nullmerge = -2
32 revignored = -3
32 revignored = -3
33 # successor in rebase destination
33 # successor in rebase destination
34 revprecursor = -4
34 revprecursor = -4
35 # plain prune (no successor)
35 # plain prune (no successor)
36 revpruned = -5
36 revpruned = -5
37 revskipped = (revignored, revprecursor, revpruned)
37 revskipped = (revignored, revprecursor, revpruned)
38
38
39 cmdtable = {}
39 cmdtable = {}
40 command = cmdutil.command(cmdtable)
40 command = cmdutil.command(cmdtable)
41 # Note for extension authors: ONLY specify testedwith = 'internal' for
41 # Note for extension authors: ONLY specify testedwith = 'internal' for
42 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
42 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
43 # be specifying the version(s) of Mercurial they are tested with, or
43 # be specifying the version(s) of Mercurial they are tested with, or
44 # leave the attribute unspecified.
44 # leave the attribute unspecified.
45 testedwith = 'internal'
45 testedwith = 'internal'
46
46
47 def _nothingtorebase():
47 def _nothingtorebase():
48 return 1
48 return 1
49
49
50 def _savegraft(ctx, extra):
50 def _savegraft(ctx, extra):
51 s = ctx.extra().get('source', None)
51 s = ctx.extra().get('source', None)
52 if s is not None:
52 if s is not None:
53 extra['source'] = s
53 extra['source'] = s
54 s = ctx.extra().get('intermediate-source', None)
54 s = ctx.extra().get('intermediate-source', None)
55 if s is not None:
55 if s is not None:
56 extra['intermediate-source'] = s
56 extra['intermediate-source'] = s
57
57
58 def _savebranch(ctx, extra):
58 def _savebranch(ctx, extra):
59 extra['branch'] = ctx.branch()
59 extra['branch'] = ctx.branch()
60
60
61 def _makeextrafn(copiers):
61 def _makeextrafn(copiers):
62 """make an extrafn out of the given copy-functions.
62 """make an extrafn out of the given copy-functions.
63
63
64 A copy function takes a context and an extra dict, and mutates the
64 A copy function takes a context and an extra dict, and mutates the
65 extra dict as needed based on the given context.
65 extra dict as needed based on the given context.
66 """
66 """
67 def extrafn(ctx, extra):
67 def extrafn(ctx, extra):
68 for c in copiers:
68 for c in copiers:
69 c(ctx, extra)
69 c(ctx, extra)
70 return extrafn
70 return extrafn
71
71
72 def _destrebase(repo):
72 def _destrebase(repo):
73 # Destination defaults to the latest revision in the
73 # Destination defaults to the latest revision in the
74 # current branch
74 # current branch
75 branch = repo[None].branch()
75 branch = repo[None].branch()
76 return repo[branch].rev()
76 return repo[branch].rev()
77
77
78 revsetpredicate = revset.extpredicate()
78 revsetpredicate = revset.extpredicate()
79
79
80 @revsetpredicate('_destrebase')
80 @revsetpredicate('_destrebase')
81 def _revsetdestrebase(repo, subset, x):
81 def _revsetdestrebase(repo, subset, x):
82 # ``_rebasedefaultdest()``
82 # ``_rebasedefaultdest()``
83
83
84 # default destination for rebase.
84 # default destination for rebase.
85 # # XXX: Currently private because I expect the signature to change.
85 # # XXX: Currently private because I expect the signature to change.
86 # # XXX: - taking rev as arguments,
86 # # XXX: - taking rev as arguments,
87 # # XXX: - bailing out in case of ambiguity vs returning all data.
87 # # XXX: - bailing out in case of ambiguity vs returning all data.
88 # # XXX: - probably merging with the merge destination.
88 # # XXX: - probably merging with the merge destination.
89 # i18n: "_rebasedefaultdest" is a keyword
89 # i18n: "_rebasedefaultdest" is a keyword
90 revset.getargs(x, 0, 0, _("_rebasedefaultdest takes no arguments"))
90 revset.getargs(x, 0, 0, _("_rebasedefaultdest takes no arguments"))
91 return subset & revset.baseset([_destrebase(repo)])
91 return subset & revset.baseset([_destrebase(repo)])
92
92
93 @command('rebase',
93 @command('rebase',
94 [('s', 'source', '',
94 [('s', 'source', '',
95 _('rebase the specified changeset and descendants'), _('REV')),
95 _('rebase the specified changeset and descendants'), _('REV')),
96 ('b', 'base', '',
96 ('b', 'base', '',
97 _('rebase everything from branching point of specified changeset'),
97 _('rebase everything from branching point of specified changeset'),
98 _('REV')),
98 _('REV')),
99 ('r', 'rev', [],
99 ('r', 'rev', [],
100 _('rebase these revisions'),
100 _('rebase these revisions'),
101 _('REV')),
101 _('REV')),
102 ('d', 'dest', '',
102 ('d', 'dest', '',
103 _('rebase onto the specified changeset'), _('REV')),
103 _('rebase onto the specified changeset'), _('REV')),
104 ('', 'collapse', False, _('collapse the rebased changesets')),
104 ('', 'collapse', False, _('collapse the rebased changesets')),
105 ('m', 'message', '',
105 ('m', 'message', '',
106 _('use text as collapse commit message'), _('TEXT')),
106 _('use text as collapse commit message'), _('TEXT')),
107 ('e', 'edit', False, _('invoke editor on commit messages')),
107 ('e', 'edit', False, _('invoke editor on commit messages')),
108 ('l', 'logfile', '',
108 ('l', 'logfile', '',
109 _('read collapse commit message from file'), _('FILE')),
109 _('read collapse commit message from file'), _('FILE')),
110 ('k', 'keep', False, _('keep original changesets')),
110 ('k', 'keep', False, _('keep original changesets')),
111 ('', 'keepbranches', False, _('keep original branch names')),
111 ('', 'keepbranches', False, _('keep original branch names')),
112 ('D', 'detach', False, _('(DEPRECATED)')),
112 ('D', 'detach', False, _('(DEPRECATED)')),
113 ('i', 'interactive', False, _('(DEPRECATED)')),
113 ('i', 'interactive', False, _('(DEPRECATED)')),
114 ('t', 'tool', '', _('specify merge tool')),
114 ('t', 'tool', '', _('specify merge tool')),
115 ('c', 'continue', False, _('continue an interrupted rebase')),
115 ('c', 'continue', False, _('continue an interrupted rebase')),
116 ('a', 'abort', False, _('abort an interrupted rebase'))] +
116 ('a', 'abort', False, _('abort an interrupted rebase'))] +
117 templateopts,
117 templateopts,
118 _('[-s REV | -b REV] [-d REV] [OPTION]'))
118 _('[-s REV | -b REV] [-d REV] [OPTION]'))
119 def rebase(ui, repo, **opts):
119 def rebase(ui, repo, **opts):
120 """move changeset (and descendants) to a different branch
120 """move changeset (and descendants) to a different branch
121
121
122 Rebase uses repeated merging to graft changesets from one part of
122 Rebase uses repeated merging to graft changesets from one part of
123 history (the source) onto another (the destination). This can be
123 history (the source) onto another (the destination). This can be
124 useful for linearizing *local* changes relative to a master
124 useful for linearizing *local* changes relative to a master
125 development tree.
125 development tree.
126
126
127 Published commits cannot be rebased (see :hg:`help phases`).
127 Published commits cannot be rebased (see :hg:`help phases`).
128 To copy commits, see :hg:`help graft`.
128 To copy commits, see :hg:`help graft`.
129
129
130 If you don't specify a destination changeset (``-d/--dest``),
130 If you don't specify a destination changeset (``-d/--dest``),
131 rebase uses the current branch tip as the destination. (The
131 rebase uses the current branch tip as the destination. (The
132 destination changeset is not modified by rebasing, but new
132 destination changeset is not modified by rebasing, but new
133 changesets are added as its descendants.)
133 changesets are added as its descendants.)
134
134
135 Here are the ways to select changesets:
135 Here are the ways to select changesets:
136
136
137 1. Explicitly select them using ``--rev``.
137 1. Explicitly select them using ``--rev``.
138
138
139 2. Use ``--source`` to select a root changeset and include all of its
139 2. Use ``--source`` to select a root changeset and include all of its
140 descendants.
140 descendants.
141
141
142 3. Use ``--base`` to select a changeset; rebase will find ancestors
142 3. Use ``--base`` to select a changeset; rebase will find ancestors
143 and their descendants which are not also ancestors of the destination.
143 and their descendants which are not also ancestors of the destination.
144
144
145 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
145 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
146 rebase will use ``--base .`` as above.
146 rebase will use ``--base .`` as above.
147
147
148 Rebase will destroy original changesets unless you use ``--keep``.
148 Rebase will destroy original changesets unless you use ``--keep``.
149 It will also move your bookmarks (even if you do).
149 It will also move your bookmarks (even if you do).
150
150
151 Some changesets may be dropped if they do not contribute changes
151 Some changesets may be dropped if they do not contribute changes
152 (e.g. merges from the destination branch).
152 (e.g. merges from the destination branch).
153
153
154 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
154 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
155 a named branch with two heads. You will need to explicitly specify source
155 a named branch with two heads. You will need to explicitly specify source
156 and/or destination.
156 and/or destination.
157
157
158 If a rebase is interrupted to manually resolve a conflict, it can be
158 If a rebase is interrupted to manually resolve a conflict, it can be
159 continued with --continue/-c or aborted with --abort/-a.
159 continued with --continue/-c or aborted with --abort/-a.
160
160
161 .. container:: verbose
161 .. container:: verbose
162
162
163 Examples:
163 Examples:
164
164
165 - move "local changes" (current commit back to branching point)
165 - move "local changes" (current commit back to branching point)
166 to the current branch tip after a pull::
166 to the current branch tip after a pull::
167
167
168 hg rebase
168 hg rebase
169
169
170 - move a single changeset to the stable branch::
170 - move a single changeset to the stable branch::
171
171
172 hg rebase -r 5f493448 -d stable
172 hg rebase -r 5f493448 -d stable
173
173
174 - splice a commit and all its descendants onto another part of history::
174 - splice a commit and all its descendants onto another part of history::
175
175
176 hg rebase --source c0c3 --dest 4cf9
176 hg rebase --source c0c3 --dest 4cf9
177
177
178 - rebase everything on a branch marked by a bookmark onto the
178 - rebase everything on a branch marked by a bookmark onto the
179 default branch::
179 default branch::
180
180
181 hg rebase --base myfeature --dest default
181 hg rebase --base myfeature --dest default
182
182
183 - collapse a sequence of changes into a single commit::
183 - collapse a sequence of changes into a single commit::
184
184
185 hg rebase --collapse -r 1520:1525 -d .
185 hg rebase --collapse -r 1520:1525 -d .
186
186
187 - move a named branch while preserving its name::
187 - move a named branch while preserving its name::
188
188
189 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
189 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
190
190
191 Returns 0 on success, 1 if nothing to rebase or there are
191 Returns 0 on success, 1 if nothing to rebase or there are
192 unresolved conflicts.
192 unresolved conflicts.
193
193
194 """
194 """
195 originalwd = target = None
195 originalwd = target = None
196 activebookmark = None
196 activebookmark = None
197 external = nullrev
197 external = nullrev
198 # Mapping between the old revision id and either what is the new rebased
198 # Mapping between the old revision id and either what is the new rebased
199 # revision or what needs to be done with the old revision. The state dict
199 # revision or what needs to be done with the old revision. The state dict
200 # will be what contains most of the rebase progress state.
200 # will be what contains most of the rebase progress state.
201 state = {}
201 state = {}
202 skipped = set()
202 skipped = set()
203 targetancestors = set()
203 targetancestors = set()
204
204
205
205
206 lock = wlock = None
206 lock = wlock = None
207 try:
207 try:
208 wlock = repo.wlock()
208 wlock = repo.wlock()
209 lock = repo.lock()
209 lock = repo.lock()
210
210
211 # Validate input and define rebasing points
211 # Validate input and define rebasing points
212 destf = opts.get('dest', None)
212 destf = opts.get('dest', None)
213 srcf = opts.get('source', None)
213 srcf = opts.get('source', None)
214 basef = opts.get('base', None)
214 basef = opts.get('base', None)
215 revf = opts.get('rev', [])
215 revf = opts.get('rev', [])
216 contf = opts.get('continue')
216 contf = opts.get('continue')
217 abortf = opts.get('abort')
217 abortf = opts.get('abort')
218 collapsef = opts.get('collapse', False)
218 collapsef = opts.get('collapse', False)
219 collapsemsg = cmdutil.logmessage(ui, opts)
219 collapsemsg = cmdutil.logmessage(ui, opts)
220 date = opts.get('date', None)
220 date = opts.get('date', None)
221 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
221 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
222 extrafns = [_savegraft]
222 extrafns = [_savegraft]
223 if e:
223 if e:
224 extrafns = [e]
224 extrafns = [e]
225 keepf = opts.get('keep', False)
225 keepf = opts.get('keep', False)
226 keepbranchesf = opts.get('keepbranches', False)
226 keepbranchesf = opts.get('keepbranches', False)
227 # keepopen is not meant for use on the command line, but by
227 # keepopen is not meant for use on the command line, but by
228 # other extensions
228 # other extensions
229 keepopen = opts.get('keepopen', False)
229 keepopen = opts.get('keepopen', False)
230
230
231 if opts.get('interactive'):
231 if opts.get('interactive'):
232 try:
232 try:
233 if extensions.find('histedit'):
233 if extensions.find('histedit'):
234 enablehistedit = ''
234 enablehistedit = ''
235 except KeyError:
235 except KeyError:
236 enablehistedit = " --config extensions.histedit="
236 enablehistedit = " --config extensions.histedit="
237 help = "hg%s help -e histedit" % enablehistedit
237 help = "hg%s help -e histedit" % enablehistedit
238 msg = _("interactive history editing is supported by the "
238 msg = _("interactive history editing is supported by the "
239 "'histedit' extension (see \"%s\")") % help
239 "'histedit' extension (see \"%s\")") % help
240 raise error.Abort(msg)
240 raise error.Abort(msg)
241
241
242 if collapsemsg and not collapsef:
242 if collapsemsg and not collapsef:
243 raise error.Abort(
243 raise error.Abort(
244 _('message can only be specified with collapse'))
244 _('message can only be specified with collapse'))
245
245
246 if contf or abortf:
246 if contf or abortf:
247 if contf and abortf:
247 if contf and abortf:
248 raise error.Abort(_('cannot use both abort and continue'))
248 raise error.Abort(_('cannot use both abort and continue'))
249 if collapsef:
249 if collapsef:
250 raise error.Abort(
250 raise error.Abort(
251 _('cannot use collapse with continue or abort'))
251 _('cannot use collapse with continue or abort'))
252 if srcf or basef or destf:
252 if srcf or basef or destf:
253 raise error.Abort(
253 raise error.Abort(
254 _('abort and continue do not allow specifying revisions'))
254 _('abort and continue do not allow specifying revisions'))
255 if abortf and opts.get('tool', False):
255 if abortf and opts.get('tool', False):
256 ui.warn(_('tool option will be ignored\n'))
256 ui.warn(_('tool option will be ignored\n'))
257
257
258 try:
258 try:
259 (originalwd, target, state, skipped, collapsef, keepf,
259 (originalwd, target, state, skipped, collapsef, keepf,
260 keepbranchesf, external, activebookmark) = restorestatus(repo)
260 keepbranchesf, external, activebookmark) = restorestatus(repo)
261 except error.RepoLookupError:
261 except error.RepoLookupError:
262 if abortf:
262 if abortf:
263 clearstatus(repo)
263 clearstatus(repo)
264 repo.ui.warn(_('rebase aborted (no revision is removed,'
264 repo.ui.warn(_('rebase aborted (no revision is removed,'
265 ' only broken state is cleared)\n'))
265 ' only broken state is cleared)\n'))
266 return 0
266 return 0
267 else:
267 else:
268 msg = _('cannot continue inconsistent rebase')
268 msg = _('cannot continue inconsistent rebase')
269 hint = _('use "hg rebase --abort" to clear broken state')
269 hint = _('use "hg rebase --abort" to clear broken state')
270 raise error.Abort(msg, hint=hint)
270 raise error.Abort(msg, hint=hint)
271 if abortf:
271 if abortf:
272 return abort(repo, originalwd, target, state,
272 return abort(repo, originalwd, target, state,
273 activebookmark=activebookmark)
273 activebookmark=activebookmark)
274 else:
274 else:
275 if srcf and basef:
275 if srcf and basef:
276 raise error.Abort(_('cannot specify both a '
276 raise error.Abort(_('cannot specify both a '
277 'source and a base'))
277 'source and a base'))
278 if revf and basef:
278 if revf and basef:
279 raise error.Abort(_('cannot specify both a '
279 raise error.Abort(_('cannot specify both a '
280 'revision and a base'))
280 'revision and a base'))
281 if revf and srcf:
281 if revf and srcf:
282 raise error.Abort(_('cannot specify both a '
282 raise error.Abort(_('cannot specify both a '
283 'revision and a source'))
283 'revision and a source'))
284
284
285 cmdutil.checkunfinished(repo)
285 cmdutil.checkunfinished(repo)
286 cmdutil.bailifchanged(repo)
286 cmdutil.bailifchanged(repo)
287
287
288 if destf:
288 if destf:
289 dest = scmutil.revsingle(repo, destf)
289 dest = scmutil.revsingle(repo, destf)
290 else:
290 else:
291 dest = repo[_destrebase(repo)]
291 dest = repo[_destrebase(repo)]
292 destf = str(dest)
292 destf = str(dest)
293
293
294 if revf:
294 if revf:
295 rebaseset = scmutil.revrange(repo, revf)
295 rebaseset = scmutil.revrange(repo, revf)
296 if not rebaseset:
296 if not rebaseset:
297 ui.status(_('empty "rev" revision set - '
297 ui.status(_('empty "rev" revision set - '
298 'nothing to rebase\n'))
298 'nothing to rebase\n'))
299 return _nothingtorebase()
299 return _nothingtorebase()
300 elif srcf:
300 elif srcf:
301 src = scmutil.revrange(repo, [srcf])
301 src = scmutil.revrange(repo, [srcf])
302 if not src:
302 if not src:
303 ui.status(_('empty "source" revision set - '
303 ui.status(_('empty "source" revision set - '
304 'nothing to rebase\n'))
304 'nothing to rebase\n'))
305 return _nothingtorebase()
305 return _nothingtorebase()
306 rebaseset = repo.revs('(%ld)::', src)
306 rebaseset = repo.revs('(%ld)::', src)
307 assert rebaseset
307 assert rebaseset
308 else:
308 else:
309 base = scmutil.revrange(repo, [basef or '.'])
309 base = scmutil.revrange(repo, [basef or '.'])
310 if not base:
310 if not base:
311 ui.status(_('empty "base" revision set - '
311 ui.status(_('empty "base" revision set - '
312 "can't compute rebase set\n"))
312 "can't compute rebase set\n"))
313 return _nothingtorebase()
313 return _nothingtorebase()
314 commonanc = repo.revs('ancestor(%ld, %d)', base, dest).first()
314 commonanc = repo.revs('ancestor(%ld, %d)', base, dest).first()
315 if commonanc is not None:
315 if commonanc is not None:
316 rebaseset = repo.revs('(%d::(%ld) - %d)::',
316 rebaseset = repo.revs('(%d::(%ld) - %d)::',
317 commonanc, base, commonanc)
317 commonanc, base, commonanc)
318 else:
318 else:
319 rebaseset = []
319 rebaseset = []
320
320
321 if not rebaseset:
321 if not rebaseset:
322 # transform to list because smartsets are not comparable to
322 # transform to list because smartsets are not comparable to
323 # lists. This should be improved to honor laziness of
323 # lists. This should be improved to honor laziness of
324 # smartset.
324 # smartset.
325 if list(base) == [dest.rev()]:
325 if list(base) == [dest.rev()]:
326 if basef:
326 if basef:
327 ui.status(_('nothing to rebase - %s is both "base"'
327 ui.status(_('nothing to rebase - %s is both "base"'
328 ' and destination\n') % dest)
328 ' and destination\n') % dest)
329 else:
329 else:
330 ui.status(_('nothing to rebase - working directory '
330 ui.status(_('nothing to rebase - working directory '
331 'parent is also destination\n'))
331 'parent is also destination\n'))
332 elif not repo.revs('%ld - ::%d', base, dest):
332 elif not repo.revs('%ld - ::%d', base, dest):
333 if basef:
333 if basef:
334 ui.status(_('nothing to rebase - "base" %s is '
334 ui.status(_('nothing to rebase - "base" %s is '
335 'already an ancestor of destination '
335 'already an ancestor of destination '
336 '%s\n') %
336 '%s\n') %
337 ('+'.join(str(repo[r]) for r in base),
337 ('+'.join(str(repo[r]) for r in base),
338 dest))
338 dest))
339 else:
339 else:
340 ui.status(_('nothing to rebase - working '
340 ui.status(_('nothing to rebase - working '
341 'directory parent is already an '
341 'directory parent is already an '
342 'ancestor of destination %s\n') % dest)
342 'ancestor of destination %s\n') % dest)
343 else: # can it happen?
343 else: # can it happen?
344 ui.status(_('nothing to rebase from %s to %s\n') %
344 ui.status(_('nothing to rebase from %s to %s\n') %
345 ('+'.join(str(repo[r]) for r in base), dest))
345 ('+'.join(str(repo[r]) for r in base), dest))
346 return _nothingtorebase()
346 return _nothingtorebase()
347
347
348 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
348 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
349 if (not (keepf or allowunstable)
349 if (not (keepf or allowunstable)
350 and repo.revs('first(children(%ld) - %ld)',
350 and repo.revs('first(children(%ld) - %ld)',
351 rebaseset, rebaseset)):
351 rebaseset, rebaseset)):
352 raise error.Abort(
352 raise error.Abort(
353 _("can't remove original changesets with"
353 _("can't remove original changesets with"
354 " unrebased descendants"),
354 " unrebased descendants"),
355 hint=_('use --keep to keep original changesets'))
355 hint=_('use --keep to keep original changesets'))
356
356
357 obsoletenotrebased = {}
357 obsoletenotrebased = {}
358 if ui.configbool('experimental', 'rebaseskipobsolete'):
358 if ui.configbool('experimental', 'rebaseskipobsolete'):
359 rebasesetrevs = set(rebaseset)
359 rebasesetrevs = set(rebaseset)
360 rebaseobsrevs = _filterobsoleterevs(repo, rebasesetrevs)
360 rebaseobsrevs = _filterobsoleterevs(repo, rebasesetrevs)
361 obsoletenotrebased = _computeobsoletenotrebased(repo,
361 obsoletenotrebased = _computeobsoletenotrebased(repo,
362 rebaseobsrevs,
362 rebaseobsrevs,
363 dest)
363 dest)
364 rebaseobsskipped = set(obsoletenotrebased)
364 rebaseobsskipped = set(obsoletenotrebased)
365
365
366 # Obsolete node with successors not in dest leads to divergence
366 # Obsolete node with successors not in dest leads to divergence
367 divergenceok = ui.configbool('rebase',
367 divergenceok = ui.configbool('rebase',
368 'allowdivergence')
368 'allowdivergence')
369 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
369 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
370
370
371 if divergencebasecandidates and not divergenceok:
371 if divergencebasecandidates and not divergenceok:
372 msg = _("this rebase will cause divergence")
372 msg = _("this rebase will cause divergence")
373 h = _("to force the rebase please set "
373 h = _("to force the rebase please set "
374 "rebase.allowdivergence=True")
374 "rebase.allowdivergence=True")
375 raise error.Abort(msg, hint=h)
375 raise error.Abort(msg, hint=h)
376
376
377 # - plain prune (no successor) changesets are rebased
377 # - plain prune (no successor) changesets are rebased
378 # - split changesets are not rebased if at least one of the
378 # - split changesets are not rebased if at least one of the
379 # changeset resulting from the split is an ancestor of dest
379 # changeset resulting from the split is an ancestor of dest
380 rebaseset = rebasesetrevs - rebaseobsskipped
380 rebaseset = rebasesetrevs - rebaseobsskipped
381 if rebasesetrevs and not rebaseset:
381 if rebasesetrevs and not rebaseset:
382 msg = _('all requested changesets have equivalents '
382 msg = _('all requested changesets have equivalents '
383 'or were marked as obsolete')
383 'or were marked as obsolete')
384 hint = _('to force the rebase, set the config '
384 hint = _('to force the rebase, set the config '
385 'experimental.rebaseskipobsolete to False')
385 'experimental.rebaseskipobsolete to False')
386 raise error.Abort(msg, hint=hint)
386 raise error.Abort(msg, hint=hint)
387
387
388 result = buildstate(repo, dest, rebaseset, collapsef,
388 result = buildstate(repo, dest, rebaseset, collapsef,
389 obsoletenotrebased)
389 obsoletenotrebased)
390
390
391 if not result:
391 if not result:
392 # Empty state built, nothing to rebase
392 # Empty state built, nothing to rebase
393 ui.status(_('nothing to rebase\n'))
393 ui.status(_('nothing to rebase\n'))
394 return _nothingtorebase()
394 return _nothingtorebase()
395
395
396 root = min(rebaseset)
396 root = min(rebaseset)
397 if not keepf and not repo[root].mutable():
397 if not keepf and not repo[root].mutable():
398 raise error.Abort(_("can't rebase public changeset %s")
398 raise error.Abort(_("can't rebase public changeset %s")
399 % repo[root],
399 % repo[root],
400 hint=_('see "hg help phases" for details'))
400 hint=_('see "hg help phases" for details'))
401
401
402 originalwd, target, state = result
402 originalwd, target, state = result
403 if collapsef:
403 if collapsef:
404 targetancestors = repo.changelog.ancestors([target],
404 targetancestors = repo.changelog.ancestors([target],
405 inclusive=True)
405 inclusive=True)
406 external = externalparent(repo, state, targetancestors)
406 external = externalparent(repo, state, targetancestors)
407
407
408 if dest.closesbranch() and not keepbranchesf:
408 if dest.closesbranch() and not keepbranchesf:
409 ui.status(_('reopening closed branch head %s\n') % dest)
409 ui.status(_('reopening closed branch head %s\n') % dest)
410
410
411 if keepbranchesf:
411 if keepbranchesf:
412 # insert _savebranch at the start of extrafns so if
412 # insert _savebranch at the start of extrafns so if
413 # there's a user-provided extrafn it can clobber branch if
413 # there's a user-provided extrafn it can clobber branch if
414 # desired
414 # desired
415 extrafns.insert(0, _savebranch)
415 extrafns.insert(0, _savebranch)
416 if collapsef:
416 if collapsef:
417 branches = set()
417 branches = set()
418 for rev in state:
418 for rev in state:
419 branches.add(repo[rev].branch())
419 branches.add(repo[rev].branch())
420 if len(branches) > 1:
420 if len(branches) > 1:
421 raise error.Abort(_('cannot collapse multiple named '
421 raise error.Abort(_('cannot collapse multiple named '
422 'branches'))
422 'branches'))
423
423
424 # Rebase
424 # Rebase
425 if not targetancestors:
425 if not targetancestors:
426 targetancestors = repo.changelog.ancestors([target], inclusive=True)
426 targetancestors = repo.changelog.ancestors([target], inclusive=True)
427
427
428 # Keep track of the current bookmarks in order to reset them later
428 # Keep track of the current bookmarks in order to reset them later
429 currentbookmarks = repo._bookmarks.copy()
429 currentbookmarks = repo._bookmarks.copy()
430 activebookmark = activebookmark or repo._activebookmark
430 activebookmark = activebookmark or repo._activebookmark
431 if activebookmark:
431 if activebookmark:
432 bookmarks.deactivate(repo)
432 bookmarks.deactivate(repo)
433
433
434 extrafn = _makeextrafn(extrafns)
434 extrafn = _makeextrafn(extrafns)
435
435
436 sortedstate = sorted(state)
436 sortedstate = sorted(state)
437 total = len(sortedstate)
437 total = len(sortedstate)
438 pos = 0
438 pos = 0
439 for rev in sortedstate:
439 for rev in sortedstate:
440 ctx = repo[rev]
440 ctx = repo[rev]
441 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
441 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
442 ctx.description().split('\n', 1)[0])
442 ctx.description().split('\n', 1)[0])
443 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
443 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
444 if names:
444 if names:
445 desc += ' (%s)' % ' '.join(names)
445 desc += ' (%s)' % ' '.join(names)
446 pos += 1
446 pos += 1
447 if state[rev] == revtodo:
447 if state[rev] == revtodo:
448 ui.status(_('rebasing %s\n') % desc)
448 ui.status(_('rebasing %s\n') % desc)
449 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
449 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
450 _('changesets'), total)
450 _('changesets'), total)
451 p1, p2, base = defineparents(repo, rev, target, state,
451 p1, p2, base = defineparents(repo, rev, target, state,
452 targetancestors)
452 targetancestors)
453 storestatus(repo, originalwd, target, state, collapsef, keepf,
453 storestatus(repo, originalwd, target, state, collapsef, keepf,
454 keepbranchesf, external, activebookmark)
454 keepbranchesf, external, activebookmark)
455 if len(repo[None].parents()) == 2:
455 if len(repo[None].parents()) == 2:
456 repo.ui.debug('resuming interrupted rebase\n')
456 repo.ui.debug('resuming interrupted rebase\n')
457 else:
457 else:
458 try:
458 try:
459 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
459 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
460 'rebase')
460 'rebase')
461 stats = rebasenode(repo, rev, p1, base, state,
461 stats = rebasenode(repo, rev, p1, base, state,
462 collapsef, target)
462 collapsef, target)
463 if stats and stats[3] > 0:
463 if stats and stats[3] > 0:
464 raise error.InterventionRequired(
464 raise error.InterventionRequired(
465 _('unresolved conflicts (see hg '
465 _('unresolved conflicts (see hg '
466 'resolve, then hg rebase --continue)'))
466 'resolve, then hg rebase --continue)'))
467 finally:
467 finally:
468 ui.setconfig('ui', 'forcemerge', '', 'rebase')
468 ui.setconfig('ui', 'forcemerge', '', 'rebase')
469 if not collapsef:
469 if not collapsef:
470 merging = p2 != nullrev
470 merging = p2 != nullrev
471 editform = cmdutil.mergeeditform(merging, 'rebase')
471 editform = cmdutil.mergeeditform(merging, 'rebase')
472 editor = cmdutil.getcommiteditor(editform=editform, **opts)
472 editor = cmdutil.getcommiteditor(editform=editform, **opts)
473 newnode = concludenode(repo, rev, p1, p2, extrafn=extrafn,
473 newnode = concludenode(repo, rev, p1, p2, extrafn=extrafn,
474 editor=editor,
474 editor=editor,
475 keepbranches=keepbranchesf,
475 keepbranches=keepbranchesf,
476 date=date)
476 date=date)
477 else:
477 else:
478 # Skip commit if we are collapsing
478 # Skip commit if we are collapsing
479 repo.dirstate.beginparentchange()
479 repo.dirstate.beginparentchange()
480 repo.setparents(repo[p1].node())
480 repo.setparents(repo[p1].node())
481 repo.dirstate.endparentchange()
481 repo.dirstate.endparentchange()
482 newnode = None
482 newnode = None
483 # Update the state
483 # Update the state
484 if newnode is not None:
484 if newnode is not None:
485 state[rev] = repo[newnode].rev()
485 state[rev] = repo[newnode].rev()
486 ui.debug('rebased as %s\n' % short(newnode))
486 ui.debug('rebased as %s\n' % short(newnode))
487 else:
487 else:
488 if not collapsef:
488 if not collapsef:
489 ui.warn(_('note: rebase of %d:%s created no changes '
489 ui.warn(_('note: rebase of %d:%s created no changes '
490 'to commit\n') % (rev, ctx))
490 'to commit\n') % (rev, ctx))
491 skipped.add(rev)
491 skipped.add(rev)
492 state[rev] = p1
492 state[rev] = p1
493 ui.debug('next revision set to %s\n' % p1)
493 ui.debug('next revision set to %s\n' % p1)
494 elif state[rev] == nullmerge:
494 elif state[rev] == nullmerge:
495 ui.debug('ignoring null merge rebase of %s\n' % rev)
495 ui.debug('ignoring null merge rebase of %s\n' % rev)
496 elif state[rev] == revignored:
496 elif state[rev] == revignored:
497 ui.status(_('not rebasing ignored %s\n') % desc)
497 ui.status(_('not rebasing ignored %s\n') % desc)
498 elif state[rev] == revprecursor:
498 elif state[rev] == revprecursor:
499 targetctx = repo[obsoletenotrebased[rev]]
499 targetctx = repo[obsoletenotrebased[rev]]
500 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
500 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
501 targetctx.description().split('\n', 1)[0])
501 targetctx.description().split('\n', 1)[0])
502 msg = _('note: not rebasing %s, already in destination as %s\n')
502 msg = _('note: not rebasing %s, already in destination as %s\n')
503 ui.status(msg % (desc, desctarget))
503 ui.status(msg % (desc, desctarget))
504 elif state[rev] == revpruned:
504 elif state[rev] == revpruned:
505 msg = _('note: not rebasing %s, it has no successor\n')
505 msg = _('note: not rebasing %s, it has no successor\n')
506 ui.status(msg % desc)
506 ui.status(msg % desc)
507 else:
507 else:
508 ui.status(_('already rebased %s as %s\n') %
508 ui.status(_('already rebased %s as %s\n') %
509 (desc, repo[state[rev]]))
509 (desc, repo[state[rev]]))
510
510
511 ui.progress(_('rebasing'), None)
511 ui.progress(_('rebasing'), None)
512 ui.note(_('rebase merging completed\n'))
512 ui.note(_('rebase merging completed\n'))
513
513
514 if collapsef and not keepopen:
514 if collapsef and not keepopen:
515 p1, p2, _base = defineparents(repo, min(state), target,
515 p1, p2, _base = defineparents(repo, min(state), target,
516 state, targetancestors)
516 state, targetancestors)
517 editopt = opts.get('edit')
517 editopt = opts.get('edit')
518 editform = 'rebase.collapse'
518 editform = 'rebase.collapse'
519 if collapsemsg:
519 if collapsemsg:
520 commitmsg = collapsemsg
520 commitmsg = collapsemsg
521 else:
521 else:
522 commitmsg = 'Collapsed revision'
522 commitmsg = 'Collapsed revision'
523 for rebased in state:
523 for rebased in state:
524 if rebased not in skipped and state[rebased] > nullmerge:
524 if rebased not in skipped and state[rebased] > nullmerge:
525 commitmsg += '\n* %s' % repo[rebased].description()
525 commitmsg += '\n* %s' % repo[rebased].description()
526 editopt = True
526 editopt = True
527 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
527 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
528 newnode = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
528 newnode = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
529 extrafn=extrafn, editor=editor,
529 extrafn=extrafn, editor=editor,
530 keepbranches=keepbranchesf,
530 keepbranches=keepbranchesf,
531 date=date)
531 date=date)
532 if newnode is None:
532 if newnode is None:
533 newrev = target
533 newrev = target
534 else:
534 else:
535 newrev = repo[newnode].rev()
535 newrev = repo[newnode].rev()
536 for oldrev in state.iterkeys():
536 for oldrev in state.iterkeys():
537 if state[oldrev] > nullmerge:
537 if state[oldrev] > nullmerge:
538 state[oldrev] = newrev
538 state[oldrev] = newrev
539
539
540 if 'qtip' in repo.tags():
540 if 'qtip' in repo.tags():
541 updatemq(repo, state, skipped, **opts)
541 updatemq(repo, state, skipped, **opts)
542
542
543 if currentbookmarks:
543 if currentbookmarks:
544 # Nodeids are needed to reset bookmarks
544 # Nodeids are needed to reset bookmarks
545 nstate = {}
545 nstate = {}
546 for k, v in state.iteritems():
546 for k, v in state.iteritems():
547 if v > nullmerge:
547 if v > nullmerge:
548 nstate[repo[k].node()] = repo[v].node()
548 nstate[repo[k].node()] = repo[v].node()
549 # XXX this is the same as dest.node() for the non-continue path --
549 # XXX this is the same as dest.node() for the non-continue path --
550 # this should probably be cleaned up
550 # this should probably be cleaned up
551 targetnode = repo[target].node()
551 targetnode = repo[target].node()
552
552
553 # restore original working directory
553 # restore original working directory
554 # (we do this before stripping)
554 # (we do this before stripping)
555 newwd = state.get(originalwd, originalwd)
555 newwd = state.get(originalwd, originalwd)
556 if newwd < 0:
556 if newwd < 0:
557 # original directory is a parent of rebase set root or ignored
557 # original directory is a parent of rebase set root or ignored
558 newwd = originalwd
558 newwd = originalwd
559 if newwd not in [c.rev() for c in repo[None].parents()]:
559 if newwd not in [c.rev() for c in repo[None].parents()]:
560 ui.note(_("update back to initial working directory parent\n"))
560 ui.note(_("update back to initial working directory parent\n"))
561 hg.updaterepo(repo, newwd, False)
561 hg.updaterepo(repo, newwd, False)
562
562
563 if not keepf:
563 if not keepf:
564 collapsedas = None
564 collapsedas = None
565 if collapsef:
565 if collapsef:
566 collapsedas = newnode
566 collapsedas = newnode
567 clearrebased(ui, repo, state, skipped, collapsedas)
567 clearrebased(ui, repo, state, skipped, collapsedas)
568
568
569 with repo.transaction('bookmark') as tr:
569 with repo.transaction('bookmark') as tr:
570 if currentbookmarks:
570 if currentbookmarks:
571 updatebookmarks(repo, targetnode, nstate, currentbookmarks, tr)
571 updatebookmarks(repo, targetnode, nstate, currentbookmarks, tr)
572 if activebookmark not in repo._bookmarks:
572 if activebookmark not in repo._bookmarks:
573 # active bookmark was divergent one and has been deleted
573 # active bookmark was divergent one and has been deleted
574 activebookmark = None
574 activebookmark = None
575 clearstatus(repo)
575 clearstatus(repo)
576
576
577 ui.note(_("rebase completed\n"))
577 ui.note(_("rebase completed\n"))
578 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
578 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
579 if skipped:
579 if skipped:
580 ui.note(_("%d revisions have been skipped\n") % len(skipped))
580 ui.note(_("%d revisions have been skipped\n") % len(skipped))
581
581
582 if (activebookmark and
582 if (activebookmark and
583 repo['.'].node() == repo._bookmarks[activebookmark]):
583 repo['.'].node() == repo._bookmarks[activebookmark]):
584 bookmarks.activate(repo, activebookmark)
584 bookmarks.activate(repo, activebookmark)
585
585
586 finally:
586 finally:
587 release(lock, wlock)
587 release(lock, wlock)
588
588
589 def externalparent(repo, state, targetancestors):
589 def externalparent(repo, state, targetancestors):
590 """Return the revision that should be used as the second parent
590 """Return the revision that should be used as the second parent
591 when the revisions in state is collapsed on top of targetancestors.
591 when the revisions in state is collapsed on top of targetancestors.
592 Abort if there is more than one parent.
592 Abort if there is more than one parent.
593 """
593 """
594 parents = set()
594 parents = set()
595 source = min(state)
595 source = min(state)
596 for rev in state:
596 for rev in state:
597 if rev == source:
597 if rev == source:
598 continue
598 continue
599 for p in repo[rev].parents():
599 for p in repo[rev].parents():
600 if (p.rev() not in state
600 if (p.rev() not in state
601 and p.rev() not in targetancestors):
601 and p.rev() not in targetancestors):
602 parents.add(p.rev())
602 parents.add(p.rev())
603 if not parents:
603 if not parents:
604 return nullrev
604 return nullrev
605 if len(parents) == 1:
605 if len(parents) == 1:
606 return parents.pop()
606 return parents.pop()
607 raise error.Abort(_('unable to collapse on top of %s, there is more '
607 raise error.Abort(_('unable to collapse on top of %s, there is more '
608 'than one external parent: %s') %
608 'than one external parent: %s') %
609 (max(targetancestors),
609 (max(targetancestors),
610 ', '.join(str(p) for p in sorted(parents))))
610 ', '.join(str(p) for p in sorted(parents))))
611
611
612 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
612 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
613 keepbranches=False, date=None):
613 keepbranches=False, date=None):
614 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
614 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
615 but also store useful information in extra.
615 but also store useful information in extra.
616 Return node of committed revision.'''
616 Return node of committed revision.'''
617 dsguard = cmdutil.dirstateguard(repo, 'rebase')
617 dsguard = cmdutil.dirstateguard(repo, 'rebase')
618 try:
618 try:
619 repo.setparents(repo[p1].node(), repo[p2].node())
619 repo.setparents(repo[p1].node(), repo[p2].node())
620 ctx = repo[rev]
620 ctx = repo[rev]
621 if commitmsg is None:
621 if commitmsg is None:
622 commitmsg = ctx.description()
622 commitmsg = ctx.description()
623 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
623 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
624 extra = ctx.extra().copy()
624 extra = {'rebase_source': ctx.hex()}
625 if not keepbranches:
626 del extra['branch']
627 extra['rebase_source'] = ctx.hex()
628 if extrafn:
625 if extrafn:
629 extrafn(ctx, extra)
626 extrafn(ctx, extra)
630
627
631 backup = repo.ui.backupconfig('phases', 'new-commit')
628 backup = repo.ui.backupconfig('phases', 'new-commit')
632 try:
629 try:
633 targetphase = max(ctx.phase(), phases.draft)
630 targetphase = max(ctx.phase(), phases.draft)
634 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
631 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
635 if keepbranch:
632 if keepbranch:
636 repo.ui.setconfig('ui', 'allowemptycommit', True)
633 repo.ui.setconfig('ui', 'allowemptycommit', True)
637 # Commit might fail if unresolved files exist
634 # Commit might fail if unresolved files exist
638 if date is None:
635 if date is None:
639 date = ctx.date()
636 date = ctx.date()
640 newnode = repo.commit(text=commitmsg, user=ctx.user(),
637 newnode = repo.commit(text=commitmsg, user=ctx.user(),
641 date=date, extra=extra, editor=editor)
638 date=date, extra=extra, editor=editor)
642 finally:
639 finally:
643 repo.ui.restoreconfig(backup)
640 repo.ui.restoreconfig(backup)
644
641
645 repo.dirstate.setbranch(repo[newnode].branch())
642 repo.dirstate.setbranch(repo[newnode].branch())
646 dsguard.close()
643 dsguard.close()
647 return newnode
644 return newnode
648 finally:
645 finally:
649 release(dsguard)
646 release(dsguard)
650
647
651 def rebasenode(repo, rev, p1, base, state, collapse, target):
648 def rebasenode(repo, rev, p1, base, state, collapse, target):
652 'Rebase a single revision rev on top of p1 using base as merge ancestor'
649 'Rebase a single revision rev on top of p1 using base as merge ancestor'
653 # Merge phase
650 # Merge phase
654 # Update to target and merge it with local
651 # Update to target and merge it with local
655 if repo['.'].rev() != p1:
652 if repo['.'].rev() != p1:
656 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
653 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
657 merge.update(repo, p1, False, True)
654 merge.update(repo, p1, False, True)
658 else:
655 else:
659 repo.ui.debug(" already in target\n")
656 repo.ui.debug(" already in target\n")
660 repo.dirstate.write(repo.currenttransaction())
657 repo.dirstate.write(repo.currenttransaction())
661 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
658 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
662 if base is not None:
659 if base is not None:
663 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
660 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
664 # When collapsing in-place, the parent is the common ancestor, we
661 # When collapsing in-place, the parent is the common ancestor, we
665 # have to allow merging with it.
662 # have to allow merging with it.
666 stats = merge.update(repo, rev, True, True, base, collapse,
663 stats = merge.update(repo, rev, True, True, base, collapse,
667 labels=['dest', 'source'])
664 labels=['dest', 'source'])
668 if collapse:
665 if collapse:
669 copies.duplicatecopies(repo, rev, target)
666 copies.duplicatecopies(repo, rev, target)
670 else:
667 else:
671 # If we're not using --collapse, we need to
668 # If we're not using --collapse, we need to
672 # duplicate copies between the revision we're
669 # duplicate copies between the revision we're
673 # rebasing and its first parent, but *not*
670 # rebasing and its first parent, but *not*
674 # duplicate any copies that have already been
671 # duplicate any copies that have already been
675 # performed in the destination.
672 # performed in the destination.
676 p1rev = repo[rev].p1().rev()
673 p1rev = repo[rev].p1().rev()
677 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
674 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
678 return stats
675 return stats
679
676
680 def nearestrebased(repo, rev, state):
677 def nearestrebased(repo, rev, state):
681 """return the nearest ancestors of rev in the rebase result"""
678 """return the nearest ancestors of rev in the rebase result"""
682 rebased = [r for r in state if state[r] > nullmerge]
679 rebased = [r for r in state if state[r] > nullmerge]
683 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
680 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
684 if candidates:
681 if candidates:
685 return state[candidates.first()]
682 return state[candidates.first()]
686 else:
683 else:
687 return None
684 return None
688
685
689 def defineparents(repo, rev, target, state, targetancestors):
686 def defineparents(repo, rev, target, state, targetancestors):
690 'Return the new parent relationship of the revision that will be rebased'
687 'Return the new parent relationship of the revision that will be rebased'
691 parents = repo[rev].parents()
688 parents = repo[rev].parents()
692 p1 = p2 = nullrev
689 p1 = p2 = nullrev
693
690
694 p1n = parents[0].rev()
691 p1n = parents[0].rev()
695 if p1n in targetancestors:
692 if p1n in targetancestors:
696 p1 = target
693 p1 = target
697 elif p1n in state:
694 elif p1n in state:
698 if state[p1n] == nullmerge:
695 if state[p1n] == nullmerge:
699 p1 = target
696 p1 = target
700 elif state[p1n] in revskipped:
697 elif state[p1n] in revskipped:
701 p1 = nearestrebased(repo, p1n, state)
698 p1 = nearestrebased(repo, p1n, state)
702 if p1 is None:
699 if p1 is None:
703 p1 = target
700 p1 = target
704 else:
701 else:
705 p1 = state[p1n]
702 p1 = state[p1n]
706 else: # p1n external
703 else: # p1n external
707 p1 = target
704 p1 = target
708 p2 = p1n
705 p2 = p1n
709
706
710 if len(parents) == 2 and parents[1].rev() not in targetancestors:
707 if len(parents) == 2 and parents[1].rev() not in targetancestors:
711 p2n = parents[1].rev()
708 p2n = parents[1].rev()
712 # interesting second parent
709 # interesting second parent
713 if p2n in state:
710 if p2n in state:
714 if p1 == target: # p1n in targetancestors or external
711 if p1 == target: # p1n in targetancestors or external
715 p1 = state[p2n]
712 p1 = state[p2n]
716 elif state[p2n] in revskipped:
713 elif state[p2n] in revskipped:
717 p2 = nearestrebased(repo, p2n, state)
714 p2 = nearestrebased(repo, p2n, state)
718 if p2 is None:
715 if p2 is None:
719 # no ancestors rebased yet, detach
716 # no ancestors rebased yet, detach
720 p2 = target
717 p2 = target
721 else:
718 else:
722 p2 = state[p2n]
719 p2 = state[p2n]
723 else: # p2n external
720 else: # p2n external
724 if p2 != nullrev: # p1n external too => rev is a merged revision
721 if p2 != nullrev: # p1n external too => rev is a merged revision
725 raise error.Abort(_('cannot use revision %d as base, result '
722 raise error.Abort(_('cannot use revision %d as base, result '
726 'would have 3 parents') % rev)
723 'would have 3 parents') % rev)
727 p2 = p2n
724 p2 = p2n
728 repo.ui.debug(" future parents are %d and %d\n" %
725 repo.ui.debug(" future parents are %d and %d\n" %
729 (repo[p1].rev(), repo[p2].rev()))
726 (repo[p1].rev(), repo[p2].rev()))
730
727
731 if not any(p.rev() in state for p in parents):
728 if not any(p.rev() in state for p in parents):
732 # Case (1) root changeset of a non-detaching rebase set.
729 # Case (1) root changeset of a non-detaching rebase set.
733 # Let the merge mechanism find the base itself.
730 # Let the merge mechanism find the base itself.
734 base = None
731 base = None
735 elif not repo[rev].p2():
732 elif not repo[rev].p2():
736 # Case (2) detaching the node with a single parent, use this parent
733 # Case (2) detaching the node with a single parent, use this parent
737 base = repo[rev].p1().rev()
734 base = repo[rev].p1().rev()
738 else:
735 else:
739 # Assuming there is a p1, this is the case where there also is a p2.
736 # Assuming there is a p1, this is the case where there also is a p2.
740 # We are thus rebasing a merge and need to pick the right merge base.
737 # We are thus rebasing a merge and need to pick the right merge base.
741 #
738 #
742 # Imagine we have:
739 # Imagine we have:
743 # - M: current rebase revision in this step
740 # - M: current rebase revision in this step
744 # - A: one parent of M
741 # - A: one parent of M
745 # - B: other parent of M
742 # - B: other parent of M
746 # - D: destination of this merge step (p1 var)
743 # - D: destination of this merge step (p1 var)
747 #
744 #
748 # Consider the case where D is a descendant of A or B and the other is
745 # Consider the case where D is a descendant of A or B and the other is
749 # 'outside'. In this case, the right merge base is the D ancestor.
746 # 'outside'. In this case, the right merge base is the D ancestor.
750 #
747 #
751 # An informal proof, assuming A is 'outside' and B is the D ancestor:
748 # An informal proof, assuming A is 'outside' and B is the D ancestor:
752 #
749 #
753 # If we pick B as the base, the merge involves:
750 # If we pick B as the base, the merge involves:
754 # - changes from B to M (actual changeset payload)
751 # - changes from B to M (actual changeset payload)
755 # - changes from B to D (induced by rebase) as D is a rebased
752 # - changes from B to D (induced by rebase) as D is a rebased
756 # version of B)
753 # version of B)
757 # Which exactly represent the rebase operation.
754 # Which exactly represent the rebase operation.
758 #
755 #
759 # If we pick A as the base, the merge involves:
756 # If we pick A as the base, the merge involves:
760 # - changes from A to M (actual changeset payload)
757 # - changes from A to M (actual changeset payload)
761 # - changes from A to D (with include changes between unrelated A and B
758 # - changes from A to D (with include changes between unrelated A and B
762 # plus changes induced by rebase)
759 # plus changes induced by rebase)
763 # Which does not represent anything sensible and creates a lot of
760 # Which does not represent anything sensible and creates a lot of
764 # conflicts. A is thus not the right choice - B is.
761 # conflicts. A is thus not the right choice - B is.
765 #
762 #
766 # Note: The base found in this 'proof' is only correct in the specified
763 # Note: The base found in this 'proof' is only correct in the specified
767 # case. This base does not make sense if is not D a descendant of A or B
764 # case. This base does not make sense if is not D a descendant of A or B
768 # or if the other is not parent 'outside' (especially not if the other
765 # or if the other is not parent 'outside' (especially not if the other
769 # parent has been rebased). The current implementation does not
766 # parent has been rebased). The current implementation does not
770 # make it feasible to consider different cases separately. In these
767 # make it feasible to consider different cases separately. In these
771 # other cases we currently just leave it to the user to correctly
768 # other cases we currently just leave it to the user to correctly
772 # resolve an impossible merge using a wrong ancestor.
769 # resolve an impossible merge using a wrong ancestor.
773 for p in repo[rev].parents():
770 for p in repo[rev].parents():
774 if state.get(p.rev()) == p1:
771 if state.get(p.rev()) == p1:
775 base = p.rev()
772 base = p.rev()
776 break
773 break
777 else: # fallback when base not found
774 else: # fallback when base not found
778 base = None
775 base = None
779
776
780 # Raise because this function is called wrong (see issue 4106)
777 # Raise because this function is called wrong (see issue 4106)
781 raise AssertionError('no base found to rebase on '
778 raise AssertionError('no base found to rebase on '
782 '(defineparents called wrong)')
779 '(defineparents called wrong)')
783 return p1, p2, base
780 return p1, p2, base
784
781
785 def isagitpatch(repo, patchname):
782 def isagitpatch(repo, patchname):
786 'Return true if the given patch is in git format'
783 'Return true if the given patch is in git format'
787 mqpatch = os.path.join(repo.mq.path, patchname)
784 mqpatch = os.path.join(repo.mq.path, patchname)
788 for line in patch.linereader(file(mqpatch, 'rb')):
785 for line in patch.linereader(file(mqpatch, 'rb')):
789 if line.startswith('diff --git'):
786 if line.startswith('diff --git'):
790 return True
787 return True
791 return False
788 return False
792
789
793 def updatemq(repo, state, skipped, **opts):
790 def updatemq(repo, state, skipped, **opts):
794 'Update rebased mq patches - finalize and then import them'
791 'Update rebased mq patches - finalize and then import them'
795 mqrebase = {}
792 mqrebase = {}
796 mq = repo.mq
793 mq = repo.mq
797 original_series = mq.fullseries[:]
794 original_series = mq.fullseries[:]
798 skippedpatches = set()
795 skippedpatches = set()
799
796
800 for p in mq.applied:
797 for p in mq.applied:
801 rev = repo[p.node].rev()
798 rev = repo[p.node].rev()
802 if rev in state:
799 if rev in state:
803 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
800 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
804 (rev, p.name))
801 (rev, p.name))
805 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
802 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
806 else:
803 else:
807 # Applied but not rebased, not sure this should happen
804 # Applied but not rebased, not sure this should happen
808 skippedpatches.add(p.name)
805 skippedpatches.add(p.name)
809
806
810 if mqrebase:
807 if mqrebase:
811 mq.finish(repo, mqrebase.keys())
808 mq.finish(repo, mqrebase.keys())
812
809
813 # We must start import from the newest revision
810 # We must start import from the newest revision
814 for rev in sorted(mqrebase, reverse=True):
811 for rev in sorted(mqrebase, reverse=True):
815 if rev not in skipped:
812 if rev not in skipped:
816 name, isgit = mqrebase[rev]
813 name, isgit = mqrebase[rev]
817 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
814 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
818 (name, state[rev], repo[state[rev]]))
815 (name, state[rev], repo[state[rev]]))
819 mq.qimport(repo, (), patchname=name, git=isgit,
816 mq.qimport(repo, (), patchname=name, git=isgit,
820 rev=[str(state[rev])])
817 rev=[str(state[rev])])
821 else:
818 else:
822 # Rebased and skipped
819 # Rebased and skipped
823 skippedpatches.add(mqrebase[rev][0])
820 skippedpatches.add(mqrebase[rev][0])
824
821
825 # Patches were either applied and rebased and imported in
822 # Patches were either applied and rebased and imported in
826 # order, applied and removed or unapplied. Discard the removed
823 # order, applied and removed or unapplied. Discard the removed
827 # ones while preserving the original series order and guards.
824 # ones while preserving the original series order and guards.
828 newseries = [s for s in original_series
825 newseries = [s for s in original_series
829 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
826 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
830 mq.fullseries[:] = newseries
827 mq.fullseries[:] = newseries
831 mq.seriesdirty = True
828 mq.seriesdirty = True
832 mq.savedirty()
829 mq.savedirty()
833
830
834 def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr):
831 def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr):
835 'Move bookmarks to their correct changesets, and delete divergent ones'
832 'Move bookmarks to their correct changesets, and delete divergent ones'
836 marks = repo._bookmarks
833 marks = repo._bookmarks
837 for k, v in originalbookmarks.iteritems():
834 for k, v in originalbookmarks.iteritems():
838 if v in nstate:
835 if v in nstate:
839 # update the bookmarks for revs that have moved
836 # update the bookmarks for revs that have moved
840 marks[k] = nstate[v]
837 marks[k] = nstate[v]
841 bookmarks.deletedivergent(repo, [targetnode], k)
838 bookmarks.deletedivergent(repo, [targetnode], k)
842 marks.recordchange(tr)
839 marks.recordchange(tr)
843
840
844 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
841 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
845 external, activebookmark):
842 external, activebookmark):
846 'Store the current status to allow recovery'
843 'Store the current status to allow recovery'
847 f = repo.vfs("rebasestate", "w")
844 f = repo.vfs("rebasestate", "w")
848 f.write(repo[originalwd].hex() + '\n')
845 f.write(repo[originalwd].hex() + '\n')
849 f.write(repo[target].hex() + '\n')
846 f.write(repo[target].hex() + '\n')
850 f.write(repo[external].hex() + '\n')
847 f.write(repo[external].hex() + '\n')
851 f.write('%d\n' % int(collapse))
848 f.write('%d\n' % int(collapse))
852 f.write('%d\n' % int(keep))
849 f.write('%d\n' % int(keep))
853 f.write('%d\n' % int(keepbranches))
850 f.write('%d\n' % int(keepbranches))
854 f.write('%s\n' % (activebookmark or ''))
851 f.write('%s\n' % (activebookmark or ''))
855 for d, v in state.iteritems():
852 for d, v in state.iteritems():
856 oldrev = repo[d].hex()
853 oldrev = repo[d].hex()
857 if v >= 0:
854 if v >= 0:
858 newrev = repo[v].hex()
855 newrev = repo[v].hex()
859 elif v == revtodo:
856 elif v == revtodo:
860 # To maintain format compatibility, we have to use nullid.
857 # To maintain format compatibility, we have to use nullid.
861 # Please do remove this special case when upgrading the format.
858 # Please do remove this special case when upgrading the format.
862 newrev = hex(nullid)
859 newrev = hex(nullid)
863 else:
860 else:
864 newrev = v
861 newrev = v
865 f.write("%s:%s\n" % (oldrev, newrev))
862 f.write("%s:%s\n" % (oldrev, newrev))
866 f.close()
863 f.close()
867 repo.ui.debug('rebase status stored\n')
864 repo.ui.debug('rebase status stored\n')
868
865
869 def clearstatus(repo):
866 def clearstatus(repo):
870 'Remove the status files'
867 'Remove the status files'
871 _clearrebasesetvisibiliy(repo)
868 _clearrebasesetvisibiliy(repo)
872 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
869 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
873
870
874 def restorestatus(repo):
871 def restorestatus(repo):
875 'Restore a previously stored status'
872 'Restore a previously stored status'
876 keepbranches = None
873 keepbranches = None
877 target = None
874 target = None
878 collapse = False
875 collapse = False
879 external = nullrev
876 external = nullrev
880 activebookmark = None
877 activebookmark = None
881 state = {}
878 state = {}
882
879
883 try:
880 try:
884 f = repo.vfs("rebasestate")
881 f = repo.vfs("rebasestate")
885 for i, l in enumerate(f.read().splitlines()):
882 for i, l in enumerate(f.read().splitlines()):
886 if i == 0:
883 if i == 0:
887 originalwd = repo[l].rev()
884 originalwd = repo[l].rev()
888 elif i == 1:
885 elif i == 1:
889 target = repo[l].rev()
886 target = repo[l].rev()
890 elif i == 2:
887 elif i == 2:
891 external = repo[l].rev()
888 external = repo[l].rev()
892 elif i == 3:
889 elif i == 3:
893 collapse = bool(int(l))
890 collapse = bool(int(l))
894 elif i == 4:
891 elif i == 4:
895 keep = bool(int(l))
892 keep = bool(int(l))
896 elif i == 5:
893 elif i == 5:
897 keepbranches = bool(int(l))
894 keepbranches = bool(int(l))
898 elif i == 6 and not (len(l) == 81 and ':' in l):
895 elif i == 6 and not (len(l) == 81 and ':' in l):
899 # line 6 is a recent addition, so for backwards compatibility
896 # line 6 is a recent addition, so for backwards compatibility
900 # check that the line doesn't look like the oldrev:newrev lines
897 # check that the line doesn't look like the oldrev:newrev lines
901 activebookmark = l
898 activebookmark = l
902 else:
899 else:
903 oldrev, newrev = l.split(':')
900 oldrev, newrev = l.split(':')
904 if newrev in (str(nullmerge), str(revignored),
901 if newrev in (str(nullmerge), str(revignored),
905 str(revprecursor), str(revpruned)):
902 str(revprecursor), str(revpruned)):
906 state[repo[oldrev].rev()] = int(newrev)
903 state[repo[oldrev].rev()] = int(newrev)
907 elif newrev == nullid:
904 elif newrev == nullid:
908 state[repo[oldrev].rev()] = revtodo
905 state[repo[oldrev].rev()] = revtodo
909 # Legacy compat special case
906 # Legacy compat special case
910 else:
907 else:
911 state[repo[oldrev].rev()] = repo[newrev].rev()
908 state[repo[oldrev].rev()] = repo[newrev].rev()
912
909
913 except IOError as err:
910 except IOError as err:
914 if err.errno != errno.ENOENT:
911 if err.errno != errno.ENOENT:
915 raise
912 raise
916 raise error.Abort(_('no rebase in progress'))
913 raise error.Abort(_('no rebase in progress'))
917
914
918 if keepbranches is None:
915 if keepbranches is None:
919 raise error.Abort(_('.hg/rebasestate is incomplete'))
916 raise error.Abort(_('.hg/rebasestate is incomplete'))
920
917
921 skipped = set()
918 skipped = set()
922 # recompute the set of skipped revs
919 # recompute the set of skipped revs
923 if not collapse:
920 if not collapse:
924 seen = set([target])
921 seen = set([target])
925 for old, new in sorted(state.items()):
922 for old, new in sorted(state.items()):
926 if new != revtodo and new in seen:
923 if new != revtodo and new in seen:
927 skipped.add(old)
924 skipped.add(old)
928 seen.add(new)
925 seen.add(new)
929 repo.ui.debug('computed skipped revs: %s\n' %
926 repo.ui.debug('computed skipped revs: %s\n' %
930 (' '.join(str(r) for r in sorted(skipped)) or None))
927 (' '.join(str(r) for r in sorted(skipped)) or None))
931 repo.ui.debug('rebase status resumed\n')
928 repo.ui.debug('rebase status resumed\n')
932 _setrebasesetvisibility(repo, state.keys())
929 _setrebasesetvisibility(repo, state.keys())
933 return (originalwd, target, state, skipped,
930 return (originalwd, target, state, skipped,
934 collapse, keep, keepbranches, external, activebookmark)
931 collapse, keep, keepbranches, external, activebookmark)
935
932
936 def needupdate(repo, state):
933 def needupdate(repo, state):
937 '''check whether we should `update --clean` away from a merge, or if
934 '''check whether we should `update --clean` away from a merge, or if
938 somehow the working dir got forcibly updated, e.g. by older hg'''
935 somehow the working dir got forcibly updated, e.g. by older hg'''
939 parents = [p.rev() for p in repo[None].parents()]
936 parents = [p.rev() for p in repo[None].parents()]
940
937
941 # Are we in a merge state at all?
938 # Are we in a merge state at all?
942 if len(parents) < 2:
939 if len(parents) < 2:
943 return False
940 return False
944
941
945 # We should be standing on the first as-of-yet unrebased commit.
942 # We should be standing on the first as-of-yet unrebased commit.
946 firstunrebased = min([old for old, new in state.iteritems()
943 firstunrebased = min([old for old, new in state.iteritems()
947 if new == nullrev])
944 if new == nullrev])
948 if firstunrebased in parents:
945 if firstunrebased in parents:
949 return True
946 return True
950
947
951 return False
948 return False
952
949
953 def abort(repo, originalwd, target, state, activebookmark=None):
950 def abort(repo, originalwd, target, state, activebookmark=None):
954 '''Restore the repository to its original state. Additional args:
951 '''Restore the repository to its original state. Additional args:
955
952
956 activebookmark: the name of the bookmark that should be active after the
953 activebookmark: the name of the bookmark that should be active after the
957 restore'''
954 restore'''
958
955
959 try:
956 try:
960 # If the first commits in the rebased set get skipped during the rebase,
957 # If the first commits in the rebased set get skipped during the rebase,
961 # their values within the state mapping will be the target rev id. The
958 # their values within the state mapping will be the target rev id. The
962 # dstates list must must not contain the target rev (issue4896)
959 # dstates list must must not contain the target rev (issue4896)
963 dstates = [s for s in state.values() if s >= 0 and s != target]
960 dstates = [s for s in state.values() if s >= 0 and s != target]
964 immutable = [d for d in dstates if not repo[d].mutable()]
961 immutable = [d for d in dstates if not repo[d].mutable()]
965 cleanup = True
962 cleanup = True
966 if immutable:
963 if immutable:
967 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
964 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
968 % ', '.join(str(repo[r]) for r in immutable),
965 % ', '.join(str(repo[r]) for r in immutable),
969 hint=_('see "hg help phases" for details'))
966 hint=_('see "hg help phases" for details'))
970 cleanup = False
967 cleanup = False
971
968
972 descendants = set()
969 descendants = set()
973 if dstates:
970 if dstates:
974 descendants = set(repo.changelog.descendants(dstates))
971 descendants = set(repo.changelog.descendants(dstates))
975 if descendants - set(dstates):
972 if descendants - set(dstates):
976 repo.ui.warn(_("warning: new changesets detected on target branch, "
973 repo.ui.warn(_("warning: new changesets detected on target branch, "
977 "can't strip\n"))
974 "can't strip\n"))
978 cleanup = False
975 cleanup = False
979
976
980 if cleanup:
977 if cleanup:
981 # Update away from the rebase if necessary
978 # Update away from the rebase if necessary
982 if needupdate(repo, state):
979 if needupdate(repo, state):
983 merge.update(repo, originalwd, False, True)
980 merge.update(repo, originalwd, False, True)
984
981
985 # Strip from the first rebased revision
982 # Strip from the first rebased revision
986 rebased = filter(lambda x: x >= 0 and x != target, state.values())
983 rebased = filter(lambda x: x >= 0 and x != target, state.values())
987 if rebased:
984 if rebased:
988 strippoints = [
985 strippoints = [
989 c.node() for c in repo.set('roots(%ld)', rebased)]
986 c.node() for c in repo.set('roots(%ld)', rebased)]
990 # no backup of rebased cset versions needed
987 # no backup of rebased cset versions needed
991 repair.strip(repo.ui, repo, strippoints)
988 repair.strip(repo.ui, repo, strippoints)
992
989
993 if activebookmark and activebookmark in repo._bookmarks:
990 if activebookmark and activebookmark in repo._bookmarks:
994 bookmarks.activate(repo, activebookmark)
991 bookmarks.activate(repo, activebookmark)
995
992
996 finally:
993 finally:
997 clearstatus(repo)
994 clearstatus(repo)
998 repo.ui.warn(_('rebase aborted\n'))
995 repo.ui.warn(_('rebase aborted\n'))
999 return 0
996 return 0
1000
997
1001 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
998 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1002 '''Define which revisions are going to be rebased and where
999 '''Define which revisions are going to be rebased and where
1003
1000
1004 repo: repo
1001 repo: repo
1005 dest: context
1002 dest: context
1006 rebaseset: set of rev
1003 rebaseset: set of rev
1007 '''
1004 '''
1008 _setrebasesetvisibility(repo, rebaseset)
1005 _setrebasesetvisibility(repo, rebaseset)
1009
1006
1010 # This check isn't strictly necessary, since mq detects commits over an
1007 # This check isn't strictly necessary, since mq detects commits over an
1011 # applied patch. But it prevents messing up the working directory when
1008 # applied patch. But it prevents messing up the working directory when
1012 # a partially completed rebase is blocked by mq.
1009 # a partially completed rebase is blocked by mq.
1013 if 'qtip' in repo.tags() and (dest.node() in
1010 if 'qtip' in repo.tags() and (dest.node() in
1014 [s.node for s in repo.mq.applied]):
1011 [s.node for s in repo.mq.applied]):
1015 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1012 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1016
1013
1017 roots = list(repo.set('roots(%ld)', rebaseset))
1014 roots = list(repo.set('roots(%ld)', rebaseset))
1018 if not roots:
1015 if not roots:
1019 raise error.Abort(_('no matching revisions'))
1016 raise error.Abort(_('no matching revisions'))
1020 roots.sort()
1017 roots.sort()
1021 state = {}
1018 state = {}
1022 detachset = set()
1019 detachset = set()
1023 for root in roots:
1020 for root in roots:
1024 commonbase = root.ancestor(dest)
1021 commonbase = root.ancestor(dest)
1025 if commonbase == root:
1022 if commonbase == root:
1026 raise error.Abort(_('source is ancestor of destination'))
1023 raise error.Abort(_('source is ancestor of destination'))
1027 if commonbase == dest:
1024 if commonbase == dest:
1028 samebranch = root.branch() == dest.branch()
1025 samebranch = root.branch() == dest.branch()
1029 if not collapse and samebranch and root in dest.children():
1026 if not collapse and samebranch and root in dest.children():
1030 repo.ui.debug('source is a child of destination\n')
1027 repo.ui.debug('source is a child of destination\n')
1031 return None
1028 return None
1032
1029
1033 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
1030 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
1034 state.update(dict.fromkeys(rebaseset, revtodo))
1031 state.update(dict.fromkeys(rebaseset, revtodo))
1035 # Rebase tries to turn <dest> into a parent of <root> while
1032 # Rebase tries to turn <dest> into a parent of <root> while
1036 # preserving the number of parents of rebased changesets:
1033 # preserving the number of parents of rebased changesets:
1037 #
1034 #
1038 # - A changeset with a single parent will always be rebased as a
1035 # - A changeset with a single parent will always be rebased as a
1039 # changeset with a single parent.
1036 # changeset with a single parent.
1040 #
1037 #
1041 # - A merge will be rebased as merge unless its parents are both
1038 # - A merge will be rebased as merge unless its parents are both
1042 # ancestors of <dest> or are themselves in the rebased set and
1039 # ancestors of <dest> or are themselves in the rebased set and
1043 # pruned while rebased.
1040 # pruned while rebased.
1044 #
1041 #
1045 # If one parent of <root> is an ancestor of <dest>, the rebased
1042 # If one parent of <root> is an ancestor of <dest>, the rebased
1046 # version of this parent will be <dest>. This is always true with
1043 # version of this parent will be <dest>. This is always true with
1047 # --base option.
1044 # --base option.
1048 #
1045 #
1049 # Otherwise, we need to *replace* the original parents with
1046 # Otherwise, we need to *replace* the original parents with
1050 # <dest>. This "detaches" the rebased set from its former location
1047 # <dest>. This "detaches" the rebased set from its former location
1051 # and rebases it onto <dest>. Changes introduced by ancestors of
1048 # and rebases it onto <dest>. Changes introduced by ancestors of
1052 # <root> not common with <dest> (the detachset, marked as
1049 # <root> not common with <dest> (the detachset, marked as
1053 # nullmerge) are "removed" from the rebased changesets.
1050 # nullmerge) are "removed" from the rebased changesets.
1054 #
1051 #
1055 # - If <root> has a single parent, set it to <dest>.
1052 # - If <root> has a single parent, set it to <dest>.
1056 #
1053 #
1057 # - If <root> is a merge, we cannot decide which parent to
1054 # - If <root> is a merge, we cannot decide which parent to
1058 # replace, the rebase operation is not clearly defined.
1055 # replace, the rebase operation is not clearly defined.
1059 #
1056 #
1060 # The table below sums up this behavior:
1057 # The table below sums up this behavior:
1061 #
1058 #
1062 # +------------------+----------------------+-------------------------+
1059 # +------------------+----------------------+-------------------------+
1063 # | | one parent | merge |
1060 # | | one parent | merge |
1064 # +------------------+----------------------+-------------------------+
1061 # +------------------+----------------------+-------------------------+
1065 # | parent in | new parent is <dest> | parents in ::<dest> are |
1062 # | parent in | new parent is <dest> | parents in ::<dest> are |
1066 # | ::<dest> | | remapped to <dest> |
1063 # | ::<dest> | | remapped to <dest> |
1067 # +------------------+----------------------+-------------------------+
1064 # +------------------+----------------------+-------------------------+
1068 # | unrelated source | new parent is <dest> | ambiguous, abort |
1065 # | unrelated source | new parent is <dest> | ambiguous, abort |
1069 # +------------------+----------------------+-------------------------+
1066 # +------------------+----------------------+-------------------------+
1070 #
1067 #
1071 # The actual abort is handled by `defineparents`
1068 # The actual abort is handled by `defineparents`
1072 if len(root.parents()) <= 1:
1069 if len(root.parents()) <= 1:
1073 # ancestors of <root> not ancestors of <dest>
1070 # ancestors of <root> not ancestors of <dest>
1074 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1071 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1075 [root.rev()]))
1072 [root.rev()]))
1076 for r in detachset:
1073 for r in detachset:
1077 if r not in state:
1074 if r not in state:
1078 state[r] = nullmerge
1075 state[r] = nullmerge
1079 if len(roots) > 1:
1076 if len(roots) > 1:
1080 # If we have multiple roots, we may have "hole" in the rebase set.
1077 # If we have multiple roots, we may have "hole" in the rebase set.
1081 # Rebase roots that descend from those "hole" should not be detached as
1078 # Rebase roots that descend from those "hole" should not be detached as
1082 # other root are. We use the special `revignored` to inform rebase that
1079 # other root are. We use the special `revignored` to inform rebase that
1083 # the revision should be ignored but that `defineparents` should search
1080 # the revision should be ignored but that `defineparents` should search
1084 # a rebase destination that make sense regarding rebased topology.
1081 # a rebase destination that make sense regarding rebased topology.
1085 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1082 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1086 for ignored in set(rebasedomain) - set(rebaseset):
1083 for ignored in set(rebasedomain) - set(rebaseset):
1087 state[ignored] = revignored
1084 state[ignored] = revignored
1088 for r in obsoletenotrebased:
1085 for r in obsoletenotrebased:
1089 if obsoletenotrebased[r] is None:
1086 if obsoletenotrebased[r] is None:
1090 state[r] = revpruned
1087 state[r] = revpruned
1091 else:
1088 else:
1092 state[r] = revprecursor
1089 state[r] = revprecursor
1093 return repo['.'].rev(), dest.rev(), state
1090 return repo['.'].rev(), dest.rev(), state
1094
1091
1095 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1092 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1096 """dispose of rebased revision at the end of the rebase
1093 """dispose of rebased revision at the end of the rebase
1097
1094
1098 If `collapsedas` is not None, the rebase was a collapse whose result if the
1095 If `collapsedas` is not None, the rebase was a collapse whose result if the
1099 `collapsedas` node."""
1096 `collapsedas` node."""
1100 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1097 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1101 markers = []
1098 markers = []
1102 for rev, newrev in sorted(state.items()):
1099 for rev, newrev in sorted(state.items()):
1103 if newrev >= 0:
1100 if newrev >= 0:
1104 if rev in skipped:
1101 if rev in skipped:
1105 succs = ()
1102 succs = ()
1106 elif collapsedas is not None:
1103 elif collapsedas is not None:
1107 succs = (repo[collapsedas],)
1104 succs = (repo[collapsedas],)
1108 else:
1105 else:
1109 succs = (repo[newrev],)
1106 succs = (repo[newrev],)
1110 markers.append((repo[rev], succs))
1107 markers.append((repo[rev], succs))
1111 if markers:
1108 if markers:
1112 obsolete.createmarkers(repo, markers)
1109 obsolete.createmarkers(repo, markers)
1113 else:
1110 else:
1114 rebased = [rev for rev in state if state[rev] > nullmerge]
1111 rebased = [rev for rev in state if state[rev] > nullmerge]
1115 if rebased:
1112 if rebased:
1116 stripped = []
1113 stripped = []
1117 for root in repo.set('roots(%ld)', rebased):
1114 for root in repo.set('roots(%ld)', rebased):
1118 if set(repo.changelog.descendants([root.rev()])) - set(state):
1115 if set(repo.changelog.descendants([root.rev()])) - set(state):
1119 ui.warn(_("warning: new changesets detected "
1116 ui.warn(_("warning: new changesets detected "
1120 "on source branch, not stripping\n"))
1117 "on source branch, not stripping\n"))
1121 else:
1118 else:
1122 stripped.append(root.node())
1119 stripped.append(root.node())
1123 if stripped:
1120 if stripped:
1124 # backup the old csets by default
1121 # backup the old csets by default
1125 repair.strip(ui, repo, stripped, "all")
1122 repair.strip(ui, repo, stripped, "all")
1126
1123
1127
1124
1128 def pullrebase(orig, ui, repo, *args, **opts):
1125 def pullrebase(orig, ui, repo, *args, **opts):
1129 'Call rebase after pull if the latter has been invoked with --rebase'
1126 'Call rebase after pull if the latter has been invoked with --rebase'
1130 ret = None
1127 ret = None
1131 if opts.get('rebase'):
1128 if opts.get('rebase'):
1132 wlock = lock = None
1129 wlock = lock = None
1133 try:
1130 try:
1134 wlock = repo.wlock()
1131 wlock = repo.wlock()
1135 lock = repo.lock()
1132 lock = repo.lock()
1136 if opts.get('update'):
1133 if opts.get('update'):
1137 del opts['update']
1134 del opts['update']
1138 ui.debug('--update and --rebase are not compatible, ignoring '
1135 ui.debug('--update and --rebase are not compatible, ignoring '
1139 'the update flag\n')
1136 'the update flag\n')
1140
1137
1141 movemarkfrom = repo['.'].node()
1138 movemarkfrom = repo['.'].node()
1142 revsprepull = len(repo)
1139 revsprepull = len(repo)
1143 origpostincoming = commands.postincoming
1140 origpostincoming = commands.postincoming
1144 def _dummy(*args, **kwargs):
1141 def _dummy(*args, **kwargs):
1145 pass
1142 pass
1146 commands.postincoming = _dummy
1143 commands.postincoming = _dummy
1147 try:
1144 try:
1148 ret = orig(ui, repo, *args, **opts)
1145 ret = orig(ui, repo, *args, **opts)
1149 finally:
1146 finally:
1150 commands.postincoming = origpostincoming
1147 commands.postincoming = origpostincoming
1151 revspostpull = len(repo)
1148 revspostpull = len(repo)
1152 if revspostpull > revsprepull:
1149 if revspostpull > revsprepull:
1153 # --rev option from pull conflict with rebase own --rev
1150 # --rev option from pull conflict with rebase own --rev
1154 # dropping it
1151 # dropping it
1155 if 'rev' in opts:
1152 if 'rev' in opts:
1156 del opts['rev']
1153 del opts['rev']
1157 # positional argument from pull conflicts with rebase's own
1154 # positional argument from pull conflicts with rebase's own
1158 # --source.
1155 # --source.
1159 if 'source' in opts:
1156 if 'source' in opts:
1160 del opts['source']
1157 del opts['source']
1161 rebase(ui, repo, **opts)
1158 rebase(ui, repo, **opts)
1162 branch = repo[None].branch()
1159 branch = repo[None].branch()
1163 dest = repo[branch].rev()
1160 dest = repo[branch].rev()
1164 if dest != repo['.'].rev():
1161 if dest != repo['.'].rev():
1165 # there was nothing to rebase we force an update
1162 # there was nothing to rebase we force an update
1166 hg.update(repo, dest)
1163 hg.update(repo, dest)
1167 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
1164 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
1168 ui.status(_("updating bookmark %s\n")
1165 ui.status(_("updating bookmark %s\n")
1169 % repo._activebookmark)
1166 % repo._activebookmark)
1170 finally:
1167 finally:
1171 release(lock, wlock)
1168 release(lock, wlock)
1172 else:
1169 else:
1173 if opts.get('tool'):
1170 if opts.get('tool'):
1174 raise error.Abort(_('--tool can only be used with --rebase'))
1171 raise error.Abort(_('--tool can only be used with --rebase'))
1175 ret = orig(ui, repo, *args, **opts)
1172 ret = orig(ui, repo, *args, **opts)
1176
1173
1177 return ret
1174 return ret
1178
1175
1179 def _setrebasesetvisibility(repo, revs):
1176 def _setrebasesetvisibility(repo, revs):
1180 """store the currently rebased set on the repo object
1177 """store the currently rebased set on the repo object
1181
1178
1182 This is used by another function to prevent rebased revision to because
1179 This is used by another function to prevent rebased revision to because
1183 hidden (see issue4505)"""
1180 hidden (see issue4505)"""
1184 repo = repo.unfiltered()
1181 repo = repo.unfiltered()
1185 revs = set(revs)
1182 revs = set(revs)
1186 repo._rebaseset = revs
1183 repo._rebaseset = revs
1187 # invalidate cache if visibility changes
1184 # invalidate cache if visibility changes
1188 hiddens = repo.filteredrevcache.get('visible', set())
1185 hiddens = repo.filteredrevcache.get('visible', set())
1189 if revs & hiddens:
1186 if revs & hiddens:
1190 repo.invalidatevolatilesets()
1187 repo.invalidatevolatilesets()
1191
1188
1192 def _clearrebasesetvisibiliy(repo):
1189 def _clearrebasesetvisibiliy(repo):
1193 """remove rebaseset data from the repo"""
1190 """remove rebaseset data from the repo"""
1194 repo = repo.unfiltered()
1191 repo = repo.unfiltered()
1195 if '_rebaseset' in vars(repo):
1192 if '_rebaseset' in vars(repo):
1196 del repo._rebaseset
1193 del repo._rebaseset
1197
1194
1198 def _rebasedvisible(orig, repo):
1195 def _rebasedvisible(orig, repo):
1199 """ensure rebased revs stay visible (see issue4505)"""
1196 """ensure rebased revs stay visible (see issue4505)"""
1200 blockers = orig(repo)
1197 blockers = orig(repo)
1201 blockers.update(getattr(repo, '_rebaseset', ()))
1198 blockers.update(getattr(repo, '_rebaseset', ()))
1202 return blockers
1199 return blockers
1203
1200
1204 def _filterobsoleterevs(repo, revs):
1201 def _filterobsoleterevs(repo, revs):
1205 """returns a set of the obsolete revisions in revs"""
1202 """returns a set of the obsolete revisions in revs"""
1206 return set(r for r in revs if repo[r].obsolete())
1203 return set(r for r in revs if repo[r].obsolete())
1207
1204
1208 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1205 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1209 """return a mapping obsolete => successor for all obsolete nodes to be
1206 """return a mapping obsolete => successor for all obsolete nodes to be
1210 rebased that have a successors in the destination
1207 rebased that have a successors in the destination
1211
1208
1212 obsolete => None entries in the mapping indicate nodes with no succesor"""
1209 obsolete => None entries in the mapping indicate nodes with no succesor"""
1213 obsoletenotrebased = {}
1210 obsoletenotrebased = {}
1214
1211
1215 # Build a mapping successor => obsolete nodes for the obsolete
1212 # Build a mapping successor => obsolete nodes for the obsolete
1216 # nodes to be rebased
1213 # nodes to be rebased
1217 allsuccessors = {}
1214 allsuccessors = {}
1218 cl = repo.changelog
1215 cl = repo.changelog
1219 for r in rebaseobsrevs:
1216 for r in rebaseobsrevs:
1220 node = cl.node(r)
1217 node = cl.node(r)
1221 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1218 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1222 try:
1219 try:
1223 allsuccessors[cl.rev(s)] = cl.rev(node)
1220 allsuccessors[cl.rev(s)] = cl.rev(node)
1224 except LookupError:
1221 except LookupError:
1225 pass
1222 pass
1226
1223
1227 if allsuccessors:
1224 if allsuccessors:
1228 # Look for successors of obsolete nodes to be rebased among
1225 # Look for successors of obsolete nodes to be rebased among
1229 # the ancestors of dest
1226 # the ancestors of dest
1230 ancs = cl.ancestors([repo[dest].rev()],
1227 ancs = cl.ancestors([repo[dest].rev()],
1231 stoprev=min(allsuccessors),
1228 stoprev=min(allsuccessors),
1232 inclusive=True)
1229 inclusive=True)
1233 for s in allsuccessors:
1230 for s in allsuccessors:
1234 if s in ancs:
1231 if s in ancs:
1235 obsoletenotrebased[allsuccessors[s]] = s
1232 obsoletenotrebased[allsuccessors[s]] = s
1236 elif (s == allsuccessors[s] and
1233 elif (s == allsuccessors[s] and
1237 allsuccessors.values().count(s) == 1):
1234 allsuccessors.values().count(s) == 1):
1238 # plain prune
1235 # plain prune
1239 obsoletenotrebased[s] = None
1236 obsoletenotrebased[s] = None
1240
1237
1241 return obsoletenotrebased
1238 return obsoletenotrebased
1242
1239
1243 def summaryhook(ui, repo):
1240 def summaryhook(ui, repo):
1244 if not os.path.exists(repo.join('rebasestate')):
1241 if not os.path.exists(repo.join('rebasestate')):
1245 return
1242 return
1246 try:
1243 try:
1247 state = restorestatus(repo)[2]
1244 state = restorestatus(repo)[2]
1248 except error.RepoLookupError:
1245 except error.RepoLookupError:
1249 # i18n: column positioning for "hg summary"
1246 # i18n: column positioning for "hg summary"
1250 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1247 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1251 ui.write(msg)
1248 ui.write(msg)
1252 return
1249 return
1253 numrebased = len([i for i in state.itervalues() if i >= 0])
1250 numrebased = len([i for i in state.itervalues() if i >= 0])
1254 # i18n: column positioning for "hg summary"
1251 # i18n: column positioning for "hg summary"
1255 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1252 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1256 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1253 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1257 ui.label(_('%d remaining'), 'rebase.remaining') %
1254 ui.label(_('%d remaining'), 'rebase.remaining') %
1258 (len(state) - numrebased)))
1255 (len(state) - numrebased)))
1259
1256
1260 def uisetup(ui):
1257 def uisetup(ui):
1261 #Replace pull with a decorator to provide --rebase option
1258 #Replace pull with a decorator to provide --rebase option
1262 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1259 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1263 entry[1].append(('', 'rebase', None,
1260 entry[1].append(('', 'rebase', None,
1264 _("rebase working directory to branch head")))
1261 _("rebase working directory to branch head")))
1265 entry[1].append(('t', 'tool', '',
1262 entry[1].append(('t', 'tool', '',
1266 _("specify merge tool for rebase")))
1263 _("specify merge tool for rebase")))
1267 cmdutil.summaryhooks.add('rebase', summaryhook)
1264 cmdutil.summaryhooks.add('rebase', summaryhook)
1268 cmdutil.unfinishedstates.append(
1265 cmdutil.unfinishedstates.append(
1269 ['rebasestate', False, False, _('rebase in progress'),
1266 ['rebasestate', False, False, _('rebase in progress'),
1270 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1267 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1271 cmdutil.afterresolvedstates.append(
1268 cmdutil.afterresolvedstates.append(
1272 ['rebasestate', _('hg rebase --continue')])
1269 ['rebasestate', _('hg rebase --continue')])
1273 # ensure rebased rev are not hidden
1270 # ensure rebased rev are not hidden
1274 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
1271 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
1275 revsetpredicate.setup()
1272 revsetpredicate.setup()
@@ -1,443 +1,435
1 $ . "$TESTDIR/histedit-helpers.sh"
1 $ . "$TESTDIR/histedit-helpers.sh"
2
2
3 Enable obsolete
3 Enable obsolete
4
4
5 $ cat >> $HGRCPATH << EOF
5 $ cat >> $HGRCPATH << EOF
6 > [ui]
6 > [ui]
7 > logtemplate= {rev}:{node|short} {desc|firstline}
7 > logtemplate= {rev}:{node|short} {desc|firstline}
8 > [phases]
8 > [phases]
9 > publish=False
9 > publish=False
10 > [experimental]
10 > [experimental]
11 > evolution=createmarkers,allowunstable
11 > evolution=createmarkers,allowunstable
12 > [extensions]
12 > [extensions]
13 > histedit=
13 > histedit=
14 > rebase=
14 > rebase=
15 > EOF
15 > EOF
16
16
17 $ hg init base
17 $ hg init base
18 $ cd base
18 $ cd base
19
19
20 $ for x in a b c d e f ; do
20 $ for x in a b c d e f ; do
21 > echo $x > $x
21 > echo $x > $x
22 > hg add $x
22 > hg add $x
23 > hg ci -m $x
23 > hg ci -m $x
24 > done
24 > done
25
25
26 $ hg log --graph
26 $ hg log --graph
27 @ 5:652413bf663e f
27 @ 5:652413bf663e f
28 |
28 |
29 o 4:e860deea161a e
29 o 4:e860deea161a e
30 |
30 |
31 o 3:055a42cdd887 d
31 o 3:055a42cdd887 d
32 |
32 |
33 o 2:177f92b77385 c
33 o 2:177f92b77385 c
34 |
34 |
35 o 1:d2ae7f538514 b
35 o 1:d2ae7f538514 b
36 |
36 |
37 o 0:cb9a9f314b8b a
37 o 0:cb9a9f314b8b a
38
38
39
39
40 $ HGEDITOR=cat hg histedit 1
40 $ HGEDITOR=cat hg histedit 1
41 pick d2ae7f538514 1 b
41 pick d2ae7f538514 1 b
42 pick 177f92b77385 2 c
42 pick 177f92b77385 2 c
43 pick 055a42cdd887 3 d
43 pick 055a42cdd887 3 d
44 pick e860deea161a 4 e
44 pick e860deea161a 4 e
45 pick 652413bf663e 5 f
45 pick 652413bf663e 5 f
46
46
47 # Edit history between d2ae7f538514 and 652413bf663e
47 # Edit history between d2ae7f538514 and 652413bf663e
48 #
48 #
49 # Commits are listed from least to most recent
49 # Commits are listed from least to most recent
50 #
50 #
51 # Commands:
51 # Commands:
52 #
52 #
53 # e, edit = use commit, but stop for amending
53 # e, edit = use commit, but stop for amending
54 # m, mess = edit commit message without changing commit content
54 # m, mess = edit commit message without changing commit content
55 # p, pick = use commit
55 # p, pick = use commit
56 # d, drop = remove commit from history
56 # d, drop = remove commit from history
57 # f, fold = use commit, but combine it with the one above
57 # f, fold = use commit, but combine it with the one above
58 # r, roll = like fold, but discard this commit's description
58 # r, roll = like fold, but discard this commit's description
59 #
59 #
60 $ hg histedit 1 --commands - --verbose <<EOF | grep histedit
60 $ hg histedit 1 --commands - --verbose <<EOF | grep histedit
61 > pick 177f92b77385 2 c
61 > pick 177f92b77385 2 c
62 > drop d2ae7f538514 1 b
62 > drop d2ae7f538514 1 b
63 > pick 055a42cdd887 3 d
63 > pick 055a42cdd887 3 d
64 > fold e860deea161a 4 e
64 > fold e860deea161a 4 e
65 > pick 652413bf663e 5 f
65 > pick 652413bf663e 5 f
66 > EOF
66 > EOF
67 [1]
67 [1]
68 $ hg log --graph --hidden
68 $ hg log --graph --hidden
69 @ 10:cacdfd884a93 f
69 @ 10:cacdfd884a93 f
70 |
70 |
71 o 9:59d9f330561f d
71 o 9:59d9f330561f d
72 |
72 |
73 | x 8:b558abc46d09 fold-temp-revision e860deea161a
73 | x 8:b558abc46d09 fold-temp-revision e860deea161a
74 | |
74 | |
75 | x 7:96e494a2d553 d
75 | x 7:96e494a2d553 d
76 |/
76 |/
77 o 6:b346ab9a313d c
77 o 6:b346ab9a313d c
78 |
78 |
79 | x 5:652413bf663e f
79 | x 5:652413bf663e f
80 | |
80 | |
81 | x 4:e860deea161a e
81 | x 4:e860deea161a e
82 | |
82 | |
83 | x 3:055a42cdd887 d
83 | x 3:055a42cdd887 d
84 | |
84 | |
85 | x 2:177f92b77385 c
85 | x 2:177f92b77385 c
86 | |
86 | |
87 | x 1:d2ae7f538514 b
87 | x 1:d2ae7f538514 b
88 |/
88 |/
89 o 0:cb9a9f314b8b a
89 o 0:cb9a9f314b8b a
90
90
91 $ hg debugobsolete
91 $ hg debugobsolete
92 96e494a2d553dd05902ba1cee1d94d4cb7b8faed 0 {b346ab9a313db8537ecf96fca3ca3ca984ef3bd7} (*) {'user': 'test'} (glob)
92 96e494a2d553dd05902ba1cee1d94d4cb7b8faed 0 {b346ab9a313db8537ecf96fca3ca3ca984ef3bd7} (*) {'user': 'test'} (glob)
93 b558abc46d09c30f57ac31e85a8a3d64d2e906e4 0 {96e494a2d553dd05902ba1cee1d94d4cb7b8faed} (*) {'user': 'test'} (glob)
93 b558abc46d09c30f57ac31e85a8a3d64d2e906e4 0 {96e494a2d553dd05902ba1cee1d94d4cb7b8faed} (*) {'user': 'test'} (glob)
94 d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b} (*) {'user': 'test'} (glob)
94 d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b} (*) {'user': 'test'} (glob)
95 177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (*) {'user': 'test'} (glob)
95 177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (*) {'user': 'test'} (glob)
96 055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
96 055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
97 e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
97 e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
98 652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (*) {'user': 'test'} (glob)
98 652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (*) {'user': 'test'} (glob)
99
99
100
100
101 Ensure hidden revision does not prevent histedit
101 Ensure hidden revision does not prevent histedit
102 -------------------------------------------------
102 -------------------------------------------------
103
103
104 create an hidden revision
104 create an hidden revision
105
105
106 $ hg histedit 6 --commands - << EOF
106 $ hg histedit 6 --commands - << EOF
107 > pick b346ab9a313d 6 c
107 > pick b346ab9a313d 6 c
108 > drop 59d9f330561f 7 d
108 > drop 59d9f330561f 7 d
109 > pick cacdfd884a93 8 f
109 > pick cacdfd884a93 8 f
110 > EOF
110 > EOF
111 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
111 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
112 $ hg log --graph
112 $ hg log --graph
113 @ 11:c13eb81022ca f
113 @ 11:c13eb81022ca f
114 |
114 |
115 o 6:b346ab9a313d c
115 o 6:b346ab9a313d c
116 |
116 |
117 o 0:cb9a9f314b8b a
117 o 0:cb9a9f314b8b a
118
118
119 check hidden revision are ignored (6 have hidden children 7 and 8)
119 check hidden revision are ignored (6 have hidden children 7 and 8)
120
120
121 $ hg histedit 6 --commands - << EOF
121 $ hg histedit 6 --commands - << EOF
122 > pick b346ab9a313d 6 c
122 > pick b346ab9a313d 6 c
123 > pick c13eb81022ca 8 f
123 > pick c13eb81022ca 8 f
124 > EOF
124 > EOF
125
125
126
126
127
127
128 Test that rewriting leaving instability behind is allowed
128 Test that rewriting leaving instability behind is allowed
129 ---------------------------------------------------------------------
129 ---------------------------------------------------------------------
130
130
131 $ hg up '.^'
131 $ hg up '.^'
132 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
132 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
133 $ hg log -r 'children(.)'
133 $ hg log -r 'children(.)'
134 11:c13eb81022ca f (no-eol)
134 11:c13eb81022ca f (no-eol)
135 $ hg histedit -r '.' --commands - <<EOF
135 $ hg histedit -r '.' --commands - <<EOF
136 > edit b346ab9a313d 6 c
136 > edit b346ab9a313d 6 c
137 > EOF
137 > EOF
138 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
138 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
139 adding c
139 adding c
140 Editing (b346ab9a313d), you may commit or record as needed now.
140 Editing (b346ab9a313d), you may commit or record as needed now.
141 (hg histedit --continue to resume)
141 (hg histedit --continue to resume)
142 [1]
142 [1]
143 $ echo c >> c
143 $ echo c >> c
144 $ hg histedit --continue
144 $ hg histedit --continue
145
145
146 $ hg log -r 'unstable()'
146 $ hg log -r 'unstable()'
147 11:c13eb81022ca f (no-eol)
147 11:c13eb81022ca f (no-eol)
148
148
149 stabilise
149 stabilise
150
150
151 $ hg rebase -r 'unstable()' -d .
151 $ hg rebase -r 'unstable()' -d .
152 rebasing 11:c13eb81022ca "f"
152 rebasing 11:c13eb81022ca "f"
153 $ hg up tip -q
153 $ hg up tip -q
154
154
155 check that extra has accumulated from histedit and rebase
156
157 $ hg log -T '{extras % "{key}={value}\n"}\n' -r tip
158 branch=default
159 histedit_source=cacdfd884a9321ec4e1de275ef3949fa953a1f83
160 rebase_source=c13eb81022caa686a369223fe7f926bc4f7db576
161
162
163 Test dropping of changeset on the top of the stack
155 Test dropping of changeset on the top of the stack
164 -------------------------------------------------------
156 -------------------------------------------------------
165
157
166 Nothing is rewritten below, the working directory parent must be change for the
158 Nothing is rewritten below, the working directory parent must be change for the
167 dropped changeset to be hidden.
159 dropped changeset to be hidden.
168
160
169 $ cd ..
161 $ cd ..
170 $ hg clone base droplast
162 $ hg clone base droplast
171 updating to branch default
163 updating to branch default
172 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
164 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
173 $ cd droplast
165 $ cd droplast
174 $ hg histedit -r '40db8afa467b' --commands - << EOF
166 $ hg histedit -r '40db8afa467b' --commands - << EOF
175 > pick 40db8afa467b 10 c
167 > pick 40db8afa467b 10 c
176 > drop 947ece25170f 11 f
168 > drop b449568bf7fc 11 f
177 > EOF
169 > EOF
178 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
170 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
179 $ hg log -G
171 $ hg log -G
180 @ 12:40db8afa467b c
172 @ 12:40db8afa467b c
181 |
173 |
182 o 0:cb9a9f314b8b a
174 o 0:cb9a9f314b8b a
183
175
184
176
185 With rewritten ancestors
177 With rewritten ancestors
186
178
187 $ echo e > e
179 $ echo e > e
188 $ hg add e
180 $ hg add e
189 $ hg commit -m g
181 $ hg commit -m g
190 $ echo f > f
182 $ echo f > f
191 $ hg add f
183 $ hg add f
192 $ hg commit -m h
184 $ hg commit -m h
193 $ hg histedit -r '40db8afa467b' --commands - << EOF
185 $ hg histedit -r '40db8afa467b' --commands - << EOF
194 > pick 47a8561c0449 12 g
186 > pick 47a8561c0449 12 g
195 > pick 40db8afa467b 10 c
187 > pick 40db8afa467b 10 c
196 > drop 1b3b05f35ff0 13 h
188 > drop 1b3b05f35ff0 13 h
197 > EOF
189 > EOF
198 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
190 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
199 $ hg log -G
191 $ hg log -G
200 @ 17:ee6544123ab8 c
192 @ 17:ee6544123ab8 c
201 |
193 |
202 o 16:269e713e9eae g
194 o 16:269e713e9eae g
203 |
195 |
204 o 0:cb9a9f314b8b a
196 o 0:cb9a9f314b8b a
205
197
206 $ cd ../base
198 $ cd ../base
207
199
208
200
209
201
210 Test phases support
202 Test phases support
211 ===========================================
203 ===========================================
212
204
213 Check that histedit respect immutability
205 Check that histedit respect immutability
214 -------------------------------------------
206 -------------------------------------------
215
207
216 $ cat >> $HGRCPATH << EOF
208 $ cat >> $HGRCPATH << EOF
217 > [ui]
209 > [ui]
218 > logtemplate= {rev}:{node|short} ({phase}) {desc|firstline}\n
210 > logtemplate= {rev}:{node|short} ({phase}) {desc|firstline}\n
219 > EOF
211 > EOF
220
212
221 $ hg ph -pv '.^'
213 $ hg ph -pv '.^'
222 phase changed for 2 changesets
214 phase changed for 2 changesets
223 $ hg log -G
215 $ hg log -G
224 @ 13:947ece25170f (draft) f
216 @ 13:b449568bf7fc (draft) f
225 |
217 |
226 o 12:40db8afa467b (public) c
218 o 12:40db8afa467b (public) c
227 |
219 |
228 o 0:cb9a9f314b8b (public) a
220 o 0:cb9a9f314b8b (public) a
229
221
230 $ hg histedit -r '.~2'
222 $ hg histedit -r '.~2'
231 abort: cannot edit public changeset: cb9a9f314b8b
223 abort: cannot edit public changeset: cb9a9f314b8b
232 (see "hg help phases" for details)
224 (see "hg help phases" for details)
233 [255]
225 [255]
234
226
235
227
236 Prepare further testing
228 Prepare further testing
237 -------------------------------------------
229 -------------------------------------------
238
230
239 $ for x in g h i j k ; do
231 $ for x in g h i j k ; do
240 > echo $x > $x
232 > echo $x > $x
241 > hg add $x
233 > hg add $x
242 > hg ci -m $x
234 > hg ci -m $x
243 > done
235 > done
244 $ hg phase --force --secret .~2
236 $ hg phase --force --secret .~2
245 $ hg log -G
237 $ hg log -G
246 @ 18:14bda137d5b3 (secret) k
238 @ 18:ee118ab9fa44 (secret) k
247 |
239 |
248 o 17:c62e7241a4f2 (secret) j
240 o 17:3a6c53ee7f3d (secret) j
249 |
241 |
250 o 16:9cd3934e05af (secret) i
242 o 16:b605fb7503f2 (secret) i
251 |
243 |
252 o 15:ee4a24fc4dfa (draft) h
244 o 15:7395e1ff83bd (draft) h
253 |
245 |
254 o 14:d22905de3528 (draft) g
246 o 14:6b70183d2492 (draft) g
255 |
247 |
256 o 13:947ece25170f (draft) f
248 o 13:b449568bf7fc (draft) f
257 |
249 |
258 o 12:40db8afa467b (public) c
250 o 12:40db8afa467b (public) c
259 |
251 |
260 o 0:cb9a9f314b8b (public) a
252 o 0:cb9a9f314b8b (public) a
261
253
262 $ cd ..
254 $ cd ..
263
255
264 simple phase conservation
256 simple phase conservation
265 -------------------------------------------
257 -------------------------------------------
266
258
267 Resulting changeset should conserve the phase of the original one whatever the
259 Resulting changeset should conserve the phase of the original one whatever the
268 phases.new-commit option is.
260 phases.new-commit option is.
269
261
270 New-commit as draft (default)
262 New-commit as draft (default)
271
263
272 $ cp -r base simple-draft
264 $ cp -r base simple-draft
273 $ cd simple-draft
265 $ cd simple-draft
274 $ hg histedit -r '947ece25170f' --commands - << EOF
266 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
275 > edit 947ece25170f 11 f
267 > edit b449568bf7fc 11 f
276 > pick d22905de3528 12 g
268 > pick 6b70183d2492 12 g
277 > pick ee4a24fc4dfa 13 h
269 > pick 7395e1ff83bd 13 h
278 > pick 9cd3934e05af 14 i
270 > pick b605fb7503f2 14 i
279 > pick c62e7241a4f2 15 j
271 > pick 3a6c53ee7f3d 15 j
280 > pick 14bda137d5b3 16 k
272 > pick ee118ab9fa44 16 k
281 > EOF
273 > EOF
282 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
274 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
283 adding f
275 adding f
284 Editing (947ece25170f), you may commit or record as needed now.
276 Editing (b449568bf7fc), you may commit or record as needed now.
285 (hg histedit --continue to resume)
277 (hg histedit --continue to resume)
286 [1]
278 [1]
287 $ echo f >> f
279 $ echo f >> f
288 $ hg histedit --continue
280 $ hg histedit --continue
289 $ hg log -G
281 $ hg log -G
290 @ 24:12925f763c90 (secret) k
282 @ 24:12e89af74238 (secret) k
291 |
283 |
292 o 23:4545a6e77442 (secret) j
284 o 23:636a8687b22e (secret) j
293 |
285 |
294 o 22:d947a0798e76 (secret) i
286 o 22:ccaf0a38653f (secret) i
295 |
287 |
296 o 21:28fb35ae4ebb (draft) h
288 o 21:11a89d1c2613 (draft) h
297 |
289 |
298 o 20:10b22a5a9645 (draft) g
290 o 20:c1dec7ca82ea (draft) g
299 |
291 |
300 o 19:c5a1db4a69f5 (draft) f
292 o 19:087281e68428 (draft) f
301 |
293 |
302 o 12:40db8afa467b (public) c
294 o 12:40db8afa467b (public) c
303 |
295 |
304 o 0:cb9a9f314b8b (public) a
296 o 0:cb9a9f314b8b (public) a
305
297
306 $ cd ..
298 $ cd ..
307
299
308
300
309 New-commit as draft (default)
301 New-commit as draft (default)
310
302
311 $ cp -r base simple-secret
303 $ cp -r base simple-secret
312 $ cd simple-secret
304 $ cd simple-secret
313 $ cat >> .hg/hgrc << EOF
305 $ cat >> .hg/hgrc << EOF
314 > [phases]
306 > [phases]
315 > new-commit=secret
307 > new-commit=secret
316 > EOF
308 > EOF
317 $ hg histedit -r '947ece25170f' --commands - << EOF
309 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
318 > edit 947ece25170f 11 f
310 > edit b449568bf7fc 11 f
319 > pick d22905de3528 12 g
311 > pick 6b70183d2492 12 g
320 > pick ee4a24fc4dfa 13 h
312 > pick 7395e1ff83bd 13 h
321 > pick 9cd3934e05af 14 i
313 > pick b605fb7503f2 14 i
322 > pick c62e7241a4f2 15 j
314 > pick 3a6c53ee7f3d 15 j
323 > pick 14bda137d5b3 16 k
315 > pick ee118ab9fa44 16 k
324 > EOF
316 > EOF
325 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
317 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
326 adding f
318 adding f
327 Editing (947ece25170f), you may commit or record as needed now.
319 Editing (b449568bf7fc), you may commit or record as needed now.
328 (hg histedit --continue to resume)
320 (hg histedit --continue to resume)
329 [1]
321 [1]
330 $ echo f >> f
322 $ echo f >> f
331 $ hg histedit --continue
323 $ hg histedit --continue
332 $ hg log -G
324 $ hg log -G
333 @ 24:12925f763c90 (secret) k
325 @ 24:12e89af74238 (secret) k
334 |
326 |
335 o 23:4545a6e77442 (secret) j
327 o 23:636a8687b22e (secret) j
336 |
328 |
337 o 22:d947a0798e76 (secret) i
329 o 22:ccaf0a38653f (secret) i
338 |
330 |
339 o 21:28fb35ae4ebb (draft) h
331 o 21:11a89d1c2613 (draft) h
340 |
332 |
341 o 20:10b22a5a9645 (draft) g
333 o 20:c1dec7ca82ea (draft) g
342 |
334 |
343 o 19:c5a1db4a69f5 (draft) f
335 o 19:087281e68428 (draft) f
344 |
336 |
345 o 12:40db8afa467b (public) c
337 o 12:40db8afa467b (public) c
346 |
338 |
347 o 0:cb9a9f314b8b (public) a
339 o 0:cb9a9f314b8b (public) a
348
340
349 $ cd ..
341 $ cd ..
350
342
351
343
352 Changeset reordering
344 Changeset reordering
353 -------------------------------------------
345 -------------------------------------------
354
346
355 If a secret changeset is put before a draft one, all descendant should be secret.
347 If a secret changeset is put before a draft one, all descendant should be secret.
356 It seems more important to present the secret phase.
348 It seems more important to present the secret phase.
357
349
358 $ cp -r base reorder
350 $ cp -r base reorder
359 $ cd reorder
351 $ cd reorder
360 $ hg histedit -r '947ece25170f' --commands - << EOF
352 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
361 > pick 947ece25170f 11 f
353 > pick b449568bf7fc 11 f
362 > pick c62e7241a4f2 15 j
354 > pick 3a6c53ee7f3d 15 j
363 > pick d22905de3528 12 g
355 > pick 6b70183d2492 12 g
364 > pick 9cd3934e05af 14 i
356 > pick b605fb7503f2 14 i
365 > pick ee4a24fc4dfa 13 h
357 > pick 7395e1ff83bd 13 h
366 > pick 14bda137d5b3 16 k
358 > pick ee118ab9fa44 16 k
367 > EOF
359 > EOF
368 0 files updated, 0 files merged, 5 files removed, 0 files unresolved
360 0 files updated, 0 files merged, 5 files removed, 0 files unresolved
369 $ hg log -G
361 $ hg log -G
370 @ 23:9e712162b2c1 (secret) k
362 @ 23:558246857888 (secret) k
371 |
363 |
372 o 22:490861543602 (secret) h
364 o 22:28bd44768535 (secret) h
373 |
365 |
374 o 21:86aeda50b70d (secret) i
366 o 21:d5395202aeb9 (secret) i
375 |
367 |
376 o 20:b2fa360bc090 (secret) g
368 o 20:21edda8e341b (secret) g
377 |
369 |
378 o 19:e10fb4e3eb8e (secret) j
370 o 19:5ab64f3a4832 (secret) j
379 |
371 |
380 o 13:947ece25170f (draft) f
372 o 13:b449568bf7fc (draft) f
381 |
373 |
382 o 12:40db8afa467b (public) c
374 o 12:40db8afa467b (public) c
383 |
375 |
384 o 0:cb9a9f314b8b (public) a
376 o 0:cb9a9f314b8b (public) a
385
377
386 $ cd ..
378 $ cd ..
387
379
388 Changeset folding
380 Changeset folding
389 -------------------------------------------
381 -------------------------------------------
390
382
391 Folding a secret changeset with a draft one turn the result secret (again,
383 Folding a secret changeset with a draft one turn the result secret (again,
392 better safe than sorry). Folding between same phase changeset still works
384 better safe than sorry). Folding between same phase changeset still works
393
385
394 Note that there is a few reordering in this series for more extensive test
386 Note that there is a few reordering in this series for more extensive test
395
387
396 $ cp -r base folding
388 $ cp -r base folding
397 $ cd folding
389 $ cd folding
398 $ cat >> .hg/hgrc << EOF
390 $ cat >> .hg/hgrc << EOF
399 > [phases]
391 > [phases]
400 > new-commit=secret
392 > new-commit=secret
401 > EOF
393 > EOF
402 $ hg histedit -r '947ece25170f' --commands - << EOF
394 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
403 > pick ee4a24fc4dfa 13 h
395 > pick 7395e1ff83bd 13 h
404 > fold 947ece25170f 11 f
396 > fold b449568bf7fc 11 f
405 > pick d22905de3528 12 g
397 > pick 6b70183d2492 12 g
406 > fold c62e7241a4f2 15 j
398 > fold 3a6c53ee7f3d 15 j
407 > pick 9cd3934e05af 14 i
399 > pick b605fb7503f2 14 i
408 > fold 14bda137d5b3 16 k
400 > fold ee118ab9fa44 16 k
409 > EOF
401 > EOF
410 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
402 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
411 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
403 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
412 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
404 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
413 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
405 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
414 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
406 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
415 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
407 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
416 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
408 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
417 $ hg log -G
409 $ hg log -G
418 @ 27:769e8ee8708e (secret) i
410 @ 27:f9daec13fb98 (secret) i
419 |
411 |
420 o 24:3de6dbab1b62 (secret) g
412 o 24:49807617f46a (secret) g
421 |
413 |
422 o 21:1d51647632b2 (draft) h
414 o 21:050280826e04 (draft) h
423 |
415 |
424 o 12:40db8afa467b (public) c
416 o 12:40db8afa467b (public) c
425 |
417 |
426 o 0:cb9a9f314b8b (public) a
418 o 0:cb9a9f314b8b (public) a
427
419
428 $ hg co 3de6dbab1b62
420 $ hg co 49807617f46a
429 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
421 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
430 $ echo wat >> wat
422 $ echo wat >> wat
431 $ hg add wat
423 $ hg add wat
432 $ hg ci -m 'add wat'
424 $ hg ci -m 'add wat'
433 created new head
425 created new head
434 $ hg merge 769e8ee8708e
426 $ hg merge f9daec13fb98
435 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
427 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
436 (branch merge, don't forget to commit)
428 (branch merge, don't forget to commit)
437 $ hg ci -m 'merge'
429 $ hg ci -m 'merge'
438 $ echo not wat > wat
430 $ echo not wat > wat
439 $ hg ci -m 'modify wat'
431 $ hg ci -m 'modify wat'
440 $ hg histedit 1d51647632b2
432 $ hg histedit 050280826e04
441 abort: cannot edit history that contains merges
433 abort: cannot edit history that contains merges
442 [255]
434 [255]
443 $ cd ..
435 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now