Skip to content

Comment modifier le point de départ d'une branche ?

Nos chercheurs vedettes ont épuisé leurs réserves de café, cherchant jour et nuit la solution, jusqu'à ce qu'Armando trouve la solution sur GitLab. Aujourd'hui, il la partage avec vous.

Solution :

La réponse courte est qu'une fois que vous avez quelques commits, vous voulez... git rebase les, en utilisant la forme longue de git rebase: git rebase --onto newbaseupstream. Pour savoir comment identifier chacun d'eux, voir la (très) longue réponse ci-dessous. (Malheureusement, elle s'est un peu emballée et je n'ai pas le temps de la raccourcir).

Le problème que vous avez ici est que, dans Git, les branches ne sont pas ont un "point de départ" - du moins, pas d'une manière utile.

Le terme "branche", dans Git, est ambigu.

Le premier problème ici est que, dans Git, le mot "branche" a au moins deux significations distinctes. Habituellement, lorsque nous parlons vaguement de "la branche", il est clair, d'après le contexte, si nous voulons dire la branche... nom-la chose qui est un mot comme master ou develop ou feature-X-ou la chose que j'appelle "ascendance de branche" ou "structure de branche", ou plus informellement, un "DAGlet".1 Voir aussi Qu'est-ce qu'on entend exactement par "branche" ?

Dans ce cas particulier, malheureusement, vous voulez dire les deux, en même temps.


1 Le terme DAG est l'abréviation de Directed Acyclic Graph, ce qu'est le graphe de commit : un ensemble de sommets ou de nœuds, et d'arêtes directionnelles (d'enfant à parent), tel qu'il n'y a pas de cycles à travers les arêtes dirigées de n'importe quel nœud vers lui-même. À cela, j'ajoute simplement le suffixe diminutif "-let". Le mot résultant a une heureuse ressemblance avec le mot aglet, plus une certaine assonance avec le mot "dagger", ce qui le fait sonner légèrement dangereux : "Is this a DAGlet which I see before me ?".

Dessinez votre graphe de commit

Chaque fois que vous devez vous débattre avec ces questions, il est utile de dessiner un graphique de ce que vous avez maintenant, ou au moins un sous-ensemble utile de ce que vous avez maintenant. Il y a bien sûr de nombreuses façons de dessiner cela (voir cette question liée pour plusieurs options, y compris certaines mauvaises 🙂 ), mais en texte brut dans une réponse StackOverflow, je les dessine généralement comme ceci :

...--o--o--o           <-- master
         
          o--o--o--o   <-- develop

Le tour o représentent commits et les noms des branches master et develop pointent vers une branche spécifique pointe commit sur chaque branche.

Dans Git, chaque commit pointe vers son ou ses commit(s) parent(s), et c'est ainsi que Git forme des structures de branches. Par "structures de branches", j'entends ici des sous-ensembles particuliers de la partie globale des ancêtres du graphe, ou ce que j'appelle les DAGlets. Le nom master pointe vers le commit le plus en pointe de la branche master et ce commit pointe en arrière (vers la gauche) vers un autre commit qui est le commit précédent sur la branche, et ce commit pointe à nouveau vers la gauche, et ainsi de suite.

Lorsque nous avons besoin de parler de commits spécifiques dans ce graphique, nous pouvons utiliser leurs noms réels, qui sont les gros hashs laids de 40 caractères qui identifient chaque objet Git. Ceux-ci sont vraiment maladroits cependant, donc ce que je fais ici est de remplacer le petit rond. opar des lettres majuscules :

...--A--B--C           <-- master
         
          D--E--F--G   <-- develop

et maintenant il est facile de dire, par exemple, que le nom master pointe vers le commit Cet que C pointe vers Bet B pointe vers Aqui renvoie à une autre histoire dont nous ne nous soucions pas vraiment et que nous avons donc laissée telle quelle. ....

Où commence une branche ?

Maintenant, il est parfaitement évident, pour vous et moi, sur la base de ce graphique, que la branche developdont le dernier commit est Gcommence au commit D. Mais ce n'est pas évident pour Git-et si nous dessinons le même graphique un peu différemment, cela peut être moins évident pour vous et moi aussi. Par exemple, regardez ce dessin :

          o             <-- X
         /
...--o--o--o--o--o--o   <-- Y

Branche évidente X n'a qu'un seul commit et la ligne principale est Yn'est-ce pas ? Mais mettons des lettres :

          C             <-- X
         /
...--A--B--D--E--F--G   <-- Y

et ensuite déplaçons Y sur une ligne :

          C            <-- X
         /
...--A--B
         
          D--E--F--G   <-- Y

et regardez ce qui se passe si on déplace C vers la ligne principale, et que nous réalisons que X est master et Y est develop? Quelle branche est commettre B sur après tout ?

Dans Git, les commits peuvent être sur plusieurs branches simultanément ; les DAGlets sont à votre disposition.

La réponse de Git à ce dilemme est que les commits A et B sont sur les deux branches. Le début de la branche X est très loin sur la gauche, dans la branche ... partie. Mais aussi le début de la branche Y. En ce qui concerne Git, une branche "commence" à n'importe quel commit(s) racine(s) qu'elle peut trouver dans le graphe.

Il est important de garder cela à l'esprit en général. Git n'a pas de réel concept de l'endroit où une branche "commence", donc nous finissons par devoir lui donner des informations supplémentaires. Parfois cette information est implicite, et parfois elle est explicite. Il est également important, en général, de se rappeler que les commits sont souvent sur plusieurs donc au lieu de spécifier des branches, nous spécifions habituellement des commits.

Nous utilisons souvent les noms de branches pour faire cela. Mais si nous donnons à Git juste un nom de branche, et que nous lui disons de trouver tous les ancêtres du dernier commit de cette branche, Git remonte tout le long de l'histoire.

Dans votre cas, si vous écrivez le nom develop et demandez à Git de sélectionner ce commit et ses ancêtres, vous obtenez les commits D-E-F-G (que vous vouliez) et commettre B, et commettre Aet ainsi de suite (ce que vous n'avez pas fait). L'astuce, alors, est d'identifier d'une manière ou d'une autre les commits que vous avez n'avez pas voulez, ainsi que les commits que vous faites.

Normalement, nous utilisons les deux points X..Y syntaxe

Avec la plupart des commandes Git, lorsque nous voulons sélectionner un certain DAGlet particulier, nous utilisons la syntaxe à deux points décrite dans gitrevisions, par exemple. master..develop. La plupart des 2 Les commandes Git qui travaillent sur plusieurs commits traitent cela comme : " Sélectionner tous les commits à partir de la pointe de la develop . master mais ensuite soustraire de cet ensemble, l'ensemble de tous les commits commençant à la pointe de la branche master branch." Regardez en arrière sur notre dessin de graphique de developet G cela dit "prenez les commits à partir de et en remontant en arrière" - ce qui nous donne trop de B puisqu'il inclut les commits A et et plus tôt-"mais exclure C Commits à partir de et en remontant le temps." C'est que exclure

partie qui nous permet d'obtenir ce que nous voulons. master..develop Ainsi, en écrivant D-E-F-Gest la façon dont nous nommons les commits


2 et que Git calcule cela automatiquement pour nous, sans avoir à s'asseoir d'abord et à dessiner un gros morceau du graphe. git rebaseDeux exceptions notables sont git diff, qui est dans sa propre section juste en dessous, et git diff . Le site X..Y traite la commande X Ycomme signifiant simplement git diff master..develop c'est-à-dire qu'elle ignore complètement les deux points. Notez que cela a un effet très différent de la soustraction de l'ensemble : dans notre cas, C diffère simplement l'arbre pour le commit Gcontre l'arbre du commit master..develop même si C n'a jamais fait de commit

dans le premier ensemble. master..develop En d'autres termes, mathématiquement parlant, ancestors(develop) - ancestors(master)est normalement ancestors où le ancestors(develop) inclut le commit spécifié, c'est-à-dire qu'elle teste ≤ plutôt que seulement <. Notez que la fonction n'inclut pas le commit C du tout. L'opération de soustraction de l'ensemble ignore simplement la présence de C dans l'ensemble ancestors(master). Mais quand on alimente cet ensemble avec git diff, il ne fait pas ignore C: il ne diffère pas, disons B contre G. Bien que cela puisse être une chose raisonnable à faire, git diff vole plutôt le trois-dot master...develop syntaxe pour accomplir ceci.

Git rebase est un peu spéciale

Le site rebase est presque toujours utilisée pour déplacer 3 un de ces sous-ensembles de commit de DAGlet d'un point du graphe à un autre. En fait, c'est ce que rebase est, ou était à l'origine en tout cas, défini pour faire. (Maintenant, il a une fantaisie interactive rebase mode, qui fait cela et un tas d'autres opérations d'édition de l'historique. Mercurial a une commande similaire, hg histedit, avec un nom légèrement meilleur, et une sémantique par défaut beaucoup plus serrée.4)

Puisque nous voulons toujours (ou presque toujours) déplacer un DAGlet, git rebase construit cette sélection de sous-ensembles pour nous. Et, puisque nous voulons toujours (ou presque toujours) déplacer le DAGlet pour qu'il vienne juste après la pointe d'un certain... autre branche, git rebase choisit par défaut la cible (ou --onto) commit en utilisant un nom de branche, et utilise ensuite ce même nom de branche dans la branche X..Y syntaxe.5


3 Techniquement, git rebase en fait copie commits, plutôt que de les déplacer. Il le faut, car les commits sont immuables comme tous les objets internes de Git. Le vrai nom, le hachage SHA-1, d'un commit est une somme de contrôle des bits composant le commit, donc chaque fois que vous changez quoi que ce soit - y compris quelque chose d'aussi simple que l'ID du parent - vous devez faire une modification du commit. nouveau légèrement différent, commit.

4 Dans Mercurial, tout à fait à la différence de Git, les branches sont réellement font ont des points de départ, et - plus important encore pour histedit-les engagements enregistrent leurs phase: secret, brouillon, ou publié. L'édition de l'historique s'applique volontiers aux commits en phase de secret ou de brouillon, et pas tellement aux commits publiés. C'est également vrai pour Git, mais comme Git n'a pas de concept de phases de commit, le rebasement de Git doit utiliser ces autres techniques.

5 Techniquement, le et --onto peuvent simplement être des ID de commit bruts. Notez que 1234567..develop fonctionne très bien comme un sélecteur de plage, et vous pouvez rebaser --onto 1234567 pour placer les nouveaux commits après le commit 1234567. Le seul endroit où git rebase a vraiment besoin d'une branche nom est pour le nom de la branche courante, qu'il lit normalement simplement à partir de... HEAD de toute façon. Cependant, nous avons l'habitude de voulons d'utiliser un nom, c'est donc ainsi que je décris tout cela ici.


C'est-à-dire, si nous sommes actuellement sur la branche develop, et dans cette situation que nous avons dessiné avant :

...--A--B--C           <-- master
         
          D--E--F--G   <-- develop

nous voulons probablement juste déplacer la branche D-E-F-G sur le bout de la chaîne masterpour obtenir ceci :

...--A--B--C              <-- master
            
             D'-E'-F'-G'  <-- develop

(La raison pour laquelle j'ai changé les noms de D-E-F-G à D'-E'-F'-G' est que rebase est forcé de copier les commits originaux, plutôt que de les déplacer réellement. Les nouvelles copies sont tout aussi bonnes que les originales, et nous pouvons utiliser le même nom à une seule lettre, mais nous devrions au moins noter, même vaguement, que ce sont en fait des copies. C'est ce que font les marques "prime", les marques ' sont pour cela).

Parce que c'est ce que nous voulons généralement, git rebase le fera automatiquement si nous nommons simplement le autre branche. C'est-à-dire que nous sommes sur develop maintenant :

$ git checkout develop

et nous voulons rebaser les commits qui sont sur la branche develop et qui sont pas sur masteren les déplaçant vers l'extrémité de master. On pourrait l'exprimer comme suit git somecmd master..develop mastermais nous devrions alors taper le mot master deux fois (un sort si redoutable). Donc, à la place, Git est rebase déduit ceci lorsque nous tapons simplement :

$ git rebase master

Le nom master devient le côté gauche des deux points .. DAGlet, et le nom masterégalement devient la cible du rebase; et Git rebase ensuite D-E-F-G sur C. Git récupère la branche nom, develop, en lisant le nom de la branche courante. En fait, il utilise un raccourci, à savoir que lorsque vous avez besoin du nom de la branche courante, vous pouvez normalement juste écrire HEAD à la place. Ainsi, master..develop et master..HEAD signifient la même chose, car HEADestdevelop.

de Git rebase appelle ce nom le . C'est-à-dire que, lorsque nous disons git rebase masterGit prétend, dans la documentation, que master est le argument de git rebase. La commande rebase opère alors sur les commits dans ..HEADen les copiant après n'importe quel commit se trouvant dans .

Cela va devenir un problème pour nous bientôt, mais pour l'instant, il suffit d'en prendre note.

(Rebase a aussi la caractéristique latérale sournoise, mais souhaitable, d'omettre tout ce qui est de l'ordre du D-E-F-G qui ressemble suffisamment au commit C. Pour nos objectifs, nous pouvons ignorer cela).

Quel est le problème avec l'autre réponse à cette question.

Au cas où l'autre réponse serait supprimée, ou deviendrait l'une de plusieurs autres réponses, je la résumerai ici comme "utiliser". git branch -f pour déplacer l'étiquette de la branche". La faille dans l'autre réponse - et, peut-être plus important, précisément. lorsque c'est un problème - devient évident une fois que nous dessinons notre graphe DAGlets.

Les noms de branche sont uniques, mais les commits de pointe ne le sont pas nécessairement.

Jetons un coup d'œil à ce qui se passe lorsque vous exécutez... git checkout -b newbranch starting-point. Cela demande à Git d'effectuer une recherche dans le graphe actuel pour le point de départ donné, et de faire en sorte que la nouvelle étiquette de branche pointe vers ce commit spécifique. (Je sais que j'ai dit plus haut que les branches ne sont pas ont un point de départ. C'est encore en grande partie vrai : nous donnons aux git checkout un point de départ maintenant mais Git est sur le point de le fixer et ensuite, point crucial, oublie it.) Disons que starting-point est un autre nom de branche, et dessinons tout un tas de branches :

          o--o--o--o     <-- brA
         /
...--o--o--o--o--o--o    <-- brB
            
             o--o--o     <-- brC
                 
                  o--o   <-- brD

Puisque nous avons quatre branches noms de branches nous avons quatre noms de branches conseils: quatre commits branch-tip, identifiés par les noms brA à brD. Nous en choisissons un et créons un nouveau nom de branche newbranch qui pointe vers la branche même commit que l'un de ces quatre. J'ai choisi arbitrairement brA ici :

          o--o--o--o     <-- brA, newbranch
         /
...--o--o--o--o--o--o    <-- brB
            
             o--o--o     <-- brC
                 
                  o--o   <-- brD

Nous avons maintenant cinq noms, et cinq ... euh, quatre ? ... bien, quelques tip commits. Le point délicat est que brA et newbranch pointent tous deux vers le même tip commit.

Git sait parce que git checkout le définit-que nous sommes maintenant sur newbranch. Spécifiquement, Git écrit le nom newbranch dans HEAD. Nous pouvons rendre notre dessin un peu plus précis en ajoutant cette information :

          o--o--o--o     <-- brA, HEAD -> newbranch
         /
...--o--o--o--o--o--o    <-- brB
            
             o--o--o     <-- brC
                 
                  o--o   <-- brD

À ce stade, les quatre commits qui se trouvaient uniquement sur la branche brA sont maintenant sur les deux brA et newbranch. Et, par la même occasion, Git ne sait plus que newbranch commence à l'extrémité de brA. En ce qui concerne Git, les deux brA et newbranch contiennent ces quatre commits et tous les précédents aussi, et tous deux " commencent " loin dans le temps, quelque part.

Quand nous faisons de nouveaux commits, les nom actuel se déplace

Puisque nous sommes sur la branche newbranch, si nous faisons un nouveau commit maintenant, le parent du nouveau commit sera l'ancien tip commit, et Git ajustera le nom de la branche newbranch pour pointer vers le nouveau commit :

                     o   <-- HEAD -> newbranch
                    /
          o--o--o--o     <-- brA
         /
...--o--o--o--o--o--o    <-- brB
            
             o--o--o     <-- brC
                 
                  o--o   <-- brD

Notez qu'aucune des autres étiquettes n'a bougé : les quatre "anciennes" branches restent en place, seule la branche actuelle (HEAD) change. Elle change pour s'adapter au nouveau commit que nous venons de faire.

Notez que Git continue à n'avoir aucune idée que la branche newbranch a "commencé" à brA. C'est juste le cas, maintenant, que newbranch contient un commit que brA pas, plus les quatre commits qu'ils contiennent tous les deux, plus tous ces commits précédents.

Ce que git branch -f does

Utilisation de git branch -f nous permet de déplacer une étiquette de branche. Disons que, pour une raison mystérieuse, nous ne voulons pas de l'étiquette de branche brB pointe là où elle le fait dans notre dessin actuel. Au lieu de cela, nous voulons qu'elle pointe vers le même commit que brC. Nous pouvons utiliser git branch -f pour changer le lieu vers lequel brB pointe, c'est-à-dire déplacer l'étiquette :

$ git branch -f brB brC

                     o   <-- HEAD -> newbranch
                    /
          o--o--o--o     <-- brA
         /
...--o--o--o--o--o--o    [abandoned]
            
             o--o--o     <-- brC, brB
                 
                  o--o   <-- brD

Cela fait que Git "oublie" ou "abandonne" ces trois commits qui n'étaient que sur brB auparavant. C'est probablement une mauvaise idée - pourquoi a fait nous avons décidé de faire cette chose étrange ? - donc nous voulons probablement mettre brB en arrière.

Reflogs

Heureusement, les commits "abandonnés" sont normalement mémorisés dans ce que Git appelle reflogs. Les reflogs utilisent une syntaxe étendue, [email protected]{selector}. Le site sélectionneur est généralement un nombre ou une date, comme par exemple [email protected]{1} ou [email protected]{yesterday}. Chaque fois que Git met à jour un nom de branche pour pointer vers un commit, il écrit une entrée reflog pour cette branche, avec l'ID du commit pointé, un horodatage et un message optionnel. Exécutez git reflog brB pour les voir. Le site git branch -f a écrit la nouvelle cible comme [email protected]{0}en augmentant tous les anciens numéros, donc maintenant [email protected]{1} nomme le précédent précédent. Donc :

$ git branch -f brB '[email protected]{1}'
    # you may not need the quotes, '[email protected]{...}' --
    # I need them in my shell, otherwise the shell
    # eats the braces.  Some shells do, some don't.

le remettra en place (et renumérotera également tous les numéros à nouveau : chaque mise à jour remplace l'ancienne version de @{0} et le rend @{1}et @{1} devient @{2}et ainsi de suite).

Bref, supposons que nous fassions notre git checkout -b newbranch pendant que nous sommes sur brCet qu'on oublie de mentionner brA. C'est-à-dire qu'on commence par :

          o--o--o--o     <-- brA
         /
...--o--o--o--o--o--o    <-- brB
            
             o--o--o     <-- HEAD -> brC
                 
                  o--o   <-- brD

et on exécute git checkout -b newbranch. Ensuite, nous obtenons ceci :

          o--o--o--o     <-- brA
         /
...--o--o--o--o--o--o    <-- brB
            
             o--o--o     <-- brC, HEAD -> newbranch
                 
                  o--o   <-- brD

Si nous voulait dire de faire newbranch point pour commettre brAnous pouvons en fait le faire dès maintenant, avec git branch -f. Mais disons que nous faisons un nouveau commit avant de réaliser que nous avons fait newbranch au mauvais endroit. Dessinons-le :

          o--o--o--o     <-- brA
         /
...--o--o--o--o--o--o    <-- brB
            
             o--o--o     <-- brC
                   
                 |   o   <-- HEAD -> newbranch
                 
                  o--o   <-- brD

Si nous utilisons git branch -f maintenant, nous allons abandonner - perdre - le commit que nous venons de faire. Ce que nous voulons à la place c'est le rebaser, sur le commit qui branche brA pointe vers.

Un simple git rebase copie trop

Et si, au lieu d'utiliser git branch -fnous utilisions git rebase brA? Analysons cela en utilisant - quoi d'autre - nos DAGlets. Nous commençons avec le dessin ci-dessus, avec la jambe étendue allant vers brDbien qu'à la fin nous puissions ignorer cette jambe, et avec la section allant jusqu'à brBdont nous pouvons également ignorer la majeure partie. Ce que nous ne pouvons pas ignorer, c'est tout ce qui se trouve au milieu, que nous obtenons en retraçant les lignes.

Le git rebase sous cette forme, utilisera brA..newbranch pour choisir les commits à copier. Ainsi, en commençant par l'ensemble du DAGlet, marquons (avec *) tous les commits qui sont sur (ou contenus dans) newbranch:

          o--o--o--o     <-- brA
         /
...--*--*--*--o--o--o    <-- brB
            
             *--*--*     <-- brC
                   
                 |   *   <-- HEAD -> newbranch
                 
                  o--o   <-- brD

Maintenant, dé-marquons (avec x) tous les commits qui sont sur (ou contenus dans) brA:

          x--x--x--x     <-- brA
         /
...--x--x--*--o--o--o    <-- brB
            
             *--*--*     <-- brC
                   
                 |   *   <-- HEAD -> newbranch
                 
                  o--o   <-- brD

Ce qui reste - tous les * commits - sont ceux qui git rebase va copier. C'est beaucoup trop !

Nous devons obtenir git rebase pour copier seulement le seul commit. Ce que cela signifie, c'est que pour le nous devons donner à git rebase le nom brC.6 De cette façon, Git utilisera brC..HEAD pour sélectionner les commits à copier, ce qui sera juste le seul commit que nous devons copier.

Mais, hélas!- maintenant nous avons un gros problème, parce que git rebase veut copier le commit à un point juste après le commit de que nous venons de lui donner. C'est à dire qu'il veut copier les commits juste après brC. C'est là que sont les commits maintenant ! (Enfin, le seul commit l'est.) Donc ce n'est pas bon du tout !

Heureusement, git rebase a une trappe d'évacuation, spécifiquement le --onto l'argument. Je l'ai mentionné plusieurs fois auparavant, mais c'est maintenant que nous en avons besoin. Nous voulons que les copies aillent juste après brAdonc c'est ce que nous fournirons comme argument de la fonction --onto argument. L'argument rebase utilise l'argument par défaut mais si nous lui donnons un --ontoil l'utilise à la place. Donc :

$ git branch   # just checking...
  brA
  brB
  brC
  brD
  master
* newbranch

OK, bien, nous sommes toujours sur newbranch. (Notez que git status fonctionne ici aussi, et si vous utilisez une de ces choses fantaisistes de configuration d'invite de shell, vous pouvez même obtenir que votre nom de branche actuel soit dans votre invite, de sorte que vous n'avez pas besoin d'exécuter git status aussi souvent).

$ git rebase --onto brA brC

Maintenant, Git va sélectionner les commits dans brC..HEAD, qui est le bon ensemble de commits à copier, et les copier juste après la pointe de brA, qui est le bon endroit pour les copier à. Une fois que les copies sont toutes faites, Git abandonnera les commits originaux 7 et faire le nom newbranch pointe vers le nouveau commit copié, le plus en pointe.

Notez que cela fonctionne même si vous avez pas de de nouveaux commits sur la nouvelle branche. C'est le seul cas où git branch -faussi fonctionne. Quand il n'y a pas de commits, ce git rebase copie soigneusement tous les zéro 🙂 et fait ensuite le nom, newbranchpointe vers le même commit que brA. Donc git branch -f n'est pas toujours erroné ; mais git rebase est toujours juste - quoiqu'un peu maladroit : il faut identifier à la fois le et le --onto manuellement.


6 Ou, comme nous l'avons noté dans une note de bas de page précédente, nous pouvons donner... git rebase l'ID du commit auquel le nom brC pointe. Quoi qu'il en soit, nous devons fournir cette information comme l'objet upstream argument.

7 Sauf, bien sûr, l'entrée reflog [email protected]{1} se souviendra de l'ancien, maintenant abandonné, tip commit. Des entrées reflog supplémentaires pour newbranch peuvent se souvenir d'encore plus de commits, et se souvenir du tip commit suffit à maintenir en vie tous ses ancêtres. Les entrées reflog finissent par expirer - après 30 jours pour certains cas, et 90 pour d'autres, par défaut - mais cela vous donne jusqu'à un mois environ, par défaut, pour récupérer vos erreurs.

Si vous n'avez pas de commits dans la nouvelle branche, l'utilisation d'une balise git reset --hard est le plus facile.

Si vous avez des commits dans la nouvelle branche...

Si votre branche commence sur un commit plus ancien que vous voulez, il suffit de faire un... git rebase.

Si, plus improbable, votre branche démarre sur un commit plus récent ou sur une branche complètement différente, utilisez un git rebase.

. git rebase --onto

Nous vous montrons les commentaires et les évaluations

Vous pouvez ajouter de la valeur à notre contenu en taxant votre expérience dans les références.



Utilisez notre moteur de recherche

Ricerca
Generic filters

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.