-
1. 起步
-
2. Git 基础
-
3. Git 分支
-
4. 服务器上的 Git
- 4.1 协议
- 4.2 在服务器上部署 Git
- 4.3 生成 SSH 公钥
- 4.4 架设服务器
- 4.5 Git Daemon
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 第三方托管服务
- 4.10 小结
-
5. 分布式 Git
-
A1. 附录 A: Git 在其他环境
- A1.1 图形界面
- A1.2 Visual Studio 中的 Git
- A1.3 Visual Studio Code 中的 Git
- A1.4 IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine 中的 Git
- A1.5 Sublime Text 中的 Git
- A1.6 Bash 中的 Git
- A1.7 Zsh 中的 Git
- A1.8 PowerShell 中的 Git
- A1.9 小结
-
A2. 附录 B: 在应用程序中嵌入 Git
-
A3. 附录 C: Git 命令
8.2 自定义 Git - Git 属性
Git 属性
其中一些设置也可以针对特定路径进行指定,这样 Git 就只会为子目录或文件子集应用这些设置。这些路径特定的设置被称为 Git 属性,它们可以设置在项目目录中的 .gitattributes 文件里(通常是项目的根目录),或者如果不想将属性文件提交到项目中,则可以设置在 .git/info/attributes 文件中。
通过使用属性,你可以实现诸如为项目中的单个文件或目录指定不同的合并策略,告诉 Git 如何 diff 非文本文件,或者在 Git 检查入或检出内容之前对内容进行过滤。在本节中,你将了解一些可以在 Git 项目的路径上设置的属性,并看到一些实际使用此功能的例子。
二进制文件
Git 属性可以用来告诉 Git 哪些文件是二进制文件(在 Git 无法自行判断的情况下),并为 Git 提供处理这些文件的特殊指令。例如,有些文本文件可能是机器生成的,无法进行 diff,而有些二进制文件是可以进行 diff 的。你将学会如何告诉 Git 哪些是哪些。
识别二进制文件
有些文件看起来像文本文件,但从功能上看,它们应该被视为二进制数据。例如,macOS 上的 Xcode 项目包含一个以 .pbxproj 结尾的文件,它基本上是一个由 IDE 写入磁盘的 JSON(纯文本 JavaScript 数据格式)数据集,其中记录了你的构建设置等信息。尽管它在技术上是文本文件(因为它全是 UTF-8),但你不希望将其视为文本文件,因为它实际上是一个轻量级数据库——当两个人修改它时,你无法合并其内容,而且 diff 通常也没有用。该文件是为了被机器解析的。本质上,你希望将其视为二进制文件。
要告诉 Git 将所有 pbxproj 文件视为二进制数据,请将以下行添加到你的 .gitattributes 文件中:
*.pbxproj binary
现在,当你对项目运行 git show 或 git diff 时,Git 将不会尝试转换或修复 CRLF 问题;也不会尝试计算或显示此文件中更改的 diff。
Diff 二进制文件
你还可以使用 Git 属性功能来有效地 diff 二进制文件。为此,你需要告诉 Git 如何将你的二进制数据转换为一种文本格式,然后可以通过常规的 diff 进行比较。
首先,你将使用这种技术来解决人类面临的最令人头疼的问题之一:版本控制 Microsoft Word 文档。如果你想版本控制 Word 文档,可以将它们放在 Git 仓库中,并偶尔提交。但这有什么用呢?如果你正常运行 git diff,你只会看到类似这样的结果:
$ git diff
diff --git a/chapter1.docx b/chapter1.docx
index 88839c4..4afcb7c 100644
Binary files a/chapter1.docx and b/chapter1.docx differ
你无法直接比较两个版本,除非你检出它们并手动扫描它们,对吧?事实证明,你可以通过 Git 属性很好地做到这一点。将以下行放入你的 .gitattributes 文件中:
*.docx diff=word
这告诉 Git,任何匹配此模式(.docx)的文件在尝试查看包含更改的 diff 时,都应使用“word”过滤器。什么是“word”过滤器?你需要自己设置。在这里,你将配置 Git 使用 docx2txt 程序将 Word 文档转换为可读的文本文件,然后 Git 会对这些文本文件进行 diff。
首先,你需要安装 docx2txt;你可以从 https://sourceforge.net/projects/docx2txt 下载。按照 INSTALL 文件中的说明将其放在你的 shell 可以找到的位置。接下来,你需要编写一个包装脚本来将输出转换为 Git 所需的格式。创建一个位于你的 PATH 中的文件,名为 docx2txt,并添加以下内容:
#!/bin/bash
docx2txt.pl "$1" -
别忘了为该文件执行 chmod a+x。最后,你可以配置 Git 来使用此脚本:
$ git config diff.word.textconv docx2txt
现在 Git 就知道,如果它尝试在两个快照之间执行 diff,并且任何文件都以 .docx 结尾,它应该将这些文件通过“word”过滤器进行处理,该过滤器被定义为 docx2txt 程序。这有效地在尝试 diff 之前,生成了 Word 文件漂亮的基于文本的版本。
这是一个例子:本书的第一章被转换为 Word 格式并提交到 Git 仓库。然后添加了一个新段落。以下是 git diff 显示的内容:
$ git diff
diff --git a/chapter1.docx b/chapter1.docx
index 0b013ca..ba25db5 100644
--- a/chapter1.docx
+++ b/chapter1.docx
@@ -2,6 +2,7 @@
This chapter will be about getting started with Git. We will begin at the beginning by explaining some background on version control tools, then move on to how to get Git running on your system and finally how to get it setup to start working with. At the end of this chapter you should understand why Git is around, why you should use it and you should be all setup to do so.
1.1. About Version Control
What is "version control", and why should you care? Version control is a system that records changes to a file or set of files over time so that you can recall specific versions later. For the examples in this book you will use software source code as the files being version controlled, though in reality you can do this with nearly any type of file on a computer.
+Testing: 1, 2, 3.
If you are a graphic or web designer and want to keep every version of an image or layout (which you would most certainly want to), a Version Control System (VCS) is a very wise thing to use. It allows you to revert files back to a previous state, revert the entire project back to a previous state, compare changes over time, see who last modified something that might be causing a problem, who introduced an issue and when, and more. Using a VCS also generally means that if you screw things up or lose files, you can easily recover. In addition, you get all this for very little overhead.
1.1.1. Local Version Control Systems
Many people's version-control method of choice is to copy files into another directory (perhaps a time-stamped directory, if they're clever). This approach is very common because it is so simple, but it is also incredibly error prone. It is easy to forget which directory you're in and accidentally write to the wrong file or copy over files you don't mean to.
Git 成功且简洁地告诉我们,我们添加了字符串“Testing: 1, 2, 3.”,这是正确的。它并不完美——格式更改不会在此处显示——但肯定有效。
你可以通过此方法解决的另一个有趣问题涉及 diff 图像文件。一种方法是让图像文件通过一个提取其 EXIF 信息的过滤器——EXIF 信息是大多数图像格式记录的元数据。如果你下载并安装 exiftool 程序,你可以使用它将图像转换为关于元数据的文本,这样至少 diff 会显示出任何发生的更改的文本表示。将以下行放入你的 .gitattributes 文件中:
*.png diff=exif
配置 Git 使用此工具:
$ git config diff.exif.textconv exiftool
如果你替换项目中的一个图像并运行 git diff,你会看到类似这样的结果:
diff --git a/image.png b/image.png
index 88839c4..4afcb7c 100644
--- a/image.png
+++ b/image.png
@@ -1,12 +1,12 @@
ExifTool Version Number : 7.74
-File Size : 70 kB
-File Modification Date/Time : 2009:04:21 07:02:45-07:00
+File Size : 94 kB
+File Modification Date/Time : 2009:04:21 07:02:43-07:00
File Type : PNG
MIME Type : image/png
-Image Width : 1058
-Image Height : 889
+Image Width : 1056
+Image Height : 827
Bit Depth : 8
Color Type : RGB with Alpha
你可以清楚地看到文件大小和图像尺寸都已更改。
关键字扩展
习惯了 SVN 或 CVS 的开发者经常要求进行 SVN 或 CVS 风格的关键字扩展。在 Git 中,这方面的主要问题是你无法在提交后修改包含提交信息的文件,因为 Git 首先会对文件进行校验和计算。但是,你可以在文件检出时注入文本,并在将其添加到提交之前将其移除。Git 属性提供了两种实现此目的的方法。
首先,你可以自动将 blob 的 SHA-1 校验和注入到文件中的 $Id$ 字段。如果你为文件或一组文件设置了此属性,那么下次检出该分支时,Git 将用 blob 的 SHA-1 替换该字段。需要注意的是,它不是提交的 SHA-1,而是 blob 本身的 SHA-1。将以下行放入你的 .gitattributes 文件中:
*.txt ident
向测试文件添加 $Id$ 引用:
$ echo '$Id$' > test.txt
下次检出此文件时,Git 会注入 blob 的 SHA-1:
$ rm test.txt
$ git checkout -- test.txt
$ cat test.txt
$Id: 42812b7653c7b88933f8a9d6cad0ca16714b9bb3 $
然而,这种结果的实用性有限。如果你在 CVS 或 Subversion 中使用了关键字替换,你可以包含日期戳——SHA-1 并不是很有用,因为它相当随机,而且你无法仅凭外观判断一个 SHA-1 是比另一个新还是旧。
事实证明,你可以编写自己的过滤器,用于在提交/检出时对文件进行替换。这些被称为“clean”和“smudge”过滤器。在 .gitattributes 文件中,你可以为特定路径设置过滤器,然后设置脚本,这些脚本将在文件被检出(“smudge”,参见 “smudge”过滤器在检出时运行)之前和文件被暂存(“clean”,参见 “clean”过滤器在文件暂存时运行)之前进行处理。这些过滤器可以设置为执行各种有趣的自定义操作。
此功能最初的提交信息提供了一个简单的例子,即在提交之前将所有 C 源代码通过 indent 程序进行处理。你可以通过将 .gitattributes 文件中的过滤器属性设置为使用“indent”过滤器处理 *.c 文件来设置它:
*.c filter=indent
然后,告诉 Git “indent”过滤器在 smudge 和 clean 时做什么:
$ git config --global filter.indent.clean indent
$ git config --global filter.indent.smudge cat
在这种情况下,当你提交匹配 *.c 的文件时,Git 将在暂存它们之前通过 indent 程序处理它们,然后在将它们检出到磁盘之前通过 cat 程序处理它们。cat 程序基本上不做任何事情:它输出与输入相同的数据。这种组合有效地在提交之前将所有 C 源代码文件通过 indent 进行过滤。
另一个有趣的例子是获取 RCS 风格的 $Date$ 关键字扩展。要正确做到这一点,你需要一个小型脚本,该脚本获取文件名,找出该项目的最后提交日期,并将日期插入到文件中。这是一个执行此操作的小型 Ruby 脚本:
#! /usr/bin/env ruby
data = STDIN.read
last_date = `git log --pretty=format:"%ad" -1`
puts data.gsub('$Date$', '$Date: ' + last_date.to_s + '$')
脚本所做的就是从 git log 命令获取最新提交日期,将其插入到它在 stdin 中看到的任何 $Date$ 字符串中,并打印结果——使用你最熟悉的任何语言都可以轻松完成。你可以将此文件命名为 expand_date 并将其放入你的 PATH 中。现在,你需要设置一个 Git 过滤器(称之为 dater),并告诉它使用你的 expand_date 过滤器在检出时 smudge 文件。你将使用一个 Perl 表达式来在提交时清理它:
$ git config filter.dater.smudge expand_date
$ git config filter.dater.clean 'perl -pe "s/\\\$Date[^\\\$]*\\\$/\\\$Date\\\$/"'
这个 Perl 片段会移除 $Date$ 字符串中看到的任何内容,以恢复到原始状态。现在你的过滤器已经准备就绪,你可以通过为该文件设置 Git 属性来启用新过滤器,并创建一个包含你的 $Date$ 关键字的文件来测试它:
date*.txt filter=dater
$ echo '# $Date$' > date_test.txt
如果你提交了这些更改并再次检出该文件,你将看到关键字被正确地替换了:
$ git add date_test.txt .gitattributes
$ git commit -m "Test date expansion in Git"
$ rm date_test.txt
$ git checkout date_test.txt
$ cat date_test.txt
# $Date: Tue Apr 21 07:26:52 2009 -0700$
你可以看到这项技术对于自定义应用程序来说是多么强大。但你必须小心,因为 .gitattributes 文件会与项目一起提交和分发,而驱动程序(在本例中是 dater)则不会,所以它并非在所有地方都能正常工作。在设计这些过滤器时,它们应该能够优雅地失败,并且项目仍然能够正常工作。
导出你的仓库
Git 属性数据还允许你在导出项目存档时做一些有趣的事情。
export-ignore
你可以告诉 Git 在生成项目存档时不要导出某些文件或目录。如果你有一个子目录或文件,你不想将其包含在存档文件中,但又想将其提交到你的项目中,你可以通过 export-ignore 属性来指定这些文件。
例如,假设你在 test/ 子目录中有一些测试文件,而将它们包含在项目 tarball 导出中没有意义。你可以将以下行添加到你的 Git 属性文件中:
test/ export-ignore
现在,当你运行 git archive 来创建项目 tarball 时,该目录将不会包含在存档中。
export-subst
在导出用于部署的文件时,你可以将 git log 的格式化和关键字扩展处理应用于标记了 export-subst 属性的选定文件部分。
例如,如果你想在项目中包含一个名为 LAST_COMMIT 的文件,并在 git archive 运行时自动将其注入有关最后一次提交的元数据,你可以这样设置你的 .gitattributes 和 LAST_COMMIT 文件:
LAST_COMMIT export-subst
$ echo 'Last commit date: $Format:%cd by %aN$' > LAST_COMMIT
$ git add LAST_COMMIT .gitattributes
$ git commit -am 'adding LAST_COMMIT file for archives'
当你运行 git archive 时,存档文件的内容将如下所示:
$ git archive HEAD | tar xCf ../deployment-testing -
$ cat ../deployment-testing/LAST_COMMIT
Last commit date: Tue Apr 21 08:38:48 2009 -0700 by Scott Chacon
这些替换可以包括提交消息和任何 git notes,并且 git log 可以进行简单的自动换行。
$ echo '$Format:Last commit: %h by %aN at %cd%n%+w(76,6,9)%B$' > LAST_COMMIT
$ git commit -am 'export-subst uses git log'\''s custom formatter
git archive uses git log'\''s `pretty=format:` processor
directly, and strips the surrounding `$Format:` and `$`
markup from the output.
'
$ git archive @ | tar xfO - LAST_COMMIT
Last commit: 312ccc8 by Jim Hill at Fri May 8 09:14:04 2015 -0700
export-subst uses git log's custom formatter
git archive uses git log's `pretty=format:` processor directly, and
strips the surrounding `$Format:` and `$` markup from the output.
生成的存档适用于部署工作,但与任何导出的存档一样,它不适用于进一步的开发工作。
合并策略
你还可以使用 Git 属性来告诉 Git 为项目中的特定文件使用不同的合并策略。一个非常有用的选项是告诉 Git,当特定文件发生冲突时,不要尝试合并,而是使用我们这边的版本覆盖对方的版本。
这在项目的某个分支已分叉或已专门化,但你想能够将更改合并回来,并且你想忽略某些文件时非常有用。假设你有一个名为 database.xml 的数据库配置文件,在两个分支中有所不同,而你想在不搞乱数据库文件的情况下合并另一个分支的更改。你可以设置如下属性:
database.xml merge=ours
然后通过以下方式定义一个虚拟的 ours 合并策略:
$ git config --global merge.ours.driver true
如果你合并了另一个分支,而不是与 database.xml 文件发生合并冲突,你会看到类似这样的结果:
$ git merge topic
Auto-merging database.xml
Merge made by recursive.
在这种情况下,database.xml 将保持你最初拥有的版本。