Git, quilt i przeszukiwanie binarne

Do zarządzania drzewami deweloperzy używają dwóch podstawowych narzędzi git i quilt. Można też używać kilku nakładek (Cogito, Stacked GIT, Patchy Git (pg), (h)gct), jednak skupię się na opisaniu dwóch pierwszych narzędzi, ponieważ zasadniczo różnią się sposobem działania i podejściem do zarządzania drzewem.

4.1 Git

Git jest systemem kontroli wersji napisanym przez Linusa Torvaldsa na potrzeby jądra Linux (aktualnym opiekunem git jest Junio C. Hamano). Instalacja sprowadza się do zainstalowania pakietu git-core z naszej ulubionej dystrybucji lub pobrania źródeł http://www.kernel.org/pub/software/scm/git/ i samodzielnej kompilacji.

(Przed przystąpieniem do klonowania jakiegoś drzewa dobrze się nad tym zastanów - czy będzie Ci ono potrzebne? Jeżeli nie planujesz prowadzenia intensywnych testów, to może lepiej korzystać ze zwykłych łatek? Aktualnie drzewo linux-2.6 zajmuje trochę ponad 600 MB, ze względu na duże obciążenie kernel.org klonowanie będzie trwać bardzo długo…)

Pierwszym naszym krokiem po zainstalowaniu git’a powinno być sklonowanie jakiegoś repozytorium np. tego którym zarządza Linus
$ git-clone git:git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git linux-git
Klonowanie repozytorium może trwać bardzo długo - zależy od szybkości naszego połączenia internetowego. Po przejściu do katalogu linux-git wykonujemy polecenie
$ git-checkout -f
Teraz mamy już dostęp do aktualnego drzewa „surowego”. Gdy chcemy uaktualnić nasze lokalne repozytorium wpisujemy
$ git-pull git:git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
a następnie
$ git-checkout
Uwaga! Gdy chcemy uaktualnić lokalne repozytorium, to nie powinniśmy wykonywać tego w następujący sposób
$ git-clone $drzewo $katalog
$ rm -rf $katalog
$ git-clone $drzewo $katalog

Do uaktualniania repozytorium należy używać programu git-pull! Klonując całe drzewo marnujemy cenne zasoby łącza kernel.org (piszę o tym tylko dlatego, ponieważ widziałem, że niektórzy ludzie tak robią).
Dodanie flagi „-f” do git-checkout cofnie wszystkie zmiany, jakie dokonaliśmy w plikach źródłowych. Najczęściej używane przeze mnie polecenia git to:
git-whatchanged plik - pokazuje wszystkie zmiany, jakie zostały wprowadzone w danym pliku
git-bisect * - przeszukiwanie binarne repozytorium
git-pull * - pobiera najnowszą wersję drzewa
git-revert * - odwraca commit (łatkę)
gitk - graficzny program do wizualizacji drzew
Bardzo użytecznym programem jest git-log - pokazuje on listę zmian wprowadzonych w drzewie. Przeważnie będziemy go używać w następujący sposób:
$ git log
Możemy też wyświetlić wszystkie zmiany jakie zaszły od wersji 2.6.19
$ git log v2.6.19..
lub przez ostatnie siedem dni
$ git log since=7 days ago
Za pomocą git-show wyświetlimy interesujący nas commit np.
$ git-show b5bf28cde894b3bb3bd25c13a7647020562f9ea0
Warto też zwrócić uwagę na to, że nie musimy wpisywać/wklejać całej nazwy obiektu - czasami wystarczy pierwszych kilka znaków. Ten sam efekt uzyskamy więc po wydaniu polecenia
$ git-show b5bf28
Bardzo przydatnym zastosowaniem git-show jest
$ git-show b5bf28 > latka.patch
W ten oto prosty sposób wyciągamy konkretną łatkę z drzewa.
Do transferu obiektów powinniśmy używać protokołu git: - jest on przeznaczony specjalnie do tego celu. Jednak w pewnych sytuacjach (np. gdy jesteśmy za firewallem, którego nie możemy skonfigurować) możemy użyć zwykłego protokołu http: - nie jest to jednak polecane rozwiązanie.
Sporą kolekcję różnych drzew można znaleźć pod adresami http://git.kernel.org/ oraz http://git.infradead.org/ Więcej informacji o git można uzyskać pod adresem http://git.or.cz/

4.2 Quilt

Quilt jest narzędziem służącym do zarządzania seriami łatek, najczęściej taka seria aplikuje się na konkretną wersję drzewa Linusa np. 2.6.18-rc4. Możemy go zainstalować z paczki dostarczonej z dystrybucją lub pobrać ze strony projektu https://savannah.nongnu.org/projects/quilt/ . Najszybszym sposobem na nałożenie łatek jest skopiowanie ich do katalogu patches w katalogu ze źródłami jądra, następnie kopiujemy plik series do folderu głównego.
Przykładowy folder patches może zawierać cztery pliki:

łatka01.patch
łatka02.patch
łatka03.patch
łatka04.patch

W pliku series mamy łatki poukładane w tej samej kolejności. Aby nałożyć całą serię musimy wykonać polecenie „quilt push -a” (ew. możemy podać nazwę łatki lub jej numer):
$ quilt push -a
$ quilt push łatka04.patch
$ quilt push 4

Jeżeli wszystko będzie w porządku, to zobaczymy serię komunikatów:

Applying patch patches/łatka01.patch
patching file include/linux/kernel.h
patching file include/linux/memleak.h
patching file init/main.c
Applying patch patches/łatka02.patch
patching file arch/i386/kernel/vmlinux.lds.S
patching file include/asm-i386/processor.h
Applying patch patches/łatka03.patch
patching file kernel/fork.c
Applying patch patches/łatka04.patch
patching file kernel/delayacct.c
Now at patch patches/łatka04.patch

Gdy chcemy wyrzucić wszystkie łatki wystarczy wykonać „quilt pop -a”. Możemy wyrzucić też dwie ostatnie łatki wpisując:
$ quilt pop łatka02.patch
Wyrzucamy wszystkie łatki zaaplikowane po pliku „łatka02.patch”. Dzięki temu możemy w prosty sposób przeprowadzić przeszukiwanie binarne o którym za chwilę napiszę. Zarządzanie łatkami za pomocą quilt odbywa się na zasadzie stosu FIFO, czyli pierwsza łatka wymieniona w pliku series jest aplikowana jako pierwsza. Gdy chcemy dodać do drzewa naszą łatkę, powinniśmy napisać ją w oparciu o całe drzewo (wszystkie łatki zaaplikowane), a następnie dopisać ją jako ostatnią do pliku series. Ma to duże znaczenie - szczególnie przy bardzo dużych drzewach, jak -mm - ponieważ jeden plik może być zmieniany przez większą ilość łatek. Powinny być one wykonywane addytywnie.

4.3 Przeszukiwanie binarne - idea

Wyobraźmy sobie następującą sytuację - znaleźliśmy błąd, ale nie mamy zielonego pojęcia która z ponad tysiąca łatek wchodzących w skład drzewa go wywołuje. Wyrzucanie łatek po kolei byłoby mało rozsądne, dlatego stosujemy przeszukiwanie binarne. Na czym to polega?
Mamy serię dziesięciu łatek:

łatka01.patch
łatka02.patch
[..]
łatka10.patch

Wiemy, że po nałożeniu wszystkich otrzymujemy błędnie działający system. Dlatego najpierw nakładamy pierwsze pięć i przeprowadzamy test. Jeżeli błąd występuje nadal, to musi wywoływać go jedna z tych pięciu łatek. Odwracamy dwie ostatnie łatki (zostają trzy pierwsze) i przeprowadzamy test. Okazało się, że błąd już nie występuje, należy wyciągnąć z tego wniosek, że wywołała go czwarta lub piąta łatka. Aplikujemy czwartą łatkę. Jeżeli wystąpił błąd, to spowodowała go czwarta łatka, w przeciwnym wypadku naszym podejrzanym zostaje łatka numer pięć.

Powyższy przykład ilustruje idee przeszukiwania binarnego, jednak kiepsko obrazuje jego skuteczność. Jest ona bardzo wysoka, ponieważ za każdym „przebiegiem” odrzucamy połowę łatek jakie zostały nam do przetestowania - przy grubo ponad tysiącu łatkach (standardowy rozmiar drzewa -mm) pierwszy „przebieg” odrzuca ponad pięćset łatek! Przeszukiwanie binarne jest algorytmem klasy O(log2N), więc w łatwy sposób możemy obliczyć (najlepiej za pomocą kalkulatora :) ile razy będziemy musieli przebudować system, aby znaleźć łatkę wprowadzającą błąd.

4.4 Przeszukiwanie binarne z pomocą quilt

Plik series zawiera spis następujących łatek:

01-kmemleak-base.patch
02-kmemleak-doc.patch
03-kmemleak-hooks.patch
04-kmemleak-modules.patch
05-kmemleak-i386.patch
06-kmemleak-arm.patch
07-kmemleak-false-positives.patch
08-kmemleak-keep-init.patch
09-kmemleak-test.patch
10-kmemleak-maintainers.patch
#11-new-locking.patch
#12-new-locking-fix.patch
13-vt-memleak-fix.patch
14-new-locking-fix.patch
15-fix-for-possible-leak-in-delayacctc.patch

Niestety nie możemy zbudować jądra po nałożeniu wszystkich, więc przeprowadzamy przeszukiwanie binarne.

Gdy mamy tylko piętnaście łatek to łatwiej jest sprawdzić, która z nich modyfikuje niekompilujący się plik - „quilt patches jakiś_plik.c”. Jednak w przypadku dużych drzew bardzo często nie będziemy mogli wyrzucić łatek znajdujących się „w środku”.

A wiec zaczęliśmy od zaaplikowania wszystkich łatek

$ quilt push -a
Applying patch patches/01-kmemleak-base.patch
patching file include/linux/kernel.h
[..]
patching file kernel/delayacct.c
Now at patch patches/15-fix-for-possible-leak-in-delayacctc.patch

jednak coś poszło nie tak jak miało i nie możemy skompilować systemu. Kopiujemy plik series np. do katalogu domowego i otwieramy w ulubionym edytorze tekstu, który wyświetla numer linii w której znajduje się kursor. Wybieramy łatkę, która znajduje się po środku pomiędzy
01-kmemleak-base.patch
a
15-fix-for-possible-leak-in-delayacctc.patch
wypadło na
07-kmemleak-false-positives.patch
Wycofujemy łatki znajdujące się po 07-kmemleak-false-positives.patch.

$ quilt pop 07-kmemleak-false-positives.patch
Removing patch patches/15-fix-for-possible-leak-in-delayacctc.patch
Restoring include/linux/delayacct.h
[..]
Restoring lib/Kconfig.debug
Now at patch patches/07-kmemleak-false-positives.patch

Błąd występuje nadal? Wpisujemy sobie do kopi pliku series „ZŁA” za siódmą poprawką i ponownie wybieramy łatkę. Nasz wybór padł na 04-kmemleak-modules.patch
$ quilt pop 04-kmemleak-modules.patch
Błąd zniknął? Wpisujemy za czwartą poprawką „DOBRA”, zostały już tylko trzy
$ quilt push 06-kmemleak-arm.patch
i tak dalej, aż dojedziemy do

06-kmemleak-arm.patch
DOBRA
07-kmemleak-false-positives.patch
ZŁA

W „How to perform bisection searches on -mm treeshttp://www.zip.com.au/~akpm/linux/patches/stuff/bisecting-mm-trees.txt Andrew Morton radzi aby podczas przeszukiwania binarnego w drzewie -mm nie dzielić łatek

łatka-bla.patch
łatka-bla-bla.patch
łatka-bla-bla-fix1.patch
łatka-bla-bla-fix2.patch
łatka-bla-bla-fix3.patch

zaaplikujmy wszystkie, lub nie aplikujmy żadnej. Poniższy filmik pokazuje praktyczny przykład zastosowania wyszukiwania binarnego za pomocą quilt http://www.youtube.com/watch?v=LS_hTnBDIYk

4.5 Przeszukiwanie binarne z pomocą git-bisect

Git-bisect jest moim ulubionym narzędziem do przeszukiwania binarnego - prawdziwy killerapp. W 2.6.18-rc5 napotkaliśmy mały problem i nie bardzo wiemy który commit go spowodował, wiemy jednak że wersja 2.6.18-rc4 działała dobrze. Zaczynamy nasze poszukiwania:
$ git-bisect start
Oznaczamy aktualną wersję jako złą
$ git-bisect bad
Wersję 2.6.18-rc4 (dzięki gitk dowiemy się, że jest to commit 9f737633e6ee54fc174282d49b2559bd2208391d) oznaczymy jako dobrą:
$ git-bisect good 9f737633e6ee54fc174282d49b2559bd2208391d
Teraz git nam wybierze wersję, która znajduje się pomiędzy nimi. Wybrał:

Bisecting: 202 revisions left to test after this
[c5ab964debe92d0ec7af330f350a3433c1b5b61e] spectrum_cs: Fix firmware
uploading errors

Możemy sobie zwizualizować drzewo poleceniem „git-bisect visualize”. Następnie budujemy i testujemy system. Powiedzmy, że błąd nadal występuje, dlatego oznaczamy aktualną wersję jako złą git-bisect wybierze nam nowego kandydata.

$ git-bisect bad
Bisecting: 101 revisions left to test after this
[1d7ea7324ae7a59f8e17e4ba76a2707c1e6f24d2] fuse: fix error case in
fuse_readpages

Budujemy, testujemy itd. Okazało się, że błąd już nie występuje, więc oznaczamy tę wersję jako dobrą:

git-bisect good
Bisecting: 55 revisions left to test after this
[a4657141091c2f975fa35ac1ad28fffdd756091e]
Merge gregkh@master.kernel.org:/pub/scm/linux/kernel/git/davem/net-2.6

Cały proces kontynuujemy do chwili otrzymania komunikatu podobnego do poniższego

$ git-bisect good
1d7ea7324ae7a59f8e17e4ba76a2707c1e6f24d2 is first bad commit
commit 1d7ea7324ae7a59f8e17e4ba76a2707c1e6f24d2
Author: Jan Kowalski <a@b>
Date:      Sun Aug 13 23:24:27 2006 -0700
     [PATCH] fuse: fix error case in fuse_readpages
     Don’t let fuse_readpages leave the @pages list not empty when exiting
     on error.
[..]

Jest w nim podany numer commitu wprowadzającego nasz błąd. Aby się upewnić, że wszystko działa poprawnie bez tej łatki musimy zresetować git-bisect (powrócimy do wersji 2.6.18-rc5) i wyrzucić zły commit.
$ git-bisect reset
$ git-revert 1d7ea7324ae7a59f8e17e4ba76a2707c1e6f24d2

Podczas przeszukiwania binarnego za pomocą git-bisect najczęściej będziemy korzystać z poniższych poleceń:
git-bisect start - rozpoczyna proces przeszukiwania
git-bisect reset - kończy proces
git-bisect good <commit> - oznacza aktualną wersję jako dobrą, możemy podać jako parametr commit
git-bisect bad <commit> - oznacza aktualną wersję jako złą, możemy podać jako parametr commit
git-bisect visualize - włącza „gitk” i pokazuje poprawki pomiędzy ostatnią dobrą i złą wersją
Polecam też przeczytanie „man git-bisect” - są w nim opisane dodatkowe opcje.
Poniższy filmik pokazuje praktyczny przykład zastosowania wyszukiwania binarnego za pomocą git-bisect http://www.youtube.com/watch?v=R7_LY-ceFbE

4.6 Uwaga na „make oldconfig”

Podczas prowadzenia przeszukiwania binarnego trzeba pamiętać o pewnej bardzo ważnej rzeczy - nie należy używać „make oldconfig”! Wiąże się to z tym, że wraz z nowymi łatkami dochodzą nowe opcje (czasami też znikają) - gdy np. cofniemy się o pięćdziesiąt łatek i stwierdzimy, że dana wersja działa dobrze, to nakładamy kolejne dwadzieścia pięć łatek. Teraz możemy nie pamiętać o tym, że opcja XY była wcześniej włączona - jeśli nie włączymy jej ponownie, to błąd może się już nie pojawić… To bardzo często popełniany błąd, podczas przeszukiwania binarnego, dlatego warto o tym pamiętać.

Najlepiej jest więc, skopiować nasz plik konfiguracyjny jądra do innego katalogu przed roz- poczęciem przeszukiwania binarnego i za każdym razem przed kompilacją kopiować go do katalogu ze źródłami jądra. Takie postępowanie powinno nas uchronić przed popełnieniem błędu.

O ile nie zaznaczono inaczej, treść tej strony objęta jest licencją Creative Commons Attribution-Share Alike 2.5 License.