382 lines
9.6 KiB
Go
382 lines
9.6 KiB
Go
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)
|
|
}
|
|
}
|