##// END OF EJS Templates
rebase: explicitly tests for None...
Pierre-Yves David -
r31431:40670570 default
parent child Browse files
Show More
@@ -1,1490 +1,1491
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 patch,
43 patch,
44 phases,
44 phases,
45 registrar,
45 registrar,
46 repair,
46 repair,
47 repoview,
47 repoview,
48 revset,
48 revset,
49 scmutil,
49 scmutil,
50 smartset,
50 smartset,
51 util,
51 util,
52 )
52 )
53
53
54 release = lock.release
54 release = lock.release
55 templateopts = commands.templateopts
55 templateopts = commands.templateopts
56
56
57 # The following constants are used throughout the rebase module. The ordering of
57 # The following constants are used throughout the rebase module. The ordering of
58 # their values must be maintained.
58 # their values must be maintained.
59
59
60 # Indicates that a revision needs to be rebased
60 # Indicates that a revision needs to be rebased
61 revtodo = -1
61 revtodo = -1
62 nullmerge = -2
62 nullmerge = -2
63 revignored = -3
63 revignored = -3
64 # successor in rebase destination
64 # successor in rebase destination
65 revprecursor = -4
65 revprecursor = -4
66 # plain prune (no successor)
66 # plain prune (no successor)
67 revpruned = -5
67 revpruned = -5
68 revskipped = (revignored, revprecursor, revpruned)
68 revskipped = (revignored, revprecursor, revpruned)
69
69
70 cmdtable = {}
70 cmdtable = {}
71 command = cmdutil.command(cmdtable)
71 command = cmdutil.command(cmdtable)
72 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
72 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
73 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
73 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
74 # be specifying the version(s) of Mercurial they are tested with, or
74 # be specifying the version(s) of Mercurial they are tested with, or
75 # leave the attribute unspecified.
75 # leave the attribute unspecified.
76 testedwith = 'ships-with-hg-core'
76 testedwith = 'ships-with-hg-core'
77
77
78 def _nothingtorebase():
78 def _nothingtorebase():
79 return 1
79 return 1
80
80
81 def _savegraft(ctx, extra):
81 def _savegraft(ctx, extra):
82 s = ctx.extra().get('source', None)
82 s = ctx.extra().get('source', None)
83 if s is not None:
83 if s is not None:
84 extra['source'] = s
84 extra['source'] = s
85 s = ctx.extra().get('intermediate-source', None)
85 s = ctx.extra().get('intermediate-source', None)
86 if s is not None:
86 if s is not None:
87 extra['intermediate-source'] = s
87 extra['intermediate-source'] = s
88
88
89 def _savebranch(ctx, extra):
89 def _savebranch(ctx, extra):
90 extra['branch'] = ctx.branch()
90 extra['branch'] = ctx.branch()
91
91
92 def _makeextrafn(copiers):
92 def _makeextrafn(copiers):
93 """make an extrafn out of the given copy-functions.
93 """make an extrafn out of the given copy-functions.
94
94
95 A copy function takes a context and an extra dict, and mutates the
95 A copy function takes a context and an extra dict, and mutates the
96 extra dict as needed based on the given context.
96 extra dict as needed based on the given context.
97 """
97 """
98 def extrafn(ctx, extra):
98 def extrafn(ctx, extra):
99 for c in copiers:
99 for c in copiers:
100 c(ctx, extra)
100 c(ctx, extra)
101 return extrafn
101 return extrafn
102
102
103 def _destrebase(repo, sourceset, destspace=None):
103 def _destrebase(repo, sourceset, destspace=None):
104 """small wrapper around destmerge to pass the right extra args
104 """small wrapper around destmerge to pass the right extra args
105
105
106 Please wrap destutil.destmerge instead."""
106 Please wrap destutil.destmerge instead."""
107 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
107 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
108 onheadcheck=False, destspace=destspace)
108 onheadcheck=False, destspace=destspace)
109
109
110 revsetpredicate = registrar.revsetpredicate()
110 revsetpredicate = registrar.revsetpredicate()
111
111
112 @revsetpredicate('_destrebase')
112 @revsetpredicate('_destrebase')
113 def _revsetdestrebase(repo, subset, x):
113 def _revsetdestrebase(repo, subset, x):
114 # ``_rebasedefaultdest()``
114 # ``_rebasedefaultdest()``
115
115
116 # default destination for rebase.
116 # default destination for rebase.
117 # # XXX: Currently private because I expect the signature to change.
117 # # XXX: Currently private because I expect the signature to change.
118 # # XXX: - bailing out in case of ambiguity vs returning all data.
118 # # XXX: - bailing out in case of ambiguity vs returning all data.
119 # i18n: "_rebasedefaultdest" is a keyword
119 # i18n: "_rebasedefaultdest" is a keyword
120 sourceset = None
120 sourceset = None
121 if x is not None:
121 if x is not None:
122 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
122 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
123 return subset & smartset.baseset([_destrebase(repo, sourceset)])
123 return subset & smartset.baseset([_destrebase(repo, sourceset)])
124
124
125 class rebaseruntime(object):
125 class rebaseruntime(object):
126 """This class is a container for rebase runtime state"""
126 """This class is a container for rebase runtime state"""
127 def __init__(self, repo, ui, opts=None):
127 def __init__(self, repo, ui, opts=None):
128 if opts is None:
128 if opts is None:
129 opts = {}
129 opts = {}
130
130
131 self.repo = repo
131 self.repo = repo
132 self.ui = ui
132 self.ui = ui
133 self.opts = opts
133 self.opts = opts
134 self.originalwd = None
134 self.originalwd = None
135 self.external = nullrev
135 self.external = nullrev
136 # Mapping between the old revision id and either what is the new rebased
136 # Mapping between the old revision id and either what is the new rebased
137 # revision or what needs to be done with the old revision. The state
137 # revision or what needs to be done with the old revision. The state
138 # dict will be what contains most of the rebase progress state.
138 # dict will be what contains most of the rebase progress state.
139 self.state = {}
139 self.state = {}
140 self.activebookmark = None
140 self.activebookmark = None
141 self.currentbookmarks = None
141 self.currentbookmarks = None
142 self.target = None
142 self.target = None
143 self.skipped = set()
143 self.skipped = set()
144 self.targetancestors = set()
144 self.targetancestors = set()
145
145
146 self.collapsef = opts.get('collapse', False)
146 self.collapsef = opts.get('collapse', False)
147 self.collapsemsg = cmdutil.logmessage(ui, opts)
147 self.collapsemsg = cmdutil.logmessage(ui, opts)
148 self.date = opts.get('date', None)
148 self.date = opts.get('date', None)
149
149
150 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
150 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
151 self.extrafns = [_savegraft]
151 self.extrafns = [_savegraft]
152 if e:
152 if e:
153 self.extrafns = [e]
153 self.extrafns = [e]
154
154
155 self.keepf = opts.get('keep', False)
155 self.keepf = opts.get('keep', False)
156 self.keepbranchesf = opts.get('keepbranches', False)
156 self.keepbranchesf = opts.get('keepbranches', False)
157 # keepopen is not meant for use on the command line, but by
157 # keepopen is not meant for use on the command line, but by
158 # other extensions
158 # other extensions
159 self.keepopen = opts.get('keepopen', False)
159 self.keepopen = opts.get('keepopen', False)
160 self.obsoletenotrebased = {}
160 self.obsoletenotrebased = {}
161
161
162 def storestatus(self, tr=None):
162 def storestatus(self, tr=None):
163 """Store the current status to allow recovery"""
163 """Store the current status to allow recovery"""
164 if tr:
164 if tr:
165 tr.addfilegenerator('rebasestate', ('rebasestate',),
165 tr.addfilegenerator('rebasestate', ('rebasestate',),
166 self._writestatus, location='plain')
166 self._writestatus, location='plain')
167 else:
167 else:
168 with self.repo.vfs("rebasestate", "w") as f:
168 with self.repo.vfs("rebasestate", "w") as f:
169 self._writestatus(f)
169 self._writestatus(f)
170
170
171 def _writestatus(self, f):
171 def _writestatus(self, f):
172 repo = self.repo
172 repo = self.repo
173 f.write(repo[self.originalwd].hex() + '\n')
173 f.write(repo[self.originalwd].hex() + '\n')
174 f.write(repo[self.target].hex() + '\n')
174 f.write(repo[self.target].hex() + '\n')
175 f.write(repo[self.external].hex() + '\n')
175 f.write(repo[self.external].hex() + '\n')
176 f.write('%d\n' % int(self.collapsef))
176 f.write('%d\n' % int(self.collapsef))
177 f.write('%d\n' % int(self.keepf))
177 f.write('%d\n' % int(self.keepf))
178 f.write('%d\n' % int(self.keepbranchesf))
178 f.write('%d\n' % int(self.keepbranchesf))
179 f.write('%s\n' % (self.activebookmark or ''))
179 f.write('%s\n' % (self.activebookmark or ''))
180 for d, v in self.state.iteritems():
180 for d, v in self.state.iteritems():
181 oldrev = repo[d].hex()
181 oldrev = repo[d].hex()
182 if v >= 0:
182 if v >= 0:
183 newrev = repo[v].hex()
183 newrev = repo[v].hex()
184 elif v == revtodo:
184 elif v == revtodo:
185 # To maintain format compatibility, we have to use nullid.
185 # To maintain format compatibility, we have to use nullid.
186 # Please do remove this special case when upgrading the format.
186 # Please do remove this special case when upgrading the format.
187 newrev = hex(nullid)
187 newrev = hex(nullid)
188 else:
188 else:
189 newrev = v
189 newrev = v
190 f.write("%s:%s\n" % (oldrev, newrev))
190 f.write("%s:%s\n" % (oldrev, newrev))
191 repo.ui.debug('rebase status stored\n')
191 repo.ui.debug('rebase status stored\n')
192
192
193 def restorestatus(self):
193 def restorestatus(self):
194 """Restore a previously stored status"""
194 """Restore a previously stored status"""
195 repo = self.repo
195 repo = self.repo
196 keepbranches = None
196 keepbranches = None
197 target = None
197 target = None
198 collapse = False
198 collapse = False
199 external = nullrev
199 external = nullrev
200 activebookmark = None
200 activebookmark = None
201 state = {}
201 state = {}
202
202
203 try:
203 try:
204 f = repo.vfs("rebasestate")
204 f = repo.vfs("rebasestate")
205 for i, l in enumerate(f.read().splitlines()):
205 for i, l in enumerate(f.read().splitlines()):
206 if i == 0:
206 if i == 0:
207 originalwd = repo[l].rev()
207 originalwd = repo[l].rev()
208 elif i == 1:
208 elif i == 1:
209 target = repo[l].rev()
209 target = repo[l].rev()
210 elif i == 2:
210 elif i == 2:
211 external = repo[l].rev()
211 external = repo[l].rev()
212 elif i == 3:
212 elif i == 3:
213 collapse = bool(int(l))
213 collapse = bool(int(l))
214 elif i == 4:
214 elif i == 4:
215 keep = bool(int(l))
215 keep = bool(int(l))
216 elif i == 5:
216 elif i == 5:
217 keepbranches = bool(int(l))
217 keepbranches = bool(int(l))
218 elif i == 6 and not (len(l) == 81 and ':' in l):
218 elif i == 6 and not (len(l) == 81 and ':' in l):
219 # line 6 is a recent addition, so for backwards
219 # line 6 is a recent addition, so for backwards
220 # compatibility check that the line doesn't look like the
220 # compatibility check that the line doesn't look like the
221 # oldrev:newrev lines
221 # oldrev:newrev lines
222 activebookmark = l
222 activebookmark = l
223 else:
223 else:
224 oldrev, newrev = l.split(':')
224 oldrev, newrev = l.split(':')
225 if newrev in (str(nullmerge), str(revignored),
225 if newrev in (str(nullmerge), str(revignored),
226 str(revprecursor), str(revpruned)):
226 str(revprecursor), str(revpruned)):
227 state[repo[oldrev].rev()] = int(newrev)
227 state[repo[oldrev].rev()] = int(newrev)
228 elif newrev == nullid:
228 elif newrev == nullid:
229 state[repo[oldrev].rev()] = revtodo
229 state[repo[oldrev].rev()] = revtodo
230 # Legacy compat special case
230 # Legacy compat special case
231 else:
231 else:
232 state[repo[oldrev].rev()] = repo[newrev].rev()
232 state[repo[oldrev].rev()] = repo[newrev].rev()
233
233
234 except IOError as err:
234 except IOError as err:
235 if err.errno != errno.ENOENT:
235 if err.errno != errno.ENOENT:
236 raise
236 raise
237 cmdutil.wrongtooltocontinue(repo, _('rebase'))
237 cmdutil.wrongtooltocontinue(repo, _('rebase'))
238
238
239 if keepbranches is None:
239 if keepbranches is None:
240 raise error.Abort(_('.hg/rebasestate is incomplete'))
240 raise error.Abort(_('.hg/rebasestate is incomplete'))
241
241
242 skipped = set()
242 skipped = set()
243 # recompute the set of skipped revs
243 # recompute the set of skipped revs
244 if not collapse:
244 if not collapse:
245 seen = set([target])
245 seen = set([target])
246 for old, new in sorted(state.items()):
246 for old, new in sorted(state.items()):
247 if new != revtodo and new in seen:
247 if new != revtodo and new in seen:
248 skipped.add(old)
248 skipped.add(old)
249 seen.add(new)
249 seen.add(new)
250 repo.ui.debug('computed skipped revs: %s\n' %
250 repo.ui.debug('computed skipped revs: %s\n' %
251 (' '.join(str(r) for r in sorted(skipped)) or None))
251 (' '.join(str(r) for r in sorted(skipped)) or None))
252 repo.ui.debug('rebase status resumed\n')
252 repo.ui.debug('rebase status resumed\n')
253 _setrebasesetvisibility(repo, set(state.keys()) | set([originalwd]))
253 _setrebasesetvisibility(repo, set(state.keys()) | set([originalwd]))
254
254
255 self.originalwd = originalwd
255 self.originalwd = originalwd
256 self.target = target
256 self.target = target
257 self.state = state
257 self.state = state
258 self.skipped = skipped
258 self.skipped = skipped
259 self.collapsef = collapse
259 self.collapsef = collapse
260 self.keepf = keep
260 self.keepf = keep
261 self.keepbranchesf = keepbranches
261 self.keepbranchesf = keepbranches
262 self.external = external
262 self.external = external
263 self.activebookmark = activebookmark
263 self.activebookmark = activebookmark
264
264
265 def _handleskippingobsolete(self, rebaserevs, obsoleterevs, target):
265 def _handleskippingobsolete(self, rebaserevs, obsoleterevs, target):
266 """Compute structures necessary for skipping obsolete revisions
266 """Compute structures necessary for skipping obsolete revisions
267
267
268 rebaserevs: iterable of all revisions that are to be rebased
268 rebaserevs: iterable of all revisions that are to be rebased
269 obsoleterevs: iterable of all obsolete revisions in rebaseset
269 obsoleterevs: iterable of all obsolete revisions in rebaseset
270 target: a destination revision for the rebase operation
270 target: a destination revision for the rebase operation
271 """
271 """
272 self.obsoletenotrebased = {}
272 self.obsoletenotrebased = {}
273 if not self.ui.configbool('experimental', 'rebaseskipobsolete',
273 if not self.ui.configbool('experimental', 'rebaseskipobsolete',
274 default=True):
274 default=True):
275 return
275 return
276 rebaseset = set(rebaserevs)
276 rebaseset = set(rebaserevs)
277 obsoleteset = set(obsoleterevs)
277 obsoleteset = set(obsoleterevs)
278 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
278 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
279 obsoleteset, target)
279 obsoleteset, target)
280 skippedset = set(self.obsoletenotrebased)
280 skippedset = set(self.obsoletenotrebased)
281 _checkobsrebase(self.repo, self.ui, obsoleteset, rebaseset, skippedset)
281 _checkobsrebase(self.repo, self.ui, obsoleteset, rebaseset, skippedset)
282
282
283 def _prepareabortorcontinue(self, isabort):
283 def _prepareabortorcontinue(self, isabort):
284 try:
284 try:
285 self.restorestatus()
285 self.restorestatus()
286 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
286 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
287 except error.RepoLookupError:
287 except error.RepoLookupError:
288 if isabort:
288 if isabort:
289 clearstatus(self.repo)
289 clearstatus(self.repo)
290 clearcollapsemsg(self.repo)
290 clearcollapsemsg(self.repo)
291 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
291 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
292 ' only broken state is cleared)\n'))
292 ' only broken state is cleared)\n'))
293 return 0
293 return 0
294 else:
294 else:
295 msg = _('cannot continue inconsistent rebase')
295 msg = _('cannot continue inconsistent rebase')
296 hint = _('use "hg rebase --abort" to clear broken state')
296 hint = _('use "hg rebase --abort" to clear broken state')
297 raise error.Abort(msg, hint=hint)
297 raise error.Abort(msg, hint=hint)
298 if isabort:
298 if isabort:
299 return abort(self.repo, self.originalwd, self.target,
299 return abort(self.repo, self.originalwd, self.target,
300 self.state, activebookmark=self.activebookmark)
300 self.state, activebookmark=self.activebookmark)
301
301
302 obsrevs = (r for r, st in self.state.items() if st == revprecursor)
302 obsrevs = (r for r, st in self.state.items() if st == revprecursor)
303 self._handleskippingobsolete(self.state.keys(), obsrevs, self.target)
303 self._handleskippingobsolete(self.state.keys(), obsrevs, self.target)
304
304
305 def _preparenewrebase(self, dest, rebaseset):
305 def _preparenewrebase(self, dest, rebaseset):
306 if dest is None:
306 if dest is None:
307 return _nothingtorebase()
307 return _nothingtorebase()
308
308
309 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
309 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
310 if (not (self.keepf or allowunstable)
310 if (not (self.keepf or allowunstable)
311 and self.repo.revs('first(children(%ld) - %ld)',
311 and self.repo.revs('first(children(%ld) - %ld)',
312 rebaseset, rebaseset)):
312 rebaseset, rebaseset)):
313 raise error.Abort(
313 raise error.Abort(
314 _("can't remove original changesets with"
314 _("can't remove original changesets with"
315 " unrebased descendants"),
315 " unrebased descendants"),
316 hint=_('use --keep to keep original changesets'))
316 hint=_('use --keep to keep original changesets'))
317
317
318 obsrevs = _filterobsoleterevs(self.repo, set(rebaseset))
318 obsrevs = _filterobsoleterevs(self.repo, set(rebaseset))
319 self._handleskippingobsolete(rebaseset, obsrevs, dest)
319 self._handleskippingobsolete(rebaseset, obsrevs, dest)
320
320
321 result = buildstate(self.repo, dest, rebaseset, self.collapsef,
321 result = buildstate(self.repo, dest, rebaseset, self.collapsef,
322 self.obsoletenotrebased)
322 self.obsoletenotrebased)
323
323
324 if not result:
324 if not result:
325 # Empty state built, nothing to rebase
325 # Empty state built, nothing to rebase
326 self.ui.status(_('nothing to rebase\n'))
326 self.ui.status(_('nothing to rebase\n'))
327 return _nothingtorebase()
327 return _nothingtorebase()
328
328
329 for root in self.repo.set('roots(%ld)', rebaseset):
329 for root in self.repo.set('roots(%ld)', rebaseset):
330 if not self.keepf and not root.mutable():
330 if not self.keepf and not root.mutable():
331 raise error.Abort(_("can't rebase public changeset %s")
331 raise error.Abort(_("can't rebase public changeset %s")
332 % root,
332 % root,
333 hint=_("see 'hg help phases' for details"))
333 hint=_("see 'hg help phases' for details"))
334
334
335 (self.originalwd, self.target, self.state) = result
335 (self.originalwd, self.target, self.state) = result
336 if self.collapsef:
336 if self.collapsef:
337 self.targetancestors = self.repo.changelog.ancestors(
337 self.targetancestors = self.repo.changelog.ancestors(
338 [self.target],
338 [self.target],
339 inclusive=True)
339 inclusive=True)
340 self.external = externalparent(self.repo, self.state,
340 self.external = externalparent(self.repo, self.state,
341 self.targetancestors)
341 self.targetancestors)
342
342
343 if dest.closesbranch() and not self.keepbranchesf:
343 if dest.closesbranch() and not self.keepbranchesf:
344 self.ui.status(_('reopening closed branch head %s\n') % dest)
344 self.ui.status(_('reopening closed branch head %s\n') % dest)
345
345
346 def _performrebase(self, tr):
346 def _performrebase(self, tr):
347 repo, ui, opts = self.repo, self.ui, self.opts
347 repo, ui, opts = self.repo, self.ui, self.opts
348 if self.keepbranchesf:
348 if self.keepbranchesf:
349 # insert _savebranch at the start of extrafns so if
349 # insert _savebranch at the start of extrafns so if
350 # there's a user-provided extrafn it can clobber branch if
350 # there's a user-provided extrafn it can clobber branch if
351 # desired
351 # desired
352 self.extrafns.insert(0, _savebranch)
352 self.extrafns.insert(0, _savebranch)
353 if self.collapsef:
353 if self.collapsef:
354 branches = set()
354 branches = set()
355 for rev in self.state:
355 for rev in self.state:
356 branches.add(repo[rev].branch())
356 branches.add(repo[rev].branch())
357 if len(branches) > 1:
357 if len(branches) > 1:
358 raise error.Abort(_('cannot collapse multiple named '
358 raise error.Abort(_('cannot collapse multiple named '
359 'branches'))
359 'branches'))
360
360
361 # Rebase
361 # Rebase
362 if not self.targetancestors:
362 if not self.targetancestors:
363 self.targetancestors = repo.changelog.ancestors([self.target],
363 self.targetancestors = repo.changelog.ancestors([self.target],
364 inclusive=True)
364 inclusive=True)
365
365
366 # Keep track of the current bookmarks in order to reset them later
366 # Keep track of the current bookmarks in order to reset them later
367 self.currentbookmarks = repo._bookmarks.copy()
367 self.currentbookmarks = repo._bookmarks.copy()
368 self.activebookmark = self.activebookmark or repo._activebookmark
368 self.activebookmark = self.activebookmark or repo._activebookmark
369 if self.activebookmark:
369 if self.activebookmark:
370 bookmarks.deactivate(repo)
370 bookmarks.deactivate(repo)
371
371
372 # Store the state before we begin so users can run 'hg rebase --abort'
372 # Store the state before we begin so users can run 'hg rebase --abort'
373 # if we fail before the transaction closes.
373 # if we fail before the transaction closes.
374 self.storestatus()
374 self.storestatus()
375
375
376 sortedrevs = repo.revs('sort(%ld, -topo)', self.state)
376 sortedrevs = repo.revs('sort(%ld, -topo)', self.state)
377 cands = [k for k, v in self.state.iteritems() if v == revtodo]
377 cands = [k for k, v in self.state.iteritems() if v == revtodo]
378 total = len(cands)
378 total = len(cands)
379 pos = 0
379 pos = 0
380 for rev in sortedrevs:
380 for rev in sortedrevs:
381 ctx = repo[rev]
381 ctx = repo[rev]
382 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
382 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
383 ctx.description().split('\n', 1)[0])
383 ctx.description().split('\n', 1)[0])
384 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
384 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
385 if names:
385 if names:
386 desc += ' (%s)' % ' '.join(names)
386 desc += ' (%s)' % ' '.join(names)
387 if self.state[rev] == revtodo:
387 if self.state[rev] == revtodo:
388 pos += 1
388 pos += 1
389 ui.status(_('rebasing %s\n') % desc)
389 ui.status(_('rebasing %s\n') % desc)
390 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
390 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
391 _('changesets'), total)
391 _('changesets'), total)
392 p1, p2, base = defineparents(repo, rev, self.target,
392 p1, p2, base = defineparents(repo, rev, self.target,
393 self.state,
393 self.state,
394 self.targetancestors,
394 self.targetancestors,
395 self.obsoletenotrebased)
395 self.obsoletenotrebased)
396 self.storestatus(tr=tr)
396 self.storestatus(tr=tr)
397 storecollapsemsg(repo, self.collapsemsg)
397 storecollapsemsg(repo, self.collapsemsg)
398 if len(repo[None].parents()) == 2:
398 if len(repo[None].parents()) == 2:
399 repo.ui.debug('resuming interrupted rebase\n')
399 repo.ui.debug('resuming interrupted rebase\n')
400 else:
400 else:
401 try:
401 try:
402 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
402 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
403 'rebase')
403 'rebase')
404 stats = rebasenode(repo, rev, p1, base, self.state,
404 stats = rebasenode(repo, rev, p1, base, self.state,
405 self.collapsef, self.target)
405 self.collapsef, self.target)
406 if stats and stats[3] > 0:
406 if stats and stats[3] > 0:
407 raise error.InterventionRequired(
407 raise error.InterventionRequired(
408 _('unresolved conflicts (see hg '
408 _('unresolved conflicts (see hg '
409 'resolve, then hg rebase --continue)'))
409 'resolve, then hg rebase --continue)'))
410 finally:
410 finally:
411 ui.setconfig('ui', 'forcemerge', '', 'rebase')
411 ui.setconfig('ui', 'forcemerge', '', 'rebase')
412 if not self.collapsef:
412 if not self.collapsef:
413 merging = p2 != nullrev
413 merging = p2 != nullrev
414 editform = cmdutil.mergeeditform(merging, 'rebase')
414 editform = cmdutil.mergeeditform(merging, 'rebase')
415 editor = cmdutil.getcommiteditor(editform=editform, **opts)
415 editor = cmdutil.getcommiteditor(editform=editform, **opts)
416 newnode = concludenode(repo, rev, p1, p2,
416 newnode = concludenode(repo, rev, p1, p2,
417 extrafn=_makeextrafn(self.extrafns),
417 extrafn=_makeextrafn(self.extrafns),
418 editor=editor,
418 editor=editor,
419 keepbranches=self.keepbranchesf,
419 keepbranches=self.keepbranchesf,
420 date=self.date)
420 date=self.date)
421 else:
421 else:
422 # Skip commit if we are collapsing
422 # Skip commit if we are collapsing
423 repo.dirstate.beginparentchange()
423 repo.dirstate.beginparentchange()
424 repo.setparents(repo[p1].node())
424 repo.setparents(repo[p1].node())
425 repo.dirstate.endparentchange()
425 repo.dirstate.endparentchange()
426 newnode = None
426 newnode = None
427 # Update the state
427 # Update the state
428 if newnode is not None:
428 if newnode is not None:
429 self.state[rev] = repo[newnode].rev()
429 self.state[rev] = repo[newnode].rev()
430 ui.debug('rebased as %s\n' % short(newnode))
430 ui.debug('rebased as %s\n' % short(newnode))
431 else:
431 else:
432 if not self.collapsef:
432 if not self.collapsef:
433 ui.warn(_('note: rebase of %d:%s created no changes '
433 ui.warn(_('note: rebase of %d:%s created no changes '
434 'to commit\n') % (rev, ctx))
434 'to commit\n') % (rev, ctx))
435 self.skipped.add(rev)
435 self.skipped.add(rev)
436 self.state[rev] = p1
436 self.state[rev] = p1
437 ui.debug('next revision set to %s\n' % p1)
437 ui.debug('next revision set to %s\n' % p1)
438 elif self.state[rev] == nullmerge:
438 elif self.state[rev] == nullmerge:
439 ui.debug('ignoring null merge rebase of %s\n' % rev)
439 ui.debug('ignoring null merge rebase of %s\n' % rev)
440 elif self.state[rev] == revignored:
440 elif self.state[rev] == revignored:
441 ui.status(_('not rebasing ignored %s\n') % desc)
441 ui.status(_('not rebasing ignored %s\n') % desc)
442 elif self.state[rev] == revprecursor:
442 elif self.state[rev] == revprecursor:
443 targetctx = repo[self.obsoletenotrebased[rev]]
443 targetctx = repo[self.obsoletenotrebased[rev]]
444 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
444 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
445 targetctx.description().split('\n', 1)[0])
445 targetctx.description().split('\n', 1)[0])
446 msg = _('note: not rebasing %s, already in destination as %s\n')
446 msg = _('note: not rebasing %s, already in destination as %s\n')
447 ui.status(msg % (desc, desctarget))
447 ui.status(msg % (desc, desctarget))
448 elif self.state[rev] == revpruned:
448 elif self.state[rev] == revpruned:
449 msg = _('note: not rebasing %s, it has no successor\n')
449 msg = _('note: not rebasing %s, it has no successor\n')
450 ui.status(msg % desc)
450 ui.status(msg % desc)
451 else:
451 else:
452 ui.status(_('already rebased %s as %s\n') %
452 ui.status(_('already rebased %s as %s\n') %
453 (desc, repo[self.state[rev]]))
453 (desc, repo[self.state[rev]]))
454
454
455 ui.progress(_('rebasing'), None)
455 ui.progress(_('rebasing'), None)
456 ui.note(_('rebase merging completed\n'))
456 ui.note(_('rebase merging completed\n'))
457
457
458 def _finishrebase(self):
458 def _finishrebase(self):
459 repo, ui, opts = self.repo, self.ui, self.opts
459 repo, ui, opts = self.repo, self.ui, self.opts
460 if self.collapsef and not self.keepopen:
460 if self.collapsef and not self.keepopen:
461 p1, p2, _base = defineparents(repo, min(self.state),
461 p1, p2, _base = defineparents(repo, min(self.state),
462 self.target, self.state,
462 self.target, self.state,
463 self.targetancestors,
463 self.targetancestors,
464 self.obsoletenotrebased)
464 self.obsoletenotrebased)
465 editopt = opts.get('edit')
465 editopt = opts.get('edit')
466 editform = 'rebase.collapse'
466 editform = 'rebase.collapse'
467 if self.collapsemsg:
467 if self.collapsemsg:
468 commitmsg = self.collapsemsg
468 commitmsg = self.collapsemsg
469 else:
469 else:
470 commitmsg = 'Collapsed revision'
470 commitmsg = 'Collapsed revision'
471 for rebased in self.state:
471 for rebased in self.state:
472 if rebased not in self.skipped and\
472 if rebased not in self.skipped and\
473 self.state[rebased] > nullmerge:
473 self.state[rebased] > nullmerge:
474 commitmsg += '\n* %s' % repo[rebased].description()
474 commitmsg += '\n* %s' % repo[rebased].description()
475 editopt = True
475 editopt = True
476 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
476 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
477 revtoreuse = max(self.state)
477 revtoreuse = max(self.state)
478 newnode = concludenode(repo, revtoreuse, p1, self.external,
478 newnode = concludenode(repo, revtoreuse, p1, self.external,
479 commitmsg=commitmsg,
479 commitmsg=commitmsg,
480 extrafn=_makeextrafn(self.extrafns),
480 extrafn=_makeextrafn(self.extrafns),
481 editor=editor,
481 editor=editor,
482 keepbranches=self.keepbranchesf,
482 keepbranches=self.keepbranchesf,
483 date=self.date)
483 date=self.date)
484 if newnode is None:
484 if newnode is None:
485 newrev = self.target
485 newrev = self.target
486 else:
486 else:
487 newrev = repo[newnode].rev()
487 newrev = repo[newnode].rev()
488 for oldrev in self.state.iterkeys():
488 for oldrev in self.state.iterkeys():
489 if self.state[oldrev] > nullmerge:
489 if self.state[oldrev] > nullmerge:
490 self.state[oldrev] = newrev
490 self.state[oldrev] = newrev
491
491
492 if 'qtip' in repo.tags():
492 if 'qtip' in repo.tags():
493 updatemq(repo, self.state, self.skipped, **opts)
493 updatemq(repo, self.state, self.skipped, **opts)
494
494
495 if self.currentbookmarks:
495 if self.currentbookmarks:
496 # Nodeids are needed to reset bookmarks
496 # Nodeids are needed to reset bookmarks
497 nstate = {}
497 nstate = {}
498 for k, v in self.state.iteritems():
498 for k, v in self.state.iteritems():
499 if v > nullmerge:
499 if v > nullmerge:
500 nstate[repo[k].node()] = repo[v].node()
500 nstate[repo[k].node()] = repo[v].node()
501 elif v == revprecursor:
501 elif v == revprecursor:
502 succ = self.obsoletenotrebased[k]
502 succ = self.obsoletenotrebased[k]
503 nstate[repo[k].node()] = repo[succ].node()
503 nstate[repo[k].node()] = repo[succ].node()
504 # XXX this is the same as dest.node() for the non-continue path --
504 # XXX this is the same as dest.node() for the non-continue path --
505 # this should probably be cleaned up
505 # this should probably be cleaned up
506 targetnode = repo[self.target].node()
506 targetnode = repo[self.target].node()
507
507
508 # restore original working directory
508 # restore original working directory
509 # (we do this before stripping)
509 # (we do this before stripping)
510 newwd = self.state.get(self.originalwd, self.originalwd)
510 newwd = self.state.get(self.originalwd, self.originalwd)
511 if newwd == revprecursor:
511 if newwd == revprecursor:
512 newwd = self.obsoletenotrebased[self.originalwd]
512 newwd = self.obsoletenotrebased[self.originalwd]
513 elif newwd < 0:
513 elif newwd < 0:
514 # original directory is a parent of rebase set root or ignored
514 # original directory is a parent of rebase set root or ignored
515 newwd = self.originalwd
515 newwd = self.originalwd
516 if newwd not in [c.rev() for c in repo[None].parents()]:
516 if newwd not in [c.rev() for c in repo[None].parents()]:
517 ui.note(_("update back to initial working directory parent\n"))
517 ui.note(_("update back to initial working directory parent\n"))
518 hg.updaterepo(repo, newwd, False)
518 hg.updaterepo(repo, newwd, False)
519
519
520 if self.currentbookmarks:
520 if self.currentbookmarks:
521 with repo.transaction('bookmark') as tr:
521 with repo.transaction('bookmark') as tr:
522 updatebookmarks(repo, targetnode, nstate,
522 updatebookmarks(repo, targetnode, nstate,
523 self.currentbookmarks, tr)
523 self.currentbookmarks, tr)
524 if self.activebookmark not in repo._bookmarks:
524 if self.activebookmark not in repo._bookmarks:
525 # active bookmark was divergent one and has been deleted
525 # active bookmark was divergent one and has been deleted
526 self.activebookmark = None
526 self.activebookmark = None
527
527
528 if not self.keepf:
528 if not self.keepf:
529 collapsedas = None
529 collapsedas = None
530 if self.collapsef:
530 if self.collapsef:
531 collapsedas = newnode
531 collapsedas = newnode
532 clearrebased(ui, repo, self.state, self.skipped, collapsedas)
532 clearrebased(ui, repo, self.state, self.skipped, collapsedas)
533
533
534 clearstatus(repo)
534 clearstatus(repo)
535 clearcollapsemsg(repo)
535 clearcollapsemsg(repo)
536
536
537 ui.note(_("rebase completed\n"))
537 ui.note(_("rebase completed\n"))
538 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
538 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
539 if self.skipped:
539 if self.skipped:
540 skippedlen = len(self.skipped)
540 skippedlen = len(self.skipped)
541 ui.note(_("%d revisions have been skipped\n") % skippedlen)
541 ui.note(_("%d revisions have been skipped\n") % skippedlen)
542
542
543 if (self.activebookmark and
543 if (self.activebookmark and
544 repo['.'].node() == repo._bookmarks[self.activebookmark]):
544 repo['.'].node() == repo._bookmarks[self.activebookmark]):
545 bookmarks.activate(repo, self.activebookmark)
545 bookmarks.activate(repo, self.activebookmark)
546
546
547 @command('rebase',
547 @command('rebase',
548 [('s', 'source', '',
548 [('s', 'source', '',
549 _('rebase the specified changeset and descendants'), _('REV')),
549 _('rebase the specified changeset and descendants'), _('REV')),
550 ('b', 'base', '',
550 ('b', 'base', '',
551 _('rebase everything from branching point of specified changeset'),
551 _('rebase everything from branching point of specified changeset'),
552 _('REV')),
552 _('REV')),
553 ('r', 'rev', [],
553 ('r', 'rev', [],
554 _('rebase these revisions'),
554 _('rebase these revisions'),
555 _('REV')),
555 _('REV')),
556 ('d', 'dest', '',
556 ('d', 'dest', '',
557 _('rebase onto the specified changeset'), _('REV')),
557 _('rebase onto the specified changeset'), _('REV')),
558 ('', 'collapse', False, _('collapse the rebased changesets')),
558 ('', 'collapse', False, _('collapse the rebased changesets')),
559 ('m', 'message', '',
559 ('m', 'message', '',
560 _('use text as collapse commit message'), _('TEXT')),
560 _('use text as collapse commit message'), _('TEXT')),
561 ('e', 'edit', False, _('invoke editor on commit messages')),
561 ('e', 'edit', False, _('invoke editor on commit messages')),
562 ('l', 'logfile', '',
562 ('l', 'logfile', '',
563 _('read collapse commit message from file'), _('FILE')),
563 _('read collapse commit message from file'), _('FILE')),
564 ('k', 'keep', False, _('keep original changesets')),
564 ('k', 'keep', False, _('keep original changesets')),
565 ('', 'keepbranches', False, _('keep original branch names')),
565 ('', 'keepbranches', False, _('keep original branch names')),
566 ('D', 'detach', False, _('(DEPRECATED)')),
566 ('D', 'detach', False, _('(DEPRECATED)')),
567 ('i', 'interactive', False, _('(DEPRECATED)')),
567 ('i', 'interactive', False, _('(DEPRECATED)')),
568 ('t', 'tool', '', _('specify merge tool')),
568 ('t', 'tool', '', _('specify merge tool')),
569 ('c', 'continue', False, _('continue an interrupted rebase')),
569 ('c', 'continue', False, _('continue an interrupted rebase')),
570 ('a', 'abort', False, _('abort an interrupted rebase'))] +
570 ('a', 'abort', False, _('abort an interrupted rebase'))] +
571 templateopts,
571 templateopts,
572 _('[-s REV | -b REV] [-d REV] [OPTION]'))
572 _('[-s REV | -b REV] [-d REV] [OPTION]'))
573 def rebase(ui, repo, **opts):
573 def rebase(ui, repo, **opts):
574 """move changeset (and descendants) to a different branch
574 """move changeset (and descendants) to a different branch
575
575
576 Rebase uses repeated merging to graft changesets from one part of
576 Rebase uses repeated merging to graft changesets from one part of
577 history (the source) onto another (the destination). This can be
577 history (the source) onto another (the destination). This can be
578 useful for linearizing *local* changes relative to a master
578 useful for linearizing *local* changes relative to a master
579 development tree.
579 development tree.
580
580
581 Published commits cannot be rebased (see :hg:`help phases`).
581 Published commits cannot be rebased (see :hg:`help phases`).
582 To copy commits, see :hg:`help graft`.
582 To copy commits, see :hg:`help graft`.
583
583
584 If you don't specify a destination changeset (``-d/--dest``), rebase
584 If you don't specify a destination changeset (``-d/--dest``), rebase
585 will use the same logic as :hg:`merge` to pick a destination. if
585 will use the same logic as :hg:`merge` to pick a destination. if
586 the current branch contains exactly one other head, the other head
586 the current branch contains exactly one other head, the other head
587 is merged with by default. Otherwise, an explicit revision with
587 is merged with by default. Otherwise, an explicit revision with
588 which to merge with must be provided. (destination changeset is not
588 which to merge with must be provided. (destination changeset is not
589 modified by rebasing, but new changesets are added as its
589 modified by rebasing, but new changesets are added as its
590 descendants.)
590 descendants.)
591
591
592 Here are the ways to select changesets:
592 Here are the ways to select changesets:
593
593
594 1. Explicitly select them using ``--rev``.
594 1. Explicitly select them using ``--rev``.
595
595
596 2. Use ``--source`` to select a root changeset and include all of its
596 2. Use ``--source`` to select a root changeset and include all of its
597 descendants.
597 descendants.
598
598
599 3. Use ``--base`` to select a changeset; rebase will find ancestors
599 3. Use ``--base`` to select a changeset; rebase will find ancestors
600 and their descendants which are not also ancestors of the destination.
600 and their descendants which are not also ancestors of the destination.
601
601
602 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
602 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
603 rebase will use ``--base .`` as above.
603 rebase will use ``--base .`` as above.
604
604
605 Rebase will destroy original changesets unless you use ``--keep``.
605 Rebase will destroy original changesets unless you use ``--keep``.
606 It will also move your bookmarks (even if you do).
606 It will also move your bookmarks (even if you do).
607
607
608 Some changesets may be dropped if they do not contribute changes
608 Some changesets may be dropped if they do not contribute changes
609 (e.g. merges from the destination branch).
609 (e.g. merges from the destination branch).
610
610
611 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
611 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
612 a named branch with two heads. You will need to explicitly specify source
612 a named branch with two heads. You will need to explicitly specify source
613 and/or destination.
613 and/or destination.
614
614
615 If you need to use a tool to automate merge/conflict decisions, you
615 If you need to use a tool to automate merge/conflict decisions, you
616 can specify one with ``--tool``, see :hg:`help merge-tools`.
616 can specify one with ``--tool``, see :hg:`help merge-tools`.
617 As a caveat: the tool will not be used to mediate when a file was
617 As a caveat: the tool will not be used to mediate when a file was
618 deleted, there is no hook presently available for this.
618 deleted, there is no hook presently available for this.
619
619
620 If a rebase is interrupted to manually resolve a conflict, it can be
620 If a rebase is interrupted to manually resolve a conflict, it can be
621 continued with --continue/-c or aborted with --abort/-a.
621 continued with --continue/-c or aborted with --abort/-a.
622
622
623 .. container:: verbose
623 .. container:: verbose
624
624
625 Examples:
625 Examples:
626
626
627 - move "local changes" (current commit back to branching point)
627 - move "local changes" (current commit back to branching point)
628 to the current branch tip after a pull::
628 to the current branch tip after a pull::
629
629
630 hg rebase
630 hg rebase
631
631
632 - move a single changeset to the stable branch::
632 - move a single changeset to the stable branch::
633
633
634 hg rebase -r 5f493448 -d stable
634 hg rebase -r 5f493448 -d stable
635
635
636 - splice a commit and all its descendants onto another part of history::
636 - splice a commit and all its descendants onto another part of history::
637
637
638 hg rebase --source c0c3 --dest 4cf9
638 hg rebase --source c0c3 --dest 4cf9
639
639
640 - rebase everything on a branch marked by a bookmark onto the
640 - rebase everything on a branch marked by a bookmark onto the
641 default branch::
641 default branch::
642
642
643 hg rebase --base myfeature --dest default
643 hg rebase --base myfeature --dest default
644
644
645 - collapse a sequence of changes into a single commit::
645 - collapse a sequence of changes into a single commit::
646
646
647 hg rebase --collapse -r 1520:1525 -d .
647 hg rebase --collapse -r 1520:1525 -d .
648
648
649 - move a named branch while preserving its name::
649 - move a named branch while preserving its name::
650
650
651 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
651 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
652
652
653 Returns 0 on success, 1 if nothing to rebase or there are
653 Returns 0 on success, 1 if nothing to rebase or there are
654 unresolved conflicts.
654 unresolved conflicts.
655
655
656 """
656 """
657 rbsrt = rebaseruntime(repo, ui, opts)
657 rbsrt = rebaseruntime(repo, ui, opts)
658
658
659 lock = wlock = None
659 lock = wlock = None
660 try:
660 try:
661 wlock = repo.wlock()
661 wlock = repo.wlock()
662 lock = repo.lock()
662 lock = repo.lock()
663
663
664 # Validate input and define rebasing points
664 # Validate input and define rebasing points
665 destf = opts.get('dest', None)
665 destf = opts.get('dest', None)
666 srcf = opts.get('source', None)
666 srcf = opts.get('source', None)
667 basef = opts.get('base', None)
667 basef = opts.get('base', None)
668 revf = opts.get('rev', [])
668 revf = opts.get('rev', [])
669 # search default destination in this space
669 # search default destination in this space
670 # used in the 'hg pull --rebase' case, see issue 5214.
670 # used in the 'hg pull --rebase' case, see issue 5214.
671 destspace = opts.get('_destspace')
671 destspace = opts.get('_destspace')
672 contf = opts.get('continue')
672 contf = opts.get('continue')
673 abortf = opts.get('abort')
673 abortf = opts.get('abort')
674 if opts.get('interactive'):
674 if opts.get('interactive'):
675 try:
675 try:
676 if extensions.find('histedit'):
676 if extensions.find('histedit'):
677 enablehistedit = ''
677 enablehistedit = ''
678 except KeyError:
678 except KeyError:
679 enablehistedit = " --config extensions.histedit="
679 enablehistedit = " --config extensions.histedit="
680 help = "hg%s help -e histedit" % enablehistedit
680 help = "hg%s help -e histedit" % enablehistedit
681 msg = _("interactive history editing is supported by the "
681 msg = _("interactive history editing is supported by the "
682 "'histedit' extension (see \"%s\")") % help
682 "'histedit' extension (see \"%s\")") % help
683 raise error.Abort(msg)
683 raise error.Abort(msg)
684
684
685 if rbsrt.collapsemsg and not rbsrt.collapsef:
685 if rbsrt.collapsemsg and not rbsrt.collapsef:
686 raise error.Abort(
686 raise error.Abort(
687 _('message can only be specified with collapse'))
687 _('message can only be specified with collapse'))
688
688
689 if contf or abortf:
689 if contf or abortf:
690 if contf and abortf:
690 if contf and abortf:
691 raise error.Abort(_('cannot use both abort and continue'))
691 raise error.Abort(_('cannot use both abort and continue'))
692 if rbsrt.collapsef:
692 if rbsrt.collapsef:
693 raise error.Abort(
693 raise error.Abort(
694 _('cannot use collapse with continue or abort'))
694 _('cannot use collapse with continue or abort'))
695 if srcf or basef or destf:
695 if srcf or basef or destf:
696 raise error.Abort(
696 raise error.Abort(
697 _('abort and continue do not allow specifying revisions'))
697 _('abort and continue do not allow specifying revisions'))
698 if abortf and opts.get('tool', False):
698 if abortf and opts.get('tool', False):
699 ui.warn(_('tool option will be ignored\n'))
699 ui.warn(_('tool option will be ignored\n'))
700 if contf:
700 if contf:
701 ms = mergemod.mergestate.read(repo)
701 ms = mergemod.mergestate.read(repo)
702 mergeutil.checkunresolved(ms)
702 mergeutil.checkunresolved(ms)
703
703
704 retcode = rbsrt._prepareabortorcontinue(abortf)
704 retcode = rbsrt._prepareabortorcontinue(abortf)
705 if retcode is not None:
705 if retcode is not None:
706 return retcode
706 return retcode
707 else:
707 else:
708 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
708 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
709 destspace=destspace)
709 destspace=destspace)
710 retcode = rbsrt._preparenewrebase(dest, rebaseset)
710 retcode = rbsrt._preparenewrebase(dest, rebaseset)
711 if retcode is not None:
711 if retcode is not None:
712 return retcode
712 return retcode
713
713
714 with repo.transaction('rebase') as tr:
714 with repo.transaction('rebase') as tr:
715 try:
715 try:
716 rbsrt._performrebase(tr)
716 rbsrt._performrebase(tr)
717 except error.InterventionRequired:
717 except error.InterventionRequired:
718 tr.close()
718 tr.close()
719 raise
719 raise
720 rbsrt._finishrebase()
720 rbsrt._finishrebase()
721 finally:
721 finally:
722 release(lock, wlock)
722 release(lock, wlock)
723
723
724 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
724 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
725 destspace=None):
725 destspace=None):
726 """use revisions argument to define destination and rebase set
726 """use revisions argument to define destination and rebase set
727 """
727 """
728 revf = revf or []
728 if revf is None:
729 revf = []
729
730
730 # destspace is here to work around issues with `hg pull --rebase` see
731 # destspace is here to work around issues with `hg pull --rebase` see
731 # issue5214 for details
732 # issue5214 for details
732 if srcf and basef:
733 if srcf and basef:
733 raise error.Abort(_('cannot specify both a source and a base'))
734 raise error.Abort(_('cannot specify both a source and a base'))
734 if revf and basef:
735 if revf and basef:
735 raise error.Abort(_('cannot specify both a revision and a base'))
736 raise error.Abort(_('cannot specify both a revision and a base'))
736 if revf and srcf:
737 if revf and srcf:
737 raise error.Abort(_('cannot specify both a revision and a source'))
738 raise error.Abort(_('cannot specify both a revision and a source'))
738
739
739 cmdutil.checkunfinished(repo)
740 cmdutil.checkunfinished(repo)
740 cmdutil.bailifchanged(repo)
741 cmdutil.bailifchanged(repo)
741
742
742 if destf:
743 if destf:
743 dest = scmutil.revsingle(repo, destf)
744 dest = scmutil.revsingle(repo, destf)
744
745
745 if revf:
746 if revf:
746 rebaseset = scmutil.revrange(repo, revf)
747 rebaseset = scmutil.revrange(repo, revf)
747 if not rebaseset:
748 if not rebaseset:
748 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
749 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
749 return None, None
750 return None, None
750 elif srcf:
751 elif srcf:
751 src = scmutil.revrange(repo, [srcf])
752 src = scmutil.revrange(repo, [srcf])
752 if not src:
753 if not src:
753 ui.status(_('empty "source" revision set - nothing to rebase\n'))
754 ui.status(_('empty "source" revision set - nothing to rebase\n'))
754 return None, None
755 return None, None
755 rebaseset = repo.revs('(%ld)::', src)
756 rebaseset = repo.revs('(%ld)::', src)
756 assert rebaseset
757 assert rebaseset
757 else:
758 else:
758 base = scmutil.revrange(repo, [basef or '.'])
759 base = scmutil.revrange(repo, [basef or '.'])
759 if not base:
760 if not base:
760 ui.status(_('empty "base" revision set - '
761 ui.status(_('empty "base" revision set - '
761 "can't compute rebase set\n"))
762 "can't compute rebase set\n"))
762 return None, None
763 return None, None
763 if not destf:
764 if not destf:
764 dest = repo[_destrebase(repo, base, destspace=destspace)]
765 dest = repo[_destrebase(repo, base, destspace=destspace)]
765 destf = str(dest)
766 destf = str(dest)
766
767
767 roots = [] # selected children of branching points
768 roots = [] # selected children of branching points
768 bpbase = {} # {branchingpoint: [origbase]}
769 bpbase = {} # {branchingpoint: [origbase]}
769 for b in base: # group bases by branching points
770 for b in base: # group bases by branching points
770 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
771 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
771 bpbase[bp] = bpbase.get(bp, []) + [b]
772 bpbase[bp] = bpbase.get(bp, []) + [b]
772 if None in bpbase:
773 if None in bpbase:
773 # emulate the old behavior, showing "nothing to rebase" (a better
774 # emulate the old behavior, showing "nothing to rebase" (a better
774 # behavior may be abort with "cannot find branching point" error)
775 # behavior may be abort with "cannot find branching point" error)
775 bpbase.clear()
776 bpbase.clear()
776 for bp, bs in bpbase.iteritems(): # calculate roots
777 for bp, bs in bpbase.iteritems(): # calculate roots
777 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
778 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
778
779
779 rebaseset = repo.revs('%ld::', roots)
780 rebaseset = repo.revs('%ld::', roots)
780
781
781 if not rebaseset:
782 if not rebaseset:
782 # transform to list because smartsets are not comparable to
783 # transform to list because smartsets are not comparable to
783 # lists. This should be improved to honor laziness of
784 # lists. This should be improved to honor laziness of
784 # smartset.
785 # smartset.
785 if list(base) == [dest.rev()]:
786 if list(base) == [dest.rev()]:
786 if basef:
787 if basef:
787 ui.status(_('nothing to rebase - %s is both "base"'
788 ui.status(_('nothing to rebase - %s is both "base"'
788 ' and destination\n') % dest)
789 ' and destination\n') % dest)
789 else:
790 else:
790 ui.status(_('nothing to rebase - working directory '
791 ui.status(_('nothing to rebase - working directory '
791 'parent is also destination\n'))
792 'parent is also destination\n'))
792 elif not repo.revs('%ld - ::%d', base, dest):
793 elif not repo.revs('%ld - ::%d', base, dest):
793 if basef:
794 if basef:
794 ui.status(_('nothing to rebase - "base" %s is '
795 ui.status(_('nothing to rebase - "base" %s is '
795 'already an ancestor of destination '
796 'already an ancestor of destination '
796 '%s\n') %
797 '%s\n') %
797 ('+'.join(str(repo[r]) for r in base),
798 ('+'.join(str(repo[r]) for r in base),
798 dest))
799 dest))
799 else:
800 else:
800 ui.status(_('nothing to rebase - working '
801 ui.status(_('nothing to rebase - working '
801 'directory parent is already an '
802 'directory parent is already an '
802 'ancestor of destination %s\n') % dest)
803 'ancestor of destination %s\n') % dest)
803 else: # can it happen?
804 else: # can it happen?
804 ui.status(_('nothing to rebase from %s to %s\n') %
805 ui.status(_('nothing to rebase from %s to %s\n') %
805 ('+'.join(str(repo[r]) for r in base), dest))
806 ('+'.join(str(repo[r]) for r in base), dest))
806 return None, None
807 return None, None
807
808
808 if not destf:
809 if not destf:
809 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
810 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
810 destf = str(dest)
811 destf = str(dest)
811
812
812 return dest, rebaseset
813 return dest, rebaseset
813
814
814 def externalparent(repo, state, targetancestors):
815 def externalparent(repo, state, targetancestors):
815 """Return the revision that should be used as the second parent
816 """Return the revision that should be used as the second parent
816 when the revisions in state is collapsed on top of targetancestors.
817 when the revisions in state is collapsed on top of targetancestors.
817 Abort if there is more than one parent.
818 Abort if there is more than one parent.
818 """
819 """
819 parents = set()
820 parents = set()
820 source = min(state)
821 source = min(state)
821 for rev in state:
822 for rev in state:
822 if rev == source:
823 if rev == source:
823 continue
824 continue
824 for p in repo[rev].parents():
825 for p in repo[rev].parents():
825 if (p.rev() not in state
826 if (p.rev() not in state
826 and p.rev() not in targetancestors):
827 and p.rev() not in targetancestors):
827 parents.add(p.rev())
828 parents.add(p.rev())
828 if not parents:
829 if not parents:
829 return nullrev
830 return nullrev
830 if len(parents) == 1:
831 if len(parents) == 1:
831 return parents.pop()
832 return parents.pop()
832 raise error.Abort(_('unable to collapse on top of %s, there is more '
833 raise error.Abort(_('unable to collapse on top of %s, there is more '
833 'than one external parent: %s') %
834 'than one external parent: %s') %
834 (max(targetancestors),
835 (max(targetancestors),
835 ', '.join(str(p) for p in sorted(parents))))
836 ', '.join(str(p) for p in sorted(parents))))
836
837
837 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
838 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
838 keepbranches=False, date=None):
839 keepbranches=False, date=None):
839 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
840 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
840 but also store useful information in extra.
841 but also store useful information in extra.
841 Return node of committed revision.'''
842 Return node of committed revision.'''
842 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
843 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
843 try:
844 try:
844 repo.setparents(repo[p1].node(), repo[p2].node())
845 repo.setparents(repo[p1].node(), repo[p2].node())
845 ctx = repo[rev]
846 ctx = repo[rev]
846 if commitmsg is None:
847 if commitmsg is None:
847 commitmsg = ctx.description()
848 commitmsg = ctx.description()
848 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
849 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
849 extra = {'rebase_source': ctx.hex()}
850 extra = {'rebase_source': ctx.hex()}
850 if extrafn:
851 if extrafn:
851 extrafn(ctx, extra)
852 extrafn(ctx, extra)
852
853
853 backup = repo.ui.backupconfig('phases', 'new-commit')
854 backup = repo.ui.backupconfig('phases', 'new-commit')
854 try:
855 try:
855 targetphase = max(ctx.phase(), phases.draft)
856 targetphase = max(ctx.phase(), phases.draft)
856 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
857 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
857 if keepbranch:
858 if keepbranch:
858 repo.ui.setconfig('ui', 'allowemptycommit', True)
859 repo.ui.setconfig('ui', 'allowemptycommit', True)
859 # Commit might fail if unresolved files exist
860 # Commit might fail if unresolved files exist
860 if date is None:
861 if date is None:
861 date = ctx.date()
862 date = ctx.date()
862 newnode = repo.commit(text=commitmsg, user=ctx.user(),
863 newnode = repo.commit(text=commitmsg, user=ctx.user(),
863 date=date, extra=extra, editor=editor)
864 date=date, extra=extra, editor=editor)
864 finally:
865 finally:
865 repo.ui.restoreconfig(backup)
866 repo.ui.restoreconfig(backup)
866
867
867 repo.dirstate.setbranch(repo[newnode].branch())
868 repo.dirstate.setbranch(repo[newnode].branch())
868 dsguard.close()
869 dsguard.close()
869 return newnode
870 return newnode
870 finally:
871 finally:
871 release(dsguard)
872 release(dsguard)
872
873
873 def rebasenode(repo, rev, p1, base, state, collapse, target):
874 def rebasenode(repo, rev, p1, base, state, collapse, target):
874 'Rebase a single revision rev on top of p1 using base as merge ancestor'
875 'Rebase a single revision rev on top of p1 using base as merge ancestor'
875 # Merge phase
876 # Merge phase
876 # Update to target and merge it with local
877 # Update to target and merge it with local
877 if repo['.'].rev() != p1:
878 if repo['.'].rev() != p1:
878 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
879 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
879 mergemod.update(repo, p1, False, True)
880 mergemod.update(repo, p1, False, True)
880 else:
881 else:
881 repo.ui.debug(" already in target\n")
882 repo.ui.debug(" already in target\n")
882 repo.dirstate.write(repo.currenttransaction())
883 repo.dirstate.write(repo.currenttransaction())
883 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
884 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
884 if base is not None:
885 if base is not None:
885 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
886 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
886 # When collapsing in-place, the parent is the common ancestor, we
887 # When collapsing in-place, the parent is the common ancestor, we
887 # have to allow merging with it.
888 # have to allow merging with it.
888 stats = mergemod.update(repo, rev, True, True, base, collapse,
889 stats = mergemod.update(repo, rev, True, True, base, collapse,
889 labels=['dest', 'source'])
890 labels=['dest', 'source'])
890 if collapse:
891 if collapse:
891 copies.duplicatecopies(repo, rev, target)
892 copies.duplicatecopies(repo, rev, target)
892 else:
893 else:
893 # If we're not using --collapse, we need to
894 # If we're not using --collapse, we need to
894 # duplicate copies between the revision we're
895 # duplicate copies between the revision we're
895 # rebasing and its first parent, but *not*
896 # rebasing and its first parent, but *not*
896 # duplicate any copies that have already been
897 # duplicate any copies that have already been
897 # performed in the destination.
898 # performed in the destination.
898 p1rev = repo[rev].p1().rev()
899 p1rev = repo[rev].p1().rev()
899 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
900 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
900 return stats
901 return stats
901
902
902 def nearestrebased(repo, rev, state):
903 def nearestrebased(repo, rev, state):
903 """return the nearest ancestors of rev in the rebase result"""
904 """return the nearest ancestors of rev in the rebase result"""
904 rebased = [r for r in state if state[r] > nullmerge]
905 rebased = [r for r in state if state[r] > nullmerge]
905 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
906 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
906 if candidates:
907 if candidates:
907 return state[candidates.first()]
908 return state[candidates.first()]
908 else:
909 else:
909 return None
910 return None
910
911
911 def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
912 def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
912 """
913 """
913 Abort if rebase will create divergence or rebase is noop because of markers
914 Abort if rebase will create divergence or rebase is noop because of markers
914
915
915 `rebaseobsrevs`: set of obsolete revision in source
916 `rebaseobsrevs`: set of obsolete revision in source
916 `rebasesetrevs`: set of revisions to be rebased from source
917 `rebasesetrevs`: set of revisions to be rebased from source
917 `rebaseobsskipped`: set of revisions from source skipped because they have
918 `rebaseobsskipped`: set of revisions from source skipped because they have
918 successors in destination
919 successors in destination
919 """
920 """
920 # Obsolete node with successors not in dest leads to divergence
921 # Obsolete node with successors not in dest leads to divergence
921 divergenceok = ui.configbool('experimental',
922 divergenceok = ui.configbool('experimental',
922 'allowdivergence')
923 'allowdivergence')
923 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
924 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
924
925
925 if divergencebasecandidates and not divergenceok:
926 if divergencebasecandidates and not divergenceok:
926 divhashes = (str(repo[r])
927 divhashes = (str(repo[r])
927 for r in divergencebasecandidates)
928 for r in divergencebasecandidates)
928 msg = _("this rebase will cause "
929 msg = _("this rebase will cause "
929 "divergences from: %s")
930 "divergences from: %s")
930 h = _("to force the rebase please set "
931 h = _("to force the rebase please set "
931 "experimental.allowdivergence=True")
932 "experimental.allowdivergence=True")
932 raise error.Abort(msg % (",".join(divhashes),), hint=h)
933 raise error.Abort(msg % (",".join(divhashes),), hint=h)
933
934
934 def defineparents(repo, rev, target, state, targetancestors,
935 def defineparents(repo, rev, target, state, targetancestors,
935 obsoletenotrebased):
936 obsoletenotrebased):
936 'Return the new parent relationship of the revision that will be rebased'
937 'Return the new parent relationship of the revision that will be rebased'
937 parents = repo[rev].parents()
938 parents = repo[rev].parents()
938 p1 = p2 = nullrev
939 p1 = p2 = nullrev
939 rp1 = None
940 rp1 = None
940
941
941 p1n = parents[0].rev()
942 p1n = parents[0].rev()
942 if p1n in targetancestors:
943 if p1n in targetancestors:
943 p1 = target
944 p1 = target
944 elif p1n in state:
945 elif p1n in state:
945 if state[p1n] == nullmerge:
946 if state[p1n] == nullmerge:
946 p1 = target
947 p1 = target
947 elif state[p1n] in revskipped:
948 elif state[p1n] in revskipped:
948 p1 = nearestrebased(repo, p1n, state)
949 p1 = nearestrebased(repo, p1n, state)
949 if p1 is None:
950 if p1 is None:
950 p1 = target
951 p1 = target
951 else:
952 else:
952 p1 = state[p1n]
953 p1 = state[p1n]
953 else: # p1n external
954 else: # p1n external
954 p1 = target
955 p1 = target
955 p2 = p1n
956 p2 = p1n
956
957
957 if len(parents) == 2 and parents[1].rev() not in targetancestors:
958 if len(parents) == 2 and parents[1].rev() not in targetancestors:
958 p2n = parents[1].rev()
959 p2n = parents[1].rev()
959 # interesting second parent
960 # interesting second parent
960 if p2n in state:
961 if p2n in state:
961 if p1 == target: # p1n in targetancestors or external
962 if p1 == target: # p1n in targetancestors or external
962 p1 = state[p2n]
963 p1 = state[p2n]
963 if p1 == revprecursor:
964 if p1 == revprecursor:
964 rp1 = obsoletenotrebased[p2n]
965 rp1 = obsoletenotrebased[p2n]
965 elif state[p2n] in revskipped:
966 elif state[p2n] in revskipped:
966 p2 = nearestrebased(repo, p2n, state)
967 p2 = nearestrebased(repo, p2n, state)
967 if p2 is None:
968 if p2 is None:
968 # no ancestors rebased yet, detach
969 # no ancestors rebased yet, detach
969 p2 = target
970 p2 = target
970 else:
971 else:
971 p2 = state[p2n]
972 p2 = state[p2n]
972 else: # p2n external
973 else: # p2n external
973 if p2 != nullrev: # p1n external too => rev is a merged revision
974 if p2 != nullrev: # p1n external too => rev is a merged revision
974 raise error.Abort(_('cannot use revision %d as base, result '
975 raise error.Abort(_('cannot use revision %d as base, result '
975 'would have 3 parents') % rev)
976 'would have 3 parents') % rev)
976 p2 = p2n
977 p2 = p2n
977 repo.ui.debug(" future parents are %d and %d\n" %
978 repo.ui.debug(" future parents are %d and %d\n" %
978 (repo[rp1 or p1].rev(), repo[p2].rev()))
979 (repo[rp1 or p1].rev(), repo[p2].rev()))
979
980
980 if not any(p.rev() in state for p in parents):
981 if not any(p.rev() in state for p in parents):
981 # Case (1) root changeset of a non-detaching rebase set.
982 # Case (1) root changeset of a non-detaching rebase set.
982 # Let the merge mechanism find the base itself.
983 # Let the merge mechanism find the base itself.
983 base = None
984 base = None
984 elif not repo[rev].p2():
985 elif not repo[rev].p2():
985 # Case (2) detaching the node with a single parent, use this parent
986 # Case (2) detaching the node with a single parent, use this parent
986 base = repo[rev].p1().rev()
987 base = repo[rev].p1().rev()
987 else:
988 else:
988 # Assuming there is a p1, this is the case where there also is a p2.
989 # Assuming there is a p1, this is the case where there also is a p2.
989 # We are thus rebasing a merge and need to pick the right merge base.
990 # We are thus rebasing a merge and need to pick the right merge base.
990 #
991 #
991 # Imagine we have:
992 # Imagine we have:
992 # - M: current rebase revision in this step
993 # - M: current rebase revision in this step
993 # - A: one parent of M
994 # - A: one parent of M
994 # - B: other parent of M
995 # - B: other parent of M
995 # - D: destination of this merge step (p1 var)
996 # - D: destination of this merge step (p1 var)
996 #
997 #
997 # Consider the case where D is a descendant of A or B and the other is
998 # Consider the case where D is a descendant of A or B and the other is
998 # 'outside'. In this case, the right merge base is the D ancestor.
999 # 'outside'. In this case, the right merge base is the D ancestor.
999 #
1000 #
1000 # An informal proof, assuming A is 'outside' and B is the D ancestor:
1001 # An informal proof, assuming A is 'outside' and B is the D ancestor:
1001 #
1002 #
1002 # If we pick B as the base, the merge involves:
1003 # If we pick B as the base, the merge involves:
1003 # - changes from B to M (actual changeset payload)
1004 # - changes from B to M (actual changeset payload)
1004 # - changes from B to D (induced by rebase) as D is a rebased
1005 # - changes from B to D (induced by rebase) as D is a rebased
1005 # version of B)
1006 # version of B)
1006 # Which exactly represent the rebase operation.
1007 # Which exactly represent the rebase operation.
1007 #
1008 #
1008 # If we pick A as the base, the merge involves:
1009 # If we pick A as the base, the merge involves:
1009 # - changes from A to M (actual changeset payload)
1010 # - changes from A to M (actual changeset payload)
1010 # - changes from A to D (with include changes between unrelated A and B
1011 # - changes from A to D (with include changes between unrelated A and B
1011 # plus changes induced by rebase)
1012 # plus changes induced by rebase)
1012 # Which does not represent anything sensible and creates a lot of
1013 # Which does not represent anything sensible and creates a lot of
1013 # conflicts. A is thus not the right choice - B is.
1014 # conflicts. A is thus not the right choice - B is.
1014 #
1015 #
1015 # Note: The base found in this 'proof' is only correct in the specified
1016 # Note: The base found in this 'proof' is only correct in the specified
1016 # case. This base does not make sense if is not D a descendant of A or B
1017 # case. This base does not make sense if is not D a descendant of A or B
1017 # or if the other is not parent 'outside' (especially not if the other
1018 # or if the other is not parent 'outside' (especially not if the other
1018 # parent has been rebased). The current implementation does not
1019 # parent has been rebased). The current implementation does not
1019 # make it feasible to consider different cases separately. In these
1020 # make it feasible to consider different cases separately. In these
1020 # other cases we currently just leave it to the user to correctly
1021 # other cases we currently just leave it to the user to correctly
1021 # resolve an impossible merge using a wrong ancestor.
1022 # resolve an impossible merge using a wrong ancestor.
1022 #
1023 #
1023 # xx, p1 could be -4, and both parents could probably be -4...
1024 # xx, p1 could be -4, and both parents could probably be -4...
1024 for p in repo[rev].parents():
1025 for p in repo[rev].parents():
1025 if state.get(p.rev()) == p1:
1026 if state.get(p.rev()) == p1:
1026 base = p.rev()
1027 base = p.rev()
1027 break
1028 break
1028 else: # fallback when base not found
1029 else: # fallback when base not found
1029 base = None
1030 base = None
1030
1031
1031 # Raise because this function is called wrong (see issue 4106)
1032 # Raise because this function is called wrong (see issue 4106)
1032 raise AssertionError('no base found to rebase on '
1033 raise AssertionError('no base found to rebase on '
1033 '(defineparents called wrong)')
1034 '(defineparents called wrong)')
1034 return rp1 or p1, p2, base
1035 return rp1 or p1, p2, base
1035
1036
1036 def isagitpatch(repo, patchname):
1037 def isagitpatch(repo, patchname):
1037 'Return true if the given patch is in git format'
1038 'Return true if the given patch is in git format'
1038 mqpatch = os.path.join(repo.mq.path, patchname)
1039 mqpatch = os.path.join(repo.mq.path, patchname)
1039 for line in patch.linereader(file(mqpatch, 'rb')):
1040 for line in patch.linereader(file(mqpatch, 'rb')):
1040 if line.startswith('diff --git'):
1041 if line.startswith('diff --git'):
1041 return True
1042 return True
1042 return False
1043 return False
1043
1044
1044 def updatemq(repo, state, skipped, **opts):
1045 def updatemq(repo, state, skipped, **opts):
1045 'Update rebased mq patches - finalize and then import them'
1046 'Update rebased mq patches - finalize and then import them'
1046 mqrebase = {}
1047 mqrebase = {}
1047 mq = repo.mq
1048 mq = repo.mq
1048 original_series = mq.fullseries[:]
1049 original_series = mq.fullseries[:]
1049 skippedpatches = set()
1050 skippedpatches = set()
1050
1051
1051 for p in mq.applied:
1052 for p in mq.applied:
1052 rev = repo[p.node].rev()
1053 rev = repo[p.node].rev()
1053 if rev in state:
1054 if rev in state:
1054 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1055 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1055 (rev, p.name))
1056 (rev, p.name))
1056 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1057 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1057 else:
1058 else:
1058 # Applied but not rebased, not sure this should happen
1059 # Applied but not rebased, not sure this should happen
1059 skippedpatches.add(p.name)
1060 skippedpatches.add(p.name)
1060
1061
1061 if mqrebase:
1062 if mqrebase:
1062 mq.finish(repo, mqrebase.keys())
1063 mq.finish(repo, mqrebase.keys())
1063
1064
1064 # We must start import from the newest revision
1065 # We must start import from the newest revision
1065 for rev in sorted(mqrebase, reverse=True):
1066 for rev in sorted(mqrebase, reverse=True):
1066 if rev not in skipped:
1067 if rev not in skipped:
1067 name, isgit = mqrebase[rev]
1068 name, isgit = mqrebase[rev]
1068 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1069 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1069 (name, state[rev], repo[state[rev]]))
1070 (name, state[rev], repo[state[rev]]))
1070 mq.qimport(repo, (), patchname=name, git=isgit,
1071 mq.qimport(repo, (), patchname=name, git=isgit,
1071 rev=[str(state[rev])])
1072 rev=[str(state[rev])])
1072 else:
1073 else:
1073 # Rebased and skipped
1074 # Rebased and skipped
1074 skippedpatches.add(mqrebase[rev][0])
1075 skippedpatches.add(mqrebase[rev][0])
1075
1076
1076 # Patches were either applied and rebased and imported in
1077 # Patches were either applied and rebased and imported in
1077 # order, applied and removed or unapplied. Discard the removed
1078 # order, applied and removed or unapplied. Discard the removed
1078 # ones while preserving the original series order and guards.
1079 # ones while preserving the original series order and guards.
1079 newseries = [s for s in original_series
1080 newseries = [s for s in original_series
1080 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1081 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1081 mq.fullseries[:] = newseries
1082 mq.fullseries[:] = newseries
1082 mq.seriesdirty = True
1083 mq.seriesdirty = True
1083 mq.savedirty()
1084 mq.savedirty()
1084
1085
1085 def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr):
1086 def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr):
1086 'Move bookmarks to their correct changesets, and delete divergent ones'
1087 'Move bookmarks to their correct changesets, and delete divergent ones'
1087 marks = repo._bookmarks
1088 marks = repo._bookmarks
1088 for k, v in originalbookmarks.iteritems():
1089 for k, v in originalbookmarks.iteritems():
1089 if v in nstate:
1090 if v in nstate:
1090 # update the bookmarks for revs that have moved
1091 # update the bookmarks for revs that have moved
1091 marks[k] = nstate[v]
1092 marks[k] = nstate[v]
1092 bookmarks.deletedivergent(repo, [targetnode], k)
1093 bookmarks.deletedivergent(repo, [targetnode], k)
1093 marks.recordchange(tr)
1094 marks.recordchange(tr)
1094
1095
1095 def storecollapsemsg(repo, collapsemsg):
1096 def storecollapsemsg(repo, collapsemsg):
1096 'Store the collapse message to allow recovery'
1097 'Store the collapse message to allow recovery'
1097 collapsemsg = collapsemsg or ''
1098 collapsemsg = collapsemsg or ''
1098 f = repo.vfs("last-message.txt", "w")
1099 f = repo.vfs("last-message.txt", "w")
1099 f.write("%s\n" % collapsemsg)
1100 f.write("%s\n" % collapsemsg)
1100 f.close()
1101 f.close()
1101
1102
1102 def clearcollapsemsg(repo):
1103 def clearcollapsemsg(repo):
1103 'Remove collapse message file'
1104 'Remove collapse message file'
1104 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1105 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1105
1106
1106 def restorecollapsemsg(repo, isabort):
1107 def restorecollapsemsg(repo, isabort):
1107 'Restore previously stored collapse message'
1108 'Restore previously stored collapse message'
1108 try:
1109 try:
1109 f = repo.vfs("last-message.txt")
1110 f = repo.vfs("last-message.txt")
1110 collapsemsg = f.readline().strip()
1111 collapsemsg = f.readline().strip()
1111 f.close()
1112 f.close()
1112 except IOError as err:
1113 except IOError as err:
1113 if err.errno != errno.ENOENT:
1114 if err.errno != errno.ENOENT:
1114 raise
1115 raise
1115 if isabort:
1116 if isabort:
1116 # Oh well, just abort like normal
1117 # Oh well, just abort like normal
1117 collapsemsg = ''
1118 collapsemsg = ''
1118 else:
1119 else:
1119 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1120 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1120 return collapsemsg
1121 return collapsemsg
1121
1122
1122 def clearstatus(repo):
1123 def clearstatus(repo):
1123 'Remove the status files'
1124 'Remove the status files'
1124 _clearrebasesetvisibiliy(repo)
1125 _clearrebasesetvisibiliy(repo)
1125 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1126 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1126
1127
1127 def needupdate(repo, state):
1128 def needupdate(repo, state):
1128 '''check whether we should `update --clean` away from a merge, or if
1129 '''check whether we should `update --clean` away from a merge, or if
1129 somehow the working dir got forcibly updated, e.g. by older hg'''
1130 somehow the working dir got forcibly updated, e.g. by older hg'''
1130 parents = [p.rev() for p in repo[None].parents()]
1131 parents = [p.rev() for p in repo[None].parents()]
1131
1132
1132 # Are we in a merge state at all?
1133 # Are we in a merge state at all?
1133 if len(parents) < 2:
1134 if len(parents) < 2:
1134 return False
1135 return False
1135
1136
1136 # We should be standing on the first as-of-yet unrebased commit.
1137 # We should be standing on the first as-of-yet unrebased commit.
1137 firstunrebased = min([old for old, new in state.iteritems()
1138 firstunrebased = min([old for old, new in state.iteritems()
1138 if new == nullrev])
1139 if new == nullrev])
1139 if firstunrebased in parents:
1140 if firstunrebased in parents:
1140 return True
1141 return True
1141
1142
1142 return False
1143 return False
1143
1144
1144 def abort(repo, originalwd, target, state, activebookmark=None):
1145 def abort(repo, originalwd, target, state, activebookmark=None):
1145 '''Restore the repository to its original state. Additional args:
1146 '''Restore the repository to its original state. Additional args:
1146
1147
1147 activebookmark: the name of the bookmark that should be active after the
1148 activebookmark: the name of the bookmark that should be active after the
1148 restore'''
1149 restore'''
1149
1150
1150 try:
1151 try:
1151 # If the first commits in the rebased set get skipped during the rebase,
1152 # If the first commits in the rebased set get skipped during the rebase,
1152 # their values within the state mapping will be the target rev id. The
1153 # their values within the state mapping will be the target rev id. The
1153 # dstates list must must not contain the target rev (issue4896)
1154 # dstates list must must not contain the target rev (issue4896)
1154 dstates = [s for s in state.values() if s >= 0 and s != target]
1155 dstates = [s for s in state.values() if s >= 0 and s != target]
1155 immutable = [d for d in dstates if not repo[d].mutable()]
1156 immutable = [d for d in dstates if not repo[d].mutable()]
1156 cleanup = True
1157 cleanup = True
1157 if immutable:
1158 if immutable:
1158 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1159 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1159 % ', '.join(str(repo[r]) for r in immutable),
1160 % ', '.join(str(repo[r]) for r in immutable),
1160 hint=_("see 'hg help phases' for details"))
1161 hint=_("see 'hg help phases' for details"))
1161 cleanup = False
1162 cleanup = False
1162
1163
1163 descendants = set()
1164 descendants = set()
1164 if dstates:
1165 if dstates:
1165 descendants = set(repo.changelog.descendants(dstates))
1166 descendants = set(repo.changelog.descendants(dstates))
1166 if descendants - set(dstates):
1167 if descendants - set(dstates):
1167 repo.ui.warn(_("warning: new changesets detected on target branch, "
1168 repo.ui.warn(_("warning: new changesets detected on target branch, "
1168 "can't strip\n"))
1169 "can't strip\n"))
1169 cleanup = False
1170 cleanup = False
1170
1171
1171 if cleanup:
1172 if cleanup:
1172 shouldupdate = False
1173 shouldupdate = False
1173 rebased = filter(lambda x: x >= 0 and x != target, state.values())
1174 rebased = filter(lambda x: x >= 0 and x != target, state.values())
1174 if rebased:
1175 if rebased:
1175 strippoints = [
1176 strippoints = [
1176 c.node() for c in repo.set('roots(%ld)', rebased)]
1177 c.node() for c in repo.set('roots(%ld)', rebased)]
1177
1178
1178 updateifonnodes = set(rebased)
1179 updateifonnodes = set(rebased)
1179 updateifonnodes.add(target)
1180 updateifonnodes.add(target)
1180 updateifonnodes.add(originalwd)
1181 updateifonnodes.add(originalwd)
1181 shouldupdate = repo['.'].rev() in updateifonnodes
1182 shouldupdate = repo['.'].rev() in updateifonnodes
1182
1183
1183 # Update away from the rebase if necessary
1184 # Update away from the rebase if necessary
1184 if shouldupdate or needupdate(repo, state):
1185 if shouldupdate or needupdate(repo, state):
1185 mergemod.update(repo, originalwd, False, True)
1186 mergemod.update(repo, originalwd, False, True)
1186
1187
1187 # Strip from the first rebased revision
1188 # Strip from the first rebased revision
1188 if rebased:
1189 if rebased:
1189 # no backup of rebased cset versions needed
1190 # no backup of rebased cset versions needed
1190 repair.strip(repo.ui, repo, strippoints)
1191 repair.strip(repo.ui, repo, strippoints)
1191
1192
1192 if activebookmark and activebookmark in repo._bookmarks:
1193 if activebookmark and activebookmark in repo._bookmarks:
1193 bookmarks.activate(repo, activebookmark)
1194 bookmarks.activate(repo, activebookmark)
1194
1195
1195 finally:
1196 finally:
1196 clearstatus(repo)
1197 clearstatus(repo)
1197 clearcollapsemsg(repo)
1198 clearcollapsemsg(repo)
1198 repo.ui.warn(_('rebase aborted\n'))
1199 repo.ui.warn(_('rebase aborted\n'))
1199 return 0
1200 return 0
1200
1201
1201 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1202 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1202 '''Define which revisions are going to be rebased and where
1203 '''Define which revisions are going to be rebased and where
1203
1204
1204 repo: repo
1205 repo: repo
1205 dest: context
1206 dest: context
1206 rebaseset: set of rev
1207 rebaseset: set of rev
1207 '''
1208 '''
1208 originalwd = repo['.'].rev()
1209 originalwd = repo['.'].rev()
1209 _setrebasesetvisibility(repo, set(rebaseset) | set([originalwd]))
1210 _setrebasesetvisibility(repo, set(rebaseset) | set([originalwd]))
1210
1211
1211 # This check isn't strictly necessary, since mq detects commits over an
1212 # This check isn't strictly necessary, since mq detects commits over an
1212 # applied patch. But it prevents messing up the working directory when
1213 # applied patch. But it prevents messing up the working directory when
1213 # a partially completed rebase is blocked by mq.
1214 # a partially completed rebase is blocked by mq.
1214 if 'qtip' in repo.tags() and (dest.node() in
1215 if 'qtip' in repo.tags() and (dest.node() in
1215 [s.node for s in repo.mq.applied]):
1216 [s.node for s in repo.mq.applied]):
1216 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1217 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1217
1218
1218 roots = list(repo.set('roots(%ld)', rebaseset))
1219 roots = list(repo.set('roots(%ld)', rebaseset))
1219 if not roots:
1220 if not roots:
1220 raise error.Abort(_('no matching revisions'))
1221 raise error.Abort(_('no matching revisions'))
1221 roots.sort()
1222 roots.sort()
1222 state = {}
1223 state = {}
1223 detachset = set()
1224 detachset = set()
1224 for root in roots:
1225 for root in roots:
1225 commonbase = root.ancestor(dest)
1226 commonbase = root.ancestor(dest)
1226 if commonbase == root:
1227 if commonbase == root:
1227 raise error.Abort(_('source is ancestor of destination'))
1228 raise error.Abort(_('source is ancestor of destination'))
1228 if commonbase == dest:
1229 if commonbase == dest:
1229 wctx = repo[None]
1230 wctx = repo[None]
1230 if dest == wctx.p1():
1231 if dest == wctx.p1():
1231 # when rebasing to '.', it will use the current wd branch name
1232 # when rebasing to '.', it will use the current wd branch name
1232 samebranch = root.branch() == wctx.branch()
1233 samebranch = root.branch() == wctx.branch()
1233 else:
1234 else:
1234 samebranch = root.branch() == dest.branch()
1235 samebranch = root.branch() == dest.branch()
1235 if not collapse and samebranch and root in dest.children():
1236 if not collapse and samebranch and root in dest.children():
1236 repo.ui.debug('source is a child of destination\n')
1237 repo.ui.debug('source is a child of destination\n')
1237 return None
1238 return None
1238
1239
1239 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1240 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1240 state.update(dict.fromkeys(rebaseset, revtodo))
1241 state.update(dict.fromkeys(rebaseset, revtodo))
1241 # Rebase tries to turn <dest> into a parent of <root> while
1242 # Rebase tries to turn <dest> into a parent of <root> while
1242 # preserving the number of parents of rebased changesets:
1243 # preserving the number of parents of rebased changesets:
1243 #
1244 #
1244 # - A changeset with a single parent will always be rebased as a
1245 # - A changeset with a single parent will always be rebased as a
1245 # changeset with a single parent.
1246 # changeset with a single parent.
1246 #
1247 #
1247 # - A merge will be rebased as merge unless its parents are both
1248 # - A merge will be rebased as merge unless its parents are both
1248 # ancestors of <dest> or are themselves in the rebased set and
1249 # ancestors of <dest> or are themselves in the rebased set and
1249 # pruned while rebased.
1250 # pruned while rebased.
1250 #
1251 #
1251 # If one parent of <root> is an ancestor of <dest>, the rebased
1252 # If one parent of <root> is an ancestor of <dest>, the rebased
1252 # version of this parent will be <dest>. This is always true with
1253 # version of this parent will be <dest>. This is always true with
1253 # --base option.
1254 # --base option.
1254 #
1255 #
1255 # Otherwise, we need to *replace* the original parents with
1256 # Otherwise, we need to *replace* the original parents with
1256 # <dest>. This "detaches" the rebased set from its former location
1257 # <dest>. This "detaches" the rebased set from its former location
1257 # and rebases it onto <dest>. Changes introduced by ancestors of
1258 # and rebases it onto <dest>. Changes introduced by ancestors of
1258 # <root> not common with <dest> (the detachset, marked as
1259 # <root> not common with <dest> (the detachset, marked as
1259 # nullmerge) are "removed" from the rebased changesets.
1260 # nullmerge) are "removed" from the rebased changesets.
1260 #
1261 #
1261 # - If <root> has a single parent, set it to <dest>.
1262 # - If <root> has a single parent, set it to <dest>.
1262 #
1263 #
1263 # - If <root> is a merge, we cannot decide which parent to
1264 # - If <root> is a merge, we cannot decide which parent to
1264 # replace, the rebase operation is not clearly defined.
1265 # replace, the rebase operation is not clearly defined.
1265 #
1266 #
1266 # The table below sums up this behavior:
1267 # The table below sums up this behavior:
1267 #
1268 #
1268 # +------------------+----------------------+-------------------------+
1269 # +------------------+----------------------+-------------------------+
1269 # | | one parent | merge |
1270 # | | one parent | merge |
1270 # +------------------+----------------------+-------------------------+
1271 # +------------------+----------------------+-------------------------+
1271 # | parent in | new parent is <dest> | parents in ::<dest> are |
1272 # | parent in | new parent is <dest> | parents in ::<dest> are |
1272 # | ::<dest> | | remapped to <dest> |
1273 # | ::<dest> | | remapped to <dest> |
1273 # +------------------+----------------------+-------------------------+
1274 # +------------------+----------------------+-------------------------+
1274 # | unrelated source | new parent is <dest> | ambiguous, abort |
1275 # | unrelated source | new parent is <dest> | ambiguous, abort |
1275 # +------------------+----------------------+-------------------------+
1276 # +------------------+----------------------+-------------------------+
1276 #
1277 #
1277 # The actual abort is handled by `defineparents`
1278 # The actual abort is handled by `defineparents`
1278 if len(root.parents()) <= 1:
1279 if len(root.parents()) <= 1:
1279 # ancestors of <root> not ancestors of <dest>
1280 # ancestors of <root> not ancestors of <dest>
1280 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1281 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1281 [root.rev()]))
1282 [root.rev()]))
1282 for r in detachset:
1283 for r in detachset:
1283 if r not in state:
1284 if r not in state:
1284 state[r] = nullmerge
1285 state[r] = nullmerge
1285 if len(roots) > 1:
1286 if len(roots) > 1:
1286 # If we have multiple roots, we may have "hole" in the rebase set.
1287 # If we have multiple roots, we may have "hole" in the rebase set.
1287 # Rebase roots that descend from those "hole" should not be detached as
1288 # Rebase roots that descend from those "hole" should not be detached as
1288 # other root are. We use the special `revignored` to inform rebase that
1289 # other root are. We use the special `revignored` to inform rebase that
1289 # the revision should be ignored but that `defineparents` should search
1290 # the revision should be ignored but that `defineparents` should search
1290 # a rebase destination that make sense regarding rebased topology.
1291 # a rebase destination that make sense regarding rebased topology.
1291 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1292 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1292 for ignored in set(rebasedomain) - set(rebaseset):
1293 for ignored in set(rebasedomain) - set(rebaseset):
1293 state[ignored] = revignored
1294 state[ignored] = revignored
1294 for r in obsoletenotrebased:
1295 for r in obsoletenotrebased:
1295 if obsoletenotrebased[r] is None:
1296 if obsoletenotrebased[r] is None:
1296 state[r] = revpruned
1297 state[r] = revpruned
1297 else:
1298 else:
1298 state[r] = revprecursor
1299 state[r] = revprecursor
1299 return originalwd, dest.rev(), state
1300 return originalwd, dest.rev(), state
1300
1301
1301 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1302 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1302 """dispose of rebased revision at the end of the rebase
1303 """dispose of rebased revision at the end of the rebase
1303
1304
1304 If `collapsedas` is not None, the rebase was a collapse whose result if the
1305 If `collapsedas` is not None, the rebase was a collapse whose result if the
1305 `collapsedas` node."""
1306 `collapsedas` node."""
1306 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1307 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1307 markers = []
1308 markers = []
1308 for rev, newrev in sorted(state.items()):
1309 for rev, newrev in sorted(state.items()):
1309 if newrev >= 0:
1310 if newrev >= 0:
1310 if rev in skipped:
1311 if rev in skipped:
1311 succs = ()
1312 succs = ()
1312 elif collapsedas is not None:
1313 elif collapsedas is not None:
1313 succs = (repo[collapsedas],)
1314 succs = (repo[collapsedas],)
1314 else:
1315 else:
1315 succs = (repo[newrev],)
1316 succs = (repo[newrev],)
1316 markers.append((repo[rev], succs))
1317 markers.append((repo[rev], succs))
1317 if markers:
1318 if markers:
1318 obsolete.createmarkers(repo, markers)
1319 obsolete.createmarkers(repo, markers)
1319 else:
1320 else:
1320 rebased = [rev for rev in state if state[rev] > nullmerge]
1321 rebased = [rev for rev in state if state[rev] > nullmerge]
1321 if rebased:
1322 if rebased:
1322 stripped = []
1323 stripped = []
1323 for root in repo.set('roots(%ld)', rebased):
1324 for root in repo.set('roots(%ld)', rebased):
1324 if set(repo.changelog.descendants([root.rev()])) - set(state):
1325 if set(repo.changelog.descendants([root.rev()])) - set(state):
1325 ui.warn(_("warning: new changesets detected "
1326 ui.warn(_("warning: new changesets detected "
1326 "on source branch, not stripping\n"))
1327 "on source branch, not stripping\n"))
1327 else:
1328 else:
1328 stripped.append(root.node())
1329 stripped.append(root.node())
1329 if stripped:
1330 if stripped:
1330 # backup the old csets by default
1331 # backup the old csets by default
1331 repair.strip(ui, repo, stripped, "all")
1332 repair.strip(ui, repo, stripped, "all")
1332
1333
1333
1334
1334 def pullrebase(orig, ui, repo, *args, **opts):
1335 def pullrebase(orig, ui, repo, *args, **opts):
1335 'Call rebase after pull if the latter has been invoked with --rebase'
1336 'Call rebase after pull if the latter has been invoked with --rebase'
1336 ret = None
1337 ret = None
1337 if opts.get('rebase'):
1338 if opts.get('rebase'):
1338 wlock = lock = None
1339 wlock = lock = None
1339 try:
1340 try:
1340 wlock = repo.wlock()
1341 wlock = repo.wlock()
1341 lock = repo.lock()
1342 lock = repo.lock()
1342 if opts.get('update'):
1343 if opts.get('update'):
1343 del opts['update']
1344 del opts['update']
1344 ui.debug('--update and --rebase are not compatible, ignoring '
1345 ui.debug('--update and --rebase are not compatible, ignoring '
1345 'the update flag\n')
1346 'the update flag\n')
1346
1347
1347 cmdutil.checkunfinished(repo)
1348 cmdutil.checkunfinished(repo)
1348 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1349 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1349 'please commit or shelve your changes first'))
1350 'please commit or shelve your changes first'))
1350
1351
1351 revsprepull = len(repo)
1352 revsprepull = len(repo)
1352 origpostincoming = commands.postincoming
1353 origpostincoming = commands.postincoming
1353 def _dummy(*args, **kwargs):
1354 def _dummy(*args, **kwargs):
1354 pass
1355 pass
1355 commands.postincoming = _dummy
1356 commands.postincoming = _dummy
1356 try:
1357 try:
1357 ret = orig(ui, repo, *args, **opts)
1358 ret = orig(ui, repo, *args, **opts)
1358 finally:
1359 finally:
1359 commands.postincoming = origpostincoming
1360 commands.postincoming = origpostincoming
1360 revspostpull = len(repo)
1361 revspostpull = len(repo)
1361 if revspostpull > revsprepull:
1362 if revspostpull > revsprepull:
1362 # --rev option from pull conflict with rebase own --rev
1363 # --rev option from pull conflict with rebase own --rev
1363 # dropping it
1364 # dropping it
1364 if 'rev' in opts:
1365 if 'rev' in opts:
1365 del opts['rev']
1366 del opts['rev']
1366 # positional argument from pull conflicts with rebase's own
1367 # positional argument from pull conflicts with rebase's own
1367 # --source.
1368 # --source.
1368 if 'source' in opts:
1369 if 'source' in opts:
1369 del opts['source']
1370 del opts['source']
1370 # revsprepull is the len of the repo, not revnum of tip.
1371 # revsprepull is the len of the repo, not revnum of tip.
1371 destspace = list(repo.changelog.revs(start=revsprepull))
1372 destspace = list(repo.changelog.revs(start=revsprepull))
1372 opts['_destspace'] = destspace
1373 opts['_destspace'] = destspace
1373 try:
1374 try:
1374 rebase(ui, repo, **opts)
1375 rebase(ui, repo, **opts)
1375 except error.NoMergeDestAbort:
1376 except error.NoMergeDestAbort:
1376 # we can maybe update instead
1377 # we can maybe update instead
1377 rev, _a, _b = destutil.destupdate(repo)
1378 rev, _a, _b = destutil.destupdate(repo)
1378 if rev == repo['.'].rev():
1379 if rev == repo['.'].rev():
1379 ui.status(_('nothing to rebase\n'))
1380 ui.status(_('nothing to rebase\n'))
1380 else:
1381 else:
1381 ui.status(_('nothing to rebase - updating instead\n'))
1382 ui.status(_('nothing to rebase - updating instead\n'))
1382 # not passing argument to get the bare update behavior
1383 # not passing argument to get the bare update behavior
1383 # with warning and trumpets
1384 # with warning and trumpets
1384 commands.update(ui, repo)
1385 commands.update(ui, repo)
1385 finally:
1386 finally:
1386 release(lock, wlock)
1387 release(lock, wlock)
1387 else:
1388 else:
1388 if opts.get('tool'):
1389 if opts.get('tool'):
1389 raise error.Abort(_('--tool can only be used with --rebase'))
1390 raise error.Abort(_('--tool can only be used with --rebase'))
1390 ret = orig(ui, repo, *args, **opts)
1391 ret = orig(ui, repo, *args, **opts)
1391
1392
1392 return ret
1393 return ret
1393
1394
1394 def _setrebasesetvisibility(repo, revs):
1395 def _setrebasesetvisibility(repo, revs):
1395 """store the currently rebased set on the repo object
1396 """store the currently rebased set on the repo object
1396
1397
1397 This is used by another function to prevent rebased revision to because
1398 This is used by another function to prevent rebased revision to because
1398 hidden (see issue4504)"""
1399 hidden (see issue4504)"""
1399 repo = repo.unfiltered()
1400 repo = repo.unfiltered()
1400 repo._rebaseset = revs
1401 repo._rebaseset = revs
1401 # invalidate cache if visibility changes
1402 # invalidate cache if visibility changes
1402 hiddens = repo.filteredrevcache.get('visible', set())
1403 hiddens = repo.filteredrevcache.get('visible', set())
1403 if revs & hiddens:
1404 if revs & hiddens:
1404 repo.invalidatevolatilesets()
1405 repo.invalidatevolatilesets()
1405
1406
1406 def _clearrebasesetvisibiliy(repo):
1407 def _clearrebasesetvisibiliy(repo):
1407 """remove rebaseset data from the repo"""
1408 """remove rebaseset data from the repo"""
1408 repo = repo.unfiltered()
1409 repo = repo.unfiltered()
1409 if '_rebaseset' in vars(repo):
1410 if '_rebaseset' in vars(repo):
1410 del repo._rebaseset
1411 del repo._rebaseset
1411
1412
1412 def _rebasedvisible(orig, repo):
1413 def _rebasedvisible(orig, repo):
1413 """ensure rebased revs stay visible (see issue4504)"""
1414 """ensure rebased revs stay visible (see issue4504)"""
1414 blockers = orig(repo)
1415 blockers = orig(repo)
1415 blockers.update(getattr(repo, '_rebaseset', ()))
1416 blockers.update(getattr(repo, '_rebaseset', ()))
1416 return blockers
1417 return blockers
1417
1418
1418 def _filterobsoleterevs(repo, revs):
1419 def _filterobsoleterevs(repo, revs):
1419 """returns a set of the obsolete revisions in revs"""
1420 """returns a set of the obsolete revisions in revs"""
1420 return set(r for r in revs if repo[r].obsolete())
1421 return set(r for r in revs if repo[r].obsolete())
1421
1422
1422 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1423 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1423 """return a mapping obsolete => successor for all obsolete nodes to be
1424 """return a mapping obsolete => successor for all obsolete nodes to be
1424 rebased that have a successors in the destination
1425 rebased that have a successors in the destination
1425
1426
1426 obsolete => None entries in the mapping indicate nodes with no successor"""
1427 obsolete => None entries in the mapping indicate nodes with no successor"""
1427 obsoletenotrebased = {}
1428 obsoletenotrebased = {}
1428
1429
1429 # Build a mapping successor => obsolete nodes for the obsolete
1430 # Build a mapping successor => obsolete nodes for the obsolete
1430 # nodes to be rebased
1431 # nodes to be rebased
1431 allsuccessors = {}
1432 allsuccessors = {}
1432 cl = repo.changelog
1433 cl = repo.changelog
1433 for r in rebaseobsrevs:
1434 for r in rebaseobsrevs:
1434 node = cl.node(r)
1435 node = cl.node(r)
1435 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1436 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1436 try:
1437 try:
1437 allsuccessors[cl.rev(s)] = cl.rev(node)
1438 allsuccessors[cl.rev(s)] = cl.rev(node)
1438 except LookupError:
1439 except LookupError:
1439 pass
1440 pass
1440
1441
1441 if allsuccessors:
1442 if allsuccessors:
1442 # Look for successors of obsolete nodes to be rebased among
1443 # Look for successors of obsolete nodes to be rebased among
1443 # the ancestors of dest
1444 # the ancestors of dest
1444 ancs = cl.ancestors([repo[dest].rev()],
1445 ancs = cl.ancestors([repo[dest].rev()],
1445 stoprev=min(allsuccessors),
1446 stoprev=min(allsuccessors),
1446 inclusive=True)
1447 inclusive=True)
1447 for s in allsuccessors:
1448 for s in allsuccessors:
1448 if s in ancs:
1449 if s in ancs:
1449 obsoletenotrebased[allsuccessors[s]] = s
1450 obsoletenotrebased[allsuccessors[s]] = s
1450 elif (s == allsuccessors[s] and
1451 elif (s == allsuccessors[s] and
1451 allsuccessors.values().count(s) == 1):
1452 allsuccessors.values().count(s) == 1):
1452 # plain prune
1453 # plain prune
1453 obsoletenotrebased[s] = None
1454 obsoletenotrebased[s] = None
1454
1455
1455 return obsoletenotrebased
1456 return obsoletenotrebased
1456
1457
1457 def summaryhook(ui, repo):
1458 def summaryhook(ui, repo):
1458 if not repo.vfs.exists('rebasestate'):
1459 if not repo.vfs.exists('rebasestate'):
1459 return
1460 return
1460 try:
1461 try:
1461 rbsrt = rebaseruntime(repo, ui, {})
1462 rbsrt = rebaseruntime(repo, ui, {})
1462 rbsrt.restorestatus()
1463 rbsrt.restorestatus()
1463 state = rbsrt.state
1464 state = rbsrt.state
1464 except error.RepoLookupError:
1465 except error.RepoLookupError:
1465 # i18n: column positioning for "hg summary"
1466 # i18n: column positioning for "hg summary"
1466 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1467 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1467 ui.write(msg)
1468 ui.write(msg)
1468 return
1469 return
1469 numrebased = len([i for i in state.itervalues() if i >= 0])
1470 numrebased = len([i for i in state.itervalues() if i >= 0])
1470 # i18n: column positioning for "hg summary"
1471 # i18n: column positioning for "hg summary"
1471 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1472 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1472 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1473 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1473 ui.label(_('%d remaining'), 'rebase.remaining') %
1474 ui.label(_('%d remaining'), 'rebase.remaining') %
1474 (len(state) - numrebased)))
1475 (len(state) - numrebased)))
1475
1476
1476 def uisetup(ui):
1477 def uisetup(ui):
1477 #Replace pull with a decorator to provide --rebase option
1478 #Replace pull with a decorator to provide --rebase option
1478 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1479 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1479 entry[1].append(('', 'rebase', None,
1480 entry[1].append(('', 'rebase', None,
1480 _("rebase working directory to branch head")))
1481 _("rebase working directory to branch head")))
1481 entry[1].append(('t', 'tool', '',
1482 entry[1].append(('t', 'tool', '',
1482 _("specify merge tool for rebase")))
1483 _("specify merge tool for rebase")))
1483 cmdutil.summaryhooks.add('rebase', summaryhook)
1484 cmdutil.summaryhooks.add('rebase', summaryhook)
1484 cmdutil.unfinishedstates.append(
1485 cmdutil.unfinishedstates.append(
1485 ['rebasestate', False, False, _('rebase in progress'),
1486 ['rebasestate', False, False, _('rebase in progress'),
1486 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1487 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1487 cmdutil.afterresolvedstates.append(
1488 cmdutil.afterresolvedstates.append(
1488 ['rebasestate', _('hg rebase --continue')])
1489 ['rebasestate', _('hg rebase --continue')])
1489 # ensure rebased rev are not hidden
1490 # ensure rebased rev are not hidden
1490 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
1491 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
General Comments 0
You need to be logged in to leave comments. Login now