# Giới thiệu về Git.
#### 0. Master branch
Mặc định, các project được create trên git sẽ có một branch tên là **master** (protected branch).
Protected branch được sinh ra để đảm bảo branch được an toàn và buộc các developer phải dùng merge requests để apply change,
thường thì các branch được protect sẽ là các branch chứa source code để deploy lên môi trường thật, các nhánh có vai trò quan trọng (development).
Do đó nên, protected branch có các đặc điểm sau:
- Chỉ có user với quyền *Master* mới được tạo branch được protected
- Chống push commit (trừ user với quyền *Master*)
- Chống *tất cả mọi người* force push (không thay đổi history của các commit trước)
- Chống *tất cả mọi người* xóa branch
Thêm thông tin về [protected branch](http://172.16.20.9/help/user/project/protected_branches).
## 1. Commit change
*TODO: Ảnh Local Operation.*
Khi muốn thực hiện một commit lên local repo, hãy dùng các command sau:
```
# List những thay đổi
> git status
# Stagged changes
> git add changed_file_1.cc
> git add changed_file_2.cc
# Nếu có file mà đã được stagged, tuy nhiên mình không muốn commit nữa, thì thực hiện discard.
> git checkout changed_file_1.cc
# Hoặc muốn reset working directory về một trạng thái ở commit nào đấy.
> git reset --hard HEAD
> git reset --hard HEAD~5
> git reset --hard 7b5cab5
> git reset 7b5cab5
# Commit to local repository.
> git commit -m "Add code for feature 1"
```
Thi thoảng, mình đang code mà đến giờ về, và mình muốn commit lên local để cho chắc chắn. Sang hôm sau, mình lại code tiếp, vẫn content đấy và mình muốn commit thêm vào commit ngày hôm qua. Lúc này mình sẽ sử dụng tham số *--amend* của câu lệnh commit.
```
> git add changed_file_3.cc
> git commit --amend
# Ngoài ra có thể amend để thay đổi nội dung của message commit, author, email.
> git commit --amend --author=="Author Name <email@address.com>"
```
**Note:**
- Mỗi commit chỉ nên chứa change cho một tác vụ nhất định, không nên gộp 2 hoặc nhiều tác vụ vào một commit, vì sẽ rất khó để theo dõi cũng như sửa xóa sau này.
Ex: Đầu tiên mình có một commit `Fix bug A, B`. Tuy nhiên sau đó, mình quyết định A không phải là bug nữa mà là tính năng, làm sao để bỏ changed ra khỏi commit?
Vì thế nên mình nên tách ra thành 2 commit `Fix bug A` và `Fix bug B`. Khi không muốn sử dụng A nữa thì chỉ cần dùng `git revert` là xong.
- Commit message nên mô tả được nội dung có thể hiểu được, đặc biệt cần tránh kiểu các message sau `small fix`, `minor fix`, `fix 1`, `fix 2`, `add changed_file.cc`, etc.
Nếu commit message dài quá, mình có thể break xuống dòng, trên git sẽ hiển thị phần additional của message trong dấu `...`
- Không commit **generated file** lên git để tránh việc nặng server và clone lâu không cần thiết. **Generated file** bao gồm: execution file, cmake file, node_modules, draft file, các file format như .vscode, etc. Có thể sử dụng `git ignore` để làm việc này một cách tự động. **Refer mục 9.**
Nên có description để hướng dẫn build hoặc các lệnh cơ bản để chạy chương trình như:
```
> make
> cmake
> npm install
```
Trong trường hợp cần các thao tác phức tạp hơn để build hay để setup môi trường develop trên máy local, hãy cố gắng viết script (*bash* or *python*).
## 2. Tạo/xóa branch
Idea là mỗi branch sẽ thực hiện develop cho một feature, một screen, hay cũng có thể là một hotfix. Mỗi người có thể thực hiện develop trên một branch khác nhau rồi sau đó merge vào branch chính, không nên để nhiều người develop trên cùng một branch, vì tránh conflict và khó quản lý.
Để tạo branch:
```
> git branch feature_1
> git checkout feature_1 # Chuyển sang branch 'feature_1'
# Hoặc có thể dùng lệnh sau:
> git checkout -b feature_1 # Tạo và chuyển sang branch feature_1
```
Để xóa branch:
```
# Xóa branch ở local
> git branch -d feature_1
> git branch -D feature_1
# Xóa remote branch
> git push origin --delete feature_1
> git push origin :feature_1
```
## 3. Check log, diff của các commit
Các lệnh để check log và commit:
```
# Show log
> git log
> git log --oneline
# Show change của commit: git show (COMMIT)
> git show HEAD
> git show HEAD~5
> git show 7b5cab5
# Show diff từ commit này đến commit kia: git diff (COMMIT_1) (COMMIT_2)
> git diff HEAD~2 HEAD
> git diff HEAD~2 HEAD ./test.cpp # Compare limit with file test.cpp
> git diff HEAD~2 # Compare working directory với commit HEAD~2
# Compare 2 branches
> git diff topic master
> git diff topic..master
```
**Recommendation:**
- Trước khi tạo merge request, bản thân developer nên review code của mình trước (bằng cách dùng lệnh diff hoặc dùng trực tiếp gitlab web) để tránh việc commit thừa, hoặc sửa những phần không cần thiết, code test, comment, etc.
- Về nguyên tắc, không chỉ người có quyền master mới có quyền review, mà bất cứ ai cũng có thể review code nếu muốn. Khi tạo merge request, nếu có thể, nên invite developer khác vào review. Hoặc ít nhất tự mình review.
- Để review, có thể dùng công cụ review sẵn có của gitlab.
## 4. Merge code
Để merge code từ branch phụ sang branch chính (ex: `feature_1` -> `master`), thực hiện theo các bước sau:
- Khi merge code từ feature, phải đảm bảo branch feature được update theo latest commit của branch master.
A -> B -> C -> D master
|
- -> E -> F feature_1
Thì trước khi merge, phải update commit C, D vào branch `feature_1`, tất nhiên vị trí phải là trước commit E, F
A -> B -> C -> D master
|
- -> E -> F feature_1
Sử dụng các câu lệnh sau:
```
# Update master để đảm bảo đây là latest code trên branch master
> git checkout master
> git pull
# Chuyển sang branch feature_1
> git checkout feature_1
# Rebase (Đẩy các commit mới của branch) master với (xuống dưới các commit của branch) feature_1
> git rebase master
# Push change lên remote repo
> git push origin feature_1
```
- Tạo merge request, và review nếu cần.
Với những branch được tạo ra phục vụ việc hotfix, hay fix bug trong khi test, thì không cần phải tạo merge request mà có thể merge trực tiếp vào branch chính. Tuy nhiên với những update kiểu như feature, tính năng mới, thì nên tạo merge request và review cẩn thận. Thêm nữa, sau này nếu trong trường hợp muốn gỡ feature ra khỏi product thì chỉ cần `revert merge commit` là được.
Để tạo merge request, vào `Repository` chọn branch `feature_1`, click `Create merge request` button, và làm theo hướng dẫn của Gitlab.
- Review
- Tiến hành rebase thêm lần nữa trước khi Accept merge request (trong trường hợp có thêm commit mới trên `master` trong thời gian review code).
- Accept merge request.
Có thể accept merge request bằng Gitlab web.
Ngoài ra mình có thể merge thông qua command line như sau:
```
# Chuyển về branch master
> git checkout master
# Merge
> git merge --no-ff feature_1 # No fast forward (recommend)
> git merge feature_1 # Fast forward (not recommend)
# Push update lên remote
> git push origin master
```
**Note:**
- Tham khảo thêm về `merge` và `rebase`:
- [https://backlog.com/git-tutorial/vn/stepup/stepup1_4.html](https://backlog.com/git-tutorial/vn/stepup/stepup1_4.html)
- [Why we should use --no-ff instead of --ff?](http://dev.bizo.com/2014/02/why-we-chose-not-to-git-fast-forward-merge.html)
## 5. Squash các commit trước khi merge
Khi commit, mình nên commit theo các function, screen lớn, chứ không nên commit kiểu tủn mủn -> khó quản lý. Tuy nhiên, trong quá trình làm việc, mình có thể bị break bởi các task khác nhau và phải commit nhiều commit cho một function, như ví dụ sau:
```
> git log --oneline
c21a5b4 (HEAD -> feature_1) Working on feature_1 end
f48b837 Working on feature_1 day 2
801c650 Working on feature_1 day 1
2f4594c (origin/master) Fix bug A
```
Khi đó mình nên thực hiện thao tác squash 3 commit `c21a5b4`, `f48b837` và `801c650` lại với nhau trước khi push lên remote. Sử dụng lệnh sau:
```
> git rebase -i HEAD~2
pick c21a5b4 Working on feature_1 end
pick f48b837 Working on feature_1 day 2
pick 801c650 Working on feature_1 day 1
# Rebase 801c650..c21a5b4 onto d286baa
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
```
Sau khi edit và show log mình sẽ được kết quả như sau:
```
> git log --oneline
d286baa (HEAD -> features_1) Implement feature 1
2f4594c (origin/master) Fix bug A
```
## 6. Không được thay đổi history trên `master`, làm thế nào để bỏ một commit?
Vì `master` là protected branch, nên mình không được phép `force push` (thay đổi history) trên `master`. Tuy nhiên trong một số trường hợp, mình muốn bỏ một commit thì hãy sử dụng `revert`.
```
> git log --oneline
d286baa (HEAD -> master) Implement feature 1
51612a4 Fix bug A
5049f9f Update config file for notify error to Chatwork
97alos2 Correct interface between api and bpex_core
# Mình muốn revert commit `Update config file for notify error to Chatwork`
> git revert 5049f9f
> git log --oneline
f48b837 (HEAD -> master) Revert commit 5049f9f
d286baa Implement feature 1
51612a4 Fix bug A
5049f9f Update config file for notify error to Chatwork
97alos2 Correct interface between api and bpex_core
```
## 7. git stash
Thi thoảng, khi đang làm việc trên một project, bạn muốn switch sang một branch khác để thực hiện một minor change. Tuy nhiên, vấn đề là bạn không muốn thực hiện commit vì minor change chỉ mất khoảng 20', sau đó bạn sẽ quay lại đây để thực hiện tiếp công việc. Trong trường hợp này hãy sử dụng `git stash`.
```
> git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: index.html
#
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
#
# modified: lib/simplegit.rb
#
> git stash
Saved working directory and index state \
"WIP on master: 049d078 added the index file"
HEAD is now at 049d078 added the index file
(To restore them type "git stash apply")
> git status
# On branch master
nothing to commit, working directory clean
# Ngoài ra có thể thêm message để mô tả stash
> git stash save “Your stash message”.
```
Để xem tất cả các stash được lưu trong stack
```
> git stash list
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051 Revert "added file_size"
stash@{2}: WIP on master: 21d80a5 added number to log
```
Lấy data ở trong stack đổ ra working directory.
```
# Lấy stash@{0}
> git stash apply
# On branch master
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
#
# modified: index.html
# modified: lib/simplegit.rb
#
# Nếu muốn apply stash@{2}
> git stash apply stash@{2}
# Drop stash ref ra khỏi stack
> git stash drop stash@{2}
# Pop = Apply + Drop
> git stash pop stash@{2}
```
Show content của một stash
```
> git stash show stash@{0}
```
Clear all stash in repo. Becareful! It's impossilbe to revert.
```
> git stash clear
```
## 8. git cherry-pick
Lệnh này chỉ đơn giản là pick một commit apply vào current branch
```
# git cherry-pick (COMMIT)
> git cherry-pick 51612a4
```
## 8. gitignore
Để giải quyết vấn đề không commit generated file, git đã cung cấp cho ta công cụ `gitignore`. Khi add một file mới vào git, git sẽ kiểm tra nếu file nằm trong file `.gitignore` thì nó sẽ lờ file đấy đi. Chú ý là file phải không được cache nữa (git sẽ cache tất cả các file trong working directory để check change).
Các pattern thường dùng:
- Sử dụng `#` để comment và có thể cách dòng.
- Khi ignore thư mục nên có ký tự `/` ở sau tên thư mục để phân biệt thư mục với file, symbolic link.
- Dấu `!` nghĩa là phủ định: !folder_name/example.exe
- Dấu `*` như trong linux: **.xml* nghĩa là tất cả các file có đuôi xml.
- Dấu `**` để dùng trong trường hợp thư mục không cần định rõ tên: ***/foo.xml* nó chỉ có hiệu lực cho tất cả các file foo.xml ở tất cả mọi nơi trong working directory.
Câu lệnh:
```
# Tạo file .gitignore ở root của working directory
> touch .gitignore
# Sample .gitignore file cho C++ project
# # Prerequisites
# *.d
# # Compiled Object files
# *.slo
# *.lo
# *.o
# *.obj
#
# # Precompiled Headers
# *.gch
# *.pch
#
# # Compiled Dynamic libraries
# *.so
# *.dylib
# *.dll
#
# # Fortran module files
# *.mod
# *.smod
#
# # Compiled Static libraries
# *.lai
# *.la
# *.a
# *.lib
#
# # Executables
# *.exe
# *.out
# *.app
# Remove cache đã được lưu từ trước
> git rm -r --cached .
```
**Note:** Phạm vi ảnh hưởng của `gitignore`
File .gitignore sẽ ảnh hưởng đến các file và thư mục anh em với nó hoặc là con cháu, chắt của nó. Thường thì project chỉ cần 1 file .gitignore ở ngoài cùng là đủ nhưng nếu project quá lớn ta có thể tách file .gitignore vào từng folder nhỏ để dễ quản lý.