Comment déplacer des fichiers d'un dépôt git à un autre (pas un clone), en préservant l'historique

484

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 project2vers 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 ).

Ebneter
la source
1
Cela ressemble à une approche raisonnable pour moi; Je ne vois pas de moyen évident d'améliorer considérablement votre méthode. C'est bien que Git rende cela vraiment facile (je ne voudrais pas essayer de déplacer un répertoire de fichiers entre différents référentiels dans Subversion, par exemple).
Greg Hewgill
1
@ebneter - J'ai fait cela (déplacé l'historique d'un dépôt svn vers un autre) manuellement, en utilisant des scripts shell. Fondamentalement, j'ai relu l'historique (diffs, messages de journaux de validation) de fichiers / répertoires particuliers dans un deuxième référentiel.
Adam Monsen
1
Je me demande pourquoi tu ne fais pas à la git fetch p2 && git merge p2place de git 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.
Lekensteyn
1
N'y a-t-il aucun moyen d'empêcher --filter-branch de détruire la structure du répertoire? Cette étape "git mv" se traduit par une validation massive pleine de suppressions de fichiers et de créations de fichiers.
Edward Falk
1
Notez que depuis git 2.9, la fusion d'histoires non liées est interdite par défaut. Pour le faire fonctionner, ajoutez --allow-unrelated-historiesau dernier git mergepour le faire fonctionner.
Scott Berrevoets

Réponses:

55

Oui, frapper sur la --subdirectory-filterde filter-brancha é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 exemple pull) 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.

Cascabel
la source
1
que se passe-t-il si votre fichier a traversé plusieurs répertoires et réside désormais dans un seul - le sous-répertoire-filtre fonctionnera-t-il toujours? (c'est-à-dire que je suppose que si je veux simplement déplacer un fichier, je peux le déplacer vers son propre sous-répertoire et cela fonctionnera?)
rogerdpack
1
@rogerdpack: Non, cela ne suivra pas le fichier par le biais de renommages. Je pense qu'il semble avoir été créé au moment où il a été déplacé dans le sous-répertoire sélectionné. Si vous souhaitez sélectionner un seul fichier, consultez --index-filterla filter-branchpage de manuel.
Cascabel
8
Existe-t-il une recette sur la façon de suivre les renommages?
Night Warrier
Je pense que le maintien et la conservation de l'histoire est l'un des principaux points de Git.
artburkart
288

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:

cd repository
git log --pretty=email --patch-with-stat --reverse --full-index --binary -- path/to/file_or_folder > patch
cd ../another_repository
git am --committer-date-is-author-date < ../repository/patch 

Ou en une seule ligne

git log --pretty=email --patch-with-stat --reverse -- path/to/file_or_folder | (cd /path/to/new_repository && git am --committer-date-is-author-date)

(Tiré des documents d' Exherbo )

Smar
la source
21
Pour les trois ou 4 fichiers dont j'avais besoin pour déplacer, c'était une solution beaucoup plus simple que la réponse acceptée. J'ai fini par couper les chemins dans le fichier patch avec find-replace pour l'adapter à la structure de répertoire de mon nouveau dépôt.
Rian Sanderson
8
J'ai ajouté des options afin que les fichiers binaires (comme les images) sont également correctement migré: git log --pretty=email --patch-with-stat --full-index --binary --reverse -- client > patch. Fonctionne sans problème AFAICT.
Emmanuel Touzery
35
À l'étape d'application, j'ai utilisé l' --committer-date-is-author-dateoption pour conserver la date de validation d'origine au lieu de la date à laquelle les fichiers ont été déplacés.
darrenmc
6
fusionner commet dans l'historique casser la commande "am". Vous pouvez ajouter "-m --first-parent" à la commande git log ci-dessus, puis cela a fonctionné pour moi.
Gábor Lipták
6
@Daniel Golden J'ai réussi à résoudre le problème avec les fichiers qui ont été déplacés (ce qui est la conséquence d'un bogue dans git log, afin qu'il ne fonctionne pas avec les deux --followet --reversecorrectement). J'ai utilisé cette réponse , et voici un script complet que j'utilise maintenant pour déplacer des fichiers
tsayen
75

Aprè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

  1. Faites une copie du référentiel A car les étapes suivantes apportent des modifications majeures à cette copie que vous ne devez pas pousser!

    git clone --branch <branch> --origin origin --progress \
      -v <git repository A url>
    # eg. git clone --branch master --origin origin --progress \
    #   -v https://username@giturl/scm/projects/myprojects.git
    # (assuming myprojects is the repository you want to copy from)
    
  2. cd dedans

    cd <git repository A directory>
    #  eg. cd /c/Working/GIT/myprojects
    
  3. Supprimez le lien vers le référentiel d'origine pour éviter toute modification accidentelle à distance (par exemple en appuyant sur)

    git remote rm origin
    
  4. 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.

    git filter-branch --subdirectory-filter <directory> -- --all
    # eg. git filter-branch --subdirectory-filter subfolder1/subfolder2/FOLDER_TO_KEEP -- --all
    
  5. 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.)

    git filter-branch -f --index-filter \
    'git ls-files -s | grep $'\t'FILE_TO_KEEP$ |
    GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
    git update-index --index-info && \
    mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || echo "Nothing to do"' --prune-empty -- --all
    # eg. FILE_TO_KEEP = pom.xml to keep only the pom.xml file from FOLDER_TO_KEEP
    

Étape deux

  1. Étape de nettoyage

    git reset --hard
    
  2. Étape de nettoyage

    git gc --aggressive
    
  3. Étape de nettoyage

    git prune
    

Vous voudrez peut-être importer ces fichiers dans le référentiel B dans un répertoire et non à la racine:

  1. Créez ce répertoire

    mkdir <base directory>             eg. mkdir FOLDER_TO_KEEP
    
  2. Déplacer des fichiers dans ce répertoire

    git mv * <base directory>          eg. git mv * FOLDER_TO_KEEP
    
  3. Ajouter des fichiers à ce répertoire

    git add .
    
  4. Validez vos modifications et nous sommes prêts à fusionner ces fichiers dans le nouveau référentiel

    git commit
    

Troisième étape

  1. Faites une copie du référentiel B si vous n'en avez pas déjà

    git clone <git repository B url>
    # eg. git clone https://username@giturl/scm/projects/FOLDER_TO_KEEP.git
    

    (en supposant que FOLDER_TO_KEEP est le nom du nouveau référentiel dans lequel vous copiez)

  2. cd dedans

    cd <git repository B directory>
    #  eg. cd /c/Working/GIT/FOLDER_TO_KEEP
    
  3. Créer une connexion distante au référentiel A en tant que branche dans le référentiel B

    git remote add repo-A-branch <git repository A directory>
    # (repo-A-branch can be anything - it's just an arbitrary name)
    
    # eg. git remote add repo-A-branch /c/Working/GIT/myprojects
    
  4. Tirez de cette branche (contenant uniquement le répertoire que vous souhaitez déplacer) dans le référentiel B.

    git pull repo-A-branch master --allow-unrelated-histories
    

    Le pull copie les fichiers et l'historique. Remarque: Vous pouvez utiliser une fusion au lieu d'une extraction, mais l'extraction fonctionne mieux.

  5. Enfin, vous voulez probablement nettoyer un peu en supprimant la connexion à distance au référentiel A

    git remote rm repo-A-branch
    
  6. Poussez et vous êtes prêt.

    git push
    
mcarans
la source
J'ai parcouru la plupart des étapes décrites ici, mais il semble ne copier que l'historique de validation du fichier ou du répertoire depuis le maître (et pas depuis d'autres branches). Est-ce correct?
Bao-Long Nguyen-Trong du
Je pense que c'est vrai et que vous devez suivre des étapes similaires pour toutes les branches à partir desquelles vous souhaitez déplacer des fichiers ou des dossiers, par exemple. passer à la branche par exemple. MyBranch dans le référentiel A, la branche de filtre, etc. Vous devez ensuite "git pull repo-A-branch MyBranch" dans le référentiel B.
mcarans
Merci pour la réponse. Savez-vous si les balises sur les branches seront également migrées?
Bao-Long Nguyen-Trong
Je crains de ne pas savoir, mais je suppose que oui.
mcarans
1
@mcarans Malheureusement, ce n'est PAS un moyen fiable, bien qu'il semble l'être. Il souffre du même problème que toutes les autres solutions - Il ne conserve pas l'historique après avoir été renommé. Dans mon cas, le tout premier commit est lorsque j'ai renommé le répertoire / fichier. Tout ce qui est au-delà est perdu.
xZero
20

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):

# Setup a directory to hold the patches
mkdir <patch-directory>

# Create the patches
git format-patch -o <patch-directory> --root /path/to/copy

# Apply the patches in the new repo using a 3 way merge in case of conflicts
# (merges from the other repo are not turned into patches). 
# The 3way can be omitted.
git am --3way <patch-directory>/*.patch

Le seul problème que j'ai eu, c'est que je ne pouvais pas appliquer tous les correctifs à la fois en utilisant

git am --3way <patch-directory>/*.patch

Sous Windows, j'ai eu une erreur InvalidArgument. J'ai donc dû appliquer tous les patchs l'un après l'autre.

anhoppe
la source
Cela n'a pas fonctionné pour moi car à un moment donné, les hachages manquaient. Cela m'a aidé: stackoverflow.com/questions/17371150/…
dr0i
Contrairement à l'approche "git log", cette option a parfaitement fonctionné pour moi! Merci!
AlejandroVD
1
J'ai essayé différentes approches pour déplacer des projets vers un nouveau référentiel. C'est le seul qui a fonctionné pour moi. Je ne peux pas croire qu'une tâche aussi courante doive être aussi compliquée.
Chris_D_Turk
Merci d'avoir partagé le blog de Ross Hendrickson . Cette approche a fonctionné pour moi.
Kaushik Acharya
1
C'est une solution très élégante, cependant, encore une fois, elle souffre du même problème que toutes les autres solutions - Elle ne conservera PAS l'historique après avoir été renommé.
xZero
6

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.

# 1. clone the source
git clone ssh://<user>@<source-repo url>
cd <source-repo>
# 2. remove the stuff we want to exclude
git filter-branch --tree-filter "rm -rf <files to exclude>" --prune-empty HEAD
# 3. move to target repo and create a merge branch (for safety)
cd <path to target-repo>
git checkout -b <merge branch>
# 4. Add the source-repo as remote 
git remote add source-repo <path to source-repo>
# 5. fetch it
git pull source-repo master
# 6. check that you got it right (better safe than sorry, right?)
gitk
Joachim Nilsson
la source
Ce script n'apportera aucune modification à votre dépôt d'origine. Si le référentiel dest spécifié dans le fichier de carte n'existe pas, ce script tentera de le créer.
Chetabahana
1
Je pense également qu'il est extrêmement important de conserver les noms de répertoire intacts. Sinon, vous obtiendrez des commits de renommage supplémentaires dans le référentiel cible.
ipuustin
6

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:

mkdir /tmp/mergepatchs
cd ~/repo/org
export reposrc=myfile.c #or mydir
git format-patch -o /tmp/mergepatchs $(git log $reposrc|grep ^commit|tail -1|awk '{print $2}')^..HEAD $reposrc
cd ~/repo/dest
git am /tmp/mergepatchs/*.patch
Hugh Perkins
la source
5

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

  • Vous souhaitez déplacer certains ou tous les fichiers d'un référentiel vers un autre.
  • Vous voulez garder leur histoire.
  • Mais vous ne vous souciez pas de conserver les balises et les branches.
  • Vous acceptez un historique limité pour les fichiers renommés (et les fichiers dans des répertoires renommés).

Procédure

  1. Extraire l'historique au format e-mail à l'aide
    git log --pretty=email -p --reverse --full-index --binary
  2. Réorganiser l'arborescence des fichiers et mettre à jour le changement de nom de fichier dans l'historique [facultatif]
  3. Appliquer un nouvel historique à l'aide git am

1. Extraire l'historique au format e-mail

Exemple: l' histoire Extrait du file3, file4etfile5

my_repo
├── dirA
│   ├── file1
│   └── file2
├── dirB            ^
│   ├── subdir      | To be moved
│   │   ├── file3   | with history
│   │   └── file4   | 
│   └── file5       v
└── dirC
    ├── file6
    └── file7

Nettoyer la destination du répertoire temporaire

export historydir=/tmp/mail/dir  # Absolute path
rm -rf "$historydir"             # Caution when cleaning

Nettoyez votre source repo

git commit ...           # Commit your working files
rm .gitignore            # Disable gitignore
git clean -n             # Simulate removal
git clean -f             # Remove untracked file
git checkout .gitignore  # Restore gitignore

Extraire l'historique de chaque fichier au format email

cd my_repo/dirB
find -name .git -prune -o -type d -o -exec bash -c 'mkdir -p "$historydir/${0%/*}" && git log --pretty=email -p --stat --reverse --full-index --binary -- "$0" > "$historydir/$0"' {} ';'

Malheureusement option --followou --find-copies-harderne 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

/tmp/mail/dir
    ├── subdir
    │   ├── file3
    │   └── file4
    └── file5

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).

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB              # New tree
│   ├── dirB1         # was subdir
│   │   ├── file33    # was file3
│   │   └── file44    # was file4
│   └── dirB2         # new dir
│        └── file5    # = file5
└── dirH
    └── file77

Réorganisez donc vos fichiers:

cd /tmp/mail/dir
mkdir     dirB
mv subdir dirB/dirB1
mv dirB/dirB1/file3 dirB/dirB1/file33
mv dirB/dirB1/file4 dirB/dirB1/file44
mkdir    dirB/dirB2
mv file5 dirB/dirB2

Votre historique temporaire est maintenant:

/tmp/mail/dir
    └── dirB
        ├── dirB1
        │   ├── file33
        │   └── file44
        └── dirB2
             └── file5

Modifiez également les noms de fichiers dans l'historique:

cd "$historydir"
find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i "$0"' {} ';'

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:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
└── dirH
    └── file77

Appliquer des validations à partir de fichiers d'historique temporaires:

cd my_other_repo
find "$historydir" -type f -exec cat {} + | git am 

Votre autre dépôt est maintenant:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB            ^
│   ├── dirB1       | New files
│   │   ├── file33  | with
│   │   └── file44  | history
│   └── dirB2       | kept
│        └── file5  v
└── dirH
    └── file77

Utilisez git statuspour 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)

  • Pas besoin de git mvchanger l'emplacement / nom de fichier.
  • Pas besoin d' git log --followaccé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:

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'

Plus de personnalisations: vous pouvez exécuter la commande à l' git logaide des options --find-copies-harderou --reverse. Vous pouvez également supprimer les deux premières colonnes en utilisant cut -f3-et en accueillant le motif complet '{. * =>. *}'.

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'
olibre
la source
3

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:

cd old_repo
git format-patch --thread -o "$temp" --root -- "$object"

qui sont ensuite appliqués à un nouveau référentiel:

cd new_repo
git am "$temp"/*.patch 

Pour plus de détails, veuillez consulter:

ViToni
la source
2

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

git filter-branch --index-filter 'git rm --ignore-unmatch --cached -qr -- . && git reset -q $GIT_COMMIT -- dir1/ dir2/ dir3/ ' --prune-empty -- --all

Maintenant, vous pouvez ajouter votre nouveau référentiel dans votre télécommande git et le pousser

git remote remove origin <old-repo>
git remote add origin <new-repo>
git push origin <current-branch>

ajouter -fpour écraser

Chetan Basutkar
la source
AVERTISSEMENT: git-filter-branch a une surabondance de gotchas générant des réécritures d'historique altérées. Appuyez sur Ctrl-C avant de procéder à l'abandon, puis utilisez un autre outil de filtrage tel que 'git filter-repo' ( github.com/newren/git-filter-repo ) à la place. Voir la page de manuel de branche de filtre pour plus de détails; pour éteindre cet avertissement, définissez FILTER_BRANCH_SQUELCH_WARNING = 1.
Colin
1

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:

# Migrates the git history of a file or directory from one Git repo to another.
# Start in the root directory of the source repo.
# Also, before running this, I recommended that $destRepoDir be on a new branch that the history will be migrated to.
# Inspired by: http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/
function Migrate-GitHistory
{
    # The file or directory within the current Git repo to migrate.
    param([string] $fileOrDir)
    # Path to the destination repo
    param([string] $destRepoDir)
    # A temp directory to use for storing the patch file (optional)
    param([string] $tempDir = "\temp\migrateGit")

    mkdir $tempDir

    # git log $fileOrDir -- to list commits that will be migrated
    Write-Host "Generating patch files for the history of $fileOrDir ..." -ForegroundColor Cyan
    git format-patch -o $tempDir --root -- $fileOrDir

    cd $destRepoDir
    Write-Host "Applying patch files to restore the history of $fileOrDir ..." -ForegroundColor Cyan
    ls $tempDir -Filter *.patch  `
        | foreach { git am $_.FullName }
}

Utilisation pour cet exemple:

git clone project2
git clone project1
cd project1
# Create a new branch to migrate to
git checkout -b migrate-from-project2
cd ..\project2
Migrate-GitHistory "deeply\buried\java\source\directory\A" "..\project1"

Après cela, vous pouvez réorganiser les fichiers sur la migrate-from-project2branche avant de la fusionner.

crimbo
la source
1

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/foode repo1vers /some/other/folder/barvers repo2(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_FROMdossier) 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

#!/bin/bash

# Copy a folder from one git repo to another git repo,
# preserving full history of the folder.

SRC_GIT_REPO='/d/git-experimental/your-old-webapp'
DST_GIT_REPO='/d/git-experimental/your-new-webapp'
SRC_BRANCH_NAME='master'
DST_BRANCH_NAME='import-stuff-from-old-webapp'
# Most likely you want the REWRITE_FROM and REWRITE_TO to have a trailing slash!
REWRITE_FROM='app/src/main/static/'
REWRITE_TO='app/src/main/static/'

verifyPreconditions() {
    #echo 'Checking if SRC_GIT_REPO is a git repo...' &&
      { test -d "${SRC_GIT_REPO}/.git" || { echo "Fatal: SRC_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO is a git repo...' &&
      { test -d "${DST_GIT_REPO}/.git" || { echo "Fatal: DST_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if REWRITE_FROM is not empty...' &&
      { test -n "${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM is empty"; exit; } } &&
    #echo 'Checking if REWRITE_TO is not empty...' &&
      { test -n "${REWRITE_TO}" || { echo "Fatal: REWRITE_TO is empty"; exit; } } &&
    #echo 'Checking if REWRITE_FROM folder exists in SRC_GIT_REPO' &&
      { test -d "${SRC_GIT_REPO}/${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if SRC_GIT_REPO has a branch SRC_BRANCH_NAME' &&
      { cd "${SRC_GIT_REPO}"; git rev-parse --verify "${SRC_BRANCH_NAME}" || { echo "Fatal: SRC_BRANCH_NAME does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO has a branch DST_BRANCH_NAME' &&
      { cd "${DST_GIT_REPO}"; git rev-parse --verify "${DST_BRANCH_NAME}" || { echo "Fatal: DST_BRANCH_NAME does not exist inside DST_GIT_REPO"; exit; } } &&
    echo '[OK] All preconditions met'
}

# Import folder from one git repo to another git repo, including full history.
#
# Internally, it rewrites the history of the src repo (by creating
# a temporary orphaned branch; isolating all the files from REWRITE_FROM path
# to the root of the repo, commit by commit; and rewriting them again
# to the original path).
#
# Then it creates another temporary branch in the dest repo,
# fetches the commits from the rewritten src repo, and does a merge.
#
# Before any work is done, all the preconditions are verified: all folders
# and branches must exist (except REWRITE_TO folder in dest repo, which
# can exist, but does not have to).
#
# The code should work reasonably on repos with reasonable git history.
# I did not test pathological cases, like folder being created, deleted,
# created again etc. but probably it will work fine in that case too.
#
# In case you realize something went wrong, you should be able to reverse
# the changes by calling `undoImportFolderFromAnotherGitRepo` function.
# However, to be safe, please back up your repos just in case, before running
# the script. `git filter-branch` is a powerful but dangerous command.
importFolderFromAnotherGitRepo(){
    SED_COMMAND='s-\t\"*-\t'${REWRITE_TO}'-'

    verifyPreconditions &&
    cd "${SRC_GIT_REPO}" &&
      echo "Current working directory: ${SRC_GIT_REPO}" &&
      git checkout "${SRC_BRANCH_NAME}" &&
      echo 'Backing up current branch as FILTER_BRANCH_BACKUP' &&
      git branch -f FILTER_BRANCH_BACKUP &&
      SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
      echo "Creating temporary branch '${SRC_BRANCH_NAME_EXPORTED}'..." &&
      git checkout -b "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo 'Rewriting history, step 1/2...' &&
      git filter-branch -f --prune-empty --subdirectory-filter ${REWRITE_FROM} &&
      echo 'Rewriting history, step 2/2...' &&
      git filter-branch -f --index-filter \
       "git ls-files -s | sed \"$SED_COMMAND\" |
        GIT_INDEX_FILE=\$GIT_INDEX_FILE.new git update-index --index-info &&
        mv \$GIT_INDEX_FILE.new \$GIT_INDEX_FILE" HEAD &&
    cd - &&
    cd "${DST_GIT_REPO}" &&
      echo "Current working directory: ${DST_GIT_REPO}" &&
      echo "Adding git remote pointing to SRC_GIT_REPO..." &&
      git remote add old-repo ${SRC_GIT_REPO} &&
      echo "Fetching from SRC_GIT_REPO..." &&
      git fetch old-repo "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo "Checking out DST_BRANCH_NAME..." &&
      git checkout "${DST_BRANCH_NAME}" &&
      echo "Merging SRC_GIT_REPO/" &&
      git merge "old-repo/${SRC_BRANCH_NAME}-exported" --no-commit &&
    cd -
}

# If something didn't work as you'd expect, you can undo, tune the params, and try again
undoImportFolderFromAnotherGitRepo(){
  cd "${SRC_GIT_REPO}" &&
    SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
    git checkout "${SRC_BRANCH_NAME}" &&
    git branch -D "${SRC_BRANCH_NAME_EXPORTED}" &&
  cd - &&
  cd "${DST_GIT_REPO}" &&
    git remote rm old-repo &&
    git merge --abort
  cd -
}

importFolderFromAnotherGitRepo
#undoImportFolderFromAnotherGitRepo
jakub.g
la source
0

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

#Source directory
git remote rm origin
#Target directory
git remote add branch-name-from-old-repo ../source_directory

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)

git br --set-upstream-to=origin/mainline

Maintenant, il se comportait comme si c'était juste une autre branche que j'avais poussée contre ce même dépôt.

Jason D
la source
0

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 laissera FETCH_HEADpointer 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 avec git 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>si commit-0introduit les fichiers que vous voulez, ou vous devrez peut - être construire le cOMMIT à la main ». Ajoutez -naux 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-pickles validations suivantes, en utilisant à nouveau -nsi 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> ....

cjs
la source
0

Cela devient plus simple en utilisant git-filter-repo.

Pour passer project2/sub/dirà project1/sub/dir:

# Create a new repo containing only the subdirectory:
git clone project2 project2_subdir
cd project2_subdir
git filter-repo --force --path sub/dir

# Merge the new repo:
cd ../project1
git remote add project2_subdir ../project2_subdir/
git merge remotes/project2_subdir/master --allow-unrelated-histories
git remote remove project2_subdir

Pour installer l'outil simplement: pip3 install git-filter-repo ( plus de détails et d'options dans README )

# Before: (root)
.
|-- project1
|   `-- 3
`-- project2
    |-- 1
    `-- sub
        `-- dir
            `-- 2

# After: (project1)
.
├── 3
└── sub
    └── dir
        └── 2
Tapuzi
la source
-2

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.

git clone --bare <STASH-URL>

Créez un référentiel vide dans GitLab.

git push --mirror <GitLab-URL>

Ce que j'ai effectué ci-dessus lorsque nous avons migré notre code de stash vers GitLab et cela a très bien fonctionné.

Roopkumar Akubathini
la source