mirror of
https://github.com/yarlson/lnk.git
synced 2025-09-02 18:12:33 +02:00
refactor(errors): implement structured error handling for improved debugging
This commit is contained in:
218
internal/git/errors.go
Normal file
218
internal/git/errors.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package git
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ANSI color codes for consistent formatting
|
||||
const (
|
||||
colorReset = "\033[0m"
|
||||
colorBold = "\033[1m"
|
||||
colorGreen = "\033[32m"
|
||||
colorYellow = "\033[33m"
|
||||
)
|
||||
|
||||
// formatError creates a consistently formatted error message with ❌ prefix
|
||||
func formatError(message string, args ...interface{}) string {
|
||||
return fmt.Sprintf("❌ "+message, args...)
|
||||
}
|
||||
|
||||
// formatURL formats a URL with styling
|
||||
func formatURL(url string) string {
|
||||
return fmt.Sprintf("%s%s%s", colorBold, url, colorReset)
|
||||
}
|
||||
|
||||
// formatRemote formats a remote name with styling
|
||||
func formatRemote(remote string) string {
|
||||
return fmt.Sprintf("%s%s%s", colorGreen, remote, colorReset)
|
||||
}
|
||||
|
||||
// GitInitError represents an error during git initialization
|
||||
type GitInitError struct {
|
||||
Output string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *GitInitError) Error() string {
|
||||
return formatError("Failed to initialize git repository. Please ensure git is installed and try again.")
|
||||
}
|
||||
|
||||
func (e *GitInitError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// BranchSetupError represents an error setting up the default branch
|
||||
type BranchSetupError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *BranchSetupError) Error() string {
|
||||
return formatError("Failed to set up the default branch. Please check your git installation.")
|
||||
}
|
||||
|
||||
func (e *BranchSetupError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// RemoteExistsError represents an error when a remote already exists with different URL
|
||||
type RemoteExistsError struct {
|
||||
Remote string
|
||||
ExistingURL string
|
||||
NewURL string
|
||||
}
|
||||
|
||||
func (e *RemoteExistsError) Error() string {
|
||||
return formatError("Remote %s is already configured with a different repository (%s). Cannot add %s.",
|
||||
formatRemote(e.Remote), formatURL(e.ExistingURL), formatURL(e.NewURL))
|
||||
}
|
||||
|
||||
func (e *RemoteExistsError) Unwrap() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GitCommandError represents a generic git command execution error
|
||||
type GitCommandError struct {
|
||||
Command string
|
||||
Output string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *GitCommandError) Error() string {
|
||||
// Provide user-friendly messages based on common command types
|
||||
switch e.Command {
|
||||
case "add":
|
||||
return formatError("Failed to stage files for commit. Please check file permissions and try again.")
|
||||
case "commit":
|
||||
return formatError("Failed to create commit. Please ensure you have staged changes and try again.")
|
||||
case "remote add":
|
||||
return formatError("Failed to add remote repository. Please check the repository URL and try again.")
|
||||
case "rm":
|
||||
return formatError("Failed to remove file from git tracking. Please check if the file exists and try again.")
|
||||
case "log":
|
||||
return formatError("Failed to retrieve commit history.")
|
||||
case "remote":
|
||||
return formatError("Failed to retrieve remote repository information.")
|
||||
case "clone":
|
||||
return formatError("Failed to clone repository. Please check the repository URL and your network connection.")
|
||||
default:
|
||||
return formatError("Git operation failed. Please check your repository state and try again.")
|
||||
}
|
||||
}
|
||||
|
||||
func (e *GitCommandError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// NoRemoteError represents an error when no remote is configured
|
||||
type NoRemoteError struct{}
|
||||
|
||||
func (e *NoRemoteError) Error() string {
|
||||
return formatError("No remote repository is configured. Please add a remote repository first.")
|
||||
}
|
||||
|
||||
func (e *NoRemoteError) Unwrap() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoteNotFoundError represents an error when a specific remote is not found
|
||||
type RemoteNotFoundError struct {
|
||||
Remote string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *RemoteNotFoundError) Error() string {
|
||||
return formatError("Remote repository %s is not configured.", formatRemote(e.Remote))
|
||||
}
|
||||
|
||||
func (e *RemoteNotFoundError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// GitConfigError represents an error with git configuration
|
||||
type GitConfigError struct {
|
||||
Setting string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *GitConfigError) Error() string {
|
||||
return formatError("Failed to configure git settings. Please check your git installation.")
|
||||
}
|
||||
|
||||
func (e *GitConfigError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// UncommittedChangesError represents an error checking for uncommitted changes
|
||||
type UncommittedChangesError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *UncommittedChangesError) Error() string {
|
||||
return formatError("Failed to check repository status. Please verify your git repository is valid.")
|
||||
}
|
||||
|
||||
func (e *UncommittedChangesError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// DirectoryRemovalError represents an error removing a directory
|
||||
type DirectoryRemovalError struct {
|
||||
Path string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *DirectoryRemovalError) Error() string {
|
||||
return formatError("Failed to prepare directory for operation. Please check directory permissions.")
|
||||
}
|
||||
|
||||
func (e *DirectoryRemovalError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// DirectoryCreationError represents an error creating a directory
|
||||
type DirectoryCreationError struct {
|
||||
Path string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *DirectoryCreationError) Error() string {
|
||||
return formatError("Failed to create directory. Please check permissions and available disk space.")
|
||||
}
|
||||
|
||||
func (e *DirectoryCreationError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// PushError represents an error during git push operation
|
||||
type PushError struct {
|
||||
Reason string
|
||||
Output string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *PushError) Error() string {
|
||||
if e.Reason != "" {
|
||||
return formatError("Cannot push changes: %s", e.Reason)
|
||||
}
|
||||
return formatError("Failed to push changes to remote repository. Please check your network connection and repository permissions.")
|
||||
}
|
||||
|
||||
func (e *PushError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// PullError represents an error during git pull operation
|
||||
type PullError struct {
|
||||
Reason string
|
||||
Output string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *PullError) Error() string {
|
||||
if e.Reason != "" {
|
||||
return formatError("Cannot pull changes: %s", e.Reason)
|
||||
}
|
||||
return formatError("Failed to pull changes from remote repository. Please check your network connection and resolve any conflicts.")
|
||||
}
|
||||
|
||||
func (e *PullError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
@@ -34,7 +34,7 @@ func (g *Git) Init() error {
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("git init failed: %w\nOutput: %s", err, string(output))
|
||||
return &GitInitError{Output: string(output), Err: err}
|
||||
}
|
||||
|
||||
// Set the default branch to main
|
||||
@@ -42,7 +42,7 @@ func (g *Git) Init() error {
|
||||
cmd.Dir = g.repoPath
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to set default branch to main: %w", err)
|
||||
return &BranchSetupError{Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func (g *Git) AddRemote(name, url string) error {
|
||||
return nil
|
||||
}
|
||||
// Different URL, error
|
||||
return fmt.Errorf("remote %s already exists with different URL: %s (trying to add: %s)", name, existingURL, url)
|
||||
return &RemoteExistsError{Remote: name, ExistingURL: existingURL, NewURL: url}
|
||||
}
|
||||
|
||||
// Remote doesn't exist, add it
|
||||
@@ -69,7 +69,7 @@ func (g *Git) AddRemote(name, url string) error {
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("git remote add failed: %w\nOutput: %s", err, string(output))
|
||||
return &GitCommandError{Command: "remote add", Output: string(output), Err: err}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -164,7 +164,7 @@ func (g *Git) Add(filename string) error {
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("git add failed: %w\nOutput: %s", err, string(output))
|
||||
return &GitCommandError{Command: "add", Output: string(output), Err: err}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -189,7 +189,7 @@ func (g *Git) Remove(filename string) error {
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("git rm failed: %w\nOutput: %s", err, string(output))
|
||||
return &GitCommandError{Command: "rm", Output: string(output), Err: err}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -207,7 +207,7 @@ func (g *Git) Commit(message string) error {
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("git commit failed: %w\nOutput: %s", err, string(output))
|
||||
return &GitCommandError{Command: "commit", Output: string(output), Err: err}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -223,7 +223,7 @@ func (g *Git) ensureGitConfig() error {
|
||||
cmd = exec.Command("git", "config", "user.name", "Lnk User")
|
||||
cmd.Dir = g.repoPath
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to set git user.name: %w", err)
|
||||
return &GitConfigError{Setting: "user.name", Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ func (g *Git) ensureGitConfig() error {
|
||||
cmd = exec.Command("git", "config", "user.email", "lnk@localhost")
|
||||
cmd.Dir = g.repoPath
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to set git user.email: %w", err)
|
||||
return &GitConfigError{Setting: "user.email", Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ func (g *Git) GetCommits() ([]string, error) {
|
||||
if strings.Contains(outputStr, "does not have any commits yet") {
|
||||
return []string{}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("git log failed: %w", err)
|
||||
return nil, &GitCommandError{Command: "log", Output: outputStr, Err: err}
|
||||
}
|
||||
|
||||
commits := strings.Split(strings.TrimSpace(string(output)), "\n")
|
||||
@@ -282,18 +282,18 @@ func (g *Git) GetRemoteInfo() (string, error) {
|
||||
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to list remotes: %w", err)
|
||||
return "", &GitCommandError{Command: "remote", Output: string(output), Err: err}
|
||||
}
|
||||
|
||||
remotes := strings.Split(strings.TrimSpace(string(output)), "\n")
|
||||
if len(remotes) == 0 || remotes[0] == "" {
|
||||
return "", fmt.Errorf("no remote configured")
|
||||
return "", &NoRemoteError{}
|
||||
}
|
||||
|
||||
// Use the first remote
|
||||
url, err = g.getRemoteURL(remotes[0])
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get remote URL: %w", err)
|
||||
return "", &RemoteNotFoundError{Remote: remotes[0], Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +319,7 @@ func (g *Git) GetStatus() (*StatusInfo, error) {
|
||||
// Check for uncommitted changes
|
||||
dirty, err := g.HasChanges()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check for uncommitted changes: %w", err)
|
||||
return nil, &UncommittedChangesError{Err: err}
|
||||
}
|
||||
|
||||
// Get the remote tracking branch
|
||||
@@ -410,7 +410,7 @@ func (g *Git) HasChanges() (bool, error) {
|
||||
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("git status failed: %w", err)
|
||||
return false, &GitCommandError{Command: "status", Output: string(output), Err: err}
|
||||
}
|
||||
|
||||
return len(strings.TrimSpace(string(output))) > 0, nil
|
||||
@@ -423,7 +423,7 @@ func (g *Git) AddAll() error {
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("git add failed: %w\nOutput: %s", err, string(output))
|
||||
return &GitCommandError{Command: "add", Output: string(output), Err: err}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -434,7 +434,7 @@ func (g *Git) Push() error {
|
||||
// First ensure we have a remote configured
|
||||
_, err := g.GetRemoteInfo()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot push: %w", err)
|
||||
return &PushError{Reason: err.Error(), Err: err}
|
||||
}
|
||||
|
||||
cmd := exec.Command("git", "push", "-u", "origin", "main")
|
||||
@@ -442,7 +442,7 @@ func (g *Git) Push() error {
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("git push failed: %w\nOutput: %s", err, string(output))
|
||||
return &PushError{Output: string(output), Err: err}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -453,7 +453,7 @@ func (g *Git) Pull() error {
|
||||
// First ensure we have a remote configured
|
||||
_, err := g.GetRemoteInfo()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot pull: %w", err)
|
||||
return &PullError{Reason: err.Error(), Err: err}
|
||||
}
|
||||
|
||||
cmd := exec.Command("git", "pull", "origin", "main")
|
||||
@@ -461,7 +461,7 @@ func (g *Git) Pull() error {
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("git pull failed: %w\nOutput: %s", err, string(output))
|
||||
return &PullError{Output: string(output), Err: err}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -471,20 +471,20 @@ func (g *Git) Pull() error {
|
||||
func (g *Git) Clone(url string) error {
|
||||
// Remove the directory if it exists to ensure clean clone
|
||||
if err := os.RemoveAll(g.repoPath); err != nil {
|
||||
return fmt.Errorf("failed to remove existing directory: %w", err)
|
||||
return &DirectoryRemovalError{Path: g.repoPath, Err: err}
|
||||
}
|
||||
|
||||
// Create parent directory
|
||||
parentDir := filepath.Dir(g.repoPath)
|
||||
if err := os.MkdirAll(parentDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create parent directory: %w", err)
|
||||
return &DirectoryCreationError{Path: parentDir, Err: err}
|
||||
}
|
||||
|
||||
// Clone the repository
|
||||
cmd := exec.Command("git", "clone", url, g.repoPath)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("git clone failed: %w\nOutput: %s", err, string(output))
|
||||
return &GitCommandError{Command: "clone", Output: string(output), Err: err}
|
||||
}
|
||||
|
||||
// Set up upstream tracking for main branch
|
||||
|
Reference in New Issue
Block a user