chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:47:58 +08:00
commit b16403ea71
789 changed files with 115226 additions and 0 deletions
@@ -0,0 +1,85 @@
from e2b.sandbox._git.args import (
build_add_args,
build_branches_args,
build_checkout_branch_args,
build_clone_plan,
build_commit_args,
build_credential_approve_command,
build_create_branch_args,
build_delete_branch_args,
build_git_command,
build_has_upstream_args,
build_pull_args,
build_push_args,
build_remote_add_args,
build_remote_add_shell_command,
build_remote_get_command,
build_remote_get_url_args,
build_remote_set_url_args,
build_reset_args,
build_restore_args,
build_status_args,
shell_escape,
)
from e2b.sandbox._git.auth import (
build_auth_error_message,
build_upstream_error_message,
is_auth_failure,
is_missing_upstream,
strip_credentials,
with_credentials,
)
from e2b.sandbox._git.config import resolve_config_scope
from e2b.sandbox._git.parse import (
derive_repo_dir_from_url,
parse_git_branches,
parse_git_status,
parse_remote_url,
)
from e2b.sandbox._git.types import (
ClonePlan,
GitBranches,
GitFileStatus,
GitResetMode,
GitStatus,
)
__all__ = [
"build_add_args",
"build_auth_error_message",
"build_branches_args",
"build_checkout_branch_args",
"build_clone_plan",
"build_commit_args",
"build_credential_approve_command",
"build_create_branch_args",
"build_delete_branch_args",
"build_git_command",
"build_has_upstream_args",
"build_pull_args",
"build_push_args",
"build_remote_add_args",
"build_remote_add_shell_command",
"build_remote_get_command",
"build_remote_get_url_args",
"build_remote_set_url_args",
"build_reset_args",
"build_restore_args",
"build_status_args",
"build_upstream_error_message",
"derive_repo_dir_from_url",
"is_auth_failure",
"is_missing_upstream",
"parse_git_branches",
"parse_git_status",
"parse_remote_url",
"resolve_config_scope",
"shell_escape",
"strip_credentials",
"with_credentials",
"ClonePlan",
"GitBranches",
"GitFileStatus",
"GitResetMode",
"GitStatus",
]
@@ -0,0 +1,363 @@
from typing import List, Optional
from e2b.exceptions import InvalidArgumentException
from e2b.sandbox._git.auth import strip_credentials, with_credentials
from e2b.sandbox._git.parse import derive_repo_dir_from_url
from e2b.sandbox._git.types import ClonePlan
def shell_escape(value: str) -> str:
"""
Escape a string for safe use in a shell command.
:param value: Value to escape
:return: Shell-escaped string
"""
return "'" + value.replace("'", "'\"'\"'") + "'"
def build_git_command(args: List[str], repo_path: Optional[str] = None) -> str:
"""
Build a shell-safe git command string.
:param args: Git command arguments
:param repo_path: Repository path for `git -C`, if provided
:return: Shell-safe git command
"""
parts = ["git"]
if repo_path:
parts.extend(["-C", repo_path])
parts.extend(args)
return " ".join(shell_escape(part) for part in parts)
def build_push_args(
remote_name: Optional[str],
*,
remote: Optional[str],
branch: Optional[str],
set_upstream: bool,
) -> List[str]:
"""
Build arguments for a git push command.
:param remote_name: Resolved remote name, if any
:param remote: Remote name override
:param branch: Branch name to push
:param set_upstream: Whether to set upstream tracking
:return: List of git push arguments
"""
args = ["push"]
target_remote = remote_name or remote
if set_upstream and target_remote:
args.append("--set-upstream")
if target_remote:
args.append(target_remote)
if branch:
args.append(branch)
return args
def build_pull_args(
remote: Optional[str],
branch: Optional[str],
remote_name: Optional[str] = None,
) -> List[str]:
"""
Build arguments for a git pull command.
:param remote: Remote name override
:param branch: Branch name to pull
:param remote_name: Resolved remote name, if any
:return: List of git pull arguments
"""
args = ["pull"]
target_remote = remote_name or remote
if target_remote:
args.append(target_remote)
if branch:
args.append(branch)
return args
def build_remote_add_args(name: str, url: str, fetch: bool) -> List[str]:
"""
Build arguments for a git remote add command.
:param name: Remote name
:param url: Remote URL
:param fetch: Whether to fetch after adding the remote
:return: List of git remote add arguments
"""
if not name or not url:
raise InvalidArgumentException(
"Both remote name and URL are required to add a git remote."
)
args = ["remote", "add"]
if fetch:
args.append("-f")
args.extend([name, url])
return args
def build_remote_add_shell_command(
args: List[str],
path: str,
name: str,
url: str,
fetch: bool,
) -> str:
"""
Build a shell command that adds or updates a remote and optionally fetches.
:param args: Base git remote add args
:param path: Repository path
:param name: Remote name
:param url: Remote URL
:param fetch: Whether to fetch after adding the remote
:return: Shell command string
"""
add_cmd = build_git_command(args, path)
set_url_cmd = build_git_command(build_remote_set_url_args(name, url), path)
cmd = f"{add_cmd} || {set_url_cmd}"
if fetch:
fetch_cmd = build_git_command(["fetch", name], path)
cmd = f"({cmd}) && {fetch_cmd}"
return cmd
def build_remote_get_url_args(name: str) -> List[str]:
"""
Build arguments for a git remote get-url command.
"""
return ["remote", "get-url", name]
def build_remote_set_url_args(name: str, url: str) -> List[str]:
"""
Build arguments for a git remote set-url command.
"""
return ["remote", "set-url", name, url]
def build_remote_get_command(path: str, name: str) -> str:
"""
Build a shell command that returns the remote URL or empty output.
:param path: Repository path
:param name: Remote name
:return: Shell command string
"""
if not name:
raise InvalidArgumentException("Remote name is required.")
return f"{build_git_command(build_remote_get_url_args(name), path)} || true"
def build_credential_approve_command(
username: str,
password: str,
host: str,
protocol: str,
) -> str:
"""
Build a git credential approve command for the given credentials.
"""
target_host = host.strip() or "github.com"
target_protocol = protocol.strip() or "https"
credential_input = "\n".join(
[
f"protocol={target_protocol}",
f"host={target_host}",
f"username={username}",
f"password={password}",
"",
"",
]
)
return (
f"printf %s {shell_escape(credential_input)} | "
f"{build_git_command(['credential', 'approve'])}"
)
def build_has_upstream_args() -> List[str]:
"""
Build arguments for a git upstream check command.
"""
return ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]
def build_status_args() -> List[str]:
"""
Build arguments for a git status command.
"""
return ["status", "--porcelain=1", "-b"]
def build_branches_args() -> List[str]:
"""
Build arguments for a git branch listing command.
"""
return ["branch", "--format=%(refname:short)\t%(HEAD)"]
def build_create_branch_args(branch: str) -> List[str]:
"""
Build arguments for a git checkout -b command.
"""
return ["checkout", "-b", branch]
def build_checkout_branch_args(branch: str) -> List[str]:
"""
Build arguments for a git checkout command.
"""
return ["checkout", branch]
def build_delete_branch_args(branch: str, force: bool) -> List[str]:
"""
Build arguments for a git branch delete command.
"""
return ["branch", "-D" if force else "-d", branch]
def build_add_args(files: Optional[List[str]], all: bool) -> List[str]:
"""
Build arguments for a git add command.
"""
args = ["add"]
if not files:
args.append("-A" if all else ".")
else:
args.append("--")
args.extend(files)
return args
def build_commit_args(
message: str,
author_name: Optional[str],
author_email: Optional[str],
allow_empty: bool,
) -> List[str]:
"""
Build arguments for a git commit command.
"""
args = ["commit", "-m", message]
if allow_empty:
args.append("--allow-empty")
author_args: List[str] = []
if author_name:
author_args.extend(["-c", f"user.name={author_name}"])
if author_email:
author_args.extend(["-c", f"user.email={author_email}"])
if author_args:
args = author_args + args
return args
def build_reset_args(
mode: Optional[str],
target: Optional[str],
paths: Optional[List[str]],
) -> List[str]:
"""
Build arguments for a git reset command.
"""
allowed_modes = ["soft", "mixed", "hard", "merge", "keep"]
if mode and mode not in allowed_modes:
raise InvalidArgumentException(
f"Reset mode must be one of {', '.join(allowed_modes)}."
)
args = ["reset"]
if mode:
args.append(f"--{mode}")
if target:
args.append(target)
if paths:
args.append("--")
args.extend(paths)
return args
def build_restore_args(
paths: List[str],
staged: Optional[bool],
worktree: Optional[bool],
source: Optional[str],
) -> List[str]:
"""
Build arguments for a git restore command.
"""
if not paths:
raise InvalidArgumentException("At least one path is required.")
resolved_staged = staged
resolved_worktree = worktree
if staged is None and worktree is None:
resolved_worktree = True
elif staged is True and worktree is None:
resolved_worktree = False
elif staged is None and worktree is not None:
resolved_staged = False
if resolved_staged is False and resolved_worktree is False:
raise InvalidArgumentException(
"At least one of staged or worktree must be true."
)
args = ["restore"]
if resolved_worktree:
args.append("--worktree")
if resolved_staged:
args.append("--staged")
if source:
args.extend(["--source", source])
args.append("--")
args.extend(paths)
return args
def build_clone_plan(
url: str,
path: Optional[str],
branch: Optional[str],
depth: Optional[int],
auth_username: Optional[str],
auth_password: Optional[str],
dangerously_store_credentials: bool,
) -> ClonePlan:
"""
Build clone arguments and metadata for post-clone credential stripping.
"""
clone_url = (
with_credentials(url, auth_username, auth_password)
if auth_username and auth_password
else url
)
sanitized_url = strip_credentials(clone_url)
should_strip = not dangerously_store_credentials and sanitized_url != clone_url
repo_path = path if not should_strip else path or derive_repo_dir_from_url(url)
if should_strip and not repo_path:
raise InvalidArgumentException(
"A destination path is required when using credentials without storing them."
)
args = ["clone", clone_url]
if branch:
args.extend(["--branch", branch, "--single-branch"])
if depth:
args.extend(["--depth", str(depth)])
if path:
args.append(path)
return ClonePlan(
args=args,
repo_path=repo_path,
sanitized_url=sanitized_url if should_strip else None,
should_strip=should_strip,
)
@@ -0,0 +1,132 @@
from typing import Optional
from urllib.parse import urlparse, urlunparse
from e2b.exceptions import InvalidArgumentException
from e2b.sandbox.commands.command_handle import CommandExitException
def with_credentials(url: str, username: Optional[str], password: Optional[str]) -> str:
"""
Add HTTP(S) credentials to a Git URL.
:param url: Git repository URL
:param username: Username for HTTP(S) authentication
:param password: Password or token for HTTP(S) authentication
:return: URL with embedded credentials
"""
if not username and not password:
return url
if not username or not password:
raise InvalidArgumentException(
"Both username and password are required when using Git credentials."
)
parsed = urlparse(url)
if parsed.scheme not in ("http", "https"):
raise InvalidArgumentException(
"Only http(s) Git URLs support username/password credentials."
)
netloc = f"{username}:{password}@{parsed.netloc}"
return urlunparse(parsed._replace(netloc=netloc))
def strip_credentials(url: str) -> str:
"""
Strip HTTP(S) credentials from a Git URL.
:param url: Git repository URL
:return: URL without embedded credentials
"""
parsed = urlparse(url)
if parsed.scheme not in ("http", "https"):
return url
if not parsed.username and not parsed.password:
return url
host = parsed.hostname or ""
if parsed.port:
host = f"{host}:{parsed.port}"
return urlunparse(parsed._replace(netloc=host))
def is_auth_failure(err: Exception) -> bool:
"""
Check whether a git command failed due to authentication issues.
:param err: Exception raised by a git command
:return: True when the error matches common authentication failures
"""
if not isinstance(err, CommandExitException):
return False
message = f"{err.stderr}\n{err.stdout}".lower()
auth_snippets = [
"authentication failed",
"terminal prompts disabled",
"could not read username",
"invalid username or password",
"access denied",
"permission denied",
"not authorized",
]
return any(snippet in message for snippet in auth_snippets)
def is_missing_upstream(err: Exception) -> bool:
"""
Check whether a git command failed due to missing upstream tracking.
:param err: Exception raised by a git command
:return: True when the error matches common upstream failures
"""
if not isinstance(err, CommandExitException):
return False
message = f"{err.stderr}\n{err.stdout}".lower()
upstream_snippets = [
"has no upstream branch",
"no upstream branch",
"no upstream configured",
"no tracking information for the current branch",
"no tracking information",
"set the remote as upstream",
"set the upstream branch",
"please specify which branch you want to merge with",
]
return any(snippet in message for snippet in upstream_snippets)
def build_auth_error_message(action: str, missing_password: bool) -> str:
"""
Build a git authentication error message for the given action.
:param action: Git action name
:param missing_password: Whether the password/token is missing
:return: Error message string
"""
if missing_password:
return f"Git {action} requires a password/token for private repositories."
return f"Git {action} requires credentials for private repositories."
def build_upstream_error_message(action: str) -> str:
"""
Build a git upstream tracking error message for the given action.
:param action: Git action name
:return: Error message string
"""
if action == "push":
return (
"Git push failed because no upstream branch is configured. "
"Set upstream once with set_upstream=True (and optional remote/branch), "
"or pass remote and branch explicitly."
)
return (
"Git pull failed because no upstream branch is configured. "
"Pass remote and branch explicitly, or set upstream once (push with "
"set_upstream=True or run: git branch --set-upstream-to=origin/<branch> <branch>)."
)
@@ -0,0 +1,32 @@
from typing import Optional
from e2b.exceptions import InvalidArgumentException
def resolve_config_scope(
scope: Optional[str], path: Optional[str]
) -> tuple[str, Optional[str]]:
"""
Resolve a git config scope flag and repository path.
:param scope: Requested scope ("global", "local", "system")
:param path: Repository path for local scope
:return: Tuple of (scope flag, repository path)
"""
scope_name = (scope or "global").strip().lower()
if scope_name not in {"global", "local", "system"}:
raise InvalidArgumentException(
"Git config scope must be one of: global, local, system."
)
if scope_name == "local":
if not path:
raise InvalidArgumentException(
"Repository path is required when scope is local."
)
return "--local", path
if scope_name == "system":
return "--system", None
return "--global", None
@@ -0,0 +1,222 @@
from typing import List, Optional
from urllib.parse import urlparse
from e2b.exceptions import InvalidArgumentException
from e2b.sandbox._git.types import GitBranches, GitFileStatus, GitStatus
def derive_repo_dir_from_url(url: str) -> Optional[str]:
"""
Derive the default repository directory name from a Git URL.
:param url: Git repository URL
:return: Repository directory name, if it can be determined
"""
parsed = urlparse(url)
if parsed.scheme not in ("http", "https"):
return None
trimmed_path = parsed.path.rstrip("/")
if not trimmed_path:
return None
last_segment = trimmed_path.split("/")[-1]
if not last_segment:
return None
return last_segment[:-4] if last_segment.endswith(".git") else last_segment
def _parse_ahead_behind(segment: Optional[str]) -> tuple[int, int]:
"""
Parse the ahead/behind segment from porcelain branch info.
:param segment: Segment text like "ahead 2, behind 1"
:return: Tuple of (ahead, behind)
"""
if not segment:
return 0, 0
ahead = 0
behind = 0
if "ahead" in segment:
try:
ahead = int(segment.split("ahead")[1].split(",")[0].strip())
except Exception:
ahead = 0
if "behind" in segment:
try:
behind = int(segment.split("behind")[1].split(",")[0].strip())
except Exception:
behind = 0
return ahead, behind
def _normalize_branch_name(name: str) -> str:
"""
Normalize branch names from porcelain branch output.
:param name: Raw branch name section
:return: Normalized branch name
"""
if name.startswith("HEAD (detached at "):
return name.replace("HEAD (detached at ", "").rstrip(")")
return (
name.replace("HEAD (no branch)", "HEAD")
.replace("No commits yet on ", "")
.replace("Initial commit on ", "")
)
def _derive_status(index_status: str, working_status: str) -> str:
"""
Derive a normalized status label from porcelain status characters.
:param index_status: Index status character
:param working_status: Working tree status character
:return: Normalized status label
"""
statuses = {index_status, working_status}
if "U" in statuses:
return "conflict"
if "R" in statuses:
return "renamed"
if "C" in statuses:
return "copied"
if "D" in statuses:
return "deleted"
if "A" in statuses:
return "added"
if "M" in statuses:
return "modified"
if "T" in statuses:
return "typechange"
if "?" in statuses:
return "untracked"
return "unknown"
def parse_git_status(output: str) -> GitStatus:
"""
Parse `git status --porcelain=1 -b` output into a structured object.
:param output: Git status output
:return: Parsed `GitStatus`
"""
lines = [line.rstrip() for line in output.split("\n") if line.strip()]
current_branch: Optional[str] = None
upstream: Optional[str] = None
ahead = 0
behind = 0
detached = False
file_status: List[GitFileStatus] = []
if not lines:
return GitStatus(
current_branch=current_branch,
upstream=upstream,
ahead=ahead,
behind=behind,
detached=detached,
file_status=file_status,
)
branch_line = lines[0]
if branch_line.startswith("## "):
branch_info = branch_line[3:]
ahead_start = branch_info.find(" [")
branch_part = branch_info if ahead_start == -1 else branch_info[:ahead_start]
ahead_part = None if ahead_start == -1 else branch_info[ahead_start + 2 : -1]
normalized_branch = _normalize_branch_name(branch_part)
raw_branch = branch_part
is_detached = raw_branch.startswith("HEAD (detached at ") or (
"detached" in raw_branch
)
if is_detached or normalized_branch.startswith("HEAD"):
detached = True
elif "..." in normalized_branch:
branch, upstream_branch = normalized_branch.split("...")
current_branch = branch or None
upstream = upstream_branch or None
else:
current_branch = normalized_branch or None
ahead, behind = _parse_ahead_behind(ahead_part)
for line in lines[1:]:
if line.startswith("?? "):
name = line[3:]
file_status.append(
GitFileStatus(
name=name,
status="untracked",
index_status="?",
working_tree_status="?",
staged=False,
)
)
continue
if len(line) < 3:
continue
index_status = line[0]
working_status = line[1]
path = line[3:]
renamed_from: Optional[str] = None
name = path
if " -> " in path:
renamed_from, name = path.split(" -> ", 1)
file_status.append(
GitFileStatus(
name=name,
status=_derive_status(index_status, working_status),
index_status=index_status,
working_tree_status=working_status,
staged=index_status not in (" ", "?"),
renamed_from=renamed_from,
)
)
return GitStatus(
current_branch=current_branch,
upstream=upstream,
ahead=ahead,
behind=behind,
detached=detached,
file_status=file_status,
)
def parse_git_branches(output: str) -> GitBranches:
"""
Parse `git branch --format=%(refname:short)\t%(HEAD)` output.
:param output: Git branch output
:return: Parsed `GitBranches`
"""
branches: List[str] = []
current_branch: Optional[str] = None
lines = [line.strip() for line in output.split("\n") if line.strip()]
for line in lines:
parts = line.split("\t")
name = parts[0]
branches.append(name)
if len(parts) > 1 and parts[1] == "*":
current_branch = name
return GitBranches(branches=branches, current_branch=current_branch)
def parse_remote_url(output: str, remote: str) -> str:
"""
Parse a git remote URL output and validate it's present.
:param output: Git remote get-url output
:param remote: Remote name for the error message
:return: Remote URL
"""
url = output.strip()
if not url:
raise InvalidArgumentException(
f'Remote "{remote}" URL not found in repository.'
)
return url
@@ -0,0 +1,149 @@
from dataclasses import dataclass
from typing import List, Optional
from typing_extensions import Literal
GitResetMode = Literal["soft", "mixed", "hard", "merge", "keep"]
"""Mode for a git reset operation."""
@dataclass
class GitFileStatus:
"""
Parsed git status entry for a file.
:param name: Path relative to the repository root
:param status: Normalized status string (e.g. "modified", "added")
:param index_status: Index status character from porcelain output
:param working_tree_status: Working tree status character from porcelain output
:param staged: Whether the change is staged
:param renamed_from: Original path when the file was renamed
"""
name: str
status: str
index_status: str
working_tree_status: str
staged: bool
renamed_from: Optional[str] = None
@dataclass
class GitStatus:
"""
Parsed git repository status.
:param current_branch: Current branch name, if available
:param upstream: Upstream branch name, if available
:param ahead: Number of commits the branch is ahead of upstream
:param behind: Number of commits the branch is behind upstream
:param detached: Whether HEAD is detached
:param file_status: List of file status entries
"""
current_branch: Optional[str]
upstream: Optional[str]
ahead: int
behind: int
detached: bool
file_status: List[GitFileStatus]
@property
def is_clean(self) -> bool:
"""
Return True when there are no tracked or untracked file changes.
"""
return len(self.file_status) == 0
@property
def has_changes(self) -> bool:
"""
Return True when there are any tracked or untracked file changes.
"""
return len(self.file_status) > 0
@property
def has_staged(self) -> bool:
"""
Return True when at least one file has staged changes.
"""
return any(item.staged for item in self.file_status)
@property
def has_untracked(self) -> bool:
"""
Return True when at least one file is untracked.
"""
return any(item.status == "untracked" for item in self.file_status)
@property
def has_conflicts(self) -> bool:
"""
Return True when at least one file is in conflict.
"""
return any(item.status == "conflict" for item in self.file_status)
@property
def total_count(self) -> int:
"""
Return the total number of changed files.
"""
return len(self.file_status)
@property
def staged_count(self) -> int:
"""
Return the number of files with staged changes.
"""
return sum(1 for item in self.file_status if item.staged)
@property
def unstaged_count(self) -> int:
"""
Return the number of files with unstaged changes.
"""
return sum(1 for item in self.file_status if not item.staged)
@property
def untracked_count(self) -> int:
"""
Return the number of untracked files.
"""
return sum(1 for item in self.file_status if item.status == "untracked")
@property
def conflict_count(self) -> int:
"""
Return the number of files with merge conflicts.
"""
return sum(1 for item in self.file_status if item.status == "conflict")
@dataclass
class GitBranches:
"""
Parsed git branch list.
:param branches: List of branch names
:param current_branch: Current branch name, if available
"""
branches: List[str]
current_branch: Optional[str]
@dataclass
class ClonePlan:
"""
Prepared arguments and metadata for git clone.
:param args: Command arguments for git clone
:param repo_path: Repository path to use for post-clone adjustments
:param sanitized_url: Credential-stripped URL to restore
:param should_strip: Whether to reset the remote URL after clone
"""
args: List[str]
repo_path: Optional[str]
sanitized_url: Optional[str]
should_strip: bool