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