Slide 1

Slide 1 text

1 44 ブランチ操作 慶應義塾大学理工学部物理情報工学科 渡辺 物理情報工学ソフトウェア開発演習

Slide 2

Slide 2 text

2 44 • Gitでのブランチ操作、特にブランチの必要 性とワークフローについて理解する • マージと衝突の解決について理解する • リベースについて理解する 特に「歴史を分岐、改変する」とはどういう ことか、なぜ行うかを理解する

Slide 3

Slide 3 text

3 44 Alice AliceとBobの二人が開発に関わるプロジェクト • 機能Aの達成のためには、サブモジュールA1とA2が必要 • A1を実装しただけでは正しく動作しない • A2まで実装して初めて全体として正しく動作する Bob 機能Bを開発 機能Aを開発

Slide 4

Slide 4 text

4 44 Alice Bob 中央 リポジトリ 1. クローン 2. 機能A1を実装 3. プッシュ 4. クローン 5. 機能A1を無効化して機能B追加 5. プッシュ 6. 機能A2を実装 競合

Slide 5

Slide 5 text

5 44 何が問題だったか? Aliceが機能A1を実装した状態という「中途半端な状態」をコミットした Bobはその状態をクローン、動作しないのでA1を無効化せざるを得なかった どうすれば良い? 案1: 「中途半端な状態をコミットしてはならぬ」というルールを作る 案2: ブランチを作成して自由にコミットするが、マージの時にルールを作る https://www.youtube.com/watch?v=4XpnKHJAok8 Tech Talk: Linus Torvals on git ワークフロー(Work Flow)の導入 以下の動画で、Linusが「コミットルール」について触れている

Slide 6

Slide 6 text

6 44 多人数開発時のためのGitのブランチ運用ルール みんなが参照するブランチに中途半端な状態は困る 個人のブランチは好き勝手やってよい メインブランチと開発ブランチをどう整理するか? 有名なワークフロー • フィーチャーブランチワークフロー • Git Flow • GitHub Flow • GitLab Flow ↑本講義ではこれを説明

Slide 7

Slide 7 text

7 44 フィーチャーブランチワークフローとは • シンプルなワークフロー • mainブランチで作業しない • 機能ごとにブランチを作成(フィーチャーブランチ) • 機能が完成したらマージ main feature フィーチャー ブランチ作成 好き勝手 コミット 完成したら マージ

Slide 8

Slide 8 text

8 44 Alice Bob main feature_A feature_B 機能A1を実装 機能A2を実装 機能Bを実装 mainにマージ mainにマージ

Slide 9

Slide 9 text

9 44 ワークフローは複数人開発のためのルールだが、個人開発でも有用 もしあなたが「まっすぐ一本な歴史」で開発していたら? 機能Aを途中 まで開発 機能Bを途中 まで開発 機能A完成 機能B完成 ここでバグ発覚 歴史が全て中途半端な状態 どのコミットがバグに寄与しているのかわかりづらい

Slide 10

Slide 10 text

10 44 main feature_A feature_B ここでバグ発覚 機能Aを途中 まで開発 機能A完成 機能Bを途中 まで開発 機能B完成 もし「機能ごとにブランチを派生させる」ルールを守っていたら?

Slide 11

Slide 11 text

11 44 もし「機能ごとにブランチを派生させる」ルールを守っていたら? 機能AとBが両方実装された状態 機能Aのみ実装された状態 機能Bのみ実装された状態 これらを調べることで 容疑者を限定できる 単独開発でも多人数開発のルールを導入した方がよい 三日前の自分は他人

Slide 12

Slide 12 text

12 44 なぜブランチを分けるか • いま行っている作業は何かを明確にするため • 複数の作業を同時に行わない • Gitは便利ツールではなく「作業フロー」を 実現するのを助けてくれるツール • 「作業フロー」を変えなければ意味は薄い ワークフローとは何か • 多人数開発時のブランチ運用のルール • 個人開発でも有用 • 基本は「mainでコミットしない」こと • ブランチで開発、mainにマージ

Slide 13

Slide 13 text

13 44 Gitの歴史:コミットがつながったもの ブランチ:コミットについたラベル カレントブランチ:HEADが指しているブランチ branch HEAD branch HEAD other other コミットするとコミットが作られカレントブランチが指していたコミットにつながる さらにカレントブランチが新たに作られたコミットを指す 他のブランチは動かない ブランチを切り替えてコミットすると歴史が分岐する

Slide 14

Slide 14 text

14 44 コミットは「自分の親コミット」を知っている 親コミットをたどることで歴史をたどることができる コミット1 コミット2 コミット1 コミット2 + = パッチ コミット1からコミット2が作られた コミットはそれぞれの時点でのスナップショット表す コミット間の線は差分(パッチ)を表す

Slide 15

Slide 15 text

15 44 ブランチの作成 $ git branch ブランチ名 ブランチをつけたいコミット 「コミット」を省略すると、カレントブランチが指すコミットに別名をつける $ git branch ブランチ名 ブランチの切り替え $ git switch ブランチ名 ブランチを作成して切り替える $ git switch –c ブランチ名

Slide 16

Slide 16 text

16 44 main HEAD main HEAD newbranch main newbranch HEAD $ git branch newbranch $ git switch newbranch $ git switch -c newbranch

Slide 17

Slide 17 text

17 44 「カレントブランチ」に「ブランチ名」の修正を取り込む $ git merge ブランチ名 個人開発の場合、mainであることがほとんど $ git merge ブランチ名を省略すると「上流ブランチ」をマージ(後述) マージとは、二つのブランチの共通祖先を見つけ、そこから の修正を全て取り込んだ新たなコミットを作る作業

Slide 18

Slide 18 text

18 44 main feature + + + = main feature $ git merge feature HEAD HEAD カレントブランチがマージしたいブランチの直接の祖先であるとき カレントブランチを 移動するだけでマージ完了

Slide 19

Slide 19 text

19 44 main feature HEAD main feature HEAD $ git merge feature 新に作られたコミットを マージコミットと呼ぶ

Slide 20

Slide 20 text

20 44 main branch HEAD + + = = non fast-forwardマージで作られるコミット 親コミットを二つ持つ 二つの線は、それぞれの親からの差分を表している

Slide 21

Slide 21 text

21 44 main feature HEAD main feature HEAD + $ git merge --no-ff feature Fast-forwardマージ可能な場合でもマージコミットを作ることができる

Slide 22

Slide 22 text

22 44 main feature HEAD マージコミットを作ると、機能追加をしようとした ブランチがどこから分岐したかの情報が残る main feature HEAD fast-forwardすると歴史が一本になって見やすい どこから分岐したかの情報が失われ、マージを取り消しづらい

Slide 23

Slide 23 text

23 44 $ git branch -d ブランチ名 指定したブランチを削除する 削除されるのはブランチだけで、コミットは消えない main feature HEAD $ git branch –d feature Deleted branch feature (was 1c168e1). main HEAD 1c168e1 削除前に指していたコミットハッシュが表示される

Slide 24

Slide 24 text

24 44 main HEAD feature $ git branch -d branch error: The branch 'feature' is not fully merged. If you are sure you want to delete it, run 'git branch -D feature'. マージされていないブランチを削除しようとするとエラーになる もしどうしても削除したいのなら「-D」オプションをつけろ

Slide 25

Slide 25 text

25 44 main HEAD もし削除したら? コミットは失われない しかし、既存のブランチからたどれなくなる mainブランチからたどれるコミット 各コミットは「親コミット」を覚えている 「親コミット」をたどることで自分のルーツを知ることができる 「子供コミット」は知らない マージされていないブランチを削除すると、たどれないコミットが生じる どのブランチからもたどれない

Slide 26

Slide 26 text

26 44 main old_branch new_branch new_branchからたどれるので git branch –d old_branchができる mainからたどれないので git branch –d old_branchができない 「マージされていないから削除できない」というエラーが 出る条件は「カレントブランチ」からたどれるかどうか カレントブランチがnew_branchの時は削除できる カレントブランチがmainの場合は削除できない $ git branch –d old_branch

Slide 27

Slide 27 text

27 44 分岐した歴史をマージする際、Gitは可能な限り、両方の修正を取り込もうとする main HEAD feature main HEAD $ git merge feature 異なるファイルが修正されていたら、両方の修正を取り込む 同じファイルでも、修正箇所が重なってなければ取り込む

Slide 28

Slide 28 text

28 44 同じファイルの同じ場所が同時に修正されていた場合、Gitは どのようにマージすれば良いか判断できない。これを衝突 (conflict)と呼ぶ。 main HEAD feature main HEAD $ git merge feature

Slide 29

Slide 29 text

29 44 mainとfeatureで同時にtest.txtを修正した状態でマージしようとした $ git merge feature Auto-merging test.txt CONFLICT (content): Merge conflict in test.txt Automatic merge failed; fix conflicts and then commit the result. • test.txtを自動マージしようとした • 衝突(CONFLICT)が発生した • 衝突を解決(fix)して、結果をコミットせよ Gitは衝突を検出すると、ユーザに解決を求める

Slide 30

Slide 30 text

30 44 Hello merge! This line is modified on main. カレントブランチ (HEAD)でのtest.txt featureブランチ でのtest.txt Hello merge! This line is modified on feature. マージに失敗し、衝突状態のtest.txt Hello Merge! <<<<<<< HEAD This line is modified on main. ======= This line is modified on feature. >>>>>>> feature ここがHEADではこう なってるけど featureブランチではで はこうなってるよ

Slide 31

Slide 31 text

31 44 test.txtを適切に修正したあと、git add、git commitする main HEAD 「マージコミット」を作っている Gitが自動でマージコミットを作れなかったので、マージ コミットのあるべき姿をインデックスに登録してコミット

Slide 32

Slide 32 text

32 44 マージとは何か • 分かれた歴史を一つにまとめる作業 • 合流点を「マージコミット」と呼ぶ • マージしたいブランチが直系の子孫である場合はブラ ンチの移動でマージが完了する(fast forwardマージ) 衝突とその解決 • 自動でマージコミットを作れない状態を衝突と呼ぶ • ユーザはマージコミットのあるべき姿をインデックス に登録、コミットする

Slide 33

Slide 33 text

33 44 • カレントブランチとリベース先のブランチの共通祖先から • カレントブランチまでの修正を • リベース先のブランチにぶら下げる 操作のこと リベースとは main branch HEAD main branch HEAD mainとbranchの共通祖先 $ git rebase branch

Slide 34

Slide 34 text

34 44 main branch HEAD c1 c2 c1’ c2’ $ git rebase main + + + + branch HEAD main

Slide 35

Slide 35 text

35 44 main branch HEAD main branch HEAD $ git rebase branch branchブランチのベース(赤丸)を、mainブランチの指すコ ミットに張り替えたように見えるのでリベース 移動しているのは「線(パッチ)」であってコミットではない コミットは新たに作られる

Slide 36

Slide 36 text

36 44 Alice Bob main feature_B 機能Aを実装 機能Bを実装 mainにマージ • mainブランチから、Aliceがfeature_Aを、Bobがfeature_Bを分岐 • Aliceが機能Aを実装してmainにマージ(fast-forward) • Bobが機能Bを実装してmainにマージ(non-fast-forward)

Slide 37

Slide 37 text

37 44 • mainブランチから、Aliceがfeature_Aを、Bobがfeature_Bを分岐 • Aliceが機能Aを実装してmainにマージ(fast-forward) • Bobが機能Bを実装してmainへリベース • feature_Bがfast-forwardマージ可能になる main feature_B main feature_B main rebase merge 機能A を実装 機能B を実装 順番に開発したような歴史が作られた

Slide 38

Slide 38 text

38 44 HEAD main feature_A A1追加 A2追加 Doc追加 フィーチャーブランチをそのままmainにマージすると 「中途半端な状態のコミット」が残ることがある このままfast-forwardマージすると、「途中経過」も歴史に残る mainブランチは「常にきれいな状態」であって欲しい 機能A実現のためのサブタスク

Slide 39

Slide 39 text

39 44 HEAD main feature_A A1追加 A2追加 Doc追加 HEAD main feature_A 機能A追加 + マージする前にコミットをまとめて歴史を整理しておく 歴史の整理にもリベースを使う

Slide 40

Slide 40 text

40 44 $ git rebase -i ブランチ名 pick d6f185f c1 pick b2b0b0b c2 # Rebase e9c8c91..b2b0b0b onto e9c8c91 (2 commands) エディタが開き、以下のような画面がでる リベースで移動するコミット候補が表示されており どのコミットをどうするか選ぶ pick : コミットをそのまま使う squash: コミットは使うが、前のコミットとまとめる コミットを破棄したり、並べ替えたりもできる

Slide 41

Slide 41 text

41 44 c1 c2 $ git rebase –i main + + branch HEAD main + + c1’ c2’ pick c1 pick c2 + + pick c1 squash c2 + + + c1’

Slide 42

Slide 42 text

42 44 main branch HEAD c1’ c2’ + + 移動するコミットの数だけ衝突する可能性がある リベースは「ベース」となるコミットに次々と 修正を適用すること操作 「次に作られるべきコミット」をインデックスに 登録してコミットを繰り返す

Slide 43

Slide 43 text

43 44 リベースとは、ブランチ(枝)の根本(ベース)を 別の場所に移し替える操作 マージの前処理として行う 主に以下の歴史操作を行う ・分岐した歴史を一本にまとめる ・不要なコミットをまとめる(中間状態削除) 事故が起こりやすく、トラブルが起きた時の対処が難 しいため、慣れるまでは使わなくて良い

Slide 44

Slide 44 text

44 44 Gitは原則としてmainブランチで作業せず、機能 ごとにブランチを作って作業、後でマージする ワークフロー マージと衝突の解決 マージは二つの歴史の修正をまとめたマージコ ミットを作る作業であり、衝突の解決とは「あ るべきマージコミットの姿」をGitに教えること リベース リベースはマージの前処理として行う 「欲しい歴史」を作るために使う