#007 - PATCH - co to i jak używać
TEORIA
Czym jest “patch” - jest to zbiór zmian jakie można wprowadzić do pliku/struktury plików. O patchu wspomniałem w wpisie odnoście SEGGER Sysytem Viewer - aplikacja ta wymaga zmodyfikowania źródeł FreeRTOSa i te zmiany zapisane są w pliku patch - dzięki temu łatwo można je “zaaplikować” do swoich używanych plików.
Plik patch nie jest to tylko “głupie wypisanie w linii xx dodaj taki text” - każda pojedyncza zmiana to tzw. “hunk” - z dodatkowymi informacjami np. zawartością kolejnych/poprzednich linii, dzięki czemu można zpatchować pliki, które w delikatny sposób już zmodyfikowaliśmy wcześniej. Jeśli nie da się w łatwy sposób tego zrobić automatycznie - dostaniemy o tym informację i musimy sami przeprowadzić “merge” - dokładnie jak w przypadku “merge conflict” w przypadku pracy z GITem.
Dokumentacja odnośnie tego czym jest “hunk” i inne pojęcia związane z różnicami pomiędzy plikami itp: dokumentacja chunk.
Czyli patch/diff jest to zbiór wielu “hunków” w jednym pliku.
Przykłady użycia:
- w kilku projektach używamy takiej samej biblioteki i znajdujemy w niej błąd - możemy poprawić go, a następnie wygenerować patcha zawierającego te zmiany i użyć go w innych projektach. Aczkolwiek osobiście uważam, że taka sytuacja nie powinna mieć miejsca - biblioteki lepiej trzymać jako coś typu submoduły - co ułatwi tego typu operacje.
- producent udostępnia SDKa, znajduje błąd w bibliotece i poprawkę udostępnia też jako patch - dzięki temu firmy, które korzystają z tego SDKa, ale wprowadziły tam swoje modyfikacje mogą łatwiej naprawić ten błąd - z doświadczenia wiem, że często aktualizacje SDKów od producentów to dość bolesny proces - jeśli pojedyncze krytyczne zmiany są wprowadzane w formie małych patchy - aplikacja ich przechodzi dość płynnie.
- dla STM32CubeIDE z generatorem - jeśli nasz projekt po wygenerowaniu wymaga dopisania kilku rzeczy do przegenerowanych plików - możemy je zebrać właśnie jako patch i aplikować po przegenerowaniu projektu. Taka sytuacja może powstać jeśli nie wszystkie “komponenty” HW są inicjalizowane przez CubeMX - tylko niektóre mają swoją konfigurację wpisaną “z palca” - CubeMX może wtedy nie wygenerować potrzebnych wektorów przerwań/callbacków.
- zamiast dodawać “ręcznie” rzeczy związane z SSV do projektów CubeIDE - mam utworzony odpowiedni patch, który robi to za mnie.
PRAKTYKA
Bardzo prosty przykład z komendami - na bazie plików z biblioteki do I2C - https://gitlab.com/embedownik/i2c - bazuje tutaj na commicie d0514c752c185c2dea4203cc06e4ab9044fbe023 - aczkolwiek do pokazania idei i komend nie jest to ważne.
Załóżmy, że krytyczną poprawką jest zmiana w pliku I2C.c linia 123 - zmienna “size” na “size + 1U”.
Po wprowadzeniu takiej modyfikacji Git diff pokaże taki wynik:
$ git diff
diff --git a/I2C/src/I2C.c b/I2C/src/I2C.c
index 7f88852..111c6e1 100644
--- a/I2C/src/I2C.c
+++ b/I2C/src/I2C.c
@@ -120,7 +120,7 @@ bool I2C_writeBytes(uint8_t devAddress, uint8_t regAddress, const uint8_t *value
/* because of lack of consts ST HAL library :/ */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers"
- HAL_StatusTypeDef comStatus = HAL_I2C_Mem_Write(I2CHandler, devAddress, regAddress, I2C_MEMADD_SIZE_8BIT, values, size, calculateTimeout(size));
+ HAL_StatusTypeDef comStatus = HAL_I2C_Mem_Write(I2CHandler, devAddress, regAddress, I2C_MEMADD_SIZE_8BIT, values, size + 1U, calculateTimeout(size));
#pragma GCC diagnostic pop
if(comStatus != HAL_OK)
Offtop - nawet domyślnie w Jekyllu jest dostępne kolorowanie składni dla typu “diff” - nie wiedziałem wcześniej, a nawet czytelnie to wygląda na blogu.
W outpucie komendy widzimy jaka linijka ma być usunięta, jaka dodana i w jakim “otoczeniu” się to dzieje. To co widzimy jest już w odpowiednim formacie pliku *.patch - więc aby go utworzyć wystarczy przekierować output komendy do pliku zwykłym:
git diff > naszPatch.patch
Wcześniej użyłem pojęcia “aplikowanie” patcha - kalka językowa z komendy GITa która do tego służy - ponieważ jest to:
git apply <file>
dla sprawdzenia możemy zrobić tak:
git diff > patch.patch
git stash
git apply patch.patch
git diff
W ten sposób pliku I2C.c została wprowadzona wcześniejsza zmiana (którą “skasowaliśmy” przez “git stash”).
Jeszcze jedna przydatna rzecz - wydobywanie zmian z określonego commita jako patch - służy do tego komenda:
git format-patch -1 <commit_sha>
z poprzedniego commita to komenda:
git format-patch -1 HEAD
Warto zaznaczyć, że jeśli poruszamy się w obrębie jednego repozytorium - lepszą opcją dla patcha z pojedynczego commita jest “cherry-pick”.
Tak możemy tworzyć patche z wielu commitów:
git diff <commit2_sha> <commit1_sha> > mypatch.patch
Istnieje też możliwość “negacji” patcha - czyli po jego zaaplikowaniu i poprawieniu czegoś w kodzie możemy skasować tylko zmiany z tego patcha. Służy do tego komenda:
git apply -R <patch>
I tutaj też przykład - kiedy nie możemy trzymać w źródłach projektu kodu SSV - patche umożliwiają jego łatwe dodanie i usunięcie z projektu.
Problemy z aplikacją patcha
Czasem gdy projekt się rozrośnie/wystąpi bardzo dużo zmian - wcześniej działający patch może nie zadziałać.
Jeśli zmiany są dość prymitywne - dotyczą spacji itp - można posłużyć się komendą:
git apply --ignore-space-change --ignore-whitespace mychanges.patch
W przypadku większych zmian można spróbować podejścia z 3way mergem - mechanizm jak przy merge confliktach - standard przy pracy z GITem:
git apply --3way patchFile.patch
lub w skrócie:
git apply -3 patchFile.patch
Pliki z problemami pojawią się w git status jako “Unmerged paths”. Należy je ręcznie zmergować przed kolejnymi operacjami.
Niestety/stety mogą się zdarzyć takie przypadki kiedy automatyczna aplikacja patch będzie całkowicie niemożliwa z powodu zbyt wielu zmian.