Yêu cầu về lý do hợp nhất trong DVCS tốt hơn trong Subversion chủ yếu dựa trên cách phân nhánh và hợp nhất hoạt động trong Subversion trước đây. Subversion trước 1.5.0 không lưu trữ bất kỳ thông tin nào về thời điểm các chi nhánh được hợp nhất, do đó, khi bạn muốn hợp nhất, bạn phải chỉ định phạm vi sửa đổi nào phải được hợp nhất.
Vậy tại sao Subversion sáp nhập hút ?
Suy ngẫm ví dụ này:
1 2 4 6 8
trunk o-->o-->o---->o---->o
\
\ 3 5 7
b1 +->o---->o---->o
Khi chúng tôi muốn hợp nhất các thay đổi của b1 vào trung kế, chúng tôi sẽ đưa ra lệnh sau, trong khi đang đứng trên một thư mục có kiểm tra thân cây:
svn merge -r 2:7 {link to branch b1}
Sẽ cố gắng hợp nhất các thay đổi từ b1
vào thư mục làm việc cục bộ của bạn. Và sau đó bạn cam kết thay đổi sau khi bạn giải quyết bất kỳ xung đột nào và kiểm tra kết quả. Khi bạn cam kết cây sửa đổi sẽ trông như thế này:
1 2 4 6 8 9
trunk o-->o-->o---->o---->o-->o "the merge commit is at r9"
\
\ 3 5 7
b1 +->o---->o---->o
Tuy nhiên, cách xác định phạm vi sửa đổi này nhanh chóng vượt khỏi tầm kiểm soát khi cây phiên bản phát triển khi lật đổ không có bất kỳ dữ liệu meta nào về thời điểm và những sửa đổi được hợp nhất với nhau. Suy ngẫm về những gì xảy ra sau này:
12 14
trunk …-->o-------->o
"Okay, so when did we merge last time?"
13 15
b1 …----->o-------->o
Đây phần lớn là một vấn đề của thiết kế kho lưu trữ mà Subversion có, để tạo một nhánh bạn cần tạo một thư mục ảo mới trong kho lưu trữ một bản sao của thân cây nhưng nó không lưu trữ bất kỳ thông tin nào về thời điểm và cái gì đôi khi mọi thứ trở lại. Điều đó sẽ dẫn đến xung đột hợp nhất khó chịu. Điều thậm chí còn tồi tệ hơn là Subversion được sử dụng hợp nhất hai chiều theo mặc định, có một số hạn chế làm tê liệt trong việc hợp nhất tự động khi hai đầu chi nhánh không được so sánh với tổ tiên chung của chúng.
Để giảm thiểu Subversion này hiện lưu trữ dữ liệu meta cho chi nhánh và hợp nhất. Điều đó sẽ giải quyết mọi vấn đề phải không?
Và ồ, nhân tiện, Subversion vẫn hút
Trên một hệ thống tập trung, như lật đổ, thư mục ảo hút. Tại sao? Bởi vì mọi người đều có quyền truy cập để xem họ ngay cả những người thử nghiệm rác. Sự phân nhánh là tốt nếu bạn muốn thử nghiệm nhưng bạn không muốn xem thử nghiệm của mọi người và dì của họ . Đây là tiếng ồn nhận thức nghiêm trọng. Bạn càng thêm nhiều chi nhánh, bạn sẽ càng thấy nhiều thứ tào lao.
Càng có nhiều nhánh công khai trong kho lưu trữ, bạn càng khó theo dõi tất cả các nhánh khác nhau. Vì vậy, câu hỏi bạn sẽ có là nếu chi nhánh vẫn đang được phát triển hoặc nếu nó thực sự chết, điều khó có thể nói trong bất kỳ hệ thống kiểm soát phiên bản tập trung nào.
Hầu hết thời gian, từ những gì tôi đã thấy, một tổ chức sẽ mặc định sử dụng một chi nhánh lớn. Đó là một sự xấu hổ vì điều đó sẽ khó theo dõi các phiên bản thử nghiệm và phát hành, và bất cứ điều gì tốt khác đến từ việc phân nhánh.
Vậy tại sao DVCS, chẳng hạn như Git, Mercurial và Bazaar, tốt hơn Subversion trong việc phân nhánh và sáp nhập?
Có một lý do rất đơn giản tại sao: phân nhánh là một khái niệm hạng nhất . Không có thư mục ảo theo thiết kế và các nhánh là các đối tượng cứng trong DVCS mà nó cần phải như vậy để hoạt động đơn giản với việc đồng bộ hóa các kho lưu trữ (tức là đẩy và kéo ).
Điều đầu tiên bạn làm khi bạn làm việc với DVCS là sao chép các kho lưu trữ (git's clone
, hg's clone
và bzr's branch
). Nhân bản về mặt khái niệm giống như việc tạo một nhánh trong kiểm soát phiên bản. Một số người gọi việc này là rẽ nhánh hoặc phân nhánh (mặc dù sau này thường được sử dụng để chỉ các nhánh cùng vị trí), nhưng đó chỉ là điều tương tự. Mỗi người dùng chạy kho lưu trữ của riêng họ, điều đó có nghĩa là bạn có một nhánh cho mỗi người dùng đang diễn ra.
Cấu trúc phiên bản không phải là một cái cây , mà thay vào đó là một biểu đồ . Cụ thể hơn là một biểu đồ chu kỳ có hướng (DAG, nghĩa là một biểu đồ không có bất kỳ chu kỳ nào). Bạn thực sự không cần phải chú ý đến các chi tiết cụ thể của DAG ngoài mỗi cam kết có một hoặc nhiều tài liệu tham khảo chính (mà cam kết dựa trên). Vì vậy, các biểu đồ sau đây sẽ hiển thị các mũi tên giữa các phiên bản ngược lại vì điều này.
Một ví dụ rất đơn giản về việc sáp nhập sẽ là điều này; hãy tưởng tượng một kho lưu trữ trung tâm được gọi origin
và một người dùng, Alice, nhân bản kho lưu trữ vào máy của cô ấy.
a… b… c…
origin o<---o<---o
^master
|
| clone
v
a… b… c…
alice o<---o<---o
^master
^origin/master
Điều xảy ra trong một bản sao là mọi sửa đổi được sao chép vào Alice chính xác như bản gốc (được xác thực bởi mã băm nhận dạng duy nhất) và đánh dấu nơi các nhánh của nguồn gốc đang ở.
Alice sau đó làm việc trên repo của mình, cam kết trong kho lưu trữ của riêng mình và quyết định thúc đẩy các thay đổi của mình:
a… b… c…
origin o<---o<---o
^ master
"what'll happen after a push?"
a… b… c… d… e…
alice o<---o<---o<---o<---o
^master
^origin/master
Giải pháp khá đơn giản, điều duy nhất mà origin
kho lưu trữ cần làm là thực hiện tất cả các phiên bản mới và chuyển chi nhánh của nó sang phiên bản mới nhất (mà git gọi là "chuyển tiếp nhanh"):
a… b… c… d… e…
origin o<---o<---o<---o<---o
^ master
a… b… c… d… e…
alice o<---o<---o<---o<---o
^master
^origin/master
Trường hợp sử dụng, mà tôi đã minh họa ở trên, thậm chí không cần hợp nhất bất cứ điều gì . Vì vậy, vấn đề thực sự không phải là với các thuật toán hợp nhất vì thuật toán hợp nhất ba chiều khá giống nhau giữa tất cả các hệ thống kiểm soát phiên bản. Vấn đề là về cấu trúc nhiều hơn bất cứ điều gì .
Vì vậy, làm thế nào về bạn cho tôi thấy một ví dụ có một sự hợp nhất thực sự ?
Phải thừa nhận ví dụ trên là một trường hợp sử dụng rất đơn giản, vì vậy hãy thực hiện một thao tác xoắn hơn nhiều mặc dù là trường hợp phổ biến hơn. Hãy nhớ rằng origin
bắt đầu với ba phiên bản? Chà, anh chàng đã làm chúng, hãy gọi anh ta là Bob , đã tự mình làm việc và thực hiện một cam kết trên kho lưu trữ của riêng mình:
a… b… c… f…
bob o<---o<---o<---o
^ master
^ origin/master
"can Bob push his changes?"
a… b… c… d… e…
origin o<---o<---o<---o<---o
^ master
Bây giờ Bob không thể đẩy các thay đổi của mình trực tiếp vào origin
kho lưu trữ. Làm thế nào hệ thống phát hiện ra điều này bằng cách kiểm tra xem các bản sửa đổi của Bob có trực tiếp giải thích từ origin
không, trong trường hợp này thì không. Bất kỳ nỗ lực thúc đẩy nào cũng sẽ dẫn đến hệ thống nói điều gì đó giống như " Uh ... tôi sợ không thể để bạn làm điều đó Bob ."
Vì vậy, Bob phải đăng nhập và sau đó hợp nhất các thay đổi (với git's pull
; hoặc hg's pull
và merge
; hoặc bzr's merge
). Đây là một quy trình gồm hai bước. Đầu tiên Bob phải tìm nạp các bản sửa đổi mới, bản sao này sẽ sao chép chúng khi chúng từ origin
kho lưu trữ. Bây giờ chúng ta có thể thấy rằng biểu đồ phân kỳ:
v master
a… b… c… f…
bob o<---o<---o<---o
^
| d… e…
+----o<---o
^ origin/master
a… b… c… d… e…
origin o<---o<---o<---o<---o
^ master
Bước thứ hai của quy trình kéo là hợp nhất các mẹo phân kỳ và đưa ra kết quả:
v master
a… b… c… f… 1…
bob o<---o<---o<---o<-------o
^ |
| d… e… |
+----o<---o<--+
^ origin/master
Hy vọng việc hợp nhất sẽ không xảy ra xung đột (nếu bạn dự đoán chúng, bạn có thể thực hiện hai bước thủ công trong git với fetch
và merge
). Những gì sau này cần phải được thực hiện là đẩy các thay đổi đó trở lại origin
, điều này sẽ dẫn đến một sự hợp nhất chuyển tiếp nhanh vì cam kết hợp nhất là hậu duệ trực tiếp của origin
kho lưu trữ mới nhất :
v origin/master
v master
a… b… c… f… 1…
bob o<---o<---o<---o<-------o
^ |
| d… e… |
+----o<---o<--+
v master
a… b… c… f… 1…
origin o<---o<---o<---o<-------o
^ |
| d… e… |
+----o<---o<--+
Có một tùy chọn khác để hợp nhất trong git và hg, được gọi là rebase , sẽ chuyển các thay đổi của Bob sang sau các thay đổi mới nhất. Vì tôi không muốn câu trả lời này dài dòng hơn nữa nên tôi sẽ cho phép bạn đọc các tài liệu git , mercurial hoặc bazaar về điều đó thay vào đó.
Như một bài tập cho người đọc, hãy thử tìm hiểu xem nó sẽ hoạt động như thế nào với một người dùng khác có liên quan. Nó được thực hiện tương tự như ví dụ trên với Bob. Hợp nhất giữa các kho lưu trữ dễ dàng hơn những gì bạn nghĩ bởi vì tất cả các sửa đổi / cam kết đều có thể nhận dạng duy nhất.
Ngoài ra còn có vấn đề gửi các bản vá giữa mỗi nhà phát triển, đó là một vấn đề lớn trong Subversion được giảm nhẹ trong git, hg và bzr bằng các bản sửa đổi có thể nhận dạng duy nhất. Khi ai đó đã hợp nhất các thay đổi của anh ấy (nghĩa là thực hiện một cam kết hợp nhất) và gửi nó cho mọi người khác trong nhóm để tiêu thụ bằng cách đẩy vào kho lưu trữ trung tâm hoặc gửi các bản vá thì họ không phải lo lắng về việc hợp nhất, vì nó đã xảy ra . Martin Fowler gọi đây là cách làm việc tích hợp bừa bãi .
Bởi vì cấu trúc khác với Subversion, thay vì sử dụng DAG, nó cho phép phân nhánh và hợp nhất được thực hiện một cách dễ dàng hơn không chỉ cho hệ thống mà còn cho cả người dùng.