Pourquoi ne puis-je pas pousser cette sous-arborescence Git à jour?

88

J'utilise Git subtree avec quelques projets sur lesquels je travaille, afin de partager du code de base entre eux. Le code de base est souvent mis à jour et les mises à niveau peuvent se produire dans n'importe lequel des projets, tous étant éventuellement mis à jour.

J'ai rencontré un problème où git signale que mon sous-arbre est à jour, mais le push est rejeté. Par exemple:

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

Si je pousse, je devrais recevoir un message disant qu'il n'y a rien à pousser ... Non? DROITE? :(

#! 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.

Quelle pourrait être la raison de cela? Pourquoi pousser échoue-t-il?

Mateusz
la source
pourquoi ne faites-vous pas ce que git vous dit: git pull?
AlexWien
8
Git pull n'a rien à voir avec ma question. Mais oui, git pull me donne un message à jour, car il n'y a rien de nouveau dans le repo d'origine.
mateusz
1
Il semble que la branche sur laquelle vous vous trouvez est derrière celle où se trouve le maître HEAD - par exemple, une histoire de ... -> A -> B -> C où le maître distant HEAD est en C mais votre branche actuelle est en A (ou un descendant de A non connecté à C). Je regarderais mon histoire git pour être sûr.
Mark Leighton Fisher
J'ai le même problème. J'utilise --rejoinpour accélérer les poussées de sous-arbres. Lorsque je parcours manuellement le push de sous-arborescence, en cours d'exécution subtree split --rejoin, la branche de sous-arborescence fractionnée n'a que l'historique de la dernière rejointe, alors qu'elle contient normalement l'historique complet du sous-arbre. Pour moi, c'est la cause immédiate d'une erreur non accélérée. Je ne sais toujours pas pourquoi la division des sous-arbres génère un historique tronqué.
hurrymaplelad

Réponses:

98

J'ai trouvé la réponse sur ce commentaire de blog https://coderwall.com/p/ssxp5q

Si vous rencontrez le problème "Les mises à jour ont été rejetées parce que la pointe de votre branche actuelle est derrière. Fusionner les modifications à distance (par exemple 'git pull')" lorsque vous poussez (pour une raison quelconque, en particulier avec l'historique de git) alors vous devrez imbriquer les commandes git afin de pouvoir forcer une poussée vers heroku. par exemple, étant donné l'exemple ci-dessus:

git push heroku `git subtree split --prefix pythonapp master`:master --force
Eric Woodruff
la source
2
M'a aidé! Merci.
Michael Forrest
8
Comment exécuter cette commande sous Windows? Je reçoiserror: unknown option 'prefix'
Pilau
3
Incroyable, cela vient de sauver ma journée :)
bpipat
2
C'est vrai, mais la plupart des utilisations du sous-arbre de cette manière sont une poussée à sens unique vers heroku. Généralement, heroku n'est pas considéré comme un dépôt git de facto pour que plusieurs utilisateurs puissent pousser et tirer.
Eric Woodruff
11
J'ai eu du mal à identifier quelle relation herokuet pythonappavoir dans la réponse. Traduire ceci hors d'une langue spécifique à Heroku:git push <your subtree's origin> `git subtree split --prefix=Path/to/subtree master`:master --force
aednichols
38

Sous Windows, la commande imbriquée ne fonctionne pas:

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

Vous pouvez simplement exécuter le bit imbriqué en premier:

git subtree split --prefix pythonapp master

Cela renverra (après beaucoup de nombres) un jeton, par exemple

157a66d050d7a6188f243243264c765f18bc85fb956

Utilisez ceci dans la commande contenant, par exemple:

git push heroku 157a66d050d7a6188f243243264c765f18bc85fb956:master --force
Chris Jordan
la source
C'est une solution beaucoup plus propre. Travaille pour moi!
Infinity
Quand j'exécute git subtree split --prefix subtreedirectoryname master, j'obtiens l'argument ambigu fatal 'master': révision inconnue ou chemin non dans l'arbre de travail
Demodave
C'est une excellente solution, je n'ai pas cessé de recevoir git subtreeune commande avant. Après avoir utilisé votre solution, j'ai pu déployer un dossier sur heroku au lieu de tout mon
dépôt
Excellente solution et gain de temps
Nisharg Shah
29

Utilisez le --ontodrapeau:

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

[EDIT: subtree pushne transmet malheureusement pas --ontoau sous-jacent split, donc l'opération doit être effectuée en deux commandes! Cela fait, je vois que mes commandes sont identiques à celles de l'une des autres réponses, mais l'explication est différente, je vais donc la laisser ici de toute façon.]

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

Ou si vous n'utilisez pas 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

J'ai passé des heures à parcourir la source git-subtree pour comprendre celle-ci, alors j'espère que vous l'apprécierez;)

subtree pushcommence par s'exécuter subtree split, ce qui réécrit votre historique de validation dans un format qui devrait être prêt à être envoyé. Pour ce faire, il public/shared/supprime le devant de tout chemin qui le contient et supprime toute information sur les fichiers qui ne l'ont pas. Cela signifie que même si vous tirez non-écrasé, tous les commits de sous-référentiel en amont sont ignorés car ils nomment les fichiers par leurs chemins nus. (Commits qui ne touchent aucun fichier souspublic/shared/, ou les commits de fusion identiques à un parent, sont également réduits. [EDIT: De plus, j'ai depuis trouvé une détection de squash, donc maintenant je pense que ce n'est que si vous avez tiré non écrasé, puis la fusion simpliste de la validation décrite dans une autre réponse parvient à choisir le chemin non écrasé et ignorer le chemin écrasé.]) Le résultat est que le contenu qu'il tente de pousser finit par contenir tout travail commis par une personne dans le référentiel hôte actuel à partir duquel vous poussez, mais pas les personnes engagées directement dans le sous-référentiel ou via un autre hôte dépôt.

Cependant, si vous utilisez --onto, tous les commits en amont sont enregistrés comme OK pour être utilisés textuellement, donc lorsque le processus de réécriture les trouve comme l'un des parents d'une fusion, il veut les réécrire, il les conservera au lieu d'essayer de les réécrire. de la manière habituelle.

entheh
la source
2
Je recommande cette réponse comme celle qui m'avait déjà aidé deux fois. Et la description est ce dont j'avais besoin pour comprendre comment fonctionne le fractionnement et le pousser au bon endroit. Je vous remercie.
Kamil Wozniak le
2
Qu'est-ce que «projet partagé»?
Colin D
1
@ColinD "project-shared" est le nom de la télécommande. C'est un nom que vous aurez choisi lors de la configuration de l'endroit à récupérer et à pousser. Vous pouvez obtenir une liste de télécommandes avec git remote -v.
entheh
@entheh les heures que vous avez passées là-dedans ont fait de ma journée. Merci!
juin
1
J'ai trouvé que ce bogue se produisait toujours lorsque votre sous-référentiel avait un répertoire dont le nom est le même que le préfixe, par exemple vous ajoutez le sous-référentiel en tant que "libxxx" dans le référentiel principal, et votre sous-référentiel lui-même a également un sous-répertoire nommé "libxxx ". Dans ce cas, git traitera incorrectement les commits dans le sous-référentiel comme des commits dans le référentiel principal et essaiera de les diviser. L' --ontooption aide git à identifier les commits qui sont déjà dans le sous-référentiel.
Cosyn
13

Pour une application de type "pages GitHub", où vous déployez une sous-arborescence "dist" dans une branche gh-pages, la solution peut ressembler à ceci

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

Je mentionne cela car il semble légèrement différent des exemples d'heroku donnés ci-dessus. Vous pouvez voir que mon dossier "dist" existe sur la branche master de mon repo, puis je le pousse comme sous-arborescence vers la branche gh-pages qui est également à l'origine.

Colin D
la source
3

J'ai également rencontré ce problème auparavant, et voici comment je l'ai résolu.

Ce que j'ai découvert, c'est que j'avais une branche qui n'était pas rattachée à la branche principale locale. Cette branche existe et elle est juste suspendue dans le vide. Dans votre cas, c'est probablement appelé project-shared. En supposant que c'est le cas et lorsque vous faites un, git branchvous pouvez voir une project-sharedbranche locale , alors vous pouvez `` ajouter '' de nouveaux commits à votre project-sharedbranche existante en faisant:

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

La façon dont j'ai compris est de git subtreecommencer à créer une nouvelle branche --onto, dans ce cas, c'est la public-sharedbranche locale . Ensuite, la branche signifie créer une branche, qui remplace simplement l'ancienne public-sharedbranche.

Cela conservera tous les SHA précédents de la public-sharedbranche. Enfin, vous pouvez faire un

git push project-shared project-shared:master

En supposant que vous ayez également une project-sharedtélécommande; cela poussera le local suspendu dans la project-shared branche vide vers la masterbranche de la télécommande project-shared.

snw
la source
3

Ceci est dû à la limitation de l'algorithme d'origine. Lors de la gestion des merge-commits, l'algorithme d'origine utilise un critère simplifié pour couper les parents non liés. En particulier, il vérifie s'il existe un parent qui a le même arbre. Si un tel parent était trouvé, il réduirait le commit de fusion et utiliserait le commit parent à la place, en supposant que d'autres parents ont des changements sans rapport avec le sous-arbre. Dans certains cas, cela entraînerait la suppression de certaines parties de l'historique, ce qui entraîne des modifications réelles du sous-arbre. En particulier, il supprimerait des séquences de commits, qui toucheraient un sous-arbre, mais aboutiraient à la même valeur de sous-arbre.

Voyons un exemple (que vous pouvez facilement reproduire) pour mieux comprendre comment cela fonctionne. Considérez l'historique suivant (le format de ligne est: 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

Dans cet exemple, nous nous séparons dir. Commits Det Eont le même arbre z, parce que nous avons commit C, qui a annulé le commit B, donc la B-Cséquence ne fait rien pour dirmême si elle a des modifications.

Maintenant, faisons le fractionnement. D'abord, nous nous sommes séparés sur 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

Ensuite, nous nous sommes séparés sur commit E.

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

Oui, nous avons perdu deux commits. Cela entraîne une erreur lors de la tentative de pousser le deuxième fractionnement, car il n'a pas ces deux validations, qui sont déjà entrées dans l'origine.

Habituellement, vous pouvez tolérer cette erreur en utilisant push --force, car les commits supprimés ne contiennent généralement pas d'informations critiques. À long terme, le bogue doit être corrigé, de sorte que l'historique de fractionnement aurait en fait tous les commits, qui se touchent dir, comme prévu. Je m'attendrais à ce que le correctif inclue une analyse plus approfondie des commits parent pour les dépendances cachées.

Pour référence, voici la partie du code d'origine, responsable du comportement.

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
la source
le bogue est-il corrigé à partir du 09/05/2020 (samedi)? @klimkin
halfmoonhalf
le bogue est corrigé. voir: contrib / subtree: correction du bogue de fusion sautée "subree split". github.com/git/git/commit/…
lune
0

La réponse d'Eric Woodruff ne m'a pas aidé, mais ce qui suit l'a fait:

J'ai l'habitude de «git subtree pull» avec l'option «--squash». Il semble que cela a rendu les choses plus difficiles à réconcilier, donc j'ai dû faire une extraction de sous-arbre sans écraser cette fois, résoudre certains conflits, puis pousser.

Je dois ajouter que la traction écrasée n'a révélé aucun conflit, m'a dit que tout allait bien.

Zsolt Szatmari
la source
0

Une autre solution [plus simple] est d'avancer la tête de la télécommande en faisant un autre commit si vous le pouvez. Après avoir tiré cette tête avancée dans le sous-arbre local, vous pourrez à nouveau y pousser.

9swampy
la source
1
C'est exactement ainsi que j'ai résolu le même problème avec le référentiel distant désynchronisé avec le sous-arbre local.
Aidas Bendoraitis
0

Vous pouvez forcer le transfert des modifications locales vers le dépôt de sous-arborescence distant

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

Un PowerShell rapide pour les utilisateurs de Windows basé sur la solution de Chris Jordan

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

C'est donc ce que j'ai écrit sur la base de ce que @entheh a dit.

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
Démodave
la source
0

Eu ce problème, aussi. Voici ce que j'ai fait, en fonction des principales réponses:

Donné:

#! 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.

Entrez ceci:

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

Notez que le jeton qui est généré par 'git subtree push' est utilisé dans 'git push'.

Amos
la source