##// END OF EJS Templates
bookmarks: correctly update current bookmarks on rebase (issue2277)...
David Schleimer -
r17046:4116504d stable
parent child Browse files
Show More
@@ -1,675 +1,681 b''
1 # rebase.py - rebasing feature for mercurial
1 # rebase.py - rebasing feature for mercurial
2 #
2 #
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to move sets of revisions to a different ancestor
8 '''command to move sets of revisions to a different ancestor
9
9
10 This extension lets you rebase changesets in an existing Mercurial
10 This extension lets you rebase changesets in an existing Mercurial
11 repository.
11 repository.
12
12
13 For more information:
13 For more information:
14 http://mercurial.selenic.com/wiki/RebaseExtension
14 http://mercurial.selenic.com/wiki/RebaseExtension
15 '''
15 '''
16
16
17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
18 from mercurial import extensions, patch, scmutil, phases
18 from mercurial import extensions, patch, scmutil, phases
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 ('D', 'detach', False, _('force detaching of source from its original '
50 ('D', '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 wlock = repo.wlock()
118 wlock = repo.wlock()
119 lock = repo.lock()
119 lock = repo.lock()
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 or revf):
171 if not (srcf or revf):
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 rebaseset = repo.revs('%lr', revf)
188 rebaseset = repo.revs('%lr', revf)
189 elif srcf:
189 elif srcf:
190 src = scmutil.revrange(repo, [srcf])
190 src = scmutil.revrange(repo, [srcf])
191 rebaseset = repo.revs('(%ld)::', src)
191 rebaseset = repo.revs('(%ld)::', src)
192 else:
192 else:
193 base = scmutil.revrange(repo, [basef or '.'])
193 base = scmutil.revrange(repo, [basef or '.'])
194 rebaseset = repo.revs(
194 rebaseset = repo.revs(
195 '(children(ancestor(%ld, %d)) and ::(%ld))::',
195 '(children(ancestor(%ld, %d)) and ::(%ld))::',
196 base, dest, base)
196 base, dest, base)
197
197
198 if rebaseset:
198 if rebaseset:
199 root = min(rebaseset)
199 root = min(rebaseset)
200 else:
200 else:
201 root = None
201 root = None
202
202
203 if not rebaseset:
203 if not rebaseset:
204 repo.ui.debug('base is ancestor of destination')
204 repo.ui.debug('base is ancestor of destination')
205 result = None
205 result = None
206 elif not keepf and list(repo.revs('first(children(%ld) - %ld)',
206 elif not keepf and list(repo.revs('first(children(%ld) - %ld)',
207 rebaseset, rebaseset)):
207 rebaseset, rebaseset)):
208 raise util.Abort(
208 raise util.Abort(
209 _("can't remove original changesets with"
209 _("can't remove original changesets with"
210 " unrebased descendants"),
210 " unrebased descendants"),
211 hint=_('use --keep to keep original changesets'))
211 hint=_('use --keep to keep original changesets'))
212 elif not keepf and not repo[root].mutable():
212 elif not keepf and not repo[root].mutable():
213 raise util.Abort(_("can't rebase immutable changeset %s")
213 raise util.Abort(_("can't rebase immutable changeset %s")
214 % repo[root],
214 % repo[root],
215 hint=_('see hg help phases for details'))
215 hint=_('see hg help phases for details'))
216 else:
216 else:
217 result = buildstate(repo, dest, rebaseset, detachf)
217 result = buildstate(repo, dest, rebaseset, detachf)
218
218
219 if not result:
219 if not result:
220 # Empty state built, nothing to rebase
220 # Empty state built, nothing to rebase
221 ui.status(_('nothing to rebase\n'))
221 ui.status(_('nothing to rebase\n'))
222 return 1
222 return 1
223 else:
223 else:
224 originalwd, target, state = result
224 originalwd, target, state = result
225 if collapsef:
225 if collapsef:
226 targetancestors = set(repo.changelog.ancestors(target))
226 targetancestors = set(repo.changelog.ancestors(target))
227 targetancestors.add(target)
227 targetancestors.add(target)
228 external = checkexternal(repo, state, targetancestors)
228 external = checkexternal(repo, state, targetancestors)
229
229
230 if keepbranchesf:
230 if keepbranchesf:
231 assert not extrafn, 'cannot use both keepbranches and extrafn'
231 assert not extrafn, 'cannot use both keepbranches and extrafn'
232 def extrafn(ctx, extra):
232 def extrafn(ctx, extra):
233 extra['branch'] = ctx.branch()
233 extra['branch'] = ctx.branch()
234 if collapsef:
234 if collapsef:
235 branches = set()
235 branches = set()
236 for rev in state:
236 for rev in state:
237 branches.add(repo[rev].branch())
237 branches.add(repo[rev].branch())
238 if len(branches) > 1:
238 if len(branches) > 1:
239 raise util.Abort(_('cannot collapse multiple named '
239 raise util.Abort(_('cannot collapse multiple named '
240 'branches'))
240 'branches'))
241
241
242
242
243 # Rebase
243 # Rebase
244 if not targetancestors:
244 if not targetancestors:
245 targetancestors = set(repo.changelog.ancestors(target))
245 targetancestors = set(repo.changelog.ancestors(target))
246 targetancestors.add(target)
246 targetancestors.add(target)
247
247
248 # Keep track of the current bookmarks in order to reset them later
248 # Keep track of the current bookmarks in order to reset them later
249 currentbookmarks = repo._bookmarks.copy()
249 currentbookmarks = repo._bookmarks.copy()
250 activebookmark = repo._bookmarkcurrent
251 if activebookmark:
252 bookmarks.unsetcurrent(repo)
250
253
251 sortedstate = sorted(state)
254 sortedstate = sorted(state)
252 total = len(sortedstate)
255 total = len(sortedstate)
253 pos = 0
256 pos = 0
254 for rev in sortedstate:
257 for rev in sortedstate:
255 pos += 1
258 pos += 1
256 if state[rev] == -1:
259 if state[rev] == -1:
257 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
260 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
258 _('changesets'), total)
261 _('changesets'), total)
259 storestatus(repo, originalwd, target, state, collapsef, keepf,
262 storestatus(repo, originalwd, target, state, collapsef, keepf,
260 keepbranchesf, external)
263 keepbranchesf, external)
261 p1, p2 = defineparents(repo, rev, target, state,
264 p1, p2 = defineparents(repo, rev, target, state,
262 targetancestors)
265 targetancestors)
263 if len(repo.parents()) == 2:
266 if len(repo.parents()) == 2:
264 repo.ui.debug('resuming interrupted rebase\n')
267 repo.ui.debug('resuming interrupted rebase\n')
265 else:
268 else:
266 try:
269 try:
267 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
270 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
268 stats = rebasenode(repo, rev, p1, state)
271 stats = rebasenode(repo, rev, p1, state)
269 if stats and stats[3] > 0:
272 if stats and stats[3] > 0:
270 raise util.Abort(_('unresolved conflicts (see hg '
273 raise util.Abort(_('unresolved conflicts (see hg '
271 'resolve, then hg rebase --continue)'))
274 'resolve, then hg rebase --continue)'))
272 finally:
275 finally:
273 ui.setconfig('ui', 'forcemerge', '')
276 ui.setconfig('ui', 'forcemerge', '')
274 cmdutil.duplicatecopies(repo, rev, target)
277 cmdutil.duplicatecopies(repo, rev, target)
275 if not collapsef:
278 if not collapsef:
276 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
279 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
277 editor=editor)
280 editor=editor)
278 else:
281 else:
279 # Skip commit if we are collapsing
282 # Skip commit if we are collapsing
280 repo.setparents(repo[p1].node())
283 repo.setparents(repo[p1].node())
281 newrev = None
284 newrev = None
282 # Update the state
285 # Update the state
283 if newrev is not None:
286 if newrev is not None:
284 state[rev] = repo[newrev].rev()
287 state[rev] = repo[newrev].rev()
285 else:
288 else:
286 if not collapsef:
289 if not collapsef:
287 ui.note(_('no changes, revision %d skipped\n') % rev)
290 ui.note(_('no changes, revision %d skipped\n') % rev)
288 ui.debug('next revision set to %s\n' % p1)
291 ui.debug('next revision set to %s\n' % p1)
289 skipped.add(rev)
292 skipped.add(rev)
290 state[rev] = p1
293 state[rev] = p1
291
294
292 ui.progress(_('rebasing'), None)
295 ui.progress(_('rebasing'), None)
293 ui.note(_('rebase merging completed\n'))
296 ui.note(_('rebase merging completed\n'))
294
297
295 if collapsef and not keepopen:
298 if collapsef and not keepopen:
296 p1, p2 = defineparents(repo, min(state), target,
299 p1, p2 = defineparents(repo, min(state), target,
297 state, targetancestors)
300 state, targetancestors)
298 if collapsemsg:
301 if collapsemsg:
299 commitmsg = collapsemsg
302 commitmsg = collapsemsg
300 else:
303 else:
301 commitmsg = 'Collapsed revision'
304 commitmsg = 'Collapsed revision'
302 for rebased in state:
305 for rebased in state:
303 if rebased not in skipped and state[rebased] != nullmerge:
306 if rebased not in skipped and state[rebased] != nullmerge:
304 commitmsg += '\n* %s' % repo[rebased].description()
307 commitmsg += '\n* %s' % repo[rebased].description()
305 commitmsg = ui.edit(commitmsg, repo.ui.username())
308 commitmsg = ui.edit(commitmsg, repo.ui.username())
306 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
309 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
307 extrafn=extrafn, editor=editor)
310 extrafn=extrafn, editor=editor)
308
311
309 if 'qtip' in repo.tags():
312 if 'qtip' in repo.tags():
310 updatemq(repo, state, skipped, **opts)
313 updatemq(repo, state, skipped, **opts)
311
314
312 if currentbookmarks:
315 if currentbookmarks:
313 # Nodeids are needed to reset bookmarks
316 # Nodeids are needed to reset bookmarks
314 nstate = {}
317 nstate = {}
315 for k, v in state.iteritems():
318 for k, v in state.iteritems():
316 if v != nullmerge:
319 if v != nullmerge:
317 nstate[repo[k].node()] = repo[v].node()
320 nstate[repo[k].node()] = repo[v].node()
318
321
319 if not keepf:
322 if not keepf:
320 # Remove no more useful revisions
323 # Remove no more useful revisions
321 rebased = [rev for rev in state if state[rev] != nullmerge]
324 rebased = [rev for rev in state if state[rev] != nullmerge]
322 if rebased:
325 if rebased:
323 if set(repo.changelog.descendants(min(rebased))) - set(state):
326 if set(repo.changelog.descendants(min(rebased))) - set(state):
324 ui.warn(_("warning: new changesets detected "
327 ui.warn(_("warning: new changesets detected "
325 "on source branch, not stripping\n"))
328 "on source branch, not stripping\n"))
326 else:
329 else:
327 # backup the old csets by default
330 # backup the old csets by default
328 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
331 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
329
332
330 if currentbookmarks:
333 if currentbookmarks:
331 updatebookmarks(repo, nstate, currentbookmarks, **opts)
334 updatebookmarks(repo, nstate, currentbookmarks, **opts)
332
335
333 clearstatus(repo)
336 clearstatus(repo)
334 ui.note(_("rebase completed\n"))
337 ui.note(_("rebase completed\n"))
335 if os.path.exists(repo.sjoin('undo')):
338 if os.path.exists(repo.sjoin('undo')):
336 util.unlinkpath(repo.sjoin('undo'))
339 util.unlinkpath(repo.sjoin('undo'))
337 if skipped:
340 if skipped:
338 ui.note(_("%d revisions have been skipped\n") % len(skipped))
341 ui.note(_("%d revisions have been skipped\n") % len(skipped))
342
343 if (activebookmark and
344 repo['tip'].node() == repo._bookmarks[activebookmark]):
345 bookmarks.setcurrent(repo, activebookmark)
346
339 finally:
347 finally:
340 release(lock, wlock)
348 release(lock, wlock)
341
349
342 def checkexternal(repo, state, targetancestors):
350 def checkexternal(repo, state, targetancestors):
343 """Check whether one or more external revisions need to be taken in
351 """Check whether one or more external revisions need to be taken in
344 consideration. In the latter case, abort.
352 consideration. In the latter case, abort.
345 """
353 """
346 external = nullrev
354 external = nullrev
347 source = min(state)
355 source = min(state)
348 for rev in state:
356 for rev in state:
349 if rev == source:
357 if rev == source:
350 continue
358 continue
351 # Check externals and fail if there are more than one
359 # Check externals and fail if there are more than one
352 for p in repo[rev].parents():
360 for p in repo[rev].parents():
353 if (p.rev() not in state
361 if (p.rev() not in state
354 and p.rev() not in targetancestors):
362 and p.rev() not in targetancestors):
355 if external != nullrev:
363 if external != nullrev:
356 raise util.Abort(_('unable to collapse, there is more '
364 raise util.Abort(_('unable to collapse, there is more '
357 'than one external parent'))
365 'than one external parent'))
358 external = p.rev()
366 external = p.rev()
359 return external
367 return external
360
368
361 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
369 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
362 'Commit the changes and store useful information in extra'
370 'Commit the changes and store useful information in extra'
363 try:
371 try:
364 repo.setparents(repo[p1].node(), repo[p2].node())
372 repo.setparents(repo[p1].node(), repo[p2].node())
365 ctx = repo[rev]
373 ctx = repo[rev]
366 if commitmsg is None:
374 if commitmsg is None:
367 commitmsg = ctx.description()
375 commitmsg = ctx.description()
368 extra = {'rebase_source': ctx.hex()}
376 extra = {'rebase_source': ctx.hex()}
369 if extrafn:
377 if extrafn:
370 extrafn(ctx, extra)
378 extrafn(ctx, extra)
371 # Commit might fail if unresolved files exist
379 # Commit might fail if unresolved files exist
372 newrev = repo.commit(text=commitmsg, user=ctx.user(),
380 newrev = repo.commit(text=commitmsg, user=ctx.user(),
373 date=ctx.date(), extra=extra, editor=editor)
381 date=ctx.date(), extra=extra, editor=editor)
374 repo.dirstate.setbranch(repo[newrev].branch())
382 repo.dirstate.setbranch(repo[newrev].branch())
375 targetphase = max(ctx.phase(), phases.draft)
383 targetphase = max(ctx.phase(), phases.draft)
376 # retractboundary doesn't overwrite upper phase inherited from parent
384 # retractboundary doesn't overwrite upper phase inherited from parent
377 newnode = repo[newrev].node()
385 newnode = repo[newrev].node()
378 if newnode:
386 if newnode:
379 phases.retractboundary(repo, targetphase, [newnode])
387 phases.retractboundary(repo, targetphase, [newnode])
380 return newrev
388 return newrev
381 except util.Abort:
389 except util.Abort:
382 # Invalidate the previous setparents
390 # Invalidate the previous setparents
383 repo.dirstate.invalidate()
391 repo.dirstate.invalidate()
384 raise
392 raise
385
393
386 def rebasenode(repo, rev, p1, state):
394 def rebasenode(repo, rev, p1, state):
387 'Rebase a single revision'
395 'Rebase a single revision'
388 # Merge phase
396 # Merge phase
389 # Update to target and merge it with local
397 # Update to target and merge it with local
390 if repo['.'].rev() != repo[p1].rev():
398 if repo['.'].rev() != repo[p1].rev():
391 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
399 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
392 merge.update(repo, p1, False, True, False)
400 merge.update(repo, p1, False, True, False)
393 else:
401 else:
394 repo.ui.debug(" already in target\n")
402 repo.ui.debug(" already in target\n")
395 repo.dirstate.write()
403 repo.dirstate.write()
396 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
404 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
397 base = None
405 base = None
398 if repo[rev].rev() != repo[min(state)].rev():
406 if repo[rev].rev() != repo[min(state)].rev():
399 base = repo[rev].p1().node()
407 base = repo[rev].p1().node()
400 return merge.update(repo, rev, True, True, False, base)
408 return merge.update(repo, rev, True, True, False, base)
401
409
402 def defineparents(repo, rev, target, state, targetancestors):
410 def defineparents(repo, rev, target, state, targetancestors):
403 'Return the new parent relationship of the revision that will be rebased'
411 'Return the new parent relationship of the revision that will be rebased'
404 parents = repo[rev].parents()
412 parents = repo[rev].parents()
405 p1 = p2 = nullrev
413 p1 = p2 = nullrev
406
414
407 P1n = parents[0].rev()
415 P1n = parents[0].rev()
408 if P1n in targetancestors:
416 if P1n in targetancestors:
409 p1 = target
417 p1 = target
410 elif P1n in state:
418 elif P1n in state:
411 if state[P1n] == nullmerge:
419 if state[P1n] == nullmerge:
412 p1 = target
420 p1 = target
413 else:
421 else:
414 p1 = state[P1n]
422 p1 = state[P1n]
415 else: # P1n external
423 else: # P1n external
416 p1 = target
424 p1 = target
417 p2 = P1n
425 p2 = P1n
418
426
419 if len(parents) == 2 and parents[1].rev() not in targetancestors:
427 if len(parents) == 2 and parents[1].rev() not in targetancestors:
420 P2n = parents[1].rev()
428 P2n = parents[1].rev()
421 # interesting second parent
429 # interesting second parent
422 if P2n in state:
430 if P2n in state:
423 if p1 == target: # P1n in targetancestors or external
431 if p1 == target: # P1n in targetancestors or external
424 p1 = state[P2n]
432 p1 = state[P2n]
425 else:
433 else:
426 p2 = state[P2n]
434 p2 = state[P2n]
427 else: # P2n external
435 else: # P2n external
428 if p2 != nullrev: # P1n external too => rev is a merged revision
436 if p2 != nullrev: # P1n external too => rev is a merged revision
429 raise util.Abort(_('cannot use revision %d as base, result '
437 raise util.Abort(_('cannot use revision %d as base, result '
430 'would have 3 parents') % rev)
438 'would have 3 parents') % rev)
431 p2 = P2n
439 p2 = P2n
432 repo.ui.debug(" future parents are %d and %d\n" %
440 repo.ui.debug(" future parents are %d and %d\n" %
433 (repo[p1].rev(), repo[p2].rev()))
441 (repo[p1].rev(), repo[p2].rev()))
434 return p1, p2
442 return p1, p2
435
443
436 def isagitpatch(repo, patchname):
444 def isagitpatch(repo, patchname):
437 'Return true if the given patch is in git format'
445 'Return true if the given patch is in git format'
438 mqpatch = os.path.join(repo.mq.path, patchname)
446 mqpatch = os.path.join(repo.mq.path, patchname)
439 for line in patch.linereader(file(mqpatch, 'rb')):
447 for line in patch.linereader(file(mqpatch, 'rb')):
440 if line.startswith('diff --git'):
448 if line.startswith('diff --git'):
441 return True
449 return True
442 return False
450 return False
443
451
444 def updatemq(repo, state, skipped, **opts):
452 def updatemq(repo, state, skipped, **opts):
445 'Update rebased mq patches - finalize and then import them'
453 'Update rebased mq patches - finalize and then import them'
446 mqrebase = {}
454 mqrebase = {}
447 mq = repo.mq
455 mq = repo.mq
448 original_series = mq.fullseries[:]
456 original_series = mq.fullseries[:]
449 skippedpatches = set()
457 skippedpatches = set()
450
458
451 for p in mq.applied:
459 for p in mq.applied:
452 rev = repo[p.node].rev()
460 rev = repo[p.node].rev()
453 if rev in state:
461 if rev in state:
454 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
462 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
455 (rev, p.name))
463 (rev, p.name))
456 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
464 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
457 else:
465 else:
458 # Applied but not rebased, not sure this should happen
466 # Applied but not rebased, not sure this should happen
459 skippedpatches.add(p.name)
467 skippedpatches.add(p.name)
460
468
461 if mqrebase:
469 if mqrebase:
462 mq.finish(repo, mqrebase.keys())
470 mq.finish(repo, mqrebase.keys())
463
471
464 # We must start import from the newest revision
472 # We must start import from the newest revision
465 for rev in sorted(mqrebase, reverse=True):
473 for rev in sorted(mqrebase, reverse=True):
466 if rev not in skipped:
474 if rev not in skipped:
467 name, isgit = mqrebase[rev]
475 name, isgit = mqrebase[rev]
468 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
476 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
469 mq.qimport(repo, (), patchname=name, git=isgit,
477 mq.qimport(repo, (), patchname=name, git=isgit,
470 rev=[str(state[rev])])
478 rev=[str(state[rev])])
471 else:
479 else:
472 # Rebased and skipped
480 # Rebased and skipped
473 skippedpatches.add(mqrebase[rev][0])
481 skippedpatches.add(mqrebase[rev][0])
474
482
475 # Patches were either applied and rebased and imported in
483 # Patches were either applied and rebased and imported in
476 # order, applied and removed or unapplied. Discard the removed
484 # order, applied and removed or unapplied. Discard the removed
477 # ones while preserving the original series order and guards.
485 # ones while preserving the original series order and guards.
478 newseries = [s for s in original_series
486 newseries = [s for s in original_series
479 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
487 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
480 mq.fullseries[:] = newseries
488 mq.fullseries[:] = newseries
481 mq.seriesdirty = True
489 mq.seriesdirty = True
482 mq.savedirty()
490 mq.savedirty()
483
491
484 def updatebookmarks(repo, nstate, originalbookmarks, **opts):
492 def updatebookmarks(repo, nstate, originalbookmarks, **opts):
485 'Move bookmarks to their correct changesets'
493 'Move bookmarks to their correct changesets'
486 current = repo._bookmarkcurrent
487 for k, v in originalbookmarks.iteritems():
494 for k, v in originalbookmarks.iteritems():
488 if v in nstate:
495 if v in nstate:
489 if nstate[v] != nullmerge:
496 if nstate[v] != nullmerge:
490 # reset the pointer if the bookmark was moved incorrectly
497 # update the bookmarks for revs that have moved
491 if k != current:
492 repo._bookmarks[k] = nstate[v]
498 repo._bookmarks[k] = nstate[v]
493
499
494 bookmarks.write(repo)
500 bookmarks.write(repo)
495
501
496 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
502 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
497 external):
503 external):
498 'Store the current status to allow recovery'
504 'Store the current status to allow recovery'
499 f = repo.opener("rebasestate", "w")
505 f = repo.opener("rebasestate", "w")
500 f.write(repo[originalwd].hex() + '\n')
506 f.write(repo[originalwd].hex() + '\n')
501 f.write(repo[target].hex() + '\n')
507 f.write(repo[target].hex() + '\n')
502 f.write(repo[external].hex() + '\n')
508 f.write(repo[external].hex() + '\n')
503 f.write('%d\n' % int(collapse))
509 f.write('%d\n' % int(collapse))
504 f.write('%d\n' % int(keep))
510 f.write('%d\n' % int(keep))
505 f.write('%d\n' % int(keepbranches))
511 f.write('%d\n' % int(keepbranches))
506 for d, v in state.iteritems():
512 for d, v in state.iteritems():
507 oldrev = repo[d].hex()
513 oldrev = repo[d].hex()
508 if v != nullmerge:
514 if v != nullmerge:
509 newrev = repo[v].hex()
515 newrev = repo[v].hex()
510 else:
516 else:
511 newrev = v
517 newrev = v
512 f.write("%s:%s\n" % (oldrev, newrev))
518 f.write("%s:%s\n" % (oldrev, newrev))
513 f.close()
519 f.close()
514 repo.ui.debug('rebase status stored\n')
520 repo.ui.debug('rebase status stored\n')
515
521
516 def clearstatus(repo):
522 def clearstatus(repo):
517 'Remove the status files'
523 'Remove the status files'
518 if os.path.exists(repo.join("rebasestate")):
524 if os.path.exists(repo.join("rebasestate")):
519 util.unlinkpath(repo.join("rebasestate"))
525 util.unlinkpath(repo.join("rebasestate"))
520
526
521 def restorestatus(repo):
527 def restorestatus(repo):
522 'Restore a previously stored status'
528 'Restore a previously stored status'
523 try:
529 try:
524 target = None
530 target = None
525 collapse = False
531 collapse = False
526 external = nullrev
532 external = nullrev
527 state = {}
533 state = {}
528 f = repo.opener("rebasestate")
534 f = repo.opener("rebasestate")
529 for i, l in enumerate(f.read().splitlines()):
535 for i, l in enumerate(f.read().splitlines()):
530 if i == 0:
536 if i == 0:
531 originalwd = repo[l].rev()
537 originalwd = repo[l].rev()
532 elif i == 1:
538 elif i == 1:
533 target = repo[l].rev()
539 target = repo[l].rev()
534 elif i == 2:
540 elif i == 2:
535 external = repo[l].rev()
541 external = repo[l].rev()
536 elif i == 3:
542 elif i == 3:
537 collapse = bool(int(l))
543 collapse = bool(int(l))
538 elif i == 4:
544 elif i == 4:
539 keep = bool(int(l))
545 keep = bool(int(l))
540 elif i == 5:
546 elif i == 5:
541 keepbranches = bool(int(l))
547 keepbranches = bool(int(l))
542 else:
548 else:
543 oldrev, newrev = l.split(':')
549 oldrev, newrev = l.split(':')
544 if newrev != str(nullmerge):
550 if newrev != str(nullmerge):
545 state[repo[oldrev].rev()] = repo[newrev].rev()
551 state[repo[oldrev].rev()] = repo[newrev].rev()
546 else:
552 else:
547 state[repo[oldrev].rev()] = int(newrev)
553 state[repo[oldrev].rev()] = int(newrev)
548 skipped = set()
554 skipped = set()
549 # recompute the set of skipped revs
555 # recompute the set of skipped revs
550 if not collapse:
556 if not collapse:
551 seen = set([target])
557 seen = set([target])
552 for old, new in sorted(state.items()):
558 for old, new in sorted(state.items()):
553 if new != nullrev and new in seen:
559 if new != nullrev and new in seen:
554 skipped.add(old)
560 skipped.add(old)
555 seen.add(new)
561 seen.add(new)
556 repo.ui.debug('computed skipped revs: %s\n' % skipped)
562 repo.ui.debug('computed skipped revs: %s\n' % skipped)
557 repo.ui.debug('rebase status resumed\n')
563 repo.ui.debug('rebase status resumed\n')
558 return (originalwd, target, state, skipped,
564 return (originalwd, target, state, skipped,
559 collapse, keep, keepbranches, external)
565 collapse, keep, keepbranches, external)
560 except IOError, err:
566 except IOError, err:
561 if err.errno != errno.ENOENT:
567 if err.errno != errno.ENOENT:
562 raise
568 raise
563 raise util.Abort(_('no rebase in progress'))
569 raise util.Abort(_('no rebase in progress'))
564
570
565 def abort(repo, originalwd, target, state):
571 def abort(repo, originalwd, target, state):
566 'Restore the repository to its original state'
572 'Restore the repository to its original state'
567 dstates = [s for s in state.values() if s != nullrev]
573 dstates = [s for s in state.values() if s != nullrev]
568 immutable = [d for d in dstates if not repo[d].mutable()]
574 immutable = [d for d in dstates if not repo[d].mutable()]
569 if immutable:
575 if immutable:
570 raise util.Abort(_("can't abort rebase due to immutable changesets %s")
576 raise util.Abort(_("can't abort rebase due to immutable changesets %s")
571 % ', '.join(str(repo[r]) for r in immutable),
577 % ', '.join(str(repo[r]) for r in immutable),
572 hint=_('see hg help phases for details'))
578 hint=_('see hg help phases for details'))
573
579
574 descendants = set()
580 descendants = set()
575 if dstates:
581 if dstates:
576 descendants = set(repo.changelog.descendants(*dstates))
582 descendants = set(repo.changelog.descendants(*dstates))
577 if descendants - set(dstates):
583 if descendants - set(dstates):
578 repo.ui.warn(_("warning: new changesets detected on target branch, "
584 repo.ui.warn(_("warning: new changesets detected on target branch, "
579 "can't abort\n"))
585 "can't abort\n"))
580 return -1
586 return -1
581 else:
587 else:
582 # Strip from the first rebased revision
588 # Strip from the first rebased revision
583 merge.update(repo, repo[originalwd].rev(), False, True, False)
589 merge.update(repo, repo[originalwd].rev(), False, True, False)
584 rebased = filter(lambda x: x > -1 and x != target, state.values())
590 rebased = filter(lambda x: x > -1 and x != target, state.values())
585 if rebased:
591 if rebased:
586 strippoint = min(rebased)
592 strippoint = min(rebased)
587 # no backup of rebased cset versions needed
593 # no backup of rebased cset versions needed
588 repair.strip(repo.ui, repo, repo[strippoint].node())
594 repair.strip(repo.ui, repo, repo[strippoint].node())
589 clearstatus(repo)
595 clearstatus(repo)
590 repo.ui.warn(_('rebase aborted\n'))
596 repo.ui.warn(_('rebase aborted\n'))
591 return 0
597 return 0
592
598
593 def buildstate(repo, dest, rebaseset, detach):
599 def buildstate(repo, dest, rebaseset, detach):
594 '''Define which revisions are going to be rebased and where
600 '''Define which revisions are going to be rebased and where
595
601
596 repo: repo
602 repo: repo
597 dest: context
603 dest: context
598 rebaseset: set of rev
604 rebaseset: set of rev
599 detach: boolean'''
605 detach: boolean'''
600
606
601 # This check isn't strictly necessary, since mq detects commits over an
607 # This check isn't strictly necessary, since mq detects commits over an
602 # applied patch. But it prevents messing up the working directory when
608 # applied patch. But it prevents messing up the working directory when
603 # a partially completed rebase is blocked by mq.
609 # a partially completed rebase is blocked by mq.
604 if 'qtip' in repo.tags() and (dest.node() in
610 if 'qtip' in repo.tags() and (dest.node() in
605 [s.node for s in repo.mq.applied]):
611 [s.node for s in repo.mq.applied]):
606 raise util.Abort(_('cannot rebase onto an applied mq patch'))
612 raise util.Abort(_('cannot rebase onto an applied mq patch'))
607
613
608 detachset = set()
614 detachset = set()
609 roots = list(repo.set('roots(%ld)', rebaseset))
615 roots = list(repo.set('roots(%ld)', rebaseset))
610 if not roots:
616 if not roots:
611 raise util.Abort(_('no matching revisions'))
617 raise util.Abort(_('no matching revisions'))
612 if len(roots) > 1:
618 if len(roots) > 1:
613 raise util.Abort(_("can't rebase multiple roots"))
619 raise util.Abort(_("can't rebase multiple roots"))
614 root = roots[0]
620 root = roots[0]
615
621
616 commonbase = root.ancestor(dest)
622 commonbase = root.ancestor(dest)
617 if commonbase == root:
623 if commonbase == root:
618 raise util.Abort(_('source is ancestor of destination'))
624 raise util.Abort(_('source is ancestor of destination'))
619 if commonbase == dest:
625 if commonbase == dest:
620 samebranch = root.branch() == dest.branch()
626 samebranch = root.branch() == dest.branch()
621 if samebranch and root in dest.children():
627 if samebranch and root in dest.children():
622 repo.ui.debug('source is a child of destination')
628 repo.ui.debug('source is a child of destination')
623 return None
629 return None
624 # rebase on ancestor, force detach
630 # rebase on ancestor, force detach
625 detach = True
631 detach = True
626 if detach:
632 if detach:
627 detachset = repo.revs('::%d - ::%d - %d', root, commonbase, root)
633 detachset = repo.revs('::%d - ::%d - %d', root, commonbase, root)
628
634
629 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root))
635 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root))
630 state = dict.fromkeys(rebaseset, nullrev)
636 state = dict.fromkeys(rebaseset, nullrev)
631 state.update(dict.fromkeys(detachset, nullmerge))
637 state.update(dict.fromkeys(detachset, nullmerge))
632 return repo['.'].rev(), dest.rev(), state
638 return repo['.'].rev(), dest.rev(), state
633
639
634 def pullrebase(orig, ui, repo, *args, **opts):
640 def pullrebase(orig, ui, repo, *args, **opts):
635 'Call rebase after pull if the latter has been invoked with --rebase'
641 'Call rebase after pull if the latter has been invoked with --rebase'
636 if opts.get('rebase'):
642 if opts.get('rebase'):
637 if opts.get('update'):
643 if opts.get('update'):
638 del opts['update']
644 del opts['update']
639 ui.debug('--update and --rebase are not compatible, ignoring '
645 ui.debug('--update and --rebase are not compatible, ignoring '
640 'the update flag\n')
646 'the update flag\n')
641
647
642 movemarkfrom = repo['.'].node()
648 movemarkfrom = repo['.'].node()
643 cmdutil.bailifchanged(repo)
649 cmdutil.bailifchanged(repo)
644 revsprepull = len(repo)
650 revsprepull = len(repo)
645 origpostincoming = commands.postincoming
651 origpostincoming = commands.postincoming
646 def _dummy(*args, **kwargs):
652 def _dummy(*args, **kwargs):
647 pass
653 pass
648 commands.postincoming = _dummy
654 commands.postincoming = _dummy
649 try:
655 try:
650 orig(ui, repo, *args, **opts)
656 orig(ui, repo, *args, **opts)
651 finally:
657 finally:
652 commands.postincoming = origpostincoming
658 commands.postincoming = origpostincoming
653 revspostpull = len(repo)
659 revspostpull = len(repo)
654 if revspostpull > revsprepull:
660 if revspostpull > revsprepull:
655 rebase(ui, repo, **opts)
661 rebase(ui, repo, **opts)
656 branch = repo[None].branch()
662 branch = repo[None].branch()
657 dest = repo[branch].rev()
663 dest = repo[branch].rev()
658 if dest != repo['.'].rev():
664 if dest != repo['.'].rev():
659 # there was nothing to rebase we force an update
665 # there was nothing to rebase we force an update
660 hg.update(repo, dest)
666 hg.update(repo, dest)
661 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
667 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
662 ui.status(_("updating bookmark %s\n")
668 ui.status(_("updating bookmark %s\n")
663 % repo._bookmarkcurrent)
669 % repo._bookmarkcurrent)
664 else:
670 else:
665 if opts.get('tool'):
671 if opts.get('tool'):
666 raise util.Abort(_('--tool can only be used with --rebase'))
672 raise util.Abort(_('--tool can only be used with --rebase'))
667 orig(ui, repo, *args, **opts)
673 orig(ui, repo, *args, **opts)
668
674
669 def uisetup(ui):
675 def uisetup(ui):
670 'Replace pull with a decorator to provide --rebase option'
676 'Replace pull with a decorator to provide --rebase option'
671 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
677 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
672 entry[1].append(('', 'rebase', None,
678 entry[1].append(('', 'rebase', None,
673 _("rebase working directory to branch head")))
679 _("rebase working directory to branch head")))
674 entry[1].append(('t', 'tool', '',
680 entry[1].append(('t', 'tool', '',
675 _("specify merge tool for rebase")))
681 _("specify merge tool for rebase")))
@@ -1,88 +1,113 b''
1 $ cat >> $HGRCPATH <<EOF
1 $ cat >> $HGRCPATH <<EOF
2 > [extensions]
2 > [extensions]
3 > graphlog=
3 > graphlog=
4 > rebase=
4 > rebase=
5 >
5 >
6 > [phases]
6 > [phases]
7 > publish=False
7 > publish=False
8 >
8 >
9 > [alias]
9 > [alias]
10 > tglog = log -G --template "{rev}: '{desc}' bookmarks: {bookmarks}\n"
10 > tglog = log -G --template "{rev}: '{desc}' bookmarks: {bookmarks}\n"
11 > EOF
11 > EOF
12
12
13 Create a repo with several bookmarks
13 Create a repo with several bookmarks
14 $ hg init a
14 $ hg init a
15 $ cd a
15 $ cd a
16
16
17 $ echo a > a
17 $ echo a > a
18 $ hg ci -Am A
18 $ hg ci -Am A
19 adding a
19 adding a
20
20
21 $ echo b > b
21 $ echo b > b
22 $ hg ci -Am B
22 $ hg ci -Am B
23 adding b
23 adding b
24 $ hg book 'X'
24 $ hg book 'X'
25 $ hg book 'Y'
25 $ hg book 'Y'
26
26
27 $ echo c > c
27 $ echo c > c
28 $ hg ci -Am C
28 $ hg ci -Am C
29 adding c
29 adding c
30 $ hg book 'Z'
30 $ hg book 'Z'
31
31
32 $ hg up -q 0
32 $ hg up -q 0
33
33
34 $ echo d > d
34 $ echo d > d
35 $ hg ci -Am D
35 $ hg ci -Am D
36 adding d
36 adding d
37 created new head
37 created new head
38
38
39 $ hg book W
40
39 $ hg tglog
41 $ hg tglog
40 @ 3: 'D' bookmarks:
42 @ 3: 'D' bookmarks: W
41 |
43 |
42 | o 2: 'C' bookmarks: Y Z
44 | o 2: 'C' bookmarks: Y Z
43 | |
45 | |
44 | o 1: 'B' bookmarks: X
46 | o 1: 'B' bookmarks: X
45 |/
47 |/
46 o 0: 'A' bookmarks:
48 o 0: 'A' bookmarks:
47
49
48
50
49 Move only rebased bookmarks
51 Move only rebased bookmarks
50
52
51 $ cd ..
53 $ cd ..
52 $ hg clone -q a a1
54 $ hg clone -q a a1
53
55
54 $ cd a1
56 $ cd a1
55 $ hg up -q Z
57 $ hg up -q Z
56
58
57 $ hg rebase --detach -s Y -d 3
59 $ hg rebase --detach -s Y -d 3
58 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
60 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
59
61
60 $ hg tglog
62 $ hg tglog
61 @ 3: 'C' bookmarks: Y Z
63 @ 3: 'C' bookmarks: Y Z
62 |
64 |
63 o 2: 'D' bookmarks:
65 o 2: 'D' bookmarks: W
64 |
66 |
65 | o 1: 'B' bookmarks: X
67 | o 1: 'B' bookmarks: X
66 |/
68 |/
67 o 0: 'A' bookmarks:
69 o 0: 'A' bookmarks:
68
70
69 Keep bookmarks to the correct rebased changeset
71 Keep bookmarks to the correct rebased changeset
70
72
71 $ cd ..
73 $ cd ..
72 $ hg clone -q a a2
74 $ hg clone -q a a2
73
75
74 $ cd a2
76 $ cd a2
75 $ hg up -q Z
77 $ hg up -q Z
76
78
77 $ hg rebase -s 1 -d 3
79 $ hg rebase -s 1 -d 3
78 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
80 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
79
81
80 $ hg tglog
82 $ hg tglog
81 @ 3: 'C' bookmarks: Y Z
83 @ 3: 'C' bookmarks: Y Z
82 |
84 |
83 o 2: 'B' bookmarks: X
85 o 2: 'B' bookmarks: X
84 |
86 |
85 o 1: 'D' bookmarks:
87 o 1: 'D' bookmarks: W
86 |
88 |
87 o 0: 'A' bookmarks:
89 o 0: 'A' bookmarks:
88
90
91
92 Keep active bookmark on the correct changeset
93
94 $ cd ..
95 $ hg clone -q a a3
96
97 $ cd a3
98 $ hg up -q X
99
100 $ hg rebase -d W
101 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
102
103 $ hg tglog
104 @ 3: 'C' bookmarks: Y Z
105 |
106 o 2: 'B' bookmarks: X
107 |
108 o 1: 'D' bookmarks: W
109 |
110 o 0: 'A' bookmarks:
111
112
113 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now