containers/apps/ci-os/packages/merge-diff/src/main.go

180 lines
4.5 KiB
Go
Raw Normal View History

package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
)
type Result struct {
FilteredChanges []string `json:"filteredChanges"`
NextVersion string `json:"nextVersion"`
}
func main() {
// Parse command-line arguments
var patterns patternList
var depth int
flag.Var(&patterns, "p", "Path pattern to filter changed files (can be specified multiple times)")
flag.Var(&patterns, "pattern", "Path pattern to filter changed files (can be specified multiple times)")
flag.IntVar(&depth, "d", 2, "Depth of changeset (default is 2)")
flag.IntVar(&depth, "depth", 2, "Depth of changeset (default is 2)")
help := flag.Bool("h", false, "Display this help message")
helpLong := flag.Bool("help", false, "Display this help message")
flag.Parse()
if *help || *helpLong {
flag.Usage()
os.Exit(1)
}
if len(patterns) == 0 {
fmt.Println("Error: At least one path pattern must be specified.")
flag.Usage()
os.Exit(1)
}
// Get the current commit SHA
currentSHA := getCurrentSHA()
// Get parent commits
parentSHAs := getParentSHAs(currentSHA)
if len(parentSHAs) == 0 {
fmt.Println("Single commit with no parents. Listing all changes.")
parentSHAs = append(parentSHAs, currentSHA+"~1")
}
// Collect all changed files from all parents
changedFiles := make(map[string]struct{})
for _, parentSHA := range parentSHAs {
files := getChangedFiles(parentSHA, currentSHA)
for _, file := range files {
changedFiles[file] = struct{}{}
}
}
// Filter and process changed files
filteredChanges := filterAndProcessFiles(changedFiles, patterns, depth)
// Get the next semantic version using git-sv
nextVersion := getNextVersion()
// Prepare the result
result := Result{
FilteredChanges: uniqueStrings(filteredChanges),
NextVersion: nextVersion,
}
// Output results as JSON
output, err := json.Marshal(result)
if err != nil {
log.Fatalf("Error marshaling JSON: %v", err)
}
fmt.Println(string(output))
}
// Custom flag type for multiple patterns
type patternList []string
func (p *patternList) String() string {
return strings.Join(*p, ", ")
}
func (p *patternList) Set(value string) error {
*p = append(*p, value)
return nil
}
// Helper functions
func getCurrentSHA() string {
output, err := exec.Command("git", "rev-parse", "HEAD").Output()
if err != nil {
log.Fatalf("Error getting current SHA: %v", err)
}
return strings.TrimSpace(string(output))
}
func getParentSHAs(commitSHA string) []string {
output, err := exec.Command("git", "rev-list", "--parents", "-n", "1", commitSHA).Output()
if err != nil {
log.Fatalf("Error getting parent SHAs: %v", err)
}
parts := strings.Fields(string(output))
if len(parts) > 1 {
return parts[1:] // Exclude the current commit SHA
}
return []string{}
}
func getChangedFiles(parentSHA, currentSHA string) []string {
output, err := exec.Command("git", "diff", "--name-only", parentSHA, currentSHA).Output()
if err != nil {
log.Fatalf("Error getting changed files: %v", err)
}
lines := strings.Split(strings.TrimSpace(string(output)), "\n")
if len(lines) == 1 && lines[0] == "" {
return []string{}
}
return lines
}
func filterAndProcessFiles(changedFiles map[string]struct{}, patterns []string, depth int) []string {
var filtered []string
for file := range changedFiles {
for _, pattern := range patterns {
matched, err := regexp.MatchString(pattern, file)
if err != nil {
log.Fatalf("Invalid regex pattern: %v", err)
}
if matched {
processedFile := processFilePath(file, depth)
filtered = append(filtered, processedFile)
break // Stop checking other patterns for this file
}
}
}
return filtered
}
func processFilePath(filePath string, depth int) string {
parts := strings.Split(filePath, string(os.PathSeparator))
if len(parts) > depth {
parts = parts[:depth]
}
return filepath.Join(parts...)
}
func uniqueStrings(input []string) []string {
uniqueMap := make(map[string]struct{})
for _, item := range input {
uniqueMap[item] = struct{}{}
}
var result []string
for item := range uniqueMap {
result = append(result, item)
}
return result
}
func getNextVersion() string {
// Ensure that git-sv is available
if _, err := exec.LookPath("git-sv"); err != nil {
log.Fatalf("Error: git-sv is not installed or not in PATH.")
}
output, err := exec.Command("git-sv", "next-version").Output()
if err != nil {
log.Fatalf("Error getting next version: %v", err)
}
nextVersion := strings.TrimSpace(string(output))
return nextVersion
}