配置与初始化
全 局与本地配置
Git 在三个层级存储配置:系统级(/etc/gitconfig)、全局级(用户的 ~/.gitconfig)和本地级(每个仓库的 .git/config)。低层级会覆盖高层级。在提交之前始终设置 user.name 和 user.email,否则提交将使用无意义的默认身份。将 init.defaultBranch 设置为 main 可以避免使用已弃用的 master 默认值,并与现代约定保持一致。
# set user identity (required for commits)
git config --global user.name "Alice Lee"
git config --global user.email "[email protected]"
# set default editor and branch name
git config --global core.editor "code --wait"
git config --global init.defaultBranch main
# view all settings and their origin
git config --list --show-origin
# edit config files directly
git config --global --edit # ~/.gitconfig
git config --edit # repo .git/config创建与克隆仓库
git init 通过添加一个隐藏的 .git 目录来存储所有版本数据,从而创建一个空仓库。git clone 复制远程仓库及其完整历史记录。当只需要最新快照时(例如 CI 构建),使用 --depth 1 进行浅克隆——它能大幅减少下载量。--single-branch 避免获取不相关的分支。
# create a new repo from scratch in current dir
mkdir my-app && cd my-app
git init
# clone a remote repository
git clone https://github.com/user/repo.git
# clone into a specific folder name
git clone https://github.com/user/repo.git my-folder
# clone a single branch (saves bandwidth)
git clone -b dev --single-branch https://github.com/user/repo.git
# shallow clone: only the latest commit
git clone --depth 1 https://github.com/user/repo.git别名与快捷方式
别名允许您为常用命令或命令序列定义更短的名称。它们存储在 gitconfig 的 [alias] 部分。以 ! 开头的别名作为 shell 命令运行,可实现复杂的工作流。上面的 lg 别名会生成一个紧凑的可视化历史图,对于理解分支结构非常有用。
# create shortcuts for common commands
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.lg "log --oneline --graph --all"
# now use them
git co main
git lg
# alias that runs an external command (starts with !)
git config --global alias.unstage "reset HEAD --"
git unstage file.txt帮助与文档
Git 附带全面的内置文档。git help <command> 在您的分页器中打开 man 页面。-h 标志提供快速的单屏选项摘要。指南(git help -g)包括教程、术语表和日常工作流参考——非常适合初学者学习术语。
# open the full manual for a command
git help commit
git commit --help
# show a concise synopsis
git commit -h
# list all git commands
git help -a
# list all guides (tutorial, glossary, etc.)
git help -g
git help glossary.gitignore 模式
.gitignore 告诉 Git 哪些文件应排除在版本控制之外——对于构建产物、依赖项和密钥至关重要。模式使用 glob 语法;尾部斜杠匹配目录。以 ! 为前缀会否定模式,强制跟踪某个文件。.gitignore 本身应该被提交,以便团队共享忽略规则。已被跟踪的文件不受新忽略模式的影响——先用 git rm --cached 移除它们。
# .gitignore file — patterns for files to skip
node_modules/
*.log
.env
.env.local
dist/
build/
# but track this specific file even if it matches
!important.log
# ignore all .txt files in build/ but not subdirs
build/*.txt
# ignore everything in a folder except one file
secrets/*
!secrets/template.env暂存与提交
状态与暂存
Git 使用两步模型:更改先进入暂存区(索引),然后才被提交。git status 显示哪些内容被修改、暂存和未跟踪。git add -p 允许您暂存补丁的各个块——对于将杂乱的工作树拆分为专注、有逻辑的提交非常有用。使用 git restore --staged(现代命令)取消暂存而不丢失您的编辑。
# see current state of working tree
git status
git status -s # short format
git status -sb # short + branch info
# stage changes
git add file.txt # one file
git add src/ # a directory
git add . # all changes in repo
git add -p # stage interactively by hunk
# unstage a file (keep changes in working dir)
git restore --staged file.txt
git reset HEAD file.txt # older syntax提交更改
提交记录暂存更改的快照。以祈使语气编写消息('add feature' 而不是 'added feature')。-a 标志只暂存已跟踪的文件——新文件仍需要 git add。--amend 重写最后一次提交;用它来修复拼写错误或添加遗忘的文件,但绝不要修改您已经推送到共享分支的提交。
# commit staged changes with a message
git commit -m "feat: add login form"
# multi-line commit message
git commit -m "feat: add login form" -m "Closes #42"
# stage tracked files AND commit in one step
git commit -am "fix: correct typo"
# amend the previous commit (keep message)
git commit --amend --no-edit
# amend and edit the message
git commit --amend -m "feat: add login form (v2)"提交消息约定
Conventional Commits 是一种广泛采用的规范化提交消息规范。类型前缀(feat、fix、docs 等)支持自动生成变更日志和语义化版本控制。! 标记表示破坏性变更。空行分隔主题(<=50 字符)和正文,另一个空行分隔如 'Closes #123' 的页脚,后者会在 GitHub 上自动关闭 issue。
# Conventional Commits format
feat: add user registration endpoint
fix: resolve crash on empty cart
docs: update API reference
style: format login component
refactor: extract validation logic
test: add unit tests for parser
chore: upgrade dependencies
# with scope and breaking change marker
feat(api): add pagination to list endpoint
fix!: change default port (BREAKING CHANGE)
# with body and footer
feat: add dark mode
Implement theme toggle using CSS variables.
Closes #128使用 log 查看历史
git log 是探索历史的主要工具。--oneline --graph --all 是一目了然理解分支结构的最有用组合。您可以用 --grep 按作者、日期范围或消息内容过滤。--stat 显示哪些文件更改以及多少行,而 --patch 显示每次提交的完整差异。
# full history with details
git log
git log --oneline # one line per commit
git log --oneline --graph --all # visual branch graph
git log -n 5 # last 5 commits
# filter by author, date, or message
git log --author="Alice"
git log --since="2 weeks ago"
git log --grep="fix"
# show files changed in each commit
git log --stat
git log --patch # full diffsDiff 与 Show
git diff 比较快照——不带参数时显示未暂存的更改与索引的差异。--staged 比较索引与 HEAD。git show 显示单个提交的元数据和补丁。HEAD:path 语法允许您查看任何文件在任何提交时的内容,而无需检出,这对于恢复旧版本或检查历史记录非常方便。
# compare working tree, index, and commits
git diff # unstaged changes
git diff --staged # staged but uncommitted
git diff HEAD # all changes vs last commit
git diff main feature # compare two branches
git diff abc1234 def5678 # compare two commits
# inspect a single commit
git show HEAD # latest commit diff + metadata
git show abc1234
git show HEAD:file.txt # view a file at a given commit分支与合并
创建与切换分支
Git 中的分支是指向提交的轻量级指针——创建分支几乎是瞬时的。git switch(Git 2.23+)是 checkout 的现代、更安全的替代方案,用于更改分支,将 checkout 保留用于恢复文件。-d 拒绝删除未合并的分支(保护您的工作);-D 强制删除。合并后始终删除分支以保持分支列表整洁。
# list branches
git branch # local branches
git branch -a # local + remote
git branch -vv # with tracking info
# create and switch
git branch feature # create only
git checkout feature # switch to it
git checkout -b feature # create + switch (classic)
git switch -c feature # create + switch (modern)
# delete branches
git branch -d feature # safe delete (merged only)
git branch -D feature # force delete合并分支
当目标没有新提交时,快进合并只是将分支指针向前移动——产生线性历史。--no-ff 强制创建合并提交,保留分支曾经存在的事实(对于功能跟踪很有用)。--squash 将所有分支提交合并为一个暂存的更改,然后您提交一次——非常适合在集成到 main 之前整理嘈杂的功能历史。
# merge feature into main
git checkout main
git merge feature
# fast-forward merge (default when possible)
# main just moves forward to feature's commit
# create a merge commit (preserves branch history)
git merge --no-ff feature -m "Merge feature branch"
# squash all feature commits into one
git merge --squash feature
git commit -m "feat: add feature (squashed)"
# abort a merge with conflicts
git merge --abort解决合并冲突
当相同的行在两个分支上被不同地更改时,就会发生冲突。Git 插入冲突标记(<<<<<<<、=======、>>>>>>>)显示双方。通过编辑文件到所需的最终状态来解决,然后 git add 标记为已解决。提交以完成合并。git mergetool 启动可视化差异工具。如果您感到不知所措,git merge --abort 返回到合并前的状态。
# a conflict produces markers in the file
# <<<<<<< HEAD
# my changes (current branch)
# =======
# their changes (incoming branch)
# >>>>>>> feature
# edit the file to resolve, then:
git add resolved-file.txt
git commit # finalize the merge
# use a merge tool
git mergetool
# see which files conflict
git status
# abandon the merge
git merge --abort变基
Rebase 将您的分支提交重放到另一个分支之上,产生没有合并提交的线性历史。交互式变基(-i)是一个强大的工具:squash 合并提交,reword 编辑消息,drop 移除提交,edit 暂停以让您修改提交。绝不要变基已经推送并共享的提交——它会重写历史并破坏队友的仓库。仅在自己的本地分支上使用 rebase。
# rebase feature onto main (replay feature commits)
git checkout feature
git rebase main
# interactive rebase — rewrite last 5 commits
git rebase -i HEAD~5
# options: pick, reword, squash, fixup, drop, edit
# continue, skip, or abort during a rebase
git rebase --continue
git rebase --skip
git rebase --abort
# rebase while pulling
git pull --rebase origin mainCherry-Pick 与 Reflog
Cherry-pick 将另一个分支的单个提交应用到您当前的分支——对于在不合并整个分支的情况下回溯修复 bug 很有用。reflog 是每次 HEAD 移动(提交、检出、重置)的本地日志,保留约 90 天。它是您的安全网:即使在破坏性的 reset 之后,您也可以在 reflog 中找到旧的提交哈希并恢复它。Reflog 数据仅在本地,从不推送。
# apply a specific commit onto current branch
git cherry-pick abc1234
git cherry-pick abc1234 def5678 # multiple commits
git cherry-pick abc1234..def5678 # range (exclusive start)
# reflog: record of where HEAD has been
git reflog
git reflog show feature
# recover a 'lost' commit
git reset --hard HEAD@{2}
# view a branch's history of moves
git reflog show main远程仓库
管理远程仓库
远程是对另一个仓库的命名引用,通常是您的 fork 的 origin 和原始项目的 upstream。git remote -v 显示 fetch 和 push URL。GitHub 上的 fork 使用 upstream 远程与原始项目同步:从 upstream fetch,merge 或 rebase,然后 push 到 origin。set-url 对于在 HTTPS 和 SSH 身份验证之间切换很方便。
# list configured remotes
git remote -v
git remote show origin
# add a remote
git remote add origin https://github.com/user/repo.git
# add an upstream remote (for forks)
git remote add upstream https://github.com/original/repo.git
# rename or remove
git remote rename origin upstream
git remote remove origin
# change a remote URL (e.g. HTTPS to SSH)
git remote set-url origin [email protected]:user/repo.gitFetch、Pull 与 Push
fetch 下载远程数据但不触碰您的工作树——在合并前检查是安全的。pull = fetch + merge(或使用 --rebase 进行 rebase)。新分支的第一次 push 需要 -u 来设置跟踪,以便未来的 git push/pull 无需参数即可工作。--force-with-lease 是 --force 的安全替代方案:仅当没有其他人在此期间推送时才覆盖远程,防止意外覆盖队友的工作。
# download remote changes without merging
git fetch origin
git fetch --all --prune # all remotes, drop deleted branches
# fetch + merge in one step
git pull
git pull --rebase # rebase instead of merge
# push local commits to remote
git push origin main
git push -u origin feature # push + set upstream tracking
git push # subsequent pushes (uses tracking)
# force push (rewrites remote history — DANGER)
git push --force-with-lease # safer than --force跟踪与同步分支
跟踪分支将本地分支链接到远程分支,以便 git pull 和 git push 知道在哪里 fetch/push 而无需指定。-u(--set-upstream-to 的简写)在第一次 push 时设置。当队友删除远程分支时,您的本地远程跟踪引用会变得过时——git remote prune origin 清理它们。git fetch --prune 在 fetch 期间自动执行此操作。
# set upstream for current branch
git branch -u origin/main
git branch --set-upstream-to=origin/main
# create a local branch tracking a remote one
git checkout -b feature origin/feature
git switch feature # auto-detects tracking branch
# delete a remote branch
git push origin --delete feature
# list all remote-tracking branches
git branch -r
git remote prune origin # remove stale remote refsPull Request 工作流
标准的 GitHub 流程:创建功能分支,推送它,打开 Pull Request 进行审查,合并后删除分支。对于 fork,upstream 远程允许您从原始仓库拉取更改。定期保持您的 fork 的 main 与 upstream 同步可以防止以后出现大型、痛苦的合并。许多团队启用'合并时自动删除分支'以保持分支列表整洁。
# typical feature workflow
git checkout -b feature/login
# ... make changes, commit ...
git push -u origin feature/login
# create PR on GitHub, then after merge:
git checkout main
git pull origin main
git branch -d feature/login
# sync a fork with its upstream
git fetch upstream
git checkout main
git merge upstream/main
git push origin main裸仓库与镜像
裸仓库没有工作树——它只存储 .git 数据。裸仓库用于服务器(如自托管 Git)上,作为多人推送和拉取的中央远程。--mirror 克隆所有内容,包括远程跟踪引用,用于备份或在主机之间迁移仓库。--all 推送所有分支;--tags 推送所有标签。
# create a bare repo (no working tree) — for servers
git init --bare project.git
# mirror clone (full copy including all refs)
git clone --mirror https://github.com/user/repo.git
# push all branches and tags
git push --all
git push --tags
# push a specific branch to a specific remote
git push origin local-name:remote-name撤销更改
Reset:Soft、Mixed、Hard
reset 移动当前分支指针。--soft 保持您的更改已暂存(只有提交被撤销)——非常适合重新提交。--mixed(默认)取消暂存但保留工作树更改。--hard 永久丢弃一切——您唯一的恢复方式是 reflog。绝不要对已推送的提交使用 --hard,因为它会重写共享历史并导致分歧。
# reset moves HEAD and optionally the index/worktree
git reset --soft HEAD~1 # undo commit, keep staged
git reset --mixed HEAD~1 # undo commit + unstage (DEFAULT)
git reset --hard HEAD~1 # undo commit + discard changes
# reset to a specific commit
git reset --hard abc1234
# reset a single file to HEAD
git reset HEAD file.txt # unstage
git checkout -- file.txt # discard working changesRevert(安全撤销)
与 reset(重写历史)不同,revert 添加一个新提交来反转目标提交——对于共享分支是安全的,因为历史被保留。这是撤销已推送更改的正确方式。还原合并提交需要 -m 1 来指定保留哪个父行(1 = 您合并到的分支)。
# revert creates a NEW commit that undoes another
git revert abc1234
git revert HEAD # undo last commit
# revert a range
git revert HEAD~3..HEAD
# revert without committing (stage the inverse)
git revert --no-commit abc1234
# revert a merge commit
git revert -m 1 abc1234 # -m 1 = keep main branch parentRestore 与 Clean
git restore(Git 2.23+)是现代的、专注于工作树操作的命令,将关注点与 checkout 分离。--staged 取消暂存而不触碰工作更改。git clean 移除未跟踪的文件——始终先用 -n(试运行)预览将要删除的内容。-x 是激进的:它还删除 gitignored 文件,这对于干净构建很有用,但可能擦除密钥或构建输出。
# discard unstaged changes in a file
git restore file.txt
git checkout -- file.txt # older syntax
# unstage a file (keep working changes)
git restore --staged file.txt
# restore a file from a specific commit
git restore --source=abc1234 file.txt
# remove untracked files
git clean -n # dry run (preview)
git clean -fd # remove untracked files + dirs
git clean -fdx # also remove ignored filesAmend 与 Fixup
--amend 重写最后一次提交——便于修复拼写错误或添加遗忘的文件,但绝不要修改已推送的提交。fixup 工作流很优雅:在您注意到小问题时创建 fixup 提交,然后 git rebase -i --autosquash 自动重新排序并将它们压缩到目标提交中。这保持历史整洁,同时让您增量地提交小修复。
# amend the last commit (message + content)
git add forgotten-file.txt
git commit --amend --no-edit
# change only the commit message
git commit --amend -m "better message"
# create a fixup commit (for later autosquash)
git commit --fixup=abc1234
# squash fixups during interactive rebase
git rebase -i --autosquash abc1234~1Reflog 恢复
reflog 是您的安全网。它记录每次提交、检出、reset 和 rebase——甚至是'销毁'提交的操作。条目保留约 90 天。如果您意外地 reset --hard 或删除了分支,在 reflog 中找到孤立的提交哈希并 reset 到它或创建一个指向它的新分支。reflog 仅在本地,因此即使离线也能工作。
# reflog records every HEAD movement
git reflog
# abc1234 HEAD@{0}: reset: moving to abc1234
# def5678 HEAD@{1}: commit: feat: add x
# ghi9012 HEAD@{2}: checkout: moving to feature
# recover a 'lost' commit
git reset --hard def5678
# recover a deleted branch
git branch recovered-feature def5678
# reflog for a specific branch
git reflog show featureStash 与工作流
暂存更改
Stash 将未提交的更改搁置, 以便您可以在干净的工作树上切换分支或拉取更新。apply 保留 stash 在列表中(如果您想将其应用到多个分支很有用);pop 应用并移除它。Stash 是一个 LIFO 栈,由 stash@{N} 引用。谨慎使用 clear——它会永久丢弃所有 stash。
# save uncommitted changes (reverts working tree to HEAD)
git stash
git stash push -m "wip: login form" # with a message
# list, show, apply
git stash list
git stash show -p stash@{0} # show diff
git stash apply # apply latest, keep stash
git stash pop # apply latest, drop stash
# apply a specific stash
git stash apply stash@{2}
# drop a stash
git stash drop stash@{0}
git stash clear # drop ALL stashes部分与选择性 Stash
这些标志对 stash 的内容提供细粒度控制。--keep-index 在您暂存了逻辑提交但只想测试那些更改时很有用——stash 其余部分,运行测试,然后 pop。-u 包含未跟踪的文件(否则它们留在您的工作树中)。-p 让您选择特定的块,镜像 git add -p。
# stash only staged changes
git stash --staged
# stash only unstaged changes (keep staged)
git stash --keep-index
# stash interactively by hunk
git stash -p
# stash including untracked files
git stash -u
git stash --include-untracked
# stash everything (even ignored)
git stash -aStash 分支与创建
git stash branch 在 stash 的原始父提交处创建一个新分支并在那里应用 stash——当 stash 由于冲突不再干净地应用到当前分支时非常完美。您还可以使用 show -p 将 stash 导出为补丁文件以进行归档或共享。Stash 是本地的,从不推送,因此不应用于长期存储。
# create a branch from a stash (great for conflicts)
git stash branch feature/wip stash@{0}
# create a commit from a stash without applying
git stash store -m "saved wip" stash@{0}
# apply a stash to a different branch
git checkout other-branch
git stash apply stash@{0}
# inspect stash contents
git stash show stash@{0} --stat
git stash show -p stash@{0} > patch.diffGit 工作流模型
GitHub Flow 是最简单的:一个 main 分支,带 PR 的功能分支,合并时部署——非常适合持续部署。Git Flow(Vincent Driessen 的模型)添加了 develop、release 和 hotfix 分支,用于结构化的发布管理——适合有版本的产品。Trunk-Based Development 使用非常短寿命的分支,受到高性能 DevOps 团队的青睐,以实现最大集成速度。
# GitHub Flow (simple, popular)
# main is always deployable; feature branches + PRs
git checkout -b feature/x
git push -u origin feature/x
# open PR, review, merge to main, deploy
# Git Flow (structured, with release branches)
# main: production releases
# develop: integration branch
# feature/*: features off develop
# release/*: release prep
# hotfix/*: urgent fixes off main
# Trunk-Based Development
# short-lived branches (1-2 days), frequent rebase on main子模块
子模块将一个 Git 仓库嵌入另一个仓库中——对于在项目之间共享库同时保持其独立版本控制很有用。父仓库存储指向子模块特定提交的指针。克隆默认不获取子模块内容;--recurse-submodules 一步完成。子模块可能很麻烦;对于更简单的依赖管理,考虑 Git 子树或包管理器。
# add another repo as a submodule
git submodule add https://github.com/user/lib.git libs/lib
# clone a repo with its submodules
git clone --recurse-submodules https://github.com/user/repo.git
# initialize submodules in an existing clone
git submodule update --init --recursive
# update submodules to their latest remote commit
git submodule update --remote
# record a submodule pointer change
git add libs/lib
git commit -m "chore: bump lib submodule"检查与调试
Blame 与 Annotate
git blame(也称为 annotate)显示文件每一行的提交和作者——对于理解代码为何看起来如此至关重要。-L 限制为行范围,对于大文件很有用。-w 忽略纯空白更改,-C 检测从另一个文件移动或复制的代码,为您提供行真正起源的更准确历史。
# show who last changed each line
git blame file.txt
git blame -L 10,20 file.txt # only lines 10-20
git blame -e file.txt # show author email
git blame -w file.txt # ignore whitespace changes
git blame -C file.txt # detect moved lines across files
# GUI annotation in some editors
git gui blame file.txtBisect(二分搜索 Bug)
bisect 通过历史执行二分搜索以精确定位引入 bug 的确切提交。您标记一个已知良好和已知坏的提交;Git 检出中点,您测试,然后标记好或坏,每次将范围减半。使用脚本,整个过程完全自动化——对于大型历史中的回归问题节省大量时间。
# find the commit that introduced a bug
git bisect start
git bisect bad # current commit is broken
git bisect good v1.0.0 # v1.0.0 was working
# Git checks out a midpoint; test it, then:
git bisect good # or git bisect bad
# automate with a script that exits non-zero on bug
git bisect start HEAD v1.0.0 -- npm test
# finish and return to original branch
git bisect reset
# view the bisect log
git bisect log搜索代码与历史
git grep 搜索工作树中已跟踪的文件——比 grep -r 更快,因为它使用索引。-S 'pickaxe' 选项查找添加或删除特定字符串的提交,对于追踪函数或 bug 何时引入非常有价值。-G 类似但匹配差异中任何位置的正则表达式。结合 --author 和 --since 过滤器,您可以精确定位历史中的任何更改。
# search working tree for a string
git grep "TODO"
git grep -n "TODO" -- "*.js"
git grep -i "error" src/
# search across all commits (history)
git log -S "functionName" --oneline # pickaxe: when added/removed
git log -G "functionName.*\(" --oneline # regex match in diffs
# search commit messages
git log --grep="fix.*login" --oneline
git log --author="Alice" --since="1 week ago"FSCK 与悬空对象
git fsck(文件系统检查)验证对象数据库的完整性,可以找到悬空提交——当 reflog 不足时对于恢复很有用。git gc 重新组织和压缩对象以节省磁盘空间;--prune=now 立即移除不可达对象。Git 会定期自动 gc,但手动运行可以缩小大型仓库。--aggressive 重新计算增量以获得最大压缩。
# check repository integrity
git fsck --full
git fsck --unreachable
# find dangling (unreachable) commits & blobs
git fsck --lost-found
# garbage collect and prune old objects
git gc
git gc --prune=now
git gc --aggressive
# count objects and disk usage
git count-objects -vArchive 与 Bundle
git archive 导出提交的干净快照,不包含 .git 目录——非常适合分发发布或将源代码发送给不需要历史的人。git bundle 将仓库(或一系列提交)打包成单个文件,可以从中克隆或 fetch——非常适合在气隙网络之间传输仓库,或当您无法使用远程服务器时通过电子邮件传输。
# export a snapshot as a tar/zip (no .git history)
git archive --format=zip --output=app.zip HEAD
git archive --format=tar HEAD | gzip > app.tar.gz
git archive -o release.zip v1.0.0
# bundle a repo (with history) into a single file
git bundle create repo.bundle --all
git bundle create patch.bundle main..feature
# clone from a bundle (offline transfer)
git clone repo.bundle new-repo
git fetch patch.bundle feature高级技术
钩子
钩子是在特定点自动运行的脚本。客户端钩子(pre-commit、commit-msg、pre-push)强制执 行本地策略,如 lint 或测试。服务端钩子(pre-receive、post-receive)在远程上运行,可以强制执行分支保护或触发 CI/CD。.git/hooks 目录包含以 .sample 结尾的示例脚本——重命名以激活。Husky 或 pre-commit 框架等工具在仓库本身中管理钩子以保持团队一致性。
# client-side hooks live in .git/hooks/
# pre-commit runs before a commit is created
# commit-msg validates the commit message
# pre-push runs before pushing to remote
# sample pre-commit hook (.git/hooks/pre-commit)
#!/bin/sh
npm run lint || exit 1
npm test || exit 1
# server-side hooks (on the remote)
# pre-receive, update, post-receive
# make a hook executable
chmod +x .git/hooks/pre-commitWorktree
Worktree 允许您为单个仓库拥有多个工作目录,每个目录在不同的分支上——无需克隆。当您需要在保持功能分支的工作树完整的同时处理热修复,或在编辑另一个分支的同时在一个分支上运行长时间构建时,这非常有价值。所有 worktree 共享相同的 .git 对象数据库,因此磁盘使用量最小。
# create a second working tree of the same repo
git worktree add ../app-feature feature/x
# list worktrees
git worktree list
# create a worktree with a new branch
git worktree add -b hotfix/y ../app-hotfix main
# remove a worktree
git worktree remove ../app-feature
# prune stale worktree metadata
git worktree prune过滤与重写历史
当提交了密钥或大文件时,重写历史是必要的。git filter-repo 是 git filter-branch 的现代、快速替代品。BFG Repo-Cleaner 是用于移除大文件或密码模式的用户友好替代方案。重写后,您必须强制推送并通知所有协作者重新克隆——旧提交保留在他们的 reflog 中直到过期。始终立即轮换任何泄露的密钥。
# remove a file from ALL of history (sensitive data)
git filter-repo --path secrets.env --invert-paths
# (install git-filter-repo; the old filter-branch is deprecated)
# rewrite author info across all commits
git filter-repo --mailmap mailmap.txt
# split a subdirectory into its own repo
git filter-repo --subdirectory-filter libs/lib
# the simpler BFG Repo-Cleaner (Java tool)
bfg --delete-files *.env
bfg --replace-text passwords.txt稀疏检出与部分克隆
部分克隆(--filter)获取提交和树,但延迟下载 blob(文件内容)——在您访问时——大幅加快大型仓库的克隆速度。稀疏检出将您的工作树限制为特定目录,因此您只看到您工作的部分。它们一起使庞大的 monorepo 可管理:克隆快速,您的工作树保持小。
# partial clone: download history on demand
git clone --filter=blob:none https://github.com/user/huge-repo.git
# sparse checkout: only certain directories
git clone --no-checkout https://github.com/user/repo.git
cd repo
git sparse-checkout init --cone
git sparse-checkout set src docs
git checkout main
# add a directory to the sparse set later
git sparse-checkout add tests
# disable sparse checkout (fetch everything)
git sparse-checkout disableReflog、Refspec 与 Notes
Refspec 提供对 fetch/push 期间如何映射引用的显式控制——对于不寻常的工作流(如将本地功能分支推送到远程 main)很有用。git notes 将元数据附加到提交而不重写它,这对于审查评论或 CI 链接很方便。Notes 存储在单独的引用(refs/notes/commits)中,必须显式推送和获取。
# refspec: explicit fetch/push mapping
git fetch origin "refs/heads/*:refs/remotes/origin/*"
git push origin "refs/heads/feature:refs/heads/main"
# add a note to a commit (without changing it)
git notes add -m "Reviewed by Alice" abc1234
git notes show abc1234
git log --show-notes
# configure where notes are stored
git config --global notes.displayRef "refs/notes/*"
# push notes to a remote
git push origin refs/notes/commits最佳实践与技巧
提交卫生
好的提交是小、原子和自包含的:每次提交一个逻辑更改。这使代码审查更容易,二分更快,还原更精确。使用 git add -p 只暂存与单个关注点相关的块。祈使语气消息('add' 而不是 'added')读作对代码库的指令。在合并之前将功能分支 rebase 到最新的 main 上可以保持历史线性且可理解。
# commit small, focused, logical units
git add -p # stage only relevant hunks
git commit -m "fix: handle null user in login"
# write clear, imperative messages
# GOOD: "feat: add password reset endpoint"
# BAD: "stuff", "fix", "WIP", "asdf"
# one concern per commit
git add login.ts && git commit -m "feat: add login"
git add styles.css && git commit -m "style: format login"
# rebase before merging to keep history clean
git fetch && git rebase origin/main分支保护与代码审查
分支保护规则防止强制推送,要求 PR 审查,并在通过 CI 时门控合并——对团队安全至关重要。 要求线性历史会强制 rebase 或 squash 合并,保持历史可读。签名提交(GPG 或 SSH)证明作者身份并防止冒充。这些设置在托管平台(GitHub/GitLab)上配置,而不是在 Git 本身中,但它们在整个组织范围内强制执行良好的 Git 卫生。
# on GitHub/GitLab: protect the main branch
# - require pull request reviews
# - require status checks (CI) to pass
# - require linear history (no merge commits)
# - dismiss stale reviews on push
# - require signed commits
# enforce via config (GitHub CLI)
gh api repos/:owner/:repo/branches/main/protection \
-X PUT -f required_status_checks[strict]=true
# sign commits and verify on receive
git config --global commit.gpgsign true
git config --global gpg.format ssh忽略与密钥管理
提交的密钥是最常见的 Git 安全事件。一旦推送,假设密钥已泄露——立即轮换它,即使从历史中移除后也是如此,因为克隆和 fork 保留它。预防是最好的:.gitignore 密钥,使用环境变量或密钥管理器(Vault、AWS Secrets Manager),并安装 git-secrets 或 pre-commit 钩子,在每次提交前扫描 API 密钥和密码。
# never commit secrets — use .gitignore
echo ".env" >> .gitignore
echo "*.pem" >> .gitignore
echo "secrets/" >> .gitignore
# if you accidentally committed a secret:
# 1. rotate/revoke the secret immediately
# 2. remove from history
git filter-repo --path .env --invert-paths
git push --force-with-lease
# use environment files or secret managers
echo "API_KEY=$API_KEY" > .env.local # gitignored
# load via dotenv in your app
# use git-secrets to scan pre-commit
git secrets --install
git secrets --register-aws性能技巧
大型仓库可能很慢。fsmonitor 和 untrackedcache 通过缓存文件系统状态使 git status 快得多。浅克隆和部分克隆减少初始下载。定期运行 git gc 以压缩对象。--no-verify 跳过 pre-commit 和 commit-msg 钩子——对于紧急情况很有用,但不应成为习惯,因为它绕过了团队的质量检查。
# speed up status on large repos
git config --global core.fsmonitor true
git config --global core.untrackedcache true
# shallow clone for CI
git clone --depth 1 https://github.com/user/repo.git
# partial clone for huge monorepos
git clone --filter=blob:none https://github.com/user/monorepo.git
# periodic garbage collection
git gc --prune=now
# disable slow hooks temporarily
git commit --no-verify常见陷阱
避免这些经典错误:绝不要强制推送到共享分支(--force-with-lease 是安全选项);绝不要 rebase 其他人可能已经基于其工作的提交;绝不要在分离 HEAD 上提交而不先创建分支。对大二进制文件使用 Git LFS——否则它们会永久膨胀历史。按操作系统配置行尾(autocrlf)以避免跨平台团队中嘈杂的仅空白差异。
# PITFALL: force-pushing to shared branches
git push --force origin main # DON'T — use --force-with-lease
# PITFALL: rebasing pushed commits
# rebase only your local, unpushed branches
# PITFALL: committing on detached HEAD
git checkout v1.0.0
# changes here are not on a branch — create one:
git checkout -b fix/v1.0.1
# PITFALL: large binary files bloating history
# use Git LFS for binaries
git lfs install
git lfs track "*.psd"
git add .gitattributes
# PITFALL: CRLF/LF line endings across OS
git config --global core.autocrlf input # on Linux/Mac
git config --global core.autocrlf true # on WindowsGit Flow 工作流
Git Flow 分支模型
Git Flow 是用于基于发布的项目的严格分支模型。main 始终持有生产代码;develop 持有集成工作。功能分支从 develop 分出并合并回去。发布分支从 develop 分出,稳定后合并到 main(带标签)和 develop。热修复分支从 main 分出并合并到 main 和 develop。此模型适合有计划发布的项目(桌面应用、本地部署软件)。对于持续部署,GitHub Flow(main + 功能分支)更简单。git-flow CLI 工具自动化了分支舞蹈。
# Git Flow uses long-lived branches:
# main → production-ready code
# develop → latest delivered development changes
# feature/* → new features (branched from develop)
# release/* → release preparation (branched from develop)
# hotfix/* → urgent production fixes (branched from main)
# Initialize git-flow (interactive setup)
git flow init
# Start a new feature
git flow feature start my-feature
# Creates feature/my-feature from develop
# Finish a feature (merges to develop, deletes branch)
git flow feature finish my-feature
# Publish a feature to remote
git flow feature publish my-feature
# Start a release
git flow release start 1.2.0
# Creates release/1.2.0 from develop
# Finish a release (merges to main AND develop, tags)
git flow release finish 1.2.0GitHub Flow(更简单)
GitHub Flow 是最简单的工作流:main 始终可部署,功能分支短寿命,一切都通过 Pull Request 合并。没有 develop 分支或发布分支——main 持续部署。这对于具有持续部署的 Web 应用很有效。关键规则:绝不要直接提交到 main;始终使用 PR 进行审查。保持功能分支小而短命(天,而不是周)。合并后删除分支以保持仓库整洁。此模型优先考虑速度和简单性,而不是 Git Flow 的结构化发布管理。
# GitHub Flow: only main + feature branches
# 1. Create branch from main
git checkout main
git pull origin main
git checkout -b feature/add-login
# 2. Commit changes
git add .
git commit -m "feat: add login page"
# 3. Push to remote
git push -u origin feature/add-login
# 4. Open Pull Request on GitHub
# 5. Review, discuss, get approval
# 6. Merge to main (via GitHub UI or CLI)
git checkout main
git pull origin main
git branch -d feature/add-login # delete local branch
# 7. Deploy from main (continuous deployment)Trunk-Based Development
Trunk-Based Development 是最极端的:开发者直接提交到 main(或在 24 小时内合并的非常短寿命的分支)。这实现了真正的持续集成——每个人都在不断集成。不完整的功能使用功能标志(已部署但隐藏)而不是长寿命的分支。这需要强大的 CI/CD、全面的测试和功能标志基础设施。被 Google、Facebook 和 Netflix 使用。好处:没有合并地狱,快速反馈,小更改。挑战:需要纪律、测试覆盖和功能标志管理。最适合具有强大 CI/CD 的经验丰富的团队。
# Trunk-Based: everyone commits to main (trunk)
# Short-lived feature branches optional
# Direct commit to main (small teams)
git checkout main
git pull
# make changes
git add . && git commit -m "fix: typo in header"
git push
# With short-lived branches (larger teams)
git checkout -b quick-fix
git commit -m "fix: validation bug"
git push
# PR merged within 24 hours
# Feature flags enable incomplete code on main
# Code is deployed but hidden behind a flag
if (featureFlag.isEnabled("new-checkout")) {
showNewCheckout();
} else {
showOldCheckout();
}Fork 与 Pull 工作流
Fork 与 Pull 工作流是开源的标准。贡献者 fork 仓库(创建自己的副本),将分支推送到他们的 fork,并向原始(upstream)仓库打开 PR。upstream 远程允许您将 fork 与原始仓库同步。始终从更新的 main 创建功能分支。此工作流允许任何人在没有写权限的情况下贡献。维护者审查 PR 并合并。为了保持您的 fork 同步,定期 fetch upstream 并 merge/rebase。一些项目对具有直接推送到功能分支权限的内部贡献者使用'clone、branch、PR'模型。
# For open-source projects (contributors don't have push access)
# 1. Fork the repo on GitHub (UI button)
# 2. Clone YOUR fork
git clone https://github.com/YOUR-USERNAME/project.git
cd project
# 3. Add upstream (original repo)
git remote add upstream https://github.com/ORIGINAL/project.git
# 4. Keep your fork updated
git fetch upstream
git checkout main
git merge upstream/main # or: git rebase upstream/main
git push origin main
# 5. Create feature branch
git checkout -b feature/my-contribution
# 6. Push to YOUR fork
git push origin feature/my-contribution
# 7. Open Pull Request from your fork to upstream分支命名约定
一致的分支命名提高清晰度并启用自动化。常见前缀:feature、bugfix、hotfix、release、chore、docs、refactor、experiment。包含工单号(PROJ-123)将分支链接到 issue 并启用自动链接。斜杠在 Git GUI 中创建视觉层次。一些团队通过 Git 钩子或 CI 检查强制执行命名。约定应记录在 CONTRIBUTING.md 中。保持名称描述性但简洁。避免个人名称(johns-branch)——描述工作,而不是作者。一致的命名使分支清理和历史导航容易得多。
# Common naming patterns:
feature/add-user-authentication
feature/PROJ-123-user-profile
bugfix/fix-login-redirect
bugfix/PROJ-456-crash-on-startup
hotfix/security-patch-xss
release/v2.0.0
chore/update-dependencies
docs/api-documentation
refactor/extract-auth-module
experiment/new-algorithm
# Using ticket numbers (Jira, Linear, etc.)
git checkout -b feature/PROJ-123-oauth-login
# Auto-linking in commit messages
git commit -m "PROJ-123: implement OAuth login"
# GitHub auto-links PROJ-123 to the Jira ticket
# Branch naming with slashes creates hierarchy
# in Git GUI tools (GitHub, GitKraken, SourceTree)Rebase 深入
交互式 Rebase
交互式 rebase(-i)是最强大的历史编辑工具。它允许您在推送之前重写、重新排序、合并、拆分或删除提交。squash 将提交合并到其父提交中(合并消息);fixup 做同样但丢弃提交消息(清理 'WIP' 提交)。edit 暂停 rebase 以便您可以修改提交(添加文件、更改内容)。reword 让您只更改消息。drop 移除提交。始终在推送之前 rebase 以保持历史整洁。绝不要 rebase 其他人已经拉取的提交——它会重写共享历史。
# Rebase last 5 commits interactively
git rebase -i HEAD~5
# Opens editor with:
# pick a1b2c3d First commit
# pick e4f5g6h Second commit
# pick i7j8k9l Third commit
# pick m0n1o2p Fourth commit
# pick q3r4s5t Fifth commit
# Commands:
# pick = use commit as-is
# reword = use commit, edit message
# edit = pause to amend the commit
# squash = combine with previous commit
# fixup = like squash, discard this message
# drop = remove commit entirely
# exec = run a shell command
# reorder = move lines to reorder commitsSquash 提交
Squashing 将多个提交合并为一个,创建干净的历史。这对于合并功能分支很理想:将 20 个 'WIP' 提交 squash 为一个有意义的 'feat: add login' 提交。--fixup 标志创建一个特殊提交,--autosquash 自动放置并与目标 squash——非常适合修复审查反馈而不使历史混乱。git reset --soft main 后跟单个提交一次 squash 所有内容(对于完全 squash 比交互式 rebase 更简单)。许多团队配置 PR 合并以自动 squash(GitHub 的 'Squash and merge' 选项)。
# Squash the last 3 commits into one
git rebase -i HEAD~3
# In editor, change:
# pick a1b2c3d First
# squash e4f5g6h Second (or 'fixup' to discard message)
# squash i7j8k9l Third
# Git prompts for combined commit message
# Squash everything since branching from main
git rebase -i main
# Auto-squash fixup commits
git commit --fixup a1b2c3d # creates fixup! commit
git rebase -i --autosquash main
# Git automatically reorders and squashes fixup commits
# Squash all commits into one on a branch
git reset --soft main
git commit -m "feat: complete feature X"Rebase 与 Merge
Merge 保留完整的分支历史(合并提交显示功能分叉和合并的位置)。Rebase 将提交重放到目标之上,创建没有合并提交的线性历史。Merge 更安全(不重写历史)并显示功能上下文。Rebase 更干净但重写提交历史。常见策略:在合并之前将功能分支 rebase 到最新的 main 上,然后合并(快进或使用 --no-ff 创建合并提交)。这提供干净的提交和标记功能的合并提交。对于公共/共享分支,优先使用 merge 以避免历史冲突。
# MERGE: preserves complete history with merge commits
git checkout main
git merge feature
# Creates a merge commit, history looks like a graph:
# * merge commit
# |\
# | * feature commit 2
# | * feature commit 1
# * main commit
# REBASE: linear history, no merge commits
git checkout feature
git rebase main
git checkout main
git merge feature # fast-forward, no merge commit
# History is linear:
# * feature commit 2
# * feature commit 1
# * main commit
# When to use each:
# Merge: preserve context of feature branch, public repos
# Rebase: clean linear history, before pushing feature branch解决 Rebase 冲突
Rebase 冲突在将提交重放到更改的基上时发生。解决每个冲突,git add 已解决的文件,然后 git rebase --continue。rebase 一次处理一个提交,因此您可能遇到多个冲突。--skip 丢弃提交(如果 rebase 后变空则使用)。--abort 取消一切并返回到 rebase 前的状态——始终是安全的逃生。对于复杂冲突,git mergetool 启动可视化合并工具(VS Code、Beyond Compare 等)。与合并冲突的关键区别:rebase 可能需要多次解决同一冲突(每个重放的提交一次)。
# During rebase, conflicts pause the process
git rebase main
# CONFLICT in file.txt
# 1. Resolve conflicts manually in the file
# (look for <<<<<<< ======= >>>>>>> markers)
# 2. Stage resolved files
git add file.txt
# 3. Continue the rebase
git rebase --continue
# Skip a commit (if it's empty after rebase)
git rebase --skip
# Abort the entire rebase (return to original state)
git rebase --abort
# Use a merge tool for conflicts
git mergetool
# After resolving, the rebase continues
# with the next commit automaticallyRebase --onto(高级)
git rebase --onto 是用于精确提交移植的高级形式。语法:rebase --onto NEW-BASE OLD-BASE BRANCH——它取 OLD-BASE 和 BRANCH 之间的提交,并将它们重放到 NEW-BASE 上。这对于更改分支的基点很有用(例如,您的功能基于另一个已合并的功能;rebase 到 main 以清理)。它也用于从历史中移除特定提交(绕过它们重放)。这是高级用户功能——先理解常规 rebase。在高级历史重写之前始终有备份(reflog)。
# Scenario: feature branch was based on old-branch,
# but you want to rebase onto main instead
# git rebase --onto new-base old-base branch
git rebase --onto main old-branch feature
# This takes commits from old-branch..feature
# and replays them onto main
# Use case: remove commits from the middle
# Remove commit C from branch (replay A,B,D onto base)
# base -> A -> B -> C -> D
# git rebase --onto base C D
# Result: base -> A -> B -> D'
# Use case: move a branch to a different parent
git rebase --onto main feature/sub-feature feature/main-feature
# Moves main-feature's unique commits from sub-feature to mainCherry-Pick 与 Bisect
git cherry-pick
cherry-pick 将一个分支的特定提交应用到另一个分支。用例:将 bug 修复应用到发布分支,复制您忘记合并的提交,或选择性移植功能。提交获得新哈希(不同的父提交)。如果目标分支已分歧,cherry-pick 可能导致冲突。--no-commit 暂存更改而不提交(对于组合多个 cherry-pick 很有用)。避免过度 cherry-pick——当分支最终合并时它可能创建重复提交。对于系统化回溯,使用带合并的发布分支代替。
# Apply a specific commit to current branch
git cherry-pick a1b2c3d
# Cherry-pick multiple commits
git cherry-pick a1b2c3d e4f5g6h i7j8k9l
# Cherry-pick a range (exclusive start, inclusive end)
git cherry-pick A..E # commits B, C, D, E
# Cherry-pick without committing (stage only)
git cherry-pick --no-commit a1b2c3d
# or: -n
# Cherry-pick and edit commit message
git cherry-pick --edit a1b2c3d
# Cherry-pick from another branch
git cherry-pick feature-branch~2
# If conflicts occur:
git add . && git cherry-pick --continue
git cherry-pick --abort # cancelgit bisect(二分搜索)
git bisect 通过提交历史执行二分搜索以找到引入 bug 的确切提交。您将当前状态标记为 'bad',将已知工作的提交标记为 'good'。Git 检出中点;您测试并标记好/坏。每步将搜索空间减半——在 1000 个提交中找到 bug 大约需要 10 步。bisect reset 返回到您的原始分支。这对于追踪回归问题非常宝贵。结合 git bisect log 保存/恢复 bisect 会话。罪魁祸首提交通常立即揭示根本原因。
# Find which commit introduced a bug
git bisect start
# Mark current (bad) commit
git bisect bad
# Mark a known-good commit (last working version)
git bisect good v1.0.0
# or: git bisect good a1b2c3d
# Git checks out a middle commit
# Test it, then mark:
git bisect good # if this commit works
git bisect bad # if this commit is broken
# Repeat until Git identifies the culprit:
# "a1b2c3d is the first bad commit"
# When done:
git bisect reset # return to original branch
# Skip a commit (can't test, e.g., doesn't build)
git bisect skip自动化 Bisect
自动化 bisect 为每个提交运行测试脚本,消除手动测试。脚本退出 0(好)、非零(坏)或 125(跳过——例如,构建失败)。Git 自动标记每个提交并找到罪魁祸首。这对于测试套件非常强大:git bisect run npm test 在几分钟内找到破坏提交。您还可以将搜索限制为特定文件(git bisect start -- path/to/file)以加快速度。脚本可以是任何东西:测试、构建检查或检查 API 的 curl 命令。保存 bisect 日志以稍后重现或恢复。
# Auto-bisect with a test script
git bisect start
git bisect bad HEAD
git bisect good v1.0.0
# Git runs this script for each commit
# Exit 0 = good, exit 1 = bad, 125 = skip
git bisect run npm test
# Or a custom script
git bisect run ./scripts/check-bug.sh
# Example check script:
#!/bin/bash
npm run build || exit 125 # skip if build fails
npm test -- --grep "login bug"
# exit 0 if test passes (good), 1 if fails (bad)
# Git automatically finds the bad commit
# without manual testing
# Bisect with a file range (faster)
git bisect start -- src/auth/login.jsgit blame 与 annotate
git blame 显示文件每一行的作者和提交——对于理解代码为何存在至关重要。-L 限制为行范围(更快、更专注)。-w 忽略仅空白更改(显示真实内容作 者)。-M 检测在同一文件内移动的代码;-C 检测从其他文件复制的代码(显示原始作者,而不是复制者)。blame 是为了理解,而不是指责——用它找到代码的上下文,然后用 git show 阅读完整提交。GitHub 的 'Blame' 按钮提供可视化界面。结合 git log -S 查找特定文本何时添加。
# Show who last modified each line
git blame file.txt
# Blame specific line range
git blame -L 10,20 file.txt
# Show full commit hash (not abbreviated)
git blame -l file.txt
# Show email instead of name
git blame -e file.txt
# Ignore whitespace changes
git blame -w file.txt
# Detect moved lines within file
git blame -M file.txt
# Detect moved lines from other files
git blame -C file.txt
# Blame with commit message
git blame --show-name --show-email file.txt | headgit revert(安全撤销)
git revert 创建一个新提交来撤销之前的提交——这是在共享分支上撤销更改的安全方式(与 reset 不同,后者重写历史)。revert 对于无法重写历史的生产分支很理想。还原合并提交需要 -m 1(主线父提交)——这撤销合并同时保留分支历史。还原一个 revert 会重新应用原始更改(当 revert 是错误时常见)。对于多个提交,按相反顺序还原(最新的先)以最小化冲突。始终在共享/公共分支上使用 revert;仅在本地分支上使用 reset。
# Revert a commit (creates a NEW commit that undoes it)
git revert a1b2c3d
# Revert multiple commits
git revert a1b2c3d e4f5g6h
# Revert a range
git revert A..E
# Revert without committing (stage the reversal)
git revert --no-commit a1b2c3d
# Revert a merge commit
git revert -m 1 a1b2c3d
# -m 1 specifies the mainline parent (parent 1 = the branch
# you merged INTO, parent 2 = the branch you merged FROM)
# If revert conflicts:
git add . && git revert --continue
git revert --abortReflog 与恢复
git reflog 基础
reflog 记录对 HEAD 和分支指针的每次更改——甚至是'销毁'提交的操作(reset --hard、rebase、分支删除)。这是您的安全网:'丢失'的提交可通过 reflog 恢复约 90 天(默 认)。reflog 是本地的(从不推送)并显示指针移动的时间顺序历史。要恢复,在 reflog 中找到提交哈希并 checkout/reset 到它。@{N} 语法引用条目:HEAD@{0} 是当前,HEAD@{1} 是上一个。如果您曾经认为'丢失'了工作,首先检查 reflog——它几乎肯定还在那里。
# View the reflog (reference log)
git reflog
# Shows ALL reference changes, including:
# - commits, checkouts, resets, rebases, merges
# Example output:
# a1b2c3d HEAD@{0}: reset: moving to HEAD~1
# e4f5g6h HEAD@{1}: commit: add feature
# i7j8k9l HEAD@{2}: checkout: moving from main to feature
# Reflog for a specific branch
git reflog show feature-branch
# Reflog with dates
git reflog --date=iso
# Recover a "lost" commit
git reflog
# Find the commit hash, then:
git checkout a1b2c3d # or: git reset --hard a1b2c3d恢复已删除的分支
已删除的分支和 reset 提交可通过 reflog 恢复。提交对象仍然存在于 Git 的对象存储中,直到垃圾回收(默认:不可达对象 90 天)。要恢复已删除的分支,在 reflog 中找到其尖端提交并创建指向它的新分支。对于 reset --hard 错误,reflog 显示之前的 HEAD 位置——reset 回去。对于错误的 rebase,找到 rebase 开始之前的 reflog 条目并 reset 到它。关键教训:在 Git 中,几乎没有什么会立即真正丢失。在恐慌之前始终检查 reflog。
# Accidentally deleted a branch?
git branch -D feature # force deleted
# Find it in reflog
git reflog
# e4f5g6h HEAD@{2}: commit: work on feature
# Recreate the branch at that commit
git branch feature e4f5g6h
# Or checkout and create
git checkout -b feature e4f5g6h
# Recover after reset --hard
git reset --hard HEAD~3 # oops, went too far
git reflog
# Find the commit before the reset
git reset --hard HEAD@{1} # undo the reset
# Recover after a bad rebase
git reflog
git reset --hard HEAD@{5} # before the rebase startedgit fsck(悬空对象)
git fsck 检查仓库完整性并找到悬空对象——未被任何分支或标签引用的提交、blob 和树。--lost-found 将这些写入 .git/lost-found/。这是当 reflog 没有您需要的内容时的最后手段(reflog 条目过期,或 git gc 运行了)。悬空提交通常是中止操作或过期 reflog 条目的结果。用 git show 检查,然后通过创建分支恢复。fsck --full 验证所有对象的完整性(对于检测损坏很有用)。定期在 重要仓库上运行 fsck 以及早发现问题。
# Find dangling (unreachable) commits
git fsck --lost-found
# Output:
# dangling commit a1b2c3d...
# dangling blob e4f5g6h...
# Inspect a dangling commit
git show a1b2c3d
# Recover it
git branch recovered a1b2c3d
# Full fsck (check repository integrity)
git fsck --full
# Check for dangling objects only
git fsck --no-reflogs --dangling
# When reflog doesn't have what you need
# (e.g., expired, or gc ran), fsck finds
# ALL dangling objects in the object storegit stash 深入
git stash 临时搁置未提交的更改。push -m 添加描述性消息(对于管理多个 stash 至关重要)。-u 包含未跟踪文件;-a 也包含被忽略的文件。apply 重新应用而不移除;pop 应用并移除。stash branch 从 stash 创建新分支(如果 stash 与当前分支冲突很有用)。Stash 存储在栈中(LIFO)——由 stash@{N} 引用。show -p 显示差异。Stash 在重启后持久存在但是本地的(从不推送)。定期清理旧的 stash;它们会累积。对于长期工作,创建分支而不是 stash。
# Stash with message
git stash push -m "work in progress on login"
# Stash specific files
git stash push -m "wip" file1.txt file2.txt
# Stash including untracked files
git stash push -u # or --include-untracked
# Stash including ignored files
git stash push -a # or --all
# List stashes
git stash list
# stash@{0}: On feature: wip on login
# stash@{1}: On main: quick fix
# Apply a specific stash
git stash apply stash@{1}
# Apply and drop (pop)
git stash pop # applies stash@{0} and removes it
# Show stash contents
git stash show -p stash@{0}
# Create branch from stash
git stash branch new-branch stash@{0}
# Drop a stash
git stash drop stash@{1}
git stash clear # drop ALL stashesgit tag 管理
标签将特定提交标记为重要(发布、里程碑)。附注标签(-a)存储元数据(标签者、日期、消息),推荐用于发布。轻量级标签只是命名指针(无元数据)。签名标签(-s)使用 GPG 进行验证(对于安全发布很重要)。标签默认不推送——使用 --tags 推送它们。语义化版本控制(v1.2.3)是标准命名约定。检出标签进入分离 HEAD 状态(以检查或构建发布)。GitHub 发布基于标签构建——创建标签,然后发布带说明的发布。
# Create annotated tag (recommended)
git tag -a v1.0.0 -m "Release 1.0.0"
# Create lightweight tag
git tag v1.0.0
# Tag a specific commit
git tag -a v0.9.0 -m "beta" a1b2c3d
# List all tags
git tag
git tag -l "v1.*" # filter by pattern
# Push tags to remote
git push origin v1.0.0 # single tag
git push origin --tags # all tags
# Delete a tag
git tag -d v1.0.0 # local
git push origin --delete v1.0.0 # remote
# Show tag details
git show v1.0.0
# Checkout a tag (detached HEAD)
git checkout v1.0.0
# Signed tags (GPG)
git tag -s v1.0.0 -m "signed release"Worktree 与子模块
git worktree
git worktree 从同一仓库创建额外的工作目录——无需克隆。每个 worktree 同时检出不同的分支。这非常适合:在保持功能分支打开的同时处理热修复,在一个分支上运行测试同时在另一个分支上编码,或在一个 worktree 上运行长时间构建。所有 worktree 共享相同的 .git 目录(对象、引用),因此它们保持同步并节省磁盘空间。您 不能在两个 worktree 中检出同一分支(Git 防止这样做以避免冲突)。对于多分支工作流,Worktree 比克隆更快。
# Create a new working directory linked to the repo
git worktree add ../project-hotfix hotfix-branch
# Work in the new directory
cd ../project-hotfix
# This is a full working tree on hotfix-branch
# Shares the same .git, so no cloning needed
# List worktrees
git worktree list
# /path/to/project main
# /path/to/project-hotfix hotfix-branch
# Remove a worktree
git worktree remove ../project-hotfix
# Prune stale worktree entries
git worktree prune
# Create worktree at new branch
git worktree add -b feature-x ../project-feature maingit submodule 基础
子模块将一个 Git 仓库嵌入另一个仓库——对于包含共享库或依赖项很有用。父仓库存储指向子模块的指针(提交哈希),而不是其内容。--recurse-submodules 在克隆时至关重要(否则子模块为空)。更新子模块(--remote)获取最新提交;然后您必须在父仓库中提交新哈希。子模块很复杂:分支、冲突和更新需要仔细处理。对于更简单的依赖管理,考虑 Git 子树、包管理器(npm、pip)或 monorepo 策略。当您需要跟踪特定外部提交时使用子模块。
# Add a submodule to your repo
git submodule add https://github.com/user/lib.git libs/lib
# Clone a repo with submodules
git clone --recurse-submodules https://github.com/user/project.git
# If you forgot --recurse-submodules:
git submodule update --init --recursive
# Pull latest changes in all submodules
git submodule update --remote
# Pull specific submodule
git submodule update --remote libs/lib
# After updating submodules, commit the new hash
git add libs/lib
git commit -m "update lib submodule"
# Execute command in all submodules
git submodule foreach 'git status'子模块工作流
在子模块内部工作就像在普通仓库中工作一样——您从子模块目录内提交和推送。父仓库跟踪提交哈希,因此在更改子模块后,您也必须在父级中提交。切换分支时,子模块内容可能不匹配——运行 git submodule update --init --recursive 来同步。删除子模块需要三个步骤:deinit(取消注册)、git rm(从跟踪中移除)和手动删除 .git/modules。子模块工作流容易出错;更新子模块时始终与团队沟通以避免哈希不匹配。
# Change code inside a submodule
cd libs/lib
# Make changes, commit, and push
git add .
git commit -m "fix: bug in lib"
git push origin HEAD:main
# Back in parent repo, update the pointer
cd ../..
git add libs/lib
git commit -m "chore: update lib submodule"
# Switch branches with submodules
git checkout main
git submodule update --init --recursive
# Delete a submodule
git submodule deinit -f libs/lib
git rm libs/lib
rm -rf .git/modules/libs/lib
git commit -m "remove lib submodule"
# Move a submodule
git mv libs/lib libs/new-libGit 钩子
Git 钩子在 Git 生命周期的特定点运行脚本。客户端钩子(pre-commit、pre-push、commit-msg)强制执行本地标准。pre-commit 非常适合 lint/格式化;pre-push 用于运行测试;commit-msg 用于强制执行 Conventional Commits。钩子不被 Git 跟踪(它们位于 .git/hooks/ 中),因此它们不会在克隆之间同步。要在团队之间共享钩子,使用 Husky(npm)、pre-commit(Python)等工具,或将钩子提交到签入目录并符号链接它们。服务端钩子(pre-receive、post-receive)在远程上运行,可以为所有贡献者强制执行策略。
# Hooks live in .git/hooks/ (not tracked by Git)
# Sample hooks are provided with .sample extension
# pre-commit: runs before commit is created
# .git/hooks/pre-commit
#!/bin/bash
npm run lint || exit 1 # block commit if lint fails
# pre-push: runs before pushing
#!/bin/bash
npm test || exit 1 # block push if tests fail
# commit-msg: validate commit message format
#!/bin/bash
# $1 is the path to the commit message file
if ! grep -qE "^(feat|fix|docs|chore):" "$1"; then
echo "Use conventional commit format: type: message"
exit 1
fi
# prepare-commit-msg: auto-populate message
#!/bin/bash
echo "# Branch: $(git branch --show-current)" >> "$1"
# Make hooks executable
chmod +x .git/hooks/pre-commitGit LFS(大文件存储)
Git LFS 用 Git 中的文本指针替换大文件(二进制、视频、数据集),将实际内容存储在单独的 LFS 服务器上。这保持仓库轻量——没有 LFS,二进制文件会永久膨胀仓库(每个版本都存储)。使用 git lfs track 跟踪文件模式,然后提交 .gitattributes。之后,大文件透明地工作。git lfs migrate import 将现有文件追溯转换为 LFS(重写历史——先与团队协调)。LFS 需要服务器支持(GitHub、GitLab、Bitbucket 都支持它)。注意:LFS 在托管平台上有带宽/存储配额。对于真正巨大的文件,考虑带 URL 的外部存储。
# Initialize Git LFS
git lfs install
# Track large file types
git lfs track "*.psd"
git lfs track "*.zip"
git lfs track "assets/**"
# This creates/updates .gitattributes
# Commit the .gitattributes file
git add .gitattributes
git commit -m "chore: configure LFS tracking"
# Add large files normally
git add design.psd
git commit -m "add design file"
git push
# View LFS tracked files
git lfs ls-files
# Pull LFS content (if not auto-downloaded)
git lfs pull
# Migrate existing files to LFS (rewrites history)
git lfs migrate import --include="*.psd" --everything
# Check LFS status
git lfs statusStash
保存与弹出
git stash 搁置未提交的更改,以便您可以在干净的工作树上切换分支或拉取更新。pop 应用并移除顶部 stash;apply 保留它。Stash 是一个 LIFO 栈。
# Save uncommitted changes (tracked files only)
git stash
# Save including untracked files
git stash -u
# Pop the most recent stash (removes it)
git stash pop
# Apply without removing (keeps stash in list)
git stash apply命名 Stash
始终传递 -m 来标记 stash——默认消息是分支和提交,很少具有描述性。stash@{N} 通过索引引用特定 stash。
# Save with a descriptive message
git stash push -m "WIP: refactor auth flow"
# List all stashes with messages
git stash list
# Apply a specific stash by index
git stash apply stash@{1}Stash 分支
git stash branch 从最初创建 stash 的提交创建新分支,然后在那里应用 stash。这是当 stash 不再干净地应用时恢复的最干净方式。
# Create a new branch from a stash
git stash branch feature-branch stash@{0}
# Useful when stash conflicts with current branch
# - Creates branch from the commit where stash was made
# - Applies stash on top
# - Drops stash if apply succeeds部分 Stash
使用 -p(交互式块选择)或通过列出特定文件只 stash 您需要的。--keep-index stash 未暂存的更改但保留暂存的更改。
# Interactively choose hunks to stash
git stash push -p
# Stash specific files only
git stash push -m "config tweaks" config.yml .env.local
# Stash with keep-index (staged changes stay)
git stash --keep-indexStash 管理
git stash show -p 显示 stash 的完整差异。drop 移除单个 stash;clear 清除所有(不可逆)。Stash 仅在本地;它们从不推送到远程。
# Show what's in a stash
git stash show -p stash@{0}
# Drop a specific stash
git stash drop stash@{0}
# Clear all stashes (irreversible)
git stash clear
# Show stash list with dates
git stash list --date=relativeRebase
基本 Rebase
Rebase 将您的分支提交移动到另一个分支之上,产生线性历史。与 merge 不同,它重写提交哈希。绝不要 rebase 已推送并共享的提交。
# Rebase current branch onto main
git checkout feature
git rebase main
# Abort if conflicts are too messy
git rebase --abort
# Continue after resolving conflicts
git rebase --continue交互式 Rebase
交互式 rebase(-i)允许您在共享之前重写历史:重新排序提交,将相关的 squash 为单个干净提交,重写消息,或丢弃错误。始终在未推送的提交上执行此操作。
# Rebase last 5 commits interactively
git rebase -i HEAD~5
# Commands: pick, reword, edit, squash, fixup, drop
# squash = combine with previous, keep message
# fixup = combine with previous, discard message
# reword = change commit message onlySquash 与 Fixup
--fixup 创建一个标记为另一个提交修复的提交。rebase 期间的 --autosquash 自动将 fixup! 和 squash! 提交放在其目标旁边。这简化了'先提交,后清理'的工作流。
# Create a fixup commit targeting an earlier commit
git commit --fixup a1b2c3
# Autosquash during rebase (auto-reorders fixups)
git rebase -i --autosquash HEAD~5Rebase --onto
--onto 是外科手术式 rebase:它将一系列提交从一个基移动到另一个。用它来重新为分支设置父级,或丢弃分支的前几个提交。
# Move commits from one base to another
# git rebase --onto <new-base> <old-base> <branch>
git rebase --onto main feature-old feature-new
# Drop the first N commits of a branch
git rebase --onto main HEAD~3Rebase 冲突
在 rebase 期间,冲突在每个提交处停止。解决,git add,然后 --continue 继续。--skip 完全丢弃冲突提交。--abort 返回到 rebase 前的状态。
# During rebase, conflicts pause the process
git rebase main
# CONFLICT (content): Merge conflict in file.js
# Resolve in editor, then:
git add file.js
git rebase --continue
# Skip a commit that conflicts (drop it)
git rebase --skip
# Give up entirely
git rebase --abortCherry-Pick
Cherry-Pick 一个提交
cherry-pick 将另一个分支的特定提交应用到您当前的分支,创建具有相同更改的新提交。新提交具有不同的哈希,因为父提交不同。
# Apply a specific commit to current branch
git cherry-pick a1b2c3d
# Cherry-pick multiple commits
git cherry-pick a1b2c3d e4f5g6h
# Cherry-pick a range (exclusive of start, inclusive of end)
git cherry-pick A..E不提交的 Cherry-Pick
--no-commit(-n)暂存 cherry-pick 的更改而不创建提交。这让您将多个 cherry-pick 合并为一个提交,或在提交之前修改更改。
# Apply changes to working tree without committing
git cherry-pick --no-commit a1b2c3d
# Stage the changes yourself
git add -p
git commit -m "Custom message"Cherry-Pick 冲突
cherry-pick 期间的冲突暂停操作。解决,git add,然后 --continue。--skip 放弃当前提交。--abort 取消 cherry-pick 并恢复分支。
git cherry-pick a1b2c3d
# error: could not apply a1b2c3d...
# Resolve conflicts, then:
git add resolved-file.js
git cherry-pick --continue
# Skip this commit
git cherry-pick --skip
# Abort the entire cherry-pick
git cherry-pick --abort从另一个分支 Cherry-Pick
经典的热修复工作流:在维护分支上修复 bug,然后将同一提交 cherry-pick 到 main(和其他活跃分支)。这避免合并不相关的功能工作。
# Find the commit hash on another branch
git log --oneline feature-branch
# Switch to target branch
git checkout main
# Cherry-pick the commit
git cherry-pick <hash>
# Useful for hotfixes: fix on a feature branch,
# then cherry-pick the fix to mainCherry-Pick 策略
-X theirs/ours 偏向冲突解决。-x 添加一行记录原始提交哈希——对于跨分支 cherry-pick 热修复的审计跟踪至关重要。
# Use a different merge strategy
git cherry-pick -X theirs a1b2c3d # prefer their changes on conflict
git cherry-pick -X ours a1b2c3d # prefer our changes on conflict
# Preserve original commit author
git cherry-pick -x a1b2c3d # adds "(cherry picked from ...)" to message
# Edit commit message before committing
git cherry-pick -e a1b2c3dBisect
基本 Bisect
git bisect 通过提交历史执行二分搜索以找到哪个提交引入了 bug。您将当前提交标记为 bad,将已知好的提交标记为 good。大约 ~log2(N) 步后,git 指出有问题的提交。
# Start bisecting
git bisect start
# Mark current commit as bad (has the bug)
git bisect bad
# Mark a known-good commit (older)
git bisect good v1.0.0
# Git checks out the midpoint. Test, then mark:
git bisect good # or
git bisect bad
# Exit bisect mode
git bisect resetBisect 日志与重放
git bisect log 记录每个好/坏决定。如果您错误标记提交(常见错误),reset 并重放日志,然后修复错误的步骤。
# Record what happens during bisect
git bisect log > bisect.log
# If you make a mistake, replay from the log
git bisect reset
git bisect replay bisect.log
# Visualize the bisect state
git bisect visualize自动化 Bisect
git bisect run 自动化搜索:git 检出每个候选,运行您的脚本,并根据退出代码标记提交。这比手动测试快得多。
# Auto-bisect: git runs a test script and marks good/bad itself
git bisect start HEAD v1.0.0 -- # bad=HEAD, good=v1.0.0
git bisect run npm test # exit 0=good, 1-124=bad, 125=skip
# Or with a custom script
git bisect run ./scripts/check-bug.sh按文件 Bisect
将路径传递给 git bisect start 将搜索限制为修改了该文件的提交。这跳过数百个不相关的提交,并定位到 bug 可能存在的文件。
# Limit bisect to changes in a specific file
git bisect start -- path/to/file.js
# Combine with bad/good markers
git bisect bad HEAD
git bisect good v1.0.0
# Git only considers commits that touched that fileBisect Reset
完成后始终运行 git bisect reset——它返回到您的原始分支并清理 bisect 状态。没有 reset,您的工作树停留在最后测试的提交上。
# Exit bisect mode and return to original branch
git bisect reset
# Reset to a specific branch/commit instead
git bisect reset <branch>
# View current bisect state
git bisect status子模块
添加子模块
子模块将一个 Git 仓库嵌入另一个——对于 vendor 共享库很有用。git submodule add 在 .gitmodules 中注册子模块。新克隆需要 --recurse-submodules。
# Add a submodule at a specific path
git submodule add https://github.com/user/lib.git libs/lib
# This creates:
# - libs/lib/ (the submodule checkout)
# - .gitmodules (tracks submodule URLs and paths)
# Commit the new submodule
git commit -m "Add lib submodule"
# Clone a repo with submodules included
git clone --recurse-submodules https://github.com/user/repo.git更新子模块
子模块固定到特定提交。git submodule update --remote 获取跟踪分支上的最新提交并更新指针。您必须在父仓库中提交此指针更改。
# Pull latest changes in all submodules
git submodule update --remote
# Update a specific submodule
git submodule update --remote libs/lib
# Then commit the updated pointer
git add libs/lib
git commit -m "Bump lib submodule"
# Initialize submodules after cloning
git submodule update --init --recursive子模块 Foreach
foreach 在每个子模块目录中运行 shell 命令——对于批量操作(如检查状态、拉取更新或构建)很有用。--recursive 下降到嵌套子模块。
# Run a command in every submodule
git submodule foreach 'git status'
# With recursion into nested submodules
git submodule foreach --recursive 'git checkout main'
# Use --quiet to suppress the "Entering..." messages
git submodule foreach --quiet 'git pull'Deinit 与移除
移除子模块是多步骤过程:deinit 取消注册它,rm -rf .git/modules/... 删除子模块 Git 数据,git rm 移除工作树。忘记 .git/modules 清理会留下孤立数据。
# Deinit a submodule (unregisters, keeps files)
git submodule deinit libs/lib
# Fully remove a submodule
git submodule deinit -f libs/lib
rm -rf .git/modules/libs/lib
git rm -f libs/lib
git commit -m "Remove lib submodule"子模块分支
默认情况下,子模块处于分离 HEAD。设置 submodule.<name>.branch 让 --remote 跟踪该分支。要在子模块内进行更改,cd 进入,检出分支,提交并推送。
# Configure a submodule to track a branch
git config -f .gitmodules submodule.libs/lib.branch main
# Now --remote updates from that branch
git submodule update --remote
# Work inside a submodule like a normal repo
cd libs/lib
git checkout feature-branch
git push
cd ../..
git add libs/lib
git commit -m "Update lib to feature-branch"钩子
常见钩子
Git 钩子是 .git/hooks/ 中的脚本,在特定点自动运行。客户端钩子在您的机器上运行并可以阻止操作。服务端钩子在远程上运行并强制执行策略。钩子默认不版本化——使用 Husky 共享它们。
# Client-side (run on your machine):
# pre-commit - runs before commit is created
# prepare-commit-msg - can edit commit message
# commit-msg - validates commit message
# pre-push - runs before pushing
# post-merge - runs after git merge
# Server-side (run on the remote):
# pre-receive - runs before refs are updated
# update - runs once per ref
# post-receive - runs after refs are updatedpre-commit 钩子
pre-commit 在创建提交之前运行;非零退出中止提交。常见用途:lint 暂存文件,运行专注测试,格式化代码。保持快速(<5 秒),否则开发者会用 --no-verify 绕过它。
#!/bin/sh
# .git/hooks/pre-commit
# Run linter on staged files
npm run lint-staged
if [ $? -ne 0 ]; then
echo "Linting failed. Fix errors and try again."
exit 1
fi
# Run tests
npm test -- --findRelatedTests
if [ $? -ne 0 ]; then
echo "Tests failed."
exit 1
fi
exit 0commit-msg 钩子
commit-msg 接收临时提交消息文件的路径作为 $1。它可以验证或重写消息。非零退出拒绝提交。这是强制执行 Conventional Commits 的标准方式。
#!/bin/sh
# .git/hooks/commit-msg
# Enforce Conventional Commits format
pattern='^(feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert)(\(.*\))?: .{1,72}$'
if ! grep -qE "$pattern" "$1"; then
echo "Invalid commit message format."
echo "Use: <type>(<scope>): <description>"
exit 1
fiHusky 设置
Husky 从版本化的 .husky/ 目录安装 Git 钩子,因此每个团队成员在 npm install 后获得相同的钩子。lint-staged 只在暂存文件上运行命令,保持 pre-commit 快速。
# Install Husky
npm install --save-dev husky
npx husky init
# Add a pre-commit hook
echo 'npm run lint-staged' > .husky/pre-commit
# Add a commit-msg hook
echo 'npx --no-install commitlint --edit $1' > .husky/commit-msg
# package.json
{
"scripts": { "prepare": "husky" },
"lint-staged": {
"*.{js,ts}": ["eslint --fix", "prettier --write"]
}
}pre-push 钩子
pre-push 在推送引用之前运行;非零退出中止。它从 stdin 读取提议的推送。常见用途:阻止推送到受保护分支,运行完整测试套件。
#!/bin/sh
# .husky/pre-push
# Reads stdin: <local ref> <local sha> <remote ref> <remote sha>
while read local_ref local_sha remote_ref remote_sha; do
if [ "$remote_ref" = "refs/heads/main" ]; then
echo "Direct push to main is not allowed."
exit 1
fi
done
npm test
if [ $? -ne 0 ]; then
echo "Tests failed. Push aborted."
exit 1
fiWorktree
添加 Worktree
Worktree 是链接到同一仓库的单独工作目录。您可以在不同目录中同时检出多个分支——无需 stash 即可切换上下文。
# Create a worktree at a path, on a new branch
git worktree add ../project-feature feature-branch
# Worktree on an existing branch
git worktree add ../project-hotfix hotfix-branch
# Worktree at a specific commit (detached HEAD)
git worktree add --detach ../project-inspect v1.0.0
# List all worktrees
git worktree listWorktree 工作流
Worktree 在上下文切换方面大放异彩:当您深入功能时出现紧急 bug。与其 stash 并丢失 IDE 状态,不如在 main 上创建 worktree,在那里修复 bug,然后返回。
# Scenario: working on feature, urgent bug comes in
git worktree add ../project-hotfix main
cd ../project-hotfix
git checkout -b fix-urgent
# ... fix the bug, commit, push ...
cd ../project-feature
# Your feature work is untouched
# Clean up when done
git worktree remove ../project-hotfix移除与清理
remove 删除 worktree 目录及其管理元数据。它所在的分支保留。如果 worktree 有未提交的更改,remove 会拒绝,除非您传递 --force。
# Remove a worktree (must be clean or use --force)
git worktree remove ../project-feature
# Force remove even with uncommitted changes
git worktree remove --force ../project-feature
# Prune worktree admin files for deleted directories
git worktree prune
# Show what would be pruned
git worktree prune --dry-run -vWorktree 的好处
Worktree 解决了几个痛点:无需 stash 进行上下文切换,并行构建/测试,每个分支隔离的 node_modules,以及并排分支比较。共享对象数据库意味着最小的磁盘开销。
# 1. No stashing needed for context switches
# 2. Run long builds/tests in one worktree while
# continuing work in another
# 3. Each worktree has its own node_modules
# 4. Compare two branches side-by-side in two
# editor windows
# 5. Shared object database = minimal disk overhead
# Typical layout:
# ~/projects/
# main-repo/ (main branch)
# main-repo-feature/ (feature worktree)
# main-repo-hotfix/ (hotfix worktree)锁定的 Worktree
当 worktree 包含不应被打扰的工作(长时间运行的构建、附加的调试器)时锁定它。锁定的 worktree 在清理后保留。move 重新定位 worktree;repair 修复管理文件 。
# Lock a worktree (prevents it from being pruned)
git worktree lock ../project-feature --reason "Running long build"
# Unlock when done
git worktree unlock ../project-feature
# Move a worktree to a new path
git worktree move ../project-feature ../new-location/project-feature
# Repair after moving manually
git worktree repair ../new-location/project-featureReflog
查看 Reflog
reflog 记录对 HEAD 和分支尖端的每次更改——提交、检出、reset、rebase。它是本地安全网:即使在破坏性操作之后,提交仍然在 reflog 中。
# Show reflog for HEAD
git reflog
# a1b2c3d HEAD@{0}: commit: Fix bug
# e4f5g6h HEAD@{1}: checkout: moving to feature
# 789abc0 HEAD@{2}: reset: moving to HEAD~1
# Show reflog for a specific branch
git reflog show feature
# Show reflog with dates
git reflog --date=iso恢复丢失的提交
在 hard reset 或 rebase 之后,'丢失'的提交仍然可通过 reflog 到达。在 git reflog 中找到哈希,然后 reset --hard 或 cherry-pick 来恢复。这就是为什么 Git 是安全的——几乎没有什么会真正消失。
# Find the commit you lost (e.g., after a hard reset)
git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~1
# e4f5g6h HEAD@{1}: commit: Important work <-- this one
# Reset back to the lost commit
git reset --hard e4f5g6h
# Or cherry-pick it onto your current branch
git cherry-pick e4f5g6hReflog 与 Reset
ORIG_HEAD 是一个便利引用,在破坏性操作(reset、merge、rebase)之后指向前一个 HEAD。git reset --hard ORIG_HEAD 一条命令撤销最后一次此类操作。
# Undo a git reset --hard
git reflog
git reset --hard HEAD@{1}
# Undo a rebase
git reflog
git reset --hard HEAD@{5}
# Undo a merge
git reset --hard ORIG_HEAD
# ORIG_HEAD is set by merge, rebase, reset, and cherry-pick过期与清理
Reflog 条目累积并消耗磁盘空间。expire 移除旧条目;--expire-unreachable 只针对从任何引用不可达的条目。过期后,运行 git gc --prune=now 删除不可达对象。
# Expire reflog entries older than 30 days
git reflog expire --expire=30.days
# Expire entries unreachable from current branches
git reflog expire --expire-unreachable=30.days
# Delete a specific reflog entry
git reflog delete HEAD@{2}
# Dry run (see what would be expired)
git reflog expire --dry-run --expire=now --all分支的 Reflog
每个分支维护自己的 reflog。如果您用 git branch -D 删除分支,提交仍然在 reflog 中——找到哈希并用 git branch <name> <hash> 重新创建分支。
# Each branch has its own reflog
git reflog show main
# a1b2c3d main@{0}: commit: Update docs
# e4f5g6h main@{1}: pull origin main: Fast-forward
# Recover a deleted branch
git reflog # find the hash the branch pointed to
git branch recovered-branch e4f5g6h
# Compare current state with a past reflog entry
git diff HEAD@{1} HEADLFS
安装与跟踪
Git LFS 将大文件(图像、视频、二进制文件)存储在 Git 仓库之外,在提交中用指针文件替换它们。git lfs track 在 .gitattributes 中注册模式。在添加大文件之前始终提交 .gitattributes。
# Install Git LFS (once per user)
git lfs install
# Track large file types
git lfs track "*.psd"
git lfs track "*.zip"
git lfs track "assets/*"
# View tracking rules
cat .gitattributes
# Commit the .gitattributes file
git add .gitattributes
git commit -m "Configure LFS tracking"添加与提交 LFS 文件
一旦文件模式被跟踪,git add 通过 LFS 自动暂存文件。提交存储小的指针文件(~130 字节)而不是二进制文件。实际内容在推送时上传到 LFS 服务器。
# After configuring tracking, add files normally
git add design.psd
git commit -m "Add design file"
# Verify LFS is handling the file
git lfs ls-files
# a1b2c3d4 * design.psd
# The commit contains a pointer, not the actual file
git show HEAD -- design.psd
# version https://git-lfs.github.com/spec/v1
# oid sha256:...
# size 12345678克隆与拉取
克隆带 LFS 的仓库首先下载指针文件,然后是实际内容。如果 LFS 下载失败,git lfs pull 重试。git lfs fetch 下载对象而不将它们写入工作树。
# Clone a repo with LFS files (downloads them automatically)
git clone https://github.com/user/repo.git
# If LFS files are missing, fetch them
git lfs pull
# Fetch LFS objects without checking them out
git lfs fetch
# Checkout LFS files for specific paths
git lfs checkout assets/迁移到 LFS
git lfs migrate import 将历史中的大文件追溯转换为 LFS 指针。这重写所有提交哈希——每个协作者必须重新克隆。先运行 --dry-run 查看影响。
# Convert existing large files to LFS (rewrites history!)
git lfs migrate import --include="*.psd" --include-ref=refs/heads/main
# Migrate all branches
git lfs migrate import --include="*.psd" --everything
# Check what would be migrated (dry run)
git lfs migrate import --include="*.psd" --include-ref=refs/heads/main --dry-runLFS 管理
git lfs status 显示待处理的 LFS 更改。git lfs env 显示配置。git lfs fsck 验证指针文件引用的所有 LFS 对象都存在且未损坏。
# View LFS status
git lfs status
# List all LFS files in the repo
git lfs ls-files
# Check LFS configuration
git lfs env
# Untrack a file type (does not remove existing LFS objects)
git lfs untrack "*.zip"
# Verify LFS objects are present and valid
git lfs fsckCI/CD 集成
GitHub Actions 基础
GitHub Actions 在 push/PR 时运行工作流。actions/checkout 获取仓库;fetch-depth: 0 获取完整历史。npm ci 从 lockfile 安装(比 install 更快、更严格)。使用缓存键缓存依赖项。
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm test
- run: npm run build条件工作流
使用 on: push: branches 按分支或事件过滤工作流。使用作业级 if: 条件根据上下文跳过作业。needs: 在作业之间创建依赖关系——deploy 仅在 test 通过时运行。
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- run: ./deploy.shCI 中的 Git 钩子
CI 管道通常镜像本地钩子:先 lint(快速失败),然后测试。needs: lint 确保测试仅在 lint 通过时运行,节省 CI 分钟。拆分作业允许并行运行器以加快速度。
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- run: npm run lint
- run: npm run typecheck
test:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test -- --coverage密钥与环境
在仓库设置中存储密钥(API 密钥、令牌)并通过 ${{ secrets.NAME }} 引用它们。它们从不会在日志中回显。环境可以要求部署前手动批准,添加一个门控。
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # requires manual approval
steps:
- uses: actions/checkout@v4
- name: Deploy
env:
API_KEY: ${{ secrets.API_KEY }}
DB_URL: ${{ secrets.DATABASE_URL }}
run: ./scripts/deploy.sh "$API_KEY" "$DB_URL"矩阵构建
矩阵构建并行跨多个操作系统/语言组合运行同一作业。这及早捕获特定平台的 bug。使用 fail-fast: false 即使一个失败也运行所有组合。保持矩阵合理以控制 CI 分钟。
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: ['18', '20', '22']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test相关 Git 代码片段
Copy-paste ready code for common tasks.
这篇内容对您有帮助吗?