Como você corrigir uma má impressão em série, e repetir suas boas commits em um merge fixo?
-
08-07-2019 - |
Pergunta
eu acidentalmente cometeu um arquivo não desejado (filename.orig
ao resolver uma mesclagem) para meu repositório vários commits atrás, sem me notar isso até agora. Eu quero apagar completamente o arquivo do histórico do repositório.
É possível reescrever o histórico de alterações de tal forma que filename.orig
nunca mais foi adicionado ao repositório em primeiro lugar?
Solução
Por favor, não use esta receita se a sua situação não é a descrita na questão. Esta receita é para a fixação de um mau merge, e repetindo seus bons commits em um merge fixo.
Embora filter-branch
vai fazer o que quiser, é um comando bastante complexo e eu provavelmente iria escolher para fazer isso com git rebase
. É provavelmente uma preferência pessoal. filter-branch
pode fazê-lo em um único comando, um pouco mais complexo, enquanto que a solução rebase
está realizando o equivalente operações lógicas um passo de cada vez.
Tente o seguinte receita:
# create and check out a temporary branch at the location of the bad merge
git checkout -b tmpfix <sha1-of-merge>
# remove the incorrectly added file
git rm somefile.orig
# commit the amended merge
git commit --amend
# go back to the master branch
git checkout master
# replant the master branch onto the corrected merge
git rebase tmpfix
# delete the temporary branch
git branch -d tmpfix
(Note que você não precisa realmente um ramo temporário, você pode fazer isso com um "CABEÇA campo isolada, mas você precisa tomar nota do ID de cometer gerado pelo passo git commit --amend
a alimentação para o comando git rebase
vez de usar o nome de filial temporário.)
Outras dicas
Intro: Você tem 5 Soluções disponíveis
Os estados cartaz original:
eu acidentalmente cometeu um arquivo não desejado ... para meu repositório vários commits atrás ... Eu quero apagar completamente o arquivo do histórico do repositório.
É possível reescrever o histórico de alterações de tal forma que nunca foi
filename.orig
adicionados ao repositório em primeiro lugar?
Existem muitas maneiras diferentes para remover o histórico de um arquivo completamente do git:
- commits de alteração.
- redefine rígidos (possivelmente mais um REBASE).
- rebase não-interativo.
- rebases Interactive.
- ramos de filtragem.
No caso do cartaz original, que altera a confirmação não é realmente uma opção por si só, uma vez que ele fez vários commits adicionais depois, mas por uma questão de completude, eu também irá explicar como fazê-lo, para qualquer pessoa que Justs quer alterar a sua submissão anterior.
Note-se que todas estas soluções envolvem alterando / re-escrita ??em> História / commits de uma forma de outra, para que qualquer pessoa com cópias antigas dos commits terá que fazer trabalho extra para re-sync sua história com a nova história.
Solução 1: rectificativos Compromete
Se você acidentalmente fez uma mudança (como adicionar um arquivo) no seu anterior cometer, e você não quer que a história de que a mudança de existir mais, em seguida, você pode simplesmente alterar a submissão anterior para remover o arquivo a partir dele:
git rm <file>
git commit --amend --no-edit
Solução 2: Hard Reset (Possivelmente Mais um Rebase)
Como solução # 1, se você quiser apenas para se livrar de sua submissão anterior, então você também tem a opção de simplesmente fazer um hard reset ao seu pai:
git reset --hard HEAD^
Esse comando vai hard-redefinir seu ramo para o st pai anterior 1 cometer.
No entanto , se, como o cartaz original, você fez várias submissões após a cometer você quiser desfazer a alteração para, você ainda pode usar reinicializações para modificá-lo, mas isso também envolve o uso de um rebase. Aqui estão os passos que você pode usar para alterar a comprometer ainda mais para trás na história:
# Create a new branch at the commit you want to amend
git checkout -b temp <commit>
# Amend the commit
git rm <file>
git commit --amend --no-edit
# Rebase your previous branch onto this new commit, starting from the old-commit
git rebase --preserve-merges --onto temp <old-commit> master
# Verify your changes
git diff master@{1}
Solução 3: não interativo Rebase
Isto irá funcionar se você apenas deseja remover um commit da história inteiramente:
# Create a new branch at the parent-commit of the commit that you want to remove
git branch temp <parent-commit>
# Rebase onto the parent-commit, starting from the commit-to-remove
git rebase --preserve-merges --onto temp <commit-to-remove> master
# Or use `-p` insteda of the longer `--preserve-merges`
git rebase -p --onto temp <commit-to-remove> master
# Verify your changes
git diff master@{1}
Solução 4: rebases interativos
Esta solução irá permitir-lhe realizar as mesmas coisas como soluções # 2 e # 3, ou seja, modificar ou remover compromete ainda mais para trás na história do que a sua imediatamente submissão anterior, de modo que solução você optar por usar é uma espécie de até você. rebases interativos não são bem adaptados para rebasing centenas de commits, para motivos de desempenho, então eu usaria rebases não-interativo ou o ramo filtro solução (ver abaixo) na esse tipo de situações.
Para começar o rebase interativo, use o seguinte:
git rebase --interactive <commit-to-amend-or-remove>~
# Or `-i` instead of the longer `--interactive`
git rebase -i <commit-to-amend-or-remove>~
Isto fará com que git para rebobinar a cometer história de volta para o pai do commit que você deseja modificar ou remover. Ela irá então apresentar-lhe uma lista do commits rebobinado na ordem inversa em qualquer git editor está definido para uso (este é Vim por padrão):
pick 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
pick 7668f34 Modify Bash config to use Homebrew recommended PATH
pick 475593a Add global .gitignore file for OS X
pick 1b7f496 Add alias for Dr Java to Bash config (OS X)
O comprometer que deseja modificar ou remover estará no topo desta lista. Para removê-lo, basta apagar a sua linha na lista. Caso contrário, substitua "pegar" com "Editar" no st Linha 1, assim:
edit 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
Em seguida, insira git rebase --continue
. Se você optar por remover a comprometer totalmente,
então que tudo que você precisa fazer (além de verificação, consulte o passo final para
esta solução). Se, por outro lado, você queria modificar a cometer, git, em seguida,
vai reaplicar o cometer e, em seguida, pausar o rebase.
Stopped at 00ddaacab0a85d9989217dd9fe9e1b317ed069ac... Add symlinks
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
Neste ponto, você pode remover o arquivo e alterar a cometer, em seguida, continuar a rebase:
git rm <file>
git commit --amend --no-edit
git rebase --continue
É isso. Como etapa final, se você modacidificado a confirmação ou removido completamente, é sempre uma boa idéia para verificar se há outras mudanças inesperadas foram feitas para o seu ramo por diffing-lo com o seu estado antes do rebase:
git diff master@{1}
Solução 5: Ramos de filtragem
Finalmente, esta solução é melhor se você quiser destruir completamente todos os vestígios de existência de um arquivo de história, e nenhuma das outras soluções são bastante até a tarefa.
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>'
Isso irá remover <file>
de todos os commits, a partir da raiz cometer. E se
em vez disso você só quer reescrever a cometer HEAD~5..HEAD
gama, então você pode
passar isso como um argumento adicional para filter-branch
, como fora apontado em
esta resposta :
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>' HEAD~5..HEAD
Mais uma vez, após o filter-branch
está completo, é geralmente uma boa idéia para verificar
que não há outras mudanças inesperadas por diffing seu ramo com a sua
estado anterior antes da operação de filtragem:
git diff master@{1}
filter-branch Alternativa: BFG Repo Cleaner
Eu ouvi que a BFG Repo Cleaner ferramenta é executada mais rapidamente do que git filter-branch
, assim você pode querer verificar isso como uma opção também. É ainda mencionado oficialmente no filter-branch documentação como uma alternativa viável :
git-filter-branch permite fazer regravações script-shell complexos de sua história Git, mas você provavelmente não precisa essa flexibilidade se você está simplesmente remover dados indesejados como grandes arquivos ou senhas. Para aquelas operações que você pode querer considerar A BFG Repo-Cleaner , um baseado em JVM alternativa para git-filtro-ramo, tipicamente, pelo menos, mais rápido para 10-50x esses casos de uso, e com características bastante diferentes:
Qualquer versão específica de um arquivo for limpo exatamente uma vez . A BFG, ao contrário git-filter-branch, não lhe dá a oportunidade de punho um arquivo de forma diferente dependendo de onde ou em que foi cometida dentro da sua história. Essa restrição dá o benefício de desempenho do núcleo de A BFG, e é bem adequada para a tarefa de limpeza de dados ruins - você não fazer cuidado , onde os dados ruim é, você só quer que gone .
Por padrão A BFG tira o máximo partido das máquinas multi-core, limpeza de cometer arquivo-árvores em paralelo. limpa git-filtro-ramo compromete-se sequencialmente (ou seja, de um modo único de rosca), embora é possível filtros de escrita que incluem seu próprio paralelismo, no scripts executados contra cada commit.
opções de comando são muito mais restritiva do que git branch-filtro, e dedicada apenas para o tarefas de remoção indesejada data- por exemplo:.
--strip-blobs-bigger-than 1M
Recursos adicionais
- Pro Git § 6.4 Git Ferramentas - História Rewriting .
- git-filter-branch (1) manual de página.
- git-commit (1) manual Página .
- git-reset (1) manual de página.
- git-rebase (1) manual de página.
- The Cleaner BFG Repo (ver também this resposta do próprio criador ).
Se você não cometi nada desde, apenas git rm
o arquivo e git commit --amend
.
Se você tiver
git filter-branch \
--index-filter 'git rm --cached --ignore-unmatch path/to/file/filename.orig' merge-point..HEAD
vai passar por cada mudança de merge-point
para HEAD
, filename.orig apagar e reescrever a mudança. Usando --ignore-unmatch
significa que o comando não falhará se por algum motivo filename.orig está faltando uma mudança. Essa é a maneira recomendada a partir da secção Exemplos no git-filter-branch página homem .
Nota para utilizadores de Windows: O caminho do arquivo deve utilizar barras
Esta é a melhor maneira:
http://github.com/guides/completely-remove-a -file-de-todas-as revisões
Apenas certifique-se de backup cópias dos arquivos primeiro.
Editar
A edição por Neon foi infelizmente rejeitado durante a revisão.
Veja Neons post abaixo, ele pode conter informações úteis!
por exemplo. para remover todos os arquivos *.gz
cometido acidentalmente em repositório git:
$ du -sh .git ==> e.g. 100M
$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch *.gz' HEAD
$ git push origin master --force
$ rm -rf .git/refs/original/
$ git reflog expire --expire=now --all
$ git gc --prune=now
$ git gc --aggressive --prune=now
Isso ainda não funcionou para mim? (Eu sou atualmente na versão git 1.7.6.1)
$ du -sh .git ==> e.g. 100M
Não sei por que, desde que eu só tinha um mestre ramo. De qualquer forma, eu finalmente tenho o meu git repo verdadeiramente limpo, empurrando para um novo repositório git vazio e nu, por exemplo.
$ git init --bare /path/to/newcleanrepo.git
$ git push /path/to/newcleanrepo.git master
$ du -sh /path/to/newcleanrepo.git ==> e.g. 5M
(sim!)
clone Então eu que para um novo diretório e movido sobre pasta .git-lo de em um presente. por exemplo.
$ mv .git ../large_dot_git
$ git clone /path/to/newcleanrepo.git ../tmpdir
$ mv ../tmpdir/.git .
$ du -sh .git ==> e.g. 5M
(yeah! Finalmente limpo!)
Depois de verificar que tudo está bem, então você pode excluir os diretórios ../large_dot_git
e ../tmpdir
(talvez em um par de semanas ou meses a partir de agora, apenas no caso ...)
Rewriting Git história novas exigências todos os ids afetadas cometer, e para que todos que está trabalhando no projeto terá de apagar suas antigas cópias do repo, e fazer um clone fresco depois que você limpou a história. Quanto mais as pessoas que inconvenientes, mais você precisa de uma boa razão para fazê-lo - seu arquivo supérfluo não está realmente causando um problema, mas que apenas você estão trabalhando no projeto, você poderia muito bem limpo a história Git se você quiser!
Para torná-lo tão fácil quanto possível, eu recomendo usar o BFG Repo-Cleaner , um mais simples, mais rápido alternativa para git-filter-branch
projetado especificamente para a remoção de arquivos da história Git. Uma maneira em que facilita a sua vida aqui é que ele realmente alças todas refs por padrão (todos os tags, ramos, etc), mas também é 10 - 50x mais rápido
Você deve seguir cuidadosamente os passos aqui: http://rtyley.github.com / bfg-repo-purificador / # uso - mas o bocado de núcleo é apenas isso: o download do BFG jar (requer Java 6 ou acima) e execute este comando:
$ java -jar bfg.jar --delete-files filename.orig my-repo.git
Toda a sua história repositório será digitalizado e qualquer filename.orig
arquivo chamado (que não está em sua mais recente cometer ) serão removidos. Este é consideravelmente mais fácil do que usar git-filter-branch
para fazer a mesma coisa!
A divulgação completa:. Eu sou o autor do BFG Repo-Cleaner
You should probably clone your repository first.
Remove your file from all branches history:
git filter-branch --tree-filter 'rm -f filename.orig' -- --all
Remove your file just from the current branch:
git filter-branch --tree-filter 'rm -f filename.orig' -- --HEAD
Lastly you should run to remove empty commits:
git filter-branch -f --prune-empty -- --all
Só para acrescentar que a solução de Charles Bailey, eu usei apenas um git rebase -i para remover arquivos indesejados de um anterior cometer e funcionou como um encanto. As etapas:
# Pick your commit with 'e'
$ git rebase -i
# Perform as many removes as necessary
$ git rm project/code/file.txt
# amend the commit
$ git commit --amend
# continue with rebase
$ git rebase --continue
A maneira mais simples que eu encontrei foi sugerido por leontalbot
(como um comentário), que é um post publicado por Anoopjohn . Eu acho que vale a pena o seu próprio espaço como uma resposta:
(I converteu-o para um script bash)
#!/bin/bash
if [[ $1 == "" ]]; then
echo "Usage: $0 FILE_OR_DIR [remote]";
echo "FILE_OR_DIR: the file or directory you want to remove from history"
echo "if 'remote' argument is set, it will also push to remote repository."
exit;
fi
FOLDERNAME_OR_FILENAME=$1;
#The important part starts here: ------------------------
git filter-branch -f --index-filter "git rm -rf --cached --ignore-unmatch $FOLDERNAME_OR_FILENAME" -- --all
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now
if [[ $2 == "remote" ]]; then
git push --all --force
fi
echo "Done."
Todos os créditos vai para Annopjohn
, e leontalbot
para apontar isso.
NOTA
Esteja ciente de que o roteiro não inclui validações, tenha certeza que você não cometer erros e que você tem um backup no caso de algo correr mal. Ela trabalhou para mim, mas ele pode não funcionar na sua situação. Use-o com CUIDADO (siga o link se você quer saber o que está acontecendo).
Definitivamente, git filter-branch
é o caminho a percorrer.
Infelizmente, isso não será suficiente para remover completamente filename.orig
do seu repo, pois ele pode ainda ser ser referenciado por tags, entradas reflog, controles remotos e assim por diante.
Eu recomendo remover todas essas referências, bem como, e em seguida, chamar o coletor de lixo. Você pode usar o script git forget-blob
de este site para fazer tudo isso em uma única etapa.
git forget-blob filename.orig
Se é a última cometer você deseja limpar, eu tentei com versão git 2.14.3 (Apple Git-98):
touch empty
git init
git add empty
git commit -m init
# 92K .git
du -hs .git
dd if=/dev/random of=./random bs=1m count=5
git add random
git commit -m mistake
# 5.1M .git
du -hs .git
git reset --hard HEAD^
git reflog expire --expire=now --all
git gc --prune=now
# 92K .git
du -hs .git
Isto é o que git filter-branch
foi projetado para.
Você também pode usar:
git reset HEAD file/path