comparison hgext/rebase.py @ 32272:78496ac30025

rebase: allow rebase even if some revisions need no rebase (BC) (issue5422) This allows you to do e.g. "hg rebase -d @ -r 'draft()'" even if some drafts are already based off of @. You'd still need to exclude obsolete and troubled revisions, though. We will deal with those cases later. Implemented by treating state[rev]==rev as "no need to rebase". I considered adding another fake revision number like revdone=-6. That would make the code clearer in a few places, but would add extra code in other places. I moved the existing test out of test-rebase-base.t and into a new file and added more tests there, since not all are using --base.
author Martin von Zweigbergk <martinvonz@google.com>
date Thu, 11 May 2017 11:37:18 -0700
parents 27e67cfea27f
children bd872f64a8ba
comparison
equal deleted inserted replaced
32271:6096d27dc119 32272:78496ac30025
382 desc = '%d:%s "%s"' % (ctx.rev(), ctx, 382 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
383 ctx.description().split('\n', 1)[0]) 383 ctx.description().split('\n', 1)[0])
384 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node()) 384 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
385 if names: 385 if names:
386 desc += ' (%s)' % ' '.join(names) 386 desc += ' (%s)' % ' '.join(names)
387 if self.state[rev] == revtodo: 387 if self.state[rev] == rev:
388 ui.status(_('already rebased %s\n') % desc)
389 elif self.state[rev] == revtodo:
388 pos += 1 390 pos += 1
389 ui.status(_('rebasing %s\n') % desc) 391 ui.status(_('rebasing %s\n') % desc)
390 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)), 392 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
391 _('changesets'), total) 393 _('changesets'), total)
392 p1, p2, base = defineparents(repo, rev, self.dest, 394 p1, p2, base = defineparents(repo, rev, self.dest,
506 508
507 if self.currentbookmarks: 509 if self.currentbookmarks:
508 # Nodeids are needed to reset bookmarks 510 # Nodeids are needed to reset bookmarks
509 nstate = {} 511 nstate = {}
510 for k, v in self.state.iteritems(): 512 for k, v in self.state.iteritems():
511 if v > nullmerge: 513 if v > nullmerge and v != k:
512 nstate[repo[k].node()] = repo[v].node() 514 nstate[repo[k].node()] = repo[v].node()
513 elif v == revprecursor: 515 elif v == revprecursor:
514 succ = self.obsoletenotrebased[k] 516 succ = self.obsoletenotrebased[k]
515 nstate[repo[k].node()] = repo[succ].node() 517 nstate[repo[k].node()] = repo[succ].node()
516 # XXX this is the same as dest.node() for the non-continue path -- 518 # XXX this is the same as dest.node() for the non-continue path --
1246 if not roots: 1248 if not roots:
1247 raise error.Abort(_('no matching revisions')) 1249 raise error.Abort(_('no matching revisions'))
1248 roots.sort() 1250 roots.sort()
1249 state = dict.fromkeys(rebaseset, revtodo) 1251 state = dict.fromkeys(rebaseset, revtodo)
1250 detachset = set() 1252 detachset = set()
1253 emptyrebase = True
1251 for root in roots: 1254 for root in roots:
1252 commonbase = root.ancestor(dest) 1255 commonbase = root.ancestor(dest)
1253 if commonbase == root: 1256 if commonbase == root:
1254 raise error.Abort(_('source is ancestor of destination')) 1257 raise error.Abort(_('source is ancestor of destination'))
1255 if commonbase == dest: 1258 if commonbase == dest:
1258 # when rebasing to '.', it will use the current wd branch name 1261 # when rebasing to '.', it will use the current wd branch name
1259 samebranch = root.branch() == wctx.branch() 1262 samebranch = root.branch() == wctx.branch()
1260 else: 1263 else:
1261 samebranch = root.branch() == dest.branch() 1264 samebranch = root.branch() == dest.branch()
1262 if not collapse and samebranch and root in dest.children(): 1265 if not collapse and samebranch and root in dest.children():
1266 # mark the revision as done by setting its new revision
1267 # equal to its old (current) revisions
1268 state[root.rev()] = root.rev()
1263 repo.ui.debug('source is a child of destination\n') 1269 repo.ui.debug('source is a child of destination\n')
1264 return None 1270 continue
1265 1271
1272 emptyrebase = False
1266 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root)) 1273 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1267 # Rebase tries to turn <dest> into a parent of <root> while 1274 # Rebase tries to turn <dest> into a parent of <root> while
1268 # preserving the number of parents of rebased changesets: 1275 # preserving the number of parents of rebased changesets:
1269 # 1276 #
1270 # - A changeset with a single parent will always be rebased as a 1277 # - A changeset with a single parent will always be rebased as a
1303 # The actual abort is handled by `defineparents` 1310 # The actual abort is handled by `defineparents`
1304 if len(root.parents()) <= 1: 1311 if len(root.parents()) <= 1:
1305 # ancestors of <root> not ancestors of <dest> 1312 # ancestors of <root> not ancestors of <dest>
1306 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()], 1313 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1307 [root.rev()])) 1314 [root.rev()]))
1315 if emptyrebase:
1316 return None
1317 for rev in sorted(state):
1318 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1319 # if all parents of this revision are done, then so is this revision
1320 if parents and all((state.get(p) == p for p in parents)):
1321 state[rev] = rev
1308 for r in detachset: 1322 for r in detachset:
1309 if r not in state: 1323 if r not in state:
1310 state[r] = nullmerge 1324 state[r] = nullmerge
1311 if len(roots) > 1: 1325 if len(roots) > 1:
1312 # If we have multiple roots, we may have "hole" in the rebase set. 1326 # If we have multiple roots, we may have "hole" in the rebase set.
1330 If `collapsedas` is not None, the rebase was a collapse whose result if the 1344 If `collapsedas` is not None, the rebase was a collapse whose result if the
1331 `collapsedas` node.""" 1345 `collapsedas` node."""
1332 if obsolete.isenabled(repo, obsolete.createmarkersopt): 1346 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1333 markers = [] 1347 markers = []
1334 for rev, newrev in sorted(state.items()): 1348 for rev, newrev in sorted(state.items()):
1335 if newrev >= 0: 1349 if newrev >= 0 and newrev != rev:
1336 if rev in skipped: 1350 if rev in skipped:
1337 succs = () 1351 succs = ()
1338 elif collapsedas is not None: 1352 elif collapsedas is not None:
1339 succs = (repo[collapsedas],) 1353 succs = (repo[collapsedas],)
1340 else: 1354 else:
1341 succs = (repo[newrev],) 1355 succs = (repo[newrev],)
1342 markers.append((repo[rev], succs)) 1356 markers.append((repo[rev], succs))
1343 if markers: 1357 if markers:
1344 obsolete.createmarkers(repo, markers) 1358 obsolete.createmarkers(repo, markers)
1345 else: 1359 else:
1346 rebased = [rev for rev in state if state[rev] > nullmerge] 1360 rebased = [rev for rev in state
1361 if state[rev] > nullmerge and state[rev] != rev]
1347 if rebased: 1362 if rebased:
1348 stripped = [] 1363 stripped = []
1349 for root in repo.set('roots(%ld)', rebased): 1364 for root in repo.set('roots(%ld)', rebased):
1350 if set(repo.changelog.descendants([root.rev()])) - set(state): 1365 if set(repo.changelog.descendants([root.rev()])) - set(state):
1351 ui.warn(_("warning: new changesets detected " 1366 ui.warn(_("warning: new changesets detected "