github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/covermerger/provider_monorepo.go (about)

     1  // Copyright 2024 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package covermerger
     5  
     6  import (
     7  	"fmt"
     8  	"path/filepath"
     9  	"sync"
    10  
    11  	"github.com/google/syzkaller/pkg/log"
    12  	"github.com/google/syzkaller/pkg/vcs"
    13  	"github.com/google/syzkaller/sys/targets"
    14  )
    15  
    16  type FileVersProvider interface {
    17  	GetFileVersions(targetFilePath string, repoCommits ...RepoCommit,
    18  	) (FileVersions, error)
    19  }
    20  
    21  type monoRepo struct {
    22  	repoCommits map[RepoCommit]struct{}
    23  	mu          sync.RWMutex
    24  	repo        vcs.Repo
    25  }
    26  
    27  type FileVersions map[RepoCommit]string
    28  
    29  func (mr *monoRepo) GetFileVersions(targetFilePath string, repoCommits ...RepoCommit,
    30  ) (FileVersions, error) {
    31  	mr.mu.RLock()
    32  	if !mr.allRepoCommitsPresent(repoCommits) {
    33  		mr.mu.RUnlock()
    34  		mr.cloneCommits(repoCommits)
    35  		mr.mu.RLock()
    36  	}
    37  	defer mr.mu.RUnlock()
    38  	res := make(FileVersions)
    39  	for _, repoCommit := range repoCommits {
    40  		fileBytes, err := mr.repo.Object(targetFilePath, repoCommit.Commit)
    41  		// It is ok if some file doesn't exist. It means we have repo FS diff.
    42  		// Or the upstream commit doesn't exist anymore
    43  		if err != nil {
    44  			log.Logf(1, "repo.Object(%s, %s) error: %s", targetFilePath, repoCommit.Commit, err.Error())
    45  			continue
    46  		}
    47  		res[repoCommit] = string(fileBytes)
    48  	}
    49  	return res, nil
    50  }
    51  
    52  func (mr *monoRepo) allRepoCommitsPresent(repoCommits []RepoCommit) bool {
    53  	for _, repoCommit := range repoCommits {
    54  		if _, exists := mr.repoCommits[repoCommit]; !exists {
    55  			return false
    56  		}
    57  	}
    58  	return true
    59  }
    60  
    61  func (mr *monoRepo) addRepoCommit(repoCommit RepoCommit) {
    62  	log.Logf(0, "cloning repo: %s, commit %s", repoCommit.Repo, repoCommit.Commit)
    63  	mr.repoCommits[repoCommit] = struct{}{}
    64  	repo, commit := repoCommit.Repo, repoCommit.Commit
    65  	if repo == "" || commit == "" {
    66  		panic("repo and commit are needed")
    67  	}
    68  	if _, err := mr.repo.CheckoutCommit(repo, commit); err != nil {
    69  		log.Logf(0, "failed to CheckoutCommit(repo %s, commit %s): %s", repo, commit, err.Error())
    70  	}
    71  }
    72  
    73  func MakeMonoRepo(workdir string) FileVersProvider {
    74  	repoPath := filepath.Join(workdir, "repos", "linux_kernels")
    75  	mr := &monoRepo{
    76  		repoCommits: map[RepoCommit]struct{}{},
    77  	}
    78  	var err error
    79  	if mr.repo, err = vcs.NewRepo(targets.Linux, "none", repoPath); err != nil {
    80  		panic(fmt.Sprintf("failed to create/open repo at %s: %s", repoPath, err.Error()))
    81  	}
    82  	return mr
    83  }
    84  
    85  func (mr *monoRepo) cloneCommits(repoCommits []RepoCommit) {
    86  	mr.mu.Lock()
    87  	defer mr.mu.Unlock()
    88  	for _, repoCommit := range repoCommits {
    89  		if _, exists := mr.repoCommits[repoCommit]; exists {
    90  			continue
    91  		}
    92  		commitExistsInRepo, err := mr.repo.CommitExists(repoCommit.Commit)
    93  		if err != nil {
    94  			log.Logf(0, "can't check CommitExists: %s", err.Error())
    95  		}
    96  		if commitExistsInRepo {
    97  			mr.repoCommits[repoCommit] = struct{}{}
    98  			continue
    99  		}
   100  		mr.addRepoCommit(repoCommit)
   101  	}
   102  }