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