Данный раздел посвящён работе с проектами, в которых мы не являемся непосредственным разработчиком или не имеем возможности пользоваться общей системой контроля версий. В таких ситуациях на помощь в разработке приходят патчи.
Объект исследования
Представим ситуацию, что мы - не разработчик программного продукта, но работаем с его исходниками, хранящимися где-то в открытом доступе. Нередкой бывает ситуация, когда проект не заработает “из коробки”. Причин у этого может быть множество: проект изначально писался под какую-то определённую систему, не совпадающую с нашей; в нём присутствуют ошибки, связанные с обновлением каких-то зависимых элементов. Или вариант позитивнее: мы хотим что-то добавить в проект, например, локализацию под нашу площадку, однако разработчики не принимают наши изменения в основной код.
В обоих случаях мы можем самостоятельно локально изменить исходники для наших нужд, но тогда непременно столкнёмся с ситуацией, когда у команды разработки продукта вышла новая версия без наших улучшений, и мы будем вынуждены заново всё настраивать.
Для упрощения этого бесконечного переписывания кода можно использовать механизм патчей. Патч(patch - “заплатка, накладка”) - это сообщение специального формата, в котором описывается набор изменений объектов для их преобразования.
При использовании патчей работа со сторонними исходниками становится в разы легче.
При создании первой версии изменений мы исправляем исходники, как нам необходимо; оформляем эти исправления в виде патчей; сохраняем этот патч-сет, а исправленную версию используем сами или выкладываем в открытый доступ в зависимости от нужд.
При обновлении исходников мы просто применяем наши патчи к новой версии (при этом часть может примениться сразу, а часть - отвалиться из-за изменений); адаптируем оставшиеся патчи для их применимости; сохраняем новый патч-сет и снова просто пользуемся нашими надстройками.
Работа с патчами, вообще говоря, не базовая грамотность, однако на практике встречается очень часто. Кроме того, именно патчирование было основным способом синхронизации при совместной разработке до массового использования систем контроля версий. А где-то остаётся вообще единственным вариантом такой разработки.
Технология патчирования
Разберёмся, на чём вообще основана технология создания патчей. Для начала нужно научиться находить различия в программах.
Рассмотрим пример, в котором реализована функция открытия файла с помощью системного вызова open, но с параметром прав доступа из функции fopen:
ffopen1.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
/* open()->fopen() wrapper */
static FILE *ffopen(const char *pathname, const char *mode, int flags)
{
int m;
switch(tolower(mode[0])+(mode[1]=='+')) {
case 'b': /* "a+" */
case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break;
case 'r': m = O_RDONLY; break;
case 's': m = O_RDWR; break; /* "r+" */
case 'x': /* "w+" */
case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break;
default: m = O_RDONLY; break;
}
int fd = open(pathname, flags, m);
return fd<0? NULL: fdopen(fd, mode);
}
int main(int argc, char *argv[]) {
FILE *fp;
fp = ffopen(argv[1], "r", O_NOFOLLOW);
if(fp == NULL) {
perror(argv[1]);
return 1;
}
return 0;
}
Допустим, мы хотим изменить реализацию параметра x, который отвечает за открытие файла на чтение и запись одновременно
ffopen_new.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
/* open()->fopen() wrapper */
static FILE *ffopen(const char *pathname, const char *mode, int flags)
{
int m;
switch(tolower(mode[0])+(mode[1]=='+')) {
case 'b': /* "a+" */
case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break;
case 'r': m = O_RDONLY; break;
case 's': m = O_RDWR; break; /* "r+" */
case 'x': m = O_RDWR | O_CREAT | O_TRUNC; break; /* w+ */ /*Наше изменение*/
case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break;
default: m = O_RDONLY; break;
}
int fd = open(pathname, flags, m);
return fd<0? NULL: fdopen(fd, mode);
}
int main(int argc, char *argv[]) {
FILE *fp;
fp = ffopen(argv[1], "r", O_NOFOLLOW);
if(fp == NULL) {
perror(argv[1]);
return 1;
}
return 0;
}
Для того, чтобы не искать глазами, где и как изменился файл по сравнению с исходником, воспользуемся утилитой diff
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> ls
ffopen.c ffopen_new.c
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> diff ffopen.c ffopen_new.c
17c17
< case 'x': /* "w+" */
---
> case 'x': m = O_RDWR | O_CREAT | O_TRUNC; break; /* w+ */
Diff явно описал, что необходимо сделать с первым файлом, чтобы получить из него второй: удалить 17ю строку, которая выглядит, как показано на экране, а после на 17 строку записать новую, также указанную.
Некоторые флаги diffпозволяют получить эту же информацию в других форматах:
-e - в виде скрипта для текстового однострочного редактора ed:
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> diff -e ffopen.c ffopen_new.c
17c
case 'x': m = O_RDWR | O_CREAT | O_TRUNC; break; /* w+ */
.
-c - для указания изменения вместе с контекстом, где оно проведено (ДО и ПОСЛЕ):
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> diff -c ffopen.c ffopen_new.c
*** ffopen.c 2024-11-12 16:32:10.603958261 +0300
--- ffopen_new.c 2024-11-12 16:32:23.247503694 +0300
***************
*** 14,20 ****
case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break;
case 'r': m = O_RDONLY; break;
case 's': m = O_RDWR; break; /* "r+" */
! case 'x': /* "w+" */
case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break;
default: m = O_RDONLY; break;
}
--- 14,20 ----
case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break;
case 'r': m = O_RDONLY; break;
case 's': m = O_RDWR; break; /* "r+" */
! case 'x': m = O_RDWR | O_CREAT | O_TRUNC; break; /* w+ */
case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break;
default: m = O_RDONLY; break;
}
-u - для единого контекстного блока с указанными изменениями
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> diff -u ffopen.c ffopen_new.c
--- ffopen.c 2024-11-12 16:32:10.603958261 +0300
+++ ffopen_new.c 2024-11-12 16:32:23.247503694 +0300
@@ -14,7 +14,7 @@ # Наш контекст из 7 строк от 14ой меняем на 7 строк от 14 вот так
case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break;
case 'r': m = O_RDONLY; break;
case 's': m = O_RDWR; break; /* "r+" */
- case 'x': /* "w+" */ # Было
+ case 'x': m = O_RDWR | O_CREAT | O_TRUNC; break; /* w+ */ # Стало
case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break;
default: m = O_RDONLY; break;
}
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection>
Добавим ещё одно изменение в программу.
ffopen_new2.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
/* open()->fopen() wrapper */
static FILE *ffopen(const char *pathname, const char *mode, int flags)
{
int m;
switch(tolower(mode[0])+(mode[1]=='+')) {
case 'b': /* "a+" */
case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break;
case 'r': m = O_RDONLY; break;
case 's': m = O_RDWR; break; /* "r+" */
case 'x': m = O_RDWR | O_CREAT | O_TRUNC; break; /* w+ */
case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break;
default: m = O_RDONLY; break;
}
int fd = open(pathname, flags, m);
return fd<0? NULL: fdopen(fd, mode);
}
int main(int argc, char *argv[]) {
FILE *fp;
fp = ffopen(argv[1], "r", O_NOFOLLOW);
if(fp == NULL) {
perror(argv[1]);
return 1;
}
else
fclose(fp);
return 0;
}
Трудно найти его зрительно, правда? Снова пользуемся утилитой diff:
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> diff -u ffopen.c ffopen_new2.c
--- ffopen.c 2024-11-12 16:32:10.603958261 +0300
+++ ffopen_new2.c 2024-11-12 16:44:50.173769006 +0300
@@ -14,7 +14,7 @@
case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break;
case 'r': m = O_RDONLY; break;
case 's': m = O_RDWR; break; /* "r+" */
- case 'x': /* "w+" */
+ case 'x': m = O_RDWR | O_CREAT | O_TRUNC; break; /* w+ */
case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break;
default: m = O_RDONLY; break;
}
@@ -31,6 +31,7 @@
perror(argv[1]);
return 1;
}
-
+ else
+ fclose(fp);
return 0;
}
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection>
Diff собрал для нас изменения и для каждого написал свой блок контекста и изменений
На самом деле, формат вывода diff -u это и есть формат оформления патчей. Поэтому просто сохранив его в виде файла, можно делиться патчем и использовать его для преобразования неизменённых исходников
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> diff -u ffopen.c ffopen_new.c > p0.patch
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> ls
a.out ffopen.c ffopen_new2.c ffopen_new.c p0.patch
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> cat p0.patch
--- ffopen.c 2024-11-12 16:32:10.603958261 +0300
+++ ffopen_new.c 2024-11-12 16:32:23.247503694 +0300
@@ -14,7 +14,7 @@
case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break;
case 'r': m = O_RDONLY; break;
case 's': m = O_RDWR; break; /* "r+" */
- case 'x': /* "w+" */
+ case 'x': m = O_RDWR | O_CREAT | O_TRUNC; break; /* w+ */
case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break;
default: m = O_RDONLY; break;
}
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection>
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> diff -u ffopen_new.c ffopen_new2.c > p1.patch
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> ls
a.out ffopen.c ffopen_new2.c ffopen_new.c p0.patch p1.patch
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> cat p1.patch
--- ffopen_new.c 2024-11-12 16:32:23.247503694 +0300
+++ ffopen_new2.c 2024-11-12 16:44:50.173769006 +0300
@@ -31,6 +31,7 @@
perror(argv[1]);
return 1;
}
-
+ else
+ fclose(fp);
return 0;
}
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection>
Утилита patch как раз применяет изменения к файлу
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> cp ffopen.c ffopen2.c
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> patch ffopen2.c < p0.patch
patching file ffopen2.c
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> diff ffopen2.c ffopen_new.c
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> patch ffopen2.c < p1.patch
patching file ffopen2.c
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> diff ffopen2.c ffopen_new2.c
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection>
Как мы видим, после применения патчей ffopen2.c стал неотличим от ffopen_new2.c
Наши патчи работают с разными контекстами, поэтому порядок их применения не так важен. Но нередкой бывает работа с патчами, основанными на одном контексте, для которых порядок применения будет играть роль.
Неточный контекст
Так как мы собираемся накладывать патчи на обновляющуюся версию исходников, надо разобрать случай, когда наш контекст изменяется. Возьмём те же самые патчи и применим их к слегка изменённой программе. При этом одно изменение будет попадать в контекст, а другое - нет.
ffopen3.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
/* open()->fopen() wrapper */
static FILE *ffopen(const char *pathname, const char *mode, int flags)
{
int m;
switch(tolower(mode[0])+(mode[1]=='+')) {
case 'b': /* "a+" */
case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break;
case 'r': m = O_RDONLY; break;
// TODO "b" modifier
case 's': m = O_RDWR; break; /* "r+" */
case 'x': /* "w+" */
case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break;
default: m = O_RDONLY; break;
}
int fd = open(pathname, flags, m);
return fd<0? NULL: fdopen(fd, mode);
}
/* just for test purpose */
int main(int argc, char *argv[]) {
FILE *fp;
fp = ffopen(argv[1], "r", O_NOFOLLOW);
if(fp == NULL) {
perror(argv[1]);
return 1;
}
return 0;
}
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> cat p*.patch > pall.patch
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> cat pall.patch
--- ffopen.c 2024-11-12 16:32:10.603958261 +0300
+++ ffopen_new2.c 2024-11-12 16:44:50.173769006 +0300
@@ -14,7 +14,7 @@
case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break;
case 'r': m = O_RDONLY; break;
case 's': m = O_RDWR; break; /* "r+" */
- case 'x': /* "w+" */
+ case 'x': m = O_RDWR | O_CREAT | O_TRUNC; break; /* w+ */
case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break;
default: m = O_RDONLY; break;
}
@@ -31,6 +31,7 @@
perror(argv[1]);
return 1;
}
-
+ else
+ fclose(fp);
return 0;
}
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection>
Запустим применение патчей с флагом --verbose, чтобы получить дополнительную информацию по применению
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> ls
a.out ffopen3.c ffopen_new2.c p0.patch pall.patch
ffopen2.c ffopen.c ffopen_new.c p1.patch
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> patch --verbose ffopen3.c < pall.patch
Hmm... Looks like a unified diff to me...
The text leading up to this was:
--------------------------
|--- ffopen.c 2024-11-12 16:32:10.603958261 +0300
|+++ ffopen_new2.c 2024-11-12 16:44:50.173769006 +0300
--------------------------
patching file ffopen3.c
Using Plan A...
Hunk #1 succeeded at 15 with fuzz 2 (offset 1 line).
Hunk #2 succeeded at 33 with fuzz 2 (offset 2 lines).
done
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection>
Кажется, когда программа говорит "Hmm...", это не очень хорошо…
При этом всё применилось и корректно работает
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> cc -Wall ffopen3.c
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> ./a.out /usr/bin/bzcat
/usr/bin/bzcat: Too many levels of symbolic links
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection>
Как мы видим из информации патча, оба изменения применились с неточностью какого-то уровня (у нас это уровень 2). В зависимости от того, как сильно обновление изменило контекст, для неточности рассчитывается этот самый уровень, и с какого-то уровня патч не применяется, как, например, в следующем примере
ffopen4.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
/* open()->fopen() wrapper */
static FILE *ffopen(const char *pathname, const char *mode, int flags)
{
int m;
switch(tolower(mode[0])+(mode[1]=='+')) {
case 'b': /* "a+" */
case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break;
case 'r': m = O_RDONLY; break;
case 's': m = O_RDWR; break; /* "r+" */
// TODO "b" modifier
case 'x': /* "w+" */
case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break;
default: m = O_RDONLY; break;
}
int fd = open(pathname, flags, m);
return fd<0? NULL: fdopen(fd, mode);
}
/* just for test purpose */
int main(int argc, char *argv[]) {
FILE *fp;
fp = ffopen(argv[1], "r", O_NOFOLLOW);
if(fp == NULL) {
perror(argv[1]);
return 1;
}
return 0;
}
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> patch --verbose ffopen4.c < pall.patch
Hmm... Looks like a unified diff to me...
The text leading up to this was:
--------------------------
|--- ffopen.c 2024-11-12 16:32:10.603958261 +0300
|+++ ffopen_new2.c 2024-11-12 16:44:50.173769006 +0300
--------------------------
patching file ffopen4.c
Using Plan A...
Hunk #1 FAILED at 14.
Hunk #2 succeeded at 33 with fuzz 2 (offset 2 lines).
1 out of 2 hunks FAILED -- saving rejects to file ffopen4.c.rej
done
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection>
При этом создаётся .rej-файл для описания неприменённого патча, который мы будем изменять руками
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection> cat ffopen4.c.rej
--- ffopen.c 2024-11-12 16:32:10.603958261 +0300
+++ ffopen_new2.c 2024-11-12 16:44:50.173769006 +0300
@@ -14,7 +14,7 @@
case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break;
case 'r': m = O_RDONLY; break;
case 's': m = O_RDWR; break; /* "r+" */
- case 'x': /* "w+" */
+ case 'x': m = O_RDWR | O_CREAT | O_TRUNC; break; /* w+ */
case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break;
default: m = O_RDONLY; break;
}
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection>
patchutils
Для удобной работы с патчами cуществует отдельный набор утилит patchutils. Рассмотрим его работу на примере работы с календарём
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> LC_TIME=ru_RU.UTF-8 cal -y > calend
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> cat calend
2024
Январь Февраль Март
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 7 1 2 3 4 1 2 3
8 9 10 11 12 13 14 5 6 7 8 9 10 11 4 5 6 7 8 9 10
15 16 17 18 19 20 21 12 13 14 15 16 17 18 11 12 13 14 15 16 17
22 23 24 25 26 27 28 19 20 21 22 23 24 25 18 19 20 21 22 23 24
29 30 31 26 27 28 29 25 26 27 28 29 30 31
Апрель Май Июнь
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 7 1 2 3 4 5 1 2
8 9 10 11 12 13 14 6 7 8 9 10 11 12 3 4 5 6 7 8 9
15 16 17 18 19 20 21 13 14 15 16 17 18 19 10 11 12 13 14 15 16
22 23 24 25 26 27 28 20 21 22 23 24 25 26 17 18 19 20 21 22 23
29 30 27 28 29 30 31 24 25 26 27 28 29 30
Июль Август Сентябрь
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 7 1 2 3 4 1
8 9 10 11 12 13 14 5 6 7 8 9 10 11 2 3 4 5 6 7 8
15 16 17 18 19 20 21 12 13 14 15 16 17 18 9 10 11 12 13 14 15
22 23 24 25 26 27 28 19 20 21 22 23 24 25 16 17 18 19 20 21 22
29 30 31 26 27 28 29 30 31 23 24 25 26 27 28 29
30
Октябрь Ноябрь Декабрь
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 1 2 3 1
7 8 9 10 11 12 13 4 5 6 7 8 9 10 2 3 4 5 6 7 8
14 15 16 17 18 19 20 11 12 13 14 15 16 17 9 10 11 12 13 14 15
21 22 23 24 25 26 27 18 19 20 21 22 23 24 16 17 18 19 20 21 22
28 29 30 31 25 26 27 28 29 30 23 24 25 26 27 28 29
30 31
Сделаем несколько патчей для этого файла
Добавим точки между 21 и 13 у двух месяцев
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> sed -i.old1 's/21 13/21...13/' calend
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> diff -u calend.old1 calend > p1.patch
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> cat p1.patch
--- calend.old1 2024-11-12 17:09:32.096205400 +0300
+++ calend 2024-11-12 17:09:49.309601942 +0300
@@ -12,7 +12,7 @@
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 7 1 2 3 4 5 1 2
8 9 10 11 12 13 14 6 7 8 9 10 11 12 3 4 5 6 7 8 9
-15 16 17 18 19 20 21 13 14 15 16 17 18 19 10 11 12 13 14 15 16
+15 16 17 18 19 20 21...13 14 15 16 17 18 19 10 11 12 13 14 15 16
22 23 24 25 26 27 28 20 21 22 23 24 25 26 17 18 19 20 21 22 23
29 30 27 28 29 30 31 24 25 26 27 28 29 30
Изменим названия осенних месяцев
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> sed -i.old2 's/ябрь/ёр../g' calend
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> diff -u calend.old2 calend > p2.patch
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> cat p2.patch
--- calend.old2 2024-11-12 17:09:49.309601942 +0300
+++ calend 2024-11-12 17:10:11.596350457 +0300
@@ -16,7 +16,7 @@
22 23 24 25 26 27 28 20 21 22 23 24 25 26 17 18 19 20 21 22 23
29 30 27 28 29 30 31 24 25 26 27 28 29 30
- Июль Август Сентябрь
+ Июль Август Сентёр..
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 7 1 2 3 4 1
8 9 10 11 12 13 14 5 6 7 8 9 10 11 2 3 4 5 6 7 8
@@ -24,7 +24,7 @@
22 23 24 25 26 27 28 19 20 21 22 23 24 25 16 17 18 19 20 21 22
29 30 31 26 27 28 29 30 31 23 24 25 26 27 28 29
30
- Октябрь Ноябрь Декабрь
+ Октёр.. Ноёр.. Декабрь
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 1 2 3 1
7 8 9 10 11 12 13 4 5 6 7 8 9 10 2 3 4 5 6 7 8
И третьим патчем уберём все добавленные точки
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> sed -i.old3 's/[.]/ /g' calend
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> diff -u calend.old3 calend > p3.patch
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> cat p3.patch
--- calend.old3 2024-11-12 17:10:11.596350457 +0300
+++ calend 2024-11-12 17:11:01.573216826 +0300
@@ -12,11 +12,11 @@
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 7 1 2 3 4 5 1 2
8 9 10 11 12 13 14 6 7 8 9 10 11 12 3 4 5 6 7 8 9
-15 16 17 18 19 20 21...13 14 15 16 17 18 19 10 11 12 13 14 15 16
+15 16 17 18 19 20 21 13 14 15 16 17 18 19 10 11 12 13 14 15 16
22 23 24 25 26 27 28 20 21 22 23 24 25 26 17 18 19 20 21 22 23
29 30 27 28 29 30 31 24 25 26 27 28 29 30
- Июль Август Сентёр..
+ Июль Август Сентёр
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 7 1 2 3 4 1
8 9 10 11 12 13 14 5 6 7 8 9 10 11 2 3 4 5 6 7 8
@@ -24,7 +24,7 @@
22 23 24 25 26 27 28 19 20 21 22 23 24 25 16 17 18 19 20 21 22
29 30 31 26 27 28 29 30 31 23 24 25 26 27 28 29
30
- Октёр.. Ноёр.. Декабрь
+ Октёр Ноёр Декабрь
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 1 2 3 1
7 8 9 10 11 12 13 4 5 6 7 8 9 10 2 3 4 5 6 7 8
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend>
Отметим особенность работы утилиты sed, с помощью которой мы задавали изменения. С помощью флага -i можно задать постфикс для имени файла, в котором будет сохранена версия файла до изменения. Зачем нам это? Затем, что patch умеет применять патчи к итоговому файлу, указанному в его output-namefile
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> cat p?.patch > pall.patch
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> cat pall.patch
--- calend.old1 2024-11-12 17:09:32.096205400 +0300
+++ calend 2024-11-12 17:09:49.309601942 +0300
@@ -12,7 +12,7 @@
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 7 1 2 3 4 5 1 2
8 9 10 11 12 13 14 6 7 8 9 10 11 12 3 4 5 6 7 8 9
-15 16 17 18 19 20 21 13 14 15 16 17 18 19 10 11 12 13 14 15 16
+15 16 17 18 19 20 21...13 14 15 16 17 18 19 10 11 12 13 14 15 16
22 23 24 25 26 27 28 20 21 22 23 24 25 26 17 18 19 20 21 22 23
29 30 27 28 29 30 31 24 25 26 27 28 29 30
--- calend.old2 2024-11-12 17:09:49.309601942 +0300
+++ calend 2024-11-12 17:10:11.596350457 +0300
@@ -16,7 +16,7 @@
22 23 24 25 26 27 28 20 21 22 23 24 25 26 17 18 19 20 21 22 23
29 30 27 28 29 30 31 24 25 26 27 28 29 30
- Июль Август Сентябрь
+ Июль Август Сентёр..
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 7 1 2 3 4 1
8 9 10 11 12 13 14 5 6 7 8 9 10 11 2 3 4 5 6 7 8
@@ -24,7 +24,7 @@
22 23 24 25 26 27 28 19 20 21 22 23 24 25 16 17 18 19 20 21 22
29 30 31 26 27 28 29 30 31 23 24 25 26 27 28 29
30
- Октябрь Ноябрь Декабрь
+ Октёр.. Ноёр.. Декабрь
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 1 2 3 1
7 8 9 10 11 12 13 4 5 6 7 8 9 10 2 3 4 5 6 7 8
--- calend.old3 2024-11-12 17:10:11.596350457 +0300
+++ calend 2024-11-12 17:11:01.573216826 +0300
@@ -12,11 +12,11 @@
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 7 1 2 3 4 5 1 2
8 9 10 11 12 13 14 6 7 8 9 10 11 12 3 4 5 6 7 8 9
-15 16 17 18 19 20 21...13 14 15 16 17 18 19 10 11 12 13 14 15 16
+15 16 17 18 19 20 21 13 14 15 16 17 18 19 10 11 12 13 14 15 16
22 23 24 25 26 27 28 20 21 22 23 24 25 26 17 18 19 20 21 22 23
29 30 27 28 29 30 31 24 25 26 27 28 29 30
- Июль Август Сентёр..
+ Июль Август Сентёр
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 7 1 2 3 4 1
8 9 10 11 12 13 14 5 6 7 8 9 10 11 2 3 4 5 6 7 8
@@ -24,7 +24,7 @@
22 23 24 25 26 27 28 19 20 21 22 23 24 25 16 17 18 19 20 21 22
29 30 31 26 27 28 29 30 31 23 24 25 26 27 28 29
30
- Октёр.. Ноёр.. Декабрь
+ Октёр Ноёр Декабрь
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 1 2 3 1
7 8 9 10 11 12 13 4 5 6 7 8 9 10 2 3 4 5 6 7 8
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend>
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> LC_TIME=ru_RU.UTF-8 cal -y > calend
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> patch --verbose < pall.patch
Hmm... Looks like a unified diff to me...
The text leading up to this was:
--------------------------
|--- calend.old1 2024-11-12 17:09:32.096205400 +0300
|+++ calend 2024-11-12 17:09:49.309601942 +0300
--------------------------
patching file calend
Using Plan A...
Hunk #1 succeeded at 12.
Hmm... The next patch looks like a unified diff to me...
The text leading up to this was:
--------------------------
|--- calend.old2 2024-11-12 17:09:49.309601942 +0300
|+++ calend 2024-11-12 17:10:11.596350457 +0300
--------------------------
patching file calend
Using Plan A...
Hunk #1 succeeded at 16.
Hunk #2 succeeded at 24.
Hmm... The next patch looks like a unified diff to me...
The text leading up to this was:
--------------------------
|--- calend.old3 2024-11-12 17:10:11.596350457 +0300
|+++ calend 2024-11-12 17:11:01.573216826 +0300
--------------------------
patching file calend
Using Plan A...
Hunk #1 succeeded at 12.
Hunk #2 succeeded at 24.
done
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend>
Всё корректно применяется, последовательно применяя преобразование. А теперь рассмотрим утилиты patchutils
patchutils combinediff
combinediff объединяет патчи в единый блок, при этом по возможности объединяя последовательные действия.
Патчи - самостоятельные единицы, не зависящие сами по себе от исходников, и в рамках своих контекстов могут проводить преобразования. Поэтому возможны (теоретически) как ошибочные “склеивания”, так и невыполненные объединения операций (отсюда небольшой совет - всегда при объединении указывать патчи в правильном (пусть и частичном) порядке)
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> combinediff p1.patch p2.patch
diff -u calend calend
--- calend 2024-11-12 17:09:49.309601942 +0300
+++ calend 2024-11-12 17:10:11.596350457 +0300
@@ -12,11 +12,11 @@
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 7 1 2 3 4 5 1 2
8 9 10 11 12 13 14 6 7 8 9 10 11 12 3 4 5 6 7 8 9
-15 16 17 18 19 20 21 13 14 15 16 17 18 19 10 11 12 13 14 15 16
+15 16 17 18 19 20 21...13 14 15 16 17 18 19 10 11 12 13 14 15 16
22 23 24 25 26 27 28 20 21 22 23 24 25 26 17 18 19 20 21 22 23
29 30 27 28 29 30 31 24 25 26 27 28 29 30
- Июль Август Сентябрь
+ Июль Август Сентёр..
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 7 1 2 3 4 1
8 9 10 11 12 13 14 5 6 7 8 9 10 11 2 3 4 5 6 7 8
@@ -24,7 +24,7 @@
22 23 24 25 26 27 28 19 20 21 22 23 24 25 16 17 18 19 20 21 22
29 30 31 26 27 28 29 30 31 23 24 25 26 27 28 29
30
- Октябрь Ноябрь Декабрь
+ Октёр.. Ноёр.. Декабрь
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 1 2 3 1
7 8 9 10 11 12 13 4 5 6 7 8 9 10 2 3 4 5 6 7 8
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> combinediff p1.patch p2.patch | combinediff - p3.patch
# объединили 1й патч с 3м - убралось изменение про точки между 21 и 13
diff -u calend calend
--- calend 2024-11-12 17:10:11.596350457 +0300
+++ calend 2024-11-12 17:11:01.573216826 +0300
@@ -16,7 +16,7 @@
22 23 24 25 26 27 28 20 21 22 23 24 25 26 17 18 19 20 21 22 23
29 30 27 28 29 30 31 24 25 26 27 28 29 30
- Июль Август Сентябрь
+ Июль Август Сентёр
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 7 1 2 3 4 1
8 9 10 11 12 13 14 5 6 7 8 9 10 11 2 3 4 5 6 7 8
@@ -24,7 +24,7 @@
22 23 24 25 26 27 28 19 20 21 22 23 24 25 16 17 18 19 20 21 22
29 30 31 26 27 28 29 30 31 23 24 25 26 27 28 29
30
- Октябрь Ноябрь Декабрь
+ Октёр Ноёр Декабрь
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 1 2 3 1
7 8 9 10 11 12 13 4 5 6 7 8 9 10 2 3 4 5 6 7 8
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend>
patchutils interdiff
Если combinediff описывает объединение патчей, то interdiff наоборот - показывает операции, которые надо провести над файлом, запатченным только первым параметром, чтобы получить файл, запатченный только вторым параметром (Преобразование base + patch1 -> base + patch2)
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> interdiff p1.patch p2.patch
diff -u calend calend
--- calend 2024-11-12 17:09:49.309601942 +0300
+++ calend 2024-11-12 17:10:11.596350457 +0300
@@ -12,11 +12,11 @@
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 7 1 2 3 4 5 1 2
8 9 10 11 12 13 14 6 7 8 9 10 11 12 3 4 5 6 7 8 9
-15 16 17 18 19 20 21...13 14 15 16 17 18 19 10 11 12 13 14 15 16
+15 16 17 18 19 20 21 13 14 15 16 17 18 19 10 11 12 13 14 15 16
22 23 24 25 26 27 28 20 21 22 23 24 25 26 17 18 19 20 21 22 23
29 30 27 28 29 30 31 24 25 26 27 28 29 30
- Июль Август Сентябрь
+ Июль Август Сентёр..
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 7 1 2 3 4 1
8 9 10 11 12 13 14 5 6 7 8 9 10 11 2 3 4 5 6 7 8
@@ -24,7 +24,7 @@
22 23 24 25 26 27 28 19 20 21 22 23 24 25 16 17 18 19 20 21 22
29 30 31 26 27 28 29 30 31 23 24 25 26 27 28 29
30
- Октябрь Ноябрь Декабрь
+ Октёр.. Ноёр.. Декабрь
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 1 2 3 1
7 8 9 10 11 12 13 4 5 6 7 8 9 10 2 3 4 5 6 7 8
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend>
patchutils rediff
Эта утилита поможет нам исправлять ошибки, которые мы допустили при ручном исправлении патча. При этом редактирование всё также просто основывается на контексте самого патча и никак не зависит от исходников
p1.patch
--- calend.old1 2024-11-12 17:09:32.096205400 +0300
+++ calend 2024-11-12 17:09:49.309601942 +0300
@@ -16,7 +12,7 @@ # Поменяли 12 на 16
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 7 1 2 3 4 5 1 2
8 9 10 11 12 13 14 6 7 8 9 10 11 12 3 4 5 6 7 8 9
-15 16 17 18 19 20 21 13 14 15 16 17 18 19 10 11 12 13 14 15 16
+15 16 17 18 19 20 21...13 14 15 16 17 18 19 10 11 12 13 14 15 16
+Qoo-Qoo # добавили ещё строчку
22 23 24 25 26 27 28 20 21 22 23 24 25 26 17 18 19 20 21 22 23
29 30 27 28 29 30 31 24 25 26 27 28 29 30
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> rediff p1.patch
--- calend.old1 2024-11-12 17:09:32.096205400 +0300
+++ calend 2024-11-12 17:09:49.309601942 +0300
@@ -16,7 +16,8 @@
Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс Пн Вт Ср Чт Пт Сб Вс
1 2 3 4 5 6 7 1 2 3 4 5 1 2
8 9 10 11 12 13 14 6 7 8 9 10 11 12 3 4 5 6 7 8 9
-15 16 17 18 19 20 21 13 14 15 16 17 18 19 10 11 12 13 14 15 16
+15 16 17 18 19 20 21...13 14 15 16 17 18 19 10 11 12 13 14 15 16
+Qoo-Qoo
22 23 24 25 26 27 28 20 21 22 23 24 25 26 17 18 19 20 21 22 23
29 30 27 28 29 30 31 24 25 26 27 28 29 30
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend> LC_TIME=ru_RU.UTF-8 cal -y > calend
user@localhost:~/LinuxAppDev/09_PatchDiff/Lection/calend>
Git и патчи
Мы уже упоминали git сегодня, как ресурс, пришедший отчасти на замену или на подмогу методу передачи патчей между разработчиками для синхронизации работы. Однако просто выбросить механизм было бы неправильным, он очень грамотно интегрирован в работу git.
Рассмотрим пример
user@localhost:~/test_repo> cal > txt
user@localhost:~/test_repo> cat txt
November 2024
Su Mo Tu We Th Fr Sa
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
user@localhost:~/test_repo> sed -i 's/1/\&/g' txt
user@localhost:~/test_repo> cat txt
November 2024
Su Mo Tu We Th Fr Sa
& 2
3 4 5 6 7 8 9
&0 && &2 &3 &4 &5 &6
&7 &8 &9 20 2& 22 23
24 25 26 27 28 29 30
user@localhost:~/test_repo>
В git есть свой аналог утилиты diff - git diff. Он также описывает изменения, однако между коммитами (или, при отсутствии параметров, между последним коммитом и текущей версией программы)
user@localhost:~/test_repo> git diff -u > p1.patch
user@localhost:~/test_repo> cat p1.patch
diff --git a/txt b/txt
index f6b00ae..58f6ea9 100644
--- a/txt
+++ b/txt
@@ -1,8 +1,8 @@
November 2024
Su Mo Tu We Th Fr Sa
- 1 2
+ & 2
3 4 5 6 7 8 9
-10 11 12 13 14 15 16
-17 18 19 20 21 22 23
+&0 && &2 &3 &4 &5 &6
+&7 &8 &9 20 2& 22 23
24 25 26 27 28 29 30
user@localhost:~/test_repo>
user@localhost:~/test_repo> git add .
user@localhost:~/test_repo> git commit -m "2nd"
[master 7bc23e3] 2nd
2 files changed, 19 insertions(+), 3 deletions(-)
create mode 100644 p1.patch
user@localhost:~/test_repo>
Важная фишка git diff - если в репозитории есть данные о разработчике (имя и почта), можно прямо из репозитория по предварительно настроенному каналу отправлять письма с патчами. Собственно, именно таким способом разработчики раньше и обменивались изменениями - просто пересылали друг другу патчи.
Для того, чтобы применить полученные патчи, необходимо воспользоваться функцией git am (буквально "git apply mailbox", снова отсылающее нас к основному методу передачи патчей)
user@localhost:~/test_repo> cp p1.patch ~/p1.patch
user@localhost:~/test_repo> git reset --hard HEAD~
HEAD is now at 384275a 1st
user@localhost:~/test_repo> ls
txt
user@localhost:~/test_repo> cat txt
November 2024
Su Mo Tu We Th Fr Sa
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
user@localhost:~/test_repo> git am < ~/p1.patch
Applying:
user@localhost:~/test_repo> ls
txt
user@localhost:~/test_repo> cat txt
November 2024
Su Mo Tu We Th Fr Sa
& 2
3 4 5 6 7 8 9
&0 && &2 &3 &4 &5 &6
&7 &8 &9 20 2& 22 23
24 25 26 27 28 29 30
user@localhost:~/test_repo>
Патчирование в git более строгое, чем у patch, и даже при малейшем изменении контекста патча не позволит его применить, поскольку для совместной разработки это может быть критичным.
Домашнее задание
Написать на Си программу, которая генерирует случайный лабиринт размера 6×6
- Минимальные требования:
- Лабиринт — это последовательность строк из «.» и «#».
- Длина строки — 13 символов, количество строк также 13. Таким образом, он состоит из «комнат» 3×3 с общими «стенами». Пример полностью непроходимого лабиринта 6×6:
#############
#.#.#.#.#.#.#
#############
#.#.#.#.#.#.#
#############
#.#.#.#.#.#.#
#############
#.#.#.#.#.#.#
#############
#.#.#.#.#.#.#
#############
#.#.#.#.#.#.#
#############
- Лабиринт, генерируемый программой, должен быть проходимым из любой комнаты в любую
- (необязательно) Путь между комнатами должен быть единственным. Пример:
############# #.......#...# #######.#.#.# #...#...#.#.# #.###.###.#.# #...#.....#.# ###.#######.# #.......#...# #.#######.### #...#.....#.# #.#.#.#####.# #.#.........# #############
- (необязательно) Путь между комнатами должен быть единственным. Пример:
- Приложить к файлу набор из минимум трёх
unified-патчей, которые модифицируют программу следующим образом:- Размер лабиринта задаётся из командной строки первым параметром
- Первый параметр — это строка из двух символов, «проход» и «стена» выводимого лабиринта, а второй — его размер
- Первый параметр — начальное значение генератора псевдослучайных чисел (для воспроизведения лабиринта), второй и третий — «проход»-«стена» и размер соответственно.
- Написать
Makefile(или аналог), который будет- генерировать три дополнительных исходника, соответствующх добавлению очередного патча
- генерировать, соответственно, четыре бинарника
- иметь цель
run, которая запускает все четыре бинарника с заданными параметрами - иметь цель
clean, уничтожающую все генераты