##// END OF EJS Templates
rebase: remove useless list around repo.revs...
Pierre-Yves David -
r17614:d417986e default
parent child Browse files
Show More
@@ -1,728 +1,728 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
187
188 if rebaseset:
188 if rebaseset:
189 root = min(rebaseset)
189 root = min(rebaseset)
190 else:
190 else:
191 root = None
191 root = None
192
192
193 if not rebaseset:
193 if not rebaseset:
194 repo.ui.debug('base is ancestor of destination\n')
194 repo.ui.debug('base is ancestor of destination\n')
195 result = None
195 result = None
196 elif not keepf and list(repo.revs('first(children(%ld) - %ld)',
196 elif not keepf and repo.revs('first(children(%ld) - %ld)',
197 rebaseset, rebaseset)):
197 rebaseset, rebaseset):
198 raise util.Abort(
198 raise util.Abort(
199 _("can't remove original changesets with"
199 _("can't remove original changesets with"
200 " unrebased descendants"),
200 " unrebased descendants"),
201 hint=_('use --keep to keep original changesets'))
201 hint=_('use --keep to keep original changesets'))
202 elif not keepf and not repo[root].mutable():
202 elif not keepf and not repo[root].mutable():
203 raise util.Abort(_("can't rebase immutable changeset %s")
203 raise util.Abort(_("can't rebase immutable changeset %s")
204 % repo[root],
204 % repo[root],
205 hint=_('see hg help phases for details'))
205 hint=_('see hg help phases for details'))
206 else:
206 else:
207 result = buildstate(repo, dest, rebaseset, collapsef)
207 result = buildstate(repo, dest, rebaseset, collapsef)
208
208
209 if not result:
209 if not result:
210 # Empty state built, nothing to rebase
210 # Empty state built, nothing to rebase
211 ui.status(_('nothing to rebase\n'))
211 ui.status(_('nothing to rebase\n'))
212 return 1
212 return 1
213 else:
213 else:
214 originalwd, target, state = result
214 originalwd, target, state = result
215 if collapsef:
215 if collapsef:
216 targetancestors = set(repo.changelog.ancestors([target]))
216 targetancestors = set(repo.changelog.ancestors([target]))
217 targetancestors.add(target)
217 targetancestors.add(target)
218 external = checkexternal(repo, state, targetancestors)
218 external = checkexternal(repo, state, targetancestors)
219
219
220 if keepbranchesf:
220 if keepbranchesf:
221 assert not extrafn, 'cannot use both keepbranches and extrafn'
221 assert not extrafn, 'cannot use both keepbranches and extrafn'
222 def extrafn(ctx, extra):
222 def extrafn(ctx, extra):
223 extra['branch'] = ctx.branch()
223 extra['branch'] = ctx.branch()
224 if collapsef:
224 if collapsef:
225 branches = set()
225 branches = set()
226 for rev in state:
226 for rev in state:
227 branches.add(repo[rev].branch())
227 branches.add(repo[rev].branch())
228 if len(branches) > 1:
228 if len(branches) > 1:
229 raise util.Abort(_('cannot collapse multiple named '
229 raise util.Abort(_('cannot collapse multiple named '
230 'branches'))
230 'branches'))
231
231
232
232
233 # Rebase
233 # Rebase
234 if not targetancestors:
234 if not targetancestors:
235 targetancestors = set(repo.changelog.ancestors([target]))
235 targetancestors = set(repo.changelog.ancestors([target]))
236 targetancestors.add(target)
236 targetancestors.add(target)
237
237
238 # Keep track of the current bookmarks in order to reset them later
238 # Keep track of the current bookmarks in order to reset them later
239 currentbookmarks = repo._bookmarks.copy()
239 currentbookmarks = repo._bookmarks.copy()
240 activebookmark = repo._bookmarkcurrent
240 activebookmark = repo._bookmarkcurrent
241 if activebookmark:
241 if activebookmark:
242 bookmarks.unsetcurrent(repo)
242 bookmarks.unsetcurrent(repo)
243
243
244 sortedstate = sorted(state)
244 sortedstate = sorted(state)
245 total = len(sortedstate)
245 total = len(sortedstate)
246 pos = 0
246 pos = 0
247 for rev in sortedstate:
247 for rev in sortedstate:
248 pos += 1
248 pos += 1
249 if state[rev] == -1:
249 if state[rev] == -1:
250 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
250 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
251 _('changesets'), total)
251 _('changesets'), total)
252 storestatus(repo, originalwd, target, state, collapsef, keepf,
252 storestatus(repo, originalwd, target, state, collapsef, keepf,
253 keepbranchesf, external)
253 keepbranchesf, external)
254 p1, p2 = defineparents(repo, rev, target, state,
254 p1, p2 = defineparents(repo, rev, target, state,
255 targetancestors)
255 targetancestors)
256 if len(repo.parents()) == 2:
256 if len(repo.parents()) == 2:
257 repo.ui.debug('resuming interrupted rebase\n')
257 repo.ui.debug('resuming interrupted rebase\n')
258 else:
258 else:
259 try:
259 try:
260 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
260 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
261 stats = rebasenode(repo, rev, p1, state, collapsef)
261 stats = rebasenode(repo, rev, p1, state, collapsef)
262 if stats and stats[3] > 0:
262 if stats and stats[3] > 0:
263 raise util.Abort(_('unresolved conflicts (see hg '
263 raise util.Abort(_('unresolved conflicts (see hg '
264 'resolve, then hg rebase --continue)'))
264 'resolve, then hg rebase --continue)'))
265 finally:
265 finally:
266 ui.setconfig('ui', 'forcemerge', '')
266 ui.setconfig('ui', 'forcemerge', '')
267 cmdutil.duplicatecopies(repo, rev, target)
267 cmdutil.duplicatecopies(repo, rev, target)
268 if not collapsef:
268 if not collapsef:
269 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
269 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
270 editor=editor)
270 editor=editor)
271 else:
271 else:
272 # Skip commit if we are collapsing
272 # Skip commit if we are collapsing
273 repo.setparents(repo[p1].node())
273 repo.setparents(repo[p1].node())
274 newrev = None
274 newrev = None
275 # Update the state
275 # Update the state
276 if newrev is not None:
276 if newrev is not None:
277 state[rev] = repo[newrev].rev()
277 state[rev] = repo[newrev].rev()
278 else:
278 else:
279 if not collapsef:
279 if not collapsef:
280 ui.note(_('no changes, revision %d skipped\n') % rev)
280 ui.note(_('no changes, revision %d skipped\n') % rev)
281 ui.debug('next revision set to %s\n' % p1)
281 ui.debug('next revision set to %s\n' % p1)
282 skipped.add(rev)
282 skipped.add(rev)
283 state[rev] = p1
283 state[rev] = p1
284
284
285 ui.progress(_('rebasing'), None)
285 ui.progress(_('rebasing'), None)
286 ui.note(_('rebase merging completed\n'))
286 ui.note(_('rebase merging completed\n'))
287
287
288 if collapsef and not keepopen:
288 if collapsef and not keepopen:
289 p1, p2 = defineparents(repo, min(state), target,
289 p1, p2 = defineparents(repo, min(state), target,
290 state, targetancestors)
290 state, targetancestors)
291 if collapsemsg:
291 if collapsemsg:
292 commitmsg = collapsemsg
292 commitmsg = collapsemsg
293 else:
293 else:
294 commitmsg = 'Collapsed revision'
294 commitmsg = 'Collapsed revision'
295 for rebased in state:
295 for rebased in state:
296 if rebased not in skipped and state[rebased] != nullmerge:
296 if rebased not in skipped and state[rebased] != nullmerge:
297 commitmsg += '\n* %s' % repo[rebased].description()
297 commitmsg += '\n* %s' % repo[rebased].description()
298 commitmsg = ui.edit(commitmsg, repo.ui.username())
298 commitmsg = ui.edit(commitmsg, repo.ui.username())
299 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
299 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
300 extrafn=extrafn, editor=editor)
300 extrafn=extrafn, editor=editor)
301
301
302 if 'qtip' in repo.tags():
302 if 'qtip' in repo.tags():
303 updatemq(repo, state, skipped, **opts)
303 updatemq(repo, state, skipped, **opts)
304
304
305 if currentbookmarks:
305 if currentbookmarks:
306 # Nodeids are needed to reset bookmarks
306 # Nodeids are needed to reset bookmarks
307 nstate = {}
307 nstate = {}
308 for k, v in state.iteritems():
308 for k, v in state.iteritems():
309 if v != nullmerge:
309 if v != nullmerge:
310 nstate[repo[k].node()] = repo[v].node()
310 nstate[repo[k].node()] = repo[v].node()
311
311
312 if not keepf:
312 if not keepf:
313 collapsedas = None
313 collapsedas = None
314 if collapsef:
314 if collapsef:
315 collapsedas = newrev
315 collapsedas = newrev
316 clearrebased(ui, repo, state, collapsedas)
316 clearrebased(ui, repo, state, collapsedas)
317
317
318 if currentbookmarks:
318 if currentbookmarks:
319 updatebookmarks(repo, nstate, currentbookmarks, **opts)
319 updatebookmarks(repo, nstate, currentbookmarks, **opts)
320
320
321 clearstatus(repo)
321 clearstatus(repo)
322 ui.note(_("rebase completed\n"))
322 ui.note(_("rebase completed\n"))
323 if os.path.exists(repo.sjoin('undo')):
323 if os.path.exists(repo.sjoin('undo')):
324 util.unlinkpath(repo.sjoin('undo'))
324 util.unlinkpath(repo.sjoin('undo'))
325 if skipped:
325 if skipped:
326 ui.note(_("%d revisions have been skipped\n") % len(skipped))
326 ui.note(_("%d revisions have been skipped\n") % len(skipped))
327
327
328 if (activebookmark and
328 if (activebookmark and
329 repo['tip'].node() == repo._bookmarks[activebookmark]):
329 repo['tip'].node() == repo._bookmarks[activebookmark]):
330 bookmarks.setcurrent(repo, activebookmark)
330 bookmarks.setcurrent(repo, activebookmark)
331
331
332 finally:
332 finally:
333 release(lock, wlock)
333 release(lock, wlock)
334
334
335 def checkexternal(repo, state, targetancestors):
335 def checkexternal(repo, state, targetancestors):
336 """Check whether one or more external revisions need to be taken in
336 """Check whether one or more external revisions need to be taken in
337 consideration. In the latter case, abort.
337 consideration. In the latter case, abort.
338 """
338 """
339 external = nullrev
339 external = nullrev
340 source = min(state)
340 source = min(state)
341 for rev in state:
341 for rev in state:
342 if rev == source:
342 if rev == source:
343 continue
343 continue
344 # Check externals and fail if there are more than one
344 # Check externals and fail if there are more than one
345 for p in repo[rev].parents():
345 for p in repo[rev].parents():
346 if (p.rev() not in state
346 if (p.rev() not in state
347 and p.rev() not in targetancestors):
347 and p.rev() not in targetancestors):
348 if external != nullrev:
348 if external != nullrev:
349 raise util.Abort(_('unable to collapse, there is more '
349 raise util.Abort(_('unable to collapse, there is more '
350 'than one external parent'))
350 'than one external parent'))
351 external = p.rev()
351 external = p.rev()
352 return external
352 return external
353
353
354 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
354 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
355 'Commit the changes and store useful information in extra'
355 'Commit the changes and store useful information in extra'
356 try:
356 try:
357 repo.setparents(repo[p1].node(), repo[p2].node())
357 repo.setparents(repo[p1].node(), repo[p2].node())
358 ctx = repo[rev]
358 ctx = repo[rev]
359 if commitmsg is None:
359 if commitmsg is None:
360 commitmsg = ctx.description()
360 commitmsg = ctx.description()
361 extra = {'rebase_source': ctx.hex()}
361 extra = {'rebase_source': ctx.hex()}
362 if extrafn:
362 if extrafn:
363 extrafn(ctx, extra)
363 extrafn(ctx, extra)
364 # Commit might fail if unresolved files exist
364 # Commit might fail if unresolved files exist
365 newrev = repo.commit(text=commitmsg, user=ctx.user(),
365 newrev = repo.commit(text=commitmsg, user=ctx.user(),
366 date=ctx.date(), extra=extra, editor=editor)
366 date=ctx.date(), extra=extra, editor=editor)
367 repo.dirstate.setbranch(repo[newrev].branch())
367 repo.dirstate.setbranch(repo[newrev].branch())
368 targetphase = max(ctx.phase(), phases.draft)
368 targetphase = max(ctx.phase(), phases.draft)
369 # retractboundary doesn't overwrite upper phase inherited from parent
369 # retractboundary doesn't overwrite upper phase inherited from parent
370 newnode = repo[newrev].node()
370 newnode = repo[newrev].node()
371 if newnode:
371 if newnode:
372 phases.retractboundary(repo, targetphase, [newnode])
372 phases.retractboundary(repo, targetphase, [newnode])
373 return newrev
373 return newrev
374 except util.Abort:
374 except util.Abort:
375 # Invalidate the previous setparents
375 # Invalidate the previous setparents
376 repo.dirstate.invalidate()
376 repo.dirstate.invalidate()
377 raise
377 raise
378
378
379 def rebasenode(repo, rev, p1, state, collapse):
379 def rebasenode(repo, rev, p1, state, collapse):
380 'Rebase a single revision'
380 'Rebase a single revision'
381 # Merge phase
381 # Merge phase
382 # Update to target and merge it with local
382 # Update to target and merge it with local
383 if repo['.'].rev() != repo[p1].rev():
383 if repo['.'].rev() != repo[p1].rev():
384 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
384 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
385 merge.update(repo, p1, False, True, False)
385 merge.update(repo, p1, False, True, False)
386 else:
386 else:
387 repo.ui.debug(" already in target\n")
387 repo.ui.debug(" already in target\n")
388 repo.dirstate.write()
388 repo.dirstate.write()
389 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
389 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
390 base = None
390 base = None
391 if repo[rev].rev() != repo[min(state)].rev():
391 if repo[rev].rev() != repo[min(state)].rev():
392 base = repo[rev].p1().node()
392 base = repo[rev].p1().node()
393 # When collapsing in-place, the parent is the common ancestor, we
393 # When collapsing in-place, the parent is the common ancestor, we
394 # have to allow merging with it.
394 # have to allow merging with it.
395 return merge.update(repo, rev, True, True, False, base, collapse)
395 return merge.update(repo, rev, True, True, False, base, collapse)
396
396
397 def defineparents(repo, rev, target, state, targetancestors):
397 def defineparents(repo, rev, target, state, targetancestors):
398 'Return the new parent relationship of the revision that will be rebased'
398 'Return the new parent relationship of the revision that will be rebased'
399 parents = repo[rev].parents()
399 parents = repo[rev].parents()
400 p1 = p2 = nullrev
400 p1 = p2 = nullrev
401
401
402 P1n = parents[0].rev()
402 P1n = parents[0].rev()
403 if P1n in targetancestors:
403 if P1n in targetancestors:
404 p1 = target
404 p1 = target
405 elif P1n in state:
405 elif P1n in state:
406 if state[P1n] == nullmerge:
406 if state[P1n] == nullmerge:
407 p1 = target
407 p1 = target
408 else:
408 else:
409 p1 = state[P1n]
409 p1 = state[P1n]
410 else: # P1n external
410 else: # P1n external
411 p1 = target
411 p1 = target
412 p2 = P1n
412 p2 = P1n
413
413
414 if len(parents) == 2 and parents[1].rev() not in targetancestors:
414 if len(parents) == 2 and parents[1].rev() not in targetancestors:
415 P2n = parents[1].rev()
415 P2n = parents[1].rev()
416 # interesting second parent
416 # interesting second parent
417 if P2n in state:
417 if P2n in state:
418 if p1 == target: # P1n in targetancestors or external
418 if p1 == target: # P1n in targetancestors or external
419 p1 = state[P2n]
419 p1 = state[P2n]
420 else:
420 else:
421 p2 = state[P2n]
421 p2 = state[P2n]
422 else: # P2n external
422 else: # P2n external
423 if p2 != nullrev: # P1n external too => rev is a merged revision
423 if p2 != nullrev: # P1n external too => rev is a merged revision
424 raise util.Abort(_('cannot use revision %d as base, result '
424 raise util.Abort(_('cannot use revision %d as base, result '
425 'would have 3 parents') % rev)
425 'would have 3 parents') % rev)
426 p2 = P2n
426 p2 = P2n
427 repo.ui.debug(" future parents are %d and %d\n" %
427 repo.ui.debug(" future parents are %d and %d\n" %
428 (repo[p1].rev(), repo[p2].rev()))
428 (repo[p1].rev(), repo[p2].rev()))
429 return p1, p2
429 return p1, p2
430
430
431 def isagitpatch(repo, patchname):
431 def isagitpatch(repo, patchname):
432 'Return true if the given patch is in git format'
432 'Return true if the given patch is in git format'
433 mqpatch = os.path.join(repo.mq.path, patchname)
433 mqpatch = os.path.join(repo.mq.path, patchname)
434 for line in patch.linereader(file(mqpatch, 'rb')):
434 for line in patch.linereader(file(mqpatch, 'rb')):
435 if line.startswith('diff --git'):
435 if line.startswith('diff --git'):
436 return True
436 return True
437 return False
437 return False
438
438
439 def updatemq(repo, state, skipped, **opts):
439 def updatemq(repo, state, skipped, **opts):
440 'Update rebased mq patches - finalize and then import them'
440 'Update rebased mq patches - finalize and then import them'
441 mqrebase = {}
441 mqrebase = {}
442 mq = repo.mq
442 mq = repo.mq
443 original_series = mq.fullseries[:]
443 original_series = mq.fullseries[:]
444 skippedpatches = set()
444 skippedpatches = set()
445
445
446 for p in mq.applied:
446 for p in mq.applied:
447 rev = repo[p.node].rev()
447 rev = repo[p.node].rev()
448 if rev in state:
448 if rev in state:
449 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
449 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
450 (rev, p.name))
450 (rev, p.name))
451 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
451 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
452 else:
452 else:
453 # Applied but not rebased, not sure this should happen
453 # Applied but not rebased, not sure this should happen
454 skippedpatches.add(p.name)
454 skippedpatches.add(p.name)
455
455
456 if mqrebase:
456 if mqrebase:
457 mq.finish(repo, mqrebase.keys())
457 mq.finish(repo, mqrebase.keys())
458
458
459 # We must start import from the newest revision
459 # We must start import from the newest revision
460 for rev in sorted(mqrebase, reverse=True):
460 for rev in sorted(mqrebase, reverse=True):
461 if rev not in skipped:
461 if rev not in skipped:
462 name, isgit = mqrebase[rev]
462 name, isgit = mqrebase[rev]
463 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
463 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
464 mq.qimport(repo, (), patchname=name, git=isgit,
464 mq.qimport(repo, (), patchname=name, git=isgit,
465 rev=[str(state[rev])])
465 rev=[str(state[rev])])
466 else:
466 else:
467 # Rebased and skipped
467 # Rebased and skipped
468 skippedpatches.add(mqrebase[rev][0])
468 skippedpatches.add(mqrebase[rev][0])
469
469
470 # Patches were either applied and rebased and imported in
470 # Patches were either applied and rebased and imported in
471 # order, applied and removed or unapplied. Discard the removed
471 # order, applied and removed or unapplied. Discard the removed
472 # ones while preserving the original series order and guards.
472 # ones while preserving the original series order and guards.
473 newseries = [s for s in original_series
473 newseries = [s for s in original_series
474 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
474 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
475 mq.fullseries[:] = newseries
475 mq.fullseries[:] = newseries
476 mq.seriesdirty = True
476 mq.seriesdirty = True
477 mq.savedirty()
477 mq.savedirty()
478
478
479 def updatebookmarks(repo, nstate, originalbookmarks, **opts):
479 def updatebookmarks(repo, nstate, originalbookmarks, **opts):
480 'Move bookmarks to their correct changesets'
480 'Move bookmarks to their correct changesets'
481 for k, v in originalbookmarks.iteritems():
481 for k, v in originalbookmarks.iteritems():
482 if v in nstate:
482 if v in nstate:
483 if nstate[v] != nullmerge:
483 if nstate[v] != nullmerge:
484 # update the bookmarks for revs that have moved
484 # update the bookmarks for revs that have moved
485 repo._bookmarks[k] = nstate[v]
485 repo._bookmarks[k] = nstate[v]
486
486
487 bookmarks.write(repo)
487 bookmarks.write(repo)
488
488
489 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
489 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
490 external):
490 external):
491 'Store the current status to allow recovery'
491 'Store the current status to allow recovery'
492 f = repo.opener("rebasestate", "w")
492 f = repo.opener("rebasestate", "w")
493 f.write(repo[originalwd].hex() + '\n')
493 f.write(repo[originalwd].hex() + '\n')
494 f.write(repo[target].hex() + '\n')
494 f.write(repo[target].hex() + '\n')
495 f.write(repo[external].hex() + '\n')
495 f.write(repo[external].hex() + '\n')
496 f.write('%d\n' % int(collapse))
496 f.write('%d\n' % int(collapse))
497 f.write('%d\n' % int(keep))
497 f.write('%d\n' % int(keep))
498 f.write('%d\n' % int(keepbranches))
498 f.write('%d\n' % int(keepbranches))
499 for d, v in state.iteritems():
499 for d, v in state.iteritems():
500 oldrev = repo[d].hex()
500 oldrev = repo[d].hex()
501 if v != nullmerge:
501 if v != nullmerge:
502 newrev = repo[v].hex()
502 newrev = repo[v].hex()
503 else:
503 else:
504 newrev = v
504 newrev = v
505 f.write("%s:%s\n" % (oldrev, newrev))
505 f.write("%s:%s\n" % (oldrev, newrev))
506 f.close()
506 f.close()
507 repo.ui.debug('rebase status stored\n')
507 repo.ui.debug('rebase status stored\n')
508
508
509 def clearstatus(repo):
509 def clearstatus(repo):
510 'Remove the status files'
510 'Remove the status files'
511 if os.path.exists(repo.join("rebasestate")):
511 if os.path.exists(repo.join("rebasestate")):
512 util.unlinkpath(repo.join("rebasestate"))
512 util.unlinkpath(repo.join("rebasestate"))
513
513
514 def restorestatus(repo):
514 def restorestatus(repo):
515 'Restore a previously stored status'
515 'Restore a previously stored status'
516 try:
516 try:
517 target = None
517 target = None
518 collapse = False
518 collapse = False
519 external = nullrev
519 external = nullrev
520 state = {}
520 state = {}
521 f = repo.opener("rebasestate")
521 f = repo.opener("rebasestate")
522 for i, l in enumerate(f.read().splitlines()):
522 for i, l in enumerate(f.read().splitlines()):
523 if i == 0:
523 if i == 0:
524 originalwd = repo[l].rev()
524 originalwd = repo[l].rev()
525 elif i == 1:
525 elif i == 1:
526 target = repo[l].rev()
526 target = repo[l].rev()
527 elif i == 2:
527 elif i == 2:
528 external = repo[l].rev()
528 external = repo[l].rev()
529 elif i == 3:
529 elif i == 3:
530 collapse = bool(int(l))
530 collapse = bool(int(l))
531 elif i == 4:
531 elif i == 4:
532 keep = bool(int(l))
532 keep = bool(int(l))
533 elif i == 5:
533 elif i == 5:
534 keepbranches = bool(int(l))
534 keepbranches = bool(int(l))
535 else:
535 else:
536 oldrev, newrev = l.split(':')
536 oldrev, newrev = l.split(':')
537 if newrev != str(nullmerge):
537 if newrev != str(nullmerge):
538 state[repo[oldrev].rev()] = repo[newrev].rev()
538 state[repo[oldrev].rev()] = repo[newrev].rev()
539 else:
539 else:
540 state[repo[oldrev].rev()] = int(newrev)
540 state[repo[oldrev].rev()] = int(newrev)
541 skipped = set()
541 skipped = set()
542 # recompute the set of skipped revs
542 # recompute the set of skipped revs
543 if not collapse:
543 if not collapse:
544 seen = set([target])
544 seen = set([target])
545 for old, new in sorted(state.items()):
545 for old, new in sorted(state.items()):
546 if new != nullrev and new in seen:
546 if new != nullrev and new in seen:
547 skipped.add(old)
547 skipped.add(old)
548 seen.add(new)
548 seen.add(new)
549 repo.ui.debug('computed skipped revs: %s\n' % skipped)
549 repo.ui.debug('computed skipped revs: %s\n' % skipped)
550 repo.ui.debug('rebase status resumed\n')
550 repo.ui.debug('rebase status resumed\n')
551 return (originalwd, target, state, skipped,
551 return (originalwd, target, state, skipped,
552 collapse, keep, keepbranches, external)
552 collapse, keep, keepbranches, external)
553 except IOError, err:
553 except IOError, err:
554 if err.errno != errno.ENOENT:
554 if err.errno != errno.ENOENT:
555 raise
555 raise
556 raise util.Abort(_('no rebase in progress'))
556 raise util.Abort(_('no rebase in progress'))
557
557
558 def abort(repo, originalwd, target, state):
558 def abort(repo, originalwd, target, state):
559 'Restore the repository to its original state'
559 'Restore the repository to its original state'
560 dstates = [s for s in state.values() if s != nullrev]
560 dstates = [s for s in state.values() if s != nullrev]
561 immutable = [d for d in dstates if not repo[d].mutable()]
561 immutable = [d for d in dstates if not repo[d].mutable()]
562 if immutable:
562 if immutable:
563 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")
564 % ', '.join(str(repo[r]) for r in immutable),
564 % ', '.join(str(repo[r]) for r in immutable),
565 hint=_('see hg help phases for details'))
565 hint=_('see hg help phases for details'))
566
566
567 descendants = set()
567 descendants = set()
568 if dstates:
568 if dstates:
569 descendants = set(repo.changelog.descendants(dstates))
569 descendants = set(repo.changelog.descendants(dstates))
570 if descendants - set(dstates):
570 if descendants - set(dstates):
571 repo.ui.warn(_("warning: new changesets detected on target branch, "
571 repo.ui.warn(_("warning: new changesets detected on target branch, "
572 "can't abort\n"))
572 "can't abort\n"))
573 return -1
573 return -1
574 else:
574 else:
575 # Strip from the first rebased revision
575 # Strip from the first rebased revision
576 merge.update(repo, repo[originalwd].rev(), False, True, False)
576 merge.update(repo, repo[originalwd].rev(), False, True, False)
577 rebased = filter(lambda x: x > -1 and x != target, state.values())
577 rebased = filter(lambda x: x > -1 and x != target, state.values())
578 if rebased:
578 if rebased:
579 strippoint = min(rebased)
579 strippoint = min(rebased)
580 # no backup of rebased cset versions needed
580 # no backup of rebased cset versions needed
581 repair.strip(repo.ui, repo, repo[strippoint].node())
581 repair.strip(repo.ui, repo, repo[strippoint].node())
582 clearstatus(repo)
582 clearstatus(repo)
583 repo.ui.warn(_('rebase aborted\n'))
583 repo.ui.warn(_('rebase aborted\n'))
584 return 0
584 return 0
585
585
586 def buildstate(repo, dest, rebaseset, collapse):
586 def buildstate(repo, dest, rebaseset, collapse):
587 '''Define which revisions are going to be rebased and where
587 '''Define which revisions are going to be rebased and where
588
588
589 repo: repo
589 repo: repo
590 dest: context
590 dest: context
591 rebaseset: set of rev
591 rebaseset: set of rev
592 '''
592 '''
593
593
594 # 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
595 # applied patch. But it prevents messing up the working directory when
595 # applied patch. But it prevents messing up the working directory when
596 # a partially completed rebase is blocked by mq.
596 # a partially completed rebase is blocked by mq.
597 if 'qtip' in repo.tags() and (dest.node() in
597 if 'qtip' in repo.tags() and (dest.node() in
598 [s.node for s in repo.mq.applied]):
598 [s.node for s in repo.mq.applied]):
599 raise util.Abort(_('cannot rebase onto an applied mq patch'))
599 raise util.Abort(_('cannot rebase onto an applied mq patch'))
600
600
601 roots = list(repo.set('roots(%ld)', rebaseset))
601 roots = list(repo.set('roots(%ld)', rebaseset))
602 if not roots:
602 if not roots:
603 raise util.Abort(_('no matching revisions'))
603 raise util.Abort(_('no matching revisions'))
604 if len(roots) > 1:
604 if len(roots) > 1:
605 raise util.Abort(_("can't rebase multiple roots"))
605 raise util.Abort(_("can't rebase multiple roots"))
606 root = roots[0]
606 root = roots[0]
607
607
608 commonbase = root.ancestor(dest)
608 commonbase = root.ancestor(dest)
609 if commonbase == root:
609 if commonbase == root:
610 raise util.Abort(_('source is ancestor of destination'))
610 raise util.Abort(_('source is ancestor of destination'))
611 if commonbase == dest:
611 if commonbase == dest:
612 samebranch = root.branch() == dest.branch()
612 samebranch = root.branch() == dest.branch()
613 if not collapse and samebranch and root in dest.children():
613 if not collapse and samebranch and root in dest.children():
614 repo.ui.debug('source is a child of destination\n')
614 repo.ui.debug('source is a child of destination\n')
615 return None
615 return None
616
616
617 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))
618 state = dict.fromkeys(rebaseset, nullrev)
618 state = dict.fromkeys(rebaseset, nullrev)
619 # Rebase tries to turn <dest> into a parent of <root> while
619 # Rebase tries to turn <dest> into a parent of <root> while
620 # preserving the number of parents of rebased changesets:
620 # preserving the number of parents of rebased changesets:
621 #
621 #
622 # - 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
623 # changeset with a single parent.
623 # changeset with a single parent.
624 #
624 #
625 # - 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
626 # ancestors of <dest> or are themselves in the rebased set and
626 # ancestors of <dest> or are themselves in the rebased set and
627 # pruned while rebased.
627 # pruned while rebased.
628 #
628 #
629 # 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
630 # 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
631 # --base option.
631 # --base option.
632 #
632 #
633 # Otherwise, we need to *replace* the original parents with
633 # Otherwise, we need to *replace* the original parents with
634 # <dest>. This "detaches" the rebased set from its former location
634 # <dest>. This "detaches" the rebased set from its former location
635 # and rebases it onto <dest>. Changes introduced by ancestors of
635 # and rebases it onto <dest>. Changes introduced by ancestors of
636 # <root> not common with <dest> (the detachset, marked as
636 # <root> not common with <dest> (the detachset, marked as
637 # nullmerge) are "removed" from the rebased changesets.
637 # nullmerge) are "removed" from the rebased changesets.
638 #
638 #
639 # - If <root> has a single parent, set it to <dest>.
639 # - If <root> has a single parent, set it to <dest>.
640 #
640 #
641 # - If <root> is a merge, we cannot decide which parent to
641 # - If <root> is a merge, we cannot decide which parent to
642 # replace, the rebase operation is not clearly defined.
642 # replace, the rebase operation is not clearly defined.
643 #
643 #
644 # The table below sums up this behavior:
644 # The table below sums up this behavior:
645 #
645 #
646 # +--------------------+----------------------+-------------------------+
646 # +--------------------+----------------------+-------------------------+
647 # | | one parent | merge |
647 # | | one parent | merge |
648 # +--------------------+----------------------+-------------------------+
648 # +--------------------+----------------------+-------------------------+
649 # | parent in ::<dest> | new parent is <dest> | parents in ::<dest> are |
649 # | parent in ::<dest> | new parent is <dest> | parents in ::<dest> are |
650 # | | | remapped to <dest> |
650 # | | | remapped to <dest> |
651 # +--------------------+----------------------+-------------------------+
651 # +--------------------+----------------------+-------------------------+
652 # | unrelated source | new parent is <dest> | ambiguous, abort |
652 # | unrelated source | new parent is <dest> | ambiguous, abort |
653 # +--------------------+----------------------+-------------------------+
653 # +--------------------+----------------------+-------------------------+
654 #
654 #
655 # The actual abort is handled by `defineparents`
655 # The actual abort is handled by `defineparents`
656 if len(root.parents()) <= 1:
656 if len(root.parents()) <= 1:
657 # (strict) ancestors of <root> not ancestors of <dest>
657 # (strict) ancestors of <root> not ancestors of <dest>
658 detachset = repo.revs('::%d - ::%d - %d', root, commonbase, root)
658 detachset = repo.revs('::%d - ::%d - %d', root, commonbase, root)
659 state.update(dict.fromkeys(detachset, nullmerge))
659 state.update(dict.fromkeys(detachset, nullmerge))
660 return repo['.'].rev(), dest.rev(), state
660 return repo['.'].rev(), dest.rev(), state
661
661
662 def clearrebased(ui, repo, state, collapsedas=None):
662 def clearrebased(ui, repo, state, collapsedas=None):
663 """dispose of rebased revision at the end of the rebase
663 """dispose of rebased revision at the end of the rebase
664
664
665 If `collapsedas` is not None, the rebase was a collapse whose result if the
665 If `collapsedas` is not None, the rebase was a collapse whose result if the
666 `collapsedas` node."""
666 `collapsedas` node."""
667 if obsolete._enabled:
667 if obsolete._enabled:
668 markers = []
668 markers = []
669 for rev, newrev in sorted(state.items()):
669 for rev, newrev in sorted(state.items()):
670 if newrev >= 0:
670 if newrev >= 0:
671 if collapsedas is not None:
671 if collapsedas is not None:
672 newrev = collapsedas
672 newrev = collapsedas
673 markers.append((repo[rev], (repo[newrev],)))
673 markers.append((repo[rev], (repo[newrev],)))
674 if markers:
674 if markers:
675 obsolete.createmarkers(repo, markers)
675 obsolete.createmarkers(repo, markers)
676 else:
676 else:
677 rebased = [rev for rev in state if state[rev] != nullmerge]
677 rebased = [rev for rev in state if state[rev] != nullmerge]
678 if rebased:
678 if rebased:
679 if set(repo.changelog.descendants([min(rebased)])) - set(state):
679 if set(repo.changelog.descendants([min(rebased)])) - set(state):
680 ui.warn(_("warning: new changesets detected "
680 ui.warn(_("warning: new changesets detected "
681 "on source branch, not stripping\n"))
681 "on source branch, not stripping\n"))
682 else:
682 else:
683 # backup the old csets by default
683 # backup the old csets by default
684 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
684 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
685
685
686
686
687 def pullrebase(orig, ui, repo, *args, **opts):
687 def pullrebase(orig, ui, repo, *args, **opts):
688 'Call rebase after pull if the latter has been invoked with --rebase'
688 'Call rebase after pull if the latter has been invoked with --rebase'
689 if opts.get('rebase'):
689 if opts.get('rebase'):
690 if opts.get('update'):
690 if opts.get('update'):
691 del opts['update']
691 del opts['update']
692 ui.debug('--update and --rebase are not compatible, ignoring '
692 ui.debug('--update and --rebase are not compatible, ignoring '
693 'the update flag\n')
693 'the update flag\n')
694
694
695 movemarkfrom = repo['.'].node()
695 movemarkfrom = repo['.'].node()
696 cmdutil.bailifchanged(repo)
696 cmdutil.bailifchanged(repo)
697 revsprepull = len(repo)
697 revsprepull = len(repo)
698 origpostincoming = commands.postincoming
698 origpostincoming = commands.postincoming
699 def _dummy(*args, **kwargs):
699 def _dummy(*args, **kwargs):
700 pass
700 pass
701 commands.postincoming = _dummy
701 commands.postincoming = _dummy
702 try:
702 try:
703 orig(ui, repo, *args, **opts)
703 orig(ui, repo, *args, **opts)
704 finally:
704 finally:
705 commands.postincoming = origpostincoming
705 commands.postincoming = origpostincoming
706 revspostpull = len(repo)
706 revspostpull = len(repo)
707 if revspostpull > revsprepull:
707 if revspostpull > revsprepull:
708 rebase(ui, repo, **opts)
708 rebase(ui, repo, **opts)
709 branch = repo[None].branch()
709 branch = repo[None].branch()
710 dest = repo[branch].rev()
710 dest = repo[branch].rev()
711 if dest != repo['.'].rev():
711 if dest != repo['.'].rev():
712 # there was nothing to rebase we force an update
712 # there was nothing to rebase we force an update
713 hg.update(repo, dest)
713 hg.update(repo, dest)
714 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
714 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
715 ui.status(_("updating bookmark %s\n")
715 ui.status(_("updating bookmark %s\n")
716 % repo._bookmarkcurrent)
716 % repo._bookmarkcurrent)
717 else:
717 else:
718 if opts.get('tool'):
718 if opts.get('tool'):
719 raise util.Abort(_('--tool can only be used with --rebase'))
719 raise util.Abort(_('--tool can only be used with --rebase'))
720 orig(ui, repo, *args, **opts)
720 orig(ui, repo, *args, **opts)
721
721
722 def uisetup(ui):
722 def uisetup(ui):
723 'Replace pull with a decorator to provide --rebase option'
723 'Replace pull with a decorator to provide --rebase option'
724 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
724 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
725 entry[1].append(('', 'rebase', None,
725 entry[1].append(('', 'rebase', None,
726 _("rebase working directory to branch head")))
726 _("rebase working directory to branch head")))
727 entry[1].append(('t', 'tool', '',
727 entry[1].append(('t', 'tool', '',
728 _("specify merge tool for rebase")))
728 _("specify merge tool for rebase")))
General Comments 0
You need to be logged in to leave comments. Login now