¿Por qué no puedo enviar este subárbol de Git actualizado?

88

Estoy usando el subárbol de Git con un par de proyectos en los que estoy trabajando, para compartir un código base entre ellos. El código base se actualiza con frecuencia, y las actualizaciones pueden ocurrir en cualquiera de los proyectos, y eventualmente todos se actualizan.

Me encontré con un problema en el que git informa que mi subárbol está actualizado, pero se rechaza la inserción. Por ejemplo:

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

Si presiono, debería recibir un mensaje de que no hay nada que presionar ... ¿Verdad? ¿CORRECTO? :(

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

¿Cuál podría ser la razón de ésto? ¿Por qué está fallando el empuje?

mateusz
fuente
¿Por qué no haces lo que te dice git: git pull?
AlexWien
8
Git pull no tiene nada que ver con mi pregunta. Pero sí, git pull me da un mensaje actualizado, ya que no hay nada nuevo en el repositorio de origen.
mateusz
1
Parece que la rama en la que se encuentra está detrás de donde se encuentra la HEAD principal, por ejemplo, un historial de ... -> A -> B -> C donde la HEAD principal remota está en C pero su rama actual está en A (o algún descendiente de A desconectado de C). Miraría mi historial de git para estar seguro.
Mark Leighton Fisher
Estoy teniendo el mismo problema. He estado usando --rejoinpara acelerar los empujes de subárboles. Cuando paso por el subárbol de empuje manualmente, en ejecución subtree split --rejoin, la rama del subárbol dividido solo tiene un historial hasta la última reunión, cuando normalmente contiene todo el historial del subárbol. Para mí, esa es la causa inmediata del error que no es de avance rápido. Todavía no estoy seguro de por qué la división del subárbol genera un historial truncado.
hurrymaplelad

Respuestas:

98

Encontré la respuesta en este comentario de blog https://coderwall.com/p/ssxp5q

Si te encuentras con el problema "Las actualizaciones fueron rechazadas porque la punta de tu rama actual está atrás. Fusiona los cambios remotos (por ejemplo, 'git pull')" cuando estás presionando (debido a cualquier razón, especialmente jugando con git history) entonces necesitarás anidar comandos de git para que puedas forzar un empujón a heroku. por ejemplo, dado el ejemplo anterior:

git push heroku `git subtree split --prefix pythonapp master`:master --force
Eric Woodruff
fuente
2
¡Me ayudó! Gracias.
Michael Forrest
8
¿Cómo ejecutar este comando en Windows? Estoy recibiendoerror: unknown option 'prefix'
pilau
3
Increíble, esto acaba de
salvarme el
2
Es cierto, pero probablemente la mayoría de los usos del subárbol de esta manera es un impulso unidireccional a heroku. En general, heroku no se considera un repositorio de git de facto para que varios usuarios puedan presionar y extraer.
Eric Woodruff
11
Me costó identificar qué relación herokuy qué relación pythonapptengo en la respuesta. Traduciendo esto del lenguaje específico de Heroku:git push <your subtree's origin> `git subtree split --prefix=Path/to/subtree master`:master --force
aednichols
39

En Windows, el comando anidado no funciona:

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

Primero puede ejecutar el bit anidado:

git subtree split --prefix pythonapp master

Esto (después de muchos números) devolverá un token, por ejemplo

157a66d050d7a6188f243243264c765f18bc85fb956

Use esto en el comando contenedor, por ejemplo:

git push heroku 157a66d050d7a6188f243243264c765f18bc85fb956:master --force
Chris Jordan
fuente
Esta es una solución mucho más limpia. ¡Funciona para mi!
Infinity
Cuando ejecuto git subtree split --prefix subtreedirectoryname master, obtengo un argumento ambiguo fatal 'master': revisión desconocida o ruta que no está en el árbol de trabajo
Demodave
Esta es una gran solución, simplemente seguía recibiendo git subtreeno es un comando antes. Después de usar su solución, pude implementar una carpeta en heroku en lugar de todo mi repositorio
pakman198
Gran solución y ahorro de tiempo
Nisharg Shah
29

Usa la --ontobandera:

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

[EDITAR: desafortunadamente subtree pushno reenvía --ontoal subyacente split, por lo que la operación debe realizarse en dos comandos. Una vez hecho esto, veo que mis comandos son idénticos a los de una de las otras respuestas, pero la explicación es diferente, así que lo dejaré aquí de todos modos.]

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

O si no está 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

Pasé horas estudiando detenidamente la fuente de git-subtree para averiguarlo, así que espero que lo aprecies;)

subtree pushcomienza ejecutando subtree split, lo que reescribe su historial de confirmaciones en un formato que debería estar listo para ser enviado. La forma en que lo hace es que public/shared/quita el frente de cualquier ruta que lo tenga y elimina cualquier información sobre los archivos que no lo tienen. Eso significa que incluso si extrae no aplastado, todas las confirmaciones del sub-repositorio ascendente se ignoran ya que nombran los archivos por sus rutas desnudas. (Compromisos que no tocan ningún archivo debajopublic/shared/, o fusionar confirmaciones que son idénticas a un padre, también se contraen. [EDITAR: Además, desde entonces encontré algo de detección de aplastamiento, así que ahora estoy pensando que es solo si tiraste sin aplastar, y luego el colapso de confirmación de fusión simplista descrito en otra respuesta logra elegir el camino no aplastado y descarte la ruta aplastada.]) El resultado es que las cosas que intenta impulsar terminan conteniendo cualquier trabajo que alguien haya comprometido con el repositorio de host actual desde el que está presionando, pero no las personas de trabajo comprometidas directamente con el sub-repositorio o mediante otro host repositorio.

Sin embargo, si las usa --onto, todas las confirmaciones ascendentes se registran como correctas para usarlas literalmente, por lo que cuando el proceso de reescritura las encuentra como uno de los padres de una fusión que quiere reescribir, las mantendrá en lugar de intentar reescribirlas. de la forma habitual.

entheh
fuente
2
Recomiendo esta respuesta como la que ya me había ayudado dos veces. Y la descripción es lo que necesitaba para entender cómo funciona dividir y empujar hacia el lugar correcto. Gracias.
Kamil Wozniak
2
¿Qué es "proyecto compartido"?
Colin D
1
@ColinD "proyecto compartido" es el nombre del control remoto. Es un nombre que habrá elegido al configurar el lugar desde el que buscar y enviar. Puede obtener una lista de controles remotos con git remote -v.
entheh
@entheh, las horas que pasaste en esto me han alegrado el día. ¡Gracias!
junio
1
Encontré que este error siempre ocurre cuando su sub-repositorio tiene un directorio cuyo nombre es el mismo que el prefijo, por ejemplo, agrega el sub-repositorio como "libxxx" en el repositorio principal, y su sub-repositorio también tiene un sub-directorio llamado "libxxx ". En este caso, git tratará incorrectamente las confirmaciones en el sub-repositorio como confirmaciones en el repositorio principal e intentará dividirlas. La --ontoopción ayuda a git a identificar las confirmaciones que ya están en el repositorio secundario.
Cosyn
14

Para una aplicación de tipo "GitHub pages", en la que implementas un subárbol "dist" en una rama de gh-pages, la solución podría verse así

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

Menciono esto porque se ve ligeramente diferente de los ejemplos de heroku dados anteriormente. Puede ver que mi carpeta "dist" existe en la rama maestra de mi repositorio, y luego la empujo como un subárbol a la rama gh-pages que también está en origen.

Colin D
fuente
3

También me he encontrado con este problema antes, y así es como lo resolví.

Lo que descubrí fue que tenía una rama que no estaba adjunta a la rama principal local. Esta rama existe y está colgando en el vacío. En tu caso, probablemente se llame project-shared. Suponiendo que este sea el caso y cuando lo haga git branch, puede ver una project-sharedrama local , entonces puede ' agregar ' nuevas confirmaciones a su project-sharedrama existente haciendo:

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

La forma en que entendí es git subtreeque comenzará a crear una nueva rama --onto, en este caso, es la public-sharedrama local . Entonces la rama significa crear una rama, que simplemente reemplaza la public-sharedrama anterior.

Esto mantendrá todo el SHA anterior de la public-sharedsucursal. Finalmente, puedes hacer un

git push project-shared project-shared:master

Suponiendo que también tenga un project-sharedcontrol remoto; esto empujará el local que cuelga en la project-shared rama vacía hacia la masterrama del control remoto project-shared.

snw
fuente
3

Esto se debe a la limitación del algoritmo original. Al manejar fusiones-confirmaciones, el algoritmo original usa un criterio simplificado para cortar a los padres no relacionados. En particular, comprueba si hay un padre que tiene el mismo árbol. Si tal padre lo encuentra, colapsaría la confirmación de fusión y usaría la confirmación padre en su lugar, asumiendo que otros padres tienen cambios no relacionados con el subárbol. En algunos casos, esto daría como resultado la eliminación de partes del historial, que tienen cambios reales en el subárbol. En particular, eliminaría secuencias de confirmaciones, que tocarían un subárbol, pero darían como resultado el mismo valor de subárbol.

Veamos un ejemplo (que puede reproducir fácilmente) para comprender mejor cómo funciona esto. Considere el siguiente historial (el formato de línea es: commit [árbol] asunto):

% 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

En este ejemplo, nos dividimos dir. Confirma Dy Etiene el mismo árbol z, porque tenemos confirmación C, que deshizo la confirmación B, por lo que la B-Csecuencia no hace nada a dirpesar de que tiene cambios.

Ahora hagamos la división. Primero nos dividimos en el compromiso 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

A continuación, dividimos el compromiso E.

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

Sí, perdimos dos confirmaciones. Esto da como resultado el error al intentar empujar la segunda división, ya que no tiene esas dos confirmaciones, que ya llegaron al origen.

Por lo general, puede tolerar este error mediante el uso push --force, ya que las confirmaciones descartadas generalmente no contienen información crítica. A largo plazo, el error debe corregirse, por lo que el historial dividido en realidad tendría todas las confirmaciones, que se tocan dir, como se esperaba. Esperaría que la solución incluyera un análisis más profundo de los compromisos de los padres para las dependencias ocultas.

Como referencia, aquí está la parte del código original, responsable del comportamiento.

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
fuente
¿Se corrigió el error a partir del 2020-05-09 (sábado)? @klimkin
halfmoonhalf
el error está arreglado. ver: contrib / subtree: corregir el error de fusión de subárbol "dividido". github.com/git/git/commit/…
halfmoonhalf
0

La respuesta de Eric Woodruff no me ayudó, pero lo siguiente sí:

Por lo general, 'git subtree pull' con la opción '--squash'. Parece que esto hizo que las cosas fueran más difíciles de reconciliar, por lo que necesitaba hacer una extracción de subárbol sin aplastar esta vez, resolver algunos conflictos y luego presionar.

Debo agregar que el tirón aplastado no reveló ningún conflicto, me dijo que todo estaba bien.

Zsolt Szatmari
fuente
0

Otra solución [más simple] es hacer avanzar la cabeza del control remoto haciendo otra confirmación si puede. Después de colocar este cabezal avanzado en el subárbol local, podrá volver a empujarlo.

9pantanoso
fuente
1
Así es exactamente como resolví el mismo problema con el repositorio remoto no sincronizado con el subárbol local.
Aidas Bendoraitis
0

Puede forzar la inserción de cambios locales en el repositorio de subárbol remoto

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

Powershell rápido para usuarios de Windows basado en la solución de Chris Jordan

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

Así que esto es lo que escribí basado en lo que dijo @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
Desmodave
fuente
0

También tuve este problema. Esto es lo que hice, según las principales respuestas:

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.

Ingrese esto:

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

Tenga en cuenta que el token generado por 'git subtree push' se usa en 'git push'.

Amós
fuente