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,1077 @@
1
+ from typing import Dict, List, Optional
2
+
3
+ from loopix.sandbox._git import (
4
+ GitBranches,
5
+ GitResetMode,
6
+ GitStatus,
7
+ build_add_args,
8
+ build_auth_error_message,
9
+ build_branches_args,
10
+ build_checkout_branch_args,
11
+ build_clone_plan,
12
+ build_commit_args,
13
+ build_credential_approve_command,
14
+ build_create_branch_args,
15
+ build_delete_branch_args,
16
+ build_git_command,
17
+ build_has_upstream_args,
18
+ build_pull_args,
19
+ build_push_args,
20
+ build_remote_add_args,
21
+ build_remote_add_shell_command,
22
+ build_remote_get_command,
23
+ build_remote_get_url_args,
24
+ build_remote_set_url_args,
25
+ build_reset_args,
26
+ build_restore_args,
27
+ build_status_args,
28
+ build_upstream_error_message,
29
+ is_auth_failure,
30
+ is_missing_upstream,
31
+ parse_git_branches,
32
+ parse_git_status,
33
+ parse_remote_url,
34
+ resolve_config_scope,
35
+ with_credentials,
36
+ )
37
+ from loopix.exceptions import (
38
+ GitAuthException,
39
+ GitUpstreamException,
40
+ InvalidArgumentException,
41
+ )
42
+ from loopix.sandbox.commands.command_handle import CommandExitException
43
+ from loopix.sandbox_sync.commands.command import Commands
44
+
45
+
46
+ DEFAULT_GIT_ENV = {"GIT_TERMINAL_PROMPT": "0"}
47
+
48
+
49
+ class Git:
50
+ """
51
+ 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
+ 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 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
+ 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 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
+ 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 = 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
+ def _get_remote_url(
149
+ self,
150
+ path: str,
151
+ remote: 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
+ result = self._run_git(
159
+ build_remote_get_url_args(remote),
160
+ path,
161
+ envs,
162
+ user,
163
+ cwd,
164
+ timeout,
165
+ request_timeout,
166
+ )
167
+ return parse_remote_url(result.stdout, remote)
168
+
169
+ def _resolve_remote_name(
170
+ self,
171
+ path: str,
172
+ remote: Optional[str],
173
+ envs: Optional[Dict[str, str]] = None,
174
+ user: Optional[str] = None,
175
+ cwd: Optional[str] = None,
176
+ timeout: Optional[float] = None,
177
+ request_timeout: Optional[float] = None,
178
+ ) -> str:
179
+ if remote:
180
+ return remote
181
+
182
+ result = self._run_git(
183
+ ["remote"],
184
+ path,
185
+ envs,
186
+ user,
187
+ cwd,
188
+ timeout,
189
+ request_timeout,
190
+ )
191
+ remotes = [line.strip() for line in result.stdout.splitlines() if line.strip()]
192
+ if len(remotes) == 1:
193
+ return remotes[0]
194
+
195
+ raise InvalidArgumentException(
196
+ "Remote is required when using username/password and the repository has multiple remotes."
197
+ )
198
+
199
+ def _with_remote_credentials(
200
+ self,
201
+ path: str,
202
+ remote: str,
203
+ username: str,
204
+ password: str,
205
+ envs: Optional[Dict[str, str]] = None,
206
+ user: Optional[str] = None,
207
+ cwd: Optional[str] = None,
208
+ timeout: Optional[float] = None,
209
+ request_timeout: Optional[float] = None,
210
+ operation=None,
211
+ ):
212
+ original_url = self._get_remote_url(
213
+ path, remote, envs, user, cwd, timeout, request_timeout
214
+ )
215
+ credential_url = with_credentials(original_url, username, password)
216
+ self._run_git(
217
+ ["remote", "set-url", remote, credential_url],
218
+ path,
219
+ envs,
220
+ user,
221
+ cwd,
222
+ timeout,
223
+ request_timeout,
224
+ )
225
+
226
+ result = None
227
+ operation_error: Exception | None = None
228
+ try:
229
+ if operation is None:
230
+ raise InvalidArgumentException("Operation is required.")
231
+ result = operation()
232
+ except Exception as err:
233
+ operation_error = err
234
+
235
+ restore_error: Exception | None = None
236
+ try:
237
+ self._run_git(
238
+ ["remote", "set-url", remote, original_url],
239
+ path,
240
+ envs,
241
+ user,
242
+ cwd,
243
+ timeout,
244
+ request_timeout,
245
+ )
246
+ except Exception as err:
247
+ restore_error = err
248
+
249
+ if operation_error:
250
+ raise operation_error
251
+ if restore_error:
252
+ raise restore_error
253
+
254
+ return result
255
+
256
+ 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
+ def attempt_clone(auth_username: Optional[str], auth_password: Optional[str]):
294
+ plan = build_clone_plan(
295
+ url=url,
296
+ path=path,
297
+ branch=branch,
298
+ depth=depth,
299
+ auth_username=auth_username,
300
+ auth_password=auth_password,
301
+ dangerously_store_credentials=dangerously_store_credentials,
302
+ )
303
+ result = self._run_git(
304
+ plan.args, None, envs, user, cwd, timeout, request_timeout
305
+ )
306
+ if plan.should_strip and plan.repo_path and plan.sanitized_url:
307
+ self._run_git(
308
+ build_remote_set_url_args("origin", plan.sanitized_url),
309
+ plan.repo_path,
310
+ envs,
311
+ user,
312
+ cwd,
313
+ timeout,
314
+ request_timeout,
315
+ )
316
+ return result
317
+
318
+ try:
319
+ return attempt_clone(username, password)
320
+ except CommandExitException as err:
321
+ if is_auth_failure(err):
322
+ raise GitAuthException(
323
+ build_auth_error_message("clone", bool(username) and not password)
324
+ ) from err
325
+ raise
326
+
327
+ def init(
328
+ self,
329
+ path: str,
330
+ bare: bool = False,
331
+ initial_branch: Optional[str] = None,
332
+ envs: Optional[Dict[str, str]] = None,
333
+ user: Optional[str] = None,
334
+ cwd: Optional[str] = None,
335
+ timeout: Optional[float] = None,
336
+ request_timeout: Optional[float] = None,
337
+ ):
338
+ """
339
+ Initialize a new git repository.
340
+
341
+ :param path: Destination path for the repository
342
+ :param bare: Create a bare repository when True
343
+ :param initial_branch: Initial branch name (for example, "main")
344
+ :param envs: Environment variables used for the command
345
+ :param user: User to run the command as
346
+ :param cwd: Working directory to run the command
347
+ :param timeout: Timeout for the command connection in **seconds**
348
+ :param request_timeout: Timeout for the request in **seconds**
349
+ :return: Command result from the command runner
350
+ """
351
+ args = ["init"]
352
+ if initial_branch:
353
+ args.extend(["--initial-branch", initial_branch])
354
+ if bare:
355
+ args.append("--bare")
356
+ args.append(path)
357
+ return self._run_git(args, None, envs, user, cwd, timeout, request_timeout)
358
+
359
+ def remote_add(
360
+ self,
361
+ path: str,
362
+ name: str,
363
+ url: str,
364
+ fetch: bool = False,
365
+ overwrite: bool = False,
366
+ envs: Optional[Dict[str, str]] = None,
367
+ user: Optional[str] = None,
368
+ cwd: Optional[str] = None,
369
+ timeout: Optional[float] = None,
370
+ request_timeout: Optional[float] = None,
371
+ ):
372
+ """
373
+ Add (or update) a remote for a repository.
374
+
375
+ :param path: Repository path
376
+ :param name: Remote name (for example, "origin")
377
+ :param url: Remote URL
378
+ :param fetch: Fetch the remote after adding it when True
379
+ :param overwrite: Overwrite the remote URL if it already exists when True
380
+ :param envs: Environment variables used for the command
381
+ :param user: User to run the command as
382
+ :param cwd: Working directory to run the command
383
+ :param timeout: Timeout for the command connection in **seconds**
384
+ :param request_timeout: Timeout for the request in **seconds**
385
+ :return: Command result from the command runner
386
+ """
387
+ args = build_remote_add_args(name, url, fetch)
388
+
389
+ if not overwrite:
390
+ return self._run_git(args, path, envs, user, cwd, timeout, request_timeout)
391
+
392
+ cmd = build_remote_add_shell_command(args, path, name, url, fetch)
393
+ return self._run_shell(
394
+ cmd,
395
+ envs,
396
+ user,
397
+ cwd,
398
+ timeout,
399
+ request_timeout,
400
+ )
401
+
402
+ def remote_get(
403
+ self,
404
+ path: str,
405
+ name: str,
406
+ envs: Optional[Dict[str, str]] = None,
407
+ user: Optional[str] = None,
408
+ cwd: Optional[str] = None,
409
+ timeout: Optional[float] = None,
410
+ request_timeout: Optional[float] = None,
411
+ ) -> Optional[str]:
412
+ """
413
+ Get the URL for a git remote.
414
+
415
+ Returns `None` when the remote does not exist.
416
+
417
+ :param path: Repository path
418
+ :param name: Remote name (for example, "origin")
419
+ :param envs: Environment variables used for the command
420
+ :param user: User to run the command as
421
+ :param cwd: Working directory to run the command
422
+ :param timeout: Timeout for the command connection in **seconds**
423
+ :param request_timeout: Timeout for the request in **seconds**
424
+ :return: Remote URL if present, otherwise `None`
425
+ """
426
+ cmd = build_remote_get_command(path, name)
427
+ result = self._run_shell(
428
+ cmd,
429
+ envs,
430
+ user,
431
+ cwd,
432
+ timeout,
433
+ request_timeout,
434
+ ).stdout.strip()
435
+ return result or None
436
+
437
+ def status(
438
+ self,
439
+ path: str,
440
+ envs: Optional[Dict[str, str]] = None,
441
+ user: Optional[str] = None,
442
+ cwd: Optional[str] = None,
443
+ timeout: Optional[float] = None,
444
+ request_timeout: Optional[float] = None,
445
+ ) -> GitStatus:
446
+ """
447
+ Get repository status information.
448
+
449
+ :param path: Repository path
450
+ :param envs: Environment variables used for the command
451
+ :param user: User to run the command as
452
+ :param cwd: Working directory to run the command
453
+ :param timeout: Timeout for the command connection in **seconds**
454
+ :param request_timeout: Timeout for the request in **seconds**
455
+ :return: Parsed git status
456
+ """
457
+ result = self._run_git(
458
+ build_status_args(),
459
+ path,
460
+ envs,
461
+ user,
462
+ cwd,
463
+ timeout,
464
+ request_timeout,
465
+ )
466
+ return parse_git_status(result.stdout)
467
+
468
+ def branches(
469
+ self,
470
+ path: str,
471
+ envs: Optional[Dict[str, str]] = None,
472
+ user: Optional[str] = None,
473
+ cwd: Optional[str] = None,
474
+ timeout: Optional[float] = None,
475
+ request_timeout: Optional[float] = None,
476
+ ) -> GitBranches:
477
+ """
478
+ List branches in a repository.
479
+
480
+ :param path: Repository path
481
+ :param envs: Environment variables used for the command
482
+ :param user: User to run the command as
483
+ :param cwd: Working directory to run the command
484
+ :param timeout: Timeout for the command connection in **seconds**
485
+ :param request_timeout: Timeout for the request in **seconds**
486
+ :return: Parsed branch list
487
+ """
488
+ result = self._run_git(
489
+ build_branches_args(),
490
+ path,
491
+ envs,
492
+ user,
493
+ cwd,
494
+ timeout,
495
+ request_timeout,
496
+ )
497
+ return parse_git_branches(result.stdout)
498
+
499
+ def create_branch(
500
+ self,
501
+ path: str,
502
+ branch: str,
503
+ envs: Optional[Dict[str, str]] = None,
504
+ user: Optional[str] = None,
505
+ cwd: Optional[str] = None,
506
+ timeout: Optional[float] = None,
507
+ request_timeout: Optional[float] = None,
508
+ ):
509
+ """
510
+ Create and check out a new branch.
511
+
512
+ :param path: Repository path
513
+ :param branch: Branch name to create
514
+ :param envs: Environment variables used for the command
515
+ :param user: User to run the command as
516
+ :param cwd: Working directory to run the command
517
+ :param timeout: Timeout for the command connection in **seconds**
518
+ :param request_timeout: Timeout for the request in **seconds**
519
+ :return: Command result from the command runner
520
+ """
521
+ return self._run_git(
522
+ build_create_branch_args(branch),
523
+ path,
524
+ envs,
525
+ user,
526
+ cwd,
527
+ timeout,
528
+ request_timeout,
529
+ )
530
+
531
+ def checkout_branch(
532
+ self,
533
+ path: str,
534
+ branch: str,
535
+ envs: Optional[Dict[str, str]] = None,
536
+ user: Optional[str] = None,
537
+ cwd: Optional[str] = None,
538
+ timeout: Optional[float] = None,
539
+ request_timeout: Optional[float] = None,
540
+ ):
541
+ """
542
+ Check out an existing branch.
543
+
544
+ :param path: Repository path
545
+ :param branch: Branch name to check out
546
+ :param envs: Environment variables used for the command
547
+ :param user: User to run the command as
548
+ :param cwd: Working directory to run the command
549
+ :param timeout: Timeout for the command connection in **seconds**
550
+ :param request_timeout: Timeout for the request in **seconds**
551
+ :return: Command result from the command runner
552
+ """
553
+ return self._run_git(
554
+ build_checkout_branch_args(branch),
555
+ path,
556
+ envs,
557
+ user,
558
+ cwd,
559
+ timeout,
560
+ request_timeout,
561
+ )
562
+
563
+ def delete_branch(
564
+ self,
565
+ path: str,
566
+ branch: str,
567
+ force: bool = False,
568
+ envs: Optional[Dict[str, str]] = None,
569
+ user: Optional[str] = None,
570
+ cwd: Optional[str] = None,
571
+ timeout: Optional[float] = None,
572
+ request_timeout: Optional[float] = None,
573
+ ):
574
+ """
575
+ Delete a branch.
576
+
577
+ :param path: Repository path
578
+ :param branch: Branch name to delete
579
+ :param force: Force deletion with `-D` when `True`
580
+ :param envs: Environment variables used for the command
581
+ :param user: User to run the command as
582
+ :param cwd: Working directory to run the command
583
+ :param timeout: Timeout for the command connection in **seconds**
584
+ :param request_timeout: Timeout for the request in **seconds**
585
+ :return: Command result from the command runner
586
+ """
587
+ args = build_delete_branch_args(branch, force)
588
+ return self._run_git(args, path, envs, user, cwd, timeout, request_timeout)
589
+
590
+ def add(
591
+ self,
592
+ path: str,
593
+ files: Optional[List[str]] = None,
594
+ all: bool = True,
595
+ envs: Optional[Dict[str, str]] = None,
596
+ user: Optional[str] = None,
597
+ cwd: Optional[str] = None,
598
+ timeout: Optional[float] = None,
599
+ request_timeout: Optional[float] = None,
600
+ ):
601
+ """
602
+ Stage files for commit.
603
+
604
+ :param path: Repository path
605
+ :param files: Files to add; when omitted, adds the current directory
606
+ :param all: When `True` and `files` is omitted, stage all changes
607
+ :param envs: Environment variables used for the command
608
+ :param user: User to run the command as
609
+ :param cwd: Working directory to run the command
610
+ :param timeout: Timeout for the command connection in **seconds**
611
+ :param request_timeout: Timeout for the request in **seconds**
612
+ :return: Command result from the command runner
613
+ """
614
+ args = build_add_args(files, all)
615
+ return self._run_git(args, path, envs, user, cwd, timeout, request_timeout)
616
+
617
+ def commit(
618
+ self,
619
+ path: str,
620
+ message: str,
621
+ author_name: Optional[str] = None,
622
+ author_email: Optional[str] = None,
623
+ allow_empty: bool = False,
624
+ envs: Optional[Dict[str, str]] = None,
625
+ user: Optional[str] = None,
626
+ cwd: Optional[str] = None,
627
+ timeout: Optional[float] = None,
628
+ request_timeout: Optional[float] = None,
629
+ ):
630
+ """
631
+ Create a commit in the repository.
632
+
633
+ :param path: Repository path
634
+ :param message: Commit message
635
+ :param author_name: Commit author name
636
+ :param author_email: Commit author email
637
+ :param allow_empty: Allow empty commits when `True`
638
+ :param envs: Environment variables used for the command
639
+ :param user: User to run the command as
640
+ :param cwd: Working directory to run the command
641
+ :param timeout: Timeout for the command connection in **seconds**
642
+ :param request_timeout: Timeout for the request in **seconds**
643
+ :return: Command result from the command runner
644
+ """
645
+ args = build_commit_args(message, author_name, author_email, allow_empty)
646
+ return self._run_git(args, path, envs, user, cwd, timeout, request_timeout)
647
+
648
+ def reset(
649
+ self,
650
+ path: str,
651
+ mode: Optional[GitResetMode] = None,
652
+ target: Optional[str] = None,
653
+ paths: Optional[List[str]] = None,
654
+ envs: Optional[Dict[str, str]] = None,
655
+ user: Optional[str] = None,
656
+ cwd: Optional[str] = None,
657
+ timeout: Optional[float] = None,
658
+ request_timeout: Optional[float] = None,
659
+ ):
660
+ """
661
+ Reset the current HEAD to a specified state.
662
+
663
+ :param path: Repository path
664
+ :param mode: Reset mode (soft, mixed, hard, merge, keep)
665
+ :param target: Commit, branch, or ref to reset to (defaults to HEAD)
666
+ :param paths: Paths to reset
667
+ :param envs: Environment variables used for the command
668
+ :param user: User to run the command as
669
+ :param cwd: Working directory to run the command
670
+ :param timeout: Timeout for the command connection in **seconds**
671
+ :param request_timeout: Timeout for the request in **seconds**
672
+ :return: Command result from the command runner
673
+ """
674
+ args = build_reset_args(mode, target, paths)
675
+ return self._run_git(args, path, envs, user, cwd, timeout, request_timeout)
676
+
677
+ def restore(
678
+ self,
679
+ path: str,
680
+ paths: List[str],
681
+ staged: Optional[bool] = None,
682
+ worktree: Optional[bool] = None,
683
+ source: Optional[str] = None,
684
+ envs: Optional[Dict[str, str]] = None,
685
+ user: Optional[str] = None,
686
+ cwd: Optional[str] = None,
687
+ timeout: Optional[float] = None,
688
+ request_timeout: Optional[float] = None,
689
+ ):
690
+ """
691
+ Restore working tree files or unstage changes.
692
+
693
+ :param path: Repository path
694
+ :param paths: Paths to restore (use ["."] for all)
695
+ :param staged: When True, restore the index (unstage)
696
+ :param worktree: When True, restore working tree files
697
+ :param source: Restore from the given source (commit, branch, or ref)
698
+ :param envs: Environment variables used for the command
699
+ :param user: User to run the command as
700
+ :param cwd: Working directory to run the command
701
+ :param timeout: Timeout for the command connection in **seconds**
702
+ :param request_timeout: Timeout for the request in **seconds**
703
+ :return: Command result from the command runner
704
+ """
705
+ args = build_restore_args(paths, staged, worktree, source)
706
+ return self._run_git(args, path, envs, user, cwd, timeout, request_timeout)
707
+
708
+ def push(
709
+ self,
710
+ path: str,
711
+ remote: Optional[str] = None,
712
+ branch: Optional[str] = None,
713
+ set_upstream: bool = True,
714
+ username: Optional[str] = None,
715
+ password: Optional[str] = None,
716
+ envs: Optional[Dict[str, str]] = None,
717
+ user: Optional[str] = None,
718
+ cwd: Optional[str] = None,
719
+ timeout: Optional[float] = None,
720
+ request_timeout: Optional[float] = None,
721
+ ):
722
+ """
723
+ Push commits to a remote.
724
+
725
+ :param path: Repository path
726
+ :param remote: Remote name, e.g. `origin`
727
+ :param branch: Branch name to push
728
+ :param set_upstream: Set upstream tracking when `True`
729
+ :param username: Username for HTTP(S) authentication
730
+ :param password: Password or token for HTTP(S) authentication
731
+ :param envs: Environment variables used for the command
732
+ :param user: User to run the command as
733
+ :param cwd: Working directory to run the command
734
+ :param timeout: Timeout for the command connection in **seconds**
735
+ :param request_timeout: Timeout for the request in **seconds**
736
+ :return: Command result from the command runner
737
+ """
738
+ if password and not username:
739
+ raise InvalidArgumentException(
740
+ "Username is required when using a password or token for git push."
741
+ )
742
+
743
+ if username and password:
744
+ remote_name = self._resolve_remote_name(
745
+ path, remote, envs, user, cwd, timeout, request_timeout
746
+ )
747
+ return self._with_remote_credentials(
748
+ path,
749
+ remote_name,
750
+ username,
751
+ password,
752
+ envs,
753
+ user,
754
+ cwd,
755
+ timeout,
756
+ request_timeout,
757
+ operation=lambda: self._run_git(
758
+ build_push_args(
759
+ remote_name,
760
+ remote=remote,
761
+ branch=branch,
762
+ set_upstream=set_upstream,
763
+ ),
764
+ path,
765
+ envs,
766
+ user,
767
+ cwd,
768
+ timeout,
769
+ request_timeout,
770
+ ),
771
+ )
772
+
773
+ try:
774
+ return self._run_git(
775
+ build_push_args(
776
+ None,
777
+ remote=remote,
778
+ branch=branch,
779
+ set_upstream=set_upstream,
780
+ ),
781
+ path,
782
+ envs,
783
+ user,
784
+ cwd,
785
+ timeout,
786
+ request_timeout,
787
+ )
788
+ except CommandExitException as err:
789
+ if is_auth_failure(err):
790
+ raise GitAuthException(
791
+ build_auth_error_message("push", bool(username) and not password)
792
+ ) from err
793
+ if is_missing_upstream(err):
794
+ raise GitUpstreamException(
795
+ build_upstream_error_message("push")
796
+ ) from err
797
+ raise
798
+
799
+ def pull(
800
+ self,
801
+ path: str,
802
+ remote: Optional[str] = None,
803
+ branch: Optional[str] = None,
804
+ username: Optional[str] = None,
805
+ password: Optional[str] = None,
806
+ envs: Optional[Dict[str, str]] = None,
807
+ user: Optional[str] = None,
808
+ cwd: Optional[str] = None,
809
+ timeout: Optional[float] = None,
810
+ request_timeout: Optional[float] = None,
811
+ ):
812
+ """
813
+ Pull changes from a remote.
814
+
815
+ :param path: Repository path
816
+ :param remote: Remote name, e.g. `origin`
817
+ :param branch: Branch name to pull
818
+ :param username: Username for HTTP(S) authentication
819
+ :param password: Password or token for HTTP(S) authentication
820
+ :param envs: Environment variables used for the command
821
+ :param user: User to run the command as
822
+ :param cwd: Working directory to run the command
823
+ :param timeout: Timeout for the command connection in **seconds**
824
+ :param request_timeout: Timeout for the request in **seconds**
825
+ :return: Command result from the command runner
826
+ """
827
+ if password and not username:
828
+ raise InvalidArgumentException(
829
+ "Username is required when using a password or token for git pull."
830
+ )
831
+
832
+ if not remote and not branch:
833
+ if not self._has_upstream(path, envs, user, cwd, timeout, request_timeout):
834
+ raise GitUpstreamException(build_upstream_error_message("pull"))
835
+
836
+ if username and password:
837
+ remote_name = self._resolve_remote_name(
838
+ path, remote, envs, user, cwd, timeout, request_timeout
839
+ )
840
+ return self._with_remote_credentials(
841
+ path,
842
+ remote_name,
843
+ username,
844
+ password,
845
+ envs,
846
+ user,
847
+ cwd,
848
+ timeout,
849
+ request_timeout,
850
+ operation=lambda: self._run_git(
851
+ build_pull_args(remote, branch, remote_name),
852
+ path,
853
+ envs,
854
+ user,
855
+ cwd,
856
+ timeout,
857
+ request_timeout,
858
+ ),
859
+ )
860
+
861
+ try:
862
+ return self._run_git(
863
+ build_pull_args(remote, branch),
864
+ path,
865
+ envs,
866
+ user,
867
+ cwd,
868
+ timeout,
869
+ request_timeout,
870
+ )
871
+ except CommandExitException as err:
872
+ if is_auth_failure(err):
873
+ raise GitAuthException(
874
+ build_auth_error_message("pull", bool(username) and not password)
875
+ ) from err
876
+ if is_missing_upstream(err):
877
+ raise GitUpstreamException(
878
+ build_upstream_error_message("pull")
879
+ ) from err
880
+ raise
881
+
882
+ def set_config(
883
+ self,
884
+ key: str,
885
+ value: str,
886
+ scope: str = "global",
887
+ path: Optional[str] = None,
888
+ envs: Optional[Dict[str, str]] = None,
889
+ user: Optional[str] = None,
890
+ cwd: Optional[str] = None,
891
+ timeout: Optional[float] = None,
892
+ request_timeout: Optional[float] = None,
893
+ ):
894
+ """
895
+ Set a git config value.
896
+
897
+ Use `scope="local"` together with `path` to configure a specific repository.
898
+
899
+ :param key: Git config key (e.g. `pull.rebase`)
900
+ :param value: Git config value
901
+ :param scope: Config scope: `global`, `local`, or `system`
902
+ :param path: Repository path required when `scope` is `local`
903
+ :param envs: Environment variables used for the command
904
+ :param user: User to run the command as
905
+ :param cwd: Working directory to run the command
906
+ :param timeout: Timeout for the command connection in **seconds**
907
+ :param request_timeout: Timeout for the request in **seconds**
908
+ :return: Command result from the command runner
909
+ """
910
+ if not key:
911
+ raise InvalidArgumentException("Git config key is required.")
912
+
913
+ scope_flag, repo_path = resolve_config_scope(scope, path)
914
+ return self._run_git(
915
+ ["config", scope_flag, key, value],
916
+ repo_path,
917
+ envs,
918
+ user,
919
+ cwd,
920
+ timeout,
921
+ request_timeout,
922
+ )
923
+
924
+ def get_config(
925
+ self,
926
+ key: str,
927
+ scope: str = "global",
928
+ path: Optional[str] = None,
929
+ envs: Optional[Dict[str, str]] = None,
930
+ user: Optional[str] = None,
931
+ cwd: Optional[str] = None,
932
+ timeout: Optional[float] = None,
933
+ request_timeout: Optional[float] = None,
934
+ ) -> Optional[str]:
935
+ """
936
+ Get a git config value.
937
+
938
+ Returns `None` when the key is not set in the requested scope.
939
+
940
+ :param key: Git config key (e.g. `pull.rebase`)
941
+ :param scope: Config scope: `global`, `local`, or `system`
942
+ :param path: Repository path required when `scope` is `local`
943
+ :param envs: Environment variables used for the command
944
+ :param user: User to run the command as
945
+ :param cwd: Working directory to run the command
946
+ :param timeout: Timeout for the command connection in **seconds**
947
+ :param request_timeout: Timeout for the request in **seconds**
948
+ :return: Config value if present, otherwise `None`
949
+ """
950
+ if not key:
951
+ raise InvalidArgumentException("Git config key is required.")
952
+
953
+ scope_flag, repo_path = resolve_config_scope(scope, path)
954
+ cmd = (
955
+ f"{build_git_command(['config', scope_flag, '--get', key], repo_path)} "
956
+ "|| true"
957
+ )
958
+ result = self._run_shell(
959
+ cmd,
960
+ envs,
961
+ user,
962
+ cwd,
963
+ timeout,
964
+ request_timeout,
965
+ ).stdout.strip()
966
+ return result or None
967
+
968
+ def dangerously_authenticate(
969
+ self,
970
+ username: str,
971
+ password: str,
972
+ host: str = "github.com",
973
+ protocol: str = "https",
974
+ envs: Optional[Dict[str, str]] = None,
975
+ user: Optional[str] = None,
976
+ cwd: Optional[str] = None,
977
+ timeout: Optional[float] = None,
978
+ request_timeout: Optional[float] = None,
979
+ ):
980
+ """
981
+ Dangerously authenticate git globally via the credential helper.
982
+
983
+ This persists credentials in the credential store and may be accessible to agents running on the sandbox.
984
+ Prefer short-lived credentials when possible.
985
+
986
+ :param username: Username for HTTP(S) authentication
987
+ :param password: Password or token for HTTP(S) authentication
988
+ :param host: Host to authenticate for, defaults to `github.com`
989
+ :param protocol: Protocol to authenticate for, defaults to `https`
990
+ :param envs: Environment variables used for the command
991
+ :param user: User to run the command as
992
+ :param cwd: Working directory to run the command
993
+ :param timeout: Timeout for the command connection in **seconds**
994
+ :param request_timeout: Timeout for the request in **seconds**
995
+ :return: Command result from the command runner
996
+ """
997
+ if not username or not password:
998
+ raise InvalidArgumentException(
999
+ "Both username and password are required to authenticate git."
1000
+ )
1001
+
1002
+ self.set_config(
1003
+ "credential.helper",
1004
+ "store",
1005
+ scope="global",
1006
+ envs=envs,
1007
+ user=user,
1008
+ cwd=cwd,
1009
+ timeout=timeout,
1010
+ request_timeout=request_timeout,
1011
+ )
1012
+ approve_cmd = build_credential_approve_command(
1013
+ username=username,
1014
+ password=password,
1015
+ host=host,
1016
+ protocol=protocol,
1017
+ )
1018
+ return self._run_shell(
1019
+ approve_cmd,
1020
+ envs,
1021
+ user,
1022
+ cwd,
1023
+ timeout,
1024
+ request_timeout,
1025
+ )
1026
+
1027
+ def configure_user(
1028
+ self,
1029
+ name: str,
1030
+ email: str,
1031
+ scope: str = "global",
1032
+ path: Optional[str] = None,
1033
+ envs: Optional[Dict[str, str]] = None,
1034
+ user: Optional[str] = None,
1035
+ cwd: Optional[str] = None,
1036
+ timeout: Optional[float] = None,
1037
+ request_timeout: Optional[float] = None,
1038
+ ):
1039
+ """
1040
+ Configure git user name and email.
1041
+
1042
+ :param name: Git user name
1043
+ :param email: Git user email
1044
+ :param scope: Config scope: `global`, `local`, or `system`
1045
+ :param path: Repository path required when `scope` is `local`
1046
+ :param envs: Environment variables used for the command
1047
+ :param user: User to run the command as
1048
+ :param cwd: Working directory to run the command
1049
+ :param timeout: Timeout for the command connection in **seconds**
1050
+ :param request_timeout: Timeout for the request in **seconds**
1051
+ :return: Command result from the command runner
1052
+ """
1053
+ if not name or not email:
1054
+ raise InvalidArgumentException("Both name and email are required.")
1055
+
1056
+ self.set_config(
1057
+ "user.name",
1058
+ name,
1059
+ scope=scope,
1060
+ path=path,
1061
+ envs=envs,
1062
+ user=user,
1063
+ cwd=cwd,
1064
+ timeout=timeout,
1065
+ request_timeout=request_timeout,
1066
+ )
1067
+ return self.set_config(
1068
+ "user.email",
1069
+ email,
1070
+ scope=scope,
1071
+ path=path,
1072
+ envs=envs,
1073
+ user=user,
1074
+ cwd=cwd,
1075
+ timeout=timeout,
1076
+ request_timeout=request_timeout,
1077
+ )