- 第1弾(本記事): Git入門 — init/commit/branch/merge
- 第2弾: GitHub入門 — repo/PR/Issue/fork
- 第3弾: ブランチ戦略 — GitFlow/GitHub Flow/trunk-based
- 第4弾: セキュリティ — .gitignore/シークレット/pre-commit/tflint/tfsec
- 第5弾(最終回): Terraform実践 — モジュール化・tfstate・OIDC CI/CD
姉妹シリーズ — 合わせて読みたい記事

- 1 Section 1: なぜGitか — 分散型バージョン管理とIaC
- 2 Section 2: 環境構築 — Git 2.40+ インストール・SSH 鍵・初期設定
- 3 §3 基礎操作 — init / add / commit / diff / log / switch / restore
- 3.1 3-1. Terraform ハンズオンリポジトリの作成
- 3.2 3-2. Git の3エリア — Working Tree / Staging Area / Repository
- 3.3 3-3. git status — 現在の状態を確認
- 3.4 3-4. git add — ステージングに追加する
- 3.5 3-5. git commit — 変更をリポジトリに記録する
- 3.6 3-6. git diff — 変更内容を確認する
- 3.7 3-7. git log — コミット履歴を確認する
- 3.8 3-8. git switch / git restore — Git 2.23+ の新標準コマンド
- 4 §4 ブランチ運用 — feature branch / merge / コンフリクト解消
- 5 Section 5: 【実務編 前半】stash + cherry-pick + reflog
- 5.1 5-1. git stash — 作業を一時退避して別ブランチへ切り替える
- 5.1.1 5-1-1. git stash push -u — untracked ファイルも含めて退避
- 5.1.2 5-1-2. git stash list / show -p — 一覧確認と差分表示
- 5.1.3 5-1-3. git stash pop vs git stash apply — 適用時の使い分け
- 5.1.4 5-1-4. git stash drop / git stash clear — 削除
- 5.1.5 5-1-5. git stash branch — stash から新ブランチを作成する
- 5.1.6 5-1-6. Terraform 作業中断シナリオ(main.tf 編集中に hotfix 対応)
- 5.2 5-2. git cherry-pick — 特定コミットを別ブランチへ取り込む
- 5.3 5-3. git reflog — 失われたコミットを救済する
- 5.4 5-3-4. reflog を使った削除済みブランチのコミット救済
- 5.5 5-4. Mermaid01: stash / cherry-pick / reflog ワークフロー図
- 5.6 5-5. まとめ: stash / cherry-pick / reflog の使い分け
- 5.1 5-1. git stash — 作業を一時退避して別ブランチへ切り替える
- 6 Section 6: 実務編 後半 — rebase -i / bisect / worktree / blame
- 7 §7: Gitで詰まるポイント10選 + アンチパターン演習
- 7.1 7-1. detached HEAD の原因と解消
- 7.2 7-2. force-push の災害
- 7.3 7-3. merge vs rebase の選択基準
- 7.4 7-4. git reset --hard の取り消し(reflog で救済)
- 7.5 7-5. submodule の罠
- 7.6 7-6. LF/CRLF 問題(git config core.autocrlf)
- 7.7 7-7. .gitignore が効かない(git rm --cached)
- 7.8 7-8. large file problem(.tfstate の誤コミット)
- 7.9 7-9. git stash pop でのコンフリクト
- 7.10 7-10. git bisect 後の reset 忘れ
- 7.11 7-11. アンチパターン演習(5件)
- 8 §8: まとめ + Git/GitHub×Terraform実践シリーズ全軸クロスリンク
Section 1: なぜGitか — 分散型バージョン管理とIaC
Terraform でインフラをコード化(IaC)すると「コードを書いた後」の課題が浮かび上がります。main.tf のどこを誰がいつ変えたか記録がない、本番反映前にチームでコードをレビューする仕組みがない、誤ってリソース定義を削除した後に元に戻せない——これらをすべて解決するのが Git です。
1-1. バージョン管理システムとは
バージョン管理システム(VCS: Version Control System)とは、ファイルの変更履歴を記録・管理し、過去の状態への巻き戻しや複数人の変更の統合を可能にするシステムです。
Git の登場以前、エンジニアはファイルのコピーでバージョン管理を行っていました。
main_v1.tf、main_v2.tf、main_final.tf、main_final_final.tf…backup_20260415/というディレクトリを手動で作成してコピー- 誰かが書き換えると上書きされ、元に戻せない
- 「最新版はどれか」「誰がいつ変えたか」を追跡できない
VCS はこの問題を体系的に解決します。VCS は大きく 集中型(CVCS) と 分散型(DVCS) に分類されます。
| 比較項目 | 集中型 VCS(例: SVN / CVS) | 分散型 VCS(例: Git) |
|---|---|---|
| リポジトリの場所 | 中央サーバーのみ | 各開発者のローカルにも全履歴が存在 |
| オフライン作業 | 不可(サーバー接続必須) | 可(コミット・ブランチはローカルで完結) |
| 障害耐性 | サーバー障害で全員作業停止 | ローカルに全履歴があるため継続可能 |
| ブランチ操作 | 重く低速(ファイルコピーが発生) | 軽量・高速(ポインタ操作のみ) |
| コミット速度 | サーバーとの通信が必要 | ローカル書き込みのみ(高速) |
| 普及状況 | レガシーなエンタープライズ | GitHub / GitLab / Bitbucket(現在の主流) |
Git は分散型(DVCS)です。各開発者のローカル PC に全コミット履歴を含むリポジトリのコピーが存在します。ネットワークなしでもコミット・ブランチ作成・履歴参照が行えます。
- オフライン作業: git commit はすべてローカルで完結するため、VPN が切れても
git commitできる。出張先・在宅ワーク時でも履歴を正確に記録可能。 - 高速なブランチ切り替え:
feature/add-rdsブランチとmainブランチの切り替えが1秒以内に完了。数十ファイルのリポジトリでも遅延なし。 - 安全な実験: ブランチで「試しにこのリソース定義を変えてみる」実験が低コストで行える。問題があればブランチを削除するだけで元の状態に戻せる。
1-2. なぜ Terraform コードに Git が必要か
Terraform には terraform.tfstate というインフラの現在状態を管理するファイルがありますが、これは「現在の AWS リソース状態のスナップショット」であり「コードの変更履歴」ではありません。
Git がない状態を想像してください。main.tf の S3 バケットのリージョンを変更して terraform apply した翌日、本番環境に予期しない変更が発生したとします。「いつ誰が何を変えたか」の記録がなければ原因の特定に大変な時間がかかります。
tfstate と Git の役割分担
| 比較項目 | terraform.tfstate | Git リポジトリ |
|---|---|---|
| 管理する情報 | AWS の「現在の状態」(リソース ID 等) | コードの「変更履歴」(誰がいつ何を変えたか) |
| 変更履歴 | 保持しない(上書き) | すべてのコミットを永続的に保持 |
| 差分確認 | terraform plan で確認 | git diff でコードレベルの差分確認 |
| 複数人での利用 | Remote State(S3+DynamoDB)が必要 | git push / pull / PR でマージ |
| バックアップ | S3 バージョニング等で別途管理 | git clone でどこでも全履歴を複製可能 |
Terraform コードを Git で管理する主な理由は4つあります。
① 変更の追跡と責任の明確化: 誰が・いつ・どのリソース定義を変更したかが git log で一目瞭然になります。コミット単位で変更内容とメッセージが記録されるため「なぜこのリソースをこの設定にしたか」という意図も後から追跡できます。
② 差分レビュー(安全な本番反映): terraform apply の前に git diff main..feature/add-ec2 でコードの変更差分を確認し、意図しない変更が混入していないか確認できます。実務では「コードレビュー → terraform plan 結果レビュー → terraform apply」の3段階が標準です。
③ 並列開発とブランチによる安全な変更管理: main ブランチを常にデプロイ可能な状態に保ちながら、feature/add-rds ブランチで新リソースを開発・テスト・レビューしてから統合できます。
④ ロールバックの安全網: git revert <commit-hash> で問題のある変更を安全に取り消せます(変更履歴を保持したまま巻き戻し)。緊急時に「昨日の terraform apply 前の状態に戻したい」場合も、該当コミットのコードをすぐに確認して巻き戻せます。
チーム開発において main ブランチは「常にデプロイ可能な状態」を維持するブランチです。直接 main に push すると以下のリスクが生じます。
- レビューなしの変更が本番に直結: PR なしでマージされるため、意図しないリソース変更が
terraform applyされるリスクがある。 - ロールバックが困難: 複数の変更が混在したコミットは、特定の変更だけを
git revertしにくい。 - チームメンバーとの競合: 複数人が同時に
mainへ push するとコンフリクトが発生し、最悪の場合どちらかの変更が消える。
解決策: 必ず feature ブランチを切ってから作業し、PR 経由でマージする(詳細は第2弾・第3弾で解説)。
1-3. Git の3エリア — Working / Staging / Repository
Git がファイルを管理する仕組みの核心は「3エリア」の概念です。この概念を理解することで、git add と git commit が何をしているかが直感的に把握できます。
| エリア | 別名 | 説明 | 移動コマンド |
|---|---|---|---|
| Working Tree | 作業ディレクトリ | 実際のファイルが存在する作業領域 | git add <file> で Staging Area へ |
| Staging Area | インデックス | コミット対象を選択する中間領域 | git commit で Repository へ |
| Repository | .git/ 以下 | コミット済みスナップショットが永続化される領域 | git restore <file> で Working Tree へ戻す |
Staging Area の存在意義: main.tf と variables.tf の両方を編集していても、git add main.tf だけ実行すれば main.tf の変更だけをコミットできます。Terraform リポジトリでは「S3バケット定義の追加」と「変数の整理」を別々のコミットとして記録することが推奨されます。Staging Area がその選別を可能にします。
| 操作 | コマンド | 移動方向 |
|---|---|---|
| 変更をステージングに追加 | git add <file> | Working Tree → Staging Area |
| ステージングをコミット | git commit -m "message" | Staging Area → Repository |
| ステージングから取り消し | git restore --staged <file> | Staging Area → Working Tree |
| コミット済みを作業ディレクトリに展開 | git restore <file> | Repository → Working Tree |
| 変更の確認(未ステージ) | git diff | Working Tree と Staging Area の差分 |
| 変更の確認(ステージ済み) | git diff --staged | Staging Area と Repository の差分 |
Section 2: 環境構築 — Git 2.40+ インストール・SSH 鍵・初期設定
本 Section では Git を使い始める前に必要な環境構築を行います。インストール確認・ユーザー情報設定・SSH 鍵生成・エディタ設定・.gitignore テンプレート作成まで一気に完成させます。設定は1回行えばマシン上のすべてのリポジトリに適用されます。
2-1. Git バージョン確認とインストール
まず Git がインストールされているか確認します。ターミナルを開いて以下を実行してください。
# バージョン確認(2.40以上が推奨)
git --version
# 期待する出力例: git version 2.45.2
git version 2.x.x と表示されれば Git はインストール済みです。バージョンが 2.40 未満の場合は以下でアップデートしてください。
macOS — Homebrew でインストール / アップデート
# Homebrew が未インストールの場合は先にインストール
# /bin/bash -c "$(curl -fsSL https://brew.sh/install.sh)"
# Git のインストール(または最新版へのアップデート)
brew install git
# シェル再起動後、バージョン確認
git --version
# git version 2.45.2
macOS では Xcode Command Line Tools に同梱された Git(/usr/bin/git)が古いままになりがちです。brew install git 後は /opt/homebrew/bin/git が優先されるよう PATH を確認してください。
# どの git が使われているか確認
which git
# /opt/homebrew/bin/git なら OK
# /usr/bin/git が返る場合は ~/.zshrc に以下を追加
# export PATH="/opt/homebrew/bin:$PATH"
Linux (Ubuntu / Debian) — apt でインストール
sudo apt update && sudo apt install git -y
git --version
Windows — WSL2 環境での確認
# WSL2 のターミナルで実行(PowerShell ではなく Linux シェル)
git --version
# バージョンが古い場合は ppa から最新版を取得
sudo add-apt-repository ppa:git-core/ppa -y
sudo apt update && sudo apt install git -y
git --version
git switch / git restore について(git checkout 廃止予告)
Git 2.23 で git checkout の機能が git switch(ブランチ操作)と git restore(ファイル操作)に分離されました。Git 2.40 以降では git switch / git restore が標準となり、git checkout は将来的に廃止予定です。本記事では git switch / git restore を主として使い、対応する旧コマンドを併記します。
| 操作 | 新コマンド(推奨) | 旧コマンド(非推奨) |
|---|---|---|
| ブランチ切り替え | git switch <branch> | git checkout <branch> |
| 新ブランチ作成・切り替え | git switch -c <branch> | git checkout -b <branch> |
| ファイルを最新コミットに戻す | git restore <file> | git checkout -- <file> |
| ステージングを取り消し | git restore --staged <file> | git reset HEAD <file> |
2-2. ユーザー情報の設定
git config --global でユーザー名とメールアドレスを設定します。これはコミット履歴に「誰が変更したか」として記録される情報です。
# ユーザー名とメールアドレスを設定(GitHub のアカウントと合わせる)
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"
# デフォルトブランチ名を main に設定(Git 2.28+ から設定可能)
git config --global init.defaultBranch main
# VS Code をデフォルトエディタに設定(git commit でエディタが開く場合)
git config --global core.editor "code --wait"
# 設定を確認
git config --list
- メールアドレスの一致が重要: GitHub の登録メールアドレスと
user.emailが異なると、コミットが GitHub アカウントに紐付かず「Unknown author」扱いになる。設定後はgit logで確認する。 - 複数アカウントの使い分け: 会社用と個人用で異なる GitHub アカウントを使う場合、
--globalではなく--local(リポジトリ単位)で設定を上書きする。~/.gitconfigの設定はすべてのリポジトリに適用される点に注意。 - Windows パスの罠(WSL2 ユーザー): WSL2 の Linux 環境と Windows ネイティブ Git で設定が別管理になる。混在させると改行コード変換(
core.autocrlf)が予期せず動作することがある。WSL2 内ではcore.autocrlf=falseを推奨。
2-3. SSH 鍵の生成と GitHub への登録
GitHub にコードを push するには SSH 鍵認証が必要です(第2弾のハンズオンで使用)。本 Section で事前に準備しておきます。
# SSH 鍵を生成(Ed25519 アルゴリズムを推奨)
ssh-keygen -t ed25519 -C "your.email@example.com"
# パスフレーズの入力を求められる(設定推奨 — 省略も可能)
# 鍵は ~/.ssh/id_ed25519(秘密鍵)と ~/.ssh/id_ed25519.pub(公開鍵)に保存される
# 公開鍵の内容を表示(GitHub に登録する)
cat ~/.ssh/id_ed25519.pub
# ssh-ed25519 AAAA... your.email@example.com
# SSH エージェントに秘密鍵を追加
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
GitHub への公開鍵登録手順
cat ~/.ssh/id_ed25519.pubの出力をコピー- GitHub → Settings → SSH and GPG keys → New SSH key
- Title に「My Laptop」等の識別名を入力し、Key フィールドにペースト
- Add SSH key をクリック
接続確認(GitHub アカウント登録後):
ssh -T git@github.com
# Hi <username>! You've successfully authenticated... と表示されれば成功
2-4. .gitignore の設定
Terraform リポジトリでは、機密情報を含むファイルやキャッシュファイルを Git の追跡対象から除外する .gitignore が必須です。
# プロジェクトルートに .gitignore を作成
cat > .gitignore << 'EOF'
# Terraform ステートファイル(AWSリソースのIDや機密情報を含む)
*.tfstate
*.tfstate.*
.terraform.tfstate.lock.info
# Terraform ローカルキャッシュ(provider バイナリ等)
.terraform/
.terraform.lock.hcl
# 機密情報ファイル(絶対に Git にコミットしない)
*.tfvars
!example.tfvars
# OS 生成ファイル
.DS_Store
Thumbs.db
# エディタ設定
.vscode/
.idea/
*.swp
*.swo
# ログファイル
*.log
crash.log
EOF
!example.tfvars の記述により、変数のサンプルファイル(example.tfvars)だけを Git 追跡対象に残し、実際の機密値を含む terraform.tfvars は除外する運用が可能です。
# .gitignore が正しく機能しているか確認
git check-ignore -v .terraform/
# .gitignore:6:.terraform/ .terraform/
# すでに追跡されているファイルをキャッシュから除外する場合
git rm --cached <file>
グローバル .gitignore の設定(OS固有ファイルをリポジトリごとに書かない)
# グローバル .gitignore ファイルを作成
cat > ~/.gitignore_global << 'EOF'
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
EOF
# git config でグローバル .gitignore を登録
git config --global core.excludesfile ~/.gitignore_global
2-5. 設定の最終確認
環境構築が完了したら、すべての設定をまとめて確認します。
# git config の全設定を一覧表示
git config --list
# 確認すべき主要項目
# user.name=Your Name
# user.email=your.email@example.com
# init.defaultBranch=main
# core.editor=code --wait
# core.excludesfile=/Users/yourname/.gitignore_global
# Git バージョン最終確認(2.40以上であること)
git --version
以上で環境構築は完了です。Section 3 以降では実際にリポジトリを作成し、git init / git add / git commit のハンズオンを進めます。
§3 基礎操作 — init / add / commit / diff / log / switch / restore
Git の理論を学んだ次のステップは「実際に動かす」ことです。本セクションでは Terraform の設定ファイルを題材に、リポジトリの作成からコミット履歴の確認まで、基本コマンドをハンズオン形式で習得します。
3-1. Terraform ハンズオンリポジトリの作成
まず、ハンズオン全体で使う作業ディレクトリを作成し、Git リポジトリとして初期化します。
# 作業ディレクトリを作成(場所はどこでも可)
mkdir -p ~/terraform-git-handson && cd ~/terraform-git-handson
# Git リポジトリとして初期化(-b main でデフォルトブランチを main に指定)
git init -b main
# 出力: Initialized empty Git repository in ~/terraform-git-handson/.git/
.git/ ディレクトリが作成されていれば初期化成功です。
次に Terraform の最小構成ファイルを作成します。
# providers.tf — AWS プロバイダー設定
cat > providers.tf << 'EOF'
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
EOF
# variables.tf — 変数定義
cat > variables.tf << 'EOF'
variable "aws_region" {
description = "AWSリージョン"
type = string
default = "ap-northeast-1"
}
variable "bucket_name" {
description = "S3バケット名(グローバル一意)"
type = string
}
EOF
# main.tf — メインリソース定義
cat > main.tf << 'EOF'
module "s3" {
source= "./modules/s3"
bucket_name = var.bucket_name
}
EOF
# .gitignore — Terraform 用の除外設定
cat > .gitignore << 'EOF'
.terraform/
*.tfstate
*.tfstate.backup
*.tfvars
crash.log
crash.*.log
*.tfplan
EOF
3-2. Git の3エリア — Working Tree / Staging Area / Repository
Git の変更管理は3つのエリアを行き来する仕組みで動いています。

| エリア | 別名 | 役割 |
|---|---|---|
| Working Tree(作業ディレクトリ) | — | 実際にファイルを編集する場所 |
| Staging Area(インデックス) | Index | コミット対象として「選択済み」の変更を一時保管 |
| Repository(リポジトリ) | .git/ | コミット済みのスナップショットが蓄積される場所 |
git add で Working Tree → Staging Area へ、git commit で Staging Area → Repository へ変更が記録されます。この2ステップ構造が「どの変更をコミットに含めるか」を細かく制御できる Git の特徴です。
3-3. git status — 現在の状態を確認
git status は最も頻繁に使うコマンドです。ファイルを作成した直後に実行してみましょう。
git status
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
main.tf
providers.tf
variables.tf
nothing added to commit but untracked files present (use "git add" to start tracking)
「Untracked files」は Git がまだ追跡していない新規ファイルです。Working Tree に存在しますが、Staging Area にも Repository にも登録されていません。
短縮表示(-s)を使うと変更の全体像を素早く把握できます。
git status -s
# ?? .gitignore
# ?? main.tf
# ?? providers.tf
# ?? variables.tf
?? は Untracked(未追跡)を意味します。
3-4. git add — ステージングに追加する
git add でファイルを Staging Area に登録します。
# providers.tf だけを先にステージング
git add providers.tf
git status -s
# A providers.tf
# ?? .gitignore
# ?? main.tf
# ?? variables.tf
A は「Added(Staging Area に追加済み)」を意味します。残りのファイルを追加します。
git add .gitignore main.tf variables.tf
git status -s
# A .gitignore
# A main.tf
# A providers.tf
# A variables.tf
git add . の危険性 — 意図しないファイルをコミットしない
git add . はカレントディレクトリ以下のすべての変更をステージングします。一見便利ですが、.tfvars(シークレット値を含む)、*.tfstate(インフラ状態)、crash.log など、コミットすべきでないファイルが混入するリスクがあります。
対策として、常に git status -s で追加対象を確認してからステージングしてください。ファイルを個別に指定する git add か、変更をハンク単位で選択する git add -p の使用を推奨します。.gitignore で除外設定を済ませておくことも重要です。
3-5. git commit — 変更をリポジトリに記録する
ステージングされた変更をコミットします。
git commit -m "feat: Terraform S3バケット管理の初期構成を追加"
[main (root-commit) a1b2c3d] feat: Terraform S3バケット管理の初期構成を追加
4 files changed, 42 insertions(+)
create mode 100644 .gitignore
create mode 100644 main.tf
create mode 100644 providers.tf
create mode 100644 variables.tf
root-commit はリポジトリの最初のコミットを示します。コミット後に git status を確認すると「nothing to commit, working tree clean」となり、Working Tree がクリーンな状態になります。
次に main.tf にタグ設定を追加し、2つ目のコミットを作ります。
cat > main.tf << 'EOF'
module "s3" {
source= "./modules/s3"
bucket_name = var.bucket_name
}
locals {
common_tags = {
Project= "terraform-git-handson"
ManagedBy = "Terraform"
Env = "dev"
}
}
EOF
git add main.tf
git commit -m "feat: main.tf に common_tags locals を追加"
git commit --amend の注意点 — 使えるのはローカルのみ
直前のコミットのメッセージや内容を修正したい場合、git commit --amend が使えます。しかし --amend はコミットハッシュを書き換えるため、すでにリモート(GitHub 等)にプッシュ済みのコミットに使うと、他のメンバーの履歴と食い違いが生じます。
--amend は「まだプッシュしていないローカルの直前コミットだけ」に限定して使ってください。プッシュ済みのコミットを修正する場合は、チームに影響を与えない方法(新たなコミットで上書き)を選択します。
# ローカルの直前コミットメッセージを修正(プッシュ前のみ)
git commit --amend -m "feat: main.tf に common_tags locals を追加(修正)"
3-6. git diff — 変更内容を確認する
コミット前に変更内容を確認する習慣をつけましょう。
# Working Tree と Staging Area の差分(未ステージの変更を確認)
git diff
# Staging Area と HEAD(最新コミット)の差分(コミット前の最終確認)
git diff --staged
| コマンド | 比較対象 | 主な用途 |
|---|---|---|
git diff | Working Tree vs Staging Area | 未ステージの変更を確認 |
git diff --staged | Staging Area vs HEAD | コミット前の最終確認 |
git diff HEAD | Working Tree vs HEAD | すべての変更を一括確認 |
git diff <hash1> <hash2> | 任意の2コミット間 | バージョン間の差異を把握 |
3-7. git log — コミット履歴を確認する
git log --oneline --graph
* a2b3c4d (HEAD -> main) feat: main.tf に common_tags locals を追加
* a1b2c3d feat: Terraform S3バケット管理の初期構成を追加
--oneline でコミット1件を1行に圧縮、--graph でブランチの分岐をテキストで可視化します。ブランチが増えたときに --all を付けると全ブランチのコミットが表示されます。
よく使うオプションを組み合わせたエイリアスを設定しておくと便利です。
git config --global alias.lg "log --oneline --graph --all --decorate"
# 以降は git lg で実行可能
3-8. git switch / git restore — Git 2.23+ の新標準コマンド
Git 2.23 で git switch(ブランチ切替)と git restore(ファイル復元)が導入されました。Git 2.40 以降では安定版として推奨されており、従来の git checkout のブランチ切替・ファイル復元用途は廃止予告されています。
git restore — Working Tree のファイルを復元する
誤って main.tf を編集してしまった場合、git restore で最後のコミット状態に戻せます。
# Working Tree の変更を破棄して最後のコミット状態に戻す
git restore main.tf
# Staging Area の変更をアンステージ(git add を取り消す)
git restore --staged main.tf
git switch — ブランチを切り替える
# 既存ブランチへの切替(詳細は §4 で解説)
git switch main
# 新規ブランチを作成して切替(-c は --create の短縮形)
git switch -c feature/add-vpc
git checkout は「ブランチ切替」「ファイル復元」「タグへのデタッチ」など複数の役割を持ち、オプションの意味が直感的でないため初学者が混乱しやすい点が問題視されていました。git switch / git restore への分割で、コマンドの意図が明確になっています。
§4 ブランチ運用 — feature branch / merge / コンフリクト解消
ブランチは Git の最も強力な機能のひとつです。本セクションでは feature ブランチを使った開発サイクル、2種類のマージ方式、そしてコンフリクトの解消手順を Terraform コードを題材に実践します。
4-1. ブランチとは — コミットを指すポインタ
ブランチの実体は「コミットを指すポインタ(参照)」です。ファイルをコピーするわけではありません。
コミット履歴:A ─── B ─── C
↑
main (ブランチポインタ)
↑
HEAD(現在の作業場所)
git switch -c feature/new でブランチを作成すると、同じコミット C を指す新しいポインタが増えます。feature ブランチ上でコミット D を作ると:
コミット履歴:A ─── B ─── C ─── D
↑↑
mainfeature/new
main は C を指したまま、feature/new だけが D に進みます。この独立性がブランチの本質です。
4-2. git branch / git switch -c — ブランチの作成と切替
EC2 インスタンスリソースを追加する feature ブランチを作成します。
# ブランチ一覧確認(* が現在のブランチ)
git branch
# * main
# feature/add-ec2 ブランチを作成して切替
git switch -c feature/add-ec2
# 出力: Switched to a new branch 'feature/add-ec2'
# 確認
git branch
# * feature/add-ec2
#main
git checkout -b と git switch -c の違い
git switch -c は git checkout -b と同じ動作をします。
| コマンド | 対応バージョン | 推奨度 |
|———|————–|——–|
| git switch -c | Git 2.23+ | 推奨(ブランチ操作専用) |
| git checkout -b | 全バージョン | 非推奨(多機能で混乱しやすい) |
git switch はブランチ切替に特化したコマンドです。git checkout の「ブランチ切替」と「ファイル復元」の2つの役割を分離するために導入されました。Git 2.23+ 環境では git switch を使い、git checkout のブランチ切替用途は置き換えていきましょう。
4-3. feature ブランチで Terraform コードを追加・コミット
feature ブランチ上で EC2 インスタンスリソースを追加します。
cat > ec2.tf << 'EOF'
resource "aws_instance" "web" {
ami = "ami-0d52744d6551d851e" # Amazon Linux 2023 (ap-northeast-1)
instance_type = "t3.micro"
tags = {
Name= "web-server"
ManagedBy = "Terraform"
}
}
EOF
git add ec2.tf
git commit -m "feat: EC2インスタンス(t3.micro)リソースを追加"
main に戻ると ec2.tf が消えます。これがブランチの独立性を示す実例です。
git switch main
ls
# .gitignore main.tf providers.tf variables.tf (ec2.tf は存在しない)
git switch main した瞬間に Working Tree が main の状態に切り替わっています。ファイルを物理的にコピーしているわけではなく、参照するコミットが変わっているだけなので、大規模なリポジトリでも高速にブランチを切り替えられます。
4-4. git merge — fast-forward マージ
main が feature ブランチの分岐点から進んでいない場合、Git は自動的に fast-forward マージを実行します。
# feature/add-s3 ブランチで S3 バージョニングを追加
git switch -c feature/add-s3
cat > modules/s3/main.tf << 'EOF'
resource "aws_s3_bucket" "this" {
bucket = var.bucket_name
tags= { ManagedBy = "Terraform" }
}
resource "aws_s3_bucket_versioning" "this" {
bucket = aws_s3_bucket.this.id
versioning_configuration { status = "Enabled" }
}
EOF
git add modules/s3/main.tf
git commit -m "feat: S3バケットのバージョニングを有効化"
# main に戻ってマージ
git switch main
git merge feature/add-s3
Updating a2b3c4d..7d8e9f0
Fast-forward
modules/s3/main.tf | 8 ++++++++
「Fast-forward」の文字が確認できます。マージコミットは生成されず、main のポインタが前進しただけです。
--no-ff でマージコミットを強制生成する
feature ブランチの存在を履歴に残したい場合は --no-ff を使います。
git switch main
git merge --no-ff feature/add-ec2 -m "Merge: EC2インスタンスリソースを追加"
| 方式 | マージコミット | 履歴の形状 | 向いている場面 |
|---|---|---|---|
| fast-forward | 生成されない | 直線状 | 小さな修正・個人作業 |
--no-ff | 必ず生成 | 分岐・合流が記録される | 機能ブランチ・PR マージ |
4-5. コンフリクト — 発生から解消まで
同じファイルの同じ行を2つのブランチが別々に変更すると、Git は自動マージできずコンフリクトを報告します。
コンフリクトを意図的に発生させる
# feature ブランチで instance_type を t3.small に変更
git switch -c feature/change-instance-type
sed -i 's/t3.micro/t3.small/' ec2.tf
git add ec2.tf
git commit -m "feat: instance_type を t3.small に変更"
# main でも同じ行を t3.medium に変更
git switch main
sed -i 's/t3.micro/t3.medium/' ec2.tf
git add ec2.tf
git commit -m "feat: instance_type を t3.medium に変更"
# マージを試みる
git merge --no-ff feature/change-instance-type
CONFLICT (content): Merge conflict in ec2.tf
Automatic merge failed; fix conflicts and then commit the result.
コンフリクトマーカーを読む
ec2.tf を開くと以下のマーカーが挿入されています。
<<<<<<< HEAD
instance_type = "t3.medium"
=======
instance_type = "t3.small"
>>>>>>> feature/change-instance-type
| マーカー | 意味 |
|---|---|
<<<<<<< HEAD | 現在のブランチ(main)の変更 |
======= | 2つの変更の区切り |
>>>>>>> feature/... | マージ対象ブランチの変更 |
マージコンフリクト放置の危険性
コンフリクトが発生したまま放置すると、マーカー文字列(<<<<<<<、=======、>>>>>>>)がそのままファイルに残ります。Terraform の場合、このファイルを terraform plan に渡すと構文エラーとなり、インフラの変更ができなくなります。
コンフリクトは発生したその場で解消することが鉄則です。解消に時間がかかる場合は git merge --abort で一旦マージ前の状態に戻し、チームメンバーと方針を確認してから改めてマージしてください。
コンフリクト解消の手順
# Step 1: ec2.tf をエディタで開き、マーカーをすべて削除して正しい値に修正
# (今回は t3.small を採用)
# マーカー3行を削除して instance_type = "t3.small" のみ残す
# Step 2: 解決済みをステージング
git add ec2.tf
# Step 3: 状態確認
git status
# All conflicts fixed but you are still merging.
# Step 4: マージコミットを作成
git commit -m "Merge: feature/change-instance-type — t3.small を採用"
解消後に git log --oneline --graph --all でブランチの分岐・合流を視覚的に確認できます。
4-6. feature branch ワークフロー全体像
Terraform リポジトリでの日常作業は以下のサイクルを繰り返します。
| ステップ | コマンド | 説明 |
|---|---|---|
| ① ブランチ作成 | git switch -c feature/xxx | main から feature を切る |
| ② コード編集 | (エディタ / Terraform コード) | リソース追加・修正 |
| ③ ステージング | git add <file> | コミット対象を選択 |
| ④ コミット | git commit -m "feat: ..." | スナップショットを記録 |
| ⑤ 状態確認 | git status / git log --oneline | 現在の状態と履歴を把握 |
| ⑥ マージ | git switch main && git merge --no-ff feature/xxx | main へ統合 |
| ⑦ ブランチ削除 | git branch -d feature/xxx | マージ済みブランチを整理 |
| → ①へ | 次の機能開発へ | サイクルを繰り返す |
原則は 「1つの論理的変更 = 1ブランチ = 複数の小さなコミット」 です。
4-7. Terraform workspace との対比
Git ブランチと Terraform workspace は似た概念ですが、役割が異なります。
| 概念 | Git ブランチ | Terraform workspace |
|---|---|---|
| 管理対象 | コードの変更履歴 | インフラの状態(tfstate) |
| 主な用途 | 開発・レビュー・コードの分離 | dev / stg / prod 環境の分離 |
| 切替コマンド | git switch <branch> | terraform workspace select <name> |
| 依存関係 | なし(独立) | 同じコードベースを参照 |
実務では「Git ブランチで機能開発を進め、マージ後に Terraform workspace を切り替えて各環境へ適用する」というパターンが一般的です。Git ブランチが「コードの次の状態」を管理し、Terraform workspace が「インフラの現在の状態」を分離します。
ブランチ命名規則 — Terraform リポジトリでの推奨例
| プレフィックス | 用途 | 例 |
|-------------|------|-----|
| feature/ | 新リソース追加 | feature/add-vpc, feature/add-rds |
| fix/ | 設定ミスの修正 | fix/sg-rule-cidr, fix/variable-type |
| chore/ | ドキュメント・ CI 整備 | chore/update-gitignore |
| refactor/ | 機能変更なしのコード整理 | refactor/extract-network-module |
ブランチ名に課題番号を含める(feature/issue-42-add-ec2)と、チケット管理システムとの連動が容易になります。
Section 5: 【実務編 前半】stash + cherry-pick + reflog
Section 1〜4 では Git の基本操作とブランチ管理を学びました。Section 5 では、実務で不可欠な3つのコマンド「stash(作業の一時退避)」「cherry-pick(特定コミットの取り込み)」「reflog(操作履歴から失ったコミットを救済)」を Terraform 開発シナリオで習得します。これら3つのコマンドは、毎日の開発作業で繰り返し使う実務必須ツールです。
5-1. git stash — 作業を一時退避して別ブランチへ切り替える
git stash は、コミットしていない変更(Working Tree と Staging Area の両方)を一時的に保存し、クリーンな状態で別の作業へ切り替えるコマンドです。「コミットする前に急な割り込み作業が入った」という場面で特に威力を発揮します。Terraform 作業中に緊急の hotfix 対応が発生したケースを例に、全サブコマンドを習得します。
5-1-1. git stash push -u — untracked ファイルも含めて退避
# 現在の状態(main.tf を編集途中、かつ未追跡ファイルも存在)
git status
# On branch feature/add-rds
# Changes not staged for commit:
#modified:main.tf
# Untracked files:
#variables_new.tf
# -u オプション: untracked ファイルも含めて退避する(★実務では必須)
# -u なしだと variables_new.tf のような未追跡ファイルが残り、ワークツリーがクリーンにならない
git stash push -u -m "WIP: RDS モジュール追加(途中)"
# Saved working directory and index state On feature/add-rds: WIP: RDS モジュール追加(途中)
# ワークツリーがクリーンになったことを確認
git status
# On branch feature/add-rds
# nothing to commit, working tree clean
-u(--include-untracked)オプションがなぜ重要かというと、Terraform 開発では新しいリソース定義ファイル(例: modules/rds/main.tf)を作成した直後はまだ git add していないことが多いためです。-u なしでは、これらの未追跡ファイルがそのまま残ってしまいます。
5-1-2. git stash list / show -p — 一覧確認と差分表示
# stash の一覧表示(複数の stash が積み重なっている場合)
git stash list
# stash@{0}: On feature/add-rds: WIP: RDS モジュール追加(途中)
# stash@{1}: On feature/vpc: WIP: サブネット追加中
# stash@{2}: WIP on main: hotfix 一時退避
# 最新 stash の概要確認(変更ファイル一覧)
git stash show
# main.tf| 12 ++++++++++++
# variables_new.tf |8 ++++++++
# 2 files changed, 20 insertions(+)
# 特定 stash の詳細差分を確認する(-p でパッチ形式)
git stash show -p stash@{0}
# diff --git a/main.tf b/main.tf
# index a1b2c3d..d4e5f6g 100644
# --- a/main.tf
# +++ b/main.tf
# @@ -10,3 +10,12 @@
# +resource "aws_db_instance" "this" {
# + allocated_storage = 20
# + engine= "mysql"
# + instance_class = "db.t3.micro"
# +}
5-1-3. git stash pop vs git stash apply — 適用時の使い分け
# stash pop: stash を適用して stash リストから削除する(通常はこちら)
git stash pop
# On branch feature/add-rds
# Changes not staged for commit:
#modified:main.tf
# Untracked files:
#variables_new.tf
# Dropped stash@{0} (abc123...)
# stash apply: stash を適用するが stash リストには残す
# → 同じ変更を複数ブランチへ適用したい場合に使う
git stash apply stash@{0}
# 適用後も stash@{0} はリストに残る(手動で削除が必要)
# stash pop でコンフリクトが発生した場合
git stash pop
# CONFLICT (content): Merge conflict in main.tf
# The stash entry is kept in case you need it again.
# コンフリクト解消後、手動でステージングして処理を完結させる
git add main.tf
git stash drop # コンフリクト解消後、手動で stash エントリを削除
pop と apply の使い分けのポイント: 1回だけ適用する通常の復元には pop、同じ stash を複数の場所に適用する場合は apply を使います。
5-1-4. git stash drop / git stash clear — 削除
# 特定の stash を削除する(インデックスを指定)
git stash drop stash@{1}
# Dropped stash@{1} (def456...)
# 最新の stash を削除する(インデックス省略)
git stash drop
# Dropped stash@{0} (abc123...)
# 全 stash を一括削除する(★取り消し不可・要注意)
git stash clear
# すべての stash が削除される(確認メッセージなし)
git stash drop や git stash clear で削除した stash は、reflog でも救済できません。stash の内部実装はコミットオブジェクトとして保持されますが、stash リストから外れた参照は GC(ガベージコレクション)対象となり、通常の reflog には表示されません。削除前に必ず git stash show -p stash@{N} で差分を確認する習慣をつけてください。git stash push -m "わかりやすいメッセージ" でラベルを付けると、git stash list の出力が格段に読みやすくなります。ラベルなしの場合は WIP on feature/add-rds: a1b2c3d feat: ... のように直前のコミットメッセージが表示されますが、複数の stash が積み重なるとどれが何の変更か判別困難になります。-m オプションは常に付ける習慣にしましょう。5-1-5. git stash branch — stash から新ブランチを作成する
退避した変更を元のブランチではなく新しいブランチで再開したい場合に便利なコマンドです。
# stash@{0} の変更を新ブランチ feature/rds-v2 として展開する
git stash branch feature/rds-v2 stash@{0}
# Switched to a new branch 'feature/rds-v2'
# On branch feature/rds-v2
# Changes not staged for commit:
#modified:main.tf
# Untracked files:
#variables_new.tf
# Dropped stash@{0} (abc123...)
# stash branch は以下の3操作を一括実行してくれる
# 1. stash を適用したときの commit(stash の親コミット)からブランチを作成
# 2. stash を apply する
# 3. 成功したら stash を drop する
# → 元のブランチでコンフリクトが起きたときに特に有効
5-1-6. Terraform 作業中断シナリオ(main.tf 編集中に hotfix 対応)
実務で最も頻繁に発生するシナリオです。feature/add-rds で main.tf を編集中に、本番障害の hotfix 対応を求められた場合の手順を示します。
# ① 現在の作業を stash に退避(untracked ファイルも含める)
git stash push -u -m "WIP: RDS インスタンス追加"
# Saved working directory and index state On feature/add-rds: WIP: RDS インスタンス追加
# ② main ブランチへ切り替え
git switch main
# Switched to branch 'main'
# ③ hotfix ブランチを作成して修正をコミット
git switch -c hotfix/fix-security-group
# ... security_group.tf を修正 ...
git add security_group.tf
git commit -m "fix: tighten security group ingress rules"
# ④ hotfix を main にマージ
git switch main
git merge --no-ff hotfix/fix-security-group -m "Merge: hotfix/fix-security-group"
git branch -d hotfix/fix-security-group
# ⑤ 元の feature ブランチへ戻り、stash を復元
git switch feature/add-rds
git stash pop
# On branch feature/add-rds
# Changes not staged for commit:
#modified:main.tf
# Untracked files:
#variables_new.tf
# RDS 追加作業を再開できる状態に戻った
5-2. git cherry-pick — 特定コミットを別ブランチへ取り込む
git cherry-pick は、他のブランチの特定のコミットを現在のブランチへ「摘み取る」コマンドです。hotfix を main と複数のリリースブランチに適用するシナリオや、誤ったブランチにコミットしてしまった場合の修正に使います。
5-2-1. 基本的な cherry-pick と -x オプション
# main ブランチで hotfix コミットのハッシュを確認
git log --oneline main
# a3f9c12 fix: tighten security group ingress rules
# b2e8b01 feat: add RDS module
# c1d7a00 chore: initial commit
# feature/add-rds ブランチへ hotfix コミットを取り込む
git switch feature/add-rds
git cherry-pick a3f9c12 -x
# [feature/add-rds d5f0e23] fix: tighten security group ingress rules
# Date: Thu May 15 15:00:00 2026 +0900
# 1 file changed, 3 insertions(+), 1 deletion(-)
# -x オプションの効果: コミットメッセージに出自が追記される
git log --oneline -3
# d5f0e23 fix: tighten security group ingress rules
#(cherry picked from commit a3f9c12)
# b4c3d2e WIP: RDS インスタンス追加
# c1d7a00 chore: initial commit
-x オプションはコミットメッセージに (cherry picked from commit <hash>) を追記します。後から「このコミットはどこから取り込んだものか」を追跡できるため、チーム開発では常に付けることを推奨します。
5-2-2. コンフリクト発生時の解消フロー
# cherry-pick でコンフリクトが発生した場合
git cherry-pick a3f9c12 -x
# error: could not apply a3f9c12...
# hint: After resolving the conflicts, mark them with
# hint:git add <pathspec>
# hint: and run
# hint:git cherry-pick --continue
# コンフリクト状態の確認
git status
# You are currently cherry-picking commit a3f9c12.
#(fix conflicts and run "git cherry-pick --continue")
# Unmerged paths:
#both modified:security_group.tf
# ① コンフリクトマーカーを手動で解消する
# <<<<<<< HEAD
# ingress_cidr = "10.0.0.0/8"
# =======
# ingress_cidr = "10.0.0.0/24"
# >>>>>>> a3f9c12... fix: tighten security group ingress rules
# → 正しい内容に修正する
# ② 解消したファイルをステージング
git add security_group.tf
# ③ cherry-pick を継続
git cherry-pick --continue
# エディタが開く(コミットメッセージを確認・修正できる)
# 途中でやめたい場合はアボート(cherry-pick 前の状態に完全に戻る)
git cherry-pick --abort
5-2-3. 複数コミットの cherry-pick
# A..B 形式: A の次(A は含まない)から B まで連続して取り込む
git cherry-pick a3f9c12..d5f0e23
# a3f9c12 の次のコミットから d5f0e23 まで順番に適用される
# A を含めたい場合は A^ を使う
git cherry-pick a3f9c12^..d5f0e23
# 複数の不連続なコミットを個別に指定(スペース区切り)
git cherry-pick abc1234 def5678 ghi9012
# 3つのコミットが順番に適用される
cherry-pick は「同じ変更内容を持つ新しいコミット(異なるハッシュ)」を作成します。そのため、後で元のブランチをマージすると同じ変更が二重に存在することになります。Git は内容が同一であれば自動でスキップすることもありますが、確実ではありません。
-x オプションで出自を記録しておくことでレビュー時に気づきやすくなりますが、cherry-pick は一時的な対処手段として使い、最終的には適切なブランチ戦略(rebase やマージ)で統合することを推奨します。5-2-4. Terraform hotfix を main に取り込むシナリオ
# 状況: release/v1.2 ブランチで適用した hotfix を main にも取り込む
# release/v1.2 での hotfix コミットを確認
git log --oneline release/v1.2 -5
# f7a8b9c fix: correct IAM role ARN in Lambda function
# e6d7c8b feat: add CloudWatch alarms for RDS
# main へ切り替えて cherry-pick を適用
git switch main
git cherry-pick f7a8b9c -x
# [main 1a2b3c4] fix: correct IAM role ARN in Lambda function
# (cherry picked from commit f7a8b9c)
# terraform plan で差分が意図通りかを確認
terraform plan
# Plan: 0 to add, 1 to change, 0 to destroy.
# ~ aws_lambda_function.handler # role ARN が修正される
# 意図した変更のみであることを確認して apply
terraform apply
5-3. git reflog — 失われたコミットを救済する
git reflog は、HEAD が移動した全操作履歴を記録したコマンドです。git reset --hard によるコミットの消失、誤ったブランチ削除でコミットが見えなくなった場合でも、reflog に記録が残っていれば復旧できます。Git を使う上での「最後の安全網」です。
5-3-1. reflog の基本構造
# HEAD の移動履歴を確認(最新順に表示される)
git reflog
# d5f0e23 HEAD@{0}: cherry-pick: fix: tighten security group
# b4c3d2e HEAD@{1}: commit: feat: add RDS module variables
# a3f9c12 HEAD@{2}: reset: moving to HEAD^
# c1d7a00 HEAD@{3}: commit (initial): chore: initial commit
# 9e8f7a6 HEAD@{4}: branch: Created from HEAD
# 各行の構造:
# <ハッシュ> HEAD@{N}: <操作の種類>: <説明>
# N は操作の古さを示す(0 が最新)
# 特定ブランチの reflog も確認できる
git reflog show main
# a3f9c12 main@{0}: merge hotfix/fix-security-group: Merge made by ...
# c1d7a00 main@{1}: commit: chore: initial commit
# 操作種別ごとに絞り込み(grep との組み合わせ)
git reflog | grep "reset"
# a3f9c12 HEAD@{2}: reset: moving to HEAD^
5-3-2. git reset --hard 後の復旧手順
# 誤って git reset --hard を実行してしまった場面
git log --oneline
# b4c3d2e (HEAD -> feature/add-rds) feat: add RDS module variables
# c1d7a00 chore: initial commit
git reset --hard HEAD~2
# HEAD is now at 9e8f7a6 WIP on main: hotfix 一時退避
# ← 2つのコミットが消えてしまったように見える
git log --oneline
# 9e8f7a6 (HEAD -> feature/add-rds) WIP on main...
# ← b4c3d2e と c1d7a00 が見えなくなった
# reflog で失われたコミットのハッシュを探す
git reflog
# 9e8f7a6 HEAD@{0}: reset: moving to HEAD~2 ← 今ここ
# b4c3d2e HEAD@{1}: commit: feat: add RDS module variables ← 元の HEAD
# c1d7a00 HEAD@{2}: commit (initial): chore: initial commit
# b4c3d2e が元の HEAD だったことがわかった → reset で戻す
git reset --hard b4c3d2e
# HEAD is now at b4c3d2e feat: add RDS module variables
# コミットが完全に復旧した!
git log --oneline
# b4c3d2e (HEAD -> feature/add-rds) feat: add RDS module variables
# c1d7a00 chore: initial commit
5-3-3. git checkout HEAD@{N} で過去状態を確認する
# HEAD@{3} の時点のファイル状態を確認(detached HEAD で読み取り専用)
git checkout HEAD@{3}
# Note: switching to 'HEAD@{3}'.
# You are in 'detached HEAD' state.
# You can look around, make experimental changes and commit them,
# but these changes won't affect any branch.
# HEAD is now at c1d7a00
# この状態でファイルの内容を確認
cat main.tf
# → 当時の内容が表示される
# 変更を加えずに元のブランチに戻る
git switch -
# Switched to branch 'feature/add-rds'
# Previous HEAD position was c1d7a00
# 過去の内容を現在のブランチにコピーしたい場合
git checkout HEAD@{3} -- main.tf
# → main.tf だけを HEAD@{3} 時点の内容に戻す(ブランチは変わらない)
git diff --staged main.tf # 変更内容を確認
reflog のエントリはデフォルトで 90 日後に自動削除されます(到達不能なコミットはさらに短い 30 日)。90 日を超えると
git reflog に表示されなくなり、git gc の実行で完全に消去されます。reset --hard などで失ったコミットに気づいたら、すぐに reflog で確認・復旧することが重要です。また、stash の誤削除(5-1-4 参照)は reflog でも救えない点に注意してください。特定リポジトリで reflog を永続化したい場合は以下のコマンドを使います。
git config gc.reflogExpire nevergit config gc.reflogExpireUnreachable neverまたは一度だけ実行する場合:
git reflog expire --expire=never --allただし、このオプションはリポジトリサイズの増大につながります。長期的なバックアップが必要な場合は、リモートリポジトリへのプッシュや
git bundle によるバックアップを検討してください。5-3-4. reflog を使った削除済みブランチのコミット救済
git branch -d や git branch -D で削除したブランチのコミットも、reflog から復旧できます。
# 誤ってブランチを削除してしまった場面
git branch -D feature/important-work
# Deleted branch feature/important-work (was e9f1a2b).
# → e9f1a2b というヒントが表示される場合はそのハッシュを使う
# reflog で削除前の最終コミットを探す
git reflog
# e9f1a2b HEAD@{5}: commit: feat: add important Terraform resource
# ...
# 削除されたコミットから新しいブランチを再作成して復旧
git switch -c feature/important-work-restored e9f1a2b
# Switched to a new branch 'feature/important-work-restored'
# → 削除されたブランチの全コミットが復旧した
git log --oneline
# e9f1a2b (HEAD -> feature/important-work-restored) feat: add important Terraform resource
# b4c3d2e chore: initial setup
ブランチの削除は比較的安全で、コミット自体は reflog が保持している限り失われません。ただし reflog の 90 日制限(前述)は同様に適用されます。
5-4. Mermaid01: stash / cherry-pick / reflog ワークフロー図
以下のシーケンス図は、「Terraform 作業中断 → stash 退避 → hotfix 対応 → stash 復元 → cherry-pick による取り込み」の一連の流れと、reflog の補足を示します。
sequenceDiagram
participant Dev as Developer
participant WD as Working Directory
participant Stash as git stash
participant Feature as feature/branch
participant Main as main
Dev->>WD: main.tf 編集中
Dev->>Stash: git stash push -u
Dev->>Main: git switch main (hotfix)
Dev->>Main: hotfix commit
Dev->>Feature: git switch feature/branch
Dev->>Feature: git cherry-pick <hotfix-hash> -x
Stash-->>WD: git stash pop (作業再開)
Note over Dev,Stash: stash drop誤削除→reflogで救済不可
この図で重要なのは以下の3点です。
- stash は作業の一時退避: コミットせずに別ブランチへ切り替えるための安全装置。
-uオプションで未追跡ファイルも退避 - cherry-pick は選択的な取り込み: main への hotfix を feature ブランチにも適用する場合に使う。
-xオプションで出自を記録 - reflog は最後の砦:
reset --hardなどで失ったコミットを 90 日以内であれば救済できる
5-5. まとめ: stash / cherry-pick / reflog の使い分け
| 状況 | 使うコマンド | 注意点 |
|---|---|---|
| コミット前の変更を一時退避したい | git stash push -u -m "..." | clear / drop は取り消し不可 |
| 退避した変更を一覧表示したい | git stash list | ラベルなしだと判別困難 |
| stash の差分内容を確認したい | git stash show -p stash@{N} | 削除前に必ず確認 |
| 退避した変更を元に戻したい | git stash pop | コンフリクト時は手動解消が必要 |
| 同じ変更を複数ブランチへ適用したい | git stash apply | リストには残るため別途 drop が必要 |
| stash の変更を新ブランチで再開したい | git stash branch <name> | apply + drop を一括実行 |
| 特定コミットだけ別ブランチへ取り込む | git cherry-pick <hash> -x | 重複コミットに注意 |
| cherry-pick のコンフリクトを解消する | 手動解消 → git cherry-pick --continue | --abort でキャンセル可 |
reset --hard 後に復旧したい | git reflog + git reset --hard HEAD@{N} | 90 日以内が条件 |
| 消えた変更の内容確認だけしたい | git checkout HEAD@{N} | detached HEAD → git switch - で戻る |
| 特定ファイルだけ過去の状態に戻したい | git checkout HEAD@{N} -- <file> | ファイルはステージングに入る |
Section 6: 実務編 後半 — rebase -i / bisect / worktree / blame
Section 1〜5 で Git の基礎から初期設定、ファイル管理、ブランチ操作、マージまでを習得しました。Section 6 では実務で差がつく上級コマンドを集中的に学びます。git rebase -i でコミット履歴を整形し、git bisect でバグ混入コミットを素早く特定し、git worktree で複数ブランチを並列作業し、git blame で変更者を追跡する——これらのコマンドを Terraform 開発の文脈で身につけましょう。
6-1. git rebase -i — インタラクティブリベースでコミット履歴を整形する
git rebase -i(インタラクティブリベース)は、push 前のローカルコミット履歴をきれいに整形するための最強ツールです。複数の雑なコミットを 1 つにまとめたり、メッセージを修正したり、不要なコミットを削除したりできます。
基本構文
# 直近 N 件のコミットを対象に対話的なリベースを開始する
git rebase -i HEAD~N
HEAD~3 なら直近 3 件のコミットが対象です。コマンド実行後、エディタが開き、各コミットに対するアクション(pick / squash / fixup / reword / edit / drop)を指定します。
アクション一覧
| アクション | 略語 | 説明 |
|---|---|---|
pick | p | そのままコミットを採用する(デフォルト) |
reword | r | コミットを採用するがメッセージを書き直す |
edit | e | コミットを採用した後、追加作業を挟む |
squash | s | 直前のコミットと統合し、メッセージも統合する |
fixup | f | 直前のコミットと統合するが、メッセージは破棄する |
drop | d | そのコミットを削除する |
ハンズオン: Terraform コミット履歴の整形
Terraform 開発中によくある「WIP コミットが散らかった状態」を整理するシナリオです。
# 現在の雑な履歴を確認する
git log --oneline -5
# 出力例:
# a4f8c1b fix: typo
# 3d7e2a9 wip: vpc試行錯誤
# 9b1c4f2 wip: まだ途中
# 7e3a8d1 feat: variables.tf追加
# 2c5b9e0 chore: add .gitignore
# 直近 3 件(fix typo / wip×2)を squash でまとめる
git rebase -i HEAD~3
エディタが開いたら、以下のように書き換えます:
# 変更前(エディタ初期状態)
pick 9b1c4f2 wip: まだ途中
pick 3d7e2a9 wip: vpc試行錯誤
pick a4f8c1b fix: typo
# 変更後(squash/fixup で統合)
pick 9b1c4f2 wip: まだ途中
squash 3d7e2a9 wip: vpc試行錯誤
fixup a4f8c1b fix: typo
保存・終了するとメッセージ統合画面が開きます。最終メッセージを feat: add VPC configuration などに書き直して保存すれば、3 件が 1 件にまとまります。
# 整形後の履歴を確認する
git log --oneline -3
# 出力例:
# b2e7a4c feat: add VPC configuration
# 7e3a8d1 feat: variables.tf追加
# 2c5b9e0 chore: add .gitignore
--autosquash: git commit --fixup との連携
git commit --fixup <commit-hash> で「修正対象コミット」を明示してコミットすると、--autosquash フラグで自動的に並び替えと fixup 指定が行われます。
# 7e3a8d1 の修正コミットを作成する
git add variables.tf
git commit --fixup 7e3a8d1
# コミットメッセージが自動で "fixup! feat: variables.tf追加" になる
# rebase -i --autosquash で自動整列する
git rebase -i --autosquash HEAD~3
# エディタが開いた時点で fixup が正しい位置に自動配置されている
警告: push 済みコミットへの rebase -i は絶対禁止
git push 済みのコミットに対して git rebase -i を実行すると、コミットハッシュが書き換わります。その後 git push --force でリモートに強制上書きすると、チームメンバーのローカル履歴と乖離し、全員の git pull がコンフリクトする「force-push 災害」が発生します。
rebase -i は必ず push 前のローカルのみ で使用してください。git log --oneline origin/main..HEAD でローカルのみのコミットを事前確認するのが安全です。
情報: git rebase --onto で特定コミットを別ブランチに移植する
# feature/vpc ブランチの特定範囲を main に移植する
git rebase --onto main feature/base feature/vpc
--onto は、upstream〜branch 間のコミットを newbase の先頭に付け替えます。「誤ったブランチから切った feature を正しいブランチに移植したい」ときに有効です。
6-2. git bisect — 二分探索でバグ混入コミットを特定する
git bisect は「いつバグが混入したか」を二分探索で高速特定するコマンドです。100 件のコミット履歴があっても、最大 7 回の動作確認で原因コミットを特定できます(2^7 = 128)。
基本サブコマンド
| サブコマンド | 説明 |
|---|---|
git bisect start | 二分探索を開始する |
git bisect bad [commit] | 問題のあるコミットを指定する(省略時は HEAD) |
git bisect good [commit] | 問題のないコミットを指定する |
git bisect reset | 二分探索を終了して元のブランチに戻る |
git bisect run <script> | テストスクリプトで自動二分探索する |
ハンズオン: Terraform apply が壊れたコミットを特定する
# ① 二分探索を開始する
git bisect start
# ② 現在(HEAD)がバグあり状態であることを宣言する
git bisect bad
# ③ 2週間前のコミット(正常だったことが分かっている)をgoodに指定する
git bisect good v1.2.0
# 出力例:
# Bisecting: 47 revisions left to test after this (roughly 6 steps)
# [3a8f9c2] feat: add ECS cluster module
Git が自動的に中間コミットをチェックアウトします。そのコミットで Terraform を実行して良否を判断します。
# ④ チェックアウトされたコミットで terraform plan を実行して確認する
terraform plan
# plan が成功(正常)なら
git bisect good
# 出力例:
# Bisecting: 23 revisions left to test after this (roughly 5 steps)
# plan が失敗(バグあり)なら
git bisect bad
Good / Bad の繰り返しで Git が範囲を絞り込み、最終的に原因コミットを特定します。
# ⑤ 最終的に原因コミットが特定される
# 出力例:
# a7c3e1f is the first bad commit
# commit a7c3e1f
# Author: dev <dev@example.com>
# Date:Thu May 8 14:30:00 2026 +0900
#
# feat: modify security group rules
# ⑥ 二分探索を終了して元のブランチに戻る(必須)
git bisect reset
# 出力: HEAD is now at 8d2f1a3 feat: add VPC configuration
git bisect run — スクリプトで全自動化する
テストスクリプトが存在する場合、bisect run で人手不要の自動二分探索が可能です。
# terraform validate が通るかどうかをテストするスクリプト (test_tf.sh)
cat > test_tf.sh << 'EOF'
#!/bin/bash
terraform init -backend=false -no-color 2>/dev/null
terraform validate -no-color
EOF
chmod +x test_tf.sh
# bisect を開始して自動実行する
git bisect start
git bisect bad HEAD
git bisect good v1.2.0
git bisect run ./test_tf.sh
# スクリプトが exit 0 なら good、非ゼロなら bad として自動判定する
# 完了後は必ず reset する
git bisect reset
スクリプトの終了コードで good(0)と bad(1〜127)を自動判定します。CI に組み込んで「どの PR でテストが壊れたか」を自動特定するユースケースにも活用できます。

注意: bisect 後は必ず git bisect reset する
git bisect 中は Git が内部状態(.git/BISECT_* ファイル)を保持しており、通常の git switch や git checkout が制限されます。原因コミットが特定できたら、作業の前に必ず git bisect reset を実行してください。reset しないまま作業を続けると、誤ったブランチで変更が入り混じる事故が発生します。
# bisect 状態のまま作業しようとするとエラーが出る場合がある
git switch main
# warning: you are in 'detached HEAD' state.
# 必ず reset してから作業する
git bisect reset
git switch main
6-3. git worktree — 複数ブランチを同時に並列作業する
git worktree は、1 つのリポジトリを複数のディレクトリに同時展開する機能です。「feature ブランチを開発しながら、緊急の hotfix も並行して対応したい」というシナリオで真価を発揮します。
基本サブコマンド
| サブコマンド | 説明 |
|---|---|
git worktree add <path> <branch> | 指定パスに指定ブランチの作業ツリーを追加する |
git worktree list | 現在の全 worktree 一覧を表示する |
git worktree remove <path> | worktree を削除する |
ハンズオン: feature 開発と hotfix を並列作業する
Terraform で VPC 機能追加ブランチを開発中に、本番の Security Group に緊急修正が必要になったシナリオです。
# 現在は feature/vpc ブランチで作業中(stash 不要)
git branch
# * feature/vpc
#main
# hotfix ブランチ用の worktree を ../terraform-hotfix に追加する
git worktree add ../terraform-hotfix -b hotfix/sg-cidr main
# 出力:
# Preparing worktree (new branch 'hotfix/sg-cidr')
# HEAD is now at 8d2f1a3 feat: add VPC configuration
# worktree 一覧を確認する
git worktree list
# 出力例:
# /home/dev/terraform-repo 8d2f1a3 [feature/vpc]
# /home/dev/terraform-hotfix 8d2f1a3 [hotfix/sg-cidr]
# hotfix 作業ディレクトリに移動して修正する
cd ../terraform-hotfix
# Security Group の CIDR を修正する
vi security_group.tf
# コミットしてメインリポジトリに戻る
git add security_group.tf
git commit -m "fix: restrict security group ingress to /32"
# メインの作業ディレクトリに戻って feature 開発を継続する
cd ../terraform-repo
# feature/vpc の作業はそのまま継続できる
Terraform plan を worktree で並列実行する
worktree は Terraform の並列 plan 実行にも有効です。
# staging 環境用と production 環境用の worktree を並列に追加する
git worktree add ../terraform-staging staging
git worktree add ../terraform-production production
# 並列で terraform plan を実行する(ターミナル分割やバックグラウンド実行)
(cd ../terraform-staging && terraform plan -var-file=staging.tfvars > /tmp/plan-staging.txt) &
(cd ../terraform-production && terraform plan -var-file=production.tfvars > /tmp/plan-production.txt) &
wait
# 結果を比較する
diff /tmp/plan-staging.txt /tmp/plan-production.txt
worktree の後片付け
# 不要になった worktree を削除する
git worktree remove ../terraform-hotfix
# 出力:
# Removing worktrees/terraform-hotfix...
# 全 worktree の状態を確認する
git worktree list
# 出力:
# /home/dev/terraform-repo 8d2f1a3 [feature/vpc]
情報: worktree で同一リポジトリを複数ディレクトリに展開する仕組み
git worktree add で追加された作業ツリーは、メインの .git ディレクトリを共有します。それぞれの worktree には専用の HEAD ファイルと index が存在し、ブランチを独立して管理します。.git/worktrees/ 配下に各 worktree のメタデータが格納されます。
# worktree のメタデータ場所を確認する
ls .git/worktrees/
# terraform-hotfix/ terraform-staging/
コミット・ブランチ操作はメインリポジトリと完全に共有されるため、どの worktree でコミットしても git log には同一の履歴として現れます。
警告: worktree のロックと残留ファイルに注意する
git worktree remove を実行せずにディレクトリを手動削除すると、.git/worktrees/ に残骸が残ります。この状態では git worktree list に削除済みの worktree が (prunable) として表示され続けます。
# 残骸を一括クリーンアップする
git worktree prune
# 削除後に一覧を確認する
git worktree list
また、同一ブランチを 2 つの worktree で同時にチェックアウトすることはできません。「already checked out」エラーが出た場合は、git worktree list で競合を確認してください。
6-4. git blame / log -p / log --follow — 変更者を追跡する
「この Terraform モジュールの変数、誰がいつ変えたの?」——git blame と git log -p はコードの変更履歴と変更者を行レベルで追跡するためのコマンドです。
git blame — 行ごとの変更者とコミットを確認する
# variables.tf の各行を最後に変更したコミットと変更者を表示する
git blame variables.tf
# 出力例:
# a4f8c1b (Taro Yamada 2026-05-01 10:23:15 +0900 1) variable "region" {
# 3d7e2a9 (Hanako Sato 2026-05-03 14:45:30 +0900 2)default = "ap-northeast-1"
# 9b1c4f2 (Taro Yamada 2026-05-05 09:12:00 +0900 3) }
# 特定の行範囲だけを対象にする
git blame -L 10,25 variables.tf
# 短いハッシュ表示にする
git blame --abbrev=8 variables.tf
git log -p — 差分つきのコミット詳細を確認する
# 特定ファイルのコミット履歴を差分付きで表示する
git log -p variables.tf
# 直近 3 件だけ表示する
git log -p -3 variables.tf
# 特定コミットの詳細差分を確認する
git show a4f8c1b
git log --follow — ファイル改名を跨いだ履歴を追跡する
Terraform のリファクタリングでファイルを改名した場合も、--follow なら改名前後の履歴を一覧できます。
# ファイル改名後でも旧名称からの履歴を追跡する
git log --follow -p modules/vpc/main.tf
# network.tf → modules/vpc/main.tf に改名された場合でも、旧ファイルのコミットまで遡って表示する
ハンズオン: Terraform モジュール変更の犯人探し
# terraform.tfvars の変更者を確認する
git blame terraform.tfvars
# 出力例:
# b7a9c3f (Jiro Tanaka 2026-05-10 16:30:00 +0900 1) region = "us-east-1"
# ↑ 本来 ap-northeast-1 のはずが us-east-1 に変更されている
# 変更コミットの詳細を確認する
git show b7a9c3f
# diff --git a/terraform.tfvars b/terraform.tfvars
# -region = "ap-northeast-1"
# +region = "us-east-1"
# 変更の背景をコミット履歴で確認する
git log --oneline --follow terraform.tfvars
情報: git blame -w で空白のみの変更を無視する
コードフォーマッターや indent 整理による空白変更は、blame の結果を汚します。-w フラグで空白変更を無視することで、実質的なロジック変更の担当者を正確に特定できます。
# 空白変更を無視してblameを実行する
git blame -w main.tf
# 複数オプションの組み合わせ(空白無視 + 短縮ハッシュ + 行範囲指定)
git blame -w --abbrev=8 -L 5,30 main.tf
コードレビューや障害対応時に、フォーマッタによる一括変更コミットがノイズになる場合に特に有効です。
6-5. Mermaid02: rebase -i と bisect の Git 履歴遷移図
git rebase -i でコミットを squash する前後の履歴変化と、git bisect が二分探索で絞り込む様子を図示します。
rebase -i による履歴整形の前後比較
gitGraph
commit id: "A: init"
commit id: "B: feat: variables.tf"
commit id: "C: wip: まだ途中"
commit id: "D: wip: vpc試行錯誤"
commit id: "E: fix: typo"
上記(rebase -i 前)の C / D / E を squash + fixup でまとめると、以下のようにすっきりします。
gitGraph
commit id: "A: init"
commit id: "B: feat: variables.tf"
commit id: "C': feat: add VPC configuration"
3 件が 1 件に統合され、C' は新しいハッシュを持ちます。push 済みコミットに適用すると B 以降の履歴が全員に影響するため、ローカル push 前のみ実行が鉄則です。
git bisect による二分探索の絞り込みイメージ
| ステップ | 対象範囲 | 判定 |
|---|---|---|
| bisect start | コミット 1〜48 | — |
| 1 回目 | コミット 24 を確認 | good → 25〜48 を対象に |
| 2 回目 | コミット 36 を確認 | bad → 25〜35 を対象に |
| 3 回目 | コミット 30 を確認 | good → 31〜35 を対象に |
| 4 回目 | コミット 33 を確認 | bad → 31〜32 を対象に |
| 5 回目 | コミット 31 を確認 | good → コミット 32 が確定 |
| 完了 | コミット 32 が first bad commit | bisect reset で終了 |
48 件のコミットを 5 回の確認で特定できます。手動で 1 件ずつ確認するより大幅に効率が上がります。
6-6. Section 6 まとめ — 実務後半コマンド早見表
| コマンド | 主な用途 | 注意点 |
|---|---|---|
git rebase -i HEAD~N | push 前のコミット整形 | push 済みには絶対使わない |
git rebase -i --autosquash | --fixup コミットを自動整列 | git commit --fixup と組み合わせる |
git bisect start/good/bad | バグ混入コミットの二分探索 | 終了後は必ず git bisect reset |
git bisect run <script> | テストスクリプトによる自動二分探索 | exit 0 = good、非ゼロ = bad |
git worktree add <path> <branch> | 複数ブランチの並列作業 | 同一ブランチの二重チェックアウト不可 |
git worktree list | 全 worktree の状態確認 | (prunable) は git worktree prune で削除 |
git worktree remove <path> | worktree の削除 | 手動削除は残骸が残るため非推奨 |
git blame <file> | 行ごとの変更者・コミット確認 | -w で空白変更を無視できる |
git log -p <file> | 差分付きのコミット詳細確認 | -N で件数を絞れる |
git log --follow <file> | 改名を跨いだ履歴追跡 | Terraform リファクタリング時に有効 |
これらのコマンドを組み合わせることで、Terraform リポジトリの履歴管理・障害対応・並列開発が大幅に効率化します。第2弾では Pull Request / GitHub Actions / ブランチ戦略をさらに深掘りします。
§7: Gitで詰まるポイント10選 + アンチパターン演習
Section 1〜6で Git の基本操作を体験した後、実際の開発現場でよく遭遇する「あるある詰まりポイント」を 10 件まとめます。Terraform コードの運用で特に引っかかりやすいケースを中心に、原因・解消手順・再発防止策をセットで解説します。
7-1. detached HEAD の原因と解消
git checkout <コミットハッシュ> や git checkout <タグ> を実行すると、ブランチではなく特定のコミットを直接参照する "detached HEAD" 状態になります。この状態でコミットすると、ブランチに紐づかない「迷子コミット」が生まれ、後から参照できなくなります。
# 意図せず detached HEAD になってしまう操作例
git checkout abc1234 # コミットハッシュを直接指定
git checkout v1.0.0# タグを直接チェックアウト
# 状態確認
git status
# HEAD detached at abc1234
# nothing to commit, working tree clean
# 解消手順: 既存ブランチに戻る
git switch main
# 解消手順: detached HEAD 上のコミットを残したい場合
# まずブランチを作成してからコミットを保存する
git switch -c rescue/detached-work
git switch main
git merge --no-ff rescue/detached-work
detached HEAD 状態でコミットしてから
git switch main すると、そのコミットはどのブランチにも属さず、ガベージコレクションで削除されます。git reflog で 30 日間は復元可能ですが、気づかないまま失うケースが多いです。git checkout の代わりに git switch を使えば、誤って detached HEAD になるリスクを大幅に減らせます。7-2. force-push の災害
チームリポジトリで git push --force を実行すると、リモートの履歴が強制上書きされ、他のメンバーのローカルリポジトリと履歴が乖離します。最悪の場合、他者のコミットが失われます。
# 危険: --force でリモート履歴を上書き
git push --force origin main# チームリポジトリでは禁止
# 安全: --force-with-lease を使う(リモートに自分が知らないコミットがあれば失敗)
git push --force-with-lease origin feature/my-branch
# --force-with-lease が拒否した場合
# error: failed to push some refs
# まずリモートの変更を確認する
git fetch origin
git log --oneline origin/feature/my-branch
feature ブランチでの rebase 後に
--force-with-lease を使うのは許容されますが、main/master ブランチへの force-push は、チーム全員のローカルリポジトリを壊します。GitHub の Branch Protection Rules で「Force push を禁止」を設定し、ルールで防ぐことを推奨します(第2弾で詳説)。7-3. merge vs rebase の選択基準
「マージとリベース、どちらを使えばいいか」は Git 学習者が最もよく迷うポイントです。用途によって使い分けが決まります。
| 状況 | 推奨 | 理由 |
|---|---|---|
| feature ブランチを main に統合する | merge --no-ff | 分岐の履歴が残り、機能単位でのロールバックが容易 |
| feature ブランチを最新の main に追従させる | rebase | 履歴が一直線になり、PR のレビューがしやすい |
| push 済みのブランチに対して | merge | rebase は履歴を書き換えるため他者に影響する |
| 個人ブランチ(push 前)の整理 | rebase -i | コミット履歴をきれいにしてから PR を出せる |
# merge(ブランチ履歴を残す)
git switch main
git merge --no-ff feature/vpc
# rebase(履歴を一直線に整理、push 前のみ)
git switch feature/vpc
git rebase main
# コンフリクトが発生した場合
git add <解消したファイル>
git rebase --continue
# rebase を中断したい場合
git rebase --abort
Terraform リポジトリでは main ブランチへは常に merge --no-ff、feature ブランチの最新化には rebase を使うパターンが一般的です。理由は、インフラ変更の履歴を feature 単位で追跡しやすくするためです。「このリソースがいつ追加されたか」を調べるとき、merge コミットがあれば
git log --merges で絞り込めます。7-4. git reset --hard の取り消し(reflog で救済)
git reset --hard を誤って実行しても、30 日以内であれば git reflog で復元できます。Working Tree の変更は消えますが、コミット自体は reflog に残っています。
# 誤って reset --hard を実行してしまった
git reset --hard HEAD~3# 3つ前のコミットに戻し、変更も消えた
# reflog でコミット履歴を確認
git reflog
# abc1234 HEAD@{0}: reset: moving to HEAD~3
# def5678 HEAD@{1}: commit: feat: add S3 bucket for tfstate
# ghi9012 HEAD@{2}: commit: feat: add DynamoDB for state lock
# jkl3456 HEAD@{3}: commit: feat: add vpc module
# 失いたくなかったコミットのハッシュを確認し、ブランチを復元
git switch -c recovery/reset-undo
git reset --hard def5678# 失う前のコミットハッシュを指定
# 元のブランチを復元
git switch main
git reset --hard def5678
git reset --hard で失ったコミットは git reflog で 30 日間は復元可能です。ただし git gc(ガベージコレクション)実行後は早期に削除される場合があります。重要な変更を誤って reset した直後は、すぐに git reflog を実行して復元作業を開始してください。git stash で退避してから作業する習慣を持つと、この事故を防げます。7-5. submodule の罠
Git submodule はサブディレクトリに別リポジトリを埋め込む機能ですが、Terraform プロジェクトでは modules/ を submodule で管理しようとして詰まるケースが多いです。
# submodule が "detached HEAD" のまま放置されやすい問題
git submodule update --init # 特定コミットにピンを刺した状態でチェックアウト
cd modules/network
git status
# HEAD detached at a1b2c3d ← submodule は常に detached HEAD
# submodule のコミットを更新する正しい手順
cd modules/network
git switch main
git pull
cd ../..
git add modules/network # 親リポジトリが参照するコミットを更新
git commit -m "chore: update network submodule to latest main"
# clone 時に submodule を含めて取得する
git clone --recurse-submodules <URL>
# clone 後に submodule を初期化する(--recurse-submodules を忘れた場合)
git submodule update --init --recursive
Terraform モジュールは
source = "git::https://..." や Terraform Registry から直接参照できるため、Git submodule を使わなくても済む場合がほとんどです。チームが Git submodule に慣れていない場合は、Terraform の source ブロックでバージョン管理する方法(第3弾で詳説)を優先してください。7-6. LF/CRLF 問題(git config core.autocrlf)
Windows 環境と macOS/Linux 環境が混在するチームでは、改行コードの自動変換が原因でコンフリクトが発生したり、意図しない差分が出たりします。
# 改行コードの現在の設定を確認
git config core.autocrlf
# macOS / Linux: CRLF → LF に自動変換(チェックアウト時は変換しない)
git config --global core.autocrlf input
# Windows: チェックアウト時に LF → CRLF に変換、コミット時に CRLF → LF に変換
git config --global core.autocrlf true
# 変換を無効化(Terraform ファイルは LF 固定が望ましい)
git config --global core.autocrlf false
# .gitattributes で明示的に管理(推奨: core.autocrlf より確実)
# ファイル: .gitattributes
# * text=auto
# *.tf text eol=lf
# *.sh text eol=lf
# *.ps1 text eol=crlf
Terraform の
*.tf ファイルは LF(Unix 改行)で統一することを推奨します。.gitattributes で *.tf text eol=lf を設定しておくと、Windows ユーザーがコミットしても CRLF が混入しません。チームで .gitattributes を共有することで、個人の core.autocrlf 設定に依存しない環境を作れます。7-7. .gitignore が効かない(git rm --cached)
.gitignore にパターンを追加しても、すでに追跡されているファイルには効果がありません。git rm --cached でインデックスから除外してから .gitignore を適用する必要があります。
# 状況: .terraform/ を .gitignore に追加したのに git status で表示される
git status
# Changes not staged for commit:
#modified:.terraform/providers/...
# 原因: .terraform/ がすでにインデックスに登録されている
# 解消手順: インデックスからディレクトリを除外
git rm -r --cached .terraform/
git rm --cached terraform.tfstate # ファイルの場合
# 確認: インデックスから除外されたことを確認
git status
# Changes to be committed:
#deleted:.terraform/providers/...← インデックスから除外(ファイル自体は残る)
# .gitignore に追加してコミット
echo ".terraform/" >> .gitignore
git add .gitignore
git commit -m "chore: remove .terraform from tracking and update .gitignore"
git rm --cached で追跡を外したファイルは、他のメンバーがリモートをプルすると削除コミットとして届きます。特に .terraform/ のような大容量ディレクトリを誤ってコミット・push していた場合、リモートからの削除を全員に反映してもらう必要があります。push 前に .gitignore を正しく設定する習慣が根本的な解決策です。7-8. large file problem(.tfstate の誤コミット)
terraform.tfstate を誤ってコミット・push してしまうと、2つの問題が発生します。① リポジトリが肥大化する(tfstate は大容量になりがち)、② 機密情報(リソース ID・パスワード)が Git 履歴に残る。
# 状況: terraform.tfstate を誤ってコミット・push してしまった
# Step 1: 即時対応 — AWSリソースの認証情報をローテーションする
# (tfstate に含まれる可能性のある情報: RDS パスワード、IAM アクセスキーなど)
# Step 2: push 前であれば reset で取り消せる
git reset --soft HEAD^ # 直前のコミットを取り消す(変更は Staging Area に残る)
git restore --staged terraform.tfstate
echo "*.tfstate" >> .gitignore
git add .gitignore
git commit -m "chore: remove tfstate from tracking"
# Step 3: push 済みの場合 — git filter-repo で履歴から完全削除
# (git filter-branch は非推奨。git filter-repo を pip install して使う)
# pip install git-filter-repo
git filter-repo --path terraform.tfstate --invert-paths
# Step 4: リモートへ force-push(チームへ事前周知必須)
git push --force-with-lease origin main
# 再発防止: リモートバックエンドへ移行(推奨)
# S3 + DynamoDB バックエンドを使えばローカルに tfstate は生成されない
terraform.tfstate には RDS のマスターパスワード・IAM アクセスキー・各リソースの ARN など、機密性の高い情報が平文で含まれます。push した瞬間から漏洩リスクが発生するため、push 後 1 分以内に認証情報のローテーションを完了してください。根本対策は S3 バックエンドへの移行です(第3弾で詳説)。7-9. git stash pop でのコンフリクト
git stash pop は stash した変更を現在の Working Tree に適用しますが、その間に同じファイルを編集していた場合にコンフリクトが発生します。stash pop はコンフリクト時に stash を自動削除しないため、手動で処理が必要です。
# 状況: stash pop でコンフリクトが発生した
git stash pop
# CONFLICT (content): Merge conflict in providers.tf
# The stash entry is kept in case you need it again.
# コンフリクトマーカーを確認・解消
git diff
# <<<<<<< Updated upstream
# provider "aws" { region = "ap-northeast-1" }
# =======
# provider "aws" { region = "us-east-1" }
# >>>>>>> Stash
# エディタでコンフリクトを解消してから
git add providers.tf
git stash drop# stash を手動で削除(pop と異なり自動削除されない)
# stash の一覧確認
git stash list
# stash@{0}: WIP on feature/vpc: abc1234 feat: add vpc module
# 特定の stash を適用(コンフリクトリスクを把握してから適用)
git stash show -p stash@{0}# 内容を確認してから
git stash pop stash@{0}
「少しの間だけ変更を退避したい」という場面で stash を使いがちですが、長期間 stash に放置すると内容を忘れてコンフリクトの原因になります。退避したい変更は
git switch -c wip/xxx でブランチを作りコミットしておく方が、後から git log で確認でき、チームにも共有できます。7-10. git bisect 後の reset 忘れ
git bisect はバグの原因コミットを二分探索で特定する強力なコマンドですが、終了後に git bisect reset を忘れると detached HEAD のまま作業を続けてしまいます。
# git bisect の正しい使い方
git bisect start
git bisect bad HEAD # 現在は bad(バグあり)
git bisect good v1.0.0 # v1.0.0 は good(バグなし)
# Git が自動的に中間コミットをチェックアウトする
# → テストを実行して good / bad を判定する
terraform validate# Terraform の場合はバリデーション
git bisect good# 正常なら good
# または
git bisect bad # バグがあるなら bad
# 原因コミットが特定される
# abc1234 is the first bad commit
# 必ずリセットして元のブランチに戻る(忘れやすい!)
git bisect reset
# Switched to branch 'main'
# bisect ログの確認(後から振り返り用)
git bisect log
「いつから
terraform plan でエラーが出るようになったか」を調査するとき、git bisect に terraform validate を組み合わせると原因コミットを素早く特定できます。git bisect run terraform validate で自動化も可能です。調査後は必ず git bisect reset を実行してください。7-11. アンチパターン演習(5件)
詰まりポイントを体験的に理解するための演習です。各演習で「悪い操作 → 解消 → 再発防止」のセットを実践してください。
演習1: detached HEAD からの脱出
# 準備
git log --oneline -5
# abc1234 feat: add vpc module
# def5678 chore: add .gitignore
# 悪い操作: コミットハッシュを直接チェックアウト
git checkout def5678 # detached HEAD になる
# 脱出: ブランチに戻る
git switch main
演習2: 誤って .tfstate を add してしまった
# 悪い操作: tfstate を add
git add terraform.tfstate
# 解消: ステージングから除外
git restore --staged terraform.tfstate
echo "*.tfstate" >> .gitignore
git add .gitignore
git commit -m "chore: add tfstate to gitignore"
演習3: .gitignore が効かなかった場合の修正
# 確認: 追跡状態の確認
git ls-files .terraform/ # 出力があれば追跡中
# 解消: インデックスから除外
git rm -r --cached .terraform/
git commit -m "chore: stop tracking .terraform directory"
演習4: push 済みコミットの安全な取り消し(revert)
# 悪い操作の代わり: revert でコミットを打ち消す(--force 不要)
git log --oneline
# abc1234 feat: add vpc module(このコミットを取り消したい)
git revert abc1234 # 逆操作のコミットを作成
git push origin main # force なしで push 可能
演習5: stash の安全な運用
# stash 前に内容を確認する習慣
git stash list # 既存の stash を確認
git diff # 変更内容を確認してから stash
# stash に名前をつける(後から内容を思い出せる)
git stash push -m "WIP: vpc subnet cidr change"
# stash の内容を確認してから pop
git stash show -p stash@{0}
git stash pop

- detached HEAD:
git switch mainで即座に脱出。git checkoutよりgit switchを使う習慣が予防策 - force-push 事故: チームリポジトリでは
--force-with-leaseのみ使用。main ブランチへの force-push は禁止 - merge vs rebase: main への統合は
merge --no-ff、ローカル整理はrebase -i(push 前のみ) - reset --hard 後の復元: 30 日以内なら
git reflogで救済可能 - submodule の detached HEAD:
git switch+git pull+ 親リポジトリでgit add - LF/CRLF 混在:
.gitattributesで*.tf eol=lfを設定して統一 - .gitignore 無効:
git rm --cachedでインデックスから除外してから再適用 - tfstate 誤コミット: 即時認証情報ローテーション →
git filter-repoで履歴削除 - stash pop コンフリクト:
git stash show -pで確認してから pop、解消後はgit stash drop - bisect 後の reset 忘れ: 調査完了後は必ず
git bisect resetを実行
§8: まとめ + Git/GitHub×Terraform実践シリーズ全軸クロスリンク
Section 1〜§7 を通じて、Terraform コードを題材に Git のローカル操作から詰まりポイントの解消まで一通り習得しました。§8 では習得した実務コマンドを振り返り、Git/GitHub × Terraform の知識をさらに深めるための関連記事を軸別に紹介します。
8-1. 実務コマンド8項目の振り返り
以下の操作がすべてできれば、この記事のゴール達成です。
- ✅
git init/git config --global: リポジトリ初期化と初期設定 - ✅
git add/git commit/git status/git log --oneline: 基本の記録サイクル - ✅
git switch -c/git merge --no-ff: feature ブランチ開発フロー - ✅
git diff/git diff --staged: コミット前の変更確認 - ✅
git reset --soft HEAD^/git restore --staged: コミット・ステージングの取り消し - ✅
git stash push -m/git stash pop: 作業の一時退避 - ✅
git reflog: 誤操作後の復元 - ✅
git bisect start/good/bad/reset: バグ原因コミットの特定
第2弾以降で学ぶ発展コマンド
| コマンド | 学ぶ弾 | 用途 |
|---|---|---|
git push / git pull / git fetch | 第2弾 | リモートリポジトリとの同期 |
git revert | 第2弾 | push 済みコミットの安全な取り消し |
git rebase -i | 第3弾 | コミット履歴の整理・squash |
git cherry-pick | 第3弾 | 特定コミットの選択的な適用 |
git filter-repo | 第4弾 | 機密情報の履歴からの完全削除 |
8-2. Git/GitHub×Terraform実践シリーズ 全14軸クロスリンク
Git で管理する Terraform コードをさらに深く学ぶ出発点。
Terraform で IAM リソースを安全にコード管理するための前提知識。
第5弾で学ぶ GitHub Actions × OIDC の前提となる CI/CD 設計。
Terraform で EKS クラスターを構築し、GitOps で継続的デリバリーを実現する。
Terraform で構築したインフラの耐障害性検証と復旧設計。
Terraform で Bedrock インフラを構築し、IAM と Git で安全に管理する。
Git リポジトリに機密情報を入れないための設計と、AWS セキュリティ監視の実践。
Terraform × Git でマルチアカウント環境のインフラをコードで一元管理する。
Terraform で構築したネットワーク・監視基盤を Git で安全に管理する。
Lambda / API Gateway / Step Functions を Terraform と Git で管理するパターン。
RDS / Aurora / DynamoDB の Terraform 管理と tfstate バックエンド設計。
ecspresso × Terraform 統合で ECS デプロイをコード管理する実践パターン。
WAF ルール・CloudFront ディストリビューションを Terraform と Git で安全に管理する。
次の記事 → 第2弾: GitHub入門 — push/pull/PR/レビューで協業
第3弾へジャンプ → ブランチ戦略 — GitFlow / trunk-based / モジュール管理
本記事では Git のローカル操作から詰まりポイントの解消まで、Terraform コードを題材に実践的なスキルを習得しました。第2弾では GitHub を使ったリモート協業(push / pull / PR / レビュー)を学びます。§7 の一気通貫デモで作成した terraform-git-handson/ リポジトリをそのまま持ち越して GitHub にプッシュするところから始めます。引き続きシリーズをお楽しみください。
- [Container本番運用 Vol1: ECS×Fargate×ECR×ECS Exec×Service Connect 完全ガイド](https://www.watchittrend.com/aws-container-production-vol1-ecs-fargate-ecr/)
- [Storage本番運用 Vol1: S3×EFS×FSx×Storage Gateway 完全ガイド](https://www.watchittrend.com/aws-storage-production-vol1-s3-efs-fsx-storage-gateway/)
- [Edge/CDN本番運用 Vol1: CloudFront・Lambda@Edge・Route53 完全ガイド](https://www.watchittrend.com/aws-edge-cdn-production-vol1-cloudfront-lambda-edge-route53/)
- [Analytics/Data Lake本番運用 Vol1: Glue / Athena / Redshift Serverless / Lake Formation / QuickSight](https://www.watchittrend.com/aws-analytics-data-lake-production-vol1-glue-athena-redshift/)