0

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:

  1. Bạn dùng GitHub account nào để kết nối và push code?
  2. 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.nameuser.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.nameuser.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, pullpush 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:

  1. Tạo một SSH key riêng cho từng GitHub account;
  2. Khai báo mỗi key bằng một Host alias trong ~/.ssh/config;
  3. Dùng ssh -T <alias> để kiểm tra alias đang đăng nhập vào account nào;
  4. Clone bằng URL git@<alias>:<organization>/<repository>.git;
  5. Với repository đã có, dùng git remote set-url để chuyển sang đúng alias;
  6. Dùng global config làm mặc định và local config cho repository đặc biệt;
  7. Kiểm tra remote, name, email và status trước khi commit/push;
  8. Dùng .git/info/exclude cho file untracked chỉ muốn ignore local;
  9. Chỉ dùng skip-worktree có 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.nameuser.email giú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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí