github.com/driusan/dgit@v0.0.0-20221118233547-f39f0c15edbb/git/file.go (about)

     1  package git
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  )
    11  
    12  // A file represents a file (or directory) relative to os.Getwd()
    13  type File string
    14  
    15  // Determines if the file exists on the filesystem.
    16  func (f File) Exists() bool {
    17  	if _, err := os.Lstat(string(f)); err == nil {
    18  		return true
    19  	}
    20  	return false
    21  }
    22  
    23  func (f File) String() string {
    24  	return string(f)
    25  }
    26  
    27  // Appends the value val to the end of the file f.
    28  // Not that f must already exist.
    29  func (f File) Append(val string) error {
    30  	if !f.Exists() {
    31  		return fmt.Errorf("File %s does not exist", f)
    32  	}
    33  	fi, err := os.OpenFile(f.String(), os.O_RDWR|os.O_APPEND, 0660)
    34  	if err != nil {
    35  		return err
    36  	}
    37  	defer fi.Close()
    38  	fmt.Fprintf(fi, "%s", val)
    39  	return nil
    40  }
    41  
    42  // Normalizes the file name that's relative to the current working directory
    43  // to be relative to the workdir root. Ie. convert it from a file system
    44  // path to an index path.
    45  func (f File) IndexPath(c *Client) (IndexPath, error) {
    46  	p, err := filepath.Abs(f.String())
    47  	if err != nil {
    48  		return "", err
    49  	}
    50  	// BUG(driusan): This should verify that there is a prefix and return
    51  	// an error if it's outside of the tree.
    52  	return IndexPath(strings.TrimPrefix(p, string(c.WorkDir)+"/")), nil
    53  }
    54  
    55  // Returns stat information for the given file.
    56  func (f File) Stat() (os.FileInfo, error) {
    57  	return os.Stat(string(f))
    58  }
    59  
    60  // Returns lstat information for the given file.
    61  func (f File) Lstat() (os.FileInfo, error) {
    62  	return os.Lstat(string(f))
    63  }
    64  
    65  func (f File) IsDir() bool {
    66  	stat, err := f.Lstat()
    67  	if err != nil {
    68  		// If we couldn't stat it, it's not a directory..
    69  		return false
    70  	}
    71  	return stat.IsDir()
    72  
    73  }
    74  
    75  // Returns true if the file is inside a submodule and the submodule file.
    76  // FIXME: invert this when submodules are implemented
    77  func (f File) IsInSubmodule(c *Client) (bool, File, error) {
    78  	abs, err := filepath.Abs(f.String())
    79  	if err != nil {
    80  		return false, File(""), err
    81  	}
    82  
    83  	for abs != c.WorkDir.String() {
    84  		stat, _ := os.Lstat(filepath.Join(abs, ".git"))
    85  		if stat != nil {
    86  			submodule, err := File(abs).IndexPath(c)
    87  			if err != nil {
    88  				return false, File(""), err
    89  			}
    90  			return true, File(string(submodule)), nil
    91  		}
    92  
    93  		abs = filepath.Dir(abs)
    94  	}
    95  
    96  	return false, File(""), nil
    97  }
    98  
    99  func (f File) IsInsideSymlink() (bool, error) {
   100  	abs, err := filepath.Abs(f.String())
   101  	if err != nil {
   102  		return false, err
   103  	}
   104  
   105  	dir := filepath.Dir(abs)
   106  
   107  	evalPath, _ := filepath.EvalSymlinks(dir)
   108  	if evalPath != "" && evalPath != dir {
   109  		return true, nil
   110  	}
   111  
   112  	return false, nil
   113  }
   114  
   115  func (f File) IsSymlink() bool {
   116  	stat, err := f.Lstat()
   117  	if err != nil {
   118  		// If we couldn't stat it, it's not a directory..
   119  		return false
   120  	}
   121  	// This is probably not robust. It's assuming every OS
   122  	// uses the same modes as git, but is probably good enough.
   123  	return stat.Mode()&os.ModeSymlink == os.ModeSymlink
   124  }
   125  
   126  func (f File) Create() (*os.File, error) {
   127  	dir := File(filepath.Dir(f.String()))
   128  	if !dir.Exists() {
   129  		if err := os.MkdirAll(dir.String(), 0755); err != nil {
   130  			return nil, err
   131  		}
   132  	}
   133  
   134  	return os.Create(f.String())
   135  }
   136  
   137  // Reads the entire contents of file and return as a string. Note
   138  // that this should only be used for very small files (like refspecs)
   139  //
   140  func (f File) ReadAll() (string, error) {
   141  	val, err := ioutil.ReadFile(f.String())
   142  	if err != nil {
   143  		return "", err
   144  	}
   145  	return string(val), nil
   146  }
   147  
   148  // Reads the first line of File. (This is primarily to extract commit message
   149  // lines for reflogs)
   150  func (f File) ReadFirstLine() (string, error) {
   151  	if !f.Exists() {
   152  		return "", fmt.Errorf("File %s does not exist", f)
   153  	}
   154  	fi, err := os.Open(f.String())
   155  	if err != nil {
   156  		return "", err
   157  	}
   158  	defer fi.Close()
   159  	scanner := bufio.NewScanner(fi)
   160  	scanner.Scan()
   161  	if err := scanner.Err(); err != nil {
   162  		return "", err
   163  	}
   164  	return scanner.Text(), nil
   165  }
   166  
   167  func (f File) Remove() error {
   168  	return os.Remove(f.String())
   169  }
   170  
   171  func (f File) Open() (*os.File, error) {
   172  	return os.Open(f.String())
   173  }
   174  
   175  // Returns true if f matches the filesystem glob pattern pattern.
   176  func (f File) MatchGlob(pattern string) bool {
   177  	m, err := filepath.Match(pattern, string(f))
   178  	if err != nil {
   179  		panic(err)
   180  	}
   181  	return m
   182  }