Làm thế nào để squash tất cả các cam kết git thành một?


480

Làm thế nào để bạn nén toàn bộ kho lưu trữ của bạn xuống cam kết đầu tiên?

Tôi có thể phản hồi lại cam kết đầu tiên, nhưng điều đó sẽ khiến tôi có 2 lần cam kết. Có cách nào để tham chiếu các cam kết trước khi đầu tiên?


8
"Cam kết trước người đầu tiên"?
innaM

31
@innaM - Đó là cam kết nguyên thủy bắt đầu git . (Hy vọng sự hài hước vượt qua đủ tốt thông qua các interweb).
ripper234

11
Đối với những người đến sau câu hỏi này, hãy chắc chắn sử dụng câu trả lời hiện đại hơn .
Droogans

1
Liên quan, nhưng không phải là một bản sao ( --rootthực sự không phải là giải pháp tốt nhất để xóa tất cả các xác nhận nếu có rất nhiều trong số chúng để xóa): Kết hợp hai cam kết đầu tiên của kho lưu trữ Git? .

2
Imo: đây là thứ tốt nhất từ ​​@MrTux: stackoverflow.com/questions/30236694/iêu .
J0hnG4lt

Câu trả lời:


130

Có lẽ cách dễ nhất là chỉ cần tạo một kho lưu trữ mới với trạng thái hiện tại của bản sao đang hoạt động. Nếu bạn muốn giữ tất cả các thông điệp cam kết trước tiên bạn có thể thực hiện git log > original.logvà sau đó chỉnh sửa thông điệp cam kết ban đầu của bạn trong kho lưu trữ mới:

rm -rf .git
git init
git add .
git commit

hoặc là

git log > original.log
# edit original.log as desired
rm -rf .git
git init
git add .
git commit -F original.log

62
nhưng bạn đang mất chi nhánh với phương pháp này
Olivier Refalo

150
Git đã phát triển kể từ khi câu trả lời này được đưa ra. Không, có một cách đơn giản và tốt hơn : git rebase -i --root. Xem: stackoverflow.com/a/9254257/109618
David J.

5
Điều này có thể làm việc cho một số trường hợp, nhưng về cơ bản nó không phải là câu trả lời cho câu hỏi. Với công thức này, bạn mất tất cả cấu hình của bạn và tất cả các chi nhánh khác.
iwein

3
Đây là một giải pháp khủng khiếp mà không cần thiết phải phá hủy. Xin đừng sử dụng nó.
Daniel Kamil Kozar

5
cũng sẽ phá vỡ mô hình con. -1
Krum

694

Kể từ git 1.6.2 , bạn có thể sử dụng git rebase --root -i.

Đối với mỗi cam kết ngoại trừ đầu tiên, thay đổi pickthành squash.


49
Vui lòng thêm một ví dụ lệnh hoàn chỉnh, làm việc để trả lời câu hỏi ban đầu.
Jake

29
Tôi ước tôi đã đọc được điều này trước khi thổi bay toàn bộ kho lưu trữ của mình như câu trả lời được chấp nhận nói: /
Mike Chamberlain

38
Câu trả lời này là ổn , nhưng nếu bạn tương tác nổi loạn nhiều hơn, giả sử, 20 lần cam kết, thì rebase tương tác có thể sẽ quá chậm và khó sử dụng. Có lẽ bạn sẽ có một thời gian khó khăn để cố gắng xóa sổ hàng trăm hoặc hàng ngàn cam kết. Tôi sẽ đi với một thiết lập lại mềm hoặc hỗn hợp đến cam kết gốc, sau đó khuyến nghị, trong trường hợp đó.

20
@Pred Đừng sử dụng squashcho tất cả các cam kết. Người đầu tiên cần phải có pick.
Geert

14
Nếu bạn có nhiều cam kết, thật khó để thay đổi 'chọn' thành 'squash' theo cách thủ công. Sử dụng :% s / pick / squash / g trong dòng lệnh VIM để thực hiện việc này nhanh hơn.
eilas

314

Cập nhật

Tôi đã tạo một bí danh git squash-all.
Ví dụ sử dụng : git squash-all "a brand new start".

[alias]
  squash-all = "!f(){ git reset $(git commit-tree HEAD^{tree} -m \"${1:-A new start}\");};f"

Hãy cẩn thận : nhớ cung cấp một nhận xét, nếu không, thông báo cam kết mặc định "Một khởi đầu mới" sẽ được sử dụng.

Hoặc bạn có thể tạo bí danh bằng lệnh sau:

git config --global alias.squash-all '!f(){ git reset $(git commit-tree HEAD^{tree} -m "${1:-A new start}");};f'

Lót

git reset $(git commit-tree HEAD^{tree} -m "A new start")

Lưu ý : ở đây " A new start" chỉ là một ví dụ, vui lòng sử dụng ngôn ngữ của riêng bạn.

TL; DR

Không cần phải ép, sử dụng git commit-treeđể tạo một cam kết mồ côi và đi với nó.

Giải thích

  1. tạo một cam kết thông qua git commit-tree

    Cái gì git commit-tree HEAD^{tree} -m "A new start"là:

    Tạo một đối tượng cam kết mới dựa trên đối tượng cây được cung cấp và phát ra id đối tượng cam kết mới trên thiết bị xuất chuẩn. Thông điệp tường trình được đọc từ đầu vào tiêu chuẩn, trừ khi các tùy chọn -m hoặc -F được đưa ra.

    Biểu thức HEAD^{tree}có nghĩa là đối tượng cây tương ứng với HEAD, cụ thể là đầu của nhánh hiện tại của bạn. xem Cây-Đối tượngCam kết-Đối tượng .

  2. đặt lại nhánh hiện tại về cam kết mới

    Sau đó, git resetchỉ cần đặt lại nhánh hiện tại thành đối tượng cam kết mới được tạo.

Bằng cách này, không có gì trong không gian làm việc được chạm vào, cũng không cần rebase / squash, điều này làm cho nó thực sự nhanh. Và thời gian cần thiết không liên quan đến kích thước kho lưu trữ hoặc độ sâu lịch sử.

Biến thể: Repo mới từ mẫu dự án

Điều này rất hữu ích để tạo "cam kết ban đầu" trong một dự án mới bằng cách sử dụng một kho lưu trữ khác làm mẫu / archetype / seed / skeleton. Ví dụ:

cd my-new-project
git init
git fetch --depth=1 -n https://github.com/toolbear/panda.git
git reset --hard $(git commit-tree FETCH_HEAD^{tree} -m "initial commit")

Điều này tránh việc thêm repo mẫu dưới dạng từ xa ( originhoặc nếu không) và thu gọn lịch sử của repo mẫu vào cam kết ban đầu của bạn.


6
Cú pháp sửa đổi git (HEAD ^ {cây}) được giải thích ở đây trong trường hợp bất kỳ ai khác thắc mắc: jk.gs/gitrevutions.html
Colin Bowern

1
Điều này có thiết lập lại cả kho lưu trữ cục bộ và từ xa không, hay chỉ một trong số chúng?
aleclarson

4
@aleclarson, điều này chỉ đặt lại nhánh hiện tại trong kho lưu trữ cục bộ, sử dụng git push -fđể nhân giống.
ryenus

2
Tôi đã tìm thấy câu trả lời này trong khi tìm cách bắt đầu một dự án mới từ kho lưu trữ mẫu dự án không liên quan git clone. Nếu bạn thêm --hardvào git resetvà chuyển đổi HEADvới FETCH_HEADtrong git commit-treebạn có thể tạo một cam kết ban đầu sau khi lấy mẫu repo. Tôi đã chỉnh sửa câu trả lời với một phần ở cuối chứng minh điều này.
toolbear 7/2/2015

4
Bạn có thể thoát khỏi "Caveat" đó nhưng chỉ cần sử dụng${1?Please enter a message}
Elliot Cameron

172

Nếu tất cả những gì bạn muốn làm là đè bẹp tất cả các cam kết của bạn xuống cam kết gốc, thì trong khi

git rebase --interactive --root

có thể hoạt động, điều này không thực tế đối với một số lượng lớn các cam kết (ví dụ: hàng trăm lần xác nhận), bởi vì hoạt động rebase có thể sẽ chạy rất chậm để tạo danh sách cam kết của trình soạn thảo rebase tương tác, cũng như tự chạy rebase.

Dưới đây là hai giải pháp nhanh hơn và hiệu quả hơn khi bạn thực hiện một số lượng lớn các cam kết:

Giải pháp thay thế số 1: các chi nhánh mồ côi

Bạn có thể chỉ cần tạo một nhánh mồ côi mới ở đầu (tức là cam kết gần đây nhất) của chi nhánh hiện tại của bạn. Nhánh mồ côi này tạo thành cam kết gốc ban đầu của cây lịch sử cam kết hoàn toàn mới và riêng biệt, tương đương với tất cả các cam kết của bạn:

git checkout --orphan new-master master
git commit -m "Enter commit message for your new initial commit"

# Overwrite the old master branch reference with the new one
git branch -M new-master master

Tài liệu:

Giải pháp thay thế # 2: thiết lập lại mềm

Một giải pháp hiệu quả khác là chỉ cần sử dụng thiết lập lại hỗn hợp hoặc mềm cho cam kết gốc <root>:

git branch beforeReset

git reset --soft <root>
git commit --amend

# Verify that the new amended root is no different
# from the previous branch state
git diff beforeReset

Tài liệu:


22
Giải pháp thay thế số 1: cành mồ côi - đá!
Thomas

8
Giải pháp thay thế # 1 FTW. Chỉ cần thêm, nếu bạn muốn đẩy các thay đổi của mình lên điều khiển từ xa, hãy làm git push origin master --force.
Eddy Verbruggen

1
không nên quêngit push --force
NecipAllef

Đừng làm chi nhánh mồ côi trên mã (tức là không thực hiện giải pháp thay thế số 1 ở trên) nếu bạn chuẩn bị chuyển sang yêu cầu kéo Github mở !!! Github sẽ đóng PR của bạn vì đầu hiện tại không phải là hậu duệ của sha đầu được lưu trữ .
Andrew Mackie

Giải pháp thay thế số 1 cũng tránh được các xung đột hợp nhất có thể xảy ra khi thực hiện
ép

52
echo "message" | git commit-tree HEAD^{tree}

Điều này sẽ tạo ra một cam kết mồ côi với cây TRƯỚC và xuất ra tên của nó (SHA-1) trên thiết bị xuất chuẩn. Sau đó, chỉ cần thiết lập lại chi nhánh của bạn ở đó.

git reset SHA-1

27
git reset $(git commit-tree HEAD^{tree} -m "commit message")sẽ làm cho nó dễ dàng hơn.
ryenus

4
^ NÀY! - nên là một câu trả lời. Không hoàn toàn chắc chắn nếu đó là ý định của tác giả, nhưng là của tôi (cần một repo nguyên sơ với một cam kết duy nhất, và điều đó đã hoàn thành công việc).
chesterbr

@ryenus, giải pháp của bạn đã làm chính xác những gì tôi đang tìm kiếm. Nếu bạn thêm nhận xét của bạn làm câu trả lời, tôi sẽ chấp nhận nó.
tldr

2
Lý do tôi không tự đề xuất biến thể subshell là vì nó không hoạt động trên cmd.exe trong Windows.
kusma

Trong lời nhắc của Windows, bạn có thể phải trích dẫn tham số cuối cùng: echo "message" | git commit-tree "HEAD^{tree}"
Bernard

41

Đây là cách tôi đã kết thúc làm điều này, chỉ trong trường hợp nó làm việc cho người khác:

Hãy nhớ rằng luôn có rủi ro khi làm những việc như thế này và không bao giờ là một ý tưởng tồi để tạo ra một nhánh lưu trước khi bắt đầu.

Bắt đầu bằng cách đăng nhập

git log --oneline

Di chuyển đến cam kết đầu tiên, sao chép SHA

git reset --soft <#sha#>

Thay thế <#sha#>w / SHA được sao chép từ nhật ký

git status

Đảm bảo mọi thứ đều xanh, nếu không thì chạy git add -A

git commit --amend

Sửa đổi tất cả các thay đổi hiện tại đối với cam kết đầu tiên hiện tại

Bây giờ lực lượng đẩy chi nhánh này và nó sẽ ghi đè lên những gì ở đó.


1
Lựa chọn tuyệt vời! Thực sự đơn giản.
hai lần vào

1
Điều này là hơn cả tuyệt vời! Cảm ơn!
Matt Komarnicki

1
Lưu ý rằng điều này thực sự dường như để lại lịch sử xung quanh. Bạn mồ côi nó, nhưng nó vẫn ở đó.
Brad

Câu trả lời rất hữu ích ... nhưng bạn nên lưu ý rằng sau lệnh sửa đổi, bạn sẽ thấy mình trong trình soạn thảo vim với cú pháp đặc biệt của nó. ESC, ENTER ,: x là bạn của bạn.
Erich Kuester

Lựa chọn tuyệt vời!
danivicario

36

Tôi đọc một cái gì đó về việc sử dụng ghép nhưng không bao giờ tra nó nhiều.

Dù sao, bạn có thể đè bẹp 2 cam kết cuối cùng bằng tay với một cái gì đó như thế này:

git reset HEAD~1
git add -A
git commit --amend

4
Đây thực sự là câu trả lời tôi đang tìm kiếm, ước gì nó được chấp nhận!
Jay

Đây là một câu trả lời tuyệt vời như vậy
Master Yoda

36

Cách đơn giản nhất là sử dụng lệnh 'ống nước' update-refđể xóa các chi nhánh hiện hành.

Bạn không thể sử dụng git branch -Dvì nó có van an toàn để ngăn bạn xóa chi nhánh hiện tại.

Điều này đưa bạn trở lại trạng thái 'cam kết ban đầu' nơi bạn có thể bắt đầu với một cam kết ban đầu mới.

git update-ref -d refs/heads/master
git commit -m "New initial commit"


15

Trong một dòng gồm 6 từ

git checkout --orphan new_root_branch  &&  git commit

@AlexanderMills, bạn nên đọc git help checkoutvề--orphan
KYB

bạn có thể liên kết đến các tài liệu cho điều đó để mọi người đọc nó không phải tự tìm kiếm nó không?
Alexander Mills

1
dễ dàng. đây rồigit help checkout --orphan
kyb

8

tạo một bản sao lưu

git branch backup

thiết lập lại cam kết đã chỉ định

git reset --soft <#root>

sau đó thêm tất cả các tập tin để dàn dựng

git add .

cam kết mà không cập nhật tin nhắn

git commit --amend --no-edit

đẩy chi nhánh mới với cam kết squash để repo

git push -f

Điều này sẽ bảo tồn các thông điệp cam kết trước đó?
not2qubit

1
@ not2qubit không, điều này sẽ không lưu giữ các thông điệp cam kết trước đó, thay vì cam kết số 1, cam kết số 2, cam kết số 3, bạn sẽ nhận được tất cả các thay đổi trong các cam kết đó được đóng gói thành một cam kết số 1. Cam kết số 1 sẽ là <root>cam kết bạn đặt lại. git commit --amend --no-editsẽ cam kết tất cả các thay đổi đối với cam kết hiện tại mà <root>không cần chỉnh sửa thông báo cam kết.
David Morton

5

Để ép bằng cách sử dụng mảnh ghép

Thêm một tệp .git/info/grafts, đặt ở đó hàm băm cam kết bạn muốn trở thành root của bạn

git log bây giờ sẽ bắt đầu từ cam kết đó

Để làm cho nó 'thực sự' chạy git filter-branch


1

Câu trả lời này cải thiện cho một cặp vợ chồng ở trên (vui lòng bỏ phiếu cho họ), giả sử rằng ngoài việc tạo một cam kết (không có cha mẹ không có lịch sử), bạn cũng muốn giữ lại tất cả dữ liệu cam kết của cam kết đó:

  • Tác giả (tên và email)
  • Ngày ủy quyền
  • Commiter (tên và email)
  • Ngày cam kết
  • Tin nhắn nhật ký

Tất nhiên, cam kết-SHA của cam kết mới / đơn sẽ thay đổi, bởi vì nó đại diện cho một lịch sử (không) mới, trở thành một cam kết không có cha mẹ / gốc.

Điều này có thể được thực hiện bằng cách đọc git logvà thiết lập một số biến cho git commit-tree. Giả sử rằng bạn muốn tạo một cam kết duy nhất từ mastermột nhánh mới one-commit, giữ lại dữ liệu cam kết ở trên:

git checkout -b one-commit master ## create new branch to reset
git reset --hard \
$(eval "$(git log master -n1 --format='\
COMMIT_MESSAGE="%B" \
GIT_AUTHOR_NAME="%an" \
GIT_AUTHOR_EMAIL="%ae" \
GIT_AUTHOR_DATE="%ad" \
GIT_COMMITTER_NAME="%cn" \
GIT_COMMITTER_EMAIL="%ce" \
GIT_COMMITTER_DATE="%cd"')" 'git commit-tree master^{tree} <<COMMITMESSAGE
$COMMIT_MESSAGE
COMMITMESSAGE
')

1

Để thực hiện việc này, bạn có thể đặt lại kho lưu trữ git cục bộ của mình thành hashtag cam kết đầu tiên, vì vậy tất cả các thay đổi của bạn sau cam kết đó sẽ không được thực hiện, sau đó bạn có thể cam kết với tùy chọn --amend.

git reset your-first-commit-hashtag
git add .
git commit --amend

Và sau đó chỉnh sửa tên cam kết đầu tiên nếu cần và lưu tệp.


1

Đối với tôi, nó hoạt động như thế này: Tôi có tổng cộng 4 lần cam kết và đã sử dụng rebase tương tác:

git rebase -i HEAD~3

Cam kết đầu tiên vẫn còn và tôi đã thực hiện 3 lần cam kết mới nhất.

Trong trường hợp bạn bị kẹt trong trình chỉnh sửa xuất hiện tiếp theo, bạn sẽ thấy smth như:

pick fda59df commit 1
pick x536897 commit 2
pick c01a668 commit 3

Bạn phải thực hiện cam kết đầu tiên và đè bẹp người khác lên nó. Những gì bạn nên có là:

pick fda59df commit 1
squash x536897 commit 2
squash c01a668 commit 3

Đối với điều đó, sử dụng phím INSERT để thay đổi chế độ 'chèn' và 'chỉnh sửa'.

Để lưu và thoát khỏi trình soạn thảo sử dụng :wq. Nếu con trỏ của bạn nằm giữa các dòng cam kết đó hoặc ở một nơi khác, hãy đẩy ESC và thử lại.

Kết quả là tôi đã có hai lần cam kết: lần đầu tiên còn lại và lần thứ hai với thông báo "Đây là sự kết hợp của 3 lần cam kết."

Kiểm tra chi tiết tại đây: https://makandracards.com/makandra/527-squash-several-git-commits-into-a-single-commit


0

Tôi thường làm như thế này:

  • Đảm bảo mọi thứ đã được cam kết và ghi lại id xác nhận mới nhất trong trường hợp có lỗi xảy ra hoặc tạo một nhánh riêng làm bản sao lưu

  • Chạy git reset --soft `git rev-list --max-parents=0 --abbrev-commit HEAD`để thiết lập lại đầu của bạn để cam kết đầu tiên, nhưng giữ nguyên chỉ số của bạn. Tất cả các thay đổi kể từ lần cam kết đầu tiên sẽ xuất hiện để sẵn sàng được cam kết.

  • Chạy git commit --amend -m "initial commit"để sửa đổi cam kết của bạn thành cam kết đầu tiên và thay đổi thông điệp cam kết hoặc nếu bạn muốn giữ thông điệp cam kết hiện tại, bạn có thể chạygit commit --amend --no-edit

  • Chạy git push -fđể buộc đẩy những thay đổi của bạn

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.