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