2012-04-16 7 views
13

OK, he hecho un desastre. Aparentemente, en mi máquina en casa, la rama de desarrollo no se actualizó. Hice un compromiso y empujé. El resultado fue que la rama real de origen/desarrollo se fusionó con mi rama de desarrollo local, que por alguna razón se consideraron como ramas diferentes.Deshacer una fusión que ha sido presionada

Por un lado, realmente no entiendo cómo sucedió esto y, en segundo lugar, ¿puedo deshacer esto?

Para ilustrarlo, la red se ve algo como esto ahora:

local-develop ---------------------------------- C*--- M - 
origin/develop --- C --- C --- C --- C --- C ---------/

Lo que realmente quería era que el C * se ha comprometido con el origen/desarrollar en lugar de la fusión de las ramas.

Como dije, esto ya se ha enviado. ¿Hay alguna forma de eliminar los cambios y enviarlos de la forma que yo quisiera?

Por ejemplo yo:

git reset --hard HEAD~1 

No estoy seguro si esto deshace la fusión y tengo dos diferentes desarrolla, y después de la fusión borrada etc ...?

Respuesta

26

Las fusiones no suceden al pulsar, suceden en git merge (bueno, y pull, pero eso es realmente solo obtener + fusionar, por lo tanto, la fusión ocurre en la fusión).

parece más probable que usted hizo algo como esto:

<get copy from origin/develop> 
<wait around for remote's origin/develop to acquire new commits> 
git checkout develop  # get onto (local) develop branch 
<edit> 
git commit -m message  # create some new commit(s) 
git push origin develop # attempt to push -- but this fails! 
git pull 

Es el último paso que crea la combinación de cometer (M arriba), porque pull significa fetch (conseguir todas esas nuevas confirmaciones que ahora están en origin/develop), luego merge (tome su develop local y combine su confirmación con las nuevas que acabamos de obtener).

Si no lo ha git push ed este resultado nueva, entonces el repositorio remoto no tiene cualquiera de sus commits locales, las que has etiquetado C* y M. En este caso, ¡estás en buena forma! (Usted puede comprobar mediante la ejecución de git fetch de nuevo para asegurarse de su repo local de origin/develop coincida con la de la distancia, y luego haciendo git log origin/develop para ver lo que hay en ella.)

Se puede ayudar a recordar que hay dos repositorios Git completamente separadas aquí : tuyo, con tus cosas; y el que está llamando al origin aquí. (Llamemos a esa máquina X). Si tuviera que iniciar sesión en X, tiene su propio historial de confirmaciones y nombres de ramales, y así sucesivamente. Allá, cambiarías el directorio al repositorio, ejecuta git log -1 develop y verás qué hay en la punta de esa rama.

Ahora, si cierra sesión en X y está de vuelta en su propia máquina, puede ejecutar git log -1 origin/develop. Si es lo mismo que lo que viste en X, entonces get fetch no tiene nada que actualizar, porque lo que git fetch hace es, en efecto (pero de manera más eficiente), iniciar sesión en X y ver lo que hay en develop ahí. Cualquier cosa que X tiene que no tiene en origin/develop, fetch lo trae y lo agrega al origin/develop.Ahora estás sincronizado con X. X no tiene tus cosas, pero tienes las suyas.

Si a continuación, tomar el paso adicional de hacer un merge (incluyendo el implicado por pull), git, si se tiene que, hacer una combinación de cometer ... pero todo esto está en su cesión temporal, por lo la punta de la rama (todavía develop en este caso). A menos que y hasta que presione esta fusión, comprométase con X (o alguien en X extrae sus compromisos de usted, pero ignorémoslo por ahora :-)), X no lo tendrá.

De todos modos, mientras el control remoto (X aquí) no tenga su compromiso de fusión, está dorado. Desde ellos no lo tienen, nadie más lo hace. Puede hacer un rebase de su rama develop para poner su confirmación (C*) en la parte superior de origin/develop. Eso eliminará la confirmación de fusión (M), y luego puede presionar un avance rápido simple a origin/develop.

Si X hace que la combinación de cometer, es decir, si empujado después de pull ed y tienes que merge-entonces usted está atascado (hasta cierto punto), ya que, presumiblemente, otras personas tienen acceso a X y ahora están utilizando tu fusión se compromete Es posible deshacer el repositorio en X, similar a la forma en que puede hacerlo en su propio repositorio con git reset y git rebase y así sucesivamente, pero en general es una mala idea.


Ahora, supongamos que tiene , de hecho, empujó a la otra cesión temporal (en la máquina X), pero que esté absolutamente seguro de que nadie más ha visto los cambios todavía, y que definitivamente van a restablecerlos, en lugar de volver ellos (revertir cantidades equivale a "confesar" y dejar atrás el registro atormentado, lo que permite a todos los demás recuperarse fácilmente, pero también les permite ver su error :-)).

Aquí está el truco: primero tiene que conseguir repo máquina de X a decir que "la punta de la rama devel se cometen C7", donde C7 está en su propio diagrama de antes, sólo re-numeradas para que yo puede nombrar cada compromiso de manera diferente:

--------------------------- C*--- M 
--- C4 --- C5 --- C6 --- C7 ----/

Entonces, ¿cómo se puede hacer eso? Bueno, una forma es iniciar sesión en X, cd en el repositorio (incluso si es --bare), y usar git update-ref allí. Digamos que SHA1 para C7 es realmente 50db850 (como se muestra en "git log"). Posteriormente, se podría hacer esto:

localhost$ ssh X 
X$ cd /path/to/repo.git 
X$ git update-ref refs/heads/develop 50db850 

Pero si no se puede acceder para X, o incluso simplemente no quiere, puede hacer lo mismo con git push -f. (esto tiene otras ventajas: en particular, su repositorio git sabrá que origin/develop se ha rebobinado, una vez que el push -f se completa satisfactoriamente.) Simplemente haga una sucursal local de punta apuntando a la derecha cometer:

localhost$ git branch resetter 50db850 
localhost$ git log resetter   # make sure it looks right 
... 
localhost$ git push -f origin resetter:develop 
Total 0 (delta 0), reused 0 (delta 0) 
To ssh://[redacted] 
+ 21011c9...50db850 resetter -> develop (forced update) 
localhost$ git branch -d resetter # we don't need it anymore 

Una vez que haya hecho esto, la máquina X volverá al estado que deseaba y podrá proceder como si nunca hubiera pulsado la combinación que no le gustaba.

Tenga en cuenta que cuando hace lo push -f, si alguien más ha hecho nuevas confirmaciones en la parte superior de M, esos serán también hacerse invisible (técnicamente todavía están allí, junto con la combinación de cometer, pero son " perdido "en el sentido lost+found de git fsck --lost-found, y después de unos meses realmente desaparecerán para siempre).

Una y muy importante: este tipo de "reducción de la cesión temporal compartida" es un gran dolor para otros usuarios de esa cesión temporal compartida, así que sea muy seguro de que está bien antes de hacerlo.


Este tipo de combinación trivial no necesita ni siquiera volver. No hay nada fundamentalmente malo en dejar la fusión allí. Sin embargo, si se trata de una fusión errónea más seria, hay otra desventaja para revertir una fusión, además del registro de sus "oops": hace que "rehacer" los cambios más tarde sea un poco más difícil, en una fusión posterior ". a propósito "verán la combinación anterior y pensarán: OK, no necesito volver a fusionar esos cambios. Luego debe "revertir la reversión".

Creo que la lección para llevar aquí es: mirada (git log) antes de empujar para asegurarse de que lo que está a punto de empujar es lo que va a empujar.

O, más fácil y simple: git ls-remote. Utiliza el código de protocolo de búsqueda para ver qué tiene el control remoto. Pero oculta el tema que estoy buscando aquí, que es: ¡el control remoto es un repositorio como el tuyo!

Las próximas versiones de git versión 2 tienen nuevas "características de seguridad" que se podrán usar con git push -f. Pero aún no han salido, así que eso no te ayuda. Voy a volver a editar esto más adelante cuando estén, para tomar nota de ellos. Hasta entonces, sé realmente cuidado aquí: estás compitiendo contra cualquier otra persona que intente insertar cosas nuevas en el repositorio compartido, en este momento.

Incluso puede hacerlo mediante RAW-1 ID. Sin embargo, el nombre de la sucursal es más fácil de escribir correctamente varias veces seguidas.

El retenido y la caducidad se realiza a través de los "reflogs" de git. El servidor compartido puede no estar registrando todas las actualizaciones de ref; si no, solo los repos privados que ya tenían los nuevos commits los retendrán. La caducidad predeterminada más corta es de 30 días, es decir, alrededor de un mes, para las confirmaciones a las que ya no se puede acceder desde la sugerencia de la sucursal. Pero tenga en cuenta que es una lucha loca y no divertida para hacer que todos busquen en sus repositorios confirmaciones "perdidas" después de empujar forzosamente "perder trabajo" desde un repositorio compartido.

+0

wow, muchas gracias por esta extensa respuesta. Desgraciadamente lo he llevado a X. Como estoy seguro de que nadie más ha hecho una nueva búsqueda, me preguntaba si puedo deshacer estos cambios. Una cosa que realmente no tengo clara: si realizo un restablecimiento dos pasos hacia atrás, ¿me queda una de las ramas o ambas? – dmeu

+0

OK, bueno, después de presionar, si estás * realmente seguro * :-) ... lo que quieres hacer es obtener la idea de X de la punta de la rama 'desarrollo 'para rebobinar un poco. Déjame editar la respuesta ... – torek

+2

Ah, y: si'j reinicia HEAD ~ 2' para "mover dos pasos hacia atrás", esto realmente equivale a decirle a git: "establece la punta de la rama en la que estoy ahora", presumiblemente este sería tu ' desarrolla'- "a la ID de compromiso que obtienes buscando HEAD ~ 2". Si quieres ver qué commit es, 'git show HEAD ~ 2' primero. Si estás en commit 'M' del diagrama de arriba,' HEAD ~ 2' es 'M ~ 2' que retrocede tanto en' M' como 'C *'. Probablemente no es lo que quieres. – torek

Cuestiones relacionadas