gitwise-cli 0.27.0__py3-none-any.whl → 0.28.0__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,4 +1,4 @@
1
- __version__ = "0.27.0"
1
+ __version__ = "0.28.0"
2
2
 
3
3
 
4
4
  def get_version() -> str:
gitwise/__main__.py CHANGED
@@ -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)
gitwise/_i18n_data.json CHANGED
@@ -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}"
gitwise/branches.py CHANGED
@@ -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:
gitwise/clean.py CHANGED
@@ -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:
gitwise/conflicts.py CHANGED
@@ -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)
gitwise/context.py CHANGED
@@ -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
gitwise/diff.py CHANGED
@@ -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
gitwise/doctor.py CHANGED
@@ -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,
gitwise/git.py CHANGED
@@ -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:
gitwise/health.py CHANGED
@@ -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))
gitwise/log.py CHANGED
@@ -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"))
gitwise/output.py CHANGED
@@ -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
gitwise/pr.py CHANGED
@@ -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
 
gitwise/show.py CHANGED
@@ -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
gitwise/snapshot.py CHANGED
@@ -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)))
gitwise/stash.py CHANGED
@@ -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:
gitwise/status.py CHANGED
@@ -1,5 +1,7 @@
1
1
  """gitwise status — enhanced git status for humans and AI agents."""
2
2
 
3
+ from pathlib import Path
4
+
3
5
  from .git import current_branch, has_upstream, require_root
4
6
  from .git import run as git_run
5
7
  from .i18n import t
@@ -8,13 +10,27 @@ from .output import (
8
10
  ok,
9
11
  print_blank,
10
12
  print_bracket,
13
+ print_commit_line,
11
14
  print_file_status,
12
15
  print_header,
13
16
  print_json,
17
+ status,
14
18
  )
15
19
  from .utils.parsing import parse_two_ints
16
20
 
17
21
 
22
+ def _range_commits(root: Path, rev_range: str) -> list[dict[str, str]]:
23
+ r = git_run(["log", "--format=%H%x09%h%x09%s", rev_range], cwd=root, check=False)
24
+ if r.returncode != 0:
25
+ return []
26
+ commits: list[dict[str, str]] = []
27
+ for line in r.stdout.splitlines():
28
+ parts = line.split("\t", 2)
29
+ if len(parts) == 3:
30
+ commits.append({"hash": parts[0], "short_hash": parts[1], "subject": parts[2]})
31
+ return commits
32
+
33
+
18
34
  def run_status(*, as_json: bool = False) -> int:
19
35
  root, err = require_root()
20
36
  if err:
@@ -24,24 +40,32 @@ def run_status(*, as_json: bool = False) -> int:
24
40
 
25
41
  branch = current_branch(root) or t("detached_head")
26
42
 
27
- status_r = git_run(["status", "--porcelain"], cwd=root, check=False)
28
- status_lines = status_r.stdout.splitlines() if status_r.returncode == 0 else []
43
+ ahead = behind = 0
44
+ ahead_commits: list[dict[str, str]] = []
45
+ behind_commits: list[dict[str, str]] = []
46
+ with status(t("status_reading_status")):
47
+ status_r = git_run(["status", "--porcelain"], cwd=root, check=False)
48
+ status_lines = status_r.stdout.splitlines() if status_r.returncode == 0 else []
29
49
 
30
- staged = [ln for ln in status_lines if ln and ln[0] not in (" ", "?")]
31
- unstaged = [ln for ln in status_lines if ln and ln[1] not in (" ", "?")]
32
- untracked = [ln for ln in status_lines if ln and ln.startswith("??")]
50
+ staged = [ln for ln in status_lines if ln and ln[0] not in (" ", "?")]
51
+ unstaged = [ln for ln in status_lines if ln and ln[1] not in (" ", "?")]
52
+ untracked = [ln for ln in status_lines if ln and ln.startswith("??")]
33
53
 
34
- ahead = behind = 0
35
- if has_upstream(root):
36
- ab_r = git_run(
37
- ["rev-list", "--left-right", "--count", "HEAD...@{u}"],
38
- cwd=root,
39
- check=False,
40
- )
41
- if ab_r.returncode == 0:
42
- parsed = parse_two_ints(ab_r.stdout)
43
- if parsed is not None:
44
- ahead, behind = parsed
54
+ upstream = has_upstream(root)
55
+ if upstream:
56
+ ab_r = git_run(
57
+ ["rev-list", "--left-right", "--count", "HEAD...@{u}"],
58
+ cwd=root,
59
+ check=False,
60
+ )
61
+ if ab_r.returncode == 0:
62
+ parsed = parse_two_ints(ab_r.stdout)
63
+ if parsed is not None:
64
+ ahead, behind = parsed
65
+ if ahead:
66
+ ahead_commits = _range_commits(root, "@{u}..HEAD")
67
+ if behind:
68
+ behind_commits = _range_commits(root, "HEAD..@{u}")
45
69
 
46
70
  if as_json:
47
71
  print_json(
@@ -49,9 +73,11 @@ def run_status(*, as_json: bool = False) -> int:
49
73
  "v": 2,
50
74
  "ok": True,
51
75
  "branch": branch,
52
- "has_upstream": has_upstream(root),
76
+ "has_upstream": upstream,
53
77
  "ahead": ahead,
54
78
  "behind": behind,
79
+ "ahead_commits": ahead_commits,
80
+ "behind_commits": behind_commits,
55
81
  "staged": len(staged),
56
82
  "unstaged": len(unstaged),
57
83
  "untracked": len(untracked),
@@ -63,7 +89,11 @@ def run_status(*, as_json: bool = False) -> int:
63
89
  print_header(t("branch_label", branch=branch))
64
90
  if ahead or behind:
65
91
  print_bracket(t("status_ahead_label"), str(ahead))
92
+ for commit in ahead_commits:
93
+ print_commit_line(f"{commit['short_hash']} {commit['subject']}", indent=4)
66
94
  print_bracket(t("status_behind_label"), str(behind))
95
+ for commit in behind_commits:
96
+ print_commit_line(f"{commit['short_hash']} {commit['subject']}", indent=4)
67
97
 
68
98
  if not status_lines:
69
99
  print_blank()
gitwise/suggest.py CHANGED
@@ -5,7 +5,7 @@ import re
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, print_bracket, print_file_status, print_header, print_json
8
+ from .output import error, print_bracket, print_file_status, print_header, print_json, status
9
9
  from .utils.json_envelope import error_envelope, ok_envelope
10
10
  from .utils.parsing import stripped_non_empty_lines
11
11
 
@@ -117,7 +117,9 @@ def run_suggest(*, as_json: bool = False) -> int:
117
117
  return 1
118
118
 
119
119
  try:
120
- staged_files, staged_map = _collect_staged_files(root)
120
+ with status(t("status_analyzing_staged")):
121
+ staged_files, staged_map = _collect_staged_files(root)
122
+ additions, deletions = _numstat_totals(root) if staged_files else (0, 0)
121
123
  except RuntimeError:
122
124
  error(t("suggest_diff_failed"))
123
125
  return 1
@@ -129,8 +131,6 @@ def run_suggest(*, as_json: bool = False) -> int:
129
131
  error(t("suggest_no_staged"))
130
132
  return 1
131
133
 
132
- additions, deletions = _numstat_totals(root)
133
-
134
134
  message = _build_message(staged_files, additions, deletions)
135
135
 
136
136
  if as_json:
gitwise/summarize.py CHANGED
@@ -16,6 +16,7 @@ from .output import (
16
16
  print_file_status,
17
17
  print_header,
18
18
  print_json,
19
+ status,
19
20
  warn,
20
21
  )
21
22
 
@@ -98,24 +99,25 @@ def run_summarize(*, as_json: bool = False, diff: bool = False, max_commits: int
98
99
  return 1
99
100
  cwd = root
100
101
 
101
- branch_r = git_run(["branch", "--show-current"], cwd=cwd, check=False)
102
- branch = branch_r.stdout.strip() if branch_r.returncode == 0 else "detached-HEAD"
102
+ with status(t("status_summarizing")):
103
+ branch_r = git_run(["branch", "--show-current"], cwd=cwd, check=False)
104
+ branch = branch_r.stdout.strip() if branch_r.returncode == 0 else "detached-HEAD"
103
105
 
104
- status_r = git_run(["status", "--short"], cwd=cwd, check=False)
105
- status_lines = status_r.stdout.splitlines() if status_r.returncode == 0 else []
106
+ status_r = git_run(["status", "--short"], cwd=cwd, check=False)
107
+ status_lines = status_r.stdout.splitlines() if status_r.returncode == 0 else []
106
108
 
107
- log_r = git_run(
108
- ["--no-pager", "log", "--oneline", f"-n{max_commits}"],
109
- cwd=cwd,
110
- check=False,
111
- )
112
- log_lines = log_r.stdout.splitlines() if log_r.returncode == 0 else []
109
+ log_r = git_run(
110
+ ["--no-pager", "log", "--oneline", f"-n{max_commits}"],
111
+ cwd=cwd,
112
+ check=False,
113
+ )
114
+ log_lines = log_r.stdout.splitlines() if log_r.returncode == 0 else []
113
115
 
114
- shortstat_r = git_run(["--no-pager", "diff", "--shortstat"], cwd=cwd, check=False)
115
- shortstat = shortstat_r.stdout.strip() if shortstat_r.returncode == 0 else ""
116
+ shortstat_r = git_run(["--no-pager", "diff", "--shortstat"], cwd=cwd, check=False)
117
+ shortstat = shortstat_r.stdout.strip() if shortstat_r.returncode == 0 else ""
116
118
 
117
- changed_r = git_run(["--no-pager", "diff", "--name-status", "HEAD"], cwd=cwd, check=False)
118
- changed_files = changed_r.stdout.splitlines() if changed_r.returncode == 0 else []
119
+ changed_r = git_run(["--no-pager", "diff", "--name-status", "HEAD"], cwd=cwd, check=False)
120
+ changed_files = changed_r.stdout.splitlines() if changed_r.returncode == 0 else []
119
121
 
120
122
  status_entries = _parse_status_entries(status_lines)
121
123
  status_map = {entry["path"]: entry["status"] for entry in status_entries}
gitwise/tag.py CHANGED
@@ -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.json_envelope import error_envelope, ok_envelope
@@ -22,15 +23,16 @@ _SEMVER_RE = re.compile(r"^v?(\d+)\.(\d+)\.(\d+)(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]
22
23
 
23
24
 
24
25
  def _list_tags(root: Path) -> list[dict[str, str]]:
25
- r = git_run(
26
- [
27
- "for-each-ref",
28
- "--format=%(refname:short)\t%(objectname:short)\t%(creatordate:iso)",
29
- "refs/tags/",
30
- ],
31
- cwd=root,
32
- check=False,
33
- )
26
+ with status(t("status_reading_tags")):
27
+ r = git_run(
28
+ [
29
+ "for-each-ref",
30
+ "--format=%(refname:short)\t%(objectname:short)\t%(creatordate:iso-strict)",
31
+ "refs/tags/",
32
+ ],
33
+ cwd=root,
34
+ check=False,
35
+ )
34
36
  if r.returncode != 0 or not r.stdout.strip():
35
37
  return []
36
38
  tags: list[dict[str, str]] = []
gitwise/update.py CHANGED
@@ -2,9 +2,10 @@
2
2
 
3
3
  from pathlib import Path
4
4
 
5
+ from .git import current_branch, has_upstream
5
6
  from .git import run as git_run
6
7
  from .i18n import t
7
- from .output import error, info, print_dim, print_header, print_json
8
+ from .output import error, info, print_dim, print_header, print_json, status
8
9
  from .utils.json_envelope import error_envelope, ok_envelope
9
10
 
10
11
 
@@ -22,8 +23,19 @@ def run_update(*, dry_run: bool = False, as_json: bool = False) -> int:
22
23
  return 0
23
24
  print_dim(t("update_dry_run", dir=str(install_dir)))
24
25
  return 0
26
+ if not has_upstream(install_dir):
27
+ branch = current_branch(install_dir) or "HEAD"
28
+ msg = t("update_no_upstream", branch=branch)
29
+ hint = t("update_no_upstream_hint", branch=branch)
30
+ if as_json:
31
+ print_json(error_envelope(error=msg, code="no_upstream", hint=hint))
32
+ else:
33
+ error(msg, hint=hint)
34
+ return 1
35
+
25
36
  print_header(t("updating_from", dir=str(install_dir)))
26
- r = git_run(["pull", "--ff-only"], cwd=install_dir, check=False)
37
+ with status(t("status_updating")):
38
+ r = git_run(["pull", "--ff-only"], cwd=install_dir, check=False)
27
39
  if r.returncode == 0 and r.stdout.strip() and r.stdout.strip() != "Already up to date.":
28
40
  if as_json:
29
41
  print_json(ok_envelope(updated=True, output=r.stdout.strip()))
@@ -1,5 +1,29 @@
1
1
  """Reusable parsers for git command output formats."""
2
2
 
3
+ _STATUS_LABELS = {
4
+ "M": "modified",
5
+ "A": "added",
6
+ "D": "deleted",
7
+ "R": "renamed",
8
+ "C": "copied",
9
+ "T": "type_changed",
10
+ "U": "conflicted",
11
+ "?": "untracked",
12
+ "!": "ignored",
13
+ }
14
+
15
+ _CONFLICT_CODES = frozenset({"DD", "AU", "UD", "UA", "DU", "AA", "UU"})
16
+
17
+
18
+ def status_label(code: str) -> str:
19
+ """Map a raw git status code (M, ??, UU, R100, ...) to a stable label for agents."""
20
+ normalized = code.strip().upper()
21
+ if not normalized:
22
+ return "unknown"
23
+ if normalized in _CONFLICT_CODES:
24
+ return "conflicted"
25
+ return _STATUS_LABELS.get(normalized[0], "unknown")
26
+
3
27
 
4
28
  def parse_diffstat_entries(raw: str, *, default_status: str | None = None) -> list[dict[str, str]]:
5
29
  entries: list[dict[str, str]] = []
gitwise/worktree.py CHANGED
@@ -14,6 +14,7 @@ from .output import (
14
14
  print_dim,
15
15
  print_header,
16
16
  print_json,
17
+ status,
17
18
  )
18
19
  from .utils.json_envelope import ok_envelope, passthrough_envelope
19
20
 
@@ -73,10 +74,13 @@ def _worktree_create(branch: str, root: Path) -> tuple[int, str | None, dict]:
73
74
  == 0
74
75
  )
75
76
 
76
- if branch_exists:
77
- r = git_run(["worktree", "add", "--", str(wt_path), branch], cwd=root, check=False)
78
- else:
79
- r = git_run(["worktree", "add", "-b", branch, "--", str(wt_path)], cwd=root, check=False)
77
+ with status(t("status_worktree_add", branch=branch)):
78
+ if branch_exists:
79
+ r = git_run(["worktree", "add", "--", str(wt_path), branch], cwd=root, check=False)
80
+ else:
81
+ r = git_run(
82
+ ["worktree", "add", "-b", branch, "--", str(wt_path)], cwd=root, check=False
83
+ )
80
84
 
81
85
  if r.returncode != 0:
82
86
  return 1, None, {"ok": False, "error": r.stderr.strip()}
@@ -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,45 +1,45 @@
1
- gitwise/__init__.py,sha256=EQSJ5WQ_IAB7iTOoK5-l-JbxMSiHJCjMzGaA9gW00QE,267
2
- gitwise/__main__.py,sha256=3cDMMDJlua0qyW_nGj8w2YigbPy4QdrM3weMkQp5Qcs,4309
1
+ gitwise/__init__.py,sha256=DDSLYZsVEGkP_c_KhYWm4Tw-aKi4-OYzd2kUw6s8ZTY,267
2
+ gitwise/__main__.py,sha256=FqdvhDqIEA1PJQqWJExu3MsyFquWqAhMN-9l6wOsOOI,4353
3
3
  gitwise/_cli_completions.py,sha256=85-aF5I-SFFehbSDvrj22eFb3DYiHiNxpwr6GxW0B4Q,3091
4
4
  gitwise/_cli_dispatch.py,sha256=x75GaHWYS_Hcem0XvKVL1cB-dS0Q_d6_u3k00KZk6GU,12150
5
5
  gitwise/_cli_introspection.py,sha256=wlRMcRrGpitUGs5Sm1g-Eyf0avLeHpYhsBs48PCC2eI,9112
6
6
  gitwise/_cli_parser.py,sha256=eN-LCksh-Fhp4UUyDTLaHJlC7YviFPcf4PamRQLTrdM,13702
7
7
  gitwise/_cli_setup_agents.py,sha256=HC65Y3lzSvzKOz5Rdl9zMOpM5Rc-HS0YU7q8re09RBg,14926
8
- gitwise/_i18n_data.json,sha256=muH3yzVlGp8FUgq5f5JCqtL2lqSofYR86TYKyX3qGw4,61600
8
+ gitwise/_i18n_data.json,sha256=2O2IYdqvAGAgWy3q67WlqLn3_p2BkTHuR3BgiTeqr-E,64143
9
9
  gitwise/_paths.py,sha256=VRkql9HQ5_OjwbpcX-18fwe_lMpuQffP4AiBV6suSFg,658
10
10
  gitwise/_runtime_config.py,sha256=G3MqJwPoKWMak3dZkPHXqCeJPph2_9KBEomQ5YT2RHI,7176
11
11
  gitwise/audit.py,sha256=mdovDdJ2PQ4dSTBBNujA0KrYzdqZaOQZt6GrbSW7vfA,10507
12
- gitwise/branches.py,sha256=6YBZh3QkNPSxPEHoWxsnO0nR17zkD8y9LhPia60gBCk,5627
13
- gitwise/clean.py,sha256=HdoMTzY2GHiSmyjQnwA6FmItfdCYA_7wfQ8WA1zuTHA,5637
12
+ gitwise/branches.py,sha256=qleh7ZuT_XxO1KoEDjCX-thlXTYZeiZg9vsWFOMdZ7k,6006
13
+ gitwise/clean.py,sha256=JubEZV5P43INKRJnXTTtoLjKoVZab0Z4Ph3OYZgctj4,5698
14
14
  gitwise/commit.py,sha256=9CUrogVrDtlTGXINIC67oab0Jyl_Bnn5X-KLbQ8c5uA,3926
15
- gitwise/conflicts.py,sha256=LKnPLvhCdkaNv9QCBywpQTvInLboNsug3upi7XK-4uo,3620
16
- gitwise/context.py,sha256=rxeZNso-BfqTpYTMomGzx2Q85nojJpoHfMk3CYmV2Vw,5454
15
+ gitwise/conflicts.py,sha256=v3yfbDMHn62m8EUjRrWzDcT-EH0jiNba2gmqFVsnpWQ,3719
16
+ gitwise/context.py,sha256=uC7nwY5phJwTeYQIhxpqDS_RWhvp01oFhKjT-oM4hF8,5525
17
17
  gitwise/design.py,sha256=lr7R6PaUTOGNyY8bPHU-soG8FV0Zsf3cBkRZkyUWuBk,11156
18
- gitwise/diff.py,sha256=jXG02qu4pNjae9scGnrs27cNpz1BEYB8hLTvrXuYxW4,8843
19
- gitwise/doctor.py,sha256=z6EpxrBbdXtb1kMiCT1qPBiqwZCrmXjkik0gnUvHp-g,3848
20
- gitwise/git.py,sha256=2eC1H_NS_kgriPKiBfWGXPEQwajvciVN4IkMP2X0Z2Y,7535
21
- gitwise/health.py,sha256=02PFdcP0Mb67M4fVdBI7rEYbknVMHKUdHisTQYjE-Uw,9562
18
+ gitwise/diff.py,sha256=b7o7YgT9TMOPfqdvKYnLSGaqBBmdaekq_jmvLNN4Nb4,9278
19
+ gitwise/doctor.py,sha256=Gq0q3YcFVEsHqiG3fxoIwrrDI7BCYs1DoLtDDWNi1OM,3935
20
+ gitwise/git.py,sha256=dgQhAggiy8-jbP6GX1aeQOGxZWMYaQDZg2i86VRklXI,7570
21
+ gitwise/health.py,sha256=y8abwy_tEbv6fhdKU5iVR9yMOupsWj1bodSDfC9eyaI,9616
22
22
  gitwise/i18n.py,sha256=9tEI-xs6lUdku3NnqMGwtM7Ck6AlUSm7nrPd95yzXcg,2448
23
- gitwise/log.py,sha256=gaPTDW12ZVDR1dPSdJbCx7d9hhyg5HPsUs85WC4ErHo,9149
23
+ gitwise/log.py,sha256=e74rOZtSTQe8ea9e1fyygjQPXUuNFUSamPNy7S9pvAs,9210
24
24
  gitwise/merge.py,sha256=DHqxqRmfToOu-j8qo2DP16xtist4e4-tJhozHalIQh0,5711
25
25
  gitwise/optimize.py,sha256=Eqka6u2s-eSMKfkhWynAYlEwjjAL4rOwEHoPikxXew8,5955
26
- gitwise/output.py,sha256=fuyu2L0K8-l6A4NHSZGt-SLS5Km6_bFKoUNM7V9T5m8,18668
26
+ gitwise/output.py,sha256=bD8Yjp4tW-D7hw0u3upYqzJIGc3k70gCS5_Au7foosE,18827
27
27
  gitwise/pick.py,sha256=2JNkcaL4D7z0dRfFyoDRQsa7_2vGtkBV22MMxHFIYG4,2985
28
- gitwise/pr.py,sha256=Kbv8WhC3esRS3lI34HU9oFNbDqpTQNJn3Yl2CvAw8TM,16730
28
+ gitwise/pr.py,sha256=zTc_T3WtZN6o5ScbGyFs1mm1JqUG7AgwCBtCqUePSMs,16820
29
29
  gitwise/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
30
  gitwise/schema.py,sha256=IWPnx8Yv6ubt8E57AHz94hbKu0Jnwxm2zbPigxQZSbk,1418
31
31
  gitwise/setup.py,sha256=CbOc8wyRytLQza-v1XkVr0bV29Es6UbDWrh2lQDUfSs,16625
32
- gitwise/show.py,sha256=QY_4-sJjS_Q6rInzkalDXiZIHw2Sw7ir0zNQJ3S6RKs,3729
33
- gitwise/snapshot.py,sha256=sXzWlkjjhuHokD3wNjNkC5QJus0kr09_qf4ftHVRJ_8,3415
34
- gitwise/stash.py,sha256=dGyurYTVYB5I1KBLvVR-WAAHSD8oqyYp9HlchSPTvws,5325
35
- gitwise/status.py,sha256=Th13IFi9z35muy8avzmbMb38ZaenVstKAoLpivXW3nk,2792
36
- gitwise/suggest.py,sha256=5abkVvFnIISRToSt4jP6qHh6Fg8G_K3fo10G--EuRB0,4484
37
- gitwise/summarize.py,sha256=qgR4teozwtS3AOI1rc24QUtkoeYJvXPagXlKBjtHtH8,6600
32
+ gitwise/show.py,sha256=gFjYbUY560HfSAlACDwyx7j7ST_XRfxe-BnwompPqWw,3851
33
+ gitwise/snapshot.py,sha256=N1TVA27YqJKJBg-a37pZK0U1nDIB07IWG6sj6aa8JfA,3470
34
+ gitwise/stash.py,sha256=J6LvNCXwJvovL3CH8JEYxqajQAb2j33P42D3drXa_e8,5437
35
+ gitwise/status.py,sha256=Fbfr5-GUHlZi42h0Bm0lMQUPwiTsXYGld6pfHaLscnk,4051
36
+ gitwise/suggest.py,sha256=FoTJFJpQvCDIkzqHIeLJ2Ik9e2hm2QNyFzNLM9Cn42o,4582
37
+ gitwise/summarize.py,sha256=KIlfG1mqOj71jGY7VMaA0GIlzwhQjAtX6aNDUR2niHo,6710
38
38
  gitwise/sync.py,sha256=kpnToYTdwRvdh6NDx-ZCgpqU2Ayz7_uKnudJYAxIV_o,7420
39
- gitwise/tag.py,sha256=af6hEFHlngWh5XA2Gxd0sIV_VVvO_NdJToWL3jJ1tgw,6397
39
+ gitwise/tag.py,sha256=X2AWglsLfFb4chYxg5YNrqmoZmPMbwo8bDBS7X3GgVQ,6495
40
40
  gitwise/undo.py,sha256=MoeeFVETDJNtREx_tR6j8yWYnyYHH3n1EmVUuHjJNOg,3988
41
- gitwise/update.py,sha256=4GuUFzNxQEsVqpKngJ59Md0zodTmDKHNl8xtNl6R4pE,1581
42
- gitwise/worktree.py,sha256=yRUB5QUMpntarsqpXJJD0Zha701Vim4DC1daHZ_1h3I,5640
41
+ gitwise/update.py,sha256=DOnMh3YwYYPiUBK0AHKMO5UrE4DXjADZF-uibQQm2n8,2050
42
+ gitwise/worktree.py,sha256=pMPGq0J7TJ6SwwMxuH39_3CGg3XQHntcqEMqfmS2xm4,5756
43
43
  gitwise/setup_agents/__init__.py,sha256=P5IrjTxvT8kZUNNPqIyN9rMvlcNuyckFRyL02ZFuCic,1001
44
44
  gitwise/setup_agents/exec.py,sha256=ziunMi10dicfpd7Tc5Npp-uWyKcLtq3RBwakchwdRLk,16205
45
45
  gitwise/setup_agents/format.py,sha256=j1oLr6pw8_1H03sftFAl1UZP8mEUNRTuDE-iBKexLX8,4634
@@ -66,7 +66,7 @@ gitwise/setup_agents/providers/cursor.py,sha256=bz2_AboPIFc1gkvsEon6JP659ksgtFiW
66
66
  gitwise/setup_agents/providers/opencode.py,sha256=ZfA18EP9Pcqn-wStBcBAmFwHcu7PO_mJ68ErztaKZXM,330
67
67
  gitwise/setup_agents/providers/pi.py,sha256=-sj_Ufa25OhBbhZ6P3QlKBTLcIVRdKrgCZ6I8NYJ4OM,312
68
68
  gitwise/utils/__init__.py,sha256=sW1hu0zxDj57_wkxhxLce2ZRGgHPtn3ey0IBeIw69Xg,42
69
- gitwise/utils/git_output.py,sha256=KJOTuB_ce5fHWvkwRYoYQcXsCU2wKABt1YT2eP02cnc,1741
69
+ gitwise/utils/git_output.py,sha256=35aYi1duChO9TWR16cP5Zy517dkMiDCcmgj18o3agTA,2359
70
70
  gitwise/utils/json_envelope.py,sha256=3lQF2_CxJtqYsarGwBa49d8ycF7-Eq2mvRZqe28yVz0,1320
71
71
  gitwise/utils/parsing.py,sha256=2rm-b6Hd1QqA0WlPVzFburFIP8QWMPhhHpr0mhX59R0,939
72
72
  gitwise/share/git-config-modern.txt,sha256=CwIu4xVZuGrujWioHq6yjmjfzPNFgSfWHXtL0EmREIs,875
@@ -118,8 +118,8 @@ gitwise/share/schemas/v1/input/tag.json,sha256=KmrCyqcJsJU_uDhNba_KYpAkYg7vIlQac
118
118
  gitwise/share/schemas/v1/input/undo.json,sha256=PRab96r2e1iU2FN8yBV_njgly_-7lm7Vy4KcMh3NMQ4,1406
119
119
  gitwise/share/schemas/v1/input/update.json,sha256=OmuSgSp_USg2f45mywnSpxUB0NMSn_lQDLC4yDzCopo,911
120
120
  gitwise/share/schemas/v1/input/worktree.json,sha256=rKKOtWyAgARIa-b-Raw7ZNPqKkO7DlPd7PE1c0flRDs,1063
121
- gitwise_cli-0.27.0.dist-info/METADATA,sha256=LQ05aRRUBgHYiK4xG48KvMqstjK6dyMmwg7EkjKJ1LU,7092
122
- gitwise_cli-0.27.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
123
- gitwise_cli-0.27.0.dist-info/entry_points.txt,sha256=rGwxSDmUHtzlY6yp7KeMJhRARijXkSE6OKuOJ8PF51E,50
124
- gitwise_cli-0.27.0.dist-info/licenses/LICENSE,sha256=vfJO-ThMtWhZOD9MsArN2yul1EJmxAXxfeGHKnEk9QQ,1063
125
- gitwise_cli-0.27.0.dist-info/RECORD,,
121
+ gitwise_cli-0.28.0.dist-info/METADATA,sha256=b0SpSOz_IlGs4oKqT1TUN9_9d_RgQghP6A_tYM0R9zI,7092
122
+ gitwise_cli-0.28.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
123
+ gitwise_cli-0.28.0.dist-info/entry_points.txt,sha256=rGwxSDmUHtzlY6yp7KeMJhRARijXkSE6OKuOJ8PF51E,50
124
+ gitwise_cli-0.28.0.dist-info/licenses/LICENSE,sha256=vfJO-ThMtWhZOD9MsArN2yul1EJmxAXxfeGHKnEk9QQ,1063
125
+ gitwise_cli-0.28.0.dist-info/RECORD,,