Łączenie wielu zatwierdzeń w jedno przed wysłaniem

135

To pytanie dotyczy nie tylko tego, jak wykonać to zadanie, ale także tego, czy jest to dobra, czy zła praktyka w Git.

Weź pod uwagę, że lokalnie wykonuję większość prac na gałęzi głównej, ale utworzyłem gałąź tematyczną, którą nazwę „topical_xFeature”. W trakcie pracy nad "topical_xFeature" i przełączania się tam iz powrotem w celu wykonania innej pracy na gałęzi master, okazuje się, że wykonałem więcej niż jedno zatwierdzenie w gałęzi "topical_xFeature", ale między każdym zatwierdzeniem nie zrobiłem Pchać.

Po pierwsze , czy uważasz tę złą praktykę? Czy nie byłoby rozsądniej trzymać się jednego zatwierdzenia na gałąź na push? W jakich przypadkach byłoby dobrze mieć wiele zatwierdzeń na gałęzi przed wykonaniem push?

Po drugie , jak najlepiej wykonać przeniesienie wielu zatwierdzeń z gałęzi topical_xFeature do gałęzi głównej w celu wypchnięcia? Czy jest to uciążliwe, aby się tym nie martwić i po prostu wykonać push, gdy wiele zatwierdzeń zostanie wypchniętych, czy może mniej irytujące jest jakoś scalenie zatwierdzeń w jedno, a następnie wciśnięcie? Ponownie, jak to zrobić?

Todd Hopkinson
źródło

Odpowiedzi:

143

Pierwsze pytanie brzmi: nie, nie ma nic złego w wciskaniu wielu zatwierdzeń naraz. Często możesz chcieć podzielić swoją pracę na kilka małych, logicznych zatwierdzeń, ale podnieś je tylko wtedy, gdy poczujesz, że cała seria jest gotowa. Lub możesz wykonywać kilka zatwierdzeń lokalnie, gdy jesteś rozłączony, i wypychasz je wszystkie po ponownym połączeniu. Nie ma powodu, aby ograniczać się do jednego zatwierdzenia na push.

Generalnie uważam, że dobrym pomysłem jest pozostawienie każdego zatwierdzenia pojedynczej, logicznej, spójnej zmiany, która zawiera wszystko, czego potrzebuje do działania (więc nie pozostawia twojego kodu w stanie zepsutym). Jeśli masz dwa zatwierdzenia, które spowodowałyby uszkodzenie kodu, gdybyś zastosował tylko pierwszy, dobrym pomysłem może być zmiażdżenie drugiego zatwierdzenia do pierwszego. Ale jeśli masz dwa zatwierdzenia, z których każdy wprowadza rozsądną zmianę, wypchnięcie ich jako oddzielnych zatwierdzeń jest w porządku.

Jeśli chcesz zgnieść kilka zatwierdzeń razem, możesz użyć git rebase -i. Jeśli jesteś na gałęzi topical_xFeature, uciekniesz git rebase -i master. Spowoduje to otwarcie okna edytora, w którym znajduje się lista zatwierdzeń z przedrostkiem pick. Możesz zmienić wszystkie oprócz pierwszego na squash, co spowoduje, że Git zachowa wszystkie te zmiany, ale zgniat je w pierwszym zatwierdzeniu. Po wykonaniu tej czynności sprawdź masteri scal w swojej gałęzi funkcji:

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

Ewentualnie, jeśli chcesz po prostu wszystko w squasha topical_xFeaturew master, można po prostu wykonaj następujące czynności:

git checkout master
git merge --squash topical_xFeature
git commit

Który wybierzesz, zależy od Ciebie. Generalnie nie martwiłbym się posiadaniem wielu mniejszych zatwierdzeń, ale czasami nie chcesz zawracać sobie głowy dodatkowymi pomniejszymi zatwierdzeniami, więc po prostu zgniatasz je w jedno.

Brian Campbell
źródło
1
Po połączeniu z --squash nie mogę usunąć gałęzi tematu za pomocą git branch -d topic. Dlaczego git nie jest w stanie zidentyfikować, że wszystkie zmiany zostały scalone?
balki
7
@balki Ponieważ Git wykrywa, czy poprawki są scalane na podstawie tego, czy pojawią się w historii danej gałęzi. Zgniatanie zatwierdzeń zmienia je; stają się nowym zatwierdzeniem i chociaż to nowe zatwierdzenie robi to samo, co inne, Git nie może tego powiedzieć, może tylko stwierdzić, czy zatwierdzenia są takie same, jeśli mają ten sam identyfikator zatwierdzenia (SHA-1) . Więc kiedy już go zgniotłeś, musisz powiedzieć gitowi, aby usunął starą gałąź, git branch -D topicaby wymusić jej usunięcie.
Brian Campbell
68

Jest to sposób, w jaki zwykle podążam, łącząc wiele zatwierdzeń w jeden zatwierdzony kod, zanim wypchnę kod.

Aby to osiągnąć, sugeruję użycie koncepcji „ squasha ” dostarczonej przez GIT.

Wykonaj poniższe kroki.

1) git rebase -i master (zamiast mastera możesz też użyć konkretnego zatwierdzenia)

otwórz interaktywny edytor rebase, w którym pokaże wszystkie Twoje zatwierdzenia. Zasadniczo tam, gdzie musisz zidentyfikować zatwierdzenia, które chcesz połączyć w jedno zatwierdzenie.

Wyobraź sobie, że są to Twoje zmiany i pokazane w edytorze coś takiego.

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

Ważne jest, aby pamiętać, że te zatwierdzenia są wymienione w odwrotnej kolejności niż normalnie widzisz je przy użyciu polecenia log. Oznacza, że ​​starszy commit zostanie wyświetlony jako pierwszy.

2) Zmień „pick” na „squash” dla ostatnio zatwierdzonych zmian. coś jak pokazano poniżej. Robiąc to, twoje ostatnie 2 zatwierdzenia zostaną połączone z pierwszym.

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

Możesz także użyć krótkiej formy, jeśli masz dużo zatwierdzeń do połączenia:

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

do edycji użyj „i”, umożliwi to edytorowi wstawianie. Pamiętaj, że większość (najstarszych) zatwierdzeń nie może zostać zgnieciona, ponieważ nie ma poprzedniego zatwierdzenia do połączenia. Więc trzeba to wybrać lub „p”. Użyj klawisza „Esc”, aby wyjść z trybu wstawiania.

3) Teraz zapisz edytor za pomocą następującego polecenia. : wq

Kiedy to zapiszesz, masz jedno zatwierdzenie, które wprowadza zmiany we wszystkich trzech poprzednich zatwierdzeniach.

Mam nadzieję, że to ci pomoże.

Kondal Kolipaka
źródło
5
Być może jest to oczywiste dla innych, ale kiedy mówisz "git rebase -i", musisz także określić, od którego zatwierdzenia zaczynasz. Jest to coś, czego nie zdawałem sobie sprawy, kiedy próbowałem podążać za tym przykładem. W tym przykładzie będzie to „git rebase -i xxxxx”, gdzie xxxxx to zatwierdzenie chronologicznie tuż przed f7f3f6d. Kiedy to zrozumiałem, wszystko poszło dokładnie tak, jak opisano powyżej.
nukeguy
To interesujące @nukeguy, nie miałem żadnego problemu z nieokreśleniem konkretnego zatwierdzenia. Po prostu domyślnie to, co tam było.
JCrooks
Może tak jak @nukeguy, git rebase -i HEAD~2było dla mnie pomocnym miejscem na początek. Wtedy ta odpowiedź była pomocna. Następnie git statuspokazałem , że "Twoja gałąź i 'pochodzenie / cecha / xyz' rozeszły się i mają odpowiednio 1 i 1 różne zatwierdzenia." Musiałem więc git push origin feature/xyz --force-with-leasezobaczyć stackoverflow.com/a/59309553/470749 i freecodecamp.org/forum/t/...
Ryan
11

Po pierwsze : nic nie mówi ci, że masz tylko jedno zatwierdzenie na gałąź na wypychanie: wypychanie jest mechanizmem publikacji umożliwiającym publikowanie lokalnej historii (tj. Kolekcji zatwierdzeń) w zdalnym repozytorium.

Po drugie : git merge --no-ff topical_xFeaturenagrałby na master jako pojedyncze zatwierdzenie twojej pracy tematycznej, przed naciśnięciem master.
(W ten sposób będziesz w topical_xFeaturepobliżu dla dalszych ewolucji, które możesz nagrywać masterjako pojedyncze nowe zatwierdzenie przy następnym scalaniu - no-ff.
Jeśli topical_xFeaturecelem jest pozbycie się tego, git merge --squashjest to właściwa opcja, jak opisano szczegółowo w Brian Campbell „s odpowiedź .)

VonC
źródło
Myślę, że to --squashnie --no-ffjest to, czego chcesz. --no-ffstworzyłoby zatwierdzenie scalające, ale także pozostawiłoby wszystkie zatwierdzenia z topical_xFeature.
Brian Campbell
@Brian: Zgadzam się i zagłosowałem za twoją odpowiedzią, ale najpierw pomyślałem o opcji --no-ff, ponieważ chciałem zachować topical_featuregałąź i po prostu nagrać pojedyncze zatwierdzenie na mastergałęzi.
VonC
8

Przełącz się na gałąź główną i upewnij się, że jesteś na bieżąco.

git checkout master

git fetch może to być konieczne (w zależności od konfiguracji git), aby otrzymywać aktualizacje na stronie origin / master

git pull

Połącz gałąź funkcji z gałęzią główną.

git merge feature_branch

Zresetuj gałąź główną do stanu pochodzenia.

git reset origin/master

Git traktuje teraz wszystkie zmiany jako zmiany niestacjonarne. Możemy dodać te zmiany jako jedno zatwierdzenie. Dodawanie. doda również nieśledzone pliki.

git add --all

git commit

Odniesienie: https://makandracards.com/makandra/527-squash-several-git-commits-into-a-single-commit

shiva kumar
źródło
3
ta odpowiedź jest łatwa do zrozumienia i naprawdę łatwa do wizualizacji.
jokab
6
  1. Najpierw wybierz zobowiązanie, po którym chcesz, aby wszystko miało nastąpić.

    git reflog
    5976f2b HEAD@{0}: commit: Fix conflicts
    80e85a1 HEAD@{1}: commit: Add feature
    b860ddb HEAD@{2}: commit: Add something
    
  2. Zresetuj do wybranej głowy (wybrałem HEAD@{2})

    git reset b860ddb --soft
    
  3. git status (tak dla pewności)

  4. Dodaj nowe zatwierdzenie

    git commit -m "Add new commit"
    

Uwaga: HEAD@{0}& HEAD@{1}Są teraz scalane w 1 zatwierdzenie, można to zrobić również dla wielu zatwierdzeń.

git reflog powinien ponownie wyświetlić:

git reflog
5976f2b HEAD@{0}: commit: Add new commit
b860ddb HEAD@{1}: commit: Add something
Eddy Ekofo
źródło
0

Narzędzie do automatyzacji wielu zatwierdzeń w jedno

jak mówi Kondal Kolipaka . Korzystanie z „git rebase -i”

Logika „git rebase”

Używając "git rebase -i", git generuje plik git-rebase-todo w bieżącym katalogu .git / rebase-merge, a następnie wywołuje edytor git, aby umożliwić użytkownikom edycję pliku git-rebase-todo w celu przetworzenia. Narzędzie musi więc spełniać:

  1. Zmodyfikuj edytor git do dostarczonego przez nas narzędzia;
  2. Narzędzie przetwarza plik git-rebase-todo.

Zmodyfikuj domyślny edytor git

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

Dlatego narzędzie musi zmienić edytor git i przetworzyć plik git-rebase-todo. Narzędzie korzystające z Pythona poniżej:

#!/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)

Ref: https://liwugang.github.io/2019/12/30/git_commits_en.html

liwugang
źródło
4
Zmniejsz promocję swojej witryny internetowej. Zobacz też Jak nie być spamerem.
tripleee