github.com/psexton/git-lfs@v2.1.1-0.20170517224304-289a18b2bc53+incompatible/lfs/gitscanner_refs.go (about) 1 package lfs 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "regexp" 9 "strconv" 10 "strings" 11 ) 12 13 var z40 = regexp.MustCompile(`\^?0{40}`) 14 15 type lockableNameSet struct { 16 opt *ScanRefsOptions 17 set GitScannerSet 18 } 19 20 // Determines if the given blob sha matches a locked file. 21 func (s *lockableNameSet) Check(blobSha string) (string, bool) { 22 if s == nil || s.opt == nil || s.set == nil { 23 return "", false 24 } 25 26 name, ok := s.opt.GetName(blobSha) 27 if !ok { 28 return name, ok 29 } 30 31 if s.set.Contains(name) { 32 return name, true 33 } 34 return name, false 35 } 36 37 func noopFoundLockable(name string) {} 38 39 // scanRefsToChan takes a ref and returns a channel of WrappedPointer objects 40 // for all Git LFS pointers it finds for that ref. 41 // Reports unique oids once only, not multiple times if >1 file uses the same content 42 func scanRefsToChan(scanner *GitScanner, pointerCb GitScannerFoundPointer, refLeft, refRight string, opt *ScanRefsOptions) error { 43 if opt == nil { 44 panic("no scan ref options") 45 } 46 47 revs, err := revListShas(refLeft, refRight, opt) 48 if err != nil { 49 return err 50 } 51 52 lockableSet := &lockableNameSet{opt: opt, set: scanner.PotentialLockables} 53 smallShas, batchLockableCh, err := catFileBatchCheck(revs, lockableSet) 54 if err != nil { 55 return err 56 } 57 58 lockableCb := scanner.FoundLockable 59 if lockableCb == nil { 60 lockableCb = noopFoundLockable 61 } 62 63 go func(cb GitScannerFoundLockable, ch chan string) { 64 for name := range ch { 65 cb(name) 66 } 67 }(lockableCb, batchLockableCh) 68 69 pointers, checkLockableCh, err := catFileBatch(smallShas, lockableSet) 70 if err != nil { 71 return err 72 } 73 74 for p := range pointers.Results { 75 if name, ok := opt.GetName(p.Sha1); ok { 76 p.Name = name 77 } 78 pointerCb(p, nil) 79 } 80 81 for lockableName := range checkLockableCh { 82 lockableCb(lockableName) 83 } 84 85 if err := pointers.Wait(); err != nil { 86 pointerCb(nil, err) 87 } 88 89 return nil 90 } 91 92 // revListShas uses git rev-list to return the list of object sha1s 93 // for the given ref. If all is true, ref is ignored. It returns a 94 // channel from which sha1 strings can be read. 95 func revListShas(refLeft, refRight string, opt *ScanRefsOptions) (*StringChannelWrapper, error) { 96 refArgs := []string{"rev-list", "--objects"} 97 var stdin []string 98 switch opt.ScanMode { 99 case ScanRefsMode: 100 if opt.SkipDeletedBlobs { 101 refArgs = append(refArgs, "--no-walk") 102 } else { 103 refArgs = append(refArgs, "--do-walk") 104 } 105 106 refArgs = append(refArgs, refLeft) 107 if refRight != "" && !z40.MatchString(refRight) { 108 refArgs = append(refArgs, refRight) 109 } 110 case ScanAllMode: 111 refArgs = append(refArgs, "--all") 112 case ScanLeftToRemoteMode: 113 args, commits := revListArgsRefVsRemote(refLeft, opt.RemoteName, opt.skippedRefs) 114 refArgs = append(refArgs, args...) 115 if len(commits) > 0 { 116 stdin = commits 117 } 118 default: 119 return nil, errors.New("scanner: unknown scan type: " + strconv.Itoa(int(opt.ScanMode))) 120 } 121 122 // Use "--" at the end of the command to disambiguate arguments as refs, 123 // so Git doesn't complain about ambiguity if you happen to also have a 124 // file named "master". 125 refArgs = append(refArgs, "--") 126 127 cmd, err := startCommand("git", refArgs...) 128 if err != nil { 129 return nil, err 130 } 131 132 if len(stdin) > 0 { 133 cmd.Stdin.Write([]byte(strings.Join(stdin, "\n"))) 134 } 135 136 cmd.Stdin.Close() 137 138 revs := make(chan string, chanBufSize) 139 errchan := make(chan error, 5) // may be multiple errors 140 141 go func() { 142 scanner := bufio.NewScanner(cmd.Stdout) 143 for scanner.Scan() { 144 line := strings.TrimSpace(scanner.Text()) 145 if len(line) < 40 { 146 continue 147 } 148 149 sha1 := line[0:40] 150 if len(line) > 40 { 151 opt.SetName(sha1, line[41:len(line)]) 152 } 153 revs <- sha1 154 } 155 156 stderr, _ := ioutil.ReadAll(cmd.Stderr) 157 err := cmd.Wait() 158 if err != nil { 159 errchan <- fmt.Errorf("Error in git rev-list --objects: %v %v", err, string(stderr)) 160 } else { 161 // Special case detection of ambiguous refs; lower level commands like 162 // git rev-list do not return non-zero exit codes in this case, just warn 163 ambiguousRegex := regexp.MustCompile(`warning: refname (.*) is ambiguous`) 164 if match := ambiguousRegex.FindStringSubmatch(string(stderr)); match != nil { 165 // Promote to fatal & exit 166 errchan <- fmt.Errorf("Error: ref %s is ambiguous", match[1]) 167 } 168 } 169 close(revs) 170 close(errchan) 171 }() 172 173 return NewStringChannelWrapper(revs, errchan), nil 174 } 175 176 // Get additional arguments needed to limit 'git rev-list' to just the changes 177 // in refTo that are also not on remoteName. 178 // 179 // Returns a slice of string command arguments, and a slice of string git 180 // commits to pass to `git rev-list` via STDIN. 181 func revListArgsRefVsRemote(refTo, remoteName string, skippedRefs []string) ([]string, []string) { 182 if len(skippedRefs) < 1 { 183 // Safe to use cached 184 return []string{refTo, "--not", "--remotes=" + remoteName}, nil 185 } 186 187 // Use only the non-missing refs as 'from' points 188 commits := make([]string, 1, len(skippedRefs)+1) 189 commits[0] = refTo 190 return []string{"--stdin"}, append(commits, skippedRefs...) 191 }