﻿# Git 學習筆記 (1)：安裝、選項設定、在本地使用 Git 工具

之前常想，Git 這麼複雜的東西，怎麼國外這麼多人在用啊？今天徹底研究了一番之後發現，Git 真的比 SVN 強大太多，只是分散式版控機制確實跟自己以往熟悉的 SVN 或 TFS 差好多，用舊有的思維模式 (Mindset) 學習新技術還真的有點辛苦，今天算是重新打造全新的思維模式，收穫還不少。這篇文章是我 Git 學習筆記的第一篇，寫了十幾個小時，洋洋灑灑的印出來應該也有幾十頁，還覺得很多東西沒講清楚，歡迎各位留言大家一起學習囉！ (^\_^)

**§ Git 工具安裝 ( Windows ) §**

要安裝 Git for Windows 可以先到以下網站下載並安裝：

-   [Git for Windows](http://git-scm.com/) \[官網\]
-   [Git for Windows 直接下載](http://git-scm.com/download/win "http://git-scm.com/download/win") (點擊後可直接下載最新版)

以下是 msysGit 在 GitHub 上面的專案網址：

-   [The msysGit repository](https://github.com/msysgit/msysgit)

安裝好之後，所有文件會有一份離線版，資料夾路徑如下：

-   C:\\Program Files (x86)\\Git\\doc\\git\\html

安裝 [Git for Windows](http://git-scm.com/download/win "http://git-scm.com/download/win") 時，安裝到 **Adjusting your PATH environment** 步驟時，請選擇 **Run Git from the Windows Command Prompt** 這個選項，相容性比較高，問題也會少很多：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/a61c949b774b0eba6336-image_2238.png)

或者，你也可以直接安裝 [GitHub for Windows](http://windows.github.com/) 工具，他有內建一個以 Windows PowerShell 為主的 Git Shell 執行環境，已內建 Git for Windows 工具。另外，微軟也有推出 [Visual Studio Tools for Git](http://visualstudiogallery.msdn.microsoft.com/abafc7d6-dcaa-40f4-8a5e-d6724bdb980c) 擴充套件，不過安裝這套的基本要求是要要事先安裝 [Visual Studio 2012](http://go.microsoft.com/fwlink/p/?LinkId=254509) 與 [Visual Studio 2012 Update 3](https://visualstudio.microsoft.com/vs/older-downloads/) 才行。我則是三套都裝了：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/29f4a89ec96a463c5b99-image_2252.png)

本篇文章會以 [Git for Windows](http://git-scm.com/download/win "http://git-scm.com/download/win") 指令列工具為主。

**§ Git 工具的選項設定 §**

**設定 Git 執行環境的三個層級** ( **註**: 執行 **git config --help** 可以取得完整指令說明 )

-   **系統層級** (System-level configuration) (設定於整台電腦，所有使用者的預設值)

-   git config \--system --list
-   預設路徑: **C:\\Program Files (x86)\\Git\\etc\\gitconfig**
-   或是路徑: **%LOCALAPPDATA%\\VirtualStore\\Program Files (x86)\\Git\\etc\\gitconfig  
    補充參考**: [VirtualStore - Inside Windows Vista User Account Control](http://technet.microsoft.com/zh-tw/magazine/2007.06.uac\(en-us\).aspx)

-   **使用者層級** (User-level configuration) (設定於目前登入的使用者)

-   git config \--global --list
-   預設路徑: C:\\Users\\<使用者帳號>\\.gitconfig      (這是個檔案)
-   常用指令

-   最常見的設定就是 user 區段下的 name 與 email 參數，第一次用 Git 一定要設定!

-   git config --global user.name "John Doe"
-   git config --global user.email "johndoe@example.com"

-   在 Linux 底下可以指定編輯器

-   git config --global core.editor vim

-   在Windows底下，最好打開 core.autocrlf 選項，讓 commit 的檔案沒有 CR 字元  
    ( 純 Windows 開發環境，可以設定為 false；跨平台協同開發，建議設定為 true )

-   git config --global core.autocrlf true

-   自動訂正打錯的參數，例如 git statsu 會自動修正為 git status 讓你不用重打一次

-   git config --global help.autocorrect 1

-   **儲存區層級** (Repository-level configuration)

-   git config --list
-   預設路徑: <Git儲存區>\\.git\\config

**Git 執行環境中，三個不同設定層級的套用順序**

-   **儲存區層級** (優先權最高) \> **使用者層級** \> **系統層級** (優先權最低)
-   使用 **git config --list** 取出參數時，會顯示所有層級的參數，但越下方的參數優先權越高。  
    如下範例，**user.email** 出現過兩次，下方的 user.email 將會被套用 (因為它定義在**儲存區層級**)
    
    ```
    G:\GitRepo>git config --list
    core.symlinks=false
    core.autocrlf=true
    color.diff=auto
    color.status=auto
    color.branch=auto
    color.interactive=true
    pack.packsizelimit=2g
    help.format=html
    http.sslcainfo=/bin/curl-ca-bundle.crt
    sendemail.smtpserver=/bin/msmtp.exe
    diff.astextplain.textconv=astextplain
    rebase.autosquash=true
    user.name=John Doe
    user.email=johndoe@example.com
    help.autocorrect=1
    core.autocrlf=true
    core.filemode=false
    core.bare=false
    core.logallrefupdates=true
    core.symlinks=false
    core.ignorecase=true
    core.hidedotfiles=dotGitOnly
    user.email=
    ```
    
-   取得特定選項的值 (這就是最終套用各層級參數的結果)

-   git config user.name
-   git config user.email

-   刪除特定選項的值 (會從設定檔中移除該設定)

-   git config \--unset user.email            (刪除**儲存區層級**的設定值)
-   git config \--global \--unset user.email   (刪除**使用者層級**的設定值)

-   除了透過指令設定外，其實你也可以直接透過 Notepad 編輯設定檔

-   **系統層級**預設路徑: C:\\Program Files (x86)\\Git\\etc\\gitconfig
-   **使用者層級**預設路徑: C:\\Users\\*<使用者帳號>*\\.gitconfig
-   **儲存區層級**預設路徑: *<Git儲存區>*\\.git\\config   

**§ 在本地 (本機) 使用 Git 版本控管工具 §**

**取得 Git 指令列工具版本**

-   git --version

**建立本地儲存區 (local repository)** ( **註**: 從指令來看，比較像是**初始化 Git 目錄**的意思 )

-   git init  
    建立好的所有儲存區檔案 (repository files) 都會儲存在目前目錄的 .git 目錄下。

**取得目前 Git 工作目錄的狀態**

-   git status  
    用這個指令可以取得當前目錄的版控狀態，例如有檔案被變更、刪除、新增或其他。

**新增檔案** ( 將尚未被 Git 追蹤的新增檔案加進去 )

-   git add .  
    git add *<filename.ext>*  
    上述指令最後若加上一個小數點 ( **.** ) 代表目前目錄，它會自動把所有**尚未版控的檔案** (**Untracked files**) 加入到 Git 的追蹤清單中，也代表這些檔案才會經由 Git 進行版本控管。
-   如果你新增了一個檔案 **a.txt**，此時你用 git status 會看到以下畫面：  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/649359e9ee57584e6004-image_2253.png)
-   如果執行了 git add . 之後，就會變成以下畫面：  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/53193ddad1a9556a694d-image_2254.png)

**提交變更** ( 將 Git 狀態中所有異動的部分送進 **儲存區** ) ( Note: **儲存區** = **Repository** )

-   git commit  
    git commit -m "*<要留下的版本紀錄訊息>*"  
    提交變更一定要填寫紀錄，所以會自動開啟 Notepad ( 或 vim ) 讓你輸入紀錄，輸入完儲存並關閉 Notepad，就會自動提交變更完成。

**查看歷史紀錄**

-   git log  
    git log --stat  
    git log --pretty=oneline 查詢紀錄時，你就會發現有幾個重要的欄位，分別是：  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/db31b04a46f7da95f486-image_2255.png)

-   **commit** 後面接著一串亂碼 ( SHA1 Hash ) 代表這個版本的唯一記號
-   **Author** 代表提交這次變更的作者，這裡的資料是由提交的人當下設定的 user.name 與 user.email 參數，所以如果有人有改名過，從這裡可能不好看出到底是誰改的，或覺得紀錄有點亂，這可能一開始大家要先講好比較不會亂。
-   **Date** 代表這個版本被提交的日期
-   **最後**，就是當時留下的紀錄訊息。

**更新檔案** (不包括**刪除檔案**喔)

-   **git add -u**  
    如果你在 Git 的**工作副本目錄**下有更新檔案，其實 Git 並不知道你改了，所以如果你直接執行 git commit 指令企圖提交變更的話，會立刻看到以下訊息：  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/5fce0295bf9f08787816-image_2256.png)  
    這裡你會看到 “**Changes not staged for commit**” 這句話，當中的 “**not staged**” 這個字照字面翻譯是「**不在舞台上**」的意思。
-   舉個例子來說：假設**你**是一個**舞團指揮**，目前正在**編舞** (程式開發中)，排練時有個**舞台**可以練習，而這個**舞台**就是你的 **Git 工作區** (目前這個目錄)，該舞團有個工具可以控制舞台上所有事情 (就是你的 Git 工具)，而舞台上所有 **舞者** (目錄下的所有檔案) 都會聽指揮的命令。今天，舞台上有一個 **舞者** (a.txt) 自己發明了一個**新舞步** (檔案被更新)，但 **指揮** 並不知道，當 **指揮** 詢問每位舞者的狀態 ( **git status** ) 時，發現了這個舞者 (a.txt) 發明了新舞步，他必須決定是否要把舞步編進**舞冊**上 ( **git commit** )。此時，假設**指揮**同意了這個舞步變更，他就必須把這個檔案加到編舞裡去，你就要輸入 **git add -u** 命令讓他上台試跳，如果跳的不錯，這時輸入 **git commit** 才會真正把舞步編進**舞冊**上。
-   **註 1**：你直接輸入 **git add .** 而不加上 **\-u** 參數，也可以把這些變更過的檔案加入追蹤。不加上 -u 則代表「新增」與「變更」都會一併提交追蹤。
-   **註 2**：使用 **git add** 的時候，最後一個參數可以放小數點 ( **.** )，也可以放檔名或特定目錄下的檔名，甚至於輸入 **萬用字元** ( **\*** ) 也可以。

**刪除檔案**

-   **git rm '*<filename>*'**  
    直接標記刪除檔案
-   **git add –u *<path\\filename.ext>*****  
    git add -u**  
    先透過檔案總管把檔案刪除，然後再透過 **git add** 標註刪除，不過在下指令的時候，一定要加上 **\-u** 參數，才能把要刪除的檔案標示為 staged (代表可被 commit 的意思)。

**重置檔案狀態 (不會變更檔案內容)**

-   **git reset HEAD \[*<path\\filename>*\]  
    git reset HEAD**  
    如果你發現這這個**變更過的編舞**實在太醜，不想將他提交變更，則可以利用 **git reset HEAD** 指令將這些變更都退回 “未追蹤” (Untracked) 狀態，也代表未來執行 **git commit** 時不會提交這些變更。

**請注意**：這裡的 **HEAD** 代表的是**目前版本庫中的最新版**，而 reset HEAD 的意思，就是將**當前目錄**或**特定檔案**重置為 **HEAD** 版本的狀態，所以 **HEAD** 版本之後的所做的變更，都會變成 **not staged** 或 **untracked** 狀態。 ( 重置的動作，並不會修改任何檔案的內容 )

**復原檔案變更 (Undo Changes)**

-   **git checkout -- .**  
    **git checkout -- <filename.ext>**  
    當修改了工作目錄中版控中的檔案，如果檔案還處於 “**not staged**” 狀態，就可以透過這個指令，重新**取出** (Checkout) 檔案，然後讓上一個版本的內容蓋掉這個檔案的內容。不過，如果該檔案已經被標示為 “staged” 的話 ( 也就是已經透過 **git add .** 指令標示成 staged 的話 )，執行這個指令就不會蓋掉這些檔案，你必須先執行 **git reset HEAD** 才能將檔案回復為 “**not staged**” 狀態。

**請注意**：這裡的 **\--** 等同於 **HEAD** 的意思，是 **HEAD** 的簡寫。

**清除未追蹤的檔案 ( Clean untracked files )**

-   **git clean  
    git clean –n**   (僅列出要清空的檔案清單)  
    **git clean -f**   (執行刪除動作，把那些要清空的檔案逐一刪除)  
    自動清除沒那些不在追蹤行列的檔案有時候很方便，例如我們用 Visual Studio 經常會產生很多暫存檔，用了 git 之後，直接執行 **git clean -f** 就可以把那些多餘的檔案清空。  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/5f3cedf3767e53c7ced7-image_2257.png)

**同時有檔案新增與檔案更新的狀態怎樣控制**

-   假設我們先異動過 a.txt 檔案，然後新增一個 b.txt 檔案，此時輸入 **git status** 指令結果如下：  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/f9a1274a8503a15e27f6-image_2258.png)  
    此時你會發現，a.txt 屬於 “**not staged**” 的狀態，而 b.txt 屬於 “**Untracked**” 的狀態。這代表 a.txt 本來就是在控管的行列內，只是因為他變了，不會被編進舞蹈 (not staged) 之中而已；而 b.txt 根本不是舞團的成員，可能只是某個小屁孩在舞台上亂跳而無法控管 (Untracked)，他跳的所有舞蹈也不會編進正式舞蹈之中。
-   如果你想一次把所有新增檔案與更新檔案加入編舞，可以輸入以下指令：  
    **git add .  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/5f7fa12a421a30493aae-image_2259.png)**
-   如果你只想把**舞團成員**的編務納入編舞的話，則可以輸入以下指令：  
    **git add -u  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/954feafa2fdf367d3b12-image_2260.png)**
-   如果你只想把未控管 (Untracked) 的檔案加入編舞，則必須指定檔名來加入，如下指令：  
    **git add *b.txt  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/12a65970f52d036f33f9-image_2261.png)***
-   要取消所有變更狀態，也就是全部都不納入編舞，可以輸入以下指令：  
    **git reset HEAD**  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/1d773700902cbff684ff-image_2262.png)
-   重新全部加入，然後將 b.txt 這個新增檔案重置成未追蹤 (Untracked) 的狀態  
    **git reset HEAD *b.txt***  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/a82c66ba412e83d9398b-image_2263.png)
-   重新全部加入，然後將所有 **a.\*** (使用萬用字元) 取消納入編舞  
    **git reset HEAD a.\***  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/57bd3181313df5ae4410-image_2264.png)
-   最後我們取消 a.txt 變更過的內容，回復到原本版本庫中的內容 (上一個版本)，可輸入以下指令：  
    **git checkout -- a.txt   
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/a6e325489d15a9122c35-image_2265.png)** 

**摘要複習一下上述學到的**

-   所謂 **staged** 代表要把檔案可以被 commit 的意思 (可以納入舞冊)
-   所謂 **not staged** 代表檔案有變更，但不會被 commit 進版本庫 (有變更舞步但不被採納)
-   所謂 **untracked** 代表是該檔案尚未被追蹤，也不會被 commit 進版本庫 (在舞台上亂跳的小屁孩)
-   把所有檔案標示可以被提交，使用 **git add .** 指令 (允許納入舞冊，但還沒真的納入)
-   把所有檔案新增或更新的標示取消，使用 **git reset HEAD** 指令
-   任意時刻查詢工作目錄的狀態，使用 **git status** 指令
-   僅把更新的檔案標示為可提交，使用 **git add -u .** 指令 (只把舞團成員的變更納入)
-   將所有標示為可提交的檔案送入版本庫，使用 **git commit** 指令
-   查看歷史紀錄使用 **git log** 指令
-   要把 **HEAD** 版本之後所做的變更全部還原 (Undo All)，可以輸入以下指令：  
    **git reset HEAD**  
    **git checkout --  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/2704dde313f3bd7348d5-image_2266.png)**

**查看不同版本之間的變更比較**

-   比較 **版控中的最新版** ( **HEAD** ) 與 **目前工作目錄檔案** 的版本差異  
    **git diff HEAD  
    git diff --staged**
-   比較 **版控中的倒數第二版** ( **HEAD~1** 或 **HEAD^** ) 到 **版控中的最新版** ( **HEAD** ) 的版本差異  
    **git diff HEAD~1..HEAD  
    **git diff HEAD^..HEAD**  
    ****注意**: HEAD 代表最新版，而 HEAD~1 即代表「最新版再往前一版」的意思。
-   若要明確比對兩個版本，則需先透過 **git log** 取得 commit id (用 SHA1 Hash 的那串亂碼)，然後再透過 **git diff <*OldCommitID*\>..<*NewCommitID*\>** 進行比對。從下圖你可以看到，當我們用 **git log** 取得版本歷史紀錄時，每個版本都會有個很長的 commit id，如果手動輸入這些 ID 真的會要人命，所以 git 再讓你輸入時，可以最少輸入 commit id 最前面四個字元，就能代表這個版本，使用時的範例如下：  
    ( **注意**: 輸入版本號時，記得舊版本要先輸入，打上兩個小數點，再打新的版本號 )  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/4fbd8a463d00c4486496-image_2267.png)

**變更檔名**

-   變更檔名這件事比較奇怪一點，如下圖示，當你把原本版控中的檔案 a.txt 更名為 a.log 之後，你會發現使用 git status 會看到兩個變更，一個被刪除的檔案、一個被新增的檔案：  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/d51d29d90a17fefd5bc0-image_2268.png)  
    然而，如果你這時執行 **git add .** 是不行的，他會出現以下警告訊息  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/41d0cdabd38625e3bccd-image_2269.png)  
    要解決「檔案重新命名」的問題，有兩種方式可以解決。  
    **1\.** 因為一個是「刪除檔案」，另一個是「新增檔案」，分別要下的指令是以下兩個：  
    **git add .**  
    **git add -u**  
    所以上述兩個指令都輸入一次，重新命名就會被正確標註：  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/131fc7b37349a59ae4f7-image_2270.png)  
    **2.** 另外 **git add** 還有一個 **\-A** 參數可用，只要下一個指令，就可以將所有檔案標示為 staged  
    **git add –A *<path\\filename.ext>*****  
    git add -A**  
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/c11abea9ec94c79ab883-image_2271.png)

**請注意**：在變更檔名時，只有當**檔案內容**沒有變更的情況下，才會被 Git 視為是 rename 標示！

**重新提交版本**

如果你在執行了 **git commit** 指令提交變更之後才發現漏了一個檔案，或是 Log 不小心打錯字了，你可以透過一個 **\--amend** 參數幫助你重新提交一次版本，你可以在這次提交變更的時候，新增檔案、刪除檔案、變更檔案、檔案更名，甚至於修正原本寫好的 Log 內容都可以。其指令輸入範例如下：

```
git commit --amend
```

以下為實際範例：

1\. 我們先用 **git log** 顯示版本歷史紀錄

    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/4841f12e1361fec9fe49-image_2272.png)

2\. 然後新增一個檔案後，再利用 **\--amend** 重新提交一次變更 (順便修改成不一樣的 Log 訊息)

```
echo. > amend_test.txtgit add .git commit --amend -m "Rename a.txt -> a.log!"
```

3\. 再看一次 **git log** 顯示版本歷史紀錄，你可以看到**版本**並沒有變多，不過 commit id 已經不一樣了！

    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/034af5a8f3c56832ce93-image_2273.png)

**重置版本 / 復原版本**

推薦閱讀：

-   [Reset Demystified](http://git-scm.com/blog)
-   [Can you explain what “git reset” does in plain english?](http://stackoverflow.com/questions/2530060/can-you-explain-what-git-reset-does-in-plain-english)

**[git reset](http://git-scm.com/docs/git-reset)** 有個用法如下：

```
git reset [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>]
```

由於原文的說明好難懂，我看了好久還是似懂非懂，我已經盡量寫清楚了，如果有錯誤的地方，歡迎不吝告知，感恩！

以下是官網對 **[git reset](http://git-scm.com/docs/git-reset)** 的摘錄：

> *git reset* \[<mode>\] \[<commit>\]
> 
> This form resets the current branch head to <commit> and possibly updates the index (resetting it to the tree of <commit>) and the working tree depending on <mode>. If <mode> is omitted, defaults to "--mixed". The <mode> must be one of the following:
> 
> \--soft
> 
> **Does not touch** the index file **nor** the working tree at all (but resets the head to <commit>, just like all modes do). This leaves all your changed files "Changes to be committed", as *git status* would put it.
> 
> \--mixed
> 
> **Resets** the index but not the working tree (i.e., the changed files are preserved but not marked for commit) and reports what has not been updated. This is the default action.
> 
> \--hard
> 
> **Resets** the index **and** working tree. Any changes to tracked files in the working tree since <commit> are discarded.
> 
> \--merge
> 
> **Resets** the index **and update the files** in the working tree that are different between <commit> and HEAD, but **keeps** those which are different between the index and working tree (i.e. which have changes which have not been added). If a file that is different between <commit> and the index has **unstaged** changes, **reset is aborted**.
> 
> In other words, --merge does something like a *git read-tree -u -m <commit>*, but carries forward unmerged index entries.
> 
> \--keep
> 
> **Resets** index entries **and updates files** in the working tree that are different between <commit> and HEAD. If a file that is different between <commit> and HEAD has local changes, **reset is aborted**.

我們先不討論複雜的版控的分支架構，如下圖，我們總共 commit 了三次，各別的 commit id 為 C0, C1, C2 與 C3，而目前箭頭指向的地方則是代表 HEAD，所以 HEAD 等於 C3：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/69d1df3ba15e58c81962-image_2274.png)

假設我們 HEAD 等於 C3 的情況，結果發現 C3 這個版本不是很乾淨，例如漏 commit 了一個檔案，或 Log 寫錯了要改，在本文稍早有個「**重新提交版本**」部分，有提到一個 **\--amend** 參數可以幫你重新 commit 一次，並且覆蓋掉最新的這一版。除了重新提交版本外，我們也可以利用 **git reset** 指令， 將 HEAD 指標移向 C2 版本，移動完成後的結果大概如下圖解：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/6aa9d0669919915f94a2-image_2275.png)

其重點在於 **git reset** **一定會修正 HEAD 指向的版本**，但主要是有兩個東西**可能**會被改變，分別是：

-   重置 **index** 檔案 ( **.git/index** )，此檔案紀錄了你在工作目錄下的各種檔案與版本資訊
-   重置 **working tree** 檔案，這裡的 working tree 就是指你的**工作目錄**下的所有相關檔案

你可以輸入以下 5 種的其中一種模式進行重置，用以決定是否更新 **index** 或 **working tree** 檔案：

**第一種：使用 \--soft 參數**

```
git reset --soft HEAD~1
```

**註**: HEAD~1 代表 HEAD 往前一版的意思。以上圖為例，當 HEAD 等於 C3 時，當下的 HEAD~1 就等於 C2

使用 \--soft 參數，執行完後會修正 HEAD 指向的版本 (這五種模式都會修正 HEAD 指向的版本)，而且不會重置 index 也不會重置 working tree。這句話的意思是指，執行完後：

-   不會重置 **index** 檔案 ( **.git/index** )，代表你在**工作目錄**下所做的任何檔案標記 (標記新增、刪除、更新、更名、…)，所有標記要 commit 的檔案狀態，都不會異動。
-   不會重置 **working tree** 檔案，代表工作目錄下的檔案，所有檔案內容都不會被異動，雖然版本變成上一版 (或任意版本)，但本次修正的檔案完全不會被蓋掉。

**注意**: **檔案內容被重置**與 **index 狀態被重置**，是兩件事。

**第二種：使用 \--mixed 參數**

本文稍早寫過，若要把 HEAD 版本之後所做的變更全部還原 (Undo All)，可以輸入以下指令：

```
git reset HEAD git checkout --
```

事實上，這段程式等同於以下：

```
git reset --mixed HEADgit checkout --
```

也等於這樣，git reset 預設的模式就是 \--mixed 與預設 commit 也是 HEAD 等等，都可以自動省略

```
git resetgit checkout --
```

使用 –mixed 參數，會重置 index 但不會重置 working tree。這句話的意思是指，執行完後：

-   會重置 **index** 檔案 ( **.git/index** )，代表你在**工作目錄**下所做的任何檔案標記 (標記新增、刪除、更新、更名、…) 通通都會被重置，也就是若有檔案被標註新增，執行完後檔案會變成 untracked 狀態。
-   不會重置 **working tree** 檔案，代表工作目錄下的檔案，所有檔案內容都不會被異動，雖然版本變成上一版 (或任意版本)，但本次修正的檔案完全不會被蓋掉。我們以上圖 C3 重置成 C2 為例，C3 之後所做的變更，當執行了 **git reset --soft C2** 之後，代表原本 C3 的變更與 C3 到目前所作的變更，其變更的內容都在，指令執行完後，原本工作目錄下的檔案內容都是原封不動的，所以當你執行 git status 的時候，就會發現從 C2 到現有的這個目錄，所有的變更都屬於 unstaged 的狀態。

**第三種：使用 \--hard 參數**

```
git reset --hard HEAD~1
```

使用 \--hard 參數，會重置 index 也會重置 working tree。這句話的意思是指，執行完後：

-   會重置 **index** 檔案 ( **.git/index** )，代表你在**工作目錄**下所做的任何檔案標記 (標記新增、刪除、更新、更名、…) 通通都會被重置，也就是若有檔案被標註新增，執行完後檔案會變成 untracked 狀態。
-   會重置 **working tree** 檔案，代表工作目錄下的檔案，只要是 tracked 的檔案，全部都會被蓋掉 (或透過 git add 新增進去但尚未 commit 的新增檔案也會被刪除)。**注意**：那些 unstaged 的檔案，其實都是 tracked 的檔案，因為 unstaged 的檔案已經是被追蹤的檔案了，只是被修改或刪除，還沒被標記可被 commit 而已，這些檔案在透過 **git reset --hard** 之後，所有變更都會消失。

**注意**: 透過 **git reset --hard** 之後，現有尚未 commit 的檔案變更都會消失，而且救不回來喔！所以通常在執行危險的命令前，常有人會把目前尚未 commit 的檔案先 commit 成一個新版本，然後建立好一個新的分支 (Branch)，然後再將現有的 master 分支 reset 成舊版，然後再繼續改。

範例如下，我們先把尚未簽入的檔案都給加入並 commit 提交變更，在把提交後的結果透過分支來做一個類似備份的動作： ( **註**: 之後的文章會介紹到 [git stash](http://git-scm.com/docs/git-stash) 的用法，他可以把不完整的檔案暫存起來 )

```
git add -Agit commit -m "Backup for this time"git branch Backup01git reset --hard HEAD~1
```

改完之後，如果想把備份的資料合併回來，可以用以下指令：   
**注意**: 合併後會建立一個新版本，你原本尚未 commit 的版本還要再 commit 一次。

```
git merge Backup01
```

如果確認沒問題，可以用以下指令把剛建立的 Backup01 分支給刪除：

```
git branch Backup01 -D
```

**第四種：使用 \--merge 參數**

```
git reset --merge ORIG_HEAD
```

使用 \--merge 參數，有點類似 \--hard 參數，會重置 index 也會重置 working tree，但有些條件，只有當指定的 <commit> 版本與目前 HEAD 當中檔案不一致的時候，才會重置目前 working tree 下面的檔案內容為 <commit> 這個版本下的內容，而其他的本地檔案變更則會被保留。

這參數通常用在使用 **git pull** 從遠端合併變更進來，不過你可能在此時，已經有些檔案已經變更過，再加上 **git pull** 從遠端合併過的結果後，你的**工作目錄**可能會髒掉 (例如有些檔案合併後不能用)，這時如果你用 **git reset --hard ORIG\_HEAD** 雖然可以回復到合併前的上一版，但你所有的本地變更都會消失。此時你如果用 **git reset --merge ORIG\_HEAD** 的話，就會僅針對「合併所變更的那些檔案」進行重置，就不會影響到你其他已經變更的檔案。以下是使用範例：

```
$ git pull                         <1>Auto-merging nitfolMerge made by recursive. nitfol                |   20 +++++---- ...$ git reset --merge ORIG_HEAD      <2>
```

**第五種：使用 --keep 參數**

```
git reset --keep <commit>
```

使用 --keep 參數，有點類似 \--merge 參數，會重置 index 也會重置 working tree，更新的條件也跟 \--merge 一樣，只有當指定的 <commit> 版本與目前 HEAD 當中檔案不一致的時候，才會重置目前 working tree 下面的檔案內容為 <commit> 這個版本下的內容。但跟 \--merge 參數不一樣的地方在於，當你指定的 **<commit> 版本中的變更檔案**與你**本地變更的檔案**有重疊到時，因為他會強制保留 <commit> 這邊的版本，所以本次的 git reset 命令會被中斷，不會被執行。這樣可以比較安全的重置到特定版本。

**§ **設定 .gitignore 忽略清單** §**

在開發專案時，工作目錄下可能經常會有新的檔案產生 (可能是透過 Visual Studio 工具產生的那些暫存檔案或快取檔案)，可能有許多檔案並不需要列入版本控管，所以必須要排除這些檔案，我們稱為「忽略清單」檔案。

在 Git 裡面，是透過 .gitignore 檔案來進行定義，主要的目的是排除一些不需要加入版控的檔案，列在裡面的檔名或路徑 (可包含萬用字元)，都不會出現在 git status 的結果中，但僅限於 untracked 檔案而已，那些已經列入版控的檔案，不受 .gitignore 檔案控制。

.gitignore 檔案內容範例如下：

```
# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
[Bb]in/
[Oo]bj/

# mstest test results
TestResults
[Tt]est[Rr]esult*

## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.

# User-specific files
*.suo
*.user
*.sln.docstates

# Build results
[Dd]ebug/
[Rr]elease/
x64/

# Others
sql
*.Cache
ClientBin
[Ss]tyle[Cc]op.*
~$*
*.dbmdl
Generated_Code #added for RIA/Silverlight projects

# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
```

詳細說明文件如下：

-   [gitignore(5) Manual Page](http://git-scm.com/docs/gitignore)

也可參考我之前寫給 SVN 的文章：

-   [請勿將某些檔案類型的檔案簽入到 Subversion 版本庫 (二版)](/post/2012/03/30/Do-not-commit-these-file-type-into-subversion-repository-2.aspx)

**§ 復原 Git 操作過程中的所有變更 §**

在 Git 裡，所有指令操作都會被留下紀錄，也代表，無論你怎麼用，只要是 commit 過的版本，預設就一定會留在版本庫裡，即便你做了一些非常危險的指令操作也都一樣，都是能夠復原的！

**額外提醒**：有一種情況不能復原檔案內容，就是你在 commit 之前就把版本給還原，那就救不回來囉！

-   **git reflog  
    git reflog show master  
    **取得 Git 管理日誌，你可以透過這份日誌，找到可以讓你還原的版本。透過 **git reset** 指令。

1\. 我先透過 **git reset** 指令刪除最後一個版本：

```
git reset --hard HEAD~1
```

  ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/b7f5e62597c64e70a03a-image_2276.png)

2\. 然後透過 **git reflog** 取得管理日誌，並得知：

  ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/581e10a5a923490a3e58-image_2277.png)

3\. 最後得知你想要還原回去的版本，再透過 **git reset** 指令回復到日誌的上一個版本

```
git reset HEAD@{1}
```

  ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/75f4194b100437b66a57-image_2278.png)

當你知道如何復原變更之後，在使用 Git 的同時，相對的也會安心許多，因為你至少知道所有的 commit 都會保存下來，沒有資料會不見。

**總結**

這是我今天的學習紀錄，學習過程順便筆記與反覆驗證的結果，內容有點雜亂，但照著做應該能做出來，使用 Git 重點還是在觀念，否則一直鬼打牆真的很累，而且也會很灰心的。

研究後發現，Git 博大精深，東西太多了，今天寫了一整天，卻還沒講到分支與合併的細節，以及如何跟遠端的 Git 專案協同作業，下次繼續，非要戰勝 Git 不可！

**相關連結**

-   [Git Documentation](https://git-scm.com/docs)
-   [Git – Reference](http://git-scm.com/docs)
-   [Git Cheatsheet](http://ndpsoftware.com/git-cheatsheet.html) (線上互動版 - 還不錯)
-   Git Cheat Sheet (PDF)
-   [Pro Git](http://git-scm.com/book/)  \[ 免費電子書 \] ( [**正體中文版**](http://git-scm.com/book/zh-tw) )
-   [gitglossary(7) Manual Page](https://www.kernel.org/pub/software/scm/git/docs/gitglossary.html)  (原文有好多單字看不懂，來這邊查就對了)
-   [what does the git index EXACTLY contain?](http://stackoverflow.com/questions/4084921/what-does-the-git-index-exactly-contain)
-   [HEAD and ORIG\_HEAD in Git](http://stackoverflow.com/questions/964876/head-and-orig-head-in-git/967611#967611)
-   [Code School - Try Git](http://try.github.io/ "http://try.github.io/")
-   [Learn Git Branching](http://pcottle.github.io/learnGitBranching/)
