Skip to content
Blog Programming

Bagaimana Saya Mengubah Catatan untuk Mesin Jadi Catatan untuk Orang

By Romi Fadli, S.Kom. 17 min read Tooling & Automation
On this page
One source branching into two panels: an uneven stream of lines above, a two-column grid below

Ada yang bertanya apa saja yang saya kerjakan bulan lalu. Jawabannya sudah ada — ia menumpuk di sebuah repository sepanjang waktu itu, satu commit per satuan pekerjaan, bertanggal dan berurutan. Yang belum ada adalah versi jawaban itu yang sanggup dibaca orang yang bertanya.

Pendahuluan

A. Pesan commit ditulis untuk siapa

Pesan commit punya dua pembaca, dan tak satu pun di antaranya adalah orang yang menanyakan bulan lalu kepada saya.

Pembaca pertama git sendiri, yang membutuhkan pesannya cuma sebagai label — sesuatu untuk dicetak di samping hash supaya satu revisi bisa dibedakan dari revisi lain. Git tak membaca pesannya; ia menyimpannya.

Pembaca kedua saya sendiri enam bulan lagi, menatap satu baris di tengah bisect sambil berusaha memahami kenapa sebuah fungsi berubah. Versi saya yang itu berbagi seluruh konteks: project-nya, kosakatanya, arti singkatan-singkatannya, subsistem mana yang dimaksud "resolver". Saya menulis untuk orang itu, dan saya menulis dengan hemat, sebab orang itu sudah tahu hampir segalanya.

Tak satu pun dari keduanya adalah atasan atau klien. Keduanya membaca teks yang sama tanpa konteks bersama sedikit pun dan dengan pertanyaan yang sama sekali berbeda di kepala — bukan "kenapa ini berubah" melainkan "apa yang berhasil dikerjakan".

B. Apa yang berubah ketika pembacanya berganti

Bagian menariknya adalah seberapa banyak yang harus bergeser begitu pembaca ketiga itu muncul, dan seberapa sedikit dari pergeseran itu yang menyangkut isi pesannya.

Kata-katanya tetap. Yang berubah justru segala yang mengelilinginya: kolom mana yang ditampilkan, urutan barisnya seperti apa, tanggalnya dirender bagaimana, pekerjaan siapa yang ikut, dan apakah hasilnya berupa aliran teks atau file yang bisa dibuka di spreadsheet lalu disortir.

Tiap satunya keputusan kecil, dan tiap satunya punya jawaban yang jelas benar untuk developer yang membaca di terminal sekaligus jawaban lain yang jelas benar untuk orang yang menilai sebulan pekerjaan. Jarak antara kedua kumpulan jawaban itulah yang disebut laporan.

C. Kenapa ini bukan menulis laporannya

Saya ingin tepat soal cakupannya, sebab ada versi lain dari perkara ini yang jauh lebih besar dan bukan itu yang dibahas di sini.

Mengubah commit jadi narasi — mengelompokkan pekerjaan yang berkerabat, menjelaskan maknanya, menyebut bagian mana yang penting — itu menulis, dan tak ada tool yang mengerjakannya. Yang sanggup dikerjakan sebuah tool adalah menghasilkan bahan mentahnya dalam bentuk yang memungkinkan penulisan itu: daftar yang akurat, berurutan, dan terbaca tentang apa yang terjadi dan kapan — yang kalau tidak, harus saya susun sendiri dari ingatan sambil menggulir layar.

Jadi keluaran di sini bukan laporan. Ia bahan berbentuk spreadsheet untuk sebuah laporan, dan pembedaan itu menentukan sebab ia menjelaskan kenapa formatnya CSV ketimbang format yang lebih enak dipandang.

D. Kenapa jawabannya sebuah file, bukan sebuah perintah

Membaca riwayat di terminal sudah terpecahkan — git mengerjakannya, dan mengerjakannya dengan baik. Alasan itu belum cukup sama sekali tak menyangkut kemampuan.

Terminal menghasilkan aliran yang menggulir lewat. Orang yang membutuhkan ini ingin menyortirnya, menyaring satu minggu, menghitung barisnya, lalu menempelkan sebagian ke dokumen yang lebih besar. Semua itu operasi spreadsheet, dan semuanya menuntut file berkolom beserta pemisahnya — benda yang berbeda dari aliran teks terformat, bahkan ketika keduanya memuat keterangan yang sama.

E. Tujuh hambatan yang menghalangi

Jadi tujuannya gampang dirumuskan: menghasilkan, dari sebuah repository, catatan akurat tentang apa yang dikerjakan sepanjang rentang waktu tertentu, dalam bentuk yang terbuka di spreadsheet dan terbaca benar oleh orang yang tak pernah memakai git. Bisa diulang, sebab pertanyaannya datang lagi tiap bulan.

Gampang dirumuskan, canggung dibangun. Tujuh hambatan berdiri antara tujuan itu dan sebuah file yang berani saya serahkan kepada orang, dan tiap satunya tempat di mana default git benar untuk developer sekaligus keliru untuk keperluan ini.

Yang pertama, keluaran git dibentuk untuk orang yang men-debug, bukan orang yang menilai. Entri log bawaannya dibuka dengan hash, disusul nama author dan timestamp lengkap, lalu menghiasi branch dan tag — seluruhnya penting saat memburu regresi dan seluruhnya kebisingan di ringkasan pekerjaan. Perbaikan yang menggoda adalah memangkasnya langsung jadi dua kolom yang memang dibutuhkan, dan itu ternyata agak keliru karena alasan yang baru muncul belakangan.

Yang kedua, git mengurutkan riwayat terbalik dari cara laporan dibaca. Terbaru dulu memang benar untuk terminal, di mana puncak layar tempat perhatian bermula dan commit terkini biasanya yang dicari orang. Laporan sebulan dibaca dari awal bulan, dan daftar yang mengalir sebaliknya memaksa pembacanya membalik urutannya di kepala sebelum ia sanggup mengikuti apa pun.

Yang ketiga, timestamp membawa zona waktu sementara laporan tidak. Git mencatat kapan sebuah commit terjadi lengkap dengan offset tempat ia terjadi. Laporan berurusan dengan tanggal, bukan momen. Merender timestamp itu tanpa memutuskan zona mana yang berlaku berarti commit yang dibuat larut malam bisa mendarat di hari berikutnya — dan di batas bulan, itu menaruh pekerjaan di laporan yang sama sekali keliru.

Yang keempat, git mengandaikan ada manusia yang menonton. Ketika keluarannya melebihi satu layar, git membuka pager lalu menunggu. Itu menolong di terminal dan fatal di dalam script, di mana pager-nya tak punya siapa pun untuk menekan tombol dan seluruhnya berhenti, tanpa menghasilkan apa pun, tanpa error yang menjelaskan sebabnya.

Yang kelima, repository memuat pekerjaan semua orang. Tiap commit di repository bersama ada di dalam log, dan laporan pekerjaan saya cuma sebagian darinya. Penyaringan jadi wajib — dan penyaring itulah keputusan paling berkonsekuensi di seluruh tool ini, sebab dialah yang diam-diam menentukan apa yang diklaim dicakup laporannya.

Yang keenam, rentang waktu adalah pertanyaannya, bukan pengaturannya. Memakai bulan berjalan sebagai default memang nyaman, sebab itu keadaan yang paling sering. Ia juga cukup sering keliru sampai berarti: pertanyaannya datang sebagai kuartal, sebagai sprint, sebagai rentang satu project. Default yang biasanya benar menghasilkan laporan yang percaya diri membahas tanggal yang keliru.

Yang ketujuh, satu angka yang meleset merusak kepercayaan pada angka lainnya. Tool-nya melaporkan berapa commit yang ia ekspor, dan angka itu sepele diturunkan dari file yang baru saja ia tulis — kecuali bahwa file-nya punya baris header, dan header bukan commit. Keliru satu di sini menghasilkan angka yang diam-diam palsu, di dalam dokumen yang seluruh tujuannya justru untuk dipercaya.

Masing-masing punya seksi di bawah. Empat yang pertama soal membentuk ulang keluarannya; dua berikutnya soal memutuskan apa yang berhak masuk; yang terakhir soal laporannya bisa dipercaya — dan cuma sifat itulah yang berarti begitu ada orang lain yang membacanya.

Metode

A. Mengambil empat kolom lalu menyimpan dua

Inilah jawaban atas hambatan pertama, dan ia memuat keputusan yang kelihatan mubazir sampai alasannya muncul.

bash
git --no-pager log \
  --no-decorate \
  --format="%h|%an|%ad|%s" \
| awk -F "|" 'BEGIN {print "Date;Message"} {print $3 ";" $4}'

Empat kolom diminta dari git: hash pendek, nama author, tanggal, subjek. Lalu awk, tool pengolah teks kecil yang memecah tiap baris menjadi kolom, cuma mencetak yang ketiga dan keempat. Dua yang pertama diambil lalu seketika dibuang.

Meminta tanggal dan subjek saja kepada git akan menghasilkan keluaran identik dengan kerja lebih sedikit, dan saya sengaja tak melakukannya. Kedua baris itu lapisan yang berbeda. Tugas git mengekstrak kolom dari repository; tugas penyaringnya memutuskan mana di antaranya yang pantas dilihat pembaca. Memisahkan keduanya membuat perubahan atas apa yang tampil di laporan cukup jadi perubahan pada satu ungkapan awk, tanpa menyentuh bagian yang berbicara dengan git.

Ini juga menjelaskan kedua pemisahnya, yang mengerjakan tugas berbeda walau rupanya seperti trik yang sama. Garis tegak adalah batas kolom milik git, internal di dalam pipeline dan tak pernah dilihat siapa pun. Titik koma adalah batas kolom CSV, bagian dari benda hasilnya. Yang satu milik perpipaan dan yang lain milik keluarannya, dan meleburkan keduanya jadi satu pilihan akan mengikat format laporannya pada cara datanya diambil.

--no-decorate membuang keterangan branch dan tag. Di terminal keduanya memberitahu developer ia sedang di mana; di laporan keduanya kosakata kedua yang tak berguna bagi pembacanya dan tak ada cara menafsirkannya.

B. Menjalankan riwayatnya maju

Inilah jawaban atas hambatan kedua, dan ia satu flag.

bash
git --no-pager log --reverse ...   # oldest first: the order a report is read in

Default git — terbaru dulu — benar untuk tujuan biasanya. Yang membuka log hampir selalu menanyakan pekerjaan terkini, dan menaruhnya di puncak berarti jawabannya langsung ada di layar.

Laporan justru kebalikannya. Ia mencakup rentang yang sudah berakhir, dan ia dibaca sebagai urutan: beginilah bulan itu dimulai, begini jalannya, begini akhirnya. Menyajikannya terbalik tak membuatnya mustahil dibaca, tapi memaksa pembacanya mengerjakan satu transformasi sebelum ia sanggup mengikuti bentuk pekerjaannya — dan pembaca yang harus berjuang pada formatnya menyisakan lebih sedikit perhatian untuk isinya.

Satu flag, dan itulah beda antara daftar commit dengan catatan sebulan pekerjaan.

C. Memutuskan sebuah tanggal itu artinya apa

Inilah jawaban atas hambatan ketiga, dan ia pilihan format yang sebenarnya pilihan makna.

bash
--date=format-local:%Y-%m-%d

Ada dua perkara yang sedang diputuskan. Formatnya membuang jamnya sama sekali, menyisakan tanggal kalender, sebab laporan adalah catatan atas hari dan jam sebuah commit dibuat tak membawa makna apa pun bagi yang membacanya.

Bagian local-nya yang sungguh menentukan. Git menyimpan tiap commit beserta offset zona waktu mesin yang membuatnya, dan merender itu tanpa mengambil keputusan berarti tanggal yang tampil bergantung pada saya sedang duduk di mana ketimbang pada hari kerja mana ia termasuk. Menambatkan segalanya ke waktu lokal membuat tanggalnya berarti seperti yang diandaikan pembacanya: hari sebagaimana terasa saat pekerjaannya berlangsung.

Akibatnya kelihatan di batas rentang. Commit yang dibuat menjelang tengah malam di hari terakhir sebuah bulan adalah pekerjaan bulan itu, dan render yang menggesernya maju menaruhnya di laporan berikutnya — sekaligus jadi tambahan bagi rentang yang bukan miliknya dan kekosongan di rentang yang memang miliknya.

D. Membuang andaian bahwa ada yang menonton

Inilah jawaban atas hambatan keempat, dan ia flag yang sama sekali tak mengubah isinya.

bash
git --no-pager log ...   # write to the pipe, do not open a pager

Git membuka pager ketika keluarannya panjang, dan itu perilaku yang penuh perhatian untuk pemakaian interaktif sekaligus kegagalan telak di dalam script. Pager-nya menunggu tombol ditekan. Tak ada siapa pun di sana. Script-nya tak error, tak kehabisan waktu, dan tak menghasilkan file — ia sekadar berhenti, dan satu-satunya gejalanya perintahnya tak pernah kembali.

Yang membuat ini pantas dipertimbangkan sendiri adalah cara kegagalannya menampakkan diri. Script yang jatuh memberitahu di mana. Script yang menggantung tak memberitahu apa-apa, dan sebabnya justru fitur yang dirancang untuk menolong, di keadaan yang tak punya siapa pun untuk ditolong.

E. Memilih pekerjaan siapa yang dihitung

Inilah jawaban atas hambatan kelima, dan ia satu baris yang membentuk seluruh makna keluarannya.

bash
--author tnromy   # a personal report: my commits, not the repository's

Repository bersama memuat commit semua orang berselang-seling. Laporan pekerjaan saya cuma sebagian darinya, dan penyaringnya yang menetapkan bagian mana.

Memaku nama author di dalam script ketimbang membukanya sebagai argumen adalah keputusan tentang keadaan yang paling sering. Nyaris tiap kali saya menjalankan ini, jawabannya saya sendiri, dan argumen yang akan saya isi sama persis tiap kali adalah argumen yang cepat atau lambat terisi keliru.

Ia sekaligus menetapkan cakupan tool ini dengan sengaja. Laporan yang mencakup satu tim butuh penyaring lain atau tak butuh penyaring sama sekali, dan itu pertanyaan yang berbeda; tool ini menjawab satu pertanyaan dengan baik, dan pertanyaan itu tentang satu orang.

F. Menjadikan rentangnya sebuah argumen

Inilah jawaban atas hambatan keenam: rentangnya wajib, dan script-nya menolak menebak.

bash
if [ "$#" -lt 3 ]; then
  echo "Usage: $0 <after_date> <before_date> <output_path>"
  exit 1
fi

Memakai bulan berjalan sebagai default memang menggoda dan akan benar di sebagian besar kesempatan. Alasan penolakannya terletak pada ongkos "sebagian besar" di sini: pertanyaannya datang sebagai kuartal ketika ada yang sedang melakukan tinjauan, sebagai rentang project ketika klien bertanya apa yang sudah diserahkan, sebagai sepasang tanggal sembarang ketika sebuah rentang perlu disusun ulang belakangan.

Tool yang memilih default masuk akal pada keadaan semacam itu menghasilkan file yang tampak benar sambil mencakup tanggal yang keliru — dan tak ada di keluarannya yang menyatakan begitu, sebab tanggal di dalamnya seluruhnya nyata. Mewajibkan kedua batasnya berarti rentang di dalam file selalu rentang yang ditanyakan.

Argumen ketiganya path keluaran, dan folder yang ia sebut dibuat bila belum ada:

bash
mkdir -p "$(dirname "$OUTPUT_PATH")"

Laporan ditulis ke folder yang disusun menurut rentangnya, dan folder itu kerap belum ada. Gagal karena satu folder belum dibuat akan jadi kegagalan soal pembukuan filesystem di dalam tool yang pokok urusannya sebuah repository.

G. Menghitung yang sungguh terekspor

Inilah jawaban atas hambatan ketujuh, dan ia perkara aritmetika yang memikul bobot lebih besar dari ukurannya.

bash
TOTAL_LINES=$(wc -l < "$OUTPUT_PATH")
COMMIT_COUNT=$((TOTAL_LINES - 1))   # the header row is not a commit

Script-nya melaporkan berapa commit yang ia ekspor, dan sumber paling kentara untuk angka itu adalah file yang baru saja ia tulis. Sumber paling kentara itu meleset satu, sebab awk mencetak satu baris header dan header bukan commit.

Angkanya sendiri sepele. Yang dipengaruhinya tidak. Laporan diserahkan di atas premis bahwa ia akurat, dan pembaca yang menyadari jumlah yang disebut tak cocok dengan barisnya sudah belajar sesuatu tentang dokumennya secara keseluruhan — bukan bahwa satu angka keliru, melainkan bahwa angkanya tak diperiksa. Segala isi lainnya berubah dari fakta jadi klaim.

Hasil dan Pembahasan

A. Tiap keputusan adalah keputusan yang sama

Dijajarkan bersama, flag-flag itu berhenti terlihat sebagai kumpulan perbaikan yang tak berkaitan.

KeputusanBenar untuk developerBenar untuk laporan
Kolom yang tampilHash, author, timestamp lengkapTanggal dan subjek saja
UrutanTerbaru duluTerlama dulu
TanggalMomen persis beserta offsetTanggal kalender, lokal
Keluaran panjangMembuka pagerMenulis ke file
Commit siapaSemua orangSatu orang

Tak satu pun default git yang keliru. Tiap satunya pilihan yang benar bagi orang yang membaca di terminal dengan seluruh konteks repository-nya di kepala. Seluruh tool ini empat puluh lima baris yang menimpa default yang benar, semata-mata karena pembacanya berganti.

Pembingkaian ulang itulah yang saya bawa dari membangunnya. Pekerjaannya tak pernah ada pada mengalahkan perilaku git; melainkan pada menyadari bahwa tiap default memuat andaian tentang siapa yang membaca, dan bahwa seluruh andaian itu jadi keliru sekaligus begitu keluarannya meninggalkan terminal.

B. Kenapa format keluarannya tak menarik dipandang

CSV bukan format yang enak dilihat, dan bukan untuk itu ia ada.

File-nya bukan barang yang diserahkan. Ia bahan bagi apa pun yang nanti jadi barang serahannya — ringkasan yang ditulis di sebuah dokumen, tabel yang ditempel ke slide, angka yang dikutip di email. Yang dibutuhkan orang yang mengerjakan itu adalah menyortir menurut tanggal, menyaring satu minggu, menghitung baris, lalu menyalin sebagian — dan semuanya dikerjakan spreadsheet dengan baik sementara dokumen terformat tidak.

Markdown atau PDF akan lebih enak dipandang sekaligus lebih sulit dipakai, sebab keduanya dioptimalkan untuk dibaca apa adanya, sementara yang ini tak dibaca apa adanya. Memilih format yang lebih polos berarti memilih melayani langkah berikutnya ketimbang langkah sekarang.

C. Di mana ini berhenti jadi tool yang tepat

Cakupannya sempit dengan cara-cara yang pantas dinyatakan terang-terangan.

Penyaring author-nya dipaku, jadi laporan tim di luar jangkauannya tanpa menyunting script. Ia membaca satu repository, jadi pekerjaan yang tersebar di beberapa repo menuntut beberapa kali jalan lalu digabung dengan tangan. Lalu ia melaporkan commit, dan commit cuma proksi atas pekerjaan ketimbang ukuran atasnya — seminggu perancangan yang cermat bisa menghasilkan satu commit sementara sehari penggantian nama yang mekanis bisa menghasilkan tiga puluh, dan tak ada di keluarannya yang membedakan keduanya.

Simpulan

Empat puluh lima baris, dan nyaris seluruhnya flag. Tool-nya tak mengerjakan pengolahan yang pantas disebut begitu: ia meminta daftar kepada git, membuang dua kolom, lalu menulis hasilnya ke sebuah file.

Yang membuatnya sepadan ditulis: tiap flag itu koreksi atas satu andaian tentang siapa yang membaca. Default git disetel untuk developer di terminal, dan tiap satunya — pemilihan kolomnya, urutannya, ketelitian timestamp-nya, pager-nya, log yang tak disaring — persis benar untuk pembaca itu dan persis keliru untuk orang yang menilai sebulan pekerjaan. Tool ini bukan transformasi atas datanya. Ia transformasi atas pembacanya, dan begitu itu jadi bingkainya, pertanyaan flag mana yang perlu diraih berhenti jadi pertanyaan.

Istilah

Pesan commit
Teks yang menempel pada sebuah revisi, ditulis untuk git sebagai label dan untuk diri penulisnya di masa depan sebagai pengingat. Tak satu pun dari keduanya adalah pembaca yang menilai pekerjaannya.
Pager
Program yang dibuka git ketika keluarannya melebihi satu layar, menunggu tombol ditekan untuk menggulir. Menolong saat interaktif; di dalam script ia menunggu masukan yang tak pernah datang.
Decoration
Keterangan branch dan tag yang ditambahkan git di samping commit, menunjukkan letak sebuah revisi di dalam graph-nya. Berguna saat menjelajah, tak bermakna di dalam ringkasan.
Penyaring author
Pembatasan yang mengurung log pada commit milik orang tertentu. Menetapkan apa yang sebenarnya dicakup laporan hasilnya, dan itu membuatnya setelan paling berkonsekuensi di tool ini.
Render waktu lokal
Menampilkan timestamp dalam zona waktu pembacanya ketimbang zona yang tercatat bersama commit-nya, supaya sebuah tanggal berarti hari saat pekerjaannya berlangsung.
Baris header
Baris pertama sebuah CSV, yang menamai kolomnya. Bukan baris data, dan karena itu perhitungan apa pun yang diturunkan dari file-nya harus memperhitungkannya.

Technologies covered

  • Bash
  • awk
  • Git

About the author

Photo of Romi Fadli

Romi Fadli, S.Kom.

Solution Architect · Backend Engineer · Embedded Systems

In my day job I work as a solution architect. I design the system, lead the team that builds it — quality assurance, backend developers, a UI developer, DevOps and DevSecOps — and write the most critical modules myself, such as a certificate authority, a long-term PDF signer, and the core API of a personnel system.

I work across the whole stack that a running system actually needs. On the hardware side that means PCB design, microcontrollers, and embedded firmware — including RISC-V boards running FreeRTOS and Embassy, speaking MQTT over mutually authenticated TLS on a microcontroller with 64 KB of SRAM.

Most of my day-to-day work is software: Rust backends and API modules built on Clean Architecture, an OAuth2/OpenID Connect authorization server written from scratch, PAdES document signing, and certificate authorities — root CAs, timestamping authorities, and OCSP responders. I also build the frontends that sit on top of them.

Running all of that means infrastructure: Docker Compose stacks, networking, Mikrotik and RADIUS, observability with OpenTelemetry, and a steady habit of writing command-line tools and utilities for Linux when the existing ones do not fit.

Share

Enjoyed this?

Get an email when a new article is published.

Referenced by

A stack of Markdown documents and an orange arrow leading to a browser page with a side menu, a heading, paragraphs, a dark code panel, and a table of contents with its active entry highlighted

Satu script yang mengubah dokumentasi Markdown satu project menjadi situs statis yang terbuka dari folder tanpa server maupun internet: menu, daftar isi yang mengikuti pembaca, tabel yang tak pernah terpotong, kode dengan tombol salin, dan tautan yang tetap berfungsi.

7 min read Read more →

Related Articles

A stack of Markdown documents and an orange arrow leading to a browser page with a side menu, a heading, paragraphs, a dark code panel, and a table of contents with its active entry highlighted

Satu script yang mengubah dokumentasi Markdown satu project menjadi situs statis yang terbuka dari folder tanpa server maupun internet: menu, daftar isi yang mengikuti pembaca, tabel yang tak pernah terpotong, kode dengan tombol salin, dan tautan yang tetap berfungsi.

7 min read Read more →
An old layout with a grey banner and a new structure with a green banner, each holding a blue database, a purple document store, and an orange telemetry store; three coloured paths pass through one export package marked with a green check

Empat perintah yang memindahkan stack dari layout lama ke struktur baru melintasi versi mayor: export tanpa mengubah data sumber, import tiap store sesuai cara yang diizinkan versinya, verifikasi apa yang benar-benar tiba, dan rename modul sambil memindahkan datanya.

11 min read Read more →
Three image layer stacks, each with a version tag of its own colour, blue, orange, and green, each leading to an archive file banded in the same colour

Perintah yang memberi versi tersendiri pada tiap modul stack, mem-build image-nya di bawah tag yang berarti tepat satu build, menolak memakai ulang versi atau menelan compile yang gagal, dan mengarsipkan image menurut nama dan tag supaya versi tak saling menimpa.

7 min read Read more →