Kết hợp nhiều cam kết thành một trước khi đẩy


130

Câu hỏi này không chỉ liên quan đến cách hoàn thành nhiệm vụ này, mà còn là liệu thực hành tốt hay xấu với Git.

Hãy xem xét rằng tại địa phương tôi làm hầu hết các công việc trên nhánh chính, nhưng tôi đã tạo một nhánh tại chỗ mà tôi sẽ gọi là "topical_xFeature". Trong quá trình làm việc trên "topical_xFeature" và chuyển đổi qua lại để thực hiện các công việc khác trên nhánh chính, hóa ra tôi đã thực hiện nhiều hơn một cam kết trên nhánh "topical_xFeature", nhưng giữa mỗi lần cam kết, tôi đã không thực hiện đẩy.

Đầu tiên , bạn sẽ xem xét thực hành xấu này? Nó sẽ không khôn ngoan hơn khi gắn bó với một cam kết trên mỗi nhánh mỗi lần đẩy? Trong trường hợp nào sẽ tốt nếu có nhiều lần xác nhận trên một nhánh trước khi thực hiện một cú đẩy?

Thứ hai , làm thế nào để tôi hoàn thành tốt nhất việc đưa nhiều cam kết trên nhánh topical_xFeature vào nhánh chính để đẩy? Có phải là phiền toái khi không lo lắng về nó và chỉ thực hiện việc đẩy mà nhiều cam kết được đẩy, hoặc ít phiền toái hơn bằng cách nào đó hợp nhất các cam kết thành một và sau đó đẩy? Một lần nữa, làm thế nào để làm điều này?

Câu trả lời:


139

Đối với câu hỏi đầu tiên của bạn, không, không có gì sai khi đẩy nhiều lần xác nhận cùng một lúc. Nhiều lần, bạn có thể muốn chia công việc của mình thành một vài cam kết nhỏ, hợp lý, nhưng chỉ đẩy chúng lên một khi bạn cảm thấy như toàn bộ loạt phim đã sẵn sàng. Hoặc bạn có thể thực hiện một số cam kết cục bộ trong khi bị ngắt kết nối và bạn đẩy tất cả chúng sau khi bạn kết nối lại. Không có lý do gì để giới hạn bản thân trong một lần cam kết.

Tôi thường thấy rằng nên giữ cho mỗi cam kết một thay đổi duy nhất, hợp lý, mạch lạc, bao gồm mọi thứ nó cần để làm việc (vì vậy, nó không để mã của bạn ở trạng thái bị hỏng). Nếu bạn có hai lần xác nhận, nhưng chúng sẽ khiến mã bị phá vỡ nếu bạn chỉ áp dụng lần đầu tiên, đó có thể là một ý tưởng tốt để nén cam kết thứ hai vào lần đầu tiên. Nhưng nếu bạn có hai cam kết trong đó mỗi lần thực hiện một thay đổi hợp lý, đẩy chúng thành các cam kết riêng biệt là ổn.

Nếu bạn muốn kết hợp nhiều cam kết với nhau, bạn có thể sử dụng git rebase -i. Nếu bạn ở chi nhánh topical_xFeature, bạn sẽ chạy git rebase -i master. Điều này sẽ mở một cửa sổ soạn thảo, với một loạt các cam kết được liệt kê bởi tiền tố pick. Bạn có thể thay đổi tất cả ngoại trừ lần đầu tiên squash, điều này sẽ nói với Git để giữ tất cả những thay đổi đó, nhưng nén chúng thành cam kết đầu tiên. Sau khi bạn thực hiện điều đó, hãy kiểm tra mastervà hợp nhất trong nhánh tính năng của bạn:

git checkout topical_xFeature
git rebase -i master
git checkout master
git merge topical_xFeature

Ngoài ra, nếu bạn chỉ muốn nén mọi thứ topical_xFeaturevào master, bạn có thể làm như sau:

git checkout master
git merge --squash topical_xFeature
git commit

Cái nào bạn chọn là tùy thuộc vào bạn. Nói chung, tôi sẽ không lo lắng về việc có nhiều cam kết nhỏ hơn, nhưng đôi khi bạn không muốn làm phiền với các cam kết nhỏ hơn, vì vậy bạn chỉ cần ép chúng thành một.


1
Sau khi tôi hợp nhất với --squash, tôi không thể xóa nhánh chủ đề với git branch -d topic. Tại sao git không thể xác định rằng tất cả các thay đổi được hợp nhất?
balki

7
@balki Bởi vì Git phát hiện xem các bản vá có được hợp nhất hay không dựa trên việc chúng có xuất hiện trong lịch sử của chi nhánh nhất định hay không. Squashing cam kết thay đổi chúng; chúng trở thành một cam kết mới và trong khi cam kết mới đó thực hiện tương tự như các cam kết khác, Git không thể nói rằng, nó chỉ có thể cho biết các cam kết có giống nhau hay không nếu chúng có cùng một ID cam kết (SHA-1) . Vì vậy, một khi bạn đè bẹp nó, bạn cần nói với git để xóa nhánh cũ git branch -D topicđể xóa nó.
Brian Campbell

66

Đây là cách tôi thường làm theo để kết hợp nhiều Cam kết thành một cam kết duy nhất trước khi tôi đẩy mã.

Để đạt được điều này, tôi khuyên bạn nên sử dụng khái niệm ' squash ' do GIT cung cấp.

Thực hiện theo các bước dưới đây.

1) git rebase -i master (thay vì master bạn cũng có thể sử dụng một cam kết cụ thể)

mở trình soạn thảo tương tác rebase, nơi nó sẽ hiển thị tất cả các cam kết của bạn. Về cơ bản, nơi bạn cần xác định các cam kết mà bạn muốn hợp nhất thành một cam kết duy nhất.

Hãy tưởng tượng đây là những cam kết của bạn và hiển thị một cái gì đó như thế này trong trình chỉnh sửa.

pick f7f3f6d changed my name a bit    
pick 310154e updated README formatting and added blame   
pick a5f4a0d added cat-file  

Điều quan trọng cần lưu ý là các cam kết này được liệt kê theo thứ tự ngược lại so với bạn thường thấy chúng bằng lệnh log. Có nghĩa là, các cam kết cũ hơn sẽ được hiển thị đầu tiên.

2) Thay đổi 'pick' thành 'squash' cho những thay đổi được cam kết cuối cùng. một cái gì đó như hiển thị dưới đây. Làm như vậy, 2 lần xác nhận cuối cùng của bạn sẽ được hợp nhất với lần đầu tiên.

pick f7f3f6d changed my name a bit         
squash 310154e updated README formatting and added blame   
squash a5f4a0d added cat-file

Bạn cũng có thể sử dụng biểu mẫu ngắn nếu bạn có nhiều cam kết để kết hợp:

p f7f3f6d changed my name a bit         
s 310154e updated README formatting and added blame   
s a5f4a0d added cat-file

để chỉnh sửa sử dụng 'i', nó sẽ cho phép trình chỉnh sửa chèn. Hãy ghi nhớ rằng hầu hết các cam kết (cũ nhất) không thể bị xóa vì không có cam kết trước đó để kết hợp với. Vì vậy, nó phải được chọn hoặc 'p'. Sử dụng 'Esc' để thoát chế độ chèn.

3) Bây giờ, lưu trình soạn thảo bằng lệnh sau. : wq

Khi bạn lưu nó, bạn có một cam kết duy nhất giới thiệu các thay đổi của cả ba lần xác nhận trước đó.

Hy vọng điều này sẽ giúp bạn.


5
Có lẽ điều này là hiển nhiên đối với người khác, nhưng khi bạn nói "git rebase -i", bạn cũng cần xác định cam kết nào bạn bắt đầu. Đây là điều tôi không nhận ra khi tôi thử làm theo ví dụ này. Vì vậy, trong ví dụ này, nó sẽ là "git rebase -i xxxxx" trong đó xxxxx là cam kết ngay trước f7f3f6d theo trình tự thời gian. Khi tôi nhận ra điều đó, mọi thứ diễn ra chính xác như được mô tả ở trên.
nukeguy

Thật thú vị @nukeguy, tôi không có vấn đề gì khi không chỉ định một cam kết cụ thể. Nó chỉ mặc định những gì đã có.
JCrooks

Có lẽ như @nukeguy, git rebase -i HEAD~2là một nơi hữu ích để tôi bắt đầu. Sau đó, câu trả lời này là hữu ích. Sau đó, tôi git statuscho thấy "Chi nhánh của bạn và 'origin / Feature / xyz' đã chuyển hướng và lần lượt có 1 và 1 cam kết khác nhau." Vì vậy, tôi cần phải git push origin feature/xyz --force-with-leasexem stackoverflow.com/a/59309553/470749freecodecamp.org/forum/t/ Kẻ
Ryan

11

Thứ nhất : không có gì cho bạn biết chỉ có một cam kết cho mỗi chi nhánh cho mỗi lần đẩy: đẩy là một cơ chế xuất bản cho phép bạn xuất bản lịch sử địa phương (tức là một tập hợp các cam kết) trên một repo từ xa.

Thứ hai : a git merge --no-ff topical_xFeaturesẽ ghi lại trên master như một cam kết duy nhất của bạn làm việc chủ đề, trước khi đẩy master.
(Bằng cách đó, bạn tiếp topical_xFeaturetục phát triển thêm, bạn có thể ghi lại masterdưới dạng một cam kết mới duy nhất trong lần hợp nhất tiếp theo - không-ff.
Nếu loại bỏ topical_xFeaturelà mục tiêu, thì đó git merge --squashlà lựa chọn phù hợp, như chi tiết trong Brian Campbell 's câu trả lời .)


Tôi nghĩ rằng --squash, không phải --no-fflà những gì bạn muốn. --no-ffsẽ tạo ra một cam kết hợp nhất, nhưng cũng để lại tất cả các cam kết từ đó topical_xFeature.
Brian Campbell

@Brian: Tôi đồng ý và nâng cao câu trả lời của bạn, nhưng trước tiên tôi nghĩ đến tùy chọn --no-ff vì tôi muốn giữ topical_featurechi nhánh xung quanh và chỉ ghi lại một cam kết duy nhất trên masterchi nhánh.
VonC

8

Chuyển sang nhánh chính và đảm bảo bạn được cập nhật.

git checkout master

git fetch điều này có thể cần thiết (tùy thuộc vào cấu hình git của bạn) để nhận cập nhật về nguồn gốc / bản gốc

git pull

Hợp nhất nhánh tính năng vào nhánh chính.

git merge feature_branch

Đặt lại nhánh chính về trạng thái gốc.

git reset origin/master

Git hiện coi tất cả các thay đổi là những thay đổi không có căn cứ. Chúng tôi có thể thêm những thay đổi này như một cam kết. Thêm. cũng sẽ thêm các tập tin chưa được theo dõi.

git add --all

git commit

Tham chiếu: https://makandracards.com/makandra/527-squash-several-git-commits-into-a-single-commit


3
Câu trả lời này rất dễ làm theo và thực sự dễ hình dung.
jokab

6
  1. Đầu tiên chọn cam kết mà bạn muốn mọi thứ đến sau.

    git reflog
    5976f2b HEAD@{0}: commit: Fix conflicts
    80e85a1 HEAD@{1}: commit: Add feature
    b860ddb HEAD@{2}: commit: Add something
    
  2. Đặt lại về đầu đã chọn của bạn (Tôi đã chọn HEAD@{2})

    git reset b860ddb --soft
    
  3. git status (chỉ để được chắc chắn)

  4. Thêm cam kết mới của bạn

    git commit -m "Add new commit"
    

Lưu ý: HEAD@{0}& HEAD@{1}Hiện được hợp nhất thành 1 cam kết, điều này cũng có thể được thực hiện cho nhiều lần xác nhận.

git reflog một lần nữa sẽ hiển thị:

git reflog
5976f2b HEAD@{0}: commit: Add new commit
b860ddb HEAD@{1}: commit: Add something

0

Một công cụ để tự động hóa nhiều cam kết thành một

như Kondal Kolipaka nói . Sử dụng "git rebase -i"

Logic của "git rebase"

Khi sử dụng "git rebase -i", git tạo tệp git-rebase-todo trong thư mục .git / rebase-merge hiện tại, sau đó gọi trình soạn thảo git để cho phép người dùng chỉnh sửa tệp git-rebase-todo để xử lý. Vì vậy, công cụ cần phải đáp ứng:

  1. Sửa đổi trình soạn thảo git thành công cụ mà chúng tôi cung cấp;
  2. Công cụ xử lý tệp git-rebase-todo.

Sửa đổi trình soạn thảo git mặc định

git config core.editor #show current default git editor
git config --local --replace-all  core.editor NEW_EDITOR # set the local branch using NEW_EDITOR as git editor

Vì vậy, công cụ cần thay đổi trình soạn thảo git và xử lý tệp git-rebase-todo. Công cụ sử dụng python dưới đây:

#!/usr/bin/env python3
#encoding: UTF-8

import os
import sys

def change_editor(current_file):
    os.system("git config --local --replace-all  core.editor " + current_file) # Set current_file as git editor
    os.system("git rebase -i") # execute the "git rebase -i" and will invoke the python file later with git-rebase-todo file as argument
    os.system("git config --local --replace-all core.editor vim") # after work reset the git editor to default

def rebase_commits(todo_file):
    with open(todo_file, "r+") as f:
        contents = f.read() # read git-rebase-todo's content
        contents = contents.split("\n")
        first_commit = True
        f.truncate()
        f.seek(0)
        for content in contents:
            if content.startswith("pick"):
                if first_commit:
                    first_commit = False
                else:
                    content = content.replace("pick", "squash") # replace the pick to squash except for the first pick
            f.write(content + "\n")

def main(args):
    if len(args) == 2:
        rebase_commits(args[1]) # process the git-rebase-todo
    else:
        change_editor(os.path.abspath(args[0])) # set git editor

if __name__ == "__main__":
    main(sys.argv)

Tham chiếu: https://liwugang.github.io/2019/12/30/git_commits_en.html


4
Vui lòng giảm quảng cáo trang web của bạn. Xem thêm Làm thế nào để không trở thành một người gửi thư rác.
tripleee
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.