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