コンテンツにスキップ

1.2.4 ブランチと協働

Git ブランチ協働フローチャート

このページを終えたら、この evidence card を残します。

リポジトリ状態
操作前後の git status
操作
init、add、commit、branch、merge、remote、pull、またはpushコマンドを使用
履歴
何が変わったかを示す git log またはブランチグラフ
失敗確認
未追跡ファイル、誤ったブランチ、マージ衝突、またはリモート/認証の問題
期待される成果
別の学習者が安全に再実行できる、きれいな Git の trace

この節では、Git がどうして安全な共同作業を支えられるのかを説明します。ブランチを使うと、メインのコードを壊さずに新機能を試せることを理解し、Pull Request とマージコンフリクトの基本も学びます。これからチーム開発やオープンソースへの貢献を始めるための準備になります。

  • ブランチの概念と使いどころを理解する
  • ブランチの作成、切り替え、マージの操作を身につける
  • Pull Request の協働フローを理解する
  • 簡単なマージコンフリクトを解決できるようになる

あなたがマンションの部屋に住んでいるとします(main ブランチ = 今住んでいる家)。新しい内装スタイルを試してみたいけれど、うまくいくかはまだ分かりません。

選択肢は 2 つあります。

  1. いきなり今の家を改造する — 失敗したら住めなくなるかもしれません
  2. 同じ間取りの部屋をもう1つ借りて(新しいブランチ)、そこで試す — 気に入ったら戻す、微妙なら解約する

ブランチは 2 の方法です。新しいブランチで自由に変更し、うまくいったら main にマージ、失敗したらブランチを消すだけ。main には影響しません。

あなたは AI 画像分類プロジェクトを進めています。main ブランチには、正常に動くコードがあります。

今試したいこと:

  • モデルを CNN から Vision Transformer に変える。
  • 本当に性能が良くなるか確かめる。
  • 数日かけて、多くのファイルにまたがる変更を進める。

main で直接作業すると、途中のコードが動かなくなったり、急な bug 修正を出しにくくなったり、ViT が合わなかったときに何十ファイルも戻すことになります。

ブランチを使えば、feature/vit で少しずつ試し、急ぎの修正は main に戻って対応し、失敗した実験はブランチごと消せます。


Terminal window
# ローカルブランチを表示する(現在のブランチには * が付く)
git branch
# 出力:
# * main
# すべてのブランチを表示する(リモートも含む)
git branch -a

ブランチを作成して切り替える

Section titled “ブランチを作成して切り替える”
Terminal window
# 新しいブランチを作成する
git branch feature/data-augmentation
# 新しいブランチへ切り替える
git checkout feature/data-augmentation
# あるいは一度で完了する方法(こちらがよく使われる)
git checkout -b feature/data-augmentation

例:ブランチで新機能を開発する

Section titled “例:ブランチで新機能を開発する”

実際に操作してみましょう。前に使った ai-image-classifier プロジェクトを続けて使います。

Terminal window
cd ai-image-classifier
# 今が main ブランチか確認する
git branch
# * main
# 新しいブランチを作成して切り替える:データ拡張機能を追加する
git checkout -b feature/data-augmentation

これで新しいブランチに移動しました。コードを書いていきます。

Terminal window
# データ拡張モジュールを作成する
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_transforms
train_transform = get_train_transforms()
test_transform = get_test_transforms()
print("データ拡張の設定を読み込みました")
EOF
# 現在のブランチにコミットする
git add .
git commit -m "feat: データ拡張モジュールを追加(左右反転、回転、色の揺らぎ)"

今の 2 つのブランチの状態を見てみましょう。

Terminal window
# 現在のブランチの履歴を確認する
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-augmentation
ls src/
# augmentation.py model.py train.py utils.py (ある!)

これがブランチの便利さです。2 本の時間線が互いに影響しません。


ブランチ上の機能開発が終わってテストも通ったら、それを main にマージできます。

Terminal window
# 手順1: main ブランチに切り替える
git checkout main
# 手順2: feature ブランチを main にマージする
git merge feature/data-augmentation

出力:

Updating bbb2222..aaa1111
Fast-forward
src/augmentation.py | 25 +++++++++++++++++++++++++
src/train.py | 5 +++++
2 files changed, 30 insertions(+)
create mode 100644 src/augmentation.py

これで main ブランチにもデータ拡張のコードが入りました。

Terminal window
ls src/
# augmentation.py model.py train.py utils.py ✅
Terminal window
# 機能ブランチはもうマージされたので、削除してよい(リポジトリをきれいに保つ)
git branch -d feature/data-augmentation
# ブランチを確認する — main だけ残っている
git branch
# * main

Git マージコンフリクト解決フロー

2 つのブランチが同じファイルの同じ場所を変更すると、Git はどちらを残すべきか判断できず、コンフリクトが起こります。

例:コンフリクトを起こして解決する

Section titled “例:コンフリクトを起こして解決する”
Terminal window
# main から 2 つのブランチを作り、2 人が同時に作業している状況を再現する
git checkout -b alice/update-model
cat > src/model.py << 'EOF'
import torch
import 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)
EOF
git add . && git commit -m "alice: フィルター数を 32 に増やす"
# main に戻って、bob のブランチを作成する
git checkout main
git checkout -b bob/update-model
cat > src/model.py << 'EOF'
import torch
import 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)
EOF
git add . && git commit -m "bob: 64 フィルターと 5x5 カーネルを採用"

まず エンジニアA の変更をマージします。

Terminal window
git checkout main
git merge alice/update-model # ✅ 成功、コンフリクトなし

次に エンジニアB の変更をマージします。

Terminal window
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 の同じ行を変更していたからです。

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 torch
import 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)

<<<<<<<=======>>>>>>> の印はすべて削除し、残したいコードだけにします。それから:

Terminal window
git add src/model.py
git commit -m "merge: エンジニアA と エンジニアB の変更を統合し、エンジニアB の 64 フィルター案を採用"

これでコンフリクトは解決です。

Terminal window
# ブランチを整理する
git branch -d alice/update-model
git branch -d bob/update-model

Pull Request(知っておけば十分)

Section titled “Pull Request(知っておけば十分)”

チーム開発では、通常 main ブランチへ直接マージしません。代わりに Pull Request(PR) を使い、まず他の人にコードを確認してもらい、問題がなければマージします。

1. feature ブランチを作ってコードを書く
2. GitHub に push する
3. GitHub 上で Pull Request を作成する
4. 同僚がコードをレビューし、修正点を伝える
5. あなたが修正して、新しい commit を push する
6. 同僚が "Approve"(承認)する
7. コードが main ブランチにマージされる
Terminal window
# 1. ブランチを作ってコードを書く
git checkout -b feature/add-evaluation
echo "def evaluate(model, dataloader): pass" > src/evaluate.py
git 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 を押してマージしてもかまいません。


以下を確認して、Git の基礎を身につけたか確かめましょう。

  • ゼロから Git リポジトリを作成できる
  • addcommit でコードを保存できる
  • git diff で何が変更されたか確認できる
  • .gitignore ファイルを書ける
  • コードを GitHub に push できる
  • git clone で他人のプロジェクトを取得できる
  • ブランチの概念を理解し、作成とマージができる
  • マージコンフリクトが起きても慌てず、解決方法が分かる
確認の考え方と解説
  1. ゼロから repo を作り、commit、diff、.gitignore、push または clone、ブランチ作成と merge までできれば通過です。
  2. 安全な Git 証拠には、重要な操作の前後の git status --short が含まれます。
  3. コンフリクトでは、両方の内容が必要なときだけ両方を残します。commit 前に conflict marker は必ず消します。
  4. 未 commit のミスは git restore、共有済み履歴の修正は新しい commit が基本です。reset --hard は一回限りの練習 repo だけに使います。
  5. よい証拠は、ブランチ図、短い PR、またはブランチ作成・merge・clean 状態を示す端末記録です。