in eden/scm/edenscm/hgext/fastlog.py [0:0]
def fastlogfollow(orig, repo, subset, x, name, followfirst=False):
if followfirst:
# fastlog does not support followfirst=True
repo.ui.debug("fastlog: not used because 'followfirst' is set\n")
return orig(repo, subset, x, name, followfirst)
args = revset.getargsdict(x, name, "file startrev")
if "file" not in args:
# Not interesting for fastlog case.
repo.ui.debug("fastlog: not used because 'file' is not provided\n")
return orig(repo, subset, x, name, followfirst)
if "startrev" in args:
revs = revset.getset(repo, smartset.fullreposet(repo), args["startrev"])
it = iter(revs)
try:
startrev = next(it)
except StopIteration:
startrev = repo["."].rev()
try:
next(it)
# fastlog does not support multiple startrevs
repo.ui.debug("fastlog: not used because multiple revs are provided\n")
return orig(repo, subset, x, name, followfirst)
except StopIteration:
# supported by fastlog: startrev contains a single rev
pass
else:
startrev = repo["."].rev()
reponame = repo.ui.config("fbscmquery", "reponame")
if not reponame or not repo.ui.configbool("fastlog", "enabled"):
repo.ui.debug("fastlog: not used because fastlog is disabled\n")
return orig(repo, subset, x, name, followfirst)
try:
# Test that the GraphQL client can be constructed, to rule
# out configuration issues like missing `.arcrc` etc.
_graphqlclient = graphql.Client(repo=repo)
except Exception as ex:
repo.ui.debug(
"fastlog: not used because graphql client cannot be constructed: %r\n" % ex
)
return orig(repo, subset, x, name, followfirst)
path = revset.getstring(args["file"], _("%s expected a pattern") % name)
if path.startswith("path:"):
# strip "path:" prefix
path = path[5:]
if any(path.startswith("%s:" % prefix) for prefix in matchmod.allpatternkinds):
# Patterns other than "path:" are not supported
repo.ui.debug(
"fastlog: not used because '%s:' patterns are not supported\n"
% path.split(":", 1)[0]
)
return orig(repo, subset, x, name, followfirst)
files = [path]
if not files or "." in files:
# Walking the whole repo - bail on fastlog
repo.ui.debug("fastlog: not used because walking through the entire repo\n")
return orig(repo, subset, x, name, followfirst)
dirs = set()
wvfs = repo.wvfs
for path in files:
if wvfs.isdir(path) and not wvfs.islink(path):
dirs.update([path + "/"])
else:
if repo.ui.configbool("fastlog", "files"):
dirs.update([path])
else:
# bail on symlinks, and also bail on files for now
# with follow behavior, for files, we are supposed
# to track copies / renames, but it isn't convenient
# to do this through scmquery
repo.ui.debug(
"fastlog: not used because %s is not a directory\n" % path
)
return orig(repo, subset, x, name, followfirst)
rev = startrev
parents = repo.changelog.parentrevs
public = set()
# Our criterion for invoking fastlog is finding a single
# common public ancestor from the current head. First we
# have to walk back through drafts to find all interesting
# public parents. Typically this will just be one, but if
# there are merged drafts, we may have multiple parents.
if repo[rev].phase() == phases.public:
public.add(rev)
else:
queue = deque()
queue.append(rev)
seen = set()
while queue:
cur = queue.popleft()
if cur not in seen:
seen.add(cur)
if repo[cur].mutable():
for p in parents(cur):
if p != nullrev:
queue.append(p)
else:
public.add(cur)
def fastlog(repo, startrev, dirs, localmatch):
filefunc = repo.changelog.readfiles
for parent in lazyparents(startrev, public, parents):
files = filefunc(parent)
if dirmatches(files, dirs):
yield parent
repo.ui.debug("found common parent at %s\n" % repo[parent].hex())
for rev in combinator(repo, parent, dirs, localmatch):
yield rev
def combinator(repo, rev, dirs, localmatch):
"""combinator(repo, rev, dirs, localmatch)
Make parallel local and remote queries along ancestors of
rev along path and combine results, eliminating duplicates,
restricting results to those which match dirs
"""
LOCAL = "L"
REMOTE = "R"
queue = util.queue(FASTLOG_QUEUE_SIZE + 100)
hash = repo[rev].hex()
localenabled = repo.ui.configbool("fastlog", "scan-local-repo")
if localenabled:
local = LocalIteratorThread(queue, LOCAL, rev, dirs, localmatch, repo)
else:
local = None
remote = FastLogThread(queue, REMOTE, reponame, "hg", hash, dirs, repo)
# Allow debugging either remote or local path
debug = repo.ui.config("fastlog", "debug")
if debug != "local":
repo.ui.debug("starting fastlog at %s\n" % hash)
remote.start()
if local is not None and debug != "remote":
local.start()
seen = set([rev])
try:
while True:
try:
producer, success, msg = queue.get(True, 3600)
except util.empty:
raise error.Abort("Timeout reading log data")
if not success:
if producer == LOCAL or local is None:
raise error.Abort(msg)
elif msg:
repo.ui.log("hgfastlog", msg)
continue
if msg is None:
# Empty message means no more results
return
rev = msg
if debug:
if producer == LOCAL:
repo.ui.debug("LOCAL:: %s\n" % msg)
elif producer == REMOTE:
repo.ui.debug("REMOTE:: %s\n" % msg)
if rev not in seen:
seen.add(rev)
yield rev
finally:
if local is not None:
local.stop()
remote.stop()
revgen = fastlog(repo, rev, dirs, dirmatches)
fastlogset = smartset.generatorset(revgen, iterasc=False, repo=repo)
# Optimization: typically for "reverse(:.) & follow(path)" used by
# "hg log". The left side is more expensive, although it has smaller
# "weight". Make sure fastlogset is on the left side to avoid slow
# walking through ":.".
if subset.isdescending():
fastlogset.reverse()
return fastlogset & subset
return subset & fastlogset