Skip to content

Commit 1ac7c66

Browse files
authored
feat(git): add git resource (#45)
* feat(git): add git resource
1 parent d7dea84 commit 1ac7c66

File tree

11 files changed

+270
-11
lines changed

11 files changed

+270
-11
lines changed
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1+
from .git import GitHead, WorkspaceGitManager
2+
from .resources import WorkspacesResource
13
from .schemas import (
4+
CommandInput,
5+
CommandOutput,
26
Workspace,
37
WorkspaceCreate,
4-
WorkspaceUpdate,
58
WorkspaceStatus,
6-
CommandInput,
7-
CommandOutput,
9+
WorkspaceUpdate,
810
)
9-
from .resources import WorkspacesResource
1011

1112
__all__ = [
1213
"Workspace",
@@ -16,4 +17,6 @@
1617
"WorkspacesResource",
1718
"CommandInput",
1819
"CommandOutput",
20+
"WorkspaceGitManager",
21+
"GitHead",
1922
]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .models import WorkspaceGitManager
2+
from .schema import GitHead
3+
4+
__all__ = ["WorkspaceGitManager", "GitHead"]
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from __future__ import annotations
2+
3+
import logging
4+
from typing import Optional
5+
6+
from ....core.handler import _APIOperationExecutor
7+
from ....http_client import APIHttpClient
8+
from .operations import (
9+
_GET_HEAD_OP,
10+
_PULL_OP,
11+
_PULL_WITH_REMOTE_AND_BRANCH_OP,
12+
_PULL_WITH_REMOTE_OP,
13+
)
14+
from .schema import GitHead
15+
16+
log = logging.getLogger(__name__)
17+
18+
19+
class WorkspaceGitManager(_APIOperationExecutor):
20+
"""Manager for git operations on a workspace."""
21+
22+
def __init__(self, http_client: APIHttpClient, workspace_id: int):
23+
self._http_client = http_client
24+
self._workspace_id = workspace_id
25+
self.id = workspace_id
26+
27+
async def get_head(self) -> GitHead:
28+
return await self._execute_operation(_GET_HEAD_OP)
29+
30+
async def pull(
31+
self,
32+
remote: Optional[str] = None,
33+
branch: Optional[str] = None,
34+
) -> None:
35+
if remote is not None and branch is not None:
36+
await self._execute_operation(
37+
_PULL_WITH_REMOTE_AND_BRANCH_OP, remote=remote, branch=branch
38+
)
39+
elif remote is not None:
40+
await self._execute_operation(_PULL_WITH_REMOTE_OP, remote=remote)
41+
else:
42+
await self._execute_operation(_PULL_OP)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from __future__ import annotations
2+
3+
from ....core.operations import APIOperation
4+
from .schema import GitHead
5+
6+
_GET_HEAD_OP = APIOperation(
7+
method="GET",
8+
endpoint_template="/workspaces/{id}/git/head",
9+
response_model=GitHead,
10+
)
11+
12+
_PULL_OP = APIOperation(
13+
method="POST",
14+
endpoint_template="/workspaces/{id}/git/pull",
15+
response_model=type(None),
16+
)
17+
18+
_PULL_WITH_REMOTE_OP = APIOperation(
19+
method="POST",
20+
endpoint_template="/workspaces/{id}/git/pull/{remote}",
21+
response_model=type(None),
22+
)
23+
24+
_PULL_WITH_REMOTE_AND_BRANCH_OP = APIOperation(
25+
method="POST",
26+
endpoint_template="/workspaces/{id}/git/pull/{remote}/{branch}",
27+
response_model=type(None),
28+
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from __future__ import annotations
2+
3+
from ....core.base import CamelModel
4+
5+
6+
class GitHead(CamelModel):
7+
head: str

src/codesphere/resources/workspace/pipeline/resources.py

Whitespace-only changes.

src/codesphere/resources/workspace/schemas.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from ...core.base import CamelModel
1010
from ...utils import update_model_fields
1111
from .envVars import EnvVar, WorkspaceEnvVarManager
12+
from .git import WorkspaceGitManager
1213
from .landscape import WorkspaceLandscapeManager
1314

1415
log = logging.getLogger(__name__)
@@ -137,3 +138,9 @@ def landscape(self) -> WorkspaceLandscapeManager:
137138
"""Manager for landscape operations (Multi Server Deployments)."""
138139
http_client = self.validate_http_client()
139140
return WorkspaceLandscapeManager(http_client, workspace_id=self.id)
141+
142+
@cached_property
143+
def git(self) -> WorkspaceGitManager:
144+
"""Manager for git operations (head, pull)."""
145+
http_client = self.validate_http_client()
146+
return WorkspaceGitManager(http_client, workspace_id=self.id)

tests/integration/conftest.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717

1818
def pytest_addoption(parser):
19-
"""Add custom command line options for integration tests."""
2019
parser.addoption(
2120
"--run-integration",
2221
action="store_true",
@@ -26,14 +25,12 @@ def pytest_addoption(parser):
2625

2726

2827
def pytest_configure(config):
29-
"""Register custom markers."""
3028
config.addinivalue_line(
3129
"markers", "integration: mark test as integration test (requires API token)"
3230
)
3331

3432

3533
def pytest_collection_modifyitems(config, items):
36-
"""Skip integration tests unless --run-integration is passed."""
3734
if config.getoption("--run-integration"):
3835
return
3936

@@ -121,19 +118,27 @@ async def test_workspaces(
121118
) -> AsyncGenerator[List[Workspace], None]:
122119
created_workspaces: List[Workspace] = []
123120

124-
for i in range(2):
125-
workspace_name = f"{TEST_WORKSPACE_PREFIX}-{i + 1}"
121+
workspace_configs = [
122+
{"name": f"{TEST_WORKSPACE_PREFIX}-1", "git_url": None},
123+
{
124+
"name": f"{TEST_WORKSPACE_PREFIX}-git",
125+
"git_url": "https://github.com/octocat/Hello-World.git",
126+
},
127+
]
128+
129+
for config in workspace_configs:
126130
payload = WorkspaceCreate(
127131
team_id=test_team_id,
128-
name=workspace_name,
132+
name=config["name"],
129133
plan_id=test_plan_id,
134+
git_url=config["git_url"],
130135
)
131136
try:
132137
workspace = await session_sdk_client.workspaces.create(payload=payload)
133138
created_workspaces.append(workspace)
134139
log.info(f"Created test workspace: {workspace.name} (ID: {workspace.id})")
135140
except Exception as e:
136-
log.error(f"Failed to create test workspace {workspace_name}: {e}")
141+
log.error(f"Failed to create test workspace {config['name']}: {e}")
137142
for ws in created_workspaces:
138143
try:
139144
await ws.delete()
@@ -155,3 +160,17 @@ async def test_workspaces(
155160
@pytest.fixture(scope="session")
156161
async def test_workspace(test_workspaces: List[Workspace]) -> Workspace:
157162
return test_workspaces[0]
163+
164+
165+
@pytest.fixture(scope="session")
166+
def git_workspace_id(test_workspaces: List[Workspace]) -> int:
167+
return test_workspaces[1].id
168+
169+
170+
@pytest.fixture
171+
async def workspace_with_git(
172+
sdk_client: CodesphereSDK, git_workspace_id: int
173+
) -> Workspace:
174+
workspace = await sdk_client.workspaces.get(workspace_id=git_workspace_id)
175+
await workspace.wait_until_running(timeout=120.0)
176+
return workspace

tests/integration/test_git.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import pytest
2+
3+
from codesphere.resources.workspace import Workspace
4+
5+
pytestmark = pytest.mark.integration
6+
7+
8+
class TestGitIntegration:
9+
@pytest.mark.asyncio
10+
async def test_get_head(self, workspace_with_git: Workspace):
11+
result = await workspace_with_git.git.get_head()
12+
13+
assert result.head is not None
14+
assert len(result.head) > 0
15+
16+
@pytest.mark.asyncio
17+
async def test_pull_default(self, workspace_with_git: Workspace):
18+
await workspace_with_git.git.pull()
19+
20+
@pytest.mark.asyncio
21+
async def test_pull_with_remote(self, workspace_with_git: Workspace):
22+
await workspace_with_git.git.pull(remote="origin")
23+
24+
@pytest.mark.asyncio
25+
async def test_pull_with_remote_and_branch(self, workspace_with_git: Workspace):
26+
await workspace_with_git.git.pull(remote="origin", branch="master")
File renamed without changes.

0 commit comments

Comments
 (0)