gitwise-cli 0.32.0__tar.gz → 0.33.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.
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/CHANGELOG.md +12 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/PKG-INFO +16 -3
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/README.md +14 -1
- gitwise_cli-0.33.1/bin/gw +5 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/__init__.py +1 -1
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/_cli_completions.py +76 -1
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/_cli_dispatch.py +3 -1
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/_cli_parser.py +25 -5
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/_i18n_data.json +24 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/conflicts.py +79 -2
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/diff.py +37 -3
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/log.py +27 -5
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/output.py +8 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/worktree.py +26 -1
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/pyproject.toml +3 -2
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/claude/rules/gitwise.md +1 -1
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/claude/settings.json.template +1 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/codex/agents/gitwise.toml.template +1 -1
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/completions.json +2 -1
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/conflicts.json +5 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/diff.json +5 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/log.json +5 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/worktree.json +2 -1
- gitwise_cli-0.33.1/tests/test_conflicts.py +86 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_diff.py +21 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_log.py +18 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_main.py +16 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_worktree.py +25 -0
- gitwise_cli-0.32.0/tests/test_conflicts.py +0 -45
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/.gitignore +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/LICENSE +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/bin/gitwise +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/__main__.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/_cli_introspection.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/_cli_setup_agents.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/_paths.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/_runtime_config.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/audit.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/branches.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/clean.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/commit.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/context.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/design.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/doctor.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/git.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/health.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/i18n.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/merge.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/optimize.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/pick.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/pr.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/py.typed +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/schema.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/__init__.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/adapters/__init__.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/adapters/aider.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/adapters/base.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/adapters/codex.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/adapters/continue_adapter.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/adapters/cursor.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/adapters/opencode.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/adapters/pi.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/exec.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/format.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/plan.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/plan_gitfiles.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/plan_skills.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/providers/__init__.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/providers/aider.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/providers/base.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/providers/claude.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/providers/codex.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/providers/continue_adapter.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/providers/cursor.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/providers/opencode.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/providers/pi.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/state.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/setup_agents/types.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/show.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/snapshot.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/stash.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/status.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/suggest.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/summarize.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/sync.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/tag.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/undo.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/update.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/utils/__init__.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/utils/git_output.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/utils/in_progress.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/utils/json_envelope.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/utils/parsing.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/utils/secret_scan.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/gitwise/utils/types.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/install.sh +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/agents/skills/git-audit/SKILL.md +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/agents/skills/git-clean/SKILL.md +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/agents/skills/git-optimize/SKILL.md +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/aider/CONVENTIONS.md.template +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/aider/aider.conf.yml.template +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/claude/CLAUDE.md.template +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/claude/skills/git-audit/SKILL.md +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/claude/skills/git-clean/SKILL.md +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/claude/skills/git-optimize/SKILL.md +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/continue/rules/gitwise.md.template +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/cursor/rules/gitwise.mdc.template +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/git-config-modern.txt +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/hooks/commit-msg +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/hooks/pre-commit +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/opencode/agents/gitwise.md.template +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/pi/skills/gitwise.md.template +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/audit.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/branches.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/clean.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/commands.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/commit.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/context.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/doctor.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/health.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/merge.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/optimize.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/pick.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/pr.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/schema.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/setup-agents.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/setup.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/show.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/snapshot.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/stash.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/status.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/suggest.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/summarize.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/sync.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/tag.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/undo.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/input/update.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/output/diff.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/output/log.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/output/setup-agents.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/share/schemas/v1/output/status.json +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/__init__.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/conftest.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_adapters.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_audit.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_branches.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_clean.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_commit.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_context.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_design.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_doctor.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_edge_cases.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_envelope_contract.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_git.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_git_output_utils.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_health.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_i18n.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_in_progress.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_json_envelope_utils.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_md_links_script.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_merge.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_optimize.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_output.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_parsing_utils.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_pick.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_pr.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_roadmap_baseline_script.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_sa_plan.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_sa_unit.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_schema_catalog.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_secret_scan.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_setup.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_setup_agents.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_show.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_snapshot.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_stash.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_status.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_suggest.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_summarize.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_sync.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_sync_changelog_es_script.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_tag.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_types.py +0 -0
- {gitwise_cli-0.32.0 → gitwise_cli-0.33.1}/tests/test_undo.py +0 -0
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
4
4
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
5
|
|
|
6
|
+
## v0.33.1 (2026-06-23)
|
|
7
|
+
|
|
8
|
+
### Fix
|
|
9
|
+
|
|
10
|
+
- make gw a real binary for non-interactive shells (#80)
|
|
11
|
+
|
|
12
|
+
## v0.33.0 (2026-06-20)
|
|
13
|
+
|
|
14
|
+
### Feat
|
|
15
|
+
|
|
16
|
+
- Sprint 4 DX polish -- worktree list, conflicts --union, powershell, ndjson (#71)
|
|
17
|
+
|
|
6
18
|
## v0.32.0 (2026-06-20)
|
|
7
19
|
|
|
8
20
|
### BREAKING CHANGE
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitwise-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.33.1
|
|
4
4
|
Summary: Python CLI for optimizing git workflows and Claude Code integration
|
|
5
5
|
Project-URL: Homepage, https://github.com/drzioner/gitwise
|
|
6
6
|
Project-URL: Repository, https://github.com/drzioner/gitwise
|
|
@@ -27,7 +27,7 @@ Classifier: Topic :: Software Development :: Version Control :: Git
|
|
|
27
27
|
Classifier: Typing :: Typed
|
|
28
28
|
Requires-Python: >=3.10
|
|
29
29
|
Requires-Dist: rich-argparse<2,>=1.8.0
|
|
30
|
-
Requires-Dist: rich<
|
|
30
|
+
Requires-Dist: rich<16,>=13.0
|
|
31
31
|
Requires-Dist: shtab<2,>=1.8.0
|
|
32
32
|
Description-Content-Type: text/markdown
|
|
33
33
|
|
|
@@ -135,7 +135,7 @@ gitwise summarize
|
|
|
135
135
|
| `gitwise status` | Enhanced status with staged/unstaged and ahead/behind |
|
|
136
136
|
| `gitwise commands --json` | List subcommands with aliases and metadata |
|
|
137
137
|
| `gitwise schema <command> --json` | Return versioned JSON Schema for command inputs |
|
|
138
|
-
| `gitwise completions <shell>` | Generate shell completion scripts (bash/zsh/fish) |
|
|
138
|
+
| `gitwise completions <shell>` | Generate shell completion scripts (bash/zsh/fish/powershell) |
|
|
139
139
|
| `gitwise pr` | List/check/view PRs via GitHub CLI |
|
|
140
140
|
|
|
141
141
|
For all commands, examples, aliases, and JSON usage, see:
|
|
@@ -188,6 +188,19 @@ gitwise completions zsh > ~/.zsh/completions/_gitwise
|
|
|
188
188
|
gitwise completions fish > ~/.config/fish/completions/gitwise.fish
|
|
189
189
|
```
|
|
190
190
|
|
|
191
|
+
**PowerShell** (Windows / PowerShell Core): generate and dot-source the
|
|
192
|
+
`Register-ArgumentCompleter` script. Add it to your `$PROFILE` for persistence:
|
|
193
|
+
|
|
194
|
+
```powershell
|
|
195
|
+
gitwise completions powershell > gitwise.ps1
|
|
196
|
+
. .\gitwise.ps1
|
|
197
|
+
# or, to load on every session:
|
|
198
|
+
Add-Content $PROFILE ('. ' + ((Resolve-Path 'gitwise.ps1').Path))
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Completion covers subcommands as the first token and per-command flags
|
|
202
|
+
(`--json`, `--dry-run`, `--max-count`, etc.) thereafter.
|
|
203
|
+
|
|
191
204
|
## Demo
|
|
192
205
|
|
|
193
206
|
[](https://asciinema.org/a/6tm4TnYMygEQT7ef)
|
|
@@ -102,7 +102,7 @@ gitwise summarize
|
|
|
102
102
|
| `gitwise status` | Enhanced status with staged/unstaged and ahead/behind |
|
|
103
103
|
| `gitwise commands --json` | List subcommands with aliases and metadata |
|
|
104
104
|
| `gitwise schema <command> --json` | Return versioned JSON Schema for command inputs |
|
|
105
|
-
| `gitwise completions <shell>` | Generate shell completion scripts (bash/zsh/fish) |
|
|
105
|
+
| `gitwise completions <shell>` | Generate shell completion scripts (bash/zsh/fish/powershell) |
|
|
106
106
|
| `gitwise pr` | List/check/view PRs via GitHub CLI |
|
|
107
107
|
|
|
108
108
|
For all commands, examples, aliases, and JSON usage, see:
|
|
@@ -155,6 +155,19 @@ gitwise completions zsh > ~/.zsh/completions/_gitwise
|
|
|
155
155
|
gitwise completions fish > ~/.config/fish/completions/gitwise.fish
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
+
**PowerShell** (Windows / PowerShell Core): generate and dot-source the
|
|
159
|
+
`Register-ArgumentCompleter` script. Add it to your `$PROFILE` for persistence:
|
|
160
|
+
|
|
161
|
+
```powershell
|
|
162
|
+
gitwise completions powershell > gitwise.ps1
|
|
163
|
+
. .\gitwise.ps1
|
|
164
|
+
# or, to load on every session:
|
|
165
|
+
Add-Content $PROFILE ('. ' + ((Resolve-Path 'gitwise.ps1').Path))
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Completion covers subcommands as the first token and per-command flags
|
|
169
|
+
(`--json`, `--dry-run`, `--max-count`, etc.) thereafter.
|
|
170
|
+
|
|
158
171
|
## Demo
|
|
159
172
|
|
|
160
173
|
[](https://asciinema.org/a/6tm4TnYMygEQT7ef)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Shell completions generation: bash, zsh, fish."""
|
|
1
|
+
"""Shell completions generation: bash, zsh, fish, powershell."""
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
|
|
@@ -20,6 +20,9 @@ def build_completions_script(*, shell: str, prog: str) -> str:
|
|
|
20
20
|
if shell == "fish":
|
|
21
21
|
return _build_fish_completions_script(parser=parser, prog=prog)
|
|
22
22
|
|
|
23
|
+
if shell == "powershell":
|
|
24
|
+
return _build_powershell_completions_script(parser=parser, prog=prog)
|
|
25
|
+
|
|
23
26
|
raise ValueError(f"unsupported shell: {shell}")
|
|
24
27
|
|
|
25
28
|
|
|
@@ -90,3 +93,75 @@ def _build_fish_completions_script(*, parser: argparse.ArgumentParser, prog: str
|
|
|
90
93
|
lines.append(line)
|
|
91
94
|
|
|
92
95
|
return "\n".join(lines) + "\n"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _collect_flags(action: argparse.Action) -> list[str]:
|
|
99
|
+
"""Return the long/short option strings for an argparse action."""
|
|
100
|
+
return [flag for flag in action.option_strings if flag.startswith("-")]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _build_powershell_completions_script(*, parser: argparse.ArgumentParser, prog: str) -> str:
|
|
104
|
+
"""Generate a PowerShell ``Register-ArgumentCompleter`` script from the parser tree.
|
|
105
|
+
|
|
106
|
+
Completes subcommands as the first token and per-command flags thereafter.
|
|
107
|
+
Hand-built because ``shtab`` has no PowerShell backend.
|
|
108
|
+
"""
|
|
109
|
+
sub_action = _subparsers_action(parser)
|
|
110
|
+
command_names = sorted(sub_action.choices.keys()) if sub_action else []
|
|
111
|
+
|
|
112
|
+
per_command_flags: dict[str, list[str]] = {}
|
|
113
|
+
global_flags: list[str] = []
|
|
114
|
+
for action in parser._actions:
|
|
115
|
+
if action.dest == "help" or isinstance(action, argparse._SubParsersAction):
|
|
116
|
+
continue
|
|
117
|
+
global_flags.extend(_collect_flags(action))
|
|
118
|
+
|
|
119
|
+
if sub_action is not None:
|
|
120
|
+
for command, command_parser in sorted(sub_action.choices.items()):
|
|
121
|
+
flags: list[str] = []
|
|
122
|
+
for action in command_parser._actions:
|
|
123
|
+
if action.dest == "help" or isinstance(action, argparse._SubParsersAction):
|
|
124
|
+
continue
|
|
125
|
+
flags.extend(_collect_flags(action))
|
|
126
|
+
per_command_flags[command] = sorted(set(flags))
|
|
127
|
+
|
|
128
|
+
commands_ps = ",".join(f"'{c}'" for c in command_names)
|
|
129
|
+
global_flags_ps = ",".join(f"'{f}'" for f in sorted(set(global_flags)))
|
|
130
|
+
per_command_block = "\n".join(
|
|
131
|
+
" '{}' = @({})".format(cmd, ", ".join(f'"{f}"' for f in flags))
|
|
132
|
+
for cmd, flags in per_command_flags.items()
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return f"""# PowerShell completion for {prog}
|
|
136
|
+
# Source it from your PowerShell profile:
|
|
137
|
+
# Add-Content $PROFILE ('. ' + ((Resolve-Path 'path/to/{prog}.ps1').Path))
|
|
138
|
+
# or dot-source: . ./{prog}.ps1
|
|
139
|
+
Register-ArgumentCompleter -Native -CommandName '{prog}' -ScriptBlock {{
|
|
140
|
+
param($wordToComplete, $commandAst, $cursorPosition)
|
|
141
|
+
|
|
142
|
+
$commands = @({commands_ps})
|
|
143
|
+
$globalFlags = @({global_flags_ps})
|
|
144
|
+
$perCommandFlags = @{{
|
|
145
|
+
{per_command_block}
|
|
146
|
+
}}
|
|
147
|
+
|
|
148
|
+
# Only tokens fully before the cursor are "completed"; the word at the
|
|
149
|
+
# cursor is being typed and must not be mistaken for the subcommand.
|
|
150
|
+
$priorTokens = @($commandAst.CommandElements | Select-Object -Skip 1 | Where-Object {{ $_.Extent.EndOffset -lt $cursorPosition }})
|
|
151
|
+
$subcommand = ($priorTokens | Where-Object {{ $_.Value -notlike '-*' }} | Select-Object -First 1).Value
|
|
152
|
+
|
|
153
|
+
if (-not $subcommand) {{
|
|
154
|
+
$candidates = $commands + $globalFlags
|
|
155
|
+
}}
|
|
156
|
+
elseif ($perCommandFlags.ContainsKey($subcommand)) {{
|
|
157
|
+
$candidates = $perCommandFlags[$subcommand] + $globalFlags
|
|
158
|
+
}}
|
|
159
|
+
else {{
|
|
160
|
+
$candidates = @()
|
|
161
|
+
}}
|
|
162
|
+
|
|
163
|
+
$candidates | Where-Object {{ $_ -like "$wordToComplete*" }} |
|
|
164
|
+
Sort-Object -Unique |
|
|
165
|
+
ForEach-Object {{ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }}
|
|
166
|
+
}}
|
|
167
|
+
"""
|
|
@@ -206,6 +206,7 @@ def _run_diff(args: argparse.Namespace) -> int:
|
|
|
206
206
|
summary=args.summary,
|
|
207
207
|
scan_secrets=args.scan_secrets,
|
|
208
208
|
as_json=args.json,
|
|
209
|
+
json_lines=getattr(args, "json_lines", False),
|
|
209
210
|
)
|
|
210
211
|
|
|
211
212
|
|
|
@@ -215,6 +216,7 @@ def _run_log(args: argparse.Namespace) -> int:
|
|
|
215
216
|
|
|
216
217
|
return run_log(
|
|
217
218
|
as_json=args.json,
|
|
219
|
+
json_lines=getattr(args, "json_lines", False),
|
|
218
220
|
oneline=args.oneline,
|
|
219
221
|
graph=args.graph,
|
|
220
222
|
author=args.author,
|
|
@@ -353,7 +355,7 @@ def _run_conflicts(args: argparse.Namespace) -> int:
|
|
|
353
355
|
"""Dispatch to ``conflicts`` subcommand."""
|
|
354
356
|
from gitwise.conflicts import run_conflicts
|
|
355
357
|
|
|
356
|
-
return run_conflicts(ours=args.ours, theirs=args.theirs, as_json=args.json)
|
|
358
|
+
return run_conflicts(ours=args.ours, theirs=args.theirs, union=args.union, as_json=args.json)
|
|
357
359
|
|
|
358
360
|
|
|
359
361
|
def _run_suggest(args: argparse.Namespace) -> int:
|
|
@@ -163,7 +163,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
163
163
|
p.add_argument("--yes", "-y", action="store_true")
|
|
164
164
|
|
|
165
165
|
p = sub.add_parser("worktree", help="worktree helpers for Claude agents", parents=[parent])
|
|
166
|
-
p.add_argument("action", choices=["new", "clean"], nargs="?", metavar="new|clean")
|
|
166
|
+
p.add_argument("action", choices=["new", "clean", "list"], nargs="?", metavar="new|clean|list")
|
|
167
167
|
p.add_argument("branch", nargs="?")
|
|
168
168
|
p.add_argument("--dry-run", action="store_true")
|
|
169
169
|
|
|
@@ -189,6 +189,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
189
189
|
action="store_true",
|
|
190
190
|
help="scan the diff for leaked credentials (advisory, opt-in)",
|
|
191
191
|
)
|
|
192
|
+
p.add_argument(
|
|
193
|
+
"--json-lines",
|
|
194
|
+
action="store_true",
|
|
195
|
+
dest="json_lines",
|
|
196
|
+
help="stream one JSON envelope per changed file (NDJSON)",
|
|
197
|
+
)
|
|
192
198
|
p.add_argument(
|
|
193
199
|
"refspec",
|
|
194
200
|
nargs="?",
|
|
@@ -211,6 +217,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
211
217
|
p.add_argument("--until", type=str, default=None, help="show commits until date")
|
|
212
218
|
p.add_argument("--file", type=str, default=None, help="show commits for file")
|
|
213
219
|
p.add_argument("--max-count", type=int, default=20, dest="max_count", help="max commits")
|
|
220
|
+
p.add_argument(
|
|
221
|
+
"--json-lines",
|
|
222
|
+
action="store_true",
|
|
223
|
+
dest="json_lines",
|
|
224
|
+
help="stream one JSON envelope per commit (NDJSON; implies JSON mode)",
|
|
225
|
+
)
|
|
214
226
|
|
|
215
227
|
p = sub.add_parser("show", help="commit inspector", parents=[parent])
|
|
216
228
|
p.add_argument("ref", nargs="?", default="HEAD", help="commit ref (default: HEAD)")
|
|
@@ -316,8 +328,16 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
316
328
|
p = sub.add_parser(
|
|
317
329
|
"conflicts", help="conflict detection and resolution helper", parents=[parent]
|
|
318
330
|
)
|
|
319
|
-
p.
|
|
320
|
-
|
|
331
|
+
strategy = p.add_mutually_exclusive_group()
|
|
332
|
+
strategy.add_argument("--ours", action="store_true", help="resolve all conflicts using ours")
|
|
333
|
+
strategy.add_argument(
|
|
334
|
+
"--theirs", action="store_true", help="resolve all conflicts using theirs"
|
|
335
|
+
)
|
|
336
|
+
strategy.add_argument(
|
|
337
|
+
"--union",
|
|
338
|
+
action="store_true",
|
|
339
|
+
help="resolve all conflicts keeping both sides (union)",
|
|
340
|
+
)
|
|
321
341
|
|
|
322
342
|
p = sub.add_parser(
|
|
323
343
|
"suggest",
|
|
@@ -369,12 +389,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
369
389
|
|
|
370
390
|
p = sub.add_parser(
|
|
371
391
|
"completions",
|
|
372
|
-
help="print shell completion script (bash/zsh/fish)",
|
|
392
|
+
help="print shell completion script (bash/zsh/fish/powershell)",
|
|
373
393
|
parents=[parent],
|
|
374
394
|
)
|
|
375
395
|
p.add_argument(
|
|
376
396
|
"shell",
|
|
377
|
-
choices=["bash", "zsh", "fish"],
|
|
397
|
+
choices=["bash", "zsh", "fish", "powershell"],
|
|
378
398
|
default="bash",
|
|
379
399
|
nargs="?",
|
|
380
400
|
help="target shell for completion script",
|
|
@@ -351,6 +351,14 @@
|
|
|
351
351
|
"es": "{count} conflicto(s) resuelto(s) con theirs",
|
|
352
352
|
"en": "{count} conflict(s) resolved with theirs"
|
|
353
353
|
},
|
|
354
|
+
"conflicts_resolved_union": {
|
|
355
|
+
"es": "{count} conflicto(s) resuelto(s) con union (ambos lados)",
|
|
356
|
+
"en": "{count} conflict(s) resolved with union (both sides)"
|
|
357
|
+
},
|
|
358
|
+
"conflicts_union_failed": {
|
|
359
|
+
"es": "no se pudo resolver union para {file}",
|
|
360
|
+
"en": "could not union-resolve {file}"
|
|
361
|
+
},
|
|
354
362
|
"continue_prompt": {
|
|
355
363
|
"es": "¿continuar? [s/N] ",
|
|
356
364
|
"en": "continue? [y/N] "
|
|
@@ -2023,6 +2031,22 @@
|
|
|
2023
2031
|
"es": " rama: {branch}",
|
|
2024
2032
|
"en": " branch: {branch}"
|
|
2025
2033
|
},
|
|
2034
|
+
"worktree_flag_locked": {
|
|
2035
|
+
"es": "bloqueado",
|
|
2036
|
+
"en": "locked"
|
|
2037
|
+
},
|
|
2038
|
+
"worktree_flag_prunable": {
|
|
2039
|
+
"es": "podable",
|
|
2040
|
+
"en": "prunable"
|
|
2041
|
+
},
|
|
2042
|
+
"worktree_list_empty": {
|
|
2043
|
+
"es": "no hay worktrees registrados",
|
|
2044
|
+
"en": "no registered worktrees"
|
|
2045
|
+
},
|
|
2046
|
+
"worktree_list_header": {
|
|
2047
|
+
"es": "worktrees ({count}):",
|
|
2048
|
+
"en": "worktrees ({count}):"
|
|
2049
|
+
},
|
|
2026
2050
|
"worktree_created": {
|
|
2027
2051
|
"es": "worktree creado: {path}",
|
|
2028
2052
|
"en": "worktree created: {path}"
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""gitwise conflicts — conflict detection and resolution helper."""
|
|
2
2
|
|
|
3
|
+
import subprocess
|
|
4
|
+
import tempfile
|
|
3
5
|
from pathlib import Path
|
|
4
6
|
|
|
5
7
|
from gitwise.git import require_root
|
|
@@ -19,6 +21,17 @@ from gitwise.utils.json_envelope import error_envelope, ok_envelope
|
|
|
19
21
|
from gitwise.utils.parsing import stripped_non_empty_lines, to_int
|
|
20
22
|
|
|
21
23
|
|
|
24
|
+
def _git_bytes(args: list[str], *, cwd: Path) -> tuple[int, bytes, bytes]:
|
|
25
|
+
"""Run git capturing raw bytes (no text decoding) for binary-safe I/O."""
|
|
26
|
+
r = subprocess.run(
|
|
27
|
+
["git", *args],
|
|
28
|
+
cwd=cwd,
|
|
29
|
+
capture_output=True,
|
|
30
|
+
timeout=120,
|
|
31
|
+
)
|
|
32
|
+
return r.returncode, r.stdout, r.stderr
|
|
33
|
+
|
|
34
|
+
|
|
22
35
|
def _find_conflict_files(root: Path) -> list[str]:
|
|
23
36
|
"""Return file paths with unmerged (U) status."""
|
|
24
37
|
r = git_run(["diff", "--name-only", "--diff-filter=U"], cwd=root, check=False)
|
|
@@ -40,7 +53,9 @@ def _conflict_markers(root: Path, filepath: str) -> int:
|
|
|
40
53
|
|
|
41
54
|
|
|
42
55
|
def _resolve_all_conflicts(*, root: Path, conflicts: list[str], strategy: str) -> int:
|
|
43
|
-
"""Resolve conflicts in all files using ``--ours
|
|
56
|
+
"""Resolve conflicts in all files using ``--ours``/``--theirs``, then stage."""
|
|
57
|
+
if strategy == "union":
|
|
58
|
+
return _resolve_union(root=root, conflicts=conflicts)
|
|
44
59
|
checkout_flag = "--ours" if strategy == "ours" else "--theirs"
|
|
45
60
|
result = git_run(["checkout", checkout_flag, "--"] + conflicts, cwd=root, check=False)
|
|
46
61
|
if result.returncode != 0:
|
|
@@ -50,6 +65,58 @@ def _resolve_all_conflicts(*, root: Path, conflicts: list[str], strategy: str) -
|
|
|
50
65
|
return 0
|
|
51
66
|
|
|
52
67
|
|
|
68
|
+
def _stage_blob(root: Path, stage_ref: str) -> bytes:
|
|
69
|
+
"""Return the raw bytes of a staged blob (e.g. ``:2:path`` = ours).
|
|
70
|
+
|
|
71
|
+
Binary-safe (no UTF-8 round-trip). Returns b"" only for a missing base
|
|
72
|
+
(``:1:``); callers treat base absence as an empty common ancestor.
|
|
73
|
+
"""
|
|
74
|
+
rc, out, _ = _git_bytes(["show", stage_ref], cwd=root)
|
|
75
|
+
return out if rc == 0 else b""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _resolve_union(*, root: Path, conflicts: list[str]) -> int:
|
|
79
|
+
"""Resolve conflicts with ``git merge-file --union`` (keeps both sides, no markers).
|
|
80
|
+
|
|
81
|
+
For each file, materialize the base (:1:), ours (:2:), theirs (:3:) stage
|
|
82
|
+
blobs as raw bytes, union-merge them, write the result back, and stage it.
|
|
83
|
+
Uses bytes throughout so binary/non-UTF-8 conflict files keep full fidelity.
|
|
84
|
+
"""
|
|
85
|
+
for filepath in conflicts:
|
|
86
|
+
ours = _stage_blob(root, f":2:{filepath}")
|
|
87
|
+
base = _stage_blob(root, f":1:{filepath}")
|
|
88
|
+
theirs = _stage_blob(root, f":3:{filepath}")
|
|
89
|
+
if not ours and not theirs:
|
|
90
|
+
error(t("conflicts_union_failed", file=filepath))
|
|
91
|
+
return 1
|
|
92
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
93
|
+
ours_p = Path(tmp) / "ours"
|
|
94
|
+
base_p = Path(tmp) / "base"
|
|
95
|
+
theirs_p = Path(tmp) / "theirs"
|
|
96
|
+
ours_p.write_bytes(ours)
|
|
97
|
+
base_p.write_bytes(base)
|
|
98
|
+
theirs_p.write_bytes(theirs)
|
|
99
|
+
rc, out, err = _git_bytes(
|
|
100
|
+
["merge-file", "--union", "-p", str(ours_p), str(base_p), str(theirs_p)],
|
|
101
|
+
cwd=root,
|
|
102
|
+
)
|
|
103
|
+
if rc not in (0, 1):
|
|
104
|
+
error(
|
|
105
|
+
err.decode("utf-8", errors="replace").strip()
|
|
106
|
+
or t("conflicts_union_failed", file=filepath)
|
|
107
|
+
)
|
|
108
|
+
return 1
|
|
109
|
+
(root / filepath).write_bytes(out)
|
|
110
|
+
add_rc, _, add_err = _git_bytes(["add", "--", filepath], cwd=root)
|
|
111
|
+
if add_rc != 0:
|
|
112
|
+
error(
|
|
113
|
+
add_err.decode("utf-8", errors="replace").strip()
|
|
114
|
+
or t("conflicts_union_failed", file=filepath)
|
|
115
|
+
)
|
|
116
|
+
return 1
|
|
117
|
+
return 0
|
|
118
|
+
|
|
119
|
+
|
|
53
120
|
def _conflict_details(root: Path, conflicts: list[str]) -> list[dict[str, str | int]]:
|
|
54
121
|
"""Return ``[{file, markers}]`` with conflict marker counts per file."""
|
|
55
122
|
details: list[dict[str, str | int]] = []
|
|
@@ -76,7 +143,11 @@ def _resolve_by_strategy(*, root: Path, conflicts: list[str], strategy: str, as_
|
|
|
76
143
|
if as_json:
|
|
77
144
|
print_json(ok_envelope("conflicts", resolved=len(conflicts), strategy=strategy))
|
|
78
145
|
return 0
|
|
79
|
-
key =
|
|
146
|
+
key = {
|
|
147
|
+
"ours": "conflicts_resolved_ours",
|
|
148
|
+
"theirs": "conflicts_resolved_theirs",
|
|
149
|
+
"union": "conflicts_resolved_union",
|
|
150
|
+
}[strategy]
|
|
80
151
|
ok(t(key, count=str(len(conflicts))))
|
|
81
152
|
return 0
|
|
82
153
|
|
|
@@ -100,6 +171,7 @@ def run_conflicts(
|
|
|
100
171
|
*,
|
|
101
172
|
ours: bool = False,
|
|
102
173
|
theirs: bool = False,
|
|
174
|
+
union: bool = False,
|
|
103
175
|
as_json: bool = False,
|
|
104
176
|
) -> int:
|
|
105
177
|
"""Entry point for the ``gitwise conflicts`` command."""
|
|
@@ -115,6 +187,11 @@ def run_conflicts(
|
|
|
115
187
|
if not conflicts:
|
|
116
188
|
return _report_no_conflicts(as_json=as_json)
|
|
117
189
|
|
|
190
|
+
if union:
|
|
191
|
+
return _resolve_by_strategy(
|
|
192
|
+
root=root, conflicts=conflicts, strategy="union", as_json=as_json
|
|
193
|
+
)
|
|
194
|
+
|
|
118
195
|
if ours:
|
|
119
196
|
return _resolve_by_strategy(
|
|
120
197
|
root=root, conflicts=conflicts, strategy="ours", as_json=as_json
|
|
@@ -122,6 +122,19 @@ def _name_status_details(
|
|
|
122
122
|
return _parse_name_status_lines(r.stdout.splitlines())
|
|
123
123
|
|
|
124
124
|
|
|
125
|
+
def _normalize_rename_path(path: str) -> str:
|
|
126
|
+
"""Collapse git's ``{old => new}`` rename syntax to the destination path."""
|
|
127
|
+
if " => " not in path or "{" not in path or "}" not in path:
|
|
128
|
+
return path
|
|
129
|
+
# forms: "{old => new}", "dir/{old => new}", "{a => b}/c"
|
|
130
|
+
prefix, _, rest = path.partition("{")
|
|
131
|
+
inner, _, suffix = rest.partition("}")
|
|
132
|
+
if " => " in inner:
|
|
133
|
+
_, _, new = inner.partition(" => ")
|
|
134
|
+
return f"{prefix}{new}{suffix}"
|
|
135
|
+
return path
|
|
136
|
+
|
|
137
|
+
|
|
125
138
|
def _numstat_details(
|
|
126
139
|
cwd: Path, *, staged: bool, refspec: str | None, paths: list[str] | None
|
|
127
140
|
) -> dict[str, DiffFileEntry]:
|
|
@@ -147,7 +160,7 @@ def _numstat_details(
|
|
|
147
160
|
continue
|
|
148
161
|
add_raw = parts[0].strip()
|
|
149
162
|
del_raw = parts[1].strip()
|
|
150
|
-
path = parts[2].strip()
|
|
163
|
+
path = _normalize_rename_path(parts[2].strip())
|
|
151
164
|
if not path:
|
|
152
165
|
continue
|
|
153
166
|
|
|
@@ -485,6 +498,7 @@ def run_diff(
|
|
|
485
498
|
summary: bool = False,
|
|
486
499
|
scan_secrets: bool = False,
|
|
487
500
|
as_json: bool = False,
|
|
501
|
+
json_lines: bool = False,
|
|
488
502
|
) -> int:
|
|
489
503
|
"""Render a diff of changed files, a refspec, or a range.
|
|
490
504
|
|
|
@@ -502,12 +516,32 @@ def run_diff(
|
|
|
502
516
|
cwd = root
|
|
503
517
|
|
|
504
518
|
if not refspec and not staged and not _has_commits(cwd):
|
|
505
|
-
if as_json:
|
|
506
|
-
|
|
519
|
+
if as_json or json_lines:
|
|
520
|
+
note_env = ok_envelope("diff", files=[], count=0, note=t("no_commits_yet"))
|
|
521
|
+
if json_lines:
|
|
522
|
+
from gitwise.output import print_json_line
|
|
523
|
+
|
|
524
|
+
print_json_line(note_env)
|
|
525
|
+
else:
|
|
526
|
+
print_json(note_env)
|
|
507
527
|
return 0
|
|
508
528
|
info(t("no_commits_yet"))
|
|
509
529
|
return 0
|
|
510
530
|
|
|
531
|
+
if json_lines:
|
|
532
|
+
from gitwise.output import print_json_line
|
|
533
|
+
|
|
534
|
+
numstat_details = _numstat_details(cwd, staged=staged, refspec=refspec, paths=paths)
|
|
535
|
+
status_details = _name_status_details(cwd, staged=staged, refspec=refspec, paths=paths)
|
|
536
|
+
for path, entry in numstat_details.items():
|
|
537
|
+
st = status_details.get(path, {})
|
|
538
|
+
code = str(st.get("code") or st.get("status") or "")
|
|
539
|
+
if code:
|
|
540
|
+
entry["code"] = code
|
|
541
|
+
entry["status_label"] = status_label(code)
|
|
542
|
+
print_json_line(ok_envelope("diff", file=entry))
|
|
543
|
+
return 0
|
|
544
|
+
|
|
511
545
|
if scan_secrets:
|
|
512
546
|
return _render_secret_scan_output(root=cwd, refspec=refspec, paths=paths, as_json=as_json)
|
|
513
547
|
|
|
@@ -195,8 +195,22 @@ def _run_log_json(
|
|
|
195
195
|
until: str | None,
|
|
196
196
|
file: str | None,
|
|
197
197
|
max_count: int,
|
|
198
|
+
json_lines: bool = False,
|
|
198
199
|
) -> int:
|
|
199
|
-
"""Execute git log and emit structured JSON with enriched per-commit stats.
|
|
200
|
+
"""Execute git log and emit structured JSON with enriched per-commit stats.
|
|
201
|
+
|
|
202
|
+
With ``json_lines``, stream one compact v3 envelope per commit (NDJSON) for
|
|
203
|
+
incremental processing instead of a single blob. All output paths (including
|
|
204
|
+
errors) emit single-line envelopes to preserve the NDJSON record contract.
|
|
205
|
+
"""
|
|
206
|
+
from gitwise.output import print_json_line
|
|
207
|
+
|
|
208
|
+
def _emit(env: dict) -> None:
|
|
209
|
+
if json_lines:
|
|
210
|
+
print_json_line(env)
|
|
211
|
+
else:
|
|
212
|
+
print_json(env)
|
|
213
|
+
|
|
200
214
|
try:
|
|
201
215
|
args = _build_log_json_args(
|
|
202
216
|
author=author,
|
|
@@ -207,14 +221,14 @@ def _run_log_json(
|
|
|
207
221
|
max_count=max_count + 1,
|
|
208
222
|
)
|
|
209
223
|
except ValueError as exc:
|
|
210
|
-
|
|
224
|
+
_emit(error_envelope("log", error=str(exc), code="invalid_argument"))
|
|
211
225
|
return 1
|
|
212
226
|
result = git_run(args, cwd=root, check=False)
|
|
213
227
|
if result.returncode != 0:
|
|
214
228
|
if "does not have any commits yet" in result.stderr:
|
|
215
|
-
|
|
229
|
+
_emit(ok_envelope("log", commits=[], count=0, total=0, truncated=False))
|
|
216
230
|
return 0
|
|
217
|
-
|
|
231
|
+
_emit(
|
|
218
232
|
error_envelope(
|
|
219
233
|
"log",
|
|
220
234
|
error=t("git_log_failed", error=result.stderr.strip()),
|
|
@@ -226,6 +240,12 @@ def _run_log_json(
|
|
|
226
240
|
total = len(commits)
|
|
227
241
|
truncated = total > max_count
|
|
228
242
|
commits = commits[:max_count]
|
|
243
|
+
if json_lines:
|
|
244
|
+
from gitwise.output import print_json_line
|
|
245
|
+
|
|
246
|
+
for commit in commits:
|
|
247
|
+
print_json_line(ok_envelope("log", commit=commit))
|
|
248
|
+
return 0
|
|
229
249
|
hints: list[str] = []
|
|
230
250
|
if truncated:
|
|
231
251
|
hints.append(f"gitwise log --max-count {max_count} (more commits exist)")
|
|
@@ -324,6 +344,7 @@ def _run_log_human_plain_or_graph(*, raw: str, graph: bool, oneline: bool) -> bo
|
|
|
324
344
|
def run_log(
|
|
325
345
|
*,
|
|
326
346
|
as_json: bool = False,
|
|
347
|
+
json_lines: bool = False,
|
|
327
348
|
oneline: bool = False,
|
|
328
349
|
graph: bool = False,
|
|
329
350
|
author: str | None = None,
|
|
@@ -340,7 +361,7 @@ def run_log(
|
|
|
340
361
|
if root is None:
|
|
341
362
|
return 1
|
|
342
363
|
|
|
343
|
-
if as_json:
|
|
364
|
+
if as_json or json_lines:
|
|
344
365
|
return _run_log_json(
|
|
345
366
|
root=root,
|
|
346
367
|
author=author,
|
|
@@ -349,6 +370,7 @@ def run_log(
|
|
|
349
370
|
until=until,
|
|
350
371
|
file=file,
|
|
351
372
|
max_count=max_count,
|
|
373
|
+
json_lines=json_lines,
|
|
352
374
|
)
|
|
353
375
|
|
|
354
376
|
return _run_log_human(
|
|
@@ -279,6 +279,14 @@ def print_json(data: Any) -> None:
|
|
|
279
279
|
print(json.dumps(data, ensure_ascii=False, separators=(",", ":")))
|
|
280
280
|
|
|
281
281
|
|
|
282
|
+
def print_json_line(data: Any) -> None:
|
|
283
|
+
"""Print data as a single compact JSON line (NDJSON), ignoring pretty mode.
|
|
284
|
+
|
|
285
|
+
Used by ``--json-lines`` streaming so each record is one parseable line.
|
|
286
|
+
"""
|
|
287
|
+
print(json.dumps(data, ensure_ascii=False, separators=(",", ":")))
|
|
288
|
+
|
|
289
|
+
|
|
282
290
|
def print_blank() -> None:
|
|
283
291
|
"""Print an empty line to stdout."""
|
|
284
292
|
if _should_use_rich():
|
|
@@ -38,7 +38,7 @@ def _list_worktrees(cwd: Path) -> list[dict]:
|
|
|
38
38
|
}
|
|
39
39
|
elif line.startswith("branch refs/heads/"):
|
|
40
40
|
current["branch"] = line.removeprefix("branch refs/heads/")
|
|
41
|
-
elif line
|
|
41
|
+
elif line.startswith("locked"):
|
|
42
42
|
current["locked"] = True
|
|
43
43
|
elif line.startswith("prunable"):
|
|
44
44
|
current["prunable"] = True
|
|
@@ -107,6 +107,28 @@ def _worktree_new_json(branch: str, root: Path) -> tuple[int, dict]:
|
|
|
107
107
|
return rc, data
|
|
108
108
|
|
|
109
109
|
|
|
110
|
+
def _worktree_list(cwd: Path, *, as_json: bool = False) -> int:
|
|
111
|
+
"""List registered worktrees (human table or v3 JSON envelope)."""
|
|
112
|
+
worktrees = _list_worktrees(cwd)
|
|
113
|
+
if as_json:
|
|
114
|
+
print_json(ok_envelope("worktree", worktrees=worktrees, count=len(worktrees)))
|
|
115
|
+
return 0
|
|
116
|
+
if not worktrees:
|
|
117
|
+
info(t("worktree_list_empty"))
|
|
118
|
+
return 0
|
|
119
|
+
print_header(t("worktree_list_header", count=str(len(worktrees))))
|
|
120
|
+
for wt in worktrees:
|
|
121
|
+
branch = wt["branch"] or t("unknown_branch")
|
|
122
|
+
flags = []
|
|
123
|
+
if wt["locked"]:
|
|
124
|
+
flags.append(t("worktree_flag_locked"))
|
|
125
|
+
if wt["prunable"]:
|
|
126
|
+
flags.append(t("worktree_flag_prunable"))
|
|
127
|
+
suffix = f" [{', '.join(flags)}]" if flags else ""
|
|
128
|
+
print_bracket(wt["path"], t("branch_label", branch=branch) + suffix)
|
|
129
|
+
return 0
|
|
130
|
+
|
|
131
|
+
|
|
110
132
|
def _worktree_clean(cwd: Path, *, dry_run: bool = False, as_json: bool = False) -> int:
|
|
111
133
|
"""Prune stale worktree admin files and report orphaned worktrees."""
|
|
112
134
|
# git worktree prune removes stale admin files
|
|
@@ -193,6 +215,9 @@ def run_worktree(
|
|
|
193
215
|
elif action == "clean":
|
|
194
216
|
return _worktree_clean(root, dry_run=dry_run, as_json=as_json)
|
|
195
217
|
|
|
218
|
+
elif action == "list":
|
|
219
|
+
return _worktree_list(root, as_json=as_json)
|
|
220
|
+
|
|
196
221
|
else:
|
|
197
222
|
error(t("worktree_usage_full"))
|
|
198
223
|
return 1
|