Que fait exactement «rebaser - conserver-fusionner» de git (et pourquoi?)

355

La documentationrebase de Git pour la commande est assez brève:

--preserve-merges
    Instead of ignoring merges, try to recreate them.

This uses the --interactive machinery internally, but combining it
with the --interactive option explicitly is generally not a good idea
unless you know what you are doing (see BUGS below).

Alors, que se passe-t-il réellement lorsque vous utilisez --preserve-merges? En quoi diffère-t-il du comportement par défaut (sans cet indicateur)? Que signifie «recréer» une fusion, etc.

Chris
la source
20
Attention: à partir de Git 2.18 (T2 2018, 5 ans plus tard), il git --rebase-mergesfinira par remplacer l'ancien git --preserve-merges. Voir ma réponse ci
VonC

Réponses:

464

Comme avec un rebase git normal, git with --preserve-mergesidentifie d'abord une liste de validations effectuées dans une partie du graphe de validation, puis relit ces validations au-dessus d'une autre partie. Les différences --preserve-mergesconcernant les validations sont sélectionnées pour la relecture et comment cette relecture fonctionne pour les validations de fusion.

Pour être plus explicite sur les principales différences entre le rebasage normal et le maintien de la fusion:

  • Le rebase préservant la fusion est prêt à rejouer (certains) les validations de fusion, tandis que le rebase normal ignore complètement les validations de fusion.
  • Parce qu'il est prêt à rejouer les validations de fusion, le rebase préservant la fusion doit définir ce que signifie rejouer une validation de fusion et gérer certaines rides supplémentaires.
    • Sur le plan conceptuel, la partie la plus intéressante est peut-être de choisir ce que devraient être les parents de fusion du nouveau commit.
    • La git checkout <desired first parent>relecture des validations de fusion nécessite également de vérifier explicitement des validations particulières ( ), alors que le rebase normal n'a pas à s'en soucier.
  • Le rebase préservant la fusion considère un ensemble de commits moins profond pour la relecture:
    • En particulier, il ne considérera que la relecture des validations effectuées depuis la ou les bases de fusion les plus récentes - c'est-à-dire la date la plus récente de divergence des deux branches -, alors que le rebase normal pourrait rejouer les validations en remontant à la première divergence des deux branches.
    • Pour être provisoire et peu clair, je pense que c'est en fin de compte un moyen de filtrer la relecture des "anciens commits" qui ont déjà été "incorporés dans" un commit de fusion.

Je vais d'abord essayer de décrire "suffisamment exactement" ce que --preserve-mergesfait rebase , puis il y aura quelques exemples. On peut bien sûr commencer par les exemples, si cela semble plus utile.

L'algorithme en "bref"

Si vous voulez vraiment entrer dans les mauvaises herbes, téléchargez la source git et explorez le fichier git-rebase--interactive.sh. (Rebase ne fait pas partie du noyau C de Git, mais est plutôt écrit en bash. Et, dans les coulisses, il partage le code avec "rebase interactif".)

Mais ici, je vais esquisser ce que je pense en être l'essence. Afin de réduire le nombre de choses à penser, j'ai pris quelques libertés. (par exemple, je n'essaie pas de capturer avec une précision de 100% l'ordre précis dans lequel les calculs ont lieu, et d'ignorer certains sujets moins centraux, par exemple ce qu'il faut faire à propos des commits qui ont déjà été choisis entre les branches).

Tout d'abord, notez qu'un rebase sans préservation de fusion est plutôt simple. C'est plus ou moins:

Find all commits on B but not on A ("git log A..B")
Reset B to A ("git reset --hard A") 
Replay all those commits onto B one at a time in order.

Le rebase --preserve-mergesest relativement compliqué. Voici aussi simple que j'ai pu le faire sans perdre des choses qui semblent assez importantes:

Find the commits to replay:
  First find the merge-base(s) of A and B (i.e. the most recent common ancestor(s))
    This (these) merge base(s) will serve as a root/boundary for the rebase.
    In particular, we'll take its (their) descendants and replay them on top of new parents
  Now we can define C, the set of commits to replay. In particular, it's those commits:
    1) reachable from B but not A (as in a normal rebase), and ALSO
    2) descendants of the merge base(s)
  If we ignore cherry-picks and other cleverness preserve-merges does, it's more or less:
    git log A..B --not $(git merge-base --all A B)
Replay the commits:
  Create a branch B_new, on which to replay our commits.
  Switch to B_new (i.e. "git checkout B_new")
  Proceeding parents-before-children (--topo-order), replay each commit c in C on top of B_new:
    If it's a non-merge commit, cherry-pick as usual (i.e. "git cherry-pick c")
    Otherwise it's a merge commit, and we'll construct an "equivalent" merge commit c':
      To create a merge commit, its parents must exist and we must know what they are.
      So first, figure out which parents to use for c', by reference to the parents of c:
        For each parent p_i in parents_of(c):
          If p_i is one of the merge bases mentioned above:
            # p_i is one of the "boundary commits" that we no longer want to use as parents
            For the new commit's ith parent (p_i'), use the HEAD of B_new.
          Else if p_i is one of the commits being rewritten (i.e. if p_i is in R):
            # Note: Because we're moving parents-before-children, a rewritten version
            # of p_i must already exist. So reuse it:
            For the new commit's ith parent (p_i'), use the rewritten version of p_i.
          Otherwise:
            # p_i is one of the commits that's *not* slated for rewrite. So don't rewrite it
            For the new commit's ith parent (p_i'), use p_i, i.e. the old commit's ith parent.
      Second, actually create the new commit c':
        Go to p_1'. (i.e. "git checkout p_1'", p_1' being the "first parent" we want for our new commit)
        Merge in the other parent(s):
          For a typical two-parent merge, it's just "git merge p_2'".
          For an octopus merge, it's "git merge p_2' p_3' p_4' ...".
        Switch (i.e. "git reset") B_new to the current commit (i.e. HEAD), if it's not already there
  Change the label B to apply to this new branch, rather than the old one. (i.e. "git reset --hard B")

Rebaser avec un --onto Cargument devrait être très similaire. Juste au lieu de commencer la lecture de validation à la TÊTE de B, vous commencez plutôt la lecture de validation à la TÊTE de C. (Et utilisez C_new au lieu de B_new.)

Exemple 1

Par exemple, prenez le graphe de validation

  B---C <-- master
 /                     
A-------D------E----m----H <-- topic
         \         /
          F-------G

m est un engagement de fusion avec les parents E et G.

Supposons que nous ayons rebasé le sujet (H) au-dessus du maître (C) à l'aide d'un rebase normal, sans conservation de fusion. (Par exemple, rubrique de paiement; maître de rebase .) Dans ce cas, git sélectionnerait les validations suivantes pour la relecture:

  • choisir D
  • choisir E
  • choisir F
  • choisir G
  • choisir H

puis mettez à jour le graphique de validation comme suit:

  B---C <-- master
 /     \                
A       D'---E'---F'---G'---H' <-- topic

(D 'est l'équivalent rejoué de D, etc.)

Notez que la validation de fusion m n'est pas sélectionnée pour la relecture.

Si nous faisions plutôt une --preserve-mergesrebase de H au-dessus de C. (Par exemple, rubrique de paiement; rebase --preserve-merges master .) Dans ce nouveau cas, git sélectionnerait les validations suivantes pour la relecture:

  • choisir D
  • choisir E
  • choisissez F (sur D 'dans la branche' sous-thème ')
  • choisissez G (sur F 'dans la branche' sous-thème ')
  • choisissez Fusionner la sous-rubrique de la branche dans le sujet
  • choisir H

Maintenant, m a été choisi pour la relecture. Notez également que les parents de fusion E et G ont été sélectionnés pour inclusion avant la validation de fusion m.

Voici le graphe de validation résultant:

 B---C <-- master
/     \                
A      D'-----E'----m'----H' <-- topic
        \          / 
         F'-------G'

Encore une fois, D 'est une version sélectionnée de D. (c'est-à-dire recréée) de D. Idem pour E', etc. Chaque commit qui n'est pas sur master a été rejoué. E et G (les parents de fusion de m) ont été recréés en tant que E 'et G' pour servir de parents de m '(après rebase, l'histoire de l'arbre reste la même).

Exemple 2

Contrairement au rebase normal, le rebase préservant la fusion peut créer plusieurs enfants de la tête en amont.

Par exemple, considérez:

  B---C <-- master
 /                     
A-------D------E---m----H <-- topic
 \                 |
  ------- F-----G--/ 

Si nous rebase H (sujet) au-dessus de C (maître), alors les commits choisis pour rebaser sont:

  • choisir D
  • choisir E
  • choisir F
  • choisir G
  • choisir m
  • choisir H

Et le résultat est comme ça:

  B---C  <-- master
 /    | \                
A     |  D'----E'---m'----H' <-- topic
       \            |
         F'----G'---/

Exemple 3

Dans les exemples ci-dessus, la validation de fusion et ses deux parents sont des validations rejouées, plutôt que les parents d'origine que la validation de fusion d'origine possède. Cependant, dans d'autres rebases, une validation de fusion rejouée peut se retrouver avec des parents qui étaient déjà dans le graphique de validation avant la fusion.

Par exemple, considérez:

  B--C---D <-- master
 /    \                
A---E--m------F <-- topic

Si nous rebasons le sujet sur le maître (préservant les fusions), les validations de relecture seront

  • choisir merge commit m
  • choisir F

Le graphique de validation réécrit ressemblera à ceci:

                     B--C--D <-- master
                    /       \             
                   A-----E---m'--F'; <-- topic

Ici, le commit de fusion rejoué m 'obtient les parents qui préexistaient dans le graphe de commit, à savoir D (la tête de maître) et E (l'un des parents du commit de fusion d'origine m).

Exemple 4

Le rebase préservant la fusion peut être confus dans certains cas de «commit vide». Au moins, cela n'est vrai que pour certaines anciennes versions de git (par exemple 1.7.8.)

Prenez ce graphique de validation:

                   A--------B-----C-----m2---D <-- master
                    \        \         /
                      E--- F--\--G----/
                            \  \
                             ---m1--H <--topic

Notez que les commit m1 et m2 auraient dû incorporer toutes les modifications de B et F.

Si nous essayons de faire git rebase --preserve-mergesde H (sujet) sur D (maître), les commits suivants sont choisis pour être rejoués:

  • choisir m1
  • choisir H

Notez que les changements (B, F) unis dans m1 devraient déjà être incorporés dans D. (Ces changements devraient déjà être incorporés dans m2, car m2 fusionne les enfants de B et F.) Par conséquent, conceptuellement, rejouer m1 au-dessus de D devrait probablement être un no-op ou créer un commit vide (c'est-à-dire un où la différence entre les révisions successives est vide).

Au lieu de cela, cependant, git peut rejeter la tentative de rejouer m1 sur D. Vous pouvez obtenir une erreur comme ceci:

error: Commit 90caf85 is a merge but no -m option was given.
fatal: cherry-pick failed

Il semble que l'on ait oublié de passer un indicateur à git, mais le problème sous-jacent est que git n'aime pas créer des commits vides.

Chris
la source
6
J'ai remarqué que git rebase --preserve-mergesc'est beaucoup plus lent qu'un rebasesans --preserve-merges. Est-ce un effet secondaire de trouver les bons engagements? Peut-on faire quelque chose pour l'accélérer? (Au fait… merci pour la réponse très détaillée!)
David Alan Hjelle
7
Il semble que vous devriez toujours utiliser --preserve-merges. Sinon, il est possible de perdre l'historique, c'est-à-dire que la fusion est validée.
DarVar
19
@DarVar Vous perdez toujours l'historique sur un rebase, car vous prétendez que les modifications ont été apportées à une base de code différente de celle en vigueur.
Chronial
5
Est-ce encore une "réponse provisoire"?
Andrew Grimm
5
@Chronial Bien sûr, vous avez raison, que le rebasage incorpore toujours la perte de l'historique, mais peut-être que DarVar faisait allusion au fait que non seulement vous perdez l'historique, mais que vous modifiez également la base de code. La résolution des conflits contient des informations qui sont perdues de toutes les manières possibles pour un rebasage. Vous devez toujours le refaire. N'y a-t-il vraiment aucun moyen de laisser git refaire votre résolution de conflit? Pourquoi Git ne peut-il pas choisir le commit de fusion?
Nils_M
94

Git 2.18 (Q2 2018) améliorera considérablement l' --preserve-mergeoption en ajoutant une nouvelle option.

" git rebase" appris " --rebase-merges" pour transplanter toute la topologie du graphe de validation ailleurs .

(Remarque: Git 2.22, Q2 2019, devient obsolète --preserve-merge , et Git 2.25, Q1 2020, cesse de le publier dans la git rebase --helpsortie " " )

Voir commettre 25cff9f , engager 7543f6f , engager 1131ec9 , engager 7ccdf65 , engager 537e7d6 , commettre a9be29c , commettre 8f6aed7 , commettre 1644c73 , commettre d1e8b01 , commettre 4c68e7d , engager 9055e40 , engager cb5206e , engager a01c2a5 , engager 2f6b1d1 , engager bf5c057 (25 avril 2018) par Johannes Schindelin ( dscho) .
Voir commit f431d73 (25 avril 2018) par Stefan Beller ( stefanbeller) .
Voir commit 2429335 (25 avril 2018) par Phillip Wood ( phillipwood) .
(Fusionné par Junio ​​C Hamano - gitster- en commit 2c18e6a , 23 mai 2018)

pull: accepter --rebase-mergesde recréer la topologie de branche

Semblable au preservemode passant simplement l' --preserve-merges option à la rebasecommande, le mergesmode passe simplement l' --rebase-mergesoption.

Cela permettra aux utilisateurs de rebaser facilement les topologies de validation non triviales lors de l'extraction de nouvelles validations, sans les aplatir.


git rebaseLa page de manuel contient désormais une section complète dédiée au rebasage de l'historique avec les fusions .

Extrait:

Il existe des raisons légitimes pour lesquelles un développeur peut vouloir recréer des validations de fusion: pour conserver la structure de branche (ou «validation de la topologie») lorsqu'il travaille sur plusieurs branches liées entre elles.

Dans l'exemple suivant, le développeur travaille sur une branche de rubrique qui réorganise la façon dont les boutons sont définis, et sur une autre branche de rubrique qui utilise cette refactorisation pour implémenter un bouton "Signaler un bogue".
La sortie de git log --graph --format=%s -5peut ressembler à ceci:

*   Merge branch 'report-a-bug'
|\
| * Add the feedback button
* | Merge branch 'refactor-button'
|\ \
| |/
| * Use the Button class for all buttons
| * Extract a generic Button class from the DownloadButton one

Le développeur peut vouloir rebaser ces validations vers une version plus récente master tout en conservant la topologie de branche, par exemple lorsque la première branche de sujet devrait être intégrée masterbeaucoup plus tôt que la seconde, par exemple, pour résoudre les conflits de fusion avec les modifications apportées à la DownloadButtonclasse qui a fait il en master.

Ce rebase peut être effectué à l'aide de l' --rebase-mergesoption.


Voir commit 1644c73 pour un petit exemple:

rebase-helper --make-script: introduire un drapeau pour rebaser les fusions

Le séquenceur vient d'apprendre de nouvelles commandes destinées à recréer la structure des branches ( similaire dans l'esprit --preserve-merges, mais avec une conception sensiblement moins cassée ).

Laissons le rebase--helpergénérer des listes de tâches en utilisant ces commandes, déclenchées par la nouvelle --rebase-mergesoption.
Pour une topologie de validation comme celle-ci (où le HEAD pointe vers C):

- A - B - C (HEAD)
    \   /
      D

la liste de tâches générée ressemblerait à ceci:

# branch D
pick 0123 A
label branch-point
pick 1234 D
label D

reset branch-point
pick 2345 B
merge -C 3456 D # C

Quelle est la différence avec --preserve-merge?
Le commit 8f6aed7 explique:

Il était une fois, ce développeur pensait: ce ne serait pas bien si, disons, les correctifs de Git pour Windows au-dessus du noyau Git pouvaient être représentés comme un bosquet de branches, et être rebasés au-dessus du noyau Git afin de maintenir un ensemble de patchs sélectionnables?

La tentative originale pour répondre à c'était: git rebase --preserve-merges.

Cependant, cette expérience n'a jamais été conçue comme une option interactive, et elle n'a été soutenue git rebase --interactiveque parce que l'implémentation de cette commande semblait déjà très, très familière: elle a été conçue par la même personne qui a conçu --preserve-merges: la vôtre vraiment.

Et par "le vôtre vraiment", l'auteur se réfère à lui-même: Johannes Schindelin ( dscho) , qui est la raison principale (avec quelques autres héros - Hannes, Steffen, Sebastian, ...) que nous avons Git pour Windows (même si à l'époque - 2009 - ce n'était pas facile ).
Il travaille chez Microsoft depuis septembre 2015 , ce qui est logique étant donné que Microsoft utilise désormais fortement Git et a besoin de ses services.
Cette tendance a commencé en 2013 avec TFS . Depuis lors, Microsoft gère le plus grand référentiel Git de la planète ! Et, depuis octobre 2018, Microsoft a acquis GitHub .

Vous pouvez voir Johannes parler dans cette vidéo pour Git Merge 2018 en avril 2018.

Quelque temps plus tard, un autre développeur (je vous regarde, Andreas! ;-)) a décidé que ce serait une bonne idée de permettre --preserve-mergesd'être combiné avec --interactive(avec des mises en garde!) Et le mainteneur Git (enfin, le mainteneur intérimaire Git pendant l'absence de Junio, c'est-à-dire) d'accord, et c'est alors que le glamour du --preserve-mergesdesign a commencé à s'effondrer assez rapidement et sans glamour.

Ici, Jonathan parle d' Andreas Schwab de Suse.
Vous pouvez voir certaines de leurs discussions en 2012 .

La raison? En --preserve-mergesmode, les parents d'un commit de fusion (ou d'ailleurs, de n'importe quel commit) n'étaient pas indiqués explicitement, mais étaient impliqués par le nom de commit passé à la pickcommande .

Cela a rendu impossible, par exemple, la réorganisation des commits .
Sans oublier de déplacer les commits entre les branches ou, Dieu ne plaise, de diviser les branches du sujet en deux.

Hélas, ces lacunes ont également empêché ce mode (dont le but initial était de répondre aux besoins de Git pour Windows, avec l'espoir supplémentaire qu'il pourrait être utile à d'autres aussi) de répondre aux besoins de Git pour Windows.

Cinq ans plus tard, quand il est devenu vraiment intenable d'avoir une grande série de patchs Hodge-Podge peu maniables de Git pour Windows partiellement liés et partiellement non liés qui a été rebasé de temps en temps sur les balises Git de base (gagnant la colère imméritée du développeur de la git-remote-hgsérie malheureuse qui a d'abord obsédé l'approche concurrentielle de Git pour Windows, pour ensuite être abandonnée sans responsable plus tard) était vraiment intenable, les " cisailles de jardin Git " étaient nées : un script, le ferroutage au-dessus du rebase interactif, qui déterminerait d'abord la topologie de branche des correctifs à rebaser, créerait une pseudo liste de tâches à éditer, transformerait le résultat en une véritable liste de tâches (faisant un usage intensif de laexec pour "implémenter" les commandes de liste de tâches manquantes) et enfin recréer la série de correctifs au-dessus du nouveau commit de base.

(Le script Git garden shears est référencé dans ce patch dans commit 9055e40 )

C'était en 2013.
Et il a fallu environ trois semaines pour arriver à la conception et l'implémenter en tant que script hors arbre. Inutile de dire que la mise en œuvre a eu besoin de quelques années pour se stabiliser, tout en garantissant que la conception elle-même a fait ses preuves.

Avec ce patch, la bonté des ciseaux de jardin Git revient à git rebase -ielle-même .
Passer l' --rebase-mergesoption générera une liste de tâches qui peut être facilement comprise et où il est évident de réorganiser les commits .
De nouvelles branches peuvent être introduites en insérant des labelcommandes et en appelant merge <label>.
Et une fois que ce mode sera devenu stable et universellement accepté, nous pouvons déprécier l'erreur de conception qui était--preserve-merges .


Git 2.19 (Q3 2018) améliore la nouvelle --rebase-mergesoption en la faisant fonctionner avec --exec.

L' --execoption " " pour " git rebase --rebase-merges" a placé les commandes exec aux mauvais endroits, ce qui a été corrigé.

Voir commit 1ace63b (09 août 2018) et commit f0880f7 (06 août 2018) par Johannes Schindelin ( dscho) .
(Fusionné par Junio ​​C Hamano - gitster- dans commit 750eb11 , 20 août 2018)

rebase --exec: faire fonctionner avec --rebase-merges

L'idée --execest d'ajouter un execappel après chacun pick.

Depuis l'introduction de fixup!/ s quash!commits, cette idée a été étendue pour s'appliquer à "pick, éventuellement suivi d'une chaîne fixup / squash", c'est-à-dire qu'un exec ne serait pas inséré entre a picket aucune de ses lignes fixupou correspondantes squash.

L'implémentation actuelle utilise une sale astuce pour y parvenir: elle suppose qu'il n'y a que des commandes pick / fixup / squash, puis insère les execlignes avant tout picksauf la première, et ajoute une dernière.

Avec les listes de tâches générées par git rebase --rebase-merges, cette implémentation simple montre ses problèmes: elle produit exactement la mauvaise chose quand il y en a label, resetet les mergecommandes.

Modifions l'implémentation pour faire exactement ce que nous voulons: rechercher des picklignes, ignorer les chaînes de correction / squash, puis insérer la exec ligne . Faire mousser, rincer, répéter.

Remarque: nous nous efforçons d'insérer avant les lignes de commentaire chaque fois que possible, car les validations vides sont représentées par des lignes de sélection commentées (et nous voulons insérer la ligne d'exécution d'un choix précédent avant une telle ligne, pas après).

Pendant que vous y êtes , ajoutez également des execlignes après les mergecommandes, car elles sont similaires dans leur esprit aux pickcommandes: elles ajoutent de nouveaux commits.


Git 2.22 (Q2 2019) corrige l'utilisation de refs / rewritten / hierarchy pour stocker un état intermédiaire de rebase, ce qui rend intrinsèquement la hiérarchie par arbre de travail.

Voir commit b9317d5 , commit 90d31ff , commit 09e6564 (07 mars 2019) par Nguyễn Thái Ngọc Duy ( pclouds) .
(Fusionné par Junio ​​C Hamano - gitster- dans commit 917f2cd , 09 avr.2019 )

Assurez-vous que refs / réécrit / est per-worktree

a9be29c (séquenceur: créer des références générées par la labelcommande worktree-local, 2018-04-25, Git 2.19) ajoute refs/rewritten/comme espace de référence par arbre de travail.
Malheureusement (ma mauvaise), il y a quelques endroits qui ont besoin d'une mise à jour pour s'assurer que c'est vraiment par arbre de travail.

- add_per_worktree_entries_to_dir()est mis à jour pour s'assurer que la liste des références examine l'arborescence par travail refs/rewritten/plutôt que celle par repo.

  • common_list[]est mis à jour afin de git_path()renvoyer l'emplacement correct. Cela inclut " rev-parse --git-path".

Ce gâchis est créé par moi.
J'ai commencé à essayer de le réparer avec l'introduction de l' refs/worktree,endroit où toutes les références seront par arbre de travail sans traitements spéciaux.
Des références malheureuses / réécrites sont venues avant refs / worktree donc c'est tout ce que nous pouvons faire.


Avec Git 2.24 (Q4 2019), " git rebase --rebase-merges" a appris à piloter différentes stratégies de fusion et à leur transmettre des options spécifiques.

Voir commit 476998d (04 sept. 2019) par Elijah Newren ( newren) .
Voir commettre e1fac53 , engager a63f990 , engager 5dcdd74 , engager e145d99 , engager 4e6023b , engager f67336d , engager a9c7107 , engager b8c6f24 , engager d51b771 , engager c248d32 , engager 8c1e240 , engager 5efed0e , engager 68b54f6 , engager 2e7bbac , engager 6180b20 , engager d5b581f (31 Juil.2019) parJohannes Schindelin ( dscho) .
(Fusionné par Junio ​​C Hamano - gitster- en commit 917a319 , 18 sept. 2019)


Avec Git 2.25 (T1 2020), la logique utilisée pour différencier les références globales locales et référentielles de Worktree est fixe, pour faciliter la conservation-fusion.

Voir commit f45f88b , commit c72fc40 , commit 8a64881 , commit 7cb8c92 , commit e536b1f (21 octobre 2019) par SZEDER Gábor ( szeder) .
(Fusionné par Junio ​​C Hamano - gitster- dans commit db806d7 , 10 nov.2019 )

path.c: n'appelle pas la matchfonction sans valeur danstrie_find()

Signé par: SZEDER Gábor

'logs / refs' n'est pas un chemin spécifique à l'arborescence de travail, mais depuis la validation b9317d55a3 (assurez-vous que refs / réécrit / est par arbre de travail, 2019-03-07, v2.22.0-rc0) ' git rev-parse --git-path' a renvoyé un faux chemin si un ' /' de fin est présent:

$ git -C WT/ rev-parse --git-path logs/refs --git-path logs/refs/
/home/szeder/src/git/.git/logs/refs
/home/szeder/src/git/.git/worktrees/WT/logs/refs/

Nous utilisons une triestructure de données pour décider efficacement si un chemin appartient au répertoire commun ou s'il est spécifique à l'arborescence.

En l' occurrence, b9317d55a3 a déclenché un bogue aussi ancien que l' trieimplémentation elle-même, ajouté dans 4e09cf2acf (" path: optimisation de la vérification du répertoire commun", 2015-08-31, Git v2.7.0-rc0 - fusion répertoriée dans le lot # 2 ).

  • Selon le commentaire décrivant trie_find(), il ne devrait appeler la fonction de correspondance donnée 'fn' que pour un "préfixe terminé par / / ou - \ 0 de la clé pour laquelle le trie contient une valeur".
    Ce n'est pas vrai: il y a trois endroits où trie_find () appelle la fonction de correspondance, mais l'un d'eux manque la vérification de l'existence de la valeur.

  • b9317d55a3 a ajouté deux nouvelles clés à trie:

    • ' logs/refs/rewritten', et
    • ' logs/refs/worktree', à côté de l'existant ' logs/refs/bisect'.
      Cela a abouti à un trienœud avec le chemin d'accès ' logs/refs/', qui n'existait pas auparavant et auquel aucune valeur n'est attachée.
      Une requête pour ' logs/refs/' trouve ce nœud, puis frappe ce site d'appel de la matchfonction qui ne vérifie pas l'existence de la valeur, et appelle ainsi la matchfonction avec NULLcomme valeur.
  • Lorsque la matchfonction check_common()est invoquée avec une NULLvaleur, elle retourne 0, ce qui indique que le chemin interrogé n'appartient pas au répertoire commun, résultant finalement le faux chemin indiqué ci-dessus.

Ajoutez la condition manquante à trie_find()afin qu'elle n'invoque jamais la fonction de correspondance avec une valeur inexistante.

check_common() n'aura alors plus à vérifier qu'elle a obtenu une valeur non NULL, donc supprimez cette condition.

Je crois qu'il n'y a aucun autre chemin qui pourrait provoquer une sortie bidon similaire.

AFAICT la seule autre clé entraînant l'appel de la fonction de correspondance avec une NULLvaleur est ' co' (en raison des touches ' common' et ' config').

Cependant, comme ils ne se trouvent pas dans un répertoire qui appartient au répertoire commun, le chemin spécifique à l'arborescence de travail résultant est attendu.

VonC
la source
3
Je pense que cela devrait être la meilleure réponse, --preserve-mergesne "conserve" pas réellement les fusions comme vous le souhaitez, c'est très naïf. Cela vous permet de conserver les validations de fusion et leurs relations de validation parentes tout en vous offrant la flexibilité d'un rebase interactif. Cette nouvelle fonctionnalité est géniale et sinon pour cette réponse SO bien écrite, je ne l'aurais pas su!
egucciar
@egucciar Merci. Et ce n'est pas la seule fonctionnalité de Git 2.18 ( stackoverflow.com/search?q=user%3A6309+%22git+2.18%22 ), et Git 2.19 ( stackoverflow.com/search?q=user%3A6309+%22git+2.19% 22 )
VonC
1
Extrêmement utile si vous essayez de déplacer des morceaux de commits comme dans ce Q / A, stackoverflow.com/questions/45059039/…
okovko
1
Oh, c'est vraiment ce que je cherchais depuis un moment! J'avais une solution de contournement manuelle pour des cas comme celui sur lequel il fallait créer un commit fictif joignant toutes les fusions.
carnicer
Git typique. Osez poser une question simple et vous devrez probablement apprendre l'histoire de Git, les algorithmes internes, tous les détails d'implémentation en désordre et vous avez également besoin d'une majeure en théorie des graphes pour comprendre ce qui se passe.
Dimitris