Files
e2b-dev--e2b/packages/python-sdk/e2b/sandbox_sync/git.py
T
2026-07-13 12:47:58 +08:00

1078 lines
36 KiB
Python

from typing import Dict, List, Optional
from e2b.sandbox._git import (
GitBranches,
GitResetMode,
GitStatus,
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,
is_auth_failure,
is_missing_upstream,
parse_git_branches,
parse_git_status,
parse_remote_url,
resolve_config_scope,
with_credentials,
)
from e2b.exceptions import (
GitAuthException,
GitUpstreamException,
InvalidArgumentException,
)
from e2b.sandbox.commands.command_handle import CommandExitException
from e2b.sandbox_sync.commands.command import Commands
DEFAULT_GIT_ENV = {"GIT_TERMINAL_PROMPT": "0"}
class Git:
"""
Module for running git operations in the sandbox.
"""
def __init__(self, commands: Commands) -> None:
"""
Create a Git helper bound to the sandbox command runner.
:param commands: Command runner used to execute git commands
"""
self._commands = commands
def _run_git(
self,
args: List[str],
repo_path: Optional[str],
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
):
"""
Build and execute a git command inside the sandbox.
:param args: Git arguments to pass to the git binary
:param repo_path: Repository path used with `git -C`, if provided
:param envs: Extra environment variables for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Command result from the command runner
"""
cmd = build_git_command(args, repo_path)
merged_envs = {**DEFAULT_GIT_ENV, **(envs or {})}
return self._commands.run(
cmd,
envs=merged_envs,
user=user,
cwd=cwd,
timeout=timeout,
request_timeout=request_timeout,
)
def _run_shell(
self,
cmd: str,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
):
"""
Execute a raw shell command while applying default git environment variables.
:param cmd: Shell command to execute
:param envs: Extra environment variables for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Command result from the command runner
"""
merged_envs = {**DEFAULT_GIT_ENV, **(envs or {})}
return self._commands.run(
cmd,
envs=merged_envs,
user=user,
cwd=cwd,
timeout=timeout,
request_timeout=request_timeout,
)
def _has_upstream(
self,
path: str,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
) -> bool:
try:
result = self._run_git(
build_has_upstream_args(),
path,
envs,
user,
cwd,
timeout,
request_timeout,
)
return bool(result.stdout.strip())
except Exception:
return False
def _get_remote_url(
self,
path: str,
remote: str,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
) -> str:
result = self._run_git(
build_remote_get_url_args(remote),
path,
envs,
user,
cwd,
timeout,
request_timeout,
)
return parse_remote_url(result.stdout, remote)
def _resolve_remote_name(
self,
path: str,
remote: Optional[str],
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
) -> str:
if remote:
return remote
result = self._run_git(
["remote"],
path,
envs,
user,
cwd,
timeout,
request_timeout,
)
remotes = [line.strip() for line in result.stdout.splitlines() if line.strip()]
if len(remotes) == 1:
return remotes[0]
raise InvalidArgumentException(
"Remote is required when using username/password and the repository has multiple remotes."
)
def _with_remote_credentials(
self,
path: str,
remote: str,
username: str,
password: str,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
operation=None,
):
original_url = self._get_remote_url(
path, remote, envs, user, cwd, timeout, request_timeout
)
credential_url = with_credentials(original_url, username, password)
self._run_git(
["remote", "set-url", remote, credential_url],
path,
envs,
user,
cwd,
timeout,
request_timeout,
)
result = None
operation_error: Exception | None = None
try:
if operation is None:
raise InvalidArgumentException("Operation is required.")
result = operation()
except Exception as err:
operation_error = err
restore_error: Exception | None = None
try:
self._run_git(
["remote", "set-url", remote, original_url],
path,
envs,
user,
cwd,
timeout,
request_timeout,
)
except Exception as err:
restore_error = err
if operation_error:
raise operation_error
if restore_error:
raise restore_error
return result
def clone(
self,
url: str,
path: Optional[str] = None,
branch: Optional[str] = None,
depth: Optional[int] = None,
username: Optional[str] = None,
password: Optional[str] = None,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
dangerously_store_credentials: bool = False,
):
"""
Clone a git repository into the sandbox.
:param url: Git repository URL
:param path: Destination path for the clone
:param branch: Branch to check out
:param depth: If set, perform a shallow clone with this depth
:param username: Username for HTTP(S) authentication
:param password: Password or token for HTTP(S) authentication
:param envs: Environment variables used for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:param dangerously_store_credentials: Store credentials in the cloned repository when True
:return: Command result from the command runner
"""
if password and not username:
raise InvalidArgumentException(
"Username is required when using a password or token for git clone."
)
def attempt_clone(auth_username: Optional[str], auth_password: Optional[str]):
plan = build_clone_plan(
url=url,
path=path,
branch=branch,
depth=depth,
auth_username=auth_username,
auth_password=auth_password,
dangerously_store_credentials=dangerously_store_credentials,
)
result = self._run_git(
plan.args, None, envs, user, cwd, timeout, request_timeout
)
if plan.should_strip and plan.repo_path and plan.sanitized_url:
self._run_git(
build_remote_set_url_args("origin", plan.sanitized_url),
plan.repo_path,
envs,
user,
cwd,
timeout,
request_timeout,
)
return result
try:
return attempt_clone(username, password)
except CommandExitException as err:
if is_auth_failure(err):
raise GitAuthException(
build_auth_error_message("clone", bool(username) and not password)
) from err
raise
def init(
self,
path: str,
bare: bool = False,
initial_branch: Optional[str] = None,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
):
"""
Initialize a new git repository.
:param path: Destination path for the repository
:param bare: Create a bare repository when True
:param initial_branch: Initial branch name (for example, "main")
:param envs: Environment variables used for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Command result from the command runner
"""
args = ["init"]
if initial_branch:
args.extend(["--initial-branch", initial_branch])
if bare:
args.append("--bare")
args.append(path)
return self._run_git(args, None, envs, user, cwd, timeout, request_timeout)
def remote_add(
self,
path: str,
name: str,
url: str,
fetch: bool = False,
overwrite: bool = False,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
):
"""
Add (or update) a remote for a repository.
:param path: Repository path
:param name: Remote name (for example, "origin")
:param url: Remote URL
:param fetch: Fetch the remote after adding it when True
:param overwrite: Overwrite the remote URL if it already exists when True
:param envs: Environment variables used for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Command result from the command runner
"""
args = build_remote_add_args(name, url, fetch)
if not overwrite:
return self._run_git(args, path, envs, user, cwd, timeout, request_timeout)
cmd = build_remote_add_shell_command(args, path, name, url, fetch)
return self._run_shell(
cmd,
envs,
user,
cwd,
timeout,
request_timeout,
)
def remote_get(
self,
path: str,
name: str,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
) -> Optional[str]:
"""
Get the URL for a git remote.
Returns `None` when the remote does not exist.
:param path: Repository path
:param name: Remote name (for example, "origin")
:param envs: Environment variables used for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Remote URL if present, otherwise `None`
"""
cmd = build_remote_get_command(path, name)
result = self._run_shell(
cmd,
envs,
user,
cwd,
timeout,
request_timeout,
).stdout.strip()
return result or None
def status(
self,
path: str,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
) -> GitStatus:
"""
Get repository status information.
:param path: Repository path
:param envs: Environment variables used for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Parsed git status
"""
result = self._run_git(
build_status_args(),
path,
envs,
user,
cwd,
timeout,
request_timeout,
)
return parse_git_status(result.stdout)
def branches(
self,
path: str,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
) -> GitBranches:
"""
List branches in a repository.
:param path: Repository path
:param envs: Environment variables used for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Parsed branch list
"""
result = self._run_git(
build_branches_args(),
path,
envs,
user,
cwd,
timeout,
request_timeout,
)
return parse_git_branches(result.stdout)
def create_branch(
self,
path: str,
branch: str,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
):
"""
Create and check out a new branch.
:param path: Repository path
:param branch: Branch name to create
:param envs: Environment variables used for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Command result from the command runner
"""
return self._run_git(
build_create_branch_args(branch),
path,
envs,
user,
cwd,
timeout,
request_timeout,
)
def checkout_branch(
self,
path: str,
branch: str,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
):
"""
Check out an existing branch.
:param path: Repository path
:param branch: Branch name to check out
:param envs: Environment variables used for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Command result from the command runner
"""
return self._run_git(
build_checkout_branch_args(branch),
path,
envs,
user,
cwd,
timeout,
request_timeout,
)
def delete_branch(
self,
path: str,
branch: str,
force: bool = False,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
):
"""
Delete a branch.
:param path: Repository path
:param branch: Branch name to delete
:param force: Force deletion with `-D` when `True`
:param envs: Environment variables used for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Command result from the command runner
"""
args = build_delete_branch_args(branch, force)
return self._run_git(args, path, envs, user, cwd, timeout, request_timeout)
def add(
self,
path: str,
files: Optional[List[str]] = None,
all: bool = True,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
):
"""
Stage files for commit.
:param path: Repository path
:param files: Files to add; when omitted, adds the current directory
:param all: When `True` and `files` is omitted, stage all changes
:param envs: Environment variables used for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Command result from the command runner
"""
args = build_add_args(files, all)
return self._run_git(args, path, envs, user, cwd, timeout, request_timeout)
def commit(
self,
path: str,
message: str,
author_name: Optional[str] = None,
author_email: Optional[str] = None,
allow_empty: bool = False,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
):
"""
Create a commit in the repository.
:param path: Repository path
:param message: Commit message
:param author_name: Commit author name
:param author_email: Commit author email
:param allow_empty: Allow empty commits when `True`
:param envs: Environment variables used for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Command result from the command runner
"""
args = build_commit_args(message, author_name, author_email, allow_empty)
return self._run_git(args, path, envs, user, cwd, timeout, request_timeout)
def reset(
self,
path: str,
mode: Optional[GitResetMode] = None,
target: Optional[str] = None,
paths: Optional[List[str]] = None,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
):
"""
Reset the current HEAD to a specified state.
:param path: Repository path
:param mode: Reset mode (soft, mixed, hard, merge, keep)
:param target: Commit, branch, or ref to reset to (defaults to HEAD)
:param paths: Paths to reset
:param envs: Environment variables used for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Command result from the command runner
"""
args = build_reset_args(mode, target, paths)
return self._run_git(args, path, envs, user, cwd, timeout, request_timeout)
def restore(
self,
path: str,
paths: List[str],
staged: Optional[bool] = None,
worktree: Optional[bool] = None,
source: Optional[str] = None,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
):
"""
Restore working tree files or unstage changes.
:param path: Repository path
:param paths: Paths to restore (use ["."] for all)
:param staged: When True, restore the index (unstage)
:param worktree: When True, restore working tree files
:param source: Restore from the given source (commit, branch, or ref)
:param envs: Environment variables used for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Command result from the command runner
"""
args = build_restore_args(paths, staged, worktree, source)
return self._run_git(args, path, envs, user, cwd, timeout, request_timeout)
def push(
self,
path: str,
remote: Optional[str] = None,
branch: Optional[str] = None,
set_upstream: bool = True,
username: Optional[str] = None,
password: Optional[str] = None,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
):
"""
Push commits to a remote.
:param path: Repository path
:param remote: Remote name, e.g. `origin`
:param branch: Branch name to push
:param set_upstream: Set upstream tracking when `True`
:param username: Username for HTTP(S) authentication
:param password: Password or token for HTTP(S) authentication
:param envs: Environment variables used for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Command result from the command runner
"""
if password and not username:
raise InvalidArgumentException(
"Username is required when using a password or token for git push."
)
if username and password:
remote_name = self._resolve_remote_name(
path, remote, envs, user, cwd, timeout, request_timeout
)
return self._with_remote_credentials(
path,
remote_name,
username,
password,
envs,
user,
cwd,
timeout,
request_timeout,
operation=lambda: self._run_git(
build_push_args(
remote_name,
remote=remote,
branch=branch,
set_upstream=set_upstream,
),
path,
envs,
user,
cwd,
timeout,
request_timeout,
),
)
try:
return self._run_git(
build_push_args(
None,
remote=remote,
branch=branch,
set_upstream=set_upstream,
),
path,
envs,
user,
cwd,
timeout,
request_timeout,
)
except CommandExitException as err:
if is_auth_failure(err):
raise GitAuthException(
build_auth_error_message("push", bool(username) and not password)
) from err
if is_missing_upstream(err):
raise GitUpstreamException(
build_upstream_error_message("push")
) from err
raise
def pull(
self,
path: str,
remote: Optional[str] = None,
branch: Optional[str] = None,
username: Optional[str] = None,
password: Optional[str] = None,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
):
"""
Pull changes from a remote.
:param path: Repository path
:param remote: Remote name, e.g. `origin`
:param branch: Branch name to pull
:param username: Username for HTTP(S) authentication
:param password: Password or token for HTTP(S) authentication
:param envs: Environment variables used for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Command result from the command runner
"""
if password and not username:
raise InvalidArgumentException(
"Username is required when using a password or token for git pull."
)
if not remote and not branch:
if not self._has_upstream(path, envs, user, cwd, timeout, request_timeout):
raise GitUpstreamException(build_upstream_error_message("pull"))
if username and password:
remote_name = self._resolve_remote_name(
path, remote, envs, user, cwd, timeout, request_timeout
)
return self._with_remote_credentials(
path,
remote_name,
username,
password,
envs,
user,
cwd,
timeout,
request_timeout,
operation=lambda: self._run_git(
build_pull_args(remote, branch, remote_name),
path,
envs,
user,
cwd,
timeout,
request_timeout,
),
)
try:
return self._run_git(
build_pull_args(remote, branch),
path,
envs,
user,
cwd,
timeout,
request_timeout,
)
except CommandExitException as err:
if is_auth_failure(err):
raise GitAuthException(
build_auth_error_message("pull", bool(username) and not password)
) from err
if is_missing_upstream(err):
raise GitUpstreamException(
build_upstream_error_message("pull")
) from err
raise
def set_config(
self,
key: str,
value: str,
scope: str = "global",
path: Optional[str] = None,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
):
"""
Set a git config value.
Use `scope="local"` together with `path` to configure a specific repository.
:param key: Git config key (e.g. `pull.rebase`)
:param value: Git config value
:param scope: Config scope: `global`, `local`, or `system`
:param path: Repository path required when `scope` is `local`
:param envs: Environment variables used for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Command result from the command runner
"""
if not key:
raise InvalidArgumentException("Git config key is required.")
scope_flag, repo_path = resolve_config_scope(scope, path)
return self._run_git(
["config", scope_flag, key, value],
repo_path,
envs,
user,
cwd,
timeout,
request_timeout,
)
def get_config(
self,
key: str,
scope: str = "global",
path: Optional[str] = None,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
) -> Optional[str]:
"""
Get a git config value.
Returns `None` when the key is not set in the requested scope.
:param key: Git config key (e.g. `pull.rebase`)
:param scope: Config scope: `global`, `local`, or `system`
:param path: Repository path required when `scope` is `local`
:param envs: Environment variables used for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Config value if present, otherwise `None`
"""
if not key:
raise InvalidArgumentException("Git config key is required.")
scope_flag, repo_path = resolve_config_scope(scope, path)
cmd = (
f"{build_git_command(['config', scope_flag, '--get', key], repo_path)} "
"|| true"
)
result = self._run_shell(
cmd,
envs,
user,
cwd,
timeout,
request_timeout,
).stdout.strip()
return result or None
def dangerously_authenticate(
self,
username: str,
password: str,
host: str = "github.com",
protocol: str = "https",
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
):
"""
Dangerously authenticate git globally via the credential helper.
This persists credentials in the credential store and may be accessible to agents running on the sandbox.
Prefer short-lived credentials when possible.
:param username: Username for HTTP(S) authentication
:param password: Password or token for HTTP(S) authentication
:param host: Host to authenticate for, defaults to `github.com`
:param protocol: Protocol to authenticate for, defaults to `https`
:param envs: Environment variables used for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Command result from the command runner
"""
if not username or not password:
raise InvalidArgumentException(
"Both username and password are required to authenticate git."
)
self.set_config(
"credential.helper",
"store",
scope="global",
envs=envs,
user=user,
cwd=cwd,
timeout=timeout,
request_timeout=request_timeout,
)
approve_cmd = build_credential_approve_command(
username=username,
password=password,
host=host,
protocol=protocol,
)
return self._run_shell(
approve_cmd,
envs,
user,
cwd,
timeout,
request_timeout,
)
def configure_user(
self,
name: str,
email: str,
scope: str = "global",
path: Optional[str] = None,
envs: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
cwd: Optional[str] = None,
timeout: Optional[float] = None,
request_timeout: Optional[float] = None,
):
"""
Configure git user name and email.
:param name: Git user name
:param email: Git user email
:param scope: Config scope: `global`, `local`, or `system`
:param path: Repository path required when `scope` is `local`
:param envs: Environment variables used for the command
:param user: User to run the command as
:param cwd: Working directory to run the command
:param timeout: Timeout for the command connection in **seconds**
:param request_timeout: Timeout for the request in **seconds**
:return: Command result from the command runner
"""
if not name or not email:
raise InvalidArgumentException("Both name and email are required.")
self.set_config(
"user.name",
name,
scope=scope,
path=path,
envs=envs,
user=user,
cwd=cwd,
timeout=timeout,
request_timeout=request_timeout,
)
return self.set_config(
"user.email",
email,
scope=scope,
path=path,
envs=envs,
user=user,
cwd=cwd,
timeout=timeout,
request_timeout=request_timeout,
)