git —— 版本控制 + GitOps 的底座
一句话定义
git 是分布式版本控制系统。开发用法这里不展开(assumed knowledge),这篇聚焦运维 / SRE 视角:GitOps 工作流、CI 脚本里的 git、git log 排查"谁改的"、临时改动 stash 救命、误操作的恢复。
典型场景
- GitOps:所有 K8s manifests 在 git 里、Argo CD / Flux 监听
- CI 脚本里克隆代码:
git clone --depth=1 --branch main ... - 排查问题:"这个文件是谁改的、什么时候、改了啥":
git log -p/git blame - 把临时改动收起来:
git stash - 误删 commit 后救回来:
git reflog
配置(一次性)
git config --global user.name "Yuanjun"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main # 新仓库默认分支
git config --global pull.rebase true # pull 用 rebase(**强烈推荐**)
git config --global rebase.autoStash true # rebase 前自动 stash
git config --global core.editor vim
git config --global color.ui auto
# 列当前配置
git config --list
git config --get user.email
.gitconfig 在 ~/.gitconfig 或 ~/.config/git/config。
clone / pull / push 的实战 flag
clone
git clone https://github.com/org/repo.git
git clone --depth=1 https://github.com/org/repo.git # shallow clone,CI 快很多
git clone --branch v1.0 https://github.com/org/repo.git
git clone --single-branch --branch main ... # 只 fetch 一个分支
git clone --recurse-submodules ... # 含 submodule
# SSH
git clone git@github.com:org/repo.git
CI 流水线里用 --depth=1:
git clone --depth=1 --branch $TAG https://github.com/org/repo.git
# 只下最近一个 commit,比全量快 10 倍
pull vs pull --rebase
git pull # 默认 fetch + merge
git pull --rebase # fetch + rebase(**推荐**)
pull (merge) 会产生 merge commit、历史乱糟糟。pull --rebase 把本地 commit 重新摞在远端 HEAD 之上、历史线性、漂亮。
设默认:git config --global pull.rebase true。
push
git push # 推当前分支
git push -u origin feat-x # 第一次推 + 设上游
git push --force # 强推(**危险**)
git push --force-with-lease # 强推但保护别人的 commit(**推荐**)
git push origin :feat-x # 删远端分支
git push origin --delete feat-x # 等价
--force 永远不要直接用。用 --force-with-lease:
--force—— 不管远端有什么、用本地覆盖--force-with-lease—— 远端如果有你不知道的新 commit 会拒绝(救命)
看历史:log / show / blame
git log # 完整历史
git log --oneline # 一行一个
git log --oneline --graph --all # 图形化所有分支
git log --since="2 days ago" # 时间过滤
git log --author="Alice"
git log --grep="bugfix" # message 含 "bugfix"
git log -- path/to/file # 某文件的历史
git log -p -- path/to/file # 含每个 commit 的 diff
git log --stat # 含文件统计
# 单 commit 详情
git show <commit-id>
git show <commit-id> -- path/to/file # 该 commit 中某文件的 diff
git show <commit-id>:path/to/file # 该 commit 中某文件的内容
git blame —— 这行是谁改的
git blame path/to/file
# abcdef12 (Alice 2026-01-15 ...) line 1: hello
# bcdef123 (Bob 2026-02-20 ...) line 2: world
git blame -L 100,120 path/to/file # 只看 100-120 行
排查"这行配置是谁加的、为啥加的" 黄金套路:
git blame -L 50,60 deployment.yaml | head # 找 commit ID
git show <commit-id> # 看完整 commit message
diff —— 看改动
git diff # 工作区 vs 暂存区
git diff --staged # 暂存区 vs HEAD
git diff HEAD # 工作区 vs HEAD
git diff main feat-x # 两个分支
git diff main..feat-x # 同上
git diff --stat # 统计(不显示内容)
git diff --name-only # 只列文件名
git diff HEAD -- path/to/file # 限单文件
工作流:feature branch + PR
# 1. 拉最新 main
git checkout main
git pull --rebase
# 2. 开新分支
git checkout -b feat-x
# 或者新写法
git switch -c feat-x
# 3. 改改改 + commit
git add -p # 交互式选 chunk 加
git commit -m "feat: add x"
# 4. 推
git push -u origin feat-x
# 5. GitHub / GitLab 上发 PR
# 通过审核后被 merge
# 6. 清本地
git checkout main
git pull --rebase
git branch -d feat-x # 已 merge 的分支
git fetch --prune # 清远端已删的分支引用
stash —— 临时藏起改动
工作做一半要切分支处理紧急 bug:
git stash # 把当前未提交改动藏起来
git stash list # 看 stash 队列
# stash@{0}: WIP on feat-x: ...
# stash@{1}: WIP on main: ...
git stash pop # 取出最新一个(弹出)
git stash apply # 取出但不删
git stash apply stash@{1} # 指定哪个
git stash drop stash@{0} # 删某个
git stash clear # 清所有 stash
git stash -u 包含未追踪的文件(新增文件 add 之前)。
救命系列
reset —— 把分支指针往回挪
git reset --soft HEAD~1 # 撤销最近 commit,改动留在暂存区
git reset --mixed HEAD~1 # (默认)撤销 commit + 取消暂存
git reset --hard HEAD~1 # **删 commit + 删改动**(危险)
git reset --hard origin/main # 强制和远端一致(**慎用**)
--soft < --mixed < --hard,--hard 是核选项——丢的东西基本回不来(除非 reflog 救)。
revert —— 创建反向 commit 回滚
git revert <commit-id> # 创建一个新 commit 抵消 <commit-id>
# 历史保留,操作可被追溯
已 push 到共享分支的 commit 不要 reset,用 revert。否则别人 pull 会乱。
reflog —— 时光机
git reflog
# abc1234 HEAD@{0}: reset: moving to HEAD~1
# def5678 HEAD@{1}: commit: my important commit
# ...
每次 HEAD 移动 git 都记录。reset --hard 之后从这里救:
git reset --hard HEAD@{1} # 回到 reset 之前
git reflog 是程序员的 undo 历史,90% 的"完了我把代码删了"都能救。
cherry-pick —— 把单个 commit 摘到当前分支
git cherry-pick <commit-id>
git cherry-pick <id1> <id2> <id3>
git cherry-pick A..B # A 之后到 B(不含 A)
bugfix 在 feature 分支、想合到 main 用 cherry-pick。
冲突时:
git cherry-pick --continue # 解决后继续
git cherry-pick --abort # 取消
rebase —— 重写历史
git rebase main # 把当前分支 rebase 到 main
git rebase --interactive HEAD~3 # 交互式 rebase 最近 3 个 commit
# 弹出编辑器,可以:
# pick -- 保留
# reword -- 改 message
# edit -- 暂停让你改
# squash -- 合并到前一个
# fixup -- 同 squash 但丢弃 message
# drop -- 删
CI 工具 / GitOps 推 PR 前常用 interactive rebase 整理 commit。
rebase 冲突处理
git rebase main
# CONFLICT in ...
# 手动改文件 / 解决冲突
git add <files>
git rebase --continue # 继续
git rebase --skip # 跳过当前 commit
git rebase --abort # 取消(回到 rebase 前)
rebase 之后必 --force-with-lease
git rebase main
git push --force-with-lease # 因为历史变了,普通 push 拒
GitOps 场景
1. CI 里只拉一次代码(快)
git clone --depth=1 --branch $BRANCH https://github.com/org/manifests
2. 在 CI 里改 manifests 后推回
git config user.name "ci-bot"
git config user.email "ci@example.com"
git add k8s/deployment.yaml
git commit -m "ci: bump image to v$VERSION"
git push origin main
# Argo CD 监听 main、自动 sync
3. 看 manifests 历史 + 谁改的
git log -p --all -- k8s/deployment.yaml | less
git blame k8s/deployment.yaml
4. 回滚部署 = git revert
git revert <bad-commit-id> # 反向 commit
git push # Argo CD 自动同步
GitOps 的好处:rollback = git revert,操作可审计。
5. 看 PR 改了什么 K8s 资源
git fetch origin pull/123/head:pr-123 # 拉 PR
git diff main pr-123 -- k8s/
submodule
git submodule add https://github.com/org/lib lib # 加
git submodule update --init --recursive # 初始化
git submodule update --remote # 更新到上游最新
git clone --recurse-submodules ... # clone 时连带拉
submodule 难管。优先用 monorepo / 包管理替代。
tag
git tag # 列所有
git tag v1.0.0 # 轻量 tag
git tag -a v1.0.0 -m "Release 1.0" # annotated tag(**推荐**)
git tag -d v1.0.0 # 删本地
git push origin v1.0.0 # 推单个 tag
git push origin --tags # 推所有 tag
git push origin :refs/tags/v1.0.0 # 删远端 tag
Helm chart / Docker image 版本通常和 git tag 绑定。
常见踩坑
坑 1:在错的分支 commit 了
# 在 main 上 commit 了改动,应该在 feat-x 上
git checkout -b feat-x # 在当前 commit 开新分支
git checkout main
git reset --hard origin/main # main 回到远端状态
坑 2:commit 包含敏感信息(密码 / key)
已 push 的话视作泄漏,下面操作只是清痕迹,密钥要换:
# 删整个文件历史
git filter-repo --invert-paths --path secrets.yml # 现代工具
# 或老工具
git filter-branch --tree-filter 'rm -f secrets.yml' HEAD
git push --force-with-lease
或者更彻底:BFG Repo-Cleaner。
预防:.gitignore 加 .env、*.key、*-secret.yaml。
坑 3:误 git reset --hard 删了工作
git reflog # 找到之前的 HEAD
git reset --hard HEAD@{1} # 救回
只要 git 进程还在跑、commit 没被 GC(默认 30 天)就能救。
坑 4:merge conflict 不知道怎么解
git merge main
# CONFLICT in deployment.yaml
文件里出现:
<<<<<<< HEAD
image: nginx:1.25
=======
image: nginx:1.26
>>>>>>> main
选一个 / 合并 / 都用、删掉 <<<<<<< ======= >>>>>>> 标记。
git add deployment.yaml
git commit # 或 git merge --continue
工具辅助:
git mergetool # 用 vimdiff / meld 等
坑 5:误推到 main
git push origin main
# 完了,main 有问题
如果你唯一推了:
git push --force-with-lease origin <good-commit>:main # 强推到正确 commit
多人共享 main:用 revert 而不是 force push:
git revert <bad-commit-id>
git push origin main
坑 6:误 commit 了大文件
git add big-binary.zip # 1GB
git commit
git push
# 慢 / 失败 / 仓库永远大
git 不擅长大文件。用 Git LFS:
git lfs track "*.zip"
或者别 commit 大文件,用对象存储 / artifact registry。
坑 7:clone 慢
git clone https://github.com/big-org/big-repo
# 卡几分钟
git clone --depth=1 --branch main https://... # shallow clone
git clone --filter=blob:none --branch main https://... # 部分 clone
坑 8:line ending 跨平台问题
Windows / Linux 行尾不同(CRLF vs LF)。
.gitattributes:
* text=auto eol=lf
或全局:
git config --global core.autocrlf input # Linux / Mac
git config --global core.autocrlf true # Windows
坑 9:把 .git 目录提交进了 docker 镜像
COPY . /app
# /app/.git 也复制进去 → 镜像变大、可能泄漏 commit 历史
.dockerignore:
.git
.gitignore
坑 10:pull 之后本地修改没了
git pull # 本地未 commit 改动消失?
git 一般不会 silently 丢——通常是有 conflict 让你解。但 git checkout . / git reset --hard 是真的丢。
防御:改之前先 git stash 或先 commit(哪怕是 wip)。
SSH key 多账号
详见 ssh-config.md 场景 3。
# ~/.ssh/config
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
git clone git@github-work:company/repo.git # 走公司 key
关联命令
- ssh-config —— git over SSH 配置
- ssh-keygen —— 生成 deploy key
- argocd —— GitOps 工具消费 git
- helm —— chart 通常在 git 仓库里
- kubectl —— GitOps 最终通过 kubectl apply 收敛
gh/glab—— GitHub / GitLab 官方 CLI(管 PR / Issue / Action)