The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

Visual Studio Tools for Git 處理斷行字元 (CRLF) 的注意事項

今天遇到一個奇怪的問題,我在開發一個使用 Git 版本控管的 ASP.NET MVC 專案時,發現無法在 Visual Studio 2013 中使用「同步處理」功能,該專案明明就沒有異動過,但就跟我說有檔案變更,我怎樣都無法同步,完全鬼打牆。另一方面,在 Visual Studio Tools for Git 裡面的「變更」視窗所看到的檔案變更,經常與使用 Git for Windows 或 TortoiseGit 視窗中看到的檔案不一樣,這也是很煩人的事,因為這樣就很難在同一個專案下交替使用不同的 Git 版控工具,本篇文章將來介紹各種不同使用 Visual Studio Tools for Git 的注意事項。

我們先來看看執行「同步命令」時,到底發生什麼問題。我們開啟「輸出」視窗,看到的是���無法完成作業,因為下列檔案有變更】:

image

不過,從 Visual Studio Tools for Git 裡面的「變更」視窗所看到的,是完全沒有變更的提示:

image

簡言之,都是「斷行字元」(換行字元) 搞的鬼!

各位在看我之前的【Git 在 Windows 平台處理斷行字元 (CRLF) 的注意事項】文章時,會看到一句話:

當執行 git add 命令時,文字檔案中出現的 CRLF 斷行字元會自動被轉換成 LF 字元。而利用 git checkout 取出檔案到工作目錄時,則會自動將 LF 字元,轉換成 CRLF 字元。

另外還有一句話:

因為 .gitAttributes 檔案定義了要對所有文字檔案進行正規化處理,所以儲存在版本庫中檔案內容必須以 LF 結尾,在工作目錄中的文字檔案必須以 CRLF 結尾,這就是「正規化」要做的事。而我們雖然工作目錄中的檔案是 CRLF 結尾,但版本庫中的檔案卻也是以 CRLF 結尾,那就不對了!

如果你今天透過 Visual Studio 2012/2013 將專案加入到 Git 版本控制之中,那麼可能遇到問題的機會不大,但還是有可能會遇到問題,本文稍後會提到。不過我想大部分的情況下,你會從其他地方取得使用 Git 版控的專案,例如 Visual Studio Online 或 GitHub 等等。因此,對所有「文字檔案」進行「正規化」處理的動作,不見得是做的夠到位,導致經常會有這種鬼打牆的情況發生!為了要能徹底解決這個問題,我整理了以下步驟,各位照著步驟檢查 (依照順序),應該能解決 99% 以上的詭異的 Git 版控問題:

1. 確保在專案中加入 .gitattributes 設定檔案

以下我列出 Visual Studio 預設的 .gitattributes 設定檔版本,直接拿去用應該是沒問題的,請把該檔案直接置於 Git 工作目錄的最上層目錄下。

###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto

###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
*.cs diff=csharp

###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
*.sln merge=binary
*.csproj merge=binary
*.vbproj merge=binary
*.vcxproj merge=binary
*.vcproj merge=binary
*.dbproj merge=binary
*.fsproj merge=binary
*.lsproj merge=binary
*.wixproj merge=binary
*.modelproj merge=binary
*.sqlproj merge=binary
*.wwaproj merge=binary

###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
*.jpg binary
*.png binary
*.gif binary

###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

2. 特別在 .gitattributes 設定檔案中加入特定幾個副檔名,並明確表示為「純文字」檔案

我們就真的遇過這樣的問題,在 ASP.NET MVC 專案中有個名為 EmailTemplates.cshtml 檔案,某同事在某次提交版本的過程中,該 CSHTML 檔案竟然被視為「二進位」檔案,以至於被提交到 Git 儲存庫中的斷行字元變成了 CRLF,照理說應該要被自動轉換為 LF 斷行字元才對!然而,我在取出該變更後,卻把該檔案視為「純文字」檔案,所以發生了【Git 在 Windows 平台處理斷行字元 (CRLF) 的注意事項】文章中提到的詭異現象。

這時我意識到,雖然我們在 .gitattributes 設定了 * text=auto,讓 Git 自動判斷檔案的類型,而且大部分的情況下也都判斷正確,但還是有那麼一點機率會誤判。所以,我決定在 .gitattributes 設定檔多新增一條 *.cshtml 的設定,讓所有 *.cshtml 都被視為文字檔案,其設定如下,請加在 * text=auto 的下一行:

*.cshtml text

如果你想定義特定副檔名為「二進位」檔案,那麼你可以設定如下:(我以 *.eot 為例)

*.eot  -text

3. 重置專案目錄下的所有版控中的檔案,重新執行一次「正規化」的動作

由於那些在版控中的檔案,檔案非常多,而且被許多人提交過不同版本,且可能有些版本是從 SVN 遷移過來的檔案,所以可能並不是所有檔案都被正規化過。這時你可以參考以下指令,將專案目錄下的所有版控中的檔案,重新執行一次「正規化」的動作:
注意: 執行以下指令前,請先確保工作目錄是乾淨的,並且開啟命令提示字元到工作目錄的最上層目錄!

del .git\index 
git reset
git status
git add -u .
git add .
git commit -m "Introduce end-of-line normalization"

如此一來,所有版控中的檔案,就會重新執行一次「正規化」的動作!

上述指令,我們先將 .git\index 索引檔刪除,再透過 git reset 指令會將所有檔案會重新取出,然後對於那些重新被正規化的檔案會被標示為被修改 ( git status ),這時再將這些檔案都加入版控並提交新版,就大功告成了!

請注意:你一定要先設定好 .gitattributes 再執行這批指令!

4. 清除 Visual Studio Tools for Git 建立的工作目錄狀態快取

Visual Studio Tools for Git 比 Git for Windows 工具多了一個功能,那就是「排除的變更」,有別於 .gitignore 檔案,這些明確列在這裡的檔案僅適用於 Visual Studio 開發環境,所以請不要誤以為這跟 .gitignore 檔案有任何關係。你在 Visual Studio 中透過 Visual Studio Tools for Git 操作的版控狀態,事實上都被寫入到 .git\ms-persist.xml 檔案中,要清除詭異的檔案狀態,建議可以將此檔案刪除,許多在 Visual Studio 裡面操作 Git 版控時遇到的鬼打牆事件都會煙消雲散。( 此檔案會自動重建,所以各位可以放心刪除 )

5. 通知所有團隊成員重新取出專案

如果你發現真的有許多檔案沒有正規化,請通知所有團隊成員,重新取出一次該專案,確保工作目錄都是乾淨的,以免日後再次發生詭異的 Git 版控事件。

6. 建議同時設定 AutoCRLF 與 SafeCRLF 這兩個設定選項

git config --global core.autocrlf true
git config --global core.safecrlf true

7. 確保團隊成員都使用相同版本的 Git 工具,並隨時更新到最新版

相關連結