gitwise-cli 0.27.0__tar.gz → 0.28.0__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.
Files changed (173) hide show
  1. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/CHANGELOG.md +22 -0
  2. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/PKG-INFO +1 -1
  3. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/__init__.py +1 -1
  4. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/__main__.py +2 -1
  5. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/_i18n_data.json +80 -0
  6. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/branches.py +46 -27
  7. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/clean.py +3 -1
  8. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/conflicts.py +12 -2
  9. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/context.py +7 -6
  10. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/diff.py +15 -3
  11. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/doctor.py +10 -8
  12. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/git.py +1 -1
  13. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/health.py +3 -2
  14. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/log.py +4 -3
  15. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/output.py +10 -0
  16. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/pr.py +10 -8
  17. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/show.py +5 -3
  18. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/snapshot.py +3 -2
  19. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/stash.py +5 -2
  20. gitwise_cli-0.28.0/gitwise/status.py +123 -0
  21. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/suggest.py +4 -4
  22. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/summarize.py +16 -14
  23. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/tag.py +11 -9
  24. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/update.py +14 -2
  25. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/utils/git_output.py +24 -0
  26. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/worktree.py +8 -4
  27. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/pyproject.toml +2 -1
  28. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_audit.py +5 -1
  29. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_i18n.py +28 -0
  30. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_log.py +16 -2
  31. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_snapshot.py +3 -1
  32. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_tag.py +20 -0
  33. gitwise_cli-0.27.0/gitwise/status.py +0 -93
  34. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/.gitignore +0 -0
  35. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/LICENSE +0 -0
  36. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/README.md +0 -0
  37. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/bin/gitwise +0 -0
  38. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/_cli_completions.py +0 -0
  39. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/_cli_dispatch.py +0 -0
  40. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/_cli_introspection.py +0 -0
  41. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/_cli_parser.py +0 -0
  42. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/_cli_setup_agents.py +0 -0
  43. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/_paths.py +0 -0
  44. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/_runtime_config.py +0 -0
  45. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/audit.py +0 -0
  46. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/commit.py +0 -0
  47. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/design.py +0 -0
  48. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/i18n.py +0 -0
  49. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/merge.py +0 -0
  50. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/optimize.py +0 -0
  51. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/pick.py +0 -0
  52. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/py.typed +0 -0
  53. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/schema.py +0 -0
  54. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup.py +0 -0
  55. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/__init__.py +0 -0
  56. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/adapters/__init__.py +0 -0
  57. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/adapters/aider.py +0 -0
  58. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/adapters/base.py +0 -0
  59. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/adapters/codex.py +0 -0
  60. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/adapters/continue_adapter.py +0 -0
  61. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/adapters/cursor.py +0 -0
  62. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/adapters/opencode.py +0 -0
  63. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/adapters/pi.py +0 -0
  64. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/exec.py +0 -0
  65. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/format.py +0 -0
  66. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/plan.py +0 -0
  67. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/plan_gitfiles.py +0 -0
  68. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/plan_skills.py +0 -0
  69. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/providers/__init__.py +0 -0
  70. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/providers/aider.py +0 -0
  71. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/providers/base.py +0 -0
  72. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/providers/claude.py +0 -0
  73. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/providers/codex.py +0 -0
  74. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/providers/continue_adapter.py +0 -0
  75. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/providers/cursor.py +0 -0
  76. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/providers/opencode.py +0 -0
  77. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/providers/pi.py +0 -0
  78. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/state.py +0 -0
  79. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/setup_agents/types.py +0 -0
  80. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/sync.py +0 -0
  81. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/undo.py +0 -0
  82. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/utils/__init__.py +0 -0
  83. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/utils/json_envelope.py +0 -0
  84. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/gitwise/utils/parsing.py +0 -0
  85. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/install.sh +0 -0
  86. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/agents/skills/git-audit/SKILL.md +0 -0
  87. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/agents/skills/git-clean/SKILL.md +0 -0
  88. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/agents/skills/git-optimize/SKILL.md +0 -0
  89. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/aider/CONVENTIONS.md.template +0 -0
  90. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/aider/aider.conf.yml.template +0 -0
  91. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/claude/CLAUDE.md.template +0 -0
  92. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/claude/rules/gitwise.md +0 -0
  93. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/claude/settings.json.template +0 -0
  94. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/claude/skills/git-audit/SKILL.md +0 -0
  95. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/claude/skills/git-clean/SKILL.md +0 -0
  96. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/claude/skills/git-optimize/SKILL.md +0 -0
  97. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/codex/agents/gitwise.toml.template +0 -0
  98. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/continue/rules/gitwise.md.template +0 -0
  99. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/cursor/rules/gitwise.mdc.template +0 -0
  100. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/git-config-modern.txt +0 -0
  101. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/hooks/commit-msg +0 -0
  102. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/hooks/pre-commit +0 -0
  103. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/opencode/agents/gitwise.md.template +0 -0
  104. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/pi/skills/gitwise.md.template +0 -0
  105. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/audit.json +0 -0
  106. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/branches.json +0 -0
  107. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/clean.json +0 -0
  108. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/commands.json +0 -0
  109. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/commit.json +0 -0
  110. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/completions.json +0 -0
  111. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/conflicts.json +0 -0
  112. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/context.json +0 -0
  113. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/diff.json +0 -0
  114. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/doctor.json +0 -0
  115. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/health.json +0 -0
  116. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/log.json +0 -0
  117. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/merge.json +0 -0
  118. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/optimize.json +0 -0
  119. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/pick.json +0 -0
  120. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/pr.json +0 -0
  121. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/schema.json +0 -0
  122. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/setup-agents.json +0 -0
  123. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/setup.json +0 -0
  124. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/show.json +0 -0
  125. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/snapshot.json +0 -0
  126. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/stash.json +0 -0
  127. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/status.json +0 -0
  128. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/suggest.json +0 -0
  129. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/summarize.json +0 -0
  130. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/sync.json +0 -0
  131. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/tag.json +0 -0
  132. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/undo.json +0 -0
  133. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/update.json +0 -0
  134. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/share/schemas/v1/input/worktree.json +0 -0
  135. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/__init__.py +0 -0
  136. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/conftest.py +0 -0
  137. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_adapters.py +0 -0
  138. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_branches.py +0 -0
  139. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_clean.py +0 -0
  140. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_commit.py +0 -0
  141. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_conflicts.py +0 -0
  142. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_context.py +0 -0
  143. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_design.py +0 -0
  144. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_diff.py +0 -0
  145. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_doctor.py +0 -0
  146. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_edge_cases.py +0 -0
  147. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_git.py +0 -0
  148. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_git_output_utils.py +0 -0
  149. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_health.py +0 -0
  150. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_json_envelope_utils.py +0 -0
  151. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_main.py +0 -0
  152. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_md_links_script.py +0 -0
  153. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_merge.py +0 -0
  154. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_optimize.py +0 -0
  155. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_output.py +0 -0
  156. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_parsing_utils.py +0 -0
  157. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_pick.py +0 -0
  158. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_pr.py +0 -0
  159. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_roadmap_baseline_script.py +0 -0
  160. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_sa_plan.py +0 -0
  161. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_sa_unit.py +0 -0
  162. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_schema_catalog.py +0 -0
  163. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_setup.py +0 -0
  164. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_setup_agents.py +0 -0
  165. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_show.py +0 -0
  166. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_stash.py +0 -0
  167. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_status.py +0 -0
  168. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_suggest.py +0 -0
  169. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_summarize.py +0 -0
  170. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_sync.py +0 -0
  171. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_sync_changelog_es_script.py +0 -0
  172. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_undo.py +0 -0
  173. {gitwise_cli-0.27.0 → gitwise_cli-0.28.0}/tests/test_worktree.py +0 -0
@@ -3,6 +3,28 @@
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.28.0 (2026-06-19)
7
+
8
+ ### BREAKING CHANGE
9
+
10
+ - three JSON contract changes for agents. (1) branches --json: current/in_worktree are now bool (were strings); ahead/behind are now int|null (were strings); upstream/tracking are now string|null. (2) log --json: date field changes from --date=iso to --date=iso-strict (e.g. "2026-06-18T14:30:00+00:00"). (3) tag list --json: date field changes from %(creatordate:iso) to %(creatordate:iso-strict), same shift as log. Agents and scripts parsing these fields must update their deserializers. With major_version_zero=true this rotates the minor (0.27.x -> 0.28.0); the auto-release workflow will bump pyproject.toml and regenerate CHANGELOG.md on push to main, surfacing all three breaks under the Breaking Changes section.
11
+ - `branches --json` changes types in 5 fields. `current`
12
+ and `in_worktree` are now bool (were "true"/"false" strings); `ahead` and
13
+ `behind` are now int|null (were strings); `upstream` and `tracking` are
14
+ now string|null (were always strings). Agents and scripts parsing these
15
+ fields must update their deserializers. With major_version_zero=true this
16
+ rotates the minor (0.27.x -> 0.28.0); the auto-release workflow will bump
17
+ pyproject.toml and regenerate CHANGELOG.md on push to main.
18
+
19
+ ### Feat
20
+
21
+ - unified loading feedback + structured JSON (BREAKING branches/log/tag) (#64)
22
+ - unified loading feedback + structured JSON for status/branches
23
+
24
+ ### Fix
25
+
26
+ - address CI failures and CodeRabbit review findings
27
+
6
28
  ## v0.27.0 (2026-06-19)
7
29
 
8
30
  ### Feat
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitwise-cli
3
- Version: 0.27.0
3
+ Version: 0.28.0
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
@@ -1,4 +1,4 @@
1
- __version__ = "0.27.0"
1
+ __version__ = "0.28.0"
2
2
 
3
3
 
4
4
  def get_version() -> str:
@@ -7,7 +7,7 @@ from ._cli_dispatch import DISPATCH
7
7
  from ._cli_introspection import extract_command_token, help_payload
8
8
  from ._cli_parser import build_parser
9
9
  from .i18n import t
10
- from .output import print_dim, print_json, set_json_pretty
10
+ from .output import print_dim, print_json, set_json_mode, set_json_pretty
11
11
 
12
12
 
13
13
  def _is_log_json_enabled() -> bool:
@@ -100,6 +100,7 @@ def main() -> int:
100
100
  reset_runtime_config()
101
101
 
102
102
  set_json_pretty(args.json_pretty)
103
+ set_json_mode(args.json)
103
104
 
104
105
  if args.lang:
105
106
  set_locale(args.lang)
@@ -1571,6 +1571,78 @@
1571
1571
  "es": "Auditando: ejecutando git-sizer...",
1572
1572
  "en": "Auditing: running git-sizer..."
1573
1573
  },
1574
+ "status_worktree_add": {
1575
+ "es": "Creando worktree para {branch}...",
1576
+ "en": "Creating worktree for {branch}..."
1577
+ },
1578
+ "status_health_scan": {
1579
+ "es": "Calculando salud del repositorio...",
1580
+ "en": "Computing repository health..."
1581
+ },
1582
+ "status_context_scan": {
1583
+ "es": "Recolectando contexto del repositorio...",
1584
+ "en": "Gathering repository context..."
1585
+ },
1586
+ "status_snapshot_gen": {
1587
+ "es": "Generando snapshot...",
1588
+ "en": "Generating snapshot..."
1589
+ },
1590
+ "status_reading_status": {
1591
+ "es": "Leyendo estado del repositorio...",
1592
+ "en": "Reading repository status..."
1593
+ },
1594
+ "status_summarizing": {
1595
+ "es": "Resumiendo repositorio...",
1596
+ "en": "Summarizing repository..."
1597
+ },
1598
+ "status_reading_diff": {
1599
+ "es": "Calculando cambios...",
1600
+ "en": "Computing changes..."
1601
+ },
1602
+ "status_reading_log": {
1603
+ "es": "Leyendo historial de commits...",
1604
+ "en": "Reading commit history..."
1605
+ },
1606
+ "status_loading_commit": {
1607
+ "es": "Cargando commit...",
1608
+ "en": "Loading commit..."
1609
+ },
1610
+ "status_analyzing_branches": {
1611
+ "es": "Analizando ramas...",
1612
+ "en": "Analyzing branches..."
1613
+ },
1614
+ "status_analyzing_staged": {
1615
+ "es": "Analizando cambios preparados...",
1616
+ "en": "Analyzing staged changes..."
1617
+ },
1618
+ "status_detecting_conflicts": {
1619
+ "es": "Detectando conflictos...",
1620
+ "en": "Detecting conflicts..."
1621
+ },
1622
+ "status_reading_stashes": {
1623
+ "es": "Leyendo stashes...",
1624
+ "en": "Reading stashes..."
1625
+ },
1626
+ "status_reading_tags": {
1627
+ "es": "Leyendo tags...",
1628
+ "en": "Reading tags..."
1629
+ },
1630
+ "status_querying_github": {
1631
+ "es": "Consultando GitHub...",
1632
+ "en": "Querying GitHub..."
1633
+ },
1634
+ "status_checking_env": {
1635
+ "es": "Verificando entorno...",
1636
+ "en": "Checking environment..."
1637
+ },
1638
+ "status_scanning_stale": {
1639
+ "es": "Buscando ramas y refs obsoletos...",
1640
+ "en": "Scanning stale branches and refs..."
1641
+ },
1642
+ "status_updating": {
1643
+ "es": "Actualizando gitwise...",
1644
+ "en": "Updating gitwise..."
1645
+ },
1574
1646
  "step_commit_graph": {
1575
1647
  "es": "git commit-graph write --reachable --changed-paths",
1576
1648
  "en": "git commit-graph write --reachable --changed-paths"
@@ -1927,6 +1999,14 @@
1927
1999
  "es": "update requiere una instalación vía git clone (no se encontró .git/)",
1928
2000
  "en": "update requires a git clone installation (.git/ not found)"
1929
2001
  },
2002
+ "update_no_upstream": {
2003
+ "es": "la rama actual '{branch}' no tiene rama de seguimiento; update solo puede actualizar una rama con upstream",
2004
+ "en": "current branch '{branch}' has no upstream; update can only fast-forward a tracking branch"
2005
+ },
2006
+ "update_no_upstream_hint": {
2007
+ "es": "cambia a tu rama principal (p.ej. 'git switch main') o configura el upstream con 'git branch --set-upstream-to=origin/{branch}'",
2008
+ "en": "switch to your main branch (e.g. 'git switch main') or set an upstream with 'git branch --set-upstream-to=origin/{branch}'"
2009
+ },
1930
2010
  "invalid_grep_pattern": {
1931
2011
  "es": "patrón de búsqueda inválido o peligroso: {pattern}",
1932
2012
  "en": "invalid or unsafe grep pattern: {pattern}"
@@ -1,14 +1,37 @@
1
1
  """gitwise branches — intelligence dashboard with ahead/behind, merged, stale, worktree info."""
2
2
 
3
+ from pathlib import Path
4
+ from typing import TypedDict
5
+
3
6
  from .git import require_root, stale_branches, worktree_branches
4
7
  from .git import run as git_run
5
8
  from .i18n import t
6
- from .output import error, info, print_dim, print_json, print_table
9
+ from .output import error, info, print_dim, print_json, print_table, status
7
10
  from .utils.json_envelope import ok_envelope
8
11
 
9
12
 
10
- def _parse_branches(raw: str, wt_branches: set[str]) -> list[dict[str, str]]:
11
- branches: list[dict[str, str]] = []
13
+ class BranchEntry(TypedDict):
14
+ name: str
15
+ current: bool
16
+ sha: str
17
+ subject: str
18
+ age: str
19
+ upstream: str | None
20
+ ahead: int | None
21
+ behind: int | None
22
+ tracking: str | None
23
+ in_worktree: bool
24
+
25
+
26
+ def _parse_track_count(tracking: str, marker: str) -> int | None:
27
+ if marker not in tracking:
28
+ return None
29
+ raw = tracking.split(marker)[1].split(",")[0].strip().rstrip("]")
30
+ return int(raw) if raw.isdigit() else None
31
+
32
+
33
+ def _parse_branches(raw: str, wt_branches: set[str]) -> list[BranchEntry]:
34
+ branches: list[BranchEntry] = []
12
35
  for line in raw.splitlines():
13
36
  if not line.strip():
14
37
  continue
@@ -23,24 +46,18 @@ def _parse_branches(raw: str, wt_branches: set[str]) -> list[dict[str, str]]:
23
46
  tracking = parts[5] if len(parts) > 5 else ""
24
47
  upstream = parts[6] if len(parts) > 6 else ""
25
48
 
26
- ahead = behind = ""
27
- if "[ahead" in tracking:
28
- ahead = tracking.split("ahead")[1].split(",")[0].strip().rstrip("]")
29
- if "behind" in tracking:
30
- behind = tracking.split("behind")[1].strip().rstrip("]")
31
-
32
49
  branches.append(
33
50
  {
34
51
  "name": name,
35
- "current": str(is_current).lower(),
52
+ "current": is_current,
36
53
  "sha": sha,
37
54
  "subject": subject,
38
55
  "age": age,
39
- "upstream": upstream,
40
- "ahead": ahead,
41
- "behind": behind,
42
- "tracking": tracking,
43
- "in_worktree": str(name in wt_branches).lower(),
56
+ "upstream": upstream or None,
57
+ "ahead": _parse_track_count(tracking, "ahead"),
58
+ "behind": _parse_track_count(tracking, "behind"),
59
+ "tracking": tracking or None,
60
+ "in_worktree": name in wt_branches,
44
61
  }
45
62
  )
46
63
  return branches
@@ -72,7 +89,7 @@ def _print_stale_branches(*, names: list[str], as_json: bool) -> int:
72
89
  return 0
73
90
 
74
91
 
75
- def _fetch_branch_rows(*, root, remote: bool, sort: str) -> list[dict[str, str]] | None:
92
+ def _fetch_branch_rows(*, root: Path, remote: bool, sort: str) -> list[BranchEntry] | None:
76
93
  wt_branches = worktree_branches(cwd=root)
77
94
  ref_pattern = "refs/remotes/" if remote else "refs/heads/"
78
95
  fmt = "%(HEAD)\t%(refname:short)\t%(objectname:short)\t%(subject)\t%(committerdate:relative)\t%(upstream:track)\t%(upstream:short)"
@@ -94,7 +111,7 @@ def _fetch_branch_rows(*, root, remote: bool, sort: str) -> list[dict[str, str]]
94
111
 
95
112
 
96
113
  def _build_branch_rows(
97
- branches: list[dict[str, str]],
114
+ branches: list[BranchEntry],
98
115
  ) -> tuple[list[list[str]], set[int], int | None]:
99
116
  rows: list[list[str]] = []
100
117
  highlight_rows: set[int] = set()
@@ -102,28 +119,28 @@ def _build_branch_rows(
102
119
  for idx, branch_item in enumerate(branches):
103
120
  sha = branch_item["sha"][:8]
104
121
  subject = branch_item["subject"][:40]
105
- age = branch_item.get("age", "")
122
+ age = branch_item["age"]
106
123
  flags: list[str] = []
107
- if branch_item.get("ahead"):
124
+ if branch_item["ahead"]:
108
125
  flags.append(f"↑{branch_item['ahead']}")
109
- if branch_item.get("behind"):
126
+ if branch_item["behind"]:
110
127
  flags.append(f"↓{branch_item['behind']}")
111
- if branch_item.get("in_worktree") == "true":
128
+ if branch_item["in_worktree"]:
112
129
  flags.append("wt")
113
- if branch_item.get("upstream"):
130
+ if branch_item["upstream"]:
114
131
  flags.append(f"→{branch_item['upstream']}")
115
132
  status = " ".join(flags) if flags else ""
116
133
  name_display = (
117
- f"* {branch_item['name']}" if branch_item["current"] == "true" else branch_item["name"]
134
+ f"* {branch_item['name']}" if branch_item["current"] else branch_item["name"]
118
135
  )
119
136
  rows.append([name_display, sha, subject, age, status])
120
- if branch_item["current"] == "true":
137
+ if branch_item["current"]:
121
138
  current_idx = idx
122
139
  highlight_rows.add(idx)
123
140
  return rows, highlight_rows, current_idx
124
141
 
125
142
 
126
- def _print_branch_table(branches: list[dict[str, str]]) -> None:
143
+ def _print_branch_table(branches: list[BranchEntry]) -> None:
127
144
  columns = [
128
145
  (t("col_branch"), "name"),
129
146
  (t("col_sha"), "sha"),
@@ -163,13 +180,15 @@ def run_branches(
163
180
  return 1
164
181
 
165
182
  if stale:
166
- names = stale_branches(cwd=root)
183
+ with status(t("status_analyzing_branches")):
184
+ names = stale_branches(cwd=root)
167
185
  return _print_stale_branches(names=names, as_json=as_json)
168
186
 
169
187
  if not _validate_sort_field(sort):
170
188
  return 1
171
189
 
172
- branches = _fetch_branch_rows(root=root, remote=remote, sort=sort)
190
+ with status(t("status_analyzing_branches")):
191
+ branches = _fetch_branch_rows(root=root, remote=remote, sort=sort)
173
192
  if branches is None:
174
193
  return 1
175
194
  if not branches:
@@ -22,6 +22,7 @@ from .output import (
22
22
  print_header,
23
23
  print_json,
24
24
  print_success,
25
+ status,
25
26
  warn,
26
27
  )
27
28
  from .utils.json_envelope import error_envelope, ok_envelope
@@ -78,7 +79,8 @@ def run_clean(
78
79
  return 1
79
80
  cwd = root
80
81
 
81
- deletable, skipped = _categorize(cwd)
82
+ with status(t("status_scanning_stale")):
83
+ deletable, skipped = _categorize(cwd)
82
84
 
83
85
  if dry_run:
84
86
  if as_json:
@@ -5,7 +5,16 @@ from pathlib import Path
5
5
  from .git import require_root
6
6
  from .git import run as git_run
7
7
  from .i18n import t
8
- from .output import error, ok, print_accent, print_blank, print_dim, print_header, print_json
8
+ from .output import (
9
+ error,
10
+ ok,
11
+ print_accent,
12
+ print_blank,
13
+ print_dim,
14
+ print_header,
15
+ print_json,
16
+ status,
17
+ )
9
18
  from .utils.json_envelope import error_envelope, ok_envelope
10
19
  from .utils.parsing import stripped_non_empty_lines, to_int
11
20
 
@@ -90,7 +99,8 @@ def run_conflicts(
90
99
  if root is None:
91
100
  return 1
92
101
 
93
- conflicts = _find_conflict_files(root)
102
+ with status(t("status_detecting_conflicts")):
103
+ conflicts = _find_conflict_files(root)
94
104
 
95
105
  if not conflicts:
96
106
  return _report_no_conflicts(as_json=as_json)
@@ -5,7 +5,7 @@ from pathlib import Path
5
5
  from .git import require_root
6
6
  from .git import run as git_run
7
7
  from .i18n import t
8
- from .output import info, print_blank, print_bracket, print_dim, print_header, print_json
8
+ from .output import info, print_blank, print_bracket, print_dim, print_header, print_json, status
9
9
 
10
10
 
11
11
  def _directory_tree(root: Path, max_depth: int = 3) -> list[str]:
@@ -108,11 +108,12 @@ def run_context(*, as_json: bool = False) -> int:
108
108
  if root is None:
109
109
  return 1
110
110
 
111
- tree = _directory_tree(root)
112
- contributors = _top_contributors(root)
113
- file_types = _file_type_breakdown(root)
114
- todo_fixme = _todo_fixme_counts(root)
115
- topology = _branch_topology(root)
111
+ with status(t("status_context_scan")):
112
+ tree = _directory_tree(root)
113
+ contributors = _top_contributors(root)
114
+ file_types = _file_type_breakdown(root)
115
+ todo_fixme = _todo_fixme_counts(root)
116
+ topology = _branch_topology(root)
116
117
 
117
118
  if as_json:
118
119
  from .health import compute_health
@@ -15,8 +15,9 @@ from .output import (
15
15
  print_file_status,
16
16
  print_header,
17
17
  print_json,
18
+ status,
18
19
  )
19
- from .utils.git_output import parse_name_status_entries
20
+ from .utils.git_output import parse_name_status_entries, status_label
20
21
  from .utils.json_envelope import ok_envelope
21
22
 
22
23
  DiffValue = str | int | bool
@@ -205,6 +206,9 @@ def _render_stat_output(
205
206
  numstat_details=numstat_details,
206
207
  )
207
208
  if as_json:
209
+ for entry in merged_files:
210
+ raw_code = str(entry.get("code", entry.get("status", "")))
211
+ entry["status_label"] = status_label(raw_code)
208
212
  print_json(
209
213
  ok_envelope(
210
214
  files=merged_files,
@@ -251,7 +255,14 @@ def _render_non_stat_output(
251
255
  for line in lines:
252
256
  parts = line.split("\t", 1)
253
257
  if len(parts) == 2:
254
- files.append({"status": parts[0].strip(), "path": parts[1].strip()})
258
+ code = parts[0].strip()
259
+ files.append(
260
+ {
261
+ "status": code,
262
+ "status_label": status_label(code),
263
+ "path": parts[1].strip(),
264
+ }
265
+ )
255
266
 
256
267
  if as_json:
257
268
  print_json(ok_envelope(files=files, count=len(files)))
@@ -287,7 +298,8 @@ def run_diff(
287
298
 
288
299
  use_stat = stat or (not staged and not name_only and not full)
289
300
  cmd = _diff_cmd(use_stat=use_stat, staged=staged, name_only=name_only, full=full)
290
- result = git_run(cmd, cwd=cwd, check=False)
301
+ with status(t("status_reading_diff")):
302
+ result = git_run(cmd, cwd=cwd, check=False)
291
303
  if result.returncode != 0:
292
304
  error(t("git_diff_failed", error=result.stderr.strip()))
293
305
  return 1
@@ -15,6 +15,7 @@ from .output import (
15
15
  print_json,
16
16
  print_kv,
17
17
  print_status_line,
18
+ status,
18
19
  warn,
19
20
  )
20
21
 
@@ -33,17 +34,18 @@ _TOOL_INFO: dict[str, tuple[str, str]] = {
33
34
 
34
35
 
35
36
  def run_doctor(*, as_json: bool = False) -> int:
36
- git_ver = git_version()
37
- git_ok = git_ver >= MIN_GIT
37
+ with status(t("status_checking_env")):
38
+ git_ver = git_version()
39
+ git_ok = git_ver >= MIN_GIT
38
40
 
39
- python_ver = sys.version_info[:3]
40
- python_ok = python_ver >= (3, 9)
41
+ python_ver = sys.version_info[:3]
42
+ python_ok = python_ver >= (3, 9)
41
43
 
42
- platform_name = platform.system()
43
- fsmonitor_supported = platform_name in ("Darwin", "Windows")
44
+ platform_name = platform.system()
45
+ fsmonitor_supported = platform_name in ("Darwin", "Windows")
44
46
 
45
- optional = {tool: bool(shutil.which(tool)) for tool in _OPTIONAL_TOOLS}
46
- gpg = gpg_status()
47
+ optional = {tool: bool(shutil.which(tool)) for tool in _OPTIONAL_TOOLS}
48
+ gpg = gpg_status()
47
49
 
48
50
  result = {
49
51
  "v": 2,
@@ -61,7 +61,7 @@ def run(
61
61
  ) -> subprocess.CompletedProcess[str]:
62
62
  from .output import debug
63
63
 
64
- cmd_name = args[0] if args else None
64
+ cmd_name = next((arg for arg in args if not arg.startswith("-")), None)
65
65
  actual_timeout = timeout if timeout is not None else _get_timeout(cmd_name)
66
66
  debug(f"git {' '.join(args)}")
67
67
  try:
@@ -12,7 +12,7 @@ from .git import (
12
12
  )
13
13
  from .git import run as git_run
14
14
  from .i18n import t
15
- from .output import print_header, print_json, print_status_line
15
+ from .output import print_header, print_json, print_status_line, status
16
16
  from .utils.json_envelope import ok_envelope
17
17
  from .utils.parsing import non_empty_lines, to_int
18
18
 
@@ -330,7 +330,8 @@ def run_health(*, as_json: bool = False) -> int:
330
330
  if root is None:
331
331
  return 1
332
332
 
333
- h = compute_health(root)
333
+ with status(t("status_health_scan")):
334
+ h = compute_health(root)
334
335
 
335
336
  if as_json:
336
337
  print_json(ok_envelope(payload=h))
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
  from .git import require_root, validate_author_pattern, validate_grep_pattern
7
7
  from .git import run as git_run
8
8
  from .i18n import t
9
- from .output import bat_pipe, error, info, print_json, print_table
9
+ from .output import bat_pipe, error, info, print_json, print_table, status
10
10
  from .utils.json_envelope import ok_envelope
11
11
 
12
12
 
@@ -62,7 +62,7 @@ def _build_log_json_args(
62
62
  "log",
63
63
  f"--max-count={max_count}",
64
64
  "--format=%H%n%h%n%an%n%ae%n%ad%n%s%n%P%n---END---",
65
- "--date=iso",
65
+ "--date=iso-strict",
66
66
  ]
67
67
  if author:
68
68
  if not validate_author_pattern(author):
@@ -240,7 +240,8 @@ def _run_log_human(
240
240
  )
241
241
  except ValueError:
242
242
  return 1
243
- result = git_run(args, cwd=root, check=False)
243
+ with status(t("status_reading_log")):
244
+ result = git_run(args, cwd=root, check=False)
244
245
  if result.returncode != 0:
245
246
  if result.stderr and "does not have any commits yet" in result.stderr:
246
247
  info(t("no_commits_yet"))
@@ -40,6 +40,7 @@ _COLOR_SYSTEM_MAP: dict[ColorDepth, str] = {
40
40
 
41
41
  _LOG_JSON = os.environ.get("GITWISE_LOG_JSON", "").lower() in ("1", "true")
42
42
  _JSON_PRETTY = os.environ.get("GITWISE_JSON_PRETTY", "").lower() in ("1", "true")
43
+ _JSON_MODE = False
43
44
 
44
45
 
45
46
  def _structured_log(level: str, msg: str, **kwargs: Any) -> None:
@@ -58,6 +59,11 @@ def set_json_pretty(pretty: bool) -> None:
58
59
  _JSON_PRETTY = pretty
59
60
 
60
61
 
62
+ def set_json_mode(enabled: bool) -> None:
63
+ global _JSON_MODE
64
+ _JSON_MODE = enabled
65
+
66
+
61
67
  class _ModuleAttr:
62
68
  __slots__ = ("_name",)
63
69
 
@@ -287,6 +293,10 @@ def confirm(prompt: str) -> bool:
287
293
 
288
294
  @contextmanager
289
295
  def status(message: str) -> Iterator[None]:
296
+ if _JSON_MODE:
297
+ yield
298
+ return
299
+
290
300
  if _should_use_rich():
291
301
  with _get_console().status(message):
292
302
  yield
@@ -17,6 +17,7 @@ from .output import (
17
17
  print_header,
18
18
  print_json,
19
19
  print_table,
20
+ status,
20
21
  )
21
22
  from .utils.json_envelope import error_envelope, ok_envelope
22
23
  from .utils.parsing import dict_list, to_int
@@ -60,14 +61,15 @@ def _gh_available() -> bool:
60
61
  def _gh(args: list[str], cwd) -> tuple[int, str, str]:
61
62
  import subprocess
62
63
 
63
- r = subprocess.run(
64
- ["gh"] + args,
65
- cwd=cwd,
66
- capture_output=True,
67
- text=True,
68
- check=False,
69
- timeout=120,
70
- )
64
+ with status(t("status_querying_github")):
65
+ r = subprocess.run(
66
+ ["gh"] + args,
67
+ cwd=cwd,
68
+ capture_output=True,
69
+ text=True,
70
+ check=False,
71
+ timeout=120,
72
+ )
71
73
  return r.returncode, r.stdout.strip(), r.stderr.strip()
72
74
 
73
75
 
@@ -3,7 +3,7 @@
3
3
  from .git import require_root, validate_ref
4
4
  from .git import run as git_run
5
5
  from .i18n import t
6
- from .output import bat_pipe, error, print_diffstat, print_header, print_json
6
+ from .output import bat_pipe, error, print_diffstat, print_header, print_json, status
7
7
  from .utils.git_output import parse_diffstat_entries, parse_name_status_entries
8
8
  from .utils.json_envelope import ok_envelope
9
9
 
@@ -87,7 +87,8 @@ def run_show(
87
87
  print_json(ok_envelope(payload=data))
88
88
  else:
89
89
  if stat:
90
- r = git_run(["show", "--stat", "--format=", ref], cwd=root, check=False)
90
+ with status(t("status_loading_commit")):
91
+ r = git_run(["show", "--stat", "--format=", ref], cwd=root, check=False)
91
92
  if r.returncode != 0:
92
93
  error(t("git_show_failed", error=r.stderr.strip()))
93
94
  return 1
@@ -108,7 +109,8 @@ def run_show(
108
109
  bat_pipe(r.stdout, language="diff")
109
110
  else:
110
111
  args = _build_show_args(ref, stat)
111
- r = git_run(args, cwd=root, check=False)
112
+ with status(t("status_loading_commit")):
113
+ r = git_run(args, cwd=root, check=False)
112
114
  if r.returncode != 0:
113
115
  error(t("git_show_failed", error=r.stderr.strip()))
114
116
  return 1
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
  from .git import require_root
7
7
  from .git import run as git_run
8
8
  from .i18n import t
9
- from .output import debug, print_header, print_json
9
+ from .output import debug, print_header, print_json, status
10
10
  from .utils.json_envelope import ok_envelope
11
11
 
12
12
 
@@ -100,7 +100,8 @@ def run_snapshot(*, as_json: bool = False) -> int:
100
100
  if root is None:
101
101
  return 1
102
102
 
103
- path = generate_snapshot(root)
103
+ with status(t("status_snapshot_gen")):
104
+ path = generate_snapshot(root)
104
105
 
105
106
  if as_json:
106
107
  print_json(ok_envelope(path=str(path)))
@@ -14,6 +14,7 @@ from .output import (
14
14
  print_header,
15
15
  print_json,
16
16
  print_table,
17
+ status,
17
18
  warn,
18
19
  )
19
20
  from .utils.git_output import parse_diffstat_entries
@@ -25,7 +26,8 @@ def _parse_diffstat_entries(raw: str) -> list[dict[str, str]]:
25
26
 
26
27
 
27
28
  def _stash_list(root: Path) -> list[dict[str, str]]:
28
- r = git_run(["stash", "list"], cwd=root, check=False)
29
+ with status(t("status_reading_stashes")):
30
+ r = git_run(["stash", "list"], cwd=root, check=False)
29
31
  if r.returncode != 0 or not r.stdout.strip():
30
32
  return []
31
33
  result: list[dict[str, str]] = []
@@ -78,7 +80,8 @@ def _cmd_show(root: Path, index: int, *, as_json: bool, patch: bool = False) ->
78
80
  stat_args = ["stash", "show", "--stat", ref]
79
81
  if patch:
80
82
  stat_args = ["stash", "show", "-p", ref]
81
- r = git_run(stat_args, cwd=root, check=False)
83
+ with status(t("status_reading_stashes")):
84
+ r = git_run(stat_args, cwd=root, check=False)
82
85
  if r.returncode != 0:
83
86
  msg = t("stash_not_found", index=str(index))
84
87
  if as_json: