Установка патчей – работа с утилитой patch

Thank you for reading this post, don't forget to subscribe!

Прак­ти­че­ски каж­дый раз­ра­бот­чик про­грамм­но­го обес­пе­че­ния (ПО), про­грам­мист или вер­сталь­щик стал­ки­ва­ет­ся (и доволь­но часто) с необ­хо­ди­мо­стью моди­фи­ка­ции неко­то­рой части рабо­че­го про­ек­та или даже несколь­ких строк кода. Осо­бен­но это акту­аль­но, когда в раз­ра­бот­ке участ­ву­ет несколь­ко чело­век, кото­рые могут вно­сить прав­ки в раз­ных частях про­ек­та. Для удоб­ства и авто­ма­ти­за­ции дей­ствий по состав­ле­нию таких пра­вок исполь­зу­ют­ся спе­ци­а­ли­зи­ро­ван­ные ути­ли­ты. Одной из таких явля­ет­ся ути­ли­та patch и о ней более подроб­но будет рас­ска­за­но в дан­ной статье.

Что такое патч?

Гово­ря о пат­чах вку­пе с ути­ли­той patch, сле­ду­ет под­ра­зу­ме­вать, что это каса­ет­ся исклю­чи­тель­но тек­сто­вых дан­ных. Дру­ги­ми сло­ва­ми, про­ис­хо­дит рабо­та с исход­ны­ми кода­ми про­ек­та, будь то код C++, PHP, HTML и т. д. Вооб­ще, все самые «суро­вые» про­грам­ми­сты или раз­ра­бот­чи­ки в про­цес­се сво­ей сов­мест­ной рабо­ты над про­ек­том обме­ни­ва­ют­ся исклю­чи­тель­но отдель­ны­ми прав­ка­ми, а не пере­сы­ла­ют друг дру­гу акту­аль­ные вер­сии про­ек­тов целиком.

Сама прав­ка, т. е. тек­сто­вые изме­не­ния в исход­ном коде про­ек­тов (для одно­го его фай­ла или сра­зу для несколь­ких) и есть патч или «заплат­ка». Патч, поми­мо самих изме­не­ний кода содер­жит так­же и неко­то­рую слу­жеб­ную инфор­ма­цию, необ­хо­ди­мую для пра­виль­но­го «нало­же­ния заплат­ки», т. е. для уста­нов­ки пат­ча. Таким обра­зом, патч — это тек­сто­вый файл опре­де­лён­но­го фор­ма­та, содер­жа­щий в себе дан­ные и инструк­ции для при­ве­де­ния конеч­но­го фай­ла (или про­ек­та) к нуж­но­му или акту­аль­но­му виду.

Ути­ли­та patch уме­ет быст­ро и эффек­тив­но рас­по­ря­жать­ся дан­ны­ми из фай­ла-пат­ча, исполь­зуя для это­го хра­ня­щи­е­ся в нём инструк­ции. И таким обра­зом выпол­ня­ет все рутин­ные дей­ствия по редак­ти­ро­ва­нию. Поль­зо­ва­те­лю (раз­ра­бот­чи­ку) необ­хо­ди­мо лишь пра­виль­но выпол­нить соот­вет­ству­ю­щую коман­ду, задав все необ­хо­ди­мые аргу­мен­ты и опции.

Синтаксис и основные опции команды patch

Нет ниче­го уди­ви­тель­но­го в том, что ути­ли­та patch отно­сит­ся к кате­го­рии ПО, кото­рое обя­за­тель­но долж­но быть уста­нов­ле­но на любой машине для раз­ра­бот­ки про­грамм, да и вооб­ще для веде­ния раз­ра­бот­ки. Прак­ти­че­ски любой дис­три­бу­тив Linux предо­став­ля­ет ути­ли­ту patch пред­уста­нов­лен­ной по-умолчанию.

Сто­ит так­же отме­тить, что по сво­ей функ­ци­о­наль­но­сти, patch доволь­но слож­на и обла­да­ет, без пре­уве­ли­че­ния, про­сто огром­ным набо­ром опций. По этой при­чине в дан­ной ста­тье будут при­ве­де­ны толь­ко самые рас­про­стра­нён­ные при­ё­мы при рабо­те с этой ути­ли­той и толь­ко сопут­ству­ю­щие им опции команд. Син­так­сис коман­ды patch следующий:

Здесь originalfile – это файл, кото­рый необ­хо­ди­мо «про­пат­чить» до акту­аль­но­го состо­я­ния. А patchfile – файл-патч. Сра­зу воз­ни­ка­ет вопрос: а отку­да берёт­ся этот файл-патч? Ответ: он гене­ри­ру­ет­ся дру­гой ути­ли­той — diff, кото­рая нахо­дит построч­ные раз­ли­чия меж­ду фай­ла­ми. Либо же патч может быть состав­лен вруч­ную, авто­ром, если он зна­ком с соот­вет­ству­ю­щим фор­ма­том. Но это быва­ет крайне ред­ко, обыч­но при­бе­га­ют к помо­щи diff или её аналогов.
В сле­ду­ю­щей таб­ли­це при­ве­де­ны опции коман­ды patch, кото­рые исполь­зу­ют­ся наи­бо­лее часто:

<td»>Помещает неудав­ши­е­ся (откло­нён­ные) изме­не­ния в отдель­ный файл rejecfile вме­сто фай­ла .rej по-умолчанию.

Опция Зна­че­ние
-i patchfile Чита­ет инфор­ма­цию из патч-фай­ла, ука­зы­ва­е­мо­го пара­мет­ром patchfile.
-r rejectfile, —reject-file=rejectfile
-N, —forward Когда патч не при­ме­ня­ет­ся, то ути­ли­та patch обыч­но пыта­ет­ся опре­де­лить, выгля­дит ли ситу­а­ция так, как если бы патч уже был при­ме­нён. Опция -N отклю­ча­ет такое поведение.
-pnum, strip=num Обре­за­ет части пути к фай­лу, раз­де­ля­е­мые сим­во­лом косой чер­ты до уров­ня, ука­зан­но­го в пара­мет­ре num. Напри­мер: p0 оста­вит путь /u/john/src/blurfl/blurfl.cpp неиз­мен­ным, а p4 обре­жет тот же путь до blurfl/blurfl.cpp.
-o outputfile, —output=outputfile Отправ­ля­ет вывод в ука­зы­ва­е­мый в пара­мет­ре outputfile файл. Не сле­ду­ет исполь­зо­вать эту опцию, если в каче­стве outputfile ука­зы­ва­ет­ся файл, кото­рый дол­жен быть про­пат­чен. Если в каче­стве outputfile ука­зать сим­вол дефи­са «-», то вывод будет направ­лять­ся в стан­дарт­ный поток STD_OUT.
-E, —remove-empty-file Уда­ля­ет фай­лы, ока­зав­ши­е­ся пусты­ми после при­ме­не­ния пат­ча. Эта опция име­ет смысл, когда исполь­зу­е­мые пат­чи име­ют не кон­текст­ный формат.
—dry-run Печа­та­ет резуль­та­ты при­ме­не­ния пат­ча без реаль­ной моди­фи­ка­ции фай­лов. Полез­но для быст­ро­го и без­опас­но­го тести­ро­ва­ния патчей.
-R, —reverse Отка­ты­ва­ет все изме­не­ния (если они воз­мож­ны), т. е. отме­ня­ет уста­нов­ку патча.
-c, —context Интер­пре­ти­ру­ет файл пат­ча как обыч­ный кон­текст­ный фор­мат, гене­ри­ру­е­мый ути­ли­той diff.
-b, —backup Созда­ёт резерв­ную копию ори­ги­наль­но­го фай­ла вме­сто его удаления.

Применение патчей к отдельным файлам

Преж­де, чем начать рас­смот­ре­ние прак­ти­че­ских при­ме­ров, необ­хо­ди­мо ска­зать несколь­ко слов о той самой ути­ли­те, кото­рая и созда­ёт пат­чи — diff. Она может гене­ри­ро­вать пат­чи трёх типов — про­стой, кон­текст­ный и кон­текст­ный уни­фи­ци­ро­ван­ный. Про­стой гораз­до более ком­пакт­ный по раз­ме­ру, чем кон­текст­ные, но послед­ние гораз­до более удо­бо­чи­та­е­мы и понят­ны для вос­при­я­тия поль­зо­ва­те­лем. Для того, что­бы сге­не­ри­ро­вать про­стой патч, для коман­ды diff ника­ких спе­ци­аль­ных опций не тре­бу­ет­ся. А для гене­ра­ции кон­текст­но­го или уни­фи­ци­ро­ван­но­го кон­текст­но­го пат­чей пред­на­зна­че­ны опции -с и -u соответственно:

Пусть име­ет­ся файл с кодом C++ ChildClass.cpp:

И пусть в этот файл было вне­се­но сле­ду­ю­щее изме­не­ние: метод valueSqr() был пере­име­но­ван в calcSqr(). Тогда кон­текст­ный патч (файл contextpatch) будет выгля­деть сле­ду­ю­щим образом:

Теперь, что­бы про­пат­чить ста­рую вер­сию ChildClass.cpp, нуж­но выпол­нить команду:

В резуль­та­те будет полу­чен файл ChildClass_new.cpp с акту­аль­ным содержимым.

Работа с проектами

С помо­щью ути­ли­ты patch мож­но так­же при­ме­нять пат­чи для несколь­ких фай­лов, при­чём рас­по­ло­жен­ных в раз­ных ката­ло­гах. Это удоб­но, когда изме­не­ния про­во­дят­ся в мас­шта­бах цело­го про­ек­та. Но в этом слу­чае и сам патч дол­жен быть осо­бым обра­зом под­го­тов­лен ути­ли­той diff.

Пусть име­ет­ся ста­рый про­ект в ката­ло­ге base-project. Внут­ри него име­ют­ся под­ка­та­ло­ги include и src, в кото­рых, в свою оче­редь нахо­дят­ся фай­лы с изме­не­ни­я­ми — ChildClass.h (в ката­ло­ге include) и ChildClass.cpp (в ката­ло­ге src). Сам изме­нён­ный (акту­аль­ный) про­ект был поме­щён в отдель­ный ката­лог new-project. Под­го­тов­ка пат­ча будет выгля­деть сле­ду­ю­щим образом:

Сге­не­ри­ро­ван­ный файл-патч project-patch:

Сле­ду­ет обра­тить вни­ма­ние, что в дан­ных при­ме­рах ука­зы­ва­ют­ся отно­си­тель­ные пути. Файл-патч будет поме­щён в теку­щий актив­ный каталог.
Что­бы при­ме­нить патч нуж­но выпол­нить сле­ду­ю­щую команду:

Как вид­но, вме­сто клю­ча -i мож­но исполь­зо­вать сим­вол «<» для пере­на­прав­ле­ния пото­ка из фай­ла на вход коман­ды patch. Здесь так­же нуж­но обра­тить вни­ма­ние и пони­мать, что при выпол­не­нии коман­ды patch актив­ным ката­ло­гом дол­жен быть ката­лог уров­нем выше, чем ката­лог про­ек­та, к кото­ро­му при­ме­ня­ет­ся патч, ведь исполь­зу­ют­ся отно­си­тель­ные пути. Пара­метр -p0 (см. таб­ли­цу из гла­вы «Син­так­сис и основ­ные опции коман­ды patch») ука­зы­ва­ет, что при­ме­не­ние пат­ча долж­но затра­ги­вать весь про­ект. Если бы этот пара­метр был бы равен -p1, то патч при­ме­нял­ся не выше уров­ня ката­ло­гов include и src. Неред­ко быва­ют слу­чаи, когда кро­ме изме­не­ний в содер­жи­мом фай­лов меня­ет­ся так­же и содер­жи­мое ката­ло­гов про­ек­та. Дру­ги­ми сло­ва­ми, добав­ля­ют­ся новые или уда­ля­ют­ся ранее суще­ство­вав­шие фай­лы и под­ка­та­ло­ги. Пусть, напри­мер, в про­ект из преды­ду­ще­го при­ме­ра в ката­лог include был добав­лен файл Readme.txt с содер­жа­ни­ем «This is Readme content.». В этом слу­чае под­го­тов­ка пат­ча будет выгля­деть сле­ду­ю­щим образом:

Сге­не­ри­ро­ван­ный файл-патч project-patch:

Теперь мож­но про­пат­чить проект:

Откат патчей

Если по каким-то при­чи­нам патч ока­зал­ся бес­по­ле­зен и необ­хо­ди­мо вер­нуть­ся к преды­ду­щей вер­сии фай­лов (про­ек­та), то мож­но сде­лать откат изме­не­ний, исполь­зуя опцию -R:

В резуль­та­те будет уда­лён файл Readme.txt, кото­рый был вне­сён в про­ект в при­ме­ре из преды­ду­щей гла­вы, т. е. фак­ти­че­ски откат изменений.
Реко­мен­ду­ет­ся перед при­ме­не­ни­ем пат­чей про­ве­рять, под­хо­дят ли они. Для это­го исполь­зу­ет­ся опция —dry-run:

При воз­ник­но­ве­нии каких-либо оши­бок во вре­мя при­ме­не­ния пат­ча, ути­ли­та patch созда­ёт фай­лы *.rej, по кото­рым мож­но вос­ста­но­вить исход­ную вер­сию фай­ла. Одна­ко, сле­ду­ет учи­ты­вать, что вос­ста­нов­ле­ние содер­жи­мо­го фай­лов таким спо­со­бом — доволь­но дол­гое и нуд­ное заня­тие. Прак­тич­нее созда­вать резерв­ные копии фай­лов, ука­зы­вая в коман­де patch опцию -b: