Cách chia cam kết cuối cùng thành hai trong Git


277

Tôi có hai chi nhánh làm việc, thạc sĩdiễn đàn và tôi vừa thực hiện một số sửa đổi trong diễn đàn chi nhánh , tôi muốn chọn anh đào thành chủ . Nhưng thật không may, cam kết tôi muốn chọn cherry cũng chứa một số sửa đổi mà tôi không muốn.

Giải pháp có lẽ là bằng cách nào đó xóa các cam kết sai và thay thế nó bằng hai cam kết riêng biệt, một với các thay đổi tôi muốn chọn trong tổng thể và các thay đổi khác không thuộc về đó.

Tôi đã thử làm

git reset --hard HEAD^

đã xóa tất cả các thay đổi, vì vậy tôi phải quay lại với

git reset ORIG_HEAD

Vì vậy, câu hỏi của tôi là, cách tốt nhất để phân chia cam kết cuối cùng thành hai cam kết riêng biệt là gì?

Câu trả lời:


332

Bạn nên sử dụng chỉ mục. Sau khi thực hiện thiết lập lại hỗn hợp (" git reset HEAD ^"), hãy thêm bộ thay đổi đầu tiên vào chỉ mục, sau đó cam kết chúng. Sau đó cam kết phần còn lại.

Bạn có thể sử dụng " git add " để đặt tất cả các thay đổi được thực hiện trong một tệp vào chỉ mục. Nếu bạn không muốn thực hiện mọi sửa đổi được thực hiện trong một tệp, chỉ một số trong số chúng, bạn có thể sử dụng "git add -p".

Hãy xem một ví dụ. Giả sử tôi có một tệp có tên myfile, chứa văn bản sau:

something
something else
something again

Tôi đã sửa đổi nó trong lần cam kết cuối cùng của mình để bây giờ nó trông như thế này:

1
something
something else
something again
2

Bây giờ tôi quyết định rằng tôi muốn chia nó thành hai và tôi muốn chèn dòng đầu tiên vào cam kết đầu tiên và chèn dòng cuối cùng vào cam kết thứ hai.

Đầu tiên tôi quay lại cha mẹ của HEAD, nhưng tôi muốn giữ các sửa đổi trong hệ thống tệp, vì vậy tôi sử dụng "git reset" mà không cần đối số (sẽ thực hiện cái gọi là "thiết lập lại" hỗn hợp):

$ git reset HEAD^
myfile: locally modified
$ cat myfile
1
something
something else
something again
2

Bây giờ tôi sử dụng "git add -p" để thêm các thay đổi tôi muốn cam kết vào chỉ mục (= Tôi giai đoạn chúng). "Git add -p" là một công cụ tương tác hỏi bạn về những thay đổi đối với tệp nên thêm vào chỉ mục.

$ git add -p myfile
diff --git a/myfile b/myfile
index 93db4cb..2f113ce 100644
--- a/myfile
+++ b/myfile
@@ -1,3 +1,5 @@
+1
 something
 something else
 something again
+2
Stage this hunk [y,n,a,d,/,s,e,?]? s    # split this section into two!
Split into 2 hunks.
@@ -1,3 +1,4 @@
+1
 something
 something else
 something again
Stage this hunk [y,n,a,d,/,j,J,g,e,?]? y  # yes, I want to stage this
@@ -1,3 +2,4 @@
 something
 something else
 something again
+2
Stage this hunk [y,n,a,d,/,K,g,e,?]? n   # no, I don't want to stage this

Sau đó, tôi cam kết thay đổi đầu tiên này:

$ git commit -m "Added first line"
[master cef3d4e] Added first line
 1 files changed, 1 insertions(+), 0 deletions(-)

Bây giờ tôi có thể cam kết tất cả các thay đổi khác (cụ thể là chữ số "2" được đặt ở dòng cuối cùng):

$ git commit -am "Added last line"
[master 5e284e6] Added last line
 1 files changed, 1 insertions(+), 0 deletions(-)

Hãy kiểm tra nhật ký để xem những gì chúng tôi cam kết:

$ git log -p -n2 | cat
Commit 5e284e652f5e05a47ad8883d9f59ed9817be59d8
Author: ...
Date: ...

    Added last line

Diff --git a/myfile b/myfile
Index f9e1a67..2f113ce 100644
--- a/myfile
+++ b/myfile
@@ -2,3 +2,4 @@
 something
 something else
 something again
+2

Commit cef3d4e0298dd5d279a911440bb72d39410e7898
Author: ...
Date: ...

    Added first line

Diff --git a/myfile b/myfile
Index 93db4cb..f9e1a67 100644
--- a/myfile
+++ b/myfile
@@ -1,3 +1,4 @@
+1
 something
 something else
 something again

1
Tôi đã dần quen với git từ Mercurial trong một tuần rưỡi qua, và có một lệnh tắt tiện dụng git reset [--patch|-p] <commit>mà bạn có thể sử dụng để cứu bạn khỏi những rắc rối phải làm git add -psau khi đặt lại. Tôi có đúng không Sử dụng git 1.7.9.5.
trojjer

2
Dưới đây là một chút về kỹ thuật này, bao gồm cả việc khởi động lại nếu đó là một cam kết cũ hơn hoặc bạn cần thay đổi N cam kết thành M cam kết: emmanuelbernard.com/blog/2014/04/14/ .
Chris Westin

84

Bàn thắng:

  • Tôi muốn chia một quá khứ cam kết ( splitme) thành hai.
  • Tôi muốn duy trì thông điệp cam kết .

Kế hoạch:

  1. rebase tương tác từ một trước splitme.
  2. chỉnh sửa splitme.
  3. Đặt lại các tập tin để phân chia thành một cam kết thứ hai.
  4. Sửa đổi cam kết, duy trì tin nhắn, sửa đổi khi cần thiết.
  5. Thêm lại các tập tin tách ra từ cam kết đầu tiên.
  6. Cam kết với một tin nhắn mới.
  7. Tiếp tục rebase.

Các bước rebase (1 & 7) có thể được bỏ qua nếu đó splitmelà cam kết gần đây nhất.

git rebase -i splitme^
# mark splitme commit with 'e'
git reset HEAD^ -- $files
git commit --amend
git add $files
git commit -m "commit with just some files"
git rebase --continue

Nếu tôi muốn các tệp được phân tách được cam kết trước tiên, thì tôi sẽ khởi động lại -i một lần nữa và chuyển thứ tự

git rebase -i splitme^
# swap order of splitme and 'just some files'

1
git reset HEAD^là mảnh ghép còn thiếu của câu đố. Hoạt động tốt với -pquá. Cảm ơn!
Marius Gedminas

10
Điều quan trọng cần lưu ý -- $filesđối số là git reset. Với các đường dẫn được truyền, git resetkhôi phục các tệp đó về trạng thái của cam kết được tham chiếu nhưng không thay đổi bất kỳ cam kết nào. Nếu bạn rời khỏi các đường dẫn, thì bạn "mất" cam kết bạn muốn sửa đổi trong bước tiếp theo.
đánh dấu duelin

2
Phương pháp này ngăn bạn khỏi phải sao chép và dán tin nhắn cam kết đầu tiên của bạn lên một lần nữa, so với câu trả lời được chấp nhận.
Calvin

Ngoài ra: nếu bạn muốn đặt lại tất cả các tệp, chỉ cần sử dụng git reset HEAD^ -- .. Vô cùng ngạc nhiên, đây không chính xác là hành vi của git reset HEAD^.
allidoiswin

52

Để thay đổi cam kết hiện tại thành hai cam kết, bạn có thể làm một cái gì đó như sau.

Hoặc:

git reset --soft HEAD^

Điều này hoàn tác cam kết cuối cùng nhưng để lại mọi thứ được dàn dựng. Sau đó, bạn có thể hủy bỏ một số tệp nhất định:

git reset -- file.file

Tùy chọn khôi phục các phần của các tệp đó:

git add -p file.file

Tạo một cam kết đầu tiên mới:

git commit

Giai đoạn và cam kết phần còn lại của các thay đổi trong lần xác nhận thứ hai:

git commit -a

Hoặc là:

Hoàn tác và hủy bỏ tất cả các thay đổi từ lần xác nhận cuối cùng:

git reset HEAD^

Chọn lọc giai đoạn đầu của các thay đổi:

git add -p

Cam kết:

git commit

Cam kết phần còn lại của các thay đổi:

git commit -a

(Trong cả hai bước, nếu bạn hủy bỏ một cam kết đã thêm một tệp hoàn toàn mới và muốn thêm nó vào cam kết thứ hai, bạn sẽ phải thêm nó theo cách thủ công khi commit -achỉ các giai đoạn thay đổi thành các tệp đã được theo dõi.)


22

Chạy git gui, chọn nút radio "Sửa đổi lần cam kết cuối cùng" và bỏ qua (Cam kết> Unstage From Commit, hoặc Ctrl-U thay đổi ) thay đổi mà bạn không muốn thực hiện lần cam kết đầu tiên. Tôi nghĩ đó là cách dễ nhất để làm điều đó.

Một điều khác bạn có thể làm là chọn cherry thay đổi mà không cam kết ( git cherry-pick -n) và sau đó bằng tay hoặc với git guicác thay đổi mong muốn được chọn trước khi cam kết.


15
git reset HEAD^

- đó là những gì giết chết sự thay đổi của bạn.


13

Tôi ngạc nhiên không ai đề nghị git cherry-pick -n forum. Điều này sẽ tạo ra các thay đổi từ forumcam kết mới nhất nhưng không cam kết chúng - sau đó bạn có thể resetloại bỏ những thay đổi bạn không cần và cam kết những gì bạn muốn giữ.


3

Phương pháp hai lần hoàn nguyên

  1. Thực hiện một cam kết khác mà loại bỏ những thay đổi không mong muốn. (Nếu là mỗi tệp, điều này thực sự dễ dàng: git checkout HEAD~1 -- files with unwanted changesgit commit. Nếu không, các tệp có thay đổi hỗn hợp có thể được dàn dựng một phần git reset filegit add -p filenhư một bước trung gian.) Gọi đây là hoàn nguyên .
  2. git revert HEAD- Thực hiện một cam kết khác, điều đó thêm lại những thay đổi không mong muốn. Đây là hoàn nguyên kép
  3. Trong số 2 cam kết bạn đã thực hiện, hãy nén lần đầu tiên vào cam kết tách ( git rebase -i HEAD~3). Cam kết này bây giờ trở nên miễn phí với những thay đổi không mong muốn, đối với những thay đổi trong cam kết thứ hai.

Những lợi ích

  • Giữ nguyên thông điệp cam kết
  • Hoạt động ngay cả khi cam kết chia tách không phải là lần cuối cùng. Nó chỉ yêu cầu những thay đổi không mong muốn không xung đột với các cam kết sau này

1

Vì bạn đang hái anh đào, bạn có thể:

  1. cherry-picknó với --no-committùy chọn được thêm vào.
  2. resetvà sử dụng add --patch, add --edithoặc chỉ addđể giai đoạn những gì bạn muốn giữ.
  3. commit những thay đổi theo giai đoạn.
    • Để sử dụng lại thông điệp cam kết ban đầu, bạn có thể thêm --reuse-message=<old-commit-ref>hoặc --reedit-message=<old-commit-ref>tùy chọn vào commitlệnh.
  4. Thổi bay những thay đổi không có căn cứ với reset --hard.

Một cách khác, bảo tồn hoặc chỉnh sửa thông điệp cam kết ban đầu:

  1. cherry-pick các cam kết ban đầu như bình thường.
  2. Đảo ngược những thay đổi bạn không muốn và sử dụng addđể tạo ra sự đảo ngược.
    • Bước này sẽ dễ dàng nếu bạn xóa những gì bạn đã thêm, nhưng hơi khó nếu bạn thêm những gì bạn đã xóa hoặc đảo ngược thay đổi.
  3. commit --amend để thực hiện đảo ngược trên cam kết chọn anh đào.
    • Bạn sẽ nhận được cùng một thông điệp cam kết một lần nữa, mà bạn có thể giữ hoặc sửa đổi khi cần thiết.

0

Đây có thể là một giải pháp khác được nhắm mục tiêu cho các trường hợp có một cam kết lớn và một lượng nhỏ tệp cần được chuyển sang một cam kết mới. Điều này sẽ hoạt động nếu một bộ<path> tệp được trích xuất ra khỏi lần xác nhận cuối cùng tại HEAD và tất cả được chuyển sang một cam kết mới. Nếu cần nhiều cam kết, các giải pháp khác có thể được sử dụng.

Trước tiên, thực hiện các bản vá vào các khu vực được dàn dựng và không được tổ chức có chứa các thay đổi để hoàn nguyên mã về trước khi sửa đổi và sau khi sửa đổi tương ứng:

git reset HEAD^ <path>

$ git status
On branch <your-branch>
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   <path>

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   <path>

Để hiểu những gì sẽ xảy ra (mũi tên và ý kiến ​​không phải là một phần của lệnh):

git diff --cached   -> show staged changes to revert <path> to before HEAD
git diff            -> show unstaged changes to add current <path> changes

Hoàn nguyên các <path>thay đổi trong lần xác nhận cuối cùng:

git commit --amend  -> reverts changes on HEAD by amending with staged changes

Tạo cam kết mới với các <path>thay đổi:

git commit -a -m "New Commit" -> adds new commit with unstaged changes

Điều này có tác dụng tạo ra một cam kết mới có chứa các thay đổi được trích ra từ cam kết cuối cùng.

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.