Combinando múltiples commits en uno antes de empujar

132

Esta pregunta se refiere no solo a cómo realizar esta tarea, sino a si hacerlo es una buena o mala práctica con Git.

Considere que localmente trabajo más en la rama maestra, pero he creado una rama tópica que llamaré "topical_xFeature". En el proceso de trabajar en "topical_xFeature" y cambiar de un lado a otro para realizar otro trabajo en la rama maestra, resulta que he realizado más de un commit en la rama "topical_xFeature", pero entre cada commit, no he hecho empujar.

Primero , ¿considerarías esta mala práctica? ¿No sería más prudente mantener una confirmación por rama por inserción? ¿En qué casos sería bueno tener varias confirmaciones en una rama antes de realizar una inserción?

En segundo lugar , ¿cómo debo lograr llevar las múltiples confirmaciones de la rama topical_xFeature a la rama maestra para un impulso? ¿Es una molestia no preocuparse por eso y solo hacer el empuje donde se empujan múltiples confirmaciones, o es menos molesto fusionar las confirmaciones en una y luego empujar? De nuevo, ¿cómo hacer esto?

Todd Hopkinson
fuente

Respuestas:

140

Para su primera pregunta, no, no hay nada de malo en enviar múltiples confirmaciones a la vez. Muchas veces, es posible que desee dividir su trabajo en algunas pequeñas confirmaciones lógicas, pero solo empuje hacia arriba una vez que sienta que toda la serie está lista. O puede estar realizando varias confirmaciones localmente mientras está desconectado, y las empuja todas una vez que está conectado nuevamente. No hay razón para limitarse a un compromiso por impulso.

En general, creo que es una buena idea mantener cada confirmación de un cambio único, lógico y coherente, que incluye todo lo que necesita para funcionar (por lo tanto, no deja su código en un estado roto). Si tiene dos commits, pero causarían que el código se rompa si solo aplica el primero, podría ser una buena idea aplastar el segundo commit en el primero. Pero si tiene dos commits donde cada uno hace un cambio razonable, empujarlos como commits separados está bien.

Si quieres aplastar varias confirmaciones juntas, puedes usarlas git rebase -i. Si estás en la rama topical_xFeature, correrás git rebase -i master. Esto abrirá una ventana del editor, con un montón de confirmaciones enumeradas con el prefijo pick. Puede cambiar todos menos el primero a squash, lo que le indicará a Git que mantenga todos esos cambios, pero los aplastará en el primer commit. Una vez que hayas hecho eso, revisa mastery fusiona en tu rama de características:

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

Por otra parte, si lo que desea es todo en la calabaza topical_xFeatureen master, usted podría hacer lo siguiente:

git checkout master
git merge --squash topical_xFeature
git commit

El que elijas depende de ti. En general, no me preocuparía tener varias confirmaciones más pequeñas, pero a veces no desea molestarse con confirmaciones menores adicionales, por lo que simplemente las agrupa en una.

Brian Campbell
fuente
1
Después de fusionarme con --squash, no puedo eliminar la rama del tema con git branch -d topic. ¿Por qué git no puede identificar que todos los cambios se fusionan?
balki
77
@balki Porque Git detecta si los parches se fusionan en función de si aparecen en el historial de la rama dada. Los cambios de aplastamiento los cambian; se convierten en un nuevo commit, y aunque ese nuevo commit hace lo mismo que los otros, Git no puede decir eso, solo puede decir si los commits son iguales si tienen la misma ID de commit (SHA-1) . Entonces, una vez que lo aplastas, debes decirle a git que elimine la rama anterior git branch -D topicpara eliminarla por la fuerza.
Brian Campbell
66

Esta es la forma en que generalmente sigo para combinar varias confirmaciones en una sola confirmación antes de insertar el código.

Para lograr esto, le sugiero que use el concepto de ' squash ' proporcionado por GIT.

Sigue los pasos a continuación.

1) git rebase -i master (en lugar de master también puedes usar un commit específico)

abra el editor interactivo rebase, donde mostrará todas sus confirmaciones. Básicamente, donde necesita identificar las confirmaciones que desea fusionar en una única confirmación.

Imagina que estos son tus commits y se muestran algo como esto en el editor.

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

Es importante tener en cuenta que estas confirmaciones se enumeran en el orden opuesto al que normalmente se ve con el comando log. Significa que la confirmación anterior se mostrará primero.

2) Cambie 'pick' a 'squash' para los últimos cambios confirmados. algo como se muestra a continuación. Al hacer eso, tus últimos 2 commits se fusionarán con el primero.

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

También puede usar la forma abreviada si tiene muchas confirmaciones para combinar:

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

para editar use 'i', habilitará el editor para la inserción. Tenga en cuenta que la confirmación más alta (más antigua) no se puede aplastar ya que no hay una confirmación previa para combinar. Por lo tanto, debe ser elegido o 'p'. Use 'Esc' para salir del modo de inserción.

3) Ahora, guarde el editor con el siguiente comando. : wq

Cuando guarda eso, tiene una única confirmación que introduce los cambios de las tres confirmaciones anteriores.

Espero que esto te ayudará.

Kondal Kolipaka
fuente
55
Quizás esto sea obvio para los demás, pero cuando dices "git rebase -i", también debes especificar en qué commit empiezas. Esto es algo de lo que no me di cuenta cuando intenté seguir este ejemplo. Entonces, en este ejemplo, sería "git rebase -i xxxxx" donde xxxxx es el commit justo antes de f7f3f6d cronológicamente. Una vez que lo descubrí, todo salió exactamente como se describió anteriormente.
nukeguy
Eso es interesante @nukeguy, no tuve ningún problema al no especificar una confirmación específica. Simplemente se ajustó a lo que había allí.
JCrooks
Tal vez como @nukeguy, git rebase -i HEAD~2fue un lugar útil para comenzar. Entonces esta respuesta fue útil. Luego, mi git statusmostró "Su rama y 'origin / feature / xyz' han divergido, y tienen 1 y 1 confirmaciones diferentes cada uno, respectivamente". Así que necesitaba git push origin feature/xyz --force-with-leasever stackoverflow.com/a/59309553/470749 y freecodecamp.org/forum/t/…
Ryan
11

Primero : nada le dice que solo tenga una confirmación por rama por inserción: una inserción es un mecanismo de publicación que le permite publicar un historial local (es decir, una colección de confirmaciones) en un repositorio remoto.

Segundo : un git merge --no-ff topical_xFeatureregistro en master como un solo compromiso de su trabajo de tema, antes de presionar master.
(De esa manera, se mantiene topical_xFeaturepara futuras evoluciones, en las que puede grabar mastercomo un nuevo compromiso en la próxima fusión --no-ff.
Si deshacerse de él topical_xFeaturees el objetivo, entonces git merge --squashes la opción correcta, como se detalla en Brian Campbell la respuesta )

VonC
fuente
Creo que eso --squashno --no-ffes lo que quieres. --no-ffcrearía una confirmación de fusión, pero también dejaría todas las confirmaciones de topical_xFeature.
Brian Campbell
@Brian: estoy de acuerdo y voté tu respuesta, pero primero pensé en la opción --no-ff porque quería mantener una topical_featurebifurcación y solo registrar una confirmación en la masterbifurcación.
VonC
8

Cambie a la rama maestra y asegúrese de estar actualizado.

git checkout master

git fetch esto puede ser necesario (dependiendo de su configuración de git) para recibir actualizaciones sobre origen / maestro

git pull

Combinar la rama de características en la rama maestra.

git merge feature_branch

Restablece la rama maestra al estado de origen.

git reset origin/master

Git ahora considera todos los cambios como cambios no organizados. Podemos agregar estos cambios como una confirmación. Sumando también agregará archivos sin seguimiento.

git add --all

git commit

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

shiva kumar
fuente
3
Esta respuesta es fácil de seguir y muy fácil de visualizar.
jokab
6
  1. Primero elija qué compromiso quiere que todo venga después.

    git reflog
    5976f2b HEAD@{0}: commit: Fix conflicts
    80e85a1 HEAD@{1}: commit: Add feature
    b860ddb HEAD@{2}: commit: Add something
    
  2. Restablecer a la cabeza seleccionada (he elegido HEAD@{2})

    git reset b860ddb --soft
    
  3. git status (sólo para estar seguro)

  4. Agrega tu nueva confirmación

    git commit -m "Add new commit"
    

Nota: HEAD@{0}& HEAD@{1}Ahora se fusionan en 1 commit, esto también se puede hacer para múltiples commits.

git reflog nuevamente debería mostrar:

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

Una herramienta para automatizar múltiples confirmaciones en una

como dice Kondal Kolipaka . Usando "git rebase -i"

La lógica de "git rebase"

Cuando se usa "git rebase -i", git genera el archivo git-rebase-todo en el directorio actual .git / rebase-merge, y luego invoca el editor git para permitir a los usuarios editar el archivo git-rebase-todo para su procesamiento. Entonces la herramienta necesita cumplir:

  1. Modifique el editor de git a la herramienta que proporcionamos;
  2. La herramienta procesa el archivo git-rebase-todo.

Modificar el editor de git predeterminado

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

Entonces, la herramienta necesita cambiar el editor git y procesar el archivo git-rebase-todo. La herramienta que usa Python a continuación:

#!/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
fuente
44
Por favor, atenúe la promoción de su sitio web. Vea también Cómo no ser un spammer.
tripleee