git-cai-cli 0.17.0__tar.gz → 0.17.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/PKG-INFO +1 -1
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/docs/git-cai.txt +2 -1
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/docs/man/git-cai.1 +4 -3
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/_version.py +3 -3
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/cli/cli.py +0 -1
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/cli/modes.py +3 -2
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/gitutils.py +12 -4
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/init.py +4 -4
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/options.py +4 -8
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/squash.py +3 -4
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/main.py +4 -2
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli.egg-info/PKG-INFO +1 -1
- git_cai_cli-0.17.1/src/git_cai_cli.egg-info/scm_version.json +8 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/integration/test_gitutils_integration.py +22 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/integration/test_modes_integration.py +4 -16
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_gitutils.py +12 -12
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_options.py +1 -1
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_squash.py +4 -5
- git_cai_cli-0.17.0/src/git_cai_cli.egg-info/scm_version.json +0 -8
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.caiignore +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.gitattributes +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.github/cd/.SRCINFO +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.github/cd/PKGBUILD +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.github/ci/_version.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.github/ci/cai_config.ci.yml +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.github/ci/tokens.ci.yml +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.github/workflows/python-tests.yml +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.github/workflows/release.yml +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.github/workflows/release_aur.yml +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.gitignore +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.linters/.bandit.yml +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.linters/.checkov.yml +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.linters/.flake8 +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.linters/.ls-lint.yml +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.linters/.markdownlint.json +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.linters/.proselintrc.json +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.linters/.pylintrc +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.linters/.yamllint.yml +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.linters/check_git_branch_name.sh +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.linters/lychee.toml +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.linters/pyrightconfig.json +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.markdownlintignore +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.mega-linter.yml +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.semgrepignore +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/.trivyignore +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/CLAUDE.md +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/LICENSE +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/Makefile +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/README.md +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/cai_config.yml +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/pyproject.toml +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/setup.cfg +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/__init__.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/cli/__init__.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/cli/helptext.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/__init__.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/changelog.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/completion.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/config.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/doctor.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/explain.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/generation.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/languages.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/llm.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/pr.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/prompts_fallback.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/release.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/secrets.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/spinner.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/split.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/stats.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli/core/validate.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli.egg-info/SOURCES.txt +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli.egg-info/dependency_links.txt +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli.egg-info/entry_points.txt +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli.egg-info/requires.txt +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli.egg-info/scm_file_list.json +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/src/git_cai_cli.egg-info/top_level.txt +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/conftest.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/integration/test_cli_integration.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/integration/test_config_integration.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/integration/test_options_integration.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/integration/test_pr_integration.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/integration/test_squash_integration.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_amend.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_branch_context.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_changelog.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_classification.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_cli.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_completion.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_config.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_conventional.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_custom_provider.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_doctor.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_explain.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_generation.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_helptext.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_init.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_llm.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_main.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_modes.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_pr.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_print.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_prompt_loading.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_release.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_secrets.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_set_config.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_signoff.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_spinner.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_split.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_stats.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/tests/unit/test_validate.py +0 -0
- {git_cai_cli-0.17.0 → git_cai_cli-0.17.1}/uv.lock +0 -0
|
@@ -324,7 +324,8 @@ Useful when a working tree contains unrelated staged changes and the commit
|
|
|
324
324
|
should focus on a subset of them, or when you want the LLM to concentrate
|
|
325
325
|
on a specific file.
|
|
326
326
|
+
|
|
327
|
-
|
|
327
|
+
Only valid in the default commit mode; cannot be used with `--amend`,
|
|
328
|
+
`--list`, `--update`, or `--squash`.
|
|
328
329
|
+
|
|
329
330
|
----
|
|
330
331
|
git cai -f src/foo.py
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
.\" Title: git-cai
|
|
3
3
|
.\" Author: Thorsten Foltz
|
|
4
4
|
.\" Generator: Asciidoctor 2.0.26
|
|
5
|
-
.\" Date: 2026-
|
|
5
|
+
.\" Date: 2026-10-11
|
|
6
6
|
.\" Manual: \ \&
|
|
7
7
|
.\" Source: \ \&
|
|
8
8
|
.\" Language: English
|
|
9
9
|
.\"
|
|
10
|
-
.TH "GIT\-CAI" "1" "2026-
|
|
10
|
+
.TH "GIT\-CAI" "1" "2026-10-11" "\ \&" "\ \&"
|
|
11
11
|
.ie \n(.g .ds Aq \(aq
|
|
12
12
|
.el .ds Aq '
|
|
13
13
|
.ss \n[.ss] 0
|
|
@@ -606,7 +606,8 @@ Useful when a working tree contains unrelated staged changes and the commit
|
|
|
606
606
|
should focus on a subset of them, or when you want the LLM to concentrate
|
|
607
607
|
on a specific file.
|
|
608
608
|
.sp
|
|
609
|
-
|
|
609
|
+
Only valid in the default commit mode; cannot be used with \f(CR\-\-amend\fP,
|
|
610
|
+
\f(CR\-\-list\fP, \f(CR\-\-update\fP, or \f(CR\-\-squash\fP.
|
|
610
611
|
.sp
|
|
611
612
|
.if n .RS 4
|
|
612
613
|
.nf
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.17.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 17,
|
|
21
|
+
__version__ = version = '0.17.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 17, 1)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g1fd24936a'
|
|
@@ -83,7 +83,6 @@ def callback( # pylint: disable=too-many-arguments,too-many-positional-argument
|
|
|
83
83
|
"--list",
|
|
84
84
|
"-l",
|
|
85
85
|
help="List information (e.g. languages, styles, providers, config)",
|
|
86
|
-
is_flag=True,
|
|
87
86
|
),
|
|
88
87
|
list_arg: str = typer.Argument(
|
|
89
88
|
None, help="Optional argument for --list or --squash (see docs)"
|
|
@@ -163,9 +163,10 @@ def validate_options(
|
|
|
163
163
|
)
|
|
164
164
|
raise typer.Exit(code=1)
|
|
165
165
|
|
|
166
|
-
|
|
166
|
+
# AMEND regenerates from the last commit's diff and never reads --files.
|
|
167
|
+
if files and mode is not Mode.COMMIT:
|
|
167
168
|
typer.echo(
|
|
168
|
-
"Error: --files can only be used in COMMIT
|
|
169
|
+
"Error: --files can only be used in COMMIT mode.",
|
|
169
170
|
err=True,
|
|
170
171
|
)
|
|
171
172
|
raise typer.Exit(code=1)
|
|
@@ -328,12 +328,18 @@ def git_diff_excluding(
|
|
|
328
328
|
"""Run `git diff --cached` honoring `.caiignore` and an optional file whitelist."""
|
|
329
329
|
exclude_files = _load_caiignore_patterns(repo_root)
|
|
330
330
|
|
|
331
|
+
# `:/` and `top` anchor pathspecs at the repo root, so running from a
|
|
332
|
+
# subdirectory still sees every staged file (the commit includes them all)
|
|
333
|
+
# and `.caiignore` patterns match relative to the root. `files` stay
|
|
334
|
+
# relative to the user's cwd, as typed. A leading `/` is dropped: `top`
|
|
335
|
+
# already anchors at the root, and in long form it would mean an
|
|
336
|
+
# absolute path that silently matches nothing.
|
|
331
337
|
cmd = ["git", "diff", "--cached", "--"]
|
|
332
338
|
if files:
|
|
333
339
|
cmd.extend(files)
|
|
334
340
|
else:
|
|
335
|
-
cmd.append("
|
|
336
|
-
cmd.extend(f"
|
|
341
|
+
cmd.append(":/")
|
|
342
|
+
cmd.extend(f":(top,exclude){pattern.lstrip('/')}" for pattern in exclude_files)
|
|
337
343
|
|
|
338
344
|
result = run_cmd(cmd, capture_output=True, text=True, check=False)
|
|
339
345
|
if result.returncode != 0:
|
|
@@ -423,13 +429,15 @@ def collect_staged_file_contents(
|
|
|
423
429
|
"""
|
|
424
430
|
exclude_patterns = _load_caiignore_patterns(repo_root)
|
|
425
431
|
|
|
432
|
+
# Same pathspec rules as git_diff_excluding: whole repo by default,
|
|
433
|
+
# `files` relative to the user's cwd. Output names are root-relative.
|
|
426
434
|
cmd = ["git", "diff", "--cached", "--name-only", "--"]
|
|
427
435
|
if files:
|
|
428
436
|
cmd.extend(files)
|
|
429
437
|
else:
|
|
430
|
-
cmd.append("
|
|
438
|
+
cmd.append(":/")
|
|
431
439
|
|
|
432
|
-
result = run_cmd(cmd, capture_output=True, text=True, check=False
|
|
440
|
+
result = run_cmd(cmd, capture_output=True, text=True, check=False)
|
|
433
441
|
if result.returncode != 0:
|
|
434
442
|
log.error("git diff --name-only failed: %s", result.stderr.strip())
|
|
435
443
|
return ""
|
|
@@ -124,7 +124,7 @@ def _write_tokens_file(provider: str, token: str, tokens_path: Path) -> None:
|
|
|
124
124
|
|
|
125
125
|
if provider in tokens and not typer.confirm(
|
|
126
126
|
f"A {provider} token already exists in {tokens_path}. Overwrite?",
|
|
127
|
-
default=
|
|
127
|
+
default=None,
|
|
128
128
|
):
|
|
129
129
|
typer.echo("Keeping existing token.")
|
|
130
130
|
return
|
|
@@ -178,7 +178,7 @@ def run_init_wizard(
|
|
|
178
178
|
try:
|
|
179
179
|
if config_path.exists() and config_path.stat().st_size > 0:
|
|
180
180
|
if not typer.confirm(
|
|
181
|
-
f"\n{config_path} already exists. Overwrite?", default=
|
|
181
|
+
f"\n{config_path} already exists. Overwrite?", default=None
|
|
182
182
|
):
|
|
183
183
|
typer.echo("Aborted. Existing configuration left untouched.")
|
|
184
184
|
return 0
|
|
@@ -186,7 +186,7 @@ def run_init_wizard(
|
|
|
186
186
|
provider = _pick_provider()
|
|
187
187
|
language = _pick_language()
|
|
188
188
|
style = _pick_style()
|
|
189
|
-
emoji = typer.confirm("Use emoji in commit messages?", default=
|
|
189
|
+
emoji = typer.confirm("Use emoji in commit messages?", default=None)
|
|
190
190
|
|
|
191
191
|
if provider not in TOKENLESS_PROVIDERS:
|
|
192
192
|
token = _read_token(provider)
|
|
@@ -205,6 +205,6 @@ def run_init_wizard(
|
|
|
205
205
|
"and run 'git cai'."
|
|
206
206
|
)
|
|
207
207
|
return 0
|
|
208
|
-
except KeyboardInterrupt:
|
|
208
|
+
except (KeyboardInterrupt, typer.Abort):
|
|
209
209
|
typer.echo("\nAborted.", err=True)
|
|
210
210
|
return 130
|
|
@@ -104,14 +104,10 @@ class CliManager:
|
|
|
104
104
|
print(f"⬆️ Update available: {current_version} → {latest_version}")
|
|
105
105
|
|
|
106
106
|
if not auto_confirm:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
.strip()
|
|
112
|
-
.lower()
|
|
113
|
-
)
|
|
114
|
-
if choice not in ("y", "yes"):
|
|
107
|
+
if not typer.confirm(
|
|
108
|
+
"Do you want to update now using 'pipx upgrade git-cai-cli'?",
|
|
109
|
+
default=None,
|
|
110
|
+
):
|
|
115
111
|
print("❌ Update cancelled.")
|
|
116
112
|
return
|
|
117
113
|
|
|
@@ -11,6 +11,7 @@ import tempfile
|
|
|
11
11
|
import time
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
|
+
import typer
|
|
14
15
|
from git_cai_cli.core.config import (
|
|
15
16
|
apply_provider_overrides,
|
|
16
17
|
load_config,
|
|
@@ -140,8 +141,7 @@ def _resolve_squash_target(squash_arg: str) -> str:
|
|
|
140
141
|
count,
|
|
141
142
|
branch_commits,
|
|
142
143
|
)
|
|
143
|
-
|
|
144
|
-
if choice not in ("y", "yes"):
|
|
144
|
+
if not typer.confirm("Continue anyway?", default=None):
|
|
145
145
|
log.info("Squash cancelled.")
|
|
146
146
|
sys.exit(0)
|
|
147
147
|
|
|
@@ -472,8 +472,7 @@ def squash_branch(
|
|
|
472
472
|
" git push --force-with-lease\n\n"
|
|
473
473
|
"This is a safe force-push that prevents overwriting others' commits.\n\n"
|
|
474
474
|
)
|
|
475
|
-
|
|
476
|
-
if choice in ("y", "yes"):
|
|
475
|
+
if typer.confirm("Shall I execute it for you now?", default=None):
|
|
477
476
|
push_result = subprocess.run(
|
|
478
477
|
["git", "push", "--force-with-lease"],
|
|
479
478
|
capture_output=True,
|
|
@@ -82,7 +82,7 @@ def _route_false_alarm(findings: list, repo_root: Path | None) -> None:
|
|
|
82
82
|
err=True,
|
|
83
83
|
)
|
|
84
84
|
typer.echo(" 3) skip — send it this once; remember nothing", err=True)
|
|
85
|
-
choice = typer.prompt("Choose", default="3").strip()
|
|
85
|
+
choice = typer.prompt("Choose", default="3", err=True).strip()
|
|
86
86
|
|
|
87
87
|
if choice == "1":
|
|
88
88
|
if repo_root is None:
|
|
@@ -384,7 +384,9 @@ def run(
|
|
|
384
384
|
)
|
|
385
385
|
raise typer.Exit(code=1)
|
|
386
386
|
if not typer.confirm(
|
|
387
|
-
"Send this content to the provider anyway?",
|
|
387
|
+
"Send this content to the provider anyway?",
|
|
388
|
+
default=None,
|
|
389
|
+
err=True,
|
|
388
390
|
):
|
|
389
391
|
typer.echo("Aborted. Nothing was sent.", err=True)
|
|
390
392
|
raise typer.Exit(code=1)
|
|
@@ -68,3 +68,25 @@ def test_git_diff_excluding_integration(temp_git_repo):
|
|
|
68
68
|
|
|
69
69
|
assert any("file1.txt" in h for h in headers)
|
|
70
70
|
assert not any("file2.txt" in h for h in headers)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_git_diff_excluding_from_subdirectory_sees_whole_repo(temp_git_repo):
|
|
74
|
+
"""Run from a subdirectory: every staged file is included (the commit
|
|
75
|
+
takes them all) and root `.caiignore` patterns still match."""
|
|
76
|
+
(temp_git_repo / ".caiignore").write_text("/file2.txt\n")
|
|
77
|
+
sub = temp_git_repo / "sub"
|
|
78
|
+
sub.mkdir()
|
|
79
|
+
(sub / "inner.txt").write_text("inner\n")
|
|
80
|
+
(temp_git_repo / "file1.txt").write_text("changed 1\n")
|
|
81
|
+
(temp_git_repo / "file2.txt").write_text("changed 2\n")
|
|
82
|
+
subprocess.run(["git", "add", "."], cwd=temp_git_repo, check=True)
|
|
83
|
+
|
|
84
|
+
diff = git_diff_excluding(
|
|
85
|
+
repo_root=temp_git_repo,
|
|
86
|
+
run_cmd=lambda *args, **kwargs: subprocess.run(*args, cwd=sub, **kwargs),
|
|
87
|
+
)
|
|
88
|
+
headers = [line for line in diff.splitlines() if line.startswith("diff --git")]
|
|
89
|
+
|
|
90
|
+
assert any("sub/inner.txt" in h for h in headers)
|
|
91
|
+
assert any("file1.txt" in h for h in headers)
|
|
92
|
+
assert not any("file2.txt" in h for h in headers)
|
|
@@ -174,10 +174,10 @@ def test_validate_options_valid_combinations():
|
|
|
174
174
|
|
|
175
175
|
@pytest.mark.parametrize(
|
|
176
176
|
"bad_mode",
|
|
177
|
-
[Mode.LIST, Mode.UPDATE, Mode.SQUASH],
|
|
177
|
+
[Mode.LIST, Mode.UPDATE, Mode.SQUASH, Mode.AMEND],
|
|
178
178
|
)
|
|
179
|
-
def
|
|
180
|
-
"""--files must be rejected
|
|
179
|
+
def test_validate_options_files_rejected_outside_commit(bad_mode, capsys):
|
|
180
|
+
"""--files must be rejected outside COMMIT (AMEND never reads it)."""
|
|
181
181
|
with pytest.raises(typer.Exit) as exc:
|
|
182
182
|
modes.validate_options(
|
|
183
183
|
mode=bad_mode,
|
|
@@ -188,7 +188,7 @@ def test_validate_options_files_rejected_outside_commit_amend(bad_mode, capsys):
|
|
|
188
188
|
files=["x.py"],
|
|
189
189
|
)
|
|
190
190
|
captured = capsys.readouterr()
|
|
191
|
-
assert "--files can only be used in COMMIT
|
|
191
|
+
assert "--files can only be used in COMMIT mode." in captured.err
|
|
192
192
|
assert exc.value.exit_code == 1
|
|
193
193
|
|
|
194
194
|
|
|
@@ -202,15 +202,3 @@ def test_validate_options_files_allowed_in_commit_mode():
|
|
|
202
202
|
version_flag=False,
|
|
203
203
|
files=["src/foo.py", "src/bar.py"],
|
|
204
204
|
)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
def test_validate_options_files_allowed_in_amend_mode():
|
|
208
|
-
"""--files is allowed in AMEND mode."""
|
|
209
|
-
modes.validate_options(
|
|
210
|
-
mode=Mode.AMEND,
|
|
211
|
-
stage_tracked=False,
|
|
212
|
-
enable_debug=False,
|
|
213
|
-
help_flag=False,
|
|
214
|
-
version_flag=False,
|
|
215
|
-
files=["src/foo.py"],
|
|
216
|
-
)
|
|
@@ -181,7 +181,7 @@ def test_get_current_branch_not_in_repo():
|
|
|
181
181
|
|
|
182
182
|
def test_git_diff_excluding_reads_ignore_file_and_excludes_patterns(tmp_path):
|
|
183
183
|
"""
|
|
184
|
-
git_diff_excluding() should append
|
|
184
|
+
git_diff_excluding() should append :(top,exclude)pattern for each line in .caiignore.
|
|
185
185
|
"""
|
|
186
186
|
repo_root = tmp_path
|
|
187
187
|
ignore_file = repo_root / ".caiignore"
|
|
@@ -193,8 +193,8 @@ def test_git_diff_excluding_reads_ignore_file_and_excludes_patterns(tmp_path):
|
|
|
193
193
|
|
|
194
194
|
def fake_run(cmd, capture_output, text, check):
|
|
195
195
|
# Ensure ignore patterns were added
|
|
196
|
-
assert "
|
|
197
|
-
assert "
|
|
196
|
+
assert ":(top,exclude)*.pyc" in cmd
|
|
197
|
+
assert ":(top,exclude)build/" in cmd
|
|
198
198
|
return mock_proc
|
|
199
199
|
|
|
200
200
|
output = git_diff_excluding(repo_root, run_cmd=fake_run)
|
|
@@ -252,7 +252,7 @@ def test_git_diff_excluding_no_caiignore(tmp_path):
|
|
|
252
252
|
|
|
253
253
|
def fake_run(cmd, **kwargs):
|
|
254
254
|
# No :! patterns should be in the command
|
|
255
|
-
assert not any(arg.startswith("
|
|
255
|
+
assert not any(arg.startswith(":(top,exclude)") for arg in cmd)
|
|
256
256
|
mock_proc = MagicMock()
|
|
257
257
|
mock_proc.returncode = 0
|
|
258
258
|
mock_proc.stdout = "diff output"
|
|
@@ -270,7 +270,7 @@ def test_git_diff_excluding_empty_caiignore(tmp_path):
|
|
|
270
270
|
(repo_root / ".caiignore").write_text("# only comments\n\n")
|
|
271
271
|
|
|
272
272
|
def fake_run(cmd, **kwargs):
|
|
273
|
-
assert not any(arg.startswith("
|
|
273
|
+
assert not any(arg.startswith(":(top,exclude)") for arg in cmd)
|
|
274
274
|
mock_proc = MagicMock()
|
|
275
275
|
mock_proc.returncode = 0
|
|
276
276
|
mock_proc.stdout = "diff output"
|
|
@@ -305,13 +305,13 @@ def test_git_diff_excluding_with_files_filter(tmp_path):
|
|
|
305
305
|
assert "a.py" in captured_cmd
|
|
306
306
|
assert "b.py" in captured_cmd
|
|
307
307
|
# Excludes still apply
|
|
308
|
-
assert "
|
|
309
|
-
# The
|
|
310
|
-
assert "
|
|
308
|
+
assert ":(top,exclude)*.pyc" in captured_cmd
|
|
309
|
+
# The whole-repo `:/` pathspec must NOT be present when files are passed
|
|
310
|
+
assert ":/" not in captured_cmd
|
|
311
311
|
|
|
312
312
|
|
|
313
|
-
def
|
|
314
|
-
"""When `files` is None,
|
|
313
|
+
def test_git_diff_excluding_without_files_uses_repo_root(tmp_path):
|
|
314
|
+
"""When `files` is None, `:/` (whole repo) is used as pathspec."""
|
|
315
315
|
repo_root = tmp_path
|
|
316
316
|
|
|
317
317
|
captured_cmd = []
|
|
@@ -325,8 +325,8 @@ def test_git_diff_excluding_without_files_uses_dot(tmp_path):
|
|
|
325
325
|
|
|
326
326
|
git_diff_excluding(repo_root, run_cmd=fake_run)
|
|
327
327
|
|
|
328
|
-
#
|
|
329
|
-
assert "
|
|
328
|
+
# Whole repo, not just the cwd subtree
|
|
329
|
+
assert ":/" in captured_cmd
|
|
330
330
|
|
|
331
331
|
|
|
332
332
|
# ------------------------------------------------------------------------------
|
|
@@ -75,7 +75,7 @@ def test_check_and_update_user_declines(capsys) -> None:
|
|
|
75
75
|
with (
|
|
76
76
|
patch("git_cai_cli.core.options.version", return_value="1.0.0"),
|
|
77
77
|
patch("git_cai_cli.core.options.requests.get", return_value=response),
|
|
78
|
-
patch("
|
|
78
|
+
patch("typer.confirm", return_value=False),
|
|
79
79
|
):
|
|
80
80
|
manager.check_and_update()
|
|
81
81
|
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Unit tests for git_cai_cli.core.squash.squash_branch function.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import builtins
|
|
6
5
|
import subprocess
|
|
7
6
|
import typing
|
|
8
7
|
from pathlib import Path
|
|
@@ -311,7 +310,7 @@ def test_force_push_prompt_and_execution(
|
|
|
311
310
|
patch("git_cai_cli.core.squash.sha256_of_file", side_effect=["a", "b"]),
|
|
312
311
|
patch("subprocess.run", run_mock),
|
|
313
312
|
patch("git_cai_cli.core.squash._has_upstream", return_value=True),
|
|
314
|
-
patch
|
|
313
|
+
patch("typer.confirm", return_value=True),
|
|
315
314
|
):
|
|
316
315
|
squash_branch()
|
|
317
316
|
|
|
@@ -352,7 +351,7 @@ def test_force_push_failure_is_handled(
|
|
|
352
351
|
patch("git_cai_cli.core.squash.sha256_of_file", side_effect=["a", "b"]),
|
|
353
352
|
patch("subprocess.run", run_mock),
|
|
354
353
|
patch("git_cai_cli.core.squash._has_upstream", return_value=True),
|
|
355
|
-
patch
|
|
354
|
+
patch("typer.confirm", return_value=True),
|
|
356
355
|
):
|
|
357
356
|
squash_branch()
|
|
358
357
|
|
|
@@ -399,7 +398,7 @@ def test_resolve_squash_target_number_exceeds_branch_warns() -> None:
|
|
|
399
398
|
patch("git_cai_cli.core.squash._count_total_commits", return_value=20),
|
|
400
399
|
patch("git_cai_cli.core.squash._get_branch_base", return_value="BASE"),
|
|
401
400
|
patch("git_cai_cli.core.squash._count_commits_on_branch", return_value=3),
|
|
402
|
-
patch
|
|
401
|
+
patch("typer.confirm", return_value=False),
|
|
403
402
|
pytest.raises(SystemExit),
|
|
404
403
|
):
|
|
405
404
|
_resolve_squash_target("5")
|
|
@@ -411,7 +410,7 @@ def test_resolve_squash_target_number_exceeds_branch_continues() -> None:
|
|
|
411
410
|
patch("git_cai_cli.core.squash._count_total_commits", return_value=20),
|
|
412
411
|
patch("git_cai_cli.core.squash._get_branch_base", return_value="BASE"),
|
|
413
412
|
patch("git_cai_cli.core.squash._count_commits_on_branch", return_value=3),
|
|
414
|
-
patch
|
|
413
|
+
patch("typer.confirm", return_value=True),
|
|
415
414
|
patch("subprocess.check_output", return_value="def456\n"),
|
|
416
415
|
):
|
|
417
416
|
result = _resolve_squash_target("5")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|