Git Archive ile Kaynak Kod Paketleme
Dağıtım süreçlerinde en çok göz ardı edilen araçlardan biri git archive komutu. Çoğu ekip, kaynak kodu paketlemek için ya elle tar çekiyor ya da rsync ile bir şeyler yapmaya çalışıyor. Oysa Git’in içinde, tam da bu iş için tasarlanmış, .git dizinini dışarıda bırakarak temiz bir paket üreten bir komut var. Ben bu aracı ilk kez bir müşteri projesinde production deployment scriptlerini sadeleştirirken keşfettim ve o günden bu yana standart araç setimdeki yerini koruyor.
git archive Nedir ve Neden Önemlidir
git archive, belirli bir commit, branch veya tag referansından kaynak kod arşivi oluşturmanızı sağlar. Çıktı formatı olarak tar, tar.gz veya zip seçebilirsiniz. En önemli özelliği şu: .git dizini, geliştirme araçlarınız, test dosyalarınız, konfigürasyon şablonlarınız – bunların hiçbirini çıktıya dahil etmezsiniz, eğer istemiyorsanız.
Klasik tar -czf proje.tar.gz . yönteminin sorununa bakın: siz reponun tamamını paketliyorsunuz. İçinde .git var, belki node_modules var, belki geliştirici ortamına özel .env.local dosyaları var. Bu paketi production sunucusuna attığınızda ne götürdüğünüzden tam emin olamıyorsunuz.
git archive ile tam olarak hangi commit’in içeriğini paketlediğinizi biliyorsunuz. Deterministik bir çıktı alıyorsunuz. Aynı komutu iki kez çalıştırdığınızda aynı içeriği elde ediyorsunuz, bu da audit ve rollback senaryoları için kritik.
Temel Kullanım
En basit haliyle şöyle çalışır:
git archive --format=tar.gz --output=uygulama-v1.0.tar.gz HEAD
Bu komut mevcut HEAD’i, yani bulunduğunuz branch’in son commit’ini arşivler. HEAD yerine herhangi bir referans verebilirsiniz:
# Belirli bir branch
git archive --format=tar.gz --output=uygulama-main.tar.gz main
# Belirli bir tag
git archive --format=tar.gz --output=uygulama-v2.3.1.tar.gz v2.3.1
# Belirli bir commit hash
git archive --format=tar.gz --output=uygulama-abc1234.tar.gz abc1234f
# Zip formatında
git archive --format=zip --output=uygulama.zip HEAD
Prefix parametresi de oldukça işlevsel. Paketi açtığınızda doğrudan dosyalar çıkmasın, bir dizin altında çıksın istiyorsanız:
git archive --format=tar.gz --prefix=uygulama-v1.0/ --output=uygulama-v1.0.tar.gz v1.0
Bu şekilde paketi açtığınızda uygulama-v1.0/ dizini altında dosyalar gelir. Özellikle birden fazla versiyonu aynı dizine açacaksanız bu parametreyi kullanmayı alışkanlık edinmek iyi fikir.
Sadece Belirli Dizinleri veya Dosyaları Arşivlemek
Projenin tamamını değil, belirli bir alt dizini paketlemek isteyebilirsiniz. Bunu path argümanıyla yapıyorsunuz:
# Sadece src/ dizinini paketle
git archive --format=tar.gz --output=sadece-src.tar.gz HEAD src/
# Birden fazla dizin
git archive --format=tar.gz --output=api-ve-config.tar.gz HEAD src/api/ config/
# Belirli dosya tipleri (glob ile)
git archive --format=tar.gz --output=python-dosyalari.tar.gz HEAD -- "*.py"
Bu özelliği monorepo yapılarında sık kullanıyorum. Tek bir repoda birden fazla servis varken, deployment sırasında sadece ilgili servisin dosyalarını paketleyip göndermek istiyorsunuz. Tüm repoyu taşımak yerine ilgili dizini arşivlemek hem bant genişliği hem de disk alanı açısından mantıklı.
.gitattributes ile Arşivden Dosya Hariç Tutmak
İşte git archive‘ın az bilinen ama son derece güçlü özelliği: .gitattributes dosyasında export-ignore direktifi. Bu sayede bazı dosya ve dizinleri versiyonlamaya dahil edip arşivden otomatik olarak dışlayabilirsiniz.
.gitattributes dosyanıza şunları ekleyin:
# Test dosyalarını dışla
tests/ export-ignore
spec/ export-ignore
# CI/CD konfigürasyonlarını dışla
.github/ export-ignore
.gitlab-ci.yml export-ignore
Jenkinsfile export-ignore
# Geliştirici araçları
.editorconfig export-ignore
.eslintrc.js export-ignore
.prettierrc export-ignore
# Dokümantasyon (production'a gitmesini istemiyorsanız)
docs/ export-ignore
*.md export-ignore
# Docker geliştirme dosyaları
docker-compose.dev.yml export-ignore
Bu değişiklikleri commit ettikten sonra git archive çalıştırdığınızda bu dosyalar ve dizinler otomatik olarak paket dışında kalır. Bunu bir kez yapılandırıyorsunuz, sonrası otomatik. Ekibin yeni bir üyesi geldiğinde ona “şunu şunu paketleme” diye söylemek zorunda kalmıyorsunuz, kural repoda yazıyor.
Gerçek dünya örneği olarak şunu anlatayım: Bir e-ticaret projesinde tests/ dizini yaklaşık 80 MB’tı, fixture’lar ve test veritabanı dump’ları nedeniyle. Production paketine bu dizin girdiğinde hem paket boyutu büyüyordu hem de gereksiz dosyalar sunucuya taşınıyordu. export-ignore direktifini ekledikten sonra paket boyutu 80 MB’tan 12 MB’a düştü.
Remote Repo’dan Doğrudan Arşiv Almak
Yerel bir klon olmadan, uzak bir repodan doğrudan arşiv çekebilirsiniz. Bunun için git archive --remote kullanıyorsunuz:
# SSH üzerinden
git archive [email protected]:backend/api.git --format=tar.gz HEAD | tar -xz
# HTTP üzerinden (sunucu destekliyorsa)
git archive --remote=https://git.sirket.com/repo.git --format=tar.gz v2.1.0 --output=v2.1.0.tar.gz
Ancak hemen belirteyim: GitHub ve GitLab bu özelliği HTTP üzerinden desteklemiyor, güvenlik nedeniyle. SSH üzerinden ve kendi self-hosted kurulumlarınızda çalışıyor. Eğer GitHub API’si üzerinden bir şey yapmak istiyorsanız, tarball endpoint’lerini kullanmanız gerekiyor, ama bu farklı bir konu.
Kendi GitLab CE kurulumunuz varsa ve CI runner’lar farklı bir ağ segmentindeyse bu özellik gerçekten işe yarıyor. Runner, repoyu klonlamak yerine sadece ihtiyaç duyduğu commit’in arşivini çekiyor.
Pratik Deployment Script Örneği
Şimdi bu komutu gerçek bir deployment senaryosunda kullanalım. Basit ama işlevsel bir deployment scripti:
#!/bin/bash
set -euo pipefail
APP_NAME="backend-api"
DEPLOY_ENV="${1:-staging}"
GIT_REF="${2:-HEAD}"
DEPLOY_DIR="/var/www/${APP_NAME}"
BACKUP_DIR="/var/backups/${APP_NAME}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# Git ref'inden commit hash al
COMMIT_HASH=$(git rev-parse --short "${GIT_REF}")
ARCHIVE_NAME="${APP_NAME}-${COMMIT_HASH}-${TIMESTAMP}.tar.gz"
echo "==> Arşiv oluşturuluyor: ${ARCHIVE_NAME}"
git archive
--format=tar.gz
--prefix="${APP_NAME}/"
--output="/tmp/${ARCHIVE_NAME}"
"${GIT_REF}"
echo "==> Mevcut deployment yedekleniyor..."
if [ -d "${DEPLOY_DIR}" ]; then
mkdir -p "${BACKUP_DIR}"
tar -czf "${BACKUP_DIR}/${APP_NAME}-backup-${TIMESTAMP}.tar.gz"
-C "$(dirname ${DEPLOY_DIR})"
"$(basename ${DEPLOY_DIR})"
fi
echo "==> Yeni versiyon açılıyor..."
mkdir -p "${DEPLOY_DIR}"
tar -xzf "/tmp/${ARCHIVE_NAME}" -C "$(dirname ${DEPLOY_DIR})" --strip-components=1
echo "==> Temizlik yapılıyor..."
rm -f "/tmp/${ARCHIVE_NAME}"
echo "==> Deployment tamamlandi: ${COMMIT_HASH}"
Bu script, export-ignore kurallarına uyan temiz bir paket oluşturuyor, mevcut deployment’ı yedekliyor ve yeni versiyonu açıyor. Basit, okunabilir, bakımı kolay.
Submodule İçeren Repolarda Dikkat
git archive‘ın bir kısıtlaması var: submodule’leri otomatik olarak arşive dahil etmiyor. Sadece ana reponun içeriğini paketliyor, submodule dizinleri boş kalıyor. Eğer projenizde submodule varsa bunu göz önünde bulundurmanız gerekiyor.
Bu durumu çözmek için birkaç yaklaşım var:
#!/bin/bash
# Submodule'lü repo için arşiv oluşturma
OUTPUT_FILE="proje-tam.tar.gz"
TMP_DIR=$(mktemp -d)
# Ana repoyu arşivle
git archive --format=tar HEAD | tar -xC "${TMP_DIR}"
# Her submodule için ayrıca arşivle
git submodule foreach --quiet --recursive
'git archive --format=tar HEAD | tar -xC "${TMP_DIR}/${displaypath}"'
# Hepsini tek pakete koy
tar -czf "${OUTPUT_FILE}" -C "${TMP_DIR}" .
# Temizle
rm -rf "${TMP_DIR}"
echo "Arşiv hazır: ${OUTPUT_FILE}"
Bu yaklaşım biraz kaba ama işe yarıyor. Daha sofistike çözümler için bazı ekipler git-archive-all gibi üçüncü parti araçlara başvuruyor, ama çoğu zaman bu script yeterli.
CI/CD Pipeline Entegrasyonu
GitLab CI’da git archive kullanımına gerçek bir örnek verelim:
stages:
- build
- deploy
variables:
APP_NAME: "backend-api"
build-package:
stage: build
script:
- |
COMMIT_SHORT=$(git rev-parse --short HEAD)
PACKAGE_NAME="${APP_NAME}-${CI_COMMIT_REF_SLUG}-${COMMIT_SHORT}.tar.gz"
git archive
--format=tar.gz
--prefix="${APP_NAME}/"
--output="${PACKAGE_NAME}"
HEAD
echo "PACKAGE_NAME=${PACKAGE_NAME}" > build.env
echo "COMMIT_SHORT=${COMMIT_SHORT}" >> build.env
# Paket boyutunu logla
du -sh "${PACKAGE_NAME}"
artifacts:
paths:
- "*.tar.gz"
reports:
dotenv: build.env
expire_in: 1 week
only:
- main
- /^release/.*$/
deploy-staging:
stage: deploy
needs:
- build-package
script:
- echo "Paket: ${PACKAGE_NAME}, Commit: ${COMMIT_SHORT}"
- scp "${PACKAGE_NAME}" deploy@staging-server:/tmp/
- ssh deploy@staging-server "
tar -xzf /tmp/${PACKAGE_NAME} -C /var/www/ &&
cd /var/www/${APP_NAME} &&
composer install --no-dev --optimize-autoloader &&
php artisan migrate --force &&
php artisan config:cache
"
environment:
name: staging
only:
- main
Bu pipeline birkaç açıdan temiz: arşiv oluşturma bir kez yapılıyor, artifact olarak saklanıyor ve deploy aşamasında tekrar kullanılıyor. Aynı arşiv staging’e de production’a da gidebiliyor, böylece “staging’de test ettiğimiz ile production’a gönderdiğimiz aynı mı” sorusuna kesin cevap verebiliyorsunuz.
Versiyon Bilgisini Arşive Gömmek
Deployment sırasında hangi versiyonun çalıştığını anlamak için bir VERSION veya BUILD_INFO dosyası oluşturmak yaygın bir pratik. git archive bu dosyayı otomatik olarak oluşturmanıza da yardımcı olabilir.
.gitattributes dosyasına şunu ekleyin:
VERSION.txt export-subst
Sonra VERSION.txt dosyası oluşturun:
commit=$Format:%H$
abbrev=$Format:%h$
date=$Format:%ci$
author=$Format:%an$
Arşiv oluşturduğunuzda bu placeholder’lar gerçek değerlerle otomatik olarak doldurulur. $Format:%H$ yerine gerçek commit hash’i, $Format:%ci$ yerine commit tarihi gelir. Uygulamanız bu dosyayı okuyarak hangi versiyonda çalıştığını anlayabilir. Debug süreçlerinde bu bilgi paha biçilmez.
Karşılaşılan Yaygın Sorunlar
“not a git repository” hatası: Script’i reponun dışında çalıştırıyorsunuz demektir. Ya repo dizinine geçin ya da GIT_DIR ve GIT_WORK_TREE environment variable’larını ayarlayın.
Arşiv boş geliyor veya beklenenden küçük: Büyük ihtimalle .gitattributes dosyanızdaki export-ignore direktifleri beklenmedik dosyaları da kapsıyor. git archive çıktısını önce bir yerde açıp kontrol edin:
git archive --format=tar HEAD | tar -tv | head -50
Bu komut arşivin içeriğini listeliyor, açmadan. Neyin içinde neyin olmadığını hızlıca görebilirsiniz.
Submodule dizinleri boş: Yukarıda bahsettiğimiz sorun. Ya submodule script’ini kullanın ya da submodule yerine dependency manager tercih etmeyi düşünün.
Remote archive çalışmıyor: GitHub/GitLab HTTP üzerinden bu özelliği desteklemiyor demiştik. SSH üzerinden ya da API endpoint’lerini kullanmanız gerekiyor.
Alternatif: git bundle
git archive ile birlikte anılmayı hak eden bir başka komut git bundle. git archive sadece working tree içeriğini verirken, git bundle Git geçmişiyle birlikte tam bir paket üretiyor. İnternet bağlantısı olmayan ortamlar arasında repo aktarımı için idealdir:
# Tüm main branch'i bundle'la
git bundle create repo-transfer.bundle main
# Hedef makinede
git clone repo-transfer.bundle yeni-repo/
git checkout main
Ama deployment senaryoları için genellikle git archive daha uygun. Sunucuda Git history’sine ihtiyacınız olmadığı durumlar çok yaygın.
Sonuç
git archive basit bir komut ama doğru kullanıldığında deployment süreçlerini ciddi ölçüde temizleyebiliyor. Özetlemek gerekirse:
- Deterministik çıktı: Aynı ref, her zaman aynı içerik.
- Temiz paket:
.gitdizini veexport-ignoreile işaretlenmiş dosyalar dahil değil. - Esneklik: Belirli dizinler, belirli dosya tipleri, prefix ile kontrollü açma.
- CI/CD uyumu: Artifact olarak saklama ve pipeline aşamaları arasında paylaşma için biçilmiş kaftan.
Deployment scriptlerinizi gözden geçiriyorsanız ve içinde tar -czf . gibi bir şey görüyorsanız, git archive‘a geçmeyi ciddiye alın. Özellikle .gitattributes entegrasyonuyla birlikte, “production’a ne gönderiyoruz” sorusuna her zaman net bir cevabınız olur. Ve o netlik, gece yarısı incident çözerken gerçekten fark yaratıyor.
