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  }