LoginSignup
3

More than 5 years have passed since last update.

Golangで作るgitサブコマンド

Posted at

gitを使っていて,もう少し気の利いたサブコマンドがほしくなるときはありませんか? この記事では, Golangを使って簡単にgitのサブコマンドを作る方法をご紹介します.

目標とする状態

lbranch というgitのサブコマンドを作成します. lbranchgit lbranch という形で呼び出され, デフォルトでは5日以内にコミットのあったブランチの一覧を表示します.

また lbranch--days (何日前のコミットまで含めるか, デフォルトは5日), --through (branchの詳細な説明を表示するか否か, デフォルトはfalse) という2つのオプションをとることができます. lbranchのソースコードは以下.

gitがサブコマンドを認識する仕組み

gitは git-[サブコマンド名] という実行可能ファイルにパスが通っていれば,勝手にgitのサブコマンドとして認識してくれます.

そのため,今回であれば git-lbranch という実行可能ファイルにパスが通っていれば, git lbranch というgitのサブコマンドが叩ける用になるというわけです.

git-lbranchコマンドを作る

上の仕組みが分かれば,あとは git-[サブコマンド名] のCLIをGolangで書いて, $GOBIN 以下に置いてあげれば良いだけなので簡単. 以下に一応git-lbranchのソースコードも残しおてきますが, 普通にCLIをGolangで書いているだけです.

main.go
package main

import (
    "os"
)

func main() {
    cli := &CLI{outStream: os.Stdout, errStream: os.Stderr}
    os.Exit(cli.Run(os.Args))
}
cli.go

package main

import (
    "flag"
    "fmt"
    "io"

    "github.com/fatih/color"
)

const (
    ExitCodeOK = iota
    ExitCodeError
    ExitCodeParseFlagsError
)

type CLI struct {
    outStream, errStream io.Writer
}

func (cli *CLI) Run(args []string) int {
    var (
        days    int
        through bool
        version bool
    )

    flags := flag.NewFlagSet(Name, flag.ContinueOnError)
    flags.SetOutput(cli.errStream)
    flags.Usage = func() {
        fmt.Fprint(cli.errStream, usage)
    }

    flags.IntVar(&days, "days", 5, "")
    flags.IntVar(&days, "d", 5, "")

    flags.BoolVar(&through, "through", false, "")
    flags.BoolVar(&through, "t", false, "")

    flags.BoolVar(&version, "version", false, "")
    flags.BoolVar(&version, "v", false, "")

    if err := flags.Parse(args[1:]); err != nil {
        return ExitCodeParseFlagsError
    }

    if version {
        fmt.Fprint(cli.outStream, OutputVersion())
        return ExitCodeOK
    }

    lb := LBranch{outStream: cli.outStream, errStream: cli.errStream}
    err := lb.GetLatestBranches(days, through)

    if err != nil {
        red := color.New(color.FgRed).SprintFunc()
        fmt.Fprintln(cli.errStream, red("[error] "), err)
        return ExitCodeError
    }

    return ExitCodeOK
}

var usage = `Usage: git lbranch [options...]
lbranch is a git subcommand to show a list of recently committed branches.
OPTIONS:
  --days value, -d value  specifies the number of days branches last committed (default 5)
  --through, -t           print detailed explanation of branches, adding last commit hash and date
  --version, -v           print the current version
  --help, -h              print help

lbranch.go
package main

import (
    "fmt"
    "io"
    "time"

    "gopkg.in/src-d/go-git.v4"
    "gopkg.in/src-d/go-git.v4/plumbing"
)

type LBranch struct {
    outStream, errStream io.Writer
}

func (lb *LBranch) GetLatestBranches(days int, through bool) error {
    r, err := git.PlainOpen(".")
    if err != nil {
        return err
    }

    iterator, err := r.Branches()
    if err != nil {
        return err
    }

    err = iterator.ForEach(func(reference *plumbing.Reference) error {
        co, err := r.CommitObject(reference.Hash())
        if err != nil {
            return err
        }

        if co.Author.When.After(time.Now().AddDate(0, 0, -days)) {

            if through {
                // branch's ref is in the following form: refs/heads/branchName
                fmt.Fprint(lb.outStream, "  ")
                fmt.Fprint(lb.outStream, "[Name] ")
                fmt.Fprintln(lb.outStream, reference.Name()[11:])

                fmt.Fprint(lb.outStream, "  ")
                fmt.Fprint(lb.outStream, "[Hash] ")
                fmt.Fprintln(lb.outStream, reference.Hash())

                fmt.Fprint(lb.outStream, "  ")
                fmt.Fprint(lb.outStream, "[Date] ")
                fmt.Fprintln(lb.outStream, co.Author.When)
                fmt.Fprintln(lb.outStream, "")
                return nil
            }

            // branch's ref is in the following form: refs/heads/branchName
            fmt.Fprint(lb.outStream, "  ")
            fmt.Fprintln(lb.outStream, reference.Name()[11:])
        }

        return nil
    })

    if err != nil {
        return err
    }

    return nil
}

Golang製gitサブコマンドを配布する

goxzでクロスコンパイルし, ghrでGitHubにアップロードするのが良いと思います. また手前味噌ではありますが, 私が作ったghbrというCLIを使えば簡単にHomebrewからもgitサブコマンドをインストールできるようになります.

それぞれの組み合わせは git-lranchのdeploy.shにも記載していますので,そちらも合わせてご確認いただけますと幸いです.

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3