in internal/git/core.go [74:180]
func (cache *gitCache) cloneRepo(repo string, commit string, branch string) chan *gitCloneResult {
cloneResultChan := make(chan *gitCloneResult)
go func() {
cacheToken := cacheKey(repo, branch, commit)
// Check if the repo is cloned/being-cloned
if cloneResult, ok := cache.get(cacheToken); ok {
logger.Info(emoji.Sprintf(":atm: Previously cloned '%s' this install; reusing cached result", cacheToken))
cloneResultChan <- cloneResult
close(cloneResultChan)
return
}
// Add the clone future to cache
cloneResult := gitCloneResult{}
cloneResult.mu.Lock() // lock the future
defer func() {
cloneResult.mu.Unlock() // ensure the lock is released
close(cloneResultChan)
}()
cache.set(cacheToken, &cloneResult) // store future in cache
// Default options for a clone
cloneCommandArgs := []string{"clone"}
// check for access token and append to repo if present
if token, exists := AccessTokens.Get(repo); exists {
// Only match when the repo string does not contain a an access token already
// "(https?)://(?!(.+:)?.+@)(.+)" would be preferred but go does not support negative lookahead
pattern, err := regexp.Compile("^(https?)://([^@]+@)?(.+)$")
if err != nil {
cloneResultChan <- &gitCloneResult{Error: err}
return
}
// If match is found, inject the access token into the repo string
if matches := pattern.FindStringSubmatch(repo); matches != nil {
protocol := matches[1]
// credentialsWithAtSign := matches[2]
cleanedRepoString := matches[3]
repo = fmt.Sprintf("%v://%v@%v", protocol, token, cleanedRepoString)
}
}
// Add repo to clone args
cloneCommandArgs = append(cloneCommandArgs, repo)
// Only fetch latest commit if commit not provided
if len(commit) == 0 {
logger.Info(emoji.Sprintf(":helicopter: Component requested latest commit: fast cloning at --depth 1"))
cloneCommandArgs = append(cloneCommandArgs, "--depth", "1")
} else {
logger.Info(emoji.Sprintf(":helicopter: Component requested commit '%s': need full clone", commit))
}
// Add branch reference option if provided
if len(branch) != 0 {
logger.Info(emoji.Sprintf(":helicopter: Component requested branch '%s'", branch))
cloneCommandArgs = append(cloneCommandArgs, "--branch", branch)
}
// Clone into a random path in the host temp dir
randomFolderName, err := uuid.NewRandom()
if err != nil {
cloneResultChan <- &gitCloneResult{Error: err}
return
}
clonePathOnFS := path.Join(os.TempDir(), randomFolderName.String())
logger.Info(emoji.Sprintf(":helicopter: Cloning %s => %s", cacheToken, clonePathOnFS))
cloneCommandArgs = append(cloneCommandArgs, clonePathOnFS)
cloneCommand := exec.Command("git", cloneCommandArgs...)
cloneCommand.Env = append(cloneCommand.Env, os.Environ()...) // pass all env variables to git command so proper SSH config is passed if needed
cloneCommand.Env = append(cloneCommand.Env, "GIT_TERMINAL_PROMPT=0") // tell git to fail if it asks for credentials
var stdout, stderr bytes.Buffer
cloneCommand.Stdout = &stdout
cloneCommand.Stderr = &stderr
if err := cloneCommand.Run(); err != nil {
logger.Error(emoji.Sprintf(":no_entry_sign: Error occurred while cloning: '%s'\n%s: %s", cacheToken, err, stderr.String()))
cloneResultChan <- &gitCloneResult{Error: fmt.Errorf("%v: %v", err, stderr.String())}
return
}
// If commit provided, checkout the commit
if len(commit) != 0 {
logger.Info(emoji.Sprintf(":helicopter: Performing checkout commit '%s' for repo '%s'", commit, repo))
checkoutCommit := exec.Command("git", "checkout", commit)
var stdout, stderr bytes.Buffer
checkoutCommit.Stdout = &stdout
checkoutCommit.Stderr = &stderr
checkoutCommit.Dir = clonePathOnFS
if err := checkoutCommit.Run(); err != nil {
logger.Error(emoji.Sprintf(":no_entry_sign: Error occurred checking out commit '%s' from repo '%s'\n%s: %s", commit, repo, err, stderr.String()))
cloneResultChan <- &gitCloneResult{Error: fmt.Errorf("%v: %v", err, stderr.String())}
return
}
}
// Save the gitCloneResult into cache
cloneResult.ClonePath = clonePathOnFS
// Push the cached result to the channel
cloneResultChan <- &cloneResult
}()
return cloneResultChan
}