昨天整理完專案結構後,我們最後留下一個問題:
每一次有人進來改過什麼,我們要怎麼留下紀錄?
這就來到我學程式初期的一個大魔王:
Git。
我一開始學 Git 的時候,花了很多時間把各種指令整理起來。
git add 是什麼、git commit 是什麼,甚至還畫了一張超~~大的心智圖,把看到的指令和概念全部歸納在一起。
但當時還沒有真正做專案,也沒有跟別人協作,所以其實也不太知道:
哪些才是最常用、真正重要的?這些指令又是在什麼情況下派上用場?
而當時的我,其實也還沒有真的搞懂:
所謂的「版本控制」,到底是在控制什麼?
Git 是一種版本控制(Version Control)工具,用來追蹤與管理程式碼的修改歷史。
假設今天我一直在調整一份魔法藥水配方。
第一版是:
月光草 2 株
星砂 1 匙
後來覺得魔力不夠,我直接改成第二版,存檔:
月光草 3 株
星砂 1 匙
再試了一次之後,又調整成第三版:
月光草 3 株
星砂 2 匙
龍之露 1 滴
做到這裡都沒什麼問題。
直到我突然覺得:
「等等,好像第二版的效果比較好,我想改回去。」
這時才發現,前面的配方早就被後面的修改覆蓋掉了。
如果沒有另外留下舊版本,單純一直修改、存檔,之後想回到前面某一次的內容,就不一定還找得回來。
Git 要解決的其中一個重要問題,就是:
讓同一份專案在不同時間點留下可以追蹤的版本歷史。
先把整張地圖攤開來看:
③ 本地儲存庫(Local Repository)
▲
│ git commit
│
② 暫存區(Staging Area)
▲
│ git add
│
① 工作目錄(Working Directory)
└─ 平常正在新增、修改檔案的地方
這三個區域,可以先很白話地理解成:
| 區域 | 在做什麼 |
|---|---|
| 工作目錄(Working Directory) | 我現在正在修改、還沒準備提交的檔案 |
| 暫存區(Staging Area) | 先挑出這一次準備提交的修改 |
| 本地儲存庫(Local Repository) | 把已經提交(Commit)的版本正式保存下來 |
一開始學 Git 的時候,我也覺得這三個名字很抽象,但把它們放進同一條流程之後,就會清楚很多。
一份修改會先出現在工作目錄,透過 git add 選進暫存區,再透過 git commit 正式留下版本紀錄。
原來這三個區域不是各自獨立的名詞,而是在描述:
一份修改,怎麼一步一步變成一個版本。
先把整張地圖攤開來看:
③ 本地儲存庫(Local Repository)
▲
│ git commit
│
② 暫存區(Staging Area)
▲
│ git add
│
① 工作目錄(Working Directory)
└─ 平常正在新增、修改檔案的地方
這三個區域,可以先很白話地理解成:
| 區域 | 在做什麼 |
|---|---|
| 工作目錄(Working Directory) | 我現在正在修改、還沒準備提交的檔案 |
| 暫存區(Staging Area) | 先挑出這一次準備提交的修改 |
| 本地儲存庫(Local Repository) | 把已經提交(Commit)的版本正式保存下來 |
一開始學 Git 的時候,我也覺得這三個名字很抽象,但把它們放進同一條流程之後,就會清楚很多。
一份修改會先出現在工作目錄,透過 git add 選進暫存區,再透過 git commit 正式留下版本紀錄。
原來這三個區域不是各自獨立的名詞,而是在描述:
一份修改,怎麼一步一步變成一個版本。
git add:這次要把哪些進度一起存下來?前面知道了 git add 會把修改送進暫存區,但這一步真正重要的地方其實是:
我不一定要把所有修改一次全部 Commit。
假設今天一路闖關,而這三段進度剛好分別記錄在不同檔案裡:
完成第一關.js
修好壞掉的傳送門.js
新增一個支線任務.js
但這一次,我只想先把「完成第一關」這段進度整理成一個版本。
這時就可以用:
git add 完成第一關.js
先把這個檔案的修改選進這一次準備提交的內容。
也就是先告訴 Git:
等等建立下一個存檔點時,這些修改要算進去。
所以 git add 還不是正式留下版本。
它比較像是存檔以前,先整理好這一次準備記錄的進度。
等這些修改都準備好了,下一步才是真的按下「存檔」——
git commit。
git commit:像在遊戲裡留下一個存檔點我覺得 git commit 很像玩遊戲闖關時留下的存檔點。
假設前面已經用 git add 把這次想記錄的進度整理好了,接下來就可以:
git commit -m "完成第一關"
建立一筆 Commit。
如果用遊戲來想,就是:
真的按下存檔,把目前準備好的進度正式留成一個節點。
↑ 較新的提交
● 新增支線任務
│
● 修好壞掉的傳送門
│
● 完成第一關
↓ 較舊的提交
一路打怪、解任務的過程中,可以在不同時間點留下不同的存檔。
如果後面真的打怪打死了,至少前面還有已經留下來的存檔點,可以回頭找先前的版本重新處理。
Git 裡也是一樣。
每執行一次 git commit,就會在版本歷史裡留下一筆新的 Commit,記錄這一次準備好的修改。
所以:
git add
→ 決定這次要記錄哪些修改
git commit
→ 正式把這些修改留成一筆版本紀錄
這也是我第一次真的搞懂:
原來 Git 不是我每打一個字,都偷偷在後面存一份。
我以前甚至很怕:
「那我打錯一個字又刪掉,大家是不是也會看到??」
後來才發現,只是修改又刪掉、沒有把那個狀態 Commit,根本不會因此憑空多出一個 Commit(笑)。
git status:我現在到底在哪個狀態?前面知道了修改會經過工作目錄、暫存區,再變成 Commit,但實際操作時我最常遇到的問題反而是:
我現在到底做到哪一步了?
這時候一個很實用的指令就是:
git status
它可以幫我們查看目前工作目錄和暫存區的狀態,例如:
哪些檔案被修改了?
哪些修改已經加入暫存區?
哪些修改還沒有加入暫存區?
所以實際操作時,比起慌張到開始亂打一堆指令,我現在反而會先確認:
git status
先知道自己的修改現在走到哪一步,再決定下一步。
前面把 git commit 想成遊戲裡的存檔點之後,接下來就會遇到一個很自然的問題:
如果後面真的改壞了,要怎麼回頭?
Git 裡其實有不同的做法,要看現在想處理的是「檔案修改」,還是已經留下來的 Commit。
| 指令 | 可以先怎麼理解 |
|---|---|
git restore |
還原檔案內容,例如放棄還沒提交的修改 |
git revert |
新增一筆 Commit,把先前某次 Commit 造成的修改反向抵消 |
git reset |
把目前所在的版本位置移到指定的 Commit,再依使用模式決定修改要保留在哪裡 |
git restore:檔案改壞了,想先還原如果只是正在修改某個檔案,結果越改越奇怪,而且這些修改還沒有 Commit,可以用 git restore 把檔案內容還原。
例如:
git restore game.js
可以先理解成:
這個檔案剛剛改的內容我不要了,幫我恢復。
不過要注意,沒有另外保存的修改可能會因此消失,所以執行以前還是要先確認。
git revert:版本已經留下來了,再新增一筆把它抵消如果有問題的修改已經變成一筆 Commit,git revert 不會把原本的 Commit 從歷史裡刪掉。
它會另外新增一筆 Commit,把指定 Commit 帶來的修改反向抵消。
可以想成:
● 取消「打開錯誤傳送門」
│
● 打開錯誤傳送門
│
● 完成第一關
錯誤的那筆版本還看得到,只是後面又多留下一筆「把它取消」的紀錄。
git reset:把目前的版本位置移到指定的 Commitgit reset 做的事情又不太一樣。
它可以把目前所在的版本位置移到指定的 Commit。
但移動之後,原本那些修改要留在哪裡,會依 reset 使用的模式而不同。
可以先這樣理解:
| 模式 | 修改最後在哪裡? |
|---|---|
--soft |
修改仍保留在暫存區 |
--mixed |
修改保留在工作目錄,變回尚未加入暫存區的狀態 |
--hard |
尚未另外保存的修改可能被直接丟掉 |
所以看起來都像是在「回到前面的存檔」,但 Git 實際上會依你想處理的東西,用不同的方法:
Git 裡的「回頭」不是只有一顆復原鍵,而是要先判斷自己想還原的是檔案、取消某次 Commit,還是調整目前所在的版本位置。
我覺得用 AI 寫程式,很像突然多了一個超會打怪的隊友。
我只跟它說:
「幫我把活動頁重構一下,順便整理相關元件、狀態管理和 API(應用程式介面)。」
它馬上一路清怪、解任務,一口氣動了好幾個相關檔案。
結果程式一跑——
全滅。
連原本正常的功能也一起壞掉。
這時候我最想做的就是:
「拜託讓我回到上一個還正常的存檔點復活!」
如果前面有用 Git 留下 Commit,至少還知道上一個正常的版本在哪裡,可以從那裡重新處理。
AI 幫我高速闖關,Git 則提醒我:打 Boss 以前,先存檔。
以前我一直覺得 Git 的重點,是把每個指令記熟。
真的開始做專案後,我才慢慢發現:
版本控制真正讓我安心的地方,是我知道自己可以繼續往前改。
因為前面的版本還在,後面的嘗試就不會每一次都像在孤注一擲。
不過現在這些版本,還全部留在我的電腦裡。
如果今天想把成果分享給別人,甚至要和其他人一起開發呢?
下一篇,就把這份「本機存檔」正式送上雲端。