mirror of
https://github.com/yarlson/lnk.git
synced 2025-08-29 17:49:47 +02:00
refactor(errors): implement structured error handling for improved debugging
This commit is contained in:
14
cmd/add.go
14
cmd/add.go
@@ -1,7 +1,6 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -10,11 +9,12 @@ import (
|
||||
|
||||
func newAddCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "add <file>",
|
||||
Short: "✨ Add a file to lnk management",
|
||||
Long: "Moves a file to the lnk repository and creates a symlink in its place.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
SilenceUsage: true,
|
||||
Use: "add <file>",
|
||||
Short: "✨ Add a file to lnk management",
|
||||
Long: "Moves a file to the lnk repository and creates a symlink in its place.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
filePath := args[0]
|
||||
host, _ := cmd.Flags().GetString("host")
|
||||
@@ -22,7 +22,7 @@ func newAddCmd() *cobra.Command {
|
||||
lnk := core.NewLnk(core.WithHost(host))
|
||||
|
||||
if err := lnk.Add(filePath); err != nil {
|
||||
return fmt.Errorf("failed to add file: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
basename := filepath.Base(filePath)
|
||||
|
13
cmd/init.go
13
cmd/init.go
@@ -1,24 +1,23 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/yarlson/lnk/internal/core"
|
||||
)
|
||||
|
||||
func newInitCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "🎯 Initialize a new lnk repository",
|
||||
Long: "Creates the lnk directory and initializes a Git repository for managing dotfiles.",
|
||||
SilenceUsage: true,
|
||||
Use: "init",
|
||||
Short: "🎯 Initialize a new lnk repository",
|
||||
Long: "Creates the lnk directory and initializes a Git repository for managing dotfiles.",
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
remote, _ := cmd.Flags().GetString("remote")
|
||||
|
||||
lnk := core.NewLnk()
|
||||
if err := lnk.InitWithRemote(remote); err != nil {
|
||||
return fmt.Errorf("failed to initialize lnk: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if remote != "" {
|
||||
|
20
cmd/list.go
20
cmd/list.go
@@ -1,7 +1,6 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -12,10 +11,11 @@ import (
|
||||
|
||||
func newListCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "📋 List files managed by lnk",
|
||||
Long: "Display all files and directories currently managed by lnk.",
|
||||
SilenceUsage: true,
|
||||
Use: "list",
|
||||
Short: "📋 List files managed by lnk",
|
||||
Long: "Display all files and directories currently managed by lnk.",
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
host, _ := cmd.Flags().GetString("host")
|
||||
all, _ := cmd.Flags().GetBool("all")
|
||||
@@ -44,7 +44,7 @@ func listCommonConfig(cmd *cobra.Command) error {
|
||||
lnk := core.NewLnk()
|
||||
managedItems, err := lnk.List()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list managed items: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(managedItems) == 0 {
|
||||
@@ -71,7 +71,7 @@ func listHostConfig(cmd *cobra.Command, host string) error {
|
||||
lnk := core.NewLnk(core.WithHost(host))
|
||||
managedItems, err := lnk.List()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list managed items for host %s: %w", host, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(managedItems) == 0 {
|
||||
@@ -101,7 +101,7 @@ func listAllConfigs(cmd *cobra.Command) error {
|
||||
lnk := core.NewLnk()
|
||||
commonItems, err := lnk.List()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list common managed items: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
printf(cmd, "🌐 \033[1mCommon configuration\033[0m (\033[36m%d item", len(commonItems))
|
||||
@@ -121,7 +121,7 @@ func listAllConfigs(cmd *cobra.Command) error {
|
||||
// Find all host-specific configurations
|
||||
hosts, err := findHostConfigs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find host configurations: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
@@ -163,7 +163,7 @@ func findHostConfigs() ([]string, error) {
|
||||
|
||||
entries, err := os.ReadDir(repoPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read repository directory: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var hosts []string
|
||||
|
13
cmd/pull.go
13
cmd/pull.go
@@ -1,18 +1,17 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/yarlson/lnk/internal/core"
|
||||
)
|
||||
|
||||
func newPullCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "pull",
|
||||
Short: "⬇️ Pull changes from remote and restore symlinks",
|
||||
Long: "Fetches changes from remote repository and automatically restores symlinks for all managed files.",
|
||||
SilenceUsage: true,
|
||||
Use: "pull",
|
||||
Short: "⬇️ Pull changes from remote and restore symlinks",
|
||||
Long: "Fetches changes from remote repository and automatically restores symlinks for all managed files.",
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
host, _ := cmd.Flags().GetString("host")
|
||||
|
||||
@@ -20,7 +19,7 @@ func newPullCmd() *cobra.Command {
|
||||
|
||||
restored, err := lnk.Pull()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to pull changes: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(restored) > 0 {
|
||||
|
15
cmd/push.go
15
cmd/push.go
@@ -1,19 +1,18 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/yarlson/lnk/internal/core"
|
||||
)
|
||||
|
||||
func newPushCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "push [message]",
|
||||
Short: "🚀 Push local changes to remote repository",
|
||||
Long: "Stages all changes, creates a sync commit with the provided message, and pushes to remote.",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
SilenceUsage: true,
|
||||
Use: "push [message]",
|
||||
Short: "🚀 Push local changes to remote repository",
|
||||
Long: "Stages all changes, creates a sync commit with the provided message, and pushes to remote.",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
message := "lnk: sync configuration files"
|
||||
if len(args) > 0 {
|
||||
@@ -22,7 +21,7 @@ func newPushCmd() *cobra.Command {
|
||||
|
||||
lnk := core.NewLnk()
|
||||
if err := lnk.Push(message); err != nil {
|
||||
return fmt.Errorf("failed to push changes: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
printf(cmd, "🚀 \033[1;32mSuccessfully pushed changes\033[0m\n")
|
||||
|
14
cmd/rm.go
14
cmd/rm.go
@@ -1,7 +1,6 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -10,11 +9,12 @@ import (
|
||||
|
||||
func newRemoveCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "rm <file>",
|
||||
Short: "🗑️ Remove a file from lnk management",
|
||||
Long: "Removes a symlink and restores the original file from the lnk repository.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
SilenceUsage: true,
|
||||
Use: "rm <file>",
|
||||
Short: "🗑️ Remove a file from lnk management",
|
||||
Long: "Removes a symlink and restores the original file from the lnk repository.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
filePath := args[0]
|
||||
host, _ := cmd.Flags().GetString("host")
|
||||
@@ -22,7 +22,7 @@ func newRemoveCmd() *cobra.Command {
|
||||
lnk := core.NewLnk(core.WithHost(host))
|
||||
|
||||
if err := lnk.Remove(filePath); err != nil {
|
||||
return fmt.Errorf("failed to remove file: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
basename := filepath.Base(filePath)
|
||||
|
@@ -32,8 +32,9 @@ Supports both common configurations and host-specific setups.
|
||||
lnk push "setup complete" # Sync to remote
|
||||
|
||||
🎯 Simple, fast, Git-native, and multi-host ready.`,
|
||||
SilenceUsage: true,
|
||||
Version: fmt.Sprintf("%s (built %s)", version, buildTime),
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
Version: fmt.Sprintf("%s (built %s)", version, buildTime),
|
||||
}
|
||||
|
||||
// Add subcommands
|
||||
@@ -57,7 +58,7 @@ func SetVersion(v, bt string) {
|
||||
func Execute() {
|
||||
rootCmd := NewRootCommand()
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
_, _ = fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
@@ -157,7 +157,7 @@ func (suite *CLITestSuite) TestStatusCommand() {
|
||||
// Test status without remote - should fail
|
||||
err = suite.runCommand("status")
|
||||
suite.Error(err)
|
||||
suite.Contains(err.Error(), "no remote configured")
|
||||
suite.Contains(err.Error(), "No remote repository is configured")
|
||||
}
|
||||
|
||||
func (suite *CLITestSuite) TestListCommand() {
|
||||
@@ -247,7 +247,7 @@ func (suite *CLITestSuite) TestErrorHandling() {
|
||||
name: "add nonexistent file",
|
||||
args: []string{"add", "/nonexistent/file"},
|
||||
wantErr: true,
|
||||
errContains: "File does not exist",
|
||||
errContains: "File or directory not found",
|
||||
},
|
||||
{
|
||||
name: "status without init",
|
||||
|
@@ -1,23 +1,22 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/yarlson/lnk/internal/core"
|
||||
)
|
||||
|
||||
func newStatusCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "📊 Show repository sync status",
|
||||
Long: "Display how many commits ahead/behind the local repository is relative to the remote and check for uncommitted changes.",
|
||||
SilenceUsage: true,
|
||||
Use: "status",
|
||||
Short: "📊 Show repository sync status",
|
||||
Long: "Display how many commits ahead/behind the local repository is relative to the remote and check for uncommitted changes.",
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
lnk := core.NewLnk()
|
||||
status, err := lnk.Status()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get status: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if status.Dirty {
|
||||
|
@@ -141,27 +141,17 @@ func (l *Lnk) InitWithRemote(remoteURL string) error {
|
||||
}
|
||||
|
||||
// No existing repository, initialize Git repository
|
||||
if err := l.git.Init(); err != nil {
|
||||
return fmt.Errorf("failed to initialize git repository: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return l.git.Init()
|
||||
}
|
||||
|
||||
// Clone clones a repository from the given URL
|
||||
func (l *Lnk) Clone(url string) error {
|
||||
if err := l.git.Clone(url); err != nil {
|
||||
return fmt.Errorf("failed to clone repository: %w", err)
|
||||
}
|
||||
return nil
|
||||
return l.git.Clone(url)
|
||||
}
|
||||
|
||||
// AddRemote adds a remote to the repository
|
||||
func (l *Lnk) AddRemote(name, url string) error {
|
||||
if err := l.git.AddRemote(name, url); err != nil {
|
||||
return fmt.Errorf("failed to add remote %s: %w", name, err)
|
||||
}
|
||||
return nil
|
||||
return l.git.AddRemote(name, url)
|
||||
}
|
||||
|
||||
// Add moves a file or directory to the repository and creates a symlink
|
||||
@@ -211,36 +201,22 @@ func (l *Lnk) Add(filePath string) error {
|
||||
}
|
||||
|
||||
// Move to repository (handles both files and directories)
|
||||
if info.IsDir() {
|
||||
if err := l.fs.MoveDirectory(absPath, destPath); err != nil {
|
||||
return fmt.Errorf("failed to move directory to repository: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err := l.fs.MoveFile(absPath, destPath); err != nil {
|
||||
return fmt.Errorf("failed to move file to repository: %w", err)
|
||||
}
|
||||
if err := l.fs.Move(absPath, destPath, info); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create symlink
|
||||
if err := l.fs.CreateSymlink(destPath, absPath); err != nil {
|
||||
// Try to restore the original if symlink creation fails
|
||||
if info.IsDir() {
|
||||
_ = l.fs.MoveDirectory(destPath, absPath) // Ignore error in cleanup
|
||||
} else {
|
||||
_ = l.fs.MoveFile(destPath, absPath) // Ignore error in cleanup
|
||||
}
|
||||
return fmt.Errorf("failed to create symlink: %w", err)
|
||||
_ = l.fs.Move(destPath, absPath, info)
|
||||
return err
|
||||
}
|
||||
|
||||
// Add to .lnk tracking file using relative path
|
||||
if err := l.addManagedItem(relativePath); err != nil {
|
||||
// Try to restore the original state if tracking fails
|
||||
_ = os.Remove(absPath) // Ignore error in cleanup
|
||||
if info.IsDir() {
|
||||
_ = l.fs.MoveDirectory(destPath, absPath) // Ignore error in cleanup
|
||||
} else {
|
||||
_ = l.fs.MoveFile(destPath, absPath) // Ignore error in cleanup
|
||||
}
|
||||
_ = os.Remove(absPath)
|
||||
_ = l.fs.Move(destPath, absPath, info)
|
||||
return fmt.Errorf("failed to update tracking file: %w", err)
|
||||
}
|
||||
|
||||
@@ -252,41 +228,29 @@ func (l *Lnk) Add(filePath string) error {
|
||||
}
|
||||
if err := l.git.Add(gitPath); err != nil {
|
||||
// Try to restore the original state if git add fails
|
||||
_ = os.Remove(absPath) // Ignore error in cleanup
|
||||
_ = l.removeManagedItem(relativePath) // Ignore error in cleanup
|
||||
if info.IsDir() {
|
||||
_ = l.fs.MoveDirectory(destPath, absPath) // Ignore error in cleanup
|
||||
} else {
|
||||
_ = l.fs.MoveFile(destPath, absPath) // Ignore error in cleanup
|
||||
}
|
||||
return fmt.Errorf("failed to add item to git: %w", err)
|
||||
_ = os.Remove(absPath)
|
||||
_ = l.removeManagedItem(relativePath)
|
||||
_ = l.fs.Move(destPath, absPath, info)
|
||||
return err
|
||||
}
|
||||
|
||||
// Add .lnk file to the same commit
|
||||
if err := l.git.Add(l.getLnkFileName()); err != nil {
|
||||
// Try to restore the original state if git add fails
|
||||
_ = os.Remove(absPath) // Ignore error in cleanup
|
||||
_ = l.removeManagedItem(relativePath) // Ignore error in cleanup
|
||||
if info.IsDir() {
|
||||
_ = l.fs.MoveDirectory(destPath, absPath) // Ignore error in cleanup
|
||||
} else {
|
||||
_ = l.fs.MoveFile(destPath, absPath) // Ignore error in cleanup
|
||||
}
|
||||
return fmt.Errorf("failed to add .lnk file to git: %w", err)
|
||||
_ = os.Remove(absPath)
|
||||
_ = l.removeManagedItem(relativePath)
|
||||
_ = l.fs.Move(destPath, absPath, info)
|
||||
return err
|
||||
}
|
||||
|
||||
// Commit both changes together
|
||||
basename := filepath.Base(relativePath)
|
||||
if err := l.git.Commit(fmt.Sprintf("lnk: added %s", basename)); err != nil {
|
||||
// Try to restore the original state if commit fails
|
||||
_ = os.Remove(absPath) // Ignore error in cleanup
|
||||
_ = l.removeManagedItem(relativePath) // Ignore error in cleanup
|
||||
if info.IsDir() {
|
||||
_ = l.fs.MoveDirectory(destPath, absPath) // Ignore error in cleanup
|
||||
} else {
|
||||
_ = l.fs.MoveFile(destPath, absPath) // Ignore error in cleanup
|
||||
}
|
||||
return fmt.Errorf("failed to commit changes: %w", err)
|
||||
_ = os.Remove(absPath)
|
||||
_ = l.removeManagedItem(relativePath)
|
||||
_ = l.fs.Move(destPath, absPath, info)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -361,29 +325,23 @@ func (l *Lnk) Remove(filePath string) error {
|
||||
gitPath = filepath.Join(l.host+".lnk", relativePath)
|
||||
}
|
||||
if err := l.git.Remove(gitPath); err != nil {
|
||||
return fmt.Errorf("failed to remove from git: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Add .lnk file to the same commit
|
||||
if err := l.git.Add(l.getLnkFileName()); err != nil {
|
||||
return fmt.Errorf("failed to add .lnk file to git: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Commit both changes together
|
||||
basename := filepath.Base(relativePath)
|
||||
if err := l.git.Commit(fmt.Sprintf("lnk: removed %s", basename)); err != nil {
|
||||
return fmt.Errorf("failed to commit changes: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Move back from repository (handles both files and directories)
|
||||
if info.IsDir() {
|
||||
if err := l.fs.MoveDirectory(target, absPath); err != nil {
|
||||
return fmt.Errorf("failed to restore directory: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err := l.fs.MoveFile(target, absPath); err != nil {
|
||||
return fmt.Errorf("failed to restore file: %w", err)
|
||||
}
|
||||
if err := l.fs.Move(target, absPath, info); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -411,7 +369,7 @@ func (l *Lnk) Status() (*StatusInfo, error) {
|
||||
|
||||
gitStatus, err := l.git.GetStatus()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get repository status: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &StatusInfo{
|
||||
@@ -432,28 +390,24 @@ func (l *Lnk) Push(message string) error {
|
||||
// Check if there are any changes
|
||||
hasChanges, err := l.git.HasChanges()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check for changes: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if hasChanges {
|
||||
// Stage all changes
|
||||
if err := l.git.AddAll(); err != nil {
|
||||
return fmt.Errorf("failed to stage changes: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a sync commit
|
||||
if err := l.git.Commit(message); err != nil {
|
||||
return fmt.Errorf("failed to commit changes: %w", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Push to remote (this will be a no-op in tests since we don't have real remotes)
|
||||
// In real usage, this would push to the actual remote repository
|
||||
if err := l.git.Push(); err != nil {
|
||||
return fmt.Errorf("failed to push to remote: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return l.git.Push()
|
||||
}
|
||||
|
||||
// Pull fetches changes from remote and restores symlinks as needed
|
||||
@@ -465,7 +419,7 @@ func (l *Lnk) Pull() ([]string, error) {
|
||||
|
||||
// Pull changes from remote (this will be a no-op in tests since we don't have real remotes)
|
||||
if err := l.git.Pull(); err != nil {
|
||||
return nil, fmt.Errorf("failed to pull from remote: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Find all managed files in the repository and restore symlinks
|
||||
@@ -541,7 +495,7 @@ func (l *Lnk) RestoreSymlinks() ([]string, error) {
|
||||
|
||||
// Create symlink
|
||||
if err := l.fs.CreateSymlink(repoItem, symlinkPath); err != nil {
|
||||
return nil, fmt.Errorf("failed to create symlink for %s: %w", relativePath, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
restored = append(restored, relativePath)
|
||||
|
@@ -275,7 +275,7 @@ func (suite *CoreTestSuite) TestErrorConditions() {
|
||||
|
||||
err = suite.lnk.Add("/nonexistent/file")
|
||||
suite.Error(err)
|
||||
suite.Contains(err.Error(), "File does not exist")
|
||||
suite.Contains(err.Error(), "File or directory not found")
|
||||
|
||||
// Test remove unmanaged file
|
||||
testFile := filepath.Join(suite.tempDir, ".regularfile")
|
||||
@@ -289,7 +289,7 @@ func (suite *CoreTestSuite) TestErrorConditions() {
|
||||
// Test status without remote
|
||||
_, err = suite.lnk.Status()
|
||||
suite.Error(err)
|
||||
suite.Contains(err.Error(), "no remote configured")
|
||||
suite.Contains(err.Error(), "No remote repository is configured")
|
||||
}
|
||||
|
||||
// Test git operations
|
||||
|
119
internal/fs/errors.go
Normal file
119
internal/fs/errors.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package fs
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ANSI color codes for consistent formatting
|
||||
const (
|
||||
colorReset = "\033[0m"
|
||||
colorRed = "\033[31m"
|
||||
colorBold = "\033[1m"
|
||||
)
|
||||
|
||||
// formatError creates a consistently formatted error message with ❌ prefix
|
||||
func formatError(message string, args ...interface{}) string {
|
||||
return fmt.Sprintf("❌ "+message, args...)
|
||||
}
|
||||
|
||||
// formatPath formats a file path with red color
|
||||
func formatPath(path string) string {
|
||||
return fmt.Sprintf("%s%s%s", colorRed, path, colorReset)
|
||||
}
|
||||
|
||||
// formatCommand formats a command with bold styling
|
||||
func formatCommand(command string) string {
|
||||
return fmt.Sprintf("%s%s%s", colorBold, command, colorReset)
|
||||
}
|
||||
|
||||
// FileNotExistsError represents an error when a file does not exist
|
||||
type FileNotExistsError struct {
|
||||
Path string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *FileNotExistsError) Error() string {
|
||||
return formatError("File or directory not found: %s", formatPath(e.Path))
|
||||
}
|
||||
|
||||
func (e *FileNotExistsError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// FileCheckError represents an error when failing to check a file
|
||||
type FileCheckError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *FileCheckError) Error() string {
|
||||
return formatError("Unable to access file. Please check file permissions and try again.")
|
||||
}
|
||||
|
||||
func (e *FileCheckError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// UnsupportedFileTypeError represents an error when a file type is not supported
|
||||
type UnsupportedFileTypeError struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
func (e *UnsupportedFileTypeError) Error() string {
|
||||
return formatError("Cannot manage this type of file: %s\n 💡 lnk can only manage regular files and directories", formatPath(e.Path))
|
||||
}
|
||||
|
||||
func (e *UnsupportedFileTypeError) Unwrap() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NotManagedByLnkError represents an error when a file is not managed by lnk
|
||||
type NotManagedByLnkError struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
func (e *NotManagedByLnkError) Error() string {
|
||||
return formatError("File is not managed by lnk: %s\n 💡 Use %s to manage this file first",
|
||||
formatPath(e.Path), formatCommand("lnk add"))
|
||||
}
|
||||
|
||||
func (e *NotManagedByLnkError) Unwrap() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SymlinkReadError represents an error when failing to read a symlink
|
||||
type SymlinkReadError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *SymlinkReadError) Error() string {
|
||||
return formatError("Unable to read symlink. The file may be corrupted or have invalid permissions.")
|
||||
}
|
||||
|
||||
func (e *SymlinkReadError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// DirectoryCreationError represents an error when failing to create a directory
|
||||
type DirectoryCreationError struct {
|
||||
Operation 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
|
||||
}
|
||||
|
||||
// RelativePathCalculationError represents an error when failing to calculate relative path
|
||||
type RelativePathCalculationError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *RelativePathCalculationError) Error() string {
|
||||
return formatError("Unable to create symlink due to path configuration issues. Please check file locations.")
|
||||
}
|
||||
|
||||
func (e *RelativePathCalculationError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
@@ -1,7 +1,6 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -17,18 +16,19 @@ func New() *FileSystem {
|
||||
|
||||
// ValidateFileForAdd validates that a file or directory can be added to lnk
|
||||
func (fs *FileSystem) ValidateFileForAdd(filePath string) error {
|
||||
// Check if file exists
|
||||
// Check if file exists and get its info
|
||||
info, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("❌ File does not exist: \033[31m%s\033[0m", filePath)
|
||||
return &FileNotExistsError{Path: filePath, Err: err}
|
||||
}
|
||||
return fmt.Errorf("❌ Failed to check file: %w", err)
|
||||
|
||||
return &FileCheckError{Err: err}
|
||||
}
|
||||
|
||||
// Allow both regular files and directories
|
||||
if !info.Mode().IsRegular() && !info.IsDir() {
|
||||
return fmt.Errorf("❌ Only regular files and directories are supported: \033[31m%s\033[0m", filePath)
|
||||
return &UnsupportedFileTypeError{Path: filePath}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -36,98 +36,79 @@ func (fs *FileSystem) ValidateFileForAdd(filePath string) error {
|
||||
|
||||
// ValidateSymlinkForRemove validates that a symlink can be removed from lnk
|
||||
func (fs *FileSystem) ValidateSymlinkForRemove(filePath, repoPath string) error {
|
||||
// Check if file exists
|
||||
// Check if file exists and is a symlink
|
||||
info, err := os.Lstat(filePath) // Use Lstat to not follow symlinks
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("❌ File does not exist: \033[31m%s\033[0m", filePath)
|
||||
return &FileNotExistsError{Path: filePath, Err: err}
|
||||
}
|
||||
return fmt.Errorf("❌ Failed to check file: %w", err)
|
||||
|
||||
return &FileCheckError{Err: err}
|
||||
}
|
||||
|
||||
// Check if it's a symlink
|
||||
if info.Mode()&os.ModeSymlink == 0 {
|
||||
return fmt.Errorf("❌ File is not managed by lnk: \033[31m%s\033[0m\n 💡 Use \033[1mlnk add\033[0m to manage this file first", filePath)
|
||||
return &NotManagedByLnkError{Path: filePath}
|
||||
}
|
||||
|
||||
// Check if symlink points to the repository
|
||||
// Get symlink target and resolve to absolute path
|
||||
target, err := os.Readlink(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read symlink: %w", err)
|
||||
return &SymlinkReadError{Err: err}
|
||||
}
|
||||
|
||||
// Convert relative path to absolute if needed
|
||||
if !filepath.IsAbs(target) {
|
||||
target = filepath.Join(filepath.Dir(filePath), target)
|
||||
}
|
||||
|
||||
// Clean the path to resolve any .. or . components
|
||||
// Clean paths and check if target is inside the repository
|
||||
target = filepath.Clean(target)
|
||||
repoPath = filepath.Clean(repoPath)
|
||||
|
||||
// Check if target is inside the repository
|
||||
if !strings.HasPrefix(target, repoPath+string(filepath.Separator)) && target != repoPath {
|
||||
return fmt.Errorf("❌ File is not managed by lnk: \033[31m%s\033[0m", filePath)
|
||||
return &NotManagedByLnkError{Path: filePath}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Move moves a file or directory from source to destination based on the file info
|
||||
func (fs *FileSystem) Move(src, dst string, info os.FileInfo) error {
|
||||
if info.IsDir() {
|
||||
return fs.MoveDirectory(src, dst)
|
||||
}
|
||||
return fs.MoveFile(src, dst)
|
||||
}
|
||||
|
||||
// MoveFile moves a file from source to destination
|
||||
func (fs *FileSystem) MoveFile(src, dst string) error {
|
||||
// Ensure destination directory exists
|
||||
dstDir := filepath.Dir(dst)
|
||||
if err := os.MkdirAll(dstDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create destination directory: %w", err)
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||
return &DirectoryCreationError{Operation: "destination directory", Err: err}
|
||||
}
|
||||
|
||||
// Move the file
|
||||
if err := os.Rename(src, dst); err != nil {
|
||||
return fmt.Errorf("failed to move file from %s to %s: %w", src, dst, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return os.Rename(src, dst)
|
||||
}
|
||||
|
||||
// CreateSymlink creates a relative symlink from target to linkPath
|
||||
func (fs *FileSystem) CreateSymlink(target, linkPath string) error {
|
||||
// Calculate relative path from linkPath to target
|
||||
linkDir := filepath.Dir(linkPath)
|
||||
relTarget, err := filepath.Rel(linkDir, target)
|
||||
relTarget, err := filepath.Rel(filepath.Dir(linkPath), target)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to calculate relative path: %w", err)
|
||||
return &RelativePathCalculationError{Err: err}
|
||||
}
|
||||
|
||||
// Create the symlink
|
||||
if err := os.Symlink(relTarget, linkPath); err != nil {
|
||||
return fmt.Errorf("failed to create symlink: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return os.Symlink(relTarget, linkPath)
|
||||
}
|
||||
|
||||
// MoveDirectory moves a directory from source to destination recursively
|
||||
func (fs *FileSystem) MoveDirectory(src, dst string) error {
|
||||
// Check if source is a directory
|
||||
info, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stat source: %w", err)
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
return fmt.Errorf("source is not a directory: %s", src)
|
||||
}
|
||||
|
||||
// Ensure destination parent directory exists
|
||||
dstParent := filepath.Dir(dst)
|
||||
if err := os.MkdirAll(dstParent, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create destination parent directory: %w", err)
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||
return &DirectoryCreationError{Operation: "destination parent directory", Err: err}
|
||||
}
|
||||
|
||||
// Use os.Rename which works for directories
|
||||
if err := os.Rename(src, dst); err != nil {
|
||||
return fmt.Errorf("failed to move directory from %s to %s: %w", src, dst, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
// Move the directory
|
||||
return os.Rename(src, dst)
|
||||
}
|
||||
|
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