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