refactor(doctor): split doctor.go into modular package files (#653)
* refactor(doctor): split doctor.go into modular package files Split the 3,171-line doctor.go into logical sub-files within the cmd/bd/doctor/ package, reducing the main file to 834 lines (74% reduction). New package structure: - types.go: DoctorCheck struct and status constants - installation.go: CheckInstallation, CheckMultipleDatabases, CheckPermissions - git.go: CheckGitHooks, CheckMergeDriver, CheckSyncBranch* checks - database.go: CheckDatabaseVersion, CheckSchemaCompatibility, CheckDatabaseJSONLSync - version.go: CheckCLIVersion, CheckMetadataVersionTracking, CompareVersions - integrity.go: CheckIDFormat, CheckDependencyCycles, CheckTombstones - daemon.go: CheckDaemonStatus, CheckVersionMismatch - quick.go: Quick checks for sync-branch and hooks Updated tests to use exported doctor.CheckXxx() functions and doctor.StatusXxx constants. * fix(doctor): suppress gosec G204 false positives Add #nosec G204 comments to exec.Command calls in CheckSyncBranchHealth where variables come from trusted sources (config files or hardcoded values like "main"/"master"/"origin"), not untrusted user input.
This commit is contained in:
155
cmd/bd/doctor/quick.go
Normal file
155
cmd/bd/doctor/quick.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package doctor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/steveyegge/beads/internal/git"
|
||||
"github.com/steveyegge/beads/internal/syncbranch"
|
||||
)
|
||||
|
||||
// CheckSyncBranchQuick does a fast check for sync-branch configuration.
|
||||
// Returns empty string if OK, otherwise returns issue description.
|
||||
func CheckSyncBranchQuick() string {
|
||||
if syncbranch.IsConfigured() {
|
||||
return ""
|
||||
}
|
||||
return "sync-branch not configured in config.yaml"
|
||||
}
|
||||
|
||||
// CheckHooksQuick does a fast check for outdated git hooks.
|
||||
// Checks all beads hooks: pre-commit, post-merge, pre-push, post-checkout.
|
||||
// cliVersion is the current CLI version to compare against.
|
||||
func CheckHooksQuick(cliVersion string) string {
|
||||
// Get actual git directory (handles worktrees where .git is a file)
|
||||
gitDir, err := git.GetGitDir()
|
||||
if err != nil {
|
||||
return "" // Not a git repo, skip
|
||||
}
|
||||
hooksDir := filepath.Join(gitDir, "hooks")
|
||||
|
||||
// Check if hooks dir exists
|
||||
if _, err := os.Stat(hooksDir); os.IsNotExist(err) {
|
||||
return "" // No git hooks directory, skip
|
||||
}
|
||||
|
||||
// Check all beads-managed hooks
|
||||
hookNames := []string{"pre-commit", "post-merge", "pre-push", "post-checkout"}
|
||||
|
||||
var outdatedHooks []string
|
||||
var oldestVersion string
|
||||
|
||||
for _, hookName := range hookNames {
|
||||
hookPath := filepath.Join(hooksDir, hookName)
|
||||
content, err := os.ReadFile(hookPath) // #nosec G304 - path is controlled
|
||||
if err != nil {
|
||||
continue // Hook doesn't exist, skip (will be caught by full doctor)
|
||||
}
|
||||
|
||||
// Look for version marker
|
||||
hookContent := string(content)
|
||||
if !strings.Contains(hookContent, "bd-hooks-version:") {
|
||||
continue // Not a bd hook or old format, skip
|
||||
}
|
||||
|
||||
// Extract version
|
||||
for _, line := range strings.Split(hookContent, "\n") {
|
||||
if strings.Contains(line, "bd-hooks-version:") {
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
hookVersion := strings.TrimSpace(parts[1])
|
||||
if hookVersion != cliVersion {
|
||||
outdatedHooks = append(outdatedHooks, hookName)
|
||||
// Track the oldest version for display
|
||||
if oldestVersion == "" || CompareVersions(hookVersion, oldestVersion) < 0 {
|
||||
oldestVersion = hookVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(outdatedHooks) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Return summary of outdated hooks
|
||||
if len(outdatedHooks) == 1 {
|
||||
return fmt.Sprintf("Git hook %s outdated (%s → %s)", outdatedHooks[0], oldestVersion, cliVersion)
|
||||
}
|
||||
return fmt.Sprintf("Git hooks outdated: %s (%s → %s)", strings.Join(outdatedHooks, ", "), oldestVersion, cliVersion)
|
||||
}
|
||||
|
||||
// CheckSyncBranchHookQuick does a fast check for sync-branch hook compatibility.
|
||||
// Returns empty string if OK, otherwise returns issue description.
|
||||
func CheckSyncBranchHookQuick(path string) string {
|
||||
// Check if sync-branch is configured
|
||||
syncBranch := syncbranch.GetFromYAML()
|
||||
if syncBranch == "" {
|
||||
return "" // sync-branch not configured, nothing to check
|
||||
}
|
||||
|
||||
// Get git directory
|
||||
cmd := exec.Command("git", "rev-parse", "--git-dir")
|
||||
cmd.Dir = path
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "" // Not a git repo, skip
|
||||
}
|
||||
gitDir := strings.TrimSpace(string(output))
|
||||
if !filepath.IsAbs(gitDir) {
|
||||
gitDir = filepath.Join(path, gitDir)
|
||||
}
|
||||
|
||||
// Find pre-push hook (check shared hooks first)
|
||||
var hookPath string
|
||||
hooksPathCmd := exec.Command("git", "config", "--get", "core.hooksPath")
|
||||
hooksPathCmd.Dir = path
|
||||
if hooksPathOutput, err := hooksPathCmd.Output(); err == nil {
|
||||
sharedHooksDir := strings.TrimSpace(string(hooksPathOutput))
|
||||
if !filepath.IsAbs(sharedHooksDir) {
|
||||
sharedHooksDir = filepath.Join(path, sharedHooksDir)
|
||||
}
|
||||
hookPath = filepath.Join(sharedHooksDir, "pre-push")
|
||||
} else {
|
||||
hookPath = filepath.Join(gitDir, "hooks", "pre-push")
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(hookPath) // #nosec G304 - path is controlled
|
||||
if err != nil {
|
||||
return "" // No pre-push hook, covered by other checks
|
||||
}
|
||||
|
||||
// Check if bd hook and extract version
|
||||
hookStr := string(content)
|
||||
if !strings.Contains(hookStr, "bd-hooks-version:") {
|
||||
return "" // Not a bd hook, can't check
|
||||
}
|
||||
|
||||
var hookVersion string
|
||||
for _, line := range strings.Split(hookStr, "\n") {
|
||||
if strings.Contains(line, "bd-hooks-version:") {
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
hookVersion = strings.TrimSpace(parts[1])
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hookVersion == "" {
|
||||
return "" // Can't determine version
|
||||
}
|
||||
|
||||
// Check if version < MinSyncBranchHookVersion (when sync-branch bypass was added)
|
||||
if CompareVersions(hookVersion, MinSyncBranchHookVersion) < 0 {
|
||||
return fmt.Sprintf("Pre-push hook (%s) incompatible with sync-branch mode (requires %s+)", hookVersion, MinSyncBranchHookVersion)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user