跳转至

Git 笔记

基本文件操作

git add

  • git add . 添加当前项目所有文件
  • git add [<pathspec>…​] 添加任意多个文件或文件夹到暂存区

git commit

git commit 的常用选项有:

  • -a/--all 自动暂存所有文件,未追踪的文件不受影响
  • -m/--message <msg> 指定提交的注释,若不指定则会弹出编辑器
  • -C <commit> 重用指定提交的信息,包括注释、作者信息和时间戳
  • -c <commit>-C 选项相似,但会打开编辑器提供机会修改提交信息
  • --amend 让新的提交替换当前分支的末端,相当于git reset --soft HEAD^ + git commit -c ORIG_HEAD

git mv

  • git mv <source> <destination> 若目标不存在则改变路径,若目标已存在且是文件则会报错,加上 -f/--force 选项则可以强制重命名或移动
  • git mv <source>…​ <destination directory> 移动多个文件到目标文件夹

由于 git 不追踪文件的移动或重命名,直接操作被当成删除原文件并增加新的未追踪文件,使用 git mv 可以避免这一点,减少麻烦,这在大规模移动文件时尤其重要。

git rm

  • git rm [<pathspec>…​] 若文件没有修改,则将文件从暂存区和工作区中移除;若已修改,可使用 -f/--force 选项强制移除
  • git rm --cached [<pathspec>…​] 仅将文件从暂存区移除,解除追踪

在 pathspec 中,可以使用通配符进行匹配,例如 git rm 'log/*.log' 会删除 log 文件夹中的所有 .log 文件。


信息查看

git status

  • git status 显示当前所处分支、暂存区与 HEAD 有区别的文件、工作区与暂存区有区别的文件,以及未追踪的文件
  • git status -s/--short 以简短的形式输出

git diff

  • git diff [--] [<path>…​] 显示工作区与暂存区的差别,不包括未追踪的文件
  • git diff --no-index [--] <path> <path> 显示两个指定文件的差别
  • git diff --cached/--staged [<commit>] [--] [<path>…​] 显示暂存区与某次提交的差别,默认为 HEAD
  • git diff <commit> [--] [<path>…​] 显示工作区与某次提交的差别
  • git diff <commit> <commit> [--] [<path>…​] 显示任意两次提交的区别

若在上述命令中使用了 --stat 选项,则只会显示每个文件的统计情况。

在 git 中,可用 -- 分隔 path 与其前面部分,以避免选项混淆。

git log

git log [<options>] [<revision-range>] [[--] <path>…​]

git log 会列出可由指定提交追溯到的提交历史,并会排除可由前带有 ^ 的提交追溯到的提交历史,例如 git log ^foo bar 会列出 bar 的提交历史,但不包括 foo 的提交历史,foo..bar 可用作其简写,常用于表示一连串提交。

需要注意的是,与之类似还有 foo...bar,其定义为 r1 r2 --not $(git merge-base --all r1 r2),常用于表示由同一祖先分出的两串分支。同时,这两种写法都可以省略一端,以默认的 HEAD 来代替,例如 origin..HEAD 可以简写为 origin..,表示当前分支尚未推送到远程的提交。

若指定了 path,则只会显示与 path 相关的提交历史。

以下选项可以用于筛选提交:

  • -<number> 最多只显示 number 条提交
  • --since/--after=<date> 只显示在指定日期之后的提交,日期格式可为:
    • YYYY-MM-DD YYYY.MM.DD YYYY/MM/DD,若不指定则默认为今天
    • HH:MM:SS,若不指定则默认为午夜
    • n minutes/hours/days/weeks/months/years ago,其中末尾的 s 可省略,空格也可替换成 . - /
  • --until/--before=<date> 只显示在指定日期之前的提交
  • --author/--commiter=<pattern> 只显示作者名符合 pattern 的提交,可使用 -i 选项忽略大小写

以下选项可用于修改显示格式:

  • --stat 显示修改的统计信息
  • -p/--patch 显示提交的具体修改,可与 --stat 选项一起使用
  • --abbrev-commit 显示提交哈希的缩写
  • --pretty[=<format>] --format=<format> 修改显示格式,默认为 medium
  • --oneline 等价于 --pretty=oneline --abbrev-commit
  • --graph 在左侧绘制图,显示分支与合并历史

--pretty--format 选项中的 format 可为:

  • =oneling 显示在一行
  • =short/medium/full/fuller 显示内容依次变多,short 只含提交哈希、作者名与标题行,fuller 则包括提交哈希、作者名、作者日期、提交者名、提交日期、标题行与完整提交注释
  • =format:<format-string> 显示指定格式,可以使用以下占位符:
    • %H 提交哈希
    • %h 提交哈希的缩写
    • %an 作者名
    • %ae 作者邮箱
    • %ad 作者日期
    • %ar 作者相对日期
    • %ai 类 ISO 8601 格式的作者日期
    • %as 作者日期的缩写,格式为 YYYY-MM-DD
    • %cn 提交者名。其余各占位符与作者信息相同,只需将 a 替换成 c
    • %d 引用名,包括分支与标签
    • %s 标题行,即提交注释的第一行
    • %Cred %Cgreen %Cblue %Creset %C(…​) 切换之后的颜色,其中 %Creset 用于重置颜色,%C(auto) 可以自动设置颜色

git show

git show [<options>] [<object>…​]

与 git log 和 git cat-file 类似,但侧重于显示指定对象且可读性更强,可以是提交、标签、树或者 blob,会根据对象的类型,显示对象的内容:

  • 若对象是提交和标签,则会显示详细信息与具体修改,可接受 git log 的那些修改显示格式的选项
  • 若是树,则会列出其下文件名称
  • 若是 blob,则会显示其内容

git cat-file

  • git cat-file -t <object> 显示对象的类型
  • git cat-file -s <object> 显示对象的大小
  • git cat-file -p <object> 根据对象的类型,显示对象的内容
    • 若对象是提交或标签,则会显示其所有信息,包括树、父提交、作者等
    • 若对象是树,则会显示其所有 blob 与子树的信息,包括权限、类型、哈希值与文件名
    • 若对象是 blob,则会显示其内容
  • git cat-file --batch-check=<format> 打印 stdin 提供的对象信息,可用于批量检查对象信息,format 可为:
    • %(objecttype) %(objectname) %(objectsize) %(rest) 显示对象类型、名称、大小及路径

git ls-files

  • git ls-files [-c/--cached] 列出暂存区的文件,即所有被追踪的文件
  • git ls-files -d/--deleted 列出所有已被删除但尚未暂存的文件
  • git ls-files -m/--modified 列出所有已被修改但尚未暂存的文件,包括被删除
  • git ls-files -o/--other 列出尚未被追踪的文件
  • git ls-files -i/--ignored 列出所有被忽略的文件,必须使用 -c-o 选项来指定搜索范围,同时还必须使用 --exclude* 选项来进行匹配,例如 git ls-files -io --exclude='*.log' 可以列出所有未追踪的被忽略的 .log 文件
  • git ls-files -s/--stage 列出暂存区文件的信息,包括权限、哈希值与文件名等

git rev-list

git rev-list 是 git log 的底层命令,用于列出提交对象,其接受的参数与 git log 类似,但更适用于脚本。

  • git rev-list --count HEAD 统计从 HEAD 开始的提交数量
  • git rev-list --objects --all 列出所有可达对象,包括提交、树和 blob
  • git rev-list --all --objects | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | grep '^blob' | sort -k3 -n -r | tail -n 5 查找仓库中最大的 5 个文件


恢复保存

git restore

git restore [<options>] [--source=<tree>] [--staged] [--worktree] [--] <pathspec>…​

git restore 可用于恢复工作区和暂存区中的文件,恢复源可以通过 -s/--source <tree> 手动指定。若 pathspec 已被追踪但不存在于恢复源中,则会被删除以匹恢复源。

默认情况下,git restore 恢复工作区文件,恢复源为 index;若使用了 -S/--stage 选项,则恢复暂存区文件,恢复源为 HEAD;若同时使用了 -S/--stage-W/--worktree 选项,则同时恢复工作区与暂存区文件。

git reset

git reset 可用于重置暂存区的文件,如:

  • git reset [<tree-ish>] [--] <pathspec>…​ 重置暂存区的文件,使其与 tree-ish 一致,tree-ish 默认为 HEAD

但相比之下,git reset 更常用于回退版本,具体命令为:

  • git reset [<mode>] [<commit>] 将当前分支重置为指定 commit,暂存区和工作区的文件的改变视模式而定,默认为 mixed
    • --soft 不改变工作区和暂存区
    • --mixed 仅重置暂存区
    • --hard 重置工作区与暂存区

需要注意的是,该操作会同时移动 HEAD 和当前分支到指定提交上,更加危险。不过在进行该操作前,git 会将原分支设为 ORIG_HEAD,以便回退。

在指定提交时,可以使用相对引用,例如 HEAD^ 可以表示 HEAD 的父提交,其中 ^ 可以叠加使用;HEAD~2 可以更方便地表示 HEAD 的父提交的父提交;当存在多个父提交时,可以使用 ^n 来指定第 n 父级,若不使用则默认为第一父级。

git reflog

git reflog [show] [<log-options>] [<ref>]

git 的引用存储在 .git/refs 文件夹中,主要分为以下几类:

  • 本地分支,格式为 refs/heads/<branch>
  • 远程追踪分支,格式为 refs/remotes/<remote>/<branch>
  • 标签,格式为 refs/tags/<tagname>

以上引用在使用时,大多可将 refs/heads 等前缀略去。

除此以外,还会有一些特殊的引用存储在 .git 文件夹中,例如 HEAD 指向当前检出 commit,ORIG_HEAD 指向危险操作前的 HEAD,FETCH_HEAD 指向最近一次拉取的分支(可能会有多个),MERGE_HEAD 指向欲合并的目标分支。

git 会记录下所有引用的变动,包括提交、合并、重置等,这些记录被称为 reflog,可以通过 git reflog 来查看,具体形式为 HEAD@{n},可用于恢复历史。

git reflog 的默认子命令为 show,可以接受 git log 的所有参数。除 show 外,还有以下子命令:

  • expire 用于修剪过期的 reflog 条目
  • delete 用于删除指定的 reflog 条目

git stash

  • git stash [push] [--] [<pathspec>…] 临时保存未提交的更改,使工作目录回到干净的状态,默认会贮藏所有更改
    • -S/--staged 仅贮藏暂存区的更改
    • -u/--include-untracked 也贮藏未追踪的文件
    • -k/--keep-index 保留暂存区的更改
    • -m/--message <msg> 指定贮藏项的注释,默认为当前 commit 的注释
  • git stash list [<log-options>] 列出所有贮藏项,具体形式为 stash@{n}: <message>
  • git stash show [<diff-options>] [<stash>] 显示贮藏项的内容
  • git stash pop [<stash>] 弹出一个贮藏项并应用,默认为 stash@{0}
  • git stash drop [<stash>] 删除一个贮藏项
  • git stash clear 删除所有贮藏项


分支管理

git branch

以下命令可以列出分支:

  • git branch git branch -l/--list 列出所有本地分支
  • git branch -r/--remotes 列出所有远程追踪分支
  • git branch -a/--all 列出所有分支
  • git branch -v 列出本地分支的最后一次提交
  • git branch -vv 列出本地分支的最后一次提交与上游分支

上述各选项可以组合使用,而且需要注意的是,如果想要使用 pattern 匹配分支,则必须要使用 --list 选项,例如 git branch -rl *dev*

以下命令可以修改分支:

  • git branch <branchname> [<start-point>] 新建一个指向 start-point 的本地分支,默认为 HEAD,若为远程追踪分支则会自动设置其为上游分支
  • git branch -d/--delete <branchname>…​ 删除本地分支
  • git branch -m/--move [<oldbranch>] <newbranch> 重命名本地分支
  • git branch -c/--copy [<oldbranch>] <newbranch> 复制本地分支

被删除的分支不能是当前分支,且必须要完全合并进上游分支,若没有上游分支则要完全合并进 HEAD,也就是说,删除目标需要能通过上游分支或 HEAD 恢复,以免丢失未合并的更改;newbranch 也不能是已存在的分支名。若要强制如此,可以使用 -f/--force 选项,或者直接使用 -D -M -C 选项来代替。此外,-d-r 选项可以组合使用,以删除存储在本地的远程追踪分支。

以下命令可以设置上游分支:

  • git branch -u/--set-upstream-to <upstream> [<branchname>] 设置上游分支
  • git branch --unset-upstream [<branchname>] 移除上游分支
  • <upstream> 的形式为 <remote>/<branch>
  • <branchname> 若未被指定,则默认为当前分支

本地分支在推送和拉取前,需要先设置上游分支,以与某个远程仓库的远程分支建立联系,其在本地的存在形式为远程追踪分支。如果此前本地并没有远程追踪分支,可以先使用 git fetch 拉取远程仓库,然后再将拉取来远程追踪分支的设置为上游分支。

git checkout

git checkout 可以切换 HEAD 的位置,更新工作区和暂存区,常用的命令有:

  • git checkout <branch> 切换到指定分支
  • git checkout <commit> 切换到指定 commit
  • git checkout - 切换到前一个位置
  • git checkout -d/--detach [<branch>] 切换分支并分离 HEAD
  • git checkout -b <new-branch> [<start-point>] 新建分支并切换,start-point 默认为 HEAD,若为远程追踪分支则会自动设置其为上游分支

需要注意的是,只有在前后 HEAD 的文件完全相同的情况下,git checkout 才会保持本地的更改,否则会报错,这样可以避免本地的更改被覆盖。

此外,git checkout 还可以做到从已给分支名推测远程追踪分支,或者从远程追踪分支推测分支名:在使用 git checkout <branch> 时,如果本地没有该分支名,而且刚好有一个远程仓库有一个同名的远程追踪分支,则会自动新建分支并追踪;在使用 git checkout -t <remote>/<branch> 时,如果本地没有该分支名,同样也会自动新建分支并追踪。这两种情况都相当于在使用 git checkout -b <branch> --track <remote>/<branch>。当然,如果想要新建分支名和远程追踪分支名不同,就不能省略 -b 选项了。

git switch

  • git switch <branch> 切换分支
  • git switch - 切换到前一个分支,不能是 commit
  • git switch -d/--detach [<start-point>] 切换位置并分离 HEAD
  • git switch -c/--create <new-branch> [<start-point>] 新建分支并切换

git switchgit checkout 一样,可以在前后 HEAD 的文件完全相同的情况下保持更改,也可以做到从已给分支名推测远程追踪分支,或者从远程追踪分支推测分支名,从而在省略 -c 选项的情况下完成自动新建与追踪。

git merge

  • git merge <commit>…​ 将指定提交合并到当前分支
  • git merge --continue 在解决了冲突后继续合并,需要处于合并过程
  • git merge --abort 取消合并,回到合并前的状态
  • git merge --quit 退出合并,但不回到合并前的状态

若当前分支是目标分支的祖先,则会进行快进合并,不会产生 merge commit,否则在没有冲突的情况下,会自动产生一个新的 merge commit。如果不想自动产生合并提交,可以使用 --no-commit 选项。

合并分支有多种策略,可以使用 -s/--strategy 选项来指定,常用的策略有:

  • ort 是合并两个分支时的默认策略,使用三路合并算法,具体就是,如果只有一方有修改,或者双方都有修改但修改相同,则选择修改后的文件,否则就会产生冲突,需要手动解决
    • 可以使用 -X/--strategy-option ours/theirs 选项来指定合并时的冲突解决策略,ours 代表保留当前分支的修改,theirs 代表保留合并分支的修改
  • octopus 是合并多于两个分支时的默认策略,在合并过程中一旦出现需要手动解决的冲突就会拒绝合并
  • ours 策略不同于 -X ours,会完全保留当前分支的更改,不考虑其他分支

如果不想留下其他分支的记录,保持线性的提交历史,可以使用 --squash 选项,这样就会将其他分支的所有提交合并成一个提交附加到当前分支。

git rebase

  • git rebase [--onto <newbase>] [<upstream> [<branch>]] 保存 branch 有而 upstream 没有的提交,也即保存从 upstream 到 branch 的提交,然后将其逐个应用到 upstream 上
    • 若有 --onto 选项,则会将补丁应用到 newbase 上
    • 若未指定 <upstream>,则会使用当前分支的上游分支
  • git rebase --continue 在解决了冲突后继续变基
  • git rebase --abort 取消变基,回到变基前的状态,若有 branch 则会切换到 branch
  • git rebase --quit 退出变基,但不回到变基前的状态
  • git rebase --skip 继续变基,跳过当前补丁

git rebase 可用于删除提交,例如 git rebase --onto feature~5 feature~3 feature 会删除从 feature^4feature^3 的提交;也可用于将某一分支的部分更改应用到另一分支上,例如 git rebase --onto master feature bugfix 会将 feature 分支上对 bug 的修复应用到 master 分支上。

git rebase 更常用的用法是加上 -i/--interactive 选项,以交互式的方式来变基,可以方便地对提交进行删除、合并等操作,并且只会改变提交时间,不会改变作者时间,其命令有:

  • p, pick <commit> 使用提交
  • r, reword <commit> 使用提交,但是会弹出编辑器,提供修改提交信息的机会
  • e, edit <commit> 使用提交,但是会在应用该提交后停下,以便使用 --amend 选项来修改提交
  • s, squash <commit> 使用提交,但是会将该提交合并进上一个提交,然后弹出编辑器,显示合并后的提交信息,适用于合并多个相似的小更改
  • f, fixup [-C | -c] <commit> 和 squash 一样能够合并多个提交,但会自动使用之前的提交信息,若使用 -C 选项则会使用当前提交信息,若使用 -c 选项还会在使用当前提交信息的基础上打开编辑器,适用于将修复合并进之前的提交
  • d, drop <commit> 移除提交

git filter-branch

git filter-branch 可用于批量修改历史提交,但该命令较为复杂且缓慢,现在更推荐使用 git-filter-repo 等第三方工具。其基本用法如下:

git filter-branch --<filter> <command> [<rev-list-options>]

其中 <filter> 用于指定修改的范围,常用的有:

  • --env-filter <command> 修改环境变量,可用于修改作者或提交者信息
  • --tree-filter <command> 修改暂存区和工作区,可用于删除或修改文件
  • --index-filter <command> 修改暂存区,比 --tree-filter 更快
  • --parent-filter <command> 修改父提交列表
  • --msg-filter <command> 修改提交信息

例如,若要删除所有提交中的 password.txt 文件,可以使用 git filter-branch --tree-filter 'rm -f password.txt' HEAD

git tag

  • git tag 列出所有标签
  • git tag -l/--list [<pattern>…] 列出所有标签,若有 pattern 则会匹配
  • git tag <tagname> [<commit>] 创建轻量标签
  • git tag -a/--annotate <tagname> [-m <msg>] [<commit>] 创建附注标签
  • git tag -d/--delete <tagname>… 删除标签


远程管理

git remote

  • git remote 显示所有远程仓库
  • git remote -v/--verbose 显示所有远程仓库的详细信息,包括 URL
  • git remote add <name> <URL> 添加远程仓库
  • git remote rename <old> <new> 重命名远程仓库
  • git remote rm/remove <name> 删除远程仓库
  • git remote set-url [--push] <name> <newurl> 修改远程仓库的 URL,若使用 --push 选项则只修改 push 地址

git fetch

git fetch [<options>] [<repository> [<refspec>…​]]

git fetch 能从远程仓库拉取分支,用以更新远程追踪分支,其常用选项有:

  • --all 拉取所有的远程仓库
  • -v/--verbose 显示详细信息
  • -p/--prune 删除远程仓库中不存在的远程追踪分支

refspec 用于表示本地引用和远程引用之间的映射关系,其格式为 [+]<src>:<dst>。在 git fetch 中,src 表示要拉取的远程引用,dst 表示要更新的本地引用,+ 号表示允许 non-fast-forward。

在使用 git fetch 时,若未指定 repository,则会使用上游分支的远程仓库,若没有上游分支,则会使用 origin 仓库;若未指定 refspec,则会使用 remote.<repository>.fetch 中的配置,一般为 +refs/heads/*:refs/remotes/origin/*,即将远程仓库的所有分支都拉取到本地对应的远程追踪分支中;若 refspec 中的 :<dst> 被忽略,则会更新与 src 对应的远程追踪分支。

git pull

git pull [<options>] [<repository> [<refspec>…​]]

git pull 形式与 git fetch 相同,但会在拉取后自动合并,相当于 git fetch + git merge,其常用选项有:

  • -r/--rebase[=false|true|merges|interactive] 使用变基而不是合并,可以在 pull.rebase branch.<name>.rebase branch.autoSetupRebase 中配置
  • --no-rebase 相当于 --rebase=false
  • --set-upstream 若拉取成功,则设置上游分支
  • 其余更多选项可见 git fetch 和 git merge

git push

git push [<repository> [<refspec>…​]]

git push 能推送本地分支到远程仓库,使用本地引用更新远程引用,其常用选项有:

  • --all 推送所有分支
  • --tags 推送所有标签
  • --prune 删除远程仓库中不存在对应本地分支的分支
  • -f/--force 强制推送
  • -d/--delete 从远程仓库删去列出的引用,常见用法为 git push origin --delete <branch>
  • -u/--set-upstream 若分支已最新或推送成功,则设置上游分支,常见用法为 git push -u origin main

在 git push 中,src 表示要推送的本地引用,dst 表示要更新的远程引用,+ 号表示强制推送。可以使用 git push <repository> +:<dst> 强制删除任意引用。

在使用 git push 时,若未指定 repository,则会使用 branch.<name>.remote 中的配置,若没有配置则会默认使用 origin 仓库;若未指定 refspec,则会使用 remote.<name>.push 中的配置,若没有配置则会默认推送到远程仓库的同名分支;若 refspec 中的 :<dst> 被忽略,则默认为 :<src>,即推送到同名分支。


配置管理

git config

  • git config <name> <value> 设置配置
  • git config -l/--list 列出所有配置
  • git config --get <name> 显示指定配置
  • git config --get-regexp <name-regex> [<value-pattern>] 显示匹配 name-regex 以及可选的 value-pattern 的配置
  • git config --unset <name> 删除指定配置

在 git 中,配置分为三个级别:系统级别、全局级别和仓库级别,分别对应三个配置文件:/etc/gitconfig ~/.gitconfig .git/config。在使用 git config 时,可以使用 --system --global --local 选项来指定配置级别,若未指定则默认为 --local

在列出和显示配置时,可以使用 --show-origin 选项来显示配置的来源,还可以使用 --show-scope 选项来显示配置的级别。

常见的配置有:

  • user.name 用户名
  • user.email 用户邮箱
  • alias.<name> 别名
  • core.editor 默认编辑器
  • core.autocrlf 行结束符的转换
    • 若为 true,则在提交时将 CRLF 转换成 LF,在检出时将 LF 转换成 CRLF
    • 若为 input,则在提交时将 CRLF 转换成 LF
    • 若为 false,则不做任何转换
  • merge.tool 默认合并工具
  • mergetool.<tool>.cmd 使用某合并工具时的命令
  • diff.tool 默认比较工具
  • difftool.<tool>.cmd 使用某比较工具时的命令
  • init.defaultBranch 默认分支名

另外一种与 git 无关但与 Github 有关的配置是 SSH 密钥,可以使用 ssh-keygen 来生成密钥,然后将 .pub 公钥添加到 Github 上。

# 生成带邮箱注释的的 rsa 密钥
ssh-keygen -t rsa -C "hr.zheng@outlook.com"

# 检验能否成功连接到 Github
ssh -T git@github.com

gitignore

可在项目根目录中的 .gitignore 文件中定义忽略规则,以忽略不需要追踪的文件,已追踪的文件不受影响。常见的规则如下:

  • build 忽略所有名为 build 的文件或文件夹
  • build/ 只忽略名为 build 的文件夹,忽略其内所有文件
  • /build/ 只忽略当前目录下的名为 build 的文件夹
  • /codes/*.c 只忽略 codes 第一层目录下的 .c 文件
  • /codes/*/*.c 只忽略 codes 第二层目录下的 .c 文件
  • /codes/**/*.c 忽略 codes 内的所有 .c 文件,/**/ 可匹配零个或多个目录
  • ! 所有因之前规则被忽略的匹配文件会被重新追踪


仓库清理

git gc

git gc [--aggressive | --auto | --prune=<date> | --no-prune]

git gc 会清理不必要的文件并优化本地仓库。

git 会在版本库中保留所有对象,即使它们已无法访问。git gc (garbage collection) 命令可以清理这些对象,减小仓库体积。

  • --aggressive 选项会更积极地优化仓库,但耗时更长
  • --auto 选项会让 git 自行判断是否需要进行垃圾回收
  • --prune=<date> 选项可以指定一个日期,早于该日期的无法访问对象都将被清理,默认为两周前

git clean

git clean [-d] [-f] [-x] [-X]

git clean 会清理工作目录,删除未追踪的文件

常用选项有:

  • -d 选项会删除未追踪的目录
  • -f 选项会强制删除未追踪的文件,默认情况下 git clean 是不删除任何文件的
  • -x 选项会删除所有未追踪的文件,包括 .gitignore 中的文件
  • -X 选项会删除 .gitignore 中的文件,但保留其他未追踪的文件


常见场景

查看文件的历史版本与比较差异

  • 查看某个提交中的文件内容

可以使用 git show <commit>:<path> 来查看指定提交中某个文件的内容。

# 查看 5 个提交前 README.md 文件的内容
git show HEAD~5:README.md
  • 比较历史版本与当前工作区文件的差异

可以使用 git diff <commit> -- <path> 来比较指定提交中的文件版本与当前工作目录中的文件。

# 比较 5 个提交前 README.md 文件与当前工作区版本的差异
git diff HEAD~5 -- README.md
  • 比较两个历史版本之间的文件差异

可以使用 git diff <commit1> <commit2> -- <path> 来比较两个不同提交中同一个文件的差异。

# 比较 5 个提交前和 3 个提交前 README.md 文件的差异
git diff HEAD~5 HEAD~3 -- README.md

修改上一次提交的内容

  • 修改上一次提交的内容,同时使用相同的提交信息

如果需要修改上一次提交的内容,可以使用 git commit --amend 命令。该命令会将当前暂存区的更改合并到上一次提交中。

# 修改上一次提交的内容
git add <file>  # 添加需要修改的文件到暂存区
git commit --amend -C HEAD
  • 修改上一次提交的内容,并修改提交信息 如果需要修改上一次提交的内容,并且修改提交信息,可以使用 -c HEAD 命令,打开编辑器进行修改。

    # 修改上一次提交的内容,并修改提交信息
    git add <file>  # 添加需要修改的文件到暂存区
    git commit --amend -c HEAD
    

压缩 .git 大小

如果使用 git 原生命令,可以使用以下步骤来压缩 .git 目录的大小:

  1. 使用 git count-objects -vH 命令来查看 .git 目录的大小
  2. 使用 git rev-list --all --objects | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | grep '^blob' | sort -k3 -n -r | tail -n 5 查找仓库中最大的几个文件
  3. 使用 git filter-branch --force --index-filter "git rm --cached --ignore-unmatch PATH" --prune-empty --tag-name-filter cat -- --all 删除指定文件的所有历史记录
  4. 使用 rm -rf .git/refs/original/ 命令删除自动保存的原始引用,以免 Git 继续保留那些历史对象
  5. 使用 git reflog expire --expire=now --all 命令清理引用日志,释放那些在 Git 中看不到但实际仍保存的历史引用
  6. 使用 git gc --prune=now --aggressive 命令进行垃圾回收,压缩和清理 .git/objects/ 中的对象数据库,释放磁盘空间

但是更好的选择是使用 git-filter-repo 工具来压缩 .git 目录的大小。该工具可以更快地处理大仓库,并且支持更多的过滤选项。

修改作者信息

可以使用 git filter-branch 来批量修改作者信息。

以修改作者名为例:

git filter-branch --env-filter "GIT_COMMITTER_NAME='新的作者名'; GIT_AUTHOR_NAME='新的作者名'"

另一个通过条件判断修改 email 的例子是(bash):

git filter-branch --env-filter '
OLD_EMAIL="旧的邮箱@example.com"
CORRECT_NAME="新的作者名"
CORRECT_EMAIL="新的邮箱@example.com"

if [ "$GIT_COMMITTER_EMAIL" = "$OLD_EMAIL" ]
then
    export GIT_COMMITTER_NAME="$CORRECT_NAME"
    export GIT_COMMITTER_EMAIL="$CORRECT_EMAIL"
fi

if [ "$GIT_AUTHOR_EMAIL" = "$OLD_EMAIL" ]
then
    export GIT_AUTHOR_NAME="$CORRECT_NAME"
    export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL"
fi
' --tag-name-filter cat -- --branches --tags