Est-ce que git-svn dcommit après la fusion dans git est dangereux?

133

Ma motivation pour essayer git-svn est la fusion et la création de branches sans effort. Puis j'ai remarqué que man git-svn (1) dit:

Exécuter git-merge ou git-pull n'est PAS recommandé sur une branche à partir de laquelle vous prévoyez de dcommit. Subversion ne représente pas les fusions de manière raisonnable ou utile; donc les utilisateurs utilisant Subversion ne peuvent pas voir les fusions que vous avez effectuées. De plus, si vous fusionnez ou tirez d'une branche git qui est un miroir d'une branche SVN, dcommit peut s'engager dans la mauvaise branche.

Cela signifie-t-il que je ne peux pas créer une branche locale à partir de svn / trunk (ou une branche), pirater, fusionner à nouveau dans svn / trunk, puis dcommit? Je comprends que les utilisateurs de svn verront le même désordre que celui qui fusionne dans svn pre 1.5.x, mais y a-t-il d'autres inconvénients? Cette dernière phrase m'inquiète aussi. Les gens font-ils régulièrement ce genre de choses?

Knut Eldhuset
la source
14
C'est tout simplement faux: Subversion ne représente pas les fusions de manière raisonnable ou utile; afin que les utilisateurs utilisant Subversion ne puissent pas voir les fusions que vous avez effectuées. De plus, si vous fusionnez ou tirez d'une branche git qui est un miroir d'une branche SVN, dcommit peut s'engager dans la mauvaise branche. Subversion pourrait représenter non seulement des informations de suivi de git merge, mais également des informations beaucoup plus fines. C'est git-svn qui ne parvient pas à composer svn: mergeinfo ou à faire dcommit dans une branche appropriée.
Alexander Kitaev le
2
@AlexanderKitaev: les docs git-svn ont changé depuis et il existe en plus une --mergeinfo=<mergeinfo>option qui peut être capable de transmettre les informations de fusion au SVN. Je ne sais pas comment il doit être utilisé.
Mr_and_Mrs_D

Réponses:

174

En fait, j'ai trouvé un moyen encore meilleur avec l' --no-ffoption sur git merge. Toute cette technique de squash que j'utilisais auparavant n'est plus nécessaire.

Mon nouveau workflow est désormais le suivant:

  • J'ai une branche « maître » qui est la seule branche que je dcommit à partir et que le dépôt SVN clone ( -ssupposons que vous avez une mise en page standard SVN dans le référentiel trunk/, branches/et tags/):

    git svn clone [-s] <svn-url>
    
  • Je travaille sur une branche locale "work" ( -bcrée la branche "work")

    git checkout -b work
    
  • commit localement dans la branche "work" ( -spour valider votre message de commit). Dans la suite, je suppose que vous avez effectué 3 commits locaux

    ...
    (work)$> git commit -s -m "msg 1"
    ...
    (work)$> git commit -s -m "msg 2"
    ...
    (work)$> git commit -s -m "msg 3"
    

Vous souhaitez maintenant vous engager sur le serveur SVN

  • [Finalement] cacher les modifications que vous ne voulez pas voir validées sur le serveur SVN (souvent vous avez commenté du code dans le fichier principal juste parce que vous voulez accélérer la compilation et vous concentrer sur une fonctionnalité donnée)

    (work)$> git stash
    
  • rebase de la branche maître avec le référentiel SVN (pour mettre à jour depuis le serveur SVN)

    (work)$> git checkout master
    (master)$> git svn rebase
    
  • revenir à la branche de travail et rebase avec master

    (master)$> git checkout work
    (work)$> git rebase master
    
  • Assurez-vous que tout va bien en utilisant, par exemple:

    (work)$> git log --graph --oneline --decorate
    
  • Il est maintenant temps de fusionner les trois commits de la branche "work" dans "master" en utilisant cette merveilleuse --no-ffoption

    (work)$> git checkout master
    (master)$> git merge --no-ff work
    
  • Vous pouvez remarquer l'état des journaux:

    (master)$> git log --graph --oneline --decorate
    * 56a779b (work, master) Merge branch 'work'
    |\  
    | * af6f7ae msg 3
    | * 8750643 msg 2
    | * 08464ae msg 1
    |/  
    * 21e20fa (git-svn) last svn commit
    
  • Maintenant, vous voulez probablement éditer ( amend) le dernier commit pour vos mecs SVN (sinon ils ne verront qu'un seul commit avec le message "Merge branch 'work'"

    (master)$> git commit --amend
    
  • Enfin commit sur le serveur SVN

    (master)$> git svn dcommit
    
  • Revenez au travail et récupérez éventuellement vos fichiers cachés:

    (master)$> git checkout work
    (work)$> git stash pop
    
Sébastien Varrette
la source
4
C'est EXACTEMENT le flux de travail recherché par git n00b. Créez une branche locale pour corriger un bogue, faites n'importe quoi, puis envoyez-la à svn avec un SEUL commit. Utiliser la réponse la mieux notée ici a gâché ce que je faisais - je ne pouvais pas git svn rebase sans erreurs.
João Bragança
19
N'est-ce pas exactement ce que les docs git-svn cités dans l'op mettent en garde? En exécutant merge avec l' --no-ffoption, vous créez explicitement une validation de fusion (une validation avec deux parents) au lieu d'une avance rapide. Pour vous assurer que tous les commits de la branche svn-tracking sont des commits monoparentaux, toutes les fusions doivent être rapides (cela --ff-onlypeut vous aider) ou, si le tronc a changé dans votre dos --squash, non?
jemmons
4
Cela fonctionne pour une branche unique que vous supprimez immédiatement, mais git svn dcommitréécrit le commit git que vous lui donnez. Cela signifie que vous perdez son autre parent, et maintenant votre dépôt git n'a aucun enregistrement dans lequel vous avez jamais fusionné cette branche master. Cela peut également laisser votre répertoire de travail dans un état incohérent.
rescdsk
9
utiliser git merge --no-ff work -m "commit message"au lieu d'avoir une git commit --amendétape supplémentaire
tekumara
3
pour moi, je préférerais utiliser "git merge --ff-only work" car je veux conserver tous mes commits et pas seulement le dernier
Moataz Elmasry
49

Créer des branches locales est certainement possible avec git-svn. Tant que vous n'utilisez que des branches locales pour vous-même et que vous n'essayez pas d'utiliser git pour fusionner entre les branches svn en amont, tout devrait bien se passer.

J'ai une branche "maître" que j'utilise pour suivre le serveur svn. C'est la seule branche dont je m'engage. Si je travaille, je crée une branche thématique et j'y travaille. Lorsque je veux le valider, je fais ce qui suit:

  1. Tout valider dans la branche thématique
  2. git svn rebase (résolvez tout conflit entre votre travail et svn)
  3. git checkout master
  4. git svn rebase (cela fait de l'étape suivante une fusion rapide, voir les commentaires d'Aaron ci-dessous)
  5. git merge topic_branch
  6. résoudre les conflits de fusion (il ne devrait pas y en avoir à ce stade)
  7. git svn dcommit

J'ai aussi une autre situation où je dois maintenir quelques changements locaux (pour le débogage) qui ne devraient jamais être poussés vers svn. Pour cela, j'ai la branche master ci-dessus mais aussi une branche appelée "work" où je travaille normalement. Les branches thématiques sont dérivées du travail. Quand je veux valider le travail là-bas, je vérifie le maître et utilise le choix de cerise pour choisir les commits de la branche de travail que je veux commettre dans svn. C'est parce que je veux éviter de valider les trois commits de changement locaux. Ensuite, je me désengage de la branche master et je rebase tout.

Cela vaut la peine de git svn dcommit -ncommencer par vous assurer que vous êtes sur le point de valider exactement ce que vous avez l'intention de commettre. Contrairement à git, réécrire l'historique dans svn est difficile!

Je pense qu'il doit y avoir un meilleur moyen de fusionner le changement sur une branche de sujet tout en ignorant ces commits de changement local que d'utiliser le choix de la cerise, donc si quelqu'un a des idées, ils seraient les bienvenus.

Greg Hewgill
la source
Si je comprends bien, dans git, fusionner git avec svn est OK, mais pas svn avec svn? Donc je ne peux pas fusionner mon svn / branch avec svn / trunk dans git?
Knut Eldhuset
1
La réécriture de l'historique dans SVN est difficile si vous voulez faire quelque chose de trivial. Dans des cas non triviaux, c'est pratiquement impossible.
Aristotle Pagaltzis
19
La réponse de Greg Hewgill a reçu beaucoup de votes, mais je pense que c'est faux. La fusion de topic_branch vers master n'est sûre que s'il s'agit simplement d'une fusion rapide. S'il s'agit d'une véritable fusion nécessitant une validation de fusion, alors la validation de fusion sera perdue lorsque vous la désactivez. La prochaine fois que vous essayez de fusionner topic_branch dans master, git pense que la première fusion ne s'est jamais produite, et tout l'enfer se déchaîne. Voir la question Pourquoi git svn dcommit perd-il l'historique des commits de fusion pour les branches locales?
Aaron
1
@Greg Hewgill, la modification n'est pas claire. Vous avez écrit que le rebase "fait du prochain commit une fusion rapide". Je ne sais pas ce que cela signifie, car une fusion à avance rapide n'implique pas de validation, mais peut-être que vous vouliez dire "fait de la prochaine fusion une fusion à avance rapide". Mais si c'est ce que vous vouliez dire, ce n'est pas correct. Le fait qu'une fusion de topic_branch vers master soit une fusion rapide ou non dépend du fait que des validations ont été effectuées sur master depuis le point de branchement. Lorsque vous rebasez le maître, cela crée de nouveaux commits sur le maître, donc une fusion ultérieure n'est pas nécessairement une fusion à avance rapide.
Aaron
1
@Aaron: Ouais, je ne sais pas à quoi je pensais. Je l'ai réparé à nouveau, j'espère que cela a du sens. L'un des problèmes est qu'il y a tellement de façons de faire les choses dans Git, qu'il faut pas mal d'explications pour décrire une séquence d'actions spécifique .
Greg Hewgill
33

Solution simple: supprimez la branche 'work' après la fusion

Réponse courte: vous pouvez utiliser git comme vous le souhaitez (voir ci-dessous pour un flux de travail simple), y compris la fusion. Assurez-vous simplement de suivre chaque ' git merge work ' avec ' git branch -d work ' pour supprimer la branche de travail temporaire.

Explication d'arrière-plan: Le problème de merge / dcommit est que chaque fois que vous 'git svn dcommit' une branche, l'historique de fusion de cette branche est 'aplati': git oublie toutes les opérations de fusion qui sont allées dans cette branche: Seul le contenu du fichier est conservé, mais le fait que ce contenu provienne (partiellement) d'une autre branche spécifique est perdu. Voir: Pourquoi git svn dcommit perd-il l'historique des commits de fusion pour les branches locales?

(Remarque: il n'y a pas grand-chose que git-svn puisse faire à ce sujet: svn ne comprend tout simplement pas les fusions git beaucoup plus puissantes. Ainsi, à l'intérieur du dépôt svn, ces informations de fusion ne peuvent être représentées d'aucune façon.)

Mais c'est tout le problème. Si vous supprimez la branche 'work' après avoir été fusionnée dans la 'branche principale', votre dépôt git est 100% propre et ressemble exactement à votre dépôt svn.

Mon workflow: Bien sûr, j'ai d'abord cloné le référentiel svn distant dans un référentiel git local (cela peut prendre un certain temps):

$> git svn clone <svn-repository-url> <local-directory>

Tout le travail se déroule alors dans le "répertoire local". Chaque fois que j'ai besoin d'obtenir des mises à jour du serveur (comme 'svn update'), je fais:

$> git checkout master
$> git svn rebase

Je fais tout mon travail de développement dans un `` travail '' de branche distinct qui est créé comme ceci:

$> git checkout -b work

Bien sûr, vous pouvez créer autant de branches pour votre travail que vous le souhaitez et fusionner et rebaser entre elles comme vous le souhaitez (supprimez-les simplement lorsque vous en avez terminé avec elles - comme indiqué ci-dessous). Dans mon travail normal, je m'engage très fréquemment:

$> git commit -am '-- finished a little piece of work'

La prochaine étape (git rebase -i) est facultative --- il s'agit simplement de nettoyer l'historique avant de l'archiver sur svn: Une fois que j'ai atteint un mile stable que je veux partager avec d'autres, je réécris l'histoire de ce `` travail '' branchez et nettoyez les messages de validation (les autres développeurs n'ont pas besoin de voir toutes les petites étapes et erreurs que j'ai faites en cours de route - juste le résultat). Pour cela, je fais

$> git log

et copiez le hachage sha-1 du dernier commit qui est actif dans le dépôt svn (comme indiqué par un git-svn-id). Alors j'appelle

$> git rebase -i 74e4068360e34b2ccf0c5869703af458cde0cdcb

Collez simplement le hachage sha-1 de notre dernier commit svn au lieu du mien. Vous voudrez peut-être lire la documentation avec 'git help rebase' pour plus de détails. En bref: cette commande ouvre d'abord un éditeur présentant vos commits - changez simplement «pick» en «squash» pour tous les commits que vous voulez écraser avec les commits précédents. Bien sûr, la première ligne doit rester un «choix». De cette manière, vous pouvez condenser vos nombreux petits engagements en une ou plusieurs unités significatives. Enregistrez et quittez l'éditeur. Vous obtiendrez un autre éditeur vous demandant de réécrire les messages du journal de validation.

En bref: après avoir terminé le `` code hacking '', je masse ma branche `` travail '' jusqu'à ce qu'elle ressemble à comment je veux la présenter aux autres programmeurs (ou comment je veux voir le travail dans quelques semaines lorsque je parcours l'historique) .

Afin de pousser les modifications vers le référentiel svn, je fais:

$> git checkout master
$> git svn rebase

Nous sommes maintenant de retour à l'ancienne branche «master» mise à jour avec tous les changements intervenus entre-temps dans le dépôt svn (vos nouveaux changements sont cachés dans la branche «work»).

S'il y a des changements qui peuvent entrer en conflit avec vos nouveaux changements de «travail», vous devez les résoudre localement avant de pouvoir pousser votre nouveau travail (voir les détails ci-dessous). Ensuite, nous pouvons appliquer nos modifications à svn:

$> git checkout master
$> git merge work        # (1) merge your 'work' into 'master'
$> git branch -d work    # (2) remove the work branch immediately after merging
$> git svn dcommit       # (3) push your changes to the svn repository

Note 1: La commande 'git branch -d work' est assez sûre: elle vous permet uniquement de supprimer les branches dont vous n'avez plus besoin (car elles sont déjà fusionnées dans votre branche actuelle). Si vous exécutez cette commande par erreur avant de fusionner votre travail avec la branche «master», vous obtenez un message d'erreur.

Remarque 2: assurez-vous de supprimer votre branche avec 'git branch -d work' entre la fusion et dcommit: si vous essayez de supprimer la branche après dcommit, vous obtenez un message d'erreur: lorsque vous faites 'git svn dcommit', git oublie que votre branche a été fusionnée avec «master». Vous devez le supprimer avec 'git branch -D work' qui ne fait pas le contrôle de sécurité.

Maintenant, je crée immédiatement une nouvelle branche 'work' pour éviter de pirater accidentellement la branche 'master':

$> git checkout -b work
$> git branch            # show my branches:
  master
* work

Intégrer votre 'travail' avec les changements sur svn: Voici ce que je fais quand 'git svn rebase' révèle que d'autres ont changé le dépôt svn pendant que je travaillais sur ma branche 'work':

$> git checkout master
$> git svn rebase              # 'svn pull' changes
$> git checkout work           # go to my work
$> git checkout -b integration # make a copy of the branch
$> git merge master            # integrate my changes with theirs
$> ... check/fix/debug ...
$> ... rewrite history with rebase -i if needed

$> git checkout master         # try again to push my changes
$> git svn rebase              # hopefully no further changes to merge
$> git merge integration       # (1) merge your work with theirs
$> git branch -d work          # (2) remove branches that are merged
$> git branch -d integration   # (2) remove branches that are merged
$> git svn dcommit             # (3) push your changes to the svn repository

Des solutions plus puissantes existent: Le flux de travail présenté est simpliste: il utilise les pouvoirs de git uniquement dans chaque cycle de 'update / hack / dcommit' --- mais laisse l'historique du projet à long terme aussi linéaire que le dépôt svn. Ce n'est pas grave si vous voulez simplement commencer à utiliser les fusions git par petites étapes dans un projet svn hérité.

Lorsque vous vous familiariserez avec la fusion git, n'hésitez pas à explorer d'autres flux de travail: si vous savez ce que vous faites, vous pouvez mélanger des fusions git avec des fusions svn (en utilisant git-svn (ou similaire) juste pour aider avec svn merge? )

Yaakov Belch
la source
Cela semble inutilement complexe. J'ai entendu parler de gens qui font git merge --squash work, pourquoi ne pas faire ça? Je peux voir faire des commits squash dans la branche avant de fusionner si vous créez plus d'un 'pick' (disons que vous avez 8 commits et que vous transformez chacun des 4 commits en 1, et fusionnez 2 commits en master). Aussi, lors de la mise à jour de ma branche 'travail', je le fais rebase, c'est plus simple que de créer une autre branche pour ma branche et de faire des fusions ...
void.pointer
8

La réponse de Greg Hewgill sur le dessus n'est pas sûre! Si de nouveaux commits apparaissent sur le tronc entre les deux "git svn rebase", la fusion ne sera pas en avance rapide.

Cela peut être assuré en utilisant le drapeau "--ff-only" sur git-merge, mais je n'exécute généralement pas "git svn rebase" dans la branche, seulement "git rebase master" dessus (en supposant que ce n'est qu'un local branche). Ensuite, un "git merge thebranch" est garanti d'avance rapide.

Marius K
la source
6

Un moyen sûr de fusionner des branches svn dans git est d'utiliser git merge --squash. Cela créera un seul commit et s'arrêtera pour que vous ajoutiez un message.

Disons que vous avez une branche svn de sujet, appelée svn-branch.

git svn fetch
git checkout remotes/trunk -b big-merge
git merge --squash svn-branch

à ce stade, vous avez toutes les modifications de la branche svn écrasées en un seul commit en attente dans l'index

git commit
luntain
la source
Comme d'autres l'ont souligné, cependant, cela perd la granularité des commits.
Kzqai
@Tchalvak: Parfois, vous voulez juste cela. J'ai souvent des commits dans une branche de fonctionnalité comme "le désordre corrigé du commit précédent" et ces impasses que j'aime cacher à cause d'une bonne réputation :-)
schoetbi
1
Ouais, même si je considère que c'est une marche sur la corde raide, car chaque fois que vous écrasez, vous perdez également la capacité de séparer les commits individuels plus tard. Juste une question de choix différents de normes d'engagement.
Kzqai
1
@schoetbi oui, vous devez parfois nettoyer l'historique désordonné avant de publier. C'est là que 'git rebase -i <trunk>' est d'une grande aide: vous pouvez rejoindre / réorganiser / supprimer et même diviser (!) Les commits, corriger les msg de manière sélective.
inger
5

Rebase la branche git locale sur la branche git principale puis dcommit et de cette façon, il semble que vous ayez fait tous ces commits dans l'ordre afin que les gens svn puissent le voir linéairement comme ils en sont habitués. Donc, en supposant que vous ayez une branche locale appelée topic, vous pouvez le faire

git rebase master topic

qui jouera ensuite vos commits sur la branche master, prêt à être décommit

JoeyJ
la source