gitwise-cli 0.27.0__py3-none-any.whl → 0.29.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.29.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/_cli_dispatch.py CHANGED
@@ -255,6 +255,8 @@ def _run_merge(args: argparse.Namespace) -> int:
255
255
  no_ff=args.no_ff,
256
256
  dry_run=args.dry_run,
257
257
  yes=args.yes,
258
+ abort=args.abort,
259
+ continue_merge=args.continue_merge,
258
260
  as_json=args.json,
259
261
  )
260
262
 
gitwise/_cli_parser.py CHANGED
@@ -269,11 +269,20 @@ def build_parser() -> argparse.ArgumentParser:
269
269
  p.add_argument("--yes", "-y", action="store_true", help="skip confirmation")
270
270
 
271
271
  p = sub.add_parser("merge", help="merge/rebase with pre-flight checks", parents=[parent])
272
- p.add_argument("branch", help="branch to merge/rebase from")
272
+ p.add_argument(
273
+ "branch", nargs="?", help="branch to merge/rebase from (omit with --abort/--continue)"
274
+ )
273
275
  p.add_argument("--rebase", action="store_true", help="rebase instead of merge")
274
276
  p.add_argument("--no-ff", action="store_true", help="force no-fast-forward")
275
277
  p.add_argument("--dry-run", action="store_true", help="show checks without merging")
276
278
  p.add_argument("--yes", "-y", action="store_true", help="skip confirmation")
279
+ p.add_argument("--abort", action="store_true", help="abort an in-progress merge or rebase")
280
+ p.add_argument(
281
+ "--continue",
282
+ dest="continue_merge",
283
+ action="store_true",
284
+ help="continue an in-progress merge or rebase after resolving conflicts",
285
+ )
277
286
 
278
287
  p = sub.add_parser(
279
288
  "conflicts", help="conflict detection and resolution helper", parents=[parent]
gitwise/_i18n_data.json CHANGED
@@ -251,6 +251,10 @@
251
251
  "es": "mensaje",
252
252
  "en": "msg"
253
253
  },
254
+ "commit_blocked_in_progress": {
255
+ "es": "No se puede commitear: hay una operación {state} en curso. Resuélvela o aborta primero.",
256
+ "en": "Cannot commit: a {state} operation is in progress. Resolve or abort it first."
257
+ },
254
258
  "commit_no_message": {
255
259
  "es": "usa -m para proveer un mensaje de commit",
256
260
  "en": "use -m to provide a commit message"
@@ -847,6 +851,26 @@
847
851
  "es": "merge",
848
852
  "en": "merge"
849
853
  },
854
+ "merge_aborted": {
855
+ "es": "Mezcla abortada",
856
+ "en": "Merge aborted"
857
+ },
858
+ "merge_abort_continue_mutually_exclusive": {
859
+ "es": "--abort y --continue son mutuamente excluyentes",
860
+ "en": "--abort and --continue are mutually exclusive"
861
+ },
862
+ "merge_branch_required": {
863
+ "es": "Especifica la rama a mezclar, o usa --abort/--continue para una operación pausada",
864
+ "en": "Specify a branch to merge, or use --abort/--continue for a paused operation"
865
+ },
866
+ "merge_continued": {
867
+ "es": "Mezcla continuada",
868
+ "en": "Merge continued"
869
+ },
870
+ "merge_no_in_progress": {
871
+ "es": "No hay mezcla/rebase en curso (estado actual: {state}). Usa {action} solo cuando gitwise detecta operación pausada.",
872
+ "en": "No merge/rebase in progress (current state: {state}). Use {action} only when gitwise detects a paused operation."
873
+ },
850
874
  "merge_ok": {
851
875
  "es": "Mezcla completada: {branch} → {into}",
852
876
  "en": "Merge completed: {branch} → {into}"
@@ -1571,6 +1595,78 @@
1571
1595
  "es": "Auditando: ejecutando git-sizer...",
1572
1596
  "en": "Auditing: running git-sizer..."
1573
1597
  },
1598
+ "status_worktree_add": {
1599
+ "es": "Creando worktree para {branch}...",
1600
+ "en": "Creating worktree for {branch}..."
1601
+ },
1602
+ "status_health_scan": {
1603
+ "es": "Calculando salud del repositorio...",
1604
+ "en": "Computing repository health..."
1605
+ },
1606
+ "status_context_scan": {
1607
+ "es": "Recolectando contexto del repositorio...",
1608
+ "en": "Gathering repository context..."
1609
+ },
1610
+ "status_snapshot_gen": {
1611
+ "es": "Generando snapshot...",
1612
+ "en": "Generating snapshot..."
1613
+ },
1614
+ "status_reading_status": {
1615
+ "es": "Leyendo estado del repositorio...",
1616
+ "en": "Reading repository status..."
1617
+ },
1618
+ "status_summarizing": {
1619
+ "es": "Resumiendo repositorio...",
1620
+ "en": "Summarizing repository..."
1621
+ },
1622
+ "status_reading_diff": {
1623
+ "es": "Calculando cambios...",
1624
+ "en": "Computing changes..."
1625
+ },
1626
+ "status_reading_log": {
1627
+ "es": "Leyendo historial de commits...",
1628
+ "en": "Reading commit history..."
1629
+ },
1630
+ "status_loading_commit": {
1631
+ "es": "Cargando commit...",
1632
+ "en": "Loading commit..."
1633
+ },
1634
+ "status_analyzing_branches": {
1635
+ "es": "Analizando ramas...",
1636
+ "en": "Analyzing branches..."
1637
+ },
1638
+ "status_analyzing_staged": {
1639
+ "es": "Analizando cambios preparados...",
1640
+ "en": "Analyzing staged changes..."
1641
+ },
1642
+ "status_detecting_conflicts": {
1643
+ "es": "Detectando conflictos...",
1644
+ "en": "Detecting conflicts..."
1645
+ },
1646
+ "status_reading_stashes": {
1647
+ "es": "Leyendo stashes...",
1648
+ "en": "Reading stashes..."
1649
+ },
1650
+ "status_reading_tags": {
1651
+ "es": "Leyendo tags...",
1652
+ "en": "Reading tags..."
1653
+ },
1654
+ "status_querying_github": {
1655
+ "es": "Consultando GitHub...",
1656
+ "en": "Querying GitHub..."
1657
+ },
1658
+ "status_checking_env": {
1659
+ "es": "Verificando entorno...",
1660
+ "en": "Checking environment..."
1661
+ },
1662
+ "status_scanning_stale": {
1663
+ "es": "Buscando ramas y refs obsoletos...",
1664
+ "en": "Scanning stale branches and refs..."
1665
+ },
1666
+ "status_updating": {
1667
+ "es": "Actualizando gitwise...",
1668
+ "en": "Updating gitwise..."
1669
+ },
1574
1670
  "step_commit_graph": {
1575
1671
  "es": "git commit-graph write --reachable --changed-paths",
1576
1672
  "en": "git commit-graph write --reachable --changed-paths"
@@ -1595,6 +1691,10 @@
1595
1691
  "es": "Sugerencia: {message}",
1596
1692
  "en": "Suggestion: {message}"
1597
1693
  },
1694
+ "suggest_blocked_in_progress": {
1695
+ "es": "No se puede sugerir commit: hay una operación {state} en curso. Resuélvela o aborta primero.",
1696
+ "en": "Cannot suggest commit: a {state} operation is in progress. Resolve or abort it first."
1697
+ },
1598
1698
  "suggest_no_staged": {
1599
1699
  "es": "No hay archivos en área de preparación",
1600
1700
  "en": "No staged files"
@@ -1927,6 +2027,14 @@
1927
2027
  "es": "update requiere una instalación vía git clone (no se encontró .git/)",
1928
2028
  "en": "update requires a git clone installation (.git/ not found)"
1929
2029
  },
2030
+ "update_no_upstream": {
2031
+ "es": "la rama actual '{branch}' no tiene rama de seguimiento; update solo puede actualizar una rama con upstream",
2032
+ "en": "current branch '{branch}' has no upstream; update can only fast-forward a tracking branch"
2033
+ },
2034
+ "update_no_upstream_hint": {
2035
+ "es": "cambia a tu rama principal (p.ej. 'git switch main') o configura el upstream con 'git branch --set-upstream-to=origin/{branch}'",
2036
+ "en": "switch to your main branch (e.g. 'git switch main') or set an upstream with 'git branch --set-upstream-to=origin/{branch}'"
2037
+ },
1930
2038
  "invalid_grep_pattern": {
1931
2039
  "es": "patrón de búsqueda inválido o peligroso: {pattern}",
1932
2040
  "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/commit.py CHANGED
@@ -7,6 +7,7 @@ from .git import PROTECTED_BRANCHES, current_branch, gpg_status, require_root
7
7
  from .git import run as git_run
8
8
  from .i18n import t
9
9
  from .output import error, print_bracket, print_header, print_json
10
+ from .utils.in_progress import detect_in_progress, in_progress_hint
10
11
  from .utils.json_envelope import error_envelope, ok_envelope
11
12
 
12
13
  _CONVENTIONAL_RE = re.compile(
@@ -104,12 +105,34 @@ def run_commit(
104
105
  dry_run: bool = False,
105
106
  as_json: bool = False,
106
107
  ) -> int:
108
+ """Validate a conventional-commit message and create a GPG-signed commit.
109
+
110
+ Refuses with ``in_progress_<state>`` if an operation is paused. Enforces
111
+ the project's amend policy (no amending pushed or protected branches)
112
+ and GPG readiness before delegating to ``git commit``.
113
+ """
107
114
  root, err = require_root()
108
115
  if err:
109
116
  return err
110
117
  if root is None:
111
118
  return 1
112
119
 
120
+ in_progress = detect_in_progress(root)
121
+ if in_progress["state"] != "none":
122
+ hint = in_progress_hint(in_progress["state"])
123
+ blocked_msg = t("commit_blocked_in_progress", state=in_progress["state"])
124
+ if as_json:
125
+ print_json(
126
+ error_envelope(
127
+ error=blocked_msg,
128
+ code=f"in_progress_{in_progress['state']}",
129
+ hint=hint,
130
+ )
131
+ )
132
+ return 1
133
+ error(blocked_msg, hint=hint)
134
+ return 1
135
+
113
136
  amend_policy_rc = _validate_amend_policy(amend=amend, root=root)
114
137
  if amend_policy_rc != 0:
115
138
  return amend_policy_rc
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"))