đŸ’¡Original japanese post is here.
https://zenn.dev/tttol/articles/c7dfc74d27e45d
Introduction
I implemented a CLI command in Go and published it on GitHub.
https://github.com/tttol/mergectl
It's called mergectl
. You can use mergectl
by downloading the binary with curl and saving it to /usr/local/bin. In this article, I’ll document the journey of implementing, publishing, and distributing a CLI command in Go as a reference.
Explanation of mergectl
's Functionality and Development Background
Before diving into the main topic, let me explain the functionality and development background of mergectl
.
Functionality
mergectl
is a command to merge multiple git branches at once.
For example, let's assume we have the following branch structure.
In my work, we often develop multiple versions in parallel, cutting the v1 branch from the main branch and the v2 branch from the v1 branch.
Any changes pushed to v1 need to be reflected in v2 as well. This means that we need to regularly merge v1 into v2. While this is a simple task of executing git merge remotes/origin/v1
on the v2 branch, the number of commits pushed to v1 and v2 by the development team makes manual merging cumbersome.
With mergectl
, you can merge v1 into v2 by simply running mergectl exec v1 v2
in the directory where the git repository is cloned.
Similarly, if there are changes in the main branch due to a hotfix, you can reflect those changes in the v1 and v2 branches by running mergectl exec main v1 v2
.
mergectl exec [source branch] [target branch 1] [target branch 2] [target branch 3] ...
Development Background
mergectl
was originally a logic that I ran with a shell script. I had set up the following shell script to run regularly on my PC.
# merge main into v1
git checkout v1
git pull
git merge remotes/origin/main --no-ff
git push
# merge v1 into v2
git checkout v2
git pull
git merge remotes/origin/v1 --no-ff
git push
While this shell script was functioning well, it became cumbersome to change branch names within the script whenever a new version like v3 or v4 was introduced. Manually updating the script posed a risk of mixing up source and target branches.
This is where the idea of creating a CLI command came from, to simplify these tasks.
Library for CLI Command Development - Cobra
Now, let’s get into the main topic.
One way to implement a CLI command in Go is to use a library called Cobra.
https://github.com/spf13/cobra
Cobra is a library that supports and accelerates the implementation of CLI applications. For detailed usage, please refer to the documentation.
Since Cobra is implemented in Go, you can easily install it with the following command:
go get -u github.com/spf13/cobra@latest
With Cobra, you can easily implement the mergectl
command as follows:
func main() {
cmd.Execute()
}
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "mergectl",
Short: "USAGE: mergectl [source branch] [target branch]",
Long: `mergectl is a tool of "git merge". This command merges multiple git branches.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Hello mergectl!")
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
$ mergectl
Hello mergectl!
With this, we have created a CLI command that returns "Hello mergectl!" when executed.
Additionally, you can implement subcommands as well. By implementing the following version.go
, running mergectl version
will return the version of the CLI.
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var Version string
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of mergectl",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("mergectl version:", Version)
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}
$ mergectl version
1.0.0-rc
You can add the subcommand defined with var versionCmd
to the root with rootCmd.AddCommand(versionCmd)
. How the value is assigned to var Version string
is explained in the next chapter.
Managing Application Version
The version value is assigned to var Version string
in version.go
. The assignment is specified during the build process with the following command:
go build -ldflags "-X main.version=1.0.0"
You can receive the value of main.version
in main.go
as var version
. Once received in main.go
, you can pass this value to var Version string
in version.go
and display it appropriately.
Library for Release
Next, I’ll explain the release process.
GoReleaser
The created CLI command is distributed as a binary file. For creating the binary, we use GoReleaser.
For detailed usage, please refer to the documentation.
GoReleaser manages release settings with a .goreleaser.yml file. You can automatically generate this yml file by running goreleaser init
.
In my case, I used the generated yml file with minimal modifications. The parts I modified are as follows:
Changes in .goreleaser.yml
builds:
- ldflags:
- -s -w
- -X main.version={{ .Version }}
# Omitted
flags:
- -trimpath
-
-ldflags "-X main.version={{ .Version }}"
- This option sets the application version as explained earlier.
-
-s
- Strips debug information.
-
-w
- Strips debug information.
Reference Link
https://qiita.com/ssc-ynakamura/items/da37856f7f217d708a07
Conclusion
The mergectl
I created is available as OSS, so feel free to use it according to the license.
Top comments (0)