github.com/jfrog/jfrog-client-go@v1.40.2/utils/vcsdetails.go (about)

     1  package utils
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  	"strings"
     7  	"sync"
     8  	"sync/atomic"
     9  
    10  	"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
    11  )
    12  
    13  type (
    14  	VcsCache struct {
    15  		// Key - Path to the .git directory.
    16  		// Value - Reference to a struct, storing the URL and revision.
    17  		vcsRootDir sync.Map
    18  		// Key - Path to a directory.
    19  		// Value - Reference to a struct, storing the URL and revision from the upstream .git. Can also include nil, if there's no upstream .git.
    20  		vcsDir sync.Map
    21  		// The current size of vcsDir
    22  		vcsDirSize *int32 // Size of vcs folders entries
    23  	}
    24  	vcsDetails struct {
    25  		url      string
    26  		revision string
    27  		branch   string
    28  	}
    29  )
    30  
    31  const MaxEntries = 10000
    32  
    33  func NewVcsDetails() *VcsCache {
    34  	return &VcsCache{vcsRootDir: sync.Map{}, vcsDir: sync.Map{}, vcsDirSize: new(int32)}
    35  }
    36  
    37  func (vc *VcsCache) incCacheSize(num int32) {
    38  	atomic.AddInt32(vc.vcsDirSize, num)
    39  }
    40  
    41  func (vc *VcsCache) getCacheSize() int32 {
    42  	return atomic.LoadInt32(vc.vcsDirSize)
    43  }
    44  
    45  // Search for '.git' directory inside 'path', incase there is one, extract the details and add a new entry to the cache(key:path in the file system ,value: git revision & url).
    46  // otherwise, search in the parent folder and try:
    47  // 1. search for .git, and save the details for the current dir and all subpath
    48  // 2. .git not found, go to parent dir and repeat
    49  // 3. not found on the root directory, add all subpath to cache with nil as a value
    50  func (vc *VcsCache) GetVcsDetails(path string) (revision, refUrl, branch string, err error) {
    51  	keys := strings.Split(path, string(os.PathSeparator))
    52  	var subPath string
    53  	var subPaths []string
    54  	var vcsDetailsResult *vcsDetails
    55  	for i := len(keys); i > 0; i-- {
    56  		subPath = strings.Join(keys[:i], string(os.PathSeparator))
    57  		// Try to get from cache
    58  		if vcsDetails, found := vc.searchCache(subPath); found {
    59  			if vcsDetails != nil {
    60  				revision, refUrl, branch, vcsDetailsResult = vcsDetails.revision, vcsDetails.url, vcsDetails.branch, vcsDetails
    61  			}
    62  			break
    63  		}
    64  		// Begin directory search
    65  		revision, refUrl, branch, err = tryGetGitDetails(subPath)
    66  		if revision != "" || refUrl != "" || branch != "" {
    67  			vcsDetailsResult = &vcsDetails{revision: revision, url: refUrl, branch: branch}
    68  			vc.vcsRootDir.Store(subPath, vcsDetailsResult)
    69  			break
    70  		}
    71  		if err != nil {
    72  			return
    73  		}
    74  		subPaths = append(subPaths, subPath)
    75  	}
    76  	if size := len(subPaths); size > 0 {
    77  		vc.clearCacheIfExceedsMax()
    78  		for _, v := range subPaths {
    79  			vc.vcsDir.Store(v, vcsDetailsResult)
    80  		}
    81  		vc.incCacheSize(int32(size))
    82  	}
    83  	return
    84  }
    85  
    86  func (vc *VcsCache) clearCacheIfExceedsMax() {
    87  	if vc.getCacheSize() > MaxEntries {
    88  		vc.vcsDir = sync.Map{}
    89  		vc.vcsDirSize = new(int32)
    90  	}
    91  }
    92  
    93  func tryGetGitDetails(path string) (string, string, string, error) {
    94  	exists := fileutils.IsPathExists(filepath.Join(path, ".git"), false)
    95  	if exists {
    96  		return extractGitDetails(path)
    97  	}
    98  	return "", "", "", nil
    99  }
   100  
   101  func extractGitDetails(path string) (string, string, string, error) {
   102  	gitService := NewGitManager(path)
   103  	if err := gitService.ReadConfig(); err != nil {
   104  		return "", "", "", err
   105  	}
   106  	return gitService.GetRevision(), gitService.GetUrl(), gitService.GetBranch(), nil
   107  }
   108  
   109  func (vc *VcsCache) searchCache(path string) (*vcsDetails, bool) {
   110  	if data, found := vc.vcsDir.Load(path); found {
   111  		if vcsDetails, ok := data.(*vcsDetails); ok {
   112  			return vcsDetails, ok
   113  		}
   114  	}
   115  	if data, found := vc.vcsRootDir.Load(path); found {
   116  		if vcsDetails, ok := data.(*vcsDetails); ok {
   117  			return vcsDetails, ok
   118  		}
   119  	}
   120  	return nil, false
   121  }