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