En mi lugar de trabajo, utilizamos un flujo de trabajo de rebase. Recientemente ayudé a tres de mis colegas con el siguiente problema, así que pensé que valía la pena publicar un Q&A para ayudar a más personas y proporcionar una referencia para señalar a mis compañeros de trabajo.

Supongamos que ejecuto la siguiente serie de comandos para crear un repositorio git e inicializarlo con algunos datos y una sola confirmación.

git init .
cat > MyFile.txt <<'EOF'
> Line 1
> Line 2
> Line 3
> Line 4
> EOF
git commit -m 'Initial commit'

Luego, hago una rama y agrego un commit para modificar una línea en particular.

$ git checkout -b MyFeature
Switched to a new branch 'MyFeature'
$ ed MyFile.txt
28
2s/$/ This is the initial implementation of my glorious feature
Line 2 This is the initial implementation of my glorious feature
wq
86
git add -u
git commit -m 'WIP Add glorious feature'

Después de enviarlo para su revisión, trabajo en una función diferente que depende de esta función y realizo una nueva confirmación.

git checkout -b MySecondFeature
cat >> ThisFileDependsOnMyFile.txt <<'EOF'
> This commit touches a completely different file.
> EOF
git add ThisFileDependsOnMyFile.txt
git commit -m 'Add Feature2'

Ahora mis revisiones para la primera característica vuelven, así que le hago algunos cambios y luego la fusiono en master.

git checkout MyFeature
sed -i '2s/initial/final/' MyFile.txt
git add -u
git commit --amend -m 'Add glorious feature'
git checkout master
git merge MyFeature

Finalmente, intento volver a crear MySecondFeature en master. Desafortunadamente, explota, aunque el nuevo commit en MySecondFeature ni siquiera toca el archivo que está en conflicto.

git checkout MySecondFeature
git rebase master
First, rewinding head to replay your work on top of it...
Applying: WIP Add glorious feature
Using index info to reconstruct a base tree...
M       MyFile.txt
Falling back to patching base and 3-way merge...
Auto-merging MyFile.txt
CONFLICT (content): Merge conflict in MyFile.txt
error: Failed to merge in the changes.
Patch failed at 0001 WIP Add glorious feature
hint: Use 'git am --show-current-patch' to see the failed patch
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".

¿Cómo se evitan estos conflictos de fusión espurios al bifurcar una rama que posteriormente tiene su historia reescrita? Tenga en cuenta que siempre podemos solucionar el conflicto manualmente, pero es una gran pérdida de tiempo seguir haciendo esto.

Para que esto sea más fácil de discutir, aquí hay un par de hashes:

git log MySecondFeature
hq6:GitPlay hqin$ git log
commit 061fc8448e93f0e31239b2f4806d5caac6bfe578 (HEAD -> MySecondFeature)
Author: ***
Date:   Sun Dec 15 15:36:21 2019 -0800

    Add Feature2

commit f878084412c04542e297cb5c52b0fb6f6a2b2870
Author: ***
Date:   Sun Dec 15 15:30:45 2019 -0800

    WIP Add glorious feature

commit fe88218ac2432e086a40f052bfa4e7c759f677b4
Author: ***
Date:   Sun Dec 15 15:26:18 2019 -0800

    Initial commit


git log master
commit 522cc625b709b37c4ccfd1878465f8da30f9e082 (master, MyFeature)
Author: ***
Date:   Sun Dec 15 15:30:45 2019 -0800

    Add glorious feature

commit fe88218ac2432e086a40f052bfa4e7c759f677b4
Author: ***
Date:   Sun Dec 15 15:26:18 2019 -0800

    Initial commit

git
2
merlin2011 16 dic. 2019 a las 03:08

2 respuestas

La mejor respuesta

Me gusta hacer esto usando un rebase interactivo y descartando cualquier commit que no debería estar allí:

git rebase -i master

Su editor de git se abrirá con un archivo que le permite reordenar, editar y eliminar confirmaciones. En este caso, solo queremos eliminar el compromiso MyFeature que ahora no existe, lo que se hace colocando "drop" antes del hash de compromiso:

drop f878084 WIP Add glorious feature
pick 061fc84 Add Feature2

# Rebase 522cc62..061fc84 onto 522cc62 (2 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Y ahora puedes ver que el rebase tuvo éxito:

Successfully rebased and updated refs/heads/MySecondFeature.
1
piccoloninja 16 dic. 2019 a las 19:29

Hay tres formas de evitar la resolución manual del conflicto de fusión espurio.

Omita la confirmación conflictiva

Como en realidad no nos importa f878084412c04542e297cb5c52b0fb6f6a2b2870, simplemente podemos omitirlo durante el rebase.

git rebase master
# Bunch of merge 
git rebase --skip

Diga a rebase explícitamente qué parte de la historia debe rebase

Esta es mi solución favorita. Uno puede ejecutar git rebase con --onto y pasar la rama de destino seguida por el padre del commit más antiguo en MySecondFeature que nos interesa.

# Get out of the merge conflict situation
git rebase --abort

git rebase --onto master 061fc8448e93f0e31239b2f4806d5caac6bfe578~1

Selección de cereza

Podemos crear una rama temporal a partir del historial que queramos y seleccionar los commits de MySecondFeature que queramos.

# Get out of the merge conflict situation
git rebase --abort

git checkout -b TempBranch master
# Note that if there are multiple commits we care about on MySecond Feature, these should be cherry-picked either individually or together in the right order.
git cherry-pick MySecondFeature
git checkout MySecondFeature

# Note that this command will blow away changes in your working directory.
git reset --hard TempBranch

# git branch -d TempBranch

0
merlin2011 16 dic. 2019 a las 00:08