package validation import ( "context" "fmt" "regexp" "strings" "github.com/iwasforcedtobehere/git-automation-cli/internal/git" ) // ValidationResult represents the result of a validation type ValidationResult struct { IsValid bool Errors []string } // NewValidationResult creates a new validation result func NewValidationResult() *ValidationResult { return &ValidationResult{ IsValid: true, Errors: make([]string, 0), } } // AddError adds an error to the validation result func (vr *ValidationResult) AddError(format string, args ...interface{}) { vr.IsValid = false vr.Errors = append(vr.Errors, fmt.Sprintf(format, args...)) } // GetErrors returns all validation errors as a single string func (vr *ValidationResult) GetErrors() string { return strings.Join(vr.Errors, "\n") } // ValidateGitRepository checks if the current directory is a Git repository func ValidateGitRepository(ctx context.Context) *ValidationResult { result := NewValidationResult() if _, err := git.CurrentBranch(ctx); err != nil { result.AddError("not a git repository or no branch found") } return result } // ValidateBranchName checks if a branch name is valid func ValidateBranchName(name string) *ValidationResult { result := NewValidationResult() // Check if branch name is empty if name == "" { result.AddError("branch name cannot be empty") return result } // Check for invalid characters if strings.Contains(name, "..") || strings.Contains(name, " ") || strings.Contains(name, ":") || strings.Contains(name, "?") || strings.Contains(name, "*") || strings.Contains(name, "[") || strings.Contains(name, "@") || strings.Contains(name, "\\") { result.AddError("branch name contains invalid characters") } // Check if branch name starts with a dot if strings.HasPrefix(name, ".") { result.AddError("branch name cannot start with a dot") } // Check if branch name ends with a slash if strings.HasSuffix(name, "/") { result.AddError("branch name cannot end with a slash") } // Check for consecutive slashes if strings.Contains(name, "//") { result.AddError("branch name cannot contain consecutive slashes") } // Check if branch name is a valid refname // Git refname rules: must contain at least one / if !strings.Contains(name, "/") && name != "HEAD" && name != "main" && name != "master" { // This is not necessarily an error, but a warning // We'll allow it but log a warning } return result } // ValidateCommitMessage checks if a commit message is valid func ValidateCommitMessage(message string) *ValidationResult { result := NewValidationResult() // Check if commit message is empty if strings.TrimSpace(message) == "" { result.AddError("commit message cannot be empty") return result } // Check if commit message is too long if len(message) > 72 { result.AddError("commit message should be 72 characters or less") } // Check if commit message starts with a whitespace if strings.HasPrefix(message, " ") { result.AddError("commit message should not start with whitespace") } return result } // ValidateConventionalCommit checks if a commit message follows the conventional commit format func ValidateConventionalCommit(message string) *ValidationResult { result := NewValidationResult() // Conventional commit format: type(scope): description // Example: feat(auth): add login functionality // Check if commit message is empty if strings.TrimSpace(message) == "" { result.AddError("commit message cannot be empty") return result } // Regex pattern for conventional commit pattern := `^(\w+)(\([^)]+\))?(!)?:\s.+` matched, err := regexp.MatchString(pattern, message) if err != nil { result.AddError("error validating conventional commit format: %v", err) return result } if !matched { result.AddError("commit message does not follow conventional commit format") result.AddError("expected format: type(scope): description") result.AddError("example: feat(auth): add login functionality") } return result } // ValidateGitHubToken checks if a GitHub token is valid func ValidateGitHubToken(token string) *ValidationResult { result := NewValidationResult() // Check if token is empty if token == "" { result.AddError("GitHub token cannot be empty") return result } // Check if token starts with "ghp_" (personal access token) or "gho_" (OAuth token) if !strings.HasPrefix(token, "ghp_") && !strings.HasPrefix(token, "gho_") { result.AddError("GitHub token should start with 'ghp_' or 'gho_'") } // Check token length (GitHub tokens are typically 40 characters long) if len(token) != 40 { result.AddError("GitHub token should be 40 characters long") } return result } // ValidateWorkingDirectory checks if the working directory is clean func ValidateWorkingDirectory(ctx context.Context) *ValidationResult { result := NewValidationResult() clean, err := git.IsCleanWorkingDir(ctx) if err != nil { result.AddError("failed to check working directory: %v", err) return result } if clean { result.AddError("no changes to commit") } return result }