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