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