未记录事实核验 · 状态:维护中
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 适用范围与待维护事项
本文适用于已掌握分支、提交和冲突处理的读者;普通操作可在一次性练习仓库完成,历史清理则需要备份、权限管理和团队协作能力。原稿的忽略规则例外、reflog保留期限与恢复方式存在误导,须结合修订使用。敏感信息清理段仅是处置概要,未覆盖远程引用、托管缓存、他人克隆及强制推送后的协作恢复,不可直接作为生产泄密处置流程;应先吊销泄露凭据,再按仓库托管方要求协调清理。
Git 进阶技巧 - stash、rebase、tag 与 .gitignore
abstract 本章概要
- git stash:临时保存工作现场
- cherry-pick:精准挑选提交
- rebase vs merge:两种历史整理方式
- tag:版本标记管理
- .gitignore:正确排除不需要追踪的文件
git stash:工作现场的暂存箱
场景:改到一半被打断
你正在开发 feature/new-metric 分支,改了一半的 SQL,突然来了个紧急需求,要你马上去修 main 分支的 bug。
但当前工作没完成,不想提交一个半成品的 commit,又不能就这样切走……这时候就用 stash。
# 暂存已跟踪文件的修改并还原这些文件;默认不保存未跟踪或忽略文件
# 若也要保存未跟踪文件,可用 git stash push -u;切分支前用 git status 检查
git stash
# 或者附上说明
git stash push -m "WIP: 正在开发周GMV指标,尚未完成"
# 切到 main 去修 bug
git switch main
git switch -c hotfix/fix-null-issue
# ... 修 bug,提交,PR 合并 ...
# 回到原来的功能分支
git switch feature/new-metric
# 把之前暂存的工作恢复出来
git stash pop
常用 stash 命令
# 查看所有 stash
git stash list
# 输出:
# stash@{0}: On feature/new-metric: WIP: 正在开发周GMV指标
# stash@{1}: On main: temp save
# 查看某个 stash 的内容
git stash show stash@{0}
git stash show -p stash@{0} # 显示详细 diff
# 恢复最近一个 stash,成功后删除;若发生冲突则保留 stash
git stash pop
# 恢复指定 stash,成功后删除;若发生冲突则保留 stash
git stash pop stash@{1}
# 恢复但不删除(保留在 stash 列表中)
git stash apply stash@{0}
# 删除某个 stash
git stash drop stash@{0}
# 清空所有 stash
git stash clear
warning stash 是临时保存,不是长期存储
stash 没有同步到远程仓库,换台电脑就没了。如果要长期保存某个进行中的工作,更好的方式是提交一个 WIP: xxx 的 commit 到个人分支,推到远程。
把 stash 应用到另一个分支
# 在 feature-A 分支做了一些修改,发现应该是在 feature-B 上改的
git stash
git switch feature-B
git stash pop # 把修改应用到 feature-B
cherry-pick:精准挑选提交
cherry-pick 让你从一个分支挑选某个(些)提交,应用到当前分支,而不需要合并整个分支。
场景:把 hotfix 也应用到 develop
main: ●──●──●──hotfix-commit──●
develop: ●──●──●──────────────────────●
hotfix 已经合并到 main 了,但 develop 分支还没有这个修复:
# 查看 hotfix commit 的 hash
git log main --oneline -5
# a3f8b2c (main) fix: 修复NULL值处理
# 把这个 commit 应用到 develop
git switch develop
git cherry-pick a3f8b2c
# 一次挑多个 commit
git cherry-pick a3f8b2c 9e1d234
# 挑选一个范围(不含起点)
git cherry-pick start_hash..end_hash
cherry-pick 时的冲突处理
git cherry-pick a3f8b2c
# 如果有冲突...
# 1. 解决冲突
# 2. git add <resolved-files>
# 3. 继续
git cherry-pick --continue
# 如果不想要了
git cherry-pick --abort
tip cherry-pick 的使用场景
- 把紧急修复从 main 同步到 develop
- 从别人的分支借用某个改动,但不想合并整个分支
- 在错误的分支上做了提交,想把它移到正确的分支
rebase:整理提交历史
rebase vs merge:核心区别
merge:合并两条分支的历史;非快进合并会追加一个 merge commit(如下图),快进合并则只移动分支指针
before: ●──●──● (main)
└──A──B (feature)
after merge:
●──●──●──────M (main)
└──A──B──┘
(M 是 merge commit,能看到分支历史)
rebase:把 feature 的提交"接"到 main 最新提交后面
before: ●──●──● (main)
└──A──B (feature)
after rebase:
●──●──●──A'──B' (feature, 线性历史)
(A'和B'是原来A和B的"重放"版本,hash 变了)
为什么 rebase 能产生更干净的历史
# 没有 rebase,merge 后的历史
* a3f8b2c (main) Merge: 合并feature/new-metric
|\
| * 9e1d234 feat: 添加month维度
| * 7c2f891 feat: 初始化周GMV指标
|/
* 4b5a6d7 feat: 核心ETL框架
# rebase 后,merge 到 main 的历史
* a3f8b2c (main) feat: 添加month维度
* 9e1d234 feat: 初始化周GMV指标
* 4b5a6d7 feat: 核心ETL框架
(看起来像一条直线)
功能分支上的 rebase 工作流
# 开发功能时,定期把 main 的最新内容 rebase 进来(避免后期大量冲突)
git switch feature/new-metric
git fetch origin
git rebase origin/main
# 如果有冲突,逐个解决
# git add <file>
# git rebase --continue
# 功能完成后,在 GitHub 上提 PR
# 合并时选择 "Rebase and merge"
交互式 rebase:整理提交历史
git rebase -i 让你在把分支合并到主干之前,整理自己分支上的提交历史(合并零碎提交、修改 message、删除多余提交等)。
# 交互式 rebase 最近 5 个提交
git rebase -i HEAD~5
打开编辑器,看到类似内容:
pick a3f8b2c 初始化周GMV指标
pick 9e1d234 调试中...
pick 7c2f891 调试中2...
pick 4b5a6d7 修复一个bug
pick 1e2c3b4 feat: 完成周GMV指标开发
# Commands:
# p, pick = 保留这个提交
# r, reword = 保留提交,但修改 message
# s, squash = 把这个提交合并到前一个提交
# d, drop = 删除这个提交
整理成干净的历史:
pick a3f8b2c 初始化周GMV指标
squash 9e1d234 调试中... ← 合并到上面
squash 7c2f891 调试中2... ← 合并到上面
squash 4b5a6d7 修复一个bug ← 合并到上面
reword 1e2c3b4 feat: 完成周GMV指标开发 ← 保留,但改 message
结果:5 个提交压缩成 2 个干净的提交。
warning rebase 会改写历史
git rebase 会修改提交的 hash 值(即使内容相同)。已经 push 到远程共享分支的提交,绝对不要 rebase,否则会导致所有人的本地仓库和远程不同步。
安全原则:只对还没有 push 的本地提交,或者只有你自己在用的私有分支使用 rebase。
git tag:版本标记
tag 是 Git 历史上的一个静态指针,通常用来标记重要版本(如上线节点、里程碑版本)。
创建 tag
# 以下两种方式二选一,不要连续创建同名 tag
# 轻量 tag(只是一个指针,没有额外信息)
git tag v1.0.0
# 带注释的 tag(推荐,包含创建者、日期、说明)
git tag -a v1.0.0 -m "数据仓库2024年Q1版本,新增周GMV指标体系"
# 给历史提交打 tag
git tag -a v0.9.0 a3f8b2c -m "2024-01-15上线版本"
查看 tag
# 列出所有 tag
git tag
# 按模式过滤
git tag -l "v1.*"
# 查看某个 tag 的详细信息
git show v1.0.0
# 查看 tag 对应的 commit
git log v1.0.0 --oneline -1
推送 tag 到远程
# push 默认不推送 tag,需要显式指定
git push origin v1.0.0
# 推送所有本地 tag
git push origin --tags
从 tag 创建分支(回溯历史版本)
# 查看 v1.0.0 版本的代码
git checkout v1.0.0 # 进入 detached HEAD 状态
# 基于 v1.0.0 创建新分支
git checkout -b hotfix/v1.0.0-fix v1.0.0
数据项目中的 tag 使用实践
# 每次数仓发版时打 tag
git tag -a dw-v2024q1 -m "2024 Q1数据仓库发版:新增用户分层体系、周GMV指标"
git push origin dw-v2024q1
# 紧急回退时,可以快速找到上个稳定版本
git log --tags --simplify-by-decoration --pretty="format:%D %s"
.gitignore:告诉 Git 哪些文件不要管
为什么需要 .gitignore
数据项目里经常有不应该进版本库的文件:
- 数据库连接密码、API Key 等敏感信息
- 本地环境配置(每个人不一样)
- 自动生成的临时文件
- 大型数据文件(应该用其他方式存储)
- IDE 配置文件
.gitignore 语法
# 这是注释
# 忽略所有 .log 文件
*.log
# 忽略 data 目录内的内容,但保留目录可遍历,以便设置后续例外
data/*
# 忽略根目录下的 config.ini,但不忽略子目录的
/config.ini
# 忽略特定文件
database_config.py
.env
credentials.json
# 用 ! 取消忽略;父目录必须未被排除,否则需先重新包含父目录
!data/sample.csv
# 忽略所有 .csv 文件,但保留 data/sample 目录及其中的 CSV 文件
*.csv
# 后匹配规则优先,需在 *.csv 后重申 sample.csv 的例外
!data/sample.csv
!data/sample/
!data/sample/**/*.csv
数据项目的 .gitignore 模板
# =====================
# 数据文件(不进版本库)
# =====================
*.csv
*.parquet
*.orc
*.avro
*.json.gz
data/raw/
data/processed/
output/
# =====================
# 敏感配置(绝对不能进版本库!)
# =====================
.env
.env.local
.env.*.local
config/local.py
config/database_*.ini
credentials/
secrets/
*.pem
*.key
# =====================
# Python
# =====================
__pycache__/
*.py[cod]
*$py.class
*.pyc
*.pyo
.Python
env/
venv/
.venv/
ENV/
.pytest_cache/
.coverage
htmlcov/
dist/
build/
*.egg-info/
# =====================
# Jupyter Notebook
# =====================
.ipynb_checkpoints/
*.ipynb_checkpoints
# 如果不想忽略 notebook 本身,注释掉下面这行
# *.ipynb
# =====================
# IDE 配置
# =====================
.idea/
.vscode/
*.swp
*.swo
# macOS
.DS_Store
# Windows
Thumbs.db
# =====================
# 日志和临时文件
# =====================
*.log
logs/
tmp/
temp/
.cache/
# =====================
# 依赖目录
# =====================
node_modules/
.node_modules/
把 .gitignore 放在哪里
# 项目根目录(最常见)
/project-root/.gitignore
# 子目录(对该目录及其下级目录生效)
/project-root/data/.gitignore
# 全局 gitignore(对你所有项目生效,适合放 IDE 配置)
git config --global core.excludesfile ~/.gitignore_global
常见问题:文件已经被追踪了,加到 .gitignore 不生效
# 先从 Git 追踪中移除(不删除本地文件)
git rm --cached credentials.json
git rm --cached -r data/raw/
# 然后提交这个变更
git commit -m "chore: 将敏感文件从版本库中移除"
# 现在 .gitignore 的规则对这个文件生效了
warning 一旦密码进了 Git 历史,光删除文件是不够的
如果你不小心提交了密码,仅仅在 .gitignore 中添加并删除文件是不够的,因为历史记录里还存着。需要:
- 立即更换泄露的密码/密钥(第一优先级)
- 在备份并确认清理范围后,优先考虑使用
git filter-repo或BFG Repo Cleaner清理历史;不要把git filter-branch当作常规首选 - 与协作者及仓库管理员协调后,按托管平台要求更新受影响的远程引用;强制推送本身不能清除他人克隆、分叉或托管缓存中的副本 这个过程很麻烦,最好的防范是建仓库第一件事就加好 .gitignore。
查看某个文件为什么被忽略
git check-ignore -v data/raw/orders.csv
# 输出:
# .gitignore:5:data/raw/ data/raw/orders.csv
# 说明是 .gitignore 第 5 行的规则在起作用
实用技巧集锦
查看某行代码是谁写的(git blame)
数据质量问题,想知道某行 SQL 是谁改的:
git blame sql/orders_etl.sql
# 输出:
# a3f8b2c (Zhang San 2024-01-15 10:30:00 +0800 15) WHERE order_status = 'completed'
# 9e1d234 (Li Si 2024-01-20 14:22:33 +0800 16) AND user_type = 'VIP'
VS Code 安装 GitLens 插件后,鼠标悬停在代码行上就能看到谁改的、什么时候改的。
找回误删的提交(git reflog)
git reflog 可查看本地保留的 HEAD 引用更新记录,常用于寻找误操作前的提交;它不是所有操作的永久记录,也不会随普通推送同步到远程。
# 查看本地尚未过期的 HEAD 更新记录(可能包含 reset 前的提交)
git reflog
# 输出:
# a3f8b2c (HEAD -> main) HEAD@{0}: commit: fix: 修复NULL处理
# 9e1d234 HEAD@{1}: reset: moving to 9e1d234
# 7c2f891 HEAD@{2}: commit: feat: 添加周GMV指标(已被 reset 删除)
# 先创建救援分支,保留找到的提交及其历史
git branch rescue/recovered-commit 7c2f891
# 或者:在确认工作区干净、提交适合移植且无重复应用后,应用到当前分支
git cherry-pick 7c2f891
reflog 条目的默认过期设置通常为 90 天,不可达提交对应条目通常为 30 天,具体取决于配置和维护操作。能否恢复还取决于条目及提交对象是否仍存在;未提交且未暂存的工作区改动通常不能靠 reflog 恢复,不能把保留期限当作恢复保证。
查看某个提交的内容
# 查看某次提交改了什么
git show a3f8b2c
# 查看某次提交的某个文件
git show a3f8b2c:sql/orders_etl.sql
# 把某个历史版本的文件提取出来
git show a3f8b2c:sql/orders_etl.sql > /tmp/orders_etl_old.sql
统计代码贡献
# 查看每个人的提交数量
git shortlog -sn
# 查看某人的提交历史
git log --author="Zhang San" --oneline
小结
| 技巧 | 使用场景 | 核心命令 |
|---|---|---|
| stash | 临时保存未完成工作 | git stash / git stash pop |
| cherry-pick | 精准挑选某个提交 | git cherry-pick <hash> |
| rebase | 整理线性历史 | git rebase main |
| 交互式 rebase | 清理提交记录 | git rebase -i HEAD~N |
| tag | 标记重要版本 | git tag -a v1.0 -m "..." |
| .gitignore | 排除不需要追踪的文件 | 创建 .gitignore 文件 |
| blame | 查看谁改了哪行 | git blame <file> |
| reflog | 恢复误删提交 | git reflog |
掌握检查
- [ ] 能用
git stash push -m "描述"保存当前未完成的工作,切到另一个任务后用git stash pop恢复 - [ ] 理解
git rebase main的含义:把当前分支的提交"嫁接"到 main 最新节点上,让历史更线性 - [ ] 知道
git cherry-pick <hash>的场景:把某个分支上的单个提交精准应用到当前分支 - [ ] 项目第一件事知道先创建好
.gitignore,并用git check-ignore -v验证规则是否生效 - [ ] 能用
git reflog查找仍有记录的提交,并用git branch rescue/recovered-commit <hash>建立救援分支;知道git checkout <hash>只会进入该提交的 detached HEAD 状态
实践练习
基础题:在开发分支上用 git stash 保存 3 个未完成改动,切到 main 做一个紧急修复提交,再切回开发分支恢复 stash,验证改动保留完整。
进阶题:用 git log -S "某段代码" 找到某个特性是在哪次提交引入的,然后用 git cherry-pick 把这个提交应用到另一个分支(模拟"热修复移植"场景)。
下一步:数据项目Git实践 - 把所有技能融合到数据工程实际工作中。