Git: Git差异比较与变更分析

差异比较是理解代码变更的重要工具。通过比较不同版本之间的差异,可以清楚地看到修改了什么、为什么修改、修改的影响范围。

1. 差异比较基础

(1) Git的三个区域

Git有三个重要的区域,理解它们是掌握diff的关键:

100%
graph TB
    A[工作区<br/>Working Directory] -->|git add| B[暂存区<br/>Staging Area]
    B -->|git commit| C[版本库<br/>Repository]
    
    A -.->|git diff| B
    B -.->|git diff --staged| C
    A -.->|git diff HEAD| C
    
    style A fill:#fff3cd
    style B fill:#d4edda
    style C fill:#c3e6cb

(2) 差异比较的场景

(3) diff命令概述

git diff 用于显示差异,基本用法:

BASH
git diff [options] [<commit>] [--] [<path>...]

2. 比较三个区域

(1) 工作区 vs 暂存区

比较工作区和暂存区的差异,即未暂存的修改。

▶ 示例:查看未暂存的修改

BASH
# 修改文件
echo "new line" >> README.md

# 查看工作区修改(相对于暂存区)
git diff

# 输出:
# diff --git a/README.md b/README.md
# index abc1234..def5678 100644
# --- a/README.md
# +++ b/README.md
# @@ -1,3 +1,4 @@
#  # My Project
#  
#  ## Features
# +new line

# 查看特定文件
git diff README.md

# 查看多个文件
git diff file1.js file2.js

(2) 暂存区 vs 版本库

比较暂存区和版本库的差异,即已暂存但未提交的修改。

▶ 示例:查看已暂存的修改

BASH
# 添加修改到暂存区
git add README.md

# 查看暂存区修改(相对于最新提交)
git diff --staged

# 或者使用旧语法
git diff --cached

# 输出:
# diff --git a/README.md b/README.md
# index abc1234..def5678 100644
# --- a/README.md
# +++ b/README.md
# @@ -1,3 +1,4 @@
#  # My Project
#  
#  ## Features
# +new line

(3) 工作区 vs 版本库

比较工作区和版本库的差异,即所有未提交的修改。

▶ 示例:查看所有未提交的修改

BASH
# 查看工作区相对于HEAD的差异
git diff HEAD

# 查看工作区相对于某个提交的差异
git diff a1b2c3d

# 查看工作区相对于上一次提交的差异
git diff HEAD^

# 输出包含已暂存和未暂存的所有修改

3. 差异输出解读

(1) diff输出格式

▶ 示例:理解diff输出

BASH
git diff

# 输出格式说明:
# diff --git a/file.js b/file.js     <- Git差异头
# index abc1234..def5678 100644      <- 文件模式
# --- a/file.js                      <- 原文件(-表示删除)
# +++ b/file.js                      <- 新文件(+表示添加)
# @@ -1,3 +1,4 @@                     <- 变化位置(hunk头)
#  # My Project                      <- 上下文行(空格开头)
#  
#  ## Features                       <- 上下文行
# +new line                          <- 新增行(+开头)
# -old line                          <- 删除行(-开头)

(2) hunk头格式

hunk头的格式:@@ -a,b +c,d @@

TEXT 📖 仅展示
@@ -10,5 +10,7 @@ function login() {

表示:原文件第10行开始的5行,变成了新文件第10行开始的7行。

(3) 统计信息

▶ 示例:查看统计摘要

BASH
# 显示统计信息
git diff --stat

# 输出:
#  README.md | 1 +
#  file1.js  | 5 ++---
#  file2.js  | 3 +++
#  3 files changed, 5 insertions(+), 3 deletions(-)

# 显示简短统计
git diff --shortstat

# 输出:
# 3 files changed, 5 insertions(+), 3 deletions(-)

# 显示数字统计
git diff --numstat

# 输出:
# 1       0       README.md
# 2       3       file1.js
# 3       0       file2.js

4. 比较不同版本

(1) 比较两次提交

▶ 示例:提交对比

BASH
# 比较两次提交
git diff a1b2c3d d4e5f6g

# 比较某个提交和当前工作区
git diff a1b2c3d

# 比较某个提交和暂存区
git diff --staged a1b2c3d

# 比较相邻提交
git diff HEAD^ HEAD

# 查看某次提交引入的修改
git show a1b2c3d

# 等价于
git diff a1b2c3d^ a1b2c3d

(2) 比较分支

▶ 示例:分支对比

BASH
# 比较两个分支
git diff main feature

# 查看feature分支相对于main的修改
git diff main...feature

# 查看main分支相对于feature的修改
git diff feature...main

# 比较分支的某个文件
git diff main feature -- file.js

# 查看分支分叉后的修改
git diff $(git merge-base main feature) feature

(3) 比较标签

▶ 示例:版本对比

BASH
# 比较两个版本
git diff v1.0.0 v2.0.0

# 查看某个版本的修改
git diff v1.0.0 HEAD

# 查看版本发布说明
git diff v1.0.0 v2.0.0 --stat

# 输出:
#  src/auth.js   | 25 +++++++++++++++++++++++++
#  src/user.js   | 10 +++++-----
#  package.json  |  2 +-
#  3 files changed, 32 insertions(+), 5 deletions(-)

5. diff选项详解

(1) 常用选项

选项 说明 用途
--staged 暂存区vs版本库 查看已暂存的修改
--stat 显示统计 查看修改统计
--shortstat 简短统计 查看总修改量
--name-only 只显示文件名 查看修改的文件列表
--name-status 显示状态 查看文件修改状态
-w 忽略空白 忽略空格和Tab差异

▶ 示例:使用diff选项

BASH
# 只显示修改的文件名
git diff --name-only

# 输出:
# README.md
# file1.js
# file2.js

# 显示文件修改状态
git diff --name-status

# 输出:
# M  README.md     <- 修改
# A  file1.js      <- 新增
# D  file2.js      <- 删除
# R  old.js new.js <- 重命名

# 忽略空白字符差异
git diff -w

# 忽略所有空白
git diff -w --ignore-blank-lines

# 显示单词级别的差异
git diff --word-diff

# 显示颜色高亮
git diff --color-words

(2) 上下文控制

▶ 示例:控制上下文行数

BASH
# 减少上下文行数
git diff -U1

# 输出只显示1行上下文:
# @@ -10,3 +10,4 @@ function login() {
#   const user = authenticate();
# +  if (!user) return;
#   return user;

# 不显示上下文
git diff -U0

# 增加上下文行数
git diff -U10

(3) 路径限制

▶ 示例:限制比较范围

BASH
# 只比较特定文件
git diff -- file.js

# 只比较特定目录
git diff -- src/

# 排除某些文件
git diff -- . ':!*.test.js'

# 只比较某种类型的文件
git diff -- '*.js'

6. 实际应用场景

(1) 提交前检查

▶ 示例:提交前完整检查

BASH
# 1. 查看未暂存的修改
git diff

# 2. 查看已暂存的修改
git diff --staged

# 3. 查看所有修改的统计
git diff HEAD --stat

# 4. 确认无误后提交
git commit -m "feat: 添加新功能"

(2) 代码审查

▶ 示例:审查他人代码

BASH
# 查看某个提交的修改
git show a1b2c3d

# 查看某个提交的统计
git show --stat a1b2c3d

# 查看某个提交修改的文件列表
git show --name-only a1b2c3d

# 比较两个分支的差异
git diff main...feature

# 查看Pull Request的差异
git diff origin/main...origin/feature

(3) 冲突分析

▶ 示例:分析合并冲突

BASH
# 查看将要合并的内容
git diff main...feature

# 查看共同祖先到feature的修改
git diff $(git merge-base main feature) feature

# 查看可能冲突的文件
git diff --name-only main...feature

# 查看具体的冲突内容
git diff main feature -- file.js

(4) 版本回退确认

▶ 示例:确认回退内容

BASH
# 查看回退会丢失的内容
git diff HEAD HEAD~1

# 查看回退的统计
git diff HEAD HEAD~1 --stat

# 查看特定文件的回退内容
git diff HEAD HEAD~1 -- file.js

# 确认后执行回退
git reset --hard HEAD~1

❓ 常见问题

Q git diff、git diff --staged、git diff HEAD有什么区别?
A git diff 比较工作区和暂存区(未暂存的修改);git diff --staged 比较暂存区和版本库(已暂存的修改);git diff HEAD 比较工作区和版本库(所有未提交的修改)。
Q 如何只查看修改的文件名而不看具体内容?
A 使用 git diff --name-only 只显示文件名,或使用 git diff --stat 显示文件名和修改统计。
Q diff输出中的+和-是什么意思?
A 以+开头的行表示新增的内容,以-开头的行表示删除的内容,以空格开头的行是上下文(未修改)。
Q 如何比较两个分支的差异?
A 使用 git diff main feature 比较两个分支,使用 git diff main...feature 查看feature分支相对于main的修改(从分叉点开始)。
Q 如何忽略空格差异?
A 使用 git diff -w 忽略空格和Tab的差异,使用 git diff --ignore-space-at-eol 忽略行尾空格差异。

📖 小节


📝 作业

  1. 基础题:创建一个Git仓库,修改文件后使用 git diff 查看未暂存的修改,添加到暂存区后使用 git diff --staged 查看已暂存的修改。

  2. 进阶题:创建两个分支,在不同分支上修改相同文件,使用 git diff 比较两个分支的差异,分析可能产生的冲突。

  3. 挑战题:使用 git diff 的各种选项(--stat、--name-status、-w等)分析一个实际项目的修改,生成一份完整的变更报告。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏