##// END OF EJS Templates
rebase: use cmdutil.command decorator
Adrian Buehlmann -
r14306:db2a8eab default
parent child Browse files
Show More
@@ -1,589 +1,587
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
17 from mercurial import hg, util, repair, merge, cmdutil, commands
18 from mercurial import extensions, copies, patch
18 from mercurial import extensions, copies, patch
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 = {}
28 command = cmdutil.command(cmdtable)
29
30 @command('rebase',
31 [('s', 'source', '',
32 _('rebase from the specified changeset'), _('REV')),
33 ('b', 'base', '',
34 _('rebase from the base of the specified changeset '
35 '(up to greatest common ancestor of base and dest)'),
36 _('REV')),
37 ('d', 'dest', '',
38 _('rebase onto the specified changeset'), _('REV')),
39 ('', 'collapse', False, _('collapse the rebased changesets')),
40 ('m', 'message', '',
41 _('use text as collapse commit message'), _('TEXT')),
42 ('l', 'logfile', '',
43 _('read collapse commit message from file'), _('FILE')),
44 ('', 'keep', False, _('keep original changesets')),
45 ('', 'keepbranches', False, _('keep original branch names')),
46 ('', 'detach', False, _('force detaching of source from its original '
47 'branch')),
48 ('t', 'tool', '', _('specify merge tool')),
49 ('c', 'continue', False, _('continue an interrupted rebase')),
50 ('a', 'abort', False, _('abort an interrupted rebase'))] +
51 templateopts,
52 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
53 'hg rebase {-a|-c}'))
27 def rebase(ui, repo, **opts):
54 def rebase(ui, repo, **opts):
28 """move changeset (and descendants) to a different branch
55 """move changeset (and descendants) to a different branch
29
56
30 Rebase uses repeated merging to graft changesets from one part of
57 Rebase uses repeated merging to graft changesets from one part of
31 history (the source) onto another (the destination). This can be
58 history (the source) onto another (the destination). This can be
32 useful for linearizing *local* changes relative to a master
59 useful for linearizing *local* changes relative to a master
33 development tree.
60 development tree.
34
61
35 You should not rebase changesets that have already been shared
62 You should not rebase changesets that have already been shared
36 with others. Doing so will force everybody else to perform the
63 with others. Doing so will force everybody else to perform the
37 same rebase or they will end up with duplicated changesets after
64 same rebase or they will end up with duplicated changesets after
38 pulling in your rebased changesets.
65 pulling in your rebased changesets.
39
66
40 If you don't specify a destination changeset (``-d/--dest``),
67 If you don't specify a destination changeset (``-d/--dest``),
41 rebase uses the tipmost head of the current named branch as the
68 rebase uses the tipmost head of the current named branch as the
42 destination. (The destination changeset is not modified by
69 destination. (The destination changeset is not modified by
43 rebasing, but new changesets are added as its descendants.)
70 rebasing, but new changesets are added as its descendants.)
44
71
45 You can specify which changesets to rebase in two ways: as a
72 You can specify which changesets to rebase in two ways: as a
46 "source" changeset or as a "base" changeset. Both are shorthand
73 "source" changeset or as a "base" changeset. Both are shorthand
47 for a topologically related set of changesets (the "source
74 for a topologically related set of changesets (the "source
48 branch"). If you specify source (``-s/--source``), rebase will
75 branch"). If you specify source (``-s/--source``), rebase will
49 rebase that changeset and all of its descendants onto dest. If you
76 rebase that changeset and all of its descendants onto dest. If you
50 specify base (``-b/--base``), rebase will select ancestors of base
77 specify base (``-b/--base``), rebase will select ancestors of base
51 back to but not including the common ancestor with dest. Thus,
78 back to but not including the common ancestor with dest. Thus,
52 ``-b`` is less precise but more convenient than ``-s``: you can
79 ``-b`` is less precise but more convenient than ``-s``: you can
53 specify any changeset in the source branch, and rebase will select
80 specify any changeset in the source branch, and rebase will select
54 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
81 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
55 uses the parent of the working directory as the base.
82 uses the parent of the working directory as the base.
56
83
57 By default, rebase recreates the changesets in the source branch
84 By default, rebase recreates the changesets in the source branch
58 as descendants of dest and then destroys the originals. Use
85 as descendants of dest and then destroys the originals. Use
59 ``--keep`` to preserve the original source changesets. Some
86 ``--keep`` to preserve the original source changesets. Some
60 changesets in the source branch (e.g. merges from the destination
87 changesets in the source branch (e.g. merges from the destination
61 branch) may be dropped if they no longer contribute any change.
88 branch) may be dropped if they no longer contribute any change.
62
89
63 One result of the rules for selecting the destination changeset
90 One result of the rules for selecting the destination changeset
64 and source branch is that, unlike ``merge``, rebase will do
91 and source branch is that, unlike ``merge``, rebase will do
65 nothing if you are at the latest (tipmost) head of a named branch
92 nothing if you are at the latest (tipmost) head of a named branch
66 with two heads. You need to explicitly specify source and/or
93 with two heads. You need to explicitly specify source and/or
67 destination (or ``update`` to the other head, if it's the head of
94 destination (or ``update`` to the other head, if it's the head of
68 the intended source branch).
95 the intended source branch).
69
96
70 If a rebase is interrupted to manually resolve a merge, it can be
97 If a rebase is interrupted to manually resolve a merge, it can be
71 continued with --continue/-c or aborted with --abort/-a.
98 continued with --continue/-c or aborted with --abort/-a.
72
99
73 Returns 0 on success, 1 if nothing to rebase.
100 Returns 0 on success, 1 if nothing to rebase.
74 """
101 """
75 originalwd = target = None
102 originalwd = target = None
76 external = nullrev
103 external = nullrev
77 state = {}
104 state = {}
78 skipped = set()
105 skipped = set()
79 targetancestors = set()
106 targetancestors = set()
80
107
81 lock = wlock = None
108 lock = wlock = None
82 try:
109 try:
83 lock = repo.lock()
110 lock = repo.lock()
84 wlock = repo.wlock()
111 wlock = repo.wlock()
85
112
86 # Validate input and define rebasing points
113 # Validate input and define rebasing points
87 destf = opts.get('dest', None)
114 destf = opts.get('dest', None)
88 srcf = opts.get('source', None)
115 srcf = opts.get('source', None)
89 basef = opts.get('base', None)
116 basef = opts.get('base', None)
90 contf = opts.get('continue')
117 contf = opts.get('continue')
91 abortf = opts.get('abort')
118 abortf = opts.get('abort')
92 collapsef = opts.get('collapse', False)
119 collapsef = opts.get('collapse', False)
93 collapsemsg = cmdutil.logmessage(opts)
120 collapsemsg = cmdutil.logmessage(opts)
94 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
121 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
95 keepf = opts.get('keep', False)
122 keepf = opts.get('keep', False)
96 keepbranchesf = opts.get('keepbranches', False)
123 keepbranchesf = opts.get('keepbranches', False)
97 detachf = opts.get('detach', False)
124 detachf = opts.get('detach', False)
98 # keepopen is not meant for use on the command line, but by
125 # keepopen is not meant for use on the command line, but by
99 # other extensions
126 # other extensions
100 keepopen = opts.get('keepopen', False)
127 keepopen = opts.get('keepopen', False)
101
128
102 if collapsemsg and not collapsef:
129 if collapsemsg and not collapsef:
103 raise util.Abort(
130 raise util.Abort(
104 _('message can only be specified with collapse'))
131 _('message can only be specified with collapse'))
105
132
106 if contf or abortf:
133 if contf or abortf:
107 if contf and abortf:
134 if contf and abortf:
108 raise util.Abort(_('cannot use both abort and continue'))
135 raise util.Abort(_('cannot use both abort and continue'))
109 if collapsef:
136 if collapsef:
110 raise util.Abort(
137 raise util.Abort(
111 _('cannot use collapse with continue or abort'))
138 _('cannot use collapse with continue or abort'))
112 if detachf:
139 if detachf:
113 raise util.Abort(_('cannot use detach with continue or abort'))
140 raise util.Abort(_('cannot use detach with continue or abort'))
114 if srcf or basef or destf:
141 if srcf or basef or destf:
115 raise util.Abort(
142 raise util.Abort(
116 _('abort and continue do not allow specifying revisions'))
143 _('abort and continue do not allow specifying revisions'))
117 if opts.get('tool', False):
144 if opts.get('tool', False):
118 ui.warn(_('tool option will be ignored\n'))
145 ui.warn(_('tool option will be ignored\n'))
119
146
120 (originalwd, target, state, skipped, collapsef, keepf,
147 (originalwd, target, state, skipped, collapsef, keepf,
121 keepbranchesf, external) = restorestatus(repo)
148 keepbranchesf, external) = restorestatus(repo)
122 if abortf:
149 if abortf:
123 return abort(repo, originalwd, target, state)
150 return abort(repo, originalwd, target, state)
124 else:
151 else:
125 if srcf and basef:
152 if srcf and basef:
126 raise util.Abort(_('cannot specify both a '
153 raise util.Abort(_('cannot specify both a '
127 'revision and a base'))
154 'revision and a base'))
128 if detachf:
155 if detachf:
129 if not srcf:
156 if not srcf:
130 raise util.Abort(
157 raise util.Abort(
131 _('detach requires a revision to be specified'))
158 _('detach requires a revision to be specified'))
132 if basef:
159 if basef:
133 raise util.Abort(_('cannot specify a base with detach'))
160 raise util.Abort(_('cannot specify a base with detach'))
134
161
135 cmdutil.bailifchanged(repo)
162 cmdutil.bailifchanged(repo)
136 result = buildstate(repo, destf, srcf, basef, detachf)
163 result = buildstate(repo, destf, srcf, basef, detachf)
137 if not result:
164 if not result:
138 # Empty state built, nothing to rebase
165 # Empty state built, nothing to rebase
139 ui.status(_('nothing to rebase\n'))
166 ui.status(_('nothing to rebase\n'))
140 return 1
167 return 1
141 else:
168 else:
142 originalwd, target, state = result
169 originalwd, target, state = result
143 if collapsef:
170 if collapsef:
144 targetancestors = set(repo.changelog.ancestors(target))
171 targetancestors = set(repo.changelog.ancestors(target))
145 external = checkexternal(repo, state, targetancestors)
172 external = checkexternal(repo, state, targetancestors)
146
173
147 if keepbranchesf:
174 if keepbranchesf:
148 assert not extrafn, 'cannot use both keepbranches and extrafn'
175 assert not extrafn, 'cannot use both keepbranches and extrafn'
149 def extrafn(ctx, extra):
176 def extrafn(ctx, extra):
150 extra['branch'] = ctx.branch()
177 extra['branch'] = ctx.branch()
151
178
152 # Rebase
179 # Rebase
153 if not targetancestors:
180 if not targetancestors:
154 targetancestors = set(repo.changelog.ancestors(target))
181 targetancestors = set(repo.changelog.ancestors(target))
155 targetancestors.add(target)
182 targetancestors.add(target)
156
183
157 sortedstate = sorted(state)
184 sortedstate = sorted(state)
158 total = len(sortedstate)
185 total = len(sortedstate)
159 pos = 0
186 pos = 0
160 for rev in sortedstate:
187 for rev in sortedstate:
161 pos += 1
188 pos += 1
162 if state[rev] == -1:
189 if state[rev] == -1:
163 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
190 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
164 _('changesets'), total)
191 _('changesets'), total)
165 storestatus(repo, originalwd, target, state, collapsef, keepf,
192 storestatus(repo, originalwd, target, state, collapsef, keepf,
166 keepbranchesf, external)
193 keepbranchesf, external)
167 p1, p2 = defineparents(repo, rev, target, state,
194 p1, p2 = defineparents(repo, rev, target, state,
168 targetancestors)
195 targetancestors)
169 if len(repo.parents()) == 2:
196 if len(repo.parents()) == 2:
170 repo.ui.debug('resuming interrupted rebase\n')
197 repo.ui.debug('resuming interrupted rebase\n')
171 else:
198 else:
172 try:
199 try:
173 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
200 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
174 stats = rebasenode(repo, rev, p1, state)
201 stats = rebasenode(repo, rev, p1, state)
175 if stats and stats[3] > 0:
202 if stats and stats[3] > 0:
176 raise util.Abort(_('unresolved conflicts (see hg '
203 raise util.Abort(_('unresolved conflicts (see hg '
177 'resolve, then hg rebase --continue)'))
204 'resolve, then hg rebase --continue)'))
178 finally:
205 finally:
179 ui.setconfig('ui', 'forcemerge', '')
206 ui.setconfig('ui', 'forcemerge', '')
180 updatedirstate(repo, rev, target, p2)
207 updatedirstate(repo, rev, target, p2)
181 if not collapsef:
208 if not collapsef:
182 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn)
209 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn)
183 else:
210 else:
184 # Skip commit if we are collapsing
211 # Skip commit if we are collapsing
185 repo.dirstate.setparents(repo[p1].node())
212 repo.dirstate.setparents(repo[p1].node())
186 newrev = None
213 newrev = None
187 # Update the state
214 # Update the state
188 if newrev is not None:
215 if newrev is not None:
189 state[rev] = repo[newrev].rev()
216 state[rev] = repo[newrev].rev()
190 else:
217 else:
191 if not collapsef:
218 if not collapsef:
192 ui.note(_('no changes, revision %d skipped\n') % rev)
219 ui.note(_('no changes, revision %d skipped\n') % rev)
193 ui.debug('next revision set to %s\n' % p1)
220 ui.debug('next revision set to %s\n' % p1)
194 skipped.add(rev)
221 skipped.add(rev)
195 state[rev] = p1
222 state[rev] = p1
196
223
197 ui.progress(_('rebasing'), None)
224 ui.progress(_('rebasing'), None)
198 ui.note(_('rebase merging completed\n'))
225 ui.note(_('rebase merging completed\n'))
199
226
200 if collapsef and not keepopen:
227 if collapsef and not keepopen:
201 p1, p2 = defineparents(repo, min(state), target,
228 p1, p2 = defineparents(repo, min(state), target,
202 state, targetancestors)
229 state, targetancestors)
203 if collapsemsg:
230 if collapsemsg:
204 commitmsg = collapsemsg
231 commitmsg = collapsemsg
205 else:
232 else:
206 commitmsg = 'Collapsed revision'
233 commitmsg = 'Collapsed revision'
207 for rebased in state:
234 for rebased in state:
208 if rebased not in skipped and state[rebased] != nullmerge:
235 if rebased not in skipped and state[rebased] != nullmerge:
209 commitmsg += '\n* %s' % repo[rebased].description()
236 commitmsg += '\n* %s' % repo[rebased].description()
210 commitmsg = ui.edit(commitmsg, repo.ui.username())
237 commitmsg = ui.edit(commitmsg, repo.ui.username())
211 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
238 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
212 extrafn=extrafn)
239 extrafn=extrafn)
213
240
214 if 'qtip' in repo.tags():
241 if 'qtip' in repo.tags():
215 updatemq(repo, state, skipped, **opts)
242 updatemq(repo, state, skipped, **opts)
216
243
217 if not keepf:
244 if not keepf:
218 # Remove no more useful revisions
245 # Remove no more useful revisions
219 rebased = [rev for rev in state if state[rev] != nullmerge]
246 rebased = [rev for rev in state if state[rev] != nullmerge]
220 if rebased:
247 if rebased:
221 if set(repo.changelog.descendants(min(rebased))) - set(state):
248 if set(repo.changelog.descendants(min(rebased))) - set(state):
222 ui.warn(_("warning: new changesets detected "
249 ui.warn(_("warning: new changesets detected "
223 "on source branch, not stripping\n"))
250 "on source branch, not stripping\n"))
224 else:
251 else:
225 # backup the old csets by default
252 # backup the old csets by default
226 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
253 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
227
254
228 clearstatus(repo)
255 clearstatus(repo)
229 ui.note(_("rebase completed\n"))
256 ui.note(_("rebase completed\n"))
230 if os.path.exists(repo.sjoin('undo')):
257 if os.path.exists(repo.sjoin('undo')):
231 util.unlinkpath(repo.sjoin('undo'))
258 util.unlinkpath(repo.sjoin('undo'))
232 if skipped:
259 if skipped:
233 ui.note(_("%d revisions have been skipped\n") % len(skipped))
260 ui.note(_("%d revisions have been skipped\n") % len(skipped))
234 finally:
261 finally:
235 release(lock, wlock)
262 release(lock, wlock)
236
263
237 def checkexternal(repo, state, targetancestors):
264 def checkexternal(repo, state, targetancestors):
238 """Check whether one or more external revisions need to be taken in
265 """Check whether one or more external revisions need to be taken in
239 consideration. In the latter case, abort.
266 consideration. In the latter case, abort.
240 """
267 """
241 external = nullrev
268 external = nullrev
242 source = min(state)
269 source = min(state)
243 for rev in state:
270 for rev in state:
244 if rev == source:
271 if rev == source:
245 continue
272 continue
246 # Check externals and fail if there are more than one
273 # Check externals and fail if there are more than one
247 for p in repo[rev].parents():
274 for p in repo[rev].parents():
248 if (p.rev() not in state
275 if (p.rev() not in state
249 and p.rev() not in targetancestors):
276 and p.rev() not in targetancestors):
250 if external != nullrev:
277 if external != nullrev:
251 raise util.Abort(_('unable to collapse, there is more '
278 raise util.Abort(_('unable to collapse, there is more '
252 'than one external parent'))
279 'than one external parent'))
253 external = p.rev()
280 external = p.rev()
254 return external
281 return external
255
282
256 def updatedirstate(repo, rev, p1, p2):
283 def updatedirstate(repo, rev, p1, p2):
257 """Keep track of renamed files in the revision that is going to be rebased
284 """Keep track of renamed files in the revision that is going to be rebased
258 """
285 """
259 # Here we simulate the copies and renames in the source changeset
286 # Here we simulate the copies and renames in the source changeset
260 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
287 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
261 m1 = repo[rev].manifest()
288 m1 = repo[rev].manifest()
262 m2 = repo[p1].manifest()
289 m2 = repo[p1].manifest()
263 for k, v in cop.iteritems():
290 for k, v in cop.iteritems():
264 if k in m1:
291 if k in m1:
265 if v in m1 or v in m2:
292 if v in m1 or v in m2:
266 repo.dirstate.copy(v, k)
293 repo.dirstate.copy(v, k)
267 if v in m2 and v not in m1 and k in m2:
294 if v in m2 and v not in m1 and k in m2:
268 repo.dirstate.remove(v)
295 repo.dirstate.remove(v)
269
296
270 def concludenode(repo, rev, p1, p2, commitmsg=None, extrafn=None):
297 def concludenode(repo, rev, p1, p2, commitmsg=None, extrafn=None):
271 'Commit the changes and store useful information in extra'
298 'Commit the changes and store useful information in extra'
272 try:
299 try:
273 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
300 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
274 ctx = repo[rev]
301 ctx = repo[rev]
275 if commitmsg is None:
302 if commitmsg is None:
276 commitmsg = ctx.description()
303 commitmsg = ctx.description()
277 extra = {'rebase_source': ctx.hex()}
304 extra = {'rebase_source': ctx.hex()}
278 if extrafn:
305 if extrafn:
279 extrafn(ctx, extra)
306 extrafn(ctx, extra)
280 # Commit might fail if unresolved files exist
307 # Commit might fail if unresolved files exist
281 newrev = repo.commit(text=commitmsg, user=ctx.user(),
308 newrev = repo.commit(text=commitmsg, user=ctx.user(),
282 date=ctx.date(), extra=extra)
309 date=ctx.date(), extra=extra)
283 repo.dirstate.setbranch(repo[newrev].branch())
310 repo.dirstate.setbranch(repo[newrev].branch())
284 return newrev
311 return newrev
285 except util.Abort:
312 except util.Abort:
286 # Invalidate the previous setparents
313 # Invalidate the previous setparents
287 repo.dirstate.invalidate()
314 repo.dirstate.invalidate()
288 raise
315 raise
289
316
290 def rebasenode(repo, rev, p1, state):
317 def rebasenode(repo, rev, p1, state):
291 'Rebase a single revision'
318 'Rebase a single revision'
292 # Merge phase
319 # Merge phase
293 # Update to target and merge it with local
320 # Update to target and merge it with local
294 if repo['.'].rev() != repo[p1].rev():
321 if repo['.'].rev() != repo[p1].rev():
295 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
322 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
296 merge.update(repo, p1, False, True, False)
323 merge.update(repo, p1, False, True, False)
297 else:
324 else:
298 repo.ui.debug(" already in target\n")
325 repo.ui.debug(" already in target\n")
299 repo.dirstate.write()
326 repo.dirstate.write()
300 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
327 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
301 base = None
328 base = None
302 if repo[rev].rev() != repo[min(state)].rev():
329 if repo[rev].rev() != repo[min(state)].rev():
303 base = repo[rev].p1().node()
330 base = repo[rev].p1().node()
304 return merge.update(repo, rev, True, True, False, base)
331 return merge.update(repo, rev, True, True, False, base)
305
332
306 def defineparents(repo, rev, target, state, targetancestors):
333 def defineparents(repo, rev, target, state, targetancestors):
307 'Return the new parent relationship of the revision that will be rebased'
334 'Return the new parent relationship of the revision that will be rebased'
308 parents = repo[rev].parents()
335 parents = repo[rev].parents()
309 p1 = p2 = nullrev
336 p1 = p2 = nullrev
310
337
311 P1n = parents[0].rev()
338 P1n = parents[0].rev()
312 if P1n in targetancestors:
339 if P1n in targetancestors:
313 p1 = target
340 p1 = target
314 elif P1n in state:
341 elif P1n in state:
315 if state[P1n] == nullmerge:
342 if state[P1n] == nullmerge:
316 p1 = target
343 p1 = target
317 else:
344 else:
318 p1 = state[P1n]
345 p1 = state[P1n]
319 else: # P1n external
346 else: # P1n external
320 p1 = target
347 p1 = target
321 p2 = P1n
348 p2 = P1n
322
349
323 if len(parents) == 2 and parents[1].rev() not in targetancestors:
350 if len(parents) == 2 and parents[1].rev() not in targetancestors:
324 P2n = parents[1].rev()
351 P2n = parents[1].rev()
325 # interesting second parent
352 # interesting second parent
326 if P2n in state:
353 if P2n in state:
327 if p1 == target: # P1n in targetancestors or external
354 if p1 == target: # P1n in targetancestors or external
328 p1 = state[P2n]
355 p1 = state[P2n]
329 else:
356 else:
330 p2 = state[P2n]
357 p2 = state[P2n]
331 else: # P2n external
358 else: # P2n external
332 if p2 != nullrev: # P1n external too => rev is a merged revision
359 if p2 != nullrev: # P1n external too => rev is a merged revision
333 raise util.Abort(_('cannot use revision %d as base, result '
360 raise util.Abort(_('cannot use revision %d as base, result '
334 'would have 3 parents') % rev)
361 'would have 3 parents') % rev)
335 p2 = P2n
362 p2 = P2n
336 repo.ui.debug(" future parents are %d and %d\n" %
363 repo.ui.debug(" future parents are %d and %d\n" %
337 (repo[p1].rev(), repo[p2].rev()))
364 (repo[p1].rev(), repo[p2].rev()))
338 return p1, p2
365 return p1, p2
339
366
340 def isagitpatch(repo, patchname):
367 def isagitpatch(repo, patchname):
341 'Return true if the given patch is in git format'
368 'Return true if the given patch is in git format'
342 mqpatch = os.path.join(repo.mq.path, patchname)
369 mqpatch = os.path.join(repo.mq.path, patchname)
343 for line in patch.linereader(file(mqpatch, 'rb')):
370 for line in patch.linereader(file(mqpatch, 'rb')):
344 if line.startswith('diff --git'):
371 if line.startswith('diff --git'):
345 return True
372 return True
346 return False
373 return False
347
374
348 def updatemq(repo, state, skipped, **opts):
375 def updatemq(repo, state, skipped, **opts):
349 'Update rebased mq patches - finalize and then import them'
376 'Update rebased mq patches - finalize and then import them'
350 mqrebase = {}
377 mqrebase = {}
351 mq = repo.mq
378 mq = repo.mq
352 original_series = mq.full_series[:]
379 original_series = mq.full_series[:]
353
380
354 for p in mq.applied:
381 for p in mq.applied:
355 rev = repo[p.node].rev()
382 rev = repo[p.node].rev()
356 if rev in state:
383 if rev in state:
357 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
384 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
358 (rev, p.name))
385 (rev, p.name))
359 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
386 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
360
387
361 if mqrebase:
388 if mqrebase:
362 mq.finish(repo, mqrebase.keys())
389 mq.finish(repo, mqrebase.keys())
363
390
364 # We must start import from the newest revision
391 # We must start import from the newest revision
365 for rev in sorted(mqrebase, reverse=True):
392 for rev in sorted(mqrebase, reverse=True):
366 if rev not in skipped:
393 if rev not in skipped:
367 name, isgit = mqrebase[rev]
394 name, isgit = mqrebase[rev]
368 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
395 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
369 mq.qimport(repo, (), patchname=name, git=isgit,
396 mq.qimport(repo, (), patchname=name, git=isgit,
370 rev=[str(state[rev])])
397 rev=[str(state[rev])])
371
398
372 # Restore missing guards
399 # Restore missing guards
373 for s in original_series:
400 for s in original_series:
374 pname = mq.guard_re.split(s, 1)[0]
401 pname = mq.guard_re.split(s, 1)[0]
375 if pname in mq.full_series:
402 if pname in mq.full_series:
376 repo.ui.debug('restoring guard for patch %s' % (pname))
403 repo.ui.debug('restoring guard for patch %s' % (pname))
377 mq.full_series.remove(pname)
404 mq.full_series.remove(pname)
378 mq.full_series.append(s)
405 mq.full_series.append(s)
379 mq.series_dirty = True
406 mq.series_dirty = True
380 mq.save_dirty()
407 mq.save_dirty()
381
408
382 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
409 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
383 external):
410 external):
384 'Store the current status to allow recovery'
411 'Store the current status to allow recovery'
385 f = repo.opener("rebasestate", "w")
412 f = repo.opener("rebasestate", "w")
386 f.write(repo[originalwd].hex() + '\n')
413 f.write(repo[originalwd].hex() + '\n')
387 f.write(repo[target].hex() + '\n')
414 f.write(repo[target].hex() + '\n')
388 f.write(repo[external].hex() + '\n')
415 f.write(repo[external].hex() + '\n')
389 f.write('%d\n' % int(collapse))
416 f.write('%d\n' % int(collapse))
390 f.write('%d\n' % int(keep))
417 f.write('%d\n' % int(keep))
391 f.write('%d\n' % int(keepbranches))
418 f.write('%d\n' % int(keepbranches))
392 for d, v in state.iteritems():
419 for d, v in state.iteritems():
393 oldrev = repo[d].hex()
420 oldrev = repo[d].hex()
394 newrev = repo[v].hex()
421 newrev = repo[v].hex()
395 f.write("%s:%s\n" % (oldrev, newrev))
422 f.write("%s:%s\n" % (oldrev, newrev))
396 f.close()
423 f.close()
397 repo.ui.debug('rebase status stored\n')
424 repo.ui.debug('rebase status stored\n')
398
425
399 def clearstatus(repo):
426 def clearstatus(repo):
400 'Remove the status files'
427 'Remove the status files'
401 if os.path.exists(repo.join("rebasestate")):
428 if os.path.exists(repo.join("rebasestate")):
402 util.unlinkpath(repo.join("rebasestate"))
429 util.unlinkpath(repo.join("rebasestate"))
403
430
404 def restorestatus(repo):
431 def restorestatus(repo):
405 'Restore a previously stored status'
432 'Restore a previously stored status'
406 try:
433 try:
407 target = None
434 target = None
408 collapse = False
435 collapse = False
409 external = nullrev
436 external = nullrev
410 state = {}
437 state = {}
411 f = repo.opener("rebasestate")
438 f = repo.opener("rebasestate")
412 for i, l in enumerate(f.read().splitlines()):
439 for i, l in enumerate(f.read().splitlines()):
413 if i == 0:
440 if i == 0:
414 originalwd = repo[l].rev()
441 originalwd = repo[l].rev()
415 elif i == 1:
442 elif i == 1:
416 target = repo[l].rev()
443 target = repo[l].rev()
417 elif i == 2:
444 elif i == 2:
418 external = repo[l].rev()
445 external = repo[l].rev()
419 elif i == 3:
446 elif i == 3:
420 collapse = bool(int(l))
447 collapse = bool(int(l))
421 elif i == 4:
448 elif i == 4:
422 keep = bool(int(l))
449 keep = bool(int(l))
423 elif i == 5:
450 elif i == 5:
424 keepbranches = bool(int(l))
451 keepbranches = bool(int(l))
425 else:
452 else:
426 oldrev, newrev = l.split(':')
453 oldrev, newrev = l.split(':')
427 state[repo[oldrev].rev()] = repo[newrev].rev()
454 state[repo[oldrev].rev()] = repo[newrev].rev()
428 skipped = set()
455 skipped = set()
429 # recompute the set of skipped revs
456 # recompute the set of skipped revs
430 if not collapse:
457 if not collapse:
431 seen = set([target])
458 seen = set([target])
432 for old, new in sorted(state.items()):
459 for old, new in sorted(state.items()):
433 if new != nullrev and new in seen:
460 if new != nullrev and new in seen:
434 skipped.add(old)
461 skipped.add(old)
435 seen.add(new)
462 seen.add(new)
436 repo.ui.debug('computed skipped revs: %s\n' % skipped)
463 repo.ui.debug('computed skipped revs: %s\n' % skipped)
437 repo.ui.debug('rebase status resumed\n')
464 repo.ui.debug('rebase status resumed\n')
438 return (originalwd, target, state, skipped,
465 return (originalwd, target, state, skipped,
439 collapse, keep, keepbranches, external)
466 collapse, keep, keepbranches, external)
440 except IOError, err:
467 except IOError, err:
441 if err.errno != errno.ENOENT:
468 if err.errno != errno.ENOENT:
442 raise
469 raise
443 raise util.Abort(_('no rebase in progress'))
470 raise util.Abort(_('no rebase in progress'))
444
471
445 def abort(repo, originalwd, target, state):
472 def abort(repo, originalwd, target, state):
446 'Restore the repository to its original state'
473 'Restore the repository to its original state'
447 if set(repo.changelog.descendants(target)) - set(state.values()):
474 if set(repo.changelog.descendants(target)) - set(state.values()):
448 repo.ui.warn(_("warning: new changesets detected on target branch, "
475 repo.ui.warn(_("warning: new changesets detected on target branch, "
449 "can't abort\n"))
476 "can't abort\n"))
450 return -1
477 return -1
451 else:
478 else:
452 # Strip from the first rebased revision
479 # Strip from the first rebased revision
453 merge.update(repo, repo[originalwd].rev(), False, True, False)
480 merge.update(repo, repo[originalwd].rev(), False, True, False)
454 rebased = filter(lambda x: x > -1 and x != target, state.values())
481 rebased = filter(lambda x: x > -1 and x != target, state.values())
455 if rebased:
482 if rebased:
456 strippoint = min(rebased)
483 strippoint = min(rebased)
457 # no backup of rebased cset versions needed
484 # no backup of rebased cset versions needed
458 repair.strip(repo.ui, repo, repo[strippoint].node())
485 repair.strip(repo.ui, repo, repo[strippoint].node())
459 clearstatus(repo)
486 clearstatus(repo)
460 repo.ui.warn(_('rebase aborted\n'))
487 repo.ui.warn(_('rebase aborted\n'))
461 return 0
488 return 0
462
489
463 def buildstate(repo, dest, src, base, detach):
490 def buildstate(repo, dest, src, base, detach):
464 'Define which revisions are going to be rebased and where'
491 'Define which revisions are going to be rebased and where'
465 targetancestors = set()
492 targetancestors = set()
466 detachset = set()
493 detachset = set()
467
494
468 if not dest:
495 if not dest:
469 # Destination defaults to the latest revision in the current branch
496 # Destination defaults to the latest revision in the current branch
470 branch = repo[None].branch()
497 branch = repo[None].branch()
471 dest = repo[branch].rev()
498 dest = repo[branch].rev()
472 else:
499 else:
473 dest = repo[dest].rev()
500 dest = repo[dest].rev()
474
501
475 # This check isn't strictly necessary, since mq detects commits over an
502 # This check isn't strictly necessary, since mq detects commits over an
476 # applied patch. But it prevents messing up the working directory when
503 # applied patch. But it prevents messing up the working directory when
477 # a partially completed rebase is blocked by mq.
504 # a partially completed rebase is blocked by mq.
478 if 'qtip' in repo.tags() and (repo[dest].node() in
505 if 'qtip' in repo.tags() and (repo[dest].node() in
479 [s.node for s in repo.mq.applied]):
506 [s.node for s in repo.mq.applied]):
480 raise util.Abort(_('cannot rebase onto an applied mq patch'))
507 raise util.Abort(_('cannot rebase onto an applied mq patch'))
481
508
482 if src:
509 if src:
483 commonbase = repo[src].ancestor(repo[dest])
510 commonbase = repo[src].ancestor(repo[dest])
484 samebranch = repo[src].branch() == repo[dest].branch()
511 samebranch = repo[src].branch() == repo[dest].branch()
485 if commonbase == repo[src]:
512 if commonbase == repo[src]:
486 raise util.Abort(_('source is ancestor of destination'))
513 raise util.Abort(_('source is ancestor of destination'))
487 if samebranch and commonbase == repo[dest]:
514 if samebranch and commonbase == repo[dest]:
488 raise util.Abort(_('source is descendant of destination'))
515 raise util.Abort(_('source is descendant of destination'))
489 source = repo[src].rev()
516 source = repo[src].rev()
490 if detach:
517 if detach:
491 # We need to keep track of source's ancestors up to the common base
518 # We need to keep track of source's ancestors up to the common base
492 srcancestors = set(repo.changelog.ancestors(source))
519 srcancestors = set(repo.changelog.ancestors(source))
493 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
520 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
494 detachset = srcancestors - baseancestors
521 detachset = srcancestors - baseancestors
495 detachset.discard(commonbase.rev())
522 detachset.discard(commonbase.rev())
496 else:
523 else:
497 if base:
524 if base:
498 cwd = repo[base].rev()
525 cwd = repo[base].rev()
499 else:
526 else:
500 cwd = repo['.'].rev()
527 cwd = repo['.'].rev()
501
528
502 if cwd == dest:
529 if cwd == dest:
503 repo.ui.debug('source and destination are the same\n')
530 repo.ui.debug('source and destination are the same\n')
504 return None
531 return None
505
532
506 targetancestors = set(repo.changelog.ancestors(dest))
533 targetancestors = set(repo.changelog.ancestors(dest))
507 if cwd in targetancestors:
534 if cwd in targetancestors:
508 repo.ui.debug('source is ancestor of destination\n')
535 repo.ui.debug('source is ancestor of destination\n')
509 return None
536 return None
510
537
511 cwdancestors = set(repo.changelog.ancestors(cwd))
538 cwdancestors = set(repo.changelog.ancestors(cwd))
512 if dest in cwdancestors:
539 if dest in cwdancestors:
513 repo.ui.debug('source is descendant of destination\n')
540 repo.ui.debug('source is descendant of destination\n')
514 return None
541 return None
515
542
516 cwdancestors.add(cwd)
543 cwdancestors.add(cwd)
517 rebasingbranch = cwdancestors - targetancestors
544 rebasingbranch = cwdancestors - targetancestors
518 source = min(rebasingbranch)
545 source = min(rebasingbranch)
519
546
520 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
547 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
521 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
548 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
522 state.update(dict.fromkeys(detachset, nullmerge))
549 state.update(dict.fromkeys(detachset, nullmerge))
523 state[source] = nullrev
550 state[source] = nullrev
524 return repo['.'].rev(), repo[dest].rev(), state
551 return repo['.'].rev(), repo[dest].rev(), state
525
552
526 def pullrebase(orig, ui, repo, *args, **opts):
553 def pullrebase(orig, ui, repo, *args, **opts):
527 'Call rebase after pull if the latter has been invoked with --rebase'
554 'Call rebase after pull if the latter has been invoked with --rebase'
528 if opts.get('rebase'):
555 if opts.get('rebase'):
529 if opts.get('update'):
556 if opts.get('update'):
530 del opts['update']
557 del opts['update']
531 ui.debug('--update and --rebase are not compatible, ignoring '
558 ui.debug('--update and --rebase are not compatible, ignoring '
532 'the update flag\n')
559 'the update flag\n')
533
560
534 cmdutil.bailifchanged(repo)
561 cmdutil.bailifchanged(repo)
535 revsprepull = len(repo)
562 revsprepull = len(repo)
536 origpostincoming = commands.postincoming
563 origpostincoming = commands.postincoming
537 def _dummy(*args, **kwargs):
564 def _dummy(*args, **kwargs):
538 pass
565 pass
539 commands.postincoming = _dummy
566 commands.postincoming = _dummy
540 try:
567 try:
541 orig(ui, repo, *args, **opts)
568 orig(ui, repo, *args, **opts)
542 finally:
569 finally:
543 commands.postincoming = origpostincoming
570 commands.postincoming = origpostincoming
544 revspostpull = len(repo)
571 revspostpull = len(repo)
545 if revspostpull > revsprepull:
572 if revspostpull > revsprepull:
546 rebase(ui, repo, **opts)
573 rebase(ui, repo, **opts)
547 branch = repo[None].branch()
574 branch = repo[None].branch()
548 dest = repo[branch].rev()
575 dest = repo[branch].rev()
549 if dest != repo['.'].rev():
576 if dest != repo['.'].rev():
550 # there was nothing to rebase we force an update
577 # there was nothing to rebase we force an update
551 hg.update(repo, dest)
578 hg.update(repo, dest)
552 else:
579 else:
553 orig(ui, repo, *args, **opts)
580 orig(ui, repo, *args, **opts)
554
581
555 def uisetup(ui):
582 def uisetup(ui):
556 'Replace pull with a decorator to provide --rebase option'
583 'Replace pull with a decorator to provide --rebase option'
557 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
584 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
558 entry[1].append(('', 'rebase', None,
585 entry[1].append(('', 'rebase', None,
559 _("rebase working directory to branch head"))
586 _("rebase working directory to branch head"))
560 )
587 )
561
562 cmdtable = {
563 "rebase":
564 (rebase,
565 [
566 ('s', 'source', '',
567 _('rebase from the specified changeset'), _('REV')),
568 ('b', 'base', '',
569 _('rebase from the base of the specified changeset '
570 '(up to greatest common ancestor of base and dest)'),
571 _('REV')),
572 ('d', 'dest', '',
573 _('rebase onto the specified changeset'), _('REV')),
574 ('', 'collapse', False, _('collapse the rebased changesets')),
575 ('m', 'message', '',
576 _('use text as collapse commit message'), _('TEXT')),
577 ('l', 'logfile', '',
578 _('read collapse commit message from file'), _('FILE')),
579 ('', 'keep', False, _('keep original changesets')),
580 ('', 'keepbranches', False, _('keep original branch names')),
581 ('', 'detach', False, _('force detaching of source from its original '
582 'branch')),
583 ('t', 'tool', '', _('specify merge tool')),
584 ('c', 'continue', False, _('continue an interrupted rebase')),
585 ('a', 'abort', False, _('abort an interrupted rebase'))] +
586 templateopts,
587 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
588 'hg rebase {-a|-c}'))
589 }
General Comments 0
You need to be logged in to leave comments. Login now