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