Blog

Igualar dos ramas en git / sobreescribir una rama en otra

Igualar dos ramas en git / sobreescribir una rama en otra

Normalmente utilizaríamos la opción 1 o 2 que aparecen a continuación, empezar una rama nueva o borrar los commis existentes con un reset:

1. Don’t bother. Abandon branch A, make a new A2, and use that.

2. Use git reset or equivalent to re-point A elsewhere.Methods 1 and 2 are effectively the same in the long run. For instance, suppose you start by abandoning A. Everyone develops on A2 instead for a while. Then, once everyone is using A2, you delete the name A entirely. Now that A is gone, you can even rename A2 to A (everyone else using it will have to do the same rename, of course). At this point, what, if anything, looks different between the case where you used method 1 and the case where you used method 2? (There is one place you may still be able to see a difference, depending on how long your «long run» is and when you expire old reflogs.)

3. Make a merge, using a special strategy (see «alternative merge strategies» below). What you want here is -s theirs, but it doesn’t exist and you must fake it.

https://stackoverflow.com/a/36321787

La segunda opción no vale si ya hemos hecho push, porque tendríamos que hacer --force y fastidiaríamos a los demás. La primera opción está bien pero quizá pueda ser un poco confuso para los demás. En mi trabajo utilizamos el identificador de la tarea del Jira como nombre de la rama, y hacer feature/proyecto-XXXX-2 es salirse un poco de esa norma. La gente va a piñón haciendo checkout de feature/proyecto-XXXX y se descargarían la antigua, así que también quería evitar este método. ¿Se puede seguir con la rama antigua añadiendo un commit que iguale a la otra?

El propósito de git merge es, como indica su nombre, juntar el código de dos ramas. Salvo para conflictos, no existe ninguna rama «dominante»:

The modifier «dominant» is not defined by Git. The way you use the word appears to me to make an incorrect assumption, which I think makes the question un-answerable as is. With one small change, though, the answer becomes simple: it’s neither. No branch is «dominant» here; both branches are equal partners, and the result of the merge is the same, whether you merge A into B, or B into A—but you can change this, in several ways.

https://stackoverflow.com/a/42104116

Si tenemos un fichero con una sola línea y dos ramas diferentes:

If we changed line 1 and they didn’t, the merge result is our version.
If we didn’t change line 1, and they did, the merge result is their version.
If we both changed line 1, the merge result is that the merge fails, with a merge conflict. Git writes both lines into the file and stops the merge with an error, and makes us clean up the mess:

https://stackoverflow.com/a/42104116

Como explica ahí, si nuestra rama ha cambiado un fichero que no se ha modificado en la otra, git seguirá tomando la nuestra. Si simplemente queremos «igualar» a la otra, esto no nos vale.

Las opciones X ours/theirs tampoco nos valen porque sólo se aplican en caso de conflicto:

While git merge‘s default recursive strategy has -X options ours and theirs, they do not do what we want here. These simply say that in the case of a conflict, git should automatically resolve that conflict by choosing «our change» (-X ours) or «their change» (-X theirs).

https://stackoverflow.com/a/36321787

Finalmente, encontré aquí una solución: (aquí intenta igualar A, que es la rama «mala», a B):

As outlined in this other answer, there are a number of ways to force git to effectively implement -s theirs. I think the simplest to explain is this sequence:

git checkout A
git merge --no-commit -s ours B
git rm -rf .         # make sure you are at the top level!
git checkout B -- .
git commit

The first step is to ensure that we are on branch A, as usual. The second is to fire up a merge, but avoid committing the result yet (--no-commit). To make the merge easier for git—this is not needed, it just makes things faster and quieter—we use -s ours so that git can skip the diff steps entirely and we avoid all merge conflict complaints.

At this point we get to the meat of the trick. First we remove the entire merge result, since it is actually worthless: we do not want the work-tree from the tip commit of A, but rather the one from the tip of B. Then we check out every file from the tip of B, making it ready to commit.

Last, we commit the new merge, which has as its first parent the old tip of A and as its second parent the tip of B, but has the tree from commit B.

If the graph just before the commit was:

...---o--...--o--o    <-- A
     /       /
...-o--...--*--o--o   <-- B

then the new graph is now:

...---o--...--o--o--o   <-- A
     /       /     /
...-o--...--o--o--*     <-- B

The new merge-base is the tip of B as usual, and from the perspective of the commit graph, this merge looks exactly like any other merge. What’s unusual is that the source tree for the new merge at the tip of A exactly matches the source tree for the tip of B.

https://stackoverflow.com/a/36321787