Git这个东西,刚接触的时候觉得反人类——add、commit、push、pull,每一步都像在跟一个脾气古怪的老头打交道。但真正用熟了,你会发现它几乎是现代软件开发里最值得先啃下来的基础设施。这篇笔记不是官方文档的翻译,而是我这些年从零折腾到团队协作、从本地提交到远程仓库、从天天被命令折磨到能徒手解决疑难杂症的实战记录。内容覆盖Git安装配置、常用命令、分支管理、提交规范、免密登录、远程仓库协作,以及几个高频报错的排错过程。适合刚上手Git的初学者、被各种诡异报错卡住的开发者,以及想系统梳理一遍Git体系的同学。
1. 环境准备:安装、终端选择与第一份配置
1.1 安装方式和版本选择
Windows环境我推荐直接去Git官网下载安装包,选64-bit版本就行。如果觉得官网下载慢,可以用国内镜像,或者通过包管理器安装——Windows上可以用winget install Git.Git,macOS上brew install git,Linux发行版用apt、yum、pacman都行。
有一点值得注意:别装太老的版本。Git的旧版本在Windows上有不少历史遗留问题,比如路径处理、换行符转换、SSH支持等都可能引入莫名其妙的bug。我见过同事用Git 1.9版本连GitHub都连不上,折腾半天换了新版立刻解决。现在主流的2.3x、2.4x版本都比较稳定,建议保持更新。
安装过程中的选项,大多数保持默认即可。唯一建议改的,是默认编辑器——如果你不喜欢Vim,在安装时把默认编辑器改成VS Code或Notepad++,否则以后commit时打开编辑器会死在那里,新手很容易卡住。
1.2 终端选择:Git Bash 与“小乌龟”的定位
Windows上装完Git后,右键菜单会多出几个选项:Git Bash Here、Git GUI Here,还有可能看到TortoiseGit(小乌龟)的菜单。我用过的终端不算少,但Git Bash始终是我在Windows上最推荐的学习环境。
为什么是Git Bash而不是CMD或PowerShell?因为Git Bash模拟了Linux的shell环境,很多在Linux服务器上能用的命令(ls、grep、sed、awk、ssh)在Git Bash里都能直接跑,这样你在本地学的命令和服务器上的操作习惯是统一的,不用切换思维。
至于TortoiseGit,也就是大家常说的“小乌龟”,它是个GUI工具,做得确实很成熟,右键就能提交、推送、拉取。但我的建议是:新手先别急着用GUI掩盖命令操作,至少先把add、commit、branch这些核心命令练熟,再去用GUI提升日常效率。 命令操作能让你理解Git到底在干什么,GUI只是一个壳。等理解了原理,你用TortoiseGit、VS Code的图形界面、甚至任何Git客户端都会很顺畅。
1.3 第一份配置:user.name 和 user.email
安装完Git,第一件事不是急着clone代码,而是配置身份信息。这个配置写在每次提交里,相当于你的签名。
bash复制git config --global user.name "你的名字"
git config --global user.email "你的邮箱@example.com"
这里有两个很容易踩的坑:
--global是全局配置,只对当前用户生效。如果你在公司电脑上既工作又写个人项目,建议个人项目用--local方式配置单独的user.name和user.email,否则提交记录里的身份会串。- 邮箱不一定非得是你的真实邮箱,但最好能通过邮件联系到你。GitHub支持匿名邮箱功能,如果不介意别人通过提交记录搜到你的邮箱,可以配置成
你的username@users.noreply.github.com这种匿名的。
查看当前配置用 git config --list,修改仓库级配置时去掉--global、在目标仓库目录下执行即可。
1.4 安装后的自检与“无法识别git命令”
安装完先验证一下:
bash复制git --version
如果你是在Windows上装了Git但PowerShell里敲git提示“无法将“git”项识别为 cmdlet、函数、脚本文件或可运行程序的名称”,十有八九是PATH环境变量没生效或没配置。
这种情况的处理思路是:
- 看看Git到底装在哪个目录,典型路径是
C:\Program Files\Git\bin和C:\Program Files\Git\cmd。 - 把
C:\Program Files\Git\cmd加到系统PATH(在系统属性 -> 环境变量里编辑Path)。 - 改完PATH必须重启终端,重启后新的PATH才能被加载。
安装时如果勾选了“Add to PATH”选项,一般不会有这个问题,但如果你用的是绿色版、或者手动改过安装位置,就很容易遇到。这类问题本质上不是Git坏了,而是系统找不到git这个可执行文件,属于环境变量问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心命令的工作流拆解:从工作区到版本库
2.1 四个状态,一张图看懂
Git的文件生命周期可以简化成几个状态:未跟踪(untracked)、已修改(modified)、已暂存(staged)、已提交(committed)。
我教新人的时候常用一个比喻:你写论文,改完一版存在U盘里(modified),把要定稿的版本放到一个新文件夹(staged),最后提交给导师归档(committed)。Git的暂存区就是那个新文件夹,它让你可以在一次提交前精挑细选到底哪些文件放进这个版本里。
git status 是你最常用的命令,它告诉你当前仓库里什么文件处于什么状态。看到红色是已修改未暂存,绿色是已暂存待提交。
2.2 add 和 commit 的正确用法
初期最基础的操作就是:
bash复制git add <文件名> # 添加某个文件
git add . # 添加所有修改(但要注意别把所有乱七八糟的东西都加进去)
git commit -m "提交说明"
我可以很负责任地说:每天因为乱用git add .把不该提交的文件提交上去的人,排起来能绕地球一圈。正确做法是提交前先git status看一眼,确认了要提交什么再add。要是仓库里恰好有密码、证书、大文件,一条git add .就能让你后悔到想穿越时空。
日常中我用得最多的是:
bash复制git add -p # 交互式暂存,可以逐个hunk选择要不要暂存
git commit -m "feat: 新增用户登录接口"
git add -p 是个被低估的好功能。当你在一个文件里既有bug修复、又加了新功能、还改了格式时,你可以用-p把不同的改动分别暂存,然后分别提交,保持历史清晰。
2.3 回退、撤销与误操作自救
写代码没有后悔药,但Git有。下面是几个我踩过无数次坑之后总结出的“救命命令”:
- 改到一半想取消修改:
git restore <文件名>,本地工作区的修改就被丢弃,回到最后一次提交的状态。 - add错了,想取消暂存:
git restore --staged <文件名>,把文件从暂存区退回工作区,改动还在,不用担心。 - commit完了想改提交信息:
git commit --amend -m "新的提交信息",修改最近一次的提交说明。 - commit完又想补几个文件进去:先
git add,再git commit --amend,此时不换提交信息也可以直接合并到上一条。 - 误删分支、误复位、想找回丢失的提交:
git reflog,它是Git的时光机。git reflog会列出你HEAD指针每次移动的记录,找到那条丢失提交的哈希值,git checkout <哈希>或git branch <新分支名> <哈希>就能找回。
关于git reset,它有三个经典参数:
| 参数 | 作用 | 工作区状态 | 暂存区状态 |
|---|---|---|---|
--soft |
只移动HEAD指针 | 保留 | 保留 |
--mixed(默认) |
移动HEAD并回退暂存区 | 保留 | 清空 |
--hard |
全部回退 | 清空 | 清空 |
git reset --hard 是双刃剑,用了之后本地修改和提交都没了,除非你记得哈希值并立刻用reflog救回来。我的习惯是:搞不清情况的时候绝不轻易--hard,先备份或先git stash。 git stash可以把当前没提交的改动临时存起来,等处理完其他事再git stash pop恢复。
3. 分支管理与合并策略:从单打独斗到团队协作
3.1 分支的本质,是移动的指针
分支这个概念,很多新手容易把它想得太玄乎。其实分支在Git里就是一个指向某个提交对象的“指针”,你新建分支、切换分支、提交新代码,本质上都在移动这些指针。
bash复制git branch # 查看所有本地分支,当前分支前有*
git branch <分支名> # 新建分支
git checkout <分支名> # 切换分支
git switch <分支名> # Git 2.23+ 推荐用switch切换
git switch -c <分支名> # 新建并切换
git branch -d <分支名> # 删除分支
我刚开始用Git时总喜欢在master上一个分支走到黑,后来带了团队才理解:分支就是你的开发隔离舱。你在feature分支上怎么折腾都不会破坏主分支的稳定性,等代码成熟了再合并回去。这是多人协作的基本底线。
3.2 合并:merge 与 rebase 的选择
合并分支有两条路线,各有利弊。
git merge <分支名> 会把另一个分支的提交记录合并到当前分支,生成一个“合并提交”(merge commit)。优点是保留了真实的开发历史,缺点是历史会像一团乱麻,分支一多就很难看。
git rebase <分支名> 不是合并,是“把你的提交重新放到另一个分支的顶端”。它能让提交历史变成一条直线,非常干净。但rebase有个大坑:会改写提交哈希,如果那个分支是公共分支、别人也在用,rebase会搞得所有人repair。
我在实际项目中遵循的原则是:
- 自己的功能分支,开发中同步主分支的更新用rebase,能保持历史整洁。
- 功能分支合并回主分支用merge --no-ff,保留一个合并节点,方便回溯“这个功能是从哪进来的”。
- 不要对公共分支、别人正在使用的分支做rebase,这是团队级的红线。
3.3 冲突处理现场:别慌,冲突是日常
合并冲突几乎人人都会遇到,它不是灾难,只是Git告诉你“这两处改动我搞不定,需要你人工选择”。
冲突的典型标志是文件内容里出现这种内容:
code复制<<<<<<< HEAD
当前分支的代码
=======
合并进来的代码
>>>>>>> feature-xxx
我的处理步骤是:
- 打开冲突文件,搜索
<<<<<<<找到冲突位置。 - 逐个判断:保留哪个、全部保留、还是改成一版新的。
- 删除
<<<<<<<、=======、>>>>>>>这三行标记。 git add这个文件。- 所有冲突文件都add完成后,
git commit完成合并提交。
如果冲突太多、头都是大的,也可以果断git merge --abort或git rebase --abort退出,恢复到合并前状态,重新想清楚再操作。
要减少冲突,我的几个习惯:每天开工先pull一次主分支;功能分支生命周期不要拖太长,小步快跑;同一批人尽量别同时动同一个文件;公共文件的改动先沟通再动手。
4. 远程仓库协作:clone、push、pull 与免密登录
4.1 关联远程仓库
本地项目传到GitHub、GitLab或Gitee上,需要先建立本地与远程的关系:
bash复制git remote add origin <远程仓库地址>
git remote -v # 查看远程仓库
如果项目是从远程clone下来的,origin这个远程名字已经自动配置好了,直接用git push即可。自己新建的本地仓库则需要先关联。
第一次推送时,主分支的名字可能是master(老版本默认)或main(GitHub默认)。推送命令写法:
bash复制git push -u origin main
-u参数将本地分支和远程分支建立上游关系,之后的git push、git pull就不用再带分支名了。
4.2 pull、fetch 与 merge 的关系
很多人把git pull当成“从远程更新代码”,这个理解没错,但不完整。git pull其实是两个动作的合体:先git fetch把远程的提交拉取到本地仓库,再做一次git merge把远程分支合并进当前分支。
理解了这层关系,你就知道为什么有时pull会产生一个“合并提交”了——本质上跟分支合并一样。如果你希望历史保持线性,可以用git pull --rebase,它会把你本地的提交rebase到远程分支之上,相当于先把远程的改动拉下来,再把你的改动放到最上面。
我个人的偏好是:共享分支上pull用 git pull --rebase,避免产生一堆无谓的merge commit。 但同样,如果你和别人同时在同一个分支上开发,请慎用rebase,这是团队约定问题,没有绝对对错。
4.3 免密登录配置:SSH Key 一次搞定
每次push都要输账号密码非常浪费生命。我主推SSH key方式,一次性配置,之后所有操作都不需要密码。
生成密钥:
bash复制ssh-keygen -t ed25519 -C "你的邮箱@example.com"
一路回车,会在~/.ssh/下生成id_ed25519(私钥)和id_ed25519.pub(公钥)。然后用:
bash复制cat ~/.ssh/id_ed25519.pub
把公钥内容复制,添加到GitHub的Settings -> SSH and GPG keys里,或者GitLab的Profile -> SSH Keys里。
最后测试:
bash复制ssh -T git@github.com # GitHub
ssh -T git@gitlab.com # GitLab
看到“Hi xxx! You've successfully authenticated”就说明连接成功。以后clone、push都走SSH协议地址(git@github.com:用户名/仓库.git),不再需要密码。
如果不用SSH,走HTTPS协议时Git会弹出让你输账号密码的窗口,也可以通过凭据管理器记住密码。但遇到公司自建的GitLab版本较老、要求用personal access token代替密码时,很多人会卡在那个login failed. check api token or gitlab version的报错上,这个后面章节我会专门讲。
5. 提交信息规范:每次 commit 都是一篇小作文
5.1 为什么要折腾提交规范
以前我commit信息随便写,什么“fix bug”“update”“改了点东西”都发过。等一个月后回来看历史,完全不知道当时干了什么。等到同事review代码或者线上出问题要回溯时,一份乱七八糟的commit历史就是灾难。
提交信息规范的核心目的只有一个:让历史可以被阅读。 谁在什么时候、出于什么原因、做了哪个改动,如果commit信息写得好,整套流程都能一目了然。
5.2 Conventional Commits 的基本格式
目前通行的规范是Conventional Commits(约定式提交),格式如下:
code复制<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
常见的type有这些:
| type | 含义 | 典型场景 |
|---|---|---|
| feat | 新功能 | 新增接口、新增页面 |
| fix | 修复bug | 修复崩溃、修数据错误 |
| docs | 文档变更 | README、注释 |
| style | 代码格式 | 缩进、分号、空格 |
| refactor | 重构 | 不改变外部行为的代码结构调整 |
| perf | 性能优化 | 减少耗时、降低内存 |
| test | 测试相关 | 新增或修改测试用例 |
| chore | 构建、工具 | 依赖升级、CI变更 |
| revert | 回滚 | 撤销之前的提交 |
示例:
code复制feat: 新增用户注册接口
- 增加 /api/register 接口
- 添加邮箱格式校验
- 补充注册成功/失败日志
如果是破坏性变更,在footer里加BREAKING CHANGE:说明。
5.3 让规范落地:commitlint 和 Commitizen
一个人自觉遵守规范很难,所以需要用工具约束。常见组合是:
- Commitizen:交互式引导你填写提交信息的CLI工具,输入
git cz后一步步选type、写scope、填description。 - commitlint:在提交时校验commit信息格式是否符合规范,不符合直接拒绝。
- husky:Husky用来挂载Git hooks,在commit-msg阶段调用commitlint。
团队项目里配上这一套,能让所有人的提交历史都像出自一人之手。如果你是个人项目,至少养成“type: 简述”的习惯,短期内看不出差别,半年后回头查历史你会感谢现在的自己。
6. 关于 .git 目录泄露的安全自查与防护
6.1 .git 目录里到底有什么
每个Git仓库根目录下都有一个隐藏的.git文件夹,里面存放着这个仓库的全部元数据:config(仓库配置)、objects(所有历史版本的提交对象)、refs(分支和标签引用)、logs(操作记录)、HEAD(当前分支指针)等。
换句话说,拿到.git目录,基本等于拿到了整个仓库的所有提交历史、所有代码版本、甚至可能包含被删除但还留在对象库里的敏感信息。 有些人可能觉得删掉文件就能在历史里抹除,但只要对象还在,就有被恢复的风险。
6.2 泄露的常见场景
.git目录泄露最常见的场景,是部署阶段操作不当:
- 用
git clone或直接复制项目文件夹到web服务器目录,.git完整地跟着上线了。 - 把开发机器上的项目整个压缩成zip再传到服务器解压,连同
.git一起解压出来。 - 静态托管平台配置不对,把整个项目根目录设置为站点根目录,没有排除隐藏文件。
- nginx/Apache配置允许访问隐藏目录,且没有做访问控制。
这种问题的可怕之处在于:网站本身跑得好好的,表面看不出任何异常,但任何知道路径的人都可以直接访问站点地址/.git/config 来探测。如果服务器还开放了目录遍历,那整个.git目录都可能被逐个下载,仓库就裸奔了。
6.3 如何自查、修复和预防
作为项目负责人或服务器管理者,建议把下面几条当作部署清单里的固定项:
自查方法:
直接在浏览器里访问 你的站点地址/.git/HEAD,如果返回的是 ref: refs/heads/master 或 ref: refs/heads/main 这类内容,基本可以断定.git目录暴露了。
修复方式(按优先级):
- 立即禁用web服务对
.git目录的访问。nginx里加一条:
nginx复制location ~ /\.git {
deny all;
return 403;
}
Apache则在对应虚拟主机配置里加:
apache复制<DirectoryMatch "^/.*/\.git/">
Require all denied
</DirectoryMatch>
- 手动删除服务器上的
.git目录,并把部署流程调整成“只上传项目文件,不传版本库”。 - 如果仓库里有历史遗留的敏感信息(比如提交过的云服务器密钥、数据库密码),光删
.git还不够,因为泄露的提交历史可能已经被别人下载。需要重置敏感凭据,并用git filter-repo彻底清理历史后重推。
预防措施:
- 部署时用
.gitignore或发布流程排除.git、.env、node_modules等目录。 - 构建打包时走CI/CD产物目录,而不是把源码目录整个传上去。
- 在web服务器上层(CDN、云安全组)做基于路径的访问控制。
这里我特别想强调的是:不要把代码仓库直接当作网站根目录的上级目录来部署。正确做法是完整的代码先构建成最终产物(静态文件或编译好的程序),再把产物放到web目录,源码和版本库留在部署服务器之外。这是一条基础但极其有效的安全边界。
7. 高频疑难杂症的排查链路
7.1 “无法将“git”项识别为 cmdlet”
前面提过这是PATH问题,但在实际排错中还可以再往下挖一层:
- 先确认git是否真的安装成功:去安装目录找
git.exe是否存在。 - 在终端里执行
where git(PowerShell下是Get-Command git),看系统能不能找到。 - 找到
git.exe所在路径后,确认该路径在系统PATH中,且顺序合理。 - 修改PATH后彻底关闭终端重新打开。如果你是在VS Code集成终端里测试的,VS Code也要重启。
- 如果还是不行,检查是否安装了多个Git版本、或者有杀毒软件拦截了git.exe。
这个问题本质上不是Git的知识,而是Windows环境变量管理的基本功,搞懂一次,以后装任何软件都能举一反三。
7.2 “login failed. check api token or gitlab version”
这个报错常见于IDEA、VS Code等IDE里添加GitLab仓库时。它说的是:GitLab API鉴权失败了,可能是API token不对,也可能是GitLab服务器版本太老与当前工具不兼容。
排查链路:
- 确认你用的是personal access token,不是登录密码。新版GitLab普遍取消了密码直接访问API。
- 检查token权限是否足够:至少要勾选
read_repository、write_repository。 - 确认token的状态:
Status是否为active,是否已过期。 - 确认GitLab版本:老版本GitLab对一些新API端点支持不完整,IDE插件、Git扩展可能连不上,可以看看GitLab后台的版本号,和工具文档要求的版本对一下。
- 如果公司GitLab版本确实很老,可以在IDE里改用SSH方式连接,绕开API token问题。
7.3 git clone 慢、卡住、失败
git clone失败的原因千奇百怪,但常见的有这几种:
- 网络问题导致连不上远程服务器:检查能不能ping通、能否正常访问官网页面。
- HTTPS证书问题:公司内网自签证书可能导致SSL报错,可以设置
git config --global http.sslVerify false临时绕过(不推荐长期开启)。 - 仓库太大:历史提交很多、包含大文件,clone时全部拉取会非常慢。可以尝试浅克隆:
git clone --depth 1 <仓库地址>,只拉取最近一次提交。 - 本地代理配置不生效:如果你在用代理工具,要确认
git config --global http.proxy和https.proxy是否与代理端口一致。
7.4 中文文件名乱码与 core.quotepath=false
用git status时,如果你看到中文文件名变成了一串\346\265\213\350\257\225这样的转义字符,原因就是Git出于兼容性考虑,默认把非ASCII字符做了转义显示。
解决办法很简单:
bash复制git config --global core.quotepath false
设置后,Git就会直接显示原始中文文件名,不再转义。这个配置对UTF-8编码的文件名非常友好,强烈建议全局开启。
这里顺便提一下另一个让不少人困惑的参数:git -c diff.mnemonicprefix=false -c core.quotepath=false --no-optional-locks。这是TortoiseGit(小乌龟)在执行某些操作时使用的底层命令。-c表示临时覆盖配置项;core.quotepath=false就是上面说的中文文件名显示;--no-optional-locks意思是禁止Git在过程中使用“可选锁”,避免GUI工具在后台运行时跟其他Git操作产生锁竞争。我解释这个东西,是想让大家明白:GUI工具表面上一键操作,底层还是一堆Git命令,你看懂了这些参数,遇到问题就不会抓瞎。
7.5 VS Code 和 Cursor 中如何使用 Git
VS Code内置了Git支持,左侧栏的源代码管理图标就能看到所有改动。提交、推送、拉取、冲突解决都有图形界面,对新手非常友好。需要注意的几个点:
- VS Code使用的Git是系统PATH里的git,如果终端里能跑git命令,VS Code通常也能正常识别。
- 如果VS Code提示找不到Git,在设置里手动指定
git.path为git.exe的完整路径。 - Cursor本质上是个基于VS Code的编辑器,它的Git集成方式与VS Code一致,走的也是系统Git配置,没有单独的“绑定git”这一步。只要系统Git配置好了、SSH key配好了,Cursor里就能直接pull、push。
- 两个IDE里最容易犯的错是:提交时忘记写commit message,或者没选中要提交的文件,导致提交按钮置灰,以为编辑器坏了。实际上只是操作顺序问题。
8. 从零到一提交代码的完整流程:一个能直接抄的模板
8.1 新项目本地初始化并推送到远程
假设你在GitHub上新建了一个空仓库,本地目录已经写好了代码,想把整个项目推上去,完整流程如下:
bash复制# 1. 进入项目目录
cd my-project
# 2. 初始化本地仓库
git init
# 3. 创建 .gitignore 并配置忽略规则(node_modules/、.env、target/ 等)
# 可以使用 GitHub 的 .gitignore 模板
# 4. 添加所有文件到暂存区
git add .
# 5. 确认状态
git status
# 6. 提交
git commit -m "feat: 初始化项目"
# 7. 添加远程仓库
git remote add origin git@github.com:你的用户名/my-project.git
# 8. 推送并设置上游
git push -u origin main
如果远程仓库不是空的(比如已经勾选了创建README),本地首次push可能会被拒绝,这时需要先git pull --rebase origin main把远程的README拉下来合并,再push。
8.2 日常迭代的约定动作
项目进入稳定期后,我的日常操作基本固定为:
bash复制git switch main # 切到主分支
git pull --rebase # 同步远程更新
git switch -c feature/xxx # 新建功能分支
# ...写代码...
git status # 查看改动
git add -p # 选择性暂存
git commit -m "feat: xxx"
git push -u origin feature/xxx
# 发起 PR/MR,code review 后合并
团队协作时,分支命名最好有统一格式。我们团队用的模式是type/描述,比如feat/login、fix/timeout、chore/deps,一眼就能看出分支归属和用途。配合保护分支(主分支禁止直接push,只能通过MR合入),代码质量就能从流程上得到保障。
8.3 两个极大的提效习惯
最后分享两个我从实战中沉淀下来的小习惯。
一是频繁提交,小步提交。 我见过有人憋一天才提交一次,提交信息写得像项目总结报告一样长,出了bug完全没法定位到具体是哪次改动引起的。正确做法是每完成一个功能点就提交一次,保持提交粒度小,历史清晰,回滚也精准。
二是提交前务必看一眼git status和git diff。 好多人只执行git add .和git commit,从来不检查自己到底提交了什么,直到密钥、本地配置文件、临时调试代码泄露出去才追悔莫及。确认一下再提交,成本几乎为零,收益却是几何级的。
Git这门工具真的不难,难的是养成“提交前检查、提交时规范、提交后梳理”的好习惯。把上面这些内容吃透,你基本就能在项目里独立使用Git完成日常开发了。剩下的细节,边用边查、踩坑再补,才是成长的正常节奏。
