Git thực chiến – Làm việc với nhiều GitHub account và repository [Storytelling – Phần 2]
Hi, xin chào mọi người! Sau Phần 1, chúng ta đã đi qua hành trình đầu tiên với Git: tạo SSH key, clone source code, tạo branch, commit, push và mở Pull Request.
Ở Phần 2 này, câu chuyện sẽ thực tế hơn một chút.
Hãy tưởng tượng Goku đang dùng cùng một chiếc laptop cho nhiều công việc:
- Account cá nhân:
goku-son; - Account công ty Capsule Corp:
goku-capsule; - Account cộng tác với team của Vegeta:
goku-saiyan; - Mỗi account lại có quyền truy cập vào những repository thuộc các organization khác nhau.
Mọi thứ vẫn ổn cho đến một ngày Goku clone nhầm account, commit bằng email cá nhân vào repository công ty, hoặc push và nhận được lỗi:
ERROR: Repository not found.
fatal: Could not read from remote repository.
Lúc đó, sức mạnh Super Saiyan cũng chưa chắc cứu được chúng ta 😅
Trong bài viết này, mình sẽ chia sẻ cách tổ chức SSH, clone đúng repository bằng đúng GitHub account, cấu hình danh tính cho từng repository và xử lý một số file cấu hình local mà chúng ta không muốn Git liên tục báo thay đổi.
1. Hai danh tính khác nhau thường bị nhầm lẫn
Trước khi bắt đầu, chúng ta cần phân biệt hai câu hỏi:
- Bạn dùng GitHub account nào để kết nối và push code?
- Commit được ghi nhận là do ai tạo?
Đây là hai việc khác nhau.
| Thành phần | Vai trò |
|---|---|
| SSH key và SSH host alias | Xác thực account nào được dùng để truy cập GitHub |
user.name và user.email |
Ghi tên và email tác giả vào commit |
Ví dụ, SSH key của goku-capsule có thể cho phép bạn push vào repository của Capsule Corp. Tuy nhiên, nếu repository vẫn dùng user.email của account cá nhân thì commit mới vẫn mang email cá nhân.
Ngược lại, đổi user.name và user.email không tự động cấp quyền push. Nếu SSH key đang dùng không có quyền truy cập repository, GitHub vẫn từ chối.
Nói ngắn gọn:
SSH quyết định bạn có quyền đi qua cánh cửa nào; Git config quyết định tên nào được ghi vào lịch sử commit.
2. Kiểm tra cấu hình SSH hiện tại
Thông thường, SSH config nằm tại:
~/.ssh/config
Để xem nội dung file:
cat ~/.ssh/config
Giả sử Goku có ba GitHub account, file có thể được cấu hình như sau:
Host github-goku-personal
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_goku_personal
IdentitiesOnly yes
Host github-goku-capsule
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_goku_capsule
IdentitiesOnly yes
Host github-goku-saiyan
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_goku_saiyan
IdentitiesOnly yes
Trong đó:
Host: tên alias do bạn tự đặt;HostName: máy chủ thật, ở đây làgithub.com;User git: user SSH mà GitHub sử dụng;IdentityFile: private key dành cho account tương ứng;IdentitiesOnly yes: yêu cầu SSH chỉ dùng key đã chỉ định, tránh thử nhầm những key khác đang có trong SSH agent.
Tên alias không cần giống GitHub username. Bạn chỉ cần đặt sao cho bản thân dễ nhớ.
Kiểm tra từng SSH account
Để kiểm tra alias đầu tiên:
ssh -T github-goku-personal
Hoặc viết đầy đủ:
ssh -T git@github-goku-personal
Nếu cấu hình đúng, GitHub thường trả về thông báo tương tự:
Hi goku-son! You've successfully authenticated, but GitHub does not provide shell access.
Tiếp tục kiểm tra các account còn lại:
ssh -T github-goku-capsule
ssh -T github-goku-saiyan
Lưu ý rằng lệnh ssh -T chỉ giúp kiểm tra alias đang xác thực thành account nào. Nó không tự thay đổi account của repository hiện tại.
Muốn repository hiện tại dùng account nào, chúng ta phải kiểm tra và cập nhật remote URL của repository đó.
3. Clone repository bằng một GitHub account cụ thể
Thông thường, GitHub cung cấp SSH URL như sau:
git@github.com:<organization>/<repository>.git
Khi sử dụng nhiều account, chúng ta thay github.com bằng alias đã khai báo trong ~/.ssh/config.
Ví dụ, Goku muốn clone repository dragon-radar của organization capsule-corp bằng account công ty:
git clone git@github-goku-capsule:capsule-corp/dragon-radar.git
Cấu trúc chung là:
git clone git@<ssh-host-alias>:<organization>/<repository>.git
Một ví dụ khác, clone project của team Saiyan:
git clone git@github-goku-saiyan:saiyan-labs/gravity-room.git
SSH sẽ đọc alias github-goku-saiyan, tìm đúng IdentityFile, sau đó dùng key đó để xác thực với GitHub.
Repository đã clone rồi thì sao?
Đầu tiên, kiểm tra remote hiện tại:
git remote -v
Kết quả có thể là:
origin git@github.com:capsule-corp/dragon-radar.git (fetch)
origin git@github.com:capsule-corp/dragon-radar.git (push)
Nếu muốn repository này sử dụng account goku-capsule, cập nhật remote URL:
git remote set-url origin git@github-goku-capsule:capsule-corp/dragon-radar.git
Kiểm tra lại:
git remote -v
Bạn cũng có thể xem URL ngắn gọn bằng:
git remote get-url origin
Đến đây, các thao tác fetch, pull và push qua remote origin sẽ sử dụng SSH alias cùng key tương ứng.
4. Git config global và local
Sau khi đã chọn đúng account để truy cập repository, bước tiếp theo là chọn đúng danh tính cho commit.
Global config
Global config là cấu hình mặc định cho các repository của user hiện tại trên máy:
git config --global user.name "Goku Son"
git config --global user.email "goku@kamehouse.example"
Bạn có thể kiểm tra bằng:
git config --global --list
Hoặc kiểm tra riêng từng giá trị:
git config --global user.name
git config --global user.email
Local config
Local config chỉ áp dụng cho repository hiện tại và sẽ được ưu tiên hơn global config.
Trong repository của Capsule Corp, Goku có thể cấu hình:
git config --local user.name "Goku Capsule"
git config --local user.email "goku@capsule-corp.example"
Kiểm tra lại:
git config --local --list
File cấu hình local được lưu trong:
.git/config
Thông thường bạn nên dùng git config để chỉnh sửa thay vì sửa file này bằng tay.
Git sẽ lấy config nào?
Về cơ bản, độ ưu tiên là:
local > global > system
Do đó, Goku có thể đặt account cá nhân làm mặc định ở global và cấu hình account công ty riêng cho từng repository bằng local config.
Để xem giá trị thực tế đang được dùng và nó đến từ file nào:
git config --show-origin --get user.name
git config --show-origin --get user.email
Đây là câu lệnh rất hữu ích khi bạn chắc chắn mình đã config rồi nhưng Git vẫn dùng một email khác.
Kiểm tra danh tính trước khi commit
git config user.name
git config user.email
Sau khi commit, kiểm tra commit mới nhất:
git log -1 --format='%h | %an <%ae> | %s'
Ví dụ:
a1b2c3d | Goku Capsule <goku@capsule-corp.example> | Add Dragon Radar API
Lưu ý: đổi config chỉ ảnh hưởng đến các commit được tạo sau đó. Nó không tự sửa author của những commit đã tồn tại.
5. Workflow commit và push
Sau khi SSH account, remote URL và Git identity đều đúng, phần còn lại khá quen thuộc.
Đầu tiên, kiểm tra branch và các file thay đổi:
git branch --show-current
git status
Tạo branch mới nếu cần:
git switch -c goku/add-dragon-radar-api
Sau khi hoàn thành code:
git add .
git commit -m "Add Dragon Radar API"
Push branch lên remote:
git push -u origin goku/add-dragon-radar-api
Flag -u là dạng ngắn của --set-upstream. Sau lần đầu tiên, bạn thường chỉ cần:
git push
Một checklist ngắn trước khi push:
git remote get-url origin
git config user.name
git config user.email
git status
Bốn câu lệnh này giúp trả lời bốn câu hỏi:
- Tôi đang push tới repository nào?
- Tôi đang dùng danh tính nào để commit?
- Email nào sẽ được ghi vào commit?
- Tôi sắp đưa những thay đổi nào lên remote?
6. Bonus: bỏ qua thay đổi local với skip-worktree
Trong quá trình phát triển, có những file đã được Git theo dõi nhưng mỗi developer lại cần một cấu hình local khác nhau, ví dụ:
docker-compose.infra.yml
Makefile
config/config-local.yaml
config/config-test.yaml
Bạn sửa chúng để chạy project trên máy cá nhân, nhưng không muốn mỗi lần git status lại thấy các file này xuất hiện.
Vì chúng đã được tracked, thêm vào .gitignore hoặc .git/info/exclude sẽ không có tác dụng. Một giải pháp local là đánh dấu skip-worktree:
git update-index --skip-worktree \
docker-compose.infra.yml \
Makefile \
config/config-local.yaml \
config/config-test.yaml
Sau đó, các chỉnh sửa local trên những file này thường sẽ không xuất hiện trong git status.
Kiểm tra file nào đang được đánh dấu
git ls-files -v | grep '^S'
Ký tự S ở đầu dòng biểu thị file đang có cờ skip-worktree.
Bỏ đánh dấu để Git theo dõi thay đổi trở lại
git update-index --no-skip-worktree \
docker-compose.infra.yml \
Makefile \
config/config-local.yaml \
config/config-test.yaml
Sau đó kiểm tra:
git status
Cẩn thận khi sử dụng
skip-worktree là trạng thái chỉ tồn tại trong Git index ở máy local. Nó không được chia sẻ cho đồng đội và cũng không phải cơ chế bảo vệ file tuyệt đối.
Khi remote có thay đổi trên cùng file, các thao tác như checkout, merge, rebase hoặc pull có thể khiến Git yêu cầu bạn xử lý file đó. Vì vậy:
- Không dùng nó để che giấu secret;
- Không xem nó như một phiên bản mạnh hơn của
.gitignore; - Nên bỏ cờ trước khi cần cập nhật hoặc commit file;
- Luôn kiểm tra và backup thay đổi local quan trọng trước khi switch branch, merge hoặc rebase.
Về lâu dài, cách thiết kế project tốt hơn là commit file mẫu, ví dụ:
config.example.yaml
.env.example
Sau đó mỗi developer tạo file local riêng đã được ignore:
config.local.yaml
.env
7. File chưa được Git theo dõi: dùng .git/info/exclude
Nếu file chưa từng được tracked và bạn chỉ muốn ignore trên máy mình, không muốn sửa .gitignore chung của project, có thể thêm pattern vào:
.git/info/exclude
Ví dụ:
.camber/
.cursor/
scripts/
docker-compose.infra.yml
config/
Makefile
File này hoạt động gần giống .gitignore, nhưng chỉ áp dụng cho repository local và không được commit lên remote.
Bạn có thể kiểm tra các file untracked đang không bị exclude bằng:
git ls-files --others --exclude-standard
Hoặc kiểm tra vì sao một file đang bị ignore:
git check-ignore -v config/config-local.yaml
Khi nào dùng cách nào?
| Tình trạng file | Nhu cầu | Cách phù hợp |
|---|---|---|
| File chưa tracked, cả team đều không nên commit | Ignore cho toàn team | .gitignore |
| File chưa tracked, chỉ riêng máy bạn muốn ignore | Ignore local | .git/info/exclude |
| File đã tracked, tạm thời không muốn thấy thay đổi local | Giảm nhiễu ở máy local | git update-index --skip-worktree |
| File cấu hình khác nhau trên từng máy | Giải pháp bền vững | Commit file mẫu, ignore file local thật |
Một điểm quan trọng: nếu file đã được Git theo dõi, thêm nó vào .git/info/exclude cũng không làm Git ngừng theo dõi. Khi đó, bạn cần thiết kế lại file cấu hình hoặc cân nhắc skip-worktree cho nhu cầu tạm thời.
8. Tổng kết tình huống của Goku
Sau một buổi chiến đấu với Git, Goku đã rút ra workflow sau:
- Tạo một SSH key riêng cho từng GitHub account;
- Khai báo mỗi key bằng một
Hostalias trong~/.ssh/config; - Dùng
ssh -T <alias>để kiểm tra alias đang đăng nhập vào account nào; - Clone bằng URL
git@<alias>:<organization>/<repository>.git; - Với repository đã có, dùng
git remote set-urlđể chuyển sang đúng alias; - Dùng global config làm mặc định và local config cho repository đặc biệt;
- Kiểm tra remote, name, email và status trước khi commit/push;
- Dùng
.git/info/excludecho file untracked chỉ muốn ignore local; - Chỉ dùng
skip-worktreecó chủ đích cho file đã tracked.
Nếu chỉ nhớ một điều trong bài này, hãy nhớ:
Đúng SSH key giúp bạn push bằng đúng account; đúng
user.namevàuser.emailgiúp commit mang đúng danh tính.
Vậy là Goku có thể code cho Kame House vào buổi sáng, Capsule Corp vào buổi chiều và team Saiyan vào buổi tối mà không vô tình dùng nhầm account nữa.
Ở phần tiếp theo, mình sẽ tiếp tục chia sẻ các tình huống Git thực tế thường gặp khi làm việc theo team: cập nhật branch, rebase, xử lý conflict, sửa commit và khôi phục khi lỡ thao tác sai.
Cảm ơn mọi người đã đọc bài viết!
Cheat sheet
# Kiểm tra SSH alias
ssh -T github-goku-capsule
# Clone bằng account cụ thể
git clone git@github-goku-capsule:capsule-corp/dragon-radar.git
# Kiểm tra và đổi remote của repository hiện tại
git remote -v
git remote set-url origin git@github-goku-capsule:capsule-corp/dragon-radar.git
# Cấu hình danh tính mặc định
git config --global user.name "Goku Son"
git config --global user.email "goku@kamehouse.example"
# Cấu hình danh tính cho repository hiện tại
git config --local user.name "Goku Capsule"
git config --local user.email "goku@capsule-corp.example"
# Kiểm tra config thực tế và nguồn của config
git config --show-origin --get user.name
git config --show-origin --get user.email
# Commit và push
git add .
git commit -m "Add Dragon Radar API"
git push -u origin goku/add-dragon-radar-api
# Tạm bỏ qua thay đổi của file đã tracked
git update-index --skip-worktree config/config-local.yaml
# Theo dõi thay đổi trở lại
git update-index --no-skip-worktree config/config-local.yaml
# Liệt kê file có cờ skip-worktree
git ls-files -v | grep '^S'
All rights reserved