¿Cómo elimino el historial antiguo de un repositorio git?

209

Me temo que no pude encontrar nada como este escenario en particular.

Tengo un repositorio git con mucha historia: más de 500 sucursales, más de 500 etiquetas, desde mediados de 2007. Contiene ~ 19,500 confirmaciones. Nos gustaría eliminar todo el historial antes del 1 de enero de 2010, para que sea más pequeño y más fácil de manejar (mantendríamos una copia completa del historial en un repositorio de archivos).

Conozco el commit que quiero que se haya convertido en la raíz del nuevo repositorio. Sin embargo, no puedo encontrar el git mojo correcto para truncar el repositorio para comenzar con ese commit. Estoy adivinando alguna variante de

git filter-branch

implicaría injertos sería necesario; También podría ser necesario tratar cada una de las más de 200 ramas que queremos mantener por separado y luego volver a unir el repositorio (algo que sé hacer).

¿Alguien ha hecho algo como esto? Tengo git 1.7.2.3 si eso importa.

ebneter
fuente

Respuestas:

118

Simplemente cree un injerto del elemento primario de su nueva confirmación raíz a ninguna referencia primaria (o a una confirmación vacía, por ejemplo, la confirmación raíz real de su repositorio). P.ejecho "<NEW-ROOT-SHA1>" > .git/info/grafts

Después de crear el injerto, surte efecto de inmediato; deberías poder mirar git logy ver que los viejos commits no deseados han desaparecido:

$ echo 4a46bc886318679d8b15e05aea40b83ff6c3bd47 > .git/info/grafts
$ git log --decorate | tail --lines=11
commit cb3da2d4d8c3378919844b29e815bfd5fdc0210c
Author: Your Name <[email protected]>
Date:   Fri May 24 14:04:10 2013 +0200

    Another message

commit 4a46bc886318679d8b15e05aea40b83ff6c3bd47 (grafted)
Author: Your Name <[email protected]>
Date:   Thu May 23 22:27:48 2013 +0200

    Some message

Si todo se ve según lo previsto, puede hacer un simple git filter-branch -- --allpara que sea permanente.

CUIDADO: después de hacer el paso de rama de filtro , todos los ID de confirmación habrán cambiado, por lo que cualquiera que use el repositorio anterior nunca debe fusionarse con nadie que use el repositorio nuevo.

Apenwarr
fuente
66
Tuve que hacer git filter-branch --tag-name-filter cat -- --allpara actualizar las etiquetas. Pero también tengo etiquetas más antiguas que apuntan al historial antiguo que quiero eliminar. ¿Cómo puedo deshacerme de todas esas etiquetas viejas? Si no los elimino, el historial anterior no desaparece y aún puedo verlo gitk --all.
Craig McQueen
9
"Simplemente cree un injerto del elemento primario de su nueva confirmación de raíz para ningún elemento primario" necesita algo de elaboración. Intenté eso y no pude averiguar la sintaxis de "no parent". La página del manual afirma que se requiere una ID de confirmación principal; usar todos los ceros solo me da un error.
Marius Gedminas
66
En caso de que alguien más se preguntara cómo funciona exactamente, es bastante fácil:echo "<NEW-ROOT-HASH>" > .git/info/grafts
friederbluemle
3
Estoy de acuerdo, explicar qué es un injerto sería más que útil
Charles Martin
44
Citado de la página wiki vinculada sobre injertos. "A partir de Git 1.6.5, se ha agregado el reemplazo de git más flexible, que le permite reemplazar cualquier objeto con cualquier otro objeto, y rastrea las asociaciones a través de referencias que se pueden empujar y arrastrar entre repositorios". Entonces, esta respuesta podría estar desactualizada para las versiones actuales de git.
ThorSummoner
130

Tal vez sea demasiado tarde para publicar una respuesta, pero como esta página es el primer resultado de Google, aún puede ser útil.

Si desea liberar algo de espacio en su repositorio de git, pero no desea reconstruir todos sus commits (rebase o injerto), y aún así puede empujar / tirar / fusionarse de las personas que tienen el repositorio completo, puede usar el git clone clon superficial ( - parámetro de profundidad ).

; Clone the original repo into limitedRepo
git clone file:///path_to/originalRepo limitedRepo --depth=10

; Remove the original repo, to free up some space
rm -rf originalRepo
cd limitedRepo
git remote rm origin

Es posible que pueda reducir su repositorio existente siguiendo estos pasos:

; Shallow to last 5 commits
git rev-parse HEAD~5 > .git/shallow

; Manually remove all other branches, tags and remotes that refers to old commits

; Prune unreachable objects
git fsck --unreachable ; Will show you the list of what will be deleted
git gc --prune=now     ; Will actually delete your data

¿Cómo eliminar todas las etiquetas locales de git?

Ps: las versiones anteriores de git no admitían clonar / empujar / tirar de / a repositorios poco profundos.

Alexandre T.
fuente
9
+1 Esta es la respuesta correcta para las nuevas versiones de Git. (¡Ah, y vuelve a PPCG !)
wizzwizz4
66
¿Cómo puedes acceder cda una carpeta que acaba de ser eliminada? Siento que falta información aquí. Además, ¿hay alguna forma de aplicar estos cambios al repositorio remoto?
Trogdor
44
@Jez Esa sería la otra respuesta más votada. Esta respuesta no es para ti si quieres deshacerte permanentemente de la historia. Es para trabajar con grandes historias.
Nadie el
44
Para responder a mi propia pregunta: ¡ git clone file:///Users/me/Projects/myProject myClonedProject --shallow-since=2016-09-02Funciona como un encanto!
Micros
55
@Jez puedes convertir tu repositorio superficial en uno normal ejecutando git filter-branch -- --all. Esto cambiará todos los hashes, pero después de eso podrás llevarlo a un nuevo repositorio
Ed'ka
61

Este método es fácil de entender y funciona bien. El argumento de la secuencia de comandos ( $1) es una referencia (etiqueta, hash, ...) a la confirmación a partir de la cual desea mantener su historial.

#!/bin/bash
git checkout --orphan temp $1 # create a new branch without parent history
git commit -m "Truncated history" # create a first commit on this branch
git rebase --onto temp $1 master # now rebase the part of master branch that we want to keep onto this branch
git branch -D temp # delete the temp branch

# The following 2 commands are optional - they keep your git repo in good shape.
git prune --progress # delete all the objects w/o references
git gc --aggressive # aggressively collect garbage; may take a lot of time on large repos

Tenga en cuenta que las etiquetas antiguas seguirán presentes; por lo que es posible que deba eliminarlos manualmente

Observación: Sé que esto es casi lo mismo que @yoyodin, pero aquí hay algunos comandos e información adicionales importantes. Traté de editar la respuesta, pero como es un cambio sustancial a la respuesta de @yoyodin, mi edición fue rechazada, ¡así que aquí está la información!

Chris Maes
fuente
Agradezco las explicaciones dadas para los comandos git pruney git gc. ¿Hay alguna explicación para el resto de los comandos en el script? Tal como está, no está claro qué argumentos se le están pasando y qué está haciendo cada comando. Gracias.
user5359531
2
@ user5359531 gracias por tu comentario, agregué algunos comentarios más para cada comando. Espero que esto ayude.
Chris Maes
44
Combinar conflictos en todo el lugar ... no es muy útil
Warpzit
3
@Warpzit Me libré de los conflictos de fusión agregando -pal rebasecomando, como se sugiere en otra respuesta
leonbloy
1
Seguí esto exactamente, y todo lo que obtuve fue la misma historia que antes con una nueva rama que comenzaba en el commit que quería podar con toda la misma historia que antes. No se eliminó el historial.
DrStrangepork
51

Pruebe este método Cómo truncar el historial de git :

#!/bin/bash
git checkout --orphan temp $1
git commit -m "Truncated history"
git rebase --onto temp $1 master
git branch -D temp

Aquí $1es SHA-1 de la confirmación desea mantener y el script creará nueva rama que contiene todos los envíos entre $1y mastery toda la historia más antigua se descarta. Tenga en cuenta que este script simple asume que no tiene una rama existente llamada temp. También tenga en cuenta que este script no borra los datos de git para el historial antiguo. Corre git gc --prune=all && git repack -a -f -F -ddespués de que hayas verificado que realmente quieres perder todo el historial. Es posible que también necesite, rebase --preserve-mergespero tenga en cuenta que la implementación de git de esa característica no es perfecta. Inspeccione los resultados manualmente si lo usa.

yoyodyn
fuente
22
Intenté esto, pero obtuve conflictos de fusión en el rebasepaso. Extraño: no esperaba que los conflictos de fusión pudieran ser posibles en estas circunstancias.
Craig McQueen
2
Úselo git commit --allow-empty -m "Truncate history"si la confirmación que desprotegió no contiene ningún archivo.
Friederbluemle
2
¿Cómo devuelvo esto al maestro remoto? Cuando hago eso, termino con la historia antigua y la nueva.
rustyx
1
¿Qué se supone que es 'temp'? ¿Qué se supone que debes pasar como argumento para esto? ¿Hay un ejemplo de cómo se supone que se ven estos comandos cuando realmente los ejecutas? Gracias.
user5359531
1
Creo que $ 1 es el hash de compromiso. (Se proporcionan más detalles en el artículo vinculado).
Chris Nolet
34

Como alternativa a la reescritura del historial, considere usar git replacecomo en este artículo del libro Pro Git . El ejemplo discutido implica reemplazar un compromiso principal para simular el comienzo de un árbol, mientras se mantiene el historial completo como una rama separada para su custodia.

Jeff Bowman
fuente
Sí, creo que probablemente podría hacer lo que quisiéramos con eso, si también eliminara la rama de historial completo por separado. (Estábamos tratando de reducir el repositorio)
Ebneter
1
Me desanimó la respuesta al estar fuera del sitio; pero sí enlaza con el sitio de GitScm y el tutorial al que enlaza está muy bien escrito y parece directamente al punto de la pregunta del OP.
ThorSummoner
@ThorSummoner ¡Lo siento! Desarrollaré la respuesta un poco más en el sitio
Jeff Bowman
Lamentablemente, esta no es una alternativa a la reescritura del historial. Hay una frase confusa al comienzo del artículo que probablemente dio esta impresión. ¿Podría eso ser eliminado de esta respuesta? Verá en el artículo que el autor reescribe la historia de la rama truncada, pero propone una forma de volver a unir la rama "historia" heredada utilizando git replace. Creo que esto se corrigió en otra pregunta donde publicaste esta respuesta.
Mitch
1
Se realiza una discusión sobre git replaceversus git graften stackoverflow.com/q/6800692/873282
koppor
25

Si desea mantener el repositorio ascendente con un historial completo , pero con pagos locales más pequeños, haga un clon superficial congit clone --depth=1 [repo] .

Después de presionar un commit, puedes hacer

  1. git fetch --depth=1para podar los viejos commits. Esto hace que los viejos commits y sus objetos sean inalcanzables.
  2. git reflog expire --expire-unreachable=now --all. Para caducar todos los commits antiguos y sus objetos
  3. git gc --aggressive --prune=all para eliminar los objetos viejos

Consulte también ¿Cómo eliminar el historial local de git después de una confirmación?.

Tenga en cuenta que no puede empujar este repositorio "superficial" a otro lugar: "actualización superficial no permitida". Consulte Remoto rechazado (actualización superficial no permitida) después de cambiar la URL remota de Git . Si quieres hacerlo, debes seguir con el injerto.

koppor
fuente
1
El punto número 1. marcó la diferencia para mí. Saludos
clapas
21

Necesitaba leer varias respuestas y otra información para entender lo que estaba haciendo.

1. Ignora todo lo anterior que cierta confirmación

El archivo .git/info/graftspuede definir padres falsos para un commit. Una línea con solo una identificación de confirmación dice que la confirmación no tiene un elemento primario. Si quisiéramos decir que solo nos importan los últimos 2000 commits, podemos escribir:

git rev-parse HEAD~2000 > .git/info/grafts

git rev-parse nos da la identificación del commit del padre número 2000 del commit actual. El comando anterior sobrescribirá el archivo de injertos si está presente. Comprueba si está allí primero.

2. Reescribe el historial de Git (opcional)

Si desea hacer que este padre falso injertado sea real, ejecute:

git filter-branch -- --all

Cambiará todos los ID de confirmación. Cada copia de este repositorio debe actualizarse con fuerza.

3. Limpie el espacio en disco

No hice el paso 2, porque quería que mi copia siguiera siendo compatible con el flujo ascendente. Solo quería ahorrar algo de espacio en disco. Para olvidar todas las confirmaciones anteriores:

git prune
git gc

Alternativa: copias poco profundas

Si tiene una copia superficial de otro repositorio y solo quiere ahorrar algo de espacio en disco, puede actualizar .git/shallow. Pero tenga cuidado de que nada apunta a una confirmación de antes. Entonces podrías ejecutar algo como esto:

git fetch --prune
git rev-parse HEAD~2000 > .git/shallow
git prune
git gc

La entrada en poca profundidad funciona como un injerto. Pero tenga cuidado de no usar injertos y poco profundos al mismo tiempo. Al menos, no tiene las mismas entradas allí, fallará.

Si todavía tiene algunas referencias antiguas (etiquetas, ramas, cabezales remotos) que apuntan a confirmaciones anteriores, no se limpiarán y no ahorrará más espacio en disco.

Maikel
fuente
La compatibilidad con <GIT_DIR> / info / grafts está en desuso y se eliminará en una futura versión de Git.
danny
Por favor considere usar en su git replacelugar. Ver stackoverflow.com/questions/6800692/…
Joel AZEMAR
3

Cuando se cambia la base o se empuja al encabezado / maestro, puede ocurrir este error

remote: GitLab: You are not allowed to access some of the refs!
To git@giturl:main/xyz.git
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'git@giturl:main/xyz.git'

Para resolver este problema en el panel de git, debe eliminar la rama maestra de "Ramas protegidas"

ingrese la descripción de la imagen aquí

entonces puedes ejecutar este comando

git push -f origin master

o

git rebase --onto temp $1 master
HMagdy
fuente
0

Aquí hay demasiadas respuestas que no son actuales y algunas no explican completamente las consecuencias. Esto es lo que funcionó para mí para recortar el historial usando el último git 2.26:

Primero crea una confirmación ficticia. Esta confirmación aparecerá como la primera confirmación en su repositorio truncado. Necesita esto porque esta confirmación mantendrá todos los archivos base para el historial que está manteniendo. El SHA es el ID de la confirmación anterior de la confirmación que desea mantener (en este ejemplo, 8365366). La cadena 'Inicial' aparecerá como mensaje de confirmación de la primera confirmación. Si está utilizando Windows, escriba el siguiente comando desde el símbolo del sistema de Git Bash.

# 8365366 is id of parent commit after which you want to preserve history
echo 'Initial' | git commit-tree 8365366^{tree}

El comando anterior imprimirá SHA, por ejemplo, d10f7503bc1ec9d367da15b540887730db862023 .

Ahora solo escribe:

# d10f750 is commit ID from previous command
git rebase --onto d10f750 8365366

Esto primero colocará todos los archivos como confirmación 8365366en la confirmación ficticia d10f750. Luego reproducirá todas las confirmaciones después de 8365366 sobre la parte superior d10f750. Finalmentemaster , el puntero de rama se actualizará a la última confirmación reproducida.

Ahora, si desea impulsar estos repositorios truncados, simplemente haga git push -f .

Algunas cosas a tener en cuenta (esto se aplica tanto a otros métodos como a este): las etiquetas no se transfieren. Mientras se conservan las ID de confirmación y las marcas de tiempo, verá que GitHub muestra estas confirmaciones en un encabezado como Commits on XY date.

Afortunadamente, es posible mantener el historial truncado como "archivo" y luego puede volver a unir el repositorio recortado con el repositorio de archivo. Para hacer esto, vea esta guía .

Shital Shah
fuente
-3

puede eliminar el directorio, los archivos y también todo el historial relacionado con el directorio o el archivo usando el jar [descargarlo] y los comandos mencionados a continuación

Archivo bfg.jar: https://rtyley.github.io/bfg-repo-cleaner/

git clone --bare repo-url cd repo_dir java -jar bfg.jar --delete-folder folder_name git reflog expire --expire = now --all && git gc --prune = now --aggressive git push --mirror repo_url

RahulMohan Kolakandy
fuente
-10
  1. eliminar datos de git, rm .git
  2. git init
  3. agregar un control remoto git
  4. empuje forzado
Brad Reid
fuente
66
eso funcionará para eliminar TODO el historial, pero no por lo que pidió: mantener el historial desde enero de 2010
Chris Maes
1
Sólo quería decir gracias ya que me ayudó en mi escenario a pesar de que esto podría no ser la respuesta correcta a la pregunta
apnerve