¿Quiénes somos "nosotros" y quiénes son "ellos" según Git?
Después de una rebase de Git, y en otras circunstancias, puede encontrar algunos archivos marcados como eliminados por nosotros en el git status
informe. ¿ Quiénes somos según Git y por qué?
¿Se refiere a que estoy sentado en esta rama y funciona para mí? ¿O se refiere a sí mismo y a las personas que trabajan en la rama contra la que estoy rebasando?
Cuando fusionas , us
se refiere a la rama en la que te estás fusionando, a diferencia de them
, la rama que se fusionará.
Cuando rebase , us
se refiere a la rama ascendente y them
es la rama por la que se está moviendo. Es un poco contrario a la intuición en caso de una rebase.
La razón es que Git usa el mismo motor de fusión para rebase, y en realidad está seleccionando tus cosas en la rama ascendente. us
= hacia, them
= desde.
¿Quiénes somos "nosotros"/"nuestros" y "ellos"/"de ellos" según Git?
(Esto también responde a la pregunta: "¿Cómo funciona un rebase de git y qué está sucediendo exactamente con él?")
Cuando tienes conflictos en medio de un git merge
, cherry-pick
, rebase
o revert
, es posible que tengas que elegir un lado para elegir mantener el contenido conflictivo solo del lado --ours
o --theirs
(y de lo contrario, mantener el contenido no conflictivo de ambos lados), lo que lleva a la Pregunta: ¿quiénes somos "nosotros" frente a "ellos" en cada uno de esos casos?
La resolución de conflictos podría implicar lo siguiente, por ejemplo:
git checkout master
git merge feature_branch
git checkout --theirs -- path/to/some/dir
git add path/to/some/dir
git status
git merge --continue
Pero, ¿qué hace la git checkout --theirs
línea y qué otras opciones tenemos? Aquí hay algunas opciones:
# ------------------------------------------------------------------------------
# Do this:
# - Generally, one of these might be useful during conflict resolution:
# ------------------------------------------------------------------------------
# Integrate changes from both sides, but in the lines which conflict, keep the
# `--theirs` changes for all conflicts within files inside this directory.
git checkout --theirs -- path/to/some/dir
# OR: Integrate changes from both sides, but in the lines which conflict, keep
# the `--ours` changes for all conflicts within files inside this directory.
git checkout --ours -- path/to/some/dir
# ------------------------------------------------------------------------------
# Do *not* do this:
# - Generally, do *not* do this during conflict resolution:
# ------------------------------------------------------------------------------
# Discard all changes from both sides by hard resetting all the contents inside of
# this directory back to their state at the `some_branch` commit.
# - But, oddly enough, this does *not* do deletions of files inside this directory
# which don't exist at this `some_branch` commit but are there now. So, this
# isn't even a proper hard reset of this directory. It's a mishmash.
git checkout some_branch -- path/to/some/dir
Consulte el "mejor ejemplo de resolución de conflictos" a continuación, así como todos los ejemplos de la sección "Casos de uso de ejemplo" , para obtener más detalles. Consulte también "¡ADVERTENCIA, ADVERTENCIA, ADVERTENCIA!" sección sin embargo. En la mayoría de los casos, no querrás hacerlo git checkout some_branch -- path/to/some/dir
en un intento de resolver conflictos, ya que eso descarta todo merge
, cherry-pick
, rebase
o revert
, deshaciendo todos los cambios de un lado en lugar de mantener los cambios de ambos lados pero favoreciendo a un lado en las líneas en conflicto. Entonces, usar --ours
o --theirs
, como se muestra arriba, generalmente es el curso de acción correcto en lugar de usar some_branch
.
Estudie esas secciones al final de mi respuesta para obtener más detalles.
Pasemos ahora a la respuesta principal:
En todos los casos:
En términos sencillos ( gracias, @user20358 ):
"nosotros" es el código que ya está en el repositorio y "ellos" es el código que [estás] intentando fusionar
En términos más completos:
- "nosotros" (o "nuestro"
HEAD
) = el commit ( ) actualmente desprotegido en el momento en que git realiza la acción que causa el conflicto (más sobre esto más adelante), y: - "ellos" (o "de ellos" ) = el otro compromiso, NO verificado por git en el momento en que git realiza la acción que causa el conflicto (más sobre esto más adelante).
IMPORTANTE: HEAD
en el momento en que realiza la acción que causa el conflicto NO es necesariamente la HEAD
en el momento en que escribe el comando git. Esto es esencial de entender. Git puede realizar algunas comprobaciones y cambiar HEAD
(qué confirmación se verifica) antes de ejecutar la acción que causa el conflicto, lo que hace que "nosotros" y "ellos" aparezcan intercambiados o al revés para el ojo inexperto.
Los 4 casos, en detalle: fusionar, seleccionar, rebase, revertir:
git merge
(intuitivo):- Comando de muestra:
git checkout master git merge feature_branch # merge feature_branch into master
- "us"/"ours" =
HEAD
, es decirmaster
, porque estabas en la sucursalmaster
en el momento en que ejecutastegit merge feature_branch
. - "them"/"theirs" =
feature_branch
, que es la rama en la que te estás fusionandomaster
.
- Comando de muestra:
git cherry-pick
(intuitivo):- Comando de muestra:
git checkout feature_branch git cherry-pick some_commit # apply some_commit to feature_branch
- "us"/"ours" =
HEAD
, es decirfeature_branch
, porque estabas en la sucursalfeature_branch
en el momento en que ejecutastegit cherry-pick some_commit
. - "them"/"theirs" =
some_commit
, que es el compromiso que estás seleccionandofeature_branch
.
- Comando de muestra:
git rebase
(contraintuitivo, pero tiene mucho sentido una vez que comprendes la mecánica de cómo funciona):- Comando de muestra:
git checkout feature_branch git rebase master # rebase feature_branch onto latest master
- Diagrama de esto (dibujado en https://asciiflow.com ), con las confirmaciones más recientes o más recientes en la parte superior y/o a la derecha:
# Prior to rebase: feature_branch # received new commits while # master did too # # master # x # | feature_branch # x y # | | # x y # | / # git merge-base ────► x--y--y--y # master feature_branch | # x # # # After rebase: feature_branch has # been completely cherry-picked onto # the end of master # # feature_branch # y' # | # y' # / # y'--y'--y' # | # master x # | # x # | # x # | # x # | # x
- "us"/"ours" =
HEAD
, que es la rama ascendente: inicialmente la últimax
confirmación enmaster
, y luego, alguna NUEVA confirmación,y'
ademáscherry-picked
de eso (¡ésta es complicada!). Esto se debe a que cuando escribistegit rebase master
, git primero compruebamaster
como el punto de partida para comenzar a seleccionar tusfeature_branch
confirmaciones , luego determina cuáles confirmacionesfeature_branch
seleccionarás (es decir, cuáles de tusfeature_branch
confirmaciones aún no están activadasmaster
). Lo hace encontrandomerge-base
(el compromiso que es común a ambosfeature_branch
ymaster
que se puede encontrar congit merge-base master feature_branch
), y ENTONCES comienza a seleccionar los compromisos desde el primero después de estemerge-base
y en adelante, trabajando uno a la vez, hacia la última confirmación enfeature_branch
, en la punta demaster
, "rebasando" todasy
las confirmaciones "nuevas" que agregófeature_branch
a la últimamaster
, como nuevasy'
confirmaciones. Por lo tanto, "nosotros"/"nuestros" =HEAD
, pero dado que git realizó una nueva verificación detrás de escena para realizar esta rebase,HEAD
NO es la rama en la que estaba cuando escribiógit rebase master
. En cambio, nosotros , oHEAD
, es la últimax
confirmaciónmaster
si el conflicto ocurre durante la primeracherry-pick
, o es cualquier NUEVA confirmación, quey'
fue seleccionada con éxito por última vezmaster
si el conflicto ocurre durante una selección posterior. Por lo tanto, es el otro compromiso, que es uny
compromisofeature_branch
que se aplica a este nuevoHEAD
mediante una selección, en orden, DESDE el primery
compromisofeature_branch
que es inmediatamente despuésgit merge-base master feature_branch
HASTA el últimoy
compromisofeature_branch
. Vea también la explicación para "ellos", justo debajo. - "ellos"/"sus" = alguna
y
confirmaciónfeature_branch
que se está aplicando a un recién retiradoHEAD
, dondeHEAD
está la últimax
confirmaciónmaster
para la primera operación de selección durante la rebase, O una de estasy'
confirmaciones recién creadas en la parte superior demaster
asfeature_branch
está "rebasada", o se selecciona una confirmación a la vez (a lo largo de su cadena de nuevas confirmaciones desdegit merge-base master feature_branch
la última confirmación enfeature_branch
) enmaster
. Vea también la explicación para "nosotros", justo arriba.
- Comando de muestra:
git revert
(algo intuitivo):- Comando de muestra:
git checkout feature_branch # create a new commit to undo the changes from some_previous_commit # within feature_branch git revert some_previous_commit
- Para conocer algunas de las mecánicas detalladas de bajo nivel de esta, consulte mi sección "Resultados y conclusiones" al final de mi otra respuesta aquí , así como esta respuesta muy larga y detallada de @torek aquí .
- "us"/"ours" =
HEAD
, es decirfeature_branch
, porque estabas en la sucursalfeature_branch
en el momento en que ejecutastegit revert some_previous_commit
. Más específicamente, "nosotros"/"nuestro" contiene los cambios expresados porgit diff some_previous_commit..HEAD
, que son los cambios desde el punto en el que se confirmó la confirmación revertida (some_previous_commit
) hasta la confirmación en la que nos encontramos ahora. - "them"/"theirs" = (lo inverso u opuesto a)
some_previous_commit
, que es la confirmación cuyos cambios estás revirtiendo (deshacer, creando una nueva confirmación encimafeature_branch
). En otras palabras, "ellos"/"de ellos" contiene los cambios expresados porgit diff some_previous_commit..some_previous_commit~
, dondesome_previous_commit~
está el compromiso principal desome_previous_commit
. Esto significa que "ellos"/"de ellos" es lo opuesto asome_previous_commit
, ya que contiene lo opuesto a sus cambios, para deshacersome_previous_commit
los cambios de .
- Comando de muestra:
Casos de uso de ejemplo:
Fusionar ejemplos de resolución de conflictos:
# 1. Merge `feature_branch` into `master`, accepting ALL of
# `master`'s (`ours`) changes in the event of
# any merge conflicts!
git checkout master
git merge -X ours feature_branch
# 2. Merge `feature_branch` into `master`, accepting ALL of
# `feature_branch`'s (`theirs`) changes in the event of
# any merge conflicts!
git checkout master
git merge -X theirs feature_branch
Aquí hay algunos más. Estas son mis técnicas más utilizadas , en lugar de los dos ejemplos anteriores.
# 3. Assuming this merge attempt results in merge conflicts in
# these 3 files, but we know all of the changes on the `master`
# branch side are the ones we wish to keep, check out these 3
# files from `master` (`--ours`) to overwrite the conflicted
# files. Then, add these now-fixed files to stage them for
# committing, and continue (finish) the merge.
git checkout master
git merge feature_branch
git checkout --ours -- path/to/somefile1.c path/to/somefile2.c path/to/somefile3.c
git add path/to/somefile1.c path/to/somefile2.c path/to/somefile3.c
git status
git merge --continue
# 4. Assuming this merge attempt results in merge conflicts in
# these 3 files, but we know all of the changes on the `feature_branch`
# side are the ones we wish to keep, check out these 3
# files from `feature_branch` (`--theirs`) to overwrite the conflicted
# files. Then, add these now-fixed files to stage them for
# committing, and continue (finish) the merge.
git checkout master
git merge feature_branch
git checkout --theirs -- path/to/somefile1.c path/to/somefile2.c path/to/somefile3.c
git add path/to/somefile1.c path/to/somefile2.c path/to/somefile3.c
git status
git merge --continue
MUY ÚTIL: Si existe una carpeta completa de conflictos, también puede especificar aceptar todos los cambios conflictivos de la rama --ours
o para toda la carpeta a la vez , ¡así!:--theirs
**MEJOR EJEMPLO DE RESOLUCIÓN DE CONFLICTOS DE FUSIÓN:**
# 5. [BEST EXAMPLE] Assuming this merge attempt results in merge conflicts in
# a bunch of files, some of which are inside `path/to/some/dir`, I can
# choose to accept the changes from one side or the other **for
# all conflicts within files inside this directory**, like this!:
git checkout master
git merge feature_branch
# Keep `--theirs` for all conflicts within files inside this dir
git checkout --theirs -- path/to/some/dir
# OR: keep `--ours` for all conflicts within files inside this dir
git checkout --ours -- path/to/some/dir
# Add (stage for committing) all changes within files inside this dir
# all at once
git add path/to/some/dir
git status
git merge --continue
TRATAMIENTO path does not have our version
DE path does not have their version
ERRORES:
Si alguna vez ejecutas algo como esto:
git checkout --ours -- path/to/some/dir
...y no funcionó! No hizo nada. En cambio, genera estos errores:
error: path 'path/to/some/dir/file1.cpp' does not have our version error: path 'path/to/some/dir/file2.cpp' does not have our version error: path 'path/to/some/dir/file3.cpp' does not have our version
El problema es que estos archivos con errores son archivos eliminados en el our
lateral, por lo que debemos git rm
cada uno de ellos manualmente ANTES de ejecutar git checkout --ours -- path/to/some/dir
.
git rm path/to/some/dir/file1.cpp path/to/some/dir/file2.cpp \
path/to/some/dir/file3.cpp
# then try again
git checkout --ours -- path/to/some/dir
También puedes hacer esto para automatizar el proceso:
git checkout --ours -- path/to/some/dir \
|& gawk '{ print $3 }' | xargs git rm
git checkout --ours -- path/to/some/dir
Vea mi respuesta aquí para obtener una explicación detallada de los comandos anteriores: git checkout --ours cuando la especificación del archivo incluye el archivo eliminado .
¡ADVERTENCIA ADVERTENCIA ADVERTENCIA!
Para obtener un ejemplo más detallado del problema que se describe a continuación, consulte mi otra respuesta aquí .
NO lo haga git checkout -- path/to/some/dir
, ni git checkout some_branch -- path/to/some/dir
en medio de la resolución de un conflicto (como durante un merge
conflicto como en los ejemplos anteriores), A MENOS QUE INTENTE VERIFICAR TODOS LOS ARCHIVOS DE HEAD
, o de some_branch
, respectivamente, en el directorio path/to/some/dir
, Y SOBRESCRIBIR LOS ARCHIVOS LOCALES CON AQUELLOS ARCHIVOS, por lo que no se limita a aceptar los cambios conflictivos de un lado o del otro.
En otras palabras, en medio de la resolución de un conflicto , esto:
BIEN:
# GOOD :)
# Accept all conflicts from one side or the other (while still
# merging changes from both sides into one, in the event of being
# in the middle of a `git merge`).
git checkout --ours -- path/to/some/dir
# OR
git checkout --ours -- path/to/some/file
aceptará solo los cambios del lado --ours
, lo cual siempre es bueno y seguro si eso es lo que queremos, mientras que esto:
MALO:
# BAD :(
# OVERWRITE all files with these files from `some_branch` instead,
# thereby _losing_ any changes and/or files contained in the other
# side but which are not in `some_branch`.
git checkout some_branch -- path/to/some/dir
# OR
git checkout some_branch -- path/to/some/file
will fully check out and overwrite the ENTIRE DIRECTORY or ENTIRE FILE, as specified, rather than only the conflicting changes themselves. This means you may be inadvertently deleting changes from one side or the other by doing a full checkout with git checkout some_branch
rather than a conflict resolution with git checkout --ours
or git checkout --theirs
. FOR THIS REASON, IT IS RECOMMENDED TO USE git checkout --ours -- file_or_dir_paths
or git checkout --theirs -- file_or_dir_paths
, NOT git checkout some_branch -- file_or_dir_paths
whenever you are in the middle of a conflict resolution such as for git merge
, git cherry-pick
, git rebase
, or git revert
.
HOWEVER, if you DO run git checkout some_branch -- file_or_dir_paths
because you understand this behavior and that's what you want, then you need to be aware of this too: checking out an entire directory like that does NOT delete local files in that dir which do not exist at some_branch
, like you'd expect it would. Instead, it leaves them alone in your local file system if they exist locally but not at some_branch
. So, you must MANUALLY remove all of those files instead. Keep that in mind or else it will leave you very very confused in the end. Read more about this in my other answers here (this answer has a good solution to that too) and here.
Going further / additional notes and tips:
- The above examples of file conflict resolution can apply in the event of conflicts in any of the various types of operations (
git merge
,git cherry-pick
,git rebase
,git revert
, etc.), except you need to keep in mind whattheirs
andours
means for each type of conflict resolution, as explained above. - You can also just use branch names such as
master
orfeature_branch
in place of-X ours
/-X theirs
and--ours
and--theirs
. WARNING: Passing branch names may seem easier and clearer, but CAN BE DANGEROUS to your changes, as doing it this way does a FULL FILE OR DIRECTORY REPLACEMENT, RATHER THAN A CONFLICT RESOLUTION FOR JUST THE CONFLICTS WITHIN THE FILES. See the "WARNING WARNING WARNING" section above. If you DO want to do a full file replacement, however, rather than just accepting conflicting changes from one side or the other, here's how:# See "WARNING WARNING WARNING" section above. git checkout feature_branch -- path/to/somefile1.c path/to/somefile2.c path/to/somefile3.c
- You can also check out entire directories rather than specifying files individually! See this answer here and my answer here and my other answer here. I just tested it. This works too. Again, however, pay attention to the "WARNING WARNING WARNING" section above. This does a full directory replacement, rather than just accepting conflicting changes from one side or the other for an entire folder:
# Check out ALL files from feature_branch which are in # directory "path/to/dir". See "WARNING WARNING WARNING" # section above. git checkout feature_branch -- path/to/dir
- Remember, if you do intentionally use
git checkout feature_branch -- path/to/dir
, expecting/hoping it will delete local files in directorypath/to/dir
which do NOT exist atfeature_branch
, it will NOT. You must remove those files manually in your local file system before or after running thecheckout
command. Read more in my answers here:- All about checking out files or directories in git
- How to do a
--soft
or--hard
git reset by path
References:
- [my answer, which I reference all the time!] "All about checking out files or directories in git": How to get just one file from another branch
- [my answer] Why git can't do hard/soft resets by path? --> see especially the git terminology and definitions in the "Background knowledge" section at the end!
- [my own answer on what "them" and "us" mean during
git revert
] Who is `them` and `us` in a `git revert`? - [@LeGEC's answer which points out that "ours/us" is always
HEAD
, and "them/theirs" is always the other branch or commit] Who is `them` and `us` in a `git revert`? - [the pre-existing answer which I cross-checked against regarding my understandings of
git merge
andgit rebase
] Who is "us" and who is "them" according to Git? - From
man git checkout
: "Note that during git rebase and git pull --rebase, ours and theirs may appear swapped":--ours, --theirs When checking out paths from the index, check out stage #2 (ours) or #3 (theirs) for unmerged paths. Note that during git rebase and git pull --rebase, ours and theirs may appear swapped; --ours gives the version from the branch the changes are rebased onto, while --theirs gives the version from the branch that holds your work that is being rebased. This is because rebase is used in a workflow that treats the history at the remote as the shared canonical one, and treats the work done on the branch you are rebasing as the third-party work to be integrated, and you are temporarily assuming the role of the keeper of the canonical history during the rebase. As the keeper of the canonical history, you need to view the history from the remote as ours (i.e. "our shared canonical history"), while what you did on your side branch as theirs (i.e. "one contributor’s work on top of it").
- [answer to my question]: you can pass directory paths to git too to check out all files from entire directories, rather than having to specify files individually: How do I accept git merge conflicts from "their" branch for only a certain directory?
- [my answer] git checkout --ours when file spec includes deleted file
- For drawing pretty ASCII pictures or diagrams to place in code: https://asciiflow.com/.
Additional Study:
- [
I NEED TO STUDY THIS ANSWER MYSELF STILL!--done; I've figured it out and updated my answer here now as of 7 Jan. 2020] Who is `them` and `us` in a `git revert`? - [I NEED TO STUDY AND READ THIS STILL] git checkout --ours does not remove files from unmerged files list
See also:
- Quick links to my answers I reference frequently and consider to be "git fundamentals":
- Various ways to create a branch in git from another branch
- All about checking out files or directories in git
- Who is "us" and who is "them" according to Git?