package transportrequest import ( "fmt" gitUtils "github.com/SAP/jenkins-library/pkg/git" "github.com/SAP/jenkins-library/pkg/log" "github.com/SAP/jenkins-library/pkg/piperutils" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing/object" "github.com/pkg/errors" "os" "regexp" "sort" "strings" ) var logRange = gitUtils.LogRange var findLabelsInCommits = FindLabelsInCommits type iTransportRequestGitUtils interface { PlainOpen(directory string) (*git.Repository, error) } type transportRequestGitUtils struct { } func (g *transportRequestGitUtils) PlainOpen(directory string) (*git.Repository, error) { r, err := gitUtils.PlainOpen(directory) if err != nil { return nil, errors.Wrapf(err, "Unable to open git repository at '%s'", directory) } return r, nil } // FindIDInRange finds a ID according to the label in a commit range <from>..<to>. // We assume the git repo is present in the current working directory. func FindIDInRange(label, from, to string) (string, error) { return findIDInRange(label, from, to, &transportRequestGitUtils{}) } func findIDInRange(label, from, to string, trGitUtils iTransportRequestGitUtils) (string, error) { workdir, err := os.Getwd() if err != nil { return "", errors.Wrapf(err, "Cannot open git repo in current working directory '%s'", workdir) } log.Entry().Infof("Opening git repo at '%s'", workdir) r, err := trGitUtils.PlainOpen(workdir) if err != nil { return "", errors.Wrapf(err, "Unable to open git repository at '%s'", workdir) } cIter, err := logRange(r, from, to) if err != nil { return "", errors.Wrapf(err, "Cannot retrieve '%s'. Unable to resolve commits in range '%s..%s'", label, from, to) } ids, err := findLabelsInCommits(cIter, label) if err != nil { return "", errors.Wrapf(err, "Cannot retrieve '%s'. Unable to traverse commits in range '%s..%s'", label, from, to) } if len(ids) > 1 { return "", fmt.Errorf("More than one values found for label '%s' in range '%s..%s': '%s'", label, from, to, ids) } if len(ids) == 0 { return "", fmt.Errorf("No values found for '%s' in range '%s..%s'", label, from, to) } return ids[0], nil } // FindLabelsInCommits a label is considered to be something like // key: label, e.g. TransportRequest: 123456 // These labels are expected to be contained in the git commit message as // a separate line in the commit message body. // In case several labels are found they are returned in ascending order. func FindLabelsInCommits(commits object.CommitIter, label string) ([]string, error) { labelRegex, err := regexp.Compile(finishLabel(label)) if err != nil { return []string{}, fmt.Errorf("Cannot extract label: %w", err) } ids := []string{} err = commits.ForEach(func(c *object.Commit) error { for _, e := range labelRegex.FindAllStringSubmatch(c.Message, -1) { if len(e) < 2 { // the first entry is the full match, the second entry (at index 1) is the group return fmt.Errorf("Cannot extract label '%s' from commit '%s': '%s'", label, c.ID(), c.Message) } ids = append(ids, e[1]) } return nil }) if err != nil { return []string{}, fmt.Errorf("Cannot extract label: %w", err) } labels := piperutils.UniqueStrings(ids) sort.Strings(labels) return labels, nil } func finishLabel(label string) string { // contains prefix, like the old default if strings.ContainsAny(label, ":=") { return fmt.Sprintf(`(?m)^\s*%s\s*(\S*)\s*$`, label) } // contains key only, like the new default return fmt.Sprintf(`(?m)^\s*%s\s*:\s*(\S*)\s*$`, label) }