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 }