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