関連記事
GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ
概要
以下、自分用のメモです。存在を忘れてしまいそうなので、忘れないうちにメモメモ。。。
たまーにですが、特定のコミットで変更されたファイル一式を手元に置いておきたい場合があったりします。
以下のコマンドを実行したら出来るのですが
$ REV1=$(git rev-parse HEAD~1) $ REV2=$(git rev-parse HEAD) $ git archive --prefix=archive/ main $(git diff --name-only ${REV1} ${REV2}) -o archive.zip
毎回、これを思い出して打ち込んで実行するのを覚えてる自信がまず無いです。
てことで、ツールにしてしまって、コマンド引数のデフォルトで直近コミットのファイル一覧をアーカイブ出力するようにしてしまえば良いかなと。
んじゃ、シェルスクリプト作ればよいやんってなりますが、WindowsとLinuxでシェルスクリプトを分けて作らないといけないのも面倒なので、Goでコマンド呼び出しちゃえという感じ。
サンプル
main.go
package main import ( "archive/zip" "bytes" "flag" "fmt" "io" "log" "os" "os/exec" "strings" ) const ( DefaultCommitFrom = "HEAD~1" DefaultCommitTo = "HEAD" DefaultRepoPath = "." DefaultArchivePrefix = "archive" DefaultArchiveFilePath = "./archive.zip" ) type ( Gitcmd string ) func NewGitcmd() Gitcmd { return Gitcmd("git") } func (me Gitcmd) Exec(args ...string) ([]byte, error) { var ( cmd = exec.Command(string(me), args...) out []byte err error ) out, err = cmd.CombinedOutput() if err != nil { return nil, err } return bytes.TrimSuffix(out, []byte("\n")), nil } func (me Gitcmd) ExecString(args ...string) (string, error) { var ( out []byte err error ) out, err = me.Exec(args...) if err != nil { return "", err } return string(out), nil } type ( Args struct { CommitFrom string CommitTo string RepoPath string ArchivePrefix string ArchiveFilePath string Verbose bool } ) func (me *Args) restore() { if me.CommitFrom == "" { me.CommitFrom = DefaultCommitFrom } if me.CommitTo == "" { me.CommitTo = DefaultCommitTo } if me.RepoPath == "" { me.RepoPath = DefaultRepoPath } if me.ArchivePrefix == "" { me.ArchivePrefix = DefaultArchivePrefix } if me.ArchiveFilePath == "" { me.ArchiveFilePath = DefaultArchiveFilePath } } var ( args Args ) func init() { flag.StringVar(&args.CommitFrom, "from", DefaultCommitFrom, "起点となるコミットハッシュ") flag.StringVar(&args.CommitTo, "to", DefaultCommitTo, "終点となるコミットハッシュ") flag.StringVar(&args.RepoPath, "repo", DefaultRepoPath, "リポジトリパス") flag.StringVar(&args.ArchivePrefix, "prefix", DefaultArchivePrefix, "アーカイブ内のプレフィックスディレクトリ") flag.StringVar(&args.ArchiveFilePath, "archive", DefaultArchiveFilePath, "アーカイブファイル") flag.BoolVar(&args.Verbose, "v", false, "詳細表示") } func main() { log.SetFlags(0) flag.Parse() (&args).restore() if err := run(); err != nil { log.Fatal(err) } } func run() error { var ( git = NewGitcmd() err error ) // リポジトリの場所に移動 err = os.Chdir(args.RepoPath) if err != nil { return err } // -------------------------------------------------------------------------------------------- // 以下と同じことをする // // $ REV1=$(git rev-parse HEAD~1) // $ REV2=$(git rev-parse HEAD) // $ git archive --prefix=archive/ main $(git diff --name-only ${REV1} ${REV2}) -o archive.zip // -------------------------------------------------------------------------------------------- // コミットハッシュを取得 var ( from string to string ) from, err = git.ExecString("rev-parse", args.CommitFrom) if err != nil { return err } to, err = git.ExecString("rev-parse", args.CommitTo) if err != nil { return err } if args.Verbose { log.Printf("起点コミットハッシュ: %s(%s)", args.CommitFrom, from) log.Printf("終点コミットハッシュ: %s(%s)", args.CommitTo, to) } // 変更されたファイルの一覧を取得 var ( diff string files []string ) diff, err = git.ExecString("diff", "--name-only", from, to) if err != nil { return err } files = strings.Split(diff, "\n") if len(files) == 0 { return nil } // ZIPファイルを作成 var ( zipFile *os.File writer *zip.Writer ) zipFile, err = os.Create(args.ArchiveFilePath) if err != nil { return err } defer zipFile.Close() writer = zip.NewWriter(zipFile) defer writer.Close() for _, file := range files { if file == "" { continue } // ファイルの内容を取得し、ZIPファイルにエントリ書き込み var ( contents []byte entry io.Writer ) contents, err = git.Exec("show", fmt.Sprintf("%s:%s", to, file)) if err != nil { return err } entry, err = writer.Create(fmt.Sprintf("%s/%s", args.ArchivePrefix, file)) if err != nil { return err } if _, err = entry.Write(contents); err != nil { return err } if args.Verbose { log.Printf("ファイル追加: %s", file) } } if err = writer.Close(); err != nil { return err } return nil }
Taskfile.yml
# https://taskfile.dev version: '3' vars: APP_NAME: gitar tasks: default: cmds: - task: clean - task: build - task: run clean: cmds: - rm -f ./{{.APP_NAME}} build: cmds: - go build -o {{.APP_NAME}} . run: cmds: - ./{{.APP_NAME}} - unzip -l archive.zip
自分で使う場合のコマンド名は git + ar(chive) で gitar って名前で go install しておいて利用したりしています。たまーに便利です。
参考情報
Goのおすすめ書籍
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。