問題所在:final_v3_REALLY-final.docx
你大概已經做過這種事,哪怕你還沒寫過程式。你做完一份東西,存成 report.docx,改了之後存成 report_v2.docx,再來是 report_final.docx,然後是 report_final_REALLY.docx。一週後,沒人(包括你自己)知道哪個才是真正的版本,也不知道它們之間到底改了什麼。
Git 正是治這種亂象的良藥。它是一個工具,會在你工作時悄悄記錄專案的歷史,於是你只保留一套檔案——而不是十二個副本——卻依然能回到過去任何一個時刻。可以把它想成一個無限量、自動的「復原」:它記得的不只是上一步,而是你保存過的每一個重要步驟,還附帶一句「為什麼」。
思維模型:儲存庫與快照
Git 裡的一切都住在一個儲存庫(repository,常簡稱 repo)裡。儲存庫其實就是你的專案資料夾加上它的全部歷史。在你電腦上,這些檔案看起來完全正常;歷史則藏在一個由 Git 替你管理的隱藏資料夾裡。把一個普通資料夾變成儲存庫,只需要一條指令。
git init
歷史由一個個提交(commit)組成。一次提交就是你所有檔案在某一刻的快照,外加一句你自己寫的簡短說明,解釋改了什麼、為什麼改——比如「加上登入按鈕」或「修正首頁的錯別字」。每次提交都連著它前面的那一次,於是整條鏈讀起來就像一條帶標籤的專案時間線。你隨時都能回到任何一個快照。
日常循環:編輯、暫存、提交
日常工作裡,你會反覆做一個簡短的循環。你照常編輯檔案。然後你告訴 Git 想把哪些改動打包在一起——這個中間步驟叫暫存(staging),它讓你可以現在提交一部分改動、晚點再提交另一部分。最後你提交,並附上說明。整個節奏就是這樣。
- 在編輯器裡改檔案,照常儲存。
- 暫存你想保留的改動:git add .(那個點表示「我改過的所有內容」)。
- 帶一句說明提交:git commit -m "Add contact form"。
那句說明比初學者想像的更重要。幾個月後,你正是靠這句話來理解某次改動,而不必重新打開程式碼。好的說明能接上「這次提交會……」——「修復信箱為空時的當機」永遠勝過「一些東西」或「asdf」。目標是:簡短、具體、用現在式。
分支:一條安全的並行線
有時你想試一個新點子,又不想冒險動到那個已經能用的版本。分支(branch)正是為此而生:它是一條從主歷史岔出去的並行工作線。在分支上你可以放手實驗——提交、把東西弄壞、改主意——而你那個穩定的主版本始終毫髮無傷。
git switch -c new-idea
等這個點子成功了,你就用合併(merge)把它帶回來。合併是在告訴 Git 把你分支上的提交疊回主線,於是兩條歷史再次合為一條。如果點子失敗了,你只要刪掉分支,就好像那段繞路從未發生過。這正是分支讓人變勇敢的原因:你嘗試的任何東西,都傷不到那個已經能用的版本。
和別人協作:Git 與 GitHub 的區別
人們常把這兩者混為一談,但它們是不同的東西。Git 是跑在你自己電腦上、負責追蹤歷史的工具。GitHub 則是一個網站,它在雲端存一份你儲存庫的副本,讓你和別人能共享它、查看它、一起在上面工作。你完全可以一個人用 Git;而 GitHub 才是把它變成團隊協作的關鍵。(GitLab 和 Bitbucket 是類似的網站。)
想給別人的專案做貢獻,你通常會複刻(fork)它——這會在 GitHub 上為你建一份屬於你自己的副本。你改你的副本,然後開一個拉取請求(pull request):一份禮貌、可供審查的提議,意思是「這是我的改動,請考慮把它們合併到你的專案裡」。專案擁有者會讀它、留言、也許請你再調整,滿意了就點「合併」。
當它打結時:衝突與變基
遲早,你和隊友會用兩種不同的方式去改同一個檔案的同一行。Git 沒法猜誰的版本才對,於是它停下來,給你看一個合併衝突(merge conflict)。這不是失敗,也不是懲罰——只是 Git 在誠實地請你做選擇。它會在檔案裡用清楚的分隔標記,把兩個互相競爭的版本標出來。
<<<<<<< HEAD Welcome to our site! ======= Welcome to our shop! >>>>>>> their-branch
你還會聽到一個詞叫變基(rebase)。它是合併之外的一種進階選擇,會把你的提交「重放」到最新的工作之上,得到一條更整潔、筆直、沒有多餘「合併」記錄的歷史。它確實很有用,但它會改寫歷史,所以作為初學者,現在大可先放著它、用普通的合併就好——等基礎用順了,再去學變基。
回顧:日常五條指令
基礎就這些。隨著時間你會學到更多,但日常 Git 裡九成的操作,不過是少數幾條指令以一種順手的節奏不斷重複。把這張小抄放在手邊,直到你的手指自己記住它。
git status # what's changed? git add . # stage everything git commit -m "..." # save a snapshot git push # send commits to GitHub git pull # get others' commits