¿Cómo encontrar las confirmaciones que apuntan a un objeto de árbol git?

6 minutos de lectura

Al intentar reflejar un repositorio en un servidor remoto, el servidor rechaza el objeto de árbol 4e8f805dd45088219b5662bd3d434eb4c5428ec0. Este no es un árbol de nivel superior, por cierto, sino un subdirectorio.

¿Cómo puedo averiguar qué confirmaciones hacen referencia indirecta a ese objeto de árbol para poder evitar enviar las referencias que se vinculan a esas confirmaciones para que el resto de mi repositorio se envíe correctamente?

  • He considerado eliminar el objeto del árbol y luego ejecutar git fsck con la esperanza de que eliminaría todas las referencias a él como parte de la recuperación. Pero tampoco sé cómo eliminar un objeto de un archivo de paquete.

    – Andrew Arnott

    11 de diciembre de 2016 a las 15:58

  • ¿Qué tal si solucionamos el problema? Use “git bisect” para encontrar la confirmación que introdujo la referencia del árbol defectuoso, y luego puede usar git ls-tree para encontrar el árbol defectuoso.

    –Raymond Chen

    11 de diciembre de 2016 a las 18:03

  • @RaymondChen Eso podría no funcionar. Además de tomar tanto tiempo (la bisección es increíble, pero no tanto en un árbol tan grande), puede fallar porque el árbol en sí mismo puede fallar al realizar el pago en el compromiso relevante. Además, necesito un compromiso de muestra “bueno” y “malo” para bisect para comenzar, y no sé qué compromiso es malo.

    – Andrew Arnott

    12 de diciembre de 2016 a las 0:16

avatar de usuario de torek
Torek

Como notó, solo necesita encontrar las confirmaciones con el deseado tree. si pudiera ser un nivel superior árbol, necesitaría una prueba adicional, pero como no lo es, no la necesita.

Usted quiere:

  • para algún conjunto de confirmaciones (todas aquellas accesibles desde un nombre de rama dado, por ejemplo)
  • si esa confirmación tiene, como subárbol, el hash del árbol de destino: imprima la ID de la confirmación

lo cual es trivial con dos comandos de “plomería” de Git más grep.

Aquí hay una versión ligeramente actualizada de mi script original (actualizado para tomar argumentos y predeterminado a --all como en la edición de badp):

#! /bin/sh
#
case $# in
0) echo "usage: git-searchfor <object-id> [<starting commit>...]" 1>&2; exit 1;;
esac

searchfor=$(git rev-parse --verify "$1") || exit 1
searchfor=$(git rev-parse --verify "$searchfor"^{tree}) || exit 1
shift
  
git log ${@-"--all"} --pretty='format:%H' |
    while read commithash; do
        if git ls-tree -d -r --full-tree $commithash | grep $searchfor; then
            echo " -- found at $commithash"
        fi
    done

Para verificar los árboles de nivel superior, haría git cat-file -p $commithash también y ver si tiene el hash en él.

Tenga en cuenta que este mismo código encontrará blobs (suponiendo que elimine el -d opción de git ls-tree). Sin embargo, ningún árbol puede tener el ID de un blob, o viceversa. los grep imprimirá la línea coincidente para que veas, por ejemplo:

040000 tree a3a6276bba360af74985afa8d79cfb4dfc33e337    perl/Git/SVN/Memoize
 -- found at 3ab228137f980ff72dbdf5064a877d07bec76df9

Para limpiar esto para uso general, es posible que desee utilizar git cat-file -t en la búsqueda de blob-or-tree para obtener su tipo.

Como jthill señala en un comentario, git diff-tree ahora tiene un --find-object opción. Esto se introdujo en Git 2.17 (lanzado en 2018, mucho después de la pregunta original aquí). los git log comando también tiene esto, pero por lo general estamos más interesados ​​en qué compromiso específico se agregó un archivo o árbol. Al eliminar la línea adicional que intenta forzar la searchfor hash ID para ser un árbol, podemos obtener una secuencia de comandos mucho más rápida que encuentre cada aparición de cualquier árbol o objeto blob (aunque debe tener cuidado de especificar la ID hash correcta o usar el ^{tree} adjunte su sufijo si va a proporcionar un ID de hash de confirmación). Luego simplemente ejecutamos:

git log --all --find-object=$searchfor

o, como en el comentario a continuación:

git rev-list --all | git diff-tree --stdin --find-object=$searchfor

para encontrar lo que estamos buscando. (Agregar ${2-"--all"} si/como se desee.)

  • ¡Gracias! Esto debería funcionar, aunque como no sé qué rama/etiqueta tiene el árbol defectuoso, tendría que ejecutar todo en un bucle sobre cada una de mis varias miles de ramas (es un gran repositorio con muchos usuarios) . Así que tendré que diseñar alguna forma de obtener una lista de cada confirmación en el repositorio en las ramas y eliminar los duplicados primero. Pero este es un gran comienzo.

    – Andrew Arnott

    12 de diciembre de 2016 a las 0:33

  • Parece que git rev-list devuelve un conjunto de confirmaciones para cualquier número de referencias. y se necesita --stdin como parámetro. Entonces puedo hacer git branch -r | git rev-list --stdin y, de lo contrario, siga usando su script. 🙂 Excepto git branch agrega espacios en blanco delante de cada nombre de rama, lo que git rev-list no le gusta, así que escribí en un archivo, lo limpié y luego lo introduje en su secuencia de comandos. Ahora está muy ocupado buscando.

    – Andrew Arnott

    12 de diciembre de 2016 a las 0:42

  • Incluso pude cambiar origin/master a ^origin/master para reducir en gran medida la cantidad de confirmaciones, ya que sé que el árbol en cuestión no está en ninguna parte de la rama principal.

    – Andrew Arnott

    12 de diciembre de 2016 a las 0:43

  • git rev-list toma el mismo argumentos como git log. De hecho, ¡son básicamente el mismo comando! Se construyen a partir de un archivo fuente que solo cambia la configuración predeterminada cuando se ejecuta como git log contra git rev-list. Sin embargo, Rev-list está diseñado para usarse en scripts, mientras que log está diseñado para ser utilizado por humanos. En todo caso A..B “medio” B ^A asi que origin/master..master y master ^origin/master son exactamente lo mismo aquí. En este caso puedes usar git rev-list --branches ^origin/master (o tal vez --branches --tags).

    – torek

    12 de diciembre de 2016 a las 2:50


  • Entonces, ¡funcionó! Descubrí que el script sutilmente solo encontraría árboles que son subdirectorios del actual (en lugar de comenzar en la raíz). Lo arreglé, pero tardó tanto en completarse, y tenía una idea bastante buena de qué directorio representaba el árbol, así que aproveché eso como una optimización. Ahora tengo varias confirmaciones con las que trabajar. 🙂

    – Andrew Arnott

    12 de diciembre de 2016 a las 3:57

Variación de gran respuesta por torek en caso de que quiera acelerar las cosas a través de GNU paralelo:

#!/bin/bash    
searchfor="$1"
startpoints="${2-HEAD}"

git rev-list "$startpoints" |
    parallel "if git ls-tree -d -r --full-tree '{}' | grep '$searchfor'; then echo ' -- found at {}'; fi"

¿Ha sido útil esta solución?