¿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?
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.
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-empty
elimina 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 eliminaroops.iso
donde sea que se encuentre, pero no está presente en todas las confirmaciones. El comandogit 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 decat
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 paragit 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.
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.
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
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
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.
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 .
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
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
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-DIRECTORY
con 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
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
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).
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í
Armé esto gracias a las respuestas de Stack Overflow y algunas entradas de blog. ¡Créditos para ellos!
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.
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