Perché non posso eseguire il push di questa sottostruttura Git aggiornata?


88

Sto usando la sottostruttura Git con un paio di progetti su cui sto lavorando, al fine di condividere un po 'di codice di base tra di loro. Il codice di base viene aggiornato spesso e gli aggiornamenti possono avvenire in qualsiasi progetto, con tutti gli aggiornamenti alla fine.

Ho riscontrato un problema in cui git segnala che il mio sottoalbero è aggiornato, ma il push viene rifiutato. Per esempio:

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

Se spingo, dovrei ricevere un messaggio che non c'è niente da spingere ... giusto? DESTRA? :(

#! git subtree push --prefix=public/shared project-shared master
git push using:  project-shared master
To git@github.com:***
! [rejected]        72a6157733c4e0bf22f72b443e4ad3be0bc555ce -> master (non-fast-forward)
error: failed to push some refs to 'git@github.com:***'
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.

Quale potrebbe essere la ragione di ciò? Perché la spinta fallisce?


perché non fai quello che ti sta dicendo git: git pull?
AlexWien

8
Git pull non ha nulla a che fare con la mia domanda. Ma sì, git pull mi fornisce un messaggio aggiornato, poiché non c'è nulla di nuovo nel repository di origine.
mateusz

1
Sembra che il ramo in cui ti trovi sia dietro il punto in cui si trova il master HEAD, ad esempio una cronologia di ... -> A -> B -> C dove il master remoto HEAD è in C ma il tuo ramo corrente è in A (o qualche discendente di A non connesso a C). Guarderei la mia storia git per essere sicuro.
Mark Leighton Fisher

Ho lo stesso problema. Ho usato --rejoinper accelerare i push dei sottoalberi. Quando passo manualmente attraverso il push del sottoalbero, in esecuzione subtree split --rejoin, il ramo del sottoalbero diviso riporta solo la cronologia fino all'ultimo ricongiungimento, quando normalmente contiene l'intera cronologia del sottoalbero. Per me, questa è la causa immediata dell'errore di avanzamento rapido. Non sono ancora sicuro del motivo per cui la divisione del sottoalbero stia generando una cronologia troncata.
hurrymaplelad

Risposte:


99

Ho trovato la risposta su questo commento del blog https://coderwall.com/p/ssxp5q

Se ti imbatti nel problema "Gli aggiornamenti sono stati rifiutati perché la punta del tuo ramo attuale è dietro. Unisci le modifiche remote (es. 'Git pull')" problema quando stai spingendo (per qualsiasi motivo, specialmente se stai scherzando con git history) quindi dovrai annidare i comandi git in modo da poter forzare un push su heroku. ad esempio, dato l'esempio sopra:

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

8
Come eseguire questo comando su Windows? Ricevoerror: unknown option 'prefix'
pilau

3
Incredibile, questo mi ha appena salvato la giornata :)
bpipat

1
Anche se fornisce una soluzione, non spiega il comportamento.
klimkin

2
Vero, ma probabilmente la maggior parte degli usi della sottostruttura in questo modo è un modo unidirezionale per heroku. Generalmente heroku non è considerato un repository git di fatto da cui più utenti possono spingere e tirare.
Eric Woodruff

11
Ho avuto difficoltà a identificare quale relazione herokue pythonappavere nella risposta. Traducendo questo dal linguaggio specifico di Heroku:git push <your subtree's origin> `git subtree split --prefix=Path/to/subtree master`:master --force
aednichols

39

Su Windows il comando annidato non funziona:

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

Puoi semplicemente eseguire prima il bit annidato:

git subtree split --prefix pythonapp master

Questo (dopo molti numeri) restituirà un token, ad es

157a66d050d7a6188f243243264c765f18bc85fb956

Usalo nel comando contenitore, ad esempio:

git push heroku 157a66d050d7a6188f243243264c765f18bc85fb956:master --force

Questa è una soluzione molto più pulita. Per me va bene!
Infinity

Quando eseguo git subtree split --prefix subtreedirectoryname master, ottengo l'argomento ambiguo fatale 'master': revisione sconosciuta o percorso non nell'albero di lavoro
Demodave

Questa è un'ottima soluzione, ho continuato a ricevere git subtreenon è un comando prima. Dopo aver utilizzato la tua soluzione, sono stato in grado di distribuire una cartella su heroku invece di tutto il mio repository
pakman198

Ottima soluzione e risparmio di tempo
Nisharg Shah

29

Usa la --ontobandiera:

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

[EDIT: purtroppo subtree pushnon inoltra --ontoal sottostante split, quindi l'operazione deve essere eseguita in due comandi! Fatto ciò, vedo che i miei comandi sono identici a quelli in una delle altre risposte, ma la spiegazione è diversa, quindi la lascerò qui comunque.]

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

O se non stai usando 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

Ho passato ore a esaminare attentamente la fonte git-subtree per capirlo, quindi spero che lo apprezzi;)

subtree pushinizia con l'esecuzione subtree split, che riscrive la cronologia dei commit in un formato che dovrebbe essere pronto per il push. Il modo in cui lo fa è che rimuove public/shared/la parte anteriore di qualsiasi percorso che lo contiene e rimuove qualsiasi informazione sui file che non lo hanno. Ciò significa che anche se si esegue il pull non schiacciato, tutti i commit del sub-repository upstream vengono ignorati poiché denominano i file in base ai loro percorsi nudi. (Impegni che non toccano alcun file sottopublic/shared/, o unire i commit identici a un genitore, vengono anch'essi compressi. [EDIT: Inoltre, da allora ho trovato qualche rilevamento di squash, quindi ora penso che sia solo se hai tirato non schiacciato, e quindi il semplicistico collasso del commit di unione descritto in un'altra risposta riesce a scegliere il percorso non schiacciato e scartare il percorso schiacciato.]) Il risultato è che le cose che cerca di inviare finiscono per contenere qualsiasi lavoro che qualcuno ha impegnato nel repository host corrente da cui stai spingendo, ma non lavorano persone impegnate direttamente nel repository secondario o tramite un altro host repository.

Tuttavia, se usi --onto, tutti i commit a monte vengono registrati come OK per essere usati alla lettera, quindi quando il processo di riscrittura li incontra come uno dei genitori di una fusione che vuole riscrivere, li manterrà invece di provare a riscriverli nel solito modo.


2
Raccomando questa risposta come quella che mi aveva già aiutato due volte. E la descrizione è ciò di cui avevo bisogno per capire come funziona la divisione e la spinta al posto giusto. Grazie.
Kamil Wozniak

2
Che cos'è il "progetto condiviso"?
Colin D

1
@ColinD "project-shared" è il nome del telecomando. È un nome che avrai scelto durante la configurazione della posizione da cui recuperare e in cui inviare. Puoi ottenere un elenco di telecomandi con git remote -v.
entheh

@entheh, le ore che ci hai passato mi hanno appena migliorato la giornata. Grazie!
giugno

1
Ho scoperto che questo bug si verifica sempre quando il tuo repository secondario ha una directory il cui nome è uguale al prefisso, ad es. Aggiungi il repository secondario come "libxxx" nel repository principale e il tuo repository secondario stesso ha anche una sottodirectory chiamata "libxxx ". In questo caso git tratterà erroneamente i commit nel repository secondario come commit nel repository principale e tenterà di dividerli. L' --ontoopzione aiuta git a identificare i commit che sono già nel repository secondario.
Cosyn

14

Per un'app di tipo "GitHub pages", in cui distribuisci una sottostruttura "dist" a un ramo gh-pages, la soluzione potrebbe essere simile a questa

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

Lo menziono poiché sembra leggermente diverso dagli esempi di heroku forniti sopra. Puoi vedere che la mia cartella "dist" esiste nel ramo principale del mio repository, quindi la inserisco come sottostruttura nel ramo gh-pages che è anche su origin.


3

Ho riscontrato anche questo problema in passato, ed ecco come l'ho risolto.

Quello che ho scoperto è che avevo un ramo che non era collegato al ramo principale locale. Questo ramo esiste ed è semplicemente sospeso nel vuoto. Nel tuo caso, probabilmente si chiama project-shared. Supponendo che questo sia il caso e quando fai un git branchpuoi vedere un project-sharedramo locale , quindi puoi ' aggiungere ' nuovi commit al tuo project-sharedramo esistente eseguendo:

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

Per come ho capito git subtree, inizierò a creare un nuovo ramo --onto, in questo caso è il public-sharedramo locale . Quindi il ramo significa creare un ramo, che sostituisce semplicemente il vecchio public-sharedramo.

Ciò manterrà tutto il precedente SHA del public-sharedramo. Infine, puoi fare un file

git push project-shared project-shared:master

Supponendo che tu abbia anche un project-sharedtelecomando; questo spingerà il locale sospeso nel project-shared ramo vuoto al masterramo del telecomando project-shared.


3

Ciò è dovuto alla limitazione dell'algoritmo originale. Quando si gestiscono i merge-commit, l'algoritmo originale utilizza criteri semplificati per tagliare i genitori non correlati. In particolare, controlla se c'è un genitore che ha lo stesso albero. Se un tale genitore lo trova, comprimerà il commit di unione e utilizzerà invece il commit genitore, assumendo che altri genitori abbiano modifiche non correlate al sottoalbero. In alcuni casi, ciò comporterebbe l'eliminazione di parti della cronologia, che presenta modifiche effettive al sottoalbero. In particolare, eliminerebbe sequenze di commit, che toccherebbero un sottoalbero, ma restituirebbero lo stesso valore del sottoalbero.

Vediamo un esempio (che puoi facilmente riprodurre) per capire meglio come funziona. Considera la seguente cronologia (il formato della riga è: 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

In questo esempio, ci stiamo dividendo dir. Esegue il commit De Eha lo stesso albero z, perché abbiamo il commit C, che ha annullato il commit B, quindi la B-Csequenza non fa nulla diranche se ha delle modifiche.

Ora facciamo la divisione. Per prima cosa ci siamo separati su 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

Successivamente ci siamo separati su commit E.

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

Sì, abbiamo perso due commit. Ciò si traduce nell'errore quando si tenta di spingere la seconda divisione, poiché non ha quei due commit, che sono già entrati nell'origine.

Di solito è possibile tollerare questo errore utilizzando push --force, poiché i commit eliminati generalmente non contengono informazioni critiche. A lungo termine, il bug deve essere risolto, quindi la cronologia divisa avrebbe effettivamente tutti i commit, che si toccano dir, come previsto. Mi aspetto che la correzione includa un'analisi più approfondita dei commit padre per le dipendenze nascoste.

Per riferimento, ecco la porzione di codice originale, responsabile del 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

il bug è stato risolto a partire dal 09/05/2020 (sabato)? @klimkin
halfmoonhalf

il bug è stato risolto. vedi: contrib / subtree: corregge il bug di unione saltata della "divisione sottostruttura". github.com/git/git/commit/…
halfmoonhalf

0

La risposta di Eric Woodruff non mi ha aiutato, ma quanto segue lo ha fatto:

Di solito "git subtree pull" con l'opzione "--squash". Sembra che questo abbia reso le cose più difficili da riconciliare, quindi ho dovuto fare un pull di sottostruttura senza schiacciare questa volta, risolvere alcuni conflitti e poi spingere.

Devo aggiungere che il pull schiacciato non ha rivelato alcun conflitto, mi ha detto che era tutto OK.


0

Un'altra soluzione [più semplice] è far avanzare la testa del telecomando facendo un altro commit, se possibile. Dopo aver inserito questa testa avanzata nella sottostruttura locale, sarai in grado di spingerla di nuovo.


1
Questo è esattamente il modo in cui ho risolto lo stesso problema con il repository remoto non sincronizzato con la sottostruttura locale.
Aidas Bendoraitis

0

È possibile forzare il push delle modifiche locali al repository di sottostruttura remoto

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

0

Un rapido PowerShell per utenti Windows basato sulla soluzione di Chris Jordan

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

0

Quindi questo è quello che ho scritto in base a ciò che ha detto @entheh.

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

0

Ho avuto anche questo problema. Ecco cosa ho fatto, in base alle risposte migliori:

Dato:

#! git subtree push --prefix=public/shared project-shared master
git push using:  project-shared master
To git@github.com:***
! [rejected]        72a6157733c4e0bf22f72b443e4ad3be0bc555ce -> master (non-fast-forward)
error: failed to push some refs to 'git@github.com:***'
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.

Inserisci questo:

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

Notare che il token generato da "git subtree push" viene utilizzato in "git push".

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.