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