Lima repository tersebar di lima folder, dan tiap pagi kita membuka lima terminal untuk memeriksa git status satu per satu. Pekerjaan berulang pada banyak repository adalah tugas yang bukan milik manusia — dan shell loop menyelesaikannya dalam sepuluh baris. Chapter ini merangkai perintah-perintah yang sudah kita kuasai menjadi tiga skrip otomasi nyata — status harian, backup harian, sinkronisasi batch — lalu membangun kerangka yang membuat skrip seperti ini layak dipercaya: gagal cepat, bisa diuji tanpa efek samping, dan benar ketika dijalankan dari cron tanpa terminal pengguna.
Panggung: Folder Multi-Proyek #
Dua repository kecil di satu folder induk cukup mewakili situasi nyata:
mkdir -p latihan-otomasi/proyek && cd latihan-otomasi/proyek
for p in alpha beta; do
git init -b main "$p"
(
cd "$p"
git config user.name "Budi Santoso"
git config user.email "[email protected]"
printf "proyek $p\n" > README.md
git add README.md
GIT_AUTHOR_DATE="2026-11-12T09:00:00+07:00" GIT_COMMITTER_DATE="2026-11-12T09:00:00+07:00" \
git commit -m "Mulai proyek $p"
)
done
printf 'perubahan beta\n' > beta/catatan.txt
cd beta && git add catatan.txt && cd ..Skrip Status Harian #
Inti polanya satu loop: glob */.git menemukan semua repository di folder, ${repo%/.git} memangkas akhirannya menjadi nama folder, dan git -C menjalankan perintah tanpa cd bolak-balik:
cd ..
cat > status-harian.sh <<'EOF'
#!/bin/sh
# status-harian — ringkas status semua repository di folder ini.
for repo in */.git; do
nama="${repo%/.git}"
printf '== %s ==\n' "$nama"
git -C "$nama" status -sb
printf '\n'
done
EOF
chmod +x status-harian.sh
cd proyek && ../status-harian.shOutput yang Dihasilkan (perintah terakhir):
== alpha ==
## main
== beta ==
## main
A catatan.txtRingkasan satu-baris-per-repo lebih cocok kalau jumlahnya belasan — printf dengan lebar kolom tetap merapikannya menjadi tabel:
cd .. && cat > ringkas-harian.sh <<'EOF'
#!/bin/sh
# ringkas-harian — satu baris ringkasan per repository.
printf '%-10s %-10s %s\n' REPO BRANCH PERUBAHAN
for repo in proyek/*/.git; do
nama="$(basename "$(dirname "$repo")")"
cabang="$(git -C "proyek/$nama" rev-parse --abbrev-ref HEAD)"
perubahan="$(git -C "proyek/$nama" status --porcelain | wc -l)"
printf '%-10s %-10s %s\n' "$nama" "$cabang" "$perubahan"
done
EOF
chmod +x ringkas-harian.sh && ./ringkas-harian.shOutput yang Dihasilkan (perintah terakhir):
REPO BRANCH PERUBAHAN
alpha main 0
beta main 1git status --porcelain dipilih karena keluarannya stabil untuk diurai — satu baris per perubahan, tanpa teks penjelas yang bisa berubah antar versi.
Skrip Backup Harian #
Cadangan berkala adalah pemakaian otomasi yang paling membuahkan, dan git bundle sudah dibedah tuntas di Backup Repository
: seluruh riwayat dibungkus menjadi satu berkas biasa. Skripnya menambahkan dua hal yang membuatnya aman dipasang di cron — flag dry-run dan set -eu:
cat > backup-harian.sh <<'EOF'
#!/bin/sh
# backup-harian — bungkus semua repository menjadi bundle harian.
# pemakaian: backup-harian.sh [folder-tujuan]
# opsional: DRY_RUN=1 untuk melihat tanpa menulis
set -eu
tujuan="${1:-cadangan}"
[ "${DRY_RUN:-0}" = "1" ] && kering=1 || kering=0
mkdir -p "$tujuan"
# jalur mutlak: git -C pindah direktori dulu, jalur relatif akan
# salah sasaran
tujuan_abs="$(cd "$tujuan" && pwd)"
for repo in proyek/*/.git; do
nama="$(basename "$(dirname "$repo")")"
berkas="$tujuan_abs/$nama.bundle"
if [ "$kering" = "1" ]; then
echo "[dry-run] akan membuat $berkas"
else
git -C "proyek/$nama" bundle create "$berkas" --all
fi
done
echo "selesai: $(ls "$tujuan" | wc -l) berkas di $tujuan/"
EOF
chmod +x backup-harian.sh
DRY_RUN=1 ./backup-harian.sh cadangan-uji
./backup-harian.shOutput yang Dihasilkan (perintah terakhir):
selesai: 2 berkas di cadangan/Jalankan dengan DRY_RUN=1 lebih dulu — keluarannya [dry-run] akan membuat .../alpha.bundle dan .../beta.bundle — untuk melihat rencana tanpa efek samping. Catatan desain pentingnya:
set -eumenghentikan skrip pada kegagalan pertama — backup setengah jadi lebih menyesatkan daripada tidak ada backup.tujuan_abs="$(cd "$tujuan" && pwd)"bukan hiasan: versi awal skrip ini memakai jalur relatif, dangit -C— yang memindahkan direktori sebelum bekerja — membuatcadangan/alpha.bundleditafsirkan relatif ke dalamproyek/alpha/, sehinggagit bundle creategagal denganfatal: Unable to create '.../proyek/alpha/cadangan/alpha.bundle.lock'. Kesalahan klasik otomasi Git; jalur mutlak menutupnya.git bundle createsukses dalam diam — tidak ada keluaran; bukti kerjanyals cadangan/yang menampilkanalpha.bundledanbeta.bundle, sertagit bundle verify cadangan/alpha.bundleyang menjawabalpha.bundle is okaylengkap dengan daftar refs di dalamnya.
Skrip Sinkronisasi Batch #
Pola ketiga menjaga semuanya tetap mutakhir — git fetch --all untuk setiap repo yang punya remote, dengan kegagalan yang tidak mematikan loop (satu repo bermasalah tidak boleh menghentikan yang lain):
git init --bare pusat-alpha.git >/dev/null
git -C proyek/alpha remote add origin ../../pusat-alpha.git
git clone pusat-alpha.git rekan >/dev/null 2>&1
(
cd rekan
git config user.name "Siti Rahma"
git config user.email "[email protected]"
printf 'saran siti\n' > saran.md
git add saran.md
GIT_AUTHOR_DATE="2026-11-12T11:00:00+07:00" GIT_COMMITTER_DATE="2026-11-12T11:00:00+07:00" \
git commit -m "Tambah saran"
git push origin main >/dev/null
)
cat > sinkron-semua.sh <<'EOF'
#!/bin/sh
# sinkron-semua — ambil pembaruan dari remote untuk semua repo.
set -u
gagal=0
for repo in proyek/*/.git; do
nama="$(basename "$(dirname "$repo")")"
if git -C "proyek/$nama" remote | grep -q .; then
printf '== %s ==\n' "$nama"
if ! git -C "proyek/$nama" fetch --all; then
echo "sinkron-semua: $nama gagal fetch" >&2
gagal=1
fi
else
echo "lewati $nama: tanpa remote"
fi
done
exit $gagal
EOF
chmod +x sinkron-semua.sh
./sinkron-semua.shOutput yang Dihasilkan (perintah terakhir):
== alpha ==
From ../../pusat-alpha
5af624b..d25ee2f main -> origin/main
lewati beta: tanpa remoteSkrip memang hanya fetch — menyimpan pembaruan tanpa menyentuh working directory, pilihan sadar yang dibahas di git fetch
. Status setelahnya terbaca dari git -C proyek/alpha status -sb: ## main...origin/main [behind 1] — repo tertinggal satu commit, keputusan menariknya (merge atau rebase) tetap di tangan kita. Perhatikan pula exit $gagal: kode keluar skrip menandai apakah semua repo sehat — informasi yang akan dibaca oleh cron.
Cron: Terminal Tanpa Manusia #
Otomasi sejati berjalan sendiri, dan cron adalah waktunya — 30 6 * * * /home/z/proyek/backup-harian.sh menjalankan backup setiap pukul 06.30. Tapi cron menjalankan perintah tanpa login shell: PATH minimal, berkas profil tidak dibaca, dan identitas Git bisa gagal terurai. Simulasi paling kasarnya dengan env -i — environment kosong:
mkdir proyek-tanpa-id && git init -b main proyek-tanpa-id
printf 'tes\n' > proyek-tanpa-id/t.txt
cd proyek-tanpa-id && env -i /usr/bin/git add t.txt
env -i /usr/bin/git commit -m "tes"Output yang Dihasilkan (perintah terakhir):
Author identity unknown
*** Please tell me who you are.
Run
git config --global user.email "[email protected]"
git config --global user.name "Your Name"
to set your account's default identity.
Omit --global to set the identity only in this repository.
fatal: unable to auto-detect email address (got 'z@c-6acdae07-145cf406-01cb462c14cf.(none)')Tanpa HOME, Git tidak menemukan ~/.gitconfig, lalu gagal menebak identitas dari nama mesin. Tiga lapisan perbaikannya, dari yang paling tahan banting:
- Identitas lokal di repository —
git config user.name/user.emailper repo, persis pola sandbox kita; skrip yang meng-commit otomatis sebaiknya memang meng-commit atas identitas yang jelas. - Variabel lingkungan eksplisit di crontab —
GIT_AUTHOR_NAME,GIT_AUTHOR_EMAIL,GIT_COMMITTER_NAME,GIT_COMMITTER_EMAILmengabaikan berkas konfigurasi sepenuhnya. Uji yang sama lolos dengan kombinasi ini:env PATH=/usr/local/bin:/usr/bin:/bin HOME=/home/z GIT_AUTHOR_NAME="Budi Santoso" ... git commit -m "tes dari cron". - PATH ditulis penuh di crontab —
PATH=/usr/local/bin:/usr/bin:/bindi atas jadwal, karenagitbisa saja tidak berada di PATH bawaan cron.
Tiga skrip ini bekerja karena Git menyediakan keluaran yang stabil untuk diurai dan git -C yang meniadakan cd. Otomasi berikutnya naik satu lapisan lagi: dari mesin kita ke mesin server, di mana push dan tag bukan lagi perintah yang kita ketik, melainkan peristiwa yang memicu seluruh pipeline — CI/CD.