W02 版本控制

- 存檔、同步、紀錄版本
- 這是一個讓我們安心,也讓我們合作的工具
再次確認:你有分組嗎?
打開 Gitea,你應該看到兩個版本庫:

notes:全班共用- 你們組的版本庫
只看到 notes 的舉手。
版本庫(repository),簡稱 repo:放一個專案所有檔案和歷史的地方。
投影片在哪裡

shu-dma-mult285.pages.dev 每週的投影片都會公開,課後可以回來看。
心態調整

上週大家已經體驗到了一些挫折
- 沒有不會出錯的系統
- 不管是寫程式還是工作流程,很少一次就對
重要的是這三件事

- 及早發現
- 找到原因
- 解決它
卡住了不要自己悶著,勇敢問。
從需求出發:一個專案裡有什麼
一個複雜的專案會包含:

- 很多不同類型的檔案:程式、圖、音效、模型、文件
- 很多決策:為什麼這樣做、後來為什麼改
- 很多人:每個人同時在改不同的地方
- 很多版本:昨天的、上週能動的、要交出去的那一版
所以團隊需要三件事

- 資料要存得住
- 組員都拿得到檔案
- 大家都知道哪一份才是對的
第三件最常出事:檔案都在,但沒人確定該用哪一個。
平常我們怎麼做

- 開一個資料夾,然後越開越多
- 資料散在每個人的電腦裡
- 各種好用的網路工具:
- 發想:Canva
- 文字:Google Docs/HackMD
- 資料:Google Sheets
每種內容,各自的軟體
每種內容有自己的製作軟體,各自輸出檔案:

- 畫圖:Photoshop、Krita
- 建模:Blender
- 音效:Audacity
- 程式:VS Code
輸出的檔案最後都要回到同一個專案裡。 工具都很好,但東西會散在各處。
一個遊戲專案裡有什麼
- 程式碼
- 各式各樣的素材(assets):圖、音效、模型、動畫
- 團隊的工作紀錄
- 團隊決定好的事

最後一項最容易消失:「那時候說好的是哪個版本?」
單一事實來源(Source of Truth)
一個專案資料夾,就是全組唯一的正本。

- 一個大家可以一起工作的地方
- 正確性優先
- 不用那麼即時
需要即時討論?用通訊軟體就好。
還需要:記錄每一次改變

在發想和製作的過程中:
- 記下我們做過的所有改變
- 隨時都能恢復
- 隨時都能回到某個版本
這就是版本控制

版本控制能幫我們:
- 儲存空間:一個全組都拿得到的地方
- 記錄變化:誰、什麼時候、改了什麼、為什麼
為什麼需要版本控制
你一定遇過:

- 改壞了想回到昨天的版本 → 回不去
- 兩個人改同一個檔案 → 一個人的工作不見了
- 「這段是誰做的、為什麼這樣做」→ 沒人記得
版本控制解決這三件事。而且它可以當作個人貢獻的評分依據。
Gitea 是什麼?GitHub 是什麼?
| 是什麼 | 網址 | |
|---|---|---|
| Git | 版本控制的本體 | git-scm.com |
| GitHub | 放 Git 專案的網站,全世界最多人用 | github.com |
| Gitea | 同樣的東西,自己架的版本,只有修課的人看得到 | anxiously-unrecipient-aurora.ngrok-free.dev |

GitHub 上還有非常多好用的工具和開源專案。 善用工具,不要只局限於老師教的幾種。
像蓋房子:一層一層往上
- 每做完一件事,就往上蓋一層
- 新的一層蓋在上一層的基礎上,不是憑空出現
- 每一層都有自己的編號(ID),指名道姓找得到
- Git 記的是每一層跟上一層的差異:這層改了什麼
- 所以想看以前的樣子,往下數幾層就回得去

打開 Gitea 看看你們自己蓋的房子。
上週的衝突,是正常的
上週大家在網頁上簽到時,撞到了 Git 的衝突。

- 兩個人從同一層往上蓋
- 各蓋各的,變成兩棟不一樣的房子
- 要合起來,得先決定:上面那層照誰的做
衝突不是錯誤,是 Git 在問你。
提交(commit)是什麼
一次提交=一個存檔點。 每個提交都帶著:

- 日期
- ID(一串亂碼,每個提交獨一無二)
- 作者
- 說明(comment)
上週在網頁上簽到,就是一次線上提交。
提交會疊成一棵樹
- 每次提交都接在上一次後面
- 目前大家的提交都蓋在主線上,叫主分支(main)

●─●─●─● main
分支:多元宇宙
Git 可以開分支。

- 就像多元宇宙:每條分支是一個不同的現實
- 我們一次只能體驗一個現實
- 所以每個宇宙都要有名字,才能在宇宙之間穿越
分支長什麼樣

●─● feature
/
●─●─●─●─● main
從某一層岔出去往上蓋,就是一條新的分支。
分支其實是一個標籤
- 分支=一個標籤,指著某根樹枝的末端
- 在樹枝之間跳來跳去叫 checkout
- 跳過去,你看到的檔案就會變成那根樹枝上的樣子

在 Gitea 的分支選單裡,打勾的那個就是你現在站的分支。
雲端與本地端
到這裡為止,我們看的都是 Gitea 上的東西——那是雲端。

- 雲端(remote):Gitea 上的那一份,全組共用
- 本地端(local):你電腦裡的那一份
接下來這半堂課,全部都在講本地端。
為什麼要下載到自己電腦

- 網頁只適合改幾行字
- 真正要新增、修改內容,得在自己電腦上做
- 各種素材有各自的專業軟體(Photoshop、Blender、Godot…)
雲端和本地端的關係
- 雲端有一棵樹,本地端也有一棵樹
- 版本控制做的事:讓兩棵樹同步

雲端 ●─●─●
⇅ 同步
本地 ●─●─●
需要軟體來幫忙

- Git 原本是用命令列操作
- 能做的事很多,但有點複雜
- 所以我們用圖形介面軟體來幫忙
本地端的版本控制軟體:Fork

- 下載:https://git-fork.com/
- 用滑鼠就能看歷史、提交、同步
- 它做的每件事,底下都是 Git 指令
設定帳號
Fork → File → Preferences → Git:

- User Name=學號
- Email=學號@mail.shu.edu.tw
要跟 Gitea 帳號一樣,貢獻才算得到你頭上。
把專案複製到本地端:Clone

Clone:把雲端的整棵樹複製到本地端。
- 在 Gitea 的 repo 頁複製網址
- Fork → File → Clone,貼上網址
- 選一個放專案的資料夾
HEAD:你現在站在哪裡

- HEAD=你現在所在的位置
- 資料夾裡看到的檔案,就是 HEAD 指著的那個版本
在本地端編輯檔案
用你習慣的編輯器打開專案資料夾:

- VS Code
- Obsidian
改完存檔,回到 Fork,會看到哪些檔案變了。
從本地端提交:兩個步驟
- Stage(暫存):挑出這次要提交哪些改動
- Commit(提交):寫說明,存成一個存檔點

改檔案 ⇄ Stage → Commit
↓ Unstage
Discard(這次改壞了,不要了)
反悔的兩種按法
- Unstage:這個檔案這次不要一起提交,放回去就好,內容不會變
- Discard:把還沒提交的修改丟掉,檔案回到上一個提交的樣子

⚠️ Discard 丟掉就回不來,因為它還沒被記錄過。Unstage 則完全無害。
提交之後發生什麼

- 樹枝往上長一節
- HEAD 跟著移動到最新的提交
- ⚠️ 這時候提交還在你的電腦裡,雲端還沒有
提交的注意事項
- 一次提交只做一件事
- 說明不要亂寫:用一句話講這次做了什麼,動詞開頭

| 好的 | 壞的 |
|---|---|
新增組員名單 |
❌ update |
修正敵人卡在斜坡的問題 |
❌ 修正bug |
調整第一關的難度 |
❌ asdf |
實作(一):第一個提交
老師已經在每組的 repo 放了 README.md。
每組派一位同學:

- 在
README.md寫上組員名單 - Stage → Commit
- 做到這裡就停,其他按鈕先不要碰
實作(二):第二個提交
每組第二位同學:

- 在
README.md填上平台(例如 PC、手機、網頁) - Stage → Commit
- 一樣做完就停
現在兩個人的電腦裡各有一個新提交,Gitea 上還是舊的。
Push:同步到雲端
Push:把剛剛的提交送到雲端。

- 雲端的樹也往上長一節
- Fork 裡是同一棵樹、兩個標籤:
main= 你本地端的位置origin/main= 雲端的位置
- push 前兩個標籤不在同一層;push 後疊在一起
第一位同學先 push。 到 Gitea 重新整理,看到了嗎?
本地端與雲端的衝突

第二位同學 push → 出現警告。 恭喜,這是第二次衝突(第一次在上週)! 這次撞的不是同一行文字,是你的樹和雲端的樹對不起來。
為什麼會這樣
- 兩位同學改的是不同地方,commit 都成功
- 但第一位已經 push 了,
origin/main這個標籤往前移過了 - 你的提交接在舊的那一層上,接不回現在的
origin/main

看 Fork 的提交樹:main 和 origin/main 不但不在同一層,還岔開成兩邊。
衝突長什麼樣

● ← main(你的提交,只在本地)
/
●─●─●─● ← origin/main(雲端,多了組員的提交)
兩個標籤各自往上長,樹岔開了。要想辦法把它們接回同一條。
解決衝突:先看是什麼檔案
- 文字檔:有機會自動合併;改到同一行就手動決定留哪個
- 二進位檔(圖片、模型、場景):沒辦法合併,只能二選一

所以:素材和場景一個人負責一個,不要兩個人同時改。
動手之前:先 Fetch
Fetch = 去雲端問一下:你那邊現在長怎樣?

- 只是把雲端的最新狀態抓下來,不會動到你的檔案
- 抓完
origin/main這個標籤才會移到正確的位置 - 看得到真正的分叉,才選得出要用哪個方法
沒 fetch 之前,你看到的雲端是舊的。
方法一:不要了(Reset --hard)
你手邊改的東西(已提交)不重要、或本來就想重做:

- 直接丟掉自己的提交與修改
- 把雲端那份原封不動拉下來
- 從乾淨的狀態重新開始
⚠️ 丟掉就回不來了。 只有在「這些改動我不要」時才用。
Reset --hard 在 Fork 裡怎麼按

- 先 Fetch,讓
origin/main是最新的 - 在提交樹上找到
origin/main那一層 - 右鍵 →
Reset 'main' to Here - 模式選 Hard
方法二:Rebase(接枝)
改的東西要留著:

- 像花藝的接枝:把你的提交剪下來,接到雲端最新那一層上面
- 樹會保持一直線,比較好讀
●─●─●─●─● 你的提交接到最新的後面
Rebase 在 Fork 裡怎麼按
- 先 Fetch
- 確認你站在
main(左邊清單粗體的那個) - 在
origin/main上按右鍵 →Rebase 'main' onto origin/main - 有衝突 → Fork 跳出合併工具,解完按 Continue

做完:main 接到 origin/main 上面,樹變成一直線,可以 push 了。
方法三:Merge(合併)

- 把兩根樹枝合在一起,多一個「合併提交」
- 改到不同地方:自動合好
- 改到同一行:Git 標出來問你
改到同一行,Git 會問你
<<<<<<< HEAD
你的版本
=======
組員的版本
>>>>>>>
決定留哪個(或兩個都留)→ 刪掉標記 → commit → push

Merge 在 Fork 裡怎麼按

先站到要保留的那個分支上,再把另一邊的改動合進來。
- 你現在在
main→ 對origin/main按右鍵 → Merge into main - 合進來的是改動,不是整棵樹搬家
方法四:取消重來(Reset,改動留著)

- 用 Reset 撤銷自己的提交,但改動還留在檔案裡
- 先把雲端最新的拉下來
- 重新 commit,再 push
改動不多的時候,這個最簡單。
接下來:討論你們的專案

每組開始討論這學期想做什麼遊戲:
- 寫進
README.md,老師有提供模板 - 每個人用自己的帳號登入
- 試著編輯
README.md的不同段落,各自 commit、push
簽到

今天沒有提交和 push 過的人:
- 在
docs/attendance/w02.md簽名,commit、push
下週預告

W03 · 9/30 · AI 輔助開發
- 今天的 repo 下週繼續用
分支
前面都在 main 上一起做。分支是「另外開一條路」。

- 想試一個做法,又不想弄壞現在能動的版本
- 兩個人要改同一個東西,先各走各的,之後再合
分支很便宜:開一條、丟掉一條,都只是動一個標籤。
開一條分支(Create Branch)
在 Fork 裡:

- 在提交樹上,找到要從哪一層岔出去(通常是
main的最上面) - 右鍵 →
Create Branch… - 取個名字,例如
try-jump - 勾「Check out after create」=開完直接站過去
開完只是多一個標籤,檔案不會變。
站過去(Checkout)
Checkout = 換一條路站。

- 左邊清單雙擊那個分支名,就站過去了
- 檔案跟著變成那條路上的樣子
- 粗體的那個就是你現在站的分支
⚠️ 站過去之前,手邊的修改先 commit 或 discard,不然會擋著。
丟掉一條分支(Delete Branch)
這條路試完了、或不要了:

- 先站到別條分支(不能站在自己身上刪自己)
- 在要刪的分支上右鍵 →
Delete…
- 刪的是標籤,提交還在,只是沒有名字指著它了
- 雲端那條要另外刪(Fork 會問要不要一起刪遠端)
分支要不要用在這門課

- 今天開始:全組在
main上一起做就好 - 之後專案變大、兩個人要同時改同一個場景,再開分支
- 那時候會一起講 Pull Request 和 review
不要為了用而用。 三個人的小專案,分支常常只是增加麻煩。