git 實作練習 2

@ver 0.1.2 @date 2019-02-28 04:16:24 (星期四)

@ver 0.1.1 @date 2019-02-21 19:49:17 (星期四)

這一章要先介紹 git 裡很重要的設定檔, 檔名 .gitignore, 隨著開發時間流轉, 專案會越來越龐大, 塞滿了各式各樣令人驚奇的程式或文字或圖片. 重要的可能是跟加密或密碼有關的檔案, 例如 .key, .pem 密碼檔或 .crt 證書或儲存資料庫密碼的設定檔. 有些是開發過程中必然產生的中間檔, 例如編輯器自動備份的 .bak 或是 C/C++ Compiler 產生的 .o obj 檔, 會跟著程式更新而異動. 有些是換了又換的圖片, 或是曾經在建立資料時要用到卻又曇花一現的 .xml 或 .json, .csv 等內容. 又有些是暫時備而不用的 .log 記錄檔.

以上種種各式各樣的檔案, 必須留在專案工作目錄裡, 以備不時之需, 但又不想時時刻刻記錄到 git 倉庫, 此時就要善用 .gitignore 的專長.

接著會教學如何使用 git log, git diff 工具來抓住各個版本之間的變化; 以及使用 git reset 施行還原大法, 把過去某一段美好時間點的程式碼, 重新呈現出來.

  1. .gitignore 忽略文件清單

.gitignore 指定 git 刻意要忽略索引追踪的文件. 先前 git 已經索引追踪的文件不受影響, 如果也要把它從暫存區(索引)刪除, 可用 git rm --cached 檔名

一般忽略文件的原則如下 :

作業系統自動產生的文件, 目錄資訊, 縮略圖 ... 等. plaintext # windows 系統 Thumbs.db ehthumbs.db Desktop.ini # macOS 系統 .DS_Store 編譯產生的中間檔、可執行檔案, 自動產生的備份, 記錄 ... 等 plaintext *.o *.a *.class *.exe *.bak *.log 跟加密或密碼儲存有關的檔案 ```plaintext .env .key .pem .crt * 其他任何不想 git 的目錄或檔案 plaintext my_key/ my_birthday_data.txt `` * 若為空的子目錄, 此目錄會被忽略, 除非至少 touch 一個檔案在目錄之下, 例如touch /res1/.gitkeep`

  1. .gitignore 忽略文件清單的語法規則

> 什麼是 glob pattern > > 所謂的 glob pattern 是指 shell 所使用的簡化版本的正則表達式. > > 檔名及目錄可以使用標準的 glob pattern (以下規則簡稱為模式) 匹配 > > 問號 ? 只匹配一個任意字符 > > 星號 * 匹配零個或多個任意字符. > > [abc] 匹配任何一個列在方括號中的字符 ( 這個例子匹配一個 a, 或匹配一個 b, 或匹配一個 c ); 如果在方括號中使用 - 短划線 分隔兩個字符, 表示所有在這兩個字符範圍內的都可以匹配 [0-9] 表示匹配所有 0 到 9 的數字. > > 使用雙星號 \*\* 表示匹配任意中間目錄, 比如 a/**/b 可以匹配 a/ba/x/b 或 a/x/y/b 等.

### 忽略文件語法規則簡化版本 :

空行或 # 開頭表示註解 plaintext # Windows 作業系統相關 # 自動產生 # 重要個人檔案, 不能備份 指定模式匹配檔案或目錄 plaintext my_key/ my_*day_data.txt 在前面添加正斜線 / 來匹配頂層目錄, 下面的例子表示 /datas 裡的所有子目錄及檔案全部忽略, 但如果是其他子目錄裡有相同名稱, 例如 public/datas/ 則不會被忽略 plaintext /datas 在後面添加正斜線 / 來匹配各層級目錄, 例如 logs/ 表示所有各個層級的目錄裡, 只要有 logs 目錄名稱就把它忽略 plaintext logs/ * 以驚嘆號 ! 表示不能忽略, 通常要加在最後 plaintext !/todo.bak/1.md.bak

### 忽略文件語法規則完整版本 : (翻譯自原文使用手冊)

1. { style=list-style-type:decimal; } 空行 沒有任何作用, 可用來分段, 以便於閱讀. 2. # 開頭表示註解, 如果檔名第一字元剛好是 #, 就用老方法, 在前方加入反斜線 \#. 3. 除非使用反斜線引用尾隨空格 \空格, 否則將忽略行末尾隨空格. 4. 前綴 驚嘆號 ! 表示不忽略此目錄或文件, 通常要加在最後一段, 讓先前已匹配為忽略再次包含進來. 5. 如果檔名剛好有 ! 開頭, 而且要忽略, 例如 !important!.txt, 也是用老方法, 在前方加入反斜線 \!important!.txt 避免被誤判為前一個不忽略的規則. 6. 模式開頭為 正斜線 / , 它只會從頂層目錄開始來匹配. 假如設為 /datas, 則頂層目錄名為 datas 會忽略(也包括它裡面的所有檔案), 但如果是其他非頂層目錄裡的子目錄有相同名稱, 例如 /public/datas/ 則不會被忽略. 或者設為 /docs/security.md 則只有此路徑的檔案會忽略, 而其他目錄下, 如 /public/docs/security.md, 不會忽略. 7. 模式開頭為 正斜線 / , 例如, /*.c 匹配頂層目錄的 cat-file.c 但不匹配 /src/sha1.c. 8. 模式以 正斜線 / 結尾, 它會找所有目錄來匹配. 換句話說, foo/ 將匹配目錄名 foo 和它下層的所有子目錄, 但是不匹配檔名為 foo 的一般檔案或符號鏈接. 9. 如果模式前後不包含 正斜線 /, git 將其視為一般路徑檔案來忽略, 例如 Desktop.ini, *.bak, /src/hello?.cpp ... 等符合模式的各個路徑下的檔案或指定路徑的檔案 10. 與完整路徑名匹配的兩個連續星號 ** 具有特殊含義. 在它之後加正斜線 **/ 意味匹配所有的路徑, 不管階層. 例如 **/foo 會忽略所有路徑之下有 foo 的路徑或檔名. 又如 **/foo/bar 則會忽略所有 foo 路徑之下有 bar 的路徑或檔名. 11. 在 ** 之前加正斜線 /** 匹配目錄裡的所有內容. 例如 foo/** 匹配 foo 目錄下 所有子目錄及檔案, 具有無限深度. 12. 在 ** 前後加正斜線 /**/ 匹配零個或多個目錄. 例如 a/**/b 匹配 a/b, a/x/b, a/x/y/b ...等, 無限延伸.

  1. .gitignore 忽略文件清單範例及規則說明

以下表格逐行說明 .gitignore 的引用語法規則

| 範例 | 規則 | 說明 | | :--- | :--: | :--- | | # 這是一個範例檔案 | 2 | 註解 | | | 1 | 空行, 用來分段 | | # Laravel 相關 | 2 | 註解 | | .env | 9 | 前後不含正斜線, 任何目錄下的相同檔名或目錄名 | | /public/storage | 6 | 開頭為正斜線, 頂層目錄下的 /public/storage 檔名或目錄名 | | /private/.key | 6,7 | 開頭為正斜線, 頂層目錄下的 /private/ 裡的任何 .key 檔案 | | /vendor | 6 | 開頭為正斜線, 頂層目錄下的 /vendor 檔名或目錄名 | | logs/ | 8 | 結尾為正斜線, 任何目錄下的 logs 目錄名 | | | 1 | 空行, 用來分段 | | # Windows 作業系統相關 | 2 | 註解 | | Thumbs.db | 9 | 前後不含正斜線, 任何目錄下的相同檔名或目錄名 | | ehthumbs.db | 9 | 前後不含正斜線, 任何目錄下的相同檔名或目錄名 | | Desktop.ini | 9 | 前後不含正斜線, 任何目錄下的相同檔名或目錄名 | | | 1 | 空行, 用來分段 | | # Mac osx 作業系統相關 | 2 | 註解 | | .DS_Store | 9 | 前後不含正斜線, 任何目錄下的相同檔名或目錄名 | | .DS_Store? | 9 | 前後不含正斜線, 任何目錄下的類似檔名或目錄名 | | ~ | 9 | 前後不含正斜線, 任何目錄下, 以 ~ 開頭, 備份檔或鎖定檔或目錄名 | | ~ | 9 | 前後不含正斜線, 任何目錄下, 以 ~ 結尾, 備份檔檔名或目錄名 | | ..swp | 9 | 前後不含正斜線, 任何目錄下, 以 . 開頭 .swp 結尾, vi 的編輯中鎖定檔 | | | 1 | 空行, 用來分段 | | # GCC compiler | 2 | 註解 | | .[oa] | 9 | 前後不含正斜線, 任何目錄下的 .o (c 的物件檔) 或 .a (c 的執行檔) 檔名或目錄名 | | | 1 | 空行, 用來分段 | | # 其他 | 2 | 註解 | | .bak | 9 | 前後不含正斜線, 任何目錄下, 文件備份檔名或目錄名 | | .log | 9 | 前後不含正斜線, 任何目錄下, .log 記錄檔名或目錄名 | | ..marks | 9 | 前後不含正斜線, 任何目錄下, .marks 結尾, 備份檔名或目錄名 | | **/.sass-cache | 10 | 任何目錄下, SCSS 快取目錄 | | **/.sass-cache/ | 10 | 任何目錄下, SCSS 快取目錄下的所有檔案 | | | 1 | 空行, 用來分段 | | # 特例 | 2 | 註解 | | \#\# | 2,9 | 不是註解, # 開頭, # 結尾, 未命名備份檔 | | !/todo.bak/todo.md.bak | 4 | 驚嘆號開頭, 此檔案不能忽略, 特別注意, 前面有設定 *.bak, 所以這個要放在它之後, 才能正常運作 |

  1. 實際測試 .gitignore

回到上次實作練習的目錄下, 我們實際產生一些檔案來 ignore 一下

現在建立第一個 .gitignore 檔案

### 命令 bash vi .gitignore

### .gitignore 內容 ```shell [alex@nvda /tmp/prj1 ]$ cat .gitignore # 第一個 .gitignore

.bak private logs/ /my-data ```

### .gitignore 內容對照說明(檔案每列的模式之後裡不能有 #) shell *.bak # 任何子目錄, 所有名稱以 .bak 結尾的檔案或目錄 private* # 任何子目錄, 所有名稱以 private 開頭的檔案或目錄 logs/ # 任何子目錄, 目錄名稱為 logs /my-data # 頂層目錄下的 my-data 目錄

使用 RSA 1024 位元加密,產出公鑰 public_1024.pem 與私鑰 private_1024.key

### 命令 bash openssl genrsa -out private_1024.key 1024 openssl rsa -in private_1024.key -out public_1024.pem -outform PEM -pubout ls -a git st -s

### 執行結果 shell [alex@nvda /tmp/prj1 ]$ openssl genrsa -out private_1024.key 1024 Generating RSA private key, 1024 bit long modulus (2 primes) .........+++++ ......+++++ e is 65537 (0x010001) [alex@nvda /tmp/prj1 ]$ openssl rsa -in private_1024.key -out public_1024.pem -outform PEM -pubout writing RSA key [alex@nvda /tmp/prj1 ]$ rm -fr logs [alex@nvda /tmp/prj1 ]$ ls -a . .. .git .gitignore index.php private_1024.key public_1024.pem README.md [alex@nvda /tmp/prj1 ]$ git st -s ?? .gitignore ?? public_1024.pem [alex@nvda /tmp/prj1 ]$

觀察檔案的變化後, 私鑰 private_1024.key 這個檔案, 並沒有在 git st -s 的內容裡, 顯然, .gitignore 符合我們的期待, 接著測試其他項目

### 命令 bash mkdir public mkdir public/logs touch public/main.css touch public/logs/1.log touch public/logs/2.log tree public

### 執行結果 ```shell [alex@nvda /tmp/prj1 ]$ mkdir public [alex@nvda /tmp/prj1 ]$ mkdir public/logs [alex@nvda /tmp/prj1 ]$ touch public/main.css [alex@nvda /tmp/prj1 ]$ touch public/logs/1.log [alex@nvda /tmp/prj1 ]$ touch public/logs/2.log [alex@nvda /tmp/prj1 ]$ tree public public ├── logs │   ├── 1.log │   └── 2.log └── main.css

1 directory, 3 files [alex@nvda /tmp/prj1 ]$ ```

### 命令 bash git st -s git add . git st -s

### 執行結果 shell [alex@nvda /tmp/prj1 ]$ git st -s ?? .gitignore ?? public/ ?? public_1024.pem [alex@nvda /tmp/prj1 ]$ git add . [alex@nvda /tmp/prj1 ]$ git st -s A .gitignore A public/main.css A public_1024.pem [alex@nvda /tmp/prj1 ]$

public 目錄裡的 logs 子目錄及其下的所有檔案, 沒有被 git 索引追踪, 但其他檔案 main.css 有被 git 索引追踪, 也符合期待

### 命令 bash mkdir my-data mkdir public/my-data touch my-data/1st.md touch public/my-data/2nd.md git st -s git add . git st -s

### 執行結果 shell [alex@nvda /tmp/prj1 ]$ mkdir my-data [alex@nvda /tmp/prj1 ]$ mkdir public/my-data [alex@nvda /tmp/prj1 ]$ touch my-data/1st.md [alex@nvda /tmp/prj1 ]$ touch public/my-data/2nd.md [alex@nvda /tmp/prj1 ]$ git st -s A .gitignore A public/main.css A public_1024.pem ?? public/my-data/ [alex@nvda /tmp/prj1 ]$ git add . [alex@nvda /tmp/prj1 ]$ git st -s A .gitignore A public/main.css A public/my-data/2nd.md A public_1024.pem [alex@nvda /tmp/prj1 ]$

從最後的 git st -s 結果得知, public/my-data/2nd.md 有被 git 索引追踪, 但最頂層的 my-data/ 沒有被 git 索引追踪, 這樣的結果也是正確的

### 命令 bash mkdir 190228.bak touch 190228.bak/a1.txt touch 190228.bak/b2.md touch public/c3.bak touch public/d4.js git st -s

### 執行結果 shell [alex@nvda /tmp/prj1 ]$ mkdir 190228.bak [alex@nvda /tmp/prj1 ]$ touch 190228.bak/a1.txt [alex@nvda /tmp/prj1 ]$ touch 190228.bak/b2.md [alex@nvda /tmp/prj1 ]$ touch public/c3.bak [alex@nvda /tmp/prj1 ]$ touch public/d4.js [alex@nvda /tmp/prj1 ]$ [alex@nvda /tmp/prj1 ]$ git st -s A .gitignore A public/main.css A public/my-data/2nd.md A public_1024.pem ?? public/d4.js [alex@nvda /tmp/prj1 ]$

子目錄 190228.bakpublic/c3.bak 檔案, 沒有被 git 索引追踪, 因為它們都符合 *.bak 的規則, 只有最後的 public/d4.js 有被 git 索引追踪, 完全沒問題.

實測結束, 未來你們再根據自己開發進行中的狀態隨時更新 .gitignore 檔案

### 命令 bash git add . git commit -m '實測 .gitignore 完成' git log

### 執行結果 shell [alex@nvda /tmp/prj1 ]$ git add . [alex@nvda /tmp/prj1 ]$ git commit -m '實測 .gitignore 完成' [master cd002dc] 實測 .gitignore 完成 5 files changed, 12 insertions(+) create mode 100644 .gitignore create mode 100644 public/d4.js create mode 100644 public/main.css create mode 100644 public/my-data/2nd.md create mode 100644 public_1024.pem [alex@nvda /tmp/prj1 ]$ [alex@nvda /tmp/prj1 ]$ git log 1 commit cd002dc3023c214120b3eb9e50a177d28a66fb26 2 Author: alex <alex@forblind.org.tw> 3 Date: Fri Mar 1 10:44:21 2019 +0800 4 5 實測 .gitignore 完成 6 7 commit f2ed5d757c9a81de0d1ccd601af1e4a6a48333e4 8 Author: alex <alex@forblind.org.tw> 9 Date: Tue Feb 19 16:24:19 2019 +0800 10 11 第一次提交到 git repo (END)

### 命令 bash tree -fU

### 執行結果 ```shell [alex@nvda /tmp/prj1 ]$ tree -fU . ├── ./private_1024.key ├── ./public_1024.pem ├── ./my-data │   └── ./my-data/1st.md ├── ./190228.bak │   ├── ./190228.bak/a1.txt │   └── ./190228.bak/b2.md ├── ./public │   ├── ./public/d4.js │   ├── ./public/my-data │   │   └── ./public/my-data/2nd.md │   ├── ./public/c3.bak │   ├── ./public/main.css │   └── ./public/logs │   ├── ./public/logs/2.log │   └── ./public/logs/1.log ├── ./index.php └── ./README.md

5 directories, 13 files [alex@nvda /tmp/prj1 ]$ ```

  1. One More Thing 萬一改了 .gitignore

每接到一次任務就會改變一點命運, 隨著開發過程千變萬化. 假設有一個套件會產生 cache 目錄及檔案, 在還沒測試完成前, 沒注意到, 就把這些檔案都加入暫存區(索引).

我們先手動模擬建置, 並加入暫存區(索引)

### 命令 bash mkdir cache touch cache/x1.php touch cache/x2.php

### 執行結果 shell [alex@nvda /tmp/prj1 ]$ mkdir cache [alex@nvda /tmp/prj1 ]$ touch cache/x1.php [alex@nvda /tmp/prj1 ]$ touch cache/x2.php [alex@nvda /tmp/prj1 ]$

### 命令 bash git add . git st -s

### 執行結果 shell [alex@nvda /tmp/prj1 ]$ git add . [alex@nvda /tmp/prj1 ]$ git st -s A cache/x1.php A cache/x2.php [alex@nvda /tmp/prj1 ]$

此刻 cache/ 裡的所有檔案都暫存區(索引), 但還沒 commit, 沒關係, 先修改 .gitignore , 免得以後又發生相同錯誤

### 命令 bash vi .gitignore cat .gitignore git st -s

### 執行結果 ```shell [alex@nvda /tmp/prj1 ]$ vi .gitignore # 在最後加一行 cache/ [alex@nvda /tmp/prj1 ]$ cat .gitignore # 第一個 .gitignore

.bak private logs/ /my-data cache/ [alex@nvda /tmp/prj1 ]$ git st -s M .gitignore A cache/x1.php A cache/x2.php [alex@nvda /tmp/prj1 ]$ ```

觀察狀態, 看到 .gitignore 已修改, 但原先的 cache/x1.php, cache/x2.php 都仍然存在暫存區(索引). 現在要施法術 git rm --cached 把它們兩個移出暫存區(索引).

### 命令 bash git rm --cached cache/x1.php git rm --cached cache/x2.php git st -s git add . git commit -m '從暫存區(索引)移除不要追踪的檔案'

### 執行結果 shell [alex@nvda /tmp/prj1 ]$ git rm --cached cache/x1.php rm 'cache/x1.php' [alex@nvda /tmp/prj1 ]$ git rm --cached cache/x2.php rm 'cache/x2.php' [alex@nvda /tmp/prj1 ]$ git st -s M .gitignore [alex@nvda /tmp/prj1 ]$ git add . [alex@nvda /tmp/prj1 ]$ git commit -m '從暫存區(索引)移除不要追踪的檔案' [master 821764e] 從暫存區(索引)移除不要追踪的檔案 1 file changed, 1 insertion(+) [alex@nvda /tmp/prj1 ]$

最後觀察狀態, 只剩下 .gitignore 顯示為已修改, 這就我們所要的.

  1. 本章總結

### 本章使用 git 命令總結 bash .gitignore # 忽略文件清單 git rm --cached <filename> # 將此檔案從暫存區(索引)移走