gitd 0.7.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.
- gitd-0.7.0/.gitignore +3 -0
- gitd-0.7.0/PKG-INFO +77 -0
- gitd-0.7.0/README.md +60 -0
- gitd-0.7.0/examples/agent.py +81 -0
- gitd-0.7.0/pyproject.toml +34 -0
- gitd-0.7.0/src/gitd/__init__.py +42 -0
- gitd-0.7.0/src/gitd/client.py +124 -0
- gitd-0.7.0/src/gitd/connection.py +71 -0
- gitd-0.7.0/src/gitd/contract.json +1291 -0
- gitd-0.7.0/src/gitd/contract.py +50 -0
- gitd-0.7.0/src/gitd/errors.py +36 -0
- gitd-0.7.0/src/gitd/messages.py +30 -0
- gitd-0.7.0/src/gitd/methods.py +32 -0
- gitd-0.7.0/src/gitd/models.py +285 -0
- gitd-0.7.0/src/gitd/timestamps.py +22 -0
gitd-0.7.0/.gitignore
ADDED
gitd-0.7.0/PKG-INFO
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: gitd
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: Client for gitd: instant, secretless git checkouts for agent sandboxes
|
|
5
|
+
Project-URL: Homepage, https://github.com/dooservice/gitd
|
|
6
|
+
Project-URL: Source, https://github.com/dooservice/gitd/tree/main/sdk/python
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
Keywords: ai-agents,git,grpc,sandbox,workspace
|
|
9
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Requires-Dist: grpcio-status>=1.84.0
|
|
14
|
+
Requires-Dist: grpcio>=1.84.0
|
|
15
|
+
Requires-Dist: protobuf>=7.36.2
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# gitd for Python
|
|
19
|
+
|
|
20
|
+
A client for [gitd](https://github.com/dooservice/gitd): instant, secretless git checkouts
|
|
21
|
+
for agent sandboxes. Your agents work in their own copy and publish a branch; they never
|
|
22
|
+
see your token.
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
pip install gitd
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from gitd import Gitd, GitdError
|
|
30
|
+
|
|
31
|
+
with Gitd() as gitd: # $GITD_SOCKET, then /run/gitd/gitd.sock
|
|
32
|
+
workspace = gitd.prepare("https://github.com/acme/web", "/var/lib/gitd/workspaces/agent-1")
|
|
33
|
+
print(workspace.path, workspace.commit, workspace.strategy)
|
|
34
|
+
|
|
35
|
+
# … the agent works in workspace.path with plain git, and commits …
|
|
36
|
+
|
|
37
|
+
published = gitd.publish(workspace.path, "agents/fix-login")
|
|
38
|
+
gitd.remove(workspace.path)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
No call takes a credential: gitd reads and publishes with the ones it keeps.
|
|
42
|
+
|
|
43
|
+
| Method | What it does |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `prepare(url, path, *, branch, commit, submodules, full_history, strategy, on_progress)` | A workspace of a repository at a path under gitd's roots |
|
|
46
|
+
| `update(path, *, branch, submodules, on_progress)` | To the tip of its branch, or of the branch given |
|
|
47
|
+
| `checkout(path, commit, *, submodules, on_progress)` | To a commit, as a rollback does |
|
|
48
|
+
| `describe(path)` | Its repository, commit, branch, uncommitted changes and submodules |
|
|
49
|
+
| `publish(path, branch)` | Its commits pushed to a new branch |
|
|
50
|
+
| `remove(path)` | Deleted; its pool stays |
|
|
51
|
+
| `probe(url)` | Whether it can be read, its default branch and branches |
|
|
52
|
+
| `info()` | Which gitd answers, and how it makes workspaces here and why |
|
|
53
|
+
| `audit(*, since, url, limit)` | Every publish, done or refused, newest first |
|
|
54
|
+
|
|
55
|
+
A refusal raises `GitdError`, with `reason` (`AUTH_REQUIRED`, `NOT_FOUND`, `PATH_REFUSED`,
|
|
56
|
+
`BRANCH_REFUSED`, `BUSY`, …) and `said`, what git said. Decide from the reason, never from
|
|
57
|
+
the message.
|
|
58
|
+
|
|
59
|
+
[`examples/agent.py`](examples/agent.py) is an agent's whole round trip.
|
|
60
|
+
|
|
61
|
+
## Layout
|
|
62
|
+
|
|
63
|
+
| Path | What it is |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `src/gitd/client.py` | `Gitd`, the client |
|
|
66
|
+
| `src/gitd/models.py` | What gitd answers, as dataclasses |
|
|
67
|
+
| `src/gitd/errors.py` | `GitdError` |
|
|
68
|
+
| `src/gitd/timestamps.py` | Between the contract's timestamps and `datetime` |
|
|
69
|
+
| `src/gitd/connection.py` | The channel to the socket, and how calls are made |
|
|
70
|
+
| `src/gitd/methods.py` | The gitd methods called, by their gRPC path |
|
|
71
|
+
| `src/gitd/messages.py` | The messages of the contract the client uses |
|
|
72
|
+
| `src/gitd/contract.py` | Reads the contract at run time, with protobuf's public API |
|
|
73
|
+
| `src/gitd/contract.json` | The contract, built from [`proto/gitd/v1/gitd.proto`](../../proto/gitd/v1/gitd.proto) by `make generate` |
|
|
74
|
+
|
|
75
|
+
No code is generated: the client reads gitd's contract when it starts.
|
|
76
|
+
|
|
77
|
+
Python 3.11 or later, on Linux. Apache-2.0.
|
gitd-0.7.0/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# gitd for Python
|
|
2
|
+
|
|
3
|
+
A client for [gitd](https://github.com/dooservice/gitd): instant, secretless git checkouts
|
|
4
|
+
for agent sandboxes. Your agents work in their own copy and publish a branch; they never
|
|
5
|
+
see your token.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pip install gitd
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from gitd import Gitd, GitdError
|
|
13
|
+
|
|
14
|
+
with Gitd() as gitd: # $GITD_SOCKET, then /run/gitd/gitd.sock
|
|
15
|
+
workspace = gitd.prepare("https://github.com/acme/web", "/var/lib/gitd/workspaces/agent-1")
|
|
16
|
+
print(workspace.path, workspace.commit, workspace.strategy)
|
|
17
|
+
|
|
18
|
+
# … the agent works in workspace.path with plain git, and commits …
|
|
19
|
+
|
|
20
|
+
published = gitd.publish(workspace.path, "agents/fix-login")
|
|
21
|
+
gitd.remove(workspace.path)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
No call takes a credential: gitd reads and publishes with the ones it keeps.
|
|
25
|
+
|
|
26
|
+
| Method | What it does |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `prepare(url, path, *, branch, commit, submodules, full_history, strategy, on_progress)` | A workspace of a repository at a path under gitd's roots |
|
|
29
|
+
| `update(path, *, branch, submodules, on_progress)` | To the tip of its branch, or of the branch given |
|
|
30
|
+
| `checkout(path, commit, *, submodules, on_progress)` | To a commit, as a rollback does |
|
|
31
|
+
| `describe(path)` | Its repository, commit, branch, uncommitted changes and submodules |
|
|
32
|
+
| `publish(path, branch)` | Its commits pushed to a new branch |
|
|
33
|
+
| `remove(path)` | Deleted; its pool stays |
|
|
34
|
+
| `probe(url)` | Whether it can be read, its default branch and branches |
|
|
35
|
+
| `info()` | Which gitd answers, and how it makes workspaces here and why |
|
|
36
|
+
| `audit(*, since, url, limit)` | Every publish, done or refused, newest first |
|
|
37
|
+
|
|
38
|
+
A refusal raises `GitdError`, with `reason` (`AUTH_REQUIRED`, `NOT_FOUND`, `PATH_REFUSED`,
|
|
39
|
+
`BRANCH_REFUSED`, `BUSY`, …) and `said`, what git said. Decide from the reason, never from
|
|
40
|
+
the message.
|
|
41
|
+
|
|
42
|
+
[`examples/agent.py`](examples/agent.py) is an agent's whole round trip.
|
|
43
|
+
|
|
44
|
+
## Layout
|
|
45
|
+
|
|
46
|
+
| Path | What it is |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `src/gitd/client.py` | `Gitd`, the client |
|
|
49
|
+
| `src/gitd/models.py` | What gitd answers, as dataclasses |
|
|
50
|
+
| `src/gitd/errors.py` | `GitdError` |
|
|
51
|
+
| `src/gitd/timestamps.py` | Between the contract's timestamps and `datetime` |
|
|
52
|
+
| `src/gitd/connection.py` | The channel to the socket, and how calls are made |
|
|
53
|
+
| `src/gitd/methods.py` | The gitd methods called, by their gRPC path |
|
|
54
|
+
| `src/gitd/messages.py` | The messages of the contract the client uses |
|
|
55
|
+
| `src/gitd/contract.py` | Reads the contract at run time, with protobuf's public API |
|
|
56
|
+
| `src/gitd/contract.json` | The contract, built from [`proto/gitd/v1/gitd.proto`](../../proto/gitd/v1/gitd.proto) by `make generate` |
|
|
57
|
+
|
|
58
|
+
No code is generated: the client reads gitd's contract when it starts.
|
|
59
|
+
|
|
60
|
+
Python 3.11 or later, on Linux. Apache-2.0.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""An agent's round trip with gitd:
|
|
2
|
+
|
|
3
|
+
1. gitd prepares a workspace of the repository, in milliseconds after the first time;
|
|
4
|
+
2. the agent works in it with plain git, and commits;
|
|
5
|
+
3. gitd publishes the commit as a new branch, with the token it keeps.
|
|
6
|
+
|
|
7
|
+
The agent never holds a credential.
|
|
8
|
+
|
|
9
|
+
python examples/agent.py https://github.com/your-org/your-repo /var/lib/gitd/workspaces/agent-1
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
import time
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from gitd import Gitd, GitdError, Progress, Workspace
|
|
20
|
+
|
|
21
|
+
AUTHOR = ["-c", "user.name=agent", "-c", "user.email=agent@example.com"]
|
|
22
|
+
BRANCH_PREFIX = "agents/example-"
|
|
23
|
+
NOTE_FILE = "AGENT.md"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Agent:
|
|
27
|
+
"""Works on one repository in its own workspace, and publishes what it commits."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, gitd: Gitd, url: str, path: str) -> None:
|
|
30
|
+
self.gitd = gitd
|
|
31
|
+
self.url = url
|
|
32
|
+
self.path = path
|
|
33
|
+
|
|
34
|
+
def run(self) -> None:
|
|
35
|
+
workspace = self.prepare()
|
|
36
|
+
try:
|
|
37
|
+
self.commit_a_change(workspace)
|
|
38
|
+
self.publish(workspace)
|
|
39
|
+
finally:
|
|
40
|
+
self.gitd.remove(workspace.path)
|
|
41
|
+
|
|
42
|
+
def prepare(self) -> Workspace:
|
|
43
|
+
workspace = self.gitd.prepare(self.url, self.path, on_progress=self.show_progress)
|
|
44
|
+
print(file=sys.stderr)
|
|
45
|
+
print(f"Prepared {workspace.path} at {workspace.commit[:12]}, by {workspace.strategy.value}.")
|
|
46
|
+
return workspace
|
|
47
|
+
|
|
48
|
+
def commit_a_change(self, workspace: Workspace) -> None:
|
|
49
|
+
Path(workspace.path, NOTE_FILE).write_text("Written by an agent.\n")
|
|
50
|
+
self.git(workspace, "add", NOTE_FILE)
|
|
51
|
+
self.git(workspace, "commit", "--quiet", "--message", "An agent was here")
|
|
52
|
+
print(f"Committed {NOTE_FILE}.")
|
|
53
|
+
|
|
54
|
+
def publish(self, workspace: Workspace) -> None:
|
|
55
|
+
published = self.gitd.publish(workspace.path, f"{BRANCH_PREFIX}{int(time.time())}")
|
|
56
|
+
repository = published.repositories[0]
|
|
57
|
+
print(f"Published {published.branch} to {repository.url}: {', '.join(repository.subjects)}.")
|
|
58
|
+
|
|
59
|
+
def git(self, workspace: Workspace, *arguments: str) -> None:
|
|
60
|
+
subprocess.run(["git", *AUTHOR, *arguments], cwd=workspace.path, check=True)
|
|
61
|
+
|
|
62
|
+
def show_progress(self, progress: Progress) -> None:
|
|
63
|
+
print(f"\r{progress.phase} {progress.percent}%", end="", file=sys.stderr)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def main(arguments: list[str]) -> int:
|
|
67
|
+
if len(arguments) != 2:
|
|
68
|
+
print("usage: python examples/agent.py <repository url> <workspace path>", file=sys.stderr)
|
|
69
|
+
return 2
|
|
70
|
+
url, path = arguments
|
|
71
|
+
with Gitd() as gitd:
|
|
72
|
+
try:
|
|
73
|
+
Agent(gitd, url, path).run()
|
|
74
|
+
except GitdError as error:
|
|
75
|
+
print(f"gitd refused: {error.reason}: {error.said}", file=sys.stderr)
|
|
76
|
+
return 1
|
|
77
|
+
return 0
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
sys.exit(main(sys.argv[1:]))
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.32.4"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gitd"
|
|
7
|
+
version = "0.7.0"
|
|
8
|
+
description = "Client for gitd: instant, secretless git checkouts for agent sandboxes"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "Apache-2.0"
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
keywords = ["git", "ai-agents", "sandbox", "workspace", "grpc"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Operating System :: POSIX :: Linux",
|
|
16
|
+
"Topic :: Software Development :: Version Control :: Git",
|
|
17
|
+
]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"grpcio>=1.84.0",
|
|
20
|
+
"grpcio-status>=1.84.0",
|
|
21
|
+
"protobuf>=7.36.2",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/dooservice/gitd"
|
|
26
|
+
Source = "https://github.com/dooservice/gitd/tree/main/sdk/python"
|
|
27
|
+
|
|
28
|
+
[tool.hatch.build.targets.wheel]
|
|
29
|
+
packages = ["src/gitd"]
|
|
30
|
+
|
|
31
|
+
[tool.ruff]
|
|
32
|
+
line-length = 110
|
|
33
|
+
[tool.ruff.lint]
|
|
34
|
+
select = ["E", "F", "I", "B", "UP", "N", "SIM"]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Client for gitd: instant, secretless git checkouts for agent sandboxes."""
|
|
2
|
+
|
|
3
|
+
from .client import Gitd
|
|
4
|
+
from .connection import DEFAULT_SOCKET, SOCKET_VARIABLE
|
|
5
|
+
from .errors import GitdError
|
|
6
|
+
from .models import (
|
|
7
|
+
Access,
|
|
8
|
+
AuditEntry,
|
|
9
|
+
Description,
|
|
10
|
+
Info,
|
|
11
|
+
Layers,
|
|
12
|
+
PendingSubmodule,
|
|
13
|
+
Probe,
|
|
14
|
+
Progress,
|
|
15
|
+
Published,
|
|
16
|
+
PublishedRepository,
|
|
17
|
+
Refusal,
|
|
18
|
+
Strategy,
|
|
19
|
+
Submodule,
|
|
20
|
+
Workspace,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"DEFAULT_SOCKET",
|
|
25
|
+
"SOCKET_VARIABLE",
|
|
26
|
+
"Access",
|
|
27
|
+
"AuditEntry",
|
|
28
|
+
"Description",
|
|
29
|
+
"Gitd",
|
|
30
|
+
"GitdError",
|
|
31
|
+
"Info",
|
|
32
|
+
"Layers",
|
|
33
|
+
"PendingSubmodule",
|
|
34
|
+
"Probe",
|
|
35
|
+
"Progress",
|
|
36
|
+
"Published",
|
|
37
|
+
"PublishedRepository",
|
|
38
|
+
"Refusal",
|
|
39
|
+
"Strategy",
|
|
40
|
+
"Submodule",
|
|
41
|
+
"Workspace",
|
|
42
|
+
]
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""The gitd client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from . import messages, methods
|
|
9
|
+
from .connection import Connection, OnProgress
|
|
10
|
+
from .models import AuditEntry, Description, Info, Probe, Published, Strategy, Workspace
|
|
11
|
+
from .timestamps import Timestamp
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Gitd:
|
|
15
|
+
"""A gitd on its Unix socket. No call takes a credential: gitd reads and publishes with
|
|
16
|
+
the ones it keeps.
|
|
17
|
+
|
|
18
|
+
with Gitd() as gitd:
|
|
19
|
+
workspace = gitd.prepare("https://github.com/acme/web", "/var/lib/gitd/workspaces/agent-1")
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, socket: str | None = None, *, timeout: float | None = None) -> None:
|
|
23
|
+
self.connection = Connection(socket, timeout)
|
|
24
|
+
|
|
25
|
+
def __enter__(self) -> Gitd:
|
|
26
|
+
return self
|
|
27
|
+
|
|
28
|
+
def __exit__(self, *exception: object) -> None:
|
|
29
|
+
self.close()
|
|
30
|
+
|
|
31
|
+
def close(self) -> None:
|
|
32
|
+
self.connection.close()
|
|
33
|
+
|
|
34
|
+
def info(self) -> Info:
|
|
35
|
+
"""Which gitd answers, and how it makes workspaces here and why."""
|
|
36
|
+
return Info.from_message(self.connection.call(methods.INFO, messages.InfoRequest()))
|
|
37
|
+
|
|
38
|
+
def probe(self, url: str) -> Probe:
|
|
39
|
+
"""Whether a repository can be read with the credentials gitd keeps, and its branches."""
|
|
40
|
+
return Probe.from_message(self.connection.call(methods.PROBE, messages.ProbeRequest(url=url)))
|
|
41
|
+
|
|
42
|
+
def prepare(
|
|
43
|
+
self,
|
|
44
|
+
url: str,
|
|
45
|
+
path: str,
|
|
46
|
+
*,
|
|
47
|
+
branch: str | None = None,
|
|
48
|
+
commit: str | None = None,
|
|
49
|
+
submodules: bool = False,
|
|
50
|
+
full_history: bool = False,
|
|
51
|
+
strategy: Strategy | None = None,
|
|
52
|
+
on_progress: OnProgress | None = None,
|
|
53
|
+
) -> Workspace:
|
|
54
|
+
"""Makes a workspace of a repository at a path under gitd's roots, at a commit, a
|
|
55
|
+
branch, or the default branch. strategy is a preference: gitd falls back to copy when
|
|
56
|
+
it cannot make one that way here."""
|
|
57
|
+
if commit:
|
|
58
|
+
revision = messages.Revision(commit=commit)
|
|
59
|
+
elif branch:
|
|
60
|
+
revision = messages.Revision(branch=branch)
|
|
61
|
+
else:
|
|
62
|
+
revision = None
|
|
63
|
+
request = messages.PrepareRequest(
|
|
64
|
+
url=url,
|
|
65
|
+
path=path,
|
|
66
|
+
revision=revision,
|
|
67
|
+
with_submodules=submodules,
|
|
68
|
+
full_history=full_history,
|
|
69
|
+
strategy=Strategy.preference(strategy),
|
|
70
|
+
)
|
|
71
|
+
return Workspace.from_message(path, self.connection.follow(methods.PREPARE, request, on_progress))
|
|
72
|
+
|
|
73
|
+
def update(
|
|
74
|
+
self,
|
|
75
|
+
path: str,
|
|
76
|
+
*,
|
|
77
|
+
branch: str | None = None,
|
|
78
|
+
submodules: bool = False,
|
|
79
|
+
on_progress: OnProgress | None = None,
|
|
80
|
+
) -> Workspace:
|
|
81
|
+
"""Brings a workspace to the tip of its branch, or of the branch given."""
|
|
82
|
+
request = messages.UpdateRequest(path=path, branch=branch or "", with_submodules=submodules)
|
|
83
|
+
return Workspace.from_message(path, self.connection.follow(methods.UPDATE, request, on_progress))
|
|
84
|
+
|
|
85
|
+
def checkout(
|
|
86
|
+
self,
|
|
87
|
+
path: str,
|
|
88
|
+
commit: str,
|
|
89
|
+
*,
|
|
90
|
+
submodules: bool = False,
|
|
91
|
+
on_progress: OnProgress | None = None,
|
|
92
|
+
) -> Workspace:
|
|
93
|
+
"""Moves a workspace to a commit, as a rollback does."""
|
|
94
|
+
request = messages.CheckoutRequest(path=path, commit=commit, with_submodules=submodules)
|
|
95
|
+
return Workspace.from_message(path, self.connection.follow(methods.CHECKOUT, request, on_progress))
|
|
96
|
+
|
|
97
|
+
def describe(self, path: str) -> Description:
|
|
98
|
+
"""What a workspace is on disk, without the network."""
|
|
99
|
+
response = self.connection.call(methods.DESCRIBE, messages.DescribeRequest(path=path))
|
|
100
|
+
return Description.from_message(response)
|
|
101
|
+
|
|
102
|
+
def publish(self, path: str, branch: str) -> Published:
|
|
103
|
+
"""Pushes what was committed in a workspace to a new branch, with the token gitd keeps."""
|
|
104
|
+
response = self.connection.call(methods.PUBLISH, messages.PublishRequest(path=path, branch=branch))
|
|
105
|
+
return Published.from_message(response)
|
|
106
|
+
|
|
107
|
+
def remove(self, path: str) -> None:
|
|
108
|
+
"""Deletes a workspace; its pool stays."""
|
|
109
|
+
self.connection.call(methods.REMOVE, messages.RemoveRequest(path=path))
|
|
110
|
+
|
|
111
|
+
def audit(
|
|
112
|
+
self,
|
|
113
|
+
*,
|
|
114
|
+
since: datetime | None = None,
|
|
115
|
+
url: str | None = None,
|
|
116
|
+
limit: int | None = None,
|
|
117
|
+
) -> list[AuditEntry]:
|
|
118
|
+
"""The audit log: every publish, done or refused, newest first; from since on, of
|
|
119
|
+
the repository at url, at most limit of them (100 by default, never more than 1000)."""
|
|
120
|
+
request: Any = messages.AuditRequest(url=url or "", limit=limit or 0)
|
|
121
|
+
if since is not None:
|
|
122
|
+
Timestamp.fill(request.since, since)
|
|
123
|
+
response = self.connection.call(methods.AUDIT, request)
|
|
124
|
+
return [AuditEntry.from_message(entry) for entry in response.entries]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""The channel to gitd's Unix socket, and how a call on it is made and read."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import grpc
|
|
10
|
+
from google.protobuf.message import Message
|
|
11
|
+
|
|
12
|
+
from .errors import GitdError
|
|
13
|
+
from .methods import Method
|
|
14
|
+
from .models import Progress
|
|
15
|
+
|
|
16
|
+
DEFAULT_SOCKET = "/run/gitd/gitd.sock"
|
|
17
|
+
SOCKET_VARIABLE = "GITD_SOCKET"
|
|
18
|
+
|
|
19
|
+
OnProgress = Callable[[Progress], None]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Connection:
|
|
23
|
+
"""A channel to gitd. The socket defaults to $GITD_SOCKET, then /run/gitd/gitd.sock; the
|
|
24
|
+
timeout, in seconds, bounds every call, which gitd otherwise bounds itself."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, socket: str | None, timeout: float | None) -> None:
|
|
27
|
+
self.socket = socket or os.environ.get(SOCKET_VARIABLE) or DEFAULT_SOCKET
|
|
28
|
+
self.timeout = timeout
|
|
29
|
+
self.channel = grpc.insecure_channel(f"unix:{self.socket}")
|
|
30
|
+
|
|
31
|
+
def close(self) -> None:
|
|
32
|
+
self.channel.close()
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def serialize(message: Message) -> bytes:
|
|
36
|
+
return message.SerializeToString()
|
|
37
|
+
|
|
38
|
+
def call(self, method: Method, request: Message) -> Any:
|
|
39
|
+
"""Makes a call that answers once."""
|
|
40
|
+
invoke: grpc.UnaryUnaryMultiCallable[Message, Any] = self.channel.unary_unary(
|
|
41
|
+
method.path,
|
|
42
|
+
request_serializer=self.serialize,
|
|
43
|
+
response_deserializer=method.response.FromString,
|
|
44
|
+
)
|
|
45
|
+
try:
|
|
46
|
+
return invoke(request, timeout=self.timeout)
|
|
47
|
+
except grpc.RpcError as error:
|
|
48
|
+
raise GitdError.from_rpc_error(error) from None
|
|
49
|
+
|
|
50
|
+
def follow(
|
|
51
|
+
self,
|
|
52
|
+
method: Method,
|
|
53
|
+
request: Message,
|
|
54
|
+
on_progress: OnProgress | None,
|
|
55
|
+
) -> Any:
|
|
56
|
+
"""Makes a call that streams its progress, and reads it to its result."""
|
|
57
|
+
invoke: grpc.UnaryStreamMultiCallable[Message, Any] = self.channel.unary_stream(
|
|
58
|
+
method.path,
|
|
59
|
+
request_serializer=self.serialize,
|
|
60
|
+
response_deserializer=method.response.FromString,
|
|
61
|
+
)
|
|
62
|
+
try:
|
|
63
|
+
for response in invoke(request, timeout=self.timeout):
|
|
64
|
+
event = response.WhichOneof("event")
|
|
65
|
+
if event == "progress" and on_progress is not None:
|
|
66
|
+
on_progress(Progress.from_message(response.progress))
|
|
67
|
+
elif event == "result":
|
|
68
|
+
return response.result
|
|
69
|
+
except grpc.RpcError as error:
|
|
70
|
+
raise GitdError.from_rpc_error(error) from None
|
|
71
|
+
raise GitdError("", "gitd ended the call without a result")
|