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