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