Git での新規ファイル作成を含んだファイル変更有無の判定方法

Written by @ryysud

Dec 17, 2018 23:33 · 2144 words · 5 minutes read #git

TL;DR

  • git add -N (–intent-to-add) で新規ファイル作成でもファイル変更として git diff で拾うことが可能になる
  • git diff –exit-code で終了ステータス元にファイル変更有無をハンドリングすることが可能になる
  • この2つを組み合わせて Git での新規ファイル作成を含んだファイル変更有無の判定が実現できる

Git での新規ファイル作成を含んだファイル変更有無の判定方法

Git の2つのコマンドを組み合わせて、新規ファイル作成を含んだファイル変更有無の判定を行うので、まずは2つのコマンドの解説をしていきます。なお、こちらの記事は Git での操作とファイルのバージョン情報がどのように遷移していくかのマッピングが理解できていることを前提に説明していきます。ちょっと不安な方は こちら をチェックするか、適当にググっておさらいしてみるのも良いかもしれません。

利用する Git のバージョン

$ git --version
git version 2.16.1

git add -N (–intent-to-add)

こちらのコマンドを実行することで「このファイルは後で git add しますよー」ということが宣言できます。あまりピンと来ていない方(自分が実際にそうでした…。)もいるかもしれませんが、この処理を実行することで新規に作成されたファイル(Untracked File)も git diff で差分(= 新規作成なのでファイルの全内容)を確認することが可能になります。百聞は一見に如かずなので、コマンドを実行しながら実際に動きをみていきます。

前準備として動作確認用のリポジトリ sample-repo を作成

$ mkdir sample-repo
$ cd sample-repo
$ git init
Initialized empty Git repository in /path/to/sample-repo/.git/

新規にファイルを作成した際の git diff の挙動

新規作成されたファイルは git status で Untracked files としては判定されていますが、stage/index に登録されていないので、git diff では何も出力されません。

$ echo 'This file is untracked file' > untracked_file

$ git status
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	untracked_file

nothing added to commit but untracked files present (use "git add" to track)

$ git diff untracked_file

新規にファイルを作成した後で git add -N (–intent-to-add) した際の git diff の挙動

新規作成されたファイルは git status では変わらず Untracked files としては判定されていますが、git add -N (–intent-to-add) を実行した後だと git diff で変更内容が差分として出力されることがわかります。

$ git add -N untracked_file

$ git status
On branch master

No commits yet

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	new file:   untracked_file

no changes added to commit (use "git add" and/or "git commit -a")

$ git diff untracked_file
diff --git a/untracked_file b/untracked_file
index e69de29..0e9c55c 100644
--- a/untracked_file
+++ b/untracked_file
@@ -0,0 +1 @@
+This file is untracked file

なお、注意事項として git add -N (–intent-to-add) は、あくまで「このファイルは後で git add しますよー」とう宣言をするだけなので、コミットする際には再度 git add を実行することが必須(git commit -a でも問題なし)となります。以下に例を記載しておきます。

git add しないとエラー終了となりコミットできない
$ git diff untracked_file
diff --git a/untracked_file b/untracked_file
index e69de29..0e9c55c 100644
--- a/untracked_file
+++ b/untracked_file
@@ -0,0 +1 @@
+This file is untracked file

$ git commit -m 'Add file'
On branch master

Initial commit

Changes not staged for commit:
	new file:   untracked_file

no changes added to commit
git add することでコミットできる
$ git add untracked_file

$ git commit -m 'Add file'
[master (root-commit) fc64f9d] Add file
 1 file changed, 1 insertion(+)
 create mode 100644 untracked_file

このように git add -N (–intent-to-add) で新規ファイル作成でもファイル変更として git diff で拾うことが可能になります。

git diff –exit-code

通常の git diff だと、ファイルの変更有無問わず、常に終了ステータスは 0 となりますが、git diff に –exit-code のオプションを付与することで、diff と同じように差分がないときは終了ステータスを 0 に、差分があるときは終了ステータスを 1 とすることが可能になります。

通常の git diff

ファイルの変更有無問わず、常に終了ステータスは 0 となる。

$ cat untracked_file
This file is untracked file

$ echo 'New line' >> untracked_file

$ git diff untracked_file
diff --git a/untracked_file b/untracked_file
index 0e9c55c..304274a 100644
--- a/untracked_file
+++ b/untracked_file
@@ -1 +1,2 @@
 This file is untracked file
+New line

$ echo $?
0

git diff –exit-code

差分がないときは終了ステータスが 0 で、差分があるときは終了ステータスが 1 となる。

$ git diff --exit-code untracked_file
diff --git a/untracked_file b/untracked_file
index 0e9c55c..304274a 100644
--- a/untracked_file
+++ b/untracked_file
@@ -1 +1,2 @@
 This file is untracked file
+New line

$ echo $?
1

このように git diff –exit-code で終了ステータス元にファイル変更有無をハンドリングすることが可能になります。

シェルスクリプト

上記2つのコマンドを用いて、以下の仕様のシェルスクリプトを書きました。

  • sample-repo に新規ファイル作成を含んだ変更があった際に ‘There are changes’ という文字列を出力
  • sample-repo に新規ファイル作成を含んだ変更がない際に ‘There are no changes’ という文字列を出力

git diff –exit-code で終了ステータスが 1 となり途中で処理が中断されないように set +e したり、git diff –exit-code で判定に不要な差分を出力しないために –quiet オプションを付与するなど、細かな制御もいれたシェルスクリプトとなっております。

$ cat check.sh
#!/usr/bin/env bash

set -e

TARGET_REPO='./sample-repo'

cd ${TARGET_REPO}
git add -N .
set +e
git diff --exit-code --quiet
if [[ $? -eq 1 ]]; then
    echo 'There are changes'
else
    echo 'There are no changes'
fi

新規ファイル作成を含んだ変更を一切加えていない sample-repo にシェルスクリプトを実行してみる

想定通り ‘There are no changes’ が出力された。

$ ll -d sample-repo
drwxr-xr-x  4 ryysud  staff   136B 12 18 00:22 sample-repo

$ bash check.sh
There are no changes

sample-repo で新規にファイルを作成してシェルスクリプトを実行してみる

想定通り ‘There are changes’ が出力された。

$ echo 'New file' > ./sample-repo/new_file

$ bash check.sh
There are changes

sample-repo でファイルを変更してシェルスクリプトを実行してみる

想定通り ‘There are changes’ が出力された。

$ rm -f ./sample-repo/new_file

$ echo 'New line' >> ./sample-repo/untracked_file

$ bash check.sh
There are changes

また、当スクリプトとは関係ありませんが、判定処理の中でコミットをする際には git add -N (–intent-to-add) の後で再度 git add が必要なことは忘れずに。

さいごに

結構レアなユースケースだとは思いますが、インターネッツにまとまってるものがなかったので、今回記事としてまとめてみました。例として記載したシェルスクリプトは非常に単純なものなので、各位のユースケースに応じて適宜修正してご活用ください。では、良きシェル芸ライフを〜!