github.com/hattya/nazuna@v0.7.1-0.20240331055452-55e14c275c1c/repository.go (about) 1 // 2 // nazuna :: repository.go 3 // 4 // Copyright (c) 2013-2021 Akinori Hattori <hattya@gmail.com> 5 // 6 // SPDX-License-Identifier: MIT 7 // 8 9 package nazuna 10 11 import ( 12 "bufio" 13 "fmt" 14 "os" 15 "path/filepath" 16 "sort" 17 "strings" 18 ) 19 20 var discover = true 21 22 func Discover(b bool) bool { 23 old := discover 24 discover = b 25 return old 26 } 27 28 type Repository struct { 29 Layers []*Layer 30 31 ui UI 32 vcs VCS 33 root string 34 nzndir string 35 rdir string 36 subroot string 37 } 38 39 func Open(ui UI, path string) (*Repository, error) { 40 root, err := filepath.Abs(path) 41 if err != nil { 42 return nil, err 43 } 44 for !IsDir(filepath.Join(root, ".nzn")) { 45 p := root 46 root = filepath.Dir(root) 47 if !discover || root == p { 48 return nil, fmt.Errorf("no repository found in '%v' (.nzn not found)!", path) 49 } 50 } 51 52 nzndir := filepath.Join(root, ".nzn") 53 rdir := filepath.Join(nzndir, "r") 54 vcs, err := VCSFor(ui, rdir) 55 if err != nil { 56 return nil, err 57 } 58 repo := &Repository{ 59 ui: ui, 60 vcs: vcs, 61 root: root, 62 nzndir: nzndir, 63 rdir: rdir, 64 subroot: filepath.Join(nzndir, "sub"), 65 } 66 67 if err := unmarshal(repo, filepath.Join(repo.rdir, "nazuna.json"), &repo.Layers); err != nil { 68 return nil, err 69 } 70 if repo.Layers == nil { 71 repo.Layers = []*Layer{} 72 } 73 return repo, nil 74 } 75 76 func (repo *Repository) Flush() error { 77 return marshal(repo, filepath.Join(repo.rdir, "nazuna.json"), repo.Layers) 78 } 79 80 func (repo *Repository) LayerOf(name string) (*Layer, error) { 81 n, err := repo.splitLayer(name) 82 if err != nil { 83 return nil, err 84 } 85 for _, l := range repo.Layers { 86 if n[0] == l.Name { 87 switch { 88 case len(n) == 1: 89 l.repo = repo 90 return l, nil 91 case len(l.Layers) == 0: 92 return nil, fmt.Errorf("layer '%v' is not abstract", n[0]) 93 } 94 for _, ll := range l.Layers { 95 if n[1] == ll.Name { 96 ll.repo = repo 97 ll.abst = l 98 return ll, nil 99 } 100 } 101 } 102 } 103 return nil, fmt.Errorf("layer '%v' does not exist!", name) 104 } 105 106 func (repo *Repository) NewLayer(name string) (*Layer, error) { 107 switch _, err := repo.LayerOf(name); { 108 case err != nil && !strings.Contains(err.Error(), "not exist"): 109 return nil, err 110 case err == nil || !IsEmptyDir(filepath.Join(repo.rdir, name)): 111 return nil, fmt.Errorf("layer '%v' already exists!", name) 112 } 113 114 var l *Layer 115 switch n, _ := repo.splitLayer(name); len(n) { 116 case 1: 117 l = repo.newLayer(n[0]) 118 default: 119 var err error 120 l, err = repo.LayerOf(n[0]) 121 if err != nil { 122 l = repo.newLayer(n[0]) 123 } 124 ll := &Layer{ 125 Name: n[1], 126 repo: repo, 127 abst: l, 128 } 129 l.Layers = append(l.Layers, ll) 130 sort.Slice(l.Layers, func(i, j int) bool { return l.Layers[i].Name < l.Layers[j].Name }) 131 l = ll 132 } 133 os.MkdirAll(repo.PathFor(l, "/"), 0o777) 134 return l, nil 135 } 136 137 func (repo *Repository) newLayer(name string) *Layer { 138 repo.Layers = append(repo.Layers, nil) 139 copy(repo.Layers[1:], repo.Layers) 140 repo.Layers[0] = &Layer{ 141 Name: name, 142 repo: repo, 143 } 144 return repo.Layers[0] 145 } 146 147 func (repo *Repository) splitLayer(name string) ([]string, error) { 148 n := strings.Split(name, "/") 149 for i := range n { 150 n[i] = strings.TrimSpace(n[i]) 151 } 152 if n[0] == "" || (len(n) > 1 && n[1] == "") || len(n) > 2 { 153 return nil, fmt.Errorf("invalid layer '%v'", name) 154 } 155 return n, nil 156 } 157 158 func (repo *Repository) PathFor(layer *Layer, path string) string { 159 if layer == nil { 160 return filepath.Join(repo.rdir, path) 161 } 162 return filepath.Join(repo.rdir, layer.Path(), path) 163 } 164 165 func (repo *Repository) SubrepoFor(path string) string { 166 return filepath.Join(repo.subroot, path) 167 } 168 169 func (repo *Repository) WC() (*WC, error) { 170 return openWC(repo) 171 } 172 173 func (repo *Repository) Find(layer *Layer, path string) (typ string) { 174 err := repo.Walk(repo.PathFor(layer, path), func(p string, fi os.FileInfo, err error) error { 175 if !strings.HasSuffix(p, "/"+filepath.ToSlash(path)) { 176 typ = "dir" 177 } else { 178 typ = "file" 179 } 180 return filepath.SkipDir 181 }) 182 if err == filepath.SkipDir { 183 return 184 } 185 186 for _, dst := range layer.Aliases { 187 if dst == path { 188 return "alias" 189 } 190 } 191 192 dir, name := SplitPath(path) 193 for _, l := range layer.Links[dir] { 194 if l.Dst == name { 195 return "link" 196 } 197 } 198 199 for _, s := range layer.Subrepos[dir] { 200 if s.Name == name || filepath.Base(s.Src) == name { 201 return "subrepo" 202 } 203 } 204 return 205 } 206 207 func (repo *Repository) Walk(path string, walk filepath.WalkFunc) error { 208 cmd := repo.vcs.List(path) 209 out, err := cmd.StdoutPipe() 210 if err != nil { 211 return err 212 } 213 if err := cmd.Start(); err != nil { 214 return err 215 } 216 defer cmd.Wait() 217 defer cmd.Process.Kill() 218 s := bufio.NewScanner(out) 219 for s.Scan() { 220 p := s.Text() 221 fi, err := os.Stat(filepath.Join(repo.rdir, p)) 222 if err = walk(p, fi, err); err != nil { 223 return err 224 } 225 } 226 return s.Err() 227 } 228 229 func (repo *Repository) Add(paths ...string) error { 230 return repo.vcs.Add(paths...) 231 } 232 233 func (repo *Repository) Command(args ...string) error { 234 return repo.vcs.Exec(args...) 235 }