Como removo o histórico antigo de um repositório git?

209

Receio não ter encontrado nada parecido com esse cenário em particular.

Eu tenho um repositório git com muito histórico: mais de 500 ramos, mais de 500 tags, desde meados de 2007. Ele contém ~ 19.500 confirmações. Gostaríamos de remover todo o histórico antes de 1º de janeiro de 2010, para torná-lo menor e mais fácil de lidar (manteríamos uma cópia completa do histórico em um repositório de archive).

Conheço o commit que quero que tenha se tornado a raiz do novo repositório. No entanto, não consigo descobrir o git mojo correto para truncar o repositório e começar com esse commit. Eu estou supondo que alguma variante de

git filter-branch

seria necessário envolver enxertos; ele também pode ser necessária para tratar cada um dos mais de 200 filiais queremos manter separadamente e depois corrigir o repo de volta juntos (algo que eu não sei como fazer).

Alguém já fez algo assim? Eu tenho o git 1.7.2.3, se isso importa.

ebneter
fonte

Respostas:

118

Basta criar um enxerto do pai do seu novo commit raiz para nenhum pai (ou para um commit vazio, por exemplo, o commit root real do seu repositório). Por exemploecho "<NEW-ROOT-SHA1>" > .git/info/grafts

Depois de criar o enxerto, ele entra em vigor imediatamente; você deve poder ver git loge ver se os antigos commit indesejados desapareceram:

$ 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

Se tudo parecer como pretendido, você pode simplesmente fazer um simples git filter-branch -- --allpara torná-lo permanente.

CUIDADO: depois de executar a etapa de filtragem de ramificação , todos os IDs de confirmação serão alterados; portanto, qualquer pessoa que use o repositório antigo nunca deve se unir a alguém usando o novo repositório.

apenwarr
fonte
6
Eu tive que fazer git filter-branch --tag-name-filter cat -- --allpara atualizar as tags. Mas também tenho tags mais antigas apontando para o histórico antigo que desejo excluir. Como posso me livrar de todas essas tags antigas? Se eu não excluí-los, o histórico mais antigo não desaparece e ainda posso vê-lo gitk --all.
Craig McQueen
9
"Basta criar um enxerto do pai do seu novo commit raiz para nenhum pai" precisa de alguma elaboração. Eu tentei isso e não consegui descobrir a sintaxe para "sem pai". A página do manual afirma que um ID de confirmação pai é necessário; usar todos os zeros me dá um erro.
Marius Gedminas
6
No caso de alguém queria saber exatamente como ele funciona, é muito fácil:echo "<NEW-ROOT-HASH>" > .git/info/grafts
friederbluemle
3
Concordo, explicando o que um enxerto é seria mais do que útil
Charles Martin
4
Citado na página wiki vinculada em enxertos. "A partir do Git 1.6.5, a substituição mais flexível do git foi adicionada, o que permite substituir qualquer objeto por qualquer outro objeto e rastreia as associações por meio de referências que podem ser pressionadas e puxadas entre repos." Portanto, essa resposta pode estar desatualizada para as versões atuais do git.
ThorSummoner
130

Talvez seja tarde demais para postar uma resposta, mas como esta página é o primeiro resultado do Google, ainda pode ser útil.

Se você deseja liberar espaço no seu repositório git, mas não deseja reconstruir todos os seus commits (rebase ou enxerto) e ainda conseguir empurrar / puxar / mesclar pessoas que possuem o repositório completo, você pode usar o git clone clone superficial (- profundidade parâmetro).

; 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

Você poderá rasgar seu repo existente, seguindo estas etapas:

; 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

Como remover todas as tags locais do git?

Ps: As versões mais antigas do git não suportavam clone / push / pull de / para repositórios rasos.

Alexandre T.
fonte
9
+1 Esta é a resposta correta para versões mais recentes do Git. (Ah, e por favor, volte para PPCG !)
wizzwizz4
6
Como você pode acessar cduma pasta que acabou de ser excluída? Sinto que há algumas informações ausentes aqui. Além disso, existe uma maneira de aplicar essas alterações ao repositório remoto?
Trogdor # 6/16
4
@ Jez Essa seria a outra resposta votada pelo topo. Esta resposta não é para você se você quiser se livrar permanentemente da história. É para trabalhar com grandes histórias.
Ninguém
4
Para responder à minha própria pergunta: git clone file:///Users/me/Projects/myProject myClonedProject --shallow-since=2016-09-02Funciona como um encanto!
Micros
5
@ Jez, você pode converter seu repositório raso em normal executando um git filter-branch -- --all. Isso vai mudar todos os hashes nele, mas depois que você vai ser capaz de empurrá-lo para um novo repo
Ed'ka
61

este método é fácil de entender e funciona bem. O argumento para o script ( $1) é uma referência (tag, hash, ...) ao commit a partir do qual você deseja manter seu histórico.

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

NOTA as tags antigas ainda permanecerão presentes; então você pode precisar removê-los manualmente

observação: eu sei que isso é quase o mesmo que o @yoyodin, mas existem alguns comandos e informações extras importantes aqui. Tentei editar a resposta, mas como é uma alteração substancial na resposta de @ yoyodin, minha edição foi rejeitada, então aqui estão as informações!

Chris Maes
fonte
Agradeço as explicações dadas para os comandos git prunee git gc. Existe uma explicação para o restante dos comandos no script? Como está, não está claro quais argumentos estão sendo passados ​​para ele e o que cada comando está fazendo. Obrigado.
user5359531
2
@ user5359531 obrigado pela sua observação, adicionei mais alguns comentários para cada comando. Espero que isto ajude.
Chris Maes
4
Conflitos de mesclagem em todo o lugar ... não muito útil
Warpzit
3
@Warpzit me livrei de conflitos de mesclagem, adicionando -pao rebasecomando, como sugerido em outra resposta
leonbloy
1
Eu segui exatamente isso, e tudo o que consegui foi a mesma história de antes, com uma nova ramificação iniciando no commit que eu queria remover com a mesma história de antes. Nenhum histórico foi removido.
DrStrangepork
51

Tente este método Como truncar o histórico do git :

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

Aqui $1é SHA-1 da submissão que você deseja manter eo script vai criar novo ramo que contém todos os commits entre $1e mastere toda a história mais antigo é descartado. Observe que esse script simples pressupõe que você não possui ramificação existente chamada temp. Observe também que esse script não limpa os dados do git para o histórico antigo. Execute git gc --prune=all && git repack -a -f -F -ddepois de verificar se realmente deseja perder todo o histórico. Você também pode precisar, rebase --preserve-mergesmas esteja avisado de que a implementação git desse recurso não é perfeita. Inspecione os resultados manualmente, se você usar isso.

yoyodyn
fonte
22
Eu tentei isso, mas obtive conflitos de mesclagem na rebaseetapa. Estranho - eu não esperava que conflitos de mesclagem fossem possíveis nessas circunstâncias.
Craig McQueen
2
Use git commit --allow-empty -m "Truncate history"se a confirmação que você fez check-out não contém nenhum arquivo.
Friederbluemle
2
Como envio isso de volta ao mestre remoto? Quando faço isso, acabo com a velha e a nova história.
Rustyx
1
O que 'temp' deveria ser? O que você deve passar como argumento para isso? Existe um exemplo de como esses comandos devem parecer quando você os executa? Obrigado.
user5359531
1
Eu acredito que $ 1 é o hash de confirmação. (Mais detalhes são fornecidos no artigo vinculado).
Chris Nolet
34

Como alternativa à reescrita do histórico, considere usar git replacecomo neste artigo do livro Pro Git . O exemplo discutido envolve a substituição de uma confirmação pai para simular o início de uma árvore, mantendo o histórico completo como um ramo separado para proteção.

Jeff Bowman
fonte
Sim, acho que você provavelmente poderia fazer o que queríamos com isso, se também dispusesse o ramo de história completo separado. (Nós estávamos tentando diminuir o repositório.)
Ebneter
1
Fiquei desanimado com a resposta estar fora do local; mas está vinculado ao site GitScm e o tutorial ao qual está vinculado está muito bem escrito e parece diretamente ao ponto da pergunta do OP.
precisa saber é o seguinte
@ThorSummoner Desculpe por isso! Vou desenvolver a resposta um pouco mais plenamente no local
Jeff Bowman
Infelizmente, essa não é uma alternativa para reescrever a história. Há uma frase confusa no começo do artigo que provavelmente deu essa impressão. Isso poderia ser removido desta resposta? Você verá no artigo que o autor reescreve o histórico do ramo truncado, mas propõe uma maneira de anexar novamente o ramo "histórico" herdado usando git replace. Acredito que isso foi corrigido em outra pergunta em que você postou esta resposta.
Mitch
1
A discussão de git replacecomparação git grafté feita em stackoverflow.com/q/6800692/873282
koppor
25

Se você deseja manter o repositório upstream com histórico completo , mas com caixas menores locais, faça um clone superficial git clone --depth=1 [repo].

Depois de enviar uma confirmação, você pode fazer

  1. git fetch --depth=1podar os velhos commits. Isso torna os antigos commit e seus objetos inacessíveis.
  2. git reflog expire --expire-unreachable=now --all. Expirar todos os commit antigos e seus objetos
  3. git gc --aggressive --prune=all remover os objetos antigos

Consulte também Como remover o histórico local do git após uma confirmação? .

Observe que você não pode enviar este repositório "superficial" para outro lugar: "atualização superficial não permitida". Consulte Remoto rejeitado (atualização superficial não permitida) após alterar o URL remoto do Git . Se você quiser fazer isso, terá que se ater à enxertia.

koppor
fonte
1
O ponto número 1. fez a diferença para mim. Cheers
clapas
21

Eu precisava ler várias respostas e outras informações para entender o que estava fazendo.

1. Ignore tudo mais antigo que um determinado commit

O arquivo .git/info/graftspode definir pais falsos para uma confirmação. Uma linha com apenas um ID de confirmação diz que o commit não tem um pai. Se quisermos dizer que nos preocupamos apenas com os últimos 2000 confirmados, podemos digitar:

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

O git rev-parse nos fornece o id de confirmação do 2000º pai do commit atual. O comando acima substituirá o arquivo de enxertos, se presente. Verifique se está lá primeiro.

2. Reescreva o histórico do Git (opcional)

Se você quiser tornar esse pai falso enxertado real, execute:

git filter-branch -- --all

Ele mudará todos os IDs de confirmação. Todas as cópias deste repositório precisam ser atualizadas com força.

3. Limpe o espaço em disco

Não fiz o passo 2, porque queria que minha cópia permanecesse compatível com o upstream. Eu só queria economizar espaço em disco. Para esquecer todos os antigos commit:

git prune
git gc

Alternativa: cópias rasas

Se você possui uma cópia superficial de outro repositório e deseja apenas economizar espaço em disco, é possível atualizar .git/shallow. Mas tome cuidado para que nada esteja apontando para um commit de antes. Então você pode executar algo parecido com isto:

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

A entrada em águas rasas funciona como um enxerto. Mas tome cuidado para não usar enxertos e superficiais ao mesmo tempo. Pelo menos, não tem as mesmas entradas, isso irá falhar.

Se você ainda tiver algumas referências antigas (tags, ramificações, cabeças remotas) que apontam para confirmações mais antigas, elas não serão limpas e você não economizará mais espaço em disco.

Maikel
fonte
O suporte para <GIT_DIR> / info / grafts foi descontinuado e será removido em uma versão futura do Git.
danny
Por favor, considere usar git replace. Veja stackoverflow.com/questions/6800692/…
Joel AZEMAR
3

Quando você rebase ou pressiona a cabeça / mestre, este erro pode ocorrer

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 esse problema no painel git, remova a ramificação principal de "Ramificações protegidas"

insira a descrição da imagem aqui

então você pode executar este comando

git push -f origin master

ou

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

Existem muitas respostas aqui que não são atuais e algumas não explicam completamente as consequências. Aqui está o que funcionou para mim ao aparar a história usando o último git 2.26:

Primeiro, crie uma confirmação fictícia. Essa confirmação aparecerá como a primeira confirmação no seu repositório truncado. Você precisa disso porque esse commit manterá todos os arquivos base para o histórico que você está mantendo. O SHA é o ID da confirmação anterior da confirmação que você deseja manter (neste exemplo 8365366). A string 'Initial' aparecerá como mensagem de confirmação do primeiro commit. Se você estiver usando o Windows, digite o comando abaixo no prompt de comando do Git Bash.

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

O comando acima imprimirá SHA, por exemplo d10f7503bc1ec9d367da15b540887730db862023,.

Agora, basta digitar:

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

Isso primeiro colocará todos os arquivos como confirmação 8365366no commit dummy d10f750. Em seguida, ele reproduzirá todas as confirmações após 8365366 por cima d10f750. Finalmentemaster , o ponteiro de ramificação será atualizado para a última confirmação reproduzida.

Agora, se você deseja enviar esses repositórios truncados, basta git push -f .

Lembre-se de algumas coisas (isso se aplica a outros métodos e também a este): As tags não são transferidas. Enquanto os IDs de confirmação e os carimbos de data e hora são preservados, você verá o GitHub mostrando esses commits no cabeçalho do lumpsum Commits on XY date.

Felizmente, é possível manter o histórico truncado como "arquivo morto" e, posteriormente, você pode associar-se ao repositório aparado com o repositório de repositório. Para fazer isso, consulte este guia .

Shital Shah
fonte
-3

você pode excluir o diretório, os arquivos e também todo o histórico relacionado ao diretório ou arquivo usando o jar abaixo mencionado [faça o download] e os comandos

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

git clone --bare repo-url cd repo_dir java -jar bfg.jar --delete-folders folder_name git reflog expirar --expire = now --todos && git gc --prune = now --gitive git push --mirror repo_url

RahulMohan Kolakandy
fonte
-10
  1. remover dados do git, rm .git
  2. git init
  3. adicionar um controle remoto git
  4. empurrão forçado
Brad Reid
fonte
6
que irá trabalhar para remover toda a história, mas não para o que ele pediu: história sustento desde janeiro de 2010
Chris Maes
1
Só queria agradecer, pois me ajudou no meu cenário, mesmo que essa não seja a resposta certa para a pergunta #
apnerve