干这行久了你会发现,Git这东西,很少有人系统学过一遍。git常用命令说起来就是几个单词,但大多数人的状态是:能用就行,clone、add、commit、push这四板斧走天下,一旦遇到分支合并、撤销提交、过滤文件失效这类问题,就得现查现搜,查完还不敢确定会不会把代码弄丢。我从写第一行代码到现在,Git仓库操作过不下上千次,踩过不少坑,也帮同事救回过不少被reset掉的提交,所以这篇文章不打算做官方文档的翻译,而是把我日常真正高频使用的命令按场景重新整理一遍,讲清楚每条命令为什么要这么用。无论你是刚接触Git的初学者,还是用了好几年命令行但没系统梳理过的老手,挑需要的部分看就行。
1. 先把环境搞定:安装、身份配置与SSH免密
日常用Git,第一步不是敲命令,而是把环境收拾利索。我见过太多人在Windows上装了Git之后直接用默认配置,结果提交代码时发现提交人信息全是乱码,或者每次push都要输密码输到怀疑人生。这些问题,源头都在最开始的十分钟配置上。
1.1 三大平台的安装方式
先说安装。Windows用户推荐去Git官网下载安装包,一路Next安装完就自带Git Bash终端了。Mac用户最简单的方式是brew install git,或者装上Xcode Command Line Tools之后系统也会有自带Git版本,不过自带的版本通常偏老,建议还是用brew装新版。Linux用户根据发行版不同,Ubuntu/Debian用sudo apt install git,CentOS/RHEL系列用sudo yum install git。
版本这个事我一直很在意。有人觉得Git版本无所谓,能跑就行,但实际不是这样。老版本对新SSH算法支持不好,Submodule的很多修复也只在较新版本里,Git LFS偶尔的诡异问题升级版本之后就直接消失了。所以我的习惯是装好之后先跑一条git --version确认版本号,Windows和macOS上的话尽量保持在2.30以上。
1.2 第一次clone前必须做的两件事
装好Git第一件事,是配置你的身份信息。Git的每次提交都会记录作者和邮箱,这个信息不是随便填的:
git config --global user.name "你的名字" git config --global user.email "你的邮箱"--global表示全局生效,作用于这台机器上你的所有仓库。如果你不清楚当前配了什么,用git config --list查看全部配置。这里有个实际教训:如果忘了配置user.email就提交代码,Git会在提交记录里嵌一个默认生成的邮箱,后期再想改就非常麻烦。所以入职新公司、换了新电脑,第一步永远是配这个,不是急着clone代码。
除了身份,还有一个很多人没注意的配置项是提交时的默认编辑器。Git在需要你填写多行提交说明、或者执行git merge碰上冲突时会自动打开一个文本编辑器,默认可能是Vi/Vim。老手无所谓,新手一进去就是两眼一抹黑不知道怎么退出。建议先改成自己熟悉的编辑器:
git config --global core.editor "code --wait" # VSCode git config --global core.editor "nano"Git的配置分成三个作用域:system(系统级)、global(用户级)、local(仓库级)。优先级是local大于global大于system。同一个配置项在不同层级出现时,越具体的越先生效。如果你想临时在某一个仓库用不同的邮箱提交,不要改全局配置,直接在这个仓库里执行git config user.email "xxx"即可,只影响当前仓库。
1.3 配置SSH密钥,告别每次输密码
代码托管平台支持HTTPS和SSH两种协议。HTTPS简单,但每次push都要输账号密码,即便有credential helper缓存,也总有意外情况。SSH配置好之后一劳永逸,强烈推荐。
生成密钥的命令:
ssh-keygen -t ed25519 -C "你的邮箱"一路回车就能在~/.ssh/id_ed25519.pub生成公钥文件。如果平台不支持ed25519算法,改用ssh-keygen -t rsa -b 4096 -C "你的邮箱"也行。然后把.pub文件里的内容复制到GitHub/Gitee/GitLab的SSH Keys设置页面里,保存之后本机验证:
ssh -T git@github.com看到类似Hi xxx! You've successfully authenticated的输出,说明配置成功。之后clone仓库时记得选SSH地址,也就是形如git@github.com:user/repo.git那种,而不是https://github.com/user/repo.git。
这里有个多账号场景:如果你的机器同时要访问公司和个人的GitHub账号,可以在~/.ssh/config文件里配置多个Host,每个Host对应一个HostName、User和IdentityFile,然后在不同目录的仓库里把remote地址写成对应Host名。这套配置我用了很久,稳定可靠,但属于进阶玩法,新手暂时用不上。
1.4 换行符与git bash的小坑
Windows用户还有个必经的坑,就是换行符。Windows的文本文件默认用CRLF(回车加换行)结尾,而Linux/macOS用LF(仅换行)。Git为了兼容,有个core.autocrlf配置项:
- Windows上建议设置
git config --global core.autocrlf true - macOS/Linux上建议设置
git config --global core.autocrlf input
这个配置的意思是:Windows上检出代码时自动把LF转成CRLF,提交时自动把CRLF转回LF;macOS/Linux上提交时把CRLF转成LF,但不主动转检出。这样能最大限度避免因为换行符差异导致整个文件被标记为改动。这个坑我放到疑难杂症章节再展开,先埋个伏笔。
另外提一句Git Bash:Windows下装了Git之后自带的Bash终端,日常跑Linux命令会很顺手。但有个细节,在Git Bash里执行命令时,路径分隔符和Windows资源管理器看到的路径略有差异,比如C:\Users\xxx在Git Bash里要写成/c/Users/xxx。刚上手的人容易在这上面绕弯,知道有这回事就好。
2. 每天都要走的提交链路:add、commit、push、log
环境配置完之后,就是最核心的日常操作链路了。我统计过自己一天敲得最多的命令,无非就是那几个:clone、status、diff、add、commit、push、log。但每一个命令,其实都有一些容易被忽略的细节。
2.1 clone之后先看remote
拿到一个项目,第一件事通常是git clone <地址>。如果仓库很大,可以加--depth 1只拉最近一次提交,速度会快很多,这叫浅克隆。不过浅克隆之后要注意,这个仓库没有完整历史,git log里看不到旧提交,一些依赖完整历史的操作也会受限。
克隆完成后,我习惯先看一眼远程配置:
git remote -v这条命令显示当前仓库配置的所有远程仓库地址。正常情况下你会看到origin对应你clone时的地址。如果你拿到的是别人发给你的压缩包而不是clone的,需要手动把远程仓库地址加进去:
git remote add origin git@github.com:user/repo.git这个场景在接手同事项目或者公司内部分发代码时很常见,我看到很多新人拿到压缩包后不知道怎么配remote,push的时候直接报错No remote configured。知道这个就够了。
2.2 status与diff:提交前最后的检查
改完代码之后,先不急着git add,而是用git status查看当前工作区状态。这条命令会告诉你哪些文件被修改了、哪些是新文件未跟踪、哪些已经进了暂存区。状态清楚了再决定下一步。
然后推荐一条我几乎每日必用的命令:
git diff它显示的是工作区里还没有暂存的改动,逐行对比。提交前扫一遍diff,能避免很多低级错误,比如不小心把调试用的console.log、临时代码、密钥文件混进提交里。想查看已经暂存内容的差异,用git diff --cached。
有个针对新手的提醒:git diff默认只看已跟踪文件的改动,不显示未跟踪的新文件。新文件要先git add才会出现在暂存区,也才会被git diff --cached看到。这条逻辑搞明白了,再看Git的"三个区"概念就不晕了:工作区、暂存区、本地仓库区,Git的所有命令都是在这三个区域之间搬运内容。
2.3 commit与commit message
git add <文件>把文件加入暂存区,git commit -m "提交说明"把它固化成本地提交。这里是两个我踩出来的经验。
第一,git add .要慎用。它会一股脑把所有改动都加进暂存区,包括你可能不想提交的临时文件。我见过有人不小心把密钥文件、几百MB的日志文件提交进仓库,然后整个团队都跟着遭殃。建议还是有明确意图地git add <具体文件>,或者先git status看清楚再决定。
git add .要慎用——它会一股脑把所有改动都加进暂存区,包括你可能不想提交的临时文件。我见过有人不小心把密钥文件提交进仓库,整个团队都跟着遭殃。
第二,commit message不是随便写的。写update、fix这种含混信息,三个月后你自己都看不懂。约定俗成的做法是类型: 简要描述,比如fix: 修复用户登录时token过期问题、feat: 新增导出功能。团队有规范就按团队规范来,没有的话起码做到"能让人不看代码也大概知道这次改了什么"。
如果需要提交的文件比较多,可以用git commit -am "说明",它的作用是跳过暂存区,直接把所有已跟踪文件的改动一起提交。注意:新文件不在其中,必须显式add。
2.4 push与log:推送到远程与查看历史
git push origin <分支名>把本地提交推送到远程仓库。如果远程设置了上游追踪,直接git push即可。第一次推送新分支时,命令会提示加-u:
git push -u origin feature/xxx-u是把本地分支和远程分支绑定,之后git push和git pull就可以直接输,不用每次带远程名和分支名。
查看提交历史,我常用的组合:
git log --oneline --graph --decorate --all这个命令用一行显示一个提交,用图形展示分支结构,非常直观。--oneline精简输出,--graph画分支线,--decorate标注标签和分支指向,--all显示所有分支历史。看团队协作的提交脉络,这一条就够了。
如果只是快速看最近几条,git log --oneline -5更轻量。想查某个文件的历史变动,用git log -- <文件名>单独过滤。
3. 分支实战:创建、切换、合并与清理
分支是Git最核心、也最有价值的能力。我见过太多人在分支管理上全凭记忆和勇气,直接在master上改,合并一出问题就整个人都不好了。分支操作弄明白,日常开发效率能提升一个档次。
3.1 创建与切换分支
创建分支通常两种写法:
git branch feature/login # 创建分支但不切换 git checkout -b feature/login # 创建分支并切换过去新版Git还推荐用git switch替代checkout相关功能:git switch -c feature/login创建并切换,git switch feature/login切换到已有分支。switch语义更单纯,只负责分支切换,不容易像checkout那样让人迷惑。毕竟checkout还要兼顾"还原文件"的职能,这是历史包袱。
我的习惯是每次开发新功能一定新建分支,绝不在master上直接改。有人觉得项目小、就自己一个人,无所谓。但真实项目中,主分支往往承担发布职责,任何人直接往主分支上堆未验证代码,都会让"能不能发布"变成一个黑盒。哪怕只有两三个人协作,也建议遵守:功能分支开发,合并后删除分支,保持主分支干净。
3.2 分支合并:merge与rebase怎么选
把一个分支的改动合并到另一个分支,最基础的操作是git merge <分支名>,比如当前在develop分支上,想把feature分支合并进来:
git checkout develop git merge feature/loginGit默认会走Fast-forward合并,如果有共同祖先且可以线性推进,Git会直接移动指针;如果两边都有分叉提交,也会自动生成一个合并提交。如果你想强制保留分支合并的痕迹,加--no-ff参数;不希望生成合并提交的,加--ff-only。
还有一条路是rebase:
git checkout feature/login git rebase developrebase和merge的区别在于提交历史形态。merge保留真实的合并结构,历史看起来像带分岔的河流;rebase会把当前分支的提交重放到目标分支顶端,历史变成干净的直线。我个人的观点是:提交历史是给后人看的,如果不是必须,尽量让历史清晰。团队协作时用merge保留合并点,单个功能分支在合并前用rebase整理自己的提交,这两种组合是我觉得比较健康的平衡。
千万别在主分支上rebase,也千万不要rebase别人已经push到远端的提交。rebase会重写提交哈希,如果别人的提交已经被拉取过,强制推送rebase结果会导致大家的分叉历史非常难收拾。
3.3 冲突解决的完整链路
合并时出现冲突(conflict)是最常见的"Git把自己绕晕"的时刻。实际原因是两个分支修改了同一段内容,Git不知道怎么选。文件里会标着<<<<<<<、=======、>>>>>>>,把冲突区域圈出来。
我的处理流程是这样:
- 先跑
git status看哪些文件冲突; - 逐个打开冲突文件,Git会把冲突区域标记出来,手动确认保留哪边内容,或者两边都保留;
- 修改完成后,
git add <冲突文件>,告诉Git这个冲突你处理好了; - 继续
git commit,Git会自动生成一个合并提交。
这里有个新手容易犯的错:解决冲突时只想着"把爆红删掉",删了标记符号但内容选得不对。冲突解决的核心是你要理解这个文件的两个版本各自想表达什么,而不是躲避警告。如果真的不确定,最好和改动这两个分支的人沟通一下,我帮同事解决过的绝大多数冲突,都是因为两边各自改动同一处而没有对齐预期。
3.4 分支删除与清理
功能开发完、合并完了,本地分支可以删:
git branch -d feature/login如果分支还没有合并过,普通删除会报错,这时要么确认真的不要了强制删git branch -D feature/login,要么老老实实先把该合的内容合掉。-D是--delete --force,目的是防止手滑丢掉未合并的工作。远程分支的删除命令是:
git push origin --delete feature/login如果本地有一堆已经合并过、早就该删的分支,可以用git branch --merged查看哪些能删,再配合批量操作清理。保持分支列表干净,长期来看非常值。
4. 撤销操作:给你一颗后悔药
写代码不可能永远一次到位,撤销和改错是日常。但Git的撤销命令特别容易让新手吓得不敢动手,因为reset、revert、checkout、amend这几个词长得太像了。我拆开来讲。
4.1 commit --amend:修改最近一次提交
如果你刚git commit完,马上发现漏了一个文件、或者message写错了,这时候不用新建一个提交,直接:
git commit --amend -m "新的提交说明"它的逻辑是:用一个新的提交替换最近一次提交。如果只是修改message,git commit --amend后重新填说明即可;如果还要补充文件,先git add再git commit --amend。
但这里有个重要提醒:--amend会改写提交哈希,所以如果这个提交已经push到远端且别人可能已经基于它工作了,就不要amend了,否则push时会冲突,还得force push,给别人造成困扰。理想的使用场景是:提交还在本地、还没push,随便改。
4.2 reset与revert:别再傻傻分不清
git reset是把当前分支的HEAD指针往回移。配合不同参数,影响范围不同:
git reset --soft HEAD~1 # 回到上次提交,但改动留在暂存区 git reset --mixed HEAD~1 # 默认参数,改动留到工作区 git reset --hard HEAD~1 # 改动直接丢弃,危险操作要三思--soft常用于"提交信息写错了,想重新来一次提交";--mixed常用于"不想分成那么多次提交,想合并成一次";--hard是把代码彻底恢复到之前的状态,本地所有改动都不要了。HEAD~1表示前一个提交,~2表示前两个,也可以直接写提交哈希。
git revert则是完全不同的思路:它不是把历史抹掉,而是生成一个新的提交,把某个旧提交的改动反向应用回去。比如:
git revert abc123会创建一个"撤销了abc123这个提交"的新提交。这样历史是完整的,适合已经push到远端的提交、或者多人协作的分支上做撤销。我个人的铁律是:还没push的提交用reset,已push的提交用revert。
4.3 手滑reset之后,reflog怎么捞
git reset --hard误操作之后,代码丢了吗?绝大多数情况没有。Git有个日志叫reflog,记录了所有HEAD移动的痕迹:
git refloggit reset --hard并不是真的删除提交对象,只是让HEAD不再指向它。只要这个提交还没被Git的垃圾回收机制清理(通常至少在90天内),你就能从reflog里找到它当时的位置,然后git reset --hard <哈希>或者git cherry-pick <哈希>把它救回来。我有一次搞砸了整天的改动,经理在旁边看着,我心都凉了,结果跑了一下git reflog发现之前的提交都还在,一条一条捞回来,那是Git第一次让我觉得"这工具靠谱"。这个习惯我一直保持着:越慌越要看reflog,乱输入只会更糟。
4.4 撤销场景速查表
下面这个表是我贴在自己工作文档里的,遇到不确定直接查:
| 场景 | 推荐命令 | 注意事项 |
|---|---|---|
| 提交说明写错了(未push) | git commit --amend -m "新说明" | 仅限未推送的提交 |
| 漏提交了一个文件(未push) | git add 文件 && git commit --amend | 会改写哈希 |
| 想撤掉已push的提交 | git revert <哈希> | 保留历史,推荐 |
| 想彻底放弃本地改动 | git checkout -- <文件>或git restore <文件> | 丢弃工作区改动 |
| 想回退本地多次提交(未push) | git reset --hard HEAD~n | 确认无可挽回再操作 |
| 误reset后找回 | git reflog | 先看再动 |
5. 那些让你抓狂的Git疑难杂症
最后这部分,我专门聊几个高频出现的诡异问题。这些问题不是命令不会用,而是对Git的运行机制理解有偏差,或者说环境坑太深。
5.1 .gitignore不生效:不是它没生效,是文件已经被追踪
有个问题我收到过好多次求助:明明在.gitignore里加了某个文件,它还是出现在git status里。原因很简单——.gitignore只管尚未被Git跟踪的文件。如果这个文件之前已经被git add或者提交过,它就在Git的追踪名单里,.gitignore对已追踪文件是不生效的。
解决办法是先从追踪名单里移除,再补上.gitignore规则:
git rm --cached <文件>--cached表示只把文件从暂存区/追踪名单里移除,不动本地实际文件。如果是一整个目录,用git rm -r --cached <目录>。执行完之后提交一次,后续该文件才会被ignore规则正确忽略。
还有一个隐藏很深的情况:.gitignore规则本身写得不匹配。比如你写了*.log,但实际文件是logs/debug.txt,那就根本不在匹配范围。Git的忽略规则支持/开头表示目录级别,**表示任意中间层目录,具体写法值得花十分钟看一下官方文档。在生产环境部署时也要注意别把.git目录暴露到Web根目录,这个目录里存着完整历史,泄露出去等于把源码奉上。
5.2 Git LFS:安装、track与clone卡住的排查
仓库里有大文件(比如设计稿、二进制模型、数据集),直接用Git托管会撑爆仓库,这时候需要Git LFS(Large File Storage)。这套机制把大文件替换成指针,真正的文件内容存到远端LFS服务上,本地工作区在需要时才拉取。
安装使用的基本流程:
git lfs install git lfs track "*.psd" git add .gitattributes # track规则会写进这个文件 git add 大文件 git commit -m "add big file" git push我实际遇到过的LFS问题主要有两个。一是clone大仓库卡住,大概率是因为仓库里LFS文件数量多、体积大,建议用git lfs pull或先设置GIT_LFS_SKIP_SMUDGE=1跳过自动下载,只拉指针文件,用到时再按需git lfs pull --include取特定文件。二是某些历史版本LFS和Git版本不兼容,升级Git版本后莫名解决。如果你在配置里看到git config --global --unset lfs.fetchexclude这类命令,它通常用于清除以前设置过的LFS拉取排除规则,比如某个目录被配置为不自动拉取,后来想恢复就unset掉。这类命令按实际情况调整即可,不必背。
5.3 CRLF与换行符:团队协作的隐性大坑
前面安装部分埋了换行符的伏笔,这里展开。Windows上开发的同事提交代码时,Git把CRLF转成了LF,没问题。但有人没有配core.autocrlf,把CRLF原样提交了,然后Linux同事拉下来,整个文件的每一行都会被标记为"改动",实际上内容一模一样,只是换行符不同。
处理这类问题的正规武器是.gitattributes文件,在仓库根目录写:
* text=auto *.bat text eol=crlf *.sh text eol=lf这比每个人改自己的全局配置要靠谱,因为规则是跟着仓库走的,全团队生效。我在维护的仓库里都会放一个.gitattributes,避免换行符问题反复出现。已经混入历史的错误换行符,可以用git config --global core.autocrlf true配合重新检出处理,但如果仓库历史已经乱掉,往往需要一次性重新归一化,操作前一定要备份。
5.4 SSH认证失败与提交账户错误
ssh: connect to host github.com port 22: Connection timed out或者Permission denied (publickey)这类报错,多数是密钥没配好或SSH端口不通。几个排查步骤,按顺序:
ssh -T git@github.com看具体报错;- 确认
~/.ssh/id_ed25519.pub内容是否已经贴到平台; - 检查
~/.ssh/config里有没有多余配置干扰; - 端口不通的,可以试试改用443端口。
提交账户错误的问题我也遇到过,本来该用公司邮箱提交,结果用的是个人GitHub的全局配置,历史记录里身份信息全串掉。解决办法是对特定仓库单独设置local配置:
git config user.name "公司名" git config user.email "公司邮箱"如果历史提交已经暴露,可以配合git filter-branch或git rebase -i改写,但这是个危险操作,强烈建议先备份仓库再说。
6. 一点个人使用习惯的分享
写到这里,分享几个我长期坚持的小习惯,算不上教程,就是实在好用。
6.1 我从上班到下班的工作流
每天早上到工位第一件事,git status看看昨天有没有留尾巴,然后git fetch --prune把远端分支变化同步下来。接着git log --oneline --graph -15快速扫一眼项目动向,看看同事有没有合入什么重要改动。一天开发结束前,git diff检查自己今天所有改动,确认没有测试代码、密钥等漏网之鱼后,再add和commit,最后git push。
这样做的好处是,每天早上和晚上各看一次全局,提交历史不会出现"我昨天改啥了?"的失忆状态。尤其是git fetch --prune这条命令,它只更新远程跟踪引用,不影响工作区代码,比git pull安全得多,适合随时查看远端最新状态。
6.2 stash:临时切换场景的保命技能
开发到一半被叫去处理另一个紧急bug,或者切换分支的时候发现工作区有未提交的改动导致切换报错,这时候git stash把当前改动暂存起来,工作区就干净了。处理完紧急事项后git stash pop恢复。
git stash默认只暂存已跟踪文件的改动,如果要连新文件一并暂存,得用git stash -u。查看stash列表用git stash list。遇到多个stash堆叠,恢复指定条目用git stash apply stash@{1}然后手动清理git stash drop stash@{1}。注意apply和pop的区别:pop是取出并从列表删除,apply是取出但保留列表项。
6.3 配置alias,让高频命令再短一点
每天敲几十遍的命令,值得用alias缩短:
git config --global alias.st status git config --global alias.co checkout git config --global alias.br branch git config --global alias.lg "log --oneline --graph --decorate --all"配完之后,git st、git lg就直接可用了。我自己的alias清单里还有一个git ci对应commit、git pl对应pull。需要注意的是,alias只是方便,别配一些自己都记不住的花哨缩写,团队协作时尽量输出原始命令给别人看,减少沟通成本。
另外几个长期习惯:主分支从来不直接rebase和强推;紧急情况先stash再行动,绝不慌着乱敲命令;每接手一个新仓库,先看分支图和README再动手。Git命令看着多,但真正高频的来回就那一二十条。把这二十条的逻辑彻底搞明白,剩下的都是查表题。用好Git的关键不在背命令,而在理解它那套"工作区—暂存区—本地仓库—远程仓库"的流动模型,理解了模型,命令只是顺手的工具。