diff --git a/.gitignore b/.gitignore index 4b867a0..4ddd203 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,6 @@ # Dependency directories (remove the comment below to include it) vendor/ -.idea/ \ No newline at end of file +.idea/ +.vscode/ +.history/ diff --git a/README.md b/README.md index 58ca929..9118aaa 100644 --- a/README.md +++ b/README.md @@ -13,4 +13,6 @@ gs release tag | pull | 新建并拉取 go-spring 子项目 | | push | 将修改推送到 go-spring 子项目 | | remove | 本地移除 go-spring 子项目 | -| release | 本地所有子项目发布远程标签 | \ No newline at end of file +| release | 本地所有子项目发布远程标签 | +| repair | 修复远程项目的链接 | +| backup | 备份本地项目文件 | \ No newline at end of file diff --git a/cmd/gs/backup.go b/cmd/gs/backup.go new file mode 100644 index 0000000..5e22fa1 --- /dev/null +++ b/cmd/gs/backup.go @@ -0,0 +1,26 @@ +package gs + +import ( + "os" + + "github.com/go-spring/gs/internal" + "github.com/spf13/cobra" +) + +var backpupCmd = &cobra.Command{ + Use: "backup", + Aliases: []string{"bak"}, + Short: "backup a project", + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) { + rootDir, err := os.Getwd() + if err != nil { + panic(err) + } + internal.Zip(rootDir) + }, +} + +func init() { + rootCmd.AddCommand(backpupCmd) +} diff --git a/cmd/gs/pull.go b/cmd/gs/pull.go new file mode 100644 index 0000000..ef03e75 --- /dev/null +++ b/cmd/gs/pull.go @@ -0,0 +1,64 @@ +package gs + +import ( + "os" + + "github.com/go-spring/gs/internal" + "github.com/spf13/cobra" +) + +var pullCmd = &cobra.Command{ + Use: "pull spring-*/starter-* [branch]", + Aliases: []string{"pl"}, + Short: "pull remote code", + Args: cobra.ExactArgs(2), + Run: func(cmd *cobra.Command, args []string) { + projectName := args[0] + branch := args[1] + if branch == "" { + branch = "main" + } + rootDir, err := os.Getwd() + if err != nil { + panic(err) + } + + backup, err := cmd.Flags().GetBool("backup") + if err != nil { + panic("illegal backup value,must") + } + if backup { + internal.Zip(rootDir) + } + pull(rootDir, projectName, branch) + }, +} + +func init() { + rootCmd.AddCommand(pullCmd) +} + +// pull 拉取远程项目 +func pull(rootDir string, projectName string, branch string) { + _, dir, project := validProject(projectName) + internal.SafeStash(rootDir, func() { + remotes := internal.Remotes(rootDir) + if internal.ContainsString(remotes, project) < 0 { + add := false + defer func() { + if !add { + remove(rootDir, projectName) + } + }() + repository := internal.Add(rootDir, project, dir, branch) + projectXml.Add(internal.Project{ + Name: project, + Dir: dir, + Url: repository, + Branch: branch, + }) + add = true + } + internal.Sync(rootDir, project, dir, branch) + }) +} diff --git a/cmd/gs/push.go b/cmd/gs/push.go new file mode 100644 index 0000000..f5b450d --- /dev/null +++ b/cmd/gs/push.go @@ -0,0 +1,47 @@ +package gs + +import ( + "os" + + "github.com/go-spring/gs/internal" + "github.com/spf13/cobra" +) + +var pushCmd = &cobra.Command{ + Use: "push spring-*/starter-*", + Aliases: []string{"ps"}, + Short: "push local code to remote repo", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + projectName := args[0] + rootDir, err := os.Getwd() + if err != nil { + panic(err) + } + backup, err := cmd.Flags().GetBool("backup") + if err != nil { + panic("illegal backup value,must") + } + if backup { + internal.Zip(rootDir) + } + push(rootDir, projectName) + }, +} + +func init() { + rootCmd.AddCommand(pushCmd) +} + +// push 推送远程项目 +func push(rootDir string, projectName string) { + + _, dir, project := validProject(projectName) + internal.SafeStash(rootDir, func() { + + // 将修改提交到远程项目,不需要往回合并 + if p, ok := projectXml.Find(project); ok { + internal.Push(rootDir, project, dir, p.Branch) + } + }) +} diff --git a/cmd/gs/release.go b/cmd/gs/release.go new file mode 100644 index 0000000..55c0a0d --- /dev/null +++ b/cmd/gs/release.go @@ -0,0 +1,126 @@ +package gs + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + "time" + + "github.com/go-spring/gs/internal" + "github.com/spf13/cobra" +) + +var releaseCmd = &cobra.Command{ + Use: "release tag", + Aliases: []string{"rs"}, + Short: "release tag", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + tag := args[0] + rootDir, err := os.Getwd() + if err != nil { + panic(err) + } + backup, err := cmd.Flags().GetBool("backup") + if err != nil { + panic("illegal backup value,must") + } + if backup { + internal.Zip(rootDir) + } + release(rootDir, tag) + }, +} + +func init() { + rootCmd.AddCommand(releaseCmd) +} + +// release 发布所有远程项目 +func release(rootDir string, tag string) { + // tag := arg(2) + err := filepath.Walk(rootDir, func(walkFile string, _ os.FileInfo, err error) error { + + if err != nil { + return err + } + + if path.Base(walkFile) != "go.mod" { + return nil + } + + fmt.Println(walkFile) + fileData, e0 := ioutil.ReadFile(walkFile) + if e0 != nil { + return nil + } + + outBuf := bytes.NewBuffer(nil) + r := bufio.NewReader(strings.NewReader(string(fileData))) + for { + line, isPrefix, e1 := r.ReadLine() + if len(line) > 0 && e1 != nil { + panic(e1) + } + if isPrefix { + panic(errors.New("ReadLine returned prefix")) + } + if e1 != nil { + if e1 != io.EOF { + panic(err) + } + break + } + s := strings.TrimSpace(string(line)) + if strings.HasPrefix(s, "github.com/go-spring/spring-") || + strings.HasPrefix(s, "github.com/go-spring/starter-") { + index := strings.LastIndexByte(s, ' ') + if index <= 0 { + panic(errors.New(s)) + } + b := append(line[:index+2], []byte(tag)...) + outBuf.Write(b) + } else { + outBuf.Write(line) + } + outBuf.WriteString("\n") + } + + fmt.Println(outBuf.String()) + return ioutil.WriteFile(walkFile, outBuf.Bytes(), os.ModePerm) + }) + + if err != nil { + panic(err) + } + + // 提交代码更新 + internal.Commit(rootDir, "publish "+tag) + + // 遍历所有项目,推送远程更新 + for _, project := range projectXml.Projects { + _, dir, _ := validProject(project.Name) + internal.Push(rootDir, project.Name, dir, project.Branch) + } + + // 创建临时目录 + now := time.Now().Format("20060102150405") + buildDir := path.Join(rootDir, "..", "go-spring-build-"+now) + err = os.MkdirAll(buildDir, os.ModePerm) + if err != nil { + panic(err) + } + + // 遍历所有项目,推送远程标签 + for _, project := range projectXml.Projects { + projectDir := internal.Clone(buildDir, project.Name, project.Url) + internal.Release(projectDir, tag) + } +} diff --git a/cmd/gs/remove.go b/cmd/gs/remove.go new file mode 100644 index 0000000..3238a8f --- /dev/null +++ b/cmd/gs/remove.go @@ -0,0 +1,53 @@ +package gs + +import ( + "os" + "path" + + "github.com/go-spring/gs/internal" + "github.com/spf13/cobra" +) + +var removeCmd = &cobra.Command{ + Use: "remove spring-*/starter-*", + Aliases: []string{"pl"}, + Short: "pull remote code", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + projectName := args[0] + rootDir, err := os.Getwd() + if err != nil { + panic(err) + } + backup, err := cmd.Flags().GetBool("backup") + if err != nil { + panic("illegal backup value,must") + } + if backup { + internal.Zip(rootDir) + } + + remove(rootDir, projectName) + }, +} + +func init() { + rootCmd.AddCommand(removeCmd) +} + +// remove 移除远程项目 +func remove(rootDir string, projectName string) { + + _, dir, project := validProject(projectName) + internal.Remove(rootDir, project) + + projectDir := path.Join(rootDir, dir) + _ = os.RemoveAll(projectDir) + + if _, err := os.Stat(projectDir); !os.IsNotExist(err) { + panic(err) + } + + projectXml.Remove(project) + internal.Remotes(rootDir) +} diff --git a/cmd/gs/repair.go b/cmd/gs/repair.go new file mode 100644 index 0000000..eff7ea3 --- /dev/null +++ b/cmd/gs/repair.go @@ -0,0 +1,37 @@ +package gs + +import ( + "os" + + "github.com/go-spring/gs/internal" + "github.com/spf13/cobra" +) + +var repairCmd = &cobra.Command{ + Use: "repair spring-*/starter-* [branch]", + Aliases: []string{"rp"}, + Short: "repair project dir", + Args: cobra.ExactArgs(2), + Run: func(cmd *cobra.Command, args []string) { + projectName := args[0] + branch := args[1] + if branch == "" { + branch = "main" + } + rootDir, err := os.Getwd() + if err != nil { + panic(err) + } + repair(rootDir, projectName, branch) + }, +} + +func init() { + rootCmd.AddCommand(repairCmd) +} + +// repair 修复远程项目的链接 +func repair(rootDir string, projectName string, branch string) { + _, dir, project := validProject(projectName) + internal.Add(rootDir, project, dir, branch) +} diff --git a/cmd/gs/root.go b/cmd/gs/root.go new file mode 100644 index 0000000..6c191d2 --- /dev/null +++ b/cmd/gs/root.go @@ -0,0 +1,48 @@ +package gs + +import ( + "fmt" + "log" + "os" + "regexp" + "strings" + + "github.com/go-spring/gs/internal" + "github.com/spf13/cobra" +) + +// 配置文件 +var projectXml internal.ProjectXml +var springProject = regexp.MustCompile("spring-.*") +var starterProject = regexp.MustCompile("starter-.*") + +// validProject 项目名称是否有效,返回项目前缀、项目目录、项目名称 +func validProject(project string) (prefix string, dir string, _ string) { + if !springProject.MatchString(project) && !starterProject.MatchString(project) { + panic("error project " + project) + } + prefix = strings.Split(project, "-")[0] + return prefix, fmt.Sprintf("%s/%s", prefix, project), project +} + +var rootCmd = &cobra.Command{ + Use: "gs", + Short: "gs - a simple CLI to transform and inspect strings", + Long: `gs is a super fancy CLI (kidding) + +One can use gs to add or modfiy go spring project from the terminal`, + Run: func(cmd *cobra.Command, args []string) { + }, +} + +func init() { + log.SetFlags(log.Lshortfile) + rootCmd.PersistentFlags().BoolP("backup", "b", true, "backup project code.") +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintf(os.Stderr, "Whoops. There was an error while executing your CLI '%s'", err) + os.Exit(1) + } +} diff --git a/go.mod b/go.mod index 62e1263..a66933a 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/go-spring/gs -go 1.12 +go 1.16 + +require github.com/spf13/cobra v1.5.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0d85248 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/main.go b/main.go index 05d6cce..36d5bb4 100644 --- a/main.go +++ b/main.go @@ -1,282 +1,7 @@ package main -import ( - "bufio" - "bytes" - "errors" - "fmt" - "io" - "io/ioutil" - "log" - "os" - "path" - "path/filepath" - "regexp" - "strings" - "time" - - "github.com/go-spring/gs/internal" -) - -func init() { - log.SetFlags(log.Lshortfile) -} - -// help 展示命令行用法 -const help = `command: - gs pull spring-*/starter-* [branch] - gs repair spring-*/starter-* [branch] - gs push spring-*/starter-* - gs remove spring-*/starter-* - gs release tag` - -var springProject = regexp.MustCompile("spring-.*") -var starterProject = regexp.MustCompile("starter-.*") - -// validProject 项目名称是否有效,返回项目前缀、项目目录、项目名称 -func validProject(project string) (prefix string, dir string, _ string) { - if !springProject.MatchString(project) && !starterProject.MatchString(project) { - panic("error project " + project) - } - prefix = strings.Split(project, "-")[0] - return prefix, fmt.Sprintf("%s/%s", prefix, project), project -} - -type Command struct { - backup bool // 是否需要备份 - fn func(rootDir string) -} - -// commands 命令与处理函数的映射 -var commands = map[string]Command{ - "pull": {backup: true, fn: pull}, // 拉取单个远程项目 - "repair": {backup: false, fn: repair}, // 拉取单个远程项目 - "push": {backup: true, fn: push}, // 推送单个远程项目 - "remove": {backup: true, fn: remove}, // 移除单个远程项目 - "release": {backup: true, fn: release}, // 发布所有远程项目 - "backup": {backup: true, fn: nil}, // 备份本地项目文件 -} - -// arg 获取命令行参数 -func arg(index int) string { - if len(os.Args) > index { - return os.Args[index] - } - panic("not enough arg") -} - -// 配置文件 -var projectXml internal.ProjectXml +import "github.com/go-spring/gs/cmd/gs" func main() { - fmt.Println(help) - defer func() { fmt.Println() }() - - defer func() { - if r := recover(); r != nil { - log.Println(r) - os.Exit(-1) - } - }() - - command := arg(1) - cmd, ok := commands[command] - if !ok { - panic("error command " + command) - } - - // 获取工作目录 - rootDir, err := os.Getwd() - if err != nil { - panic(err) - } - - // 加载 project.xml 配置文件 - projectFile := path.Join(rootDir, "project.xml") - err = projectXml.Read(projectFile) - if err != nil { - panic(err) - } - - count := len(projectXml.Projects) - defer func() { - if count != len(projectXml.Projects) { - // 保存 project.xml 配置文件 - err = projectXml.Save(projectFile) - if err != nil { - panic(err) - } - } - }() - - fmt.Print(os.Args, " 输入 Yes 执行该命令: ") - input, _ := bufio.NewReader(os.Stdin).ReadString('\n') - if strings.TrimSpace(input) != "Yes" { - os.Exit(-1) - } - - // 备份本地文件 - if cmd.backup { - internal.Zip(rootDir) - } - - // 执行命令 - if cmd.fn != nil { - cmd.fn(rootDir) - } -} - -// pull 拉取远程项目 -func pull(rootDir string) { - - _, dir, project := validProject(arg(2)) - internal.SafeStash(rootDir, func() { - - branch := "main" - if len(os.Args) > 3 { - branch = os.Args[3] - } - - remotes := internal.Remotes(rootDir) - if internal.ContainsString(remotes, project) < 0 { - add := false - defer func() { - if !add { - remove(rootDir) - } - }() - repository := internal.Add(rootDir, project, dir, branch) - projectXml.Add(internal.Project{ - Name: project, - Dir: dir, - Url: repository, - Branch: branch, - }) - add = true - } - - internal.Sync(rootDir, project, dir, branch) - }) -} - -// repair 修复远程项目的链接 -func repair(rootDir string) { - branch := "main" - if len(os.Args) > 3 { - branch = os.Args[3] - } - _, dir, project := validProject(arg(2)) - internal.Add(rootDir, project, dir, branch) -} - -// push 推送远程项目 -func push(rootDir string) { - - _, dir, project := validProject(arg(2)) - internal.SafeStash(rootDir, func() { - - // 将修改提交到远程项目,不需要往回合并 - if p, ok := projectXml.Find(project); ok { - internal.Push(rootDir, project, dir, p.Branch) - } - }) -} - -// remove 移除远程项目 -func remove(rootDir string) { - - _, dir, project := validProject(arg(2)) - internal.Remove(rootDir, project) - - projectDir := path.Join(rootDir, dir) - _ = os.RemoveAll(projectDir) - - if _, err := os.Stat(projectDir); !os.IsNotExist(err) { - panic(err) - } - - projectXml.Remove(project) - internal.Remotes(rootDir) -} - -// release 发布所有远程项目 -func release(rootDir string) { - tag := arg(2) - err := filepath.Walk(rootDir, func(walkFile string, _ os.FileInfo, err error) error { - - if err != nil { - return err - } - - if path.Base(walkFile) != "go.mod" { - return nil - } - - fmt.Println(walkFile) - fileData, e0 := ioutil.ReadFile(walkFile) - if e0 != nil { - return nil - } - - outBuf := bytes.NewBuffer(nil) - r := bufio.NewReader(strings.NewReader(string(fileData))) - for { - line, isPrefix, e1 := r.ReadLine() - if len(line) > 0 && e1 != nil { - panic(e1) - } - if isPrefix { - panic(errors.New("ReadLine returned prefix")) - } - if e1 != nil { - if e1 != io.EOF { - panic(err) - } - break - } - s := strings.TrimSpace(string(line)) - if strings.HasPrefix(s, "github.com/go-spring/spring-") || - strings.HasPrefix(s, "github.com/go-spring/starter-") { - index := strings.LastIndexByte(s, ' ') - if index <= 0 { - panic(errors.New(s)) - } - b := append(line[:index+2], []byte(tag)...) - outBuf.Write(b) - } else { - outBuf.Write(line) - } - outBuf.WriteString("\n") - } - - fmt.Println(outBuf.String()) - return ioutil.WriteFile(walkFile, outBuf.Bytes(), os.ModePerm) - }) - - if err != nil { - panic(err) - } - - // 提交代码更新 - internal.Commit(rootDir, "publish "+tag) - - // 遍历所有项目,推送远程更新 - for _, project := range projectXml.Projects { - _, dir, _ := validProject(project.Name) - internal.Push(rootDir, project.Name, dir, project.Branch) - } - - // 创建临时目录 - now := time.Now().Format("20060102150405") - buildDir := path.Join(rootDir, "..", "go-spring-build-"+now) - err = os.MkdirAll(buildDir, os.ModePerm) - if err != nil { - panic(err) - } - - // 遍历所有项目,推送远程标签 - for _, project := range projectXml.Projects { - projectDir := internal.Clone(buildDir, project.Name, project.Url) - internal.Release(projectDir, tag) - } + gs.Execute() }