Skip to content
Blog Programming

Bagaimana Saya Menjalankan Perintah yang Benar Setelah Berbulan-bulan Tak Menjalankannya

By Romi Fadli, S.Kom. 16 min read Tooling & Automation
On this page
Three horizontal bars of very different lengths, one blue and two orange, comparing how many guards each command has

Ada segolongan perintah yang saya jalankan kira-kira empat kali setahun. Tak cukup sering untuk diingat, tak cukup jarang untuk dicari dari nol tiap kali, dan — inilah bagian yang menentukan — selalu pada saat ada hal lain yang sudah mulai meleset. Repository yang remote-nya harus diarahkan ke tempat baru. Working tree yang harus dikembalikan ke keadaan bersih. Disk virtual yang kehabisan ruang di tengah proses build.

Pendahuluan

A. Perintah yang jatuh di celah

Frekuensi menentukan cara sebuah perintah diingat, dan bentuknya tak senyaman kedengarannya.

Perintah yang dijalankan tiap hari tinggal di ingatan otot. Saya tak mengingat-ingat susunan git status; jari saya yang mengetikkannya. Perintah yang dijalankan sekali seumur hidup dicari dari nol, dan itu justru baik, sebab menghampirinya tanpa andaian apa pun memang sikap yang tepat.

Perkaranya duduk di tengah. Perintah yang dijalankan tiap beberapa bulan terasa cukup akrab sampai mencarinya terasa berlebihan, dan perasaan itu keliru. Yang sungguh tersisa setelah empat bulan cuma bentuk kasarnya — bahwa ia melibatkan remote, bahwa ada flag yang mengandung upstream di suatu tempat — dan itu cukup untuk menyusun perintah yang masuk akal sekaligus tak cukup untuk menyusun perintah yang benar.

B. Kenapa yang masuk-akal-tapi-keliru justru paling mahal

Perintah yang sama sekali tak saya ingat tak berbahaya. Saya berhenti, membaca manual, lalu melangkah hati-hati sebab saya sadar saya tak tahu.

Perintah yang saya ingat separuh melahirkan urutan yang berbeda. Saya mengetik sesuatu, sintaksnya sah, dan ia mengerjakan sesuatu — cuma bukan yang dimaksud. Git terutama murah hati di sini: ada beberapa verb yang menerima argumen serupa dan berperilaku sama sekali berbeda, dan tak satu pun akan bertanya apakah saya yakin.

Ketiga tool di artikel ini menyentuh benda yang enggan dibatalkan. URL remote yang menunjuk repository keliru mengirim push berikutnya ke tempat yang tak ditonton siapa pun. Working tree yang dibersihkan adalah working tree yang perubahan belum-ter-commit-nya tak lagi ada di mana pun. Disk image adalah file berisi seluruh filesystem, dan operasi atasnya tidak transaksional.

C. Tiga tool yang tak berbagi apa pun selain frekuensi

Kumpulan ini memang sengaja tak seragam. Dua operasi git; satu mengubah ukuran disk image untuk QEMU, program open-source yang menjalankan virtual machine. Ketiganya tak dirancang bersamaan — masing-masing muncul sendiri-sendiri, berselang bulan, saat kebutuhannya datang.

Jarak itulah bagian yang menarik. Seandainya polanya soal git, jalan keluarnya pun akan berbentuk git. Polanya justru soal seberapa sering sebuah perintah dipakai, dan itu sifat kehidupan kerja saya, bukan sifat teknologi mana pun — dan karena itulah tool virtualisasi berakhir di golongan yang sama dengan dua tool version control.

D. Ketiga script ini bukan apa

Tak satu pun mewujudkan apa pun. Bertiga jumlahnya enam puluh enam baris, dan bagian yang benar-benar bekerja cuma tiga perintah yang sudah disediakan git dan QEMU.

Yang disimpan script-nya adalah sebuah keputusan — flag mana, urutan mana, mana di antara dua operasi yang mirip rupanya yang benar untuk keadaan itu. Keputusan itu diambil sekali, dengan hati-hati, sambil membuka dokumentasinya. Menyimpannya berarti ia tak perlu diambil ulang pada saat perhatian sedang di tempat lain.

E. Enam hambatan yang menghalangi

Jadi tujuannya gampang dirumuskan: menjalankan perintah yang benar pada saat ia dibutuhkan, berbulan-bulan sesudah terakhir membutuhkannya, tanpa menyusunnya ulang dari ingatan separuh. Benar pada percobaan pertama, pada operasi yang percobaan keduanya belum tentu tersedia.

Gampang dirumuskan, canggung dibangun. Enam hambatan berdiri antara tujuan itu dan tiga perintah yang bisa saya jalankan tanpa ragu, dan tak satu pun menyangkut perintahnya yang sulit.

Yang pertama, ongkosnya ada pada mengingat, bukan pada mengerjakan. Tiap operasi ini cuma satu baris begitu barisnya diketahui. Bagian mahalnya adalah menariknya kembali dengan benar sesudah jeda panjang, dan sebanyak apa pun pemahaman atas tool-nya tak mencegah itu. Alternatif yang ditolak cukup memberi pelajaran: file catatan memuat teksnya tapi tak bisa dijalankan, jadi ia tetap menuntut dibaca lalu diketik ulang, sementara history | grep memunculkan tiap percobaan sebelumnya termasuk yang keliru, tanpa penanda apa pun mana yang mana.

Yang kedua, dua keadaan yang terasa identik menuntut perintah yang sama sekali berbeda. Mengarahkan repository ke sebuah remote cuma satu kalimat dalam bahasa Indonesia dan dua operasi terpisah di git, bergantung pada sudah adanya remote atau belum. Kedua perintahnya tak berbagi satu verb pun. Salah memilih menghasilkan error yang menjelaskan apa yang gagal tanpa menunjukkan operasi mana yang sebenarnya diinginkan.

Yang ketiga, perintah yang jarang dijalankan dieksekusi dengan konteks paling sedikit dan ditawari paling sedikit pula. Sesudah beberapa bulan saya tak yakin sedang berada di folder mana, branch mana yang aktif, atau remote-nya sekarang menunjuk ke mana — dan perintah yang paling membutuhkan konteks itu justru yang secara bawaan tak mencetak apa pun sebelum bertindak.

Yang keempat, berjalan di tempat yang keliru lebih murah dicegah ketimbang diperbaiki. Perintah git yang dikeluarkan di luar repository gagal tanpa akibat; yang dikeluarkan di dalam repository yang keliru justru berhasil. Sementara itu script yang menjalankan beberapa langkah punya mode kegagalannya sendiri — langkah pertama gagal, langkah kedua tetap berjalan dengan andaian langkah pertama berhasil, dan hasilnya keadaan yang tak dimaksud oleh keduanya.

Yang kelima, sebuah nama bisa menjanjikan lebih dari yang dikerjakan kodenya. Salah satu dari ketiganya bernama rollback, dan itu menyiratkan pembatalan commit. Ia tak mengerjakan itu. Ia membuang perubahan yang belum di-commit beserta file untracked, sambil meninggalkan history tak tersentuh — operasi yang sungguh berguna dengan nama yang menggambarkan operasi lain, dan itu menentukan sebab yang membaca namanya adalah orang yang sudah empat bulan tak menjalankannya.

Yang keenam, menambah validasi juga berongkos. Naluri sesudah menulis beberapa pagar adalah memasang pagar di mana-mana. Tapi ketika tool di bawahnya sudah menghasilkan error yang jelas untuk input yang keliru, pembungkus yang memeriksa lebih dulu cuma menggandakan pesan itu — dan tiap pesan yang digandakan adalah pesan yang bisa melenceng dari aslinya seiring waktu.

Masing-masing punya seksi di bawah. Empat yang pertama soal satu tool yang tumbuh berpagar sebab ia memang membutuhkannya; dua terakhir soal dua tool yang tidak, dan kenapa ketimpangan itu disengaja dan bukan kelalaian.

Metode

A. Menyimpan bentuknya, bukan logikanya

Inilah jawaban atas hambatan pertama, dan ia gagasan sekecil mungkin: taruh perintahnya di sebuah file yang namanya menggambarkan keadaannya, bukan mekanismenya.

bash
# the name is the index into memory — not "qemu-img", but what I want
qemu-resize-img disk.qcow2 +20G

Yang saya ingat sesudah empat bulan bukan qemu-img resize. Melainkan bahwa saya ingin membuat disk image jadi lebih besar. Perintah yang dinamai menurut niatnya bisa dijangkau dari niatnya, dan cuma itulah yang masih tersisa di ingatan saat kebutuhannya datang.

Ini pula kenapa file catatan tak memecahkannya. Catatan memuat teks yang sama, tapi menjangkaunya menuntut mengingat bahwa catatan itu ada, menemukannya, lalu mengetik ulang isinya tanpa salah salin — tiga langkah yang masing-masing gagal persis pada saat saya sedang terburu-buru. Nama yang bisa dieksekusi terjangkau dengan mengetik tiga huruf pertama lalu menekan tab.

B. Membuat pilihannya eksplisit, bukan pintar

Inilah jawaban atas hambatan kedua. Dua keadaan, dua mode, dinyatakan sebagai argumen pertama.

bash
git-set-remote create git@example.com:user/repo.git   # no remote yet
git-set-remote update git@example.com:user/repo.git   # remote exists

Kedua operasi di bawahnya tak punya kesamaan apa pun. Repository yang belum punya remote butuh remote ditambahkan lalu butuh push pertamanya untuk menegakkan tracking. Repository yang remote-nya keliru butuh URL remote itu ditulis ulang, dan push sama sekali tak dibutuhkan maupun diinginkan.

bash
if [ "$ACTION" == "create" ]; then
    git remote add origin "$REPO_URL"
    git push --set-upstream origin "$(git branch --show-current)"
elif [ "$ACTION" == "update" ]; then
    git remote set-url origin "$REPO_URL"
fi

Alternatif yang menggoda adalah mendeteksi keadaannya — periksa apakah remote sudah ada, lalu pilih yang tepat secara otomatis. Saya menolaknya, dan alasannya menyangkut apa yang terjadi ketika deteksinya benar tapi keyakinan saya atas keadaannya yang keliru. Kalau saya mengira sebuah repository masih segar padahal tidak, tool yang pintar diam-diam mengerjakan yang satunya lalu melaporkan berhasil. Mode yang eksplisit justru gagal, dan gagal itulah yang saya inginkan saat gambaran saya atas keadaannya meleset — sebab cuma itulah penanda bahwa ia memang meleset.

C. Mencetak konteks yang tak dimiliki keadaannya

Inilah jawaban atas hambatan ketiga: sebutkan di mana kamu berdiri sebelum berbuat apa pun, dan apa yang berubah sesudahnya.

bash
echo "📂 Current directory: $(pwd)"
echo "🔎 Current branch: $(git branch --show-current)"
# ... the operation ...
echo "📡 Current remote configuration:"
git remote -v

Tak satu pun baris itu dibutuhkan operasinya. Keduanya ada sebab yang menjalankan ini punya beberapa terminal terbuka dan belum menyentuh repository ini sejak sebelum libur. Folder dan branch menjawab "apakah saya berada di tempat yang saya kira" persis pada saat pertanyaan itu paling murah dijawab — sebelum ada yang diubah.

git remote -v di penutup menjawab separuh lainnya. Sesudah mengarahkan repository ke URL yang saya ketik dari ingatan, pertanyaan yang langsung muncul adalah apakah ia mendarat dengan benar, dan alternatif dari mencetaknya adalah menjalankan perintah kedua untuk memeriksa — langkah yang justru dilewati ketika yang pertama tampak berhasil.

D. Menolak lebih awal, dan berhenti di kegagalan pertama

Inilah jawaban atas hambatan keempat, dan ia dua pagar yang menangani dua bentuk kegagalan berbeda.

bash
set -e   # stop at the first failure, not the second

if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
    echo "Error: not a git repository."
    exit 1
fi

Pemeriksaan repository adalah pagar jenis biasa. Nilainya bukan pada mencegah kerusakan — git toh akan menolak perintahnya — melainkan pada menghasilkan pesan yang menyebut perkara sebenarnya, ketimbang error git tentang operasi yang tak sadar dipilih pemanggilnya.

set -e menangani perkara yang kurang kentara. Mode create menjalankan dua perintah berurutan, dan yang kedua mengandaikan yang pertama berhasil. Tanpanya, remote add yang gagal disusul push yang tak punya tujuan, dan keluarannya memuat dua error yang menggambarkan satu sebab yang sama. Dengannya, eksekusi berhenti di yang pertama — dan yang pertama itulah yang menjelaskan apa yang terjadi.

Ini juga beda antara sebuah script dengan sederet perintah. Deretan menjalankan segalanya tanpa peduli; script sanggup memutuskan bahwa kegagalan satu langkah membatalkan makna langkah sesudahnya.

E. Mengerjakan persis yang ia kerjakan, di bawah nama yang menyiratkan lebih

Inilah jawaban atas hambatan kelima, dan versi jujurnya: namanya memang tak cermat.

bash
git clean --force -d   # delete untracked files and directories
git reset --hard       # discard all uncommitted changes

Tak ada di sini yang menyentuh commit. History persis seperti sebelumnya. Yang lenyap adalah segala yang belum di-commit: file yang diubah kembali ke asalnya, file untracked dihapus, dan working tree pulang ke apa pun yang disebut commit terkini.

Itu operasi yang kerap berguna. Eksperimen yang berujung buntu, install dependency yang menyerakkan file, hasil generate yang berantakan — seluruhnya lenyap dalam satu perintah, tanpa perlu membaca git status untuk memutuskan apa yang dibuang.

Ia juga tak bisa dipulihkan, dan ia sengaja tak bertanya. Tool sinkronisasi yang saya pakai untuk menyalin folder menolak tujuan berbahaya dan bertanya sebelum menimpa tujuan yang tak kosong, dan perbandingannya layak dinyatakan: di sana, tujuannya sebuah argumen yang bisa salah ketik, dan pagarnya melindungi dari path yang memang tak pernah dimaksud. Di sini tak ada argumen sama sekali. Perintahnya bekerja atas folder tempat saya berdiri, dan saya mengetik nama yang berbunyi bersihkan. Bertanya berarti menanyakan apakah saya memang memaksudkan satu-satunya pekerjaan yang mungkin dikerjakan perintah ini.

F. Ketika meneruskan argumennya memang jawaban yang benar

Inilah jawaban atas hambatan keenam, dan tool-nya tiga baris termasuk shebang.

bash
#!/bin/bash

qemu-img resize $1 $2

Nol validasi, nol pagar, nol yang dicetak. Tiap pemeriksaan yang mungkin saya tambahkan — apakah file-nya ada, apakah ukurannya berbentuk benar, apakah image-nya dalam format yang bisa di-resize — adalah pemeriksaan yang sudah dikerjakan qemu-img, dengan pesan error lebih baik ketimbang yang akan saya tulis, sebab ia paham format image sementara saya cuma akan mencocokkan pola pada string.

Pembungkus yang memvalidasi lebih dulu akan menghasilkan dua pesan error untuk satu perkara yang sama, dan yang saya tulis itulah yang akan basi. Seluruh sumbangan script ini cuma namanya dan urutan argumennya, dan justru itulah seluruh nilainya: sesudah enam bulan saya ingat bahwa saya ingin me-resize sebuah image dan saya ingat ia menerima image lalu ukuran, dalam urutan itu, sebab begitulah urutan yang disiratkan namanya.

Yang membuatnya sepadan jadi script ketimbang catatan adalah apa yang sebenarnya dikerjakan operasinya. Ia membesarkan image tanpa membangunnya ulang, dan itu berarti filesystem beserta seluruh isinya tetap di tempatnya — justru itulah seluruh alasan meraih perintah ini ketimbang membuat image baru lalu menyalin datanya.

Hasil dan Pembahasan

A. Pagar yang sebanding dengan yang bisa meleset

Ketiga script menerapkan kadar kehati-hatian yang sangat berbeda, dan begitu dijajarkan polanya jadi lebih terang ketimbang saat menulisnya.

ToolBarisPagarKenapa sebanyak itu
Menetapkan remote repository59Cek argumen, cek repository, set -e, konteks dicetak sebelum dan sesudahDua mode yang gampang tertukar; beberapa langkah yang satunya bergantung pada yang lain
Kembali ke tree yang bersih4NolTak ada argumen yang bisa keliru; namanya menyebut satu-satunya akibat yang mungkin
Me-resize disk image3NolTool di bawahnya sudah memvalidasi, lebih baik ketimbang yang sanggup dikerjakan pembungkus

Baris pertama memang pantas berpagar sebab ada yang perlu dijaga: pilihan antar mode, argumen yang bisa menunjuk ke mana saja, dan urutan yang langkah pertamanya gagal membuat langkah kedua tak bermakna. Dua sisanya tak punya satu pun sifat itu, dan menambahkan pagar di sana cuma menghasilkan upacara, bukan keamanan.

Pertanyaan yang ternyata berguna bukan "apakah operasi ini berbahaya" — membuang pekerjaan yang belum di-commit jelas berbahaya — melainkan "adakah keputusan di dalamnya yang bisa diambil dengan keliru". Bila tak ada, pagar tak punya apa pun untuk ditangkap.

B. Untuk apa emoji di keluarannya

Salah satu script ini mencetak emoji di depan tiap baris, dan itu kelihatan sebagai hiasan padahal tak seluruhnya begitu.

Keluaran perintah yang jarang dijalankan itu dipindai, bukan dibaca. Dinding teks seragam menuntut dibaca untuk menemukan baris yang penting; simbol di depan membuat strukturnya kelihatan sekilas — ini tempat saya berada, ini yang hendak terjadi, ini hasilnya. Untuk keluaran yang muncul mungkin empat kali setahun kepada orang yang sedang menyusun ulang konteksnya, selisih itu bernilai lebih ketimbang pada tool yang jalan tiap jam.

Ini juga keputusan yang takkan saya ulangi pada apa pun yang dipanggil script lain, di mana keluarannya di-parse ketimbang dilihat, dan di mana emoji berubah jadi perkara encoding ketimbang bantuan visual.

C. Di mana pendekatan ini berhenti cocok

Ketiga script ini memuat kebiasaan satu orang, dan pemuatan itu memang intinya — tapi ia tetap membatasi di mana ketiganya berlaku.

Ketiganya mengandaikan origin sebagai satu-satunya nama remote, dan itu benar untuk repository saya sekaligus keliru bagi siapa pun yang bekerja dengan fork dan upstream. Ketiganya mencetak untuk manusia, jadi tak satu pun pantas duduk di dalam pipeline. Lalu seluruh premisnya — bahwa keputusan yang diambil sekali bisa disimpan lalu dipakai ulang — cuma berlaku selama keputusan itu masih sahih; tool yang flag-nya berubah antar versi menuntut bentuk simpanannya ditinjau ulang ketimbang dipercaya.

Simpulan

Enam puluh enam baris tersebar di tiga file, dan bagian yang bekerja cuma tiga perintah yang bukan saya yang menulisnya. Yang dimuat ketiga file itu adalah keputusan tentang cara memanggilnya: mode mana, urutan mana, flag mana, dan — pada dua kasus — keputusan bahwa tak ada upacara lanjutan yang pantas ditambahkan.

Yang tak saya duga: pagarnya ternyata terbagi begitu timpang, dan ketimpangan itu justru jawaban yang benar. Naluri saya sesudah membangun satu tool yang hati-hati adalah membuat sisanya hati-hati dengan cara yang sama, dan naluri itu keliru. Upacara yang tak menangkap apa pun bukan kehati-hatian; ia kebisingan yang membuat peringatan sungguhan lebih sulit terlihat. Yang menentukan kadarnya bukan seberapa berbahaya operasinya, melainkan adakah keputusan di dalamnya yang bisa saya ambil dengan keliru — dan untuk perintah yang saya jalankan empat kali setahun, keputusan itulah yang biasanya satu-satunya bagian yang layak dicatat.

Istilah

Remote
Rujukan bernama ke repository di tempat lain, yang lazimnya disebut origin. Menambahkannya dan mengubah URL yang sudah ada adalah dua operasi terpisah dengan perintah terpisah.
Upstream branch
Branch remote yang diikuti sebuah branch lokal, supaya push dan pull tak butuh argumen. Ditegakkan pada push pertama lewat flag khusus.
Working tree
File sebagaimana adanya di disk saat ini, berbeda dari yang sudah di-commit. Perubahan yang belum di-commit cuma hidup di sini, dan karena itu membuangnya tak bisa dibatalkan.
File untracked
File yang ada di working tree tapi tak pernah diminta dikelola git. Tak tersentuh perintah yang bekerja atas file tracked, dan karena itu membuangnya butuh perintah tersendiri.
Disk image
File yang memuat seluruh filesystem, dipakai virtual machine sebagai disk-nya. Me-resize-nya membesarkan file itu di tempat, sambil membiarkan isinya utuh.
qcow2
Format image copy-on-write milik QEMU, yang mengalokasikan ruang seiring pemakaian ketimbang di muka, dan mendukung di-resize sesudah dibuat.

Technologies covered

  • Bash
  • Git
  • QEMU

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.

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 →