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:**

    :::sh
    #!/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.
