# 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 施行還原大法, 把過去某一段美好時間點的程式碼, 重新呈現出來. 999. ## .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` 999. ## .gitignore 忽略文件清單的語法規則 > **什麼是 glob pattern** > > 所謂的 `glob pattern` 是指 shell 所使用的簡化版本的正則表達式. > > 檔名及目錄可以使用標準的 `glob pattern` (以下規則簡稱為模式) 匹配 > > 問號 `?` 只匹配一個任意字符 > > 星號 `*` 匹配零個或多個任意字符. > > `[abc]` 匹配任何一個列在方括號中的字符 ( 這個例子匹配一個 a, 或匹配一個 b, 或匹配一個 c ); 如果在方括號中使用 `- 短划線` 分隔兩個字符, 表示所有在這兩個字符範圍內的都可以匹配 `[0-9] 表示匹配所有 0 到 9 的數字`. > > 使用雙星號 `\*\*` 表示匹配任意中間目錄, 比如 `a/**/b` 可以匹配 `a/b`, `a/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` ...等, 無限延伸. 999. ## .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, 所以這個要放在它之後, 才能正常運作 | 999. ## 實際測試 .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.bak` 及 `public/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 3 Date: Fri Mar 1 10:44:21 2019 +0800 4 5 實測 .gitignore 完成 6 7 commit f2ed5d757c9a81de0d1ccd601af1e4a6a48333e4 8 Author: alex 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 ]$ ``` 999. ## 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` 顯示為已修改, 這就我們所要的. 999. ## 本章總結 ### 本章使用 git 命令總結 ```bash .gitignore # 忽略文件清單 git rm --cached # 將此檔案從暫存區(索引)移走 ```