月份: 2020 年 1 月

Git 用法

No Comments

Git 是由 Linux 創建者所創建。其目的是維護控管程式源碼的版本更迭;在此基礎上賦有強大厚實的功能。筆者此前完全不懂 git,也沒拿它運用過。所以於此再多說些什麼就顯得班門弄斧了。
不過,既然對象是程式源碼,與 wordpress 何干?確實!我們目前有的就是整個網站的備份,備一次就産出一個檔案,頂多就是對每個備份檔做詳細的歷程說明便已足夠。拿 Git 來,不僅大材小用屬性上也格格不入。確實!
筆者一言以蔽之:因為目前對於 wordpress 內部的運作機制還不了解,為了確保網站長遠的健康狀態,筆者會對兩版備份之間做詳細的條列記錄有哪些變更。
Git 是記錄與控管源碼的歷史更迭,
那麼筆者就拿來記錄與控管(wordpress 的)歷史的歷史更迭。

# 查詢版本
git --version


# 設定使用者的名稱及郵箱
git config --global user.name "<Your Name>"
git config --global user.email "<your@gmail.com>"

# 列出目前設定
git config --list
# 系統層級的設定
git config --list --system
# 使用者層級的設定
git config --list --global
# 儲存區層級的設定
git config --list --local
# 取得特定選項值
# git config [config_section.config_name]
# 例如 git config user.name
# 刪除特定選項設定
# git config --unset --system [config_section.config_name]
# git config --unset --global [config_section.config_name]
# git config --unset --local  [config_section.config_name]

# 可設定一些常用的 Git Alias,指令如下:
# git config --global alias.co   checkout
# git config --global alias.ci   commit
# git config --global alias.st   status
# git config --global alias.sts  "status -s"
# git config --global alias.br   branch
# git config --global alias.re   remote
# git config --global alias.di   diff
# git config --global alias.type "cat-file -t"
# git config --global alias.dump "cat-file -p"
# git config --global alias.lo   "log --oneline"
# git config --global alias.ll "log --pretty=format:'%h %ad | %s%d [%Cgreen%an%Creset]' --graph --date=short"
# git config --global alias.lg "log --graph --pretty=format:'%Cred%h%Creset %ad |%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset [%Cgreen%an%Creset]' --abbrev-commit --date=short"

# 指定預設文字編輯器,只要把執行檔路徑寫對即可
# git config --global core.editor vim
# 直接編輯設定檔
# git config --edit --[system|global|local]
# 自動訂正打錯的參數
# git config --global help.autocorrect 1
# 啟用訊息顏色
# git config --system color.ui auto
# 自訂 commit 訊息範本。須先建立一個文字範本檔案
# git config --local commit.template "<path-to-template-file>"


# 建立一個資料夾,並於該目錄下初始化成由 git 來管理。如此我們可以
# 視為創建了一個專案資料夾了。
mkdir "<my_folder>"
cd "<my_folder>"
git init
# 在該目錄下會産生一個 .git 的子目錄,存放著專案管理/版本控制的資訊


# 查看專案資料夾內/working directory 的狀態。
# 目的是顯示出目前最新版與索引檔之間的差異。於該目錄下下達
git status
# 精簡狀態
git status -s


# 檔案狀態分為本地 local 及遠端 remote。
# 本地又細分為 working(編輯中) -> staging(已提交但暫存) -> repository(本地正式提交)
# repository 這名詞經常出現,因此筆者將它簡稱為窖。
# local 會有一個窖,remote 也會有一個窖。本地窖遠端窖,或也說本地庫遠端庫。
# 從 working 推入 staging 稱為 add,從 staging 到 local repository 稱為 commit,
# 從本地窖拉回來 working 稱為 checkout 或 merge,
# 從 local 到 remote repository 稱為 push,拉回來本地窖稱為 pull。


# 建立多人共用的共用窖 shared repository
git init --bare
# 建立後我們會發現資料夾內不存在 .git 子目錄,
# 而是 .git 內的所有資料都直接置於此資料夾內
# 這是一個「沒有工作目錄的純儲存庫」,
# 所以共用儲存庫也有個別名叫做「裸儲存庫」(bare repository)


# 以下 add 增加,都是將 working dir 中的 working files 往前推一級到暫存區
# 其目的是為了將目前「工作目錄」的變更寫入到「索引檔」裡
# 增加檔案
git add "<file_name>"
# 增加目錄
git add "<dir_name>"
# 增加所有檔案與目錄包含子目錄
git add .
# 僅將「更新」或「刪除」的檔案變更寫入到「索引檔」中/索引檔已既存該檔記錄
git add -u


# 對暫存區的檔案建立版本的指令。而且暫存區中的所有檔案視為一個群組/一個版本
# 其目的是把「索引檔」與「目前最新版」中的資料比對出差異,
# 然後把差異部分提交變更成一個 commit 物件
git commit
# 或是加添說明
git commit -m "版本紀錄的說明文字"


# 查詢版本的歷史紀錄
git log
#最近 n 筆紀錄,n 是數字
git log -n
# 以較簡潔的方式顯示
git log --pretty=oneline
# 以更簡潔的方式顯示
git log --pretty=oneline --abbrev-commit


# 執行刪除檔案的指令的時候,會同時做兩件事:
# 刪除工作目錄快取的 "<file_name>" 這個檔案(用來標示這個刪除檔案的動作要列入版本控管)
# 刪除工作目錄下的 "<file_name>" 這個實體檔案(代表真的把這個實體檔案給刪除)
git rm "<file_name>"

# 僅將檔案移出暫存區到前一級。
# 即,若只想刪除索引檔中的該檔,又要保留工作目錄下的實體檔案
git rm --cached "<file_name>"


# mv 指令,可以用來變更檔案或目錄的名稱
git mv "<old_file_name>" "<new_file_name>"

# 查詢現存檔案。在索引檔之中,預設就包含了目前最新版的所有檔案,
# 外加你後來在工作目錄中透過 git add 更新索引檔後的那些檔案。
git ls-files


# 重置目前工作目錄的索引狀態,但檔案實體存在與否不影響
git reset

# 把工作目錄還原到目前的最新版,包含檔案與狀態
git reset --hard

# 還原單個檔案與狀態
git checkout master "<file_name>"


# 由於在使用 Git 版本控管時,會遭遇到很多分支的狀況,
# 所以工作目錄很有可能會在不同的分支之間進行切換。
# 有些 git 指令在執行的時候,會一併更新工作目錄下的檔案。
# 例如當你使用 git checkout 切換到不同分支時,
# 由於目前分支與想要切換過去的分支的目錄結構不太一樣,
# 所以很有可能會將你目前工作目錄下的檔案進行更新,
# 好讓目前的工作目錄下的這些目錄與檔案,
# 都與另一個要切換過去的分支下的目錄與檔案一樣。
# 所以,適時的保持工作目錄的乾淨,是版本控管過程中的一個基本原則,
# 更尤其是日後要進行合併的時候,這點尤其重要。


# 在 Git 裡有兩個重要的資料結構,分別是「物件」與「索引」。
# 「物件」用來保存版本庫中所有檔案與版本紀錄,
# 「索引」則是用來保存當下要進版本庫之前的目錄狀態。


# 算出檔案的 hash code
git hash-object "<file_name>"
# 由 SHA1 算出的 object id 很長,所以取用上可以略寫,至少 4 個字元

# 查詢 commit id 本身的資訊
git show "<commit_id>"

# 查詢 commit 後的 tree object。master/HEAD 代表當前最新版本的 commit id。
git cat-file -p "[master|HEAD|commit_id]"
# 結果會列出 tree object id 和 parent id,
# 其中 tree object 代表著該次 commit 的檔案內容,
# parent id 代表著前一次 commit 的 commit id。
# 因此查詢到 tree object id 後我們可以查看該版本的內容。
# 注意的是,master 是初始的 branch,
# 後續可建立其他 branch 而 master 可被刪除,
# 那麼 master 名稱便不復用。

# 查詢 tree object 的內容
git cat-file -p "<tree_object_hash_code>"
# 通常由 git log 查到當前 commit 過的狀態及個別 commit id,
# 再透過 git cat-file -p commit_id 列出某版本的連貫狀態,
# 會看到開頭有個 tree id,其也代表著該版本,也就是說,
# git cat-file -p "<tree_object_hash_code>"
# 就會看到該版包含的檔案。


# branch:
# 建立了 git 專案,預設的根/主軸,名為 master,
# 用以對照於之後建立的分支。若我們從 master 主軸建立分支叫 ms_br1,
# 那麼,ms_br1 在其建立的那個時間節點,與 master 有完全相同的屬性(clone)。
# 再者,之所以要建立分支,就是要做變更,以有別於原先所處的那個時間節點。
# 因此我們可認定,在那個時間節點以前,master 與 ms_br1 必相同(互為備份),
# 在那個時間節點之後 master 必異於 ms_br1。因此,任何分支都是獨立的存在。
# 刪除任一分支不影響其他分支;例如把 master 主軸刪除了。
# 我們在任何時刻只會位於某一分支上(或主軸上)。提到分支,都包含主軸。
# 而任何一分支上,例如 A,都可再建立一個以上的分支,例如 B,C,。。。
# 在 git 專案中,處於不同分支(且若已 commit 過),
# 那麼用 ls 看到的結果可能是不同的,於此,使用者應已明白原因。
# 查看分支及所處於哪分支
git branch
# 建立分支(後還處於原分支)
git branch "<new_branch_name>"
# 刪除分支。須先切換到其他分支上
git branch -d "<branch_name>"
# 建立分支並切換到該分支上
git checkout -b "<new_branch_name>"
# 切換到其他分支
git checkout "<branch_name>"

# 切換到任何一次 commit 後的狀態下,可由該點(commit id)再生分支:
# 首先先 git branch,來確認想要跳躍的點會位於哪條分支;
# 搭配 git checkout "<some_branch>" 及 git log 查看,
# 再 git checkout "<the_branch>" 站到該分支上,
# 接著 git log 查看當前分支下的 commit 歷史,commit 後面接的就是 commit id,
# 並且站到該 commit 節點上,
git checkout "<commit_id>"
# 最後在該節點建立新分支。
git checkout -b "<new_branch>"


# 安裝圖形化的 git 輔助工具
sudo apt install gitk


# 比對兩版間的差異
git diff "<older_commit_id>" "<newer_commit_id>"
# 輸出的資訊欄位
# index 37bcc8b..d855592 100644
# _____ blob_id..blob_id file_mode
# file_mode descriptions:
# 0100000000000000 (040000): Directory
# 1000000110100100 (100644): Regular non-executable file
# 1000000110110100 (100664): Regular non-executable group-writeable file
# 1000000111101101 (100755): Regular executable file
# 1010000000000000 (120000): Symbolic link
# 1110000000000000 (160000): Gitlink

# 針對工作目錄
git diff
# 在工作目錄做的任何變更做 add 才會同步入暫存區,做 commit 才會同步入本地窖。
# 檢視最後一次的 git add 時的所有檔案的狀態(索引檔),
# 其在當前工作目錄下有何變化。即若當前工作目錄下若有新增檔案不算變化。
git diff "<commit_id>"
# 或目前分支
git diff HEAD
# 同上,該 commit id 狀態比對於工作目錄。HEAD 表示指定當前分支的最新版。

# 針對暫存區
git diff --cached "<commit_id>"
# 不同於上的是新增的檔案視為差異。即索引檔與 commit 狀態之間的差異。
git diff --cached
# 或同於
git diff --staged
# 表示對象是暫存區,其後再接 commit id,或 HEAD。可不接代表 HEAD。
git diff HEAD^ HEAD
# 代表著前一版最新與當前最新的比對


# master 是初始預設的 branch 名稱,其代表著某個 commit id,
# 且代表著當前最新的 commit id。其他 branch 也都有名稱也如此。
# 不過真正參與運作的是 commit id。名稱只是讓使用者便於指定。
# 可想而知,名稱與 commit id 便會被記錄著對應,其儲存在
# .git\refs\heads 目錄下。並且我們去查看其內容便可看到
# branch-name 與 newest-commit-id 的對應。
# 因此簡言之 name/id 可互為替用。
# 而想當然爾,git 必定舉措從名稱去找出 id,其程序如下,找到即停:
# .git/<參照簡稱>
# .git/refs/<參照簡稱>
# .git/refs/tags/<參照簡稱;標籤名稱>
# .git/refs/heads/<參照簡稱;本地分支名稱>
# .git/refs/remotes/<參照簡稱>
# .git/refs/remotes/<參照簡稱;遠端分支名稱>/HEAD

# 進一步地,因我們一定會有常用的對象,對那些對象便也可建立符號參照簡稱。
# 它呈現的方式是,檔案名稱就是符號參照簡稱,
# 檔案內容則是 ref: /path/.../ref-file;即最終仍會對應到某 commit id。
# Git 內建會維護一些特別的符號參照,方便我們快速取得常用的 commit 物件,
# 其預設都會儲存在 .git/ 目錄下。這些符號參考有以下四個:
# HEAD
# 永遠會指向「工作目錄」中所設定的「分支」當中的「最新版」。
# 所以當你在這個分支執行 git commit 後,
# 這個 HEAD 符號參照也會更新成該分支最新版的那個 commit 物件。
# ORIG_HEAD
# 簡單來說就是 HEAD 這個 commit 物件的「前一版」,經常用來復原上一次的版本變更。
# FETCH_HEAD
# 使用遠端儲存庫時,可能會使用 git fetch 指令取回所有遠端儲存庫的物件。
# 這個 FETCH_HEAD 符號參考則會記錄遠端儲存庫中
# 每個分支的 HEAD (最新版) 的「絕對名稱」(commit id)。
# MERGE_HEAD
# 當你執行合併工作時 (關於合併的議題會在日後的文章中會提到),
# 「合併來源」的 commit 物件絕對名稱會被記錄在 MERGE_HEAD 這個符號參照中

# 你可以建立任意數量的「自訂參照名稱」,只要透過 git update-ref
# 就可以自由建立「一般參照」。建立完成後,預設檔案會放在 .git 資料夾下。
# 請記得,參照名稱可以指向任意 Git 物件,並沒有限定非要 commit 物件不可。
git update-ref "<ref-name>" "<commit_id/object_id>"
# 若要建立較為正式的參照名稱,最好加上 refs/ 開頭(即指定存放路徑),例如:
git update-ref refs/"<ref-name>" "<object_id>"
# 若要刪除一般參照,則可以使用 -d 選項。
# 顯示所有參照的方式(在.git/refs/路徑下的),則可以使用
git show-ref
# 建立符號參照名稱
git symbolic-ref "<ref-name>" "<to-ref-file>"

# 相對參照名稱
# 關於 ^ 代表的意思則是「擁有多個上一層 commit 物件(在同一層)時,
# 要代表第幾個第一代的上層物件」。
# 關於 ~ 的意義,代表「上一層 commit 物件」的意思。
# 以個數或數字來代表回推第幾個第幾層,以此類推:
# A~/A~1/回推一層/上一層
# A~~/A~2/A~1~1/回推二層/上第二層
# A^2/上一層第二個物件
# 表示法之下,符號會先被轉為數字,即 ~ -> ~1,~~ -> ~1~1,^^ -> ^1^1,
# 且一旦足以表示,便代表該物件,再從該點追加表示下去。
# 所以例如上一層有多個物件,A~ 和 A^ 都代表上一層第一個物件。

# 把任意「參考名稱」或「相對名稱」解析出「絕對名稱」,例如:
# git rev-parse master
# git rev-parse HEAD
# git rev-parse ORIG_HEAD
# git rev-parse HEAD^
# git rev-parse HEAD~5


# 改到一半的檔案,可能會有以下狀態:
# 新增檔案 (尚未列入追蹤的檔案) (untracked files)
# 新增檔案 (已經加入索引的檔案) (tracked/staged files)
# 修改檔案 (尚未加入索引的檔案) (tracked/unstaged files)
# 修改檔案 (已經加入索引的檔案) (tracked/staged files)
# 刪除檔案 (尚未加入索引的檔案) (tracked/unstaged files)
# 刪除檔案 (已經加入索引的檔案) (tracked/staged files)
git stash 或 git stash save
# 會將所有已列入追蹤(tracked)的檔案建立暫存版
git stash -u
# 會包括所有已追蹤或未追蹤的檔案,全部都建立成暫存版
# git stash save -u "<message>" 指令,就可自訂暫存版的註解
# 在建立完成「暫存版」之後,Git 會順便幫我們建立一個暫存版的「參考名稱」,
# 而且是「一般參考」,在 .git\refs\stash 儲存的是一個 commit 物件的「絕對名稱」
# 我們用 git cat-file -p stash 即可查出該物件的內容,
# 這時你可以發現它其實就是個具有三個 parent (上層 commit 物件) 的 commit 物件。
# 分別代表那些內容:
# 原本工作目錄的 HEAD 版本
# 原本工作目錄裡所有追蹤中的內容 (在索引中的內容)
# 原本工作目錄裡所有未追蹤的內容 (不在索引中的內容)

git stash pop
# 透過 git stash pop 重新「合併」回來。
# 取回最近的一筆暫存版 stash@{0} 並且把這一筆刪除。
# 事實上 Git 骨子裡是透過「合併」的功能把這個名為 stash
# 的版本給合併回目前分支而已。最後,它還會自動將這個 stash 分支給刪除。
git stash apply
# 另一種取回暫存版的方法是透過 git stash apply 指令,
# 唯一差別則是取回該版本 (其實是執行合併動作) 後,該暫存版還會留在 stash 清單上。
# 如果想取回「特定一個暫存版」,就必須在最後指名 stash id,
# 例如 stash@{1} 這樣的格式。
git stash drop
# 如果確定合併正確,你想刪除 stash@{1} 的話,
# 可以透過 git stash drop "stash@{1}" 將特定暫存版刪除。
git stash clear
# 如果想清理掉所有的暫存版,直接下達 git stash clear 即可全部刪除。
git stash list
# 列出目前所有的 stash 清單


# Git 標籤 (Tag) 擁有兩種型態,這兩種類型分別是:
# 輕量標籤 (lightweight tag):可以說是某個 commit 版本的「別名」而已。
# 標示標籤 (annotated tag):是一種 Git 物件。Git 物件包含 4 種物件類型,
# 分別是 Blob, Tree, Commit 與 Tag 物件。「標示標籤」就是 Tag 物件。
# Tag 物件會儲存在 Git 的物件儲存區當中 ( 會存到 .git\objects\ 目錄下 ),
# 並且會關聯到另一個 commit 物件。
# 建立「標示標籤」時還能像建立 commit 物件時一樣包含「版本訊息」。
# 在內建的 Git 標籤機制中,甚至你還可以利用 GnuPG 金鑰對 Tag 物件加以簽章,
# 以確保訊息的「不可否認性」。

# 列出所有標籤:git tag
# 建立輕量標籤:git tag [tagname]
# 刪除輕量標籤:git tag [tagname] -d
# 看這個「輕量標籤」的內容:git cat-file -p [tagname]
# 看這個「輕量標籤」的物件類型(commit):git cat-file -t [tagname]
# 建立「標示標籤」要多加 -a 參數
# 例如 git tag -a "<new-tag-name>" -m "<message>"
# 看這個「標示標籤」的物件類型(tag):git cat-file -t [tagname]
# 預設 git tag [tagname] -a 會使用當前的 HEAD 版本建立成「標籤物件」,
# 如果要將其他特定物件建立為標籤的用法為 git tag [tagname] [object_id]。




15天完

參考資料

  1. https://github.com/doggy8088/Learn-Git-in-30-days/blob/master/zh-tw/README.md
  2. https://backlog.com/git-tutorial/tw/intro/intro1_1.html
  3. https://github.com/twtrubiks/Git-Tutorials
  4. http://www-cs-students.stanford.edu/~blynn/gitmagic/intl/zh_tw/

Categories: Linux

Tags:

PHP Code Snippets Powered By : XYZScripts.com