| 92 | } |
| 93 | |
| 94 | func copyTree(src, dst string) error { |
| 95 | info, err := os.Stat(src) |
| 96 | if err != nil { |
| 97 | return err |
| 98 | } |
| 99 | if !info.IsDir() { |
| 100 | return fmt.Errorf("%s is not a directory", src) |
| 101 | } |
| 102 | return filepath.WalkDir(src, func(path string, entry fs.DirEntry, walkErr error) error { |
| 103 | if walkErr != nil { |
| 104 | return walkErr |
| 105 | } |
| 106 | rel, err := filepath.Rel(src, path) |
| 107 | if err != nil { |
| 108 | return err |
| 109 | } |
| 110 | if rel == "." { |
| 111 | return nil |
| 112 | } |
| 113 | if entry.IsDir() && entry.Name() == ".git" { |
| 114 | return filepath.SkipDir |
| 115 | } |
| 116 | target := filepath.Join(dst, rel) |
| 117 | info, err := entry.Info() |
| 118 | if err != nil { |
| 119 | return err |
| 120 | } |
| 121 | if entry.IsDir() { |
| 122 | return os.MkdirAll(target, info.Mode()) |
| 123 | } |
| 124 | if entry.Type()&os.ModeSymlink != 0 { |
| 125 | link, err := os.Readlink(path) |
| 126 | if err != nil { |
| 127 | return err |
| 128 | } |
| 129 | return os.Symlink(link, target) |
| 130 | } |
| 131 | data, err := os.ReadFile(path) |
| 132 | if err != nil { |
| 133 | return err |
| 134 | } |
| 135 | if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil { |
| 136 | return err |
| 137 | } |
| 138 | return os.WriteFile(target, data, info.Mode()) |
| 139 | }) |
| 140 | } |
| 141 | |
| 142 | func ensureGitBaseline(ctx context.Context, dir string) error { |
| 143 | if _, err := os.Stat(filepath.Join(dir, ".git")); err == nil { |