Machen Sie das aktuelle Commit zum einzigen (anfänglichen) Commit in einem Git-Repository?

664

Ich habe derzeit ein lokales Git-Repository, das ich in ein Github-Repository pushe.

Das lokale Repository hat ~ 10 Commits, und das Github-Repository ist ein synchronisiertes Duplikat davon.

Ich möchte den gesamten Versionsverlauf aus dem lokalen Git-Repository entfernen, sodass der aktuelle Inhalt des Repositorys als einziges Commit angezeigt wird (und daher ältere Versionen von Dateien im Repository nicht gespeichert werden).

Ich möchte diese Änderungen dann gerne an Github weitergeben.

Ich habe Git Rebase untersucht, aber dies scheint besser geeignet zu sein, um bestimmte Versionen zu entfernen. Eine andere mögliche Lösung besteht darin, das lokale Repo zu löschen und ein neues zu erstellen - obwohl dies wahrscheinlich viel Arbeit verursachen würde!

ETA: Es gibt bestimmte Verzeichnisse / Dateien, die nicht verfolgt werden. Wenn möglich, möchte ich das Aufspüren dieser Dateien beibehalten.

Kaese
quelle
6
Siehe auch stackoverflow.com/questions/435646/… ("Wie kombiniere ich die ersten beiden Commits eines Git-Repositorys?")
Anonymoose

Antworten:

981

Hier ist der Brute-Force-Ansatz. Außerdem wird die Konfiguration des Repositorys entfernt.

Hinweis : Dies funktioniert NICHT, wenn das Repository Submodule hat! Wenn Sie Submodule verwenden, sollten Sie z. B. interaktive Rebase verwenden

Schritt 1: Entfernen Sie den gesamten Verlauf ( Stellen Sie sicher, dass Sie eine Sicherung haben, diese kann nicht zurückgesetzt werden )

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

Schritt 2: Rekonstruieren Sie das Git-Repo nur mit dem aktuellen Inhalt

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

Schritt 3: Drücken Sie auf GitHub.

git remote add origin <github-uri>
git push -u --force origin master
Fred Foo
quelle
3
Vielen Dank, Larsmans - ich habe mich dafür entschieden, dies als meine Lösung zu verwenden. Obwohl das Initialisieren des Git-Repos die Aufzeichnung nicht verfolgter Dateien im alten Repo verliert, ist dies wahrscheinlich eine einfachere Lösung für mein Problem.
Kaese
5
@kaese: Ich denke du .gitignoresolltest damit umgehen, oder?
Fred Foo
48
Speichern Sie Ihre .git / config vorher und stellen Sie sie danach wieder her.
Lalebarde
@lalebarde Wenn Sie git commit -m "Initial commit"danach .git / config wiederherstellen , können Sie den git remote add ...Teil wahrscheinlich überspringen , vorausgesetzt, er war bereits in Ihrer Konfiguration enthalten, und direkt mit dem Push fortfahren. Es hat bei mir funktioniert.
Buttle Butkus
24
Seien Sie vorsichtig, wenn Sie versuchen, vertrauliche Daten zu entfernen: Das Vorhandensein nur eines einzigen Commits im neu gepushen Hauptzweig ist irreführend - der Verlauf bleibt bestehen , auf den von diesem Zweig aus nur nicht zugegriffen werden kann. Wenn Sie beispielsweise Tags haben, die auf ältere Commits verweisen, können Sie auf diese Commits zugreifen. Tatsächlich bin ich mir sicher, dass jeder, der ein bisschen Git Foo hat, nach diesem Git Push immer noch den gesamten Verlauf aus dem GitHub-Repository wiederherstellen kann - und wenn Sie andere Zweige oder Tags haben, tun sie dies nicht brauche sogar viel git foo.
Robert Muil
620

Die einzige Lösung, die für mich funktioniert (und Submodule am Laufen hält), ist

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

Das Löschen .git/verursacht immer große Probleme, wenn ich Submodule habe. Das Verwenden git rebase --rootwürde irgendwie Konflikte für mich verursachen (und es würde lange dauern, seit ich viel Geschichte hatte).

Zeelot
quelle
54
das sollte die richtige Antwort sein! füge einfach ein hinzu, git push -f origin masterda der letzte Op und die Sonne wieder auf dein frisches Repo scheint! :)
gru
2
Hält dies nicht alte Commits aufrecht?
Brad
4
@JonePolvora git fetch; Git Reset - Hard Origin / Master Stackoverflow.com/questions/4785107/…
Echo
5
Wird das Repo danach Speicherplatz freigeben?
Inuart
8
Ich glaube, Sie sollten den Vorschlag von @JasonGoemaat als letzte Zeile zu Ihrer Antwort hinzufügen. Ohne git gc --aggressive --prune allden ganzen Sinn, die Geschichte zu verlieren, würde man sie verpassen.
Tuncay Göncüoğlu
93

Dies ist mein bevorzugter Ansatz:

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

Dadurch wird ein neuer Zweig mit einem Commit erstellt, der alles in HEAD hinzufügt. Es ändert nichts anderes, also ist es absolut sicher.

dan_waterworth
quelle
3
Bester Ansatz! Klar und mach die Arbeit. Zusätzlich benenne ich den Zweig mit vielen Änderungen von "master" in "local-work" und "new_branch_name" in "master" um. Gehen Sie im Master wie folgt vor: git -m lokale Änderungen git branch -m lokale Änderungen git checkout new_branch_name git branch -m master <
Valtoni Boaventura
Das sieht wirklich kurz und elegant aus. Das einzige, was ich nicht verstehe oder noch nicht gesehen habe, ist HEAD ^ {tree}, könnte jemand das erklären? Abgesehen davon würde ich dies als "Erstellen eines neuen Zweigs aus einem bestimmten Commit, erstellt durch Erstellen eines neuen Commit-Objekts mit einer bestimmten Commit-Nachricht von ___"
lesen
3
Der endgültige Ort, um nach Antworten auf Fragen zur Git-Referenzsyntax zu suchen, befindet sich in den git-rev-parseDokumenten. Was hier passiert, git-commit-treeerfordert einen Verweis auf einen Baum (eine Momentaufnahme des Repos), ist aber HEADeine Überarbeitung. Um den mit einem Commit verknüpften Baum zu finden, verwenden wir das <rev>^{<type>}Formular.
dan_waterworth
Gute Antwort. Funktioniert gut. Sagen Sie schließlichgit push --force <remote> new_branch_name:<remote-branch>
Felipe Alvarez
31

Die andere Option, die sich bei vielen Commits als viel Arbeit herausstellen könnte, ist eine interaktive Rebase (vorausgesetzt, Ihre Git-Version ist> = 1.7.12):git rebase --root -i

Wenn in Ihrem Editor eine Liste mit Commits angezeigt wird:

  • Ändern Sie "pick" für das erste Commit in "reword"
  • Ändern Sie "pick" in "fixup" für jedes zweite Commit

Speichern und schließen. Git wird neu basiert.

Am Ende hätten Sie ein neues Root-Commit, das eine Kombination aller nachfolgenden Commits ist.

Der Vorteil ist, dass Sie Ihr Repository nicht löschen müssen und wenn Sie Bedenken haben, haben Sie immer einen Fallback.

Wenn Sie Ihren Verlauf wirklich nuklearisieren möchten, setzen Sie den Master auf dieses Commit zurück und löschen Sie alle anderen Zweige.

Carl
quelle
Nachdem die Rebase abgeschlossen ist, kann ich nicht pushen:error: failed to push some refs to
Begueradj
@Begueradj Wenn Sie den Zweig, den Sie neu basiert haben, bereits verschoben haben, müssen Sie Push erzwingen git push --force-with-lease. Force-with-Lease wird verwendet, weil es weniger destruktiv ist als --force.
Carl
19

Variante der von Larsmans vorgeschlagenen Methode:

Speichern Sie Ihre Liste der nicht verfolgten Dateien:

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

Speichern Sie Ihre Git-Konfiguration:

mv .git/config /tmp/

Führen Sie dann die ersten Schritte von larsmans aus:

rm -rf .git
git init
git add .

Stellen Sie Ihre Konfiguration wieder her:

mv /tmp/config .git/

Entfernen Sie nicht verfolgte Dateien:

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

Dann verpflichten Sie sich:

git commit -m "Initial commit"

Und schließlich in Ihr Repository verschieben:

git push -u --force origin master
lalebarde
quelle
6

Unten finden Sie ein Skript, das aus der Antwort von @Zeelot übernommen wurde. Es sollte den Verlauf aus allen Zweigen entfernen, nicht nur aus dem Hauptzweig:

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

Es hat für meine Zwecke funktioniert (ich benutze keine Submodule).

Shafique Jamal
quelle
4
Ich denke, Sie haben vergessen, den Push-Master zu zwingen, den Vorgang abzuschließen.
not2qubit
2
Ich musste eine kleine Änderung vornehmen. git branchwird neben Ihrem ausgecheckten Zweig ein Sternchen einfügen, das dann mit einem Globus versehen wird, sodass es in alle Dateien oder Ordner aufgelöst wird, als wären dies auch Zweignamen. Stattdessen habe ich verwendet, git branch --format="%(refname:lstrip=2)"was mir nur die Filialnamen gab.
Ben Richards
@ not2qubit: Danke dafür. Was wäre der genaue Befehl? git push --force origin masteroder git push --force-with-lease? Anscheinend ist letzteres sicherer (siehe stackoverflow.com/questions/5509543/… )
Shafique Jamal
@ BenRichards. Interessant. Ich werde dies irgendwann mit einem Ordner erneut versuchen, der einem Zweignamen entspricht, um ihn zu testen, und dann die Antwort aktualisieren. Vielen Dank.
Shafique Jamal
5

Sie könnten flache Klone verwenden (git> 1.9):

git clone --depth depth remote-url

Weiterführende Literatur: http://blogs.atlassian.com/2014/05/handle-big-repositories-git/

Matthias M.
quelle
4
Ein solcher Klon kann nicht in ein neues Repository verschoben werden.
Seweryn Niemiec
1
Es wäre nützlich zu wissen, wie diese Einschränkung umgangen werden kann. Kann jemand erklären, warum dies nicht erzwungen werden kann?
not2qubit
Die Antwort auf Ihre Frage: stackoverflow.com/questions/6900103/…
Matthias M
4

git filter-branch ist das Hauptchirurgiewerkzeug.

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

--parent-filterbringt die Eltern auf stdin und sollte die umgeschriebenen Eltern auf stdout drucken; Unix wird trueerfolgreich beendet und druckt nichts, also: keine Eltern. @^!ist Git Abkürzung für " The Head Commit, aber keiner seiner Eltern". Löschen Sie dann alle anderen Refs und drücken Sie nach Belieben.

jthill
quelle
3

Löschen Sie einfach das Github-Repo und erstellen Sie ein neues. Mit Abstand der schnellste, einfachste und sicherste Ansatz. Was müssen Sie schließlich tun, um all diese Befehle in der akzeptierten Lösung auszuführen, wenn Sie nur den Hauptzweig mit einem einzigen Commit benötigen?

AndroidDev
quelle
1
Einer der Hauptpunkte ist zu sehen, woher es gabelte.
not2qubit
Ich habe das gerade gemacht und es ist in Ordnung
thanos.a
2

Die folgende Methode ist genau reproduzierbar. Wenn beide Seiten konsistent sind, müssen Sie den Klon nicht erneut ausführen. Führen Sie das Skript einfach auch auf der anderen Seite aus.

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

Wenn Sie es dann bereinigen möchten, versuchen Sie dieses Skript:

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

Ich habe ein Skript geschrieben, das den Verlauf für jeden Zweig im Repository "beendet":

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

Siehe auch: http://sam.nipl.net/b/confirm

Sam Watkins
quelle
1
Danke dafür. Nur zu Ihrer Information: Ihr Skript zum Löschen des Verlaufs für jeden Zweig könnte aktualisiert werden - es gibt die folgenden Fehler: git-hash: not foundundSupport for <GIT_DIR>/info/grafts is deprecated
Shafique Jamal
1
@ShafiqueJamal, danke, das kleine "Git-Hash" -Skript ist git log HEAD~${1:-0} -n1 --format=%Hhier sam.aiki.info/b/git-hash. Es wäre besser, alles in einem Skript für den öffentlichen Verbrauch zusammenzufassen. Wenn ich es jemals wieder benutze, könnte ich herausfinden, wie es mit der neuen Funktion gemacht wird, die "Transplantate" ersetzt.
Sam Watkins
2

Ich möchte den gesamten Versionsverlauf aus dem lokalen Git-Repository entfernen, sodass der aktuelle Inhalt des Repositorys als einziges Commit angezeigt wird (und daher ältere Versionen von Dateien im Repository nicht gespeichert werden).

Eine konzeptionellere Antwort:

git müll sammelt automatisch alte Commits, wenn keine Tags / Zweige / Refs auf sie verweisen. Sie müssen also einfach alle Tags / Zweige entfernen und ein neues verwaistes Commit erstellen, das jedem Zweig zugeordnet ist. Konventionell würden Sie den Zweig masterauf dieses Commit verweisen lassen .

Die alten, nicht erreichbaren Commits werden dann von niemandem mehr gesehen, es sei denn, sie graben mit Git-Befehlen auf niedriger Ebene. Wenn das für Sie ausreicht, würde ich einfach dort anhalten und den automatischen GC seine Arbeit machen lassen, wann immer er möchte. Wenn Sie sie sofort loswerden möchten, können Sie git gc(möglicherweise mit --aggressive --prune=all) verwenden. Für das Remote-Git-Repository gibt es jedoch keine Möglichkeit, dies zu erzwingen, es sei denn, Sie haben Shell-Zugriff auf das Dateisystem.

AnoE
quelle
Schöne Ergänzung, wenn man sie im Kontext der Antwort von @Zeelot sieht.
Mogens TrasherDK
Ja, Zeelot's hat die Befehle, die dies im Grunde tun (nur anders, indem man komplett von vorne anfängt, was für OP in Ordnung sein könnte). @MogensTrasherDK
AnoE
0

Bitte schön:

#!/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"
}

Auch hier gehostet: https://gist.github.com/Zibri/76614988478a076bbe105545a16ee743

Zibri
quelle
Gah! Lassen Sie mich mein nicht verstecktes, ungeschütztes Passwort nicht in der Befehlszeile eingeben! Außerdem ist die Ausgabe des Git-Zweigs normalerweise schlecht für die Skripterstellung geeignet. Vielleicht möchten Sie sich die Sanitärwerkzeuge ansehen.
D. Ben Knoble
-1

Ich habe ein ähnliches Problem gelöst, indem ich einfach den .gitOrdner aus meinem Projekt gelöscht und über IntelliJ wieder in die Versionskontrolle integriert habe. Hinweis: Der .gitOrdner ist ausgeblendet. Sie können es im Terminal mit anzeigen ls -aund dann mit entfernen rm -rf .git.

JB Lovell
quelle
Das ist es, was er in Schritt 1 tut: rm -rf .git?
Nächte
-1

Verwenden Sie dazu den Shallow Clone-Befehl git clone --depth 1 URL - Klont nur den aktuellen HEAD des Repositorys

kkarki
quelle
-2

Um das letzte Commit von git zu entfernen, können Sie es einfach ausführen

git reset --hard HEAD^ 

Wenn Sie mehrere Commits von oben entfernen, können Sie sie ausführen

git reset --hard HEAD~2 

um die letzten beiden Commits zu entfernen. Sie können die Anzahl erhöhen, um noch mehr Commits zu entfernen.

Mehr Infos hier.

Das Git-Tutoturial hier bietet Hilfe zum Löschen des Repositorys:

Sie möchten die Datei aus dem Verlauf entfernen und zum Gitignore hinzufügen, um sicherzustellen, dass sie nicht versehentlich erneut festgeschrieben wird. In unseren Beispielen entfernen wir Rakefile aus dem GitHub-Gem-Repository.

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

Nachdem wir die Datei aus dem Verlauf gelöscht haben, stellen wir sicher, dass wir sie nicht versehentlich erneut festschreiben.

echo "Rakefile" >> .gitignore

git add .gitignore

git commit -m "Add Rakefile to .gitignore"

Wenn Sie mit dem Status des Repositorys zufrieden sind, müssen Sie die Änderungen erzwingen, um das Remote-Repository zu überschreiben.

git push origin master --force
kiriloff
quelle
6
Das Entfernen von Dateien oder Commits aus dem Repository hat absolut keine Beziehung zu der Frage (die das Entfernen des Verlaufs erfordert, eine völlig andere Sache). Das OP möchte einen sauberen Verlauf, möchte jedoch den aktuellen Status des Repositorys beibehalten.
Victor Schröder
Dies führt nicht zu dem in der Frage gestellten Ergebnis. Sie verwerfen alle Änderungen nach dem Festschreiben, das Sie zuletzt beibehalten haben, und verlieren seitdem alle Änderungen. In der Frage wird jedoch gefragt, ob die aktuellen Dateien beibehalten und der Verlauf gelöscht werden sollen.
Tuncay Göncüoğlu