Câu hỏi Di chuyển (các) commit mới nhất tới một nhánh mới với Git


Tôi muốn di chuyển một số cam kết cuối cùng mà tôi đã cam kết làm chủ cho một chi nhánh mới và nắm vững trở lại trước khi những cam kết đó được thực hiện. Thật không may, Git-fu của tôi chưa đủ mạnh, có sự giúp đỡ nào không?

I E. Làm thế nào tôi có thể đi từ này

master A - B - C - D - E

này?

newbranch     C - D - E
             /
master A - B 

3763
2017-10-27 03:07


gốc


Lưu ý: Tôi đã hỏi câu hỏi ngược lại đây - Benjol
có thể trùng lặp Di chuyển các commit từ master vào branch bằng git - Chris Moschini
eddmann.com/posts/… cái này hoạt động - Sagar Naliyapara
Các bình luận ở đây có bị tẩy sạch không? Tôi hỏi vì trong chuyến thăm hai tháng một lần của tôi cho câu hỏi này, tôi luôn luôn cuộn qua bình luận đó. - Tejas Kale


Các câu trả lời:


Chuyển đến một chi nhánh mới

CẢNH BÁO: Phương thức này hoạt động vì bạn đang tạo một nhánh mới với lệnh đầu tiên: git branch newbranch. Nếu bạn muốn chuyển các cam kết đến một chi nhánh hiện tại bạn cần hợp nhất các thay đổi của mình vào nhánh hiện có trước khi thực thi git reset --hard HEAD~3 (xem Chuyển đến một chi nhánh hiện có phía dưới). Nếu trước tiên bạn không hợp nhất các thay đổi của mình, chúng sẽ bị mất.

Trừ khi có những trường hợp khác liên quan, điều này có thể dễ dàng thực hiện bằng cách phân nhánh và quay trở lại.

# Note: Any changes not committed will be lost.
git branch newbranch      # Create a new branch, saving the desired commits
git reset --hard HEAD~3   # Move master back by 3 commits (GONE from master)
git checkout newbranch    # Go to the new branch that still has the desired commits

Nhưng hãy chắc chắn có bao nhiêu cam kết quay trở lại. Ngoài ra, bạn có thể thay vì HEAD~3, chỉ cần cung cấp băm của cam kết (hoặc tham chiếu như nguồn gốc / chủ) bạn muốn "hoàn nguyên về" trên bậc thầy (/ hiện tại) chi nhánh, ví dụ:

git reset --hard a1b2c3d4

* 1 Bạn sẽ chỉ có được "mất" cam kết từ các chi nhánh chủ, nhưng đừng lo lắng, bạn sẽ có những cam kết trong newbranch!

CẢNH BÁO: Với phiên bản Git 2.0 trở lên, nếu sau này bạn git rebase nhánh mới trên bản gốc (master) chi nhánh, bạn có thể cần một --no-fork-point tùy chọn trong quá trình rebase để tránh mất các cam kết đã chuyển. Đang có branch.autosetuprebase always đặt làm cho điều này có khả năng hơn. Xem Câu trả lời của John Mellor để biết chi tiết.

Chuyển đến một chi nhánh hiện có

Nếu bạn muốn chuyển các cam kết của mình sang chi nhánh hiện tại, Nó sẽ trông giống thế này:

git checkout existingbranch
git merge master
git checkout master
git reset --hard HEAD~3 # Go back 3 commits. You *will* lose uncommitted work.
git checkout existingbranch

4796
2017-07-22 22:37



Và đặc biệt, không cố gắng quay trở lại xa hơn điểm mà bạn cuối cùng đã đẩy cam kết vào một kho lưu trữ khác mà từ đó ai đó có thể đã kéo. - Greg Hewgill
Tự hỏi nếu bạn có thể giải thích TẠI SAO điều này hoạt động. Với tôi, bạn đang tạo một chi nhánh mới, hãy xóa 3 cam kết khỏi nhánh cũ mà bạn vẫn đang truy cập và sau đó kiểm tra chi nhánh bạn đã thực hiện. Vậy các cam kết mà bạn đã loại bỏ kỳ diệu xuất hiện trong chi nhánh mới như thế nào? - Jonathan Dumaine
@ Jonathan Dumaine: Bởi vì tôi đã tạo chi nhánh mới trước khi xóa các cam kết khỏi chi nhánh cũ. Chúng vẫn ở đó trong nhánh mới. - sykora
các nhánh trong git chỉ là các điểm đánh dấu trỏ đến cam kết trong lịch sử, không có gì được nhân bản, tạo hoặc xóa (trừ các dấu) - knittl
Cũng lưu ý: Đừng làm điều này với những thay đổi không được cam kết trong bản sao làm việc của bạn! Điều này chỉ là bit tôi! :( - Adam Tuttle


Đối với những người tự hỏi tại sao nó hoạt động (như tôi đã ở đầu tiên):

Bạn muốn quay trở lại C, và di chuyển D và E đến nhánh mới. Đây là những gì nó trông giống như lúc đầu:

A-B-C-D-E (HEAD)
        ↑
      master

Sau git branch newBranch:

    newBranch
        ↓
A-B-C-D-E (HEAD)
        ↑
      master

Sau git reset --hard HEAD~2:

    newBranch
        ↓
A-B-C-D-E (HEAD)
    ↑
  master

Vì nhánh cây chỉ là con trỏ, bậc thầy chỉ vào cam kết cuối cùng. Khi bạn thực hiện chi nhánh mới, bạn chỉ cần tạo một con trỏ mới cho lần commit cuối cùng. Sau đó sử dụng git reset bạn đã di chuyển bậc thầy con trỏ trở lại hai cam kết. Nhưng vì bạn không di chuyển chi nhánh mới, nó vẫn trỏ đến cam kết ban đầu nó đã làm.


848
2018-02-07 16:58



Bạn cần - hard, bởi vì nếu không git để lại những thay đổi từ các commit reset trong thư mục làm việc (hoặc ít nhất là đã làm cho tôi). - Andrew
Tôi cũng cần phải làm git push origin master --force để thay đổi hiển thị trong kho lưu trữ chính. - Dženan
Câu trả lời này khiến cho các cam kết bị mất: lần tới bạn git rebase, 3 cam kết sẽ được loại bỏ âm thầm từ newbranch. Xem câu trả lời của tôi để biết chi tiết và các giải pháp thay thế an toàn hơn. - John Mellor
@ John, đó là vô nghĩa. Rebasing mà không biết bạn đang làm gì khiến cho cam kết bị mất. Nếu bạn bị mất cam kết, tôi xin lỗi vì bạn, nhưng câu trả lời này không mất đi cam kết của bạn. Lưu ý rằng origin/master không xuất hiện trong sơ đồ trên. Nếu bạn đã đẩy origin/master và sau đó thực hiện các thay đổi ở trên, chắc chắn, mọi thứ sẽ trở nên buồn cười. Nhưng đó là một "Bác sĩ, nó đau khi tôi làm điều này" loại vấn đề. Và nó nằm ngoài phạm vi cho câu hỏi ban đầu được hỏi. Tôi đề nghị bạn viết câu hỏi của riêng bạn để khám phá kịch bản của bạn thay vì chiếm đoạt điều này. - Ryan Lundy
@ John, trong câu trả lời của bạn, bạn nói "Đừng làm điều này! git branch -t newbranch"Quay lại và đọc lại câu trả lời. Không ai đề nghị làm điều đó. - Ryan Lundy


Nói chung...

Các phương pháp tiếp xúc bởi sykora là lựa chọn tốt nhất trong trường hợp này. Nhưng đôi khi không phải là dễ nhất và nó không phải là một phương pháp chung. Để sử dụng phương pháp chung git cherry-pick:

Để đạt được những gì OP muốn, đó là quy trình gồm 2 bước:

Bước 1 - Lưu ý cam kết từ chủ bạn muốn trên một newbranch

Thi hành

git checkout master
git log

Lưu ý các băm (nói 3) cam kết bạn muốn newbranch. Ở đây tôi sẽ sử dụng:
C cam kết: 9aa1233
D cam kết: 453ac3d
Cam kết E: 612ecb3 

Chú thích: Bạn có thể sử dụng bảy ký tự đầu tiên hoặc   toàn bộ băm cam kết

Bước 2 - Đặt chúng vào newbranch

git checkout newbranch
git cherry-pick 612ecb3
git cherry-pick 453ac3d
git cherry-pick 9aa1233

OR (trên Git 1.7.2+, sử dụng dải ô)

git checkout newbranch
git cherry-pick 612ecb3~1..9aa1233

git cherry-pick áp dụng ba cam kết đó cho newbranch.


343
2018-03-26 08:13



Không phải OP đang cố gắng di chuyển từ bậc thầy đến chi nhánh mới? Nếu bạn chọn cherry trong khi làm chủ, bạn sẽ thêm vào nhánh master - thêm vào các commit mà nó đã có, trên thực tế. Và nó không di chuyển trở lại một trong hai chi nhánh đầu để B. Hoặc là có một cái gì đó tinh tế và mát mẻ tôi không nhận được? - RaveTheTadpole
Điều này làm việc rất tốt nếu bạn vô tình cam kết sai, chi nhánh không phải là chủ, khi bạn nên tạo một nhánh tính năng mới. - julianc
1 cho một cách tiếp cận hữu ích trong một số trường hợp. Điều này là tốt nếu bạn chỉ muốn kéo các cam kết của riêng bạn (được xen kẽ với những người khác) vào một chi nhánh mới. - Tyler V.
Đó là câu trả lời tốt hơn. Bằng cách này, bạn có thể di chuyển các cam kết đến bất kỳ chi nhánh nào. - skywinder
Thứ tự anh đào có quan trọng không? - kon psych


Tuy nhiên, một cách khác để làm điều này, chỉ sử dụng 2 lệnh. Cũng giữ cho cây làm việc hiện tại của bạn còn nguyên vẹn.

git checkout -b newbranch # switch to a new branch
git branch -f master HEAD~3 # make master point to some older commit

Phiên bản cũ - trước khi tôi học về git branch -f

git checkout -b newbranch # switch to a new branch
git push . +HEAD~3:master # make master point to some older commit 

Có thể push đến . là một mẹo hay để biết.


252
2018-04-06 22:38



Thư mục hiện tại. Tôi đoán điều này sẽ chỉ hoạt động nếu bạn đang ở trong một thư mục hàng đầu. - aragaer
Các địa phương đẩy là grin-inducing, nhưng trên sự phản ánh, làm thế nào là nó khác nhau để git branch -f đây? - jthill
@GerardSexton . là giám đốc hiện tại. git có thể đẩy tới REMOTES hoặc GIT URL. path to local directory được hỗ trợ cú pháp URL Git. Xem phần URL GIT trong git help clone. - weakish
Tôi không biết tại sao điều này không được đánh giá cao hơn. Chết đơn giản, và không có nguy cơ nhỏ nhưng tiềm năng của git reset --hard. - Godsmith
@Godsmith Đoán của tôi là mọi người thích ba lệnh đơn giản hơn với hai lệnh hơi mơ hồ hơn một chút. Ngoài ra, các câu trả lời được bình chọn hàng đầu sẽ nhận được nhiều phiếu bầu hơn bởi bản chất được hiển thị trước tiên. - JS_Riddler


Hầu hết các câu trả lời trước đây là sai nguy hiểm!

KHÔNG làm điều này:

git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch

Lần sau bạn chạy git rebase (hoặc là git pull --rebase) 3 cam kết đó sẽ bị loại bỏ một cách âm thầm newbranch! (xem giải thích bên dưới)

Thay vào đó hãy làm điều này:

git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
  • Đầu tiên nó loại bỏ 3 cam kết gần đây nhất (--keep giống như --hard, nhưng an toàn hơn, vì thất bại hơn là vứt bỏ những thay đổi không được cam kết).
  • Sau đó, nó tắt newbranch.
  • Sau đó, anh đào chọn 3 cam kết đó trở lại newbranch. Vì chúng không còn được tham chiếu bởi một chi nhánh, nó thực hiện điều đó bằng cách sử dụng git's reflog: HEAD@{2} là cam kết HEAD được sử dụng để chỉ 2 hoạt động trước đây, tức là trước khi chúng tôi 1. kiểm tra newbranch và 2. được sử dụng git reset để loại bỏ 3 cam kết.

Cảnh báo: reflog được bật theo mặc định, nhưng nếu bạn đã vô hiệu hóa nó theo cách thủ công (ví dụ: bằng cách sử dụng kho lưu trữ git "bare"), bạn sẽ không thể lấy lại 3 commit sau khi chạy git reset --keep HEAD~3.

Một giải pháp thay thế không dựa vào việc reflog là:

# newbranch will omit the 3 most recent commits.
git checkout -b newbranch HEAD~3
git branch --set-upstream-to=oldbranch
# Cherry-picks the extra commits from oldbranch.
git cherry-pick ..oldbranch
# Discards the 3 most recent commits from oldbranch.
git branch --force oldbranch oldbranch~3

(nếu bạn thích, bạn có thể viết @{-1} - chi nhánh đã được kiểm tra trước đó - thay vì oldbranch).


Giải thích kỹ thuật

Tại sao git rebase loại bỏ 3 cam kết sau ví dụ đầu tiên? Là vì git rebase không có đối số nào cho phép --fork-point tùy chọn theo mặc định, sử dụng công cụ khôi phục cục bộ để cố gắng mạnh mẽ chống lại nhánh ngược dòng bị ép buộc.

Giả sử bạn phân nhánh gốc / gốc khi nó chứa các cam kết M1, M2, M3, sau đó thực hiện ba cam kết chính mình:

M1--M2--M3  <-- origin/master
         \
          T1--T2--T3  <-- topic

nhưng sau đó một người nào đó viết lại lịch sử bằng lực đẩy / gốc để loại bỏ M2:

M1--M3'  <-- origin/master
 \
  M2--M3--T1--T2--T3  <-- topic

Sử dụng công cụ sửa lỗi cục bộ của bạn, git rebase có thể thấy rằng bạn đã chia đôi từ một hóa thân trước đó của nhánh gốc / master, và do đó các cam kết M2 và M3 không thực sự là một phần của nhánh chủ đề của bạn. Do đó nó hợp lý giả định rằng kể từ khi M2 đã được gỡ bỏ từ nhánh ngược dòng, bạn không còn muốn nó trong nhánh chủ đề của bạn, hoặc khi nhánh chủ đề được rebased:

M1--M3'  <-- origin/master
     \
      T1'--T2'--T3'  <-- topic (rebased)

Hành vi này có ý nghĩa, và nói chung là điều đúng đắn cần làm khi rebasing.

Vì vậy, lý do mà các lệnh sau không thành công:

git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch

là bởi vì họ rời khỏi tình trạng reflog ở trạng thái sai. Git thấy newbranch như đã chia rẽ khỏi nhánh thượng nguồn tại bản sửa đổi bao gồm 3 cam kết, sau đó là reset --hard viết lại lịch sử của thượng nguồn để xóa các cam kết, và vì vậy lần sau bạn chạy git rebase nó loại bỏ chúng giống như bất kỳ cam kết nào khác đã bị xóa khỏi thượng nguồn.

Nhưng trong trường hợp cụ thể này, chúng tôi muốn 3 cam kết đó được coi là một phần của nhánh chủ đề. Để đạt được điều đó, chúng tôi cần phải ngã ba khỏi thượng lưu tại bản sửa đổi trước đó mà không bao gồm 3 cam kết. Đó là những gì các giải pháp được đề xuất của tôi làm, do đó cả hai đều rời khỏi trạng thái chính xác.

Để biết thêm chi tiết, hãy xem định nghĩa của --fork-point bên trong git rebase và git merge-basetài liệu.


210
2017-10-19 14:12



Câu trả lời này nói "KHÔNG làm điều này!" trên một cái gì đó mà không ai đề nghị làm. - Ryan Lundy
@Kyralessa, -t bạn đang đề cập đến git branch xảy ra ngầm nếu bạn có git config --global branch.autosetuprebase always bộ. Ngay cả khi bạn không, tôi đã được giải thích với bạn rằng cùng một vấn đề xảy ra nếu bạn thiết lập theo dõi sau khi thực hiện các lệnh này, như OP có khả năng dự định đưa ra câu hỏi của họ. - John Mellor
@ RockLee, vâng, cách chung để khắc phục tình huống như vậy là tạo ra một nhánh mới (newbranch2) từ một điểm khởi đầu an toàn sau đó chọn tất cả các cam kết bạn muốn giữ (từ badnewbranch sang newbranch2). Cherry-chọn sẽ cho các cam kết băm mới, vì vậy bạn sẽ có thể an toàn rebase newbranch2 (và bây giờ có thể xóa badnewbranch). - John Mellor
@ Walf, bạn hiểu lầm: git rebase được thiết kế để trở nên mạnh mẽ chống lại dòng thượng nguồn có lịch sử được viết lại. Thật không may, các tác dụng phụ của sự mạnh mẽ đó ảnh hưởng đến tất cả mọi người, ngay cả khi họ cũng không phải lịch sử của họ bao giờ viết lại. - John Mellor
Bạn cũng có thể dùng --no-fork-point khi bạn chạy git rebase. - torek


Điều này không "di chuyển" chúng theo nghĩa kỹ thuật nhưng nó có tác dụng tương tự:

A--B--C  (branch-foo)
 \    ^-- I wanted them here!
  \
   D--E--F--G  (branch-bar)
      ^--^--^-- Opps wrong branch!

While on branch-bar:
$ git reset --hard D # remember the SHAs for E, F, G (or E and G for a range)

A--B--C  (branch-foo)
 \
  \
   D-(E--F--G) detached
   ^-- (branch-bar)

Switch to branch-foo
$ git cherry-pick E..G

A--B--C--E'--F'--G' (branch-foo)
 \   E--F--G detached (This can be ignored)
  \ /
   D--H--I (branch-bar)

Now you won't need to worry about the detached branch because it is basically
like they are in the trash can waiting for the day it gets garbage collected.
Eventually some time in the far future it will look like:

A--B--C--E'--F'--G'--L--M--N--... (branch-foo)
 \
  \
   D--H--I--J--K--.... (branch-bar)

27
2018-01-21 16:10



Bạn không thể dùng rebase cho cùng một điều? - Bergi
Có, bạn có thể sử dụng cách khác rebase trên nhánh tách rời trong kịch bản trên. - Sukima


Để làm điều này mà không cần viết lại lịch sử (tức là nếu bạn đã đẩy các cam kết):

git checkout master
git revert <commitID(s)>
git checkout -b new-branch
git cherry-pick <commitID(s)>

Cả hai nhánh sau đó có thể được đẩy mà không có lực!


19
2017-09-20 10:17



Nhưng sau đó bạn phải đối phó với kịch bản hoàn nguyên, tùy theo hoàn cảnh của bạn, có thể phức tạp hơn nhiều. Nếu bạn hoàn nguyên một cam kết trên nhánh, Git sẽ vẫn thấy những cam kết đó đã xảy ra, vì vậy để hoàn tác, bạn phải hoàn nguyên việc hoàn nguyên. Điều này đốt cháy một vài người, đặc biệt là khi họ quay trở lại một hợp nhất và cố gắng hợp nhất các chi nhánh trở lại, chỉ để thấy rằng Git tin rằng nó đã sáp nhập mà chi nhánh trong (đó là hoàn toàn đúng). - Makoto
Đó là lý do tại sao tôi cherry-pick các cam kết ở cuối, vào một chi nhánh mới. Bằng cách đó, git coi chúng như những cam kết mới, giải quyết vấn đề của bạn. - teh_senaus
Điều này là nguy hiểm hơn nó lần đầu tiên có vẻ, kể từ khi bạn đang thay đổi trạng thái của lịch sử của kho lưu trữ mà không thực sự hiểu được ý nghĩa của trạng thái này. - Makoto
Tôi không tuân theo lập luận của bạn - điểm của câu trả lời này là bạn không thay đổi lịch sử, chỉ cần thêm các cam kết mới (có hiệu quả hoàn tác việc làm lại các thay đổi). Các cam kết mới này có thể được đẩy và hợp nhất như bình thường. - teh_senaus


Chỉ có tình huống này:

Branch one: A B C D E F     J   L M  
                       \ (Merge)
Branch two:             G I   K     N

Tôi đã biểu diễn:

git branch newbranch 
git reset --hard HEAD~8 
git checkout newbranch

Tôi hy vọng rằng cam kết tôi sẽ là HEAD, nhưng cam kết L là bây giờ ...

Để chắc chắn hạ cánh đúng vị trí trong lịch sử, nó dễ dàng hơn để làm việc với hàm băm của cam kết

git branch newbranch 
git reset --hard #########
git checkout newbranch

12
2018-05-01 07:50





Giải pháp đơn giản hơn nhiều

Nếu:

  1. Mục đích chính của bạn là quay trở lại master
  2. Bạn muốn giữ các thay đổi nhưng không đặc biệt quan tâm đến cam kết cá nhân và
  3. Bạn chưa đẩy,

Sau đó, sau đây là đơn giản hơn nhiều (bắt đầu từ chi nhánh master với các cam kết nhầm lẫn):

git reset HEAD~3
git stash
git checkout newbranch
git stash pop

Điều này làm gì:

  1. Hoàn tác ba cam kết cuối cùng (và thông điệp của họ) thành master, nhưng để nguyên tất cả các tệp đang hoạt động
  2. Stashes đi tất cả các thay đổi tập tin làm việc, làm cho master cây làm việc bằng trạng thái HEAD ~ 3.
  3. Chuyển sang nhánh hiện có newbranch
  4. Kéo các tệp đã thay đổi ra khỏi stash và vào thư mục làm việc của bạn.

Bây giờ bạn có thể sử dụng git add và git commit như bạn thường làm. Mọi thay đổi sẽ được cam kết trong newbranch.

Điều này không làm:

  • Nó không để các nhánh tạm thời ngẫu nhiên làm lộn xộn cây của bạn.
  • Nó không bảo vệ các cam kết nhầm lẫn và cam kết tin nhắn. Bạn sẽ cần phải thêm thông báo cam kết mới vào cam kết mới này. Tuy nhiên, OP tuyên bố mục tiêu là "làm chủ trở lại trước khi những cam kết đó được thực hiện" mà không làm mất các thay đổi và giải pháp này thực hiện điều đó.

Tôi làm điều này ít nhất một lần một tuần khi tôi vô tình thực hiện các cam kết mới master thay vì develop. Thông thường tôi chỉ có một cam kết để quay ngược lại trong trường hợp đó git reset HEAD^ hoạt động để khôi phục chỉ một lần commit.


8
2018-06-01 05:40





1) Tạo một nhánh mới, chuyển tất cả các thay đổi của bạn sang new_branch.

git checkout -b new_branch

2) Sau đó quay lại nhánh cũ.

git checkout master

3) Làm git rebase

git rebase -i <short-hash-of-B-commit>

4) Sau đó trình soạn thảo đã mở chứa 3 thông tin cam kết cuối cùng.

...
pick <C's hash> C
pick <D's hash> D
pick <E's hash> E
...

5) Thay đổi pick đến drop trong tất cả 3 cam kết đó. Sau đó lưu và đóng trình chỉnh sửa.

...
drop <C's hash> C
drop <D's hash> D
drop <E's hash> E
...

6) Bây giờ 3 lần commit cuối cùng được loại bỏ khỏi nhánh hiện tại (master). Bây giờ đẩy nhánh mạnh mẽ, với + ký tên trước tên chi nhánh.

git push origin +master

1
2018-05-25 14:14





Bạn có thể làm điều này chỉ là 3 bước đơn giản mà tôi đã sử dụng.

1) tạo chi nhánh mới nơi bạn muốn cam kết cập nhật gần đây.

git branch <branch name> 

2) Tìm ID cam kết gần đây để cam kết chi nhánh mới.

git log

3) Sao chép ghi chú ID cam kết rằng danh sách cam kết gần đây nhất diễn ra trên đầu trang. để bạn có thể tìm thấy cam kết của mình. bạn cũng tìm thấy thông báo này qua tin nhắn.

git cherry-pick d34bcef232f6c... 

bạn cũng có thể cung cấp một số reo của cam kết id.

git cherry-pick d34bcef...86d2aec

Bây giờ công việc của bạn đã xong. Nếu bạn chọn đúng id và chi nhánh chính xác thì bạn sẽ thành công. Vì vậy, trước khi làm điều này hãy cẩn thận. một vấn đề khác có thể xảy ra.

Bây giờ bạn có thể đẩy mã của bạn

git push


0