Tuần 2 - Ngày 5: Fork/Upstream Sync — Agency & Contractor Pattern
Mục tiêu học tập
- Hiểu triangular workflow (fork ↔ upstream)
- Setup remote
upstreamtrỏ đến client repo - Thực hành sync flow: fetch upstream → rebase → force-push to fork an toàn
- Xử lý conflict khi rebase nhiều commits lên upstream mới nhất
- Nắm bidirectional sync khi client merge back
1. Bối Cảnh: Agency/Contractor Pattern
Tình huống thực tế: Công ty bạn (agency) làm dự án cho client. Client có repo riêng. Workflow:
2. Setup Ban Đầu
# 1. Clone fork của agency (origin tự động được setup)
git clone https://github.com/your-agency/project-repo.git
cd project-repo
# 2. Thêm upstream (client repo)
git remote add upstream https://github.com/client-org/project-repo.git
# 3. Kiểm tra remotes
git remote -v
# origin https://github.com/your-agency/project-repo.git (fetch)
# origin https://github.com/your-agency/project-repo.git (push)
# upstream https://github.com/client-org/project-repo.git (fetch)
# upstream https://github.com/client-org/project-repo.git (push)
# Best practice: chỉ fetch từ upstream, không push thẳng vào upstream
# (PR mới là cách đúng để contribute code lên upstream)
git remote set-url --push upstream DISABLE
# Giờ "git push upstream" sẽ báo lỗi — tránh accident push
# Verify
git remote -v
# upstream https://github.com/client-org/project-repo.git (fetch)
# upstream DISABLE (push)
3. Daily Sync Flow
Mỗi ngày trước khi bắt đầu làm việc:
# Bước 1: Fetch toàn bộ cập nhật từ upstream
git fetch upstream
# remote: Enumerating objects: 23, done.
# remote: Counting objects: 100% (23/23), done.
# From https://github.com/client-org/project-repo
# abc1234..def5678 main -> upstream/main
# xyz9012..uvw3456 develop -> upstream/develop
# Bước 2: Rebase local main lên upstream/main
git switch main
git rebase upstream/main
# Successfully rebased and updated refs/heads/main.
# Bước 3: Sync origin (team fork) với upstream
git push origin main
# Nếu origin đã có divergent history (ai đó push trực tiếp), dùng --force-with-lease:
git push origin main --force-with-lease
# Bước 4: Rebase feature branch đang làm lên main mới nhất
git switch feature/my-task
git rebase main
Tại sao dùng rebase thay vì merge?
# Merge: tạo merge commit mỗi lần sync → lịch sử fork lộn xộn khi PR
#
# git merge upstream/main
#
# * abc1234 Merge remote-tracking branch 'upstream/main' ← xấu, không cần thiết
# * def5678 feat: my feature
# Rebase: lịch sử tuyến tính, PR dễ review
#
# git rebase upstream/main
#
# * def5678 feat: my feature ← commit của bạn nằm trên đỉnh upstream
# * [upstream commits]
4. Triangular Workflow — Push to Fork, PR to Upstream
# Workflow chuẩn:
# 1. Sync (như trên)
# 2. Tạo feature branch
git switch -c feature/new-api-endpoint
# 3. Code và commit
git add . && git commit -m "feat: add /api/v2/users endpoint"
git add . && git commit -m "feat: add pagination to user list"
# 4. Trước khi push, rebase lên upstream mới nhất
git fetch upstream
git rebase upstream/main
# Nếu conflict → xử lý (xem section 5)
# 5. Push feature branch lên fork (origin)
git push origin feature/new-api-endpoint
# 6. Tạo PR từ fork → upstream trên GitHub
gh pr create \
--repo client-org/project-repo \
--head your-agency:feature/new-api-endpoint \
--base main \
--title "feat: add /api/v2/users endpoint with pagination" \
--body "..."
5. Xử Lý Conflict Khi Rebase Nhiều Commits
Khi rebase một feature branch dài lên upstream đã có nhiều commits mới:
git fetch upstream
git rebase upstream/main
# Git rebase nhiều commits — có thể conflict ở từng commit:
# CONFLICT (content): Merge conflict in src/api/users.ts
# error: could not apply a1b2c3d... feat: add /api/v2/users endpoint
# Xem conflict hiện tại đang ở commit nào
git status
# rebase in progress; onto upstream/main
# Currently rebasing branch 'feature/new-api-endpoint' on 'abc9876'
# Resolve conflict (xem bài day-03)
vim src/api/users.ts
git add src/api/users.ts
git rebase --continue
# Nếu conflict tiếp tục ở commit tiếp theo → lại resolve → continue
# ...cho đến hết
# Bỏ qua commit hiện tại (nếu nó đã được upstream xử lý rồi)
git rebase --skip
# Hủy toàn bộ rebase, quay về trạng thái trước
git rebase --abort
Rebase nhiều commits với rerere
# Bật rerere để tái dùng conflict resolution (quan trọng khi rebase thường xuyên)
git config rerere.enabled true
# Lần đầu resolve conflict ở commit A → rerere ghi nhớ
# Lần sau rebase lại, gặp conflict y hệt → rerere tự apply
6. Force-Push to Fork An Toàn
Sau khi rebase, cần force-push để cập nhật fork:
# KHÔNG an toàn — xoá commits của đồng nghiệp nếu họ đã push lên cùng branch
git push origin feature/new-api-endpoint --force
# AN TOÀN — kiểm tra remote-tracking ref trước khi push
git push origin feature/new-api-endpoint --force-with-lease
# Nếu đồng nghiệp đã push lên feature branch đó:
# error: failed to push some refs
# hint: Updates were rejected because the remote contains work that you do
# hint: not have locally.
# → Bạn phải fetch và giải quyết conflict trước
# Cấu hình alias cho tiện
git config --global alias.fpush 'push --force-with-lease'
git fpush origin feature/new-api-endpoint
Khi nào force-push được phép?
OK để force-push:
✓ Branch cá nhân (chỉ mình bạn làm việc trên đó)
✓ Feature branch chưa ai khác check out
✓ Sau khi thông báo team: "Tôi đang rebase + force push feature/X"
KHÔNG force-push:
✗ main, develop, release/* — shared branches
✗ Branch đang có open PR mà reviewer đã để comments theo line number
(rebase thay đổi commit hash → comments bị mất context)
✗ Không có --force-with-lease (luôn dùng --force-with-lease)
7. Bidirectional Sync: Client Merges Back
Khi client accept PR của team bạn vào upstream, fork cần được sync lại:
# Sau khi PR được merged vào upstream/main:
git fetch upstream
git switch main
git rebase upstream/main
# Origin (fork) cần được cập nhật
git push origin main
# Nếu main đã diverge:
git push origin main --force-with-lease
# Xoá branch đã merged (local + remote)
git branch -d feature/new-api-endpoint
git push origin --delete feature/new-api-endpoint
Khi client có hotfix chưa được PR
Đôi khi client push trực tiếp lên upstream mà không qua PR của team:
# Phát hiện: upstream/main có commits mà origin/main không có
git fetch upstream
git log origin/main..upstream/main --oneline
# abc1234 hotfix: patch critical security issue (from client team)
# Sync fork với upstream
git switch main
git rebase upstream/main
git push origin main --force-with-lease
# Rebase tất cả feature branches đang active lên main mới
git switch feature/my-ongoing-work
git rebase main
8. Divergent History và Reflog Recovery
Khi history bị phân kỳ quá nhiều
# Xem mức độ diverge
git log origin/main..upstream/main --oneline | wc -l # upstream ahead bao nhiêu commit
git log upstream/main..origin/main --oneline | wc -l # fork ahead bao nhiêu commit
# Nếu fork có commits không nên có (accident commits trực tiếp vào main):
# Option 1: Reset fork/main về upstream/main
git switch main
git reset --hard upstream/main
git push origin main --force-with-lease
# Option 2: Nếu cần giữ lại vài commits cụ thể từ fork
git log origin/main..upstream/main --oneline # xem commits cần cherry-pick
git reset --hard upstream/main
git cherry-pick <commit-hash>
git push origin main --force-with-lease
Lấy lại commits bị mất qua reflog
# Tình huống: rebase sai → commits "biến mất"
git reflog
# abc1234 HEAD@{0}: rebase (finish): returning to refs/heads/feature/X
# def5678 HEAD@{3}: commit: feat: my important commit ← commit bị mất
# ...
# Khôi phục commit bị mất
git checkout -b recovery/feature-x def5678
# Hoặc cherry-pick:
git cherry-pick def5678
# Nếu rebase bị lỗi hoàn toàn, trở về ORIG_HEAD
git reset --hard ORIG_HEAD
# (ORIG_HEAD được Git set tự động trước khi rebase/merge)
Câu hỏi ôn tập
-
Tại sao nên disable push URL cho
upstreamremote?Xem đáp án
Để tránh accident push thẳng vào upstream (repo của client). Nguyên tắc: chỉ contribute code qua PR từ fork → upstream, không push trực tiếp.
git remote set-url --push upstream DISABLElàm chogit push upstreambáo lỗi ngay — nhắc nhở bạn phải dùng PR. Vẫn có thểgit fetch upstreambình thường. -
Khi rebase feature branch lên upstream/main gặp conflict ở commit thứ 3, làm gì sau khi resolve?
Xem đáp án
(1) Chỉnh sửa file để resolve conflict, (2)
git add <file>, (3)git rebase --continue. Git sẽ áp dụng commit thứ 3 xong rồi tiếp tục với các commits tiếp theo — có thể gặp thêm conflict ở commit sau. Nếu muốn bỏ qua commit hiện tại (đã được upstream handle rồi):git rebase --skip. Để hủy toàn bộ:git rebase --abort. -
--force-with-leasebảo vệ điều gì so với--force?Xem đáp án
--force-with-leasekiểm tra remote-tracking ref local (ví dụorigin/feature/x) có khớp với remote thực hay không. Nếu đồng nghiệp đã push lên branch đó trong lúc bạn rebase, remote-tracking ref local đã lỗi thời → Git từ chối push.--forceghi đè vô điều kiện, có thể xoá commits của đồng nghiệp mà không biết. -
Sau khi PR được merged vào upstream, bạn phải làm gì để fork đồng bộ?
Xem đáp án
(1)
git fetch upstreamđể cập nhật remote-tracking branches, (2)git switch main && git rebase upstream/mainđể sync local main, (3)git push origin main(có thể cần--force-with-lease) để cập nhật fork, (4)git branch -d feature/my-branch && git push origin --delete feature/my-branchđể dọn branch đã merged. -
Lệnh nào giúp xem commits đang có trong upstream nhưng chưa có trong fork?
Xem đáp án
git log origin/main..upstream/main --oneline— hiện commits có trongupstream/mainnhưng chưa có trongorigin/main(fork). Nhớgit fetch upstreamtrước để cập nhật remote-tracking refs. Để xem chiều ngược lại (fork ahead upstream):git log upstream/main..origin/main --oneline.
Bài tập thực hành
# Mô phỏng triangular workflow với 2 repos local
mkdir upstream-sim && cd upstream-sim
git init --bare upstream.git
mkdir client-work && cd client-work
git clone ../upstream.git .
git config user.name "Client" && git config user.email "client@test.com"
echo "initial" > README.md
git add . && git commit -m "feat: initial commit"
git push origin main
cd ..
# "Fork" — clone upstream làm origin cho agency
mkdir agency-fork && cd agency-fork
git clone ../upstream.git .
git config user.name "Agency Dev" && git config user.email "dev@agency.com"
git remote add upstream ../upstream.git
git remote set-url --push upstream DISABLE
git remote -v
# Client thêm commits vào upstream
cd ../client-work
echo "client feature" >> README.md
git add . && git commit -m "feat: client adds new feature"
git push origin main
# Agency sync với upstream
cd ../agency-fork
git fetch upstream
git log upstream/main --oneline # thấy commit mới từ client
git switch main
git rebase upstream/main # sync
git log --oneline # history tuyến tính
# Agency tạo feature branch và làm việc
git switch -c feature/agency-work
echo "agency feature" >> README.md
git add . && git commit -m "feat: agency adds API endpoint"
# Trước khi push, sync với upstream mới nhất (giả sử client push thêm)
cd ../client-work
echo "another client commit" >> README.md
git add . && git commit -m "feat: client hotfix"
git push origin main
cd ../agency-fork
git fetch upstream
git rebase upstream/main # rebase feature branch lên upstream mới nhất
git log --oneline --graph --all
Tài liệu tham khảo chính thức
- GitHub: Fork a repo
- GitHub: Syncing a fork
- git rebase documentation
- git remote documentation
- Atlassian: Forking Workflow
Tiếp theo: Ngày 6 — Advanced Git Tools