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