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