-
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 將保持你最初擁有的版本。