1. 环境准备:先把 Git 装好、配好、连上远程仓库
我没少见过这样的场景:电脑里已经装了一堆 IDE,写代码也写了几个月,但某天想用 Git 拉个仓库,终端敲了个git pull,直接弹出一句"git 不是内部或外部命令"。这时候第一步还真不是学命令,而是先把 Git 安装和环境配明白。
在 Windows 上装 Git 其实很简单,去官网下载安装包,一路 next 就行。真正的问题是安装完之后要做的事。装完必须验证一下:
git --version如果输出了类似git version 2.40.0.windows.1,说明安装成功。要是终端提示无法识别,十有八九是没加环境变量,或者你开着旧的终端窗口,重新打开一个就好。这里有个小建议,安装时勾选"Add to PATH",后面省去一堆麻烦。要是环境变量已经坏了,手动去系统设置里把 Git 的cmd目录加到 PATH 就行。
装完之后别急着 clone,先把自己身份信息配上。这一步不做,哪怕你 commit 成功,提交记录里也是匿名状态,推到远端后别人看到的就是一串乱码一样的作者名。
git config --global user.name "你的名字" git config --global user.email "你的邮箱"现在多数代码托管平台,提交邮箱最好和账号绑定的邮箱一致,这样提交记录能正确关联到你的头像和账号。配完之后可以用git config --global --list检查一下。
然后就是连接远程库。现在几乎都是走 SSH 或 HTTPS 两种方式。个人电脑上我强烈建议用 SSH,配好一次之后可以实现长期免密操作,之后几乎不用再输账号密码。生成密钥命令:
ssh-keygen -t rsa -b 4096 -C "你的邮箱"一路回车确认默认路径就行。生成完看到个类似"Your public key has been saved"的提示,再把公钥内容复制出来。Windows 下可以直接这么操作:
cat ~/.ssh/id_rsa.pub把输出的内容整个复制,然后去代码托管平台(比如 Gitee、GitLab 或者 GitHub)的个人设置里找到 SSH 公钥管理入口,新建一个 key,粘贴进去保存。之后测试一下连接:
ssh -T git@gitee.com看到欢迎提示就说明配好了。这里很多新手会出问题,就是明明公钥贴对了,还是提示验证失败。大概率原因是第一次连接时,是否信任该主机没有确认,有个交互提示输入 yes,很多人没注意到就卡在那。多试一次,看到确认提示敲 yes 回车就行。
这些配置完成后,环境才算真正可用。
2. 基础工作流:一条代码从写出来到合进主干,中间到底发生了什么
Git 的基本工作流可以用一句非常生活化的话概括:在本地仓库里改文件,然后给改动拍快照,最后把快照推到云端。很多人学了几个月 Git,会用 pull 和 push,但对中间环节的理解一直模糊,导致出了问题完全没法排查。
2.1 克隆仓库和创建分支,别在主干上直接动刀
拿到一个项目后第一步是把它拉到本地:
git clone git@gitee.com:yourname/yourproject.git也可以带协议换 HTTPS,但前面说了推荐 SSH。clone 完成之后,你一定不想直接在默认分支上改东西,尤其多人协作时,主干分支往往有保护机制,直接推上去大概率被拒。正确做法是先建自己的分支:
git checkout -b feature/user-login这条命令等于git branch feature/user-login加git checkout feature/user-login,创建并切换一步搞定。分支名建议语义化,一眼能看出你要做什么,比如手机号登录、修复首页白屏这类,不要瞎起个test或者aaa,不然过两周你自己都看不懂。
2.2 从工作区到暂存区,git add 到底是干什么的
很多人对git add的理解就是"提交之前先执行一下",其实它的本质是把文件从工作区放入暂存区。打个最简单的比方,工作区是你办公桌上的草稿,暂存区是你的待办收纳盒,只有放进收纳盒的东西,最后才可能被正式归档。
日常工作流一般是:
# 查看当前文件状态 git status # 添加单个文件 git add src/App.js # 添加所有改动文件(谨慎使用) git add .git add .很省事,但也很危险,容易把不该提交的文件也加进去,比如临时生成的日志、本地的配置文件、IDE 目录。所以项目里一定得有.gitignore文件,把.idea/、node_modules/、*.log这类东西排除掉。实操中我的习惯是先git status看清楚有哪些改动,然后逐个 add,或者用git add加指定路径,尽量避免无脑全部提交。
2.3 commit 是在创造项目历史节点
add 完之后就要 commit 了。这是给当前暂存区的改动创建一个历史节点。基本命令就一行:
git commit -m "feat: 新增用户登录功能"很多人觉得 commit 就是留个记录,随便写个"更新"或者"修改"就完事。但我得说,提交信息是你留给未来同事和未来自己的阅读笔记,价值很大。看一眼历史时,如果全是update、fix、改bug,那你根本不知道哪个提交对应哪个功能。好的提交信息格式大概是:提交类型 + 范围 + 简述。比如feat(user-login): 完成手机验证码登录接口,或者fix(cart): 修复购物车金额精度丢失问题。这套规范属于经验层面,不一定有硬性工具约束,但对协作效率的提升非常明显。
git commit 有个很重要的逻辑要先理解:commit 提交的是暂存区的内容,不是工作区所有改动。假如你改了三个文件,只 add 了两个就 commit,那第三个文件不会出现在这次提交里。查漏补缺之前可以通过git status看剩余改动。
2.4 push 和 pull,一个是寄出去,一个是收回来
本地 commit 提交完了,代码还在本地,得推送远端,这样别人才看得到你的分支:
git push origin feature/user-login如果分支第一次推送,会提示你设置上游分支。按它提示的敲就行,或者直接用:
git push -u origin feature/user-login-u的作用是把你本地分支和远端分支关联起来,之后在同一分支上直接敲git push就能推送,不用再指定远端。收代码则用 pull:
git pull origin devgit pull 的本质是 fetch 加 merge。fetch 会把远端的新提交下载到本地,merge 则将这些提交合并到当前分支。所以有时候网络没问题但 pull 失败,往往是 merge 的时候发现了冲突。这个后面专门说。
如果把这套流程走完,基本的一条代码流转闭环就完成了。但协作项目中往往没那么顺利,中间会遇到分支合并、冲突处理、提交信息写错之类的情况,这就是下一部分要解决的问题。
3. 分支合并与冲突:协作开发绕不开的核心环节
多人协作用一个仓库时,分支合并是每天都要做的事情。你开发完功能,要把分支合回主干;别人往主干推了新东西,你也要把自己的分支更新到最新,这个过程必然大量涉及 merge 和 rebase。
3.1 merge 和 rebase,什么时候用哪个
合并分支最常见的方式是 merge:
# 先把要合入的分支代码拉到本地 git checkout dev git pull origin dev # 切回功能分支,把 dev 合并进来 git checkout feature/user-login git merge devmerge 的特点是会生成一个额外的合并提交,保存了两条分支的历史轨迹。好处是保留了完整的真实开发记录,适合主力分支合入功能分支时用。缺点就是历史里会有很多分叉节点,看起来比较乱。
如果不想让历史那么多分叉,可以改用 rebase:
git checkout feature/user-login git rebase devrebase 的理念是把当前分支的基础移动到目标分支最新提交之上,让提交历史呈现一条干净的直线。但它有个前提,就是你正在 rebase 的分支还没推送到远端,或者只有你一个人在维护。如果你已经把分支推到远端,别人也拉了,这时候 rebase 就会改写远端已有提交的哈希,导致别人本地和远端对不上。
我个人的习惯是:本地分支整理和更新用 rebase 多一点,最终并入主干用 merge。这个属于团队风格和倾向的问题,没有绝对的对错,但原理一定要理解清楚。
3.2 冲突到底是怎么产生的,又如何解决
冲突产生的原因很简单:两个分支改了同一个文件的同一片区域,Git 无法自动判断谁是对的,只能让人类来裁决。比如你在功能分支里把某一行从a = 1改成了a = 2,同时 dev 分支上另一个人也把这一行改成了a = 3,合并时就必然冲突。
遇到冲突时 Git 会给出提示,一般会说CONFLICT (content): Merge conflict in src/App.js。同时被冲突的文件里会出现标记:
<<<<<<< HEAD a = 2 ======= a = 3 >>>>>>> dev上面部分是当前分支的内容,下面部分是合并过来的目标分支内容。你要做的就是打开文件,把两个版本整合成最终想要的样子,然后删掉这些标记。整合完保存,接着执行:
git add src/App.js git commit这里有个很重要的细节,merge 时如果冲突解决完,不需要再 commit,Git 会自动生成合并提交。但如果中间乱了,想放弃这次合并回到之前状态,可以用:
git merge --abort我遇到过不少新手被冲突标记吓退,本来合并完成后一套操作就行,结果直接在编辑器里乱改,把代码结构改坏了。记住,冲突不可怕,它只是 Git 在确认到底以哪方为准,解决完用 add 和 commit 收尾即可。只要别在解决冲突时顺手改一堆无关内容就好,一次合并只处理合并相关的问题,无关功能改动混进来会让 review 的人非常头疼。
3.3 合并之后如何验证代码没有引入问题
分支合并完成不代表万事大吉,最怕的就是改完冲突后代码整体跑不起来了。所以我每次合并之后都会做几件事:先跑一遍编译或构建,再跑一遍测试用例,最后再本地启动一下看主要功能是否正常。这一步虽然不是 Git 命令本身的内容,但它是完整工作流的一部分,不做的话,你推上去一个坏合并,整个团队都会受影响。
如果有基础检查工具(比如 lint、typecheck),合并后顺便跑一下,能拦截掉大量低级错误。代码形式上保证不了业务正确,但至少能保证层面正确。
4. 救急与疑难杂症:那些一不小心就会踩进去的坑
Git 用久了你就会发现,翻来覆去就是那么几个问题。我把这些年实操中踩过和见别人踩过的典型问题集中梳理出来,基本能覆盖绝大多数人的需求。
4.1 提交信息写错了,或者少提交了一个文件,怎么办
有时候提交完才发现信息写错了,或者突然想起还有一个文件没 add 进去。有现成的补救工具:
# 修改最近一次提交的信息 git commit --amend -m "修正后的提交信息" # 把漏掉的文件补进上一次提交,且不改变提交信息 git add 漏掉的文件 git commit --amend --no-edit--amend会将当前提交替换掉上一个提交。注意,如果你已经push到远端,并且这个分支是多人协作的,amend改写历史会造成远端同步问题。遇到这种情况,要么别 amend,新增一个 commit 说明修复;要么确认只有你自己在维护这个分支,amend 后再git push --force。但这属于有点危险的操作,团队里最好先沟通,别冷不丁强推一下把别人的记录冲掉。
4.2 推错分支或者提交太大,想撤销怎么办
撤销是 Git 里面最容易搞混的点,因为不同场景有不同的命令。
如果你想撤销还没有 push 上去的本地 commit,可以这样:
git reset --soft HEAD~1这条命令会把 commit 撤销,但保留改动内容到暂存区,相当于提交白做了,改动还在。极其适合"提交信息写错"或"想把一个大的提交拆成几个小的提交"这类场景。
如果你不仅想撤销提交,连改动内容都想不要,用--hard:
git reset --hard HEAD~1这一步会彻底丢弃改动,执行前一定要确认自己不需要这些文件了,否则找不回来。但如果提交已经 push 到远端,就不能用 reset 了,否则本地和远端不一致,下次 push 会被拒。此时用 revert 更安全:
git revert HEADrevert 的本质是新增一个反向提交,把这个提交带来的修改撤销掉,历史记录保持完整。多人协作时,revert 永远比 reset 稳妥。
4.3 SSH 认证失败是一个绕不过去的坎
ssh 认证失败是 Git 使用中最常见、也是最容易卡住新人的问题。典型的报错就是Permission denied (publickey)。排查思路按顺序来:
第一步,确认密钥存在,执行:
ls ~/.ssh/看有没有id_rsa和id_rsa.pub两个文件。如果连目录都没有,按前面讲过的方式重新生成密钥。
第二步,确认公钥是否成功添加到远程平台账户。很多平台不叫"公钥管理",叫"SSH keys"或者"部署公钥",路径一般在设置里面的安全相关菜单里。复制公钥内容时注意别多复制了空格。
第三步,确认你测试的 ssh 地址是哪家的。举个最简单的例子,如果你用的是 Gitee 平台,测试命令应该是ssh -T git@gitee.com,如果你敲的是git@github.com,它当然不认你的公钥。
还有一种常见情况,你配置了多个 SSH key 或者 config 文件里写了多条规则,导致连接时用了错误的那把钥匙。可以临时用命令指定:
ssh -i ~/.ssh/id_rsa -T git@gitee.com用它来验证某一个特定密钥是否有效,比在配置里翻来覆去改半天快得多。基本上按照这套流程走下来,九成以上 ssh 认证问题都能定位到根因。
4.4 遇到大文件,普通 Git 撑不住,LFS 来兜底
项目中如果有资源包、模型文件、二进制文件等大文件,直接往 Git 里提交会导致仓库体积膨胀,clone 越来越慢,push 也会越来越卡。现在的通用做法是配合 Git LFS(Large File Storage)使用。
基本使用流程是:
git lfs install git lfs track "*.psd" "*.zip" "*.onnx" git add .gitattributes执行之后,指定扩展名的文件会被 LFS 接管,实际的大文件内容存储到 LFS 服务器上,仓库里只保留轻量指针。之后 commit 和 push 的操作和平时完全一样,不需要改变日常习惯。
常见的坑有两个。一个是忘了 install,直接 track 会报错;另一个是 LFS 拉取卡住,尤其文件比较大的时候,看起来像是没响应,其实在后台下载。这种情况可以先确认网络状态,再决定是否重试。另外在 clone 含 LFS 文件的仓库时,如果网络不好可能卡住,可以先普通 clone,再在仓库里执行git lfs pull单独拉取 LFS 内容,这样能把普通文件和 LFS 文件分步处理,体验会好一些。
4.5 一个仓库想同时维护多个工作目录,git worktree 来帮忙
很多人不知道 Git 有 worktree 这个功能。它的作用是一个仓库可以同时检出多个工作目录,每个目录对应不同的分支,适合你同时要改两个分支、又不想频繁切换的场景。
举个实际例子,我在一个项目里要改功能 A,同时突然有个紧急的小需求要修在旧分支上。普通做法是git stash切分支改完再切回来,但这样工作区变动容易出问题。用 worktree 可以这样:
git worktree add ../project-hotfix hotfix/urgent-fix这条命令会在指定路径新创建一个工作目录,里面的代码是hotfix/urgent-fix分支的内容。你在原来目录继续开发功能 A,在新目录改紧急修复,两边互不干扰。等修复完,在新目录正常 commit 和 push,然后删掉这个 worktree:
git worktree remove ../project-hotfix这个功能在有两三个分支并行需求时特别顺手,实测下来比反复 stash、checkout 心情舒畅得多。
4.6 源码暴露在公网,Git 目录泄露这种问题怎么处理
这个场景可能偏安全一些,但也遇到过不少次。如果你把一个项目的.git目录通过某种方式暴露到了 Web 服务器上,比如部署时把整个项目目录直接拷到了站点根目录,攻击者就可以通过访问/.git/路径下载仓库里的源代码,最终造成源码泄露。
判断是否存在这种问题,可以尝试访问类似域名/.git/config这样的 URL。如果能看到内容,那就说明.git目录确实暴露在公网。
修复方式其实不算复杂:第一,部署时的发布目录里不要包含.git文件夹,用构建产物目录作为站点根目录;第二,配置 Web 服务器的访问规则,将.git目录的请求直接拒绝;第三,如果已经泄露,建议检查仓库历史里存放过的敏感信息,比如密码、密钥、内部 API 地址等,该换的换该撤销的撤销。
这个问题有时候被当成 Git 使用教程里的边角料,但实际运维中一旦发生,影响范围可能相当大,值得平时留意。Git 在本质上是一个内容寻址文件系统,.git里存的就是完整的版本历史,任何人拿到它,就等于拥有了你整个项目的所有提交记录,所以部署时一定要把它隔绝在发布目录之外。
5. 把命令和习惯融进日常:我的一些实际体会
Git 这东西,命令量大,但真正高频用到的基础工作流就那十几条。完全没必要死记硬背,把核心逻辑想清楚,用的时候查一下,次数多了自然就记牢了。
我个人习惯在电脑上设置一个全局别名,让常用命令短一些:
git config --global alias.st status git config --global alias.co checkout git config --global alias.br branch git config --global alias.cm commit git config --global alias.lg "log --oneline --graph --all -10"配置完之后,git st看状态、git co切分支、git cm提交,确实能省不少打字量,操作也更顺手。尤其是git lg,配合--graph看分支形状,一眼就能看出提交历史是线性的还是有分叉的。
还有一个细节是.gitignore一定要养成随项目初始化的习惯。很多新项目 clone 下来后,第一件事就是看到一堆系统文件被列在 untracked 列表里,非常影响判断。写一个覆盖常见开发工具的.gitignore模板,一开始就提交进仓库,后面会省掉大量的额外工作量。平时还要注意别把本地配置类文件(比如带密码的.env、本地路径的配置文件)传上去,这种问题一旦 push 出去,就算后面删掉,历史记录里还是会留着,收回成本极高。
另外,提交的频率也值得说一下。写代码按逻辑拆分成小步提交,每完成一个独立的小功能就 commit 一次,而不是攒了三天的改动一次性提交。小提交的好处是,出问题之后能精确到具体的某一步,定位和回滚都更轻松。比如你改了 A、B、C 三个功能,结果 C 出了 bug,如果三个改动在一个提交里,排查起来就需要人工分辨,如果分开提交,直接 revert C 那一条就行。
团队协作还有一个不太起眼但非常重要的点:pull 之前先看一眼自己的改动区域,养成git status的习惯。有时候你本地改了一堆东西,远端也更新了一堆,直接 pull 有概率产生大量冲突。先看清自己改了什么,再决定是 commit 之后再 pull,还是先 stash 暂存再更新,这样处理冲突会从容得多。
最后再多聊一个不算命令的操作习惯:提交之前先用git diff看一遍改动内容。我见过太多人提交时把调试代码、console.log、临时注释一起带上去了。提交前扫一眼 diff 是个非常便宜但收益极高的好习惯,它能把很多低级问题挡在提交门外。
Git 这套基础工作流,本质上就是一套反复循环的机制:拉代码、建分支、改文件、add、commit、push、合并、解决冲突、再拉代码。你把它跑顺了,理解每一次操作背后到底动了什么,后面再碰到什么报错都不会慌,因为所有的问题本质上都是这套机制某个环节出现了预期外的情况。平时多留一份.git的敬畏心,多理解一点底层逻辑,工具就只是工具,不会反过来成为你的负担。