##// END OF EJS Templates
rebase: hide help for revisions.Predicates._destautoorphanrebase
timeless -
r42485:80a213f9 default
parent child Browse files
Show More
@@ -1,1954 +1,1956 b''
1 # rebase.py - rebasing feature for mercurial
1 # rebase.py - rebasing feature for mercurial
2 #
2 #
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to move sets of revisions to a different ancestor
8 '''command to move sets of revisions to a different ancestor
9
9
10 This extension lets you rebase changesets in an existing Mercurial
10 This extension lets you rebase changesets in an existing Mercurial
11 repository.
11 repository.
12
12
13 For more information:
13 For more information:
14 https://mercurial-scm.org/wiki/RebaseExtension
14 https://mercurial-scm.org/wiki/RebaseExtension
15 '''
15 '''
16
16
17 from __future__ import absolute_import
17 from __future__ import absolute_import
18
18
19 import errno
19 import errno
20 import os
20 import os
21
21
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23 from mercurial.node import (
23 from mercurial.node import (
24 nullrev,
24 nullrev,
25 short,
25 short,
26 )
26 )
27 from mercurial import (
27 from mercurial import (
28 bookmarks,
28 bookmarks,
29 cmdutil,
29 cmdutil,
30 commands,
30 commands,
31 copies,
31 copies,
32 destutil,
32 destutil,
33 dirstateguard,
33 dirstateguard,
34 error,
34 error,
35 extensions,
35 extensions,
36 hg,
36 hg,
37 merge as mergemod,
37 merge as mergemod,
38 mergeutil,
38 mergeutil,
39 obsolete,
39 obsolete,
40 obsutil,
40 obsutil,
41 patch,
41 patch,
42 phases,
42 phases,
43 pycompat,
43 pycompat,
44 registrar,
44 registrar,
45 repair,
45 repair,
46 revset,
46 revset,
47 revsetlang,
47 revsetlang,
48 scmutil,
48 scmutil,
49 smartset,
49 smartset,
50 state as statemod,
50 state as statemod,
51 util,
51 util,
52 )
52 )
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 revtodostr = '-1'
59 revtodostr = '-1'
60
60
61 # legacy revstates no longer needed in current code
61 # legacy revstates no longer needed in current code
62 # -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
62 # -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
63 legacystates = {'-2', '-3', '-4', '-5'}
63 legacystates = {'-2', '-3', '-4', '-5'}
64
64
65 cmdtable = {}
65 cmdtable = {}
66 command = registrar.command(cmdtable)
66 command = registrar.command(cmdtable)
67 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
67 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
68 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
68 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
69 # be specifying the version(s) of Mercurial they are tested with, or
69 # be specifying the version(s) of Mercurial they are tested with, or
70 # leave the attribute unspecified.
70 # leave the attribute unspecified.
71 testedwith = 'ships-with-hg-core'
71 testedwith = 'ships-with-hg-core'
72
72
73 def _nothingtorebase():
73 def _nothingtorebase():
74 return 1
74 return 1
75
75
76 def _savegraft(ctx, extra):
76 def _savegraft(ctx, extra):
77 s = ctx.extra().get('source', None)
77 s = ctx.extra().get('source', None)
78 if s is not None:
78 if s is not None:
79 extra['source'] = s
79 extra['source'] = s
80 s = ctx.extra().get('intermediate-source', None)
80 s = ctx.extra().get('intermediate-source', None)
81 if s is not None:
81 if s is not None:
82 extra['intermediate-source'] = s
82 extra['intermediate-source'] = s
83
83
84 def _savebranch(ctx, extra):
84 def _savebranch(ctx, extra):
85 extra['branch'] = ctx.branch()
85 extra['branch'] = ctx.branch()
86
86
87 def _destrebase(repo, sourceset, destspace=None):
87 def _destrebase(repo, sourceset, destspace=None):
88 """small wrapper around destmerge to pass the right extra args
88 """small wrapper around destmerge to pass the right extra args
89
89
90 Please wrap destutil.destmerge instead."""
90 Please wrap destutil.destmerge instead."""
91 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
91 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
92 onheadcheck=False, destspace=destspace)
92 onheadcheck=False, destspace=destspace)
93
93
94 revsetpredicate = registrar.revsetpredicate()
94 revsetpredicate = registrar.revsetpredicate()
95
95
96 @revsetpredicate('_destrebase')
96 @revsetpredicate('_destrebase')
97 def _revsetdestrebase(repo, subset, x):
97 def _revsetdestrebase(repo, subset, x):
98 # ``_rebasedefaultdest()``
98 # ``_rebasedefaultdest()``
99
99
100 # default destination for rebase.
100 # default destination for rebase.
101 # # XXX: Currently private because I expect the signature to change.
101 # # XXX: Currently private because I expect the signature to change.
102 # # XXX: - bailing out in case of ambiguity vs returning all data.
102 # # XXX: - bailing out in case of ambiguity vs returning all data.
103 # i18n: "_rebasedefaultdest" is a keyword
103 # i18n: "_rebasedefaultdest" is a keyword
104 sourceset = None
104 sourceset = None
105 if x is not None:
105 if x is not None:
106 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
106 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
107 return subset & smartset.baseset([_destrebase(repo, sourceset)])
107 return subset & smartset.baseset([_destrebase(repo, sourceset)])
108
108
109 @revsetpredicate('_destautoorphanrebase')
109 @revsetpredicate('_destautoorphanrebase')
110 def _revsetdestautoorphanrebase(repo, subset, x):
110 def _revsetdestautoorphanrebase(repo, subset, x):
111 """automatic rebase destination for a single orphan revision"""
111 # ``_destautoorphanrebase()``
112
113 # automatic rebase destination for a single orphan revision.
112 unfi = repo.unfiltered()
114 unfi = repo.unfiltered()
113 obsoleted = unfi.revs('obsolete()')
115 obsoleted = unfi.revs('obsolete()')
114
116
115 src = revset.getset(repo, subset, x).first()
117 src = revset.getset(repo, subset, x).first()
116
118
117 # Empty src or already obsoleted - Do not return a destination
119 # Empty src or already obsoleted - Do not return a destination
118 if not src or src in obsoleted:
120 if not src or src in obsoleted:
119 return smartset.baseset()
121 return smartset.baseset()
120 dests = destutil.orphanpossibledestination(repo, src)
122 dests = destutil.orphanpossibledestination(repo, src)
121 if len(dests) > 1:
123 if len(dests) > 1:
122 raise error.Abort(
124 raise error.Abort(
123 _("ambiguous automatic rebase: %r could end up on any of %r") % (
125 _("ambiguous automatic rebase: %r could end up on any of %r") % (
124 src, dests))
126 src, dests))
125 # We have zero or one destination, so we can just return here.
127 # We have zero or one destination, so we can just return here.
126 return smartset.baseset(dests)
128 return smartset.baseset(dests)
127
129
128 def _ctxdesc(ctx):
130 def _ctxdesc(ctx):
129 """short description for a context"""
131 """short description for a context"""
130 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
132 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
131 ctx.description().split('\n', 1)[0])
133 ctx.description().split('\n', 1)[0])
132 repo = ctx.repo()
134 repo = ctx.repo()
133 names = []
135 names = []
134 for nsname, ns in repo.names.iteritems():
136 for nsname, ns in repo.names.iteritems():
135 if nsname == 'branches':
137 if nsname == 'branches':
136 continue
138 continue
137 names.extend(ns.names(repo, ctx.node()))
139 names.extend(ns.names(repo, ctx.node()))
138 if names:
140 if names:
139 desc += ' (%s)' % ' '.join(names)
141 desc += ' (%s)' % ' '.join(names)
140 return desc
142 return desc
141
143
142 class rebaseruntime(object):
144 class rebaseruntime(object):
143 """This class is a container for rebase runtime state"""
145 """This class is a container for rebase runtime state"""
144 def __init__(self, repo, ui, inmemory=False, opts=None):
146 def __init__(self, repo, ui, inmemory=False, opts=None):
145 if opts is None:
147 if opts is None:
146 opts = {}
148 opts = {}
147
149
148 # prepared: whether we have rebasestate prepared or not. Currently it
150 # prepared: whether we have rebasestate prepared or not. Currently it
149 # decides whether "self.repo" is unfiltered or not.
151 # decides whether "self.repo" is unfiltered or not.
150 # The rebasestate has explicit hash to hash instructions not depending
152 # The rebasestate has explicit hash to hash instructions not depending
151 # on visibility. If rebasestate exists (in-memory or on-disk), use
153 # on visibility. If rebasestate exists (in-memory or on-disk), use
152 # unfiltered repo to avoid visibility issues.
154 # unfiltered repo to avoid visibility issues.
153 # Before knowing rebasestate (i.e. when starting a new rebase (not
155 # Before knowing rebasestate (i.e. when starting a new rebase (not
154 # --continue or --abort)), the original repo should be used so
156 # --continue or --abort)), the original repo should be used so
155 # visibility-dependent revsets are correct.
157 # visibility-dependent revsets are correct.
156 self.prepared = False
158 self.prepared = False
157 self._repo = repo
159 self._repo = repo
158
160
159 self.ui = ui
161 self.ui = ui
160 self.opts = opts
162 self.opts = opts
161 self.originalwd = None
163 self.originalwd = None
162 self.external = nullrev
164 self.external = nullrev
163 # Mapping between the old revision id and either what is the new rebased
165 # Mapping between the old revision id and either what is the new rebased
164 # revision or what needs to be done with the old revision. The state
166 # revision or what needs to be done with the old revision. The state
165 # dict will be what contains most of the rebase progress state.
167 # dict will be what contains most of the rebase progress state.
166 self.state = {}
168 self.state = {}
167 self.activebookmark = None
169 self.activebookmark = None
168 self.destmap = {}
170 self.destmap = {}
169 self.skipped = set()
171 self.skipped = set()
170
172
171 self.collapsef = opts.get('collapse', False)
173 self.collapsef = opts.get('collapse', False)
172 self.collapsemsg = cmdutil.logmessage(ui, opts)
174 self.collapsemsg = cmdutil.logmessage(ui, opts)
173 self.date = opts.get('date', None)
175 self.date = opts.get('date', None)
174
176
175 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
177 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
176 self.extrafns = [_savegraft]
178 self.extrafns = [_savegraft]
177 if e:
179 if e:
178 self.extrafns = [e]
180 self.extrafns = [e]
179
181
180 self.backupf = ui.configbool('rewrite', 'backup-bundle')
182 self.backupf = ui.configbool('rewrite', 'backup-bundle')
181 self.keepf = opts.get('keep', False)
183 self.keepf = opts.get('keep', False)
182 self.keepbranchesf = opts.get('keepbranches', False)
184 self.keepbranchesf = opts.get('keepbranches', False)
183 self.obsoletenotrebased = {}
185 self.obsoletenotrebased = {}
184 self.obsoletewithoutsuccessorindestination = set()
186 self.obsoletewithoutsuccessorindestination = set()
185 self.inmemory = inmemory
187 self.inmemory = inmemory
186 self.stateobj = statemod.cmdstate(repo, 'rebasestate')
188 self.stateobj = statemod.cmdstate(repo, 'rebasestate')
187
189
188 @property
190 @property
189 def repo(self):
191 def repo(self):
190 if self.prepared:
192 if self.prepared:
191 return self._repo.unfiltered()
193 return self._repo.unfiltered()
192 else:
194 else:
193 return self._repo
195 return self._repo
194
196
195 def storestatus(self, tr=None):
197 def storestatus(self, tr=None):
196 """Store the current status to allow recovery"""
198 """Store the current status to allow recovery"""
197 if tr:
199 if tr:
198 tr.addfilegenerator('rebasestate', ('rebasestate',),
200 tr.addfilegenerator('rebasestate', ('rebasestate',),
199 self._writestatus, location='plain')
201 self._writestatus, location='plain')
200 else:
202 else:
201 with self.repo.vfs("rebasestate", "w") as f:
203 with self.repo.vfs("rebasestate", "w") as f:
202 self._writestatus(f)
204 self._writestatus(f)
203
205
204 def _writestatus(self, f):
206 def _writestatus(self, f):
205 repo = self.repo
207 repo = self.repo
206 assert repo.filtername is None
208 assert repo.filtername is None
207 f.write(repo[self.originalwd].hex() + '\n')
209 f.write(repo[self.originalwd].hex() + '\n')
208 # was "dest". we now write dest per src root below.
210 # was "dest". we now write dest per src root below.
209 f.write('\n')
211 f.write('\n')
210 f.write(repo[self.external].hex() + '\n')
212 f.write(repo[self.external].hex() + '\n')
211 f.write('%d\n' % int(self.collapsef))
213 f.write('%d\n' % int(self.collapsef))
212 f.write('%d\n' % int(self.keepf))
214 f.write('%d\n' % int(self.keepf))
213 f.write('%d\n' % int(self.keepbranchesf))
215 f.write('%d\n' % int(self.keepbranchesf))
214 f.write('%s\n' % (self.activebookmark or ''))
216 f.write('%s\n' % (self.activebookmark or ''))
215 destmap = self.destmap
217 destmap = self.destmap
216 for d, v in self.state.iteritems():
218 for d, v in self.state.iteritems():
217 oldrev = repo[d].hex()
219 oldrev = repo[d].hex()
218 if v >= 0:
220 if v >= 0:
219 newrev = repo[v].hex()
221 newrev = repo[v].hex()
220 else:
222 else:
221 newrev = "%d" % v
223 newrev = "%d" % v
222 destnode = repo[destmap[d]].hex()
224 destnode = repo[destmap[d]].hex()
223 f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
225 f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
224 repo.ui.debug('rebase status stored\n')
226 repo.ui.debug('rebase status stored\n')
225
227
226 def restorestatus(self):
228 def restorestatus(self):
227 """Restore a previously stored status"""
229 """Restore a previously stored status"""
228 if not self.stateobj.exists():
230 if not self.stateobj.exists():
229 cmdutil.wrongtooltocontinue(self.repo, _('rebase'))
231 cmdutil.wrongtooltocontinue(self.repo, _('rebase'))
230
232
231 data = self._read()
233 data = self._read()
232 self.repo.ui.debug('rebase status resumed\n')
234 self.repo.ui.debug('rebase status resumed\n')
233
235
234 self.originalwd = data['originalwd']
236 self.originalwd = data['originalwd']
235 self.destmap = data['destmap']
237 self.destmap = data['destmap']
236 self.state = data['state']
238 self.state = data['state']
237 self.skipped = data['skipped']
239 self.skipped = data['skipped']
238 self.collapsef = data['collapse']
240 self.collapsef = data['collapse']
239 self.keepf = data['keep']
241 self.keepf = data['keep']
240 self.keepbranchesf = data['keepbranches']
242 self.keepbranchesf = data['keepbranches']
241 self.external = data['external']
243 self.external = data['external']
242 self.activebookmark = data['activebookmark']
244 self.activebookmark = data['activebookmark']
243
245
244 def _read(self):
246 def _read(self):
245 self.prepared = True
247 self.prepared = True
246 repo = self.repo
248 repo = self.repo
247 assert repo.filtername is None
249 assert repo.filtername is None
248 data = {'keepbranches': None, 'collapse': None, 'activebookmark': None,
250 data = {'keepbranches': None, 'collapse': None, 'activebookmark': None,
249 'external': nullrev, 'keep': None, 'originalwd': None}
251 'external': nullrev, 'keep': None, 'originalwd': None}
250 legacydest = None
252 legacydest = None
251 state = {}
253 state = {}
252 destmap = {}
254 destmap = {}
253
255
254 if True:
256 if True:
255 f = repo.vfs("rebasestate")
257 f = repo.vfs("rebasestate")
256 for i, l in enumerate(f.read().splitlines()):
258 for i, l in enumerate(f.read().splitlines()):
257 if i == 0:
259 if i == 0:
258 data['originalwd'] = repo[l].rev()
260 data['originalwd'] = repo[l].rev()
259 elif i == 1:
261 elif i == 1:
260 # this line should be empty in newer version. but legacy
262 # this line should be empty in newer version. but legacy
261 # clients may still use it
263 # clients may still use it
262 if l:
264 if l:
263 legacydest = repo[l].rev()
265 legacydest = repo[l].rev()
264 elif i == 2:
266 elif i == 2:
265 data['external'] = repo[l].rev()
267 data['external'] = repo[l].rev()
266 elif i == 3:
268 elif i == 3:
267 data['collapse'] = bool(int(l))
269 data['collapse'] = bool(int(l))
268 elif i == 4:
270 elif i == 4:
269 data['keep'] = bool(int(l))
271 data['keep'] = bool(int(l))
270 elif i == 5:
272 elif i == 5:
271 data['keepbranches'] = bool(int(l))
273 data['keepbranches'] = bool(int(l))
272 elif i == 6 and not (len(l) == 81 and ':' in l):
274 elif i == 6 and not (len(l) == 81 and ':' in l):
273 # line 6 is a recent addition, so for backwards
275 # line 6 is a recent addition, so for backwards
274 # compatibility check that the line doesn't look like the
276 # compatibility check that the line doesn't look like the
275 # oldrev:newrev lines
277 # oldrev:newrev lines
276 data['activebookmark'] = l
278 data['activebookmark'] = l
277 else:
279 else:
278 args = l.split(':')
280 args = l.split(':')
279 oldrev = repo[args[0]].rev()
281 oldrev = repo[args[0]].rev()
280 newrev = args[1]
282 newrev = args[1]
281 if newrev in legacystates:
283 if newrev in legacystates:
282 continue
284 continue
283 if len(args) > 2:
285 if len(args) > 2:
284 destrev = repo[args[2]].rev()
286 destrev = repo[args[2]].rev()
285 else:
287 else:
286 destrev = legacydest
288 destrev = legacydest
287 destmap[oldrev] = destrev
289 destmap[oldrev] = destrev
288 if newrev == revtodostr:
290 if newrev == revtodostr:
289 state[oldrev] = revtodo
291 state[oldrev] = revtodo
290 # Legacy compat special case
292 # Legacy compat special case
291 else:
293 else:
292 state[oldrev] = repo[newrev].rev()
294 state[oldrev] = repo[newrev].rev()
293
295
294 if data['keepbranches'] is None:
296 if data['keepbranches'] is None:
295 raise error.Abort(_('.hg/rebasestate is incomplete'))
297 raise error.Abort(_('.hg/rebasestate is incomplete'))
296
298
297 data['destmap'] = destmap
299 data['destmap'] = destmap
298 data['state'] = state
300 data['state'] = state
299 skipped = set()
301 skipped = set()
300 # recompute the set of skipped revs
302 # recompute the set of skipped revs
301 if not data['collapse']:
303 if not data['collapse']:
302 seen = set(destmap.values())
304 seen = set(destmap.values())
303 for old, new in sorted(state.items()):
305 for old, new in sorted(state.items()):
304 if new != revtodo and new in seen:
306 if new != revtodo and new in seen:
305 skipped.add(old)
307 skipped.add(old)
306 seen.add(new)
308 seen.add(new)
307 data['skipped'] = skipped
309 data['skipped'] = skipped
308 repo.ui.debug('computed skipped revs: %s\n' %
310 repo.ui.debug('computed skipped revs: %s\n' %
309 (' '.join('%d' % r for r in sorted(skipped)) or ''))
311 (' '.join('%d' % r for r in sorted(skipped)) or ''))
310
312
311 return data
313 return data
312
314
313 def _handleskippingobsolete(self, obsoleterevs, destmap):
315 def _handleskippingobsolete(self, obsoleterevs, destmap):
314 """Compute structures necessary for skipping obsolete revisions
316 """Compute structures necessary for skipping obsolete revisions
315
317
316 obsoleterevs: iterable of all obsolete revisions in rebaseset
318 obsoleterevs: iterable of all obsolete revisions in rebaseset
317 destmap: {srcrev: destrev} destination revisions
319 destmap: {srcrev: destrev} destination revisions
318 """
320 """
319 self.obsoletenotrebased = {}
321 self.obsoletenotrebased = {}
320 if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
322 if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
321 return
323 return
322 obsoleteset = set(obsoleterevs)
324 obsoleteset = set(obsoleterevs)
323 (self.obsoletenotrebased,
325 (self.obsoletenotrebased,
324 self.obsoletewithoutsuccessorindestination,
326 self.obsoletewithoutsuccessorindestination,
325 obsoleteextinctsuccessors) = _computeobsoletenotrebased(
327 obsoleteextinctsuccessors) = _computeobsoletenotrebased(
326 self.repo, obsoleteset, destmap)
328 self.repo, obsoleteset, destmap)
327 skippedset = set(self.obsoletenotrebased)
329 skippedset = set(self.obsoletenotrebased)
328 skippedset.update(self.obsoletewithoutsuccessorindestination)
330 skippedset.update(self.obsoletewithoutsuccessorindestination)
329 skippedset.update(obsoleteextinctsuccessors)
331 skippedset.update(obsoleteextinctsuccessors)
330 _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
332 _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
331
333
332 def _prepareabortorcontinue(self, isabort, backup=True, suppwarns=False):
334 def _prepareabortorcontinue(self, isabort, backup=True, suppwarns=False):
333 try:
335 try:
334 self.restorestatus()
336 self.restorestatus()
335 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
337 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
336 except error.RepoLookupError:
338 except error.RepoLookupError:
337 if isabort:
339 if isabort:
338 clearstatus(self.repo)
340 clearstatus(self.repo)
339 clearcollapsemsg(self.repo)
341 clearcollapsemsg(self.repo)
340 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
342 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
341 ' only broken state is cleared)\n'))
343 ' only broken state is cleared)\n'))
342 return 0
344 return 0
343 else:
345 else:
344 msg = _('cannot continue inconsistent rebase')
346 msg = _('cannot continue inconsistent rebase')
345 hint = _('use "hg rebase --abort" to clear broken state')
347 hint = _('use "hg rebase --abort" to clear broken state')
346 raise error.Abort(msg, hint=hint)
348 raise error.Abort(msg, hint=hint)
347
349
348 if isabort:
350 if isabort:
349 backup = backup and self.backupf
351 backup = backup and self.backupf
350 return self._abort(backup=backup, suppwarns=suppwarns)
352 return self._abort(backup=backup, suppwarns=suppwarns)
351
353
352 def _preparenewrebase(self, destmap):
354 def _preparenewrebase(self, destmap):
353 if not destmap:
355 if not destmap:
354 return _nothingtorebase()
356 return _nothingtorebase()
355
357
356 rebaseset = destmap.keys()
358 rebaseset = destmap.keys()
357 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
359 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
358 if (not (self.keepf or allowunstable)
360 if (not (self.keepf or allowunstable)
359 and self.repo.revs('first(children(%ld) - %ld)',
361 and self.repo.revs('first(children(%ld) - %ld)',
360 rebaseset, rebaseset)):
362 rebaseset, rebaseset)):
361 raise error.Abort(
363 raise error.Abort(
362 _("can't remove original changesets with"
364 _("can't remove original changesets with"
363 " unrebased descendants"),
365 " unrebased descendants"),
364 hint=_('use --keep to keep original changesets'))
366 hint=_('use --keep to keep original changesets'))
365
367
366 result = buildstate(self.repo, destmap, self.collapsef)
368 result = buildstate(self.repo, destmap, self.collapsef)
367
369
368 if not result:
370 if not result:
369 # Empty state built, nothing to rebase
371 # Empty state built, nothing to rebase
370 self.ui.status(_('nothing to rebase\n'))
372 self.ui.status(_('nothing to rebase\n'))
371 return _nothingtorebase()
373 return _nothingtorebase()
372
374
373 for root in self.repo.set('roots(%ld)', rebaseset):
375 for root in self.repo.set('roots(%ld)', rebaseset):
374 if not self.keepf and not root.mutable():
376 if not self.keepf and not root.mutable():
375 raise error.Abort(_("can't rebase public changeset %s")
377 raise error.Abort(_("can't rebase public changeset %s")
376 % root,
378 % root,
377 hint=_("see 'hg help phases' for details"))
379 hint=_("see 'hg help phases' for details"))
378
380
379 (self.originalwd, self.destmap, self.state) = result
381 (self.originalwd, self.destmap, self.state) = result
380 if self.collapsef:
382 if self.collapsef:
381 dests = set(self.destmap.values())
383 dests = set(self.destmap.values())
382 if len(dests) != 1:
384 if len(dests) != 1:
383 raise error.Abort(
385 raise error.Abort(
384 _('--collapse does not work with multiple destinations'))
386 _('--collapse does not work with multiple destinations'))
385 destrev = next(iter(dests))
387 destrev = next(iter(dests))
386 destancestors = self.repo.changelog.ancestors([destrev],
388 destancestors = self.repo.changelog.ancestors([destrev],
387 inclusive=True)
389 inclusive=True)
388 self.external = externalparent(self.repo, self.state, destancestors)
390 self.external = externalparent(self.repo, self.state, destancestors)
389
391
390 for destrev in sorted(set(destmap.values())):
392 for destrev in sorted(set(destmap.values())):
391 dest = self.repo[destrev]
393 dest = self.repo[destrev]
392 if dest.closesbranch() and not self.keepbranchesf:
394 if dest.closesbranch() and not self.keepbranchesf:
393 self.ui.status(_('reopening closed branch head %s\n') % dest)
395 self.ui.status(_('reopening closed branch head %s\n') % dest)
394
396
395 self.prepared = True
397 self.prepared = True
396
398
397 def _assignworkingcopy(self):
399 def _assignworkingcopy(self):
398 if self.inmemory:
400 if self.inmemory:
399 from mercurial.context import overlayworkingctx
401 from mercurial.context import overlayworkingctx
400 self.wctx = overlayworkingctx(self.repo)
402 self.wctx = overlayworkingctx(self.repo)
401 self.repo.ui.debug("rebasing in-memory\n")
403 self.repo.ui.debug("rebasing in-memory\n")
402 else:
404 else:
403 self.wctx = self.repo[None]
405 self.wctx = self.repo[None]
404 self.repo.ui.debug("rebasing on disk\n")
406 self.repo.ui.debug("rebasing on disk\n")
405 self.repo.ui.log("rebase",
407 self.repo.ui.log("rebase",
406 "using in-memory rebase: %r\n", self.inmemory,
408 "using in-memory rebase: %r\n", self.inmemory,
407 rebase_imm_used=self.inmemory)
409 rebase_imm_used=self.inmemory)
408
410
409 def _performrebase(self, tr):
411 def _performrebase(self, tr):
410 self._assignworkingcopy()
412 self._assignworkingcopy()
411 repo, ui = self.repo, self.ui
413 repo, ui = self.repo, self.ui
412 if self.keepbranchesf:
414 if self.keepbranchesf:
413 # insert _savebranch at the start of extrafns so if
415 # insert _savebranch at the start of extrafns so if
414 # there's a user-provided extrafn it can clobber branch if
416 # there's a user-provided extrafn it can clobber branch if
415 # desired
417 # desired
416 self.extrafns.insert(0, _savebranch)
418 self.extrafns.insert(0, _savebranch)
417 if self.collapsef:
419 if self.collapsef:
418 branches = set()
420 branches = set()
419 for rev in self.state:
421 for rev in self.state:
420 branches.add(repo[rev].branch())
422 branches.add(repo[rev].branch())
421 if len(branches) > 1:
423 if len(branches) > 1:
422 raise error.Abort(_('cannot collapse multiple named '
424 raise error.Abort(_('cannot collapse multiple named '
423 'branches'))
425 'branches'))
424
426
425 # Calculate self.obsoletenotrebased
427 # Calculate self.obsoletenotrebased
426 obsrevs = _filterobsoleterevs(self.repo, self.state)
428 obsrevs = _filterobsoleterevs(self.repo, self.state)
427 self._handleskippingobsolete(obsrevs, self.destmap)
429 self._handleskippingobsolete(obsrevs, self.destmap)
428
430
429 # Keep track of the active bookmarks in order to reset them later
431 # Keep track of the active bookmarks in order to reset them later
430 self.activebookmark = self.activebookmark or repo._activebookmark
432 self.activebookmark = self.activebookmark or repo._activebookmark
431 if self.activebookmark:
433 if self.activebookmark:
432 bookmarks.deactivate(repo)
434 bookmarks.deactivate(repo)
433
435
434 # Store the state before we begin so users can run 'hg rebase --abort'
436 # Store the state before we begin so users can run 'hg rebase --abort'
435 # if we fail before the transaction closes.
437 # if we fail before the transaction closes.
436 self.storestatus()
438 self.storestatus()
437 if tr:
439 if tr:
438 # When using single transaction, store state when transaction
440 # When using single transaction, store state when transaction
439 # commits.
441 # commits.
440 self.storestatus(tr)
442 self.storestatus(tr)
441
443
442 cands = [k for k, v in self.state.iteritems() if v == revtodo]
444 cands = [k for k, v in self.state.iteritems() if v == revtodo]
443 p = repo.ui.makeprogress(_("rebasing"), unit=_('changesets'),
445 p = repo.ui.makeprogress(_("rebasing"), unit=_('changesets'),
444 total=len(cands))
446 total=len(cands))
445 def progress(ctx):
447 def progress(ctx):
446 p.increment(item=("%d:%s" % (ctx.rev(), ctx)))
448 p.increment(item=("%d:%s" % (ctx.rev(), ctx)))
447 allowdivergence = self.ui.configbool(
449 allowdivergence = self.ui.configbool(
448 'experimental', 'evolution.allowdivergence')
450 'experimental', 'evolution.allowdivergence')
449 for subset in sortsource(self.destmap):
451 for subset in sortsource(self.destmap):
450 sortedrevs = self.repo.revs('sort(%ld, -topo)', subset)
452 sortedrevs = self.repo.revs('sort(%ld, -topo)', subset)
451 if not allowdivergence:
453 if not allowdivergence:
452 sortedrevs -= self.repo.revs(
454 sortedrevs -= self.repo.revs(
453 'descendants(%ld) and not %ld',
455 'descendants(%ld) and not %ld',
454 self.obsoletewithoutsuccessorindestination,
456 self.obsoletewithoutsuccessorindestination,
455 self.obsoletewithoutsuccessorindestination,
457 self.obsoletewithoutsuccessorindestination,
456 )
458 )
457 for rev in sortedrevs:
459 for rev in sortedrevs:
458 self._rebasenode(tr, rev, allowdivergence, progress)
460 self._rebasenode(tr, rev, allowdivergence, progress)
459 p.complete()
461 p.complete()
460 ui.note(_('rebase merging completed\n'))
462 ui.note(_('rebase merging completed\n'))
461
463
462 def _concludenode(self, rev, p1, p2, editor, commitmsg=None):
464 def _concludenode(self, rev, p1, p2, editor, commitmsg=None):
463 '''Commit the wd changes with parents p1 and p2.
465 '''Commit the wd changes with parents p1 and p2.
464
466
465 Reuse commit info from rev but also store useful information in extra.
467 Reuse commit info from rev but also store useful information in extra.
466 Return node of committed revision.'''
468 Return node of committed revision.'''
467 repo = self.repo
469 repo = self.repo
468 ctx = repo[rev]
470 ctx = repo[rev]
469 if commitmsg is None:
471 if commitmsg is None:
470 commitmsg = ctx.description()
472 commitmsg = ctx.description()
471 date = self.date
473 date = self.date
472 if date is None:
474 if date is None:
473 date = ctx.date()
475 date = ctx.date()
474 extra = {'rebase_source': ctx.hex()}
476 extra = {'rebase_source': ctx.hex()}
475 for c in self.extrafns:
477 for c in self.extrafns:
476 c(ctx, extra)
478 c(ctx, extra)
477 keepbranch = self.keepbranchesf and repo[p1].branch() != ctx.branch()
479 keepbranch = self.keepbranchesf and repo[p1].branch() != ctx.branch()
478 destphase = max(ctx.phase(), phases.draft)
480 destphase = max(ctx.phase(), phases.draft)
479 overrides = {('phases', 'new-commit'): destphase}
481 overrides = {('phases', 'new-commit'): destphase}
480 if keepbranch:
482 if keepbranch:
481 overrides[('ui', 'allowemptycommit')] = True
483 overrides[('ui', 'allowemptycommit')] = True
482 with repo.ui.configoverride(overrides, 'rebase'):
484 with repo.ui.configoverride(overrides, 'rebase'):
483 if self.inmemory:
485 if self.inmemory:
484 newnode = commitmemorynode(repo, p1, p2,
486 newnode = commitmemorynode(repo, p1, p2,
485 wctx=self.wctx,
487 wctx=self.wctx,
486 extra=extra,
488 extra=extra,
487 commitmsg=commitmsg,
489 commitmsg=commitmsg,
488 editor=editor,
490 editor=editor,
489 user=ctx.user(),
491 user=ctx.user(),
490 date=date)
492 date=date)
491 mergemod.mergestate.clean(repo)
493 mergemod.mergestate.clean(repo)
492 else:
494 else:
493 newnode = commitnode(repo, p1, p2,
495 newnode = commitnode(repo, p1, p2,
494 extra=extra,
496 extra=extra,
495 commitmsg=commitmsg,
497 commitmsg=commitmsg,
496 editor=editor,
498 editor=editor,
497 user=ctx.user(),
499 user=ctx.user(),
498 date=date)
500 date=date)
499
501
500 if newnode is None:
502 if newnode is None:
501 # If it ended up being a no-op commit, then the normal
503 # If it ended up being a no-op commit, then the normal
502 # merge state clean-up path doesn't happen, so do it
504 # merge state clean-up path doesn't happen, so do it
503 # here. Fix issue5494
505 # here. Fix issue5494
504 mergemod.mergestate.clean(repo)
506 mergemod.mergestate.clean(repo)
505 return newnode
507 return newnode
506
508
507 def _rebasenode(self, tr, rev, allowdivergence, progressfn):
509 def _rebasenode(self, tr, rev, allowdivergence, progressfn):
508 repo, ui, opts = self.repo, self.ui, self.opts
510 repo, ui, opts = self.repo, self.ui, self.opts
509 dest = self.destmap[rev]
511 dest = self.destmap[rev]
510 ctx = repo[rev]
512 ctx = repo[rev]
511 desc = _ctxdesc(ctx)
513 desc = _ctxdesc(ctx)
512 if self.state[rev] == rev:
514 if self.state[rev] == rev:
513 ui.status(_('already rebased %s\n') % desc)
515 ui.status(_('already rebased %s\n') % desc)
514 elif (not allowdivergence
516 elif (not allowdivergence
515 and rev in self.obsoletewithoutsuccessorindestination):
517 and rev in self.obsoletewithoutsuccessorindestination):
516 msg = _('note: not rebasing %s and its descendants as '
518 msg = _('note: not rebasing %s and its descendants as '
517 'this would cause divergence\n') % desc
519 'this would cause divergence\n') % desc
518 repo.ui.status(msg)
520 repo.ui.status(msg)
519 self.skipped.add(rev)
521 self.skipped.add(rev)
520 elif rev in self.obsoletenotrebased:
522 elif rev in self.obsoletenotrebased:
521 succ = self.obsoletenotrebased[rev]
523 succ = self.obsoletenotrebased[rev]
522 if succ is None:
524 if succ is None:
523 msg = _('note: not rebasing %s, it has no '
525 msg = _('note: not rebasing %s, it has no '
524 'successor\n') % desc
526 'successor\n') % desc
525 else:
527 else:
526 succdesc = _ctxdesc(repo[succ])
528 succdesc = _ctxdesc(repo[succ])
527 msg = (_('note: not rebasing %s, already in '
529 msg = (_('note: not rebasing %s, already in '
528 'destination as %s\n') % (desc, succdesc))
530 'destination as %s\n') % (desc, succdesc))
529 repo.ui.status(msg)
531 repo.ui.status(msg)
530 # Make clearrebased aware state[rev] is not a true successor
532 # Make clearrebased aware state[rev] is not a true successor
531 self.skipped.add(rev)
533 self.skipped.add(rev)
532 # Record rev as moved to its desired destination in self.state.
534 # Record rev as moved to its desired destination in self.state.
533 # This helps bookmark and working parent movement.
535 # This helps bookmark and working parent movement.
534 dest = max(adjustdest(repo, rev, self.destmap, self.state,
536 dest = max(adjustdest(repo, rev, self.destmap, self.state,
535 self.skipped))
537 self.skipped))
536 self.state[rev] = dest
538 self.state[rev] = dest
537 elif self.state[rev] == revtodo:
539 elif self.state[rev] == revtodo:
538 ui.status(_('rebasing %s\n') % desc)
540 ui.status(_('rebasing %s\n') % desc)
539 progressfn(ctx)
541 progressfn(ctx)
540 p1, p2, base = defineparents(repo, rev, self.destmap,
542 p1, p2, base = defineparents(repo, rev, self.destmap,
541 self.state, self.skipped,
543 self.state, self.skipped,
542 self.obsoletenotrebased)
544 self.obsoletenotrebased)
543 if not self.inmemory and len(repo[None].parents()) == 2:
545 if not self.inmemory and len(repo[None].parents()) == 2:
544 repo.ui.debug('resuming interrupted rebase\n')
546 repo.ui.debug('resuming interrupted rebase\n')
545 else:
547 else:
546 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
548 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
547 with ui.configoverride(overrides, 'rebase'):
549 with ui.configoverride(overrides, 'rebase'):
548 stats = rebasenode(repo, rev, p1, base, self.collapsef,
550 stats = rebasenode(repo, rev, p1, base, self.collapsef,
549 dest, wctx=self.wctx)
551 dest, wctx=self.wctx)
550 if stats.unresolvedcount > 0:
552 if stats.unresolvedcount > 0:
551 if self.inmemory:
553 if self.inmemory:
552 raise error.InMemoryMergeConflictsError()
554 raise error.InMemoryMergeConflictsError()
553 else:
555 else:
554 raise error.InterventionRequired(
556 raise error.InterventionRequired(
555 _('unresolved conflicts (see hg '
557 _('unresolved conflicts (see hg '
556 'resolve, then hg rebase --continue)'))
558 'resolve, then hg rebase --continue)'))
557 if not self.collapsef:
559 if not self.collapsef:
558 merging = p2 != nullrev
560 merging = p2 != nullrev
559 editform = cmdutil.mergeeditform(merging, 'rebase')
561 editform = cmdutil.mergeeditform(merging, 'rebase')
560 editor = cmdutil.getcommiteditor(editform=editform,
562 editor = cmdutil.getcommiteditor(editform=editform,
561 **pycompat.strkwargs(opts))
563 **pycompat.strkwargs(opts))
562 newnode = self._concludenode(rev, p1, p2, editor)
564 newnode = self._concludenode(rev, p1, p2, editor)
563 else:
565 else:
564 # Skip commit if we are collapsing
566 # Skip commit if we are collapsing
565 if self.inmemory:
567 if self.inmemory:
566 self.wctx.setbase(repo[p1])
568 self.wctx.setbase(repo[p1])
567 else:
569 else:
568 repo.setparents(repo[p1].node())
570 repo.setparents(repo[p1].node())
569 newnode = None
571 newnode = None
570 # Update the state
572 # Update the state
571 if newnode is not None:
573 if newnode is not None:
572 self.state[rev] = repo[newnode].rev()
574 self.state[rev] = repo[newnode].rev()
573 ui.debug('rebased as %s\n' % short(newnode))
575 ui.debug('rebased as %s\n' % short(newnode))
574 else:
576 else:
575 if not self.collapsef:
577 if not self.collapsef:
576 ui.warn(_('note: not rebasing %s, its destination already '
578 ui.warn(_('note: not rebasing %s, its destination already '
577 'has all its changes\n') % desc)
579 'has all its changes\n') % desc)
578 self.skipped.add(rev)
580 self.skipped.add(rev)
579 self.state[rev] = p1
581 self.state[rev] = p1
580 ui.debug('next revision set to %d\n' % p1)
582 ui.debug('next revision set to %d\n' % p1)
581 else:
583 else:
582 ui.status(_('already rebased %s as %s\n') %
584 ui.status(_('already rebased %s as %s\n') %
583 (desc, repo[self.state[rev]]))
585 (desc, repo[self.state[rev]]))
584 if not tr:
586 if not tr:
585 # When not using single transaction, store state after each
587 # When not using single transaction, store state after each
586 # commit is completely done. On InterventionRequired, we thus
588 # commit is completely done. On InterventionRequired, we thus
587 # won't store the status. Instead, we'll hit the "len(parents) == 2"
589 # won't store the status. Instead, we'll hit the "len(parents) == 2"
588 # case and realize that the commit was in progress.
590 # case and realize that the commit was in progress.
589 self.storestatus()
591 self.storestatus()
590
592
591 def _finishrebase(self):
593 def _finishrebase(self):
592 repo, ui, opts = self.repo, self.ui, self.opts
594 repo, ui, opts = self.repo, self.ui, self.opts
593 fm = ui.formatter('rebase', opts)
595 fm = ui.formatter('rebase', opts)
594 fm.startitem()
596 fm.startitem()
595 if self.collapsef:
597 if self.collapsef:
596 p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
598 p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
597 self.state, self.skipped,
599 self.state, self.skipped,
598 self.obsoletenotrebased)
600 self.obsoletenotrebased)
599 editopt = opts.get('edit')
601 editopt = opts.get('edit')
600 editform = 'rebase.collapse'
602 editform = 'rebase.collapse'
601 if self.collapsemsg:
603 if self.collapsemsg:
602 commitmsg = self.collapsemsg
604 commitmsg = self.collapsemsg
603 else:
605 else:
604 commitmsg = 'Collapsed revision'
606 commitmsg = 'Collapsed revision'
605 for rebased in sorted(self.state):
607 for rebased in sorted(self.state):
606 if rebased not in self.skipped:
608 if rebased not in self.skipped:
607 commitmsg += '\n* %s' % repo[rebased].description()
609 commitmsg += '\n* %s' % repo[rebased].description()
608 editopt = True
610 editopt = True
609 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
611 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
610 revtoreuse = max(self.state)
612 revtoreuse = max(self.state)
611
613
612 newnode = self._concludenode(revtoreuse, p1, self.external,
614 newnode = self._concludenode(revtoreuse, p1, self.external,
613 editor, commitmsg=commitmsg)
615 editor, commitmsg=commitmsg)
614
616
615 if newnode is not None:
617 if newnode is not None:
616 newrev = repo[newnode].rev()
618 newrev = repo[newnode].rev()
617 for oldrev in self.state:
619 for oldrev in self.state:
618 self.state[oldrev] = newrev
620 self.state[oldrev] = newrev
619
621
620 if 'qtip' in repo.tags():
622 if 'qtip' in repo.tags():
621 updatemq(repo, self.state, self.skipped,
623 updatemq(repo, self.state, self.skipped,
622 **pycompat.strkwargs(opts))
624 **pycompat.strkwargs(opts))
623
625
624 # restore original working directory
626 # restore original working directory
625 # (we do this before stripping)
627 # (we do this before stripping)
626 newwd = self.state.get(self.originalwd, self.originalwd)
628 newwd = self.state.get(self.originalwd, self.originalwd)
627 if newwd < 0:
629 if newwd < 0:
628 # original directory is a parent of rebase set root or ignored
630 # original directory is a parent of rebase set root or ignored
629 newwd = self.originalwd
631 newwd = self.originalwd
630 if newwd not in [c.rev() for c in repo[None].parents()]:
632 if newwd not in [c.rev() for c in repo[None].parents()]:
631 ui.note(_("update back to initial working directory parent\n"))
633 ui.note(_("update back to initial working directory parent\n"))
632 hg.updaterepo(repo, newwd, overwrite=False)
634 hg.updaterepo(repo, newwd, overwrite=False)
633
635
634 collapsedas = None
636 collapsedas = None
635 if self.collapsef and not self.keepf:
637 if self.collapsef and not self.keepf:
636 collapsedas = newnode
638 collapsedas = newnode
637 clearrebased(ui, repo, self.destmap, self.state, self.skipped,
639 clearrebased(ui, repo, self.destmap, self.state, self.skipped,
638 collapsedas, self.keepf, fm=fm, backup=self.backupf)
640 collapsedas, self.keepf, fm=fm, backup=self.backupf)
639
641
640 clearstatus(repo)
642 clearstatus(repo)
641 clearcollapsemsg(repo)
643 clearcollapsemsg(repo)
642
644
643 ui.note(_("rebase completed\n"))
645 ui.note(_("rebase completed\n"))
644 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
646 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
645 if self.skipped:
647 if self.skipped:
646 skippedlen = len(self.skipped)
648 skippedlen = len(self.skipped)
647 ui.note(_("%d revisions have been skipped\n") % skippedlen)
649 ui.note(_("%d revisions have been skipped\n") % skippedlen)
648 fm.end()
650 fm.end()
649
651
650 if (self.activebookmark and self.activebookmark in repo._bookmarks and
652 if (self.activebookmark and self.activebookmark in repo._bookmarks and
651 repo['.'].node() == repo._bookmarks[self.activebookmark]):
653 repo['.'].node() == repo._bookmarks[self.activebookmark]):
652 bookmarks.activate(repo, self.activebookmark)
654 bookmarks.activate(repo, self.activebookmark)
653
655
654 def _abort(self, backup=True, suppwarns=False):
656 def _abort(self, backup=True, suppwarns=False):
655 '''Restore the repository to its original state.'''
657 '''Restore the repository to its original state.'''
656
658
657 repo = self.repo
659 repo = self.repo
658 try:
660 try:
659 # If the first commits in the rebased set get skipped during the
661 # If the first commits in the rebased set get skipped during the
660 # rebase, their values within the state mapping will be the dest
662 # rebase, their values within the state mapping will be the dest
661 # rev id. The rebased list must must not contain the dest rev
663 # rev id. The rebased list must must not contain the dest rev
662 # (issue4896)
664 # (issue4896)
663 rebased = [s for r, s in self.state.items()
665 rebased = [s for r, s in self.state.items()
664 if s >= 0 and s != r and s != self.destmap[r]]
666 if s >= 0 and s != r and s != self.destmap[r]]
665 immutable = [d for d in rebased if not repo[d].mutable()]
667 immutable = [d for d in rebased if not repo[d].mutable()]
666 cleanup = True
668 cleanup = True
667 if immutable:
669 if immutable:
668 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
670 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
669 % ', '.join(bytes(repo[r]) for r in immutable),
671 % ', '.join(bytes(repo[r]) for r in immutable),
670 hint=_("see 'hg help phases' for details"))
672 hint=_("see 'hg help phases' for details"))
671 cleanup = False
673 cleanup = False
672
674
673 descendants = set()
675 descendants = set()
674 if rebased:
676 if rebased:
675 descendants = set(repo.changelog.descendants(rebased))
677 descendants = set(repo.changelog.descendants(rebased))
676 if descendants - set(rebased):
678 if descendants - set(rebased):
677 repo.ui.warn(_("warning: new changesets detected on "
679 repo.ui.warn(_("warning: new changesets detected on "
678 "destination branch, can't strip\n"))
680 "destination branch, can't strip\n"))
679 cleanup = False
681 cleanup = False
680
682
681 if cleanup:
683 if cleanup:
682 shouldupdate = False
684 shouldupdate = False
683 if rebased:
685 if rebased:
684 strippoints = [
686 strippoints = [
685 c.node() for c in repo.set('roots(%ld)', rebased)]
687 c.node() for c in repo.set('roots(%ld)', rebased)]
686
688
687 updateifonnodes = set(rebased)
689 updateifonnodes = set(rebased)
688 updateifonnodes.update(self.destmap.values())
690 updateifonnodes.update(self.destmap.values())
689 updateifonnodes.add(self.originalwd)
691 updateifonnodes.add(self.originalwd)
690 shouldupdate = repo['.'].rev() in updateifonnodes
692 shouldupdate = repo['.'].rev() in updateifonnodes
691
693
692 # Update away from the rebase if necessary
694 # Update away from the rebase if necessary
693 if shouldupdate or needupdate(repo, self.state):
695 if shouldupdate or needupdate(repo, self.state):
694 mergemod.update(repo, self.originalwd, branchmerge=False,
696 mergemod.update(repo, self.originalwd, branchmerge=False,
695 force=True)
697 force=True)
696
698
697 # Strip from the first rebased revision
699 # Strip from the first rebased revision
698 if rebased:
700 if rebased:
699 repair.strip(repo.ui, repo, strippoints, backup=backup)
701 repair.strip(repo.ui, repo, strippoints, backup=backup)
700
702
701 if self.activebookmark and self.activebookmark in repo._bookmarks:
703 if self.activebookmark and self.activebookmark in repo._bookmarks:
702 bookmarks.activate(repo, self.activebookmark)
704 bookmarks.activate(repo, self.activebookmark)
703
705
704 finally:
706 finally:
705 clearstatus(repo)
707 clearstatus(repo)
706 clearcollapsemsg(repo)
708 clearcollapsemsg(repo)
707 if not suppwarns:
709 if not suppwarns:
708 repo.ui.warn(_('rebase aborted\n'))
710 repo.ui.warn(_('rebase aborted\n'))
709 return 0
711 return 0
710
712
711 @command('rebase',
713 @command('rebase',
712 [('s', 'source', '',
714 [('s', 'source', '',
713 _('rebase the specified changeset and descendants'), _('REV')),
715 _('rebase the specified changeset and descendants'), _('REV')),
714 ('b', 'base', '',
716 ('b', 'base', '',
715 _('rebase everything from branching point of specified changeset'),
717 _('rebase everything from branching point of specified changeset'),
716 _('REV')),
718 _('REV')),
717 ('r', 'rev', [],
719 ('r', 'rev', [],
718 _('rebase these revisions'),
720 _('rebase these revisions'),
719 _('REV')),
721 _('REV')),
720 ('d', 'dest', '',
722 ('d', 'dest', '',
721 _('rebase onto the specified changeset'), _('REV')),
723 _('rebase onto the specified changeset'), _('REV')),
722 ('', 'collapse', False, _('collapse the rebased changesets')),
724 ('', 'collapse', False, _('collapse the rebased changesets')),
723 ('m', 'message', '',
725 ('m', 'message', '',
724 _('use text as collapse commit message'), _('TEXT')),
726 _('use text as collapse commit message'), _('TEXT')),
725 ('e', 'edit', False, _('invoke editor on commit messages')),
727 ('e', 'edit', False, _('invoke editor on commit messages')),
726 ('l', 'logfile', '',
728 ('l', 'logfile', '',
727 _('read collapse commit message from file'), _('FILE')),
729 _('read collapse commit message from file'), _('FILE')),
728 ('k', 'keep', False, _('keep original changesets')),
730 ('k', 'keep', False, _('keep original changesets')),
729 ('', 'keepbranches', False, _('keep original branch names')),
731 ('', 'keepbranches', False, _('keep original branch names')),
730 ('D', 'detach', False, _('(DEPRECATED)')),
732 ('D', 'detach', False, _('(DEPRECATED)')),
731 ('i', 'interactive', False, _('(DEPRECATED)')),
733 ('i', 'interactive', False, _('(DEPRECATED)')),
732 ('t', 'tool', '', _('specify merge tool')),
734 ('t', 'tool', '', _('specify merge tool')),
733 ('', 'stop', False, _('stop interrupted rebase')),
735 ('', 'stop', False, _('stop interrupted rebase')),
734 ('c', 'continue', False, _('continue an interrupted rebase')),
736 ('c', 'continue', False, _('continue an interrupted rebase')),
735 ('a', 'abort', False, _('abort an interrupted rebase')),
737 ('a', 'abort', False, _('abort an interrupted rebase')),
736 ('', 'auto-orphans', '', _('automatically rebase orphan revisions '
738 ('', 'auto-orphans', '', _('automatically rebase orphan revisions '
737 'in the specified revset (EXPERIMENTAL)')),
739 'in the specified revset (EXPERIMENTAL)')),
738 ] + cmdutil.dryrunopts + cmdutil.formatteropts + cmdutil.confirmopts,
740 ] + cmdutil.dryrunopts + cmdutil.formatteropts + cmdutil.confirmopts,
739 _('[-s REV | -b REV] [-d REV] [OPTION]'),
741 _('[-s REV | -b REV] [-d REV] [OPTION]'),
740 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
742 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
741 def rebase(ui, repo, **opts):
743 def rebase(ui, repo, **opts):
742 """move changeset (and descendants) to a different branch
744 """move changeset (and descendants) to a different branch
743
745
744 Rebase uses repeated merging to graft changesets from one part of
746 Rebase uses repeated merging to graft changesets from one part of
745 history (the source) onto another (the destination). This can be
747 history (the source) onto another (the destination). This can be
746 useful for linearizing *local* changes relative to a master
748 useful for linearizing *local* changes relative to a master
747 development tree.
749 development tree.
748
750
749 Published commits cannot be rebased (see :hg:`help phases`).
751 Published commits cannot be rebased (see :hg:`help phases`).
750 To copy commits, see :hg:`help graft`.
752 To copy commits, see :hg:`help graft`.
751
753
752 If you don't specify a destination changeset (``-d/--dest``), rebase
754 If you don't specify a destination changeset (``-d/--dest``), rebase
753 will use the same logic as :hg:`merge` to pick a destination. if
755 will use the same logic as :hg:`merge` to pick a destination. if
754 the current branch contains exactly one other head, the other head
756 the current branch contains exactly one other head, the other head
755 is merged with by default. Otherwise, an explicit revision with
757 is merged with by default. Otherwise, an explicit revision with
756 which to merge with must be provided. (destination changeset is not
758 which to merge with must be provided. (destination changeset is not
757 modified by rebasing, but new changesets are added as its
759 modified by rebasing, but new changesets are added as its
758 descendants.)
760 descendants.)
759
761
760 Here are the ways to select changesets:
762 Here are the ways to select changesets:
761
763
762 1. Explicitly select them using ``--rev``.
764 1. Explicitly select them using ``--rev``.
763
765
764 2. Use ``--source`` to select a root changeset and include all of its
766 2. Use ``--source`` to select a root changeset and include all of its
765 descendants.
767 descendants.
766
768
767 3. Use ``--base`` to select a changeset; rebase will find ancestors
769 3. Use ``--base`` to select a changeset; rebase will find ancestors
768 and their descendants which are not also ancestors of the destination.
770 and their descendants which are not also ancestors of the destination.
769
771
770 4. If you do not specify any of ``--rev``, ``--source``, or ``--base``,
772 4. If you do not specify any of ``--rev``, ``--source``, or ``--base``,
771 rebase will use ``--base .`` as above.
773 rebase will use ``--base .`` as above.
772
774
773 If ``--source`` or ``--rev`` is used, special names ``SRC`` and ``ALLSRC``
775 If ``--source`` or ``--rev`` is used, special names ``SRC`` and ``ALLSRC``
774 can be used in ``--dest``. Destination would be calculated per source
776 can be used in ``--dest``. Destination would be calculated per source
775 revision with ``SRC`` substituted by that single source revision and
777 revision with ``SRC`` substituted by that single source revision and
776 ``ALLSRC`` substituted by all source revisions.
778 ``ALLSRC`` substituted by all source revisions.
777
779
778 Rebase will destroy original changesets unless you use ``--keep``.
780 Rebase will destroy original changesets unless you use ``--keep``.
779 It will also move your bookmarks (even if you do).
781 It will also move your bookmarks (even if you do).
780
782
781 Some changesets may be dropped if they do not contribute changes
783 Some changesets may be dropped if they do not contribute changes
782 (e.g. merges from the destination branch).
784 (e.g. merges from the destination branch).
783
785
784 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
786 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
785 a named branch with two heads. You will need to explicitly specify source
787 a named branch with two heads. You will need to explicitly specify source
786 and/or destination.
788 and/or destination.
787
789
788 If you need to use a tool to automate merge/conflict decisions, you
790 If you need to use a tool to automate merge/conflict decisions, you
789 can specify one with ``--tool``, see :hg:`help merge-tools`.
791 can specify one with ``--tool``, see :hg:`help merge-tools`.
790 As a caveat: the tool will not be used to mediate when a file was
792 As a caveat: the tool will not be used to mediate when a file was
791 deleted, there is no hook presently available for this.
793 deleted, there is no hook presently available for this.
792
794
793 If a rebase is interrupted to manually resolve a conflict, it can be
795 If a rebase is interrupted to manually resolve a conflict, it can be
794 continued with --continue/-c, aborted with --abort/-a, or stopped with
796 continued with --continue/-c, aborted with --abort/-a, or stopped with
795 --stop.
797 --stop.
796
798
797 .. container:: verbose
799 .. container:: verbose
798
800
799 Examples:
801 Examples:
800
802
801 - move "local changes" (current commit back to branching point)
803 - move "local changes" (current commit back to branching point)
802 to the current branch tip after a pull::
804 to the current branch tip after a pull::
803
805
804 hg rebase
806 hg rebase
805
807
806 - move a single changeset to the stable branch::
808 - move a single changeset to the stable branch::
807
809
808 hg rebase -r 5f493448 -d stable
810 hg rebase -r 5f493448 -d stable
809
811
810 - splice a commit and all its descendants onto another part of history::
812 - splice a commit and all its descendants onto another part of history::
811
813
812 hg rebase --source c0c3 --dest 4cf9
814 hg rebase --source c0c3 --dest 4cf9
813
815
814 - rebase everything on a branch marked by a bookmark onto the
816 - rebase everything on a branch marked by a bookmark onto the
815 default branch::
817 default branch::
816
818
817 hg rebase --base myfeature --dest default
819 hg rebase --base myfeature --dest default
818
820
819 - collapse a sequence of changes into a single commit::
821 - collapse a sequence of changes into a single commit::
820
822
821 hg rebase --collapse -r 1520:1525 -d .
823 hg rebase --collapse -r 1520:1525 -d .
822
824
823 - move a named branch while preserving its name::
825 - move a named branch while preserving its name::
824
826
825 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
827 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
826
828
827 - stabilize orphaned changesets so history looks linear::
829 - stabilize orphaned changesets so history looks linear::
828
830
829 hg rebase -r 'orphan()-obsolete()'\
831 hg rebase -r 'orphan()-obsolete()'\
830 -d 'first(max((successors(max(roots(ALLSRC) & ::SRC)^)-obsolete())::) +\
832 -d 'first(max((successors(max(roots(ALLSRC) & ::SRC)^)-obsolete())::) +\
831 max(::((roots(ALLSRC) & ::SRC)^)-obsolete()))'
833 max(::((roots(ALLSRC) & ::SRC)^)-obsolete()))'
832
834
833 Configuration Options:
835 Configuration Options:
834
836
835 You can make rebase require a destination if you set the following config
837 You can make rebase require a destination if you set the following config
836 option::
838 option::
837
839
838 [commands]
840 [commands]
839 rebase.requiredest = True
841 rebase.requiredest = True
840
842
841 By default, rebase will close the transaction after each commit. For
843 By default, rebase will close the transaction after each commit. For
842 performance purposes, you can configure rebase to use a single transaction
844 performance purposes, you can configure rebase to use a single transaction
843 across the entire rebase. WARNING: This setting introduces a significant
845 across the entire rebase. WARNING: This setting introduces a significant
844 risk of losing the work you've done in a rebase if the rebase aborts
846 risk of losing the work you've done in a rebase if the rebase aborts
845 unexpectedly::
847 unexpectedly::
846
848
847 [rebase]
849 [rebase]
848 singletransaction = True
850 singletransaction = True
849
851
850 By default, rebase writes to the working copy, but you can configure it to
852 By default, rebase writes to the working copy, but you can configure it to
851 run in-memory for for better performance, and to allow it to run if the
853 run in-memory for for better performance, and to allow it to run if the
852 working copy is dirty::
854 working copy is dirty::
853
855
854 [rebase]
856 [rebase]
855 experimental.inmemory = True
857 experimental.inmemory = True
856
858
857 Return Values:
859 Return Values:
858
860
859 Returns 0 on success, 1 if nothing to rebase or there are
861 Returns 0 on success, 1 if nothing to rebase or there are
860 unresolved conflicts.
862 unresolved conflicts.
861
863
862 """
864 """
863 opts = pycompat.byteskwargs(opts)
865 opts = pycompat.byteskwargs(opts)
864 inmemory = ui.configbool('rebase', 'experimental.inmemory')
866 inmemory = ui.configbool('rebase', 'experimental.inmemory')
865 dryrun = opts.get('dry_run')
867 dryrun = opts.get('dry_run')
866 confirm = opts.get('confirm')
868 confirm = opts.get('confirm')
867 selactions = [k for k in ['abort', 'stop', 'continue'] if opts.get(k)]
869 selactions = [k for k in ['abort', 'stop', 'continue'] if opts.get(k)]
868 if len(selactions) > 1:
870 if len(selactions) > 1:
869 raise error.Abort(_('cannot use --%s with --%s')
871 raise error.Abort(_('cannot use --%s with --%s')
870 % tuple(selactions[:2]))
872 % tuple(selactions[:2]))
871 action = selactions[0] if selactions else None
873 action = selactions[0] if selactions else None
872 if dryrun and action:
874 if dryrun and action:
873 raise error.Abort(_('cannot specify both --dry-run and --%s') % action)
875 raise error.Abort(_('cannot specify both --dry-run and --%s') % action)
874 if confirm and action:
876 if confirm and action:
875 raise error.Abort(_('cannot specify both --confirm and --%s') % action)
877 raise error.Abort(_('cannot specify both --confirm and --%s') % action)
876 if dryrun and confirm:
878 if dryrun and confirm:
877 raise error.Abort(_('cannot specify both --confirm and --dry-run'))
879 raise error.Abort(_('cannot specify both --confirm and --dry-run'))
878
880
879 if action or repo.currenttransaction() is not None:
881 if action or repo.currenttransaction() is not None:
880 # in-memory rebase is not compatible with resuming rebases.
882 # in-memory rebase is not compatible with resuming rebases.
881 # (Or if it is run within a transaction, since the restart logic can
883 # (Or if it is run within a transaction, since the restart logic can
882 # fail the entire transaction.)
884 # fail the entire transaction.)
883 inmemory = False
885 inmemory = False
884
886
885 if opts.get('auto_orphans'):
887 if opts.get('auto_orphans'):
886 for key in opts:
888 for key in opts:
887 if key != 'auto_orphans' and opts.get(key):
889 if key != 'auto_orphans' and opts.get(key):
888 raise error.Abort(_('--auto-orphans is incompatible with %s') %
890 raise error.Abort(_('--auto-orphans is incompatible with %s') %
889 ('--' + key))
891 ('--' + key))
890 userrevs = list(repo.revs(opts.get('auto_orphans')))
892 userrevs = list(repo.revs(opts.get('auto_orphans')))
891 opts['rev'] = [revsetlang.formatspec('%ld and orphan()', userrevs)]
893 opts['rev'] = [revsetlang.formatspec('%ld and orphan()', userrevs)]
892 opts['dest'] = '_destautoorphanrebase(SRC)'
894 opts['dest'] = '_destautoorphanrebase(SRC)'
893
895
894 if dryrun or confirm:
896 if dryrun or confirm:
895 return _dryrunrebase(ui, repo, action, opts)
897 return _dryrunrebase(ui, repo, action, opts)
896 elif action == 'stop':
898 elif action == 'stop':
897 rbsrt = rebaseruntime(repo, ui)
899 rbsrt = rebaseruntime(repo, ui)
898 with repo.wlock(), repo.lock():
900 with repo.wlock(), repo.lock():
899 rbsrt.restorestatus()
901 rbsrt.restorestatus()
900 if rbsrt.collapsef:
902 if rbsrt.collapsef:
901 raise error.Abort(_("cannot stop in --collapse session"))
903 raise error.Abort(_("cannot stop in --collapse session"))
902 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
904 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
903 if not (rbsrt.keepf or allowunstable):
905 if not (rbsrt.keepf or allowunstable):
904 raise error.Abort(_("cannot remove original changesets with"
906 raise error.Abort(_("cannot remove original changesets with"
905 " unrebased descendants"),
907 " unrebased descendants"),
906 hint=_('either enable obsmarkers to allow unstable '
908 hint=_('either enable obsmarkers to allow unstable '
907 'revisions or use --keep to keep original '
909 'revisions or use --keep to keep original '
908 'changesets'))
910 'changesets'))
909 if needupdate(repo, rbsrt.state):
911 if needupdate(repo, rbsrt.state):
910 # update to the current working revision
912 # update to the current working revision
911 # to clear interrupted merge
913 # to clear interrupted merge
912 hg.updaterepo(repo, rbsrt.originalwd, overwrite=True)
914 hg.updaterepo(repo, rbsrt.originalwd, overwrite=True)
913 rbsrt._finishrebase()
915 rbsrt._finishrebase()
914 return 0
916 return 0
915 elif inmemory:
917 elif inmemory:
916 try:
918 try:
917 # in-memory merge doesn't support conflicts, so if we hit any, abort
919 # in-memory merge doesn't support conflicts, so if we hit any, abort
918 # and re-run as an on-disk merge.
920 # and re-run as an on-disk merge.
919 overrides = {('rebase', 'singletransaction'): True}
921 overrides = {('rebase', 'singletransaction'): True}
920 with ui.configoverride(overrides, 'rebase'):
922 with ui.configoverride(overrides, 'rebase'):
921 return _dorebase(ui, repo, action, opts, inmemory=inmemory)
923 return _dorebase(ui, repo, action, opts, inmemory=inmemory)
922 except error.InMemoryMergeConflictsError:
924 except error.InMemoryMergeConflictsError:
923 ui.warn(_('hit merge conflicts; re-running rebase without in-memory'
925 ui.warn(_('hit merge conflicts; re-running rebase without in-memory'
924 ' merge\n'))
926 ' merge\n'))
925 # TODO: Make in-memory merge not use the on-disk merge state, so
927 # TODO: Make in-memory merge not use the on-disk merge state, so
926 # we don't have to clean it here
928 # we don't have to clean it here
927 mergemod.mergestate.clean(repo)
929 mergemod.mergestate.clean(repo)
928 clearstatus(repo)
930 clearstatus(repo)
929 clearcollapsemsg(repo)
931 clearcollapsemsg(repo)
930 return _dorebase(ui, repo, action, opts, inmemory=False)
932 return _dorebase(ui, repo, action, opts, inmemory=False)
931 else:
933 else:
932 return _dorebase(ui, repo, action, opts)
934 return _dorebase(ui, repo, action, opts)
933
935
934 def _dryrunrebase(ui, repo, action, opts):
936 def _dryrunrebase(ui, repo, action, opts):
935 rbsrt = rebaseruntime(repo, ui, inmemory=True, opts=opts)
937 rbsrt = rebaseruntime(repo, ui, inmemory=True, opts=opts)
936 confirm = opts.get('confirm')
938 confirm = opts.get('confirm')
937 if confirm:
939 if confirm:
938 ui.status(_('starting in-memory rebase\n'))
940 ui.status(_('starting in-memory rebase\n'))
939 else:
941 else:
940 ui.status(_('starting dry-run rebase; repository will not be '
942 ui.status(_('starting dry-run rebase; repository will not be '
941 'changed\n'))
943 'changed\n'))
942 with repo.wlock(), repo.lock():
944 with repo.wlock(), repo.lock():
943 needsabort = True
945 needsabort = True
944 try:
946 try:
945 overrides = {('rebase', 'singletransaction'): True}
947 overrides = {('rebase', 'singletransaction'): True}
946 with ui.configoverride(overrides, 'rebase'):
948 with ui.configoverride(overrides, 'rebase'):
947 _origrebase(ui, repo, action, opts, rbsrt, inmemory=True,
949 _origrebase(ui, repo, action, opts, rbsrt, inmemory=True,
948 leaveunfinished=True)
950 leaveunfinished=True)
949 except error.InMemoryMergeConflictsError:
951 except error.InMemoryMergeConflictsError:
950 ui.status(_('hit a merge conflict\n'))
952 ui.status(_('hit a merge conflict\n'))
951 return 1
953 return 1
952 except error.Abort:
954 except error.Abort:
953 needsabort = False
955 needsabort = False
954 raise
956 raise
955 else:
957 else:
956 if confirm:
958 if confirm:
957 ui.status(_('rebase completed successfully\n'))
959 ui.status(_('rebase completed successfully\n'))
958 if not ui.promptchoice(_(b'apply changes (yn)?'
960 if not ui.promptchoice(_(b'apply changes (yn)?'
959 b'$$ &Yes $$ &No')):
961 b'$$ &Yes $$ &No')):
960 # finish unfinished rebase
962 # finish unfinished rebase
961 rbsrt._finishrebase()
963 rbsrt._finishrebase()
962 else:
964 else:
963 rbsrt._prepareabortorcontinue(isabort=True, backup=False,
965 rbsrt._prepareabortorcontinue(isabort=True, backup=False,
964 suppwarns=True)
966 suppwarns=True)
965 needsabort = False
967 needsabort = False
966 else:
968 else:
967 ui.status(_('dry-run rebase completed successfully; run without'
969 ui.status(_('dry-run rebase completed successfully; run without'
968 ' -n/--dry-run to perform this rebase\n'))
970 ' -n/--dry-run to perform this rebase\n'))
969 return 0
971 return 0
970 finally:
972 finally:
971 if needsabort:
973 if needsabort:
972 # no need to store backup in case of dryrun
974 # no need to store backup in case of dryrun
973 rbsrt._prepareabortorcontinue(isabort=True, backup=False,
975 rbsrt._prepareabortorcontinue(isabort=True, backup=False,
974 suppwarns=True)
976 suppwarns=True)
975
977
976 def _dorebase(ui, repo, action, opts, inmemory=False):
978 def _dorebase(ui, repo, action, opts, inmemory=False):
977 rbsrt = rebaseruntime(repo, ui, inmemory, opts)
979 rbsrt = rebaseruntime(repo, ui, inmemory, opts)
978 return _origrebase(ui, repo, action, opts, rbsrt, inmemory=inmemory)
980 return _origrebase(ui, repo, action, opts, rbsrt, inmemory=inmemory)
979
981
980 def _origrebase(ui, repo, action, opts, rbsrt, inmemory=False,
982 def _origrebase(ui, repo, action, opts, rbsrt, inmemory=False,
981 leaveunfinished=False):
983 leaveunfinished=False):
982 assert action != 'stop'
984 assert action != 'stop'
983 with repo.wlock(), repo.lock():
985 with repo.wlock(), repo.lock():
984 # Validate input and define rebasing points
986 # Validate input and define rebasing points
985 destf = opts.get('dest', None)
987 destf = opts.get('dest', None)
986 srcf = opts.get('source', None)
988 srcf = opts.get('source', None)
987 basef = opts.get('base', None)
989 basef = opts.get('base', None)
988 revf = opts.get('rev', [])
990 revf = opts.get('rev', [])
989 # search default destination in this space
991 # search default destination in this space
990 # used in the 'hg pull --rebase' case, see issue 5214.
992 # used in the 'hg pull --rebase' case, see issue 5214.
991 destspace = opts.get('_destspace')
993 destspace = opts.get('_destspace')
992 if opts.get('interactive'):
994 if opts.get('interactive'):
993 try:
995 try:
994 if extensions.find('histedit'):
996 if extensions.find('histedit'):
995 enablehistedit = ''
997 enablehistedit = ''
996 except KeyError:
998 except KeyError:
997 enablehistedit = " --config extensions.histedit="
999 enablehistedit = " --config extensions.histedit="
998 help = "hg%s help -e histedit" % enablehistedit
1000 help = "hg%s help -e histedit" % enablehistedit
999 msg = _("interactive history editing is supported by the "
1001 msg = _("interactive history editing is supported by the "
1000 "'histedit' extension (see \"%s\")") % help
1002 "'histedit' extension (see \"%s\")") % help
1001 raise error.Abort(msg)
1003 raise error.Abort(msg)
1002
1004
1003 if rbsrt.collapsemsg and not rbsrt.collapsef:
1005 if rbsrt.collapsemsg and not rbsrt.collapsef:
1004 raise error.Abort(
1006 raise error.Abort(
1005 _('message can only be specified with collapse'))
1007 _('message can only be specified with collapse'))
1006
1008
1007 if action:
1009 if action:
1008 if rbsrt.collapsef:
1010 if rbsrt.collapsef:
1009 raise error.Abort(
1011 raise error.Abort(
1010 _('cannot use collapse with continue or abort'))
1012 _('cannot use collapse with continue or abort'))
1011 if srcf or basef or destf:
1013 if srcf or basef or destf:
1012 raise error.Abort(
1014 raise error.Abort(
1013 _('abort and continue do not allow specifying revisions'))
1015 _('abort and continue do not allow specifying revisions'))
1014 if action == 'abort' and opts.get('tool', False):
1016 if action == 'abort' and opts.get('tool', False):
1015 ui.warn(_('tool option will be ignored\n'))
1017 ui.warn(_('tool option will be ignored\n'))
1016 if action == 'continue':
1018 if action == 'continue':
1017 ms = mergemod.mergestate.read(repo)
1019 ms = mergemod.mergestate.read(repo)
1018 mergeutil.checkunresolved(ms)
1020 mergeutil.checkunresolved(ms)
1019
1021
1020 retcode = rbsrt._prepareabortorcontinue(isabort=(action == 'abort'))
1022 retcode = rbsrt._prepareabortorcontinue(isabort=(action == 'abort'))
1021 if retcode is not None:
1023 if retcode is not None:
1022 return retcode
1024 return retcode
1023 else:
1025 else:
1024 destmap = _definedestmap(ui, repo, inmemory, destf, srcf, basef,
1026 destmap = _definedestmap(ui, repo, inmemory, destf, srcf, basef,
1025 revf, destspace=destspace)
1027 revf, destspace=destspace)
1026 retcode = rbsrt._preparenewrebase(destmap)
1028 retcode = rbsrt._preparenewrebase(destmap)
1027 if retcode is not None:
1029 if retcode is not None:
1028 return retcode
1030 return retcode
1029 storecollapsemsg(repo, rbsrt.collapsemsg)
1031 storecollapsemsg(repo, rbsrt.collapsemsg)
1030
1032
1031 tr = None
1033 tr = None
1032
1034
1033 singletr = ui.configbool('rebase', 'singletransaction')
1035 singletr = ui.configbool('rebase', 'singletransaction')
1034 if singletr:
1036 if singletr:
1035 tr = repo.transaction('rebase')
1037 tr = repo.transaction('rebase')
1036
1038
1037 # If `rebase.singletransaction` is enabled, wrap the entire operation in
1039 # If `rebase.singletransaction` is enabled, wrap the entire operation in
1038 # one transaction here. Otherwise, transactions are obtained when
1040 # one transaction here. Otherwise, transactions are obtained when
1039 # committing each node, which is slower but allows partial success.
1041 # committing each node, which is slower but allows partial success.
1040 with util.acceptintervention(tr):
1042 with util.acceptintervention(tr):
1041 # Same logic for the dirstate guard, except we don't create one when
1043 # Same logic for the dirstate guard, except we don't create one when
1042 # rebasing in-memory (it's not needed).
1044 # rebasing in-memory (it's not needed).
1043 dsguard = None
1045 dsguard = None
1044 if singletr and not inmemory:
1046 if singletr and not inmemory:
1045 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
1047 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
1046 with util.acceptintervention(dsguard):
1048 with util.acceptintervention(dsguard):
1047 rbsrt._performrebase(tr)
1049 rbsrt._performrebase(tr)
1048 if not leaveunfinished:
1050 if not leaveunfinished:
1049 rbsrt._finishrebase()
1051 rbsrt._finishrebase()
1050
1052
1051 def _definedestmap(ui, repo, inmemory, destf=None, srcf=None, basef=None,
1053 def _definedestmap(ui, repo, inmemory, destf=None, srcf=None, basef=None,
1052 revf=None, destspace=None):
1054 revf=None, destspace=None):
1053 """use revisions argument to define destmap {srcrev: destrev}"""
1055 """use revisions argument to define destmap {srcrev: destrev}"""
1054 if revf is None:
1056 if revf is None:
1055 revf = []
1057 revf = []
1056
1058
1057 # destspace is here to work around issues with `hg pull --rebase` see
1059 # destspace is here to work around issues with `hg pull --rebase` see
1058 # issue5214 for details
1060 # issue5214 for details
1059 if srcf and basef:
1061 if srcf and basef:
1060 raise error.Abort(_('cannot specify both a source and a base'))
1062 raise error.Abort(_('cannot specify both a source and a base'))
1061 if revf and basef:
1063 if revf and basef:
1062 raise error.Abort(_('cannot specify both a revision and a base'))
1064 raise error.Abort(_('cannot specify both a revision and a base'))
1063 if revf and srcf:
1065 if revf and srcf:
1064 raise error.Abort(_('cannot specify both a revision and a source'))
1066 raise error.Abort(_('cannot specify both a revision and a source'))
1065
1067
1066 if not inmemory:
1068 if not inmemory:
1067 cmdutil.checkunfinished(repo)
1069 cmdutil.checkunfinished(repo)
1068 cmdutil.bailifchanged(repo)
1070 cmdutil.bailifchanged(repo)
1069
1071
1070 if ui.configbool('commands', 'rebase.requiredest') and not destf:
1072 if ui.configbool('commands', 'rebase.requiredest') and not destf:
1071 raise error.Abort(_('you must specify a destination'),
1073 raise error.Abort(_('you must specify a destination'),
1072 hint=_('use: hg rebase -d REV'))
1074 hint=_('use: hg rebase -d REV'))
1073
1075
1074 dest = None
1076 dest = None
1075
1077
1076 if revf:
1078 if revf:
1077 rebaseset = scmutil.revrange(repo, revf)
1079 rebaseset = scmutil.revrange(repo, revf)
1078 if not rebaseset:
1080 if not rebaseset:
1079 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
1081 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
1080 return None
1082 return None
1081 elif srcf:
1083 elif srcf:
1082 src = scmutil.revrange(repo, [srcf])
1084 src = scmutil.revrange(repo, [srcf])
1083 if not src:
1085 if not src:
1084 ui.status(_('empty "source" revision set - nothing to rebase\n'))
1086 ui.status(_('empty "source" revision set - nothing to rebase\n'))
1085 return None
1087 return None
1086 rebaseset = repo.revs('(%ld)::', src)
1088 rebaseset = repo.revs('(%ld)::', src)
1087 assert rebaseset
1089 assert rebaseset
1088 else:
1090 else:
1089 base = scmutil.revrange(repo, [basef or '.'])
1091 base = scmutil.revrange(repo, [basef or '.'])
1090 if not base:
1092 if not base:
1091 ui.status(_('empty "base" revision set - '
1093 ui.status(_('empty "base" revision set - '
1092 "can't compute rebase set\n"))
1094 "can't compute rebase set\n"))
1093 return None
1095 return None
1094 if destf:
1096 if destf:
1095 # --base does not support multiple destinations
1097 # --base does not support multiple destinations
1096 dest = scmutil.revsingle(repo, destf)
1098 dest = scmutil.revsingle(repo, destf)
1097 else:
1099 else:
1098 dest = repo[_destrebase(repo, base, destspace=destspace)]
1100 dest = repo[_destrebase(repo, base, destspace=destspace)]
1099 destf = bytes(dest)
1101 destf = bytes(dest)
1100
1102
1101 roots = [] # selected children of branching points
1103 roots = [] # selected children of branching points
1102 bpbase = {} # {branchingpoint: [origbase]}
1104 bpbase = {} # {branchingpoint: [origbase]}
1103 for b in base: # group bases by branching points
1105 for b in base: # group bases by branching points
1104 bp = repo.revs('ancestor(%d, %d)', b, dest.rev()).first()
1106 bp = repo.revs('ancestor(%d, %d)', b, dest.rev()).first()
1105 bpbase[bp] = bpbase.get(bp, []) + [b]
1107 bpbase[bp] = bpbase.get(bp, []) + [b]
1106 if None in bpbase:
1108 if None in bpbase:
1107 # emulate the old behavior, showing "nothing to rebase" (a better
1109 # emulate the old behavior, showing "nothing to rebase" (a better
1108 # behavior may be abort with "cannot find branching point" error)
1110 # behavior may be abort with "cannot find branching point" error)
1109 bpbase.clear()
1111 bpbase.clear()
1110 for bp, bs in bpbase.iteritems(): # calculate roots
1112 for bp, bs in bpbase.iteritems(): # calculate roots
1111 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
1113 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
1112
1114
1113 rebaseset = repo.revs('%ld::', roots)
1115 rebaseset = repo.revs('%ld::', roots)
1114
1116
1115 if not rebaseset:
1117 if not rebaseset:
1116 # transform to list because smartsets are not comparable to
1118 # transform to list because smartsets are not comparable to
1117 # lists. This should be improved to honor laziness of
1119 # lists. This should be improved to honor laziness of
1118 # smartset.
1120 # smartset.
1119 if list(base) == [dest.rev()]:
1121 if list(base) == [dest.rev()]:
1120 if basef:
1122 if basef:
1121 ui.status(_('nothing to rebase - %s is both "base"'
1123 ui.status(_('nothing to rebase - %s is both "base"'
1122 ' and destination\n') % dest)
1124 ' and destination\n') % dest)
1123 else:
1125 else:
1124 ui.status(_('nothing to rebase - working directory '
1126 ui.status(_('nothing to rebase - working directory '
1125 'parent is also destination\n'))
1127 'parent is also destination\n'))
1126 elif not repo.revs('%ld - ::%d', base, dest.rev()):
1128 elif not repo.revs('%ld - ::%d', base, dest.rev()):
1127 if basef:
1129 if basef:
1128 ui.status(_('nothing to rebase - "base" %s is '
1130 ui.status(_('nothing to rebase - "base" %s is '
1129 'already an ancestor of destination '
1131 'already an ancestor of destination '
1130 '%s\n') %
1132 '%s\n') %
1131 ('+'.join(bytes(repo[r]) for r in base),
1133 ('+'.join(bytes(repo[r]) for r in base),
1132 dest))
1134 dest))
1133 else:
1135 else:
1134 ui.status(_('nothing to rebase - working '
1136 ui.status(_('nothing to rebase - working '
1135 'directory parent is already an '
1137 'directory parent is already an '
1136 'ancestor of destination %s\n') % dest)
1138 'ancestor of destination %s\n') % dest)
1137 else: # can it happen?
1139 else: # can it happen?
1138 ui.status(_('nothing to rebase from %s to %s\n') %
1140 ui.status(_('nothing to rebase from %s to %s\n') %
1139 ('+'.join(bytes(repo[r]) for r in base), dest))
1141 ('+'.join(bytes(repo[r]) for r in base), dest))
1140 return None
1142 return None
1141
1143
1142 rebasingwcp = repo['.'].rev() in rebaseset
1144 rebasingwcp = repo['.'].rev() in rebaseset
1143 ui.log("rebase", "rebasing working copy parent: %r\n", rebasingwcp,
1145 ui.log("rebase", "rebasing working copy parent: %r\n", rebasingwcp,
1144 rebase_rebasing_wcp=rebasingwcp)
1146 rebase_rebasing_wcp=rebasingwcp)
1145 if inmemory and rebasingwcp:
1147 if inmemory and rebasingwcp:
1146 # Check these since we did not before.
1148 # Check these since we did not before.
1147 cmdutil.checkunfinished(repo)
1149 cmdutil.checkunfinished(repo)
1148 cmdutil.bailifchanged(repo)
1150 cmdutil.bailifchanged(repo)
1149
1151
1150 if not destf:
1152 if not destf:
1151 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
1153 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
1152 destf = bytes(dest)
1154 destf = bytes(dest)
1153
1155
1154 allsrc = revsetlang.formatspec('%ld', rebaseset)
1156 allsrc = revsetlang.formatspec('%ld', rebaseset)
1155 alias = {'ALLSRC': allsrc}
1157 alias = {'ALLSRC': allsrc}
1156
1158
1157 if dest is None:
1159 if dest is None:
1158 try:
1160 try:
1159 # fast path: try to resolve dest without SRC alias
1161 # fast path: try to resolve dest without SRC alias
1160 dest = scmutil.revsingle(repo, destf, localalias=alias)
1162 dest = scmutil.revsingle(repo, destf, localalias=alias)
1161 except error.RepoLookupError:
1163 except error.RepoLookupError:
1162 # multi-dest path: resolve dest for each SRC separately
1164 # multi-dest path: resolve dest for each SRC separately
1163 destmap = {}
1165 destmap = {}
1164 for r in rebaseset:
1166 for r in rebaseset:
1165 alias['SRC'] = revsetlang.formatspec('%d', r)
1167 alias['SRC'] = revsetlang.formatspec('%d', r)
1166 # use repo.anyrevs instead of scmutil.revsingle because we
1168 # use repo.anyrevs instead of scmutil.revsingle because we
1167 # don't want to abort if destset is empty.
1169 # don't want to abort if destset is empty.
1168 destset = repo.anyrevs([destf], user=True, localalias=alias)
1170 destset = repo.anyrevs([destf], user=True, localalias=alias)
1169 size = len(destset)
1171 size = len(destset)
1170 if size == 1:
1172 if size == 1:
1171 destmap[r] = destset.first()
1173 destmap[r] = destset.first()
1172 elif size == 0:
1174 elif size == 0:
1173 ui.note(_('skipping %s - empty destination\n') % repo[r])
1175 ui.note(_('skipping %s - empty destination\n') % repo[r])
1174 else:
1176 else:
1175 raise error.Abort(_('rebase destination for %s is not '
1177 raise error.Abort(_('rebase destination for %s is not '
1176 'unique') % repo[r])
1178 'unique') % repo[r])
1177
1179
1178 if dest is not None:
1180 if dest is not None:
1179 # single-dest case: assign dest to each rev in rebaseset
1181 # single-dest case: assign dest to each rev in rebaseset
1180 destrev = dest.rev()
1182 destrev = dest.rev()
1181 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
1183 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
1182
1184
1183 if not destmap:
1185 if not destmap:
1184 ui.status(_('nothing to rebase - empty destination\n'))
1186 ui.status(_('nothing to rebase - empty destination\n'))
1185 return None
1187 return None
1186
1188
1187 return destmap
1189 return destmap
1188
1190
1189 def externalparent(repo, state, destancestors):
1191 def externalparent(repo, state, destancestors):
1190 """Return the revision that should be used as the second parent
1192 """Return the revision that should be used as the second parent
1191 when the revisions in state is collapsed on top of destancestors.
1193 when the revisions in state is collapsed on top of destancestors.
1192 Abort if there is more than one parent.
1194 Abort if there is more than one parent.
1193 """
1195 """
1194 parents = set()
1196 parents = set()
1195 source = min(state)
1197 source = min(state)
1196 for rev in state:
1198 for rev in state:
1197 if rev == source:
1199 if rev == source:
1198 continue
1200 continue
1199 for p in repo[rev].parents():
1201 for p in repo[rev].parents():
1200 if (p.rev() not in state
1202 if (p.rev() not in state
1201 and p.rev() not in destancestors):
1203 and p.rev() not in destancestors):
1202 parents.add(p.rev())
1204 parents.add(p.rev())
1203 if not parents:
1205 if not parents:
1204 return nullrev
1206 return nullrev
1205 if len(parents) == 1:
1207 if len(parents) == 1:
1206 return parents.pop()
1208 return parents.pop()
1207 raise error.Abort(_('unable to collapse on top of %d, there is more '
1209 raise error.Abort(_('unable to collapse on top of %d, there is more '
1208 'than one external parent: %s') %
1210 'than one external parent: %s') %
1209 (max(destancestors),
1211 (max(destancestors),
1210 ', '.join("%d" % p for p in sorted(parents))))
1212 ', '.join("%d" % p for p in sorted(parents))))
1211
1213
1212 def commitmemorynode(repo, p1, p2, wctx, editor, extra, user, date, commitmsg):
1214 def commitmemorynode(repo, p1, p2, wctx, editor, extra, user, date, commitmsg):
1213 '''Commit the memory changes with parents p1 and p2.
1215 '''Commit the memory changes with parents p1 and p2.
1214 Return node of committed revision.'''
1216 Return node of committed revision.'''
1215 # Replicates the empty check in ``repo.commit``.
1217 # Replicates the empty check in ``repo.commit``.
1216 if wctx.isempty() and not repo.ui.configbool('ui', 'allowemptycommit'):
1218 if wctx.isempty() and not repo.ui.configbool('ui', 'allowemptycommit'):
1217 return None
1219 return None
1218
1220
1219 # By convention, ``extra['branch']`` (set by extrafn) clobbers
1221 # By convention, ``extra['branch']`` (set by extrafn) clobbers
1220 # ``branch`` (used when passing ``--keepbranches``).
1222 # ``branch`` (used when passing ``--keepbranches``).
1221 branch = repo[p1].branch()
1223 branch = repo[p1].branch()
1222 if 'branch' in extra:
1224 if 'branch' in extra:
1223 branch = extra['branch']
1225 branch = extra['branch']
1224
1226
1225 memctx = wctx.tomemctx(commitmsg, parents=(p1, p2), date=date,
1227 memctx = wctx.tomemctx(commitmsg, parents=(p1, p2), date=date,
1226 extra=extra, user=user, branch=branch, editor=editor)
1228 extra=extra, user=user, branch=branch, editor=editor)
1227 commitres = repo.commitctx(memctx)
1229 commitres = repo.commitctx(memctx)
1228 wctx.clean() # Might be reused
1230 wctx.clean() # Might be reused
1229 return commitres
1231 return commitres
1230
1232
1231 def commitnode(repo, p1, p2, editor, extra, user, date, commitmsg):
1233 def commitnode(repo, p1, p2, editor, extra, user, date, commitmsg):
1232 '''Commit the wd changes with parents p1 and p2.
1234 '''Commit the wd changes with parents p1 and p2.
1233 Return node of committed revision.'''
1235 Return node of committed revision.'''
1234 dsguard = util.nullcontextmanager()
1236 dsguard = util.nullcontextmanager()
1235 if not repo.ui.configbool('rebase', 'singletransaction'):
1237 if not repo.ui.configbool('rebase', 'singletransaction'):
1236 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
1238 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
1237 with dsguard:
1239 with dsguard:
1238 repo.setparents(repo[p1].node(), repo[p2].node())
1240 repo.setparents(repo[p1].node(), repo[p2].node())
1239
1241
1240 # Commit might fail if unresolved files exist
1242 # Commit might fail if unresolved files exist
1241 newnode = repo.commit(text=commitmsg, user=user, date=date,
1243 newnode = repo.commit(text=commitmsg, user=user, date=date,
1242 extra=extra, editor=editor)
1244 extra=extra, editor=editor)
1243
1245
1244 repo.dirstate.setbranch(repo[newnode].branch())
1246 repo.dirstate.setbranch(repo[newnode].branch())
1245 return newnode
1247 return newnode
1246
1248
1247 def rebasenode(repo, rev, p1, base, collapse, dest, wctx):
1249 def rebasenode(repo, rev, p1, base, collapse, dest, wctx):
1248 'Rebase a single revision rev on top of p1 using base as merge ancestor'
1250 'Rebase a single revision rev on top of p1 using base as merge ancestor'
1249 # Merge phase
1251 # Merge phase
1250 # Update to destination and merge it with local
1252 # Update to destination and merge it with local
1251 if wctx.isinmemory():
1253 if wctx.isinmemory():
1252 wctx.setbase(repo[p1])
1254 wctx.setbase(repo[p1])
1253 else:
1255 else:
1254 if repo['.'].rev() != p1:
1256 if repo['.'].rev() != p1:
1255 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
1257 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
1256 mergemod.update(repo, p1, branchmerge=False, force=True)
1258 mergemod.update(repo, p1, branchmerge=False, force=True)
1257 else:
1259 else:
1258 repo.ui.debug(" already in destination\n")
1260 repo.ui.debug(" already in destination\n")
1259 # This is, alas, necessary to invalidate workingctx's manifest cache,
1261 # This is, alas, necessary to invalidate workingctx's manifest cache,
1260 # as well as other data we litter on it in other places.
1262 # as well as other data we litter on it in other places.
1261 wctx = repo[None]
1263 wctx = repo[None]
1262 repo.dirstate.write(repo.currenttransaction())
1264 repo.dirstate.write(repo.currenttransaction())
1263 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
1265 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
1264 if base is not None:
1266 if base is not None:
1265 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
1267 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
1266 # When collapsing in-place, the parent is the common ancestor, we
1268 # When collapsing in-place, the parent is the common ancestor, we
1267 # have to allow merging with it.
1269 # have to allow merging with it.
1268 stats = mergemod.update(repo, rev, branchmerge=True, force=True,
1270 stats = mergemod.update(repo, rev, branchmerge=True, force=True,
1269 ancestor=base, mergeancestor=collapse,
1271 ancestor=base, mergeancestor=collapse,
1270 labels=['dest', 'source'], wc=wctx)
1272 labels=['dest', 'source'], wc=wctx)
1271 if collapse:
1273 if collapse:
1272 copies.duplicatecopies(repo, wctx, rev, dest)
1274 copies.duplicatecopies(repo, wctx, rev, dest)
1273 else:
1275 else:
1274 # If we're not using --collapse, we need to
1276 # If we're not using --collapse, we need to
1275 # duplicate copies between the revision we're
1277 # duplicate copies between the revision we're
1276 # rebasing and its first parent, but *not*
1278 # rebasing and its first parent, but *not*
1277 # duplicate any copies that have already been
1279 # duplicate any copies that have already been
1278 # performed in the destination.
1280 # performed in the destination.
1279 p1rev = repo[rev].p1().rev()
1281 p1rev = repo[rev].p1().rev()
1280 copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
1282 copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
1281 return stats
1283 return stats
1282
1284
1283 def adjustdest(repo, rev, destmap, state, skipped):
1285 def adjustdest(repo, rev, destmap, state, skipped):
1284 r"""adjust rebase destination given the current rebase state
1286 r"""adjust rebase destination given the current rebase state
1285
1287
1286 rev is what is being rebased. Return a list of two revs, which are the
1288 rev is what is being rebased. Return a list of two revs, which are the
1287 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1289 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1288 nullrev, return dest without adjustment for it.
1290 nullrev, return dest without adjustment for it.
1289
1291
1290 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1292 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1291 to B1, and E's destination will be adjusted from F to B1.
1293 to B1, and E's destination will be adjusted from F to B1.
1292
1294
1293 B1 <- written during rebasing B
1295 B1 <- written during rebasing B
1294 |
1296 |
1295 F <- original destination of B, E
1297 F <- original destination of B, E
1296 |
1298 |
1297 | E <- rev, which is being rebased
1299 | E <- rev, which is being rebased
1298 | |
1300 | |
1299 | D <- prev, one parent of rev being checked
1301 | D <- prev, one parent of rev being checked
1300 | |
1302 | |
1301 | x <- skipped, ex. no successor or successor in (::dest)
1303 | x <- skipped, ex. no successor or successor in (::dest)
1302 | |
1304 | |
1303 | C <- rebased as C', different destination
1305 | C <- rebased as C', different destination
1304 | |
1306 | |
1305 | B <- rebased as B1 C'
1307 | B <- rebased as B1 C'
1306 |/ |
1308 |/ |
1307 A G <- destination of C, different
1309 A G <- destination of C, different
1308
1310
1309 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1311 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1310 first move C to C1, G to G1, and when it's checking H, the adjusted
1312 first move C to C1, G to G1, and when it's checking H, the adjusted
1311 destinations will be [C1, G1].
1313 destinations will be [C1, G1].
1312
1314
1313 H C1 G1
1315 H C1 G1
1314 /| | /
1316 /| | /
1315 F G |/
1317 F G |/
1316 K | | -> K
1318 K | | -> K
1317 | C D |
1319 | C D |
1318 | |/ |
1320 | |/ |
1319 | B | ...
1321 | B | ...
1320 |/ |/
1322 |/ |/
1321 A A
1323 A A
1322
1324
1323 Besides, adjust dest according to existing rebase information. For example,
1325 Besides, adjust dest according to existing rebase information. For example,
1324
1326
1325 B C D B needs to be rebased on top of C, C needs to be rebased on top
1327 B C D B needs to be rebased on top of C, C needs to be rebased on top
1326 \|/ of D. We will rebase C first.
1328 \|/ of D. We will rebase C first.
1327 A
1329 A
1328
1330
1329 C' After rebasing C, when considering B's destination, use C'
1331 C' After rebasing C, when considering B's destination, use C'
1330 | instead of the original C.
1332 | instead of the original C.
1331 B D
1333 B D
1332 \ /
1334 \ /
1333 A
1335 A
1334 """
1336 """
1335 # pick already rebased revs with same dest from state as interesting source
1337 # pick already rebased revs with same dest from state as interesting source
1336 dest = destmap[rev]
1338 dest = destmap[rev]
1337 source = [s for s, d in state.items()
1339 source = [s for s, d in state.items()
1338 if d > 0 and destmap[s] == dest and s not in skipped]
1340 if d > 0 and destmap[s] == dest and s not in skipped]
1339
1341
1340 result = []
1342 result = []
1341 for prev in repo.changelog.parentrevs(rev):
1343 for prev in repo.changelog.parentrevs(rev):
1342 adjusted = dest
1344 adjusted = dest
1343 if prev != nullrev:
1345 if prev != nullrev:
1344 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
1346 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
1345 if candidate is not None:
1347 if candidate is not None:
1346 adjusted = state[candidate]
1348 adjusted = state[candidate]
1347 if adjusted == dest and dest in state:
1349 if adjusted == dest and dest in state:
1348 adjusted = state[dest]
1350 adjusted = state[dest]
1349 if adjusted == revtodo:
1351 if adjusted == revtodo:
1350 # sortsource should produce an order that makes this impossible
1352 # sortsource should produce an order that makes this impossible
1351 raise error.ProgrammingError(
1353 raise error.ProgrammingError(
1352 'rev %d should be rebased already at this time' % dest)
1354 'rev %d should be rebased already at this time' % dest)
1353 result.append(adjusted)
1355 result.append(adjusted)
1354 return result
1356 return result
1355
1357
1356 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1358 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1357 """
1359 """
1358 Abort if rebase will create divergence or rebase is noop because of markers
1360 Abort if rebase will create divergence or rebase is noop because of markers
1359
1361
1360 `rebaseobsrevs`: set of obsolete revision in source
1362 `rebaseobsrevs`: set of obsolete revision in source
1361 `rebaseobsskipped`: set of revisions from source skipped because they have
1363 `rebaseobsskipped`: set of revisions from source skipped because they have
1362 successors in destination or no non-obsolete successor.
1364 successors in destination or no non-obsolete successor.
1363 """
1365 """
1364 # Obsolete node with successors not in dest leads to divergence
1366 # Obsolete node with successors not in dest leads to divergence
1365 divergenceok = ui.configbool('experimental',
1367 divergenceok = ui.configbool('experimental',
1366 'evolution.allowdivergence')
1368 'evolution.allowdivergence')
1367 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1369 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1368
1370
1369 if divergencebasecandidates and not divergenceok:
1371 if divergencebasecandidates and not divergenceok:
1370 divhashes = (bytes(repo[r])
1372 divhashes = (bytes(repo[r])
1371 for r in divergencebasecandidates)
1373 for r in divergencebasecandidates)
1372 msg = _("this rebase will cause "
1374 msg = _("this rebase will cause "
1373 "divergences from: %s")
1375 "divergences from: %s")
1374 h = _("to force the rebase please set "
1376 h = _("to force the rebase please set "
1375 "experimental.evolution.allowdivergence=True")
1377 "experimental.evolution.allowdivergence=True")
1376 raise error.Abort(msg % (",".join(divhashes),), hint=h)
1378 raise error.Abort(msg % (",".join(divhashes),), hint=h)
1377
1379
1378 def successorrevs(unfi, rev):
1380 def successorrevs(unfi, rev):
1379 """yield revision numbers for successors of rev"""
1381 """yield revision numbers for successors of rev"""
1380 assert unfi.filtername is None
1382 assert unfi.filtername is None
1381 nodemap = unfi.changelog.nodemap
1383 nodemap = unfi.changelog.nodemap
1382 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1384 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1383 if s in nodemap:
1385 if s in nodemap:
1384 yield nodemap[s]
1386 yield nodemap[s]
1385
1387
1386 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1388 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1387 """Return new parents and optionally a merge base for rev being rebased
1389 """Return new parents and optionally a merge base for rev being rebased
1388
1390
1389 The destination specified by "dest" cannot always be used directly because
1391 The destination specified by "dest" cannot always be used directly because
1390 previously rebase result could affect destination. For example,
1392 previously rebase result could affect destination. For example,
1391
1393
1392 D E rebase -r C+D+E -d B
1394 D E rebase -r C+D+E -d B
1393 |/ C will be rebased to C'
1395 |/ C will be rebased to C'
1394 B C D's new destination will be C' instead of B
1396 B C D's new destination will be C' instead of B
1395 |/ E's new destination will be C' instead of B
1397 |/ E's new destination will be C' instead of B
1396 A
1398 A
1397
1399
1398 The new parents of a merge is slightly more complicated. See the comment
1400 The new parents of a merge is slightly more complicated. See the comment
1399 block below.
1401 block below.
1400 """
1402 """
1401 # use unfiltered changelog since successorrevs may return filtered nodes
1403 # use unfiltered changelog since successorrevs may return filtered nodes
1402 assert repo.filtername is None
1404 assert repo.filtername is None
1403 cl = repo.changelog
1405 cl = repo.changelog
1404 isancestor = cl.isancestorrev
1406 isancestor = cl.isancestorrev
1405
1407
1406 dest = destmap[rev]
1408 dest = destmap[rev]
1407 oldps = repo.changelog.parentrevs(rev) # old parents
1409 oldps = repo.changelog.parentrevs(rev) # old parents
1408 newps = [nullrev, nullrev] # new parents
1410 newps = [nullrev, nullrev] # new parents
1409 dests = adjustdest(repo, rev, destmap, state, skipped)
1411 dests = adjustdest(repo, rev, destmap, state, skipped)
1410 bases = list(oldps) # merge base candidates, initially just old parents
1412 bases = list(oldps) # merge base candidates, initially just old parents
1411
1413
1412 if all(r == nullrev for r in oldps[1:]):
1414 if all(r == nullrev for r in oldps[1:]):
1413 # For non-merge changeset, just move p to adjusted dest as requested.
1415 # For non-merge changeset, just move p to adjusted dest as requested.
1414 newps[0] = dests[0]
1416 newps[0] = dests[0]
1415 else:
1417 else:
1416 # For merge changeset, if we move p to dests[i] unconditionally, both
1418 # For merge changeset, if we move p to dests[i] unconditionally, both
1417 # parents may change and the end result looks like "the merge loses a
1419 # parents may change and the end result looks like "the merge loses a
1418 # parent", which is a surprise. This is a limit because "--dest" only
1420 # parent", which is a surprise. This is a limit because "--dest" only
1419 # accepts one dest per src.
1421 # accepts one dest per src.
1420 #
1422 #
1421 # Therefore, only move p with reasonable conditions (in this order):
1423 # Therefore, only move p with reasonable conditions (in this order):
1422 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1424 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1423 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1425 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1424 #
1426 #
1425 # Comparing with adjustdest, the logic here does some additional work:
1427 # Comparing with adjustdest, the logic here does some additional work:
1426 # 1. decide which parents will not be moved towards dest
1428 # 1. decide which parents will not be moved towards dest
1427 # 2. if the above decision is "no", should a parent still be moved
1429 # 2. if the above decision is "no", should a parent still be moved
1428 # because it was rebased?
1430 # because it was rebased?
1429 #
1431 #
1430 # For example:
1432 # For example:
1431 #
1433 #
1432 # C # "rebase -r C -d D" is an error since none of the parents
1434 # C # "rebase -r C -d D" is an error since none of the parents
1433 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1435 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1434 # A B D # B (using rule "2."), since B will be rebased.
1436 # A B D # B (using rule "2."), since B will be rebased.
1435 #
1437 #
1436 # The loop tries to be not rely on the fact that a Mercurial node has
1438 # The loop tries to be not rely on the fact that a Mercurial node has
1437 # at most 2 parents.
1439 # at most 2 parents.
1438 for i, p in enumerate(oldps):
1440 for i, p in enumerate(oldps):
1439 np = p # new parent
1441 np = p # new parent
1440 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1442 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1441 np = dests[i]
1443 np = dests[i]
1442 elif p in state and state[p] > 0:
1444 elif p in state and state[p] > 0:
1443 np = state[p]
1445 np = state[p]
1444
1446
1445 # "bases" only record "special" merge bases that cannot be
1447 # "bases" only record "special" merge bases that cannot be
1446 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1448 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1447 # For example:
1449 # For example:
1448 #
1450 #
1449 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1451 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1450 # | C # is B', but merge base for C is B, instead of
1452 # | C # is B', but merge base for C is B, instead of
1451 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1453 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1452 # | B # "state" edges are merged (so there will be an edge from
1454 # | B # "state" edges are merged (so there will be an edge from
1453 # |/ # B to B'), the merge base is still ancestor(C, B') in
1455 # |/ # B to B'), the merge base is still ancestor(C, B') in
1454 # A # the merged graph.
1456 # A # the merged graph.
1455 #
1457 #
1456 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1458 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1457 # which uses "virtual null merge" to explain this situation.
1459 # which uses "virtual null merge" to explain this situation.
1458 if isancestor(p, np):
1460 if isancestor(p, np):
1459 bases[i] = nullrev
1461 bases[i] = nullrev
1460
1462
1461 # If one parent becomes an ancestor of the other, drop the ancestor
1463 # If one parent becomes an ancestor of the other, drop the ancestor
1462 for j, x in enumerate(newps[:i]):
1464 for j, x in enumerate(newps[:i]):
1463 if x == nullrev:
1465 if x == nullrev:
1464 continue
1466 continue
1465 if isancestor(np, x): # CASE-1
1467 if isancestor(np, x): # CASE-1
1466 np = nullrev
1468 np = nullrev
1467 elif isancestor(x, np): # CASE-2
1469 elif isancestor(x, np): # CASE-2
1468 newps[j] = np
1470 newps[j] = np
1469 np = nullrev
1471 np = nullrev
1470 # New parents forming an ancestor relationship does not
1472 # New parents forming an ancestor relationship does not
1471 # mean the old parents have a similar relationship. Do not
1473 # mean the old parents have a similar relationship. Do not
1472 # set bases[x] to nullrev.
1474 # set bases[x] to nullrev.
1473 bases[j], bases[i] = bases[i], bases[j]
1475 bases[j], bases[i] = bases[i], bases[j]
1474
1476
1475 newps[i] = np
1477 newps[i] = np
1476
1478
1477 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1479 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1478 # base. If only p2 changes, merging using unchanged p1 as merge base is
1480 # base. If only p2 changes, merging using unchanged p1 as merge base is
1479 # suboptimal. Therefore swap parents to make the merge sane.
1481 # suboptimal. Therefore swap parents to make the merge sane.
1480 if newps[1] != nullrev and oldps[0] == newps[0]:
1482 if newps[1] != nullrev and oldps[0] == newps[0]:
1481 assert len(newps) == 2 and len(oldps) == 2
1483 assert len(newps) == 2 and len(oldps) == 2
1482 newps.reverse()
1484 newps.reverse()
1483 bases.reverse()
1485 bases.reverse()
1484
1486
1485 # No parent change might be an error because we fail to make rev a
1487 # No parent change might be an error because we fail to make rev a
1486 # descendent of requested dest. This can happen, for example:
1488 # descendent of requested dest. This can happen, for example:
1487 #
1489 #
1488 # C # rebase -r C -d D
1490 # C # rebase -r C -d D
1489 # /| # None of A and B will be changed to D and rebase fails.
1491 # /| # None of A and B will be changed to D and rebase fails.
1490 # A B D
1492 # A B D
1491 if set(newps) == set(oldps) and dest not in newps:
1493 if set(newps) == set(oldps) and dest not in newps:
1492 raise error.Abort(_('cannot rebase %d:%s without '
1494 raise error.Abort(_('cannot rebase %d:%s without '
1493 'moving at least one of its parents')
1495 'moving at least one of its parents')
1494 % (rev, repo[rev]))
1496 % (rev, repo[rev]))
1495
1497
1496 # Source should not be ancestor of dest. The check here guarantees it's
1498 # Source should not be ancestor of dest. The check here guarantees it's
1497 # impossible. With multi-dest, the initial check does not cover complex
1499 # impossible. With multi-dest, the initial check does not cover complex
1498 # cases since we don't have abstractions to dry-run rebase cheaply.
1500 # cases since we don't have abstractions to dry-run rebase cheaply.
1499 if any(p != nullrev and isancestor(rev, p) for p in newps):
1501 if any(p != nullrev and isancestor(rev, p) for p in newps):
1500 raise error.Abort(_('source is ancestor of destination'))
1502 raise error.Abort(_('source is ancestor of destination'))
1501
1503
1502 # "rebasenode" updates to new p1, use the corresponding merge base.
1504 # "rebasenode" updates to new p1, use the corresponding merge base.
1503 if bases[0] != nullrev:
1505 if bases[0] != nullrev:
1504 base = bases[0]
1506 base = bases[0]
1505 else:
1507 else:
1506 base = None
1508 base = None
1507
1509
1508 # Check if the merge will contain unwanted changes. That may happen if
1510 # Check if the merge will contain unwanted changes. That may happen if
1509 # there are multiple special (non-changelog ancestor) merge bases, which
1511 # there are multiple special (non-changelog ancestor) merge bases, which
1510 # cannot be handled well by the 3-way merge algorithm. For example:
1512 # cannot be handled well by the 3-way merge algorithm. For example:
1511 #
1513 #
1512 # F
1514 # F
1513 # /|
1515 # /|
1514 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1516 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1515 # | | # as merge base, the difference between D and F will include
1517 # | | # as merge base, the difference between D and F will include
1516 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1518 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1517 # |/ # chosen, the rebased F will contain B.
1519 # |/ # chosen, the rebased F will contain B.
1518 # A Z
1520 # A Z
1519 #
1521 #
1520 # But our merge base candidates (D and E in above case) could still be
1522 # But our merge base candidates (D and E in above case) could still be
1521 # better than the default (ancestor(F, Z) == null). Therefore still
1523 # better than the default (ancestor(F, Z) == null). Therefore still
1522 # pick one (so choose p1 above).
1524 # pick one (so choose p1 above).
1523 if sum(1 for b in bases if b != nullrev) > 1:
1525 if sum(1 for b in bases if b != nullrev) > 1:
1524 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1526 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1525 for i, base in enumerate(bases):
1527 for i, base in enumerate(bases):
1526 if base == nullrev:
1528 if base == nullrev:
1527 continue
1529 continue
1528 # Revisions in the side (not chosen as merge base) branch that
1530 # Revisions in the side (not chosen as merge base) branch that
1529 # might contain "surprising" contents
1531 # might contain "surprising" contents
1530 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1532 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1531 bases, base, base, dest))
1533 bases, base, base, dest))
1532
1534
1533 # If those revisions are covered by rebaseset, the result is good.
1535 # If those revisions are covered by rebaseset, the result is good.
1534 # A merge in rebaseset would be considered to cover its ancestors.
1536 # A merge in rebaseset would be considered to cover its ancestors.
1535 if siderevs:
1537 if siderevs:
1536 rebaseset = [r for r, d in state.items()
1538 rebaseset = [r for r, d in state.items()
1537 if d > 0 and r not in obsskipped]
1539 if d > 0 and r not in obsskipped]
1538 merges = [r for r in rebaseset
1540 merges = [r for r in rebaseset
1539 if cl.parentrevs(r)[1] != nullrev]
1541 if cl.parentrevs(r)[1] != nullrev]
1540 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
1542 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
1541 siderevs, merges, rebaseset))
1543 siderevs, merges, rebaseset))
1542
1544
1543 # Choose a merge base that has a minimal number of unwanted revs.
1545 # Choose a merge base that has a minimal number of unwanted revs.
1544 l, i = min((len(revs), i)
1546 l, i = min((len(revs), i)
1545 for i, revs in enumerate(unwanted) if revs is not None)
1547 for i, revs in enumerate(unwanted) if revs is not None)
1546 base = bases[i]
1548 base = bases[i]
1547
1549
1548 # newps[0] should match merge base if possible. Currently, if newps[i]
1550 # newps[0] should match merge base if possible. Currently, if newps[i]
1549 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1551 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1550 # the other's ancestor. In that case, it's fine to not swap newps here.
1552 # the other's ancestor. In that case, it's fine to not swap newps here.
1551 # (see CASE-1 and CASE-2 above)
1553 # (see CASE-1 and CASE-2 above)
1552 if i != 0 and newps[i] != nullrev:
1554 if i != 0 and newps[i] != nullrev:
1553 newps[0], newps[i] = newps[i], newps[0]
1555 newps[0], newps[i] = newps[i], newps[0]
1554
1556
1555 # The merge will include unwanted revisions. Abort now. Revisit this if
1557 # The merge will include unwanted revisions. Abort now. Revisit this if
1556 # we have a more advanced merge algorithm that handles multiple bases.
1558 # we have a more advanced merge algorithm that handles multiple bases.
1557 if l > 0:
1559 if l > 0:
1558 unwanteddesc = _(' or ').join(
1560 unwanteddesc = _(' or ').join(
1559 (', '.join('%d:%s' % (r, repo[r]) for r in revs)
1561 (', '.join('%d:%s' % (r, repo[r]) for r in revs)
1560 for revs in unwanted if revs is not None))
1562 for revs in unwanted if revs is not None))
1561 raise error.Abort(
1563 raise error.Abort(
1562 _('rebasing %d:%s will include unwanted changes from %s')
1564 _('rebasing %d:%s will include unwanted changes from %s')
1563 % (rev, repo[rev], unwanteddesc))
1565 % (rev, repo[rev], unwanteddesc))
1564
1566
1565 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1567 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1566
1568
1567 return newps[0], newps[1], base
1569 return newps[0], newps[1], base
1568
1570
1569 def isagitpatch(repo, patchname):
1571 def isagitpatch(repo, patchname):
1570 'Return true if the given patch is in git format'
1572 'Return true if the given patch is in git format'
1571 mqpatch = os.path.join(repo.mq.path, patchname)
1573 mqpatch = os.path.join(repo.mq.path, patchname)
1572 for line in patch.linereader(open(mqpatch, 'rb')):
1574 for line in patch.linereader(open(mqpatch, 'rb')):
1573 if line.startswith('diff --git'):
1575 if line.startswith('diff --git'):
1574 return True
1576 return True
1575 return False
1577 return False
1576
1578
1577 def updatemq(repo, state, skipped, **opts):
1579 def updatemq(repo, state, skipped, **opts):
1578 'Update rebased mq patches - finalize and then import them'
1580 'Update rebased mq patches - finalize and then import them'
1579 mqrebase = {}
1581 mqrebase = {}
1580 mq = repo.mq
1582 mq = repo.mq
1581 original_series = mq.fullseries[:]
1583 original_series = mq.fullseries[:]
1582 skippedpatches = set()
1584 skippedpatches = set()
1583
1585
1584 for p in mq.applied:
1586 for p in mq.applied:
1585 rev = repo[p.node].rev()
1587 rev = repo[p.node].rev()
1586 if rev in state:
1588 if rev in state:
1587 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1589 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1588 (rev, p.name))
1590 (rev, p.name))
1589 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1591 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1590 else:
1592 else:
1591 # Applied but not rebased, not sure this should happen
1593 # Applied but not rebased, not sure this should happen
1592 skippedpatches.add(p.name)
1594 skippedpatches.add(p.name)
1593
1595
1594 if mqrebase:
1596 if mqrebase:
1595 mq.finish(repo, mqrebase.keys())
1597 mq.finish(repo, mqrebase.keys())
1596
1598
1597 # We must start import from the newest revision
1599 # We must start import from the newest revision
1598 for rev in sorted(mqrebase, reverse=True):
1600 for rev in sorted(mqrebase, reverse=True):
1599 if rev not in skipped:
1601 if rev not in skipped:
1600 name, isgit = mqrebase[rev]
1602 name, isgit = mqrebase[rev]
1601 repo.ui.note(_('updating mq patch %s to %d:%s\n') %
1603 repo.ui.note(_('updating mq patch %s to %d:%s\n') %
1602 (name, state[rev], repo[state[rev]]))
1604 (name, state[rev], repo[state[rev]]))
1603 mq.qimport(repo, (), patchname=name, git=isgit,
1605 mq.qimport(repo, (), patchname=name, git=isgit,
1604 rev=["%d" % state[rev]])
1606 rev=["%d" % state[rev]])
1605 else:
1607 else:
1606 # Rebased and skipped
1608 # Rebased and skipped
1607 skippedpatches.add(mqrebase[rev][0])
1609 skippedpatches.add(mqrebase[rev][0])
1608
1610
1609 # Patches were either applied and rebased and imported in
1611 # Patches were either applied and rebased and imported in
1610 # order, applied and removed or unapplied. Discard the removed
1612 # order, applied and removed or unapplied. Discard the removed
1611 # ones while preserving the original series order and guards.
1613 # ones while preserving the original series order and guards.
1612 newseries = [s for s in original_series
1614 newseries = [s for s in original_series
1613 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1615 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1614 mq.fullseries[:] = newseries
1616 mq.fullseries[:] = newseries
1615 mq.seriesdirty = True
1617 mq.seriesdirty = True
1616 mq.savedirty()
1618 mq.savedirty()
1617
1619
1618 def storecollapsemsg(repo, collapsemsg):
1620 def storecollapsemsg(repo, collapsemsg):
1619 'Store the collapse message to allow recovery'
1621 'Store the collapse message to allow recovery'
1620 collapsemsg = collapsemsg or ''
1622 collapsemsg = collapsemsg or ''
1621 f = repo.vfs("last-message.txt", "w")
1623 f = repo.vfs("last-message.txt", "w")
1622 f.write("%s\n" % collapsemsg)
1624 f.write("%s\n" % collapsemsg)
1623 f.close()
1625 f.close()
1624
1626
1625 def clearcollapsemsg(repo):
1627 def clearcollapsemsg(repo):
1626 'Remove collapse message file'
1628 'Remove collapse message file'
1627 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1629 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1628
1630
1629 def restorecollapsemsg(repo, isabort):
1631 def restorecollapsemsg(repo, isabort):
1630 'Restore previously stored collapse message'
1632 'Restore previously stored collapse message'
1631 try:
1633 try:
1632 f = repo.vfs("last-message.txt")
1634 f = repo.vfs("last-message.txt")
1633 collapsemsg = f.readline().strip()
1635 collapsemsg = f.readline().strip()
1634 f.close()
1636 f.close()
1635 except IOError as err:
1637 except IOError as err:
1636 if err.errno != errno.ENOENT:
1638 if err.errno != errno.ENOENT:
1637 raise
1639 raise
1638 if isabort:
1640 if isabort:
1639 # Oh well, just abort like normal
1641 # Oh well, just abort like normal
1640 collapsemsg = ''
1642 collapsemsg = ''
1641 else:
1643 else:
1642 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1644 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1643 return collapsemsg
1645 return collapsemsg
1644
1646
1645 def clearstatus(repo):
1647 def clearstatus(repo):
1646 'Remove the status files'
1648 'Remove the status files'
1647 # Make sure the active transaction won't write the state file
1649 # Make sure the active transaction won't write the state file
1648 tr = repo.currenttransaction()
1650 tr = repo.currenttransaction()
1649 if tr:
1651 if tr:
1650 tr.removefilegenerator('rebasestate')
1652 tr.removefilegenerator('rebasestate')
1651 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1653 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1652
1654
1653 def needupdate(repo, state):
1655 def needupdate(repo, state):
1654 '''check whether we should `update --clean` away from a merge, or if
1656 '''check whether we should `update --clean` away from a merge, or if
1655 somehow the working dir got forcibly updated, e.g. by older hg'''
1657 somehow the working dir got forcibly updated, e.g. by older hg'''
1656 parents = [p.rev() for p in repo[None].parents()]
1658 parents = [p.rev() for p in repo[None].parents()]
1657
1659
1658 # Are we in a merge state at all?
1660 # Are we in a merge state at all?
1659 if len(parents) < 2:
1661 if len(parents) < 2:
1660 return False
1662 return False
1661
1663
1662 # We should be standing on the first as-of-yet unrebased commit.
1664 # We should be standing on the first as-of-yet unrebased commit.
1663 firstunrebased = min([old for old, new in state.iteritems()
1665 firstunrebased = min([old for old, new in state.iteritems()
1664 if new == nullrev])
1666 if new == nullrev])
1665 if firstunrebased in parents:
1667 if firstunrebased in parents:
1666 return True
1668 return True
1667
1669
1668 return False
1670 return False
1669
1671
1670 def sortsource(destmap):
1672 def sortsource(destmap):
1671 """yield source revisions in an order that we only rebase things once
1673 """yield source revisions in an order that we only rebase things once
1672
1674
1673 If source and destination overlaps, we should filter out revisions
1675 If source and destination overlaps, we should filter out revisions
1674 depending on other revisions which hasn't been rebased yet.
1676 depending on other revisions which hasn't been rebased yet.
1675
1677
1676 Yield a sorted list of revisions each time.
1678 Yield a sorted list of revisions each time.
1677
1679
1678 For example, when rebasing A to B, B to C. This function yields [B], then
1680 For example, when rebasing A to B, B to C. This function yields [B], then
1679 [A], indicating B needs to be rebased first.
1681 [A], indicating B needs to be rebased first.
1680
1682
1681 Raise if there is a cycle so the rebase is impossible.
1683 Raise if there is a cycle so the rebase is impossible.
1682 """
1684 """
1683 srcset = set(destmap)
1685 srcset = set(destmap)
1684 while srcset:
1686 while srcset:
1685 srclist = sorted(srcset)
1687 srclist = sorted(srcset)
1686 result = []
1688 result = []
1687 for r in srclist:
1689 for r in srclist:
1688 if destmap[r] not in srcset:
1690 if destmap[r] not in srcset:
1689 result.append(r)
1691 result.append(r)
1690 if not result:
1692 if not result:
1691 raise error.Abort(_('source and destination form a cycle'))
1693 raise error.Abort(_('source and destination form a cycle'))
1692 srcset -= set(result)
1694 srcset -= set(result)
1693 yield result
1695 yield result
1694
1696
1695 def buildstate(repo, destmap, collapse):
1697 def buildstate(repo, destmap, collapse):
1696 '''Define which revisions are going to be rebased and where
1698 '''Define which revisions are going to be rebased and where
1697
1699
1698 repo: repo
1700 repo: repo
1699 destmap: {srcrev: destrev}
1701 destmap: {srcrev: destrev}
1700 '''
1702 '''
1701 rebaseset = destmap.keys()
1703 rebaseset = destmap.keys()
1702 originalwd = repo['.'].rev()
1704 originalwd = repo['.'].rev()
1703
1705
1704 # This check isn't strictly necessary, since mq detects commits over an
1706 # This check isn't strictly necessary, since mq detects commits over an
1705 # applied patch. But it prevents messing up the working directory when
1707 # applied patch. But it prevents messing up the working directory when
1706 # a partially completed rebase is blocked by mq.
1708 # a partially completed rebase is blocked by mq.
1707 if 'qtip' in repo.tags():
1709 if 'qtip' in repo.tags():
1708 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
1710 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
1709 if set(destmap.values()) & mqapplied:
1711 if set(destmap.values()) & mqapplied:
1710 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1712 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1711
1713
1712 # Get "cycle" error early by exhausting the generator.
1714 # Get "cycle" error early by exhausting the generator.
1713 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
1715 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
1714 if not sortedsrc:
1716 if not sortedsrc:
1715 raise error.Abort(_('no matching revisions'))
1717 raise error.Abort(_('no matching revisions'))
1716
1718
1717 # Only check the first batch of revisions to rebase not depending on other
1719 # Only check the first batch of revisions to rebase not depending on other
1718 # rebaseset. This means "source is ancestor of destination" for the second
1720 # rebaseset. This means "source is ancestor of destination" for the second
1719 # (and following) batches of revisions are not checked here. We rely on
1721 # (and following) batches of revisions are not checked here. We rely on
1720 # "defineparents" to do that check.
1722 # "defineparents" to do that check.
1721 roots = list(repo.set('roots(%ld)', sortedsrc[0]))
1723 roots = list(repo.set('roots(%ld)', sortedsrc[0]))
1722 if not roots:
1724 if not roots:
1723 raise error.Abort(_('no matching revisions'))
1725 raise error.Abort(_('no matching revisions'))
1724 def revof(r):
1726 def revof(r):
1725 return r.rev()
1727 return r.rev()
1726 roots = sorted(roots, key=revof)
1728 roots = sorted(roots, key=revof)
1727 state = dict.fromkeys(rebaseset, revtodo)
1729 state = dict.fromkeys(rebaseset, revtodo)
1728 emptyrebase = (len(sortedsrc) == 1)
1730 emptyrebase = (len(sortedsrc) == 1)
1729 for root in roots:
1731 for root in roots:
1730 dest = repo[destmap[root.rev()]]
1732 dest = repo[destmap[root.rev()]]
1731 commonbase = root.ancestor(dest)
1733 commonbase = root.ancestor(dest)
1732 if commonbase == root:
1734 if commonbase == root:
1733 raise error.Abort(_('source is ancestor of destination'))
1735 raise error.Abort(_('source is ancestor of destination'))
1734 if commonbase == dest:
1736 if commonbase == dest:
1735 wctx = repo[None]
1737 wctx = repo[None]
1736 if dest == wctx.p1():
1738 if dest == wctx.p1():
1737 # when rebasing to '.', it will use the current wd branch name
1739 # when rebasing to '.', it will use the current wd branch name
1738 samebranch = root.branch() == wctx.branch()
1740 samebranch = root.branch() == wctx.branch()
1739 else:
1741 else:
1740 samebranch = root.branch() == dest.branch()
1742 samebranch = root.branch() == dest.branch()
1741 if not collapse and samebranch and dest in root.parents():
1743 if not collapse and samebranch and dest in root.parents():
1742 # mark the revision as done by setting its new revision
1744 # mark the revision as done by setting its new revision
1743 # equal to its old (current) revisions
1745 # equal to its old (current) revisions
1744 state[root.rev()] = root.rev()
1746 state[root.rev()] = root.rev()
1745 repo.ui.debug('source is a child of destination\n')
1747 repo.ui.debug('source is a child of destination\n')
1746 continue
1748 continue
1747
1749
1748 emptyrebase = False
1750 emptyrebase = False
1749 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1751 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1750 if emptyrebase:
1752 if emptyrebase:
1751 return None
1753 return None
1752 for rev in sorted(state):
1754 for rev in sorted(state):
1753 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1755 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1754 # if all parents of this revision are done, then so is this revision
1756 # if all parents of this revision are done, then so is this revision
1755 if parents and all((state.get(p) == p for p in parents)):
1757 if parents and all((state.get(p) == p for p in parents)):
1756 state[rev] = rev
1758 state[rev] = rev
1757 return originalwd, destmap, state
1759 return originalwd, destmap, state
1758
1760
1759 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
1761 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
1760 keepf=False, fm=None, backup=True):
1762 keepf=False, fm=None, backup=True):
1761 """dispose of rebased revision at the end of the rebase
1763 """dispose of rebased revision at the end of the rebase
1762
1764
1763 If `collapsedas` is not None, the rebase was a collapse whose result if the
1765 If `collapsedas` is not None, the rebase was a collapse whose result if the
1764 `collapsedas` node.
1766 `collapsedas` node.
1765
1767
1766 If `keepf` is not True, the rebase has --keep set and no nodes should be
1768 If `keepf` is not True, the rebase has --keep set and no nodes should be
1767 removed (but bookmarks still need to be moved).
1769 removed (but bookmarks still need to be moved).
1768
1770
1769 If `backup` is False, no backup will be stored when stripping rebased
1771 If `backup` is False, no backup will be stored when stripping rebased
1770 revisions.
1772 revisions.
1771 """
1773 """
1772 tonode = repo.changelog.node
1774 tonode = repo.changelog.node
1773 replacements = {}
1775 replacements = {}
1774 moves = {}
1776 moves = {}
1775 stripcleanup = not obsolete.isenabled(repo, obsolete.createmarkersopt)
1777 stripcleanup = not obsolete.isenabled(repo, obsolete.createmarkersopt)
1776
1778
1777 collapsednodes = []
1779 collapsednodes = []
1778 for rev, newrev in sorted(state.items()):
1780 for rev, newrev in sorted(state.items()):
1779 if newrev >= 0 and newrev != rev:
1781 if newrev >= 0 and newrev != rev:
1780 oldnode = tonode(rev)
1782 oldnode = tonode(rev)
1781 newnode = collapsedas or tonode(newrev)
1783 newnode = collapsedas or tonode(newrev)
1782 moves[oldnode] = newnode
1784 moves[oldnode] = newnode
1783 if not keepf:
1785 if not keepf:
1784 succs = None
1786 succs = None
1785 if rev in skipped:
1787 if rev in skipped:
1786 if stripcleanup or not repo[rev].obsolete():
1788 if stripcleanup or not repo[rev].obsolete():
1787 succs = ()
1789 succs = ()
1788 elif collapsedas:
1790 elif collapsedas:
1789 collapsednodes.append(oldnode)
1791 collapsednodes.append(oldnode)
1790 else:
1792 else:
1791 succs = (newnode,)
1793 succs = (newnode,)
1792 if succs is not None:
1794 if succs is not None:
1793 replacements[(oldnode,)] = succs
1795 replacements[(oldnode,)] = succs
1794 if collapsednodes:
1796 if collapsednodes:
1795 replacements[tuple(collapsednodes)] = (collapsedas,)
1797 replacements[tuple(collapsednodes)] = (collapsedas,)
1796 scmutil.cleanupnodes(repo, replacements, 'rebase', moves, backup=backup)
1798 scmutil.cleanupnodes(repo, replacements, 'rebase', moves, backup=backup)
1797 if fm:
1799 if fm:
1798 hf = fm.hexfunc
1800 hf = fm.hexfunc
1799 fl = fm.formatlist
1801 fl = fm.formatlist
1800 fd = fm.formatdict
1802 fd = fm.formatdict
1801 changes = {}
1803 changes = {}
1802 for oldns, newn in replacements.iteritems():
1804 for oldns, newn in replacements.iteritems():
1803 for oldn in oldns:
1805 for oldn in oldns:
1804 changes[hf(oldn)] = fl([hf(n) for n in newn], name='node')
1806 changes[hf(oldn)] = fl([hf(n) for n in newn], name='node')
1805 nodechanges = fd(changes, key="oldnode", value="newnodes")
1807 nodechanges = fd(changes, key="oldnode", value="newnodes")
1806 fm.data(nodechanges=nodechanges)
1808 fm.data(nodechanges=nodechanges)
1807
1809
1808 def pullrebase(orig, ui, repo, *args, **opts):
1810 def pullrebase(orig, ui, repo, *args, **opts):
1809 'Call rebase after pull if the latter has been invoked with --rebase'
1811 'Call rebase after pull if the latter has been invoked with --rebase'
1810 if opts.get(r'rebase'):
1812 if opts.get(r'rebase'):
1811 if ui.configbool('commands', 'rebase.requiredest'):
1813 if ui.configbool('commands', 'rebase.requiredest'):
1812 msg = _('rebase destination required by configuration')
1814 msg = _('rebase destination required by configuration')
1813 hint = _('use hg pull followed by hg rebase -d DEST')
1815 hint = _('use hg pull followed by hg rebase -d DEST')
1814 raise error.Abort(msg, hint=hint)
1816 raise error.Abort(msg, hint=hint)
1815
1817
1816 with repo.wlock(), repo.lock():
1818 with repo.wlock(), repo.lock():
1817 if opts.get(r'update'):
1819 if opts.get(r'update'):
1818 del opts[r'update']
1820 del opts[r'update']
1819 ui.debug('--update and --rebase are not compatible, ignoring '
1821 ui.debug('--update and --rebase are not compatible, ignoring '
1820 'the update flag\n')
1822 'the update flag\n')
1821
1823
1822 cmdutil.checkunfinished(repo)
1824 cmdutil.checkunfinished(repo)
1823 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1825 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1824 'please commit or shelve your changes first'))
1826 'please commit or shelve your changes first'))
1825
1827
1826 revsprepull = len(repo)
1828 revsprepull = len(repo)
1827 origpostincoming = commands.postincoming
1829 origpostincoming = commands.postincoming
1828 def _dummy(*args, **kwargs):
1830 def _dummy(*args, **kwargs):
1829 pass
1831 pass
1830 commands.postincoming = _dummy
1832 commands.postincoming = _dummy
1831 try:
1833 try:
1832 ret = orig(ui, repo, *args, **opts)
1834 ret = orig(ui, repo, *args, **opts)
1833 finally:
1835 finally:
1834 commands.postincoming = origpostincoming
1836 commands.postincoming = origpostincoming
1835 revspostpull = len(repo)
1837 revspostpull = len(repo)
1836 if revspostpull > revsprepull:
1838 if revspostpull > revsprepull:
1837 # --rev option from pull conflict with rebase own --rev
1839 # --rev option from pull conflict with rebase own --rev
1838 # dropping it
1840 # dropping it
1839 if r'rev' in opts:
1841 if r'rev' in opts:
1840 del opts[r'rev']
1842 del opts[r'rev']
1841 # positional argument from pull conflicts with rebase's own
1843 # positional argument from pull conflicts with rebase's own
1842 # --source.
1844 # --source.
1843 if r'source' in opts:
1845 if r'source' in opts:
1844 del opts[r'source']
1846 del opts[r'source']
1845 # revsprepull is the len of the repo, not revnum of tip.
1847 # revsprepull is the len of the repo, not revnum of tip.
1846 destspace = list(repo.changelog.revs(start=revsprepull))
1848 destspace = list(repo.changelog.revs(start=revsprepull))
1847 opts[r'_destspace'] = destspace
1849 opts[r'_destspace'] = destspace
1848 try:
1850 try:
1849 rebase(ui, repo, **opts)
1851 rebase(ui, repo, **opts)
1850 except error.NoMergeDestAbort:
1852 except error.NoMergeDestAbort:
1851 # we can maybe update instead
1853 # we can maybe update instead
1852 rev, _a, _b = destutil.destupdate(repo)
1854 rev, _a, _b = destutil.destupdate(repo)
1853 if rev == repo['.'].rev():
1855 if rev == repo['.'].rev():
1854 ui.status(_('nothing to rebase\n'))
1856 ui.status(_('nothing to rebase\n'))
1855 else:
1857 else:
1856 ui.status(_('nothing to rebase - updating instead\n'))
1858 ui.status(_('nothing to rebase - updating instead\n'))
1857 # not passing argument to get the bare update behavior
1859 # not passing argument to get the bare update behavior
1858 # with warning and trumpets
1860 # with warning and trumpets
1859 commands.update(ui, repo)
1861 commands.update(ui, repo)
1860 else:
1862 else:
1861 if opts.get(r'tool'):
1863 if opts.get(r'tool'):
1862 raise error.Abort(_('--tool can only be used with --rebase'))
1864 raise error.Abort(_('--tool can only be used with --rebase'))
1863 ret = orig(ui, repo, *args, **opts)
1865 ret = orig(ui, repo, *args, **opts)
1864
1866
1865 return ret
1867 return ret
1866
1868
1867 def _filterobsoleterevs(repo, revs):
1869 def _filterobsoleterevs(repo, revs):
1868 """returns a set of the obsolete revisions in revs"""
1870 """returns a set of the obsolete revisions in revs"""
1869 return set(r for r in revs if repo[r].obsolete())
1871 return set(r for r in revs if repo[r].obsolete())
1870
1872
1871 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
1873 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
1872 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
1874 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
1873
1875
1874 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
1876 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
1875 obsolete nodes to be rebased given in `rebaseobsrevs`.
1877 obsolete nodes to be rebased given in `rebaseobsrevs`.
1876
1878
1877 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
1879 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
1878 without a successor in destination.
1880 without a successor in destination.
1879
1881
1880 `obsoleteextinctsuccessors` is a set of obsolete revisions with only
1882 `obsoleteextinctsuccessors` is a set of obsolete revisions with only
1881 obsolete successors.
1883 obsolete successors.
1882 """
1884 """
1883 obsoletenotrebased = {}
1885 obsoletenotrebased = {}
1884 obsoletewithoutsuccessorindestination = set()
1886 obsoletewithoutsuccessorindestination = set()
1885 obsoleteextinctsuccessors = set()
1887 obsoleteextinctsuccessors = set()
1886
1888
1887 assert repo.filtername is None
1889 assert repo.filtername is None
1888 cl = repo.changelog
1890 cl = repo.changelog
1889 nodemap = cl.nodemap
1891 nodemap = cl.nodemap
1890 extinctrevs = set(repo.revs('extinct()'))
1892 extinctrevs = set(repo.revs('extinct()'))
1891 for srcrev in rebaseobsrevs:
1893 for srcrev in rebaseobsrevs:
1892 srcnode = cl.node(srcrev)
1894 srcnode = cl.node(srcrev)
1893 # XXX: more advanced APIs are required to handle split correctly
1895 # XXX: more advanced APIs are required to handle split correctly
1894 successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode]))
1896 successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode]))
1895 # obsutil.allsuccessors includes node itself
1897 # obsutil.allsuccessors includes node itself
1896 successors.remove(srcnode)
1898 successors.remove(srcnode)
1897 succrevs = {nodemap[s] for s in successors if s in nodemap}
1899 succrevs = {nodemap[s] for s in successors if s in nodemap}
1898 if succrevs.issubset(extinctrevs):
1900 if succrevs.issubset(extinctrevs):
1899 # all successors are extinct
1901 # all successors are extinct
1900 obsoleteextinctsuccessors.add(srcrev)
1902 obsoleteextinctsuccessors.add(srcrev)
1901 if not successors:
1903 if not successors:
1902 # no successor
1904 # no successor
1903 obsoletenotrebased[srcrev] = None
1905 obsoletenotrebased[srcrev] = None
1904 else:
1906 else:
1905 dstrev = destmap[srcrev]
1907 dstrev = destmap[srcrev]
1906 for succrev in succrevs:
1908 for succrev in succrevs:
1907 if cl.isancestorrev(succrev, dstrev):
1909 if cl.isancestorrev(succrev, dstrev):
1908 obsoletenotrebased[srcrev] = succrev
1910 obsoletenotrebased[srcrev] = succrev
1909 break
1911 break
1910 else:
1912 else:
1911 # If 'srcrev' has a successor in rebase set but none in
1913 # If 'srcrev' has a successor in rebase set but none in
1912 # destination (which would be catched above), we shall skip it
1914 # destination (which would be catched above), we shall skip it
1913 # and its descendants to avoid divergence.
1915 # and its descendants to avoid divergence.
1914 if srcrev in extinctrevs or any(s in destmap for s in succrevs):
1916 if srcrev in extinctrevs or any(s in destmap for s in succrevs):
1915 obsoletewithoutsuccessorindestination.add(srcrev)
1917 obsoletewithoutsuccessorindestination.add(srcrev)
1916
1918
1917 return (
1919 return (
1918 obsoletenotrebased,
1920 obsoletenotrebased,
1919 obsoletewithoutsuccessorindestination,
1921 obsoletewithoutsuccessorindestination,
1920 obsoleteextinctsuccessors,
1922 obsoleteextinctsuccessors,
1921 )
1923 )
1922
1924
1923 def summaryhook(ui, repo):
1925 def summaryhook(ui, repo):
1924 if not repo.vfs.exists('rebasestate'):
1926 if not repo.vfs.exists('rebasestate'):
1925 return
1927 return
1926 try:
1928 try:
1927 rbsrt = rebaseruntime(repo, ui, {})
1929 rbsrt = rebaseruntime(repo, ui, {})
1928 rbsrt.restorestatus()
1930 rbsrt.restorestatus()
1929 state = rbsrt.state
1931 state = rbsrt.state
1930 except error.RepoLookupError:
1932 except error.RepoLookupError:
1931 # i18n: column positioning for "hg summary"
1933 # i18n: column positioning for "hg summary"
1932 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1934 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1933 ui.write(msg)
1935 ui.write(msg)
1934 return
1936 return
1935 numrebased = len([i for i in state.itervalues() if i >= 0])
1937 numrebased = len([i for i in state.itervalues() if i >= 0])
1936 # i18n: column positioning for "hg summary"
1938 # i18n: column positioning for "hg summary"
1937 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1939 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1938 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1940 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1939 ui.label(_('%d remaining'), 'rebase.remaining') %
1941 ui.label(_('%d remaining'), 'rebase.remaining') %
1940 (len(state) - numrebased)))
1942 (len(state) - numrebased)))
1941
1943
1942 def uisetup(ui):
1944 def uisetup(ui):
1943 #Replace pull with a decorator to provide --rebase option
1945 #Replace pull with a decorator to provide --rebase option
1944 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1946 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1945 entry[1].append(('', 'rebase', None,
1947 entry[1].append(('', 'rebase', None,
1946 _("rebase working directory to branch head")))
1948 _("rebase working directory to branch head")))
1947 entry[1].append(('t', 'tool', '',
1949 entry[1].append(('t', 'tool', '',
1948 _("specify merge tool for rebase")))
1950 _("specify merge tool for rebase")))
1949 cmdutil.summaryhooks.add('rebase', summaryhook)
1951 cmdutil.summaryhooks.add('rebase', summaryhook)
1950 cmdutil.unfinishedstates.append(
1952 cmdutil.unfinishedstates.append(
1951 ['rebasestate', False, False, _('rebase in progress'),
1953 ['rebasestate', False, False, _('rebase in progress'),
1952 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1954 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1953 cmdutil.afterresolvedstates.append(
1955 cmdutil.afterresolvedstates.append(
1954 ['rebasestate', _('hg rebase --continue')])
1956 ['rebasestate', _('hg rebase --continue')])
General Comments 0
You need to be logged in to leave comments. Login now