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