¿Cómo eliminar / eliminar un archivo grande del historial de confirmaciones en el repositorio Git?


De vez en cuando se me cayó un DVD-rip en un proyecto de sitio web, entonces descuidadamente git commit -a -m ..., y, zap, el repositorio fue hinchado por 2.2 gigs. La próxima vez hice algunas ediciones, borré el archivo de video y confirmé todo, pero el archivo comprimido sigue ahí en el repositorio, en la historia.

Sé que puedo iniciar ramas desde esas confirmaciones y rebase una rama en otra. Pero ¿qué debo hacer para fusionar las 2 confirmaciones para que el archivo grande no se muestre en el historial y se limpien en procedimiento de recolección de basura?

Author: Whymarrh, 2010-01-20

14 answers

Use el BFG Repo-Cleaner, una alternativa más simple y rápida a git-filter-branch diseñada específicamente para eliminar archivos no deseados del historial de Git.

Siga cuidadosamente las instrucciones de uso , la parte central es solo esto:

$ java -jar bfg.jar --strip-blobs-bigger-than 100M my-repo.git

Cualquier archivo de más de 100 MB de tamaño (que no esté en su última confirmación) se eliminará del historial de su repositorio Git. Luego puede usar git gc para limpiar los datos muertos:

$ git gc --prune=now --aggressive

El BFG es típicamente al menos 10-50x más rápido que ejecutar git-filter-branch, y generalmente más fácil de usar.

Revelación completa: Soy el autor del Repo-Cleaner de BFG.

 435
Author: Roberto Tyley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-20 21:44:20

Lo que quieres hacer es altamente disruptivo si has publicado el historial a otros desarrolladores. Vea "Recuperando de Rebase ascendente" en la documentación git rebase para los pasos necesarios después de reparar su historial.

Tienes al menos dos opciones: git filter-branch y una rebase interactiva, ambas explicadas a continuación.

Usando git filter-branch

Tuve un problema similar con los datos de prueba binarios voluminosos de una importación de Subversion y escribí sobre la eliminación de datos de un git repository .

Digamos que tu historial de git es:

$ git lola --name-status
* f772d66 (HEAD, master) Login page
| A     login.html
* cb14efd Remove DVD-rip
| D     oops.iso
* ce36c98 Careless
| A     oops.iso
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

Tenga en cuenta que git lola es un alias no estándar pero muy útil. Con el interruptor --name-status, podemos ver modificaciones de árbol asociadas con cada commit.

En el commit "Careless" (cuyo nombre de objeto SHA1 es ce36c98) el archivo oops.iso es el DVD-rip añadido por accidente y eliminado en el siguiente commit, cb14efd. Usando la técnica descrita en la entrada de blog antes mencionada, el comando a ejecutar is:

git filter-branch --prune-empty -d /dev/shm/scratch \
  --index-filter "git rm --cached -f --ignore-unmatch oops.iso" \
  --tag-name-filter cat -- --all

Opciones:

  • --prune-emptyelimina las confirmaciones que se vuelven vacías ( es decir, , no cambian el árbol) como resultado de la operación de filtro. En el caso típico, esta opción produce un historial más limpio.
  • -d nombra un directorio temporal que aún no existe para usar para construir el historial filtrado. Si se está ejecutando en una distribución Linux moderna, especificar un árbol en /dev/shm dará como resultado una ejecución más rápida.
  • --index-filter es el evento principal y se ejecuta contra el índice en cada paso de la historia. Quieres eliminar oops.iso donde sea que se encuentre, pero no está presente en todas las confirmaciones. El comando git rm --cached -f --ignore-unmatch oops.iso elimina el DVD-rip cuando está presente y no falla de lo contrario.
  • --tag-name-filter describe cómo reescribir nombres de etiquetas. Un filtro de cat es la operación de identidad. Su repositorio, como el ejemplo anterior, puede no tener ninguna etiqueta, pero incluí esta opción para generalidad completa.
  • -- especifica el final de opciones para git filter-branch
  • --all después de -- es la abreviatura de todas las referencias. Su repositorio, como el ejemplo anterior, puede tener solo una ref (master), pero incluí esta opción para generalidad completa.

Después de algunos batidos, la historia es ahora:{[43]]}

$ git lola --name-status
* 8e0a11c (HEAD, master) Login page
| A     login.html
* e45ac59 Careless
| A     other.html
| * f772d66 (refs/original/refs/heads/master) Login page
| | A   login.html
| * cb14efd Remove DVD-rip
| | D   oops.iso
| * ce36c98 Careless
|/
|   A   oops.iso
|   A   other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

Observe que el nuevo commit "Careless" añade solo other.html y que el commit "Remove DVD-rip" ya no está en la rama master. La rama etiquetada refs/original/refs/heads/master contiene tus commits originales en caso de que cometieras un error. A elimínelo, siga los pasos en "Lista de verificación para Reducir un repositorio."

$ git update-ref -d refs/original/refs/heads/master
$ git reflog expire --expire=now --all
$ git gc --prune=now

Para una alternativa más simple, clone el repositorio para descartar los bits no deseados.

$ cd ~/src
$ mv repo repo.old
$ git clone file:///home/user/src/repo.old repo

Usando una URL de clonación file:///... copia objetos en lugar de crear solo enlaces duros.

Ahora su historia es:

$ git lola --name-status
* 8e0a11c (HEAD, master) Login page
| A     login.html
* e45ac59 Careless
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

Los nombres de objeto SHA1 para las dos primeras confirmaciones ("Index" y "Admin page") permanecieron iguales porque la operación de filtro no modificó esas confirmaciones. "Descuidado" perdido oops.iso y "Página de inicio de sesión" tienen un nuevo padre, por lo que sus SHA1s hicieron cambiar.

Rebase interactivo

Con una historia de:

$ git lola --name-status
* f772d66 (HEAD, master) Login page
| A     login.html
* cb14efd Remove DVD-rip
| D     oops.iso
* ce36c98 Careless
| A     oops.iso
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

Desea eliminar oops.iso de "Descuidado" como si nunca lo hubiera agregado, y luego "Eliminar DVD-rip" es inútil para usted. Por lo tanto, nuestro plan de ir a una rebase interactiva es mantener "Página de administración", editar "Descuidado" y descartar "Eliminar DVD-rip."

Al ejecutar $ git rebase -i 5af4522 se inicia un editor con lo siguiente contenido.

pick ce36c98 Careless
pick cb14efd Remove DVD-rip
pick f772d66 Login page

# Rebase 5af4522..f772d66 onto 5af4522
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

Ejecutando nuestro plan, lo modificamos a{[43]]}

edit ce36c98 Careless
pick f772d66 Login page

# Rebase 5af4522..f772d66 onto 5af4522
# ...

Es decir, eliminamos la línea con "Remove DVD-rip" y cambiamos la operación en "Careless" a edit en lugar de pick.

Guardar-salir del editor nos deja en un símbolo del sistema con el siguiente mensaje.

Stopped at ce36c98... Careless
You can amend the commit now, with

        git commit --amend

Once you are satisfied with your changes, run

        git rebase --continue

Como nos dice el mensaje, estamos en el commit "Descuidado" que queremos editar, por lo que ejecutamos dos comandos.

$ git rm --cached oops.iso
$ git commit --amend -C HEAD
$ git rebase --continue

El primero elimina el archivo ofensivo del índice. El second modifica o modifica "Careless" para que sea el índice actualizado y -C HEAD indica a git que reutilice el mensaje de confirmación antiguo. Finalmente, git rebase --continue continúa con el resto de la operación de rebase.

Esto da una historia de:

$ git lola --name-status
* 93174be (HEAD, master) Login page
| A     login.html
* a570198 Careless
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

Que es lo Que quieres.

 479
Author: Greg Bacon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-20 10:04:24

¿Por qué no usar este comando simple pero poderoso?

git filter-branch --tree-filter 'rm -f DVD-rip' HEAD

La opción --tree-filter ejecuta el comando especificado después de cada checkout del proyecto y luego vuelve a enviar los resultados. En este caso, se elimina un archivo llamado DVD-rip de cada instantánea, exista o no.

Ver este enlace.

 119
Author: Gary Gauh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-27 02:39:52

Estos comandos funcionaron en mi caso:

git filter-branch --force --index-filter 'git rm --cached -r --ignore-unmatch oops.iso' --prune-empty --tag-name-filter cat -- --all
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now

Es un poco diferente de las versiones anteriores.

Para aquellos que necesitan enviar esto a github / bitbucket (solo probé esto con bitbucket):

# WARNING!!!
# this will rewrite completely your bitbucket refs
# will delete all branches that you didn't have in your local

git push --all --prune --force

# Once you pushed, all your teammates need to clone repository again
# git pull will not work
 27
Author: Kostanos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-23 15:54:06

(La mejor respuesta que he visto a este problema es: https://stackoverflow.com/a/42544963/714112 , copiado aquí ya que este hilo aparece alto en los rankings de búsqueda de Google, pero el otro no)

Un proyectil ultrarrápido one-liner

Este script de shell muestra todos los objetos blob del repositorio, ordenados de menor a mayor.

Para mi repositorio de muestra, corrió alrededor de 100 veces más rápido que los otros que se encuentran aquí.
En mi fiel Athlon II X4 sistema, maneja el repositorio del Kernel de Linux con sus 5.622.155 objetos en poco más de un minuto.

El Script Base

git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| awk '/^blob/ {print substr($0,6)}' \
| sort --numeric-sort --key=2 \
| cut --complement --characters=13-40 \
| numfmt --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest

Cuando ejecuta el código anterior, obtendrá una salida agradable legible por humanos como esta:

...
0d99bb931299  530KiB path/to/some-image.jpg
2ba44098e28f   12MiB path/to/hires-image.png
bd1741ddce0d   63MiB path/to/some-video-1080p.mp4

Eliminación rápida de archivos

Supongamos que luego desea eliminar los archivos a y b de cada confirmación accesible desde HEAD, puede usar este comando:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch a b' HEAD
 25
Author: Sridhar-Sarnobat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-07 00:37:00

Después de probar prácticamente todas las respuestas en SO, finalmente encontré esta gema que eliminó y borró rápidamente los archivos grandes en mi repositorio y me permitió sincronizar de nuevo: http://www.zyxware.com/articles/4027/how-to-delete-files-permanently-from-your-local-and-remote-git-repositories

CD a su carpeta de trabajo local y ejecute el siguiente comando:

git filter-branch -f --index-filter "git rm -rf --cached --ignore-unmatch FOLDERNAME" -- --all

Reemplace FOLDERNAME con el archivo o carpeta que desea eliminar del repositorio git dado.

Una vez esto se hace ejecutando los siguientes comandos para limpiar el repositorio local:

rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now

Ahora envía todos los cambios al repositorio remoto:

git push --all --force

Esto limpiará el repositorio remoto.

 18
Author: Justin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-26 17:35:48

git filter-branch --tree-filter 'rm -f path/to/file' HEAD funcionó bastante bien para mí, aunque me encontré con el mismo problema que se describe aquí, que resolví siguiendo esta sugerencia.

El libro pro-git tiene un capítulo completo sobre reescribir la historia - echa un vistazo a la filter-branch/Eliminar un Archivo de Cada sección Commit .

 9
Author: Thorsten Lorenz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:57

Solo tenga en cuenta que estos comandos pueden ser muy destructivos. Si más personas están trabajando en el repositorio, todos tendrán que tirar del nuevo árbol. Los tres comandos del medio no son necesarios si su objetivo NO es reducir el tamaño. Porque la rama filter crea una copia de seguridad del archivo eliminado y puede permanecer allí durante mucho tiempo.

$ git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch YOURFILENAME" HEAD
$ rm -rf .git/refs/original/ 
$ git reflog expire --all 
$ git gc --aggressive --prune
$ git push origin master --force
 8
Author: mkljun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-16 15:41:05

Si sabes que tu commit fue reciente en lugar de pasar por todo el árbol, haz lo siguiente: git filter-branch --tree-filter 'rm LARGE_FILE.zip' HEAD~10..HEAD

 7
Author: Soheil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-01 06:21:33

Me encontré con esto con una cuenta bitbucket, donde accidentalmente había almacenado enormes *.jpa copias de seguridad de mi sitio.

git filter-branch --prune-empty --index-filter 'git rm -rf --cached --ignore-unmatch MY-BIG-DIRECTORY-OR-FILE' --tag-name-filter cat -- --all

Relpace MY-BIG-DIRECTORYcon la carpeta en cuestión para reescribir completamente su historial ( incluyendo etiquetas).

Fuente: http://naleid.com/blog/2012/01/17/finding-and-purging-big-files-from-git-history

 5
Author: lfender6445,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-03 12:54:26

Puede hacer esto usando el comando branch filter:

git filter-branch --tree-filter 'rm -rf path/to/your/file' HEAD

 3
Author: John Foley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-05 23:28:30

Use Git Extensions, es una herramienta de interfaz de usuario. Tiene un plugin llamado "Find large files" que encuentra archivos lage en repositorios y permite eliminarlos permenentemente.

No use 'git filter-branch' antes de usar esta herramienta, ya que no podrá encontrar archivos eliminados por 'filter-branch' (Aunque 'filter-branch' no elimina archivos completamente de los archivos del paquete del repositorio).

 1
Author: Nir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-31 13:22:11

Cuando se encuentra con este problema, git rm no será suficiente, ya que git recuerda que el archivo existió una vez en nuestro historial, y por lo tanto mantendrá una referencia a él.

Para empeorar las cosas, el cambio de base tampoco es fácil, porque cualquier referencia al blob evitará que git garbage collector limpie el espacio. Esto incluye referencias remotas y referencias de reflog.

Armé git forget-blob, un pequeño script que intenta eliminar todas estas referencias, y luego usa git filter-branch para reescribir cada commit en la rama.

Una vez que su blob esté completamente sin referencia, git gc se deshará de él

El uso es bastante simple git forget-blob file-to-forget. Puede obtener más información aquí

Https://ownyourbits.com/2017/01/18/completely-remove-a-file-from-a-git-repository-with-git-forget-blob/

Armé esto gracias a las respuestas de Stack Overflow y algunas entradas de blog. ¡Créditos para ellos!

 1
Author: nachoparker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-23 12:21:46

Básicamente hice lo que estaba en esta respuesta: https://stackoverflow.com/a/11032521/1286423

(para la historia, la copiaré y pegaré aquí)

$ git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch YOURFILENAME" HEAD
$ rm -rf .git/refs/original/ 
$ git reflog expire --all 
$ git gc --aggressive --prune
$ git push origin master --force

No funcionó, porque me gusta renombrar y mover cosas mucho. Así que algunos archivos grandes estaban en carpetas que se han renombrado, y creo que el gc no pudo eliminar la referencia a esos archivos debido a la referencia en los objetos tree que apuntan a esos archivos. Mi solución definitiva para realmente matarlo fue:

# First, apply what's in the answer linked in the front
# and before doing the gc --prune --aggressive, do:

# Go back at the origin of the repository
git checkout -b newinit <sha1 of first commit>
# Create a parallel initial commit
git commit --amend
# go back on the master branch that has big file
# still referenced in history, even though 
# we thought we removed them.
git checkout master
# rebase on the newinit created earlier. By reapply patches,
# it will really forget about the references to hidden big files.
git rebase newinit

# Do the previous part (checkout + rebase) for each branch
# still connected to the original initial commit, 
# so we remove all the references.

# Remove the .git/logs folder, also containing references
# to commits that could make git gc not remove them.
rm -rf .git/logs/

# Then you can do a garbage collection,
# and the hidden files really will get gc'ed
git gc --prune --aggressive

Mi repo (el .git) cambió de 32MB a 388KB, que ni siquiera la rama de filtro podía limpiar.

 1
Author: Dolanor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-31 14:22:12