Exemple #1
0
def _oldworkingcopyparent(repo, subset, x):
    """``oldworkingcopyparent([index])``
    previous working copy parent

    'index' is how many undoable commands you want to look back.  See 'hg undo'.
    """
    args = revset.getargsdict(x, "oldoworkingcopyrevset", "reverseindex")
    reverseindex = revsetlang.getinteger(
        args.get("reverseindex"), _("index must be a positive interger"), 1)
    revs = _getoldworkingcopyparent(repo, reverseindex)
    return subset & smartset.baseset(revs)
Exemple #2
0
def smartlogrevset(repo, subset, x):
    """``smartlog([heads], [master])``
    Changesets relevent to you.

    'heads' overrides what feature branches to include.
    (default: 'interestingbookmarks() + heads(draft()) + .')

    'master' is the head of the public branch.
    (default: 'interestingmaster()')
    """
    args = revset.getargsdict(x, "smartlogrevset", "heads master")
    if "master" in args:
        masterset = revset.getset(repo, subset, args["master"])
    else:
        masterset = repo.revs("interestingmaster()")

    if "heads" in args:
        heads = set(revset.getset(repo, subset, args["heads"]))
    else:
        heads = set(repo.revs("interestingbookmarks() + heads(draft()) + ."))

    # Remove "null" commit. "::x" does not support it.
    masterset -= smartset.baseset([nodemod.nullrev])
    if nodemod.nullrev in heads:
        heads.remove(nodemod.nullrev)
    # Explicitly disable revnum deprecation warnings.
    with repo.ui.configoverride({("devel", "legacy.revnum:real"): ""}):
        # Select ancestors that are draft.
        drafts = repo.revs("draft() & ::%ld", heads)
        # Include parents of drafts, and public heads.
        revs = repo.revs(
            "parents(%ld) + %ld + %ld + %ld", drafts, drafts, heads, masterset
        )

    # Include the ancestor of above commits to make the graph connected.
    #
    # When calculating ancestors, filter commits using 'public()' to reduce the
    # number of commits to calculate. This is sound because the above logic
    # includes p1 of draft commits, and assume master is public. Practically,
    # this optimization can make a 3x difference.
    revs = smartset.baseset(repo.revs("ancestor(%ld & public()) + %ld", revs, revs))

    # Collapse long obsoleted stack - only keep their heads and roots.
    # This is incompatible with automation (namely, nuclide-core) yet.
    if repo.ui.configbool("smartlog", "collapse-obsolete") and not repo.ui.plain():
        obsrevs = smartset.baseset(repo.revs("%ld & obsolete()", revs))
        hiderevs = smartset.baseset(
            repo.revs("%ld - (heads(%ld) + roots(%ld))", obsrevs, obsrevs, obsrevs)
        )
        revs = repo.revs("%ld - %ld", revs, hiderevs)

    return subset & revs
Exemple #3
0
def _olddraft(repo, subset, x):
    """``olddraft([index])``
    previous draft commits

    'index' is how many undoable commands you want to look back
    an undoable command is one that changed draft heads, bookmarks
    and or working copy parent.  Note that olddraft uses an absolute index and
    so olddraft(1) represents the state after an hg undo -a and not an hg undo.
    Note: this revset may include hidden commits
    """
    args = revset.getargsdict(x, "olddraftrevset", "reverseindex")
    reverseindex = revsetlang.getinteger(args.get("reverseindex"),
                                         _("index must be a positive integer"),
                                         1)
    revs = _getolddrafts(repo, reverseindex)
    return subset & smartset.baseset(revs)
Exemple #4
0
def _localbranch(repo, subset, x):
    """``_localbranch(changectx)``
    localbranch changesets

    Returns all commits within the same localbranch as the changeset(s). A local
    branch is all draft changesets that are connected, uninterupted by public
    changesets.  Any draft commit within a branch, or a public commit at the
    base of the branch, can be used to identify localbranches.
    """
    # executed on an filtered repo
    args = revset.getargsdict(x, "branchrevset", "changectx")
    revstring = revsetlang.getstring(
        args.get("changectx"), _("localbranch argument must be a changectx"))
    revs = repo.revs(revstring)
    # we assume that there is only a single rev
    if repo[revs.first()].phase() == phases.public:
        querystring = revsetlang.formatspec("(children(%d) & draft())::",
                                            revs.first())
    else:
        querystring = revsetlang.formatspec("((::%ld) & draft())::", revs)
    return subset & smartset.baseset(repo.revs(querystring))
Exemple #5
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)

    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()

        local = LocalIteratorThread(queue, LOCAL, rev, dirs, localmatch, repo)
        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 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:
                        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:
            local.stop()
            remote.stop()

    revgen = fastlog(repo, rev, dirs, dirmatches)
    fastlogset = smartset.generatorset(revgen, iterasc=False)
    # 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