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