func()

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
}