gitpro 0.3.2__tar.gz → 0.3.3__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.
- {gitpro-0.3.2 → gitpro-0.3.3}/PKG-INFO +10 -2
- {gitpro-0.3.2 → gitpro-0.3.3}/README.md +6 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/pyproject.toml +3 -1
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/__init__.py +1 -1
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/config.py +1 -1
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/discover.py +1 -1
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/lint.py +3 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/resolve.py +65 -15
- {gitpro-0.3.2 → gitpro-0.3.3}/.gitignore +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/LICENSE +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/__main__.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/agents.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/approval_policy.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/args.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/audit.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/change_contract.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/changes.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/cli.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/clone.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/completion.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/devcontainer.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/doctor.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/envcmd.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/example.toml +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/files.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/forge.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/guard.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/health.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/hot.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/jsonfile.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/jsonout.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/keys.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/lost.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/mcp.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/operation_contract.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/operation_io.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/operation_reconcile.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/operations.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/pin.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/plan.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/policy.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/prompt.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/render.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/scan.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/scanroots.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/setup.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/trailers.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/tui.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/undo.py +0 -0
- {gitpro-0.3.2 → gitpro-0.3.3}/src/gitpro/view.py +0 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: gitpro
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Per-remote git identities (name, email, SSH key, commit signing) from one TOML file
|
|
5
|
-
Project-URL: Homepage, https://
|
|
5
|
+
Project-URL: Homepage, https://gitpro.dev
|
|
6
|
+
Project-URL: Source, https://github.com/Yantrakar/gitpro
|
|
6
7
|
Project-URL: Documentation, https://github.com/Yantrakar/gitpro/blob/main/docs/guide.md
|
|
7
8
|
Project-URL: Issues, https://github.com/Yantrakar/gitpro/issues
|
|
9
|
+
Project-URL: VS Code extension, https://marketplace.visualstudio.com/items?itemName=yantrakar.gitpro
|
|
8
10
|
Author: Yantrakar, Sandeep Shelke
|
|
9
11
|
License-Expression: MIT
|
|
10
12
|
License-File: LICENSE
|
|
@@ -22,10 +24,16 @@ Provides-Extra: tui
|
|
|
22
24
|
Requires-Dist: textual>=0.80; extra == 'tui'
|
|
23
25
|
Description-Content-Type: text/markdown
|
|
24
26
|
|
|
27
|
+
<a href="https://gitpro.dev"><img src="https://gitpro.dev/icon.png" alt="gitpro" width="72" height="72"></a>
|
|
28
|
+
|
|
25
29
|
# gitpro
|
|
26
30
|
|
|
31
|
+
<!-- mcp-name: io.github.Yantrakar/gitpro -->
|
|
32
|
+
|
|
27
33
|
**The right Git identity, SSH key and signature for every repository.**
|
|
28
34
|
|
|
35
|
+
[gitpro.dev](https://gitpro.dev) · [PyPI](https://pypi.org/project/gitpro/) · [VS Code](https://marketplace.visualstudio.com/items?itemName=yantrakar.gitpro) · made by [Yantrakar](https://yantrakar.com)
|
|
36
|
+
|
|
29
37
|
Work on a company repository, commit with your work email. Switch to a personal
|
|
30
38
|
project, use your personal identity. Gitpro chooses by **remote URL**, not by
|
|
31
39
|
where you happened to clone the repository.
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
<a href="https://gitpro.dev"><img src="https://gitpro.dev/icon.png" alt="gitpro" width="72" height="72"></a>
|
|
2
|
+
|
|
1
3
|
# gitpro
|
|
2
4
|
|
|
5
|
+
<!-- mcp-name: io.github.Yantrakar/gitpro -->
|
|
6
|
+
|
|
3
7
|
**The right Git identity, SSH key and signature for every repository.**
|
|
4
8
|
|
|
9
|
+
[gitpro.dev](https://gitpro.dev) · [PyPI](https://pypi.org/project/gitpro/) · [VS Code](https://marketplace.visualstudio.com/items?itemName=yantrakar.gitpro) · made by [Yantrakar](https://yantrakar.com)
|
|
10
|
+
|
|
5
11
|
Work on a company repository, commit with your work email. Switch to a personal
|
|
6
12
|
project, use your personal identity. Gitpro chooses by **remote URL**, not by
|
|
7
13
|
where you happened to clone the repository.
|
|
@@ -26,9 +26,11 @@ classifiers = [
|
|
|
26
26
|
tui = ["textual>=0.80"] # interactive scan --fix
|
|
27
27
|
|
|
28
28
|
[project.urls]
|
|
29
|
-
Homepage = "https://
|
|
29
|
+
Homepage = "https://gitpro.dev"
|
|
30
|
+
Source = "https://github.com/Yantrakar/gitpro"
|
|
30
31
|
Documentation = "https://github.com/Yantrakar/gitpro/blob/main/docs/guide.md"
|
|
31
32
|
Issues = "https://github.com/Yantrakar/gitpro/issues"
|
|
33
|
+
"VS Code extension" = "https://marketplace.visualstudio.com/items?itemName=yantrakar.gitpro"
|
|
32
34
|
|
|
33
35
|
[project.scripts]
|
|
34
36
|
gitpro = "gitpro.hot:main"
|
|
@@ -24,7 +24,7 @@ _LABEL = r"[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?"
|
|
|
24
24
|
HOST_RE = re.compile(rf"^{_LABEL}(?:\.{_LABEL})*$")
|
|
25
25
|
ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
|
|
26
26
|
USER_RE = ID_RE
|
|
27
|
-
OWNER_RE = re.compile(r"
|
|
27
|
+
OWNER_RE = re.compile(r"^~?[A-Za-z0-9*][A-Za-z0-9._*-]*(?:/[A-Za-z0-9*][A-Za-z0-9._*-]*)*$") # ~: SourceHut users
|
|
28
28
|
PATH_RE = re.compile(r"^(?:~/|/)[A-Za-z0-9._/@+-]+$")
|
|
29
29
|
EMAIL_RE = re.compile(r'^[^@\s<>"]+@[^@\s<>"]+$')
|
|
30
30
|
CONTROL_RE = re.compile(r"[\x00-\x08\x0a-\x1f\x7f]")
|
|
@@ -1228,7 +1228,7 @@ def render_toml(d: dict) -> str:
|
|
|
1228
1228
|
notes.append(f"{where}: already in config" + (f" (identity {o['config_id']})" if o.get("config_id") else ""))
|
|
1229
1229
|
continue
|
|
1230
1230
|
if not OWNER_RE.match(o["owner"]):
|
|
1231
|
-
notes.append(f"{where}: owners can hold only A-Z a-z 0-9 . _ -
|
|
1231
|
+
notes.append(f"{where}: owners can hold only A-Z a-z 0-9 . _ - / and a leading ~; add it by hand if it is yours")
|
|
1232
1232
|
continue
|
|
1233
1233
|
rule = o.get("rule") or {}
|
|
1234
1234
|
email = _top(o.get("emails"), _usable_email)[0]
|
|
@@ -7,11 +7,14 @@ import subprocess
|
|
|
7
7
|
from . import keys, render
|
|
8
8
|
from .config import Default, Identity
|
|
9
9
|
from .plan import Env
|
|
10
|
+
from .resolve import is_codecommit
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def warnings(d: Default, ids: list[Identity], env: Env) -> list[str]:
|
|
13
14
|
out = []
|
|
14
15
|
for i in ids:
|
|
16
|
+
if is_codecommit(i.host) and i.ssh_user == "git" and (i.ssh_key or i.ssh_alias):
|
|
17
|
+
out.append(f"identity {i.id!r}: CodeCommit's SSH user is the SSH key ID, not git; set ssh_user to it")
|
|
15
18
|
if i.ssh_alias and i.rewrite:
|
|
16
19
|
out += [f"identity {i.id!r}: owner {o!r} is a glob; insteadOf needs a literal prefix, so "
|
|
17
20
|
f"its URLs aren't rewritten to {i.ssh_alias}" for o in i.owners if "*" in o]
|
|
@@ -607,28 +607,75 @@ def _p(code: str, message: str, argv: Optional[Sequence[str]], fix: Optional[str
|
|
|
607
607
|
tuple(argv) if argv else None)
|
|
608
608
|
|
|
609
609
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
610
|
+
_DEFAULT_PORT = {"ssh": 22, "https": 443, "http": 80}
|
|
611
|
+
_CODECOMMIT = re.compile(r"^git-codecommit\.[a-z0-9-]+\.amazonaws\.com$", re.I)
|
|
612
|
+
_Fix = Tuple[Optional[str], str, Identity, str, Optional[Tuple[str, str]]]
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
def is_codecommit(host: str) -> bool:
|
|
616
|
+
return bool(_CODECOMMIT.match(host))
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
def _repairs(url: str, users: List[str]) -> List[Tuple[str, str, Optional[Tuple[str, int]]]]:
|
|
620
|
+
tries: List[Tuple[str, str, Optional[Tuple[str, int]]]] = []
|
|
614
621
|
m = re.match(r"^(https?://)[^@/]+@(.*)$", url)
|
|
615
622
|
if m:
|
|
616
|
-
tries.append((m[1] + m[2], "no user in the URL"))
|
|
617
|
-
users = list(dict.fromkeys(i.ssh_user for i in cfg.identities)) or ["git"]
|
|
623
|
+
tries.append((m[1] + m[2], "no user in the URL", None))
|
|
618
624
|
m = re.match(r"^ssh://([^@/]+)(/.*)$", url)
|
|
619
625
|
if m:
|
|
620
|
-
tries += [(f"ssh://{u}@{m[1]}{m[2]}", f"user {u}@ in the URL") for u in users]
|
|
626
|
+
tries += [(f"ssh://{u}@{m[1]}{m[2]}", f"user {u}@ in the URL", None) for u in users]
|
|
621
627
|
elif "://" not in url:
|
|
622
628
|
m = _SCP_RE.match(url)
|
|
623
629
|
if m and m[1] is None and "/" not in url.split(":", 1)[0]:
|
|
624
|
-
tries += [(f"{u}@{url}", f"user {u}@ in the URL") for u in users]
|
|
625
|
-
m = re.match(r"^((
|
|
630
|
+
tries += [(f"{u}@{url}", f"user {u}@ in the URL", None) for u in users]
|
|
631
|
+
m = re.match(r"^((ssh|https?)://[^/]*?):(\d+)(/.*)$", url)
|
|
626
632
|
if m:
|
|
627
|
-
tries.append((m[1] + m[
|
|
628
|
-
|
|
633
|
+
tries.append((m[1] + m[4], "no port in the URL", (m[2], int(m[3]))))
|
|
634
|
+
return tries
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def _url_fixes(url: str, cfg: Config, label: str = "the remote") -> List[_Fix]:
|
|
638
|
+
"""[(fixed url, reason, identity, pattern, note)] for the repairs, in order: git+ssh:// or ssh+git://
|
|
639
|
+
written as ssh://, then (on that url) userinfo removed, missing user added, port removed, then
|
|
640
|
+
letter case changed to an identity's. First repair that makes some identity match wins. When the
|
|
641
|
+
fixed url would point at the wrong place (a non-default port removed, user git on CodeCommit),
|
|
642
|
+
fixed is None and note is (message tail, fix text) instead."""
|
|
643
|
+
users = list(dict.fromkeys(i.ssh_user for i in cfg.identities)) or ["git"]
|
|
644
|
+
tries: List[Tuple[str, str, Optional[Tuple[str, int]]]] = []
|
|
645
|
+
m = re.match(r"^(git\+ssh|ssh\+git)://(.*)$", url)
|
|
646
|
+
if m:
|
|
647
|
+
pre = f"ssh:// instead of {m[1]}://"
|
|
648
|
+
tries.append((f"ssh://{m[2]}", pre, None))
|
|
649
|
+
tries += [(f, f"{pre}, {r}", p) for f, r, p in _repairs(f"ssh://{m[2]}", users)]
|
|
650
|
+
tries += _repairs(url, users)
|
|
651
|
+
for fixed, reason, port in tries:
|
|
629
652
|
i, pat = match_url(fixed, cfg)
|
|
630
|
-
if i is
|
|
631
|
-
|
|
653
|
+
if i is None:
|
|
654
|
+
continue
|
|
655
|
+
if port and port[1] != _DEFAULT_PORT[port[0]]:
|
|
656
|
+
if port[0] == "ssh":
|
|
657
|
+
tail = f"it looks like {i.id}, whose patterns have no port {port[1]}"
|
|
658
|
+
fix = f"set ssh_port = {port[1]} on identity {i.id} in the gitpro config, then run: gitpro apply"
|
|
659
|
+
if i.ssh_port:
|
|
660
|
+
fix += f" (it is {i.ssh_port} now; an identity has one ssh_port)"
|
|
661
|
+
else:
|
|
662
|
+
tail = f"it looks like {i.id}, but gitpro's https patterns never carry a port"
|
|
663
|
+
fix = f"switch {label} to an ssh URL for {i.id}"
|
|
664
|
+
return [(None, reason, i, pat, (tail, fix))]
|
|
665
|
+
if reason.endswith("user git@ in the URL") and is_codecommit(i.host):
|
|
666
|
+
return [(None, reason, i, pat, (
|
|
667
|
+
f"it looks like {i.id}, but CodeCommit's SSH user is the SSH key ID, not git",
|
|
668
|
+
f'set ssh_user = "<your SSH key ID>" on identity {i.id} in the gitpro config, then run: gitpro apply'))]
|
|
669
|
+
return [(fixed, reason, i, pat, None)]
|
|
670
|
+
from .render import url_patterns
|
|
671
|
+
for i in cfg.identities:
|
|
672
|
+
for pat in url_patterns(i):
|
|
673
|
+
prefix = pat.split("*", 1)[0]
|
|
674
|
+
if url.lower().startswith(prefix.lower()) and not url.startswith(prefix):
|
|
675
|
+
fixed = prefix + url[len(prefix):]
|
|
676
|
+
j, jpat = match_url(fixed, cfg)
|
|
677
|
+
if j is not None:
|
|
678
|
+
return [(fixed, "letter case differs; git matches case-sensitively", j, jpat, None)]
|
|
632
679
|
return []
|
|
633
680
|
|
|
634
681
|
|
|
@@ -686,9 +733,12 @@ def compare(repo: Path, cfg: Config, *, remote: Optional[str] = None,
|
|
|
686
733
|
|
|
687
734
|
if exp.match.identity is None:
|
|
688
735
|
for name, url in candidates:
|
|
689
|
-
fixes = _url_fixes(url, cfg)
|
|
736
|
+
fixes = _url_fixes(url, cfg, name or "the remote")
|
|
690
737
|
if fixes:
|
|
691
|
-
fixed, reason, i, pat = fixes[0]
|
|
738
|
+
fixed, reason, i, pat, note = fixes[0]
|
|
739
|
+
if note:
|
|
740
|
+
out.append(_p("url_form", f"{name or 'the remote'} is {redact_url(url)}; {note[0]}", None, note[1]))
|
|
741
|
+
break
|
|
692
742
|
argv = ["git", "-C", str(top), "remote", "set-url", name, fixed] \
|
|
693
743
|
if name and remote_url is None else None
|
|
694
744
|
need = pat.replace("**", "…")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|