Работа со сторонними исходными текстами

Рассмотрим ситуацию, когда сторонние исходники используются, но нужно их немного поправить:

Работа с патчами

Цикл обновления / внесения изменений:

  1. Первоначальный импорт исходников
    1. Адаптация к вашим нуждам
    2. Оформление серии патчей (см. ниже)

    3. Релиз
  2. Отныне и навеки
    1. Обновление апстримной версии
    2. Применение патчей к обновлённой версии
    3. Адаптация отвалившихся патчей
    4. Релиз

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   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': m = O_RDWR | O_CREAT | O_TRUNC; break; /* 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 }

Утилиту diff можно запустить рекурсивно (-r), в выводе будет несколько файлов (отмеченных --- и +++).

patch

Утилита patch умеет применять результат diff к исходному файлу, при этом получается целевой файл

Файл ffopen.c стал таким же, как ffopen_new.c

Файл ffopen.c стал таким же, как ffopen_new2.c

Неточный контекст в patch

Что гораздо важнее, patch умеет находить перемещение контекста и даже определять приблизительное совпадение контекста:

В случае, когда какие-то блоки применяются, а какие-то — нет, patch создаёт reject-файл — патч, содержащий только неприложившиеся блоки.

Ручное преобразование патчей

Patchutils

/!\ Что-то вроде примера:

   1   ## combinediff
   2 $ rm -rf *.old* p?.patch
   3 $ cal -y 2024 > calend
   4 $ sed -i.old1 's/21   13/21...13/' calend
   5 $ diff -u calend.old1 calend > p1.patch
   6 $ sed -i.old2 's/ябрь/ёр../g' calend
   7 $ diff -u calend.old2 calend > p2.patch
   8 $ sed -i.old3 's/[.]/ /g' calend
   9 $ diff -u calend.old3 calend > p3.patch
  10 $ combinediff p1.patch p2.patch | combinediff - p3.patch
  11 
  12   ## interdiff
  13 $ interdiff p1.patch p2.patch
  14 
  15   ## splitdiff
  16 $ cp calend.old1 calend # restore
  17 $ cat p?.patch > pall.patch
  18 $ patch < pall.patch
  19 $ cp calend.old1 calend # restore
  20 $ splitdiff pall.patch
  21 
  22 $ { echo "QQ"; cat calend.old1; } > calend
  23 $ patch < pall.patch

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

Сочетание git и patch

Если никакого git-репозитория нет, всё равно удобно использовать git:

Д/З

  1. Прочитать про diff и patch

  2. Написать на Си программу, которая генерирует случайный лабиринт размера 6×6
    • Минимальные требования:
      • Лабиринт — это последовательность строк из «.» и «#».

      • Длина строки — 13 символов, количество строк также 13. Таким образом, он состоит из «комнат» 3×3 с общими «стенами». Пример полностью непроходимого лабиринта 6×6:
        #############
        #.#.#.#.#.#.#
        #############
        #.#.#.#.#.#.#
        #############
        #.#.#.#.#.#.#
        #############
        #.#.#.#.#.#.#
        #############
        #.#.#.#.#.#.#
        #############
        #.#.#.#.#.#.#
        #############
    • Блоки по сторонам от точек — это «стены», а по диагоналям — «углы». Чтобы пройти из комнаты в комнату, надо разрушить стену. Пример лабиринта 6×6 без стен:
      #############
      #...........#
      #.#.#.#.#.#.#
      #...........#
      #.#.#.#.#.#.#
      #...........#
      #.#.#.#.#.#.#
      #...........#
      #.#.#.#.#.#.#
      #...........#
      #.#.#.#.#.#.#
      #...........#
      #############
    • Лабиринт, генерируемый программой, должен быть проходимым из любой комнаты в любую

      • <!> (необязательно) Путь между комнатами должен быть единственным. Пример:

        #############
        #.......#...#
        #######.#.#.#
        #...#...#.#.#
        #.###.###.#.#
        #...#.....#.#
        ###.#######.#
        #.......#...#
        #.#######.###
        #...#.....#.#
        #.#.#.#####.#
        #.#.........#
        #############
    • Приложить к файлу набор из минимум трёх unified-патчей, которые модифицируют программу следующим образом:
      1. Размер лабиринта задаётся из командной строки первым параметром
      2. Первый параметр — это строка из двух символов, «проход» и «стена» выводимого лабиринта, а второй — его размер
      3. Первый параметр — начальное значение генератора псевдослучайных чисел (для воспроизведения лабиринта), второй и третий — «проход»-«стена» и размер соответственно.
    • Написать Makefile (или аналог), который будет

      • генерировать три дополнительных исходника, соответствующх добавлению очередного патча
      • генерировать, соответственно, четыре бинарника
      • иметь цель run, которая запускает все четыре бинарника с заданными параметрами

      • иметь цель clean, уничтожающую все генераты

  3. Создать в репозитории с домашними заданиями подкаталог 09_PatchDiff и поместить туда решение.

Получилось примерно так:

LecturesCMC/LinuxApplicationDevelopment2024/09_PatchDiff (последним исправлял пользователь FrBrGeorge 2024-11-19 12:47:19)