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