helen's blog

ずっとおもしろいことしてたいな。

開発環境の設定ファイルを守りたい話

保守の直近のリリースに突然異動になったものの
開始1時間くらいローカル環境の修復に費やしてしまったので
今後発生させないために対処法まとめ

症状 : eclipseとDB(vagrant)の接続ができなくなる
原因 : git stashでcatalina.propertiesの変更を取り戻せなくなる
対処法:git update-index --skip-worktree ファイル名 でgitの管理外にする

前から原因は知ってたんですけど
java案件から離れていて放置してたのでいい加減対策しようということで
stash popできずローカル変更が取り戻せなくなる問題対策します (catalina.properties で実験)

stashするに至った原因
$ git pull # ローカルに変更があるとpullできない
Cannot pull with rebase: You have unstaged changes.
Please commit or stash them.
$ git checkout develop_20150709 # 別のブランチに切り替えることもできない
error: Your local changes to the following files would be overwritten by checkout:
	catalina.properties
Please, commit your changes or stash them before you can switch branches.
Aborting

$ git status
On branch develop_20151118
Your branch is up-to-date with 'origin/develop_20151118'.
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)
	modified:   catalina.properties # ←こいつのせいでpullできない
	
Untracked files:
  (use "git add <file>..." to include in what will be committed)
...
諦めてstashしてpullする
$ git stash
$ git pull
First, rewinding head to replay your work on top of it...
# よし!pullできた!
 
$ git stash pop
error: Your local changes to the following files would be overwritten by merge:
	catalina.properties
Please, commit your changes or stash them before you can merge.
Aborting
# stashしたものを取り戻せなくなった!?

pullした結果、ローカルに差分が発生するとpopさせてくれません
その結果catalinaの設定がstashから取り出せなくなるという罠にはまり続けていました

解決策1:gitの追跡対象から外す(Untracked filesのみ)

プロジェクトの中で除外する必要があるファイルは.gitignoreに書くけど、
自分の環境だけで除外したいファイルがある場合は.git/info/excludeに書くのがよいです。
覚えておくと便利なgitのtipsをいくつか - Webtech Walker

※ ↑ ただしUntracked filesに限る

.git/info/excludeに追記してみた

# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
gittest.txt # Untracked filesであればこれでgitの管理対象から外れます

※ すでにgitの追跡対象になっているファイルはgitignoreに書いても追跡されます(Changes not staged for commitのやつ)
.gitignoreに追記する場合は、一度gitの管理下から外さなければ反映されません
さらに、このリポジトリの.gitignoreはgitで管理されているので
変更すると変更があるファイルとみなされてしまうため厄介です

解決策2:gitの追跡対象から外す(Changes not staged for commit)

そして変更があった時にgitの管理下に戻す

コマンド
# ファイルの変更を無視
$ git update-index --skip-worktree ファイル名
# ↑ 解除
$ git update-index --no-skip-worktree ファイル名
# 設定確認
$ git ls-files -v
やってみる
$ git status
On branch develop_20151118
Your branch is up-to-date with 'origin/develop_20151118'.
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)
	modified:   catalina.properties # こいつを除外します

$ git ls-files -v | grep catalina.properties
H catalina.properties

$ git update-index --skip-worktree catalina.properties

$ git ls-files -v | grep catalina.properties # 設定の確認
S catalina.properties # HからSになりました

#  確認
$ git status
On branch develop_20151118
Your branch is up-to-date with 'origin/develop_20151118'.
# なくなってます
コマンド詳細
$ git update-index -h
    --assume-unchanged    mark files as "not changing"
    --no-assume-unchanged
                          clear assumed-unchanged bit
    --skip-worktree       mark files as "index-only"
    --no-skip-worktree    clear skip-worktree bit

違い
・assume-unchanged : そのファイルが作業ツリー上で変更されているときでも、git はその変更を無視して変更されていないとみなします
・skip-worktree : そのファイルが作業ツリー上で変更されているときには、git はその変更を保ちます。

作業ツリー上でもリポジトリ上でもファイル更新があって、それをマージするとき
・assume-unchanged : 作業ツリー上の更新は破棄されて、リポジトリの内容が取り込まれます。
・skip-worktree : 作業ツリー上の更新は保持されて、リポジトリの内容は取り込まれません。

作業ツリー上でファイル更新があって、git reset --hard を実行したとき
・assume-unchanged : 作業ツリー上の更新は破棄されます。
・skip-worktree : 作業ツリー上の更新は保持されます。
既に git 管理しているファイルをあえて無視したい - Qiita

skip-worktreeしておくのが無難かなと
必要になればすぐ戻せますし自分の環境だけに設定できるので
他の人やリポジトリに影響を与えないのが素敵

解決策3:stash save してapplyする

popできないから使えないけど設定をstashして復元する方法の1つってことで...

git stash save settings # settingsという名前でstashする
git stash list # stash一覧
git stash apply stash@\{1\} # stashされたものを適用するが削除しない ← これ大事
git stash pop stash@\{1\} # stashされたものを適用して削除
# 環境によってはstash@{1}でいけるかも(エスケープいらない?)
stashで fatal: ambiguous argument がでるとき
$ git stash pop stash@{7}
 'stash@7': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
 
$ git stash pop stash@\{7\}

# iTermの問題かもしれませんがエスケープすると動きます
名前をつけてsaveして使いまわしたいものをわかりやすくして
applyすることでstash上には残します(popはapply→dropします)

$ git stash save settings
Saved working directory and index state On develop_20151118: settings
HEAD is now at f562188 Merge branch 'develop_20151116' into develop_20151118
 
$ git stash list
stash@{0}: On develop_20151118: settings # ここにいる
stash@{1}: WIP on develop_20151118: hoge Merge branch 'develop_20151116' into develop_20151118
stash@{2}: WIP on develop_20151118: fuga Merge branch 'develop_20151116' into develop_20151118
 
$ git stash apply stash@\{0\}
On branch develop_20151118
Your branch is up-to-date with 'origin/develop_20151118'.
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)
	modified: catalina.properties # おかえりなさい!!
....
 
$ git stash list
stash@{0}: On develop_20151118: settings # stashには残ってます
stash@{1}: WIP on develop_20151118: f562188 Merge branch 'develop_20151116' into develop_20151118
stash@{2}: WIP on develop_20151118: f562188 Merge branch 'develop_20151116' into develop_20151118
 
# ちなみにpopした場合
git stash pop stash@\{0\}
On branch develop_20151118
Your branch is up-to-date with 'origin/develop_20151118'.
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)
	modified:   catalina.properties

no changes added to commit (use "git add" and/or "git commit -a")
Dropped stash@{0} (920e8c3b79e06893161405f038db5c32fca7c9bf) # ← ここでDROPされてる

解決策4:.gitignoreに書いてpush

個人の開発環境の問題解決を目的としているため超絶非推奨

  1. .git/info/excludeに.gitignore追記
  2. リポジトリ内の.gitignoreにgitから外したいファイルを書く
  3. git rm --cached ファイル名 でキャッシュ削除
  4. commit, pushする\(^o^)/
  5. 晴れて管理外に☆

git rm --cached . して苦しんだのでもう二度とやらない

おまけ

stash使いすぎてstash list が散らかってきたとき
$ git stash list
stash@{0}: On develop_20151118: settings
stash@{1}: WIP on develop_20151118: f562188 Merge branch 'develop_20151116' into develop_20151118
stash@{2}: WIP on develop_20151118: f562188 Merge branch 'develop_20151116' into develop_20151118
stash@{3}: On develop_20151118: settigns # ← これを抹消したい
 
$ git stash drop stash@\{3\} # stash list から削除
Dropped stash@{3} (c7e0ab5453da05289432f9aec151cec4791d8f1f)
 
$ git stash list # 消えていることを確認
stash@{0}: On develop_20151118: settings
stash@{1}: WIP on develop_20151118: f562188 Merge branch 'develop_20151116' into develop_20151118
stash@{2}: WIP on develop_20151118: f562188 Merge branch 'develop_20151116' into develop_20151118
stash@{3}: WIP on develop_20151118: 32ac549 TDPRINT-1102 障害票No.29対応

.gitignoreの変更を共有するのであればpushしますが
個人の環境でgitに変更を追跡されたくないため
gitの管理下から外しました
これで個人で変更・追加したファイルがあってもgitから外しちゃえば
git add . で気持ち良くcommitすることも夢じゃないはず!

最後に
今回の私の実験のために壊れてくれた環境たちありがとう
私は git update-index --skip-worktree を使っていこうと思います

ちなみにリリースは無事完了しました( ′◡‵ )

参考
既に git 管理しているファイルをあえて無視したい - Qiita
gitignoreまとめ - maeharin log