競合とレビュー|コマンドライン・Git・変更管理
このページはプロモーション(広告)を含みます。
コマンドライン・Git・変更管理(中級・読む35分+演習40分)
この授業のゴール
- 競合マーカー(<<<<<<< ======= >>>>>>>)を読み、自分の版・相手の版・競合していない部分を区別できる
- 両方の変更の意図を確認したうえで競合を解決し、マーカーが残っていないことを確かめてから理由つきでcommitできる
- 解決に迷ったときに git merge --abort でマージ前の状態に安全に戻れる
- プルリクエストの差分をレビューし、根拠・場所・提案を含む具体的なコメントを書ける
仕事でどう使うか
2人が同じ設定値を別々の理由で変えると、Gitはどちらが正しいか判断できず、競合(conflict:コンフリクト)として人に判断を求めます。競合は事故ではなく、「意図がぶつかっている」というお知らせです。また、チームでは変更を本線に入れる前に、他の人に差分を確認してもらうレビューを行います。
| 路線 | この授業の使いどころ |
|---|---|
| DS | 同じ集計SQLを2人が直したときに、両方の条件(期間・除外ルール)を正しく残して解決する |
| ML | 設定ファイル(学習率・特徴量リスト)の競合を、実験結果の根拠に基づいて解決し、理由を記録する |
| DX | 業務ルールの変更をレビューで承認し、「誰が・なぜ認めたか」を残す |
| RS | 共著者の原稿修正の衝突を、双方の意図を確認して統合し、解析コードの変更はレビューを経て取り込む |
前提
前提:
- 3方向マージの判定(共通の祖先・自分・相手)と、マージcommit → branchとmerge
- commitメッセージに「なぜ」を書く → commitと履歴
git diffの読み方 → Gitと変更差分
模擬Git(ブラウザ内)はファイル単位で競合を判定し、ファイル全体を競合マーカーで囲みます。本物のGitは競合した塊(hunk)だけを囲みます。マーカーの形と解決の手順は同じです。
身近な状況:同じ閾値を、反対の理由で変えた
異常検知の設定ファイル config.txt で、共通の祖先では threshold=0.5 でした。
| 人 | ブランチ | 変更 | 理由 |
|---|---|---|---|
| ケンさん | main | threshold=0.6、min_rows=80 |
異常の見逃しが多いと店舗から指摘 |
| ハナさん | feature/threshold | threshold=0.7、exclude_test_stores=true を追加 |
誤検知が多い。テスト店舗のデータが混ざっていた |
ハナさんが main に feature/threshold をマージすると、threshold の行を両方が別々の値に変えているので競合します。ここで「自分の0.7を採用」とすると、ケンさんの「見逃し対策」という意図が黙って消えます。「ケンさんの0.6を採用」とすると、ハナさんのテスト店舗除外まで消えるかもしれません。
正しい解決は、両方の意図を確かめて、最終的な内容を決めることです。この例では2人で話し、「テスト店舗を除外すれば誤検知の多くは消えるので、閾値はケンさんの0.6にする。最低行数80とテスト店舗除外はどちらも残す」と決めました。
直感的な説明:Gitは推測しない
3方向マージで「片方だけが変えた」部分は、Gitが自動で取り込みます。「両方が別々に変えた」部分だけは、どちらが正しいか内容の意味を知らないGitには判断できないので、両方の版をファイルに並べて書き、人に渡します。これが競合マーカーです。
解決とは、次の4つのどれかを、理由を持って選ぶことです。
- 自分の版を採用する
- 相手の版を採用する
- 両方を組み合わせる(今回のように、片方の値と他方の追加行を残す)
- どちらでもない新しい内容に書き直す
たとえと実際の違い:「推測しない」は安全のための性質ですが、Gitは意味までは見ていません。行の位置が離れていれば、意味的に矛盾する変更(例:片方が列名を変え、もう片方が古い列名を使う行を追加)でも自動で合流します。競合が出なかったからといって正しいとは限らず、マージ後のテストやレビューが必要です。
正確な定義:競合マーカーと解決の手順
競合したファイルには、次のような競合マーカーが書き込まれます。
<<<<<<< HEAD
threshold=0.6
=======
threshold=0.7
>>>>>>> feature/threshold
| 部分 | 意味 |
|---|---|
<<<<<<< HEAD から ======= まで |
自分(今いるブランチ、ours)の版 |
======= から >>>>>>> feature/threshold まで |
相手(取り込むブランチ、theirs)の版 |
| マーカーの外 | 競合していない部分(自動で合流済み) |
git config --global merge.conflictStyle diff3 を設定すると、||||||| の後に共通の祖先の版も表示され、「誰が何から何に変えたか」が読みやすくなります。
解決の手順(実Git)
| 手順 | コマンド・操作 | 確認すること |
|---|---|---|
| 1 | git status |
both modified: に競合したファイルが並ぶ |
| 2 | ファイルを開き、両方の版と理由(git log のメッセージ、相手への確認)を読む |
両方の意図 |
| 3 | 最終的な内容に書き換え、マーカー3種をすべて消して保存 | マーカーが残っていない |
| 4 | git add ファイル |
「解決した」という印。status から both modified が消える |
| 5 | git commit -m "理由" |
親が2つのマージcommitができる |
| 迷ったら | git merge --abort |
マージを始める前の状態に戻る(未commitの変更が無い状態で始めたとき) |
レビュー:チーム開発では、ブランチを本線に入れる前にプルリクエスト(pull request、PR。GitLabではマージリクエスト)を作ります。PRは「このブランチを main にマージしたい」という依頼で、差分・説明・コメントのやり取り・承認が1つのページにまとまります。PRの機能はGitそのものではなく、GitHubなどのサービスの機能です。
| レビューで見る観点 | 例 |
|---|---|
| 目的と差分が一致しているか | 説明は「表示の調整」なのに計算式が変わっていないか |
| 正しさ(境界・0と欠損・型) | 空欄を0で埋めて平均を下げていないか |
| 再現性 | 自分のMacにしかない絶対パスを書いていないか |
| 安全 | パスワード・個人データ・巨大ファイルが入っていないか(秘密情報と共同作業) |
| 確認方法 | どう動作を確かめたかが書いてあるか |
段階付き例題:config.txt の競合を解く
入力:main(ケンさん)に feature/threshold(ハナさん)をマージしたところ、本物のGitでは次のように表示されました。
<<<<<<< HEAD
threshold=0.6
min_rows=80
=======
threshold=0.7
min_rows=100
exclude_test_stores=true
>>>>>>> feature/threshold
ステップ1:3つの版を表にする(祖先は git show で、各版の理由は git log で確認)。
| 項目 | 祖先 | HEAD(ケン) | 相手(ハナ) | 決定 | 根拠 |
|---|---|---|---|---|---|
| threshold | 0.5 | 0.6 | 0.7 | 0.6 | 除外で誤検知が減るので見逃し対策を優先(2人で合意) |
| min_rows | 100 | 80 | 100(変更なし) | 80 | ハナさんは変えていない。ケンさんの変更を採用 |
| exclude_test_stores | なし | なし | true | true | ハナさんの追加。ケンさんは触れていない |
ステップ2:ファイルを書き換える。マーカー3種を消し、決定どおりの内容にします。
threshold=0.6
min_rows=80
exclude_test_stores=true
ステップ3:add と commit。メッセージに決定の理由を書きます。
Merge feature/threshold: 閾値は0.6、テスト店舗除外を採用
閾値はケン(0.6:見逃し対策)とハナ(0.7:誤検知対策)で競合。
テスト店舗を除外すると誤検知が減るため、0.6に合意(10/9 打合せ)。
出力と意味:親が2つのマージcommitができ、2人の意図(見逃し対策・テスト店舗除外・最低行数)が残り、なぜ0.6にしたかが履歴から読めます。
Macのターミナルで試す(実Git):~/se01-practice/git-demo で、わざと競合を作って解きます。
cd ~/se01-practice/git-demo
git switch main
echo "threshold=0.5" > config.txt
git add config.txt
git commit -m "設定ファイルを追加"
git switch -c feature/threshold
echo "threshold=0.7" > config.txt
git commit -am "閾値を0.7に(誤検知対策)"
git switch main
echo "threshold=0.6" > config.txt
git commit -am "閾値を0.6に(見逃し対策)"
git merge feature/threshold
git status
cat config.txt
CONFLICT (content): Merge conflict in config.txt と表示され、cat でマーカーが見えます。ここで一度 git merge --abort を実行し、cat config.txt が threshold=0.6 に戻ることを確かめましょう。もう一度 git merge feature/threshold を実行し、今度は open -e config.txt で開いて threshold=0.6 の1行だけにして保存し、次を実行します。
grep -n -e '<<<<<<<' -e '>>>>>>>' config.txt
git add config.txt
git commit -m "Merge feature/threshold: 閾値は0.6に合意(見逃し対策を優先)"
git log --oneline --graph --all
grep -n は指定した文字列を含む行を探して表示する、読むだけのコマンドです。何も表示されなければマーカーは残っていません。
操作して理解する:競合マーカーを読み取る関数
ブラウザ内のPythonで、競合マーカー付きの文字列から自分の版・相手の版を取り出し、解決後にマーカーが残っていないかを確かめます。
conflicted = """threshold=0.5
<<<<<<< HEAD
min_rows=80
=======
min_rows=120
exclude_test_stores=true
>>>>>>> feature/threshold
log=on
"""
def split_conflict(text):
"""競合マーカーのある文字列から (自分の行, 相手の行, 共通の行) を取り出す"""
ours, theirs, common, side = [], [], [], None
for line in text.splitlines():
if line.startswith('<<<<<<<'): side = 'ours'
elif line.startswith('=======') and side == 'ours': side = 'theirs'
elif line.startswith('>>>>>>>'): side = None
elif side == 'ours': ours.append(line)
elif side == 'theirs': theirs.append(line)
else: common.append(line)
return ours, theirs, common
def has_markers(text):
return any(l.startswith(('<<<<<<<', '=======', '>>>>>>>')) for l in text.splitlines())
o, t, c = split_conflict(conflicted)
print('HEAD(自分):', o)
print('相手 :', t)
print('競合外 :', c)
resolved = 'threshold=0.5\nmin_rows=80\nexclude_test_stores=true\nlog=on\n' # 話し合いで決めた内容
print('解決前にマーカーあり:', has_markers(conflicted), '/ 解決後:', has_markers(resolved))
出力:
HEAD(自分): ['min_rows=80']
相手 : ['min_rows=120', 'exclude_test_stores=true']
競合外 : ['threshold=0.5', 'log=on']
解決前にマーカーあり: True / 解決後: False
threshold=0.5 と log=on はマーカーの外なので、自動で合流済みの部分です。解決で触るのはマーカーの中だけです。
試してみよう:①resolved の文字列に ======= の行を残して、has_markers が True になることを確かめる。②競合ブロックを2つ含む文字列を作り、split_conflict が両方の中身を集めることを確かめる。
発展:競合を減らす工夫と、レビューの限界
競合を減らす工夫:①ブランチを小さく短く保ち、こまめに main を取り込む(git merge main を自分のブランチで行う)。②自動整形ツールの設定をチームでそろえ、整形だけの差分を出さない。③1つの設定ファイルに全員が書き込む構成を見直す。
マージできないファイル:Excel・画像などのバイナリは行で比べられないので、競合したら「どちらかの版を選ぶ」しかありません。Jupyter Notebook(.ipynb)は中身がJSONで、出力や実行回数まで差分になるため競合しやすく、出力を消してからcommitする、専用の差分ツールを使う、などの対策をとります。
道具:VS Code などのエディタは、競合ブロックに「Accept Current(自分)」「Accept Incoming(相手)」「Accept Both」のボタンを表示します。便利ですが、ボタンは意図を判断してくれません。押す前に3つの版と理由を確認します。
レビューの限界:レビューは人の目による確認なので、見落としはあります。数百行の差分では指摘の質が下がることが知られており、PRを小さく保つこと、テストを自動で走らせること(テストはモジュール テスト・デバッグ・品質(有料版) で扱います)と組み合わせます。
よくある失敗
失敗1:競合マーカーを残したままcommitする
誤り:エディタで片方の行を消したが、======= や >>>>>>> の行を消し忘れて add・commit した。
code = """THRESHOLD = 0.5
<<<<<<< HEAD
MIN_ROWS = 80
=======
MIN_ROWS = 120
>>>>>>> feature/threshold
"""
try:
compile(code, 'config.py', 'exec') # Python として読めるか確認
print('OK')
except SyntaxError as e:
print('SyntaxError:', e.msg, '行', e.lineno)
出力:SyntaxError: invalid syntax 行 2(Pythonの版によって文言が少し違います)。設定ファイルやコードが読めなくなり、本線を使う全員の処理が止まります。
直し方:add の前に grep -n -e '<<<<<<<' -e '>>>>>>>' ファイル でマーカーを探す。コードなら実行・テストして確かめる。模擬Gitの finish() も、マーカーが残っていると記録を拒否します。
失敗2:意図を確かめずに「全部自分の版」で解決する
誤り:競合が面倒なので、エディタの「Accept Current」を全部押した(実Gitの git checkout --ours ファイル も同じ効果)。
| 項目 | 自分の版で解決した結果 | 失われたもの |
|---|---|---|
| threshold | 0.6 | ―(今回は合意と一致) |
| exclude_test_stores | 行が無い | ハナさんのテスト店舗除外。誤検知が再発する |
なぜ誤りか:競合ブロックの中には、相手が追加しただけの行も含まれることがあります。片方を丸ごと採用すると、それも消えます。しかも競合は「解決済み」になるので、誰も気づきません。
直し方:ブロックの中を1行ずつ表にして、祖先・自分・相手を比べる。迷ったら相手に聞く。決めた理由をcommitメッセージに書く。判断できないうちは git merge --abort で戻る。
失敗3:中身のないレビューコメント
| 悪いコメント | 問題 | 良いコメント |
|---|---|---|
LGTM(Looks Good To Me)だけ |
何を確認したか不明 | 差分の3ファイルを確認。手元で sample.csv を集計し、件数が説明どおり 1,203→1,215 になることを確認しました。承認します |
ここおかしい |
どこが、なぜ、どうすれば | clean.py 12行目:fillna(0) だと未入力の金額が0円として平均に入ります。欠損は除外するか、件数を別に数えませんか |
全部書き直して |
相手が動けない | 必須:絶対パス /Users/ken/... を相対パスに。任意:変数名 df2 を orders_paid にするとわかりやすいです |
直し方:①場所(ファイル・行)、②根拠(何が起きるか)、③提案(どう直すか)を書き、必須か任意かを区別する。人ではなくコードについて書く。承認するときも、何を確かめたかを書く。
まとめ
- 競合は、両方が同じ場所を別々に変えたときにGitが判断を人に渡す仕組み。誤りではなく意図の衝突。
- <<<<<<< HEAD〜======= が自分、=======〜>>>>>>> が相手。マーカーの外は自動で合流済み。
- 解決は、両方の意図を確かめて最終内容を決め、マーカーを消し、add して理由つきでcommitする。迷ったら git merge --abort。
- 競合が出なくても意味的に正しいとは限らない。マージ後もテストとレビューで確かめる。
- レビューは場所・根拠・提案を書き、必須と任意を区別する。説明と差分の一致から確かめる。
用語
- 競合(コンフリクト)(merge conflict)
マージで両方のブランチが同じ箇所を別々に変更していて、Gitが自動で結果を決められない状態。
- 競合マーカー(conflict markers)
競合箇所に書き込まれる <<<<<<<、=======、>>>>>>> の行。自分と相手の版を区切る。
- プルリクエスト(pull request (PR))
ブランチを本線にマージしたいという依頼。差分・説明・レビュー・承認をサービス上で扱う。
- レビュー(code review)
変更を本線に入れる前に、他の人が差分を読んで正しさ・安全・読みやすさを確認すること。
よくある質問
競合が起きるのは、誰かが間違えたからですか?
いいえ。同じ場所を別々の理由で変えれば、正しい作業同士でも競合します。競合は意図の衝突を見える化する仕組みです。ブランチを小さく保ち、こまめに本線を取り込むと頻度は減ります。
自分の版と相手の版、どちらを優先すべきですか?
決まりはありません。両方の理由(commitメッセージ、PRの説明、本人への確認)を読み、目的に合う内容を決めます。片方を丸ごと採用すると、相手の追加行まで消えることがある点に注意してください。
git merge --abort と git restore の違いは?
git merge --abort は進行中のマージを中止して、マージ前の状態に戻します。git restore ファイル は作業ツリーの変更を捨てる操作で、マージの中止ではありません。競合中に迷ったら --abort を使います。
どこで失敗しやすいですか?
①マーカーの消し忘れ、②『Accept Current』の一括押しで相手の変更を消す、③解決後にテストしない、④理由を書かずにcommitする、の4つです。解決後は grep でマーカーを探し、動作を確かめてからcommitします。
条件が変わって、競合したのがExcelファイルだったら?
Excelはバイナリなので行の比較ができず、マーカーも書き込まれません。どちらかの版を選ぶしかないため、相手と相談して選び、必要なら手で統合した版を作ります。表データはCSVなどのテキストで管理すると競合を扱いやすくなります。
仕事では、レビューで何をどこまで見ればよいですか?
まず『説明と差分が一致しているか』、次に正しさ(境界・0と欠損・型)、再現性(パス・環境)、安全(秘密情報・個人データ・巨大ファイル)、最後に読みやすさです。重大な問題を必須、好みを任意として区別し、確認した内容を書いて承認します。
確認問題
問1 次の競合ブロックで、======= より下、>>>>>>> feature/x より上にある行は何か。
- ① 取り込もうとしているブランチ(feature/x)の版
- ② 今いるブランチ(HEAD)の版
- ③ 共通の祖先の版
- ④ Gitが自動で決めた最終版
解答と解説
正解:①
<<<<<<< HEAD 〜 ======= が自分の版、======= 〜 >>>>>>> 名前 が相手の版です。
問2 競合を解決した後、commitの前に必ず確かめるべきことはどれか。
- ① ファイルの更新日時
- ② コミットIDの桁数
- ③ ブランチ名の長さ
- ④ 競合マーカー3種がすべて消え、内容が両方の意図に沿っていること
解答と解説
正解:④
マーカーが残るとファイルが壊れ、意図の確認を怠ると相手の変更が黙って消えます。
問3 競合の解決方法がわからず、いったんやめたい。最も安全な操作はどれか。
- ① フォルダをゴミ箱に入れる
- ② git merge --abort
- ③ マーカーを残したままcommitする
- ④ 競合したファイルを削除してcommitする
解答と解説
正解:②
merge --abort はマージを始める前の状態に戻します(未commitの変更が無い状態で始めた場合)。
問4 マージで競合が出なかった。正しい理解はどれか。
- ① Gitが行の位置で衝突を検出しなかっただけで、意味的な矛盾はテストやレビューで確かめる必要がある
- ② 変更の意味としても必ず正しい
- ③ レビューは不要になる
- ④ テストは不要になる
解答と解説
正解:①
Gitは意味を見ていません。離れた行の変更が意味的に矛盾していても自動で合流します。
問5 レビューコメントとして最も役に立つものはどれか。
- ① LGTM
- ② ここおかしい
- ③ 全体的にいまいち
- ④ clean.py 12行目:fillna(0) だと未入力の金額が平均に0円として入ります。欠損は除外しませんか(必須)
解答と解説
正解:④
場所・何が起きるか・提案・必須かどうかがそろっていて、作者がすぐ対応できます。
問6 プルリクエスト(PR)について正しい説明はどれか。
- ① Git本体のコマンドで、ローカルだけで完結する
- ② commitを削除するための機能
- ③ ブランチを本線にマージしたいという依頼で、差分・説明・レビュー・承認をGitHubなどのサービス上でまとめて扱う
- ④ 競合を自動で解決する機能
解答と解説
正解:③
PRはGitHub・GitLabなどのサービスの機能です。マージ前のレビューの場になります。
次に学ぶこと
次は 秘密情報と共同作業 で、レビューで見つけたAPIキーのような秘密情報や、個人データ・巨大データをリポジトリに入れない方法と、リモートリポジトリを使った共同作業の流れを学びます。
3方向マージの判定に不安があれば branchとmerge に戻りましょう。
キャリア学習:データサイエンティスト・機械学習エンジニア・DX推進・研究者を目指す人の学習アプリ。164モジュール・984授業を、ブラウザで動くSQLとPythonの演習で。基礎と各分野の最初のモジュールは無料。全部は2,980円の買い切り。
アプリを無料で開く有料版と無料版の違い