gitd-client 0.14.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_client-0.14.0/.gitignore +3 -0
- gitd_client-0.14.0/PKG-INFO +92 -0
- gitd_client-0.14.0/README.md +75 -0
- gitd_client-0.14.0/examples/agent.py +81 -0
- gitd_client-0.14.0/pyproject.toml +34 -0
- gitd_client-0.14.0/src/gitd/__init__.py +88 -0
- gitd_client-0.14.0/src/gitd/changes.py +77 -0
- gitd_client-0.14.0/src/gitd/client.py +245 -0
- gitd_client-0.14.0/src/gitd/comparison.py +111 -0
- gitd_client-0.14.0/src/gitd/connection.py +128 -0
- gitd_client-0.14.0/src/gitd/contract.json +3189 -0
- gitd_client-0.14.0/src/gitd/contract.py +50 -0
- gitd_client-0.14.0/src/gitd/credentials_calls.py +69 -0
- gitd_client-0.14.0/src/gitd/endpoint_calls.py +52 -0
- gitd_client-0.14.0/src/gitd/endpoint_tokens.py +55 -0
- gitd_client-0.14.0/src/gitd/errors.py +36 -0
- gitd_client-0.14.0/src/gitd/kept_credentials.py +107 -0
- gitd_client-0.14.0/src/gitd/messages.py +92 -0
- gitd_client-0.14.0/src/gitd/methods.py +58 -0
- gitd_client-0.14.0/src/gitd/models.py +369 -0
- gitd_client-0.14.0/src/gitd/publishing_calls.py +47 -0
- gitd_client-0.14.0/src/gitd/publishing_settings.py +216 -0
- gitd_client-0.14.0/src/gitd/timestamps.py +22 -0
- gitd_client-0.14.0/src/gitd/warm_layers.py +34 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: gitd-client
|
|
3
|
+
Version: 0.14.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 :: OS Independent
|
|
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-client
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
It is imported as `gitd`. Up to 0.13.0 it was published as `gitd`; from 0.14.0 it is
|
|
29
|
+
`gitd-client`, the name every gitd client has.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from gitd import Gitd, GitdError
|
|
33
|
+
|
|
34
|
+
with Gitd() as gitd: # $GITD_SOCKET, then /run/gitd/gitd.sock
|
|
35
|
+
workspace = gitd.prepare("https://github.com/acme/web", "/var/lib/gitd/workspaces/agent-1")
|
|
36
|
+
print(workspace.path, workspace.commit, workspace.strategy)
|
|
37
|
+
|
|
38
|
+
# … the agent works in workspace.path with plain git, and commits …
|
|
39
|
+
|
|
40
|
+
published = gitd.publish(workspace.path, "agents/fix-login")
|
|
41
|
+
gitd.remove(workspace.path)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
No call takes a credential: gitd reads and publishes with the ones it keeps.
|
|
45
|
+
|
|
46
|
+
| Method | What it does |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `prepare(url, path, *, branch, commit, submodules, full_history, strategy, on_progress)` | A workspace of a repository at a path under gitd's roots |
|
|
49
|
+
| `update(path, *, branch, submodules, on_progress)` | To the tip of its branch, or of the branch given |
|
|
50
|
+
| `checkout(path, commit, *, submodules, on_progress)` | To a commit, as a rollback does |
|
|
51
|
+
| `describe(path)` | Its repository, commit, branch, uncommitted changes and submodules |
|
|
52
|
+
| `publish(path, branch, *, requested_by, pull_request, provenance, sandbox, update)` | Its commits pushed to a new branch, or with `update` an earlier publish's branch moved forward; its pull request opened or updated |
|
|
53
|
+
| `remove(path)` | Deleted; its pool stays |
|
|
54
|
+
| `probe(url)` | Whether it can be read, its default branch and branches |
|
|
55
|
+
| `info()` | Which gitd answers, and how it makes workspaces here and why |
|
|
56
|
+
| `audit(*, since, url, limit)` | Every publish, done or refused, newest first |
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from gitd import PullRequestOptions
|
|
60
|
+
|
|
61
|
+
published = gitd.publish(
|
|
62
|
+
workspace.path, "agents/fix-login", update=True,
|
|
63
|
+
pull_request=PullRequestOptions(open=True, submodules=True, title="Fix the login", body="…"),
|
|
64
|
+
)
|
|
65
|
+
print(published.pull_request.url, published.pull_request.updated)
|
|
66
|
+
for submodule in published.submodule_pull_requests: # merged first, with a merge commit
|
|
67
|
+
print(submodule.path, submodule.pull_request or submodule.refused)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
A refusal raises `GitdError`, with `reason` (`AUTH_REQUIRED`, `NOT_FOUND`, `PATH_REFUSED`,
|
|
71
|
+
`BRANCH_REFUSED`, `BUSY`, …) and `said`, what git said. Decide from the reason, never from
|
|
72
|
+
the message.
|
|
73
|
+
|
|
74
|
+
[`examples/agent.py`](examples/agent.py) is an agent's whole round trip.
|
|
75
|
+
|
|
76
|
+
## Layout
|
|
77
|
+
|
|
78
|
+
| Path | What it is |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `src/gitd/client.py` | `Gitd`, the client |
|
|
81
|
+
| `src/gitd/models.py` | What gitd answers, as dataclasses |
|
|
82
|
+
| `src/gitd/errors.py` | `GitdError` |
|
|
83
|
+
| `src/gitd/timestamps.py` | Between the contract's timestamps and `datetime` |
|
|
84
|
+
| `src/gitd/connection.py` | The channel to the socket, and how calls are made |
|
|
85
|
+
| `src/gitd/methods.py` | The gitd methods called, by their gRPC path |
|
|
86
|
+
| `src/gitd/messages.py` | The messages of the contract the client uses |
|
|
87
|
+
| `src/gitd/contract.py` | Reads the contract at run time, with protobuf's public API |
|
|
88
|
+
| `src/gitd/contract.json` | The contract, built from [`proto/gitd/v1/gitd.proto`](../../proto/gitd/v1/gitd.proto) by `make generate` |
|
|
89
|
+
|
|
90
|
+
No code is generated: the client reads gitd's contract when it starts.
|
|
91
|
+
|
|
92
|
+
Python 3.11 or later, on Linux, macOS and Windows. Apache-2.0.
|
|
@@ -0,0 +1,75 @@
|
|
|
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-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
It is imported as `gitd`. Up to 0.13.0 it was published as `gitd`; from 0.14.0 it is
|
|
12
|
+
`gitd-client`, the name every gitd client has.
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from gitd import Gitd, GitdError
|
|
16
|
+
|
|
17
|
+
with Gitd() as gitd: # $GITD_SOCKET, then /run/gitd/gitd.sock
|
|
18
|
+
workspace = gitd.prepare("https://github.com/acme/web", "/var/lib/gitd/workspaces/agent-1")
|
|
19
|
+
print(workspace.path, workspace.commit, workspace.strategy)
|
|
20
|
+
|
|
21
|
+
# … the agent works in workspace.path with plain git, and commits …
|
|
22
|
+
|
|
23
|
+
published = gitd.publish(workspace.path, "agents/fix-login")
|
|
24
|
+
gitd.remove(workspace.path)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
No call takes a credential: gitd reads and publishes with the ones it keeps.
|
|
28
|
+
|
|
29
|
+
| Method | What it does |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `prepare(url, path, *, branch, commit, submodules, full_history, strategy, on_progress)` | A workspace of a repository at a path under gitd's roots |
|
|
32
|
+
| `update(path, *, branch, submodules, on_progress)` | To the tip of its branch, or of the branch given |
|
|
33
|
+
| `checkout(path, commit, *, submodules, on_progress)` | To a commit, as a rollback does |
|
|
34
|
+
| `describe(path)` | Its repository, commit, branch, uncommitted changes and submodules |
|
|
35
|
+
| `publish(path, branch, *, requested_by, pull_request, provenance, sandbox, update)` | Its commits pushed to a new branch, or with `update` an earlier publish's branch moved forward; its pull request opened or updated |
|
|
36
|
+
| `remove(path)` | Deleted; its pool stays |
|
|
37
|
+
| `probe(url)` | Whether it can be read, its default branch and branches |
|
|
38
|
+
| `info()` | Which gitd answers, and how it makes workspaces here and why |
|
|
39
|
+
| `audit(*, since, url, limit)` | Every publish, done or refused, newest first |
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from gitd import PullRequestOptions
|
|
43
|
+
|
|
44
|
+
published = gitd.publish(
|
|
45
|
+
workspace.path, "agents/fix-login", update=True,
|
|
46
|
+
pull_request=PullRequestOptions(open=True, submodules=True, title="Fix the login", body="…"),
|
|
47
|
+
)
|
|
48
|
+
print(published.pull_request.url, published.pull_request.updated)
|
|
49
|
+
for submodule in published.submodule_pull_requests: # merged first, with a merge commit
|
|
50
|
+
print(submodule.path, submodule.pull_request or submodule.refused)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
A refusal raises `GitdError`, with `reason` (`AUTH_REQUIRED`, `NOT_FOUND`, `PATH_REFUSED`,
|
|
54
|
+
`BRANCH_REFUSED`, `BUSY`, …) and `said`, what git said. Decide from the reason, never from
|
|
55
|
+
the message.
|
|
56
|
+
|
|
57
|
+
[`examples/agent.py`](examples/agent.py) is an agent's whole round trip.
|
|
58
|
+
|
|
59
|
+
## Layout
|
|
60
|
+
|
|
61
|
+
| Path | What it is |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `src/gitd/client.py` | `Gitd`, the client |
|
|
64
|
+
| `src/gitd/models.py` | What gitd answers, as dataclasses |
|
|
65
|
+
| `src/gitd/errors.py` | `GitdError` |
|
|
66
|
+
| `src/gitd/timestamps.py` | Between the contract's timestamps and `datetime` |
|
|
67
|
+
| `src/gitd/connection.py` | The channel to the socket, and how calls are made |
|
|
68
|
+
| `src/gitd/methods.py` | The gitd methods called, by their gRPC path |
|
|
69
|
+
| `src/gitd/messages.py` | The messages of the contract the client uses |
|
|
70
|
+
| `src/gitd/contract.py` | Reads the contract at run time, with protobuf's public API |
|
|
71
|
+
| `src/gitd/contract.json` | The contract, built from [`proto/gitd/v1/gitd.proto`](../../proto/gitd/v1/gitd.proto) by `make generate` |
|
|
72
|
+
|
|
73
|
+
No code is generated: the client reads gitd's contract when it starts.
|
|
74
|
+
|
|
75
|
+
Python 3.11 or later, on Linux, macOS and Windows. 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-client"
|
|
7
|
+
version = "0.14.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 :: OS Independent",
|
|
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,88 @@
|
|
|
1
|
+
"""Client for gitd: instant, secretless git checkouts for agent sandboxes."""
|
|
2
|
+
|
|
3
|
+
from .changes import ChangedFile, ChangeKind, Changes, Diff
|
|
4
|
+
from .client import Gitd
|
|
5
|
+
from .comparison import Comparison, Conflict, Event, EventKind, Side
|
|
6
|
+
from .connection import DEFAULT_SOCKET, SOCKET_VARIABLE
|
|
7
|
+
from .endpoint_calls import Endpoint
|
|
8
|
+
from .endpoint_tokens import IssuedToken, TokenInfo
|
|
9
|
+
from .errors import GitdError
|
|
10
|
+
from .kept_credentials import AppToCreate, Connected, CredentialKind, KeptCredential, SignIn
|
|
11
|
+
from .models import (
|
|
12
|
+
Access,
|
|
13
|
+
AuditEntry,
|
|
14
|
+
ConfigurationChange,
|
|
15
|
+
Description,
|
|
16
|
+
Info,
|
|
17
|
+
Layers,
|
|
18
|
+
PendingSubmodule,
|
|
19
|
+
Probe,
|
|
20
|
+
Progress,
|
|
21
|
+
Published,
|
|
22
|
+
PublishedRepository,
|
|
23
|
+
Refusal,
|
|
24
|
+
Strategy,
|
|
25
|
+
Submodule,
|
|
26
|
+
SubmodulePullRequest,
|
|
27
|
+
Workspace,
|
|
28
|
+
)
|
|
29
|
+
from .publishing_settings import (
|
|
30
|
+
OpenedPullRequest,
|
|
31
|
+
Opening,
|
|
32
|
+
Preset,
|
|
33
|
+
PresetOrigin,
|
|
34
|
+
Provenance,
|
|
35
|
+
PublishSettings,
|
|
36
|
+
PullRequestOptions,
|
|
37
|
+
PullRequestSettings,
|
|
38
|
+
)
|
|
39
|
+
from .warm_layers import WarmLayer
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"Comparison",
|
|
43
|
+
"Conflict",
|
|
44
|
+
"Event",
|
|
45
|
+
"EventKind",
|
|
46
|
+
"Side",
|
|
47
|
+
"ChangeKind",
|
|
48
|
+
"ChangedFile",
|
|
49
|
+
"Changes",
|
|
50
|
+
"Diff",
|
|
51
|
+
"AppToCreate",
|
|
52
|
+
"ConfigurationChange",
|
|
53
|
+
"Connected",
|
|
54
|
+
"CredentialKind",
|
|
55
|
+
"KeptCredential",
|
|
56
|
+
"OpenedPullRequest",
|
|
57
|
+
"Opening",
|
|
58
|
+
"Preset",
|
|
59
|
+
"PresetOrigin",
|
|
60
|
+
"Provenance",
|
|
61
|
+
"PublishSettings",
|
|
62
|
+
"PullRequestOptions",
|
|
63
|
+
"PullRequestSettings",
|
|
64
|
+
"SignIn",
|
|
65
|
+
"DEFAULT_SOCKET",
|
|
66
|
+
"SOCKET_VARIABLE",
|
|
67
|
+
"Access",
|
|
68
|
+
"AuditEntry",
|
|
69
|
+
"Description",
|
|
70
|
+
"Gitd",
|
|
71
|
+
"GitdError",
|
|
72
|
+
"Info",
|
|
73
|
+
"Layers",
|
|
74
|
+
"PendingSubmodule",
|
|
75
|
+
"Probe",
|
|
76
|
+
"Progress",
|
|
77
|
+
"Published",
|
|
78
|
+
"PublishedRepository",
|
|
79
|
+
"Refusal",
|
|
80
|
+
"Strategy",
|
|
81
|
+
"Submodule",
|
|
82
|
+
"SubmodulePullRequest",
|
|
83
|
+
"Workspace",
|
|
84
|
+
"WarmLayer",
|
|
85
|
+
"Endpoint",
|
|
86
|
+
"IssuedToken",
|
|
87
|
+
"TokenInfo",
|
|
88
|
+
]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""What was changed in a workspace, and its diff, as plain dataclasses built from the
|
|
2
|
+
messages gitd answers."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from enum import StrEnum
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from . import messages
|
|
11
|
+
|
|
12
|
+
KIND_PREFIX = "KIND_"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ChangeKind(StrEnum):
|
|
16
|
+
"""How a file changed: added, modified or deleted."""
|
|
17
|
+
|
|
18
|
+
ADDED = "added"
|
|
19
|
+
MODIFIED = "modified"
|
|
20
|
+
DELETED = "deleted"
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_message(cls, number: int) -> ChangeKind:
|
|
24
|
+
name = messages.CHANGE_KIND.name(number).removeprefix(KIND_PREFIX).lower()
|
|
25
|
+
return cls(name) if name in {member.value for member in cls} else cls.MODIFIED
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class ChangedFile:
|
|
30
|
+
"""A file changed, relative to the workspace, with its lines; a binary one counts none."""
|
|
31
|
+
|
|
32
|
+
path: str
|
|
33
|
+
kind: ChangeKind
|
|
34
|
+
added_lines: int
|
|
35
|
+
removed_lines: int
|
|
36
|
+
binary: bool
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def from_message(cls, file: Any) -> ChangedFile:
|
|
40
|
+
return cls(
|
|
41
|
+
path=file.path,
|
|
42
|
+
kind=ChangeKind.from_message(file.kind),
|
|
43
|
+
added_lines=file.added_lines,
|
|
44
|
+
removed_lines=file.removed_lines,
|
|
45
|
+
binary=file.binary,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class Changes:
|
|
51
|
+
"""What was changed in a workspace since gitd put it on base, committed or not.
|
|
52
|
+
from_layer says it was read from the agent's own layer, with no git run in the workspace."""
|
|
53
|
+
|
|
54
|
+
base: str
|
|
55
|
+
from_layer: bool
|
|
56
|
+
files: list[ChangedFile] = field(default_factory=list)
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def from_message(cls, response: Any) -> Changes:
|
|
60
|
+
return cls(
|
|
61
|
+
base=response.base,
|
|
62
|
+
from_layer=response.from_layer,
|
|
63
|
+
files=[ChangedFile.from_message(file) for file in response.files],
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True)
|
|
68
|
+
class Diff:
|
|
69
|
+
"""The unified diff of a workspace's changes; truncated says it was cut short."""
|
|
70
|
+
|
|
71
|
+
base: str
|
|
72
|
+
diff: str
|
|
73
|
+
truncated: bool
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def from_message(cls, response: Any) -> Diff:
|
|
77
|
+
return cls(base=response.base, diff=response.diff, truncated=response.truncated)
|