这两年不管是在大厂带新人,还是自己写开源项目,我发现一个扎心的规律:很多人不是不会用 Git,而是只会那几个固定命令,一遇到"分支合错了""提交信息写错""远程连不上""不小心把密钥提交上去了"就彻底卡住,然后开始在各种群里发求助截图。其实 Git 的核心命令就那么几十个,真正决定你效率的,不是背多少命令,而是搞清楚每个命令在什么场景下用、背后改了什么、风险在哪。这篇就结合我实际踩过的坑,把 git 常用命令按场景重新捋一遍,希望能帮你从"会用"升级到"用得明白"。
1. 装好 Git 后先做这几件事:全局配置与默认行为
先聊安装。不同平台的安装方式不太一样,Windows 建议直接去官网下载 Git for Windows,装完自带 Git Bash;macOS 可以brew install git;Linux 按发行版走 apt/yum/dnf 就行。装完第一件事不是急着 clone,而是把全局配置写好,否则每次提交都会报"Please tell me who you are"。
1.1 全局身份配置与默认分支名
身份配置是提交的"署名",它会写进每一次 commit 记录里,后期做代码追溯、版本审计全靠它。命令很简单:
git config --global user.name "你的名字" git config --global user.email "你的邮箱"这里有个细节很多人不知道:邮箱不一定非要和 GitHub/Gitee 注册邮箱一致,但建议保持一致。因为平台上"绿色方块"贡献图的统计逻辑,就是按邮箱匹配提交者的,邮箱不一致你会发现自己在 GitHub 上的贡献图一片空白,哪怕你天天提交。
默认分支名也是一个容易忽略的点。老版本 Git 默认分支是master,新版本已经改成main。如果你不想每次都碰到不同的默认分支名,可以统一一下:
git config --global init.defaultBranch main这个设置影响的是git init时创建的默认分支,对 clone 下来的仓库没有影响,所以改不改看你个人习惯。我个人建议统一成main,少一点历史包袱。
1.2 换行符问题:CRLF 与 LF 的坑
Windows 和 Linux/macOS 对换行符的处理不一样:Windows 用CRLF(回车+换行),Linux/macOS 用LF(换行)。如果你在 Windows 上写代码,又和 Mac 同事协作,最典型的现象是:你只是改了一行,提交时却显示整个文件都变了,因为每一行的行尾符都被自动替换了。
解决办法在安装 Git for Windows 时就有一个选项:Checkout Windows-style, commit Unix-style line endings,对应配置就是:
git config --global core.autocrlf true这个配置的意思是:从仓库检出代码到工作区时,把 LF 自动转成 CRLF;提交时把 CRLF 转回 LF 存进仓库。这样仓库里永远存的是 LF,Windows 本地用 CRLF,跨平台不会互相污染。
如果你已经在协作过程中了,更稳妥的做法是直接在仓库里放一个.gitattributes文件,强制指定文件类型和换行符规则,比如:
* text=auto *.sh text eol=lf *.bat text eol=crlf.gitattributes的优先级高于个人全局配置,团队所有人进来就会被统一约束。这个文件值得每个仓库都放一份,能省掉大量无意义的 diff 噪音。
1.3 编辑器与 Git Bash 的操作习惯
默认编辑器建议改成你自己顺手的,不然git commit没带-m参数时,会直接把你丢进 Vim。我见过太多新人在 Vim 里不知道怎么保存退出,直接关终端导致提交失败。改用 VS Code 的话:
git config --global core.editor "code --wait"还有个实用配置是免交互提交时直接跳过编辑器:
git config --global core.editor true这个技巧让git commit在没有-m时不打开编辑器,适合写临时提交的场景。还有人在 Git Bash 里不习惯复制粘贴,Windows 的 Git Bash 默认选中即复制、按鼠标中键或 Shift+Insert 粘贴,刚开始会有点别扭,用习惯了效率反而高。
安装配置阶段最容易出问题的还有一个git open /dev/null or dup failed: no such file or directory,这个报错一般出现在 Git Bash 的环境异常上,常见原因是系统临时目录被清理工具误删或者环境变量缺失,重启 Git Bash 或重新安装 Git 基本能解决。
2. 日常提交链路:add、commit、.gitignore 与误提大文件
日常开发最核心的三个命令就是git add、git commit、git push。看似简单,实际用的时候到处是细节。
2.1 git add 的三种范围与真实用法
基础用法不用多说,关键是别只会git add .。全量添加虽然省事,但会把临时文件、编译产物全部拉进来,也导致提交粒度不清晰,后面想回滚某个功能时非常痛苦。
我习惯的做法是:
git add src/xxx/yyy.js tests/xxx/yyy.test.js按文件精确添加,一个提交只解决一个问题。想偷懒时可以用git add -A把所有改动加入索引,或者git add -u只添加已跟踪文件的修改和删除,不添加新增未跟踪文件。还有个交互式命令git add -p,可以逐个 hunk 确认,适合把一个大文件改动拆成几个逻辑提交。这个命令初用会有点繁琐,但配合git commit打出来的提交历史会非常干净。
2.2 commit 的正确写法与 amend 使用
提交信息是写给人看的,不是写给机器看的。规范的格式一般是第一行摘要、空一行、正文说明。
git commit -m "feat: 增加用户注册接口" -m "包含手机号校验和短信验证码发送逻辑"多个-m在 Git 里会自动连成多段提交信息,比写一个带换行符的长字符串方便很多。
提交之后发现漏了一个文件,或者提交信息写错了,别慌,用git commit --amend。它会把暂存区的内容追加到上一次提交里,同时允许修改提交信息。这个命令的适用场景是:提交还在本地、没有推送,或者推送到了自己的临时分支、还没有人基于它开发。我在实际项目里经常用它来"补漏",比如加一个debug.log忘了传,或者在提交信息里把单号写错了。
但要注意,amend本质是"替换"上一次提交,会生成一个新的 commit 对象。如果上一次提交已经推送到公共分支且被其他人拉走了,此时amend再强推会造成别人本地历史错乱,这是协作大忌。
2.3 .gitignore 看似不起作用的原因
"git 的过滤文件没有作用"是我被问过最多的问题之一。绝大多数情况不是.gitignore写错了,而是那个文件已经被 Git 跟踪了。
.gitignore只对未跟踪的文件生效。一旦某个文件被git add提交过,之后就永远处于已跟踪状态,你再怎么往.gitignore里加规则都没用。解决方案是把它从 Git 索引里移除,但保留本地文件:
git rm --cached filename git commit -m "停止跟踪 filename"之后再把规则写进.gitignore,后续改动就不会被纳入了。我见过不少人因此在生产环境提交了配置文件,然后天天手忙脚乱地改回。切记:先加.gitignore再git add,顺序不能反。
2.4 提交大文件被 Git 拒绝怎么办
当你执行git push遇到 "remote: error: File is larger than 100.00 MB" 或 "GH001: Large files detected" 时,说明仓库里混入了不该提交的大文件。GitHub 单文件限制是 100MB,Gitee 会更高一点但同样有限制。
如果大文件还没提交成功,直接在本地删除并确保没进索引就行。如果已经提交进了历史记录,那只是从工作区删除文件还不够,因为历史提交里还留着它。处理方案有两种:
- 简单做法:用
git filter-repo或git filter-branch把大文件从历史中彻底抹掉,然后强推所有分支。 - 项目确实需要存放二进制大文件时,用 Git LFS,Git 只存文件指针,真实内容放到 LFS 服务器。
git filter-repo 安装和使用示例:
pip install git-filter-repo git filter-repo --path path/to/large.zip --invert-paths注意,改写历史后所有人的本地仓库都需要重新 clone,这是个伤筋动骨的操作,最好在项目组内确认后再做。
日常提交链路里还有个小建议:每个 commit 保持单一职责,不要攒几十个文件再一股脑提交。这样查 bug 时git log和git blame会给你非常精准的定位,而不是在一个庞大提交里大海捞针。
3. 分支切换与合并:fetch、pull、merge、rebase 别混着用
说到 Git 就绕不开分支问题。"分支合并""pick 和 fetch 有什么区别""idea 中 git 如何合并分支"这类问题背后,其实是很多人没搞懂 Git 的本地仓库、远程跟踪分支和远端分支这三层概念。
3.1 fetch 与 pull 的本质区别
git fetch是把远程仓库的最新状态下载到本地,但它只更新远程跟踪分支(比如origin/main),不会动你的工作区,也不会自动合并。也就是说,fetch 之后你会看到"远程有更新",但本地代码完全没变。
git pull等价于git fetch加git merge,一步到位把远程改动合到当前分支。听起来 pull 更方便,但实际协作中我喜欢先用 fetch 看一眼再决定,因为当本地和远程都有各自的新提交时,直接 pull 很可能触发一次你毫无心理准备的合并或冲突。
如果你就想让本地分支严格对齐远程,用:
git fetch origin git reset --hard origin/main注意这个操作会丢弃本地所有未提交的改动和新增提交,务必谨慎。适合的场景是"本地已经改乱了,我不想留了,以远程为准"。
3.2 merge 的三种主要场景
git merge最常规的用法是把一个分支合并到当前分支。比如当前在main,要把feature/login合进来:
git checkout main git pull git merge feature/login当两个分支从同一个提交点分叉后再各自产生了新提交,merge 会自动创建一个新的合并提交。如果只有一条分支领先、另一条没动,Git 会走 fast-forward 模式,直接把当前分支指针快速移动到目标分支,不生成合并提交。
不想让 Git 走 fast-forward,可以加--no-ff强制创建合并提交:
git merge --no-ff feature/login这个习惯在团队项目里很流行,因为合并提交能非常清晰地记录"某个功能是在哪个节点合进来的",回购历史一拉就懂。反过来说,如果分支很多、合并频繁,--no-ff也会让历史显得冗长,单人或小项目按需选择即可。
3.3 "master 上的代码怎么移到 dev" 的三种操作法
这个问题其实是问"如何把当前分支的改动/提交复制到另一个分支",要分情况回答。
情况一:改动还没提交。用git stash暂存当前工作区,切到目标分支后git stash pop:
git stash git checkout dev git stash pop情况二:已经提交了,想把某个特定提交复制过去。用git cherry-pick:
git cherry-pick <commit_hash>这个命令会把指定提交的改动应用到当前分支上,相当于"移植"。我用它最多的地方是把热修复从临时分支搬到主分支,而不需要把整个分支合并过去。
情况三:想把整个master分支上的所有新提交合并到dev。那直接切换后 merge 就行:
git checkout dev git merge master看清楚需求属于哪种,别动不动就重建分支拷贝文件,既低效又容易漏东西。
3.4 worktree 与 branch 的分工差异
许多人把git worktree单纯理解成"另一个文件夹里的项目副本",其实它是设计用来解决一个痛点的:同一个仓库,在多个分支上并行开发时,不停地 stash 和 checkout 切换很痛苦,尤其是任务来回穿插的时候。
git branch只是逻辑指针,一个工作目录同一时刻只能检出一个分支。git worktree则允许同一个仓库同时有多个工作目录,每个目录对应不同分支。用法:
git worktree add ../proj-dev dev git worktree add ../proj-hotfix hotfix上面的命令会在proj-dev目录里单独检出一个dev分支的工作区,两个目录互不干扰,但共享同一个 Git 仓库对象库。适合的场景是"线上 bug 要立刻修,手上的新功能又不想动"。
日常不需要多个工作区,就老老实实用一个 checkout 加 stash;需要频繁切换上下文或者想隔离构建目录,worktree 是比"再 clone 一份仓库"更省磁盘、更方便的选择。
3.5 合并冲突怎么看、怎么解决
真的遇到冲突时,千万别慌。Git 指的是把冲突标记清晰地放在文件里,标记<<<<<<<和>>>>>>>之间的是两个人各自的代码。我用 vscode 对比冲突有点繁琐,实际操作反而依赖下面的命令:
git status它会明确告诉你哪个文件 "both modified"。打开文件后搜索<<<<<<<,一个个处理。解决完删掉冲突标记,然后:
git add filename git commit至于 merge 和 rebase 的选择,如果你开发的功能分支已经合到主分支了,我一般只用 merge,简单可回溯。rebase 更适合在个人分支上整理历史,让提交看起来变成一条直线,但它有个前提:不要 rebase 已推送且被其他人拉取的分支,否则会重新生成 commit hash,别人再 pull 时会出现一堆重复提交。
4. 历史改写:commit --amend、reset、revert、rebase -i 的安全边界
Git 最强大的能力之一就是修改历史,但也是最容易出事故的能力。我见过有人因为一次 reset 把一天的代码清空,也见过有人 revert 之后一脸懵,提交没了但代码却还在。这里把几个高频历史操作讲透。
4.1 commit --amend 的边界
前面已经说过,amend就是把上一次提交连同暂存区的新改动打包成一个新提交。如果你只修改提交信息不改内容,也走这个命令:
git commit --amend -m "新的提交信息"记住一条铁律:不要对已经推送到远程且被多人拉取过的提交执行amend。如果确实改了,远程合并请求里的旧 commit 记录还在,推进会导致两边历史不一致,协商成本极高。
4.2 reset 三种模式到底会丢什么
git reset是把当前分支的 HEAD 指针往回移动的神奇命令,它有三个模式,我每次讲这个都会配一张对比表,这里直接用文字描述:
--soft:指针回退,但索引和工作区不变。也就是提交记录回退了,但所有改动已暂存,随时可以重新提交。这是用完amend后发现心里有点虚时的补救方式。--mixed(默认):指针回退,索引也回退,但工作区文件保留。改动会回到"已修改未暂存"状态,是回退提交后继续改代码的常用方式。--hard:指针、索引、工作区全部回退。工作区里那些在这个提交之后产生的改动也会被丢弃,相当于时光倒流。执行前必须确认。
git reset --hard HEAD~1 # 回退一次提交并丢弃改动 git reset --soft HEAD~1 # 回退一次提交但保留改动在暂存区危险之处在于,reset 之后你原来的提交并没有被立刻清除,而是进入 reflog 里,30 天内还可以找回来。所以哪天手误执行了--hard,先别砸键盘,用git reflog找到原来的 commit hash,再 reset 回去即可。
4.3 revert 与 reset:一个建新碑,一个毁旧碑
git revert和git reset表面看都是"撤销",机制完全不同。revert会生成一个新的提交,这个提交的作用是"反着应用"目标提交的改动。比如 commit A 加了 10 行代码,revert A 就是删掉这 10 行,并留一条完整的撤销记录。
git revert <commit_hash>重点来了:revert 不会删除历史,它只是"追加了一段补丁"。所以 revert 适用于已经推到远程、或者其他人已经因为你那个提交做了进一步开发的场景。它保留了所有历史轨迹,适合正式项目里的回溯操作。
reset 则是直接把 HEAD 指回老的提交,中间那段历史就"消失"了(实际还在 reflog 里)。reset 更适合本地整理:比如连续提交了三笔,你发现全是一团乱麻,想重新来过。
什么时候选哪个?一句话:只要提交已经 push 到共享分支,优先 revert;只要提交还在本地没上过天,随便 reset。
4.4 rebase -i:整理提交历史的瑞士军刀
git rebase -i交互式变基是我个人最推荐的"历史整理工具",没有之一。它的能力列表很长,但日常用到的主要是这几个操作:
pick:保留该提交,可以改变顺序。reword:修改提交信息。edit:停下来,允许拆分提交或修改内容。squash:把当前提交和上一个提交合并成一个,常用于把一堆"wip"、"fix typo"之类的琐碎提交压缩成一个完整功能提交。
一个典型的操作:把最近 3 个提交压缩为一个。
git rebase -i HEAD~3编辑器里会列出最近 3 条提交,把后面两条的pick改成squash,保存退出后按提示写一个新的提交信息。整个过程都在本地,对远端没有影响,直到你主动 push。
rebase 的危险是它会改写提交 hash,和 amend 同理,已经推到共享分支的绝对不要 rebase。我的经验是,rebase 只用于个人分支或合并前清理自己的提交历史,合并进公共分支后一个字都不要动。
5. 远程协作:clone 失败、SSH 认证失败与免密登录
远程仓库相关的命令和问题,占了日常求助的很大一部分。尤其ssh 认证失败、clone failed to connect to 127.0.0.1 port 7890: connection refused这类报错,我帮别人排查的次数多到数不清。
5.1 clone 失败最常用的三个排查方向
connection refused类型的报错,本质是 Git 尝试连接远程服务器时,TCP 层就被拒绝了。常见原因有三类:
- 本地网络访问不了 GitHub/Gitee 的服务器,属于网络层面的问题,跟 Git 本身没关系。
- 本地有代理类软件或系统代理设置异常,导致 Git 把请求指向了某个本地端口,而那个端口的代理服务没有正常启动。这种情况下建议检查系统代理设置环境变量,或者执行
git config --global --unset http.proxy、git config --global --unset https.proxy把 Git 的代理配置清掉。 - 目标地址主机名解析异常,或者公司内网限制直接访问外网。可以先
ping github.com看通不通,也可以ssh -T git@github.com测试 SSH 通道是否正常。
排查顺序我一般这样走:先看报错带不带port,带端口基本就与代理有关;再看能不能ping通域名;最后curl -I https://github.com看 HTTP 通不通。一步一步缩小范围,别一开始就重装 Git。
5.2 SSH 认证失败:从生成密钥到配置全流程
SSH 认证失败最常见的特征是Permission denied (publickey)。很多人以为把本地生成的公钥贴到 GitHub/Gitee 就完事了,实际还有几个环节:
生成密钥:
ssh-keygen -t ed25519 -C "你的邮箱或备注"生成的文件默认在~/.ssh/id_ed25519.pub,内容是公钥,id_ed25519是私钥,私钥绝不能外传。
然后把公钥内容复制到代码平台的设置页里。GitHub 是 Settings -> SSH and GPG keys,Gitee 是安全设置 -> SSH 公钥。但到这里还没完,你还要确保本地 SSH 客户端认这个私钥。测试命令:
ssh -T git@github.com ssh -T git@gitee.com如果提示Hi 用户名! You've successfully authenticated,说明认证已通过。
如果显示Permission denied,优先检查:
ls -l ~/.ssh/id_ed25519.pub ~/.ssh/id_ed25519私钥文件权限太宽松,ssh 会拒绝使用。Windows 下要注意文件权限,Linux/macOS 下改:
chmod 600 ~/.ssh/id_ed25519另一个坑是用了非默认路径的私钥,比如我把密钥放在~/.ssh/company/id_ed25519,就得配置~/.ssh/config:
Host github.com HostName github.com User git IdentityFile ~/.ssh/company/id_ed25519之后ssh -T git@github.com就能识别了。
5.3 免密与账密缓存问题
SSH 方式配置好以后本身就是免密的,不需要输密码。如果你还是用 HTTPS 方式 clone,那么用户名密码或 token 会被 Git 提示输入,可能还会要求反复输入,这时可以开启凭证缓存:
git config --global credential.helper store这样首次输入后,凭证会明文保存在~/.git-credentials里,之后不再提示。macOS 可以用osxkeychain,Windows 上 Git for Windows 自带manager,用系统安全地存储凭据,比明文要安全些。
如果发现缓存里存了错误的账号,需要清除:
git config --global --unset credential.helper或者在 Windows 的"凭据管理器"里删除对应条目,Git 就会重新让你输入账号密码。这个操作在多人共用一台机器、或者切换公司账号和个人账号时特别重要。
5.4 git remote:添加、查看与切换
git remote管理的是远程仓库别名。一个本地仓库可以同时关联多个远程,比如公司内网一份、GitHub 公开一份。常用命令:
git remote -v git remote add origin git@github.com:user/repo.git git remote set-url origin git@gitee.com:user/repo.git git remote remove origin修改关联地址时不要删掉再重新 add,直接set-url更稳妥。还有git remote show origin可以查看远程分支与本地分支的跟踪关系,排查"为什么 push 不到远程"时很好用。
IDE 里(比如 IntelliJ IDEA 或全家桶)自带的 Git 插件本质就是这些命令的图形化封装。遇到"IDEA 创建新项目拉取 Git"的需求,我建议还是先在命令行把 clone 和 remote 搞定,再回到 IDE 里打开项目,这样出问题时你至少知道自己每一步做了什么。
6. 敏感信息与仓库安全:从 .git 目录泄露说起
"git 目录泄露如何下载"这个热词让我有点担心。网上确实存在利用网站部署时遗漏了.git目录,通过访问/.git/config或/.git/HEAD来下载源码的攻击手法。这里我不打算演示怎么攻击,但作为开发人员,你更需要知道的是:为什么你的.git目录会暴露、暴露后会造成什么后果,以及如何从源头上避免提交敏感文件。
6.1 部署时为什么会把 .git 留在服务器上
很多静态站点、CMS 或小型项目部署时,图省事直接把整个项目文件夹打包上传,或者用 rsync 同步整个目录到服务器,.git目录就会跟着上去。.git目录本身包含了所有历史提交、对象文件、配置信息,一旦被人通过 HTTP 直连访问到,等于把整个仓库的完整历史暴露了,包括你曾经提交过又删除的数据库密码、密钥、内网地址。
正确的部署姿势是只同步构建产物,比如dist、build目录,或者在服务器端把.git目录加入 Web 服务器的拒绝访问列表。Nginx 可以加一条:
location ~ /\.git { deny all; }Apache 则可以在.htaccess里写:
RedirectMatch 404 /\.git6.2 历史中的敏感信息清理:filter-repo 与重新生成密钥
比部署更常见的场景是:你稀里糊涂把.env文件或密钥写进过一次提交,后来虽然删了文件,但提交记录里依然永久保留着这个内容。这种信息不是靠"删掉文件再提交一次"能抹掉的,因为它躺在历史对象里。
清理思路分三步:
第一步,把当前状态里的敏感文件彻底移除跟踪并加进.gitignore。
git rm --cached .env echo ".env" >> .gitignore git commit -m "移除环境变量文件"第二步,用 git filter-repo 把所有历史提交中的该文件剔除:
git filter-repo --path .env --invert-paths这个命令会遍历所有提交,把.env的存在彻底从历史中删除。执行后提交 hash 全部变化,所有协作者需要重新 clone。
第三步,最硬核的一条:密码、密钥、Token 这类信息,即使从历史中删除了,也可能已经被抓取并缓存了。安全界的主流建议是"当它泄露,永远不要相信它",直接作废旧密钥、重新生成新的,否则清理历史只是心理安慰。
6.3 用 .gitignore 和扫描工具守住底线
把防线前置比事后擦屁股高效太多。我给每个新仓库都初始化一份基础.gitignore,至少包含这几类:
.env .env.* *.pem *.key *.p12 node_modules/ dist/ build/ target/ .idea/ .vscode/ *.log还有一个小习惯:在提交前用git diff --cached --name-only扫一眼,看看暂存区到底有哪些文件,避免git add .顺手把不该传的传上去。更严格一点可以在 CI 里加一个密钥扫描工具,在推送前自动拦截。GitHub 本身也有 secret scanning,会把扫描到的密钥信息通知平台方,并建议你主动失效。
真正被泄露后的第一反应也不应该是"我删掉重新提交一下",而是立刻判断泄露信息的严重程度:如果你是把自己的云厂商密钥传上去了,第一时间去控制台禁用或轮换密钥,这比任何 Git 操作都紧急。
我自己的习惯是:每次新建仓库,先把.gitignore、.gitattributes、全局配置全部就位,再动手写代码;每次提交前用git diff自己审查一遍;每次 push 前想一下"这个分支有没有被别人在用,这个提交是不是真的要推到公共分支"。这些看起来慢,其实是在给未来的自己省事。Git 命令本身不复杂,复杂的是你在什么时机用哪个命令,而这一点只能靠大量实际操作来积累肌肉记忆。平时多练练 stash、rebase -i、reset 和 revert 的排列组合,等真正线上出问题时,你才能不慌。