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