code.gitea.io/gitea@v1.19.3/modules/git/pipeline/lfs.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  	"fmt"
    11  	"io"
    12  	"sort"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"code.gitea.io/gitea/modules/git"
    18  
    19  	gogit "github.com/go-git/go-git/v5"
    20  	"github.com/go-git/go-git/v5/plumbing/object"
    21  )
    22  
    23  // LFSResult represents commits found using a provided pointer file hash
    24  type LFSResult struct {
    25  	Name           string
    26  	SHA            string
    27  	Summary        string
    28  	When           time.Time
    29  	ParentHashes   []git.SHA1
    30  	BranchName     string
    31  	FullCommitName string
    32  }
    33  
    34  type lfsResultSlice []*LFSResult
    35  
    36  func (a lfsResultSlice) Len() int           { return len(a) }
    37  func (a lfsResultSlice) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
    38  func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
    39  
    40  // FindLFSFile finds commits that contain a provided pointer file hash
    41  func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
    42  	resultsMap := map[string]*LFSResult{}
    43  	results := make([]*LFSResult, 0)
    44  
    45  	basePath := repo.Path
    46  	gogitRepo := repo.GoGitRepo()
    47  
    48  	commitsIter, err := gogitRepo.Log(&gogit.LogOptions{
    49  		Order: gogit.LogOrderCommitterTime,
    50  		All:   true,
    51  	})
    52  	if err != nil {
    53  		return nil, fmt.Errorf("Failed to get GoGit CommitsIter. Error: %w", err)
    54  	}
    55  
    56  	err = commitsIter.ForEach(func(gitCommit *object.Commit) error {
    57  		tree, err := gitCommit.Tree()
    58  		if err != nil {
    59  			return err
    60  		}
    61  		treeWalker := object.NewTreeWalker(tree, true, nil)
    62  		defer treeWalker.Close()
    63  		for {
    64  			name, entry, err := treeWalker.Next()
    65  			if err == io.EOF {
    66  				break
    67  			}
    68  			if entry.Hash == hash {
    69  				result := LFSResult{
    70  					Name:         name,
    71  					SHA:          gitCommit.Hash.String(),
    72  					Summary:      strings.Split(strings.TrimSpace(gitCommit.Message), "\n")[0],
    73  					When:         gitCommit.Author.When,
    74  					ParentHashes: gitCommit.ParentHashes,
    75  				}
    76  				resultsMap[gitCommit.Hash.String()+":"+name] = &result
    77  			}
    78  		}
    79  		return nil
    80  	})
    81  	if err != nil && err != io.EOF {
    82  		return nil, fmt.Errorf("Failure in CommitIter.ForEach: %w", err)
    83  	}
    84  
    85  	for _, result := range resultsMap {
    86  		hasParent := false
    87  		for _, parentHash := range result.ParentHashes {
    88  			if _, hasParent = resultsMap[parentHash.String()+":"+result.Name]; hasParent {
    89  				break
    90  			}
    91  		}
    92  		if !hasParent {
    93  			results = append(results, result)
    94  		}
    95  	}
    96  
    97  	sort.Sort(lfsResultSlice(results))
    98  
    99  	// Should really use a go-git function here but name-rev is not completed and recapitulating it is not simple
   100  	shasToNameReader, shasToNameWriter := io.Pipe()
   101  	nameRevStdinReader, nameRevStdinWriter := io.Pipe()
   102  	errChan := make(chan error, 1)
   103  	wg := sync.WaitGroup{}
   104  	wg.Add(3)
   105  
   106  	go func() {
   107  		defer wg.Done()
   108  		scanner := bufio.NewScanner(nameRevStdinReader)
   109  		i := 0
   110  		for scanner.Scan() {
   111  			line := scanner.Text()
   112  			if len(line) == 0 {
   113  				continue
   114  			}
   115  			result := results[i]
   116  			result.FullCommitName = line
   117  			result.BranchName = strings.Split(line, "~")[0]
   118  			i++
   119  		}
   120  	}()
   121  	go NameRevStdin(repo.Ctx, shasToNameReader, nameRevStdinWriter, &wg, basePath)
   122  	go func() {
   123  		defer wg.Done()
   124  		defer shasToNameWriter.Close()
   125  		for _, result := range results {
   126  			i := 0
   127  			if i < len(result.SHA) {
   128  				n, err := shasToNameWriter.Write([]byte(result.SHA)[i:])
   129  				if err != nil {
   130  					errChan <- err
   131  					break
   132  				}
   133  				i += n
   134  			}
   135  			n := 0
   136  			for n < 1 {
   137  				n, err = shasToNameWriter.Write([]byte{'\n'})
   138  				if err != nil {
   139  					errChan <- err
   140  					break
   141  				}
   142  
   143  			}
   144  
   145  		}
   146  	}()
   147  
   148  	wg.Wait()
   149  
   150  	select {
   151  	case err, has := <-errChan:
   152  		if has {
   153  			return nil, fmt.Errorf("Unable to obtain name for LFS files. Error: %w", err)
   154  		}
   155  	default:
   156  	}
   157  
   158  	return results, nil
   159  }