1.2.4 ブランチと協働

このページを終えたら、この evidence card を残します。
- リポジトリ状態
- 操作前後の git status
- 操作
- init、add、commit、branch、merge、remote、pull、またはpushコマンドを使用
- 履歴
- 何が変わったかを示す git log またはブランチグラフ
- 失敗確認
- 未追跡ファイル、誤ったブランチ、マージ衝突、またはリモート/認証の問題
- 期待される成果
- 別の学習者が安全に再実行できる、きれいな Git の trace
この節の位置づけ
Section titled “この節の位置づけ”この節では、Git がどうして安全な共同作業を支えられるのかを説明します。ブランチを使うと、メインのコードを壊さずに新機能を試せることを理解し、Pull Request とマージコンフリクトの基本も学びます。これからチーム開発やオープンソースへの貢献を始めるための準備になります。
- ブランチの概念と使いどころを理解する
- ブランチの作成、切り替え、マージの操作を身につける
- Pull Request の協働フローを理解する
- 簡単なマージコンフリクトを解決できるようになる
ブランチとは?
Section titled “ブランチとは?”リフォームにたとえると
Section titled “リフォームにたとえると”あなたがマンションの部屋に住んでいるとします(main ブランチ = 今住んでいる家)。新しい内装スタイルを試してみたいけれど、うまくいくかはまだ分かりません。
選択肢は 2 つあります。
- いきなり今の家を改造する — 失敗したら住めなくなるかもしれません
- 同じ間取りの部屋をもう1つ借りて(新しいブランチ)、そこで試す — 気に入ったら戻す、微妙なら解約する
ブランチは 2 の方法です。新しいブランチで自由に変更し、うまくいったら main にマージ、失敗したらブランチを消すだけ。main には影響しません。
コードでの実際の場面
Section titled “コードでの実際の場面”あなたは AI 画像分類プロジェクトを進めています。main ブランチには、正常に動くコードがあります。
今試したいこと:
- モデルを CNN から Vision Transformer に変える。
- 本当に性能が良くなるか確かめる。
- 数日かけて、多くのファイルにまたがる変更を進める。
main で直接作業すると、途中のコードが動かなくなったり、急な bug 修正を出しにくくなったり、ViT が合わなかったときに何十ファイルも戻すことになります。
ブランチを使えば、feature/vit で少しずつ試し、急ぎの修正は main に戻って対応し、失敗した実験はブランチごと消せます。
ブランチの基本操作
Section titled “ブランチの基本操作”ブランチを表示する
Section titled “ブランチを表示する”# ローカルブランチを表示する(現在のブランチには * が付く)git branch# 出力:# * main
# すべてのブランチを表示する(リモートも含む)git branch -aブランチを作成して切り替える
Section titled “ブランチを作成して切り替える”# 新しいブランチを作成するgit branch feature/data-augmentation
# 新しいブランチへ切り替えるgit checkout feature/data-augmentation
# あるいは一度で完了する方法(こちらがよく使われる)git checkout -b feature/data-augmentation例:ブランチで新機能を開発する
Section titled “例:ブランチで新機能を開発する”実際に操作してみましょう。前に使った ai-image-classifier プロジェクトを続けて使います。
cd ai-image-classifier
# 今が main ブランチか確認するgit branch# * main
# 新しいブランチを作成して切り替える:データ拡張機能を追加するgit checkout -b feature/data-augmentationこれで新しいブランチに移動しました。コードを書いていきます。
# データ拡張モジュールを作成するcat > src/augmentation.py << 'EOF'import torchvision.transforms as T
def get_train_transforms(): """訓練データの拡張方法""" return T.Compose([ T.RandomHorizontalFlip(p=0.5), # 50% の確率で左右反転 T.RandomRotation(degrees=15), # ±15 度のランダム回転 T.ColorJitter( # 色の揺らぎ brightness=0.2, contrast=0.2, saturation=0.2 ), T.ToTensor(), T.Normalize( mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] ), ])
def get_test_transforms(): """テストデータは標準化のみで、拡張はしない""" return T.Compose([ T.ToTensor(), T.Normalize( mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] ), ])EOF
# train.py を更新して、データ拡張を使うcat >> src/train.py << 'EOF'
# 追加: データ拡張を使うfrom augmentation import get_train_transforms, get_test_transformstrain_transform = get_train_transforms()test_transform = get_test_transforms()print("データ拡張の設定を読み込みました")EOF
# 現在のブランチにコミットするgit add .git commit -m "feat: データ拡張モジュールを追加(左右反転、回転、色の揺らぎ)"今の 2 つのブランチの状態を見てみましょう。
# 現在のブランチの履歴を確認するgit log --oneline -3# 出力:# aaa1111 feat: データ拡張モジュールを追加(左右反転、回転、色の揺らぎ)# bbb2222 README を改善:プロジェクト説明と使い方を追加# ccc3333 .gitignore を追加
# main に切り替えて確認するgit checkout main
# main には augmentation.py がない!ls src/# model.py train.py utils.py (augmentation.py はない)
# feature ブランチに戻るgit checkout feature/data-augmentationls src/# augmentation.py model.py train.py utils.py (ある!)これがブランチの便利さです。2 本の時間線が互いに影響しません。
ブランチのマージ
Section titled “ブランチのマージ”ブランチ上の機能開発が終わってテストも通ったら、それを main にマージできます。
# 手順1: main ブランチに切り替えるgit checkout main
# 手順2: feature ブランチを main にマージするgit merge feature/data-augmentation出力:
Updating bbb2222..aaa1111Fast-forward src/augmentation.py | 25 +++++++++++++++++++++++++ src/train.py | 5 +++++ 2 files changed, 30 insertions(+) create mode 100644 src/augmentation.pyこれで main ブランチにもデータ拡張のコードが入りました。
ls src/# augmentation.py model.py train.py utils.py ✅マージ後の整理
Section titled “マージ後の整理”# 機能ブランチはもうマージされたので、削除してよい(リポジトリをきれいに保つ)git branch -d feature/data-augmentation
# ブランチを確認する — main だけ残っているgit branch# * mainマージコンフリクト
Section titled “マージコンフリクト”
いつ起こる?
Section titled “いつ起こる?”2 つのブランチが同じファイルの同じ場所を変更すると、Git はどちらを残すべきか判断できず、コンフリクトが起こります。
例:コンフリクトを起こして解決する
Section titled “例:コンフリクトを起こして解決する”# main から 2 つのブランチを作り、2 人が同時に作業している状況を再現するgit checkout -b alice/update-modelcat > src/model.py << 'EOF'import torchimport torch.nn as nn
class SimpleCNN(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(3, 32, 3, padding=1) # エンジニアA: フィルター数を 32 に変更 self.pool = nn.MaxPool2d(2, 2) self.fc1 = nn.Linear(32 * 16 * 16, 10)
def forward(self, x): x = self.pool(torch.relu(self.conv1(x))) x = x.view(-1, 32 * 16 * 16) return self.fc1(x)EOFgit add . && git commit -m "alice: フィルター数を 32 に増やす"
# main に戻って、bob のブランチを作成するgit checkout maingit checkout -b bob/update-modelcat > src/model.py << 'EOF'import torchimport torch.nn as nn
class SimpleCNN(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(3, 64, 5, padding=2) # エンジニアB: フィルター数を 64 にし、5x5 カーネルに変更 self.pool = nn.MaxPool2d(2, 2) self.fc1 = nn.Linear(64 * 16 * 16, 10)
def forward(self, x): x = self.pool(torch.relu(self.conv1(x))) x = x.view(-1, 64 * 16 * 16) return self.fc1(x)EOFgit add . && git commit -m "bob: 64 フィルターと 5x5 カーネルを採用"まず エンジニアA の変更をマージします。
git checkout maingit merge alice/update-model # ✅ 成功、コンフリクトなし次に エンジニアB の変更をマージします。
git merge bob/update-model# 出力:# CONFLICT (content): Merge conflict in src/model.py# Automatic merge failed; fix conflicts and then commit the result.コンフリクトが発生しました。 エンジニアA と エンジニアB が model.py の同じ行を変更していたからです。
コンフリクトを解決する
Section titled “コンフリクトを解決する”src/model.py を開くと、Git がコンフリクト箇所を示しています。
class SimpleCNN(nn.Module): def __init__(self): super().__init__() # エンジニアA: 3x3 kernel のまま、filter 数を 32 にする。 self.conv1 = nn.Conv2d(3, 32, 3, padding=1) self.fc1 = nn.Linear(32 * 16 * 16, 10)
# エンジニアB: filter 数と kernel size の両方を変える。 self.conv1 = nn.Conv2d(3, 64, 5, padding=2) self.fc1 = nn.Linear(64 * 16 * 16, 10)- 実際のコンフリクトでは、Git は
<<<<<<< HEAD、現在のブランチの内容、=======、取り込む側の内容、最後に>>>>>>> branch-nameという順で表示します。 - 上の例では、リポジトリのチェックが教材サンプルを未解決コンフリクトと誤判定しないように
CONFLICT_MARKER_*というプレースホルダーを使っています。
最終的に何を残すかを手動で決める必要があります。 たとえば、ここでは エンジニアB の案を採用するとします。
import torchimport torch.nn as nn
class SimpleCNN(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(3, 64, 5, padding=2) # エンジニアB の案を採用 self.pool = nn.MaxPool2d(2, 2) self.fc1 = nn.Linear(64 * 16 * 16, 10)
def forward(self, x): x = self.pool(torch.relu(self.conv1(x))) x = x.view(-1, 64 * 16 * 16) return self.fc1(x)<<<<<<<、=======、>>>>>>> の印はすべて削除し、残したいコードだけにします。それから:
git add src/model.pygit commit -m "merge: エンジニアA と エンジニアB の変更を統合し、エンジニアB の 64 フィルター案を採用"これでコンフリクトは解決です。
# ブランチを整理するgit branch -d alice/update-modelgit branch -d bob/update-modelPull Request(知っておけば十分)
Section titled “Pull Request(知っておけば十分)”チーム開発では、通常 main ブランチへ直接マージしません。代わりに Pull Request(PR) を使い、まず他の人にコードを確認してもらい、問題がなければマージします。
Pull Request の流れ
Section titled “Pull Request の流れ”1. feature ブランチを作ってコードを書く2. GitHub に push する3. GitHub 上で Pull Request を作成する4. 同僚がコードをレビューし、修正点を伝える5. あなたが修正して、新しい commit を push する6. 同僚が "Approve"(承認)する7. コードが main ブランチにマージされる# 1. ブランチを作ってコードを書くgit checkout -b feature/add-evaluationecho "def evaluate(model, dataloader): pass" > src/evaluate.pygit add . && git commit -m "モデル評価モジュールを追加"
# 2. ブランチを GitHub に push するgit push -u origin feature/add-evaluationその後 GitHub を開くと、次のような表示が出ます。
feature/add-evaluation had recent pushes — Compare & pull request
このボタンをクリックして、PR のタイトルと説明を書き、Create pull request を押せば完了です。
個人プロジェクトなら、自分で確認したあと GitHub のページで Merge pull request を押してマージしてもかまいません。
本章のセルフチェック
Section titled “本章のセルフチェック”以下を確認して、Git の基礎を身につけたか確かめましょう。
- ゼロから Git リポジトリを作成できる
-
add→commitでコードを保存できる -
git diffで何が変更されたか確認できる -
.gitignoreファイルを書ける - コードを GitHub に push できる
-
git cloneで他人のプロジェクトを取得できる - ブランチの概念を理解し、作成とマージができる
- マージコンフリクトが起きても慌てず、解決方法が分かる
確認の考え方と解説
- ゼロから repo を作り、commit、diff、
.gitignore、push または clone、ブランチ作成と merge までできれば通過です。 - 安全な Git 証拠には、重要な操作の前後の
git status --shortが含まれます。 - コンフリクトでは、両方の内容が必要なときだけ両方を残します。commit 前に conflict marker は必ず消します。
- 未 commit のミスは
git restore、共有済み履歴の修正は新しい commit が基本です。reset --hardは一回限りの練習 repo だけに使います。 - よい証拠は、ブランチ図、短い PR、またはブランチ作成・merge・clean 状態を示す端末記録です。