Desanexe muitos subdiretórios em um novo repository Git separado

Esta questão é baseada no subdiretório Detach em um repository Git separado

Em vez de destacar um único subdiretório, quero destacar um casal. Por exemplo, minha tree de diretórios atual se parece com isso:

/apps /AAA /BBB /CCC /libs /XXX /YYY /ZZZ 

E eu gostaria disso:

 /apps /AAA /libs /XXX 

O argumento --subdirectory-filter para git filter-branch não funciona porque se livra de tudo, exceto do diretório fornecido, na primeira vez que é executado. Eu pensei que usar o argumento --index-filter para todos os arquivos indesejados funcionaria (embora tedioso), mas se eu tentar executá-lo mais de uma vez, recebo a seguinte mensagem:

 Cannot create a new backup. A previous backup already exists in refs/original/ Force overwriting the backup with -f 

Alguma ideia? TIA

Em vez de ter que lidar com um subshell e usar ext glob (como sugerido por kynan), tente esta abordagem muito mais simples:

 git filter-branch --index-filter 'git rm --cached -qr --ignore-unmatch -- . && git reset -q $GIT_COMMIT -- apps/AAA libs/XXX' --prune-empty -- --all 

Por que você deseja executar o filter-branch mais de uma vez? Você pode fazer tudo de uma vez, então não precisa forçá-lo (note que você precisa de extglob habilitado em seu shell para que isso funcione):

 git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch $(ls -xd apps/!(AAA) libs/!(XXX))" --prune-empty -- --all 

Isso deve se livrar de todas as alterações nos subdiretórios indesejados e manter todos os seus branches e commits (a menos que eles afetem apenas os arquivos nos subdiretórios --prune-empty , em virtude de --prune-empty ) – nenhum problema com commits duplicados, etc.

Após esta operação, os diretórios indesejados serão listados como não rastreados pelo git status .

O $(ls ...) é necessário para que o extglob seja avaliado pelo seu shell ao invés do filtro index, que usa o sh builtin eval (onde extglob não está disponível). Veja Como eu ativo as opções do shell no git? para mais detalhes sobre isso.

Etapas manuais com comandos git simples

O plano é dividir diretórios individuais em seus próprios repositorys e depois mesclá-los. As seguintes etapas manuais não empregam scripts geek-to-use, mas comandos fáceis de entender e podem ajudar a mesclar sub-pastas N extras em outro repository único.

Dividir

Vamos supor que o seu repo original seja: original_repo

1 – Aplicativos divididos:

 git clone original_repo apps-repo cd apps-repo git filter-branch --prune-empty --subdirectory-filter apps master 

2 – Dividir libs

 git clone original_repo libs-repo cd libs-repo git filter-branch --prune-empty --subdirectory-filter libs master 

Continue se você tiver mais de 2 pastas. Agora você terá dois novos e temporários repositorys git.

Conquiste mesclando aplicativos e libs

3 – Prepare o novo repo:

 mkdir my-desired-repo cd my-desired-repo git init 

E você precisará fazer pelo menos um commit. Se as três linhas a seguir forem ignoradas, seu primeiro repo aparecerá imediatamente abaixo da raiz do seu repo:

 touch a_file_and_make_a_commit # see user's feedback git add a_file_and_make_a_commit git commit -am "at least one commit is needed for it to work" 

Com o arquivo temporário confirmado, o comando de merge na seção posterior será interrompido conforme o esperado.

Tomando de feedback do usuário, em vez de adicionar um arquivo random como a_file_and_make_a_commit , você pode optar por adicionar um .gitignore , ou README.md etc.

4 – Mesclar aplicativos repo primeiro:

 git remote add apps-repo ../apps-repo git fetch apps-repo git merge -s ours --no-commit apps-repo/master # see below note. git read-tree --prefix=apps -u apps-repo/master git commit -m "import apps" 

Agora você deve ver o diretório de aplicativos dentro do seu novo repository. git log deve mostrar todas as mensagens de confirmação históricas relevantes.

Nota: como Chris observou abaixo nos comentários, para uma versão mais nova (> = 2.9) do git, você precisa especificar – --allow-unrelated-histories com git merge

5 – Mesclar libs repo em seguida da mesma maneira:

 git remote add libs-repo ../libs-repo git fetch libs-repo git merge -s ours --no-commit libs-repo/master # see above note. git read-tree --prefix=libs -u libs-repo/master git commit -m "import libs" 

Continue se você tiver mais de 2 repos para mesclar.

Referência: Mesclar um subdiretório de outro repository com git

Respondendo minha própria pergunta aqui … depois de muita tentativa e erro.

Eu consegui fazer isso usando uma combinação de git subtree e git-stitch-repo . Estas instruções são baseadas em:

Primeiro, peguei os diretórios que queria manter em seu próprio repository separado:

 cd origRepo git subtree split -P apps/AAA -b aaa git subtree split -P libs/XXX -b xxx cd .. mkdir aaaRepo cd aaaRepo git init git fetch ../origRepo aaa git checkout -b master FETCH_HEAD cd .. mkdir xxxRepo cd xxxRepo git init git fetch ../origRepo xxx git checkout -b master FETCH_HEAD 

Eu então criei um novo repository vazio e importei / costurei os dois últimos:

 cd .. mkdir newRepo cd newRepo git init git-stitch-repo ../aaaRepo:apps/AAA ../xxxRepo:libs/XXX | git fast-import 

Isso cria dois ramos, master-A e master-B , cada um contendo o conteúdo de um dos repositorys costurados. Para combiná-los e limpar:

 git checkout master-A git pull . master-B git checkout master git branch -d master-A git branch -d master-B 

Agora eu não tenho certeza de como / quando isso acontece, mas depois do primeiro checkout e do pull , o código magicamente se funde com o branch master (qualquer insight sobre o que está acontecendo aqui é apreciado!)

Tudo parece ter funcionado como esperado, exceto pelo fato de que, se eu examinar o newRepo histórico de newRepo , haverá duplicatas quando o changeset afetar os dois apps/AAA e libs/XXX . Se existe uma maneira de remover duplicatas, então seria perfeito.

Eu escrevi um filtro git para resolver exatamente esse problema. Ele tem o nome fantástico de git_filter e está localizado no github aqui:

https://github.com/slobobaby/git_filter

Baseia-se na excelente libgit2.

Eu precisava dividir um grande repository com muitos commits (~ 100.000) e as soluções baseadas no git filter-branch levavam vários dias para serem executadas. git_filter leva um minuto para fazer a mesma coisa.

Use a extensão git ‘git splits’

git splits é um script bash que é um wrapper em torno do git branch-filter que eu criei como uma extensão git, com base na solução do jkeating .

Foi feito exatamente para esta situação. Para o seu erro, tente usar a opção git splits -f para forçar a remoção do backup. Como o git splits opera em uma nova ramificação, ele não irá rewrite sua ramificação atual, portanto, o backup é irrelevante. Veja o leia-me para mais detalhes e certifique-se de usá-lo em uma cópia / clone do seu repo (por via das dúvidas!) .

  1. instalar git splits .
  2. Divida 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 apps/AAA libs/ZZZ
    #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 apps/AAA libs/ZZZ

  3. Crie um repository vazio em algum lugar. Vamos supor que criamos um git@github.com:simpliwp/xyz.git vazio chamado xyz no GitHub que possui path: git@github.com:simpliwp/xyz.git

  4. Empurre para o novo repo. #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 repository 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

Sim. Forçar a sobrescrever o backup usando o sinalizador -f nas chamadas subseqüentes à filter-branch para replace esse aviso. 🙂 Caso contrário, eu acho que você tem a solução (ou seja, erradicar um diretório indesejado de cada vez com filter-branch ).

 git clone git@example.com:thing.git cd thing git fetch for originBranch in `git branch -r | grep -v master`; do branch=${originBranch:7:${#originBranch}} git checkout $branch done git checkout master git filter-branch --index-filter 'git rm --cached -qr --ignore-unmatch -- . && git reset -q $GIT_COMMIT -- dir1 dir2 .gitignore' --prune-empty -- --all git remote set-url origin git@example.com:newthing.git git push --all 

Exclua o backup presente no diretório .git em refs / original, como a mensagem sugere. O diretório está oculto.