180 lines
4.5 KiB
Go
180 lines
4.5 KiB
Go
|
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
|
||
|
}
|