##// END OF EJS Templates
rebase: add --rev option to rebase...
Pierre-Yves David -
r15270:6cb6064f default
parent child Browse files
Show More
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
@@ -1,609 +1,634 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
18 from mercurial import extensions, patch, scmutil
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
26
27 cmdtable = {}
27 cmdtable = {}
28 command = cmdutil.command(cmdtable)
28 command = cmdutil.command(cmdtable)
29
29
30 @command('rebase',
30 @command('rebase',
31 [('s', 'source', '',
31 [('s', 'source', '',
32 _('rebase from the specified changeset'), _('REV')),
32 _('rebase from the specified changeset'), _('REV')),
33 ('b', 'base', '',
33 ('b', 'base', '',
34 _('rebase from the base of the specified changeset '
34 _('rebase from the base of the specified changeset '
35 '(up to greatest common ancestor of base and dest)'),
35 '(up to greatest common ancestor of base and dest)'),
36 _('REV')),
36 _('REV')),
37 ('r', 'rev', [],
38 _('rebase these revisions'),
39 _('REV')),
37 ('d', 'dest', '',
40 ('d', 'dest', '',
38 _('rebase onto the specified changeset'), _('REV')),
41 _('rebase onto the specified changeset'), _('REV')),
39 ('', 'collapse', False, _('collapse the rebased changesets')),
42 ('', 'collapse', False, _('collapse the rebased changesets')),
40 ('m', 'message', '',
43 ('m', 'message', '',
41 _('use text as collapse commit message'), _('TEXT')),
44 _('use text as collapse commit message'), _('TEXT')),
42 ('e', 'edit', False, _('invoke editor on commit messages')),
45 ('e', 'edit', False, _('invoke editor on commit messages')),
43 ('l', 'logfile', '',
46 ('l', 'logfile', '',
44 _('read collapse commit message from file'), _('FILE')),
47 _('read collapse commit message from file'), _('FILE')),
45 ('', 'keep', False, _('keep original changesets')),
48 ('', 'keep', False, _('keep original changesets')),
46 ('', 'keepbranches', False, _('keep original branch names')),
49 ('', 'keepbranches', False, _('keep original branch names')),
47 ('', 'detach', False, _('force detaching of source from its original '
50 ('', 'detach', False, _('force detaching of source from its original '
48 'branch')),
51 'branch')),
49 ('t', 'tool', '', _('specify merge tool')),
52 ('t', 'tool', '', _('specify merge tool')),
50 ('c', 'continue', False, _('continue an interrupted rebase')),
53 ('c', 'continue', False, _('continue an interrupted rebase')),
51 ('a', 'abort', False, _('abort an interrupted rebase'))] +
54 ('a', 'abort', False, _('abort an interrupted rebase'))] +
52 templateopts,
55 templateopts,
53 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
56 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
54 'hg rebase {-a|-c}'))
57 'hg rebase {-a|-c}'))
55 def rebase(ui, repo, **opts):
58 def rebase(ui, repo, **opts):
56 """move changeset (and descendants) to a different branch
59 """move changeset (and descendants) to a different branch
57
60
58 Rebase uses repeated merging to graft changesets from one part of
61 Rebase uses repeated merging to graft changesets from one part of
59 history (the source) onto another (the destination). This can be
62 history (the source) onto another (the destination). This can be
60 useful for linearizing *local* changes relative to a master
63 useful for linearizing *local* changes relative to a master
61 development tree.
64 development tree.
62
65
63 You should not rebase changesets that have already been shared
66 You should not rebase changesets that have already been shared
64 with others. Doing so will force everybody else to perform the
67 with others. Doing so will force everybody else to perform the
65 same rebase or they will end up with duplicated changesets after
68 same rebase or they will end up with duplicated changesets after
66 pulling in your rebased changesets.
69 pulling in your rebased changesets.
67
70
68 If you don't specify a destination changeset (``-d/--dest``),
71 If you don't specify a destination changeset (``-d/--dest``),
69 rebase uses the tipmost head of the current named branch as the
72 rebase uses the tipmost head of the current named branch as the
70 destination. (The destination changeset is not modified by
73 destination. (The destination changeset is not modified by
71 rebasing, but new changesets are added as its descendants.)
74 rebasing, but new changesets are added as its descendants.)
72
75
73 You can specify which changesets to rebase in two ways: as a
76 You can specify which changesets to rebase in two ways: as a
74 "source" changeset or as a "base" changeset. Both are shorthand
77 "source" changeset or as a "base" changeset. Both are shorthand
75 for a topologically related set of changesets (the "source
78 for a topologically related set of changesets (the "source
76 branch"). If you specify source (``-s/--source``), rebase will
79 branch"). If you specify source (``-s/--source``), rebase will
77 rebase that changeset and all of its descendants onto dest. If you
80 rebase that changeset and all of its descendants onto dest. If you
78 specify base (``-b/--base``), rebase will select ancestors of base
81 specify base (``-b/--base``), rebase will select ancestors of base
79 back to but not including the common ancestor with dest. Thus,
82 back to but not including the common ancestor with dest. Thus,
80 ``-b`` is less precise but more convenient than ``-s``: you can
83 ``-b`` is less precise but more convenient than ``-s``: you can
81 specify any changeset in the source branch, and rebase will select
84 specify any changeset in the source branch, and rebase will select
82 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
85 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
83 uses the parent of the working directory as the base.
86 uses the parent of the working directory as the base.
84
87
85 By default, rebase recreates the changesets in the source branch
88 By default, rebase recreates the changesets in the source branch
86 as descendants of dest and then destroys the originals. Use
89 as descendants of dest and then destroys the originals. Use
87 ``--keep`` to preserve the original source changesets. Some
90 ``--keep`` to preserve the original source changesets. Some
88 changesets in the source branch (e.g. merges from the destination
91 changesets in the source branch (e.g. merges from the destination
89 branch) may be dropped if they no longer contribute any change.
92 branch) may be dropped if they no longer contribute any change.
90
93
91 One result of the rules for selecting the destination changeset
94 One result of the rules for selecting the destination changeset
92 and source branch is that, unlike ``merge``, rebase will do
95 and source branch is that, unlike ``merge``, rebase will do
93 nothing if you are at the latest (tipmost) head of a named branch
96 nothing if you are at the latest (tipmost) head of a named branch
94 with two heads. You need to explicitly specify source and/or
97 with two heads. You need to explicitly specify source and/or
95 destination (or ``update`` to the other head, if it's the head of
98 destination (or ``update`` to the other head, if it's the head of
96 the intended source branch).
99 the intended source branch).
97
100
98 If a rebase is interrupted to manually resolve a merge, it can be
101 If a rebase is interrupted to manually resolve a merge, it can be
99 continued with --continue/-c or aborted with --abort/-a.
102 continued with --continue/-c or aborted with --abort/-a.
100
103
101 Returns 0 on success, 1 if nothing to rebase.
104 Returns 0 on success, 1 if nothing to rebase.
102 """
105 """
103 originalwd = target = None
106 originalwd = target = None
104 external = nullrev
107 external = nullrev
105 state = {}
108 state = {}
106 skipped = set()
109 skipped = set()
107 targetancestors = set()
110 targetancestors = set()
108
111
109 editor = None
112 editor = None
110 if opts.get('edit'):
113 if opts.get('edit'):
111 editor = cmdutil.commitforceeditor
114 editor = cmdutil.commitforceeditor
112
115
113 lock = wlock = None
116 lock = wlock = None
114 try:
117 try:
115 lock = repo.lock()
118 lock = repo.lock()
116 wlock = repo.wlock()
119 wlock = repo.wlock()
117
120
118 # Validate input and define rebasing points
121 # Validate input and define rebasing points
119 destf = opts.get('dest', None)
122 destf = opts.get('dest', None)
120 srcf = opts.get('source', None)
123 srcf = opts.get('source', None)
121 basef = opts.get('base', None)
124 basef = opts.get('base', None)
125 revf = opts.get('rev', [])
122 contf = opts.get('continue')
126 contf = opts.get('continue')
123 abortf = opts.get('abort')
127 abortf = opts.get('abort')
124 collapsef = opts.get('collapse', False)
128 collapsef = opts.get('collapse', False)
125 collapsemsg = cmdutil.logmessage(ui, opts)
129 collapsemsg = cmdutil.logmessage(ui, opts)
126 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
130 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
127 keepf = opts.get('keep', False)
131 keepf = opts.get('keep', False)
128 keepbranchesf = opts.get('keepbranches', False)
132 keepbranchesf = opts.get('keepbranches', False)
129 detachf = opts.get('detach', False)
133 detachf = opts.get('detach', False)
130 # keepopen is not meant for use on the command line, but by
134 # keepopen is not meant for use on the command line, but by
131 # other extensions
135 # other extensions
132 keepopen = opts.get('keepopen', False)
136 keepopen = opts.get('keepopen', False)
133
137
134 if collapsemsg and not collapsef:
138 if collapsemsg and not collapsef:
135 raise util.Abort(
139 raise util.Abort(
136 _('message can only be specified with collapse'))
140 _('message can only be specified with collapse'))
137
141
138 if contf or abortf:
142 if contf or abortf:
139 if contf and abortf:
143 if contf and abortf:
140 raise util.Abort(_('cannot use both abort and continue'))
144 raise util.Abort(_('cannot use both abort and continue'))
141 if collapsef:
145 if collapsef:
142 raise util.Abort(
146 raise util.Abort(
143 _('cannot use collapse with continue or abort'))
147 _('cannot use collapse with continue or abort'))
144 if detachf:
148 if detachf:
145 raise util.Abort(_('cannot use detach with continue or abort'))
149 raise util.Abort(_('cannot use detach with continue or abort'))
146 if srcf or basef or destf:
150 if srcf or basef or destf:
147 raise util.Abort(
151 raise util.Abort(
148 _('abort and continue do not allow specifying revisions'))
152 _('abort and continue do not allow specifying revisions'))
149 if opts.get('tool', False):
153 if opts.get('tool', False):
150 ui.warn(_('tool option will be ignored\n'))
154 ui.warn(_('tool option will be ignored\n'))
151
155
152 (originalwd, target, state, skipped, collapsef, keepf,
156 (originalwd, target, state, skipped, collapsef, keepf,
153 keepbranchesf, external) = restorestatus(repo)
157 keepbranchesf, external) = restorestatus(repo)
154 if abortf:
158 if abortf:
155 return abort(repo, originalwd, target, state)
159 return abort(repo, originalwd, target, state)
156 else:
160 else:
157 if srcf and basef:
161 if srcf and basef:
158 raise util.Abort(_('cannot specify both a '
162 raise util.Abort(_('cannot specify both a '
163 'source and a base'))
164 if revf and basef:
165 raise util.Abort(_('cannot specify both a'
159 'revision and a base'))
166 'revision and a base'))
167 if revf and srcf:
168 raise util.Abort(_('cannot specify both a'
169 'revision and a source'))
160 if detachf:
170 if detachf:
161 if not srcf:
171 if not srcf:
162 raise util.Abort(
172 raise util.Abort(
163 _('detach requires a revision to be specified'))
173 _('detach requires a revision to be specified'))
164 if basef:
174 if basef:
165 raise util.Abort(_('cannot specify a base with detach'))
175 raise util.Abort(_('cannot specify a base with detach'))
166
176
167 cmdutil.bailifchanged(repo)
177 cmdutil.bailifchanged(repo)
168
178
169 if not destf:
179 if not destf:
170 # Destination defaults to the latest revision in the current branch
180 # Destination defaults to the latest revision in the
181 # current branch
171 branch = repo[None].branch()
182 branch = repo[None].branch()
172 dest = repo[branch]
183 dest = repo[branch]
173 else:
184 else:
174 dest = repo[destf]
185 dest = repo[destf]
175
186
187 rebaseset = None
176 if srcf:
188 if srcf:
177 revsetargs = ('(%r)::', srcf)
189 revsetargs = ('(%r)::', srcf)
190 elif revf:
191 rebaseset = scmutil.revrange(repo, revf)
192 if not keepf and rebaseset:
193 try:
194 repo.set('children(%ld) - %ld',
195 rebaseset, rebaseset).next()
196 except StopIteration:
197 pass # empty revset is what we look for
198 else:
199 msg = _("can't remove original changesets with"
200 " unrebased descendants")
201 hint = _('use --keep to keep original changesets')
202 raise util.Abort(msg, hint=hint)
178 else:
203 else:
179 base = basef or '.'
204 base = basef or '.'
180 revsetargs = ('(children(ancestor(%r, %d)) and ::(%r))::',
205 revsetargs = ('(children(ancestor(%r, %d)) and ::(%r))::',
181 base, dest, base)
206 base, dest, base)
182
207 if rebaseset is None:
183 rebaseset = [c.rev() for c in repo.set(*revsetargs)]
208 rebaseset = [c.rev() for c in repo.set(*revsetargs)]
184 if rebaseset:
209 if rebaseset:
185 result = buildstate(repo, dest, rebaseset, detachf)
210 result = buildstate(repo, dest, rebaseset, detachf)
186 else:
211 else:
187 repo.ui.debug(_('base is ancestor of destination'))
212 repo.ui.debug('base is ancestor of destination')
188 result = None
213 result = None
189 if not result:
214 if not result:
190 # Empty state built, nothing to rebase
215 # Empty state built, nothing to rebase
191 ui.status(_('nothing to rebase\n'))
216 ui.status(_('nothing to rebase\n'))
192 return 1
217 return 1
193 else:
218 else:
194 originalwd, target, state = result
219 originalwd, target, state = result
195 if collapsef:
220 if collapsef:
196 targetancestors = set(repo.changelog.ancestors(target))
221 targetancestors = set(repo.changelog.ancestors(target))
197 external = checkexternal(repo, state, targetancestors)
222 external = checkexternal(repo, state, targetancestors)
198
223
199 if keepbranchesf:
224 if keepbranchesf:
200 assert not extrafn, 'cannot use both keepbranches and extrafn'
225 assert not extrafn, 'cannot use both keepbranches and extrafn'
201 def extrafn(ctx, extra):
226 def extrafn(ctx, extra):
202 extra['branch'] = ctx.branch()
227 extra['branch'] = ctx.branch()
203 if collapsef:
228 if collapsef:
204 branches = set()
229 branches = set()
205 for rev in state:
230 for rev in state:
206 branches.add(repo[rev].branch())
231 branches.add(repo[rev].branch())
207 if len(branches) > 1:
232 if len(branches) > 1:
208 raise util.Abort(_('cannot collapse multiple named '
233 raise util.Abort(_('cannot collapse multiple named '
209 'branches'))
234 'branches'))
210
235
211
236
212 # Rebase
237 # Rebase
213 if not targetancestors:
238 if not targetancestors:
214 targetancestors = set(repo.changelog.ancestors(target))
239 targetancestors = set(repo.changelog.ancestors(target))
215 targetancestors.add(target)
240 targetancestors.add(target)
216
241
217 # Keep track of the current bookmarks in order to reset them later
242 # Keep track of the current bookmarks in order to reset them later
218 currentbookmarks = repo._bookmarks.copy()
243 currentbookmarks = repo._bookmarks.copy()
219
244
220 sortedstate = sorted(state)
245 sortedstate = sorted(state)
221 total = len(sortedstate)
246 total = len(sortedstate)
222 pos = 0
247 pos = 0
223 for rev in sortedstate:
248 for rev in sortedstate:
224 pos += 1
249 pos += 1
225 if state[rev] == -1:
250 if state[rev] == -1:
226 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
251 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
227 _('changesets'), total)
252 _('changesets'), total)
228 storestatus(repo, originalwd, target, state, collapsef, keepf,
253 storestatus(repo, originalwd, target, state, collapsef, keepf,
229 keepbranchesf, external)
254 keepbranchesf, external)
230 p1, p2 = defineparents(repo, rev, target, state,
255 p1, p2 = defineparents(repo, rev, target, state,
231 targetancestors)
256 targetancestors)
232 if len(repo.parents()) == 2:
257 if len(repo.parents()) == 2:
233 repo.ui.debug('resuming interrupted rebase\n')
258 repo.ui.debug('resuming interrupted rebase\n')
234 else:
259 else:
235 try:
260 try:
236 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
261 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
237 stats = rebasenode(repo, rev, p1, state)
262 stats = rebasenode(repo, rev, p1, state)
238 if stats and stats[3] > 0:
263 if stats and stats[3] > 0:
239 raise util.Abort(_('unresolved conflicts (see hg '
264 raise util.Abort(_('unresolved conflicts (see hg '
240 'resolve, then hg rebase --continue)'))
265 'resolve, then hg rebase --continue)'))
241 finally:
266 finally:
242 ui.setconfig('ui', 'forcemerge', '')
267 ui.setconfig('ui', 'forcemerge', '')
243 cmdutil.duplicatecopies(repo, rev, target, p2)
268 cmdutil.duplicatecopies(repo, rev, target, p2)
244 if not collapsef:
269 if not collapsef:
245 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
270 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
246 editor=editor)
271 editor=editor)
247 else:
272 else:
248 # Skip commit if we are collapsing
273 # Skip commit if we are collapsing
249 repo.dirstate.setparents(repo[p1].node())
274 repo.dirstate.setparents(repo[p1].node())
250 newrev = None
275 newrev = None
251 # Update the state
276 # Update the state
252 if newrev is not None:
277 if newrev is not None:
253 state[rev] = repo[newrev].rev()
278 state[rev] = repo[newrev].rev()
254 else:
279 else:
255 if not collapsef:
280 if not collapsef:
256 ui.note(_('no changes, revision %d skipped\n') % rev)
281 ui.note(_('no changes, revision %d skipped\n') % rev)
257 ui.debug('next revision set to %s\n' % p1)
282 ui.debug('next revision set to %s\n' % p1)
258 skipped.add(rev)
283 skipped.add(rev)
259 state[rev] = p1
284 state[rev] = p1
260
285
261 ui.progress(_('rebasing'), None)
286 ui.progress(_('rebasing'), None)
262 ui.note(_('rebase merging completed\n'))
287 ui.note(_('rebase merging completed\n'))
263
288
264 if collapsef and not keepopen:
289 if collapsef and not keepopen:
265 p1, p2 = defineparents(repo, min(state), target,
290 p1, p2 = defineparents(repo, min(state), target,
266 state, targetancestors)
291 state, targetancestors)
267 if collapsemsg:
292 if collapsemsg:
268 commitmsg = collapsemsg
293 commitmsg = collapsemsg
269 else:
294 else:
270 commitmsg = 'Collapsed revision'
295 commitmsg = 'Collapsed revision'
271 for rebased in state:
296 for rebased in state:
272 if rebased not in skipped and state[rebased] != nullmerge:
297 if rebased not in skipped and state[rebased] != nullmerge:
273 commitmsg += '\n* %s' % repo[rebased].description()
298 commitmsg += '\n* %s' % repo[rebased].description()
274 commitmsg = ui.edit(commitmsg, repo.ui.username())
299 commitmsg = ui.edit(commitmsg, repo.ui.username())
275 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
300 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
276 extrafn=extrafn, editor=editor)
301 extrafn=extrafn, editor=editor)
277
302
278 if 'qtip' in repo.tags():
303 if 'qtip' in repo.tags():
279 updatemq(repo, state, skipped, **opts)
304 updatemq(repo, state, skipped, **opts)
280
305
281 if currentbookmarks:
306 if currentbookmarks:
282 # Nodeids are needed to reset bookmarks
307 # Nodeids are needed to reset bookmarks
283 nstate = {}
308 nstate = {}
284 for k, v in state.iteritems():
309 for k, v in state.iteritems():
285 if v != nullmerge:
310 if v != nullmerge:
286 nstate[repo[k].node()] = repo[v].node()
311 nstate[repo[k].node()] = repo[v].node()
287
312
288 if not keepf:
313 if not keepf:
289 # Remove no more useful revisions
314 # Remove no more useful revisions
290 rebased = [rev for rev in state if state[rev] != nullmerge]
315 rebased = [rev for rev in state if state[rev] != nullmerge]
291 if rebased:
316 if rebased:
292 if set(repo.changelog.descendants(min(rebased))) - set(state):
317 if set(repo.changelog.descendants(min(rebased))) - set(state):
293 ui.warn(_("warning: new changesets detected "
318 ui.warn(_("warning: new changesets detected "
294 "on source branch, not stripping\n"))
319 "on source branch, not stripping\n"))
295 else:
320 else:
296 # backup the old csets by default
321 # backup the old csets by default
297 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
322 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
298
323
299 if currentbookmarks:
324 if currentbookmarks:
300 updatebookmarks(repo, nstate, currentbookmarks, **opts)
325 updatebookmarks(repo, nstate, currentbookmarks, **opts)
301
326
302 clearstatus(repo)
327 clearstatus(repo)
303 ui.note(_("rebase completed\n"))
328 ui.note(_("rebase completed\n"))
304 if os.path.exists(repo.sjoin('undo')):
329 if os.path.exists(repo.sjoin('undo')):
305 util.unlinkpath(repo.sjoin('undo'))
330 util.unlinkpath(repo.sjoin('undo'))
306 if skipped:
331 if skipped:
307 ui.note(_("%d revisions have been skipped\n") % len(skipped))
332 ui.note(_("%d revisions have been skipped\n") % len(skipped))
308 finally:
333 finally:
309 release(lock, wlock)
334 release(lock, wlock)
310
335
311 def checkexternal(repo, state, targetancestors):
336 def checkexternal(repo, state, targetancestors):
312 """Check whether one or more external revisions need to be taken in
337 """Check whether one or more external revisions need to be taken in
313 consideration. In the latter case, abort.
338 consideration. In the latter case, abort.
314 """
339 """
315 external = nullrev
340 external = nullrev
316 source = min(state)
341 source = min(state)
317 for rev in state:
342 for rev in state:
318 if rev == source:
343 if rev == source:
319 continue
344 continue
320 # Check externals and fail if there are more than one
345 # Check externals and fail if there are more than one
321 for p in repo[rev].parents():
346 for p in repo[rev].parents():
322 if (p.rev() not in state
347 if (p.rev() not in state
323 and p.rev() not in targetancestors):
348 and p.rev() not in targetancestors):
324 if external != nullrev:
349 if external != nullrev:
325 raise util.Abort(_('unable to collapse, there is more '
350 raise util.Abort(_('unable to collapse, there is more '
326 'than one external parent'))
351 'than one external parent'))
327 external = p.rev()
352 external = p.rev()
328 return external
353 return external
329
354
330 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
355 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
331 'Commit the changes and store useful information in extra'
356 'Commit the changes and store useful information in extra'
332 try:
357 try:
333 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
358 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
334 ctx = repo[rev]
359 ctx = repo[rev]
335 if commitmsg is None:
360 if commitmsg is None:
336 commitmsg = ctx.description()
361 commitmsg = ctx.description()
337 extra = {'rebase_source': ctx.hex()}
362 extra = {'rebase_source': ctx.hex()}
338 if extrafn:
363 if extrafn:
339 extrafn(ctx, extra)
364 extrafn(ctx, extra)
340 # Commit might fail if unresolved files exist
365 # Commit might fail if unresolved files exist
341 newrev = repo.commit(text=commitmsg, user=ctx.user(),
366 newrev = repo.commit(text=commitmsg, user=ctx.user(),
342 date=ctx.date(), extra=extra, editor=editor)
367 date=ctx.date(), extra=extra, editor=editor)
343 repo.dirstate.setbranch(repo[newrev].branch())
368 repo.dirstate.setbranch(repo[newrev].branch())
344 return newrev
369 return newrev
345 except util.Abort:
370 except util.Abort:
346 # Invalidate the previous setparents
371 # Invalidate the previous setparents
347 repo.dirstate.invalidate()
372 repo.dirstate.invalidate()
348 raise
373 raise
349
374
350 def rebasenode(repo, rev, p1, state):
375 def rebasenode(repo, rev, p1, state):
351 'Rebase a single revision'
376 'Rebase a single revision'
352 # Merge phase
377 # Merge phase
353 # Update to target and merge it with local
378 # Update to target and merge it with local
354 if repo['.'].rev() != repo[p1].rev():
379 if repo['.'].rev() != repo[p1].rev():
355 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
380 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
356 merge.update(repo, p1, False, True, False)
381 merge.update(repo, p1, False, True, False)
357 else:
382 else:
358 repo.ui.debug(" already in target\n")
383 repo.ui.debug(" already in target\n")
359 repo.dirstate.write()
384 repo.dirstate.write()
360 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
385 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
361 base = None
386 base = None
362 if repo[rev].rev() != repo[min(state)].rev():
387 if repo[rev].rev() != repo[min(state)].rev():
363 base = repo[rev].p1().node()
388 base = repo[rev].p1().node()
364 return merge.update(repo, rev, True, True, False, base)
389 return merge.update(repo, rev, True, True, False, base)
365
390
366 def defineparents(repo, rev, target, state, targetancestors):
391 def defineparents(repo, rev, target, state, targetancestors):
367 'Return the new parent relationship of the revision that will be rebased'
392 'Return the new parent relationship of the revision that will be rebased'
368 parents = repo[rev].parents()
393 parents = repo[rev].parents()
369 p1 = p2 = nullrev
394 p1 = p2 = nullrev
370
395
371 P1n = parents[0].rev()
396 P1n = parents[0].rev()
372 if P1n in targetancestors:
397 if P1n in targetancestors:
373 p1 = target
398 p1 = target
374 elif P1n in state:
399 elif P1n in state:
375 if state[P1n] == nullmerge:
400 if state[P1n] == nullmerge:
376 p1 = target
401 p1 = target
377 else:
402 else:
378 p1 = state[P1n]
403 p1 = state[P1n]
379 else: # P1n external
404 else: # P1n external
380 p1 = target
405 p1 = target
381 p2 = P1n
406 p2 = P1n
382
407
383 if len(parents) == 2 and parents[1].rev() not in targetancestors:
408 if len(parents) == 2 and parents[1].rev() not in targetancestors:
384 P2n = parents[1].rev()
409 P2n = parents[1].rev()
385 # interesting second parent
410 # interesting second parent
386 if P2n in state:
411 if P2n in state:
387 if p1 == target: # P1n in targetancestors or external
412 if p1 == target: # P1n in targetancestors or external
388 p1 = state[P2n]
413 p1 = state[P2n]
389 else:
414 else:
390 p2 = state[P2n]
415 p2 = state[P2n]
391 else: # P2n external
416 else: # P2n external
392 if p2 != nullrev: # P1n external too => rev is a merged revision
417 if p2 != nullrev: # P1n external too => rev is a merged revision
393 raise util.Abort(_('cannot use revision %d as base, result '
418 raise util.Abort(_('cannot use revision %d as base, result '
394 'would have 3 parents') % rev)
419 'would have 3 parents') % rev)
395 p2 = P2n
420 p2 = P2n
396 repo.ui.debug(" future parents are %d and %d\n" %
421 repo.ui.debug(" future parents are %d and %d\n" %
397 (repo[p1].rev(), repo[p2].rev()))
422 (repo[p1].rev(), repo[p2].rev()))
398 return p1, p2
423 return p1, p2
399
424
400 def isagitpatch(repo, patchname):
425 def isagitpatch(repo, patchname):
401 'Return true if the given patch is in git format'
426 'Return true if the given patch is in git format'
402 mqpatch = os.path.join(repo.mq.path, patchname)
427 mqpatch = os.path.join(repo.mq.path, patchname)
403 for line in patch.linereader(file(mqpatch, 'rb')):
428 for line in patch.linereader(file(mqpatch, 'rb')):
404 if line.startswith('diff --git'):
429 if line.startswith('diff --git'):
405 return True
430 return True
406 return False
431 return False
407
432
408 def updatemq(repo, state, skipped, **opts):
433 def updatemq(repo, state, skipped, **opts):
409 'Update rebased mq patches - finalize and then import them'
434 'Update rebased mq patches - finalize and then import them'
410 mqrebase = {}
435 mqrebase = {}
411 mq = repo.mq
436 mq = repo.mq
412 original_series = mq.fullseries[:]
437 original_series = mq.fullseries[:]
413
438
414 for p in mq.applied:
439 for p in mq.applied:
415 rev = repo[p.node].rev()
440 rev = repo[p.node].rev()
416 if rev in state:
441 if rev in state:
417 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
442 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
418 (rev, p.name))
443 (rev, p.name))
419 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
444 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
420
445
421 if mqrebase:
446 if mqrebase:
422 mq.finish(repo, mqrebase.keys())
447 mq.finish(repo, mqrebase.keys())
423
448
424 # We must start import from the newest revision
449 # We must start import from the newest revision
425 for rev in sorted(mqrebase, reverse=True):
450 for rev in sorted(mqrebase, reverse=True):
426 if rev not in skipped:
451 if rev not in skipped:
427 name, isgit = mqrebase[rev]
452 name, isgit = mqrebase[rev]
428 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
453 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
429 mq.qimport(repo, (), patchname=name, git=isgit,
454 mq.qimport(repo, (), patchname=name, git=isgit,
430 rev=[str(state[rev])])
455 rev=[str(state[rev])])
431
456
432 # restore old series to preserve guards
457 # restore old series to preserve guards
433 mq.fullseries = original_series
458 mq.fullseries = original_series
434 mq.series_dirty = True
459 mq.series_dirty = True
435 mq.savedirty()
460 mq.savedirty()
436
461
437 def updatebookmarks(repo, nstate, originalbookmarks, **opts):
462 def updatebookmarks(repo, nstate, originalbookmarks, **opts):
438 'Move bookmarks to their correct changesets'
463 'Move bookmarks to their correct changesets'
439 current = repo._bookmarkcurrent
464 current = repo._bookmarkcurrent
440 for k, v in originalbookmarks.iteritems():
465 for k, v in originalbookmarks.iteritems():
441 if v in nstate:
466 if v in nstate:
442 if nstate[v] != nullmerge:
467 if nstate[v] != nullmerge:
443 # reset the pointer if the bookmark was moved incorrectly
468 # reset the pointer if the bookmark was moved incorrectly
444 if k != current:
469 if k != current:
445 repo._bookmarks[k] = nstate[v]
470 repo._bookmarks[k] = nstate[v]
446
471
447 bookmarks.write(repo)
472 bookmarks.write(repo)
448
473
449 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
474 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
450 external):
475 external):
451 'Store the current status to allow recovery'
476 'Store the current status to allow recovery'
452 f = repo.opener("rebasestate", "w")
477 f = repo.opener("rebasestate", "w")
453 f.write(repo[originalwd].hex() + '\n')
478 f.write(repo[originalwd].hex() + '\n')
454 f.write(repo[target].hex() + '\n')
479 f.write(repo[target].hex() + '\n')
455 f.write(repo[external].hex() + '\n')
480 f.write(repo[external].hex() + '\n')
456 f.write('%d\n' % int(collapse))
481 f.write('%d\n' % int(collapse))
457 f.write('%d\n' % int(keep))
482 f.write('%d\n' % int(keep))
458 f.write('%d\n' % int(keepbranches))
483 f.write('%d\n' % int(keepbranches))
459 for d, v in state.iteritems():
484 for d, v in state.iteritems():
460 oldrev = repo[d].hex()
485 oldrev = repo[d].hex()
461 newrev = repo[v].hex()
486 newrev = repo[v].hex()
462 f.write("%s:%s\n" % (oldrev, newrev))
487 f.write("%s:%s\n" % (oldrev, newrev))
463 f.close()
488 f.close()
464 repo.ui.debug('rebase status stored\n')
489 repo.ui.debug('rebase status stored\n')
465
490
466 def clearstatus(repo):
491 def clearstatus(repo):
467 'Remove the status files'
492 'Remove the status files'
468 if os.path.exists(repo.join("rebasestate")):
493 if os.path.exists(repo.join("rebasestate")):
469 util.unlinkpath(repo.join("rebasestate"))
494 util.unlinkpath(repo.join("rebasestate"))
470
495
471 def restorestatus(repo):
496 def restorestatus(repo):
472 'Restore a previously stored status'
497 'Restore a previously stored status'
473 try:
498 try:
474 target = None
499 target = None
475 collapse = False
500 collapse = False
476 external = nullrev
501 external = nullrev
477 state = {}
502 state = {}
478 f = repo.opener("rebasestate")
503 f = repo.opener("rebasestate")
479 for i, l in enumerate(f.read().splitlines()):
504 for i, l in enumerate(f.read().splitlines()):
480 if i == 0:
505 if i == 0:
481 originalwd = repo[l].rev()
506 originalwd = repo[l].rev()
482 elif i == 1:
507 elif i == 1:
483 target = repo[l].rev()
508 target = repo[l].rev()
484 elif i == 2:
509 elif i == 2:
485 external = repo[l].rev()
510 external = repo[l].rev()
486 elif i == 3:
511 elif i == 3:
487 collapse = bool(int(l))
512 collapse = bool(int(l))
488 elif i == 4:
513 elif i == 4:
489 keep = bool(int(l))
514 keep = bool(int(l))
490 elif i == 5:
515 elif i == 5:
491 keepbranches = bool(int(l))
516 keepbranches = bool(int(l))
492 else:
517 else:
493 oldrev, newrev = l.split(':')
518 oldrev, newrev = l.split(':')
494 state[repo[oldrev].rev()] = repo[newrev].rev()
519 state[repo[oldrev].rev()] = repo[newrev].rev()
495 skipped = set()
520 skipped = set()
496 # recompute the set of skipped revs
521 # recompute the set of skipped revs
497 if not collapse:
522 if not collapse:
498 seen = set([target])
523 seen = set([target])
499 for old, new in sorted(state.items()):
524 for old, new in sorted(state.items()):
500 if new != nullrev and new in seen:
525 if new != nullrev and new in seen:
501 skipped.add(old)
526 skipped.add(old)
502 seen.add(new)
527 seen.add(new)
503 repo.ui.debug('computed skipped revs: %s\n' % skipped)
528 repo.ui.debug('computed skipped revs: %s\n' % skipped)
504 repo.ui.debug('rebase status resumed\n')
529 repo.ui.debug('rebase status resumed\n')
505 return (originalwd, target, state, skipped,
530 return (originalwd, target, state, skipped,
506 collapse, keep, keepbranches, external)
531 collapse, keep, keepbranches, external)
507 except IOError, err:
532 except IOError, err:
508 if err.errno != errno.ENOENT:
533 if err.errno != errno.ENOENT:
509 raise
534 raise
510 raise util.Abort(_('no rebase in progress'))
535 raise util.Abort(_('no rebase in progress'))
511
536
512 def abort(repo, originalwd, target, state):
537 def abort(repo, originalwd, target, state):
513 'Restore the repository to its original state'
538 'Restore the repository to its original state'
514 if set(repo.changelog.descendants(target)) - set(state.values()):
539 if set(repo.changelog.descendants(target)) - set(state.values()):
515 repo.ui.warn(_("warning: new changesets detected on target branch, "
540 repo.ui.warn(_("warning: new changesets detected on target branch, "
516 "can't abort\n"))
541 "can't abort\n"))
517 return -1
542 return -1
518 else:
543 else:
519 # Strip from the first rebased revision
544 # Strip from the first rebased revision
520 merge.update(repo, repo[originalwd].rev(), False, True, False)
545 merge.update(repo, repo[originalwd].rev(), False, True, False)
521 rebased = filter(lambda x: x > -1 and x != target, state.values())
546 rebased = filter(lambda x: x > -1 and x != target, state.values())
522 if rebased:
547 if rebased:
523 strippoint = min(rebased)
548 strippoint = min(rebased)
524 # no backup of rebased cset versions needed
549 # no backup of rebased cset versions needed
525 repair.strip(repo.ui, repo, repo[strippoint].node())
550 repair.strip(repo.ui, repo, repo[strippoint].node())
526 clearstatus(repo)
551 clearstatus(repo)
527 repo.ui.warn(_('rebase aborted\n'))
552 repo.ui.warn(_('rebase aborted\n'))
528 return 0
553 return 0
529
554
530 def buildstate(repo, dest, rebaseset, detach):
555 def buildstate(repo, dest, rebaseset, detach):
531 '''Define which revisions are going to be rebased and where
556 '''Define which revisions are going to be rebased and where
532
557
533 repo: repo
558 repo: repo
534 dest: context
559 dest: context
535 rebaseset: set of rev
560 rebaseset: set of rev
536 detach: boolean'''
561 detach: boolean'''
537
562
538 # This check isn't strictly necessary, since mq detects commits over an
563 # This check isn't strictly necessary, since mq detects commits over an
539 # applied patch. But it prevents messing up the working directory when
564 # applied patch. But it prevents messing up the working directory when
540 # a partially completed rebase is blocked by mq.
565 # a partially completed rebase is blocked by mq.
541 if 'qtip' in repo.tags() and (dest.node() in
566 if 'qtip' in repo.tags() and (dest.node() in
542 [s.node for s in repo.mq.applied]):
567 [s.node for s in repo.mq.applied]):
543 raise util.Abort(_('cannot rebase onto an applied mq patch'))
568 raise util.Abort(_('cannot rebase onto an applied mq patch'))
544
569
545 detachset = set()
570 detachset = set()
546 roots = list(repo.set('roots(%ld)', rebaseset))
571 roots = list(repo.set('roots(%ld)', rebaseset))
547 if not roots:
572 if not roots:
548 raise util.Abort( _('no matching revisions'))
573 raise util.Abort(_('no matching revisions'))
549 if len(roots) > 1:
574 if len(roots) > 1:
550 raise util.Abort( _("can't rebase multiple roots"))
575 raise util.Abort(_("can't rebase multiple roots"))
551 root = roots[0]
576 root = roots[0]
552
577
553 commonbase = root.ancestor(dest)
578 commonbase = root.ancestor(dest)
554 if commonbase == root:
579 if commonbase == root:
555 raise util.Abort(_('source is ancestor of destination'))
580 raise util.Abort(_('source is ancestor of destination'))
556 if commonbase == dest:
581 if commonbase == dest:
557 samebranch = root.branch() == dest.branch()
582 samebranch = root.branch() == dest.branch()
558 if samebranch and root in dest.children():
583 if samebranch and root in dest.children():
559 repo.ui.debug(_('source is a child of destination'))
584 repo.ui.debug(_('source is a child of destination'))
560 return None
585 return None
561 # rebase on ancestor, force detach
586 # rebase on ancestor, force detach
562 detach = True
587 detach = True
563 if detach:
588 if detach:
564 detachset = [c.rev() for c in repo.set('::%d - ::%d - %d',
589 detachset = [c.rev() for c in repo.set('::%d - ::%d - %d',
565 root, commonbase, root)]
590 root, commonbase, root)]
566
591
567 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root))
592 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root))
568 state = dict.fromkeys(rebaseset, nullrev)
593 state = dict.fromkeys(rebaseset, nullrev)
569 state.update(dict.fromkeys(detachset, nullmerge))
594 state.update(dict.fromkeys(detachset, nullmerge))
570 return repo['.'].rev(), dest.rev(), state
595 return repo['.'].rev(), dest.rev(), state
571
596
572 def pullrebase(orig, ui, repo, *args, **opts):
597 def pullrebase(orig, ui, repo, *args, **opts):
573 'Call rebase after pull if the latter has been invoked with --rebase'
598 'Call rebase after pull if the latter has been invoked with --rebase'
574 if opts.get('rebase'):
599 if opts.get('rebase'):
575 if opts.get('update'):
600 if opts.get('update'):
576 del opts['update']
601 del opts['update']
577 ui.debug('--update and --rebase are not compatible, ignoring '
602 ui.debug('--update and --rebase are not compatible, ignoring '
578 'the update flag\n')
603 'the update flag\n')
579
604
580 cmdutil.bailifchanged(repo)
605 cmdutil.bailifchanged(repo)
581 revsprepull = len(repo)
606 revsprepull = len(repo)
582 origpostincoming = commands.postincoming
607 origpostincoming = commands.postincoming
583 def _dummy(*args, **kwargs):
608 def _dummy(*args, **kwargs):
584 pass
609 pass
585 commands.postincoming = _dummy
610 commands.postincoming = _dummy
586 try:
611 try:
587 orig(ui, repo, *args, **opts)
612 orig(ui, repo, *args, **opts)
588 finally:
613 finally:
589 commands.postincoming = origpostincoming
614 commands.postincoming = origpostincoming
590 revspostpull = len(repo)
615 revspostpull = len(repo)
591 if revspostpull > revsprepull:
616 if revspostpull > revsprepull:
592 rebase(ui, repo, **opts)
617 rebase(ui, repo, **opts)
593 branch = repo[None].branch()
618 branch = repo[None].branch()
594 dest = repo[branch].rev()
619 dest = repo[branch].rev()
595 if dest != repo['.'].rev():
620 if dest != repo['.'].rev():
596 # there was nothing to rebase we force an update
621 # there was nothing to rebase we force an update
597 hg.update(repo, dest)
622 hg.update(repo, dest)
598 else:
623 else:
599 if opts.get('tool'):
624 if opts.get('tool'):
600 raise util.Abort(_('--tool can only be used with --rebase'))
625 raise util.Abort(_('--tool can only be used with --rebase'))
601 orig(ui, repo, *args, **opts)
626 orig(ui, repo, *args, **opts)
602
627
603 def uisetup(ui):
628 def uisetup(ui):
604 'Replace pull with a decorator to provide --rebase option'
629 'Replace pull with a decorator to provide --rebase option'
605 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
630 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
606 entry[1].append(('', 'rebase', None,
631 entry[1].append(('', 'rebase', None,
607 _("rebase working directory to branch head")))
632 _("rebase working directory to branch head")))
608 entry[1].append(('t', 'tool', '',
633 entry[1].append(('t', 'tool', '',
609 _("specify merge tool for rebase")))
634 _("specify merge tool for rebase")))
@@ -1,389 +1,389 b''
1 $ cat >> $HGRCPATH <<EOF
1 $ cat >> $HGRCPATH <<EOF
2 > [extensions]
2 > [extensions]
3 > graphlog=
3 > graphlog=
4 > rebase=
4 > rebase=
5 >
5 >
6 > [alias]
6 > [alias]
7 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
7 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
8 > EOF
8 > EOF
9
9
10
10
11 $ hg init a
11 $ hg init a
12 $ cd a
12 $ cd a
13 $ hg unbundle $TESTDIR/bundles/rebase.hg
13 $ hg unbundle $TESTDIR/bundles/rebase.hg
14 adding changesets
14 adding changesets
15 adding manifests
15 adding manifests
16 adding file changes
16 adding file changes
17 added 8 changesets with 7 changes to 7 files (+2 heads)
17 added 8 changesets with 7 changes to 7 files (+2 heads)
18 (run 'hg heads' to see heads, 'hg merge' to merge)
18 (run 'hg heads' to see heads, 'hg merge' to merge)
19 $ hg up tip
19 $ hg up tip
20 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
20 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
21
21
22 $ echo I > I
22 $ echo I > I
23 $ hg ci -AmI
23 $ hg ci -AmI
24 adding I
24 adding I
25
25
26 $ hg tglog
26 $ hg tglog
27 @ 8: 'I'
27 @ 8: 'I'
28 |
28 |
29 o 7: 'H'
29 o 7: 'H'
30 |
30 |
31 | o 6: 'G'
31 | o 6: 'G'
32 |/|
32 |/|
33 o | 5: 'F'
33 o | 5: 'F'
34 | |
34 | |
35 | o 4: 'E'
35 | o 4: 'E'
36 |/
36 |/
37 | o 3: 'D'
37 | o 3: 'D'
38 | |
38 | |
39 | o 2: 'C'
39 | o 2: 'C'
40 | |
40 | |
41 | o 1: 'B'
41 | o 1: 'B'
42 |/
42 |/
43 o 0: 'A'
43 o 0: 'A'
44
44
45 $ cd ..
45 $ cd ..
46
46
47
47
48 These fail:
48 These fail:
49
49
50 $ hg clone -q -u . a a1
50 $ hg clone -q -u . a a1
51 $ cd a1
51 $ cd a1
52
52
53 $ hg rebase -s 8 -d 7
53 $ hg rebase -s 8 -d 7
54 nothing to rebase
54 nothing to rebase
55 [1]
55 [1]
56
56
57 $ hg rebase --continue --abort
57 $ hg rebase --continue --abort
58 abort: cannot use both abort and continue
58 abort: cannot use both abort and continue
59 [255]
59 [255]
60
60
61 $ hg rebase --continue --collapse
61 $ hg rebase --continue --collapse
62 abort: cannot use collapse with continue or abort
62 abort: cannot use collapse with continue or abort
63 [255]
63 [255]
64
64
65 $ hg rebase --continue --dest 4
65 $ hg rebase --continue --dest 4
66 abort: abort and continue do not allow specifying revisions
66 abort: abort and continue do not allow specifying revisions
67 [255]
67 [255]
68
68
69 $ hg rebase --base 5 --source 4
69 $ hg rebase --base 5 --source 4
70 abort: cannot specify both a revision and a base
70 abort: cannot specify both a source and a base
71 [255]
71 [255]
72
72
73 $ hg rebase
73 $ hg rebase
74 nothing to rebase
74 nothing to rebase
75 [1]
75 [1]
76
76
77 $ hg up -q 7
77 $ hg up -q 7
78
78
79 $ hg rebase --traceback
79 $ hg rebase --traceback
80 nothing to rebase
80 nothing to rebase
81 [1]
81 [1]
82
82
83
83
84 These work:
84 These work:
85
85
86 Rebase with no arguments (from 3 onto 8):
86 Rebase with no arguments (from 3 onto 8):
87
87
88 $ hg up -q -C 3
88 $ hg up -q -C 3
89
89
90 $ hg rebase
90 $ hg rebase
91 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
91 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
92
92
93 $ hg tglog
93 $ hg tglog
94 @ 8: 'D'
94 @ 8: 'D'
95 |
95 |
96 o 7: 'C'
96 o 7: 'C'
97 |
97 |
98 o 6: 'B'
98 o 6: 'B'
99 |
99 |
100 o 5: 'I'
100 o 5: 'I'
101 |
101 |
102 o 4: 'H'
102 o 4: 'H'
103 |
103 |
104 | o 3: 'G'
104 | o 3: 'G'
105 |/|
105 |/|
106 o | 2: 'F'
106 o | 2: 'F'
107 | |
107 | |
108 | o 1: 'E'
108 | o 1: 'E'
109 |/
109 |/
110 o 0: 'A'
110 o 0: 'A'
111
111
112 Try to rollback after a rebase (fail):
112 Try to rollback after a rebase (fail):
113
113
114 $ hg rollback
114 $ hg rollback
115 no rollback information available
115 no rollback information available
116 [1]
116 [1]
117
117
118 $ cd ..
118 $ cd ..
119
119
120
120
121 Rebase with base == '.' => same as no arguments (from 3 onto 8):
121 Rebase with base == '.' => same as no arguments (from 3 onto 8):
122
122
123 $ hg clone -q -u 3 a a2
123 $ hg clone -q -u 3 a a2
124 $ cd a2
124 $ cd a2
125
125
126 $ hg rebase --base .
126 $ hg rebase --base .
127 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
127 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
128
128
129 $ hg tglog
129 $ hg tglog
130 @ 8: 'D'
130 @ 8: 'D'
131 |
131 |
132 o 7: 'C'
132 o 7: 'C'
133 |
133 |
134 o 6: 'B'
134 o 6: 'B'
135 |
135 |
136 o 5: 'I'
136 o 5: 'I'
137 |
137 |
138 o 4: 'H'
138 o 4: 'H'
139 |
139 |
140 | o 3: 'G'
140 | o 3: 'G'
141 |/|
141 |/|
142 o | 2: 'F'
142 o | 2: 'F'
143 | |
143 | |
144 | o 1: 'E'
144 | o 1: 'E'
145 |/
145 |/
146 o 0: 'A'
146 o 0: 'A'
147
147
148 $ cd ..
148 $ cd ..
149
149
150
150
151 Rebase with dest == `hg branch` => same as no arguments (from 3 onto 8):
151 Rebase with dest == `hg branch` => same as no arguments (from 3 onto 8):
152
152
153 $ hg clone -q -u 3 a a3
153 $ hg clone -q -u 3 a a3
154 $ cd a3
154 $ cd a3
155
155
156 $ hg rebase --dest `hg branch`
156 $ hg rebase --dest `hg branch`
157 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
157 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
158
158
159 $ hg tglog
159 $ hg tglog
160 @ 8: 'D'
160 @ 8: 'D'
161 |
161 |
162 o 7: 'C'
162 o 7: 'C'
163 |
163 |
164 o 6: 'B'
164 o 6: 'B'
165 |
165 |
166 o 5: 'I'
166 o 5: 'I'
167 |
167 |
168 o 4: 'H'
168 o 4: 'H'
169 |
169 |
170 | o 3: 'G'
170 | o 3: 'G'
171 |/|
171 |/|
172 o | 2: 'F'
172 o | 2: 'F'
173 | |
173 | |
174 | o 1: 'E'
174 | o 1: 'E'
175 |/
175 |/
176 o 0: 'A'
176 o 0: 'A'
177
177
178 $ cd ..
178 $ cd ..
179
179
180
180
181 Specify only source (from 2 onto 8):
181 Specify only source (from 2 onto 8):
182
182
183 $ hg clone -q -u . a a4
183 $ hg clone -q -u . a a4
184 $ cd a4
184 $ cd a4
185
185
186 $ hg rebase --source 2
186 $ hg rebase --source 2
187 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/*-backup.hg (glob)
187 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/*-backup.hg (glob)
188
188
189 $ hg tglog
189 $ hg tglog
190 @ 8: 'D'
190 @ 8: 'D'
191 |
191 |
192 o 7: 'C'
192 o 7: 'C'
193 |\
193 |\
194 | o 6: 'I'
194 | o 6: 'I'
195 | |
195 | |
196 | o 5: 'H'
196 | o 5: 'H'
197 | |
197 | |
198 | | o 4: 'G'
198 | | o 4: 'G'
199 | |/|
199 | |/|
200 | o | 3: 'F'
200 | o | 3: 'F'
201 | | |
201 | | |
202 | | o 2: 'E'
202 | | o 2: 'E'
203 | |/
203 | |/
204 o | 1: 'B'
204 o | 1: 'B'
205 |/
205 |/
206 o 0: 'A'
206 o 0: 'A'
207
207
208 $ cd ..
208 $ cd ..
209
209
210
210
211 Specify only dest (from 3 onto 6):
211 Specify only dest (from 3 onto 6):
212
212
213 $ hg clone -q -u 3 a a5
213 $ hg clone -q -u 3 a a5
214 $ cd a5
214 $ cd a5
215
215
216 $ hg rebase --dest 6
216 $ hg rebase --dest 6
217 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/*-backup.hg (glob)
217 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/*-backup.hg (glob)
218
218
219 $ hg tglog
219 $ hg tglog
220 @ 8: 'D'
220 @ 8: 'D'
221 |
221 |
222 o 7: 'C'
222 o 7: 'C'
223 |
223 |
224 o 6: 'B'
224 o 6: 'B'
225 |
225 |
226 | o 5: 'I'
226 | o 5: 'I'
227 | |
227 | |
228 | o 4: 'H'
228 | o 4: 'H'
229 | |
229 | |
230 o | 3: 'G'
230 o | 3: 'G'
231 |\|
231 |\|
232 | o 2: 'F'
232 | o 2: 'F'
233 | |
233 | |
234 o | 1: 'E'
234 o | 1: 'E'
235 |/
235 |/
236 o 0: 'A'
236 o 0: 'A'
237
237
238 $ cd ..
238 $ cd ..
239
239
240
240
241 Specify only base (from 1 onto 8):
241 Specify only base (from 1 onto 8):
242
242
243 $ hg clone -q -u . a a6
243 $ hg clone -q -u . a a6
244 $ cd a6
244 $ cd a6
245
245
246 $ hg rebase --base 3
246 $ hg rebase --base 3
247 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/*-backup.hg (glob)
247 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/*-backup.hg (glob)
248
248
249 $ hg tglog
249 $ hg tglog
250 @ 8: 'D'
250 @ 8: 'D'
251 |
251 |
252 o 7: 'C'
252 o 7: 'C'
253 |
253 |
254 o 6: 'B'
254 o 6: 'B'
255 |
255 |
256 o 5: 'I'
256 o 5: 'I'
257 |
257 |
258 o 4: 'H'
258 o 4: 'H'
259 |
259 |
260 | o 3: 'G'
260 | o 3: 'G'
261 |/|
261 |/|
262 o | 2: 'F'
262 o | 2: 'F'
263 | |
263 | |
264 | o 1: 'E'
264 | o 1: 'E'
265 |/
265 |/
266 o 0: 'A'
266 o 0: 'A'
267
267
268 $ cd ..
268 $ cd ..
269
269
270
270
271 Specify source and dest (from 2 onto 7):
271 Specify source and dest (from 2 onto 7):
272
272
273 $ hg clone -q -u . a a7
273 $ hg clone -q -u . a a7
274 $ cd a7
274 $ cd a7
275
275
276 $ hg rebase --detach --source 2 --dest 7
276 $ hg rebase --detach --source 2 --dest 7
277 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/*-backup.hg (glob)
277 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/*-backup.hg (glob)
278
278
279 $ hg tglog
279 $ hg tglog
280 @ 8: 'D'
280 @ 8: 'D'
281 |
281 |
282 o 7: 'C'
282 o 7: 'C'
283 |
283 |
284 | o 6: 'I'
284 | o 6: 'I'
285 |/
285 |/
286 o 5: 'H'
286 o 5: 'H'
287 |
287 |
288 | o 4: 'G'
288 | o 4: 'G'
289 |/|
289 |/|
290 o | 3: 'F'
290 o | 3: 'F'
291 | |
291 | |
292 | o 2: 'E'
292 | o 2: 'E'
293 |/
293 |/
294 | o 1: 'B'
294 | o 1: 'B'
295 |/
295 |/
296 o 0: 'A'
296 o 0: 'A'
297
297
298 $ cd ..
298 $ cd ..
299
299
300
300
301 Specify base and dest (from 1 onto 7):
301 Specify base and dest (from 1 onto 7):
302
302
303 $ hg clone -q -u . a a8
303 $ hg clone -q -u . a a8
304 $ cd a8
304 $ cd a8
305
305
306 $ hg rebase --base 3 --dest 7
306 $ hg rebase --base 3 --dest 7
307 saved backup bundle to $TESTTMP/a8/.hg/strip-backup/*-backup.hg (glob)
307 saved backup bundle to $TESTTMP/a8/.hg/strip-backup/*-backup.hg (glob)
308
308
309 $ hg tglog
309 $ hg tglog
310 @ 8: 'D'
310 @ 8: 'D'
311 |
311 |
312 o 7: 'C'
312 o 7: 'C'
313 |
313 |
314 o 6: 'B'
314 o 6: 'B'
315 |
315 |
316 | o 5: 'I'
316 | o 5: 'I'
317 |/
317 |/
318 o 4: 'H'
318 o 4: 'H'
319 |
319 |
320 | o 3: 'G'
320 | o 3: 'G'
321 |/|
321 |/|
322 o | 2: 'F'
322 o | 2: 'F'
323 | |
323 | |
324 | o 1: 'E'
324 | o 1: 'E'
325 |/
325 |/
326 o 0: 'A'
326 o 0: 'A'
327
327
328 $ cd ..
328 $ cd ..
329
329
330 Test --tool parameter:
330 Test --tool parameter:
331
331
332 $ hg init b
332 $ hg init b
333 $ cd b
333 $ cd b
334
334
335 $ echo c1 > c1
335 $ echo c1 > c1
336 $ hg ci -Am c1
336 $ hg ci -Am c1
337 adding c1
337 adding c1
338
338
339 $ echo c2 > c2
339 $ echo c2 > c2
340 $ hg ci -Am c2
340 $ hg ci -Am c2
341 adding c2
341 adding c2
342
342
343 $ hg up -q 0
343 $ hg up -q 0
344 $ echo c2b > c2
344 $ echo c2b > c2
345 $ hg ci -Am c2b
345 $ hg ci -Am c2b
346 adding c2
346 adding c2
347 created new head
347 created new head
348
348
349 $ cd ..
349 $ cd ..
350
350
351 $ hg clone -q -u . b b1
351 $ hg clone -q -u . b b1
352 $ cd b1
352 $ cd b1
353
353
354 $ hg rebase -s 2 -d 1 --tool internal:local
354 $ hg rebase -s 2 -d 1 --tool internal:local
355 saved backup bundle to $TESTTMP/b1/.hg/strip-backup/*-backup.hg (glob)
355 saved backup bundle to $TESTTMP/b1/.hg/strip-backup/*-backup.hg (glob)
356
356
357 $ hg cat c2
357 $ hg cat c2
358 c2
358 c2
359
359
360 $ cd ..
360 $ cd ..
361
361
362
362
363 $ hg clone -q -u . b b2
363 $ hg clone -q -u . b b2
364 $ cd b2
364 $ cd b2
365
365
366 $ hg rebase -s 2 -d 1 --tool internal:other
366 $ hg rebase -s 2 -d 1 --tool internal:other
367 saved backup bundle to $TESTTMP/b2/.hg/strip-backup/*-backup.hg (glob)
367 saved backup bundle to $TESTTMP/b2/.hg/strip-backup/*-backup.hg (glob)
368
368
369 $ hg cat c2
369 $ hg cat c2
370 c2b
370 c2b
371
371
372 $ cd ..
372 $ cd ..
373
373
374
374
375 $ hg clone -q -u . b b3
375 $ hg clone -q -u . b b3
376 $ cd b3
376 $ cd b3
377
377
378 $ hg rebase -s 2 -d 1 --tool internal:fail
378 $ hg rebase -s 2 -d 1 --tool internal:fail
379 abort: unresolved conflicts (see hg resolve, then hg rebase --continue)
379 abort: unresolved conflicts (see hg resolve, then hg rebase --continue)
380 [255]
380 [255]
381
381
382 $ hg resolve -l
382 $ hg resolve -l
383 U c2
383 U c2
384
384
385 $ hg resolve -m c2
385 $ hg resolve -m c2
386 $ hg rebase -c --tool internal:fail
386 $ hg rebase -c --tool internal:fail
387 tool option will be ignored
387 tool option will be ignored
388 saved backup bundle to $TESTTMP/b3/.hg/strip-backup/*-backup.hg (glob)
388 saved backup bundle to $TESTTMP/b3/.hg/strip-backup/*-backup.hg (glob)
389
389
@@ -1,272 +1,508 b''
1 $ cat >> $HGRCPATH <<EOF
1 $ cat >> $HGRCPATH <<EOF
2 > [extensions]
2 > [extensions]
3 > graphlog=
3 > graphlog=
4 > rebase=
4 > rebase=
5 >
5 >
6 > [alias]
6 > [alias]
7 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
7 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
8 > EOF
8 > EOF
9
9
10
10
11 $ hg init a
11 $ hg init a
12 $ cd a
12 $ cd a
13 $ hg unbundle $TESTDIR/bundles/rebase.hg
13 $ hg unbundle $TESTDIR/bundles/rebase.hg
14 adding changesets
14 adding changesets
15 adding manifests
15 adding manifests
16 adding file changes
16 adding file changes
17 added 8 changesets with 7 changes to 7 files (+2 heads)
17 added 8 changesets with 7 changes to 7 files (+2 heads)
18 (run 'hg heads' to see heads, 'hg merge' to merge)
18 (run 'hg heads' to see heads, 'hg merge' to merge)
19 $ hg up tip
19 $ hg up tip
20 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
20 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
21 $ cd ..
21 $ cd ..
22
22
23
23
24 Rebasing
24 Rebasing
25 D onto H - simple rebase:
25 D onto H - simple rebase:
26
26
27 $ hg clone -q -u . a a1
27 $ hg clone -q -u . a a1
28 $ cd a1
28 $ cd a1
29
29
30 $ hg tglog
30 $ hg tglog
31 @ 7: 'H'
31 @ 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
47
48 $ hg rebase -s 3 -d 7
48 $ hg rebase -s 3 -d 7
49 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
49 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
50
50
51 $ hg tglog
51 $ hg tglog
52 @ 7: 'D'
52 @ 7: 'D'
53 |\
53 |\
54 | o 6: 'H'
54 | o 6: 'H'
55 | |
55 | |
56 | | o 5: 'G'
56 | | o 5: 'G'
57 | |/|
57 | |/|
58 | o | 4: 'F'
58 | o | 4: 'F'
59 | | |
59 | | |
60 | | o 3: 'E'
60 | | o 3: 'E'
61 | |/
61 | |/
62 o | 2: 'C'
62 o | 2: 'C'
63 | |
63 | |
64 o | 1: 'B'
64 o | 1: 'B'
65 |/
65 |/
66 o 0: 'A'
66 o 0: 'A'
67
67
68 $ cd ..
68 $ cd ..
69
69
70
70
71 D onto F - intermediate point:
71 D onto F - intermediate point:
72
72
73 $ hg clone -q -u . a a2
73 $ hg clone -q -u . a a2
74 $ cd a2
74 $ cd a2
75
75
76 $ hg rebase -s 3 -d 5
76 $ hg rebase -s 3 -d 5
77 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
77 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
78
78
79 $ hg tglog
79 $ hg tglog
80 @ 7: 'D'
80 @ 7: 'D'
81 |\
81 |\
82 | | o 6: 'H'
82 | | o 6: 'H'
83 | |/
83 | |/
84 | | o 5: 'G'
84 | | o 5: 'G'
85 | |/|
85 | |/|
86 | o | 4: 'F'
86 | o | 4: 'F'
87 | | |
87 | | |
88 | | o 3: 'E'
88 | | o 3: 'E'
89 | |/
89 | |/
90 o | 2: 'C'
90 o | 2: 'C'
91 | |
91 | |
92 o | 1: 'B'
92 o | 1: 'B'
93 |/
93 |/
94 o 0: 'A'
94 o 0: 'A'
95
95
96 $ cd ..
96 $ cd ..
97
97
98
98
99 E onto H - skip of G:
99 E onto H - skip of G:
100
100
101 $ hg clone -q -u . a a3
101 $ hg clone -q -u . a a3
102 $ cd a3
102 $ cd a3
103
103
104 $ hg rebase -s 4 -d 7
104 $ hg rebase -s 4 -d 7
105 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
105 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
106
106
107 $ hg tglog
107 $ hg tglog
108 @ 6: 'E'
108 @ 6: 'E'
109 |
109 |
110 o 5: 'H'
110 o 5: 'H'
111 |
111 |
112 o 4: 'F'
112 o 4: 'F'
113 |
113 |
114 | o 3: 'D'
114 | o 3: 'D'
115 | |
115 | |
116 | o 2: 'C'
116 | o 2: 'C'
117 | |
117 | |
118 | o 1: 'B'
118 | o 1: 'B'
119 |/
119 |/
120 o 0: 'A'
120 o 0: 'A'
121
121
122 $ cd ..
122 $ cd ..
123
123
124
124
125 F onto E - rebase of a branching point (skip G):
125 F onto E - rebase of a branching point (skip G):
126
126
127 $ hg clone -q -u . a a4
127 $ hg clone -q -u . a a4
128 $ cd a4
128 $ cd a4
129
129
130 $ hg rebase -s 5 -d 4
130 $ hg rebase -s 5 -d 4
131 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/*-backup.hg (glob)
131 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/*-backup.hg (glob)
132
132
133 $ hg tglog
133 $ hg tglog
134 @ 6: 'H'
134 @ 6: 'H'
135 |
135 |
136 o 5: 'F'
136 o 5: 'F'
137 |
137 |
138 o 4: 'E'
138 o 4: 'E'
139 |
139 |
140 | o 3: 'D'
140 | o 3: 'D'
141 | |
141 | |
142 | o 2: 'C'
142 | o 2: 'C'
143 | |
143 | |
144 | o 1: 'B'
144 | o 1: 'B'
145 |/
145 |/
146 o 0: 'A'
146 o 0: 'A'
147
147
148 $ cd ..
148 $ cd ..
149
149
150
150
151 G onto H - merged revision having a parent in ancestors of target:
151 G onto H - merged revision having a parent in ancestors of target:
152
152
153 $ hg clone -q -u . a a5
153 $ hg clone -q -u . a a5
154 $ cd a5
154 $ cd a5
155
155
156 $ hg rebase -s 6 -d 7
156 $ hg rebase -s 6 -d 7
157 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/*-backup.hg (glob)
157 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/*-backup.hg (glob)
158
158
159 $ hg tglog
159 $ hg tglog
160 @ 7: 'G'
160 @ 7: 'G'
161 |\
161 |\
162 | o 6: 'H'
162 | o 6: 'H'
163 | |
163 | |
164 | o 5: 'F'
164 | o 5: 'F'
165 | |
165 | |
166 o | 4: 'E'
166 o | 4: 'E'
167 |/
167 |/
168 | o 3: 'D'
168 | o 3: 'D'
169 | |
169 | |
170 | o 2: 'C'
170 | o 2: 'C'
171 | |
171 | |
172 | o 1: 'B'
172 | o 1: 'B'
173 |/
173 |/
174 o 0: 'A'
174 o 0: 'A'
175
175
176 $ cd ..
176 $ cd ..
177
177
178
178
179 F onto B - G maintains E as parent:
179 F onto B - G maintains E as parent:
180
180
181 $ hg clone -q -u . a a6
181 $ hg clone -q -u . a a6
182 $ cd a6
182 $ cd a6
183
183
184 $ hg rebase -s 5 -d 1
184 $ hg rebase -s 5 -d 1
185 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/*-backup.hg (glob)
185 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/*-backup.hg (glob)
186
186
187 $ hg tglog
187 $ hg tglog
188 @ 7: 'H'
188 @ 7: 'H'
189 |
189 |
190 | o 6: 'G'
190 | o 6: 'G'
191 |/|
191 |/|
192 o | 5: 'F'
192 o | 5: 'F'
193 | |
193 | |
194 | o 4: 'E'
194 | o 4: 'E'
195 | |
195 | |
196 | | o 3: 'D'
196 | | o 3: 'D'
197 | | |
197 | | |
198 +---o 2: 'C'
198 +---o 2: 'C'
199 | |
199 | |
200 o | 1: 'B'
200 o | 1: 'B'
201 |/
201 |/
202 o 0: 'A'
202 o 0: 'A'
203
203
204 $ cd ..
204 $ cd ..
205
205
206
206
207 These will fail (using --source):
207 These will fail (using --source):
208
208
209 G onto F - rebase onto an ancestor:
209 G onto F - rebase onto an ancestor:
210
210
211 $ hg clone -q -u . a a7
211 $ hg clone -q -u . a a7
212 $ cd a7
212 $ cd a7
213
213
214 $ hg rebase -s 6 -d 5
214 $ hg rebase -s 6 -d 5
215 nothing to rebase
215 nothing to rebase
216 [1]
216 [1]
217
217
218 F onto G - rebase onto a descendant:
218 F onto G - rebase onto a descendant:
219
219
220 $ hg rebase -s 5 -d 6
220 $ hg rebase -s 5 -d 6
221 abort: source is ancestor of destination
221 abort: source is ancestor of destination
222 [255]
222 [255]
223
223
224 G onto B - merge revision with both parents not in ancestors of target:
224 G onto B - merge revision with both parents not in ancestors of target:
225
225
226 $ hg rebase -s 6 -d 1
226 $ hg rebase -s 6 -d 1
227 abort: cannot use revision 6 as base, result would have 3 parents
227 abort: cannot use revision 6 as base, result would have 3 parents
228 [255]
228 [255]
229
229
230
230
231 These will abort gracefully (using --base):
231 These will abort gracefully (using --base):
232
232
233 G onto G - rebase onto same changeset:
233 G onto G - rebase onto same changeset:
234
234
235 $ hg rebase -b 6 -d 6
235 $ hg rebase -b 6 -d 6
236 nothing to rebase
236 nothing to rebase
237 [1]
237 [1]
238
238
239 G onto F - rebase onto an ancestor:
239 G onto F - rebase onto an ancestor:
240
240
241 $ hg rebase -b 6 -d 5
241 $ hg rebase -b 6 -d 5
242 nothing to rebase
242 nothing to rebase
243 [1]
243 [1]
244
244
245 F onto G - rebase onto a descendant:
245 F onto G - rebase onto a descendant:
246
246
247 $ hg rebase -b 5 -d 6
247 $ hg rebase -b 5 -d 6
248 nothing to rebase
248 nothing to rebase
249 [1]
249 [1]
250
250
251 C onto A - rebase onto an ancestor:
251 C onto A - rebase onto an ancestor:
252
252
253 $ hg rebase -d 0 -s 2
253 $ hg rebase -d 0 -s 2
254 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/5fddd98957c8-backup.hg
254 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/5fddd98957c8-backup.hg
255 $ hg tglog
255 $ hg tglog
256 @ 7: 'D'
256 @ 7: 'D'
257 |
257 |
258 o 6: 'C'
258 o 6: 'C'
259 |
259 |
260 | o 5: 'H'
260 | o 5: 'H'
261 | |
261 | |
262 | | o 4: 'G'
262 | | o 4: 'G'
263 | |/|
263 | |/|
264 | o | 3: 'F'
264 | o | 3: 'F'
265 |/ /
265 |/ /
266 | o 2: 'E'
266 | o 2: 'E'
267 |/
267 |/
268 | o 1: 'B'
268 | o 1: 'B'
269 |/
269 |/
270 o 0: 'A'
270 o 0: 'A'
271
271
272 $ cd ..
272
273
274 Test for revset
275
276 We need a bit different graph
277 All destination are B
278
279 $ hg init ah
280 $ cd ah
281 $ hg unbundle $TESTDIR/bundles/rebase-revset.hg
282 adding changesets
283 adding manifests
284 adding file changes
285 added 9 changesets with 9 changes to 9 files (+2 heads)
286 (run 'hg heads' to see heads, 'hg merge' to merge)
287 $ hg tglog
288 o 8: 'I'
289 |
290 o 7: 'H'
291 |
292 o 6: 'G'
293 |
294 | o 5: 'F'
295 | |
296 | o 4: 'E'
297 |/
298 o 3: 'D'
299 |
300 o 2: 'C'
301 |
302 | o 1: 'B'
303 |/
304 o 0: 'A'
305
306 $ cd ..
307
308
309 Simple case with keep:
310
311 Source on have two descendant heads but ask for one
312
313 $ hg clone -q -u . ah ah1
314 $ cd ah1
315 $ hg rebase -r '2::8' -d 1
316 abort: can't remove original changesets with unrebased descendants
317 (use --keep to keep original changesets)
318 [255]
319 $ hg rebase -r '2::8' -d 1 --keep
320 $ hg tglog
321 @ 13: 'I'
322 |
323 o 12: 'H'
324 |
325 o 11: 'G'
326 |
327 o 10: 'D'
328 |
329 o 9: 'C'
330 |
331 | o 8: 'I'
332 | |
333 | o 7: 'H'
334 | |
335 | o 6: 'G'
336 | |
337 | | o 5: 'F'
338 | | |
339 | | o 4: 'E'
340 | |/
341 | o 3: 'D'
342 | |
343 | o 2: 'C'
344 | |
345 o | 1: 'B'
346 |/
347 o 0: 'A'
348
349
350 $ cd ..
351
352 Base on have one descendant heads we ask for but common ancestor have two
353
354 $ hg clone -q -u . ah ah2
355 $ cd ah2
356 $ hg rebase -r '3::8' -d 1
357 abort: can't remove original changesets with unrebased descendants
358 (use --keep to keep original changesets)
359 [255]
360 $ hg rebase -r '3::8' -d 1 --keep
361 $ hg tglog
362 @ 12: 'I'
363 |
364 o 11: 'H'
365 |
366 o 10: 'G'
367 |
368 o 9: 'D'
369 |\
370 | | o 8: 'I'
371 | | |
372 | | o 7: 'H'
373 | | |
374 | | o 6: 'G'
375 | | |
376 | | | o 5: 'F'
377 | | | |
378 | | | o 4: 'E'
379 | | |/
380 | | o 3: 'D'
381 | |/
382 | o 2: 'C'
383 | |
384 o | 1: 'B'
385 |/
386 o 0: 'A'
387
388
389 $ cd ..
390
391 rebase subset
392
393 $ hg clone -q -u . ah ah3
394 $ cd ah3
395 $ hg rebase -r '3::7' -d 1
396 abort: can't remove original changesets with unrebased descendants
397 (use --keep to keep original changesets)
398 [255]
399 $ hg rebase -r '3::7' -d 1 --keep
400 $ hg tglog
401 @ 11: 'H'
402 |
403 o 10: 'G'
404 |
405 o 9: 'D'
406 |\
407 | | o 8: 'I'
408 | | |
409 | | o 7: 'H'
410 | | |
411 | | o 6: 'G'
412 | | |
413 | | | o 5: 'F'
414 | | | |
415 | | | o 4: 'E'
416 | | |/
417 | | o 3: 'D'
418 | |/
419 | o 2: 'C'
420 | |
421 o | 1: 'B'
422 |/
423 o 0: 'A'
424
425
426 $ cd ..
427
428 rebase subset with multiple head
429
430 $ hg clone -q -u . ah ah4
431 $ cd ah4
432 $ hg rebase -r '3::(7+5)' -d 1
433 abort: can't remove original changesets with unrebased descendants
434 (use --keep to keep original changesets)
435 [255]
436 $ hg rebase -r '3::(7+5)' -d 1 --keep
437 $ hg tglog
438 @ 13: 'H'
439 |
440 o 12: 'G'
441 |
442 | o 11: 'F'
443 | |
444 | o 10: 'E'
445 |/
446 o 9: 'D'
447 |\
448 | | o 8: 'I'
449 | | |
450 | | o 7: 'H'
451 | | |
452 | | o 6: 'G'
453 | | |
454 | | | o 5: 'F'
455 | | | |
456 | | | o 4: 'E'
457 | | |/
458 | | o 3: 'D'
459 | |/
460 | o 2: 'C'
461 | |
462 o | 1: 'B'
463 |/
464 o 0: 'A'
465
466
467 $ cd ..
468
469 More advanced tests
470
471 rebase on ancestor with revset
472
473 $ hg clone -q -u . ah ah5
474 $ cd ah5
475 $ hg rebase -r '6::' -d 2
476 saved backup bundle to $TESTTMP/ah5/.hg/strip-backup/3d8a618087a7-backup.hg
477 $ hg tglog
478 @ 8: 'I'
479 |
480 o 7: 'H'
481 |
482 o 6: 'G'
483 |
484 | o 5: 'F'
485 | |
486 | o 4: 'E'
487 | |
488 | o 3: 'D'
489 |/
490 o 2: 'C'
491 |
492 | o 1: 'B'
493 |/
494 o 0: 'A'
495
496 $ cd ..
497
498
499 rebase with multiple root.
500 We rebase E and G on B
501 We would expect heads are I, F if it was supported
502
503 $ hg clone -q -u . ah ah6
504 $ cd ah6
505 $ hg rebase -r '(4+6)::' -d 1
506 abort: can't rebase multiple roots
507 [255]
508 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now