Desanexar (mover) o subdiretório em um repositório Git separado


1758

Eu tenho um repositório Git que contém um número de subdiretórios. Agora, descobri que um dos subdiretórios não está relacionado ao outro e deve ser desanexado para um repositório separado.

Como posso fazer isso mantendo o histórico dos arquivos no subdiretório?

Acho que eu poderia criar um clone e remover as partes indesejadas de cada clone, mas suponho que isso me daria a árvore completa ao verificar uma revisão mais antiga etc. Isso pode ser aceitável, mas eu preferiria fingir que o dois repositórios não têm um histórico compartilhado.

Só para esclarecer, tenho a seguinte estrutura:

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

Mas eu gostaria disso:

XYZ/
    .git/
    XY1/
    XY2/
ABC/
    .git/
    ABC/

7
Isso é trivial agora, com a git filter-branchminha resposta abaixo.
precisa saber é o seguinte

8
@jeremyjjbrown está certo. Isso não é mais difícil de fazer, mas é difícil encontrar a resposta certa no Google, porque todas as respostas antigas dominam os resultados.
Agnel Kurian

Respostas:


1228

Atualização : esse processo é tão comum que a equipe do git tornou muito mais simples com uma nova ferramenta git subtree,. Veja aqui: Desanexar (mover) o subdiretório em um repositório Git separado


Você deseja clonar seu repositório e depois usá-lo git filter-branchpara marcar tudo, exceto o subdiretório que você deseja que seu novo repositório seja coletado como lixo.

  1. Para clonar seu repositório local:

    git clone /XYZ /ABC
    

    (Nota: o repositório será clonado usando links físicos, mas isso não é um problema, pois os arquivos vinculados não serão modificados por si mesmos - novos serão criados.)

  2. Agora, vamos preservar os ramos interessantes que também queremos reescrever e remover a origem para evitar avançar até lá e garantir que os antigos commit não sejam referenciados pela origem:

    cd /ABC
    for i in branch1 br2 br3; do git branch -t $i origin/$i; done
    git remote rm origin
    

    ou para todas as ramificações remotas:

    cd /ABC
    for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
    git remote rm origin
    
  3. Agora você também pode remover tags que não têm relação com o subprojeto; você também pode fazer isso mais tarde, mas pode precisar podar seu repo novamente. Eu não fiz isso e recebi um WARNING: Ref 'refs/tags/v0.1' is unchangedpara todas as tags (uma vez que não estavam relacionadas ao subprojeto); Além disso, após a remoção dessas tags, mais espaço será recuperado. Aparentemente, git filter-branchdeve ser possível reescrever outras tags, mas não foi possível verificar isso. Se você deseja remover todas as tags, use git tag -l | xargs git tag -d.

  4. Em seguida, use filter-branch e redefina para excluir os outros arquivos, para que possam ser removidos. Vamos adicionar também --tag-name-filter cat --prune-emptypara remover confirmações vazias e reescrever tags (observe que isso terá que retirar sua assinatura):

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC -- --all
    

    ou, como alternativa, reescrever apenas a ramificação HEAD e ignorar tags e outras ramificações:

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
  5. Em seguida, exclua os reflogs de backup para que o espaço possa ser realmente recuperado (embora agora a operação seja destrutiva)

    git reset --hard
    git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
    git reflog expire --expire=now --all
    git gc --aggressive --prune=now
    

    e agora você tem um repositório git local do subdiretório ABC com todo o histórico preservado.

Nota: Para a maioria dos usos, git filter-branchrealmente deve ter o parâmetro adicionado -- --all. Sim, é mesmo --space-- all. Esse precisa ser o último parâmetro para o comando. Como Matli descobriu, isso mantém os ramos e as tags do projeto incluídos no novo repositório.

Edit: várias sugestões dos comentários abaixo foram incorporadas para garantir, por exemplo, que o repositório esteja realmente reduzido (o que nem sempre era o caso antes).


29
Resposta muito boa. Obrigado! E para realmente obter exatamente o que eu queria, adicionei "- --all" ao comando filter-branch.
matli 12/12/08

12
Por que você precisa --no-hardlinks? A remoção de um hardlink não afetará o outro arquivo. Objetos Git também são imutáveis. Somente se você alterar as permissões de proprietário / arquivo, precisará --no-hardlinks.
Vdboor

67
Uma etapa adicional que eu recomendaria seria "git remote rm origin". Isso impediria que os push retornassem ao repositório original, se não me engano.
Tom

13
Outro comando ao qual acrescentar filter-branché --prune-emptyremover as confirmações agora vazias.
Seth Johnson

8
Como Paul, eu não queria tags de projeto em meu novo repositório, então não usei -- --all. Eu também corri git remote rm origin, e git tag -l | xargs git tag -dantes do git filter-branchcomando. Isso reduziu meu .gitdiretório de 60M para ~ 300K. Observe que eu precisava executar esses dois comandos para obter a redução de tamanho.
precisa

1321

The Easy Way ™

Acontece que essa é uma prática tão comum e útil que os senhores superiores do Git tornaram muito fácil, mas você precisa ter uma versão mais recente do Git (> = 1.7.11 maio de 2012). Veja o apêndice para saber como instalar o Git mais recente. Além disso, há um exemplo do mundo real na explicação abaixo.

  1. Prepare o repo antigo

    cd <big-repo>
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    

    Nota: <name-of-folder> NÃO deve conter caracteres iniciais ou finais. Por exemplo, a pasta denominada subprojectDEVE ser passada como subproject, NÃO./subproject/

    Nota para usuários do Windows: quando a profundidade da pasta for> 1, <name-of-folder>deve haver * separador de pastas no estilo nix (/). Por exemplo, a pasta denominada path1\path2\subprojectDEVE ser passada comopath1/path2/subproject

  2. Crie o novo repositório

    mkdir ~/<new-repo> && cd ~/<new-repo>
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Vincule o novo repositório ao GitHub ou a qualquer outro lugar

    git remote add origin <git@github.com:user/new-repo.git>
    git push -u origin master
    
  4. Limpeza interna <big-repo>, se desejado

    git rm -rf <name-of-folder>
    

    Nota : Isso deixa todas as referências históricas no repositório. Consulte o Apêndice abaixo se você estiver realmente preocupado em confirmar uma senha ou se precisar diminuir o tamanho do arquivo da sua .gitpasta.

...

Passo a passo

Estas são as mesmas etapas acima , mas seguindo as etapas exatas para o meu repositório em vez de usar <meta-named-things>.

Aqui está um projeto que tenho para implementar os módulos do navegador JavaScript no nó:

tree ~/node-browser-compat

node-browser-compat
├── ArrayBuffer
├── Audio
├── Blob
├── FormData
├── atob
├── btoa
├── location
└── navigator

Quero dividir uma única pasta btoa,, em um repositório Git separado

cd ~/node-browser-compat/
git subtree split -P btoa -b btoa-only

Agora tenho uma nova ramificação,, btoa-onlyque só tem confirmações btoae quero criar um novo repositório.

mkdir ~/btoa/ && cd ~/btoa/
git init
git pull ~/node-browser-compat btoa-only

Em seguida, crio um novo repositório no GitHub ou Bitbucket, ou qualquer outra coisa e adiciono-o como origin

git remote add origin git@github.com:node-browser-compat/btoa.git
git push -u origin master

Dia feliz!

Nota: Se você criou um repositório com a README.md, .gitignoree LICENSE, precisará puxar primeiro:

git pull origin master
git push origin master

Por fim, desejarei remover a pasta do repositório maior

git rm -rf btoa

...

Apêndice

Git mais recente no macOS

Para obter a versão mais recente do Git usando o Homebrew :

brew install git

Últimas Git no Ubuntu

sudo apt-get update
sudo apt-get install git
git --version

Se isso não funcionar (você tem uma versão muito antiga do Ubuntu), tente

sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git

Se isso ainda não funcionar, tente

sudo chmod +x /usr/share/doc/git/contrib/subtree/git-subtree.sh
sudo ln -s \
/usr/share/doc/git/contrib/subtree/git-subtree.sh \
/usr/lib/git-core/git-subtree

Obrigado a rui.araujo pelos comentários.

Limpando seu histórico

Por padrão, remover arquivos do Git na verdade não os remove, apenas confirma que eles não estão mais lá. Se você realmente deseja remover as referências históricas (por exemplo, uma senha confirmada), é necessário fazer o seguinte:

git filter-branch --prune-empty --tree-filter 'rm -rf <name-of-folder>' HEAD

Depois disso, você pode verificar se seu arquivo ou pasta não aparece mais no histórico do Git

git log -- <name-of-folder> # should show nothing

No entanto, você não pode "empurrar" exclusões para o GitHub e similares. Se você tentar, receberá um erro e precisará fazê- git pulllo antes de poder git push- e voltará a ter tudo no seu histórico.

Portanto, se você deseja excluir o histórico da "origem" - ou seja, para excluí-lo do GitHub, Bitbucket, etc -, será necessário excluir o repositório e reenviar uma cópia removida do repositório. Mas espere - há mais ! - Se você estiver realmente preocupado em se livrar de uma senha ou algo parecido, precisará remover o backup (veja abaixo).

Fazendo .gitmenor

O comando delete history mencionado acima ainda deixa para trás um monte de arquivos de backup - porque o Git é muito gentil em ajudá-lo a não arruinar seu repositório por acidente. Eventualmente, ele excluirá arquivos órfãos ao longo dos dias e meses, mas os deixará lá por um tempo, caso você perceba que excluiu acidentalmente algo que não queria.

Portanto, se você realmente quiser esvaziar o lixo para reduzir o tamanho do clone de um repositório imediatamente, precisará fazer todas essas coisas realmente estranhas:

rm -rf .git/refs/original/ && \
git reflog expire --all && \
git gc --aggressive --prune=now

git reflog expire --all --expire-unreachable=0
git repack -A -d
git prune

Dito isso, eu recomendaria não executar essas etapas, a menos que você saiba que precisa - apenas no caso de remover o subdiretório errado, sabe? Os arquivos de backup não devem ser clonados quando você envia o repositório, eles estarão apenas na sua cópia local.

Crédito


16
git subtreeainda faz parte da pasta 'contrib' e não está instalado por padrão em todas as distros. github.com/git/git/blob/master/contrib/subtree
onionjake

11
@krlmlr sudo chmod + x /usr/share/doc/git/contrib/subtree/git-subtree.sh sudo ln -s /usr/share/doc/git/contrib/subtree/git-subtree.sh / usr / lib / git-core / git-subtree Para ativar no Ubuntu 13.04
rui.araujo

41
Se você colocou uma senha em um repositório público, altere a senha, não tente removê-la do repositório público e espere que ninguém a tenha visto.
Route de milhas

8
Esta solução não preserva a história.
Cœur

18
O popde pushdcomando make este em vez implícita e mais difícil de grok o que pretende fazer ...
jones77

133

A resposta de Paul cria um novo repositório contendo / ABC, mas não remove / ABC de dentro de / XYZ. O comando a seguir removerá / ABC de / XYZ:

git filter-branch --tree-filter "rm -rf ABC" --prune-empty HEAD

Obviamente, teste-o primeiro no repositório 'clone --no-hardlinks' e siga-o com os comandos reset, gc and prune que Paul lista.


53
faça isso git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch ABC" --prune-empty HEADe será muito mais rápido. O filtro de índice funciona no índice, enquanto o filtro de árvore precisa fazer o checkout e preparar tudo para cada confirmação .
fmarc 17/09/09

51
em alguns casos, atrapalhar a história do repositório XYZ é um exagero ... apenas um simples "rm -rf ABC; git rm -r ABC; git commit -m'extraído ABC em seu próprio repositório" funcionaria melhor para a maioria das pessoas.
Evgeny

2
Você provavelmente deseja usar -f (force) neste comando se fizer isso mais de uma vez, por exemplo, para remover dois diretórios depois que eles foram separados. Caso contrário, você receberá "Não é possível criar um novo backup".
Brian Carlton

4
Se você estiver usando o --index-filtermétodo, convém fazer isso git rm -q -r -f, para que cada invocação não imprima uma linha para cada arquivo excluído.
Eric Naeseth

1
Eu sugeriria editar a resposta de Paulo, apenas porque a de Paulo é tão completa.
Erik Aronesty

96

Descobri que, para excluir corretamente o histórico antigo do novo repositório, você precisa fazer um pouco mais de trabalho após a filter-branchetapa.

  1. Faça o clone e o filtro:

    git clone --no-hardlinks foo bar; cd bar
    git filter-branch --subdirectory-filter subdir/you/want
    
  2. Remova todas as referências ao histórico antigo. "Origin" estava acompanhando seu clone e "original" é onde o ramo de filtro salva o material antigo:

    git remote rm origin
    git update-ref -d refs/original/refs/heads/master
    git reflog expire --expire=now --all
    
  3. Mesmo agora, seu histórico pode estar preso em um pacote que o fsck não toca. Rasgue-o em pedaços, criando um novo pacote e excluindo os objetos não utilizados:

    git repack -ad
    

uma explicação disso no manual para filtro-ramificação .


3
Acho que git gc --aggressive --prune=nowainda falta alguma coisa , não é?
Albert

1
@ Albert O comando repack cuida disso, e não haveria objetos soltos.
21712 Josh Lee

sim, git gc --aggressive --prune=nowreduziu muito do novo repo
Tomek Wyderka

Simples e elegante. Obrigado!
Marco Pelegrini

40

Editar: script Bash adicionado.

As respostas dadas aqui funcionaram apenas parcialmente para mim; Muitos arquivos grandes permaneceram no cache. O que finalmente funcionou (depois de horas no #git no freenode):

git clone --no-hardlinks file:///SOURCE /tmp/blubb
cd blubb
git filter-branch --subdirectory-filter ./PATH_TO_EXTRACT  --prune-empty --tag-name-filter cat -- --all
git clone file:///tmp/blubb/ /tmp/blooh
cd /tmp/blooh
git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

Com as soluções anteriores, o tamanho do repositório era de cerca de 100 MB. Este reduziu para 1,7 MB. Talvez ajude alguém :)


O seguinte script bash automatiza a tarefa:

!/bin/bash

if (( $# < 3 ))
then
    echo "Usage:   $0 </path/to/repo/> <directory/to/extract/> <newName>"
    echo
    echo "Example: $0 /Projects/42.git first/answer/ firstAnswer"
    exit 1
fi


clone=/tmp/${3}Clone
newN=/tmp/${3}

git clone --no-hardlinks file://$1 ${clone}
cd ${clone}

git filter-branch --subdirectory-filter $2  --prune-empty --tag-name-filter cat -- --all

git clone file://${clone} ${newN}
cd ${newN}

git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

26

Isso não é mais tão complexo que você pode simplesmente usar o comando git filter-branch em um clone de seu repositório para selecionar os subdiretórios que você não deseja e depois enviar para o novo controle remoto.

git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .

3
Isso funcionou como um encanto. YOUR_SUBDIR no exemplo acima é o subdiretório que você deseja manter, tudo o resto vai ser removido
JT Taylor

1
Atualizações baseadas em seu comentário.
precisa saber é o seguinte

2
Isso não responde à pergunta. A partir dos documentos que diz The result will contain that directory (and only that) as its project root.e, de fato, é isso que você obterá, ou seja, a estrutura original do projeto não é preservada.
precisa saber é o seguinte

2
@NicBright Você pode ilustrar seu problema com XYZ e ABC, como na pergunta, para mostrar o que há de errado?
26417 Adam

@jeremyjjbrown é possível reutilizar o repo clonado e não usar um novo repo, ou seja, a minha pergunta aqui stackoverflow.com/questions/49269602/...
Qiulang

19

Atualização : O módulo git-subtree foi tão útil que a equipe do git o colocou no núcleo e o criou git subtree. Veja aqui: Desanexar (mover) o subdiretório em um repositório Git separado

git-subtree pode ser útil para isso

http://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt (descontinuado)

http://psionides.jogger.pl/2010/02/04/sharing-code-between-projects-with-git-subtree/


1
O git-subtree agora faz parte do Git, embora esteja na árvore de contribuição, portanto nem sempre é instalado por padrão. Eu sei que ele é instalado pela fórmula Homebrew git, mas sem sua página de manual. apenwarr chama assim sua versão de obsoleta.
Echristopherson

19

Aqui está uma pequena modificação para CoolAJ86 's 'A maneira fácil ™' resposta , a fim de dividir várias sub-pastas (digamos que sub1esub2 ) em um novo repositório git.

The Easy Way ™ (várias subpastas)

  1. Prepare o repo antigo

    pushd <big-repo>
    git filter-branch --tree-filter "mkdir <name-of-folder>; mv <sub1> <sub2> <name-of-folder>/" HEAD
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    popd
    

    Nota: <name-of-folder> NÃO deve conter caracteres iniciais ou finais. Por exemplo, a pasta denominada subprojectDEVE ser passada como subproject, NÃO./subproject/

    Nota para usuários do Windows: quando a profundidade da pasta for> 1, <name-of-folder>deve haver * separador de pastas no estilo nix (/). Por exemplo, a pasta denominada path1\path2\subprojectDEVE ser passada como path1/path2/subproject. Além disso, não use o mvcomando, masmove .

    Nota final: a única e grande diferença com a resposta base é a segunda linha do script " git filter-branch..."

  2. Crie o novo repositório

    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Vincule o novo repositório ao Github ou a qualquer outro lugar

    git remote add origin <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
  4. Limpeza, se desejado

    popd # get out of <new-repo>
    pushd <big-repo>
    
    git rm -rf <name-of-folder>
    

    Nota : Isso deixa todas as referências históricas no repositório. Consulte o Apêndice na resposta original se você estiver realmente preocupado em confirmar uma senha ou se precisar diminuir o tamanho do arquivo da sua .gitpasta.


1
Isso funcionou para mim com pequenas modificações. Porque meus sub1e sub2pastas não existir com a versão inicial, tive de modificar meu --tree-filterscript como segue: "mkdir <name-of-folder>; if [ -d sub1 ]; then mv <sub1> <name-of-folder>/; fi". Para o segundo filter-branchcomando, substituí <sub1> por <sub2>, omiti a criação de <name-of-folder> e incluí -fdepois filter-branchpara substituir o aviso de um backup existente.
pglezen

Isso não funciona se algum dos subdiretórios tiver sido alterado durante o histórico no git. como isso pode ser resolvido?
Nietras 3/03

@nietras veja a resposta de rogerdpack. Levei um tempo para encontrá-lo depois de ler e absorver todas as informações nessas outras respostas.
30717 Adam

12

A pergunta original quer que XYZ / ABC / (* arquivos) se torne ABC / ABC / (* arquivos). Depois de implementar a resposta aceita para meu próprio código, notei que ele realmente altera XYZ / ABC / (* arquivos) para ABC / (* arquivos). A página do manual filter-branch até diz:

O resultado conterá esse diretório (e somente isso) como raiz do projeto . "

Em outras palavras, promove a pasta de nível superior "acima" de um nível. Essa é uma distinção importante porque, por exemplo, na minha história eu havia renomeado uma pasta de nível superior. Ao promover pastas "para cima" em um nível, o git perde a continuidade no commit onde eu renomeei.

Perdi contiuity após filtro-ramo

Minha resposta para a pergunta é fazer 2 cópias do repositório e excluir manualmente as pastas que você deseja manter em cada uma. A página de manual me apóia com isso:

[...] evite usar [este comando] se uma única confirmação simples for suficiente para corrigir seu problema


1
Eu gosto do estilo desse gráfico. Posso perguntar qual ferramenta você está usando?
precisa

3
Torre para Mac. Eu realmente gosto. Quase vale a pena mudar para o Mac por si só.
MM.

2
Sim, embora no meu caso, minha subpasta targetdirtenha sido renomeada em algum momento e git filter-branchsimplesmente encerrada, excluindo todos os commits feitos antes da renomeação! Chocante, considerando o quão hábil no Git é rastrear essas coisas e até mesmo a migração de partes de conteúdo individuais!
Jay Allen

1
Ah, também, se alguém se encontrar no mesmo barco, aqui está o comando que eu usei. Não se esqueça que git rmleva vários argumentos, então não há nenhuma razão para executá-lo para cada arquivo / pasta: BYEBYE="dir/subdir2 dir2 file1 dir/file2"; git filter-branch -f --index-filter "git rm -q -r -f --cached --ignore-unmatch $BYEBYE" --prune-empty -- --all
Jay Allen

7

Para adicionar à resposta de Paulo , descobri que, para recuperar o espaço, preciso enviar o HEAD para um repositório limpo e reduzir o tamanho do diretório .git / objects / pack.

ie

$ mkdir ... ABC.git
$ cd ... ABC.git
$ git init --bare

Após a remoção do gc, faça também:

$ git push ... ABC.git HEAD

Então você pode fazer

$ git clone ... ABC.git

e o tamanho do ABC / .git é reduzido

Na verdade, algumas das etapas demoradas (por exemplo, git gc) não são necessárias com o push para limpar o repositório, ou seja:

$ git clone --no-hardlinks / XYZ / ABC
$ git filter-branch --subdirectory-filter ABC HEAD
$ git reset --hard
$ git push ... ABC.git HEAD

6

A maneira correta agora é a seguinte:

git filter-branch --prune-empty --subdirectory-filter FOLDER_NAME [first_branch] [another_branch]

Agora o GitHub ainda tem um pequeno artigo sobre esses casos.

Mas lembre-se de clonar seu repositório original primeiro para separar o diretório (pois isso excluiria todos os arquivos e outros diretórios e você provavelmente precisará trabalhar com eles).

Portanto, seu algoritmo deve ser:

  1. clonar seu repositório remoto para outro diretório
  2. usando git filter-branchapenas arquivos à esquerda sob algum subdiretório, pressione para o novo controle remoto
  3. crie commit para remover este subdiretório do seu repositório remoto original

6

Parece que a maioria (todas?) Das respostas aqui se baseiam em alguma forma git filter-branch --subdirectory-filtere no seu tipo. Isso pode funcionar "na maioria das vezes", no entanto, em alguns casos, por exemplo, quando você renomeou a pasta, por exemplo:

 ABC/
    /move_this_dir # did some work here, then renamed it to

ABC/
    /move_this_dir_renamed

Se você usar um estilo de filtro git normal para extrair "move_me_renamed", perderá o histórico de alterações no arquivo que ocorreu de volta quando era move_this_dir ( ref ).

Parece, portanto, que a única maneira de realmente manter todo o histórico de alterações (se o seu for um caso como esse) é, em essência, copiar o repositório (criar um novo repositório, definir isso como a origem) e, em seguida, destruir tudo o mais e renomeie o subdiretório para o pai desta maneira:

  1. Clonar o projeto de vários módulos localmente
  2. Ramos - verifique o que está lá: git branch -a
  3. Faça um check-out em cada filial para ser incluído na divisão para obter uma cópia local em sua estação de trabalho: git checkout --track origin/branchABC
  4. Faça uma cópia em um novo diretório: cp -r oldmultimod simple
  5. Vá para a nova cópia do projeto: cd simple
  6. Livre-se dos outros módulos que não são necessários neste projeto:
  7. git rm otherModule1 other2 other3
  8. Agora, apenas o subdiretório do módulo de destino permanece
  9. Livre-se do subdiretório do módulo para que a raiz do módulo se torne a nova raiz do projeto
  10. git mv moduleSubdir1/* .
  11. Exclua o subdiretório da relíquia: rmdir moduleSubdir1
  12. Verifique as alterações a qualquer momento: git status
  13. Crie o novo repositório git e copie sua URL para apontar este projeto para ele:
  14. git remote set-url origin http://mygithost:8080/git/our-splitted-module-repo
  15. Verifique se isso é bom: git remote -v
  16. Envie as alterações até o repositório remoto: git push
  17. Vá para o repositório remoto e verifique se está tudo lá
  18. Repita-o para qualquer outro ramo necessário: git checkout branch2

A seguir, o documento do github "Dividindo uma subpasta em um novo repositório" etapas 6 a 11 para enviar o módulo para um novo .

Isso não poupará espaço na pasta .git, mas preservará todo o histórico de alterações desses arquivos, mesmo com as renomeações. E isso pode não valer a pena se não houver "muita" história perdida, etc. Mas pelo menos você tem a garantia de não perder compromissos antigos!


1
Encontrei a agulha no palheiro! Agora eu posso manter TODO meu histórico de consolidação.
30717 Adam

5

Eu recomendo o guia do GitHub para dividir subpastas em um novo repositório . Os passos são semelhantes à resposta de Paulo , mas achei as instruções mais fáceis de entender.

Modifiquei as instruções para que elas se apliquem a um repositório local, em vez de um hospedado no GitHub.


Dividindo uma subpasta em um novo repositório

  1. Abra o Git Bash.

  2. Altere o diretório de trabalho atual para o local em que deseja criar seu novo repositório.

  3. Clone o repositório que contém a subpasta.

git clone OLD-REPOSITORY-FOLDER NEW-REPOSITORY-FOLDER
  1. Altere o diretório de trabalho atual para seu repositório clonado.

cd REPOSITORY-NAME
  1. Para filtrar a subpasta do restante dos arquivos no repositório, execute git filter-branch, fornecendo estas informações:
    • FOLDER-NAME: A pasta do seu projeto da qual você deseja criar um repositório separado.
      • Dica: os usuários do Windows devem usar /para delimitar pastas.
    • BRANCH-NAME: A ramificação padrão para o seu projeto atual, por exemplo, masterou gh-pages.

git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME  BRANCH-NAME 
# Filter the specified branch in your directory and remove empty commits
Rewrite 48dc599c80e20527ed902928085e7861e6b3cbe6 (89/89)
Ref 'refs/heads/BRANCH-NAME' was rewritten

Boa postagem, mas noto que o primeiro parágrafo do documento que você vinculou diz: If you create a new clone of the repository, you won't lose any of your Git history or changes when you split a folder into a separate repository.Ainda de acordo com os comentários de todas as respostas aqui, filter-brancho subtreescript resulta na perda de histórico onde quer que um subdiretório tenha sido renomeado. Existe algo que possa ser feito para resolver isso?
Adam

Encontrei a solução para preservar todas as confirmações, incluindo as renomeações / mudanças de diretório anteriores - é a resposta do rogerdpack para essa pergunta.
Adam

O único problema é que eu não posso usar o clonado repo mais
Qiulang

5

Ao rodar git filter-branchusando uma versão mais recente do git( 2.22+talvez?), Ele diz para usar esta nova ferramenta git-filter-repo . Essa ferramenta certamente simplificou as coisas para mim.

Filtrando com filtro-repositório

Comandos para criar o XYZrepositório a partir da pergunta original:

# create local clone of original repo in directory XYZ
tmp $ git clone git@github.com:user/original.git XYZ

# switch to working in XYZ
tmp $ cd XYZ

# keep subdirectories XY1 and XY2 (dropping ABC)
XYZ $ git filter-repo --path XY1 --path XY2

# note: original remote origin was dropped
# (protecting against accidental pushes overwriting original repo data)

# XYZ $ ls -1
# XY1
# XY2

# XYZ $ git log --oneline
# last commit modifying ./XY1 or ./XY2
# first commit modifying ./XY1 or ./XY2

# point at new hosted, dedicated repo
XYZ $ git remote add origin git@github.com:user/XYZ.git

# push (and track) remote master
XYZ $ git push -u origin master

suposições: * o repo XYZ remoto era novo e vazio antes do envio

Filtragem e movimentação

No meu caso, eu também queria mover alguns diretórios para uma estrutura mais consistente. Inicialmente, executei esse filter-repocomando simples seguido de git mv dir-to-rename, mas descobri que poderia obter um histórico um pouco "melhor" usando a --path-renameopção Em vez de ver a última modificação 5 hours agonos arquivos movidos no novo repositório, agora vejo last year(na interface do usuário do GitHub), que corresponde aos horários modificados no repositório original.

Ao invés de...

git filter-repo --path XY1 --path XY2 --path inconsistent
git mv inconsistent XY3  # which updates last modification time

Eu finalmente corri ...

git filter-repo --path XY1 --path XY2 --path inconsistent --path-rename inconsistent:XY3
Notas:
  • Eu pensei que a postagem no blog Git Rev News explicava bem o motivo por trás da criação de mais uma ferramenta de repo-filtering.
  • Inicialmente, tentei o caminho de criar um subdiretório correspondente ao nome do repositório de destino no repositório original e depois filtrar (usando git filter-repo --subdirectory-filter dir-matching-new-repo-name). Esse comando converteu corretamente esse subdiretório na raiz do repositório local copiado, mas também resultou em um histórico de apenas as três confirmações necessárias para criar o subdiretório. (Eu não tinha percebido que isso --pathpodia ser especificado várias vezes; portanto, evitando a necessidade de criar um subdiretório no repositório de origem.) Como alguém havia se comprometido com o repositório de origem no momento em que percebi que não havia conseguido levar adiante o histórico, eu apenas usei git reset commit-before-subdir-move --hardapós o clonecomando e adicionei --forceaofilter-repo comando para fazê-lo operar no clone local ligeiramente modificado.
git clone ...
git reset HEAD~7 --hard      # roll back before mistake
git filter-repo ... --force  # tell filter-repo the alterations are expected
  • Fiquei perplexo com a instalação porque não conhecia o padrão de extensão git, mas no final das contas eu clonei o git-filter-repo e o vinculei a $(git --exec-path):
ln -s ~/github/newren/git-filter-repo/git-filter-repo $(git --exec-path)

1
Promovido o voto por recomendar a nova filter-repoferramenta (que apresentei no mês passado em stackoverflow.com/a/58251653/6309 )
VonC

O uso git-filter-repodefinitivamente deve ser a abordagem preferida neste momento. É muito, muito mais rápido e mais seguro do que git-filter-branch, e as salvaguardas contra muitas das dicas que podemos encontrar ao reescrever a história do Git. Esperamos que esta resposta receba mais atenção, já que é a única a ser abordada git-filter-repo.
Jeremy Caney

4

Eu tinha exatamente esse problema, mas todas as soluções padrão baseadas no ramo de filtro git eram extremamente lentas. Se você tem um pequeno repositório, isso pode não ser um problema, foi para mim. Escrevi outro programa de filtragem git baseado no libgit2 que, como primeiro passo, cria ramificações para cada filtragem do repositório primário e as envia para limpar repositórios como a próxima etapa. No meu repositório (500Mb 100000 confirma) os métodos padrão de ramificação de filtro git levavam dias. Meu programa leva alguns minutos para fazer a mesma filtragem.

Tem o nome fabuloso de git_filter e mora aqui:

https://github.com/slobobaby/git_filter

no GitHub.

Espero que seja útil para alguém.


4

Use este comando de filtro para remover um subdiretório, preservando suas tags e ramificações:

git filter-branch --index-filter \
"git rm -r -f --cached --ignore-unmatch DIR" --prune-empty \
--tag-name-filter cat -- --all

o que é gato aqui?
Rogerdpack 19/09/16

4

Para o que vale a pena, aqui está como usar o GitHub em uma máquina Windows. Digamos que você tenha um repositório clonado em residir em C:\dir1. A estrutura de diretórios parecida com esta: C:\dir1\dir2\dir3. O dir3diretório é o que eu quero ser um novo repositório separado.

Github:

  1. Crie seu novo repositório: MyTeam/mynewrepo

Prompt do Bash:

  1. $ cd c:/Dir1
  2. $ git filter-branch --prune-empty --subdirectory-filter dir2/dir3 HEAD
    Retornado: Ref 'refs/heads/master' was rewritten(fyi: dir2 / dir3 faz distinção entre maiúsculas e minúsculas.)

  3. $ git remote add some_name git@github.com:MyTeam/mynewrepo.git
    git remote add origin etc. não funcionou, retornou " remote origin already exists"

  4. $ git push --progress some_name master


3

Como mencionei acima , eu tive que usar a solução reversa (excluir todos os commits que não tocassem no meu dir/subdir/targetdir), que parecia funcionar muito bem, removendo cerca de 95% dos commits (conforme desejado). Há, no entanto, dois pequenos problemas restantes.

PRIMEIRO , filter-branchfez um excelente trabalho ao remover confirmações que introduzem ou modificam código, mas aparentemente as confirmações de mesclagem estão abaixo de sua estação no Gitiverse.

Esta é uma questão cosmética com a qual eu provavelmente posso viver (ele diz ... recuando lentamente com os olhos desviados) .

SEGUNDO, os poucos commits remanescentes são praticamente TODOS duplicados! Parece que adquiri uma segunda linha do tempo redundante que abrange quase toda a história do projeto. O interessante (que você pode ver na foto abaixo) é que meus três ramos locais não estão todos na mesma linha do tempo (que é, certamente, por que ele existe e não é apenas lixo coletado).

A única coisa que posso imaginar é que uma das confirmações excluídas foi, talvez, a confirmação de mesclagem única que filter-branch realmente foi excluída e que criou a linha do tempo paralela à medida que cada vertente agora imersa tirava sua própria cópia das confirmações. ( encolhe os ombros Onde está o meu TARDiS?) Tenho certeza de que posso resolver esse problema, embora eu realmente amo a entender como isso aconteceu.

No caso do louco mergefest-O-RAMA, provavelmente deixarei esse em paz, já que ele se firmou tão firmemente na minha história de comprometimento - ameaçador para mim sempre que eu chego perto -, não parece estar realmente causando quaisquer problemas não cosméticos e porque é bastante bonito no Tower.app.


3

A maneira mais fácil

  1. instalar git splits. Eu o criei como uma extensão git, com base na solução de jkeating .
  2. Dividir os diretórios em uma ramificação local #change into your repo's directory cd /path/to/repo #checkout the branch git checkout XYZ
    #split multiple directories into new branch XYZ git splits -b XYZ XY1 XY2

  3. Crie um repositório vazio em algum lugar. Vamos assumir que criamos um repositório vazio chamado xyzno GitHub que possui o caminho:git@github.com:simpliwp/xyz.git

  4. Envie para o novo repositório. #add a new remote origin for the empty repo so we can push to the empty repo on GitHub git remote add origin_xyz git@github.com:simpliwp/xyz.git #push the branch to the empty repo's master branch git push origin_xyz XYZ:master

  5. Clone o repositório remoto recém-criado em um novo diretório local
    #change current directory out of the old repo cd /path/to/where/you/want/the/new/local/repo #clone the remote repo you just pushed to git clone git@github.com:simpliwp/xyz.git


Uma vantagem desse método em comparação com "The Easy Way" é que o controle remoto já está configurado para o novo repositório, para que você possa fazer imediatamente uma adição à subárvore. Na verdade, esta forma parece mais fácil para mim (mesmo sem git splits)
MM

Adereços para AndrewD por postar esta solução. Tenho bifurcada sua repo para fazê-lo funcionar no OSX ( github.com/ricardoespsanto/git-splits ) se isso é útil para qualquer outra pessoa
ricardoespsanto

2

Você pode precisar de algo como "git reflog expire --expire = now --all" antes da coleta de lixo para realmente limpar os arquivos. O git filter-branch apenas remove as referências no histórico, mas não remove as entradas de reflog que mantêm os dados. Claro, teste isso primeiro.

Meu uso de disco caiu drasticamente ao fazer isso, embora minhas condições iniciais fossem um pouco diferentes. Talvez --subdirectory-filter negue essa necessidade, mas duvido.


2

Confira o projeto git_split em https://github.com/vangorra/git_split

Transforme diretórios git em seus próprios repositórios em seu próprio local. Nenhum negócio engraçado de subárvore. Esse script pega um diretório existente no seu repositório git e o transforma em um repositório independente próprio. Ao longo do caminho, ele copiará todo o histórico de alterações do diretório que você forneceu.

./git_split.sh <src_repo> <src_branch> <relative_dir_path> <dest_repo>
        src_repo  - The source repo to pull from.
        src_branch - The branch of the source repo to pull from. (usually master)
        relative_dir_path   - Relative path of the directory in the source repo to split.
        dest_repo - The repo to push to.

1

Coloque isso no seu gitconfig:

reduce-to-subfolder = !sh -c 'git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter cookbooks/unicorn HEAD && git reset --hard && git for-each-ref refs/original/ | cut -f 2 | xargs -n 1 git update-ref -d && git reflog expire --expire=now --all && git gc --aggressive --prune=now && git remote rm origin'

1

Tenho certeza de que a subárvore git é ótima e maravilhosa, mas meus subdiretórios do código gerenciado pelo git que eu queria mover estavam todos em eclipse. Portanto, se você estiver usando o egit, é dolorosamente fácil. Pegue o projeto que você deseja mover e equipe-> desconecte-o e depois equipe-> compartilhe-o no novo local. O padrão é tentar usar o local do repositório antigo, mas você pode desmarcar a seleção existente e usar o novo local para movê-lo. Todos saúdam egit.


3
A parte "fina e maravilhosa" da subárvore é que a história do seu subdiretório vem acompanhada. Se você não precisa da história, seu método dolorosamente fácil é o caminho a percorrer.
pglezen

0

Você pode facilmente experimentar o https://help.github.com/enterprise/2.15/user/articles/splitting-a-subfolder-out-into-a-new-repository/

Isso funcionou para mim. Os problemas que enfrentei nas etapas descritas acima são

  1. neste comando git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME BRANCH-NAME o BRANCH-NAMEé mestre

  2. se a última etapa falhar ao confirmar devido a um problema de proteção, siga - https://docs.gitlab.com/ee/user/project/protected_branches.html


0

Eu encontrei uma solução bastante direta, a idéia é copiar o repositório e remover a parte desnecessária. É assim que funciona:

1) Clone um repositório que você deseja dividir

git clone git@git.thehost.io:testrepo/test.git

2) Mover para a pasta git

cd test/

2) Remova pastas desnecessárias e confirme

rm -r ABC/
git add .
enter code here
git commit -m 'Remove ABC'

3) Remova o (s) histórico (s) de pastas desnecessárias com o BFG

cd ..
java -jar bfg.jar --delete-folders "{ABC}" test
cd test/
git reflog expire --expire=now --all && git gc --prune=now --aggressive

para multiplicar pastas, você pode usar vírgula

java -jar bfg.jar --delete-folders "{ABC1,ABC2}" metric.git

4) Verifique se o histórico não contém os arquivos / pastas que você acabou de excluir

git log --diff-filter=D --summary | grep delete

5) Agora você tem um repositório limpo, sem ABC, então basta empurrá-lo para uma nova origem

remote add origin git@github.com:username/new_repo
git push -u origin master

É isso aí. Você pode repetir as etapas para obter outro repositório,

basta remover XY1, XY2 e renomear XYZ -> ABC na etapa 3


Quase perfeito ... mas você esqueceu "git filter-branch --une-empty" para remover todos os commits antigos que agora estão vazios. Para fazer antes de empurrar para mestre de origem!
ZettaCircl 24/05/19

Se você cometeu o erro e ainda querem "repush" depois de ter removido vazio velho cometer, execute: "git push-u mestre origem --force-com-lease"
ZettaCircl
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.