homelab: add work verbs (start/land/clean) with a land verification gate
Completes the infra-loop verb surface. work start creates .worktrees/<topic> on <user>/<topic> off <remote>/master (git-crypt-aware, ensures .worktrees is ignored) and prints the path for native EnterWorktree entry. work land fetches, merges master in, verifies, pushes HEAD:master with non-fast-forward retry, and falls back to pushing the feature branch for a PR when the direct push is rejected (branch protection). work clean removes the worktree + branch. Safety: work land REFUSES to push when it cannot verify (no --verify-cmd and no auto-detected suite) unless --no-verify is passed. This was added after an accidental smoke-test invocation pushed unverified WIP to master (benign — the infra CI applied 0 stacks since the diff was cli/-only — but the gate makes an unverified land a deliberate choice, not the default). Known v0.1 limitation: land does not yet block on CI to green; that arrives with the ci/deploy watch verbs. It prints a reminder to follow the pipeline manually. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
36d562c15c
commit
087b415f73
4 changed files with 276 additions and 0 deletions
38
cli/repo.go
38
cli/repo.go
|
|
@ -1,7 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
|
@ -61,3 +64,38 @@ func gitRemotes(dir string) ([]string, error) {
|
|||
}
|
||||
return strings.Split(out, "\n"), nil
|
||||
}
|
||||
|
||||
// isGitCryptRepo reports whether the repo at repoRoot uses git-crypt.
|
||||
func isGitCryptRepo(repoRoot string) bool {
|
||||
b, err := os.ReadFile(filepath.Join(repoRoot, ".gitattributes"))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return hasGitCryptAttr(string(b))
|
||||
}
|
||||
|
||||
// cryptFlagsFor returns the git-crypt filter flags when repoRoot is encrypted,
|
||||
// else nil. These are injected per-command and never persisted.
|
||||
func cryptFlagsFor(repoRoot string) []string {
|
||||
if isGitCryptRepo(repoRoot) {
|
||||
return gitCryptFlags()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// gitStream runs `git [cryptFlags] -C repoRoot <args>` with live output.
|
||||
func gitStream(repoRoot string, cryptFlags []string, args ...string) error {
|
||||
full := append(append([]string{}, cryptFlags...), append([]string{"-C", repoRoot}, args...)...)
|
||||
return runStreamingIn("", "git", full...)
|
||||
}
|
||||
|
||||
// currentUser returns the OS username for branch naming (<user>/<topic>).
|
||||
func currentUser() string {
|
||||
if u := os.Getenv("USER"); u != "" {
|
||||
return u
|
||||
}
|
||||
if u, err := user.Current(); err == nil && u.Username != "" {
|
||||
return u.Username
|
||||
}
|
||||
return "user"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue