From ec313ab3c369f4265a446d2563c8ee1dbb5b021c Mon Sep 17 00:00:00 2001 From: Kevin Offet Date: Mon, 17 Apr 2023 05:19:25 -0400 Subject: [PATCH] +gitea bare repo cloning --- Makefile | 4 +- README.md | 17 ++- data-strings.go | 11 +- main.go | 310 ++++++++++++++++++++++++------------------- support_functions.go | 73 ++++++++++ 5 files changed, 274 insertions(+), 141 deletions(-) create mode 100644 support_functions.go diff --git a/Makefile b/Makefile index 6197397..7d341e5 100644 --- a/Makefile +++ b/Makefile @@ -57,9 +57,9 @@ clean: linkerflags = '-s -w' prep: clean +> @mkdir -p build > date +"%F %a %T %Z" > buildTime.txt > git describe --always --tags --dirty --long > buildVersion.txt -> @mkdir -p build build: prep linux windows @@ -80,7 +80,7 @@ linux-production: release: prep linux-release windows #------------------------------------------------------------- -# Install - same as build, but places binary on system path +# Install - same as build, but places linux binary on system path #------------------------------------------------------------- install: prep diff --git a/README.md b/README.md index 43e01e4..e3a95dc 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Use scaffold to setup a new project with a directory skeleton of your design, an ## Installation -1. Grab a version for your system from the releases page. +1. Grab a binary version for your system from the releases page. 2. Put it in a directory that is on your path. 3. Now configure your preferred setups/layouts/skeletons/templates for your projects. @@ -33,6 +33,20 @@ It's a little simpler at a command prompt -- type the command `set` and hit `Ent The examples directory contains an example-scaffold-projectType.toml configuration file. Place a copy in each projectType directory, adjusted to your preferences per the given project type. +## Naming convention warning + +Creating repositories on gitea via a ssh push has a side effect -- the project name is forced to lowercase. There are no configuration options to change this. Please see gitea and it's documentation for full explanations. + +Here are a couple of thoughts: + +1. Adapt and only create projects use lowercase (myspecialproject) or lowersnakecase (my_special_project) or use hyphens between words (my-special-project) + - all lowercase is universally accepted but hard to read for multi-word project names + - lowersnakecase is not universally accepted + - hyphens might not be what you are used to, or prefer, but it seems to be universally accepted and reasonably readable. + +2. A work around would be to manually create a reposity with the CamelCase name you want via gitea's web UI. And use scaffold with the -g flag + eg. `scaffold -g MySpecialProject go MySpecialProject` to have scaffold clone it, build it out according to your skeleton/templates, and then push the changes. + ## The `scaffold` command | Command | Description | @@ -40,6 +54,7 @@ The examples directory contains an example-scaffold-projectType.toml configurati | scaffold | The program name | | flags: -i or -Info | Display the available project types and the build and version information about the program. | +| -g GiteaProjectName | Clone a gitea repository, not create one. Then push the new structure to it. | | **Examples:** | | | scaffold *ProjectType* *NewProjectName* | Stuff | diff --git a/data-strings.go b/data-strings.go index 820ad3c..560f237 100644 --- a/data-strings.go +++ b/data-strings.go @@ -17,4 +17,13 @@ var cfg_content = `# Scaffold program configuration # Too funny - this config file doesn't seem to be needed ;-) # let's keep it for future use -- very silly -testkey = "test value"` +testkey = "test value" + +# Turn on/off testing for the existence of the ssh executable +# it's just an extra safe guard. +# Because it's required for the remote repo and gitea setups. +# If you turn it off, you can still +# control the remote repo and gitea setups +# via the create_remote_repo and setup_gitea options in scaffold-projectType.toml +test_for_ssh = true +` diff --git a/main.go b/main.go index ec05d4e..0730e15 100644 --- a/main.go +++ b/main.go @@ -32,6 +32,7 @@ type ( Remote_User string Remote_Location string Remote_Label string + Remote_Alt_Label string Commands [][]string Setup_gitea bool Gitea_server_account string // should be in ~/.ssh/config @@ -40,18 +41,23 @@ type ( Remote_Label_gitea string } ScaffoldSetup struct { - TestKey string + TestKey string + TestForSSH bool } ) var ( - infoFlag = flag.Bool("Info", false, "Displays the program build information and exits.") - infoFlag2 = flag.Bool("i", false, "Displays the program build information and exits.") - sep = "/" - cfg_dir = "devel/scaffold" // relative to , on linux /home/user/.config - cfg_filename = "scaffold-cfg.toml" - setup ScaffoldSetup - npsetup Setup + infoFlag = flag.Bool("i", false, "Displays the program build information and exits.") + cloneGiteaFlag = flag.Bool("c", false, "Clone from gitea. scaffold expects it to be an empty repository") + giteaRepoFlag = flag.String("g", "", "Specifies a gitea repository to clone from rather than create") + sep = "/" + cfg_dir = "devel/scaffold" // relative to , on linux /home/user/.config + cfg_filename = "scaffold-cfg.toml" + setup ScaffoldSetup + npsetup Setup + hasSSH = true + projType string + newProjectName string ) func main() { @@ -61,13 +67,15 @@ func main() { user_cfg_dir, err := os.UserConfigDir() ifFerr("Unable to determine user config directory", err) - if *infoFlag || *infoFlag2 { + if *infoFlag { fmt.Printf("Scaffold version: %s", buildVersion) fmt.Printf("Built: %s\n", buildTime) - if runtime.GOOS == "windows" { - fmt.Println("It's a windows system") - } + fmt.Printf("Running on a %s system.\n", runtime.GOOS) + fmt.Printf("Arg count: %+v\n", os.Args) + _ = isSSHAvailable() + + fmt.Printf("Gitea repo: %s\n", *giteaRepoFlag) at, err := availableProjectTypes(user_cfg_dir + sep + cfg_dir) ifFerr("Unable to get available project types", err) @@ -78,7 +86,7 @@ func main() { os.Exit(0) } - // Get setup + // Setup scaffold u, err := user.Current() ifFerr("Unable to get current user details", err) homeDir := u.HomeDir @@ -108,19 +116,34 @@ func main() { ifFerr("[Failed] to parse config file", err) } + if setup.TestForSSH { + hasSSH = isSSHAvailable() + } + pts, err := availableProjectTypes(scaffold_cfg_dir) ifFerr("Unable to read scaffold config directory contents", err) - // Handle request - pc := len(os.Args) - if pc != 3 { - fmt.Println(prog_help) - fmt.Printf("Available project types: %v\n", pts) - os.Exit(1) + // check command line + // two forms of command + if *cloneGiteaFlag { + if len(os.Args) != 4 { + leave(prog_help, pts) + } + projType = os.Args[2] + newProjectName = os.Args[3] + fmt.Printf("Not creating ... cloning %s project %s\n", projType, newProjectName) + } else { + if len(os.Args) != 3 { + leave(prog_help, pts) + } + projType = os.Args[1] + newProjectName = os.Args[2] } - projType := os.Args[1] - newProjectName := os.Args[2] + if !knownType(projType, pts) { + log.Fatalf("Unkown project type: %s\n", projType) + os.Exit(1) + } has := isConfigAvailable(pts, projType) if !has { @@ -135,8 +158,6 @@ func main() { _, err = toml.DecodeFile(projectCfgFile, &npsetup) ifFerr("Unable to read configuration for requested project type", err) - //fmt.Printf("Found commands: %+v\n", npsetup.Commands) - //Setup and Ready to begin // Permissions are octal (ugo - user, group, other) @@ -147,19 +168,37 @@ func main() { // make new project directory newProjDir := homeDir + sep + npsetup.Projects_basedir + sep + newProjectName - if _, err = os.Stat(newProjDir); os.IsExist(err) { - log.Fatalf("Project directory already exists -- %s\n", err) - } - err = os.MkdirAll(newProjDir, os.FileMode(inval)) - ifFerr("Unable to create new project directory ", err) - fmt.Printf("created new project directory: %s\n", newProjDir) + // Clone or create ?? + if *cloneGiteaFlag { + bd := homeDir + sep + npsetup.Projects_basedir + + _, err = exec.Command("mkdir", "-p", bd).Output() + ifFerr("Unable to create projects base directory", err) + fmt.Println("Made or found projects base directory") + + err = os.Chdir(bd) + ifFerr("Unable to change into projects basedir: "+bd, err) + + loc := "ssh://" + npsetup.Gitea_ssh_host_label + sep + npsetup.Gitea_user_account + sep + newProjectName + ".git" + _, err = exec.Command("git", "clone", loc).Output() + ifFerr("Unable to clone gitea repository at: "+loc, err) + fmt.Printf("Cloned repository from %s\n", loc) + + } else { + if _, err = os.Stat(newProjDir); os.IsExist(err) { + log.Fatalf("Project directory already exists -- %s\n", err) + } + + err = os.MkdirAll(newProjDir, os.FileMode(inval)) + ifFerr("Unable to create new project directory ", err) + fmt.Printf("created new project directory: %s\n", newProjDir) + } err = os.Chdir(newProjDir) ifFerr("Unable to change into new project directory to continue", err) // copy contents from projectType/sample - err = filepath.Walk(projectTypeDir+sep+"sample", func(path string, info os.FileInfo, err error) error { if err != nil { return err @@ -205,125 +244,122 @@ func main() { fmt.Printf("executing: %+v %+v\n", comm[0], args) } - // setup git - if npsetup.Setup_git { - // init repo - _, err = exec.Command("git", "init").Output() - ifFerr("Unable to initialize git repository", err) - - nifd, err := os.Create(".gitignore") - ifFerr("Unable to create .gitignore file", err) - defer nifd.Close() - - var gi_content string - var gi_in []byte = []byte("bin/") - - gisrc, err := os.Open("GITIGNORE") - if err == nil { // assume error means there is no source file - gi_in, err = io.ReadAll(gisrc) - if err != nil { - fmt.Println("Unable to read GITIGNORE, using default content") - } - gi_content = fmt.Sprintf("%s\n\n%s", newProjectName, string(gi_in)) - - _, err = nifd.WriteString(gi_content) - ifFerr("Unable to write .gitignore content", err) - fmt.Println("Wrote .gitignore content") - - // remove GITIGNORE - err = os.Remove("GITIGNORE") - if err != nil { - fmt.Println("Unable to remove GITIGNORE file") - } - } - defer gisrc.Close() - - // stage files - _, err = exec.Command("git", "add", ".").Output() - ifFerr("Unable to stage files prior to first commit", err) - - // first commit - _, err = exec.Command("git", "commit", "-m", "\"Initial Commit\"").Output() - ifFerr("Unable to make first commit, are your git config --global user.email and user.name set?", err) - fmt.Println("Made initial commit to local repository") - - if npsetup.Create_Remote_Repo { - // create remote repo - acomm := fmt.Sprintf("cd %s && git init --bare %s.git", npsetup.Remote_Location, newProjectName) - _, err = exec.Command("ssh", npsetup.Remote_User, acomm).Output() - ifFerr("Unable to create remote repository", err) - fmt.Println("Remote repository created") - - // add remote to local repo - new_remote_repo := npsetup.Remote_User + ":" + npsetup.Remote_Location + "/" + newProjectName + ".git" - _, err = exec.Command("git", "remote", "add", npsetup.Remote_Label, new_remote_repo).Output() - ifFerr("Unable to set git remote for new repo", err) - fmt.Println("Added remote repo to git as origin") - - // push initial commit - // be sure you have set - // git config --global init.defaultBranch main - _, err = exec.Command("git", "push", "origin", "main").Output() - ifFerr("Unable to push initial commit to origin", err) - fmt.Println("Pushed initial commit to remote") - - } - - if npsetup.Setup_gitea { - // create gitea repo - grcomm := fmt.Sprintf("%s:%s/%s.git", npsetup.Gitea_ssh_host_label, npsetup.Gitea_user_account, newProjectName) - _, err = exec.Command("git", "remote", "add", npsetup.Remote_Label_gitea, grcomm).Output() - ifFerr("Unable to set git remote for new project to gitea", err) - fmt.Println("Added remote repo to git for gitea as ", npsetup.Remote_Label_gitea) - - // -u sets upstream tracking - _, err = exec.Command("git", "push", "-u", npsetup.Remote_Label_gitea, "main").Output() - ifFerr("Unable to push new project to gitea", err) - fmt.Println("Pushed new project to gitea") - } + // setup git and gitea + if hasSSH && npsetup.Setup_git { + setupGit(newProjectName) } fmt.Printf("New %s Project: %s ready\n", projType, newProjectName) } -// if Fatal err -func ifFerr(msg string, err error) { - if err != nil { - log.Fatalf("%s -- %s\n", msg, err) - } -} - -func availableProjectTypes(adir string) ([]string, error) { - var res []string - - sf, err := os.Open(adir) - if err != nil { - return nil, err +func setupGit(anpn string) { + if !*cloneGiteaFlag { + // init repo + _, err := exec.Command("git", "init").Output() + ifFerr("Unable to initialize git repository", err) } - entries, err := sf.ReadDir(0) - if err != nil { - return nil, err - } + nifd, err := os.Create(".gitignore") + ifFerr("Unable to create .gitignore file", err) + defer nifd.Close() - for _, e := range entries { - if e.IsDir() { - if e.Name() == ".git" { - continue - } - res = append(res, e.Name()) + var gi_content string + var gi_in []byte = []byte("bin/") + + gisrc, err := os.Open("GITIGNORE") + if err == nil { // assume error means there is no source file + gi_in, err = io.ReadAll(gisrc) + if err != nil { + fmt.Println("Unable to read GITIGNORE, using default content") + } + gi_content = fmt.Sprintf("%s\n\n%s", anpn, string(gi_in)) + + _, err = nifd.WriteString(gi_content) + ifFerr("Unable to write .gitignore content", err) + fmt.Println("Wrote .gitignore content") + + // remove GITIGNORE + err = os.Remove("GITIGNORE") + if err != nil { + fmt.Println("Unable to remove GITIGNORE file") } } - return res, nil + defer gisrc.Close() + + // stage files + _, err = exec.Command("git", "add", ".").Output() + ifFerr("Unable to stage files prior to first commit", err) + + // first commit + _, err = exec.Command("git", "commit", "-m", "\"Initial Commit\"").Output() + ifFerr("Unable to make first commit, are your git config --global user.email and user.name set?", err) + fmt.Println("Made initial commit to local repository") + + if npsetup.Create_Remote_Repo { + // create remote repo + acomm := fmt.Sprintf("cd %s && git init --bare %s.git", npsetup.Remote_Location, anpn) + _, err = exec.Command("ssh", npsetup.Remote_User, acomm).Output() + ifFerr("Unable to create remote repository", err) + fmt.Println("Remote repository created") + + // add remote to local repo + new_remote_repo := npsetup.Remote_User + ":" + npsetup.Remote_Location + "/" + anpn + ".git" + var rlabel string + if *cloneGiteaFlag { + rlabel = npsetup.Remote_Alt_Label + } else { + rlabel = npsetup.Remote_Label + } + + _, err = exec.Command("git", "remote", "add", rlabel, new_remote_repo).Output() + ifFerr("Unable to set git remote for new repo", err) + fmt.Println("Added remote repo to git as " + rlabel) + + // push initial commit + // be sure you have set + // git config --global init.defaultBranch main + _, err = exec.Command("git", "push", rlabel, "main").Output() + ifFerr("Unable to push initial commit to "+rlabel, err) + fmt.Println("Pushed initial commit to remote") + } + + if npsetup.Setup_gitea { + setupGitea(anpn, *cloneGiteaFlag) + } } -func isConfigAvailable(types []string, aPType string) bool { - res := false - for _, v := range types { - if v == aPType { - res = true - break +func setupGitea(npn string, cloned bool) { + if !cloned { + // create gitea repo + grcomm := fmt.Sprintf("%s:%s/%s.git", npsetup.Gitea_ssh_host_label, npsetup.Gitea_user_account, npn) + _, err := exec.Command("git", "remote", "add", npsetup.Remote_Label_gitea, grcomm).Output() + ifFerr("Unable to set git remote for new project to gitea", err) + fmt.Println("Added remote repo to git for gitea as ", npsetup.Remote_Label_gitea) + } + var rlabel string + if *cloneGiteaFlag { + rlabel = "origin" + } else { + rlabel = npsetup.Remote_Label_gitea + } + + // -u sets upstream tracking + _, err := exec.Command("git", "push", "-u", rlabel, "main").Output() + ifFerr("Unable to push new project to gitea", err) + fmt.Println("Pushed new project to gitea") +} + +func leave(ph string, ptypes []string) { + fmt.Println(ph) + fmt.Printf("Available project types: %v\n", ptypes) + os.Exit(1) +} + +func knownType(at string, ptypes []string) bool { + for _, v := range ptypes { + if v == at { + return true } } - return res + return false } diff --git a/support_functions.go b/support_functions.go new file mode 100644 index 0000000..5c33489 --- /dev/null +++ b/support_functions.go @@ -0,0 +1,73 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + "runtime" +) + +// if Fatal err +func ifFerr(msg string, err error) { + if err != nil { + log.Fatalf("%s -- %s\n", msg, err) + } +} + +func availableProjectTypes(adir string) ([]string, error) { + var res []string + + sf, err := os.Open(adir) + if err != nil { + return nil, err + } + + entries, err := sf.ReadDir(0) + if err != nil { + return nil, err + } + + for _, e := range entries { + if e.IsDir() { + if e.Name() == ".git" { + continue + } + res = append(res, e.Name()) + } + } + return res, nil +} + +func isConfigAvailable(types []string, aPType string) bool { + res := false + for _, v := range types { + if v == aPType { + res = true + break + } + } + return res +} + +func isSSHAvailable() bool { + //rv := false + //var res string + var resb, resb2 []byte + var err error + system := runtime.GOOS + switch system { + case "linux": + resb, err = exec.Command("which", "ssh").Output() + ifFerr("Unable to test for ssh on linux", err) + resb2, _ = exec.Command("which", "ssh3").Output() + case "window": + resb, err = exec.Command("where", "ssh").Output() + ifFerr("Unable to test for ssh on windows", err) + resb2, _ = exec.Command("which", "ssh3").Output() + } + fmt.Println("ssh is at: ", string(resb)) + fmt.Println("ssh - len resb: ", len(resb)) + fmt.Println("ssh3 - len resb2: ", len(resb2)) + return len(resb) != 0 +}