// ScanRefs takes a ref and returns a slice of WrappedPointer objects // for all Git LFS pointers it finds for that ref. func ScanRefs(refLeft, refRight string) ([]*WrappedPointer, error) { nameMap := make(map[string]string, 0) start := time.Now() defer func() { tracerx.PerformanceSince("scan", start) }() revs, err := revListShas(refLeft, refRight, refLeft == "", nameMap) if err != nil { return nil, err } smallShas, err := catFileBatchCheck(revs) if err != nil { return nil, err } pointerc, err := catFileBatch(smallShas) if err != nil { return nil, err } pointers := make([]*WrappedPointer, 0) for p := range pointerc { if name, ok := nameMap[p.Sha1]; ok { p.Name = name } pointers = append(pointers, p) } return pointers, nil }
// ScanTree takes a ref and returns a slice of WrappedPointer objects in the tree at that ref // Differs from ScanRefs in that multiple files in the tree with the same content are all reported func ScanTree(ref string) ([]*WrappedPointer, error) { start := time.Now() defer func() { tracerx.PerformanceSince("scan", start) }() // We don't use the nameMap approach here since that's imprecise when >1 file // can be using the same content treeShas, err := lsTreeBlobs(ref) if err != nil { return nil, err } pointerc, err := catFileBatchTree(treeShas) if err != nil { return nil, err } pointers := make([]*WrappedPointer, 0) for p := range pointerc { pointers = append(pointers, p) } return pointers, nil }
// ScanIndex returns a slice of WrappedPointer objects for all // Git LFS pointers it finds in the index. // Reports unique oids once only, not multiple times if >1 file uses the same content func ScanIndex() ([]*WrappedPointer, error) { nameMap := make(map[string]*indexFile, 0) start := time.Now() defer func() { tracerx.PerformanceSince("scan-staging", start) }() revs, err := revListIndex(false, nameMap) if err != nil { return nil, err } cachedRevs, err := revListIndex(true, nameMap) if err != nil { return nil, err } allRevs := make(chan string) go func() { seenRevs := make(map[string]bool, 0) for rev := range revs { seenRevs[rev] = true allRevs <- rev } for rev := range cachedRevs { if _, ok := seenRevs[rev]; !ok { allRevs <- rev } } close(allRevs) }() smallShas, err := catFileBatchCheck(allRevs) if err != nil { return nil, err } pointerc, err := catFileBatch(smallShas) if err != nil { return nil, err } pointers := make([]*WrappedPointer, 0) for p := range pointerc { if e, ok := nameMap[p.Sha1]; ok { p.Name = e.Name p.Status = e.Status p.SrcName = e.SrcName } pointers = append(pointers, p) } return pointers, nil }
// Fetch and report completion of each OID to a channel (optional, pass nil to skip) func fetchAndReportToChan(pointers []*lfs.WrappedPointer, include, exclude []string, out chan<- *lfs.WrappedPointer) { totalSize := int64(0) for _, p := range pointers { totalSize += p.Size } q := lfs.NewDownloadQueue(len(pointers), totalSize, false) for _, p := range pointers { // Only add to download queue if local file is not the right size already // This avoids previous case of over-reporting a requirement for files we already have // which would only be skipped by PointerSmudgeObject later passFilter := lfs.FilenamePassesIncludeExcludeFilter(p.Name, include, exclude) if !lfs.ObjectExistsOfSize(p.Oid, p.Size) && passFilter { q.Add(lfs.NewDownloadable(p)) } else { // If we already have it, or it won't be fetched // report it to chan immediately to support pull/checkout if out != nil { out <- p } } } if out != nil { dlwatch := q.Watch() go func() { // fetch only reports single OID, but OID *might* be referenced by multiple // WrappedPointers if same content is at multiple paths, so map oid->slice oidToPointers := make(map[string][]*lfs.WrappedPointer, len(pointers)) for _, pointer := range pointers { plist := oidToPointers[pointer.Oid] oidToPointers[pointer.Oid] = append(plist, pointer) } for oid := range dlwatch { plist, ok := oidToPointers[oid] if !ok { continue } for _, p := range plist { out <- p } } close(out) }() } processQueue := time.Now() q.Wait() tracerx.PerformanceSince("process queue", processQueue) }
// ScanRefsToChan takes a ref and returns a channel of WrappedPointer objects // for all Git LFS pointers it finds for that ref. // Reports unique oids once only, not multiple times if >1 file uses the same content func ScanRefsToChan(refLeft, refRight string, opt *ScanRefsOptions) (*PointerChannelWrapper, error) { if opt == nil { opt = NewScanRefsOptions() } if refLeft == "" { opt.ScanMode = ScanAllMode } start := time.Now() defer func() { tracerx.PerformanceSince("scan", start) }() revs, err := revListShas(refLeft, refRight, opt) if err != nil { return nil, err } smallShas, err := catFileBatchCheck(revs) if err != nil { return nil, err } pointers, err := catFileBatch(smallShas) if err != nil { return nil, err } retchan := make(chan *WrappedPointer, chanBufSize) errchan := make(chan error, 1) go func() { for p := range pointers.Results { if name, ok := opt.GetName(p.Sha1); ok { p.Name = name } retchan <- p } err := pointers.Wait() if err != nil { errchan <- err } close(retchan) close(errchan) }() return NewPointerChannelWrapper(retchan, errchan), nil }
// ScanPreviousVersions scans changes reachable from ref (commit) back to since. // Returns pointers for *previous* versions that overlap that time. Does not // return pointers which were still in use at ref (use ScanRef for that) func ScanPreviousVersions(ref string, since time.Time) ([]*WrappedPointer, error) { start := time.Now() defer func() { tracerx.PerformanceSince("scan", start) }() pointerchan, err := logPreviousSHAs(ref, since) if err != nil { return nil, err } pointers := make([]*WrappedPointer, 0, 10) for p := range pointerchan { pointers = append(pointers, p) } return pointers, nil }
// ScanUnpushed scans history for all LFS pointers which have been added but not pushed to any remote func ScanUnpushed() ([]*WrappedPointer, error) { start := time.Now() defer func() { tracerx.PerformanceSince("scan", start) }() pointerchan, err := logUnpushedSHAs() if err != nil { return nil, err } pointers := make([]*WrappedPointer, 0, 10) for p := range pointerchan { pointers = append(pointers, p) } return pointers, nil }
// ScanUnpushed scans history for all LFS pointers which have been added but not // pushed to the named remote. remoteName can be left blank to mean 'any remote' func ScanUnpushed(remoteName string) ([]*WrappedPointer, error) { start := time.Now() defer func() { tracerx.PerformanceSince("scan", start) }() pointerchan, err := ScanUnpushedToChan(remoteName) if err != nil { return nil, err } pointers := make([]*WrappedPointer, 0, 10) for p := range pointerchan.Results { pointers = append(pointers, p) } err = pointerchan.Wait() return pointers, err }
// ScanRefsToChan takes a ref and returns a channel of WrappedPointer objects // for all Git LFS pointers it finds for that ref. // Reports unique oids once only, not multiple times if >1 file uses the same content func ScanRefsToChan(refLeft, refRight string, opt *ScanRefsOptions) (<-chan *WrappedPointer, error) { if opt == nil { opt = &ScanRefsOptions{} } if refLeft == "" { opt.ScanMode = ScanAllMode } opt.nameMap = make(map[string]string, 0) start := time.Now() defer func() { tracerx.PerformanceSince("scan", start) }() revs, err := revListShas(refLeft, refRight, *opt) if err != nil { return nil, err } smallShas, err := catFileBatchCheck(revs) if err != nil { return nil, err } pointerc, err := catFileBatch(smallShas) if err != nil { return nil, err } retchan := make(chan *WrappedPointer, chanBufSize) go func() { for p := range pointerc { if name, ok := opt.nameMap[p.Sha1]; ok { p.Name = name } retchan <- p } close(retchan) }() return retchan, nil }
// ScanRefs takes a ref and returns a slice of WrappedPointer objects // for all Git LFS pointers it finds for that ref. // Reports unique oids once only, not multiple times if >1 file uses the same content func ScanRefs(refLeft, refRight string, opt *ScanRefsOptions) ([]*WrappedPointer, error) { if opt == nil { opt = &ScanRefsOptions{} } if refLeft == "" { opt.ScanMode = ScanAllMode } opt.nameMap = make(map[string]string, 0) start := time.Now() defer func() { tracerx.PerformanceSince("scan", start) }() revs, err := revListShas(refLeft, refRight, *opt) if err != nil { return nil, err } smallShas, err := catFileBatchCheck(revs) if err != nil { return nil, err } pointerc, err := catFileBatch(smallShas) if err != nil { return nil, err } pointers := make([]*WrappedPointer, 0) for p := range pointerc { if name, ok := opt.nameMap[p.Sha1]; ok { p.Name = name } pointers = append(pointers, p) } return pointers, nil }
func fetchCommand(cmd *cobra.Command, args []string) { var ref string var err error if len(args) == 1 { ref = args[0] } else { ref, err = git.CurrentRef() if err != nil { Panic(err, "Could not fetch") } } pointers, err := lfs.ScanRefs(ref, "") if err != nil { Panic(err, "Could not scan for Git LFS files") } q := lfs.NewDownloadQueue(lfs.Config.ConcurrentTransfers(), len(pointers)) for _, p := range pointers { q.Add(lfs.NewDownloadable(p)) } target, err := git.ResolveRef(ref) if err != nil { Panic(err, "Could not resolve git ref") } current, err := git.CurrentRef() if err != nil { Panic(err, "Could not fetch the current git ref") } if target == current { // We just downloaded the files for the current ref, we can copy them into // the working directory and update the git index. We're doing this in a // goroutine so they can be copied as they come in, for efficiency. watch := q.Watch() go func() { files := make(map[string]*lfs.WrappedPointer, len(pointers)) for _, pointer := range pointers { files[pointer.Oid] = pointer } // Fire up the update-index command cmd := exec.Command("git", "update-index", "-q", "--refresh", "--stdin") stdin, err := cmd.StdinPipe() if err != nil { Panic(err, "Could not update the index") } if err := cmd.Start(); err != nil { Panic(err, "Could not update the index") } // As files come in, write them to the wd and update the index for oid := range watch { pointer, ok := files[oid] if !ok { continue } file, err := os.Create(pointer.Name) if err != nil { Panic(err, "Could not create working directory file") } if err := lfs.PointerSmudge(file, pointer.Pointer, pointer.Name, nil); err != nil { Panic(err, "Could not write working directory file") } file.Close() stdin.Write([]byte(pointer.Name + "\n")) } stdin.Close() if err := cmd.Wait(); err != nil { Panic(err, "Error updating the git index") } }() processQueue := time.Now() q.Process() tracerx.PerformanceSince("process queue", processQueue) } }
// ScanIndex returns a slice of WrappedPointer objects for all // Git LFS pointers it finds in the index. // Reports unique oids once only, not multiple times if >1 file uses the same content func ScanIndex() ([]*WrappedPointer, error) { indexMap := &indexFileMap{ nameMap: make(map[string]*indexFile, 0), mutex: &sync.Mutex{}, } start := time.Now() defer func() { tracerx.PerformanceSince("scan-staging", start) }() revs, err := revListIndex(false, indexMap) if err != nil { return nil, err } cachedRevs, err := revListIndex(true, indexMap) if err != nil { return nil, err } allRevsErr := make(chan error, 5) // can be multiple errors below allRevsChan := make(chan string, 1) allRevs := NewStringChannelWrapper(allRevsChan, allRevsErr) go func() { seenRevs := make(map[string]bool, 0) for rev := range revs.Results { seenRevs[rev] = true allRevsChan <- rev } err := revs.Wait() if err != nil { allRevsErr <- err } for rev := range cachedRevs.Results { if _, ok := seenRevs[rev]; !ok { allRevsChan <- rev } } err = cachedRevs.Wait() if err != nil { allRevsErr <- err } close(allRevsChan) close(allRevsErr) }() smallShas, err := catFileBatchCheck(allRevs) if err != nil { return nil, err } pointerc, err := catFileBatch(smallShas) if err != nil { return nil, err } pointers := make([]*WrappedPointer, 0) for p := range pointerc.Results { if e, ok := indexMap.Get(p.Sha1); ok { p.Name = e.Name p.Status = e.Status p.SrcName = e.SrcName } pointers = append(pointers, p) } err = pointerc.Wait() return pointers, err }