¿Cómo puedo eliminar/eliminar un archivo grande del historial de confirmaciones en el repositorio de Git?
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?
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 gc
para 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-branch
y, en general, más fácil de usar.
Divulgación completa: soy el autor de BFG Repo-Cleaner.
NB : Desde que se escribió esta respuesta, git filter-branch
ha 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 rebase
documentación para conocer los pasos necesarios después de reparar su historial.
Tiene al menos dos opciones: git filter-branch
y 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 lola
es un alias no estándar pero muy útil. (Consulte el anexo al final de esta respuesta para obtener más detalles). El --name-status
cambio git log
muestra las modificaciones del árbol asociadas con cada confirmación.
En la confirmación "Careless" (cuyo nombre de objeto SHA1 es ce36c98), el archivo oops.iso
es 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-empty
elimina 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.-d
nombra 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/shm
dará como resultado una ejecución más rápida .--index-filter
es el evento principal y va contra el índice en cada paso de la historia. Desea eliminaroops.iso
dondequiera que se encuentre, pero no está presente en todas las confirmaciones. El comandogit rm --cached -f --ignore-unmatch oops.iso
elimina la copia del DVD cuando está presente y no falla en caso contrario.--tag-name-filter
describe cómo reescribir nombres de etiquetas. Un filtro decat
es 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
--all
Lo 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.html
y que la confirmación "Eliminar DVD-rip" ya no está en la rama maestra. La rama etiquetada refs/original/refs/heads/master
contiene 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.iso
y 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.iso
de "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 5af4522
inicia 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 edit
en 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 HEAD
indica a git que reutilice el mensaje de confirmación anterior. 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.
Anexo: Habilitar git lola
ví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 lol
que funciona en una consola o en otrassh
, por lo que es útil para desarrollo remoto, o desarrollo nativo en placa embebida…
Entonces, simplemente copie lo siguiente
~/.gitconfig
para sugit lola
acció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
NB : Desde que se escribió esta respuesta, git filter-branch
ha 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-filter
opció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 .
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-repo
Es mucho más rápido y fácil de usar.
git-filter-repo
es 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.
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.