Czy bieżące zatwierdzenie jest jedynym (początkowym) zatwierdzeniem w repozytorium Git?

664

Obecnie mam lokalne repozytorium Git, które przekazuję do repozytorium Github.

Lokalne repozytorium zawiera ~ 10 zatwierdzeń, a repozytorium Github jest zsynchronizowanym duplikatem tego.

Chciałbym usunąć CAŁĄ historię wersji z lokalnego repozytorium Git, więc bieżąca zawartość repozytorium pojawi się jako jedyne zatwierdzenie (a zatem starsze wersje plików w repozytorium nie są przechowywane).

Chciałbym wtedy przekazać te zmiany Githubowi.

Zbadałem bazę Git, ale wydaje się, że jest to bardziej odpowiednie do usuwania określonych wersji. Innym potencjalnym rozwiązaniem jest usunięcie lokalnego repozytorium i utworzenie nowego - choć prawdopodobnie spowodowałoby to dużo pracy!

ETA: Istnieją określone katalogi / pliki, które nie są śledzone - jeśli to możliwe, chciałbym zachować śledzenie tych plików.

kaese
źródło
6
Zobacz także stackoverflow.com/questions/435646/… („Jak połączyć dwa pierwsze zatwierdzenia z repozytorium Git?”)
Anonimowy

Odpowiedzi:

981

Oto podejście brutalnej siły. Usuwa również konfigurację repozytorium.

Uwaga : NIE działa to, jeśli repozytorium ma podmoduły! Jeśli korzystasz z submodułów, powinieneś użyć np. Interaktywnej bazy

Krok 1: Usuń całą historię ( upewnij się, że masz kopię zapasową, której nie można przywrócić )

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

Krok 2: zrekonstruuj repozytorium Git tylko z bieżącą zawartością

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

Krok 3: push do GitHub.

git remote add origin <github-uri>
git push -u --force origin master
Fred Foo
źródło
3
Dzięki, larsmans - zdecydowałem się użyć tego jako mojego rozwiązania. Chociaż inicjowanie repozytorium Git traci zapis nieśledzonych plików w starym repo, jest to prawdopodobnie prostsze rozwiązanie mojego problemu.
kaese
5
@kaese: Myślę, że .gitignorepowinieneś sobie z tym poradzić, prawda?
Fred Foo
48
Zapisz .git / config wcześniej i przywróć go później.
lalebarde
@lalebarde Jeśli później przywrócisz .git / config git commit -m "Initial commit", prawdopodobnie możesz pominąć git remote add ...część, zakładając, że była już w konfiguracji i przejść od razu do wypychania. To zadziałało dla mnie.
Buttle Butkus,
24
Bądź ostrożny, jeśli próbujesz usunąć wrażliwe dane: obecność tylko jednego zatwierdzenia w nowo przesłanej gałęzi master jest myląca - historia nadal będzie istnieć , po prostu nie będzie dostępna z tej gałęzi. Jeśli masz na przykład tagi, które wskazują na starsze zatwierdzenia, te zatwierdzenia będą dostępne. W rzeczywistości dla każdego, kto ma trochę git foo, jestem pewien, że po tym git push nadal będą mogli odzyskać całą historię z repozytorium GitHub - a jeśli masz inne gałęzie lub tagi, to nie nawet potrzebuję dużo git foo.
Robert Muil
621

Jedyne rozwiązanie, które działa dla mnie (i utrzymuje działanie podmodułów), to

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

Usuwanie .git/zawsze powoduje ogromne problemy, gdy mam submoduły. Używanie w git rebase --rootjakiś sposób spowodowałoby dla mnie konflikty (i zajęło mi dużo czasu, odkąd miałem dużo historii).

Zeelot
źródło
55
to powinna być poprawna odpowiedź! po prostu dodaj git push -f origin masterostatnią operację, a słońce znów zaświeci na twoim świeżym repo! :)
gru
2
Czy to nie utrzymuje starych zobowiązań?
Brad
4
@JonePolvora git fetch; git reset --hard origin / master stackoverflow.com/questions/4785107/…
echo
5
czy po wykonaniu tej czynności repo zwolni miejsce?
Inuart
8
Uważam, że powinieneś dodać sugestię @JasonGoemaat jako ostatnią linię do swojej odpowiedzi. Bez git gc --aggressive --prune allcałego punktu utraty historii nie można byłoby przegapić.
Tuncay Göncüoğlu
93

Oto moje ulubione podejście:

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

Spowoduje to utworzenie nowej gałęzi z jednym zatwierdzeniem, która doda wszystko do HEAD. Nie zmienia niczego innego, więc jest całkowicie bezpieczny.

dan_waterworth
źródło
3
Najlepsze podejście! Wyczyść i wykonaj pracę. Dodatkowo zmieniam nazwę gałęzi z wieloma zmianami z „master” na „local-work” i „new_branch_name” na „master”. W trybie głównym wykonaj następujące czynności: git -m zmiany lokalne gałąź git -m zmiany lokalne git kasa nowa_nazwa_gałęzi git gałąź -m master <
Valtoni Boaventura
Wygląda to naprawdę krótko i elegancko, jedyne czego nie rozumiem lub jeszcze nie widziałem to HEAD ^ {tree}, czy ktoś mógłby to wyjaśnić? Poza tym przeczytałbym to jako: „Utwórz nową gałąź z podanego zatwierdzenia, utworzony przez utworzenie nowego obiektu zatwierdzenia z podaną wiadomością zatwierdzenia od ___”
TomKeegasi
3
Ostateczne miejsce, w którym można znaleźć odpowiedzi na pytania dotyczące składni referencji git, znajduje się w git-rev-parsedokumentacji. To, co się tutaj dzieje, git-commit-treewymaga odwołania do drzewa (migawki repozytorium), ale HEADjest wersją. Aby znaleźć drzewo powiązane z zatwierdzeniem, używamy <rev>^{<type>}formularza.
dan_waterworth
Niezła odpowiedź. Działa dobrze. Na koniec powiedzgit push --force <remote> new_branch_name:<remote-branch>
Felipe Alvarez,
31

Inną opcją, która może okazać się bardzo pracochłonna, jeśli masz dużo zatwierdzeń, jest interaktywny rebase (zakładając, że twoja wersja git to> = 1.7.12):git rebase --root -i

Po wyświetleniu listy zatwierdzeń w edytorze:

  • Zmień „pick” na „reword” dla pierwszego zatwierdzenia
  • Zmień „pick” na „fixup” przy każdym innym zatwierdzeniu

Zapisz i zamknij. Git zacznie się opierać.

Na koniec miałbyś nowy główny zatwierdzenie, które jest kombinacją wszystkich, które pojawiły się po nim.

Zaletą jest to, że nie musisz usuwać swojego repozytorium, a jeśli masz wątpliwości, zawsze masz awarię.

Jeśli naprawdę chcesz zniszczyć swoją historię, zresetuj master do tego zatwierdzenia i usuń wszystkie inne gałęzie.

Carl
źródło
Po zakończeniu zmiany error: failed to push some refs to
bazy
@ Begueradj, jeśli już wypchnąłeś gałąź, którą wyreżyserowałeś, musisz wymusić push git push --force-with-lease. użyto opcji przymusowej dzierżawy, ponieważ jest mniej niszcząca niż --force.
Carl
19

Wariant proponowanej metody larsmansa :

Zapisz listę plików nieśledzących:

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

Zapisz swoją konfigurację git:

mv .git/config /tmp/

Następnie wykonaj pierwsze kroki larsmansa:

rm -rf .git
git init
git add .

Przywróć konfigurację:

mv /tmp/config .git/

Wyśledzić wyśledzone pliki:

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

Następnie dokonaj:

git commit -m "Initial commit"

I wreszcie wypchnij do swojego repozytorium:

git push -u --force origin master
Lalebarde
źródło
6

Poniżej znajduje się skrypt zaadaptowany z odpowiedzi @Zeelot. Powinien usunąć historię ze wszystkich gałęzi, nie tylko gałęzi głównej:

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

Działa dla moich celów (nie używam submodułów).

Shafique Jamal
źródło
4
Myślę, że zapomniałeś zmusić mistrza push do ukończenia procedury.
not2qubit
2
Musiałem dokonać niewielkiej modyfikacji. git branchbędzie zawierać gwiazdkę obok wyewidencjonowanej gałęzi, która zostanie następnie globowana, powodując, że będzie ona rozpoznawana we wszystkich plikach lub folderach tak, jakby były to również nazwy gałęzi. Zamiast tego użyłem, git branch --format="%(refname:lstrip=2)"co dało mi tylko nazwy oddziałów.
Ben Richards,
@ not2qubit: Dzięki za to. Jakie byłoby dokładne polecenie? git push --force origin masterlub git push --force-with-lease? Najwyraźniej ten drugi jest bezpieczniejszy (patrz stackoverflow.com/questions/5509543/… )
Shafique Jamal
@BenRichards. Ciekawy. Spróbuję to jeszcze raz w folderze pasującym do nazwy oddziału, aby go przetestować, a następnie zaktualizuję odpowiedź. Dzięki.
Shafique Jamal
4

git filter-branch jest głównym narzędziem chirurgicznym.

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

--parent-filterdostaje rodziców na standardowe wyjście i powinien wydrukować przepisanych rodziców na standardowych; unix truekończy działanie pomyślnie i nic nie drukuje, więc: brak rodziców. @^!jest skrótem Git od słowa „głowa popełnia, ale żaden z jej rodziców”. Następnie usuń wszystkie inne referencje i pchaj w wolnym czasie.

jthill
źródło
3

Wystarczy usunąć repozytorium Github i utworzyć nowe. Zdecydowanie najszybsze, najłatwiejsze i najbezpieczniejsze podejście. W końcu, co musisz zyskać, wykonując wszystkie te polecenia w zaakceptowanym rozwiązaniu, gdy wszystko, czego potrzebujesz, to gałąź master z jednym zatwierdzeniem?

AndroidDev
źródło
1
Jednym z głównych punktów jest możliwość zobaczenia, skąd został rozwidlony.
not2qubit
Właśnie to zrobiłem i jest w porządku
thanos.a
2

Poniższa metoda jest dokładnie powtarzalna, więc nie trzeba ponownie uruchamiać klonowania, jeśli obie strony są spójne, wystarczy uruchomić skrypt również po drugiej stronie.

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

Jeśli chcesz to wyczyścić, wypróbuj ten skrypt:

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

Napisałem skrypt, który „zabija historię” dla każdej gałęzi w repozytorium:

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

patrz także: http://sam.nipl.net/b/confirm

Sam Watkins
źródło
1
Dzięki za to. Tylko do waszej informacji: twój skrypt do zabicia historii dla każdej gałęzi może git-hash: not foundSupport for <GIT_DIR>/info/grafts is deprecated
wymagać
1
@ShafiqueJamal, dzięki, mały skrypt „git-hash” to git log HEAD~${1:-0} -n1 --format=%Htutaj sam.aiki.info/b/git-hash Lepiej byłoby umieścić to wszystko w jednym skrypcie do publicznego użytku. Jeśli kiedykolwiek go użyję, mogę wymyślić, jak to zrobić, dzięki nowej funkcji, która zastępuje „przeszczepy”.
Sam Watkins
2

Chciałbym usunąć CAŁĄ historię wersji z lokalnego repozytorium Git, więc bieżąca zawartość repozytorium pojawi się jako jedyne zatwierdzenie (a zatem starsze wersje plików w repozytorium nie są przechowywane).

Bardziej konceptualna odpowiedź:

git automatycznie śmieci zbiera stare zatwierdzenia, jeśli nie wskazują na nie żadne tagi / gałęzie / referencje. Musisz więc po prostu usunąć wszystkie tagi / gałęzie i utworzyć nowe sieroce zatwierdzenie, powiązane z dowolną gałęzią - zgodnie z konwencją pozwolisz gałęzi masterwskazywać na to zatwierdzenie.

Stare, nieosiągalne zatwierdzenia nigdy już nie będą widoczne, chyba że zaczną kopać za pomocą poleceń git niskiego poziomu. Jeśli to ci wystarczy, zatrzymam się i pozwolę, aby automatyczny GC wykonał swoją pracę, kiedy tylko zechce. Jeśli chcesz się ich od razu pozbyć, możesz użyć git gc(być może z --aggressive --prune=all). W przypadku zdalnego repozytorium git nie można tego wymusić, chyba że masz dostęp do powłoki systemu plików.

AnoE
źródło
Przyjemny dodatek, gdy widzi się go w kontekście odpowiedzi @Zeelot.
Mogens TrasherDK
Tak, Zeelot ma polecenia, które w zasadzie to robią (zupełnie inaczej, zaczynając całkowicie od nowa, co może być w porządku dla OP). @MogensTrasherDK
AnoE
0

Proszę bardzo:

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

Hostowane również tutaj: https://gist.github.com/Zibri/76614988478a076bbe105545a16ee743

Zibri
źródło
Gah! Nie zmuszaj mnie do podania w wierszu poleceń mojego ukrytego, niechronionego hasła! Ponadto dane wyjściowe gałęzi git są zwykle słabo dostosowane do skryptów. Możesz spojrzeć na narzędzia hydrauliczne.
D. Ben Knoble,
-1

Rozwiązałem podobny problem, po prostu usuwając .gitfolder z mojego projektu i ponownie integrując się z kontrolą wersji za pośrednictwem IntelliJ. Uwaga: .gitfolder jest ukryty. Możesz go wyświetlić w terminalu za pomocą ls -a, a następnie usunąć za pomocą rm -rf .git.

JB Lovell
źródło
to właśnie robi w kroku 1: rm -rf .git?
noce
-1

W tym celu użyj polecenia Shallow Clone git clone --depth 1 URL - Sklonuje tylko bieżącą HEAD repozytorium

kkarki
źródło
-2

Aby usunąć ostatnie zatwierdzenie z git, możesz po prostu uruchomić

git reset --hard HEAD^ 

Jeśli usuwasz wiele zatwierdzeń z góry, możesz uruchomić

git reset --hard HEAD~2 

aby usunąć dwa ostatnie zatwierdzenia. Możesz zwiększyć liczbę, aby usunąć jeszcze więcej zatwierdzeń.

Więcej informacji tutaj.

Git tutoturial zapewnia pomoc dotyczącą czyszczenia repozytorium:

chcesz usunąć plik z historii i dodać go do .gitignore, aby upewnić się, że nie zostanie przypadkowo ponownie zatwierdzony. W naszych przykładach usuniemy Rakefile z repozytorium klejnotów 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

Teraz, gdy usunęliśmy plik z historii, upewnijmy się, że nie popełnimy go przypadkowo ponownie.

echo "Rakefile" >> .gitignore

git add .gitignore

git commit -m "Add Rakefile to .gitignore"

Jeśli jesteś zadowolony ze stanu repozytorium, musisz wymusić zmiany w celu zastąpienia zdalnego repozytorium.

git push origin master --force
Kiriloff
źródło
6
Usuń pliki lub zatwierdzenia z repozytorium absolutnie nie ma związku z pytaniem (które wymaga usunięcia historii, zupełnie inna sprawa). OP chce czystej historii, ale chce zachować obecny stan repozytorium.
Victor Schröder
nie daje to wyniku zadanego w pytaniu. odrzucasz wszystkie zmiany po zatwierdzeniu, które zachowujesz jako ostatnie i tracisz wszystkie zmiany od tego czasu, ale pytanie wymaga zachowania bieżących plików i usunięcia historii.
Tuncay Göncüoğlu