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