Chi nhánh được đặt tên và nhiều kho lưu trữ


130

Chúng tôi hiện đang sử dụng lật đổ trên một cơ sở mã tương đối lớn. Mỗi bản phát hành có nhánh riêng và các bản sửa lỗi được thực hiện đối với thân cây và được di chuyển vào các nhánh phát hành bằng cách sử dụngsvnmerge.py

Tôi tin rằng đã đến lúc phải chuyển sang kiểm soát nguồn tốt hơn và tôi đã chơi với Mercurial một thời gian.

Dường như có hai trường mặc dù về việc quản lý cấu trúc phát hành như vậy bằng Mercurial. Mỗi bản phát hành đều có repo riêng và các bản sửa lỗi được thực hiện đối với nhánh phát hành và được đẩy sang nhánh chính (và bất kỳ nhánh phát hành mới nào khác.) HOẶC sử dụng các nhánh có tên trong một kho lưu trữ (hoặc nhiều bản sao phù hợp.)

Trong cả hai trường hợp, có vẻ như tôi có thể đang sử dụng một cái gì đó như cấy ghép để thay đổi cherryopper để đưa vào các nhánh phát hành.

Tôi yêu cầu bạn; các giá trị tương đối của mỗi phương pháp là gì?

Câu trả lời:


129

Sự khác biệt lớn nhất là cách các tên chi nhánh được ghi lại trong lịch sử. Với các nhánh được đặt tên, tên nhánh được nhúng trong mỗi bộ thay đổi và do đó sẽ trở thành một phần bất biến của lịch sử. Với bản sao sẽ không có hồ sơ vĩnh viễn về nơi một bộ thay đổi cụ thể đến từ đâu.

Điều này có nghĩa là nhân bản rất tốt cho các thử nghiệm nhanh, nơi bạn không muốn ghi lại tên chi nhánh và các nhánh được đặt tên là tốt cho các nhánh dài hạn ("1.x", "2.x" và tương tự).

Cũng lưu ý rằng một kho lưu trữ duy nhất có thể dễ dàng chứa nhiều nhánh trọng lượng nhẹ trong Mercurial. Các nhánh kho lưu trữ như vậy có thể được đánh dấu để bạn có thể dễ dàng tìm thấy chúng một lần nữa. Giả sử bạn đã nhân bản kho lưu trữ của công ty khi nó trông như thế này:

[a] --- [b]

Bạn hack đi và thực hiện [x][y]:

[a] --- [b] --- [x] --- [y]

Có nghĩa là trong khi ai đó đặt [c][d]vào kho lưu trữ, vì vậy khi bạn kéo bạn sẽ có được một biểu đồ lịch sử như thế này:

            [x] --- [y]
           /
[A B C D]

Ở đây có hai đầu trong một kho lưu trữ duy nhất. Bản sao làm việc của bạn sẽ luôn phản ánh một bộ thay đổi duy nhất, cái gọi là bộ thay đổi cha mẹ làm việc. Kiểm tra điều này với:

% hg parents

Hãy nói rằng nó báo cáo [y]. Bạn có thể thấy những cái đầu với

% hg heads

và điều này sẽ báo cáo [y][d]. Nếu bạn muốn cập nhật kho lưu trữ của mình thành một kiểm tra sạch [d], thì chỉ cần làm (thay thế [d]bằng số sửa đổi cho [d]):

% hg update --clean [d]

Sau đó bạn sẽ thấy hg parentsbáo cáo đó [d]. Điều này có nghĩa là cam kết tiếp theo của bạn sẽ có [d]vai trò là cha mẹ. Do đó, bạn có thể sửa một lỗi bạn nhận thấy trong nhánh chính và tạo thay đổi [e]:

            [x] --- [y]
           /
[a] --- [b] --- [c] --- [d] --- [e]

Để [e]chỉ thay đổi, bạn cần phải làm

% hg push -r [e]

nơi [e]là băm changeset. Theo mặc định hg pushchỉ đơn giản là sẽ so sánh các kho và thấy rằng [x], [y][e]đang mất tích, nhưng bạn có thể không muốn cổ phần [x][y]được nêu ra.

Nếu lỗi cũng ảnh hưởng đến bạn, bạn muốn hợp nhất nó với nhánh tính năng của bạn:

% hg update [y]
% hg merge

Điều đó sẽ để lại biểu đồ kho lưu trữ của bạn trông như thế này:

            [XYZ]
           / /
[a] --- [b] --- [c] --- [d] --- [e]

nơi [z]là việc hợp nhất giữa [y][e]. Bạn cũng có thể đã chọn ném chi nhánh đi:

% hg strip [x]

Điểm chính của tôi về câu chuyện này là: một bản sao duy nhất có thể dễ dàng đại diện cho một số dấu vết phát triển. Điều này luôn đúng với "hg đơn giản" mà không sử dụng bất kỳ tiện ích mở rộng nào. Phần mở rộng bookmark là một trợ giúp tuyệt vời, mặc dù. Nó sẽ cho phép bạn gán tên (dấu trang) cho bộ thay đổi. Trong trường hợp trên, bạn sẽ muốn có một dấu trang trên đầu phát triển của bạn và một dấu trên đầu ngược dòng. Dấu trang có thể được đẩy và kéo bằng Mercurial 1.6 và đã trở thành một tính năng tích hợp trong Mercurial 1.8.

Nếu bạn đã chọn tạo hai bản sao, bản sao phát triển của bạn sẽ trông như thế này sau khi thực hiện [x][y]:

[a] --- [b] --- [x] --- [y]

Và bản sao ngược dòng của bạn sẽ chứa:

[a] --- [b] --- [c] --- [d]

Bây giờ bạn nhận thấy lỗi và sửa nó. Ở đây bạn không phải hg updatekể từ khi bản sao ngược dòng đã sẵn sàng để sử dụng. Bạn cam kết và tạo [e]:

[a] --- [b] --- [c] --- [d] --- [e]

Để bao gồm lỗi trong bản sao phát triển của bạn, bạn kéo nó vào đó:

[a] --- [b] --- [x] --- [y]
           \
            [c] --- [d] --- [e]

và hợp nhất:

[a] --- [b] --- [x] --- [y] --- [z]
           \ /
            [c] --- [d] --- [e]

Biểu đồ có thể trông khác nhau, nhưng nó có cùng cấu trúc và kết quả cuối cùng là như nhau. Sử dụng các bản sao bạn phải làm một ít sổ sách tinh thần.

Các chi nhánh được đặt tên không thực sự đi vào hình ảnh ở đây vì chúng khá tùy chọn. Bản thân Mercurial đã được phát triển bằng cách sử dụng hai bản sao trong nhiều năm trước khi chúng tôi chuyển sang sử dụng các nhánh được đặt tên. Chúng tôi duy trì một nhánh gọi là 'ổn định' ngoài nhánh 'mặc định' và thực hiện các bản phát hành của chúng tôi dựa trên nhánh 'ổn định'. Xem trang phân nhánh tiêu chuẩn trong wiki để biết mô tả về quy trình làm việc được đề xuất.


1
nếu thay đổi đến từ một người dùng khác, điều đó sẽ được ghi lại, vì vậy sử dụng bản sao không có gì xấu. Khi đẩy một Tính năng mới, thường không thú vị khi biết bạn đã làm điều đó từ một repo riêng biệt. Ngoài ra còn có một phần mở rộng localbranch, cung cấp cho bạn một chi nhánh duy nhất tại địa phương. Hữu ích khi nhân bản repo có liên quan đến chi phí cao (thời gian / không gian).
Julian Rudolph

2
tham khảo: 'bản sao rất tốt cho các thử nghiệm nhanh' - Không, chúng không phải! Điều gì xảy ra nếu bạn có một vài tập tin trong repo? Nhân bản sẽ mất nhiều thời gian (bất cứ lúc nào trên 1 phút) trong khi chuyển nhánh chỉ trong chốc lát (<1 giây). Vẫn sử dụng các nhánh được đặt tên sẽ gây ô nhiễm thay đổi. Nó không phải là một ngõ cụt? Hay tôi đang thiếu một cái gì đó?
chọn

Được chọn bộ chọn; Âm thanh như một sửa đổi cho lập luận ban đầu của mình; Bản sao là tốt khi chi phí của nhiều bản sao hoàn chỉnh không quan trọng đối với bạn hoặc khi bạn có thể sử dụng symlink / liên kết cứng của hg để giảm chi phí của các bản sao làm việc cục bộ trên mỗi chi nhánh.
Warren P

@seler: bạn hoàn toàn đúng khi nhân bản là không thực tế nếu mã khoe lớn. Dấu trang là giải pháp sau đó.
Martin Geisler

29

Tôi nghĩ rằng bạn muốn toàn bộ lịch sử trong một repo. Sinh ra một repo ngắn hạn là dành cho các thử nghiệm ngắn hạn, không phải các sự kiện lớn như phát hành.

Một trong những nỗi thất vọng của Mercurial là dường như không có cách nào dễ dàng để tạo ra một nhánh sống ngắn, chơi với nó, từ bỏ nó và thu gom rác. Chi nhánh là mãi mãi. Tôi đồng cảm với việc không bao giờ muốn từ bỏ lịch sử, nhưng các chi nhánh dùng một lần, siêu rẻ là một gittính năng mà tôi thực sự muốn thấy hg.


20
Bạn có thể dễ dàng tạo một nhánh tính năng như vậy: "hg update" đến điểm nhánh của bạn, chỉnh sửa đi và "hg commit". Bạn mới tạo ra một dòng phát triển khác nhau - các cam kết mới sẽ mở rộng chi nhánh này. Sử dụng "hg clone -r" để loại bỏ nó hoặc xóa nội tuyến bằng "hg dải". Vì vậy, đừng thất vọng, hoặc đến với danh sách gửi thư Mercurial với các yêu cầu tính năng của bạn.
Martin Geisler

8
Có vẻ như hg striplà những gì tôi muốn. Tại sao tài liệu trực tuyến yêu cầu chi nhánh không thể bị xóa?
Norman Ramsey

11
Xem thêm bài đăng trên blog này để được giải thích về cách Mercurial có, theo cách nào đó, chi nhánh rẻ hơn git: stevelosh.com/blog/entry/2009/8/30/ Lỗi
Martin Geisler

9
Bạn có thể đóng một chi nhánh được đặt tên với hg ci --close-branch.
Andrey Vlasovskikh

3
@Norman Ramsey: khi mọi người nói rằng các nhánh không thể bị xóa, điều đó có nghĩa là bạn không thể thay đổi tên nhánh được nhúng trong bộ thay đổi. Một thay đổi chúng tôi không có trên một chi nhánh, nó xác định một chi nhánh. Bạn sẽ cần xóa bộ thay đổi và tạo lại nó bằng một tên nhánh khác nếu bạn muốn "di chuyển" nó sang một nhánh khác.
Martin Geisler

14

Bạn nên làm cả hai .

Bắt đầu với câu trả lời được chấp nhận từ @Norman: Sử dụng một kho lưu trữ với một nhánh có tên trên mỗi bản phát hành.

Sau đó, có một bản sao trên mỗi nhánh phát hành để xây dựng và thử nghiệm.

Một lưu ý chính là ngay cả khi bạn sử dụng nhiều kho lưu trữ, bạn nên tránh sử dụng transplantđể di chuyển các thay đổi giữa chúng vì 1) nó thay đổi hàm băm và 2) nó có thể gây ra các lỗi rất khó phát hiện khi có những thay đổi xung đột giữa các thay đổi của bạn ghép và nhánh đích. Thay vào đó, bạn muốn thực hiện hợp nhất thông thường (và không có phần mở đầu: luôn luôn kiểm tra trực quan việc hợp nhất), điều này sẽ dẫn đến kết quả @mg nói ở cuối câu trả lời của anh ấy:

Biểu đồ có thể trông khác nhau, nhưng nó có cùng cấu trúc và kết quả cuối cùng là như nhau.

Cụ thể hơn, nếu bạn sử dụng nhiều kho lưu trữ, kho lưu trữ "thân cây" (hoặc mặc định, chính, phát triển, bất cứ thứ gì) chứa TẤT CẢ các thay đổi trong TẤT CẢ các kho lưu trữ. Mỗi kho lưu trữ phát hành / chi nhánh chỉ đơn giản là một nhánh trong trung kế, tất cả được hợp nhất trở lại theo cách này hoặc cách khác trở lại trung kế, cho đến khi bạn muốn để lại một bản phát hành cũ phía sau. Do đó, sự khác biệt thực sự duy nhất giữa repo chính đó và repo đơn trong sơ đồ nhánh được đặt tên chỉ đơn giản là liệu các nhánh có được đặt tên hay không.

Điều đó sẽ làm cho rõ ràng lý do tại sao tôi nói "bắt đầu với một repo". Repo duy nhất đó là nơi duy nhất bạn cần tìm kiếm bất kỳ thay đổi nào trong bất kỳ bản phát hành nào . Bạn có thể thêm thẻ thay đổi trên các nhánh phát hành để tạo phiên bản. Khái niệm này rõ ràng và đơn giản, và làm cho quản trị viên hệ thống đơn giản hơn, vì đó là điều duy nhất hoàn toàn phải có sẵn và có thể phục hồi mọi lúc.

Nhưng sau đó bạn vẫn cần duy trì một bản sao trên mỗi nhánh / bản phát hành mà bạn cần xây dựng và kiểm tra. Đó là chuyện nhỏ như bạn có thể hg clone <main repo>#<branch> <branch repo>, và sau đó hg pulltrong repo chi nhánh sẽ chỉ kéo các thay đổi mới trên nhánh đó (cộng với thay đổi tổ tiên trên các nhánh trước đó đã được hợp nhất).

Thiết lập này phù hợp nhất với mô hình cam kết hạt nhân linux của trình kéo đơn (không cảm thấy tốt khi hoạt động như Lord Linus. Tại công ty chúng tôi gọi là bộ tích hợp vai trò ), vì repo chính là thứ duy nhất mà các nhà phát triển cần sao chép và puller cần phải kéo vào. Bảo trì các repos chi nhánh hoàn toàn để quản lý phát hành và có thể hoàn toàn tự động. Các nhà phát triển không bao giờ cần phải kéo từ / đẩy đến các repos chi nhánh.


Dưới đây là ví dụ của @ mg được lấy lại cho thiết lập này. Điểm khởi đầu:

[a] - [b]

Tạo một nhánh được đặt tên cho phiên bản phát hành, nói "1.0", khi bạn nhận được bản phát hành alpha. Cam kết sửa lỗi trên nó:

[a] - [b] ------------------ [m1]
         \                 /
          (1.0) - [x] - [y]

(1.0)không phải là một thay đổi thực sự vì nhánh được đặt tên không tồn tại cho đến khi bạn cam kết. (Bạn có thể thực hiện một cam kết tầm thường, chẳng hạn như thêm thẻ, để đảm bảo các nhánh có tên được tạo đúng.)

Hợp nhất [m1]là chìa khóa để thiết lập này. Không giống như kho lưu trữ dành cho nhà phát triển nơi có số lượng người đứng đầu không giới hạn, bạn KHÔNG muốn có nhiều đầu trong kho chính của mình (ngoại trừ nhánh phát hành cũ, đã chết như đã đề cập trước đó). Vì vậy, bất cứ khi nào bạn có các thay đổi mới trên các nhánh phát hành, bạn phải hợp nhất chúng trở lại nhánh mặc định (hoặc nhánh phát hành sau) ngay lập tức. Điều này đảm bảo rằng mọi sửa lỗi trong một bản phát hành cũng được bao gồm trong tất cả các bản phát hành sau.

Trong khi đó, sự phát triển trên nhánh mặc định tiếp tục hướng tới phiên bản tiếp theo:

          ------- [c] - [d]
         /
[a] - [b] ------------------ [m1]
         \                 /
          (1.0) - [x] - [y]

Và như thường lệ, bạn cần hợp nhất hai đầu trên nhánh mặc định:

          ------- [c] - [d] -------
         /                         \
[a] - [b] ------------------ [m1] - [m2]
         \                 /
          (1.0) - [x] - [y]

Và đây là bản sao nhánh 1.0:

[a] - [b] - (1.0) - [x] - [y]

Bây giờ là một bài tập để thêm nhánh phát hành tiếp theo. Nếu là 2.0 thì nó chắc chắn sẽ phân nhánh mặc định. Nếu là 1.1, bạn có thể chọn rẽ nhánh 1.0 hoặc mặc định. Bất kể, bất kỳ thay đổi mới nào trên 1.0 trước tiên nên được hợp nhất với nhánh tiếp theo, sau đó để mặc định. Điều này có thể được thực hiện tự động nếu không có xung đột, dẫn đến chỉ là một sự hợp nhất trống rỗng.


Tôi hy vọng ví dụ làm cho các điểm trước đây của tôi rõ ràng. Tóm lại, ưu điểm của phương pháp này là:

  1. Kho lưu trữ có thẩm quyền duy nhất có chứa thay đổi hoàn toàn và lịch sử phiên bản.
  2. Quản lý phát hành rõ ràng và đơn giản hóa.
  3. Quy trình làm việc rõ ràng và đơn giản hóa cho các nhà phát triển và tích hợp.
  4. Tạo điều kiện lặp lại quy trình công việc (đánh giá mã) và tự động hóa (tự động kết hợp trống).

CẬP NHẬT hg chính nó làm điều này : repo chính chứa các nhánh mặc định và ổn định, và repo ổn định là bản sao nhánh ổn định. Tuy nhiên, nó không sử dụng nhánh được phiên bản, vì các thẻ phiên bản dọc theo nhánh ổn định đủ tốt cho mục đích quản lý phát hành của nó.


5

Sự khác biệt chính, theo như tôi biết, là một điều bạn đã nêu: tên được phân nhánh nằm trong một kho lưu trữ duy nhất. Các chi nhánh được đặt tên có mọi thứ tiện dụng ở một nơi. Repos riêng biệt nhỏ hơn và dễ dàng để di chuyển xung quanh. Lý do có hai trường phái suy nghĩ về điều này là vì không có người chiến thắng rõ ràng. Bất kỳ đối số nào có ý nghĩa nhất đối với bạn có lẽ là điều bạn nên đi cùng, bởi vì có khả năng môi trường của họ giống với môi trường của bạn nhất.


2

Tôi nghĩ đó rõ ràng là một quyết định thực dụng tùy thuộc vào tình hình hiện tại, ví dụ như kích thước của một tính năng / thiết kế lại. Tôi nghĩ rằng dĩa thực sự tốt cho những người đóng góp với vai trò chưa bắt đầu tham gia nhóm nhà phát triển bằng cách chứng minh năng khiếu của họ với chi phí kỹ thuật không thể bỏ qua.


0

Tôi thực sự khuyên bạn không nên sử dụng các nhánh được đặt tên cho các phiên bản. Đó thực sự là những gì các thẻ dành cho. Các chi nhánh được đặt tên có nghĩa là để chuyển hướng lâu dài, giống như một stablechi nhánh.

Vậy tại sao không chỉ sử dụng thẻ? Một ví dụ cơ bản:

  • Sự phát triển xảy ra trên một nhánh
  • Bất cứ khi nào một bản phát hành được tạo ra, bạn gắn thẻ nó cho phù hợp
  • Sự phát triển chỉ tiếp tục từ đó
  • Nếu bạn có một số lỗi cần sửa (hoặc bất cứ điều gì) trong một bản phát hành nhất định, bạn chỉ cần cập nhật vào thẻ của nó, thực hiện các thay đổi và cam kết

Điều đó sẽ tạo ra một cái đầu mới, không tên trên defaultnhánh, aka. một nhánh ẩn danh, đó là hoàn toàn tốt trong hg. Sau đó, tại bất kỳ thời điểm nào, bạn có thể hợp nhất lỗi sửa lỗi vào bản phát triển chính. Không cần cho các chi nhánh được đặt tên.


Điều này phụ thuộc rất nhiều vào quá trình của bạn. Ví dụ, một ứng dụng web hoạt động tốt với hệ thống phân cấp nhánh ổn định / thử nghiệm / phát. Khi xây dựng phần mềm máy tính để bàn, chúng tôi thường có một nhánh phát triển (mặc định) cũng như một đến ba (!) Chi nhánh khác nhau trong bảo trì. Thật khó để dự đoán khi nào chúng ta có thể cần phải xem lại một chi nhánh và có một sự tao nhã nhất định về việc có một chi nhánh theo dõi một phiên bản Major.minor.
James Emerton
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.