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