log commands to stderr
This commit is contained in:
parent
051121c79e
commit
d0159c2ba4
10 changed files with 112 additions and 89 deletions
|
@ -32,4 +32,4 @@ Examples:
|
|||
|
||||
return manager.Down(containerName)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,4 +31,4 @@ Examples:
|
|||
|
||||
return manager.Init(language)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,4 +34,4 @@ var psCmd = &cobra.Command{
|
|||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,4 +30,4 @@ func init() {
|
|||
rootCmd.AddCommand(syncCmd)
|
||||
rootCmd.AddCommand(psCmd)
|
||||
rootCmd.AddCommand(downCmd)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,4 +25,4 @@ project directory.`,
|
|||
|
||||
return manager.Sync()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,4 +31,4 @@ Examples:
|
|||
language := args[0]
|
||||
return manager.Up(language)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ func (c *Config) BaseDockerfilePath() string {
|
|||
// EnsureBaseDockerfile creates the base Dockerfile if it doesn't exist.
|
||||
func (c *Config) EnsureBaseDockerfile() error {
|
||||
path := c.BaseDockerfilePath()
|
||||
|
||||
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return nil // File already exists
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ CMD ["/bin/bash"]
|
|||
// LanguageDockerfile returns the path to a language-specific Dockerfile.
|
||||
func (c *Config) LanguageDockerfile(language string) (string, error) {
|
||||
path := filepath.Join(c.ConfigDir, fmt.Sprintf("Dockerfile.%s", language))
|
||||
|
||||
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return path, nil // File already exists
|
||||
}
|
||||
|
@ -167,4 +167,4 @@ CMD ["/bin/bash"]
|
|||
default:
|
||||
return "", fmt.Errorf("unsupported language: %s", language)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package docker
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
@ -11,6 +12,12 @@ import (
|
|||
"l4.pm/hako/pkg/config"
|
||||
)
|
||||
|
||||
// loggedCommand creates a command and logs it to stderr
|
||||
func loggedCommand(name string, args ...string) *exec.Cmd {
|
||||
log.Printf("exec: %s %s", name, strings.Join(args, " "))
|
||||
return exec.Command(name, args...)
|
||||
}
|
||||
|
||||
// Client wraps Docker operations.
|
||||
type Client struct {
|
||||
ctx context.Context
|
||||
|
@ -19,7 +26,7 @@ type Client struct {
|
|||
// NewClient creates a new Docker client.
|
||||
func NewClient() (*Client, error) {
|
||||
// Check if Docker is available
|
||||
cmd := exec.Command("docker", "version", "--format", "{{.Server.Version}}")
|
||||
cmd := loggedCommand("docker", "version", "--format", "{{.Server.Version}}")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("docker is not available: %w", err)
|
||||
}
|
||||
|
@ -32,21 +39,21 @@ func NewClient() (*Client, error) {
|
|||
// BuildBaseImage builds the base hako image.
|
||||
func (c *Client) BuildBaseImage(cfg *config.Config) error {
|
||||
dockerfile := cfg.BaseDockerfilePath()
|
||||
|
||||
|
||||
// Ensure base dockerfile exists
|
||||
if err := cfg.EnsureBaseDockerfile(); err != nil {
|
||||
return fmt.Errorf("failed to create base dockerfile: %w", err)
|
||||
}
|
||||
|
||||
// Build the base image
|
||||
cmd := exec.Command("docker", "build",
|
||||
cmd := loggedCommand("docker", "build",
|
||||
"-f", dockerfile,
|
||||
"-t", "hako-userland",
|
||||
filepath.Dir(dockerfile))
|
||||
|
||||
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to build base image: %w", err)
|
||||
}
|
||||
|
@ -70,16 +77,16 @@ func (c *Client) BuildLanguageImage(language string, cfg *config.Config) error {
|
|||
}
|
||||
|
||||
imageName := fmt.Sprintf("hako-userland-%s", language)
|
||||
|
||||
|
||||
// Build the language image
|
||||
cmd := exec.Command("docker", "build",
|
||||
cmd := loggedCommand("docker", "build",
|
||||
"-f", dockerfile,
|
||||
"-t", imageName,
|
||||
filepath.Dir(dockerfile))
|
||||
|
||||
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to build %s image: %w", language, err)
|
||||
}
|
||||
|
@ -93,97 +100,97 @@ func (c *Client) GenerateContainerName(language, path string) (string, error) {
|
|||
// Sanitize the path for container naming
|
||||
sanitized := sanitizeContainerName(path)
|
||||
name := fmt.Sprintf("hako-%s-%s", language, sanitized)
|
||||
|
||||
|
||||
// Docker container names have a 63 character limit
|
||||
if len(name) > 63 {
|
||||
return "", fmt.Errorf("container name too long: %s (max 63 chars)", name)
|
||||
}
|
||||
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// StartShell creates/starts a container and drops into a shell.
|
||||
func (c *Client) StartShell(containerName, language string, syncer interface{ ToContainer(string) error }) error {
|
||||
imageName := fmt.Sprintf("hako-userland-%s", language)
|
||||
|
||||
|
||||
// Check if container exists
|
||||
if c.containerExists(containerName) {
|
||||
fmt.Printf("Starting existing container: %s\n", containerName)
|
||||
|
||||
|
||||
// Start the container if it's stopped
|
||||
cmd := exec.Command("docker", "start", containerName)
|
||||
cmd := loggedCommand("docker", "start", containerName)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to start container: %w", err)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Creating new container: %s\n", containerName)
|
||||
|
||||
|
||||
// Get current user info for running container as host user
|
||||
uid := os.Getenv("UID")
|
||||
if uid == "" {
|
||||
cmd := exec.Command("id", "-u")
|
||||
cmd := loggedCommand("id", "-u")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get user ID: %w", err)
|
||||
}
|
||||
uid = strings.TrimSpace(string(output))
|
||||
}
|
||||
|
||||
|
||||
gid := os.Getenv("GID")
|
||||
if gid == "" {
|
||||
cmd := exec.Command("id", "-g")
|
||||
cmd := loggedCommand("id", "-g")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get group ID: %w", err)
|
||||
}
|
||||
gid = strings.TrimSpace(string(output))
|
||||
}
|
||||
|
||||
|
||||
// Get home directory for mounting Claude auth
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get home directory: %w", err)
|
||||
}
|
||||
|
||||
|
||||
args := []string{"run", "-it", "-d",
|
||||
"--name", containerName,
|
||||
"--user", fmt.Sprintf("%s:%s", uid, gid),
|
||||
"-w", "/workspace"}
|
||||
|
||||
|
||||
// Mount Claude config file if it exists
|
||||
claudeConfigPath := filepath.Join(homeDir, ".claude.json")
|
||||
if _, err := os.Stat(claudeConfigPath); err == nil {
|
||||
args = append(args, "-v", fmt.Sprintf("%s:/home/user/.claude.json:ro", claudeConfigPath))
|
||||
args = append(args, "-v", fmt.Sprintf("%s:/workspace/.claude.json:ro", claudeConfigPath))
|
||||
}
|
||||
|
||||
|
||||
// Mount Claude directory if it exists
|
||||
claudeDirPath := filepath.Join(homeDir, ".claude")
|
||||
if _, err := os.Stat(claudeDirPath); err == nil {
|
||||
args = append(args, "-v", fmt.Sprintf("%s:/home/user/.claude", claudeDirPath))
|
||||
args = append(args, "-v", fmt.Sprintf("%s:/workspace/.claude", claudeDirPath))
|
||||
}
|
||||
|
||||
|
||||
args = append(args, imageName, "/bin/bash")
|
||||
|
||||
|
||||
// Create and start the container
|
||||
cmd := exec.Command("docker", args...)
|
||||
|
||||
cmd := loggedCommand("docker", args...)
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to create container: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Copy workspace files to container
|
||||
if err := syncer.ToContainer(containerName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// Execute interactive shell
|
||||
fmt.Printf("Dropping into shell in container: %s\n", containerName)
|
||||
cmd := exec.Command("docker", "exec", "-it", containerName, "/bin/bash")
|
||||
cmd := loggedCommand("docker", "exec", "-it", containerName, "/bin/bash")
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
|
@ -192,68 +199,68 @@ func (c *Client) GetCurrentContainer(path string) (string, error) {
|
|||
// This would need to search through containers to find one matching the path
|
||||
// For now, we'll implement a simple version
|
||||
sanitized := sanitizeContainerName(path)
|
||||
|
||||
|
||||
// Try to find a container that matches this path pattern
|
||||
cmd := exec.Command("docker", "ps", "-a", "--format", "{{.Names}}")
|
||||
cmd := loggedCommand("docker", "ps", "-a", "--format", "{{.Names}}")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to list containers: %w", err)
|
||||
}
|
||||
|
||||
|
||||
names := strings.Split(strings.TrimSpace(string(output)), "\n")
|
||||
for _, name := range names {
|
||||
if strings.Contains(name, sanitized) {
|
||||
return name, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return "", fmt.Errorf("no container found for current directory")
|
||||
}
|
||||
|
||||
// ListContainers returns all running hako containers.
|
||||
func (c *Client) ListContainers() ([]string, error) {
|
||||
cmd := exec.Command("docker", "ps", "--filter", "name=hako-", "--format", "{{.Names}}")
|
||||
cmd := loggedCommand("docker", "ps", "--filter", "name=hako-", "--format", "{{.Names}}")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list containers: %w", err)
|
||||
}
|
||||
|
||||
|
||||
names := strings.Split(strings.TrimSpace(string(output)), "\n")
|
||||
if len(names) == 1 && names[0] == "" {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// StopContainer stops and removes a container.
|
||||
func (c *Client) StopContainer(containerName string) error {
|
||||
// Stop the container
|
||||
cmd := exec.Command("docker", "stop", containerName)
|
||||
cmd := loggedCommand("docker", "stop", containerName)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to stop container: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Remove the container
|
||||
cmd = exec.Command("docker", "rm", containerName)
|
||||
cmd = loggedCommand("docker", "rm", containerName)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to remove container: %w", err)
|
||||
}
|
||||
|
||||
|
||||
fmt.Printf("✅ Stopped and removed container: %s\n", containerName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// imageExists checks if a Docker image exists locally.
|
||||
func (c *Client) imageExists(imageName string) bool {
|
||||
cmd := exec.Command("docker", "images", "-q", imageName)
|
||||
cmd := loggedCommand("docker", "images", "-q", imageName)
|
||||
output, err := cmd.Output()
|
||||
return err == nil && len(strings.TrimSpace(string(output))) > 0
|
||||
}
|
||||
|
||||
// containerExists checks if a container exists.
|
||||
func (c *Client) containerExists(containerName string) bool {
|
||||
cmd := exec.Command("docker", "ps", "-a", "-q", "-f", fmt.Sprintf("name=%s", containerName))
|
||||
cmd := loggedCommand("docker", "ps", "-a", "-q", "-f", fmt.Sprintf("name=%s", containerName))
|
||||
output, err := cmd.Output()
|
||||
return err == nil && len(strings.TrimSpace(string(output))) > 0
|
||||
}
|
||||
|
@ -262,7 +269,7 @@ func (c *Client) containerExists(containerName string) bool {
|
|||
func sanitizeContainerName(path string) string {
|
||||
// Get just the directory name, not the full path
|
||||
base := filepath.Base(path)
|
||||
|
||||
|
||||
// Replace unsafe characters with hyphens
|
||||
result := strings.Map(func(r rune) rune {
|
||||
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') {
|
||||
|
@ -270,12 +277,12 @@ func sanitizeContainerName(path string) string {
|
|||
}
|
||||
return '-'
|
||||
}, base)
|
||||
|
||||
|
||||
// Remove leading/trailing hyphens and collapse multiple hyphens
|
||||
result = strings.Trim(result, "-")
|
||||
for strings.Contains(result, "--") {
|
||||
result = strings.ReplaceAll(result, "--", "-")
|
||||
}
|
||||
|
||||
|
||||
return strings.ToLower(result)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,27 @@ package git
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// loggedCommand creates a command and logs it to stderr
|
||||
func loggedCommand(name string, args ...string) *exec.Cmd {
|
||||
log.Printf("exec: %s %s", name, strings.Join(args, " "))
|
||||
return exec.Command(name, args...)
|
||||
}
|
||||
|
||||
// loggedCommandWithDir creates a command with a working directory and logs it
|
||||
func loggedCommandWithDir(dir, name string, args ...string) *exec.Cmd {
|
||||
log.Printf("exec (in %s): %s %s", dir, name, strings.Join(args, " "))
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd.Dir = dir
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Repo represents a git repository.
|
||||
type Repo struct {
|
||||
workDir string
|
||||
|
@ -22,8 +37,7 @@ func NewRepo() (*Repo, error) {
|
|||
}
|
||||
|
||||
// Check if we're inside a git repository
|
||||
cmd := exec.Command("git", "rev-parse", "--is-inside-work-tree")
|
||||
cmd.Dir = workDir
|
||||
cmd := loggedCommandWithDir(workDir, "git", "rev-parse", "--is-inside-work-tree")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("not inside a git repository")
|
||||
|
@ -34,8 +48,7 @@ func NewRepo() (*Repo, error) {
|
|||
}
|
||||
|
||||
// Get the repository root directory
|
||||
cmd = exec.Command("git", "rev-parse", "--show-toplevel")
|
||||
cmd.Dir = workDir
|
||||
cmd = loggedCommandWithDir(workDir, "git", "rev-parse", "--show-toplevel")
|
||||
output, err = cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get git root directory: %w", err)
|
||||
|
@ -61,8 +74,7 @@ func (r *Repo) Root() string {
|
|||
|
||||
// RelativePath returns the current directory relative to the git root.
|
||||
func (r *Repo) RelativePath() (string, error) {
|
||||
cmd := exec.Command("git", "rev-parse", "--show-prefix")
|
||||
cmd.Dir = r.workDir
|
||||
cmd := loggedCommandWithDir(r.workDir, "git", "rev-parse", "--show-prefix")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get relative path: %w", err)
|
||||
|
@ -73,8 +85,7 @@ func (r *Repo) RelativePath() (string, error) {
|
|||
|
||||
// ListFiles returns all tracked files in the repository.
|
||||
func (r *Repo) ListFiles() ([]string, error) {
|
||||
cmd := exec.Command("git", "ls-files")
|
||||
cmd.Dir = r.rootDir
|
||||
cmd := loggedCommandWithDir(r.rootDir, "git", "ls-files")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list git files: %w", err)
|
||||
|
@ -90,8 +101,7 @@ func (r *Repo) ListFiles() ([]string, error) {
|
|||
|
||||
// ListUntrackedFiles returns all untracked files that are not ignored.
|
||||
func (r *Repo) ListUntrackedFiles() ([]string, error) {
|
||||
cmd := exec.Command("git", "ls-files", "--others", "--exclude-standard")
|
||||
cmd.Dir = r.rootDir
|
||||
cmd := loggedCommandWithDir(r.rootDir, "git", "ls-files", "--others", "--exclude-standard")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list untracked files: %w", err)
|
||||
|
@ -127,20 +137,19 @@ func (r *Repo) ListAllFiles() ([]string, error) {
|
|||
|
||||
// IsIgnored checks if a file path is ignored by git.
|
||||
func (r *Repo) IsIgnored(path string) (bool, error) {
|
||||
cmd := exec.Command("git", "check-ignore", path)
|
||||
cmd.Dir = r.rootDir
|
||||
cmd := loggedCommandWithDir(r.rootDir, "git", "check-ignore", path)
|
||||
err := cmd.Run()
|
||||
|
||||
|
||||
if err == nil {
|
||||
return true, nil // File is ignored
|
||||
}
|
||||
|
||||
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
if exitError.ExitCode() == 1 {
|
||||
return false, nil // File is not ignored
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false, fmt.Errorf("failed to check if file is ignored: %w", err)
|
||||
}
|
||||
|
||||
|
@ -158,12 +167,12 @@ func sanitizeName(name string) string {
|
|||
}
|
||||
return '-'
|
||||
}, name)
|
||||
|
||||
|
||||
// Remove leading/trailing hyphens and collapse multiple hyphens
|
||||
result = strings.Trim(result, "-")
|
||||
for strings.Contains(result, "--") {
|
||||
result = strings.ReplaceAll(result, "--", "-")
|
||||
}
|
||||
|
||||
|
||||
return strings.ToLower(result)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package sync
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
@ -10,6 +11,12 @@ import (
|
|||
"l4.pm/hako/pkg/git"
|
||||
)
|
||||
|
||||
// loggedCommand creates a command and logs it to stderr
|
||||
func loggedCommand(name string, args ...string) *exec.Cmd {
|
||||
log.Printf("exec: %s %s", name, strings.Join(args, " "))
|
||||
return exec.Command(name, args...)
|
||||
}
|
||||
|
||||
// Syncer handles file synchronization between host and containers.
|
||||
type Syncer struct {
|
||||
repo *git.Repo
|
||||
|
@ -35,7 +42,7 @@ func (s *Syncer) ToContainer(containerName string) error {
|
|||
}
|
||||
|
||||
// Create workspace directory in container
|
||||
cmd := exec.Command("docker", "exec", containerName, "mkdir", "-p", "/workspace")
|
||||
cmd := loggedCommand("docker", "exec", containerName, "mkdir", "-p", "/workspace")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to create workspace directory in container: %w", err)
|
||||
}
|
||||
|
@ -44,17 +51,17 @@ func (s *Syncer) ToContainer(containerName string) error {
|
|||
for _, file := range workspaceFiles {
|
||||
srcPath := filepath.Join(s.repo.Root(), file)
|
||||
dstPath := fmt.Sprintf("%s:/workspace/%s", containerName, file)
|
||||
|
||||
|
||||
// Ensure directory exists in container
|
||||
dir := filepath.Dir(file)
|
||||
if dir != "." {
|
||||
cmd := exec.Command("docker", "exec", containerName, "mkdir", "-p", fmt.Sprintf("/workspace/%s", dir))
|
||||
cmd := loggedCommand("docker", "exec", containerName, "mkdir", "-p", fmt.Sprintf("/workspace/%s", dir))
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to create directory %s in container: %w", dir, err)
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command("docker", "cp", srcPath, dstPath)
|
||||
|
||||
cmd := loggedCommand("docker", "cp", srcPath, dstPath)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to copy %s to container: %w", file, err)
|
||||
}
|
||||
|
@ -67,7 +74,7 @@ func (s *Syncer) ToContainer(containerName string) error {
|
|||
// FromContainer copies files from container back to host.
|
||||
func (s *Syncer) FromContainer(containerName string) error {
|
||||
// Get list of files in container workspace
|
||||
cmd := exec.Command("docker", "exec", containerName, "find", "/workspace", "-type", "f", "-not", "-path", "/workspace/.git/*")
|
||||
cmd := loggedCommand("docker", "exec", containerName, "find", "/workspace", "-type", "f", "-not", "-path", "/workspace/.git/*")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list files in container: %w", err)
|
||||
|
@ -99,18 +106,18 @@ func (s *Syncer) FromContainer(containerName string) error {
|
|||
// Copy file from container to host
|
||||
srcPath := fmt.Sprintf("%s:%s", containerName, containerFile)
|
||||
dstPath := filepath.Join(s.repo.Root(), relPath)
|
||||
|
||||
|
||||
// Ensure directory exists on host
|
||||
dir := filepath.Dir(dstPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create directory %s: %w", dir, err)
|
||||
}
|
||||
|
||||
cmd := exec.Command("docker", "cp", srcPath, dstPath)
|
||||
|
||||
cmd := loggedCommand("docker", "cp", srcPath, dstPath)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to copy %s from container: %w", relPath, err)
|
||||
}
|
||||
|
||||
|
||||
copied++
|
||||
}
|
||||
|
||||
|
@ -133,15 +140,15 @@ func (s *Syncer) getWorkspaceFiles() ([]string, error) {
|
|||
if strings.HasPrefix(file, ".git/") {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
// Check if file exists (might have been deleted)
|
||||
fullPath := filepath.Join(s.repo.Root(), file)
|
||||
if _, err := os.Stat(fullPath); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
workspaceFiles = append(workspaceFiles, file)
|
||||
}
|
||||
|
||||
return workspaceFiles, nil
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue