İçeriğe geç

Git Hooks ve Pre-Commit

Giriş.

Versiyon kontrol sistemi, modern geliştirme dünyasının vazgeçilmez bir parçasıdır. Projede yapılan tüm değişikliklerin kaydedilmesini, farklı versiyonlar arasında geçiş yapmayı, kodun bütünlüğünü sağlamayı ve birden fazla geliştiricinin uyum içinde çalışmasını mümkün kılar.

Bu blog yazısında, Git’in yerleşik özelliklerinden biri olan hooks kavramı ve pre-commit paketi üzerinde duracağız.

Git hooks nedir?

Git hooks, versiyon kontrol sisteminde gerçekleşen olaylar sırasında çalışan komutlardır. .git/hooks dizininde saklanırlar ve geliştiricilere iş akışlarını otomatikleştirme, öntanımlı protokolleri uygulama, kontrol mekanizmaları kurgulamalarına olanak sağlar.

Herhangi bir dil ile geliştirilebilirler, tek gerekliliği executable bir script olmasıdır.

Neden kullanılır?

Örneğin, her geliştiricinin kendi kod format stiline sahip olması durumunda kod bütünlüğü sağlanamaz. Ayrıca, açıklayıcı commit mesajları oluşturulmadığında geçmiş değişiklikleri irdelemek zorlaşır veya bir merge işleminin diğer paydaş geliştiricilerle paylaşılmaması gibi durumlar modern geliştirme dünyasında istenmeyen durumlardır.

Git hooks ile yukarıdaki problemleri çözebilir, versiyon kontrol akışında denetimi sağlayabilirsiniz.


Client Side Hooks ve Server Side Hooks.

Git’te iki farklı tip hook bulunmaktadır ve bunlar, repo kapsamına göre ayrılır.

Client (istemci) tarafında çalışan hooklar, lokal repo üzerinde gerçekleşen olaylar sonrası devreye girerken, server (sunucu) tarafında çalışan hooklar remote (uzak) repo üzerinde gerçekleşen olaylar sonrası devreye girer.

Remote repo üzerinde çalışan hooklar, CI/CD süreçlerini yönetmek için kullanılabilirken, lokal repo üzerinde çalışan hooklar geliştirme sürecini standartize etmek için kullanılabilir. Örneğin branch üstüne her commit çıktığınızda çalışan bir pipeline süreciniz varsa, Git sağlayıcınız bunu içsel yada dış kaynakta çalışan pipeline mekanizmanıza hooklar vasıtasıyla bildirip, pipeline’ı yürütebilir.

Kapsamlara göz attıktan sonra client tarafında çalışacak bir hook oluşturalım. Böylece hooks kavramını daha iyi anlayabiliriz. Server tarafında farklı sağlayıcıların farklı konfigüratorleri bulunduğundan yada Git sunucunuz muhtemelen size özel konfigüre edildiğinden ortak bir paydada buluşulamayacağından bu yazı kapsamında server hookslara derinlemesine değinmeyecegiz.

Fakat göz atmak isterseniz, Gitlab’in şu dökümanına göz atabilirsiniz.


Client side hook oluşturalım.

Client side tarafında kullanılabilecek birden fazla hook bulunur fakat fikir vermesi açısından örnek üç hook ve çalışma zamanlarını ekliyorum.

  • pre-commit (Commit atılmadan hemen önce çalışır.)
  • post-rewrite (Halihazırda varolan bir commit mesajını düzenleme yada rebase işlemi sırasında çalışır.)
  • post-checkout (Checkout komutu öncesinde çalışır.)

Artık hooklar hakkında bir bilgiye sahibiz fakat ihtiyaclarımız doğrultusunda nasıl oluşturup, konfigüre edebiliriz? Bu sorulara göz atalım.

Versiyon takibi yaptığınız reponun ana dizininde .git/hooks/ altında lskomutunu çalıştırırsanız -sampleson ekine sahip bir çok dosya göreceksiniz. Bu dosyalar Git tarafından otomatik oluşturulan ve size fikir vermesi açısından bazı konfigürasyonlar içerir.

Herhangi bir varsayılan hook’ı içeriğine göz attıktan sonra kullanmak isterseniz -sample son ekini dosya adından kaldırmanız yeterli olacaktır.

Örnek.

Varsayalım ki işleri takip ettiğiniz iş takip uygulamanız ile Git sağlayıcınız arasında bir bağıntı var ve TASK-123 kodlu bir iş açılıp, geliştirme yapıldığında bu PR’ın doğrudan iş içerisinde linkli olarak web arayüzünde görünmesini istiyorsunuz. Takip uygulamanızın doğru eşleşme sağlayabilmesi adına oluşturulan branch isminin TASK prefixine ve iş numarasına sahip olması gerekir.

'TASK-{NUMBER}'

Bu kontrolü pre-commit hookına yerleştirebiliriz. Böylece yanlış isimle bir branch oluşturulmuş olsa dahi o branche commit atamayacağından dolayı eşleşmeyi garanti etmiş oluruz.

Öncelikle, repo dizininde

$ touch .git/hooks/pre-commit

dosyasını oluşturup sonrasında da executable olabilmesi adına aşağıdaki komutu çalıştıralım.

$ chmod +x .git/hooks/pre-commit

Oluşturulan bu dosyanın içerisine favori metin editör uygulamanız ile aşağıdaki bash betiğini ekleyip kaydedelim.

#!/bin/bash
# Gecerli branch adini al
current_branch=$(git branch --show-current)
# Branch adinin 'TASK-{NUMBER}' formatinda olup olmadigini kontrol et
if [[ ! $current_branch =~ ^TASK-[0-9]+$ ]]; then
    echo "Error: Branch name '$current_branch' is not in the format 'TASK-{NUMBER}'."
    exit 1
fi
echo "Branch name '$current_branch' is valid."
exit 0

Şimdi hook’ı aşağıdaki senaryo üzerinden kontrol edelim.

Lokal repoda MY-TASK adlı bir branch oluşturuldu ve üzerinde bir kaç değişiklik yapıldı. Değişiklikleri commit atmaya çalışalım;

$ git branch --show-current
MY_TASK
$ echo "echo 'hello world'" >> build.sh
$ git add build.sh
$ git commit -m "add build.sh file."
Error: Branch name 'MY_TASK' is not in the format 'TASK-{NUMBER}'.

Branch ismi validasyonu başarılı şekilde çalışarak, iş numarasıyla eşleşmeyecek bir branche commit atılmasını ve işin bu branch üzerinden ilerletilmesini önlüyor.

Şimdi doğru bir branch ismi oluşturduğumuzu varsayalım ve işlemleri tekrarlayalım.

$ git branch --show-current
TASK-123
$ echo "echo 'hello world'" >> build.sh
$ git add build.sh
$ git commit -m "add build.sh file."
Branch name 'TASK-123' is valid.
[TASK-123 bf222f1] add build.sh file.
 1 file changed, 1 insertion(+)
 create mode 100644 build.sh

Validasyon sırasında bir problem yaşamadık ve commiti başarılı bir şekilde atabildik. Artık değişikliklerimiz remote repo üzerine yollayabiliriz.

Genel olarak bir Git hook’ının neden kullanıldığı ve nasıl oluşturulup kullanılabileceği konusunda artık fikir sahibiyiz. Şimdi pre-commit projesi hakkında konuşabiliriz.


Pre-commit projesi ve hook işlemlerini standartize etme.

İstemci tarafında oluşturulan ve kullanılan Git hook’ları lokal bir repository’nin özelliğidir. Bu hook’lar, .git/hooks/ dizini altında saklanır ve yalnızca o lokal repository için geçerli olurlar. Uzak repoya (remote repository) bu hook’lar push edilmez, bu nedenle yeni bir geliştirici ekibe katıldığında veya başka bir kullanıcı projeyi klonladığında, bu hook’lar otomatik olarak klonlanmaz veya paylaşılmaz.

Dolayısıyla, denetimi gerçekleştiren hookların geliştiriciler arasında paylaşılması gerekir.

Bunun yanında farklı kontrollerin farklı gereklilikleri bulunabilir. Örneğin Scss linteri olan scss-lint Ruby ile geliştirilmiştir. Hooklar vasıtasyla yazılan kodu linterdan geçirmek istediginizde Ruby ve ilgili gemleri bulundurmak gerekir.

Pre-commit projesi ise bağımlılık problemlerinden sizi soyutlama ve hooklarınızın kolayca dağıtılabilmesi konularında çözüm üretir. Herhangi bir yazılım dili zorunluluğu bulunmaksızın, kendisine tanımlanacak konfigürasyon listesi dahilinde işlemlerinizi gerçekleştirebilir.

pre-commit’i yükleyelim.

pre-commit Python programlama diliyle geliştirilmiştir. Eğer bir Python projesi geliştiriyorsanız pip yardımıyla ya da çalışmakta olduğunuz isletim sisteminin paket yöneticisiyle kurulumunu yapabilirsiniz.

$ pip install pre-commit

MacOS kullanıyorsanız;

$ brew install pre-commit

Kurulumun kontrolü;

$ pre-commit --version
pre-commit 3.7.1

Konfigürasyon.

pre-commit yürütülecek olan denetimleri .pre-commit-config.yaml dosyasında barındırır, dolayısıyla proje dizininde önce bu dosyayı oluşturalım.

$ touch .pre-commit-config.yaml

Sonrasında örnek bir config elde etmek için $ pre-commit sample-config komutunu çalıştırabiliriz.

# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
-   repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v3.2.0
    hooks:
    -   id: trailing-whitespace
    -   id: end-of-file-fixer
    -   id: check-yaml
    -   id: check-added-large-files

Çıktıya göre bu konfigürasyonı kullanacak olursak, pre-commitin ön tanımlı işlemlerinden olan bir kaçını kullanacağız. Örneğin proje dizininde bir  YAML dosyası varsa bunun syntaxinin kontrol edilmesi yada dosyalardaki boşluk kontrolü.

Doğrudan şu haliyle pek işlevsel değil fakat herhangi konfigürasyonu pre-commit’e nasıl tanıtacağımıza göz atalım. Çıktıyı .pre-commit-config.yamldosyasının içerisine kaydedin.

$ pre-commit sample-config >> .pre-commit-config.yaml

Konfigürasyonu tanıtalım ve yürütelim.

$ pre-commit install
Running in migration mode with existing hooks at .git/hooks/pre-commit.legacy
Use -f to use only pre-commit.
pre-commit installed at .git/hooks/pre-commit

pre-commit artık bir commit atmaya çalıştığımızda hangi denetimlerin yapılacağı hakkında haberdar.

Yazının başında manuel oluşturmuş olduğumuz branch ismini kontrol eden scriptte farklı bir dosyaya kayıt edilerek pre-commiti run ettiğimizde kullanılacak.

$ echo "echo 'hello world'" >> build.sh
$ git add build.sh
$ git commit -m "add build.sh file."
Branch name 'TASK-991' is valid.
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Passed
Check Yaml...........................................(no files to check)Skipped
Check for added large files..............................................Passed
[TASK-991 995f704] add build.sh file.
 1 file changed, 1 insertion(+)
 create mode 100644 build.sh

Çıktıdanda görebileceğimiz üzere hem branch kontrolü, hemde config dosyasında belirtilen kontroller yapıldı. Gönül rahatlığıyla commit atılabilir. 😄

Konfigürasyonu değiştirelim.

Konfigürasyonu bir Python projesi geliştirirken ihtiyaç duyulabilecek şekilde aşağıdaki gibi genişletebiliriz.

Black ile kod formatının kontrolünü, isort ile importların sıralı ve düzenli olmasını kontrol ederek standartize bir syntax sağlayabiliriz.

Tekrar pre-commit installçalıştırmayı unutmayın.

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v3.2.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files
      - id: check-json
  - repo: https://github.com/psf/black
    rev: 22.3.0
    hooks:
      - id: black
        args: [-S, --line-length=120]
  - repo: https://github.com/pycqa/isort
    rev: 5.12.0
    hooks:
      - id: isort
        args: [--profile=black, -m=3, -l=120]

Aşağıdaki gibi bir script.py dosyası ekleyip tekrar commit atmaya çalışalım.

from bs4 import BeautifulSoup
import requests
titles = [tag.text for tag in BeautifulSoup(requests.get('https://www.example.com').content, 'html.parser').find_all(['h1', 'h2', 'h3'])]

120 karakter kuralına uymayan ve import sırası hatalı bir script. pre-commitin bizi engellemesini bekliyoruz.

$ git add script.py
$ git commit -m "add python script for html parsing."
Branch name 'TASK-991' is valid.
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Passed
Check Yaml...........................................(no files to check)Skipped
Check for added large files..............................................Passed
Check JSON...........................................(no files to check)Skipped
black....................................................................Failed
- hook id: black
- files were modified by this hook
reformatted script.py
All done! ✨ 🍰 ✨
1 file reformatted.
isort....................................................................Failed
- hook id: isort
- files were modified by this hook
Fixing /Users/egehangundogdu/personal/hooks_git/script.py

pre-commit asli görevini yaparak commit atmamızı engelledi ve ekstra olarak Black ve isort kullanarak dosyayı tekrar düzenleyerek modified stage’ine tekrar getirdi.

Eğer dosyayı tekrar, git add ile ekleyip commit atmaya çalışırsanız kontroller problemsiz geçileceği için sorun yaşanmayacaktır.

$ git add script.py
$ git commit -m "add python script for html parsing."
Branch name 'TASK-991' is valid.
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Passed
Check Yaml...........................................(no files to check)Skipped
Check for added large files..............................................Passed
Check JSON...........................................(no files to check)Skipped
black....................................................................Passed
isort....................................................................Passed
[TASK-991 33818ff] add python script for html parsing.
 1 file changed, 9 insertions(+)

Not.

Eğer tüm hookları manuel olarak tetiklemek isterseniz, pre-commit run komutunu çalıştırabilirsiniz. Herhangi bir commit atmadan önce dosyalarınızı kontrol edecek ve değişiklik yapabiliyorsa formata uygun şekilde değişiklik yapacaktır.


Sonuç.

Bu blog yazısında, Git’in güçlü ve esnek özelliklerinden biri olan hooks kavramını ve pre-commit paketini detaylıca anlatmaya çalıştım. Geliştirme süreçlerinizde bu araçları etkin bir şekilde kullanarak daha verimli ve organize bir çalışma ortamı yaratabilirsiniz.

Umarım bu yazı, Git ve pre-commit hakkında bir temel sağlama konusunda yardımcı olmuştur.

Daha detaylı göz atmak isterseniz;

pre-commit dökümantasyon linki.

git hooks dökümantasyon linki.

Görüşmek üzere!

Tarih:Blog

Tek Yorum

  1. Cem Y. Cem Y.

    Eline saglik Egehan, guzel bir yazi olmus.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.

Göster
Gizle