Работа со сторонними исходными текстами
Рассмотрим ситуацию, когда сторонние исходники используются, но нужно их немного поправить:
- Потому что это правка, необходимая только вашему проекту
- Потому что вы — майнтейнер пакета в Linux, а исходники не соответствуют принятой в вашем сообществе дисциплине, или апстрим не считает багу ошибкой, а вы считаете ☺
- …
В любом случае при обновлении апстрима исправления придётся вносить заново
Patch / diff
Цикл обновления / внесения изменений:
- Первоначальный импорт исходников
- Адаптация к вашим нуждам
Оформление серии патчей (см. ниже)
- Релиз
- Отныне и навеки
- Обновление апстримной версии
- Применение патчей к обновлённой версии
- Адаптация отвалившихся патчей
- Релиз
Утилита diff — построчное сравнение и демонстрация изменений двух текстов
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <ctype.h>
6
7 /* open()->fopen() wrapper */
8 static FILE *ffopen(const char *pathname, const char *mode, int flags)
9 {
10 int m;
11
12 switch(tolower(mode[0])+(mode[1]=='+')) {
13 case 'b': /* "a+" */
14 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break;
15 case 'r': m = O_RDONLY; break;
16 case 's': m = O_RDWR; break; /* "r+" */
17 case 'x': /* "w+" */
18 case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break;
19 default: m = O_RDONLY; break;
20 }
21
22 int fd = open(pathname, flags, m);
23 return fd<0? NULL: fdopen(fd, mode);
24 }
25
26 int main(int argc, char *argv[]) {
27 FILE *fp;
28
29 fp = ffopen(argv[1], "r", O_NOFOLLOW);
30 if(fp == NULL) {
31 perror(argv[1]);
32 return 1;
33 }
34
35 return 0;
36 }
|
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <ctype.h>
6
7 /* open()->fopen() wrapper */
8 static FILE *ffopen(const char *pathname, const char *mode, int flags)
9 {
10 int m;
11
12 /* Simulate fopen flags */
13 switch(tolower(mode[0])+(mode[1]=='+')) {
14 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break;
15 case 'b': /* "a+" */
16 case 'r': m = O_RDONLY; break;
17 case 's': m = O_RDWR; break; /* "r+" */
18 case 'x': /* "w+" */
19 case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break;
20 default: m = O_RDONLY; break;
21 }
22
23 int fd = open(pathname, flags, m);
24 return fd<0? NULL: fdopen(fd, mode);
25 }
26
27 int main(int argc, char *argv[]) {
28 FILE *fp;
29
30 fp = ffopen(argv[1], "r", O_NOFOLLOW);
31 if(fp == NULL) {
32 perror(argv[1]);
33 return 1;
34 }
35
36 return 0;
37 }
|
Просто diff: как из одного файла сделать другой
diff -e — команды для текстового редактора ed
diff -c — разница вместе с окружающим её контекстом
1 *** ffopen.c 2020-06-13 20:58:46.702703936 +0300 2 --- ffopen_new.c 2020-12-08 19:53:02.082341887 +0300 3 *************** 4 *** 9,17 **** 5 { 6 int m; 7 8 switch(tolower(mode[0])+(mode[1]=='+')) { 9 - case 'b': /* "a+" */ 10 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 11 case 'r': m = O_RDONLY; break; 12 case 's': m = O_RDWR; break; /* "r+" */ 13 case 'x': /* "w+" */ 14 --- 9,18 ---- 15 { 16 int m; 17 18 + /* Simulate fopen flags */ 19 switch(tolower(mode[0])+(mode[1]=='+')) { 20 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 21 + case 'b': /* "a+" */ 22 case 'r': m = O_RDONLY; break; 23 case 's': m = O_RDWR; break; /* "r+" */ 24 case 'x': /* "w+" */
diff -u — наиболее удобный формат
1 --- ffopen.c 2020-06-13 20:58:46.702703936 +0300 2 +++ ffopen_new.c 2020-12-08 19:53:02.082341887 +0300 3 @@ -9,9 +9,10 @@ 4 { 5 int m; 6 7 + /* Simulate fopen flags */ 8 switch(tolower(mode[0])+(mode[1]=='+')) { 9 - case 'b': /* "a+" */ 10 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 11 + case 'b': /* "a+" */ 12 case 'r': m = O_RDONLY; break; 13 case 's': m = O_RDWR; break; /* "r+" */ 14 case 'x': /* "w+" */
@@ -9,9 +9,10 @@ означает: «в строчке 9 исходного файла взять 9 строк и превратить их в 10 строк целевого файла»
Есть варианты -C размер контекста и -U размер контекста
- Вариант с несколькими изменениями (chunks):
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 #include <ctype.h> 6 7 /* open()->fopen() wrapper */ 8 static FILE *ffopen(const char *pathname, const char *mode, int flags) 9 { 10 int m; 11 12 switch(tolower(mode[0])+(mode[1]=='+')) { 13 case 'b': /* "a+" */ 14 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 15 case 'r': m = O_RDONLY; break; 16 case 's': m = O_RDWR; break; /* "r+" */ 17 case 'x': /* "w+" */ 18 case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break; 19 default: m = O_RDONLY; break; 20 } 21 22 int fd = open(pathname, flags, m); 23 return fd<0? NULL: fdopen(fd, mode); 24 } 25 26 int main(int argc, char *argv[]) { 27 FILE *fp; 28 29 fp = ffopen(argv[1], "r", O_NOFOLLOW); 30 if(fp == NULL) { 31 perror(argv[1]); 32 return 1; 33 } 34 35 return 0; 36 }
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 #include <ctype.h> 6 7 /* open()->fopen() wrapper */ 8 static FILE *ffopen(const char *pathname, const char *mode, int flags) 9 { 10 int m; 11 12 /* Simulate fopen flags */ 13 switch(tolower(mode[0])+(mode[1]=='+')) { 14 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 15 case 'b': /* "a+" */ 16 case 'r': m = O_RDONLY; break; 17 case 's': m = O_RDWR; break; /* "r+" */ 18 case 'x': /* "w+" */ 19 case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break; 20 default: m = O_RDONLY; break; 21 } 22 23 int fd = open(pathname, flags, m); 24 return fd<0? NULL: fdopen(fd, mode); 25 } 26 27 int main(int argc, char *argv[]) { 28 FILE *fp; 29 30 fp = ffopen(argv[1], "r", O_NOFOLLOW); 31 if(fp == NULL) { 32 perror(argv[1]); 33 return 1; 34 } 35 else 36 fclose(fp); 37 38 return 0; 39 }
- ⇒
1 --- ffopen.c 2020-12-08 20:29:07.452395896 +0300 2 +++ ffopen_new.c 2020-12-08 20:12:38.004914230 +0300 3 @@ -9,9 +9,10 @@ 4 { 5 int m; 6 7 + /* Simulate fopen flags */ 8 switch(tolower(mode[0])+(mode[1]=='+')) { 9 - case 'b': /* "a+" */ 10 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 11 + case 'b': /* "a+" */ 12 case 'r': m = O_RDONLY; break; 13 case 's': m = O_RDWR; break; /* "r+" */ 14 case 'x': /* "w+" */ 15 @@ -31,6 +32,8 @@ 16 perror(argv[1]); 17 return 1; 18 } 19 + else 20 + fclose(fp); 21 22 return 0; 23 }
Утилита patch умеет применять результат diff к исходному файлу, при этом получается целевой файл
Этот-то «результат diff» и называется патчем
- А несколько таких патчей в правильном порядке — патчсетом
Что гораздо важнее, patch умеет находить перемещение контекста и даже определять приблизительное совпадение контекста:
Возьмём слегка изменённый файл ffopen2.c (в нём изменена строка case 'x': и добавлен комментарий перед main()):
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 #include <ctype.h> 6 7 /* open()->fopen() wrapper */ 8 static FILE *ffopen(const char *pathname, const char *mode, int flags) 9 { 10 int m; 11 12 switch(tolower(mode[0])+(mode[1]=='+')) { 13 case 'b': /* "a+" */ 14 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 15 case 'r': m = O_RDONLY; break; 16 case 's': m = O_RDWR; break; /* "r+" */ 17 case 'x': /* alias for "w+" */ 18 case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break; 19 default: m = O_RDONLY; break; 20 } 21 22 int fd = open(pathname, flags, m); 23 return fd<0? NULL: fdopen(fd, mode); 24 } 25 26 /* just ordinary main() */ 27 int main(int argc, char *argv[]) { 28 FILE *fp; 29 30 fp = ffopen(argv[1], "r", O_NOFOLLOW); 31 if(fp == NULL) { 32 perror(argv[1]); 33 return 1; 34 } 35 36 return 0; 37 }
и применим к нему патч, предназначенный для исходного файла:
Контекст первого блока неточен (в патче одна строка контекста слегка другая); тем не менее это очень похожий контекст (с разницей 1, что бы это ни значило), так что блок применён
- Контекст второго блока точен, но найден со смещением в одну строку, блок применён
Получившийся файл имеет свойства как общего предка (ffopen.c), так и обоих родителей (ffopen2.c и ffopen_changed.c)
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 #include <ctype.h> 6 7 /* open()->fopen() wrapper */ 8 static FILE *ffopen(const char *pathname, const char *mode, int flags) 9 { 10 int m; 11 12 switch(tolower(mode[0])+(mode[1]=='+')) { 13 case 'b': /* "a+" */ 14 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 15 case 'r': m = O_RDONLY; break; 16 case 's': m = O_RDWR; break; /* "r+" */ 17 case 'x': /* "w+" */ 18 case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break; 19 default: m = O_RDONLY; break; 20 } 21 22 int fd = open(pathname, flags, m); 23 return fd<0? NULL: fdopen(fd, mode); 24 } 25 26 int main(int argc, char *argv[]) { 27 FILE *fp; 28 29 fp = ffopen(argv[1], "r", O_NOFOLLOW); 30 if(fp == NULL) { 31 perror(argv[1]); 32 return 1; 33 } 34 35 return 0; 36 }
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 #include <ctype.h> 6 7 /* open()->fopen() wrapper */ 8 static FILE *ffopen(const char *pathname, const char *mode, int flags) 9 { 10 int m; 11 12 /* Simulate fopen flags */ 13 switch(tolower(mode[0])+(mode[1]=='+')) { 14 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 15 case 'b': /* "a+" */ 16 case 'r': m = O_RDONLY; break; 17 case 's': m = O_RDWR; break; /* "r+" */ 18 case 'x': /* alias for "w+" */ 19 case 'w': m = O_WRONLY | O_CREAT | O_TRUNC; break; 20 default: m = O_RDONLY; break; 21 } 22 23 int fd = open(pathname, flags, m); 24 return fd<0? NULL: fdopen(fd, mode); 25 } 26 27 /* just ordinary main() */ 28 int main(int argc, char *argv[]) { 29 FILE *fp; 30 31 fp = ffopen(argv[1], "r", O_NOFOLLOW); 32 if(fp == NULL) { 33 perror(argv[1]); 34 return 1; 35 } 36 else 37 fclose(fp); 38 39 return 0; 40 }
В случае, когда какие-то блоки применяются, а какие-то — нет, patch создаёт reject-файл — патч, содержащий только неприложившиеся блоки.
Например, модифицируем в ffopen.c строчку case 'b'::
- Поскольку изменения сделаны прямо в исправляемой строке, такой блок не приложится; второй блок приложится нормально:
1 $ patch --verbose ffopen3_changed.c < changeset.patch 2 Hmm... Looks like a unified diff to me... 3 The text leading up to this was: 4 -------------------------- 5 |--- ffopen.c 2020-12-08 20:29:07.452395896 +0300 6 |+++ ffopen_new.c 2020-12-08 20:12:38.004914230 +0300 7 -------------------------- 8 patching file ffopen3_changed.c 9 Using Plan A... 10 Hunk #1 FAILED at 9. 11 Hunk #2 succeeded at 31. 12 1 out of 2 hunks FAILED -- saving rejects to file ffopen3_changed.c.rej 13 done 14
- Этот reject файл предполагается открыть редактором и ручками применить
Ручное преобразование патчей
Примеров нет ⇒ буду тупить в эфире!
combinediff — слияние двух патчей (инкрементальные в один кумулятивный)
interdiff — превращение кумулятивного патча2 в инкрементальный относительно патча1
splitdiff — несколько патчей по отдельным файлам
lsdiff — какие файлы модифицируются в патче
grepdiff — поиск РВ в патче — какие файлы модифицируются строкой с этим РВ
filterdiff — подпатч только по указанным файлам
rediff — пересчитать патч, отредактированный вручную
recountdiff — пересчитать годный патч со смещением и/или изменившимся размером контекста
Использование git
Команда git diff порождает не что иное, как diff -u. Предположим, мы отредактировали файл ffopen.c в git-репозитории. Вот что покажет git diff:
1 diff --git a/ffopen.c b/ffopen.c 2 index 7ac8822..bf6ba89 100644 3 --- a/ffopen.c 4 +++ b/ffopen.c 5 @@ -9,9 +9,10 @@ static FILE *ffopen(const char *pathname, const char *mode, int flags) 6 { 7 int m; 8 9 + /* Simulate fopen flags */ 10 switch(tolower(mode[0])+(mode[1]=='+')) { 11 - case 'b': /* "a+" */ 12 case 'a': m = O_WRONLY | O_CREAT | O_APPEND; break; 13 + case 'b': /* "a+" */ 14 case 'r': m = O_RDONLY; break; 15 case 's': m = O_RDWR; break; /* "r+" */ 16 case 'x': /* "w+" */ 17 @@ -31,6 +32,8 @@ int main(int argc, char *argv[]) { 18 perror(argv[1]); 19 return 1; 20 } 21 + else 22 + fclose(fp); 23 24 return 0; 25 }
Поскольку git сравнивает две версии одного и того же файла, для большей понятности исходный файл считается лежащим в воображаемом каталоге a/, а целевой — в воображаемом каталоге b/.
В заголовке патча присутствуют (урезанные) идентификаторы сравниваемых объектов (7ac8822 и bf6ba89)
Умный git догадался, что это файл на Си (?) и в описании контекста добавил сигнатуру функции, в которой он встретился (это комментарий)
Если сейчас сделать commit, тот же формат будет у git log -p (+загловок коммита)
- Из последовательности коммитов можно сделать патчсет (в примере патч один)
1 $ git format-patch HEAD^ 2 0001-FFopen-updated.patch 3 $ head -20 0001-FFopen-updated.patch 4 From e3522f55a58d7f3ea6d85d518ef55a9d36ce887e Mon Sep 17 00:00:00 2001 5 From: "George V. Kouryachy (Fr. Br. George)" <george@altlinux.ru> 6 Date: Tue, 8 Dec 2020 21:11:51 +0300 7 Subject: [PATCH] FFopen updated 8 9 --- 10 ffopen.c | 5 ++++- 11 1 file changed, 4 insertions(+), 1 deletion(-) 12 13 diff --git a/ffopen.c b/ffopen.c 14 index 7ac8822..bf6ba89 100644 15 --- a/ffopen.c 16 +++ b/ffopen.c 17 @@ -9,9 +9,10 @@ static FILE *ffopen(const char *pathname, const char *mode, int flags) 18 { 19 int m; 20 21 + /* Simulate fopen flags */ 22 switch(tolower(mode[0])+(mode[1]=='+')) { 23 - case 'b': /* "a+" */ 24
Это патч можно применить к старой версии файла!
С помощью patch -p1 < 0001-FFopen-updated.patch
Если не указывать, какой файл патчим, patch возьмёт её из заголовка
Поэтому надо с помощью -p удалить один уровень воображаемых каталогов a/ или b/
С помощью git apply 0001-FFopen-updated.patch
С помощью git am 0001-FFopen-updated.patch (при этом произведётся также и commit с данными из заголовка)
Важно: правила наложения патчей в git очень строгие: fuzz не допускается. Возможно, накладывать старый патч на обновлённые исходники лучше всё-таки patch-ем
Сочетание git и patch
Если никакого git-репозитория нет, всё равно удобно использовать git:
- Создаём git-репозиторий
Помечаем тегом, например, base
- Накатываем патч
- Часть блоков прикладывается, част отваливается
- Коммитим то, что приложилось
- Глядя в reject-файлы применяем вручную то, что не приложилось, проверяем сборку, и тоже коммитим, возможно, несколько раз
Выкатываем преобразованный (и унифицированный под patch -p1) патчсет:
git format-patch base (патчи будут пронумерованы, а их названия — начало первой строки соответствующих commit mnessages)
Можно слить всё в один коммит с помощью git rebase -i base и последующего fixup
Д/З
- Написать на Си программу, которая генерирует случайный лабиринт размера 6×6
- Минимальные требования:
Лабиринт — это последовательность строк из «.» и «#».
- Длина строки — 13 символов, количество строк также 13. Таким образом, он состоит из «комнат» 3×3 с общими «стенами». Пример полностью непроходимого лабиринта 6×6:
############# #.#.#.#.#.#.# ############# #.#.#.#.#.#.# ############# #.#.#.#.#.#.# ############# #.#.#.#.#.#.# ############# #.#.#.#.#.#.# ############# #.#.#.#.#.#.# #############
- Лабиринт, генерируемый программой, должен быть проходимым из любой комнаты в любую
(необязательно) Путь между комнатами должен быть единственным. Пример:
############# #.......#...# #######.#.#.# #...#...#.#.# #.###.###.#.# #...#.....#.# ###.#######.# #.......#...# #.#######.### #...#.....#.# #.#.#.#####.# #.#.........# #############
- Приложить к файлу набор из минимум трёх unified-патчей, которые модифицируют программу следующим образом:
- Размер лабиринта задаётся из командной строки первым параметром
- Первый параметр — это строка из двух символов, «проход» и «стена» выводимого лабиринта, а второй — его размер
- Первый параметр — начальное значение генератора псевдослучайных чисел (для воспроизведения лабиринта), второй и третий — «проход»-«стена» и размер соответственно.
Написать Makefile (или аналог), который будет
- генерировать три дополнительных исходника, соответствующх добавлению очередного патча
- генерировать, соответственно, четыре бинарника
иметь цель run, которая запускает все четыре бинарника с заданными параметрами
иметь цель clean, уничтожающую все генераты
- Минимальные требования:
Создать в репозитории с домашними заданиями подкаталог 09_PatchDiff и поместить туда решение.
Получилось примерно так: