Goで怒りのコマンドラインツール作成

業務でgitを使っていると色々あって、時には.gitignoreをignoreしてリポジトリに全ファイルがpushされているなんて事態に出くわすこともあります。
諸々の事情により新しくリポジトリを作成し直すことができなかったため、気合のgit rm --cachedを繰り返そうとしたのですが。。。

f:id:t-miliya612:20180701232846p:plain (写真は別で準備したリポジトリです)

ちょっと手作業だと事故を起こしそうな数だったので、練習がてらgoでコマンド作ってみました。備忘録も兼ねてまとめていきます。

github.com

何してるの

中身はシンプルで、

  • 引数で.gitignoreへのpathを受け取る
  • .gitignoreファイルを1行ずつ読み込む
  • 1行ごとにgit rm --cachedを実行する

くらいしかしていません。

引数で.gitignoreへのpathを受け取る

pathは引数で受け取ることにしました。
flagパッケージを使いたくなるところですが、今回のpathはrequiredのため、optionにしちゃいけないなと。素直にos.Args()でいきます。

flag - The Go Programming Language

os - The Go Programming Language

引数の数と、ちゃんと.gitignoreを指定してるかどうかだけちゃんと見てあげています。

// validate arguments amount
if len(os.Args) != 2 {
    log.Fatal("1 argument should be given.")
}
// get argument
path := os.Args[1]

ignorePath := filepath.Clean(path)
if filepath.Base(ignorePath) != ".gitignore" {
    log.Fatal("arg should specify .gitignore file.")
}

.gitignoreファイルを1行ずつ読み込む

まずはファイルを開きます。deferで閉じてあげるのを忘れずに。

f, err := os.Open(ignorePath)
if err != nil {
    log.Fatal(err)
}
defer f.Close()

次に、ファイルの内容を読み込んであげます。bufio.NewScanner()でいきましょう。

bufio - The Go Programming Language

s := bufio.NewScanner(f)
for s.Scan() {
    ...
}
if s.Err() != nil {
    log.Fatal(s.Err())
}

1行ごとにgit rm --cachedを実行する

最後の段階です。前の章で読み取った1行分の.gitignoreを使ってコマンドを実行してあげたいのですが、そのまえにちょっと整形が必要です。
.gitignoreからコメントアウトされた行と、改行のみの行を省いてあげます。

str := strings.TrimSpace(s.Text())
if isLineValid(str) {
    // execute command
}
...

func isLineValid(line string) bool {
    if line == "" {
        return false
    }
    return !strings.HasPrefix(line, "#")
}

いよいよラスト、コマンドを実行してあげます。goでの外部コマンドの実行はexec.Command()を使用します。

exec - The Go Programming Language

err := exec.Command("git", "rm", "--cached", str).Run()
if err != nil {
    fmt.Println(err)
}

以上です。buildして実行してあげましょう。

exit status 128
exit status 128
exit status 128
exit status 128
exit status 128
exit status 128
exit status 128
exit status 128
exit status 128
exit status 128
exit status 128
REGNORE: 2 files are ignored.

大量のエラーメッセージを吐いてますが、これは既にignoreに追加済みのファイルに対してコマンドを実行した際のものです。ここのエラー処理はお好みに合わせて各位よろしくおねがいします。

おわりに

やっぱりGoはコマンド作るのめちゃくちゃ簡単ですね。。。最後のバイナリ生成の部分、サラッと流してますけど素晴らしいです。
今回はかなりやっつけで書いてますが、もうちょっと公開するに向けてREADMEやhelpオプションなどと整えていきたいです。