94 votes

Commande git pour rendre une branche comme une autre

J'essaie de prendre une branche avec des changements et de la ramener à l'identique de l'amont dont elle a divergé. Les changements sont à la fois locaux et ont été poussés sur github, donc ni l'un ni l'autre ne peut être modifié. git reset o git rebase sont vraiment viables, puisqu'ils modifient l'historique, ce qui est une mauvaise chose avec une branche qui a déjà été poussée.

J'ai aussi essayé git merge avec diverses stratégies mais aucune d'entre elles n'annule les changements locaux, c'est-à-dire que si j'avais ajouté un fichier, une fusion pourrait remettre d'autres fichiers en ligne, mais j'aurais toujours ce fichier que l'amont n'a pas.

Je pourrais simplement créer une nouvelle branche à partir de l'amont, mais j'aimerais vraiment une fusion qui, en termes d'historique de révision, applique toutes les modifications pour prendre ma branche et la rendre à nouveau identique à l'amont, de sorte que je puisse pousser cette modification en toute sécurité sans altérer l'historique. Existe-t-il une telle commande ou série de commandes ?

12 votes

Si vous ne vous souciez pas de préserver les changements, pourquoi ne pas simplement supprimer et recréer la branche ? "L'histoire du projet" n'a pas besoin d'être sacrée. Git est un outil pour aider les développeurs à communiquer. Si ces changements n'aident pas à cela, jetez-les.

0 votes

+100 @wnoise - surtout si les changements ont déjà été fusionnés.

13 votes

Je tiens à préserver l'histoire à la fois parce qu'elle est publiée à des fins de collaboration et parce que je pourrais avoir envie d'y revenir. Pourquoi s'embêter à utiliser le contrôle de révision si vous ne gardez que la dernière version ?

128voto

VonC Points 414372

Vous pourriez fusionner votre branche amont avec votre dev la branche, avec un conducteur de fusion personnalisé "keepTheirs" :
Voir " " git merge -s theirs "nécessaire - mais je sais qu'il n'existe pas ".
Dans votre cas, un seul .gitattributes serait nécessaire, et un keepTheirs script comme :

mv -f $3 $2
exit 0

git merge --strategy=theirs Simulation n° 1

Il s'agit d'une fusion, avec l'amont comme premier parent.

Jefromi mentionne (dans les commentaires) le merge -s ours en fusionnant votre travail sur l'amont (ou sur une branche temporaire partant de l'amont), puis en faisant avancer rapidement votre branche vers le résultat de cette fusion :

git checkout -b tmp origin/upstream
git merge -s ours downstream         # ignoring all changes from downstream
git checkout downstream
git merge tmp                        # fast-forward to tmp HEAD
git branch -D tmp                    # deleting tmp

Cela présente l'avantage d'enregistrer l'ancêtre en amont en tant que premier parent, de sorte que l'élément merge signifie "absorber cette branche thématique périmée" plutôt que "détruire cette branche thématique et la remplacer par l'amont". .

(Edit 2011) :

Ce flux de travail a été rapporté dans ce billet de blog de l'OP :

Pourquoi est-ce que j'en veux encore ?

Tant que mon repo n'avait rien à voir avec la version publique, tout allait bien, mais comme je veux maintenant pouvoir collaborer sur le WIP avec d'autres membres de l'équipe et des contributeurs extérieurs, je veux m'assurer que mes branches publiques sont fiables pour que d'autres puissent s'en inspirer, c'est-à-dire qu'il n'y a plus de rebase et de reset sur les choses que j'ai poussées sur la sauvegarde distante, puisqu'elles sont maintenant sur GitHub et publiques.

Il me reste donc à savoir comment je dois procéder.
99% du temps, ma copie ira dans le master amont, donc je veux travailler mon master et pousser dans l'amont la plupart du temps.
Mais de temps en temps, ce que j'ai en tête wip sera invalidée par ce qui se passe en amont et j'abandonnerai une partie de mon wip .
À ce moment-là, je veux ramener mon master en synchronisation avec upstream, mais sans détruire aucun point de commit sur mon master poussé publiquement. C'est à dire que je veux une fusion avec upstream qui se termine avec les changements qui rendent ma copie identique à upstream. .
Et c'est ce que git merge --strategy=theirs devrait faire.


git merge --strategy=theirs Simulation n°2

Il s'agit d'une fusion, avec le nôtre comme premier parent.

(proposé par jcwenger )

git checkout -b tmp upstream
git merge -s ours thebranch         # ignoring all changes from downstream
git checkout downstream
git merge --squash tmp               # apply changes from tmp but not as merge.
git rev-parse upstream > .git/MERGE_HEAD #record upstream 2nd merge head
git commit -m "rebaselined thebranch from upstream" # make the commit.
git branch -D tmp                    # deleting tmp

git merge --strategy=theirs Simulation n°3

Ce site mentions d'articles de blog :

git merge -s ours ref-to-be-merged
git diff --binary ref-to-be-merged | git apply -R --index
git commit -F .git/COMMIT_EDITMSG --amend

parfois, vous avez envie de le faire, et pas parce que vous avez des "conneries" dans votre histoire, mais peut-être parce que vous voulez changer la ligne de base pour le développement dans un dépôt public où le rebasage doit être évité. .


git merge --strategy=theirs Simulation n°4

(même article de blog)

Alternativement, si vous voulez garder les branches locales amont en avance rapide, un compromis potentiel est de travailler avec la compréhension que pour sid/unstable, la branche amont peut de temps en temps être réinitialisée/rebasée (basée sur des événements qui sont finalement hors de votre contrôle du côté du projet amont).
Ce n'est pas un gros problème et travailler avec cette hypothèse signifie qu'il est facile de garder la branche locale en amont dans un état où elle ne prend que les mises à jour en avance rapide.

git branch -m upstream-unstable upstream-unstable-save
git branch upstream-unstable upstream-remote/master
git merge -s ours upstream-unstable
git diff --binary ref-to-be-merged | git apply -R --index --exclude="debian/*"
git commit -F .git/COMMIT_EDITMSG --amend

git merge --strategy=theirs Simulation n°5

(proposé par Barak A. Pearlmutter ):

git checkout MINE
git merge --no-commit -s ours HERS
git rm -rf .
git checkout HERS -- .
git checkout MINE -- debian # or whatever, as appropriate
git gui # edit commit message & click commit button

git merge --strategy=theirs Simulation n°6

(proposé par le même Michael Gebetsroither ):

Michael Gebetsroither est intervenu, affirmant que je "trichais" ;) et a proposé une autre solution avec des commandes de plomberie de niveau inférieur :

(ce ne serait pas git si ce n'était pas possible avec des commandes git uniquement, tout ce qui est dans git avec diff/patch/apply n'est pas une vraie solution ;).

# get the contents of another branch
git read-tree -u --reset <ID>
# selectivly merge subdirectories
# e.g superseed upstream source with that from another branch
git merge -s ours --no-commit other_upstream
git read-tree --reset -u other_upstream     # or use --prefix=foo/
git checkout HEAD -- debian/
git checkout HEAD -- .gitignore
git commit -m 'superseed upstream source' -a

git merge --strategy=theirs Simulation n°7

Les étapes nécessaires peuvent être décrites comme suit :

  1. Remplacez votre arbre de travail en amont
  2. Appliquez les changements à l'index
  3. Ajouter l'amont comme deuxième parent
  4. Engagez-vous

La commande git read-tree écrase l'index avec un arbre différent, ce qui permet d'atteindre l'objectif de l'opération. deuxième étape et dispose de drapeaux pour mettre à jour l'arbre de travail, ce qui permet d'atteindre l'objectif suivant première étape . Lors d'un commit, git utilise le SHA1 dans .git/MERGE_HEAD comme second parent, nous pouvons donc le remplir pour créer un commit de fusion. Par conséquent, ceci peut être accompli avec :

git read-tree -u --reset upstream                 # update files and stage changes
git rev-parse upstream > .git/MERGE_HEAD          # setup merge commit
git commit -m "Merge branch 'upstream' into mine" # commit

8 votes

Vous pouvez toujours utiliser la nôtre au lieu de la leur : vérifiez l'autre branche, fusionnez la vôtre dans celle-ci, puis faites suivre la vôtre de la fusion. git checkout upstream; git merge -s ours downstream; git checkout downstream; git merge upstream . (Utilisez une branche temporaire en amont si nécessaire.) Cela a l'avantage d'enregistrer l'ancêtre amont comme premier parent, de sorte que la fusion signifie "absorber cette branche de sujet obsolète" plutôt que "détruire cette branche de sujet et la remplacer par amont".

0 votes

@Jefromi : excellent point, comme d'habitude. Je l'ai inclus dans ma réponse.

0 votes

Une autre option -- comme git merge --strategy=theirs Simulation #1 -- sauf que celle-ci préserve le brange comme le premier parent de fusion : git checkout -b tmp origin/upstream git merge -s ours downstream # ignorer tous les changements de downstream git checkout downstream git merge --squash tmp # appliquer les changements de tmp mais pas comme merge. git rev-parse upstream > .git/MERGE_HEAD #enregistrer upstream comme la deuxième merge head git commit -m "rebaselined ours from upstream" # fait le commit. git branch -D tmp # supprime tmp

14voto

Vous pouvez le faire assez facilement maintenant :

$ git fetch origin
$ git merge origin/master -s recursive -Xtheirs

Cela permet de synchroniser votre dépôt local avec l'origine, et de préserver l'historique.

13voto

wuputah Points 8189

Il me semble que vous avez juste besoin de faire :

$ git reset --hard origin/master

S'il n'y a pas de changement à pousser vers l'amont, et que vous voulez simplement que la branche amont devienne votre branche courante, ceci fera cela. Il n'est pas dangereux de faire cela localement mais vous perdrez toutes les modifications locales** qui n'ont pas été poussées vers master.

** En fait, les changements sont toujours là si vous les avez validés localement, car les validations seront toujours dans votre fichier git reflog généralement pendant au moins 30 jours.

0 votes

Cela fonctionne si vous avez besoin de cette modification à tout prix, car cela peut modifier l'historique de la branche (vous devez pousser avec -f). cela peut être configuré pour être bloqué, donc fondamentalement cela ne fonctionnera que pour vos propres dépôts privés, en pratique.

9voto

michas Points 6206

Une autre simulation pour git merge -s theirs ref-to-be-merged :

git merge --no-ff -s ours ref-to-be-merged         # enforce a merge commit; content is still wrong
git reset --hard HEAD^2; git reset --soft HEAD@{1} # fix the content
git commit --amend

Une alternative à la double remise à zéro serait d'appliquer le patch inverse :

git diff --binary ref-to-be-merged | git apply -R --index

0 votes

Utilisation intéressante de l'écusson inversé. +1

0 votes

La réinitialisation n'a pas fonctionné pour moi, j'ai eu "fatal : argument ambigu 'HEAD2' : révision inconnue ou chemin ne se trouvant pas dans l'arbre de travail". . (Oui, j'ai tapé HEAD^2 ) La méthode du patch a fonctionné.

0 votes

@Stijn : Il est fort probable que tu n'aies pas tapé ^ correctement. Parfois, d'autres frappes comme "ctrl-c" sont affichées comme "^C". - Si vous avez vraiment tapé le bon "^", vous avez trouvé un bogue sérieux dans votre version de git.

4voto

Michal Soltys Points 21

Il y a aussi une façon de faire avec un peu d'aide de la commande plumbing - à mon avis la plus simple. Disons que vous voulez émuler "leur" pour le cas de 2 branches :

head1=$(git show --pretty=format:"%H" -s foo)
head2=$(git show --pretty=format:"%H" -s bar)
tree=$(git show --pretty=format:"%T" -s bar)
newhead=$(git commit-tree $tree -p $head1 -p $head2 <<<"merge commit message")
git reset --hard $newhead

Ceci fusionne un nombre arbitraire de têtes (2 dans l'exemple ci-dessus) en utilisant l'arbre de l'une d'entre elles (bar dans l'exemple ci-dessus, fournissant 'leur' arbre), sans tenir compte des problèmes de diff/file (commit-tree est une commande de bas niveau, donc elle ne s'en soucie pas). Notez que head peut être seulement 1 (donc équivalent à cherry-pick avec "theirs").

Notez que la tête parente spécifiée en premier peut influencer certaines choses (voir par exemple --first-parent de la commande git-log) - gardez donc cela à l'esprit.

Au lieu de git-show, n'importe quel autre outil capable de produire des hachages d'arbre et de commit peut être utilisé - quel que soit celui que l'on a l'habitude d'analyser (cat-file, rev-list, ...). Vous pouvez suivre le tout avec git commit --amend pour embellir interactivement le message de commit.

0 votes

C'est le plus simple à mes yeux. Vous utilisez des commandes de plomberie pour dire "Créer un nouvel objet commit avec cet arbre, ce premier parent, ce second parent. Puis faites pointer la tête vers ce nouveau commit", ce qui est exactement ce que nous voulons que "git merge -s theirs" fasse. L'inconvénient est que vous devez enregistrer 4 hachages différents pour que cette opération fonctionne.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X