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