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