関連記事
GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ
概要
以下、自分用のメモです。
たまにgit管理されているファイルのN世代前を別名で出力したいってときがあったります。
普通にコマンド実行すれば
$ git show コミットのSHA:リポジトリルートからのパス
で出力できます。リポジトリルートからのパスとなるので
カレントディレクトリを対象ファイルの場所まで移動している場合は
git rev-parse --show-prefix などでリポジトリルートからのパスを取得してから実行する必要があります。
で、手習いがてらにGoでそのあたりの処理をするサンプルつくってみました。
Goで作らないといけない理由なんて特にないですが(シェルスクリプトとかPythonで書いた方が楽w)
サンプル
main.go
package main import ( "flag" "fmt" "io" "log" "os" "path/filepath" "strings" ) type ( AppArgs struct { NumLogCount int File string } ) var ( appArgs AppArgs out io.Writer ) func init() { flag.IntVar(&appArgs.NumLogCount, "n", 1, "何世代前の版を取得するかの値") flag.StringVar(&appArgs.File, "f", "", "ファイル") } func main() { log.SetFlags(0) flag.Parse() if appArgs.File == "" { log.Fatal("invalid argument: file") } out = os.Stdout if err := run(); err != nil { log.Fatal(err) } } func run() error { // 1.パス文字が含まれていない場合、Prefixを取得してパス作る (git rev-parse --show-prefix) // 含んでいる場合、ユーザが明示的に指定しているとみなしPrefixは付けない // 2.SHA取得 (git log --pretty=format:"%h" -世代数 ファイル名) // 3.ファイル生成 (git show SHA:パス) // // MEMO: // git log と git show では渡すパスの形が異なる // - git log は カレントディレクトリからの相対パスを受け付ける仕様 // - git show は リポジトリルートからのパスを受け付ける仕様 var ( git = new(GitCmd) fpath = appArgs.File err error ) if !strings.Contains(fpath, "/") { var ( prefix string ) if prefix, err = git.Prefix(); err != nil { return err } fpath = filepath.Join(prefix, fpath) } var ( sha string ) if sha, err = git.Sha(appArgs.File, appArgs.NumLogCount); err != nil { return err } if sha == "" { return fmt.Errorf("SHA取得失敗: %s", appArgs.File) } var ( r io.ReadCloser ) if r, err = git.Show(sha, fpath); err != nil { return err } defer r.Close() if _, err = io.Copy(out, r); err != nil { return err } return nil }
reader.go
package main import ( "io" "os/exec" ) type ( CmdReader struct { pipe io.ReadCloser cmd *exec.Cmd } ) var _ io.ReadCloser = (*CmdReader)(nil) func (me *CmdReader) Read(p []byte) (int, error) { return me.pipe.Read(p) } func (me *CmdReader) Close() error { var ( errPipe = me.pipe.Close() errWait = me.cmd.Wait() ) if errPipe != nil { return errPipe } if errWait != nil { return errWait } return nil }
git.go
package main import ( "bufio" "bytes" "fmt" "io" "os/exec" "strings" ) type ( GitCmd struct{} ) func (me *GitCmd) exec(args []string) (*CmdReader, error) { var ( cmd = exec.Command("git", args...) stdout io.ReadCloser err error ) if stdout, err = cmd.StdoutPipe(); err != nil { return nil, err } if err = cmd.Start(); err != nil { return nil, err } return &CmdReader{stdout, cmd}, nil } // Prefix は、git rev-parse --show-prefix を実行します。 func (me *GitCmd) Prefix() (string, error) { var ( args = []string{"rev-parse", "--show-prefix"} reader *CmdReader err error ) if reader, err = me.exec(args); err != nil { return "", err } defer reader.Close() var ( buf = new(bytes.Buffer) ) if _, err = io.Copy(buf, reader); err != nil { return "", err } return strings.ReplaceAll(buf.String(), "\n", ""), nil } // Sha は、 git log --pretty=format:"%h" を実行しcount番目のSHAを返します。 func (me *GitCmd) Sha(fpath string, count int) (string, error) { var ( args = []string{"log", "--pretty=format:'%h'", fpath} reader *CmdReader err error ) if reader, err = me.exec(args); err != nil { return "", err } defer reader.Close() var ( buf = new(bytes.Buffer) ) if _, err = io.Copy(buf, reader); err != nil { return "", err } var ( scanner = bufio.NewScanner(buf) sha string ) for i := 0; scanner.Scan(); i++ { if i == count { sha = scanner.Text() break } } if err = scanner.Err(); err != nil { return "", err } return strings.ReplaceAll(sha, "'", ""), nil } // Show は、 git show sha:fpath を実行します。 func (me *GitCmd) Show(sha, fpath string) (io.ReadCloser, error) { var ( args = []string{"show", fmt.Sprintf("%s:%s", sha, fpath)} reader *CmdReader err error ) if reader, err = me.exec(args); err != nil { return nil, err } return reader, nil }
Taskfile.yml
# https://taskfile.dev version: '3' vars: APP_NAME: gitbkup tasks: default: cmds: - task: build build: cmds: - go build -o {{.APP_NAME}}{{exeExt}} . clean: cmds: - go clean
実行イメージ
$ cd /path/to/ファイルがある場所 $ gitbkup -f ファイル > /path/to/1世代前のファイル
ってすると1世代前のファイルが出力されます。
$ cd /path/to/ファイルがある場所 $ gitbkup -n 2 -f ファイル > /path/to/2世代前のファイル
ってすると2世代前のファイルが出力されます。
参考情報
Goのおすすめ書籍
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。





