Làm thế nào để sửa đổi một cam kết được chỉ định?


2233

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

  1. HEAD
  2. Commit3
  3. Commit2
  4. Commit1

... Tôi biết rằng tôi có thể sửa đổi cam kết với git commit --amend. Nhưng làm thế nào tôi có thể sửa đổi Commit1, cho rằng đó không phải là HEADcam kết?


31
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 đã sẵn sàng cam kết mới trước khi bạn 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ể hoạt động với nhiều cam kết bạn muốn hợp nhất / squash cùng với một cam kết cũ hơn.
akostadinov

6
Ngoài ra, bạn chỉ có thể xem Chia tách một cam kết trong Công cụ Git - Viết lại Lịch sử để biết thêm thông tin.
hakre

Câu trả lời:


2956

Bạn có thể sử dụng git rebase . Ví dụ: nếu bạn muốn sửa đổi cam kết bbc643cd, hãy chạy

$ git rebase --interactive 'bbc643cd^'

Xin lưu ý dấu mũ ^ở cuối lệnh, bởi vì bạn thực sự cần phải quay trở lại cam kết trước khi bạn muốn sửa đổi .

Trong trình chỉnh sửa mặc định, sửa đổi pickthành edittrong dòng đề cập đến 'bbc643cd'.

Lưu tệp và thoát: git sẽ diễn giải và tự động thực hiện các lệnh trong tệp. Bạn sẽ thấy mình trong tình huống trước đó mà bạn vừa tạo ra cam kết bbc643cd.

Tại thời điểm này, bbc643cdlà cam kết cuối cùng của bạn và bạn có thể dễ dàng sửa đổi nó : thực hiện các thay đổi của bạn và sau đó cam kết chúng bằng lệnh:

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

Sau đó, gõ:

$ git rebase --continue

để trở lại cam kết CHÍNH 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 đó trở đi. Bạn có thể ngắt repos làm điều này nếu bạn sử dụng lệnhgit push --force


125
Một tùy chọn thú vị khác trong luồng này là khi bạn đã chuyển sang cam kết bạn muốn sửa đổi, thay vì sửa đổi các tệp và được xử lý cam kết trên đầu trang (bạn đang chỉnh sửa), bạn có thể muốn chia cam kết đó thành hai cam kết 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 như bạn muốn. Luồng này được giải thích khá rõ trong trang man "git-rebase". Xem phần "Chia tách cam kết". bit.ly/d50w1M
Diego Pino

200
Trong Git 1.6.6 và mới hơn, bạn có thể sử dụng rewordhành động git rebase -ithay thế edit(nó tự động mở trình chỉnh sửa và tiếp tục với các bước rebase còn lại; điều này làm giảm việc sử dụng git commit --ammendgit rebase --continuekhi bạn chỉ cần thay đổi thông báo cam kết chứ không phải nội dung ).
Chris Johnsen

108
Điều đáng chú ý là bạn có thể cần phải chạy git stashtrước git rebasegit stash popsau, nếu bạn có các thay đổi đang chờ xử lý.
user123444555621

3
Có một lệnh rút gọn để chỉnh sửa một cam kết cụ thể trong rebase tương tác mà không cần mở trình soạn thảo, tìm cam kết, đánh dấu nó chỉnh sửa, sau đó thả trở lại dòng lệnh?
vào

15
Lưu ý rằng với git mới hơn, sẽ khôn ngoan hơn khi làm theo hướng dẫn nhanh chóng thay vì sử dụng mù quáng git commit --all --amend --no-editở đây. Tất cả tôi phải làm sau đó git rebase -i ...git commit --amendbình thường sau đó git rebase --continue.
Eric Chen

453

Sử dụ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 pickthành e( edit), 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 thay đổi các tệp (tức là đưa bạn đến điểm bạn đã ở khi bạn chỉnh sửa các tệp, nhưng chưa cam kết).

Cái sau hữu ích để làm những thứ phức tạp hơn như chia thành nhiều lần xác nhận.

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.

Lưu ý: @là viết tắt cho HEAD~là cam kết trước khi xác định được chỉ định.

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


Đừng sợ để nổi loạn

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 các 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

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



Trên nhiều hệ thống, git rebase -isẽ mở Vim theo mặc định. Vim không hoạt độ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 cách khởi động lại bằ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.


29
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ư một quảng cáo nhỏ cho VIM. Nó không liên quan đến câu hỏi và chỉ làm hỏng câu trả lời của bạn.
Ý định

21
@Intentss: Ah, tôi có thể thấy tại sao điều đó trông kỳ lạ. Lý do đằng sau đó là Vim là trình soạn thảo văn bản mặc định trên nhiều hệ thống, vì vậy trải nghiệm đầu tiên của việc khởi động lại tương tác của nhiều người là một màn hình trong đó việc gõ khiến con trỏ bay khắp nơi. Sau đó, họ chuyển trình soạn thảo của mình sang một thứ khác và trải nghiệm thứ hai của họ về phản ứng tương tác là khá bình thường, nhưng khiến họ tự hỏi tại sao nó sử dụng tệp văn bản thay vì GUI. Để đạt được lưu lượng khi khởi động lại, bạn cần một cái gì đó như chế độ rebase của Vim hoặc Emacs.
Zaz

9
Được chứ. Thấy rất nhiều người thấy phần đó không liên quan, tôi đã cô đọng lại thành 3 dòng và cũng giải thích cách thay đổi trình chỉnh sửa nếu cần.
Zaz

17
Tuyệt vời! Tôi không biết bạn có thể sử dụng @như tốc ký HEAD. Cảm ơn đã đăng bài này.
James Ko

3
git reset @~chính xác những gì tôi muốn làm sau khi chọn cam kết với git rebase .... Bạn là người hùng của tôi)
18aug vào

79

Rebase tương tác với --autosquashlà thứ tôi thường sử dụng khi tôi cần sửa chữ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 tiện dụng khi bạn có nhiều hơn một cam kết bạn cần chỉnh sửa.

Từ tài liệu:

--autosquash

Khi thông báo nhật ký cam kết bắt đầu bằng "squash! Sầu" (hoặc "sửa lỗi! Sầu") và có một cam kết có tiêu đề bắt đầu bằng cùng một trò chơi, tự động sửa đổi danh sách việc 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ư thế này:

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

và bạn có các thay đổi mà bạn muốn sửa đổi thành Commit2 sau đó cam kết các 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 cam kết thay vì thông điệp cam kết, "fixup! e8adec4hoặ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ở với các xác nhận đã đượ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


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

42

Chạy:

$ git rebase --interactive commit_hash^

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

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

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

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

EDIT : để điều hướng trong vimbạn sử dụng jđể đi lên, kđi xuống, hsang trái và lsang phải (tất cả điều này trong NORMALchế độ, nhấn ESCđể chuyển sang NORMALchế độ). Để chỉnh sửa văn bản, nhấn iđể bạn vào INSERTchế độ, nơi bạn chèn văn bản. Nhấn ESCđể quay lại NORMALchế độ :)

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


4
Làm việc hoàn hảo cho tôi. Đáng nói git push --force?
u01jmg3

Những gì git push --forcehiện được ghi đè lên các cam kết từ xa 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 từ xa và bạn đã sửa đổi thông báo cam kết cục bộ, bạn có muốn buộc đẩy sang điều khiển từ xa 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 thông điệp mà tôi đã thay đổi, đó là siêu xấu xí.
ruffin

2
@greenhouse nếu bạn sửa đổi và ép buộc, thì các thành viên khác trong nhóm rất có thể sẽ gặp phải xung đột hợp nhất. Vì vậy, bạn nên nói chung là siêu thận trọng về nó. Nhưng nếu bạn sửa đổi một cái gì đó mà không ai khác tìm nạp, nó sẽ ổn (sẽ không nhận thấy nó). Vì vậy, tôi sẽ xem xét - lực lượng như là phương sách cuối cùng và luôn luôn tham khảo trạng thái của repo với các thành viên khác.
Tomasz Kaczmarhot

18

Nếu vì lý do nào đó bạn không thích các biên tập viên tương tác, bạn có thể sử dụng git rebase --onto.

Nói rằng 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 Commit1vớ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 thực hiện bất kỳ thay đổi nào khác, hãy ghép phần còn lại của cam kết lên mastertrên cam kết mới của bạn:

git rebase --onto amending Commit1 master

Đọc: "rebase, trên chi 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 hoàn toàn Commit1 cũ. Bạn chỉ có thể chọn anh đào, nhưng cách này dễ hơn.

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

git branch -d amending

4
bạn có thể sử dụng git checkout -b amending Commit1~1để nhận được cam kết trước
Arin Taylor

Là hai bước đầu tiên tương đương với git checkout -b amending Commit1?
Haoshu

16

Dựa trên tài liệu

Sửa đổi tin nhắn của tin nhắn cam kết cũ hoặc nhiều

git rebase -i HEAD~3 

Ở trên hiển thị danh sách 3 cam kết cuối cùng trên nhánh hiện tại, thay đổi 3 thành một thứ khác nếu bạn muốn nhiều hơn. Danh sách này sẽ trông giống như sau:

pick e499d89 Delete CNAME
pick 0c39034 Better README
pick f7fde4a Change the commit message but push the same commit.

Thay thế chọn bằng tua lại trước mỗi thông điệp cam kết bạn muốn thay đổi. Giả sử bạn thay đổi cam kết thứ hai trong danh sách, tệp của bạn sẽ trông như sau:

pick e499d89 Delete CNAME
reword 0c39034 Better README
pick f7fde4a Change the commit message but push the same commit.

Lưu và đóng tệp danh sách cam kết, điều này sẽ bật lên một trình chỉnh sửa mới để bạn thay đổi thông điệp cam kết, thay đổi thông điệp cam kết và lưu.

Lực lượng cuối cùng-đẩy các cam kết sửa đổi.

git push --force

Tôi gặp lỗi sau: lỗi: Có vấn đề với trình soạn thảo 'vi'. Vui lòng cung cấp tin nhắn bằng tùy chọn -m hoặc -F.
Erick Maynard

12

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

Tôi chỉ nghĩ rằng tôi sẽ chia sẻ một bí danh mà tôi đang sử dụng cho việc này. Nó dựa trên rebase tương tác không tương tác. Để thêm nó vào git của bạn, hãy chạy lệnh này (giải thích được đưa ra 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-tocó vẻ thích hợp IMHO. So sánh lưu lượ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 một bí danh git toàn cầu có tên <NAME>sẽ thực thi lệnh không phải git<COMMAND>
  • f() { <BODY> }; f - một hàm bash "ẩn danh".
  • SHA=`git rev-parse "$1"`; - chuyển đổi đối số thành sửa đổi git và gán kết quả cho biến SHA
  • git commit --fixup "$SHA"- sửa lỗi-cam kết cho SHA. Xem git-committài liệu
  • GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^"
    • git rebase --interactive "$SHA^" một phần đã được bao phủ bởi các câu trả lời khác.
    • --autosquashlà những gì được sử dụng cùng với git commit --fixup, xem git-rebasetài liệu để biết thêm thông tin
    • GIT_SEQUENCE_EDITOR=truelà 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 viết trên blog này .

1
Người ta cũng có thể amend-toxử lý các tệp chưa được xử lý: 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

2
Một mối quan tâm với phương pháp này là nó có thể áp dụng các bản sửa lỗi không liên quan.
Ciro Santilli 冠状 病毒 审查 事件

Không phải là điểm của câu hỏi để thay đổi thông điệp cam kết? Bởi vì câu trả lời này không giải quyết điều đó, hoặc ít nhất là không trực tiếp.
wytten

@wytten câu hỏi không hỏi về việc thay đổi thông điệp cam kết, đó là về việc sửa đổi cam kết không phải là CHÍNH. Vì vậy, một câu trả lời cho câu hỏi của bạn sẽ là "không, đó không phải là điểm của câu hỏi"
Dethariel

Tôi đang bối rối, vậy làm cách nào để thay đổi thông điệp cam kết?
ggb667

8

Chỉnh sửa rebase tương tác tự động theo sau bởi cam kết hoàn lại sẵn sàng cho việc làm lại

Tôi thấy mình đã sửa chữa một cam kết trong quá khứ thường xuyên đến mức tôi đã viết một kịch bản cho nó.

Đây là quy trình làm việc:

  1. git commit-edit <commit-hash>
    

    Điều này sẽ thả bạn vào cam kết bạn muốn chỉnh sửa.

  2. Sửa chữa và thực hiện cam kết như bạn muốn nó đã ở vị trí đầu tiên.

    (Bạn có thể muốn sử dụng git stash saveđể giữ bất kỳ tệp nào bạn không cam kết)

  3. Làm lại cam kết với --amend, ví dụ:

    git commit --amend
    
  4. Hoàn thành cuộc nổi loạn:

    git rebase --continue
    

Để làm việc ở trên, hãy đặt đoạn mã dưới đây vào một tệp thực thi được gọi git-commit-editở đâu đó trong $PATH:

#!/bin/bash

set -euo pipefail

script_name=${0##*/}

warn () { printf '%s: %s\n' "$script_name" "$*" >&2; }
die () { warn "$@"; exit 1; }

[[ $# -ge 2 ]] && die "Expected single commit to edit. Defaults to HEAD~"

# Default to editing the parent of the most recent commit
# The most recent commit can be edited with `git commit --amend`
commit=$(git rev-parse --short "${1:-HEAD~}")
message=$(git log -1 --format='%h %s' "$commit")

if [[ $OSTYPE =~ ^darwin ]]; then
  sed_inplace=(sed -Ei "")
else
  sed_inplace=(sed -Ei)
fi

export GIT_SEQUENCE_EDITOR="${sed_inplace[*]} "' "s/^pick ('"$commit"' .*)/edit \\1/"'
git rebase --quiet --interactive --autostash --autosquash "$commit"~
git reset --quiet @~ "$(git rev-parse --show-toplevel)"  # Reset the cache of the toplevel directory to the previous commit
git commit --quiet --amend --no-edit --allow-empty  #  Commit an empty commit so that that cache diffs are un-reversed

echo
echo "Editing commit: $message" >&2
echo

7

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

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

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

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

Thanh toán cho cam kết này và tạo ra một quick-branch. Bạn cũng có thể sao chép 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, cất giấu mọi thứ khác.

git add ./example.txt
git stash

Cam kết thay đổi và thanh toán trở 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-branchlên quick-branch(giải quyết bất kỳ xung đột trên đường đi). Áp dụng stash và loại bỏ 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 thực sự không thể nói ở mức độ nào) cam kết 0 khi nổi loạn.

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

Để nhận lệnh không tương tác, hãy đặt mộ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ó vẫn sẽ tương tác nếu bạn gặp xung đột.


1
Đ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 lỗi từng phần của một cây bẩn để hoàn thiện một bộ cam kết. `Dirtdiff = $ (git diff); if ["$ {Dirtdiff}"! = ""]; sau đó lặp lại "Đâm cây bẩn"> & 2; git stash; fi;
Simon Feelman

6

git stash+ rebasetự động hóa

Vì khi tôi cần sửa đổi một cam kết cũ rất nhiều lần cho các đánh giá của Gerrit, tôi đã thực hiện:

git-amend-old() (
  # Stash, apply to past commit, and rebase the current branch on to of the result.
  current_branch="$(git rev-parse --abbrev-ref HEAD)"
  apply_to="$1"
  git stash
  git checkout "$apply_to"
  git stash apply
  git add -u
  git commit --amend --no-edit
  new_sha="$(git log --format="%H" -n 1)"
  git checkout "$current_branch"
  git rebase --onto "$new_sha" "$apply_to"
)

GitHub ngược dòng .

Sử dụng:

  • sửa đổi tập tin nguồn, không cần git addnếu đã có trong repo
  • git-amend-old $old_sha

Tôi thích điều này --autosquashvì nó không đè bẹp các bản sửa lỗi không liên quan khác.


Cách giải quyết rất hay, đây phải là một tùy chọn mặc định git amendđể áp dụng các thay đổi cho một cam kết cụ thể với việc sử dụng stash hiện tại, rất thông minh!
caiohamamura

5

Tôi đã giải quyết điều này,

1) bằng cách tạo cam kết mới với những thay đổi tôi muốn ..

r8gs4r commit 0

2) tôi biết cam kết nào tôi cần hợp nhất với nó. đó là cam kết 3.

vì vậy, git rebase -i HEAD~4# 4 đại diện cho 4 cam kết gần đây (ở đây cam kết 3 ở vị trí thứ 4)

3) trong cam kết tương tác rebase gần đây sẽ nằm ở dưới cùng. nó sẽ trông giống nhau

pick q6ade6 commit 3
pick vr43de commit 2
pick ac123d commit 1
pick r8gs4r commit 0

4) ở đây chúng tôi cần sắp xếp lại cam kết nếu bạn muốn hợp nhất với một cam kết cụ thể. nó nên như thế

parent
|_child

pick q6ade6 commit 3
f r8gs4r commit 0
pick vr43de commit 2
pick ac123d commit 1

sau khi sắp xếp lại, bạn cần thay thế p pickbằng f( sửa lỗi sẽ hợp nhất mà không cần thông báo cam kết) hoặc s( hợp nhất squash với thông điệp cam kết có thể thay đổi trong thời gian chạy)

và sau đó lưu cây của bạn.

bây giờ hợp nhất được thực hiện với cam kết hiện có.

Lưu ý: Phương pháp không thích hợp hơn trừ khi bạn tự duy trì. Nếu bạn có kích thước nhóm lớn, phương pháp không thể chấp nhận để viết lại cây git sẽ dẫn đến xung đột mà bạn không biết. nếu bạn muốn duy trì cây của bạn sạch sẽ với ít cam kết hơn có thể thử điều này và nếu nhóm nhỏ của nó nếu không thì không nên .....


Đây là một giải pháp tốt nếu bạn không muốn thực hiện sửa đổi trực tiếp trong một cuộc nổi loạn tương tác.
Dunatotatos

4

Tùy chọn tốt nhất là sử dụng "Lệnh rebase tương tác" .

Các git rebaselệnh là vô cùng mạnh mẽ. Nó cho phép bạn chỉnh sửa các thông điệp cam kết , kết hợp các cam kết, sắp xếp lại chúng ... vv.

Mỗi khi bạn khởi động lại một cam kết, một SHA mới sẽ được tạo cho mỗi cam kết bất kể nội dung sẽ được thay đổi hay không! Bạn nên cẩn thận khi sử dụng lệnh này vì nó có thể có ý nghĩa quyết liệt đặc biệt là nếu bạn hợp tác với các nhà phát triển khác. Họ có thể bắt đầu làm việc với cam kết của bạn trong khi bạn đang từ chối một số. Sau khi bạn buộc phải đẩy các cam kết, chúng sẽ không đồng bộ và bạn có thể phát hiện ra sau đó trong một tình huống lộn xộn. Vì vậy, hãy cẩn thận!

Bạn nên tạo một backupnhánh trước khi khởi động lại để bất cứ khi nào bạn tìm thấy những thứ ngoài tầm kiểm soát, bạn có thể quay lại trạng thái trước đó.

Bây giờ làm thế nào để sử dụng lệnh này?

git rebase -i <base> 

-iviết tắt của "tương tác" . Lưu ý rằng bạn có thể thực hiện rebase trong chế độ không tương tác. Ví dụ:

#interactivly rebase the n commits from the current position, n is a given number(2,3 ...etc)
git rebase -i HEAD~n 

HEADcho biết vị trí hiện tại của bạn (cũng có thể là tên chi nhánh hoặc cam kết SHA). Nghĩa ~nlà "n beforeé, vì vậy HEAD~nsẽ là danh sách" n "cam kết trước khi bạn hiện đang sử dụng.

git rebase có lệnh khác nhau như:

  • phoặc pickđể giữ cam kết như nó là.
  • rhoặc reword: để giữ nội dung của cam kết nhưng thay đổi thông điệp cam kết.
  • shoặc squash: để kết hợp các thay đổi của cam kết này với cam kết trước đó (cam kết phía trên nó trong danh sách).
  • ... Vân vân.

    Lưu ý: Tốt hơn là để Git làm việc với trình chỉnh sửa mã của bạn để làm cho mọi thứ đơn giản hơn. Ví dụ như nếu bạn sử dụng mã hình ảnh, bạn có thể thêm như thế này git config --global core.editor "code --wait". Hoặc bạn có thể tìm kiếm trong Google cách liên kết bạn thích trình soạn thảo mã của mình với GIT.

Ví dụ của git rebase

Tôi muốn thay đổi 2 lần xác nhận cuối cùng mà tôi đã thực hiện để xử lý như sau:

  1. Hiển thị các cam kết hiện tại:
    #This to show all the commits on one line
    $git log --oneline
    4f3d0c8 (HEAD -> documentation) docs: Add project description and included files"
    4d95e08 docs: Add created date and project title"
    eaf7978 (origin/master , origin/HEAD, master) Inital commit
    46a5819 Create README.md
    
  2. Bây giờ tôi sử dụng git rebaseđể thay đổi 2 thông báo cam kết cuối cùng: $git rebase -i HEAD~2 Nó mở trình soạn thảo mã và hiển thị điều này:

    pick 4d95e08 docs: Add created date and project title
    pick 4f3d0c8 docs: Add project description and included files
    
    # Rebase eaf7978..4f3d0c8 onto eaf7978 (2 commands)
    #
    # Commands:
    # p, pick <commit> = use commit
    # r, reword <commit> = use commit, but edit the commit message
    ...
    

    Vì tôi muốn thay đổi thông báo cam kết cho 2 lần xác nhận này. Vì vậy, tôi sẽ gõ rhoặc rewordthay thế pick. Sau đó Lưu tệp và đóng tab. Lưu ý rằng rebaseđược thực hiện trong một quy trình gồm nhiều bước, vì vậy bước tiếp theo là cập nhật các thông báo. Cũng lưu ý rằng các xác nhận được hiển thị theo thứ tự thời gian đảo ngược để cam kết cuối cùng được hiển thị trong đó và cam kết đầu tiên trong dòng đầu tiên và vv.

  3. Cập nhật tin nhắn: Cập nhật tin nhắn đầu tiên:

    docs: Add created date and project title to the documentation "README.md"
    
    # Please enter the commit message for your changes. Lines starting
    # with '#' will be ignored, and an empty message aborts the commit.
    ...
    

    lưu và đóng Chỉnh sửa tin nhắn thứ hai

    docs: Add project description and included files to the documentation "README.md"
    
    # Please enter the commit message for your changes. Lines starting
    # with '#' will be ignored, and an empty message aborts the commit.
    ...
    

    lưu và đóng

  4. Bạn sẽ nhận được một tin nhắn như thế này vào cuối cuộc nổi loạn: Successfully rebased and updated refs/heads/documentationcó nghĩa là bạn đã thành công. Bạn có thể hiển thị các thay đổi:

    5dff827 (HEAD -> documentation) docs: Add project description and included files to the documentation "README.md"
    4585c68 docs: Add created date and project title to the documentation "README.md"
    eaf7978 (origin/master, origin/HEAD, master) Inital commit
    46a5819 Create README.md
    

    Tôi ước rằng có thể giúp người dùng mới :).


2

Đối với tôi đó là để xóa một số thông tin từ một repo. Tôi đã cố gắng nổi loạn và gặp phải vô số mâu thuẫn dường như không liên quan trên đường đi khi cố gắng nổi loạn - tiếp tục. Đừng bận tâm cố gắng tự nổi loạn, hãy sử dụng công cụ có tên BFG (brew install bfg) trên mac.


0

Nếu bạn chưa đẩy các cam kết thì bạn có thể quay lại cam kết trước đó bằng cách sử dụng git reset HEAD^[1,2,3,4...]

Ví dụ

git commit <file1> -m "Updated files 1 and 2"
git commit <file3> -m "Updated file 3"

Rất tiếc, đã quên thêm tệp2 vào lần xác nhận đầu tiên ...

git reset HEAD^1 // because I only need to go back 1 commit

git add <file2>

Điều này sẽ thêm file2 vào lần xác nhận đầu tiên.


0

Chà, giải pháp này nghe có vẻ rất ngớ ngẩn, nhưng có thể giúp bạn tiết kiệm trong một số điều kiện nhất định.

Một người bạn của tôi đã vô tình phạm phải một số tệp rất lớn (bốn tệp được tạo tự động trong khoảng từ 3 GB đến 5 GB mỗi tệp) và sau đó thực hiện một số mã bổ sung trên đó trước khi nhận ra sự cố git pushkhông còn hoạt động nữa!

Các tập tin đã được liệt kê trong .gitignorenhưng sau khi đổi tên thư mục container, chúng đã bị lộ và cam kết! Và bây giờ có thêm một vài lần xác nhận mã trên đó, nhưng pushđã chạy mãi mãi (cố gắng tải lên GB dữ liệu!) Và cuối cùng sẽ thất bại do giới hạn kích thước tệp của Github .

Vấn đề với rebase tương tác hoặc bất cứ điều gì tương tự là họ sẽ đối phó với việc chọc vào những tệp khổng lồ này và sẽ mất mãi mãi để làm bất cứ điều gì. Tuy nhiên, sau khi dành gần một giờ trong CLI, chúng tôi không chắc liệu các tệp (và deltas) có thực sự bị xóa khỏi lịch sử hay đơn giản là không bao gồm trong các cam kết hiện tại. Việc đẩy cũng không hiệu quả và bạn tôi đã thực sự bế tắc.

Vì vậy, giải pháp tôi đưa ra là:

  1. Đổi tên thư mục git hiện tại thành ~/Project-old.
  2. Sao chép thư mục git một lần nữa từ github (đến ~/Project).
  3. Thanh toán cho cùng chi nhánh.
  4. Thủ cp -rcông các tập tin từ ~/Project-oldthư mục đến ~/Project.
  5. Đảm bảo rằng các tệp lớn, không cần phải kiểm tra là mved và được bao gồm .gitignoređúng cách.
  6. Ngoài ra, hãy đảm bảo bạn không ghi đè lên .gitthư mục trong thư mục ~/Projectcũ được sao chép gần đây . Đó là nơi các bản ghi của lịch sử có vấn đề sống!
  7. Bây giờ xem lại những thay đổi. Nó phải là sự kết hợp của tất cả các cam kết gần đây, ngoại trừ các tệp có vấn đề.
  8. Cuối cùng cam kết các thay đổi, và thật tốt khi được push'ed.

Vấn đề lớn nhất với giải pháp này là, nó liên quan đến việc sao chép thủ công một số tệp và cũng hợp nhất tất cả các cam kết gần đây thành một (rõ ràng là với một cam kết băm mới.) B

Những lợi ích lớn là, nó rất rõ ràng trong từng bước, nó hoạt động tuyệt vời cho các tệp lớn (cũng như những tệp nhạy cảm) và nó không để lại bất kỳ dấu vết nào trong lịch sử!

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.