Mercurial > hg
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 " |