Skip to main content

Git

Lệnh hay dùng (đủ dùng hàng ngày)

Khởi tạo & Clone

# Khởi tạo repo mới
git init

# Clone repo từ remote
git clone <url>

# Clone một branch cụ thể
git clone -b <branch> <url>

Ví dụ thực tế:

# Bạn mới join project, clone repo về máy
git clone https://github.com/company/ecommerce-api.git

# Chỉ muốn clone branch develop (không cần toàn bộ)
git clone -b develop https://github.com/company/ecommerce-api.git

Kiểm tra trạng thái

# Xem trạng thái working directory
git status

# Xem lịch sử commit
git log

# Log ngắn gọn trên 1 dòng
git log --oneline

# Log dạng graph (nhìn được flow branch/merge)
git log --oneline --graph --all

# Xem thay đổi chưa stage
git diff

# Xem thay đổi đã stage (sẵn sàng commit)
git diff --staged

Ví dụ thực tế:

# Bạn sửa xong vài file, muốn xem đã thay đổi gì
git status
# Output:
# modified: src/UserService.java
# modified: src/UserController.java
# Untracked files:
# src/UserDTO.java

# Muốn xem cụ thể sửa gì trong UserService.java (chưa add)
git diff src/UserService.java

# Đã `git add`, muốn review lại trước khi commit
git diff --staged

# Xem 5 commit gần nhất dạng ngắn gọn
git log --oneline -5
# Output:
# a1b2c3d feat: add user registration
# e4f5g6h fix: null pointer in login
# i7j8k9l refactor: extract validation logic
# m0n1o2p feat: add email service
# q3r4s5t chore: update dependencies

Stage & Commit

# Stage một file
git add <file>

# Stage tất cả thay đổi
git add .

# Commit với message
git commit -m "message"

# Stage + Commit (chỉ với file đã tracked)
git commit -am "message"

# Sửa commit cuối (message hoặc thêm file)
git commit --amend -m "new message"

Ví dụ thực tế:

# Sửa xong UserService, chỉ muốn commit riêng file này
git add src/UserService.java
git commit -m "fix: handle null email in registration"

# Sửa nhiều file liên quan, add hết rồi commit
git add src/UserService.java src/UserController.java src/UserDTO.java
git commit -m "feat: add user registration endpoint"

# Oops! Commit xong mới thấy quên thêm file test
git add src/UserServiceTest.java
git commit --amend -m "feat: add user registration endpoint"
# → Commit trước bị thay thế, giờ gồm cả file test

# Đang code nhanh, sửa file đã tracked, commit luôn không cần add
git commit -am "fix: typo in error message"

Branch

# Liệt kê branch local
git branch

# Liệt kê tất cả branch (bao gồm remote)
git branch -a

# Tạo branch mới
git branch <branch-name>

# Chuyển sang branch khác
git checkout <branch-name>
# hoặc (Git 2.23+)
git switch <branch-name>

# Tạo và chuyển sang branch mới
git checkout -b <branch-name>
# hoặc
git switch -c <branch-name>

# Xoá branch local
git branch -d <branch-name>

# Xoá branch chưa merge (force)
git branch -D <branch-name>

# Đổi tên branch hiện tại
git branch -m <new-name>

Ví dụ thực tế:

# Bắt đầu làm feature mới, tạo branch từ develop
git switch develop
git switch -c feature/user-registration

# Code xong, push lên, tạo PR. Sau khi merge xong, xoá branch local
git switch develop
git pull
git branch -d feature/user-registration

# Branch tên sai, đổi lại
git branch -m feature/user-registraton feature/user-registration

# Muốn xoá branch mà chưa merge (ví dụ: thí nghiệm thất bại)
git branch -D experiment/new-auth-flow

Merge & Rebase

# Merge branch vào branch hiện tại
git merge <branch-name>

# Rebase branch hiện tại lên branch khác
git rebase <branch-name>

# Huỷ rebase đang dở
git rebase --abort

# Tiếp tục rebase sau khi resolve conflict
git rebase --continue

Ví dụ thực tế:

# === MERGE ===
# Feature đã xong, muốn merge vào develop
git switch develop
git pull origin develop
git merge feature/user-registration
# → Tạo merge commit, giữ lịch sử đầy đủ

# === REBASE ===
# Đang code trên feature branch, develop đã có commit mới
# Muốn cập nhật develop vào feature (lịch sử sạch, không merge commit)
git switch feature/payment
git rebase develop
# → Các commit của feature sẽ được đặt LÊN TRÊN commit mới nhất của develop

# Rebase bị conflict
# Git báo: CONFLICT in src/OrderService.java
# 1. Sửa conflict trong file
# 2. git add src/OrderService.java
# 3. git rebase --continue

# Rebase loạn quá, muốn huỷ quay về trước
git rebase --abort

Khi nào dùng Merge vs Rebase?

  • merge: an toàn, giữ lịch sử gốc. Dùng khi merge feature vào develop/main.
  • rebase: lịch sử sạch, linear. Dùng khi cập nhật develop mới nhất vào feature branch CỦA BẠN.
  • Không rebase branch đã push và người khác đang dùng.

Remote (Push / Pull / Fetch)

# Xem danh sách remote
git remote -v

# Thêm remote
git remote add origin <url>

# Pull (fetch + merge)
git pull

# Pull với rebase thay vì merge
git pull --rebase

# Push branch lên remote
git push

# Push branch mới lên remote và set tracking
git push -u origin <branch-name>

# Fetch cập nhật từ remote (không merge)
git fetch

# Fetch tất cả remote
git fetch --all

Ví dụ thực tế:

# Sáng đi làm, pull code mới nhất từ develop
git switch develop
git pull origin develop

# Tạo feature branch, code xong, push lên lần đầu
git switch -c feature/order-export
# ... code ...
git commit -am "feat: add order CSV export"
git push -u origin feature/order-export
# → Lần sau chỉ cần `git push` là đủ (đã set tracking)

# Muốn xem remote có gì mới không, nhưng chưa muốn merge
git fetch origin
git log origin/develop --oneline -5
# → Xem commit mới trên develop mà không ảnh hưởng code local

# Pull nhưng dùng rebase cho sạch (không tạo merge commit thừa)
git pull --rebase origin develop

Stash (lưu tạm thay đổi)

# Lưu tạm thay đổi
git stash

# Lưu tạm với message
git stash push -m "message"

# Xem danh sách stash
git stash list

# Lấy lại stash gần nhất (xoá khỏi danh sách)
git stash pop

# Lấy lại stash gần nhất (giữ trong danh sách)
git stash apply

# Xoá tất cả stash
git stash clear

Ví dụ thực tế:

# Đang code feature dở, sếp bảo fix bug gấp trên develop
# Lưu tạm code đang làm
git stash push -m "WIP: user registration form"

# Chuyển sang develop, fix bug
git switch develop
git switch -c hotfix/login-crash
# ... fix bug, commit, push, tạo PR ...

# Quay lại feature, lấy code cũ ra tiếp tục
git switch feature/user-registration
git stash pop
# → Code đang làm dở quay lại đúng chỗ

# Xem có bao nhiêu stash đang giữ
git stash list
# Output:
# stash@{0}: On feature/payment: WIP: payment validation
# stash@{1}: On develop: experiment with caching

# Lấy stash cụ thể (không phải gần nhất)
git stash apply stash@{1}

Undo / Khôi phục

# Bỏ file khỏi stage (giữ thay đổi trong working directory)
git restore --staged <file>

# Khôi phục file về trạng thái commit cuối (mất thay đổi!)
git restore <file>

# Soft reset: quay về commit trước, giữ thay đổi ở staged
git reset --soft HEAD~1

# Mixed reset (mặc định): quay về commit trước, giữ thay đổi ở working directory
git reset HEAD~1

# Hard reset: quay về commit trước, XOÁ hết thay đổi
git reset --hard HEAD~1

Ví dụ thực tế:

# === Lỡ `git add` file không muốn ===
git add . # Oops! Add cả file debug vào
git restore --staged src/debug-test.java
# → File debug bị bỏ khỏi stage, các file khác vẫn staged

# === Sửa file lung tung, muốn quay về bản gốc ===
# CẢNH BÁO: mất hết thay đổi trong file này!
git restore src/UserService.java
# → File quay về đúng trạng thái commit cuối

# === Commit xong nhưng chưa muốn commit (muốn sửa thêm) ===
git reset --soft HEAD~1
# → Commit bị xoá, nhưng code vẫn ở staged, sửa thêm rồi commit lại

# === Commit sai, muốn uncommit và unstage luôn ===
git reset HEAD~1
# → Code vẫn trong working directory nhưng chưa staged

# === Commit sai hoàn toàn, muốn XOÁ SẠCH như chưa từng code ===
# CẢNH BÁO: MẤT HẾT CODE!
git reset --hard HEAD~1

# === Quay về 3 commit trước ===
git reset --soft HEAD~3
# → 3 commit bị gộp lại thành staged changes, commit lại 1 lần

Tóm tắt reset:

LệnhCommitStageWorking Dir
--soft❌ Xoá✅ Giữ✅ Giữ
--mixed (mặc định)❌ Xoá❌ Xoá✅ Giữ
--hard❌ Xoá❌ Xoá❌ Xoá

Lệnh nâng cao

Interactive Rebase

# Rebase tương tác n commit gần nhất (squash, reorder, edit, drop)
git rebase -i HEAD~<n>

Ví dụ thực tế:

# Bạn có 4 commit lộn xộn trên feature branch:
# a1b2c3d fix: typo
# e4f5g6h feat: add validation
# i7j8k9l fix: validation edge case
# m0n1o2p feat: add validation (tiếp)

# Muốn gộp thành 1 commit sạch trước khi tạo PR
git rebase -i HEAD~4

# Editor mở ra:
# pick m0n1o2p feat: add validation (tiếp)
# pick i7j8k9l fix: validation edge case
# pick e4f5g6h feat: add validation
# pick a1b2c3d fix: typo

# Sửa thành:
# pick m0n1o2p feat: add validation (tiếp)
# squash i7j8k9l fix: validation edge case
# squash e4f5g6h feat: add validation
# squash a1b2c3d fix: typo

# Save → Git gộp 4 commit thành 1, cho bạn sửa message mới
# Kết quả: 1 commit duy nhất "feat: add input validation"

Các action:

  • pick = giữ nguyên commit
  • reword = giữ commit, sửa message
  • squash = gộp vào commit trước, GIỮ cả 2 message
  • fixup = gộp vào commit trước, BỎ message commit này
  • drop = xoá commit
  • edit = dừng lại ở commit này, cho bạn sửa rồi --continue

Cherry-pick

# Áp dụng một commit cụ thể vào branch hiện tại
git cherry-pick <commit-hash>

# Cherry-pick nhiều commit
git cherry-pick <hash1> <hash2>

# Cherry-pick không tự động commit (chỉ stage)
git cherry-pick --no-commit <commit-hash>

Ví dụ thực tế:

# Team member fix bug trên branch khác, bạn cần lấy đúng commit đó
# về branch của mình mà không merge cả branch

# Tìm commit hash cần lấy
git log --oneline feature/other-branch
# x1y2z3a fix: solve timezone issue ← cần commit này
# b4c5d6e feat: something else

git switch feature/my-branch
git cherry-pick x1y2z3a
# → Commit fix timezone được copy vào branch của bạn

# Hotfix trên main cần backport về release/v2.0
git switch release/v2.0
git cherry-pick abc123f

Reflog (cứu cánh khi lỡ tay)

# Xem lịch sử mọi thao tác HEAD đã trỏ tới
git reflog

# Khôi phục về trạng thái trước đó
git reset --hard HEAD@{n}

Ví dụ thực tế:

# TRƯỜNG HỢP: Lỡ `git reset --hard` mất code!
git reflog
# Output:
# a1b2c3d HEAD@{0}: reset: moving to HEAD~3 ← lỡ tay reset
# e4f5g6h HEAD@{1}: commit: feat: important feature ← commit bị mất
# i7j8k9l HEAD@{2}: commit: fix: bug fix
# m0n1o2p HEAD@{3}: commit: chore: cleanup

# Muốn quay lại trạng thái trước khi reset
git reset --hard HEAD@{1}
# → Code quay lại! Commit "feat: important feature" sống lại

# TRƯỜNG HỢP: Lỡ xoá branch
git branch -D feature/important-work # Oops!
git reflog
# Tìm commit cuối của branch đó
git switch -c feature/important-work HEAD@{5}
# → Branch được khôi phục

Reflog giữ lịch sử ~90 ngày. Nếu mới lỡ tay, reflog hầu như luôn cứu được.


Bisect (tìm commit gây bug)

git bisect start
git bisect bad # commit hiện tại bị bug
git bisect good <commit-hash> # commit này chắc chắn OK
# Git checkout giữa → test → đánh dấu good/bad → lặp lại
git bisect reset # kết thúc

Ví dụ thực tế:

# Bug xuất hiện nhưng không biết commit nào gây ra
# Biết chắc version v2.1.0 (2 tuần trước) vẫn OK

git bisect start
git bisect bad # HEAD hiện tại bị bug
git bisect good v2.1.0 # tag v2.1.0 vẫn OK

# Git checkout giữa (ví dụ commit thứ 50/100)
# → Bạn test: bug có không?
# Nếu CÓ bug:
git bisect bad
# Nếu KHÔNG có bug:
git bisect good

# Git tiếp tục chia đôi... sau ~7 bước (log2 của 100)
# Git báo: "abc123f is the first bad commit"
# → Tìm được commit gây bug!

git bisect reset # quay về branch ban đầu

Tag

# Tạo lightweight tag
git tag <tag-name>

# Tạo annotated tag (khuyên dùng cho release)
git tag -a <tag-name> -m "message"

# Push tag lên remote
git push origin <tag-name>

# Push tất cả tag
git push --tags

# Xoá tag
git tag -d <tag-name> # local
git push origin --delete <tag-name> # remote

Ví dụ thực tế:

# Deploy xong version mới, đánh tag để đánh dấu
git tag -a v2.3.0 -m "Release 2.3.0: add payment module"
git push origin v2.3.0

# Checkout về 1 version cũ để debug
git checkout v2.1.0

# Xoá tag đánh nhầm
git tag -d v2.3.0-beta
git push origin --delete v2.3.0-beta

Submodule

# Thêm submodule
git submodule add <url> <path>

# Clone repo có submodule
git clone --recurse-submodules <url>

# Cập nhật submodule
git submodule update --init --recursive

Ví dụ thực tế:

# Project dùng shared library từ repo khác
git submodule add https://github.com/company/shared-utils.git libs/shared-utils

# Đồng nghiệp mới clone repo, submodule folder rỗng
git clone --recurse-submodules https://github.com/company/main-project.git
# hoặc nếu đã clone rồi:
git submodule update --init --recursive

Worktree (nhiều working directory cùng 1 repo)

# Tạo worktree mới cho branch
git worktree add <path> <branch>

# Liệt kê worktree
git worktree list

# Xoá worktree
git worktree remove <path>

Ví dụ thực tế:

# Đang code feature, cần fix hotfix mà không muốn stash/switch
# Tạo thêm 1 thư mục riêng cho hotfix
git worktree add ../hotfix-login hotfix/login-crash

# Giờ có 2 thư mục:
# ~/projects/ecommerce/ → feature/user-reg (đang code)
# ~/projects/hotfix-login/ → hotfix/login-crash (fix bug)

# Fix xong, xoá worktree
git worktree remove ../hotfix-login

Lợi ích: Không cần stash, không cần switch branch. Mỗi worktree là 1 thư mục riêng biệt với branch riêng.


Clean (xoá untracked files)

# Xem file nào sẽ bị xoá (dry run)
git clean -n

# Xoá untracked files
git clean -f

# Xoá untracked files + directories
git clean -fd

Ví dụ thực tế:

# Sau khi build, có nhiều file generated rác
git clean -n
# Output:
# Would remove build/output.jar
# Would remove temp/debug.log
# Would remove generated/

# Chắc chắn muốn xoá
git clean -fd
# → Xoá hết untracked files và thư mục

# CHÚ Ý: file trong .gitignore không bị xoá
# Muốn xoá cả file ignored:
git clean -fdx

Blame & Log nâng cao

# Xem ai sửa dòng nào
git blame <file>

# Log thay đổi của 1 file
git log -- <file>

# Log với diff
git log -p

# Tìm commit chứa text trong message
git log --grep="keyword"

# Tìm commit thêm/xoá string trong code
git log -S "string"

# Log giữa 2 branch
git log <branch1>..<branch2>

Ví dụ thực tế:

# Ai sửa dòng 50-60 trong UserService?
git blame -L 50,60 src/UserService.java
# Output:
# a1b2c3d (Linh 2026-03-15) public void register(User user) {
# e4f5g6h (Minh 2026-04-01) validate(user.getEmail());
# ...

# Xem lịch sử thay đổi của 1 file cụ thể
git log --oneline -- src/UserService.java

# Tìm commit nào đã xoá function "processPayment"
git log -S "processPayment" --oneline
# → Hiện commit thêm và commit xoá function đó

# Xem feature branch có gì mà develop chưa có
git log develop..feature/payment --oneline

# Tìm commit liên quan đến "fix" trong message
git log --grep="fix" --oneline

Cấu hình

# Xem config hiện tại
git config --list

# Set username/email (global)
git config --global user.name "Your Name"
git config --global user.email "your@email.com"

# Set default branch name
git config --global init.defaultBranch main

# Set editor mặc định
git config --global core.editor "code --wait"

# Alias (shortcut tiện dụng)
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.st status
git config --global alias.lg "log --oneline --graph --all"

Ví dụ thực tế:

# Sau khi cài alias, dùng lệnh ngắn hơn:
git co develop # = git checkout develop
git br # = git branch
git st # = git status
git lg # = git log --oneline --graph --all

# Set config riêng cho 1 project (không dùng --global)
git config user.email "linh@company.com"
# → Chỉ áp dụng cho repo hiện tại (work email)
# Các repo khác vẫn dùng personal email từ global config

Một số mẹo hữu ích

# Xem remote URL
git remote get-url origin

# Đổi remote URL (ví dụ: đổi từ HTTPS sang SSH)
git remote set-url origin git@github.com:company/project.git

# Xem branch nào đã merge vào branch hiện tại (safe to delete)
git branch --merged

# Xem branch nào chưa merge (đừng xoá!)
git branch --no-merged

# Tạo patch (gửi thay đổi qua email/chat)
git format-patch -1 HEAD

# Apply patch
git apply 0001-fix-something.patch

Ví dụ thực tế:

# Dọn dẹp branch cũ: tìm branch đã merge xong rồi xoá
git branch --merged
# feature/user-reg ← đã merge, xoá được
# feature/payment ← đã merge, xoá được
# * develop
git branch -d feature/user-reg feature/payment

# Đổi remote từ HTTPS sang SSH (không cần nhập password nữa)
git remote set-url origin git@github.com:linh/ecommerce-api.git