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