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