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