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