##// END OF EJS Templates
clfilter: drop unnecessary explicit filtering on rebase...
Pierre-Yves David -
r18269:9454e40e default
parent child Browse files
Show More
@@ -1,737 +1,735 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
18 from mercurial import extensions, patch, scmutil, phases, obsolete
19 from mercurial.commands import templateopts
19 from mercurial.commands import templateopts
20 from mercurial.node import nullrev
20 from mercurial.node import nullrev
21 from mercurial.lock import release
21 from mercurial.lock import release
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23 import os, errno
23 import os, errno
24
24
25 nullmerge = -2
25 nullmerge = -2
26
26
27 cmdtable = {}
27 cmdtable = {}
28 command = cmdutil.command(cmdtable)
28 command = cmdutil.command(cmdtable)
29 testedwith = 'internal'
29 testedwith = 'internal'
30
30
31 @command('rebase',
31 @command('rebase',
32 [('s', 'source', '',
32 [('s', 'source', '',
33 _('rebase from the specified changeset'), _('REV')),
33 _('rebase from the specified changeset'), _('REV')),
34 ('b', 'base', '',
34 ('b', 'base', '',
35 _('rebase from the base of the specified changeset '
35 _('rebase from the base of the specified changeset '
36 '(up to greatest common ancestor of base and dest)'),
36 '(up to greatest common ancestor of base and dest)'),
37 _('REV')),
37 _('REV')),
38 ('r', 'rev', [],
38 ('r', 'rev', [],
39 _('rebase these revisions'),
39 _('rebase these revisions'),
40 _('REV')),
40 _('REV')),
41 ('d', 'dest', '',
41 ('d', 'dest', '',
42 _('rebase onto the specified changeset'), _('REV')),
42 _('rebase onto the specified changeset'), _('REV')),
43 ('', 'collapse', False, _('collapse the rebased changesets')),
43 ('', 'collapse', False, _('collapse the rebased changesets')),
44 ('m', 'message', '',
44 ('m', 'message', '',
45 _('use text as collapse commit message'), _('TEXT')),
45 _('use text as collapse commit message'), _('TEXT')),
46 ('e', 'edit', False, _('invoke editor on commit messages')),
46 ('e', 'edit', False, _('invoke editor on commit messages')),
47 ('l', 'logfile', '',
47 ('l', 'logfile', '',
48 _('read collapse commit message from file'), _('FILE')),
48 _('read collapse commit message from file'), _('FILE')),
49 ('', 'keep', False, _('keep original changesets')),
49 ('', 'keep', False, _('keep original changesets')),
50 ('', 'keepbranches', False, _('keep original branch names')),
50 ('', 'keepbranches', False, _('keep original branch names')),
51 ('D', 'detach', False, _('(DEPRECATED)')),
51 ('D', 'detach', False, _('(DEPRECATED)')),
52 ('t', 'tool', '', _('specify merge tool')),
52 ('t', 'tool', '', _('specify merge tool')),
53 ('c', 'continue', False, _('continue an interrupted rebase')),
53 ('c', 'continue', False, _('continue an interrupted rebase')),
54 ('a', 'abort', False, _('abort an interrupted rebase'))] +
54 ('a', 'abort', False, _('abort an interrupted rebase'))] +
55 templateopts,
55 templateopts,
56 _('[-s REV | -b REV] [-d REV] [OPTION]'))
56 _('[-s REV | -b REV] [-d REV] [OPTION]'))
57 def rebase(ui, repo, **opts):
57 def rebase(ui, repo, **opts):
58 """move changeset (and descendants) to a different branch
58 """move changeset (and descendants) to a different branch
59
59
60 Rebase uses repeated merging to graft changesets from one part of
60 Rebase uses repeated merging to graft changesets from one part of
61 history (the source) onto another (the destination). This can be
61 history (the source) onto another (the destination). This can be
62 useful for linearizing *local* changes relative to a master
62 useful for linearizing *local* changes relative to a master
63 development tree.
63 development tree.
64
64
65 You should not rebase changesets that have already been shared
65 You should not rebase changesets that have already been shared
66 with others. Doing so will force everybody else to perform the
66 with others. Doing so will force everybody else to perform the
67 same rebase or they will end up with duplicated changesets after
67 same rebase or they will end up with duplicated changesets after
68 pulling in your rebased changesets.
68 pulling in your rebased changesets.
69
69
70 If you don't specify a destination changeset (``-d/--dest``),
70 If you don't specify a destination changeset (``-d/--dest``),
71 rebase uses the tipmost head of the current named branch as the
71 rebase uses the tipmost head of the current named branch as the
72 destination. (The destination changeset is not modified by
72 destination. (The destination changeset is not modified by
73 rebasing, but new changesets are added as its descendants.)
73 rebasing, but new changesets are added as its descendants.)
74
74
75 You can specify which changesets to rebase in two ways: as a
75 You can specify which changesets to rebase in two ways: as a
76 "source" changeset or as a "base" changeset. Both are shorthand
76 "source" changeset or as a "base" changeset. Both are shorthand
77 for a topologically related set of changesets (the "source
77 for a topologically related set of changesets (the "source
78 branch"). If you specify source (``-s/--source``), rebase will
78 branch"). If you specify source (``-s/--source``), rebase will
79 rebase that changeset and all of its descendants onto dest. If you
79 rebase that changeset and all of its descendants onto dest. If you
80 specify base (``-b/--base``), rebase will select ancestors of base
80 specify base (``-b/--base``), rebase will select ancestors of base
81 back to but not including the common ancestor with dest. Thus,
81 back to but not including the common ancestor with dest. Thus,
82 ``-b`` is less precise but more convenient than ``-s``: you can
82 ``-b`` is less precise but more convenient than ``-s``: you can
83 specify any changeset in the source branch, and rebase will select
83 specify any changeset in the source branch, and rebase will select
84 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
84 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
85 uses the parent of the working directory as the base.
85 uses the parent of the working directory as the base.
86
86
87 By default, rebase recreates the changesets in the source branch
87 By default, rebase recreates the changesets in the source branch
88 as descendants of dest and then destroys the originals. Use
88 as descendants of dest and then destroys the originals. Use
89 ``--keep`` to preserve the original source changesets. Some
89 ``--keep`` to preserve the original source changesets. Some
90 changesets in the source branch (e.g. merges from the destination
90 changesets in the source branch (e.g. merges from the destination
91 branch) may be dropped if they no longer contribute any change.
91 branch) may be dropped if they no longer contribute any change.
92
92
93 One result of the rules for selecting the destination changeset
93 One result of the rules for selecting the destination changeset
94 and source branch is that, unlike ``merge``, rebase will do
94 and source branch is that, unlike ``merge``, rebase will do
95 nothing if you are at the latest (tipmost) head of a named branch
95 nothing if you are at the latest (tipmost) head of a named branch
96 with two heads. You need to explicitly specify source and/or
96 with two heads. You need to explicitly specify source and/or
97 destination (or ``update`` to the other head, if it's the head of
97 destination (or ``update`` to the other head, if it's the head of
98 the intended source branch).
98 the intended source branch).
99
99
100 If a rebase is interrupted to manually resolve a merge, it can be
100 If a rebase is interrupted to manually resolve a merge, it can be
101 continued with --continue/-c or aborted with --abort/-a.
101 continued with --continue/-c or aborted with --abort/-a.
102
102
103 Returns 0 on success, 1 if nothing to rebase.
103 Returns 0 on success, 1 if nothing to rebase.
104 """
104 """
105 originalwd = target = None
105 originalwd = target = None
106 external = nullrev
106 external = nullrev
107 state = {}
107 state = {}
108 skipped = set()
108 skipped = set()
109 targetancestors = set()
109 targetancestors = set()
110
110
111 editor = None
111 editor = None
112 if opts.get('edit'):
112 if opts.get('edit'):
113 editor = cmdutil.commitforceeditor
113 editor = cmdutil.commitforceeditor
114
114
115 lock = wlock = None
115 lock = wlock = None
116 try:
116 try:
117 wlock = repo.wlock()
117 wlock = repo.wlock()
118 lock = repo.lock()
118 lock = repo.lock()
119
119
120 # Validate input and define rebasing points
120 # Validate input and define rebasing points
121 destf = opts.get('dest', None)
121 destf = opts.get('dest', None)
122 srcf = opts.get('source', None)
122 srcf = opts.get('source', None)
123 basef = opts.get('base', None)
123 basef = opts.get('base', None)
124 revf = opts.get('rev', [])
124 revf = opts.get('rev', [])
125 contf = opts.get('continue')
125 contf = opts.get('continue')
126 abortf = opts.get('abort')
126 abortf = opts.get('abort')
127 collapsef = opts.get('collapse', False)
127 collapsef = opts.get('collapse', False)
128 collapsemsg = cmdutil.logmessage(ui, opts)
128 collapsemsg = cmdutil.logmessage(ui, opts)
129 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
129 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
130 keepf = opts.get('keep', False)
130 keepf = opts.get('keep', False)
131 keepbranchesf = opts.get('keepbranches', False)
131 keepbranchesf = opts.get('keepbranches', False)
132 # keepopen is not meant for use on the command line, but by
132 # keepopen is not meant for use on the command line, but by
133 # other extensions
133 # other extensions
134 keepopen = opts.get('keepopen', False)
134 keepopen = opts.get('keepopen', False)
135
135
136 if collapsemsg and not collapsef:
136 if collapsemsg and not collapsef:
137 raise util.Abort(
137 raise util.Abort(
138 _('message can only be specified with collapse'))
138 _('message can only be specified with collapse'))
139
139
140 if contf or abortf:
140 if contf or abortf:
141 if contf and abortf:
141 if contf and abortf:
142 raise util.Abort(_('cannot use both abort and continue'))
142 raise util.Abort(_('cannot use both abort and continue'))
143 if collapsef:
143 if collapsef:
144 raise util.Abort(
144 raise util.Abort(
145 _('cannot use collapse with continue or abort'))
145 _('cannot use collapse with continue or abort'))
146 if srcf or basef or destf:
146 if srcf or basef or destf:
147 raise util.Abort(
147 raise util.Abort(
148 _('abort and continue do not allow specifying revisions'))
148 _('abort and continue do not allow specifying revisions'))
149 if opts.get('tool', False):
149 if opts.get('tool', False):
150 ui.warn(_('tool option will be ignored\n'))
150 ui.warn(_('tool option will be ignored\n'))
151
151
152 (originalwd, target, state, skipped, collapsef, keepf,
152 (originalwd, target, state, skipped, collapsef, keepf,
153 keepbranchesf, external) = restorestatus(repo)
153 keepbranchesf, external) = restorestatus(repo)
154 if abortf:
154 if abortf:
155 return abort(repo, originalwd, target, state)
155 return abort(repo, originalwd, target, state)
156 else:
156 else:
157 if srcf and basef:
157 if srcf and basef:
158 raise util.Abort(_('cannot specify both a '
158 raise util.Abort(_('cannot specify both a '
159 'source and a base'))
159 'source and a base'))
160 if revf and basef:
160 if revf and basef:
161 raise util.Abort(_('cannot specify both a '
161 raise util.Abort(_('cannot specify both a '
162 'revision and a base'))
162 'revision and a base'))
163 if revf and srcf:
163 if revf and srcf:
164 raise util.Abort(_('cannot specify both a '
164 raise util.Abort(_('cannot specify both a '
165 'revision and a source'))
165 'revision and a source'))
166
166
167 cmdutil.bailifchanged(repo)
167 cmdutil.bailifchanged(repo)
168
168
169 if not destf:
169 if not destf:
170 # Destination defaults to the latest revision in the
170 # Destination defaults to the latest revision in the
171 # current branch
171 # current branch
172 branch = repo[None].branch()
172 branch = repo[None].branch()
173 dest = repo[branch]
173 dest = repo[branch]
174 else:
174 else:
175 dest = scmutil.revsingle(repo, destf)
175 dest = scmutil.revsingle(repo, destf)
176
176
177 if revf:
177 if revf:
178 rebaseset = repo.revs('%lr', revf)
178 rebaseset = repo.revs('%lr', revf)
179 elif srcf:
179 elif srcf:
180 src = scmutil.revrange(repo, [srcf])
180 src = scmutil.revrange(repo, [srcf])
181 rebaseset = repo.revs('(%ld)::', src)
181 rebaseset = repo.revs('(%ld)::', src)
182 else:
182 else:
183 base = scmutil.revrange(repo, [basef or '.'])
183 base = scmutil.revrange(repo, [basef or '.'])
184 rebaseset = repo.revs(
184 rebaseset = repo.revs(
185 '(children(ancestor(%ld, %d)) and ::(%ld))::',
185 '(children(ancestor(%ld, %d)) and ::(%ld))::',
186 base, dest, base)
186 base, dest, base)
187 # temporary top level filtering of extinct revisions
188 rebaseset = repo.revs('%ld - hidden()', rebaseset)
189 if rebaseset:
187 if rebaseset:
190 root = min(rebaseset)
188 root = min(rebaseset)
191 else:
189 else:
192 root = None
190 root = None
193
191
194 if not rebaseset:
192 if not rebaseset:
195 repo.ui.debug('base is ancestor of destination\n')
193 repo.ui.debug('base is ancestor of destination\n')
196 result = None
194 result = None
197 elif (not (keepf or obsolete._enabled)
195 elif (not (keepf or obsolete._enabled)
198 and repo.revs('first(children(%ld) - %ld)-hidden()',
196 and repo.revs('first(children(%ld) - %ld)',
199 rebaseset, rebaseset)):
197 rebaseset, rebaseset)):
200 raise util.Abort(
198 raise util.Abort(
201 _("can't remove original changesets with"
199 _("can't remove original changesets with"
202 " unrebased descendants"),
200 " unrebased descendants"),
203 hint=_('use --keep to keep original changesets'))
201 hint=_('use --keep to keep original changesets'))
204 elif not keepf and not repo[root].mutable():
202 elif not keepf and not repo[root].mutable():
205 raise util.Abort(_("can't rebase immutable changeset %s")
203 raise util.Abort(_("can't rebase immutable changeset %s")
206 % repo[root],
204 % repo[root],
207 hint=_('see hg help phases for details'))
205 hint=_('see hg help phases for details'))
208 else:
206 else:
209 result = buildstate(repo, dest, rebaseset, collapsef)
207 result = buildstate(repo, dest, rebaseset, collapsef)
210
208
211 if not result:
209 if not result:
212 # Empty state built, nothing to rebase
210 # Empty state built, nothing to rebase
213 ui.status(_('nothing to rebase\n'))
211 ui.status(_('nothing to rebase\n'))
214 return 1
212 return 1
215 else:
213 else:
216 originalwd, target, state = result
214 originalwd, target, state = result
217 if collapsef:
215 if collapsef:
218 targetancestors = repo.changelog.ancestors([target],
216 targetancestors = repo.changelog.ancestors([target],
219 inclusive=True)
217 inclusive=True)
220 external = checkexternal(repo, state, targetancestors)
218 external = checkexternal(repo, state, targetancestors)
221
219
222 if keepbranchesf:
220 if keepbranchesf:
223 assert not extrafn, 'cannot use both keepbranches and extrafn'
221 assert not extrafn, 'cannot use both keepbranches and extrafn'
224 def extrafn(ctx, extra):
222 def extrafn(ctx, extra):
225 extra['branch'] = ctx.branch()
223 extra['branch'] = ctx.branch()
226 if collapsef:
224 if collapsef:
227 branches = set()
225 branches = set()
228 for rev in state:
226 for rev in state:
229 branches.add(repo[rev].branch())
227 branches.add(repo[rev].branch())
230 if len(branches) > 1:
228 if len(branches) > 1:
231 raise util.Abort(_('cannot collapse multiple named '
229 raise util.Abort(_('cannot collapse multiple named '
232 'branches'))
230 'branches'))
233
231
234
232
235 # Rebase
233 # Rebase
236 if not targetancestors:
234 if not targetancestors:
237 targetancestors = repo.changelog.ancestors([target], inclusive=True)
235 targetancestors = repo.changelog.ancestors([target], inclusive=True)
238
236
239 # Keep track of the current bookmarks in order to reset them later
237 # Keep track of the current bookmarks in order to reset them later
240 currentbookmarks = repo._bookmarks.copy()
238 currentbookmarks = repo._bookmarks.copy()
241 activebookmark = repo._bookmarkcurrent
239 activebookmark = repo._bookmarkcurrent
242 if activebookmark:
240 if activebookmark:
243 bookmarks.unsetcurrent(repo)
241 bookmarks.unsetcurrent(repo)
244
242
245 sortedstate = sorted(state)
243 sortedstate = sorted(state)
246 total = len(sortedstate)
244 total = len(sortedstate)
247 pos = 0
245 pos = 0
248 for rev in sortedstate:
246 for rev in sortedstate:
249 pos += 1
247 pos += 1
250 if state[rev] == -1:
248 if state[rev] == -1:
251 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
249 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
252 _('changesets'), total)
250 _('changesets'), total)
253 storestatus(repo, originalwd, target, state, collapsef, keepf,
251 storestatus(repo, originalwd, target, state, collapsef, keepf,
254 keepbranchesf, external)
252 keepbranchesf, external)
255 p1, p2 = defineparents(repo, rev, target, state,
253 p1, p2 = defineparents(repo, rev, target, state,
256 targetancestors)
254 targetancestors)
257 if len(repo.parents()) == 2:
255 if len(repo.parents()) == 2:
258 repo.ui.debug('resuming interrupted rebase\n')
256 repo.ui.debug('resuming interrupted rebase\n')
259 else:
257 else:
260 try:
258 try:
261 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
259 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
262 stats = rebasenode(repo, rev, p1, state, collapsef)
260 stats = rebasenode(repo, rev, p1, state, collapsef)
263 if stats and stats[3] > 0:
261 if stats and stats[3] > 0:
264 raise util.Abort(_('unresolved conflicts (see hg '
262 raise util.Abort(_('unresolved conflicts (see hg '
265 'resolve, then hg rebase --continue)'))
263 'resolve, then hg rebase --continue)'))
266 finally:
264 finally:
267 ui.setconfig('ui', 'forcemerge', '')
265 ui.setconfig('ui', 'forcemerge', '')
268 cmdutil.duplicatecopies(repo, rev, target)
266 cmdutil.duplicatecopies(repo, rev, target)
269 if not collapsef:
267 if not collapsef:
270 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
268 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
271 editor=editor)
269 editor=editor)
272 else:
270 else:
273 # Skip commit if we are collapsing
271 # Skip commit if we are collapsing
274 repo.setparents(repo[p1].node())
272 repo.setparents(repo[p1].node())
275 newrev = None
273 newrev = None
276 # Update the state
274 # Update the state
277 if newrev is not None:
275 if newrev is not None:
278 state[rev] = repo[newrev].rev()
276 state[rev] = repo[newrev].rev()
279 else:
277 else:
280 if not collapsef:
278 if not collapsef:
281 ui.note(_('no changes, revision %d skipped\n') % rev)
279 ui.note(_('no changes, revision %d skipped\n') % rev)
282 ui.debug('next revision set to %s\n' % p1)
280 ui.debug('next revision set to %s\n' % p1)
283 skipped.add(rev)
281 skipped.add(rev)
284 state[rev] = p1
282 state[rev] = p1
285
283
286 ui.progress(_('rebasing'), None)
284 ui.progress(_('rebasing'), None)
287 ui.note(_('rebase merging completed\n'))
285 ui.note(_('rebase merging completed\n'))
288
286
289 if collapsef and not keepopen:
287 if collapsef and not keepopen:
290 p1, p2 = defineparents(repo, min(state), target,
288 p1, p2 = defineparents(repo, min(state), target,
291 state, targetancestors)
289 state, targetancestors)
292 if collapsemsg:
290 if collapsemsg:
293 commitmsg = collapsemsg
291 commitmsg = collapsemsg
294 else:
292 else:
295 commitmsg = 'Collapsed revision'
293 commitmsg = 'Collapsed revision'
296 for rebased in state:
294 for rebased in state:
297 if rebased not in skipped and state[rebased] != nullmerge:
295 if rebased not in skipped and state[rebased] != nullmerge:
298 commitmsg += '\n* %s' % repo[rebased].description()
296 commitmsg += '\n* %s' % repo[rebased].description()
299 commitmsg = ui.edit(commitmsg, repo.ui.username())
297 commitmsg = ui.edit(commitmsg, repo.ui.username())
300 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
298 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
301 extrafn=extrafn, editor=editor)
299 extrafn=extrafn, editor=editor)
302
300
303 if 'qtip' in repo.tags():
301 if 'qtip' in repo.tags():
304 updatemq(repo, state, skipped, **opts)
302 updatemq(repo, state, skipped, **opts)
305
303
306 if currentbookmarks:
304 if currentbookmarks:
307 # Nodeids are needed to reset bookmarks
305 # Nodeids are needed to reset bookmarks
308 nstate = {}
306 nstate = {}
309 for k, v in state.iteritems():
307 for k, v in state.iteritems():
310 if v != nullmerge:
308 if v != nullmerge:
311 nstate[repo[k].node()] = repo[v].node()
309 nstate[repo[k].node()] = repo[v].node()
312
310
313 if not keepf:
311 if not keepf:
314 collapsedas = None
312 collapsedas = None
315 if collapsef:
313 if collapsef:
316 collapsedas = newrev
314 collapsedas = newrev
317 clearrebased(ui, repo, state, collapsedas)
315 clearrebased(ui, repo, state, collapsedas)
318
316
319 if currentbookmarks:
317 if currentbookmarks:
320 updatebookmarks(repo, nstate, currentbookmarks, **opts)
318 updatebookmarks(repo, nstate, currentbookmarks, **opts)
321
319
322 clearstatus(repo)
320 clearstatus(repo)
323 ui.note(_("rebase completed\n"))
321 ui.note(_("rebase completed\n"))
324 if os.path.exists(repo.sjoin('undo')):
322 if os.path.exists(repo.sjoin('undo')):
325 util.unlinkpath(repo.sjoin('undo'))
323 util.unlinkpath(repo.sjoin('undo'))
326 if skipped:
324 if skipped:
327 ui.note(_("%d revisions have been skipped\n") % len(skipped))
325 ui.note(_("%d revisions have been skipped\n") % len(skipped))
328
326
329 if (activebookmark and
327 if (activebookmark and
330 repo['tip'].node() == repo._bookmarks[activebookmark]):
328 repo['tip'].node() == repo._bookmarks[activebookmark]):
331 bookmarks.setcurrent(repo, activebookmark)
329 bookmarks.setcurrent(repo, activebookmark)
332
330
333 finally:
331 finally:
334 release(lock, wlock)
332 release(lock, wlock)
335
333
336 def checkexternal(repo, state, targetancestors):
334 def checkexternal(repo, state, targetancestors):
337 """Check whether one or more external revisions need to be taken in
335 """Check whether one or more external revisions need to be taken in
338 consideration. In the latter case, abort.
336 consideration. In the latter case, abort.
339 """
337 """
340 external = nullrev
338 external = nullrev
341 source = min(state)
339 source = min(state)
342 for rev in state:
340 for rev in state:
343 if rev == source:
341 if rev == source:
344 continue
342 continue
345 # Check externals and fail if there are more than one
343 # Check externals and fail if there are more than one
346 for p in repo[rev].parents():
344 for p in repo[rev].parents():
347 if (p.rev() not in state
345 if (p.rev() not in state
348 and p.rev() not in targetancestors):
346 and p.rev() not in targetancestors):
349 if external != nullrev:
347 if external != nullrev:
350 raise util.Abort(_('unable to collapse, there is more '
348 raise util.Abort(_('unable to collapse, there is more '
351 'than one external parent'))
349 'than one external parent'))
352 external = p.rev()
350 external = p.rev()
353 return external
351 return external
354
352
355 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
353 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
356 'Commit the changes and store useful information in extra'
354 'Commit the changes and store useful information in extra'
357 try:
355 try:
358 repo.setparents(repo[p1].node(), repo[p2].node())
356 repo.setparents(repo[p1].node(), repo[p2].node())
359 ctx = repo[rev]
357 ctx = repo[rev]
360 if commitmsg is None:
358 if commitmsg is None:
361 commitmsg = ctx.description()
359 commitmsg = ctx.description()
362 extra = {'rebase_source': ctx.hex()}
360 extra = {'rebase_source': ctx.hex()}
363 if extrafn:
361 if extrafn:
364 extrafn(ctx, extra)
362 extrafn(ctx, extra)
365 # Commit might fail if unresolved files exist
363 # Commit might fail if unresolved files exist
366 newrev = repo.commit(text=commitmsg, user=ctx.user(),
364 newrev = repo.commit(text=commitmsg, user=ctx.user(),
367 date=ctx.date(), extra=extra, editor=editor)
365 date=ctx.date(), extra=extra, editor=editor)
368 repo.dirstate.setbranch(repo[newrev].branch())
366 repo.dirstate.setbranch(repo[newrev].branch())
369 targetphase = max(ctx.phase(), phases.draft)
367 targetphase = max(ctx.phase(), phases.draft)
370 # retractboundary doesn't overwrite upper phase inherited from parent
368 # retractboundary doesn't overwrite upper phase inherited from parent
371 newnode = repo[newrev].node()
369 newnode = repo[newrev].node()
372 if newnode:
370 if newnode:
373 phases.retractboundary(repo, targetphase, [newnode])
371 phases.retractboundary(repo, targetphase, [newnode])
374 return newrev
372 return newrev
375 except util.Abort:
373 except util.Abort:
376 # Invalidate the previous setparents
374 # Invalidate the previous setparents
377 repo.dirstate.invalidate()
375 repo.dirstate.invalidate()
378 raise
376 raise
379
377
380 def rebasenode(repo, rev, p1, state, collapse):
378 def rebasenode(repo, rev, p1, state, collapse):
381 'Rebase a single revision'
379 'Rebase a single revision'
382 # Merge phase
380 # Merge phase
383 # Update to target and merge it with local
381 # Update to target and merge it with local
384 if repo['.'].rev() != repo[p1].rev():
382 if repo['.'].rev() != repo[p1].rev():
385 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
383 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
386 merge.update(repo, p1, False, True, False)
384 merge.update(repo, p1, False, True, False)
387 else:
385 else:
388 repo.ui.debug(" already in target\n")
386 repo.ui.debug(" already in target\n")
389 repo.dirstate.write()
387 repo.dirstate.write()
390 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
388 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
391 base = None
389 base = None
392 if repo[rev].rev() != repo[min(state)].rev():
390 if repo[rev].rev() != repo[min(state)].rev():
393 base = repo[rev].p1().node()
391 base = repo[rev].p1().node()
394 # When collapsing in-place, the parent is the common ancestor, we
392 # When collapsing in-place, the parent is the common ancestor, we
395 # have to allow merging with it.
393 # have to allow merging with it.
396 return merge.update(repo, rev, True, True, False, base, collapse)
394 return merge.update(repo, rev, True, True, False, base, collapse)
397
395
398 def defineparents(repo, rev, target, state, targetancestors):
396 def defineparents(repo, rev, target, state, targetancestors):
399 'Return the new parent relationship of the revision that will be rebased'
397 'Return the new parent relationship of the revision that will be rebased'
400 parents = repo[rev].parents()
398 parents = repo[rev].parents()
401 p1 = p2 = nullrev
399 p1 = p2 = nullrev
402
400
403 P1n = parents[0].rev()
401 P1n = parents[0].rev()
404 if P1n in targetancestors:
402 if P1n in targetancestors:
405 p1 = target
403 p1 = target
406 elif P1n in state:
404 elif P1n in state:
407 if state[P1n] == nullmerge:
405 if state[P1n] == nullmerge:
408 p1 = target
406 p1 = target
409 else:
407 else:
410 p1 = state[P1n]
408 p1 = state[P1n]
411 else: # P1n external
409 else: # P1n external
412 p1 = target
410 p1 = target
413 p2 = P1n
411 p2 = P1n
414
412
415 if len(parents) == 2 and parents[1].rev() not in targetancestors:
413 if len(parents) == 2 and parents[1].rev() not in targetancestors:
416 P2n = parents[1].rev()
414 P2n = parents[1].rev()
417 # interesting second parent
415 # interesting second parent
418 if P2n in state:
416 if P2n in state:
419 if p1 == target: # P1n in targetancestors or external
417 if p1 == target: # P1n in targetancestors or external
420 p1 = state[P2n]
418 p1 = state[P2n]
421 else:
419 else:
422 p2 = state[P2n]
420 p2 = state[P2n]
423 else: # P2n external
421 else: # P2n external
424 if p2 != nullrev: # P1n external too => rev is a merged revision
422 if p2 != nullrev: # P1n external too => rev is a merged revision
425 raise util.Abort(_('cannot use revision %d as base, result '
423 raise util.Abort(_('cannot use revision %d as base, result '
426 'would have 3 parents') % rev)
424 'would have 3 parents') % rev)
427 p2 = P2n
425 p2 = P2n
428 repo.ui.debug(" future parents are %d and %d\n" %
426 repo.ui.debug(" future parents are %d and %d\n" %
429 (repo[p1].rev(), repo[p2].rev()))
427 (repo[p1].rev(), repo[p2].rev()))
430 return p1, p2
428 return p1, p2
431
429
432 def isagitpatch(repo, patchname):
430 def isagitpatch(repo, patchname):
433 'Return true if the given patch is in git format'
431 'Return true if the given patch is in git format'
434 mqpatch = os.path.join(repo.mq.path, patchname)
432 mqpatch = os.path.join(repo.mq.path, patchname)
435 for line in patch.linereader(file(mqpatch, 'rb')):
433 for line in patch.linereader(file(mqpatch, 'rb')):
436 if line.startswith('diff --git'):
434 if line.startswith('diff --git'):
437 return True
435 return True
438 return False
436 return False
439
437
440 def updatemq(repo, state, skipped, **opts):
438 def updatemq(repo, state, skipped, **opts):
441 'Update rebased mq patches - finalize and then import them'
439 'Update rebased mq patches - finalize and then import them'
442 mqrebase = {}
440 mqrebase = {}
443 mq = repo.mq
441 mq = repo.mq
444 original_series = mq.fullseries[:]
442 original_series = mq.fullseries[:]
445 skippedpatches = set()
443 skippedpatches = set()
446
444
447 for p in mq.applied:
445 for p in mq.applied:
448 rev = repo[p.node].rev()
446 rev = repo[p.node].rev()
449 if rev in state:
447 if rev in state:
450 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
448 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
451 (rev, p.name))
449 (rev, p.name))
452 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
450 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
453 else:
451 else:
454 # Applied but not rebased, not sure this should happen
452 # Applied but not rebased, not sure this should happen
455 skippedpatches.add(p.name)
453 skippedpatches.add(p.name)
456
454
457 if mqrebase:
455 if mqrebase:
458 mq.finish(repo, mqrebase.keys())
456 mq.finish(repo, mqrebase.keys())
459
457
460 # We must start import from the newest revision
458 # We must start import from the newest revision
461 for rev in sorted(mqrebase, reverse=True):
459 for rev in sorted(mqrebase, reverse=True):
462 if rev not in skipped:
460 if rev not in skipped:
463 name, isgit = mqrebase[rev]
461 name, isgit = mqrebase[rev]
464 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
462 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
465 mq.qimport(repo, (), patchname=name, git=isgit,
463 mq.qimport(repo, (), patchname=name, git=isgit,
466 rev=[str(state[rev])])
464 rev=[str(state[rev])])
467 else:
465 else:
468 # Rebased and skipped
466 # Rebased and skipped
469 skippedpatches.add(mqrebase[rev][0])
467 skippedpatches.add(mqrebase[rev][0])
470
468
471 # Patches were either applied and rebased and imported in
469 # Patches were either applied and rebased and imported in
472 # order, applied and removed or unapplied. Discard the removed
470 # order, applied and removed or unapplied. Discard the removed
473 # ones while preserving the original series order and guards.
471 # ones while preserving the original series order and guards.
474 newseries = [s for s in original_series
472 newseries = [s for s in original_series
475 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
473 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
476 mq.fullseries[:] = newseries
474 mq.fullseries[:] = newseries
477 mq.seriesdirty = True
475 mq.seriesdirty = True
478 mq.savedirty()
476 mq.savedirty()
479
477
480 def updatebookmarks(repo, nstate, originalbookmarks, **opts):
478 def updatebookmarks(repo, nstate, originalbookmarks, **opts):
481 'Move bookmarks to their correct changesets'
479 'Move bookmarks to their correct changesets'
482 marks = repo._bookmarks
480 marks = repo._bookmarks
483 for k, v in originalbookmarks.iteritems():
481 for k, v in originalbookmarks.iteritems():
484 if v in nstate:
482 if v in nstate:
485 if nstate[v] != nullmerge:
483 if nstate[v] != nullmerge:
486 # update the bookmarks for revs that have moved
484 # update the bookmarks for revs that have moved
487 marks[k] = nstate[v]
485 marks[k] = nstate[v]
488
486
489 marks.write()
487 marks.write()
490
488
491 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
489 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
492 external):
490 external):
493 'Store the current status to allow recovery'
491 'Store the current status to allow recovery'
494 f = repo.opener("rebasestate", "w")
492 f = repo.opener("rebasestate", "w")
495 f.write(repo[originalwd].hex() + '\n')
493 f.write(repo[originalwd].hex() + '\n')
496 f.write(repo[target].hex() + '\n')
494 f.write(repo[target].hex() + '\n')
497 f.write(repo[external].hex() + '\n')
495 f.write(repo[external].hex() + '\n')
498 f.write('%d\n' % int(collapse))
496 f.write('%d\n' % int(collapse))
499 f.write('%d\n' % int(keep))
497 f.write('%d\n' % int(keep))
500 f.write('%d\n' % int(keepbranches))
498 f.write('%d\n' % int(keepbranches))
501 for d, v in state.iteritems():
499 for d, v in state.iteritems():
502 oldrev = repo[d].hex()
500 oldrev = repo[d].hex()
503 if v != nullmerge:
501 if v != nullmerge:
504 newrev = repo[v].hex()
502 newrev = repo[v].hex()
505 else:
503 else:
506 newrev = v
504 newrev = v
507 f.write("%s:%s\n" % (oldrev, newrev))
505 f.write("%s:%s\n" % (oldrev, newrev))
508 f.close()
506 f.close()
509 repo.ui.debug('rebase status stored\n')
507 repo.ui.debug('rebase status stored\n')
510
508
511 def clearstatus(repo):
509 def clearstatus(repo):
512 'Remove the status files'
510 'Remove the status files'
513 if os.path.exists(repo.join("rebasestate")):
511 if os.path.exists(repo.join("rebasestate")):
514 util.unlinkpath(repo.join("rebasestate"))
512 util.unlinkpath(repo.join("rebasestate"))
515
513
516 def restorestatus(repo):
514 def restorestatus(repo):
517 'Restore a previously stored status'
515 'Restore a previously stored status'
518 try:
516 try:
519 target = None
517 target = None
520 collapse = False
518 collapse = False
521 external = nullrev
519 external = nullrev
522 state = {}
520 state = {}
523 f = repo.opener("rebasestate")
521 f = repo.opener("rebasestate")
524 for i, l in enumerate(f.read().splitlines()):
522 for i, l in enumerate(f.read().splitlines()):
525 if i == 0:
523 if i == 0:
526 originalwd = repo[l].rev()
524 originalwd = repo[l].rev()
527 elif i == 1:
525 elif i == 1:
528 target = repo[l].rev()
526 target = repo[l].rev()
529 elif i == 2:
527 elif i == 2:
530 external = repo[l].rev()
528 external = repo[l].rev()
531 elif i == 3:
529 elif i == 3:
532 collapse = bool(int(l))
530 collapse = bool(int(l))
533 elif i == 4:
531 elif i == 4:
534 keep = bool(int(l))
532 keep = bool(int(l))
535 elif i == 5:
533 elif i == 5:
536 keepbranches = bool(int(l))
534 keepbranches = bool(int(l))
537 else:
535 else:
538 oldrev, newrev = l.split(':')
536 oldrev, newrev = l.split(':')
539 if newrev != str(nullmerge):
537 if newrev != str(nullmerge):
540 state[repo[oldrev].rev()] = repo[newrev].rev()
538 state[repo[oldrev].rev()] = repo[newrev].rev()
541 else:
539 else:
542 state[repo[oldrev].rev()] = int(newrev)
540 state[repo[oldrev].rev()] = int(newrev)
543 skipped = set()
541 skipped = set()
544 # recompute the set of skipped revs
542 # recompute the set of skipped revs
545 if not collapse:
543 if not collapse:
546 seen = set([target])
544 seen = set([target])
547 for old, new in sorted(state.items()):
545 for old, new in sorted(state.items()):
548 if new != nullrev and new in seen:
546 if new != nullrev and new in seen:
549 skipped.add(old)
547 skipped.add(old)
550 seen.add(new)
548 seen.add(new)
551 repo.ui.debug('computed skipped revs: %s\n' % skipped)
549 repo.ui.debug('computed skipped revs: %s\n' % skipped)
552 repo.ui.debug('rebase status resumed\n')
550 repo.ui.debug('rebase status resumed\n')
553 return (originalwd, target, state, skipped,
551 return (originalwd, target, state, skipped,
554 collapse, keep, keepbranches, external)
552 collapse, keep, keepbranches, external)
555 except IOError, err:
553 except IOError, err:
556 if err.errno != errno.ENOENT:
554 if err.errno != errno.ENOENT:
557 raise
555 raise
558 raise util.Abort(_('no rebase in progress'))
556 raise util.Abort(_('no rebase in progress'))
559
557
560 def abort(repo, originalwd, target, state):
558 def abort(repo, originalwd, target, state):
561 'Restore the repository to its original state'
559 'Restore the repository to its original state'
562 dstates = [s for s in state.values() if s != nullrev]
560 dstates = [s for s in state.values() if s != nullrev]
563 immutable = [d for d in dstates if not repo[d].mutable()]
561 immutable = [d for d in dstates if not repo[d].mutable()]
564 if immutable:
562 if immutable:
565 raise util.Abort(_("can't abort rebase due to immutable changesets %s")
563 raise util.Abort(_("can't abort rebase due to immutable changesets %s")
566 % ', '.join(str(repo[r]) for r in immutable),
564 % ', '.join(str(repo[r]) for r in immutable),
567 hint=_('see hg help phases for details'))
565 hint=_('see hg help phases for details'))
568
566
569 descendants = set()
567 descendants = set()
570 if dstates:
568 if dstates:
571 descendants = set(repo.changelog.descendants(dstates))
569 descendants = set(repo.changelog.descendants(dstates))
572 if descendants - set(dstates):
570 if descendants - set(dstates):
573 repo.ui.warn(_("warning: new changesets detected on target branch, "
571 repo.ui.warn(_("warning: new changesets detected on target branch, "
574 "can't abort\n"))
572 "can't abort\n"))
575 return -1
573 return -1
576 else:
574 else:
577 # Strip from the first rebased revision
575 # Strip from the first rebased revision
578 merge.update(repo, repo[originalwd].rev(), False, True, False)
576 merge.update(repo, repo[originalwd].rev(), False, True, False)
579 rebased = filter(lambda x: x > -1 and x != target, state.values())
577 rebased = filter(lambda x: x > -1 and x != target, state.values())
580 if rebased:
578 if rebased:
581 strippoint = min(rebased)
579 strippoint = min(rebased)
582 # no backup of rebased cset versions needed
580 # no backup of rebased cset versions needed
583 repair.strip(repo.ui, repo, repo[strippoint].node())
581 repair.strip(repo.ui, repo, repo[strippoint].node())
584 clearstatus(repo)
582 clearstatus(repo)
585 repo.ui.warn(_('rebase aborted\n'))
583 repo.ui.warn(_('rebase aborted\n'))
586 return 0
584 return 0
587
585
588 def buildstate(repo, dest, rebaseset, collapse):
586 def buildstate(repo, dest, rebaseset, collapse):
589 '''Define which revisions are going to be rebased and where
587 '''Define which revisions are going to be rebased and where
590
588
591 repo: repo
589 repo: repo
592 dest: context
590 dest: context
593 rebaseset: set of rev
591 rebaseset: set of rev
594 '''
592 '''
595
593
596 # This check isn't strictly necessary, since mq detects commits over an
594 # This check isn't strictly necessary, since mq detects commits over an
597 # applied patch. But it prevents messing up the working directory when
595 # applied patch. But it prevents messing up the working directory when
598 # a partially completed rebase is blocked by mq.
596 # a partially completed rebase is blocked by mq.
599 if 'qtip' in repo.tags() and (dest.node() in
597 if 'qtip' in repo.tags() and (dest.node() in
600 [s.node for s in repo.mq.applied]):
598 [s.node for s in repo.mq.applied]):
601 raise util.Abort(_('cannot rebase onto an applied mq patch'))
599 raise util.Abort(_('cannot rebase onto an applied mq patch'))
602
600
603 roots = list(repo.set('roots(%ld)', rebaseset))
601 roots = list(repo.set('roots(%ld)', rebaseset))
604 if not roots:
602 if not roots:
605 raise util.Abort(_('no matching revisions'))
603 raise util.Abort(_('no matching revisions'))
606 if len(roots) > 1:
604 if len(roots) > 1:
607 raise util.Abort(_("can't rebase multiple roots"))
605 raise util.Abort(_("can't rebase multiple roots"))
608 root = roots[0]
606 root = roots[0]
609
607
610 commonbase = root.ancestor(dest)
608 commonbase = root.ancestor(dest)
611 if commonbase == root:
609 if commonbase == root:
612 raise util.Abort(_('source is ancestor of destination'))
610 raise util.Abort(_('source is ancestor of destination'))
613 if commonbase == dest:
611 if commonbase == dest:
614 samebranch = root.branch() == dest.branch()
612 samebranch = root.branch() == dest.branch()
615 if not collapse and samebranch and root in dest.children():
613 if not collapse and samebranch and root in dest.children():
616 repo.ui.debug('source is a child of destination\n')
614 repo.ui.debug('source is a child of destination\n')
617 return None
615 return None
618
616
619 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root))
617 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root))
620 state = dict.fromkeys(rebaseset, nullrev)
618 state = dict.fromkeys(rebaseset, nullrev)
621 # Rebase tries to turn <dest> into a parent of <root> while
619 # Rebase tries to turn <dest> into a parent of <root> while
622 # preserving the number of parents of rebased changesets:
620 # preserving the number of parents of rebased changesets:
623 #
621 #
624 # - A changeset with a single parent will always be rebased as a
622 # - A changeset with a single parent will always be rebased as a
625 # changeset with a single parent.
623 # changeset with a single parent.
626 #
624 #
627 # - A merge will be rebased as merge unless its parents are both
625 # - A merge will be rebased as merge unless its parents are both
628 # ancestors of <dest> or are themselves in the rebased set and
626 # ancestors of <dest> or are themselves in the rebased set and
629 # pruned while rebased.
627 # pruned while rebased.
630 #
628 #
631 # If one parent of <root> is an ancestor of <dest>, the rebased
629 # If one parent of <root> is an ancestor of <dest>, the rebased
632 # version of this parent will be <dest>. This is always true with
630 # version of this parent will be <dest>. This is always true with
633 # --base option.
631 # --base option.
634 #
632 #
635 # Otherwise, we need to *replace* the original parents with
633 # Otherwise, we need to *replace* the original parents with
636 # <dest>. This "detaches" the rebased set from its former location
634 # <dest>. This "detaches" the rebased set from its former location
637 # and rebases it onto <dest>. Changes introduced by ancestors of
635 # and rebases it onto <dest>. Changes introduced by ancestors of
638 # <root> not common with <dest> (the detachset, marked as
636 # <root> not common with <dest> (the detachset, marked as
639 # nullmerge) are "removed" from the rebased changesets.
637 # nullmerge) are "removed" from the rebased changesets.
640 #
638 #
641 # - If <root> has a single parent, set it to <dest>.
639 # - If <root> has a single parent, set it to <dest>.
642 #
640 #
643 # - If <root> is a merge, we cannot decide which parent to
641 # - If <root> is a merge, we cannot decide which parent to
644 # replace, the rebase operation is not clearly defined.
642 # replace, the rebase operation is not clearly defined.
645 #
643 #
646 # The table below sums up this behavior:
644 # The table below sums up this behavior:
647 #
645 #
648 # +--------------------+----------------------+-------------------------+
646 # +--------------------+----------------------+-------------------------+
649 # | | one parent | merge |
647 # | | one parent | merge |
650 # +--------------------+----------------------+-------------------------+
648 # +--------------------+----------------------+-------------------------+
651 # | parent in ::<dest> | new parent is <dest> | parents in ::<dest> are |
649 # | parent in ::<dest> | new parent is <dest> | parents in ::<dest> are |
652 # | | | remapped to <dest> |
650 # | | | remapped to <dest> |
653 # +--------------------+----------------------+-------------------------+
651 # +--------------------+----------------------+-------------------------+
654 # | unrelated source | new parent is <dest> | ambiguous, abort |
652 # | unrelated source | new parent is <dest> | ambiguous, abort |
655 # +--------------------+----------------------+-------------------------+
653 # +--------------------+----------------------+-------------------------+
656 #
654 #
657 # The actual abort is handled by `defineparents`
655 # The actual abort is handled by `defineparents`
658 if len(root.parents()) <= 1:
656 if len(root.parents()) <= 1:
659 # ancestors of <root> not ancestors of <dest>
657 # ancestors of <root> not ancestors of <dest>
660 detachset = repo.changelog.findmissingrevs([commonbase.rev()],
658 detachset = repo.changelog.findmissingrevs([commonbase.rev()],
661 [root.rev()])
659 [root.rev()])
662 state.update(dict.fromkeys(detachset, nullmerge))
660 state.update(dict.fromkeys(detachset, nullmerge))
663 # detachset can have root, and we definitely want to rebase that
661 # detachset can have root, and we definitely want to rebase that
664 state[root.rev()] = nullrev
662 state[root.rev()] = nullrev
665 return repo['.'].rev(), dest.rev(), state
663 return repo['.'].rev(), dest.rev(), state
666
664
667 def clearrebased(ui, repo, state, collapsedas=None):
665 def clearrebased(ui, repo, state, collapsedas=None):
668 """dispose of rebased revision at the end of the rebase
666 """dispose of rebased revision at the end of the rebase
669
667
670 If `collapsedas` is not None, the rebase was a collapse whose result if the
668 If `collapsedas` is not None, the rebase was a collapse whose result if the
671 `collapsedas` node."""
669 `collapsedas` node."""
672 if obsolete._enabled:
670 if obsolete._enabled:
673 markers = []
671 markers = []
674 for rev, newrev in sorted(state.items()):
672 for rev, newrev in sorted(state.items()):
675 if newrev >= 0:
673 if newrev >= 0:
676 if collapsedas is not None:
674 if collapsedas is not None:
677 newrev = collapsedas
675 newrev = collapsedas
678 markers.append((repo[rev], (repo[newrev],)))
676 markers.append((repo[rev], (repo[newrev],)))
679 if markers:
677 if markers:
680 obsolete.createmarkers(repo, markers)
678 obsolete.createmarkers(repo, markers)
681 else:
679 else:
682 rebased = [rev for rev in state if state[rev] != nullmerge]
680 rebased = [rev for rev in state if state[rev] != nullmerge]
683 if rebased:
681 if rebased:
684 if set(repo.changelog.descendants([min(rebased)])) - set(state):
682 if set(repo.changelog.descendants([min(rebased)])) - set(state):
685 ui.warn(_("warning: new changesets detected "
683 ui.warn(_("warning: new changesets detected "
686 "on source branch, not stripping\n"))
684 "on source branch, not stripping\n"))
687 else:
685 else:
688 # backup the old csets by default
686 # backup the old csets by default
689 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
687 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
690
688
691
689
692 def pullrebase(orig, ui, repo, *args, **opts):
690 def pullrebase(orig, ui, repo, *args, **opts):
693 'Call rebase after pull if the latter has been invoked with --rebase'
691 'Call rebase after pull if the latter has been invoked with --rebase'
694 if opts.get('rebase'):
692 if opts.get('rebase'):
695 if opts.get('update'):
693 if opts.get('update'):
696 del opts['update']
694 del opts['update']
697 ui.debug('--update and --rebase are not compatible, ignoring '
695 ui.debug('--update and --rebase are not compatible, ignoring '
698 'the update flag\n')
696 'the update flag\n')
699
697
700 movemarkfrom = repo['.'].node()
698 movemarkfrom = repo['.'].node()
701 cmdutil.bailifchanged(repo)
699 cmdutil.bailifchanged(repo)
702 revsprepull = len(repo)
700 revsprepull = len(repo)
703 origpostincoming = commands.postincoming
701 origpostincoming = commands.postincoming
704 def _dummy(*args, **kwargs):
702 def _dummy(*args, **kwargs):
705 pass
703 pass
706 commands.postincoming = _dummy
704 commands.postincoming = _dummy
707 try:
705 try:
708 orig(ui, repo, *args, **opts)
706 orig(ui, repo, *args, **opts)
709 finally:
707 finally:
710 commands.postincoming = origpostincoming
708 commands.postincoming = origpostincoming
711 revspostpull = len(repo)
709 revspostpull = len(repo)
712 if revspostpull > revsprepull:
710 if revspostpull > revsprepull:
713 # --rev option from pull conflict with rebase own --rev
711 # --rev option from pull conflict with rebase own --rev
714 # dropping it
712 # dropping it
715 if 'rev' in opts:
713 if 'rev' in opts:
716 del opts['rev']
714 del opts['rev']
717 rebase(ui, repo, **opts)
715 rebase(ui, repo, **opts)
718 branch = repo[None].branch()
716 branch = repo[None].branch()
719 dest = repo[branch].rev()
717 dest = repo[branch].rev()
720 if dest != repo['.'].rev():
718 if dest != repo['.'].rev():
721 # there was nothing to rebase we force an update
719 # there was nothing to rebase we force an update
722 hg.update(repo, dest)
720 hg.update(repo, dest)
723 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
721 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
724 ui.status(_("updating bookmark %s\n")
722 ui.status(_("updating bookmark %s\n")
725 % repo._bookmarkcurrent)
723 % repo._bookmarkcurrent)
726 else:
724 else:
727 if opts.get('tool'):
725 if opts.get('tool'):
728 raise util.Abort(_('--tool can only be used with --rebase'))
726 raise util.Abort(_('--tool can only be used with --rebase'))
729 orig(ui, repo, *args, **opts)
727 orig(ui, repo, *args, **opts)
730
728
731 def uisetup(ui):
729 def uisetup(ui):
732 'Replace pull with a decorator to provide --rebase option'
730 'Replace pull with a decorator to provide --rebase option'
733 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
731 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
734 entry[1].append(('', 'rebase', None,
732 entry[1].append(('', 'rebase', None,
735 _("rebase working directory to branch head")))
733 _("rebase working directory to branch head")))
736 entry[1].append(('t', 'tool', '',
734 entry[1].append(('t', 'tool', '',
737 _("specify merge tool for rebase")))
735 _("specify merge tool for rebase")))
General Comments 0
You need to be logged in to leave comments. Login now