git-01 Git基础
1. Git 简介
Git 是 Linus Torvalds 于 2005 年为管理 Linux 内核代码而开发的分布式版本控制系统。
与集中式版本控制(如 SVN)不同,每个 Git 仓库都包含完整的历史记录和版本追踪能力,无需依赖中央服务器即可工作。
核心优势:
- 分布式:每个开发者本地有完整仓库
- 快速:大部分操作在本地完成
- 分支轻量:创建/切换分支几乎零成本
- 数据完整性:SHA-1 哈希保证数据不被篡改
2. 安装与配置
2.1 安装
brew install git
apt install git
yum install git
git --version
2.2 全局配置
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global core.editor vim
git config --global init.defaultBranch main
git config --list
git config --global --list # ~/.gitconfig
git config --local --list # .git/config(仓库级)
2.3 SSH 密钥配置
ssh-keygen -t ed25519 -C "you@example.com"
ssh-keygen -t rsa -b 4096 -C "you@example.com"
执行后交互式询问:
Enter file in which to save the key: ~/.ssh/id_ed25519 # 回车使用默认路径
Enter passphrase (empty for no passphrase): # 密钥口令,可为空
生成两个文件:
~/.ssh/id_ed25519 ← 私钥,绝对不能泄露
~/.ssh/id_ed25519.pub ← 公钥,上传到 GitHub/GitLab
| Ed25519 | RSA 4096 | |
|---|---|---|
| 安全性 | 更高 | 高 |
| 性能 | 更快 | 慢 |
| 兼容性 | 需要较新系统 | 老系统兼容 |
| 推荐 | 现代首选 | 老系统备用 |
cat ~/.ssh/id_ed25519.pub
ssh -T git@github.com
3. 核心概念
3.1 三个工作区域
工作区 (Working Directory)
↓ git add
暂存区 (Staging Area / Index)
↓ git commit
本地仓库 (Local Repository)
↓ git push
远程仓库 (Remote Repository)
| 区域 | 说明 | 位置 |
|---|---|---|
| 工作区 | 实际编辑的文件目录 | 项目根目录 |
| 暂存区 | 下次提交将包含的文件快照 | .git/index |
| 本地仓库 | 所有提交历史 | .git/objects/ |
| 远程仓库 | 托管在服务器上的仓库 | GitHub / GitLab 等 |
3.2 文件状态
Untracked → git add → Staged
Staged → git commit → Committed (Unmodified)
Unmodified → 编辑文件 → Modified
Modified → git add → Staged
Committed → git rm → Untracked
4. 基本命令
4.1 初始化与克隆
git init
git init my-project # 创建并初始化目录
git clone https://github.com/user/repo.git
git clone https://github.com/user/repo.git my-dir # 克隆到指定目录
git clone --depth 1 https://github.com/user/repo.git # 浅克隆,只取最近1次提交
4.2 查看状态与历史
git status
git status -s # 简洁模式
git log
git log --oneline # 单行显示
git log --oneline --graph # 显示分支图
git log --oneline -10 # 最近10条
git log --author="Name" # 按作者过滤
git log --since="2024-01-01" --until="2024-12-31"
git log --follow -p file.txt
git diff # 工作区 vs 暂存区
git diff --staged # 暂存区 vs 最近提交
git diff HEAD # 工作区 vs 最近提交
git diff commit1 commit2 # 两次提交之间的差异
4.3 暂存与提交
git add file.txt # 指定文件
git add . # 当前目录所有变更
git add -p # 交互式选择部分变更(patch模式)
git commit -m "feat: add login feature"
git commit -am "fix: typo" # 跳过 add,直接提交已追踪文件的修改
git commit --amend -m "new message"
git commit --amend --no-edit # 追加暂存区内容,不改消息
4.4 撤销操作
git checkout -- file.txt # 旧语法
git restore file.txt # 新语法(Git 2.23+)
git reset HEAD file.txt # 旧语法
git restore --staged file.txt # 新语法
git revert <commit> # 生成新提交来撤销,安全,适合已 push
git reset --soft HEAD~1 # 撤销提交,保留暂存区
git reset --mixed HEAD~1 # 撤销提交,保留工作区(默认)
git reset --hard HEAD~1 # 撤销提交,丢弃所有变更(危险)
| 命令 | 提交历史 | 暂存区 | 工作区 |
|---|---|---|---|
reset --soft |
回退 | 保留 | 保留 |
reset --mixed |
回退 | 清空 | 保留 |
reset --hard |
回退 | 清空 | 清空 |
revert |
新增 | 不变 | 不变 |
5. 分支管理
5.1 基本操作
git branch # 本地分支
git branch -r # 远程分支
git branch -a # 所有分支
git branch feature/login
git checkout feature/login # 旧语法
git switch feature/login # 新语法(Git 2.23+)
git checkout -b feature/login
git switch -c feature/login
git branch -d feature/login # 安全删除(已合并才能删)
git branch -D feature/login # 强制删除
git branch -m old-name new-name
5.2 合并分支:三种方式
Fast-forward merge
条件:目标分支是当前分支的直接后代,中间没有分叉。
合并前:
main: A──B
\
feature: C──D
fast-forward 后:
main: A──B──C──D (main 指针直接移动到 D,不产生新提交)
git merge feature/login # 默认,能 ff 就 ff
git merge --ff-only feature/login # 强制 ff,不能 ff 则报错
No fast-forward merge(–no-ff)
即使能 ff 也强制产生一个合并提交,保留分支轨迹。
--no-ff 后:
main: A──B──────M (M 是合并提交,有两个 parent:B 和 D)
\ /
feature: C──D
git merge --no-ff feature/login -m "Merge feature/login"
团队开发时推荐使用,合并后可以清晰看到哪些提交属于同一个功能。
Squash merge
把 feature 上的所有提交压缩成一个提交合入主干,不产生合并关系。
squash 后:
main: A──B──S (S 包含 C+D+E 的所有变更,但只有一个 parent:B)
git merge --squash feature/login
git commit -m "feat: add login feature" # 需要手动提交
适合场景:feature 分支上有很多"WIP"“fix typo"等零碎提交,合并到主干时只想保留一个干净的提交。
三种方式对比
| Fast-forward | No-fast-forward | Squash | |
|---|---|---|---|
| 是否产生合并提交 | 否 | 是 | 否(新提交) |
| 能否看出分支历史 | 否 | 能 | 否 |
| feature 提交是否保留 | 保留 | 保留 | 合并为一个 |
| 适合场景 | 个人分支、小修改 | 团队协作、功能分支 | PR 合并、保持主干整洁 |
默认行为:
git merge 默认是能 ff 就 ff,不能 ff 则自动 no-ff。即:
- 无分叉(目标分支是当前分支的直接后代)→ 直接移动指针(ff)
- 有分叉 → 自动产生合并提交(no-ff)
可以通过配置改变全局默认行为:
git config --global merge.ff true # 默认(能 ff 就 ff)
git config --global merge.ff false # 永远不 ff,始终产生合并提交(团队推荐)
git config --global merge.ff only # 只允许 ff,有分叉直接报错
常见误区:merge 一定会多一个提交记录?
不一定,取决于是否发生了 Fast-forward:
- merge + 能 ff → 直接移动指针,不产生额外提交,和 rebase 效果一样
- merge + 不能 ff → 产生一个合并提交 M
- rebase → 不产生合并提交,但会重写每个提交的 SHA
真正的区别不是提交数量,而是:merge 不重写历史,rebase 重写历史换取线性记录。
哪些情况不支持 ff?
只要 main 在 feature 分叉之后自己有过新提交,就不能 ff:
情况一:main 在 feature 分叉后有了新提交
main: A──B──C
\
feature: D──E
两边都以 B 为起点出现分叉,不能 ff。
情况二:团队协作中 main 持续推进(日常最常见)
main: A──B──C──F
\
feature: D──E ← 基于 B 拉出,main 后来又推进了 C、F
main 超前了多个提交,出现分叉,不能 ff。
日常团队开发中 main 几乎一直在推进,所以大多数情况都不能 ff,能 ff 的场景反而少见。
5.3 Rebase
rebase 不是"移动提交”,而是在新的基点上重新应用变更,每个提交都会生成新的 SHA。
rebase 前:
main: A──B──C
\
feature: D──E (feature 从 B 分叉)
git checkout feature
git rebase main
rebase 后:
main: A──B──C
\
feature: D'──E' (D'、E' 内容同 D、E,但 parent 变了,SHA 不同)
rebase 执行过程:
- 找到 feature 和 main 的公共祖先(B)
- 把 feature 上 B 之后的提交(D、E)存为 patch
- 将 feature 指针重置到 main 的顶端(C)
- 依次把 patch 应用到 C 上,生成 D’、E'
rebase vs merge:
| merge | rebase | |
|---|---|---|
| 历史记录 | 保留真实历史,有分叉 | 线性,整洁 |
| 是否重写提交 SHA | 不重写 | 重写 |
| 冲突处理 | 只处理一次 | 每个提交都可能处理一次 |
| 公共分支安全性 | 安全 | 危险,禁止对公共分支 rebase |
| 适合场景 | 主干合并、保留完整历史 | 个人分支整理、保持线性历史 |
什么时候用 merge,什么时候用 rebase:
- 合并到主干(main/develop)→ merge
- 多人共用的分支之间合并 → merge
- 同步主干最新代码到 feature 分支 → rebase
- 提交 PR 前整理本地提交历史 → rebase -i
rebase 黄金法则:永远不要对已推送到公共分支的提交做 rebase!
rebase 改变提交 SHA,其他人基于旧 SHA 开发的代码 pull 后会看到重复提交,造成混乱。
git push --force-with-lease origin feature/login
交互式 rebase(整理提交历史):
git rebase -i HEAD~3 # 整理最近 3 次提交
git rebase -i origin/main # 整理从分叉点到现在的所有提交(推荐)
打开编辑器,每行代表一次提交(从旧到新):
pick a1b2c3 feat: add user model
pick d4e5f6 WIP: working on controller
pick e5f6g7 fix: typo
pick g7h8i9 feat: add user controller
| 命令 | 缩写 | 说明 |
|---|---|---|
pick |
p |
保留提交 |
reword |
r |
保留提交,修改消息 |
edit |
e |
保留提交,暂停以修改内容(可拆分提交) |
squash |
s |
合并到上一个提交,保留消息 |
fixup |
f |
合并到上一个提交,丢弃本次消息 |
drop |
d |
丢弃提交 |
解决 rebase 冲突:
vim main.go # 编辑冲突文件
git add main.go # 标记已解决
git rebase --continue # 继续处理下一个提交
git rebase --skip # 跳过当前提交
git rebase --abort # 放弃整个 rebase
5.4 解决冲突
git merge feature/login
git status
git add app.go
git commit
git merge --abort
使用 mergetool 可视化解决冲突:
git config --global merge.tool vimdiff
git mergetool
6. 远程仓库
6.1 管理远程
git remote -v
git remote add origin https://github.com/user/repo.git
git remote set-url origin git@github.com:user/repo.git
git remote remove origin
6.2 推送与拉取
git push origin main
git push -u origin main # -u 设置上游,之后可直接 git push
git push --force-with-lease # 安全的强制推送(推荐替代 --force)
git fetch origin # 只下载,不合并
git pull # fetch + merge
git pull --rebase # fetch + rebase(保持线性历史)
git branch --set-upstream-to=origin/main main
6.3 标签
git tag v1.0.0
git tag -a v1.0.0 -m "Release version 1.0.0"
git tag -a v1.0.0 a1b2c3
git tag
git tag -l "v1.*" # 通配符过滤
git show v1.0.0
git push origin v1.0.0
git push origin --tags # 推送所有标签
git tag -d v1.0.0
git push origin --delete v1.0.0
标签排序
git tag 默认按字母序排列,版本号会乱序(v1.10 排在 v1.2 前面):
git tag
git tag --sort=version:refname
git tag --sort=-version:refname
git tag --sort=-version:refname | head -5
设置全局默认排序,以后 git tag 直接按版本号排:
git config --global tag.sort version:refname
其他实用操作:
git checkout v1.0.0
git checkout -b hotfix/v1.0.1 v1.0.0
git log v1.0.0..v1.1.0 --oneline
git describe --tags
git describe --tags a1b2c3
7. .gitignore
*.log # 忽略所有 .log 文件
build/ # 忽略 build 目录
!important.log # 不忽略 important.log
/TODO # 只忽略根目录的 TODO
doc/*.txt # 忽略 doc 目录下的 .txt(不递归)
doc/**/*.pdf # 忽略 doc 目录下所有 .pdf(递归)
git rm --cached file.txt
git rm -r --cached . # 取消所有追踪,再重新 add
8. 常用快捷操作
git blame file.txt
git blame -L 10,20 file.txt # 只看 10-20 行
git bisect start
git bisect bad # 当前版本是坏的
git bisect good v1.0.0 # v1.0.0 是好的
git bisect good / git bisect bad
git bisect reset # 结束后重置
git log --grep="fix: login"
git log -S "function login" # 查找添加/删除了该字符串的提交
git clean -fd # 删除未追踪的文件和目录
git clean -nfd # 预览,不实际删除
9. 提交规范(Conventional Commits)
良好的提交信息便于追溯和生成 CHANGELOG:
<type>(<scope>): <subject>
<body>
<footer>
| type | 说明 |
|---|---|
feat |
新功能 |
fix |
Bug 修复 |
docs |
文档变更 |
style |
格式调整(不影响逻辑) |
refactor |
重构 |
perf |
性能优化 |
test |
测试相关 |
chore |
构建/工具链变更 |
revert |
回滚提交 |
示例:
feat(auth): add JWT token refresh
Implement sliding window token refresh to improve UX.
Tokens are refreshed when < 5 minutes remain.
Closes #123
xingliuhua