223 lines
6.9 KiB
Go
223 lines
6.9 KiB
Go
package cmd
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"kmanage/pkg/git"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var gitInitRepoCmd = &cobra.Command{
|
|
Use: "init-repo [name]",
|
|
Aliases: []string{"init", "create", "new"},
|
|
Short: "Create a remote Git repository and push the current project directory",
|
|
Long: `Creates a new remote repository on your configured Git provider (Gitea / Forgejo / GitHub)
|
|
for the current working directory, initializes Git locally if needed, creates an initial commit,
|
|
and pushes the branch to remote origin.
|
|
|
|
Examples:
|
|
# Create repo with current directory name and push:
|
|
kmanage git init-repo
|
|
|
|
# Create repo with custom name under an organization:
|
|
kmanage git init-repo my-service --org Docker
|
|
|
|
# Create private repo on GitHub:
|
|
kmanage git init-repo --account github --private
|
|
|
|
# Custom branch and commit message:
|
|
kmanage git init-repo -b main -m "Initial commit created by kmanage"`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get current working directory: %w", err)
|
|
}
|
|
|
|
repoName := filepath.Base(cwd)
|
|
if len(args) > 0 && strings.TrimSpace(args[0]) != "" {
|
|
repoName = strings.TrimSpace(args[0])
|
|
}
|
|
|
|
accountFlag, _ := cmd.Flags().GetString("account")
|
|
orgFlag, _ := cmd.Flags().GetString("org")
|
|
ownerFlag, _ := cmd.Flags().GetString("owner")
|
|
if orgFlag == "" && ownerFlag != "" {
|
|
orgFlag = ownerFlag
|
|
}
|
|
|
|
privateFlag, _ := cmd.Flags().GetBool("private")
|
|
descFlag, _ := cmd.Flags().GetString("description")
|
|
branchFlag, _ := cmd.Flags().GetString("branch")
|
|
if branchFlag == "" {
|
|
branchFlag = "main"
|
|
}
|
|
commitMsg, _ := cmd.Flags().GetString("message")
|
|
if commitMsg == "" {
|
|
commitMsg = "Initial commit created by kmanage"
|
|
}
|
|
useHTTPS, _ := cmd.Flags().GetBool("https")
|
|
|
|
// 1. Verify Git CLI is available
|
|
if _, err := exec.LookPath("git"); err != nil {
|
|
return fmt.Errorf("git binary not found in PATH. Please install git")
|
|
}
|
|
|
|
// 2. Resolve Git client
|
|
client, err := getGitClientForAccount(accountFlag)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
targetOwner := orgFlag
|
|
if targetOwner == "" {
|
|
targetOwner = client.AccountName
|
|
}
|
|
|
|
fmt.Printf("🔨 Creating remote repository '%s' on %s (%s)...\n", repoName, client.AccountName, client.BaseURL)
|
|
|
|
createdRepo, err := client.CreateRepository(git.CreateRepoOptions{
|
|
Name: repoName,
|
|
Description: descFlag,
|
|
Private: privateFlag,
|
|
AutoInit: false,
|
|
DefaultBranch: branchFlag,
|
|
Org: orgFlag,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create remote repository: %w", err)
|
|
}
|
|
|
|
// 3. Determine remote URL (prefer SSH unless HTTPS flag is specified)
|
|
remoteURL := createdRepo.SSHURL
|
|
if useHTTPS || remoteURL == "" {
|
|
remoteURL = createdRepo.CloneURL
|
|
}
|
|
if remoteURL == "" {
|
|
remoteURL = createdRepo.HTMLURL
|
|
}
|
|
|
|
// 4. Initialize local git repository if not already initialized
|
|
fmt.Printf("📦 Setting up local git repository in %s...\n", cwd)
|
|
if !isGitRepo(cwd) {
|
|
if err := runGitCmd(cwd, "init"); err != nil {
|
|
return fmt.Errorf("failed to initialize git repository: %w", err)
|
|
}
|
|
}
|
|
|
|
// 5. Ensure desired branch name (e.g. main)
|
|
_ = runGitCmd(cwd, "branch", "-M", branchFlag)
|
|
|
|
// 6. Configure remote origin
|
|
if hasRemote(cwd, "origin") {
|
|
if err := runGitCmd(cwd, "remote", "set-url", "origin", remoteURL); err != nil {
|
|
return fmt.Errorf("failed to update remote origin: %w", err)
|
|
}
|
|
} else {
|
|
if err := runGitCmd(cwd, "remote", "add", "origin", remoteURL); err != nil {
|
|
return fmt.Errorf("failed to add remote origin: %w", err)
|
|
}
|
|
}
|
|
|
|
// 7. Stage all files
|
|
fmt.Println("📝 Staging files...")
|
|
if err := runGitCmd(cwd, "add", "-A"); err != nil {
|
|
return fmt.Errorf("failed to stage files: %w", err)
|
|
}
|
|
|
|
// 8. Create commit if needed
|
|
if hasUncommittedChanges(cwd) {
|
|
fmt.Printf("📝 Creating commit (\"%s\")...\n", commitMsg)
|
|
if err := runGitCmd(cwd, "commit", "-m", commitMsg); err != nil {
|
|
return fmt.Errorf("failed to commit: %w", err)
|
|
}
|
|
} else if !hasAnyCommits(cwd) {
|
|
fmt.Printf("📝 Creating initial commit (\"%s\")...\n", commitMsg)
|
|
if err := runGitCmd(cwd, "commit", "--allow-empty", "-m", commitMsg); err != nil {
|
|
return fmt.Errorf("failed to commit: %w", err)
|
|
}
|
|
}
|
|
|
|
// 9. Push to remote origin
|
|
fmt.Printf("🚀 Pushing branch '%s' to remote origin...\n", branchFlag)
|
|
if err := runGitCmd(cwd, "push", "-u", "origin", branchFlag); err != nil {
|
|
return fmt.Errorf("failed to push to remote repository (%s): %w\nMake sure your SSH key or Git credentials are configured for remote access", remoteURL, err)
|
|
}
|
|
|
|
fmt.Println("\n✅ Repository successfully created and published!")
|
|
if createdRepo.HTMLURL != "" {
|
|
fmt.Printf(" 🔗 Web URL: %s\n", createdRepo.HTMLURL)
|
|
}
|
|
fmt.Printf(" 📡 Remote: %s\n", remoteURL)
|
|
fmt.Printf(" 🌿 Branch: %s\n", branchFlag)
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
func isGitRepo(dir string) bool {
|
|
cmd := exec.Command("git", "rev-parse", "--is-inside-work-tree")
|
|
cmd.Dir = dir
|
|
return cmd.Run() == nil
|
|
}
|
|
|
|
func hasRemote(dir, remoteName string) bool {
|
|
cmd := exec.Command("git", "remote", "get-url", remoteName)
|
|
cmd.Dir = dir
|
|
return cmd.Run() == nil
|
|
}
|
|
|
|
func hasUncommittedChanges(dir string) bool {
|
|
cmd := exec.Command("git", "status", "--porcelain")
|
|
cmd.Dir = dir
|
|
var out bytes.Buffer
|
|
cmd.Stdout = &out
|
|
if err := cmd.Run(); err != nil {
|
|
return false
|
|
}
|
|
return strings.TrimSpace(out.String()) != ""
|
|
}
|
|
|
|
func hasAnyCommits(dir string) bool {
|
|
cmd := exec.Command("git", "rev-parse", "--verify", "HEAD")
|
|
cmd.Dir = dir
|
|
return cmd.Run() == nil
|
|
}
|
|
|
|
func runGitCmd(dir string, args ...string) error {
|
|
cmd := exec.Command("git", args...)
|
|
cmd.Dir = dir
|
|
var stderr bytes.Buffer
|
|
cmd.Stderr = &stderr
|
|
if err := cmd.Run(); err != nil {
|
|
errStr := strings.TrimSpace(stderr.String())
|
|
if errStr != "" {
|
|
return fmt.Errorf("%s", errStr)
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func init() {
|
|
gitCmd.AddCommand(gitInitRepoCmd)
|
|
|
|
gitInitRepoCmd.Flags().StringP("account", "A", "", "Git account to create the repository on (default: active default account)")
|
|
gitInitRepoCmd.Flags().StringP("org", "o", "", "Organization or owner under which to create the repository")
|
|
gitInitRepoCmd.Flags().String("owner", "", "Alias for --org")
|
|
gitInitRepoCmd.Flags().BoolP("private", "p", false, "Make the repository private")
|
|
gitInitRepoCmd.Flags().StringP("description", "d", "", "Repository description")
|
|
gitInitRepoCmd.Flags().StringP("branch", "b", "main", "Initial/default branch name")
|
|
gitInitRepoCmd.Flags().StringP("message", "m", "Initial commit created by kmanage", "Commit message for the initial commit")
|
|
gitInitRepoCmd.Flags().Bool("https", false, "Use HTTPS clone URL instead of SSH for git remote")
|
|
|
|
_ = gitInitRepoCmd.RegisterFlagCompletionFunc("account", completeGitAccountNames)
|
|
_ = gitInitRepoCmd.RegisterFlagCompletionFunc("org", completeOwnerNames)
|
|
_ = gitInitRepoCmd.RegisterFlagCompletionFunc("owner", completeOwnerNames)
|
|
}
|