¿Es peligroso git-svn dcommit después de fusionarse en git?

133

Mi motivación para probar git-svn es la fusión y ramificación sin esfuerzo. Entonces noté que el hombre git-svn (1) dice:

NO se recomienda ejecutar git-merge o git-pull en una rama desde la que planee enviar. Subversion no representa fusiones de ninguna manera razonable o útil; por lo que los usuarios que usan Subversion no pueden ver ninguna fusión que hayas realizado. Además, si se fusiona o extrae de una rama git que es un espejo de una rama SVN, dcommit puede comprometerse con la rama incorrecta.

¿Esto significa que no puedo crear una rama local desde svn / trunk (o una rama), cortar, fusionar de nuevo en svn / trunk, y luego dcommit? Entiendo que los usuarios de svn verán el mismo desorden que las fusiones en svn pre 1.5.x siempre han sido, pero ¿hay otros inconvenientes? Esa última oración también me preocupa. ¿Las personas hacen rutinariamente este tipo de cosas?

Knut Eldhuset
fuente
14
Esto es simplemente incorrecto: Subversion no representa fusiones de ninguna manera razonable o útil; por lo que los usuarios que usan Subversion no pueden ver ninguna fusión que hayas realizado Además, si se fusiona o extrae de una rama git que es un espejo de una rama SVN, dcommit puede comprometerse con la rama incorrecta. Subversion podría representar no solo la información de seguimiento de git merge, sino también información mucho más detallada. Es git-svn el que no puede componer svn apropiado: mergeinfo o hacer que dcommit a una rama adecuada.
Alexander Kitaev
2
@AlexanderKitaev: los documentos de git-svn han cambiado desde entonces y, además, hay una --mergeinfo=<mergeinfo>opción que puede transferir información de fusión al SVN. Sin embargo, no estoy seguro de cómo debería usarse.
Mr_and_Mrs_D

Respuestas:

174

En realidad, encontré una forma aún mejor con la --no-ffopción de git merge. Toda esta técnica de squash que usé antes ya no es necesaria.

Mi nuevo flujo de trabajo ahora es el siguiente:

  • Tengo una rama "master", que es la única rama que dcommit de que el clon y el repositorio SVN ( -sse supone que tiene un diseño estándar de SVN en el repositorio trunk/, branches/y tags/):

    git svn clone [-s] <svn-url>
    
  • Trabajo en una rama local "trabajo" ( -bcrea la rama "trabajo")

    git checkout -b work
    
  • comprometerse localmente en la rama "trabajo" ( -spara cerrar su mensaje de confirmación). En la secuela, supongo que hiciste 3 confirmaciones locales

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

Ahora quieres comprometerte con el servidor SVN

  • [Eventualmente] guarde las modificaciones que no desea ver confirmadas en el servidor SVN (a menudo comentaba algún código en el archivo principal solo porque desea acelerar la compilación y centrarse en una característica dada)

    (work)$> git stash
    
  • Reubique la rama maestra con el repositorio SVN (para actualizar desde el servidor SVN)

    (work)$> git checkout master
    (master)$> git svn rebase
    
  • volver a la rama de trabajo y rebase con master

    (master)$> git checkout work
    (work)$> git rebase master
    
  • Asegúrese de que todo esté bien usando, por ejemplo:

    (work)$> git log --graph --oneline --decorate
    
  • Ahora es el momento de fusionar los tres commits de la rama "trabajo" en "master" usando esta maravillosa --no-ffopción

    (work)$> git checkout master
    (master)$> git merge --no-ff work
    
  • Puede observar el estado de los registros:

    (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
    
  • Ahora probablemente desee editar ( amend) la última confirmación para sus tipos SVN (de lo contrario, solo verán una única confirmación con el mensaje "Combinar rama 'trabajo'"

    (master)$> git commit --amend
    
  • Finalmente comprometerse en el servidor SVN

    (master)$> git svn dcommit
    
  • Vuelva al trabajo y finalmente recupere sus archivos escondidos:

    (master)$> git checkout work
    (work)$> git stash pop
    
Sebastien Varrette
fuente
44
Este es EXACTAMENTE el flujo de trabajo que estaba buscando este git n00b. Cree una rama local para corregir un error, haga lo que sea, luego envíelo a svn con una SOLO confirmación. Usar la respuesta mejor calificada aquí arruinó lo que estaba haciendo: no podía obtener svn rebase sin errores.
João Bragança
19
¿No es esto exactamente lo que los documentos de git-svn citados en la advertencia de guerra contra? Al ejecutar merge con la --no-ffopción, está creando explícitamente un commit de fusión (un commit con dos padres) en lugar de un avance rápido. Para garantizar que todas las confirmaciones en la rama de seguimiento de svn sean confirmaciones monoparentales, todas las fusiones deben adelantarse ( --ff-onlypuede ayudar con esto) o, si el tronco ha cambiado a sus espaldas, a --squash, ¿verdad?
jemmons
44
Esto funciona para una rama única que eliminas inmediatamente, pero git svn dcommitreescribe el git commit que le das. Esto significa que usted pierde su otro padre, y ahora tu repositorio Git tiene ningún registro de que alguna vez combinada que se ramifican en master. Esto también puede dejar su directorio de trabajo en un estado inconsistente.
rescdsk
9
usar en git merge --no-ff work -m "commit message"lugar de tener un git commit --amendpaso extra
tekumara
3
para mí preferiría usar "git merge --ff-only work" ya que quiero preservar todos mis commits y no solo el último
Moataz Elmasry
49

Crear sucursales locales es definitivamente posible con git-svn. Siempre y cuando solo estés usando ramas locales para ti mismo, y no intentes usar git para fusionar entre las ramas svn aguas arriba, deberías estar bien.

Tengo una rama "maestra" que uso para rastrear el servidor svn. Esta es la única rama de la que me comprometo. Si estoy haciendo algo de trabajo, creo una rama de tema y trabajo en ello. Cuando quiero cometerlo, hago lo siguiente:

  1. Compromete todo a la rama temática
  2. git svn rebase (resolver cualquier conflicto entre su trabajo y SVN)
  3. git checkout master
  4. git svn rebase (esto hace que el siguiente paso sea una fusión de avance rápido, vea los comentarios de Aaron a continuación)
  5. git merge topic_branch
  6. resolver cualquier conflicto de fusión (no debería haber ninguno en este momento)
  7. git svn dcommit

También tengo otra situación en la que necesito mantener algunos cambios locales (para la depuración) que nunca se deben impulsar a svn. Para eso, tengo la rama maestra anterior pero también una rama llamada "trabajo" donde normalmente trabajo. Las ramas temáticas se ramifican del trabajo. Cuando quiero comprometer el trabajo allí, pago master y uso cherry-pick para elegir los commits de la rama de trabajo que quiero comprometer a svn. Esto se debe a que quiero evitar comprometer los tres compromisos de cambio local. Luego, me comprometo desde la rama maestra y rebaso todo.

Vale la pena ejecutar git svn dcommit -nprimero para asegurarse de que está a punto de comprometer exactamente lo que pretende cometer. A diferencia de git, ¡reescribir el historial en svn es difícil!

Siento que debe haber una mejor manera de fusionar el cambio en una rama temática mientras se omiten esos compromisos de cambio local que usar cherry-pick, por lo que si alguien tiene alguna idea, sería bienvenido.

Greg Hewgill
fuente
Si entiendo esto correctamente, en git, ¿está bien fusionar git con svn, pero no svn con svn? ¿Entonces no puedo fusionar mi svn / branch con svn / trunk en git?
Knut Eldhuset
1
Reescribir el historial en SVN es difícil si quieres hacer algo trivial. En casos no triviales es prácticamente imposible.
Aristóteles Pagaltzis
19
La respuesta de Greg Hewgill tiene muchos votos, pero creo que está mal. La fusión de topic_branch en master solo es segura si es solo una fusión de avance rápido. Si se trata de una fusión real que requiere una confirmación de fusión, entonces la confirmación de fusión se perderá cuando realice una confirmación. La próxima vez que intentes fusionar topic_branch en master, git piensa que la primera fusión nunca sucedió, y todo el infierno se desata. Vea la pregunta ¿Por qué git svn dcommit pierde el historial de confirmaciones de fusión para sucursales locales?
Aaron
1
@ Greg Hewgill, la edición no está clara. Usted escribió que el rebase "hace que la próxima confirmación se fusione rápidamente". No sé lo que eso significa, porque una combinación de avance rápido no implica una confirmación, pero tal vez quisiste decir "hace que la siguiente combinación sea una combinación de avance rápido". Pero si eso es lo que querías decir, no es correcto. Si una fusión de topic_branch en master es una combinación de avance rápido o no depende de si se han realizado confirmaciones en master desde el punto de bifurcación. Cuando vuelve a crear un master, eso crea nuevos commits en master, por lo que una fusión posterior no es necesariamente una fusión de avance rápido.
Aaron
1
@ Aaron: Sí, no sé lo que estaba pensando allí. Lo he arreglado de nuevo, espero que tenga sentido. Uno de los problemas es que hay tantas maneras de hacer las cosas en Git, que se necesita bastante explicación para describir una secuencia específica de acciones.
Greg Hewgill
33

Solución simple: eliminar la rama 'trabajo' después de fusionar

Respuesta corta: puede usar git como desee (consulte a continuación un flujo de trabajo simple), incluida la fusión. Solo asegúrese de seguir cada ' trabajo de fusión git ' con ' git branch -d work ' para eliminar la rama de trabajo temporal.

Explicación de fondo: El problema de combinación / dcommit es que siempre que 'git svn dcommit' una rama, el historial de fusión de esa rama se 'aplana': git se olvida de todas las operaciones de fusión que entraron en esta rama: solo se preserva el contenido del archivo, pero se pierde el hecho de que este contenido (parcialmente) proviene de otra rama específica. Ver: ¿Por qué git svn dcommit pierde el historial de confirmaciones de fusión para sucursales locales?

(Nota: no hay mucho que git-svn pueda hacer al respecto: svn simplemente no comprende las combinaciones de git mucho más potentes. Por lo tanto, dentro del repositorio de svn, esta información de combinación no se puede representar de ninguna manera).

Pero este es todo el problema. Si elimina la rama 'trabajo' después de que se haya fusionado con la 'rama maestra', entonces su repositorio git está 100% limpio y se ve exactamente como su repositorio svn.

Mi flujo de trabajo: Por supuesto, primero cloné el repositorio svn remoto en un repositorio git local (esto puede llevar algo de tiempo):

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

Todo el trabajo se realiza dentro del "directorio local". Cada vez que necesito obtener actualizaciones del servidor (como 'svn update'), hago:

$> git checkout master
$> git svn rebase

Hago todo mi trabajo de desarrollo en una rama separada 'trabajo' que se crea así:

$> git checkout -b work

Por supuesto, puede crear tantas ramas para su trabajo como desee y fusionarlas y volver a crearlas como desee (simplemente elimínelas cuando haya terminado con ellas, como se explica a continuación). En mi trabajo normal, me comprometo con mucha frecuencia:

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

El siguiente paso (git rebase -i) es opcional --- es solo limpiar el historial antes de archivarlo en svn: una vez que alcancé una piedra estable que quiero compartir con los demás, reescribo el historial de este 'trabajo' bifurca y limpia los mensajes de confirmación (otros desarrolladores no necesitan ver todos los pequeños pasos y errores que cometí en el camino, solo el resultado). Para esto, lo hago

$> git log

y copie el hash sha-1 del último commit que esté activo en el repositorio svn (como lo indica un git-svn-id). Entonces llamo

$> git rebase -i 74e4068360e34b2ccf0c5869703af458cde0cdcb

Solo pegue sha-1 hash de nuestro último svn commit en lugar del mío. Es posible que desee leer la documentación con 'git help rebase' para obtener detalles. En resumen: este comando primero abre un editor que presenta sus confirmaciones ---- solo cambie 'elegir' a 'aplastar' para todas las confirmaciones que desea eliminar con las confirmaciones anteriores. Por supuesto, la primera línea debe permanecer como una "elección". De esta manera, puede condensar sus muchos pequeños compromisos en una o más unidades significativas. Guarde y salga del editor. Obtendrá otro editor que le pedirá que reescriba los mensajes de registro de confirmación.

En resumen: después de terminar el 'pirateo de códigos', masajeo mi rama 'trabajo' hasta que se vea cómo quiero presentarlo a los otros programadores (o cómo quiero ver el trabajo dentro de unas semanas cuando navego por el historial) .

Para enviar los cambios al repositorio svn, hago:

$> git checkout master
$> git svn rebase

Ahora estamos de vuelta en la antigua rama 'maestra' actualizada con todos los cambios que ocurrieron mientras tanto en el repositorio svn (sus nuevos cambios están ocultos en la rama 'trabajo').

Si hay cambios que pueden chocar con sus nuevos cambios de 'trabajo', debe resolverlos localmente antes de que pueda impulsar su nuevo trabajo (vea los detalles más abajo). Entonces, podemos empujar nuestros cambios a 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

Nota 1: El comando 'git branch -d work' es bastante seguro: solo le permite eliminar ramas que ya no necesita (porque ya están fusionadas en su rama actual). Si ejecuta este comando por error antes de fusionar su trabajo con la rama 'maestra', recibirá un mensaje de error.

Nota 2: asegúrese de eliminar su rama con 'git branch -d work' entre la fusión y dcommit: si intenta eliminar la rama después de dcommit, recibirá un mensaje de error: cuando hace 'git svn dcommit', git olvida que su sucursal se ha fusionado con 'maestro'. Debe eliminarlo con 'git branch -D work' que no realiza la verificación de seguridad.

Ahora, inmediatamente creo una nueva rama 'de trabajo' para evitar piratear accidentalmente la rama 'maestra':

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

Integrando su 'trabajo' con cambios en svn: Esto es lo que hago cuando 'git svn rebase' revela que otros cambiaron el repositorio de svn mientras estaba trabajando en mi rama 'trabajo':

$> 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

Existen soluciones más potentes: el flujo de trabajo presentado es simplista: utiliza los poderes de git solo dentro de cada ronda de 'actualizar / piratear / dcommitir', pero deja el historial del proyecto a largo plazo tan lineal como el repositorio svn. Esto está bien si solo desea comenzar a usar git merges en pequeños primeros pasos en un proyecto svn heredado.

Cuando se familiarice con git merging, siéntase libre de explorar otros flujos de trabajo: si sabe lo que está haciendo, puede mezclar git merges con svn merges (¿ Usando git-svn (o similar) solo para ayudar con svn merge? )

Yaakov Belch
fuente
Esto parece innecesariamente complejo. He oído hablar de personas que hacen git merge --squash work, ¿por qué no hacer esto? Puedo ver hacer commits de squash en la rama antes de fusionar si está creando más de una 'selección' (digamos que tiene 8 commits y está convirtiendo cada uno de 4 commits en 1, y fusionando 2 commits para masterizar). Además, cuando actualizo mi rama 'trabajo', lo hago rebase, es más simple que crear otra rama para mi rama y hacer fusiones ...
void.pointer
8

¡La respuesta de Greg Hewgill en la parte superior no es segura! Si aparecieron nuevas confirmaciones en el enlace troncal entre los dos "git svn rebase", la fusión no avanzará rápidamente.

Se puede garantizar mediante el uso de la bandera "--ff-only" para git-merge, pero generalmente no ejecuto "git svn rebase" en la rama, solo "git rebase master" (suponiendo que sea solo un local rama). Luego, se garantiza que un "git merge thebranch" será un avance rápido.

Marius K
fuente
6

Una forma segura de fusionar ramas svn en git es usar git merge --squash. Esto creará una única confirmación y detendrá la adición de un mensaje.

Digamos que tiene un tema svn branch, llamado svn-branch.

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

en este punto, tiene todos los cambios de la rama svn aplastada en una confirmación esperando en el índice

git commit
luntain
fuente
Sin embargo, como otros han señalado, esto pierde la granularidad de los commits.
Kzqai
@Tchalvak: A veces solo quieres eso. A menudo he compromete en una rama de la característica ala "desorden fijo de cometer anterior" y estos callejones sin salida que me gusta esconder a causa de una buena reputación :-)
schoetbi
1
Sí, aunque considero que es una caminata por la cuerda floja, ya que cada vez que aplastas, también pierdes la capacidad de separar los compromisos individuales más tarde. Solo es una cuestión de diferentes opciones de compromiso de estándares.
Kzqai
1
@schoetbi sí, a veces necesitas limpiar el historial desordenado antes de publicar. Aquí es donde 'git rebase -i <trunk>' es de gran ayuda: puede unirse / reordenar / eliminar e incluso dividir (!) Confirmaciones, repara mensajes de forma selectiva.
Inger
5

Rebase la rama local de git en la rama maestra de git, luego dcommit y de esa manera parece que hiciste todos esos commits en secuencia para que la gente pueda verlo linealmente como están acostumbrados. Entonces, suponiendo que tenga una rama local llamada tema que podría hacer

git rebase master topic

que luego jugará sus commits sobre la rama maestra lista para que usted se comprometa

JoeyJ
fuente