Source code for xompletions.git

"""Git completer: subcommands, aliases, options, and refs."""

import subprocess

from xonsh.completers.tools import RichCompletion
from xonsh.parsers.completion_context import CommandContext

_REF_SUBCMDS = frozenset(
    {
        "checkout",
        "switch",
        "merge",
        "rebase",
        "branch",
        "cherry-pick",
        "log",
        "diff",
        "show",
        "reset",
        "push",
        "pull",
        "fetch",
        "stash",
        "bisect",
        "blame",
        "revert",
        "tag",
    }
)


def _run_git(*args) -> "str | None":
    try:
        return subprocess.check_output(
            ["git", *args],
            stderr=subprocess.PIPE,
            text=True,
            timeout=5,
        )
    except subprocess.CalledProcessError as e:
        err = (e.stderr or "").strip()
        if err:
            from xonsh.tools import print_above_prompt

            print_above_prompt(f"completer git: {err}")
        return None
    except (OSError, FileNotFoundError, subprocess.TimeoutExpired):
        return None


def _get_aliases() -> "dict[str, str]":
    """Configured git aliases mapped to their bodies (``co`` → ``checkout``).

    ``-z`` NUL-terminates entries so multi-line alias bodies stay
    parseable; within an entry the key is separated from the value by
    the first newline.
    """
    out = _run_git("config", "-z", "--get-regexp", r"^alias\.")
    if out is None:
        return {}
    aliases = {}
    for entry in out.split("\0"):
        name, _, body = entry.partition("\n")
        if name.startswith("alias."):
            aliases[name.removeprefix("alias.")] = body.replace("\n", " ")
    return aliases


[docs] def xonsh_complete(context: CommandContext): """Complete git subcommands, aliases, options, and branch/tag refs.""" if context.arg_index == 0: return # git <subcmd><Tab> — the ``alias`` group lists user-configured # aliases (``co = checkout``) next to the subcommands; the alias # body is shown as the completion description. if context.arg_index == 1: out = _run_git("--list-cmds=main,others,alias") if out is None: return aliases = _get_aliases() return { RichCompletion(s, append_space=True, description=aliases.get(s, "")) for s in out.split() }, False subcmd = context.args[1].value # Resolve a configured alias (``co`` → ``checkout``) so option and # ref completion work for aliases too. Subcommands already known to # take refs skip the lookup — git ignores aliases that shadow real # commands. A shell alias (``!...``) has no git subcommand to # resolve to and must never reach ``--git-completion-helper`` below # (git would *execute* the alias body), so defer to the next # completer. if subcmd not in _REF_SUBCMDS: alias_body = _get_aliases().get(subcmd) if alias_body is not None: if alias_body.startswith("!"): return body_words = alias_body.split() if body_words: subcmd = body_words[0] # git <subcmd> -<Tab> if context.prefix.startswith("-"): out = _run_git(subcmd, "--git-completion-helper") if out is None: return return {RichCompletion(o) for o in out.split() if o.startswith("-")}, False # git <subcmd> <ref><Tab> if subcmd in _REF_SUBCMDS: out = _run_git( "for-each-ref", "--format=%(refname:short)", "refs/heads/", "refs/tags/", "refs/remotes/", ) if out is None: return return {RichCompletion(r, append_space=True) for r in out.split()}, False