internal/platform/sarifVersioning.go (119 lines of code) (raw):
/*
* Copyright 2021-2024 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package platform
import (
"errors"
"net/url"
"os"
"path/filepath"
"strings"
cienvironment "github.com/cucumber/ci-environment/go"
log "github.com/sirupsen/logrus"
"github.com/JetBrains/qodana-cli/internal/platform/utils"
"github.com/JetBrains/qodana-cli/internal/sarif"
)
// it is needed for third party linters
// GetVersionDetails returns the version control details for the current repository.
func GetVersionDetails(pwd string) (sarif.VersionControlDetails, error) {
ret := sarif.VersionControlDetails{}
if os.Getenv("QODANA_REMOTE_URL") != "" {
ret.RepositoryUri = os.Getenv("QODANA_REMOTE_URL") // TODO : reuse consts
} else {
uri, err := getRepositoryUri(pwd)
if err != nil {
return ret, err
}
ret.RepositoryUri = uri
}
ret.Branch = os.Getenv("QODANA_BRANCH")
if ret.Branch == "" {
branch, err := getBranchName(pwd)
if err != nil {
return ret, err
}
ret.Branch = branch
}
// Sometimes in CI the HEAD is detached even on push-based runs.
// As a last resort, try to pick up the branch name from pre-defined environment variables.
if ret.Branch == "" {
ci := cienvironment.DetectCIEnvironment()
if ci != nil && ci.Git != nil {
ret.Branch = ci.Git.Branch
}
}
if os.Getenv("QODANA_REVISION") != "" {
ret.RevisionId = os.Getenv("QODANA_REVISION")
} else {
rev, err := getRevisionId(pwd)
if err != nil {
return ret, err
}
ret.RevisionId = rev
}
ret.Properties = &sarif.PropertyBag{}
ret.Properties.AdditionalProperties = map[string]interface{}{
"repoUrl": ret.RepositoryUri,
"vcsType": "Git",
"lastAuthorName": getLastAuthorName(pwd),
"lastAuthorEmail": getAuthorEmail(pwd),
}
return ret, nil
}
func getRepositoryUri(pwd string) (string, error) {
uri, stderr, ret, err := utils.RunCmdRedirectOutput(pwd, "git", "ls-remote", "--get-url")
if err != nil {
return "", err
}
if ret != 0 {
// Returned when a remote is not configured or multiple remotes exist, none of which is the default
log.Warn("Failed to retrieve remote URI: ", stderr)
uriStruct := url.URL{
Scheme: "file",
Host: "",
Path: filepath.ToSlash(pwd),
}
return uriStruct.String(), nil
}
trimUrl := strings.TrimSpace(uri)
if !strings.Contains(trimUrl, "://") {
trimUrl = "ssh://" + trimUrl
}
return trimUrl, nil
}
func getRevisionId(pwd string) (string, error) {
rev, _, ret, err := utils.RunCmdRedirectOutput(pwd, "git", "rev-parse", "HEAD")
if err != nil {
return "", err
}
if ret != 0 {
return "", errors.New("git rev-parse HEAD failed")
}
return strings.TrimSpace(rev), nil
}
func getBranchName(pwd string) (string, error) {
// note: git branch --show-current not used because the flag is too recent at the time of writing
branch, _, ret, err := utils.RunCmdRedirectOutput(pwd, "git", "rev-parse", "--abbrev-ref", "HEAD")
if err != nil {
return "", err
}
if ret == 128 {
// this approach covers some corner cases, notably when no commits exist
branch, _, ret, err = utils.RunCmdRedirectOutput(pwd, "git", "symbolic-ref", "--short", "HEAD")
if err != nil {
return "", err
}
}
if ret != 0 {
return "", errors.New("git rev-parse --abbrev-ref HEAD failed")
}
branch = strings.TrimSpace(branch)
if branch == "HEAD" {
// HEAD is a reserved name in git, so HEAD can only mean that HEAD is detached.
return "", nil
}
return branch, nil
}
func getLastAuthorName(pwd string) string {
name, _, ret, err := utils.RunCmdRedirectOutput(pwd, "git", "log", "-1", "--pretty=format:%an")
if err != nil || ret != 0 {
return ""
}
return strings.TrimSpace(name)
}
func getAuthorEmail(pwd string) string {
email, _, ret, err := utils.RunCmdRedirectOutput(pwd, "git", "log", "-1", "--pretty=format:%ae")
if err != nil || ret != 0 {
return ""
}
return strings.TrimSpace(email)
}