---
# System prepended metadata

title: Git 使用建議
tags: [Git]

---

###### tags: `Git`

Git 使用建議
===========


> 小弟之前有花蠻多時間研究 Git 的操作及用法，略有一些心得，提出一些建議供各位參考:

## 1. Git commit message

### Message 撰寫相關資訊建議:
   - 第一行為標題/主題(subject)，盡量不要超過 50 個字元，內容簡單一句話帶過但需包含修改重點
       - 標題開頭為類別
       - 標題不以句點結尾
   - 第二行為==一個空白行==, 空白行分隔標題與內容
   - 第三行為內文，每行建議 72 字以內
   - 內文解釋至少包含 why、how

### Format

```
<type>:<空格><action><空格><subject>
//空一行
<--! 修改內容描述開始 -->
原因(why):

調整項目(how):

<--! 修改內容描述結束 -->
<issue tracking ID>

```
### Type 類別定義

* Feature
* Docs
* UI
* Barcode
* FPGA
* CnvEngine
...
::: warning
  需討論Fucntion 的分類
::: 

 
### Action 定義

* add (新增;addition)
* mod (修改;modify)
* rev (修正;Revise)
* del (刪除;delete)
* refactor (重構)

### Example:
```  =
UI: 修改 Qt 內建 Dialog 物件使用 Page 改寫

原因:
    由於虛擬鍵盤的　page scoll 功能只能在page component 使用,
    若 Dialog 內建包含 Inputbox/Textfield 物件,開啟鍵盤將會擋住該物件,看不到輸入的數值.

調整項目:
  1. 改寫 DialogBase.qml
  2. 修改所有引用 DialogBase 物件的qml 檔

issue #1234
```
### 建立 reopsitory 共用的 "git commit meassge template" 檔案

``` =
vi .gitCommitTemplate/git-commit-template
```

新增message 內容,例如:

``` =
<type>:<空格><action><空格><subject>

原因:
  1. 
  
調整項目:
  1. 
  
# issue #xxxx
# type:
#   Feature
#   Docs
#   UI
#   Barcode
#   FPGA
#   CnvEngine
# action:
#  add (新增;addition)
#  mod (修改;modify)
#  rev (修正;Revise)
#  del (刪除;delete)
#  refactor (重構)
```
::: info
"#" 開頭為註解, git commit時, "#" 開頭那一行不會被當成訊息資訊
:::

Syntax:

``` =
git config --global commit.template <.git-commit-template file path>
```

example:

``` =
git config --global commit.template .gitCommitTemplate/git_commit_template
```

### 建立 message hooks (具強制性)

* 使用 Aangular Js 的 git hook script

``` python =
#!/usr/bin/env python
"""
Git commit hook:
 .git/hooks/commit-msg
 Check commit message according to angularjs guidelines:
  * https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
"""

import sys
import re

valid_commit_types = ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore', ]
commit_file = sys.argv[1]
help_address = 'https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#'

with open(commit_file) as commit:
    lines = commit.readlines()
    if len(lines) == 0:
        sys.stderr.write("\nEmpty commit message\n")
        sys.stderr.write("\n - Refer commit guide: %s\n\n" % help_address)
        sys.exit(1)

    # first line
    line = lines[0]
    m = re.search('^(.*)\((.*)\): (.*)$', line)

    if not m or len(m.groups()) != 3:
        sys.stderr.write("\nFirst commit message line (header) does not follow format: type(scope): message\n")
        sys.stderr.write("\n - Refer commit guide: %s\n\n" % help_address)
        sys.exit(1)
    commit_type, commit_scope, commit_message = m.groups()
    if commit_type not in valid_commit_types:
        sys.stderr.write("\nCommit type not in valid ones: %s\n" % ", ".join(valid_commit_types))
        sys.stderr.write("\n - Refer commit guide: %s\n\n" % help_address)
        sys.exit(1)

    if len(lines) > 1 and lines[1].strip():
        sys.stderr.write("\nSecond commit message line must be empty\n")
        sys.stderr.write("\n - Refer commit guide: %s\n\n" % help_address)
        sys.exit(1)

    if len(lines) > 2 and not lines[2].strip():
        sys.stderr.write("\nThird commit message line (body) must not be empty\n")
        sys.stderr.write("\n - Refer commit guide: %s\n\n" % help_address)
        sys.exit(1)

sys.exit(0)
```

* 使用 commitlint

  * https://github.com/conventional-changelog/commitlint
  
* 使用validate-commit-msg
https://github.com/conventional-changelog-archived-repos/validate-commit-msg

### 自動生成 changelog file

若 Commit 符合 [Angular 所定義的格式](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#),可使用以下工具自動產生CHANGELOG.md:

:::info
==Angular Format:==
``` =
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```
:::
* 使用 conventional-changelog
  * https://github.com/conventional-changelog/conventional-changelog
* 使用 cz-conventional-changelog
  * https://github.com/commitizen/cz-conventional-changelog

### `commitizen`


https://juejin.im/post/5afc5242f265da0b7f44bee4

https://gist.github.com/leohxj/7bc928f60bfa46a3856ddf7c0f91ab98

https://gist.github.com/motin/5896c5b04d039aac48e6a2985d12171b
https://github.com/GoogleChrome/lighthouse/blob/master/.eslintrc.js

### 參考資料

* https://wadehuanglearning.blogspot.com/2019/05/commit-commit-commit-why-what-commit.html

* https://allen-hsu.github.io/2017/07/02/git-message-template-and-githook/

* https://backlog.com/blog/git-commit-messages-bold-daring/

* https://blog.louie.lu/2017/03/21/如何寫一個-git-commit-message

* http://www.ruanyifeng.com/blog/2016/01/commit_message_change_log.html



* 约定式提交
https://www.conventionalcommits.org/zh-hans/v1.0.0-beta.4/#%e7%ba%a6%e5%ae%9a%e5%bc%8f%e6%8f%90%e4%ba%a4%e8%a7%84%e8%8c%83

*  Git Commit Message 這樣寫會更好，替專案引入規範與範例
[https://wadehuanglearning.blogspot.com/2019/05/commit-commit-commit-why-what-commit.html](https://wadehuanglearning.blogspot.com/2019/05/commit-commit-commit-why-what-commit.html)

## 2. Git 同步更新 source code

### Git Pull (git fetch + git merge)

* git fetch: 從遠端下載最新的commit
* git merge: 合併你所指定的commit
* git pull: 做完 fetch 在自動幫你做 merge

### Git Merge
     
> git merge 預設提供兩種方式: 
  * fast-forward merge(-ff)
  * none fast-forward merge(-no-ff)

#### fast-forward merge (-ff):
::: info
快轉合併，由於Git 有時間軸的概念，通常要合併的commit 節點建立時間都比你現在來的晚，Git 提供fast-forward 的功能，讓你可以快轉到你要合併的commit，並以該commit 為base ，在後面再建立一個新的commit.

> Note: 
==fast-forward 你可以把他想成撥放器，當你要看某個時間的片段(commit)，你需要執行快轉的動作。或是你可以想像你目前的commit 跟欲合併的commit 是兩個平時時空，當你要進入他的世界，你需要把時間/時空調成跟他一樣，這樣才能看的到它，並跟他手牽手 ~~做愛做的事~~ 做該做的事 :smile:==

:::
   圖示說明:
    ![](https://i.imgur.com/wLKxGNS.png)

#### none fast-forward merge(-no-ff) : 

::: info
執行 Merge 時會自動產生一個 Merge 的 commit 並產生分支支線圖(小耳朵).
:::
圖示說明:
    ![](https://i.imgur.com/VCBS6Rv.png)
     
#### 建議事項:
    
==對於需要經常性同步的 Repository 建議使用 fast-forward merge 的方式==

::: info
需要經常性同步的 Repository並共同維護同一個分支，通常都是 Commit 會有先後順序的問題，看誰先 Commit (先佔先行的概念)，後面同步code 時，需要做merge 來更新，這個case 個人覺得 none fast-forward merge 就不是很恰當，同一個檔案一直被修改及合併，查看log 將會變得複雜，之後要用 git blame 查詢某個檔案的修改歷程，將會看到一的大堆merge 的 commit，於 trace Bug 的時候可能會造成一些困擾.
:::

Example:
![](https://i.imgur.com/R0W0aqm.png)

#### 建議操作方式:

1. 使用 git pull

後面帶參數 --rebase

``` =
git pull --rebase 
```
::: info

設定預設值為 rebase 的方法:

* 方法1. 指定某個branch 執行 git pull 時預設為 rebase 模式 (git version >= 1.7.9)

``` git
git config --global branch.<name>.rebase true
```

``` git
git config --global pull.rebase true
```

如果在master執行 git config --global pull.rebase true .git/config 設定檔將會產生: 

``` config=
[branch "master"]
  remote = origin
  merge = refs/heads/master
  rebase = true
```

* 方法2. 所有 branch 預設執行 git pull 時自動執行rebase (git version >= 1.7.9)

``` git
git config --global branch.autosetuprebase always
```

:::
2. 使用 git fetch + git merge


``` =
git fetch origin/master
git merge -ff origin/master
```

3. 使用 Smartgit

* 按下左上方的 Pull 按鈕
![](https://i.imgur.com/r707ySs.png)

* 預設為rebase mode
![](https://i.imgur.com/UHtNFve.png)


### 什麼時候要用 none fast-forward merge ?


::: info
通常用在開發一個複雜的獨立新功能，需要比較長的時間開發，不從主要分支做同步更新(各走各的路，個是平行時空)。當這個分支開發完成，需要被合併的到主要的分支的時候，建議使用 none fast-forward merge(-no-ff) 來產生線圖，較容易分辨是從主要分支的哪的節點分出來的且產生一個合併的commit 及容易區分相關性.
:::

### 合併衝突 (Merge conflict)

:::info
> 在執行合併(Merge)時,不外乎會發生程式碼衝突(conflict),請參考以下資訊:
>[merge時衝突(Conflict)操作參考](https://max_chen.gitlab.io/knowledgebase/Git/data/Base/GITMerge-Conflict//)
:::

### 參考資料

https://ihower.tw/blog/archives/3843

[Git 分支合併](https://max_chen.gitlab.io/knowledgebase/Git/data/Base/GITMerge-branchMerge/)


## 3. Git workflow

### 分支策略(效仿[Git flow Script](https://max_chen.gitlab.io/knowledgebase/Git/data/Base/GitWorkflow-gitflow/)的策略)

* 1. Develop 為開發中之版本，永不穩定。所有 Feature 開發都從這分支出去，完成後在 merge回來

* 2. Master  為發行版本，穩定版分支。Commit 只存在 develop 和 Release 的 merge commit (永遠處在 production-ready 狀態，除非重大 bug，則會分出 hotfix 分支)

Ref:
[Git flow Script](https://max_chen.gitlab.io/knowledgebase/Git/data/Base/GitWorkflow-gitflow/)

### 操作參考:

#### 更新 Source Code.

==使用 git pull --rebase==
* 1. 若你目前修改還未commit 建議您先將worktree 有異動的檔案先執行Stash

     >[Stash 操作參考](https://max_chen.gitlab.io/knowledgebase/Git/data/Base/GITStash-Stash/)

* 2. 若當下你已有數個commit節點，你需要同步code 這時你執行git pull --rebase 若有衝突需確認哪個版本才是正確的(git 不會知道你要用哪個版本)

     >[merge時衝突(Conflict)操作參考](https://max_chen.gitlab.io/knowledgebase/Git/data/Base/GITMerge-Conflict//)
     
#### Develop 開發流程操作
* 方法1. 直接在 Develop 建立修改的 commit
* 方法2(建議使用). 使用 git flow script (切出另一個分支 進行修改)
    * git flow init (只需設定一次)
    * git flow feature start 新功能分支
    * git flow feature finish 新功能分支

  >[git flow操作參考](https://max_chen.gitlab.io/knowledgebase/Git/data/Base/GitWorkflow-gitflow/#cofigure-gitflow)
  

::: info
Note:
1. git 分支名稱==可以為中文==
2. 使用 git flow script 的好處:
   * 操作簡單: ==Start== and ==Finish==
   * 由於是切出一個分支(切換另一個時空)，所以Develop 分支若有增加 commit，可以自行執行 git pull 更新 Develop 分支，你所處的 "新功能分支" 將不會有影響。等你功能開發完成，你可以執行 finish 的操作將你的修改merge 至 Develop 分支。
   
:::


    



## 4. 團隊共同開發時不要把 Git Server(e.g. Gitlab/GitHub/Gitea),當作程式碼的 FTP.

::: info
   1. Push 出去就是詔告天下，在沒有 Code review 機制下，需自行 review 修改的code。
   2. 不要 Push 一個未完成的修改 (可在本地自己一個測試分支之後再merge及rebase 至develop 分支)
   3. Commit 不是寫給自己看的, 也不是 memo
:::

## 5. 建立git code review 機制

https://hackmd.io/@MaxChen/Codereview






ref:

https://willh.gitbook.io/gitpro/7d615cb13a55ac231e35e8658a897e0e
https://gitbook.tw/interview

https://backlog.com/blog/git-commit-messages-bold-daring/
