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