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