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.

50
git
demandé sur Profpatsch 2010-07-09 16:20:31

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.

24
répondu Walter Mundt 2015-08-15 07:11:32

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.

16
répondu Andrew Aylett 2010-07-09 12:37:52

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.

6
répondu Jon Carter 2013-08-23 17:49:48

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.

5
répondu Matthew Alpert 2012-06-14 00:53:24

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

3
répondu Vic Seedoubleyew 2016-04-11 19:42:48

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.

2
répondu Joeblade 2013-06-05 14:15:29

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

0
répondu VonC 2017-05-23 12:18:18