rama - git remove tag
¿Cómo solucionas una fusión incorrecta y reproduces tus buenos compromisos en una combinación fija? (12)
Introducción: tienes 5 soluciones disponibles
El cartel original dice:
Accedí accidentalmente un archivo no deseado ... a mi repositorio hace varios intentos ... Quiero eliminar completamente el archivo del historial del repositorio.
¿Es posible volver a escribir el historial de cambios de forma tal que
filename.orig
nunca se haya agregado al repositorio en primer lugar?
Hay muchas formas diferentes de eliminar el historial de un archivo completamente de git:
- Enmendando los compromisos.
- Reajustes duros (posiblemente más un rebase).
- Rebase no interactivo.
- Rebases interactivos.
- Filtrado de ramas.
En el caso del póster original, enmendar el compromiso no es realmente una opción por sí mismo, ya que luego realizó varios compromisos adicionales, pero en aras de la integridad, también explicaré cómo hacerlo, para cualquier persona que solo quiera. para modificar su compromiso previo.
Tenga en cuenta que todas estas soluciones implican alterar / reescribir el historial / las confirmaciones de una manera a otra, por lo que cualquier persona con copias antiguas de las confirmaciones tendrá que hacer un trabajo adicional para volver a sincronizar su historia con la nueva historia.
Solución 1: enmiendas de compromisos
Si accidentalmente realizó un cambio (como agregar un archivo) en su confirmación anterior, y no desea que el historial de ese cambio siga existiendo, entonces simplemente puede modificar la confirmación anterior para eliminar el archivo:
git rm <file>
git commit --amend --no-edit
Solución 2: restablecimiento completo (posiblemente más un rebase)
Al igual que la solución 1, si solo desea deshacerse de su confirmación anterior, también tiene la opción de simplemente hacer un restablecimiento completo a su padre:
git reset --hard HEAD^
Ese comando restablecerá por completo tu rama a la primera confirmación principal.
Sin embargo , si, al igual que el póster original, ha realizado varias confirmaciones después de la confirmación a la que desea deshacer el cambio, todavía puede usar restablecimientos para modificarlo, pero hacerlo también implica utilizar un rebase. Estos son los pasos que puede usar para enmendar una confirmación más atrás en la historia:
# Create a new branch at the commit you want to amend
git checkout -b temp <commit>
# Amend the commit
git rm <file>
git commit --amend --no-edit
# Rebase your previous branch onto this new commit, starting from the old-commit
git rebase --preserve-merges --onto temp <old-commit> master
# Verify your changes
git diff master@{1}
Solución 3: Rebase no interactivo
Esto funcionará si solo desea eliminar un compromiso del historial por completo:
# Create a new branch at the parent-commit of the commit that you want to remove
git branch temp <parent-commit>
# Rebase onto the parent-commit, starting from the commit-to-remove
git rebase --preserve-merges --onto temp <commit-to-remove> master
# Or use `-p` insteda of the longer `--preserve-merges`
git rebase -p --onto temp <commit-to-remove> master
# Verify your changes
git diff master@{1}
Solución 4: Rebases interactivos
Esta solución le permitirá lograr las mismas cosas que las soluciones nº 2 y nº 3, es decir, modificar o eliminar confirmaciones más atrás en el historial que su confirmación previa anterior, por lo que la solución que elija utilizar depende de usted. Las rebases interactivas no están bien adaptadas para rebasar cientos de confirmaciones, por razones de rendimiento, por lo que usaría rebases no interactivas o la solución de rama de filtro (ver más abajo) en ese tipo de situaciones.
Para comenzar la rebase interactiva, use lo siguiente:
git rebase --interactive <commit-to-amend-or-remove>~
# Or `-i` instead of the longer `--interactive`
git rebase -i <commit-to-amend-or-remove>~
Esto hará que git rebobine el historial de confirmación al padre de la confirmación que desea modificar o eliminar. Luego le presentará una lista de las confirmaciones de rebobinado en orden inverso en cualquier editor que git esté configurado para usar (esto es Vim por defecto):
pick 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
pick 7668f34 Modify Bash config to use Homebrew recommended PATH
pick 475593a Add global .gitignore file for OS X
pick 1b7f496 Add alias for Dr Java to Bash config (OS X)
La confirmación que desea modificar o eliminar estará en la parte superior de esta lista. Para eliminarlo, simplemente elimine su línea en la lista. De lo contrario, reemplace "pick" con "edit" en la 1ª línea, así:
edit 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
A continuación, ingrese git rebase --continue
. Si eligió eliminar el compromiso por completo, entonces todo lo que necesita hacer (aparte de la verificación, vea el paso final para esta solución). Si, por otro lado, desea modificar la confirmación, git volverá a aplicar la confirmación y luego pausará la rebase.
Stopped at 00ddaacab0a85d9989217dd9fe9e1b317ed069ac... Add symlinks
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
En este punto, puede eliminar el archivo y enmendar el compromiso, luego continuar con el rebase:
git rm <file>
git commit --amend --no-edit
git rebase --continue
Eso es. Como paso final, ya sea que hayas modificado o eliminado por completo la confirmación, siempre es una buena idea verificar que no se hayan realizado otros cambios inesperados en tu sucursal al diferenciarlo con su estado antes de la rebase:
git diff master@{1}
Solución 5: Filtrado de Ramas
Finalmente, esta solución es mejor si desea borrar por completo todos los rastros de la existencia de un archivo del historial, y ninguna de las otras soluciones está a la altura de la tarea.
git filter-branch --index-filter /
''git rm --cached --ignore-unmatch <file>''
Eso eliminará <file>
de todas las confirmaciones, comenzando desde la confirmación de la raíz. Si, en cambio, solo desea volver a escribir el rango de confirmación HEAD~5..HEAD
, entonces puede pasarlo como un argumento adicional a filter-branch
, como se indica en esta respuesta :
git filter-branch --index-filter /
''git rm --cached --ignore-unmatch <file>'' HEAD~5..HEAD
Nuevamente, una vez que se completa la filter-branch
del filter-branch
, generalmente es una buena idea verificar que no haya otros cambios inesperados al diferenciar su rama con su estado anterior antes de la operación de filtrado:
git diff master@{1}
Alternativa filtro-rama: BFG Repo Cleaner
He escuchado que la herramienta BFG Repo Cleaner se ejecuta más rápido que git filter-branch
, por lo que es posible que también desees verlo como una opción. Incluso se menciona oficialmente en la documentación de la rama de filtro como una alternativa viable:
git-filter-branch le permite realizar reescrituras complejas de shell de su historial de Git, pero probablemente no necesite esta flexibilidad si simplemente está eliminando datos no deseados como archivos grandes o contraseñas. Para esas operaciones, es posible que desee considerar BFG Repo-Cleaner , una alternativa basada en JVM para git-filter-branch, generalmente al menos 10-50 veces más rápido para esos casos de uso, y con características bastante diferentes:
Cualquier versión particular de un archivo se limpia exactamente una vez . El BFG, a diferencia de git-filter-branch, no le da la oportunidad de manejar un archivo de manera diferente según dónde o cuándo se confirmó en su historial. Esta restricción proporciona el beneficio de rendimiento principal de The BFG, y se adapta bien a la tarea de limpiar datos incorrectos: no importa dónde se encuentran los datos incorrectos, solo quiere que desaparezcan .
Por defecto, BFG aprovecha al máximo las máquinas de múltiples núcleos, limpiando los archivos de confirmación en paralelo. git-filter-branch limpia las confirmaciones de forma secuencial (es decir, de una sola manera), aunque es posible escribir filtros que incluyan su propio paralelismo, en los scripts ejecutados contra cada confirmación.
Las opciones de comando son mucho más restrictivas que la rama de git-filter, y están dedicadas solo a las tareas de eliminar datos no deseados, por ejemplo: -
--strip-blobs-bigger-than 1M
.
Recursos adicionales
- Pro Git § 6.4 Git Tools - Reescribiendo la historia .
- git-filter-branch (1) Manual .
- Manual de git-commit (1) .
- git-reset (1) Manual .
- git-rebase (1) Manual .
- The BFG Repo Cleaner (ver también esta respuesta del propio creador ).
Accidentalmente cometí un archivo no deseado ( filename.orig
al resolver una fusión) en mi repositorio hace varios intentos, sin que me diera cuenta hasta ahora. Quiero eliminar completamente el archivo del historial del repositorio.
¿Es posible volver a escribir el historial de cambios de forma tal que filename.orig
nunca se haya agregado al repositorio en primer lugar?
Definitivamente, git filter-branch
es el camino a seguir.
Lamentablemente, esto no será suficiente para eliminar el filename.orig
de filename.orig
de su repositorio, ya que puede ser referenciado por etiquetas, entradas de búsqueda, controles remotos, etc.
Recomiendo eliminar todas estas referencias también, y luego llamar al recolector de basura. Puede usar el script git forget-blob
de this sitio web para hacer todo esto en un solo paso.
git forget-blob filename.orig
Esta es la mejor manera:
github.com/guides/completely-remove-a-file-from-all-revisions
Solo asegúrese de hacer una copia de seguridad de las copias de los archivos primero.
EDITAR
La edición de Neon fue desafortunadamente rechazada durante la revisión.
Vea la publicación de Neones a continuación, ¡podría contener información útil!
Por ejemplo, para eliminar todos los archivos *.gz
comprometidos accidentalmente en el repositorio git:
$ du -sh .git ==> e.g. 100M
$ git filter-branch --index-filter ''git rm --cached --ignore-unmatch *.gz'' HEAD
$ git push origin master --force
$ rm -rf .git/refs/original/
$ git reflog expire --expire=now --all
$ git gc --prune=now
$ git gc --aggressive --prune=now
¿Eso todavía no funcionó para mí? (Actualmente estoy en la versión 1.7.6.1 de git)
$ du -sh .git ==> e.g. 100M
No estoy seguro de por qué, ya que solo tenía UNA rama principal. De todos modos, finalmente conseguí que mi repositorio de git realmente se limpiara empujando hacia un nuevo repositorio de git vacío y vacío, por ejemplo
$ git init --bare /path/to/newcleanrepo.git
$ git push /path/to/newcleanrepo.git master
$ du -sh /path/to/newcleanrepo.git ==> e.g. 5M
(¡sí!)
Luego cloné eso en un nuevo directorio y lo moví sobre su carpeta .git a este. p.ej
$ mv .git ../large_dot_git
$ git clone /path/to/newcleanrepo.git ../tmpdir
$ mv ../tmpdir/.git .
$ du -sh .git ==> e.g. 5M
(si! finalmente limpiado!)
Después de verificar que todo está bien, puede eliminar los directorios ../large_dot_git
y ../tmpdir
(tal vez dentro de un par de semanas o meses, por si acaso ...)
Esto es para lo que fue diseñado git filter-branch
.
La forma más sencilla que encontré fue sugerida por leontalbot
(como comentario), que es una zyxware.com/articles/4027/… . Creo que vale su propio espacio como respuesta:
(Lo convertí a un script de bash)
#!/bin/bash
if [[ $1 == "" ]]; then
echo "Usage: $0 FILE_OR_DIR [remote]";
echo "FILE_OR_DIR: the file or directory you want to remove from history"
echo "if ''remote'' argument is set, it will also push to remote repository."
exit;
fi
FOLDERNAME_OR_FILENAME=$1;
#The important part starts here: ------------------------
git filter-branch -f --index-filter "git rm -rf --cached --ignore-unmatch $FOLDERNAME_OR_FILENAME" -- --all
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now
if [[ $2 == "remote" ]]; then
git push --all --force
fi
echo "Done."
Todos los créditos van a Annopjohn
y a leontalbot
por señalarlo.
NOTA
Tenga en cuenta que el script no incluye validaciones, así que asegúrese de no cometer errores y de que tiene una copia de seguridad en caso de que algo salga mal. Funcionó para mí, pero puede que no funcione en tu situación. Úselo con precaución (siga el enlace si desea saber qué está pasando).
La reescritura del historial de Git exige cambiar todos los ID de confirmación afectados, por lo que todos los que estén trabajando en el proyecto deberán eliminar sus copias antiguas del repositorio y hacer un nuevo clon después de que haya limpiado el historial. Mientras más inconvenientes tenga para la gente, más necesitará una buena razón para hacerlo: su archivo superfluo realmente no está causando un problema, pero si solo está trabajando en el proyecto, también puede limpiar el historial de Git si lo desea ¡a!
Para hacerlo lo más fácil posible, recomiendo usar BFG Repo-Cleaner , una alternativa más simple y rápida a git-filter-branch
específicamente diseñada para eliminar archivos del historial de Git. Una forma en la que hace su vida más fácil aquí es que en realidad maneja todas las referencias de forma predeterminada (todas las etiquetas, sucursales, etc.) pero también es 10 - 50 veces más rápido.
Debe seguir cuidadosamente los pasos aquí: http://rtyley.github.com/bfg-repo-cleaner/#usage - pero el bit del núcleo es simplemente esto: descargue el archivo BFG jar (requiere Java 6 o superior) y ejecute este comando :
$ java -jar bfg.jar --delete-files filename.orig my-repo.git
Se escaneará todo el historial del repositorio y se eliminará cualquier archivo llamado filename.orig
(que no se encuentra en su última confirmación ). ¡Esto es considerablemente más fácil que usar git-filter-branch
para hacer lo mismo!
Revelación completa: soy el autor de BFG Repo-Cleaner.
Si es la última confirmación que desea limpiar, probé con la versión 2.14.3 de git (Apple Git-98):
touch empty
git init
git add empty
git commit -m init
# 92K .git
du -hs .git
dd if=/dev/random of=./random bs=1m count=5
git add random
git commit -m mistake
# 5.1M .git
du -hs .git
git reset --hard HEAD^
git reflog expire --expire=now --all
git gc --prune=now
# 92K .git
du -hs .git
Si no ha cometido nada desde entonces, simplemente git rm
el archivo y git commit --amend
.
Si usted tiene
git filter-branch /
--index-filter ''git rm --cached --ignore-unmatch path/to/file/filename.orig'' merge-point..HEAD
Pasará por cada cambio de merge-point
de merge-point
a HEAD
, eliminará el nombre de archivo.orig y reescribirá el cambio. El uso de --ignore-unmatch
significa que el comando no fallará si, por alguna razón, filename.orig falta en un cambio. Esa es la forma recomendada de la sección de ejemplos en la página de manual de git-filter-branch .
Nota para los usuarios de Windows: la ruta del archivo debe usar barras diagonales
Solo para agregar eso a la solución de Charles Bailey, acabo de usar git rebase -i para eliminar archivos no deseados de una confirmación anterior y funcionó a la perfección. Los pasos:
# Pick your commit with ''e''
$ git rebase -i
# Perform as many removes as necessary
$ git rm project/code/file.txt
# amend the commit
$ git commit --amend
# continue with rebase
$ git rebase --continue
También puedes usar:
git reset HEAD file/path
Por favor, no use esta receta si su situación no es la descrita en la pregunta. Esta receta es para corregir una fusión incorrecta y para reproducir sus buenos compromisos en una combinación fija.
Aunque filter-branch
hará lo que quieras, es un comando bastante complejo y probablemente elegiría hacer esto con git rebase
. Probablemente sea una preferencia personal. filter-branch
puede hacerlo en un solo comando un poco más complejo, mientras que la solución de rebase
está realizando las operaciones lógicas equivalentes un paso a la vez.
Prueba la siguiente receta:
# create and check out a temporary branch at the location of the bad merge
git checkout -b tmpfix <sha1-of-merge>
# remove the incorrectly added file
git rm somefile.orig
# commit the amended merge
git commit --amend
# go back to the master branch
git checkout master
# replant the master branch onto the corrected merge
git rebase tmpfix
# delete the temporary branch
git branch -d tmpfix
(Tenga en cuenta que en realidad no necesita una sucursal temporal, puede hacer esto con un ''HEAD extraído'', pero debe tomar nota del ID de confirmación generado por el paso git commit --amend
para suministrar al git rebase
comando en lugar de usar el nombre de rama temporal.)
You should probably clone your repository first.
Remove your file from all branches history:
git filter-branch --tree-filter ''rm -f filename.orig'' -- --all
Remove your file just from the current branch:
git filter-branch --tree-filter ''rm -f filename.orig'' -- --HEAD
Lastly you should run to remove empty commits:
git filter-branch -f --prune-empty -- --all