Tại sao git cam kết không chứa tên của nhánh mà chúng được tạo?


20

Khi làm việc với git trong một nhóm sử dụng các nhánh tính năng, tôi thường cảm thấy khó hiểu về cấu trúc nhánh trong lịch sử.

Thí dụ:

Giả sử có một tính năng nhánh / pha cà phê và sửa lỗi tiếp tục trên chủ song song với nhánh tính năng.

Lịch sử có thể trông như thế này:

*     merge feature/make-coffee
|\
| *   small bugfix
| |
* |    fix bug #1234
| |
| *    add milk and sugar
| |
* |    improve comments
| |
* |    fix bug #9434
| |
| *    make coffe (without milk or sugar)
| |
* |    improve comments
|/
*

Vấn đề

Thoạt nhìn, tôi thấy khó có thể nói được bên nào là nhánh tính năng. Tôi thường cần duyệt một số bình luận ở cả hai phía để có ý tưởng về cái nào. Điều này trở nên phức tạp hơn nếu có nhiều nhánh tính năng song song (đặc biệt nếu chúng dành cho các tính năng liên quan chặt chẽ) hoặc nếu có sự hợp nhất theo cả hai hướng giữa nhánh tính năng và chủ.

Ngược lại, trong Subversion, điều này dễ dàng hơn đáng kể vì tên chi nhánh là một phần của lịch sử - vì vậy tôi có thể nói ngay rằng một cam kết ban đầu được thực hiện trên "tính năng / pha cà phê".

Git có thể làm điều này dễ dàng hơn bằng cách bao gồm tên của nhánh hiện tại trong siêu dữ liệu cam kết khi tạo một cam kết (cùng với tác giả, ngày, v.v.). Tuy nhiên, git không làm điều này.

Có một số lý do cơ bản tại sao điều này không được thực hiện? Hay chỉ là không ai muốn tính năng này? Nếu đó là cái sau, có cách nào khác để hiểu mục đích của các nhánh lịch sử mà không nhìn thấy tên không?


1
Câu hỏi liên quan: Tìm kiếm một nhánh mà một cam kết đến từ . Đây là về cách tìm thông tin này mặc dù thực tế là git không ghi lại rõ ràng.
sleske

1
Lưu ý đến bản thân: Thật thú vị, trong Mercurial, cam kết không chứa tên chi nhánh. Vì vậy, có vẻ như các nhà phát triển Mercurial đã đưa ra một quyết định thiết kế khác với các nhà phát triển git đã làm. Xem ví dụ felipec.wordpress.com/2012/05/26/ để biết chi tiết.
sleske

Câu trả lời:


14

Có lẽ bởi vì tên chi nhánh chỉ có ý nghĩa trong một kho lưu trữ. Nếu tôi ở trong make-coffeeđội và gửi tất cả các thay đổi của mình thông qua một người gác cổng, masterchi nhánh của tôi có thể bị kéo đến make-coffee-guichi nhánh của người gác cổng , anh ta sẽ hợp nhất với make-coffee-backendchi nhánh của mình , trước khi chuyển sang một make-coffeechi nhánh được sáp nhập vào masterchi nhánh trung tâm .

Ngay cả trong một kho lưu trữ, tên chi nhánh có thể và thay đổi khi quy trình công việc của bạn phát triển. mastercó thể thay đổi sau này để được gọi development, ví dụ.

Như CodeGnome đã ám chỉ, git có triết lý thiết kế mạnh mẽ là không nướng thứ gì đó vào dữ liệu nếu không cần thiết. Người dùng dự kiến ​​sẽ sử dụng các tùy chọn trong git loggit diffđể xuất dữ liệu theo ý thích của họ sau khi thực tế. Tôi khuyên bạn nên thử một số cho đến khi bạn tìm thấy một định dạng phù hợp hơn với bạn. git log --first-parent, ví dụ, sẽ không hiển thị các cam kết từ một chi nhánh được sáp nhập.


2
Cần lưu ý rằng cha mẹ đầu tiên thường là sai. Bởi vì thông thường người ta chỉ cần kéo chủ vào bất cứ điều gì họ đang làm và đẩy ra làm cho công việc của họ trở thành cha mẹ đầu tiên và làm chủ thứ hai.
Jan Hudec

1
@JanHudec: Thật ra, điều này phụ thuộc vào :-). Nếu người thực hiện công việc hợp nhất nó, thì có. Nếu việc hợp nhất xảy ra sau đó, có thể sau khi đánh giá, thì nhiều khả năng người đánh giá đã kiểm tra chính và hợp nhất nhánh tính năng thành chủ, kiểm tra và đẩy nó.
sleske

5

Theo dõi lịch sử chi nhánh với --no-ff

Trong Git, một cam kết có tổ tiên, nhưng một "nhánh" thực sự chỉ là người đứng đầu hiện tại của một số dòng phát triển. Nói cách khác, một cam kết là một ảnh chụp nhanh của cây làm việc tại một thời điểm và có thể thuộc về bất kỳ số lượng nhánh nào cùng một lúc. Đây là một phần của những gì làm cho phân nhánh Git rất nhẹ khi so sánh với các DVCS khác.

Git cam kết không mang thông tin chi nhánh vì chúng không cần thiết cho lịch sử Git. Tuy nhiên, các hợp nhất không nhanh chóng chuyển tiếp chắc chắn có thể mang thêm thông tin về chi nhánh được sáp nhập. Bạn có thể đảm bảo rằng lịch sử của bạn chứa thông tin này bằng cách luôn luôn chuyển --no-ffcờ cho các kết hợp của bạn. git-merge (1) nói:

   --no-ff
       Create a merge commit even when the merge resolves as a
       fast-forward. This is the default behaviour when merging an
       annotated (and possibly signed) tag.

Điều này thường sẽ tạo ra một thông điệp hợp nhất tương tự Merge branch 'foo', vì vậy bạn thường có thể tìm thấy thông tin về "các nhánh tổ tiên" với một dòng tương tự như sau:

$ git log --regexp-ignore-case --grep 'merge branch'

Cảm ơn, nhưng điều đó (chủ yếu) không trả lời câu hỏi của tôi. Tôi không hỏi những cách khác để tìm chi nhánh ban đầu, nhưng tại sao thông tin không được bao gồm như siêu dữ liệu thông thường - đó là câu hỏi thiết kế nhiều hơn.
sleske

1
@sleske Tôi nghĩ rằng câu trả lời của tôi đã phản hồi: Git không theo dõi nó vì lịch sử Git không cần nó; Tôi không nghĩ rằng nó còn cơ bản hơn thế nữa. Bạn sẽ phải xem nguồn để biết chi tiết cụ thể, nhưng lịch sử được tính toán một cách hiệu quả ngược từ đầu của nhánh hiện tại. Bạn luôn có thể so sánh điều đó với một số DVCS khác coi các nhánh là đối tượng hạng nhất và xem bạn có thích triết lý thiết kế đó hơn không. YMMV.
CodeGnome

1
Đó là cuối cùng nên được git log --merges.
Dan

3

Câu trả lời đơn giản nhất là tên của các nhánh là phù du. Điều gì sẽ xảy ra nếu bạn định đổi tên một nhánh ( git branch -m <oldname> <newname>)? Điều gì sẽ xảy ra với tất cả các cam kết chống lại chi nhánh đó? Hoặc nếu hai người có các nhánh có cùng tên trên các kho lưu trữ cục bộ khác nhau thì sao?

Điều duy nhất có ý nghĩa trong git là tổng kiểm tra của chính cam kết. Đây là đơn vị cơ bản của theo dõi.

Thông tin là ở đó, bạn chỉ không yêu cầu nó, và nó không chính xác ở đó.

Git lưu trữ tổng kiểm tra của người đứng đầu của mỗi chi nhánh trong .git/refs/heads. Ví dụ:

... /. git / refs / Heads $ ls test 
-rw-rw-r-- 1 michealt michealt 41 tháng 9 30 11:50 kiểm tra
... /. git / refs / Heads $ cat test 
87111111111111111111111111111111111111111111d4d4

(vâng, tôi đang ghi đè lên tổng kiểm tra git của tôi, nó không đặc biệt, nhưng chúng là kho lưu trữ riêng tư mà tôi đang nhìn vào thời điểm đó không cần phải có bất cứ điều gì vô tình bị rò rỉ).

Bằng cách xem xét điều này và theo dõi lại mọi cam kết có cha mẹ hoặc cha mẹ hoặc cha mẹ cha mẹ ... bạn có thể tìm ra các nhánh mà một cam kết đã cho là gì. Đây chỉ là một biểu đồ lớn để hiển thị.

Lưu trữ cụ thể tên của một chi nhánh (có thể thay đổi như đã đề cập ở trên) một cam kết nằm trong chính cam kết đó là chi phí phụ trên cam kết không giúp ích gì cho nó. Lưu trữ (các) cha mẹ của cam kết và lợi ích của nó.

Bạn có thể thấy các nhánh bằng cách chạy lệnh git log với cờ thích hợp.

git log --branches - nguồn --pretty = oneline --graph
git log --all --source --pretty = oneline --graph

Có nhiều cách khác nhau để chọn những gì bạn muốn cho điều đó - xem tài liệu nhật ký git - -allphần và một vài tùy chọn tiếp theo.

* 8711111111111111111111111111111111111111d4 kiểm tra Kết hợp nhánh 'test1' vào kiểm tra
|  
| * 42111111111111111111111111111111111111118 cập nhật test1: thêm công cụ
* | dd1111111111111111111111111111111111111159 kiểm tra Hợp nhất nhánh 'test3' vào kiểm tra
| \ \  

Đó là 'test', 'test1' và 'test2' là những nhánh mà chúng được cam kết.

Lưu ý rằng tài liệu nhật ký git là rất lớn và hoàn toàn có thể lấy hầu hết mọi thứ bạn muốn từ nó.


3

Tại sao git cam kết không chứa tên của chi nhánh mà chúng được tạo ra?

Chỉ là một quyết định thiết kế. Nếu bạn cần thông tin đó, bạn có thể thêm móc chuẩn bị-cam kết hoặc thông báo cam kết để thực thi thông tin đó trên một kho lưu trữ duy nhất.

Sau thảm họa BitKeeper, Linus Torvalds đã phát triển git để phù hợp với quy trình phát triển nhân Linux. Ở đó, cho đến khi một bản vá được chấp nhận, nó được sửa đổi, chỉnh sửa, đánh bóng nhiều lần (tất cả thông qua Thư), ký tắt (= chỉnh sửa một cam kết), chọn anh đào, lang thang đến các chi nhánh của trung úy có thể thường bị phản đối, nhiều chỉnh sửa, anh đào -nhấp và cứ thế cho đến khi họ cuối cùng được Linus sáp nhập ngược dòng.

Trong quy trình công việc như vậy, có thể không có nguồn gốc cụ thể của một cam kết và thường thì một cam kết chỉ được tạo trong một nhánh gỡ lỗi tạm thời trong một kho lưu trữ ngẫu nhiên riêng tư không quan trọng với các tên nhánh vui nhộn, vì git được phân phối.

Có thể quyết định thiết kế đó chỉ xảy ra ngẫu nhiên, nhưng nó hoàn toàn phù hợp với quy trình phát triển nhân Linux.


2

Bởi vì trong Git cam kết như "pha cà phê" được coi là một phần của cả hai nhánh khi nhánh tính năng được hợp nhất. Thậm chí còn có một lệnh để kiểm tra rằng:

% git branch --contains @
  feature/make-coffee
  master

Ngoài ra, các chi nhánh là giá rẻ, địa phương và thanh tao; chúng có thể dễ dàng được thêm, xóa, đổi tên, đẩy và xóa khỏi máy chủ.

Git tuân theo nguyên tắc rằng mọi thứ đều có thể được thực hiện, đó là lý do tại sao bạn có thể dễ dàng xóa một nhánh khỏi máy chủ từ xa và bạn có thể dễ dàng viết lại lịch sử.

Giả sử tôi đã ở trong nhánh 'chính chủ' và tôi đã thực hiện hai cam kết: 'pha cà phê' và 'thêm sữa và đường', sau đó tôi quyết định sử dụng nhánh mới cho điều đó, vì vậy tôi đặt lại 'gốc' trở lại 'nguồn gốc /bậc thầy'. Không phải là vấn đề, tất cả lịch sử vẫn sạch sẽ: 'pha cà phê' và 'thêm sữa và đường' không phải là một phần của chủ, chỉ có tính năng / pha cà phê.

Mercurial thì ngược lại; nó không muốn thay đổi mọi thứ Các chi nhánh là vĩnh viễn, lịch sử viết lại được tán thành, bạn không thể xóa một chi nhánh khỏi máy chủ.

Tóm lại: Git cho phép bạn làm mọi thứ, Mercurial muốn làm cho mọi thứ trở nên dễ dàng (và làm cho nó thực sự khó khăn để cho phép bạn làm những gì bạn muốn).

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.