Nos référentiels Git ont commencé comme des parties d'un référentiel SVN monster unique où les projets individuels avaient chacun leur propre arborescence comme ceci:
project1/branches
/tags
/trunk
project2/branches
/tags
/trunk
De toute évidence, il était assez facile de déplacer des fichiers de l'un à l'autre avec svn mv
. Mais dans Git, chaque projet est dans son propre référentiel, et aujourd'hui, on m'a demandé de déplacer un sous-répertoire de project2
vers project1
. J'ai fait quelque chose comme ça:
$ git clone project2
$ cd project2
$ git filter-branch --subdirectory-filter deeply/buried/java/source/directory/A -- --all
$ git remote rm origin # so I don't accidentally overwrite the repo ;-)
$ mkdir -p deeply/buried/different/java/source/directory/B
$ for f in *.java; do
> git mv $f deeply/buried/different/java/source/directory/B
> done
$ git commit -m "moved files to new subdirectory"
$ cd ..
$
$ git clone project1
$ cd project1
$ git remote add p2 ../project2
$ git fetch p2
$ git branch p2 remotes/p2/master
$ git merge p2 # --allow-unrelated-histories for git 2.9+
$ git remote rm p2
$ git push
Mais cela semble assez compliqué. Y a-t-il une meilleure façon de faire ce genre de chose en général? Ou ai-je adopté la bonne approche?
Notez que cela implique de fusionner l'historique dans un référentiel existant, plutôt que de simplement créer un nouveau référentiel autonome à partir d'une partie d'un autre ( comme dans une question précédente ).
la source
git fetch p2 && git merge p2
place degit fetch p2 && git branch .. && git merge p2
? Edit: d'accord, il semble que vous souhaitiez obtenir les modifications dans une nouvelle branche nommée p2, pas dans la branche actuelle.--allow-unrelated-histories
au derniergit merge
pour le faire fonctionner.Réponses:
Oui, frapper sur la
--subdirectory-filter
defilter-branch
a été la clé. Le fait que vous l'utilisiez prouve essentiellement qu'il n'y a pas de moyen plus simple - vous n'aviez pas d'autre choix que de réécrire l'historique, car vous vouliez vous retrouver avec seulement un sous-ensemble (renommé) des fichiers, et cela change par définition les hachages. Puisqu'aucune des commandes standard (par exemplepull
) ne réécrit l'historique, il n'y a aucun moyen de les utiliser pour accomplir cela.Vous pouvez bien sûr affiner les détails - certains de vos clonages et branchements n'étaient pas strictement nécessaires - mais l'approche globale est bonne! C'est dommage, c'est compliqué, mais bien sûr, le but de git n'est pas de faciliter la réécriture de l'histoire.
la source
--index-filter
lafilter-branch
page de manuel.Si votre historique est sain d'esprit, vous pouvez retirer les validations sous forme de correctif et les appliquer dans le nouveau référentiel:
Ou en une seule ligne
(Tiré des documents d' Exherbo )
la source
git log --pretty=email --patch-with-stat --full-index --binary --reverse -- client > patch
. Fonctionne sans problème AFAICT.--committer-date-is-author-date
option pour conserver la date de validation d'origine au lieu de la date à laquelle les fichiers ont été déplacés.git log
, afin qu'il ne fonctionne pas avec les deux--follow
et--reverse
correctement). J'ai utilisé cette réponse , et voici un script complet que j'utilise maintenant pour déplacer des fichiersAprès avoir essayé différentes approches pour déplacer un fichier ou un dossier d'un référentiel Git à un autre, la seule qui semble fonctionner de manière fiable est décrite ci-dessous.
Cela implique le clonage du référentiel à partir duquel vous souhaitez déplacer le fichier ou le dossier, le déplacement de ce fichier ou de ce dossier vers la racine, la réécriture de l'historique Git, le clonage du référentiel cible et l'extraction du fichier ou du dossier avec l'historique directement dans ce référentiel cible.
Première étape
Faites une copie du référentiel A car les étapes suivantes apportent des modifications majeures à cette copie que vous ne devez pas pousser!
cd dedans
Supprimez le lien vers le référentiel d'origine pour éviter toute modification accidentelle à distance (par exemple en appuyant sur)
Parcourez votre historique et vos fichiers, supprimant tout ce qui ne se trouve pas dans le répertoire 1. Le résultat est le contenu du répertoire 1 déversé dans la base du référentiel A.
Pour le déplacement d'un seul fichier uniquement: parcourez ce qui reste et supprimez tout sauf le fichier souhaité. (Vous devrez peut-être supprimer les fichiers dont vous ne voulez pas avec le même nom et les valider.)
Étape deux
Étape de nettoyage
Étape de nettoyage
Étape de nettoyage
Vous voudrez peut-être importer ces fichiers dans le référentiel B dans un répertoire et non à la racine:
Créez ce répertoire
Déplacer des fichiers dans ce répertoire
Ajouter des fichiers à ce répertoire
Validez vos modifications et nous sommes prêts à fusionner ces fichiers dans le nouveau référentiel
Troisième étape
Faites une copie du référentiel B si vous n'en avez pas déjà
(en supposant que FOLDER_TO_KEEP est le nom du nouveau référentiel dans lequel vous copiez)
cd dedans
Créer une connexion distante au référentiel A en tant que branche dans le référentiel B
Tirez de cette branche (contenant uniquement le répertoire que vous souhaitez déplacer) dans le référentiel B.
Le pull copie les fichiers et l'historique. Remarque: Vous pouvez utiliser une fusion au lieu d'une extraction, mais l'extraction fonctionne mieux.
Enfin, vous voulez probablement nettoyer un peu en supprimant la connexion à distance au référentiel A
Poussez et vous êtes prêt.
la source
J'ai trouvé cela très utile. Il s'agit d'une approche très simple où vous créez des correctifs qui sont appliqués au nouveau dépôt. Voir la page liée pour plus de détails.
Il ne contient que trois étapes (copiées à partir du blog):
Le seul problème que j'ai eu, c'est que je ne pouvais pas appliquer tous les correctifs à la fois en utilisant
Sous Windows, j'ai eu une erreur InvalidArgument. J'ai donc dû appliquer tous les patchs l'un après l'autre.
la source
GARDER LE NOM DU RÉPERTOIRE
Le sous-répertoire-filtre (ou la sous-arborescence de commande plus courte git) fonctionne bien mais n'a pas fonctionné pour moi car ils suppriment le nom du répertoire des informations de validation. Dans mon scénario, je veux simplement fusionner des parties d'un référentiel dans un autre et conserver l'historique AVEC le nom de chemin complet.
Ma solution était d'utiliser l'arborescence et de supprimer simplement les fichiers et répertoires indésirables d'un clone temporaire du référentiel source, puis de tirer de ce clone dans mon référentiel cible en 5 étapes simples.
la source
Celui que j'utilise toujours est ici http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/ . Simple et rapide.
Pour la conformité aux normes stackoverflow, voici la procédure:
la source
Cette réponse fournit des commandes intéressantes basées sur
git am
et présentées à l'aide d'exemples, étape par étape.Objectif
Procédure
git log --pretty=email -p --reverse --full-index --binary
git am
1. Extraire l'historique au format e-mail
Exemple: l' histoire Extrait du
file3
,file4
etfile5
Nettoyer la destination du répertoire temporaire
Nettoyez votre source repo
Extraire l'historique de chaque fichier au format email
Malheureusement option
--follow
ou--find-copies-harder
ne peut pas être combinée avec--reverse
. C'est pourquoi l'historique est coupé lorsque le fichier est renommé (ou lorsqu'un répertoire parent est renommé).Après: historique temporaire au format e-mail
2. Réorganiser l'arborescence des fichiers et mettre à jour le changement de nom de fichier dans l'historique [facultatif]
Supposons que vous souhaitiez déplacer ces trois fichiers dans cet autre référentiel (il peut s'agir du même référentiel).
Réorganisez donc vos fichiers:
Votre historique temporaire est maintenant:
Modifiez également les noms de fichiers dans l'historique:
Remarque: Cela réécrit l'historique pour refléter le changement de chemin d'accès et de nom de fichier.
(c'est-à-dire le changement du nouvel emplacement / nom dans le nouveau référentiel)
3. Appliquer une nouvelle histoire
Votre autre dépôt est:
Appliquer des validations à partir de fichiers d'historique temporaires:
Votre autre dépôt est maintenant:
Utilisez
git status
pour voir le nombre de commits prêts à être poussés :-)Remarque: Comme l'historique a été réécrit pour refléter le changement de chemin et de nom de fichier:
(c'est-à-dire par rapport à l'emplacement / nom dans le dépôt précédent)
git mv
changer l'emplacement / nom de fichier.git log --follow
accéder à l'historique complet.Astuce supplémentaire: Détectez les fichiers renommés / déplacés dans votre référentiel
Pour répertorier les fichiers renommés:
Plus de personnalisations: vous pouvez exécuter la commande à l'
git log
aide des options--find-copies-harder
ou--reverse
. Vous pouvez également supprimer les deux premières colonnes en utilisantcut -f3-
et en accueillant le motif complet '{. * =>. *}'.la source
Ayant eu une démangeaison similaire à gratter (mais seulement pour certains fichiers d'un référentiel donné), ce script s'est avéré très utile: git-import
La version courte est qu'il crée des fichiers correctifs du fichier ou du répertoire donné (
$object
) à partir du référentiel existant:qui sont ensuite appliqués à un nouveau référentiel:
Pour plus de détails, veuillez consulter:
la source
Essaye ça
cd repo1
Cela supprimera tous les répertoires à l'exception de ceux mentionnés, en conservant l'historique uniquement pour ces répertoires
Maintenant, vous pouvez ajouter votre nouveau référentiel dans votre télécommande git et le pousser
ajouter
-f
pour écraserla source
En utilisant l'inspiration de http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/ , j'ai créé cette fonction Powershell pour faire de même, ce qui a a fonctionné très bien pour moi jusqu'à présent:
Utilisation pour cet exemple:
Après cela, vous pouvez réorganiser les fichiers sur la
migrate-from-project2
branche avant de la fusionner.la source
Je voulais quelque chose de robuste et réutilisable (une commande et aller + fonction d'annulation), j'ai donc écrit le script bash suivant. A travaillé pour moi à plusieurs reprises, j'ai donc pensé le partager ici.
Il est capable de déplacer un dossier arbitraire
/path/to/foo
derepo1
vers/some/other/folder/bar
versrepo2
(les chemins de dossier peuvent être identiques ou différents, la distance du dossier racine peut être différente).Comme il ne passe en revue que les validations qui touchent les fichiers du dossier d'entrée (pas toutes les validations du référentiel source), il devrait être assez rapide même sur les référentiels de grande source, si vous extrayez simplement un sous-dossier profondément imbriqué qui n'a pas été touché dans tous les commettre.
Étant donné que cela permet de créer une branche orpheline avec tout l'historique de l'ancien référentiel, puis de la fusionner avec la tête, cela fonctionnera même en cas de conflits de noms de fichiers (vous devrez alors résoudre une fusion à la fin du cours) .
S'il n'y a pas de conflits de noms de fichiers, il vous suffit de le faire
git commit
à la fin pour finaliser la fusion.L'inconvénient est qu'il ne suivra probablement pas les renommages de fichiers (en dehors du
REWRITE_FROM
dossier) dans le dépôt source - les demandes de tirage sont les bienvenues sur GitHub pour s'adapter à cela.Lien GitHub: git-move-folder-between-repos-keep-history
la source
Dans mon cas, je n'avais pas besoin de conserver le dépôt depuis lequel je migrais ou de conserver une histoire précédente. J'ai eu un patch de la même branche, d'une autre télécommande
Au cours de ces deux étapes, j'ai pu faire apparaître la branche de l'autre référentiel dans le même référentiel.
Enfin, j'ai défini cette branche (que j'ai importée de l'autre référentiel) pour suivre la ligne principale du référentiel cible (afin que je puisse les différencier avec précision)
Maintenant, il se comportait comme si c'était juste une autre branche que j'avais poussée contre ce même dépôt.
la source
Si les chemins d'accès des fichiers en question sont les mêmes dans les deux référentiels et que vous souhaitez apporter un seul fichier ou un petit ensemble de fichiers associés, une façon simple de le faire est d'utiliser
git cherry-pick
.La première étape consiste à importer les validations de l'autre référentiel dans votre propre référentiel local à l'aide de
git fetch <remote-url>
. Cela laisseraFETCH_HEAD
pointer vers la validation de la tête de l'autre référentiel; si vous souhaitez conserver une référence à ce commit après avoir effectué d'autres récupérations, vous pouvez le marquer avecgit tag other-head FETCH_HEAD
.Vous devrez ensuite créer un commit initial pour ce fichier (s'il n'existe pas) ou un commit pour amener le fichier à un état qui peut être corrigé avec le premier commit de l'autre référentiel que vous souhaitez introduire. Vous pouvez être en mesure de le faire avec un
git cherry-pick <commit-0>
sicommit-0
introduit les fichiers que vous voulez, ou vous devrez peut - être construire le cOMMIT à la main ». Ajoutez-n
aux options de sélection si vous devez modifier la validation initiale, par exemple, supprimez les fichiers de cette validation que vous ne souhaitez pas importer.Après cela, vous pouvez continuer
git cherry-pick
les validations suivantes, en utilisant à nouveau-n
si nécessaire. Dans le cas le plus simple (tous les commits sont exactement ce que vous voulez et appliquer proprement) vous pouvez donner la liste complète des commits sur la ligne de commande écrémer:git cherry-pick <commit-1> <commit-2> <commit-3> ...
.la source
Cela devient plus simple en utilisant git-filter-repo.
Pour passer
project2/sub/dir
àproject1/sub/dir
:Pour installer l'outil simplement:
pip3 install git-filter-repo
( plus de détails et d'options dans README )la source
La méthode ci-dessous pour migrer mon GIT Stash vers GitLab en conservant toutes les branches et en préservant l'historique.
Clonez l'ancien référentiel en local.
Créez un référentiel vide dans GitLab.
Ce que j'ai effectué ci-dessus lorsque nous avons migré notre code de stash vers GitLab et cela a très bien fonctionné.
la source