gitwise-cli 0.34.0__py3-none-any.whl → 0.34.2__py3-none-any.whl

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/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """gitwise -- CLI for optimizing git workflows and coding-agent integration."""
2
2
 
3
- __version__ = "0.34.0"
3
+ __version__ = "0.34.2"
4
4
 
5
5
 
6
6
  def get_version() -> str:
gitwise/__main__.py CHANGED
@@ -5,7 +5,7 @@ import time
5
5
 
6
6
  from ._cli_dispatch import DISPATCH
7
7
  from ._cli_introspection import extract_command_token, help_data, help_payload
8
- from ._cli_parser import build_parser
8
+ from ._cli_parser import _parse_global_options, build_parser
9
9
  from .i18n import t
10
10
  from .output import print_dim, print_json, set_json_mode, set_json_pretty
11
11
 
@@ -70,6 +70,7 @@ def main() -> int:
70
70
 
71
71
  parser = build_parser()
72
72
  raw_argv = sys.argv[1:]
73
+ global_options = _parse_global_options(raw_argv)
73
74
  wants_json_pretty = "--json-pretty" in raw_argv or "--pretty" in raw_argv
74
75
  if wants_json_pretty:
75
76
  set_json_pretty(True)
@@ -83,6 +84,10 @@ def main() -> int:
83
84
  return 0
84
85
 
85
86
  args = parser.parse_args()
87
+ for dest in ("lang", "theme", "json", "json_pretty"):
88
+ value = getattr(global_options, dest)
89
+ if value is not None and value is not False:
90
+ setattr(args, dest, value)
86
91
  if args.json_pretty:
87
92
  args.json = True
88
93
 
gitwise/_cli_parser.py CHANGED
@@ -12,8 +12,8 @@ def _root_help_epilog() -> str:
12
12
  return t("help_root_environment_epilog")
13
13
 
14
14
 
15
- def build_parser() -> argparse.ArgumentParser:
16
- """Build and return the top-level argparse parser with all subcommands registered."""
15
+ def _build_common_parser() -> argparse.ArgumentParser:
16
+ """Build the parser shared by the root command and every subcommand."""
17
17
  parent = argparse.ArgumentParser(add_help=False)
18
18
  parent.add_argument(
19
19
  "--lang",
@@ -35,6 +35,18 @@ def build_parser() -> argparse.ArgumentParser:
35
35
  action="store_true",
36
36
  help="pretty-print JSON output",
37
37
  )
38
+ return parent
39
+
40
+
41
+ def _parse_global_options(argv: list[str]) -> argparse.Namespace:
42
+ """Parse common options independently of their position in *argv*."""
43
+ options, _ = _build_common_parser().parse_known_args(argv)
44
+ return options
45
+
46
+
47
+ def build_parser() -> argparse.ArgumentParser:
48
+ """Build and return the top-level argparse parser with all subcommands registered."""
49
+ parent = _build_common_parser()
38
50
 
39
51
  parser = argparse.ArgumentParser(
40
52
  prog="gitwise",
gitwise/_i18n_data.json CHANGED
@@ -1611,6 +1611,10 @@
1611
1611
  "es": "No se pudo crear el stash (¿no hay cambios?)",
1612
1612
  "en": "Failed to create stash (no changes?)"
1613
1613
  },
1614
+ "stash_clear_failed": {
1615
+ "es": "No se pudieron limpiar los stashes",
1616
+ "en": "Failed to clear stashes"
1617
+ },
1614
1618
  "stash_hint": {
1615
1619
  "es": "Usa 'gitwise stash list' para ver índices válidos antes de show/drop/pop.",
1616
1620
  "en": "Use 'gitwise stash list' to inspect valid indexes before show/drop/pop."
gitwise/audit.py CHANGED
@@ -8,6 +8,7 @@ from pathlib import Path
8
8
  from typing import Any
9
9
 
10
10
  from gitwise.git import (
11
+ _get_timeout,
11
12
  gpg_status,
12
13
  has_commit_graph,
13
14
  has_remote,
@@ -112,13 +113,18 @@ def _run_git_sizer(cwd: Path) -> dict | None:
112
113
  """Run ``git-sizer`` if available and return its JSON output, or None."""
113
114
  if not shutil.which("git-sizer"):
114
115
  return None
115
- r = subprocess.run(
116
- ["git-sizer", "--threshold=2", "--json"],
117
- cwd=cwd,
118
- capture_output=True,
119
- text=True,
120
- check=False,
121
- )
116
+ try:
117
+ r = subprocess.run(
118
+ ["git-sizer", "--threshold=2", "--json"],
119
+ cwd=cwd,
120
+ capture_output=True,
121
+ text=True,
122
+ check=False,
123
+ timeout=_get_timeout("git-sizer"),
124
+ )
125
+ except subprocess.TimeoutExpired:
126
+ debug("git-sizer timed out")
127
+ return None
122
128
  if r.returncode not in (0, 1):
123
129
  return None
124
130
  import json
gitwise/conflicts.py CHANGED
@@ -4,7 +4,7 @@ import subprocess
4
4
  import tempfile
5
5
  from pathlib import Path
6
6
 
7
- from gitwise.git import require_root
7
+ from gitwise.git import _GIT_ENV, require_root
8
8
  from gitwise.git import run as git_run
9
9
  from gitwise.i18n import t
10
10
  from gitwise.output import (
@@ -27,6 +27,7 @@ def _git_bytes(args: list[str], *, cwd: Path) -> tuple[int, bytes, bytes]:
27
27
  ["git", *args],
28
28
  cwd=cwd,
29
29
  capture_output=True,
30
+ env=_GIT_ENV,
30
31
  timeout=120,
31
32
  )
32
33
  return r.returncode, r.stdout, r.stderr
gitwise/stash.py CHANGED
@@ -115,13 +115,28 @@ def _cmd_show(root: Path, index: int, *, as_json: bool, patch: bool = False) ->
115
115
  return 0
116
116
 
117
117
 
118
- def _cmd_pop(root: Path, index: int, *, as_json: bool) -> int:
119
- """Execute ``stash pop`` sub-action."""
118
+ def _cmd_pop(root: Path, index: int, *, as_json: bool, yes: bool = False) -> int:
119
+ """Execute ``stash pop`` sub-action with optional confirmation."""
120
120
  ref = f"stash@{{{index}}}"
121
+ if as_json and not yes:
122
+ print_json(
123
+ error_envelope(
124
+ "stash",
125
+ error=t("yes_required_with_json"),
126
+ code="yes_required",
127
+ hint=t("yes_required_hint"),
128
+ )
129
+ )
130
+ return 2
121
131
  r = git_run(["stash", "pop", ref], cwd=root, check=False)
122
132
  if r.returncode != 0:
123
- error(r.stderr.strip())
124
- return 1
133
+ return report_error(
134
+ "stash",
135
+ as_json=as_json,
136
+ msg=r.stderr.strip() or t("stash_not_found", index=str(index)),
137
+ code="stash_pop_failed",
138
+ hint=t("stash_hint"),
139
+ )
125
140
  if as_json:
126
141
  print_json(ok_envelope("stash", popped=ref))
127
142
  return 0
@@ -132,13 +147,28 @@ def _cmd_pop(root: Path, index: int, *, as_json: bool) -> int:
132
147
  def _cmd_drop(root: Path, index: int, *, as_json: bool, yes: bool = False) -> int:
133
148
  """Execute ``stash drop`` sub-action with optional confirmation."""
134
149
  ref = f"stash@{{{index}}}"
150
+ if as_json and not yes:
151
+ print_json(
152
+ error_envelope(
153
+ "stash",
154
+ error=t("yes_required_with_json"),
155
+ code="yes_required",
156
+ hint=t("yes_required_hint"),
157
+ )
158
+ )
159
+ return 2
135
160
  if not yes and not confirm(t("confirm_stash_drop", ref=ref)):
136
161
  warn(t("aborted"))
137
162
  return 1
138
163
  r = git_run(["stash", "drop", ref], cwd=root, check=False)
139
164
  if r.returncode != 0:
140
- error(r.stderr.strip())
141
- return 1
165
+ return report_error(
166
+ "stash",
167
+ as_json=as_json,
168
+ msg=r.stderr.strip() or t("stash_not_found", index=str(index)),
169
+ code="stash_drop_failed",
170
+ hint=t("stash_hint"),
171
+ )
142
172
  if as_json:
143
173
  print_json(ok_envelope("stash", dropped=ref))
144
174
  return 0
@@ -150,6 +180,9 @@ def _cmd_clean(root: Path, *, as_json: bool, yes: bool = False, dry_run: bool =
150
180
  """Execute ``stash clear`` sub-action with optional dry-run and confirmation."""
151
181
  stashes = _stash_list(root)
152
182
  if not stashes:
183
+ if as_json:
184
+ print_json(ok_envelope("stash", cleared=0))
185
+ return 0
153
186
  ok(t("stash_empty"))
154
187
  return 0
155
188
  if dry_run:
@@ -158,13 +191,27 @@ def _cmd_clean(root: Path, *, as_json: bool, yes: bool = False, dry_run: bool =
158
191
  return 0
159
192
  ok(t("stash_clean_dry", count=str(len(stashes))))
160
193
  return 0
194
+ if as_json and not yes:
195
+ print_json(
196
+ error_envelope(
197
+ "stash",
198
+ error=t("yes_required_with_json"),
199
+ code="yes_required",
200
+ hint=t("yes_required_hint"),
201
+ )
202
+ )
203
+ return 2
161
204
  if not yes and not confirm(t("confirm_stash_clean", count=str(len(stashes)))):
162
205
  warn(t("aborted"))
163
206
  return 1
164
207
  r = git_run(["stash", "clear"], cwd=root, check=False)
165
208
  if r.returncode != 0:
166
- error(r.stderr.strip())
167
- return 1
209
+ return report_error(
210
+ "stash",
211
+ as_json=as_json,
212
+ msg=r.stderr.strip() or t("stash_clear_failed"),
213
+ code="stash_clear_failed",
214
+ )
168
215
  if as_json:
169
216
  print_json(ok_envelope("stash", cleared=len(stashes)))
170
217
  return 0
@@ -253,7 +300,7 @@ def run_stash(
253
300
  if action == "apply":
254
301
  return _cmd_apply(root, index, as_json=as_json)
255
302
  if action == "pop":
256
- return _cmd_pop(root, index, as_json=as_json)
303
+ return _cmd_pop(root, index, as_json=as_json, yes=yes)
257
304
  if action == "push":
258
305
  return _cmd_push(
259
306
  root,
gitwise/worktree.py CHANGED
@@ -182,7 +182,12 @@ def _worktree_clean(cwd: Path, *, dry_run: bool = False, as_json: bool = False)
182
182
 
183
183
 
184
184
  def _worktree_remove(
185
- target: str, root: Path, *, force: bool = False, as_json: bool = False
185
+ target: str,
186
+ root: Path,
187
+ *,
188
+ force: bool = False,
189
+ dry_run: bool = False,
190
+ as_json: bool = False,
186
191
  ) -> int:
187
192
  """Remove a worktree identified by path or checked-out branch name."""
188
193
  if not target:
@@ -216,6 +221,21 @@ def _worktree_remove(
216
221
  code="worktree_remove_primary",
217
222
  )
218
223
 
224
+ if dry_run:
225
+ if as_json:
226
+ print_json(
227
+ ok_envelope(
228
+ "worktree",
229
+ would_remove=match["path"],
230
+ branch=match.get("branch") or "",
231
+ dry_run=True,
232
+ )
233
+ )
234
+ return 0
235
+ print_dim(match["path"])
236
+ print_dim(t("dry_run_no_exec"))
237
+ return 0
238
+
219
239
  args = ["worktree", "remove"]
220
240
  if force:
221
241
  args.append("--force")
@@ -252,8 +272,12 @@ def run_worktree(
252
272
 
253
273
  if action == "new":
254
274
  if not branch:
255
- error(t("worktree_usage"))
256
- return 1
275
+ return report_error(
276
+ "worktree",
277
+ as_json=as_json,
278
+ msg=t("worktree_usage"),
279
+ code="worktree_branch_required",
280
+ )
257
281
  if as_json:
258
282
  rc, data = _worktree_new_json(branch, root)
259
283
  if rc == 0:
@@ -274,7 +298,7 @@ def run_worktree(
274
298
  return _worktree_clean(root, dry_run=dry_run, as_json=as_json)
275
299
 
276
300
  elif action == "remove":
277
- return _worktree_remove(branch or "", root, force=force, as_json=as_json)
301
+ return _worktree_remove(branch or "", root, force=force, dry_run=dry_run, as_json=as_json)
278
302
 
279
303
  elif action == "list":
280
304
  return _worktree_list(root, as_json=as_json)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitwise-cli
3
- Version: 0.34.0
3
+ Version: 0.34.2
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,18 +1,18 @@
1
- gitwise/__init__.py,sha256=nS4J8bZ1qlxmCZRp41mmaXYzWPuyD9qbC44mbpknHXw,432
2
- gitwise/__main__.py,sha256=61YvgbKHUW8buv3Hw3UfM84U9PqKTuJzgS_gMkWFd80,4914
1
+ gitwise/__init__.py,sha256=J65EqE0j1_NcQFH4XCgKopAMMFlY1qvLEgALjnH5PK8,432
2
+ gitwise/__main__.py,sha256=seS0qQDTl0WH4FHBeNA_HGJtnpXnfRqi8qCc7C3MbIc,5186
3
3
  gitwise/_cli_completions.py,sha256=TlVH42FLU6_1vpTpbxKp-i7r6B8yPtk_LZ3D2l8HMWI,6570
4
4
  gitwise/_cli_dispatch.py,sha256=8cPF9dLD0n5l-4O0XeAGuvZz9_AQyJfdKg6wnV4ckE8,19161
5
5
  gitwise/_cli_introspection.py,sha256=yzPXo3OjoZXCpPK5wj908kE9BfkIOs9fwXXChIdjIZ0,11120
6
- gitwise/_cli_parser.py,sha256=1HVHYNcoS99Lrxb1H8SONh5b0RUr5C1p8ME2i-4ibcc,18874
6
+ gitwise/_cli_parser.py,sha256=Zj7N_qLV057yFqjV70zVXzGdtIzCxxPS67e3snPwkMM,19285
7
7
  gitwise/_cli_setup_agents.py,sha256=ErDGdTVCx-b416LnkH5yZzHqYEiO2rkBvYtHDi59mW4,15328
8
- gitwise/_i18n_data.json,sha256=dEhZQbrt-nXyBwh6r-RZvTVO4SnefNOGP1VJ-fzIHe4,70687
8
+ gitwise/_i18n_data.json,sha256=WJmNbKqiIGWxFLkP3jiKvVOkqqjuPEA2fuEDUpXT7qc,70802
9
9
  gitwise/_paths.py,sha256=VRkql9HQ5_OjwbpcX-18fwe_lMpuQffP4AiBV6suSFg,658
10
10
  gitwise/_runtime_config.py,sha256=EF0NOlNNvEf9awRsfHr0Ma7MNgy4Fl3aPbLY9HEVEpQ,8350
11
- gitwise/audit.py,sha256=32NgupNXHEZI-bjS2bkTKm8C5BieukNszo1BnKpE1Aw,11125
11
+ gitwise/audit.py,sha256=644-WhyxelBS6lkSKvidQgOXGSs-UWwyWKfxphQ8PSE,11322
12
12
  gitwise/branches.py,sha256=rCl0oV8Us1qTDlaHVHihlmTkQz-qmQL6tL4lg-ttj2w,7748
13
13
  gitwise/clean.py,sha256=pU6TA40T1dr7z3RomRY8VU5B3_laDdbsXpWDrdpRVZM,7024
14
14
  gitwise/commit.py,sha256=VmoDrf-yqFKqMi0gcl74RYS525gbnrdQhQSoy-Q3UKk,9714
15
- gitwise/conflicts.py,sha256=rM1q8ZoDIcrkjg-Q6reGLm2mx5Z88zGuSk3C4iZpJ64,8470
15
+ gitwise/conflicts.py,sha256=eyTmqA4HEavn-rW7xCA3TSSaZq3VE5gmVH2BONM_Ah8,8502
16
16
  gitwise/context.py,sha256=Drxv7isxsQy4haf5uo0BTCugvaClT_qUAWoLw-IdJ8Y,6240
17
17
  gitwise/design.py,sha256=9P_XJc6P_glRc9DPOwb_VbnxaB-ekFRcd71rcs4JWc8,12936
18
18
  gitwise/diff.py,sha256=6FBLC2NOitaOpAdLveMNN1BG3G8DmYsUGmvWFu2RQFY,21163
@@ -31,7 +31,7 @@ gitwise/schema.py,sha256=O8IMVWVRCQlOg6y84FHtQflhOb5iE5Lm99zajaYef9A,4107
31
31
  gitwise/setup.py,sha256=0kM7fJ28fLWoIS2Ks--QR9UWbA2M-z6WcGW8TIWWb8A,18427
32
32
  gitwise/show.py,sha256=tdDKeHhrlagmjcg2tHxTVZTEesa0XYylG3hZwRdhrio,5216
33
33
  gitwise/snapshot.py,sha256=4_kGmaRfMTVMEUHLJiI-M6_dygCwNszEQjBpu4-VawU,3899
34
- gitwise/stash.py,sha256=3A_Wj9nkLxhJhH0NAm-jlFxJXHHE7Y90aXu5KotPLT4,8298
34
+ gitwise/stash.py,sha256=GLM_UHJqwds64k1xscEyNKwc9iFbJaAX1FqvkQu17XM,9790
35
35
  gitwise/status.py,sha256=IyMLauvAMTjqqgCa8jUeVAg2djy4A5PdSnb2ZY9PWMs,6954
36
36
  gitwise/suggest.py,sha256=pgbTQdGJ7K7_viHd602Ib1T04SfE2IYddnDoC_WEJu8,6212
37
37
  gitwise/summarize.py,sha256=SqTHBSxNUL1TG4hXMdBZnl-n1Q5AmH-7TJjAOwph7sA,7201
@@ -39,7 +39,7 @@ gitwise/sync.py,sha256=NG50yVJ36M0IWD5YoZ8bK0APJqETIjId-lcnsXI4JqU,8727
39
39
  gitwise/tag.py,sha256=h2YCqXLR35zdst3EkAiSrOHe4m9OLCybENsA8ul0cjw,8281
40
40
  gitwise/undo.py,sha256=YIGwthyfflYe3ZBR1PCdHmj7jVSDgmLHeG2-fMMuDY0,4641
41
41
  gitwise/update.py,sha256=n6l89aFuXAyowFpGxoH9TbMjZQ4eLJxlFqHquEi37hs,2446
42
- gitwise/worktree.py,sha256=acfiX6S8wiY7TVC9_DSBAVMYFY9i6VKYE3N_xnID3yA,9643
42
+ gitwise/worktree.py,sha256=gJprT1BMZ2r8DqUK-AO05ewAfxKcOIUP1Xq31eKv3_Q,10243
43
43
  gitwise/setup_agents/__init__.py,sha256=P5IrjTxvT8kZUNNPqIyN9rMvlcNuyckFRyL02ZFuCic,1001
44
44
  gitwise/setup_agents/exec.py,sha256=rT8KulX9T-R71e-HuFRYpbnceWb0IHXme2g0O78z2Sw,18303
45
45
  gitwise/setup_agents/format.py,sha256=DFa7LA4gms6K-0bSohzoBBUfUQlgGgNowR1IDsivOlI,5466
@@ -148,8 +148,8 @@ gitwise/share/schemas/v1/output/tag.json,sha256=EPDFtSjrPXqUJfhf0j8ciCrEhjACq1VW
148
148
  gitwise/share/schemas/v1/output/undo.json,sha256=eVCVYhGNnUF025ExbxHygaRzWybGCWFhp540U5JMk60,1467
149
149
  gitwise/share/schemas/v1/output/update.json,sha256=Gm0ACHlpgRhOr1-qrV6fAXxi_Pcbv9zsfZ17_2btJVs,1411
150
150
  gitwise/share/schemas/v1/output/worktree.json,sha256=55Z6wgv0BOV4YhAZAqLI0vgKXIBcw0Xspbn0N0tW0I4,1929
151
- gitwise_cli-0.34.0.dist-info/METADATA,sha256=kxsWSljKkDRY5y-kRoCYdBPQ0KNMESuFOQidsPpVZ9U,7563
152
- gitwise_cli-0.34.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
153
- gitwise_cli-0.34.0.dist-info/entry_points.txt,sha256=m3_cGIC4VTGp7Wn5_s7NBPACs9Yiwdn4ZBWUBX4PMfQ,77
154
- gitwise_cli-0.34.0.dist-info/licenses/LICENSE,sha256=vfJO-ThMtWhZOD9MsArN2yul1EJmxAXxfeGHKnEk9QQ,1063
155
- gitwise_cli-0.34.0.dist-info/RECORD,,
151
+ gitwise_cli-0.34.2.dist-info/METADATA,sha256=k_m8SR6AaRdacN-Bz8FgPtsMqGotH4DK5E1V_pkdXoY,7563
152
+ gitwise_cli-0.34.2.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
153
+ gitwise_cli-0.34.2.dist-info/entry_points.txt,sha256=m3_cGIC4VTGp7Wn5_s7NBPACs9Yiwdn4ZBWUBX4PMfQ,77
154
+ gitwise_cli-0.34.2.dist-info/licenses/LICENSE,sha256=vfJO-ThMtWhZOD9MsArN2yul1EJmxAXxfeGHKnEk9QQ,1063
155
+ gitwise_cli-0.34.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.30.1
2
+ Generator: hatchling 1.31.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any