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