Câu hỏi Làm thế nào để sửa đổi một cam kết quy định trong git?


Tôi thường gửi danh sách các cam kết để xem xét. Nếu tôi có:

  • HEAD 
  • Commit3 
  • Commit2 
  • Commit1

Tôi biết rằng tôi có thể sửa đổi cam kết đầu git commit --amend, nhưng làm cách nào tôi có thể sửa đổi Commit1, cho rằng nó không phải là HEAD cam kết?


1704
2017-07-27 05:19


gốc


Xem câu trả lời thay thế tại đây: stackoverflow.com/a/18150592/520567 Câu trả lời được chấp nhận của bạn thực sự là một câu trả lời chính xác cho câu hỏi của bạn nhưng nếu bạn có cam kết mới sẵn sàng trước khi quyết định sử dụng chỉnh sửa, thì câu trả lời này sẽ đơn giản hơn. Nó cũng có thể làm việc với nhiều commit bạn muốn kết hợp / squash cùng với một commit cũ hơn. - akostadinov
Bạn cũng có thể thấy Tách một cam kết trong Công cụ Git - Lịch sử viết lại để biết thêm thông tin. - hakre
Có thể trùng lặp Làm cách nào để sửa đổi các cam kết hiện có, chưa được xóa? - tkruse


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


Bạn có thể sử dụng git rebase, ví dụ, nếu bạn muốn sửa đổi lại thành commit bbc643cd, chạy

$ git rebase --interactive 'bbc643cd^'

Trong trình chỉnh sửa mặc định, sửa đổi pick đến edit trong dòng có cam kết bạn muốn sửa đổi. Thực hiện các thay đổi của bạn và sau đó cam kết chúng với cùng một thông báo bạn đã có trước đây:

$ git commit --all --amend --no-edit

để sửa đổi cam kết và sau đó

$ git rebase --continue

để quay trở lại cam kết trước đó.

CẢNH BÁO: Lưu ý rằng điều này sẽ thay đổi SHA-1 của cam kết đó cũng như tất cả trẻ em - nói cách khác, điều này viết lại lịch sử từ thời điểm đó về phía trước. Bạn có thể phá vỡ repos làm điều này nếu bạn đẩy bằng lệnh git push --force


2240
2017-07-27 05:28



Một tùy chọn thú vị khác trong luồng này là khi bạn đã chuyển sang commit bạn muốn sửa đổi, thay vì sửa đổi các tập tin và được nhúng trên cam kết trên đầu trang (cái bạn đang chỉnh sửa), bạn có thể muốn phân chia commit đó thành hai commit khác nhau (hoặc thậm chí nhiều hơn). Trong trường hợp đó, hãy quay lại cam kết chỉnh sửa và chạy "git reset HEAD ^". sẽ đưa các tệp đã sửa đổi của cam kết đó vào giai đoạn. Bây giờ chọn và cam kết bất kỳ tập tin nào bạn muốn. Luồng này được giải thích khá rõ trong trang "git-rebase". Xem phần "Tách các cam kết". bit.ly/d50w1M - Diego Pino
Trong Git 1.6.6 và mới hơn, bạn có thể sử dụng reword hành động trong git rebase -i thay vì edit (nó sẽ tự động mở trình soạn thảo và tiếp tục với phần còn lại của các bước rebase, điều này sẽ làm giảm bớt việc sử dụng git commit --ammend và git rebase --continue khi bạn chỉ cần thay đổi thông báo cam kết chứ không phải nội dung). - Chris Johnsen
Sau khi chạy 'git rebase hash ^ --interactive', sau đó đánh dấu chỉnh sửa trên cam kết, 'git commit --amend' chỉ hiển thị thông điệp cam kết - không phải mã thực. Làm thế nào tôi có thể thay đổi mã đã được cam kết? Cảm ơn! - mikemaccana
Cần lưu ý rằng bạn có thể cần phải chạy git stash trước git rebase và git stash pop sau đó, nếu bạn có các thay đổi đang chờ xử lý. - user123444555621
Lưu ý rằng với git mới hơn, nó sẽ khôn ngoan hơn để làm theo các chỉ dẫn nhắc thay vì sử dụng một cách mù quáng git commit --all --amend --no-edit đây. Tất cả những gì tôi phải làm sau git rebase -i ... là git commit --amend bình thường rồi git rebase --continue. - Eric Chen


Sử dụng tính năng rebase tương tác tuyệt vời:

git rebase -i @~9   # Show the last 9 commits in a text editor

Tìm cam kết bạn muốn, thay đổi pick đến e (edit) và lưu và đóng tệp. Git sẽ tua lại cam kết đó, cho phép bạn:

  • sử dụng git commit --amend để thực hiện thay đổi hoặc
  • sử dụng git reset @~ để loại bỏ cam kết cuối cùng, nhưng không phải là thay đổi đối với tệp (nghĩa là đưa bạn đến điểm bạn đã ở khi bạn chỉnh sửa tệp nhưng chưa cam kết).

Cách thứ hai là hữu ích để thực hiện các công cụ phức tạp hơn như chia nhỏ thành nhiều lần commit.

Sau đó chạy git rebase --continuevà Git sẽ phát lại các thay đổi tiếp theo trên đầu trang của cam kết đã sửa đổi của bạn. Bạn có thể được yêu cầu sửa một số xung đột hợp nhất.

Chú thích: @ là viết tắt của HEAD~ là cam kết trước cam kết được chỉ định.

Đọc thêm về viết lại lịch sử trong tài liệu Git.


Đừng sợ phải rebase

ProTip: Đừng ngại thử nghiệm các lệnh "nguy hiểm" viết lại lịch sử * - Git không xóa cam kết của bạn trong 90 ngày theo mặc định; bạn có thể tìm thấy chúng trong reflog:

$ git reset @~3   # go back 3 commits
$ git reflog
c4f708b HEAD@{0}: reset: moving to @~3
2c52489 HEAD@{1}: commit: more changes
4a5246d HEAD@{2}: commit: make important changes
e8571e4 HEAD@{3}: commit: make some changes
... earlier commits ...
$ git reset 2c52489
... and you're back where you started

* Xem ra các tùy chọn như --hard và --force mặc dù - họ có thể loại bỏ dữ liệu.
* Ngoài ra, đừng viết lại lịch sử trên bất kỳ nhánh nào bạn đang cộng tác.



Trên nhiều hệ thống, git rebase -i sẽ mở Vim theo mặc định. Vim không hoạt động giống như hầu hết các trình soạn thảo văn bản hiện đại, vì vậy hãy xem làm thế nào để rebase bằng cách sử dụng Vim. Nếu bạn muốn sử dụng một trình soạn thảo khác, hãy thay đổi nó bằng git config --global core.editor your-favorite-text-editor.


304
2018-04-29 17:50



Giữa câu trả lời của bạn là một nơi kỳ lạ để đặt những gì tôi chỉ có thể mô tả như là một quảng cáo miniture cho VIM. Điều đó không liên quan đến câu hỏi và chỉ làm xáo trộn câu trả lời của bạn. - Intentss
@Intentss: Ah, tôi có thể thấy lý do tại sao nó trông kỳ lạ. Lý do đằng sau nó là Vim là trình soạn thảo văn bản mặc định trên nhiều hệ thống, do đó, trải nghiệm đầu tiên của nhiều người về khôi phục tương tác là một màn hình mà việc đánh máy khiến con trỏ di chuyển khắp nơi. Sau đó, họ chuyển trình soạn thảo của họ sang một thứ khác và trải nghiệm thứ hai của họ về tương tác lại là khá bình thường, nhưng để họ tự hỏi tại sao nó sử dụng tệp văn bản thay vì GUI. Để đạt được dòng chảy với rebasing, bạn cần một cái gì đó như Vim, hoặc chế độ rebase-Emacs. - Zaz
Nếu tôi đã phải sử dụng một cái gì đó như Gedit hoặc nano để rebase tương tác, tôi sẽ rebase ít hơn rất nhiều. Có lẽ đó sẽ không phải là một điều xấu như vậy, vì tôi là một người nghiện thuốc chống lại. - Zaz
Đuợc. Thấy rất nhiều người thấy rằng một phần không liên quan, tôi đã ngưng tụ nó xuống 3 dòng và cũng giải thích cách thay đổi trình soạn thảo nếu cần. - Zaz
Tuyệt vời! Tôi không biết bạn có thể sử dụng @ như viết tắt cho HEAD. Cảm ơn bạn đã đăng bài này. - James Ko


Tương tác rebase với --autosquash là điều tôi thường xuyên sử dụng khi tôi cần sửa các cam kết trước đó sâu hơn trong lịch sử. Về cơ bản nó tăng tốc quá trình mà câu trả lời của ZelluX minh họa, và đặc biệt hữu ích khi bạn có nhiều hơn một commit bạn cần chỉnh sửa.

Từ tài liệu:

--autosquash

Khi thông báo đăng nhập cam kết bắt đầu bằng "squash! ..." (hoặc "fixup! ..."), và có một cam kết có tiêu đề bắt đầu bằng nhau ..., tự động sửa đổi danh sách cần làm của rebase -i để cam kết đánh dấu cho squashing đến ngay sau khi cam kết được sửa đổi

Giả sử bạn có một lịch sử giống như sau:

$ git log --graph --oneline
* b42d293 Commit3
* e8adec4 Commit2
* faaf19f Commit1

và bạn có thay đổi mà bạn muốn sửa đổi thành Commit2 rồi cam kết thay đổi của bạn bằng cách sử dụng

$ git commit -m "fixup! Commit2"

cách khác bạn có thể sử dụng commit-sha thay vì thông điệp cam kết, vì vậy "fixup! e8adec4 hoặc thậm chí chỉ là tiền tố của thông điệp cam kết.

Sau đó bắt đầu một rebase tương tác trên cam kết trước

$ git rebase e8adec4^ -i --autosquash

trình chỉnh sửa của bạn sẽ mở ra với các cam kết đã được sắp xếp chính xác

pick e8adec4 Commit2
fixup 54e1a99 fixup! Commit2
pick b42d293 Commit3

tất cả những gì bạn cần làm là lưu và thoát


57
2017-09-29 17:59



Bạn cũng có thể dùng git commit --fixup=@~ thay vì git commit -m "fixup! Commit2". Điều này đặc biệt hữu ích khi tin nhắn cam kết của bạn dài hơn và sẽ là một nỗi đau để loại bỏ toàn bộ điều. - Zaz


Chạy:

$ git rebase --interactive commit_hash^

mỗi ^ cho biết bạn muốn chỉnh sửa bao nhiêu lần commit, nếu nó chỉ là một (số băm cam kết mà bạn đã chỉ định), thì bạn chỉ cần thêm một ^.

Sử dụng Vim bạn thay đổi các từ pick đến reword cho các cam kết bạn muốn thay đổi, lưu và thoát (:wq). Sau đó git sẽ nhắc bạn với mỗi cam kết mà bạn đã đánh dấu là reword để bạn có thể thay đổi thông điệp cam kết.

Mỗi thư cam kết bạn phải lưu và thoát (:wq) để đi đến thư cam kết tiếp theo

Nếu bạn muốn thoát mà không áp dụng thay đổi, nhấn :q!

CHỈNH SỬA: để điều hướng trong vim bạn dùng j đi lên, k đi xuống, h đi bên trái và l để đi đúng (tất cả điều này trong NORMAL chế độ, nhấn ESC đi đến NORMAL chế độ ). Để chỉnh sửa văn bản, nhấn i để bạn nhập INSERT chế độ, nơi bạn chèn văn bản. nhấn ESC quay lại NORMAL chế độ :)

CẬP NHẬT: Đây là một liên kết tuyệt vời từ danh sách github Cách hoàn tác (gần như) mọi thứ với git 


30
2017-07-02 19:11



Làm việc hoàn hảo cho tôi. Đáng nói git push --force? - u01jmg3
Gì git push --force không ghi đè các điều khiển từ xa cam kết với các cam kết cục bộ của bạn. Đó không phải là trường hợp của chủ đề này :) - betoharres
@BetuUuUu tất nhiên nếu các cam kết của bạn được đẩy đến điều khiển từ xa và bạn đã sửa đổi thông điệp cam kết cục bộ, bạn sẽ muốn ép buộc từ xa, phải không? - Sudip Bhandari
@SudipBhandari Đó là cảm giác tôi nhận được. Tôi đã không ép buộc, và bây giờ tôi có thêm một chi nhánh, phản ánh tất cả các cam kết trở lại với một thông điệp mà tôi đã thay đổi, đó là siêu xấu xí. - ruffin
Sự hồi phục tương tác có thể có vẻ khó khăn ngay từ đầu. Tôi đã viết một bài (với hình ảnh) trình bày chi tiết, từng bước: blog.tratif.com/2018/04/19/the-power-of-git-interactive-rebase - Tomasz Kaczmarzyk


Nếu vì một số lý do bạn không thích trình chỉnh sửa tương tác, bạn có thể sử dụng git rebase --onto.

Giả sử bạn muốn sửa đổi Commit1. Đầu tiên, chi nhánh từ trước  Commit1:

git checkout -b amending [commit before Commit1]

Thứ hai, lấy Commit1 với cherry-pick:

git cherry-pick Commit1

Bây giờ, sửa đổi các thay đổi của bạn, tạo Commit1':

git add ...
git commit --amend -m "new message for Commit1"

Và cuối cùng, sau khi đã vất vả bất kỳ thay đổi nào khác, hãy ghép phần còn lại của các cam kết của bạn lên master trên đầu trang của bạn cam kết mới:

git rebase --onto amending Commit1 master

Đọc: "rebase, vào nhánh amending, tất cả các cam kết giữa Commit1 (không bao gồm) và master (bao gồm) ".Đó là, Commit2 và Commit3, cắt bỏ Commit1 cũ hoàn toàn. Bạn chỉ có thể chọn anh đào, nhưng cách này dễ dàng hơn.

Hãy nhớ để làm sạch các chi nhánh của bạn!

git branch -d amending

12
2017-10-22 12:19



bạn có thể dùng git checkout -b amending Commit1~1 để có được cam kết trước - Arin Taylor


Đến cách tiếp cận này (và nó có lẽ là chính xác giống như sử dụng rebase tương tác) nhưng đối với tôi nó là loại đơn giản.

Lưu ý: Tôi trình bày cách tiếp cận này để minh họa những gì bạn có thể làm thay vì thay thế hàng ngày. Vì nó có nhiều bước (và có thể có một số cảnh báo.)

Giả sử bạn muốn thay đổi cam kết 0 và bạn hiện đang ở feature-branch

some-commit---0---1---2---(feature-branch)HEAD

Thanh toán cho cam kết này và tạo quick-branch. Bạn cũng có thể sao chép chi nhánh tính năng của mình làm điểm khôi phục (trước khi bắt đầu).

?(git checkout -b feature-branch-backup)
git checkout 0
git checkout -b quick-branch

Bây giờ bạn sẽ có một cái gì đó như thế này:

0(quick-branch)HEAD---1---2---(feature-branch)

Giai đoạn thay đổi, stash mọi thứ khác.

git add ./example.txt
git stash

Cam kết thay đổi và kiểm tra lại feature-branch

git commit --amend
git checkout feature-branch

Bây giờ bạn sẽ có một cái gì đó như thế này:

some-commit---0---1---2---(feature-branch)HEAD
           \
             ---0'(quick-branch)

Rebase feature-branch trên quick-branch (giải quyết mọi xung đột trên đường đi). Áp dụng stash và xóa quick-branch.

git rebase quick-branch
git stash pop
git branch -D quick-branch

Và bạn kết thúc với:

some-commit---0'---1'---2'---HEAD(feature-branch)

Git sẽ không trùng lặp (mặc dù tôi không thể thực sự nói đến mức độ nào) 0 cam kết khi rebasing.

Lưu ý: tất cả các băm cam kết được thay đổi bắt đầu từ cam kết ban đầu chúng tôi dự định thay đổi.


6
2018-06-01 11:57





Để nhận lệnh không tương tác, hãy đặt tập lệnh có nội dung này trong PATH của bạn:

#!/bin/sh
#
# git-fixup
# Use staged changes to modify a specified commit
set -e
cmt=$(git rev-parse $1)
git commit --fixup="$cmt"
GIT_EDITOR=true git rebase -i --autosquash "$cmt~1"

Sử dụng nó bằng cách dàn dựng các thay đổi của bạn (với git add) và sau đó chạy git fixup <commit-to-modify>. Tất nhiên, nó sẽ vẫn tương tác nếu bạn nhận được xung đột.


4
2018-01-16 15:27



Điều này hoạt động tốt. Tôi đã thêm một số chức năng bổ sung để thực hiện sửa chữa từng phần của một cây bẩn để hoàn thiện bộ cam kết. `dirtydiff = $ (git diff); nếu ["$ {dirtydiff}"! = ""]; sau đó echo "Stashing dirty tree"> & 2; git stash; fi; - Simon Feltman


Lệnh không tương tác hoàn toàn(1)

Tôi chỉ nghĩ rằng tôi muốn chia sẻ một bí danh mà tôi đang sử dụng cho việc này. Nó dựa trên không tương tác tương tác rebase. Để thêm nó vào git của bạn, hãy chạy lệnh này (giải thích dưới đây):

git config --global alias.amend-to '!f() { SHA=`git rev-parse "$1"`; git commit --fixup "$SHA" && GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^"; }; f'

Ưu điểm lớn nhất của lệnh này là thực tế là nó không có vim.


(1)cho rằng không có xung đột trong quá trình rebase, tất nhiên

Sử dụng

git amend-to <REV> # e.g.
git amend-to HEAD~1
git amend-to aaaa1111

Tên amend-to dường như IMHO thích hợp. So sánh luồng với --amend:

git add . && git commit --amend --no-edit
# vs
git add . && git amend-to <REV>

Giải trình

  • git config --global alias.<NAME> '!<COMMAND>' - tạo bí danh git chung có tên <NAME> sẽ thực thi lệnh không git <COMMAND>
  • f() { <BODY> }; f - một hàm bash "ẩn danh".
  • SHA=`git rev-parse "$1"`; - chuyển đổi đối số thành bản sửa đổi git và gán kết quả cho biến SHA
  • git commit --fixup "$SHA" - cam kết sửa lỗi cho SHA. Xem git-commit tài liệu
  • GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^"
    • git rebase --interactive "$SHA^" một phần đã được đề cập trong các câu trả lời khác.
    • --autosquash là những gì được sử dụng kết hợp với git commit --fixup, xem git-rebase tài liệu để biết thêm thông tin
    • GIT_SEQUENCE_EDITOR=true là những gì làm cho toàn bộ điều không tương tác. Hack này tôi đã học được từ bài đăng trên blog này.

4
2018-02-27 01:47



Người ta cũng có thể làm amend-to xử lý các tệp chưa được tạo: git config --global alias.amend-to '!f() { SHA=git rev-parse "$ 1"; git stash -k && git commit --fixup "$SHA" && GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^" && git stash pop; }; f' - Dethariel
Tôi sử dụng nó với --autostash gắn cờ lệnh rebase. - idanp