Listando e excluindo confirmações do Git sem ramificação (dangling?)

146

Eu tenho um repositório Git com muitos commits que não estão sob nenhuma ramificação específica, eu posso git show, mas quando tento listar as ramificações que os contêm, ele não informa nada.

Eu pensei que este é o problema de commit / tree pendente (como resultado da ramificação -D), então eu removi o repo, mas ainda vejo o mesmo comportamento depois disso:

$ git fetch origin

$ git fsck --unreachable
$ git fsck

Sem saída, nada pendente (certo?). Mas o commit existe

$ git show 793db7f272ba4bbdd1e32f14410a52a412667042
commit 793db7f272ba4bbdd1e32f14410a52a412667042
Author: ...

e não é alcançável através de nenhum ramo como

$ git branch --contains 793db7f272ba4bbdd1e32f14410a52a412667042

não dá saída.

Qual é exatamente o estado desse commit? Como posso listar todos os commits em um estado semelhante? Como posso excluir confirmações como essas?

Samer Buna
fonte

Respostas:

75

Sem saída, nada pendente (certo?)

Observe que as confirmações referidas no seu reflog são consideradas acessíveis.

Qual é exatamente o estado desse commit? Como posso listar todos os commits com estado semelhante

Passe --no-reflogspara convencer git fscka mostrá-los a você.

Como posso excluir confirmações como essas?

Depois que suas entradas de reflog expirarem, esses objetos também serão limpos git gc.

A expiração é regulada pelo gc.pruneexpire,gc.reflogexpire e gc.reflogexpireunreachableconfigurações. Cf. git help config.

Os padrões são todos bastante razoáveis.

Aristóteles Pagaltzis
fonte
2
então você está basicamente dizendo que os refluxos de confirmações pendentes serão removidos depois de um tempo automaticamente?
MoralCode 23/11
2
Basicamente: sim - exceto que a pergunta é um pouco confusa. Estou dizendo que todas as entradas de reflog são removidas automaticamente depois de um tempo, mas você pode alterar isso através das definições de configuração. E como um commit é chamado de dangling apenas quando não tem nada a apontar para ele - incluindo entradas de reflog -, “reflogs para commits dangling” não são uma coisa. Eles seriam "reflogs para confirmações inacessíveis ".
Aristóteles Pagaltzis
'Eles seriam "reflogs para commits inacessíveis".' Mas você disse que "confirmações mencionadas no reflog são consideradas acessíveis". Então, como pode "reflogs para confirmações inacessíveis" ser uma coisa? Estou tão confuso.
precisa saber é
1
Sim, eu não era consistente. Normalmente, as pessoas não pensam no reflog e, quando dizem "inacessível", isso implica "de um juiz". Inclusive a git help glossarydefine dessa maneira ... enquanto sua definição de "alcançável" não é reduzida dessa maneira, por isso são contraditórias. Engraçado - então o que eu disse é realmente consistente com a confusão em gitglossary... Não são os conceitos que são confusos, apenas a terminologia. O ponto é que os compromissos "pendentes" são aqueles que nada mais aponta. Ajudaria se eu digo “reflogs para outra forma commits inacessível” ...?
Aristóteles Pagaltzis 10/0318
Isto é tudo muito confuso. Vamos simplificar. Quando está no ramo master, você faz git commite recebe um commit 000001. Então você faz git commit --amend, o que lhe dá confirmação 000002. Não há mais tags ou ramificações apontando para 000001você, e você não pode vê-lo em seu log sem a --reflogopção, mas se desejar, ainda poderá acessá-lo git checkout 000001. Agora, a pergunta é: 000001um commit pendente , ou um commit inacessível , ou nenhum, ou ambos?
chharvey
264

Para remover todos os commits dangling e aqueles alcançáveis ​​dos reflogs, faça o seguinte:

git reflog expire --expire-unreachable=now --all
git gc --prune=now

Mas esteja certo de que é isso que você deseja. Eu recomendo que você leia as páginas de manual, mas aqui está a essência:

git gcremove objetos inacessíveis (confirmações, árvores, blobs (arquivos)). Um objeto está inacessível se não faz parte do histórico de algum ramo. Na verdade, é um pouco mais complicado:

git gc faz algumas outras coisas, mas elas não são relevantes aqui e não são perigosas.

Objetos inacessíveis com menos de duas semanas não são removidos, portanto, usamos --prune=now que significa "remover objetos inacessíveis que foram criados antes agora".

Os objetos também podem ser alcançados através do reflog. Enquanto os ramos registram o histórico de alguns projetos, os reflogs registram o histórico desses ramos. Se você alterar, redefinir etc. as confirmações serão removidas do histórico da ramificação, mas o git as manterá por perto caso você perceba que cometeu um erro. Os registros são uma maneira conveniente de descobrir quais operações destrutivas (e outras) foram executadas em uma ramificação (ou HEAD), facilitando a destruição de uma operação destrutiva.

Portanto, também precisamos remover os reflogs para remover tudo o que não é acessível a partir de um ramo. Fazemos isso expirando --allreflogs. Novamente, o git mantém um pouco dos reflogs para proteger os usuários, então, novamente, temos que dizer para não fazer isso:--expire-unreachable=now .

Como eu uso principalmente o reflog para me recuperar de operações destrutivas, costumo usá-lo --expire=now, o que reduz completamente os reflexos.

tarso
fonte
1
Digo a você quais comandos usar que não são óbvios - o gc não deve ser suficiente? Se você nunca usou o git-reflog antes, não saberá. Portanto, agora que você sabe quais comandos deve usar, consulte as opções mencionadas nas páginas de manual. Claro que, em vez poderia apenas copiar a informação de lá ...
tarsius
1
Bem, na verdade eu digo exatamente o que faz: "remova todos os commits dangling e aqueles alcançáveis ​​dos reflogs". Se você não sabe o que são reflogs: leia novamente o manual.
tarsius
7
Embora a resposta dada possa estar correta, @ erikb85 está correto ao apontar que não houve instrução sobre o que você estava sendo instruído a fazer. O acompanhamento do RTFM é ainda menos útil. Sim, todos devemos ler toda a documentação. Em alguns casos, talvez a pessoa que faz a pesquisa não preencha a documentação o suficiente para saber o que está acontecendo. Portanto, um pouco de educação sobre o que os comandos estão fazendo seria útil para todos que encontrarem essa resposta posteriormente.
Lee Saferite 28/03
@LeeSaferite espero que estejam felizes agora :-)
tarsius
12
git reflog expire --expire-unreachable=now --allderruba todos os seus esconderijos!
Vsevolod Golovanov
22

Eu tive o mesmo problema, ainda depois de seguir todos os conselhos deste tópico:

git reflog expire --expire-unreachable=now --all
git gc --prune=now
git fsck --unreachable --no-reflogs   # no output
git branch -a --contains <commit>     # no output
git show <commit>                     # still shows up

Se não é um reflog e não um ramo, ... deve ser uma tag !

git tag                             # showed several old tags created before the cleanup

Removai as tags git tag -d <tagname>e refiz a limpeza, e os commits antigos haviam desaparecido.

jakub.g
fonte
Já existe uma resposta sobre tags ( stackoverflow.com/a/37335660/450127 ) e não parece que isso adicione algo novo. Isso não deveria ser removido em favor da resposta anterior?
Ian Dunn
De fato, de alguma maneira eu ignorei essa resposta. Embora 4 pessoas tenham achado minha resposta útil, talvez não seja tão inútil? Também agrupei todas as possibilidades em uma resposta concisa.
Jkub.g 10/10
1
Mesmo se duplicados, esta página pode aparecer no Google resultado, e imediatamente ajuda as pessoas com o mesmo problema, melhor do que apenas redirecionando as pessoas uma e outra vez para links que podem ter a resposta correta.
Alexandre T.
14
git branch --contains 793db7f272ba4bbdd1e32f14410a52a412667042

provavelmente só precisa ser

git branch -a --contains 793db7f272ba4bbdd1e32f14410a52a412667042

para também informar sobre ramificações de controles remotos

ver
fonte
obrigado, agora encontrei meus controles remotos / origin / next que ainda mantém esse commit. como removê-lo? git push -d origin nextnão ajuda.
precisa saber é
obrigado - o git fetch --prunefez o truque. mas em todas as respostas, estou faltando uma verificação de tags que fazem referência a esse commit. Ainda não sei como verificar as tags com uma confirmação (removi todas).
IRaS 13/0418
Mas ... isso significa que confirmações acessíveis apenas a partir de ramificações remotas (e sem ramificações locais) são consideradas alcançáveis ​​e, portanto, git fsck --unreachableestá realmente se comunicando através da rede com a remota para descobrir quais confirmações são alcançáveis?
Larsh
1
Respondeu minha própria pergunta ... sim, confirmações que são acessíveis apenas a partir de ramificações remotas (e sem ramificações locais) são consideradas alcançáveis; mas git fsck --unreachablenão precisa se comunicar pela rede com o controle remoto para descobrir quais ramificações remotas contêm quais confirmações. As informações da filial remota são armazenadas localmente, em, por exemplo, .git/refs/remotes/origin(ou em packed-refs).
Larsh
8

Eu tive uma questão semelhante. Eu corri git branch --contains <commit>e ele não retornou nenhuma saída, como na pergunta.

Mas mesmo depois de correr

git reflog expire --expire-unreachable=now --all
git gc --prune=now

meu commit ainda estava acessível usando git show <commit>. Isso ocorreu porque um dos commits em seu "branch" desanexado / danificado foi marcado. Tirei a etiqueta, executei os comandos acima novamente e fiquei com o ouro. git show <commit>retornou fatal: bad object <commit>- exatamente o que eu precisava. Espero que isso ajude alguém que estava tão preso quanto eu.

Andrew Larsson
fonte
como você removeu a tag?
bapors
@bapors Liste todas as tags, encontre a que faz referência ao commit em questão e exclua-o. stackoverflow.com/questions/5480258/…
Andrew Larsson
4

Acidentalmente, acertei a mesma situação e constatei que meus stashes contêm referência ao commit inacessível e, portanto, o commit presumível inacessível estava acessível a partir dos stashes.

Foi isso que eu fiz para torná-lo verdadeiramente inacessível.

git stash clear
git reflog expire --expire-unreachable=now --all
git fsck --unreachable
git gc --prune=now
Lei Zhao
fonte
2

git gc --prune=<date>o padrão é remover objetos com mais de duas semanas atrás. Você pode definir uma data mais recente. Porém, os comandos git que criam objetos soltos geralmente executam git gc --auto (que remove objetos soltos se seu número exceder o valor da variável de configuração gc.auto).

Tem certeza de que deseja excluir essas confirmações? A configuração padrão do gc.auto garantirá que os objetos soltos não ocupem uma quantidade razoável de memória, e armazenar objetos soltos por um certo período de tempo geralmente é uma boa idéia. Dessa forma, se você perceber amanhã que seu ramo excluído continha uma confirmação necessária, poderá recuperá-lo.

Dublin
fonte