Dans git, existe-t-il un moyen simple d'introduire une branche indépendante dans un référentiel?

329

Tout en aidant un ami avec un problème git aujourd'hui, j'ai dû introduire une branche qui devait être totalement séparée de la masterbranche. Le contenu de cette branche avait vraiment une origine différente de ce qui avait été développé sur la masterbranche, mais ils allaient être fusionnés dans la masterbranche ultérieurement.

Je me suis souvenu de la lecture de Git de John Wiegley de bas en haut comment les branches sont essentiellement une étiquette pour un commit qui suit une certaine convention et comment un commit est lié à une arborescence de fichiers et, éventuellement, aux commit parents. Nous sommes allés créer un commit sans parent dans le dépôt existant en utilisant la plomberie de git:

Nous nous sommes donc débarrassés de tous les fichiers de l'index ...

$ git rm -rf .

... extrait les répertoires et fichiers d'une archive tar, ajouté ceux-ci à l'index ...

$ git add .

... et créé un objet arbre ...

$ git write-tree

( git-write-treenous a indiqué le sha1sum de l'objet arbre créé.)

Ensuite, nous avons validé l'arborescence, sans spécifier les validations parentales ...

$ echo "Imported project foo" | git commit-tree $TREE

( git-commit-treenous a indiqué le sha1sum de l'objet commit créé.)

... et créé une nouvelle branche qui pointe vers notre commit nouvellement créé.

$ git update-ref refs/heads/other-branch $COMMIT

Enfin, nous sommes retournés à la mastersuccursale pour continuer à travailler là-bas.

$ git checkout -f master

Cela semble avoir fonctionné comme prévu. Mais ce n'est clairement pas le genre de procédure que je recommanderais à quelqu'un qui commence tout juste à utiliser git, c'est un euphémisme. Existe-t-il un moyen plus simple de créer une nouvelle branche sans aucun rapport avec tout ce qui s'est passé dans le référentiel jusqu'à présent?

hillu
la source

Réponses:

510

Il y a une nouvelle fonctionnalité (depuis la V1.7.2) qui rend cette tâche un peu plus élevée que celle de toutes les autres réponses.

git checkoutprend désormais en charge l' --orphanoption. Depuis la page de manuel :

git checkout [-q] [-f] [-m] --orphan <new_branch> [<start_point>]

Créez une nouvelle branche orpheline , nommée <new_branch>, démarrée à partir de <start_point> et basculez vers celle-ci. Le premier commit effectué sur cette nouvelle branche n'aura pas de parents et ce sera la racine d'une nouvelle histoire totalement déconnectée de toutes les autres branches et commits.

Cela ne fait pas exactement ce que le demandeur voulait, car il remplit l'index et l'arborescence de travail <start_point>(car il s'agit, après tout, d'une commande de paiement). La seule autre action nécessaire consiste à supprimer tous les éléments indésirables de l'arborescence de travail et de l'index. Malheureusement, git reset --hardcela ne fonctionne pas, mais git rm -rf .peut être utilisé à la place (je pense que cela équivaut à celui rm .git/index; git clean -fdxdonné dans d'autres réponses).


En résumé:

git checkout --orphan newbranch
git rm -rf .
<do work>
git add your files
git commit -m 'Initial commit'

Je suis resté <start_point>non spécifié car il est par défaut HEAD, et nous ne nous en soucions pas vraiment de toute façon. Cette séquence fait essentiellement la même chose que la séquence de commandes dans la réponse d'Artem , juste sans recourir à des commandes de plomberie effrayantes.

tcovo
la source
Savez-vous s'il est possible de créer une branche orpheline qui reste visible lorsque vous extrayez une branche de l'autre "arbre"?
JJD
1
@JJD: Je pense que ce que vous voulez, c'est fusionner git. ( git checkout master && git merge --no-commit "orphan-branch" ) Certaines astuces similaires fonctionneront en utilisant git-reset ou en jouant avec l'index. Mais cela dépend de votre flux de travail souhaité.
phord
@Matthew Voici le changelog pour git 1.7.2 .
JJD
@phord J'ai plus ou moins pensé à l'orphelin pour contenir des choses comme des tests unitaires ou de la documentation séparée du code source du projet.
JJD
2
@JJD: Le script que j'ai donné devrait vous donner ce que vous voulez, sauf si je vous ai mal compris. Lorsque vous avez dit "reste visible", voulez-vous dire que les fichiers resteront dans le répertoire de travail même si vous avez extrait une autre branche? Utiliser --no-commiton git mergey parviendra. Vous devrez peut-être effectuer un suivi git reset origin/masterpour que votre prochain commit se rend où vous le souhaitez, mais les fichiers de votre branche orpheline apparaîtront comme des "fichiers non suivis", sauf si vous les incluez également dans votre fichier .gitignore.
phord
32

De Git Community Book :

git symbolic-ref HEAD refs/heads/newbranch 
rm .git/index 
git clean -fdx 
<do work> 
git add your files 
git commit -m 'Initial commit'
Artem Tikhomirov
la source
1
La réponse suivante est meilleure pour les versions modernes de git.
kikito
14
@kikito: Re: "La réponse suivante est meilleure" ... La commande ici sur SO n'est pas stable. Pourriez-vous ajouter un lien pointant vers ce que vous pensez être une meilleure réponse.
David
2
Je faisais référence à stackoverflow.com/a/4288660/312586 . Tu as raison, je n'aurais pas dû dire "suivant". Elle avait moins de points que cette réponse lorsque j'ai commenté.
kikito
1
rm .git/indexest moche :-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Celui-ci fonctionne mieux pour le cas "Je veux m'engager dans la branche, en la créant si elle n'existait pas auparavant"
max630
22

Bien que la solution avec git symbolic-refet suppression de l'index fonctionne, il peut être conceptuellement plus propre de créer un nouveau référentiel

$ cd /path/to/unrelated
$ git init
[edit and add files]
$ git add .
$ git commit -m "Initial commit of unrelated"
[master (root-commit) 2a665f6] Initial commit of unrelated
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 foo

puis en chercher

$ cd /path/to/repo
$ git fetch /path/to/unrelated master:unrelated-branch
warning: no common commits
remote: Counting objects: 3, done.
Unpacking objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
From /path/to/unrelated
 * [new branch]      master     -> unrelated-branch

Vous pouvez maintenant supprimer / chemin / vers / sans rapport

Jakub Narębski
la source
À mon avis, un concept propre impliquerait une option pour git branchou git checkout. Je suis content que git rend ce genre de choses possible, mais pourquoi ne serait-il pas plus facile?
hillu
3
Parce que c'est et devrait être une chose rare. Si vous avez une branche des choses non liées, elle appartient généralement au référentiel non lié, au lieu de le bourrer dans un existant (bien qu'il y ait des exceptions).
Jakub Narębski
1
+1. Pour les débutants git, vous pouvez apparemment ensuite lister les branches via git branchet basculer entre elles via git checkout BRANCH_NAME.
akavel
Merci pour cela, je n'y aurais pas pensé tout seul. Ceci est très utile car il conserve l'historique (le cas échéant) de l'autre dépôt.
BM5k
13

Github a une fonctionnalité appelée Pages de projet où vous pouvez créer une branche nommée particulière dans votre projet pour fournir des fichiers qui seront servis par Github. Leurs instructions sont les suivantes:

$ cd /path/to/fancypants
$ git symbolic-ref HEAD refs/heads/gh-pages
$ rm .git/index
$ git clean -fdx

De là, vous avez un référentiel vide auquel vous pouvez ensuite ajouter votre nouveau contenu.

Greg Hewgill
la source
10

La réponse actuellement sélectionnée est correcte, je voudrais juste ajouter que par coïncidence ...

C'est en fait exactement comment github.com permet aux utilisateurs de créer des pages Github pour leurs dépôts, via une branche orpheline appelée gh-pages. Les jolies étapes sont données et expliquées ici:

https://help.github.com/articles/creating-project-pages-manually

Fondamentalement, les commandes git pour configurer cela sont les suivantes:

  1. git checkout --orphan gh-pages (créez une branche sans parents appelée gh-pages sur votre repo)
  2. git rm -rf . (supprime tous les fichiers de l'arborescence de travail de la branche)
  3. rm '.gitignore' (même le gitignore)
  4. Ajoutez maintenant le contenu du site Web (ajoutez index.html, etc.) et validez et envoyez.
  5. Profit.

Notez que vous pouvez également désigner un dossier / docs sur votre référentiel pour être la source du «site du projet» que Github utilise pour créer le site Web.

J'espère que cela t'aides!

protocole inconnu
la source
3

Parfois, je veux juste créer une branche vide dans le projet instantanément, puis commencer à travailler, je vais simplement exécuter la commande suivante:

git checkout --orphan unrelated.branch.name
git rm --cached -r .
echo "init unrelated branch" > README.md
git add README.md
git commit -m "init unrelated branch"
Chanter
la source
1

Si votre contenu existant a déjà été validé, vous pouvez maintenant (Git 2.18 Q2 2018) l'extraire dans sa propre nouvelle branche orpheline, car l'implémentation de " git rebase -i --root" a été mise à jour pour utiliser davantage la machinerie du séquenceur.

Ce séquenceur est celui qui permet maintenant de transplanter toute la topologie du graphe de validation ailleurs .

Voir commit 8fa6eea , commit 9c85a1c , commit ebddf39 , commit 21d0764 , commit d87d48b , commit ba97aea (03 mai 2018) par Johannes Schindelin ( dscho) .
(Fusionné par Junio ​​C Hamano - gitster- en commit c5aa4bc , 30 mai 2018)

séquenceur: permet d'introduire de nouveaux commit root

Dans le contexte du nouveau --rebase-mergesmode, qui a été conçu spécifiquement pour permettre de changer la topologie de branche existante de manière libérale, un utilisateur peut vouloir extraire les validations dans une branche complètement nouvelle qui commence par une validation racine nouvellement créée .

Cela est désormais possible en insérant la commande reset [new root]avant de pickvalider le commit qui veut devenir un commit root. Exemple:

reset [new root]
pick 012345 a commit that is about to become a root commit
pick 234567 this commit will have the previous one as parent

Cela n'entre pas en conflit avec d'autres utilisations de la resetcommande car il [new root]ne fait pas (partie de) un nom de référence valide: le support d'ouverture ainsi que l'espace sont illégaux dans les noms de référence.

VonC
la source
0

J'ai trouvé ce script sur http://wingolog.org/archives/2008/10/14/merging-in-unrelated-git-branches et cela fonctionne très bien!

#!/bin/bash

set -e

if test -z "$2" -o -n "$3"; then
    echo "usage: $0 REPO BRANCHNAME" >&2
    exit 1
fi

repo=$1
branch=$2

git fetch "$repo" "$branch"

head=$(git rev-parse HEAD)
fetched=$(git rev-parse FETCH_HEAD)
headref=$(git rev-parse --symbolic-full-name HEAD)

git checkout $fetched .

tree=$(git write-tree)

newhead=$(echo "merged in branch '$branch' from $repo" | git commit-tree $tree -p $head -p $fetched)
git update-ref $headref $newhead $head
git reset --hard $headref
Benoît
la source
1
Je crois que le même effet peut être obtenu en utilisant git fetch $REMOTE $REMOTE_BRANCH:$LOCAL_BRANCH. Suis-je en train de manquer quelque chose?
hillu