web-dev-qa-db-fra.com

Détachez de nombreux sous-répertoires dans un nouveau référentiel Git séparé

Cette question est basée sur Détacher le sous-répertoire dans un référentiel Git séparé

Au lieu de détacher un seul sous-répertoire, je veux en détacher deux. Par exemple, mon arborescence de répertoires actuelle ressemble à ceci:

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

Et je voudrais plutôt ceci:

/apps
  /AAA
/libs
  /XXX

Le --subdirectory-filter argument à git filter-branch ne fonctionnera pas car il se débarrasse de tout sauf du répertoire donné lors de sa première exécution. J'ai pensé utiliser le --index-filter l'argument pour tous les fichiers indésirables fonctionnerait (bien que fastidieux), mais si j'essaye de l'exécuter plus d'une fois, j'obtiens le message suivant:

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

Des idées? TIA

121
prisonerjohn

Répondre à ma propre question ici ... après beaucoup d'essais et d'erreurs.

J'ai réussi à le faire en utilisant une combinaison de git subtree et git-stitch-repo . Ces instructions sont basées sur:

Tout d'abord, j'ai retiré les répertoires que je voulais conserver dans leur propre référentiel séparé:

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

J'ai ensuite créé un nouveau référentiel vide et j'y ai importé/cousu les deux derniers:

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

Cela crée deux branches, master-A et master-B, chacun contenant le contenu de l'un des dépôts cousus. Pour les combiner et nettoyer:

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

Maintenant, je ne sais pas trop comment/quand cela se produit, mais après le premier checkout et le pull, le code fusionne comme par magie dans la branche principale (tout aperçu de ce qui se passe ici est apprécié !)

Tout semble avoir fonctionné comme prévu, sauf que si je regarde dans l'historique de commit newRepo, il y a des doublons lorsque le changeset a affecté les deux apps/AAA et libs/XXX. S'il existe un moyen de supprimer les doublons, ce serait parfait.

19
prisonerjohn

Au lieu d'avoir à gérer un sous-shell et d'utiliser ext glob (comme l'a suggéré kynan), essayez cette approche beaucoup plus simple:

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

Étapes manuelles avec des commandes git simples

Le plan consiste à diviser des répertoires individuels en ses propres référentiels, puis à les fusionner. Les étapes manuelles suivantes n'ont pas utilisé de scripts geek à utiliser mais des commandes faciles à comprendre et pourraient aider à fusionner des sous-dossiers N supplémentaires dans un autre référentiel unique.

Diviser

Supposons que votre dépôt d'origine soit: original_repo

1 - Applications fractionnées:

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

2 - Bibliothèques divisées

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

Continuez si vous avez plus de 2 dossiers. Vous allez maintenant avoir deux nouveaux dépôts git temporaires.

Conquérir en fusionnant des applications et des bibliothèques

3 - Préparez le tout nouveau repo:

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

Et vous devrez effectuer au moins un commit. Si les trois lignes suivantes doivent être ignorées, votre premier référentiel apparaîtra immédiatement sous la racine de votre référentiel:

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"

Une fois le fichier temporaire validé, la commande merge dans la section ultérieure s'arrêtera comme prévu.

À partir des commentaires des utilisateurs, au lieu d'ajouter un fichier aléatoire comme a_file_and_make_a_commit, vous pouvez choisir d'ajouter un .gitignore, ou README.md etc.

4 - Fusionner d'abord le dépôt d'applications:

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"

Vous devriez maintenant voir le répertoire apps dans votre nouveau référentiel. git log devrait afficher tous les messages de validation historiques pertinents.

Remarque: comme Chris l'a noté ci-dessous dans les commentaires, pour une version plus récente (> = 2.9) de git, vous devez spécifier --allow-unrelated-histories avec git merge

5 - Fusionnez ensuite le dépôt des libs de la même manière:

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"

Continuez si vous avez plus de 2 dépôts à fusionner.

Référence: Fusionner un sous-répertoire d'un autre référentiel avec git

36
chfw

Pourquoi voudriez-vous exécuter filter-branch Plus d'une fois? Vous pouvez tout faire en un seul balayage, donc pas besoin de le forcer (notez que vous avez besoin de extglob activé dans votre Shell pour que cela fonctionne):

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

Cela devrait supprimer toutes les modifications dans les sous-répertoires indésirables et conserver toutes vos branches et validations (sauf si elles n'affectent que les fichiers dans les sous-répertoires élagués, en vertu de --Prune-empty) - aucun problème avec les validations en double, etc.

Après cette opération, les répertoires indésirables seront répertoriés comme non suivis par git status.

La $(ls ...) est nécessaire s.t. le extglob est évalué par votre shell au lieu du filtre d'index, qui utilise le sh intégré eval (où extglob n'est pas disponible). Voir Comment activer les options Shell dans git? pour plus de détails à ce sujet.

27
kynan

Utiliser l'extension git 'git splits'

git splits est un script bash qui entoure git branch-filter que j'ai créé comme une extension git, basée sur la solution de jkeating .

Il a été fait exactement pour cette situation. Pour votre erreur, essayez d'utiliser le git splits -f option pour forcer la suppression de la sauvegarde. Car git splits fonctionne sur une nouvelle branche, elle ne réécrira pas votre branche actuelle, donc la sauvegarde est superflue. Voir le fichier Lisez-moi pour plus de détails et assurez-vous de l'utiliser sur une copie/clone de votre dépôt (juste au cas où!) .

  1. installer git splits .
  2. Fractionner les répertoires en une branche locale #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. Créez un dépôt vide quelque part. Nous supposerons que nous avons créé un référentiel vide appelé xyz sur GitHub qui a le chemin: [email protected]:simpliwp/xyz.git

  4. Poussez vers le nouveau dépôt. #add a new remote Origin for the empty repo so we can Push to the empty repo on GitHub git remote add Origin_xyz [email protected]:simpliwp/xyz.git #Push the branch to the empty repo's master branch git Push Origin_xyz XYZ:master

  5. Clonez le référentiel distant nouvellement créé dans un nouveau répertoire 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 [email protected]:simpliwp/xyz.git

7
AndrewD

J'ai écrit un filtre git pour résoudre exactement ce problème. Il a le nom fantastique de git_filter et se trouve sur github ici:

https://github.com/slobobaby/git_filter

Il est basé sur l'excellent libgit2.

J'avais besoin de diviser un grand référentiel avec de nombreuses validations (~ 100 000) et les solutions basées sur git filter-branch ont pris plusieurs jours à s'exécuter. git_filter prend une minute pour faire la même chose.

7
slobobaby

Ouais. Forcer l'écrasement de la sauvegarde à l'aide de -f indicateur lors d'appels ultérieurs à filter-branch pour remplacer cet avertissement. :) Sinon, je pense que vous avez la solution (c'est-à-dire, éradiquez un répertoire indésirable à la fois avec filter-branch).

3
Jakob Borg
git clone [email protected]: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 [email protected]:newthing.git
git Push --all
1