git-muster 0.4.0__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {git_muster-0.4.0 → git_muster-0.5.0}/PKG-INFO +6 -6
- {git_muster-0.4.0 → git_muster-0.5.0}/README.md +4 -4
- {git_muster-0.4.0 → git_muster-0.5.0}/pyproject.toml +2 -2
- {git_muster-0.4.0 → git_muster-0.5.0}/pyproject.toml.orig +2 -2
- {git_muster-0.4.0 → git_muster-0.5.0}/src/git_muster/cli.py +127 -4
- {git_muster-0.4.0 → git_muster-0.5.0}/LICENSE +0 -0
- {git_muster-0.4.0 → git_muster-0.5.0}/src/git_muster/__init__.py +0 -0
- {git_muster-0.4.0 → git_muster-0.5.0}/src/git_muster/__main__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-muster
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: See every local Git branch and what needs attention
|
|
5
5
|
Keywords: branch,cli,git,github,worktree
|
|
6
6
|
Author: Dan O'Leary
|
|
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.14
|
|
15
15
|
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
16
|
-
Requires-Dist: rich>=14,<
|
|
16
|
+
Requires-Dist: rich>=14,<16
|
|
17
17
|
Requires-Dist: typer>=0.27,<0.28
|
|
18
18
|
Requires-Python: >=3.13, <3.15
|
|
19
19
|
Project-URL: Homepage, https://github.com/olearydj/git-muster
|
|
@@ -57,7 +57,7 @@ BRANCH (4 local) REMOTE STATE WORKTREE UPDATED PULL REQUES
|
|
|
57
57
|
- Conditional linked-worktree names and checkout paths.
|
|
58
58
|
- Draft, open, approved, changes-requested, merged, and closed GitHub pull requests.
|
|
59
59
|
- Clickable, underlined PR numbers in supported interactive terminals.
|
|
60
|
-
- Responsive terminal layout plus stable
|
|
60
|
+
- Responsive terminal layout plus stable plain output with ASCII interface glyphs for logs and pipes.
|
|
61
61
|
- Read-only operation with `--no-fetch`; no branch switching, deletion, merging, rebasing, or pushing.
|
|
62
62
|
|
|
63
63
|
## Requirements
|
|
@@ -123,7 +123,7 @@ git muster --version
|
|
|
123
123
|
|
|
124
124
|
### Color, symbols, and width
|
|
125
125
|
|
|
126
|
-
Color, Unicode symbols, and clickable pull-request numbers appear when the report is written to an interactive terminal that can render them. Git Muster honors the usual overrides:
|
|
126
|
+
Color, Unicode symbols, clickable repository identities, and clickable pull-request numbers appear when the report is written to an interactive terminal that can render them. GitHub repositories appear as `owner / repository`, with the owner and repository linked separately. The title includes `[public]`, `[private]`, or `[internal]` when GitHub reports visibility, `[fork]` when the repository is a fork, and `[archived]` when it is archived. A fork label links to its parent repository. Git Muster resolves the repository from `origin`, or another GitHub remote when `origin` is not hosted there, and honors the usual overrides:
|
|
127
127
|
|
|
128
128
|
| Variable | Effect |
|
|
129
129
|
|---|---|
|
|
@@ -131,7 +131,7 @@ Color, Unicode symbols, and clickable pull-request numbers appear when the repor
|
|
|
131
131
|
| `FORCE_COLOR`, `CLICOLOR_FORCE` | Keep color when output is redirected to a file or pipe. |
|
|
132
132
|
| `COLUMNS` | Overrides the detected terminal width used to fit columns. |
|
|
133
133
|
|
|
134
|
-
`--plain` overrides all of them and
|
|
134
|
+
`--plain` overrides all of them and uses ASCII interface glyphs without color or terminal hyperlinks. Repository-provided text, including branch, remote, and worktree names, is preserved and may contain Unicode. Git Muster also falls back to the ASCII glyph set on its own whenever the destination stream cannot encode its symbols, so its built-in decorations never cause an encoding failure.
|
|
135
135
|
|
|
136
136
|
## Reading the report
|
|
137
137
|
|
|
@@ -157,7 +157,7 @@ Publication is deliberately separate from Git's configured upstream. A branch is
|
|
|
157
157
|
|
|
158
158
|
When another linked worktree holds a branch, Git Muster shows its directory in the table and lists the full checkout path below it. It does not scan those other worktrees for dirtiness or change them.
|
|
159
159
|
|
|
160
|
-
GitHub integration uses one optional `gh pr list --state all --limit 100` query
|
|
160
|
+
GitHub integration uses one `gh repo view` query for canonical identity, visibility, fork parent, and archive state, plus one optional `gh pr list --state all --limit 100` query for pull requests. A branch whose pull request falls outside the hundred most recent is reported without pull-request state. When several pull requests share a head branch, the newest open, draft, approved, or changes-requested one wins over closed and merged ones. Without an installed and authenticated `gh`, or when no remote points at a known GitHub host, the complete Git report still works, falls back to identity parsed from the remote URL, omits metadata it cannot establish, and names the reason pull-request state is missing.
|
|
161
161
|
|
|
162
162
|
## Safety and scope
|
|
163
163
|
|
|
@@ -33,7 +33,7 @@ BRANCH (4 local) REMOTE STATE WORKTREE UPDATED PULL REQUES
|
|
|
33
33
|
- Conditional linked-worktree names and checkout paths.
|
|
34
34
|
- Draft, open, approved, changes-requested, merged, and closed GitHub pull requests.
|
|
35
35
|
- Clickable, underlined PR numbers in supported interactive terminals.
|
|
36
|
-
- Responsive terminal layout plus stable
|
|
36
|
+
- Responsive terminal layout plus stable plain output with ASCII interface glyphs for logs and pipes.
|
|
37
37
|
- Read-only operation with `--no-fetch`; no branch switching, deletion, merging, rebasing, or pushing.
|
|
38
38
|
|
|
39
39
|
## Requirements
|
|
@@ -99,7 +99,7 @@ git muster --version
|
|
|
99
99
|
|
|
100
100
|
### Color, symbols, and width
|
|
101
101
|
|
|
102
|
-
Color, Unicode symbols, and clickable pull-request numbers appear when the report is written to an interactive terminal that can render them. Git Muster honors the usual overrides:
|
|
102
|
+
Color, Unicode symbols, clickable repository identities, and clickable pull-request numbers appear when the report is written to an interactive terminal that can render them. GitHub repositories appear as `owner / repository`, with the owner and repository linked separately. The title includes `[public]`, `[private]`, or `[internal]` when GitHub reports visibility, `[fork]` when the repository is a fork, and `[archived]` when it is archived. A fork label links to its parent repository. Git Muster resolves the repository from `origin`, or another GitHub remote when `origin` is not hosted there, and honors the usual overrides:
|
|
103
103
|
|
|
104
104
|
| Variable | Effect |
|
|
105
105
|
|---|---|
|
|
@@ -107,7 +107,7 @@ Color, Unicode symbols, and clickable pull-request numbers appear when the repor
|
|
|
107
107
|
| `FORCE_COLOR`, `CLICOLOR_FORCE` | Keep color when output is redirected to a file or pipe. |
|
|
108
108
|
| `COLUMNS` | Overrides the detected terminal width used to fit columns. |
|
|
109
109
|
|
|
110
|
-
`--plain` overrides all of them and
|
|
110
|
+
`--plain` overrides all of them and uses ASCII interface glyphs without color or terminal hyperlinks. Repository-provided text, including branch, remote, and worktree names, is preserved and may contain Unicode. Git Muster also falls back to the ASCII glyph set on its own whenever the destination stream cannot encode its symbols, so its built-in decorations never cause an encoding failure.
|
|
111
111
|
|
|
112
112
|
## Reading the report
|
|
113
113
|
|
|
@@ -133,7 +133,7 @@ Publication is deliberately separate from Git's configured upstream. A branch is
|
|
|
133
133
|
|
|
134
134
|
When another linked worktree holds a branch, Git Muster shows its directory in the table and lists the full checkout path below it. It does not scan those other worktrees for dirtiness or change them.
|
|
135
135
|
|
|
136
|
-
GitHub integration uses one optional `gh pr list --state all --limit 100` query
|
|
136
|
+
GitHub integration uses one `gh repo view` query for canonical identity, visibility, fork parent, and archive state, plus one optional `gh pr list --state all --limit 100` query for pull requests. A branch whose pull request falls outside the hundred most recent is reported without pull-request state. When several pull requests share a head branch, the newest open, draft, approved, or changes-requested one wins over closed and merged ones. Without an installed and authenticated `gh`, or when no remote points at a known GitHub host, the complete Git report still works, falls back to identity parsed from the remote URL, omits metadata it cannot establish, and names the reason pull-request state is missing.
|
|
137
137
|
|
|
138
138
|
## Safety and scope
|
|
139
139
|
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "git-muster"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.0"
|
|
8
8
|
description = "See every local Git branch and what needs attention"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13,<3.15"
|
|
@@ -27,7 +27,7 @@ classifiers = [
|
|
|
27
27
|
"Topic :: Software Development :: Version Control :: Git",
|
|
28
28
|
]
|
|
29
29
|
dependencies = [
|
|
30
|
-
"rich>=14,<
|
|
30
|
+
"rich>=14,<16",
|
|
31
31
|
"typer>=0.27,<0.28",
|
|
32
32
|
]
|
|
33
33
|
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "git-muster"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.0"
|
|
8
8
|
description = "See every local Git branch and what needs attention"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13,<3.15"
|
|
@@ -21,7 +21,7 @@ classifiers = [
|
|
|
21
21
|
"Programming Language :: Python :: 3.14",
|
|
22
22
|
"Topic :: Software Development :: Version Control :: Git",
|
|
23
23
|
]
|
|
24
|
-
dependencies = ["rich>=14,<
|
|
24
|
+
dependencies = ["rich>=14,<16", "typer>=0.27,<0.28"]
|
|
25
25
|
|
|
26
26
|
[project.scripts]
|
|
27
27
|
git-muster = "git_muster.cli:entrypoint"
|
|
@@ -12,6 +12,7 @@ from collections.abc import Callable
|
|
|
12
12
|
from dataclasses import dataclass
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
from typing import Annotated, Any
|
|
15
|
+
from urllib.parse import urlsplit
|
|
15
16
|
|
|
16
17
|
import typer
|
|
17
18
|
from rich.console import Console
|
|
@@ -38,7 +39,7 @@ EPILOG = """
|
|
|
38
39
|
Use the remote-tracking data already on disk.
|
|
39
40
|
|
|
40
41
|
[cyan]git muster --plain[/cyan]
|
|
41
|
-
Emit stable ASCII
|
|
42
|
+
Emit stable output with ASCII decorations and no color for logs and pipes.
|
|
42
43
|
|
|
43
44
|
[bold]Linked worktrees[/bold]
|
|
44
45
|
|
|
@@ -81,6 +82,7 @@ ASCII_GLYPHS = {
|
|
|
81
82
|
|
|
82
83
|
PR_LIMIT = 100
|
|
83
84
|
PR_FIELDS = "headRefName,number,state,isDraft,reviewDecision,url"
|
|
85
|
+
REPOSITORY_FIELDS = "nameWithOwner,url,visibility,isFork,parent,isArchived"
|
|
84
86
|
ACTIVE_PR_STATES = {"draft", "open", "approved", "changes requested"}
|
|
85
87
|
|
|
86
88
|
PR_NOTICES: dict[str, tuple[str, ...]] = {
|
|
@@ -242,6 +244,19 @@ class PullRequest:
|
|
|
242
244
|
url: str = ""
|
|
243
245
|
|
|
244
246
|
|
|
247
|
+
@dataclass(frozen=True)
|
|
248
|
+
class GitHubRepository:
|
|
249
|
+
"""GitHub identity and presentation metadata for the current repository."""
|
|
250
|
+
|
|
251
|
+
owner: str
|
|
252
|
+
name: str
|
|
253
|
+
url: str
|
|
254
|
+
visibility: str = ""
|
|
255
|
+
is_fork: bool = False
|
|
256
|
+
parent_url: str = ""
|
|
257
|
+
is_archived: bool = False
|
|
258
|
+
|
|
259
|
+
|
|
245
260
|
@dataclass(frozen=True)
|
|
246
261
|
class Branch:
|
|
247
262
|
"""Collected status for one local branch."""
|
|
@@ -268,6 +283,90 @@ def hyperlink(text: str, url: str, *, enabled: bool) -> str:
|
|
|
268
283
|
return f"\033]8;;{url}\033\\{underlined}\033]8;;\033\\"
|
|
269
284
|
|
|
270
285
|
|
|
286
|
+
def github_repository_url(remote_url: str) -> str:
|
|
287
|
+
"""Turn a GitHub clone URL into its canonical HTTPS repository page."""
|
|
288
|
+
scp_match = re.fullmatch(r"(?:[^@/:]+@)?([^/:]+):(.+)", remote_url)
|
|
289
|
+
if scp_match and "://" not in remote_url:
|
|
290
|
+
host, path = scp_match.groups()
|
|
291
|
+
else:
|
|
292
|
+
parsed = urlsplit(remote_url)
|
|
293
|
+
if parsed.scheme not in {"git", "http", "https", "ssh"}:
|
|
294
|
+
return ""
|
|
295
|
+
host, path = parsed.hostname or "", parsed.path
|
|
296
|
+
|
|
297
|
+
if host.lower() != "github.com":
|
|
298
|
+
return ""
|
|
299
|
+
path = path.strip("/")
|
|
300
|
+
if path.endswith(".git"):
|
|
301
|
+
path = path[:-4]
|
|
302
|
+
if not re.fullmatch(r"[-A-Za-z0-9_.]+/[-A-Za-z0-9_.]+", path):
|
|
303
|
+
return ""
|
|
304
|
+
return f"https://github.com/{path}"
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def repository_github_url(remotes: set[str]) -> str:
|
|
308
|
+
"""Find the repository page, preferring the conventional origin remote."""
|
|
309
|
+
for remote in sorted(remotes, key=lambda name: (name != "origin", name)):
|
|
310
|
+
remote_url = command_output("git", "remote", "get-url", remote) or ""
|
|
311
|
+
if url := github_repository_url(remote_url):
|
|
312
|
+
return url
|
|
313
|
+
return ""
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def github_repository(repository_url: str) -> GitHubRepository:
|
|
317
|
+
"""Read repository metadata from GitHub, retaining URL-derived identity on failure."""
|
|
318
|
+
owner, name = urlsplit(repository_url).path.strip("/").split("/", 1)
|
|
319
|
+
fallback = GitHubRepository(owner, name, repository_url)
|
|
320
|
+
result = command_result("gh", "repo", "view", f"{owner}/{name}", "--json", REPOSITORY_FIELDS)
|
|
321
|
+
if result is None or result[0] != 0:
|
|
322
|
+
return fallback
|
|
323
|
+
try:
|
|
324
|
+
payload = json.loads(result[1])
|
|
325
|
+
except (json.JSONDecodeError, TypeError, ValueError):
|
|
326
|
+
return fallback
|
|
327
|
+
if not isinstance(payload, dict):
|
|
328
|
+
return fallback
|
|
329
|
+
|
|
330
|
+
canonical_url = github_repository_url(str(payload.get("url", ""))) or repository_url
|
|
331
|
+
canonical_name = payload.get("nameWithOwner")
|
|
332
|
+
if isinstance(canonical_name, str) and re.fullmatch(
|
|
333
|
+
r"[-A-Za-z0-9_.]+/[-A-Za-z0-9_.]+", canonical_name
|
|
334
|
+
):
|
|
335
|
+
owner, name = canonical_name.split("/", 1)
|
|
336
|
+
canonical_url = f"https://github.com/{canonical_name}"
|
|
337
|
+
else:
|
|
338
|
+
owner, name = urlsplit(canonical_url).path.strip("/").split("/", 1)
|
|
339
|
+
|
|
340
|
+
visibility = payload.get("visibility")
|
|
341
|
+
visibility = (
|
|
342
|
+
visibility.lower()
|
|
343
|
+
if isinstance(visibility, str) and visibility.upper() in {"PUBLIC", "PRIVATE", "INTERNAL"}
|
|
344
|
+
else ""
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
parent_url = ""
|
|
348
|
+
parent = payload.get("parent")
|
|
349
|
+
if isinstance(parent, dict):
|
|
350
|
+
parent_url = github_repository_url(str(parent.get("url", "")))
|
|
351
|
+
parent_name = parent.get("nameWithOwner")
|
|
352
|
+
if (
|
|
353
|
+
not parent_url
|
|
354
|
+
and isinstance(parent_name, str)
|
|
355
|
+
and re.fullmatch(r"[-A-Za-z0-9_.]+/[-A-Za-z0-9_.]+", parent_name)
|
|
356
|
+
):
|
|
357
|
+
parent_url = f"https://github.com/{parent_name}"
|
|
358
|
+
|
|
359
|
+
return GitHubRepository(
|
|
360
|
+
owner=owner,
|
|
361
|
+
name=name,
|
|
362
|
+
url=canonical_url,
|
|
363
|
+
visibility=visibility,
|
|
364
|
+
is_fork=payload.get("isFork") is True,
|
|
365
|
+
parent_url=parent_url,
|
|
366
|
+
is_archived=payload.get("isArchived") is True,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
|
|
271
370
|
def pull_request_status(row: dict[str, object]) -> str:
|
|
272
371
|
"""Normalize GitHub's PR and review fields into a compact status."""
|
|
273
372
|
state = str(row.get("state", "")).upper()
|
|
@@ -315,7 +414,10 @@ def github_pull_requests() -> tuple[list[dict[str, object]], str | None]:
|
|
|
315
414
|
return [], "unusable"
|
|
316
415
|
if not isinstance(parsed, list):
|
|
317
416
|
return [], "unusable"
|
|
318
|
-
|
|
417
|
+
rows = [row for row in parsed if isinstance(row, dict)]
|
|
418
|
+
if parsed and not rows:
|
|
419
|
+
return [], "unusable"
|
|
420
|
+
return rows, None
|
|
319
421
|
|
|
320
422
|
|
|
321
423
|
def parse_track(track: str) -> tuple[int, int] | None:
|
|
@@ -488,6 +590,8 @@ def run_report(*, no_fetch: bool = False, plain: bool = False) -> int:
|
|
|
488
590
|
if not symref
|
|
489
591
|
}
|
|
490
592
|
remotes = set(git_output("remote").splitlines())
|
|
593
|
+
repo_url = repository_github_url(remotes)
|
|
594
|
+
github_repo = github_repository(repo_url) if repo_url else None
|
|
491
595
|
|
|
492
596
|
raw_branches = git_output(
|
|
493
597
|
"for-each-ref",
|
|
@@ -621,7 +725,23 @@ def run_report(*, no_fetch: bool = False, plain: bool = False) -> int:
|
|
|
621
725
|
behind_count = sum("behind" in branch.state_text for branch in branches)
|
|
622
726
|
|
|
623
727
|
print()
|
|
624
|
-
|
|
728
|
+
if github_repo:
|
|
729
|
+
owner_url = f"https://github.com/{github_repo.owner}"
|
|
730
|
+
rendered_repo = (
|
|
731
|
+
bold(hyperlink(github_repo.owner, owner_url, enabled=use_links))
|
|
732
|
+
+ dim(" / ")
|
|
733
|
+
+ bold(hyperlink(github_repo.name, github_repo.url, enabled=use_links))
|
|
734
|
+
)
|
|
735
|
+
if github_repo.visibility:
|
|
736
|
+
rendered_repo += dim(f" [{github_repo.visibility}]")
|
|
737
|
+
if github_repo.is_fork:
|
|
738
|
+
fork_label = hyperlink("[fork]", github_repo.parent_url, enabled=use_links)
|
|
739
|
+
rendered_repo += " " + dim(fork_label)
|
|
740
|
+
if github_repo.is_archived:
|
|
741
|
+
rendered_repo += " " + yellow("[archived]")
|
|
742
|
+
else:
|
|
743
|
+
rendered_repo = bold(repo_name)
|
|
744
|
+
print(rendered_repo + dim(" :: ") + bold(current or "detached HEAD"))
|
|
625
745
|
separator = glyph["separator"]
|
|
626
746
|
worktree = (
|
|
627
747
|
yellow(f"dirty: {f' {separator} '.join(worktree_parts)}") if dirty_lines else green("clean")
|
|
@@ -747,7 +867,10 @@ def main(
|
|
|
747
867
|
bool,
|
|
748
868
|
typer.Option(
|
|
749
869
|
"--plain",
|
|
750
|
-
help=
|
|
870
|
+
help=(
|
|
871
|
+
"Disable color, hyperlinks, and Unicode interface symbols for stable logs and "
|
|
872
|
+
"pipes. Repository-provided text is preserved."
|
|
873
|
+
),
|
|
751
874
|
rich_help_panel="Report options",
|
|
752
875
|
),
|
|
753
876
|
] = False,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|