Eliminar la carpeta y su contenido del historial de git / GitHub

318

Estaba trabajando en un repositorio en mi cuenta de GitHub y este es un problema con el que me topé.

  • Proyecto Node.js con una carpeta con algunos paquetes npm instalados
  • Los paquetes estaban en la node_modulescarpeta
  • Agregó esa carpeta al repositorio de git y empujó el código a github (no estaba pensando en la parte npm en ese momento)
  • Me di cuenta de que realmente no necesitas esa carpeta para ser parte del código
  • Eliminé esa carpeta, la empujé

En ese caso, el tamaño del repositorio total de git era de alrededor de 6 MB, donde el código real (todo excepto esa carpeta) era de solo alrededor de 300 KB .

Ahora, lo que estoy buscando al final es una forma de deshacerme de los detalles de esa carpeta del paquete del historial de git, por lo que si alguien lo clona, ​​no tienen que descargar un historial de 6 MB donde solo obtendrán los archivos reales. a partir de la última confirmación sería de 300 KB.

Busqué posibles soluciones para esto y probé estos 2 métodos

Gist parecía que funcionaba donde, después de ejecutar el script, mostraba que se había deshecho de esa carpeta y luego mostraba que se habían modificado 50 commits diferentes. Pero no me dejó empujar ese código. Cuando traté de presionarlo, dijo Branch up to datepero mostró que 50 commits fueron modificados en a git status. Los otros 2 métodos tampoco ayudaron.

Ahora, aunque demostró que se deshizo del historial de esa carpeta, cuando verifiqué el tamaño de ese repositorio en mi localhost, todavía era de alrededor de 6 MB. (También eliminé la refs/originalcarpeta pero no vi el cambio en el tamaño del repositorio).

Lo que quiero aclarar es, si hay una manera de deshacerse no solo del historial de confirmaciones (que es lo único que creo que sucedió) sino también de esos archivos que git sigue asumiendo que uno quiere revertir.

Digamos que se presenta una solución para esto y se aplica en mi host local, pero no se puede reproducir en ese repositorio de GitHub, ¿es posible clonar ese repositorio? ¿Todavía tienes un historial de todos esos commits?

Mi objetivo final aquí es básicamente encontrar la mejor manera de deshacerme del contenido de la carpeta de git para que un usuario no tenga que descargar cosas por valor de 6MB y aún así posiblemente tenga los otros commits que nunca tocaron la carpeta de módulos (eso es bastante mucho todos ellos) en la historia de git.

¿Cómo puedo hacer esto?

Kartik
fuente
3
Si alguna de las respuestas a continuación resolvió su problema, tal vez debería considerar aceptar una como respuesta a su pregunta. meta.stackexchange.com/questions/5234/…
starbeamrainbowlabs
La mejor respuesta es: stackoverflow.com/a/32886427/5973334
Kuzeko

Respuestas:

556

Si está aquí para copiar y pegar código:

Este es un ejemplo que elimina node_modulesdel historial

git filter-branch --tree-filter "rm -rf node_modules" --prune-empty HEAD
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
echo node_modules/ >> .gitignore
git add .gitignore
git commit -m 'Removing node_modules from git history'
git gc
git push origin master --force

Lo que realmente hace git:

La primera línea itera a través de todas las referencias en el mismo árbol ( --tree-filter) que HEAD (su rama actual), ejecutando el comando rm -rf node_modules. Este comando elimina la carpeta node_modules ( -r, sin -r, rmno eliminará carpetas), sin que se indique al usuario ( -f). Los --prune-emptyborrados añadidos se comprometen inútilmente (sin cambiar nada) de forma recursiva.

La segunda línea elimina la referencia a esa antigua rama.

El resto de los comandos son relativamente sencillos.

Mohsen
fuente
3
Solo una nota al margen: solía git count-objects -vverificar si los archivos se eliminaron realmente, pero el tamaño del repositorio sigue siendo el mismo hasta que cloné el repositorio nuevamente. Git mantiene una copia de todos los archivos originales, creo.
Davide Icardi
44
Con un git no antiguo, esto probablemente debería leer --force-with-lease, no --force.
Griwes
44
Ninguno de estos comandos funciona en Windows. O al menos no Windows 10, publique el sistema operativo en el que funciona "cortar y pegar"
David
3
Para los usuarios de Windows 10, esto funciona muy bien en Bash para Windows (usé Ubuntu)
Andrej Kyselica
3
Lo probé con el shell de Windows y con git bash, y no funcionó. El primer comando pasa, el segundo comando falla.
Mohy Eldeen
240

Encuentro que la --tree-filteropción utilizada en otras respuestas puede ser muy lenta, especialmente en repositorios más grandes con muchas confirmaciones.

Aquí está el método que uso para eliminar completamente un directorio del historial de git usando la --index-filteropción, que se ejecuta mucho más rápido:

# Make a fresh clone of YOUR_REPO
git clone YOUR_REPO
cd YOUR_REPO

# Create tracking branches of all branches
for remote in `git branch -r | grep -v /HEAD`; do git checkout --track $remote ; done

# Remove DIRECTORY_NAME from all commits, then remove the refs to the old commits
# (repeat these two commands for as many directories that you want to remove)
git filter-branch --index-filter 'git rm -rf --cached --ignore-unmatch DIRECTORY_NAME/' --prune-empty --tag-name-filter cat -- --all
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d

# Ensure all old refs are fully removed
rm -Rf .git/logs .git/refs/original

# Perform a garbage collection to remove commits with no refs
git gc --prune=all --aggressive

# Force push all branches to overwrite their history
# (use with caution!)
git push origin --all --force
git push origin --tags --force

Puede verificar el tamaño del repositorio antes y después del gccon:

git count-objects -vH
Lee Netherton
fuente
3
¿Podría explicar por qué esto es mucho más rápido?
Knocte
77
@knocte: de los documentos ( git-scm.com/docs/git-filter-branch ). "--index-filter: ... es similar al filtro de árbol pero no comprueba el árbol, lo que lo hace mucho más rápido"
Lee Netherton
23
¿Por qué esta no es la respuesta aceptada? Es muy minucioso
Físico loco
2
Si hace esto en Windows, necesita comillas dobles en lugar de comillas simples.
Kris Morness
12
Pasar --quieta lo git rmanterior aceleró mi reescritura al menos por el factor 4.
ctusch
46

Además de la respuesta popular anterior, me gustaría agregar algunas notas para los sistemas Windows . El comando

git filter-branch --tree-filter 'rm -rf node_modules' --prune-empty HEAD
  • funciona perfectamente sin ninguna modificación! Por lo tanto, no debe usar Remove-Item, delni nada más en lugar de rm -rf.

  • Si necesita especificar una ruta a un archivo o directorio, utilice barras como./path/to/node_modules

partícipe
fuente
Esto no funcionará en Windows si el directorio contiene un. (punto) en el nombre.
Corneliu Serediuc
44
Y encontré la solución. Use comillas dobles invertidas para el comando rm como este: "rm -rf node.modules".
Corneliu Serediuc
23

El método mejor y más preciso que encontré fue descargar el archivo bfg.jar: https://rtyley.github.io/bfg-repo-cleaner/

Luego ejecuta los comandos:

git clone --bare https://project/repository project-repository
cd project-repository
java -jar bfg.jar --delete-folders DIRECTORY_NAME  # i.e. 'node_modules' in other examples
git reflog expire --expire=now --all && git gc --prune=now --aggressive
git push --mirror https://project/new-repository

Si desea eliminar archivos, use la opción eliminar archivos en su lugar:

java -jar bfg.jar --delete-files *.pyc
Kim T
fuente
1
muy fácil :) si quieres asegurar que solo se elimine una carpeta específica, esto ayudará: stackoverflow.com/questions/21142986/…
emjay
9

Parece que la respuesta actualizada a esto es no usar filter-branchdirectamente (al menos git en sí mismo ya no lo recomienda) y diferir ese trabajo a una herramienta externa. En particular, actualmente se recomienda git-filter-repo . El autor de esa herramienta proporciona argumentos sobre por qué usar filter-branchdirectamente puede generar problemas.

La mayoría de los scripts de varias líneas anteriores para eliminar dirdel historial podrían reescribirse como:

git filter-repo --path dir --invert-paths

La herramienta es más poderosa que solo eso, aparentemente. Puede aplicar filtros por autor, correo electrónico, nombre de referencia y más (página de manual completa aquí ). Además, es rápido . La instalación es fácil: se distribuye en una variedad de formatos .

André Anjos
fuente
Buena herramienta! Funciona bien en Ubuntu 20.04, puede pip3 install git-filter-repohacerlo ya que es solo stdlib y no instala ninguna dependencia. En Ubuntu 18 es incompatible con la versión git de distro Error: need a version of git whose diff-tree command has the --combined-all-paths option, pero es bastante fácil ejecutarlo en undocker run -ti ubuntu:20.04
kubanczyk
7

Complete la receta de copiar y pegar, simplemente agregue los comandos en los comentarios (para la solución de copiar y pegar), después de probarlos:

git filter-branch --tree-filter 'rm -rf node_modules' --prune-empty HEAD
echo node_modules/ >> .gitignore
git add .gitignore
git commit -m 'Removing node_modules from git history'
git gc
git push origin master --force

Después de esto, puede eliminar la línea "node_modules /" de .gitignore

jgbarah
fuente
¿Por qué usted luego retire node_modulesde .gitignore? ¿Para que puedan ser cometidos accidentalmente de nuevo?
Adamski
1
No se elimina de gitignore, se agrega a gitignore. El mensaje de confirmación dice "git history", no "gitignore" :)
Danny Tuppeny
pero el comentario dice que luego se puede quitar node_modulesde .gitignore.
zavr
7

Para usuarios de Windows, tenga en cuenta el uso en "lugar de ' También se agrega -fpara forzar el comando si ya hay otra copia de seguridad allí.

git filter-branch -f --tree-filter "rm -rf FOLDERNAME" --prune-empty HEAD
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
echo FOLDERNAME/ >> .gitignore
git add .gitignore
git commit -m "Removing FOLDERNAME from git history"
git gc
git push origin master --force
kcode
fuente
3

Eliminé las carpetas bin y obj de antiguos proyectos de C # usando git en windows. Ten cuidado con

git filter-branch --tree-filter "rm -rf bin" --prune-empty HEAD

Destruye la integridad de la instalación de git eliminando la carpeta usr / bin en la carpeta de instalación de git.

LordObi
fuente