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