From 63a0e0d420d3b476f40e702e8904d8aff733ed62 Mon Sep 17 00:00:00 2001 From: Jerry Zhang Date: Fri, 7 Aug 2026 14:49:18 -0700 Subject: [PATCH] completion: Use same config as list-topics for topic completion Tab completion built git with hardcoded values (origin/main, empty base branch globs) instead of the repo config, so it picked the wrong base branch and surfaced thousands of stale historical topics. Two fixes: - Apply config to the parsers before argcomplete runs, so completers see the same resolved settings normal commands do. - topic_completer now builds git and topics via make_git/get_topics exactly as list-topics does, from the parsed namespace. Consolidate get_git into make_git (now takes the namespace directly), which also breaks the completion -> revup cyclic import. --- revup/completion.py | 28 ++++++++++++++-------------- revup/git.py | 21 +++++++++++---------- revup/revup.py | 27 ++++++++++----------------- 3 files changed, 35 insertions(+), 41 deletions(-) diff --git a/revup/completion.py b/revup/completion.py index 9ea50fe..99150f5 100644 --- a/revup/completion.py +++ b/revup/completion.py @@ -41,26 +41,26 @@ def default_rc_file(self) -> str: } -def topic_completer( - prefix: str, parsed_args: Optional[argparse.Namespace] = None, **_kwargs: object -) -> List[str]: +def topic_completer(prefix: str, parsed_args: argparse.Namespace, **_kwargs: object) -> List[str]: try: - from revup import git, shell, toolkit + from revup import git, toolkit async def _get_names() -> List[str]: - sh = shell.Shell(quiet=True) - git_ctx = await git.make_git(sh, "", "", "origin", "main", "", False, "") - topics = await toolkit.get_topics(git_ctx) + # Build git and topics exactly as `revup toolkit list-topics` does, so + # completion resolves the same base branch and returns the same topics. + git_ctx = await git.make_git(parsed_args) + base_branch = getattr(parsed_args, "base_branch", None) or "" + relative_branch = getattr(parsed_args, "relative_branch", None) or "" + topics = await toolkit.get_topics(git_ctx, base_branch, relative_branch) return [t.name for t in topics.topics.values()] already = set() - if parsed_args is not None: - for attr in ("topics", "ref_or_topic"): - val = getattr(parsed_args, attr, None) - if isinstance(val, list): - already.update(val) - elif isinstance(val, str): - already.add(val) + for attr in ("topics", "ref_or_topic"): + val = getattr(parsed_args, attr, None) + if isinstance(val, list): + already.update(val) + elif isinstance(val, str): + already.add(val) return [n for n in asyncio.run(_get_names()) if n.startswith(prefix) and n not in already] except (OSError, RuntimeError): diff --git a/revup/git.py b/revup/git.py index efc23cd..bf71b68 100644 --- a/revup/git.py +++ b/revup/git.py @@ -1,5 +1,6 @@ from __future__ import annotations +import argparse import asyncio import copy import logging @@ -116,16 +117,16 @@ def get_default_git() -> str: return ret -async def make_git( - sh: shell.Shell, - git_path: str = "", - git_version: str = "", - remote_name: str = "", - main_branch: str = "", - base_branch_globs: str = "", - keep_temp: bool = False, - editor: str = "", -) -> "Git": +async def make_git(args: argparse.Namespace) -> "Git": + sh = shell.Shell(not args.verbose) + git_path = args.git_path + git_version = args.git_version + remote_name = args.fork_name if args.fork_name else args.remote_name + main_branch = args.main_branch + base_branch_globs = args.base_branch_globs + keep_temp = args.keep_temp + editor = args.editor + if not git_path: git_path = get_default_git() diff --git a/revup/revup.py b/revup/revup.py index 7137f55..dec2504 100755 --- a/revup/revup.py +++ b/revup/revup.py @@ -1,6 +1,7 @@ from __future__ import annotations import argparse +import asyncio import logging import os import stat @@ -108,22 +109,6 @@ async def get_config() -> config.Config: return conf -async def get_git(args: argparse.Namespace) -> git.Git: - sh = shell.Shell(not args.verbose) - git_ctx = await git.make_git( - sh, - args.git_path, - args.git_version, - args.fork_name if args.fork_name else args.remote_name, - args.main_branch, - args.base_branch_globs, - args.keep_temp, - args.editor, - ) - - return git_ctx - - def dump_args(args: argparse.Namespace) -> None: if args.verbose: import json @@ -315,6 +300,14 @@ def build_parser() -> Tuple[RevupArgParser, List[RevupArgParser]]: if "_ARGCOMPLETE" in os.environ: import argcomplete + # Apply config before completing so completers resolve the same settings + # (main branch, remote, base branch globs) that normal commands use. + try: + conf = asyncio.run(get_config()) + conf.apply_to_parsers(all_parsers) + except (OSError, RuntimeError, ValueError, RevupUsageException): + pass + argcomplete.autocomplete( revup_parser, always_complete_options=False, @@ -353,7 +346,7 @@ async def main(revup_parser: RevupArgParser, all_parsers: List[RevupArgParser]) dump_args(args) - git_ctx = await get_git(args) + git_ctx = await git.make_git(args) if args.cmd == "toolkit": from revup import toolkit