def annotate()

in eden/scm/edenscm/mercurial/commands/__init__.py [0:0]


def annotate(ui, repo, *pats, **opts):
    """show changeset information by line for each file

    List changes in files, showing the revision id responsible for
    each line.

    This command is useful for discovering when a change was made and
    by whom.

    If you include --file, --user, or --date, the revision number is
    suppressed unless you also include --number.

    Without the -a/--text option, annotate will avoid processing files
    it detects as binary. With -a, annotate will annotate the file
    anyway, although the results will probably be neither useful
    nor desirable.

    Returns 0 on success.
    """
    if not pats:
        raise error.Abort(_("at least one filename or pattern is required"))

    ctx = scmutil.revsingle(repo, opts.get("rev"))

    rootfm = ui.formatter("annotate", opts)
    if ui.quiet:
        datefunc = util.shortdate
    else:
        datefunc = util.datestr
    if ctx.rev() is None:

        def hexfn(node):
            if node is None:
                return None
            else:
                return rootfm.hexfunc(node)

        if opts.get("changeset"):
            # omit "+" suffix which is appended to node hex
            def formatrev(rev):
                if rev is None:
                    return "%d" % ctx.p1().rev()
                else:
                    return "%d" % rev

        else:

            def formatrev(rev):
                if rev is None:
                    return "%d+" % ctx.p1().rev()
                else:
                    return "%d " % rev

        def formathex(hex):
            if hex is None:
                return "%s+" % rootfm.hexfunc(ctx.p1().node())
            else:
                return "%s " % hex

    else:
        hexfn = rootfm.hexfunc
        formatrev = formathex = pycompat.bytestr

    now = time.time()

    def agebucket(d):
        t, tz = d
        if t > now - 3600:
            return "1hour"
        day = 86400
        if t > now - day:
            return "1day"
        if t > now - 7 * day:
            return "7day"
        if t > now - 30 * day:
            return "30day"
        if t > now - 60 * day:
            return "60day"
        if t > now - 180 * day:
            return "180day"
        if t > now - 360 * day:
            return "360day"
        return "old"

    opmap = [
        ("user", " ", lambda x: x.fctx.user(), ui.shortuser),
        ("number", " ", lambda x: x.fctx.rev(), formatrev),
        ("changeset", " ", lambda x: hexfn(x.fctx.node()), formathex),
        ("date", " ", lambda x: x.fctx.date(), util.cachefunc(datefunc)),
        ("file", " ", lambda x: x.fctx.path(), str),
        ("line_number", ":", lambda x: x.lineno, str),
        ("age_bucket", "", lambda x: agebucket(x.fctx.date()), lambda x: ""),
    ]
    fieldnamemap = {"number": "rev", "changeset": "node"}

    if (
        not opts.get("user")
        and not opts.get("changeset")
        and not opts.get("date")
        and not opts.get("file")
    ):
        opts["number"] = True
    opts["age_bucket"] = True

    linenumber = opts.get("line_number") is not None
    if linenumber and (not opts.get("changeset")) and (not opts.get("number")):
        raise error.Abort(_("at least one of -n/-c is required for -l"))

    ui.pager("annotate")

    if rootfm.isplain():

        def makefunc(get, fmt):
            return lambda x: fmt(get(x))

    else:

        def makefunc(get, fmt):
            return get

    funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap if opts.get(op)]
    funcmap[0] = (funcmap[0][0], "")  # no separator in front of first column
    fields = " ".join(
        fieldnamemap.get(op, op) for op, sep, get, fmt in opmap if opts.get(op)
    )

    def bad(x, y):
        raise error.Abort("%s: %s" % (x, y))

    m = scmutil.match(ctx, pats, opts, badfn=bad)

    follow = not opts.get("no_follow")
    diffopts = patch.difffeatureopts(ui, opts, section="annotate", whitespace=True)
    skiprevs = opts.get("skip")
    if skiprevs:
        skiprevs = scmutil.revrange(repo, skiprevs)

    for abs in ctx.walk(m):
        fctx = ctx[abs]
        rootfm.startitem()
        rootfm.data(abspath=abs, path=m.rel(abs))
        if not opts.get("text") and fctx.isbinary():
            rootfm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
            continue

        fm = rootfm.nested("lines")
        lines = list(
            fctx.annotate(
                follow=follow,
                linenumber=linenumber,
                skiprevs=skiprevs,
                diffopts=diffopts,
            )
        )
        if not lines:
            fm.end()
            continue
        formats = []
        pieces = []

        for f, sep in funcmap:
            l = [f(n) for n, dummy in lines]
            if fm.isplain():
                sizes = [encoding.colwidth(x) for x in l]
                ml = max(sizes)
                formats.append([sep + " " * (ml - w) + "%s" for w in sizes])
            else:
                formats.append(["%s" for x in l])
            pieces.append(l)

        agebuckets = [agebucket(x.fctx.date()) for x, dummy in lines]

        for f, p, l, a in zip(zip(*formats), zip(*pieces), lines, agebuckets):
            fm.startitem()
            sep = "* " if l[0].skip else ": "
            fm.write(fields, "".join(f) + sep, *p, label="blame.age." + a)
            fm.write("line", "%s", pycompat.decodeutf8(l[1], errors="replace"))

        if not lines[-1][1].endswith(b"\n"):
            fm.plain("\n")
        fm.end()

    rootfm.end()