From cd1f23527ecade95b8c1ce0c972090fb3631bc31 Mon Sep 17 00:00:00 2001 From: Luna Date: Tue, 10 Jun 2025 22:16:16 -0300 Subject: [PATCH] fix hako sync --- commands.go | 34 +++++++++++++--- docker.go | 8 ++-- utils.go | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 11 deletions(-) diff --git a/commands.go b/commands.go index 41d17f1..3fc368b 100644 --- a/commands.go +++ b/commands.go @@ -130,6 +130,7 @@ func psCommand() error { } func syncCommand() error { + // TODO use the container for the currently working directory instead of just the first one in the filter... output, err := runCommandOutput("docker", "ps", "-a", "--filter", "name=hako-", "--format", "{{.Names}}") if err != nil { return err @@ -160,44 +161,65 @@ func syncCommand() error { } func syncFiles(srcDir, destDir string) error { + gitignore, err := NewGitIgnore() + if err != nil { + return fmt.Errorf("failed to parse .gitignore: %v", err) + } + return filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { if err != nil { - return err + return fmt.Errorf("failed to walk %q: %v", path, err) } relPath, err := filepath.Rel(srcDir, path) if err != nil { - return err + return fmt.Errorf("failed to calculate relative path for %q: %v", path, err) } - if relPath == ".git" || relPath == ".gitignore" { + // Skip .git directory and its contents + if relPath == ".git" { return filepath.SkipDir } + // Skip files that start with .git (like .gitignore, .gitmodules, etc.) if strings.HasPrefix(filepath.Base(relPath), ".git") { return nil } + // Check if the file should be ignored according to .gitignore + if gitignore.IsIgnored(relPath) { + if info.IsDir() { + return filepath.SkipDir + } + return nil + } + destPath := filepath.Join(destDir, relPath) if info.IsDir() { return os.MkdirAll(destPath, info.Mode()) } + fmt.Println("cp", path, destPath) srcFile, err := os.Open(path) if err != nil { - return err + fmt.Fprintf(os.Stderr, "Error: failed to open %s: %v\n", path, err) + return nil } defer srcFile.Close() destFile, err := os.Create(destPath) if err != nil { - return err + fmt.Fprintf(os.Stderr, "Error: failed to create %s: %v\n", destPath, err) + return nil } defer destFile.Close() _, err = io.Copy(destFile, srcFile) - return err + if err != nil { + fmt.Fprintf(os.Stderr, "Error: failed to copy %s to %s: %v\n", path, destPath, err) + } + return nil }) } diff --git a/docker.go b/docker.go index 6faea18..f671ea6 100644 --- a/docker.go +++ b/docker.go @@ -123,12 +123,12 @@ func buildLanguageImage(lang string) error { } func imageExists(imageName string) bool { - err := runCommand("docker", "image", "inspect", imageName) + err := runCommandSilent("docker", "image", "inspect", imageName) return err == nil } func containerExists(containerName string) bool { - err := runCommand("docker", "container", "inspect", containerName) + err := runCommandSilent("docker", "container", "inspect", containerName) return err == nil } @@ -145,9 +145,7 @@ func createContainer(containerName, imageName, workspaceDir string) error { args := []string{"create", "--name", containerName} - // Mount workspace - args = append(args, "-v", workspaceDir+":/workspace") - + // No mounting - workspace is isolated and copied separately args = append(args, "-it", imageName) if err := runCommand("docker", args...); err != nil { diff --git a/utils.go b/utils.go index 65508f9..8d6ac7f 100644 --- a/utils.go +++ b/utils.go @@ -1,9 +1,11 @@ package main import ( + "bufio" "fmt" "os" "os/exec" + "path/filepath" "strings" ) @@ -15,6 +17,14 @@ func runCommand(name string, args ...string) error { return cmd.Run() } +func runCommandSilent(name string, args ...string) error { + cmd := exec.Command(name, args...) + fmt.Fprintf(os.Stderr, "+ %s %s\n", name, strings.Join(args, " ")) + cmd.Stdout = nil + cmd.Stderr = nil + return cmd.Run() +} + func runCommandOutput(name string, args ...string) (string, error) { cmd := exec.Command(name, args...) fmt.Fprintf(os.Stderr, "+ %s %s\n", name, strings.Join(args, " ")) @@ -24,3 +34,107 @@ func runCommandOutput(name string, args ...string) (string, error) { } return strings.TrimSpace(string(output)), nil } + +// GitIgnore represents a .gitignore parser +type GitIgnore struct { + patterns []string +} + +// NewGitIgnore creates a new GitIgnore parser by reading .gitignore file +func NewGitIgnore() (*GitIgnore, error) { + gi := &GitIgnore{} + + file, err := os.Open(".gitignore") + if err != nil { + if os.IsNotExist(err) { + return gi, nil // No .gitignore file is fine + } + return nil, err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line != "" && !strings.HasPrefix(line, "#") { + gi.patterns = append(gi.patterns, line) + } + } + + return gi, scanner.Err() +} + +// IsIgnored checks if a file path should be ignored according to .gitignore patterns +func (gi *GitIgnore) IsIgnored(path string) bool { + for _, pattern := range gi.patterns { + if gi.matchPattern(pattern, path) { + return true + } + } + return false +} + +// matchPattern implements basic gitignore pattern matching +func (gi *GitIgnore) matchPattern(pattern, path string) bool { + // Handle negation patterns (starting with !) + if strings.HasPrefix(pattern, "!") { + return false // Negation patterns would require more complex logic + } + + // Convert gitignore pattern to filepath.Match compatible pattern + // Handle directory patterns (ending with /) + if strings.HasSuffix(pattern, "/") { + pattern = strings.TrimSuffix(pattern, "/") + // Check if any directory component matches + parts := strings.Split(path, string(filepath.Separator)) + for _, part := range parts { + if matched, _ := filepath.Match(pattern, part); matched { + return true + } + } + return false + } + + // Handle patterns with path separators + if strings.Contains(pattern, "/") { + // Exact path match + if matched, _ := filepath.Match(pattern, path); matched { + return true + } + // Check if pattern matches any suffix of the path + pathParts := strings.Split(path, string(filepath.Separator)) + patternParts := strings.Split(pattern, "/") + + if len(patternParts) <= len(pathParts) { + for i := 0; i <= len(pathParts)-len(patternParts); i++ { + match := true + for j, patternPart := range patternParts { + if matched, _ := filepath.Match(patternPart, pathParts[i+j]); !matched { + match = false + break + } + } + if match { + return true + } + } + } + return false + } + + // Simple filename pattern - check against basename and any path component + basename := filepath.Base(path) + if matched, _ := filepath.Match(pattern, basename); matched { + return true + } + + // Check if pattern matches any directory component + parts := strings.Split(path, string(filepath.Separator)) + for _, part := range parts { + if matched, _ := filepath.Match(pattern, part); matched { + return true + } + } + + return false +}