github.com/hattya/nazuna@v0.7.1-0.20240331055452-55e14c275c1c/wc.go (about)

     1  //
     2  // nazuna :: wc.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  	"errors"
    13  	"fmt"
    14  	"os"
    15  	"path/filepath"
    16  	"strings"
    17  )
    18  
    19  var (
    20  	ErrLink    = errors.New("path is link")
    21  	ErrNotLink = errors.New("path is not link")
    22  )
    23  
    24  type WC struct {
    25  	State State
    26  
    27  	ui   UI
    28  	repo *Repository
    29  }
    30  
    31  func openWC(repo *Repository) (*WC, error) {
    32  	wc := &WC{
    33  		ui:   repo.ui,
    34  		repo: repo,
    35  	}
    36  	if err := unmarshal(repo, filepath.Join(repo.nzndir, "state.json"), &wc.State); err != nil {
    37  		return nil, err
    38  	}
    39  	if wc.State.WC == nil {
    40  		wc.State.WC = []*Entry{}
    41  	}
    42  	return wc, nil
    43  }
    44  
    45  func (wc *WC) Flush() error {
    46  	return marshal(wc.repo, filepath.Join(wc.repo.nzndir, "state.json"), &wc.State)
    47  }
    48  
    49  func (wc *WC) PathFor(path string) string {
    50  	return filepath.Join(wc.repo.root, path)
    51  }
    52  
    53  func (wc *WC) Rel(base rune, path string) (string, error) {
    54  	if strings.HasPrefix(path, "$") {
    55  		return filepath.ToSlash(path), nil
    56  	}
    57  
    58  	var abs string
    59  	var err error
    60  	switch base {
    61  	case '/':
    62  		if filepath.IsAbs(path) {
    63  			abs = path
    64  		} else {
    65  			abs = filepath.Join(wc.repo.root, path)
    66  		}
    67  	case '.':
    68  		if abs, err = filepath.Abs(path); err != nil {
    69  			return "", err
    70  		}
    71  	default:
    72  		return "", fmt.Errorf("unknown base '%c'", base)
    73  	}
    74  	rel, err := filepath.Rel(wc.repo.root, abs)
    75  	if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
    76  		return "", fmt.Errorf("'%v' is not under root", path)
    77  	}
    78  	return filepath.ToSlash(rel), nil
    79  }
    80  
    81  func (wc *WC) Exists(path string) bool {
    82  	_, err := os.Lstat(wc.PathFor(path))
    83  	return err == nil
    84  }
    85  
    86  func (wc *WC) IsLink(path string) bool {
    87  	return IsLink(wc.PathFor(path))
    88  }
    89  
    90  func (wc *WC) LinksTo(path, origin string) bool {
    91  	return LinksTo(wc.PathFor(path), origin)
    92  }
    93  
    94  func (wc *WC) Link(src, dst string) error {
    95  	dst = wc.PathFor(dst)
    96  	for p := filepath.Dir(dst); p != wc.repo.root; p = filepath.Dir(p) {
    97  		if IsLink(p) {
    98  			return &os.PathError{
    99  				Op:   "link",
   100  				Path: p,
   101  				Err:  ErrLink,
   102  			}
   103  		}
   104  	}
   105  	dir := filepath.Dir(dst)
   106  	if _, err := os.Lstat(dir); err != nil {
   107  		if err := os.MkdirAll(dir, 0o777); err != nil {
   108  			return err
   109  		}
   110  	}
   111  	return CreateLink(src, dst)
   112  }
   113  
   114  func (wc *WC) Unlink(path string) error {
   115  	path = wc.PathFor(path)
   116  	if err := Unlink(path); err != nil {
   117  		return err
   118  	}
   119  	for p := filepath.Dir(path); p != wc.repo.root; p = filepath.Dir(p) {
   120  		if IsLink(p) || !IsEmptyDir(p) {
   121  			break
   122  		}
   123  		if err := os.Remove(p); err != nil {
   124  			return err
   125  		}
   126  	}
   127  	return nil
   128  }
   129  
   130  func (wc *WC) SelectLayer(name string) error {
   131  	l, err := wc.repo.LayerOf(name)
   132  	switch {
   133  	case err != nil:
   134  		return err
   135  	case len(l.Layers) != 0:
   136  		return fmt.Errorf("layer '%v' is abstract", name)
   137  	case l.abst == nil:
   138  		return fmt.Errorf("layer '%v' is not abstract", name)
   139  	}
   140  	for k, v := range wc.State.Layers {
   141  		if k == l.abst.Name {
   142  			if v == l.Name {
   143  				return fmt.Errorf("layer '%v' is already '%v'", k, v)
   144  			}
   145  			wc.State.Layers[k] = l.Name
   146  			return nil
   147  		}
   148  	}
   149  	if wc.State.Layers == nil {
   150  		wc.State.Layers = make(map[string]string)
   151  	}
   152  	wc.State.Layers[l.abst.Name] = l.Name
   153  	return nil
   154  }
   155  
   156  func (wc *WC) LayerFor(name string) (*Layer, error) {
   157  	for k, v := range wc.State.Layers {
   158  		if name == k {
   159  			return wc.repo.LayerOf(k + "/" + v)
   160  		}
   161  	}
   162  	return nil, ResolveError{Name: name}
   163  }
   164  
   165  func (wc *WC) Layers() ([]*Layer, error) {
   166  	list := make([]*Layer, len(wc.repo.Layers))
   167  	for i, l := range wc.repo.Layers {
   168  		if len(l.Layers) != 0 {
   169  			wl, err := wc.LayerFor(l.Name)
   170  			if err != nil {
   171  				list := make([]string, len(l.Layers))
   172  				for i, ll := range l.Layers {
   173  					list[i] = ll.Name
   174  				}
   175  				return nil, ResolveError{
   176  					Name: l.Name,
   177  					List: list,
   178  				}
   179  			}
   180  			l = wl
   181  		}
   182  		list[i] = l
   183  	}
   184  	return list, nil
   185  }
   186  
   187  func (wc *WC) MergeLayers() ([]*Entry, error) {
   188  	b := wcBuilder{
   189  		ui: wc.ui,
   190  		wc: wc,
   191  	}
   192  	if err := b.build(); err != nil {
   193  		return nil, err
   194  	}
   195  
   196  	wc.State.WC = wc.State.WC[:0]
   197  	dir := ""
   198  	for _, p := range sortKeys(b.WC) {
   199  		switch {
   200  		case dir != "" && strings.HasPrefix(p, dir):
   201  		case len(b.WC[p]) == 1:
   202  			e := b.WC[p][0]
   203  			if e.Type == unlinkable {
   204  				continue
   205  			}
   206  			wc.State.WC = append(wc.State.WC, e)
   207  			if e.IsDir {
   208  				dir = p + "/"
   209  			} else {
   210  				dir = ""
   211  			}
   212  			if c, ok := b.State[p]; ok {
   213  				if c.Layer == e.Layer && c.IsDir == e.IsDir {
   214  					delete(b.State, p)
   215  				}
   216  			}
   217  		}
   218  	}
   219  
   220  	ul := make([]*Entry, len(b.State))
   221  	for i, p := range sortKeys(b.State) {
   222  		ul[i] = b.State[p]
   223  	}
   224  	return ul, nil
   225  }
   226  
   227  func (wc *WC) Errorf(err error) error {
   228  	switch v := err.(type) {
   229  	case *os.LinkError:
   230  		if r, err := wc.Rel('/', v.New); err == nil {
   231  			v.New = filepath.ToSlash(r)
   232  		}
   233  		return fmt.Errorf("%v: %v", v.New, v.Err)
   234  	case *os.PathError:
   235  		if r, err := wc.Rel('/', v.Path); err == nil {
   236  			v.Path = filepath.ToSlash(r)
   237  		}
   238  		return fmt.Errorf("%v: %v", v.Path, v.Err)
   239  	}
   240  	return err
   241  }
   242  
   243  type State struct {
   244  	Layers map[string]string `json:"layers,omitempty"`
   245  	WC     []*Entry          `json:"wc,omitempty"`
   246  }
   247  
   248  type Entry struct {
   249  	Layer  string `json:"layer"`
   250  	Path   string `json:"path"`
   251  	Origin string `json:"origin,omitempty"`
   252  	IsDir  bool   `json:"dir,omitempty"`
   253  	Type   string `json:"type,omitempty"`
   254  }
   255  
   256  func (e *Entry) Format(format string) string {
   257  	var sep, lhs, rhs string
   258  	if e.IsDir {
   259  		sep = "/"
   260  	}
   261  	if e.Path != "" {
   262  		lhs = e.Path + sep
   263  	}
   264  	switch {
   265  	case e.Origin == "":
   266  		rhs = e.Layer
   267  	case e.Type == "link":
   268  		rhs = filepath.FromSlash(e.Origin + sep)
   269  	case e.Type == "subrepo":
   270  		rhs = e.Origin
   271  	default:
   272  		rhs = e.Layer + ":" + e.Origin + sep
   273  	}
   274  	return fmt.Sprintf(format, lhs, rhs)
   275  }
   276  
   277  type ResolveError struct {
   278  	Name string
   279  	List []string
   280  }
   281  
   282  func (e ResolveError) Error() string {
   283  	s := fmt.Sprintf("cannot resolve layer '%v'", e.Name)
   284  	if len(e.List) == 0 {
   285  		return s
   286  	}
   287  	return fmt.Sprintf("%v:\n    %v", s, strings.Join(e.List, "\n    "))
   288  }
   289  
   290  const unlinkable = "_"
   291  
   292  type wcBuilder struct {
   293  	State map[string]*Entry
   294  	WC    map[string][]*Entry
   295  
   296  	ui      UI
   297  	wc      *WC
   298  	l       *Layer
   299  	layer   string
   300  	aliases map[string]string
   301  }
   302  
   303  func (b *wcBuilder) build() error {
   304  	layers, err := b.wc.Layers()
   305  	if err != nil {
   306  		return err
   307  	}
   308  	b.State = make(map[string]*Entry)
   309  	for _, e := range b.wc.State.WC {
   310  		b.State[e.Path] = e
   311  	}
   312  	b.WC = make(map[string][]*Entry)
   313  	b.aliases = make(map[string]string)
   314  	for _, l := range layers {
   315  		b.l = l
   316  		b.layer = l.Path()
   317  		if err := b.repo(); err != nil {
   318  			return err
   319  		}
   320  		if err := b.link(); err != nil {
   321  			return err
   322  		}
   323  		if err := b.subrepo(); err != nil {
   324  			return err
   325  		}
   326  		for src, dst := range b.l.Aliases {
   327  			if _, ok := b.aliases[src]; !ok {
   328  				b.aliases[src] = dst
   329  			}
   330  		}
   331  	}
   332  	return nil
   333  }
   334  
   335  func (b *wcBuilder) repo() error {
   336  	return b.wc.repo.Walk(b.layer, func(path string, fi os.FileInfo, err error) error {
   337  		if err != nil {
   338  			return err
   339  		}
   340  		origin := path[len(b.layer)+1:]
   341  		path, err = b.alias(origin)
   342  		if err != nil {
   343  			return err
   344  		}
   345  		if _, ok := b.WC[path]; !ok {
   346  			b.parents(path, true)
   347  			e := &Entry{
   348  				Layer: b.layer,
   349  				Path:  path,
   350  				IsDir: fi.IsDir(),
   351  			}
   352  			b.WC[path] = append(b.WC[path], e)
   353  			if path != origin {
   354  				e.Origin = origin
   355  				for p, o := filepath.Dir(path), filepath.Dir(origin); p != "."; p = filepath.Dir(p) {
   356  					e := b.find(filepath.ToSlash(p))
   357  					if o != "." {
   358  						e.Origin = filepath.ToSlash(o)
   359  						o = filepath.Dir(o)
   360  					} else {
   361  						e.Type = unlinkable
   362  					}
   363  				}
   364  			}
   365  		}
   366  		return nil
   367  	})
   368  }
   369  
   370  func (b *wcBuilder) link() error {
   371  	link := func(src, dst string) (bool, error) {
   372  		fi, err := os.Stat(src)
   373  		if err != nil {
   374  			return false, nil
   375  		}
   376  		dst, err = b.alias(dst)
   377  		if err != nil {
   378  			return false, fmt.Errorf("link %v", err)
   379  		}
   380  		switch list, ok := b.WC[dst]; {
   381  		case !ok:
   382  			b.parents(dst, false)
   383  			b.WC[dst] = append(b.WC[dst], &Entry{
   384  				Layer:  b.layer,
   385  				Path:   dst,
   386  				Origin: src,
   387  				IsDir:  fi.IsDir(),
   388  				Type:   "link",
   389  			})
   390  		case list[0].Layer == b.layer && list[0].Type != "link":
   391  			b.ui.Errorf("warning: link: '%v' exists in the repository\n", dst)
   392  		}
   393  		return true, nil
   394  	}
   395  	for _, dir := range sortKeys(b.l.Links) {
   396  		for _, l := range b.l.Links[dir] {
   397  			src := filepath.FromSlash(filepath.Clean(os.ExpandEnv(l.Src)))
   398  			dst := filepath.ToSlash(filepath.Join(dir, l.Dst))
   399  			if len(l.Path) > 0 {
   400  			L:
   401  				for _, v := range l.Path {
   402  					for _, p := range filepath.SplitList(os.ExpandEnv(v)) {
   403  						switch ok, err := link(filepath.FromSlash(filepath.Clean(filepath.Join(p, src))), dst); {
   404  						case err != nil:
   405  							return err
   406  						case ok:
   407  							break L
   408  						}
   409  					}
   410  				}
   411  			} else if _, err := link(src, dst); err != nil {
   412  				return err
   413  			}
   414  		}
   415  	}
   416  	return nil
   417  }
   418  
   419  func (b *wcBuilder) subrepo() error {
   420  	for _, dir := range sortKeys(b.l.Subrepos) {
   421  		for _, sub := range b.l.Subrepos[dir] {
   422  			name := sub.Name
   423  			if name == "" {
   424  				name = filepath.Base(sub.Src)
   425  			}
   426  			dst, err := b.alias(filepath.ToSlash(filepath.Join(dir, name)))
   427  			if err != nil {
   428  				return fmt.Errorf("subrepo %v", err)
   429  			}
   430  			switch list, ok := b.WC[dst]; {
   431  			case !ok:
   432  				b.parents(dst, false)
   433  				b.WC[dst] = append(b.WC[dst], &Entry{
   434  					Layer:  b.layer,
   435  					Path:   dst,
   436  					Origin: sub.Src,
   437  					Type:   "subrepo",
   438  				})
   439  			case list[0].Layer == b.layer && list[0].Type != "subrepo":
   440  				b.ui.Errorf("warning: subrepo: '%v' exists in the repository\n", dst)
   441  			}
   442  		}
   443  	}
   444  	return nil
   445  }
   446  
   447  func (b *wcBuilder) alias(path string) (string, error) {
   448  	for src := path; src != "."; src = filepath.Dir(src) {
   449  		if dst, ok := b.aliases[src]; ok {
   450  			if path == src {
   451  				path = dst
   452  			} else {
   453  				path = filepath.Join(dst, path[len(src)+1:])
   454  			}
   455  			return b.wc.Rel('/', filepath.Clean(os.ExpandEnv(path)))
   456  		}
   457  	}
   458  	return path, nil
   459  }
   460  
   461  func (b *wcBuilder) parents(path string, linkable bool) {
   462  	inWC := true
   463  	for i, r := range path {
   464  		if r != '/' {
   465  			continue
   466  		}
   467  		p := path[:i]
   468  		e := b.find(p)
   469  		if e == nil {
   470  			e = &Entry{
   471  				Layer: b.layer,
   472  				Path:  p,
   473  				IsDir: true,
   474  			}
   475  			b.WC[p] = append(b.WC[p], e)
   476  		}
   477  		if !inWC {
   478  			continue
   479  		}
   480  		if linkable {
   481  			switch _, ok := b.State[p]; {
   482  			case ok:
   483  				inWC = false
   484  				if b.wc.Exists(p) && !b.wc.IsLink(p) {
   485  					e.Type = unlinkable
   486  					delete(b.State, p)
   487  				}
   488  			case b.wc.Exists(p):
   489  				e.Type = unlinkable
   490  			}
   491  		} else {
   492  			e.Type = unlinkable
   493  		}
   494  	}
   495  }
   496  
   497  func (b *wcBuilder) find(p string) *Entry {
   498  	for _, e := range b.WC[p] {
   499  		if e.Layer == b.layer {
   500  			return e
   501  		}
   502  	}
   503  	return nil
   504  }