Trong bài viết này, mình sẽ không đào quá sâu vào Git. Mục tiêu của mình đơn giản là gom lại những kiến thức đủ dùng để làm việc hằng ngày, và chia sẻ lại như một cách để mình tự ôn tập.
Nếu bạn mới học Git, đừng cố nuốt hết mọi thứ cùng lúc. Git có rất nhiều khái niệm hay, nhưng để bắt đầu thì chỉ cần nắm vài lệnh cốt lõi và hiểu chúng đang làm gì là đã ổn hơn rất nhiều rồi.
Hiểu cơ bản về Git
Chúng ta đều biết Git dùng để quản lý mã nguồn, nhưng quản lý như thế nào?
Hiểu đơn giản, bạn có thể tưởng tượng Git có 4 tầng:
- Working directory: code đang nằm trên máy bạn.
- Staging: khu vực “xếp hàng” trước khi commit.
- Local repository (.git): nơi Git lưu trữ toàn bộ lịch sử commit, branch và metadata của dự án trên máy.
- Remote repository: bản sao của repository trên GitHub, GitLab, Bitbucket hoặc bất kỳ server Git nào.

Luồng làm việc thường là: sửa file ở Working directory → chọn những thay đổi muốn ghi nhận vào Staging → commit để tạo một mốc lịch sử → push lên Remote để đồng đội cùng xem hoặc cùng làm tiếp.
Tư duy này rất quan trọng vì ban đầu mình cũng hay bị lẫn giữa mình đã “sửa file”, “add file”, “commit file” và “mình đã push file”. Mấy bước này nhìn thì gần nhau, nhưng ý nghĩa hoàn toàn khác nhau.
.gitignore — người bạn thầm lặng
Trước khi đi vào các lệnh, có một file bạn nên biết ngay từ đầu: .gitignore.
Đây là file dùng để nói với Git “Đừng theo dõi những thứ này nhé”, Thường là các file như:
node_modules/
.env
.DS_Store
__pycache__/
*.log
Nếu không có .gitignore, bạn rất dễ vô tình commit cả thư mục node_modules nặng vài trăm MB, hoặc tệ hơn là push file .env chứa API key lên GitHub công khai. Chuyện thường gặp khi “vai cốt” :D
Khi tạo repo mới trên GitHub, bạn có thể chọn template .gitignore sẵn theo ngôn ngữ/framework. Còn nếu muốn tự tạo thì có cái này khá hay gitignore.io.
Các lệnh khai mở thiên nhãn
git init
Đây là lệnh tạo một repository Git mới trong thư mục hiện tại. Nó giống như việc bạn bảo Git “từ giờ theo dõi mọi thứ trong folder nha bro.”
Thông thường bạn chỉ dùng git init khi bắt đầu một dự án mới, hoặc khi muốn biến một thư mục có sẵn thành project Git.
git clone
git clone dùng để sao chép một remote repository về máy, thường là bước đầu khi bạn muốn bắt đầu làm việc với project có sẵn.
git clone <url>
git clone <url> folder-name # clone vào thư mục đặt tên riênggit status
git status là lệnh mình dùng rất thường xuyên. Nó cho bạn biết hiện tại đang có gì thay đổi, file nào đã được add, file nào chưa được theo dõi, và branch hiện tại đang ở đâu.
Nếu ví Git là một chuyến đi chợ, thì git status giống như việc bạn nhìn xuống giỏ đã có gì, trên kệ có món nào mới, và bạn đang đứng ở quầy nào. Trước khi git add, git commit hay git push, cứ liếc git status một cái cho nó chắc cốp.
git add
git add dùng để đưa thay đổi vào staging area, đây là bước bạn chọn những file này mình muốn đưa vào commit tiếp theo. Nếu tưởng tượng ra 4 tầng ở trên thì nó đang đưa từ Working directory lên Staging.

Một số lệnh hay gặp:
git add . # add tất cả thay đổi trong thư mục hiện tại
git add <file-name> # add một file cụ thể
git add -p # add từng đoạn thay đổi, chọn lọc hơnMình thường không add bừa hết mọi thứ nếu chưa kiểm tra kỹ. Với project lớn, việc nhìn lại git status trước khi add là thói quen rất đáng giữ. Nếu dùng VSCode thì bạn có thể check bằng UI trong phần Source Control, sẽ thấy rõ phần Changes và Staged Changes.
Nếu lỡ add nhầm file hoặc muốn trả file về trạng thái trước đó, có 2 lệnh thường dùng:
git restore <file>: trả file về trạng thái ở working directory từ commit gần nhất.git restore --staged <file>: bỏ file ra khỏi staging nhưng giữ thay đổi trong working directory.
Hai lệnh này giúp bạn chỉnh sửa giỏ hàng trước khi thanh toán, tránh mang lên máy thanh toán những món chưa muốn mua.
git commit
git commit tạo ra một snapshot của code tại thời điểm đó. Nó sẽ đẩy code từ Staging lên Local repository.

git commit -m "feat: add login page"
git commit # mở editor để viết message dài hơn
git commit --amend # sửa lại commit vừa tạo (chưa push)Khi đi làm thì mình thường viết commit message kiểu như sau:
feat: add login page
TICKET-123
Add a login page with an email/password form.
Validation is not included yet.
Một commit tốt không cần quá văn vẻ, nhưng nên đủ rõ để sau này nhìn lại còn biết mình đã làm gì. Nếu team có convention riêng thì cứ theo convention của team. Còn không thì có thể tham khảo Conventional Commits.
git push
git push đưa commit từ máy bạn lên remote — tức là đẩy code từ Local repository lên Remote repository.

git push
git push origin <branch-name> # push branch cụ thể
git push -u origin <branch-name> # push và set upstream, lần sau chỉ cần git push
git push --force # =))))Các lệnh kiểu gì cũng dùng
git log
git log dùng để xem lịch sử commit. Khi cần biết ai đã sửa gì, sửa lúc nào, hoặc một thay đổi đến từ đâu, đây là lệnh rất đáng nhớ.
git log
git log --oneline # xem nhanh, mỗi commit một dòng
git log --oneline --graph --all # vẽ cây branch, nhìn rất trực quan
git log --author="name" # lọc theo tác giảMình khuyến khích dùng git log thường xuyên, vì nó giúp bạn hiểu flow của code tốt hơn nhiều so với việc chỉ nhìn vào trạng thái hiện tại.
git diff
git diff hiển thị sự khác biệt giữa các phiên bản file, có thể là giữa working directory và staging, giữa staging và last commit, hoặc giữa hai commit cụ thể.
git diff # thay đổi chưa được staged
git diff --staged # thay đổi đã staged so với commit cuối
git diff <commit1> <commit2> # so sánh giữa hai commit
git diff master feature/login # so sánh giữa hai branchNói vậy chứ mình vẫn hay dùng UI để xem diff cho nó trực quan, khi nào không có IDE mới dùng git diff thôi :D
git fetch
git fetch chỉ lấy các thay đổi từ remote về local repository nhưng không tự động hợp nhất vào working directory. Nó hữu ích khi bạn muốn cập nhật thông tin remote (như branch mới, commit mới) mà chưa muốn merge/rebase ngay.

git fetch origin
git fetch --all # fetch tất cả remoteSau khi fetch, bạn có thể xem log của remote branch bằng git log origin/master hoặc merge/rebase thủ công.
git pull
git pull kết hợp hai bước: git fetch (lấy thay đổi từ remote) rồi git merge (gộp thay đổi đó vào branch hiện tại). Vì merge có thể sinh commit merge, mình thích dùng git pull --rebase để áp các commit local lên đầu lịch sử mới, giúp history thẳng hơn.

git pull
git pull --rebaseChọn cách nào tuỳ bạn, nếu chưa quen rebase hãy tìm hiểu cách xử lý conflict khi rebase trước khi dùng thường xuyên.
git rebase
git rebase đặt các commit của bạn lên một base mới, nói cách khác là viết lại lịch sử để có một dòng commit thẳng. Mục tiêu thường là giữ lịch sử sạch và tuyến tính.
git checkout feature
git fetch origin
git rebase origin/masterKhi rebase xong mà có conflict, Git sẽ dừng lại và cho bạn xử lý từng bước. Sau khi sửa xong thì git rebase --continue, muốn bỏ giữa chừng thì git rebase --abort.
Rebase khá rắc rối để giải thích gọn trong một bài note như này, nên nếu muốn tìm hiểu kỹ hơn thì xem tại git-scm.com/docs/git-rebase. Chắc sau bài này mình sẽ viết thêm bài về git rebase và branching các kiểu :P
git merge
git merge trộn lịch sử từ hai nhánh lại với nhau. Khác với rebase, merge tạo ra một “merge commit” ghi lại điểm hội tụ của hai nhánh, nên lịch sử sẽ có dạng nhánh cây thay vì đường thẳng.
git checkout master
git merge feature/loginMình hay dùng UI cho thao tác này, nhất là khi muốn nhìn rõ branch nào đang nhập vào branch nào.
Xử lý conflict
Dù dùng merge hay rebase, conflict vẫn có thể xảy ra khi hai người cùng sửa một đoạn code. Git sẽ đánh dấu vùng bị conflict như sau:
<<<<<<< HEAD
code của bạn ở nhánh hiện tại
=======
code từ nhánh kia
>>>>>>> feature/login
Cách xử lý:
- Mở file bị conflict, tìm các đoạn được đánh dấu như trên.
- Quyết định giữ lại phần nào của bạn, của người kia, hoặc kết hợp cả hai.
- Xóa các dòng marker (
<<<<<<<,=======,>>>>>>>). git add <file>rồi tiếp tục (git merge --continuehoặcgit rebase --continue).
VSCode có giao diện xử lý conflict khá trực quan, sẽ hiển thị nút Accept Current, Accept Incoming, Accept Both ngay trong editor. Mình thường dùng cách đó cho nhanh.
git stash
git stash rất hữu ích khi bạn đang sửa dang dở nhưng cần chuyển sang việc khác gấp. Nó giống như cất tạm đống thay đổi vào ngăn kéo để sau này mở ra làm tiếp.
git stash # cất thay đổi vào stash
git stash pop # lấy ra và áp lại thay đổi gần nhất
git stash list # xem danh sách các stash
git stash apply stash@{2} # áp một stash cụ thể mà không xóa nó
git stash drop stash@{0} # xóa một stashVí dụ bạn đang debug một lỗi, nhưng cần cherry-pick một commit khác về để sửa lỗi khác gấp. Lúc đó chỉ cần stash đống changes đang dở vào ngăn kéo, cherry-pick commit kia về xử lý, xong rồi git stash pop để mở lại phần đang làm.
git squash
Squash thường được dùng để gộp nhiều commit nhỏ thành một commit gọn hơn. Khi tạo pull request trên GitHub, bạn thường sẽ thấy các lựa chọn như Merge, Rebase, hoặc Squash and merge.
Nếu feature branch của bạn có quá nhiều commit kiểu “wip”, “fix typo”, “update” thì squash là cách khá sạch để dọn lịch sử trước khi đưa vào master branch.
git cherry-pick
git cherry-pick dùng khi bạn muốn lấy đúng một commit cụ thể từ branch khác mang sang branch hiện tại.
git cherry-pick <commit-hash>
git cherry-pick <hash1> <hash2> # lấy nhiều commit
git cherry-pick <hash1>..<hash2> # lấy một dải commitNếu bạn từng dùng Gerrit thì chắc sẽ thấy cherry-pick rất quen. Nó cực kỳ tiện khi cần backport một bản fix nhỏ, hoặc lấy riêng một thay đổi mà không muốn merge cả một loạt commit liên quan.
git branch
git branch dùng để xem, tạo, hoặc xóa nhánh. Trong thực tế hầu như mọi việc nên được làm trên branch riêng thay vì đụng trực tiếp vào master branch, cơ mà ở project mình làm thì VCS dùng Gerrit và mọi người sẽ làm chung trên master branch nên mấy lệnh branching này cũng ít sờ vào.
git branch # xem danh sách branch local
git branch -a # xem cả remote branch
git branch <branch-name> # tạo branch mới (chưa chuyển sang)
git branch -d <branch-name> # xóa branch đã merge
git branch -D <branch-name> # xóa branch kể cả chưa merge (cẩn thận)Branch giúp bạn tách công việc ra rõ ràng, dễ review hơn, và giảm rủi ro phá nhầm code đang ổn.
git checkout
git checkout là lệnh “đa năng” của Git, nó vừa chuyển nhánh, vừa tạo nhánh mới, vừa khôi phục file. Chính vì làm được quá nhiều việc nên đôi khi gây nhầm lẫn.
git checkout <branch-name> # chuyển sang branch đã có
git checkout -b <branch-name> # tạo branch mới và chuyển sang luôn
git checkout <commit-hash> # chuyển về một commit cụ thể (detached HEAD)
git checkout -- <file> # khôi phục file về trạng thái commit gần nhấtBạn vẫn sẽ gặp git checkout rất nhiều trong tài liệu cũ, tutorial, hoặc khi đã quen tay dùng nó. Nên biết để đọc hiểu là ổn. Còn trong thực tế, Git đã tách chức năng của checkout ra thành 2 lệnh rõ ràng hơn: git switch để chuyển nhánh và git restore để khôi phục file. Mình khuyến khích dùng 2 lệnh này thay thế.
git switch
git switch là lệnh được sinh ra để chuyên biệt cho việc chuyển nhánh, đọc dễ hơn và đúng mục đích hơn git checkout (vốn làm được quá nhiều việc, đôi khi gây nhầm lẫn).
git switch <ten-branch> # chuyển sang branch đã có
git switch -c <ten-branch> # tạo branch mới và chuyển sang luôn
git switch - # quay về branch vừa rờiMình hay dùng git switch -c feature/add-login khi bắt đầu làm tính năng mới. Nhanh, gọn, không cần nhớ thêm flag nào của checkout.
git reset
git reset là lệnh nên dùng có ý thức, vì nó có thể thay đổi lịch sử hoặc làm mất thay đổi local nếu dùng sai.
Ba mode hay gặp:
# Quay lại 1 commit, giữ thay đổi ở staging
git reset --soft HEAD~1
# Quay lại 1 commit, bỏ staging nhưng giữ thay đổi ở working directory (mặc định)
git reset HEAD~1
# Quay lại 1 commit và mất tất cả thay đổi chưa commit
git reset --hard HEAD~1Tóm tắt:
--soft: commit biến mất, thay đổi vẫn còn trong staging, tiện nếu muốn viết lại commit message.--mixed: commit biến mất, thay đổi về working directory, không còn staged, tiện nếu muốn chỉnh sửa thêm.--hard: commit biến mất, thay đổi mất luôn, chỉ dùng khi chắc chắn 100%.
Nếu lỡ tay --hard thì còn cứu được bằng git reflog để xem lịch sử của HEAD, rồi git reset --hard <hash> để quay lại. Git vẫn giữ các commit trong một thời gian trước khi dọn dẹp.
Flow mình thường dùng hằng ngày
Mỗi khi mở repo lên làm việc, flow mình hay đi theo khá đơn giản:
git pull --rebaseđể kéo code mới nhất về trước khi bắt đầu.git switch -c feature/abcđể tạo branch riêng cho công việc mới nếu cần.git logđể nhìn nhanh lịch sử, xem branch của mình đang nằm ở đâu.- Tiếp tục code. Trước khi tạo commit thì pull lại một lần nữa nếu project đang có nhiều người đụng vào.
- Nếu cần đổi việc gấp,
git stashđể cất tạm thay đổi, xử lý việc kia rồigit stash popđể mở lại. - Nếu có conflict thì xử lý ngay ở local trước khi push, xử lý sớm luôn dễ hơn đợi đến lúc mọi thứ chồng lên nhau.
git push -u origin <branch>rồi tạo pull request.
Nói ngắn gọn, mình thích giữ thói quen cập nhật branch thường xuyên và kiểm tra lịch sử bằng git log. Hai việc này nghe nhỏ nhưng giúp giảm khá nhiều rắc rối khi làm việc team.
Tips
Trên VS Code, mình cài GitLens để xem history, blame và diff tiện hơn. Extension giúp làm việc với Git thoải mái hơn rất nhiều, nhất là khi cần lần ngược lại xem dòng code này được ai sửa, sửa khi nào và vì sao.
Ngoài ra còn một số thứ nhỏ mà mình thấy hữu ích:
- Dùng
git commit --amendđể sửa commit vừa tạo (khi chưa push) thay vì tạo thêm một commit “fix typo”. - Gạch đầu dòng này chưa nghĩ ra, khi nào lên Senior update tiếp :P