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
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.
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
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
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.
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ù!) .
git splits
.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
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
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
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
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.
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
).
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