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