github.com/git-lfs/git-lfs@v2.5.2+incompatible/lfs/util.go (about)

     1  package lfs
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strings"
    11  
    12  	"github.com/git-lfs/git-lfs/config"
    13  	"github.com/git-lfs/git-lfs/tools"
    14  )
    15  
    16  type Platform int
    17  
    18  const (
    19  	PlatformWindows      = Platform(iota)
    20  	PlatformLinux        = Platform(iota)
    21  	PlatformOSX          = Platform(iota)
    22  	PlatformOther        = Platform(iota) // most likely a *nix variant e.g. freebsd
    23  	PlatformUndetermined = Platform(iota)
    24  )
    25  
    26  var currentPlatform = PlatformUndetermined
    27  
    28  func join(parts ...string) string {
    29  	return strings.Join(parts, "/")
    30  }
    31  
    32  func (f *GitFilter) CopyCallbackFile(event, filename string, index, totalFiles int) (tools.CopyCallback, *os.File, error) {
    33  	logPath, _ := f.cfg.Os.Get("GIT_LFS_PROGRESS")
    34  	if len(logPath) == 0 || len(filename) == 0 || len(event) == 0 {
    35  		return nil, nil, nil
    36  	}
    37  
    38  	if !filepath.IsAbs(logPath) {
    39  		return nil, nil, fmt.Errorf("GIT_LFS_PROGRESS must be an absolute path")
    40  	}
    41  
    42  	cbDir := filepath.Dir(logPath)
    43  	if err := os.MkdirAll(cbDir, 0755); err != nil {
    44  		return nil, nil, wrapProgressError(err, event, logPath)
    45  	}
    46  
    47  	file, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
    48  	if err != nil {
    49  		return nil, file, wrapProgressError(err, event, logPath)
    50  	}
    51  
    52  	var prevWritten int64
    53  
    54  	cb := tools.CopyCallback(func(total int64, written int64, current int) error {
    55  		if written != prevWritten {
    56  			_, err := file.Write([]byte(fmt.Sprintf("%s %d/%d %d/%d %s\n", event, index, totalFiles, written, total, filename)))
    57  			file.Sync()
    58  			prevWritten = written
    59  			return wrapProgressError(err, event, logPath)
    60  		}
    61  
    62  		return nil
    63  	})
    64  
    65  	return cb, file, nil
    66  }
    67  
    68  func wrapProgressError(err error, event, filename string) error {
    69  	if err != nil {
    70  		return fmt.Errorf("Error writing Git LFS %s progress to %s: %s", event, filename, err.Error())
    71  	}
    72  
    73  	return nil
    74  }
    75  
    76  var localDirSet = tools.NewStringSetFromSlice([]string{".", "./", ".\\"})
    77  
    78  func GetPlatform() Platform {
    79  	if currentPlatform == PlatformUndetermined {
    80  		switch runtime.GOOS {
    81  		case "windows":
    82  			currentPlatform = PlatformWindows
    83  		case "linux":
    84  			currentPlatform = PlatformLinux
    85  		case "darwin":
    86  			currentPlatform = PlatformOSX
    87  		default:
    88  			currentPlatform = PlatformOther
    89  		}
    90  	}
    91  	return currentPlatform
    92  }
    93  
    94  type PathConverter interface {
    95  	Convert(string) string
    96  }
    97  
    98  // Convert filenames expressed relative to the root of the repo relative to the
    99  // current working dir. Useful when needing to calling git with results from a rooted command,
   100  // but the user is in a subdir of their repo
   101  // Pass in a channel which you will fill with relative files & receive a channel which will get results
   102  func NewRepoToCurrentPathConverter(cfg *config.Configuration) (PathConverter, error) {
   103  	r, c, p, err := pathConverterArgs(cfg)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	return &repoToCurrentPathConverter{
   109  		repoDir:     r,
   110  		currDir:     c,
   111  		passthrough: p,
   112  	}, nil
   113  }
   114  
   115  type repoToCurrentPathConverter struct {
   116  	repoDir     string
   117  	currDir     string
   118  	passthrough bool
   119  }
   120  
   121  func (p *repoToCurrentPathConverter) Convert(filename string) string {
   122  	if p.passthrough {
   123  		return filename
   124  	}
   125  
   126  	abs := join(p.repoDir, filename)
   127  	rel, err := filepath.Rel(p.currDir, abs)
   128  	if err != nil {
   129  		// Use absolute file instead
   130  		return abs
   131  	}
   132  	return filepath.ToSlash(rel)
   133  }
   134  
   135  // Convert filenames expressed relative to the current directory to be
   136  // relative to the repo root. Useful when calling git with arguments that requires them
   137  // to be rooted but the user is in a subdir of their repo & expects to use relative args
   138  // Pass in a channel which you will fill with relative files & receive a channel which will get results
   139  func NewCurrentToRepoPathConverter(cfg *config.Configuration) (PathConverter, error) {
   140  	r, c, p, err := pathConverterArgs(cfg)
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  
   145  	return &currentToRepoPathConverter{
   146  		repoDir:     r,
   147  		currDir:     c,
   148  		passthrough: p,
   149  	}, nil
   150  }
   151  
   152  type currentToRepoPathConverter struct {
   153  	repoDir     string
   154  	currDir     string
   155  	passthrough bool
   156  }
   157  
   158  func (p *currentToRepoPathConverter) Convert(filename string) string {
   159  	if p.passthrough {
   160  		return filename
   161  	}
   162  
   163  	var abs string
   164  	if filepath.IsAbs(filename) {
   165  		abs = tools.ResolveSymlinks(filename)
   166  	} else {
   167  		abs = join(p.currDir, filename)
   168  	}
   169  	reltoroot, err := filepath.Rel(p.repoDir, abs)
   170  	if err != nil {
   171  		// Can't do this, use absolute as best fallback
   172  		return abs
   173  	}
   174  	return filepath.ToSlash(reltoroot)
   175  }
   176  
   177  func pathConverterArgs(cfg *config.Configuration) (string, string, bool, error) {
   178  	currDir, err := os.Getwd()
   179  	if err != nil {
   180  		return "", "", false, fmt.Errorf("Unable to get working dir: %v", err)
   181  	}
   182  	currDir = tools.ResolveSymlinks(currDir)
   183  	return cfg.LocalWorkingDir(), currDir, cfg.LocalWorkingDir() == currDir, nil
   184  }
   185  
   186  // Are we running on Windows? Need to handle some extra path shenanigans
   187  func IsWindows() bool {
   188  	return GetPlatform() == PlatformWindows
   189  }
   190  
   191  func CopyFileContents(cfg *config.Configuration, src string, dst string) error {
   192  	tmp, err := ioutil.TempFile(cfg.TempDir(), filepath.Base(dst))
   193  	if err != nil {
   194  		return err
   195  	}
   196  	defer func() {
   197  		tmp.Close()
   198  		os.Remove(tmp.Name())
   199  	}()
   200  	in, err := os.Open(src)
   201  	if err != nil {
   202  		return err
   203  	}
   204  	defer in.Close()
   205  	_, err = io.Copy(tmp, in)
   206  	if err != nil {
   207  		return err
   208  	}
   209  	err = tmp.Close()
   210  	if err != nil {
   211  		return err
   212  	}
   213  	return os.Rename(tmp.Name(), dst)
   214  }
   215  
   216  func LinkOrCopy(cfg *config.Configuration, src string, dst string) error {
   217  	if src == dst {
   218  		return nil
   219  	}
   220  	err := os.Link(src, dst)
   221  	if err == nil {
   222  		return err
   223  	}
   224  	return CopyFileContents(cfg, src, dst)
   225  }