package main import ( _ "embed" "fmt" "html" "os" "os/exec" "path/filepath" "runtime" "strings" "time" "github.com/spf13/cobra" "gopkg.in/yaml.v3" ) // Embedded assets // //go:embed ../../licenses/MIT.txt var mitLicense string //go:embed ../../licenses/GPL-3.0.txt var gplLicense string //go:embed ../../licenses/AGPL-3.0.txt var agplLicense string //go:embed ../../licenses/Unlicense.txt var unlicense string //go:embed ../../.gitignore var defaultGitignore string type LicenseType string const ( LicenseMIT LicenseType = "MIT" LicenseGPLv3 LicenseType = "GPLv3" LicenseAGPLv3 LicenseType = "AGPLv3" LicenseUnlicense LicenseType = "Unlicense" ) type DeployType string const ( DeployDocker DeployType = "docker" DeployPyPI DeployType = "pypi" DeployCargo DeployType = "cargo" DeployGo DeployType = "go" ) type Config struct { Owner string `yaml:"owner"` Name string `yaml:"name"` License string `yaml:"license"` DevelopBranch string `yaml:"develop_branch"` DefaultGitignore bool `yaml:"default_gitignore"` DefaultGitURL string `yaml:"default_git_url"` } func defaultConfig() *Config { return &Config{ Owner: "", Name: "", License: "MIT", DevelopBranch: "dev", DefaultGitignore: true, DefaultGitURL: "https://git.theprivateserver.de/{owner}/{repo}.git", } } func getConfigPath() string { var home string if runtime.GOOS == "windows" { home = os.Getenv("USERPROFILE") } else { home = os.Getenv("HOME") } return filepath.Join(home, ".config", "GMS", ".config.yaml") } func loadConfig() (*Config, error) { configPath := getConfigPath() data, err := os.ReadFile(configPath) if err != nil { // Create default config config := defaultConfig() // Create config directory dir := filepath.Dir(configPath) if err := os.MkdirAll(dir, 0755); err != nil { return nil, err } // Write default config yamlData, err := yaml.Marshal(config) if err != nil { return nil, err } if err := os.WriteFile(configPath, yamlData, 0644); err != nil { return nil, err } fmt.Printf("Created default config at: %s\n", configPath) return config, nil } config := &Config{} if err := yaml.Unmarshal(data, config); err != nil { return nil, err } return config, nil } func runGitCommand(args []string, check bool) (string, error) { cmd := exec.Command("git", args...) output, err := cmd.CombinedOutput() if err != nil && check { return "", fmt.Errorf("git command failed: %v", err) } return strings.TrimSpace(string(output)), nil } func isGitRepo() bool { _, err := runGitCommand([]string{"rev-parse", "--git-dir"}, false) return err == nil } func getGitUserName() string { name, _ := runGitCommand([]string{"config", "user.name"}, false) return name } func decodeHTMLEntities(text string) string { return html.UnescapeString(text) } func getLicenseContent(licenseType LicenseType, owner string) string { var licenseText string switch licenseType { case LicenseMIT: licenseText = mitLicense case LicenseGPLv3: licenseText = gplLicense case LicenseAGPLv3: licenseText = agplLicense case LicenseUnlicense: licenseText = unlicense default: licenseText = mitLicense } licenseText = decodeHTMLEntities(licenseText) // Get fullname from git config or use owner fullname := getGitUserName() if fullname == "" { fullname = owner } currentYear := fmt.Sprintf("%d", time.Now().Year()) // Substitute placeholders licenseText = strings.ReplaceAll(licenseText, "{year}", currentYear) licenseText = strings.ReplaceAll(licenseText, "{fullname}", fullname) return licenseText } func removeAllRemotes() { remotes, err := runGitCommand([]string{"remote"}, false) if err != nil { return } for _, remote := range strings.Split(remotes, "\n") { remote = strings.TrimSpace(remote) if remote != "" { runGitCommand([]string{"remote", "remove", remote}, false) } } } func setupGitRepo(config *Config, forceLicense bool) error { // Initialize repo if needed if !isGitRepo() { if _, err := runGitCommand([]string{"init"}, true); err != nil { return err } fmt.Println("Initialized new git repository") } // Ensure main branch exists branches, _ := runGitCommand([]string{"branch", "--list"}, false) if !strings.Contains(branches, "main") { if strings.TrimSpace(branches) == "" { runGitCommand([]string{"checkout", "-b", "main"}, true) } else { runGitCommand([]string{"branch", "-M", "main"}, true) } } else { runGitCommand([]string{"checkout", "main"}, false) } // Set up remote remoteURL := strings.ReplaceAll(config.DefaultGitURL, "{owner}", config.Owner) remoteURL = strings.ReplaceAll(remoteURL, "{repo}", config.Name) // Remove all existing remotes and add new origin removeAllRemotes() if _, err := runGitCommand([]string{"remote", "add", "origin", remoteURL}, true); err != nil { return err } fmt.Printf("Set remote 'origin' to: %s\n", remoteURL) // Add LICENSE if missing or forced licensePath := "LICENSE" if _, err := os.Stat(licensePath); os.IsNotExist(err) || forceLicense { var licenseType LicenseType switch config.License { case "GPLv3": licenseType = LicenseGPLv3 case "AGPLv3": licenseType = LicenseAGPLv3 case "Unlicense": licenseType = LicenseUnlicense default: licenseType = LicenseMIT } licenseContent := getLicenseContent(licenseType, config.Owner) if err := os.WriteFile(licensePath, []byte(licenseContent), 0644); err != nil { return err } fmt.Printf("Added LICENSE: %s\n", config.License) } // Add/overwrite .gitignore if enabled if config.DefaultGitignore { if err := os.WriteFile(".gitignore", []byte(defaultGitignore), 0644); err != nil { return err } fmt.Println("Added/updated .gitignore") } // Stage changes runGitCommand([]string{"add", "."}, true) // Commit if there are staged changes status, _ := runGitCommand([]string{"status", "--porcelain"}, false) if status != "" { if _, err := runGitCommand([]string{"commit", "-m", "Initial commit"}, true); err != nil { return err } fmt.Println("Created initial commit") } // Create and checkout dev branch devBranch := config.DevelopBranch runGitCommand([]string{"checkout", "-b", devBranch}, false) fmt.Printf("Created and switched to branch: %s\n", devBranch) // Try to push to remote _, err1 := runGitCommand([]string{"push", "-u", "origin", "main"}, false) _, err2 := runGitCommand([]string{"push", "-u", "origin", devBranch}, false) if err1 == nil && err2 == nil { fmt.Println("Pushed to remote successfully") } else { fmt.Println("⚠️ Warning: Could not push to remote. Network may be unavailable or remote not accessible.") fmt.Println(" Repository configured locally. Push manually when ready.") } return nil } func setupGiteaWorkflows(deployType DeployType) { giteaDir := ".gitea" workflowsDir := filepath.Join(giteaDir, "workflows") // Create directories os.MkdirAll(workflowsDir, 0755) // TODO: Copy/create workflow files based on deployType fmt.Printf("Set up Gitea workflows for: %s\n", deployType) fmt.Println("⚠️ Note: Ensure required secrets are configured in Gitea user/org settings:") fmt.Println(" - GITEA_TOKEN, TOKEN, REGISTRY, DOCKER_USERNAME") if deployType == DeployDocker { fmt.Println("⚠️ Warning: Docker workflows require a Dockerfile in the repository root.") } } var ( owner string name string license string developBranch string defaultGitignore *bool defaultGitURL string force bool deployType string ) var rootCmd = &cobra.Command{ Use: "gitreposetup", Short: "Initialize and configure git repositories with licenses and workflows", Run: func(cmd *cobra.Command, args []string) { // Load config config, err := loadConfig() if err != nil { fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err) os.Exit(1) } // Override config with CLI arguments if owner != "" { config.Owner = owner } if name != "" { config.Name = name } if license != "" { config.License = license } if developBranch != "" { config.DevelopBranch = developBranch } if defaultGitignore != nil { config.DefaultGitignore = *defaultGitignore } if defaultGitURL != "" { config.DefaultGitURL = defaultGitURL } // Validate required fields if config.Owner == "" || config.Name == "" { fmt.Fprintln(os.Stderr, "Error: --owner and --name are required (or set in config file)") os.Exit(1) } // Setup git repository if err := setupGitRepo(config, force); err != nil { fmt.Fprintf(os.Stderr, "Error setting up git repository: %v\n", err) os.Exit(1) } // Setup workflows if requested if deployType != "" { setupGiteaWorkflows(DeployType(deployType)) } fmt.Println("\n✅ Repository setup complete!") }, } func init() { rootCmd.Flags().StringVar(&owner, "owner", "", "Repository owner/organization name") rootCmd.Flags().StringVar(&name, "name", "", "Repository name") rootCmd.Flags().StringVar(&license, "license", "", "License type (MIT, GPLv3, AGPLv3, Unlicense)") rootCmd.Flags().StringVar(&developBranch, "develop-branch-name", "", "Development branch name (default: dev)") defaultGitignore = rootCmd.Flags().Bool("default-gitignore", true, "Use default .gitignore") rootCmd.Flags().StringVar(&defaultGitURL, "default-git-url", "", "Git remote URL template") rootCmd.Flags().BoolVar(&force, "force", false, "Force overwrite existing LICENSE") rootCmd.Flags().StringVar(&deployType, "deploy-type", "", "Deployment type for Gitea workflows (docker, pypi, cargo, go)") } func main() { if err := rootCmd.Execute(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }