Por que não consigo empurrar esta subárvore Git atualizada?

88

Estou usando a subárvore Git com alguns projetos nos quais estou trabalhando, para compartilhar algum código base entre eles. O código base é atualizado com frequência, e as atualizações podem acontecer em qualquer um dos projetos, com todos eles sendo atualizados, eventualmente.

Eu tive um problema onde o git relata que minha subárvore está atualizada, mas o push é rejeitado. Por exemplo:

#! git subtree pull --prefix=public/shared project-shared master
From github.com:****
* branch            master     -> FETCH_HEAD
Already up-to-date.

Se eu empurrar, devo receber uma mensagem de que não há nada para empurrar ... Certo? CERTO? :(

#! git subtree push --prefix=public/shared project-shared master
git push using:  project-shared master
To [email protected]:***
! [rejected]        72a6157733c4e0bf22f72b443e4ad3be0bc555ce -> master (non-fast-forward)
error: failed to push some refs to '[email protected]:***'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Qual poderia ser o motivo disso? Por que o push está falhando?

Mateusz
fonte
por que você não faz o que o git está dizendo: git pull?
AlexWien
8
Git pull não tem nada a ver com a minha pergunta. Mas sim, git pull me dá uma mensagem atualizada, já que não há nada de novo no repositório de origem.
Mateusz
1
Parece que o branch em que você está está atrás de onde o HEAD principal está - por exemplo, um histórico de ... -> A -> B -> C onde o HEAD principal remoto está em C, mas seu branch atual está em A (ou algum descendente de A não conectado a C). Eu olharia para o meu histórico do git para ter certeza.
Mark Leighton Fisher
Estou tendo o mesmo problema. Tenho usado --rejoinpara acelerar os empurrões de subárvore. Quando eu passo pelo push da subárvore manualmente, executando subtree split --rejoin, a ramificação da subárvore dividida só tem o histórico de volta para a última reunião, quando normalmente contém todo o histórico da subárvore. Para mim, essa é a causa imediata do erro de não avanço rápido. Ainda não sei por que a divisão de subárvore está gerando um histórico truncado.
hurrymaplelad

Respostas:

98

Encontrei a resposta neste comentário de blog https://coderwall.com/p/ssxp5q

Se você se deparar com o problema "Atualizações foram rejeitadas porque a ponta de seu branch atual está atrasada. Mesclar as alterações remotas (por exemplo, 'git pull')" quando estiver fazendo push (por qualquer motivo, especialmente mexendo com o histórico do git) então, você precisará aninhar comandos git para que possa forçar um push para o heroku. por exemplo, dado o exemplo acima:

git push heroku `git subtree split --prefix pythonapp master`:master --force
Eric Woodruff
fonte
2
Me ajudou! Obrigado.
Michael Forrest
8
Como executar este comando no Windows? Estou recebendoerror: unknown option 'prefix'
pilau,
3
Incrível, isso acabou de salvar meu dia :)
bpipat
2
Verdadeiro, mas provavelmente a maioria dos usos de subárvore dessa maneira é um impulso unilateral para o heroku. Geralmente, o heroku não é considerado um repositório git de fato para que vários usuários façam push e pull.
Eric Woodruff
11
Tive dificuldade em identificar qual relacionamento herokue pythonappter na resposta. Traduzindo para uma linguagem específica de Heroku:git push <your subtree's origin> `git subtree split --prefix=Path/to/subtree master`:master --force
aednichols
38

No Windows, o comando aninhado não funciona:

git push heroku `git subtree split --prefix pythonapp master`:master --force

Você pode apenas executar o bit aninhado primeiro:

git subtree split --prefix pythonapp master

Isso (depois de muitos números) retornará um token, por exemplo

157a66d050d7a6188f243243264c765f18bc85fb956

Use isso no comando de conteúdo, por exemplo:

git push heroku 157a66d050d7a6188f243243264c765f18bc85fb956:master --force
Chris Jordan
fonte
Esta é uma solução muito mais limpa. Funciona para mim!
Infinity de
Quando executo git subtree split --prefix subtreedirectoryname master, recebo o argumento ambíguo fatal 'master': revisão desconhecida ou caminho não na árvore de trabalho
Demodave
Esta é uma ótima solução, eu continuava recebendo git subtreenão é um comando antes. Depois de usar sua solução, consegui implantar uma pasta no heroku em vez de todo o meu repo
pakman198 01 de
Ótima solução e economia de tempo
Nisharg Shah
29

Use a --ontobandeira:

# DOESN'T WORK: git subtree push --prefix=public/shared project-shared master --onto=project-shared/master

[EDITAR: infelizmente subtree pushnão encaminha --ontopara o subjacente split, então a operação tem que ser feita em dois comandos! Feito isso, vejo que meus comandos são idênticos aos de uma das outras respostas, mas a explicação é diferente, então vou deixá-la aqui mesmo assim.]

git push project-shared $(git subtree split --prefix=public/shared --onto=project-shared/master):master

Ou se você não estiver usando o bash:

git subtree split --prefix=public/shared --onto=project-shared/master
# This will print an ID, say 0123456789abcdef0123456789abcdef,
# which you then use as follows:
git push project-shared 01234567:master

Passei horas estudando a fonte git-subtree para descobrir isso, então espero que você goste;)

subtree pushcomeça executando subtree split, que reescreve seu histórico de commits em um formato que deve estar pronto para envio. A maneira como ele faz isso é public/shared/retirando a frente de qualquer caminho que o contenha e removendo todas as informações sobre os arquivos que não o possuem. Isso significa que mesmo se você puxar não compactado, todos os commits do sub-repositório upstream são desconsiderados, pois eles nomeiam os arquivos por seus caminhos vazios. (Confirmações que não afetam nenhum arquivo sobpublic/shared/, ou commits de mesclagem idênticos a um pai, também são reduzidos. [EDIT: Além disso, já encontrei alguma detecção de squash, então agora estou pensando que é apenas se você puxou não esmagado, e então o colapso simplista do commit de mesclagem descrito em outra resposta consegue escolher o caminho não esmagado e descartar o caminho comprimido.]) O resultado é que o material que ele tenta enviar acaba contendo qualquer trabalho que alguém tenha enviado ao repositório host atual do qual você está enviando, mas não pessoas comprometidas diretamente com o sub-repositório ou por meio de outro host repositório.

No entanto, se você usar --onto, então todos os commits upstream são registrados como OK para usar literalmente, então quando o processo de reescrita os encontra como um dos pais de uma fusão que deseja reescrever, ele os manterá ao invés de tentar reescrevê-los da maneira usual.

entheh
fonte
2
Estou recomendando esta resposta como a que já me ajudou duas vezes. E a descrição é o que eu precisava para entender como a divisão e empurrar para o lugar certo funcionam. Obrigado.
Kamil Wozniak
2
O que é "projeto compartilhado"?
Colin D
1
@ColinD "project-shared" é o nome do controle remoto. É um nome que você escolheu ao configurar o local de onde buscar e enviar. Você pode obter uma lista de controles remotos com git remote -v.
entheh
@entheh, as horas que você gastou nisso apenas fizeram o meu dia. Obrigado!
junho
1
Eu descobri que esse bug sempre acontece quando seu sub-repositório tem um diretório cujo nome é igual ao prefixo, por exemplo, você adiciona o sub-repositório como "libxxx" no repositório principal, e seu próprio sub-repositório também tem um sub-diretório chamado "libxxx " Neste caso, o git tratará incorretamente os commits no sub-repositório como commits no repositório principal e tentará dividi-los. A --ontoopção ajuda o git a identificar commits que já estão no sub-repositório.
Cosyn
13

Para um aplicativo do tipo "páginas do GitHub", em que você implanta uma subárvore "dist" em um branch gh-pages, a solução pode ser parecida com esta

git push origin `git subtree split --prefix dist master`:gh-pages --force

Menciono isso porque parece um pouco diferente dos exemplos de heroku dados acima. Você pode ver que minha pasta "dist" existe no branch master do meu repo, e então eu a empurro como uma subárvore para o branch gh-pages que também está na origem.

Colin D
fonte
3

Eu também já encontrei esse problema e aqui está como resolvi.

O que descobri foi que tinha um ramo que não estava vinculado ao ramo mestre local. Este galho existe e está apenas pendurado no vazio. No seu caso, provavelmente é chamado project-shared. Assumindo que este é o caso e quando você faz um, git branchvocê pode ver um project-sharedbranch local , então você pode ' anexar ' novos commits ao seu project-sharedbranch existente fazendo:

git subtree split --prefix=public/shared --onto public-shared --branch public-shared

Pelo que entendi, git subtreevou começar a criar um novo branch --onto, neste caso é o public-sharedbranch local . Então, o ramo significa criar um ramo, que apenas substitui o antigo public-sharedramo.

Isso manterá todos os SHA anteriores do public-sharedramo. Finalmente, você pode fazer um

git push project-shared project-shared:master

Supondo que você também tenha um project-sharedcontrole remoto; isso irá empurrar o local pendurado no project-shared ramo vazio para o masterramo do remoto project-shared.

snw
fonte
3

Isso ocorre devido à limitação do algoritmo original. Ao lidar com mesclagem-commits, o algoritmo original usa um critério simplificado para cortar pais não relacionados. Em particular, ele verifica se há um pai, que tem a mesma árvore. Se tal pai for encontrado, ele colapsará o commit de mesclagem e usará o commit pai, assumindo que outros pais tenham mudanças não relacionadas à subárvore. Em alguns casos, isso resultaria na eliminação de partes do histórico, que apresentam mudanças reais na subárvore. Em particular, ele descartaria sequências de commits, que tocariam uma subárvore, mas resultariam no mesmo valor de subárvore.

Vamos ver um exemplo (que você pode reproduzir facilmente) para entender melhor como isso funciona. Considere o seguinte histórico (o formato da linha é: commit [tree] subject):

% git log --graph --decorate --pretty=oneline --pretty="%h [%t] %s"
*   E [z] Merge branch 'master' into side-branch
|\
| * D [z] add dir/file2.txt
* | C [y] Revert "change dir/file1.txt"
* | B [x] change dir/file1.txt
|/
*   A [w] add dir/file1.txt

Neste exemplo, estamos nos dividindo dir. Commits De Etêm a mesma árvore z, porque temos commit C, que desfaz o commit B, então a B-Cseqüência não faz nada dirmesmo que tenha alterações nela.

Agora vamos dividir. Primeiro, dividimos no commit C.

% git log `git subtree split -P dir C` ...
* C' [y'] Revert "change dir/file1.txt"
* B' [x'] change dir/file1.txt
* A' [w'] add dir/file1.txt

Em seguida, dividimos no commit E.

% git log `git subtree split -P dir E` ...
* D' [z'] add dir/file2.txt
* A' [w'] add dir/file1.txt

Sim, perdemos dois commits. Isso resulta no erro ao tentar empurrar a segunda divisão, uma vez que não tem esses dois commits, que já entraram na origem.

Normalmente você pode tolerar esse erro usando push --force, uma vez que commits descartados geralmente não terão informações críticas neles. No longo prazo, o bug precisa ser consertado, então o histórico de divisão teria, na verdade, todos os commits, que tocam dir, conforme o esperado. Eu esperaria que a correção incluísse uma análise mais profunda dos commits pai para dependências ocultas.

Para referência, aqui está a parte do código original, responsável pelo comportamento.

copy_or_skip()
  ...
  for parent in $newparents; do
      ptree=$(toptree_for_commit $parent) || exit $?
      [ -z "$ptree" ] && continue
      if [ "$ptree" = "$tree" ]; then
          # an identical parent could be used in place of this rev.
          identical="$parent"
      else
          nonidentical="$parent"
      fi
  ...
  if [ -n "$identical" ]; then
      echo $identical
  else
      copy_commit $rev $tree "$p" || exit $?
  fi
Klimkin
fonte
o bug foi corrigido em 2020-05-09 (sáb)? @klimkin
halfmoonhalf
o bug foi corrigido. veja: contrib / subtree: conserta bug de skipped-merge "divisão de subárvore". github.com/git/git/commit/...
halfmoonhalf
0

A resposta de Eric Woodruff não me ajudou, mas o seguinte sim:

Eu geralmente 'git subtree pull' com a opção '--squash'. Parece que isso tornou as coisas mais difíceis de reconciliar, então eu precisei fazer uma subtree pull sem esmagar desta vez, resolver alguns conflitos e depois empurrar.

Devo acrescentar que o puxão esmagado não revelou nenhum conflito, disse-me que estava tudo bem.

Zsolt Szatmari
fonte
0

Outra solução [mais simples] é avançar o controle remoto fazendo outro commit, se puder. Depois de puxar esse cabeçote avançado para a subárvore local, você poderá empurrá-lo novamente.

9 swampy
fonte
1
Foi exatamente assim que resolvi o mesmo problema com o repositório remoto fora de sincronia com a subárvore local.
Aidas Bendoraitis
0

Você pode forçar o envio de alterações locais para o repositório de subárvore remoto

git push subtree_remote_address.git `git subtree split --prefix=subtree_folder`:refs/heads/branch --force
FunkyKat
fonte
0

Um PowerShell rápido para usuários do Windows com base na solução de Chris Jordan

$id = git subtree split --prefix pythonapp master
Write-Host "Id is: $id"
Invoke-Expression "git push heroku $id`:master --force"
Rtype
fonte
0

Então, isso é o que eu escrevi baseado no que @entheh disse.

for /f "delims=" %%b in ('git subtree split --prefix [name-of-your-directory-on-the-file-system-you-setup] -b [name-of-your-subtree-branch]') do @set token=%%b
git push [alias-for-your-subtree] %token%:[name-of-your-subtree-branch] --force

pause
Demodave
fonte
0

Tive esse problema também. Aqui está o que eu fiz, com base nas principais respostas:

Dado:

#! git subtree push --prefix=public/shared project-shared master
git push using:  project-shared master
To [email protected]:***
! [rejected]        72a6157733c4e0bf22f72b443e4ad3be0bc555ce -> master (non-fast-forward)
error: failed to push some refs to '[email protected]:***'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Insira isto:

git push <origin> 72a6157733c4e0bf22f72b443e4ad3be0bc555ce:<branch> --force

Observe que o token gerado por 'git subtree push' é usado em 'git push'.

Amos
fonte