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