Tornar a confirmação atual a única confirmação (inicial) em um repositório Git?

664

Atualmente, tenho um repositório Git local, o qual envio por push para um repositório Github.

O repositório local possui ~ 10 confirmações e o repositório Github é uma duplicata sincronizada disso.

O que eu gostaria de fazer é remover TODO o histórico de versões do repositório Git local, para que o conteúdo atual do repositório apareça como o único commit (e, portanto, as versões mais antigas dos arquivos no repositório não são armazenadas).

Eu gostaria de fazer essas alterações no Github.

Eu investiguei a rebase do Git, mas isso parece ser mais adequado para remover versões específicas. Outra solução em potencial é excluir o repositório local e criar um novo - embora isso provavelmente crie muito trabalho!

ETA: Existem diretórios / arquivos específicos que não são rastreados - se possível, eu gostaria de manter o rastreamento desses arquivos.

kaese
fonte
6
Consulte também stackoverflow.com/questions/435646/… ("Como eu combino os dois primeiros commits de um repositório Git?")
Anonymoose
e isto: Como esmagar todos os commits do git em um?
perfil completo de ryenus

Respostas:

981

Aqui está a abordagem da força bruta. Também remove a configuração do repositório.

Nota : Isso NÃO funciona se o repositório tiver submódulos! Se você estiver usando submódulos, deverá usar, por exemplo, rebase interativa

Etapa 1: remover todo o histórico ( verifique se você tem backup, isso não pode ser revertido )

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

Etapa 2: reconstruir o repositório Git apenas com o conteúdo atual

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

Etapa 3: pressione o GitHub.

git remote add origin <github-uri>
git push -u --force origin master
Fred Foo
fonte
3
Obrigado larsmans - optei por usar isso como minha solução. Embora a inicialização do repositório Git perca o registro de arquivos não rastreados no repositório antigo, essa é provavelmente uma solução mais simples para o meu problema.
Kaese
5
@kaese: Eu acho que você .gitignoredeve lidar com isso, certo?
Fred Foo
48
Salve seu .git / config antes e restaure-o depois.
Lalebarde 19/04
@lalebarde Se você restaurar o .git / config depois git commit -m "Initial commit", provavelmente poderá pular a git remote add ...parte, assumindo que já estava na sua configuração, e seguir em frente pressionando. Funcionou para mim.
Buttle Butkus
24
Tenha cuidado com isso se estiver tentando remover dados confidenciais: a presença de apenas uma única confirmação no ramo mestre recém-enviado é enganosa - o histórico ainda existirá , mas não será acessível a partir desse ramo. Se você tiver tags, por exemplo, que apontam para confirmações mais antigas, essas confirmações estarão acessíveis. De fato, para qualquer um que tenha um pouco de git foo, tenho certeza de que, após esse push do git, eles ainda poderão recuperar todo o histórico do repositório do GitHub - e se você tiver outras ramificações ou tags, elas não terão ainda precisa de muito git foo.
Robert Muil
621

A única solução que funciona para mim (e mantém os submódulos funcionando) é

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

A exclusão .git/sempre causa problemas enormes quando tenho submódulos. O uso de git rebase --rootalguma forma causaria conflitos para mim (e levaria muito tempo, pois eu tinha muita história).

Zeelot
fonte
55
essa deve ser a resposta correta! basta adicionar um git push -f origin mastercomo a última operação e o sol brilhará novamente em seu novo repositório! :)
gru
2
Isso não mantém velhos compromissos por aí?
26414 Brad
4
@JonePolvora git fetch; git reset --hard origem / master stackoverflow.com/questions/4785107/…
echo
5
Depois de fazer isso, o repositório liberará espaço?
Inuart 28/08/14
8
Eu acredito que você deve adicionar a sugestão de @JasonGoemaat como a última linha da sua resposta. Sem git gc --aggressive --prune alltodo o ponto de perder a história seria desperdiçada.
Tuncay Göncüoğlu 22/10
93

Esta é a minha abordagem preferida:

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

Isso criará um novo ramo com um commit que adiciona tudo no HEAD. Não altera mais nada, por isso é completamente seguro.

dan_waterworth
fonte
3
Melhor abordagem! Limpe e faça o trabalho. Além disso, renomeio o ramo com muitas alterações de "mestre" para "trabalho local" e "new_branch_name" para "mestre". Em Mestre, o seguinte: git -m locais-muda mestre git branch -m-alterações locais git checkout new_branch_name git -m ramo <
Valtoni Boaventura
Parece muito curto e elegante, a única coisa que ainda não entendi ou ainda não vi é HEAD ^ {tree}, alguém poderia explicar? Além disso, eu leria isso como "criar nova ramificação a partir de um dado commit, criado com a criação de um novo objeto de commit com uma mensagem de commit de ___"
TomKeegasi
3
O local definitivo para procurar respostas para perguntas sobre a sintaxe de referência do git está nos git-rev-parsedocumentos. O que está acontecendo aqui é git-commit-treerequer uma referência a uma árvore (um instantâneo do repositório), mas HEADé uma revisão. Para encontrar a árvore associada a uma confirmação, usamos o <rev>^{<type>}formulário
dan_waterworth
Boa resposta. Funciona bem. Finalmente digagit push --force <remote> new_branch_name:<remote-branch>
Felipe Alvarez
31

A outra opção, que pode resultar em muito trabalho, se você tiver muitos commits, é uma rebase interativa (assumindo que sua versão do git seja> = 1.7.12):git rebase --root -i

Quando apresentado com uma lista de confirmações no seu editor:

  • Altere "pick" para "reformular" para o primeiro commit
  • Altere "pick" para "consertar" todos os outros commit

Salvar e fechar. O Git começará a ser reformulado.

No final, você teria um novo commit raiz que é uma combinação de todos os que vieram depois dele.

A vantagem é que você não precisa excluir seu repositório e, se tiver dúvidas, sempre terá um substituto.

Se você realmente deseja aprimorar seu histórico, redefina o mestre para esse commit e exclua todos os outros ramos.

Carl
fonte
Depois que o rebase foi concluído, não consigo enviar:error: failed to push some refs to
Begueradj 12/03/19
@Begueradj, se você já empurrou a ramificação que rebatizou, precisará forçar a pressão git push --force-with-lease. force-with-lease é usado porque é menos destrutivo que --force.
Carl
19

Variante do método proposto por larsmans :

Salve sua lista de arquivos não rastreados:

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

Salve sua configuração do git:

mv .git/config /tmp/

Em seguida, execute os primeiros passos de larsmans:

rm -rf .git
git init
git add .

Restaure sua configuração:

mv /tmp/config .git/

Rastreie seus arquivos não rastreados:

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

Em seguida, confirme:

git commit -m "Initial commit"

E, finalmente, envie para o seu repositório:

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

Abaixo está um script adaptado da resposta de @Zeelot. Ele deve remover o histórico de todas as ramificações, não apenas da ramificação principal:

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

Funcionou para meus propósitos (não estou usando submódulos).

Shafique Jamal
fonte
4
Eu acho que você se esqueceu de forçar o push master a concluir o procedimento.
Not2qubit 7/03/19
2
Eu tive que fazer uma ligeira modificação. git branchincluirá um asterisco ao lado de sua ramificação com check-out, que será globbed, fazendo com que ela seja resolvida em todos os arquivos ou pastas como se esses também fossem nomes de ramificações. Em vez disso, usei o git branch --format="%(refname:lstrip=2)"que me deu apenas os nomes das filiais.
Ben Richards
@ not2qubit: Obrigado por isso. Qual seria o comando exato? git push --force origin masterou git push --force-with-lease? Aparentemente, o último é mais seguro (consulte stackoverflow.com/questions/5509543/… ) #
Shafique Jamal
@BenRichards. Interessante. Tentarei novamente em algum momento com uma pasta que corresponda ao nome de uma filial para testá-la e, em seguida, atualize a resposta. Obrigado.
Shafique Jamal
4

git filter-branch é a ferramenta de cirurgia principal.

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

--parent-filtercoloca os pais no stdin e deve imprimir os pais reescritos no stdout; O unix truesai com sucesso e imprime nada, então: sem pais. @^!é uma abreviação do Git para "o chefe compromete, mas nenhum de seus pais". Em seguida, exclua todos os outros árbitros e empurre à vontade.

jthill
fonte
3

Basta excluir o repositório do Github e criar um novo. De longe, a abordagem mais rápida, fácil e segura. Afinal, o que você precisa obter ao executar todos esses comandos na solução aceita quando tudo o que você deseja é o ramo principal com uma única confirmação?

AndroidDev
fonte
1
Um dos pontos principais é poder ver de onde foi bifurcada.
not2qubit # 039
Eu só fiz isso e é bom
thanos.a
2

O método abaixo é exatamente reproduzível, portanto, não há necessidade de executar o clone novamente se os dois lados forem consistentes; basta executar o script do outro lado também.

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

Se você quiser limpá-lo, tente este script:

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

Eu escrevi um script que "mata histórico" para cada ramificação no repositório:

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

veja também: http://sam.nipl.net/b/confirm

Sam Watkins
fonte
1
Obrigado por isso. Apenas FYI: seu script para matar o histórico de cada ramo pode usar alguma atualização - ele fornece os seguintes erros: git-hash: not foundeSupport for <GIT_DIR>/info/grafts is deprecated
Shafique Jamal
1
@ ShafiqueJamal, obrigado, o pequeno script "git-hash" é git log HEAD~${1:-0} -n1 --format=%H, aqui, sam.aiki.info/b/git-hash Seria melhor colocar tudo em um script para consumo público. Se eu usá-lo novamente, posso descobrir como fazê-lo com o novo recurso que substitui "enxertos".
Sam Watkins
2

O que eu gostaria de fazer é remover TODO o histórico de versões do repositório Git local, para que o conteúdo atual do repositório apareça como o único commit (e, portanto, as versões mais antigas dos arquivos no repositório não são armazenadas).

Uma resposta mais conceitual:

O git garbage coleta automaticamente commits antigos se nenhuma tag / branches / refs apontar para eles. Então você simplesmente precisa remover todas as tags / branches e criar um novo commit órfão, associado a qualquer branch - por convenção, você deixaria o branch masterapontar para esse commit.

As confirmações antigas e inacessíveis nunca mais serão vistas por ninguém, a menos que elas pesquisem com comandos git de baixo nível. Se isso é suficiente para você, eu apenas pararia por aí e deixaria o GC automático fazer seu trabalho sempre que desejar. Se você quiser se livrar deles imediatamente, pode usar git gc(possivelmente com --aggressive --prune=all). Para o repositório remoto do git, não há como forçar isso, a menos que você tenha acesso ao shell do sistema de arquivos.

AnoE
fonte
Além disso, quando vista no contexto da resposta de @Zeelot.
Mogens TrasherDK
Sim, o Zeelot tem os comandos que basicamente fazem isso (de maneira diferente, iniciando completamente de novo, o que pode ser bom para o OP). @MogensTrasherDK
AnoE 9/09/19
0

Aqui está:

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

Também hospedado aqui: https://gist.github.com/Zibri/76614988478a076bbe105545a16ee743

Zibri
fonte
Gah! Não me faça fornecer minha senha não oculta e desprotegida na linha de comando! Além disso, a saída do ramo git geralmente não é adequada para scripts. Você pode querer olhar para as ferramentas de encanamento.
D. Ben Knoble 01/09/19
-1

Resolvi um problema semelhante apenas excluindo a .gitpasta do meu projeto e reintegrando-o com o controle de versão através do IntelliJ. Nota: A .gitpasta está oculta. Você pode vê-lo no terminal com ls -ae, em seguida, removê-lo usando rm -rf .git.

JB Lovell
fonte
é isso que ele está fazendo na etapa 1: rm -rf .git?
noites
-1

Para isso, use o comando Shallow Clone git clone - profundidade 1 URL - Clona apenas o HEAD atual do repositório

kkarki
fonte
-2

Para remover o último commit do git, você pode simplesmente executar

git reset --hard HEAD^ 

Se você estiver removendo várias confirmações da parte superior, poderá executar

git reset --hard HEAD~2 

para remover os dois últimos commits. Você pode aumentar o número para remover ainda mais confirmações.

Mais informações aqui.

O tutoturial do Git aqui fornece ajuda sobre como limpar o repositório:

você deseja remover o arquivo do histórico e adicioná-lo ao .gitignore para garantir que ele não seja confirmado acidentalmente. Para nossos exemplos, removeremos o Rakefile do repositório de gemas do 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

Agora que apagamos o arquivo do histórico, vamos garantir que não o comprometamos acidentalmente novamente.

echo "Rakefile" >> .gitignore

git add .gitignore

git commit -m "Add Rakefile to .gitignore"

Se você estiver satisfeito com o estado do repositório, precisará forçar as alterações para substituir o repositório remoto.

git push origin master --force
Kiriloff
fonte
6
Remover arquivos ou confirmações do repositório não tem absolutamente nenhuma relação com a pergunta (que pede para remover o histórico, uma coisa completamente diferente). O OP quer um histórico limpo, mas deseja preservar o estado atual do repositório.
Victor Schröder
isso não produz o resultado solicitado na pergunta. você está descartando todas as alterações após a confirmação mantida por último e perdendo todas as alterações desde então, mas a pergunta pede para manter os arquivos atuais e descartar o histórico.
Tuncay Göncüoğlu 22/10