【iOS】【Git】ライブラリの管理でリポジトリの肥大化を防ぐ


概要

iOSのプロジェクトにライブラリやFrameworkを含めてGitのリポジトリで管理すると、バイナリデータとして管理されるため、その更新毎に差分が増えていきリポジトリが肥大化していきます。
特に、他のプラットフォームでも利用できるFrameworkを同時に開発している状況ではFrameworkの更新が多くなってしまうので、その分、差分がリポジトリに残っていくことになります。
更新頻度が高い動画データなどをリポジトリで管理してしまったために肥大化したというのは、多くの人が経験しているようです。ライブラリやFrameworkもコードで管理してしまうと同じ事が起こってしまうわけです。

Cocoapodsを利用する

ライブラリやFrameworkをCocoapodsで管理すれば、ライブラリが更新されたら、podfileのversionを書き換えるだけで管理出来るため、容量の肥大化を防ぐ事ができます。

podfile
target 'TestProj' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  pod 'Alamofire', '~> 4.8.1'
  # Pods for TestProj

end

プロジェクトを一年後に起動してみたときに、ライブラリやFrameworkが更新されていて、アプリがビルド出来なくなる場合があるので、最新のライブラリを利用する場合でもversionは書いておいた方がいいです

リポジトリでライブラリやFrameworkを管理する場合

リポジトリを壊してしまう可能性があるため作業前にリポジトリの複製を取ることをお勧めします

Cocoapods等を利用せず、リポジトリで管理する場合は定期的にメンテナンス作業を行なってリポジトリの肥大化を防ぎます。
ライブラリを複数利用する場合はその都度podsを作成したり、テスト端末にbeta版を配信する場合は配信するPCにもpod installが必要となったりと色々面倒になります。
それをやるぐらいならリポジトリをメンテする方が楽な場合もあります。

削除方法1 ライブラリの更新毎に前回の履歴を削除する

rebaseを使って削除する

$ git rebase -i コミットid

(前回ライブラリをコミットした直前のコミットid <コミットid^でそのコミットの直前>)

TestLibrary.aというライブラリを追加して、その後5回更新した例

ターミナル
$ git rebase -i e01dc8ff914570f5fa8b27aaaaaaaaaaaaaa

pick 46fbe67 TestLibrary 追加
pick ea498bb TestLibrary 更新1
pick fbba016 TestLibrary 更新2
pick 056409a TestLibrary 更新3
pick d6e2ccb TestLibrary 更新4
pick 49e0436 TestLibrary 更新5

# Rebase e01dc8f..49e0436 onto e01dc8f (6 commands)
#
# 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

dropでcommitの削除を行う

# d, drop = remove commit

更新5をdropでcommitの削除を行った場合

ターミナル
$ git rebase -i e01dc8ff914570f5fa8b27aaaaaaaaaaaaaa

pick 46fbe67 TestLibrary 追加
pick ea498bb TestLibrary 更新1
pick fbba016 TestLibrary 更新2
pick 056409a TestLibrary 更新3
pick d6e2ccb TestLibrary 更新4

# Rebase e01dc8f..49e0436 onto e01dc8f (6 commands)
#
# 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

:x でrebaseを完了する

# x, exec = run command (the rest of the line) using shell

:x
Successfully rebased and updated refs/heads/master.

git logで確認すると "TestLibrary 更新5"のcommitが削除されています

ここでガベージコレクタを実行します
reflogのクリアとガベージコレクタの実行

過去のcommitが削除されていることを確認したら新しく更新されたTestLibrary.aをadd してcommitします

$ git commit -m "TestLibrary 更新6"

push -f でリモートを更新

$ git push -f origin master

削除方法2 コミット履歴からライブラリの変更を全て削除する

現在のブランチのみ

$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch TestLibrary.a' HEAD

実行すると、過去の履歴からTestLibrary.aに関するcommitが全て削除されます

$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch TestLibrary.a' HEAD
Rewrite 46fbe678a6744c600896eaaaaaaaaaaaaaaaaaaa (3/8) (0 seconds passed, remaining 0 predicted)    rm 'TestLibrary.a'
Rewrite ea498bbe7e36658ad4a5aaaaaaaaaaaaaaaaaaaa (4/8) (0 seconds passed, remaining 0 predicted)    rm 'TestLibrary.a'
Rewrite fbba016e5cf759994f43aaaaaaaaaaaaaaaaaaaa (5/8) (0 seconds passed, remaining 0 predicted)    rm 'TestLibrary.a'
Rewrite 056409aa82bedbaed775aaaaaaaaaaaaaaaaaaaa (6/8) (0 seconds passed, remaining 0 predicted)    rm 'TestLibrary.a'
Rewrite d6e2ccb67f847a54937caaaaaaaaaaaaaaaaaaaa (7/8) (1 seconds passed, remaining 0 predicted)    rm 'TestLibrary.a'
Rewrite 49e04362598e487af49a9aaaaaaaaaaaaaaaaaaa (7/8) (1 seconds passed, remaining 0 predicted)    rm 'TestLibrary.a'

Ref 'refs/heads/master' was rewritten

Xcodeでcommit履歴を確認すると、No File : No Version Editor と表示されてTestLibrary.aの変更履歴が存在しない状態になります

ここでガベージコレクタを実行します
reflogのクリアとガベージコレクタの実行

新しく更新されたTestLibrary.aをadd してcommitする

$ git commit -m "TestLibrary 更新6"

push -f でリモートを更新

$ git push -f origin master

複数のブランチで実行する場合

リモートのブランチを全てチェックアウトする

$ git branch -r | sed -e "s#origin/##" | xargs -I{} git checkout -b {} origin/{}

HEADを-- --allにする

$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch TestLibrary.a' -- --all

reflogのクリアとガベージコレクタの実行

新しく更新されたTestLibrary.aをcommitしてpushする
(ブランチのライブラリはどの時点のライブラリをcommitするのか注意が必要です)

pushも全てのブランチに行います

$ git push -f origin --all

既に肥大化したリポジトリのメンテナンスを行う場合

既にリポジトリが肥大化していた場合は、どのファイルが肥大化の原因を作っているのかを探すことから始めます

git_find_big.shを使ってコミットサイズを調べる

https://confluence.atlassian.com/bitbucket/maintaining-a-git-repository-321848291.html
Download the script からダウンロード

プロジェクトのディレクトリにgit_find_big.shを置いてコマンドを実行

$ sh git_find_big.sh
ターミナル
$ sh git_find_big.sh
All sizes are in kB's. The pack column is the size of the object, compressed, inside the pack file.
size   pack   SHA                                       location
72884  20756  42a6369a39f7789f70697a77b6a098e0771df9b1  TestLibrary.a
61175  17479  ddec7525ff829a3689724ba9ccf9bb26bc78e206  TestLibrary.a
46753  13459  8b47a2617bd39110ca20493a6b76adf37ca8df96  TestLibrary.a
12     2      08d6e6fbb63fc578ac75eb183461bc01db94a632  TestProj.xcodeproj/project.pbxproj

TestLibrary.aのサイズが大きい事がわかります
git_find_big.shのスクリプトを変更する事で、取得できる件数を変更できます
デフォルトでは10件となっています

あとは、 リポジトリでライブラリやFrameworkを管理する場合 を参考にメンテナンスを行います

reflogのクリアとガベージコレクタの実行

履歴からライブラリを削除しただけではまだrefsに参照が含まれている状態ですので、それを取り除いてガベージコレクタを行わないとリモート側の整理が行われません

$ git reflog expire --expire=now --all

$ git gc --aggressive --prune=now

作業前にリポジトリの複製を作っておく

git push -f を使うため、失敗した場合はもう一度push -f を行なって以前のリポジトリに戻さなくてはなりません。
そのため別のリポジトリを複製しておいた方が安全です

git をクローン

$ git clone https://[email protected]/TestProj.git

リモートのブランチを全てチェックアウトする

$ git branch -r | sed -e "s#origin/##" | xargs -I{} git checkout -b {} origin/{}

リモート先のURLを変更

$ git remote set-url origin https://[email protected]/TestProj_Copy.git

setしたURLの確認

$ git remote -v

push

$ git push -f origin --all

参考

.7 Gitの内側 - メインテナンスとデータリカバリ
https://git-scm.com/book/ja/v1/Gitの内側-メインテナンスとデータリカバリ
Gitリポジトリをメンテナンスして軽量化する
https://qiita.com/kaneshin/items/0d19fc1cd86f931dc855
リポジトリのサイズを減らす
https://ja.confluence.atlassian.com/bitbucket/reduce-repository-size-321848262.html
gitのremote urlを変更する(レポジトリ移行時)
https://qiita.com/minoringo/items/917e325892733e0d606e
Gitの話:不要になったコミットオブジェクトが削除される瞬間を観測する
https://blog.clock-up.jp/entry/2017/12/03/git-gc