在这一章中,我们将会带你来看一组技巧。这些技巧中的一些在特定情况下是非常有用的,但我们也有可能永远都会不需要它们。因此,或许你可 以先粗略地浏览一下本章,充分了解一下这里所涉及的内容。然后在真正需要它们时,再回过头来看具体细节。
1️⃣ 引用日志
Git 像是一条牧羊犬。因为它始终在试图保护它(开发者)的牧群。我们都知道,Git 通常不会立即删除版本中的对象。因此,无论何时我们修改了一些东西也好,用 Git 在版本库中新建了一些对象也罢,旧的对象都不会被删除。即使是垃圾回收机制也一样,例如,即使我们调用了gc
命令,也仅仅是删除了一些符合特定最小年龄值的对象, 默认设置是两个星期(其配置选项:gc.pruneexpire
)。
此外,Git 也会持续跟踪一个分支上所发生的所有修改,并将这种所谓的引用日志长期保存在 .git/logs
目录中。我们可以通过log
命令的 --walk-reflogs
选项来显示一个分支本地历史记录。
> git log --walk-reflogs mybranch
只要我们能找到包含“丢失”修改的提交,就可以把这次修改恢复过来,例如通过 cherry-pick
命令、rebase
命令,或者执行因此简单的合并操作都可以。
请注意! 对于本地克隆版本库来说,引用日志通常是处于活动状态的。而我们平常放在服务器上的裸版本库在默认情况下是没有引用日志选项的,你可以通过以下命令来打开它。
> git config core.logAllRefUpdates true
当然,我们也可以直接将其用于系统级默认设置。
> git config --system core.logAllRefUpdates true
2️⃣ 忽略临时性的本地修改
有时候,我们虽然也会用 Git 去修改一些管理文件,但可能并不想将这些修改纳入版本化控制。举个例子:在写博文的时候,我们常常会注释掉一些章节,为的是可以更快地完成整个文档。在这种情况下,我们一般是不会希望将这些修改版本化的。再比如:为了找到某个错误,我们可能会需要生成一些额外的调试信息,这些信息也不是我们以后所需要的。
.gitignore
文件中的那些条目在这里可起不了作用,因为它们只针对那些不受Git 管理的文件。当然。我们也可以用选择性提交的方式来绕过这个问题。但这会很无趣,因为这样一来,我们在后续每次提交中都不得不再重新选择一次,以说明自己修改了什么以及不接受什么修改。
忽略一些已被版本化的文件
在这里, Git 的管理文件会被暂时忽略,所以我们对这些文件的修改将不会被接受。
- 忽略一些已被版本化的文件
我们可以通过update-index
命令的--assume-unchanged
选项来在暂存区创建某种标记, 以确保 Git 会不再检查这些被标记所指定的文件是否被修改过,这就对于我们假设该文件不会再被修改了。
> git update-index --assume-unchanged foo.txt
- 回到工作上来
现在,我们可以回到工作上来了,因为status
和add
命令都不会显示 foo.txt 文件中所发生的修改了。- 停止忽略
我们可以用--no-assume-unchanged
选项来取消--assume-unchanged
选项对各单一文件的影响。另外,你还可以用--really-refresh
命令来重置一下所有文件的状态。
> git update-index --really-refresh
3️⃣ 检查对文本文件的修改
一般情况下,Git 的 diff
算法会去逐行比较两个文件。因为在源代码中,我们往往修改的都是单行内容,其邻近行通常不会被波及,所以 diff 算法很容易就能察觉到其中的不同。但在文本文件中,事情就不一样了。因为其修改往往是整体性的,例如我们可能会将某些单词整体从一行移动到另一行。所以从 diff 的输出中,我们往往很难看出一个文本文件究竟修改了哪些内容。
> git diff
...
-Walter goes every
-day to schiil.
+Walter goes to
+school.
...
对于连续性的文本, --word-diff
选项会很有用,因为它可以按单词显示我们所做的修改。
> git diff --word-diff
...
Walter goes [-every -]
[-day] to
[-schiil.-] {+school.+}
...
另外,我们也可以设置 --word-diff=color
, 以便用一种不同的颜色来显示文件中的不同。
4️⃣ 别名——Git 命令的快捷方式
如果我们经常以命令行的形式使用 Git 的话,为频繁使用的命令定义一些快捷方式可能会很有帮助。
> git config --global alias.ci commit
> git config --global alias.st status
在这里,我们为commit
和status
这两个命令分别设置了别名ci
和 st
。现在你可以立即使用它们了。例如:
> git st
别名也被 Git 的自动完成功能(如果已安装了的话)考虑在内。因此,它也可为一些很少被用到的命令设置别名。例如:
> git config --global alias.ignore-temporarily 'update-index --assume-unchanged'
5️⃣ 为临时指向的提交创建分支
如果我们正在查找错误或试图解决某些棘手的合并冲突,可能经常要记住那些相关的提交,例如能指向这一切的某个指针,或是某个合并基点。我们似乎得从在纸上列出这些提交 的散列值开始,其实完全不必如此! 我们只需要在发现感兴趣的提交时简单地为其创建一个分支就可以了。
> git branch tmp/a-silly-error 8b167
现在,我们可以直接用名字来应用提交了。因为 Git 的自动完成跟你现在“知道”了这个名字。例如,我们可以这样找出包含了该提交的标签。
> git tag --contains tmp/a-silly-error
1.0.2
1.0.3
接下来,我们可用以下命令来创建一个名为 tmp/merge-base
的分支,以指向 master 和 feature 这两个分支的合并基点。
> git branch tmp/merge-base 'git merge-base master feature'
这里的 tmp/
前缀是一个命名约定:属于临时分支的名称空间,以便我们更容易将它们与正常的分支区分开。当然,这不是一个技术要求。我们可以调用任何想调用的临时分支。晚些时候,我们可以用以下命令清理这些分支。
> git branch -D
'git branch --no-color --list tmp/\*
| grep -v '* '
| xargs '
6️⃣ 将提交移动到另一分支
当我们进行某些修改的时候,当然最好是能在对的分支上做。但有时我们会犯错,并且在 某个错的分支上做事。在这种情况下,我们就不得不需要将一些移动到另一个分支上去了,如图所示。
将提交移动到另一分支
在这里,我们需要将一些提交从分支A 移动到分支B 上去。
对提交进行重新排序,并用
tmp/SPLIT
标记出其分界点
为了完成移动,我们必须要让这些提交置于分支的末端。
> git rebase --interactive
在编辑器中,我们进行了逐行排序,然后将其顶部那些行继续留在A 中,而位于底部 的则都移动到 B。在这个过程中,我们会用exec
命令创建一个临时分支tmp/SPLIT
, 该临时分支所标记的就是上述两者的分割点。
pick 6a2f459 should stay in A 1
pick 05c2935 should stay in A 2
exec git branch -ftmp/SPLIT
pick af22ed6 should go to B 1
pick 4f30adf should go to B 2
现在,tmp/SPLIT
分支所指向应该是留在A 中的最后一次提交。执行转移
首先,我们要创建一个名为tmp/MOVE
的临时分支,然后再来移动它。再接下来,用merge
命令将tmp/MOVE
转移到分支B 上。最后,将剩余的提交截留在分支A 中。
> git checkout -B tmp/MOVE A
> git rebase tmp/SPLIT --onto B
> git checkout B
> git merge --ff tmp/MOVE
> git branch --force A tmp/SPLIT
请注意! 移动提交可能会扰乱其他开发者的工作。所以,我们应该只移动那些没有与其 他开发者共享的提交,或者你必须要将相关信息知会其他开发者,并要求他们各自将已开发的提交移动到这些被移动提交的顶部。
《【Git教程】(十)版本库之间的依赖 —— 项目与子模块之间的依赖、与子树之间的依赖 ~》
《【Git教程】(十二)工作流之项目设置 — 何时使用工作流,工作流的结构,项目设置概述、执行过程及其实现 ~》