Comment ré-enraciner un repo git dans un dossier parent tout en préservant l'historique? [dupliquer]
Cette question a déjà une réponse ici:
J'ai un repo git dans /foo/bar
avec un grand historique de commit et plusieurs branches.
Je veux maintenant que /foo/baz
soit dans le même repo que /foo/bar
, ce qui (je pense) signifie que je dois créer un nouveau repo dans /foo
. Cependant, je veux préserver l'historique des modifications que j'ai apportées à /foo/bar
.
J'ai d'abord pensé à git format-patch suivi de apply, mais les messages de validation ne sont pas conservés.
7 réponses
Ce que vous voulez, c'est git filter-branch
, qui peut déplacer un dépôt entier dans un sous-arbre, en préservant l'histoire en le faisant ressembler à ça. Sauvegardez votre référentiel avant d'utiliser ceci!
Voici la magie. Dans /foo/bar
, exécutez:
git filter-branch --commit-filter '
TREE="$1";
shift;
SUBTREE=`echo -e 040000 tree $TREE"\tbar" | git mktree`
git commit-tree $SUBTREE "$@"' -- --all
Cela fera que le dépôt /foo/bar
aura un autre sous-répertoire ' bar ' avec tout son contenu tout au long de son historique. Ensuite, vous pouvez déplacer l'ensemble du repo jusqu'au niveau foo
et ajouter du code baz
à il.
mise à Jour:
Voilà ce qui se passe. Un commit est un lien vers un " arbre "(pensez-y comme un SHA représentant le contenu d'un sous-répertoire de système de fichiers entier) plus quelques SHA" parents " et quelques métadonnées lien auteur/message/etc. La commande git commit-tree
est le bit de bas niveau qui enveloppe tout cela ensemble. Le paramètre de --commit-filter
est traité comme une fonction shell et exécuté à la place de git commit-tree
pendant le processus de filtre, et doit agir comme ça.
Ce que je fais est en prenant le premier paramètre, l'arbre d'origine à valider, et en construisant un nouvel objet "arbre" qui dit qu'il est dans un sous-dossier via git mktree
, une autre commande git de bas niveau. Pour ce faire, je dois y introduire quelque chose qui ressemble à un arbre git, c'est-à-dire un ensemble de lignes (mode SP type SP SHA TAB filename); ainsi la commande echo. La sortie de mktree
est ensuite substituée au premier paramètre lorsque je chaîne au réel commit-tree
; "$@"
est un moyen de passer tous les autres paramètres intacts, après avoir dépouillé le premier avec shift
. Voir git help mktree
et git help commit-tree
pour plus d'informations.
Donc, si vous avez besoin de plusieurs niveaux, vous devez imbriquer quelques niveaux supplémentaires d'objets arborescents (ce n'est pas testé mais c'est l'idée générale):
git filter-branch --commit-filter '
TREE="$1"
shift
SUBTREE1=`echo -e 040000 tree $TREE"\tbar" | git mktree`
SUBTREE2=`echo -e 040000 tree $SUBTREE1"\tb" | git mktree`
SUBTREE3=`echo -e 040000 tree $SUBTREE2"\ta" | git mktree`
git commit-tree $SUBTREE3 "$@"' -- --all
Cela devrait déplacer le contenu réel vers le bas dans a/b/bar
(notez l'ordre inversé).
Update : améliorations intégrées de la réponse de Matthew Alpert ci-dessous. Sans -- --all
cela ne fonctionne que sur la branche actuellement extraite, mais puisque la question demande un ensemble de pensions, il est plus logique de le faire de cette façon que par branche.
Plutôt que de créer un nouveau référentiel, déplacez ce qui se trouve dans votre référentiel actuel au bon endroit: créez un nouveau répertoire bar
dans votre répertoire actuel et déplacez le contenu actuel (donc votre code est dans /foo/bar/bar
). Ensuite, créez un répertoire baz
à côté de votre nouveau répertoire bar
(/foo/bar/baz
). mv /foo /foo2; mv /foo2/bar /foo; rmdir /foo2
et vous avez terminé :).
Le suivi des renommages de Git signifie que votre historique fonctionnera toujours et que le hachage du contenu de Git signifie que même si vous avez déplacé des choses, vous faites toujours référence les mêmes objets dans le référentiel.
J'avais une solution que personne ne semble encore avoir dite:
Ce dont j'avais spécifiquement besoin était d'inclure des fichiers du répertoire parent dans mon référentiel (en déplaçant efficacement le repo dans un répertoire).
J'ai réalisé ceci via:
- déplacer tous les fichiers (sauf pour .git) dans un nouveau sous-répertoire portant le même nom. Et indiquer à git à ce sujet (avec
git mv
) - déplacer tous les fichiers du répertoire parent dans le maintenant vide (sauf pour .git/) répertoire courant et parlez-en à git (avec
git add
) - commettre le tout dans le repo, qui n'est pas déplacé (
git commit
). - déplace le répertoire courant vers le haut d'un niveau dans la hiérarchie des répertoires. (avec la ligne de commande jiggery-pokery)
J'espère que cela aidera le prochain gars à venir-je suis probablement juste une journée sans cerveau, mais j'ai trouvé les réponses ci-dessus trop élaborées et effrayantes (pour ce que j'avais besoin de.) Je sais que c'est similaire à la réponse D'Andrew Aylett ci-dessus, mais Ma situation semblait un peu différente et je voulais une vue plus générale.
Cela ajoute à la réponse acceptée de Walter Mundt. J'aurais plutôt commenté sa réponse, mais je n'ai pas la réputation.
La méthode de Walter Mundt fonctionne très bien, mais elle ne fonctionne que pour une branche à la fois. Et après la première branche, il peut y avoir des avertissements qui nécessitent -f pour forcer l'action. Donc, pour faire cela pour toutes les branches à la fois, ajoutez simplement "-- -- all " à la fin:
git filter-branch --commit-filter '
tree="$1";
shift;
subtree=`echo -e 040000 tree $tree"\tsrc" | git mktree`
git commit-tree $subtree "$@"' -- --all
Et pour ce faire pour des branches spécifiques, ajoutez leurs noms à la fin, bien que je Je ne peux pas imaginer pourquoi vous changeriez la structure de répertoire de seulement quelques-unes des branches.
En savoir plus à ce sujet dans la page de manuel pour git filter-branch. Cependant, veuillez noter l'avertissement sur les difficultés possibles à pousser après avoir utilisé une telle commande. Assurez-vous de savoir ce que vous faites.
J'apprécierais plus d'informations sur les problèmes potentiels avec cette méthode.
Solution la plus courante
Dans la plupart des situations normales, git regarde tous les fichiers relativement à son emplacement (ce qui signifie le .répertoire git), par opposition à l'utilisation de chemins de fichiers absolus.
Ainsi, si cela ne vous dérange pas d'avoir un commit dans votre historique qui montre que vous avez tout déplacé, il existe une solution très simple, qui consiste à déplacer le répertoire git. Le seul point délicat, c'est de git comprend que les fichiers sont les mêmes et qu'ils n'déplacé relativement à lui :
# Create sub-directory with the same name in /foo/bar
mkdir bar
# Move everything down, notifying git :
git mv file1 file2 file3 bar/
# Then move everything up one level :
mv .git ../.git
mv bar/* .
mv .gitignore ../
# Here, take care to move untracked files
# Then delete unused directory
rmdir bar
# and commit
cd ../
git commit
La seule chose à faire attention, est de mettre à jour correctement .gitignore lors du déplacement vers le nouveau répertoire, pour éviter la mise en scène des fichiers indésirables, ou oublier certains.
Solution Bonus
Dans certains paramètres, git parvient à comprendre par lui-même que les fichiers ont été déplacés quand il voit de nouveaux fichiers qui sont exactement les mêmes que les fichiers supprimés. Dans ce cas, la solution est encore plus simple :
mv .git ../.git
mv .gitignore ../.gitignore
cd ../
git commit
Encore une fois, soyez prudent avec votre .gitignore
1 ajout à la réponse acceptée, ce qui m'a aidé à le faire fonctionner: quand je mets le texte répertorié dans un script shell, pour une raison quelconque, le-e a été conservé. (très probablement parce que je suis trop épais pour travailler avec des scripts shell)
Quand j'ai supprimé le-e et déplacé les guillemets pour englober tout, cela a fonctionné.
SUBTRE2=echo "040000 tree $SUBTREE1 modules" | git mktree
Notez qu'il y a un onglet entre $ SUBTREE1 et modules, qui est le même \t que-e devrait interpréter.
, Vous pouvez créer un repo git dans foo
et de référence à la fois baz
et bar
par git submodules
.
Alors les deux bar
et baz
coexistent avec leur histoire complète préservée.
Si vous ne voulez vraiment qu'un seul repo (foo), avec à la fois l'historique bar et baz, alors une technique de greffes ou une stratégie de fusion de sous-arbres sont en ordre.