目录

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 执行过程:

  1. 找到 feature 和 main 的公共祖先(B)
  2. 把 feature 上 B 之后的提交(D、E)存为 patch
  3. 将 feature 指针重置到 main 的顶端(C)
  4. 依次把 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