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