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