// os.IsNotExist(err) returns true.
ReadFile(rev, file string, maxSize int64) (data []byte, err error)
- // ReadFileRevs reads a single file at multiple versions.
- // It should refuse to read more than maxSize bytes.
- // The result is a map from each requested rev strings
- // to the associated FileRev. The map must have a non-nil
- // entry for every requested rev (unless ReadFileRevs returned an error).
- // A file simply being missing or even corrupted in revs[i]
- // should be reported only in files[revs[i]].Err, not in the error result
- // from ReadFileRevs.
- // The overall call should return an error (and no map) only
- // in the case of a problem with obtaining the data, such as
- // a network failure.
- // Implementations may assume that revs only contain tags,
- // not direct commit hashes.
- ReadFileRevs(revs []string, file string, maxSize int64) (files map[string]*FileRev, err error)
-
// ReadZip downloads a zip file for the subdir subdirectory
// of the given revision to a new file in a given temporary directory.
// It should refuse to read more than maxSize bytes.
return out, nil
}
-func (r *gitRepo) ReadFileRevs(revs []string, file string, maxSize int64) (map[string]*FileRev, error) {
- // Create space to hold results.
- files := make(map[string]*FileRev)
- for _, rev := range revs {
- f := &FileRev{Rev: rev}
- files[rev] = f
- }
-
- // Collect locally-known revs.
- need, err := r.readFileRevs(revs, file, files)
- if err != nil {
- return nil, err
- }
- if len(need) == 0 {
- return files, nil
- }
-
- // Build list of known remote refs that might help.
- var redo []string
- refs, err := r.loadRefs()
- if err != nil {
- return nil, err
- }
- for _, tag := range need {
- if refs["refs/tags/"+tag] != "" {
- redo = append(redo, tag)
- }
- }
- if len(redo) == 0 {
- return files, nil
- }
-
- // Protect r.fetchLevel and the "fetch more and more" sequence.
- // See stat method above.
- unlock, err := r.mu.Lock()
- if err != nil {
- return nil, err
- }
- defer unlock()
-
- if err := r.fetchRefsLocked(); err != nil {
- return nil, err
- }
-
- if _, err := r.readFileRevs(redo, file, files); err != nil {
- return nil, err
- }
-
- return files, nil
-}
-
-func (r *gitRepo) readFileRevs(tags []string, file string, fileMap map[string]*FileRev) (missing []string, err error) {
- var stdin bytes.Buffer
- for _, tag := range tags {
- fmt.Fprintf(&stdin, "refs/tags/%s\n", tag)
- fmt.Fprintf(&stdin, "refs/tags/%s:%s\n", tag, file)
- }
-
- data, err := RunWithStdin(r.dir, &stdin, "git", "cat-file", "--batch")
- if err != nil {
- return nil, err
- }
-
- next := func() (typ string, body []byte, ok bool) {
- var line string
- i := bytes.IndexByte(data, '\n')
- if i < 0 {
- return "", nil, false
- }
- line, data = string(bytes.TrimSpace(data[:i])), data[i+1:]
- if strings.HasSuffix(line, " missing") {
- return "missing", nil, true
- }
- f := strings.Fields(line)
- if len(f) != 3 {
- return "", nil, false
- }
- n, err := strconv.Atoi(f[2])
- if err != nil || n > len(data) {
- return "", nil, false
- }
- body, data = data[:n], data[n:]
- if len(data) > 0 && data[0] == '\r' {
- data = data[1:]
- }
- if len(data) > 0 && data[0] == '\n' {
- data = data[1:]
- }
- return f[1], body, true
- }
-
- badGit := func() ([]string, error) {
- return nil, fmt.Errorf("malformed output from git cat-file --batch")
- }
-
- for _, tag := range tags {
- commitType, _, ok := next()
- if !ok {
- return badGit()
- }
- fileType, fileData, ok := next()
- if !ok {
- return badGit()
- }
- f := fileMap[tag]
- f.Data = nil
- f.Err = nil
- switch commitType {
- default:
- f.Err = fmt.Errorf("unexpected non-commit type %q for rev %s", commitType, tag)
-
- case "missing":
- // Note: f.Err must not satisfy os.IsNotExist. That's reserved for the file not existing in a valid commit.
- f.Err = fmt.Errorf("no such rev %s", tag)
- missing = append(missing, tag)
-
- case "tag", "commit":
- switch fileType {
- default:
- f.Err = &fs.PathError{Path: tag + ":" + file, Op: "read", Err: fmt.Errorf("unexpected non-blob type %q", fileType)}
- case "missing":
- f.Err = &fs.PathError{Path: tag + ":" + file, Op: "read", Err: fs.ErrNotExist}
- case "blob":
- f.Data = fileData
- }
- }
- }
- if len(bytes.TrimSpace(data)) != 0 {
- return badGit()
- }
-
- return missing, nil
-}
-
func (r *gitRepo) RecentTag(rev, prefix string, allowed func(string) bool) (tag string, err error) {
info, err := r.Stat(rev)
if err != nil {