Skip to content

Bab 37 — Git & GitHub

Git bukan topik Flutter, tetapi ia adalah keterampilan yang paling sering dianggap sudah dikuasai padahal tidak. Bab ini membahas apa yang benar-benar kamu butuhkan untuk mengelola proyek Flutter — termasuk hal-hal khusus Flutter seperti berkas apa yang tidak boleh ikut ter-commit.

Kenapa ini penting sekarang

Sampai bab ini, kamu sudah membangun aplikasi dengan kunci API, konfigurasi Firebase, dan berkas rahasia. Satu commit yang ceroboh bisa membocorkan semuanya ke internet secara permanen — dan menghapusnya dari riwayat Git jauh lebih sulit daripada mencegahnya.

Konsep dasar

   WORKING DIRECTORY        STAGING AREA         REPOSITORY
   (berkas yang kamu edit)  (siap di-commit)     (riwayat permanen)
          │                        │                    │
          │  git add               │  git commit        │
          ├───────────────────────►│───────────────────►│
          │                        │                    │
          │◄───────────────────────┴────────────────────┤
          │           git checkout / git restore        │

                                              git push  │

                                                     GITHUB

Working directory adalah berkas di komputermu saat ini.

Staging area adalah tempat kamu memilih perubahan mana yang akan masuk ke commit berikutnya. Ini yang memungkinkan kamu membuat commit yang rapi meskipun sedang mengerjakan beberapa hal sekaligus.

Repository adalah riwayat commit yang tersimpan permanen.

Memulai

bash
# Konfigurasi sekali di komputermu
git config --global user.name "Nama Kamu"
git config --global user.email "email@contoh.com"

# Nama branch utama — 'main' adalah standar sekarang
git config --global init.defaultBranch main

# Editor untuk pesan commit
git config --global core.editor "code --wait"

Di dalam folder proyek Flutter:

bash
git init
git add .
git commit -m "Initial commit: proyek Flutter baru"

.gitignore untuk Flutter

Ini bagian yang paling penting dan paling sering salah.

flutter create sudah membuat .gitignore dasar, tetapi ia tidak mencakup berkas rahasia yang kamu tambahkan sendiri.

gitignore
# ============================================
# Flutter & Dart
# ============================================
.dart_tool/
.packages
.pub-cache/
.pub/
build/
.flutter-plugins
.flutter-plugins-dependencies
.metadata

# Berkas ini HARUS di-commit — jangan diabaikan
!pubspec.lock

# ============================================
# IDE
# ============================================
.idea/
*.iml
*.iws
.vscode/*
!.vscode/settings.json
!.vscode/launch.json
!.vscode/extensions.json

# ============================================
# Android
# ============================================
android/.gradle/
android/local.properties
android/key.properties          # ⚠️ berisi kata sandi keystore
android/app/*.jks               # ⚠️ keystore penandatanganan
android/app/*.keystore
android/app/google-services.json  # ⚠️ kalau repositori publik

# ============================================
# iOS
# ============================================
ios/Pods/
ios/.symlinks/
ios/Flutter/flutter_export_environment.sh
ios/Flutter/Generated.xcconfig
ios/Runner/GoogleService-Info.plist  # ⚠️ kalau repositori publik
*.mobileprovision
*.p8
*.p12
*.cer

# ============================================
# Rahasia
# ============================================
.env
.env.*
!.env.example
lib/config/rahasia.dart
service-account*.json
*-adminsdk-*.json               # ⚠️ kunci Firebase Admin

# ============================================
# Sistem
# ============================================
.DS_Store
Thumbs.db
*.log

Berkas yang paling berbahaya kalau bocor

BerkasRisikonya
android/key.propertiesKata sandi keystore — orang lain bisa merilis pembaruan palsu
*.jks / *.keystoreKunci penandatanganan aplikasi
*-adminsdk-*.jsonAkses penuh ke seluruh proyek Firebase
.env dengan sk_...Kunci rahasia Stripe
*.p8Kunci APNs

Kalau salah satu ini pernah ter-commit, menghapusnya di commit berikutnya tidak cukup — ia tetap ada di riwayat. Kamu harus menganggapnya bocor dan segera membuat kunci baru.

Tentang pubspec.lock dan google-services.json

Dua berkas ini sering diperdebatkan.

pubspec.lock sebaiknya di-commit untuk aplikasi. Ia mencatat versi persis setiap paket, sehingga seluruh tim dan CI memakai versi yang sama. Untuk pustaka yang akan dipakai orang lain, jangan di-commit.

google-services.json tidak berisi rahasia (lihat Bab 27), tetapi ia menunjukkan struktur proyek Firebase-mu. Untuk repositori privat, aman di-commit. Untuk repositori publik, lebih baik diabaikan dan disediakan lewat CI.

Berkas yang sudah terlanjur ter-commit

bash
# Hapus dari Git tapi pertahankan di komputer
git rm --cached android/key.properties
echo "android/key.properties" >> .gitignore
git commit -m "Hapus berkas rahasia dari version control"

Untuk menghapusnya dari seluruh riwayat:

bash
# Perlu dipasang: pip install git-filter-repo
git filter-repo --path android/key.properties --invert-paths

Anggap kunci yang pernah ter-push sudah bocor

Menghapus dari riwayat tidak menghapusnya dari klona orang lain, dari cache GitHub, atau dari bot yang memindai repositori publik.

Kalau kunci rahasia pernah ter-push ke repositori publik, buat kunci baru dan cabut yang lama. Itu satu-satunya perbaikan yang sungguhan.

Alur kerja harian

bash
# Melihat apa yang berubah
git status
git diff                      # perubahan yang belum di-stage
git diff --staged             # yang sudah di-stage

# Memilih perubahan
git add lib/screens/beranda.dart
git add lib/                  # seluruh folder
git add .                     # semuanya
git add -p                    # interaktif, per potongan

# Commit
git commit -m "Tambah layar detail polling"

# Melihat riwayat
git log --oneline --graph --decorate --all
git log -p lib/main.dart      # riwayat satu berkas

git add -p layak dibiasakan. Ia menampilkan setiap potongan perubahan dan menanyakan apakah kamu ingin memasukkannya — sangat berguna untuk memisahkan perbaikan bug dari eksperimen yang tidak sengaja tertinggal.

Pesan commit yang baik

bash
# ❌ Tidak memberi informasi
git commit -m "update"
git commit -m "fix"
git commit -m "perubahan"

# ✅ Menjelaskan APA dan KENAPA
git commit -m "Perbaiki crash saat memuat polling tanpa gambar"
git commit -m "Tambah validasi minimal 2 opsi pada form polling"
git commit -m "Ganti ListView dengan ListView.builder di daftar polling"

Untuk perubahan yang butuh penjelasan lebih:

bash
git commit

Lalu di editor:

Perbaiki kebocoran memori pada layar chat

TextEditingController dan ScrollController tidak di-dispose,
sehingga setiap kali layar chat dibuka dan ditutup, memori
bertambah sekitar 2 MB.

Ditemukan lewat tab Memory di DevTools setelah laporan
pengguna bahwa aplikasi melambat setelah dipakai lama.

Konvensi yang banyak dipakai adalah Conventional Commits:

bash
feat: tambah fitur polling unggulan
fix: perbaiki crash saat gambar gagal dimuat
docs: perbarui README dengan cara instalasi
refactor: pisahkan logika polling ke repository
test: tambah pengujian untuk PollCubit
chore: perbarui dependensi Firebase
perf: ganti ListView dengan ListView.builder

Selain rapi, format ini memungkinkan pembuatan changelog otomatis.

Branch

Branch memungkinkan mengerjakan sesuatu tanpa mengganggu kode yang sudah berjalan.

bash
# Membuat dan berpindah sekaligus
git switch -c fitur/polling-unggulan

# Berpindah
git switch main

# Melihat daftar
git branch -a

# Menghapus setelah digabung
git branch -d fitur/polling-unggulan

switch dan restore menggantikan checkout

git checkout melakukan dua hal yang berbeda — berpindah branch dan memulihkan berkas — yang membingungkan. Git modern memisahkannya:

bash
git switch nama-branch          # berpindah branch
git restore lib/main.dart       # batalkan perubahan pada berkas
git restore --staged lib/       # keluarkan dari staging

Keduanya lebih jelas maksudnya dan lebih sulit dipakai secara keliru.

Penamaan branch

main                    ← selalu bisa dirilis
develop                 ← integrasi (kalau timnya besar)
fitur/nama-fitur        ← pengembangan fitur
perbaikan/nama-bug      ← perbaikan bug
rilis/1.2.0             ← persiapan rilis

Menggabungkan

bash
git switch main
git merge fitur/polling-unggulan

Kalau ada konflik, Git menandainya di berkas:

dart
<<<<<<< HEAD
final batasPolling = 3;
=======
final batasPolling = 5;
>>>>>>> fitur/polling-unggulan

Selesaikan dengan memilih atau menggabungkan keduanya, hapus penanda, lalu:

bash
git add lib/config.dart
git commit

GitHub

bash
# Hubungkan ke repositori remote
git remote add origin https://github.com/pengguna/repo.git
git branch -M main
git push -u origin main

# Setelahnya cukup
git push
git pull

GitHub CLI

Jauh lebih nyaman daripada bolak-balik ke peramban:

bash
# Pasang, lalu masuk
gh auth login

# Buat repositori dari folder saat ini
gh repo create nama-repo --private --source=. --push

# Buat pull request
gh pr create --title "Tambah polling unggulan" --body "Menutup #12"

# Lihat & checkout PR
gh pr list
gh pr checkout 5

Pull request

Meskipun bekerja sendiri, PR berguna: ia memberi tempat untuk meninjau perubahanmu sendiri sebelum digabung, dan menjalankan CI.

bash
git switch -c fitur/notifikasi
# ... kerjakan, commit
git push -u origin fitur/notifikasi
gh pr create

Alur kerja tim

Alurnya:

  1. Buat branch dari main untuk setiap tugas.
  2. Commit sesering mungkin di branch itu.
  3. Push dan buat pull request.
  4. Tinjau, perbaiki kalau perlu.
  5. Gabungkan ke main.
  6. Hapus branch.

Memperbaiki kesalahan

Bagian yang paling sering dibutuhkan dan paling menakutkan.

bash
# Batalkan perubahan yang belum di-stage
git restore lib/main.dart

# Keluarkan dari staging (perubahan tetap ada)
git restore --staged lib/main.dart

# Ubah pesan commit terakhir
git commit --amend -m "Pesan yang benar"

# Tambahkan berkas yang lupa ke commit terakhir
git add berkas-yang-lupa.dart
git commit --amend --no-edit

# Batalkan commit terakhir, pertahankan perubahannya
git reset --soft HEAD~1

# Batalkan commit terakhir dan buang perubahannya
git reset --hard HEAD~1     # ⚠️ perubahan hilang permanen

# Batalkan commit yang SUDAH di-push (aman)
git revert abc1234

reset versus revert

  • reset menulis ulang riwayat. Aman untuk commit yang belum di-push.
  • revert membuat commit baru yang membatalkan commit lama. Ini yang benar untuk commit yang sudah di-push.

Melakukan reset lalu push --force pada branch bersama akan menghapus pekerjaan orang lain. Jangan lakukan itu di main.

Stash: menyimpan sementara

bash
# Simpan perubahan yang sedang dikerjakan
git stash push -m "Setengah jadi: layar profil"

# Lihat daftar
git stash list

# Ambil kembali
git stash pop            # ambil dan hapus dari stash
git stash apply          # ambil tapi biarkan di stash

# Buang
git stash drop

Berguna ketika kamu sedang mengerjakan sesuatu lalu harus segera memperbaiki bug di branch lain.

Menemukan commit yang menyebabkan bug

bash
git bisect start
git bisect bad                # commit sekarang bermasalah
git bisect good v1.0          # versi ini masih baik

# Git akan checkout ke commit di tengah.
# Uji aplikasinya, lalu:
git bisect good    # atau: git bisect bad

# Ulangi sampai Git menemukan commit penyebabnya
git bisect reset

Untuk riwayat 1.000 commit, ini menemukan penyebabnya dalam sekitar 10 langkah.

GitHub Actions untuk Flutter

Otomatiskan pemeriksaan setiap kali kamu push.

yaml
# .github/workflows/flutter.yml
name: Flutter CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  analisis-dan-uji:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.27.x'
          channel: stable
          cache: true

      - name: Pasang dependensi
        run: flutter pub get

      - name: Periksa format
        run: dart format --output=none --set-exit-if-changed .

      - name: Analisis statis
        run: flutter analyze --fatal-infos

      - name: Jalankan pengujian
        run: flutter test --coverage

      - name: Unggah cakupan
        uses: codecov/codecov-action@v4
        with:
          file: coverage/lcov.info

  bangun-android:
    needs: analisis-dan-uji
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version: '17'

      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.27.x'
          channel: stable
          cache: true

      - name: Pulihkan berkas rahasia
        env:
          GOOGLE_SERVICES: ${{ secrets.GOOGLE_SERVICES_JSON }}
        run: echo "$GOOGLE_SERVICES" > android/app/google-services.json

      - run: flutter pub get

      - name: Bangun APK
        run: flutter build apk --release --dart-define=STRIPE_PUBLISHABLE_KEY=${{ secrets.STRIPE_PK }}

      - uses: actions/upload-artifact@v4
        with:
          name: apk-rilis
          path: build/app/outputs/flutter-apk/app-release.apk

Berkas rahasia disimpan di GitHub → SettingsSecrets and variablesActions, lalu dipulihkan saat build. Ini yang memungkinkan kamu mengabaikan google-services.json di .gitignore tanpa merusak CI.

Menandai rilis

bash
git tag -a v1.2.0 -m "Rilis 1.2.0: polling unggulan & notifikasi"
git push origin v1.2.0

# Buat rilis GitHub sekaligus
gh release create v1.2.0 \
  --title "Versi 1.2.0" \
  --notes "Fitur baru: polling unggulan, push notification"

Tag berguna karena memberi nama pada titik tertentu di riwayat — jauh lebih mudah diingat daripada abc1234.

Hook: pemeriksaan otomatis sebelum commit

bash
# .git/hooks/pre-commit — jangan lupa: chmod +x
#!/bin/sh

echo "Memeriksa format..."
if ! dart format --output=none --set-exit-if-changed .; then
  echo "❌ Format tidak sesuai. Jalankan: dart format ."
  exit 1
fi

echo "Menjalankan analisis..."
if ! flutter analyze --no-pub; then
  echo "❌ Analisis gagal."
  exit 1
fi

echo "✅ Semua pemeriksaan lolos."

Hook ini mencegah commit yang tidak lolos analisis. Untuk berbaginya dengan tim (hook di .git/ tidak ikut ter-commit), pakai paket lefthook atau husky.

Praktik yang baik

Commit kecil dan sering. Satu commit = satu perubahan logis. Ini membuat git bisect berguna dan revert presisi.

Jangan commit kode yang tidak berjalan ke main. Branch pribadi bebas.

Tarik sebelum mendorong.

bash
git pull --rebase origin main

--rebase menghasilkan riwayat linier yang lebih mudah dibaca daripada merge commit yang bertumpuk.

Tinjau sebelum commit.

bash
git diff --staged

Sering kali ada print() atau kode eksperimen yang tertinggal.

Jangan commit berkas yang dihasilkan. build/, .dart_tool/, ios/Pods/ bisa dibuat ulang dan hanya memperbesar repositori.

Latihan Mandiri

Kerjakan salah satu, beberapa, atau semuanya secara berurutan untuk melatih pemahamanmu sampai benar-benar lekat.

Variasi 1: Satu Siklus Kerja Penuh — ⭐⭐ · 40–60 menit

Tantangan: Ambil satu proyek Flutter yang sudah kamu punya dan belum berada di Git. Buat repositorinya dengan benar dari nol, lalu jalani satu siklus kerja lengkap: branch, beberapa commit, pull request, dan gabungkan. Ini latihan yang benar-benar dikerjakan, bukan dibaca.

Kriteria selesai:

  • .gitignore mencakup berkas rahasia yang tidak ada di template bawaan Flutter — key.properties, *.jks, *-adminsdk-*.json, .env.
  • pubspec.lock ikut di-commit, dan kamu tahu kenapa (dan kapan sebaliknya).
  • Ada minimal lima commit dengan pesan yang menjelaskan apa dan kenapa, bukan "update" atau "fix".
  • Satu pull request sungguhan di GitHub, dengan deskripsi yang bisa dibaca orang lain, lalu digabungkan.
  • Kamu sudah mencoba git add -p setidaknya sekali untuk memecah satu perubahan besar menjadi dua commit yang terpisah rapi.

Petunjuk: Sebelum commit pertama, luangkan waktu memeriksa git status baris demi baris. Berkas rahasia yang ikut di commit pertama adalah masalah yang jauh lebih mahal daripada terlihat: menghapusnya di commit berikutnya tidak menghilangkannya dari riwayat, dan kalau repositorinya publik, kuncinya harus dianggap sudah bocor — buat yang baru, jangan sekadar hapus. Untuk git add -p, jalankan setelah mengubah dua hal yang tidak berhubungan dalam satu berkas; ia akan menawarkan setiap potongan satu per satu. Terasa aneh dua kali pertama, lalu menjadi kebiasaan.

Variasi 2: Membatalkan dengan Benar — ⭐⭐ · 30–45 menit

Tantangan: Di repositori latihan, buat empat situasi kesalahan dengan sengaja lalu pulihkan masing-masing dengan perintah yang tepat: perubahan yang belum di-stage tetapi ingin dibuang, commit terakhir yang belum di-push dan ingin diubah, commit yang sudah di-push dan harus dibatalkan, dan pekerjaan setengah jadi yang harus ditinggalkan sementara.

Kriteria selesai:

  • Keempat situasi dibuat dan dipulihkan, dan kamu mencatat perintah yang dipakai untuk masing-masing.
  • Kamu bisa menjelaskan kenapa commit yang sudah di-push dibatalkan dengan revert, bukan reset.
  • git stash dipakai setidaknya sekali, termasuk mengembalikannya lagi.
  • Tidak ada push --force ke branch yang dipakai bersama.

Petunjuk: Perbedaan reset dan revert menjadi jelas begitu kamu memikirkan orang lain: reset menulis ulang riwayat, dan riwayat yang sudah dimiliki orang lain tidak bisa ditulis ulang tanpa merusak salinan mereka. revert membuat commit baru yang membatalkan efek commit lama — riwayatnya tetap utuh dan aman bagi semua orang. Untuk git restore dan git switch: keduanya menggantikan git checkout yang dulu mengerjakan dua hal berbeda dengan nama yang sama.

Variasi 3: Menelusuri Riwayat — ⭐⭐⭐ · 45–60 menit

Tantangan: Ambil repositori dengan riwayat yang cukup panjang — milikmu atau proyek sumber terbuka mana pun — lalu jawab empat pertanyaan hanya dengan perintah Git: kapan sebuah baris terakhir diubah dan oleh siapa, commit mana yang memperkenalkan sebuah bug, berkas apa yang paling sering diubah, dan apa yang berubah antara dua rilis.

Kriteria selesai:

  • git bisect dipakai untuk menemukan commit penyebab, bukan pemeriksaan satu per satu.
  • git blame dipakai dan kamu bisa membaca keluarannya.
  • Kamu bisa menghasilkan daftar perubahan antara dua tag.
  • Kamu menemukan berkas yang paling sering diubah dan punya dugaan kenapa.

Petunjuk: git bisect menemukan commit penyebab dalam jumlah langkah yang logaritmik — riwayat seribu commit selesai dalam sekitar sepuluh pemeriksaan. Ia terasa seperti sihir pertama kali dipakai, dan syarat satu-satunya adalah kamu bisa menjawab "rusak atau tidak" pada setiap titik. Berkas yang paling sering diubah biasanya menunjukkan sesuatu tentang desain — ia mungkin memegang terlalu banyak tanggung jawab.

Variasi 4: Pemeriksaan Otomatis — ⭐⭐⭐ · 45–60 menit

Tantangan: Siapkan GitHub Actions untuk proyek Flutter-mu yang menjalankan format, analisis, dan pengujian pada setiap pull request. Tambahkan juga hook lokal yang menolak commit kalau formatnya belum rapi.

Kriteria selesai:

  • Alur kerja berjalan pada pull request dan hasilnya terlihat di halaman PR.
  • Pull request yang gagal pemeriksaan tidak bisa digabungkan.
  • Ada satu berkas rahasia yang dipulihkan dari GitHub Secrets saat build, bukan disimpan di repositori.
  • Hook lokal berjalan cepat — di bawah beberapa detik — supaya tidak mengganggu.

Petunjuk: Hook yang lambat akan dilewati orang, termasuk dirimu sendiri beberapa minggu lagi. Batasi hook lokal pada pemeriksaan yang cepat seperti format, dan serahkan pengujian penuh ke CI yang berjalan di latar belakang. Untuk berkas rahasia di CI, polanya adalah menyimpan isinya dalam bentuk terkodekan sebagai secret, lalu menuliskannya kembali menjadi berkas di awal build.

Variasi 5: Kerja Bersama — ⭐⭐⭐⭐ · 60–90 menit

Tantangan: Simulasikan kerja tim: kloning repositorimu ke dua folder berbeda dan perlakukan keduanya sebagai dua orang. Buat konflik penggabungan yang sungguhan, selesaikan, lalu jalani satu siklus tinjauan kode lengkap termasuk perubahan yang diminta.

Kriteria selesai:

  • Konflik dibuat, dipahami, dan diselesaikan tanpa membuang perubahan salah satu pihak.
  • Satu pull request menerima komentar tinjauan, lalu commit perbaikan, lalu digabungkan.
  • Branch yang sudah digabungkan dibersihkan di lokal maupun di remote.
  • Kamu menulis konvensi tim singkat: penamaan branch, format pesan commit, dan siapa yang boleh menggabungkan.

Petunjuk: Konflik penggabungan menakutkan sampai kamu menyadari Git tidak pernah menebak — ia menandai persis bagian yang tidak bisa ia putuskan sendiri dan menyerahkan keputusannya padamu. Yang membuat konflik terasa buruk biasanya adalah ukurannya, dan ukurannya berbanding lurus dengan berapa lama branch dibiarkan hidup. Branch pendek yang sering digabungkan hampir tidak pernah menghasilkan konflik yang menyakitkan.

Ikhtisar

  • Git punya tiga area: working directory, staging, dan repository. Staging yang memungkinkan commit rapi.
  • .gitignore bawaan Flutter tidak mencakup berkas rahasia yang kamu tambahkan — key.properties, *.jks, *-adminsdk-*.json, .env.
  • Berkas rahasia yang pernah ter-push ke repositori publik harus dianggap bocor. Buat kunci baru; menghapus dari riwayat tidak cukup.
  • Commit pubspec.lock untuk aplikasi (bukan untuk pustaka).
  • Pesan commit menjelaskan apa dan kenapa, bukan "update" atau "fix". Conventional Commits (feat:, fix:, refactor:) memudahkan changelog.
  • git switch dan git restore menggantikan git checkout yang membingungkan.
  • reset untuk commit yang belum di-push; revert untuk yang sudah. Jangan pernah push --force ke branch bersama.
  • git add -p memisahkan perubahan per potongan — berguna untuk commit yang bersih.
  • git stash untuk menyimpan pekerjaan setengah jadi saat harus berpindah.
  • git bisect menemukan commit penyebab bug dalam waktu logaritmik.
  • GitHub Actions menjalankan format, analisis, dan pengujian otomatis; berkas rahasia disimpan di Secrets dan dipulihkan saat build.
  • Tandai rilis dengan tag agar titik penting di riwayat mudah ditemukan.
  • Commit kecil dan sering; git pull --rebase menjaga riwayat tetap linier.

Berikutnya: Bab 38 — Branding & Rilis.

Transkrip asli

Disintesis dari 4_flutter_ai-chatbot-n-firebase/9_version-control-with-git-and-github.md (4 video: GitHub Desktop Installation and Setup, Creating Repository and Pushing Changes, Cloning Repository and Creating Branches, Merging Branches and Discarding Unwanted Changes). Lihat PDF Firebase & AI.

Rangkuman pembelajaran pribadi, disusun ulang dari beberapa kursus Flutter.