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