@January 7, 2021 开始汇总啦~ 本文参考《Git权威指南》整理。

和 Git 相遇

打开终端,定位到需要版本控制目录, 输入 git init 后你就进入 Git 的世界啦。

在了解更多的命令前,先了解一下背景知识吧:

  • Git 中文件有三个版本:工作区 VS 暂存区 VS 版本库

git init --bare 创建一个空的裸版本库于目录XXX

追踪文件

git add {{path/to/file}} 添加文件到暂存区(1. 修改/新增文件内容写入到对象库中 2. 更新暂存区的目录树,在文件索引中记录对象的ID)

1 git add -A vs 2 git add -u vs 3 git add .

1: 将文件的修改,文件的删除,文件的新建,添加到暂存区 (all files)

2: 将文件的修改、文件的删除,添加到暂存区 (already tracked files)

3: 将文件的修改,文件的新建,添加到暂存区 (files in the work directory)

git add -f 尽管 .gitignore 说要忽略某个文件,但我偏偏要加你拿我没办法!

$ git add -i
*** Commands ***
  1: status	  2: update	  3: revert	  4: add untracked
  5: patch	  6: diff	    7: quit	    8: help

What now> h
status        - show paths with changes
update        - add working tree state to the staged set of changes
revert        - revert staged set of changes back to the HEAD version
patch         - pick hunks and update selectively
diff          - view diff between HEAD and index
add untracked - add contents of untracked files to the staged set of changes

分支管理

  • git branch 列出所有本地分支,当前分支会用 * 标记
  • git branch -a 列出所有分支,包括本地和远程分支
  • git branch -r 列出所有远程分支
  • git branch -v 列出所有本地分支的同时,显示各个分支最后一个提交信息
  • git branch {{branch_name}} 基于当前头指针(HEAD)指向的提交创建新分支
  • git branch {{branch_name}} {{commit_hash}} 基于 {{commit_hash}} 指向的提交创建新分支
  • git branch -d {{branch_name}} 删除分支,并检查该分支是否已合并到其他分支中,否则拒绝删除
  • git branch -D {{branch_name}} 强制删除分支
  • git branch -m {{old_branch_name}} {{new_branch_name}} 重命名分支,若版本库中已经存在同名分支,则拒绝重命名
  • git branch -M {{old_branch_name}} {{new_branch_name}} 强制重命名

检出命令

  • git checkout & git checkout HEAD 提醒当前工作区哪些文件可以被暂存区(或版本库)的覆盖(包含被删除和被修改的文件,不包含新增的文件)
  • git checkout {{branch_name}} 更新 HEAD 以指向 branch_name 分支,以 branch_name 指向的树更新暂存区和工作区。
  • git checkout -- {{filename}} 用暂存区指定的文件 filename 替换工作区的 filename,相当于取消自上次执行 git add filename 以来(如果执行过)本地的修改。根据我的测试,不加 -- 也是相同的效果,区别在于不加会输出一行信息 Updated 1 path from the index,而加了没有任何输出。后面带有 {{filename}} 的命令都是这样的。
  • git checkout {{branch_name}} -- {{filename}} 维持 HEAD 的指向不变。将 branch_name 所指向的提交中的 filename 替换暂存区和工作区中相应的文件。
  • git checkout -- . & git checkout . 取消当前目录下所有没被追踪的修改、删除文件操作。
  • git checkout -b {{branch_name}} {{reference}} 基于 reference 创建一个新分支,名叫 {{branch_name}} 并切换到该分支上。
  • git checkout --track -b {{branch_name}} {{reference}} 和前一个差不多,不过这里的 branch_name 是远程分支

克隆命令

  • git clone {{remote_repository_location}} {{path/to/directory}}{{remote_repository_location}} 指向的版本库创建一个克隆到 {{path/to/directory}} 目录。目录 {{path/to/directory}} 相当于克隆版本库的工作区,文件都会检出,版本库位于工作区下的 .git 目录中。
  • git clone --bare {{remote_repository_location}} {{path/to/directory.git}} 创建一个裸版本库,即克隆版本库不含工作区,直接就是版本库的内容。一般约定俗成裸版本库的目录名以 .git 为后缀。
  • git clone --mirror {{remote_repository_location}} {{path/to/directory.git}} 和前一个的区别在于克隆出来的裸版本对上游版本库进行了注册,这样可以在裸版本库中使用 git fetch ****命令和上游版本库进行持续同步。
  • 更多 --bare--mirror 之间的区别参考 What’s the difference between git clone –mirror and git clone –bare - Stack Overflow

提交命令

  • git commit -m {{message}} 将暂存区里的改动给提交到本地的版本库
    • -a 自动对本地所有变更的文件执行提交操作,包括本地修改的文件,删除的文件,但不包括未被版本库跟踪的文件。⚠️ 尽量不要使用该命令,尽管它的确可以简化一些操作,但如果习惯了使用这个“偷懒“的提交命令,就会丢掉Git暂存区带给用户最大的好处:对提交内容进行控制的能力
    • -s 在提交的说明里会自动添加上提交者姓名和邮箱地址,这对于一些合作项目是必须的。
    • --amend刚刚的提交进行修补,这样就可以改正前面错误的提交(用户信息错误),而不会产生另外的新提交。
    • --allow-empty 进行修补的提交实际上是一个空白提交,默认不允许空白提交。
    • -C {{commit}} 提交说明重用 {{commit}} 的提交说明。
    • --reset-author 重置提交者的 ID,这条命令也会重置 AuthorDate 时间戳信息。一般用于上一条复用之前别的提交者的信息时需要修改当前提交者的信息这一场景。

配置命令

  • Git 配置文件采用的是 INI 文件格式
  • 三种级别配置文件的修改
    • workplace级:/path/to/my/workspace/demo/.git/config
      • git config -e
    • user级:/home/username/.gitconfig
      • git config -e --global
    • system级:/etc/gitconfig or /usr/local/etc/gitconfig
      • git config -e --system
  • git config a.b 读取某个配置属性的值
  • git config a.b something 修改某个配置属性的值
  • git config --unset a.b 取消某个配置属性
  • 第一次使用 Git 建议配置以下内容
    • git config --global user.name "XXX"
    • git config --global user.email "XXX@XXX"
  • 小彩蛋:实际上 git config 命令可以操作任何其他的 INI 文件哦~

比较命令

  • Git 扩展了 GNU 的差异比较语法,提供了对重命名二进制文件文件权限变更的支持。
  • Git 的差异比较缺省是逐行比较,分别显示改动前的行和改动后的行,到底改动哪里还需要仔细辨别。Git 还提供一种逐词比较的输出,有的人会更喜欢。使用 --word-diff 参数可以显示逐词比较。
  • 比较里程碑B和里程碑A,用命令:git diff B A
  • 比较工作区和里程碑A,用命令:git diff A
  • 比较暂存区和里程碑A,用命令:git diff --cached A
  • 比较工作区和暂存区,用命令:git diff
  • 比较暂存区和HEAD,用命令:git diff --cached
  • 比较工作区和HEAD,用命令:git diff HEAD
  • 小彩蛋:git diff 还可以在 Git 版本库之外执行哦~

拉取命令

  • git fetch 从默认的远程上游仓库拉取最近的更新
  • git fetch {{remote_name}} 从指定的远程上游仓库拉取更新
  • git fetch --no-tags 设置不获取里程碑只获取分支及提交

历史记录查看

  • git log 显示当前 HEAD 能够访问到的所有历史提交
  • git log {{commit1}} {{commit2}} 可查看从 {{commit2}}{{commit1}} 的历史提交
    • 也可以使用 git log --stat --oneline I..C
  • git log -p {{path/to/file_or_directory}} 可在显示某一个文件/文件夹日志的时候同时显示相关改动
  • --pretty=raw 参数可以显示每个提交对象的 commit/tree/parent 等属性
  • --pretty=fuller 参数会同时显示作者和提交者,两者可以不同
  • —-pretty=oneline 参数可显示更短小的提交 ID
  • --graph 参数展示父子提交关系图
  • -<n> 参数显示最近的 <n> 条日志
  • --stat 参数可以看到每次提交后文件的增删修改情况
  • --decorate 参数可以在提交 ID 的旁边显示该提交关联的引用(里程碑或分支)

分支合并

  • git merge {{commit}} 大多数情况,只须提供一个 {{commit}}(提交ID或对应的引用:分支、里程碑等)作为参数,即可和当前工作分支的目录树的内容进行合并。合并后的提交以当前分支的提交作为第一个父提交,以 {{commit}} 为第二个父提交。
  • 合并操作还支持将多个 {{commit}} 代表的分支和当前分支进行合并,过程类似。
  • 默认情况下,合并后的结果会自动提交,但是如果提供 -no-commit 选项,则合并后的结果会放入暂存区,用户可以对合并结果进行检查、更改,然后手动提交。
  • 注意,合并操作并非总会成功,因为合并的不同提交可能同时修改了同一文件相同区域的内容,导致冲突。
  • 冲突可分为以下几种类型:成功的自动合并、逻辑冲突、真正的冲突和树冲突。 #to-understand
  • Git 合并操作支持很多合并策略,默认会选择最适合的合并策略。例如,和一个分支进行合并时会选择 recursive 合并策略,当和两个或两个以上的其他分支进行合并时采用 octopus 合并策略。可以通过传递参数使用指定的合并策略。参数 s 用于设定合并策略,参数 X 用于为所选的合并策略提供附加的参数。#to-understand

拉取代码

  • git pull {{remote_name}} {{ref}} 下载远程仓库某分支或里程碑的变化,并与本地的记录合并
  • git pull 如果事先设定好了远程仓库的地址,就不需要每次繁琐地输入了
  • git pull = git fetch + git merge
  • git pull --rebase = git fetch + git rebase

上传代码

  • git push {{remote_name}} {{ref}} 不用赘述,git pull 的逆向操作
  • git push origin {{tag_name}} 或者 git push --tags 可以上传里程碑
  • git push origin refs/tags/* 也可以使用通配符上传多个里程碑
  • git push origin :mytag2 删除远程版本库中的里程碑

变基命令

  • git rebase {{new_base_branch}} 将当前分支变基到另一个指定的分支之上
  • git rebase --onto {{new_base}} {{since}} {{till}}
    • 首先会执行 git checkout 切换到 {{till}}。因为会切换到 {{till}},因此如果 {{till}} 指向的不是一个分支(如 master),则变基操作是在 detached HEAD(分离头指针)状态进行的,当变基结束后,要对 master 分支执行重置以实现把变基结果记录在分支中。
    • {{since}} .. {{till}} 所标识的提交范围写到一个临时文件中,它包括 {{till}} 的所有历史提交排除 {{since}} 以及 {{since}} 的历史提交后形成的版本范围。
    • 当前分支强制重置到 {{new_base}}。相当于执行:git reset --hard {{newbase}}
    • 从保存在临时文件中的提交列表中,一个一个将提交按照顺序重新提交到重置之后的分支上。
    • 如果遇到提交已经在分支中包含,跳过该提交。
    • 如果在提交过程遇到冲突,变基过程暂停。用户解决冲突后,执行 git rebase --continue 继续变基操作。或者执行 git rebase --skip 跳过此提交。或者执行 git rebase --abort 就此终止变基操作切换到变基前的分支上。
  • git rebase -i {{target_base_branch_or_commit_hash}} 交互式变基操作

远程命令

  • git remote -v 查看对上游版本库的注册信息(名称 + URL)
  • git remote add {{remote_name}} {{remote_url}} 注册新的远程版本库
  • git remote set-url {{remote_name}} {{remote_url}} 更换远程版本库的 URL 地址
  • git remote set-url --push {{remote_name}} {{remote_url}} 单独更换远程版本库 push 操作的 URL 地址。当单独为推送设置了 URL 后,配置文件 .git/config 的对应 [remote] 小节也会增加一条新的名为 pushurl 的配置。
  • git remote rename {{old_name}} {{new_name}} 修改远程版本库的注册名称。完成改名后,不但远程版本库的注册名称更改过来了,就连远程分支名称都会自动进行相应的更改。
  • git remote update 当注册了多个远程版本库并希望获取所有远程版本库的更新时使用这个命令。如果某个远程版本库不想在执行 git remote update 时获得更新,可以通过参数关闭自动更新 git config remote.user2.skipDefaultUpdate true
  • git remote rm {{remote_name}} 删除注册的远程版本库

    $ cat /path/to/my/workspace/demo-backup/.git/config 
    ... 
    [remote "origin"] 
    fetch = +refs/heads/*:refs/remotes/origin/* 
    		url = /path/to/my/workspace/demo 
    [branch "master"] 
    remote = origin
    		merge = refs/heads/master
    

重置命令

  • git reset & git reset HEAD 撤销目前正在追踪的所有文件,即 unstage everthing
  • git reset {{file_path}} 同上,仅撤销指定文件;该用法不会重置引用,也不会改变工作区,而是用指定提交状态下的文件替换掉暂存区中的文件,相当于取消 git add {{file_path}}
  • git reset [--soft | --mixed | --hard | --merge | --keep] {{commit}} 这种用法则会重置引用,根据不同的选项对暂存区或者工作区进行重置。
    • --hard 会执行下图中的 1、2、3 全部的三个动作
    • --mixed 或不使用参数会执行操作 1 和操作 2
    • --soft 会执行操作 1
  • git reset --soft HEAD^ 工作区和暂存区不改变,但是引用向前回退一次。当对最新提交的提交说明或者提交的更改不满意时,撤销最新的提交以便重新提交。
    • git commit --amend 实际上相当于执行了下面两条命令: git reset --soft HEAD^ & git commit -e -F .git/COMMIT_EDITMSG (注:文件 .git/COMMIT_EDITMSG 保存了上次的提交日志)
  • git reset --hard HEAD^ 彻底撤销最近的提交。引用回退到前一次,而且工作区和暂存区都会回退到上一次提交的状态。自上一次以来的提交全部丢失。

暂存变更命令

参考了 软件开发|git stash 命令实用指南

git stash 将所有未提交的修改(工作区暂存区)存储在本地(在你的项目的 .git 目录内,准确的说是 /.git/refs/stash),并允许你在需要时检索这些修改。运行完 git stash 之后,再查看工作区状态,会看见工作区尚未提交的改动(包括暂存区的改动)全都不见了。

git stash 中的内容不仅仅可以恢复到原先开发的分支,也可以恢复到其他任意指定的分支上。

  • git stash list 显示进度列表。此命令显然暗示了 git stash 可以多次保存工作进度,并且在恢复的时候进行选择。
  • git stash pop 从最近保存的进度进行恢复,并将恢复的工作进度从存储的工作进度列表中清除。
  • git stash pop --index {{stash}} 如果提供 {{stash}} 参数(来自于 git stash list 显示的列表),则从该 {{stash}} 中恢复。恢复完毕也将从进度列表中删除 {{stash}}。选项 --index 除了恢复工作区的文件外,还尝试恢复暂存区。
  • git stash save --patch --keep-index {{message}} 实际上是 git stash 命令的完整版。使用参数 -patch 会显示工作区和 HEAD 的差异,通过对差异文件的编辑决定在进度中最终要保存的工作区的内容,通过编辑差异文件可以在进度中排除无关内容。使用 k 或者 --keep-index 参数,在保存进度后不会将暂存区重置。缺省会将暂存区和工作区强制重置。
  • git stash apply --index {{stash}} 相较 git stash pop 命令,它不从进度列表中删除进度。
  • git stash drop {{stash}} 删除一个存储的进度,缺省则删除最新的进度。
  • git stash clear 删除所有存储的进度。
  • git stash branch {{branchname}} {{stash}} 基于进度创建分支。

下面是使用 git stash 时要遵循的顺序:

  1. 将修改保存到分支 A。
  2. 运行 git stash
  3. 签出分支 B。
  4. 修正 B 分支的错误。
  5. 提交并(可选)推送到远程。
  6. 查看分支 A
  7. 运行 git stash pop 来取回你的暂存的改动。

反向提交命令

反向提交,即重新做一次新的提交,相当于错误的历史提交的反向提交,修正错误的历史提交。

  • git revert HEAD 相当于将 HEAD 提交反向再提交一次,在提交说明编辑状态下暂停,可以修改提交说明

相较于通过 git reset 命令的撤销提交, git revert 可以撤销之前的某一个版本,保留该版本后面的变更;此外它还可以保留历史提交记录,如果是多人合作我认为使用 git revert 更好,但如果个人使用希望历史记录清爽些也可以使用 git reset

查看状态命令

  • git status 用于查看文件状态,显示 Changes not staged for commit (add 之前), Changes to be committed (add 之后)
  • git status -s 显示精简格式的状态输出
    • 第一列的字符 M :版本库中的文件和暂存区的文件相比有差异
    • 第二列的字符 M :工作库中的文件和暂存区的文件相比有差异
  • git status -sb 除了显示精简输出外,还能同时显示出当前工作分支的名称
  • git status --ignored.gitignore 忽略追踪的文件显示

里程碑命令

  • git tag 显示当前版本库的里程碑列表
  • git tag {{tag_name}} 创建以 tag_name 命名的里程碑,指向当前的提交
  • git tag {{tag_name}} {{commit}} 创建以 tag_name 命名的里程碑,指向指定的提交
  • git tag {{tag_name}} -m {{tag_message}} 创建一个带注释的里程碑
  • git tag -d {{tag_name}} 删除该里程碑
  • git tag -n<num> 显示当前版本库的里程碑列表以及注释,最多显示 <num> 行里程碑的注释`
  • git tag -l 可在后面加需要匹配的字符串, -l 表示通配符过滤

冷门命令

git am {{path/to/file.patch}}

应用补丁文件

git archive --verbose --format=zip HEAD

基于最新提交建立归档文件,HEAD 后面可以指定需要的文件夹 or 文件

可以使用 -o {{path/to/file}} or > {{path/to/file}} 保存归档文件

git bisect start ...

这个命令蛮有趣的,具体怎么用参考 git bisect 命令教程 - 阮一峰的网络日志,适合发现了一个 bug 但不知道是由哪段代码引起的场景,通过不断缩小错误范围最终找到 bug 源头!

  • git bisect start {{bad_commit}} {{good_commit}} 开始二分查找
  • git bisect {{good|bad}} 将当前版本标记为“好提交”或“坏提交”
  • git bisect reset 在找到有问题的提交版本并修复后,撤销二分查找在版本库中遗留的临时文件和引用
  • git bisect log > logfile & git bisect replay logfile 查看二分查找的日志记录 & 通过日志文件恢复进度
  • git bisect run sh good-or-bad.sh 使用脚本 good-or-bad.sh 自动化测试

git blame {{file}}

啊,这里代码有问题!到底是谁的锅?通过这个命令我们可以查询到哪些提交中包含对该文件的修改,进而找到背锅侠。你还可以更细致地指定哪几行,比如 git blame L n,m {{file}} 就规定了{{file}} 中从第 n 行到第 m 行的相关提交。不过如果可以使用可视化的工具,那基本上用不着了。

git cat-file ...

  • git cat-file -s HEAD 获取当前头指针(HEAD)指向提交的大小(bytes)
  • git cat-file -t {{e695606}} 查看 Git 对象类型,包含 commit、tree、blob
  • git cat-file -p {{e695606}} 查看 Git 对象内容
  • git cat-file tag {{tag_name}} 查看该 tag 对象

commit 对象

$ git cat-file -p e695606
tree f58da9a820e3fd9d84ab2ca2f1b467ac265038f9
parent a0c641e92b10d8bcca1ed1bf84ca80340fdefee6
author Jiang Xin <jiangxin@ossxp.com> 1291022581 +0800
committer Jiang Xin <jiangxin@ossxp.com> 1291022581 +0800

which version checked in?

//合并后的分支拥有两个父亲
$ git cat-file -p HEAD
tree ab676f92936000457b01507e04f4058e855d4df0
parent 4902dc375672fbf52a226e0354100b75d4fe31e3
parent acc2f69cf6f0ae346732382c819080df75bb2191
author Jiang Xin <jiangxin@ossxp.com> 1291535485 +0800
committer Jiang Xin <jiangxin@ossxp.com> 1291535485 +0800

Merge commit 'acc2f69'

tree 对象

$ git cat-file -p f58da9a
100644 blob fd3c069c1de4f4bc9b15940f490aeb48852f3c42    welcome.txt

blob 对象

$ git cat-file -p fd3c069c1de4f4bc9b15940f490aeb48852f3c42
Hello.
Nice to meet you.

tag 对象

$ git cat-file tag mytag3
object ebcf6d6b06545331df156687ca2940800a3c599d
type commit
tag mytag3
tagger user1 <user1@sun.ossxp.com> 1293960936 +0800

My first GPG-signed tag.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)

iQEcBAABAgAGBQJNIEboAAoJEO9W1fg3N5xn42gH/jFDEKobqlupNKFvmkI1t9d6
lApDFUdcFMPWvxo/eq8VjcQyRcb1X1bGJj+pxXk455fDL1NWonaJa6HE6RLu868x
CQIWqWelkCelfm05GE9FnPd2SmJsiDkTPZzINya1HylF5ZbrExH506JyCFk//FC2
8zRApSbrsj3yAWMStW0fGqHKLuYq+sdepzGnnFnhhzkJhusMHUkTIfpLwaprhMsm
1IIxKNm9i0Zf/tzq4a/R0N8NiFHl/9M95iV200I9PuuRWedV0tEPS6Onax2yT3JE
I/w9gtIBOeb5uAz2Xrt5AUwt9JJTk5mmv2HBqWCq5wefxs/ub26iPmef35PwAgA=
=jdrN
-----END PGP SIGNATURE-----

git cherry-pick {{commit}}

从众多的提交中挑选出一个提交应用在当前的工作分支中,该命令需要提供一个提交ID作为参数,操作过程相当于将该提交导出为补丁文件,然后在当前 HEAD 上重放形成无论内容还是提交说明都一致的提交。

git clean -fd

清除当前工作区中没有加入版本库的文件和目录(非跟踪文件和目录),如果怕误操作,可以使用 git clean -fdn 先确认一下哪些文件和目录会被删除。

git commit-tree {{tree}} [-m "{{message}}]"

基于一个 tree 的 hash id 创建了一个 commit 对象。参考 Git commit-tree 与 Git commit的区别_solinger的博客-CSDN博客

git describe

使用 tag 之后,可以执行该命令显示当前版本库的最新提交的版本号。显示的时候会选取离该提交最近的里程碑作为“基础版本号”,后面附加标识距离“基础版本”的数字以及该提交的SHA1哈希值缩写。如果最新的提交上恰好被打了一个“里程碑”,则直接显示“里程碑”的名字。

注意,git describe命令默认不使用轻量级里程碑生成版本描述字符串,如果需要请加上 --tags 参数。

$ git describe
old_practice

$ git describe //做了一些改动后
old_practice-3-gc024f34

$ git log --oneline --decorate -4 //此时的提交记录
c024f34 (HEAD, master) README is from welcome.txt.
63992f0 restore file: welcome.txt
7161977 delete trash files. (using: git add -u)
2b31c19 (tag: old_practice) Merge commit 'acc2f69'

$ git describe 384f1e0 //也可以加一个commit为参数
jx/v2.2

$ echo hacked >> README; git describe --dirty; git checkout -- README //工作区对文件有修改
jx/v1.0-dirty

$ git describe master^ --always //提交不包含里程碑,显示精简提交ID;不加该参数则会报错
75346b3

git format-patch

一般使用 git format-patch {{revision_1}}...{{revision_2}} 将这两个版本之间的所有更改打包成一个补丁文件。

如果想应用该补丁,可以先执行 git stash 保存自己的修改进度,然后执行 patch -p1 < XXX .patch 命令应用补丁文件。应用完补丁后,再执行 git stash pop 将您的改动合并到工作区。(⚠️没太搞明白这一过程)

git fsck

运行仓库的一致性检查,如果有任何问题就会报告。这项操作有点耗时,通常报的警告是“悬空对象“(dangling objects),即没有被任何引用关联松散对象。

如果使用 git fsck --no-reflogs,那就表明不要认为仅由 reflog 中的条目引用的提交是可访问的,也就是说连防止误触的最后一道防线也放弃了!

详情参考 git gc和fsck的用法 - 虚生 - 博客园

git gc

清除未被关联的对象,默认情况只清除 2 周以前的未被关联的对象。可以向 git gc ****提供 --prune=<date> 参数,其中的时间参数传递给 git prune –expire <date> ****,实现对指定日期之前的未被关联的松散对象进行清理。

打包分散在 .git/refs 下的文件到文件 .git/packed-refs 中。 如果没有关闭配置 gc.packrefs,则会执行 git pack-refs –all –prune ****实现对引用的打包。(???)

git reflog expire –all

丢弃 90 天前的 reflog 记录。

git repack

对松散对象进行打包。凡是有引用关联的对象都被打在包里,未被关联的对象仍旧以松散对象形式保存。

git rerere gc

对合并冲突的历史记录进行过期操作。

git ls-files

  • git ls-files -s 显示暂存的条目的相关信息(模式位,文件哈希后的值,暂存号和文件名)
  • git ls-files -d 显示删除了的文件
  • git ls-files -c 显示缓存了的文件(默认)
  • git ls-files -m 显示修改了的文件
  • git ls-files -o 显示其他类型的文件(比如未追踪的)
  • 注意这个输出和之前使用 git ls-tree 命令输出不一样,如果想要使用 git ls-tree 命令,需要先将暂存区的目录树写入Git对象库(用 git write-tree 命令),然后在针对 git write-tree 命令写入的 tree 执行 git ls-tree 命令。

git ls-remote

查看远程版本库的分支。还可以使用正则匹配,例如 git ls-remote origin my* 可以匹配到 tag 以 my 开头的分支。

git ls-tree

列出树对象的内容。使用 l 参数,可以显示文件的大小;如果想要递归显示目录内容,则使用 r 参数调用;使用参数 t 可以把递归过程遇到的每棵树都显示出来,而不只是显示最终的文件。

git name-rev

git describe 类似,会显示提交 ID 及其对应的一个引用。默认优先使用分支名,除非使用 –tags 参数。还有一个显著的不同是,如果提交上没有相对应的引用,则会使用最新提交上的引用名称并加上向后回溯的符号 ~<num>

//默认优先显示分支名
$ git name-rev HEAD
HEAD master

//之所以对应的里程碑引用名称后面加上后缀^0,是因为该引用指向的是一个tag对象而非提交。用^0后缀指向对应的提交。
$ git name-rev HEAD --tags
HEAD tags/jx/v1.0^0

//如果提交上没有对应的引用名称,则会使用新提交上的引用名称并加上后缀~<num>。后缀的含义是第<num>个祖先提交。
$ git name-rev --tags 610e78fc95bf2324dc5595fa684e08e1089f5757
610e78fc95bf2324dc5595fa684e08e1089f5757 tags/jx/v2.3~1

最酷的是,可以对标准输入中的提交 ID 进行改写,使用管道符号对前一个命令的输出进行改写,会显示神奇的效果:

$ git log --pretty=oneline origin/helper/master | git name-rev --tags --stdin
bb4fef88fee435bfac04b8389cf193d9c04105a6 (tags/jx/v2.3^0) Translate for Chinese.
610e78fc95bf2324dc5595fa684e08e1089f5757 (tags/jx/v2.3~1) Add I18N support.
384f1e0d5106c9c6033311a608b91c69332fe0a8 (tags/jx/v2.2^0) Bugfix: allow spaces in username.
e5e62107f8f8d0a5358c3aff993cf874935bb7fb (tags/jx/v2.1^0) fixed typo: -help to --help
5d7657b2f1a8e595c01c812dd5b2f67ea133f456 (tags/jx/v2.0^0) Parse arguments using getopt_long.
3e6070eb2062746861b20e1e6235fed6f6d15609 (tags/jx/v1.0^0) Show version.
75346b3283da5d8117f3fe66815f8aaaf5387321 (tags/jx/v1.0~1) Hello world initialized.

git pack-refs

传统上,分支和标签(统称为 refs)的提示每个参考文件存储在目录下的(子)$GIT_DIR/refs 目录中。尽管许多分支技巧往往会经常更新,但大多数标签和一些分支技巧从未更新过。当一个存储库有数百或数千个标签时,这种每文件一格式的格式既浪费存储空间又损害性能。

该命令用于通过将 ref 存储在单个文件中来解决存储和性能问题,$GIT_DIR/packed-refs。如果传统 $GIT_DIR/refs 目录层次结构中缺少 ref ,则会在此文件中查找并在找到时使用。对分支的后续更新总是在 $GIT_DIR/refs 目录层次结构下创建新文件。

  • git pack-refs -all 该命令默认打包已经打包的所有标签和引用,并且仅保留其他引用。这是因为分支预计会积极开发,打包他们的提示无助于性能。此选项也会导致分支提示被打包。用于具有许多历史兴趣分支的存储库。
  • git pack-refs -no-prune 这个命令通常会在打包之后删除松散参考,而该选项告诉它不要。

(资料看起来不多?翻译得怪怪的!)

git prune

Git 提供了一个清理的命令,用来清理标识为 dangling 的对象也就是没有被任何引用直接或者间接关联到的对象。

git receive-pack

当版本库接收到其他版本库的推送(push)请求时,会调用 git receive-pack ****命令以接收请求。在接收到推送的提交后,对版本库进行按需整理。(hanmei:看起来 GitHub 作为一个 Git 的后端,也配有这一功能吧?)

git reflog

master 分支的日志文件存放在 .git/logs/refs/heads/master 中。我们可以通过 linux 的文件操作命令 tail -5 .git/logs/refs/heads/master 来访问它。也可以通过 git reflog 命令,对这个文件进行操作:

$ git reflog show master | head -5

9e8a761 master@{0}: 9e8a761: updating HEAD

e695606 master@{1}: HEAD^: updating HEAD

4902dc3 master@{2}: commit: does master follow this new commit?

e695606 master@{3}: commit: which version checked in?

a0c641e master@{4}: commit (amend): who does commit?

使用 git reflog 的输出中还提供一个方便易记的表达式:<refname>@{<n>}。这个表达式的含义是引用 <refname> 之前第 <n> 次改变时的SHA1哈希值。

还有一种功能,让 log 过期:

# 事实上这条命令没用
$ git reflog expire --all
$ git reflog
6652a0d HEAD@{0}: HEAD^: updating HEAD
51519c7 HEAD@{1}: commit: add bigfiles.

# 需要要为它提供 --expire=<date> 参数,才能强制 <date> 之前的记录全部过期。
$ git reflog expire --expire=now --all
$ git reflog

git rev-list

把查看历史提交玩出了花样。

  • git rev-list HEAD 列出当前分支的所有提交,结果显示为一串 complete commit id,阅读不友好;结合 wc -l 可以查看版本库提交次数。
  • git rev-list --oneline HEAD 功能同上,但可以显示成 commit id 的前几位和提交说明的友好格式
  • git rev-list --since={{'2019-12-01 00:00:00'}} {{branch_name}} 某个时间点之后的所有历史提交,依然是阅读不友好的格式
  • git rev-list --oneline {{commit}} 从该版本开始的所有历史提交,阅读友好的格式
  • git rev-list --oneline {{commit1}} {{commit2}} 两个或多个版本,相当于每个版本单独使用时指代的列表的并集
  • git rev-list --oneline ^{{commit1}} {{commit2}} 在一个版本前面加上符号(^)含义是取反,即排除这个版本及其历史版本
  • git rev-list --oneline {{commit1}}..{{commit2}} 使用两个点连接两个版本,如G..D,就相当于^G D。这两种表示法的区别:版本取反,参数的顺序不重要,但是“点点”表示法前后的版本顺序很重要
  • git rev-list --oneline {{commit1}}...{{commit2}} 三点表示法的含义是除去两个版本共同能够访问到的历史版本,即交集的补集。见 git checkout - What are the differences between double-dot “..” and triple-dot “…” in Git commit ranges? - Stack Overflow
  • git rev-list --oneline {{commit}}^@ 除去自身的历史提交记录
  • git rev-list --oneline {{commit}}^! 不包含历史提交,只包含自身的提交
  • git rev-list --merges {{commit}} 列举出所有在该 commit 之上的 merge commits

git rev-parse

Git 的一个底层命令,其功能非常丰富(或者说杂乱),很多 Git 脚本或工具都会用到这条命令。

  • git rev-parse --git-dir 显示版本库 .git 目录所在的位置
  • git rev-parse --show-toplevel 显示工作区根目录
  • git rev-parse --show-prefix 相对于工作区根目录的相对目录
  • git rev-parse --show-cdup 显示从当前目录(cd)后退(up)到工作区的根的深度
  • git rev-parse {{commit}} 显示引用对应的 long commit id,其中 {{commit}} 可以有多个,可以用哈希值的前几位指代整个哈希值,可以是里程碑
    • git rev-parse A^{} A^0 A^{commit} 里程碑A指向了一个Tag对象而非提交的时候,用下面的三个表示法都可以指向里程碑对应的提交。实际上下面的语法也可以直接作用于轻量级里程碑(直接指向提交的里程碑)或者作用于提交本身。
    • git rev-parse A^ A^1 B^0 A^ 就相当于 A^1。而B^0代表了B所指向的一个Commit对象(因为B是Tag对象)。
    • git rev-parse A^^3^2 F^2 J^{} 更为复杂的表示法。连续的^符号依次沿着父提交进行定位至某一祖先提交。^后面的数字代表该提交的第几个父提交。
    • git rev-parse A~3 A^^^ G^0 记号~就相当于连续个符号^
    • git rev-parse A^{tree} A: 显示里程碑A对应的目录树,两种表示法均可。
    • git rev-parse A^{tree}:src/Makefile A:src/Makefile 显示树里面的文件,两种表示法均可。
    • git rev-parse :gitg.png HEAD:gitg.png 暂存区里的文件和HEAD中的文件相同。
    • git rev-parse :/"Commit A" 还可以通过在提交日志中查找字串的方式显示提交。
    • git rev-parse HEAD@{0} master@{0} 再有就是reflog相关的语法。
  • git rev-parse --symbolic --branches 显示分支
  • git rev-parse --symbolic --tags 显示里程碑
  • git rev-parse --symbolic --glob=refs/* 显示定义的所有引用

git rm

删除命令

  • git rm {{file}} 从仓库和文件系统中移除文件
  • git rm -r {{directory}} 移除文件夹
  • git rm --cached {{file}} 从暂存区删除文件,但工作区不做出改变

git show

显示命令

  • git show 显示上一个 commit 的信息
  • git show {{commit}} 显示指定 commit 的信息
  • git show {{tag}} 显示与指定 tag 关联的 commit 的信息
  • git show --stat {{commit}} 只显示变更文件的统计信息
  • git show --summary {{commit}} 只显示变更文件是 added,renamed 还是 deleted

git show-ref

查看全部的本地引用

$ git show-ref

c4acab26ff1c1125f5e585ffa8284d27f8ceea55 refs/heads/master

c4acab26ff1c1125f5e585ffa8284d27f8ceea55 refs/remotes/origin/HEAD

git write-tree

如果想要使用 git ls-tree 命令,需要先将暂存区的目录树写入 Git 对象库(用 git write-tree 命令),然后在针对 git write-tree 命令写入的 tree 执行 git ls-tree 命令。

$ git write-tree
9431f4a3f3e1504e03659406faa9529f83cd56f8
$ git ls-tree -l 9431f4a
040000 tree 53583ee687fbb2e913d18d508aefd512465b2092       -    a
100644 blob 51dbfd25a804c30e9d8dc441740452534de8264b      34    welcome.txt

Git 术语

来自 软件开发|Git 入门:术语基础

  • 提交Commit —— 将当前索引的内容保存在一个新的提交中,并附上用户描述更改的日志信息。
  • 分支Branch —— 指向一个提交的指针。
  • master —— 第一个分支的默认名称。
  • HEAD —— 指向当前分支上最近一次提交的指针。
  • 合并Merge —— 合并两个或多个提交的历史。
  • 工作空间Workspace —— Git 仓库本地副本的通俗名称。
  • 工作树Working tree —— 工作区中的当前分支;任何时候你都可以在 git status 的输出中看到这个。
  • 缓存Cache —— 用于临时存储未提交的变更的空间。
  • 索引Index —— 变更提交前存储其变化的缓存。
  • 跟踪和未跟踪的文件 —— 没有被索引缓存的文件或尚未加入其中的文件。
  • 暂存Stash —— 另一个缓存,作为一个堆栈,在这里可以存储更改而不需要提交它们。
  • origin —— 远程版本库的默认名称。
  • 本地仓库Local repository —— 也就是你在工作站上保存 Git 仓库副本的地方。
  • 远程存储库Remote repository —— Git 存储库的第二副本,你可以在这里推送变更以便协作或备份。
  • 上游存储库Upstream repository —— 你跟踪的远程存储库的通俗说法。
  • 拉取请求Pull request —— 这是 GitHub 的专用术语,用于让其他人知道你推送到仓库分支的变化。
  • 合并请求Merge request —— 这是 GitLab 的专用术语,用于让其他人知道你推送到仓库分支的变化。
  • origin/master —— 远程版本库及其主要分支的默认名称。