git でファイルを過去の履歴から除外する

git を使っていて,後からファイルをリポジトリから削除したいという場合があります. git rmを用いるとファイルをリポジトリから削除することが可能ですが,今回は過去の履歴からもファイルを除外したい場合の操作について紹介します.

ファイルをリポジトリから削除するgit rm

ファイルを git 管理から除外する方法としてgit rmを用いる方法があります.

$ git rm trash.txt # trash.txt を git 管理から除外 + trash.txt 自体も削除
$ git rm --cached trash.txt # trash.txt を git 管理から除外

git rmを実行後,commit することでファイルを除外したことが記録されます.

ファイルを過去の履歴からも除外する

git rmでは,ファイルを除外したことを記録することになりますが,既にリポジトリに記録されているファイルを,過去の履歴からも除外したい場合もあると思います.

そこで,以下のような履歴のリポジトリを例に,trash.txt を過去の履歴から除外する場合について解説していきます.

$ git log --graph --pretty="%h %s" --stat
* 322d421 Update files
|
|  sample.txt | 1 +
|  trash.txt  | 1 +
|  2 files changed, 2 insertions(+)
* 1aa7e6b Add files
|
|  sample.txt | 1 +
|  trash.txt  | 1 +
|  2 files changed, 2 insertions(+)
* 8f832af Initial commit

1. rebase を行う

まず,trash.txt が追加された commit の1つ前のコミットハッシュ値を指定し rebase を行います.

$ git rebase -i 8f832af

エディタが起動し,以下の内容が表示されます.

pick 1aa7e6b Add files
pick 322d421 Update files

# Rebase 8f832af..322d421 onto 8f832af (2 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

1行目の pick を edit へ変更し,保存・終了します.

edit 1aa7e6b Add files
pick 322d421 Update files

# Rebase 8f832af..322d421 onto 8f832af (2 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

以下のように,edit を指定した commit で停止します.

Stopped at 1aa7e6b... Add files
You can amend the commit now, with

        git commit --amend

Once you are satisfied with your changes, run

        git rebase --continue

2. ファイルを削除

現在1aa7e6bの commit で停止しているので,git rmを用いて trash.txt を除外した後,git commit --amendで commit を修正します.

$ git rm --cached trash.txt
$ git commit --amend

以下のように,元のコミットメッセージが入った状態でエディタが起動するので,メッセージに変更が無い場合はそのまま保存・終了します.

Add files

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Mon Aug 15 22:35:48 2016 +0900
#
# interactive rebase in progress; onto 8f832af
# Last command done (1 command done):
#    edit 1aa7e6b Add files
# Next command to do (1 remaining command):
#    pick 322d421 Update files
# You are currently editing a commit while rebasing branch 'master' on '8f832af'.
#
# Changes to be committed:
#  new file:   sample.txt
#
# Untracked files:
#  trash.txt
#

3. rebase を継続する

trash.txt を追加していた commit の修正が終わったので,git rebase --continueで次の commit へ処理を移行します.

次の commit は322d421ですが,先ほどgit rebase -iで開いたエディタで,pick を指定してあるため,何もせず元の commit を利用するはずです. しかし,2. で直前の commit を trash.txt を無かったことにする修正を加えたため,以下のようにエラーが出ます.

$ git rebase --continue
error: refusing to lose untracked file at 'trash.txt'
error: could not apply 322d421... Update files

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
Could not apply 322d421dd371d4483a53050e19ddb43e61b69fe5... Update files

git statusで状況を確認すると,以下のように trash.txt が削除されているためコンフリクトが発生していることが分かります.

$ git status
interactive rebase in progress; onto 8f832af
Last commands done (2 commands done):
   edit 1aa7e6b Add files
   pick 322d421 Update files
No commands remaining.
You are currently rebasing branch 'master' on '8f832af'.
  (fix conflicts and then run "git rebase --continue")
  (use "git rebase --skip" to skip this patch)
  (use "git rebase --abort" to check out the original branch)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   sample.txt

Unmerged paths:
  (use "git reset HEAD <file>..." to unstage)
  (use "git add/rm <file>..." as appropriate to mark resolution)

        deleted by us:   trash.txt

git rmで trash.txt を除外してあげます.

$ git rm --cached trash.txt
trash.txt: needs merge
rm 'trash.txt'

git statusで状況を確認すると,以下のように先ほどと異なる結果となっています.
ここでgit commit --amendを行ってはいけません
8行目に (all conflicts fixed: run "git rebase --continue")とあるので,git rebase --continueを実行します.

$ git status
interactive rebase in progress; onto 8f832af
Last commands done (2 commands done):
   edit 1aa7e6b Add files
   pick 322d421 Update files
No commands remaining.
You are currently rebasing branch 'master' on '8f832af'.
  (all conflicts fixed: run "git rebase --continue")

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   sample.txt

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

        trash.txt

$ git rebase --continue

エディタが起動し,元 (322d421の commit) のコミットメッセージが入力されています. 変更が無い場合,そのまま保存・終了します.

Update files

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# interactive rebase in progress; onto 8f832af
# Last commands done (2 commands done):
#    edit 1aa7e6b Add files
#    pick 322d421 Update files
# No commands remaining.
# You are currently rebasing branch 'master' on '8f832af'.
#
# Changes to be committed:
#  modified:   sample.txt
#
# Untracked files:
#  trash.txt
#

エディタを閉じると,以下のように rebase が完了します.

[detached HEAD d783042] Update files
 1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/master.

git logを実行すると,trash.txt が除外されていることが確認できます.

$ git log --graph --pretty="%h %s" --stat
* d783042 Update files
|
|  sample.txt | 1 +
|  1 file changed, 1 insertion(+)
* 7af8d82 Add files
|
|  sample.txt | 1 +
|  1 file changed, 1 insertion(+)
* 8f832af Initial commit

まとめ

git でファイルを過去の履歴からも除外する方法を紹介しました.

ポイントは,edit に変更した commit 以外(すなわち pick の commit)では,git commit --amendではなく,git rebase --continueを実行することです.

コンフリクト時にgit commit --amendを実行してしまうと,以下のように,元の commit (Update files) ではなく,直前の edit で trash.txt を除外する修正を行った commit (Add files) と1つになってしまうため,注意が必要です.

$ git log --graph --pretty="%h %s" --stat
* 97c757b Add files
|
|  sample.txt | 2 ++
|  1 file changed, 2 insertions(+)
* 8f832af Initial commit