diff --git a/mercurial/branchmap.py b/mercurial/branchmap.py --- a/mercurial/branchmap.py +++ b/mercurial/branchmap.py @@ -17,6 +17,7 @@ from . import ( encoding, error, + obsolete, pycompat, scmutil, util, @@ -184,7 +185,7 @@ The first line is used to check if the cache is still valid. If the branch cache is for a filtered repo view, an optional third hash is - included that hashes the hashes of all filtered revisions. + included that hashes the hashes of all filtered and obsolete revisions. The open/closed state is represented by a single letter 'o' or 'c'. This field can be used to avoid changelog reads when determining if a @@ -357,7 +358,8 @@ - True when cache is up to date or a subset of current repo.""" try: return (self.tipnode == repo.changelog.node(self.tiprev)) and ( - self.filteredhash == scmutil.filteredhash(repo, self.tiprev) + self.filteredhash + == scmutil.filteredhash(repo, self.tiprev, needobsolete=True) ) except IndexError: return False @@ -478,6 +480,9 @@ # use the faster unfiltered parent accessor. parentrevs = repo.unfiltered().changelog.parentrevs + # Faster than using ctx.obsolete() + obsrevs = obsolete.getrevs(repo, b'obsolete') + for branch, newheadrevs in pycompat.iteritems(newbranches): # For every branch, compute the new branchheads. # A branchhead is a revision such that no descendant is on @@ -518,6 +523,11 @@ bheadset = {cl.rev(node) for node in bheads} uncertain = set() for newrev in sorted(newheadrevs): + if newrev in obsrevs: + # We ignore obsolete changesets as they shouldn't be + # considered heads. + continue + if not bheadset: bheadset.add(newrev) continue @@ -525,13 +535,22 @@ parents = [p for p in parentrevs(newrev) if p != nullrev] samebranch = set() otherbranch = set() + obsparents = set() for p in parents: - if p in bheadset or getbranchinfo(p)[0] == branch: + if p in obsrevs: + # We ignored this obsolete changeset earlier, but now + # that it has non-ignored children, we need to make + # sure their ancestors are not considered heads. To + # achieve that, we will simply treat this obsolete + # changeset as a parent from other branch. + obsparents.add(p) + elif p in bheadset or getbranchinfo(p)[0] == branch: samebranch.add(p) else: otherbranch.add(p) - if otherbranch and not (len(bheadset) == len(samebranch) == 1): + if not (len(bheadset) == len(samebranch) == 1): uncertain.update(otherbranch) + uncertain.update(obsparents) bheadset.difference_update(samebranch) bheadset.add(newrev) @@ -540,11 +559,12 @@ topoheads = set(cl.headrevs()) if bheadset - topoheads: floorrev = min(bheadset) - ancestors = set(cl.ancestors(newheadrevs, floorrev)) - bheadset -= ancestors + if floorrev <= max(uncertain): + ancestors = set(cl.ancestors(uncertain, floorrev)) + bheadset -= ancestors bheadrevs = sorted(bheadset) self[branch] = [cl.node(rev) for rev in bheadrevs] - tiprev = bheadrevs[-1] + tiprev = max(newheadrevs) if tiprev > ntiprev: ntiprev = tiprev @@ -553,15 +573,24 @@ self.tipnode = cl.node(ntiprev) if not self.validfor(repo): - # cache key are not valid anymore + # old cache key is now invalid for the repo, but we've just updated + # the cache and we assume it's valid, so let's make the cache key + # valid as well by recomputing it from the cached data self.tipnode = repo.nullid self.tiprev = nullrev for heads in self.iterheads(): + if not heads: + # all revisions on a branch are obsolete + continue + # note: tiprev is not necessarily the tip revision of repo, + # because the tip could be obsolete (i.e. not a head) tiprev = max(cl.rev(node) for node in heads) if tiprev > self.tiprev: self.tipnode = cl.node(tiprev) self.tiprev = tiprev - self.filteredhash = scmutil.filteredhash(repo, self.tiprev) + self.filteredhash = scmutil.filteredhash( + repo, self.tiprev, needobsolete=True + ) duration = util.timer() - starttime repo.ui.log( diff --git a/mercurial/destutil.py b/mercurial/destutil.py --- a/mercurial/destutil.py +++ b/mercurial/destutil.py @@ -79,6 +79,9 @@ node = repo.revs(b'max(%ln)', successors).first() if bookmarks.isactivewdirparent(repo): movemark = repo[b'.'].node() + else: + # TODO: copy hg prune logic + node = repo[b'.'].node() return node, movemark, None diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py --- a/mercurial/scmutil.py +++ b/mercurial/scmutil.py @@ -349,7 +349,7 @@ self._newfiles.add(f) -def filteredhash(repo, maxrev): +def filteredhash(repo, maxrev, needobsolete=False): """build hash of filtered revisions in the current repoview. Multiple caches perform up-to-date validation by checking that the @@ -358,22 +358,33 @@ of revisions in the view may change without the repository tiprev and tipnode changing. - This function hashes all the revs filtered from the view and returns - that SHA-1 digest. + This function hashes all the revs filtered from the view (and, optionally, + all obsolete revs) up to maxrev and returns that SHA-1 digest. """ cl = repo.changelog - if not cl.filteredrevs: - return None - key = cl._filteredrevs_hashcache.get(maxrev) - if not key: - revs = sorted(r for r in cl.filteredrevs if r <= maxrev) + if needobsolete: + obsrevs = obsolete.getrevs(repo, b'obsolete') + if not cl.filteredrevs and not obsrevs: + return None + # TODO: obsrevs should be a frozenset, but right now obsolete.getrevs() + # may return a set, which is not a hashable type. + key = (maxrev, hash(cl.filteredrevs), hash(frozenset(obsrevs))) + else: + if not cl.filteredrevs: + return None + key = maxrev + obsrevs = frozenset() + + result = cl._filteredrevs_hashcache.get(key) + if not result: + revs = sorted(r for r in cl.filteredrevs | obsrevs if r <= maxrev) if revs: s = hashutil.sha1() for rev in revs: s.update(b'%d;' % rev) - key = s.digest() - cl._filteredrevs_hashcache[maxrev] = key - return key + result = s.digest() + cl._filteredrevs_hashcache[key] = result + return result def walkrepos(path, followsym=False, seen_dirs=None, recurse=False): diff --git a/tests/test-branch-change.t b/tests/test-branch-change.t --- a/tests/test-branch-change.t +++ b/tests/test-branch-change.t @@ -185,6 +185,7 @@ changed branch on 2 changesets updating the branch cache invalid branch cache (served): tip differs + invalid branch cache (served.hidden): tip differs $ hg glog -r '(.^)::' @ 9:de1404b45a69 Added e @@ -211,7 +212,7 @@ secret 11:38a9b2d53f98 foo 7:8a4729a5e2b8 wat 9:de1404b45a69 (inactive) - default 2:28ad74487de9 (inactive) + default 1:29becc82797a (inactive) $ hg branch secret diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t --- a/tests/test-obsmarker-template.t +++ b/tests/test-obsmarker-template.t @@ -1762,7 +1762,7 @@ 2 new obsolescence markers obsoleted 1 changesets new changesets 7a230b46bf61 (1 drafts) - (run 'hg heads' to see heads, 'hg merge' to merge) + (run 'hg heads' to see heads) $ hg log --hidden -G o changeset: 2:7a230b46bf61 | tag: tip diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t --- a/tests/test-obsolete.t +++ b/tests/test-obsolete.t @@ -169,10 +169,6 @@ 5:5601fb93a350 (draft) [tip ] add new_3_c $ hg heads --hidden 5:5601fb93a350 (draft) [tip ] add new_3_c - 4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350] - 3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9] - 2:245bde4270cd (draft *obsolete*) [ ] add original_c [rewritten as 3:cdbce2fbb163] - check that summary does not report them @@ -193,7 +189,7 @@ add new_3_c branch: default commit: (clean) - update: 3 new changesets, 4 branch heads (merge) + update: (current) phases: 6 draft remote: 3 outgoing diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t --- a/tests/test-rebase-obsolete.t +++ b/tests/test-rebase-obsolete.t @@ -650,6 +650,7 @@ $ hg add J $ hg commit -m J 1 new orphan changesets + created new head $ hg debugobsolete `hg log --rev . -T '{node}'` 1 new obsolescence markers obsoleted 1 changesets diff --git a/tests/test-rebase-obsolete2.t b/tests/test-rebase-obsolete2.t --- a/tests/test-rebase-obsolete2.t +++ b/tests/test-rebase-obsolete2.t @@ -47,6 +47,7 @@ $ hg add C $ hg commit -m C 1 new orphan changesets + created new head $ hg log -G @ 4:212cb178bcbb C | @@ -73,6 +74,7 @@ $ hg add D $ hg commit -m D 1 new orphan changesets + created new head $ hg --hidden strip -r 'desc(B1)' saved backup bundle to $TESTTMP/obsskip/.hg/strip-backup/86f6414ccda7-b1c452ee-backup.hg 1 new orphan changesets @@ -194,6 +196,7 @@ $ hg add foo $ hg commit -m "bar foo" 1 new orphan changesets + created new head $ hg log -G @ 14:73568ab6879d bar foo | diff --git a/tests/test-rebase-obsolete4.t b/tests/test-rebase-obsolete4.t --- a/tests/test-rebase-obsolete4.t +++ b/tests/test-rebase-obsolete4.t @@ -169,7 +169,7 @@ D branch: default commit: 1 modified, 1 added, 1 unknown, 1 unresolved - update: 1 new changesets, 2 branch heads (merge) + update: (current) phases: 3 draft rebase: 0 rebased, 2 remaining (rebase --continue) diff --git a/tests/test-static-http.t b/tests/test-static-http.t --- a/tests/test-static-http.t +++ b/tests/test-static-http.t @@ -256,6 +256,7 @@ /remote-with-names/.hg/store/data/%7E2ehgtags.i (no-py37 !) /remote-with-names/.hg/store/data/foo.i /remote-with-names/.hg/store/data/~2ehgtags.i (py37 !) + /remote-with-names/.hg/store/obsstore /remote-with-names/.hg/store/requires /remote/.hg/bookmarks /remote/.hg/bookmarks.current @@ -276,6 +277,7 @@ /remote/.hg/store/data/quux.i /remote/.hg/store/data/~2edotfile%20with%20spaces.i (py37 !) /remote/.hg/store/data/~2ehgtags.i (py37 !) + /remote/.hg/store/obsstore /remote/.hg/store/requires /remotempty/.hg/bookmarks /remotempty/.hg/bookmarks.current