Close

Połączenia Git

Hooki Git to skrypty uruchamiane automatycznie za każdym razem, gdy w repozytorium Git wystąpi konkretne zdarzenie. Pozwalają one dostosować wewnętrzny sposób działania Git i wyzwalać niestandardowe czynności w kluczowych momentach cyklu życia tworzenia oprogramowania.

Wykonywanie hooków w trakcie procesu tworzenia commita

Typowe zastosowania hooków Git obejmują promowanie stosowania zasad dotyczących commitów, modyfikacje środowiska projektowego w zależności od stanu repozytorium i wdrażanie przepływów pracy ciągłej integracji. Z uwagi na brak ograniczeń co do możliwości dostosowywania skryptów hooki Git można wykorzystać do automatyzacji lub optymalizacji niemal każdego aspektu przepływu prac programistycznych.

Niniejszy artykuł rozpoczniemy od przeglądu zasady działania hooków Git. Następnie omówimy najpopularniejsze hooki stosowane zarówno w repozytoriach lokalnych, jak i po stronie serwera.


Przegląd koncepcji


Wszystkie hooki Git to zwykłe skrypty wykonywane przez system Git po wystąpieniu określonych zdarzeń w repozytorium. Dzięki temu bardzo łatwo je zainstalować i skonfigurować.

Hooki mogą się znajdować w repozytoriach lokalnych lub po stronie serwera i są wykonywane wyłącznie w odpowiedzi na czynności, jakie mają miejsce w danym repozytorium. W dalszej części niniejszego artykułu przyjrzymy się kategoriom hooków. Konfiguracja omówiona dalej w tej części dotyczy zarówno hooków lokalnych, jak i serwerowych.

Instalowanie hooków

Hooki znajdują się w katalogu .git/hooks każdego repozytorium Git. Podczas inicjowania repozytorium Git automatycznie zapełnia ten katalog przykładowymi skryptami. W katalogu .git/hooks można znaleźć następujące pliki:

applypatch-msg.sample       pre-push.sample
commit-msg.sample           pre-rebase.sample
post-update.sample          prepare-commit-msg.sample
pre-applypatch.sample       update.sample
pre-commit.sample
Bazy danych
materiały pokrewne

Jak przenieść pełne repozytorium Git

Logo Bitbucket
POZNAJ ROZWIĄZANIE

Poznaj środowisko Git z rozwiązaniem Bitbucket Cloud

Reprezentują one większość dostępnych hooków, ale rozszerzenie .sample sprawia, że nie są one wykonywane domyślnie. Aby „zainstalować” hook, wystarczy usunąć rozszerzenie .sample. Natomiast w przypadku napisania nowego skryptu od podstaw można po prostu dodać nowy plik zgodny z jedną z powyższych nazw plików, pomijając rozszerzenie .sample.

Spróbuj na przykład zainstalować prosty hook prepare-commit-msg. Usuń rozszerzenie .sample z tego skryptu i dodaj następujący fragment do pliku:

#!/bin/sh

echo "# Please include a useful commit message!" > $1

Hooki muszą być wykonywalne, dlatego w przypadku tworzenia ich od zera konieczna może być zmiana uprawnień do pliku skryptu. Przykładowo, aby upewnić się, że hook prepare-commit-msg jest plikiem wykonywalnym, należy uruchomić następujące polecenie:

chmod +x prepare-commit-msg

Teraz po każdym uruchomieniu polecenia git commit zamiast domyślnego komunikatu dotyczącego commita, powinien się wyświetlać ten komunikat. Przyjrzymy się, jak to faktycznie działa w sekcji Przygotowanie komunikatu dotyczącego commita. Zacznijmy jednak od tego, że możemy dostosować niektóre wewnętrzne funkcje Git.

Wbudowane przykładowe skrypty są bardzo przydatnym odniesieniem, ponieważ dokumentują parametry przekazywane do każdego hooka (różnią się one w zależności od poszczególnych hooków).

Języki skryptowe

Wbudowane skrypty to głównie skrypty powłoki i PERL, ale można używać dowolnego języka skryptowego, który da się uruchomić jako plik wykonywalny. Shebang (#!/bin/sh) w każdym skrypcie określa sposób interpretacji pliku. W związku z tym, aby użyć innego języka, wystarczy zmienić shebang na ścieżkę swojego interpretera.

Przykładowo zamiast używania poleceń powłoki możemy napisać wykonywalny skrypt w języku Python w pliku prepare-commit-msg. Przedstawiony poniżej hook będzie działał tak samo, jak skrypt powłoki w poprzedniej sekcji.

#!/usr/bin/env python

import sys, os

commit_msg_filepath = sys.argv[1]
with open(commit_msg_filepath, 'w') as f:
    f.write("# Please include a useful commit message!")

Zwróć uwagę na zmianę pierwszego wiersza wskazującą na interpreter języka Python. Zamiast użycia kodu $1 w celu uzyskania dostępu do pierwszego argumentu przekazywanego do skryptu użyliśmy kodu sys.argv[1] (więcej informacji na ten temat za chwilę).

Jest to bardzo ważna funkcja hooków Git, ponieważ pozwala na pracę w języku, który najbardziej Ci odpowiada.

Zakres hooków

Hooki są skryptami lokalnymi każdego danego repozytorium Git i nie są kopiowane do nowego repozytorium po uruchomieniu polecenia git clone. Z uwagi na lokalny charakter hooków każdy, kto ma dostęp do repozytorium, może je modyfikować.

Ma to istotny wpływ na konfigurowanie hooków dla zespołu programistów. Po pierwsze trzeba znaleźć sposób zapewnienia, że wszyscy członkowie zespołu dysponują aktualnymi hookami. Po drugie nie można zmusić programistów do tworzenia commitów, które będą wyglądały w określony sposób — można ich do tego jedynie zachęcać.

Utrzymywanie hooków na potrzeby zespołu programistów może stwarzać pewne trudności, ponieważ katalog .git/hooks nie jest klonowany wraz z resztą projektu ani nie podlega kontroli wersji. Prostym rozwiązaniem obu tych problemów jest przechowywanie hooków w faktycznym katalogu projektu (powyżej katalogu .git). W ten sposób można je edytować tak, jak każdy inny plik objęty kontrolą wersji. Aby zainstalować hook, można utworzyć symlink do niego w katalogu .git/hooks lub po prostu skopiować i wkleić go do katalogu .git/hooks po każdej aktualizacji hooka.

Wykonywanie hooków w trakcie procesu tworzenia commita

Alternatywnie system Git oferuje również mechanizm katalogu szablonów, który ułatwia automatyczne instalowanie hooków. Wszystkie pliki i katalogi zawarte w takim katalogu szablonów są kopiowane do katalogu .git przy każdym użyciu polecenia git init lub git clone.

Właściciel repozytorium może zmodyfikować lub całkowicie odinstalować wszystkie hooki lokalne opisane poniżej. Tylko od poszczególnych członków zespołu zależy, czy będą faktycznie korzystać z hooków. Mając to na uwadze, najlepiej traktować hooki Git jako wygodne narzędzie deweloperskie, a nie ściśle egzekwowane zasady programistyczne.

W związku z tym można odrzucać commity niezgodne z pewnym standardem przy użyciu hooków serwerowych. Więcej na ten temat można znaleźć w dalszej części artykułu.

Lokalne skrypty hook


Hooki lokalne mają wpływ wyłącznie na repozytorium, w którym się znajdują. Zapoznając się z treścią niniejszej części, należy pamiętać, że każdy programista może modyfikować swoje hooki lokalne, dlatego nie można ich używać do egzekwowania zasad dotyczących commitów. Można jednak wykorzystać je do ułatwienia programistom trzymania się określonych wytycznych. W tej części zapoznamy się z 6 najbardziej przydatnymi hookami lokalnymi:

  • pre-commit
  • prepare-commit-msg
  • commit-msg
  • post-commit
  • post-checkout
  • pre-rebase

Za pomocą 4 pierwszych hooków można podłączyć się do całego cyklu życia commita, natomiast 2 ostatnie umożliwiają wykonanie dodatkowych czynności lub kontroli bezpieczeństwa odpowiednio dla poleceń git checkout i git rebase.

Wszystkie hooki z przedrostkiem pre- pozwalają zmodyfikować czynność, która ma być wykonana, natomiast hooki z przedrostkiem post- służą wyłącznie do powiadomień.

Zapoznamy się również z przydatnymi technikami analizowania argumentów hooków i wysyłania wniosków o informacje na temat repozytorium przy użyciu poleceń Git niższego poziomu.

Pre-Commit

Skrypt pre-commit jest wykonywany przy każdym uruchomieniu polecenia git commit, zanim Git poprosi programistę o komunikat dotyczący commita lub wygeneruje commit. Za pomocą tego hooka można sprawdzić migawkę, która ma zostać zatwierdzona. Przykładowo można uruchomić automatyczne testy, które pozwolą sprawdzić, czy commit nie spowoduje uszkodzenia jakiejś istniejącej funkcji.

Do skryptu pre-commit nie są przekazywane żadne argumenty, a zakończenie ze statusem niezerowym powoduje przerwanie całego commita. Przyjrzyjmy się uproszczonej (i oferującej pełniejsze informacje wyjściowe) wersji wbudowanego hooka pre-commit. Ten skrypt powoduje przerwanie commita w przypadku znalezienia błędnych spacji zgodnie z definicją zawartą w poleceniu git diff-index (domyślnie za błędy uważa się spacje na początku wiersza, wiersze zawierające same spacje oraz spację, po której występuje znak tabulacji w obrębie wcięcia początkowego wiersza).

#!/bin/sh

# Check if this is the initial commit
if git rev-parse --verify HEAD >/dev/null 2>&1
then
    echo "pre-commit: About to create a new commit..."
    against=HEAD
else
    echo "pre-commit: About to create the first commit..."
    against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi

# Use git diff-index to check for whitespace errors
echo "pre-commit: Testing for whitespace errors..."
if ! git diff-index --check --cached $against
then
    echo "pre-commit: Aborting commit due to whitespace errors"
    exit 1
else
    echo "pre-commit: No whitespace errors :)"
    exit 0
fi

Aby móc użyć polecenia git diff-index, trzeba ustalić, z którym odniesieniem commita będziemy porównywać indeks. Zazwyczaj jest to wskaźnik HEAD. Jednak podczas tworzenia początkowego commita wskaźnik HEAD nie istnieje, więc pierwszym zadaniem jest uporanie się z tą nietypową sytuacją. Służy do tego polecenie git rev-parse --verify, które po prostu sprawdza, czy argument (HEAD) jest prawidłową referencją. Fragment >/dev/null 2>&1 wycisza wszelkie dane wyjściowe polecenia git rev-parse. Wskaźnik HEAD lub pusty obiekt commita zostaje zapisany w zmiennej against w celu użycia z poleceniem git diff-index. Hash 4b825d... jest magicznym identyfikatorem commita oznaczającym pusty commit.

Polecenie git diff-index --cached porównuje commit z indeksem. Przekazując opcję --check, prosimy o ostrzeżenie, gdy wprowadzone zmiany powodują błędy spacji. Jeśli tak jest, przerywamy commit, zwracając status zakończenia 1. W przeciwnym wypadku skrypt jest kończony ze statusem 0, a przepływ pracy commita jest normalnie kontynuowany.

Jest to tylko jeden przykład użycia hooka pre-commit. Użyto w nim istniejących poleceń Git do przeprowadzania testów zmian wprowadzonych za pomocą proponowanego commita, jednak w ramach hooka pre-commit można wykonać dowolne operacje, takie jak uruchomienie zewnętrznego pakietu do testowania lub sprawdzenie stylu kodu przy użyciu narzędzia Lint.

Przygotowanie komunikatu dotyczącego commita

Hook prepare-commit-msg jest wywoływany po hooku pre-commit w celu wypełnienia edytora tekstu komunikatem dotyczącym commita. Jest to dobry moment na modyfikację automatycznie generowanych komunikatów dotyczących commitów w przypadku łączonych za pomocą polecenia „squash” lub scalanych commitów.

Do skryptu prepare-commit-msg przekazuje się od jednego do trzech argumentów:

1. Nazwę pliku tymczasowego zawierającego komunikat. Komunikat dotyczący commita zmienia się poprzez miejscową modyfikację tego pliku.

2. Typu commita. Może to być komunikat message (opcja -m lub -F), szablon template (opcja -t), scalenie merge (jeśli commit jest scalany) lub połączenie squash (jeśli commit jest łączony z innymi commitami za pomocą polecenia „squash”).

3. Hash SHA1 odpowiedniego commita. Podawany tylko w przypadku użycia opcji -c, -C lub --amend.

Podobnie jak w przypadku skryptu pre-commit zakończenie ze statusem niezerowym powoduje przerwanie commita.

Poznaliśmy już prosty przykład polegający na edycji komunikatu dotyczącego commita. Przyjrzyjmy się teraz bardziej przydatnemu skryptowi. Podczas korzystania z narzędzia do śledzenia zgłoszeń powszechnie stosuje się podejście polegające na obsłudze poszczególnych zgłoszeń w odrębnej gałęzi. Uwzględniając numer zgłoszenia w nazwie gałęzi, można napisać hook prepare-commit-msg w taki sposób, aby automatycznie był on dodawany w każdym komunikacie dotyczącym commita w ramach danej gałęzi.

#!/usr/bin/env python

import sys, os, re
from subprocess import check_output

# Collect the parameters
commit_msg_filepath = sys.argv[1]
if len(sys.argv) > 2:
    commit_type = sys.argv[2]
else:
    commit_type = ''
if len(sys.argv) > 3:
    commit_hash = sys.argv[3]
else:
    commit_hash = ''

print "prepare-commit-msg: File: %s\nType: %s\nHash: %s" % (commit_msg_filepath, commit_type, commit_hash)

# Figure out which branch we're on
branch = check_output(['git', 'symbolic-ref', '--short', 'HEAD']).strip()
print "prepare-commit-msg: On branch '%s'" % branch

# Populate the commit message with the issue #, if there is one
if branch.startswith('issue-'):
    print "prepare-commit-msg: Oh hey, it's an issue branch."
    result = re.match('issue-(.*)', branch)
    issue_number = result.group(1)

    with open(commit_msg_filepath, 'r+') as f:
        content = f.read()
        f.seek(0, 0)
        f.write("ISSUE-%s %s" % (issue_number, content))

Najpierw powyższy hook prepare-commit-msg pokazuje, jak zebrać wszystkie parametry przekazywane do skryptu. Następnie wywołuje polecenie git symbolic-ref --short HEAD w celu pobrania nazwy gałęzi odpowiadającej wskaźnikowi HEAD. Jeśli nazwa takiej gałęzi rozpoczyna się od issue-, ponownie zapisuje zawartość pliku komunikatu dotyczącego commita, aby uwzględnić numer zgłoszenia w pierwszym wierszu. Jeśli więc nazwa gałęzi to issue-224, zostanie wygenerowany poniższy komunikat dotyczący commita.

ISSUE-224 

# Please enter the commit message for your changes. Lines starting 
# with '#' will be ignored, and an empty message aborts the commit. 
# On branch issue-224 
# Changes to be committed: 
#   modified:   test.txt

Jedną z rzeczy, o których należy pamiętać, korzystając z hooka prepare-commit-msg jest fakt, że jest on uruchamiany nawet wtedy, gdy użytkownik przekaże komunikat za pomocą opcji -m polecenia git commit. Oznacza to, że powyższy skrypt automatycznie wstawi ciąg ISSUE-[#], a użytkownik nie będzie mógł go edytować. W takiej sytuacji można sprawdzić, czy 2 parametr (commit_type) jest równy message.

Jednak bez opcji -m sam hook prepare-commit-msg nie daje użytkownikowi możliwości edytowania komunikatu po jego wygenerowaniu, dlatego skrypt ten stosuje się raczej dla wygody, a nie w celu egzekwowania zasad komunikatów dotyczących commitów. Do tego służy hook commit-msg omówiony w kolejnej części.

Komunikat dotyczący commita

Hook commit-msg działa bardzo podobnie, jak hook prepare-commit-msg, jednak jest on wywoływany po wprowadzeniu komunikatu dotyczącego commita przez użytkownika. Jest to dobre miejsce do umieszczania ostrzeżeń dla programistów, że ich komunikat jest niezgodny ze standardami przyjętymi w zespole.

Jedynym argumentem przekazywanym do tego hooka jest nazwa pliku zawierającego dany komunikat. Jeśli uzna on, że komunikat wprowadzony przez użytkownika jest niewłaściwy, może zmienić ten plik miejscowo (jak w przypadku hooka prepare-commit-msg) lub całkowicie przerwać commit, kończąc działanie ze statusem niezerowym.

Przykładowo poniższy skrypt sprawdza, czy użytkownik nie usunął ciągu ISSUE-[#] wygenerowanego automatycznie przez hook prepare-commit-msg w poprzedniej części.

#!/usr/bin/env python

import sys, os, re
from subprocess import check_output

# Collect the parameters
commit_msg_filepath = sys.argv[1]

# Figure out which branch we're on
branch = check_output(['git', 'symbolic-ref', '--short', 'HEAD']).strip()
print "commit-msg: On branch '%s'" % branch

# Check the commit message if we're on an issue branch
if branch.startswith('issue-'):
    print "commit-msg: Oh hey, it's an issue branch."
    result = re.match('issue-(.*)', branch)
    issue_number = result.group(1)
    required_message = "ISSUE-%s" % issue_number

    with open(commit_msg_filepath, 'r') as f:
        content = f.read()
        if not content.startswith(required_message):
            print "commit-msg: ERROR! The commit message must start with '%s'" % required_message
            sys.exit(1)

Mimo że ten skrypt jest wywoływany za każdym razem, gdy użytkownik tworzy commit, należy unikać wykonywania czynności innych niż sprawdzenie komunikatu dotyczącego commita. Aby powiadomić inne usługi o zatwierdzeniu migawki, należy użyć hooka post-commit.

Post-Commit

Hook post-commit jest wywoływany bezpośrednio po hooku commit-msg. Nie może zmienić wyniku operacji git commit, dlatego służy przede wszystkim do powiadomień.

Skrypt nie przyjmuje żadnych parametrów, a jego status zakończenia nie wpływa w żaden sposób na commit. W przypadku większości skryptów post-commit będziesz potrzebować dostępu do commita, który właśnie został utworzony. Możesz użyć polecenia git rev-parse HEAD, aby pobrać hash SHA1 nowego commita, lub polecenia git log -1 HEAD, aby pobrać wszystkie informacje na jego temat.

Przykładowo, jeśli chcesz, aby Twój szef otrzymywał wiadomość e-mail po każdym zatwierdzeniu migawki (co w przypadku większości przepływów pracy raczej nie jest najlepszym pomysłem), możesz dodać poniższy hook post-commit.

#!/usr/bin/env python

import smtplib
from email.mime.text import MIMEText
from subprocess import check_output

# Get the git log --stat entry of the new commit
log = check_output(['git', 'log', '-1', '--stat', 'HEAD'])

# Create a plaintext email message
msg = MIMEText("Look, I'm actually doing some work:\n\n%s" % log)

msg['Subject'] = 'Git post-commit hook notification'
msg['From'] = 'mary@example.com'
msg['To'] = 'boss@example.com'

# Send the message
SMTP_SERVER = 'smtp.example.com'
SMTP_PORT = 587

session = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
session.ehlo()
session.starttls()
session.ehlo()
session.login(msg['From'], 'secretPassword')

session.sendmail(msg['From'], msg['To'], msg.as_string())
session.quit()

Można co prawda użyć hooka post-commit w celu wyzwolenia lokalnego systemu ciągłej integracji, jednak w większości przypadków do realizacji tej czynności posłuży hook post-receive. Jest on uruchamiany na serwerze, a nie na komputerze lokalnym użytkownika. Poza tym jest on uruchamiany także za każdym razem, gdy dowolny programista wypycha swój kod. Dzięki temu znacznie lepiej nadaje się do przeprowadzania ciągłej integracji.

Post-Checkout

Hook post-checkout działa bardzo podobnie, jak hook post-commit, jednak jest on wywoływany po każdym pomyślnym wyewidencjonowaniu referencji za pomocą polecenia git checkout. Jest to przydatne narzędzie do czyszczenia katalogu roboczego z wygenerowanych plików, które w przeciwnym razie powodowałyby zamieszanie.

Ten hook przyjmuje trzy parametry, a jego status zakończenia nie wpływa na polecenie git checkout.

1. Referencja poprzedniego wskaźnika HEAD

2. Referencja nowego wskaźnika HEAD

3. Flaga informująca, czy wyewidencjonowano gałąź czy plik. Flaga będzie miała odpowiednio wartość: 1 i 0.

Typowy problem, jaki napotykają programiści Python, polega na pozostawaniu plików .pyc po przełączeniu gałęzi. Czasami interpreter używa tych plików .pyc zamiast pliku źródłowego .py. Aby uniknąć nieporozumień, możesz usuwać wszystkie pliki .pyc za każdym razem, gdy wyewidencjonujesz nową gałąź, korzystając z następującego skryptu post-checkout:

#!/usr/bin/env python

import sys, os, re
from subprocess import check_output

# Collect the parameters
previous_head = sys.argv[1]
new_head = sys.argv[2]
is_branch_checkout = sys.argv[3]

if is_branch_checkout == "0":
    print "post-checkout: This is a file checkout. Nothing to do."
    sys.exit(0)

print "post-checkout: Deleting all '.pyc' files in working directory"
for root, dirs, files in os.walk('.'):
    for filename in files:
        ext = os.path.splitext(filename)[1]
        if ext == '.pyc':
            os.unlink(os.path.join(root, filename))

Bieżący katalog roboczy dla skryptów hook jest zawsze ustawiony na katalog główny repozytorium, więc wywołanie os.walk('.') jest iterowane po każdym pliku w repozytorium. Następnie sprawdzamy rozszerzenie pliku, a jeśli to .pyc, usuwamy plik.

Można również za pomocą hooka post-checkout zmienić katalog roboczy w oparciu o wyewidencjonowaną gałąź. Przykładowo można użyć gałęzi plugins do przechowywania wszystkich wtyczek poza główną bazą kodu. Jeśli te wtyczki wymagają wielu plików binarnych, których nie potrzebują inne gałęzie, możesz kompilować je wybiórczo tylko wtedy, gdy jesteś w gałęzi plugins.

Pre-Rebase

Hook pre-rebase jest wywoływany, zanim polecenie git rebase wprowadzi jakąkolwiek zmianę, dzięki czemu jest to dobry moment, aby się upewnić, że nie zdarzy się żadna katastrofa.

Ten hook przyjmuje 2 parametry: gałąź nadrzędną, od której nastąpił podział, oraz gałąź, której dotyczy zmiana bazy. W przypadku zmiany bazy bieżącej gałęzi drugi parametr jest pusty. Aby przerwać operację zmiany bazy, należy zakończyć hook ze statusem niezerowym.

Przykładowo, aby całkowicie cofnąć zezwolenie na zmianę bazy w swoim repozytorium, można użyć następującego skryptu pre-rebase:

#!/bin/sh

# Disallow all rebasing
echo "pre-rebase: Rebasing is dangerous. Don't do it."
exit 1

Teraz przy każdym uruchomieniu polecenia git rebase pojawi się następujący komunikat:

pre-rebase: Rebasing is dangerous. Don't do it.
The pre-rebase hook refused to rebase.

Aby zapoznać się z bardziej szczegółowym przykładem, przyjrzyj się dołączonemu skryptowi pre-rebase.sample. Ten skrypt zawiera dodatkowe warunki dotyczące tego, kiedy nie zezwalać na zmianę bazy. Sprawdza, czy gałąź tematu, dla której próbujesz zmienić bazę, została już scalona z kolejną gałęzią (która jest uważana za gałąź mainline). Jeśli tak jest, zmiana bazy dla takiej gałęzi prawdopodobnie spowodowałaby problemy, dlatego skrypt przerwie tę operację.

Skrypty hook po stronie serwera


Hooki serwerowe działają podobnie jak hooki lokalne, z tym że znajdują się w repozytoriach po stronie serwera (np. repozytorium centralnym lub repozytorium publicznym programisty). Po dołączeniu do oficjalnego repozytorium niektóre z nich mogą służyć do egzekwowania zasad poprzez odrzucanie niektórych commitów.

W dalszej części tego artykułu omówimy 3 hooki serwerowe:

  • pre-receive
  • Aktualizuj
  • post-receive

Wszystkie te hooki pozwalają reagować na różne etapy procesu git push.

Dane wyjściowe z hooków serwerowych są potokowane do konsoli klienta, co bardzo ułatwia wysyłanie wiadomości zwrotnych do programisty. Należy jednak pamiętać, że te skrypty nie zwracają kontroli nad terminalem do czasu zakończenia ich wykonywania, dlatego należy zachować ostrożność podczas wykonywania długotrwałych operacji.

Pre-Receive

Hook pre-receive jest wykonywany za każdym razem, gdy ktoś używa polecenia git push do wypchnięcia commitów do repozytorium. Powinien się on zawsze znajdować w zdalnym repozytorium będącym miejscem docelowym wypychania, a nie w repozytorium źródłowym.

Ten hook jest uruchamiany przed zaktualizowaniem jakichkolwiek referencji, dlatego doskonale sprawdzi się on przy egzekwowaniu wszelkiego rodzaju zasad programistycznych. Jeśli nie podoba Ci się, kto wypycha kod, w jaki sposób komunikat dotyczący commita jest sformatowany lub nie zadowalają Cię zmiany zawarte w commicie, możesz go po prostu odrzucić. Choć nie da się powstrzymać programistów przed tworzeniem commitów o nieprawidłowej postaci, można uniemożliwić wprowadzanie takich commitów do oficjalnej bazy kodu, odrzucając je za pomocą hooka pre-receive.

Skrypt nie przyjmuje żadnych parametrów, ale każda wypychana referencja jest przekazywana do skryptu w odrębnym wierszu standardowego wejścia, z wykorzystaniem następującego formatu:

<old-value> <new-value> <ref-name>

Możesz zobaczyć, jak działa ten hook, używając bardzo podstawowego skryptu pre-receive, który jedynie odczytuje wypchnięte referencje i wyświetla je na ekranie.

#!/usr/bin/env python

import sys
import fileinput

# Read in each ref that the user is trying to update
for line in fileinput.input():
    print "pre-receive: Trying to push ref: %s" % line

# Abort the push
# sys.exit(1)

Ten hook różni się nieco od innych, ponieważ informacje są przekazywane do skryptu poprzez standardowe wejście, a nie za pomocą argumentów wiersza polecenia. Po umieszczeniu powyższego skryptu w katalogu .git/hooks zdalnego repozytorium i wypchnięciu gałęzi main w konsoli zostanie wyświetlona następująca treść:

b6b36c697eb2d24302f89aa22d9170dfe609855b 85baa88c22b52ddd24d71f05db31f4e46d579095 refs/heads/main

Korzystając z tych hashy SHA1 oraz poleceń Git niższego poziomu, możesz sprawdzić zmiany, które mają zostać wprowadzone. Typowe przypadki użycia obejmują:

  • Odrzucanie zmian wymagających zmiany bazy w gałęzi nadrzędnej.
  • Zapobieganie scaleniom innym niż fast-forward.
  • Sprawdzanie, czy użytkownik ma odpowiednie uprawnienia do wprowadzania zamierzonych zmian (wykorzystywane głównie w przypadku scentralizowanych przepływów pracy w Git).

W przypadku wypychania wielu referencji zwrócenie statusu niezerowego przez hook pre-receive spowoduje przerwanie ich wszystkich. Do akceptowania lub odrzucania gałęzi w konkretnych przypadkach służy natomiast hook update.

Aktualizuj

Hook update jest wywoływany po skrypcie pre-receive i działa bardzo podobnie. Także jest wywoływany, zanim cokolwiek faktycznie zostanie zaktualizowane, jednak różnica polega na tym, że jest on wywoływany osobno dla każdej wypychanej referencji. Oznacza to, że jeśli użytkownik próbuje wypchnąć 4 gałęzie, skrypt update zostanie wykonany 4-krotnie. W przeciwieństwie do hooka pre-receive ten skrypt nie musi odczytywać danych ze standardowego wejścia. Przyjmuje on następujące 3 argumenty:

1. Nazwę aktualizowanej referencji.

2. Nazwę starego obiektu zapisanego w referencji.

3. Nazwę nowego obiektu zapisanego w referencji.

Są to te same informacje, które przekazywane są do hooka pre-receive, jednak w związku z tym, że skrypt update jest wywoływany dla każdej referencji z osobna, pozwala on odrzucać niektóre referencje, zezwalając na inne.

#!/usr/bin/env python

import sys

branch = sys.argv[1]
old_commit = sys.argv[2]
new_commit = sys.argv[3]

print "Moving '%s' from %s to %s" % (branch, old_commit, new_commit)

# Abort pushing only this branch
# sys.exit(1)

Danymi wyjściowymi powyższego hooka update są po prostu gałąź oraz hashe starego/nowego commita. W przypadku wypychania do zdalnego repozytorium więcej niż jednej gałęzi instrukcja print będzie wykonywana dla każdej gałęzi.

Post-Receive

Hook post-receive jest wywoływany po pomyślnym wykonaniu operacji wypchnięcia, dzięki czemu przydaje się do powiadomień. W przypadku wielu przepływów pracy jest to lepsze miejsce do wyzwalania powiadomień niż skrypt post-commit, ponieważ zmiany są dostępne na serwerze publicznym, a nie tylko na komputerze lokalnym użytkownika. Hook post-receive najczęściej stosuje się do wysyłania wiadomości e-mail do innych programistów oraz wyzwalania systemu ciągłej integracji.

Ten skrypt nie przyjmuje żadnych parametrów, jednak poprzez standardowe wejście otrzymuje takie same informacje, jak skrypt pre-receive.

Podsumowanie


W niniejszym artykule nauczyliśmy się, w jaki sposób używać hooków Git do zmiany wewnętrznego sposobu działania i otrzymywania powiadomień po wystąpieniu konkretnych zdarzeń w repozytorium. Hooki to zwykłe skrypty przechowywane w repozytorium .git/hooks, co ułatwia ich instalowanie i dostosowywanie.

Przyjrzeliśmy się również kilku z najpopularniejszych hooków lokalnych i serwerowych. Dzięki nim możemy podłączyć się do całego cyklu życia tworzenia oprogramowania. Wiemy też, w jaki sposób wykonywać modyfikowalne czynności na każdym etapie procesu tworzenia commitów, a także procesu git push. Mając podstawową wiedzę na temat tworzenia skryptów, możemy robić z repozytorium Git praktycznie wszystko, co tylko przyjdzie nam do głowy.


Udostępnij ten artykuł

Zalecane lektury

Dodaj te zasoby do zakładek, aby dowiedzieć się więcej na temat rodzajów zespołów DevOps lub otrzymywać aktualności na temat metodyki DevOps w Atlassian.

Ludzie współpracujący przy ścianie pełnej narzędzi

Blog Bitbucket

Ilustracja DevOps

Ścieżka szkoleniowa DevOps

Demonstracje funkcji z ekspertami Atlassian

Zobacz, jak Bitbucket Cloud współpracuje z Atlassian Open DevOps

Zapisz się do newslettera DevOps

Thank you for signing up