Đôi khi điều đó thực sự không thể (với một số ngoại lệ về nơi bạn có thể may mắn có thêm dữ liệu) và các giải pháp ở đây sẽ không hoạt động.
Git không lưu giữ lịch sử ref (bao gồm các chi nhánh). Nó chỉ lưu trữ vị trí hiện tại cho mỗi chi nhánh (người đứng đầu). Điều này có nghĩa là bạn có thể mất một số lịch sử chi nhánh trong git theo thời gian. Bất cứ khi nào bạn phân nhánh chẳng hạn, nó ngay lập tức bị mất nhánh nào là nhánh ban đầu. Tất cả một chi nhánh làm là:
git checkout branch1 # refs/branch1 -> commit1
git checkout -b branch2 # branch2 -> commit1
Bạn có thể cho rằng người đầu tiên được cam kết là chi nhánh. Điều này có xu hướng là trường hợp nhưng không phải lúc nào cũng như vậy. Không có gì ngăn bạn cam kết với một trong hai chi nhánh đầu tiên sau hoạt động trên. Ngoài ra, dấu thời gian git không được đảm bảo là đáng tin cậy. Mãi cho đến khi bạn cam kết với cả hai rằng chúng thực sự trở thành các nhánh có cấu trúc.
Mặc dù trong các sơ đồ, chúng ta có xu hướng đánh số cam kết về mặt khái niệm, git không có khái niệm ổn định thực sự về chuỗi khi các nhánh cây cam kết. Trong trường hợp này, bạn có thể giả sử các số (chỉ thị thứ tự) được xác định bởi dấu thời gian (có thể rất vui khi thấy giao diện người dùng git xử lý mọi thứ khi bạn đặt tất cả các dấu thời gian giống nhau).
Đây là những gì một con người mong đợi về mặt khái niệm:
After branch:
C1 (B1)
/
-
\
C1 (B2)
After first commit:
C1 (B1)
/
-
\
C1 - C2 (B2)
Đây là những gì bạn thực sự nhận được:
After branch:
- C1 (B1) (B2)
After first commit (human):
- C1 (B1)
\
C2 (B2)
After first commit (real):
- C1 (B1) - C2 (B2)
Bạn sẽ cho rằng B1 là nhánh ban đầu nhưng nó có thể chỉ đơn giản là một nhánh chết (ai đó đã thanh toán -b nhưng không bao giờ cam kết với nó). Mãi cho đến khi bạn cam kết với cả hai bạn sẽ có được cấu trúc nhánh hợp pháp trong git:
Either:
/ - C2 (B1)
-- C1
\ - C3 (B2)
Or:
/ - C3 (B1)
-- C1
\ - C2 (B2)
Bạn luôn biết rằng C1 đến trước C2 và C3 nhưng bạn không bao giờ biết chắc chắn rằng C2 đến trước C3 hay C3 đến trước C2 (vì bạn có thể đặt thời gian trên máy trạm của mình thành bất kỳ thứ gì chẳng hạn). B1 và B2 cũng gây hiểu nhầm vì bạn không thể biết chi nhánh nào đến trước. Bạn có thể đoán rất chính xác và thường chính xác về nó trong nhiều trường hợp. Nó hơi giống một đường đua. Tất cả mọi thứ thường tương đương với những chiếc xe sau đó bạn có thể giả định rằng một chiếc xe đi vào lòng phía sau bắt đầu một vòng phía sau. Chúng tôi cũng có những quy ước rất đáng tin cậy, ví dụ, chủ nhân sẽ hầu như luôn đại diện cho những nhánh tồn tại lâu nhất mặc dù thật đáng buồn là tôi đã thấy những trường hợp mà ngay cả điều này không phải là trường hợp.
Ví dụ được đưa ra ở đây là một ví dụ bảo tồn lịch sử:
Human:
- X - A - B - C - D - F (B1)
\ / \ /
G - H ----- I - J (B2)
Real:
B ----- C - D - F (B1)
/ / \ /
- X - A / \ /
\ / \ /
G - H ----- I - J (B2)
Thực tế ở đây cũng là sai lầm bởi vì chúng ta như con người đọc nó từ trái sang phải, từ gốc đến lá (ref). Git không làm điều đó. Nơi chúng ta làm (A-> B) trong đầu chúng ta git (A <-B hoặc B-> A). Nó đọc nó từ ref đến root. Refs có thể ở bất cứ đâu nhưng có xu hướng là lá, ít nhất là cho các nhánh hoạt động. Một tham chiếu trỏ đến một cam kết và cam kết chỉ chứa một lượt thích cho cha mẹ của họ, không phải cho con cái của họ. Khi một cam kết là một cam kết hợp nhất, nó sẽ có nhiều hơn một cha mẹ. Cha mẹ đầu tiên luôn là cam kết ban đầu được sáp nhập vào. Các cha mẹ khác luôn luôn là các cam kết đã được hợp nhất vào các cam kết ban đầu.
Paths:
F->(D->(C->(B->(A->X)),(H->(G->(A->X))))),(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
J->(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
Đây không phải là một đại diện rất hiệu quả, thay vào đó là một biểu thức của tất cả các đường dẫn git có thể đi từ mỗi ref (B1 và B2).
Bộ nhớ trong của Git trông giống như thế này (không phải là A với tư cách là cha mẹ xuất hiện hai lần):
F->D,I | D->C | C->B,H | B->A | A->X | J->I | I->H,C | H->G | G->A
Nếu bạn kết xuất một cam kết git thô, bạn sẽ thấy không hoặc nhiều trường cha. Nếu có 0, điều đó có nghĩa là không có cha mẹ và cam kết là một gốc (bạn thực sự có thể có nhiều gốc). Nếu có, điều đó có nghĩa là không có hợp nhất và đó không phải là một cam kết gốc. Nếu có nhiều hơn một, điều đó có nghĩa là cam kết là kết quả của hợp nhất và tất cả các cha mẹ sau lần đầu tiên là các cam kết hợp nhất.
Paths simplified:
F->(D->C),I | J->I | I->H,C | C->(B->A),H | H->(G->A) | A->X
Paths first parents only:
F->(D->(C->(B->(A->X)))) | F->D->C->B->A->X
J->(I->(H->(G->(A->X))) | J->I->H->G->A->X
Or:
F->D->C | J->I | I->H | C->B->A | H->G->A | A->X
Paths first parents only simplified:
F->D->C->B->A | J->I->->G->A | A->X
Topological:
- X - A - B - C - D - F (B1)
\
G - H - I - J (B2)
Khi cả hai đánh A, chuỗi của họ sẽ giống nhau, trước đó chuỗi của họ sẽ hoàn toàn khác nhau. Cam kết đầu tiên hai cam kết khác có điểm chung là tổ tiên chung và từ đó họ chuyển hướng. có thể có một số nhầm lẫn ở đây giữa các điều khoản cam kết, chi nhánh và ref. Trong thực tế bạn có thể hợp nhất một cam kết. Đây là những gì hợp nhất thực sự làm. Một ref chỉ đơn giản là trỏ đến một cam kết và một nhánh không có gì khác hơn là một ref trong thư mục .git / refs / Heads, vị trí thư mục là thứ xác định rằng ref là một nhánh chứ không phải là một thứ khác như thẻ.
Trường hợp bạn mất lịch sử là hợp nhất sẽ làm một trong hai điều tùy thuộc vào hoàn cảnh.
Xem xét:
/ - B (B1)
- A
\ - C (B2)
Trong trường hợp này, một sự hợp nhất theo một trong hai hướng sẽ tạo ra một cam kết mới với cha mẹ đầu tiên như là một cam kết được chỉ ra bởi nhánh đã kiểm tra hiện tại và cha mẹ thứ hai là cam kết ở đầu nhánh bạn đã sáp nhập vào nhánh hiện tại của bạn. Nó phải tạo ra một cam kết mới vì cả hai nhánh đều có những thay đổi do tổ tiên chung của chúng phải được kết hợp.
/ - B - D (B1)
- A /
\ --- C (B2)
Tại thời điểm này, D (B1) hiện có cả hai bộ thay đổi từ cả hai nhánh (chính nó và B2). Tuy nhiên, nhánh thứ hai không có thay đổi từ B1. Nếu bạn hợp nhất các thay đổi từ B1 thành B2 để chúng được đồng bộ hóa thì bạn có thể mong đợi một cái gì đó trông như thế này (bạn có thể buộc git merge thực hiện như thế này với --no-ff):
Expected:
/ - B - D (B1)
- A / \
\ --- C - E (B2)
Reality:
/ - B - D (B1) (B2)
- A /
\ --- C
Bạn sẽ nhận được điều đó ngay cả khi B1 có các cam kết bổ sung. Miễn là không có thay đổi trong B2 mà B1 không có, hai nhánh sẽ được hợp nhất. Nó thực hiện một chuyển tiếp nhanh giống như một cuộc nổi loạn (cuộc nổi loạn cũng ăn hoặc lịch sử tuyến tính hóa), ngoại trừ không giống như một cuộc nổi loạn vì chỉ có một nhánh có một thay đổi, nó không phải áp dụng một thay đổi từ một nhánh trên đỉnh này từ nhánh khác.
From:
/ - B - D - E (B1)
- A /
\ --- C (B2)
To:
/ - B - D - E (B1) (B2)
- A /
\ --- C
Nếu bạn ngừng làm việc trên B1 thì mọi thứ phần lớn đều ổn để bảo tồn lịch sử trong thời gian dài. Chỉ B1 (có thể là chủ) sẽ tiến lên một cách điển hình để vị trí của B2 trong lịch sử của B2 thể hiện thành công điểm mà nó được sáp nhập vào B1. Đây là những gì git mong đợi bạn làm, để phân nhánh B từ A, sau đó bạn có thể hợp nhất A vào B bao nhiêu tùy thích khi các thay đổi được tích lũy, tuy nhiên khi hợp nhất B trở lại A, bạn sẽ không làm việc trên B và hơn thế nữa . Nếu bạn tiếp tục làm việc trên chi nhánh của mình sau khi chuyển tiếp nhanh chóng hợp nhất nó trở lại vào chi nhánh mà bạn đang làm việc thì lịch sử xóa B của bạn mỗi lần. Bạn thực sự đang tạo một chi nhánh mới mỗi lần sau khi chuyển tiếp nhanh cam kết với nguồn rồi cam kết với chi nhánh.
0 1 2 3 4 (B1)
/-\ /-\ /-\ /-\ /
---- - - - -
\-/ \-/ \-/ \-/ \
5 6 7 8 9 (B2)
1 đến 3 và 5 đến 8 là các nhánh cấu trúc xuất hiện nếu bạn theo dõi lịch sử cho 4 hoặc 9. Không có cách nào để biết nhánh nào trong số các nhánh cấu trúc không tên và không được phân loại này thuộc về các nhánh được đặt tên và tham chiếu như kết thúc cấu trúc. Bạn có thể cho rằng từ bản vẽ này rằng 0 đến 4 thuộc về B1 và 4 đến 9 thuộc về B2 nhưng ngoài 4 và 9 thì không thể biết nhánh nào thuộc về nhánh nào, tôi chỉ đơn giản vẽ nó theo cách mang lại cho ảo tưởng về điều đó. 0 có thể thuộc về B2 và 5 có thể thuộc về B1. Có 16 khả năng khác nhau trong trường hợp này được đặt tên là nhánh mà mỗi nhánh cấu trúc có thể thuộc về.
Có một số chiến lược git hoạt động xung quanh này. Bạn có thể buộc git merge để không bao giờ chuyển tiếp nhanh và luôn tạo một nhánh hợp nhất. Một cách khủng khiếp để bảo tồn lịch sử chi nhánh là với các thẻ và / hoặc các nhánh (các thẻ thực sự được khuyến nghị) theo một số quy ước bạn chọn. Tôi thực sự sẽ không đề xuất một cam kết trống giả trong chi nhánh mà bạn đang hợp nhất. Một quy ước rất phổ biến là không hợp nhất thành một nhánh tích hợp cho đến khi bạn thực sự muốn đóng chi nhánh của mình. Đây là một thực tế mà mọi người nên cố gắng tuân thủ nếu không bạn đang làm việc xung quanh điểm có chi nhánh. Tuy nhiên, trong thế giới thực, lý tưởng không phải lúc nào cũng có ý nghĩa thực tế, làm điều đúng đắn là không khả thi cho mọi tình huống. Nếu những gì bạn '