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