Dzisiaj o ciekawym mechanizmie wbudowanym bezpośrednio w gita – git hooks. Jest to mechanizm pozwalający na wykonywanie określonych, oskryptowanych wcześniej zadań podczas wykonywania podstawowych operacji występujących w tym systemie kontroli wersji. Możemy wykonywać automatyczne akcje np. przed stworzeniem commita albo wypchnięciem zmian na serwer.
Jednym z moich ulubionych przykładów na użycie hooków po stronie klienta (są dozwolone także akcje, które odbywają się po stronie serwera – po więcej informacji zapraszam do oficjalnej dokumentacji) jest sprawdzenie zmienionych plików code snifferem i w razie niepoprawnego formatowania - odrzucenie takiego commita.
Przygotowanie środowiska
Stwórzmy sobie pusty projekt. Zainicjujemy w nim puste repozytorium git:
git init
następnie dodajmy plik composer.json z chęcią załadowania PHP Code Sniffera:
{
"name": "bartl/git-hooks",
"authors": [
{
"name": "Bartłomiej Romanek",
"email": "b.romanek@example.com"
}
],
"require": {},
"require-dev": {
"squizlabs/php_codesniffer": "^3.5"
}
}
Dodatkowo stwórzmy plik wykonywalny, który NIE PRZECHODZI statycznej analizy kodu, może to być plik index.php:
<?php
class A {
public function __construct() {
}
}
Następnie uruchamiamy wąchacza i sprawdzamy, jak się zachowa:
./vendor/bin/phpcs --standard=PSR12 index.php
Otrzymujemy wynik analizy - negatywny – i to jest w porządku:
FILE: /var/www/html/index.php
----------------------------------------------------------------------------------------------------------------------
FOUND 4 ERRORS AFFECTING 3 LINES
----------------------------------------------------------------------------------------------------------------------
3 | ERROR | [ ] Each class must be in a namespace of at least one level (a top-level vendor name)
3 | ERROR | [x] Opening brace of a class must be on the line after the definition
4 | ERROR | [x] Opening brace should be on a new line
6 | ERROR | [x] Function closing brace must go on the next line following the body; found 1 blank lines before brace
----------------------------------------------------------------------------------------------------------------------
PHPCBF CAN FIX THE 3 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------------------------------------------------------
Time: 1.53 secs; Memory: 6MB
Stworzenie git hooka
Hooki trzymane są domyślnie w katalogu .git/hooks czyli tam, gdzie znajduje się nasz projekt. Katalog ten jest ukryty. Jeżeli do niego przejdziesz, to zobaczysz tam kilka plików:
root@b9581e100681:/var/www/html/.git/hooks# ls -l
total 32
-rwxrwxrwx 1 root root 478 Jul 28 18:35 applypatch-msg.sample
-rwxrwxrwx 1 root root 896 Jul 28 18:35 commit-msg.sample
-rwxrwxrwx 1 root root 3327 Jul 28 18:35 fsmonitor-watchman.sample
-rwxrwxrwx 1 root root 189 Jul 28 18:35 post-update.sample
-rwxrwxrwx 1 root root 424 Jul 28 18:35 pre-applypatch.sample
-rwxrwxrwx 1 root root 1642 Jul 28 18:35 pre-commit.sample
-rwxrwxrwx 1 root root 1348 Jul 28 18:35 pre-push.sample
-rwxrwxrwx 1 root root 4898 Jul 28 18:35 pre-rebase.sample
-rwxrwxrwx 1 root root 544 Jul 28 18:35 pre-receive.sample
-rwxrwxrwx 1 root root 1492 Jul 28 18:35 prepare-commit-msg.sample
-rwxrwxrwx 1 root root 3610 Jul 28 18:35 update.sample
Są to przykładowe skrypty, które możesz wykorzystać do przygotowania własnych zadań. Nas interesuje akcja wykonywana przed commitem, dlatego otwórzmy sobie plik pre-commit.sample. Na samej górze widzimy opis hooka oraz parametry, jakie on przyjmuje (w tym przypadku nie przyjmuje żadnego argumentu).
Skrypty napisane są w bashu. Bash jaki jest, każdy widzi. Sam nie za bardzo za nim przepadam 🙂. Nie stoi nic na przeszkodzie, aby pisać zadania w innym języku skryptowym, np. naszym ulubionym PHP. Stwórzmy nowy plik o nazwie pre-push (plik bez rozszerzenia – dopiero wtedy taki skrypt zostanie rozpoznany jako git hook):
#!/bin/php
<?php
declare(strict_types=1);
system("php vendor/bin/phpcs --standard=PSR12 index.php", $returnCode);
if ($returnCode !== 0) {
echo PHP_EOL . 'PHPCS verification failed!' . PHP_EOL;
}
exit ($returnCode);
Próbujemy zrobić commit. W wyniku tego otrzymujemy:
root@b9581e100681:/var/www/html/# git commit -am "first commit"
FILE: /var/www/html/index.php
----------------------------------------------------------------------
FOUND 4 ERRORS AFFECTING 3 LINES
----------------------------------------------------------------------
3 | ERROR | [ ] Each class must be in a namespace of at least one
| | level (a top-level vendor name)
3 | ERROR | [x] Opening brace of a class must be on the line after
| | the definition
4 | ERROR | [x] Opening brace should be on a new line
6 | ERROR | [x] Function closing brace must go on the next line
| | following the body; found 1 blank lines before brace
----------------------------------------------------------------------
PHPCBF CAN FIX THE 3 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------
Time: 113ms; Memory: 6MB
PHPCS verification failed!
Zmiany nie zostały zapisane! Poprawmy teraz błędy w pliku index.php:
<?php
declare(strict_types=1);
namespace N;
class A
{
public function __construct()
{
}
}
I znowu commit... tym razem przeszło bez problemu 😀.
Operowanie na dynamicznych wartościach
Powyższy przykład działa, ale tylko chwilowo. Wynika to z tego, że na sztywno sprawdzany jest plik index.php. A co w przypadku modyfikowania innych plików? Można to rozwiązać np. pobierając zmiany za pomocą git diff:
#!/bin/php
<?php
declare(strict_types=1);
exec("git diff --name-only", $files);
$filesJoined = implode (" ", $files);
system("php vendor/bin/phpcs --standard=PSR12 $filesJoined", $returnCode);
if ($returnCode !== 0) {
echo PHP_EOL . 'PHPCS verification failed!' . PHP_EOL;
}
exit ($returnCode);
Na koniec kilka ciekawych kwestii:
- git hooki nie są przesyłane na serwer podczas pusha; dlatego każdy programista w zespole musi je u siebie ręcznie skonfigurować;
- jeżeli hook otrzymuje jakieś dodatkowe parametry (np. pre-push otrzymuje nazwę remote), to w skrypcie można się do nich odwołać tak jak do każdego parametru z linii poleceń - w skryptach PHP będzie to możliwe za pomocą tablicy $argv;
- git hook zatrzymuje wykonywanie akcji, jeżeli exit code skryptu będzie inny niż 0 (oznacza to błąd wykonywania skryptu);
- w nagłówku zamiast #!/bin/php można dać #!/usr/bin/env php.
Co uważacie o mechanizmie git hooks? Fajnie się dowiedzieć jeszcze przed commitem, że nie spełniamy jakichś założeń, prawda? To o wiele lepsza sytuacja, niż dowiedzieć się tego samego z czerwonego pipeline w CI 😊.
Piotr R pseudonim ppg-świeżak 2020-08-12 03:08
Tego mi brakowało :D dzięki