comando git para hacer una rama como otra

81

Estoy tratando de tomar una rama con cambios y hacer que vuelva a ser idéntica a la corriente arriba de la que divergió. Los cambios son locales y se han enviado a github, por lo que ninguno git reseto git rebaseson realmente viables, ya que cambian el historial, lo cual es malo con una rama que ya se ha introducido.

También probé git mergecon varias estrategias, pero ninguna de ellas deshace los cambios locales, es decir, si hubiera agregado un archivo, una combinación podría traer otros archivos de nuevo a la línea, pero todavía tendré ese archivo que el upstream no tener.

Podría simplemente crear una nueva rama fuera del flujo ascendente, pero realmente me gustaría una fusión que, en términos de historial de revisión, aplique todos los cambios para tomar mi rama y hacerla idéntica al flujo ascendente nuevamente, para poder impulsar ese cambio de manera segura sin una historia aplastante. ¿Existe tal comando o serie de comandos?

Arne Claassen
fuente
9
Si no le importa conservar los cambios, ¿por qué no simplemente eliminar y volver a crear la rama? "La historia del proyecto" no tiene por qué ser sagrada. Git es una herramienta para ayudar a los desarrolladores a comunicarse. Si estos cambios no ayudan, deséchelos.
wnoise
+100 @wnoise - especialmente si los cambios ya se fusionaron.
wuputah
7
Me preocupo por preservar la historia porque está publicada para colaboración y quizás quiera volver a ella. ¿Por qué molestarse en usar el control de revisión si solo mantiene lo último?
Arne Claassen
1
Este es un argumento subjetivo, pero para mí, el propósito de un VCS no es registrar todos los detalles del historial del proyecto, sino solo registrar cambios en el contenido (confirmaciones), para permitirle manipular el árbol / historial. en esas confirmaciones (ramificación, fusión, reorganización, restablecimiento, etc.) y le permiten ver informes basados ​​en el historial (diferencias, registros, culpa, etc.). git es el "rastreador de contenido estúpido"; lo veo como una herramienta para administrar el código fuente, no como una máquina del tiempo.
wuputah
5
Como dijiste, es subjetivo. Me importa poder revisar los enfoques abandonados y poder ver qué decisiones se tomaron en algún momento del pasado. Y me importa que mi decisión de abandonar algo no destruya los puntos de fusión a los que otros podrían estar apuntando.
Arne Claassen

Respuestas:

107

Puede fusionar su rama ascendente a su devrama, con un controlador de fusión personalizado "keepTheirs" :
consulte " " git merge -s theirs"necesario, pero sé que no existe ".
En su caso, solo .gitattributesse requeriría uno y un keepTheirsscript como:

mv -f $3 $2
exit 0

git merge --strategy=theirs Simulación # 1

Se muestra como una combinación, con upstream como primer padre.

Jefromi menciona (en los comentarios) el merge -s ours, fusionando su trabajo en el flujo ascendente (o en una rama temporal comenzando desde el flujo ascendente), y luego adelantando su rama al resultado de esa fusión:

git checkout -b tmp origin/upstream
git merge -s ours downstream         # ignoring all changes from downstream
git checkout downstream
git merge tmp                        # fast-forward to tmp HEAD
git branch -D tmp                    # deleting tmp

Esto tiene la ventaja de registrar el ancestro ascendente como el primer padre, de modo que la combinación significa "absorber esta rama de tema desactualizada" en lugar de "destruir esta rama de tema y reemplazarla por ascendente" .

(Editar 2011):

Este flujo de trabajo ha sido informado en esta publicación de blog por el OP :

¿Por qué quiero esto de nuevo?

Siempre que mi repositorio no tuviera nada que ver con la versión pública, todo estaba bien, pero como ahora me gustaría tener la capacidad de colaborar en WIP con otros miembros del equipo y colaboradores externos, quiero asegurarme de que mis ramas públicas estén confiable para que otros se bifurquen y tomen, es decir, no más rebase y reinicio en las cosas que he enviado a la copia de seguridad remota, ya que ahora está en GitHub y es público.

Entonces eso me deja con cómo debo proceder.
El 99% de las veces mi copia irá al maestro ascendente, por lo que quiero trabajar con mi maestro y empujar hacia el flujo ascendente la mayor parte del tiempo.
Pero de vez en cuando, lo que tengo dentro wipserá invalidado por lo que va río arriba y abandonaré una parte de mi wip.
En ese momento, quiero que mi maestro vuelva a sincronizarse con el flujo ascendente, pero no destruir ningún punto de compromiso en mi maestro enviado públicamente. Es decir, quiero una fusión con el flujo ascendente que termina con el conjunto de cambios que hace que mi copia sea idéntica al flujo ascendente .
Y eso es lo que git merge --strategy=theirsdebería hacer.


git merge --strategy=theirs Simulación # 2

Se muestra como una fusión, con el nuestro como primer padre.

(propuesto por jcwenger )

git checkout -b tmp upstream
git merge -s ours thebranch         # ignoring all changes from downstream
git checkout downstream
git merge --squash tmp               # apply changes from tmp but not as merge.
git rev-parse upstream > .git/MERGE_HEAD #record upstream 2nd merge head
git commit -m "rebaselined thebranch from upstream" # make the commit.
git branch -D tmp                    # deleting tmp

git merge --strategy=theirs Simulación # 3

Esta publicación de blog menciona :

git merge -s ours ref-to-be-merged
git diff --binary ref-to-be-merged | git apply -R --index
git commit -F .git/COMMIT_EDITMSG --amend

a veces quieres hacer esto, y no porque tengas "basura" en tu historial, sino quizás porque quieres cambiar la línea de base para el desarrollo en un repositorio público donde se debe evitar la reorganización .


git merge --strategy=theirs Simulación # 4

(misma publicación de blog)

Alternativamente, si desea mantener las sucursales ascendentes locales con reenvío rápido, un compromiso potencial es trabajar con el entendimiento de que para sid / inestable, la sucursal ascendente se puede restablecer / reajustar de vez en cuando (según los eventos que finalmente están fuera de su control en el lado del proyecto aguas arriba).
Esto no es un gran problema y trabajar con esa suposición significa que es fácil mantener la rama ascendente local en un estado en el que solo necesita actualizaciones rápidas.

git branch -m upstream-unstable upstream-unstable-save
git branch upstream-unstable upstream-remote/master
git merge -s ours upstream-unstable
git diff --binary ref-to-be-merged | git apply -R --index --exclude="debian/*"
git commit -F .git/COMMIT_EDITMSG --amend

git merge --strategy=theirs Simulación # 5

(propuesto por Barak A. Pearlmutter ):

git checkout MINE
git merge --no-commit -s ours HERS
git rm -rf .
git checkout HERS -- .
git checkout MINE -- debian # or whatever, as appropriate
git gui # edit commit message & click commit button

git merge --strategy=theirs Simulación # 6

(propuesto por el mismo Michael Gebetsroither ):

Michael Gebetsroither intervino, alegando que estaba "haciendo trampa";) y dio otra solución con comandos de plomería de nivel inferior:

(No sería git si no fuera posible con comandos de solo git, todo en git con diff / patch / apply no es una solución real;).

# get the contents of another branch
git read-tree -u --reset <ID>
# selectivly merge subdirectories
# e.g superseed upstream source with that from another branch
git merge -s ours --no-commit other_upstream
git read-tree --reset -u other_upstream     # or use --prefix=foo/
git checkout HEAD -- debian/
git checkout HEAD -- .gitignore
git commit -m 'superseed upstream source' -a

git merge --strategy=theirs Simulación # 7

Los pasos necesarios se pueden describir como:

  1. Reemplace su árbol de trabajo con upstream
  2. Aplicar los cambios al índice
  3. Agregar upstream como segundo padre
  4. Cometer

El comando git read-treesobrescribe el índice con un árbol diferente, logrando el segundo paso , y tiene banderas para actualizar el árbol de trabajo, logrando el primer paso . Al confirmar, git usa SHA1 en .git / MERGE_HEAD como segundo padre, por lo que podemos completar esto para crear una combinación de confirmación. Por lo tanto, esto se puede lograr con:

git read-tree -u --reset upstream                 # update files and stage changes
git rev-parse upstream > .git/MERGE_HEAD          # setup merge commit
git commit -m "Merge branch 'upstream' into mine" # commit
VonC
fuente
7
Siempre puedes usar la nuestra en lugar de la de ellos: echa un vistazo a la otra rama, fusiona la tuya en ella y luego adelanta la tuya a la fusión. git checkout upstream; git merge -s ours downstream; git checkout downstream; git merge upstream. (Utilice una rama temporal en sentido ascendente si es necesario). Esto tiene la ventaja de registrar el antecesor ascendente como el primer padre, de modo que la combinación significa "absorber esta rama de tema desactualizada" en lugar de "destruir esta rama de tema y reemplazarla con aguas arriba ".
Cascabel
@Jefromi: excelente punto, como siempre. Lo he incluido en mi respuesta.
VonC
Otra opción, como git merge --strategy = theirs Simulation # 1, excepto que esta conserva el brange como el primer padre de fusión: git checkout -b tmp origin / upstream git merge -s our downstream # ignorando todos los cambios de git descendente checkout downstream git merge --squash tmp # aplica cambios desde tmp pero no como merge. git rev-parse upstream> .git / MERGE_HEAD #record upstream como el segundo cabezal de fusión git commit -m "rebaselined our from upstream" # realiza la confirmación. git branch -D tmp # eliminando tmp
jcwenger
2
Vaya, quién hubiera pensado que --strategy = suya podría implementarse de tantas maneras. Ahora, si pudiera estar en la próxima versión de git
Arne Claassen
VonC y su conocimiento son asombrosos. Es como el JonSkeet de git. :)
sjas
13

Me parece que solo necesitas hacer:

$ git reset --hard origin/master

Si no hay ningún cambio para impulsar en sentido ascendente, y simplemente desea que la rama ascendente sea su rama actual, esto lo hará. No es perjudicial hacer esto localmente, pero perderá todos los cambios locales ** que no se hayan enviado a master.

** En realidad, los cambios aún existen si los ha realizado localmente, ya que las confirmaciones seguirán estando en su git reflog, generalmente durante al menos 30 días.

wuputah
fuente
esto funciona si necesita ese cambio a cualquier precio, porque puede cambiar el historial de la rama (debe presionar con -f). que se puede configurar para ser bloqueado, por lo que básicamente funcionará solo para sus propios repositorios privados, en la práctica.
ribamar
12

Puede hacer esto con bastante facilidad ahora:

$ git fetch origin
$ git merge origin/master -s recursive -Xtheirs

Esto sincroniza su repositorio local con el origen y conserva el historial.


fuente
5
git merge -s recursive -Xtheirsno fusiona automáticamente archivos binarios, por lo que termina en una situación de conflicto que debe resolver manualmente. Los flujos de trabajo basados ​​en git merge -s oursno sufren de esto.
Stefaan
Esto parece crear una confirmación vacía.
Robin Green
Mis disculpas, de hecho funciona, pero git showen un compromiso de fusión solo muestra resoluciones de conflictos, y no hay resoluciones de conflictos si -Xtheirsse utilizan, obviamente.
Robin Green
5

Otra simulación para git merge -s theirs ref-to-be-merged:

git merge --no-ff -s ours ref-to-be-merged         # enforce a merge commit; content is still wrong
git reset --hard HEAD^2; git reset --soft HEAD@{1} # fix the content
git commit --amend

Una alternativa al doble reinicio sería aplicar el parche inverso:

git diff --binary ref-to-be-merged | git apply -R --index
michas
fuente
Interesante uso del parche inverso. +1
VonC
El reinicio no funcionó para mí, obtuve "fatal: argumento ambiguo 'HEAD2': revisión desconocida o ruta que no está en el árbol de trabajo". . (Sí, escribí HEAD^2) El método de parche funcionó.
user247702
@Stijn: Lo más probable es que no hayas escrito ^correctamente. A veces, otras pulsaciones de teclas como "ctrl-c" se muestran como "^ C". - Si realmente escribiste el "^" correcto, encontraste un error grave en tu versión de git.
michas
@michas En Windows, el carácter ^ se usa para escapar, por lo que para usarlo como literal, debe escapar por sí mismo. git reset --hard HEAD^^2; git reset --soft HEAD@{1}
Joshua Walsh
4

También hay una forma con poca ayuda del comando de plomería: en mi humilde opinión, la más sencilla. Supongamos que desea emular "el suyo" para el caso de 2 ramas:

head1=$(git show --pretty=format:"%H" -s foo)
head2=$(git show --pretty=format:"%H" -s bar)
tree=$(git show --pretty=format:"%T" -s bar)
newhead=$(git commit-tree $tree -p $head1 -p $head2 <<<"merge commit message")
git reset --hard $newhead

Esto fusiona un número arbitrario de cabezas (2 en el ejemplo anterior) usando el árbol de una de ellas (la barra en el ejemplo anterior, proporcionando el árbol 'suyo'), sin tener en cuenta cualquier problema de diferencias / archivo (el árbol de confirmación es un comando de bajo nivel, por lo que no le importan esos). Tenga en cuenta que la cabeza puede ser solo 1 (por lo que equivale a seleccionar "el suyo").

Tenga en cuenta que el encabezado principal que se especifica primero puede influir en algunas cosas (consulte, por ejemplo, el comando --first-parent of git-log), así que téngalo en cuenta.

En lugar de git-show, se puede usar cualquier otra cosa capaz de generar un árbol y confirmar hashes, cualquiera que esté acostumbrado a analizar (cat-file, rev-list, ...). Puede seguir todo con git commit --amend para embellecer interactivamente el mensaje de confirmación.

Michal Soltys
fuente
Este es el más sencillo a mis ojos. Estás usando comandos de plomería para decir "Crea un nuevo objeto de confirmación con este árbol, este primer padre, este segundo padre. Luego apunta la cabeza a esta nueva confirmación", que es exactamente lo que queremos "git merge -s theirs" que hacer. El inconveniente es que necesita guardar 4 hash diferentes para que esta operación funcione.
Michael R
2

Mano dura, pero demonios, ¿qué puede salir mal?

  • Mira la rama X que quieres que se parezca a la Y
  • cp -r .git /tmp
  • Echa un vistazo a la sucursal Y git checkout y
  • rm -rf .git && cp -r /tmp/.git .
  • Comprometerse e impulsar cualquier diferencia
  • HECHO.
sscarduzio
fuente
Esta es la forma más simple y de fuerza bruta de hacer dos ramas idénticas, asumiendo que no le importa mantener un historial de fusiones.
Damon D
1

cambie a la rama ascendente remota y realice una git mergecon la estrategia de fusión establecida en ours.

git checkout origin/master
git merge dev --strategy=ours
git commit ...
git push

Todo el historial seguirá estando presente, pero tendrá una confirmación de fusión adicional. Lo importante aquí es comenzar desde la versión en la que desea estar y fusionarse ourscon la rama en la que se encuentra realmente github.

kelloti
fuente
1
Necesito lo contrario. Eso tomará mi rama y la integrará en el flujo ascendente, pero dejará el cabezal ascendente sin cambios. Pero necesito tomar la corriente arriba e integrarla en mi rama dejando que mi cabeza parezca corriente arriba. Básicamente algo así --strategy=theirs, excepto que el más cercano --strategy=recursive -X=theirsno hace eso.
Arne Claassen
--strategy=theirses todo lo contrario de --strategy=ours. Comienzas desde el extremo opuesto (así que comienza desde github y fusiona de la otra manera).
kelloti
no hay --strategy=theirs, que es el problema. Lo más cercano es --strategy=recursive -X theirscuál no es todo lo contrario, ya que no eliminará los cambios locales extraños si no entran en conflicto.
Arne Claassen
Estos dos son opuestos: git checkout dev; git merge origin/master --strategy=oursygit checkout origin/master; git merge dev --strategy=ours
wuputah
2
@Arne: Vea mi comentario sobre la respuesta de VonC. La presencia de la oursestrategia hace que sea completamente posible hacer una theirsestrategia.
Cascabel
1

¡Usa git reset HACIA ATRÁS!

Puede hacer que una rama se parezca a cualquier otra confirmación con git reset, pero tiene que hacerlo de forma indirecta.

Para hacer que una rama en la confirmación <old>parezca una confirmación <new>, puede hacer

git reset --hard <new>

para hacer <new>el contenido del árbol de trabajo.

Entonces hazlo

git reset --mixed <old> 

para cambiar la rama de nuevo a la confirmación original pero dejando el árbol de trabajo en el <new> estado .

Luego, puede agregar y confirmar los cambios, para que su rama coincida exactamente con el contenido de la <new>confirmación.

Es contrario a la intuición que para pasar del <old>estado al <new>que necesita hacer un git reset desde <new> hasta <old> . Sin embargo, con la opción, --mixedel árbol de trabajo se deja en <new>y el puntero de rama se establece en <old>, de modo que cuando se confirman los cambios, la rama se ve como queremos.

Advertencia

No pierdas de vista tus confirmaciones, por ejemplo, olvídate de lo que <old>está haciendo git reset --hard <new>.

Iván
fuente
0

Seguí esos roles:

Obteniendo el origen, reinicio duro desde la rama, luego recursivo desde el suyo y luego forzado empujar a la rama

BAJO SU PROPIO RIESGO

git fetch origin
git reset --hard origin/<branch>
git merge origin/<branch> -s recursive -Xtheirs
git push -f <remote> <branch>
esso
fuente