##// END OF EJS Templates
merge with stable
Matt Mackall -
r25075:d1bd0fd0 merge default
parent child Browse files
Show More
@@ -1,1114 +1,1119 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 import copies, repoview
19 from mercurial import copies, repoview
20 from mercurial.commands import templateopts
20 from mercurial.commands import templateopts
21 from mercurial.node import nullrev, nullid, hex, short
21 from mercurial.node import nullrev, nullid, hex, short
22 from mercurial.lock import release
22 from mercurial.lock import release
23 from mercurial.i18n import _
23 from mercurial.i18n import _
24 import os, errno
24 import os, errno
25
25
26 revtodo = -1
26 revtodo = -1
27 nullmerge = -2
27 nullmerge = -2
28 revignored = -3
28 revignored = -3
29
29
30 cmdtable = {}
30 cmdtable = {}
31 command = cmdutil.command(cmdtable)
31 command = cmdutil.command(cmdtable)
32 testedwith = 'internal'
32 testedwith = 'internal'
33
33
34 def _savegraft(ctx, extra):
34 def _savegraft(ctx, extra):
35 s = ctx.extra().get('source', None)
35 s = ctx.extra().get('source', None)
36 if s is not None:
36 if s is not None:
37 extra['source'] = s
37 extra['source'] = s
38
38
39 def _savebranch(ctx, extra):
39 def _savebranch(ctx, extra):
40 extra['branch'] = ctx.branch()
40 extra['branch'] = ctx.branch()
41
41
42 def _makeextrafn(copiers):
42 def _makeextrafn(copiers):
43 """make an extrafn out of the given copy-functions.
43 """make an extrafn out of the given copy-functions.
44
44
45 A copy function takes a context and an extra dict, and mutates the
45 A copy function takes a context and an extra dict, and mutates the
46 extra dict as needed based on the given context.
46 extra dict as needed based on the given context.
47 """
47 """
48 def extrafn(ctx, extra):
48 def extrafn(ctx, extra):
49 for c in copiers:
49 for c in copiers:
50 c(ctx, extra)
50 c(ctx, extra)
51 return extrafn
51 return extrafn
52
52
53 @command('rebase',
53 @command('rebase',
54 [('s', 'source', '',
54 [('s', 'source', '',
55 _('rebase the specified changeset and descendants'), _('REV')),
55 _('rebase the specified changeset and descendants'), _('REV')),
56 ('b', 'base', '',
56 ('b', 'base', '',
57 _('rebase everything from branching point of specified changeset'),
57 _('rebase everything from branching point of specified changeset'),
58 _('REV')),
58 _('REV')),
59 ('r', 'rev', [],
59 ('r', 'rev', [],
60 _('rebase these revisions'),
60 _('rebase these revisions'),
61 _('REV')),
61 _('REV')),
62 ('d', 'dest', '',
62 ('d', 'dest', '',
63 _('rebase onto the specified changeset'), _('REV')),
63 _('rebase onto the specified changeset'), _('REV')),
64 ('', 'collapse', False, _('collapse the rebased changesets')),
64 ('', 'collapse', False, _('collapse the rebased changesets')),
65 ('m', 'message', '',
65 ('m', 'message', '',
66 _('use text as collapse commit message'), _('TEXT')),
66 _('use text as collapse commit message'), _('TEXT')),
67 ('e', 'edit', False, _('invoke editor on commit messages')),
67 ('e', 'edit', False, _('invoke editor on commit messages')),
68 ('l', 'logfile', '',
68 ('l', 'logfile', '',
69 _('read collapse commit message from file'), _('FILE')),
69 _('read collapse commit message from file'), _('FILE')),
70 ('k', 'keep', False, _('keep original changesets')),
70 ('k', 'keep', False, _('keep original changesets')),
71 ('', 'keepbranches', False, _('keep original branch names')),
71 ('', 'keepbranches', False, _('keep original branch names')),
72 ('D', 'detach', False, _('(DEPRECATED)')),
72 ('D', 'detach', False, _('(DEPRECATED)')),
73 ('i', 'interactive', False, _('(DEPRECATED)')),
73 ('i', 'interactive', False, _('(DEPRECATED)')),
74 ('t', 'tool', '', _('specify merge tool')),
74 ('t', 'tool', '', _('specify merge tool')),
75 ('c', 'continue', False, _('continue an interrupted rebase')),
75 ('c', 'continue', False, _('continue an interrupted rebase')),
76 ('a', 'abort', False, _('abort an interrupted rebase'))] +
76 ('a', 'abort', False, _('abort an interrupted rebase'))] +
77 templateopts,
77 templateopts,
78 _('[-s REV | -b REV] [-d REV] [OPTION]'))
78 _('[-s REV | -b REV] [-d REV] [OPTION]'))
79 def rebase(ui, repo, **opts):
79 def rebase(ui, repo, **opts):
80 """move changeset (and descendants) to a different branch
80 """move changeset (and descendants) to a different branch
81
81
82 Rebase uses repeated merging to graft changesets from one part of
82 Rebase uses repeated merging to graft changesets from one part of
83 history (the source) onto another (the destination). This can be
83 history (the source) onto another (the destination). This can be
84 useful for linearizing *local* changes relative to a master
84 useful for linearizing *local* changes relative to a master
85 development tree.
85 development tree.
86
86
87 You should not rebase changesets that have already been shared
87 You should not rebase changesets that have already been shared
88 with others. Doing so will force everybody else to perform the
88 with others. Doing so will force everybody else to perform the
89 same rebase or they will end up with duplicated changesets after
89 same rebase or they will end up with duplicated changesets after
90 pulling in your rebased changesets.
90 pulling in your rebased changesets.
91
91
92 In its default configuration, Mercurial will prevent you from
92 In its default configuration, Mercurial will prevent you from
93 rebasing published changes. See :hg:`help phases` for details.
93 rebasing published changes. See :hg:`help phases` for details.
94
94
95 If you don't specify a destination changeset (``-d/--dest``),
95 If you don't specify a destination changeset (``-d/--dest``),
96 rebase uses the current branch tip as the destination. (The
96 rebase uses the current branch tip as the destination. (The
97 destination changeset is not modified by rebasing, but new
97 destination changeset is not modified by rebasing, but new
98 changesets are added as its descendants.)
98 changesets are added as its descendants.)
99
99
100 You can specify which changesets to rebase in two ways: as a
100 You can specify which changesets to rebase in two ways: as a
101 "source" changeset or as a "base" changeset. Both are shorthand
101 "source" changeset or as a "base" changeset. Both are shorthand
102 for a topologically related set of changesets (the "source
102 for a topologically related set of changesets (the "source
103 branch"). If you specify source (``-s/--source``), rebase will
103 branch"). If you specify source (``-s/--source``), rebase will
104 rebase that changeset and all of its descendants onto dest. If you
104 rebase that changeset and all of its descendants onto dest. If you
105 specify base (``-b/--base``), rebase will select ancestors of base
105 specify base (``-b/--base``), rebase will select ancestors of base
106 back to but not including the common ancestor with dest. Thus,
106 back to but not including the common ancestor with dest. Thus,
107 ``-b`` is less precise but more convenient than ``-s``: you can
107 ``-b`` is less precise but more convenient than ``-s``: you can
108 specify any changeset in the source branch, and rebase will select
108 specify any changeset in the source branch, and rebase will select
109 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
109 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
110 uses the parent of the working directory as the base.
110 uses the parent of the working directory as the base.
111
111
112 For advanced usage, a third way is available through the ``--rev``
112 For advanced usage, a third way is available through the ``--rev``
113 option. It allows you to specify an arbitrary set of changesets to
113 option. It allows you to specify an arbitrary set of changesets to
114 rebase. Descendants of revs you specify with this option are not
114 rebase. Descendants of revs you specify with this option are not
115 automatically included in the rebase.
115 automatically included in the rebase.
116
116
117 By default, rebase recreates the changesets in the source branch
117 By default, rebase recreates the changesets in the source branch
118 as descendants of dest and then destroys the originals. Use
118 as descendants of dest and then destroys the originals. Use
119 ``--keep`` to preserve the original source changesets. Some
119 ``--keep`` to preserve the original source changesets. Some
120 changesets in the source branch (e.g. merges from the destination
120 changesets in the source branch (e.g. merges from the destination
121 branch) may be dropped if they no longer contribute any change.
121 branch) may be dropped if they no longer contribute any change.
122
122
123 One result of the rules for selecting the destination changeset
123 One result of the rules for selecting the destination changeset
124 and source branch is that, unlike ``merge``, rebase will do
124 and source branch is that, unlike ``merge``, rebase will do
125 nothing if you are at the branch tip of a named branch
125 nothing if you are at the branch tip of a named branch
126 with two heads. You need to explicitly specify source and/or
126 with two heads. You need to explicitly specify source and/or
127 destination (or ``update`` to the other head, if it's the head of
127 destination (or ``update`` to the other head, if it's the head of
128 the intended source branch).
128 the intended source branch).
129
129
130 If a rebase is interrupted to manually resolve a merge, it can be
130 If a rebase is interrupted to manually resolve a merge, it can be
131 continued with --continue/-c or aborted with --abort/-a.
131 continued with --continue/-c or aborted with --abort/-a.
132
132
133 .. container:: verbose
133 .. container:: verbose
134
134
135 Examples:
135 Examples:
136
136
137 - move "local changes" (current commit back to branching point)
137 - move "local changes" (current commit back to branching point)
138 to the current branch tip after a pull::
138 to the current branch tip after a pull::
139
139
140 hg rebase
140 hg rebase
141
141
142 - move a single changeset to the stable branch::
142 - move a single changeset to the stable branch::
143
143
144 hg rebase -r 5f493448 -d stable
144 hg rebase -r 5f493448 -d stable
145
145
146 - splice a commit and all its descendants onto another part of history::
146 - splice a commit and all its descendants onto another part of history::
147
147
148 hg rebase --source c0c3 --dest 4cf9
148 hg rebase --source c0c3 --dest 4cf9
149
149
150 - rebase everything on a branch marked by a bookmark onto the
150 - rebase everything on a branch marked by a bookmark onto the
151 default branch::
151 default branch::
152
152
153 hg rebase --base myfeature --dest default
153 hg rebase --base myfeature --dest default
154
154
155 - collapse a sequence of changes into a single commit::
155 - collapse a sequence of changes into a single commit::
156
156
157 hg rebase --collapse -r 1520:1525 -d .
157 hg rebase --collapse -r 1520:1525 -d .
158
158
159 - move a named branch while preserving its name::
159 - move a named branch while preserving its name::
160
160
161 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
161 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
162
162
163 Returns 0 on success, 1 if nothing to rebase or there are
163 Returns 0 on success, 1 if nothing to rebase or there are
164 unresolved conflicts.
164 unresolved conflicts.
165
165
166 """
166 """
167 originalwd = target = None
167 originalwd = target = None
168 activebookmark = None
168 activebookmark = None
169 external = nullrev
169 external = nullrev
170 state = {}
170 state = {}
171 skipped = set()
171 skipped = set()
172 targetancestors = set()
172 targetancestors = set()
173
173
174
174
175 lock = wlock = None
175 lock = wlock = None
176 try:
176 try:
177 wlock = repo.wlock()
177 wlock = repo.wlock()
178 lock = repo.lock()
178 lock = repo.lock()
179
179
180 # Validate input and define rebasing points
180 # Validate input and define rebasing points
181 destf = opts.get('dest', None)
181 destf = opts.get('dest', None)
182 srcf = opts.get('source', None)
182 srcf = opts.get('source', None)
183 basef = opts.get('base', None)
183 basef = opts.get('base', None)
184 revf = opts.get('rev', [])
184 revf = opts.get('rev', [])
185 contf = opts.get('continue')
185 contf = opts.get('continue')
186 abortf = opts.get('abort')
186 abortf = opts.get('abort')
187 collapsef = opts.get('collapse', False)
187 collapsef = opts.get('collapse', False)
188 collapsemsg = cmdutil.logmessage(ui, opts)
188 collapsemsg = cmdutil.logmessage(ui, opts)
189 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
189 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
190 extrafns = [_savegraft]
190 extrafns = [_savegraft]
191 if e:
191 if e:
192 extrafns = [e]
192 extrafns = [e]
193 keepf = opts.get('keep', False)
193 keepf = opts.get('keep', False)
194 keepbranchesf = opts.get('keepbranches', False)
194 keepbranchesf = opts.get('keepbranches', False)
195 # keepopen is not meant for use on the command line, but by
195 # keepopen is not meant for use on the command line, but by
196 # other extensions
196 # other extensions
197 keepopen = opts.get('keepopen', False)
197 keepopen = opts.get('keepopen', False)
198
198
199 if opts.get('interactive'):
199 if opts.get('interactive'):
200 msg = _("interactive history editing is supported by the "
200 msg = _("interactive history editing is supported by the "
201 "'histedit' extension (see \"hg help histedit\")")
201 "'histedit' extension (see \"hg help histedit\")")
202 raise util.Abort(msg)
202 raise util.Abort(msg)
203
203
204 if collapsemsg and not collapsef:
204 if collapsemsg and not collapsef:
205 raise util.Abort(
205 raise util.Abort(
206 _('message can only be specified with collapse'))
206 _('message can only be specified with collapse'))
207
207
208 if contf or abortf:
208 if contf or abortf:
209 if contf and abortf:
209 if contf and abortf:
210 raise util.Abort(_('cannot use both abort and continue'))
210 raise util.Abort(_('cannot use both abort and continue'))
211 if collapsef:
211 if collapsef:
212 raise util.Abort(
212 raise util.Abort(
213 _('cannot use collapse with continue or abort'))
213 _('cannot use collapse with continue or abort'))
214 if srcf or basef or destf:
214 if srcf or basef or destf:
215 raise util.Abort(
215 raise util.Abort(
216 _('abort and continue do not allow specifying revisions'))
216 _('abort and continue do not allow specifying revisions'))
217 if opts.get('tool', False):
217 if opts.get('tool', False):
218 ui.warn(_('tool option will be ignored\n'))
218 ui.warn(_('tool option will be ignored\n'))
219
219
220 try:
220 try:
221 (originalwd, target, state, skipped, collapsef, keepf,
221 (originalwd, target, state, skipped, collapsef, keepf,
222 keepbranchesf, external, activebookmark) = restorestatus(repo)
222 keepbranchesf, external, activebookmark) = restorestatus(repo)
223 except error.RepoLookupError:
223 except error.RepoLookupError:
224 if abortf:
224 if abortf:
225 clearstatus(repo)
225 clearstatus(repo)
226 repo.ui.warn(_('rebase aborted (no revision is removed,'
226 repo.ui.warn(_('rebase aborted (no revision is removed,'
227 ' only broken state is cleared)\n'))
227 ' only broken state is cleared)\n'))
228 return 0
228 return 0
229 else:
229 else:
230 msg = _('cannot continue inconsistent rebase')
230 msg = _('cannot continue inconsistent rebase')
231 hint = _('use "hg rebase --abort" to clear broken state')
231 hint = _('use "hg rebase --abort" to clear broken state')
232 raise util.Abort(msg, hint=hint)
232 raise util.Abort(msg, hint=hint)
233 if abortf:
233 if abortf:
234 return abort(repo, originalwd, target, state,
234 return abort(repo, originalwd, target, state,
235 activebookmark=activebookmark)
235 activebookmark=activebookmark)
236 else:
236 else:
237 if srcf and basef:
237 if srcf and basef:
238 raise util.Abort(_('cannot specify both a '
238 raise util.Abort(_('cannot specify both a '
239 'source and a base'))
239 'source and a base'))
240 if revf and basef:
240 if revf and basef:
241 raise util.Abort(_('cannot specify both a '
241 raise util.Abort(_('cannot specify both a '
242 'revision and a base'))
242 'revision and a base'))
243 if revf and srcf:
243 if revf and srcf:
244 raise util.Abort(_('cannot specify both a '
244 raise util.Abort(_('cannot specify both a '
245 'revision and a source'))
245 'revision and a source'))
246
246
247 cmdutil.checkunfinished(repo)
247 cmdutil.checkunfinished(repo)
248 cmdutil.bailifchanged(repo)
248 cmdutil.bailifchanged(repo)
249
249
250 if not destf:
250 if not destf:
251 # Destination defaults to the latest revision in the
251 # Destination defaults to the latest revision in the
252 # current branch
252 # current branch
253 branch = repo[None].branch()
253 branch = repo[None].branch()
254 dest = repo[branch]
254 dest = repo[branch]
255 else:
255 else:
256 dest = scmutil.revsingle(repo, destf)
256 dest = scmutil.revsingle(repo, destf)
257
257
258 if revf:
258 if revf:
259 rebaseset = scmutil.revrange(repo, revf)
259 rebaseset = scmutil.revrange(repo, revf)
260 if not rebaseset:
260 if not rebaseset:
261 ui.status(_('empty "rev" revision set - '
261 ui.status(_('empty "rev" revision set - '
262 'nothing to rebase\n'))
262 'nothing to rebase\n'))
263 return 1
263 return 1
264 elif srcf:
264 elif srcf:
265 src = scmutil.revrange(repo, [srcf])
265 src = scmutil.revrange(repo, [srcf])
266 if not src:
266 if not src:
267 ui.status(_('empty "source" revision set - '
267 ui.status(_('empty "source" revision set - '
268 'nothing to rebase\n'))
268 'nothing to rebase\n'))
269 return 1
269 return 1
270 rebaseset = repo.revs('(%ld)::', src)
270 rebaseset = repo.revs('(%ld)::', src)
271 assert rebaseset
271 assert rebaseset
272 else:
272 else:
273 base = scmutil.revrange(repo, [basef or '.'])
273 base = scmutil.revrange(repo, [basef or '.'])
274 if not base:
274 if not base:
275 ui.status(_('empty "base" revision set - '
275 ui.status(_('empty "base" revision set - '
276 "can't compute rebase set\n"))
276 "can't compute rebase set\n"))
277 return 1
277 return 1
278 commonanc = repo.revs('ancestor(%ld, %d)', base, dest).first()
278 commonanc = repo.revs('ancestor(%ld, %d)', base, dest).first()
279 if commonanc is not None:
279 if commonanc is not None:
280 rebaseset = repo.revs('(%d::(%ld) - %d)::',
280 rebaseset = repo.revs('(%d::(%ld) - %d)::',
281 commonanc, base, commonanc)
281 commonanc, base, commonanc)
282 else:
282 else:
283 rebaseset = []
283 rebaseset = []
284
284
285 if not rebaseset:
285 if not rebaseset:
286 # transform to list because smartsets are not comparable to
286 # transform to list because smartsets are not comparable to
287 # lists. This should be improved to honor laziness of
287 # lists. This should be improved to honor laziness of
288 # smartset.
288 # smartset.
289 if list(base) == [dest.rev()]:
289 if list(base) == [dest.rev()]:
290 if basef:
290 if basef:
291 ui.status(_('nothing to rebase - %s is both "base"'
291 ui.status(_('nothing to rebase - %s is both "base"'
292 ' and destination\n') % dest)
292 ' and destination\n') % dest)
293 else:
293 else:
294 ui.status(_('nothing to rebase - working directory '
294 ui.status(_('nothing to rebase - working directory '
295 'parent is also destination\n'))
295 'parent is also destination\n'))
296 elif not repo.revs('%ld - ::%d', base, dest):
296 elif not repo.revs('%ld - ::%d', base, dest):
297 if basef:
297 if basef:
298 ui.status(_('nothing to rebase - "base" %s is '
298 ui.status(_('nothing to rebase - "base" %s is '
299 'already an ancestor of destination '
299 'already an ancestor of destination '
300 '%s\n') %
300 '%s\n') %
301 ('+'.join(str(repo[r]) for r in base),
301 ('+'.join(str(repo[r]) for r in base),
302 dest))
302 dest))
303 else:
303 else:
304 ui.status(_('nothing to rebase - working '
304 ui.status(_('nothing to rebase - working '
305 'directory parent is already an '
305 'directory parent is already an '
306 'ancestor of destination %s\n') % dest)
306 'ancestor of destination %s\n') % dest)
307 else: # can it happen?
307 else: # can it happen?
308 ui.status(_('nothing to rebase from %s to %s\n') %
308 ui.status(_('nothing to rebase from %s to %s\n') %
309 ('+'.join(str(repo[r]) for r in base), dest))
309 ('+'.join(str(repo[r]) for r in base), dest))
310 return 1
310 return 1
311
311
312 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
312 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
313 if (not (keepf or allowunstable)
313 if (not (keepf or allowunstable)
314 and repo.revs('first(children(%ld) - %ld)',
314 and repo.revs('first(children(%ld) - %ld)',
315 rebaseset, rebaseset)):
315 rebaseset, rebaseset)):
316 raise util.Abort(
316 raise util.Abort(
317 _("can't remove original changesets with"
317 _("can't remove original changesets with"
318 " unrebased descendants"),
318 " unrebased descendants"),
319 hint=_('use --keep to keep original changesets'))
319 hint=_('use --keep to keep original changesets'))
320
320
321 result = buildstate(repo, dest, rebaseset, collapsef)
321 result = buildstate(repo, dest, rebaseset, collapsef)
322 if not result:
322 if not result:
323 # Empty state built, nothing to rebase
323 # Empty state built, nothing to rebase
324 ui.status(_('nothing to rebase\n'))
324 ui.status(_('nothing to rebase\n'))
325 return 1
325 return 1
326
326
327 root = min(rebaseset)
327 root = min(rebaseset)
328 if not keepf and not repo[root].mutable():
328 if not keepf and not repo[root].mutable():
329 raise util.Abort(_("can't rebase immutable changeset %s")
329 raise util.Abort(_("can't rebase immutable changeset %s")
330 % repo[root],
330 % repo[root],
331 hint=_('see "hg help phases" for details'))
331 hint=_('see "hg help phases" for details'))
332
332
333 originalwd, target, state = result
333 originalwd, target, state = result
334 if collapsef:
334 if collapsef:
335 targetancestors = repo.changelog.ancestors([target],
335 targetancestors = repo.changelog.ancestors([target],
336 inclusive=True)
336 inclusive=True)
337 external = externalparent(repo, state, targetancestors)
337 external = externalparent(repo, state, targetancestors)
338
338
339 if dest.closesbranch() and not keepbranchesf:
339 if dest.closesbranch() and not keepbranchesf:
340 ui.status(_('reopening closed branch head %s\n') % dest)
340 ui.status(_('reopening closed branch head %s\n') % dest)
341
341
342 if keepbranchesf:
342 if keepbranchesf:
343 # insert _savebranch at the start of extrafns so if
343 # insert _savebranch at the start of extrafns so if
344 # there's a user-provided extrafn it can clobber branch if
344 # there's a user-provided extrafn it can clobber branch if
345 # desired
345 # desired
346 extrafns.insert(0, _savebranch)
346 extrafns.insert(0, _savebranch)
347 if collapsef:
347 if collapsef:
348 branches = set()
348 branches = set()
349 for rev in state:
349 for rev in state:
350 branches.add(repo[rev].branch())
350 branches.add(repo[rev].branch())
351 if len(branches) > 1:
351 if len(branches) > 1:
352 raise util.Abort(_('cannot collapse multiple named '
352 raise util.Abort(_('cannot collapse multiple named '
353 'branches'))
353 'branches'))
354
354
355 # Rebase
355 # Rebase
356 if not targetancestors:
356 if not targetancestors:
357 targetancestors = repo.changelog.ancestors([target], inclusive=True)
357 targetancestors = repo.changelog.ancestors([target], inclusive=True)
358
358
359 # Keep track of the current bookmarks in order to reset them later
359 # Keep track of the current bookmarks in order to reset them later
360 currentbookmarks = repo._bookmarks.copy()
360 currentbookmarks = repo._bookmarks.copy()
361 activebookmark = activebookmark or repo._activebookmark
361 activebookmark = activebookmark or repo._activebookmark
362 if activebookmark:
362 if activebookmark:
363 bookmarks.deactivate(repo)
363 bookmarks.deactivate(repo)
364
364
365 extrafn = _makeextrafn(extrafns)
365 extrafn = _makeextrafn(extrafns)
366
366
367 sortedstate = sorted(state)
367 sortedstate = sorted(state)
368 total = len(sortedstate)
368 total = len(sortedstate)
369 pos = 0
369 pos = 0
370 for rev in sortedstate:
370 for rev in sortedstate:
371 ctx = repo[rev]
371 ctx = repo[rev]
372 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
372 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
373 ctx.description().split('\n', 1)[0])
373 ctx.description().split('\n', 1)[0])
374 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
374 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
375 if names:
375 if names:
376 desc += ' (%s)' % ' '.join(names)
376 desc += ' (%s)' % ' '.join(names)
377 pos += 1
377 pos += 1
378 if state[rev] == revtodo:
378 if state[rev] == revtodo:
379 ui.status(_('rebasing %s\n') % desc)
379 ui.status(_('rebasing %s\n') % desc)
380 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
380 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
381 _('changesets'), total)
381 _('changesets'), total)
382 p1, p2, base = defineparents(repo, rev, target, state,
382 p1, p2, base = defineparents(repo, rev, target, state,
383 targetancestors)
383 targetancestors)
384 storestatus(repo, originalwd, target, state, collapsef, keepf,
384 storestatus(repo, originalwd, target, state, collapsef, keepf,
385 keepbranchesf, external, activebookmark)
385 keepbranchesf, external, activebookmark)
386 if len(repo.parents()) == 2:
386 if len(repo.parents()) == 2:
387 repo.ui.debug('resuming interrupted rebase\n')
387 repo.ui.debug('resuming interrupted rebase\n')
388 else:
388 else:
389 try:
389 try:
390 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
390 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
391 'rebase')
391 'rebase')
392 stats = rebasenode(repo, rev, p1, base, state,
392 stats = rebasenode(repo, rev, p1, base, state,
393 collapsef, target)
393 collapsef, target)
394 if stats and stats[3] > 0:
394 if stats and stats[3] > 0:
395 raise error.InterventionRequired(
395 raise error.InterventionRequired(
396 _('unresolved conflicts (see hg '
396 _('unresolved conflicts (see hg '
397 'resolve, then hg rebase --continue)'))
397 'resolve, then hg rebase --continue)'))
398 finally:
398 finally:
399 ui.setconfig('ui', 'forcemerge', '', 'rebase')
399 ui.setconfig('ui', 'forcemerge', '', 'rebase')
400 if not collapsef:
400 if not collapsef:
401 merging = p2 != nullrev
401 merging = p2 != nullrev
402 editform = cmdutil.mergeeditform(merging, 'rebase')
402 editform = cmdutil.mergeeditform(merging, 'rebase')
403 editor = cmdutil.getcommiteditor(editform=editform, **opts)
403 editor = cmdutil.getcommiteditor(editform=editform, **opts)
404 newnode = concludenode(repo, rev, p1, p2, extrafn=extrafn,
404 newnode = concludenode(repo, rev, p1, p2, extrafn=extrafn,
405 editor=editor)
405 editor=editor)
406 else:
406 else:
407 # Skip commit if we are collapsing
407 # Skip commit if we are collapsing
408 repo.dirstate.beginparentchange()
408 repo.dirstate.beginparentchange()
409 repo.setparents(repo[p1].node())
409 repo.setparents(repo[p1].node())
410 repo.dirstate.endparentchange()
410 repo.dirstate.endparentchange()
411 newnode = None
411 newnode = None
412 # Update the state
412 # Update the state
413 if newnode is not None:
413 if newnode is not None:
414 state[rev] = repo[newnode].rev()
414 state[rev] = repo[newnode].rev()
415 ui.debug('rebased as %s\n' % short(newnode))
415 ui.debug('rebased as %s\n' % short(newnode))
416 else:
416 else:
417 ui.warn(_('note: rebase of %d:%s created no changes '
417 ui.warn(_('note: rebase of %d:%s created no changes '
418 'to commit\n') % (rev, ctx))
418 'to commit\n') % (rev, ctx))
419 if not collapsef:
419 if not collapsef:
420 skipped.add(rev)
420 skipped.add(rev)
421 state[rev] = p1
421 state[rev] = p1
422 ui.debug('next revision set to %s\n' % p1)
422 ui.debug('next revision set to %s\n' % p1)
423 elif state[rev] == nullmerge:
423 elif state[rev] == nullmerge:
424 ui.debug('ignoring null merge rebase of %s\n' % rev)
424 ui.debug('ignoring null merge rebase of %s\n' % rev)
425 elif state[rev] == revignored:
425 elif state[rev] == revignored:
426 ui.status(_('not rebasing ignored %s\n') % desc)
426 ui.status(_('not rebasing ignored %s\n') % desc)
427 else:
427 else:
428 ui.status(_('already rebased %s as %s\n') %
428 ui.status(_('already rebased %s as %s\n') %
429 (desc, repo[state[rev]]))
429 (desc, repo[state[rev]]))
430
430
431 ui.progress(_('rebasing'), None)
431 ui.progress(_('rebasing'), None)
432 ui.note(_('rebase merging completed\n'))
432 ui.note(_('rebase merging completed\n'))
433
433
434 if collapsef and not keepopen:
434 if collapsef and not keepopen:
435 p1, p2, _base = defineparents(repo, min(state), target,
435 p1, p2, _base = defineparents(repo, min(state), target,
436 state, targetancestors)
436 state, targetancestors)
437 editopt = opts.get('edit')
437 editopt = opts.get('edit')
438 editform = 'rebase.collapse'
438 editform = 'rebase.collapse'
439 if collapsemsg:
439 if collapsemsg:
440 commitmsg = collapsemsg
440 commitmsg = collapsemsg
441 else:
441 else:
442 commitmsg = 'Collapsed revision'
442 commitmsg = 'Collapsed revision'
443 for rebased in state:
443 for rebased in state:
444 if rebased not in skipped and state[rebased] > nullmerge:
444 if rebased not in skipped and state[rebased] > nullmerge:
445 commitmsg += '\n* %s' % repo[rebased].description()
445 commitmsg += '\n* %s' % repo[rebased].description()
446 editopt = True
446 editopt = True
447 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
447 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
448 newnode = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
448 newnode = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
449 extrafn=extrafn, editor=editor)
449 extrafn=extrafn, editor=editor)
450 if newnode is None:
450 if newnode is None:
451 newrev = target
451 newrev = target
452 else:
452 else:
453 newrev = repo[newnode].rev()
453 newrev = repo[newnode].rev()
454 for oldrev in state.iterkeys():
454 for oldrev in state.iterkeys():
455 if state[oldrev] > nullmerge:
455 if state[oldrev] > nullmerge:
456 state[oldrev] = newrev
456 state[oldrev] = newrev
457
457
458 if 'qtip' in repo.tags():
458 if 'qtip' in repo.tags():
459 updatemq(repo, state, skipped, **opts)
459 updatemq(repo, state, skipped, **opts)
460
460
461 if currentbookmarks:
461 if currentbookmarks:
462 # Nodeids are needed to reset bookmarks
462 # Nodeids are needed to reset bookmarks
463 nstate = {}
463 nstate = {}
464 for k, v in state.iteritems():
464 for k, v in state.iteritems():
465 if v > nullmerge:
465 if v > nullmerge:
466 nstate[repo[k].node()] = repo[v].node()
466 nstate[repo[k].node()] = repo[v].node()
467 # XXX this is the same as dest.node() for the non-continue path --
467 # XXX this is the same as dest.node() for the non-continue path --
468 # this should probably be cleaned up
468 # this should probably be cleaned up
469 targetnode = repo[target].node()
469 targetnode = repo[target].node()
470
470
471 # restore original working directory
471 # restore original working directory
472 # (we do this before stripping)
472 # (we do this before stripping)
473 newwd = state.get(originalwd, originalwd)
473 newwd = state.get(originalwd, originalwd)
474 if newwd < 0:
474 if newwd < 0:
475 # original directory is a parent of rebase set root or ignored
475 # original directory is a parent of rebase set root or ignored
476 newwd = originalwd
476 newwd = originalwd
477 if newwd not in [c.rev() for c in repo[None].parents()]:
477 if newwd not in [c.rev() for c in repo[None].parents()]:
478 ui.note(_("update back to initial working directory parent\n"))
478 ui.note(_("update back to initial working directory parent\n"))
479 hg.updaterepo(repo, newwd, False)
479 hg.updaterepo(repo, newwd, False)
480
480
481 if not keepf:
481 if not keepf:
482 collapsedas = None
482 collapsedas = None
483 if collapsef:
483 if collapsef:
484 collapsedas = newnode
484 collapsedas = newnode
485 clearrebased(ui, repo, state, skipped, collapsedas)
485 clearrebased(ui, repo, state, skipped, collapsedas)
486
486
487 if currentbookmarks:
487 if currentbookmarks:
488 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
488 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
489 if activebookmark not in repo._bookmarks:
489 if activebookmark not in repo._bookmarks:
490 # active bookmark was divergent one and has been deleted
490 # active bookmark was divergent one and has been deleted
491 activebookmark = None
491 activebookmark = None
492
492
493 clearstatus(repo)
493 clearstatus(repo)
494 ui.note(_("rebase completed\n"))
494 ui.note(_("rebase completed\n"))
495 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
495 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
496 if skipped:
496 if skipped:
497 ui.note(_("%d revisions have been skipped\n") % len(skipped))
497 ui.note(_("%d revisions have been skipped\n") % len(skipped))
498
498
499 if (activebookmark and
499 if (activebookmark and
500 repo['.'].node() == repo._bookmarks[activebookmark]):
500 repo['.'].node() == repo._bookmarks[activebookmark]):
501 bookmarks.activate(repo, activebookmark)
501 bookmarks.activate(repo, activebookmark)
502
502
503 finally:
503 finally:
504 release(lock, wlock)
504 release(lock, wlock)
505
505
506 def externalparent(repo, state, targetancestors):
506 def externalparent(repo, state, targetancestors):
507 """Return the revision that should be used as the second parent
507 """Return the revision that should be used as the second parent
508 when the revisions in state is collapsed on top of targetancestors.
508 when the revisions in state is collapsed on top of targetancestors.
509 Abort if there is more than one parent.
509 Abort if there is more than one parent.
510 """
510 """
511 parents = set()
511 parents = set()
512 source = min(state)
512 source = min(state)
513 for rev in state:
513 for rev in state:
514 if rev == source:
514 if rev == source:
515 continue
515 continue
516 for p in repo[rev].parents():
516 for p in repo[rev].parents():
517 if (p.rev() not in state
517 if (p.rev() not in state
518 and p.rev() not in targetancestors):
518 and p.rev() not in targetancestors):
519 parents.add(p.rev())
519 parents.add(p.rev())
520 if not parents:
520 if not parents:
521 return nullrev
521 return nullrev
522 if len(parents) == 1:
522 if len(parents) == 1:
523 return parents.pop()
523 return parents.pop()
524 raise util.Abort(_('unable to collapse on top of %s, there is more '
524 raise util.Abort(_('unable to collapse on top of %s, there is more '
525 'than one external parent: %s') %
525 'than one external parent: %s') %
526 (max(targetancestors),
526 (max(targetancestors),
527 ', '.join(str(p) for p in sorted(parents))))
527 ', '.join(str(p) for p in sorted(parents))))
528
528
529 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
529 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
530 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
530 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
531 but also store useful information in extra.
531 but also store useful information in extra.
532 Return node of committed revision.'''
532 Return node of committed revision.'''
533 dsguard = cmdutil.dirstateguard(repo, 'rebase')
533 dsguard = cmdutil.dirstateguard(repo, 'rebase')
534 try:
534 try:
535 repo.setparents(repo[p1].node(), repo[p2].node())
535 repo.setparents(repo[p1].node(), repo[p2].node())
536 ctx = repo[rev]
536 ctx = repo[rev]
537 if commitmsg is None:
537 if commitmsg is None:
538 commitmsg = ctx.description()
538 commitmsg = ctx.description()
539 extra = {'rebase_source': ctx.hex()}
539 extra = {'rebase_source': ctx.hex()}
540 if extrafn:
540 if extrafn:
541 extrafn(ctx, extra)
541 extrafn(ctx, extra)
542
542
543 backup = repo.ui.backupconfig('phases', 'new-commit')
543 backup = repo.ui.backupconfig('phases', 'new-commit')
544 try:
544 try:
545 targetphase = max(ctx.phase(), phases.draft)
545 targetphase = max(ctx.phase(), phases.draft)
546 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
546 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
547 # Commit might fail if unresolved files exist
547 # Commit might fail if unresolved files exist
548 newnode = repo.commit(text=commitmsg, user=ctx.user(),
548 newnode = repo.commit(text=commitmsg, user=ctx.user(),
549 date=ctx.date(), extra=extra, editor=editor)
549 date=ctx.date(), extra=extra, editor=editor)
550 finally:
550 finally:
551 repo.ui.restoreconfig(backup)
551 repo.ui.restoreconfig(backup)
552
552
553 repo.dirstate.setbranch(repo[newnode].branch())
553 repo.dirstate.setbranch(repo[newnode].branch())
554 dsguard.close()
554 dsguard.close()
555 return newnode
555 return newnode
556 finally:
556 finally:
557 release(dsguard)
557 release(dsguard)
558
558
559 def rebasenode(repo, rev, p1, base, state, collapse, target):
559 def rebasenode(repo, rev, p1, base, state, collapse, target):
560 'Rebase a single revision rev on top of p1 using base as merge ancestor'
560 'Rebase a single revision rev on top of p1 using base as merge ancestor'
561 # Merge phase
561 # Merge phase
562 # Update to target and merge it with local
562 # Update to target and merge it with local
563 if repo['.'].rev() != p1:
563 if repo['.'].rev() != p1:
564 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
564 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
565 merge.update(repo, p1, False, True, False)
565 merge.update(repo, p1, False, True, False)
566 else:
566 else:
567 repo.ui.debug(" already in target\n")
567 repo.ui.debug(" already in target\n")
568 repo.dirstate.write()
568 repo.dirstate.write()
569 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
569 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
570 if base is not None:
570 if base is not None:
571 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
571 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
572 # When collapsing in-place, the parent is the common ancestor, we
572 # When collapsing in-place, the parent is the common ancestor, we
573 # have to allow merging with it.
573 # have to allow merging with it.
574 stats = merge.update(repo, rev, True, True, False, base, collapse,
574 stats = merge.update(repo, rev, True, True, False, base, collapse,
575 labels=['dest', 'source'])
575 labels=['dest', 'source'])
576 if collapse:
576 if collapse:
577 copies.duplicatecopies(repo, rev, target)
577 copies.duplicatecopies(repo, rev, target)
578 else:
578 else:
579 # If we're not using --collapse, we need to
579 # If we're not using --collapse, we need to
580 # duplicate copies between the revision we're
580 # duplicate copies between the revision we're
581 # rebasing and its first parent, but *not*
581 # rebasing and its first parent, but *not*
582 # duplicate any copies that have already been
582 # duplicate any copies that have already been
583 # performed in the destination.
583 # performed in the destination.
584 p1rev = repo[rev].p1().rev()
584 p1rev = repo[rev].p1().rev()
585 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
585 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
586 return stats
586 return stats
587
587
588 def nearestrebased(repo, rev, state):
588 def nearestrebased(repo, rev, state):
589 """return the nearest ancestors of rev in the rebase result"""
589 """return the nearest ancestors of rev in the rebase result"""
590 rebased = [r for r in state if state[r] > nullmerge]
590 rebased = [r for r in state if state[r] > nullmerge]
591 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
591 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
592 if candidates:
592 if candidates:
593 return state[candidates.first()]
593 return state[candidates.first()]
594 else:
594 else:
595 return None
595 return None
596
596
597 def defineparents(repo, rev, target, state, targetancestors):
597 def defineparents(repo, rev, target, state, targetancestors):
598 'Return the new parent relationship of the revision that will be rebased'
598 'Return the new parent relationship of the revision that will be rebased'
599 parents = repo[rev].parents()
599 parents = repo[rev].parents()
600 p1 = p2 = nullrev
600 p1 = p2 = nullrev
601
601
602 p1n = parents[0].rev()
602 p1n = parents[0].rev()
603 if p1n in targetancestors:
603 if p1n in targetancestors:
604 p1 = target
604 p1 = target
605 elif p1n in state:
605 elif p1n in state:
606 if state[p1n] == nullmerge:
606 if state[p1n] == nullmerge:
607 p1 = target
607 p1 = target
608 elif state[p1n] == revignored:
608 elif state[p1n] == revignored:
609 p1 = nearestrebased(repo, p1n, state)
609 p1 = nearestrebased(repo, p1n, state)
610 if p1 is None:
610 if p1 is None:
611 p1 = target
611 p1 = target
612 else:
612 else:
613 p1 = state[p1n]
613 p1 = state[p1n]
614 else: # p1n external
614 else: # p1n external
615 p1 = target
615 p1 = target
616 p2 = p1n
616 p2 = p1n
617
617
618 if len(parents) == 2 and parents[1].rev() not in targetancestors:
618 if len(parents) == 2 and parents[1].rev() not in targetancestors:
619 p2n = parents[1].rev()
619 p2n = parents[1].rev()
620 # interesting second parent
620 # interesting second parent
621 if p2n in state:
621 if p2n in state:
622 if p1 == target: # p1n in targetancestors or external
622 if p1 == target: # p1n in targetancestors or external
623 p1 = state[p2n]
623 p1 = state[p2n]
624 elif state[p2n] == revignored:
624 elif state[p2n] == revignored:
625 p2 = nearestrebased(repo, p2n, state)
625 p2 = nearestrebased(repo, p2n, state)
626 if p2 is None:
626 if p2 is None:
627 # no ancestors rebased yet, detach
627 # no ancestors rebased yet, detach
628 p2 = target
628 p2 = target
629 else:
629 else:
630 p2 = state[p2n]
630 p2 = state[p2n]
631 else: # p2n external
631 else: # p2n external
632 if p2 != nullrev: # p1n external too => rev is a merged revision
632 if p2 != nullrev: # p1n external too => rev is a merged revision
633 raise util.Abort(_('cannot use revision %d as base, result '
633 raise util.Abort(_('cannot use revision %d as base, result '
634 'would have 3 parents') % rev)
634 'would have 3 parents') % rev)
635 p2 = p2n
635 p2 = p2n
636 repo.ui.debug(" future parents are %d and %d\n" %
636 repo.ui.debug(" future parents are %d and %d\n" %
637 (repo[p1].rev(), repo[p2].rev()))
637 (repo[p1].rev(), repo[p2].rev()))
638
638
639 if rev == min(state):
639 if rev == min(state):
640 # Case (1) initial changeset of a non-detaching rebase.
640 # Case (1) initial changeset of a non-detaching rebase.
641 # Let the merge mechanism find the base itself.
641 # Let the merge mechanism find the base itself.
642 base = None
642 base = None
643 elif not repo[rev].p2():
643 elif not repo[rev].p2():
644 # Case (2) detaching the node with a single parent, use this parent
644 # Case (2) detaching the node with a single parent, use this parent
645 base = repo[rev].p1().rev()
645 base = repo[rev].p1().rev()
646 else:
646 else:
647 # Assuming there is a p1, this is the case where there also is a p2.
647 # Assuming there is a p1, this is the case where there also is a p2.
648 # We are thus rebasing a merge and need to pick the right merge base.
648 # We are thus rebasing a merge and need to pick the right merge base.
649 #
649 #
650 # Imagine we have:
650 # Imagine we have:
651 # - M: current rebase revision in this step
651 # - M: current rebase revision in this step
652 # - A: one parent of M
652 # - A: one parent of M
653 # - B: other parent of M
653 # - B: other parent of M
654 # - D: destination of this merge step (p1 var)
654 # - D: destination of this merge step (p1 var)
655 #
655 #
656 # Consider the case where D is a descendant of A or B and the other is
656 # Consider the case where D is a descendant of A or B and the other is
657 # 'outside'. In this case, the right merge base is the D ancestor.
657 # 'outside'. In this case, the right merge base is the D ancestor.
658 #
658 #
659 # An informal proof, assuming A is 'outside' and B is the D ancestor:
659 # An informal proof, assuming A is 'outside' and B is the D ancestor:
660 #
660 #
661 # If we pick B as the base, the merge involves:
661 # If we pick B as the base, the merge involves:
662 # - changes from B to M (actual changeset payload)
662 # - changes from B to M (actual changeset payload)
663 # - changes from B to D (induced by rebase) as D is a rebased
663 # - changes from B to D (induced by rebase) as D is a rebased
664 # version of B)
664 # version of B)
665 # Which exactly represent the rebase operation.
665 # Which exactly represent the rebase operation.
666 #
666 #
667 # If we pick A as the base, the merge involves:
667 # If we pick A as the base, the merge involves:
668 # - changes from A to M (actual changeset payload)
668 # - changes from A to M (actual changeset payload)
669 # - changes from A to D (with include changes between unrelated A and B
669 # - changes from A to D (with include changes between unrelated A and B
670 # plus changes induced by rebase)
670 # plus changes induced by rebase)
671 # Which does not represent anything sensible and creates a lot of
671 # Which does not represent anything sensible and creates a lot of
672 # conflicts. A is thus not the right choice - B is.
672 # conflicts. A is thus not the right choice - B is.
673 #
673 #
674 # Note: The base found in this 'proof' is only correct in the specified
674 # Note: The base found in this 'proof' is only correct in the specified
675 # case. This base does not make sense if is not D a descendant of A or B
675 # case. This base does not make sense if is not D a descendant of A or B
676 # or if the other is not parent 'outside' (especially not if the other
676 # or if the other is not parent 'outside' (especially not if the other
677 # parent has been rebased). The current implementation does not
677 # parent has been rebased). The current implementation does not
678 # make it feasible to consider different cases separately. In these
678 # make it feasible to consider different cases separately. In these
679 # other cases we currently just leave it to the user to correctly
679 # other cases we currently just leave it to the user to correctly
680 # resolve an impossible merge using a wrong ancestor.
680 # resolve an impossible merge using a wrong ancestor.
681 for p in repo[rev].parents():
681 for p in repo[rev].parents():
682 if state.get(p.rev()) == p1:
682 if state.get(p.rev()) == p1:
683 base = p.rev()
683 base = p.rev()
684 break
684 break
685 else: # fallback when base not found
685 else: # fallback when base not found
686 base = None
686 base = None
687
687
688 # Raise because this function is called wrong (see issue 4106)
688 # Raise because this function is called wrong (see issue 4106)
689 raise AssertionError('no base found to rebase on '
689 raise AssertionError('no base found to rebase on '
690 '(defineparents called wrong)')
690 '(defineparents called wrong)')
691 return p1, p2, base
691 return p1, p2, base
692
692
693 def isagitpatch(repo, patchname):
693 def isagitpatch(repo, patchname):
694 'Return true if the given patch is in git format'
694 'Return true if the given patch is in git format'
695 mqpatch = os.path.join(repo.mq.path, patchname)
695 mqpatch = os.path.join(repo.mq.path, patchname)
696 for line in patch.linereader(file(mqpatch, 'rb')):
696 for line in patch.linereader(file(mqpatch, 'rb')):
697 if line.startswith('diff --git'):
697 if line.startswith('diff --git'):
698 return True
698 return True
699 return False
699 return False
700
700
701 def updatemq(repo, state, skipped, **opts):
701 def updatemq(repo, state, skipped, **opts):
702 'Update rebased mq patches - finalize and then import them'
702 'Update rebased mq patches - finalize and then import them'
703 mqrebase = {}
703 mqrebase = {}
704 mq = repo.mq
704 mq = repo.mq
705 original_series = mq.fullseries[:]
705 original_series = mq.fullseries[:]
706 skippedpatches = set()
706 skippedpatches = set()
707
707
708 for p in mq.applied:
708 for p in mq.applied:
709 rev = repo[p.node].rev()
709 rev = repo[p.node].rev()
710 if rev in state:
710 if rev in state:
711 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
711 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
712 (rev, p.name))
712 (rev, p.name))
713 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
713 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
714 else:
714 else:
715 # Applied but not rebased, not sure this should happen
715 # Applied but not rebased, not sure this should happen
716 skippedpatches.add(p.name)
716 skippedpatches.add(p.name)
717
717
718 if mqrebase:
718 if mqrebase:
719 mq.finish(repo, mqrebase.keys())
719 mq.finish(repo, mqrebase.keys())
720
720
721 # We must start import from the newest revision
721 # We must start import from the newest revision
722 for rev in sorted(mqrebase, reverse=True):
722 for rev in sorted(mqrebase, reverse=True):
723 if rev not in skipped:
723 if rev not in skipped:
724 name, isgit = mqrebase[rev]
724 name, isgit = mqrebase[rev]
725 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
725 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
726 (name, state[rev], repo[state[rev]]))
726 (name, state[rev], repo[state[rev]]))
727 mq.qimport(repo, (), patchname=name, git=isgit,
727 mq.qimport(repo, (), patchname=name, git=isgit,
728 rev=[str(state[rev])])
728 rev=[str(state[rev])])
729 else:
729 else:
730 # Rebased and skipped
730 # Rebased and skipped
731 skippedpatches.add(mqrebase[rev][0])
731 skippedpatches.add(mqrebase[rev][0])
732
732
733 # Patches were either applied and rebased and imported in
733 # Patches were either applied and rebased and imported in
734 # order, applied and removed or unapplied. Discard the removed
734 # order, applied and removed or unapplied. Discard the removed
735 # ones while preserving the original series order and guards.
735 # ones while preserving the original series order and guards.
736 newseries = [s for s in original_series
736 newseries = [s for s in original_series
737 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
737 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
738 mq.fullseries[:] = newseries
738 mq.fullseries[:] = newseries
739 mq.seriesdirty = True
739 mq.seriesdirty = True
740 mq.savedirty()
740 mq.savedirty()
741
741
742 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
742 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
743 'Move bookmarks to their correct changesets, and delete divergent ones'
743 'Move bookmarks to their correct changesets, and delete divergent ones'
744 marks = repo._bookmarks
744 marks = repo._bookmarks
745 for k, v in originalbookmarks.iteritems():
745 for k, v in originalbookmarks.iteritems():
746 if v in nstate:
746 if v in nstate:
747 # update the bookmarks for revs that have moved
747 # update the bookmarks for revs that have moved
748 marks[k] = nstate[v]
748 marks[k] = nstate[v]
749 bookmarks.deletedivergent(repo, [targetnode], k)
749 bookmarks.deletedivergent(repo, [targetnode], k)
750
750
751 marks.write()
751 marks.write()
752
752
753 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
753 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
754 external, activebookmark):
754 external, activebookmark):
755 'Store the current status to allow recovery'
755 'Store the current status to allow recovery'
756 f = repo.vfs("rebasestate", "w")
756 f = repo.vfs("rebasestate", "w")
757 f.write(repo[originalwd].hex() + '\n')
757 f.write(repo[originalwd].hex() + '\n')
758 f.write(repo[target].hex() + '\n')
758 f.write(repo[target].hex() + '\n')
759 f.write(repo[external].hex() + '\n')
759 f.write(repo[external].hex() + '\n')
760 f.write('%d\n' % int(collapse))
760 f.write('%d\n' % int(collapse))
761 f.write('%d\n' % int(keep))
761 f.write('%d\n' % int(keep))
762 f.write('%d\n' % int(keepbranches))
762 f.write('%d\n' % int(keepbranches))
763 f.write('%s\n' % (activebookmark or ''))
763 f.write('%s\n' % (activebookmark or ''))
764 for d, v in state.iteritems():
764 for d, v in state.iteritems():
765 oldrev = repo[d].hex()
765 oldrev = repo[d].hex()
766 if v >= 0:
766 if v >= 0:
767 newrev = repo[v].hex()
767 newrev = repo[v].hex()
768 elif v == revtodo:
768 elif v == revtodo:
769 # To maintain format compatibility, we have to use nullid.
769 # To maintain format compatibility, we have to use nullid.
770 # Please do remove this special case when upgrading the format.
770 # Please do remove this special case when upgrading the format.
771 newrev = hex(nullid)
771 newrev = hex(nullid)
772 else:
772 else:
773 newrev = v
773 newrev = v
774 f.write("%s:%s\n" % (oldrev, newrev))
774 f.write("%s:%s\n" % (oldrev, newrev))
775 f.close()
775 f.close()
776 repo.ui.debug('rebase status stored\n')
776 repo.ui.debug('rebase status stored\n')
777
777
778 def clearstatus(repo):
778 def clearstatus(repo):
779 'Remove the status files'
779 'Remove the status files'
780 _clearrebasesetvisibiliy(repo)
780 _clearrebasesetvisibiliy(repo)
781 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
781 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
782
782
783 def restorestatus(repo):
783 def restorestatus(repo):
784 'Restore a previously stored status'
784 'Restore a previously stored status'
785 try:
785 try:
786 keepbranches = None
786 keepbranches = None
787 target = None
787 target = None
788 collapse = False
788 collapse = False
789 external = nullrev
789 external = nullrev
790 activebookmark = None
790 activebookmark = None
791 state = {}
791 state = {}
792 f = repo.vfs("rebasestate")
792 f = repo.vfs("rebasestate")
793 for i, l in enumerate(f.read().splitlines()):
793 for i, l in enumerate(f.read().splitlines()):
794 if i == 0:
794 if i == 0:
795 originalwd = repo[l].rev()
795 originalwd = repo[l].rev()
796 elif i == 1:
796 elif i == 1:
797 target = repo[l].rev()
797 target = repo[l].rev()
798 elif i == 2:
798 elif i == 2:
799 external = repo[l].rev()
799 external = repo[l].rev()
800 elif i == 3:
800 elif i == 3:
801 collapse = bool(int(l))
801 collapse = bool(int(l))
802 elif i == 4:
802 elif i == 4:
803 keep = bool(int(l))
803 keep = bool(int(l))
804 elif i == 5:
804 elif i == 5:
805 keepbranches = bool(int(l))
805 keepbranches = bool(int(l))
806 elif i == 6 and not (len(l) == 81 and ':' in l):
806 elif i == 6 and not (len(l) == 81 and ':' in l):
807 # line 6 is a recent addition, so for backwards compatibility
807 # line 6 is a recent addition, so for backwards compatibility
808 # check that the line doesn't look like the oldrev:newrev lines
808 # check that the line doesn't look like the oldrev:newrev lines
809 activebookmark = l
809 activebookmark = l
810 else:
810 else:
811 oldrev, newrev = l.split(':')
811 oldrev, newrev = l.split(':')
812 if newrev in (str(nullmerge), str(revignored)):
812 if newrev in (str(nullmerge), str(revignored)):
813 state[repo[oldrev].rev()] = int(newrev)
813 state[repo[oldrev].rev()] = int(newrev)
814 elif newrev == nullid:
814 elif newrev == nullid:
815 state[repo[oldrev].rev()] = revtodo
815 state[repo[oldrev].rev()] = revtodo
816 # Legacy compat special case
816 # Legacy compat special case
817 else:
817 else:
818 state[repo[oldrev].rev()] = repo[newrev].rev()
818 state[repo[oldrev].rev()] = repo[newrev].rev()
819
819
820 if keepbranches is None:
820 if keepbranches is None:
821 raise util.Abort(_('.hg/rebasestate is incomplete'))
821 raise util.Abort(_('.hg/rebasestate is incomplete'))
822
822
823 skipped = set()
823 skipped = set()
824 # recompute the set of skipped revs
824 # recompute the set of skipped revs
825 if not collapse:
825 if not collapse:
826 seen = set([target])
826 seen = set([target])
827 for old, new in sorted(state.items()):
827 for old, new in sorted(state.items()):
828 if new != revtodo and new in seen:
828 if new != revtodo and new in seen:
829 skipped.add(old)
829 skipped.add(old)
830 seen.add(new)
830 seen.add(new)
831 repo.ui.debug('computed skipped revs: %s\n' %
831 repo.ui.debug('computed skipped revs: %s\n' %
832 (' '.join(str(r) for r in sorted(skipped)) or None))
832 (' '.join(str(r) for r in sorted(skipped)) or None))
833 repo.ui.debug('rebase status resumed\n')
833 repo.ui.debug('rebase status resumed\n')
834 _setrebasesetvisibility(repo, state.keys())
834 _setrebasesetvisibility(repo, state.keys())
835 return (originalwd, target, state, skipped,
835 return (originalwd, target, state, skipped,
836 collapse, keep, keepbranches, external, activebookmark)
836 collapse, keep, keepbranches, external, activebookmark)
837 except IOError, err:
837 except IOError, err:
838 if err.errno != errno.ENOENT:
838 if err.errno != errno.ENOENT:
839 raise
839 raise
840 raise util.Abort(_('no rebase in progress'))
840 raise util.Abort(_('no rebase in progress'))
841
841
842 def inrebase(repo, originalwd, state):
842 def needupdate(repo, state):
843 '''check whether the working dir is in an interrupted rebase'''
843 '''check whether we should `update --clean` away from a merge, or if
844 somehow the working dir got forcibly updated, e.g. by older hg'''
844 parents = [p.rev() for p in repo.parents()]
845 parents = [p.rev() for p in repo.parents()]
845 if originalwd in parents:
846
846 return True
847 # Are we in a merge state at all?
848 if len(parents) < 2:
849 return False
847
850
848 for newrev in state.itervalues():
851 # We should be standing on the first as-of-yet unrebased commit.
849 if newrev in parents:
852 firstunrebased = min([old for old, new in state.iteritems()
853 if new == nullrev])
854 if firstunrebased in parents:
850 return True
855 return True
851
856
852 return False
857 return False
853
858
854 def abort(repo, originalwd, target, state, activebookmark=None):
859 def abort(repo, originalwd, target, state, activebookmark=None):
855 '''Restore the repository to its original state. Additional args:
860 '''Restore the repository to its original state. Additional args:
856
861
857 activebookmark: the name of the bookmark that should be active after the
862 activebookmark: the name of the bookmark that should be active after the
858 restore'''
863 restore'''
859 dstates = [s for s in state.values() if s >= 0]
864 dstates = [s for s in state.values() if s >= 0]
860 immutable = [d for d in dstates if not repo[d].mutable()]
865 immutable = [d for d in dstates if not repo[d].mutable()]
861 cleanup = True
866 cleanup = True
862 if immutable:
867 if immutable:
863 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
868 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
864 % ', '.join(str(repo[r]) for r in immutable),
869 % ', '.join(str(repo[r]) for r in immutable),
865 hint=_('see "hg help phases" for details'))
870 hint=_('see "hg help phases" for details'))
866 cleanup = False
871 cleanup = False
867
872
868 descendants = set()
873 descendants = set()
869 if dstates:
874 if dstates:
870 descendants = set(repo.changelog.descendants(dstates))
875 descendants = set(repo.changelog.descendants(dstates))
871 if descendants - set(dstates):
876 if descendants - set(dstates):
872 repo.ui.warn(_("warning: new changesets detected on target branch, "
877 repo.ui.warn(_("warning: new changesets detected on target branch, "
873 "can't strip\n"))
878 "can't strip\n"))
874 cleanup = False
879 cleanup = False
875
880
876 if cleanup:
881 if cleanup:
877 # Update away from the rebase if necessary
882 # Update away from the rebase if necessary
878 if inrebase(repo, originalwd, state):
883 if needupdate(repo, state):
879 merge.update(repo, originalwd, False, True, False)
884 merge.update(repo, originalwd, False, True, False)
880
885
881 # Strip from the first rebased revision
886 # Strip from the first rebased revision
882 rebased = filter(lambda x: x >= 0 and x != target, state.values())
887 rebased = filter(lambda x: x >= 0 and x != target, state.values())
883 if rebased:
888 if rebased:
884 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
889 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
885 # no backup of rebased cset versions needed
890 # no backup of rebased cset versions needed
886 repair.strip(repo.ui, repo, strippoints)
891 repair.strip(repo.ui, repo, strippoints)
887
892
888 if activebookmark:
893 if activebookmark:
889 bookmarks.activate(repo, activebookmark)
894 bookmarks.activate(repo, activebookmark)
890
895
891 clearstatus(repo)
896 clearstatus(repo)
892 repo.ui.warn(_('rebase aborted\n'))
897 repo.ui.warn(_('rebase aborted\n'))
893 return 0
898 return 0
894
899
895 def buildstate(repo, dest, rebaseset, collapse):
900 def buildstate(repo, dest, rebaseset, collapse):
896 '''Define which revisions are going to be rebased and where
901 '''Define which revisions are going to be rebased and where
897
902
898 repo: repo
903 repo: repo
899 dest: context
904 dest: context
900 rebaseset: set of rev
905 rebaseset: set of rev
901 '''
906 '''
902 _setrebasesetvisibility(repo, rebaseset)
907 _setrebasesetvisibility(repo, rebaseset)
903
908
904 # This check isn't strictly necessary, since mq detects commits over an
909 # This check isn't strictly necessary, since mq detects commits over an
905 # applied patch. But it prevents messing up the working directory when
910 # applied patch. But it prevents messing up the working directory when
906 # a partially completed rebase is blocked by mq.
911 # a partially completed rebase is blocked by mq.
907 if 'qtip' in repo.tags() and (dest.node() in
912 if 'qtip' in repo.tags() and (dest.node() in
908 [s.node for s in repo.mq.applied]):
913 [s.node for s in repo.mq.applied]):
909 raise util.Abort(_('cannot rebase onto an applied mq patch'))
914 raise util.Abort(_('cannot rebase onto an applied mq patch'))
910
915
911 roots = list(repo.set('roots(%ld)', rebaseset))
916 roots = list(repo.set('roots(%ld)', rebaseset))
912 if not roots:
917 if not roots:
913 raise util.Abort(_('no matching revisions'))
918 raise util.Abort(_('no matching revisions'))
914 roots.sort()
919 roots.sort()
915 state = {}
920 state = {}
916 detachset = set()
921 detachset = set()
917 for root in roots:
922 for root in roots:
918 commonbase = root.ancestor(dest)
923 commonbase = root.ancestor(dest)
919 if commonbase == root:
924 if commonbase == root:
920 raise util.Abort(_('source is ancestor of destination'))
925 raise util.Abort(_('source is ancestor of destination'))
921 if commonbase == dest:
926 if commonbase == dest:
922 samebranch = root.branch() == dest.branch()
927 samebranch = root.branch() == dest.branch()
923 if not collapse and samebranch and root in dest.children():
928 if not collapse and samebranch and root in dest.children():
924 repo.ui.debug('source is a child of destination\n')
929 repo.ui.debug('source is a child of destination\n')
925 return None
930 return None
926
931
927 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
932 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
928 state.update(dict.fromkeys(rebaseset, revtodo))
933 state.update(dict.fromkeys(rebaseset, revtodo))
929 # Rebase tries to turn <dest> into a parent of <root> while
934 # Rebase tries to turn <dest> into a parent of <root> while
930 # preserving the number of parents of rebased changesets:
935 # preserving the number of parents of rebased changesets:
931 #
936 #
932 # - A changeset with a single parent will always be rebased as a
937 # - A changeset with a single parent will always be rebased as a
933 # changeset with a single parent.
938 # changeset with a single parent.
934 #
939 #
935 # - A merge will be rebased as merge unless its parents are both
940 # - A merge will be rebased as merge unless its parents are both
936 # ancestors of <dest> or are themselves in the rebased set and
941 # ancestors of <dest> or are themselves in the rebased set and
937 # pruned while rebased.
942 # pruned while rebased.
938 #
943 #
939 # If one parent of <root> is an ancestor of <dest>, the rebased
944 # If one parent of <root> is an ancestor of <dest>, the rebased
940 # version of this parent will be <dest>. This is always true with
945 # version of this parent will be <dest>. This is always true with
941 # --base option.
946 # --base option.
942 #
947 #
943 # Otherwise, we need to *replace* the original parents with
948 # Otherwise, we need to *replace* the original parents with
944 # <dest>. This "detaches" the rebased set from its former location
949 # <dest>. This "detaches" the rebased set from its former location
945 # and rebases it onto <dest>. Changes introduced by ancestors of
950 # and rebases it onto <dest>. Changes introduced by ancestors of
946 # <root> not common with <dest> (the detachset, marked as
951 # <root> not common with <dest> (the detachset, marked as
947 # nullmerge) are "removed" from the rebased changesets.
952 # nullmerge) are "removed" from the rebased changesets.
948 #
953 #
949 # - If <root> has a single parent, set it to <dest>.
954 # - If <root> has a single parent, set it to <dest>.
950 #
955 #
951 # - If <root> is a merge, we cannot decide which parent to
956 # - If <root> is a merge, we cannot decide which parent to
952 # replace, the rebase operation is not clearly defined.
957 # replace, the rebase operation is not clearly defined.
953 #
958 #
954 # The table below sums up this behavior:
959 # The table below sums up this behavior:
955 #
960 #
956 # +------------------+----------------------+-------------------------+
961 # +------------------+----------------------+-------------------------+
957 # | | one parent | merge |
962 # | | one parent | merge |
958 # +------------------+----------------------+-------------------------+
963 # +------------------+----------------------+-------------------------+
959 # | parent in | new parent is <dest> | parents in ::<dest> are |
964 # | parent in | new parent is <dest> | parents in ::<dest> are |
960 # | ::<dest> | | remapped to <dest> |
965 # | ::<dest> | | remapped to <dest> |
961 # +------------------+----------------------+-------------------------+
966 # +------------------+----------------------+-------------------------+
962 # | unrelated source | new parent is <dest> | ambiguous, abort |
967 # | unrelated source | new parent is <dest> | ambiguous, abort |
963 # +------------------+----------------------+-------------------------+
968 # +------------------+----------------------+-------------------------+
964 #
969 #
965 # The actual abort is handled by `defineparents`
970 # The actual abort is handled by `defineparents`
966 if len(root.parents()) <= 1:
971 if len(root.parents()) <= 1:
967 # ancestors of <root> not ancestors of <dest>
972 # ancestors of <root> not ancestors of <dest>
968 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
973 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
969 [root.rev()]))
974 [root.rev()]))
970 for r in detachset:
975 for r in detachset:
971 if r not in state:
976 if r not in state:
972 state[r] = nullmerge
977 state[r] = nullmerge
973 if len(roots) > 1:
978 if len(roots) > 1:
974 # If we have multiple roots, we may have "hole" in the rebase set.
979 # If we have multiple roots, we may have "hole" in the rebase set.
975 # Rebase roots that descend from those "hole" should not be detached as
980 # Rebase roots that descend from those "hole" should not be detached as
976 # other root are. We use the special `revignored` to inform rebase that
981 # other root are. We use the special `revignored` to inform rebase that
977 # the revision should be ignored but that `defineparents` should search
982 # the revision should be ignored but that `defineparents` should search
978 # a rebase destination that make sense regarding rebased topology.
983 # a rebase destination that make sense regarding rebased topology.
979 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
984 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
980 for ignored in set(rebasedomain) - set(rebaseset):
985 for ignored in set(rebasedomain) - set(rebaseset):
981 state[ignored] = revignored
986 state[ignored] = revignored
982 return repo['.'].rev(), dest.rev(), state
987 return repo['.'].rev(), dest.rev(), state
983
988
984 def clearrebased(ui, repo, state, skipped, collapsedas=None):
989 def clearrebased(ui, repo, state, skipped, collapsedas=None):
985 """dispose of rebased revision at the end of the rebase
990 """dispose of rebased revision at the end of the rebase
986
991
987 If `collapsedas` is not None, the rebase was a collapse whose result if the
992 If `collapsedas` is not None, the rebase was a collapse whose result if the
988 `collapsedas` node."""
993 `collapsedas` node."""
989 if obsolete.isenabled(repo, obsolete.createmarkersopt):
994 if obsolete.isenabled(repo, obsolete.createmarkersopt):
990 markers = []
995 markers = []
991 for rev, newrev in sorted(state.items()):
996 for rev, newrev in sorted(state.items()):
992 if newrev >= 0:
997 if newrev >= 0:
993 if rev in skipped:
998 if rev in skipped:
994 succs = ()
999 succs = ()
995 elif collapsedas is not None:
1000 elif collapsedas is not None:
996 succs = (repo[collapsedas],)
1001 succs = (repo[collapsedas],)
997 else:
1002 else:
998 succs = (repo[newrev],)
1003 succs = (repo[newrev],)
999 markers.append((repo[rev], succs))
1004 markers.append((repo[rev], succs))
1000 if markers:
1005 if markers:
1001 obsolete.createmarkers(repo, markers)
1006 obsolete.createmarkers(repo, markers)
1002 else:
1007 else:
1003 rebased = [rev for rev in state if state[rev] > nullmerge]
1008 rebased = [rev for rev in state if state[rev] > nullmerge]
1004 if rebased:
1009 if rebased:
1005 stripped = []
1010 stripped = []
1006 for root in repo.set('roots(%ld)', rebased):
1011 for root in repo.set('roots(%ld)', rebased):
1007 if set(repo.changelog.descendants([root.rev()])) - set(state):
1012 if set(repo.changelog.descendants([root.rev()])) - set(state):
1008 ui.warn(_("warning: new changesets detected "
1013 ui.warn(_("warning: new changesets detected "
1009 "on source branch, not stripping\n"))
1014 "on source branch, not stripping\n"))
1010 else:
1015 else:
1011 stripped.append(root.node())
1016 stripped.append(root.node())
1012 if stripped:
1017 if stripped:
1013 # backup the old csets by default
1018 # backup the old csets by default
1014 repair.strip(ui, repo, stripped, "all")
1019 repair.strip(ui, repo, stripped, "all")
1015
1020
1016
1021
1017 def pullrebase(orig, ui, repo, *args, **opts):
1022 def pullrebase(orig, ui, repo, *args, **opts):
1018 'Call rebase after pull if the latter has been invoked with --rebase'
1023 'Call rebase after pull if the latter has been invoked with --rebase'
1019 if opts.get('rebase'):
1024 if opts.get('rebase'):
1020 if opts.get('update'):
1025 if opts.get('update'):
1021 del opts['update']
1026 del opts['update']
1022 ui.debug('--update and --rebase are not compatible, ignoring '
1027 ui.debug('--update and --rebase are not compatible, ignoring '
1023 'the update flag\n')
1028 'the update flag\n')
1024
1029
1025 movemarkfrom = repo['.'].node()
1030 movemarkfrom = repo['.'].node()
1026 revsprepull = len(repo)
1031 revsprepull = len(repo)
1027 origpostincoming = commands.postincoming
1032 origpostincoming = commands.postincoming
1028 def _dummy(*args, **kwargs):
1033 def _dummy(*args, **kwargs):
1029 pass
1034 pass
1030 commands.postincoming = _dummy
1035 commands.postincoming = _dummy
1031 try:
1036 try:
1032 orig(ui, repo, *args, **opts)
1037 orig(ui, repo, *args, **opts)
1033 finally:
1038 finally:
1034 commands.postincoming = origpostincoming
1039 commands.postincoming = origpostincoming
1035 revspostpull = len(repo)
1040 revspostpull = len(repo)
1036 if revspostpull > revsprepull:
1041 if revspostpull > revsprepull:
1037 # --rev option from pull conflict with rebase own --rev
1042 # --rev option from pull conflict with rebase own --rev
1038 # dropping it
1043 # dropping it
1039 if 'rev' in opts:
1044 if 'rev' in opts:
1040 del opts['rev']
1045 del opts['rev']
1041 # positional argument from pull conflicts with rebase's own
1046 # positional argument from pull conflicts with rebase's own
1042 # --source.
1047 # --source.
1043 if 'source' in opts:
1048 if 'source' in opts:
1044 del opts['source']
1049 del opts['source']
1045 rebase(ui, repo, **opts)
1050 rebase(ui, repo, **opts)
1046 branch = repo[None].branch()
1051 branch = repo[None].branch()
1047 dest = repo[branch].rev()
1052 dest = repo[branch].rev()
1048 if dest != repo['.'].rev():
1053 if dest != repo['.'].rev():
1049 # there was nothing to rebase we force an update
1054 # there was nothing to rebase we force an update
1050 hg.update(repo, dest)
1055 hg.update(repo, dest)
1051 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
1056 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
1052 ui.status(_("updating bookmark %s\n")
1057 ui.status(_("updating bookmark %s\n")
1053 % repo._activebookmark)
1058 % repo._activebookmark)
1054 else:
1059 else:
1055 if opts.get('tool'):
1060 if opts.get('tool'):
1056 raise util.Abort(_('--tool can only be used with --rebase'))
1061 raise util.Abort(_('--tool can only be used with --rebase'))
1057 orig(ui, repo, *args, **opts)
1062 orig(ui, repo, *args, **opts)
1058
1063
1059 def _setrebasesetvisibility(repo, revs):
1064 def _setrebasesetvisibility(repo, revs):
1060 """store the currently rebased set on the repo object
1065 """store the currently rebased set on the repo object
1061
1066
1062 This is used by another function to prevent rebased revision to because
1067 This is used by another function to prevent rebased revision to because
1063 hidden (see issue4505)"""
1068 hidden (see issue4505)"""
1064 repo = repo.unfiltered()
1069 repo = repo.unfiltered()
1065 revs = set(revs)
1070 revs = set(revs)
1066 repo._rebaseset = revs
1071 repo._rebaseset = revs
1067 # invalidate cache if visibility changes
1072 # invalidate cache if visibility changes
1068 hiddens = repo.filteredrevcache.get('visible', set())
1073 hiddens = repo.filteredrevcache.get('visible', set())
1069 if revs & hiddens:
1074 if revs & hiddens:
1070 repo.invalidatevolatilesets()
1075 repo.invalidatevolatilesets()
1071
1076
1072 def _clearrebasesetvisibiliy(repo):
1077 def _clearrebasesetvisibiliy(repo):
1073 """remove rebaseset data from the repo"""
1078 """remove rebaseset data from the repo"""
1074 repo = repo.unfiltered()
1079 repo = repo.unfiltered()
1075 if '_rebaseset' in vars(repo):
1080 if '_rebaseset' in vars(repo):
1076 del repo._rebaseset
1081 del repo._rebaseset
1077
1082
1078 def _rebasedvisible(orig, repo):
1083 def _rebasedvisible(orig, repo):
1079 """ensure rebased revs stay visible (see issue4505)"""
1084 """ensure rebased revs stay visible (see issue4505)"""
1080 blockers = orig(repo)
1085 blockers = orig(repo)
1081 blockers.update(getattr(repo, '_rebaseset', ()))
1086 blockers.update(getattr(repo, '_rebaseset', ()))
1082 return blockers
1087 return blockers
1083
1088
1084 def summaryhook(ui, repo):
1089 def summaryhook(ui, repo):
1085 if not os.path.exists(repo.join('rebasestate')):
1090 if not os.path.exists(repo.join('rebasestate')):
1086 return
1091 return
1087 try:
1092 try:
1088 state = restorestatus(repo)[2]
1093 state = restorestatus(repo)[2]
1089 except error.RepoLookupError:
1094 except error.RepoLookupError:
1090 # i18n: column positioning for "hg summary"
1095 # i18n: column positioning for "hg summary"
1091 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1096 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1092 ui.write(msg)
1097 ui.write(msg)
1093 return
1098 return
1094 numrebased = len([i for i in state.itervalues() if i >= 0])
1099 numrebased = len([i for i in state.itervalues() if i >= 0])
1095 # i18n: column positioning for "hg summary"
1100 # i18n: column positioning for "hg summary"
1096 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1101 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1097 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1102 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1098 ui.label(_('%d remaining'), 'rebase.remaining') %
1103 ui.label(_('%d remaining'), 'rebase.remaining') %
1099 (len(state) - numrebased)))
1104 (len(state) - numrebased)))
1100
1105
1101 def uisetup(ui):
1106 def uisetup(ui):
1102 #Replace pull with a decorator to provide --rebase option
1107 #Replace pull with a decorator to provide --rebase option
1103 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1108 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1104 entry[1].append(('', 'rebase', None,
1109 entry[1].append(('', 'rebase', None,
1105 _("rebase working directory to branch head")))
1110 _("rebase working directory to branch head")))
1106 entry[1].append(('t', 'tool', '',
1111 entry[1].append(('t', 'tool', '',
1107 _("specify merge tool for rebase")))
1112 _("specify merge tool for rebase")))
1108 cmdutil.summaryhooks.add('rebase', summaryhook)
1113 cmdutil.summaryhooks.add('rebase', summaryhook)
1109 cmdutil.unfinishedstates.append(
1114 cmdutil.unfinishedstates.append(
1110 ['rebasestate', False, False, _('rebase in progress'),
1115 ['rebasestate', False, False, _('rebase in progress'),
1111 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1116 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1112 # ensure rebased rev are not hidden
1117 # ensure rebased rev are not hidden
1113 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
1118 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
1114
1119
@@ -1,243 +1,323 b''
1 $ cat >> $HGRCPATH <<EOF
1 $ cat >> $HGRCPATH <<EOF
2 > [extensions]
2 > [extensions]
3 > rebase=
3 > rebase=
4 >
4 >
5 > [phases]
5 > [phases]
6 > publish=False
6 > publish=False
7 >
7 >
8 > [alias]
8 > [alias]
9 > tglog = log -G --template "{rev}:{phase} '{desc}' {branches}\n"
9 > tglog = log -G --template "{rev}:{phase} '{desc}' {branches}\n"
10 > EOF
10 > EOF
11
11
12
12
13 $ hg init a
13 $ hg init a
14 $ cd a
14 $ cd a
15
15
16 $ touch .hg/rebasestate
16 $ touch .hg/rebasestate
17 $ hg sum
17 $ hg sum
18 parent: -1:000000000000 tip (empty repository)
18 parent: -1:000000000000 tip (empty repository)
19 branch: default
19 branch: default
20 commit: (clean)
20 commit: (clean)
21 update: (current)
21 update: (current)
22 abort: .hg/rebasestate is incomplete
22 abort: .hg/rebasestate is incomplete
23 [255]
23 [255]
24 $ rm .hg/rebasestate
24 $ rm .hg/rebasestate
25
25
26 $ echo c1 > common
26 $ echo c1 > common
27 $ hg add common
27 $ hg add common
28 $ hg ci -m C1
28 $ hg ci -m C1
29
29
30 $ echo c2 >> common
30 $ echo c2 >> common
31 $ hg ci -m C2
31 $ hg ci -m C2
32
32
33 $ echo c3 >> common
33 $ echo c3 >> common
34 $ hg ci -m C3
34 $ hg ci -m C3
35
35
36 $ hg up -q -C 1
36 $ hg up -q -C 1
37
37
38 $ echo l1 >> extra
38 $ echo l1 >> extra
39 $ hg add extra
39 $ hg add extra
40 $ hg ci -m L1
40 $ hg ci -m L1
41 created new head
41 created new head
42
42
43 $ sed -e 's/c2/l2/' common > common.new
43 $ sed -e 's/c2/l2/' common > common.new
44 $ mv common.new common
44 $ mv common.new common
45 $ hg ci -m L2
45 $ hg ci -m L2
46
46
47 $ hg phase --force --secret 2
47 $ hg phase --force --secret 2
48
48
49 $ hg tglog
49 $ hg tglog
50 @ 4:draft 'L2'
50 @ 4:draft 'L2'
51 |
51 |
52 o 3:draft 'L1'
52 o 3:draft 'L1'
53 |
53 |
54 | o 2:secret 'C3'
54 | o 2:secret 'C3'
55 |/
55 |/
56 o 1:draft 'C2'
56 o 1:draft 'C2'
57 |
57 |
58 o 0:draft 'C1'
58 o 0:draft 'C1'
59
59
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 rebasing 3:3163e20567cc "L1"
64 rebasing 3:3163e20567cc "L1"
65 rebasing 4:46f0b057b5c0 "L2" (tip)
65 rebasing 4:46f0b057b5c0 "L2" (tip)
66 merging common
66 merging common
67 warning: conflicts during merge.
67 warning: conflicts during merge.
68 merging common incomplete! (edit conflicts, then use 'hg resolve --mark')
68 merging common incomplete! (edit conflicts, then use 'hg resolve --mark')
69 unresolved conflicts (see hg resolve, then hg rebase --continue)
69 unresolved conflicts (see hg resolve, then hg rebase --continue)
70 [1]
70 [1]
71
71
72 Abort:
72 Abort:
73
73
74 $ hg rebase --abort
74 $ hg rebase --abort
75 saved backup bundle to $TESTTMP/a/.hg/strip-backup/3e046f2ecedb-6beef7d5-backup.hg (glob)
75 saved backup bundle to $TESTTMP/a/.hg/strip-backup/3e046f2ecedb-6beef7d5-backup.hg (glob)
76 rebase aborted
76 rebase aborted
77
77
78 $ hg tglog
78 $ hg tglog
79 @ 4:draft 'L2'
79 @ 4:draft 'L2'
80 |
80 |
81 o 3:draft 'L1'
81 o 3:draft 'L1'
82 |
82 |
83 | o 2:secret 'C3'
83 | o 2:secret 'C3'
84 |/
84 |/
85 o 1:draft 'C2'
85 o 1:draft 'C2'
86 |
86 |
87 o 0:draft 'C1'
87 o 0:draft 'C1'
88
88
89 Test safety for inconsistent rebase state, which may be created (and
89 Test safety for inconsistent rebase state, which may be created (and
90 forgotten) by Mercurial earlier than 2.7. This emulates Mercurial
90 forgotten) by Mercurial earlier than 2.7. This emulates Mercurial
91 earlier than 2.7 by renaming ".hg/rebasestate" temporarily.
91 earlier than 2.7 by renaming ".hg/rebasestate" temporarily.
92
92
93 $ hg rebase -s 3 -d 2
93 $ hg rebase -s 3 -d 2
94 rebasing 3:3163e20567cc "L1"
94 rebasing 3:3163e20567cc "L1"
95 rebasing 4:46f0b057b5c0 "L2" (tip)
95 rebasing 4:46f0b057b5c0 "L2" (tip)
96 merging common
96 merging common
97 warning: conflicts during merge.
97 warning: conflicts during merge.
98 merging common incomplete! (edit conflicts, then use 'hg resolve --mark')
98 merging common incomplete! (edit conflicts, then use 'hg resolve --mark')
99 unresolved conflicts (see hg resolve, then hg rebase --continue)
99 unresolved conflicts (see hg resolve, then hg rebase --continue)
100 [1]
100 [1]
101
101
102 $ mv .hg/rebasestate .hg/rebasestate.back
102 $ mv .hg/rebasestate .hg/rebasestate.back
103 $ hg update --quiet --clean 2
103 $ hg update --quiet --clean 2
104 $ hg --config extensions.mq= strip --quiet "destination()"
104 $ hg --config extensions.mq= strip --quiet "destination()"
105 $ mv .hg/rebasestate.back .hg/rebasestate
105 $ mv .hg/rebasestate.back .hg/rebasestate
106
106
107 $ hg rebase --continue
107 $ hg rebase --continue
108 abort: cannot continue inconsistent rebase
108 abort: cannot continue inconsistent rebase
109 (use "hg rebase --abort" to clear broken state)
109 (use "hg rebase --abort" to clear broken state)
110 [255]
110 [255]
111 $ hg summary | grep '^rebase: '
111 $ hg summary | grep '^rebase: '
112 rebase: (use "hg rebase --abort" to clear broken state)
112 rebase: (use "hg rebase --abort" to clear broken state)
113 $ hg rebase --abort
113 $ hg rebase --abort
114 rebase aborted (no revision is removed, only broken state is cleared)
114 rebase aborted (no revision is removed, only broken state is cleared)
115
115
116 $ cd ..
116 $ cd ..
117
117
118
118
119 Construct new repo:
119 Construct new repo:
120
120
121 $ hg init b
121 $ hg init b
122 $ cd b
122 $ cd b
123
123
124 $ echo a > a
124 $ echo a > a
125 $ hg ci -Am A
125 $ hg ci -Am A
126 adding a
126 adding a
127
127
128 $ echo b > b
128 $ echo b > b
129 $ hg ci -Am B
129 $ hg ci -Am B
130 adding b
130 adding b
131
131
132 $ echo c > c
132 $ echo c > c
133 $ hg ci -Am C
133 $ hg ci -Am C
134 adding c
134 adding c
135
135
136 $ hg up -q 0
136 $ hg up -q 0
137
137
138 $ echo b > b
138 $ echo b > b
139 $ hg ci -Am 'B bis'
139 $ hg ci -Am 'B bis'
140 adding b
140 adding b
141 created new head
141 created new head
142
142
143 $ echo c1 > c
143 $ echo c1 > c
144 $ hg ci -Am C1
144 $ hg ci -Am C1
145 adding c
145 adding c
146
146
147 $ hg phase --force --secret 1
147 $ hg phase --force --secret 1
148 $ hg phase --public 1
148 $ hg phase --public 1
149
149
150 Rebase and abort without generating new changesets:
150 Rebase and abort without generating new changesets:
151
151
152 $ hg tglog
152 $ hg tglog
153 @ 4:draft 'C1'
153 @ 4:draft 'C1'
154 |
154 |
155 o 3:draft 'B bis'
155 o 3:draft 'B bis'
156 |
156 |
157 | o 2:secret 'C'
157 | o 2:secret 'C'
158 | |
158 | |
159 | o 1:public 'B'
159 | o 1:public 'B'
160 |/
160 |/
161 o 0:public 'A'
161 o 0:public 'A'
162
162
163 $ hg rebase -b 4 -d 2
163 $ hg rebase -b 4 -d 2
164 rebasing 3:a6484957d6b9 "B bis"
164 rebasing 3:a6484957d6b9 "B bis"
165 note: rebase of 3:a6484957d6b9 created no changes to commit
165 note: rebase of 3:a6484957d6b9 created no changes to commit
166 rebasing 4:145842775fec "C1" (tip)
166 rebasing 4:145842775fec "C1" (tip)
167 merging c
167 merging c
168 warning: conflicts during merge.
168 warning: conflicts during merge.
169 merging c incomplete! (edit conflicts, then use 'hg resolve --mark')
169 merging c incomplete! (edit conflicts, then use 'hg resolve --mark')
170 unresolved conflicts (see hg resolve, then hg rebase --continue)
170 unresolved conflicts (see hg resolve, then hg rebase --continue)
171 [1]
171 [1]
172
172
173 $ hg tglog
173 $ hg tglog
174 @ 4:draft 'C1'
174 @ 4:draft 'C1'
175 |
175 |
176 o 3:draft 'B bis'
176 o 3:draft 'B bis'
177 |
177 |
178 | @ 2:secret 'C'
178 | @ 2:secret 'C'
179 | |
179 | |
180 | o 1:public 'B'
180 | o 1:public 'B'
181 |/
181 |/
182 o 0:public 'A'
182 o 0:public 'A'
183
183
184 $ hg rebase -a
184 $ hg rebase -a
185 rebase aborted
185 rebase aborted
186
186
187 $ hg tglog
187 $ hg tglog
188 @ 4:draft 'C1'
188 @ 4:draft 'C1'
189 |
189 |
190 o 3:draft 'B bis'
190 o 3:draft 'B bis'
191 |
191 |
192 | o 2:secret 'C'
192 | o 2:secret 'C'
193 | |
193 | |
194 | o 1:public 'B'
194 | o 1:public 'B'
195 |/
195 |/
196 o 0:public 'A'
196 o 0:public 'A'
197
197
198
198
199 $ cd ..
199 $ cd ..
200
200
201 rebase abort should not leave working copy in a merge state if tip-1 is public
201 rebase abort should not leave working copy in a merge state if tip-1 is public
202 (issue4082)
202 (issue4082)
203
203
204 $ hg init abortpublic
204 $ hg init abortpublic
205 $ cd abortpublic
205 $ cd abortpublic
206 $ echo a > a && hg ci -Aqm a
206 $ echo a > a && hg ci -Aqm a
207 $ hg book master
207 $ hg book master
208 $ hg book foo
208 $ hg book foo
209 $ echo b > b && hg ci -Aqm b
209 $ echo b > b && hg ci -Aqm b
210 $ hg up -q master
210 $ hg up -q master
211 $ echo c > c && hg ci -Aqm c
211 $ echo c > c && hg ci -Aqm c
212 $ hg phase -p -r .
212 $ hg phase -p -r .
213 $ hg up -q foo
213 $ hg up -q foo
214 $ echo C > c && hg ci -Aqm C
214 $ echo C > c && hg ci -Aqm C
215 $ hg log -G --template "{rev} {desc} {bookmarks}"
215 $ hg log -G --template "{rev} {desc} {bookmarks}"
216 @ 3 C foo
216 @ 3 C foo
217 |
217 |
218 | o 2 c master
218 | o 2 c master
219 | |
219 | |
220 o | 1 b
220 o | 1 b
221 |/
221 |/
222 o 0 a
222 o 0 a
223
223
224
224
225 $ hg rebase -d master -r foo
225 $ hg rebase -d master -r foo
226 rebasing 3:6c0f977a22d8 "C" (tip foo)
226 rebasing 3:6c0f977a22d8 "C" (tip foo)
227 merging c
227 merging c
228 warning: conflicts during merge.
228 warning: conflicts during merge.
229 merging c incomplete! (edit conflicts, then use 'hg resolve --mark')
229 merging c incomplete! (edit conflicts, then use 'hg resolve --mark')
230 unresolved conflicts (see hg resolve, then hg rebase --continue)
230 unresolved conflicts (see hg resolve, then hg rebase --continue)
231 [1]
231 [1]
232 $ hg rebase --abort
232 $ hg rebase --abort
233 rebase aborted
233 rebase aborted
234 $ hg log -G --template "{rev} {desc} {bookmarks}"
234 $ hg log -G --template "{rev} {desc} {bookmarks}"
235 @ 3 C foo
235 @ 3 C foo
236 |
236 |
237 | o 2 c master
237 | o 2 c master
238 | |
238 | |
239 o | 1 b
239 o | 1 b
240 |/
240 |/
241 o 0 a
241 o 0 a
242
242
243 $ cd ..
243 $ cd ..
244
245 Make sure we don't clobber changes in the working directory when the
246 user has somehow managed to update to a different revision (issue4009)
247
248 $ hg init noupdate
249 $ cd noupdate
250 $ hg book @
251 $ echo original > a
252 $ hg add a
253 $ hg commit -m a
254 $ echo x > b
255 $ hg add b
256 $ hg commit -m b1
257 $ hg up 0
258 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
259 (leaving bookmark @)
260 $ hg book foo
261 $ echo y > b
262 $ hg add b
263 $ hg commit -m b2
264 created new head
265
266 $ hg rebase -d @ -b foo --tool=internal:fail
267 rebasing 2:070cf4580bb5 "b2" (tip foo)
268 unresolved conflicts (see hg resolve, then hg rebase --continue)
269 [1]
270
271 $ mv .hg/rebasestate ./ # so we're allowed to hg up like in mercurial <2.6.3
272 $ hg up -C 0 # user does other stuff in the repo
273 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
274
275 $ mv rebasestate .hg/ # user upgrades to 2.7
276
277 $ echo new > a
278 $ hg up 1 # user gets an error saying to run hg rebase --abort
279 abort: rebase in progress
280 (use 'hg rebase --continue' or 'hg rebase --abort')
281 [255]
282
283 $ cat a
284 new
285 $ hg rebase --abort
286 rebase aborted
287 $ cat a
288 new
289
290 $ cd ..
291
292 On the other hand, make sure we *do* clobber changes whenever we
293 haven't somehow managed to update the repo to a different revision
294 during a rebase (issue4661)
295
296 $ hg ini yesupdate
297 $ cd yesupdate
298 $ echo "initial data" > foo.txt
299 $ hg add
300 adding foo.txt
301 $ hg ci -m "initial checkin"
302 $ echo "change 1" > foo.txt
303 $ hg ci -m "change 1"
304 $ hg up 0
305 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
306 $ echo "conflicting change 1" > foo.txt
307 $ hg ci -m "conflicting 1"
308 created new head
309 $ echo "conflicting change 2" > foo.txt
310 $ hg ci -m "conflicting 2"
311
312 $ hg rebase -d 1 --tool 'internal:fail'
313 rebasing 2:e4ea5cdc9789 "conflicting 1"
314 unresolved conflicts (see hg resolve, then hg rebase --continue)
315 [1]
316 $ hg rebase --abort
317 rebase aborted
318 $ hg summary
319 parent: 3:b16646383533 tip
320 conflicting 2
321 branch: default
322 commit: (clean)
323 update: 1 new changesets, 2 branch heads (merge)
General Comments 0
You need to be logged in to leave comments. Login now