上个月有个同事跑过来问我,说能不能给他整理一份“Git资料”,最好是一份拿来就能看懂的。我说网上教程一堆,为什么不直接搜。他说搜到的不是太零碎就是讲得太绕,看完等于没看。我想了想,确实,Git这玩意儿对老手来说就是日常操作,但对刚开始接触的人来说,第一次配置、第一次提交、第一次因为合并冲突而满头问号,每一步都可能是坑。这份“Git资料”不是某个超级工程,而是把Git从安装、配置到日常命令、团队协作、问题排查串起来的一套笔记,今天把它整理出来,希望对每个正在用Git或准备用Git的人有点帮助。
这套资料能解决什么问题?往小了说,是让你别再因为git pull和git push的顺序搞出冲突;往大了说,是帮你理解版本控制到底在干嘛,为什么团队里十个人同时写一个项目,代码还能不乱。适合谁看?刚入行的开发者、从SVN或直接复制粘贴文件管理版本转过来的同学、以及用了Git但一直靠两三条命令“走天下”的人。如果你已经对Git很熟,可以重点看看第4节和第5节,那部分是关于协作和避坑的经验。
1. 装Git之前,先弄明白版本管理到底在解决什么问题
1.1 没有版本管理的时候,项目有多痛
我一直觉得,理解一个工具的“为什么存在”,比记住命令本身重要得多。你没用过没有版本管理的项目,可能不知道那种痛苦:一个项目文件夹里堆着项目最终版.py、项目最终版2.py、项目真最终版_改.py、打死不改版.py,这还算好的,更怕的是改完代码发现思路走错了,想退回昨天的状态,结果昨天的代码在U盘里,U盘又落在工位上。这就是经典的“文件复制粘贴管理法”,它最大的问题是:文件多了以后,你根本分不清哪份是最新的、哪份是能跑的、哪份中间夹着同事改到一半的半成品。
Git解决的就是这件事。它把你的项目变成一个仓库(repository),每次你有意识地保存一个“版本”,Git就帮你记一次快照。这个快照不是把整个目录复制一遍那么笨,它只记录变化的部分,所以仓库越用越大也不会立刻爆炸。因为你随时可以回到任何一个快照点,所以再改代码的时候,心态完全不一样。以前改代码是“这版改坏就完蛋了”,现在改代码是“改坏了,大不了回到昨天那个能跑的版本”。这个心态变化,对开发效率的影响是很大的。
1.2 不同操作系统选哪个安装方式
Git的安装在Windows、macOS和Linux上不太一样,很多新手在这里就卡住了,觉得是不是装错了。先说Windows,最主流的方式是去Git官网下载安装包,双击一路Next。但这里有个高频问题:有些教程会让你在安装过程中选“Use Git from the Windows Command Prompt”,有些又建议选“Use Git and optional Unix tools from the Command Prompt”,到底选哪个?
我自己实践下来的建议是:如果你平时主要用CMD或者Windows Terminal,选“Use Git from the Windows Command Prompt”就够了,因为这样你在CMD里敲git命令,系统能找到它,而且不会覆盖系统自带的find、sort等命令。选项里那些“Checkout as-is, commit Unix-style line endings”之类的暂时不用管,后面配置里我再说,现在保持默认即可。
macOS用户则分两种,一种是装了Homebrew的,直接brew install git,方便以后升级;另一种是不想装额外工具链的,去官网下载macOS安装包也行,但在“允许从任意来源下载”这一步可能要多点一下右键打开。Linux用户更简单,发行版软件源里基本都有Git,比如Debian/Ubuntu系的apt install git,Fedora系的dnf install git。装完以后,正式使用前先验证一下版本,终端输入git --version,能输出版本号就算基础安装成功了。
1.3 安装这事,最常见的坑不是“装不上”
很多新手以为装Git最麻烦的是网络下载慢、安装包找不到。实际真正恼火的,是装完了之后在终端里敲git提示command not found。这种情况在macOS和Linux上出现概率较高,尤其是用官网安装包装macOS版本的时候,因为默认路径可能不在PATH里。排查方法其实很简单,先确认安装路径,再手动加个环境变量。
比如在macOS上,如果Git装在/usr/local/git/bin,你就往shell配置文件的末尾加一行export PATH="/usr/local/git/bin:$PATH",再执行source ~/.zshrc或者source ~/.bash_profile让它生效。Windows上如果出现command not found,多半是安装时没勾选加入PATH的选项,或者安装完了没重启终端。我建议装完后新开一个终端窗口,而不是在旧窗口里继续敲命令,这个细节能省下十分钟的怀疑人生时间。
提示:安装完成不等于配置完成。第一次用Git的人最容易忽略的,是还没有设置用户名和邮箱就开始提交,然后Git会默认生成一个“本机用户”的配置,提交记录乱成一锅粥,后期想改又得额外学命令。所以安装完的第一步,不是急着
git init,而是先把自己的“身份”告诉Git。
2. 初始化与全局配置,第一次提交前先把这些做好
2.1 三个必须做的基础配置
当你在一个项目目录里执行git init之后,Git只是把目录变成了一个仓库,它并不知道你是谁。所以在开始提交之前,必须先设置用户名和邮箱。这两条命令是我在任何一台新电脑上首先执行的:
git config --global user.name "你的名字" git config --global user.email "你的邮箱"这里有个细节,--global表示全局生效,意思是这台电脑上所有Git仓库都会默认使用这个身份。如果你在公司电脑上有个人项目,不想让公司的提交记录顶着自己的私人邮箱,那就要学会用局部配置。某个仓库单独设置身份的方法,是进入该仓库目录后去掉--global,执行相同命令。我见过不少同事因为一开始用了全局配置,往公司代码库提交了一堆私人邮箱的记录,最后只能找管理员后台改,非常麻烦。
第三个必做配置,是默认编辑器。当Git需要你输入提交信息而你用了git commit却没有带-m的时候,它会弹出一个文本编辑器。默认可能是Vim,很多新人进去之后怎么都退不出来,只能直接关终端。提前设置成自己熟悉的编辑器,会舒服很多:
git config --global core.editor "code --wait"上面这条是把VSCode当默认编辑器,执行后代码提交时会自动打开VSCode新建的待写入信息文件,保存关闭后Git才会继续。如果你用的编辑器不是VSCode,换成相同思路即可。核心是让Git能唤醒一个你用着顺手、且支持“等文件保存后退出”的编辑器。
2.2 换行符和编码这两个看不见的坑
Windows和Linux/macOS的文本换行符不一样,Windows用CRLF回车加换行,Unix内核系统用LF换行。Git默认会对换行符做一些自动转换,于是你经常会在提交时看到那种警告:LF will be replaced by CRLF。这个警告本身不会让你代码坏掉,但它背后是一个经典的多平台协作问题——同一个文件在Windows上提交、在Linux上部署时,可能会因为换行符不同而产生诡异差异。
我现在常用的做法是,在Windows上设置git config --global core.autocrlf true,让Git在提交时把CRLF转成LF存进仓库,检出代码时再转回CRLF。在macOS/Linux上设置core.autocrlf input,提交时转成LF,检出不额外转换。如果团队里全是macOS和Linux,直接提交时统一成LF是最省心的。
编码问题同样容易炸。团队协作时,如果项目里有老旧的GBK编码文件,而Git默认把文本内容当作UTF-8,那么在diff(差异比较)时看到的可能是一堆乱码。我建议新项目统一用UTF-8编码,历史项目如果没办法改编码,至少添加一个.gitattributes文件去指定某些文件类型的编码处理方式。不过说实话,这种存量问题优先靠沟通解决,别指望用一条配置命令就洗白所有历史文件。
2.3 .gitignore到底该写什么,不该写什么
每次初始化仓库后,我建议第一件事不是提交代码,而是先把.gitignore写好。它的作用是把某些文件或目录排除在Git管理之外,比如依赖目录、编译产物、本地环境配置文件。很多人最头疼的是不知道怎么写得合适,我的经验是:先根据项目类型去找一份对应的模板(GitHub的gitignore仓库里很全),然后结合自己的项目再改。
比如Node.js项目里,node_modules是一定要排除的,几千个第三方包没必要、也不应该提交到仓库里;Python项目里,__pycache__、.venv这些也要排除;Java项目里,target目录肯定不能进仓库。关键原则是:凡是能从代码重新生成的东西,尽量不要进仓库。这里有个反例,我见过有项目把node_modules直接塞进仓库,导致克隆一个项目要下载几百兆甚至几个G的东西,既费流量又拖慢速度,而且容易出现平台相关的二进制差异。
不过.gitignore也有个容易坑人的地方:如果某个文件已经被Git跟踪了,再把它写进.gitignore是没用的。你必须在.gitignore生效前先把文件从Git缓存里移除:
git rm -r --cached node_modules然后再把node_modules写进.gitignore,提交这一次变更,后续才不会继续跟踪。很多新人在这块卡住,以为是.gitignore写错了,其实是没有“解绑”历史跟踪。
3. 最常用的Git命令,整理成一条能跑通的工作流
3.1 拉代码、看一眼状态、提交,这个闭环每天重复十次
我不打算把Git的所有命令都列出来,那不现实,也没必要。日常中最实用的,其实是几个基础命令的组合。首先是克隆项目:
git clone git@github.com:用户名/仓库名.git克隆后,在目录下创建或修改文件,然后我要先看一眼当前仓库的状态:
git statusgit status的输出会有三块内容:已暂存(Staged,也就是即将进入下次提交的变更)、已修改但未暂存(Modified not staged)、未跟踪文件(Untracked)。新人常犯的错误是以为git status显示的就是“所有文件”,其实它更像是一块仪表盘,提示你当前有哪些文件被改动,以及这些改动处于哪个阶段。提交的流程是这样的:
git add 文件名 # 把某个文件放进暂存区 git add . # 把所有改动放进暂存区(慎用,容易把不该提交的也带进去) git commit -m "提交说明" git push origin 分支名 # 把本地提交推送到远程这套流程看起来简单,但我必须强调git add .的隐患。它会把当前目录下所有未被忽略的改动全部加入暂存区,如果你同时改了代码和某个本地配置文件,就会把不该提交的配置一起推送出去。所以我更推荐用git add 具体文件或git add src/这种按目录区分的方式。看到这里如果你觉得麻烦,那我告诉你,很多老手在审查提交时最关注的就是“有没有混进不该提交的文件”,从第一步养成好习惯,后面会少很多尴尬事。
3.2 分支操作,别再把主线搞得一团糟
分支是Git里一个核心概念,我用一句话解释:分支相当于从当前代码状态复制出一条平行线,你在自己的平行线上改动,不会影响原来的主线,等改完了再把平行线“合并”回主线。这个设计让多人同时开发不同需求成为可能。
最常用的操作是新建分支并切换过去:
git checkout -b feature/login这条命令等价于两条命令的合并:git branch feature/login新建分支,git checkout feature/login切换分支。现在新版Git也提供了git switch -c feature/login,语义更明确,但大部分教程和老机器上还是以checkout居多,两种都认识比较好。
切分支之前最重要的一句话:先把当前分支的工作区清理干净。如果你手上改到一半的文件没提交,带着脏状态切换分支,Git会自动尝试把这些改动带到目标分支。如果目标分支里同一份文件内容不同,Git就会直接不让你切,要求你先提交或暂存。这时候可以用:
git stashgit stash会把当前未提交的改动先存到一个临时区,让工作区变干净,然后再切分支。等下次切回来时执行git stash pop恢复改动。这个命令是很多人解决“手头工作没做完又必须切个分支改紧急bug”的法宝。
3.3 撤销与回滚,出错了别慌,先看清楚再动手
Git最让人心生好感的地方,是它给了你“后悔药”,但这个药也分版本,用错了可能让你更崩溃。我把最常见的几个场景梳理一下。
场景一:git add加错了文件,但不改文件本身,只想移出暂存区:
git reset HEAD 文件名这条命令把文件从暂存区退回工作区,改动还在,只是不再暂存。
场景二:本地提交信息写错了,或者把几次细小改动打成了一次提交,想修改最近一次提交:
git commit --amend这条命令会把当前暂存区的改动合并进上一次提交,并且重新编辑提交信息。注意,--amend会改变提交的哈希值,所以只适合处理“还没有推送到远程”的提交。如果已经push了再amend,推送时大概率会要求强推,强行覆盖远程历史,对团队协作来说这是危险操作。
场景三:想彻底回退到某个历史提交,但本地有一些已经提交但不想保留的版本:
git reset --hard 目标提交哈希值--hard会丢弃所有改动,包括工作区和暂存区的。这条命令很暴力,用之前一定要确认确实不要这些改动了。更温柔一点的做法是git revert 哈希值,它会反向生成一个新提交,把指定提交的改动抵消掉,适合已经推送到远程的提交回滚。我的习惯是:本地未推送的版本,用reset;远程已经存在的提交,用revert。这条规则能避免绝大多数团队协作翻车事故。
4. 团队协作里的Git细节,很多老手也在这里栽跟头
4.1 合并冲突到底怎么解决
当你执行git pull或者git merge时,如果当前分支和要合并进来的分支改动了同一个文件的同一段代码,Git会告诉你发生了冲突。很多人看到“CONFLICT”就心里一紧,其实冲突不是出了事故,而是Git在请人做决定:两边的改动它不会自动选,需要你来定夺哪个才是要的。
出现冲突后,打开那个被标记的文件,你会看到类似这样的内容:
<<<<<<< HEAD 这是当前分支的代码 ======= 这是要合并过来的代码 >>>>>>> feature/login处理方式就是自己手动把<<<<<<<、=======、>>>>>>>这些标记删除掉,保留正确的代码。然后git add这个文件,再继续提交,合并且冲突解决就算完成了。听上去简单,但有两点经验很重要:第一,处理冲突时建议打开整份文件看一眼上下文,别只看冲突标注的地方,有时候两个分支在相邻区域的改动也会互相影响。第二,不是所有冲突都是文本层的,比如两个分支各自改了package.json里的同一行,冲突很容易看到;但如果一个是基于旧版本的依赖调整,一个是新加了依赖,即使Git没报冲突,合出来的结果也可能是依赖丢失。遇到这种“无冲突但逻辑冲突”的情况,只能靠测试和代码审查兜底。
4.2 提交信息规范,是你的另一个门面
我觉得提交信息这件事,常被新手当作“随便写写”,但真到出问题的时候,才知道它多值钱。比如线上出了bug,一根线上排查线索就是git log的提交历史。如果提交信息写的是“update”“fix”,那基本没有排查价值;如果写的是“修复登录页在iOS 16下点击按钮无响应的问题”,那排查效率直接翻倍。
我现在工作的团队,约定了一套简单的提交规范,格式是类型: 简述。类型一般有feat(新功能)、fix(修bug)、docs(文档)、refactor(重构)、chore(杂项)等。例如:
fix: 修复移动端登录按钮在iOS 16下无法触发点击事件 feat: 新增用户头像上传功能 docs: 更新README中的部署步骤这套规范最直接的好处是,Git log一目了然,哪天想回滚某个发布,可以在提交历史里快速筛出相关提交。配合分支名和PR/MR的标题一起看,基本能还原整个需求的开发脉络。
4.3 push之前先pull,这个顺序能救你的命
很多新手把git push当成“上传”,觉得本地提交完成就完事了,一推却被远端拒绝。原因通常是远程分支上有其他人先推了新提交,而本地分支的基线落后了。此时Git出于安全机制会拒绝你“非快进式”的推送,要求你先git pull把远程变更同步到本地。
正确的习惯是在push之前先pull,但不是无脑git pull。如果你本地有未提交的改动,直接git pull可能会出来一堆组合问题。我自己的标准流程是:
# 1. 查看状态 git status # 2. 如果有本地的未提交改动,先提交或暂存 git commit -m "提交说明" # 3. 拉取远程变更,并尽量用变基的方式合入 git pull --rebase # 4. 推送远程 git push--rebase的意思是,把本地已经在基线之上产生的提交,先“暂时摘下”,等拉取到远程最新代码后,再重新放到最顶层。这样提交历史会是一条直线,比较干净。如果不加--rebase,Git会用merge的方式合并,本地可能会出现一个Merge branch 'xxx' into xxx的合并提交,看历史时会多一些“噪音”。不过rebase也要知道它改写了本地提交的哈希,所以绝不应对已经推送到远程的公共提交执行rebase。
5. Git配置与命令的实用技巧,能省不少事
5.1 用别名给高频命令“改个短名”
Git命令本身不短,敲起来效率不高。Git支持配置别名,也就是给命令重新起一个简短的别名。我电脑上常年使用的别名配置如下:
git config --global alias.st status git config --global alias.co checkout git config --global alias.br branch git config --global alias.ci commit git config --global alias.last 'log -1 HEAD --stat'配置之后,git st等于git status,git co等于git checkout。这些别名不会影响你写完整命令,但会让日常操作省下不少按键。在给团队分享知识时,我也会告诉大家别名配置放在~/.gitconfig里,整体上很容易迁移到新电脑。
5.2 用日志筛选提交,别再翻聊天记录
git log是查看提交历史的命令,但它有很多变体。比如想看最近三条提交的简洁列表:
git log --oneline -3想按作者筛选:
git log --author="你的名字"想搜某个提交信息里含有关键字的改变:
git log --grep="登录"这些命令在排查问题时极其好用。另外,看某一次提交动了哪些文件、每处改动是什么,可以用:
git show 提交哈希值这个命令会同时展示这次提交的元信息、改动文件名和具体增删行,是代码审查时最常用的命令之一。
5.3 暂存与清理工作区有讲究
前面提到过git stash可以暂时藏起未提交的改动,这个工具在“紧急切换任务”场景下非常香。但它的使用也有细节:git stash默认不会把新增的未跟踪文件(Untracked files)藏进来,如果你新创建了一个文件但还没git add,执行git stash之后它依然躺在工作区里,可能导致切换分支后这个问题还在。想要把未跟踪文件一并藏起来,要用:
git stash -u恢复的时候,我习惯用git stash pop而不是git stash apply,区别在于pop会把藏起来的改动弹出并清除掉这次stash记录,而apply会保留记录,适合你想在多个分支上应用同一份改动的情况。当你有一堆stash记录时,用git stash list查看,恢复指定某条记录用git stash pop stash@{2}。
6. 常见问题排查与避坑技巧实录
6.1 推送/拉取时报错,先看提示再问人
很多Git使用中的报错,英文提示本身就写得很清楚了,但新手常常被一长串英文吓到,下意识跳过提示去搜“万能的答案”。其实排查思路很固定。比如最常见的一类报错:
fatal: unable to access 'https://xxx.git/': Failed to connect to github.com port 443这是网络层面的问题,要么是DNS解析不了,要么是远程地址访问不通。如果是国内网络环境,可以在克隆或配置远程地址时使用镜像或加速前缀来处理,这不是魔法,就是把远程地址换成可达的地址而已。
另一类高频报错是:
Permission denied (publickey)意思是远程服务端没有认可你的SSH密钥。解决思路是:先在本地生成密钥ssh-keygen -t ed25519 -C "你的邮箱",然后把~/.ssh/id_ed25519.pub里的内容,配置到代码托管平台后台的SSH Keys列表里。配置完用ssh -T git@github.com测试连通性,能看到欢迎信息就说明生效了。
6.2 detached HEAD是什么,怎么理解
有些新手发现git checkout 某个提交哈希值之后,提交了新代码却感觉“丢”了,而且Git提示你处于detached HEAD状态。这其实是Git在告诉你:你现在没站在任何分支上,而是直接站在某个历史提交点上。在这个状态下提交的内容,没有分支引用它,一旦切走就可能找不回来。
正确的逃跑方式是:如果要基于这个历史提交做更改,先基于它创建一个分支:
git checkout -b feature/from-history 目标提交哈希值这样HEAD就重新指向了新分支,后续提交也不会“丢”。这个错误我在新人身上见得太多了,核心原因是不理解分支和HEAD的关系,建议看到这段文字的读者,自己对git switch和git checkout建立一套清晰记忆:日常切分支用switch,文件维度还原用checkout。
6.3 文件后缀不重要,但文件类型很重要
有些项目会把设计稿、二进制包、大体积样例数据放进Git仓库,导致仓库越来越臃肿、克隆越来越慢。这时候git rm --cached只是删除了“跟踪关系”,历史里依然保留着这些大文件的快照,仓库体积不会变小。想真正瘦身,可以借助git filter-repo这类工具重写历史,但这会改变所有历史提交的哈希,只适合在团队统一协调下操作。
如果项目确实需要在仓库里管理二进制大文件,更推荐使用Git LFS(Large File Storage),把大文件替换成轻量指针,内容存储在独立空间,克隆时按需下载。日常建议是:设计稿、视频、模型文件、密钥证书等,要么走独立的制品库,要么用LFS,别暴力塞进普通Git仓库。
7. 一套适合小团队的Git协作流程参考
7.1 从主干拉分支,别直接在主线上改
一个人的项目随便怎么搞都行,但到了两个以上的人协作时,就需要一点流程约束。小团队最简单的策略是:主干分支(main/master)保持可发布状态,其他人develop临时分支,功能开发从main拉出自己的功能分支,开发完合并回main。功能分支的命名可以用feat/下单流程、fix/修复支付回调这种格式,一看就懂改的是什么。
我遇到过最头疼的情况是团队里有人图省事,直接在main分支上改代码,也没人知道。等要发布时,发现线上版本和别人本地代码差了十万八千里,合并冲突一个接一个。后来我们定了规矩:任何改动,哪怕只改一行,都要走分支、提交、合并的流程。一开始会觉得多了一步,但三个月后你看看稳定的主线和清晰的历史,就知道这一步有多值。
7.2 Code Review和CI怎么配合Git用
代码合入主分支前,最好有一个自动化的检查流程,这就是CI(持续集成)。你推了代码之后,CI自动跑测试,跑过了才能合并。Git本身不负责这件事,但它提供了“保护分支”的配置,在代码托管平台上可以设置main分支不允许直接推送,只能通过合并请求合入,而且必须通过CI检查和指定人数Review。
这些小配置不会增加太多管理成本,但对团队代码质量提升非常明显。一方面它强制了“先审后合”的协作流程,另一方面也减少了“谁都能往主干塞代码”的恐惧。虽然有些人觉得这样很繁琐,但我个人体验下来,这恰恰是小团队逐渐正规化过程中成本最低的一步。
7.3 版本发布与Tag,线上版本要能一眼定位
每次正式发布,我非常建议给对应的提交打上一个标签(Tag)。打标签的命令:
git tag v1.0.0 git push origin v1.0.0以后线上有任何问题,直接在仓库里找对应版本的标签,git checkout v1.0.0就能定位到当时的代码状态,配合git log看那个时间窗口里改了哪些提交,排查效率会高很多。很多人觉得Tag是发布流程后面的事,但在小团队里它就是一条“线”,把线上版本和代码历史挂钩,没有这条线,版本回溯就是空话。
8. 最后再分享一些我的个人习惯
Git的语法不算难,但它的概念模型一开始确实有点反直觉。刚开始用的时候,我建议不要上来就背命令,先把 “工作区、暂存区、本地仓库、远程仓库” 这四个概念搞清楚,后面所有的命令都是在这些区域之间搬运内容。工作区就是你电脑上能看到的文件,暂存区是准备打包的临时区域,本地仓库是提交形成的版本历史,远程仓库是大家共享的中央存储。
我的一个小习惯是,每天开工前先git pull一次,收工前再git push一次,并确保工作区没有未提交的东西。这个习惯让我的分支始终跟得上团队进度,也避免了下班前发现一堆改动没提交的焦虑。另一个习惯是,每隔一段时间用git log --oneline --graph看一下整个分支脉络,能直观感受到团队协作的“轨迹”,也容易发现自己是不是在某个分支上闷头写太久了。
最后想说的是,这份“Git资料”不是拿来背的,是拿来用的。你在项目里遇到问题,翻一翻、照着敲,踩过两次坑基本就记住了。希望这份整理能帮你少走点弯路,把精力花在真正要解决的代码问题上,而不是被版本管理本身绊住脚。