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