•9 min read

Fork riêng, giữ riêng tư, đồng bộ với upstream

Fork riêng, giữ riêng tư, đồng bộ với upstream

Bạn đã bao giờ tìm thấy một dự án mã nguồn mở đáp ứng hầu hết các nhu cầu của mình, nhưng không phải tất cả? Tôi gặp phải điều này thường xuyên. Bạn muốn thêm các tính năng riêng, giữ chúng riêng tư và vẫn nhận được các bản sửa lỗi từ upstream mà không bị mất trí.

Quy trình làm việc fork tiêu chuẩn trên GitHub hoạt động tốt cho các đóng góp công khai. Nhưng nếu bạn đang xây dựng một thứ độc quyền dựa trên công việc mã nguồn mở của người khác thì sao? Bạn cần một cách tiếp cận khác.

Đây là thiết lập mà tôi đã áp dụng sau khi thử qua một vài chiến lược khác nhau. Nó không cầu kỳ. Nhưng nó hiệu quả.

Audio Briefing
0:00 / 0:00
Mục tiêu

Giữ một bản fork riêng tư với các commit tùy chỉnh của bạn được áp dụng gọn gàng trên các thay đổi upstream mới nhất. Không có nhiễu merge. Không có những cơn ác mộng xung đột thủ công.


Thiết lập

Đầu tiên, clone dự án gốc. Sau đó hoán đổi các remote để repo riêng tư của bạn trở thành origin và bản gốc trở thành upstream.

git clone https://github.com/original-author/some-project
cd some-project
git remote set-url origin https://github.com/you/your-private-repo
git remote add upstream https://github.com/original-author/some-project
git push origin main

Tại sao lại làm theo cách này? Có hai lý do. Thứ nhất, origin là remote mặc định của git. Khi bạn gõ git push mà không suy nghĩ, nó sẽ đi đến repo riêng tư của bạn. Điều đó an toàn. Thứ hai, việc giữ bản gốc là upstream giúp dễ dàng nhận biết các bản cập nhật đến từ đâu.

Tôi đã thấy nhiều người bỏ qua bước này và chỉ push trực tiếp lên remote của bản fork của họ. Điều đó cũng hoạt động. Nhưng tôi thích sự rõ ràng về remote nào là remote nào. Nó giúp tôi không push nhầm thứ gì đó vào sai chỗ lúc 11 giờ đêm.


Advertisement

Vòng lặp đồng bộ hóa

Đây là nơi quy trình làm việc thực sự diễn ra. Dự án gốc tiếp tục phát triển. Bạn cần kéo những thay đổi đó vào mà không làm hỏng công việc tùy chỉnh của mình.

git fetch upstream
git checkout main
git rebase upstream/main
git push origin main --force-with-lease

Bốn lệnh. Chỉ vậy thôi. Hãy để tôi giải thích tại sao mỗi lệnh lại quan trọng.

Fetch, đừng Pull

git fetch upstream tải xuống các commit mới nhất từ dự án gốc. Nó không merge chúng vào nhánh của bạn. Điều đó là có chủ ý. Bạn muốn xem những gì sắp tới trước khi áp dụng nó.

Tôi từng chạy git pull upstream main theo thói quen. Ý tưởng tồi. Pull thực hiện fetch và một merge ngầm. Bạn mất quyền kiểm soát cách các thay đổi của bạn được kết hợp với của họ.

Rebase

git rebase upstream/main tua lại các commit của bạn, áp dụng các thay đổi upstream, sau đó phát lại các commit của bạn lên trên. Công việc tùy chỉnh của bạn vẫn ở trên cùng. Các thay đổi upstream vẫn ở bên dưới.

Tại sao Rebase thay vì Merge

Merge commit có mục đích trong công việc nhóm cộng tác. Nhưng đối với việc đồng bộ hóa fork đơn lẻ, chúng là nhiễu. Mỗi khi bạn chạy git merge, bạn tạo một commit nói "đã merge nhánh x vào nhánh y." Làm điều đó hàng chục lần và lịch sử của bạn trở thành một mớ hỗn độn của các bong bóng merge trống rỗng. Rebase giữ cho nó tuyến tính và dễ đọc.

Force Push với lưới an toàn

Rebase viết lại lịch sử. Các hash commit thay đổi. Git sẽ la lên với bạn nếu bạn cố gắng push bình thường. Đó là lúc --force-with-lease xuất hiện.

git push origin main --force-with-lease

Điều này an toàn hơn --force đơn thuần. Nó kiểm tra xem không có ai khác đã push lên nhánh remote kể từ lần fetch cuối cùng của bạn. Nếu có, git sẽ từ chối push. Bạn sẽ không vô tình ghi đè lên công việc của bất kỳ ai.

Lease, không phải Force

Một --force đơn thuần push một cách mù quáng. --force-with-lease là dây an toàn của bạn. Hãy sử dụng nó. Mọi lúc.


Thay thế Merge

Một số người tranh luận ủng hộ merge hơn rebase. Họ nói rằng rebase viết lại lịch sử và điều đó là không tốt. Tôi hiểu lập luận đó. Trong một nhánh được chia sẻ với nhiều cộng tác viên, tôi sẽ đồng ý. Merge commit tạo ra một bản ghi về thời điểm mọi thứ xảy ra.

Nhưng đối với một bản fork riêng tư? Bạn là người duy nhất làm việc trên đó. Một lịch sử tuyến tính sạch sẽ có giá trị hơn một bản ghi được đóng dấu thời gian của mọi lần đồng bộ hóa. Bạn muốn nhìn lại sau sáu tháng và thấy các commit tùy chỉnh của mình được xếp chồng gọn gàng lên trên các bản phát hành upstream. Chứ không phải một mạng lưới rối rắm của các bong bóng merge.

Nếu bạn đang làm việc với một nhóm trên bản fork, hãy sử dụng merge. Giao tiếp với nhóm của bạn. Chọn một chiến lược và tuân thủ nó. Không có gì tệ hơn việc một nửa nhóm rebase và nửa còn lại merge.


Xử lý xung đột

Rebase đôi khi sẽ tạo ra xung đột. Nó không vui, nhưng có thể quản lý được.

git rebase upstream/main
# git says: conflict in some-file.ts
# Fix the file manually
git add some-file.ts
git rebase --continue

Điểm mấu chốt: giải quyết xung đột trong mã của bạn, không phải trong mã của họ. Các thay đổi upstream là sự thật. Các sửa đổi tùy chỉnh của bạn cần phải thích ứng với bất cứ điều gì mà người duy trì upstream đã thay đổi.

Nếu xung đột quá lớn, bạn có các lựa chọn:

  • Hủy bỏ và merge thay thế: git rebase --abort hoàn tác mọi thứ. Hãy thử git merge upstream/main nếu bạn đang bị áp lực về thời gian.
  • Chia commit của bạn: Một commit lớn chạm vào nhiều tệp thường xuyên xung đột hơn. Giữ các commit của bạn nhỏ và tập trung.
  • Liên hệ: Nếu upstream thay đổi một API mà bạn phụ thuộc vào, những người duy trì có thể có lời khuyên di chuyển.
Tư duy xung đột

Upstream không biết về bản fork của bạn. Khi họ tái cấu trúc nội bộ, họ sẽ làm hỏng mã tùy chỉnh của bạn. Điều đó là bình thường. Dành thời gian để giải quyết xung đột sau các bản phát hành upstream lớn.


Advertisement

Khi nào không nên sử dụng quy trình làm việc này

Cách tiếp cận này không phù hợp với mọi tình huống. Đây là khi tôi sẽ chọn một thứ khác:

  • Bạn dự định đóng góp ngược lại: Chỉ cần fork công khai trên GitHub và gửi PR. Không cần phải thực hiện điệu nhảy repo riêng tư.
  • Upstream thay đổi liên tục: Các bản phát hành hàng ngày có nghĩa là rebase liên tục. Hãy cân nhắc việc vendoring dependency thay thế.
  • Các thay đổi của bạn quá lớn: Nếu bạn đang viết lại một nửa dự án, việc duy trì một bản fork sẽ làm tăng ma sát. Bạn có thể tốt hơn nếu tự xây dựng thứ của riêng mình.
  • Giấy phép không cho phép bạn: Đây là điều quan trọng nhất.
Khả năng tương thích giấy phép

Trước khi tạo bất kỳ bản fork riêng tư nào, hãy đọc giấy phép upstream. MIT và Apache 2.0 cho phép các bản fork riêng tư. GPL và AGPL yêu cầu bạn phải công bố các sửa đổi nếu bạn phân phối phần mềm. Điều này không phải là tùy chọn. Vi phạm giấy phép có thể khiến bạn gặp rắc rối pháp lý.


Tham khảo nhanh

Đây là toàn bộ quy trình làm việc trong một khối. Hãy đánh dấu nó.

# Initial setup
git clone https://github.com/original-author/some-project
cd some-project
git remote set-url origin https://github.com/you/your-private-repo
git remote add upstream https://github.com/original-author/some-project
git push origin main

# Each sync cycle
git fetch upstream
git checkout main
git rebase upstream/main
git push origin main --force-with-lease

Suy nghĩ cuối cùng

Tôi đã sử dụng quy trình làm việc này khoảng hai năm trên một số dự án. Nó không hoàn hảo. Xung đột vẫn xảy ra. Các rebase lớn có thể tẻ nhạt. Nhưng nó tốt hơn các lựa chọn thay thế.

Các lựa chọn thay thế tệ hơn. Hoặc bạn không bao giờ đồng bộ hóa upstream và bản fork của bạn bị mục nát. Hoặc bạn merge một cách mù quáng và lịch sử của bạn biến thành một nút thắt mà không ai có thể đọc được. Hoặc bạn từ bỏ quyền riêng tư và mã nguồn mở mọi thứ.

Bạn làm chủ quy trình làm việc của mình

Chọn cách tiếp cận phù hợp với tình huống của bạn. Đối với tôi, một bản fork riêng tư với các lần đồng bộ hóa rebase định kỳ là điểm ngọt ngào giữa việc cập nhật và giữ quyền kiểm soát. Kết quả của bạn sẽ khác nhau. Điều đó không sao cả.

Bạn cũng có thể thích

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement
Cách tôi thiết lập CI/CD với GitHub Actions
developer-tools

Cách tôi thiết lập CI/CD với GitHub Actions

Hướng dẫn thực tế về cách thiết lập các workflow GitHub Actions cho một dự án web điển hình — chạy thử nghiệm, lưu trữ dependency vào bộ nhớ đệm, triển khai lên Vercel, xử lý secret và tránh những lỗi phổ biến khiến CI chậm hoặc không đáng tin cậy.

Read more