Использование diff и patch


== Кратко ==

diff - утилита, показывающая разницу между двумя файлами. patch - утилита позволяющая 
применить изменения (патч) к файлу более старой версии, чтобы получить из него файл 
текущей версии. Конечно, это очень примитивное и краткое описание - на самом деле обе 
утилиты обладают гораздо большими возможностями. Утилиты относятся к классам "must 
have" и "must understand" для всех, кто программирует или разбирается в кодах. Причем 
не обязательно в Linux - в других системах есть порты или аналоги. Вообще в среде 
разработчиков Linux/Unix принято не передавать друг другу огромные исправления и 
обновления программ - а присылать только патчи.

Для примера возьмем два некоторых файла с кодом - oldfile1 и newfile, где newfile - 
является улучшенной версией oldfile.

// oldfile

$file = "somefile.txt";

if ($f = fopen($file,'r')) {
   while(!eof($f)) {
       $s = fgets($f);
       print "$s\n";
   }
}

// newfile

$file = "/file.txt";

if ($f = fopen($file,'r')) {
   while(!feof($f)) {
       $s = fgets($f);
       echo "$s\n";
   }
   fclose($f);
}


== diff ==

diff может создавать два различных вида патчей - обычный и контекстный. Контекстный - 
более наглядный, более большой по размеру, и в большинстве случаев лучше, чем обычный, 
поскольку привязан к контексту (не зря ведь так называется!). В GNU есть еще 
унифицированный контекстный патч - более компактный, но чуть менее переносимый.

$ diff oldfile newfile > patch
или
$ diff -c oldfile newfile > contextpatch
или
$ diff -u oldfile newfile > upatch

Обычный патчи patch выглядит так:

1c1
< // oldfile
---
> // newfile
3c3
< $file = "somefile.txt";
---
> $file = "/file.txt";
6c6
<    while(!eof($f)) {
---
>    while(!feof($f)) {
8c8
<        print "$s\n";
---
>        echo "$s\n";
9a10
>    fclose($f);

А контекстный: 

*** oldfile	2011-11-02 12:13:10.192499384 +0400
--- newfile	2011-11-02 12:13:04.484418321 +0400
***************
*** 1,11 ****
! // oldfile
  
! $file = "somefile.txt";
  
  if ($f = fopen($file,'r')) {
!    while(!eof($f)) {
         $s = fgets($f);
!        print "$s\n";
     }
  }
  
--- 1,12 ----
! // newfile
  
! $file = "/file.txt";
  
  if ($f = fopen($file,'r')) {
!    while(!feof($f)) {
         $s = fgets($f);
!        echo "$s\n";
     }
+    fclose($f);
  }


== patch ==

Чтобы применить полученные патчи достаточно воспользоваться командой

$ patch oldfile -i patchfile -o file

Где patchfile - это имя патч-файла (у нас patch, upatch или contextpath). После 
выполнения данной команды file и newfile - должны быть полностью эквивалентны.


== Патч проекта ==

Патч можно получить не только для отдельного файла, но и для целого дерева каталогов

$ diff ./olddir/ ./newdir/ > bigpatch
или
$ dirr -r ./olddir/ ./newdir/ > rec_bigpatch

Полученные изменения применяются командой patch с ключом -p0

$ patch -p0 < bigpatch
или
$ patch -p0 -i bigpatch


Если внутри нового проекта есть файлы, которых нет в старом, т.е. изменилось не только 
содержимое файлов но и их количество, то нужно добавлять специальные ключи и для diff и 
для patch:

$ diff -r -N ./olddir/ ./newdir/ > diff_patch
$ patch -p0 -E < diff_patch


== Откат патча ==

Патч можно откатить назад, если он применился, но не помог решить проблему.

$ patch -p0 -R -i bigpatch


И напоследок. Перед тем, как применять патч, желательно убедиться, что он подходит и 
отрабатывает нормально:

$ patch -p0 --dry-run < patch

Заодно можно сделать бекап, указав для patch ключ -b.

Если всё же ошибка произошла, то patch создаст файлы .orig, .rej - по которым можно 
восстановить исходники и понять причину сбоя. Это, однако, уже совсем не быстро - 
гораздо проще забекапить проект до применения патча. Читайте маны - там еще много 
всяких возможностей.