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