¿Cómo puedo aplicar los cambios de un commit a otro?
Al trabajar con tantas ramas diferentes, a veces puede ser útil aplicar los cambios de un commit que hicimos en el pasado o que está disponible en otra rama. No es raro que un colega haya creado unhotfixen una rama y que queramos aplicarlo también en la nuestra o que un commit de hace unas semanas lo queramos volver aplicar tal cuál.
Para hacer esto, podemos utilizar el comando git cherry-pickque nos permite aplicar los cambios de uno o varios commits de cualquier rama a la rama actual sin necesidad de hacer un merge.
Literalmente, cuando hablamos de Cherry Pick, estamos hablando de selección de cerezas. Donde cada cereza es un commit.
Para poder usarlo, necesitamos conocer el SHA1 del commit (o commits) que queremos aplicar git cherry-pick
# aplicamos el commit 4ccb6d3
git cherry-pick 4ccb6d3
# aplicamos el commit 4ccb6d3 y el commit 8f9f8b8
git cherry-pick 4ccb6d3 8f9f8b8
También podemos usar el parámetro-epara editar el mensaje de commit original
(por defecto, usa el mensaje del commit que estamos seleccionando)
git cherry-pick 4ccb6d3 8f9f8b8 -e
Esto te abrirá el editor de texto que tengas configurado por defecto y podrás editar el mensaje del commit.
Para poder hacer git cherry-picktu directorio de trabajo debe estar limpio de archivos modificados.
Cómo detectar qué commit es el que ha introducido un bug
Imagina que estás en un proyecto y, de repente, hay un error en producción pero, desde el último pase, se han hecho cientos de commits.¿Cómo sabes qué commit es el culpable? Hacerlo a mano es una tarea difícil…
Para ello es bueno conocer git bisect. Bisect significa partir por la mitad y es justamente lo que va a hacer este comando: ir dividiendo toda la pila de commits en dos partes, una parte de la pila contendrá el error y otra parte no.
Congitbisectvamosdividiendolalistadecommitsendosparaencontrarelcommitquefueculpable de añadir un bug en nuestra app
Aunque algo así lo podríamos hacer de forma manual con git checkout esta herramienta es mucho más eficiente. Vamos a ver cómo lo deberíamos usar.
Primero, lo iniciamos:
$ git bisect start
Ahora tenemos que marcar el error. Para ello, vamos a ir a la rama de producción y ejecutamos git bisect bad. Si ya sabemos el commit del error, podemos pasar directamente el número de commit, pero en nuestro ejemplo vamos a usarHEADpara que el commit que estamos viendo sea el que está en producción.
Ya hemos indicado cuál es el commit que sabemos que tiene el error, ahora nos toca indicar un commit que sabemos que funciona correctamente. Podemos ir bastante
atrás (por ejemplo, una semana atrás donde no ocurría el problema) y hacemos
checkout a ese commit.
$ git checkout 587d364d # 587d364d es un commit de hace una semana
$ git bisect good # indicamos que este commit sí funcionaba
También lo puedes hacer en un solo comando con git bisect good 587d364d.
Una vez hecho esto, git bisectcambiará el HEADpor un commit entre los sospechosos y nos indicará el número de pasos que deberíamos hacer para encontrar el error y el número de commits por revisar.
Bisecting: 16 revisions left to testafter this (roughly 4 steps)
[92e11f055a73eb61ca5c17657d84f8340b9bcc57] Update youtube count
En este punto deberemos probar el código para ver si el commit que probamos contiene el error. Si contiene el error ejecutamos git bisect bady, si no lo contiene, ejecutamos git bisect good.
En cualquiera de los dos casos, git bisectvolverá a cambiar el HEADpor otro commit para que volvamos a hacer la prueba y repetiremos los pasos hasta que definitivamente nos indique en qué commit se introdujo el problema.
$ git bisect good
a6ee8be012ecd323a0cf0ba5be0c8e85bcad5d11 is the first bad commit
commit a6ee8be012ecd323a0cf0ba5be0c8e85bcad5d11
Merge: a053f1f 94fc4d4
Author: Miguel Ángel Durán <miduga@gmail.com>
Date: Sat Jul 3 16 :42:27 2021 +0200
Merge pull request #63 from tu-usuario/new-search-less-deps
New search improving perf
assets/js/scripts.js | 135 ++++++++++++------------
assets/styles/global.css | 5 +-
layouts/_default/baseof.html | 1 -
layouts/partials/logo.html | 77 +++++++---------
4 files changed, 104 insertions(+), 114 deletions(-)
Una vez que tengamos el commit, debemos ejecutar git bisect resetpara restablecer el HEADcorrecto y finalizar el proceso.
Ahora que sabes esto… no uses este comando para señalar a un colega de trabajo. Todos cometemos errores. Y somos un equipo. :)
¿Quién ha tocado este fichero? ¿Quién ha hecho cambios?
Con git logsomos capaces de ver el historial de cambios en general pero a veces queremos saber qué cambios han habido en un fichero en específico. Para ello tenemos que usar el comando git blame.
git blamenecesita como parámetro el fichero que queremos inspeccionar para conocer el historial de cambios que ha recibido y la autoría de estas alteraciones.
# ¿qué cambios han habido sobre el fichero src/main.js?
$ git blame src/main.js
c3fe8972 src/main.js(tu-usuario … c603b8aa src/main.js(Jorge del Casar … 789bd97d src/main.js(tu-usuario … c3fe8972 src/main.js(tu-usuario … 7392da07 src/main.js(Juan Vasquez …
En la primera columna encontramos el número de cambio (c3fe8972), en la segunda el fichero que se alteró y en la tercera columna el nombre del autor del cambio (tu-usuario). Después encontramos la fecha en la que se realizó el cambio y, por último, la línea que el autor alteró.
Por motivos de formato no he incluido toda la salida del comando pero en los puntos suspensivos (…) está la información de la cuarta y quinta columna (fecha y cambio realizado).
Aunque este comando, como dice el nombre, te permite buscar al culpable… no lo uses para culpar a nadie. Al final, lo que llega a producción es responsabilidad de todo el equipo.
git blamees un comando muy potente que te permite enfocarte en un archivo y, además, filtrar a través de algunas opciones la forma de inspeccionar el historial de cambios del fichero. ¡Incluso puedes hacerlo a nivel de líneas! Te dejo algunas opciones para que las pruebes:
# Inspecciona sólo los cambios entre la línea 5 y 10
$ git blame -L 5 ,10 src/main.js
# ignora los cambios que son espacios en blanco
$ git blame -w package.json
# muestra la dirección de correo electrónico en lugar
$ git blame -e README.md
# detecta líneas que se han movido o copiado
$ git blame -M src/main.js
# detecta líneas que se han movido o copiado
$ git blame -C src/main.js
¿Cómo puedo saber quién añadió una línea por primera vez?
Si intentas usar git blamepara saber quién fue la primera persona que añadió una línea a un fichero… puedes encontrarte que puede ser algo engorroso, ya que git blameestá pensado para que encuentres los cambios recientes.
Para eso es mejor usar git logya que gracias a la opción de búsqueda-Spuedes buscar líneas de código fácilmente y conocer la autoría de esos cambios. Además, es muy fácil ordenar los resultados de forma que conozcas quién fue la primera persona en añadir los cambios gracias a la opción—reverse.
# buscamos quién añadió por primera vez
$ git log -S "createEditor "--reverse
commit 5711c5f07e2fb95c60a08527b521dfb53efdf2be
Author: tu-usuario <miduga@gmail.com>
Date: Tue Aug 1721 :57:59 2021 +0200
Add editor file to handle monaco editor creation
Recupera un archivo en concreto de otra rama o commit
Al hacer un git cherry-pickestamos aplicando el commit entero. De forma que si en el commit estábamos modificando cinco ficheros, entonces tendremos esos cinco ficheros modificados.
Muchas veces queremos limitar esto y recuperar simplemente un fichero desde otra rama. Para hacer esto, podemos usar el comando git checkout
git checkout old-feature-branch -- package.json
Además de usar ramas también podemos proporcionar elSHAde un commit en específico, de forma que recuperaremos el fichero de ese commit:
git checkout 4ccb6d3 -- package.json
Encuentra el primer commit de un repositorio
En ocasiones, ya sea por curiosidad o por necesidad, a veces quieres recuperar el primer commit de un repositorio. Para ello, puedes usar el comando git logy ponerlo de la siguiente manera:
git log main HEAD~ —oneline | tail -1 | awk ’{ print $1 }’
Ten en cuenta que, dependiendo del repositorio, es posible que la rama principal no seamainy seamasterodevelop.
A veces, sin embargo, esto puede no funcionar ya que el primer commit no se hizo en la rama que ahora es la principal o existen demasiados forks que no puede encontrarse de esta forma.
$ git rev-list --max-parents= 0 HEAD
Descubre el máximo contribuidor de un repositorio
Para conseguir la lista de contribuidores ordenada por número de contribuciones puedes usar el comandoshortlog. Este comando resume la salida de git log, agrupando los commits por autor y mostrando el número de contribuciones de cada autor.
Usando-sconseguimos eliminar la descripción de los commits y muestra sólo el
conteo de contribuciones. Con-nconseguiremos ordenar la salida por número de
contribuciones por autor.
$ git shortlog -s -n
55 tu-usuario
3 Luis Badiali
2 Aarón García Hervás
1 Ismael Ramon
1 Kiko Beats
1 Dani de la Cruz
Si simplemente quieres saber el mayor contribuidor, puedes pasarle la salida al comandoheadpara quedarte con el primero.
$ git shortlog -s -n | head -1
55 tu-usuario
El número de contribuciones a un repositorio no es una medida de valor. En el caso de este ejemplo, puede ayudarte a saber quién es el contribuidor más activo… pero eso no significa que sean las contribuciones más valiosas. ¡Tenlo en cuenta!
Recupera todos los commits para un usuario en específico
A veces puede ser útil saber qué commits han sido hechos por un usuario específico. Con git logpuedes filtrar usando el argumento—authory pasando como valor una expresión regular para buscar el usuario que te interese.
$ git log --author=<pattern>
$ git log --author=^Juan
$ git log --author=Miguel
También puedes usar el correo electrónico como un patrón para filtrar:
# filtra por el usuario con este correo electrónico
$ git log --author=miguelangel.duran@adevinta.com
# filtra todos los usuarios cuyo email termina con adevinta.com
$ git log --author=adevinta.com$
Clonar un repositorio sin descargar todo el histórico
Si intentas clonar un proyecto muy grande de un repositorio remoto es posible que encuentres queel proceso tarda bastante tiempo o que el espacio que ocupa en disco es muy grande.
Por ejemplo, vamos aclonar el proyecto de Reactdesde su repositorio remoto
alojado en GitHub:
$ git clone git@github.com:facebook/react.git
Cloning into 'react '...
remote: Enumerating objects: 193700 , done.
remote: Counting objects: 100 % ( 16 /16), done.
remote: Compressing objects: 100 % ( 15 /15), done.
remote: Total 193700 (delta 3 ), reused 4 (delta 1 ), pack-reused 193684
Receiving objects: 100 % ( 193700 /193700), 163 .38 MiB | 2 .59 MiB/s, done.
Resolving deltas: 100 % ( 136680 /136680), done.
El resultado ha sido decasi un minuto de descarga y un espacio de 163MB de disco duro.
Esto es normal. Tienes que considerar que al clonar el repositorio y por la naturaleza distribuida de Git, estásrecuperandotodosloscommitsyramasquesehanhecho hasta el momento.
Pueden existir proyectos incluso más grandes. Imagina cuánto tiempo puede tomar clonar el núcleo de Linux, por ejemplo.
Para estos casos, en los que clonar el repositorio puede tomar demasiado tiempo y/o espacio, puedes usar el comando—depthparaevitar descargar todo el historial completoy especificar el número de revisiones que necesitas.
Por ejemplo, puedes considerar usar—depth=1si solo te interesa recuperar el último commit grabado en el repositorio.
$ git clone --depth= 1 <repo>
Vamos a probar otra vez con el repositorio de React:
git clone git@github.com:facebook/react.git --depth= 1
Cloning into ‘react’… remote: Enumerating objects: 2412 , done. remote: Counting objects: 100 % ( 2412 /2412), done. remote: Compressing objects: 100 % ( 2099 /2099), done. remote: Total 2412 (delta 460 ), reused 892 (delta 207 ), pack-reused 0 Receiving objects: 100 % ( 2412 /2412), 5 .37 MiB | 5 .07 MiB/s, done. Resolving deltas: 100 % ( 460 /460), done.
Bajar sólo la última revisión (—depth=1):3 segundos y 5.37MB de disco. Bajar todo el historial:59 segundos y 163.38MB de disco.
La diferencia es abismaly puede ser clave para optimizar el proceso de clonado, especialmente en procesos donde la velocidad (como un proceso de integración continua) puede ser importante.
Si por cualquier caso necesitas recuperar todo el histórico de un repositorio clonado con—depthpuedes usar el siguiente comando:
$ git pull --unshallow
Vuelve a la rama previa en la que estaba trabajando
Cuando trabajamos con ramas es normal ir saltando constantemente de una a otra y es complicado recordar de memoria el nombre de cada una de ellas. A veces simplemente quieres volver a la rama anterior.
Por suerte, existe una forma muy sencilla de volver a una rama anterior gracias a una sintaxis especial que soporta git checkouty git switch.
# miramos en qué rama estamos ahora
$ git branch --show-current
main
# cambiamos a la rama EPRSLC-16599-discard-cv
$ git switch EPRSLC-16599-discard-cv
Switched to branch 'EPRSLC-16599-discard-cv'
Your branch is up to date with 'origin/EPRSLC-16599-discard-cv'
# ¡quiero volver a la rama anterior!
$ git switch -
Switched to branch 'main'
Your branch is up to date with 'origin/main '.
# si no tienes compatibilidad con git switch, usa:
$ git checkout -
Switched to branch 'EPRSLC-16599-discard-cv'
Your branch is up to date with 'origin/EPRSLC-16599-discard-cv'
Eso hará que volvamos a justamente la rama anterior a la que estábamos trabajando. Existe otra sintaxis un poco más potente que es@{-N}dondeNes el número de revisiones a saltar, así que para volver justamente a la anterior, deberíamos hacer @{-1}y para volver dos atrás@{-2}.
De esta forma vas a poder cambiar entre ramas de forma muy sencilla sin necesidad de recordar nombres
Veamos un ejemplo práctico. En esta ocasión vamos a usar git checkout, pero recuerda que también puedes usar git switch:
$ git checkout main
Switched to branch 'main'
$ git checkout primera-rama
Switched to a new branch 'primera-rama'
$ git checkout segunda-rama
Switched to a new branch 'segunda-rama'
$ git checkout tercera-rama
Switched to a new branch 'tercer-rama'
$ git checkout @{-2} # volvemos dos atrás
Switched to branch 'primera-rama'
Descargar los ficheros de un repositorio remoto sin tener que clonarlo
Cuando clonamos un proyecto de un repositorio remoto de Git, también estamos recuperando el directorio.gitcon todo el histórico y, por lo tanto, en nuestro sistema se comportará como un proyecto de Git.
A veces sólo nos interesa descargar los ficheros para poder ejecutar o leer algo en nuestro sistema, sin necesidad de usarlo como repositorio de git.
Para ello, puedes usar el truco de clonar el repositorio sin descargar todo el histórico y luego borrar el directorio.git:
$ git clone --depth= 1 git@github.com:tu-usuario/tu-usuario.git
$ cdtu-usuario
$ rm -rf !$/.git
Otra opción es usar la herramientadegitde Rich Harris (creador deRollupySvelte). Para ello tendrás que tener instaladoNode.jsynpminstalados en tu ordenador.
Usandodegitle indicamos la dirección del repositorio remoto (ya sea su forma con SSHoHTTPS) y el directorio en el que queremos que se descargue el proyecto:
$ npx degit git@github.com:tu-usuario/tu-usuario.git tu-usuario
> cloned tu-usuario/tu-usuario#HEAD to tu-usuario
$ cdtu-usuario
$ ls
README.md instructions package package.json src
npxes un alias paraNode Package Manager(npm) que te permite instalar paquetes de forma global en una carpeta temporal y ejecutar el binario al vuelo.
Aprovecha el auto-corrector de Git para que ejecute comandos parecidos
Seguramente te ha pasado alguna vez… Has escrito git comit(con unamen lugar de dos) y Git te ha comentado que lo has escrito mal pero que se parece a un comando válido llamado commit:
$ git comit
git:'comit' is not a git command. See 'git --help '.
The most similarcommand is
commit
Esto es muy útil, ya que te avisa que lo has escrito mal y te permite corregir el error en la posterior ejecución. Sin embargo, existe una forma todavía más interesante de conseguir esto.
Puedes hacer que Git ejecute automáticamente la primera sugerencia de forma que no tengas que volver a escribir nada. Para ello, tienes que configurar la opción help.autocorrecte indicar el tiempo de espera.
# ejecuta el comando sugerido después de 2 segundos
$ git config --global help.autocorrect 20
No, no hay un error en el comando. Para esperar 2 segundos, hay que pasarle 20. Eso es porque el número que representa es la décima parte de un segundo.
Una vez configurado, verás que cuando ejecutes un comando mal, si encuentra una sugerencia, te aparecerá este aviso:
$ git comit
WARNING: You called a Gitcommand named 'comit ', which does not exist.
Continuing in 2 .0 seconds, assuming that you meant 'commit '.
# después de dos segundos...
On branch main
Your branch is up to date with 'origin/main '.
Puedes configurar el tiempo de espera con lo que encuentres más cómodo para ti. Igual 2 segundos es demasiado rápido. Si dejas el suficiente tiempo, ten en cuenta que podrías evitar la ejecución pulsandoCtrl+C.
Ten en cuenta que esto también funcionará con tus alias de comandos. De forma que si tienes un alias que se llamasty escribests, es posible que Git también termine ejecutando el alias correcto.
Domina el formato corto de git status
Ya hemos visto que git statusnos permite ver el estado actual de nuestro directorio de trabajo pero, a veces, es ideal conocer las opciones disponible para filtrar mejor lo que queremos ver o, simplemente, tener una salida más limpia.
Uno muy útil es la opción—shorto, su forma corta,-spara ver la salida lo más limpia posibleya que, por defecto, se usa la opción—longque es mucho más verbosa.
# muestra el estado actual del directorio de trabajo
$ git status -s
M manuscript/changelog.md
M manuscript/ramas.md
M manuscript/ssh.md
M manuscript/trabajando-con-git-de-forma-local.md
Como ves, el formato de salida es mucho más escueto. A la izquierda hay un código de dos letras que indican el estado del fichero. Normalmente, el primer espacio es el estado actual del índice y el segundo espacio es el estado actual del directorio de trabajo.
Aunque la salida es más escueta, puede ser difícil de leer si no tienes claro que significa cada letra. Por eso te puede ser útil esta leyenda:
= no modificado(espacio vacío)
M = modificado
A = añadido
D = borrado
R = renombrado
C = copiado
U = actualizado pero no fusionado
? = sin rastrear
! = ignorado
Con esta leyenda ahora verás que en el git status -sque hemos hecho antes tenemos cuatro archivos modificados que se encuentran actualmente en el directorio de trabajo.
Si añadimos al área de preparación el ficherochangelog.md, veremos un sutil pero importante cambio en el git status -s:
$ git add manuscript/changelog.md
$ git status -s
M manuscript/changelog.md
M manuscript/ramas.md
M manuscript/ssh.md
M manuscript/trabajando-con-git-de-forma-local.md
Si te fijas bien, ahora el archivochangelog.mdtiene la banderita deM(modificado) en la primera columna (la columna del índice) y en la segunda columna no tiene ningún valor. Esto se diferencia con el resto de ficheros que siguen modificados en el área de trabajo (segunda columna).
Prueba diferentes combinaciones de comandos y ejecuta git status -spara ver qué letras o símbolos te aparecen en las columnas. Revisa la leyenda que te he proporcionado antes e intenta comprender qué pasa con ficheros renombrados, ficheros nuevos, ficheros eliminados…
--porcelain, la opción para que nuestros scripts usen comandos de Git
Muchos comandos de Git ofrecen una opción llamada—porcelain. Esta opción nos permite que los comandos de Git sean más fáciles de leer y analizar por programas y scripts, ya que su salida es más limpia y estable entre versiones y configuraciones.
La palabraPorcelainviene del material con el mismo nombre: porcelana. La porcelana es el material del que están hechos los baños, normalmente, y tienen un acabado mucho más ideal para el usuario final. Por debajo, en realidad, están las tuberías. Esa sería la analogía por la que se usa ese nombre.
Casi todos los comandos de Git se consideran que sonde porcelana. Básicamente, todos los que hemos visto en el curso. Pero… ¿sabías que existen otros comandos internos pero públicos como git count-objectsque son los consideradosplumbing?.
Usando git statuscomo ejemplo, podríamos ejecutar lo siguiente:
# una salida similar a `git status -s` pero
$ git status --porcelain
M manuscript/changelog.md
M manuscript/ramas.md
M manuscript/ssh.md
M manuscript/trabajando-con-git-de-forma-local.md
M manuscript/trucos.md
Como puedes ver, en este caso, git status —porcelainda una salida similar a git status -spero, si lo ejecutas en tu terminal, verás que en el caso de usar—porcelain no tiene ningún tipo de color.
Como la salida es estable y previsible, podríamos crear un pequeño script que nos dijese el número de cambios que tenemos en nuestro directorio de trabajo, de la siguiente forma:
# con wc -l contamos el número de líneas
$ git status --porcelain | wc -l
5
Hay otros comandos que también proporcionan la opción de—porcelaincomo git commit. En este caso la salida te indicará el git status -sresultante que tendrías después de ejecutar la operaciónperonorealizael commit. Es una forma de asegurarte que puedes grabar los cambios sin problemas y revisar cómo quedaría el estado de tu directorio de trabajo antes de hacerlo.
Configuraciones a tener en cuenta
¡Importante! Antes de activar estas configuraciones, asegúrate de que entiendes lo que hacen y cómo afectan a tu flujo de trabajo.
Elimina automáticamente referencias remotas (por ejemplo, ramas que tienes en local y ya no existen) que ya no existen en el servidor cuando ejecutas git fetch:
git config --global fetch.prunetrue
Simplifica la salida de git statusde forma que solo muestre los ficheros que han cambiado:
git config --global status.shorttrue
# antes
$ git status
On branch main Your branch is ahead of ‘origin/main’by 13 commits. (use “git push” to publish yourlocal commits)
Changes not staged for commit: (use “git add
no changes added to commit (use "git add "and/or "git commit -a ")
# después
$ git status
M manuscript/ramas.md
M manuscript/trucos.md
Añade un color extra para diferenciar las líneas que sólo han sido movidas. Por defecto al hacer un git difftenemos dos colores que nos indican las líneas añadidas o eliminadas. Este color extra nos indica las líneas que han sido movidas:
git config --global diff.colorMoved zebra
Si quieres sólo hacer push de tu rama actual y simplificar los parámetros que tienes que pasar al hacer git push, puedes usar:
git config –global push.default simple