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