Publish only the work: leave build artifacts out and report them (#80)
CI / checks (push) Skipped
CI / checks (pull_request) Failing after 56s
CI / checks (push) Skipped
CI / checks (pull_request) Failing after 56s
The clone gets the standard build artifacts in its own exclude file, so they never show as changes, even to a model's `git add -A`. The harness's commits leave the same patterns out even when the project tracks them, as the sandbox's main did. What was left out is reported in the publish note and the PR description. Found again in bring-up run 3 (sandbox PR #208). Fixes #80. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
This commit is contained in:
1 parent
4f152af170
commit
e02bac110d
5 files changed
+164
-8
No files matched your search
@@ -33,6 +33,35 @@ class GitError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
# Build artifacts and caches that are never the work (D4, #80). Kept out of
|
||||
# every commit the harness makes, tracked or not, and excluded in the clone
|
||||
# so they don't even show as changes. Conservative on purpose: a pattern here
|
||||
# must never match a project's real source.
|
||||
ARTIFACT_PATTERNS = (
|
||||
"__pycache__/",
|
||||
"*.py[cod]",
|
||||
".pytest_cache/",
|
||||
".mypy_cache/",
|
||||
".ruff_cache/",
|
||||
".tox/",
|
||||
".nox/",
|
||||
"*.egg-info/",
|
||||
".coverage",
|
||||
"htmlcov/",
|
||||
".venv/",
|
||||
"node_modules/",
|
||||
".DS_Store",
|
||||
)
|
||||
_EXCLUDES_MARK = "# nisse-dev: build artifacts are never the work (D4)"
|
||||
|
||||
|
||||
def _pathspec(pattern: str) -> str:
|
||||
"""A pathspec leaving ``pattern`` out wherever it occurs in the tree."""
|
||||
glob = pattern.rstrip("/")
|
||||
tail = "/**" if pattern.endswith("/") else ""
|
||||
return f":(exclude,glob)**/{glob}{tail}"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class HTTPSToken:
|
||||
"""A push-only account's token, for HTTPS remotes (D4)."""
|
||||
@@ -84,6 +113,7 @@ class GitRepo:
|
||||
*args: str,
|
||||
credential: PushCredential | None = None,
|
||||
cwd: str | None = None,
|
||||
raw: bool = False,
|
||||
) -> str:
|
||||
quoted = " ".join(shlex.quote(a) for a in args)
|
||||
command, stdin, secrets = f"git {quoted}", None, {}
|
||||
@@ -117,7 +147,8 @@ class GitRepo:
|
||||
f"git {args[0]} failed: "
|
||||
f"{result.stderr.strip() or result.stdout.strip()}"
|
||||
)
|
||||
return result.stdout.strip()
|
||||
# Raw for output whose leading whitespace means something (porcelain).
|
||||
return result.stdout if raw else result.stdout.strip()
|
||||
|
||||
# -- inspection -------------------------------------------------------
|
||||
|
||||
@@ -191,15 +222,42 @@ class GitRepo:
|
||||
return False
|
||||
return True
|
||||
|
||||
def install_excludes(self) -> None:
|
||||
"""List the build artifacts in the clone's own exclude file, so they
|
||||
never appear as changes. Local to the clone: nothing in the project
|
||||
changes. Idempotent."""
|
||||
path = shlex.quote(str(self.root / ".git" / "info" / "exclude"))
|
||||
block = "\n".join([_EXCLUDES_MARK, *ARTIFACT_PATTERNS])
|
||||
result = self.host.execute(
|
||||
f'mkdir -p -- "$(dirname -- {path})" && touch -- {path} && '
|
||||
f"grep -qxF {shlex.quote(_EXCLUDES_MARK)} {path} || "
|
||||
f"cat >> {path}",
|
||||
stdin=block,
|
||||
timeout_s=TIMEOUT_S,
|
||||
)
|
||||
if not result.ok:
|
||||
raise GitError(f"could not set excludes: {result.stderr.strip()}")
|
||||
|
||||
def commit_all(self, message: str) -> str | None:
|
||||
"""Returns the new commit sha, or None when there was nothing to
|
||||
commit — an empty milestone is a fact to report, not an error."""
|
||||
"""Commit the work: every change except build artifacts, which are
|
||||
left out even when the project tracks them (#80). Returns the new
|
||||
commit sha, or None when there was nothing to commit — an empty
|
||||
milestone is a fact to report, not an error."""
|
||||
if not self.has_changes():
|
||||
return None
|
||||
self._git("add", "-A")
|
||||
self._git("add", "-A", "--", ".", *(_pathspec(p) for p in ARTIFACT_PATTERNS))
|
||||
staged = self._git("diff", "--cached", "--name-only")
|
||||
if not staged:
|
||||
return None
|
||||
self._git("commit", "-q", "-m", message)
|
||||
return self.head_sha()
|
||||
|
||||
def left_out(self) -> list[str]:
|
||||
"""Changes not in the last commit: build artifacts the harness left
|
||||
out, to be reported rather than silently dropped (D4)."""
|
||||
out = self._git("status", "--porcelain", "--untracked-files=all", raw=True)
|
||||
return sorted(line[3:] for line in out.splitlines() if line)
|
||||
|
||||
def push(
|
||||
self, remote_url: str, branch: str, *, credential: PushCredential | None = None
|
||||
) -> None:
|
||||
|
||||
Reference in new issue
Block a user