[檔案管理] git應用:實例參考 - 精華區

[檔案管理] git應用:實例參考

阿慶

阿慶圖像

2020-09-23 17:00:12

From:211.23.21.202

git主機換位置了

背景與問題

原本公司的內部系統只架在內部

如果想從外部 git 取得程式碼是不行的

但有時緊急狀況或想在假日寫程式怎麼辦

後來就在內部系統的主機的另外一張網卡設定公司另外一組對外 ip 讓外部 ssh 使用

然後在主機防火牆加入允許家裡的 ip 可以連線與轉 port 等等

不過這不是我們這篇的重點

最近我們架設了 vpn 可以讓特定的使用者從外部連進內部

所以我把主機的對外 ip 關了

在家直接使用 vpn 連進內部

問題來了,我們知道 git 背後是用 ssh 在連線,至少我是這樣設定

之前設定了 ssh 連線是對外 ip 但現在 vpn 進來後是要連協會內部 ip

就當成自己是在協會內部進行作業

所以對外ip 關掉以後,不改設定 ssh 就連不上了

當然 git 也沒辦法使用了

改遠端 ip

所以我們要把 git 的 ssh 連線 ip 改成對內 ip

在專案資料夾下找到 .git 資料夾,使用編輯器開啟檔案 config

找這一段:

[remote "origin"]
	url = logo@140.114.34.8:/xxx/yyy

改成對內 ip:

[remote "origin"]
	url = logo@192.168.0.77:/xxx/yyy

更新加密 key

存檔後連線會發現還是連不上

想把專案拉回來

$ git pull origin master
 ssh: connect to host 192.168.0.77 port 22: Connection timed out
 fatal: Could not read from remote repository.

 Please make sure you have the correct access rights
 and the repository exists.

如果確定 ip 跟路徑都打正確,那就是 ssh 的加密 key 需要更新

網路上有很多做法,我認為最簡單的就是直接用 ssh 手動連線一次,順便確保帳號、密碼、位置等都是對的

這裡要用的 ssh 命令就在我們安裝 git 到系統裡就已經提供,而且也被加入系統路徑了

事實上他跟 git 命令是在同一個路徑,所以可以在 git bash 下使用 git 就一定能用 ssh

那我們用 ssh 手動連一下內部系統的主機:

$ ssh logo@192.168.0.77
 logo@192.168.0.77's password:

輸入密碼登入後看到提示符號輸入 exit 按 enter 離開

這表示 ssh 連線都沒問題了

那這樣這個位置的連線資訊就會被 ssh 記下來,包括驗證 key 等等

接著我們再次拉回專案看看:

$ git pull origin master
 logo@192.168.0.77's password: 輸入密碼
 remote: Enumerating objects: 167, done.
 remote: Counting objects: 100% (167/167), done.
 remote: Compressing objects: 100% (147/147), done.
 remote: Total 148 (delta 110), reused 1 (delta 1)
 Receiving objects: 100% (148/148), 38.78 KiB | 4.31 MiB/s, done.
 Resolving deltas: 100% (110/110), completed with 19 local objects.
 From 192.168.0.77:/xxx/yyy
  * branch            master     -> FETCH_HEAD
    a9259f6..eee507f  master     -> origin/master
 Updating a9259f6..eee507f
 Fast-forward
  controllers/Controller.php                    |   6 +-
# 接著就是一堆更改的檔案列表,略過
  views/Views.php                               |   6 +
  24 files changed, 1094 insertions(+), 386 deletions(-)

成功了,更新了數十個檔案,一千多行,可見很久沒在家作業了

今天懶得去公司,昨天寫的功能還沒加註解,想在家補上

同步遠端單一分支

我們在 推不上去 這篇文章中有跟大家分享,當有一個新分支被推上去後,如果其他協同開發者的遠端還沒有這個分支怎麼辦?

當時是使用 git remote update 來更新所有的遠端分支,在文中我們也提到比較好的做法是把新分支 pull 回來會比較好。

我們知道在一個專案當中會有很多分支,git remote update 是會把所有的分支依據倉庫全部更新到最新,

就相當於 git fetch --all 這個指令。但有些分支可能還在開發中,或者根本不是你負責的項目,

尤其是在正式環境下,就不適合一次把所有的分支都更新到最新了。

git fetch 遠端分支名稱 就可以只單獨更新一個分支,但如果這個分支只在倉庫裡面,

這個使用者因為還沒有 update 遠端的資訊,所以根本還沒有這個分支,那使用 git fetch 遠端分支名稱 這個指令就會出錯。

因此我們應該使用 git checkout -b abc origin/abc 指令。

  • checkout 切換分支
  • -b 本地沒有的分支會自己建一個
  • abc 本地想要的分支名稱
  • origin/abc 遠端倉庫分支的名稱

這個指令幫我們把倉庫的那個分支抓回來更新並且切換過去。

有人會問,那我自己先 git branch abcgit pull origin abc 不就好了?

各位要注意的是,當下 git branch abc 是從現在的分支做一個一樣的分支出去,這會有點不同。

也就是說,小明做了一個 abc 的本地分支,是從分支 logo 的 commit 123456 做出來的,

然後做了數個 commit 之後把 abc 推上倉庫。

而小華目前處在 master 分支,他的 commit 是 112233 那這樣直接做一個 abc 分支的話是基於 commit 112233 而不是 123456 喔。

所以上面的指令 git checkout -b abc origin/abc 可想像成是把倉庫原汁原味的 abc 分支整個抓過來。

後悔藥有得買1

在 git 的世界裡面基本上沒有什麼救不回來的狀況,也就是後悔藥是有得買的。

在之前的章節中多少有提過,例如如何從 status 的狀態放棄,或者 add 加入暫存區的檔案如何排除,

以及已經 commit 的檔案如何修改等等。

這裡稍微整理各種狀況讓大家在出問題時可以先看看這兩篇,找尋你的後悔藥。

從狀態中移除

更動過的記錄檔案不外乎就是修改、刪除、新增檔案與資料夾這些狀況,記得如果有路徑的要輸入完整正確,或者使用相對路徑去表示。

不過對 git 來說事情其實沒有那麼複雜,我們只要分成兩類來看就好,也就是原本存在的跟原本不存在的。

原本存在的

原本存在的,不管是刪除或修改都可以使用 git restore 檔名或資料夾名 來恢復。

使用萬用字元 . 就表示所有的意思,這樣就不用一個個打了。

之前提過使用 git checkout 除了切換分支,也可以恢復檔案,這兩個指令沒有什麼不同,

是因為 checkout 可以切換分支跟回復檔案,當分支跟檔案同名時容易讓人誤解,

當這種情況發生時 checkout 會先作用在分支,也就是無法用這個指令回復檔案。

所以後來的 git 2.23.0 版本開始就多了 restore 讓一個指令就做一件事情比較好記憶跟管理。

也多了 switch 來切換分支,所以這兩個指令就完全取代 checkout 了

嗎?

也沒有,像 git checkout -b abc 就無法以 git switch -b abc 來取代了,不知以後的版本會不會改良。

也發現 switch 沒辦法使用 tab 鍵來補齊分支名稱,但 checkout 可以。

好吧,有點扯遠了,接著來談原本不存在的部分。

原本不存在的

原本不存在的就是新增出來的,也就是 untracked 狀態。順便復習一下,

git 是不管空資料夾的,一定要有檔案才算數,對 git 來說資料夾只是檔案的路徑而已。

新增出來的部分其實直接砍掉就好,因為新增出來的你不想要,

這跟原本存在的不同,原本存在的我們可能只是放棄這一次的修改,並不想刪除它。

而 untracked 的確是這一次新增出來的,可以直接用檔案管理來刪除,

如果想用 git 來刪也可以。

  • git clean -d # 刪資料夾
  • git clean -f # 刪檔案
  • git clean -df # 刪資料夾與檔案
  • git clean -ndf # 刪資料夾與檔案之前先列出會刪什麼,不要真的直接刪掉

後面不要接名稱的話就是處理全部的 untracked 部分。

另一招

還記得之前介紹過的 stash 吧,就是工作被打斷,臨時要切到其他分支修 bug 時,

先把目前的內容丟到緩充區,這樣也是有達到回復效果,但 untracked 的部分還是得手動處理喔。

反過來說, clean 也只能處理 untracked 的部分喔。

更名

最後提一下改名字的部分,改名字對 git 來說就是刪掉原本的再新增一個,

在看過上面的內容後,你應該知道該怎麼處理了。

所以你對原本的檔案改名,會產生兩筆紀錄,有趣的是我們 restore 雖然舊檔回來了,

但因為沒有處理 unttracked 新檔的部分,所以會變成在工作目錄裡有兩個檔案內容相同的新舊檔喔。

後悔藥有得買2

在 git 的世界裡面基本上沒有什麼救不回來的狀況,也就是後悔藥是有得買的。

在之前的章節中多少有提過,例如如何從 status 的狀態放棄,或者 add 加入暫存區的檔案如何排除,

以及已經 commit 的檔案如何修改等等。

這裡稍微整理各種狀況讓大家在出問題時可以先看看這兩篇,找尋你的後悔藥。

從暫存檔中移除

那如果已經加到暫存檔,也就是 add 進去了,有辦法後悔嗎?

當然沒問題,我們看一下目前暫存區的狀態

$ git status
 On branch newnew
 Changes to be committed:
   (use "git restore --staged <file>..." to unstage)
         modified:   abc.txt
         deleted:    log.txt
         new file:   qqq/abc.txt

看起來就是修改又刪除了一些檔案,然後產生了一個新的資料夾及檔案,並且加到暫存區(stage)了,

由 git 給我們的資訊很簡單的發現,只要使用 git restore --staged 檔名 就可以。

$ git restore --staged abc.txt

$ git status
 On branch newnew
 Changes to be committed:
   (use "git restore --staged <file>..." to unstage)
         deleted:    log.txt
         new file:   qqq/abc.txt

 Changes not staged for commit:
   (use "git add <file>..." to update what will be committed)
   (use "git restore <file>..." to discard changes in working directory)
         modified:   abc.txt

所以 abc.txt 已經從暫存區移出,如果想從 status 也還原,那就參考 上一篇 的教學吧。

我們先把 abc.txt 再次 add 進 stage 然後試看看用 . 能不能一次全部從 stage 移出:

$ git add abc.txt

$ git restore --staged .

$ git status
 On branch newnew
 Changes not staged for commit:
   (use "git add/rm <file>..." to update what will be committed)
   (use "git restore <file>..." to discard changes in working directory)
         modified:   abc.txt
         deleted:    log.txt

 Untracked files:
   (use "git add <file>..." to include in what will be committed)
         qqq/

 no changes added to commit (use "git add" and/or "git commit -a")

成功了,這個狀態應該要很熟悉,也就是類似回到上一篇的狀況。

但是,有沒有一種方法是可以直接從暫存區就直接恢復,而不是只退到工作目錄而已,

因為如果想直接還原到沒有動作,依上例就必須先從暫存區移回工作目錄,再由工作目錄還原到未更動的狀態。

當然我們可以把這兩篇的還原指令用 && 連起來完成,不過這不是我們想要的方法。

大家可以回想一下 git 的架構,其實這個問題就是還原到上一次提交的狀態而已,

因為我們還沒有做新的 commit 所以只要回到上一個提交的狀態,後面的變化全部都會被取消。

$ git reset --hard HEAD

從 commit 中移除

這部分我們之前的基礎應用都講過了,例如添加 commit 的資訊,或改寫 commit 內容,

還有刪除、拆解、合併 commit 等等,這邊就不多說了。

撿 commit 的疑惑

這邊收到伙伴的來信,詢問關於使用 cherry-pick 上的疑惑。

來撿 commit 一文中已經介紹過 cherry-pick 了。

主要是用來撿想要的 commit 但在使用上伙伴產生了疑惑,就讓我們來看一下吧。

首先,我們建立一個檔案,名為 index.txt 裡面有三行,分別是:

第一行
第二行
第三行

然後做了一個 commit

 commit 51f33baeac4aef3a8aaf6a2d6a4cbb124f25f20c (HEAD -> master)
 Author: Logo Kuo <logo@forblind.org.tw>
 Date:   Fri Jul 31 00:03:36 2020 +0800

     原始 index.txt

接下來,把 index.txt 裡的第二行改成「修改第二行了」,也加了第四行,檔案內容變這樣:

第一行
修改第二行了
第三行
我是第四行

一樣做個 commit:

 commit c69bfc015bb98ec13db6167172b23d856c99c6be (HEAD -> master)
 Author: Logo Kuo <logo@forblind.org.tw>
 Date:   Fri Jul 31 00:07:34 2020 +0800

     改第二行加第四行

最後,我們再把第三行刪掉,一樣做一個 commit:

 commit aec61567f3eb0f6fb7cb27fe6c244ddb0b6ee332 (HEAD -> master)
 Author: Logo Kuo <logo@forblind.org.tw>
 Date:   Fri Jul 31 00:11:05 2020 +0800

     刪第三行

所以關於 index.txt 總共有三個提交。

我們原本是在 master 主幹道,現在切換到 new_test 這個分支。

在這個分支目前完全沒有 index.txt 這個檔案。

我們如果直接撿第三個 commit 會怎麼樣:

$ git checkout new_test
 Switched to branch 'new_test'

$ git log -1
 commit 21d3781343c4fb29d7f94ef2449a7fe71d9f17d8 (HEAD -> new_test)
 Author: Logo Kuo <logo@forblind.org.tw>
 Date:   Thu Apr 16 17:34:35 2020 +0800

     修改 abc.txt

$ git status
 On branch new_test
 nothing to commit, working tree clean

$ git cherry-pick aec6156
 CONFLICT (modify/delete): index.txt deleted in HEAD and modified in aec6156...
 刪第三行. Version aec6156... 刪第三行 of index.txt left in tree.
 error: could not apply aec6156... 刪第三行
 hint: after resolving the conflicts, mark the corrected paths
 hint: with 'git add <paths>' or 'git rm <paths>'
 hint: and commit the result with 'git commit'

為什麼不行,因為這個分支我們沒有 index.txt 要怎麼對它做修改,在第三個 commit 並沒有新增一個 index.txt 檔案的動作,

這個是我們在第一個 commit 做的事情。

$ git status
 On branch new_test
 You are currently cherry-picking commit aec6156.
   (fix conflicts and run "git cherry-pick --continue")
   (use "git cherry-pick --skip" to skip this patch)
   (use "git cherry-pick --abort" to cancel the cherry-pick operation)

 Unmerged paths:
   (use "git add/rm <file>..." as appropriate to mark resolution)
         deleted by us:   index.txt

 no changes added to commit (use "git add" and/or "git commit -a")

他告訴我們 index.txt 沒有在工作區,那我們直接把它加進來:

$ git add index.txt
$ git commit -m '加入 index.txt'
 [new_test a970dc8] 加入 index.txt
  Date: Fri Jul 31 00:11:05 2020 +0800
  1 file changed, 3 insertions(+)
  create mode 100644 index.txt

$ git log -1
 commit a970dc84dfb5da1e8e4fa3c4d74e06e1e87f942f (HEAD -> new_test)
 Author: Logo Kuo <logo@forblind.org.tw>
 Date:   Fri Jul 31 00:11:05 2020 +0800

     加入 index.txt

趕快來看一下 index.txt 的內容:

$ cat index.txt
 第一行
 修改第二行了
 我是第四行

檔案的內容是完整的,看起來就是第三個 commit 提交的版本。

那位伙伴的問題在於把 commit 想成是前面一個個 commit 的累加,

也就是說,如果我直接撿第三個 commit 出來的結果會不完整,因為我沒有第一和第二個 commit.

經過上述的實驗,我們發現這樣的想法是錯誤的,

如果是這樣的話,撿 commit 是沒意義的,因為每個 commit 都要靠上一個 commit 來累積,

那直接撿最後一個行不通,一定要往回撿,那就全部 merge 了,何必單獨撿 commit.

所以,我們要有個正確的概念,就是可以把每個 commit 想成是單獨唯一的,

你撿了這個 commit 就是擁有這個 commit 所有看到的結果。

因此,你不需要考慮他的前一個 commit 做了什麼,你只要確定這個 commit 的結果是你要的,你就可以把他撿過來了。

像我們經常會在一個分支裡面推了好幾個 commit 例如,第一個新增刪除功能,第二個是把刪除的表格調整好,第三個是新增修改功能。

結果第三個新增修改功能時不小心把刪除功能搞壞了,

所以我們只要撿第二個 commit 就會有新增的刪除功能且表格是調整好的。

這樣我們的主幹道就會很簡潔,就挑完成且確定沒問題的 commit 過來就好了。

另外,使用 cherry-pick 後面使用空格來隔開多個 commit 一次撿多個 commit 時,

它不會自動幫我們 merge 在一起,而是一個個撿過來,如果要合併可以考慮看 合併 commit 這篇。

commit 比一比(1)

我們在基礎的章節中提過一些查看commit 後的檔案內容,或者某個檔案的歷史修改紀錄。

這次我們利用實例來了解 commit 的內容,並不只是看 commit 訊息而已。

在什麼情況下我們會想看某個 commit 的完整內容或把兩個 commit 做比較:

  • 你是專案管理人,需要對每個成員的程式進行了解
  • 系統有問題了但不知道是哪個 commit 改壞了
  • 因為光看 commit 訊息還是不清楚,想知道某個 commit 到底做了什麼事

昨天因為新伙伴第一次寫內部系統,所以我想從頭到尾看一下他寫的 code 是不是有問題,

因此使用了這個功能。

在 git 中有很多指令的功能是一樣或類似的,查紀錄也是一樣。

例如 git log -p commit 等於 git show commit 他們都是利用類似 diff 的工具列出詳細內容。

讓我們來了解一下:

$ git show
# 沒有輸入 commit 表示查看最後一個提交的詳細資訊
# 如果覺得資訊很多,不好對照,可以把結果輸出到檔案,再用編輯器開檔來閱讀
# git show > 路徑檔名

commit d1158da7b42346978dc261e0012b7f33b11880c8
Author: Logo Kuo <logo@forblind.org.tw>
Date:   Sat Sep 5 21:06:57 2020 +0800

    修正批次匯入時找到捐贈者後使用了相同的變數名稱 result 錯誤
# 以上是原本 git log 所看到的資訊
# 如果這段程式不是你寫的,相信光看這個 commit 訊息完全不知道是改了什麼檔案的什麼地方
diff --git a/xxx/models/donorsAdminModel.php b/xxx/models/donorsAdminModel.php
# 上面是修改的檔名與路徑
# a 跟 b 不是路徑,而是表示 a 與 b 相比
# 也就是說, a 是這個 commit 之前的內容, b 是這次 commit 所修改的部分
index 148f1af..7de6b6b 100644
# 我只知道 100644 是檔案權限,如果熟 linux 應該看得懂,
# 擁有者可讀寫,群組可讀與其他使用群組可讀
# 其他可以請高手補充
--- a/sys/models/donorsAdminModel.php
+++ b/sys/models/donorsAdminModel.php
@@ -973,8 +973,8 @@ class donorsAdminModel extends forblind_DB {
# 973 是行號,但是系統會列出修改開始的前三行與修改結束的後三行
# 所以實際我們修改的是 976 行
# 當然你也可以直接使用關鍵字來搜尋到該處
# class 開頭的是這個檔案的第一行有效行,也就是他會自動略過註解
 				$sql .= $tail;
 				$donorObj = $this->db->query($sql);
 				if( $donorObj->num_rows > 0 ){
# 上面這三行檔案內容前面沒有符號的,就表示這是給你參考位置用的,實際上沒有被修改\
-					$result = $donorObj->fetch_assoc();
-					$id = $result['donors_ID'];
# 上面這兩行前面有減號,表示這些被刪了,其實可能是被修改而已,不是真的全部刪掉重打的
# 反正對 diff 來說,有改過的行就是 - 然後直接給你一行修改過的新行前面標上 +
+					$ret = $donorObj->fetch_assoc();
+					$id = $ret['donors_ID'];
# 從這裡可以看出,我們把變數 $result 改成 $ret 了
# 再看一下原本 commit 的訊息應該就懂了
 					$donor['donors_ID'] = $donation['donors_ID'] = $id;
 					// 有找到就需要更新捐贈人資料
 					// 設定自然人或法人 join 的表
# 後面檔案內容的這三行一樣是給我們定位用的,實際上沒有被修改
# 接下來看一下統計
$ git log -1 -stat
 commit d1158da7b42346978dc261e0012b7f33b11880c8 (HEAD -> master, origin/master,
 origin/HEAD)
 Author: Logo Kuo <logo@forblind.org.tw>
 Date:   Sat Sep 5 21:06:57 2020 +0800

     修正批次匯入時找到捐贈者後使用了相同的變數名稱 result 錯誤
# 前面都一樣
  xxx/models/donorsAdminModel.php | 4 ++--
  1 file changed, 2 insertions(+), 2 deletions(-)
# 這是這個 commit 的統計數據,實際上就是我們在推 commit 寫完訊息後會產生的那部分
# 我們可以看出是哪一個路徑及檔案被更動了
# 它告訴我們有 4 行被加加減減
# 這個 commit 有1個檔案被更動,2行被新增與2行被刪除

我認為一定要會看這些資訊,不然我們就沒辦法得知到底這個提交實際是做了什麼事,

例如我可能做了四件事,但在 commit 訊息只寫了個大概,甚至順手改了什麼也沒寫出來

像昨天,我就從 log 發現有一行被誤刪了

從 commit 詳細紀錄還可以看到這個伙伴的撰寫軌跡與思考的過程

其實滿有趣的,下一篇我們要來看看複雜一點的 commit 紀錄。

commit 比一比(2)

有時候在一個 commit 當中會去更改很多檔案,或是一個檔案的各個地方,

因此,讓我們來看一些比較複雜的 diff 資訊,其實說穿了都是一樣的東西,

我們會感覺比較複雜只是因為修改的量比較大而已。

$ git show 62087e083x
commit 62087e083x2f7cf2349764348b40110daee372fe
Author: Logo Kuo <logo@forblind.org.tw>
Date:   Wed Sep 9 18:29:42 2020 +0800

    將所有請休假寄信功能改成 mailTemplate

diff --git a/xxx/self/leaveCalculation.php b/xxx/self/leaveCalculation.php
index 84eeeab..128cb33 100644
# index 表示這些改動資料已經進到索引裡面,也就是已經提交囉
--- a/xxx/self/leaveCalculation.php
+++ b/xxx/self/leaveCalculation.php
# 所以後面的 - 行數 與 + 行數就是表示來源檔案與目標檔案的代稱
@@ -1519,7 +1519,6 @@ class leaveCalculation extends forblind_DB
# 從 1519 行開始加3行 也就是 1522 原檔原本是
# $retData = [];
# 但這行前面有個 - 號,所以它被刪掉了
# 所以目的檔的 1522 就變成原檔的 1523
# 因此目的檔的 1522 是這行
#		$subject['name'] = $data['name'];
# 所以原檔這個區塊原本有 7 行,到了目的檔刪了一行,這個區塊就只有 6 行
# 這就是逗號後面數字的意義
 	protected function setMailContent($data,$type){ // 信件內容
 		$data = $this->dbAryStr($data);
 		$subject = ['title'=>'','name'=>'','type'=>'','time'=>'',];
-		$retData = [];
 		$subject['name'] = $data['name'];
 		switch ($data['itemsName']) {
 			case '加班':
@@ -1541,46 +1540,35 @@ class leaveCalculation extends forblind_DB
# 這邊又進入了下一個修改區段
# 這個區塊原本有 46 行,但加加減減後變成只有 35 行
# 但實際狀況要看內容,並不是很單純了少了 11 行
# 而是有些地方加,有些地方減,最後整個小區塊得到的結果
# 當改動越來越多也越來越大時,你會發現很難推算原本某一行在原檔的行數
# 但重點在於區塊當中加了什麼跟少了什麼,也就是通常注意相對性的資訊是比較重要的
# 這裡,目的檔是 1540 一樣加 3 也就是目的檔的 1543 是這行
#		// 寄送信件所需資訊
# 這是原本沒有的,因為前面有個 + 號,就是新增的行
# 從這邊開始加了 11 行
# 經過了兩行沒有被更動的
# 接下來又刪了 6 行加了 1 行
# 以此類推…
# 所以這一段總計是 +11-6+1-6+2-6+2-7+2-2+2-5+1
# 最後 -11 沒錯, 46-35 = 11 目的檔少了 15 行
 			$subject['time'] = date('ymdHi',strtotime($data['setDateTime']));
 			$bodyTime = date('Y-m-d',strtotime($data['startTime']));
 		}
+		// 寄送信件所需資訊
+		$mailTpl = new mailTemplate;
+		$bodyData = [
+			'name' => $subject['name'],
+			'type' => $subject['type'],
+			'time' => $subject['time'],
+			'toName' => $data['toName'],
+			'bodyTime' => $bodyTime,
+			'in_url' => $in_url,
+			'ex_url' => $ex_url,
+		];
 		switch ($type) {
 			case 'signing': // 審核信
-				$subject['title'] = "待簽核";
-				$retData['body'] = "<p style='margin: 4px 0;'><span style='font-weight: bold;'>$data[toName] ($bodyTime $subject[type])</span>待審核,煩請儘速連至系統簽核。</p>";
-				$retData['body'] .= "<p style='margin: 4px 0;'><a style='color: blue;font-weight: bold;' href='".$this->ishttps().IN_URL."/leave/deptMar_signing'>內部[待簽核表單]</a>";
-				$retData['body'] .= "<p style='margin: 4px 0;'><a style='color: blue;font-weight: bold;' href='".$this->ishttps().EX_URL."/leave/deptMar_signing'>外部[待簽核表單]</a>";
-				$retData['body'] .= " <span style='color:red;'>*請務必連線協會網路才可登入</span></p>";
-				$retData['body'] .= "<p style='margin: 4px 0;'>若您讀取本通知信時,已完成該份表單簽核,請忽略此信!</p>";
+				$mailContent = $mailTpl->leaveSigningMail($bodyData);
 				break;
-			case 'OK': // 審核完成
-				$subject['title'] = "簽核通過";
-				$retData['body'] = "<p style='margin: 4px 0;'><span style='font-weight: bold;'>$data[name] ($bodyTime $subject[type])</span> 已由主管簽核完成,請至系統查詢簽核結果。</p>";
-				$retData['body'] .= "<p style='margin: 4px 0;'><a style='color: blue;font-weight: bold;' href='$in_url'>內部[查詢簽核表單]</a>";
-				$retData['body'] .= "<p style='margin: 4px 0;'><a style='color: blue;font-weight: bold;' href='$ex_url'>外部[查詢簽核表單]</a>";
-				$retData['body'] .= " <span style='color:red;'>*請務必連線協會網路才可登入</span></p>";
+			case 'OK': // 審核完成信
+				$mailContent = $mailTpl->leaveOkMail($bodyData);
 				break;
-			case 'fail': // 簽核不通過
-				$subject['title'] = "簽核不通過";
-				$retData['body'] = "<p style='margin: 4px 0;'><span style='font-weight: bold;'>$data[name] ($bodyTime $subject[type])</span> 簽核不通過,請至系統查詢簽核結果。</p>";
-				$retData['body'] .= "<p style='margin: 4px 0;'><a style='color: blue;font-weight: bold;' href='$in_url'>內部[查詢簽核表單]</a>";
-				$retData['body'] .= "<p style='margin: 4px 0;'><a style='color: blue;font-weight: bold;' href='$ex_url'>外部[查詢簽核表單]</a>";
-				$retData['body'] .= " <span style='color:red;'>*請務必連線協會網路才可登入</span></p>";
+			case 'fail': // 簽核不通過信
+				$mailContent = $mailTpl->leaveFailMail($bodyData);
 				break;
-			case 'alNot':
-				$subject['title'] = "特休不通過";
-				$subject['info'] = "特休時數錯誤";
-				$retData['body'] = "<p style='margin: 4px 0;'>您的 <span style='font-weight: bold;'>$data[name] 請假單</span>時數有誤,特別休假採半日計,僅能請上半天、下半天或全天,請至系統重新填寫請假單。</p>";
-				$retData['body'] .= "<p style='margin: 4px 0;'><a style='color: blue;font-weight: bold;' href='$in_url'>內部[查詢簽核表單]</a>";
-				$retData['body'] .= "<p style='margin: 4px 0;'><a style='color: blue;font-weight: bold;' href='$ex_url'>外部[查詢簽核表單]</a>";
-				$retData['body'] .= " <span style='color:red;'>*請務必連線協會網路才可登入</span></p>";
+			case 'alNot': // 特休不通過信 (以前特休最少要請半天)
+				$mailContent = $mailTpl->leaveAlnotMail($bodyData);
 				break;
-			case 'adminDelLeave':
-				$retData['body'] .= "<p style='margin: 4px 0;'>$data[name] <span style='font-weight: bold;'>$bodyTime $subject[type]</span> 已取消。</p>";
+			case 'adminDelLeave': // 休假被取消信
+				$mailContent = $mailTpl->leaveAdminDelLeaveMail($bodyData);
 				break;
 		}
-		$retData['subject'] = "【".$subject['title']."】 ".$subject['name']." (".$subject['type'].") - ".$subject['time'];
-		if( $type == 'alNot' ) $retData['subject'] = "【".$subject['title']."】 (".$subject['info'].")";
-		if( $type == 'adminDelLeave' ) $retData['subject'] = "【".$subject['type']."已取消】 ".$subject['name']." (".$subject['type'].")";
-		$retData['body'] .= "<hr><i>此信件為系統自動寄信。</i>";
-		return $retData;
+		return $mailContent;
 	}
 	function setSigningMailData($lsysID , $type) { // 信件收件人
 		$lsysID = $this->db_string($lsysID);

再來看這一小段:

--- a/data/sysLlibrary/mail/template/announcement/iread.tpl
+++ b/data/sysLlibrary/mail/template/announcement/iread.tpl
@@ -1,4 +1,4 @@
-{extends file="basedocument.tpl"}
+{extends file="base.tpl"}
# 看起來行數都沒改,好像都一樣
# 但其實是減了一行又加了一行,所以行數一樣
# 但內容不一樣

還有這個:

diff --git a/data/sysLlibrary/mail/template/leave/admindelleave.tpl b/data/sysLlibrary/mail/template/leave/admindelleave.tpl
new file mode 100644
# new file 這邊表示這個檔案是完全新增的,原本沒有
index 0000000..8b75745
--- /dev/null
+++ b/data/sysLlibrary/mail/template/leave/admindelleave.tpl
# 因為原本沒有,所以 --- 的來源檔是空的
@@ -0,0 +1,7 @@
+{extends file="base.tpl"}
+
+{block "content"}

最後這一段

 diff --git a/xxx/controllers/rootController.php b/xxx/controllers/rootController.php
 deleted file mode 100644
# deleted file 表示這個檔案整個被刪掉了
 index 4cfdcb0..0000000
 --- a/xxx/controllers/rootController.php
 +++ /dev/null
# 因為被刪了,所以目的變成空的,沒這個檔
 @@ -1,578 +0,0 @@
 -<?php
 -/**
 - * 內部系統前台

以上是儘量帶著大家去看不同情況的 git diff 所呈現出來的資訊。

我之前曾經按照 diff 內容複製一份目的檔然後修改回原本的檔案,再拿兩個檔案做比較自己寫一份 diff 內容出來

花了很多時間,但覺得很有趣,也對於 git diff 更了解一些。

想要原本的來源檔內容,其實不用這樣做啦,切換到原本的版本就有原本的檔案內容了。

我自己是覺得用這種簡單的描述可以把來源跟目的檔的差異表示的很清楚是一個很聰明的方式,

就像我們會覺得正規表示法很神一樣。

最後補充一下,git diff 可以用來比較的環境很多源,

像是 stash 改了什麼,某兩個 commit 相比的差異,

還有 stage 跟工作目錄的差異等等,都是可以觀察的。

以下面這篇文章做個小結,讓大家可以想把誰拿來跟誰比都不成問題。

git diff

突然出現的 submodule

昨天趁著假日,在更新公司內部系統使用到的套件,主要是寄信系統與 excel 相關模組。

更新的技術細節不是我們要說的重點,這裡發生了 git 相關問題才是我們要分享的部分。

都更新好後在正式環境下 git status 看一下是我的習慣

$ git status

On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
  (commit or discard the untracked or modified content in submodules)

	modified:   web_data/PhpSpreadsheet/maennchen/zipstream-php (modified content)
	modified:   web_data/PhpSpreadsheet/phpoffice/phpspreadsheet (modified content)
	modified:   web_data/PhpSpreadsheet/psr/http-client (modified content)
	modified:   web_data/PhpSpreadsheet/psr/http-factory (modified content)
	modified:   web_data/PhpSpreadsheet/psr/simple-cache (modified content)
	modified:   web_data/PhpSpreadsheet/symfony/polyfill-mbstring (modified content)

no changes added to commit (use "git add" and/or "git commit -a")

奇怪了,在 local 端怎沒這個狀況?

看到上面一行關鍵字了沒:

(commit or discard the untracked or modified content in submodules)

怎麼會有這個東西跑出來?找其中一個資料夾看看:

$ cd web_data/PhpSpreadsheet/symfony/polyfill-mbstring
$ ls -a
.   bootstrap.php  .git     Mbstring.php  Resources
..  composer.json  LICENSE  README.md
# 有看到 .git 資料夾吧,原來是套件自帶的
# 然後在這個資料夾下 git log 看一下
$ git log -1
commit a6977d63bf9a0ad4c65cd352709e230876f9904a (HEAD, tag: v1.18.1, tag: v1.18.
 0, origin/master, origin/HEAD, master)
 Author: Nicolas Grekas <nicolas.grekas@gmail.com>
 Date:   Tue Jul 14 14:35:20 2020 +0200

     Update CHANGELOG and branch-alias

# 對呀,這個提交的人不是我,而是軟體的開發或維護者

首先,我想到了一件事,就是我在用 composer 下載這個套件時有加 prefer-source

可能是這樣,會把包括原始的一些程式通通下載回來。

其實我當時主要是要看說明文件跟範例,這邊會建議大家,

如果是正式系統要用的,最好裝個比較乾淨的版本。

原本想說重新下載然後蓋掉再 commit 就好了,但原本存在的 .git 還是在。

趁這個機會來了解一下 git submodule 是什麼東西也不錯啦。

不過我目前用不到,只想把他們移掉,反正我還沒有 init 或 update submodule 就是了。

於是,我用了最笨也是最簡單的辦法,就是把上面列出來的資料夾裡的 .git 資料夾刪掉,

好吧,問題解決了。

提交落後

這是一個經常遇到的問題,也是因為有多分支與多人開發才會容易這樣。

通常我們就是跟遠端倉庫比較,因為倉庫當作是最新最穩定也是準備上正式環境的版本。

落後跟領先在語意上都很直覺了,問題是要怎麼處理,以及為什麼會這樣。

我們先來看一下落後的狀況。

$ git status
 On branch kevinlin
 Your branch is behind 'origin/kevinlin' by 5 commits, and can be fast-forwarded.
   (use "git pull" to update your local branch)

 nothing to commit, working tree clean

之前我們通常只把重點放在有沒有動過的紀錄在工作區,

有的話,就看是要 add 還是繼續編輯或放棄。

現在我們仔細看一下上面的訊息,

它告訴我們:

  • 現在在 kevinlin 分支
  • 但我們比遠端倉庫的 kevinlin 落後了 5 個提交

所以,不要只看沒有檔案動過的紀錄後就開始工作了。

這樣到時就需要 merge 了。

一般狀況下,已經發現,也還沒開始工作,就先拉回來同步再說。

$ git pull origin kevinlin
省略
  * branch            kevinlin   -> FETCH_HEAD
 Updating 7213a71..6946ab5
 Fast-forward
  xxx/controllers/donorsAdminController.php     |  5 ++++-
  xxx/models/donorsAdminModel.php               | 19 +++++++++++++++----
  xxx/static/js/manage_donation.js              |  9 +++++----
  xxx/template/admin/donors/manage_donation.tpl |  6 +++---
  4 files changed, 27 insertions(+), 12 deletions(-)
# 再看看
$ git status
 On branch kevinlin
 Your branch is up to date with 'origin/kevinlin'.

 nothing to commit, working tree clean

好了,沒事了,收工。那為什麼會發生這個狀況?

通常就是沒有即時更新,也就是遠端倉庫已經有人把新提焦丟上去了。

那如果是自己之前拉回來已經工作一陣子了呢?

沒關係,就照之前流程,自己都 commit 好了想推推不上去就拉回來 merge 後再推上去了。

提交領先

這是一個經常遇到的問題,也是因為有多分支與多人開發才會容易這樣。

通常我們就是跟遠端倉庫比較,因為倉庫當作是最新最穩定也是準備上正式環境的版本。

落後跟領先在語意上都很直覺了,問題是要怎麼處理,以及為什麼會這樣。

我們接著看一下領先的狀況。

$ git status
 On branch master
 Your branch is ahead of 'origin/master' by 2 commits.
   (use "git push" to publish your local commits)

 nothing to commit, working tree clean

它告訴我們:

  • 現在在 master 主分支
  • 但我們比遠端倉庫的 master 還領先了 2 個提交

也就是我們的 local 端比 remote 端還新?

通常是進行了開發後有新的提交,然後還沒推上倉庫。

也可能是 merge 完後比遠端多了 merge 的 commit 紀錄。

首先,我們先確認一下兩邊的狀況:

# 先看自己的紀錄
$ git log -4
commit 5a3890f4822f30aabf0eb28f13394a184db0388e (HEAD -> master)
Merge: 8c2392b 8cae1a1
Author: Kevin Lin <kevinlin@forblind.org.tw>
Date:   Tue Sep 15 16:15:44 2020 +0800

    Merge branch 'lineQR' of 192.168.1.8:/web/repo into lineQR

commit 8c2392ba4362b9c250c5d3e67e7c519a98cc3c48
Author: Kevin Lin <kevinlin@forblind.org.tw>
Date:   Tue Sep 15 15:48:20 2020 +0800

    1. 修改批次匯入 LINEPay QRCode 表投驗證資料(根據已更新之匯入檔)。
    2. 修改編輯捐贈資料頁 LINEPay QRCode 額外資訊欄標題。

commit 8cae1a19cd5af7493f85c66e0744e518ac79a2bb (origin/master, origin/HEAD)
Author: Kevin Lin <kevinlin@forblind.org.tw>
Date:   Tue Sep 15 15:48:20 2020 +0800

    1. 修改批次匯入 LINEPay QRCode 表投驗證資料(根據已更新之匯入檔)。
    2. 修改編輯捐贈資料頁 LINEPay QRCode 額外資訊欄標題。

commit e23be8c4ecdff08d346489a128400625a572d7b8
Author: Logo Kuo <logo@forblind.org.tw>
Date:   Tue Sep 15 13:50:37 2020 +0800

    1.修正 leaveModel 的 count() warning line 57, 279
    2.修正 documentModel 的 count() warning line 374

# 好,它告訴我們,領先兩個提交
# 也就是倉庫目前最新的應該是 commit 8cae1a1
# 注意一下 commit 那行的最後面還有 HEAD 狀態
# commit 8cae1a1 (origin/master, origin/HEAD)
# 表示遠端倉庫的 master 是在這個 commit 上面
# 有加 origin 的就是遠端
# 所以有當前的 master 跟遠端的 origin/master
# 也有當前的 HEAD 跟遠端的 origin/HEAD
# commit 5a3890f (HEAD -> master)
# 表示目前 local 端的 HEAD 跟 master 都在這個 commit 上
# 所以兩邊 master 現在不一樣,理論上最後應該讓他們一樣
# 那我們來確認一下遠端倉庫的紀錄
$ git log -2 origin/master
commit 8cae1a19cd5af7493f85c66e0744e518ac79a2bb (origin/master, origin/HEAD, master)
Author: Kevin Lin <kevinlin@forblind.org.tw>
Date:   Tue Sep 15 15:48:20 2020 +0800

    1. 修改批次匯入 LINEPay QRCode 表投驗證資料(根據已更新之匯入檔)。
    2. 修改編輯捐贈資料頁 LINEPay QRCode 額外資訊欄標題。

commit e23be8c4ecdff08d346489a128400625a572d7b8
Author: Logo Kuo <logo@forblind.org.tw>
Date:   Tue Sep 15 13:50:37 2020 +0800

    1.修正 leaveModel 的 count() warning line 57, 279
    2.修正 documentModel 的 count() warning line 374
# 所以三種東西都在同一個 commit 上是最標準的

至少目前結果如 git 所示,那我們就要來確認到底想要的是什麼結果。

例如,local 端是正確的,所以我們只要把它提交上去遠端倉庫即可。

而如果確定倉庫的才是最正確的版本,local 端的都不要了,

那麼你可以把目前的 HEAD 移到遠端倉庫的 origin/HEAD 上,也就是退版:

$ git reset --hard origin/HEAD
# 這樣多的那兩個 commit 就會消失

如果沒加 origin 是什麼意思?就只是把目前的 local HEAD 往前退一個版本而已喔。

當然你也可以直接把多的 commit drop 掉。

如果要退到比較之前的 commit 會建議直接給 commit id 比較保險

畢竟一直算 ~ 可能會算錯。

最後補充一個比較麻煩的狀況,就是兩邊的 commit 有點亂了,

就是有些要、有些不要,那我會建議在 local 弄好,

看是要用 cherry-pick 撿提交或是 rebase 修改、刪除提交等動作,

都弄好後用 git push origin master -f 硬推到倉庫去,當然萬不得已不要這樣做,

因為可能對其他的伙伴帶來困擾,他們必須條整手邊的 local 才行。

還有一個狀況,就是領先了倉庫的 commit 但是又推不上去,我們下一篇來解這個難題。


來源文章


最後更新:2020-09-23 17:03:39

From: 211.23.21.202

By: 阿慶