Kiểm tra chi nhánh khác khi có những thay đổi không được cam kết trên chi nhánh hiện tại


349

Hầu hết thời gian khi tôi cố gắng kiểm tra một chi nhánh hiện có khác, Git không cho phép tôi nếu tôi có một số thay đổi không được cam kết trên chi nhánh hiện tại. Vì vậy, tôi sẽ phải cam kết hoặc bỏ qua những thay đổi đó trước tiên.

Tuy nhiên, đôi khi Git không cho phép tôi kiểm tra một chi nhánh khác mà không cam kết hoặc bỏ qua những thay đổi đó và nó sẽ mang những thay đổi đó đến chi nhánh tôi kiểm tra.

Quy tắc ở đây là gì? Liệu vấn đề thay đổi được dàn dựng hay không được dàn dựng? Mang các thay đổi đến một chi nhánh khác không có ý nghĩa gì với tôi, tại sao đôi khi git cho phép nó? Đó là, nó có hữu ích trong một số tình huống?

Câu trả lời:


350

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 branch2trướ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 savehoặc git stash pushthự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 applyhọ sau khi chuyển đổi.

Thanh bên: git stash savelà 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 stashhoặ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 branch2mặ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 rms và như vậy giả sử bạn đang ở trên branch1. A git checkout branch2sẽ phải làm điều này:

  • Đối với mỗi tập tin đó trong branch1không trong branch2, 1 loại bỏ tập tin đó.
  • Đối với mọi tệp nằm trong branch2khô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 branch2khá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 branch2là "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 branch1git rev-parse branch1:Pbranch1Pgit rev-parsebranch-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 branch1branch2:

$ 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 inbothkhớ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 --shorthiể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ó, 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 Ms ở đây có nghĩa là: tệp được phân loại khác với HEADtệp 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 checkoutsẽ không cho phép thanh toán:

$ git checkout branch2
error: Your local changes ...

Hãy đặt branch2phiê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 checkoutbản sao sẽ bị mất bản sao đó và bản git checkoutbị 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 addmộ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 addnó 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 branch2bấ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 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 XYcù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 XY đều có tệp path/to/name.txtvà 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.txtthể 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 checkouttài liệu như bạn mong đợi, nhưng thay vì các git read-treetài liệu hướng dẫn, dưới phần có tiêu đề "Hai Tree Merge" .)


3
... cũng có 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.
jthill

1
Cảm ơn lời giải thích tuyệt vời này! Nhưng tôi có thể tìm thông tin ở đâu trong các tài liệu chính thức? Hay chúng không đầy đủ? Nếu vậy, những gì tài liệu tham khảo có thẩm quyền cho git (hy vọng khác với mã nguồn của nó)?
tối đa

1
(1) bạn không thể và (2) mã nguồn. Vấn đề chính là Git không ngừng phát triển. Ví dụ, ngay bây giờ, có một sự thúc đẩy lớn để tăng cường hoặc bỏ rơi SHA-1 với hoặc ủng hộ SHA-256. Mặc dù vậy, phần đặc biệt này của Git đã khá ổn định trong một thời gian dài và cơ chế cơ bản rất đơn giản: Git so sánh chỉ mục hiện tại với cam kết hiện tại và mục tiêu và quyết định thay đổi tệp nào (nếu có) dựa trên cam kết đích , sau đó kiểm tra "độ sạch" của các tệp cây công việc nếu mục nhập chỉ mục cần thay thế.

6
Câu trả lời ngắn gọn: Có một quy tắc, nhưng người dùng bình thường quá khó có thể có hy vọng hiểu được, hãy nhớ một mình, vì vậy thay vì dựa vào công cụ để hành xử một cách thông minh, bạn nên dựa vào quy ước có kỷ luật chỉ kiểm tra khi bạn chi nhánh hiện tại được cam kết và sạch sẽ. Tôi không thấy cách này trả lời câu hỏi khi nào sẽ hữu ích khi mang những thay đổi nổi bật sang một chi nhánh khác, nhưng tôi có thể đã bỏ lỡ nó vì tôi đấu tranh để hiểu nó.
Neutrino

2
@HawkeyeParker: câu trả lời này đã trải qua nhiều lần chỉnh sửa và tôi không chắc ai trong số họ đã cải thiện nó nhiều, nhưng tôi sẽ thử thêm một cái gì đó về ý nghĩa của một tập tin là "trong một nhánh". Cuối cùng, điều này sẽ trở nên chao đảo vì khái niệm "nhánh" ở đây không được xác định đúng ngay từ đầu, nhưng đó lại là một mục khác.

50

Bạn có hai lựa chọn: bỏ qua các thay đổi của bạn:

git stash

sau đó để lấy lại chúng:

git stash apply

hoặc đặt các thay đổi của bạn trên một nhánh để bạn có thể lấy nhánh từ xa và sau đó hợp nhất các thay đổi của bạn lên nó. Đó là một trong những điều tuyệt vời nhất về git: bạn có thể tạo một nhánh, cam kết với nó, sau đó lấy các thay đổi khác trên nhánh bạn đang ở.

Bạn nói rằng nó không có ý nghĩa gì, nhưng bạn chỉ làm điều đó để bạn có thể hợp nhất chúng theo ý muốn sau khi thực hiện thao tác kéo. Rõ ràng sự lựa chọn khác của bạn là cam kết trên bản sao của chi nhánh và sau đó thực hiện việc kéo. Giả định là bạn không muốn làm điều đó (trong trường hợp tôi bối rối rằng bạn không muốn có chi nhánh) hoặc bạn sợ xung đột.


1
Không phải là lệnh chính xác git stash apply? đây các tài liệu.
Thomas8

1
Chỉ là những gì tôi đang tìm kiếm, để tạm thời chuyển sang các chi nhánh khác nhau, để tìm kiếm một cái gì đó và trở lại trạng thái tương tự của chi nhánh mà tôi đang làm việc. Cảm ơn Rob!
Naishta

1
Vâng, đây là cách đúng đắn để làm điều này. Tôi đánh giá cao chi tiết trong câu trả lời được chấp nhận, nhưng điều đó làm cho mọi thứ trở nên khó khăn hơn mức cần thiết.
Michael Leonard

5
Ngoài ra, nếu bạn không có nhu cầu giữ stash xung quanh, bạn có thể sử dụng git stash popvà nó sẽ loại bỏ stash khỏi danh sách của bạn nếu áp dụng thành công.
Michael Leonard

1
sử dụng tốt hơn git stash pop, trừ khi bạn có ý định giữ nhật ký lưu trữ trong lịch sử repo của mình
Damilola Olowookere

14

Nếu nhánh mới chứa các chỉnh sửa khác với nhánh hiện tại cho tệp đã thay đổi cụ thể đó, thì nó sẽ không cho phép bạn chuyển nhánh cho đến khi thay đổi được cam kết hoặc được bỏ. Nếu tệp đã thay đổi giống nhau trên cả hai nhánh (nghĩa là phiên bản đã cam kết của tệp đó), thì bạn có thể chuyển đổi tự do.

Thí dụ:

$ echo 'hello world' > file.txt
$ git add file.txt
$ git commit -m "adding file.txt"

$ git checkout -b experiment
$ echo 'goodbye world' >> file.txt
$ git add file.txt
$ git commit -m "added text"
     # experiment now contains changes that master doesn't have
     # any future changes to this file will keep you from changing branches
     # until the changes are stashed or committed

$ echo "and we're back" >> file.txt  # making additional changes
$ git checkout master
error: Your local changes to the following files would be overwritten by checkout:
    file.txt
Please, commit your changes or stash them before you can switch branches.
Aborting

Điều này áp dụng cho các tệp không bị theo dõi cũng như các tệp được theo dõi. Đây là một ví dụ cho một tập tin chưa được theo dõi.

Thí dụ:

$ git checkout -b experimental  # creates new branch 'experimental'
$ echo 'hello world' > file.txt
$ git add file.txt
$ git commit -m "added file.txt"

$ git checkout master # master does not have file.txt
$ echo 'goodbye world' > file.txt
$ git checkout experimental
error: The following untracked working tree files would be overwritten by checkout:
    file.txt
Please move or remove them before you can switch branches.
Aborting

Một ví dụ điển hình về lý do tại sao bạn muốn di chuyển giữa các nhánh trong khi thực hiện thay đổi sẽ là nếu bạn đang thực hiện một số thử nghiệm trên chủ, muốn thực hiện chúng, nhưng chưa thành thạo ...

$ echo 'experimental change' >> file.txt # change to existing tracked file
   # I want to save these, but not on master

$ git checkout -b experiment
M       file.txt
Switched to branch 'experiment'
$ git add file.txt
$ git commit -m "possible modification for file.txt"

Thật ra tôi vẫn không hiểu lắm. Trong ví dụ đầu tiên của bạn, sau khi bạn thêm "và chúng tôi quay lại", nó nói thay đổi cục bộ sẽ bị ghi đè, thay đổi cục bộ chính xác là gì? "và chúng tôi đã trở lại"? Tại sao git không thực hiện thay đổi này để làm chủ để trong bản chính tệp có chứa "hello world" và "và chúng tôi đã trở lại"
Xufeng

Trong ví dụ đầu tiên, chủ nhân chỉ có 'xin chào thế giới'. thử nghiệm có 'xin chào thế giới \ ngoodbye world' đã cam kết. Để thay đổi chi nhánh diễn ra, file.txt cần được sửa đổi, vấn đề là, có những thay đổi không được cam kết "xin chào thế giới \ ngoodbye world \ nand chúng tôi đã trở lại".
Gordolio

1

Đáp án đúng là

git checkout -m origin/master

Nó hợp nhất các thay đổi từ nhánh chính gốc với các thay đổi cục bộ của bạn.


0

Trong trường hợp bạn không muốn thay đổi này được cam kết git reset --hard.

Tiếp theo, bạn có thể kiểm tra chi nhánh mong muốn, nhưng hãy nhớ rằng những thay đổi không được cam kết sẽ bị mất.

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.