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:

bash
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:

bash
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.sh

Output yang Dihasilkan (perintah terakhir):

text
== alpha ==
## main

== beta ==
## main
A  catatan.txt

Ringkasan satu-baris-per-repo lebih cocok kalau jumlahnya belasan — printf dengan lebar kolom tetap merapikannya menjadi tabel:

bash
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.sh

Output yang Dihasilkan (perintah terakhir):

text
REPO       BRANCH     PERUBAHAN
alpha      main       0
beta       main       1

git 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:

bash
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.sh

Output yang Dihasilkan (perintah terakhir):

text
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:

  1. set -eu menghentikan skrip pada kegagalan pertama — backup setengah jadi lebih menyesatkan daripada tidak ada backup.
  2. tujuan_abs="$(cd "$tujuan" && pwd)" bukan hiasan: versi awal skrip ini memakai jalur relatif, dan git -C — yang memindahkan direktori sebelum bekerja — membuat cadangan/alpha.bundle ditafsirkan relatif ke dalam proyek/alpha/, sehingga git bundle create gagal dengan fatal: Unable to create '.../proyek/alpha/cadangan/alpha.bundle.lock'. Kesalahan klasik otomasi Git; jalur mutlak menutupnya.
  3. git bundle create sukses dalam diam — tidak ada keluaran; bukti kerjanya ls cadangan/ yang menampilkan alpha.bundle dan beta.bundle, serta git bundle verify cadangan/alpha.bundle yang menjawab alpha.bundle is okay lengkap 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):

bash
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.sh

Output yang Dihasilkan (perintah terakhir):

text
== alpha ==
From ../../pusat-alpha
   5af624b..d25ee2f  main       -> origin/main
lewati beta: tanpa remote

Skrip 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:

bash
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):

text
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:

  1. Identitas lokal di repository — git config user.name/user.email per repo, persis pola sandbox kita; skrip yang meng-commit otomatis sebaiknya memang meng-commit atas identitas yang jelas.
  2. Variabel lingkungan eksplisit di crontab — GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL mengabaikan 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".
  3. PATH ditulis penuh di crontab — PATH=/usr/local/bin:/usr/bin:/bin di atas jadwal, karena git bisa 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.