##// END OF EJS Templates
rebase: drop ancestor import
Matt Mackall -
r13881:086c9f20 default
parent child Browse files
Show More
@@ -1,589 +1,589 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
17 from mercurial import hg, util, repair, merge, cmdutil, commands
18 from mercurial import extensions, ancestor, 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 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 collapsemsg = cmdutil.logmessage(opts)
93 collapsemsg = cmdutil.logmessage(opts)
94 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
94 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
95 keepf = opts.get('keep', False)
95 keepf = opts.get('keep', False)
96 keepbranchesf = opts.get('keepbranches', False)
96 keepbranchesf = opts.get('keepbranches', False)
97 detachf = opts.get('detach', False)
97 detachf = opts.get('detach', False)
98 # keepopen is not meant for use on the command line, but by
98 # keepopen is not meant for use on the command line, but by
99 # other extensions
99 # other extensions
100 keepopen = opts.get('keepopen', False)
100 keepopen = opts.get('keepopen', False)
101
101
102 if collapsemsg and not collapsef:
102 if collapsemsg and not collapsef:
103 raise util.Abort(
103 raise util.Abort(
104 _('message can only be specified with collapse'))
104 _('message can only be specified with collapse'))
105
105
106 if contf or abortf:
106 if contf or abortf:
107 if contf and abortf:
107 if contf and abortf:
108 raise util.Abort(_('cannot use both abort and continue'))
108 raise util.Abort(_('cannot use both abort and continue'))
109 if collapsef:
109 if collapsef:
110 raise util.Abort(
110 raise util.Abort(
111 _('cannot use collapse with continue or abort'))
111 _('cannot use collapse with continue or abort'))
112 if detachf:
112 if detachf:
113 raise util.Abort(_('cannot use detach with continue or abort'))
113 raise util.Abort(_('cannot use detach with continue or abort'))
114 if srcf or basef or destf:
114 if srcf or basef or destf:
115 raise util.Abort(
115 raise util.Abort(
116 _('abort and continue do not allow specifying revisions'))
116 _('abort and continue do not allow specifying revisions'))
117 if opts.get('tool', False):
117 if opts.get('tool', False):
118 ui.warn(_('tool option will be ignored\n'))
118 ui.warn(_('tool option will be ignored\n'))
119
119
120 (originalwd, target, state, skipped, collapsef, keepf,
120 (originalwd, target, state, skipped, collapsef, keepf,
121 keepbranchesf, external) = restorestatus(repo)
121 keepbranchesf, external) = restorestatus(repo)
122 if abortf:
122 if abortf:
123 return abort(repo, originalwd, target, state)
123 return abort(repo, originalwd, target, state)
124 else:
124 else:
125 if srcf and basef:
125 if srcf and basef:
126 raise util.Abort(_('cannot specify both a '
126 raise util.Abort(_('cannot specify both a '
127 'revision and a base'))
127 'revision and a base'))
128 if detachf:
128 if detachf:
129 if not srcf:
129 if not srcf:
130 raise util.Abort(
130 raise util.Abort(
131 _('detach requires a revision to be specified'))
131 _('detach requires a revision to be specified'))
132 if basef:
132 if basef:
133 raise util.Abort(_('cannot specify a base with detach'))
133 raise util.Abort(_('cannot specify a base with detach'))
134
134
135 cmdutil.bail_if_changed(repo)
135 cmdutil.bail_if_changed(repo)
136 result = buildstate(repo, destf, srcf, basef, detachf)
136 result = buildstate(repo, destf, srcf, basef, detachf)
137 if not result:
137 if not result:
138 # Empty state built, nothing to rebase
138 # Empty state built, nothing to rebase
139 ui.status(_('nothing to rebase\n'))
139 ui.status(_('nothing to rebase\n'))
140 return 1
140 return 1
141 else:
141 else:
142 originalwd, target, state = result
142 originalwd, target, state = result
143 if collapsef:
143 if collapsef:
144 targetancestors = set(repo.changelog.ancestors(target))
144 targetancestors = set(repo.changelog.ancestors(target))
145 external = checkexternal(repo, state, targetancestors)
145 external = checkexternal(repo, state, targetancestors)
146
146
147 if keepbranchesf:
147 if keepbranchesf:
148 assert not extrafn, 'cannot use both keepbranches and extrafn'
148 assert not extrafn, 'cannot use both keepbranches and extrafn'
149 def extrafn(ctx, extra):
149 def extrafn(ctx, extra):
150 extra['branch'] = ctx.branch()
150 extra['branch'] = ctx.branch()
151
151
152 # Rebase
152 # Rebase
153 if not targetancestors:
153 if not targetancestors:
154 targetancestors = set(repo.changelog.ancestors(target))
154 targetancestors = set(repo.changelog.ancestors(target))
155 targetancestors.add(target)
155 targetancestors.add(target)
156
156
157 sortedstate = sorted(state)
157 sortedstate = sorted(state)
158 total = len(sortedstate)
158 total = len(sortedstate)
159 pos = 0
159 pos = 0
160 for rev in sortedstate:
160 for rev in sortedstate:
161 pos += 1
161 pos += 1
162 if state[rev] == -1:
162 if state[rev] == -1:
163 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
163 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
164 _('changesets'), total)
164 _('changesets'), total)
165 storestatus(repo, originalwd, target, state, collapsef, keepf,
165 storestatus(repo, originalwd, target, state, collapsef, keepf,
166 keepbranchesf, external)
166 keepbranchesf, external)
167 p1, p2 = defineparents(repo, rev, target, state,
167 p1, p2 = defineparents(repo, rev, target, state,
168 targetancestors)
168 targetancestors)
169 if len(repo.parents()) == 2:
169 if len(repo.parents()) == 2:
170 repo.ui.debug('resuming interrupted rebase\n')
170 repo.ui.debug('resuming interrupted rebase\n')
171 else:
171 else:
172 try:
172 try:
173 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
173 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
174 stats = rebasenode(repo, rev, p1, state)
174 stats = rebasenode(repo, rev, p1, state)
175 if stats and stats[3] > 0:
175 if stats and stats[3] > 0:
176 raise util.Abort(_('unresolved conflicts (see hg '
176 raise util.Abort(_('unresolved conflicts (see hg '
177 'resolve, then hg rebase --continue)'))
177 'resolve, then hg rebase --continue)'))
178 finally:
178 finally:
179 ui.setconfig('ui', 'forcemerge', '')
179 ui.setconfig('ui', 'forcemerge', '')
180 updatedirstate(repo, rev, target, p2)
180 updatedirstate(repo, rev, target, p2)
181 if not collapsef:
181 if not collapsef:
182 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn)
182 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn)
183 else:
183 else:
184 # Skip commit if we are collapsing
184 # Skip commit if we are collapsing
185 repo.dirstate.setparents(repo[p1].node())
185 repo.dirstate.setparents(repo[p1].node())
186 newrev = None
186 newrev = None
187 # Update the state
187 # Update the state
188 if newrev is not None:
188 if newrev is not None:
189 state[rev] = repo[newrev].rev()
189 state[rev] = repo[newrev].rev()
190 else:
190 else:
191 if not collapsef:
191 if not collapsef:
192 ui.note(_('no changes, revision %d skipped\n') % rev)
192 ui.note(_('no changes, revision %d skipped\n') % rev)
193 ui.debug('next revision set to %s\n' % p1)
193 ui.debug('next revision set to %s\n' % p1)
194 skipped.add(rev)
194 skipped.add(rev)
195 state[rev] = p1
195 state[rev] = p1
196
196
197 ui.progress(_('rebasing'), None)
197 ui.progress(_('rebasing'), None)
198 ui.note(_('rebase merging completed\n'))
198 ui.note(_('rebase merging completed\n'))
199
199
200 if collapsef and not keepopen:
200 if collapsef and not keepopen:
201 p1, p2 = defineparents(repo, min(state), target,
201 p1, p2 = defineparents(repo, min(state), target,
202 state, targetancestors)
202 state, targetancestors)
203 if collapsemsg:
203 if collapsemsg:
204 commitmsg = collapsemsg
204 commitmsg = collapsemsg
205 else:
205 else:
206 commitmsg = 'Collapsed revision'
206 commitmsg = 'Collapsed revision'
207 for rebased in state:
207 for rebased in state:
208 if rebased not in skipped and state[rebased] != nullmerge:
208 if rebased not in skipped and state[rebased] != nullmerge:
209 commitmsg += '\n* %s' % repo[rebased].description()
209 commitmsg += '\n* %s' % repo[rebased].description()
210 commitmsg = ui.edit(commitmsg, repo.ui.username())
210 commitmsg = ui.edit(commitmsg, repo.ui.username())
211 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
211 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
212 extrafn=extrafn)
212 extrafn=extrafn)
213
213
214 if 'qtip' in repo.tags():
214 if 'qtip' in repo.tags():
215 updatemq(repo, state, skipped, **opts)
215 updatemq(repo, state, skipped, **opts)
216
216
217 if not keepf:
217 if not keepf:
218 # Remove no more useful revisions
218 # Remove no more useful revisions
219 rebased = [rev for rev in state if state[rev] != nullmerge]
219 rebased = [rev for rev in state if state[rev] != nullmerge]
220 if rebased:
220 if rebased:
221 if set(repo.changelog.descendants(min(rebased))) - set(state):
221 if set(repo.changelog.descendants(min(rebased))) - set(state):
222 ui.warn(_("warning: new changesets detected "
222 ui.warn(_("warning: new changesets detected "
223 "on source branch, not stripping\n"))
223 "on source branch, not stripping\n"))
224 else:
224 else:
225 # backup the old csets by default
225 # backup the old csets by default
226 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
226 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
227
227
228 clearstatus(repo)
228 clearstatus(repo)
229 ui.note(_("rebase completed\n"))
229 ui.note(_("rebase completed\n"))
230 if os.path.exists(repo.sjoin('undo')):
230 if os.path.exists(repo.sjoin('undo')):
231 util.unlinkpath(repo.sjoin('undo'))
231 util.unlinkpath(repo.sjoin('undo'))
232 if skipped:
232 if skipped:
233 ui.note(_("%d revisions have been skipped\n") % len(skipped))
233 ui.note(_("%d revisions have been skipped\n") % len(skipped))
234 finally:
234 finally:
235 release(lock, wlock)
235 release(lock, wlock)
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 and k in m2:
267 if v in m2 and v not in m1 and k in m2:
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 ctx = repo[rev]
275 if commitmsg is None:
275 if commitmsg is None:
276 commitmsg = ctx.description()
276 commitmsg = ctx.description()
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, state):
290 def rebasenode(repo, rev, p1, 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 base = None
301 base = None
302 if repo[rev].rev() != repo[min(state)].rev():
302 if repo[rev].rev() != repo[min(state)].rev():
303 base = repo[rev].p1().node()
303 base = repo[rev].p1().node()
304 return merge.update(repo, rev, True, True, False, base)
304 return merge.update(repo, rev, True, True, False, base)
305
305
306 def defineparents(repo, rev, target, state, targetancestors):
306 def defineparents(repo, rev, target, state, targetancestors):
307 'Return the new parent relationship of the revision that will be rebased'
307 'Return the new parent relationship of the revision that will be rebased'
308 parents = repo[rev].parents()
308 parents = repo[rev].parents()
309 p1 = p2 = nullrev
309 p1 = p2 = nullrev
310
310
311 P1n = parents[0].rev()
311 P1n = parents[0].rev()
312 if P1n in targetancestors:
312 if P1n in targetancestors:
313 p1 = target
313 p1 = target
314 elif P1n in state:
314 elif P1n in state:
315 if state[P1n] == nullmerge:
315 if state[P1n] == nullmerge:
316 p1 = target
316 p1 = target
317 else:
317 else:
318 p1 = state[P1n]
318 p1 = state[P1n]
319 else: # P1n external
319 else: # P1n external
320 p1 = target
320 p1 = target
321 p2 = P1n
321 p2 = P1n
322
322
323 if len(parents) == 2 and parents[1].rev() not in targetancestors:
323 if len(parents) == 2 and parents[1].rev() not in targetancestors:
324 P2n = parents[1].rev()
324 P2n = parents[1].rev()
325 # interesting second parent
325 # interesting second parent
326 if P2n in state:
326 if P2n in state:
327 if p1 == target: # P1n in targetancestors or external
327 if p1 == target: # P1n in targetancestors or external
328 p1 = state[P2n]
328 p1 = state[P2n]
329 else:
329 else:
330 p2 = state[P2n]
330 p2 = state[P2n]
331 else: # P2n external
331 else: # P2n external
332 if p2 != nullrev: # P1n external too => rev is a merged revision
332 if p2 != nullrev: # P1n external too => rev is a merged revision
333 raise util.Abort(_('cannot use revision %d as base, result '
333 raise util.Abort(_('cannot use revision %d as base, result '
334 'would have 3 parents') % rev)
334 'would have 3 parents') % rev)
335 p2 = P2n
335 p2 = P2n
336 repo.ui.debug(" future parents are %d and %d\n" %
336 repo.ui.debug(" future parents are %d and %d\n" %
337 (repo[p1].rev(), repo[p2].rev()))
337 (repo[p1].rev(), repo[p2].rev()))
338 return p1, p2
338 return p1, p2
339
339
340 def isagitpatch(repo, patchname):
340 def isagitpatch(repo, patchname):
341 'Return true if the given patch is in git format'
341 'Return true if the given patch is in git format'
342 mqpatch = os.path.join(repo.mq.path, patchname)
342 mqpatch = os.path.join(repo.mq.path, patchname)
343 for line in patch.linereader(file(mqpatch, 'rb')):
343 for line in patch.linereader(file(mqpatch, 'rb')):
344 if line.startswith('diff --git'):
344 if line.startswith('diff --git'):
345 return True
345 return True
346 return False
346 return False
347
347
348 def updatemq(repo, state, skipped, **opts):
348 def updatemq(repo, state, skipped, **opts):
349 'Update rebased mq patches - finalize and then import them'
349 'Update rebased mq patches - finalize and then import them'
350 mqrebase = {}
350 mqrebase = {}
351 mq = repo.mq
351 mq = repo.mq
352 original_series = mq.full_series[:]
352 original_series = mq.full_series[:]
353
353
354 for p in mq.applied:
354 for p in mq.applied:
355 rev = repo[p.node].rev()
355 rev = repo[p.node].rev()
356 if rev in state:
356 if rev in state:
357 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
357 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
358 (rev, p.name))
358 (rev, p.name))
359 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
359 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
360
360
361 if mqrebase:
361 if mqrebase:
362 mq.finish(repo, mqrebase.keys())
362 mq.finish(repo, mqrebase.keys())
363
363
364 # We must start import from the newest revision
364 # We must start import from the newest revision
365 for rev in sorted(mqrebase, reverse=True):
365 for rev in sorted(mqrebase, reverse=True):
366 if rev not in skipped:
366 if rev not in skipped:
367 name, isgit = mqrebase[rev]
367 name, isgit = mqrebase[rev]
368 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
368 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
369 mq.qimport(repo, (), patchname=name, git=isgit,
369 mq.qimport(repo, (), patchname=name, git=isgit,
370 rev=[str(state[rev])])
370 rev=[str(state[rev])])
371
371
372 # Restore missing guards
372 # Restore missing guards
373 for s in original_series:
373 for s in original_series:
374 pname = mq.guard_re.split(s, 1)[0]
374 pname = mq.guard_re.split(s, 1)[0]
375 if pname in mq.full_series:
375 if pname in mq.full_series:
376 repo.ui.debug('restoring guard for patch %s' % (pname))
376 repo.ui.debug('restoring guard for patch %s' % (pname))
377 mq.full_series.remove(pname)
377 mq.full_series.remove(pname)
378 mq.full_series.append(s)
378 mq.full_series.append(s)
379 mq.series_dirty = True
379 mq.series_dirty = True
380 mq.save_dirty()
380 mq.save_dirty()
381
381
382 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
382 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
383 external):
383 external):
384 'Store the current status to allow recovery'
384 'Store the current status to allow recovery'
385 f = repo.opener("rebasestate", "w")
385 f = repo.opener("rebasestate", "w")
386 f.write(repo[originalwd].hex() + '\n')
386 f.write(repo[originalwd].hex() + '\n')
387 f.write(repo[target].hex() + '\n')
387 f.write(repo[target].hex() + '\n')
388 f.write(repo[external].hex() + '\n')
388 f.write(repo[external].hex() + '\n')
389 f.write('%d\n' % int(collapse))
389 f.write('%d\n' % int(collapse))
390 f.write('%d\n' % int(keep))
390 f.write('%d\n' % int(keep))
391 f.write('%d\n' % int(keepbranches))
391 f.write('%d\n' % int(keepbranches))
392 for d, v in state.iteritems():
392 for d, v in state.iteritems():
393 oldrev = repo[d].hex()
393 oldrev = repo[d].hex()
394 newrev = repo[v].hex()
394 newrev = repo[v].hex()
395 f.write("%s:%s\n" % (oldrev, newrev))
395 f.write("%s:%s\n" % (oldrev, newrev))
396 f.close()
396 f.close()
397 repo.ui.debug('rebase status stored\n')
397 repo.ui.debug('rebase status stored\n')
398
398
399 def clearstatus(repo):
399 def clearstatus(repo):
400 'Remove the status files'
400 'Remove the status files'
401 if os.path.exists(repo.join("rebasestate")):
401 if os.path.exists(repo.join("rebasestate")):
402 util.unlinkpath(repo.join("rebasestate"))
402 util.unlinkpath(repo.join("rebasestate"))
403
403
404 def restorestatus(repo):
404 def restorestatus(repo):
405 'Restore a previously stored status'
405 'Restore a previously stored status'
406 try:
406 try:
407 target = None
407 target = None
408 collapse = False
408 collapse = False
409 external = nullrev
409 external = nullrev
410 state = {}
410 state = {}
411 f = repo.opener("rebasestate")
411 f = repo.opener("rebasestate")
412 for i, l in enumerate(f.read().splitlines()):
412 for i, l in enumerate(f.read().splitlines()):
413 if i == 0:
413 if i == 0:
414 originalwd = repo[l].rev()
414 originalwd = repo[l].rev()
415 elif i == 1:
415 elif i == 1:
416 target = repo[l].rev()
416 target = repo[l].rev()
417 elif i == 2:
417 elif i == 2:
418 external = repo[l].rev()
418 external = repo[l].rev()
419 elif i == 3:
419 elif i == 3:
420 collapse = bool(int(l))
420 collapse = bool(int(l))
421 elif i == 4:
421 elif i == 4:
422 keep = bool(int(l))
422 keep = bool(int(l))
423 elif i == 5:
423 elif i == 5:
424 keepbranches = bool(int(l))
424 keepbranches = bool(int(l))
425 else:
425 else:
426 oldrev, newrev = l.split(':')
426 oldrev, newrev = l.split(':')
427 state[repo[oldrev].rev()] = repo[newrev].rev()
427 state[repo[oldrev].rev()] = repo[newrev].rev()
428 skipped = set()
428 skipped = set()
429 # recompute the set of skipped revs
429 # recompute the set of skipped revs
430 if not collapse:
430 if not collapse:
431 seen = set([target])
431 seen = set([target])
432 for old, new in sorted(state.items()):
432 for old, new in sorted(state.items()):
433 if new != nullrev and new in seen:
433 if new != nullrev and new in seen:
434 skipped.add(old)
434 skipped.add(old)
435 seen.add(new)
435 seen.add(new)
436 repo.ui.debug('computed skipped revs: %s\n' % skipped)
436 repo.ui.debug('computed skipped revs: %s\n' % skipped)
437 repo.ui.debug('rebase status resumed\n')
437 repo.ui.debug('rebase status resumed\n')
438 return (originalwd, target, state, skipped,
438 return (originalwd, target, state, skipped,
439 collapse, keep, keepbranches, external)
439 collapse, keep, keepbranches, external)
440 except IOError, err:
440 except IOError, err:
441 if err.errno != errno.ENOENT:
441 if err.errno != errno.ENOENT:
442 raise
442 raise
443 raise util.Abort(_('no rebase in progress'))
443 raise util.Abort(_('no rebase in progress'))
444
444
445 def abort(repo, originalwd, target, state):
445 def abort(repo, originalwd, target, state):
446 'Restore the repository to its original state'
446 'Restore the repository to its original state'
447 if set(repo.changelog.descendants(target)) - set(state.values()):
447 if set(repo.changelog.descendants(target)) - set(state.values()):
448 repo.ui.warn(_("warning: new changesets detected on target branch, "
448 repo.ui.warn(_("warning: new changesets detected on target branch, "
449 "can't abort\n"))
449 "can't abort\n"))
450 return -1
450 return -1
451 else:
451 else:
452 # Strip from the first rebased revision
452 # Strip from the first rebased revision
453 merge.update(repo, repo[originalwd].rev(), False, True, False)
453 merge.update(repo, repo[originalwd].rev(), False, True, False)
454 rebased = filter(lambda x: x > -1 and x != target, state.values())
454 rebased = filter(lambda x: x > -1 and x != target, state.values())
455 if rebased:
455 if rebased:
456 strippoint = min(rebased)
456 strippoint = min(rebased)
457 # no backup of rebased cset versions needed
457 # no backup of rebased cset versions needed
458 repair.strip(repo.ui, repo, repo[strippoint].node())
458 repair.strip(repo.ui, repo, repo[strippoint].node())
459 clearstatus(repo)
459 clearstatus(repo)
460 repo.ui.warn(_('rebase aborted\n'))
460 repo.ui.warn(_('rebase aborted\n'))
461 return 0
461 return 0
462
462
463 def buildstate(repo, dest, src, base, detach):
463 def buildstate(repo, dest, src, base, detach):
464 'Define which revisions are going to be rebased and where'
464 'Define which revisions are going to be rebased and where'
465 targetancestors = set()
465 targetancestors = set()
466 detachset = set()
466 detachset = set()
467
467
468 if not dest:
468 if not dest:
469 # Destination defaults to the latest revision in the current branch
469 # Destination defaults to the latest revision in the current branch
470 branch = repo[None].branch()
470 branch = repo[None].branch()
471 dest = repo[branch].rev()
471 dest = repo[branch].rev()
472 else:
472 else:
473 dest = repo[dest].rev()
473 dest = repo[dest].rev()
474
474
475 # This check isn't strictly necessary, since mq detects commits over an
475 # This check isn't strictly necessary, since mq detects commits over an
476 # applied patch. But it prevents messing up the working directory when
476 # applied patch. But it prevents messing up the working directory when
477 # a partially completed rebase is blocked by mq.
477 # a partially completed rebase is blocked by mq.
478 if 'qtip' in repo.tags() and (repo[dest].node() in
478 if 'qtip' in repo.tags() and (repo[dest].node() in
479 [s.node for s in repo.mq.applied]):
479 [s.node for s in repo.mq.applied]):
480 raise util.Abort(_('cannot rebase onto an applied mq patch'))
480 raise util.Abort(_('cannot rebase onto an applied mq patch'))
481
481
482 if src:
482 if src:
483 commonbase = repo[src].ancestor(repo[dest])
483 commonbase = repo[src].ancestor(repo[dest])
484 samebranch = repo[src].branch() == repo[dest].branch()
484 samebranch = repo[src].branch() == repo[dest].branch()
485 if commonbase == repo[src]:
485 if commonbase == repo[src]:
486 raise util.Abort(_('source is ancestor of destination'))
486 raise util.Abort(_('source is ancestor of destination'))
487 if samebranch and commonbase == repo[dest]:
487 if samebranch and commonbase == repo[dest]:
488 raise util.Abort(_('source is descendant of destination'))
488 raise util.Abort(_('source is descendant of destination'))
489 source = repo[src].rev()
489 source = repo[src].rev()
490 if detach:
490 if detach:
491 # We need to keep track of source's ancestors up to the common base
491 # We need to keep track of source's ancestors up to the common base
492 srcancestors = set(repo.changelog.ancestors(source))
492 srcancestors = set(repo.changelog.ancestors(source))
493 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
493 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
494 detachset = srcancestors - baseancestors
494 detachset = srcancestors - baseancestors
495 detachset.discard(commonbase.rev())
495 detachset.discard(commonbase.rev())
496 else:
496 else:
497 if base:
497 if base:
498 cwd = repo[base].rev()
498 cwd = repo[base].rev()
499 else:
499 else:
500 cwd = repo['.'].rev()
500 cwd = repo['.'].rev()
501
501
502 if cwd == dest:
502 if cwd == dest:
503 repo.ui.debug('source and destination are the same\n')
503 repo.ui.debug('source and destination are the same\n')
504 return None
504 return None
505
505
506 targetancestors = set(repo.changelog.ancestors(dest))
506 targetancestors = set(repo.changelog.ancestors(dest))
507 if cwd in targetancestors:
507 if cwd in targetancestors:
508 repo.ui.debug('source is ancestor of destination\n')
508 repo.ui.debug('source is ancestor of destination\n')
509 return None
509 return None
510
510
511 cwdancestors = set(repo.changelog.ancestors(cwd))
511 cwdancestors = set(repo.changelog.ancestors(cwd))
512 if dest in cwdancestors:
512 if dest in cwdancestors:
513 repo.ui.debug('source is descendant of destination\n')
513 repo.ui.debug('source is descendant of destination\n')
514 return None
514 return None
515
515
516 cwdancestors.add(cwd)
516 cwdancestors.add(cwd)
517 rebasingbranch = cwdancestors - targetancestors
517 rebasingbranch = cwdancestors - targetancestors
518 source = min(rebasingbranch)
518 source = min(rebasingbranch)
519
519
520 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
520 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
521 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
521 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
522 state.update(dict.fromkeys(detachset, nullmerge))
522 state.update(dict.fromkeys(detachset, nullmerge))
523 state[source] = nullrev
523 state[source] = nullrev
524 return repo['.'].rev(), repo[dest].rev(), state
524 return repo['.'].rev(), repo[dest].rev(), state
525
525
526 def pullrebase(orig, ui, repo, *args, **opts):
526 def pullrebase(orig, ui, repo, *args, **opts):
527 'Call rebase after pull if the latter has been invoked with --rebase'
527 'Call rebase after pull if the latter has been invoked with --rebase'
528 if opts.get('rebase'):
528 if opts.get('rebase'):
529 if opts.get('update'):
529 if opts.get('update'):
530 del opts['update']
530 del opts['update']
531 ui.debug('--update and --rebase are not compatible, ignoring '
531 ui.debug('--update and --rebase are not compatible, ignoring '
532 'the update flag\n')
532 'the update flag\n')
533
533
534 cmdutil.bail_if_changed(repo)
534 cmdutil.bail_if_changed(repo)
535 revsprepull = len(repo)
535 revsprepull = len(repo)
536 origpostincoming = commands.postincoming
536 origpostincoming = commands.postincoming
537 def _dummy(*args, **kwargs):
537 def _dummy(*args, **kwargs):
538 pass
538 pass
539 commands.postincoming = _dummy
539 commands.postincoming = _dummy
540 try:
540 try:
541 orig(ui, repo, *args, **opts)
541 orig(ui, repo, *args, **opts)
542 finally:
542 finally:
543 commands.postincoming = origpostincoming
543 commands.postincoming = origpostincoming
544 revspostpull = len(repo)
544 revspostpull = len(repo)
545 if revspostpull > revsprepull:
545 if revspostpull > revsprepull:
546 rebase(ui, repo, **opts)
546 rebase(ui, repo, **opts)
547 branch = repo[None].branch()
547 branch = repo[None].branch()
548 dest = repo[branch].rev()
548 dest = repo[branch].rev()
549 if dest != repo['.'].rev():
549 if dest != repo['.'].rev():
550 # there was nothing to rebase we force an update
550 # there was nothing to rebase we force an update
551 hg.update(repo, dest)
551 hg.update(repo, dest)
552 else:
552 else:
553 orig(ui, repo, *args, **opts)
553 orig(ui, repo, *args, **opts)
554
554
555 def uisetup(ui):
555 def uisetup(ui):
556 'Replace pull with a decorator to provide --rebase option'
556 'Replace pull with a decorator to provide --rebase option'
557 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
557 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
558 entry[1].append(('', 'rebase', None,
558 entry[1].append(('', 'rebase', None,
559 _("rebase working directory to branch head"))
559 _("rebase working directory to branch head"))
560 )
560 )
561
561
562 cmdtable = {
562 cmdtable = {
563 "rebase":
563 "rebase":
564 (rebase,
564 (rebase,
565 [
565 [
566 ('s', 'source', '',
566 ('s', 'source', '',
567 _('rebase from the specified changeset'), _('REV')),
567 _('rebase from the specified changeset'), _('REV')),
568 ('b', 'base', '',
568 ('b', 'base', '',
569 _('rebase from the base of the specified changeset '
569 _('rebase from the base of the specified changeset '
570 '(up to greatest common ancestor of base and dest)'),
570 '(up to greatest common ancestor of base and dest)'),
571 _('REV')),
571 _('REV')),
572 ('d', 'dest', '',
572 ('d', 'dest', '',
573 _('rebase onto the specified changeset'), _('REV')),
573 _('rebase onto the specified changeset'), _('REV')),
574 ('', 'collapse', False, _('collapse the rebased changesets')),
574 ('', 'collapse', False, _('collapse the rebased changesets')),
575 ('m', 'message', '',
575 ('m', 'message', '',
576 _('use text as collapse commit message'), _('TEXT')),
576 _('use text as collapse commit message'), _('TEXT')),
577 ('l', 'logfile', '',
577 ('l', 'logfile', '',
578 _('read collapse commit message from file'), _('FILE')),
578 _('read collapse commit message from file'), _('FILE')),
579 ('', 'keep', False, _('keep original changesets')),
579 ('', 'keep', False, _('keep original changesets')),
580 ('', 'keepbranches', False, _('keep original branch names')),
580 ('', 'keepbranches', False, _('keep original branch names')),
581 ('', 'detach', False, _('force detaching of source from its original '
581 ('', 'detach', False, _('force detaching of source from its original '
582 'branch')),
582 'branch')),
583 ('t', 'tool', '', _('specify merge tool')),
583 ('t', 'tool', '', _('specify merge tool')),
584 ('c', 'continue', False, _('continue an interrupted rebase')),
584 ('c', 'continue', False, _('continue an interrupted rebase')),
585 ('a', 'abort', False, _('abort an interrupted rebase'))] +
585 ('a', 'abort', False, _('abort an interrupted rebase'))] +
586 templateopts,
586 templateopts,
587 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
587 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
588 'hg rebase {-a|-c}'))
588 'hg rebase {-a|-c}'))
589 }
589 }
General Comments 0
You need to be logged in to leave comments. Login now