Git

Git上級テクニック集|revert・reflog・bisect・tagを使いこなす

Git 上級 バージョン管理

Git上級テクニック集
revert・reflog・bisect・tagを使いこなす

Gitの上級コマンドを使いこなして、より安全で効率的なバージョン管理を実現しましょう。
revert、reflog、bisect、tag、submodule、.gitattributesの使い方を実例付きで解説します。

こんな人向けの記事です

  • Gitの基本操作は理解しているが上級テクニックを学びたい
  • コミットの安全な取り消し方法を知りたい
  • バグの原因コミットを効率的に見つけたい

Step 1git revert — コミットの安全な取り消し

git revertは、指定したコミットの変更を打ち消す新しいコミットを作成するコマンドです。git resetと違い、履歴を書き換えないため、共有ブランチでも安全に使えます。

コマンド履歴安全性用途
git revert保持される高い共有ブランチの変更取り消し
git reset書き換わる低いローカルブランチの巻き戻し
ターミナル
# 直前のコミットを取り消す
git revert HEAD

# 特定のコミットを取り消す
git revert a1b2c3d

# コミットせずに変更だけ適用する(確認してからコミットしたい場合)
git revert --no-commit a1b2c3d

# 複数のコミットを一括で取り消す
git revert HEAD~3..HEAD

マージコミットを取り消す場合は、-mオプションでどちらの親を残すか指定します。

ターミナル
# マージコミットの取り消し(親1 = マージ先を残す)
git revert -m 1 マージコミットのハッシュ
revert と reset の使い分け
チームで共有しているブランチ(main, develop等)では必ず git revert を使いましょう。
git reset で履歴を書き換えると、他のメンバーが git pull したときにコンフリクトが大量発生します。
revert の revert に注意
revert で取り消したコミットを再度マージしようとすると、「既に取り込み済み」と判断されて変更が反映されません。
再度取り込むには、revert コミット自体を revert する必要があります:
git revert <revert コミットのハッシュ>

Step 2git reflog — 操作履歴から復元する

git reflogは、HEADの移動履歴をすべて記録しているログです。git logでは見えなくなったコミットも、reflogからは見つけることができます。

ターミナル
# reflogを表示
git reflog

# 出力例
a1b2c3d HEAD@{0}: commit: 新機能を追加
f4e5d6c HEAD@{1}: reset: moving to HEAD~1
9g8h7i6 HEAD@{2}: commit: バグ修正
b3c4d5e HEAD@{3}: checkout: moving from main to feature

reflogを使った復元の実践例を見てみましょう。

ターミナル
# 誤って reset --hard してしまった場合
git reset --hard HEAD~3  # やってしまった!

# reflogで元のコミットを探す
git reflog

# 見つかったハッシュで復元
git reset --hard a1b2c3d

# 削除したブランチの復元
git branch -D feature/important  # やってしまった!
git reflog  # ブランチの最後のコミットを探す
git branch feature/important HEAD@{2}  # ブランチを再作成
reflog の保持期間
reflog のエントリはデフォルトで 90日間 保持されます(到達不能なコミットは30日間)。
git reflog expire --expire=now --all で手動削除しない限り、この期間内なら復元できます。
ターミナル
# 特定ブランチのreflogを表示
git reflog show feature/login

# 日時付きで表示
git reflog --date=iso

# reflog の保持期間を設定
git config gc.reflogExpire "180 days"

Step 3git bisect — バグの原因コミットを特定する

git bisectは、二分探索アルゴリズムを使ってバグが混入したコミットを効率的に特定するコマンドです。1000個のコミットがあっても、約10回のテストで原因コミットを見つけられます。

ターミナル
# bisectを開始
git bisect start

# 現在のコミットはバグがある(bad)
git bisect bad

# このコミットではバグがなかった(good)
git bisect good v1.0.0  # タグやコミットハッシュを指定

# → Gitが中間のコミットをチェックアウトする
# → テストして結果を伝える
git bisect good  # バグがなければ
git bisect bad   # バグがあれば

# → 繰り返すと原因コミットが特定される
# 結果例: a1b2c3d is the first bad commit

# bisectを終了して元のブランチに戻る
git bisect reset

テストスクリプトがある場合は、自動化もできます。

ターミナル
# テストスクリプトで自動bisect
# スクリプトが終了コード0で正常、1以上で異常
git bisect start HEAD v1.0.0
git bisect run python test_login.py

# シェルコマンドでも可
git bisect run sh -c "make build && make test"
bisect の効率性
二分探索のため、コミット数 N に対して最大 log2(N) 回のテストで済みます。
コミット数最大テスト回数
1007回
1,00010回
10,00014回
bisect 中にコミットしない
bisect 中は Git が自動でコミットを切り替えています。作業中の変更がある場合は、先に git stash してから bisect を開始してください。

Step 4git tag — バージョンにタグを付ける

git tagは、特定のコミットに名前を付けるコマンドです。リリースバージョンの管理に使います。

種類コマンド特徴
軽量タグgit tag v1.0.0コミットへのポインタのみ
注釈付きタグgit tag -a v1.0.0 -m "メッセージ"作成者・日時・メッセージを含む
ターミナル
# 注釈付きタグを作成(推奨)
git tag -a v1.0.0 -m "初回リリース"

# 軽量タグを作成
git tag v1.0.0-beta

# 過去のコミットにタグを付ける
git tag -a v0.9.0 -m "ベータ版" a1b2c3d

# タグ一覧を表示
git tag

# パターンでフィルタ
git tag -l "v1.*"

# タグの詳細を表示
git show v1.0.0
ターミナル
# タグをリモートにプッシュ
git push origin v1.0.0

# すべてのタグをプッシュ
git push origin --tags

# タグを削除(ローカル)
git tag -d v1.0.0

# タグを削除(リモート)
git push origin --delete v1.0.0

# 特定のタグをチェックアウト
git checkout v1.0.0
# ※ detached HEAD 状態になるので注意
セマンティックバージョニング(SemVer)
タグ名には セマンティックバージョニング を使うのが一般的です。
vメジャー.マイナー.パッチ の形式で管理します。

v1.2.3 の場合:
1(メジャー): 後方互換性のない変更
2(マイナー): 後方互換性のある機能追加
3(パッチ): バグ修正

Step 5git submodule — サブモジュールの管理

git submoduleは、Gitリポジトリの中に別のGitリポジトリを組み込む仕組みです。共通ライブラリや外部依存の管理に使います。

ターミナル
# サブモジュールを追加
git submodule add https://github.com/example/library.git libs/library

# .gitmodulesファイルが作成される
# [submodule "libs/library"]
#     path = libs/library
#     url = https://github.com/example/library.git

# サブモジュールを含むリポジトリをクローン
git clone --recurse-submodules https://github.com/myproject.git

# 既にクローン済みの場合、サブモジュールを初期化
git submodule init
git submodule update
ターミナル
# サブモジュールを最新に更新
git submodule update --remote

# すべてのサブモジュールのステータスを確認
git submodule status

# サブモジュール内で作業する
cd libs/library
git checkout main
git pull
cd ../..
git add libs/library
git commit -m "サブモジュールを更新"

# サブモジュールを削除する
git submodule deinit libs/library
git rm libs/library
rm -rf .git/modules/libs/library
submodule の注意点
  • サブモジュールは特定のコミットを指しているため、更新を忘れると古いバージョンのままになります
  • git clone しただけではサブモジュールの中身は空です。--recurse-submodules を付けるか、後から git submodule update --init が必要です
  • チームメンバー全員がサブモジュールの仕組みを理解していないと、トラブルの原因になります
submodule の代替手段
サブモジュールが複雑だと感じる場合は、以下の代替手段も検討してください。

git subtree: サブモジュールより扱いが簡単(履歴が統合される)
パッケージマネージャー: npm, pip, composer 等で依存管理
モノレポ: すべてのコードを1つのリポジトリで管理

Step 6.gitattributes の活用

.gitattributesは、ファイルごとにGitの動作を制御する設定ファイルです。改行コードの統一、バイナリファイルの扱い、マージ戦略の指定などに使います。

.gitattributes
# 改行コードの統一(チェックアウト時にOSに合わせて変換)
* text=auto

# 特定のファイルの改行コードを指定
*.sh text eol=lf
*.bat text eol=crlf

# バイナリファイルとして扱う
*.png binary
*.jpg binary
*.pdf binary

# 画像のdiffを無効化
*.png -diff
*.jpg -diff

# ロックファイルのマージを禁止(常に競合として扱う)
package-lock.json merge=binary
yarn.lock merge=binary
.gitattributes(言語統計の制御)
# GitHubの言語統計からvendorファイルを除外
vendor/** linguist-vendored
static/lib/** linguist-vendored

# 自動生成ファイルを統計から除外
*.min.js linguist-generated
*.min.css linguist-generated

# 特定ファイルの言語を指定
*.html linguist-language=JavaScript
.gitattributes(Git LFS)
# Git LFS で大容量ファイルを管理
*.psd filter=lfs diff=lfs merge=lfs -text
*.ai filter=lfs diff=lfs merge=lfs -text
*.mp4 filter=lfs diff=lfs merge=lfs -text
.gitattributes を導入するタイミング
.gitattributes はリポジトリの初期設定時に作成するのがベストです。
途中から導入する場合は、改行コードの設定変更で大量の差分が出ることがあります。
その場合は以下のコマンドでキャッシュをリセットしてください:

git rm --cached -r .
git add .
git commit -m ".gitattributes を適用"

まとめチェックリスト

  • 共有ブランチのコミット取り消しには git revert を使う
  • git reflog で誤操作からの復元ができることを知っている
  • git bisect で二分探索によるバグ特定ができる
  • リリースには注釈付きタグ git tag -a を使う
  • 外部リポジトリは git submodule で管理できる
  • .gitattributes で改行コードやバイナリの扱いを統一する