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