A vibe coded tangled fork which supports pijul.
at master 197 lines 4.2 kB view raw
1package pijul 2 3import ( 4 "context" 5 "fmt" 6 "io/fs" 7 "os" 8 "path" 9 "path/filepath" 10 "strings" 11 12 securejoin "github.com/cyphar/filepath-securejoin" 13 "tangled.org/core/types" 14) 15 16// TreeEntry represents a file or directory in the repository tree 17type TreeEntry struct { 18 Name string `json:"name"` 19 Mode fs.FileMode `json:"mode"` 20 Size int64 `json:"size"` 21 IsDir bool `json:"is_dir"` 22} 23 24// FileTree returns the file tree at the given path 25// For Pijul, we read directly from the working directory 26func (p *PijulRepo) FileTree(ctx context.Context, treePath string) ([]types.NiceTree, error) { 27 fullPath, err := securejoin.SecureJoin(p.path, treePath) 28 if err != nil { 29 return nil, fmt.Errorf("invalid path: %w", err) 30 } 31 32 info, err := os.Stat(fullPath) 33 if err != nil { 34 if os.IsNotExist(err) { 35 return nil, ErrPathNotFound 36 } 37 return nil, err 38 } 39 40 // If it's a file, return empty (no tree for files) 41 if !info.IsDir() { 42 return []types.NiceTree{}, nil 43 } 44 45 entries, err := os.ReadDir(fullPath) 46 if err != nil { 47 return nil, err 48 } 49 50 trees := make([]types.NiceTree, 0, len(entries)) 51 52 for _, entry := range entries { 53 // Skip .pijul directory 54 if entry.Name() == ".pijul" { 55 continue 56 } 57 58 info, err := entry.Info() 59 if err != nil { 60 continue 61 } 62 63 trees = append(trees, types.NiceTree{ 64 Name: entry.Name(), 65 Mode: fileModeToString(info.Mode()), 66 Size: info.Size(), 67 // LastCommit would require additional work to implement 68 // For now, we leave it nil 69 }) 70 } 71 72 return trees, nil 73} 74 75// fileModeToString converts fs.FileMode to octal string representation 76func fileModeToString(mode fs.FileMode) string { 77 // Convert to git-style mode representation 78 if mode.IsDir() { 79 return "040000" 80 } 81 if mode&fs.ModeSymlink != 0 { 82 return "120000" 83 } 84 if mode&0111 != 0 { 85 return "100755" 86 } 87 return "100644" 88} 89 90// Walk callback type 91type WalkCallback func(path string, info fs.FileInfo, isDir bool) error 92 93// Walk traverses the file tree 94func (p *PijulRepo) Walk(ctx context.Context, root string, cb WalkCallback) error { 95 startPath, err := securejoin.SecureJoin(p.path, root) 96 if err != nil { 97 return fmt.Errorf("invalid path: %w", err) 98 } 99 100 return filepath.WalkDir(startPath, func(walkPath string, d fs.DirEntry, err error) error { 101 if err != nil { 102 return err 103 } 104 105 // Check context 106 select { 107 case <-ctx.Done(): 108 return ctx.Err() 109 default: 110 } 111 112 // Skip .pijul directory 113 if d.IsDir() && filepath.Base(walkPath) == ".pijul" { 114 return filepath.SkipDir 115 } 116 117 // Get relative path 118 relPath, err := filepath.Rel(p.path, walkPath) 119 if err != nil { 120 return err 121 } 122 123 if relPath == "." { 124 return nil 125 } 126 127 info, err := d.Info() 128 if err != nil { 129 return err 130 } 131 132 return cb(relPath, info, d.IsDir()) 133 }) 134} 135 136// ListFiles returns all tracked files in the repository 137func (p *PijulRepo) ListFiles() ([]string, error) { 138 output, err := p.runPijulCmd("ls") 139 if err != nil { 140 return nil, err 141 } 142 143 lines := strings.Split(strings.TrimSpace(string(output)), "\n") 144 if len(lines) == 1 && lines[0] == "" { 145 return []string{}, nil 146 } 147 148 return lines, nil 149} 150 151// IsTracked checks if a file is tracked by Pijul 152func (p *PijulRepo) IsTracked(filePath string) (bool, error) { 153 files, err := p.ListFiles() 154 if err != nil { 155 return false, err 156 } 157 158 for _, f := range files { 159 if f == filePath { 160 return true, nil 161 } 162 } 163 164 return false, nil 165} 166 167// FileExists checks if a file exists in the working directory 168func (p *PijulRepo) FileExists(filePath string) bool { 169 fullPath, err := securejoin.SecureJoin(p.path, filePath) 170 if err != nil { 171 return false 172 } 173 _, err = os.Stat(fullPath) 174 return err == nil 175} 176 177// IsDir checks if a path is a directory 178func (p *PijulRepo) IsDir(treePath string) (bool, error) { 179 fullPath, err := securejoin.SecureJoin(p.path, treePath) 180 if err != nil { 181 return false, fmt.Errorf("invalid path: %w", err) 182 } 183 info, err := os.Stat(fullPath) 184 if err != nil { 185 return false, err 186 } 187 return info.IsDir(), nil 188} 189 190// MakeNiceTree creates a NiceTree from file info 191func MakeNiceTree(name string, info fs.FileInfo) types.NiceTree { 192 return types.NiceTree{ 193 Name: path.Base(name), 194 Mode: fileModeToString(info.Mode()), 195 Size: info.Size(), 196 } 197}