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

Resuelto culebrón asked hace 14 años • 24 respuestas

Accidentalmente dejé caer un DVD en un proyecto de sitio web, sin cuidado git commit -a -m ..., y, zap, el repositorio estaba inflado en 2,2 GB. La próxima vez hice algunas ediciones, eliminé el archivo de vídeo y comprobé todo, pero el archivo comprimido todavía estaba en el repositorio, en el historial.

Sé que puedo iniciar ramas a partir de esas confirmaciones y cambiar la base de una rama a otra. Pero, ¿qué debo hacer para fusionar las dos confirmaciones, de modo que el archivo grande no aparezca en el historial y se limpie en el procedimiento de recolección de basura?

culebrón avatar Jan 20 '10 18:01 culebrón
Aceptado

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

Siga atentamente las instrucciones de uso . La parte central es simplemente esta:

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 puedes usar git gcpara limpiar los datos muertos:

git reflog expire --expire=now --all && git gc --prune=now --aggressive

Después de la poda, podemos forzar el envío al repositorio remoto*

git push --force

Nota : no se puede forzar el envío de una rama de protección en GitHub

El BFG suele ser al menos entre 10 y 50 veces más rápido que correr git-filter-branchy, en general, más fácil de usar.

Divulgación completa: soy el autor de BFG Repo-Cleaner.

Roberto Tyley avatar Jul 26 '2013 20:07 Roberto Tyley

NB : Desde que se escribió esta respuesta, git filter-branchha quedado obsoleta y ya no es compatible. Consulte la página de manual para obtener más información.


Lo que quiere hacer es muy perjudicial si ha publicado el historial para otros desarrolladores. Consulte "Recuperación desde Upstream Rebase" en la git rebasedocumentación para conocer los pasos necesarios después de reparar su historial.

Tiene al menos dos opciones: git filter-branchy una rebase interactiva , ambas se explican a continuación.

Usandogit filter-branch

Tuve un problema similar con datos de prueba binarios voluminosos de una importación de Subversion y escribí sobre cómo eliminar datos de un repositorio git .

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 lolaes un alias no estándar pero muy útil. (Consulte el anexo al final de esta respuesta para obtener más detalles). El --name-statuscambio git logmuestra las modificaciones del árbol asociadas con cada confirmación.

En la confirmación "Careless" (cuyo nombre de objeto SHA1 es ce36c98), el archivo oops.isoes la copia del DVD agregada por accidente y eliminada en la siguiente confirmación, cb14efd. Utilizando la técnica descrita en la publicación del blog antes mencionada, el comando a ejecutar es:

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 quedan 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.
  • -dnombra un directorio temporal que aún no existe para usarlo en la creación del historial filtrado. Si está ejecutando una distribución de Linux moderna, especificar un árbol /dev/shmdará como resultado una ejecución más rápida .
  • --index-filteres el evento principal y va contra el índice en cada paso de la historia. Desea eliminar oops.isodondequiera que se encuentre, pero no está presente en todas las confirmaciones. El comando git rm --cached -f --ignore-unmatch oops.isoelimina la copia del DVD cuando está presente y no falla en caso contrario.
  • --tag-name-filterdescribe cómo reescribir nombres de etiquetas. Un filtro de cates la operación de identidad. Es posible que su repositorio, como el ejemplo anterior, no tenga etiquetas, pero incluí esta opción para mayor generalidad.
  • --especifica el final de las opciones paragit filter-branch
  • --allLo siguiente --es una abreviatura de todas las referencias. Su repositorio, como el ejemplo anterior, puede tener solo una referencia (maestro), pero incluí esta opción para una generalidad total.

Después de algunos cambios, la historia ahora es:

$ 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 la nueva confirmación "Descuidado" solo agrega other.htmly que la confirmación "Eliminar DVD-rip" ya no está en la rama maestra. La rama etiquetada refs/original/refs/heads/mastercontiene sus confirmaciones originales en caso de que haya cometido un error. Para eliminarlo, siga los pasos de "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

Como alternativa más sencilla, clone el repositorio para descartar los bits no deseados.

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

El uso de una file:///...URL clonada copia objetos en lugar de crear únicamente enlaces físicos.

Ahora tu 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 los objetos SHA1 para las dos primeras confirmaciones ("Índice" y "Página de administración") se mantuvieron igual porque la operación de filtro no modificó esas confirmaciones. "Descuidado" se perdió oops.isoy la "Página de inicio de sesión" obtuvo un nuevo padre, por lo que sus SHA1 cambiaron .

Rebase interactivo

Con antecedentes 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.isode "Careless" como si nunca lo hubiera agregado, y luego "Eliminar DVD-rip" es inútil para usted. Por lo tanto, nuestro plan al realizar una rebase interactiva es mantener la "Página de administración", editar "Descuidado" y descartar "Eliminar DVD-rip".

La ejecución $ git rebase -i 5af4522inicia un editor con el 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 para

edit ce36c98 Careless
pick f772d66 Login page

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

Es decir, eliminamos la línea con “Quitar DVD-rip” y cambiamos la operación en “Careless” para que sea editen lugar de pick.

Guardar y salir del editor nos lleva a 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 compromiso "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 infractor del índice. El segundo modifica o modifica "Careless" para que sea el índice actualizado e -C HEADindica a git que reutilice el mensaje de confirmación anterior. Finalmente, git rebase --continuecontinú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.

Anexo: Habilitar git lolavía~/.gitconfig

Citando a Conrad Parker :

El mejor consejo que aprendí en la charla de Scott Chacon en linux.conf.au 2010, Git Wrangling - Consejos y trucos avanzados fue este alias:

lol = log --graph --decorate --pretty=oneline --abbrev-commit

Esto proporciona un gráfico realmente bonito de su árbol, que muestra la estructura de ramas de las fusiones, etc. Por supuesto, existen herramientas GUI realmente interesantes para mostrar dichos gráficos, pero la ventaja es git lolque funciona en una consola o en otra ssh, por lo que es útil para desarrollo remoto, o desarrollo nativo en placa embebida…

Entonces, simplemente copie lo siguiente ~/.gitconfigpara su git lolaacción a todo color:

[alias]
        lol = log --graph --decorate --pretty=oneline --abbrev-commit
        lola = log --graph --decorate --pretty=oneline --abbrev-commit --all
[color]
        branch = auto
        diff = auto
        interactive = auto
        status = auto
Greg Bacon avatar Jan 28 '2010 21:01 Greg Bacon

NB : Desde que se escribió esta respuesta, git filter-branchha quedado obsoleta y ya no es compatible. Consulte la página de manual para obtener más información.


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

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

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

Si sabe qué confirmación introdujo el archivo enorme (digamos 35dsa2), puede reemplazar HEAD con 35dsa2..HEAD para evitar reescribir demasiado historial, evitando así confirmaciones divergentes si aún no lo ha presionado. Este comentario, cortesía de @alpha_989, parece demasiado importante para dejarlo aquí.

Vea este enlace .

Gary Gauh avatar May 16 '2015 09:05 Gary Gauh

100 veces más rápido que git filter-branch y más fácil de usar

Hay muy buenas respuestas en este hilo, pero mientras tanto muchas de ellas están desactualizadas. Ya no se recomienda su uso git-filter-branch, porque es difícil de usar y tremendamente lento en repositorios grandes con muchas confirmaciones.

git-filter-repoEs mucho más rápido y fácil de usar.

git-filter-repoes un script de Python, disponible en github: https://github.com/newren/git-filter-repo . Cuando está instalado, parece un comando git normal y se puede llamar mediante git filter-repo.

Solo necesitas un archivo: el script Python3 git-filter-repo. Cópielo a una ruta que esté incluida en la variable PATH. En Windows, es posible que deba cambiar la primera línea del script (consulte INSTALL.md). Necesita tener Python3 instalado en su sistema, pero esto no es gran cosa.

primero puedes correr

git filter-repo --analyze

Esto le ayudará a determinar qué hacer a continuación.

Puede eliminar su archivo DVD-rip en todas partes:

git filter-repo --invert-paths --path DVD-rip
 

Filter-repo es realmente rápido. Una tarea que tomó alrededor de 9 horas en mi computadora mediante filter-branch se completó en 4 minutos mediante filter-repo. Puedes hacer muchas más cosas interesantes con filter-repo. Consulte la documentación para eso.

Advertencia: haga esto en una copia de su repositorio. Muchas acciones de filter-repo no se pueden deshacer. filter-repo cambiará los hashes de confirmación de todas las confirmaciones modificadas (por supuesto) y todos sus descendientes hasta las últimas confirmaciones.

Donat avatar May 04 '2020 22:05 Donat

Después de probar prácticamente todas las respuestas en SO, finalmente encontré esta joya que eliminó y eliminó rápidamente los archivos grandes en mi repositorio y me permitió sincronizar nuevamente: http://www.zyxware.com/articles/4027/how-to-delete -archivos-permanentemente-desde-sus-repositorios-git-locales-y-remotos

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 de git determinado.

Una vez hecho esto, ejecute 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íe todos los cambios al repositorio remoto:

git push --all --force

Esto limpiará el repositorio remoto.

Justin avatar Apr 26 '2017 17:04 Justin