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