说起Git,估计很多人都有过这样的经历:刚开始接触的时候觉得简单,不就是几个命令嘛,push、pull、commit,有什么难的?结果真正用起来发现处处是坑,代码冲突、分支混乱、历史记录乱七八糟...
我记得刚工作那会儿,因为对Git理解不够深入,曾经把整个项目的提交历史搞得一团糟,最后只能重新建仓库。那次之后我就下定决心要把Git彻底搞明白。
今天就把我这几年使用Git的经验和踩过的坑全部分享出来,相信看完之后你对Git的认识会有一个质的提升。
很多人一上来就开始学命令,但其实理解Git的工作原理更重要。Git本质上就是一个分布式版本控制系统,它会追踪文件的每一次变化,并且可以让多个人协同工作。
想象一下你在写一份重要的文档,每次修改前你都会复制一份备份,比如"方案v1.doc"、"方案v2.doc"、"方案最终版.doc"、"方案真的最终版.doc"... 这种做法很熟悉吧?Git做的事情本质上就是这样,只不过它更智能,能够追踪每个字符的变化。

image-20260105220140362
Git有几个重要的概念需要理解:
这就像是一个生产流水线,你在工作区修改文件,然后把修改放到暂存区等待质检,最后合格的修改才会被送到仓库永久保存。
刚安装Git后第一件事就是配置用户信息,这个步骤很多人会忽略,但非常重要:
git config --global user.name "你的名字"
git config --global user.email "你的邮箱"这里有个小技巧,如果你在公司和个人项目中使用不同的邮箱,可以在具体项目中单独设置:
git config user.email "work@company.com"有两种方式开始一个Git项目:
方式一:从头开始
mkdir my-project
cd my-project
git init方式二:克隆已有项目
git clone https://github.com/user/repo.git我个人更推荐方式二,因为克隆下来的仓库已经配置好了远程仓库地址。
Git中文件有几种状态,理解这些状态对后续操作很重要:
# 查看文件状态
git status
# 查看具体修改内容
git diff
# 添加文件到暂存区
git add filename
# 或者添加所有修改
git add .
# 提交到仓库
git commit -m "提交说明"这里说一个我的习惯,我很少用git add .,而是喜欢一个文件一个文件地添加,这样可以确保每次提交的内容都是我确实想要提交的。
git add src/main.js
git add src/utils.js
git commit -m "优化主要逻辑和工具函数"提交信息可能是很多人最容易忽视的部分,但好的提交信息对项目维护太重要了。我见过太多这样的提交信息:
这种信息等于没有信息。我现在遵循这样的规范:
# 格式:类型: 简短描述
git commit -m "feat: 添加用户登录功能"
git commit -m "fix: 修复购物车计算错误"
git commit -m "docs: 更新API文档"
git commit -m "refactor: 重构数据库连接逻辑"类型包括:

image-20260105220510032
分支是Git最强大的功能之一,但也是最容易出问题的地方。
# 查看所有分支
git branch -a
# 创建新分支
git branch feature/user-login
# 切换分支
git checkout feature/user-login
# 创建并切换到新分支(推荐)
git checkout -b feature/user-login
# 新版本Git可以用switch
git switch -c feature/user-login
image-20260105220535643
我现在基本上都用git checkout -b,一步到位。
在实际项目中,分支策略非常重要。我们公司现在用的是Git Flow的简化版本:
main:主分支,只放稳定代码develop:开发分支,日常开发的基础feature/*:功能分支,开发新功能时使用hotfix/*:紧急修复分支举个例子,假如我要开发一个用户注册功能:
# 从develop分支创建功能分支
git checkout develop
git pull origin develop
git checkout -b feature/user-register
# 开发完成后
git add .
git commit -m "feat: 实现用户注册功能"
git push origin feature/user-register然后在GitHub或GitLab上创建Pull Request,代码审查通过后再合并到develop分支。
合并分支有几种方式,每种都有自己的使用场景:
git checkout main
git merge feature/user-logingit merge --no-ff feature/user-logingit merge --squash feature/user-login我个人比较喜欢用--no-ff,因为能清楚看到功能分支的开发历史,对追溯问题很有帮助。
# 查看远程仓库
git remote -v
# 添加远程仓库
git remote add origin https://github.com/user/repo.git
# 推送到远程
git push origin main
# 从远程拉取
git pull origin main
# 获取远程更新但不合并
git fetch origin这里有个坑需要注意,git pull实际上等于git fetch + git merge,如果你不想自动合并,建议分开执行:
git fetch origin
git diff HEAD origin/main # 查看差异
git merge origin/main # 确认后再合并经常会遇到这种情况,你想推送代码时提示:
! [rejected] main -> main (fetch first)这时候不要直接git push --force!正确的做法是:
git fetch origin
git rebase origin/main
# 解决冲突后
git push origin main或者如果你更喜欢merge:
git pull origin main
# 解决冲突后
git push origin main代码冲突是多人协作时必然会遇到的问题,刚开始可能会觉得很复杂,其实掌握了套路就不难。
当Git无法自动合并时,会在文件中插入冲突标记:
<<<<<<< HEAD
你的修改
=======
别人的修改
>>>>>>> branch-name这些标记把冲突的内容分成了两部分:
<<<<<<< HEAD 到 =======:你的修改======= 到 >>>>>>> branch-name:别人的修改解决冲突的步骤:
git status<<<<<<<, =======, >>>>>>>)git add filenamegit commit举个实际例子,假设在配置文件中发生冲突:
// 冲突前
<<<<<<< HEAD
const API_URL = 'https://api.prod.com';
=======
const API_URL = 'https://api.test.com';
>>>>>>> feature/new-api解决后:
// 解决后
const API_URL = process.env.NODE_ENV === 'production'
? 'https://api.prod.com'
: 'https://api.test.com';这样既保留了两个版本的内容,又避免了环境问题。
命令行解决冲突有时候不够直观,可以配置图形化工具:
# 配置merge工具(以VSCode为例)
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'
# 使用工具解决冲突
git mergetool我个人更喜欢直接在编辑器中解决,VSCode对Git冲突的支持就很好。
# 查看提交历史
git log
# 简洁格式
git log --oneline
# 图形化显示分支
git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset'
# 查看某个文件的历史
git log -p filename最后那个命令有点长,但显示效果很好,我把它设置成了别名:
git config --global alias.lg "log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset'"之后就可以用git lg来查看美观的历史记录了。
有时候需要回退到之前的版本,Git提供了几种方式:
git reset --hard HEAD~1git reset --hard commit-hashgit revert commit-hashreset和revert的区别很重要:
reset:直接回退,历史记录会丢失revert:创建一个新提交来撤销之前的修改,历史记录保留在团队项目中,如果代码已经推送到远程,建议使用revert,避免影响其他人的代码。
# 修改最后一次提交信息
git commit --amend -m "新的提交信息"
# 修改最后一次提交内容(添加遗漏的文件)
git add forgotten-file.txt
git commit --amend --no-edit如果要修改更早的提交,需要用到rebase,这个稍微复杂一些:
git rebase -i HEAD~3 # 修改最近3次提交.gitignore文件用来告诉Git忽略某些文件或目录,这些通常是:
dist/, build/)node_modules/).env).DS_Store)# 依赖包
node_modules/
vendor/
# 构建产物
dist/
build/
*.min.js
# 配置文件
.env
.env.local
config/database.yml
# 日志文件
*.log
logs/
# 系统文件
.DS_Store
Thumbs.db
# IDE文件
.vscode/
.idea/
*.swp有个网站https://gitignore.io很好用,可以根据你的技术栈自动生成`.gitignore`文件。
设置一些常用的别名可以大大提高效率:
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
git config --global alias.unstage 'reset HEAD --'
git config --global alias.last 'log -1 HEAD'
git config --global alias.visual '!gitk'我最常用的几个别名:
git config --global alias.ac '!git add -A && git commit -m'
git config --global alias.acp '!git add -A && git commit -m "$1" && git push'这样就可以用git ac "提交信息"来快速添加并提交所有文件。
有时候工作做到一半需要切换分支处理其他事情,但代码还没到可以提交的状态,这时候可以用stash:
# 暂存当前工作
git stash
# 暂存时添加说明
git stash save "正在开发登录功能"
# 查看所有暂存
git stash list
# 恢复最近的暂存
git stash pop
# 恢复指定暂存
git stash apply stash@{1}
# 删除暂存
git stash drop stash@{0}我经常用这个功能,特别是当PM突然说有个紧急bug需要修复的时候。
标签通常用来标记重要的版本:
# 创建轻量标签
git tag v1.0.0
# 创建带注释的标签
git tag -a v1.0.0 -m "发布版本1.0.0"
# 查看所有标签
git tag
# 查看标签信息
git show v1.0.0
# 推送标签到远程
git push origin v1.0.0
# 推送所有标签
git push origin --tags
# 删除标签
git tag -d v1.0.0
git push origin :refs/tags/v1.0.0当项目依赖其他Git仓库时,可以使用子模块:
# 添加子模块
git submodule add https://github.com/user/library.git libs/library
# 克隆包含子模块的项目
git clone --recurse-submodules https://github.com/user/main-project.git
# 初始化子模块
git submodule init
git submodule update
# 或者一步到位
git submodule update --init --recursive不过说实话,子模块用起来比较麻烦,现在更多项目选择用包管理器(如npm、composer)来管理依赖。
在团队开发中,Git工作流的规范性非常重要。我们团队总结了一套比较实用的流程:
git checkout develop
git pull origin develop
git checkout -b feature/user-avatar-upload# 每天开始工作前
git checkout develop
git pull origin develop
git checkout feature/user-avatar-upload
git rebase develop # 保持分支基于最新的developgit add .
git commit -m "feat: 实现用户头像上传功能"
git push origin feature/user-avatar-upload
# 然后在GitLab/GitHub上创建Merge Requestgit checkout develop
git pull origin develop
git merge --no-ff feature/user-avatar-upload
git push origin develop
git branch -d feature/user-avatar-upload # 删除本地分支
git push origin --delete feature/user-avatar-upload # 删除远程分支当生产环境出现紧急bug时:
git checkout main
git pull origin main
git checkout -b hotfix/login-error-fix
# 修复bug
git add .
git commit -m "fix: 修复登录页面验证错误"
git push origin hotfix/login-error-fix
# 创建PR,审查通过后合并到main和develop这种情况经常发生,在main分支上开发了半天才发现:
# 暂存当前修改
git stash
# 创建新分支
git checkout -b feature/correct-branch
# 恢复修改
git stash pop# 错误信息:Updates were rejected because the remote contains work
git pull origin main
# 解决冲突后
git push origin main# 恢复单个文件
git checkout HEAD -- filename
# 恢复所有文件到最后一次提交状态
git reset --hard HEAD# 撤销最后一次提交,但保留修改
git reset --soft HEAD~1
# 切换到正确分支
git checkout correct-branch
# 重新提交
git add .
git commit -m "正确的提交信息"Git不适合存储大文件,但有时候无法避免:
# 使用Git LFS处理大文件
git lfs track "*.psd"
git lfs track "*.zip"
git add .gitattributes长期使用的仓库可能会变得很大:
# 清理不必要的文件
git gc --prune=now --aggressive
# 查看仓库大小
git count-objects -vH我在~/.bashrc中定义了一些有用的函数:
# 快速切换到项目并拉取最新代码
function gcd() {
cd ~/projects/$1 && git pull origin $(git branch --show-current)
}
# 快速创建功能分支
function gnb() {
git checkout develop
git pull origin develop
git checkout -b feature/$1
}公众号:运维躬行录
个人博客:躬行笔记