2012-04-16 32 views
13

OK, tôi đã thực hiện một chút lộn xộn. Rõ ràng, trên máy của tôi ở nhà, chi nhánh phát triển không được cập nhật. Tôi đã thực hiện một cam kết và đẩy. Kết quả là nhánh gốc/phát triển thực tế đã được sáp nhập vào nhánh phát triển địa phương của tôi - vì một số lý do được coi là các nhánh khác nhau!Hoàn tác hợp nhất đã được đẩy

Vì một điều, tôi thực sự không hiểu điều này đã xảy ra như thế nào và thứ hai, tôi có thể hoàn tác tác vụ này không?

Để minh họa cho điều đó, mạng trông giống như thế này bây giờ:

local-develop ---------------------------------- C*--- M - 
origin/develop --- C --- C --- C --- C --- C ---------/

Những gì tôi thực sự muốn là C * sẽ được cam kết về nguồn gốc/phát triển thay vì sáp nhập các chi nhánh.

Như tôi đã nói, điều này đã được đẩy. Có cách nào để xóa các thay đổi và cam kết theo cách tôi muốn không?

Ví dụ tôi làm:

git reset --hard HEAD~1 

Tôi không chắc chắn nếu điều này Undoes việc hợp nhất và tôi có hai khác nhau phát triển, và sau đó merge xóa vv ...?

Trả lời

26

Hợp nhất không xảy ra khi đẩy, chúng xảy ra trên git merge (tốt và pull nhưng thực sự chỉ tìm nạp + hợp nhất, do đó, hợp nhất xảy ra khi hợp nhất).

Có vẻ như nhiều khả năng rằng bạn đã làm một cái gì đó như thế này:

<get copy from origin/develop> 
<wait around for remote's origin/develop to acquire new commits> 
git checkout develop  # get onto (local) develop branch 
<edit> 
git commit -m message  # create some new commit(s) 
git push origin develop # attempt to push -- but this fails! 
git pull 

Đó chính là bước cuối cùng tạo ra việc hợp nhất cam kết (M ở trên), vì pull nghĩa fetch (nhận được tất cả những cam kết mới mà bây giờ trong là origin/develop), sau đó merge (lấy develop địa phương của bạn và hợp nhất cam kết của bạn với những cái mới vừa tìm nạp).

Nếu bạn chưa git push ed này mới kết quả, sau đó repo từ xa không có hoặc của địa phương cam kết của mình, những người bạn đã dán nhãn C*M. Trong trường hợp này, bạn đang ở trong tình trạng tốt! (Bạn có thể kiểm tra bằng cách chạy git fetch một lần nữa để chắc chắn rằng repo địa phương của bạn origin/develop trận một trong điều khiển từ xa, sau đó làm git log origin/develop để xem những gì trong đó.)

Nó có thể giúp để nhớ rằng có hai Repos git hoàn toàn tách biệt ở đây : của bạn, với công cụ của bạn; và cái bạn đang gọi origin tại đây. (Hãy gọi máy đó là X.) Nếu bạn đăng nhập vào X, nó có lịch sử cam kết riêng biệt và tên chi nhánh, v.v. Ở đó, bạn sẽ thay đổi thư mục thành repo, chạy git log -1 develop và xem những gì ở đầu nhánh đó.

Bây giờ nếu bạn đăng xuất khỏi X và quay lại máy của riêng mình, bạn có thể chạy git log -1 origin/develop. Nếu điều đó giống như những gì bạn thấy trên X thì get fetch không có gì để cập nhật, bởi vì những gì git fetch thực hiện, có hiệu lực (nhưng hiệu quả hơn), hãy đăng nhập vào X và xem những gì có trong số develop ở đó. Bất cứ điều gì mà X có mà bạn không có trong origin/develop, fetch mang lại và thêm vào origin/develop.Bây giờ bạn đang đồng bộ hóa với X. X không có nội dung của bạn, nhưng bạn có chúng.

Nếu bạn sau đó đi thêm bước làm một merge (trong đó có một ngụ ý bởi pull), git sẽ, nếu nó đã đến, làm việc kết hợp với cam kết ... nhưng tất cả điều này là trong repo của bạn, tại đầu của chi nhánh (vẫn còn develop trong trường hợp này). Trừ khi và cho đến khi bạn đẩy hợp nhất này cam kết X (hoặc ai đó trên X kéo các cam kết của bạn từ bạn nhưng hãy bỏ qua điều đó bây giờ :-)), X sẽ không có.

Dù sao, miễn là điều khiển từ xa (X ở đây) không có cam kết hợp nhất của bạn, bạn là vàng. Kể từ khi họ không có nó, không ai khác làm như vậy. Bạn có thể làm một chi tiết số rebase của chi nhánh develop để đặt cam kết của mình (C*) ở trên cùng của origin/develop. Điều đó sẽ loại bỏ cam kết hợp nhất (M) và sau đó bạn có thể đẩy nhanh về phía trước đơn giản tới origin/develop.

Nếu Xkhông có merge bạn cam kết-nghĩa là, nếu bạn đẩy sau khi bạn pull ed và đã nhận rằng merge-sau đó bạn đang bị mắc kẹt (chừng mực nào đó) bởi vì mọi người có lẽ khác có quyền truy cập vào X và bây giờ đang sử dụng hợp nhất của bạn cam kết. Có thể quay lại repo trên X, tương tự như cách bạn có thể thực hiện việc này trong repo của riêng mình với git resetgit rebase, v.v. nhưng thường là một ý tưởng tồi.


Bây giờ, giả sử bạn có trên thực tế đẩy lên repo khác (trên máy X), nhưng bạn hoàn toàn chắc chắn không ai khác đã chứng kiến ​​những thay đổi của bạn nêu ra, và bạn chắc chắn sẽ thiết lập lại cho họ, chứ không phải là quay trở lại chúng (hoàn nguyên số tiền để "fessing up" và để lại bản ghi phía sau, cho phép mọi người khác phục hồi từ nó một cách dễ dàng, nhưng cũng cho phép họ thấy lỗi của bạn :-)).

Đây là lừa: trước hết bạn cần phải nhận được repo máy X để nói rằng "đỉnh của chi nhánh devel được cam kết C7", nơi C7 là trong sơ đồ rất riêng của bạn từ trước đó, chỉ cần tái đánh số vì vậy mà tôi có thể đặt tên cho từng cam kết khác nhau:

--------------------------- C*--- M 
--- C4 --- C5 --- C6 --- C7 ----/

Vậy, bạn có thể làm như thế nào? Vâng, một cách là đăng nhập trên X, cd vào repo (ngay cả khi là --bare) và sử dụng git update-ref tại đó. Giả sử SHA1 cho C7 thực sự là 50db850 (như được hiển thị bằng "git log"). Sau đó, bạn có thể làm điều này:

localhost$ ssh X 
X$ cd /path/to/repo.git 
X$ git update-ref refs/heads/develop 50db850 

Nhưng nếu bạn không thể đăng nhập vào X, hoặc thậm chí chỉ cần không muốn, bạn có thể làm tương tự với git push -f. (Điều này có lợi thế khác: đặc biệt, repo git của bạn sẽ biết rằng origin/develop đã được rewound, một khi push -f hoàn tất thành công.) Chỉ cần làm cho một địa phương ngành mũi trỏ đến đúng cam kết:

localhost$ git branch resetter 50db850 
localhost$ git log resetter   # make sure it looks right 
... 
localhost$ git push -f origin resetter:develop 
Total 0 (delta 0), reused 0 (delta 0) 
To ssh://[redacted] 
+ 21011c9...50db850 resetter -> develop (forced update) 
localhost$ git branch -d resetter # we don't need it anymore 

Một khi bạn đã làm điều này, máy X trở lại trạng thái bạn muốn, và bạn có thể tiến hành như thể bạn chưa bao giờ đẩy hợp nhất mà bạn không thích.

Lưu ý rằng khi bạn làm push -f, nếu bất cứ ai khác đã làm cam kết mới trên đầu trang của M, những sẽ cũng trở thành vô hình (về mặt kỹ thuật họ vẫn ở đó, cùng với kết hợp của bạn cam kết, nhưng họ " bị mất "theo ý nghĩa lost+found của git fsck --lost-found và sau một vài tháng, chúng sẽ biến mất vĩnh viễn).

Again and rất quan trọng: loại "rollback của repo chia sẻ" là một nỗi đau lớn cho những người dùng khác về điều đó repo chia sẻ, vì vậy hãy thực sự chắc chắn đó là OK trước khi bạn làm điều đó.


này loại hợp nhất tầm thường thậm chí không cần phải quay trở lại. Không có gì sai về cơ bản khi rời khỏi sự hợp nhất trong đó. Tuy nhiên, nếu bạn đang đối phó với một sự kết hợp nhầm lẫn nghiêm trọng hơn, có một bất lợi khác là hoàn nguyên hợp nhất, bên cạnh bản ghi "các vòng" của bạn: nó làm cho "làm lại" những thay đổi sau này một chút khó khăn hơn, trong đó sau đó "hợp nhất về mục đích "sẽ thấy kết hợp trước đó và suy nghĩ: OK, tôi không cần phải hợp nhất lại những thay đổi đó. Sau đó, bạn phải "hoàn nguyên việc hoàn nguyên" thay thế.

Tôi nghĩ bài học cất cánh thích hợp ở đây là: xem (git log) trước khi bạn đẩy để đảm bảo rằng những gì bạn sắp đẩy là những gì bạn định đẩy.

Hoặc, dễ dàng và đơn giản hơn: git ls-remote. Điều này sử dụng mã giao thức tìm nạp để xem điều khiển từ xa. Nhưng nó ẩn chủ đề tôi sẽ cho đây, đó là: điều khiển từ xa là một repo giống như của bạn!

Bản phát hành git phiên bản 2 sắp tới có "tính năng an toàn" mới có thể sử dụng được với git push -f. Nhưng họ vẫn chưa ra, vì vậy điều đó không giúp bạn. Tôi sẽ chỉnh sửa lại sau này khi họ đang có, để lưu ý chúng. Cho đến lúc đó, hãy thực sự cẩn thận ở đây: bạn đang đua với bất kỳ ai khác đang cố gắng đẩy nội dung mới vào kho lưu trữ được chia sẻ, tại thời điểm này.

Bạn thậm chí có thể thực hiện việc này bằng ID SHA-1 thô. Tên chi nhánh được dễ dàng hơn để gõ chính xác nhiều lần trong một hàng mặc dù.

Việc lưu giữ và hết hạn là thông qua "reflog" của git. Máy chủ được chia sẻ có thể không ghi nhật ký tất cả các cập nhật ref; nếu không, chỉ có repos riêng đã có các cam kết mới sẽ giữ lại chúng. Thời gian hết hạn mặc định ngắn nhất là 30 ngày, tức là khoảng một tháng, đối với các cam kết không còn có thể truy cập được từ đầu nhánh. Nhưng lưu ý, đó là một cuộc tranh giành không vui nhộn, điên rồ khiến mọi người tìm kiếm thông qua kho lưu trữ của họ cho các giao dịch "bị mất" sau khi ép buộc "mất việc" khỏi kho lưu trữ được chia sẻ.

+0

wow, cảm ơn rất nhiều cho câu trả lời mở rộng này. Thật không may tôi đã đẩy nó đến X. Vì tôi chắc chắn rằng không ai khác đã thực hiện một lấy mới, tôi đã tự hỏi nếu tôi có thể hoàn tác những thay đổi này. Một điều tôi thực sự không rõ ràng về: Nếu tôi đặt lại hai bước trở lại, tôi có bị bỏ lại với một trong các chi nhánh hoặc cả hai không? – dmeu

+0

OK, tốt, sau khi đẩy, nếu bạn * thực sự chắc chắn * :-) ... những gì bạn muốn làm là có được ý tưởng của X của các tip của chi nhánh 'devel' để tua lại một chút. Hãy để tôi chỉnh sửa câu trả lời ... – torek

+2

Ồ, và: nếu bạn 'git reset HEAD ~ 2' thành" di chuyển hai bước trở lại ", điều này thực sự cần nói với git:" đặt đầu của nhánh mà tôi đang ở đây "- có lẽ đây sẽ là của bạn phát triển'— "với ID cam kết bạn nhận được bằng cách tra cứu HEAD ~ 2". Nếu bạn muốn xem commit nào là 'git show HEAD ~ 2' trước. Nếu bạn đang ở mức cam kết 'M' từ sơ đồ của bạn ở trên,' HEAD ~ 2' là 'M ~ 2' sao lưu trên cả' M' và 'C *'. Có lẽ không phải những gì bạn muốn. – torek

Các vấn đề liên quan