| 1 | """Untimed fixture preparation: generate, characterize, ingest, serve.
|
| 2 |
|
| 3 | Everything here happens OUTSIDE the timed path and is recorded as
|
| 4 | ``ingest.*`` diagnostic rows (setup cost is itself scale evidence: e.g. how
|
| 5 | long Oak needs to ingest a 100k-commit Git history).
|
| 6 |
|
| 7 | Layout under ``<work>/<fixture_id>/``::
|
| 8 |
|
| 9 | fixture/src.git generated bare repository (the fixture)
|
| 10 | fixture/GENERATOR_MANIFEST.json, fixture/SIZER.json
|
| 11 | git/work git working repository (all refs local)
|
| 12 | git/served/bench/fx.git bare repository behind the smart-HTTP server
|
| 13 | git/clientA, git/clientB sync clients (clone over HTTP)
|
| 14 | oak/work oak repository ingested with `oak clone <src.git>`
|
| 15 | oak/serve-data `oak serve` data directory
|
| 16 | oak/clientA, oak/clientB sync clients (clone from the loopback server)
|
| 17 |
|
| 18 | Transport: both subjects sync over loopback HTTP (git: the stdlib smart-HTTP
|
| 19 | server in oakbench.gitserver, CGI `git http-backend`; oak: `oak serve`).
|
| 20 | Rows name the transport; neither side uses a local-file shortcut.
|
| 21 | """
|
| 22 |
|
| 23 | from __future__ import annotations
|
| 24 |
|
| 25 | import json
|
| 26 | import os
|
| 27 | import re
|
| 28 | import shutil
|
| 29 | import subprocess
|
| 30 | import sys
|
| 31 | import time
|
| 32 | from dataclasses import dataclass, field
|
| 33 | from pathlib import Path
|
| 34 | from typing import Any, Dict, List, Optional, Tuple
|
| 35 |
|
| 36 | from . import generator as gen
|
| 37 | from . import sizer as sz
|
| 38 | from .tiers import Tier
|
| 39 |
|
| 40 | SERVE_REPO = "bench/fx"
|
| 41 | TRANSPORT_GIT = "loopback_http_git_http_backend"
|
| 42 | TRANSPORT_OAK = "loopback_http_oak_serve"
|
| 43 |
|
| 44 |
|
| 45 | def _say(message: str) -> None:
|
| 46 | print(message, file=sys.stderr, flush=True)
|
| 47 |
|
| 48 |
|
| 49 | class PrepError(RuntimeError):
|
| 50 | pass
|
| 51 |
|
| 52 |
|
| 53 | def run(cmd: List[str], cwd: Optional[Path] = None, env: Optional[Dict[str, str]] = None,
|
| 54 | check: bool = True, input_bytes: Optional[bytes] = None, timeout: Optional[float] = None
|
| 55 | ) -> subprocess.CompletedProcess:
|
| 56 | proc = subprocess.run(cmd, cwd=str(cwd) if cwd else None, env=env, capture_output=True,
|
| 57 | input=input_bytes, timeout=timeout)
|
| 58 | if check and proc.returncode != 0:
|
| 59 | raise PrepError(f"{' '.join(cmd)} (cwd={cwd}) exited {proc.returncode}: "
|
| 60 | f"{proc.stderr.decode(errors='replace')[-2000:]}")
|
| 61 | return proc
|
| 62 |
|
| 63 |
|
| 64 | def dir_bytes(root: Path) -> int:
|
| 65 | return gen.dir_bytes(root) if root.exists() else 0
|
| 66 |
|
| 67 |
|
| 68 | @dataclass
|
| 69 | class Expected:
|
| 70 | """Oracle facts read from the fixture with git (independent of Oak)."""
|
| 71 |
|
| 72 | main_file_count: int
|
| 73 | sample: Dict[str, str] # path -> git blob id at main
|
| 74 | probe_path: str
|
| 75 | probe_bytes: bytes
|
| 76 | probe_lines: int
|
| 77 | probe_commit_count: int
|
| 78 | main_commits: int
|
| 79 | branch: str
|
| 80 | branch_paths: List[str]
|
| 81 | branch_bytes: Dict[str, bytes]
|
| 82 | main_bytes_of_branch_paths: Dict[str, bytes]
|
| 83 | conflict: Dict[str, Any]
|
| 84 | protected: List[str]
|
| 85 | all_paths_sample_size: int
|
| 86 |
|
| 87 |
|
| 88 | @dataclass
|
| 89 | class Prepared:
|
| 90 | tier: Tier
|
| 91 | root: Path
|
| 92 | fixture_dir: Path
|
| 93 | manifest: Dict[str, Any]
|
| 94 | sizer: Dict[str, Any]
|
| 95 | expected: Expected
|
| 96 | git_bin: str
|
| 97 | oak_bin: str
|
| 98 | env: Dict[str, str]
|
| 99 | phases: Dict[str, float] = field(default_factory=dict)
|
| 100 | kinds: Tuple[str, ...] = ()
|
| 101 | git_url: Optional[str] = None
|
| 102 | oak_url: Optional[str] = None
|
| 103 | oak_base_branch: Optional[str] = None
|
| 104 | oak_sync_branch: Optional[str] = None
|
| 105 | oak_branches_created: int = 0
|
| 106 | oak_seed: Optional[str] = None
|
| 107 | oak_seed_refusal: Optional[str] = None
|
| 108 | oak_remote_unavailable: Optional[str] = None
|
| 109 | oak_identity: Dict[str, Any] = field(default_factory=dict)
|
| 110 | git_server: Any = None
|
| 111 | oak_server: Optional[subprocess.Popen] = None
|
| 112 | notes: List[str] = field(default_factory=list)
|
| 113 | # Mutable harness bookkeeping shared by ops.
|
| 114 | state: Dict[str, Any] = field(default_factory=dict)
|
| 115 |
|
| 116 | def path(self, *parts: str) -> Path:
|
| 117 | return self.root.joinpath(*parts)
|
| 118 |
|
| 119 | def fixture_fields(self) -> Dict[str, Any]:
|
| 120 | spec = self.tier.spec
|
| 121 | return {
|
| 122 | "tier": self.tier.tier_id,
|
| 123 | "tier_kind": self.tier.kind,
|
| 124 | "fixture_id": self.tier.fixture_id,
|
| 125 | "fixture_generator": "suites/scale/scalesuite/generator.py",
|
| 126 | "fixture_version": self.manifest["generator_version"],
|
| 127 | "fixture_seed": spec.seed,
|
| 128 | "fixture_manifest_sha256": self.manifest["refs_sha256"],
|
| 129 | "fixture_spec_sha256": self.manifest["spec_sha256"],
|
| 130 | "fixture_head_commit": self.manifest["head_commit"],
|
| 131 | "fixture_stats": sz.compact(self.sizer),
|
| 132 | }
|
| 133 |
|
| 134 | def close(self) -> None:
|
| 135 | if self.git_server is not None:
|
| 136 | try:
|
| 137 | self.git_server.stop()
|
| 138 | except Exception: # noqa: BLE001 - best-effort teardown
|
| 139 | pass
|
| 140 | self.git_server = None
|
| 141 | if self.oak_server is not None:
|
| 142 | self.oak_server.terminate()
|
| 143 | try:
|
| 144 | self.oak_server.wait(timeout=10)
|
| 145 | except subprocess.TimeoutExpired:
|
| 146 | self.oak_server.kill()
|
| 147 | self.oak_server.wait()
|
| 148 | self.oak_server = None
|
| 149 |
|
| 150 |
|
| 151 | def _git(prepared_git: str, repo: Path, *args: str, env: Dict[str, str], check: bool = True) -> str:
|
| 152 | return run([prepared_git, "-C", str(repo), *args], env=env, check=check).stdout.decode(errors="replace")
|
| 153 |
|
| 154 |
|
| 155 | def compute_expected(tier: Tier, fixture: Path, manifest: Dict[str, Any], git_bin: str,
|
| 156 | env: Dict[str, str], sample_size: int = 400) -> Expected:
|
| 157 | repo = fixture / "src.git"
|
| 158 | listing = run([git_bin, "-C", str(repo), "ls-tree", "-r", "-z", "main"], env=env).stdout
|
| 159 | entries: List[Tuple[str, str]] = []
|
| 160 | for item in listing.split(b"\0"):
|
| 161 | if not item:
|
| 162 | continue
|
| 163 | meta, path = item.split(b"\t", 1)
|
| 164 | entries.append((path.decode(), meta.split()[2].decode()))
|
| 165 | import random
|
| 166 | rng = random.Random(f"{tier.spec.seed}:oracle-sample")
|
| 167 | chosen = rng.sample(entries, min(sample_size, len(entries)))
|
| 168 | probe = manifest["probe_path"]
|
| 169 | probe_bytes = run([git_bin, "-C", str(repo), "cat-file", "blob", f"main:{probe}"], env=env).stdout
|
| 170 | branch = manifest["branch_sample"].get("name") or ""
|
| 171 | branch_paths = list(manifest["branch_sample"].get("changed_paths") or [])
|
| 172 | branch_bytes = {p: run([git_bin, "-C", str(repo), "cat-file", "blob", f"{branch}:{p}"], env=env).stdout
|
| 173 | for p in branch_paths} if branch else {}
|
| 174 | main_bytes = {p: run([git_bin, "-C", str(repo), "cat-file", "blob", f"main:{p}"], env=env).stdout
|
| 175 | for p in branch_paths}
|
| 176 | protected = sorted({probe, *branch_paths, *(manifest.get("conflict", {}).get("paths_sample") or []),
|
| 177 | *manifest.get("binary_paths", []), ".gitignore"})
|
| 178 | return Expected(
|
| 179 | main_file_count=len(entries), sample=dict(chosen), probe_path=probe, probe_bytes=probe_bytes,
|
| 180 | probe_lines=probe_bytes.count(b"\n"), probe_commit_count=manifest["probe_path_commit_count"],
|
| 181 | main_commits=manifest["main_commits"], branch=branch, branch_paths=branch_paths,
|
| 182 | branch_bytes=branch_bytes, main_bytes_of_branch_paths=main_bytes,
|
| 183 | conflict=manifest.get("conflict") or {}, protected=protected, all_paths_sample_size=len(chosen))
|
| 184 |
|
| 185 |
|
| 186 | def write_ignored_extras(work: Path, tier: Tier) -> None:
|
| 187 | if not tier.ignored_extras:
|
| 188 | return
|
| 189 | base = work / "build" / "cache"
|
| 190 | for index in range(tier.ignored_extras):
|
| 191 | target = base / f"g{index // 500}" / f"x{index}.o"
|
| 192 | target.parent.mkdir(parents=True, exist_ok=True)
|
| 193 | target.write_bytes(b"ignored build product %d\n" % index)
|
| 194 |
|
| 195 |
|
| 196 | def _one_line_error(text: str) -> str:
|
| 197 | try:
|
| 198 | return str(json.loads(text.strip().splitlines()[0])["error"]["message"])[:400]
|
| 199 | except (ValueError, KeyError, IndexError, TypeError):
|
| 200 | return " ".join(text.split())[:400]
|
| 201 |
|
| 202 |
|
| 203 | def _snapshot_seed_repo(p: "Prepared", src: Path, dest: Path) -> Path:
|
| 204 | """A bare git repo whose main is ONE parentless commit with the fixture's main tree."""
|
| 205 | git, env = p.git_bin, dict(p.env)
|
| 206 | env.update(GIT_AUTHOR_DATE="1700000000 +0000", GIT_COMMITTER_DATE="1700000000 +0000")
|
| 207 | if dest.exists():
|
| 208 | shutil.rmtree(dest)
|
| 209 | run([git, "init", "-q", "--bare", "-b", "main", str(dest)], env=env)
|
| 210 | run([git, "-C", str(dest), "fetch", "-q", str(src), "main:refs/heads/fixture-main"], env=env)
|
| 211 | tree = _git(git, dest, "rev-parse", "refs/heads/fixture-main^{tree}", env=env).strip()
|
| 212 | commit = run([git, "-C", str(dest), "commit-tree", tree, "-m", "snapshot of fixture main"], env=env
|
| 213 | ).stdout.decode().strip()
|
| 214 | _git(git, dest, "update-ref", "refs/heads/main", commit, env=env)
|
| 215 | _git(git, dest, "update-ref", "-d", "refs/heads/fixture-main", env=env)
|
| 216 | return dest
|
| 217 |
|
| 218 |
|
| 219 | def _oak_serve(oak_bin: str, data_dir: Path, env: Dict[str, str], log_path: Path) -> Tuple[subprocess.Popen, str]:
|
| 220 | data_dir.mkdir(parents=True, exist_ok=True)
|
| 221 | log = log_path.open("wb")
|
| 222 | proc = subprocess.Popen([oak_bin, "serve", "--port", "0", "--dir", str(data_dir)], stdout=log,
|
| 223 | stderr=subprocess.STDOUT, env=env)
|
| 224 | deadline = time.time() + 30
|
| 225 | while time.time() < deadline:
|
| 226 | text = log_path.read_text(errors="replace")
|
| 227 | match = re.search(r"listening on (http://127\.0\.0\.1:\d+)", text)
|
| 228 | if match:
|
| 229 | return proc, match.group(1)
|
| 230 | if proc.poll() is not None:
|
| 231 | break
|
| 232 | time.sleep(0.1)
|
| 233 | proc.terminate()
|
| 234 | raise PrepError(f"oak serve did not report a listening port: {log_path.read_text(errors='replace')[-500:]}")
|
| 235 |
|
| 236 |
|
| 237 | def oak_status(oak_bin: str, cwd: Path, env: Dict[str, str]) -> Dict[str, Any]:
|
| 238 | out = run([oak_bin, "status", "--json"], cwd=cwd, env=env).stdout
|
| 239 | return json.loads(out)
|
| 240 |
|
| 241 |
|
| 242 | def prepare(tier: Tier, work_root: Path, *, git_bin: str, oak_bin: str, env: Dict[str, str],
|
| 243 | kinds: Tuple[str, ...] = ("git", "oak"), oak_branch_cap: Optional[int] = None,
|
| 244 | log=None) -> Prepared:
|
| 245 | """Generate (or reuse) the fixture and build every workspace the ops need."""
|
| 246 | root = work_root / tier.fixture_id
|
| 247 | fixture = root / "fixture"
|
| 248 | phases: Dict[str, float] = {}
|
| 249 |
|
| 250 | def phase(name: str, started: float) -> None:
|
| 251 | phases[name] = round(time.perf_counter() - started, 3)
|
| 252 | (log or _say)(f"[scale] {tier.tier_id}: {name} {phases[name]:.1f}s")
|
| 253 |
|
| 254 | started = time.perf_counter()
|
| 255 | manifest = gen.generate(tier.spec, fixture, git_bin)
|
| 256 | phases["fixture.generate"] = manifest["generation_elapsed_s"] if not manifest.get("reused") else 0.0
|
| 257 | phases["fixture.generate_reused"] = 1.0 if manifest.get("reused") else 0.0
|
| 258 | sizer_path = fixture / "SIZER.json"
|
| 259 | if sizer_path.exists() and json.loads(sizer_path.read_text()).get("refs_sha256") == manifest["refs_sha256"]:
|
| 260 | sizer = json.loads(sizer_path.read_text())["stats"]
|
| 261 | else:
|
| 262 | t0 = time.perf_counter()
|
| 263 | sizer = sz.measure(fixture / "src.git", git_bin)
|
| 264 | sizer_path.write_text(json.dumps({"refs_sha256": manifest["refs_sha256"], "stats": sizer}, indent=2) + "\n")
|
| 265 | phase("fixture.sizer", t0)
|
| 266 | expected = compute_expected(tier, fixture, manifest, git_bin, env)
|
| 267 | prepared = Prepared(tier=tier, root=root, fixture_dir=fixture, manifest=manifest, sizer=sizer,
|
| 268 | expected=expected, git_bin=git_bin, oak_bin=oak_bin, env=env, phases=phases,
|
| 269 | kinds=kinds)
|
| 270 | src = fixture / "src.git"
|
| 271 | try:
|
| 272 | if "git" in kinds:
|
| 273 | _prepare_git(prepared, src, phase)
|
| 274 | if "oak" in kinds:
|
| 275 | _prepare_oak(prepared, src, phase, oak_branch_cap)
|
| 276 | except Exception:
|
| 277 | prepared.close()
|
| 278 | raise
|
| 279 | phases["prepare.total"] = round(time.perf_counter() - started, 3)
|
| 280 | return prepared
|
| 281 |
|
| 282 |
|
| 283 | def _prepare_git(p: Prepared, src: Path, phase) -> None:
|
| 284 | git, env = p.git_bin, p.env
|
| 285 | gdir = p.path("git")
|
| 286 | if gdir.exists():
|
| 287 | shutil.rmtree(gdir)
|
| 288 | gdir.mkdir(parents=True)
|
| 289 | work = gdir / "work"
|
| 290 | t0 = time.perf_counter()
|
| 291 | # All fixture refs become local branches (like the oak work repo, which
|
| 292 | # creates them locally), objects hardlinked from the read-only fixture.
|
| 293 | run([git, "clone", "-q", "--bare", str(src), str(work / ".git")], env=env)
|
| 294 | for key, value in (("core.bare", "false"), ("gc.auto", "0"), ("maintenance.auto", "false"),
|
| 295 | ("user.name", "Oak Bench"), ("user.email", "[email protected]")):
|
| 296 | _git(git, work, "config", key, value, env=env)
|
| 297 | _git(git, work, "reset", "-q", "--hard", "main", env=env)
|
| 298 | write_ignored_extras(work, p.tier)
|
| 299 | phase("ingest.git_worktree", t0)
|
| 300 |
|
| 301 | t0 = time.perf_counter()
|
| 302 | served = gdir / "served" / (SERVE_REPO + ".git")
|
| 303 | served.parent.mkdir(parents=True)
|
| 304 | run([git, "clone", "-q", "--bare", str(src), str(served)], env=env)
|
| 305 | _git(git, served, "config", "http.receivepack", "true", env=env)
|
| 306 | _git(git, served, "config", "gc.auto", "0", env=env)
|
| 307 | import sys
|
| 308 | scripts = str(Path(__file__).resolve().parents[3] / "scripts")
|
| 309 | if scripts not in sys.path:
|
| 310 | sys.path.insert(0, scripts)
|
| 311 | from oakbench.gitserver import GitHTTPServer
|
| 312 | p.git_server = GitHTTPServer(gdir / "served", git_bin=git).start()
|
| 313 | p.git_url = f"{p.git_server.url}/{SERVE_REPO}.git"
|
| 314 | phase("ingest.git_serve_seed", t0)
|
| 315 |
|
| 316 | if not p.tier.sync:
|
| 317 | return
|
| 318 | t0 = time.perf_counter()
|
| 319 | for name in ("clientA", "clientB"):
|
| 320 | run([git, "clone", "-q", p.git_url, str(gdir / name)], env=env)
|
| 321 | for key, value in (("gc.auto", "0"), ("maintenance.auto", "false"), ("pull.rebase", "false")):
|
| 322 | _git(git, gdir / name, "config", key, value, env=env)
|
| 323 | phase("ingest.git_sync_clients", t0)
|
| 324 |
|
| 325 |
|
| 326 | def _commit_file(p: Prepared, kind: str, repo: Path, rel: str, content: bytes) -> None:
|
| 327 | target = repo / rel
|
| 328 | target.parent.mkdir(parents=True, exist_ok=True)
|
| 329 | target.write_bytes(content)
|
| 330 | if kind == "git":
|
| 331 | _git(p.git_bin, repo, "add", "-A", env=p.env)
|
| 332 | _git(p.git_bin, repo, "commit", "-q", "--no-verify", "-m", f"harness {rel}", env=p.env)
|
| 333 | else:
|
| 334 | run([p.oak_bin, "commit", "--json"], cwd=repo, env=p.env)
|
| 335 |
|
| 336 |
|
| 337 | def _prepare_oak(p: Prepared, src: Path, phase, branch_cap: Optional[int]) -> None:
|
| 338 | oak, env = p.oak_bin, p.env
|
| 339 | odir = p.path("oak")
|
| 340 | if odir.exists():
|
| 341 | shutil.rmtree(odir)
|
| 342 | odir.mkdir(parents=True)
|
| 343 | work = odir / "work"
|
| 344 | t0 = time.perf_counter()
|
| 345 | # `oak clone <path>.git` is Oak's own git ingest: it runs `git clone` and
|
| 346 | # replays the HEAD history into Oak storage (other refs are not imported).
|
| 347 | ingest_env = dict(env)
|
| 348 | ingest_env["PATH"] = str(Path(p.git_bin).parent) + os.pathsep + env.get("PATH", "")
|
| 349 | run([oak, "clone", str(src), str(work)], env=ingest_env, input_bytes=b"")
|
| 350 | shutil.rmtree(work / ".git", ignore_errors=True)
|
| 351 | phase("ingest.oak_import", t0)
|
| 352 | p.oak_base_branch = oak_status(oak, work, env)["branch"]
|
| 353 |
|
| 354 | t0 = time.perf_counter()
|
| 355 | spec = p.tier.spec
|
| 356 | factory = gen.ContentFactory(spec)
|
| 357 | cache = gen.layout(spec)
|
| 358 | total = spec.branches if branch_cap is None else min(spec.branches, branch_cap)
|
| 359 | created = 0
|
| 360 | for number in range(total):
|
| 361 | name = gen.branch_name(number)
|
| 362 | run([oak, "switch", "-c", name], cwd=work, env=env)
|
| 363 | for index, path in gen.branch_changes(spec, number, cache):
|
| 364 | (work / path).write_bytes(factory.text(index, name))
|
| 365 | run([oak, "commit", "--json"], cwd=work, env=env)
|
| 366 | created += 1
|
| 367 | if spec.conflict_files:
|
| 368 | for side in ("bench-ours", "bench-theirs"):
|
| 369 | run([oak, "switch", "-c", side], cwd=work, env=env)
|
| 370 | for i in range(spec.conflict_files):
|
| 371 | (work / gen.file_path(spec, i, cache)).write_bytes(factory.text(i, side))
|
| 372 | run([oak, "commit", "--json"], cwd=work, env=env)
|
| 373 | run([oak, "switch", p.oak_base_branch], cwd=work, env=env)
|
| 374 | p.oak_branches_created = created
|
| 375 | if total < spec.branches:
|
| 376 | p.notes.append(f"oak branches capped at {total} of {spec.branches} (--oak-branch-cap)")
|
| 377 | write_ignored_extras(work, p.tier)
|
| 378 | phase("ingest.oak_branches", t0)
|
| 379 |
|
| 380 | if not p.tier.sync:
|
| 381 | p.oak_remote_unavailable = "tier_excludes_sync_clients_disk_budget"
|
| 382 | return
|
| 383 | t0 = time.perf_counter()
|
| 384 | p.oak_server, p.oak_url = _oak_serve(oak, odir / "serve-data", env, odir / "serve.log")
|
| 385 | # Seed from a SEPARATE import: pushing from the work repo would give it a
|
| 386 | # remote, after which Oak refreshes main from the server during local ops
|
| 387 | # (network inside timed local ops; and with a snapshot seed, an unrelated
|
| 388 | # main replaces the imported one). The git work repo never contacts its
|
| 389 | # origin during local ops either.
|
| 390 | ingest_env = dict(env)
|
| 391 | ingest_env["PATH"] = str(Path(p.git_bin).parent) + os.pathsep + env.get("PATH", "")
|
| 392 | full_seed = odir / "seed-full"
|
| 393 | run([oak, "clone", str(src), str(full_seed)], env=ingest_env, input_bytes=b"")
|
| 394 | shutil.rmtree(full_seed / ".git", ignore_errors=True)
|
| 395 | seeded = run([oak, "push", "-r", p.oak_url, "--repo", SERVE_REPO, "--json"], cwd=full_seed, env=env,
|
| 396 | check=False)
|
| 397 | shutil.rmtree(full_seed, ignore_errors=True)
|
| 398 | if seeded.returncode == 0:
|
| 399 | p.oak_seed = "full_history"
|
| 400 | phase("ingest.oak_serve_seed", t0)
|
| 401 | else:
|
| 402 | # Oak's staged push has fixed per-operation caps (oak_core::protocol
|
| 403 | # STAGED_*: 1M resolved manifest entries summed over every pushed
|
| 404 | # commit, 50k blobs, 100k commits, ...), so a history that exceeds them
|
| 405 | # cannot be published at all. Record the refusal, then seed the
|
| 406 | # server with a one-commit snapshot of main (like `git clone
|
| 407 | # --depth 1` content) so the sync ops still run, labelled as such.
|
| 408 | message = (seeded.stdout + seeded.stderr).decode(errors="replace")
|
| 409 | p.oak_seed_refusal = _one_line_error(message)
|
| 410 | p.notes.append(f"oak serve could not accept the full history: {p.oak_seed_refusal}")
|
| 411 | phase("ingest.oak_serve_seed_full_refused", t0)
|
| 412 | t0 = time.perf_counter()
|
| 413 | snapshot = _snapshot_seed_repo(p, src, odir / "seed-src.git")
|
| 414 | seed_work = odir / "seed-work"
|
| 415 | run([oak, "clone", str(snapshot), str(seed_work)], env=ingest_env, input_bytes=b"")
|
| 416 | shutil.rmtree(seed_work / ".git", ignore_errors=True)
|
| 417 | second = run([oak, "push", "-r", p.oak_url, "--repo", SERVE_REPO, "--json"], cwd=seed_work, env=env,
|
| 418 | check=False)
|
| 419 | shutil.rmtree(seed_work, ignore_errors=True)
|
| 420 | shutil.rmtree(snapshot, ignore_errors=True)
|
| 421 | if second.returncode != 0:
|
| 422 | p.oak_remote_unavailable = "oak_push_admission_cap: " + _one_line_error(
|
| 423 | (second.stdout + second.stderr).decode(errors="replace"))
|
| 424 | p.notes.append(f"oak snapshot seed also refused; oak sync ops skipped: {p.oak_remote_unavailable}")
|
| 425 | phase("ingest.oak_serve_seed_snapshot_refused", t0)
|
| 426 | return
|
| 427 | p.oak_seed = "snapshot_1_commit"
|
| 428 | phase("ingest.oak_serve_seed_snapshot", t0)
|
| 429 |
|
| 430 | t0 = time.perf_counter()
|
| 431 | client_a = odir / "clientA"
|
| 432 | run([oak, "clone", "-r", p.oak_url, SERVE_REPO, str(client_a)], env=env)
|
| 433 | _commit_file(p, "oak", client_a, "sync/seed.txt", b"seed\n")
|
| 434 | run([oak, "push", "--json"], cwd=client_a, env=env)
|
| 435 | p.oak_sync_branch = oak_status(oak, client_a, env)["branch"]
|
| 436 | run([oak, "clone", "-r", p.oak_url, SERVE_REPO, str(odir / "clientB"), "--branch", p.oak_sync_branch], env=env)
|
| 437 | phase("ingest.oak_sync_clients", t0)
|
| 438 |
|