Git Bare Repository adalah jenis repositori Git yang tidak memiliki direktori kerja aktif (working tree) dan hanya berisi metadata seperti branch, tag, log, dan riwayat commit. Jenis repositori ini cocok digunakan sebagai alternatif internal dari GitHub karena dapat diakses bersama oleh banyak developer serta mendukung otomatisasi deployment menggunakan Git Hooks.
Setup Git Bare Repository #
Buat direktori repositori di server, misalnya di /srv/git atau /opt/git.
mkdir -p /srv/git/nama-proyek.gitMasuk ke direktori tersebut, lalu inisialisasi repositori bare menggunakan perintah berikut.
cd /srv/git/nama-proyek.git
git init --bareAgar repositori dapat diakses dan dimodifikasi oleh sekelompok pengguna, gunakan opsi --shared.
git init --bare --shared=group /srv/git/nama-proyek.gitOpsional: sesuaikan konfigurasi Git Bare pada bagian receive dan gc seperti berikut.
[core]
repositoryformatversion = 0
filemode = true
bare = true
logallrefupdates = true
sharedrepository = 1
[receive]
denyCurrentBranch = updateInstead
[gc]
auto = 0Setelah itu, ubah kepemilikan dan izin direktori agar grup tertentu memiliki akses penuh.
sudo chgrp -R git /srv/git/nama-proyek.git
sudo chmod -R 775 /srv/git/nama-proyek.gitLakukan pengujian dengan melakukan clone repositori.
git clone user@server:/srv/git/nama-proyek.gitMengubah Repositori Lokal Menjadi Bare Repository #
Jika Anda sudah memiliki repositori biasa di komputer atau server dan ingin mengubahnya menjadi repositori bare, gunakan perintah berikut.
git clone --bare /path/to/local/repo /srv/git/nama-proyek.gitGit Hooks #
Salah satu keunggulan Git Bare Repository adalah kemampuannya menjalankan otomatisasi menggunakan Git Hooks. Fitur ini sangat berguna untuk kebutuhan auto deploy maupun proses Continuous Integration / Continuous Deployment (CI/CD), di mana kode akan otomatis diterapkan ke server setiap kali ada perubahan.
Beberapa hook yang umum digunakan:
-
pre-receiveBerjalan sebelum server menerima perubahan. Umumnya digunakan untuk validasi kode, pemeriksaan format commit, atau mencegahforce push. -
post-receiveBerjalan setelah server menerima perubahan. Biasanya digunakan untuk deployment otomatis, mengirim notifikasi, atau menjalankan workflow tertentu.
Masuk ke direktori hooks.
cd /srv/git/nama-proyek.git/hooksBuat dan edit file post-receive.
nano post-receiveContoh script post-receive untuk melakukan deploy otomatis ke direktori aplikasi.
#!/bin/bash
TARGET="/var/www/proyek"
GIT_DIR="/srv/git/nama-proyek.git"
BRANCH="main"
echo "=== POST-RECEIVE HOOK EXECUTED ==="
echo "Repository: $(basename "$(pwd)")"
echo "Time: $(date)"
while read oldrev newrev ref; do
branch=$(git rev-parse --symbolic --abbrev-ref "$ref")
echo "Branch: $branch"
echo "Old revision: $oldrev"
echo "New revision: $newrev"
echo "-------------------"
if [[ "$ref" == "refs/heads/$BRANCH" ]]; then
echo "Deploying $BRANCH branch to production..."
git \
--work-tree="$TARGET" \
--git-dir="$GIT_DIR" \
checkout -f "$BRANCH"
cd "$TARGET" || exit 1
# Jalankan perintah tambahan di sini, misalnya:
# composer install --no-dev --optimize-autoloader
# docker compose up -d
# npm run build
fi
done
echo "=== POST-RECEIVE HOOK COMPLETED ==="Berikan izin eksekusi pada file hook.
chmod +x post-receiveScript Hooks #
Berikut beberapa contoh Git Hooks yang umum digunakan pada Git Bare Repository untuk validasi branch dan deployment otomatis.
Hook pre-receive
#
Hook pre-receive digunakan untuk memvalidasi perubahan sebelum push diterima oleh server. Contoh berikut membatasi push hanya ke branch main dan develop, serta memberikan peringatan jika commit message mengandung kata WIP.
#!/bin/bash
# pre-receive:
# Memvalidasi branch dan commit message sebelum push diterima.
while read oldrev newrev refname; do
# Ambil nama branch dari referensi Git
branch="${refname#refs/heads/}"
# Hanya izinkan branch tertentu
if [[ "$branch" != "main" && "$branch" != "develop" ]]; then
echo "ERROR: Push ke branch '$branch' tidak diizinkan."
echo "Hanya branch 'main' dan 'develop' yang diperbolehkan."
exit 1
fi
# Periksa commit message terbaru
if git log -1 --pretty=format:"%s" "$newrev" | grep -qi "WIP"; then
echo "WARNING: Commit dengan message 'WIP' terdeteksi."
echo "Push tetap diizinkan."
fi
done
echo "Pre-receive validation passed."
exit 0Hook post-receive Multi-Branch Deployment
#
Hook post-receive berikut digunakan untuk deployment otomatis berdasarkan branch yang di-push.
main→ productiondevelop→ stagingfeature/*→ environment khusus feature branch
#!/bin/bash
# post-receive:
# Multi-branch automatic deployment
BASE_DIR="/var/www"
GIT_DIR="$(pwd)"
REPO_NAME="$(basename "$GIT_DIR" .git)"
echo "=== Starting multi-branch deployment ==="
while read oldrev newrev refname; do
branch="${refname#refs/heads/}"
# Tentukan direktori target berdasarkan branch
case "$branch" in
main)
TARGET="$BASE_DIR/$REPO_NAME/production"
echo "Deploying PRODUCTION environment"
;;
develop)
TARGET="$BASE_DIR/$REPO_NAME/staging"
echo "Deploying STAGING environment"
;;
feature/*)
feature_name="${branch#feature/}"
TARGET="$BASE_DIR/$REPO_NAME/features/$feature_name"
echo "Deploying FEATURE branch: $feature_name"
mkdir -p "$TARGET"
;;
*)
TARGET="$BASE_DIR/$REPO_NAME/$branch"
echo "Deploying branch: $branch"
mkdir -p "$TARGET"
;;
esac
# Pastikan direktori target tersedia
mkdir -p "$TARGET"
# Deploy branch ke target directory
git \
--work-tree="$TARGET" \
--git-dir="$GIT_DIR" \
checkout -f "$branch"
# Simpan informasi deployment
{
echo "Deployed : $(date)"
echo "Branch : $branch"
echo "Commit : $newrev"
} > "$TARGET/deploy-info.txt"
done
echo "=== Multi-branch deployment completed ==="Hook post-receive dengan Logging dan Notifikasi Slack
#
Berikut contoh hook post-receive yang lebih production-ready dengan beberapa praktik terbaik seperti:
- atomic deployment menggunakan symbolic link
- logging terpusat
- cleanup release lama
- notifikasi ke Slack
- error handling yang lebih aman
- deployment berbasis commit hash
Struktur deployment yang dihasilkan kurang lebih seperti berikut:
/home/devops/proyek/
├── main -> releases/main-a1b2c3d
├── develop -> releases/develop-e4f5g6h
└── releases/
├── main-a1b2c3d/
├── main-x7y8z9a/
└── develop-e4f5g6h/Script hook:
#!/bin/bash
# post-receive:
# Production-ready deployment hook dengan logging,
# rollback-friendly release management,
# dan notifikasi Slack.
# =========================================================
# Konfigurasi
# =========================================================
# Base directory deployment
: "${DEPLOY_BASE_DIR:=/home/devops}"
# Nama repository
REPO_NAME=$(basename "$(pwd)")
[[ "$REPO_NAME" == *.git ]] && REPO_NAME="${REPO_NAME%.git}"
# File log
: "${LOG_FILE:=/home/devops/logs/git-deploy.log}"
# Slack webhook URL
: "${SLACK_WEBHOOK:=}"
# Email admin (opsional)
: "${ADMIN_EMAIL:=}"
# =========================================================
# Logging dan shell safety
# =========================================================
mkdir -p "$(dirname "$LOG_FILE")"
exec >> "$LOG_FILE" 2>&1
echo "[$(date)] Hook triggered"
set -euo pipefail
# =========================================================
# Fungsi notifikasi
# =========================================================
notify() {
local level="$1"
local msg="$2"
echo "[$level] $msg"
if [[ -n "$SLACK_WEBHOOK" ]]; then
curl -s \
-X POST \
-H 'Content-type: application/json' \
--data "{\"text\":\"[$level] $msg\"}" \
"$SLACK_WEBHOOK" >/dev/null || true
fi
}
# =========================================================
# Cleanup release lama
# =========================================================
cleanup_old_releases() {
local branch="$1"
local releases_dir="$DEPLOY_BASE_DIR/$REPO_NAME/releases"
local keep=2
[[ -d "$releases_dir" ]] || return
echo "[INFO] Cleaning old releases (keep=$keep)"
local target_link="$DEPLOY_BASE_DIR/$REPO_NAME/$branch"
local current_active=""
if [[ -L "$target_link" ]]; then
current_active=$(readlink -f "$target_link")
fi
(
cd "$releases_dir" || exit 0
shopt -s nullglob
local dirs=()
while IFS= read -r dir; do
dirs+=("$dir")
done < <(
for d in "${branch}-"*/; do
echo "${d%/}"
done \
| xargs -I{} stat --format="%Y {}" {} 2>/dev/null \
| sort -rn \
| awk '{print $2}'
)
[[ ${#dirs[@]} -gt 0 ]] || {
echo "[INFO] No releases found for branch: $branch"
return
}
local count=0
for dir in "${dirs[@]}"; do
count=$((count + 1))
# Simpan release terbaru
if [[ $count -le $keep ]]; then
echo "[KEEP] $dir"
continue
fi
local abs_dir
abs_dir=$(readlink -f "$dir" 2>/dev/null || echo "")
[[ -n "$abs_dir" ]] || {
echo "[WARN] Failed resolving path: $dir"
continue
}
# Hindari menghapus release aktif
if [[ -n "$current_active" && "$abs_dir" == "$current_active" ]]; then
echo "[WARN] Skipping active release: $dir"
continue
fi
echo "[DELETE] Removing old release: $dir"
rm -rf "$dir"
done
)
}
# =========================================================
# Atomic deployment
# =========================================================
deploy_atomic() {
local branch="$1"
local commit_hash="$2"
local target_link="$DEPLOY_BASE_DIR/$REPO_NAME/$branch"
local new_dir="$DEPLOY_BASE_DIR/$REPO_NAME/releases/${branch}-${commit_hash:0:7}"
mkdir -p "$new_dir"
# Extract repository content
git \
--git-dir="$(pwd)" \
archive "$commit_hash" \
| tar -x -C "$new_dir"
# Deployment metadata
{
echo "Repository : $REPO_NAME"
echo "Branch : $branch"
echo "Commit : $(git rev-parse "$commit_hash")"
echo "Deployed : $(date)"
} > "$new_dir/deploy-info.txt"
# Update symlink secara atomic
mkdir -p "$(dirname "$target_link")"
ln -sfn "$new_dir" "$target_link"
cleanup_old_releases "$branch"
echo "$new_dir"
}
# =========================================================
# Main process
# =========================================================
while read oldrev newrev ref; do
branch="${ref#refs/heads/}"
echo "[INFO] Processing branch=$branch old=$oldrev new=$newrev"
# Skip HEAD
[[ "$branch" == "HEAD" ]] && continue
case "$branch" in
main|master|develop)
notify \
"INFO" \
"Starting deployment: branch=$branch commit=${newrev:0:7}"
if deploy_dir=$(deploy_atomic "$branch" "$newrev"); then
# =================================================
# Post-deployment commands
# =================================================
# (
# cd "$deploy_dir"
#
# if [[ -f composer.json ]]; then
# composer install \
# --no-dev \
# --no-interaction \
# --optimize-autoloader
# fi
#
# if [[ -f package.json ]]; then
# npm install --production
# fi
#
# if [[ -f artisan ]]; then
# php artisan migrate --force
# fi
# )
notify \
"SUCCESS" \
"Deployment completed: branch=$branch dir=$deploy_dir"
else
notify \
"ERROR" \
"Deployment failed for branch=$branch"
fi
;;
*)
echo "[INFO] Branch '$branch' ignored"
;;
esac
done
echo "[$(date)] Hook finished"
exit 0