2009-05-13 25 views
7

Recientemente hemos comenzado a usar git y tuvimos un desagradable problema cuando alguien cometió un archivo grande (~ 1.5GB), que luego causó la falla de git en varios sistemas operativos de 32 bits. Esto parece ser un error conocido (archivos git mmaps en la memoria, que no funciona si no puede obtener suficiente espacio congestivo), que no va a solucionarse en el corto plazo.Rechazar archivos grandes en git

La solución fácil (para nosotros) sería hacer que git rechace cualquier commit de más de 100MB, pero no puedo encontrar una manera de hacerlo.

EDITAR: El problema proviene del envío accidental de archivos de gran tamaño, en este caso, un volcado grande de la salida del programa. El objetivo es evitar el envío accidental, solo porque si un desarrollador presenta accidentalmente un archivo grande, tratando de recuperarlo, el repositorio es una tarde en la que nadie puede hacer ningún trabajo y tiene que arreglar todas las sucursales locales que tener.

+0

¿Tiene la intención de evitar confirmaciones inadvertidas de archivos de gran tamaño? –

Respuesta

2

¿Cuándo ocurrió exactamente el problema? ¿Cuándo cometieron el archivo originalmente o cuando se envió a otra parte? Si tiene un repositorio provisional que todo el mundo presiona, puede implementar un gancho de actualización para escanear los refs cambiantes para archivos grandes, junto con otros permisos, etc.

Muy áspera y ejemplo listo:

git --no-pager log --pretty=oneline --name-status $2..$3 -- | \ 
    perl -MGit -lne 'if (/^[0-9a-f]{40}/) { ($rev, $message) = split(/\s+/, $_, 2) } 
    else { ($action, $file) = split(/\s+/, $_, 2); next unless $action eq "A"; 
     $filesize = Git::command_oneline("cat-file", "-s", "$rev:$file"); 
     print "$rev added $file ($filesize bytes)"; die "$file too big" if ($filesize > 1024*1024*1024) }'; 

(sólo sirve para demostrar, todo se puede hacer con un Perl de una sola línea, aunque podría tomar varias líneas;))

Llamado en la forma en que se llama $ GIT_DIR/hooks/update (args are ref-name, old-rev, new-rev; por ejemplo, "refs/heads/master master ~ 2 master") esto mostrará los archivos agregados y abortará si uno se agrega que es demasiado grande.

Tenga en cuenta que diría que si va a vigilar este tipo de cosas, necesita un punto centralizado para hacerlo. Si confías en que tu equipo simplemente intercambie cambios entre ellos, debes confiar en que aprendan que agregar archivos binarios gigantes es algo malo.

1

Si tiene control sobre la cadena de herramientas de sus committers, puede ser sencillo modificar la confirmación de git para que realice una prueba de razonabilidad en el tamaño del archivo antes de la confirmación "real". Dado que tal cambio en el núcleo sería una carga para todos los usuarios de git en cada compromiso, y la estrategia alternativa de "desterrar a cualquiera que comprometiera un cambio de 1.5 GB" tiene una simplicidad atractiva, sospecho que tal prueba nunca será aceptada en el núcleo. Sugiero que sopeses la carga de mantener un tenedor local de git - nannygit - contra la carga de reparar un git colapsado después de una comisión demasiado ambiciosa.

Debo admitir que tengo curiosidad acerca de cómo llegó a ser un compromiso de 1.5 GB. ¿Están involucrados los archivos de video?

2

Puede distribuir un enlace precompromiso que evite confirmaciones. En los repositorios centrales, puede tener un gancho de pre-recepción que rechace blobs grandes al analizar los datos recibidos y evitar que se haga referencia a ellos. Se recibirán los datos, pero como usted rechaza las actualizaciones a los refs, todos los objetos nuevos recibidos no serán referenciados y pueden ser recogidos y eliminados por git gc.

No tengo un script para usted.

0
Here is my solution. I must admit it doesn't look like others I have seen, but to me it makes the most sense. It only checks the inbound commit. It does detect when a new file is too large, or an existing file becomes too big. It is a pre-receive hook. Since tags are size 0, it does not check them. 

    #!/usr/bin/env bash 
# 
# This script is run after receive-pack has accepted a pack and the 
# repository has been updated. It is passed arguments in through stdin 
# in the form 
# <oldrev> <newrev> <refname> 
# For example: 
# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master 
# 
# see contrib/hooks/ for an sample, or uncomment the next line (on debian) 
# 

set -e 

let max=1024*1024 
count=0 
echo "Checking file sizes..." 
while read oldrev newrev refname 
do 
# echo $oldrev $newrev $refname 
    # skip the size check for tag refs 
    if [[ ${refname} =~ ^refs/tags/* ]] 
    then 
     continue 
    fi 

    if [[ ${newrev} =~ ^[0]+$ ]] 
    then 
     continue 
    fi 

    # find all refs we don't care about and exclude them from diff 
    if [[ ! ${oldrev} =~ ^[0]+$ ]] 
    then 
     excludes=^${oldrev} 
    else 
     excludes=($(git for-each-ref --format '^%(refname:short)' refs/heads/)) 
    fi 
# echo "excludes " ${excludes} 
    commits=$(git rev-list $newrev "${excludes[@]}") 
    for commit in ${commits}; 
    do 
#  echo "commit " ${commit} 
     # get a list of the file changes in this commit 
     rawdiff=$(git diff-tree --no-commit-id ${commit}) 
     while read oldmode newmode oldsha newsha code fname 
     do 
#   echo "reading " ${oldmode} ${newmode} ${oldsha} ${newsha} ${code} ${fname} 
      # if diff-tree returns anything, new sha is not all 0's, and it is a file (blob) 
      if [[ "${newsha}" != "" ]] && [[ ! ${newsha} =~ ^[0]+$ ]] && [[ $(git cat-file -t ${newsha}) == "blob" ]] 
      then 
       echo -n "${fname} " 
       newsize=$(git cat-file -s ${newsha}) 
       if ((${newsize} > ${max})) 
       then 
        echo " size ${newsize}B > ${max}B" 
        let "count+=1" 
       else 
        echo "ok" 
       fi 
      fi 
     done <<< "${rawdiff}" 
    done 
done 

exit ${count}