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