Skip to content

05 常用指令

1. git reflog

git reflog 是 Git 中一个非常强大的工具,全称是 reference log(引用日志),用于记录本地仓库中引用(如分支、HEAD 等)的更新历史。它就像一个“时间机器”,可以帮助开发者追踪和恢复那些可能已经“丢失”的提交或操作,尤其是在误操作(如错误删除分支或重置提交)后。


什么是 Git Reflog?

git reflog 记录了本地仓库中引用(如 HEAD、分支或标签)的每次更新,包括: - 提交(git commit) - 分支切换(git checkoutgit switch) - 重置(git reset) - 变基(git rebase) - 合并(git merge) - 创建或删除分支(git branch

这些记录存储在本地仓库的 .git/logs/refs/heads/.git/logs/HEAD 文件中,仅存在于本地,不会被推送到远程仓库(如 git pushgit fetchgit clone)。因此,git reflog 是一个纯粹的本地工具,提供了恢复误操作的“安全网”。

git log 不同,git log 显示的是提交历史(基于提交的祖先关系),而 git reflog 记录的是引用的移动历史(包括 HEAD 和分支的每次变更),因此它能捕获一些在 git log 中不可见的操作,比如被重置或删除的提交。


基本用法

运行以下命令可以查看 git reflog 的默认输出:

git reflog

这将显示 HEAD 的引用日志,输出格式如下:

a1b2c3d HEAD@{0}: commit: 添加了新功能
d4e5f6g HEAD@{1}: reset: moving to HEAD^ 
h7i8j9k HEAD@{2}: checkout: moving from main to feature-branch
l0m1n2o HEAD@{3}: commit: 修复了 bug

  • a 1 b 2 c 3 d:提交的 SHA-1 哈希值(前 7 位)。
  • HEAD@{0}:表示这是 HEAD 的最新状态,数字递增表示更早的状态(如 HEAD@{1}HEAD@{2})。
  • commit/reset/checkout:操作类型。
  • 描述:操作的详细信息,例如提交信息或分支切换。

如果想查看特定分支的引用日志,可以指定分支名称:

git reflog <branch_name>
例如:
git reflog main
这会显示 main 分支的引用日志。


子命令

git reflog 支持多个子命令,用于管理引用日志或执行特定操作。以下是常用的子命令:

  1. show(默认)
  2. 显示指定引用的日志,默认显示 HEAD 的日志。
  3. 语法:git reflog show <ref>
  4. 示例:
    git reflog show main
    
  5. 备注:git reflog 默认运行 git reflog show,等同于 git log -g --abbrev-commit --pretty=oneline

  6. expire

  7. 用于清理过旧或不可达的引用日志条目,以优化存储空间。
  8. 语法:git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--all]
  9. 示例:
    git reflog expire --expire=30.days --all
    
  10. 默认情况下,引用日志保留 90 天,不可达条目保留 30 天(由配置 gc.reflogExpiregc.reflogExpireUnreachable 控制)。
  11. 注意:此命令可能导致数据丢失,建议使用 --dry-run 选项预览将被删除的条目:

    git reflog expire --dry-run --expire=30.days
    

  12. delete

  13. 删除指定的引用日志条目。
  14. 语法:git reflog delete <ref>@{<specifier>}
  15. 示例:
    git reflog delete master@{2}
    
  16. 注意:删除引用日志可能导致无法恢复某些提交,需谨慎操作。

  17. exists

  18. 检查某个引用是否有关联的引用日志。
  19. 语法:git reflog exists <ref>
  20. 示例:
    git reflog exists main
    
  21. 如果引用日志存在,返回状态码 0;否则返回非 0。

常见使用场景

以下是 git reflog 的几种典型应用场景,帮助开发者解决实际问题:

1. 恢复误删除的分支

假设你不小心删除了一个分支(例如 feature-branch),可以通过 git reflog 找回:

# 查看 HEAD 的引用日志
git reflog
输出可能如下:
a1b2c3d HEAD@{0}: reset: moving to main
d4e5f6g HEAD@{1}: checkout: moving from feature-branch to main
h7i8j9k HEAD@{2}: commit: 添加了新功能 (feature-branch)

找到 feature-branch 最后一次提交的哈希值(例如 h7i8j9k),然后恢复分支:

git checkout -b feature-branch h7i8j9k
这会重新创建一个指向 h7i8j9k 提交的 feature-branch 分支。

2. 恢复被重置的提交

如果你执行了 git reset --hard 并丢失了一些提交,可以通过 git reflog 找回:

git reflog
输出可能如下:
a1b2c3d HEAD@{0}: reset: moving to HEAD^
d4e5f6g HEAD@{1}: commit: 添加了新功能
h7i8j9k HEAD@{2}: commit: 修复了 bug

找到丢失的提交(例如 d4e5f6g),然后重置回去:

git reset --hard d4e5f6g
或者创建一个新分支:
git checkout -b recovered-branch d4e5f6g

3. 修复错误的变基

如果 git rebase 导致分支状态异常,可以用 git reflog 找到变基前的提交:

git reflog
输出可能如下:
a1b2c3d HEAD@{0}: rebase: 完成变基
d4e5f6g HEAD@{1}: commit: 添加了新功能
h7i8j9k HEAD@{2}: commit: 修复了 bug

找到变基前的提交(例如 d4e5f6g),然后重置:

git reset --hard d4e5f6g

4. 查看 stash 历史

git reflog 也可以查看 git stash 的历史:

git reflog stash
输出可能如下:
a1b2c3d stash@{0}: stash: 保存了工作区变更
d4e5f6g stash@{1}: stash: 保存了另一个变更

可以用以下命令恢复某个 stash:

git stash apply stash@{1}

5. 限制输出条目

如果引用日志很长,可以用 --max-count-n 限制输出条目数:

git reflog -n 5
这将只显示最近 5 条记录。

6. 查看特定时间范围

可以用时间修饰符查看特定时间段的引用日志:

git reflog --since=1.week.ago
这会显示过去一周的引用日志。


注意事项

  1. 本地性git reflog 仅记录本地操作,不会同步到远程仓库。如果你在其他机器上克隆仓库,引用日志不会跟随。
  2. 日志清理:引用日志默认保留 90 天(可达条目)和 30 天(不可达条目)。Git 会通过 git gc 自动清理过旧条目。如果需要永久保留,需调整配置:
    git config gc.reflogExpire never
    git config gc.reflogExpireUnreachable never
    
  3. 数据丢失风险:使用 git reflog expiregit reflog delete 时要小心,可能导致无法恢复某些提交。
  4. 中文乱码问题:在某些环境下(如 Windows),git reflog 输出可能出现中文乱码。可以通过设置 Git 的编码解决:
    git config --global core.quotepath false
    git config --global i18n.logOutputEncoding utf-8
    

git log 的区别

特性 git log git reflog
记录内容 提交历史(基于祖先关系) 引用更新历史(HEAD 和分支的移动)
范围 公共记录,随仓库同步到远程 本地记录,不随仓库同步
用途 查看提交历史、查找特定提交 恢复丢失提交、追踪引用变更
输出格式 详细的提交信息(作者、日期、消息等) 简洁的引用变更记录(哈希、操作、时间)

2. git reset:撤销更改或回退版本

定义

git reset 用于撤销暂存区的更改或回退到指定的历史版本,调整 HEAD 指针和文件状态。

主要模式

git reset 有三种模式,影响工作区、暂存区和本地仓库的程度不同:

  1. --soft
  2. 仅移动 HEAD 指针到指定提交,保留暂存区和工作区的更改。
  3. 用途:撤销提交,但保留更改以重新提交。
  4. --mixed(默认):
  5. 移动 HEAD 指针并重置暂存区到指定提交,工作区文件不变。
  6. 用途:撤销暂存区内容,将更改放回工作区。
  7. --hard
  8. 移动 HEAD 指针,重置暂存区和工作区到指定提交。
  9. 用途:彻底回退到某版本,丢弃后续更改(谨慎使用)。

常用命令

  • 撤销暂存(从暂存区移回工作区)
    git reset <file>  # 撤销单个文件的暂存
    git reset         # 撤销所有暂存
    
  • 回退到指定提交
    git reset --soft <commit-hash>  # 保留更改,仅回退提交
    git reset --mixed <commit-hash> # 回退提交和暂存区
    git reset --hard <commit-hash>  # 回退提交、暂存区和工作区
    
  • <commit-hash> 可通过 git log 获取。

示例

  1. 撤销暂存
  2. 修改 index.html 并暂存:
    git add index.html
    git status  # 显示已暂存
    
  3. 撤销暂存:
    git reset index.html
    git status  # 显示已修改,未暂存
    
  4. 回退提交
  5. 查看提交历史:
    git log --oneline
    # 示例输出:abc1234 Update index.html
    
  6. 回退到指定提交,保留更改:
    git reset --soft abc1234
    git status  # 显示更改在暂存区
    
  7. 彻底回退:
    git reset --hard abc1234
    git status  # 显示工作区干净
    

注意事项

  • 危险操作--hard 会永久丢失未提交的更改,建议先备份。
  • 远程仓库:若已推送(git push),回退后需谨慎使用 git push --force(通知团队)。
  • HEAD 指针
  • HEAD^ 表示上一个提交。
  • HEAD~n 表示前 n 个提交。

3. git diff:查看文件差异

定义

git diff 用于比较文件在不同工作区域之间的差异,帮助检查修改内容。

主要用法

  1. 工作区 vs 暂存区
    git diff
    
  2. 显示工作区中已修改但未暂存的更改。
  3. 暂存区 vs 本地仓库
    git diff --cached  # 或 git diff --staged
    
  4. 显示暂存区与上次提交(HEAD)的差异。
  5. 工作区 vs 本地仓库
    git diff HEAD
    
  6. 显示工作区与上次提交的差异(包括已暂存和未暂存)。
  7. 比较特定文件
    git diff <file>
    
  8. 比较两个提交
    git diff <commit1> <commit2>
    

示例

  1. 检查工作区修改
  2. 修改 index.html
    <!-- 添加一行 -->
    <p>New line</p>
    
  3. 查看差异:
    git diff index.html
    # 输出示例:
    # <hr style="border: none; border-top: 3px solid #007bff;" /> a/index.html
    # +++ b/index.html
    # @@ -1,3 +1,4 @@
    #  <html>
    #    <body>
    # +    <p>New line</p>
    
  4. 检查暂存区差异
  5. 暂存修改:
    git add index.html
    git diff --cached
    # 显示已暂存的差异
    

注意事项

  • 输出格式:绿色(+)表示添加,红色(-)表示删除。
  • 空输出:若无差异,git diff 无输出。
  • 可视化工具:可用 git difftool 配合工具(如 VS Code、Meld)查看差异。

4. git rm:从 Git 管理中移除文件

定义

git rm 用于从工作区和暂存区删除文件,并记录删除操作以提交。

主要用法

  1. 删除文件并暂存删除操作
    git rm <file>
    
  2. 删除工作区的文件并将其移除标记添加到暂存区。
  3. 仅从 Git 跟踪中移除(保留工作区文件)
    git rm --cached <file>
    
  4. 停止跟踪文件,但保留工作区中的文件。

示例

  1. 删除文件
  2. 删除 oldfile.txt
    git rm oldfile.txt
    git status  # 显示:deleted: oldfile.txt
    git commit -m "Remove oldfile.txt"
    
  3. 停止跟踪但保留文件
  4. 停止跟踪 config.local
    git rm --cached config.local
    git status  # 显示:deleted: config.local
    git commit -m "Stop tracking config.local"
    
  5. 文件仍保留在工作区。

注意事项

  • 提交确认git rm 仅修改暂存区,需 git commit 确认删除。
  • 恢复删除
  • 若未提交,可用 git reset <file> 撤销暂存,git checkout -- <file> 恢复文件。
  • 若已提交,需回退提交(见 git reset)。
  • .gitignore:移除跟踪后,建议将文件添加到 .gitignore 避免再次跟踪。

5. .gitignore:忽略文件

定义

.gitignore 是一个文本文件,定义 Git 忽略的文件或目录模式,避免将无关文件(如临时文件、日志、依赖)纳入版本控制。

创建与配置

  1. 创建 .gitignore
    touch .gitignore
    
  2. 添加忽略规则
  3. 每行一个模式,支持通配符。
  4. 示例内容:
    # 忽略日志文件
    *.log
    # 忽略 node_modules 目录
    node_modules/
    # 忽略特定文件
    .env
    # 忽略子目录中的文件
    dist/*.js
    

常用规则

  • 通配符
  • *:匹配任意字符(除路径分隔符 /),如 *.log 忽略所有 .log 文件。
  • **:匹配任意层级目录,如 **/temp/ 忽略所有 temp 文件夹。
  • !:排除忽略,如 !important.log 强制跟踪。
  • 目录:以 / 结尾,如 node_modules/
  • 注释:以 # 开头。

示例

  1. 忽略常见文件
    echo "node_modules/" >> .gitignore
    echo "*.log" >> .gitignore
    echo ".env" >> .gitignore
    git add .gitignore
    git commit -m "Add .gitignore"
    
  2. 检查忽略效果
  3. 创建 test.log 文件,运行:
    git status  # test.log 不会出现在未跟踪文件列表
    

注意事项

  • 生效范围.gitignore 只对未跟踪文件生效,已跟踪文件需用 git rm --cached
  • 全局 .gitignore
  • 创建全局忽略文件:
    git config --global core.excludesfile ~/.gitignore_global
    echo "*.log" > ~/.gitignore_global
    
  • 模板资源:参考 GitHub 的 .gitignore 模板 获取语言/框架特定规则。

综合示例

以下是一个结合所有命令的场景:

  1. 初始化并创建文件

    mkdir my-project
    cd my-project
    git init
    echo "Hello" > file1.txt
    echo "Secret" > config.local
    

  2. 配置 .gitignore

    echo "config.local" > .gitignore
    git add .gitignore
    git commit -m "Add .gitignore"
    

  3. 添加和修改文件

    git add file1.txt
    git commit -m "Add file1.txt"
    echo "New line" >> file1.txt
    git status  # 显示已修改
    

  4. 查看差异

    git diff file1.txt  # 显示新增的 "New line"
    git add file1.txt
    git diff --cached  # 显示暂存的差异
    

  5. 撤销暂存

    git reset file1.txt
    git status  # 显示已修改,未暂存
    

  6. 删除文件

    git rm file1.txt
    git commit -m "Remove file1.txt"
    

  7. 停止跟踪但保留文件

    git rm --cached config.local
    git commit -m "Stop tracking config.local"
    


最佳实践

  • 频繁检查差异:用 git diff 确认修改内容。
  • 谨慎使用 git reset --hard:确保备份重要更改。
  • 规范 .gitignore:项目初期配置,定期检查。
  • 提交前确认:用 git statusgit diff --cached 确保提交内容正确。
  • 移除后更新 .gitignore:用 git rm --cached 配合 .gitignore 清理已跟踪的无关文件。

常见问题

  • .gitignore 不生效
  • 检查文件是否已跟踪(用 git rm --cached 移除)。
  • 确保模式语法正确(如目录用 / 结尾)。
  • 误删文件
  • 未提交:用 git checkout -- <file> 恢复。
  • 已提交:用 git resetgit checkout <commit> -- <file>
  • git diff 无输出
  • 确认是否有修改或暂存内容。
  • 检查命令参数(如 git diff --cached)。