Git 高级操作指南:从历史管理到提效技巧

1. 撤销更改:Revert 与 Reset

1.1 使用 Revert 撤销

git revert 通过新增一个 Commit 的方式来抵消指定提交的修改。这是一种安全的操作,因为它不会改写已有的提交历史,非常适合在多人协作的分支上使用。

1
2
# 撤销指定的 commit-id
git revert <commit-id>

以下是一个典型的 revert 流程示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
* d38ece4 - add 1 (25 minutes ago) <liuwei>

# 撤销上一次提交
git revert d38ece4

* edaa22e - Revert "add 1" (2 minutes ago) <liuwei>
* d38ece4 - add 1 (25 minutes ago) <liuwei>

# 撤销上一次的 revert 操作(即还原修改)
git revert edaa22e

* 71903f8 - (HEAD -> branch) Revert "Revert "add 1"" (50 seconds ago) <liuwei>
* edaa22e - Revert "add 1" (2 minutes ago) <liuwei>
* d38ece4 - add 1 (25 minutes ago) <liuwei>

1.2 使用 Reset 强制回滚

如果追求提交历史的绝对整洁,可以使用 git reset --hard 配合 push -f

注意:此操作会丢弃修改并改写远端历史,仅建议在确认没有他人基于当前分支开发时使用。

1.3 图解:Revert Vs Reset

%%{init: {
  'theme': 'base',
  'themeVariables': {
    'git0': '#4F46E5',
    'git1': '#EF4444',
    'git2': '#10B981',
    'gitBranchLabel0': '#ffffff',
    'gitBranchLabel1': '#ffffff',
    'gitBranchLabel2': '#ffffff',
    'commitLabelColor': '#ffffff',
    'commitLabelFontSize': '12px'
  }
}}%%
gitGraph
    commit id: "A"
    commit id: "B"
    commit id: "C"
    
    branch revert-way
    checkout revert-way
    commit id: "D_Revert_C" tag: "Safe"
    
    checkout main
    branch reset-way
    checkout reset-way
    %% 注意:Mermaid 官方语法中 reset 并不总是能生效,通常使用从特定提交拉分支来模拟 reset 效果
    checkout main
    checkout reset-way
    commit id: "E_New_Fix" tag: "Destructive"

2. 线性历史:rebase 工作流

在合并分支时,merge 虽然更安全且保留了完整的原始信息,但会导致提交树交错复杂。rebase 则能通过重新定位基准,保持一条整齐的线性提交历史。

2.1 协作准则

执行 git rebase 前必须确认:是否有其他开发者正在此分支上工作? 如果该分支已被推送到远端且存在多人协作,请避免使用 rebase,以免造成他人本地分支冲突。

2.2 变基原理

关于 rebase 的深度分析与交互式变基技巧,可参考:Git Rebase 详解

2.3 使用 Git Pull –rebase

当你在本地 master 分支开发并提交了 Commit,而远端已有新的更新时,直接执行 git pull 会自动生成一个类似 Merge remote-tracking branch 'origin/master' 的合并提交。

为了保持 Log 的整洁,推荐使用:

1
git pull --rebase

该命令会先抓取远端修改,然后将你本地尚未推送的 Commit 重新应用(Replay)在远端最新的提交之上,从而避免不必要的 Merge Commit。

%%{init: {
  'theme': 'base',
  'themeVariables': {
    'git0': '#4F46E5',
    'git1': '#8B5CF6',
    'gitBranchLabel0': '#4F46E5',
    'gitBranchLabel1': '#8B5CF6'
  }
}}%%
gitGraph
    commit id: "Remote: A"
    commit id: "Remote: B"
    
    branch "local"
    checkout "local"
    commit id: "Local: C"
    commit id: "Local: D"
    
    checkout main
    commit id: "Remote: E (New)"
    
    checkout "local"
    merge main id: "Rebase: Apply C, D on E"

3. 实用提效命令

3.1 基础操作

3.2 代码追溯:blame

用于查看指定文件每一行代码的最后修改者及对应的 Commit。

1
git blame <file-name>

3.3 容错恢复:reflog

当发生误删 Commit、reset 出错或 rebase 失败导致 HEAD 丢失时,reflog 是最后的保障。它记录了本地仓库中 HEAD 指向的所有变更记录(包括已删除的提交)。

1
git reflog

3.4 任务暂存:stash

用于在切换分支前快速保存当前未提交的工作现场。

1
2
3
4
5
6
7
8
9
10
11
12
# 查看暂存列表
git stash list

# 示例输出:
# stash@{0}: WIP on master: a88fce2 拆分函数
# stash@{1}: WIP on master: a88fce2 拆分函数

# 常用操作
git stash show -p 0 # 查看指定序号的详细修改
git stash apply 0 # 应用暂存(不删除堆栈记录)
git stash pop # 应用并出栈(默认序号 0)
git stash drop 0 # 删除指定序号的暂存
%%{init: {
  'theme': 'base',
  'themeVariables': {
    'primaryColor': '#4F46E5',
    'primaryTextColor': '#fff',
    'primaryBorderColor': '#3730A3',
    'lineColor': '#6366F1',
    'secondaryColor': '#10B981',
    'tertiaryColor': '#F59E0B'
  }
}}%%
flowchart BT
    subgraph "Git Stash Stack (LIFO)"
        S2["stash@{2}: Older Changes"]
        S1["stash@{1}: Previous Changes"]
        S0["stash@{0}: Latest Changes"]
        S0 --> S1 --> S2
    end

    WD["Working Directory<br/>(Modified Files)"] -- "git stash push" --> S0
    S0 -- "git stash pop" --> WD

    classDef primary fill:#4F46E5,stroke:#3730A3,color:#fff
    classDef success fill:#10B981,stroke:#059669,color:#fff
    classDef warning fill:#F59E0B,stroke:#D97706,color:#fff

    class S0 success
    class S1,S2 primary
    class WD warning

4. 特定场景案例

4.1 清理历史提交记录

若需彻底删除旧的历史并减小 .git 文件夹体积,可按以下步骤操作(操作前请务必备份):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1. 创建孤立分支
git checkout --orphan latest_branch

# 2. 添加所有文件
git add -A

# 3. 提交新记录
git commit -am "chore: reset repository history"

# 4. 删除原主分支
git branch -D main

# 5. 将当前分支重命名为 main
git branch -m main

# 6. 强制更新到远端
git push -f origin main

完成后重新 Clone 仓库可获得最小体积的本地环境。

4.2 统计代码贡献量

通过 git log 结合 awk 统计特定开发者的代码增减行数:

1
git log --author="liuwei" --since=2023-10-25 --until=2023-10-31 --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "Added lines: %s, Removed lines: %s, Total lines: %s\n", add, subs, loc }' -

4.3 清理分支

1
2
3
4
5
6
# 清理远程删除的分支
git fetch --prune --prune-tags


# 删除所有远程已删除的本地分支
git branch -vv | grep ': gone]' | awk '{print $1}' | xargs git branch -d

5. Git 插件或优化

终端

  • LazyGit (终端 Git 图形界面,太复杂了不想学)
    • 强烈推荐:如果你觉得敲 Git 命令繁琐,LazyGit 是必装的。它是一个终端 UI 工具,支持鼠标和键盘操作。
    • 功能:快速查看 Diff、一键 Stage/Unstage 文件、极其方便的解决合并冲突(Conflict)、交互式 Rebase。在 Ghostty 中运行非常流畅。
  • 交互式插件:Forgit (基于 fzf)
    • 作用:为 Git 命令加上 “ 模糊搜索 “ 能力。
    • ga:进入交互模式,按 Enter 选中要 add 的文件(带预览)。
    • glo:交互式查看 Log,按 / 搜索,右侧实时显示 diff。
  • gitUI (挺对胃口)

Diff 美化

  • Git Delta(复杂)
    • 作用:让你的 git diffgit show 输出带有语法高亮、行号和并排对比模式(Side-by-side),比默认的 diff 更易读且美观。
  • diff-so-fancy (简约好看)
    • 现在用的
  • diff vs delta vs fancy
diff_默认 diff_delta diff_fuc

6. 参考资料