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