¿Hacer que la confirmación actual sea la única confirmación (inicial) en un repositorio Git?


666

Actualmente tengo un repositorio local de Git, que envío a un repositorio de Github.

El repositorio local tiene ~ 10 confirmaciones, y el repositorio de Github es un duplicado sincronizado de esto.

Lo que me gustaría hacer es eliminar TODO el historial de versiones del repositorio Git local, para que el contenido actual del repositorio aparezca como el único compromiso (y, por lo tanto, las versiones anteriores de los archivos dentro del repositorio no se almacenan).

Entonces me gustaría enviar estos cambios a Github.

He investigado Git rebase, pero esto parece ser más adecuado para eliminar versiones específicas. Otra posible solución es eliminar el repositorio local y crear uno nuevo, ¡aunque esto probablemente generaría mucho trabajo!

ETA: Hay directorios / archivos específicos que no se rastrean; si es posible, me gustaría mantener el seguimiento de estos archivos.


66
Consulte también stackoverflow.com/questions/435646/… ("¿Cómo combino las dos primeras confirmaciones de un repositorio Git?")
Anonymoose


Respuestas:


983

Aquí está el enfoque de fuerza bruta. También elimina la configuración del repositorio.

Nota : ¡Esto NO funciona si el repositorio tiene submódulos! Si está usando submódulos, debe usar, por ejemplo, rebase interactivo

Paso 1: elimine todo el historial ( asegúrese de tener una copia de seguridad, no se puede revertir )

cat .git/config  # note <github-uri>
rm -rf .git

Paso 2: reconstruya el repositorio de Git solo con el contenido actual

git init
git add .
git commit -m "Initial commit"

Paso 3: empuja a GitHub.

git remote add origin <github-uri>
git push -u --force origin master

3
Gracias, larsmans: he optado por usar esto como mi solución. Aunque la inicialización del repositorio de Git pierde el registro de los archivos no rastreados en el antiguo repositorio, esta es probablemente una solución más simple para mi problema.
kaese

55
@kaese: Creo que .gitignoredeberías manejar eso, ¿verdad?
Fred Foo

48
Guarde su .git / config antes y restaúrelo después.
lalebarde

@lalebarde Si restaura .git / config después de git commit -m "Initial commit"eso, probablemente pueda omitir la git remote add ...parte, suponiendo que ya estaba en su configuración, y pasar directamente a presionar. A mí me funcionó.
Buttle Butkus

24
Tenga cuidado con esto si está tratando de eliminar datos confidenciales: la presencia de una sola confirmación en la rama maestra recién introducida es engañosa: el historial seguirá existiendo , simplemente no será accesible desde esa rama. Si tiene etiquetas, por ejemplo, que apuntan a confirmaciones anteriores, estas confirmaciones serán accesibles. De hecho, para cualquier persona con un poco de git foo, estoy seguro de que después de este empuje de git, aún podrán recuperar todo el historial del repositorio de GitHub, y si tiene otras ramas o etiquetas, entonces no incluso necesita mucho git foo.
Robert Muil

621

La única solución que funciona para mí (y mantiene funcionando los submódulos) es

git checkout --orphan newBranch
git add -A  # Add all files and commit them
git commit
git branch -D master  # Deletes the master branch
git branch -m master  # Rename the current branch to master
git push -f origin master  # Force push master branch to github
git gc --aggressive --prune=all     # remove the old files

Eliminar .git/siempre causa grandes problemas cuando tengo submódulos. Usar de git rebase --rootalguna manera me causaría conflictos (y llevaría mucho tiempo desde que tenía mucha historia).


55
¡Esta debería ser la respuesta correcta! ¡solo agrega un git push -f origin mastercomo la última operación y el sol brillará nuevamente en tu nuevo repositorio! :)
gru

2
¿Esto no mantiene viejos compromisos alrededor?
Brad

44
@JonePolvora git fetch; git reset: origen duro / stackoverflow
echo

55
después de hacer esto, ¿el repositorio liberará espacio?
Inuart

8
Creo que debería agregar la sugerencia de @JasonGoemaat como la última línea de su respuesta. Sin git gc --aggressive --prune alltodo el punto de perder la historia se echaría de menos.
Tuncay Göncüoğlu

93

Este es mi enfoque favorito:

git branch new_branch_name $(echo "commit message" | git commit-tree HEAD^{tree})

Esto creará una nueva rama con una confirmación que agrega todo en HEAD. No altera nada más, por lo que es completamente seguro.


3
El mejor enfoque! Claro, y haz el trabajo. Además, cambio el nombre de la rama con muchos cambios de "maestro" a "trabajo local" y "nombre_branch_nuevo" a "maestro". En master, haga lo siguiente: git -m local-changes git branch -m local-changes git checkout new_branch_name git branch -m master <
Valtoni Boaventura

Esto se ve muy corto y elegante, lo único que no entiendo o que aún no he visto es HEAD ^ {tree}, ¿alguien podría explicarlo? Aparte de eso, leí esto como "crear una nueva rama a partir de un compromiso dado, creado al crear un nuevo objeto de compromiso con un mensaje de compromiso dado de ___"
TomKeegasi

3
El lugar definitivo para buscar respuestas a preguntas sobre la sintaxis de referencia de git es en los git-rev-parsedocumentos. Lo que sucede aquí git-commit-treerequiere una referencia a un árbol (una instantánea del repositorio), pero HEADes una revisión. Para encontrar el árbol asociado con un commit usamos el <rev>^{<type>}formulario.
dan_waterworth

Buena respuesta. Funciona bien. Finalmente digagit push --force <remote> new_branch_name:<remote-branch>
Felipe Alvarez

31

La otra opción, que podría resultar mucho trabajo si tienes muchos commits, es un rebase interactivo (suponiendo que tu versión de git sea> = 1.7.12):git rebase --root -i

Cuando se le presenta una lista de confirmaciones en su editor:

  • Cambie "pick" a "reword" para la primera confirmación
  • Cambie "pick" a "fixup" cada otro commit

Guardar y cerrar. Git comenzará a rebasar.

Al final, tendría una nueva confirmación de raíz que es una combinación de todas las que vinieron después.

La ventaja es que no tiene que eliminar su repositorio y si tiene dudas, siempre tiene un respaldo.

Si realmente desea destruir su historial, restablezca el maestro a esta confirmación y elimine todas las demás ramas.


Después de completar el rebase, no puedo presionar:error: failed to push some refs to
Begueradj

@Begueradj si ya presionó la rama que rebasó, entonces deberá forzar el empuje git push --force-with-lease. force-with-lease se usa porque es menos destructivo que --force.
Carl

19

Variante del método propuesto por larsmans :

Guarde su lista de archivos sin seguimiento:

git ls-files --others --exclude-standard > /tmp/my_untracked_files

Guarde su configuración de git:

mv .git/config /tmp/

Luego realice los primeros pasos de larsmans:

rm -rf .git
git init
git add .

Restaura tu configuración:

mv /tmp/config .git/

Destraza tus archivos sin seguimiento:

cat /tmp/my_untracked_files | xargs -0 git rm --cached

Entonces cometer:

git commit -m "Initial commit"

Y finalmente empuje a su repositorio:

git push -u --force origin master

6

A continuación se muestra un script adaptado de la respuesta de @Zeelot. Debe eliminar el historial de todas las ramas, no solo la rama maestra:

for BR in $(git branch); do   
  git checkout $BR
  git checkout --orphan ${BR}_temp
  git commit -m "Initial commit"
  git branch -D $BR
  git branch -m $BR
done;
git gc --aggressive --prune=all

Funcionó para mis propósitos (no estoy usando submódulos).


44
Creo que olvidó forzar a push master para completar el procedimiento.
not2qubit

2
Tuve que hacer una ligera modificación. git branchincluirá un asterisco al lado de su sucursal desprotegida, que luego se bloqueará, lo que hará que se resuelva en todos los archivos o carpetas como si también fueran nombres de sucursal. En cambio, usé lo git branch --format="%(refname:lstrip=2)"que me dio solo los nombres de las ramas.
Ben Richards

@ not2qubit: Gracias por esto. ¿Cuál sería el comando exacto? git push --force origin mastero git push --force-with-lease? Aparentemente, este último es más seguro (ver stackoverflow.com/questions/5509543/… )
Shafique Jamal

@BenRichards. Interesante. Intentaré esto nuevamente en algún momento con una carpeta que coincida con el nombre de una rama para probarlo, luego actualizaré la respuesta. Gracias.
Shafique Jamal


4

git filter-branch es la herramienta de cirugía mayor.

git filter-branch --parent-filter true -- @^!

--parent-filterpone a los padres en stdin y debe imprimir los padres reescritos en stdout; Unix truesale con éxito y no imprime nada, así que no hay padres. @^!es la abreviatura de Git para "el responsable principal pero ninguno de sus padres". Luego borre todas las otras referencias y empuje a su gusto.


3

Simplemente elimine el repositorio de Github y cree uno nuevo. Con mucho, el enfoque más rápido, fácil y seguro. Después de todo, ¿qué tiene que ganar al ejecutar todos esos comandos en la solución aceptada cuando todo lo que desea es la rama maestra con una sola confirmación?


1
Uno de los puntos principales es poder ver de dónde se bifurcó.
not2qubit

Acabo de hacer esto y está bien
gracias. A

2

El siguiente método es exactamente reproducible, por lo que no es necesario ejecutar clon nuevamente si ambos lados son consistentes, solo ejecute el script en el otro lado también.

git log -n1 --format=%H >.git/info/grafts
git filter-branch -f
rm .git/info/grafts

Si luego quieres limpiarlo, prueba este script:

http://sam.nipl.net/b/git-gc-all-ferocious

Escribí un script que "mata la historia" para cada rama en el repositorio:

http://sam.nipl.net/b/git-kill-history

ver también: http://sam.nipl.net/b/confirm


1
Gracias por esto. Solo para su información: su secuencia de comandos para eliminar el historial de cada rama podría necesitar alguna actualización; proporciona los siguientes errores: git-hash: not foundySupport for <GIT_DIR>/info/grafts is deprecated
Shafique Jamal

1
@ShafiqueJamal, gracias, el pequeño script "git-hash" es git log HEAD~${1:-0} -n1 --format=%H, aquí, sam.aiki.info/b/git-hash Sería mejor ponerlo todo en un script para consumo público. Si alguna vez lo uso nuevamente, podría descubrir cómo hacerlo con la nueva función que reemplaza a los "injertos".
Sam Watkins

2

Lo que me gustaría hacer es eliminar TODO el historial de versiones del repositorio Git local, para que el contenido actual del repositorio aparezca como el único compromiso (y, por lo tanto, las versiones anteriores de los archivos dentro del repositorio no se almacenan).

Una respuesta más conceptual:

git basura automáticamente recopila confirmaciones antiguas si no hay etiquetas / ramas / referencias que las señalen. Entonces, simplemente tiene que eliminar todas las etiquetas / ramas y crear una nueva confirmación huérfana, asociada con cualquier rama; por convención, dejaría que la rama masterapunte a esa confirmación.

Los viejos commits inalcanzables nunca serán vistos por nadie a menos que vayan a cavar con comandos git de bajo nivel. Si eso es suficiente para usted, simplemente me detendría allí y dejaría que el GC automático haga su trabajo cuando lo desee. Si desea deshacerse de ellos de inmediato, puede usarlos git gc(posiblemente con --aggressive --prune=all). Sin embargo, para el repositorio de git remoto, no hay forma de forzarlo, a menos que tenga acceso de shell a su sistema de archivos.


Buena adición, cuando se ve en el contexto de la respuesta de @Zeelot.
Mogens TrasherDK

Sí, Zeelot tiene los comandos que básicamente hacen esto (de manera diferente, comenzando de nuevo, lo que podría estar bien para OP). @MogensTrasherDK
AnoE

0

Aqui tienes:

#!/bin/bash
#
# By Zibri (2019)
#
# Usage: gitclean username password giturl
#
gitclean () 
{ 
    odir=$PWD;
    if [ "$#" -ne 3 ]; then
        echo "Usage: gitclean username password giturl";
        return 1;
    fi;
    temp=$(mktemp -d 2>/dev/null /dev/shm/git.XXX || mktemp -d 2>/dev/null /tmp/git.XXX);
    cd "$temp";
    url=$(echo "$3" |sed -e "s/[^/]*\/\/\([^@]*@\)\?\.*/\1/");
    git clone "https://$1:$2@$url" && { 
        cd *;
        for BR in "$(git branch|tr " " "\n"|grep -v '*')";
        do
            echo working on branch $BR;
            git checkout $BR;
            git checkout --orphan $(basename "$temp"|tr -d .);
            git add -A;
            git commit -m "Initial Commit" && { 
                git branch -D $BR;
                git branch -m $BR;
                git push -f origin $BR;
                git gc --aggressive --prune=all
            };
        done
    };
    cd $odir;
    rm -rf "$temp"
}

También alojado aquí: https://gist.github.com/Zibri/76614988478a076bbe105545a16ee743


Gah! ¡No me obligue a proporcionar mi contraseña oculta y sin protección en la línea de comando! Además, la salida de git branch generalmente no es adecuada para scripts. Es posible que desee ver las herramientas de fontanería.
D. Ben Knoble

-1

Resolví un problema similar simplemente borrando la .gitcarpeta de mi proyecto y reintegrando con el control de versiones a través de IntelliJ. Nota: la .gitcarpeta está oculta. Puede verlo en el terminal con ls -a, y luego eliminarlo usando rm -rf .git.


eso es lo que está haciendo en el paso 1: rm -rf .git?
noches

-1

Para eso, use el comando Shallow Clone git clone --depth 1 URL - Clonará solo el HEAD actual del repositorio


-2

Para eliminar la última confirmación de git, simplemente puede ejecutar

git reset --hard HEAD^ 

Si está eliminando varias confirmaciones desde la parte superior, puede ejecutar

git reset --hard HEAD~2 

para eliminar las dos últimas confirmaciones. Puede aumentar el número para eliminar aún más confirmaciones.

Más información aquí.

Git tutoturial aquí proporciona ayuda sobre cómo purgar el repositorio:

desea eliminar el archivo del historial y agregarlo al .gitignore para asegurarse de que no se vuelva a confirmar accidentalmente. Para nuestros ejemplos, vamos a eliminar Rakefile del repositorio de gemas GitHub.

git clone https://github.com/defunkt/github-gem.git

cd github-gem

git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch Rakefile' \
  --prune-empty --tag-name-filter cat -- --all

Ahora que hemos borrado el archivo del historial, asegurémonos de no volver a comprometerlo accidentalmente.

echo "Rakefile" >> .gitignore

git add .gitignore

git commit -m "Add Rakefile to .gitignore"

Si está satisfecho con el estado del repositorio, debe forzar los cambios para sobrescribir el repositorio remoto.

git push origin master --force

66
Eliminar archivos o confirmaciones del repositorio no tiene absolutamente ninguna relación con la pregunta (que pide eliminar el historial, algo completamente diferente). El OP quiere un historial limpio pero quiere preservar el estado actual del repositorio.
Victor Schröder

Esto no produce el resultado formulado en la pregunta. está descartando todos los cambios después de la confirmación que mantiene por última vez y perdiendo todos los cambios desde entonces, pero la pregunta solicita mantener los archivos actuales y eliminar el historial.
Tuncay Göncüoğlu
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.