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