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