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