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 +1 -1
- gitwise/__main__.py +2 -1
- gitwise/_i18n_data.json +80 -0
- gitwise/branches.py +46 -27
- gitwise/clean.py +3 -1
- gitwise/conflicts.py +12 -2
- gitwise/context.py +7 -6
- gitwise/diff.py +15 -3
- gitwise/doctor.py +10 -8
- gitwise/git.py +1 -1
- gitwise/health.py +3 -2
- gitwise/log.py +4 -3
- gitwise/output.py +10 -0
- gitwise/pr.py +10 -8
- gitwise/show.py +5 -3
- gitwise/snapshot.py +3 -2
- gitwise/stash.py +5 -2
- gitwise/status.py +47 -17
- gitwise/suggest.py +4 -4
- gitwise/summarize.py +16 -14
- gitwise/tag.py +11 -9
- gitwise/update.py +14 -2
- gitwise/utils/git_output.py +24 -0
- gitwise/worktree.py +8 -4
- {gitwise_cli-0.27.0.dist-info → gitwise_cli-0.28.0.dist-info}/METADATA +1 -1
- {gitwise_cli-0.27.0.dist-info → gitwise_cli-0.28.0.dist-info}/RECORD +29 -29
- {gitwise_cli-0.27.0.dist-info → gitwise_cli-0.28.0.dist-info}/WHEEL +0 -0
- {gitwise_cli-0.27.0.dist-info → gitwise_cli-0.28.0.dist-info}/entry_points.txt +0 -0
- {gitwise_cli-0.27.0.dist-info → gitwise_cli-0.28.0.dist-info}/licenses/LICENSE +0 -0
gitwise/__init__.py
CHANGED
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
|
-
|
|
11
|
-
|
|
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":
|
|
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":
|
|
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[
|
|
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[
|
|
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
|
|
122
|
+
age = branch_item["age"]
|
|
106
123
|
flags: list[str] = []
|
|
107
|
-
if branch_item
|
|
124
|
+
if branch_item["ahead"]:
|
|
108
125
|
flags.append(f"↑{branch_item['ahead']}")
|
|
109
|
-
if branch_item
|
|
126
|
+
if branch_item["behind"]:
|
|
110
127
|
flags.append(f"↓{branch_item['behind']}")
|
|
111
|
-
if branch_item
|
|
128
|
+
if branch_item["in_worktree"]:
|
|
112
129
|
flags.append("wt")
|
|
113
|
-
if branch_item
|
|
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"]
|
|
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"]
|
|
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[
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
37
|
+
with status(t("status_checking_env")):
|
|
38
|
+
git_ver = git_version()
|
|
39
|
+
git_ok = git_ver >= MIN_GIT
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
python_ver = sys.version_info[:3]
|
|
42
|
+
python_ok = python_ver >= (3, 9)
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
platform_name = platform.system()
|
|
45
|
+
fsmonitor_supported = platform_name in ("Darwin", "Windows")
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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":
|
|
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
|
-
|
|
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
|
-
|
|
102
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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()))
|
gitwise/utils/git_output.py
CHANGED
|
@@ -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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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.
|
|
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=
|
|
2
|
-
gitwise/__main__.py,sha256=
|
|
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=
|
|
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=
|
|
13
|
-
gitwise/clean.py,sha256=
|
|
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=
|
|
16
|
-
gitwise/context.py,sha256=
|
|
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=
|
|
19
|
-
gitwise/doctor.py,sha256=
|
|
20
|
-
gitwise/git.py,sha256=
|
|
21
|
-
gitwise/health.py,sha256=
|
|
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=
|
|
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=
|
|
26
|
+
gitwise/output.py,sha256=bD8Yjp4tW-D7hw0u3upYqzJIGc3k70gCS5_Au7foosE,18827
|
|
27
27
|
gitwise/pick.py,sha256=2JNkcaL4D7z0dRfFyoDRQsa7_2vGtkBV22MMxHFIYG4,2985
|
|
28
|
-
gitwise/pr.py,sha256=
|
|
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=
|
|
33
|
-
gitwise/snapshot.py,sha256=
|
|
34
|
-
gitwise/stash.py,sha256=
|
|
35
|
-
gitwise/status.py,sha256=
|
|
36
|
-
gitwise/suggest.py,sha256=
|
|
37
|
-
gitwise/summarize.py,sha256=
|
|
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=
|
|
39
|
+
gitwise/tag.py,sha256=X2AWglsLfFb4chYxg5YNrqmoZmPMbwo8bDBS7X3GgVQ,6495
|
|
40
40
|
gitwise/undo.py,sha256=MoeeFVETDJNtREx_tR6j8yWYnyYHH3n1EmVUuHjJNOg,3988
|
|
41
|
-
gitwise/update.py,sha256=
|
|
42
|
-
gitwise/worktree.py,sha256=
|
|
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=
|
|
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.
|
|
122
|
-
gitwise_cli-0.
|
|
123
|
-
gitwise_cli-0.
|
|
124
|
-
gitwise_cli-0.
|
|
125
|
-
gitwise_cli-0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|