web-dev-qa-db-fra.com

Faire deux branches identiques

J'ai deux branches - A et B. B a été créé à partir de A.

Les travaux sur les deux branches se sont poursuivis en parallèle. Le travail sur la branche A était mauvais (entraînant une version non opérationnelle), tandis que le travail sur la branche B était bon. Pendant ce temps, la branche B a parfois été fusionnée dans la branche A (mais pas l'inverse).

Maintenant, je veux que la branche A soit identique à la branche B. Je ne peux pas utiliser git revert car je dois annuler trop de commits. Je ne veux que restaurer les commits effectués sur la branche A, mais pas à la suite de la fusion de la branche B .

La solution que j’ai trouvée était de cloner la branche B dans un autre dossier, de supprimer tous les fichiers du dossier de travail de la branche A, de copier les fichiers du dossier de la branche temporaire B et d’ajouter tous les fichiers non suivis.

Y at-il une commande git qui fait la même chose? Un interrupteur git revert j'ai manqué?

16
zmbq

Il existe de nombreuses façons de le faire, et celle que vous devriez utiliser dépend du résultat souhaité et de ce que vous et toute personne qui collabore avec vous (s'il s'agit d'un référentiel partagé) vous attendez à l'avenir.

Les trois principaux moyens de le faire:

  1. Ne t'embête pas. Abandon de branche A, créez un nouveau A2 et utilisez-le.
  2. Utilisez git reset ou un équivalent pour re-pointer A ailleurs.

    Les méthodes 1 et 2 sont effectivement les mêmes à long terme. Par exemple, supposons que vous commenciez par abandonner A. Tout le monde développe sur A2 à la place pendant un moment. Ensuite, une fois que tout le monde utilise A2, vous supprimez entièrement le nom A. Maintenant que A est parti, vous pouvez même renommer A2 en A (tous ceux qui l'utilisent devront bien sûr le même nom). À ce stade, qu'est-ce qui semble différent entre le cas où vous avez utilisé la méthode 1 et le cas où vous avez utilisé la méthode 2? (Il y a un endroit où vous pouvez pouvez encore voir une différence, en fonction de la longueur de votre "long terme" et de la date d'expiration des anciens reflogs.)

  3. Faites une fusion en utilisant une stratégie spéciale (voir "Stratégies de fusion alternatives" ci-dessous). Ce que vous voulez ici, c'est -s theirs, mais il n'existe pas et vous devez le simuler.

Note latérale: le lieu où une branche est "créée" est fondamentalement sans importance: git ne garde pas trace de telles choses et en général, il n'est pas possible de les découvrir plus tard, cela ne devrait donc probablement pas avoir d'importance pour vous non plus. (Si cela vous importe vraiment, vous pouvez imposer quelques restrictions à la manière dont vous manipulez la branche ou marquer son "point de départ" avec une balise, de manière à pouvoir récupérer cette information plus tard mécaniquement. Mais ceci est souvent le signe vous faites quelque chose de mal en premier lieu - ou du moins, vous attendez quelque chose de git que git ne fournit pas (voir aussi la note 1 ci-dessous).

Définition de branche (voir aussi Qu'entendons-nous exactement par branche? )

Une branche - ou plus précisément une branche nom - dans git est simplement un pointeur sur une validation spécifique du graphique de validation. Ceci est également vrai d'autres références comme les noms de balises. Ce qui rend une branche spéciale, comparé à une balise par exemple, c’est que lorsque vous êtes sur une branche et effectuez un nouveau commit, git met automatiquement à jour le nom de la branche afin qu’il pointe vers le nouveau commit.

Par exemple, supposons que nous ayons un graphique de validation qui ressemble à ceci, où o nœuds (et celui marqué * également) représentent les validations et A et B sont des noms de branche:

o <- * <- o <- o <- o <- o   <-- A
      \
       o <- o <- o           <-- B

A et B chaque point pointe vers le commit le plus tip-tip d'une branche, ou plus précisément, la branche structure de données _ formée en commençant par un commit et en remontant tous les commits atteignables , avec chaque commit indiquant un ou plusieurs commit (s) parent (s).

Si vous utilisez git checkout B pour être sur la branche B et que vous effectuez un nouveau commit, le nouveau commit est configuré avec la pointe précédente de B en tant que parent (unique), et git modifie le ID stocké sous le nom de branche B de sorte que B pointe sur le nouveau commit le plus astuce:

o <- * <- o <- o <- o <- o   <-- A
      \
       o <- o <- o <- o      <-- B

Le commit marqué * est la base de fusion des deux astuces de branche. Cette validation, ainsi que toutes les précédentes, se trouve sur les deux branches.1 La base de fusion est importante pour, eh bien, la fusion (duh :-)), mais aussi pour des choses comme git cherry et d'autres opérations de type gestion des versions.

Vous mentionnez que la branche B a parfois été fusionnée dans A:

git checkout A; git merge B

Cela crée un nouveau fusion commit, qui est un commit avec deux2 Parents. Le premier parent est la dernière astuce de la branche en cours, c’est-à-dire la précédente astuce de A, et le deuxième parent est la validation nommée, c’est-à-dire la validation la plus proche de la branche B. En redessinant un peu plus compactement ce qui précède (mais en ajoutant un peu plus de -s pour améliorer la gamme os), nous commençons par:

o--*--o--o--o--o      <-- A
    \
     o---o--o---o     <-- B

et se retrouver avec:

o--*--o--o--o--o--o   <-- A
    \            /
     o---o--o---*     <-- B

Nous déplaçons le * vers la nouvelle base de fusion de A et B (qui est en fait la pointe de B). On peut supposer que nous ajoutons ensuite quelques modifications supplémentaires à B et que nous fusionnons peut-être encore quelques fois:

...---o--...--o--o    <-- A
     /       /
...-o--...--*--o--o   <-- B

Que fait git par défaut avec git merge <thing>

Pour effectuer une fusion, vous extrayez une branche (A dans ces cas), puis vous exécutez git merge et lui attribuez au moins un argument, généralement un autre nom de branche tel que B. La commande de fusion commence par transformer le nom en un ID de validation. Un nom de branche se transforme en l'ID du commit le plus tip sur la branche.

Ensuite, git merge trouve la base de fusion _. Ce sont les commits que nous avons marqués avec * depuis le début. La définition technique de la base de fusion est l'ancêtre commun le plus bas dans le graphique de validation (et dans certains cas, il peut y en avoir plus d'un), mais nous allons simplement utiliser "la validation marquée *" ici pour plus de simplicité.

Enfin, pour les fusions ordinaires, git exécute deux commandes git diff.3. Le premier git diff compare le commit * au HEAD commit, c’est-à-dire à la pointe de la branche en cours. La deuxième comparaison compare commit * à l'argument commit, c'est-à-dire au bout de l'autre branche (vous pouvez nommer un commit spécifique et il ne doit pas nécessairement s'agir du bout d'une branche, mais dans notre cas, nous fusionnons B dans A nous obtenons donc ces deux commits de branche-tip). Lorsque git trouve un fichier modifié, par rapport à la version fusion-base, dans les deux branches, il essaie de combiner ces modifications de manière semi-intelligente (mais pas vraiment intelligente): si les deux modifications ajoutent le même texte dans la même région, git conserve une copie de l’addition. Si les deux modifications suppriment le même texte dans la même région, git supprime ce texte une seule fois. Si les deux modifications modifient du texte dans la même région, vous obtenez un conflit, à moins que les modifications ne correspondent exactement (vous obtenez alors une copie des modifications). Si un camp effectue un changement et que l'autre campe un changement différent, mais que les changements ne semblent pas se chevaucher, git prend les deux changements. C’est l’essence d’une fusion à trois.

Enfin, si tout se passe bien, git fait un nouveau commit avec deux (ou plus, comme nous l’avons déjà noté à la note 2). Le arbre de travail associé à ce nouveau commit est celui que git a créé lors de sa fusion à trois.

Stratégies de fusion alternatives.

Alors que la stratégie par défaut de git mergerecursive a -X options ours et theirs, ils ne font pas ce que nous voulons ici. Celles-ci disent simplement que dans le cas d'un conflit, git devrait automatiquement résoudre ce conflit en choisissant "notre changement" (-X ours) ou "leur changement" (-X theirs).

La commande merge utilise une autre stratégie, -s ours: celle-ci indique qu'au lieu de différencier la base de fusion des deux commits, utilisez simplement notre arborescence source. En d'autres termes, si nous sommes sur la branche A et que nous exécutons git merge -s ours B, git effectuera une nouvelle validation de fusion, le second parent constituant la pointe de la branche B, mais l'arbre source correspondant à la version présente. la pointe précédente de la branche A. C'est-à-dire que le code du nouveau commit correspondra exactement au code de son parent.

Comme indiqué dans cette autre réponse , il existe un certain nombre de façons de forcer git à mettre en œuvre efficacement -s theirs. Je pense que le plus simple à expliquer est cette séquence:.

git checkout A git merge --no-commit -s ours B git rm -rf . # make sure you are at the top level! git checkout B -- . git commit

À ce stade, nous arrivons à la viande du tour. Tout d’abord, nous supprimons le résultat de la fusion dans son ensemble, car il n’a en fait aucune valeur: nous ne voulons pas que l’arbre de travail soit inscrit dans le commit de la astuce de A, mais plutôt dans celui de la pointe de B. Ensuite, nous extrayons chaque fichier de la pointe de B, le préparant ainsi.

Enfin, nous validons la nouvelle fusion, qui a pour premier parent l’ancienne pointe de A et en tant que second parent, la pointe de B, mais avec le arbre de commit B.

Si le graphique juste avant la validation était:.

...---o--...--o--o <-- A / / ...-o--...--*--o--o <-- B

...---o--...--o--o--o   <-- A
     /       /     /
...-o--...--o--o--*     <-- B

.


Dans ce cas particulier, les deux branches étant restées indépendantes (jamais fusionnées), c’est aussi probablement le point de création de l’une des deux branches (ou peut-être même où les deux ont été créées), bien que vous puissiez Ne le prouvez pas à ce stade (car quelqu'un a peut-être déjà utilisé git reset ou d'autres astuces pour déplacer les libellés de branche). Dès que nous commençons à fusionner, cependant, la base de fusion n'est clairement plus le point de départ, et un point de départ raisonnable devient plus difficile à localiser..

Techniquement, un commit de fusion est un commit avec deux ou plus _ parents. Les appels Git fusionnent avec plus de deux parents "fusions pieuvres". D'après mon expérience, elles ne sont pas courantes, sauf dans le référentiel git de git lui-même, et finissent par obtenir le même résultat que plusieurs fusions ordinaires à deux parents..

Les différences sont généralement effectuées en interne dans une certaine mesure, plutôt que d'exécuter des commandes réelles. Cela permet beaucoup d'optimisations de raccourcis. Cela signifie également que si vous écrivez un pilote de fusion personnalisé, celui-ci n'est pas exécuté à moins que git ne trouve que le fichier est modifié dans les deux diff. Si elle n'est modifiée que dans l'un des deux diffs, la fusion par défaut prend simplement celle modifiée.

29
torek

Commander votre branche cible:

git checkout A;

Pour tout supprimer de la branche A et le rendre identique à B:

git reset B --hard;

Si vous devez annuler toutes vos modifications locales (type de restauration, mais ne gâchez pas git git revert):

git reset HEAD --hard;

Lorsque vous avez terminé, n'oubliez pas de mettre à jour votre branche distante (utilisez l'indicateur --force ou -f au cas où vous auriez besoin de remplacer l'historique).

git Push Origin B -f;
4
Jack

Vous devez git reset branche A pour branche B . Voir docs .

0
AlexZam

voici la façon la plus agréable dont je me sers pour le faire pour moi ... L'idée est d'obtenir un diff de A et B, commettre ce diff, rétablir que pour créer un commet opposé et puis sélectionner le dernier retour de commettre en A

voici les commandes

git checkout B
git checkout -b B_tmp 
git merge --squash A
git commit -a -m 'diff'
git revert ⬆_commit_sha(paste here 'diff' commit sha, get from 'git log -1')
git checkout A                    
git cherry-pick revert_commit_sha (B_tmp branch last commit)
git branch -d B_tmp

et vous y êtes, A est identique à B et vous n’avez qu’un commit dans l’historique A qui redevienne identique à la branche B.

0
Sergey Sahakyan