➜ Old React website
Chung Cheuk Hang MichaelJava Web Developer
網頁抓取(二) - Selenium/Selenide網頁抓取(一) - jsoup

Git 基本操作

Table of contents

1 Git 簡介

Git 係一個分佈式既檔案版本管理工具,可以畀多個人非實時協作並且管理文本檔案。佢既用途同舊時既 SVN 相似,但係 Git 既做法更有系統,功能亦都比 SVN 強大同方便得多。今時今日 Git 已經係最普遍既檔案版本管理工具。而因為有啲 Git 服務提供者(例如 GitHub)有提供免費既網上個人 Git 服務,所以令到 Git 有時可以被用作個人既 cloud storage(當然都有容量甚或文件大小既限制)。以下係最出名既幾個 Git 服務提供者:
  • GitHub(已被 Microsoft 收購)
  • GitLab
  • Bitbucket(Atlassian 既產品)
  • Gitee(國內)

1.1 Git 概念

  • 一個 Git 既 repository 可以有 2 種形式。
    • Offline/local
      • 完全 offline,冇對應既 remote repository。
      • git init 形式產生 .git folder。
    • Remote repository
      • 如果要修改,就要將成個 repository clone 落 local 度,之後作出變更,然後提交變更到 remote repository 既某個 branch。
      • 有啲 Git 服務提供者(例如 GitHub)簡化左新增或者刪除檔案既步驟,畀我地喺網頁都做到。
      • 當需要將 local branch 同 remote branch 同步或者更新 branch 資訊,就需要連網絡。
      • 一般既個人既業餘項目,或者多人同時協作既私人公司項目、open source 項目都會咁做。
  • 每一個 Git 既 repository 既結構都係類似一個 directed graph,而如果從 branch 既角度睇就會係類似 singly linked list,而每一個 node 就叫做 commit(喺呢篇文裡面,我地會叫佢做打包左既變更)。
  • 較新既 commit 會有 pointer 指向前一個 commit。
  • Git 既 branch 其實就係一個 pointer(叫 HEAD),一個 branch 名只係某一個 commit 既自定義名稱既 label/tag。
    • 通常都會從一個現有既 branch 既 HEAD 去開一個新既 branch,喺果度分叉出新既支節。
  • 當某個 branch 有新 commit,Git 就自動將 branch 既 HEAD pointer 指向新 commit。
  • Branches 再多都好,到左開發階段既中後期,唔同 branches 既 commits 最終都會 merge 埋一齊——深層既支節會 merge 埋一齊,之後到淺層既支節 merge 返落主幹既 branch(通常係用返默認既 branch)。
    • 個別 developers 既 branches 會 merge 落 integration branch。
    • Integration branches 會 merge 落主幹既 branch(e.g. master)。
  • 正因為結構係 directed graph/linked list,所以支持毀滅性既操作。
    • 例如刪除已打包既變更,Git 只需要將 HEAD pointer 指返去上一個 commit 就得。
  • 除非個 repository 係新既(冇 source code),否則至少會有一個 branch。
  • 多 branches 既作用係畀我地應付開發項目既時候需要多個 deployment environments。
    • 可以開 dev、SIT、UAT、pre-production、production 等等 environments 對應既 branches,而 commits 可以因應測試進度一層一層往後既 branches 推。
關於 Git 默認 branch 名:
  • 以前無論係 Git 服務提供者或者係 Git 既 CLI 工具,默認既 branch 名都係 master
  • 但因為近年 IT 界著重反歧視,避免使用 master 或者 slave 既字眼,所以有啲 Git 服務提供者開始改左默認既 branch 既名,例如 GitHub 就改左新既 repositories 既默認 branch 會叫 main

2 下載 remote repository

無論係公司既 project,亦可以係個人、私人既 project,只要有條 Git URL(公司通常會用 SSH protocol),就可以下載整個 project 既 source。呢條 Git URL 通常喺 GitHub、Bitbucket、GitLab 等既 project 網頁都會顯示出黎。
git clone "https://github.com/user/project.git"
如果我地想直接下載某個 branch,而唔係默認既 branch(一般為 master),咁我地可以用:
git clone -b "master" "https://github.com/user/project.git"
以上都會建立一個同我地 project 名一樣既 folder。如果想指定新 folder 名,可以用:
git clone "https://github.com/user/project.git" "specific-folder-name" git clone -b "master" "https://github.com/user/project.git" "specific-folder-name"
如果個 Git repo 啟用左 SSH key(Deploy keys),我地可以用:
:: Windows Command Prompt SET "GIT_SSH_COMMAND=ssh -i my-key.rsa -o IdentitiesOnly=yes" git clone ssh://git@github.com/my-username/my-repo.git

2.1 獲取源 Git URL

當我地已經 clone 左個 project 落黎,呢個時候想搵返條 Git URL 出黎,其實唔需要下下都去返 GitHub、Bitbucket、GitLab 等既 project 網頁度抄,有個簡單既方法:
git remote -v

3 更新 branch 資訊

有時我地喺 web 上 create 左 branch(如 JIRA ticket 上),咁我地喺切換去新 branch 之前需要更新一下 branch 資訊:
git fetch
如果唔介意下載埋變更/同 remote branch 同步埋:
git pull

4 切換去另一個 branch

當我地有左需要既 branch 資訊,就可以切換去果個指定既 branch:
git checkout "feature/some_feature"

4.1 查看身處既 branch

有時自己唔記得左自己喺邊個 branch,想知道返就用:
git branch
曾經用過既 branches 都會出現喺結果清單上,打星(*)果個就係而家個 branch。

4.2 搵 remote branch

當我地想知道 remote repository 上面有冇存在住某個 branch,我地可以列曬 remote branches 出黎先,再用 keyword 去 filter 出我地想要既結果。
macOS:
git branch -r | grep "master"
Windows:
git branch -r | findstr "master"

5 下載變更/同 remote branch 同步

隨時間過去,其他同事可能會對我地既 branch 有代碼變更,咁我地想更新自己 local branch 既 source,同步一下,以免唔好令自己 local branch 落後太多, 搞到自己 based on 啲舊既 source code 去做改動,增加之後 integrate 完唔 work 既風險,我地可以間中下載變更:
git pull
行完句 command 之後,以下係常見既結果:
  • 冇變更
  • 有變更,自動更新 local branch 成功
  • 有變更,自動更新 local branch 過程中發現 conflicts,需要手動 merge 去解決 merge conflicts
呢個 command 亦會更新埋 branch 資訊。

6 了解 local branch 現時情況

有時想知道而家個 local branch 咩情況,下一步有咩可以做,我地可以用:
git status

7 提交變更

呢度指既變更(commit)可以係一次修改(一個全新既變更),亦可以係針對 remote repository 上既變更作二次修改(覆寫已經喺雲端既資料)。
一般黎講,如果冇野需要中途返轉頭改,提交變更既流程係:
  1. 檢視變更
  2. 揀需要既檔案,將佢地提交至暫存區
  3. 將暫存區打包,同時提供修改內容概要
  4. 提交已打包既變更
如果有野需要中途返轉頭改,提交變更既流程就會係:
  1. 檢視變更
  2. 揀需要既檔案,將佢地提交至暫存區
  3. 重覆步驟 12
  4. 將暫存區打包,同時提供修改內容概要
  5. 重覆步驟 34
  6. 提交已打包既變更
註:
  • 提交至暫存區既唔單止係檔案清單,仲包括埋實際修改既內容,所以喺提交至暫存區之後仲有野要改,係需要重覆返提交至暫存區既步驟,先可以改到暫存區既內容,暫存區唔會因為檔案內容有後續既修改而自動同步。
  • 無論上左 remote repository 未,已打包既變更都唔會再因為暫存區既內容有後續既修改而自動同步,所以將暫存區打包之後仲有野要由暫存區同步到之前已打包既變更,係需要喺上次既變更上作二次修改。

7.1 檢視變更

檢視有改動既檔案既文本變更:
git diff
檢視指定檔案既變更:
git diff "pom.xml"
如果有太多行既文字或者太多個檔案有改動,咁我地可以用 up arrowdown arrow 黎喺 CLI navigate,會有類似網頁既 scroll 效果。想退出可以撳 q

7.2 揀需要提交既檔案

如果所有變更都需要提交,就先將佢地加曬入暫存區(stage):
git add .
如果想將指定檔案加入暫存區(stage):
git add "pom.xml"
之後,如果喺變更推上 remote repository 之前仲有野改又想包括埋,都要行呢一步。

7.2.1 檢視暫存區內檔案既變更

如果檔案已經暫存(staged),呢個時候我地仲想檢視返變更,可以咁做:
git diff --staged

7.2.2 取消暫存已經暫存既檔案

如果檔案已經暫存(staged),呢個時候我地想將佢地回復至未暫存既狀態(unstaged),可以咁做:
git reset
如果想將指定既檔案回復至未暫存既狀態(unstaged):
git reset "pom.xml"

7.3 打包變更、加概要

如果要打包成一個新既變更,應提供修改內容概要:
git commit -m "Fixed some bugs."
如果想喺上次既變更(無論上左 remote repository 未)上作二次修改,然後更改修改內容概要:
git commit --amend
如果想更改文本編輯器第一行顯示既修改內容概要,可以撳 a,然後更改,完成之後撳 Esc,否則跳過呢步。
然後喺 CLI 既文字編輯器裡面打 :wq,再拍 Enter
如果唔需要更改修改內容概要,可以用:
git commit --amend --no-edit

7.3.1 取消打包已打包既變更

如果想將一個打包左既變更回復至暫存既狀態(staged):
git reset --soft HEAD~1
註:
  • ~ 後面既數字係想刪除既變更數目。
    • 如果個變更係黎自 --no-ff 既 merge,咁我地要刪既只係一個 merge commit。
    • 如果係 fast forward 既 merge,因為所有打包左既變更都喺曬度,所以全部 merge 過黎既已打包既變更都要刪除。
  • 如果涉及既變更本身存在喺 remote repository,咁呢個就係一個毀滅性既操作,因為需要刪除左 remote repository 既變更,所以需要 force push。

7.3.2 刪除已打包既變更

如果想刪除打包左既一個變更:
git reset --hard HEAD~1 git push --force-with-lease
註:
  • ~ 後面既數字係想刪除既變更數目。
    • 如果個變更係黎自 --no-ff 既 merge,咁我地要刪既只係一個 merge commit。
    • 如果係 fast forward 既 merge,因為所有打包左既變更都喺曬度,所以全部 merge 過黎既已打包既變更都要刪除。
  • 如果有任何已經存在喺 remote repository 既變更喺 local 被刪除左,咁呢個就係一個毀滅性既操作,因為需要刪除 remote repository 既變更,所以需要 force push。

7.3.3 將多個變更合併成一個

如果唔想個變更紀錄存在太多個變更,但又想保留所有變更內容,有好幾個方法可以將幾個變更合併成 1 個。
方法一:將多個已打包既變更回復至暫存既狀態(staged),再打包成 1 個變更。
假如我地想合併 2 個變更:
git reset --soft HEAD~2
方法二:刪除多個已打包既變更,再用合併變更既方式將後來既所有變更喺暫存區(stage)合併。
假如我地想合併 2 個變更:
git reset --hard HEAD~2 git merge --squash HEAD@{1} git commit -m "Combined commit message."
方法三:用比較強大既 rebase 功能。
假如我地想合併 2 個變更:
git rebase -i HEAD~2
  1. Interactive rebase 會開啟文字編輯器,將 commits(HEAD~2 就會係 2 個)依時間順序由上至下咁列出黎,而每個 commit ID 既左邊都會寫住 pick
  2. 除左最頂果 1 個 commit 留返係 pick,其餘改成 s(意思係 squash),完成之後撳 Esc,然後打 :wq,再拍 Enter
  3. 之後會進入變更概要既文字編輯器,我地可以喺裡面修改變更概要,完成之後撳 Esc,然後打 :wq,再拍 Enter
註:
  • ~ 後面既數字係想處理既變更數目,如果要合併 3 個變更,就要用 HEAD~3
    • 如果個變更係黎自 --no-ff 既 merge,咁我地要刪既只係一個 merge commit。
    • 如果係 fast forward 既 merge,因為所有打包左既變更都喺曬度,所以全部 merge 過黎既已打包既變更都要刪除。
  • 如果有任何已經存在喺 remote repository 既變更喺 local 被刪除左,咁呢個就係一個毀滅性既操作,因為需要刪除 remote repository 既變更,所以需要 force push。
參考資料:

7.3.4 還原毀滅性既操作

如果做錯左刪除既操作,除左搵有保留到相關 commits 既同事幫手之外,仲有一個補救既方法。
git reset --hard ORIG_HEAD
註:
  • 進行毀滅性既操作既時候,Git 會將之前既 HEAD pointer 覆蓋一個叫 ORIG_HEAD 既 pointer。
  • 如果有任何已經存在喺 remote repository 既變更喺 local 被刪除左,咁呢個就係一個毀滅性既操作,因為需要刪除 remote repository 既變更,所以需要 force push。

7.4 將變更推上 remote repository

如果係新既變更,可以直接行:
git push
行完句 command 之後,以下係常見既結果:
  • 成功推上 remote repository
  • 因為 remote repository 有變更,我地個變更唔可以推上去住,需要先下載變更落黎,有可能需要解決 merge conflicts
但如果係喺上次既變更上作二次修改,就必須 force push:
git push --force-with-lease
要注意既係 force push 唔係一個好既習慣,除非你係果個 branch 既唯一作者,因為呢個動作有可能會覆寫其他作者喺你最後更新 local branch 至 force push 既呢段時間做過既修改(例如另一個作者基於你上次提交既變更去做修改)。Force push 呢個動作係冇紀錄冇痕跡(Git 本身冇做紀錄,CLI 睇唔出,但 GitHub、Bitbucket 喺某啲情況或者配置下可能會有)。
一個好啲既做法係用 force with lease:
git push --force-with-lease
解釋:如果有其他人搶先一步改左野,令到個 branch 既 remote ref 唔同左,咁就同我 local branch 既 ref 唔同,咁 Git 就會 reject 我個 push,而原因就係 stale info(陳舊資訊)。
另外,force push 都會令 commit ID 隨著修改內容甚或相關 metadata 而改變。

7.5 重設 local branch

如果想 reset local branch,刪曬所有新增過既檔案、復原返刪左既檔案,及還原所有檔案既文本變更,我地可以用:
git clean -f -d -x && git reset --hard
當然最好都係重新 clone 過。

8 查看 branch 最近既變更

使用以下 commands,我地可以睇到變更概要,但唔會顯示檔案清單或者文本既實際改動內容。
基本(顯示既 layout 難睇,亦冇火車軌,唔推薦):
git log
連埋改左既檔案名:
git log --stat
一行過、連火車軌:
git log --graph --oneline
一行過(包含火車軌、作者、提交日期時間、修改內容概要,最推薦,可以自定義 alias 黎簡化):
git log --graph --pretty=format:"%C(cyan)%h%d %C(green)%ai %C(white)%<(15)%an%C(yellow)%s"
如果有太多行既文字,咁我地可以用 up arrowdown arrow 黎喺 CLI navigate,會有類似網頁既 scroll 效果。想退出可以撳 q

9 合併另一個 branch 既變更

假如我地有兩個 branches,masterfeature_branch,而 feature_branch 係由 master 開出黎,之後我地喺 feature_branch 有一個或以上既打包左既變更,咁我地就可以將我地喺開 feature_branch 之後所打包既變更合併到 master branch。
git merge --no-ff feature_branch git push
Merge 方式解釋
Fast forward --ffgit merge 既默認策略。FF merge 會直接將 source branch 既 HEAD pointer 指去 feature branch 既 HEAD pointer。呢個方法比起 non fast forward 既壞處係我地睇 log 唔會知道邊啲 commits 係關連,因為 merge 左去 target branch 上面既變更同我地喺 target branch 度直接打包變更係一樣。另外,萬一喺開 branch 之後、merge 之前,有人喺 target branch(上面例子既 master)度加左變更,咁 Git 就冇辦法就咁搬個 pointer 去 source branch(上面例子既 feature_branch)既 HEAD,因為咁做會令到 target branch 上面新既 commits 唔見左,咁 Git 就會自動轉用 non fast forward merge,command line 會見到寫住 Merge made by the 'recursive' strategy.,睇返 log 會見到多左一個 merge commit,因為 Git 幫我地既變更再打包放喺 target branch。
Non fast forward --no-ffNon FF 既 merge 會將 source branch(上面例子既 feature_branch)既所有需要 merge 既變更打包成一個 merge commit 放喺 target branch(上面例子既 master)。
Squash --squashSquash 既 merge 可以畀我地將多個變更合併打包成一個變更,呢個變更裡面包括曬本來屬於多個已打包既變更既所有修改內容。
合併完成之後,我地可以刪除 feature_branch,因為我地既變更已經喺 master branch;亦可以繼續保留 feature_branch,如果日後再有變更要 merge 可以再行上面既 commands。
參考資料:

9.1 Rebase

我地一般都會從 base branch 開新 feature branch,然後加入變更,最後合併 feature branch 到 base branch。
基於呢個 workflow,如果我地身處多人既開發環境,當我地埋頭喺自己既 feature branch 加入左好多變更之後,其他開發者都可能搶先一步,已經喺 base branch 上面加入左佢地既變更。如果我地想將變更可以重新基於最新既 base branch,喺最新既變更上面堆疊,咁我地可以用 Git 既 rebase 功能。
Rebase 既好處係 merge 既時候唔會產生 merge commit,可以畀我地用 fast forward merge。
git rebase <base branch>
之後有可能因為 base branch 同 feature branch 都修改過同一個檔案而出現 merge conflicts。

9.2 解決 merge conflicts

如果 master branch 有啲變更改既檔案同我地既變更改既檔案相同,而 Git 又冇辦法幫我地自動 merge,咁就會有 merge conflicts,只能自行解決。我地要話畀 Git 知我地想用邊個版本,或者開個檔案黎自行修正返:
1... 2<<<<<<< HEAD 3Hello! 4======= 5Hello World 6>>>>>>> 09f8da31045621b1c2d26d4130763e8f9253db81 7...
我地可以自己修正返個檔案,而另一個方法係用:
git mergetool
默認係用 vimdiff,但如果想方便啲既話,我地可以用一個內建 merge Git conflicts 功能既 IDE,例如 IntelliJ IDEA,又或者用一個支援 merge Git conflicts 既 file diff/merge tool,例如 Beyond Compare。
參考資料:

10 隱藏變更

10.1 暫時收埋目前既變更

如果我地做做下改動既時候,想做一啲冇關係既 Git 操作,例如將一個 branch merge 去另一個 branch,但又唔想搵個新 folder 重新 clone 過啲 source code,咁呢個 command 就好有用:
git stash
又或者:
git stash push
Git 會將我地目前既變更放到去一個臨時既儲存空間裡面。
註:如果變更包括新建立既檔案而我地又想包括埋佢地,咁就要打包入暫存區(stage)先。
之後我地行呢句 command 就會見到我地既變更唔見左:
git status

10.2 攞返變更出黎

當我地完成冇關係既操作之後,想回復返之前工作既狀態,咁我地行呢句 command 就可以攞返啲變更出黎:
git stash pop
之後我地行呢句 command 就會見到我地先前既變更喺返曬度:
git status

11 將單一檔案換成其他版本

將修改左既檔案還原:
git checkout HEAD path/to/file
將檔案換成其他 branch 既版本:
git checkout <branch> path/to/file

12 加入單一已存在既變更

如果我地想將某一個 branch 既一個變更加落呢個 branch 度,我地可以用:
git cherry-pick <commit ID>
註:行完之後,會多左一個帶有新 commit ID 既變更。

13 修改過往變更概要

我地要用 rebase 功能,而因為呢個係一個毀滅性既操作,所以 rebase 完之後要 force push。
假如最近既 2 個有個變更既概要輸入錯左,我地想修改,
git rebase -i HEAD~2
  1. Interactive rebase 會開啟文字編輯器,將 commits(HEAD~2 就會係 2 個)依時間順序由上至下咁列出黎,而每個 commit ID 既左邊都會寫住 pick
  2. 將需要修改既變更由 pick 改成 reword,完成之後撳 Esc,然後打 :wq,再拍 Enter
  3. 之後會進入變更概要既文字編輯器,我地可以喺裡面修改變更概要,完成之後撳 Esc,然後打 :wq,再拍 Enter
註:
  • ~ 後面既數字係想處理既變更數目,如果要合併 3 個變更,就要用 HEAD~3

14 本地 Git command 執行紀錄(reflog)

Reflog 係一個本地既 Git command 執行紀錄列表,而每個 command 執行紀錄都有對應既 commit ID。呢個功能可以方便我地恢復本地既 repository 去某個時間點既 commit ID。
例子:執行左 git reset --hard HEAD~5 之後 force push 左上 remote repository,但之後想還原返。呢個時候可以用:
git reflog
我地會見到一堆 commit IDs,然後可以根據紀錄還原返 git reset --hard 之前既狀態。
git reset --hard <commit ID>
註:
  • Reflog 既紀錄係有增無減,有別於 git log 既變更紀錄。
  • 利用 git reset --hard,我地可以隨意喺唔同既變更紀錄之間跳黎跳去。

15 自定義指令別名

git b 執行 git branch
git config --global --replace-all alias.b "branch"
git b
git co 執行 git checkout
git config --global --replace-all alias.co "checkout"
git co master
git amend 執行 git commit --amend --no-edit
git config --global --replace-all alias.amend "commit --amend --no-edit"
git amend
git pushf 執行 git push --force-with-lease
git config --global --replace-all alias.pushf "push --force-with-lease"
git pushf
git lg 執行 git log 連自定義顯示格式:
git config --global --replace-all alias.lg "log --pretty=format:'%C(cyan)%h%d %C(green)%ai %C(white)%<(15)%an%C(yellow)%s'"
git lg git lg --all
自定義 diff tool 用 Beyond Compare 4:
git config --global diff.tool bc git config --global difftool.bc.path "/path/to/Beyond Compare 4/BComp.exe"
自定義 merge tool 用 Beyond Compare 4:
git config --global merge.tool bc git config --global mergetool.bc.path "/path/to/Beyond Compare 4/BComp.exe"
話畀 Git 知 merge 完唔需要 backup 原檔做 .orig
git config --global mergetool.keepBackup false