loopix-sdk 2.30.0__py3-none-any.whl

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.
Files changed (238) hide show
  1. loopix/__init__.py +260 -0
  2. loopix/api/__init__.py +287 -0
  3. loopix/api/client/__init__.py +8 -0
  4. loopix/api/client/api/__init__.py +1 -0
  5. loopix/api/client/api/sandboxes/__init__.py +1 -0
  6. loopix/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py +161 -0
  7. loopix/api/client/api/sandboxes/get_sandboxes.py +176 -0
  8. loopix/api/client/api/sandboxes/get_sandboxes_metrics.py +173 -0
  9. loopix/api/client/api/sandboxes/get_sandboxes_sandbox_id.py +163 -0
  10. loopix/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py +199 -0
  11. loopix/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +212 -0
  12. loopix/api/client/api/sandboxes/get_v2_sandboxes.py +230 -0
  13. loopix/api/client/api/sandboxes/get_v_2_sandboxes_sandbox_id_logs.py +254 -0
  14. loopix/api/client/api/sandboxes/post_sandboxes.py +172 -0
  15. loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py +193 -0
  16. loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py +187 -0
  17. loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py +181 -0
  18. loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py +189 -0
  19. loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_snapshots.py +195 -0
  20. loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py +193 -0
  21. loopix/api/client/api/sandboxes/put_sandboxes_sandbox_id_network.py +199 -0
  22. loopix/api/client/api/snapshots/__init__.py +1 -0
  23. loopix/api/client/api/snapshots/get_snapshots.py +202 -0
  24. loopix/api/client/api/tags/__init__.py +1 -0
  25. loopix/api/client/api/tags/delete_templates_tags.py +174 -0
  26. loopix/api/client/api/tags/get_templates_template_id_tags.py +172 -0
  27. loopix/api/client/api/tags/post_templates_tags.py +176 -0
  28. loopix/api/client/api/templates/__init__.py +1 -0
  29. loopix/api/client/api/templates/delete_templates_template_id.py +157 -0
  30. loopix/api/client/api/templates/get_templates.py +172 -0
  31. loopix/api/client/api/templates/get_templates_aliases_alias.py +167 -0
  32. loopix/api/client/api/templates/get_templates_template_id.py +195 -0
  33. loopix/api/client/api/templates/get_templates_template_id_builds_build_id_logs.py +272 -0
  34. loopix/api/client/api/templates/get_templates_template_id_builds_build_id_status.py +232 -0
  35. loopix/api/client/api/templates/get_templates_template_id_files_hash.py +180 -0
  36. loopix/api/client/api/templates/patch_templates_template_id.py +183 -0
  37. loopix/api/client/api/templates/patch_v_2_templates_template_id.py +185 -0
  38. loopix/api/client/api/templates/post_templates.py +172 -0
  39. loopix/api/client/api/templates/post_templates_template_id.py +181 -0
  40. loopix/api/client/api/templates/post_templates_template_id_builds_build_id.py +170 -0
  41. loopix/api/client/api/templates/post_v2_templates.py +172 -0
  42. loopix/api/client/api/templates/post_v3_templates.py +176 -0
  43. loopix/api/client/api/templates/post_v_2_templates_template_id_builds_build_id.py +192 -0
  44. loopix/api/client/api/volumes/__init__.py +1 -0
  45. loopix/api/client/api/volumes/delete_volumes_volume_id.py +161 -0
  46. loopix/api/client/api/volumes/get_volumes.py +140 -0
  47. loopix/api/client/api/volumes/get_volumes_volume_id.py +163 -0
  48. loopix/api/client/api/volumes/post_volumes.py +172 -0
  49. loopix/api/client/client.py +286 -0
  50. loopix/api/client/errors.py +16 -0
  51. loopix/api/client/models/__init__.py +185 -0
  52. loopix/api/client/models/admin_build_cancel_result.py +67 -0
  53. loopix/api/client/models/admin_sandbox_kill_result.py +67 -0
  54. loopix/api/client/models/assign_template_tags_request.py +67 -0
  55. loopix/api/client/models/assigned_template_tags.py +68 -0
  56. loopix/api/client/models/aws_registry.py +85 -0
  57. loopix/api/client/models/aws_registry_type.py +8 -0
  58. loopix/api/client/models/build_log_entry.py +89 -0
  59. loopix/api/client/models/build_status_reason.py +95 -0
  60. loopix/api/client/models/connect_sandbox.py +59 -0
  61. loopix/api/client/models/created_access_token.py +100 -0
  62. loopix/api/client/models/created_team_api_key.py +166 -0
  63. loopix/api/client/models/delete_template_tags_request.py +67 -0
  64. loopix/api/client/models/disk_metrics.py +91 -0
  65. loopix/api/client/models/error.py +67 -0
  66. loopix/api/client/models/gcp_registry.py +69 -0
  67. loopix/api/client/models/gcp_registry_type.py +8 -0
  68. loopix/api/client/models/general_registry.py +77 -0
  69. loopix/api/client/models/general_registry_type.py +8 -0
  70. loopix/api/client/models/identifier_masking_details.py +83 -0
  71. loopix/api/client/models/listed_sandbox.py +179 -0
  72. loopix/api/client/models/log_level.py +11 -0
  73. loopix/api/client/models/logs_direction.py +9 -0
  74. loopix/api/client/models/logs_source.py +9 -0
  75. loopix/api/client/models/machine_info.py +83 -0
  76. loopix/api/client/models/max_team_metric.py +78 -0
  77. loopix/api/client/models/mcp_type_0.py +44 -0
  78. loopix/api/client/models/new_access_token.py +59 -0
  79. loopix/api/client/models/new_sandbox.py +224 -0
  80. loopix/api/client/models/new_team_api_key.py +59 -0
  81. loopix/api/client/models/new_volume.py +59 -0
  82. loopix/api/client/models/node.py +160 -0
  83. loopix/api/client/models/node_detail.py +160 -0
  84. loopix/api/client/models/node_metrics.py +122 -0
  85. loopix/api/client/models/node_status.py +12 -0
  86. loopix/api/client/models/node_status_change.py +82 -0
  87. loopix/api/client/models/post_sandboxes_sandbox_id_refreshes_body.py +59 -0
  88. loopix/api/client/models/post_sandboxes_sandbox_id_snapshots_body.py +60 -0
  89. loopix/api/client/models/post_sandboxes_sandbox_id_timeout_body.py +59 -0
  90. loopix/api/client/models/resumed_sandbox.py +68 -0
  91. loopix/api/client/models/sandbox.py +145 -0
  92. loopix/api/client/models/sandbox_auto_resume_config.py +60 -0
  93. loopix/api/client/models/sandbox_detail.py +267 -0
  94. loopix/api/client/models/sandbox_lifecycle.py +70 -0
  95. loopix/api/client/models/sandbox_log.py +70 -0
  96. loopix/api/client/models/sandbox_log_entry.py +93 -0
  97. loopix/api/client/models/sandbox_log_entry_fields.py +44 -0
  98. loopix/api/client/models/sandbox_logs.py +91 -0
  99. loopix/api/client/models/sandbox_logs_v2_response.py +73 -0
  100. loopix/api/client/models/sandbox_metric.py +126 -0
  101. loopix/api/client/models/sandbox_network_config.py +118 -0
  102. loopix/api/client/models/sandbox_network_config_rules.py +72 -0
  103. loopix/api/client/models/sandbox_network_rule.py +74 -0
  104. loopix/api/client/models/sandbox_network_transform.py +79 -0
  105. loopix/api/client/models/sandbox_network_transform_headers.py +47 -0
  106. loopix/api/client/models/sandbox_network_update_config.py +114 -0
  107. loopix/api/client/models/sandbox_network_update_config_rules.py +71 -0
  108. loopix/api/client/models/sandbox_on_timeout.py +9 -0
  109. loopix/api/client/models/sandbox_pause_request.py +62 -0
  110. loopix/api/client/models/sandbox_state.py +9 -0
  111. loopix/api/client/models/sandbox_volume_mount.py +67 -0
  112. loopix/api/client/models/sandboxes_with_metrics.py +59 -0
  113. loopix/api/client/models/snapshot_info.py +70 -0
  114. loopix/api/client/models/team.py +83 -0
  115. loopix/api/client/models/team_api_key.py +158 -0
  116. loopix/api/client/models/team_metric.py +86 -0
  117. loopix/api/client/models/team_user.py +75 -0
  118. loopix/api/client/models/template.py +225 -0
  119. loopix/api/client/models/template_alias_response.py +67 -0
  120. loopix/api/client/models/template_build.py +139 -0
  121. loopix/api/client/models/template_build_file_upload.py +70 -0
  122. loopix/api/client/models/template_build_info.py +126 -0
  123. loopix/api/client/models/template_build_logs_response.py +73 -0
  124. loopix/api/client/models/template_build_request.py +115 -0
  125. loopix/api/client/models/template_build_request_v2.py +88 -0
  126. loopix/api/client/models/template_build_request_v3.py +107 -0
  127. loopix/api/client/models/template_build_start_v2.py +184 -0
  128. loopix/api/client/models/template_build_status.py +11 -0
  129. loopix/api/client/models/template_legacy.py +207 -0
  130. loopix/api/client/models/template_request_response_v3.py +99 -0
  131. loopix/api/client/models/template_step.py +91 -0
  132. loopix/api/client/models/template_tag.py +78 -0
  133. loopix/api/client/models/template_update_request.py +59 -0
  134. loopix/api/client/models/template_update_response.py +59 -0
  135. loopix/api/client/models/template_with_builds.py +156 -0
  136. loopix/api/client/models/update_team_api_key.py +59 -0
  137. loopix/api/client/models/volume.py +67 -0
  138. loopix/api/client/models/volume_and_token.py +75 -0
  139. loopix/api/client/models/volume_token.py +59 -0
  140. loopix/api/client/py.typed +1 -0
  141. loopix/api/client/types.py +54 -0
  142. loopix/api/client_async/__init__.py +74 -0
  143. loopix/api/client_sync/__init__.py +73 -0
  144. loopix/api/metadata.py +14 -0
  145. loopix/connection_config.py +309 -0
  146. loopix/envd/api.py +170 -0
  147. loopix/envd/filesystem/filesystem_connect.py +193 -0
  148. loopix/envd/filesystem/filesystem_pb2.py +80 -0
  149. loopix/envd/filesystem/filesystem_pb2.pyi +272 -0
  150. loopix/envd/process/process_connect.py +174 -0
  151. loopix/envd/process/process_pb2.py +96 -0
  152. loopix/envd/process/process_pb2.pyi +316 -0
  153. loopix/envd/rpc.py +139 -0
  154. loopix/envd/versions.py +11 -0
  155. loopix/exceptions.py +133 -0
  156. loopix/io_utils.py +57 -0
  157. loopix/paginator.py +52 -0
  158. loopix/py.typed +0 -0
  159. loopix/sandbox/_git/__init__.py +85 -0
  160. loopix/sandbox/_git/args.py +363 -0
  161. loopix/sandbox/_git/auth.py +132 -0
  162. loopix/sandbox/_git/config.py +32 -0
  163. loopix/sandbox/_git/parse.py +222 -0
  164. loopix/sandbox/_git/types.py +149 -0
  165. loopix/sandbox/commands/command_handle.py +69 -0
  166. loopix/sandbox/commands/main.py +39 -0
  167. loopix/sandbox/filesystem/filesystem.py +337 -0
  168. loopix/sandbox/filesystem/watch_handle.py +70 -0
  169. loopix/sandbox/main.py +227 -0
  170. loopix/sandbox/mcp.py +1949 -0
  171. loopix/sandbox/network.py +8 -0
  172. loopix/sandbox/sandbox_api.py +624 -0
  173. loopix/sandbox/signature.py +47 -0
  174. loopix/sandbox/utils.py +34 -0
  175. loopix/sandbox_async/commands/command.py +396 -0
  176. loopix/sandbox_async/commands/command_handle.py +298 -0
  177. loopix/sandbox_async/commands/pty.py +257 -0
  178. loopix/sandbox_async/filesystem/filesystem.py +720 -0
  179. loopix/sandbox_async/filesystem/watch_handle.py +97 -0
  180. loopix/sandbox_async/git.py +1100 -0
  181. loopix/sandbox_async/main.py +987 -0
  182. loopix/sandbox_async/paginator.py +140 -0
  183. loopix/sandbox_async/sandbox_api.py +504 -0
  184. loopix/sandbox_async/utils.py +7 -0
  185. loopix/sandbox_domains.py +5 -0
  186. loopix/sandbox_sync/commands/command.py +420 -0
  187. loopix/sandbox_sync/commands/command_handle.py +239 -0
  188. loopix/sandbox_sync/commands/pty.py +279 -0
  189. loopix/sandbox_sync/filesystem/filesystem.py +710 -0
  190. loopix/sandbox_sync/filesystem/watch_handle.py +102 -0
  191. loopix/sandbox_sync/git.py +1077 -0
  192. loopix/sandbox_sync/main.py +975 -0
  193. loopix/sandbox_sync/paginator.py +140 -0
  194. loopix/sandbox_sync/sandbox_api.py +491 -0
  195. loopix/template/consts.py +45 -0
  196. loopix/template/dockerfile_parser.py +286 -0
  197. loopix/template/logger.py +232 -0
  198. loopix/template/main.py +1368 -0
  199. loopix/template/readycmd.py +144 -0
  200. loopix/template/types.py +194 -0
  201. loopix/template/utils.py +426 -0
  202. loopix/template_async/build_api.py +419 -0
  203. loopix/template_async/main.py +528 -0
  204. loopix/template_sync/build_api.py +409 -0
  205. loopix/template_sync/main.py +529 -0
  206. loopix/volume/client/__init__.py +8 -0
  207. loopix/volume/client/api/__init__.py +1 -0
  208. loopix/volume/client/api/volumes/__init__.py +1 -0
  209. loopix/volume/client/api/volumes/delete_volumecontent_volume_id_path.py +174 -0
  210. loopix/volume/client/api/volumes/get_volumecontent_volume_id_dir.py +204 -0
  211. loopix/volume/client/api/volumes/get_volumecontent_volume_id_file.py +179 -0
  212. loopix/volume/client/api/volumes/get_volumecontent_volume_id_path.py +176 -0
  213. loopix/volume/client/api/volumes/patch_volumecontent_volume_id_path.py +203 -0
  214. loopix/volume/client/api/volumes/post_volumecontent_volume_id_dir.py +239 -0
  215. loopix/volume/client/api/volumes/put_volumecontent_volume_id_file.py +259 -0
  216. loopix/volume/client/client.py +286 -0
  217. loopix/volume/client/errors.py +16 -0
  218. loopix/volume/client/models/__init__.py +13 -0
  219. loopix/volume/client/models/error.py +67 -0
  220. loopix/volume/client/models/patch_volumecontent_volume_id_path_body.py +77 -0
  221. loopix/volume/client/models/volume_entry_stat.py +145 -0
  222. loopix/volume/client/models/volume_entry_stat_type.py +11 -0
  223. loopix/volume/client/py.typed +1 -0
  224. loopix/volume/client/types.py +54 -0
  225. loopix/volume/client_async/__init__.py +88 -0
  226. loopix/volume/client_sync/__init__.py +80 -0
  227. loopix/volume/connection_config.py +145 -0
  228. loopix/volume/types.py +62 -0
  229. loopix/volume/utils.py +52 -0
  230. loopix/volume/volume_async.py +639 -0
  231. loopix/volume/volume_sync.py +639 -0
  232. loopix_connect/__init__.py +1 -0
  233. loopix_connect/client.py +534 -0
  234. loopix_connect/py.typed +0 -0
  235. loopix_sdk-2.30.0.dist-info/METADATA +98 -0
  236. loopix_sdk-2.30.0.dist-info/RECORD +238 -0
  237. loopix_sdk-2.30.0.dist-info/WHEEL +4 -0
  238. loopix_sdk-2.30.0.dist-info/licenses/LICENSE +9 -0
@@ -0,0 +1,1100 @@
1
+ from typing import Dict, List, Optional
2
+
3
+ from loopix.exceptions import (
4
+ GitAuthException,
5
+ GitUpstreamException,
6
+ InvalidArgumentException,
7
+ )
8
+ from loopix.sandbox.commands.command_handle import CommandExitException
9
+ from loopix.sandbox._git import (
10
+ GitBranches,
11
+ GitResetMode,
12
+ GitStatus,
13
+ build_add_args,
14
+ build_auth_error_message,
15
+ build_branches_args,
16
+ build_checkout_branch_args,
17
+ build_clone_plan,
18
+ build_commit_args,
19
+ build_credential_approve_command,
20
+ build_create_branch_args,
21
+ build_delete_branch_args,
22
+ build_git_command,
23
+ build_has_upstream_args,
24
+ build_pull_args,
25
+ build_push_args,
26
+ build_remote_add_args,
27
+ build_remote_add_shell_command,
28
+ build_remote_get_command,
29
+ build_remote_get_url_args,
30
+ build_remote_set_url_args,
31
+ build_reset_args,
32
+ build_restore_args,
33
+ build_status_args,
34
+ build_upstream_error_message,
35
+ is_auth_failure,
36
+ is_missing_upstream,
37
+ parse_git_branches,
38
+ parse_git_status,
39
+ parse_remote_url,
40
+ resolve_config_scope,
41
+ with_credentials,
42
+ )
43
+ from loopix.sandbox_async.commands.command import Commands
44
+
45
+
46
+ DEFAULT_GIT_ENV = {"GIT_TERMINAL_PROMPT": "0"}
47
+
48
+
49
+ class Git:
50
+ """
51
+ Async module for running git operations in the sandbox.
52
+ """
53
+
54
+ def __init__(self, commands: Commands) -> None:
55
+ """
56
+ Create a Git helper bound to the sandbox command runner.
57
+
58
+ :param commands: Command runner used to execute git commands
59
+ """
60
+ self._commands = commands
61
+
62
+ async def _run_git(
63
+ self,
64
+ args: List[str],
65
+ repo_path: Optional[str],
66
+ envs: Optional[Dict[str, str]] = None,
67
+ user: Optional[str] = None,
68
+ cwd: Optional[str] = None,
69
+ timeout: Optional[float] = None,
70
+ request_timeout: Optional[float] = None,
71
+ ):
72
+ """
73
+ Build and execute a git command inside the sandbox.
74
+
75
+ :param args: Git arguments to pass to the git binary
76
+ :param repo_path: Repository path used with `git -C`, if provided
77
+ :param envs: Extra environment variables for the command
78
+ :param user: User to run the command as
79
+ :param cwd: Working directory to run the command
80
+ :param timeout: Timeout for the command connection in **seconds**
81
+ :param request_timeout: Timeout for the request in **seconds**
82
+ :return: Command result from the command runner
83
+ """
84
+ cmd = build_git_command(args, repo_path)
85
+ merged_envs = {**DEFAULT_GIT_ENV, **(envs or {})}
86
+ return await self._commands.run(
87
+ cmd,
88
+ envs=merged_envs,
89
+ user=user,
90
+ cwd=cwd,
91
+ timeout=timeout,
92
+ request_timeout=request_timeout,
93
+ )
94
+
95
+ async def _run_shell(
96
+ self,
97
+ cmd: str,
98
+ envs: Optional[Dict[str, str]] = None,
99
+ user: Optional[str] = None,
100
+ cwd: Optional[str] = None,
101
+ timeout: Optional[float] = None,
102
+ request_timeout: Optional[float] = None,
103
+ ):
104
+ """
105
+ Execute a raw shell command while applying default git environment variables.
106
+
107
+ :param cmd: Shell command to execute
108
+ :param envs: Extra environment variables for the command
109
+ :param user: User to run the command as
110
+ :param cwd: Working directory to run the command
111
+ :param timeout: Timeout for the command connection in **seconds**
112
+ :param request_timeout: Timeout for the request in **seconds**
113
+ :return: Command result from the command runner
114
+ """
115
+ merged_envs = {**DEFAULT_GIT_ENV, **(envs or {})}
116
+ return await self._commands.run(
117
+ cmd,
118
+ envs=merged_envs,
119
+ user=user,
120
+ cwd=cwd,
121
+ timeout=timeout,
122
+ request_timeout=request_timeout,
123
+ )
124
+
125
+ async def _has_upstream(
126
+ self,
127
+ path: str,
128
+ envs: Optional[Dict[str, str]] = None,
129
+ user: Optional[str] = None,
130
+ cwd: Optional[str] = None,
131
+ timeout: Optional[float] = None,
132
+ request_timeout: Optional[float] = None,
133
+ ) -> bool:
134
+ try:
135
+ result = await self._run_git(
136
+ build_has_upstream_args(),
137
+ path,
138
+ envs,
139
+ user,
140
+ cwd,
141
+ timeout,
142
+ request_timeout,
143
+ )
144
+ return bool(result.stdout.strip())
145
+ except Exception:
146
+ return False
147
+
148
+ async def _resolve_remote_name(
149
+ self,
150
+ path: str,
151
+ remote: Optional[str],
152
+ envs: Optional[Dict[str, str]] = None,
153
+ user: Optional[str] = None,
154
+ cwd: Optional[str] = None,
155
+ timeout: Optional[float] = None,
156
+ request_timeout: Optional[float] = None,
157
+ ) -> str:
158
+ if remote:
159
+ return remote
160
+
161
+ result = await self._run_git(
162
+ ["remote"],
163
+ path,
164
+ envs,
165
+ user,
166
+ cwd,
167
+ timeout,
168
+ request_timeout,
169
+ )
170
+ remotes = [line.strip() for line in result.stdout.splitlines() if line.strip()]
171
+ if len(remotes) == 1:
172
+ return remotes[0]
173
+
174
+ raise InvalidArgumentException(
175
+ "Remote is required when using username/password and the repository has multiple remotes."
176
+ )
177
+
178
+ async def _with_remote_credentials(
179
+ self,
180
+ path: str,
181
+ remote: str,
182
+ username: str,
183
+ password: str,
184
+ envs: Optional[Dict[str, str]] = None,
185
+ user: Optional[str] = None,
186
+ cwd: Optional[str] = None,
187
+ timeout: Optional[float] = None,
188
+ request_timeout: Optional[float] = None,
189
+ operation=None,
190
+ ):
191
+ original_url = await self._get_remote_url(
192
+ path, remote, envs, user, cwd, timeout, request_timeout
193
+ )
194
+ credential_url = with_credentials(original_url, username, password)
195
+ await self._run_git(
196
+ ["remote", "set-url", remote, credential_url],
197
+ path,
198
+ envs,
199
+ user,
200
+ cwd,
201
+ timeout,
202
+ request_timeout,
203
+ )
204
+
205
+ result = None
206
+ operation_error: Exception | None = None
207
+ try:
208
+ if operation is None:
209
+ raise InvalidArgumentException("Operation is required.")
210
+ result = await operation()
211
+ except Exception as err:
212
+ operation_error = err
213
+
214
+ restore_error: Exception | None = None
215
+ try:
216
+ await self._run_git(
217
+ ["remote", "set-url", remote, original_url],
218
+ path,
219
+ envs,
220
+ user,
221
+ cwd,
222
+ timeout,
223
+ request_timeout,
224
+ )
225
+ except Exception as err:
226
+ restore_error = err
227
+
228
+ if operation_error:
229
+ raise operation_error
230
+ if restore_error:
231
+ raise restore_error
232
+
233
+ return result
234
+
235
+ async def _get_remote_url(
236
+ self,
237
+ path: str,
238
+ remote: str,
239
+ envs: Optional[Dict[str, str]] = None,
240
+ user: Optional[str] = None,
241
+ cwd: Optional[str] = None,
242
+ timeout: Optional[float] = None,
243
+ request_timeout: Optional[float] = None,
244
+ ) -> str:
245
+ result = await self._run_git(
246
+ build_remote_get_url_args(remote),
247
+ path,
248
+ envs,
249
+ user,
250
+ cwd,
251
+ timeout,
252
+ request_timeout,
253
+ )
254
+ return parse_remote_url(result.stdout, remote)
255
+
256
+ async def clone(
257
+ self,
258
+ url: str,
259
+ path: Optional[str] = None,
260
+ branch: Optional[str] = None,
261
+ depth: Optional[int] = None,
262
+ username: Optional[str] = None,
263
+ password: Optional[str] = None,
264
+ envs: Optional[Dict[str, str]] = None,
265
+ user: Optional[str] = None,
266
+ cwd: Optional[str] = None,
267
+ timeout: Optional[float] = None,
268
+ request_timeout: Optional[float] = None,
269
+ dangerously_store_credentials: bool = False,
270
+ ):
271
+ """
272
+ Clone a git repository into the sandbox.
273
+
274
+ :param url: Git repository URL
275
+ :param path: Destination path for the clone
276
+ :param branch: Branch to check out
277
+ :param depth: If set, perform a shallow clone with this depth
278
+ :param username: Username for HTTP(S) authentication
279
+ :param password: Password or token for HTTP(S) authentication
280
+ :param envs: Environment variables used for the command
281
+ :param user: User to run the command as
282
+ :param cwd: Working directory to run the command
283
+ :param timeout: Timeout for the command connection in **seconds**
284
+ :param request_timeout: Timeout for the request in **seconds**
285
+ :param dangerously_store_credentials: Store credentials in the cloned repository when True
286
+ :return: Command result from the command runner
287
+ """
288
+ if password and not username:
289
+ raise InvalidArgumentException(
290
+ "Username is required when using a password or token for git clone."
291
+ )
292
+
293
+ async def attempt_clone(
294
+ auth_username: Optional[str], auth_password: Optional[str]
295
+ ):
296
+ plan = build_clone_plan(
297
+ url=url,
298
+ path=path,
299
+ branch=branch,
300
+ depth=depth,
301
+ auth_username=auth_username,
302
+ auth_password=auth_password,
303
+ dangerously_store_credentials=dangerously_store_credentials,
304
+ )
305
+ result = await self._run_git(
306
+ plan.args, None, envs, user, cwd, timeout, request_timeout
307
+ )
308
+ if plan.should_strip and plan.repo_path and plan.sanitized_url:
309
+ await self._run_git(
310
+ build_remote_set_url_args("origin", plan.sanitized_url),
311
+ plan.repo_path,
312
+ envs,
313
+ user,
314
+ cwd,
315
+ timeout,
316
+ request_timeout,
317
+ )
318
+ return result
319
+
320
+ try:
321
+ return await attempt_clone(username, password)
322
+ except CommandExitException as err:
323
+ if is_auth_failure(err):
324
+ raise GitAuthException(
325
+ build_auth_error_message("clone", bool(username) and not password)
326
+ ) from err
327
+ raise
328
+
329
+ async def init(
330
+ self,
331
+ path: str,
332
+ bare: bool = False,
333
+ initial_branch: Optional[str] = None,
334
+ envs: Optional[Dict[str, str]] = None,
335
+ user: Optional[str] = None,
336
+ cwd: Optional[str] = None,
337
+ timeout: Optional[float] = None,
338
+ request_timeout: Optional[float] = None,
339
+ ):
340
+ """
341
+ Initialize a new git repository.
342
+
343
+ :param path: Destination path for the repository
344
+ :param bare: Create a bare repository when True
345
+ :param initial_branch: Initial branch name (for example, "main")
346
+ :param envs: Environment variables used for the command
347
+ :param user: User to run the command as
348
+ :param cwd: Working directory to run the command
349
+ :param timeout: Timeout for the command connection in **seconds**
350
+ :param request_timeout: Timeout for the request in **seconds**
351
+ :return: Command result from the command runner
352
+ """
353
+ args = ["init"]
354
+ if initial_branch:
355
+ args.extend(["--initial-branch", initial_branch])
356
+ if bare:
357
+ args.append("--bare")
358
+ args.append(path)
359
+ return await self._run_git(
360
+ args, None, envs, user, cwd, timeout, request_timeout
361
+ )
362
+
363
+ async def remote_add(
364
+ self,
365
+ path: str,
366
+ name: str,
367
+ url: str,
368
+ fetch: bool = False,
369
+ overwrite: bool = False,
370
+ envs: Optional[Dict[str, str]] = None,
371
+ user: Optional[str] = None,
372
+ cwd: Optional[str] = None,
373
+ timeout: Optional[float] = None,
374
+ request_timeout: Optional[float] = None,
375
+ ):
376
+ """
377
+ Add (or update) a remote for a repository.
378
+
379
+ :param path: Repository path
380
+ :param name: Remote name (for example, "origin")
381
+ :param url: Remote URL
382
+ :param fetch: Fetch the remote after adding it when True
383
+ :param overwrite: Overwrite the remote URL if it already exists when True
384
+ :param envs: Environment variables used for the command
385
+ :param user: User to run the command as
386
+ :param cwd: Working directory to run the command
387
+ :param timeout: Timeout for the command connection in **seconds**
388
+ :param request_timeout: Timeout for the request in **seconds**
389
+ :return: Command result from the command runner
390
+ """
391
+ args = build_remote_add_args(name, url, fetch)
392
+
393
+ if not overwrite:
394
+ return await self._run_git(
395
+ args, path, envs, user, cwd, timeout, request_timeout
396
+ )
397
+
398
+ cmd = build_remote_add_shell_command(args, path, name, url, fetch)
399
+ return await self._run_shell(
400
+ cmd,
401
+ envs,
402
+ user,
403
+ cwd,
404
+ timeout,
405
+ request_timeout,
406
+ )
407
+
408
+ async def remote_get(
409
+ self,
410
+ path: str,
411
+ name: str,
412
+ envs: Optional[Dict[str, str]] = None,
413
+ user: Optional[str] = None,
414
+ cwd: Optional[str] = None,
415
+ timeout: Optional[float] = None,
416
+ request_timeout: Optional[float] = None,
417
+ ) -> Optional[str]:
418
+ """
419
+ Get the URL for a git remote.
420
+
421
+ Returns `None` when the remote does not exist.
422
+
423
+ :param path: Repository path
424
+ :param name: Remote name (for example, "origin")
425
+ :param envs: Environment variables used for the command
426
+ :param user: User to run the command as
427
+ :param cwd: Working directory to run the command
428
+ :param timeout: Timeout for the command connection in **seconds**
429
+ :param request_timeout: Timeout for the request in **seconds**
430
+ :return: Remote URL if present, otherwise `None`
431
+ """
432
+ cmd = build_remote_get_command(path, name)
433
+ result = (
434
+ await self._run_shell(
435
+ cmd,
436
+ envs,
437
+ user,
438
+ cwd,
439
+ timeout,
440
+ request_timeout,
441
+ )
442
+ ).stdout.strip()
443
+ return result or None
444
+
445
+ async def status(
446
+ self,
447
+ path: str,
448
+ envs: Optional[Dict[str, str]] = None,
449
+ user: Optional[str] = None,
450
+ cwd: Optional[str] = None,
451
+ timeout: Optional[float] = None,
452
+ request_timeout: Optional[float] = None,
453
+ ) -> GitStatus:
454
+ """
455
+ Get repository status information.
456
+
457
+ :param path: Repository path
458
+ :param envs: Environment variables used for the command
459
+ :param user: User to run the command as
460
+ :param cwd: Working directory to run the command
461
+ :param timeout: Timeout for the command connection in **seconds**
462
+ :param request_timeout: Timeout for the request in **seconds**
463
+ :return: Parsed git status
464
+ """
465
+ result = await self._run_git(
466
+ build_status_args(),
467
+ path,
468
+ envs,
469
+ user,
470
+ cwd,
471
+ timeout,
472
+ request_timeout,
473
+ )
474
+ return parse_git_status(result.stdout)
475
+
476
+ async def branches(
477
+ self,
478
+ path: str,
479
+ envs: Optional[Dict[str, str]] = None,
480
+ user: Optional[str] = None,
481
+ cwd: Optional[str] = None,
482
+ timeout: Optional[float] = None,
483
+ request_timeout: Optional[float] = None,
484
+ ) -> GitBranches:
485
+ """
486
+ List branches in a repository.
487
+
488
+ :param path: Repository path
489
+ :param envs: Environment variables used for the command
490
+ :param user: User to run the command as
491
+ :param cwd: Working directory to run the command
492
+ :param timeout: Timeout for the command connection in **seconds**
493
+ :param request_timeout: Timeout for the request in **seconds**
494
+ :return: Parsed branch list
495
+ """
496
+ result = await self._run_git(
497
+ build_branches_args(),
498
+ path,
499
+ envs,
500
+ user,
501
+ cwd,
502
+ timeout,
503
+ request_timeout,
504
+ )
505
+ return parse_git_branches(result.stdout)
506
+
507
+ async def create_branch(
508
+ self,
509
+ path: str,
510
+ branch: str,
511
+ envs: Optional[Dict[str, str]] = None,
512
+ user: Optional[str] = None,
513
+ cwd: Optional[str] = None,
514
+ timeout: Optional[float] = None,
515
+ request_timeout: Optional[float] = None,
516
+ ):
517
+ """
518
+ Create and check out a new branch.
519
+
520
+ :param path: Repository path
521
+ :param branch: Branch name to create
522
+ :param envs: Environment variables used for the command
523
+ :param user: User to run the command as
524
+ :param cwd: Working directory to run the command
525
+ :param timeout: Timeout for the command connection in **seconds**
526
+ :param request_timeout: Timeout for the request in **seconds**
527
+ :return: Command result from the command runner
528
+ """
529
+ return await self._run_git(
530
+ build_create_branch_args(branch),
531
+ path,
532
+ envs,
533
+ user,
534
+ cwd,
535
+ timeout,
536
+ request_timeout,
537
+ )
538
+
539
+ async def checkout_branch(
540
+ self,
541
+ path: str,
542
+ branch: str,
543
+ envs: Optional[Dict[str, str]] = None,
544
+ user: Optional[str] = None,
545
+ cwd: Optional[str] = None,
546
+ timeout: Optional[float] = None,
547
+ request_timeout: Optional[float] = None,
548
+ ):
549
+ """
550
+ Check out an existing branch.
551
+
552
+ :param path: Repository path
553
+ :param branch: Branch name to check out
554
+ :param envs: Environment variables used for the command
555
+ :param user: User to run the command as
556
+ :param cwd: Working directory to run the command
557
+ :param timeout: Timeout for the command connection in **seconds**
558
+ :param request_timeout: Timeout for the request in **seconds**
559
+ :return: Command result from the command runner
560
+ """
561
+ return await self._run_git(
562
+ build_checkout_branch_args(branch),
563
+ path,
564
+ envs,
565
+ user,
566
+ cwd,
567
+ timeout,
568
+ request_timeout,
569
+ )
570
+
571
+ async def delete_branch(
572
+ self,
573
+ path: str,
574
+ branch: str,
575
+ force: bool = False,
576
+ envs: Optional[Dict[str, str]] = None,
577
+ user: Optional[str] = None,
578
+ cwd: Optional[str] = None,
579
+ timeout: Optional[float] = None,
580
+ request_timeout: Optional[float] = None,
581
+ ):
582
+ """
583
+ Delete a branch.
584
+
585
+ :param path: Repository path
586
+ :param branch: Branch name to delete
587
+ :param force: Force deletion with `-D` when `True`
588
+ :param envs: Environment variables used for the command
589
+ :param user: User to run the command as
590
+ :param cwd: Working directory to run the command
591
+ :param timeout: Timeout for the command connection in **seconds**
592
+ :param request_timeout: Timeout for the request in **seconds**
593
+ :return: Command result from the command runner
594
+ """
595
+ args = build_delete_branch_args(branch, force)
596
+ return await self._run_git(
597
+ args, path, envs, user, cwd, timeout, request_timeout
598
+ )
599
+
600
+ async def add(
601
+ self,
602
+ path: str,
603
+ files: Optional[List[str]] = None,
604
+ all: bool = True,
605
+ envs: Optional[Dict[str, str]] = None,
606
+ user: Optional[str] = None,
607
+ cwd: Optional[str] = None,
608
+ timeout: Optional[float] = None,
609
+ request_timeout: Optional[float] = None,
610
+ ):
611
+ """
612
+ Stage files for commit.
613
+
614
+ :param path: Repository path
615
+ :param files: Files to add; when omitted, adds the current directory
616
+ :param all: When `True` and `files` is omitted, stage all changes
617
+ :param envs: Environment variables used for the command
618
+ :param user: User to run the command as
619
+ :param cwd: Working directory to run the command
620
+ :param timeout: Timeout for the command connection in **seconds**
621
+ :param request_timeout: Timeout for the request in **seconds**
622
+ :return: Command result from the command runner
623
+ """
624
+ args = build_add_args(files, all)
625
+ return await self._run_git(
626
+ args, path, envs, user, cwd, timeout, request_timeout
627
+ )
628
+
629
+ async def commit(
630
+ self,
631
+ path: str,
632
+ message: str,
633
+ author_name: Optional[str] = None,
634
+ author_email: Optional[str] = None,
635
+ allow_empty: bool = False,
636
+ envs: Optional[Dict[str, str]] = None,
637
+ user: Optional[str] = None,
638
+ cwd: Optional[str] = None,
639
+ timeout: Optional[float] = None,
640
+ request_timeout: Optional[float] = None,
641
+ ):
642
+ """
643
+ Create a commit in the repository.
644
+
645
+ :param path: Repository path
646
+ :param message: Commit message
647
+ :param author_name: Commit author name
648
+ :param author_email: Commit author email
649
+ :param allow_empty: Allow empty commits when `True`
650
+ :param envs: Environment variables used for the command
651
+ :param user: User to run the command as
652
+ :param cwd: Working directory to run the command
653
+ :param timeout: Timeout for the command connection in **seconds**
654
+ :param request_timeout: Timeout for the request in **seconds**
655
+ :return: Command result from the command runner
656
+ """
657
+ args = build_commit_args(message, author_name, author_email, allow_empty)
658
+ return await self._run_git(
659
+ args, path, envs, user, cwd, timeout, request_timeout
660
+ )
661
+
662
+ async def reset(
663
+ self,
664
+ path: str,
665
+ mode: Optional[GitResetMode] = None,
666
+ target: Optional[str] = None,
667
+ paths: Optional[List[str]] = None,
668
+ envs: Optional[Dict[str, str]] = None,
669
+ user: Optional[str] = None,
670
+ cwd: Optional[str] = None,
671
+ timeout: Optional[float] = None,
672
+ request_timeout: Optional[float] = None,
673
+ ):
674
+ """
675
+ Reset the current HEAD to a specified state.
676
+
677
+ :param path: Repository path
678
+ :param mode: Reset mode (soft, mixed, hard, merge, keep)
679
+ :param target: Commit, branch, or ref to reset to (defaults to HEAD)
680
+ :param paths: Paths to reset
681
+ :param envs: Environment variables used for the command
682
+ :param user: User to run the command as
683
+ :param cwd: Working directory to run the command
684
+ :param timeout: Timeout for the command connection in **seconds**
685
+ :param request_timeout: Timeout for the request in **seconds**
686
+ :return: Command result from the command runner
687
+ """
688
+ args = build_reset_args(mode, target, paths)
689
+ return await self._run_git(
690
+ args, path, envs, user, cwd, timeout, request_timeout
691
+ )
692
+
693
+ async def restore(
694
+ self,
695
+ path: str,
696
+ paths: List[str],
697
+ staged: Optional[bool] = None,
698
+ worktree: Optional[bool] = None,
699
+ source: Optional[str] = None,
700
+ envs: Optional[Dict[str, str]] = None,
701
+ user: Optional[str] = None,
702
+ cwd: Optional[str] = None,
703
+ timeout: Optional[float] = None,
704
+ request_timeout: Optional[float] = None,
705
+ ):
706
+ """
707
+ Restore working tree files or unstage changes.
708
+
709
+ :param path: Repository path
710
+ :param paths: Paths to restore (use ["."] for all)
711
+ :param staged: When True, restore the index (unstage)
712
+ :param worktree: When True, restore working tree files
713
+ :param source: Restore from the given source (commit, branch, or ref)
714
+ :param envs: Environment variables used for the command
715
+ :param user: User to run the command as
716
+ :param cwd: Working directory to run the command
717
+ :param timeout: Timeout for the command connection in **seconds**
718
+ :param request_timeout: Timeout for the request in **seconds**
719
+ :return: Command result from the command runner
720
+ """
721
+ args = build_restore_args(paths, staged, worktree, source)
722
+ return await self._run_git(
723
+ args, path, envs, user, cwd, timeout, request_timeout
724
+ )
725
+
726
+ async def push(
727
+ self,
728
+ path: str,
729
+ remote: Optional[str] = None,
730
+ branch: Optional[str] = None,
731
+ set_upstream: bool = True,
732
+ username: Optional[str] = None,
733
+ password: Optional[str] = None,
734
+ envs: Optional[Dict[str, str]] = None,
735
+ user: Optional[str] = None,
736
+ cwd: Optional[str] = None,
737
+ timeout: Optional[float] = None,
738
+ request_timeout: Optional[float] = None,
739
+ ):
740
+ """
741
+ Push commits to a remote.
742
+
743
+ :param path: Repository path
744
+ :param remote: Remote name, e.g. `origin`
745
+ :param branch: Branch name to push
746
+ :param set_upstream: Set upstream tracking when `True`
747
+ :param username: Username for HTTP(S) authentication
748
+ :param password: Password or token for HTTP(S) authentication
749
+ :param envs: Environment variables used for the command
750
+ :param user: User to run the command as
751
+ :param cwd: Working directory to run the command
752
+ :param timeout: Timeout for the command connection in **seconds**
753
+ :param request_timeout: Timeout for the request in **seconds**
754
+ :return: Command result from the command runner
755
+ """
756
+ if password and not username:
757
+ raise InvalidArgumentException(
758
+ "Username is required when using a password or token for git push."
759
+ )
760
+
761
+ if username and password:
762
+ remote_name = await self._resolve_remote_name(
763
+ path, remote, envs, user, cwd, timeout, request_timeout
764
+ )
765
+ return await self._with_remote_credentials(
766
+ path,
767
+ remote_name,
768
+ username,
769
+ password,
770
+ envs,
771
+ user,
772
+ cwd,
773
+ timeout,
774
+ request_timeout,
775
+ operation=lambda: self._run_git(
776
+ build_push_args(
777
+ remote_name,
778
+ remote=remote,
779
+ branch=branch,
780
+ set_upstream=set_upstream,
781
+ ),
782
+ path,
783
+ envs,
784
+ user,
785
+ cwd,
786
+ timeout,
787
+ request_timeout,
788
+ ),
789
+ )
790
+
791
+ try:
792
+ return await self._run_git(
793
+ build_push_args(
794
+ None,
795
+ remote=remote,
796
+ branch=branch,
797
+ set_upstream=set_upstream,
798
+ ),
799
+ path,
800
+ envs,
801
+ user,
802
+ cwd,
803
+ timeout,
804
+ request_timeout,
805
+ )
806
+ except CommandExitException as err:
807
+ if is_auth_failure(err):
808
+ raise GitAuthException(
809
+ build_auth_error_message("push", bool(username) and not password)
810
+ ) from err
811
+ if is_missing_upstream(err):
812
+ raise GitUpstreamException(
813
+ build_upstream_error_message("push")
814
+ ) from err
815
+ raise
816
+
817
+ async def pull(
818
+ self,
819
+ path: str,
820
+ remote: Optional[str] = None,
821
+ branch: Optional[str] = None,
822
+ username: Optional[str] = None,
823
+ password: Optional[str] = None,
824
+ envs: Optional[Dict[str, str]] = None,
825
+ user: Optional[str] = None,
826
+ cwd: Optional[str] = None,
827
+ timeout: Optional[float] = None,
828
+ request_timeout: Optional[float] = None,
829
+ ):
830
+ """
831
+ Pull changes from a remote.
832
+
833
+ :param path: Repository path
834
+ :param remote: Remote name, e.g. `origin`
835
+ :param branch: Branch name to pull
836
+ :param username: Username for HTTP(S) authentication
837
+ :param password: Password or token for HTTP(S) authentication
838
+ :param envs: Environment variables used for the command
839
+ :param user: User to run the command as
840
+ :param cwd: Working directory to run the command
841
+ :param timeout: Timeout for the command connection in **seconds**
842
+ :param request_timeout: Timeout for the request in **seconds**
843
+ :return: Command result from the command runner
844
+ """
845
+ if password and not username:
846
+ raise InvalidArgumentException(
847
+ "Username is required when using a password or token for git pull."
848
+ )
849
+
850
+ if not remote and not branch:
851
+ has_upstream = await self._has_upstream(
852
+ path, envs, user, cwd, timeout, request_timeout
853
+ )
854
+ if not has_upstream:
855
+ raise GitUpstreamException(build_upstream_error_message("pull"))
856
+
857
+ if username and password:
858
+ remote_name = await self._resolve_remote_name(
859
+ path, remote, envs, user, cwd, timeout, request_timeout
860
+ )
861
+ return await self._with_remote_credentials(
862
+ path,
863
+ remote_name,
864
+ username,
865
+ password,
866
+ envs,
867
+ user,
868
+ cwd,
869
+ timeout,
870
+ request_timeout,
871
+ operation=lambda: self._run_git(
872
+ build_pull_args(remote, branch, remote_name),
873
+ path,
874
+ envs,
875
+ user,
876
+ cwd,
877
+ timeout,
878
+ request_timeout,
879
+ ),
880
+ )
881
+
882
+ try:
883
+ return await self._run_git(
884
+ build_pull_args(remote, branch),
885
+ path,
886
+ envs,
887
+ user,
888
+ cwd,
889
+ timeout,
890
+ request_timeout,
891
+ )
892
+ except CommandExitException as err:
893
+ if is_auth_failure(err):
894
+ raise GitAuthException(
895
+ build_auth_error_message("pull", bool(username) and not password)
896
+ ) from err
897
+ if is_missing_upstream(err):
898
+ raise GitUpstreamException(
899
+ build_upstream_error_message("pull")
900
+ ) from err
901
+ raise
902
+
903
+ async def set_config(
904
+ self,
905
+ key: str,
906
+ value: str,
907
+ scope: str = "global",
908
+ path: Optional[str] = None,
909
+ envs: Optional[Dict[str, str]] = None,
910
+ user: Optional[str] = None,
911
+ cwd: Optional[str] = None,
912
+ timeout: Optional[float] = None,
913
+ request_timeout: Optional[float] = None,
914
+ ):
915
+ """
916
+ Set a git config value.
917
+
918
+ Use `scope="local"` together with `path` to configure a specific repository.
919
+
920
+ :param key: Git config key (e.g. `pull.rebase`)
921
+ :param value: Git config value
922
+ :param scope: Config scope: `global`, `local`, or `system`
923
+ :param path: Repository path required when `scope` is `local`
924
+ :param envs: Environment variables used for the command
925
+ :param user: User to run the command as
926
+ :param cwd: Working directory to run the command
927
+ :param timeout: Timeout for the command connection in **seconds**
928
+ :param request_timeout: Timeout for the request in **seconds**
929
+ :return: Command result from the command runner
930
+ """
931
+ if not key:
932
+ raise InvalidArgumentException("Git config key is required.")
933
+
934
+ scope_flag, repo_path = resolve_config_scope(scope, path)
935
+ return await self._run_git(
936
+ ["config", scope_flag, key, value],
937
+ repo_path,
938
+ envs,
939
+ user,
940
+ cwd,
941
+ timeout,
942
+ request_timeout,
943
+ )
944
+
945
+ async def get_config(
946
+ self,
947
+ key: str,
948
+ scope: str = "global",
949
+ path: Optional[str] = None,
950
+ envs: Optional[Dict[str, str]] = None,
951
+ user: Optional[str] = None,
952
+ cwd: Optional[str] = None,
953
+ timeout: Optional[float] = None,
954
+ request_timeout: Optional[float] = None,
955
+ ) -> Optional[str]:
956
+ """
957
+ Get a git config value.
958
+
959
+ Returns `None` when the key is not set in the requested scope.
960
+
961
+ :param key: Git config key (e.g. `pull.rebase`)
962
+ :param scope: Config scope: `global`, `local`, or `system`
963
+ :param path: Repository path required when `scope` is `local`
964
+ :param envs: Environment variables used for the command
965
+ :param user: User to run the command as
966
+ :param cwd: Working directory to run the command
967
+ :param timeout: Timeout for the command connection in **seconds**
968
+ :param request_timeout: Timeout for the request in **seconds**
969
+ :return: Config value if present, otherwise `None`
970
+ """
971
+ if not key:
972
+ raise InvalidArgumentException("Git config key is required.")
973
+
974
+ scope_flag, repo_path = resolve_config_scope(scope, path)
975
+ cmd = (
976
+ f"{build_git_command(['config', scope_flag, '--get', key], repo_path)} "
977
+ "|| true"
978
+ )
979
+ result = (
980
+ await self._run_shell(
981
+ cmd,
982
+ envs,
983
+ user,
984
+ cwd,
985
+ timeout,
986
+ request_timeout,
987
+ )
988
+ ).stdout.strip()
989
+ return result or None
990
+
991
+ async def dangerously_authenticate(
992
+ self,
993
+ username: str,
994
+ password: str,
995
+ host: str = "github.com",
996
+ protocol: str = "https",
997
+ envs: Optional[Dict[str, str]] = None,
998
+ user: Optional[str] = None,
999
+ cwd: Optional[str] = None,
1000
+ timeout: Optional[float] = None,
1001
+ request_timeout: Optional[float] = None,
1002
+ ):
1003
+ """
1004
+ Dangerously authenticate git globally via the credential helper.
1005
+
1006
+ This persists credentials in the credential store and may be accessible to agents running on the sandbox.
1007
+ Prefer short-lived credentials when possible.
1008
+
1009
+ :param username: Username for HTTP(S) authentication
1010
+ :param password: Password or token for HTTP(S) authentication
1011
+ :param host: Host to authenticate for, defaults to `github.com`
1012
+ :param protocol: Protocol to authenticate for, defaults to `https`
1013
+ :param envs: Environment variables used for the command
1014
+ :param user: User to run the command as
1015
+ :param cwd: Working directory to run the command
1016
+ :param timeout: Timeout for the command connection in **seconds**
1017
+ :param request_timeout: Timeout for the request in **seconds**
1018
+ :return: Command result from the command runner
1019
+ """
1020
+ if not username or not password:
1021
+ raise InvalidArgumentException(
1022
+ "Both username and password are required to authenticate git."
1023
+ )
1024
+
1025
+ await self.set_config(
1026
+ "credential.helper",
1027
+ "store",
1028
+ scope="global",
1029
+ envs=envs,
1030
+ user=user,
1031
+ cwd=cwd,
1032
+ timeout=timeout,
1033
+ request_timeout=request_timeout,
1034
+ )
1035
+ approve_cmd = build_credential_approve_command(
1036
+ username=username,
1037
+ password=password,
1038
+ host=host,
1039
+ protocol=protocol,
1040
+ )
1041
+ return await self._run_shell(
1042
+ approve_cmd,
1043
+ envs,
1044
+ user,
1045
+ cwd,
1046
+ timeout,
1047
+ request_timeout,
1048
+ )
1049
+
1050
+ async def configure_user(
1051
+ self,
1052
+ name: str,
1053
+ email: str,
1054
+ scope: str = "global",
1055
+ path: Optional[str] = None,
1056
+ envs: Optional[Dict[str, str]] = None,
1057
+ user: Optional[str] = None,
1058
+ cwd: Optional[str] = None,
1059
+ timeout: Optional[float] = None,
1060
+ request_timeout: Optional[float] = None,
1061
+ ):
1062
+ """
1063
+ Configure git user name and email.
1064
+
1065
+ :param name: Git user name
1066
+ :param email: Git user email
1067
+ :param scope: Config scope: `global`, `local`, or `system`
1068
+ :param path: Repository path required when `scope` is `local`
1069
+ :param envs: Environment variables used for the command
1070
+ :param user: User to run the command as
1071
+ :param cwd: Working directory to run the command
1072
+ :param timeout: Timeout for the command connection in **seconds**
1073
+ :param request_timeout: Timeout for the request in **seconds**
1074
+ :return: Command result from the command runner
1075
+ """
1076
+ if not name or not email:
1077
+ raise InvalidArgumentException("Both name and email are required.")
1078
+
1079
+ await self.set_config(
1080
+ "user.name",
1081
+ name,
1082
+ scope=scope,
1083
+ path=path,
1084
+ envs=envs,
1085
+ user=user,
1086
+ cwd=cwd,
1087
+ timeout=timeout,
1088
+ request_timeout=request_timeout,
1089
+ )
1090
+ return await self.set_config(
1091
+ "user.email",
1092
+ email,
1093
+ scope=scope,
1094
+ path=path,
1095
+ envs=envs,
1096
+ user=user,
1097
+ cwd=cwd,
1098
+ timeout=timeout,
1099
+ request_timeout=request_timeout,
1100
+ )