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

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

阿慶

阿慶圖像

2020-09-23 17:00:12

From:211.23.21.202

小設定與小功能

首先,同事原本要更新遠端分支 git remote update

結果打成 git update 就無意間發現這樣可以直接自動更新 git 程式。

接下來原則上只要點 install 按鈕,最後點 finish 按鈕就會自動裝好了。

原本的設定都沒跑掉,輸入 git version 可以看版本:

$ git version                                                                   
 git version 2.29.2.windows.2                                                    

最近公司的電腦換成桌機且安裝了 win10 作業系統,跟 git 相關的事情有一件。

就是在 git bash 裡面按左或右方向鍵移動游標時發現,游標所在的字元都會消失,像這樣:

$ git

# 如果把游標移到 i 上面,點顯器上面就會變成

$ g t

# 如果把游標移到 t 上面,點顯器上面就會變成

$ gi

當然游標的7,8點還在,這讓我常常刪錯字,看起來很不習慣,另外一個同事就沒這個問題。

調了一下設定,最後終於恢復正常。

  1. 在 git bash 中按 alt+空白鍵 開啟系統功能表,快顯功能表也有
  2. 使用上方向鍵找到 Options 按 enter 開啟設定
  3. 按 tab 鍵找到 Line 單選鈕
  4. 使用下方向鍵找到 Block 單選鈕
  5. 使用 tab 找到 Apply 套用 enter 與 Save 儲存設定 enter 就完成設定了

這些設定都是比較偏視覺效果的,像是顏色、透明度等等。

最後,有同事發現他在 git bash 無法按 alt+空白鍵也無法使用快顯鍵來呼叫功能表,發現是在安裝 git 選擇的設定參數問題。

基本上就是都選擇 unix 相關的參數,不要選 windows 的就沒問題了。

所以如果不能按的伙伴,看是要改設定檔還是重新安裝看看。

還有,就是按 insert 鍵可以直接在 git bash 貼上,當然如果你把 insert 鍵設定成 NVDA 功能鍵就要連按兩下了。

我使用的 git 版本為 2.29.2 windows.2

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 但是又推不上去,我們下一篇來解這個難題。

我的 commit 亂掉了

可能在多人開發的環境,加上很多分支的關係,

不小心沒有控管好,沒有合併好或者有人改變倉庫過往的提交等等,

這些行為都有可能讓我們的 git 分支出問題。

我們現在已經學過基礎,並且也開始實戰應用,那就來見招拆招了。

當然,你確定倉庫是最正確的,同時你也沒有要保留什麼提交或分支,可以直接砍掉重新 clone 一份回來就好了。

有點像是作業系統遇到問題就直接重灌或還原,完全不想管發生了什麼事。

然後很快的這樣的狀況又發生了,於是我們沒有學到一些更深入的知識,只會重灌跟還原而已。

好了,每個人可以選擇面對問題與解決它的方式。

我們這邊是要針對 case 擅用我們的 git 技能來解決問題。

一樣先看狀態:

$ git status
 On branch master                                                                
 Your branch and 'origin/master' have diverged,                                  
 and have 2 and 3 different commits each, respectively.                          
   (use "git pull" to merge the remote branch into yours)                        

 nothing to commit, working tree clean                                           

如果看到 diverged 發散這個字大概有點不妙了。

他告訴我們:

  • 你處在 master 主分支
  • 你的分支現在跟遠端倉庫的分支各走各的道
  • 分別有 2 與 3 個提交不同,需要處理

好,那我們再來看一下現在的 log 前幾條

$ git log -3 --oneline                                                          
 64de2b1 (HEAD -> master) 修改桌曆連結為2021桌曆                                 
 f4c41e4 公文建檔成功後顯示原本之檔名                                            
 1cc9971 self\settings.php 前台語後台登入驗證方法,在 header(Location;...) 加上 d
 ie 函數。                                                                       

從上面的 log 當中,我們沒有發現 origin/master 遠端倉庫的 master HEAD,

所以它才會說,現在我們各走各的路。

再來,我們看一下遠端倉庫的 log:

$ git log -4 --oneline origin/master                                            
 73a89ff (origin/master, origin/HEAD) 修改桌曆連結為2021桌曆                     
 506d863 公文建檔成功後顯示原本之檔名                                            
 fa19f83 Auth class 在 sql 數值加上單引號                                        
 1cc9971 self\settings.php 前台語後台登入驗證方法,在 header(Location;...) 加上 d
 ie 函數。                                                                       

我們又發現了幾件事:

  • 前面兩個 commit 內容看起來兩邊好像一樣,但 commit id 是不同的
  • 本地的第 3 個 commit 等於遠端的第 4 個 commit
  • 本地少了一個 commit 也就是遠端的第 3 個

那如果從 commit 沒看出這些差別怎麼辦?

或者不同的點非常的遙遠,例如幾十幾百個 commit 那種,我們不可能一一去比對。

沒關係,我們再來思考一下剛剛 local status 告訴我們的數字 2 and 3 吧。

通常我的經驗是:

  • 前面的數字表示本地跟遠端的差別
  • 後面的數字表示遠端跟本地的差別

其實就是主體不同而已,以這個案例來講,就是說:

  • 本地往前 2 個版本可以對到遠端的相同版本
  • 遠端往前 3 個版本可以對到本地的相同版本

通常遠端倉庫是對的情況下,我們就是處理本地端的部分就好。

那如果是要調整遠端倉庫的話,可以使用 git checkout origin/master 切到遠端分支處理。

這一次,我們也是排除暴力強拉強推法。

知道問題後就比較有方向了。

首先,最懶的方法就是 git pull origin master 把倉庫拉回來 merge 就是了

這樣看起來好像解決問題了,可是呢…

$ git pull origin master
省略

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

 nothing to commit, working tree clean                                           

變成本地領先了 3 個 commit 了,我們看一下紀錄:

$ git log -8 --oneline                                                          
 abfa4bb (HEAD -> master) Merge branch 'master' of 192.168.1.8:/web/repo         
 64de2b1 修改桌曆連結為2021桌曆                                                  
 f4c41e4 公文建檔成功後顯示原本之檔名                                            
 73a89ff (origin/master, origin/HEAD) 修改桌曆連結為2021桌曆                     
 506d863 公文建檔成功後顯示原本之檔名                                            
 fa19f83 Auth class 在 sql 數值加上單引號                                        
 1cc9971 self\settings.php 前台語後台登入驗證方法,在 header(Location;...) 加上 d
 ie 函數。                                                                       
 f7f48a0 修正需簽核假單導錯頁面問題                                              

果然多了 3 個 commit 也就是一個 merge 跟兩個之前我們發現的提交內容一樣但其實 commit id 不同的紀錄,

雖然原本漏掉的 commit 已經補回來了,但這樣會有三個問題:

  • commit 很醜,會有一直重複的狀況還有 merge
  • 因為領先 3 個 commit 以後推拉專案永遠都會一直 merge merge merge
  • 以後每次看紀錄的時候,前面那三個都會先看到才會看到實際新的 commit

當然這些你都不在意的話,我也沒意見,反正專案是完整的就是了。

以前我也是這樣解決這種問題的,當時其實也搞不清楚到底 pull 回來 git 都做了什麼。

反正專案完整就好了,現在我不這麼想,應該真正去了解問題然後排除它。

其實要使用拉回來的方式來處理也不是不行,但要接著把問題處理完,也就是多了 3 個 commit 這一塊。

由上面的 commit 可以知道:

commit 73a89ff (origin/master, origin/HEAD)

這是第 4 個 commit 也是 origin/master 遠端倉庫所在的 commit,

由這個 commit 以下看起來 commit 都是完整的,那我們就放棄前面多出來的 3 個 commit 就好了

$ git reset --hard 73a89ff                                                      
 HEAD is now at 73a89ff 修改桌曆連結為2021桌曆                                   

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

 nothing to commit, working tree clean                                           

$ git log -1 --oneline                                                          
 73a89ff (HEAD -> master, origin/master, origin/HEAD) 修改桌曆連結為2021桌曆     

看到 HEAD 三個狀態才放心,表示問題解決了。

故事還沒完,這邊,我們回到還沒有 pull 回來的狀況,我想提供另外一種解法:

$ git reset --hard 64de2b1                                                      
 HEAD is now at 64de2b1 修改桌曆連結為2021桌曆                                   

$ git status                                                                    
 On branch master                                                                
 Your branch and 'origin/master' have diverged,                                  
 and have 2 and 3 different commits each, respectively.                          
   (use "git pull" to merge the remote branch into yours)                        

 nothing to commit, working tree clean                                           

$ git log -3 --oneline                                                          
 64de2b1 (HEAD -> master) 修改桌曆連結為2021桌曆                                 
 f4c41e4 公文建檔成功後顯示原本之檔名                                            
 1cc9971 self\settings.php 前台語後台登入驗證方法,在 header(Location;...) 加上 d
 ie 函數。                                                                       

嘿嘿,沒錯,我們又回來了。

既然往前退兩個版可以先跟遠端倉庫找到相同的 commit,

那我們就先退回去:

$ git reset --hard HEAD~~                                                       
 HEAD is now at 1cc9971 self\settings.php 前台語後台登入驗證方法,在 header(Locat
 ion;...) 加上 die 函數。                                                        

$ git log -2 --oneline                                                          
 1cc9971 (HEAD -> master) self\settings.php 前台語後台登入驗證方法,在 header(Loc
 ation;...) 加上 die 函數。                                                      
 f7f48a0 修正需簽核假單導錯頁面問題                                              

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

 nothing to commit, working tree clean                                           

因為我們回到了與遠端倉庫一樣擁有的 commit,

但這個 commit 並不是 origin/master origin/HEAD 也就是並非倉庫最新的 commit,

git 告訴我們落後了 3 個 commit, 這時候把遠端倉庫拉回來補上這些漏掉的 commit 就解了:

$ git pull origin master
省略

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

 nothing to commit, working tree clean                                           

$ git log -2 --oneline                                                          
 73a89ff (HEAD -> master, origin/master, origin/HEAD) 修改桌曆連結為2021桌曆     
 506d863 公文建檔成功後顯示原本之檔名                                            

好了,我們總結一下,其實這兩種處理方式只是順序的不同而已啦。

我喜歡第二種解法的原因是每次看到 merge 都會覺得很煩,好像就是告訴我什麼東西弄錯了一樣,

心裡都會有點不踏實,第二種解法不會有 merge 出現。

希望你們也跟我一樣覺得解 git 問題就像是在玩遊戲一樣有趣。

我的 commit 不見了

事情發生在某天早上,利用還在蒸饅頭的時間,連進 local 的內部系統裡看看今天要改的地方。

順便測試一下昨天改的一個功能,卻發現沒有作用,這讓我大吃一驚。

清了 cache 還是一樣,打開程式碼看過,發現居然是舊的 code?

於是快速準備好後出發到公司解決這個問題。

通常,我都是在 logo 分支作業,完成後再與 master 合併。

為什麼我的 commit 不見了?先到 master 開始檢查。

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

nothing to commit, working tree clean
# 沒問題,是最新的,跟遠端倉庫同步
$ git log -4
commit b3b1b975e4e37da01374044cd659aab07be6d637
Author: Kevin Lin <kevinlin@forblind.org.tw>
Date:   Mon Sep 28 17:50:50 2020 +0800

    修正回傳捐款來源字串判斷錯誤

commit 6bc05415c7fa11e8fee0ba318023fbf8c5f28d47
Author: Logo Kuo <logo@forblind.org.tw>
Date:   Mon Sep 28 18:12:33 2020 +0800

    修改已建立但尚未派文之收文可更換說明與附件(tpl)

commit a679a42b6413d1a0299ee6c9cb79002794c049df
Author: Logo Kuo <logo@forblind.org.tw>
Date:   Mon Sep 28 13:48:25 2020 +0800

    修復寄送附加檔信件檔案被刪掉問題

commit fb031d94a9154d3f032b2af529314ad30107627f
Author: Logo Kuo <logo@forblind.org.tw>
Date:   Sun Sep 27 22:58:28 2020 +0800

    1.在派文管理將已派文單位改成表格方式呈現
    2.在派文管理之已派文單位可以取消派文

從上面四個 commit 看出,有三個是 28 號下午,一個是 27 號。

我記得 28 早上有寫 code 而且有推 commit 才對,感覺少了幾個 commit 了。

看看是不是有東西丟到 stash 了?

$ git stash
# 沒有

接下來,我們要查 reflog 那裡有完整的 log 資訊,

包括所有分支及所有動作。

所以在哪個分支查 reflog 的話都是一樣的。

當然,它有一些參數,像是顯示相對時間等。

如果想看較完整的 reflog 包括時間與提交者等,可以使用 git log -g

像我們這次就滿需要時間的,不過因為這樣資訊太多了,就不貼出來了,

實際上當時是有使用的。

$ git reflog
7bd0835 HEAD@{0}: checkout: moving from master to logo
# 從 master 切到 logo 分支
b3b1b97 HEAD@{1}: checkout: moving from master to master
# 從 master 切到 master 分支
# 應該是我想再次確認,從這裡也可以看出 git 的每個重要動作都有紀錄
b3b1b97 HEAD@{2}: cherry-pick: 修正回傳捐款來源字串判斷錯誤
# 在 master 撿 commit 合併
# 是從更之前的紀錄看出在哪裡做這個動作的
6bc0541 HEAD@{3}: checkout: moving from donLog to master
# 從 donLog 切到 master 分支
ae39fbb HEAD@{4}: checkout: moving from master to donLog
# 從 master 切到 donLog 分支
6bc0541 HEAD@{5}: cherry-pick: 修改已建立但尚未派文之收文可更換說明與附件(tpl)
# 撿 commit 合併
a679a42 HEAD@{6}: reset: moving to HEAD~
# 往前硬退一版
60c22fe HEAD@{7}: merge logo: Merge made by the 'recursive' strategy.
# 和併 logo 分支
a679a42 HEAD@{8}: checkout: moving from logo to master
# 從 logo 切到 master 分支
7bd0835 HEAD@{9}: commit: 修改已建立但尚未派文之收文可更換說明與附件(tpl)
# 推了一般 commit
76d02d8 HEAD@{10}: commit: 1.修正讓派文管理顯示派文異動收文
# 推了一般 commit

發現這個 commit 是剛才 master 沒有的

看到這裡,我也想起來似乎掉了不只一個 commit

而且我記得很清楚,當時都是有寫 commit 的

由上面的紀錄,我想問題大概發生在

master 合併 logo 後又退版,那時是因為有 merge 訊息,我想讓它沒有 merge 所以想從弄。

看了 logo 紀錄後,用 cherry-pick 來撿 commit 合併到 master,

但應該是沒注意看,只以為有一個 commit 要撿,結果只合了一個 commit 後就跑去修 bug 了。

然後我習慣合併過後的分支會砍掉,或者把 master 再合併回來分支。

於是我就漏臉了一些 logo 的 commit 了。

看起來要動刀,所以先把現在的 master 生一個分支出來備份。

$ git branch temp

$ git branch
   donLog                                                                        
   kevinlin                                                                      
   logo                                                                          
 * master                                                                        
   temp                                                                          

好,接下來我把 master 切到 27 號最後一個 commit,

因為我覺得 28 這邊有問題,那我們就重來。

$ git reset --hard fb031d9                                                      
 HEAD is now at fb031d9 1.在派文管理將已派文單位改成表格方式呈現 2.在派文管理之已
 派文單位可以取消派文                                                            
# 然後,我們切到 logo 分支,試著回到 28 的 commit 看看漏掉哪些沒合到。
$ git checkout logo

$ git log -4

commit 7bd0835df5c25bd424238392fb57a37a65ec6708
Author: Logo Kuo <logo@forblind.org.tw>
Date:   Mon Sep 28 18:12:33 2020 +0800

    修改已建立但尚未派文之收文可更換說明與附件(tpl)

commit 76d02d88928f6cded13cd17bbce72c93941e2e58
Author: Logo Kuo <logo@forblind.org.tw>
Date:   Mon Sep 28 15:43:03 2020 +0800

    1.修正讓派文管理顯示派文異動收文
    2.修正派文管理派文與取消功能無法獲得即時資訊問題

commit fb031d94a9154d3f032b2af529314ad30107627f
Author: Logo Kuo <logo@forblind.org.tw>
Date:   Sun Sep 27 22:58:28 2020 +0800

    1.在派文管理將已派文單位改成表格方式呈現
    2.在派文管理之已派文單位可以取消派文

commit 8ad2824291ab8cc1d174de8446268101dfeb2dc2
Author: Logo Kuo <logo@forblind.org.tw>
Date:   Sun Sep 27 17:50:03 2020 +0800

    在派文管理新增刪除收文的所有收文部門紀錄

跟還沒退版 master 也就是 temp 比起來多了 76d02d8 這個 commit,

看來我那時還沒有把 logo 刪掉也還沒有 pull master 回來

但還是覺得有少 commit,所以我們必須把 logo 退版

看看退過去後還有沒有之前漏掉的 commit

$ git reset --hard 76d02d88
# 就退到漏掉的這個 commit 
 HEAD is now at 76d02d8 1.修正讓派文管理顯示派文異動收文 2.修正派文管理派文與取消
 功能無法獲得即時資訊問題                                                        

退回去後看 log 還是一樣,這樣就確定只漏掉一個 commit,

之所以會覺得還有漏,是因為那個 commit 其實做了三件事,但我寫在同一個 commit 裡面。

而且其實我有下 show 去查 diff 確認內容沒問題。

那為什麼會記得是早上在寫 code 呢?原來是早上寫一寫還沒推,

然後系統突然有使用者回報問題,就先把手上的東西丟到 stash 切到 master 去修 bug 了。

修好後先推上去,已經是下午,接著才又回去開發,所以真真 logo 推 code 已經是下午了。

好,那就切回 master 然後先撿這個 commit 來合併。

$ git cherry-pick 76d02d88                                                      
 [master bbb960e] 1.修正讓派文管理顯示派文異動收文 2.修正派文管理派文與取消功能無
 法獲得即時資訊問題                                                              
  Date: Mon Sep 28 15:43:03 2020 +0800                                           
  3 files changed, 6 insertions(+), 4 deletions(-)                               

# 在看一下 logo 還有沒有漏掉的 commit 
$ git log -2 logo
# 因為我在 master 所以想看 logo 的 log 才要這樣打
commit 76d02d88928f6cded13cd17bbce72c93941e2e58
Author: Logo Kuo <logo@forblind.org.tw>
Date:   Mon Sep 28 15:43:03 2020 +0800

    1.修正讓派文管理顯示派文異動收文
    2.修正派文管理派文與取消功能無法獲得即時資訊問題

commit fb031d94a9154d3f032b2af529314ad30107627f
Author: Logo Kuo <logo@forblind.org.tw>
Date:   Sun Sep 27 22:58:28 2020 +0800

    1.在派文管理將已派文單位改成表格方式呈現
    2.在派文管理之已派文單位可以取消派文

# 在看 temp log
# 因為他是之前的 master 
# 所以我們撿完漏掉的 commit 後再把後來 master 的 commit 也都撿回來
$ git log -4 temp

commit b3b1b975e4e37da01374044cd659aab07be6d637
Author: Kevin Lin <kevinlin@forblind.org.tw>
Date:   Mon Sep 28 17:50:50 2020 +0800

    修正回傳捐款來源字串判斷錯誤

commit 6bc05415c7fa11e8fee0ba318023fbf8c5f28d47
Author: Logo Kuo <logo@forblind.org.tw>
Date:   Mon Sep 28 18:12:33 2020 +0800

    修改已建立但尚未派文之收文可更換說明與附件(tpl)

commit a679a42b6413d1a0299ee6c9cb79002794c049df
Author: Logo Kuo <logo@forblind.org.tw>
Date:   Mon Sep 28 13:48:25 2020 +0800

    修復寄送附加檔信件檔案被刪掉問題

commit fb031d94a9154d3f032b2af529314ad30107627f
Author: Logo Kuo <logo@forblind.org.tw>
Date:   Sun Sep 27 22:58:28 2020 +0800

    1.在派文管理將已派文單位改成表格方式呈現
    2.在派文管理之已派文單位可以取消派文

$ git cherry-pick a679a42b6 b3b1b975e 6bc0541
# 所以前面三個 commit 現在沒有,要撿回來
 [master b59af14] 修復寄送附加檔信件檔案被刪掉問題                               
  Date: Mon Sep 28 13:48:25 2020 +0800                                           
  2 files changed, 1 insertion(+), 11 deletions(-)                               
 [master c62c834] 修正回傳捐款來源字串判斷錯誤                                   
  Author: Kevin Lin <kevinlin@forblind.org.tw>                                   
  Date: Mon Sep 28 17:50:50 2020 +0800                                           
  1 file changed, 1 insertion(+), 1 deletion(-)                                  
 [master fbfb1a4] 修改已建立但尚未派文之收文可更換說明與附件(tpl)                
  Date: Mon Sep 28 18:12:33 2020 +0800                                           
  2 files changed, 51 insertions(+), 16 deletions(-)                             

看一下最後弄好的 master 狀態

$ git status                                                                    
 On branch master                                                                
 Your branch and 'origin/master' have diverged,                                  
 and have 4 and 3 different commits each, respectively.                          
   (use "git pull" to merge the remote branch into yours)                        

 nothing to commit, working tree clean                                           

# 因為移來移去,所以肯定跟遠端倉庫不同了
# 我們現在確定手上的 master 是最新最完整的,
# 那最快的方式就是硬推蓋掉倉庫,不然以後就有拉不完的 merge 了
# 當然,你也可以切到遠端 origin/master 去調整
# 但就算調好了其他使用者一樣會有問題
# 而且正式環境有點急,所以這邊選擇直接蓋
$ git push origin master -f

 logo@192.123.3.2's password:                                                    
 Enumerating objects: 47, done.                                                  
 Counting objects: 100% (47/47), done.                                           
 Delta compression using up to 2 threads                                         
 Compressing objects: 100% (31/31), done.                                        
 Writing objects: 100% (31/31), 3.59 KiB | 44.00 KiB/s, done.                    
 Total 31 (delta 21), reused 0 (delta 0)                                         
 To 192.123.3.2:/xxx/yyyy                                                        
  + b3b1b97...fbfb1a4 master -> master (forced update)                           

大功告成了。
從結論上來說,直接從 master 撿回漏掉的 logo commit 就好,

但那就是結果論,正式環境不能有閃失,所以會用比較安全的方式來處理。

我們不能漏掉任何的 commit 而且那有可能不是你寫的。

好了,繼續寫程式去。

補充:

  • reflog 有時間限制,也就是太舊的 log 會被清掉,那就真的找不到了,因此發現問題最好趕快處理
  • reflog 跟 git log 一樣,顯示的方式是最上面最新,所以我習慣從中間找一個有印象的 commit 開始往上看

還沒寫完但要關門了

這個情境跟之前提過的 stash 是不同的,stash 是說專案開發到一半要去修 bug 或要切到其他分支去處理事情。

等處理好後再把 stash 接回來繼續作業,避免未完成品必須要先推上去的玕尬狀況。

但這次真的是非推上去不可,昨天寫 code 到九點多還想把最後一部分寫完時,關門的同仁不能等我了,

雖然我也有拿鑰匙啦,不過想說也實在有點晚了,明天休一整天還可以慢慢在家裡寫,就只好先推上去了。

不推不行,因為我公司筆電沒開遠端,不推上去回家就沒搞頭了。

commit 訊息就加個未完待續之類的提醒自己,重點是回家之後要怎麼繼續呢?

首先,當然是先把專案拉回來,看到最後一個未完待續的 commit 以後,把它丟回工作目錄就可以繼續寫了。

等到都寫好後再推上去就行囉。

這樣還是保持一個 commit 且是完整的工作內容。

$ git log -2 --oneline                                                          
 501ace2 (HEAD -> logo, origin/logo) 修正收文修改頁面顯示附件(js未完)            
 25a4c4d (origin/master, origin/HEAD, master) 讓前後臺登入後可以顯示登入者名字   

最後,也就是最上面的 commit 還沒完成

所以,我們是要再往前退一個版本,這樣退回的那個版本之後的紀錄全部都會被丟回工作目錄

也就是說,現在如果有 7 個 commit, 我選擇退到第 3 個 commit 後,

那麼 4, 5, 6, 7 這四個 commit 的紀錄全部會被丟到工作目錄,

假設你想合併這四個 commit 推成一個 commit 那可以這樣做。

 $ git reset 25a4c4d                                                             
 Unstaged changes after reset:                                                   
 M       xxx/controllers/documentController.php                                  
 M       xxx/models/documentModel.php                                            
 M       xxx/self/settings.php                                                   
 M       xxx/static/js/assign_document.js                                        
 M       xxx/template/document/assign_document.tpl                               

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

 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:   xxx/controllers/documentController.php                      
         modified:   xxx/models/documentModel.php                                
         modified:   xxx/self/settings.php                                       
         modified:   xxx/static/js/assign_document.js                            
         modified:   xxx/template/document/assign_document.tpl                   

 no changes added to commit (use "git add" and/or "git commit -a")               
# 果然都被丟回工作目錄了
$ git log -1 --oneline                                                          
 25a4c4d (HEAD -> logo, origin/master, origin/HEAD, master) 讓前後臺登入後可以顯 
 示登入者名字                                                                    
# 沒錯,停在之前完成的 commit 上了

接下來就是一般寫程式的流程,最後按一般步驟提交上去就好了。

斷頭了

我們在 git-21 救回分支 一文中已經介紹過如何救回分支的方法。

這次我們要來個分解動作,主要是想介紹一個 detached 「斷頭」的狀態讓大家認識。

我們已經知道 HEAD 通常會指向一個分支,而分支會指向一個 commit 這樣。

但如果有一個 commit 沒有分支指向它,它就沒有分支,也就是呈現 `` 斷頭狀態。

現在我們在 master 刪除 logo 分支,然後用 reflog 找到最後切過去的 logo commit 切回去看看。

$ git branch                                                                    
   kevinlin                                                                      
   logo                                                                          
 * master                                                                        

$ git branch -d logo                                                            
 Deleted branch logo (was fb031d9).                                              

$ git branch                                                                    
   kevinlin                                                                      
 * master                                                                        

$ git  reflog -3                                                                
 b3b1b97 (HEAD -> master, origin/master, origin/HEAD) HEAD@{0}: checkout: moving 
 from logo to master                                                             
 fb031d9 (origin/logo) HEAD@{1}: checkout: moving from master to logo            
 b3b1b97 (HEAD -> master, origin/master, origin/HEAD) HEAD@{2}: checkout: moving 
 from logo to master                                                             
# 看起來是第二個

$ git checkout fb031d9                                                          
# 之前我們用 checkout 來切換分支,現在是直接切換到某個 commit
 Note: switching to 'fb031d9'.                                                   
# 切到這個 commit 的警告                                                                                 
 You are in 'detached HEAD' state. You can look around, make experimental        
 changes and commit them, and you can discard any commits you make in this       
 state without impacting any branches by switching back to a branch.             
# 在這個斷頭狀態下,你可以像平常一樣提交,當然你也可以切換到其他分支而放棄這裡                                                                                 
# 提醒一下,在斷頭的 commit 比較不容易被找到,除非是 reflog 
# 同時,在這裡做的動作不會影響到任何分支
 If you want to create a new branch to retain commits you create, you may        
 do so (now or later) by using -c with the switch command. Example:              

   git switch -c <new-branch-name>                                               
# 你可以創建一個分支來指向這個提交,使它脫離斷頭的狀態
# 新版的切換都會用 switch 舊版的是 checkout
# 同時我們也學到 checkout -b 相當於 switch -c 這些在 [後悔藥有得買1](https://www.nvda.org.tw/discussion/ui=100200tm=1969753631) 曾經提過。
# 我們在之前
 Or undo this operation with:                                                    

   git switch -                                                                  
# 或者是你也可以下指令來回到之前還沒斷頭的最後狀態                                                                                 
 Turn off this advice by setting config variable advice.detachedHead to false    
# 如果你覺得上面這些提示很煩人,可以去設定裡把 advice.detachedHead 設定成 false                                                                                  
 HEAD is now at fb031d9 1.在派文管理將已派文單位改成表格方式呈現 2.在派文管理之已
 派文單位可以取消派文                                                            
# 好吧,我儘量翻譯了,實際上這一句才是最重要的 
# 不過我不建議新手關掉提示設定,因為有可能會忘記指令或一下子很慌張之類的。

看一下狀態:

$ git status                                                                    
 HEAD detached at fb031d9                                                        
 nothing to commit, working tree clean                                           
# 真的是斷頭中了                                                                                 

或許有伙伴會問,那用 checkout 跟 reset 來切 commit 有什麼不同?

大家要注意,這是不同的概念, reset 是會移動現在分支裡的 HEAD 到其他的 commit

你做的任何動作都是在這個分支底下的行為。但 checkout 比較像是把整個分支切到某個 commit 上面去。

像我們切換的 fb031d9 實際上 master, origin/logo 等都有這個提交存在,

所以用 reset 不會斷頭,但 checkout 過去時,因為本地的 logo 最後一個 HEAD 是指像它,可是 logo 已經被我們刪掉了,

使用 reflog 可以看到,遠端的 origin/logo 雖然也指向這個 commit,但本地沒這個分支,就會顯示斷頭了。

那我們繼續討論,使用 switch 跟 checkout 來切 commit 有什麼不同?

switch 是針對分支,如果是下 switch fb... 那串 commit id 它會以為你要切到這個分支,但實際上你沒有這個分支。

不過你可以使用 switch -d fb 那串來切到這個斷頭的 commit 上面,因此 switch 還是可以完全取代 checkout 指令。

那要處理斷頭就很簡單了,看是要開個分支指向它還是跑錯地方想回家都可以,

這部分之前的文章提過,這邊就不說了。

而斷頭發生的另外時機就是處在 rebase 當中。

最後,補上這些關於斷頭的文章。

git 的斷頭是什麼

合併分支實況

背景與做法

我現在有兩個分支,分別是 dev 開發分支與 master 主分支

通常我在 dev 分支寫程式,確定沒問題後就會到 master 主分支 cherry-pick 我要的 dev 提交回來

不過有時總有意外,例如忘記切回 dev 就在 master 開始寫,或者是很急也很簡單的 bug 修正也會直接在 master 修正

另外,dev 做了很多提交,但有些提交可能暫時先不合併到 master,也許是還沒測試或還沒寫好之類的

所以,時間一長,master 跟 dev 就不會同步了。但我們會希望 dev 以正式環境的 master 來繼續開發下一個任務,這時候就會有兩種做法:

  1. 把 dev 分支刪掉,從 master 重新建一個 dev 分支出來
  2. 在 dev 拉 master 回來合併

做法與分析

第一種方法最簡單,但不實際,像是 dev 有 stash 還沒做完,或者有一些提交還沒測試好,就不能刪掉分支

第二種方法只是把 dev 缺乏的 master 提交補上,當然,有時候合併沒那麼順立,這就是我們今天要實做的重點。

基本上,我們是在 dev 把 master 分支從遠端倉庫拉回來合併。

也就是 pull, 其實就是做 fetch 跟 merge 的動作,之前都有提過,那就直接實做。

拉回

# 確定自己在 dev 分支
$ git branch                                                                    
 * dev                                                                           
   don_accton                                                                    
   master                                                                        
# 從遠端倉庫拉回 master
$ git pull origin master
password: # 打密碼
Auto-merging sys/static/js/admin_leave_data.js
# 自動合併這個檔案
CONFLICT (content): Merge conflict in sys/static/js/admin_leave_data.js
# 發生內容衝突,合併不了
Auto-merging sys/models/leaveAdminModel.php
CONFLICT (content): Merge conflict in sys/models/leaveAdminModel.php
# 狀況同上
Automatic merge failed; fix conflicts and then commit the result.
# 自動合併失敗,請修好它重新提交

觀察

看來訊息還不是很完整,讓我們再仔細確認一下狀況:

$ git status
On branch dev
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Changes to be committed:
	modified:   sys/self/leaveCalculation.php
# 這個沒問題,已修改但尚未提交,因為我們合併發生衝突了
Unmerged paths:
  (use "git add <file>..." to mark resolution)
	both modified:   sys/models/leaveAdminModel.php
	both modified:   sys/static/js/admin_leave_data.js
# 就是這兩個檔案發生合併衝突了

簡單講,就是後悔可以打 git merge --abort 也適用於 cherry-pick, 那就回到還沒 pull 的狀態

如果要解決衝突,那就開啟衝突的檔案來修改

解決衝突

我用 notepad++ 開啟 leaveAdminModel.php, 搜尋 <<<

<<<<<<< HEAD
					$where .= " and li.itemsName = '".$data['itemsName']."'";
=======
					$where .= " and li.itemsName like '".$data['itemsName']."%'";
>>>>>>> 2c428fc952f29efcf30890a82e4761fccf45631c
  • <<< 到 === 之間的內容是 dev 原本的檔案內容
  • === 到 >>> 之間的內容是 master 那邊新的檔案內容

接下來就是要確定最後想改成怎樣,通常是新的留下,舊的刪掉

反正改的人要很清楚就是了,記得那些多餘的符號都要刪掉喔

這邊我們不講程式碼,直接秀修改的結果,我留下 master 版的程式碼:

					$where .= " and li.itemsName like '".$data['itemsName']."%'";

接著繼續搜尋 <<< 看看,因為一個檔案可能會有多個衝突點

都處理好後存檔離開

另一個檔案也是一樣的做法,這個檔案我改比較多,就不詳列了

當然,你也可以直接修改成不是這兩個版本的最終版,反正都會重新提交

都處理好後就進入我們熟悉的 git add .git commit

git add .git status 可以看到目前是處在解決衝突的狀態

其中,在 git commit 會有一個預設的訊息:

Merge branch 'master' of 192.168.0.12:/xxxx/yyyy into dev

當然可以改成我們想要的 commit 訊息

合併 master 
# 存檔繼續會看到
$ git commit                                                                    
 [dev a1fb85b] 合併 master                                                       
# 再次確認
$ git status                                                                    
 On branch dev                                                                   
 nothing to commit, working tree clean                                           
# git log -1
 commit a1fb85bb1d2fbbb4c4e83064b852aa854da5394e (HEAD -> dev)                   
 Merge: 0a867c0 2c428fc                                                          
 Author: Logo-Kuo <logo@forblind.org.tw>                                         
 Date:   Fri Dec 25 11:03:06 2020 +0800                                          

     合併 master                                                                 

好了,合併完成

補充

在 dev 中,原本 master 提交的 commit 訊息也還在,再加一個我們剛剛推的 合併 master 提交

如果在 commit 時加個參數 --no-edit 就不會要你寫 commit 訊息,但還是會有預設的,跟原本預設的 merge 內容不同

這次多了你處理衝突的檔名資訊,如下:

 commit fc06b254d95eb4c81ee18cc14e5574102837c13c (HEAD -> dev)                   
 Merge: 0a867c0 2c428fc                                                          
 Author: Logo-Kuo <logo@forblind.org.tw>                                         
 Date:   Fri Dec 25 11:41:27 2020 +0800                                          

     Merge branch 'master' of 192.168.0.11:/xxxx/yyyy into dev                     

     # Conflicts:                                                                
     #       sys/models/leaveAdminModel.php                                      
     #       sys/static/js/admin_leave_data.js                                   

其實處理衝突的檔案資訊原本就在,只是 git commit 時是被 # 註解掉了而已

那如果你完全不想要有這個 merge 的訊息,可以考慮使用 git pull origin master --rebase

為什麼說是「考慮」呢?因為我們知道 rebase 會把所有的 commit 重做一遍,所以假設兩個分支差很多

中間有一些 merge 之類的,那你 rebase 就要處理所有的衝突,但 merge 只要解決這次的衝突,所以使用時機自己評估

我自己是非不得已不會使用 rebase, dev 分支有合併訊息是很正常的,讓正式主分支保持乾淨就好了

衝突的由來

針對這個問題,我之前也一直無法理解,就是怎樣的狀況會發生衝突。

當然,不同人相互寫同一個分支拉下推上的會衝突不意外,但怎樣叫做衝突?

我們以這次 dev 合併 master 的例子來說,事實上,master 我修改的檔案不只兩個,那為什麼只有兩個檔案發生衝突?

其實是 git 會根據最近的提交去比對,假設最近的提交改了 a 檔案的第 5 行

那在合併時新的提交也修改到 a 的第 5 行,這樣 git 不知道誰才是最終需要的版本,就會發生衝突,

然後列出衝突點讓我們決定最終的版本。

我以前還以為 git 是會自動以最新日期來直接蓋掉舊的衝突點,但預設應該不是這樣

但不知道有沒有這樣的設定就是了


來源文章


最後更新:2020-12-30 10:56:22

From: 211.23.21.202

By: 阿慶