github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/commands/command_status.go (about)

     1  package commands
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"regexp"
    10  	"strings"
    11  
    12  	"github.com/git-lfs/git-lfs/git"
    13  	"github.com/git-lfs/git-lfs/lfs"
    14  	"github.com/spf13/cobra"
    15  )
    16  
    17  var (
    18  	porcelain  = false
    19  	statusJson = false
    20  )
    21  
    22  func statusCommand(cmd *cobra.Command, args []string) {
    23  	requireInRepo()
    24  
    25  	// tolerate errors getting ref so this works before first commit
    26  	ref, _ := git.CurrentRef()
    27  
    28  	scanIndexAt := "HEAD"
    29  	if ref == nil {
    30  		scanIndexAt = git.RefBeforeFirstCommit
    31  	}
    32  
    33  	scanner, err := lfs.NewPointerScanner()
    34  	if err != nil {
    35  		scanner.Close()
    36  
    37  		ExitWithError(err)
    38  	}
    39  
    40  	if porcelain {
    41  		porcelainStagedPointers(scanIndexAt)
    42  		return
    43  	} else if statusJson {
    44  		jsonStagedPointers(scanner, scanIndexAt)
    45  		return
    46  	}
    47  
    48  	statusScanRefRange(ref)
    49  
    50  	staged, unstaged, err := scanIndex(scanIndexAt)
    51  	if err != nil {
    52  		ExitWithError(err)
    53  	}
    54  
    55  	Print("\nGit LFS objects to be committed:\n")
    56  	for _, entry := range staged {
    57  		switch entry.Status {
    58  		case lfs.StatusRename, lfs.StatusCopy:
    59  			Print("\t%s -> %s (%s)", entry.SrcName, entry.DstName, formatBlobInfo(scanner, entry))
    60  		default:
    61  			Print("\t%s (%s)", entry.SrcName, formatBlobInfo(scanner, entry))
    62  		}
    63  	}
    64  
    65  	Print("\nGit LFS objects not staged for commit:\n")
    66  	for _, entry := range unstaged {
    67  		Print("\t%s (%s)", entry.SrcName, formatBlobInfo(scanner, entry))
    68  	}
    69  
    70  	Print("")
    71  
    72  	if err = scanner.Close(); err != nil {
    73  		ExitWithError(err)
    74  	}
    75  }
    76  
    77  var z40 = regexp.MustCompile(`\^?0{40}`)
    78  
    79  func formatBlobInfo(s *lfs.PointerScanner, entry *lfs.DiffIndexEntry) string {
    80  	fromSha, fromSrc, err := blobInfoFrom(s, entry)
    81  	if err != nil {
    82  		ExitWithError(err)
    83  	}
    84  
    85  	from := fmt.Sprintf("%s: %s", fromSrc, fromSha)
    86  	if entry.Status == lfs.StatusAddition {
    87  		return from
    88  	}
    89  
    90  	toSha, toSrc, err := blobInfoTo(s, entry)
    91  	if err != nil {
    92  		ExitWithError(err)
    93  	}
    94  	to := fmt.Sprintf("%s: %s", toSrc, toSha)
    95  
    96  	return fmt.Sprintf("%s -> %s", from, to)
    97  }
    98  
    99  func blobInfoFrom(s *lfs.PointerScanner, entry *lfs.DiffIndexEntry) (sha, from string, err error) {
   100  	var blobSha string = entry.SrcSha
   101  	if z40.MatchString(blobSha) {
   102  		blobSha = entry.DstSha
   103  	}
   104  
   105  	return blobInfo(s, blobSha, entry.SrcName)
   106  }
   107  
   108  func blobInfoTo(s *lfs.PointerScanner, entry *lfs.DiffIndexEntry) (sha, from string, err error) {
   109  	var name string = entry.DstName
   110  	if len(name) == 0 {
   111  		name = entry.SrcName
   112  	}
   113  
   114  	return blobInfo(s, entry.DstSha, name)
   115  }
   116  
   117  func blobInfo(s *lfs.PointerScanner, blobSha, name string) (sha, from string, err error) {
   118  	if !z40.MatchString(blobSha) {
   119  		s.Scan(blobSha)
   120  		if err := s.Err(); err != nil {
   121  			if git.IsMissingObject(err) {
   122  				return "<missing>", "?", nil
   123  			}
   124  			return "", "", err
   125  		}
   126  
   127  		var from string
   128  		if s.Pointer() != nil {
   129  			from = "LFS"
   130  		} else {
   131  			from = "Git"
   132  		}
   133  
   134  		return s.ContentsSha()[:7], from, nil
   135  	}
   136  
   137  	f, err := os.Open(name)
   138  	if err != nil {
   139  		return "", "", err
   140  	}
   141  	defer f.Close()
   142  
   143  	shasum := sha256.New()
   144  	if _, err = io.Copy(shasum, f); err != nil {
   145  		return "", "", err
   146  	}
   147  
   148  	return fmt.Sprintf("%x", shasum.Sum(nil))[:7], "File", nil
   149  }
   150  
   151  func scanIndex(ref string) (staged, unstaged []*lfs.DiffIndexEntry, err error) {
   152  	uncached, err := lfs.NewDiffIndexScanner(ref, false)
   153  	if err != nil {
   154  		return nil, nil, err
   155  	}
   156  
   157  	cached, err := lfs.NewDiffIndexScanner(ref, true)
   158  	if err != nil {
   159  		return nil, nil, err
   160  	}
   161  
   162  	seenNames := make(map[string]struct{}, 0)
   163  
   164  	staged, err = drainScanner(seenNames, cached)
   165  	if err != nil {
   166  		return nil, nil, err
   167  	}
   168  
   169  	unstaged, err = drainScanner(seenNames, uncached)
   170  	if err != nil {
   171  		return nil, nil, err
   172  	}
   173  
   174  	return
   175  }
   176  
   177  func drainScanner(cache map[string]struct{}, scanner *lfs.DiffIndexScanner) ([]*lfs.DiffIndexEntry, error) {
   178  	var to []*lfs.DiffIndexEntry
   179  
   180  	for scanner.Scan() {
   181  		entry := scanner.Entry()
   182  
   183  		key := keyFromEntry(entry)
   184  		if _, seen := cache[key]; !seen {
   185  			to = append(to, entry)
   186  
   187  			cache[key] = struct{}{}
   188  		}
   189  	}
   190  
   191  	if err := scanner.Err(); err != nil {
   192  		return nil, err
   193  	}
   194  	return to, nil
   195  }
   196  
   197  func keyFromEntry(e *lfs.DiffIndexEntry) string {
   198  	var name string = e.DstName
   199  	if len(name) == 0 {
   200  		name = e.SrcName
   201  	}
   202  
   203  	return strings.Join([]string{e.SrcSha, e.DstSha, name}, ":")
   204  }
   205  
   206  func statusScanRefRange(ref *git.Ref) {
   207  	if ref == nil {
   208  		return
   209  	}
   210  
   211  	Print("On branch %s", ref.Name)
   212  
   213  	remoteRef, err := cfg.GitConfig().CurrentRemoteRef()
   214  	if err != nil {
   215  		return
   216  	}
   217  
   218  	gitscanner := lfs.NewGitScanner(func(p *lfs.WrappedPointer, err error) {
   219  		if err != nil {
   220  			Panic(err, "Could not scan for Git LFS objects")
   221  			return
   222  		}
   223  
   224  		Print("\t%s (%s)", p.Name, p.Oid)
   225  	})
   226  	defer gitscanner.Close()
   227  
   228  	Print("Git LFS objects to be pushed to %s:\n", remoteRef.Name)
   229  	if err := gitscanner.ScanRefRange(ref.Sha, "^"+remoteRef.Sha, nil); err != nil {
   230  		Panic(err, "Could not scan for Git LFS objects")
   231  	}
   232  
   233  }
   234  
   235  type JSONStatusEntry struct {
   236  	Status string `json:"status"`
   237  	From   string `json:"from,omitempty"`
   238  }
   239  
   240  type JSONStatus struct {
   241  	Files map[string]JSONStatusEntry `json:"files"`
   242  }
   243  
   244  func jsonStagedPointers(scanner *lfs.PointerScanner, ref string) {
   245  	staged, unstaged, err := scanIndex(ref)
   246  	if err != nil {
   247  		ExitWithError(err)
   248  	}
   249  
   250  	status := JSONStatus{Files: make(map[string]JSONStatusEntry)}
   251  
   252  	for _, entry := range append(unstaged, staged...) {
   253  		_, fromSrc, err := blobInfoFrom(scanner, entry)
   254  		if err != nil {
   255  			ExitWithError(err)
   256  		}
   257  
   258  		if fromSrc != "LFS" {
   259  			continue
   260  		}
   261  
   262  		switch entry.Status {
   263  		case lfs.StatusRename, lfs.StatusCopy:
   264  			status.Files[entry.DstName] = JSONStatusEntry{
   265  				Status: string(entry.Status), From: entry.SrcName,
   266  			}
   267  		default:
   268  			status.Files[entry.SrcName] = JSONStatusEntry{
   269  				Status: string(entry.Status),
   270  			}
   271  		}
   272  	}
   273  
   274  	ret, err := json.Marshal(status)
   275  	if err != nil {
   276  		ExitWithError(err)
   277  	}
   278  	Print(string(ret))
   279  }
   280  
   281  func porcelainStagedPointers(ref string) {
   282  	staged, unstaged, err := scanIndex(ref)
   283  	if err != nil {
   284  		ExitWithError(err)
   285  	}
   286  
   287  	seenNames := make(map[string]struct{})
   288  
   289  	for _, entry := range append(unstaged, staged...) {
   290  		name := entry.DstName
   291  		if len(name) == 0 {
   292  			name = entry.SrcName
   293  		}
   294  
   295  		if _, seen := seenNames[name]; !seen {
   296  			Print(porcelainStatusLine(entry))
   297  
   298  			seenNames[name] = struct{}{}
   299  		}
   300  	}
   301  }
   302  
   303  func porcelainStatusLine(entry *lfs.DiffIndexEntry) string {
   304  	switch entry.Status {
   305  	case lfs.StatusRename, lfs.StatusCopy:
   306  		return fmt.Sprintf("%s  %s -> %s", entry.Status, entry.SrcName, entry.DstName)
   307  	case lfs.StatusModification:
   308  		return fmt.Sprintf(" %s %s", entry.Status, entry.SrcName)
   309  	}
   310  
   311  	return fmt.Sprintf("%s  %s", entry.Status, entry.SrcName)
   312  }
   313  
   314  func init() {
   315  	RegisterCommand("status", statusCommand, func(cmd *cobra.Command) {
   316  		cmd.Flags().BoolVarP(&porcelain, "porcelain", "p", false, "Give the output in an easy-to-parse format for scripts.")
   317  		cmd.Flags().BoolVarP(&statusJson, "json", "j", false, "Give the output in a stable json format for scripts.")
   318  	})
   319  }