Git Daily Workflow: Bộ Sinh Tồn Cho Junior Developer
Git Daily Workflow: Bộ Sinh Tồn Cho Junior Developer
“Mất code, hoảng loạn khi rebase, không biết merge conflict là gì – mọi junior đều từng trải qua. Bài viết này sẽ giúp bạn xây dựng mô hình tư duy đúng về Git và làm chủ những tình huống hàng ngày.”
Trung bình mỗi tuần, một lập trình viên junior sẽ gặp ít nhất một trong những tình huống sau:
- “Em commit nhầm vào main rồi push, làm sao undo?” (tim đập thình thịch)
- “Em rebase mà thấy conflict tùm lum, không biết chọn theirs hay ours”
- “Em pull về tự nhiên mất hết code local”
- “Em lỡ xóa branch chưa merge, code biến mất rồi”
- “Mở file ra thấy
<<<<<<< HEAD, đầu óc trống rỗng”
Nếu bạn từng trải qua, bạn không hề đơn độc. Sự khác biệt giữa một junior loay hoay 2 tiếng và một senior xử lý trong 5 phút không nằm ở số lượng lệnh thuộc lòng, mà ở mô hình tư duy (mental model) về Git. Git không phải là “phần mềm save file lên cloud”, mà là một cây commit (DAG – Directed Acyclic Graph), nơi mỗi commit là một snapshot của toàn bộ dự án, và các branch chỉ là những con trỏ di động.
Bài viết này sẽ đồng hành cùng bạn xây dựng mô hình tư duy đó, đi kèm với các tình huống thực tế, lệnh hàng ngày, chiến lược branching và kỹ năng “cứu hỏa” khi có sự cố. Hãy coi đây như cuốn sổ tay sinh tồn Git cho junior developer.
1. Mô hình tư duy: Git là cây commit, không phải cloud save
1.1. Ba khu vực trong Git
Để hiểu Git, trước tiên bạn phải nắm vững ba “vùng đất” mà code của bạn di chuyển qua:
┌────────────────┐ git add ┌────────────────┐ git commit ┌─────────────────┐
│ Working Dir │ ──────────────> │ Staging Area │ ──────────────> │ Local Repo │
│ (file đang │ │ (index) │ │ (lịch sử commit)│
│ sửa) │ <────────────── │ │ │ │
└────────────────┘ git checkout └────────────────┘ └─────────────────┘
│
git push │
▼
┌─────────────────┐
│ Remote Repo │
│ (GitHub, GitLab)│
└─────────────────┘
- Working Directory: thư mục thực tế trên máy bạn, nơi bạn code.
- Staging Area (Index): khu vực tạm chứa những thay đổi đã được chọn để đưa vào commit tiếp theo.
- Local Repository: lịch sử commit được lưu trong thư mục
.git, đây mới là “kho chứa” thực sự. - Remote Repository: phiên bản trên server (GitHub, GitLab,…) – chỉ thay đổi khi bạn
pushhoặcfetch.
Hiểu được sự di chuyển này giúp bạn tránh những lệnh nguy hiểm như git checkout . (xóa hết thay đổi ở working dir) hay nhầm lẫn giữa git reset và git restore.
1.2. Commit là snapshot, không phải diff
Nhiều bạn mới nghĩ commit lưu sự khác biệt (diff) so với trước đó. Thực tế, mỗi commit là một snapshot toàn bộ project tại thời điểm đó, kèm theo con trỏ tới commit cha (parent). Chính vì vậy lịch sử Git mới là một đồ thị không vòng (DAG):
A ─ B ─ C ─ D (main)
\
E ─ F (feature)
Mỗi node là một commit, mũi tên là parent pointer. Điều này lý giải vì sao bạn có thể checkout về bất kỳ commit nào và có toàn bộ code lúc đó.
1.3. Branch chỉ là con trỏ
Một hiểu lầm kinh điển: “branch là bản copy code”. Không! Branch thực chất chỉ là một label động trỏ tới một commit. Khi bạn tạo branch mới, Git chỉ tạo một con trỏ mới, không hề copy dữ liệu. Khi bạn commit trên branch đó, con trỏ tự động di chuyển tới commit mới.
main (pointer)
│
▼
A ─ B ─ C ─ D
\
E ─ F
▲
│
feature (pointer)
HEAD ──> feature ← HEAD trỏ vào branch hiện tại
HEAD là một con trỏ đặc biệt cho biết bạn đang “đứng” ở đâu. Thường thì HEAD trỏ vào một branch (gián tiếp tới commit), nhưng cũng có thể trỏ thẳng vào commit (detached HEAD). Khi ở trạng thái detached, nếu bạn commit, commit đó sẽ “mồ côi” vì không có branch nào trỏ vào. Giải pháp: ngay lập tức tạo branch mới git checkout -b new-branch để lưu lại.
2. Năm tình huống “thót tim” và cách xử lý
Tình huống 1: Pull về mất hết code local
$ git pull
error: Your local changes to the following files would be overwritten by merge:
src/UserService.java
Please commit your changes or stash them before you merge.
Aborting.
Junior hoảng loạn gõ: git checkout . → toàn bộ thay đổi chưa commit bốc hơi, khó khôi phục.
Cách đúng:
git stash # cất tạm thay đổi
git pull # pull code mới về
git stash pop # áp lại thay đổi, xử lý conflict nếu có
Tình huống 2: Commit nhầm vào branch main
Bạn đang cần làm feature/login nhưng lại commit ngay trên main.
git log --oneline -1
# abc1234 feat: add login endpoint
git branch feature/login # tạo branch mới tại vị trí hiện tại (giữ commit)
git reset --hard HEAD~1 # lùi main về trước commit nhầm
git checkout feature/login # chuyển sang branch đúng, code nguyên vẹn
Tình huống 3: Rebase và nỗi sợ conflict
Rebase là kỹ thuật “dời” các commit của branch hiện tại lên ngọn của branch khác, giúp lịch sử thẳng đẹp. Nhưng khi rebase, hash commit thay đổi, conflict có thể xuất hiện ở từng commit.
Trước rebase: Sau rebase:
main: A — B — C A — B — C
\ \
feature: D — E — F D' — E' — F' (hash đổi)
Lệnh: git checkout feature && git rebase main
Quy tắc vàng: Không rebase branch đã push lên remote và có người khác cùng làm việc. Chỉ rebase branch local riêng trước khi push lần đầu.
Tình huống 4: Merge conflict – đừng hoảng
Khi gặp conflict, Git chèn các marker:
<<<<<<< HEAD
return amount.multiply(BigDecimal.valueOf(0.05));
=======
return amount.multiply(BigDecimal.valueOf(0.07));
>>>>>>> feature/new-rate
- Đoạn từ
<<<<<<< HEADđến=======là code của branch hiện tại (thường là main) - Đoạn từ
=======đến>>>>>>> feature/...là code của branch đang merge vào.
Bạn cần quyết định giữ bên nào, hoặc kết hợp cả hai, rồi xóa các marker. Sau đó:
git add <file> # đánh dấu đã giải quyết
git commit # hoàn tất merge
Nếu đang rebase thì dùng git rebase --continue.
Tình huống 5: Lỡ tay xóa branch chưa merge
git branch -D feature/login # 200 dòng code "biến mất"
Bình tĩnh! Git vẫn giữ commit trong reflog (nhật ký tham chiếu) từ 30-90 ngày.
git reflog
# abc1234 HEAD@{1}: branch: Created from main
# def5678 HEAD@{2}: commit: feat: add login UI
git checkout -b feature/login abc1234 # khôi phục nguyên trạng
Reflog là cứu cánh tối thượng – nắm chắc nó, bạn sẽ không bao giờ mất code vĩnh viễn.
3. Bộ lệnh hàng ngày cho junior
3.1. Cấu hình ban đầu (chỉ một lần)
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main
git config --global pull.rebase false # merge khi pull (an toàn)
git config --global core.editor "code --wait"
3.2. Workflow mỗi ngày
# Bắt đầu ngày
git checkout main
git pull
# Tạo branch tính năng
git checkout -b feature/JIRA-123-add-login
# Code... rồi kiểm tra
git status
git diff
git add -p # chọn từng phần thay đổi (khuyên dùng)
git commit -m "feat(auth): add JWT refresh token endpoint"
# Push lên remote và tạo PR
git push -u origin feature/JIRA-123-add-login
# (sau khi PR được merge)
git checkout main
git pull
git branch -d feature/JIRA-123-add-login
git push origin --delete feature/JIRA-123-add-login
Một số lệnh kiểm tra lịch sử hữu ích:
git log --oneline --graph --all # nhìn toàn cảnh cây commit
git blame src/UserService.java # ai viết từng dòng?
git reflog # lịch sử HEAD (cứu tinh)
3.3. Stash – cất tạm thay đổi
Khi bạn đang code dở mà cần chuyển gấp sang branch khác (ví dụ hotfix), stash cho phép cất toàn bộ thay đổi chưa commit vào một ngăn xếp.
git stash # cất đi
git stash pop # lấy lại thay đổi gần nhất và xóa khỏi stack
git stash list # xem danh sách stash
git stash apply stash@{1} # áp dụng stash cụ thể mà không xóa
3.4. .gitignore – bảo vệ bí mật
Luôn đảm bảo file .gitignore có mặt từ commit đầu tiên, ít nhất với:
# Java / Spring Boot
target/
*.class
*.jar
.idea/
*.iml
# Secrets
.env
*.pem
*.key
application-secrets.yml
# OS
.DS_Store
Thumbs.db
Nếu lỡ commit file nhạy cảm, bạn phải xoay khóa ngay (vì nó đã lộ) và dùng git rm --cached để untrack, sau đó thêm vào .gitignore.
4. Chiến lược branching và commit message chuẩn
4.1. GitHub Flow – đơn giản, hiệu quả
Phần lớn team hiện đại dùng GitHub Flow: chỉ có một branch chính main (luôn sẵn sàng deploy) và các feature branch ngắn hạn.
main ────●────────●────────●──── (deployable)
│ │ │
feature/x ──●─┘ │ │
feature/y ────────────●─┘ │
feature/z ──────────────────────●─┘
Branch được đặt tên theo convention: feature/<ticket>-<mô tả>, bugfix/..., hotfix/....
4.2. Conventional Commits
Mỗi commit message nên tuân theo cấu trúc:
<type>(<scope>): <subject>
<body - tùy chọn>
<footer - tùy chọn>
Các type phổ biến: feat, fix, docs, style, refactor, test, chore, perf.
Ví dụ:
feat(auth): add JWT refresh token endpoint
- New endpoint POST /api/auth/refresh
- Refresh token rotation on use
- TTL: 7 days
Resolves: JIRA-123
Tránh những message vô nghĩa như “update”, “fix bug”, “WIP” (trừ phi bạn sẽ squash trước khi merge).
5. Merge, rebase và cherry-pick: chọn công cụ phù hợp
5.1. Merge vs Rebase
| Tiêu chí | Merge | Rebase |
|---|---|---|
| Cơ chế | Tạo merge commit gộp hai lịch sử | Viết lại lịch sử, đặt commit lên ngọn |
| Lịch sử | Giữ nguyên, phân nhánh rõ | Thẳng, sạch, dễ đọc |
| An toàn | An toàn tuyệt đối | Nguy hiểm nếu branch đã chia sẻ |
| Xử lý conflict | Tập trung một lần vào merge commit | Phải giải quyết cho từng commit |
| Khi dùng | Branch đã push, làm việc nhóm | Branch local riêng, trước khi push |
Lệnh merge tiêu chuẩn: git checkout main && git merge feature
Rebase: git checkout feature && git rebase main
Quy tắc vàng rebase: Không bao giờ rebase branch đã push và có người khác pull. Nếu cần làm sạch lịch sử trước khi chia sẻ, hãy dùng rebase -i (interactive rebase) để squash, reword, drop commit.
git rebase -i HEAD~5 # chỉnh sửa 5 commit gần nhất
5.2. Cherry-pick – nhặt commit như hái cherry
Khi bạn chỉ muốn lấy một vài commit cụ thể từ branch khác mà không merge toàn bộ:
git checkout main
git cherry-pick abc1234
Thường dùng cho hotfix đang nằm trong feature branch chưa hoàn thiện, hoặc backport bản vá sang release branch cũ. Nhược điểm: cùng một thay đổi xuất hiện ở hai nơi với hash khác nhau, có thể gây conflict khi merge sau này.
6. Conflict resolution – kỹ năng sinh tồn
Các bước xử lý conflict khi merge:
- Xác định file conflict:
git status - Mở file, tìm marker
<<<<<<<,=======,>>>>>>>. Phần trên là code của branch hiện tại, phần dưới là của branch đang merge vào. - Chọn phương án giữ: có thể giữ HEAD, giữ feature, kết hợp hoặc đưa ra giá trị mới. Hỏi đồng đội nếu không chắc.
- Lưu file và đánh dấu resolved:
git add <file> git commit # (hoặc git rebase --continue)
Nếu bối rối quá, bạn có thể hủy bỏ:
git merge --abort
git rebase --abort
Các IDE như VS Code cung cấp giao diện trực quan với các nút “Accept Current”, “Accept Incoming”, “Accept Both”.
7. Undo – “cứu hỏa” cho mọi tình huống
7.1. Cấp độ nhẹ: sửa file chưa add, muốn về nguyên trạng
git restore src/UserService.java # modern Git (>=2.23)
# hoặc
git checkout -- src/UserService.java
7.2. Unstage file đã add nhưng chưa commit
git restore --staged src/UserService.java
# hoặc
git reset HEAD src/UserService.java
7.3. Sửa commit message (chưa push)
git commit --amend -m "feat: correct message"
7.4. Lỡ commit trên main (chưa push)
git branch feature/add-login # giữ commit ở branch mới
git reset --hard HEAD~1 # main lùi 1 commit
git checkout feature/add-login
7.5. Đã push commit sai lên branch riêng
Dùng force push an toàn:
git reset --hard HEAD~1
git push --force-with-lease # kiểm tra remote chưa bị thay đổi từ lần fetch cuối
Tuyệt đối không dùng --force thuần.
7.6. Đã push lên branch chung
Không được force push. Hãy tạo commit đảo ngược:
git revert abc1234
git push
Commit revert sẽ ghi nhận lịch sử hoàn tác, không phá hỏng công việc của người khác.
7.7. Reflog – “bảo hiểm” của Git
Ngay cả khi bạn reset --hard, xóa branch, code vẫn tồn tại trong reflog tới 90 ngày:
git reflog
# tìm hash muốn khôi phục
git checkout -b recovered-branch <hash>
Hãy luôn nhớ “git reflog” mỗi khi tim bạn đập nhanh vì sợ mất code.
8. Git hooks và quy trình ngân hàng
Trong môi trường enterprise, đặc biệt là ngân hàng, Git thường được thắt chặt bằng các hook (script tự động chạy khi có sự kiện Git).
Ví dụ, pre-commit hook để chạy test:
#!/bin/sh
./mvnw test
if [ $? -ne 0 ]; then
echo "Tests failed. Commit aborted."
exit 1
fi
Ngoài ra, để đảm bảo audit trail, commit có thể được ký GPG (git commit -S). Khi đó GitHub/GitLab hiển thị badge “Verified”, đáp ứng yêu cầu compliance.
Quy trình PR chuẩn trong team lớn:
- Tạo branch, code, commit đúng chuẩn.
- Push, mở PR.
- CI chạy: build, unit test, security scan.
- Reviewer (thường cần 2 approvals) kiểm tra.
- Squash & merge vào main (giữ lịch sử gọn), tự động deploy dev.
- Cleanup branch.
Tuân thủ những quy ước này giúp bạn hòa nhập nhanh vào môi trường doanh nghiệp và giảm thiểu lỗi không đáng có.
9. Thực hành để thành thạo
Kiến thức chỉ thực sự thuộc về bạn khi đôi tay đã quen. Hãy thực hiện các bài tập sau trên repo cá nhân:
- Exercise 1: Tạo branch, commit theo Conventional Commits, push, mở PR, cleanup.
- Exercise 2: Tạo conflict giả, thực hành resolve bằng tay.
- Exercise 3: Mô phỏng 5 kịch bản undo từ nhẹ đến nặng, sử dụng reflog để khôi phục.
- Exercise 4: Interactive rebase: squash, reword, drop commit.
Ngoài ra, có thể thực hành trực quan qua các công cụ:
- Learn Git Branching – game hóa mô phỏng thao tác Git.
- Oh My Git! – game nhập vai học Git.
- lazygit, GitLens, GitKraken – công cụ tăng năng suất.
10. Checklist thành thạo cho junior
Sau khi học và thực hành, hãy tự kiểm tra:
- Vẽ được cây commit cho một kịch bản merge/rebase, giải thích HEAD/branch/commit.
- Phân biệt Working Dir, Staging, Repo và các lệnh di chuyển.
- Hoàn thành daily workflow mà không cần nhìn cheatsheet.
- Xử lý được conflict mà không panic.
- Thực hiện 5 undo scenario không cần Google.
- Dùng reflog khôi phục branch đã xóa.
- Squash, reword, drop commit với interactive rebase.
- Đề xuất được chiến lược branch phù hợp cho team, viết commit message chuẩn.
- Cấu hình
.gitignoređúng cho dự án Java/Spring Boot.
Lời kết
Git không phải là rào cản, mà là công cụ mạnh mẽ nhất để bảo vệ và quản lý code của bạn. Một khi nắm vững mô hình “cây commit” và các thao tác cơ bản, bạn sẽ thấy rằng những tình huống từng gây hoảng loạn thực ra chỉ cần vài dòng lệnh đơn giản.
Hãy in cheatsheet cuối bài này ra và dán ngay trước màn hình. Khi gặp sự cố, đừng vội gõ bừa – git status, git log --oneline --graph --all, và git reflog là ba câu thần chú giúp bạn bình tĩnh nhìn rõ tình hình.
Điều cuối cùng: đừng bao giờ dùng git push --force thuần, git checkout ., hay rm -rf .git khi chưa hiểu rõ. Chúng có thể gây mất mát không thể cứu vãn. Hãy tìm hiểu --force-with-lease, restore, và reflog trước.
Chúc bạn làm chủ Git và tự tin trên hành trình phát triển sự nghiệp lập trình!
Phụ lục: Cheatsheet 1 trang
SETUP
git config --global user.name "Name"
git config --global user.email "email@x.com"
DAILY
git status # Tình trạng hiện tại
git diff # Thay đổi unstaged
git diff --staged # Thay đổi staged
git add <file> # Stage file
git add -p # Stage interactive
git commit -m "message"
git push
BRANCHES
git branch
git checkout -b <name>
git switch <name>
git branch -d <name>
git push origin --delete <name>
SYNC
git fetch
git pull
git pull --rebase
HISTORY
git log --oneline --graph --all
git blame <file>
git reflog
UNDO
git restore <file>
git restore --staged <file>
git commit --amend
git reset HEAD~1
git reset --hard HEAD~1
git revert <hash>
STASH
git stash
git stash pop
git stash list
REBASE
git rebase main
git rebase -i HEAD~5
git rebase --abort
CONFLICT
git status
# sửa file
git add <file>
git commit / git rebase --continue