Combinando várias confirmações em uma antes de enviar por push

130

Essa questão diz respeito não apenas a como realizar essa tarefa, mas se é uma boa ou má prática com o Git.

Considere que localmente eu trabalho mais na ramificação principal, mas criei uma ramificação tópica que chamarei de "topical_xFeature". No processo de trabalhar em "topical_xFeature" e alternar entre fazer outros trabalhos na ramificação principal, verifica-se que fiz mais de um commit na ramificação "topical_xFeature", mas entre cada commit, não fiz nenhum empurrar.

Primeiro , você consideraria essa má prática? Não seria mais sensato manter um commit por ramificação por push? Em que casos seria bom ter vários commits em uma ramificação antes de um push ser feito?

Segundo , como melhor realizo a inserção de vários commit na ramificação topical_xFeature na ramificação mestre para um push? É um incômodo não se preocupar com isso e apenas fazer o push onde vários commits são empurrados, ou é menos irritante, de alguma forma, mesclar os commits em um e depois empurrar? Mais uma vez, como fazer isso?

Todd Hopkinson
fonte

Respostas:

139

Para sua primeira pergunta, não, não há nada errado em enviar vários commits ao mesmo tempo. Muitas vezes, você pode querer dividir seu trabalho em alguns commits lógicos pequenos, mas apenas aumentá-los quando sentir que toda a série está pronta. Ou você pode fazer vários commits localmente enquanto desconectado, e envia todos eles quando estiver conectado novamente. Não há razão para limitar-se a um commit por push.

Geralmente, acho que é uma boa ideia manter cada confirmação de uma alteração única, lógica e coerente, que inclui tudo o que precisa para funcionar (para que não deixe seu código em um estado quebrado). Se você tiver duas confirmações, mas elas causariam a quebra do código se você aplicasse apenas a primeira, pode ser uma boa ideia colocar a segunda confirmação na primeira. Mas se você tiver dois commits, em que cada um deles faça uma alteração razoável, forçá-los como commits separados é bom.

Se você deseja compactar vários commits juntos, poderá usar git rebase -i. Se você estiver no ramo topical_xFeature, você executaria git rebase -i master. Isso abrirá uma janela do editor, com um monte de confirmações listadas com o prefixo de pick. Você pode alterar todas, exceto a primeira squash, o que instruirá o Git a manter todas essas alterações, mas reduza-as para o primeiro commit. Depois de fazer isso, confira mastere junte-se ao seu ramo de recursos:

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

Alternativamente, se você quiser apenas para tudo squash no topical_xFeatureem master, você poderia apenas fazer o seguinte:

git checkout master
git merge --squash topical_xFeature
git commit

Qual você escolhe depende de você. Geralmente, eu não me preocupo em ter vários commits menores, mas às vezes você não quer se preocupar com commits extras menores, então basta esmagá-los em um.

Brian Campbell
fonte
1
Depois de mesclar com --squash, não consigo excluir o ramo de tópico git branch -d topic. Por que o git não é capaz de identificar que todas as alterações foram mescladas?
22412
7
@balki Porque o Git detecta se os patches são mesclados com base no fato de eles aparecerem no histórico do ramo especificado. Squashing comete alterações; eles se tornam um novo commit e, embora esse novo commit faça a mesma coisa que os outros, o Git não pode dizer isso, só pode dizer se os commit são iguais se eles tiverem o mesmo ID de commit (SHA-1) . Portanto, depois de esmagá-lo, você precisa dizer ao git para excluir o ramo antigo e git branch -D topicexcluí-lo à força.
Brian Campbell
66

É assim que geralmente sigo para combinar vários Commits em um único commit antes de enviar o código por push.

Para conseguir isso, sugiro que você use o conceito de ' squash ' fornecido pelo GIT.

Siga os passos abaixo.

1) git rebase -i master (em vez de master, você também pode usar um commit específico)

abra o editor interativo rebase, onde ele mostrará todos os seus commits. Basicamente, onde você precisa identificar as confirmações que deseja mesclar em uma única confirmação.

Imagine que esses são seus commits e mostrem algo assim no editor.

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

É importante observar que esses commits estão listados na ordem oposta à que você normalmente os vê usando o comando log. Significa que o commit mais antigo será mostrado primeiro.

2) Altere 'pick' para 'squash' para as últimas alterações confirmadas. algo como mostrado abaixo. Fazendo isso, seus dois últimos commits serão mesclados com o primeiro.

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

Você também pode usar o formato abreviado se tiver muitos commits 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', ele permitirá que o editor seja inserido. Lembre-se de que a maioria das confirmações (mais antigas) não pode ser esmagada, pois não há confirmação anterior com a qual combinar. Portanto, ele deve ser escolhido ou 'p'. Use 'Esc' para sair do modo de inserção.

3) Agora, salve o editor com o seguinte comando. : wq

Quando você salva isso, você tem uma única confirmação que introduz as alterações das três confirmações anteriores.

Espero que isso ajude você.

Kondal Kolipaka
fonte
5
Talvez isso seja óbvio para os outros, mas quando você diz "git rebase -i", também precisa especificar em qual commit você inicia. Isso é algo que eu não percebi quando tentei seguir este exemplo. Portanto, neste exemplo, seria "git rebase -i xxxxx" onde xxxxx é o commit antes do f7f3f6d cronologicamente. Depois que eu descobri isso, tudo funcionou exatamente como descrito acima.
Nukeguy 22/04
Isso é interessante, @nukeguy, não tive nenhum problema ao especificar um commit específico. O padrão era o que estava lá.
JCrooks
Talvez como @nukeguy, git rebase -i HEAD~2fosse um lugar útil para eu começar. Então esta resposta foi útil. Então, meu git statusshow "Sua ramificação e 'origem / recurso / xyz' divergiram e têm 1 e 1 confirmações diferentes cada, respectivamente." Então, eu precisava git push origin feature/xyz --force-with-leasever stackoverflow.com/a/59309553/470749 e freecodecamp.org/forum/t/…
Ryan
11

Primeiro : nada diz para você ter apenas uma confirmação por filial por envio: um envio é um mecanismo de publicação que permite publicar um histórico local (por exemplo, uma coleção de confirmações) em um repositório remoto.

Segundo : um git merge --no-ff topical_xFeaturegravaria no master como um único commit do seu tópico de trabalho, antes de enviar master.
(Dessa forma, você fica topical_xFeaturepor aqui para mais evoluções, que podem ser gravadas mastercomo um único novo commit na próxima mesclagem --no-ff.
Se livrar- topical_xFeaturese do objetivo git merge --squashé a opção certa, conforme detalhado em Brian Campbell da resposta .)

VonC
fonte
Eu acho que --squashnão --no-ffé o que você quer. --no-ffcriaria uma consolidação de mesclagem, mas também deixaria todas as confirmações de topical_xFeature.
27911 Brian Campbell
@Brian: Eu concordo e votei positivamente na sua resposta, mas primeiro pensei na opção --no-ff porque queria manter o topical_featurebranch por aí e apenas gravar um único commit no masterbranch.
VonC 19/04
8

Alterne para o ramo principal e verifique se está atualizado.

git checkout master

git fetch isso pode ser necessário (dependendo da sua configuração do git) para receber atualizações sobre a origem / mestre

git pull

Mesclar o ramo do recurso no ramo mestre.

git merge feature_branch

Redefina a ramificação principal para o estado de origem.

git reset origin/master

O Git agora considera todas as mudanças como mudanças não-estágios. Podemos adicionar essas alterações como uma confirmação. Adicionando. também adicionará arquivos não rastreados.

git add --all

git commit

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

shiva kumar
fonte
3
Essa resposta é fácil de seguir e realmente fácil de visualizar.
Jokab
6
  1. Primeiro escolha qual commit você deseja que tudo ocorra.

    git reflog
    5976f2b HEAD@{0}: commit: Fix conflicts
    80e85a1 HEAD@{1}: commit: Add feature
    b860ddb HEAD@{2}: commit: Add something
    
  2. Redefinir para a cabeça selecionada (eu escolhi HEAD@{2})

    git reset b860ddb --soft
    
  3. git status (só pra ter certeza)

  4. Adicione seu novo commit

    git commit -m "Add new commit"
    

Nota: HEAD@{0}& HEAD@{1}Agora estão mesclados em 1 confirmação, isso também pode ser feito para várias confirmações.

git reflog novamente deve exibir:

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

Uma ferramenta para automatizar várias confirmações em um

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

A lógica do "git rebase"

Ao usar "git rebase -i", o git gera o arquivo git-rebase-todo no diretório .git / rebase-merge atual e chama o editor git para permitir que os usuários editem o arquivo git-rebase-todo para processamento. Portanto, a ferramenta precisa atender:

  1. Modifique o editor git para a ferramenta que fornecemos;
  2. A ferramenta processa o arquivo git-rebase-todo.

Modifique o editor git padrão

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

Portanto, a ferramenta precisa alterar o editor git e processar o arquivo git-rebase-todo. A ferramenta usando python abaixo:

#!/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
fonte
4
Reduza a promoção do seu site. Veja também Como não ser um spammer.
Tripleee