问题所在: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