Ghi chú sơ bộ
Quan sát ở đây là, sau khi bạn bắt đầu làm việc branch1
(quên hoặc không nhận ra rằng sẽ tốt khi chuyển sang một nhánh khác branch2
trước), bạn chạy:
git checkout branch2
Đôi khi Git nói "OK, bạn đang ở chi nhánh2 ngay bây giờ!" Đôi khi, Git nói "Tôi không thể làm điều đó, tôi sẽ mất một số thay đổi của bạn."
Nếu Git sẽ không cho phép bạn làm điều đó, bạn phải cam kết thay đổi của mình, để lưu chúng ở đâu đó vĩnh viễn. Bạn có thể muốn sử dụng git stash
để lưu chúng; đây là một trong những thứ nó được thiết kế cho Lưu ý rằng git stash save
hoặc git stash push
thực sự có nghĩa là "Cam kết tất cả các thay đổi, nhưng không có chi nhánh nào cả, sau đó xóa chúng khỏi vị trí hiện tại của tôi." Điều đó làm cho nó có thể chuyển đổi: bây giờ bạn không có thay đổi trong tiến trình. Bạn có thể sau đó git stash apply
họ sau khi chuyển đổi.
Thanh bên: git stash save
là cú pháp cũ; git stash push
đã được giới thiệu trong Git phiên bản 2.13, để khắc phục một số vấn đề với các đối số đểgit stash
và cho phép các tùy chọn mới. Cả hai đều làm điều tương tự, khi được sử dụng theo những cách cơ bản.
Bạn có thể dừng đọc ở đây, nếu bạn thích!
Nếu Git sẽ không cho phép bạn chuyển đổi, bạn đã có một biện pháp khắc phục: sử dụng git stash
hoặc git commit
; hoặc, nếu những thay đổi của bạn là không đáng kể để tạo lại, hãy sử dụng git checkout -f
để buộc nó. Câu trả lời này là tất cả về khi Git sẽ cho phép bạn git checkout branch2
mặc dù bạn đã bắt đầu thực hiện một số thay đổi. Tại sao đôi khi nó hoạt động , mà không phải là khác lần ?
Quy tắc ở đây đơn giản theo một cách và phức tạp / khó giải thích theo cách khác:
Bạn có thể chuyển đổi các nhánh với các thay đổi không được cam kết trong cây công việc nếu và chỉ khi chuyển đổi nói không yêu cầu ghi đè các thay đổi đó.
Đó là giáo dục và xin lưu ý rằng điều này vẫn được đơn giản hóa; có một số trường hợp góc cực khó với các giai đoạn git add
, git rm
s và như vậy giả sử bạn đang ở trên branch1
. A git checkout branch2
sẽ phải làm điều này:
- Đối với mỗi tập tin đó là trong
branch1
và không trong branch2
, 1 loại bỏ tập tin đó.
- Đối với mọi tệp nằm trong
branch2
và không nằm trong branch1
, hãy tạo tệp đó (có nội dung phù hợp).
- Đối với mỗi tệp trong cả hai nhánh, nếu phiên bản trong
branch2
khác nhau, hãy cập nhật phiên bản cây làm việc.
Mỗi bước trong số này có thể ghi lại một cái gì đó trong cây công việc của bạn:
- Xóa tệp là "an toàn" nếu phiên bản trong cây công việc giống với phiên bản đã cam kết trong
branch1
; đó là "không an toàn" nếu bạn đã thay đổi.
- Tạo một tệp theo cách nó xuất hiện
branch2
là "an toàn" nếu hiện tại nó không tồn tại. 2 Nó "không an toàn" nếu nó tồn tại ngay bây giờ nhưng có nội dung "sai".
- Và tất nhiên, việc thay thế phiên bản cây công việc của một tệp bằng một phiên bản khác là "an toàn" nếu phiên bản cây công việc đã được cam kết
branch1
.
Tạo một nhánh mới ( git checkout -b newbranch
) luôn được coi là "an toàn": sẽ không có tệp nào được thêm, xóa hoặc thay đổi trong cây công việc như một phần của quy trình này và khu vực chỉ mục / khu vực cũng không được chạm tới. (Hãy cẩn thận: an toàn khi tạo chi nhánh mới mà không thay đổi điểm bắt đầu của chi nhánh mới; nhưng nếu bạn thêm một đối số khác, ví dụ: git checkout -b newbranch different-start-point
điều này có thể phải thay đổi mọi thứ, để chuyển sang different-start-point
. Git sau đó sẽ áp dụng quy tắc an toàn thanh toán như bình thường .)
1 Điều này đòi hỏi chúng ta phải xác định ý nghĩa của một tập tin trong một nhánh, do đó đòi hỏi phải xác định nhánh từ đúng. (Xem thêm Ý nghĩa chính xác của "nhánh" là gì? ) Ở đây, điều tôi thực sự muốn nói là cam kết mà tên nhánh giải quyết: một tệp có đường dẫn nằm trong nếu tạo ra hàm băm. Tập tin đó không có trong nếu bạn nhận được thông báo lỗi thay thế. Sự tồn tại của đường dẫn trong chỉ mục hoặc cây công việc của bạn không liên quan khi trả lời câu hỏi cụ thể này. Vì vậy, bí mật ở đây là kiểm tra kết quả của từngP
branch1
git rev-parse branch1:P
branch1
P
git rev-parse
branch-name:path
. Điều này không thành công vì tệp "ở" nhiều nhất là một nhánh hoặc cung cấp cho chúng tôi hai ID băm. Nếu hai ID băm giống nhau , thì tệp giống nhau ở cả hai nhánh. Không cần thay đổi. Nếu ID băm khác nhau, tệp sẽ khác nhau ở hai nhánh và phải được thay đổi để chuyển nhánh.
Khái niệm quan trọng ở đây là các tệp trong cam kết bị đóng băng mãi mãi. Các tập tin bạn sẽ chỉnh sửa rõ ràng là không bị đóng băng. Chúng tôi, ít nhất là ban đầu, chỉ nhìn vào sự không phù hợp giữa hai cam kết đóng băng. Thật không may, chúng tôi cũng không phải đối phó với các tập tin không phải là cam kết mà bạn sẽ chuyển đi và nằm trong cam kết mà bạn sẽ chuyển sang. Điều này dẫn đến các biến chứng còn lại, vì các tệp cũng có thể tồn tại trong chỉ mục và / hoặc trong cây công việc, mà không phải tồn tại hai cam kết đóng băng cụ thể này mà chúng tôi đang làm việc.
2 Nó có thể được coi là "sắp xếp an toàn" nếu nó đã tồn tại với "nội dung đúng", do đó Git không phải tạo ra nó sau tất cả. Tôi nhớ lại ít nhất một số phiên bản Git cho phép điều này, nhưng thử nghiệm ngay bây giờ cho thấy nó được coi là "không an toàn" trong Git 1.8.5.4. Đối số tương tự sẽ áp dụng cho một tệp sửa đổi có thể được sửa đổi để phù hợp với nhánh chuyển đổi thành nhánh. Một lần nữa, 1.8.5.4 chỉ nói "sẽ bị ghi đè". Cũng xem phần cuối của các ghi chú kỹ thuật: bộ nhớ của tôi có thể bị lỗi vì tôi không nghĩ rằng các quy tắc về cây đọc đã thay đổi kể từ khi tôi bắt đầu sử dụng Git ở phiên bản 1.5.
Liệu vấn đề thay đổi được dàn dựng hay không được dàn dựng?
Vâng, theo một số cách. Cụ thể, bạn có thể thực hiện thay đổi, sau đó "sửa đổi" tệp cây công việc. Đây là một tệp trong hai nhánh, khác nhau trong branch1
và branch2
:
$ git show branch1:inboth
this file is in both branches
$ git show branch2:inboth
this file is in both branches
but it has more stuff in branch2 now
$ git checkout branch1
Switched to branch 'branch1'
$ echo 'but it has more stuff in branch2 now' >> inboth
Tại thời điểm này, tệp cây làm việc inboth
khớp với tệp trong branch2
, mặc dù chúng tôi đang bật branch1
. Thay đổi này không được tổ chức cho cam kết, đó là những gì git status --short
hiển thị ở đây:
$ git status --short
M inboth
Space-then-M có nghĩa là "sửa đổi nhưng không được dàn dựng" (hay chính xác hơn là bản sao của cây làm việc khác với bản sao theo giai đoạn / chỉ mục).
$ git checkout branch2
error: Your local changes ...
OK, bây giờ hãy tạo bản sao của cây làm việc, mà chúng ta đã biết cũng khớp với bản sao trong branch2
.
$ git add inboth
$ git status --short
M inboth
$ git checkout branch2
Switched to branch 'branch2'
Ở đây, các bản sao được dàn dựng và làm việc đều khớp với những gì đã có branch2
, vì vậy việc thanh toán được cho phép.
Hãy thử một bước nữa:
$ git checkout branch1
Switched to branch 'branch1'
$ cat inboth
this file is in both branches
Thay đổi tôi đã thực hiện bị mất khỏi khu vực tổ chức ngay bây giờ (vì thanh toán ghi qua khu vực tổ chức). Đây là một chút của một trường hợp góc. Sự thay đổi không phải là đi, nhưng thực tế là tôi đã dàn dựng nó, là biến mất.
Hãy tạo một biến thể thứ ba của tệp, khác với bản sao nhánh, sau đó đặt bản sao làm việc để khớp với phiên bản nhánh hiện tại:
$ echo 'staged version different from all' > inboth
$ git add inboth
$ git show branch1:inboth > inboth
$ git status --short
MM inboth
Hai M
s ở đây có nghĩa là: tệp được phân loại khác với HEAD
tệp và tệp cây làm việc khác với tệp được phân loại. Phiên bản cây làm việc không khớp với phiên bản branch1
(aka HEAD
):
$ git diff HEAD
$
Nhưng git checkout
sẽ không cho phép thanh toán:
$ git checkout branch2
error: Your local changes ...
Hãy đặt branch2
phiên bản làm phiên bản làm việc:
$ git show branch2:inboth > inboth
$ git status --short
MM inboth
$ git diff HEAD
diff --git a/inboth b/inboth
index ecb07f7..aee20fb 100644
--- a/inboth
+++ b/inboth
@@ -1 +1,2 @@
this file is in both branches
+but it has more stuff in branch2 now
$ git diff branch2 -- inboth
$ git checkout branch2
error: Your local changes ...
Mặc dù bản sao làm việc hiện tại khớp với bản trong branch2
, nhưng tệp được dàn dựng thì không, vì vậy git checkout
bản sao sẽ bị mất bản sao đó và bản git checkout
bị từ chối.
Ghi chú kỹ thuật chỉ dành cho những người tò mò điên cuồng :-)
Cơ chế thực hiện cơ bản cho tất cả điều này là chỉ mục của Git . Chỉ mục, còn được gọi là "khu vực tổ chức", là nơi bạn xây dựng cam kết tiếp theo : nó bắt đầu khớp với cam kết hiện tại, tức là, bất cứ điều gì bạn đã kiểm tra ngay bây giờ, và sau đó mỗi khi bạn có git add
một tệp, bạn thay thế phiên bản chỉ mục với bất cứ điều gì bạn có trong cây công việc của bạn.
Hãy nhớ rằng, cây công việc là nơi bạn làm việc trên các tệp của mình. Ở đây, chúng có dạng bình thường, thay vì một dạng đặc biệt chỉ hữu ích với Git như chúng làm trong các cam kết và trong chỉ mục. Vì vậy, bạn trích xuất một tệp từ một cam kết, thông qua chỉ mục, và sau đó vào cây công việc. Sau khi thay đổi nó, bạn git add
nó vào chỉ mục. Vì vậy, trên thực tế có ba vị trí cho mỗi tệp: cam kết hiện tại, chỉ mục và cây công việc.
Khi bạn chạy git checkout branch2
, những gì Git làm bên dưới nắp là so sánh mũi cam kết của branch2
bất cứ điều gì là trong cả hai dòng cam kết và chỉ số bây giờ. Bất kỳ tệp nào khớp với những gì hiện có, Git có thể để yên. Tất cả đều hoang sơ. Bất kỳ tập tin đó là như nhau trong cả hai cam kết , Git có thể cũng đi về một mình và đó là những cái mà cho phép bạn chuyển đổi các chi nhánh.
Phần lớn Git, bao gồm chuyển đổi cam kết, tương đối nhanh vì chỉ số này. Những gì thực sự trong chỉ mục không phải là mỗi tệp, mà là băm của mỗi tệp . Bản sao của tệp được lưu trữ như những gì Git gọi là đối tượng blob , trong kho lưu trữ. Điều này cũng tương tự như cách các tệp được lưu trữ trong các cam kết: cam kết không thực sự chứa các tệp , chúng chỉ dẫn Git đến ID băm của mỗi tệp. Vì vậy, Git có thể so sánh các ID băm hiện tại có chuỗi dài 160 bit để quyết định xem các xác nhận X và Y có cùng một tệp hay không. Sau đó, nó cũng có thể so sánh các ID băm đó với ID băm trong chỉ mục.
Đây là những gì dẫn đến tất cả các trường hợp góc lẻ ở trên. Chúng tôi cam kết X và Y đều có tệp path/to/name.txt
và chúng tôi có mục nhập chỉ mục path/to/name.txt
. Có lẽ cả ba băm khớp nhau. Có lẽ hai trong số họ phù hợp và một không. Có lẽ cả ba đều khác nhau. Và, chúng ta cũng có another/file.txt
thể chỉ có X hoặc chỉ trong Y và hiện không có trong chỉ mục. Mỗi trường hợp khác nhau này đòi hỏi phải xem xét riêng: Git có cần sao chép tệp từ cam kết sang chỉ mục hoặc xóa tệp khỏi chỉ mục, để chuyển từ X sang Y không? Nếu vậy, nó cũng phảisao chép tệp vào cây công việc hoặc xóa nó khỏi cây công việc. Và nếu đó là trường hợp, các phiên bản chỉ mục và cây công việc phù hợp hơn với ít nhất một trong các phiên bản đã cam kết; nếu không, Git sẽ ghi đè một số dữ liệu.
(Các quy tắc đầy đủ cho tất cả những điều này được mô tả trong, không phải là git checkout
tài liệu như bạn mong đợi, nhưng thay vì các git read-tree
tài liệu hướng dẫn, dưới phần có tiêu đề "Hai Tree Merge" .)
git checkout -m
, kết hợp các thay đổi chỉ số và chỉ số của bạn vào thanh toán mới.