git-lfs/commands/command_status.go

320 lines
6.6 KiB
Go
Raw Normal View History

2014-10-21 14:21:47 +00:00
package commands
import (
"crypto/sha256"
2017-06-08 22:41:14 +00:00
"encoding/json"
"fmt"
"io"
"os"
"regexp"
"strings"
2016-11-15 17:01:18 +00:00
"github.com/git-lfs/git-lfs/git"
"github.com/git-lfs/git-lfs/lfs"
"github.com/spf13/cobra"
2014-10-21 14:21:47 +00:00
)
var (
2017-06-08 22:41:14 +00:00
porcelain = false
statusJson = false
2014-10-21 14:21:47 +00:00
)
func statusCommand(cmd *cobra.Command, args []string) {
requireInRepo()
// tolerate errors getting ref so this works before first commit
ref, _ := git.CurrentRef()
2014-10-21 14:21:47 +00:00
scanIndexAt := "HEAD"
if ref == nil {
scanIndexAt = git.RefBeforeFirstCommit
}
scanner, err := lfs.NewPointerScanner()
if err != nil {
scanner.Close()
ExitWithError(err)
}
if porcelain {
porcelainStagedPointers(scanIndexAt)
return
2017-06-08 22:41:14 +00:00
} else if statusJson {
jsonStagedPointers(scanner, scanIndexAt)
2017-06-08 22:41:14 +00:00
return
}
statusScanRefRange(ref)
2014-10-21 14:21:47 +00:00
staged, unstaged, err := scanIndex(scanIndexAt)
if err != nil {
ExitWithError(err)
}
Print("\nGit LFS objects to be committed:\n")
for _, entry := range staged {
switch entry.Status {
case lfs.StatusRename, lfs.StatusCopy:
Print("\t%s -> %s (%s)", entry.SrcName, entry.DstName, formatBlobInfo(scanner, entry))
default:
Print("\t%s (%s)", entry.SrcName, formatBlobInfo(scanner, entry))
2014-10-27 19:47:07 +00:00
}
}
2014-10-27 16:52:28 +00:00
2015-03-19 19:30:55 +00:00
Print("\nGit LFS objects not staged for commit:\n")
for _, entry := range unstaged {
Print("\t%s (%s)", entry.SrcName, formatBlobInfo(scanner, entry))
2014-10-27 16:52:28 +00:00
}
2014-10-27 19:47:07 +00:00
Print("")
if err = scanner.Close(); err != nil {
ExitWithError(err)
}
2014-10-27 16:52:28 +00:00
}
2014-10-27 16:42:38 +00:00
var z40 = regexp.MustCompile(`\^?0{40}`)
func formatBlobInfo(s *lfs.PointerScanner, entry *lfs.DiffIndexEntry) string {
fromSha, fromSrc, err := blobInfoFrom(s, entry)
if err != nil {
ExitWithError(err)
}
from := fmt.Sprintf("%s: %s", fromSrc, fromSha)
if entry.Status == lfs.StatusAddition {
return from
}
toSha, toSrc, err := blobInfoTo(s, entry)
if err != nil {
ExitWithError(err)
}
to := fmt.Sprintf("%s: %s", toSrc, toSha)
return fmt.Sprintf("%s -> %s", from, to)
}
func blobInfoFrom(s *lfs.PointerScanner, entry *lfs.DiffIndexEntry) (sha, from string, err error) {
var blobSha string = entry.SrcSha
if z40.MatchString(blobSha) {
blobSha = entry.DstSha
}
return blobInfo(s, blobSha, entry.SrcName)
}
func blobInfoTo(s *lfs.PointerScanner, entry *lfs.DiffIndexEntry) (sha, from string, err error) {
var name string = entry.DstName
if len(name) == 0 {
name = entry.SrcName
}
return blobInfo(s, entry.DstSha, name)
}
func blobInfo(s *lfs.PointerScanner, blobSha, name string) (sha, from string, err error) {
if !z40.MatchString(blobSha) {
s.Scan(blobSha)
if err := s.Err(); err != nil {
if git.IsMissingObject(err) {
return "<missing>", "?", nil
}
return "", "", err
}
var from string
if s.Pointer() != nil {
from = "LFS"
} else {
from = "Git"
}
return s.ContentsSha()[:7], from, nil
}
f, err := os.Open(name)
if err != nil {
return "", "", err
}
defer f.Close()
shasum := sha256.New()
if _, err = io.Copy(shasum, f); err != nil {
return "", "", err
}
return fmt.Sprintf("%x", shasum.Sum(nil))[:7], "File", nil
}
func scanIndex(ref string) (staged, unstaged []*lfs.DiffIndexEntry, err error) {
uncached, err := lfs.NewDiffIndexScanner(ref, false)
if err != nil {
return nil, nil, err
}
cached, err := lfs.NewDiffIndexScanner(ref, true)
if err != nil {
return nil, nil, err
}
seenNames := make(map[string]struct{}, 0)
staged, err = drainScanner(seenNames, cached)
if err != nil {
return nil, nil, err
}
unstaged, err = drainScanner(seenNames, uncached)
if err != nil {
return nil, nil, err
}
return
}
func drainScanner(cache map[string]struct{}, scanner *lfs.DiffIndexScanner) ([]*lfs.DiffIndexEntry, error) {
var to []*lfs.DiffIndexEntry
for scanner.Scan() {
entry := scanner.Entry()
key := keyFromEntry(entry)
if _, seen := cache[key]; !seen {
to = append(to, entry)
cache[key] = struct{}{}
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
return to, nil
}
func keyFromEntry(e *lfs.DiffIndexEntry) string {
var name string = e.DstName
if len(name) == 0 {
name = e.SrcName
}
return strings.Join([]string{e.SrcSha, e.DstSha, name}, ":")
}
func statusScanRefRange(ref *git.Ref) {
if ref == nil {
return
}
Print("On branch %s", ref.Name)
remoteRef, err := git.CurrentRemoteRef()
if err != nil {
return
}
gitscanner := lfs.NewGitScanner(func(p *lfs.WrappedPointer, err error) {
if err != nil {
Panic(err, "Could not scan for Git LFS objects")
return
}
2017-03-16 20:07:26 +00:00
Print("\t%s (%s)", p.Name)
})
defer gitscanner.Close()
Print("Git LFS objects to be pushed to %s:\n", remoteRef.Name)
if err := gitscanner.ScanRefRange(ref.Sha, "^"+remoteRef.Sha, nil); err != nil {
Panic(err, "Could not scan for Git LFS objects")
}
}
2017-06-08 22:41:14 +00:00
type JSONStatusEntry struct {
Status string `json:"status"`
From string `json:"from,omitempty"`
}
type JSONStatus struct {
Files map[string]JSONStatusEntry `json:"files"`
}
func jsonStagedPointers(scanner *lfs.PointerScanner, ref string) {
2017-06-08 22:41:14 +00:00
staged, unstaged, err := scanIndex(ref)
if err != nil {
ExitWithError(err)
}
status := JSONStatus{Files: make(map[string]JSONStatusEntry)}
for _, entry := range append(unstaged, staged...) {
_, fromSrc, err := blobInfoFrom(scanner, entry)
if err != nil {
ExitWithError(err)
}
if fromSrc != "LFS" {
continue
}
2017-06-08 22:41:14 +00:00
switch entry.Status {
case lfs.StatusRename, lfs.StatusCopy:
status.Files[entry.DstName] = JSONStatusEntry{
Status: string(entry.Status), From: entry.SrcName,
}
default:
status.Files[entry.SrcName] = JSONStatusEntry{
Status: string(entry.Status),
}
}
}
ret, err := json.Marshal(status)
if err != nil {
ExitWithError(err)
}
Print(string(ret))
}
func porcelainStagedPointers(ref string) {
staged, unstaged, err := scanIndex(ref)
if err != nil {
ExitWithError(err)
}
seenNames := make(map[string]struct{})
for _, entry := range append(unstaged, staged...) {
name := entry.DstName
if len(name) == 0 {
name = entry.SrcName
}
if _, seen := seenNames[name]; !seen {
Print(porcelainStatusLine(entry))
seenNames[name] = struct{}{}
}
}
}
func porcelainStatusLine(entry *lfs.DiffIndexEntry) string {
switch entry.Status {
case lfs.StatusRename, lfs.StatusCopy:
return fmt.Sprintf("%s %s -> %s", entry.Status, entry.SrcName, entry.DstName)
case lfs.StatusModification:
return fmt.Sprintf(" %s %s", entry.Status, entry.SrcName)
}
return fmt.Sprintf("%s %s", entry.Status, entry.SrcName)
}
2014-10-21 14:21:47 +00:00
func init() {
RegisterCommand("status", statusCommand, func(cmd *cobra.Command) {
cmd.Flags().BoolVarP(&porcelain, "porcelain", "p", false, "Give the output in an easy-to-parse format for scripts.")
2017-06-08 22:41:14 +00:00
cmd.Flags().BoolVarP(&statusJson, "json", "j", false, "Give the output in a stable json format for scripts.")
})
2014-10-21 14:21:47 +00:00
}