Git

Git 介紹

Git 是一種版本控制系統(Version Control System),可以紀錄開發時各階段版本的檔案目錄結構(新增、修改、刪除內容的變化),可以隨時切換到過去某個版本時候的專案狀態,它很像 RPG 角色扮演遊戲的紀錄檔,在前進未知能力 BOSS 或關卡前,習慣會先記錄一下,全滅後還可以回到紀錄時的狀態重新挑戰。Git 是分散式的版控系統(Distributed Version Control),好處是即使沒有網路,也可以在本機電腦使用,等到有網路時在進行同步操作。

安裝 Git

  • 安裝環境 Git Download Git
  • VSCode 安裝 Git History、GitLens、Git Graph

Git 原理

  1. git add 把檔案從工作目錄移至索引暫存區(Staging Area)。
  2. git commit 把暫存區的內容移至儲存庫(Repository)。

Git 就像倉庫貨物的控管流程,可以分為卡車卸貨區(工作目錄)、倉庫前的小廣場(索引暫存區)、倉庫(儲存庫),首先你會在卡車卸貨區(工作目錄)進行整理、打包、分類、移除瑕疵貨物(檔案新增、編輯、刪除…),接著將整理好的貨物入庫前,會將其先放到小廣場(索引暫存區)(git add .**),最後,紀錄貨物是甚麼用途、分類、明細(git commit**),將貨物送進倉庫裡(Repository)。

初始化與提交

  1. 新增專案目錄,並設定為 VSCode 專案目錄,打開終端機,確認終端機顯示該目錄路徑。
  2. 鍵入git init初始化 git,看到建立一個 .git 隱藏目錄表示成功,版控全部只靠 .git 目錄在做事,如果把 .git 目錄移除,Git 就對這個目錄失去控制權。
  3. 若終端機顯示其他目錄路徑,可以使用右側垃圾桶清掉即可變為目前專案目錄的路徑。
  4. 可以設定作者、EMAIL:
指令 說明
git config –global user.name “KaiFu Chang” 設定全域作者
git config –global user.email “powerkaifu@gmail.com 設定全域 EMAIL
git config –local user.name “powerkaifu” 設定區域作者
git config –local user.email “powerkaifu@gmail.com 設定區域 EMAIL

PS:git config --global --edit,可以開啟 global 的 .gitconfig 檔。

  1. 每次修改過檔案後(新增、編輯、刪除),都必需執行 add 的動作,可以使用git status來檢查檔案的狀態。
    在終端機以紅字顯示,表示尚未 add 的檔案,它們會有幾種狀態:

    • Untracked files(未追蹤):新增的檔案,尚未被 git 追蹤控管。
    • Modified:編輯過的檔案。
    • Deleted: 刪除的檔案。

    輸入git add .後,並再次輸入git status,檔案顯示綠色,表示已經增加到暫存區(Staging Area),只有在暫存區的檔案,才能 Commit 後存進儲存庫。最後輸入提交訊息 git commit -m "",””裡面為想 Commit 的訊息,這樣就完成一次 git 的操作(add 與 commit)。完成第一次 add 與 commit,Git 才會產生 master 分支。

    再次檢查狀態,出現「nothing to commit, working tree clean」表示沒有東西可以提交,工作目錄為空。若想將 commit 後的檔案返回工作目錄(Working Directory),可輸入git reset head~,等同git reset ~head --mixed模式。若想回到暫存區進行 commit,可以輸入git reset head~ --soft

    PS:只輸入 git commit 指令會進入文字編輯模式,要先按任意鍵 進行編輯,編輯完按 esc 輸入 :wq 退出。

提交(commit)

當每次提交一些東西時,Git 會產生一個快照,將所有檔案放置在一個物件記錄著,可以輸入git log(歷史紀錄) 會看到提交後 40 個字元長 16 進位 的 SHA-1(Secure Hash Algorithm 1)雜湊演算碼,這可以做為提交後的識別碼,使用指令配合 commit 識別碼,一般只需要 6~8 個數字即可辨識。

上傳至遠端儲存庫(GitHub)

  1. 進入 GitHub,新建一個專案 test。

  2. 本地端建立工作目錄,並且初始化(git init),接下來讓遠端與本地端綁定,如下:

    git remote add 遠端儲存庫名稱(可以自訂),綁定遠端儲存庫的網址
    例如:git remote add origin https://github.com/powerkaifu/test.git
    一般遠端專案位置會預設 origin,可以自訂名稱,它代表後面那串網址。

  3. 可以檢查隱藏目錄 .git 裡面的 config,如果有 [remote “origin”] 表示綁定成功
    [remote “origin”]
    url = https://github.com/powerkaifu/test.git

  4. push 上傳至遠端 git push -u origin master

使用 GitHub Pages

第一種方式:

  1. 進入 Settings,找到 GitHub Pages。
  2. Source 選擇哪個分支要成為 gh-pages。

第二種方式:

  1. 在本地端的 master 分支上,git 建立 gh-pages 分支。
  2. git checkout gh-pages 切換到該分支。
  3. git push origin gh-pages,將其 push 上去,GitHub 會自動開啟 gh-pages 分支靜態網頁的功能。

Push

git push -u origin 分支名稱 推送至遠端 origin 儲存庫的分支名稱

  • -u 會推到預設的遠端儲存庫服務,也可以不打 -u 直接推送指定的遠端儲存庫,git push origin master
  • origin 可以修改它的遠端儲存庫名稱,一般都使用 origin,例如:git push -u develop master
  • master 為分支名稱,可以對指定的分支 push。

git push -f 要注意使用,這是暴力推上去,如果協同作業可能會影響其他人,GitHub 有保護機制,可進入 settings 頁面,左側 Branches,選擇分支,預設是 master,

Pull

Pull 與 Fetch

git pull = git fetch + git merge,使用 pull 比較快。
git fetch 必須在遠端進度比本地端還要新使用,使用 git fetch 可以拉回遠端進度,但不會合併(此時樹圖遠端會領先本地端),因此自己還是要與遠端進行合併 git merge orgin/分支名稱,將使用快轉模式合併(無小耳朵)。

使用時機:

  • 當本機儲存庫沒有變更,或本機儲存庫和共享儲存庫上確認沒有衝突的話,就可以安心使用 pull。
  • 非上述情況時,最好先使用 fetch 取得變更後再進行 merge 會比較安全。因為 pull 會自帶 merge,當有其他人同時編輯時,就容易發生問題。

推不上解決方案

使用在協同專案時,大家都在各自的分支作業,有人會先做完先上傳到遠端 remote,此時如果要 push 上去,由於本地端的專案比較舊 push 不上去,因此可以使用 git pull –rebase 將進度拉回來,而且不需要多一次 commit 的動作。

第一招: 先拉下來,再推上去。使用git pull --rebase,拉回來不需要 commit,再 push 上去。
第二招:git push -forcegit push -f,暴力硬推,會把其他人的內容覆蓋掉,要特別小心。

HEAD 時光機

HEAD 指標指向一個分支,也就是目前所在的分支,它很重要,因為時常忘記 HEAD 在哪個分支,在開發時要注意目前是在哪個分支。
HEAD 在檔案路徑 .git/HEAD 此檔找到,它指向目前分支的 commit(40 個 SHA-1 字元的雜湊碼),
以下指向 .git/refs/heads/master 這個分支,也就是說 .git/refs/heads/ 該目錄存放了所有分支檔案,砍了分支檔案就會不見,也可以更名。

// .git/HEAD,記錄了目前HEAD指標指向誰,目前HEAD是在 master 分支上
ref: refs / heads / master

// .git/refs/heads/master
bc3037b519ea2e899649eacc62df3ee584a25941

切換 HEAD 指標

  • 先使用 git log 查詢 commit 碼。
  • git checkout commit碼 可以將 HEAD 指標回到該 commit 碼的時間節點,commit 碼至少要輸入四個。

Reset

使用 Reset 的觀念(還原、前往某個 Commit 的意思),不管是用甚麼模式進行 Reset(即便是–hard),Commit 代碼會因為 Reset 到哪裡,中間那一大段就全部消失不見。Reset 其實代表回到過去某個 commit 點,中間那一大段只是隱藏起來看不到,可以使用 git reflog 查詢這些 commit 代碼,並使用git reset commit 代碼返回。

HEAD 指的是當前指標,它也有縮寫,可以用 @ 來代替,git reset @~ 等於 git reset head~。

指令 說明
git reset head~ --soft 檔案還原至暫存區狀態,保留修改完檔案,讓使用者 Commit。
git reset head~ --mixed 等同預設值 git reset HEAD~,檔案會還原至工作目錄,保留修改完檔案。
git reset head~ --hard 直接放棄暫存區、工作區的檔案。

--soft--mixed 都會保留還原的節點,保留修改完的檔案,差異在於 --soft 是在暫存區(commit 動作),--mixed 是在工作目錄(add 與 commit 動作)。
--hard 會直接拆掉 commit 節點,回到拆掉節點前的狀態。

指令 說明
git reset head~ 還原父節點,1 個~表示 1 次
git reset head~2 還原兩次父節點。

還原 commit,原理與還原 HEAD 一樣,將 head 替換成 commit 代碼。

指令 說明
git reset commit 代碼 --soft 還原至指定的 commit 碼,檔案會還原至暫存區(需 commit)。
git reset commit 代碼 --mixed 還原至指定的 commit 碼,檔案會還原至工作區(需 add、commit)。
git reset commit 代碼 --hard 還原至指定的 commit 碼,並且拆掉整個 commit。

還原 ORIG_HEAD,紀錄一些比較危險的操作(merge、rebase、reset),Git 就會把 HEAD 的狀態存放在這裡,可以跳回危險操作之前的狀態。

指令 說明
git reset orig_head --hard 返回危險記錄點的狀態,可以在 .git 目錄查到 orig_head 目前指向哪個 commit 碼。

刪除與救回

刪除檔案對 Git 而言只是一個狀態,會在工作區標記刪除記號「-」,告訴 Git 要做刪除檔案的動作,需 add 到(暫存區),並且 commit 後才能完成刪除,檔案還是救得回來,使用 git reset head~ --hard 來還原。

Branch

分支 = 分身,開分支等於開分身,對於使用 Git 來說,它很重要。在開發過程中,藉由分支來實驗某些功能,而不會破會原本正常運作的程式碼,等待成功確認沒問題再進行合併。分支也是 40 字元的雜湊碼,標記指向哪一個 Commit,不管是快轉、非快轉模式,合併過後都表示「你的內容都被已經我接收」。

  • git merge commit代碼,雖說合併分支,其實只是合併另一個 Commit。
指令 說明
git branch 列出本地分支
git branch --remote 列出遠端分支
git branch -a 列出本地與遠端所有分支
git branch 分支名稱 新增分支
git branch -d 分支名稱 刪除分支
git branch -m 舊分支名稱 新分支名稱 修改分支名稱
git checkout 分支名稱 切換分支
git checkout -b 新分支名稱 建立分支,並且切換到該分支,配和 git checkout 到某分支上
git checkout -b 新分支名稱 commit 碼 直接在 commit 碼上建立分支
git checkout . 救回刪除檔案或修改檔案後悔回復上一次 commit 紀錄狀態
git merge 分支名稱 --no-ff 分支合併,會建立合併提交,產生小耳朵紀錄,推薦使用
git merge 分支名稱 快轉合併(fast-forward merge),直線式合併,不推薦

PS:git checkout 後面接分支名稱為切換分支,如果接檔名或路徑,則會回復上一次 commit 紀錄狀態。

回到過去建立分支

第一種方式:
先回到過去的 commit 時間點,git checkout commit碼,再建立新分支git checkout -b 新分支名稱

第二種方式:
不想回到過去,直接建立git branch 新的分支名稱 commit碼,commit 碼為過去時間點。

更改分支名稱

  • git branch -m 舊分支名稱 新分支名稱

刪除分支 -d、-D

沒有不能刪除的分支,包括預設分支 master 也可以,但是,目前所在的分支不能刪除,只要切換到別的分支就可以刪除。

  • git branch -d 分支名稱 刪除分支,還沒有合併過後的分支,-d 參數不給刪的,git 會提醒使用 -D 參數才可以刪除。

若刪除掉還沒合併的分支,怎麼救回來?

  1. git branch -D cat(刪除 cat 分支)。
  2. 刪除分支 Commit 雜湊碼其實還在,會出現這樣的訊息 Deleted branch cat(was 01168d8),
  3. git branch cat 01168d8,把 cat 分支建立回來。

分支只是一個指向某個 Commit 雜湊指標,刪除分支不會造成那些 Commit 消失,只是沒有人指到 Commit,只要把他們連接回來,內容就會回來。但是,刪掉後,卻忘記 SHA-1 commit 雜湊碼怎麼辦? 可使用git reflog查詢,跟使用 reset 某個 commit 一樣。

切換分支 checkout

  • git checkout 分支名稱 切換到分支名稱,例如:git checkout devlop
  • git checkout - 兩個分支來回切換

Merge

合併分支也就是合併自己開出去的分身,收割它們為你做的事情,合併分支的概念其實是合併他人的分支指向一個新的 commit,每個分支都只是一張便利貼,它們都是指向一個 commit 的指標。

  • 一般合併:git merge 被合併的分支名稱,做完分支項目,git checkout 切換到 master,再進行合併指令。
  • 小耳朵合併:git merge 被合併的分支名稱 --no-ff 顯示同條支線要合併,剷生合併過的小耳朵,可知道何時合併,也就是不使用快轉模式合併。

合併分支一些情況

  1. 若是系出同源的雙胞胎分支(兩分支獨立前進)要進行合併,Git 會產生一個 Commit 來處理,這個 Commit 會指向二個 Commit(一般 Commit 只會指向某個 Commit),明確標記來自哪兩
    個分支,兩獨立分支合併可能產生內容相同的衝突。
  2. 若是在同一條支線上合併,Git 會直接使用快轉模式(Fast Forward)進行合併,進度落後的會直接複製進度超前的內容並且移動至與進度超前相同的位置(HEAD),注意的是,進度超前無法
    複製進度落後的內容,快轉模式不會產生新的 Commit 節點。
  3. --no-ff 表示不使用 Fast-Forward merge,會產生小耳朵,並且產生新的 Commit 節點,也稱作為 3-way merge。
  4. 3-way merge 的處理方式比 fast-forward merge 要來的複雜。因為 fast-forward merge 的檔案內容只會在一個分支中修改,另一個分支沒有任何變動,保
    證一定可以合併成功。3-way merge 的情況是二個分支都修改了檔案內容,如果修改的是同一個檔案中相同的位置,就會造成衝突(conflict)的情況。Git 在合併過程中發現衝突,會顯示警告訊息。

rebase(另一種合併)

rebasing 是 merge branch 的第二種方法,rebasing 就是取出一連串的 commit,複製它們,然後把它們接在別的地方。
rebasing 的優點是可以建立更線性的 commit history。假如只允許使用 rebasing 的話,則 repo 中的 commit log 或者是 commit history 會更加簡潔好看。

rebase 這種合併方式盡量不要用,不會產生合併的 commit,很難知道發生狀況,rebase 重新定義真的參考基準,a、b 為各自獨立分支,假如以 a 分支是 head,要用 rebase 合併 b,那麼 b 這條分支是新的參考基準,a 會接到 b 分支上,但不會產生任何新的合併 commit。

如何取消 rebase? git reset HEAD^ --hard 只會拆掉最後一個 Commit,無法回到 Rebase 的狀態。
搭配使用git reflog,查出 rebase 前的 commit 的代碼,git reset commit代碼 --hard,或是使用記錄點 ORIG_HEAD 參數,它表示危險操作(分支合併、Reset、Rebase)之前的 HEAD 位置,git reset ORIG_HEAD --hard,更方便使用。

Rebase instead of merge:拉回來不會有額外的 Commit 的方式進行合併,也會使用快轉模式,覺得很像 Commit merge changes immediately…目前無法比較。

git pull --rebase

merge 和 rebase 的差異

  • 執行 merge 時,則會出現合併提交,也就是所謂的小耳朵。
  • 執行 git pull --rebase 時,合併會排在同一分支線上。

Merge、rebase 發生衝突

在兩個分支同時編輯同一個檔案,此時無論使用 Merge 或 rebase 合併都會發生衝突,在終端機會被標記為 both modified 狀態。

例如:同檔同行狀況

  • 修改 a 分支的 index.html 裡面的 h1,hello world
  • 修改 b 分支的 index.html 裡面的 h1,hello git
  1. Merge 衝突:3-way merge 時若發生衝突時,要手動編輯哪個才是需要的內容。
  2. rebase 衝突:git status 會出現 rebase in progress,與 merge 一樣,要手動編輯需要的內容後,以下步驟:
    • git add . 加入暫存區
    • git rebase --continue

修改 commit

修改最後一次訊息

指令 說明
git commit –amend -m 訊息 可修改最後一次 commit 訊息,產生新的 Commit 雜湊碼

不想新增一個 commit

例如,commit 後想追加檔案或修改檔案的內容卻不想新增一個 commit,一樣 git add . 後,輸入以下指令,這樣就不會再新增一次 commit。

指令 說明
git commit –amend –no-edit 新增檔案到暫存區卻不想修改 commit

或者,使用 git rest head~ –soft,將檔案還原至暫存區,再新增檔案。

修改某個歷史紀錄訊息

  1. 輸入 git rebase -i commit 碼,commit 碼為 HEAD 到 commit 碼的範圍,會開啟 GitLens Interactive Rebase。
  2. 將要修改的 commit,把 pick 改成 edit,按下 START REBASE,出現允許使用 git commit –amend 的訊息。
  3. 輸入 git commit –amend -m “想修改的訊息”,這邊要注意,會出現 detached HEAD 斷頭。
  4. 輸入 git rebase –continue,移除斷頭完成修改,修改過的 commit,其 SHA-1 值會改變,會做出一個新的 commit

如果要返回這次 rebase,記得可以使用 git reset orig_head –hard,返回危險操作前一個狀態。

合併訊息

使用在重複性 commit 卻都是雷同的操作,例如增加一個檔案後 commit,再增加一個檔案 commit,可以合併為一個。

  1. 輸入git rebase -i commit 碼,commit 碼為 HEAD 到 commit 碼的範圍,會開啟 GitLens Interactive Rebase。
  2. 將要修改的 commit,把 pick 改成 squash,表示與前一個做訊息合併,可繼續選擇 squash 再與前一個合併,按下 START REBASE。
  3. 此時會進入編輯器,修改成自己想要的訊息,存檔關閉,修改成功。

如果要返回這次 rebase,記得可以使用 git reset orig_head --hard,返回危險操作前一個狀態。

Detached HEAD

一般來說,分支會指向某個 Commit,而 HEAD 指向某個分支的指標,也就是說我們可以將 HEAD 看成目前所在的分支。但是,若 HEAD 發生沒有指到某個分支的情況,這就稱為斷頭(Detached HEAD)。HEAD 只要指到沒有分支名稱的 commit 上,我們又進行了修改並且 commit,這樣就會產生新的 commit,這個沒有分支名稱的 commit 點就叫斷頭,它還是可以繼續進行 Commit,如果你肯為它取名,它就會成為新的分支。

斷頭是無名的新分支,還有個特點,當回到其他分支後,斷頭就會消失在樹狀圖上,看似消失其實存在,如果有記得它的 commit SHA-1,還是可以使用 git checkout commit 碼返回,但都沒有回來拜訪它,過不久之後它就會被 Git 資源機制回收。

  • 處理斷頭的方式
  1. git branch 名稱:如果你想要斷頭產生的新分支,給斷頭一個分支名稱,解決斷頭問題。
  2. git checkout master:回到 master 分支,也可以解除斷頭。
  3. git merge 無名分支的分支名稱:或是回到 master 合併無名分支的分支名稱,屬於 3-way merge,在合併過程中可能發生衝突。
  • 遠端斷頭處理方式

遠端斷頭發生於與本地端 HEAD 不一致的情況。例如 HEAD 本地端、遠端都指到 master 與 master/orgin

  1. git branch –remote:顯示遠端分支
  2. git checkout origin/abc:試著切換到其他分支,會發現產生了斷頭。
  3. git checkout -t origin/abc:切換遠端分支不呈現斷頭,要加上 -t 或–track。

PS:其實 -t 是在本機建立一個追蹤分支,其實就是等於將本地端分支改為 git checkout abc。

標籤(Tag)

標籤(tag)是一個指向某個 Commit 的指標,跟分支(branch)很像,都是 SHA-1 40 字元碼,但些許不同。
標籤有兩種:輕量標籤是用於暫時標記,附註標籤可以註解更多的資訊,例如貼標籤的人是誰、及註解的訊息。

輕量標籤(lightweight tag)

git tag 標籤名稱 commit 碼,例如:git tag hello ffda1d4,在 commit 碼 ffda1d4 的地方貼上 hello 標籤

附註標籤(annotated tag)

git tag 標籤名稱 commit 碼 -a -m “附註顯示的訊息”:(-a 建立附註標籤,-m 做 commit 訊息)

git tag -d 標籤名稱:刪除標籤

不管哪種,標籤也是貼紙的概念,貼在某個 Commit 上,差異就在要不要為這標籤輸入資訊,標籤會存於 .git/refs/tags 裡面,
標籤被刪除,也不會影響到被指到的物件,標籤跟分支真正的差別在於分支會隨著 Commit 移動,但標籤不會,標籤只會留在原來貼的位置在。

分支與標籤差異?

標籤與分支都是一種指標,它們都放在.git/refs 目錄下,分支在 heads 目錄,標籤在 tags 目錄。兩者被刪除不會影響到指到的那個物件,因為他們都是便利貼的概念。差別在於分支可以在 commit 節點上移動,標籤被貼上就屬於該 commit 的便利貼,不會移動。留下來(標籤),或我跟你走(分支)。

暫存進度(stash)

如果要先忙別的任務,又不想先將現在的分支 commit,那麼要先儲存 stash 手邊的進度。

  • 第一種方式,先 Commit 目前進度,然後去處理其他的任務,完成後再 git reset HEAD^拆掉當前的 commit 將檔案丟回工作目錄繼續做。
  • 第二種方式,使用 Stash。

中斷目前的工作要切換到不同分支時候,因為修改還沒到可以提交的程度,原因只完成了部分,甚至功能不完全,處於一種尷尬狀態。Git 會在這種時候阻止切換到其他分支,除非有「commit」或「stash」的動作,否則無法切換。Stash 可以在該分支修改未提交狀態下使用,將該分支目前狀態儲存放到箱子裡冷凍起來(冷凍庫),尚未追蹤(Untracker)狀態的檔案預設是沒辦法被 Stash,除非使用 -u 參數。

指令 說明
git stash 將進度放入冰箱冷凍起來。
git stash list 查看該分支目前箱子內容,WIP(Work In Progress)指哪個分支的箱子。
git stash pop stash@{箱子編號} 倒出箱子的內容,且丟掉該箱子,pop 指令等於 apply + drop。
git stash apply stash@{箱子編號} 倒出箱子內容,但不會丟掉箱子。
git stash drop stash@{箱子編號} 丟掉該箱子。

Stash 是 Git 強大的工具,可以一次擁有好幾個儲存箱,套用箱子到不同的分支,或是取消已套用的內容,甚至還可以從儲存箱再建立新的分支。

cherry-pick

Merge 指令把指定的分支,合併到目前所在的分支。Cherry-Pick 指令其實也是一種合併。只是處理的對象不是分支,而是 Commit 節點。
也就是把某個 Commit 節點的檔案版本,合併到資料夾中的檔案。預設,執行這個指令會建立一個新的 Commit 節點。

如果不想建立新節點,可以加上 -n 選項。執行這個指令前,資料夾中被修改的檔案必須先存入儲存庫。否則會出現警告訊息,提醒必須先把修改的檔案存入檔案庫。
由於 Cherry-Pick 指令需要執行合併,因此也可能發生衝突。發生衝突可以選擇放棄執行或是編輯衝突檔案,如果決定放棄,可以執行 git cherry-pick --abort
資料夾中的檔案和 Git 儲存庫都會回復到原來的狀態。若想編輯衝突檔案,找到衝突的位置加以修改,編輯好後執行 git cherry-pick --continue

git cherry-pick commit碼 commit碼 commit碼 撿其他 commit 碼及內容過來使用。
git cherry-pick commit碼 --no-commit 不會直接合併,會先放在暫存區。

GitHub

Fork

Fork 不是 Git 原生操作,是 GitHub 的一種機制,可將別人或自己的儲存庫 Fork 一份下來,這動作就好像 Git 的 branch。

  • Clone 與 Fork 的差異

Fork 讓 Git 儲存庫分出另一個版本,很像 Clone 指令,Clone 的功能也是複製 Git 儲存庫,兩者的不同點在於 Clone 複製的 Git 儲存庫不會記錄是由哪一個 Git 儲存庫複製出來,
Fork 複製出來的 Git 儲存庫會保留它的來源 Git 儲存庫,將來這個 Fork 得到的 Git 儲存庫,可以再合併到原來的 Git 儲存庫。

也就是說,Fork 得到的儲存庫可以執行 Commit、建立分支、合併等各種操作,在同一段時間,原來的 Git 儲存庫也會進行修改和更新,現在可以在任意時間點,從原來的 Git 儲存庫取得更新,
並且套用到 Fork 得到的儲存庫。當程式專案開發完成,並且 Commit 到 Git 檔案庫後,可以發送一個 Pull Request 訊息給原來的 Git 儲存庫。
原來的 Git 儲存庫擁有者收到 Pull Request 的請求之後,如果採用我們的 Fork 儲存庫,可以把內容合併到原來的 Git 儲存庫。

Pull Requests(PR)

  • 發 PR 流程
  1. A 是開發者,並擁有一個 master 開發分支。
  2. B 很喜歡這個專案,但發現有個小問題。
  3. 於是 B fork 了 A 專案到自己 GitHub 帳號下的 Respoitory。
  4. 並將 git clone git@github.com:使用者名稱/xxxxxx.git fork 的檔案抓下來。
  5. 修改完畢後 push 到 GitHub,git push origin master
  6. 在 Pull requests 下 PR,New pull request,並建立 create pull request,建立訊息向 A 申請合併分支。
  7. A 認為 B 真是棒,於是 merge 了 A 的 PR。

跟上 fork 專案的進度

第一招:砍掉重練
把 Fork 過來的專案砍掉,再重新 Fork 一次,保證最新版本。

第二招:不砍掉 Fork 過來的專案,跟上游同步

  1. git remote -v 查看遠端自己專案的訊息。
test https://github.com/powerkaifu/test.git (fetch)
test https://github.com/powerkaifu/test.git (push)
  1. git remote add test2 對方遠端專案網址 在遠端新增一個遠端儲存庫名稱,來源是對方遠端專案的網址。
  2. git fetch test2 取得最新版的內容,但沒有合併。
  3. git merge test2/master 與遠端合併。
  4. git push test master 再推回自己遠端 fork 的專案,讓本地端與遠端都是最新的進度。

HTTPS 和 SSH

本地儲存庫可以透過兩種方式,存取 GitHub 網站上的遠端儲存庫,第一種是 HTTPS,另一種是 SSH。兩者優點如下:

  1. HTTPS 和 SSH 通訊協定在傳送資料的過程中都會進行加密,因此是很安全的資料傳輸方式。
  2. 使用 HTTPS 上傳資料,Git 會要求輸入 GitHub 網站的帳號和密碼。
  3. 使用 SSH,必須建立一對金鑰(包括公鑰、私鑰),公鑰加入 GitHub 網站的帳號中。當要上傳資料時,GitHub 網站會先檢查電腦有沒有登錄公鑰,沒有就拒絕,
    如果有就會顯示公鑰的密碼。密碼正確才會開始上傳資料。SSH 通訊提供兩道保護。但其實也可以不用設定金鑰的密碼,這樣只要在特定的電腦上執行,就可以直接傳送資料,
    不需要輸入密碼,操作上更方便。
  4. 可以隨時切換 HTTPS 或是 SSH 通訊協定。
  • 設定 SSH
  1. 終端機輸入 ssh-keygen
  2. 接著會詢問儲存金鑰的路徑,直接按 Enter,預設即可。
  3. 然後輸入密碼兩次,此密碼是本地儲存庫傳送資料給 GitHub 時需要的密碼,或是按下 Enter 兩次跳過就不需要驗證密碼。
  4. 檔案總管 C:\使用者\使用者帳號名稱\.ssh 資料夾,裡面有兩個檔案,id_rsa(私鑰)、id_rsa.pub(公鑰),用編輯器開啟 id_rsa.pub,複製全部內容。
  5. 登入 GitHub,進入右上角圖像的 setting,點選左側選單的 SSH and GPG Keys,再選擇右上角 New SSH Key,輸入 title 及貼上公鑰內容。
  6. 同一台電腦上,不同使用者帳號的 SSH 金鑰是互相獨立,不能互相使用。

Git Actions(設定自動佈署)

  1. 每個 repository 都有個 Actions 可以設定自動佈署。
  2. 進入 Actions,選擇 set up a workflow yourself,將以下內容複製貼上。
# Action 名稱
name: Deploy
# 觸發時機,當推送到分支 master 時
on:
push:
branches: [ master ]
# 執行的工作
jobs:
# 工作名稱
deploy:
# 執行工作的虛擬機作業系統
runs-on: ubuntu-latest
# 工作步驟
steps:
# 步驟一:複製程式碼
- name: checkout
# 使用的 actions/checkout 複製程式碼
uses: actions/checkout@master

# 步驟二:編譯及部署
- name: buildAndDeploy
uses: JamesIves/github-pages-deploy-action@master
# 步驟設定
env:
GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
# 使用 master 分支的檔案
BASE_BRANCH: master
# 部署到 gh-pages 分支
BRANCH: gh-pages
# 編譯後要上傳的資料夾
FOLDER: dist
# 執行指令
BUILD_SCRIPT: npm install && npm run build
# 動作完成後清除檔案
CLEAN: true
  1. 按右側 start commit,直接 commit,或替 main.yml 更名為 deploy.yml。
  2. 自動佈署完成會自動新增 gh-pages 分支,會將專案打包的靜態檔案(通常為 dist 目錄)都 push 到 gh-pages 裡面,最後要到 settings 將 gh-pages 打開。

readme 與 wiki

readme.md 只做簡單說明,要詳細說明可以使用 GitHub 的 wiki 功能。

熱門的遠端儲存庫服務平台

  • GitHub:擁有 GitHub Pages 功能,可擁有私人數據庫,免費方案是 3 人以下。(適合擁有自己的公開對外靜態網站)
  • GitLab:自架 Git 伺服器,有提供 web 視覺化管理介面,常用於企業內部開發。
  • Bitbucket:可擁有私人數據庫,免費方案是五人以下的的團隊。(適合公司專案的小型團隊)(沒有 GitHub Pages 功能)

常用指令

基本指令

指令 說明
git init 初始化空專案
git status 查看工作目錄的狀態
git add . 或 git add all 將檔案加入暫存區(stage)
git commit -m 訊息 commit 修改訊息
git commit –amend -m 訊息 可修改最後一次 commit 訊息,產生新的 Commit 雜湊碼
git commit –amend –no-edit 有時只想新增檔案到暫存區,卻不想修改 commit
git clone 遠端儲存庫路徑 將專案複製下
git remote add origin 遠端儲存庫路徑 與遠端儲存庫連線
git push origin master 將本地專案推送至遠端的 master 分支
git push origin 其他分支名稱(通常與本地端分支名稱相同) 將本地專案推送至遠端的其他分支

輔助指令

指令 說明
git 列出 git 所有指令
git config --list 或 git config -l 顯示 config 設定
git --version 版本查詢
git clean -n 顯示目前工作目錄可清除的檔案
git clean -f 清空目前工作目錄未被追蹤的檔案
git log 查看 log 歷史紀錄,最上層是最新的 commit
git log --oneline 查看簡易的 log 歷史紀錄
git log index.html 檢視某個檔案的 commit 紀錄
git reflog 或 git log -g 檢視 commit 所有紀錄,還原很重要的指令
git branch -r 檢視遠端分支,-r 或 --remote
git blame index.html 看看是誰寫的程式
git blame -L 5,10 index.html 檔案太大,可以顯示程式行數範圍,看誰寫的程式

短指令

git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.st status
git config --global alias.cm commit

也可以到以下目錄的設定檔 .gitconfig,設定短指令,此檔案也可以看到其他全域 config --global 的設定

全域設定檔:

  • Win:C:\Users\KaiFu.gitconfig
  • Mac:~/.gitconfig

列出 commit

git log --oneline 列出簡易的 commit 雜湊碼
git log --oneline --author="kaifu" 搜尋某人的 commit
git log --oneline --author="kaifu|huhu" 搜尋 kaifu 與 huhu 的 commit
git log --oneline --grep="wtf" 搜尋符合的關鍵字
git log -S "Ruby" 檔案內容有提到 “Ruby” 這個字
git log --oneline --since="9am" --until="12am" 查詢早上 9 點至 12 點工作進度

VScode

VSCode 介面

  • 下方有 Git Graph 顯示分支圖。
  • 左下角可以檢查在哪個分支,或是切換分支。

建立分支

有幾種方式可以建立分支。

  1. source control 的 … 裡面 branch 有 Create Branch,建立完畢可以切換到該分支。
  2. 在 Git Graph 的 Commit 點右側空白處按右鍵選擇 Create Branch。如果要切換建立的分支,可以勾選 check out。
  3. 指令 git checkout -b 新分支名稱,建立分支並切換到該分支。

.git 檔案與目錄結構

這目錄是 git 的一切,在 Git 裡,有四個很重要的物件,分別是 Blob 物件、Tree 物件、Commit 物件以及 Tag 物件。
Git 只對內容有興趣,對空目錄無感,空目錄無法被加入 Git 裡。

  • Git 的運作
  1. 檔案 add 加入 Git 後,檔案內容會被轉成 Blob 物件。
  2. 目錄、檔案會存放在 Tree 物件,Tree 物件會指向 Blob 物件,或是其他的 Tree 物件。
  3. Commit 物件會指向某個 Tree 物件。除了第一個 Commit 之外,其他的 Commit 都會指向前一次的 Commit 物件。
  4. Tag 物件會指向某個 Commit 物件。
  5. Branch 分支不是四個物件之一,但會指向某個 Commit 物件。
  6. 推送 Git Server 後,在 .git/refs 會多出出一個 remote 的目錄,存放遠端的分支,它們也會指向某個 Commit 物件。
  7. HEAD 指標不是四個物件之一,但會指向某個 Branch 分支。

在 Git 的世界裡,會根據當下的 Commit,一個一個得把所有物件抽出來,且每次 add 到暫存區時,即使檔案內容只改了一個字,也會算出新的 SHA-1 值,
做出一顆全新的 Blob 物件,就像拎一串葡萄, 這也是所謂的快照(Snapshot)概念。每一次的 Commit 都是一個物件,它會指向某一個 Tree 物件(目錄),
而這些 Tree 物件會指向其他 Tree 物件(子目錄)或是 Blob 物件(檔案),結構就像葡萄串,只要伸手從源頭的 Commit 物件拎起來,整串內容都可以被拿出來。

refs/heads 記錄了分支指標的 40 commit SHA-1 值。

.
├── hooks
├── info
├── logs
├── objects 存放 Blob、Tree、Commit、Tag 物件
└── refs 紀錄了 heads(分支指標)、remotes(遠端儲存庫)、tags
COMMIT_EDITMSG
config 設定檔
description
HEAD 紀錄目前指向的分支名稱(commit 碼)或是 其他 commit 碼
index
ORIG_HEAD 危險操作的紀錄點(commit 碼),git reset ORIG_HEAD --hard 可以返回之前的狀態
sourcetreeconfig.json

.gitignore

在工作目錄下新增一個 .gitignore 檔案,可以忽略某些檔案或目錄不被 Git 追蹤,也就是說 .gitignore 的名單可以讓 Git 無視,感應不到。

*.tmp
config/database.yml
node_modules
.env
nogrok.exe

Git Flow

  • Master(持續不關閉分支)
    最穩定版本,隨時可上線版本,此分支的來源只能從別的分支合併過來。不會直接 Commit 這個分支,會在這個分支上的 Commit 打上版本標籤。

  • Develop(持續不關閉分支)
    開發分支的基礎,需要新增功能時,所有的 Feature 分支都是這個分支切出去的。而 Feature 分支完成功能後再合併回 Develop,

  • Hotfix(暫時性分支)
    線上產品出現問題時,會從 Master 分支開一個 Hotfix 分支出來進行修復,完成後再合併回 Master,同時也合併一分到 Develop。
    必須如此,因為 Develop 分支完成並合併 Master 的時候,那個問題又會出現。

  • Release
    當 Develop 分支夠成熟,就可以合併到 Release 分支,進行上線前的最後測試。測試完成後,Release 分支可以合併到 Master 與 Develop 兩個分支上,
    Master 分支是上線版本,合併 Develop 分支是因為 Release 分支上還會遇到修正的問題,所以需要跟 Develop 分支同步,免得之後的版本又再度出現同樣的問題。
    Release 名稱以 release/1.0 數字格式為主,

  • Feature(暫時性分支)
    新增功能時,會從 Develop 分支分出 Feature 分支 來開發功能,完成後再合併回 Develop 分支,就可以刪除 Feature 分支。
    Feature 分支的名稱會以 feature/,例如:feature/新功能名稱或 feature/#9527,#9527 是功能編號。