> 在 Ubuntu 上使用命令行和图形界面差异工具,对 FPGA 项目各版本之间的 RTL 变更进行可视化比较和审查。
git-diff skill 提供统一的可视化界面,用于比较以下场景之间的代码差异:
main vs dev/feature)snapshot-20260701 vs snapshot-20260724)HEAD~3 vs HEAD)
# 比较两个标签(最常见的 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 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
# 查找涉及时序约束的提交
git log --oneline --all -- "*.xdc" "*.sdc"
# 查找特定模块最后一次被修改的时间
git log --oneline -- Top.v | head -5
# Blame:查看每一行是谁修改的
git blame Top.v | head -20
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:比较" |
比较任意两个引用 |
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 使用技巧:
*.v、*.sv 仅显示 Verilog 文件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 功能:
一个更现代的 Git 图形界面工具,内置差异查看功能。
sudo apt install -y git-cola
# 启动
git-cola &
使终端中的 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 将使用此格式
# 获取所有更改的概览
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
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
在批准 RTL 变更之前,请验证以下内容:
.xdc/.sdc)已按需更新ila_debug 或 synthesis keep 属性| 场景 | 使用命令 | 操作说明 |
|---|---|---|
| 撤销已推送到共享分支的提交 | git revert |
创建一个撤销更改的新提交 |
| 撤销尚未推送的本地提交 | git reset |
重写本地历史 |
| 取消暂存文件(保留更改) | git reset |
git reset HEAD <文件> |
| 丢弃工作树中的更改 | git checkout |
git checkout -- <文件> 或 git restore <文件> |
| 临时回到已知正常状态 | git checkout |
git checkout <标签> |
# 撤销最后一次提交(创建一个新的"撤销"提交)
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 --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
优点:
--force 覆盖远程)
# 查看最近几次提交做了什么
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 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 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 # 丢弃文件更改