Squash X cuối cùng của tôi cam kết với nhau bằng Git


3589

Làm cách nào tôi có thể nén các cam kết X cuối cùng của mình thành một cam kết bằng Git?




2
@matt TortoiseGit là công cụ của bạn. Nó cung cấp một chức năng duy nhất "Kết hợp với một cam kết" sẽ tự động gọi tất cả các bước trong nền. Thật không may, chỉ có sẵn cho Windows. Xem câu trả lời của tôi dưới đây.
Matthias M

1
Để
xóa sổ

1
bài squash một người cần phải thực hiện lực đẩy stackoverflow.com/questions/10298291/ trên
vikramvi

Câu trả lời:


2091

Sử dụng git rebase -i <after-this-commit>và thay thế "chọn" trên lần xác nhận thứ hai và tiếp theo bằng "squash" hoặc "fixup", như được mô tả trong hướng dẫn .

Trong ví dụ này, <after-this-commit>là hàm băm SHA1 hoặc vị trí tương đối từ đầu của nhánh hiện tại mà từ đó các cam kết được phân tích cho lệnh rebase. Ví dụ: nếu người dùng muốn xem 5 lần xác nhận từ CHÍNH hiện tại trong quá khứ thì lệnh là git rebase -i HEAD~5.


260
Điều này, tôi nghĩ rằng, trả lời câu hỏi này tốt hơn một chút stackoverflow.com/a/5201642/295797
Roy Truelove

92
Có nghĩa là <after-this-commit>gì?
2540625

43
<after-this-commit>là cam kết X + 1 tức là cha mẹ của cam kết cũ nhất mà bạn muốn xóa.
joozek

339
Tôi thấy câu trả lời này quá ngắn gọn để hiểu rõ ràng. Một ví dụ sẽ có ích.
Ian Ollmann

54
Sự khác biệt giữa rebase -iphương pháp này và reset --softlà, rebase -icho phép tôi giữ lại tác giả cam kết, trong khi reset --softcho phép tôi đề xuất. Đôi khi tôi cần phải xóa các xác nhận của các yêu cầu kéo mà vẫn duy trì thông tin tác giả. Đôi khi tôi cần phải thiết lập lại mềm trên cam kết của riêng tôi. Upvote cho cả hai câu trả lời tuyệt vời dù sao.
zionyx

3832

Bạn có thể làm điều này khá dễ dàng mà không cần git rebasehoặc git merge --squash. Trong ví dụ này, chúng tôi sẽ xóa 3 cam kết cuối cùng.

Nếu bạn muốn viết thông điệp cam kết mới từ đầu, điều này đủ:

git reset --soft HEAD~3 &&
git commit

Nếu bạn muốn bắt đầu chỉnh sửa thông điệp cam kết mới bằng cách ghép các thông điệp cam kết hiện có (nghĩa là tương tự với git rebase -idanh sách hướng dẫn chọn / squash / squash / chế độ / squash sẽ bắt đầu với bạn), thì bạn cần trích xuất các tin nhắn đó và vượt qua họ để git commit:

git reset --soft HEAD~3 && 
git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"

Cả hai phương thức này đều nén ba cam kết cuối cùng thành một cam kết mới theo cùng một cách. Thiết lập lại mềm chỉ trỏ lại TRƯỚC vào cam kết cuối cùng mà bạn không muốn xóa. Cả chỉ mục và cây làm việc đều không được chạm vào thiết lập lại mềm, để lại chỉ mục ở trạng thái mong muốn cho cam kết mới của bạn (nghĩa là nó đã có tất cả các thay đổi từ các cam kết mà bạn sắp vứt bỏ đi).


163
Hà! Tôi thích phương pháp này. Nó là một trong những đóng cửa cho tinh thần của vấn đề. Thật đáng tiếc khi nó đòi hỏi rất nhiều voodoo. Một cái gì đó như thế này nên được thêm vào một trong những lệnh cơ bản. Có thể git rebase --squash-recent, hoặc thậm chí git commit --amend-many.
Adrian Ratnapala

13
@ABB: Nếu chi nhánh của bạn có một bộ dòng ngược dòng, thì bạn có thể sử dụng branch@{upstream}(hoặc chỉ @{upstream}cho chi nhánh hiện tại; trong cả hai trường hợp, phần cuối cùng có thể được viết tắt thành @{u}; xem phần chính xác ). Điều này có thể khác với lần đẩy cuối cùng của bạn (ví dụ: nếu ai đó đã đẩy thứ gì đó được xây dựng trên đỉnh đẩy gần đây nhất của bạn và sau đó bạn đã lấy nó), nhưng có vẻ như nó có thể gần với những gì bạn muốn.
Chris Johnsen

104
Kiểu này đòi hỏi tôi push -fnhưng nếu không thì thật đáng yêu, cảm ơn.
2rs2ts

39
@ 2rs2ts git đẩy -f âm thanh nguy hiểm. Hãy cẩn thận để chỉ cam kết địa phương cam kết. Không bao giờ chạm vào cam kết đẩy!
Matthias M

20
Tôi cũng cần phải sử dụng git push --forcesau đó để có được cam kết
Zach Saucier

740

Bạn có thể sử dụng git merge --squashcho điều này, đó là một chút thanh lịch hơn git rebase -i. Giả sử bạn là chủ và bạn muốn kết hợp 12 lần cam kết cuối cùng thành một.

CẢNH BÁO: Trước tiên, hãy đảm bảo rằng bạn cam kết kiểm tra công việc của mình git statussạch sẽ (vì git reset --hardsẽ loại bỏ các thay đổi được dàn dựng và không theo giai đoạn)

Sau đó:

# Reset the current branch to the commit just before the last 12:
git reset --hard HEAD~12

# HEAD@{1} is where the branch was just before the previous command.
# This command sets the state of the index to be as it would just
# after a merge from that commit:
git merge --squash HEAD@{1}

# Commit those squashed changes.  The commit message will be helpfully
# prepopulated with the commit messages of all the squashed commits:
git commit

Các tài liệu đểgit merge mô tả các --squashtùy chọn chi tiết hơn.


Cập nhật: lợi thế thực sự duy nhất của phương pháp này so với git reset --soft HEAD~12 && git commitđề xuất đơn giản hơn của Chris Johnsen trong câu trả lời của anh ấy là bạn nhận được thông điệp cam kết được chuẩn bị trước với mọi thông điệp cam kết mà bạn đang sử dụng.


16
Bạn nói điều này là 'thanh lịch' hơn git rebase -i, nhưng bạn không đưa ra lý do tại sao. Dự kiến ​​-1 điều này bởi vì dường như với tôi rằng thực tế thì điều ngược lại là đúng và đây là một vụ hack; không phải bạn đang thực hiện nhiều lệnh hơn mức cần thiết chỉ để bắt buộc git mergethực hiện một trong những điều git rebaseđược thiết kế riêng cho?
Mark Amery

77
@Mark Amery: Có nhiều lý do mà tôi nói rằng điều này thanh lịch hơn. Ví dụ, nó không liên quan đến việc sinh ra một trình soạn thảo một cách không cần thiết và sau đó tìm kiếm và thay thế một chuỗi trong tệp "việc cần làm". Sử dụng git merge --squashcũng dễ dàng hơn để sử dụng trong một kịch bản. Về cơ bản, lý do là bạn không cần "tính tương tác" của git rebase -iviệc này.
Mark Longair

12
Một lợi thế khác git merge --squashlà ít có khả năng tạo ra xung đột hợp nhất khi đối mặt với việc di chuyển / xóa / đổi tên so với việc khởi động lại, đặc biệt nếu bạn hợp nhất từ ​​một chi nhánh địa phương. (từ chối trách nhiệm: chỉ dựa trên một kinh nghiệm, hãy sửa cho tôi nếu điều này không đúng trong trường hợp chung!)
Cheezmeister

2
Tôi luôn rất miễn cưỡng khi đặt lại cứng - Tôi sẽ sử dụng thẻ tạm thời thay vì HEAD@{1}chỉ để ở bên an toàn, ví dụ như khi quy trình làm việc của bạn bị gián đoạn trong một giờ do mất điện, v.v.
Tobias Kienzler

8
@BT: Phá hủy cam kết của bạn? :( Tôi không chắc ý của bạn là gì. Mọi thứ bạn đã cam kết bạn sẽ dễ dàng lấy lại được từ reflog của git. Nếu bạn có công việc không được cam kết, nhưng các tệp đã được phân loại, bạn vẫn có thể nhận được nội dung của họ trở lại, mặc dù đó sẽ là công việc nhiều hơn . Tuy nhiên, nếu công việc của bạn thậm chí không được dàn dựng, tôi sợ rằng có rất ít việc có thể làm được, đó là lý do tại sao câu trả lời nói thẳng: "Trước tiên hãy kiểm tra xem trạng thái git có sạch không (kể từ khi thiết lập lại git - sẽ bỏ đi các thay đổi được dàn dựng và không theo giai đoạn) ".
Mark Longair

218

Tôi khuyên bạn nên tránh git resetkhi có thể - đặc biệt là đối với người mới chơi Git. Trừ khi bạn thực sự cần tự động hóa một quy trình dựa trên một số cam kết, còn có một cách ít kỳ lạ hơn ...

  1. Đặt các cam kết bị đè bẹp lên một nhánh làm việc (nếu chúng chưa có) - sử dụng gitk cho việc này
  2. Kiểm tra nhánh mục tiêu (ví dụ: 'master')
  3. git merge --squash (working branch name)
  4. git commit

Thông điệp cam kết sẽ được chuẩn bị trước dựa trên bí đao.


4
Đây là phương pháp an toàn nhất: không thiết lập lại mềm / cứng (!!) hoặc sử dụng từ chối!
TeChn4K

14
Sẽ thật tuyệt nếu bạn mở rộng trên (1).
Adam

2
@Adam: Về cơ bản, điều này có nghĩa là sử dụng giao diện GUI của gitkđể gắn nhãn cho dòng mã mà bạn đang đè bẹp và cũng gắn nhãn cho cơ sở để đè bẹp. Trong trường hợp bình thường, cả hai nhãn này sẽ tồn tại, vì vậy bước (1) có thể được bỏ qua.
tộc

3
Lưu ý rằng phương pháp này không đánh dấu nhánh làm việc là được hợp nhất hoàn toàn, vì vậy loại bỏ nó đòi hỏi phải xóa. :(
Kyrstellaine

2
Đối với (1), tôi đã tìm thấy git branch your-feature && git reset --hard HEAD~Ncách thuận tiện nhất. Tuy nhiên, nó liên quan đến việc thiết lập lại git một lần nữa, điều mà câu trả lời này đã cố gắng tránh.
eis

134

Dựa trên câu trả lời của Chris Johnsen ,

Thêm bí danh "squash" toàn cầu từ bash: (hoặc Git Bash trên Windows)

git config --global alias.squash '!f(){ git reset --soft HEAD~${1} && git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"; };f'

... hoặc sử dụng Dấu nhắc lệnh của Windows:

git config --global alias.squash "!f(){ git reset --soft HEAD~${1} && git commit --edit -m\"$(git log --format=%B --reverse HEAD..HEAD@{1})\"; };f"


~/.gitconfigBây giờ bạn nên chứa bí danh này:

[alias]
    squash = "!f(){ git reset --soft HEAD~${1} && git commit --edit -m\"$(git log --format=%B --reverse HEAD..HEAD@{1})\"; };f"


Sử dụng:

git squash N

... Bao gồm tự động nén các Ncam kết cuối cùng , bao gồm.

Lưu ý: Thông báo cam kết kết quả là sự kết hợp của tất cả các cam kết bị nén, theo thứ tự. Nếu bạn không hài lòng với điều đó, bạn luôn có thể git commit --amendsửa đổi nó bằng tay. (Hoặc, chỉnh sửa bí danh để phù hợp với sở thích của bạn.)


6
Thú vị, nhưng tôi muốn tự mình gõ tin nhắn cam kết bị đè bẹp, như một bản tóm tắt mô tả về nhiều cam kết của tôi, hơn là nó tự động nhập cho tôi. Vì vậy, tôi muốn xác định rõ hơn git squash -m "New summary."và đã Nxác định tự động là số lần xác nhận chưa được xử lý.
Acumenus

1
@ABB, Điều này nghe giống như một câu hỏi riêng biệt. (Tôi không nghĩ đó là chính xác những gì OP đã hỏi, tôi chưa bao giờ cảm thấy một nhu cầu cho nó trong tôi git squash công việc.)
EthanB

3
Điều này là khá ngọt ngào. Cá nhân tôi muốn một phiên bản sử dụng thông điệp cam kết từ lần đầu tiên của các cam kết cùng nhau. Sẽ tốt cho những thứ như điều chỉnh khoảng trắng.
funroll

@funroll Đồng ý. Chỉ cần bỏ thông điệp cam kết cuối cùng là một nhu cầu siêu phổ biến đối với tôi. Chúng ta có thể nghĩ ra rằng ...
Steve Clay

2
@ABB bạn có thể sử dụng git commit --amendđể tiếp tục thay đổi tin nhắn, nhưng bí danh này cho phép bạn có một khởi đầu tốt về những gì nên có trong thông điệp cam kết.
bảnh bao

131

Nhờ bài đăng trên blog tiện dụng này, tôi thấy rằng bạn có thể sử dụng lệnh này để xóa 3 lần xác nhận cuối cùng:

git rebase -i HEAD~3

Điều này rất hữu ích vì nó hoạt động ngay cả khi bạn ở một chi nhánh địa phương không có thông tin theo dõi / repo từ xa.

Lệnh sẽ mở trình soạn thảo rebase tương tác, sau đó cho phép bạn sắp xếp lại, squash, tua lại, v.v. như bình thường.


Sử dụng trình soạn thảo rebase tương tác:

Trình soạn thảo rebase tương tác hiển thị ba lần xác nhận cuối cùng. Ràng buộc này được xác định bởi HEAD~3khi chạy lệnh git rebase -i HEAD~3.

Cam kết gần đây nhất HEAD, được hiển thị đầu tiên trên dòng 1. Các dòng bắt đầu bằng một #nhận xét / tài liệu.

Các tài liệu hiển thị là khá rõ ràng. Trên bất kỳ dòng nào, bạn có thể thay đổi lệnh từ pickthành lệnh bạn chọn.

Tôi thích sử dụng lệnh fixupnày vì "ép" các thay đổi của cam kết thành cam kết trên dòng trên và loại bỏ thông điệp của cam kết.

Như cam kết trên dòng 1 là HEAD, trong hầu hết các trường hợp, bạn sẽ để nó như là pick. Bạn không thể sử dụng squashhoặc fixupvì không có cam kết nào khác để ép cam kết vào.

Bạn cũng có thể thay đổi thứ tự của các cam kết. Điều này cho phép bạn squash hoặc sửa lỗi cam kết không liền kề theo thời gian.

tương tác biên tập rebase


Một ví dụ thực tế hàng ngày

Gần đây tôi đã cam kết một tính năng mới. Kể từ đó, tôi đã cam kết hai bản sửa lỗi. Nhưng bây giờ tôi đã phát hiện ra một lỗi (hoặc có thể chỉ là lỗi chính tả) trong tính năng mới mà tôi đã cam kết. Thật khó chịu! Tôi không muốn một cam kết mới làm ô nhiễm lịch sử cam kết của tôi!

Điều đầu tiên tôi làm là sửa lỗi và đưa ra một cam kết mới với nhận xét squash this into my new feature!.

Sau đó tôi chạy git loghoặc gitklấy SHA cam kết của tính năng mới (trong trường hợp này 1ff9460).

Tiếp theo, tôi đưa lên trình soạn thảo rebase tương tác với git rebase -i 1ff9460~. Các ~sau khi cam kết SHA nói với trình biên tập bao gồm cam kết rằng trong trình soạn thảo.

Tiếp theo, tôi di chuyển cam kết chứa fix ( fe7f1e0) sang bên dưới cam kết tính năng và thay đổi pickthành fixup.

Khi đóng trình chỉnh sửa, bản sửa lỗi sẽ được nén vào cam kết tính năng và lịch sử cam kết của tôi sẽ trông đẹp và sạch sẽ!

Điều này hoạt động tốt khi tất cả các xác nhận là cục bộ, nhưng nếu bạn cố gắng thay đổi bất kỳ cam kết nào đã được đẩy sang điều khiển từ xa, bạn thực sự có thể gây ra sự cố cho các nhà phát triển khác đã kiểm tra cùng chi nhánh!

nhập mô tả hình ảnh ở đây


5
Bạn có phải chọn một trong những đầu và đè bẹp phần còn lại? Bạn nên chỉnh sửa câu trả lời của mình để giải thích cách sử dụng trình soạn thảo rebase tương tác chi tiết hơn
Kolob Canyon

2
Có, để lại picktrong dòng 1. Nếu bạn chọn squashhoặc fixupcho cam kết trên dòng 1, git sẽ hiển thị thông báo "lỗi: không thể 'sửa lỗi" mà không có cam kết trước đó ". Sau đó, nó sẽ cung cấp cho bạn tùy chọn để sửa nó: "Bạn có thể sửa lỗi này bằng 'git rebase --edit-todo' và sau đó chạy 'git rebase --continue'." hoặc bạn chỉ có thể hủy bỏ và bắt đầu lại: "Hoặc bạn có thể hủy bỏ cuộc nổi loạn với 'git rebase --abort'.".
br3nt

56

Nếu bạn sử dụng TortoiseGit, bạn có thể thực hiện chức năng Combine to one commit:

  1. Mở menu ngữ cảnh TortoiseGit
  2. Lựa chọn Show Log
  3. Đánh dấu các cam kết có liên quan trong chế độ xem nhật ký
  4. Chọn Combine to one committừ menu ngữ cảnh

Kết hợp các cam kết

Hàm này tự động thực hiện tất cả các bước git đơn cần thiết. Đáng tiếc chỉ có sẵn cho Windows.


Theo như tôi biết, điều này sẽ không hoạt động đối với các cam kết hợp nhất.
Thorkil Holm-Jacobsen

1
Mặc dù nó không được bình luận bởi bất kỳ ai khác, nhưng điều này thậm chí hoạt động cho các cam kết không phải ở ĐẦU. Ví dụ, nhu cầu của tôi là xóa sạch một số cam kết WIP mà tôi đã làm với một mô tả lành mạnh hơn trước khi đẩy. Làm việc rất đẹp. Tất nhiên, tôi vẫn hy vọng tôi có thể học cách làm điều đó bằng các lệnh.
Charles Roberto Canato

55

Để làm điều này, bạn có thể sử dụng lệnh git sau đây.

 git rebase -i HEAD~n

n (= 4 ở đây) là số lần xác nhận cuối cùng. Sau đó, bạn có các tùy chọn sau,

pick 01d1124 Message....
pick 6340aaa Message....
pick ebfd367 Message....
pick 30e0ccb Message....

Cập nhật như bên dưới pickmột cam kết và squashnhững người khác vào gần đây nhất,

p 01d1124 Message....
s 6340aaa Message....
s ebfd367 Message....
s 30e0ccb Message....

Để biết chi tiết, nhấp vào Liên kết


48

Dựa trên bài viết này tôi thấy phương pháp này dễ dàng hơn cho usecase của tôi.

Chi nhánh 'dev' của tôi đã đi trước 'origin / dev' bởi 96 lần xác nhận (vì vậy những cam kết này chưa được đẩy đến điều khiển từ xa).

Tôi muốn dẹp những cam kết này thành một trước khi thay đổi. Tôi đặt trước để đặt lại chi nhánh về trạng thái 'origin / dev' (điều này sẽ để lại tất cả các thay đổi từ 96 lần xác nhận không được thực hiện) và sau đó cam kết các thay đổi cùng một lúc:

git reset origin/dev
git add --all
git commit -m 'my commit message'

1
Đúng thứ tôi cần. Giảm bớt các cam kết từ nhánh tính năng của tôi, và sau đó tôi git cherry pick mà cam kết với chủ của mình.
David Victor

1
Điều này không đè bẹp các cam kết trước đó!
IgorGanapolsky

bạn có thể nói rõ hơn một chút về @igorGanapolsky không?
trudolf

3
@trudolf Đây không thực sự là bẹp (chọn cá nhân cam kết với squash). Đây là nhiều hơn cam kết tất cả các thay đổi của bạn cùng một lúc.
IgorGanapolsky

15
vâng, do đó, nó nén tất cả các cam kết của bạn thành một. Xin chúc mừng!
trudolf 4/12/2015

45

Trong nhánh bạn muốn kết hợp các cam kết trên, hãy chạy:

git rebase -i HEAD~(n number of commits back to review)

thí dụ:

git rebase -i HEAD~1

Điều này sẽ mở trình soạn thảo văn bản và bạn phải chuyển 'chọn' trước mỗi cam kết với 'squash' nếu bạn muốn các cam kết này được hợp nhất với nhau. Từ tài liệu:

p, chọn = sử dụng cam kết

s, squash = sử dụng cam kết, nhưng kết hợp thành cam kết trước đó

Ví dụ: nếu bạn đang tìm cách hợp nhất tất cả các cam kết thành một, 'chọn' là cam kết đầu tiên bạn thực hiện và tất cả các cam kết trong tương lai (được đặt bên dưới đầu tiên) nên được đặt thành 'squash'. Nếu sử dụng vim, hãy sử dụng : x trong chế độ chèn để lưu và thoát trình chỉnh sửa.

Sau đó để tiếp tục cuộc nổi loạn:

git rebase --continue

Để biết thêm về điều này và các cách khác để viết lại lịch sử cam kết của bạn, hãy xem bài đăng hữu ích này


2
Xin vui lòng giải thích những gì --continuevà vim :xlàm.
not2qubit

Việc rebase sẽ xảy ra trong các khối khi nó đi qua các xác nhận trên nhánh của bạn, sau khi bạn git addcấu hình đúng trong các tệp bạn sử dụng git rebase --continueđể chuyển sang cam kết tiếp theo và bắt đầu hợp nhất. :xlà một lệnh sẽ lưu các thay đổi của tệp khi sử dụng vim, hãy xem điều này
aabiro

32

Câu trả lời của Anomies là tốt, nhưng tôi cảm thấy không an toàn về điều này nên tôi quyết định thêm một vài ảnh chụp màn hình.

Bước 0: nhật ký git

Xem bạn đang ở đâu với git log. Quan trọng nhất, hãy tìm hàm băm cam kết của lần xác nhận đầu tiên mà bạn không muốn ép. Vì vậy, chỉ có:

nhập mô tả hình ảnh ở đây

Bước 1: git rebase

Thực thi git rebase -i [your hash], trong trường hợp của tôi:

$ git rebase -i 2d23ea524936e612fae1ac63c95b705db44d937d

Bước 2: chọn / ép những gì bạn muốn

Trong trường hợp của tôi, tôi muốn xóa sạch mọi thứ trên cam kết lần đầu tiên. Thứ tự là từ đầu đến cuối, vì vậy chính xác theo cách khác như trong git log. Trong trường hợp của tôi, tôi muốn:

nhập mô tả hình ảnh ở đây

Bước 3: Điều chỉnh tin nhắn

Nếu bạn chỉ chọn một cam kết và đè bẹp phần còn lại, bạn có thể điều chỉnh một thông báo cam kết:

nhập mô tả hình ảnh ở đây

Đó là nó. Khi bạn lưu cái này ( :wq), bạn đã hoàn thành. Có một cái nhìn với nó git log.


2
sẽ rất tuyệt khi thấy kết quả cuối cùng, ví dụ,git log
Timothy LJ Stewart

2
Không, cám ơn. Đây chính xác là cách tôi Iost cam kết của tôi.
Axalix

@Axalix Bạn đã xóa tất cả các dòng của bạn? Đó là cách bạn đánh mất cam kết của mình.
a3y3

31

Thủ tục 1

1) Xác định hàm băm ngắn

# git log --pretty=oneline --abbrev-commit
abcd1234 Update to Fix for issue B
cdababcd Fix issue B
deab3412 Fix issue A
....

Ở đây thậm chí git log --onelinecũng có thể được sử dụng để có được băm ngắn.

2) Nếu bạn muốn squash (hợp nhất) hai lần xác nhận cuối cùng

# git rebase -i deab3412 

3) Điều này mở ra một nanotrình soạn thảo để hợp nhất. Và nó trông như dưới đây

....
pick cdababcd Fix issue B
pick abcd1234 Update to Fix for issue B
....

4) Đổi tên từ pickđể squashmà có mặt trước abcd1234. Sau khi đổi tên nó sẽ giống như dưới đây.

....
pick cdababcd Fix issue B
squash abcd1234 Update to Fix for issue B
....

5) Bây giờ lưu và đóng nanotrình chỉnh sửa. Nhấn ctrl + ovà nhấn Enterđể lưu. Và sau đó nhấn ctrl + xđể thoát khỏi trình soạn thảo.

6) Sau đó, nanobiên tập viên lại mở để cập nhật ý kiến, nếu cần cập nhật.

7) Bây giờ nó bị đè bẹp thành công, bạn có thể xác minh nó bằng cách kiểm tra nhật ký.

# git log --pretty=oneline --abbrev-commit
1122abcd Fix issue B
deab3412 Fix issue A
....

8) Bây giờ đẩy để repo. Lưu ý để thêm +dấu trước tên chi nhánh. Điều này có nghĩa là buộc phải đẩy.

# git push origin +master

Lưu ý: Điều này dựa trên việc sử dụng git trên ubuntushell. Nếu bạn đang sử dụng các os ( Windowshoặc Mac) khác nhau thì các lệnh trên đều giống nhau ngoại trừ trình soạn thảo. Bạn có thể nhận được trình soạn thảo khác nhau.

Thủ tục 2

  1. Đầu tiên thêm các tệp cần thiết cho cam kết
git add <files>
  1. Sau đó, cam kết sử dụng --fixuptùy chọn và OLDCOMMITnên trên đó chúng ta cần hợp nhất (squash) cam kết này.
git commit --fixup=OLDCOMMIT

Bây giờ điều này tạo ra một cam kết mới trên đầu trang với fixup1 <OLDCOMMIT_MSG>.

  1. Sau đó thực hiện lệnh bên dưới để hợp nhất (squash) cam kết mới với OLDCOMMIT.
git rebase --interactive --autosquash OLDCOMMIT^

Ở đây ^có nghĩa là cam kết trước đó OLDCOMMIT. rebaseLệnh này mở cửa sổ tương tác trên trình soạn thảo (vim hoặc nano) mà chúng ta không cần phải làm gì chỉ cần lưu và thoát là đủ. Bởi vì tùy chọn được chuyển cho điều này sẽ tự động chuyển cam kết mới nhất sang bên cạnh cam kết cũ và thay đổi thao tác thành fixup(tương đương với squash). Sau đó rebase tiếp tục và kết thúc.

Thủ tục 3

  1. Nếu cần thêm các thay đổi mới cho các phương tiện cam kết cuối cùng có --amendthể được sử dụng với git-commit.
    # git log --pretty=oneline --abbrev-commit
    cdababcd Fix issue B
    deab3412 Fix issue A
    ....
    # git add <files> # New changes
    # git commit --amend
    # git log --pretty=oneline --abbrev-commit
    1d4ab2e1 Fix issue B
    deab3412 Fix issue A
    ....  

Ở đây --amendhợp nhất các thay đổi mới với lần xác nhận cuối cùng cdababcdvà tạo ID cam kết mới1d4ab2e1

Phần kết luận

  • Ưu điểm của thủ tục thứ 1 là xóa sổ nhiều lần xác nhận và sắp xếp lại. Nhưng thủ tục này sẽ khó khăn nếu chúng ta cần hợp nhất một bản sửa lỗi cho cam kết rất cũ.
  • Vì vậy, thủ tục thứ 2 giúp hợp nhất cam kết với cam kết rất cũ một cách dễ dàng.
  • Và thủ tục thứ 3 rất hữu ích trong trường hợp xóa sạch một thay đổi mới đối với lần xác nhận cuối cùng.

Nó chỉ cập nhật hai lần xác nhận cuối cùng ngay cả khi tôi đặt lại thành Id cam kết thành lần xác nhận cuối cùng thứ 6, không biết tại sao
Carlos Liu

Thậm chí bạn có thể sắp xếp lại thứ tự cam kết. Nó hoạt động tốt.
rashok

29

Để xóa 10 lần xác nhận cuối cùng thành 1 lần xác nhận duy nhất:

git reset --soft HEAD~10 && git commit -m "squashed commit"

Nếu bạn cũng muốn cập nhật chi nhánh từ xa với cam kết bị đè bẹp:

git push -f

--force rất nguy hiểm khi nhiều người đang làm việc trên một chi nhánh được chia sẻ vì nó cập nhật từ xa với bản sao cục bộ của bạn. - Force-with-cho thuê có thể tốt hơn vì nó đảm bảo rằng điều khiển từ xa không có cam kết từ người khác kể từ lần cuối bạn tải nó.
bharath

27

Nếu bạn ở trên một nhánh từ xa (được gọi feature-branch) được sao chép từ Kho lưu trữ vàng ( golden_repo_name), thì đây là kỹ thuật để nén các cam kết của bạn thành một:

  1. Kiểm tra repo vàng

    git checkout golden_repo_name
    
  2. Tạo một nhánh mới từ nó (repo vàng) như sau

    git checkout -b dev-branch
    
  3. Squash hợp nhất với chi nhánh địa phương của bạn mà bạn đã có

    git merge --squash feature-branch
    
  4. Cam kết các thay đổi của bạn (đây sẽ là cam kết duy nhất có trong nhánh dev)

    git commit -m "My feature complete"
    
  5. Đẩy chi nhánh vào kho lưu trữ cục bộ của bạn

    git push origin dev-branch
    

Vì tôi vừa mới bẹp ~ 100 lần xác nhận (để đồng bộ hóa các nhánh svn thông qua git-svn), điều này nhanh hơn nhiều so với việc khởi động lại tương tác!
hiền nhân

1
Đọc xuống, tôi thấy bình luận của @ Chris, đó là những gì tôi đã từng làm (rebase --soft ...) - thật tệ khi stackoverflow không còn đặt câu trả lời với hàng trăm lượt upvote ở đầu ...
sage

1
đồng ý với bạn @sage, hãy hy vọng họ có thể làm điều đó vào lúc nào đó trong tương lai
Sandesh Kumar

Đây là cách đúng đắn. Phương pháp Rebase là tốt, nhưng chỉ nên được sử dụng cho bí đao như một giải pháp cuối cùng.
Axalix

20

Điều gì có thể thực sự thuận tiện:
Tìm hàm băm cam kết bạn muốn đè lên trên, nói d43e15.

Bây giờ sử dụng

git reset d43e15
git commit -am 'new commit name'

2
Điều này. Tại sao không có nhiều người sử dụng điều này? Đó là cách nhanh hơn so với việc nổi loạn và đè bẹp từng cam kết.
a3y3

17

Đây là siêu nhân đôi, nhưng theo một cách hay, vì vậy tôi sẽ ném nó vào vòng:

GIT_EDITOR='f() { if [ "$(basename $1)" = "git-rebase-todo" ]; then sed -i "2,\$s/pick/squash/" $1; else vim $1; fi }; f' git rebase -i foo~5 foo

Dịch: cung cấp một "trình soạn thảo" mới cho git, nếu tên tệp cần chỉnh sửa là git-rebase-todo(dấu nhắc phản hồi tương tác) thay đổi tất cả trừ "chọn" đầu tiên thành "squash", và nếu không thì sẽ sinh ra vim - để khi bạn được nhắc để chỉnh sửa thông điệp cam kết bị đè bẹp, bạn nhận được vim. (Và rõ ràng là tôi đã đè bẹp năm lần cam kết cuối cùng trên nhánh foo, nhưng bạn có thể thay đổi điều đó theo cách bạn muốn.)

Tôi có lẽ sẽ làm những gì Mark Longair đề nghị , mặc dù.


7
+1: điều đó thú vị và mang tính hướng dẫn, ở chỗ tôi không rõ ràng rằng bạn có thể đặt bất cứ điều gì phức tạp hơn tên của một chương trình trong biến môi trường GIT_EDITOR.
Mark Longair

16

Nếu bạn muốn squish mọi cam kết thành một cam kết duy nhất (ví dụ: khi phát hành dự án công khai lần đầu tiên), hãy thử:

git checkout --orphan <new-branch>
git commit

15

2020 Giải pháp đơn giản mà không cần rebase:

git reset --soft HEAD~2

git commit -m "new commit message"

git push --force

2 có nghĩa là hai cam kết cuối cùng sẽ bị nghiền nát. Bạn có thể thay thế nó bằng bất kỳ số nào


14

Tôi nghĩ rằng cách dễ nhất để làm điều này là bằng cách tạo một nhánh mới ra khỏi master và thực hiện hợp nhất --squash của nhánh tính năng.

git checkout master
git checkout -b feature_branch_squashed
git merge --squash feature_branch

Sau đó, bạn có tất cả các thay đổi đã sẵn sàng để cam kết.


12

Một lớp lót đơn giản luôn hoạt động, với điều kiện là bạn hiện đang ở nhánh bạn muốn ép, chủ là nhánh mà nó có nguồn gốc và cam kết mới nhất chứa thông điệp cam kết và tác giả bạn muốn sử dụng:

git reset --soft $(git merge-base HEAD master) && git commit --reuse-message=HEAD@{1}

4
Tôi đã hoàn toàn cáu kỉnh với sự thất vọng về các cam kết bẹp và sự phức tạp của nó ngu ngốc đến mức nào - chỉ cần sử dụng thông điệp cuối cùng và dẹp tất cả chúng thành một cam kết! Tại sao nó khó thế ???? Điều này một lót làm điều đó cho tôi. Cảm ơn bạn từ tận đáy lòng giận dữ của tôi.
Locane

12

ví dụ: nếu bạn muốn xóa 3 cam kết cuối cùng cho một cam kết duy nhất trong một nhánh (kho lưu trữ từ xa) chẳng hạn: https://bitbucket.org

Những gì tôi đã làm là

  1. thiết lập lại git - Headsoft ~ 3 &&
  2. cam kết
  3. nguồn gốc git đẩy (Branch_name) - lực lượng

3
Hãy cẩn thận, vì nếu bạn sử dụng vũ lực thì không có cách nào để lấy lại các cam kết trước đó kể từ khi bạn xóa nó
Albert Ruelan

12

CẢNH BÁO: "Cam kết X cuối cùng của tôi" có thể không rõ ràng.

  (MASTER)  
Fleetwood Mac            Fritz
      ║                    ║
  Add Danny  Lindsey     Stevie       
    Kirwan  Buckingham    Nicks                                              
      ║         ╚═══╦══════╝     
Add Christine       ║          
   Perfect      Buckingham
      ║           Nicks            
    LA1974══════════╝                                    
      ║                  
      ║                  
    Bill <══════ YOU ARE EDITING HERE
  Clinton        (CHECKED OUT, CURRENT WORKING DIRECTORY)              

Trong lịch sử viết tắt này của kho lưu trữ https://github.com/fleetwood-mac/band-history, bạn đã mở một yêu cầu kéo để hợp nhất trong cam kết của Bill Clinton vào cam kết MASTERFleetwood Mac ban đầu .

Bạn đã mở một yêu cầu kéo và trên GitHub bạn thấy điều này:

Bốn cam kết:

  • Thêm Daniel Kirwan
  • Thêm Christine hoàn hảo
  • LA1974
  • Bill Clinton

Suy nghĩ rằng không ai sẽ quan tâm để đọc toàn bộ lịch sử kho lưu trữ. (Thực sự có một kho lưu trữ, nhấp vào liên kết ở trên!) Bạn quyết định xóa sạch các cam kết này. Vì vậy, bạn đi và chạy git reset --soft HEAD~4 && git commit. Sau đó, bạn git push --forcevào GitHub để dọn dẹp PR của bạn.

Và những gì sẽ xảy ra? Bạn chỉ thực hiện một cam kết duy nhất nhận được từ Fritz đến Bill Clinton. Bởi vì bạn đã quên rằng ngày hôm qua bạn đã làm việc trên phiên bản Buckingham Nicks của dự án này. Và git logkhông khớp với những gì bạn thấy trên GitHub.

NHIỆM VỤ CỦA CÂU CHUYỆN

  1. Tìm các tập tin chính xác mà bạn muốn để có được đến , và git checkouthọ
  2. Tìm cam kết chính xác trước mà bạn muốn giữ trong lịch sử và git reset --softđó
  3. Thực hiện một git commitmà warps trực tiếp từ từ cho đến

1
Đây là 100% cách dễ nhất để làm điều này. Nếu CHÍNH hiện tại của bạn trạng thái chính xác bạn muốn, thì bạn có thể bỏ qua # 1.
Stan

Đây là cách duy nhất tôi biết cho phép viết lại lịch sử cam kết đầu tiên.
Vadorequest

9

Nếu bạn không quan tâm đến các thông điệp cam kết của các cam kết ở giữa, bạn có thể sử dụng

git reset --mixed <commit-hash-into-which-you-want-to-squash>
git commit -a --amend

7

Nếu bạn đang làm việc với GitLab, bạn có thể chỉ cần nhấp vào tùy chọn Squash trong Yêu cầu Hợp nhất như bên dưới. Thông báo cam kết sẽ là tiêu đề của Yêu cầu Hợp nhất.

nhập mô tả hình ảnh ở đây



6

Ngoài các câu trả lời xuất sắc khác, tôi muốn thêm làm thế nào git rebase -iluôn làm tôi bối rối với thứ tự cam kết - cũ hơn mới hơn hoặc ngược lại? Vì vậy, đây là quy trình làm việc của tôi:

  1. git rebase -i HEAD~[N], trong đó N là số lần xác nhận tôi muốn tham gia, bắt đầu từ lần gần đây nhất . Vì vậy, git rebase -i HEAD~5có nghĩa là "ép 5 lần cuối cùng vào một cái mới";
  2. trình chỉnh sửa bật lên, hiển thị danh sách các cam kết tôi muốn hợp nhất. Bây giờ chúng được hiển thị theo thứ tự ngược lại : cam kết cũ hơn ở trên cùng. Đánh dấu là "squash" hoặc "s" tất cả các cam kết trong đó ngoại trừ lần đầu tiên / cũ hơn : nó sẽ được sử dụng làm điểm bắt đầu. Lưu và đóng trình soạn thảo;
  3. trình chỉnh sửa bật lên một lần nữa với một thông báo mặc định cho cam kết mới: thay đổi nó theo nhu cầu của bạn, lưu và đóng. Hoàn thành bí đao!

Nguồn & đọc thêm: # 1 , # 2 .


6

Điều gì về một câu trả lời cho câu hỏi liên quan đến một quy trình công việc như thế này?

  1. nhiều cam kết cục bộ, trộn lẫn với nhiều sự hợp nhất TỪ chủ ,
  2. cuối cùng là một cú đẩy từ xa
  3. PR và hợp nhất để làm chủ bởi người đánh giá . (Vâng, nhà phát triển sẽ dễ dàng hơn merge --squashsau khi PR, nhưng nhóm nghĩ rằng điều đó sẽ làm chậm quá trình.)

Tôi chưa thấy một quy trình làm việc như thế trên trang này. (Đó có thể là đôi mắt của tôi.) Nếu tôi hiểu rebasechính xác, nhiều sự hợp nhất sẽ yêu cầu nhiều giải pháp xung đột . Tôi thậm chí không muốn nghĩ về điều đó!

Vì vậy, điều này dường như làm việc cho chúng tôi.

  1. git pull master
  2. git checkout -b new-branch
  3. git checkout -b new-branch-temp
  4. chỉnh sửa và cam kết nhiều cục bộ, hợp nhất chủ thường xuyên
  5. git checkout new-branch
  6. git merge --squash new-branch-temp // đặt tất cả các thay đổi trong giai đoạn
  7. git commit 'one message to rule them all'
  8. git push
  9. Người phản biện làm PR và sáp nhập để làm chủ.

Từ nhiều ý kiến ​​tôi thích cách tiếp cận của bạn. Thật tiện lợi và nhanh chóng
Artem Solovev

5

Tôi tìm thấy một giải pháp chung chung hơn không phải là chỉ định các cam kết 'N', mà là chi nhánh / commit-id mà bạn muốn đè lên trên. Điều này ít xảy ra lỗi hơn so với việc đếm các cam kết lên đến một cam kết cụ thể, chỉ cần xác định trực tiếp thẻ hoặc nếu bạn thực sự muốn đếm, bạn có thể chỉ định HEAD ~ N.

Trong quy trình làm việc của mình, tôi bắt đầu một chi nhánh và cam kết đầu tiên của tôi trên chi nhánh đó tóm tắt mục tiêu (nghĩa là thông thường tôi sẽ đẩy thông điệp 'cuối cùng' cho tính năng đến kho lưu trữ công cộng.) Vì vậy, khi tôi hoàn thành Tôi muốn làm là git squash masterquay lại tin nhắn đầu tiên và sau đó tôi sẵn sàng đẩy.

Tôi sử dụng bí danh:

squash = !EDITOR="\"_() { sed -n 's/^pick //p' \"\\$1\"; sed -i .tmp '2,\\$s/^pick/f/' \"\\$1\"; }; _\"" git rebase -i

Điều này sẽ loại bỏ lịch sử bị đè bẹp trước khi nó làm như vậy, điều này mang đến cho bạn cơ hội phục hồi bằng cách lấy ID xác nhận cũ ra khỏi bàn điều khiển nếu bạn muốn hoàn nguyên. (Người dùng Solaris lưu ý rằng nó sử dụng -itùy chọn GNU sed , người dùng Mac và Linux sẽ ổn với điều này.)


Tôi đã thử bí danh nhưng tôi không chắc liệu thay thế sed có ảnh hưởng gì không. Họ nên làm gì?
cơn mưa

Chiếc sed đầu tiên chỉ cần bỏ lịch sử vào giao diện điều khiển. Sed thứ hai thay thế tất cả 'pick' bằng 'f' (sửa lỗi) và viết lại tệp biên tập tại chỗ (tùy chọn -i). Vì vậy, thứ hai làm tất cả các công việc.
Ethan

Bạn đã đúng, đếm số N của các cam kết cụ thể rất dễ bị lỗi. Nó đã làm tôi khó chịu nhiều lần và lãng phí hàng giờ để cố gắng hoàn tác cuộc nổi loạn.
IgorGanapolsky

Xin chào Ethan, tôi muốn biết liệu quy trình làm việc này sẽ che giấu những xung đột có thể xảy ra khi hợp nhất. Vì vậy, hãy xem xét nếu bạn có hai nhánh chủ và nô lệ. Nếu nô lệ có xung đột với chủ và chúng tôi sử dụng git squash masterkhi chúng tôi được kiểm tra trên nô lệ. chuyện gì sẽ xảy ra chúng ta sẽ che giấu xung đột?
Sergio Bilello

@Sergio Đây là một trường hợp viết lại lịch sử, vì vậy bạn có thể sẽ có xung đột nếu bạn xóa các cam kết đã được đẩy, và sau đó cố gắng hợp nhất / khởi động lại phiên bản bị đè bẹp trở lại. (Một số trường hợp tầm thường có thể thoát khỏi nó.)
Ethan

5

Trong câu hỏi nó có thể mơ hồ những gì có nghĩa là "cuối cùng".

ví dụ git log --graphđầu ra như sau (đơn giản hóa):

* commit H0
|
* merge
|\
| * commit B0
| |
| * commit B1
| | 
* | commit H1
| |
* | commit H2
|/
|

Sau đó, lần xác nhận cuối cùng theo thời gian là H0, hợp nhất, B0. Để đè bẹp chúng, bạn sẽ phải khởi động lại nhánh đã hợp nhất của mình trên cam kết H1.

Vấn đề là H0 chứa H1 và H2 (và thường là nhiều cam kết hơn trước khi hợp nhất và sau khi phân nhánh) trong khi B0 thì không. Vì vậy, bạn phải quản lý các thay đổi từ H0, hợp nhất, H1, H2, B0 ít nhất.

Có thể sử dụng rebase nhưng theo cách khác thì các câu trả lời khác được đề cập:

rebase -i HEAD~2

Điều này sẽ cho bạn thấy các tùy chọn lựa chọn (như được đề cập trong các câu trả lời khác):

pick B1
pick B0
pick H0

Đặt bí đao thay vì chọn H0:

pick B1
pick B0
s H0

Sau khi lưu và thoát rebase sẽ áp dụng lần lượt các xác nhận sau H1. Điều đó có nghĩa là nó sẽ yêu cầu bạn giải quyết xung đột một lần nữa (trong đó ĐẦU TIÊN sẽ là H1 và sau đó tích lũy các cam kết khi chúng được áp dụng).

Sau khi rebase sẽ kết thúc, bạn có thể chọn tin nhắn cho H0 và B0 bị bẹp:

* commit squashed H0 and B0
|
* commit B1
| 
* commit H1
|
* commit H2
|

PS Nếu bạn chỉ thực hiện một số thiết lập lại thành BO: (ví dụ: sử dụng reset --mixedđược giải thích chi tiết hơn tại đây https://stackoverflow.com/a/18690845/2405850 ):

git reset --mixed hash_of_commit_B0
git add .
git commit -m 'some commit message'

sau đó bạn nén vào các thay đổi B0 của H0, H1, H2 (mất hoàn toàn cam kết cho các thay đổi sau khi phân nhánh và trước khi hợp nhất.


4

1) thiết lập lại git - ĐẦU TIÊN

n - Số lượng cam kết, cần phải đè bẹp

2) git commit -m "thông điệp cam kết mới"

3) git đẩy gốc Branch_name - Force

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.