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