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