Git & Github convention
===
## 1. Fork
### a. Vấn đề hiện tại
- Quá nhiều branch dư thừa trên repo chính (2000 branch) do tất cả mọi người đều có quyền write (tạo branch trên repo chính) tuy nhiên sau khi merge branch đó vào các branch chính như (production, staging, dev, dev-optimize, ...) thì không xóa branch đó đi. Còn nếu merge branch sau đó xóa thì có thể dẫn đến sau này cần khôi phục lại code trên branch đó sẽ không được vì nó đã bị xóa.
- Membrer cũng không có nơi để tự do thử nghiệm các tính năng của Github như Github Action, Webhooks, Codespace, ... do các tính năng này sẽ cần các user có quyền mới vào xem và config được.
### b. Giải pháp đề xuất và ưu điểm
- Fork project cho phép tại ra một bản copy từ repo chính về thành repo cá nhân, ví dụ:
Repo chính: `gempages/gempages-editor` => `HarrisonD-Seal/gempages-editor`
- Bằng cách fork repo sẽ thay đổi Github flow và sẽ loại bỏ được các branch dư thừa trên repo chính và branch của member nào sẽ tồn tại trên chính forked repo của member đó thay vì repo chính. Sau này khi merge xong cũng không cần xóa đi.
- Sau khi đã fork repo chính về repo cá nhân thì lúc này member có toàn quyền quyết định/thao tác trên repo cá nhân đó, thỏa sức sáng tạo, thử nghiệm.
- Sau khi fork repo về thì forked repo vẫn có liên kết với repo chính nên các thao tác như tự động gợi ý tạo pull request đến các branch trên repo chính vẫn sẽ hoạt động tương tự.
- Sau khi fork về repo cá nhân, tất cả các member có quyền read trong repo chính sẽ đều xem được danh sách các member đã fork repo chính về cũng như truy cập vào forked repo của member đó để xem code.
- Tương lai có thể làm plan back-up cho Github Action trong trường hợp repo chính bị chạy quá giới hạn (setup Github Action trên repository đã clone và reviewer có thể bấm vào branch đó để có thể xem được kết quả chạy Github Action).
### c. Hướng dẫn

1. Tất cả các member sẽ tiến hành fork repo chính về repo cá nhân bằng cách:
- Chọn **Fork** ở góc trên bên phải repo chính.
- Sau khi chọn **Fork** sẽ chuyển qua màn hình **Create a new fork**, ở đây lưu ý đổi phần **Owner** về cá nhân sau đó chọn **Create fork**.
- Sau khi tạo fork thành công sẽ tự chuyển về repo đã được fork về thành repo cá nhân (có thể check thông tin này bằng góc trên bên phải sẽ thấy phần text lớn sẽ hiển thị tên repo với owner là bạn, bên dưới sẽ có thêm phần mô tả **fork from ...** là biểu hiện repo mà ta fork code đồng thời thể hiện sự liên kết giữa 2 repo).
2. Sau khi đã fork repo về, lúc này ta sẽ tiến hành clone code từ forked repo (repo cá nhân) thay vì clone trực tiếp từ repo gốc. Ta làm như vậy vì:
- Mặc định khi ta clone từ một repo về thì Git sẽ tự tạo ra một `remote source` là `origin`. Theo cách làm thông thường ta sẽ push code lên theo cú pháp `git push origin feature/a` vì thể để giữ được thói quen cũ ta sẽ clone trực tiếp từ forked repo để không phải sửa lại phần này.
- Tiếp đó ta cần tạo thêm một `remote source` đến repo chính để sau này khi PR của chúng ta đã được merge lên repo chính, ta có thể pull lại branch tương ứng mà ta đã tạo PR vào đề đồng bộ code với local (lưu ý ở đây ta không cần thiết phải đồng bộ code trên repo chính về fork repo vì repo fork đó đóng vai trò trung gian). Để làm điều này ta sẽ chạy lệnh:
```
git remote add [name] [source-repo-ssh]
```
Trong đó:
- [name]: là tên tùy chọn (thường là upstream) tương ứng với `origin`
- [source-repo-ssh]: là ssh của repo chính, để lấy link này, ta vào repo chính, bấm nút clone và copy link
Trường hợp trước đó đã clone repo chính rồi (đa phần với team gempages) thì ta sẽ vẫn sẽ thực hiện bước fork repo giống bước một trước, sau đó sẽ làm như sau:
- Thay đổi origin `remote source` bằng lệnh: `git remote set-url origin [forker-repo-link]`.
- Thêm `remote source` tới repo chính bằng lệnh: `git remote add upstream [main-repo-link]`.
3. Sau khi đã hoàn thành các thao tác lần lượt là fork, clone, add remote thì ta sẽ tiến hành tạo các branch như guideline để làm việc.
4. Ta tiến hành code trên các branch đã tạo như bình thường.
5. Sau khi hoàn thành một task hoặc có nhu cấu push code lên đề về nhà có thể làm tiếp hoặc push code lên cho an toàn thì ta sẽ push lên forked repo thay vì repo chính. Cú pháp lúc này tương tự như xưa: `git push origin feature/A`.
6. Khi đã hoàn thành task và push lên forked repo, ta sẽ tạo PR từ branch đó về branch cần thiết ở repo chính (thường thì Github sẽ tự động gợi ý ta tạo PR đến repo chính như xưa).
7. Sau khi tạo PR xong, các reviewer sẽ review PR đó trên repo chính và quyết định merge hay cần update.
8. Sau khi PR tạo trước đó đã reviewer merge vào repo chính, member tiến hành đồng bộ code từ branch trên repo chính đó về repo local. Ví dụ ta tạo PR vào branch `staging` trên repo chính thì sau khi PR được merge, ở dưới local branch ta sẽ chạy lệnh sau:
```
git pull upstream staging
```
Với `upstream` là tên ta đặt cho `remote source` đên repo chính ta làm ở bước 2.
=> Sau đó tiếp tục lặp lại từ bước **3** đến bước **8** cho các task về sau.
---
## 2. Merge commit
### a. Vấn đề hiện tại
- Khi `cherry-pick` code từ các branch về một branch khác thì việc nhiều PR có trong cùng một branch sẽ dẫn đến tốn effort hơn trong việc `cherry-pick`.
- Nhiều trường hợp các PR gửi lên gồm nhiều commit khác nhau và mỗi commit lại có một mục đích. Điều này có nghĩa là task trên ClickUp phân chia chưa đủ nhỏ và phải làm nhiều việc trong một task.
- Ngoài ra việc nhiều commit trong cùng 1 PR có thể là dấu hiệu của việc có quá nhiều file đã được thêm mới/ sửa/ xóa dẫn đến việc review của reviewer sẽ kém hiệp quá và tốn nhiều effort do có quá nhiều file cần review đồng thời cũng có nhiều loại logic nhỏ (nhiều công việc) cần phải nắm bắt thay vì tổng thể task.
### b. Giải pháp đề xuất và ưu điểm
- Tiến hành gộp các commit không thực sự có ý nghĩa đối với nội dung task về cùng một commit có ý nghĩa với task đó (thường là gộp về chung một commit duy nhất).
- Việc gộp commit như vậy sẽ giúp khi việc `cherry-pick` về sau nhanh gọn hơn do mỗi branch chỉ gồm 1 commit duy nhất.
- Khi gộp được thành 1 commit thì cũng thường có nghĩa là task của chúng ta đã chia đủ nhỏ và chỉ làm duy nhất một công việc.
### c. Hướng dẫn
Phần này sẽ hướng dẫn 2 cách chính để tạo ra được 1 commit duy nhất trong cho mỗi brach như sau:
- Trường hợp trước đó đang có duy nhất 1 commit và không muốn tạo thêm commit mới do add thiếu file hoặc sửa chữa nhỏ trước khi push thì ta sẽ sử dụng lệnh `git commit --amend` dùng để thêm phần sửa đổi mới vào luôn commit đã tạo trước đó, ta sẽ chạy lần lượt các lệnh:
- `git add .`
- `git commit --amend`
- Sau đó tùy hệ điều hành và editor có thể sửa lại commit message trước đó và chọn lưu là xong.
- Trường hợp trước đó có nhiều commit không cần thiết hoặc trùng nhau thì ta sẽ tiến hành gộp toàn bộ các commit đó thành 1 commit như sau:

- Giả sử trên branch của chúng ta đang có 3 commit với các message tương tự như hình nói trên và ta muống gộp 3 commit đó thành 1 thì ta sẽ copy commit ID của commit ngay trước 3 commit đó.
- Ở đây ta muốn merge 3 commit có ID lần lượt là `a10bb7e`, `b3edc1e`, `8c11cc9` thì ta sẽ chọn commit ID ngày trước các commit này là `acae910` được bôi vàng trong hình sau đó ta chạy lệnh `git reset --soft acae910`.
- Sau khi chạy lệnh nói trên thì sẽ xảy ra 2 việc:
1. Toàn bộ các commit sau commit mà ta chọn để `reset --soft` sẽ bị xóa bỏ.
2. Toàn bộ các thay đổi từ các commit mà ta muốn loại bỏ sẽ được đưa về trạng thái `stagged` (trạng thái sau khi ta chạy lệnh `git add`). Lưu ý các thay đổi của ta trên code vẫn được giữ nguyên.
- Sau đó ta có thể chạy lại lệnh `git commit -m"[commit message]"` để commit lại với message mới dành cho toàn bộ các commit đã gộp. Lưu ý nên commit theo guideline.

- Cuối cùng sau khi đã commit thì ta push code lên forker repo. Thêm một lưu ý nữa ở đây ta sẽ cần cần dùng `push -f` thì mới đẩy được code lên.
**Lưu ý quan trọng cho người mới khi thực hiện việc reset soft:**
- Nên push code lên forked repo trước khi tiến hành gộp commit trên local để tránh trường hợp chưa quen dẫn đến gõ sai lệnh có thể làm mất code thì vẫn có code đã push lên trước đó để back-up.
- Khi `reset --soft` cần copy và check kĩ lại chính xác commit ID để chạy lệnh này, tránh trường hợp `reset --soft` sai commit ID thì khá là toang và khó khắc phục.
- Cần phân gõ chính xác lệnh `git reset --soft [commitID]` vì chẳng may gõ thành `git reset --hard [commitID]` là bay luôn toàn bộ code của bản thân đã làm trước đó và chỉ có trời mới cứu được ta.
**Lưu ý chung:**
- Việc gộp commit hiện tại chưa bắt buộc 100% tuy nhiên nó là phương pháp recommend cho mọi người nên áp dụng nếu không thực sự có lý do ưu tiên hơn.
- Trường hợp không muốn gộp chung thành một commit thì các commit phải có mục đích, ý nghĩa rõ ràng. Ngoài ra nội dung commit cũng nên mô tả hẳn một task nào đó tránh các trường hợp commit message dạng `update gryffEditor.css`, `update gfModules`, ... .
---
## 3. Rebase thay cho merge
### a. Vấn đề hiện tại:
- Vì ta đang hướng tới việc 1 commit / 1 branch / 1 PR nên việc sử dụng merge sẽ hay dẫn đến tạo ra commit thứ 2 trên cùng branch/ PR đó.
- Trường hợp code của chúng ta khi muốn merge vào một branch nào đó tuy nhiên lại bị conflict với branch đó thì ta sẽ cần merge branch đó vào branch của chúng ta và resolve conflict sau đó commit lại thao tác xử lý conflict này. Điều này sẽ tạo thêm một commit không cần thiết
### b. Giải pháp đề xuất và ưu điểm
- Thay vì dùng lệnh `git merge` để đồng bộ code thì ta nên dùng `git rebase`, với việc dùng `git rebase` sẽ không tạo ra commit dư thừa khi ta resolve conflict giữa các branch
### c. Hướng dẫn
- Giả sử ta cần tạo PR từ branch `feature/A` của chúng ta vào branch `staging` và bị conflict. Lúc này thay vì quay về branch `feature/A` của chúng ta và tiến hành merge code với `staging` và tạo ra commit thừa thì ta sẽ làm như sau:
- Thực hiện lệnh `git rebase staging` trên branch `feature/A` của bạn
- Resolve conflict như bình thường.
- Sau khi resolve conflict xong cần gõ lệnh `git add .` hoặc add bằng UI trên VSCode để thêm các file đã resolve conflict vào trạng thái `stagged`.
- Cuối cùng ta gõ lệnh `git rebase --continue` để kết thúc quá trình rebase này. Khác với merge thì kết thúc quá trình rebase sẽ không tạo ra commit thừa.
**Lưu ý quan trọng cho người mới sử dụng rebase:**
- Trường hợp bạn đã gõ lệnh `git rebase [branch-to-rebase]` và trong quá trình resolve conflict cảm thấy có điều gì đó không đúng, không hợp lý thì bạn có thể hủy và roll-back lại như cũ bằng cách chạy lệnh `git rebase --abort`.
---
## 4. PR guideline
### a. Vấn đề hiện tại:
- Việc tạo PR bao gồm commit message và branch name hiện tại đang chưa có tiêu chuẩn chung thống nhất giữa các team.
- Việc không có tiêu chuẩn chung sẽ dẫn đến việc quản lý trở nên khó khăn hơn và có thể gặp các vấn đề trong tương lai.
### b. Giải pháp đề xuất và ưu điểm
**Đối với việc tạo branch trên local repo "phải" tuân theo quy tắc sau:**
```
[taskId]/[type]/[purpose-of-branch]
```
Trong đó:
- [taskId]: Là ID của task lấy từ Clickup, việc chèn taskId vào như vậy có thể sẽ mang đến lợi ích sau này khi muốn tìm lại branch tương ứng với một task nào đó trên ClickUp do quên điền PR link vào ClickUp
- [type]: Mô tả loại của branch, gồm:
- feat: dành cho branch làm tính năng mới.
- fix: dành cho branch fix bug.
- chore: dành cho các branch sửa các đoạn code li ti, tí hon không ảnh hưởng đến logic và không có task trên ClickUp (Hãy luôn luôn tạo task trên ClickUp vì nó đóng vai trò như giấy trắng mực đen cho mọi hành đông :v).
- refactor: dành cho các branch liên quan đến việc refactor code hoặc các cập nhật mới cho tính năng.
- test: dành cho các branch với mục đích bổ sung unit test cho hệ thống
- [purose-of-branch]: Là mục đích chính của branch đó, ví dụ làm công việc x, làm tính năng y, mô ta này sẽ là tiếng anh, các từ cách nhau bởi dấy `-` và thường có thể lấy tương tự nội dung trên task từ ClickUp
Với cách tạo branch như trên, khi nhìn vào một branch từ tin nhắn trên tự động trên slack, reviewer và chính bản thân người tạo ra branch đó sau này có thể dễ dàng nắm được overview về branch đó được sinh ra với mục đích gì và bắt nguồn từ task nào trên ClickUp.
**Đối với việc tạo PR "phải" làm theo nguyên tắc sau:**
- Gắn link task ClickUp tương ứng với PR đó
- Gắn link PR sau khi tạo lại vào task trên ClickUp
Việc làm nói trên sẽ giúp việc trace qua lại giữa ClickUp và Github cho các task dễ dàng nhanh chóng và thuận lợi nhất có thể.
**Đối với việc tạo commit message "có thể" làm theo nguyên tắc sau:**
```
[[taskId]] [type]: [purpose of tag]
```
VD:
```
[3rtxyuh] feat: trigger gem meter after apply global style preset
```
Nội dung mỗi phần tương tự như phần tạo branch. Với việc tạo commit message như trên thì sau này khi tạo PR lên production hoặc staging có thể dễ dàng nhìn cũng như nắm bắt được nội dung toàn bộ các commit chuẩn bị merge, ngoài ra commit message như trên cũng góp phần cho phần tạo mô ta khi chạy release tag được rõ ràng và thống nhất hơn.
---
## 5. Label & Milestone
### a. Vấn đề hiện tại:
- Công việc `cherry-pick` diễn ra thường xuyên vì thể cần đảm bảo các yếu tố khi `cherry-pick` như sau:
- Cherry-pick đủ các PR của tính năng mong muốn để tránh tình trạng thiếu code, mất code, tính năng chạy sai.
- Cherry-pick đúng thứ tự được merge vào để tránh tình trạng code đè lên nhau, cherry-pick commit này làm xóa code commit khác.
- Hiện tại khi `cherry-pick` chủ yếu sẽ có 2 cách:
- Tạo một branch chung và PR của ai tạo người dó tự đi cherry-pick => Sai thứ tự
- Để cherry-pick đủ phải đổi chiếu từng task trên ClickUp => Tốn effort
- Trường hợp ClickUp thiếu link PR => Thiếu commit
### b. Giải pháp đề xuất và ưu điểm
- Đánh Label cho toàn bộ các PR tạo ra:

- Việc đánh label như nói trên sẽ hỗ trợ cho việc `cherry-pick` được đẩy đủ các PR và đúng theo thứ tự merge PR bằng cách chọn filter theo Label của tính năng đó:

- Lúc này ta chỉ việc mở lần lượt các PR từ dưới lên trên và `cherry-pick` lần lượt các commit. Ở đây nếu ta áp dụng phương pháp 1 commit / 1 PR thì việc `cherry-pick` sẽ vô cùng hiệu quả và nhanh chóng.
### c. Hướng dẫn
- Để việc đặt Label được thống nhất và hiệu quả, ta sẽ đặt theo quy tắc`FeatureName - verion`, ví dụ:
- `ProductAssigment - 1.0`
- `GemMeter - 1.0`
- `FreePlan - 1.0`
- Trường hợp các PR dùng đề fix bug ta sẽ đặt theo quy tắc `Bug - FeatureName`, ví dụ:
- `Bug - ProductAssigment`
- `Bug - GemMeter`
- `Bug - Common`: dành riêng cho các lượt fix bug chung không thuộc về tính năng cụ thể nào
**Lưu ý:**
- `FeatureName` đặt quy tắc viết liền và format như ví dụ trên
- `FeatureName` cần gắn cụ thể cho nhóm tính năng đó, ví dụ nếu tính năng đó là bổ sung free plan cho phần pricing model đang có thì nên đặt là `FreePlan` thay vì `PricingModel`
- `version` sẽ có dạng `1.0`, `1.1`, `1.2`, ... , sở dĩ cần version vì với hầu hết các tính năng sẽ có nhiều lần phát triển khác nhau tương ứng với nhiều phiên bản khác nhau. Việc đặt verison giúp ta dễ filter chính xác phiên bản của tính năng đó ta cần lấy, tránh lẫn lộn giữ các bản.
- Với các PR fix Bug sở dĩ ta không cần đặt phiên bản vì các PR này sẽ được đẩy lên sớm và riêng lẻ nên không cần cherry-pick nên sẽ không nhất thiết phải đánh version.
- Các Label cho tính năng nên được tạo trước khi team bắt đầu code để thống nhất về tên và format, tránh trường hợp cùng một tính năng nhưng có nhiều label khác nhau.
Ngoài việc đánh label cho PR, ta cũng nên đánh thêm `Milestone` cho các PR đó, `Milestone` sẽ dùng để đánh dấu PR đó nằm trong sprint nào của chúng ta.
- Trường hợp chẳng may branch dev bị reset để đồng bộ lại với production thì ta cũng dễ dàng filter lại các PR cũ theo `Milestone` để tạo PR lại vào branch.
- Ngoài ra với các PR có Label là `Bug - xxx` thì `Milestone` sẽ giúp chúng ta tracking được PR đó thuộc sprint nào và của squad nào.
- Quy tắc đặt `Milestone`: `[SquadName] - Sprint x`, ví dụ:
- `[OptimizeSquad] - Sprint 1`
- `[CustomerSquad] - Sprint 2`
Ngoài ra, có một số label phụ dành cho các PR mang tính release lên production, release để UAT: `Release`, `UAT` thì các PR trên cần đính thêm tag này để phân biệt, ví dụ một PR dùng để merge tính năng GemMeter verison 1.2 lên production sẽ gồm 2 label: `GemMeter - 1.2` `Release`:

---
## 6. Một số thắc mắc và giải đáp
### a. Tại sao lại cần cherry-pick?
- Trong mỗi sprint, thường các team dev sẽ làm nhiều hơn một công việc có thể là `Feat X`, `Update Y`, `Fix Z` với tính năng lớn `Big Feat A` làm nhiều sprint. Toàn bộ code cho các phần trên trong sprint đó đầu tiên sẽ được merge vào branch **dev** để dev và QA có thể test trước.
- Đến cuối sprint thì thường chỉ có code cho `Feat X` hoặc `Update Y` hoặc `Fix Z` hoặc cả 3 sẽ được ưu tiên mang đi UAT hoặc release trước. Lúc này sẽ không thể merge cả branch **dev** lên branch **staging**, đây là lúc chúng ta cần cherry-pick các PR cần thiết ra một branch riêng mới sau đó mới tạo PR từ branch riêng đó vào **staging**.
### b. Tại sao toàn bộ PR liên quan đến feature X không tạo sẵn một branch X để sau merge branch X đó vào staging?
Flow như sau:
- Tạo sẵn một branch **release-feature-X** để sau này toàn bộ các PR liên quan đến tính năng `X` đó sẽ vừa được merge vào đồng thời cả branch **dev** và branch **release-feature-X**.
- Đến cuối khi release chỉ cần merge thẳng **release-feature-X** lên **staging** thâm chí **production**.
Trong trường hợp mỗi khi PR của ta merge vào **dev** không bị conflict thì mới khả thi, còn trường hợp ta merge vào **dev** bị conflict ta sẽ phải rebase lại với branch **dev** để xử lý conflict đó và rồi PR đó của ta sẽ mang theo code của **dev** và merge vào **release-feature-X** => Lẫn các tính năng đang có trên **dev** và không thể merge trực tiếp lên **staging**.
---
## 7. Final note
- Việc apply quy trình mới đôi khi sẽ khiến mọi người cảm thấy không thoải mái và tốn thêm effort, tuy nhiên sau 1 sprint khi mọi người đã quen thì mọi thứ sẽ vô cùng dễ dàng và mang lại nhiều lợi ích như mô tả ở trên.
- Trong quá trình apply nếu bạn thấy có điều gì không hợp lý thì hãy raise lên để team cùng bàn luận tìm hướng giải quyết hoặc điều chỉnh quy trình.
- Khi tạo PR ta cần nhớ thêm một số bước:
- Đánh `label` tương ứng với PR
- Đánh `milestone` tương ứng với squad, sprint
- Đính link ClickUp vào PR
- Ngược lại reviewer cũng cần check lại các yếu tố trên trước khi bắt đầu review về coding convention và logic.