Git 可视化差异工作流

> 在 Ubuntu 上使用命令行和图形界面差异工具,对 FPGA 项目各版本之间的 RTL 变更进行可视化比较和审查。


目录


git-diff Skill 设计

用途

git-diff skill 提供统一的可视化界面,用于比较以下场景之间的代码差异:

Skill 调用


# 比较两个标签(最常见的 FPGA 使用场景)
git-diff --tag snapshot-20260701 snapshot-20260724 --repo WebServer

# 比较分支与 main
git-diff --branch dev/fix-timing main --repo Top

# 比较工作树(未提交的更改)
git-diff --staged --repo Sensor

# 比较两个版本之间的特定文件
git-diff --file Top.v --tag snapshot-20260601 snapshot-20260701 --repo Top

# 在 GUI 差异工具(meld)中打开
git-diff --branch main dev/fix-timing --repo Top --gui

实现:/home/user/.local/bin/git-diff


#!/bin/bash
# git-diff -- FPGA 仓库的可视化差异工具
# 用法:git-diff [--tag|--branch|--file|--staged] [...] --repo <名称> [--gui] [--stat]

set -euo pipefail

REPOS_CONF="/home/user/.local/share/multi-repo/repos.conf"

RED='\033[0;31m'; GREEN='\033[0;32m'
YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'

usage() {
cat << EOF
用法:git-diff [选项] --repo <项目名称>

比较来源:
--tag <标签1> <标签2> 比较两个标签
--branch <分支1> <分支2> 比较两个分支
--commit <提交1> <提交2> 比较两个提交(完整 SHA 或 HEAD~N)
--staged 显示已暂存(缓存)的更改
--file <路径> 将差异范围限定于特定文件
--stat 仅显示差异统计摘要

输出选项:
--gui 在可视化差异工具(meld)中打开
--vscode 在 VSCode 差异视图中打开
--color 强制彩色输出(默认:自动)

仓库选择:
--repo <名称> 仓库短名称(必填)

示例:
git-diff --tag v1.0 v2.0 --repo WebServer
git-diff --branch main dev/feature --repo Top --gui
git-diff --file Top.v --commit HEAD~3 HEAD --repo Common --stat
EOF
exit 1
}

# 从短名称解析仓库路径
get_repo_path() {
local name="$1"
while IFS= read -r line; do
if [[ "$line" =~ ^DECLARE[[:space:]]+([^[:space:]]+)[[:space:]]+(/[^[:space:]]+) ]]; then
if [ "${BASH_REMATCH[1]}" = "$name" ]; then
echo "${BASH_REMATCH[2]}"
return 0
fi
fi
done < "$REPOS_CONF"
return 1
}

# ======== 解析参数 ========

MODE=""
TAG1=""; TAG2=""
BRANCH1=""; BRANCH2=""
COMMIT1=""; COMMIT2=""
FILE_PATH=""
REPO_NAME=""
USE_GUI=false
USE_VSCODE=false
SHOW_STAT=false

while [ $# -gt 0 ]; do
case "$1" in
--tag) MODE="tag"; TAG1="$2"; TAG2="$3"; shift 3 ;;
--branch) MODE="branch"; BRANCH1="$2"; BRANCH2="$3"; shift 3 ;;
--commit) MODE="commit"; COMMIT1="$2"; COMMIT2="$3"; shift 3 ;;
--staged) MODE="staged"; shift ;;
--file) FILE_PATH="$2"; shift 2 ;;
--stat) SHOW_STAT=true; shift ;;
--gui) USE_GUI=true; shift ;;
--vscode) USE_VSCODE=true; shift ;;
--repo) REPO_NAME="$2"; shift 2 ;;
*) usage ;;
esac
done

[ -z "$REPO_NAME" ] && usage
[ -z "$MODE" ] && usage

REPO_PATH=$(get_repo_path "$REPO_NAME") || {
echo -e "${RED}错误:${NC} 未知项目 '$REPO_NAME'"
exit 1
}
[ -d "$REPO_PATH/.git" ] || {
echo -e "${RED}错误:${NC} $REPO_PATH 不是 Git 仓库"
exit 1
}

# ======== 构建 diff 命令 ========

DIFF_ARGS=""
SOURCE1=""; SOURCE2=""
DESCRIPTION=""

case "$MODE" in
tag)
SOURCE1="$TAG1"; SOURCE2="$TAG2"
DIFF_ARGS="$TAG1..$TAG2"
DESCRIPTION="标签差异:$TAG1 → $TAG2"
;;
branch)
SOURCE1="$BRANCH1"; SOURCE2="$BRANCH2"
DIFF_ARGS="$BRANCH1..$BRANCH2"
DESCRIPTION="分支差异:$BRANCH1 → $BRANCH2"
;;
commit)
SOURCE1="$COMMIT1"; SOURCE2="$COMMIT2"
DIFF_ARGS="$COMMIT1..$COMMIT2"
DESCRIPTION="提交差异:${COMMIT1:0:7} → ${COMMIT2:0:7}"
;;
staged)
SOURCE1="(已暂存)"; SOURCE2="(HEAD)"
DIFF_ARGS="--cached"
DESCRIPTION="已暂存的更改"
;;
esac

echo -e "${CYAN}=== ${DESCRIPTION} ===${NC}"
echo -e " 仓库:${YELLOW}$REPO_NAME${NC} → $REPO_PATH"
echo ""

cd "$REPO_PATH"

# ======== 执行 ========

if [ "$SHOW_STAT" = true ]; then
# 显示差异统计
echo -e "${CYAN}--- 差异统计 ---${NC}"
git diff --stat $DIFF_ARGS -- "$FILE_PATH"
echo ""

# 显示范围内的提交短日志
if [ "$MODE" != "staged" ]; then
echo -e "${CYAN}--- 范围内的提交 ---${NC}"
git log --oneline --no-merges $DIFF_ARGS 2>/dev/null || \
echo " (此范围内无提交,或无法解析)"
fi

elif [ "$USE_GUI" = true ]; then
# 在 meld 中打开
if [ "$MODE" = "staged" ]; then
echo "正在 meld 中打开已暂存的更改..."
# meld 不直接支持 --cached 参数,使用 difftool
git difftool --tool=meld --cached -- "$FILE_PATH"
else
echo "正在 meld 中打开差异..."
git difftool --tool=meld $DIFF_ARGS -- "$FILE_PATH"
fi

elif [ "$USE_VSCODE" = true ]; then
# 显示已更改的文件列表
changed_files=$(git diff --name-only $DIFF_ARGS -- "$FILE_PATH")
echo -e "${CYAN}已更改的文件:${NC}"
if [ -z "$changed_files" ]; then
echo " (无更改)"
else
echo "$changed_files" | while read -r f; do
echo -e " ${YELLOW}$f${NC}"
done
echo ""
echo "在 VSCode 中打开:code --diff $SOURCE1 $SOURCE2"
echo "或逐个比较文件:"
echo "$changed_files" | while read -r f; do
echo " code --diff $SOURCE1:$f $SOURCE2:$f"
done
fi

else
# 终端差异显示(彩色)
git diff --color --patch-with-stat $DIFF_ARGS -- "$FILE_PATH"
fi

# ======== 汇总 ========

echo ""
echo -e "${CYAN}=== 汇总 ===${NC}"

if [ "$MODE" != "staged" ]; then
file_count=$(git diff --name-only $DIFF_ARGS -- "$FILE_PATH" 2>/dev/null | wc -l)
add_lines=$(git diff --numstat $DIFF_ARGS -- "$FILE_PATH" 2>/dev/null | awk '{sum+=$1} END {print sum+0}')
del_lines=$(git diff --numstat $DIFF_ARGS -- "$FILE_PATH" 2>/dev/null | awk '{sum+=$2} END {print sum+0}')
echo -e " 文件变更数:${GREEN}$file_count${NC}"
echo -e " 新增行数: ${GREEN}+$add_lines${NC}"
echo -e " 删除行数: ${RED}-$del_lines${NC}"

# 列出已更改文件及其变更量
echo ""
echo -e "${CYAN}已变更的文件:${NC}"
git diff --stat $DIFF_ARGS -- "$FILE_PATH" 2>/dev/null | tail -n +1
fi


chmod +x /home/user/.local/bin/git-diff


Git 日志可视化

单行日志加图形显示


# 紧凑模式:所有分支、图形、每提交一行
git log --oneline --graph --all --decorate -30

# 附带日期(ISO 格式)
git log --oneline --graph --all --decorate --date=short \
--format="%C(auto)%h%d %C(reset)%s %C(dim green)(%ad)%C(reset)" -20

# 仅当前分支
git log --oneline --graph --decorate -20

# 带颜色显示分支
git log --oneline --graph --all --decorate \
--format="%C(yellow)%h%Creset %C(cyan)%d%Creset %s %Cgreen(%cr)%Creset" -30

筛选选项


# 影响特定文件的提交
git log --oneline --graph -- Top.v

# 按作者筛选的提交
git log --oneline --author="BuckHuang" -20

# 两个日期之间的提交
git log --oneline --after="2026-07-01" --before="2026-07-24"

# 包含特定关键字的提交
git log --oneline --grep="fix timing"

# 仅显示合并提交
git log --oneline --merges

# 排除合并提交
git log --oneline --no-merges

增强的自定义日志别名

添加到 /home/user/.gitconfig


[alias]
    lg = log --oneline --graph --all --decorate -30
    lg2 = log --oneline --graph --all --decorate \
           --format="%C(yellow)%h%C(reset) %C(cyan)%d%C(reset) %s %C(dim green)(%ar) %C(dim white)<%an>%C(reset)" -20
    lg3 = log --graph --abbrev-commit --decorate \
           --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n''          %C(white)%s%C(reset) %C(dim white)- %an%C(reset)'
    hist = log --pretty=format:"%h %ad | %s%d [%an]" --graph --date=short
    fl = log --follow -p --  # 显示文件的完整历史及差异

使用这些别名:


git lg          # 快速概览
git lg2         # 含作者 + 相对时间
git lg3         # 更详细,双行格式
git fl Top.v    # Top.v 的完整历史及内容差异

分支与标签可视化


# 显示所有分支及其最后一次提交
git branch -vva

# 按最后提交日期排序显示分支
git for-each-ref --sort=-committerdate refs/heads/ \
--format="%(color:yellow)%(refname:short)%(color:reset) | %(committerdate:short) | %(subject)"

# 显示标签及其注解
git tag -n9 # 显示标签名称,最多 9 行注解

# 可视化分支拓扑(合并前很有用)
git log --oneline --graph --decorate main..dev/fix-timing

FPGA 专用:查找与时序相关的更改


# 查找涉及时序约束的提交
git log --oneline --all -- "*.xdc" "*.sdc"

# 查找特定模块最后一次被修改的时间
git log --oneline -- Top.v | head -5

# Blame:查看每一行是谁修改的
git blame Top.v | head -20


Ubuntu 上的 GUI 差异工具

1. VSCode 内置差异查看器

VSCode 内置了强大的差异查看器,支持 Verilog 语法高亮。

命令行调用:

# 比较两个文件
code --diff file_v1.v file_v2.v

# 比较两个提交之间的文件
code --diff HEAD~3:Top.v HEAD:Top.v

# 比较两个分支之间的文件
code --diff main:Top.v dev/fix-timing:Top.v

# 在 VSCode 中打开整个仓库进行并排比较
# 使用 "GitLens" 或 "Git History" 扩展获取更丰富的视图

在 VSCode 内部操作:
快捷键 操作
Ctrl+Shift+G 打开源代码管理面板
点击已更改的文件 打开并排差异视图
F1 → "Git:查看文件历史" 完整历史记录及每次提交的差异
F1 → "GitLens:比较" 比较任意两个引用
推荐的 VSCode 差异查看扩展:

2. Meld

Meld 是专用的可视化差异和合并工具。

安装:

sudo apt install -y meld

用法:

# 比较两个目录(整个仓库版本)
meld ~/work/FPGA_Prj/Project/Top/v1/ ~/work/FPGA_Prj/Project/Top/v2/

# 比较两个文件
meld Top_v1.v Top_v2.v

# 三路合并(共同祖先 + 两个分支)
meld base.v ours.v theirs.v

# 配置 git 使用 meld 作为差异工具
git config --global diff.tool meld
git config --global difftool.meld.cmd 'meld "$LOCAL" "$REMOTE"'
git config --global difftool.prompt false

# 然后使用:
git difftool HEAD~3 HEAD
git difftool main..dev/fix-timing

Meld 使用技巧:

3. gitk

gitk 是一个轻量级的 Tcl/Tk 图形界面工具,用于浏览 git 历史。


# 安装
sudo apt install -y gitk

# 在当前仓库中启动
gitk --all &

# 显示特定分支
gitk main dev/fix-timing &

# 显示影响特定文件的提交
gitk -- Top.v &

# 显示两个日期之间的提交
gitk --after="2026-07-01" --before="2026-07-24" &

gitk 功能:

4. Git Cola

一个更现代的 Git 图形界面工具,内置差异查看功能。


sudo apt install -y git-cola

# 启动
git-cola &

5. diff-so-fancy(终端增强)

使终端中的 git diff 输出更易读,颜色和格式更佳。


# 安装
sudo apt install -y npm || true
sudo npm install -g diff-so-fancy

# 配置 git
git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX"
git config --global interactive.diffFilter "diff-so-fancy --patch"

# 现在 git diff / git show 将使用此格式


FPGA RTL 变更审查

比较场景

场景 1:FPGA 综合前审查 RTL 变更

# 获取所有更改的概览
git-diff --branch main dev/new-pipeline --repo Camera --stat

# 审查模块端口变更
git-diff --file Camera_Pipeline.sv --branch main dev/new-pipeline --repo Camera

# 在 VSCode 中打开以进行语法高亮审查
git-diff --branch main dev/new-pipeline --repo Camera --vscode

场景 2:比较快照标签以识别回归问题

# 查找上一个已知正常版本与当前版本之间的变更
git-diff --tag snapshot-20260701 snapshot-20260724 --repo Top --stat

# 聚焦时序约束文件
git-diff --file "*.xdc" --tag snapshot-20260701 snapshot-20260724 --repo Top

# 聚焦顶层模块
git-diff --file Top.v --tag snapshot-20260701 snapshot-20260724 --repo Top

场景 3:推送前审查更改

# 显示所有未提交的更改
cd /home/user/work/FPGA_Prj/Project/WebServer
git diff --stat
git diff --color | less -R

# 仅显示已暂存的更改(将要提交的内容)
git diff --cached

# 显示将要推送的内容
git log --oneline origin/main..HEAD
git diff --stat origin/main..HEAD

RTL 专用差异比较技巧

1. 忽略仅空白字符的更改(Verilog 重新格式化中常见):

git diff -w main..dev/feature
git diff --ignore-space-change --ignore-blank-lines main..dev/feature

2. 仅对比端口声明:

# 提取端口列表并比较
diff <(git show main:Top.v | grep -E '^\s*(input|output|inout)') \
     <(git show dev:Top.v | grep -E '^\s*(input|output|inout)')

3. 对比模块实例化(检查哪些子模块发生了变更):

# 比较哪些模块被实例化了
diff <(git show main:Top.v | grep -oP '\w+\s+\w+\s*\(' | sort) \
     <(git show dev:Top.v | grep -oP '\w+\s+\w+\s*\(' | sort)

4. 检查参数/泛型是否变更:

git diff main..dev/feature -- "*.v" "*.sv" | grep -E "^\+.*parameter|^\-.*parameter" | head -20

FPGA RTL 差异审查检查清单

在批准 RTL 变更之前,请验证以下内容:


版本回滚:revert 与 reset 对比

决策矩阵

场景 使用命令 操作说明
撤销已推送到共享分支的提交 git revert 创建一个撤销更改的新提交
撤销尚未推送的本地提交 git reset 重写本地历史
取消暂存文件(保留更改) git reset git reset HEAD <文件>
丢弃工作树中的更改 git checkout git checkout -- <文件>git restore <文件>
临时回到已知正常状态 git checkout git checkout <标签>

git revert(安全 -- 用于已推送的提交)


# 撤销最后一次提交(创建一个新的"撤销"提交)
git revert HEAD

# 撤销特定提交(不一定是最新的)
git revert abc1234

# 撤销一个范围的提交(每个提交对应一个 revert 提交)
git revert abc1234..def5678

# 撤销但不自动提交(先审查)
git revert --no-commit HEAD~3..HEAD
git diff --cached # 审查
git commit -m "Revert:回滚时序变更"

优点: 缺点:

git reset(本地使用 -- 推送前)


# 软重置:保留更改在暂存区
git reset --soft HEAD~3   # 撤销最后 3 个提交,保留更改在暂存区

# 混合重置(默认):保留更改在工作树中,取消暂存
git reset HEAD~3 # 撤销最后 3 个提交,更改变为未暂存状态
git reset HEAD~1 # 撤销最后一次提交

# 硬重置:丢弃所有更改
git reset --hard HEAD~3 # 完全删除最后 3 个提交及其更改

# 重置到特定提交
git reset --hard abc1234

# 重置到远程状态
git fetch origin
git reset --hard origin/main

优点: 缺点:

FPGA 专用回滚模式

模式 1:综合结果不佳后快速回滚

# 查看最近几次提交做了什么
git log --oneline -5

# 软重置以撤销但保留更改(在编辑器中审查)
git reset --soft HEAD~1
# 或:硬重置以完全丢弃
git reset --hard HEAD~1

# 验证你是否回到了已知正常状态
git log --oneline -3

模式 2:使用标签回滚(FPGA 开发推荐)

# 列出最近的快照标签
git tag -l "snapshot-*" | sort -r | head -10

# 回到已知正常的快照
git checkout snapshot-20260701

# 从该快照创建新分支以修复问题
git checkout -b fix/rollback-from-20260701

# 或:将 main 硬重置到该标签(注意:仅在独立工作时使用)
git checkout main
git reset --hard snapshot-20260701
git push --force-with-lease origin main

模式 3:使用 revert 安全回滚

# 查找哪个提交引入了问题
git log --oneline -10

# 撤销有问题的提交
git revert abc1234 --no-edit

# 推送 revert 提交
git push origin main

restore 命令(Git 2.23+)

更新的 restore 命令将"撤销工作"与"销毁历史"分离:


# 丢弃工作树中的更改(类似 git checkout -- <文件>)
git restore Top.v

# 取消暂存文件(类似 git reset HEAD <文件>)
git restore --staged Top.v

# 将文件恢复到之前的版本
git restore --source=HEAD~2 Top.v

# 从其他分支恢复文件
git restore --source=main dev/fix-timing -- Top.v


与日常工作流集成

工作流:综合前差异检查


#!/bin/bash
# pre-synth-check.sh -- FPGA 综合前审查所有更改

REPO="$1"
echo "=== 综合前审查:$REPO ==="

cd $(get_repo_path "$REPO")

# 1. 检查工作树状态
echo ""
echo "--- 未提交的更改 ---"
if git diff-index --quiet HEAD --; then
echo " (干净)"
else
git diff --stat
fi

# 2. 与上一次快照比较
echo ""
echo "--- 自上次快照以来的更改 ---"
LAST_TAG=$(git tag -l "snapshot-*" --sort=-creatordate | head -1)
if [ -z "$LAST_TAG" ]; then
echo " 未找到快照标签"
else
echo " 上次标签:$LAST_TAG"
git diff --stat $LAST_TAG..HEAD
fi

# 3. 显示所有发生变更的模块
echo ""
echo "--- 已修改的 Verilog/SystemVerilog 文件 ---"
git diff --name-only $LAST_TAG..HEAD 2>/dev/null | grep -E '\.(v|sv|vh)$' || echo " (无)"

echo ""
echo "审查完成。请先批准所有更改,然后再运行综合。"

工作流:综合后打标签存档


# 综合成功后,为当前状态打标签
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
git tag -a "synth-ok-${TIMESTAMP}" -m "已验证:综合通过 ${TIMESTAMP}"

# 将标签推送到远程
git push origin "synth-ok-${TIMESTAMP}"

示意图:Git 状态流转图


工作树                    暂存区                    本地仓库            远程仓库
    |                       |                        |                  |
    |  git add              |                        |                  |
    |---------------------->|                        |                  |
    |                       |  git commit            |                  |
    |                       |----------------------->|                  |
    |                       |                        |  git push        |
    |                       |                        |----------------->|
    |                       |                        |                  |
    |  git restore          |  git restore --staged  |                  |
    |<----------------------|<-----------------------|                  |
    |                       |                        |                  |
    |                       |                        |  git revert      |
    |  git reset --soft     |                        |  (撤销提交)     |
    |<----------------------|<-----------------------|                  |
    |  git reset --mixed    |                        |                  |
    |<----------------------|<-----------------------|                  |
    |  git reset --hard     |                        |                  |
    |<----------------------|<-----------------------|                  |


快速参考


# 在终端中查看差异
git diff                             # 工作树 vs HEAD
git diff --staged                    # 暂存区 vs HEAD
git diff main..dev/feature           # 分支 vs 分支
git diff tag1..tag2                  # 标签 vs 标签

# 查找提交
git log --oneline --graph --all -20 # 整体概览
git log --oneline main..HEAD # 将要推送的内容
git log -p Top.v # 文件历史及差异

# 使用 git-diff skill
git-diff --tag v1 v2 --repo WebServer --stat
git-diff --branch main dev --repo Top --gui

# 回滚
git revert HEAD # 安全:撤销最后一次提交
git reset --soft HEAD~1 # 撤销提交,保留更改在暂存区
git reset --hard HEAD~1 # 销毁最后一次提交(仅限本地使用!)
git restore Top.v # 丢弃文件更改