def fastlogfollow()

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