Hợp nhất (với squash) tất cả các thay đổi từ một nhánh khác dưới dạng một cam kết duy nhất


479

Trong Git, có cách nào để hợp nhất tất cả các thay đổi từ nhánh này sang nhánh khác, nhưng ép lại thành một cam kết cùng một lúc không?

Tôi thường làm việc trên một tính năng mới trong một chi nhánh riêng biệt và sẽ thường xuyên cam kết / đẩy - chủ yếu để sao lưu hoặc chuyển những gì tôi đang làm việc sang một máy khác. Chủ yếu là những cam kết nói "Tính năng xxx WIP" hoặc một cái gì đó dư thừa.

Khi công việc đó kết thúc và tôi muốn hợp nhất chi nhánh WIP trở lại thành chủ, tôi muốn loại bỏ tất cả các cam kết trung gian đó và chỉ cần có một cam kết sạch duy nhất.

Có cách nào làm dễ hơn không?

Ngoài ra, làm thế nào về một lệnh mà tất cả các cam kết trên một nhánh kể từ điểm nó được phân nhánh?

Câu trả lời:


601

Một lựa chọn khác là git merge --squash <feature branch>cuối cùng làm a git commit.

Từ hợp nhất Git

--squash

--no-squash

Tạo cây làm việc và trạng thái chỉ mục như thể một sự hợp nhất thực sự đã xảy ra (ngoại trừ thông tin hợp nhất), nhưng không thực sự thực hiện một cam kết hoặc di chuyển HEAD, cũng không ghi lại để tạo ra một lệnh kết hợp $GIT_DIR/MERGE_HEADtiếp theo git commit. Điều này cho phép bạn tạo một cam kết duy nhất trên đầu nhánh hiện tại có tác dụng tương tự như hợp nhất một nhánh khác (hoặc nhiều hơn trong trường hợp bạch tuộc).


1
Tính năng tuyệt vời! Tôi yêu git. Mặc dù bây giờ tôi chắc chắn sẽ sử dụng điều này trong tương lai, tôi vẫn khuyên bạn nên tìm hiểu cách của bạn xung quanh rebase -i. Đó là một kỹ năng tốt để có, chỉ trong trường hợp bạn thực sự muốn làm cho họ nhiều hơn chỉ là một cam kết.
Will Buck

4
Một lời cảnh báo: điều này hoạt động, nhưng thông điệp cam kết mặc định bao gồm nhật ký từ chi nhánh được hợp nhất. Vấn đề là nó trông giống với định dạng mà bạn thường thấy trong đó toàn bộ văn bản hiển thị không thực sự trở thành một phần của thông điệp cam kết, nhưng trong trường hợp này thì có. Vì vậy, nếu bạn không muốn tất cả điều đó, bạn cần xóa thủ công tất cả khỏi tin nhắn cam kết của mình. Tôi nên đã thử nghiệm điều này trước khi sử dụng nó ...
still_dreaming_1

23
Điều đó, và, được cảnh báo rằng chi nhánh sẽ không được coi là hợp nhất. stackoverflow.com/questions/19308790/ khăn
Ryan

3
IMHO cái này nên được gọirebase --squash
Andy

vì vậy (vì nó không thực sự hợp nhất nhánh tính năng) nên điều này sẽ phù hợp nếu bạn định xóa nhánh tính năng sau khi xác nhận. Đúng không? (Tôi không phải là chuyên gia về git)
Andrew Spencer

214

Tìm thấy rồi! Lệnh hợp nhất có một --squashtùy chọn

git checkout master
git merge --squash WIP

tại thời điểm này, mọi thứ được hợp nhất, có thể xung đột, nhưng không được cam kết. Vì vậy, bây giờ tôi có thể:

git add .
git commit -m "Merged WIP"

2
những gì hiện git add .làm gì?
Michael Potter

1
@MichaelPotter Nó thêm tất cả các tệp và thay đổi
Daksh Shah

2
git add .thêm tất cả các tệp không bị bỏ qua trong thư mục hiện tại, tôi sẽ cảnh giác khi chọn các tệp không mong muốn theo cách này.
Jake Cobb

7
Là một thay thế cho git add .bạn có thể sử dụng git add -uđể chỉ thêm các tệp đã được thêm vào cây.
Brandon Ogle

19
Đề xuất rằng một "git add." cũng được thực hiện là khó hiểu. Khi tôi thực hiện "git merge --squash WIP", nó đã có các thay đổi bị nén trong chỉ mục. Tất cả những gì cần thiết là cam kết chúng. Làm một "git add." sẽ thêm các thay đổi xảy ra trong thư mục làm việc, nhưng không phải là một phần của nhánh tính năng. Câu hỏi là làm thế nào để cam kết các thay đổi trong nhánh tính năng như một cam kết.
John Pankowicz

30

Hãy thử git rebase -i mastertrên nhánh tính năng của bạn. Sau đó, bạn có thể thay đổi tất cả trừ một 'chọn' thành 'squash' để kết hợp các cam kết. Xem cam kết squash với rebase

Cuối cùng, bạn có thể thực hiện hợp nhất từ ​​nhánh chính.


8
Vâng, nó hoạt động, nhưng tôi không muốn rắc rối của rebase tương tác. Tôi chỉ muốn tất cả mọi thứ kể từ khi chi nhánh phẳng.
Brad Robinson

2
+1 Điều này làm cho một lịch sử sạch sẽ. Việc xác định và quản lý các cam kết dễ dàng hơn như các bản vá, thẻ, câu chuyện, v.v.
Ryan

2

Sử dụng git merge --squash <feature branch>như câu trả lời được chấp nhận cho thấy có mẹo nhưng nó sẽ không hiển thị nhánh được hợp nhất như thực sự được hợp nhất.

Do đó, một giải pháp tốt hơn nữa là:

  • Tạo một nhánh mới từ chủ mới nhất
  • Hợp nhất <feature branch>vào phần trên bằng cách sử dụnggit merge --squash
  • Hợp nhất chi nhánh mới được tạo thành chủ

Wiki này giải thích các thủ tục chi tiết.


0

Tôi đã tạo bí danh git của riêng tôi để làm chính xác điều này. Tôi đang gọi nógit freebase ! Nó sẽ lấy nhánh tính năng lộn xộn, không thể sửa chữa hiện có của bạn và tạo lại nó để nó trở thành một nhánh mới có cùng tên với các cam kết của nó bị ép thành một cam kết và được bật lại trên nhánh mà bạn chỉ định (theo mặc định). Cuối cùng, nó sẽ cho phép bạn sử dụng bất kỳ thông điệp cam kết nào bạn muốn cho chi nhánh "miễn phí" mới của mình.

Cài đặt nó bằng cách đặt bí danh sau trong .gitconfig của bạn:

[alias]
  freebase = "!f() { \
    TOPIC="$(git branch | grep '\\*' | cut -d ' ' -f2)"; \
    NEWBASE="${1:-master}"; \
    PREVSHA1="$(git rev-parse HEAD)"; \
    echo "Freebaseing $TOPIC onto $NEWBASE, previous sha1 was $PREVSHA1"; \
    echo "---"; \
    git reset --hard "$NEWBASE"; \
    git merge --squash "$PREVSHA1"; \
    git commit; \
  }; f"

Sử dụng nó từ nhánh tính năng của bạn bằng cách chạy: git freebase <new-base>

Tôi chỉ thử nghiệm điều này một vài lần, vì vậy hãy đọc nó trước và chắc chắn rằng bạn muốn chạy nó. Như một biện pháp an toàn nhỏ, nó sẽ in sha1 bắt đầu để bạn có thể khôi phục chi nhánh cũ nếu có sự cố.

Tôi sẽ duy trì nó trong repo dotfiles của tôi trên github: https://github.com/stevecrozz/dotfiles/blob/master/.gitconfig


Làm việc như một niềm hạnh phúc! bạn cũng có thể có một cái nhìn tại evernote.com/shard/s52/sh/7f8f4ff1-9a68-413f-9225-c49e3ee2fafd/...
Ilya Sheershoff

-1

git merge --squash <feature branch> là một lựa chọn tốt. "Git commit" cho bạn biết tất cả các thông báo cam kết chi nhánh với sự lựa chọn của bạn để giữ nó.

Đối với hợp nhất ít cam kết.

git merge do x times --git reset HEAD ^ --soft rồi git commit.

Rủi ro - các tập tin bị xóa có thể trở lại.


-5

Bạn có thể làm điều này với lệnh "rebase". Hãy gọi các nhánh là "chính" và "tính năng":

git checkout feature
git rebase main

Lệnh rebase sẽ phát lại tất cả các xác nhận trên "tính năng" dưới dạng một cam kết với cha mẹ bằng "chính".

Bạn có thể muốn chạy git merge main trước git rebase mainnếu "chính" đã thay đổi kể từ khi "tính năng" được tạo (hoặc kể từ lần hợp nhất gần đây nhất). Bằng cách đó, bạn vẫn có lịch sử đầy đủ của mình trong trường hợp bạn có xung đột hợp nhất.

Sau khi rebase, bạn có thể hợp nhất nhánh của bạn thành chính, điều này sẽ dẫn đến một sự hợp nhất chuyển tiếp nhanh:

git checkout main
git merge feature

Xem trang rebase của Hiểu Git về mặt khái niệm để có cái nhìn tổng quan tốt


Điều này đã không làm việc cho tôi. Tôi vừa tạo một repo thử nghiệm đơn giản, với một nhánh WIP và đã thử ở trên và có xung đột hợp nhất (mặc dù tôi đã không thực hiện bất kỳ thay đổi nào trên master).
Brad Robinson

Nếu tính năng được tạo từ chính (git checkout -b tính năng chính) và bạn đã có một sự hợp nhất gần đây từ chính, bạn không nên có xung đột từ cuộc nổi loạn
NamshubWriter

OK, thử lại lần nữa. Không có xung đột lần này, nhưng lịch sử đã không bị phá hủy.
Brad Robinson

Nhìn lại tài liệu hợp nhất git, bạn đã đúng, một số cam kết sẽ ở lại. Nếu bạn đã thực hiện các phép hợp nhất trước đó từ "chính" thành "tính năng" thì rebase sẽ xóa một số trong số chúng, nhưng không phải tất cả.
NamshubWriter

Chỉ cần nhớ rằng việc nổi loạn có thể khá nguy hiểm nếu nhánh tính năng đã được xuất bản trước đó. Thông tin thêm về câu hỏi SO này .
Robert Rossmann
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.