##// END OF EJS Templates
rebase: link to RebaseExtension...
Martin Geisler -
r9301:ad4501d2 default
parent child Browse files
Show More
@@ -1,471 +1,471 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, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
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/RebaseProject
14 http://mercurial.selenic.com/wiki/RebaseExtension
15 '''
15 '''
16
16
17 from mercurial import util, repair, merge, cmdutil, commands, error
17 from mercurial import 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 def rebasemerge(repo, rev, first=False):
25 def rebasemerge(repo, rev, first=False):
26 'return the correct ancestor'
26 'return the correct ancestor'
27 oldancestor = ancestor.ancestor
27 oldancestor = ancestor.ancestor
28
28
29 def newancestor(a, b, pfunc):
29 def newancestor(a, b, pfunc):
30 ancestor.ancestor = oldancestor
30 ancestor.ancestor = oldancestor
31 if b == rev:
31 if b == rev:
32 return repo[rev].parents()[0].rev()
32 return repo[rev].parents()[0].rev()
33 return ancestor.ancestor(a, b, pfunc)
33 return ancestor.ancestor(a, b, pfunc)
34
34
35 if not first:
35 if not first:
36 ancestor.ancestor = newancestor
36 ancestor.ancestor = newancestor
37 else:
37 else:
38 repo.ui.debug(_("first revision, do not change ancestor\n"))
38 repo.ui.debug(_("first revision, do not change ancestor\n"))
39 stats = merge.update(repo, rev, True, True, False)
39 stats = merge.update(repo, rev, True, True, False)
40 return stats
40 return stats
41
41
42 def rebase(ui, repo, **opts):
42 def rebase(ui, repo, **opts):
43 """move changeset (and descendants) to a different branch
43 """move changeset (and descendants) to a different branch
44
44
45 Rebase uses repeated merging to graft changesets from one part of
45 Rebase uses repeated merging to graft changesets from one part of
46 history onto another. This can be useful for linearizing local
46 history onto another. This can be useful for linearizing local
47 changes relative to a master development tree.
47 changes relative to a master development tree.
48
48
49 If a rebase is interrupted to manually resolve a merge, it can be
49 If a rebase is interrupted to manually resolve a merge, it can be
50 continued with --continue/-c or aborted with --abort/-a.
50 continued with --continue/-c or aborted with --abort/-a.
51 """
51 """
52 originalwd = target = None
52 originalwd = target = None
53 external = nullrev
53 external = nullrev
54 state = {}
54 state = {}
55 skipped = set()
55 skipped = set()
56
56
57 lock = wlock = None
57 lock = wlock = None
58 try:
58 try:
59 lock = repo.lock()
59 lock = repo.lock()
60 wlock = repo.wlock()
60 wlock = repo.wlock()
61
61
62 # Validate input and define rebasing points
62 # Validate input and define rebasing points
63 destf = opts.get('dest', None)
63 destf = opts.get('dest', None)
64 srcf = opts.get('source', None)
64 srcf = opts.get('source', None)
65 basef = opts.get('base', None)
65 basef = opts.get('base', None)
66 contf = opts.get('continue')
66 contf = opts.get('continue')
67 abortf = opts.get('abort')
67 abortf = opts.get('abort')
68 collapsef = opts.get('collapse', False)
68 collapsef = opts.get('collapse', False)
69 extrafn = opts.get('extrafn')
69 extrafn = opts.get('extrafn')
70 keepf = opts.get('keep', False)
70 keepf = opts.get('keep', False)
71 keepbranchesf = opts.get('keepbranches', False)
71 keepbranchesf = opts.get('keepbranches', False)
72
72
73 if contf or abortf:
73 if contf or abortf:
74 if contf and abortf:
74 if contf and abortf:
75 raise error.ParseError('rebase',
75 raise error.ParseError('rebase',
76 _('cannot use both abort and continue'))
76 _('cannot use both abort and continue'))
77 if collapsef:
77 if collapsef:
78 raise error.ParseError(
78 raise error.ParseError(
79 'rebase', _('cannot use collapse with continue or abort'))
79 'rebase', _('cannot use collapse with continue or abort'))
80
80
81 if srcf or basef or destf:
81 if srcf or basef or destf:
82 raise error.ParseError('rebase',
82 raise error.ParseError('rebase',
83 _('abort and continue do not allow specifying revisions'))
83 _('abort and continue do not allow specifying revisions'))
84
84
85 (originalwd, target, state, collapsef, keepf,
85 (originalwd, target, state, collapsef, keepf,
86 keepbranchesf, external) = restorestatus(repo)
86 keepbranchesf, external) = restorestatus(repo)
87 if abortf:
87 if abortf:
88 abort(repo, originalwd, target, state)
88 abort(repo, originalwd, target, state)
89 return
89 return
90 else:
90 else:
91 if srcf and basef:
91 if srcf and basef:
92 raise error.ParseError('rebase', _('cannot specify both a '
92 raise error.ParseError('rebase', _('cannot specify both a '
93 'revision and a base'))
93 'revision and a base'))
94 cmdutil.bail_if_changed(repo)
94 cmdutil.bail_if_changed(repo)
95 result = buildstate(repo, destf, srcf, basef, collapsef)
95 result = buildstate(repo, destf, srcf, basef, collapsef)
96 if result:
96 if result:
97 originalwd, target, state, external = result
97 originalwd, target, state, external = result
98 else: # Empty state built, nothing to rebase
98 else: # Empty state built, nothing to rebase
99 ui.status(_('nothing to rebase\n'))
99 ui.status(_('nothing to rebase\n'))
100 return
100 return
101
101
102 if keepbranchesf:
102 if keepbranchesf:
103 if extrafn:
103 if extrafn:
104 raise error.ParseError(
104 raise error.ParseError(
105 'rebase', _('cannot use both keepbranches and extrafn'))
105 'rebase', _('cannot use both keepbranches and extrafn'))
106 def extrafn(ctx, extra):
106 def extrafn(ctx, extra):
107 extra['branch'] = ctx.branch()
107 extra['branch'] = ctx.branch()
108
108
109 # Rebase
109 # Rebase
110 targetancestors = list(repo.changelog.ancestors(target))
110 targetancestors = list(repo.changelog.ancestors(target))
111 targetancestors.append(target)
111 targetancestors.append(target)
112
112
113 for rev in sorted(state):
113 for rev in sorted(state):
114 if state[rev] == -1:
114 if state[rev] == -1:
115 storestatus(repo, originalwd, target, state, collapsef, keepf,
115 storestatus(repo, originalwd, target, state, collapsef, keepf,
116 keepbranchesf, external)
116 keepbranchesf, external)
117 rebasenode(repo, rev, target, state, skipped, targetancestors,
117 rebasenode(repo, rev, target, state, skipped, targetancestors,
118 collapsef, extrafn)
118 collapsef, extrafn)
119 ui.note(_('rebase merging completed\n'))
119 ui.note(_('rebase merging completed\n'))
120
120
121 if collapsef:
121 if collapsef:
122 p1, p2 = defineparents(repo, min(state), target,
122 p1, p2 = defineparents(repo, min(state), target,
123 state, targetancestors)
123 state, targetancestors)
124 concludenode(repo, rev, p1, external, state, collapsef,
124 concludenode(repo, rev, p1, external, state, collapsef,
125 last=True, skipped=skipped, extrafn=extrafn)
125 last=True, skipped=skipped, extrafn=extrafn)
126
126
127 if 'qtip' in repo.tags():
127 if 'qtip' in repo.tags():
128 updatemq(repo, state, skipped, **opts)
128 updatemq(repo, state, skipped, **opts)
129
129
130 if not keepf:
130 if not keepf:
131 # Remove no more useful revisions
131 # Remove no more useful revisions
132 if set(repo.changelog.descendants(min(state))) - set(state):
132 if set(repo.changelog.descendants(min(state))) - set(state):
133 ui.warn(_("warning: new changesets detected on source branch, "
133 ui.warn(_("warning: new changesets detected on source branch, "
134 "not stripping\n"))
134 "not stripping\n"))
135 else:
135 else:
136 repair.strip(ui, repo, repo[min(state)].node(), "strip")
136 repair.strip(ui, repo, repo[min(state)].node(), "strip")
137
137
138 clearstatus(repo)
138 clearstatus(repo)
139 ui.status(_("rebase completed\n"))
139 ui.status(_("rebase completed\n"))
140 if os.path.exists(repo.sjoin('undo')):
140 if os.path.exists(repo.sjoin('undo')):
141 util.unlink(repo.sjoin('undo'))
141 util.unlink(repo.sjoin('undo'))
142 if skipped:
142 if skipped:
143 ui.note(_("%d revisions have been skipped\n") % len(skipped))
143 ui.note(_("%d revisions have been skipped\n") % len(skipped))
144 finally:
144 finally:
145 release(lock, wlock)
145 release(lock, wlock)
146
146
147 def concludenode(repo, rev, p1, p2, state, collapse, last=False, skipped=None,
147 def concludenode(repo, rev, p1, p2, state, collapse, last=False, skipped=None,
148 extrafn=None):
148 extrafn=None):
149 """Skip commit if collapsing has been required and rev is not the last
149 """Skip commit if collapsing has been required and rev is not the last
150 revision, commit otherwise
150 revision, commit otherwise
151 """
151 """
152 repo.ui.debug(_(" set parents\n"))
152 repo.ui.debug(_(" set parents\n"))
153 if collapse and not last:
153 if collapse and not last:
154 repo.dirstate.setparents(repo[p1].node())
154 repo.dirstate.setparents(repo[p1].node())
155 return None
155 return None
156
156
157 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
157 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
158
158
159 if skipped is None:
159 if skipped is None:
160 skipped = set()
160 skipped = set()
161
161
162 # Commit, record the old nodeid
162 # Commit, record the old nodeid
163 newrev = nullrev
163 newrev = nullrev
164 try:
164 try:
165 if last:
165 if last:
166 # we don't translate commit messages
166 # we don't translate commit messages
167 commitmsg = 'Collapsed revision'
167 commitmsg = 'Collapsed revision'
168 for rebased in state:
168 for rebased in state:
169 if rebased not in skipped:
169 if rebased not in skipped:
170 commitmsg += '\n* %s' % repo[rebased].description()
170 commitmsg += '\n* %s' % repo[rebased].description()
171 commitmsg = repo.ui.edit(commitmsg, repo.ui.username())
171 commitmsg = repo.ui.edit(commitmsg, repo.ui.username())
172 else:
172 else:
173 commitmsg = repo[rev].description()
173 commitmsg = repo[rev].description()
174 # Commit might fail if unresolved files exist
174 # Commit might fail if unresolved files exist
175 extra = {'rebase_source': repo[rev].hex()}
175 extra = {'rebase_source': repo[rev].hex()}
176 if extrafn:
176 if extrafn:
177 extrafn(repo[rev], extra)
177 extrafn(repo[rev], extra)
178 newrev = repo.commit(text=commitmsg, user=repo[rev].user(),
178 newrev = repo.commit(text=commitmsg, user=repo[rev].user(),
179 date=repo[rev].date(), extra=extra)
179 date=repo[rev].date(), extra=extra)
180 repo.dirstate.setbranch(repo[newrev].branch())
180 repo.dirstate.setbranch(repo[newrev].branch())
181 return newrev
181 return newrev
182 except util.Abort:
182 except util.Abort:
183 # Invalidate the previous setparents
183 # Invalidate the previous setparents
184 repo.dirstate.invalidate()
184 repo.dirstate.invalidate()
185 raise
185 raise
186
186
187 def rebasenode(repo, rev, target, state, skipped, targetancestors, collapse,
187 def rebasenode(repo, rev, target, state, skipped, targetancestors, collapse,
188 extrafn):
188 extrafn):
189 'Rebase a single revision'
189 'Rebase a single revision'
190 repo.ui.debug(_("rebasing %d:%s\n") % (rev, repo[rev]))
190 repo.ui.debug(_("rebasing %d:%s\n") % (rev, repo[rev]))
191
191
192 p1, p2 = defineparents(repo, rev, target, state, targetancestors)
192 p1, p2 = defineparents(repo, rev, target, state, targetancestors)
193
193
194 repo.ui.debug(_(" future parents are %d and %d\n") % (repo[p1].rev(),
194 repo.ui.debug(_(" future parents are %d and %d\n") % (repo[p1].rev(),
195 repo[p2].rev()))
195 repo[p2].rev()))
196
196
197 # Merge phase
197 # Merge phase
198 if len(repo.parents()) != 2:
198 if len(repo.parents()) != 2:
199 # Update to target and merge it with local
199 # Update to target and merge it with local
200 if repo['.'].rev() != repo[p1].rev():
200 if repo['.'].rev() != repo[p1].rev():
201 repo.ui.debug(_(" update to %d:%s\n") % (repo[p1].rev(), repo[p1]))
201 repo.ui.debug(_(" update to %d:%s\n") % (repo[p1].rev(), repo[p1]))
202 merge.update(repo, p1, False, True, False)
202 merge.update(repo, p1, False, True, False)
203 else:
203 else:
204 repo.ui.debug(_(" already in target\n"))
204 repo.ui.debug(_(" already in target\n"))
205 repo.dirstate.write()
205 repo.dirstate.write()
206 repo.ui.debug(_(" merge against %d:%s\n") % (repo[rev].rev(), repo[rev]))
206 repo.ui.debug(_(" merge against %d:%s\n") % (repo[rev].rev(), repo[rev]))
207 first = repo[rev].rev() == repo[min(state)].rev()
207 first = repo[rev].rev() == repo[min(state)].rev()
208 stats = rebasemerge(repo, rev, first)
208 stats = rebasemerge(repo, rev, first)
209
209
210 if stats[3] > 0:
210 if stats[3] > 0:
211 raise util.Abort(_('fix unresolved conflicts with hg resolve then '
211 raise util.Abort(_('fix unresolved conflicts with hg resolve then '
212 'run hg rebase --continue'))
212 'run hg rebase --continue'))
213 else: # we have an interrupted rebase
213 else: # we have an interrupted rebase
214 repo.ui.debug(_('resuming interrupted rebase\n'))
214 repo.ui.debug(_('resuming interrupted rebase\n'))
215
215
216 # Keep track of renamed files in the revision that is going to be rebased
216 # Keep track of renamed files in the revision that is going to be rebased
217 # Here we simulate the copies and renames in the source changeset
217 # Here we simulate the copies and renames in the source changeset
218 cop, diver = copies.copies(repo, repo[rev], repo[target], repo[p2], True)
218 cop, diver = copies.copies(repo, repo[rev], repo[target], repo[p2], True)
219 m1 = repo[rev].manifest()
219 m1 = repo[rev].manifest()
220 m2 = repo[target].manifest()
220 m2 = repo[target].manifest()
221 for k, v in cop.iteritems():
221 for k, v in cop.iteritems():
222 if k in m1:
222 if k in m1:
223 if v in m1 or v in m2:
223 if v in m1 or v in m2:
224 repo.dirstate.copy(v, k)
224 repo.dirstate.copy(v, k)
225 if v in m2 and v not in m1:
225 if v in m2 and v not in m1:
226 repo.dirstate.remove(v)
226 repo.dirstate.remove(v)
227
227
228 newrev = concludenode(repo, rev, p1, p2, state, collapse,
228 newrev = concludenode(repo, rev, p1, p2, state, collapse,
229 extrafn=extrafn)
229 extrafn=extrafn)
230
230
231 # Update the state
231 # Update the state
232 if newrev is not None:
232 if newrev is not None:
233 state[rev] = repo[newrev].rev()
233 state[rev] = repo[newrev].rev()
234 else:
234 else:
235 if not collapse:
235 if not collapse:
236 repo.ui.note(_('no changes, revision %d skipped\n') % rev)
236 repo.ui.note(_('no changes, revision %d skipped\n') % rev)
237 repo.ui.debug(_('next revision set to %s\n') % p1)
237 repo.ui.debug(_('next revision set to %s\n') % p1)
238 skipped.add(rev)
238 skipped.add(rev)
239 state[rev] = p1
239 state[rev] = p1
240
240
241 def defineparents(repo, rev, target, state, targetancestors):
241 def defineparents(repo, rev, target, state, targetancestors):
242 'Return the new parent relationship of the revision that will be rebased'
242 'Return the new parent relationship of the revision that will be rebased'
243 parents = repo[rev].parents()
243 parents = repo[rev].parents()
244 p1 = p2 = nullrev
244 p1 = p2 = nullrev
245
245
246 P1n = parents[0].rev()
246 P1n = parents[0].rev()
247 if P1n in targetancestors:
247 if P1n in targetancestors:
248 p1 = target
248 p1 = target
249 elif P1n in state:
249 elif P1n in state:
250 p1 = state[P1n]
250 p1 = state[P1n]
251 else: # P1n external
251 else: # P1n external
252 p1 = target
252 p1 = target
253 p2 = P1n
253 p2 = P1n
254
254
255 if len(parents) == 2 and parents[1].rev() not in targetancestors:
255 if len(parents) == 2 and parents[1].rev() not in targetancestors:
256 P2n = parents[1].rev()
256 P2n = parents[1].rev()
257 # interesting second parent
257 # interesting second parent
258 if P2n in state:
258 if P2n in state:
259 if p1 == target: # P1n in targetancestors or external
259 if p1 == target: # P1n in targetancestors or external
260 p1 = state[P2n]
260 p1 = state[P2n]
261 else:
261 else:
262 p2 = state[P2n]
262 p2 = state[P2n]
263 else: # P2n external
263 else: # P2n external
264 if p2 != nullrev: # P1n external too => rev is a merged revision
264 if p2 != nullrev: # P1n external too => rev is a merged revision
265 raise util.Abort(_('cannot use revision %d as base, result '
265 raise util.Abort(_('cannot use revision %d as base, result '
266 'would have 3 parents') % rev)
266 'would have 3 parents') % rev)
267 p2 = P2n
267 p2 = P2n
268 return p1, p2
268 return p1, p2
269
269
270 def isagitpatch(repo, patchname):
270 def isagitpatch(repo, patchname):
271 'Return true if the given patch is in git format'
271 'Return true if the given patch is in git format'
272 mqpatch = os.path.join(repo.mq.path, patchname)
272 mqpatch = os.path.join(repo.mq.path, patchname)
273 for line in patch.linereader(file(mqpatch, 'rb')):
273 for line in patch.linereader(file(mqpatch, 'rb')):
274 if line.startswith('diff --git'):
274 if line.startswith('diff --git'):
275 return True
275 return True
276 return False
276 return False
277
277
278 def updatemq(repo, state, skipped, **opts):
278 def updatemq(repo, state, skipped, **opts):
279 'Update rebased mq patches - finalize and then import them'
279 'Update rebased mq patches - finalize and then import them'
280 mqrebase = {}
280 mqrebase = {}
281 for p in repo.mq.applied:
281 for p in repo.mq.applied:
282 if repo[p.rev].rev() in state:
282 if repo[p.rev].rev() in state:
283 repo.ui.debug(_('revision %d is an mq patch (%s), finalize it.\n') %
283 repo.ui.debug(_('revision %d is an mq patch (%s), finalize it.\n') %
284 (repo[p.rev].rev(), p.name))
284 (repo[p.rev].rev(), p.name))
285 mqrebase[repo[p.rev].rev()] = (p.name, isagitpatch(repo, p.name))
285 mqrebase[repo[p.rev].rev()] = (p.name, isagitpatch(repo, p.name))
286
286
287 if mqrebase:
287 if mqrebase:
288 repo.mq.finish(repo, mqrebase.keys())
288 repo.mq.finish(repo, mqrebase.keys())
289
289
290 # We must start import from the newest revision
290 # We must start import from the newest revision
291 for rev in sorted(mqrebase, reverse=True):
291 for rev in sorted(mqrebase, reverse=True):
292 if rev not in skipped:
292 if rev not in skipped:
293 repo.ui.debug(_('import mq patch %d (%s)\n')
293 repo.ui.debug(_('import mq patch %d (%s)\n')
294 % (state[rev], mqrebase[rev][0]))
294 % (state[rev], mqrebase[rev][0]))
295 repo.mq.qimport(repo, (), patchname=mqrebase[rev][0],
295 repo.mq.qimport(repo, (), patchname=mqrebase[rev][0],
296 git=mqrebase[rev][1],rev=[str(state[rev])])
296 git=mqrebase[rev][1],rev=[str(state[rev])])
297 repo.mq.save_dirty()
297 repo.mq.save_dirty()
298
298
299 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
299 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
300 external):
300 external):
301 'Store the current status to allow recovery'
301 'Store the current status to allow recovery'
302 f = repo.opener("rebasestate", "w")
302 f = repo.opener("rebasestate", "w")
303 f.write(repo[originalwd].hex() + '\n')
303 f.write(repo[originalwd].hex() + '\n')
304 f.write(repo[target].hex() + '\n')
304 f.write(repo[target].hex() + '\n')
305 f.write(repo[external].hex() + '\n')
305 f.write(repo[external].hex() + '\n')
306 f.write('%d\n' % int(collapse))
306 f.write('%d\n' % int(collapse))
307 f.write('%d\n' % int(keep))
307 f.write('%d\n' % int(keep))
308 f.write('%d\n' % int(keepbranches))
308 f.write('%d\n' % int(keepbranches))
309 for d, v in state.iteritems():
309 for d, v in state.iteritems():
310 oldrev = repo[d].hex()
310 oldrev = repo[d].hex()
311 newrev = repo[v].hex()
311 newrev = repo[v].hex()
312 f.write("%s:%s\n" % (oldrev, newrev))
312 f.write("%s:%s\n" % (oldrev, newrev))
313 f.close()
313 f.close()
314 repo.ui.debug(_('rebase status stored\n'))
314 repo.ui.debug(_('rebase status stored\n'))
315
315
316 def clearstatus(repo):
316 def clearstatus(repo):
317 'Remove the status files'
317 'Remove the status files'
318 if os.path.exists(repo.join("rebasestate")):
318 if os.path.exists(repo.join("rebasestate")):
319 util.unlink(repo.join("rebasestate"))
319 util.unlink(repo.join("rebasestate"))
320
320
321 def restorestatus(repo):
321 def restorestatus(repo):
322 'Restore a previously stored status'
322 'Restore a previously stored status'
323 try:
323 try:
324 target = None
324 target = None
325 collapse = False
325 collapse = False
326 external = nullrev
326 external = nullrev
327 state = {}
327 state = {}
328 f = repo.opener("rebasestate")
328 f = repo.opener("rebasestate")
329 for i, l in enumerate(f.read().splitlines()):
329 for i, l in enumerate(f.read().splitlines()):
330 if i == 0:
330 if i == 0:
331 originalwd = repo[l].rev()
331 originalwd = repo[l].rev()
332 elif i == 1:
332 elif i == 1:
333 target = repo[l].rev()
333 target = repo[l].rev()
334 elif i == 2:
334 elif i == 2:
335 external = repo[l].rev()
335 external = repo[l].rev()
336 elif i == 3:
336 elif i == 3:
337 collapse = bool(int(l))
337 collapse = bool(int(l))
338 elif i == 4:
338 elif i == 4:
339 keep = bool(int(l))
339 keep = bool(int(l))
340 elif i == 5:
340 elif i == 5:
341 keepbranches = bool(int(l))
341 keepbranches = bool(int(l))
342 else:
342 else:
343 oldrev, newrev = l.split(':')
343 oldrev, newrev = l.split(':')
344 state[repo[oldrev].rev()] = repo[newrev].rev()
344 state[repo[oldrev].rev()] = repo[newrev].rev()
345 repo.ui.debug(_('rebase status resumed\n'))
345 repo.ui.debug(_('rebase status resumed\n'))
346 return originalwd, target, state, collapse, keep, keepbranches, external
346 return originalwd, target, state, collapse, keep, keepbranches, external
347 except IOError, err:
347 except IOError, err:
348 if err.errno != errno.ENOENT:
348 if err.errno != errno.ENOENT:
349 raise
349 raise
350 raise util.Abort(_('no rebase in progress'))
350 raise util.Abort(_('no rebase in progress'))
351
351
352 def abort(repo, originalwd, target, state):
352 def abort(repo, originalwd, target, state):
353 'Restore the repository to its original state'
353 'Restore the repository to its original state'
354 if set(repo.changelog.descendants(target)) - set(state.values()):
354 if set(repo.changelog.descendants(target)) - set(state.values()):
355 repo.ui.warn(_("warning: new changesets detected on target branch, "
355 repo.ui.warn(_("warning: new changesets detected on target branch, "
356 "not stripping\n"))
356 "not stripping\n"))
357 else:
357 else:
358 # Strip from the first rebased revision
358 # Strip from the first rebased revision
359 merge.update(repo, repo[originalwd].rev(), False, True, False)
359 merge.update(repo, repo[originalwd].rev(), False, True, False)
360 rebased = filter(lambda x: x > -1, state.values())
360 rebased = filter(lambda x: x > -1, state.values())
361 if rebased:
361 if rebased:
362 strippoint = min(rebased)
362 strippoint = min(rebased)
363 repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
363 repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
364 clearstatus(repo)
364 clearstatus(repo)
365 repo.ui.status(_('rebase aborted\n'))
365 repo.ui.status(_('rebase aborted\n'))
366
366
367 def buildstate(repo, dest, src, base, collapse):
367 def buildstate(repo, dest, src, base, collapse):
368 'Define which revisions are going to be rebased and where'
368 'Define which revisions are going to be rebased and where'
369 targetancestors = set()
369 targetancestors = set()
370
370
371 if not dest:
371 if not dest:
372 # Destination defaults to the latest revision in the current branch
372 # Destination defaults to the latest revision in the current branch
373 branch = repo[None].branch()
373 branch = repo[None].branch()
374 dest = repo[branch].rev()
374 dest = repo[branch].rev()
375 else:
375 else:
376 if 'qtip' in repo.tags() and (repo[dest].hex() in
376 if 'qtip' in repo.tags() and (repo[dest].hex() in
377 [s.rev for s in repo.mq.applied]):
377 [s.rev for s in repo.mq.applied]):
378 raise util.Abort(_('cannot rebase onto an applied mq patch'))
378 raise util.Abort(_('cannot rebase onto an applied mq patch'))
379 dest = repo[dest].rev()
379 dest = repo[dest].rev()
380
380
381 if src:
381 if src:
382 commonbase = repo[src].ancestor(repo[dest])
382 commonbase = repo[src].ancestor(repo[dest])
383 if commonbase == repo[src]:
383 if commonbase == repo[src]:
384 raise util.Abort(_('cannot rebase an ancestor'))
384 raise util.Abort(_('cannot rebase an ancestor'))
385 if commonbase == repo[dest]:
385 if commonbase == repo[dest]:
386 raise util.Abort(_('cannot rebase a descendant'))
386 raise util.Abort(_('cannot rebase a descendant'))
387 source = repo[src].rev()
387 source = repo[src].rev()
388 else:
388 else:
389 if base:
389 if base:
390 cwd = repo[base].rev()
390 cwd = repo[base].rev()
391 else:
391 else:
392 cwd = repo['.'].rev()
392 cwd = repo['.'].rev()
393
393
394 if cwd == dest:
394 if cwd == dest:
395 repo.ui.debug(_('already working on current\n'))
395 repo.ui.debug(_('already working on current\n'))
396 return None
396 return None
397
397
398 targetancestors = set(repo.changelog.ancestors(dest))
398 targetancestors = set(repo.changelog.ancestors(dest))
399 if cwd in targetancestors:
399 if cwd in targetancestors:
400 repo.ui.debug(_('already working on the current branch\n'))
400 repo.ui.debug(_('already working on the current branch\n'))
401 return None
401 return None
402
402
403 cwdancestors = set(repo.changelog.ancestors(cwd))
403 cwdancestors = set(repo.changelog.ancestors(cwd))
404 cwdancestors.add(cwd)
404 cwdancestors.add(cwd)
405 rebasingbranch = cwdancestors - targetancestors
405 rebasingbranch = cwdancestors - targetancestors
406 source = min(rebasingbranch)
406 source = min(rebasingbranch)
407
407
408 repo.ui.debug(_('rebase onto %d starting from %d\n') % (dest, source))
408 repo.ui.debug(_('rebase onto %d starting from %d\n') % (dest, source))
409 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
409 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
410 external = nullrev
410 external = nullrev
411 if collapse:
411 if collapse:
412 if not targetancestors:
412 if not targetancestors:
413 targetancestors = set(repo.changelog.ancestors(dest))
413 targetancestors = set(repo.changelog.ancestors(dest))
414 for rev in state:
414 for rev in state:
415 # Check externals and fail if there are more than one
415 # Check externals and fail if there are more than one
416 for p in repo[rev].parents():
416 for p in repo[rev].parents():
417 if (p.rev() not in state and p.rev() != source
417 if (p.rev() not in state and p.rev() != source
418 and p.rev() not in targetancestors):
418 and p.rev() not in targetancestors):
419 if external != nullrev:
419 if external != nullrev:
420 raise util.Abort(_('unable to collapse, there is more '
420 raise util.Abort(_('unable to collapse, there is more '
421 'than one external parent'))
421 'than one external parent'))
422 external = p.rev()
422 external = p.rev()
423
423
424 state[source] = nullrev
424 state[source] = nullrev
425 return repo['.'].rev(), repo[dest].rev(), state, external
425 return repo['.'].rev(), repo[dest].rev(), state, external
426
426
427 def pullrebase(orig, ui, repo, *args, **opts):
427 def pullrebase(orig, ui, repo, *args, **opts):
428 'Call rebase after pull if the latter has been invoked with --rebase'
428 'Call rebase after pull if the latter has been invoked with --rebase'
429 if opts.get('rebase'):
429 if opts.get('rebase'):
430 if opts.get('update'):
430 if opts.get('update'):
431 del opts['update']
431 del opts['update']
432 ui.debug(_('--update and --rebase are not compatible, ignoring '
432 ui.debug(_('--update and --rebase are not compatible, ignoring '
433 'the update flag\n'))
433 'the update flag\n'))
434
434
435 cmdutil.bail_if_changed(repo)
435 cmdutil.bail_if_changed(repo)
436 revsprepull = len(repo)
436 revsprepull = len(repo)
437 orig(ui, repo, *args, **opts)
437 orig(ui, repo, *args, **opts)
438 revspostpull = len(repo)
438 revspostpull = len(repo)
439 if revspostpull > revsprepull:
439 if revspostpull > revsprepull:
440 rebase(ui, repo, **opts)
440 rebase(ui, repo, **opts)
441 branch = repo[None].branch()
441 branch = repo[None].branch()
442 dest = repo[branch].rev()
442 dest = repo[branch].rev()
443 if dest != repo['.'].rev():
443 if dest != repo['.'].rev():
444 # there was nothing to rebase we force an update
444 # there was nothing to rebase we force an update
445 merge.update(repo, dest, False, False, False)
445 merge.update(repo, dest, False, False, False)
446 else:
446 else:
447 orig(ui, repo, *args, **opts)
447 orig(ui, repo, *args, **opts)
448
448
449 def uisetup(ui):
449 def uisetup(ui):
450 'Replace pull with a decorator to provide --rebase option'
450 'Replace pull with a decorator to provide --rebase option'
451 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
451 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
452 entry[1].append(('', 'rebase', None,
452 entry[1].append(('', 'rebase', None,
453 _("rebase working directory to branch head"))
453 _("rebase working directory to branch head"))
454 )
454 )
455
455
456 cmdtable = {
456 cmdtable = {
457 "rebase":
457 "rebase":
458 (rebase,
458 (rebase,
459 [
459 [
460 ('s', 'source', '', _('rebase from a given revision')),
460 ('s', 'source', '', _('rebase from a given revision')),
461 ('b', 'base', '', _('rebase from the base of a given revision')),
461 ('b', 'base', '', _('rebase from the base of a given revision')),
462 ('d', 'dest', '', _('rebase onto a given revision')),
462 ('d', 'dest', '', _('rebase onto a given revision')),
463 ('', 'collapse', False, _('collapse the rebased revisions')),
463 ('', 'collapse', False, _('collapse the rebased revisions')),
464 ('', 'keep', False, _('keep original revisions')),
464 ('', 'keep', False, _('keep original revisions')),
465 ('', 'keepbranches', False, _('keep original branches')),
465 ('', 'keepbranches', False, _('keep original branches')),
466 ('c', 'continue', False, _('continue an interrupted rebase')),
466 ('c', 'continue', False, _('continue an interrupted rebase')),
467 ('a', 'abort', False, _('abort an interrupted rebase')),] +
467 ('a', 'abort', False, _('abort an interrupted rebase')),] +
468 templateopts,
468 templateopts,
469 _('hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--keep] '
469 _('hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--keep] '
470 '[--keepbranches] | [-c] | [-a]')),
470 '[--keepbranches] | [-c] | [-a]')),
471 }
471 }
General Comments 0
You need to be logged in to leave comments. Login now