Visual Studio の My Project
フォルダーの中にあるファイルを抽出しようとしてエラーになりましたので、調べてみました。
問題
Visual Studio で開発をしていまして、まだ Subversion を使っていたのですが、Visual Studio が Git をサポートしてからしばらく経過していることもあり、ようやく最近 Git に移行してみました。
コミットと同期だけするぶんには Visual Studio のチームエクスプローラーからステージしてコミットしたり(一部のファイルだけコミットするステージを覚えました)、同期したりしています。
それから、Subversion を使っていた時によく差分ファイルを抽出していて、同じことができないかと調べていたら、丁寧なブログの記事がありまして、毎回ここからコピペしています。
$ git archive HEAD `git diff --name-only HEAD~1 HEAD --diff-filter=ACMR` -o archive.zip
ですが、My Project
フォルダーのところで fatal: pathspec 'My' did not match any files
のエラーになりました。
My Project
のスペースで分割されて 2 つのファイルと認識されてしまっているようです。
これに関して、同じような問題を持った人もいるようです。
- Gitの差分ファイル抽出時にスペースを含んだファイル名でエラー - Qiita
- パスに空白を含むファイルをgit archiveする - foohogehoge's blog
- git で半角スペースが混ざったファイルを消す方法 | Crunchlog
が、あまり解決したように見えないです。 対処として、スペースを含むファイルを抽出しないようにしているようですが、それはちょっと採用したくないです。
パスをダブルクォーテーションで囲んでもエラーになります。
ちなみにダブルクォーテーションで囲む方法も試したのですが、ダメでした。
$ git archive --format=zip --prefix=root/ HEAD `git diff --name-only d0b642e 17945a1 | sed 's/\(^.*$\)/"\1"/g'` -o archive.zip fatal: path not found: "404/index.html"
スペースをバックスラッシュでエスケープしてもエラーになります。
スペースの前にバックスラッシュが必要なのか?
というわけでsedしてみる$ git diff --name-only HEAD..HEAD~ | sed -e "s/ /\\\\ /g" My\ Project/hoge
いいんじゃないかな…
$ git archive --format=zip master `git diff --name-only HEAD..HEAD~ | sed -e "s/ /\\\\ /g"` -o diff.zip fatal: pathspec 'My' did not match any files
何故じゃ!!
分かる人がいれば教えてください。
ただ、ファイルのパスをとる時にバッククォートを使わないで、直接ファイルのパスを指定する場合はスペースがあってもダブルクォーテーショーンで囲めば抽出できるようです。
$ git archive HEAD "Project/My Project/file.ext" -o archive.zip
スペースをバックスラッシュでエスケープすることもできるようです。
$ git archive HEAD Project/My\ Project/file.ext -o archive.zip
バッククォートが怪しそうです。。
対処
調べていたら、こちらがヒントになりました。
git diff --name-status commit1 commit2 | awk '{ if ($1 != "D") print $2 }' | xargs git archive -o output.zip HEAD
Git. How to create archive with files, that have been changed? - Stack Overflow
抽出対象のファイルのパスをバッククォートで入れ子のコマンドから取得しようとしているのがエラーになる原因のようでしたので、パイプを使って抽出対象のファイルのパスを git archive
コマンドに渡せば良いんじゃないかということです。
前半の git diff --name-status commit1 commit2 | awk '{ if ($1 != "D") print $2 }'
の部分は git diff --name-only commit1 commit2 --diff-filter=ACMR
とやりたいことは同じだと解釈しました。
削除したファイル以外の差分のファイルのパスだけをとってきたいのだと思います。
git diff
だけですむ後者の方が awk
を使うより良いかなと思います。
次のパイプからは xargs
がポイントです。
xargs はしばしばたくさんのシェルのバッククォート機能と同じ機能を持っている。しかし、より柔軟で、入力に空白や特殊文字を含む場合にはしばしばより安全でもある。find、locate や grep のような長いリストを出力するコマンドとともによく使われる。
xargs
はバッククォート機能と同じ機能を持っているようです。
xargsは、改行等で区切られた標準入力を読み込み、空白で区切られた1行の文字列へ加工し、それを引数として指定したコマンドへ渡して実行させる。ただし、この1行の文字列がシステムで許容される長さを超える場合は、xargsはその文字列を許容の長さになるよう最少の複数に分割し、コマンドを複数回に分けて実行させる。これにより、コマンドが許容の長さを超える引数のリストを受け付けない問題[1]を回避できる。
git archive
コマンドをファイルの数だけ繰り返し実行することになり、非効率的なのかな?とも思いましたが、そうではなく、xargs
は “その文字列を許容の長さになるよう最少の複数に分割し、コマンドを複数回に分けて実行させる” ようなので、git archive
コマンドを 1 回で実行できるだけのファイル数であれば 1 回で実行してくれるようでした。
結果
調べた結果、このコマンドになりました。
$ git diff --name-only HEAD~1 HEAD --diff-filter=ACMR | sed -e 's/ /\\\\ /g' | xargs git archive HEAD -o output.zip
まず、git diff
で差分ファイルのパスを取得して、sed
でスペースをバックスラッシュでエスケープして、git archive
で抽出しています。
終わり
パスにスペースが含まれることは、まあ、少ないですよね。 自分でファイルを作成する場合はスペースを含めないと思いますし。 Visual Studio で作成したファイルが My Project フォルダーの下にできたので、エラーになっただけのことですから。。