Làm cách nào tôi có thể hợp nhất hai cam kết thành một nếu tôi đã bắt đầu rebase?


1158

Tôi đang cố gắng hợp nhất 2 lần xác nhận thành 1, vì vậy tôi đã theo dõi các cam kết của squash với rebase .

Tôi đã chạy

git rebase --interactive HEAD~2

Trong trình chỉnh sửa kết quả, tôi thay đổi pickthành squashlưu rồi thoát, nhưng quá trình rebase không thành công với lỗi

Không thể 'squash' mà không có cam kết trước đó

Bây giờ cây công việc của tôi đã đạt đến trạng thái này, tôi đang gặp khó khăn trong việc phục hồi.

Lệnh git rebase --interactive HEAD~2không thành công với:

Rebase tương tác đã bắt đầu

git rebase --continuethất bại với

Không thể 'squash' mà không có cam kết trước đó


22
Tôi cũng đánh cái này Lỗi của tôi là do thực tế là git rebase -i liệt kê các cam kết theo thứ tự ngược lại của nhật ký git; cam kết mới nhất là ở phía dưới!
lmsurprenant


Câu trả lời:


1733

Tóm lược

Thông báo lỗi

Không thể 'squash' mà không có cam kết trước đó

có nghĩa là bạn có thể đã cố gắng để squash xuống dưới. Git luôn ép một cam kết mới hơn vào một cam kết cũ hơn hoặc về hướng lên trên như được xem trên danh sách việc cần làm tương tác, đó là một cam kết trên một dòng trước đó. Việc thay đổi lệnh trên dòng đầu tiên trong danh sách việc cần làm của squashbạn sẽ luôn tạo ra lỗi này vì không có gì cho cam kết đầu tiên đè bẹp.

Sửa chữa

Trước tiên hãy quay lại nơi bạn bắt đầu với

$ git rebase --abort

Nói lịch sử của bạn là

$ git log --pretty=oneline
a931ac7c808e2471b22b5bd20f0cad046b1c5d0d c
b76d157d507e819d7511132bdb5a80dd421d854f b
df239176e1a2ffac927d8b496ea00d5488481db5 a

Đó là, a là cam kết đầu tiên, sau đó là b, và cuối cùng là c. Sau khi cam kết c, chúng tôi quyết định ép b và c lại với nhau:

(Lưu ý: Chạy git logống dẫn đầu ra của nó vào máy nhắn tin, lesstheo mặc định trên hầu hết các nền tảng. Để thoát máy nhắn tin và quay lại dấu nhắc lệnh của bạn, hãy bấm qphím.)

Chạy git rebase --interactive HEAD~2cho bạn một trình soạn thảo với

pick b76d157 b
pick a931ac7 c

# Rebase df23917..a931ac7 onto df23917
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

(Lưu ý rằng danh sách việc cần làm này theo thứ tự ngược lại so với đầu ra của git log.)

Việc thay đổi b pickthành squashsẽ dẫn đến lỗi mà bạn đã thấy, nhưng nếu thay vào đó, bạn ép c thành b (cam kết mới hơn vào cũ hơn hoặc nghiền nát hướng lên trên) bằng cách thay đổi danh sách việc cần làm thành

pick   b76d157 b
squash a931ac7 c

và thoát khỏi trình soạn thảo của bạn, bạn sẽ nhận được một trình soạn thảo khác có nội dung

# This is a combination of 2 commits.
# The first commit's message is:

b

# This is the 2nd commit message:

c

Khi bạn lưu và thoát, nội dung của tệp đã chỉnh sửa sẽ trở thành thông báo cam kết của cam kết kết hợp mới:

$ git log --pretty=oneline
18fd73d3ce748f2a58d1b566c03dd9dafe0b6b4f b and c
df239176e1a2ffac927d8b496ea00d5488481db5 a

Lưu ý về lịch sử viết lại

Rebase tương tác viết lại lịch sử. Cố gắng đẩy đến một điều khiển từ xa có chứa lịch sử cũ sẽ thất bại bởi vì nó không phải là một chuyển tiếp nhanh.

Nếu chi nhánh bạn nổi loạn là một chủ đề hoặc chi nhánh tính năng mà bạn đang làm việc một mình , không có vấn đề gì lớn. Việc đẩy sang kho lưu trữ khác sẽ yêu cầu --forcetùy chọn, hoặc thay vào đó, bạn có thể, tùy thuộc vào quyền của kho lưu trữ từ xa, trước tiên xóa chi nhánh cũ và sau đó đẩy phiên bản được khởi động lại. Ví dụ về các lệnh sẽ có khả năng phá hủy công việc nằm ngoài phạm vi của câu trả lời này.

Viết lại lịch sử đã xuất bản trên một chi nhánh tại mà bạn đang làm việc với những người khác mà không cần rất lý do chính đáng như bị rò rỉ mật khẩu hoặc các chi tiết nhạy cảm lực lượng lao động khác vào cộng tác viên của bạn và chống đối xã hội và sẽ làm phiền nhà phát triển khác. Phần mềm phục hồi từ một phần Rebase ngược dòng trong git rebasetài liệu giải thích, với sự nhấn mạnh thêm.

Rebasing (hoặc bất kỳ hình thức viết lại nào khác) một nhánh mà những người khác đã làm việc dựa trên đó là một ý tưởng tồi: bất kỳ ai ở hạ lưu của nó đều bị buộc phải tự sửa lịch sử của họ. Phần này giải thích cách thực hiện sửa chữa theo quan điểm của hạ lưu. Tuy nhiên, cách khắc phục thực sự sẽ là tránh việc ngược dòng ở vị trí đầu tiên. Giáo dục


Nếu tôi sử dụng rebase để xóa một cam kết, một cam kết "kết hợp" mới được tạo có chứa hai thay đổi nhưng hàm băm thì khác. các cam kết ban đầu được bảo quản bởi git là tốt?
fabsenet

@fabsenet Có và không. Các cam kết ban đầu vẫn có thể truy cập được nhưng có thể không còn truy cập được từ bất kỳ ref nào (tùy thuộc vào chi tiết lịch sử của bạn). Cam kết không được ước tính cuối cùng đã được đưa ra thông qua quy trình thu gom rác.
Greg Bacon

tôi chỉ chơi xung quanh ... tôi đã làm git log hashoftheoldcommitvà nó đã hoạt động, nhưng tôi tò mò muốn xem một git log --graphvới tất cả các cam kết không thể truy cập được bao gồm
fabsenet

quả bí đao này là công cụ tốt để tổ chức các lần xác nhận trước khi đẩy, nhưng nếu tôi đẩy một lần xác nhận, tôi không thể ép nó? git nói: Đã thành công khởi động lại và cập nhật ĐẦU tách ra.
Sérgio

Không nhận được trình soạn thảo trong trường hợp thứ hai, git bash, dường như bị đóng băng trong một số quy trình. Phải làm sao

411

Nếu có nhiều lần xác nhận, bạn có thể sử dụng git rebase -iđể nén hai lần xác nhận thành một.

Nếu chỉ có hai cam kết bạn muốn hợp nhất và chúng là "hai lần gần đây nhất", các lệnh sau có thể được sử dụng để kết hợp hai cam kết thành một:

git reset --soft "HEAD^"
git commit --amend

6
Nhược điểm so với rebase là gì? Tôi tìm thấy một đơn giản hơn nhiều để sử dụng.
Guillaume86

17
Bạn không thể tham gia theo thứ tự tùy ý - chỉ hai lần cam kết cuối cùng .
dr0i

50
@ dr0i Bạn có thể hợp nhất bao nhiêu lần xác nhận tùy thích, miễn là chúng là lần xác nhận X cuối cùng, và không phải ở đâu đó ở giữa. Chỉ cần chạy git reset --soft HEAD~10, trong đó 10 là số lần xác nhận bạn muốn hợp nhất.
Fregante

2
Điều này có thể được sử dụng nếu bạn không có bộ gốc từ xa và bạn chỉ có hai lần xác nhận.
atedja

8
Bạn cũng có thể thiết lập lại để một cụ thể cam kết nếu bạn không muốn đếm có bao nhiêu từ HEADbằng cách sử dụng git reset --soft 47b5c5...nơi 47b5c5...là ID SHA1 của cam kết.
năm15

112

Rebase: Bạn không cần Gonna:

Một cách đơn giản hơn cho kịch bản thường xuyên nhất.

Trong hầu hết các trường hợp:

Trên thực tế nếu tất cả những gì bạn muốn chỉ đơn giản là kết hợp một số cam kết gần đây thành một nhưng không cần drop, rewordvà công việc rebase khác.

bạn chỉ có thể làm:

git reset --soft "HEAD~n"
  • Giả sử ~nlà số cam kết để nhẹ nhàng un-cam kết (ví dụ ~1, ~2...)

Sau đó, sử dụng lệnh sau để sửa đổi thông điệp cam kết.

git commit --amend

đó là khá giống như một phạm vi dài squashvà một pick.

Và nó hoạt động cho n cam kết nhưng không chỉ hai cam kết như câu trả lời trên đã nhắc.


3
Điều này là tốt nếu bạn muốn thực hiện một số dọn dẹp bổ sung ngoài việc chỉ xóa các xác nhận, chẳng hạn như xóa 1 cam kết ở giữa hoặc thay đổi một dòng mã.
styfle

1
Giả sử ~nlà số cam kết để nhẹ nhàng un-cam kết (ví dụ ~1, ~2...)
Ray

1
Điều gì xảy ra nếu tôi muốn hợp nhất không phải là nlần xác nhận cuối cùng, nhưng ncam kết ở giữa? Tôi có thể làm điều đó một cách dễ dàng?
chumakoff

1
Sau đó git rebase -ilà những gì bạn cần để làm squashviệc. @chumakoff
pambda

3
Vì vậy, để tham gia nhầu hết các cam kết gần đây vào một, sử dụng đầu tiên git reset --soft @~m, trong đóm = n - 1
Łukasz Rajchel

55

Trước tiên, bạn nên kiểm tra xem bạn có bao nhiêu cam kết:

git log

Có hai trạng thái:

Một là chỉ có hai cam kết:

Ví dụ:

commit A
commit B

(Trong trường hợp này, bạn không thể sử dụng git rebase để làm) bạn cần làm theo.

$ git reset --soft HEAD^1

$ git commit --amend

Một điều nữa là có nhiều hơn hai lần cam kết; bạn muốn hợp nhất cam kết C và D.

Ví dụ:

commit A
commit B
commit C
commit D

(trong điều kiện này, bạn có thể sử dụng git rebase)

git rebase -i B

Và hơn là sử dụng "squash" để làm. Các thins còn lại là rất dễ dàng. Nếu bạn vẫn chưa biết, vui lòng đọc http://zerodie.github.io/blog/2012/01/19/git-rebase-i/


Thiết lập lại --soft và commit --amend là cách duy nhất hoạt động nếu bạn đã có một cuộc nổi loạn đang diễn ra (và chọn 'chỉnh sửa' thay vì 'squash' cho cam kết này). +1
Jacek Lach

1
Hợp nhất hai cam kết đầu tiên và duy nhất trong một kho lưu trữ, chính xác là trường hợp cạnh của tôi :-)
Chris Huang-Leaver

1
Vui lòng thêm rằng git push -f origin mastercó thể cần thiết.
Rishabh Agrahari

33

Giả sử bạn đã ở trong nhánh chủ đề của riêng bạn. Nếu bạn muốn hợp nhất 2 lần xác nhận cuối cùng thành một và trông giống như một anh hùng, hãy tách nhánh cam kết ngay trước khi bạn thực hiện hai lần xác nhận cuối cùng.

git checkout -b temp_branch HEAD^2

Sau đó, squash cam kết các nhánh khác trong nhánh mới này:

git merge branch_with_two_commits --squash

Điều đó sẽ mang lại những thay đổi nhưng không cam kết chúng. Vì vậy, chỉ cần cam kết với họ và bạn đã hoàn tất.

git commit -m "my message"

Bây giờ bạn có thể hợp nhất nhánh chủ đề mới này trở lại vào nhánh chính của bạn.


5
Đây thực sự là câu trả lời hữu ích nhất đối với tôi, bởi vì nó không yêu cầu khởi động lại bằng tay, mà thay vào đó chỉ cần ép tất cả các cam kết của cả một chi nhánh thành một cam kết. Rất đẹp.
Robert

Cám ơn vì cái này! Đây là cách để làm cho git làm như thế nào tôi hình ảnh squash cam kết trong đầu của tôi!
Marjan Venema

câu trả lời tuyệt vời, đơn giản hơn nhiều so với các lựa chọn thay thế

Rõ ràng, câu trả lời này không phù hợp với trường hợp khi acyêu cầu phải được hợp nhất với nhau và giữ nguyên bnhư vậy.
Talha Ashraf

2
Có cái gì đó thay đổi trong các phiên bản gần đây của git? Khi tôi thử lệnh đầu tiên ( git checkout -b combine-last-two-commits "HEAD^2") trong phiên bản git 2.17, tôi gặp lỗi:fatal: 'HEAD^2' is not a commit and a branch 'combine-last-two-commits' cannot be created from it
mhucka

23

bạn có thể hủy bỏ cuộc nổi loạn với

git rebase --abort

và khi bạn chạy lại lệnh rebase tương tác, 'squash; cam kết phải ở dưới mức chọn cam kết trong danh sách


16

Tôi thường sử dụng git reset - được trộn để hoàn nguyên phiên bản cơ sở trước nhiều lần xác nhận mà bạn muốn hợp nhất, sau đó tôi thực hiện một cam kết mới, theo cách đó có thể cho phép cam kết mới nhất của bạn, đảm bảo phiên bản của bạn là CHÍNH sau khi bạn đẩy lên máy chủ.

commit ac72a4308ba70cc42aace47509a5e
Author: <me@me.com>
Date:   Tue Jun 11 10:23:07 2013 +0500

    Added algorithms for Cosine-similarity

commit 77df2a40e53136c7a2d58fd847372
Author: <me@me.com>
Date:   Tue Jun 11 13:02:14 2013 -0700

    Set stage for similar objects

commit 249cf9392da197573a17c8426c282
Author: Ralph <ralph@me.com>
Date:   Thu Jun 13 16:44:12 2013 -0700

    Fixed a bug in space world automation

Nếu tôi muốn hợp nhất hai cam kết thành một, đầu tiên tôi sử dụng:

git reset --mixed 249cf9392da197573a17c8426c282

"249cf9392da197573a17c8426c282" là phiên bản thứ ba, cũng là phiên bản cơ sở của bạn trước khi bạn hợp nhất, sau đó, tôi thực hiện một cam kết mới:

git add .
git commit -m 'some commit message'

Đó là tất cả, hy vọng là một cách khác cho tất cả mọi người.

FYI, từ git reset --help:

 --mixed
     Resets the index but not the working tree (i.e., the changed files are
     preserved but not marked for commit) and reports what has not been
     updated. This is the default action.

Tôi chưa đọc tài liệu cho '- đã trộn' nhưng tôi chắc chắn rằng những người khác đã đọc bài đăng và tự hỏi điều tương tự: lợi thế của việc sử dụng - trộn là gì? Có thể cải thiện bài đăng của bạn để bao gồm một đoạn của trang người đàn ông.
funroll

@funroll Tôi không biết - đã trộn lẫn rất nhiều trước khi tôi viết câu trả lời này, theo kinh nghiệm của riêng tôi, thao tác hỗn hợp sẽ biến phiên bản mà tôi chuyển thành đối số làm phiên bản ĐẦU của kho lưu trữ và không có gì có thể bị mất sau phiên bản đó, vì vậy chúng tôi vẫn có thể xử lý những thay đổi đó.
VinceStyling

14

$ git rebase --abort

Chạy mã này bất cứ lúc nào nếu bạn muốn hoàn tác gase rebase

$ git rebase -i HEAD~2

Để áp dụng lại hai lần cam kết cuối cùng. Lệnh trên sẽ mở trình soạn thảo mã

  • [ Cam kết mới nhất sẽ ở dưới cùng ]. Thay đổi cam kết cuối cùng thành squash (s). Vì squash sẽ kết hợp với cam kết trước đó.
  • Sau đó nhấn phím esc và gõ: wq để lưu và đóng

Sau: wq bạn sẽ ở chế độ rebase hoạt động

Lưu ý : Bạn sẽ nhận được một trình chỉnh sửa khác nếu không có thông báo cảnh báo / lỗi, Nếu có lỗi hoặc cảnh báo, trình chỉnh sửa khác sẽ không hiển thị, bạn có thể hủy bỏ bằng cách chạy $ git rebase --abortnếu bạn thấy lỗi hoặc cảnh báo khác chỉ tiếp tục bằng cách chạy$ git rebase --continue

Bạn sẽ thấy 2 tin nhắn cam kết của bạn. Chọn một hoặc viết tin nhắn cam kết của riêng bạn, lưu và thoát [: wq]

Lưu ý 2: Bạn có thể cần phải đẩy các thay đổi của mình sang repo từ xa nếu bạn chạy lệnh rebase

$ git push -f

$ git push -f origin master


1
Lưu ý 2: git push -f origin/masterlà những gì câu trả lời khác đang thiếu. +1
Rishabh Agrahari

2

Vì tôi sử dụng git cherry-pickcho tất cả mọi thứ, với tôi, việc làm như vậy là điều tự nhiên ngay cả ở đây.

Cho rằng tôi đã branchXkiểm tra và có hai cam kết ở đầu của nó, trong đó tôi muốn tạo một cam kết kết hợp nội dung của chúng, tôi làm điều này:

git checkout HEAD^ // Checkout the privious commit
git cherry-pick --no-commit branchX // Cherry pick the content of the second commit
git commit --amend // Create a new commit with their combined content

Nếu tôi cũng muốn cập nhật branchX(và tôi cho rằng đây là mặt trái của phương pháp này) tôi cũng phải:

git checkout branchX
git reset --hard <the_new_commit>

1

Nếu nhánh chính của bạn git logtrông giống như sau:

commit ac72a4308ba70cc42aace47509a5e
Author: <me@me.com>
Date:   Tue Jun 11 10:23:07 2013 +0500

    Added algorithms for Cosine-similarity

commit 77df2a40e53136c7a2d58fd847372
Author: <me@me.com>
Date:   Tue Jun 11 13:02:14 2013 -0700

    Set stage for similar objects

commit 249cf9392da197573a17c8426c282
Author: Ralph <ralph@me.com>
Date:   Thu Jun 13 16:44:12 2013 -0700

    Fixed a bug in space world automation

và bạn muốn hợp nhất hai cam kết hàng đầu chỉ cần thực hiện theo các bước đơn giản sau:

  1. Đầu tiên để được kiểm tra bên an toàn lần cam kết cuối cùng thứ hai trong một chi nhánh riêng biệt. Bạn có thể đặt tên cho chi nhánh bất cứ điều gì.git checkout 77df2a40e53136c7a2d58fd847372 -b merged-commits
  2. Bây giờ, chỉ cần chọn các thay đổi của bạn từ lần xác nhận cuối cùng vào nhánh mới này là : git cherry-pick -n -x ac72a4308ba70cc42aace47509a5e. (Giải quyết xung đột nếu phát sinh bất kỳ)
  3. Vì vậy, bây giờ, những thay đổi của bạn trong lần xác nhận cuối cùng đã có trong lần xác nhận cuối cùng thứ hai của bạn. Nhưng bạn vẫn phải cam kết, vì vậy trước tiên hãy thêm các thay đổi bạn vừa chọn và sau đó thực hiện git commit --amend.

Đó là nó. Bạn có thể đẩy phiên bản hợp nhất này trong nhánh "hợp nhất-cam kết" nếu bạn muốn.

Ngoài ra, bạn có thể loại bỏ hai cam kết back-to-back trong nhánh chính của bạn ngay bây giờ. Chỉ cần cập nhật chi nhánh chính của bạn là:

git checkout master
git reset --hard origin/master (CAUTION: This command will remove any local changes to your master branch)
git pull

0

Nếu bạn muốn kết hợp hai cam kết gần đây nhất và chỉ sử dụng thông điệp của cam kết cũ hơn, bạn có thể tự động hóa quy trình bằng cách sử dụng expect.

Tôi giả sử:

  • Bạn đang sử dụng vi làm biên tập viên của mình
  • Cam kết của bạn là một dòng mỗi

Tôi đã thử nghiệm với git version 2.14.3 (Apple Git-98).


#!/usr/bin/env expect
spawn git rebase -i HEAD~2

# change the second "pick" to "squash"
# down, delete word, insert 's' (for squash), Escape, save and quit
send "jdwis \033:wq\r"

expect "# This is a"

# skip past first commit message (assumed to be one line), delete rest of file
# down 4, delete remaining lines, save and quit
send "4jdG\r:wq\r"

interact

Không rõ kịch bản của bạn làm gì.
buhtz

@buhtz Mình thêm bình luận thêm. Hãy cho tôi biết nếu bạn vẫn thấy nó khó hiểu, và nếu vậy, phần nào.
erwaman

Hiện vẫn chưa rõ những gì bạn nhìn thấy. Cũng expectkhông được mô tả.
buhtz

@buhtz phần nào không rõ ràng? Tôi đã cung cấp một liên kết đến một trang với nhiều tài liệu hơn cho expect.
erwaman

Một mô tả chung của kịch bản bị thiếu. Không rõ nó làm gì. Không một phần nào là không rõ ràng. Ý định của chính kịch bản không rõ ràng.
buhtz
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.