Por que o git-rebase me causa conflitos de mesclagem quando tudo o que estou fazendo é esmagar os commits?

146

Temos um repositório Git com mais de 400 confirmações, das quais algumas dezenas foram tentativas e erros. Queremos limpar esses commits, comprimindo muitos deles em um único commit. Naturalmente, o git-rebase parece o caminho a percorrer. Meu problema é que ele acaba com conflitos de mesclagem, e esses conflitos não são fáceis de resolver. Não entendo por que deve haver algum conflito, já que estou apenas esmagando commits (não excluindo ou reorganizando). Muito provavelmente, isso demonstra que não estou entendendo completamente como o git-rebase faz suas abóboras.

Aqui está uma versão modificada dos scripts que estou usando:


repo_squash.sh (este é o script que é realmente executado):


rm -rf repo_squash
git clone repo repo_squash
cd repo_squash/
GIT_EDITOR=../repo_squash_helper.sh git rebase --strategy theirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a

repo_squash_helper.sh (este script é usado apenas por repo_squash.sh):


if grep -q "pick " $1
then
#  cp $1 ../repo_squash_history.txt
#  emacs -nw $1
  sed -f ../repo_squash_list.txt < $1 > $1.tmp
  mv $1.tmp $1
else
  if grep -q "initial import" $1
  then
    cp ../repo_squash_new_message1.txt $1
  elif grep -q "fixing bad import" $1
  then
    cp ../repo_squash_new_message2.txt $1
  else
    emacs -nw $1
  fi
fi

repo_squash_list.txt: (este arquivo é usado apenas por repo_squash_helper.sh)


# Initial import
s/pick \(251a190\)/squash \1/g
# Leaving "Needed subdir" for now
# Fixing bad import
s/pick \(46c41d1\)/squash \1/g
s/pick \(5d7agf2\)/squash \1/g
s/pick \(3da63ed\)/squash \1/g

Vou deixar o conteúdo da "nova mensagem" à sua imaginação. Inicialmente, eu fiz isso sem a opção "- estrategy his" (ou seja, usando a estratégia padrão, que se eu entender corretamente a documentação é recursiva, mas não tenho certeza de qual estratégia recursiva é usada), e também não ' não funciona. Além disso, devo salientar que, usando o código comentado em repo_squash_helper.sh, salvei o arquivo original no qual o script sed trabalha e executei o script sed para garantir que ele estivesse fazendo o que queria ( isso foi). Novamente, eu nem sei por que haveria um conflito, então parece que não importa tanto qual estratégia é usada. Qualquer conselho ou insight seria útil, mas principalmente eu quero fazer com que essa compressão funcione.

Atualizado com informações adicionais da discussão com o Jefromi:

Antes de trabalhar em nosso enorme repositório "real", usei scripts semelhantes em um repositório de teste. Era um repositório muito simples e o teste funcionou corretamente.

A mensagem que recebo quando falha é:

Finished one cherry-pick.
# Not currently on any branch.
nothing to commit (working directory clean)
Could not apply 66c45e2... Needed subdir

Esta é a primeira escolha após o primeiro squash commit. A execução git statusproduz um diretório de trabalho limpo. Se eu fizer um git rebase --continue, recebo uma mensagem muito semelhante após mais algumas confirmações. Se eu fizer de novo, recebo outra mensagem muito semelhante depois de algumas dezenas de confirmações. Se eu fizer isso de novo, desta vez, ele passa por cerca de cem confirmações e gera esta mensagem:

Automatic cherry-pick failed.  After resolving the conflicts,
mark the corrected paths with 'git add <paths>', and
run 'git rebase --continue'
Could not apply f1de3bc... Incremental

Se eu correr git status, recebo:

# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
# modified:   repo/file_A.cpp
# modified:   repo/file_B.cpp
#
# Unmerged paths:
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both modified:      repo/file_X.cpp
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
# deleted:    repo/file_Z.imp

O bit "ambos modificados" parece estranho para mim, pois esse foi apenas o resultado de uma escolha. Também vale a pena notar que, se eu olhar para o "conflito", ele se resume a uma única linha, com uma versão começando com um caractere [tab] e a outra com quatro espaços. Parecia que isso poderia ser um problema de como eu configurei meu arquivo de configuração, mas não há nada disso. (Observei que core.ignorecase está definido como true, mas evidentemente o git-clone fez isso automaticamente. Não estou completamente surpreso com isso, considerando que a fonte original estava em uma máquina Windows.)

Se eu corrigir manualmente file_X.cpp, ele falhará logo depois com outro conflito, desta vez entre um arquivo (CMakeLists.txt) que uma versão acha que deveria existir e uma versão que acha que não deveria. Se eu corrigir esse conflito dizendo que quero esse arquivo (o que eu faço), alguns commit mais tarde, recebo outro conflito (nesse mesmo arquivo), onde agora há algumas mudanças não triviais. Ainda é apenas cerca de 25% dos conflitos.

Devo também salientar, já que isso pode ser muito importante, que este projeto tenha começado em um repositório svn. É provável que esse histórico inicial tenha sido importado desse repositório svn.

Atualização # 2:

Em uma cotovia (influenciada pelos comentários de Jefromi), decidi fazer a alteração do meu repo_squash.sh para:

rm -rf repo_squash
git clone repo repo_squash
cd repo_squash/
git rebase --strategy theirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a

E então, acabei de aceitar as entradas originais, como estão. Ou seja, o "rebase" não deveria ter mudado nada. Acabou com os mesmos resultados descritos anteriormente.

Atualização # 3:

Como alternativa, se eu omitir a estratégia e substituir o último comando por:

git rebase -i bd6a09a484b8230d0810e6689cf08a24f26f287a

Eu não recebo mais o problema de rebase do "nada a confirmar", mas ainda estou com os outros conflitos.

Atualize com o repositório de brinquedos que recria o problema:

test_squash.sh (esse é o arquivo que você realmente executa):

#========================================================
# Initialize directories
#========================================================
rm -rf test_squash/ test_squash_clone/
mkdir -p test_squash
mkdir -p test_squash_clone
#========================================================

#========================================================
# Create repository with history
#========================================================
cd test_squash/
git init
echo "README">README
git add README
git commit -m"Initial commit: can't easily access for rebasing"
echo "Line 1">test_file.txt
git add test_file.txt
git commit -m"Created single line file"
echo "Line 2">>test_file.txt 
git add test_file.txt 
git commit -m"Meant for it to be two lines"
git checkout -b dev
echo Meaningful code>new_file.txt
git add new_file.txt 
git commit -m"Meaningful commit"
git checkout master
echo Conflicting meaningful code>new_file.txt
git add new_file.txt 
git commit -m"Conflicting meaningful commit"
# This will conflict
git merge dev
# Fixes conflict
echo Merged meaningful code>new_file.txt
git add new_file.txt
git commit -m"Merged dev with master"
cd ..

#========================================================
# Save off a clone of the repository prior to squashing
#========================================================
git clone test_squash test_squash_clone
#========================================================

#========================================================
# Do the squash
#========================================================
cd test_squash
GIT_EDITOR=../test_squash_helper.sh git rebase -i HEAD@{7}
#========================================================

#========================================================
# Show the results
#========================================================
git log
git gc
git reflog
#========================================================

test_squash_helper.sh (usado por test_sqash.sh):

# If the file has the phrase "pick " in it, assume it's the log file
if grep -q "pick " $1
then
  sed -e "s/pick \(.*\) \(Meant for it to be two lines\)/squash \1 \2/g" < $1 > $1.tmp
  mv $1.tmp $1
# Else, assume it's the commit message file
else
# Use our pre-canned message
  echo "Created two line file" > $1
fi

PS: Sim, eu sei que alguns de vocês se encolhem quando me veem usando o emacs como editor substituto.

PPS: Sabemos que teremos que remover todos os nossos clones do repositório existente após o rebase. (Na linha de "não devolverás um repositório depois que ele for publicado".)

PPPS: Alguém pode me dizer como adicionar uma recompensa a isso? Não estou vendo a opção em nenhum lugar desta tela, esteja no modo de edição ou no modo de exibição.

Ben Hocking
fonte
Mais relevante do que os scripts usados ​​é a ação final tentada - parece bastante uma lista de escolha e squash misturadas, certo? E há algum commit de mesclagem no ramo rebased? (Embora você não está usando rebase -pde qualquer maneira)
Cascabel
Não sei o que você quer dizer com "tentativa final de ação", mas é apenas uma lista de pick e squash misturados, com os últimos 400 ou mais todos sendo pick. Não há mesclagens nessa lista, embora a própria reestruturação esteja executando suas próprias mesclagens. De acordo com o que eu li, "rebase -p" não é recomendado no modo interativo (que no meu caso não é tão interativo, é claro). Em kernel.org/pub/software/scm/git/docs/git-rebase.html : "Isso usa o mecanismo --interactive internamente, mas combiná-lo com a opção --interactive explicitamente geralmente não é uma boa idéia"
Ben Hocking
Por "ação final tentada", quis dizer a lista de pick / squash passada para rebase --interactive- essas são uma espécie de lista de ações para o git tentar. Eu esperava que você pudesse reduzir isso a uma única squash que estava causando conflitos e evitasse toda a complexidade extra de seus scripts auxiliares. A outra informação que falta é quando os conflitos ocorrem - quando o git aplica os patches para formar o squash, ou quando tenta passar além do squash e aplicar o próximo patch? (E você tem certeza que nada de ruim acontece com o seu kludge GIT_EDITOR Outro voto para caso de teste simples?.)
Cascabel
Obrigado pelos comentários úteis. Atualizei a pergunta para refletir minhas respostas.
Ben Hocking
2
O caso do repositório de brinquedos é muito, muito mais fácil de entender do que seus scripts - e mostra que os scripts não têm nada a ver com isso, apenas o fato de que você está tentando refazer uma ramificação que contém uma mesclagem com resolução de conflitos (uma mescla levemente maligna) )
Cascabel

Respostas:

69

Tudo bem, estou confiante o suficiente para dar uma resposta. Talvez tenha que editá-lo, mas acredito que sei qual é o seu problema.

Seu caso de teste de repo de brinquedo tem uma mesclagem - pior, uma mesclagem de conflitos. E você está se recuperando da mesclagem. Sem -p(que não funciona totalmente com -i), as mesclagens são ignoradas. Isso significa que tudo o que você fez na resolução de conflitos não está presente quando o rebase tenta escolher a próxima confirmação, portanto seu patch pode não se aplicar. (Eu acredito que isso é mostrado como um conflito de mesclagem porque git cherry-pickpode aplicar o patch fazendo uma mescla de três vias entre a confirmação original, a confirmação atual e o ancestral comum.)

Infelizmente, como temos observado nos comentários, -ie -p(preservar fusões) não se dão muito bem. Sei que a edição / reformulação funciona e que a reorganização não. No entanto, acredito que funciona bem com abóboras. Isso não está documentado, mas funcionou para os casos de teste descritos abaixo. Se o seu caso for muito, muito mais complexo, você poderá ter muitos problemas para fazer o que deseja, embora ainda seja possível. (Moral da história: limpe rebase -i antes de fundir.)

Então, vamos supor que temos um caso muito simples, onde queremos agrupar A, B e C:

- o - A - B - C - X - D - E - F (master)
   \             /
    Z -----------

Agora, como eu disse, se não houvesse conflitos no X, git rebase -i -pfunciona como você esperaria.

Se houver conflitos, as coisas ficam um pouco mais complicadas. Isso fará com que seja bem esmagado, mas quando tentar recriar a mesclagem, os conflitos acontecerão novamente. Você precisará resolvê-los novamente, adicioná-los ao índice e use git rebase --continuepara seguir em frente. (Obviamente, você pode resolvê-los novamente, verificando a versão do commit de mesclagem original.)

Se acontecer de você ter rerereativado em seu repo ( rerere.enabledconjunto para true), esta será a maneira mais fácil - git será capaz de re usar o re CORDED re solução a partir de quando você tinha originalmente os conflitos, e tudo que você tem a fazer é inspecionar para garantir que funcionou corretamente, adicione os arquivos ao índice e continue. (Você pode até dar um passo adiante, ligá- rerere.autoupdatelo e adicioná-lo para você, para que a mesclagem não falhe). Suponho, no entanto, que você nunca ativou o re-redirecionamento; portanto, você mesmo terá que fazer a resolução de conflitos. *

* Ou, você pode tentar o rerere-train.shscript do git-contrib, que tenta "Preparar [o] re-banco de dados a partir de confirmações de mesclagem existentes" - basicamente, verifica todas as confirmações de mesclagem, tenta mesclá-las e, se a mesclagem falhar, ele pega os resultados e os mostra git-rerere. Isso pode levar muito tempo, e eu nunca o usei, mas pode ser muito útil.

Cascabel
fonte
PS Olhando para os meus comentários, vejo que deveria ter percebido isso antes. Perguntei se você estava refazendo uma ramificação contendo fusões e você disse que não havia fusões na lista de rebase interativa, o que não é a mesma coisa - não houve fusões lá porque você não forneceu a -popção.
Cascabel
Definitivamente vou dar uma chance. Desde que postei isso, também notei em alguns casos, posso simplesmente digitar git commit -a -m"Some message"e git rebase --continue, e continuará. Isso funciona mesmo sem a -popção, mas funciona ainda melhor com a -popção (já que não estou reordenando, parece que -pestá bem). De qualquer forma, eu vou mantê-lo informado.
Ben Hocking
A configuração git config --global rerere.enabled truee git config --global rerere.autoupdate truea execução do exemplo de teste resolvem o problema principal. Curiosamente, no entanto, ele não preserva as mesclagens, mesmo quando especificado --preserve-merges. No entanto, se eu não os tiver definido, digito git commit -a -m"Meaningful commit"e git rebase --continueao especificar --preserve-merges, ele preserva as mesclagens. De qualquer forma, obrigado por me ajudar a superar esse problema!
Ben Hocking
84

Se você não se importa em criar um novo ramo, é assim que eu lidei com o problema:

Sendo no principal:

# create a new branch
git checkout -b new_clean_branch

# apply all changes
git merge original_messy_branch

# forget the commits but have the changes staged for commit
git reset --soft main        

git commit -m "Squashed changes from original_messy_branch"
hlidka
fonte
3
A solução mais rápida e eficiente :)
IslamTaha
2
Esta foi uma ótima sugestão! Funcionou muito bem para consolidar rapidamente vários commits.
philidem
3
Esta é uma solução muito mais segura. Eu também adicionei --squash à mesclagem. Sendo: git merge --squash original_messy_branch
jezpez
1
Obrigado por salvar meu dia e vida :) A melhor solução qualquer pessoa pode obter
Quando você git diff new_clean_branch..original_messy_branchdeve fazer o mesmo? Para mim, eles são diferentes.
LeonardChallis
5

Eu estava procurando por um requisito semelhante, ou seja, descartando commits intermediários do meu ramo de desenvolvimento, achei que esse procedimento funcionou para mim.
no meu ramo de trabalho

git reset –hard mybranch-start-commit
git checkout mybranch-end-commit . // files only of the latest commit
git add -a
git commit -m”New Message intermediate commits discarded”

viola, conectamos o commit mais recente ao commit inicial do branch! Não há problemas de conflito de mesclagem! Na minha prática de aprendizado, cheguei a essa conclusão nesta fase: existe uma abordagem melhor para esse propósito.

user28186
fonte
FYI - Eu estava apenas tentando isso e descobri que fazer o checkout do mybranch-end-commit não me fornece as exclusões que ocorreram nos commits intermediários. Portanto, ele verifica apenas os arquivos que existiam no mybranch-end-commit.
ahains
1

Com base na excelente resposta do @ hlidka acima, que minimiza a intervenção manual, eu gostaria de adicionar uma versão que preserve qualquer novo commit no master que não esteja no ramo para esmagar.

Como acredito que eles poderiam ser facilmente perdidos na git resetetapa desse exemplo.

# create a new branch 
# ...from the commit in master original_messy_branch was originally based on. eg 5654da06
git checkout -b new_clean_branch 5654da06

# apply all changes
git merge original_messy_branch

# forget the commits but have the changes staged for commit
# ...base the reset on the base commit from Master
git reset --soft 5654da06       

git commit -m "Squashed changes from original_messy_branch"

# Rebase onto HEAD of master
git rebase origin/master

# Resolve any new conflicts from the new commits
JonoB
fonte
0

Observe que as -Xopções de estratégia e foram ignoradas quando usadas em uma nova base interativa.

Consulte commit db2b3b820e2b28da268cc88adff076b396392dfe (julho de 2013, git 1.8.4+),

Não ignore as opções de mesclagem no rebase interativo

A estratégia de mesclagem e suas opções podem ser especificadas em git rebase, mas com -- interactive, elas foram completamente ignoradas.

Assinado por: Arnaud Fontaine

Isso significa que a -Xestratégia agora trabalha com rebase interativa, bem como rebase simples, e seu script inicial agora pode funcionar melhor.

VonC
fonte
0

Eu estava enfrentando um problema mais simples, mas semelhante, onde eu tinha 1) resolvido um conflito de mesclagem em uma filial local, 2) continuado trabalhando adicionando muito mais pequenas confirmações, 3) queria refazer a recuperação e acertar conflitos de mesclagem.

Para mim, git rebase -p -i mastertrabalhou. Manteve o compromisso original da resolução de conflitos e me permitiu esmagar os outros por cima.

Espero que ajude alguém!

abaldwinhunter
fonte
Ainda tenho alguns conflitos a serem resolvidos manualmente ao tentar o rebase com -p É certo que havia muito menos conflitos e eles eram limitados apenas ao arquivo do projeto, e não a todos os arquivos de código com os quais eu estava tendo conflitos antes. Aparentemente, "Mesclar resoluções de conflito ou emendas manuais para mesclar confirmações não são preservadas". - stackoverflow.com/a/35714301/727345
JonoB