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