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