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