devs-common 2.0.6__tar.gz → 2.0.8__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.
- {devs_common-2.0.6 → devs_common-2.0.8}/PKG-INFO +1 -1
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/config.py +18 -1
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/core/container.py +40 -4
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/Dockerfile +7 -3
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/utils/__init__.py +4 -2
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/utils/docker_client.py +15 -0
- devs_common-2.0.8/devs_common/utils/repo_cache.py +227 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common.egg-info/PKG-INFO +1 -1
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common.egg-info/SOURCES.txt +2 -1
- {devs_common-2.0.6 → devs_common-2.0.8}/pyproject.toml +1 -1
- {devs_common-2.0.6 → devs_common-2.0.8}/LICENSE +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/README.md +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/__init__.py +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/core/__init__.py +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/core/project.py +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/core/workspace.py +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/devs_config.py +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/exceptions.py +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/devcontainer.json +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/scripts/copy-ssh.sh +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/scripts/post-create-wrapper.sh +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/scripts/python-venv-integration.sh +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/scripts/screenshot-webapp.sh +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/scripts/setup-workspace.sh +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/scripts/start-tunnel.sh +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/scripts/tunnel-status.sh +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/scripts/webapp-screenshot.py +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/sudo-scripts/init-firewall.sh +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/sudo-scripts/setup-devs-env.sh +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/sudo-scripts/setup-vscode-cache.sh +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/sudo-scripts/start-services.sh +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/vscode-settings-reference.md +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/utils/config_hash.py +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/utils/console.py +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/utils/devcontainer.py +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/utils/devcontainer_template.py +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/utils/file_utils.py +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common/utils/git_utils.py +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common.egg-info/dependency_links.txt +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common.egg-info/requires.txt +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/devs_common.egg-info/top_level.txt +0 -0
- {devs_common-2.0.6 → devs_common-2.0.8}/setup.cfg +0 -0
|
@@ -9,11 +9,25 @@ from abc import ABC, abstractmethod
|
|
|
9
9
|
class BaseConfig(ABC):
|
|
10
10
|
"""Base configuration class for devs ecosystem packages."""
|
|
11
11
|
|
|
12
|
+
# Default directories shared across CLI and webhook
|
|
13
|
+
CLAUDE_CONFIG_DIR = Path.home() / ".devs" / "claudeconfig"
|
|
14
|
+
CODEX_CONFIG_DIR = Path.home() / ".devs" / "codexconfig"
|
|
15
|
+
VSCODE_CLI_DIR = Path.home() / ".devs" / "vscode-cli"
|
|
16
|
+
|
|
12
17
|
def __init__(self) -> None:
|
|
13
18
|
"""Initialize base configuration."""
|
|
14
19
|
self._workspaces_dir: Optional[Path] = None
|
|
15
20
|
self._bridge_dir: Optional[Path] = None
|
|
16
21
|
self._project_prefix: Optional[str] = None
|
|
22
|
+
|
|
23
|
+
claude_config_env = os.getenv("DEVS_CLAUDE_CONFIG_DIR")
|
|
24
|
+
self.claude_config_dir = Path(claude_config_env) if claude_config_env else self.CLAUDE_CONFIG_DIR
|
|
25
|
+
|
|
26
|
+
codex_config_env = os.getenv("DEVS_CODEX_CONFIG_DIR")
|
|
27
|
+
self.codex_config_dir = Path(codex_config_env) if codex_config_env else self.CODEX_CONFIG_DIR
|
|
28
|
+
|
|
29
|
+
vscode_cli_env = os.getenv("DEVS_VSCODE_CLI_DIR")
|
|
30
|
+
self.vscode_cli_dir = Path(vscode_cli_env) if vscode_cli_env else self.VSCODE_CLI_DIR
|
|
17
31
|
|
|
18
32
|
@property
|
|
19
33
|
def workspaces_dir(self) -> Path:
|
|
@@ -87,4 +101,7 @@ class BaseConfig(ABC):
|
|
|
87
101
|
def ensure_directories(self) -> None:
|
|
88
102
|
"""Ensure required directories exist."""
|
|
89
103
|
self.workspaces_dir.mkdir(parents=True, exist_ok=True)
|
|
90
|
-
self.bridge_dir.mkdir(parents=True, exist_ok=True)
|
|
104
|
+
self.bridge_dir.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
self.claude_config_dir.mkdir(parents=True, exist_ok=True)
|
|
106
|
+
self.codex_config_dir.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
self.vscode_cli_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -293,9 +293,11 @@ class ContainerManager:
|
|
|
293
293
|
self.docker.stop_container(existing_container['name'])
|
|
294
294
|
self.docker.remove_container(existing_container['name'], force=True)
|
|
295
295
|
else:
|
|
296
|
-
# Container exists but not running,
|
|
297
|
-
console.print(f"
|
|
298
|
-
self.docker.
|
|
296
|
+
# Container exists but not running with matching config, just restart it
|
|
297
|
+
console.print(f" 🔄 Restarting stopped container...")
|
|
298
|
+
self.docker.start_container(existing_container['name'])
|
|
299
|
+
console.print(f" ✅ Container restarted successfully")
|
|
300
|
+
return True
|
|
299
301
|
else:
|
|
300
302
|
if rebuild_needed or force_rebuild:
|
|
301
303
|
console.print(f" 🔨 Rebuild needed (devcontainer files changed or force_rebuild)")
|
|
@@ -467,7 +469,41 @@ class ContainerManager:
|
|
|
467
469
|
|
|
468
470
|
except DockerError as e:
|
|
469
471
|
raise ContainerError(f"Failed to list containers: {e}")
|
|
470
|
-
|
|
472
|
+
|
|
473
|
+
@staticmethod
|
|
474
|
+
def list_all_containers() -> List[ContainerInfo]:
|
|
475
|
+
"""List all devs-managed containers across all projects.
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
List of ContainerInfo objects sorted by project name then dev name
|
|
479
|
+
"""
|
|
480
|
+
try:
|
|
481
|
+
docker_client = DockerClient()
|
|
482
|
+
containers = docker_client.find_containers_by_labels({"devs.managed": "true"})
|
|
483
|
+
|
|
484
|
+
result = []
|
|
485
|
+
for container_data in containers:
|
|
486
|
+
dev_name = container_data['labels'].get('devs.dev', 'unknown')
|
|
487
|
+
project_name = container_data['labels'].get('devs.project', 'unknown')
|
|
488
|
+
|
|
489
|
+
container_info = ContainerInfo(
|
|
490
|
+
name=container_data['name'],
|
|
491
|
+
dev_name=dev_name,
|
|
492
|
+
project_name=project_name,
|
|
493
|
+
status=container_data['status'],
|
|
494
|
+
container_id=container_data['id'],
|
|
495
|
+
created=_parse_docker_timestamp(container_data['created']),
|
|
496
|
+
labels=container_data['labels']
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
result.append(container_info)
|
|
500
|
+
|
|
501
|
+
result.sort(key=lambda c: (c.project_name, c.dev_name))
|
|
502
|
+
return result
|
|
503
|
+
|
|
504
|
+
except DockerError as e:
|
|
505
|
+
raise ContainerError(f"Failed to list containers: {e}")
|
|
506
|
+
|
|
471
507
|
def find_aborted_containers(self, all_projects: bool = False) -> List[ContainerInfo]:
|
|
472
508
|
"""Find aborted devs containers that failed during setup.
|
|
473
509
|
|
|
@@ -119,8 +119,11 @@ RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/
|
|
|
119
119
|
-a "export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
|
|
120
120
|
-x
|
|
121
121
|
|
|
122
|
-
# Install Claude CLI
|
|
123
|
-
RUN
|
|
122
|
+
# Install Claude CLI via native installer
|
|
123
|
+
RUN curl -fsSL https://claude.ai/install.sh | bash
|
|
124
|
+
|
|
125
|
+
# Install OpenAI Codex CLI
|
|
126
|
+
RUN npm install -g @openai/codex
|
|
124
127
|
|
|
125
128
|
# Install VS Code CLI for tunnel support (needs root for /usr/local/bin)
|
|
126
129
|
# Using the standalone CLI which supports the 'tunnel' command
|
|
@@ -140,7 +143,8 @@ RUN for shell_rc in /home/node/.zshrc /home/node/.bashrc; do \
|
|
|
140
143
|
echo "export CLAUDE_CONFIG_DIR=\"/home/node/claudeconfig\"" >> "$shell_rc" && \
|
|
141
144
|
echo "export CODEX_CONFIG_HOME=\"/home/node/codexconfig\"" >> "$shell_rc" && \
|
|
142
145
|
echo "export VSCODE_CLI_DATA_DIR=\"/home/node/.vscode/cli\"" >> "$shell_rc" && \
|
|
143
|
-
echo "
|
|
146
|
+
echo "export PATH=\"/home/node/.local/bin:\$PATH\"" >> "$shell_rc" && \
|
|
147
|
+
echo "alias claude=\"claude --dangerously-skip-permissions\"" >> "$shell_rc" && \
|
|
144
148
|
echo "alias codex=\"/usr/local/share/npm-global/bin/codex --full-auto\"" >> "$shell_rc" && \
|
|
145
149
|
echo "" >> "$shell_rc" && \
|
|
146
150
|
echo "# Auto-activate Python venv if it exists" >> "$shell_rc" && \
|
|
@@ -16,10 +16,11 @@ from .git_utils import (
|
|
|
16
16
|
|
|
17
17
|
from .docker_client import DockerClient
|
|
18
18
|
from .devcontainer import DevContainerCLI
|
|
19
|
+
from .repo_cache import RepoCache
|
|
19
20
|
|
|
20
21
|
__all__ = [
|
|
21
22
|
"copy_file_list",
|
|
22
|
-
"copy_directory_tree",
|
|
23
|
+
"copy_directory_tree",
|
|
23
24
|
"safe_remove_directory",
|
|
24
25
|
"ensure_directory_exists",
|
|
25
26
|
"get_directory_size",
|
|
@@ -27,5 +28,6 @@ __all__ = [
|
|
|
27
28
|
"get_tracked_files",
|
|
28
29
|
"is_git_repository",
|
|
29
30
|
"DockerClient",
|
|
30
|
-
"DevContainerCLI"
|
|
31
|
+
"DevContainerCLI",
|
|
32
|
+
"RepoCache",
|
|
31
33
|
]
|
|
@@ -81,6 +81,21 @@ class DockerClient:
|
|
|
81
81
|
except DockerException as e:
|
|
82
82
|
raise DockerError(f"Error checking container status {name}: {e}")
|
|
83
83
|
|
|
84
|
+
def start_container(self, name: str) -> None:
|
|
85
|
+
"""Start a stopped container.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
name: Container name
|
|
89
|
+
|
|
90
|
+
Raises:
|
|
91
|
+
DockerError: If starting fails
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
container = self.client.containers.get(name)
|
|
95
|
+
container.start()
|
|
96
|
+
except DockerException as e:
|
|
97
|
+
raise DockerError(f"Error starting container {name}: {e}")
|
|
98
|
+
|
|
84
99
|
def stop_container(self, name: str) -> None:
|
|
85
100
|
"""Stop a container.
|
|
86
101
|
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Repository cache for cloning GitHub repos locally."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import shutil
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
|
|
9
|
+
from ..exceptions import DevsError
|
|
10
|
+
from .console import get_console
|
|
11
|
+
|
|
12
|
+
console = get_console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RepoCache:
|
|
16
|
+
"""Manages a local cache of cloned GitHub repositories.
|
|
17
|
+
|
|
18
|
+
Repos are cloned into a cache directory (default: ~/.devs/repocache/)
|
|
19
|
+
using the naming convention org-repo (e.g. ideonate-devs).
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
cache_dir: Directory for cached repos. Defaults to ~/.devs/repocache/.
|
|
23
|
+
token: GitHub token for private repo access. If not provided, falls
|
|
24
|
+
back to GH_TOKEN / GITHUB_TOKEN environment variables.
|
|
25
|
+
default_branches: Ordered list of branch names to try when no explicit
|
|
26
|
+
branch is requested. Defaults to detecting via symbolic-ref, then
|
|
27
|
+
trying "main" and "master".
|
|
28
|
+
clean: If True, run ``git clean -fd`` after checkout to remove
|
|
29
|
+
untracked files from previous runs.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
cache_dir: Optional[Path] = None,
|
|
35
|
+
token: Optional[str] = None,
|
|
36
|
+
default_branches: Optional[List[str]] = None,
|
|
37
|
+
clean: bool = False,
|
|
38
|
+
) -> None:
|
|
39
|
+
self.cache_dir = cache_dir or Path.home() / ".devs" / "repocache"
|
|
40
|
+
self._token = token
|
|
41
|
+
self._default_branches = default_branches
|
|
42
|
+
self._clean = clean
|
|
43
|
+
|
|
44
|
+
def _repo_name_to_dir_name(self, repo_name: str) -> str:
|
|
45
|
+
"""Convert org/repo to org-repo directory name."""
|
|
46
|
+
return repo_name.replace("/", "-").lower()
|
|
47
|
+
|
|
48
|
+
def _get_token(self) -> Optional[str]:
|
|
49
|
+
"""Return the configured token, falling back to environment variables."""
|
|
50
|
+
if self._token:
|
|
51
|
+
return self._token
|
|
52
|
+
return os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN")
|
|
53
|
+
|
|
54
|
+
def _build_clone_url(self, repo_name: str) -> str:
|
|
55
|
+
"""Build clone URL, using token if available for private repos."""
|
|
56
|
+
token = self._get_token()
|
|
57
|
+
if token:
|
|
58
|
+
return f"https://x-access-token:{token}@github.com/{repo_name}.git"
|
|
59
|
+
return f"https://github.com/{repo_name}.git"
|
|
60
|
+
|
|
61
|
+
def get_repo_path(self, repo_name: str) -> Path:
|
|
62
|
+
"""Return the local cache path for a repo without cloning.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
repo_name: GitHub repo in org/repo format.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Path where the repo would be cached.
|
|
69
|
+
"""
|
|
70
|
+
return self.cache_dir / self._repo_name_to_dir_name(repo_name)
|
|
71
|
+
|
|
72
|
+
def ensure_repo(self, repo_name: str, branch: Optional[str] = None) -> Path:
|
|
73
|
+
"""Ensure a repository is cloned and up-to-date in the cache.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
repo_name: GitHub repo in org/repo format (e.g. "ideonate/devs")
|
|
77
|
+
branch: Optional branch to checkout. Defaults to repo's default branch.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Path to the cached repository directory.
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
DevsError: If cloning or updating fails.
|
|
84
|
+
"""
|
|
85
|
+
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
|
|
87
|
+
dir_name = self._repo_name_to_dir_name(repo_name)
|
|
88
|
+
repo_path = self.cache_dir / dir_name
|
|
89
|
+
|
|
90
|
+
if repo_path.exists() and (repo_path / ".git").exists():
|
|
91
|
+
self._update_repo(repo_path, repo_name, branch)
|
|
92
|
+
else:
|
|
93
|
+
self._clone_repo(repo_path, repo_name, branch)
|
|
94
|
+
|
|
95
|
+
return repo_path
|
|
96
|
+
|
|
97
|
+
def _clone_repo(self, repo_path: Path, repo_name: str, branch: Optional[str] = None) -> None:
|
|
98
|
+
"""Clone a repository into the cache."""
|
|
99
|
+
# Remove directory if it exists but isn't a valid git repo
|
|
100
|
+
if repo_path.exists():
|
|
101
|
+
shutil.rmtree(repo_path)
|
|
102
|
+
|
|
103
|
+
clone_url = self._build_clone_url(repo_name)
|
|
104
|
+
console.print(f" Cloning {repo_name} into cache...")
|
|
105
|
+
|
|
106
|
+
repo_path.parent.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
|
|
108
|
+
cmd = ["git", "clone", clone_url, str(repo_path)]
|
|
109
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
110
|
+
if result.returncode != 0:
|
|
111
|
+
raise DevsError(
|
|
112
|
+
f"Failed to clone {repo_name}: {result.stderr.strip()}"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
target = branch or self._detect_branch(repo_path)
|
|
116
|
+
if target:
|
|
117
|
+
self._checkout_branch(repo_path, target)
|
|
118
|
+
|
|
119
|
+
def _update_repo(self, repo_path: Path, repo_name: str, branch: Optional[str] = None) -> None:
|
|
120
|
+
"""Fetch latest changes for an existing cached repo."""
|
|
121
|
+
console.print(f" Updating cached repo {repo_name}...")
|
|
122
|
+
|
|
123
|
+
# Update remote URL in case token changed
|
|
124
|
+
clone_url = self._build_clone_url(repo_name)
|
|
125
|
+
subprocess.run(
|
|
126
|
+
["git", "remote", "set-url", "origin", clone_url],
|
|
127
|
+
cwd=repo_path,
|
|
128
|
+
capture_output=True,
|
|
129
|
+
text=True,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
result = subprocess.run(
|
|
133
|
+
["git", "fetch", "--all"],
|
|
134
|
+
cwd=repo_path,
|
|
135
|
+
capture_output=True,
|
|
136
|
+
text=True,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if result.returncode != 0:
|
|
140
|
+
# Fetch failed – re-clone from scratch
|
|
141
|
+
console.print(f" Fetch failed, re-cloning {repo_name}...")
|
|
142
|
+
self._clone_repo(repo_path, repo_name, branch)
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
# Checkout the requested branch (or default)
|
|
146
|
+
target = branch or self._detect_branch(repo_path)
|
|
147
|
+
if target:
|
|
148
|
+
self._checkout_branch(repo_path, target)
|
|
149
|
+
|
|
150
|
+
def _checkout_branch(self, repo_path: Path, branch: str) -> None:
|
|
151
|
+
"""Checkout a specific branch, pulling latest changes."""
|
|
152
|
+
result = subprocess.run(
|
|
153
|
+
["git", "checkout", "-f", branch],
|
|
154
|
+
cwd=repo_path,
|
|
155
|
+
capture_output=True,
|
|
156
|
+
text=True,
|
|
157
|
+
)
|
|
158
|
+
if result.returncode != 0:
|
|
159
|
+
raise DevsError(
|
|
160
|
+
f"Failed to checkout branch '{branch}': {result.stderr.strip()}"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Reset to match remote so we pick up fetched changes
|
|
164
|
+
subprocess.run(
|
|
165
|
+
["git", "reset", "--hard", f"origin/{branch}"],
|
|
166
|
+
cwd=repo_path,
|
|
167
|
+
capture_output=True,
|
|
168
|
+
text=True,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
if self._clean:
|
|
172
|
+
self._clean_untracked(repo_path)
|
|
173
|
+
|
|
174
|
+
def _clean_untracked(self, repo_path: Path) -> None:
|
|
175
|
+
"""Remove untracked files and directories from the repo."""
|
|
176
|
+
subprocess.run(
|
|
177
|
+
["git", "clean", "-fd"],
|
|
178
|
+
cwd=repo_path,
|
|
179
|
+
capture_output=True,
|
|
180
|
+
text=True,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def _detect_branch(self, repo_path: Path) -> Optional[str]:
|
|
184
|
+
"""Detect the default branch to checkout.
|
|
185
|
+
|
|
186
|
+
If ``default_branches`` was provided at construction, tries each in
|
|
187
|
+
order. Otherwise falls back to symbolic-ref detection then main/master.
|
|
188
|
+
"""
|
|
189
|
+
if self._default_branches:
|
|
190
|
+
for branch in self._default_branches:
|
|
191
|
+
result = subprocess.run(
|
|
192
|
+
["git", "rev-parse", "--verify", f"origin/{branch}"],
|
|
193
|
+
cwd=repo_path,
|
|
194
|
+
capture_output=True,
|
|
195
|
+
text=True,
|
|
196
|
+
)
|
|
197
|
+
if result.returncode == 0:
|
|
198
|
+
return branch
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
return self._get_default_branch(repo_path)
|
|
202
|
+
|
|
203
|
+
def _get_default_branch(self, repo_path: Path) -> Optional[str]:
|
|
204
|
+
"""Detect the default branch of a repo (main or master)."""
|
|
205
|
+
result = subprocess.run(
|
|
206
|
+
["git", "symbolic-ref", "refs/remotes/origin/HEAD"],
|
|
207
|
+
cwd=repo_path,
|
|
208
|
+
capture_output=True,
|
|
209
|
+
text=True,
|
|
210
|
+
)
|
|
211
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
212
|
+
# Output like "refs/remotes/origin/main"
|
|
213
|
+
ref = result.stdout.strip()
|
|
214
|
+
return ref.split("/")[-1]
|
|
215
|
+
|
|
216
|
+
# Fallback: try common branch names
|
|
217
|
+
for branch in ("main", "master"):
|
|
218
|
+
result = subprocess.run(
|
|
219
|
+
["git", "rev-parse", "--verify", f"origin/{branch}"],
|
|
220
|
+
cwd=repo_path,
|
|
221
|
+
capture_output=True,
|
|
222
|
+
text=True,
|
|
223
|
+
)
|
|
224
|
+
if result.returncode == 0:
|
|
225
|
+
return branch
|
|
226
|
+
|
|
227
|
+
return None
|
|
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
|
{devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/scripts/post-create-wrapper.sh
RENAMED
|
File without changes
|
{devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/scripts/python-venv-integration.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/sudo-scripts/setup-devs-env.sh
RENAMED
|
File without changes
|
{devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/sudo-scripts/setup-vscode-cache.sh
RENAMED
|
File without changes
|
{devs_common-2.0.6 → devs_common-2.0.8}/devs_common/templates/sudo-scripts/start-services.sh
RENAMED
|
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
|