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