Mercurial > hg
view mercurial/treediscovery.py @ 50662:12f13b13f414 stable
revlog: avoid possible collision between directory and temporary index
Since 6.4, we create a temporary index file to write the split data without
overwriting the inline version too early. However, the store encoding does not
prevent these new `.i.s` file to collide with a directory with the same name.
While the odds for such a collision to happens are fairly low, the collision
would prevent Mercurial from working.
The store encoding have a mitigation solution in place to prevent such
collisions from happening for `.i` and `.d` files, but not for other extensions.
We cannot update this encoding scheme to solve the issue since it would diverge
from older version of Mercurial.
Instead, we create an alternative directory tree dedicated to such files.
The use of the `.i` extension combined with store encoding will prevent
collisions there.
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Thu, 08 Jun 2023 14:28:21 +0200 |
parents | f64f66167afc |
children | f4733654f144 |
line wrap: on
line source
# discovery.py - protocol changeset discovery functions # # Copyright 2010 Olivia Mackall <olivia@selenic.com> # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. import collections from .i18n import _ from .node import short from . import ( error, ) def findcommonincoming(repo, remote, heads=None, force=False, audit=None): """Return a tuple (common, fetch, heads) used to identify the common subset of nodes between repo and remote. "common" is a list of (at least) the heads of the common subset. "fetch" is a list of roots of the nodes that would be incoming, to be supplied to changegroupsubset. "heads" is either the supplied heads, or else the remote's heads. """ knownnode = repo.changelog.hasnode search = [] fetch = set() seen = set() seenbranch = set() base = set() if not heads: with remote.commandexecutor() as e: heads = e.callcommand(b'heads', {}).result() if audit is not None: audit[b'total-roundtrips'] = 1 audit[b'total-roundtrips-heads'] = 1 audit[b'total-roundtrips-branches'] = 0 audit[b'total-roundtrips-between'] = 0 audit[b'total-queries'] = 0 audit[b'total-queries-branches'] = 0 audit[b'total-queries-between'] = 0 if repo.changelog.tip() == repo.nullid: base.add(repo.nullid) if heads != [repo.nullid]: return [repo.nullid], [repo.nullid], list(heads) return [repo.nullid], [], heads # assume we're closer to the tip than the root # and start by examining the heads repo.ui.status(_(b"searching for changes\n")) unknown = [] for h in heads: if not knownnode(h): unknown.append(h) else: base.add(h) if not unknown: return list(base), [], list(heads) req = set(unknown) reqcnt = 0 progress = repo.ui.makeprogress(_(b'searching'), unit=_(b'queries')) # search through remote branches # a 'branch' here is a linear segment of history, with four parts: # head, root, first parent, second parent # (a branch always has two parents (or none) by definition) with remote.commandexecutor() as e: if audit is not None: audit[b'total-queries'] += len(unknown) audit[b'total-queries-branches'] += len(unknown) audit[b'total-roundtrips'] += 1 audit[b'total-roundtrips-branches'] += 1 branches = e.callcommand(b'branches', {b'nodes': unknown}).result() unknown = collections.deque(branches) while unknown: r = [] while unknown: n = unknown.popleft() if n[0] in seen: continue repo.ui.debug(b"examining %s:%s\n" % (short(n[0]), short(n[1]))) if n[0] == repo.nullid: # found the end of the branch pass elif n in seenbranch: repo.ui.debug(b"branch already found\n") continue elif n[1] and knownnode(n[1]): # do we know the base? repo.ui.debug( b"found incomplete branch %s:%s\n" % (short(n[0]), short(n[1])) ) search.append(n[0:2]) # schedule branch range for scanning seenbranch.add(n) else: if n[1] not in seen and n[1] not in fetch: if knownnode(n[2]) and knownnode(n[3]): repo.ui.debug(b"found new changeset %s\n" % short(n[1])) fetch.add(n[1]) # earliest unknown for p in n[2:4]: if knownnode(p): base.add(p) # latest known for p in n[2:4]: if p not in req and not knownnode(p): r.append(p) req.add(p) seen.add(n[0]) if r: for p in range(0, len(r), 10): reqcnt += 1 progress.increment() if repo.ui.debugflag: msg = b"request %d: %s\n" msg %= (reqcnt, b" ".join(map(short, r))) repo.ui.debug(msg) with remote.commandexecutor() as e: subset = r[p : p + 10] if audit is not None: audit[b'total-queries'] += len(subset) audit[b'total-queries-branches'] += len(subset) audit[b'total-roundtrips'] += 1 audit[b'total-roundtrips-branches'] += 1 branches = e.callcommand( b'branches', { b'nodes': subset, }, ).result() for b in branches: repo.ui.debug( b"received %s:%s\n" % (short(b[0]), short(b[1])) ) unknown.append(b) # do binary search on the branches we found while search: newsearch = [] reqcnt += 1 progress.increment() with remote.commandexecutor() as e: if audit is not None: audit[b'total-queries'] += len(search) audit[b'total-queries-between'] += len(search) audit[b'total-roundtrips'] += 1 audit[b'total-roundtrips-between'] += 1 between = e.callcommand(b'between', {b'pairs': search}).result() for n, l in zip(search, between): l.append(n[1]) p = n[0] f = 1 for i in l: repo.ui.debug(b"narrowing %d:%d %s\n" % (f, len(l), short(i))) if knownnode(i): if f <= 2: repo.ui.debug( b"found new branch changeset %s\n" % short(p) ) fetch.add(p) base.add(i) else: repo.ui.debug( b"narrowed branch search to %s:%s\n" % (short(p), short(i)) ) newsearch.append((p, i)) break p, f = i, f * 2 search = newsearch # sanity check our fetch list for f in fetch: if knownnode(f): raise error.RepoError(_(b"already have changeset ") + short(f[:4])) base = list(base) if base == [repo.nullid]: if force: repo.ui.warn(_(b"warning: repository is unrelated\n")) else: raise error.Abort(_(b"repository is unrelated")) repo.ui.debug( b"found new changesets starting at " + b" ".join([short(f) for f in fetch]) + b"\n" ) progress.complete() repo.ui.debug(b"%d total queries\n" % reqcnt) return base, list(fetch), heads