web-dev-qa-db-fra.com

Comment choisir une série de commits et les fusionner dans une autre branche?

J'ai la disposition suivante du référentiel:

  • branche maîtresse (production)
  • l'intégration
  • travail

Ce que je veux réaliser est de choisir une série d’engagements dans la branche active et de les fusionner dans la branche intégration. Je suis assez nouveau pour git et je ne vois pas comment faire exactement cela (la sélection des plages de commit en une seule opération et non la fusion) sans déranger le référentiel. Des pointeurs ou des pensées à ce sujet? Merci!

577
crazybyte

En ce qui concerne une gamme de commits, choisir les cerises est  était pas pratique.

Comme mentionné ci-dessous par Keith Kim , Git 1.7.2+ introduisait la possibilité de sélectionner une série de commits (mais vous devez toujours être conscient de la conséquence de la sélection des données pour une fusion future )

git cherry-pick "a appris à choisir une gamme de commits
(par exemple, "_cherry-pick A..B_" et "_cherry-pick --stdin_"), ainsi que "_git revert_"; ceux-ci ne supportent pas le contrôle de séquençage plus agréable que "_rebase [-i]_" a cependant.

damiancommentaires et nous avertit:

Dans le formulaire "_cherry-pick A..B_", A DOIT ÊTRE PLUS ANCIEN QUE B.
S'ils ne sont pas dans le bon ordre, la commande échouera silencieusement.

Si vous voulez choisir le plage B à D (inclus), ce serait B^..D.
Voir " Git crée une branche à partir de la plage des commits précédents? " à titre d’illustration.

Comme Jubobs mentionne dans les commentaires :

Cela suppose que B n'est pas un commit root; sinon, vous obtiendrez une erreur "_unknown revision_".

Note: à partir de Git 2.9.x/2.10 (T3 2016), vous pouvez sélectionner une plage de commit directement sur une branche orpheline (tête vide): voir " Comment faire d'une branche existante un orphelin dans git ".


Réponse originale (janvier 2010)

Un _rebase --onto_ serait mieux si vous rejouez la plage donnée de commit en plus de votre branche d'intégration, comme Charles Bailey décrit ici .
(aussi, cherchez "Voici comment transplanter une branche de sujet basée sur une branche à une autre" dans la page de manuel git rebase , pour voir un exemple pratique de _git rebase --onto_)

Si votre branche actuelle est l'intégration:

_# Checkout a new temporary branch at the current location
git checkout -b tmp

# Move the integration branch to the head of the new patchset
git branch -f integration last_SHA-1_of_working_branch_range

# Rebase the patchset onto tmp, the old location of integration
git rebase --onto tmp first_SHA-1_of_working_branch_range~1 integration
_

Cela rejouera tout entre:

  • après le parent de _first_SHA-1_of_working_branch_range_ (d'où le _~1_): le premier commit que vous souhaitez rejouer
  • jusqu'à "integration" (qui pointe vers le dernier commit que vous voulez rejouer, à partir de la branche working)

to "tmp" (qui pointe là où integration pointait avant)

S'il y a un conflit lorsqu'un de ces commits est rejoué:

  • soit le résoudre et exécuter "_git rebase --continue_".
  • ou ignorez ce correctif et exécutez à la place "_git rebase --skip_"
  • ou annulez le tout avec un "_git rebase --abort_" (et remettez la branche integration sur la branche tmp)

Après cela _rebase --onto_, integration sera de retour au dernier commit de la branche d'intégration (c'est-à-dire "tmp" branch + tous les commits rejoués)

Avec la sélection de cerises ou _rebase --onto_, n'oubliez pas que cela a des conséquences sur les fusions ultérieures, comme décrit ici .


Une solution pure "_cherry-pick_" est discutée ici , et impliquerait quelque chose comme:

Si vous souhaitez utiliser une approche patch, alors "git format-patch | git am" et "git cherry" sont vos options.
Actuellement, _git cherry-pick_ n’accepte qu’un seul commit, mais si vous souhaitez choisir la plage B à D, celle-ci serait _B^..D_ dans git jargon, alors

_git rev-list --reverse --topo-order B^..D | while read rev 
do 
  git cherry-pick $rev || break 
done 
_

Quoi qu'il en soit, lorsque vous devez "rejouer" une série de commits, le mot "rejouer" devrait vous inciter à utiliser la fonctionnalité "rebase" de Git.

735
VonC

A partir de git v1.7.2, cherry pick peut accepter une gamme de commits:

git cherry-pick a appris à choisir une plage de validations (par exemple, cherry-pick A..B et cherry-pick --stdin), ainsi git revert; ceux-ci ne supportent pas le contrôle de séquençage plus agréable que rebase [-i] a, cependant.

129
Keith Kim

Supposons que vous ayez 2 branches,

"branchA": inclut les commits que vous voulez copier (de "commitA" à "commitB"

"branchB": la branche vers laquelle vous voulez transférer les commits de "branchA"

1)

 git checkout <branchA>

2) obtenir les identifiants de "commitA" et "commitB"

3)

git checkout <branchB>

4)

git cherry-pick <commitA>^..<commitB>

5) En cas de conflit, résolvez-le et tapez

git cherry-pick --continue

poursuivre le processus de sélection des cerises.

27
KostasA

Êtes-vous sûr de ne pas vouloir fusionner les branches? Si la branche active a des commits récents que vous ne voulez pas, vous pouvez simplement créer une nouvelle branche avec un HEAD au point souhaité.

Maintenant, si vous voulez vraiment choisir une gamme d’engagements, quelle que soit la raison, une manière élégante de procéder consiste simplement à créer un patch et à l’appliquer à votre nouvelle branche d’intégration:

git format-patch A..B
git checkout integration
git am *.patch

C’est essentiellement ce que git-rebase fait de toute façon, mais sans avoir besoin de jouer à des jeux. Vous pouvez ajouter --3way à git-am si vous devez fusionner. Assurez-vous qu'il n'y a pas d'autres fichiers * .patch déjà dans le répertoire où vous faites cela, si vous suivez les instructions à la lettre ...

25
djs

J'ai enveloppé code de VonC dans un court script bash, git-multi-cherry-pick, pour une exécution facile:

#!/bin/bash

if [ -z $1 ]; then
    echo "Equivalent to running git-cherry-pick on each of the commits in the range specified.";
    echo "";
    echo "Usage:  $0 start^..end";
    echo "";
    exit 1;
fi

git rev-list --reverse --topo-order $1 | while read rev 
do 
  git cherry-pick $rev || break 
done 

Je l'utilise actuellement pour reconstruire l'historique d'un projet comportant à la fois du code tiers et des personnalisations mélangées dans le même tronc svn. Je suis en train de scinder le code tiers principal, les modules tiers et les personnalisations en leurs propres branches git pour une meilleure compréhension des personnalisations à venir. git-cherry-pick est utile dans cette situation car j'ai deux arbres dans le même référentiel, mais sans ancêtre partagé.

9
Adam Franco

Toutes les options ci-dessus vous inviteront à résoudre les conflits de fusion. Si vous fusionnez des modifications validées pour une équipe, il est difficile de résoudre les conflits de fusion des développeurs et de continuer. Cependant, "git merge" fera la fusion en un seul coup, mais vous ne pouvez pas passer une plage de révisions en argument. nous devons utiliser les commandes "git diff" et "git apply" pour faire la plage de fusion des révolutions. J'ai observé que "git apply" échouera si le fichier de correctif a diff pour trop de fichiers, nous devons donc créer un correctif par fichier, puis appliquer. Notez que le script ne pourra pas supprimer les fichiers supprimés dans la branche source. Dans de rares cas, vous pouvez supprimer manuellement ces fichiers de la branche cible. Le statut de sortie de "git apply" n'est pas égal à zéro s'il n'est pas en mesure d'appliquer le correctif. Toutefois, si vous utilisez l'option -3way, il sera réduit à la fusion à 3 voies et vous n'aurez pas à vous inquiéter de cet échec.

Ci-dessous le script.

enter code here



  #!/bin/bash

    # This script will merge the diff between two git revisions to checked out branch
    # Make sure to cd to git source area and checkout the target branch
    # Make sure that checked out branch is clean run "git reset --hard HEAD"


    START=$1
    END=$2

    echo Start version: $START
    echo End version: $END

    mkdir -p ~/temp
    echo > /tmp/status
    #get files
    git --no-pager  diff  --name-only ${START}..${END} > ~/temp/files
    echo > ~/temp/error.log
    # merge every file
    for file in `cat  ~/temp/files`
    do
      git --no-pager diff --binary ${START}..${END} $file > ~/temp/git-diff
      if [ $? -ne 0 ]
      then
#      Diff usually fail if the file got deleted 
        echo Skipping the merge: git diff command failed for $file >> ~/temp/error.log
        echo Skipping the merge: git diff command failed for $file
        echo "STATUS: FAILED $file" >>  /tmp/status
        echo "STATUS: FAILED $file"
    # skip the merge for this file and continue the merge for others
        rm -f ~/temp/git-diff
        continue
      fi

      git apply  --ignore-space-change --ignore-whitespace  --3way --allow-binary-replacement ~/temp/git-diff

      if [ $? -ne 0 ]
       then
#  apply failed, but it will fall back to 3-way merge, you can ignore this failure
         echo "git apply command filed for $file"
       fi
       echo
       STATUS=`git status -s $file`


       if [ ! "$STATUS" ]
       then
#   status is null if the merged diffs are already present in the target file
         echo "STATUS:NOT_MERGED $file"
         echo "STATUS: NOT_MERGED $file$"  >>  /tmp/status
       else
#     3 way merge is successful
         echo STATUS: $STATUS
         echo "STATUS: $STATUS"  >>  /tmp/status
       fi
    done

    echo GIT merge failed for below listed files

    cat ~/temp/error.log

    echo "Git merge status per file is available in /tmp/status"
3
Yoganand Bijapur

Une autre option pourrait être de fusionner avec la stratégie our au commit avant la plage, puis une fusion "normale" avec le dernier commit de cette plage (ou branche quand il s'agit de la dernière). Supposons donc que seuls 2345 et 3456 commits du maître soient fusionnés dans une branche de fonctionnalité:

 maître: 
 1234 
 2345 
 3456 
 4567 

dans la branche de fonctionnalité:

 git merge -s our our 4567 
 git fusion 2345 
1
Koos Vriezen