Chương 08 — Git & SSH

Git là toolkit hằng ngày của dev: track lịch sử code, hợp tác qua remote, undo nhầm lẫn. SSH là kênh bảo mật để bạn nói chuyện với GitHub, server, máy đồng nghiệp. Học chương này, bạn không còn lệ thuộc vào nút bấm trong VS Code/SourceTree nữa — terminal nhanh hơn và mạnh hơn rất nhiều.

1. Git CLI cơ bản

Git theo dõi snapshot của project theo thời gian. Mỗi commit là một snapshot có hash riêng. Bạn có thể quay về bất kỳ commit nào.

Khởi tạo & commit đầu tiên

$ git init                      # tạo repo .git/ trong folder hiện tại
$ git status                    # xem file nào đã/chưa track, đã/chưa stage
$ git add README.md              # stage 1 file
$ git add .                       # stage tất cả thay đổi
$ git commit -m "init project"   # tạo commit với message
$ git log --oneline --graph     # xem lịch sử ngắn gọn dạng đồ thị

3 vùng quan trọng nhất

VùngMô tảLệnh đưa vào
Working dirFile bạn đang sửa(chỉnh sửa file)
Staging (index)Đã chọn để commitgit add
RepositoryĐã commit, có hashgit commit
📘 Vì sao terminal nhanh hơn GUI?
SourceTree, GitHub Desktop tốt cho người mới, nhưng mỗi click = 1 thao tác. Trong terminal, bạn ghép được nhiều lệnh, dùng alias (vd g co = git checkout), và mọi tính năng Git đều có sẵn — không có chuyện "GUI chưa hỗ trợ feature đó".

2. Branch & merge

Branch là nhánh phát triển song song. Bạn làm feature X trên một branch riêng, không đụng tới main, đến khi xong thì merge vào.

$ git branch                       # liệt kê branch local
$ git branch feature-login          # tạo branch mới (nhưng chưa chuyển sang)
$ git checkout feature-login        # chuyển sang branch đó (cách cũ)
$ git checkout -b feature-login     # tạo + chuyển trong 1 lệnh
$ git switch feature-login          # chuyển branch (cách mới, rõ nghĩa hơn)
$ git switch -c feature-login       # tạo + chuyển (tương đương -b)

Merge vào main

$ git switch main                   # về main
$ git merge feature-login            # gộp feature vào main
$ git branch -d feature-login        # xóa branch đã merge xong
$ git log --all --graph --decorate   # xem mọi branch dạng đồ thị
💡 switch hay checkout?
Git 2.23 (2019) tách checkout thành 2 lệnh rõ nghĩa hơn: switch (chuyển branch) và restore (khôi phục file). Dùng cái mới — đỡ nhầm lẫn, vì checkout làm quá nhiều việc.

3. Rebase & cherry-pick

Cả hai đều là cách "viết lại lịch sử" — mạnh nhưng phải dùng cẩn thận.

git rebase — đặt lại nhánh lên trên

$ git switch feature-x
$ git rebase main                   # đem các commit của feature-x lên trên main mới nhất

So với merge: rebase tạo lịch sử thẳng, không có merge commit. Nhánh trông gọn hơn.

Interactive rebase — "biên tập" lịch sử

$ git rebase -i HEAD~5             # sửa 5 commit gần nhất

Editor mở ra, bạn chọn cho từng commit:

git cherry-pick — bốc lẻ commit

$ git cherry-pick a1b2c3d            # mang commit a1b2c3d sang branch hiện tại
$ git cherry-pick a1b2c3d..f4e5d6c   # range commit

Hữu ích khi bạn cần fix bug từ branch khác mà không muốn merge cả branch đó.

🛑 Không rebase nhánh đã push public
Rebase viết lại hash các commit. Nếu nhánh đó đồng nghiệp đã pull về, sau khi bạn rebase rồi git push --force, commit cũ của họ biến mất khỏi server. Họ pull lại sẽ conflict kinh khủng. Quy tắc: chỉ rebase commit chưa share. Đã push lên branch chung (main, develop) thì dùng merge hoặc revert.

4. Remote & sync

Remote là bản sao repo trên server (GitHub, GitLab, BitBucket). origin là tên remote mặc định khi clone.

$ git remote -v                    # xem các remote đang config
$ git remote add origin git@github.com:user/repo.git
$ git fetch origin                  # tải về dữ liệu mới nhưng KHÔNG merge
$ git pull origin main               # = fetch + merge
$ git pull --rebase origin main      # = fetch + rebase (lịch sử thẳng hơn)
$ git push origin main               # đẩy commit local lên remote
$ git push -u origin feature-x       # -u: set upstream để lần sau chỉ cần `git push`

fetch vs pull — khác chỗ nào?

LệnhTải vềÁp vào branch localKhi nào dùng
git fetchMuốn xem có gì mới mà chưa muốn merge
git pull✅ (merge)Muốn cập nhật ngay, có thể tạo merge commit
git pull --rebase✅ (rebase)Muốn lịch sử thẳng, đặt commit mình lên trên

Workflow chuẩn cho team

  1. git switch -c feature-x — tạo nhánh từ main.
  2. Code, commit nhiều lần, message rõ ràng.
  3. git fetch origin && git rebase origin/main — đồng bộ với main mới nhất.
  4. git push -u origin feature-x — đẩy lên remote.
  5. Mở Pull Request trên GitHub, review, merge.
  6. git switch main && git pull && git branch -d feature-x — dọn dẹp.
🛑 git push --force — vũ khí hủy diệt
--force ghi đè branch trên remote, xóa mọi commit người khác đã đẩy lên trong lúc bạn không nhìn. Nếu thực sự phải force push (vd sau khi rebase nhánh feature của riêng mình), dùng:
$ git push --force-with-lease
Cờ này check xem remote có thay đổi từ lần fetch cuối không — nếu có (ai đó vừa push), nó từ chối ghi đè. An toàn hơn nhiều. KHÔNG BAO GIỜ force push lên main/master.

5. Stash & undo — sửa sai

Đây là phần "cứu sống" — biết undo đúng cách giúp bạn tự tin thử nghiệm.

git stash — cất tạm thay đổi

$ git stash                      # cất working dir + staging vào ngăn kéo
$ git stash list                 # xem các stash đang có
$ git stash pop                  # lấy lại stash mới nhất + xóa khỏi list
$ git stash apply stash@{1}      # lấy stash thứ 1 (giữ nguyên trong list)
$ git stash drop stash@{0}       # xóa stash

Dùng khi: đang dở dang feature thì sếp bảo "fix bug production gấp" — stash, switch sang main, fix, push, rồi stash pop quay lại.

Bảng "tôi muốn undo X → dùng lệnh nào"

Tình huốngLệnh
Bỏ thay đổi chưa stage trên 1 file (về như commit cuối)git restore file.js
Bỏ git add (unstage), giữ thay đổigit restore --staged file.js
Sửa message commit cuối (chưa push)git commit --amend
Bỏ commit cuối, giữ thay đổi đã stagedgit reset --soft HEAD~1
Bỏ commit cuối, giữ thay đổi nhưng unstagegit reset --mixed HEAD~1 (mặc định)
Bỏ commit cuối + xóa luôn thay đổi (NGUY HIỂM)git reset --hard HEAD~1
Đảo commit đã push (tạo commit mới đảo ngược)git revert <sha>
⚠️ reset --soft vs --hard
--soft: chỉ lùi con trỏ commit, file giữ nguyên + còn trong staging. An toàn.
--mixed (default): lùi commit, file giữ nguyên nhưng unstage.
--hard: lùi commit + xóa hết thay đổi trong working dir. Mất sạch nếu chưa stash. Nghĩ kỹ.

revert — undo "công khai" cho commit đã push

$ git revert a1b2c3d              # tạo commit MỚI đảo ngược a1b2c3d

Khác reset: revert không xóa lịch sử, chỉ thêm commit mới — an toàn cho branch public.

6. SSH cho git & server

SSH (Secure Shell) là cách xác thực không mật khẩu bằng cặp key public/private. Bạn cho server giữ public key, máy bạn giữ private key — server gửi thử thách, máy bạn ký bằng private key để chứng minh là bạn.

Bước 1 — Sinh SSH key

$ ssh-keygen -t ed25519 -C "your.email@example.com"

Kết quả ra 2 file trong ~/.ssh/:

Bước 2 — Copy public key vào GitHub/GitLab

$ cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3Nza... your.email@example.com
$ pbcopy < ~/.ssh/id_ed25519.pub   # macOS: copy thẳng vào clipboard

Vào GitHub → Settings → SSH and GPG keys → New SSH key → paste public key → Save.

Bước 3 — Test kết nối

$ ssh -T git@github.com
Hi yourname! You've successfully authenticated, but GitHub does not provide shell access.

Thấy "successfully authenticated" = thành công. Giờ clone repo bằng URL git@github.com:user/repo.git sẽ không bị hỏi password nữa.

~/.ssh/config — alias cho host

Khi có nhiều server hoặc nhiều account GitHub, file config giúp gõ ngắn gọn:

# ~/.ssh/config
Host github
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519

Host myserver
  HostName 203.0.113.42
  User deploy
  Port 2222
  IdentityFile ~/.ssh/id_ed25519_server

Sau khi config, bạn gõ:

$ ssh myserver                  # thay vì ssh -p 2222 deploy@203.0.113.42
$ git clone github:user/repo.git  # dùng alias trong git URL
💡 SSH config tip
Nhớ chmod 600 ~/.ssh/configchmod 600 ~/.ssh/id_ed25519. SSH sẽ từ chối dùng key nếu permission quá lỏng — đó là tính năng bảo mật, không phải bug. Folder ~/.ssh nên là 700.

scp & rsync — copy file qua mạng

$ scp file.txt myserver:~/uploads/         # copy 1 file lên server
$ scp -r dist/ myserver:/var/www/         # copy folder (đệ quy)
$ scp myserver:~/log.txt ./                # tải về máy local

$ rsync -avzP dist/ myserver:/var/www/    # sync folder, chỉ copy file đã đổi
$ rsync -avzP --delete dist/ myserver:/var/www/  # xóa file thừa ở dest

Bảng so sánh scp vs rsync

Tiêu chíscprsync
Tốc độ khi sync lại folder lớnCopy lại từ đầuChỉ copy file/khúc đã đổi (delta)
Resume khi mất mạng❌ Bắt đầu lại từ 0✅ Có (-P)
Compress khi truyền✅ (-z)
Hiển thị progressCó (basic)Có (-P) — chi tiết hơn
Đồng bộ 2 chiều / xóa file thừa✅ (--delete)
Có sẵn mặc địnhHầu hết hệ thốngHầu hết hệ thống Unix

Quy tắc đơn giản: copy 1 file nhỏ một lần → scp. Sync folder lớn / deploy / backup định kỳ → rsync -avzP.

7. Thử ngay trong terminal giả lập

Tập commit cycle ngay trong simulator — không sợ phá repo thật:

  1. mkdir myrepo && cd myrepo
  2. git init
  3. echo "# My Repo" > README.md
  4. git status — thấy README.md untracked
  5. git add README.md
  6. git commit -m "init"
  7. git log --oneline
  8. git switch -c feature-1
  9. git branch — xác nhận đang ở feature-1
you@mac — chuong8 practice
you@mac ~ $

8. Bài tập về nhà

Làm trên Terminal thật. Repo này thuộc về bạn — sai cứ rm -rf .git rồi làm lại:

  1. Tạo folder ~/Developer/git-practice, cd vào, git init. Tạo README.md, commit đầu với message "init".
  2. Tạo branch feature/hello bằng git switch -c. Sửa README.md, commit. Quay về main, merge feature/hello vào.
  3. Trên branch mới feature/oops, làm 3 commit lung tung. Dùng git rebase -i HEAD~3 để squash 3 commit thành 1, đổi message thành "feat: hello message".
  4. Tạo file secret.txt, git add nó, rồi nhận ra không nên commit. Unstage bằng git restore --staged secret.txt.
  5. Sinh SSH key ed25519 mới (nếu chưa có): ssh-keygen -t ed25519 -C "email". Copy public key lên GitHub. Test bằng ssh -T git@github.com.
  6. Tạo repo trống trên GitHub (qua web UI). Trên local, chạy git remote add origin git@github.com:user/repo.git rồi git push -u origin main.
  7. Tạo file ~/.ssh/config với một Host alias github trỏ đến github.com. chmod 600 ~/.ssh/config. Test bằng ssh -T github.
  8. Tạo 2 folder ~/tmp/src (chứa vài file) và ~/tmp/dst (rỗng). Sync bằng rsync -avzP ~/tmp/src/ ~/tmp/dst/. Sửa 1 file ở src, chạy lại — thấy rsync chỉ copy file vừa đổi.
  9. (Bonus) Cố tình tạo conflict: trên 2 branch sửa cùng dòng, merge, đọc dấu <<<<<<<, resolve thủ công, commit.

9. Quiz — Kiểm tra kiến thức

10. Tổng kết

Chương 9 chúng ta học tmux & productivity — chia terminal thành nhiều panel, để session chạy ngầm trên server, và những thói quen làm dev "đa nhiệm" thực sự.

Trước ← Chương 07: Shell scripting Tiếp theo Chương 09: Tmux & productivity →