// src/main.go package main import ( "flag" "fmt" "log" "os" "path/filepath" "strings" ) func main() { // Define command-line flags baseURL := flag.String("base-url", "https://code.252.no/api/v1", "Base URL of the Forgejo API") token := flag.String("token", "", "API token for authentication") action := flag.String("action", "", "Action to perform: get, create, get-single, update, delete, apply, sync") owner := flag.String("owner", "", "Owner of the repository") repo := flag.String("repo", "", "Name of the repository") id := flag.Int64("id", 0, "ID of the issue or pull request") name := flag.String("name", "", "Name of the label") color := flag.String("color", "", "Color of the label") description := flag.String("description", "", "Description of the label") // Define exclusive and isArchived as regular boolean flags exclusive := flag.Bool("exclusive", false, "Exclusive flag for the label (true/false)") isArchived := flag.Bool("is-archived", false, "Archived flag for the label (true/false)") // Define paths to configuration files labelerConfigPath := flag.String("labeler-config", ".forgejo/labeler.yaml", "Path to labeler.yaml configuration file") labelsConfigPath := flag.String("labels-config", ".forgejo/labels.yaml", "Path to labels.yaml configuration file") // Define changed files (comma-separated list) changedFilesInput := flag.String("changed-files", "", "Comma-separated list of changed file paths") // Define deleteOtherLabels flag deleteOtherLabels := flag.Bool("delete-other-labels", false, "Delete labels not defined in the config file") // Define dryRun flag dryRun := flag.Bool("dry-run", false, "Simulate the sync without making any changes") page := flag.Int("page", 1, "Page number for listing labels") limit := flag.Int("limit", 30, "Number of labels per page") flag.Parse() // Validate required flags if *action == "" || *owner == "" || *repo == "" { fmt.Println("Error: action, owner, and repo are required.") flag.Usage() os.Exit(1) } // Initialize the Forgejo client client := NewForgejoClient(*baseURL, *token) switch *action { case "get": labels, err := client.GetLabels(*owner, *repo, *page, *limit) if err != nil { log.Fatalf("Error getting labels: %v", err) } for _, label := range labels { fmt.Printf("ID: %d, Name: %s, Color: %s, Description: %s\n", label.ID, label.Name, label.Color, label.Description) } case "create": if *name == "" || *color == "" { fmt.Println("Error: name and color are required for creating a label.") flag.Usage() os.Exit(1) } option := CreateLabelOption{ Name: *name, Color: *color, Description: *description, Exclusive: *exclusive, IsArchived: *isArchived, } label, err := client.CreateLabel(*owner, *repo, option) if err != nil { log.Fatalf("Error creating label: %v", err) } fmt.Printf("Created Label: ID=%d, Name=%s\n", label.ID, label.Name) case "get-single": if *id == 0 { fmt.Println("Error: id is required for getting a single label.") flag.Usage() os.Exit(1) } label, err := client.GetLabel(*owner, *repo, *id) if err != nil { log.Fatalf("Error getting label: %v", err) } fmt.Printf("Label: ID=%d, Name=%s, Color=%s, Description: %s\n", label.ID, label.Name, label.Color, label.Description) case "update": if *id == 0 { fmt.Println("Error: id is required for updating a label.") flag.Usage() os.Exit(1) } option := EditLabelOption{ Name: *name, Color: *color, Description: *description, } if *exclusive { option.Exclusive = exclusive } if *isArchived { option.IsArchived = isArchived } err := client.UpdateLabel(*owner, *repo, *id, option) if err != nil { log.Fatalf("Error updating label: %v", err) } fmt.Printf("Updated Label: Name=%s\n", option.Name) case "delete": if *id == 0 { fmt.Println("Error: id is required for deleting a label.") flag.Usage() os.Exit(1) } err := client.DeleteLabel(*owner, *repo, *id) if err != nil { log.Fatalf("Error deleting label: %v", err) } fmt.Println("Label deleted successfully.") case "apply": // Action to apply labels based on configuration and changed files if *changedFilesInput == "" { fmt.Println("Error: changed-files are required for the apply action.") flag.Usage() os.Exit(1) } // Parse changed files changedFiles := parseChangedFiles(*changedFilesInput) // Load configuration files labelerConfig, err := LoadLabelerConfig(*labelerConfigPath) if err != nil { log.Fatalf("Error loading labeler config: %v", err) } labelsConfig, err := LoadLabelsConfig(*labelsConfigPath) if err != nil { log.Fatalf("Error loading labels config: %v", err) } // Validate configurations if err := ValidateLabelerConfig(labelerConfig); err != nil { log.Fatalf("Invalid labeler config: %v", err) } if err := ValidateLabelsConfig(labelsConfig); err != nil { log.Fatalf("Invalid labels config: %v", err) } // Ensure all labels exist, optionally deleting others, with dryRun err = EnsureLabelsExist(client, *owner, *repo, labelsConfig, *deleteOtherLabels, *dryRun) if err != nil { log.Fatalf("Error ensuring labels exist: %v", err) } // Determine labels to apply based on changed files labelsToApply, err := DetermineLabels(labelerConfig, changedFiles) if err != nil { log.Fatalf("Error determining labels to apply: %v", err) } if len(labelsToApply) == 0 { fmt.Println("No labels to apply based on the changed files.") return } // Apply labels to an issue or pull request // For demonstration, assume applying to a specific issue ID passed via the `id` flag if *id == 0 { fmt.Println("Error: id (issue or PR ID) is required to apply labels.") flag.Usage() os.Exit(1) } // Apply labels using SetLabelsOnIssue err = client.SetLabelsOnIssue(*owner, *repo, *id, labelsToApply) if err != nil { log.Fatalf("Error applying labels: %v", err) } fmt.Printf("Labels applied successfully: %v\n", labelsToApply) case "sync": // New "sync" action // Sync labels based on labels.yaml, optionally deleting other labels // This action doesn't require changed-files or issue ID // Load configuration files labelsConfig, err := LoadLabelsConfig(*labelsConfigPath) if err != nil { log.Fatalf("Error loading labels config: %v", err) } // Validate labels configuration if err := ValidateLabelsConfig(labelsConfig); err != nil { log.Fatalf("Invalid labels config: %v", err) } // Ensure all labels exist, optionally deleting others, with dryRun err = EnsureLabelsExist(client, *owner, *repo, labelsConfig, *deleteOtherLabels, *dryRun) if err != nil { log.Fatalf("Error ensuring labels exist: %v", err) } if *dryRun { fmt.Println("Dry-run completed successfully. No changes were made.") } else { fmt.Println("Label synchronization completed successfully.") } default: fmt.Println("Error: Invalid action. Available actions: get, create, get-single, update, delete, apply, sync.") flag.Usage() os.Exit(1) } } // parseChangedFiles parses a comma-separated list of file paths into a slice. func parseChangedFiles(input string) []string { var files []string for _, file := range strings.Split(input, ",") { trimmed := filepath.Clean(strings.TrimSpace(file)) if trimmed != "" { files = append(files, trimmed) } } return files }