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