前言
引用Git Document中开篇提到的一句话,“大多数的GUI软件只实现了Git所有功能的一个子集以降低操作难度。如果你学会了在命令行下如何操作,那么你在操作GUI软件时应该也不会遇到什么困难,但是,反之则不成立。” 所以如果你还在使用诸如SourceTree一类的GUI工具,劝你早日舍弃,用更谦卑的姿态“拥抱”Git吧。
并且阅读Git的官方文档其实不仅仅是为了巩固那么几个常用的Git命令以更胜任多人协作情景下的工作,更是为了梳理所接触到的Git碎片化知识以及Git底层的实现思路和灵感,做到对于每一种语言都能有一张知识网络体系,毕竟做到知根知底才能使我在日常工作中更有安全感。
本文不会介绍Git的基本用法,你大可以随意Google一番Git基础教程,这里只会针对官方文档中提到的一些我自己Git认知上的“遗珠”,进行摘录和附加自己的解读。
git config配置文件
Git自带一个git config的工具来帮助设置控制Git外观和行为的配置变量。这些变量存储在三个不同的位置:
/etc/gitconfig
文件: 包含系统上每一个用户及他们仓库的通用配置。 如果使用带有 –system 选项的git config时,它会从此文件读写配置变量。~/.gitconfig
或~/.config/git/config
文件:只针对当前用户。 可以传递 –global 选项让 Git 读写此文件。- 当前使用仓库的 Git 目录中的 config 文件(就是
/<指定的repo>/.git/config
):针对该仓库。
每一个级别的配置优先权会高于上一个配置。
git add命令
git add是一个多功能的命令,它可以用来:
- 开始追踪新的
未被追踪文件
- 把已跟踪的文件放到
暂存区
- 合并时把有冲突对的文件标记为
已解决状态
需要注意的是,git commit -a -m <快照的提交信息>
命令中的-a 并不等同于git add操作,此时的commit操作并不会将那些未被追踪的文件加入到已跟踪文件清单。
.gitignore格式规范
文件 .gitignore 的格式规范如下:
- 所有空行或者以 # 开头的行都会被 Git 忽略。
- 可以使用标准的 glob 模式匹配。
- 匹配模式可以以(/)开头防止递归。
- 匹配模式可以以(/)结尾指定目录。
- 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(!)取反。
所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。 星号()匹配零个或多个任意字符;[abc] 匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);问号(? )只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配 (比如 [0-9] 表示匹配所有 0 到 9 的数字)。使用两个星号() 表示匹配任意中间目录,比如a/**/z 可以匹 配 a/z, a/b/z 或 a/b/c/z 等。
GitHub 有一个十分详细的针对数十种项目及语言的 .gitignore 文件列表,你可以在 这里 找到它。
git diff命令
git diff命令比较的是工作目录中当前文件和暂存区域快照之间的差异,也就是修改之后还没有暂存起来的变化内容。
若要查看已暂存的将要添加到下次提交里的内容,可以用 git diff --cached
命令。(Git 1.6.1 及更高版本 还允许使用git diff --staged
,效果是相同的,但更好记些)
请注意,git diff 本身只显示尚未暂存的改动,而不是自上次提交以来所做的所有改动。 所以有时候你一下子暂存了所有更新过的文件后,运行git diff后却什么也没有,就是这个原因。
还可以通过图形化工具或者Vim帮助我们更直观地输出git diff的结果,我们可以先使用git difftool --tool-help
来查看你的系统支持哪些git diff插件,然后再使用git difftool --tool=<diff插件的名称>
来查看diff输出。
git rm命令
git rm命令主要是用来将指定的文件从已跟踪文件清单中移除(等同于从暂存区移除这些文件)。
当我们需要删除git仓库中某些文件的时候,如果这些文件已被修改过且已经放到暂存区域的话,则必须要用强制删除选项-f(即force的首字母)。这是一种安全特性,用于防止误删还没有添加到快照的数据,这样的数据不能被Git恢复。
当我们仅仅想要将一些文件从跟踪文件清单中移除,但此时你忘记添加.gitignore文件,已经不小心将这些文件添加到暂存区时,可以通过git rm --cached <文件名或者路径>
达到这一目的。
git rm命令后面可以列出文件或者目录的名字,也可以使用glob
模式,例如:
$ git rm logs/\*.log
注意到星号 * 之前的反斜杠 \,因为 Git 有它自己的文件模式扩展匹配方式,所以我们不用 shell 来帮忙展开。此命令删除 log/ 目录下扩展名为 .log 的所有文件。
git log命令
git log用于查看提交历史。但是实用的可选项和玩法非常的多。下面列举一些常用选项:
选项 | 说明 |
---|---|
-p | 按补丁格式显示每个更新之间的差异 |
–stat | 显示每次更新的文件修改统计信息 |
–shortstat | 只显示 –stat 中最后的行数修改添加移除统计 |
–name-only | 仅在提交信息后显示已修改的文件清单 |
–name-status | 显示新增、修改、删除的文件清单 |
–abbrev-commit | 仅显示 SHA-1 的前几个字符,而非所有的40个字符 |
–relative-date | 使用较短的相对时间显示(比如,“2 weeks ago”) |
–graph | 显示 ASCII 图形表示的分支合并历史 |
–pretty | 使用其他格式显示历史提交信息 |
-{n} | 仅显示最近的 n 条提交 |
–since, –after | 仅显示指定时间之后的提交 |
–until, –before | 仅显示指定时间之前的提交 |
–author | 仅显示指定作者相关的提交 |
–committer | 仅显示指定提交者相关的提交 |
–grep | 仅显示含指定关键字的提交 |
-S | 仅显示添加或移除了某个关键字的提交 |
使用–pretty=format可以来采用自定义的输出格式来展示提交历史,这个选项有一些内建的子选项,具体如下:
选项 | 说明 |
---|---|
%H | 提交对象(commit)的完整哈希字串 |
%h | 提交对象的简短哈希字串 |
%T | 树对象(tree)的完整哈希字串 |
%t | 树对象的简短哈希字串 |
%P | 父对象(parent)的完整哈希字串 |
%p | 父对象的简短哈希字串 |
%an | 作者(author)的名字 |
%ae | 作者的电子邮件地址 |
%ad | 作者修订日期(可以用 –date= 选项定制格式) |
%ar | 作者修订日期,按多久以前的方式显示 |
%cn | 提交者(committer)的名字 |
%ce | 提交者的电子邮件地址 |
%cd | 提交日期 |
%cr | 提交日期,按多久以前的方式显示 |
%s | 提交说明 |
结合上述一些选项带来的优点,我制作了两条比较实用的别名供大家使用:
alias git_pretty_log "git log -p --relative-date --graph"
alias git_pretty_log_grep "git log -p --relative-date --graph --grep $1"
本地撤销操作
本地撤销操作分为以下三种情景。
- 提交追加
有时候我们刚提交完一次快照才发现漏掉了几个文件没有添加,或者提交信息写错了。此时,可以使用带有--amend
选项的commit命令尝试重新提交$ git commit --amend
这个命令会将新加入到暂存区的文件提交且与上一次提交合并,并且会通过文本编辑器弹出之前的提交信息,可以重新编辑覆盖上一次的提交信息。
最终你只会有一次提交 —— 第二次提交将代替第一次提交的结果。 - 取消暂存区的文件
有时候我们修改了两个文件并且想要将它们作为两次独立的修改提交,但是却意外地使用git add *
一次性将它们存入暂存区中了,此时,可以通过$ git reset HEAD <file>
来取消暂存,也可以直接
git reset HEAD
将暂存区的所有文件全部撤回未被跟踪文件清单中。 `特别注意的是,虽然在调用时加上–hard选项可以令git reset成为一个危险的命令,可能导致工作目录所有当前进度丢失,但不加选项地调用git reset并不危险,它只会修改暂存区域。 - 撤销对文件的修改
如果有个文件修改之后还未被我们加到了暂存区中,但我们希望永久
撤销这些修改,此时,我们可以使用git checkout -- <file>
去撤销这个文件的所有修改。
另外你需要知道git checkout -- <file>是一个非常危险的命令,因为你对这个文件做的任何修改都会消失,这个命令仅仅只是从Git仓库拷贝了另一个文件来覆盖它,并不会留下操作痕迹,除非你确实清楚不想要当前这份修改了且日后也不需要用到了,否则还是建议使用git stash去保存进度,清空当前工作目录的修改