gitinstall 1.1.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 (59) hide show
  1. gitinstall/__init__.py +61 -0
  2. gitinstall/_sdk.py +541 -0
  3. gitinstall/academic.py +831 -0
  4. gitinstall/admin.html +327 -0
  5. gitinstall/auto_update.py +384 -0
  6. gitinstall/autopilot.py +349 -0
  7. gitinstall/badge.py +476 -0
  8. gitinstall/checkpoint.py +330 -0
  9. gitinstall/cicd.py +499 -0
  10. gitinstall/clawhub.html +718 -0
  11. gitinstall/config_schema.py +353 -0
  12. gitinstall/db.py +984 -0
  13. gitinstall/db_backend.py +445 -0
  14. gitinstall/dep_chain.py +337 -0
  15. gitinstall/dependency_audit.py +1153 -0
  16. gitinstall/detector.py +542 -0
  17. gitinstall/doctor.py +493 -0
  18. gitinstall/education.py +869 -0
  19. gitinstall/enterprise.py +802 -0
  20. gitinstall/error_fixer.py +953 -0
  21. gitinstall/event_bus.py +251 -0
  22. gitinstall/executor.py +577 -0
  23. gitinstall/feature_flags.py +138 -0
  24. gitinstall/fetcher.py +921 -0
  25. gitinstall/huggingface.py +922 -0
  26. gitinstall/hw_detect.py +988 -0
  27. gitinstall/i18n.py +664 -0
  28. gitinstall/installer_registry.py +362 -0
  29. gitinstall/knowledge_base.py +379 -0
  30. gitinstall/license_check.py +605 -0
  31. gitinstall/llm.py +569 -0
  32. gitinstall/log.py +236 -0
  33. gitinstall/main.py +1408 -0
  34. gitinstall/mcp_agent.py +841 -0
  35. gitinstall/mcp_server.py +386 -0
  36. gitinstall/monorepo.py +810 -0
  37. gitinstall/multi_source.py +425 -0
  38. gitinstall/onboard.py +276 -0
  39. gitinstall/planner.py +222 -0
  40. gitinstall/planner_helpers.py +323 -0
  41. gitinstall/planner_known_projects.py +1010 -0
  42. gitinstall/planner_templates.py +996 -0
  43. gitinstall/remote_gpu.py +633 -0
  44. gitinstall/resilience.py +608 -0
  45. gitinstall/run_tests.py +572 -0
  46. gitinstall/skills.py +476 -0
  47. gitinstall/tool_schemas.py +324 -0
  48. gitinstall/trending.py +279 -0
  49. gitinstall/uninstaller.py +415 -0
  50. gitinstall/validate_top100.py +607 -0
  51. gitinstall/watchdog.py +180 -0
  52. gitinstall/web.py +1277 -0
  53. gitinstall/web_ui.html +2277 -0
  54. gitinstall-1.1.0.dist-info/METADATA +275 -0
  55. gitinstall-1.1.0.dist-info/RECORD +59 -0
  56. gitinstall-1.1.0.dist-info/WHEEL +5 -0
  57. gitinstall-1.1.0.dist-info/entry_points.txt +3 -0
  58. gitinstall-1.1.0.dist-info/licenses/LICENSE +21 -0
  59. gitinstall-1.1.0.dist-info/top_level.txt +1 -0
gitinstall/cicd.py ADDED
@@ -0,0 +1,499 @@
1
+ """
2
+ cicd.py - CI/CD 集成模块
3
+ ==========================
4
+
5
+ 为 GitHub Actions、GitLab CI、Jenkins、Azure Pipelines 等
6
+ CI/CD 平台提供 gitinstall 集成能力。
7
+
8
+ 功能:
9
+ 1. 生成 CI/CD 配置文件(GitHub Actions YAML 等)
10
+ 2. CI 环境检测和自适应
11
+ 3. 批量安装 + 缓存策略
12
+ 4. 安装结果报告(JUnit / JSON)
13
+ 5. 安装锁文件(可复现安装)
14
+
15
+ 零外部依赖,纯 Python 标准库。
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import hashlib
21
+ import json
22
+ import os
23
+ import time
24
+ from dataclasses import dataclass, field
25
+ from typing import Optional
26
+
27
+
28
+ # ─────────────────────────────────────────────
29
+ # CI 环境检测
30
+ # ─────────────────────────────────────────────
31
+
32
+ @dataclass
33
+ class CIEnvironment:
34
+ """CI/CD 环境信息"""
35
+ is_ci: bool = False
36
+ platform: str = "" # github_actions, gitlab_ci, jenkins, azure, circle, travis
37
+ runner_os: str = "" # linux, macos, windows
38
+ runner_arch: str = "" # x64, arm64
39
+ branch: str = ""
40
+ commit_sha: str = ""
41
+ pr_number: str = ""
42
+ repo_url: str = ""
43
+ workspace: str = ""
44
+ job_name: str = ""
45
+ run_id: str = ""
46
+ cache_dir: str = ""
47
+
48
+
49
+ def detect_ci_environment() -> CIEnvironment:
50
+ """自动检测当前 CI/CD 环境"""
51
+ env = CIEnvironment()
52
+
53
+ # GitHub Actions
54
+ if os.getenv("GITHUB_ACTIONS") == "true":
55
+ env.is_ci = True
56
+ env.platform = "github_actions"
57
+ env.runner_os = os.getenv("RUNNER_OS", "").lower()
58
+ env.runner_arch = os.getenv("RUNNER_ARCH", "").lower()
59
+ env.branch = os.getenv("GITHUB_REF_NAME", "")
60
+ env.commit_sha = os.getenv("GITHUB_SHA", "")
61
+ env.pr_number = os.getenv("GITHUB_EVENT_NUMBER", "")
62
+ env.repo_url = f"https://github.com/{os.getenv('GITHUB_REPOSITORY', '')}"
63
+ env.workspace = os.getenv("GITHUB_WORKSPACE", "")
64
+ env.job_name = os.getenv("GITHUB_JOB", "")
65
+ env.run_id = os.getenv("GITHUB_RUN_ID", "")
66
+ env.cache_dir = os.path.expanduser("~/.cache/gitinstall")
67
+ return env
68
+
69
+ # GitLab CI
70
+ if os.getenv("GITLAB_CI") == "true":
71
+ env.is_ci = True
72
+ env.platform = "gitlab_ci"
73
+ env.branch = os.getenv("CI_COMMIT_REF_NAME", "")
74
+ env.commit_sha = os.getenv("CI_COMMIT_SHA", "")
75
+ env.pr_number = os.getenv("CI_MERGE_REQUEST_IID", "")
76
+ env.repo_url = os.getenv("CI_PROJECT_URL", "")
77
+ env.workspace = os.getenv("CI_PROJECT_DIR", "")
78
+ env.job_name = os.getenv("CI_JOB_NAME", "")
79
+ env.run_id = os.getenv("CI_PIPELINE_ID", "")
80
+ env.cache_dir = os.path.expanduser("~/.cache/gitinstall")
81
+ return env
82
+
83
+ # Jenkins
84
+ if os.getenv("JENKINS_URL"):
85
+ env.is_ci = True
86
+ env.platform = "jenkins"
87
+ env.branch = os.getenv("GIT_BRANCH", "")
88
+ env.commit_sha = os.getenv("GIT_COMMIT", "")
89
+ env.workspace = os.getenv("WORKSPACE", "")
90
+ env.job_name = os.getenv("JOB_NAME", "")
91
+ env.run_id = os.getenv("BUILD_NUMBER", "")
92
+ env.cache_dir = os.path.expanduser("~/.cache/gitinstall")
93
+ return env
94
+
95
+ # Azure Pipelines
96
+ if os.getenv("TF_BUILD") == "True":
97
+ env.is_ci = True
98
+ env.platform = "azure"
99
+ env.runner_os = os.getenv("Agent.OS", "").lower()
100
+ env.branch = os.getenv("Build.SourceBranchName", "")
101
+ env.commit_sha = os.getenv("Build.SourceVersion", "")
102
+ env.pr_number = os.getenv("System.PullRequest.PullRequestId", "")
103
+ env.workspace = os.getenv("Build.SourcesDirectory", "")
104
+ env.run_id = os.getenv("Build.BuildId", "")
105
+ env.cache_dir = os.path.expanduser("~/.cache/gitinstall")
106
+ return env
107
+
108
+ # CircleCI
109
+ if os.getenv("CIRCLECI") == "true":
110
+ env.is_ci = True
111
+ env.platform = "circle"
112
+ env.branch = os.getenv("CIRCLE_BRANCH", "")
113
+ env.commit_sha = os.getenv("CIRCLE_SHA1", "")
114
+ env.pr_number = os.getenv("CIRCLE_PR_NUMBER", "")
115
+ env.repo_url = os.getenv("CIRCLE_REPOSITORY_URL", "")
116
+ env.workspace = os.getenv("CIRCLE_WORKING_DIRECTORY", "")
117
+ env.job_name = os.getenv("CIRCLE_JOB", "")
118
+ env.run_id = os.getenv("CIRCLE_BUILD_NUM", "")
119
+ env.cache_dir = os.path.expanduser("~/.cache/gitinstall")
120
+ return env
121
+
122
+ # 通用 CI 检测
123
+ if os.getenv("CI") == "true" or os.getenv("CI") == "1":
124
+ env.is_ci = True
125
+ env.platform = "unknown"
126
+ env.cache_dir = os.path.expanduser("~/.cache/gitinstall")
127
+ return env
128
+
129
+ return env
130
+
131
+
132
+ # ─────────────────────────────────────────────
133
+ # GitHub Actions 配置生成
134
+ # ─────────────────────────────────────────────
135
+
136
+ def generate_github_action(
137
+ repos: list[str],
138
+ python_version: str = "3.12",
139
+ os_list: Optional[list[str]] = None,
140
+ cache_enabled: bool = True,
141
+ sbom_export: bool = False,
142
+ audit_enabled: bool = True,
143
+ ) -> str:
144
+ """
145
+ 生成 GitHub Actions workflow YAML。
146
+
147
+ Args:
148
+ repos: 要安装的仓库列表
149
+ python_version: Python 版本
150
+ os_list: 运行平台列表
151
+ cache_enabled: 是否启用缓存
152
+ sbom_export: 是否导出 SBOM
153
+ audit_enabled: 是否运行安全审计
154
+
155
+ Returns:
156
+ YAML 字符串
157
+ """
158
+ if os_list is None:
159
+ os_list = ["ubuntu-latest"]
160
+
161
+ # 使用字符串拼接而非 YAML 库(零依赖)
162
+ lines = [
163
+ "name: gitinstall CI",
164
+ "",
165
+ "on:",
166
+ " push:",
167
+ " branches: [main, master]",
168
+ " pull_request:",
169
+ " branches: [main, master]",
170
+ " workflow_dispatch:",
171
+ "",
172
+ "jobs:",
173
+ " install-test:",
174
+ f" runs-on: ${{{{ matrix.os }}}}",
175
+ " strategy:",
176
+ " matrix:",
177
+ f" os: [{', '.join(os_list)}]",
178
+ " fail-fast: false",
179
+ "",
180
+ " steps:",
181
+ " - uses: actions/checkout@v4",
182
+ "",
183
+ f" - name: Set up Python {python_version}",
184
+ " uses: actions/setup-python@v5",
185
+ " with:",
186
+ f" python-version: '{python_version}'",
187
+ "",
188
+ " - name: Install gitinstall",
189
+ " run: pip install gitinstall",
190
+ "",
191
+ ]
192
+
193
+ if cache_enabled:
194
+ lines.extend([
195
+ " - name: Cache gitinstall data",
196
+ " uses: actions/cache@v4",
197
+ " with:",
198
+ " path: ~/.cache/gitinstall",
199
+ " key: gitinstall-${{ runner.os }}-${{ hashFiles('**/requirements*.txt') }}",
200
+ " restore-keys: |",
201
+ " gitinstall-${{ runner.os }}-",
202
+ "",
203
+ ])
204
+
205
+ if audit_enabled:
206
+ lines.extend([
207
+ " - name: Security audit",
208
+ " run: gitinstall audit --format json --output audit-report.json",
209
+ "",
210
+ ])
211
+
212
+ # 安装各仓库
213
+ for repo in repos:
214
+ safe_name = repo.replace("/", "-").replace(".", "-")
215
+ lines.extend([
216
+ f" - name: Install {repo}",
217
+ f" run: gitinstall install {repo} --ci --json-report install-{safe_name}.json",
218
+ "",
219
+ ])
220
+
221
+ if sbom_export:
222
+ lines.extend([
223
+ " - name: Generate SBOM",
224
+ " run: gitinstall sbom --format cyclonedx --output sbom.cdx.json",
225
+ "",
226
+ " - name: Upload SBOM",
227
+ " uses: actions/upload-artifact@v4",
228
+ " with:",
229
+ " name: sbom-${{ matrix.os }}",
230
+ " path: sbom.cdx.json",
231
+ "",
232
+ ])
233
+
234
+ # 上传报告
235
+ lines.extend([
236
+ " - name: Upload install reports",
237
+ " if: always()",
238
+ " uses: actions/upload-artifact@v4",
239
+ " with:",
240
+ " name: install-reports-${{ matrix.os }}",
241
+ " path: |",
242
+ " install-*.json",
243
+ " audit-report.json",
244
+ "",
245
+ ])
246
+
247
+ return "\n".join(lines)
248
+
249
+
250
+ def generate_gitlab_ci(
251
+ repos: list[str],
252
+ python_version: str = "3.12",
253
+ audit_enabled: bool = True,
254
+ ) -> str:
255
+ """生成 GitLab CI 配置"""
256
+ lines = [
257
+ f"image: python:{python_version}-slim",
258
+ "",
259
+ "variables:",
260
+ " PIP_CACHE_DIR: $CI_PROJECT_DIR/.pip-cache",
261
+ "",
262
+ "cache:",
263
+ " paths:",
264
+ " - .pip-cache/",
265
+ " - .cache/gitinstall/",
266
+ "",
267
+ "stages:",
268
+ " - audit",
269
+ " - install",
270
+ "",
271
+ ]
272
+
273
+ if audit_enabled:
274
+ lines.extend([
275
+ "security-audit:",
276
+ " stage: audit",
277
+ " script:",
278
+ " - pip install gitinstall",
279
+ " - gitinstall audit --format json --output audit-report.json",
280
+ " artifacts:",
281
+ " reports:",
282
+ " dependency_scanning: audit-report.json",
283
+ " expire_in: 1 week",
284
+ "",
285
+ ])
286
+
287
+ for repo in repos:
288
+ safe_name = repo.replace("/", "-").replace(".", "-")
289
+ lines.extend([
290
+ f"install-{safe_name}:",
291
+ " stage: install",
292
+ " script:",
293
+ " - pip install gitinstall",
294
+ f" - gitinstall install {repo} --ci",
295
+ " allow_failure: true",
296
+ "",
297
+ ])
298
+
299
+ return "\n".join(lines)
300
+
301
+
302
+ # ─────────────────────────────────────────────
303
+ # 安装锁文件(可复现安装)
304
+ # ─────────────────────────────────────────────
305
+
306
+ @dataclass
307
+ class InstallLockEntry:
308
+ """锁文件中的单条记录"""
309
+ repo_url: str
310
+ commit_sha: str = "" # 锁定的 commit
311
+ tag: str = "" # 锁定的 tag
312
+ install_commands: list[str] = field(default_factory=list)
313
+ checksum: str = "" # 仓库文件的 hash
314
+ installed_at: float = 0.0
315
+ environment: dict = field(default_factory=dict) # os, python, arch
316
+
317
+
318
+ def generate_install_lock(
319
+ installs: list[dict],
320
+ output_path: str = "gitinstall.lock.json",
321
+ ) -> str:
322
+ """
323
+ 生成安装锁文件,确保可复现安装。
324
+
325
+ Args:
326
+ installs: 安装记录列表 [{"repo": "...", "commit": "...", "commands": [...]}]
327
+ output_path: 输出路径
328
+
329
+ Returns:
330
+ 输出文件路径
331
+ """
332
+ import platform
333
+
334
+ lock = {
335
+ "version": 1,
336
+ "generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
337
+ "generator": "gitinstall/1.1.0",
338
+ "environment": {
339
+ "os": platform.system().lower(),
340
+ "arch": platform.machine(),
341
+ "python": platform.python_version(),
342
+ },
343
+ "installs": [],
344
+ }
345
+
346
+ for inst in installs:
347
+ entry = {
348
+ "repo": inst.get("repo", ""),
349
+ "commit_sha": inst.get("commit", ""),
350
+ "tag": inst.get("tag", ""),
351
+ "commands": inst.get("commands", []),
352
+ "installed_at": inst.get("installed_at", time.time()),
353
+ }
354
+ # 生成 checksum
355
+ content = json.dumps(entry, sort_keys=True)
356
+ entry["checksum"] = hashlib.sha256(content.encode()).hexdigest()[:16]
357
+ lock["installs"].append(entry)
358
+
359
+ with open(output_path, "w", encoding="utf-8") as f:
360
+ json.dump(lock, f, indent=2, ensure_ascii=False)
361
+
362
+ return output_path
363
+
364
+
365
+ def load_install_lock(lock_path: str = "gitinstall.lock.json") -> list[dict]:
366
+ """加载安装锁文件"""
367
+ if not os.path.isfile(lock_path):
368
+ return []
369
+ with open(lock_path, encoding="utf-8") as f:
370
+ lock = json.load(f)
371
+ return lock.get("installs", [])
372
+
373
+
374
+ # ─────────────────────────────────────────────
375
+ # JUnit XML 报告生成(CI 友好)
376
+ # ─────────────────────────────────────────────
377
+
378
+ def generate_junit_report(
379
+ results: list[dict],
380
+ output_path: str = "gitinstall-results.xml",
381
+ ) -> str:
382
+ """
383
+ 将安装结果转换为 JUnit XML 格式。
384
+
385
+ CI 平台(GitHub Actions, GitLab, Jenkins)都支持 JUnit 报告。
386
+
387
+ Args:
388
+ results: [{"repo": "...", "success": bool, "duration": float, "error": "..."}]
389
+ output_path: 输出路径
390
+ """
391
+ total = len(results)
392
+ failures = sum(1 for r in results if not r.get("success"))
393
+ total_time = sum(r.get("duration", 0) for r in results)
394
+
395
+ lines = [
396
+ '<?xml version="1.0" encoding="UTF-8"?>',
397
+ f'<testsuites tests="{total}" failures="{failures}" time="{total_time:.2f}">',
398
+ f' <testsuite name="gitinstall" tests="{total}" failures="{failures}" time="{total_time:.2f}">',
399
+ ]
400
+
401
+ for r in results:
402
+ repo = _xml_escape(r.get("repo", "unknown"))
403
+ duration = r.get("duration", 0)
404
+ lines.append(f' <testcase name="{repo}" time="{duration:.2f}">')
405
+ if not r.get("success"):
406
+ error = _xml_escape(r.get("error", "Unknown error"))
407
+ lines.append(f' <failure message="Installation failed">{error}</failure>')
408
+ lines.append(" </testcase>")
409
+
410
+ lines.extend([
411
+ " </testsuite>",
412
+ "</testsuites>",
413
+ ])
414
+
415
+ xml = "\n".join(lines)
416
+ with open(output_path, "w", encoding="utf-8") as f:
417
+ f.write(xml)
418
+
419
+ return output_path
420
+
421
+
422
+ def _xml_escape(s: str) -> str:
423
+ """XML 字符转义"""
424
+ return (s.replace("&", "&amp;").replace("<", "&lt;")
425
+ .replace(">", "&gt;").replace('"', "&quot;")
426
+ .replace("'", "&apos;"))
427
+
428
+
429
+ # ─────────────────────────────────────────────
430
+ # 批量安装(CI 模式)
431
+ # ─────────────────────────────────────────────
432
+
433
+ @dataclass
434
+ class BatchInstallResult:
435
+ """批量安装结果"""
436
+ total: int = 0
437
+ success: int = 0
438
+ failed: int = 0
439
+ skipped: int = 0
440
+ results: list[dict] = field(default_factory=list)
441
+ duration: float = 0.0
442
+
443
+
444
+ def plan_batch_install(
445
+ repos: list[str],
446
+ parallelism: int = 1,
447
+ fail_fast: bool = False,
448
+ skip_audit: bool = False,
449
+ ) -> dict:
450
+ """
451
+ 规划批量安装策略。
452
+
453
+ Args:
454
+ repos: 仓库列表
455
+ parallelism: 并行度
456
+ fail_fast: 失败时立即停止
457
+ skip_audit: 跳过安全审计
458
+
459
+ Returns:
460
+ 安装计划 dict
461
+ """
462
+ ci = detect_ci_environment()
463
+
464
+ plan = {
465
+ "ci_detected": ci.is_ci,
466
+ "ci_platform": ci.platform,
467
+ "total_repos": len(repos),
468
+ "parallelism": min(parallelism, len(repos)),
469
+ "fail_fast": fail_fast,
470
+ "skip_audit": skip_audit,
471
+ "phases": [],
472
+ }
473
+
474
+ # Phase 1: 安全审计(如果启用)
475
+ if not skip_audit:
476
+ plan["phases"].append({
477
+ "name": "security_audit",
478
+ "description": "安全审计扫描",
479
+ "repos": repos,
480
+ })
481
+
482
+ # Phase 2: 分批安装
483
+ batch_size = max(1, parallelism)
484
+ for i in range(0, len(repos), batch_size):
485
+ batch = repos[i:i + batch_size]
486
+ plan["phases"].append({
487
+ "name": f"install_batch_{i // batch_size + 1}",
488
+ "description": f"安装批次 {i // batch_size + 1}",
489
+ "repos": batch,
490
+ })
491
+
492
+ # Phase 3: 报告生成
493
+ plan["phases"].append({
494
+ "name": "report",
495
+ "description": "生成安装报告",
496
+ "outputs": ["junit_xml", "json_report"],
497
+ })
498
+
499
+ return plan