Bab 20 — Git, GitHub, dan Alur Kerja Proyek: Mesin Waktu untuk Kode Anda

Seri I1 — Kode yang Rapi dan Terawat

CatatanDi bab ini, Anda akan:
  • Memahami git: menyimpan riwayat versi dengan commit, melihat perbedaan, dan membatalkan perubahan
  • Bekerja di branch (cabang), menggabungkannya dengan merge, dan menyelesaikan konflik
  • Menjaga rahasia keluar dari repo dengan .gitignore
  • Mengenal GitHub: repo, issue, pull request, dan review, serta GitHub Actions dasar
  • Menutup Seri I1: proyek inventaris menjadi kelas, bertes, rapi, dan tersimpan di git

Semua skrip bab ini ada di folder kode/bab20/. Di versi web, Anda juga bisa mengunduh semuanya sekaligus (zip).

Pernahkah Anda menyimpan laporan_final.docx, lalu laporan_final2.docx, lalu laporan_final_BENAR.docx? Itu cara manual melacak versi, dan cepat kacau. Git adalah alat yang melakukannya dengan rapi: ia mencatat setiap perubahan sebagai riwayat, sehingga Anda bisa melihat siapa mengubah apa dan kapan, serta kembali ke keadaan lama bila ada yang salah. Bayangkan buku besar kebun: setiap koreksi dicatat lengkap dengan tanggalnya, tidak pernah dihapus.

Aturan keselamatan bab ini. Semua perintah git di buku ini dijalankan di repo sementara di folder _tmp/, dengan pengaturan yang terpisah dari konfigurasi git Anda. Tidak ada repo GitHub yang dibuat, tidak ada push, dan tidak ada akun yang dipakai. Bagian GitHub dijelaskan saja.

Cabang git: jalur sampingan lalu digabung ke jalur utama.

Konsep dasar git

Istilah Artinya
repo (repository) folder proyek yang riwayatnya dilacak git (ada folder tersembunyi .git)
commit satu “foto” keadaan proyek, dengan pesan tentang apa yang berubah
staging (git add) memilih perubahan mana yang akan masuk commit berikutnya
branch (cabang) jalur riwayat tersendiri; cabang utama biasa bernama main
merge menggabungkan cabang lain ke cabang yang sedang aktif
remote salinan repo di tempat lain, misalnya GitHub

Alurnya: Anda mengubah berkas, memilihnya dengan git add, lalu git commit untuk menyimpannya ke riwayat. Perintah git status memberi tahu posisi Anda. (Git mencatat nama dan email pembuat commit. Di komputer Anda, atur sekali dengan git config --global user.name "Nama Anda" dan git config --global user.email "email@anda". Skrip buku ini tidak menyentuh pengaturan itu: identitas ujinya dipasang lewat variabel lingkungan.)

01_init_commit.py
# Bab 20 - git: repo baru, berkas pertama, commit pertama (di repo sementara)
from bantu import git, repo_baru, tulis

repo = repo_baru("repo_dasar")
tulis(repo, "panen.csv", "blok,hasil_kg\nA,245.5\nB,310.0\n")

print("Status awal   :", git("status", "--short", repo=repo)[0])
git("add", "panen.csv", repo=repo)
print("Setelah add   :", git("status", "--short", repo=repo)[0])
git("commit", "--quiet", "-m", "Catat panen blok A dan B", repo=repo)
print("Setelah commit:", repr(git("status", "--short", repo=repo)[0]))

print("--- git log ---")
print(git("log", "--format=%an: %s", repo=repo)[0])
Status awal   : ?? panen.csv
Setelah add   : A  panen.csv
Setelah commit: ''
--- git log ---
Uji: Catat panen blok A dan B

Telusuri tiga baris status: ?? berarti berkas belum dilacak, A berarti sudah di-add (menunggu commit), dan teks kosong sesudah commit berarti tidak ada perubahan tertunda. Perintah git log --format=... menampilkan riwayat: satu commit dengan pesannya.

Melihat perbedaan dan membatalkan

02_ubah_dan_diff.py
# Bab 20 - Mengubah berkas, melihat perbedaan (diff), dan membatalkan perubahan
from bantu import git, repo_baru, tulis

repo = repo_baru("repo_diff")
tulis(repo, "panen.csv", "blok,hasil_kg\nA,245.5\nB,310.0\n")
git("add", ".", repo=repo)
git("commit", "--quiet", "-m", "Catat panen blok A dan B", repo=repo)

tulis(repo, "panen.csv", "blok,hasil_kg\nA,245.5\nB,312.0\nC,198.5\n")
print("Status:", git("status", "--short", repo=repo)[0])

teks, _ = git("diff", repo=repo)
print("--- git diff ---")
print("\n".join(b for b in teks.splitlines() if not b.startswith("index ")))

git("commit", "--quiet", "-a", "-m", "Koreksi blok B dan tambah blok C", repo=repo)
print("--- riwayat ---")
print(git("log", "--format=%s", repo=repo)[0])

tulis(repo, "panen.csv", "salah ketik\n")
git("restore", "panen.csv", repo=repo)                # membuang perubahan yang belum di-commit
print("--- setelah git restore ---")
print((repo / "panen.csv").read_text(encoding="utf-8"), end="")
Status:  M panen.csv
--- git diff ---
diff --git a/panen.csv b/panen.csv
--- a/panen.csv
+++ b/panen.csv
@@ -1,3 +1,4 @@
 blok,hasil_kg
 A,245.5
-B,310.0
+B,312.0
+C,198.5
--- riwayat ---
Koreksi blok B dan tambah blok C
Catat panen blok A dan B
--- setelah git restore ---
blok,hasil_kg
A,245.5
B,312.0
C,198.5
  • M (spasi lalu M) berarti berkas sudah dimodifikasi tetapi belum di-add.
  • git diff menampilkan beda: baris berawalan - dihapus, + ditambah. Di sini B,310.0 berubah menjadi B,312.0 dan baris blok C ditambahkan. (Baris teknis berawalan index dibuang dari tampilan.)
  • git commit -a menyimpan semua berkas yang sudah dilacak yang berubah, tanpa git add terpisah.
  • git restore berkas membuang perubahan yang belum di-commit. Hasil akhirnya: berkas kembali ke isi commit terakhir, jadi “salah ketik” hilang. Hati-hati: perubahan yang dibuang dengan cara ini tidak bisa dikembalikan.

Branch dan merge

Branch memungkinkan Anda mengerjakan fitur baru di jalur sampingan, tanpa mengganggu main. Kalau berhasil, jalur itu digabung. Kalau gagal, cukup dibuang.

03_cabang_merge.py
# Bab 20 - Cabang (branch) dan merge: bekerja di jalur sampingan, lalu menggabungkan
from bantu import git, rapikan, repo_baru, tulis

repo = repo_baru("repo_cabang")
tulis(repo, "unit.txt", "plot hutan\nkebun\n")
git("add", ".", repo=repo)
git("commit", "--quiet", "-m", "Daftar unit awal", repo=repo)

git("switch", "--quiet", "-c", "fitur-kolam", repo=repo)
print("Cabang aktif:", git("branch", "--show-current", repo=repo)[0])
tulis(repo, "kolam.txt", "kolam A: 800 ikan\n")
git("add", ".", repo=repo)
git("commit", "--quiet", "-m", "Tambah data kolam", repo=repo)

git("switch", "--quiet", "main", repo=repo)
print("Di main, kolam.txt ada?", (repo / "kolam.txt").exists())

tulis(repo, "unit.txt", "plot hutan\nkebun\nkandang\n")
git("commit", "--quiet", "-a", "-m", "Tambah kandang", repo=repo)

teks, _ = git("merge", "--no-edit", "fitur-kolam", repo=repo)
print("--- git merge ---")
print(teks.splitlines()[0])
print("Di main, kolam.txt ada?", (repo / "kolam.txt").exists())
print("--- git log --graph ---")
print(rapikan(git("log", "--graph", "--format=%s", repo=repo)[0]))
Cabang aktif: fitur-kolam
Di main, kolam.txt ada? False
--- git merge ---
Merge made by the 'ort' strategy.
Di main, kolam.txt ada? True
--- git log --graph ---
*   Merge branch 'fitur-kolam'
|\
| * Tambah data kolam
* | Tambah kandang
|/
* Daftar unit awal

Perhatikan bagian yang menarik: sebelum merge, kolam.txt tidak ada di main (ia hidup di cabang fitur-kolam). Sesudah merge, ia ada. Diagram git log --graph menampilkan bentuknya: dua jalur (* di kiri dan * di kanan) yang berpisah dari commit “Daftar unit awal” lalu bertemu di commit merge. Perubahan di main (“Tambah kandang”) dan di cabang (“Tambah data kolam”) menyentuh berkas yang berbeda, jadi git menggabungkannya sendiri.

Konflik: bila dua orang mengubah baris yang sama

Bagaimana bila kedua jalur mengubah baris yang sama? Git tidak berani memilih; ia berhenti dan meminta manusia memutuskan. Itu disebut konflik dan bukan tanda Anda berbuat salah, melainkan hal biasa dalam kerja bersama.

04_konflik.py
# Bab 20 - Konflik merge: dua cabang mengubah baris yang sama
from bantu import git, rapikan, repo_baru, tulis

repo = repo_baru("repo_konflik")
tulis(repo, "kolam.txt", "kolam A\nbobot rata-rata: 500 g\n")
git("add", ".", repo=repo)
git("commit", "--quiet", "-m", "Data kolam awal", repo=repo)

git("switch", "--quiet", "-c", "koreksi-timbang", repo=repo)
tulis(repo, "kolam.txt", "kolam A\nbobot rata-rata: 540 g\n")
git("commit", "--quiet", "-a", "-m", "Bobot hasil timbang ulang: 540 g", repo=repo)

git("switch", "--quiet", "main", repo=repo)
tulis(repo, "kolam.txt", "kolam A\nbobot rata-rata: 520 g\n")
git("commit", "--quiet", "-a", "-m", "Bobot dari catatan lama: 520 g", repo=repo)

teks, kode = git("merge", "--no-edit", "koreksi-timbang", repo=repo)
print("--- git merge ---")
print(teks)
print("kode keluar:", kode)
print("--- isi kolam.txt saat konflik ---")
print((repo / "kolam.txt").read_text(encoding="utf-8"), end="")
print("--- status ---")
print(git("status", "--short", repo=repo)[0])

# Penyelesaian: manusia memutuskan angka mana yang benar, lalu menghapus penanda
tulis(repo, "kolam.txt", "kolam A\nbobot rata-rata: 540 g\n")
git("add", "kolam.txt", repo=repo)
git("commit", "--quiet", "--no-edit", repo=repo)
print("--- setelah diselesaikan ---")
print(rapikan(git("log", "--graph", "--format=%s", repo=repo)[0]))
--- git merge ---
Auto-merging kolam.txt
CONFLICT (content): Merge conflict in kolam.txt
Automatic merge failed; fix conflicts and then commit the result.
kode keluar: 1
--- isi kolam.txt saat konflik ---
kolam A
<<<<<<< HEAD
bobot rata-rata: 520 g
=======
bobot rata-rata: 540 g
>>>>>>> koreksi-timbang
--- status ---
UU kolam.txt
--- setelah diselesaikan ---
*   Merge branch 'koreksi-timbang'
|\
| * Bobot hasil timbang ulang: 540 g
* | Bobot dari catatan lama: 520 g
|/
* Data kolam awal

Cara membaca berkas yang berkonflik:

  • <<<<<<< HEAD sampai ======= adalah versi di cabang Anda sekarang (main: 520 g dari catatan lama).
  • ======= sampai >>>>>>> koreksi-timbang adalah versi dari cabang yang digabung (540 g dari timbang ulang).
  • Status UU berarti kedua pihak mengubah berkas ini dan belum diselesaikan.

Untuk menyelesaikannya: sunting berkas, buang ketiga baris penanda, pilih (atau gabungkan) isi yang benar, lalu git add dan git commit. Skrip memilih 540 g karena itu hasil timbang ulang. Keputusan angka mana yang benar hanya bisa dibuat manusia yang tahu kenyataan di lapangan. Riwayat akhir memperlihatkan commit merge yang menyatukan keduanya.

.gitignore dan menjaga rahasia

Tidak semua berkas pantas masuk git: folder .venv (Bab 17), berkas sementara, hasil unduhan besar, dan terutama rahasia seperti kata sandi dan kunci API. Daftar yang diabaikan git ditulis di berkas .gitignore. Rahasia sebaiknya disimpan di berkas .env yang diabaikan, atau di variabel lingkungan, dan dibaca program lewat os.environ.

05_gitignore_rahasia.py
# Bab 20 - .gitignore dan menjaga rahasia keluar dari repo (nilai kunci di sini hanya tiruan)
from bantu import git, repo_baru, tulis

repo = repo_baru("repo_rahasia")
tulis(repo, ".env", "API_KEY=[disamarkan]\n")
tulis(repo, "app.py", "import os\nkunci = os.environ['API_KEY']\n")
tulis(repo, "_tmp/hasil.txt", "berkas sementara\n")

print("--- tanpa .gitignore ---")
print(git("status", "--short", repo=repo)[0])

tulis(repo, ".gitignore", ".env\n_tmp/\n__pycache__/\n.venv/\n")
print("--- dengan .gitignore ---")
print(git("status", "--short", repo=repo)[0])
print("Mengapa .env diabaikan?", git("check-ignore", "-v", ".env", repo=repo)[0])

git("add", ".", repo=repo)
git("commit", "--quiet", "-m", "Awal proyek", repo=repo)
print("--- berkas yang dilacak git ---")
print(git("ls-files", repo=repo)[0])

print("=== Salah langkah: rahasia terlanjur di-commit ===")
repo2 = repo_baru("repo_terlanjur")
tulis(repo2, ".env", "API_KEY=[disamarkan]\n")
git("add", ".", repo=repo2)
git("commit", "--quiet", "-m", "oops", repo=repo2)
tulis(repo2, ".gitignore", ".env\n")
git("add", ".gitignore", repo=repo2)
git("commit", "--quiet", "-m", "tambah .gitignore", repo=repo2)
print("Dilacak setelah .gitignore ditambah:", git("ls-files", repo=repo2)[0].split())
print("Isi commit pertama masih memuat .env:", ".env" in git("show", "--stat", "--format=", "HEAD~1", repo=repo2)[0])
git("rm", "--cached", "--quiet", ".env", repo=repo2)
git("commit", "--quiet", "-m", "berhenti melacak .env", repo=repo2)
print("Dilacak setelah git rm --cached:", git("ls-files", repo=repo2)[0].split())
print("Tetapi riwayat lama masih memuatnya:", ".env" in git("show", "--stat", "--format=", "HEAD~2", repo=repo2)[0])
--- tanpa .gitignore ---
?? .env
?? _tmp/
?? app.py
--- dengan .gitignore ---
?? .gitignore
?? app.py
Mengapa .env diabaikan? .gitignore:1:.env   .env
--- berkas yang dilacak git ---
.gitignore
app.py
=== Salah langkah: rahasia terlanjur di-commit ===
Dilacak setelah .gitignore ditambah: ['.env', '.gitignore']
Isi commit pertama masih memuat .env: True
Dilacak setelah git rm --cached: ['.gitignore']
Tetapi riwayat lama masih memuatnya: True

Bagian pertama: tanpa .gitignore, git menawarkan .env dan _tmp/ untuk dimasukkan (??). Dengan .gitignore, keduanya hilang dari daftar. git check-ignore -v menjelaskan aturan mana yang berlaku: baris 1 di .gitignore. Hanya .gitignore dan app.py yang dilacak.

Bagian kedua adalah jebakan paling berbahaya. Bila rahasia sudah terlanjur di-commit, menambahkannya ke .gitignore tidak cukup: berkas tetap dilacak (['.env', '.gitignore']). git rm --cached menghentikan pelacakan, tetapi riwayat lama masih memuat isinya (True). Siapa pun yang memegang salinan repo masih bisa membacanya.

PeringatanAWAS

Rahasia yang pernah masuk commit harus dianggap bocor, terutama bila repo pernah dikirim ke GitHub. Langkah paling aman (saran penulis): cabut dan ganti kuncinya di penyedia layanan, baru bersihkan riwayat. Mencegah jauh lebih mudah daripada memperbaiki: buat .gitignore sebelum commit pertama. Dokumentasi GitHub juga menjelaskan push protection, fitur yang memblokir push bila terdeteksi rahasia; ketersediaannya bergantung pada pengaturan akun dan repo (dibaca 2026-10-10), jadi jangan menjadikannya satu-satunya pagar.

GitHub: git untuk bekerja bersama

GitHub adalah layanan situs yang menyimpan repo git di internet (git dan GitHub adalah dua hal berbeda: git alatnya, GitHub salah satu tempat penyimpanannya). Bagian ini hanya penjelasan; tidak ada yang dijalankan, karena membuat repo dan mengirim kode mengubah data di internet dan butuh akun.

  • Repo di GitHub dihubungkan ke repo lokal sebagai remote (biasanya bernama origin). git push mengirim commit ke sana, git pull mengambil perubahan orang lain.
  • Issue adalah catatan tugas, ide, atau bug dalam sebuah repo. Menurut dokumentasi GitHub (dibaca 2026-10-10), issue dapat diberi label dan penanggung jawab, dan kata kunci penutup di pull request dapat menutup issue otomatis saat digabung.
  • Pull request (PR) adalah usulan menggabungkan perubahan ke proyek. Tab Files changed memperlihatkan beda yang diperiksa, tab Conversation memuat diskusi dan review, dan status merge menunjukkan hal yang masih menghalangi, seperti persetujuan yang belum ada.
  • Review: rekan membaca perubahan Anda di PR, memberi komentar, lalu Anda memperbaikinya sebelum digabung.
07_perintah_github.py
# Bab 20 - Perintah untuk menghubungkan repo lokal ke GitHub (hanya contoh; NAMA-ANDA diganti nama akun Anda)
import subprocess

perintah = [
    ["git", "remote", "add", "origin", "https://github.com/NAMA-ANDA/inventaris.git"],
    ["git", "push", "-u", "origin", "main"],              # kirim cabang main ke GitHub
    ["git", "switch", "-c", "fitur-kolam"],               # kerja di cabang baru
    ["git", "push", "-u", "origin", "fitur-kolam"],       # kirim cabang fitur
    ["gh", "pr", "create", "--fill"],                     # buka pull request (alat GitHub CLI)
]
for p in perintah:
    print("$", " ".join(p))
    subprocess.run(p, check=True)

Kode ini tidak dijalankan saat buku dibuat karena membuat repo dan mengirim (push) kode ke GitHub butuh akun GitHub dan mengubah data di internet, jadi keluarannya tidak ditampilkan. Perintah gh pr create adalah bagian dari GitHub CLI; keberadaan perintahnya diperiksa lewat gh pr create --help, tetapi tidak dijalankan.

Alur kerja harian

Tim kecil pun bisa memakai kebiasaan ini:

  1. Ambil versi terbaru (git pull).
  2. Buat cabang untuk satu pekerjaan (git switch -c nama-fitur).
  3. Ubah kode, lalu jalankan ruff check --fix ., ruff format ., dan python -m pytest (Bab 18 dan 19). pre-commit bisa mengerjakannya otomatis saat commit.
  4. Commit dengan pesan yang menjelaskan apa dan mengapa, bukan sekadar “perbaikan”.
  5. Push cabang, buka pull request, minta review.
  6. Setelah disetujui dan semua pemeriksaan hijau, merge ke main.

Kebiasaan “satu cabang, satu tujuan, commit kecil” membuat konflik jarang dan riwayat mudah dibaca.

GitHub Actions: pemeriksaan otomatis di server

Pemeriksaan di komputer sendiri bisa terlewat. GitHub Actions menjalankan pemeriksaan otomatis di server GitHub setiap ada push atau pull request. Alurnya ditulis di berkas YAML di folder .github/workflows/ (dokumentasi GitHub, dibaca 2026-10-10). Berikut contoh untuk proyek kita:

name: Tes

on:
  push:
    branches: [main]
  pull_request:

jobs:
  tes:
    runs-on: ubuntu-latest
    steps:
      - name: Ambil kode
        uses: actions/checkout@v6
      - name: Pasang Python
        uses: actions/setup-python@v7
        with:
          python-version: "3.12"
      - name: Pasang paket dan alat
        run: python -m pip install -e ".[dev]"
      - name: Periksa gaya kode
        run: |
          ruff check .
          ruff format --check .
      - name: Jalankan tes
        run: python -m pytest -q
  • on: menentukan pemicu: setiap push ke main dan setiap pull request.
  • jobs: berisi pekerjaan; runs-on: ubuntu-latest memilih mesin GitHub; steps: adalah langkah berurutan. uses: memakai aksi siap pakai, run: menjalankan perintah.
  • Nomor versi aksi (@v6, @v7) diambil dari dokumentasi pada 2026-10-10 dan bisa berubah; periksa dokumentasinya saat Anda memakainya.

Alur kerja ini tidak dijalankan oleh GitHub saat buku dibuat (tidak ada repo GitHub), jadi belum terbukti berjalan. Yang diperiksa penulis hanya sintaks YAML-nya, dan hanya itu yang bisa dibuktikan di sini:

06_periksa_yaml.py
# Bab 20 - Memeriksa sintaks berkas alur kerja GitHub Actions (hanya sintaks YAML; tidak dijalankan GitHub)
from pathlib import Path

import yaml

berkas = Path(__file__).parent / "contoh" / ".github" / "workflows" / "tes.yml"
alur = yaml.safe_load(berkas.read_text(encoding="utf-8"))

print("Nama alur kerja:", alur["name"])
print("Pemicu (on)    :", sorted(alur[True]))        # YAML membaca kata 'on' sebagai True
for nama_job, job in alur["jobs"].items():
    print(f"Job '{nama_job}' berjalan di {job['runs-on']} dengan {len(job['steps'])} langkah:")
    for langkah in job["steps"]:
        print("  -", langkah["name"])
Nama alur kerja: Tes
Pemicu (on)    : ['pull_request', 'push']
Job 'tes' berjalan di ubuntu-latest dengan 5 langkah:
  - Ambil kode
  - Pasang Python
  - Pasang paket dan alat
  - Periksa gaya kode
  - Jalankan tes

Skrip memakai pustaka PyYAML (python -m pip install pyyaml). Hasilnya menunjukkan berkas terbaca dengan benar: satu job berisi lima langkah. Catatan teknis: YAML membaca kata on sebagai True, itu sebabnya kuncinya ditulis alur[True] dalam skrip.

Proyek Bersambung I1: semua bergabung

Inilah puncak seri. Paket inventaris (kelas dari Bab 16, dipasang di Bab 17, dites di Bab 18, dirapikan di Bab 19) kini hidup di repo git dengan alur kerja yang sehat: pemeriksaan, cabang, merge.

08_proyek_akhir.py
# Bab 20 - Proyek bersambung I1: kelas + tes + ruff + git dalam satu alur kerja
import re
import shutil
import sys

from bantu import FOLDER, git, jalan, rapikan, repo_baru, tulis

repo = repo_baru("proyek_i1")
shutil.copytree(FOLDER / "proyek_akhir", repo, dirs_exist_ok=True,
                ignore=shutil.ignore_patterns("__pycache__"))
tulis(repo, ".gitignore", ".venv/\n_tmp/\n__pycache__/\n.pytest_cache/\n.ruff_cache/\n.env\n")


def pemeriksaan():
    """Tiga pemeriksaan yang juga dijalankan pre-commit: ruff check, ruff format, pytest."""
    ruff = [sys.executable, "-m", "ruff"]
    a = jalan([*ruff, "check", "--no-cache", "."], repo)[0].splitlines()[-1]
    b = jalan([*ruff, "format", "--no-cache", "--check", "."], repo)[0].splitlines()[-1]
    c = jalan([sys.executable, "-m", "pytest", "-q", "-p", "no:cacheprovider"], repo)[0]
    print("  ruff check :", a)
    print("  ruff format:", b)
    print("  pytest     :", re.sub(r" in \d+\.\d+s", "", c.splitlines()[-1]))


print("1. Pemeriksaan di main")
pemeriksaan()
git("add", ".", repo=repo)
git("commit", "--quiet", "-m", "Paket inventaris: kelas, tes, dan konfigurasi", repo=repo)

print("2. Cabang baru: tes untuk Sawah")
git("switch", "--quiet", "-c", "tes-sawah", repo=repo)
tulis(repo, "tes/test_sawah.py", '''import pytest

from inventaris import Sawah, Tanaman


def test_produktivitas_sawah():
    sawah = Sawah("Hilir", 0.5)
    sawah.tambah(Tanaman("padi", "1", 110, 1250.0))
    assert sawah.produktivitas() == pytest.approx(2500)
''')
pemeriksaan()
git("add", ".", repo=repo)
git("commit", "--quiet", "-m", "Tambah tes produktivitas sawah", repo=repo)

print("3. Gabungkan ke main")
git("switch", "--quiet", "main", repo=repo)
git("merge", "--quiet", "--no-ff", "-m", "Gabung cabang tes-sawah", "tes-sawah", repo=repo)
pemeriksaan()

print("--- riwayat ---")
print(rapikan(git("log", "--graph", "--format=%s", repo=repo)[0]))
print("Berkas dilacak git:", len(git("ls-files", repo=repo)[0].splitlines()))
print("Status akhir (kosong = bersih):", repr(git("status", "--short", repo=repo)[0]))
1. Pemeriksaan di main
  ruff check : All checks passed!
  ruff format: 9 files already formatted
  pytest     : 14 passed
2. Cabang baru: tes untuk Sawah
  ruff check : All checks passed!
  ruff format: 10 files already formatted
  pytest     : 15 passed
3. Gabungkan ke main
  ruff check : All checks passed!
  ruff format: 10 files already formatted
  pytest     : 15 passed
--- riwayat ---
*   Gabung cabang tes-sawah
|\
| * Tambah tes produktivitas sawah
|/
* Paket inventaris: kelas, tes, dan konfigurasi
Berkas dilacak git: 12
Status akhir (kosong = bersih): ''

Telusuri:

  • Langkah 1: di main, ruff lolos, 9 berkas rapi, dan 14 tes lulus. Commit pertama.
  • Langkah 2: di cabang tes-sawah, satu berkas tes baru ditambahkan. Semua pemeriksaan dijalankan sebelum commit: 10 berkas rapi dan 15 tes lulus.
  • Langkah 3: merge ke main dengan --no-ff (selalu membuat commit merge supaya jalur cabang terlihat di riwayat), lalu pemeriksaan ulang: tetap 15 tes lulus.
  • Git melacak 12 berkas: 10 berkas paket, satu berkas tes baru, dan .gitignore. Status akhir kosong: tidak ada yang tertinggal.

Latihan

  1. Jalankan 01_init_commit.py, lalu buka folder _tmp/repo_dasar di terminal dan ketik git log serta git status. Bandingkan dengan keluaran buku.
  2. Ubah 03_cabang_merge.py supaya cabang kedua menambah berkas kandang.txt. Apakah merge masih berjalan tanpa konflik? Mengapa?
  3. Tambahkan .idea/ dan *.log ke .gitignore di 05_gitignore_rahasia.py, buat berkas debug.log, dan periksa dengan git check-ignore -v debug.log.
  4. Tantangan: ubah 04_konflik.py sehingga konflik diselesaikan dengan menghitung rata-rata kedua angka (530 g), bukan memilih salah satu. Mengapa keputusan seperti ini tidak bisa diambil git sendiri?

Ringkasan Bab

  • git mencatat riwayat: git add memilih, git commit menyimpan, git status dan git diff memeriksa, git restore membuang perubahan yang belum di-commit.
  • Branch = jalur sampingan; merge menggabungkannya. Konflik muncul bila baris yang sama diubah; manusia yang memutuskan lalu commit.
  • .gitignore menjauhkan berkas sementara dan rahasia. Rahasia yang sudah ter-commit harus dianggap bocor.
  • GitHub menyimpan repo di internet; issue, pull request, dan review mengatur kerja bersama. Bagian ini hanya dijelaskan, tidak dijalankan.
  • GitHub Actions menjalankan pemeriksaan otomatis dari berkas YAML di .github/workflows/; contohnya di sini baru diperiksa sintaksnya.
  • Alur harian: cabang, ubah, ruff, tes, commit, pull request, review, merge.

Penutup Seri I1

Selamat! Anda menyelesaikan Seri I1 dan melewati gerbang pertama Tingkat Intermediate. Kemampuan Anda kini sudah bergeser dari “menulis program yang jalan” menjadi “membangun proyek yang terawat”:

  • Mengubah data lapangan menjadi kelas: Unit dan anak-anaknya, dengan pewarisan, polimorfisme, dan dataclass (Bab 16).
  • Memberi tiap proyek environment sendiri dengan venv, pip, uv, dan pyproject.toml (Bab 17).
  • Menjaga kode dengan tes pytest (Bab 18) dan ruff, pre-commit, serta logging (Bab 19).
  • Menyimpan riwayat dan bekerja bersama dengan git dan alur GitHub (Bab 20).

Proyek inventaris Anda kini rapi dan aman diubah. Tetapi seluruh datanya masih tiruan yang kita ketik sendiri. Di Seri I2, program Anda mulai mengambil dan mengolah data dari luar: internet, berkas besar, dan database.

Selanjutnya, di Bab 21: bekerja dengan API, cara program meminta data dari layanan di internet dan membaca balasannya.