code.gitea.io/gitea@v1.22.3/modules/git/pipeline/lfs_gogit.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  //go:build gogit
     5  
     6  package pipeline
     7  
     8  import (
     9  	"bufio"
    10  	"io"
    11  	"sort"
    12  	"strings"
    13  	"sync"
    14  
    15  	"code.gitea.io/gitea/modules/git"
    16  
    17  	gogit "github.com/go-git/go-git/v5"
    18  	"github.com/go-git/go-git/v5/plumbing"
    19  	"github.com/go-git/go-git/v5/plumbing/object"
    20  )
    21  
    22  // FindLFSFile finds commits that contain a provided pointer file hash
    23  func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
    24  	resultsMap := map[string]*LFSResult{}
    25  	results := make([]*LFSResult, 0)
    26  
    27  	basePath := repo.Path
    28  	gogitRepo := repo.GoGitRepo()
    29  
    30  	commitsIter, err := gogitRepo.Log(&gogit.LogOptions{
    31  		Order: gogit.LogOrderCommitterTime,
    32  		All:   true,
    33  	})
    34  	if err != nil {
    35  		return nil, lfsError("failed to get GoGit CommitsIter", err)
    36  	}
    37  
    38  	err = commitsIter.ForEach(func(gitCommit *object.Commit) error {
    39  		tree, err := gitCommit.Tree()
    40  		if err != nil {
    41  			return err
    42  		}
    43  		treeWalker := object.NewTreeWalker(tree, true, nil)
    44  		defer treeWalker.Close()
    45  		for {
    46  			name, entry, err := treeWalker.Next()
    47  			if err == io.EOF {
    48  				break
    49  			}
    50  			if entry.Hash == plumbing.Hash(objectID.RawValue()) {
    51  				parents := make([]git.ObjectID, len(gitCommit.ParentHashes))
    52  				for i, parentCommitID := range gitCommit.ParentHashes {
    53  					parents[i] = git.ParseGogitHash(parentCommitID)
    54  				}
    55  
    56  				result := LFSResult{
    57  					Name:         name,
    58  					SHA:          gitCommit.Hash.String(),
    59  					Summary:      strings.Split(strings.TrimSpace(gitCommit.Message), "\n")[0],
    60  					When:         gitCommit.Author.When,
    61  					ParentHashes: parents,
    62  				}
    63  				resultsMap[gitCommit.Hash.String()+":"+name] = &result
    64  			}
    65  		}
    66  		return nil
    67  	})
    68  	if err != nil && err != io.EOF {
    69  		return nil, lfsError("failure in CommitIter.ForEach", err)
    70  	}
    71  
    72  	for _, result := range resultsMap {
    73  		hasParent := false
    74  		for _, parentHash := range result.ParentHashes {
    75  			if _, hasParent = resultsMap[parentHash.String()+":"+result.Name]; hasParent {
    76  				break
    77  			}
    78  		}
    79  		if !hasParent {
    80  			results = append(results, result)
    81  		}
    82  	}
    83  
    84  	sort.Sort(lfsResultSlice(results))
    85  
    86  	// Should really use a go-git function here but name-rev is not completed and recapitulating it is not simple
    87  	shasToNameReader, shasToNameWriter := io.Pipe()
    88  	nameRevStdinReader, nameRevStdinWriter := io.Pipe()
    89  	errChan := make(chan error, 1)
    90  	wg := sync.WaitGroup{}
    91  	wg.Add(3)
    92  
    93  	go func() {
    94  		defer wg.Done()
    95  		scanner := bufio.NewScanner(nameRevStdinReader)
    96  		i := 0
    97  		for scanner.Scan() {
    98  			line := scanner.Text()
    99  			if len(line) == 0 {
   100  				continue
   101  			}
   102  			result := results[i]
   103  			result.FullCommitName = line
   104  			result.BranchName = strings.Split(line, "~")[0]
   105  			i++
   106  		}
   107  	}()
   108  	go NameRevStdin(repo.Ctx, shasToNameReader, nameRevStdinWriter, &wg, basePath)
   109  	go func() {
   110  		defer wg.Done()
   111  		defer shasToNameWriter.Close()
   112  		for _, result := range results {
   113  			i := 0
   114  			if i < len(result.SHA) {
   115  				n, err := shasToNameWriter.Write([]byte(result.SHA)[i:])
   116  				if err != nil {
   117  					errChan <- err
   118  					break
   119  				}
   120  				i += n
   121  			}
   122  			n := 0
   123  			for n < 1 {
   124  				n, err = shasToNameWriter.Write([]byte{'\n'})
   125  				if err != nil {
   126  					errChan <- err
   127  					break
   128  				}
   129  
   130  			}
   131  
   132  		}
   133  	}()
   134  
   135  	wg.Wait()
   136  
   137  	select {
   138  	case err, has := <-errChan:
   139  		if has {
   140  			return nil, lfsError("unable to obtain name for LFS files", err)
   141  		}
   142  	default:
   143  	}
   144  
   145  	return results, nil
   146  }