Author: Megver83 Category: GNU/Linux Date: 2017-07-08 10:02 Image: 2018/04/git-diff.png Lang: es Slug: crear-parches-con-git Tags: git, diff, patch Title: Crear parches con Git Muchas veces pasa, especialmente cuando se trabaja en desarrollo de código, que modificamos software (por ej. algo tan simple como un script o varios archivos del código fuente de un programa) y queremos compartir esa modificación o solamente guardarla para tener esa "diferenciación" en la forma de un archivo de texto plano para más tarde aplicarla cuando el programa en el que nos basamos se actualice. Pues esa es la función que cumplen los parches. En Wikipedia dice lo siguiente sobre los parches: >En informática, un parche consta de cambios que se aplican a un programa, >para corregir errores, agregarle funcionalidad, actualizarlo, etc. Pues hay varios métodos para crear parches, los más usados son `diff` y `git diff`. En este tutorial se enseñará el uso de `git diff`, por ser más completo. ## Primer paso: crear los directorios Este es un paso muy importante, que la mayoría de los tutoriales omiten, más adelante se explicará por qué. Si se fijan bien, en Git, cada vez que se hace un commit se crea un parche, y cuando muestra un archivo modificado aparece el comando `diff --git a/ruta/al/archivo/modificado.sh b/ruta/al/archivo/modificado.sh` donde `modificado.sh` es, en este caso, un script que fue modificado (.❛ ᴗ ❛.) Entonces, para modificar nuestro script, texto o código fuente primero hay que crear el directorio `a` y `b` :::bash mkdir a b En el directorio `a` pondremos el o los archivos sin modificar, y en el directorio `b` el modificado. ## Segundo paso: crea el parche Ejecuta: ```bash git diff --no-prefix --no-index --no-renames --binary a b > parche.patch ``` + --no-prefix: No mostrar ningún prefijo de origen o destino. + --no-index: Se usa para comparar las dos rutas dadas en el sistema de archivos. + --no-remanes: Desactiva la detección de cambio de nombre de un archivo. + --binary: Crea un diff binario que puede ser aplicado con git apply. Ya tienen listo su parche. Sencillo ¿no?. Pues bien, ahora es la hora de probarlo. ## Tercer paso: aplicar el parche Una vez tenemos nuestro parche como archivo `.diff` o `.patch` (aunque en general se puede usar cualquier extensión), lo aplicaremos con `patch` o `git apply` dependiendo del caso. 1. Solo texto plano: Si su parche únicamente modifica texto plano, como scripts, archivos de código fuente en C/C++, Python, Pascal, Javascript, PHP, HMTL, etc. entonces usaremos este comando: :::bash patch -p1 -i /ruta/del/parche.diff 2. Con archivos binarios: Es decir, cosas como programas ejecutables ya compilados, imágenes PNG, JPEG, Gif, etc. que no sean texto plano. En general podrás identificar cuando se parcha un binario cuando en parche dice algo como "GIT binary patch". En este caso aplicaremos el parche de la siguiente manera: :::bash git apply -v /ruta/del/parche.diff ## El problema con diff y no hacer directorios a y b Ahora, regresando a lo que decía anteriormente sobre por qué esto es importante, se debe a que en muchas guías, wikis, etc. he encontrado que en vez de crear estos directorios, crean un archivo (por ej.) `script.sh` y `script.sh.new` y luego en base a eso ejecutan `diff -u scripts.sh script.sh.new`. Resulta que hay dos problemas en esto: + Al hacer eso, en el parche en vez de decir algo como `diff --opciones a/ruta/al/archivo/modificado.sh b/ruta/al/archivo/modificado.sh` dice (en este caso) `diff --opciones script.sh script.sh.new`, pero resulta que tu quieres parchar `b/script.sh`, no `script.sh.new` (porque dentro de `b/` están los archivos modificados). + Si se usa `diff`, cuando se detecte un archivo que no existía originalmente en `a/` (seguramente porque creaste uno en `b/`), no lo va a agregar en el parche, y si eliminaste uno dentro del árbol original, tampoco quitará dicho archivo. + `diff` no puede hacer parches de binarios. Para que se entienda mejor, voy a ejemplificar cada caso con dos ejemplos. En el primero, crearé los archivos que puse de ejemplo (valga la redundancia) y usaré diff: **script.sh:** ```bash #!/bin/bash echo "Hello world" ``` **script.sh.new:** ```bash #!/bin/sh echo "Hello world" echo "This is a patched file :D" ``` Ahora haremos lo que la mayoría de tutoriales de internet te dicen que hagas: :::bash diff -u script.sh script.sh.new Y me queda así: ```diff --- script.sh 2018-03-16 15:52:49.887087539 -0300 +++ script.sh.new 2018-03-16 15:53:02.490420209 -0300 @@ -1,2 +1,3 @@ -#!/bin/bash +#!/bin/sh echo "Hello world" +echo "This is a patched file :D" ``` Todo aparentemente bien, pero ahora apliquemos dicho parche ```bash $ diff -u script.sh script.sh.new | patch -p1 -i /dev/stdin ``` ```diff can't find file to patch at input line 3 Perhaps you used the wrong -p or --strip option? The text leading up to this was: -------------------------- |--- script.sh 2018-03-16 15:52:49.887087539 -0300 |+++ script.sh.new 2018-03-16 15:53:02.490420209 -0300 -------------------------- File to patch: ``` Falla siendo que estoy en el mismo directorio que `script.sh{.new}`, de modo que esto se corrige usando el hack de crear los directorios `a/` y `b/`. Sin embargo, esto no resulve el punto 2 y 3. Vamos a por ello. Supongamos que tenemos esto dentro de `a/` y `b/`: a: script.sh b: archivo_binario.bin script.sh Bien, ahora hagamos el parche con diff: ```bash $ diff -ur a b ``` ```diff Sólo en b: archivo_binario.bin diff -ur a/script.sh b/script.sh --- a/script.sh 2018-03-16 15:37:27.513802777 -0300 +++ b/script.sh 2018-03-16 15:41:17.717123987 -0300 @@ -1,2 +1,3 @@ -#!/bin/bash +#!/bin/sh echo "Hello world" +echo "This is a patched file :D" ``` Y se cumple lo que decía en el punto 2, no te pone el archivo nuevo, te dice "Sólo en b" o si hay un fichero que está en `a/` pero no en `b/` (es decir, seguro que lo eliminaste de tu fork), te saldrá el mensaje "Sólo en a" en vez de eliminarlo o crearlo. Si aplicamos este parche solo afectará a los archivos de texto plano, y aunque hiciera bien su trabajo y creara este nuevo archivo no funcionaría porque `archivo_binario.bin` es un binario, el cual no está soportado por `diff` pero sí por `git` lo cual nos lleva al tercer punto. Mira lo que pasa si uso `git` en vez de `diff`: ```bash $ git diff --no-prefix --no-index --no-renames --binary a b ``` ```diff diff --git b/archivo_binario.bin b/archivo_binario.bin new file mode 100644 index 0000000000000000000000000000000000000000..1ce3c1c596d7a7f400b0cc89bda5a41eed2780c5 GIT binary patch literal 73 pcmd-HXHZUIU{c}EWl|AfLZWk+R0P|Ad@#)bSHb~R0-{lr003gr3L5|b literal 0 HcmV?d00001 diff --git a/script.sh b/script.sh index da049c4..3d351f5 100644 --- a/script.sh +++ b/script.sh @@ -1,2 +1,3 @@ -#!/bin/bash +#!/bin/sh echo "Hello world" +echo "This is a patched file :D" ``` Ahora sí me consideró el archivo binario inexistente en `a/` pero tangible en `b/`. Noten que en este caso particular, como ya expliqué anteriormente, al tratar con archivos binarios que solo git soporta (vean el mensaje "GIT binary patch") se debe usar `git apply` obligatoriamente. Pero les recomiendo usarlo solo cuando sea obligatorio, no siempre (en general no se usan muchos binarios en el software que es 100% libre, a no ser que se traten de casos como firmware para el kernel o librerías precompiladas, pero el software libre blobbeado suele tener binarios privativos en su código, aunque el hecho de que sea binario no significa que sea necesariamente privativo). Si tienes dudas al respecto sobre el uso de `diff` y `git diff` o `patch` y `git apply` recuerda que puedes dejarlas en los comentarios, así como también leer sus **manpages** y consultar sus páginas web para más información.