Tampilan
Kenapa penting
Hampir semua tim backend menyimpan kode di Git, dan alur kerjanya ikut menentukan seberapa aman sebuah rilis. Commit yang kecil dan jelas memudahkan code review, mencari kapan bug masuk, dan membatalkan perubahan yang salah tanpa panik. Sebaliknya, kebiasaan buruk — commit raksasa "update", push --force ke main, atau file rahasia ikut ter-commit — bisa menghapus kerja rekan atau membocorkan kredensial database. Di bab ini kamu mempelajari Git dari sudut pandang kerja tim: konsep yang membuat perintahnya masuk akal, alur branch dan pull request, cara menyelesaikan konflik, dan cara membatalkan perubahan dengan risiko yang terukur.
Konsep inti
Tiga area: working tree, staging area, repository
Git memisahkan perubahanmu ke tiga tempat:
| Area | Isi | Perintah untuk pindah ke area berikutnya |
|---|---|---|
| Working tree | File yang sedang kamu edit di disk | git add <file> |
| Staging area (index) | Snapshot yang akan masuk ke commit berikut | git commit -m "..." |
Repository (.git) | Riwayat commit yang permanen | git push (ke remote) |
Sebuah commit adalah snapshot seluruh project plus metadata (penulis, waktu, pesan, dan commit induk). Setiap commit punya hash unik seperti 9380fb8. HEAD adalah penunjuk ke commit (biasanya lewat branch) tempat kamu sedang berada — commit baru selalu dibuat di atas HEAD.
Contoh nyata di repository kosong:
bash
git init -b main repo
cd repo
# ... buat src/PriceCalculator.java ...
git statustext
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
src/
nothing added to commit but untracked files present (use "git add" to track)bash
git add src/PriceCalculator.java
git status --short
git commit -m "feat(pricing): hitung total harga"text
A src/PriceCalculator.java
[main (root-commit) 9380fb8] feat(pricing): hitung total harga
1 file changed, 5 insertions(+)
create mode 100644 src/PriceCalculator.javaStaging area membuat kamu bisa memilih perubahan mana yang masuk ke satu commit. Misalnya kamu memperbaiki bug dan sekaligus merapikan format di file lain — stage dan commit keduanya terpisah agar reviewer melihat dua perubahan logis, bukan satu gumpalan.
Perintah inti sehari-hari
| Perintah | Fungsi |
|---|---|
git status / --short | Melihat file yang berubah, di-stage, atau belum dilacak |
git diff | Perubahan di working tree yang belum di-stage |
git diff --staged | Perubahan yang sudah di-stage (isi commit berikutnya) |
git add -p | Stage perubahan per potongan (hunk) — memisahkan commit dengan rapi |
git log --oneline --graph | Riwayat ringkas beserta bentuk percabangannya |
git switch -c <branch> | Membuat branch baru dan pindah ke sana |
git show <hash> | Isi satu commit |
Contoh output git diff setelah satu baris diubah:
text
diff --git a/src/PriceCalculator.java b/src/PriceCalculator.java
index eee85c6..0e5b73e 100644
--- a/src/PriceCalculator.java
+++ b/src/PriceCalculator.java
@@ -1,5 +1,5 @@
public class PriceCalculator {
int total(int qty, int price) {
- return qty * price;
+ return qty * price - discount(qty);
}
}Biasakan membaca git diff --staged sebelum commit. Ini kesempatan terakhir menangkap System.out.println debug, file yang tidak sengaja ikut, atau password di file konfigurasi.
Branch dan alur kerja tim
Branch hanyalah penunjuk bernama ke sebuah commit — murah dibuat dan dihapus. Dua alur yang paling umum:
- Feature branch — setiap fitur atau perbaikan dikerjakan di branch sendiri (
feature/voucher,fix/ongkir-negatif), lalu digabung kemainlewat pull request setelah review dan CI hijau. - Trunk-based development — semua orang menggabungkan perubahan kecil ke
main(trunk) sesering mungkin, minimal harian. Branch hidup sangat singkat; fitur yang belum siap disembunyikan dengan feature flag.
Keduanya sepakat pada satu hal: branch yang hidup berminggu-minggu menumpuk konflik. Makin kecil dan sering kamu menggabungkan, makin sedikit rasa sakitnya.
Merge vs rebase
Misalnya kamu membuat dua commit di feature/voucher, sementara rekan menambah commit di main:
text
* 892ff13 feat(voucher): validasi kode voucher
* 7680df1 feat(voucher): model voucher
| * 0f23cfc feat(payment): entity payment
|/
* 71c3683 feat(order): entity orderMerge (git merge feature/voucher dari main) menyatukan dua sejarah dengan sebuah merge commit; sejarah asli tetap utuh. Rebase (git rebase main dari feature/voucher) memutar ulang commit-mu di atas ujung main:
text
* 00e413e feat(voucher): validasi kode voucher
* 5d96f82 feat(voucher): model voucher
* 0f23cfc feat(payment): entity payment
* 71c3683 feat(order): entity orderPerhatikan hash commit voucher berubah (7680df1 → 5d96f82) — rebase membuat commit baru. Setelah itu merge ke main cukup fast-forward tanpa merge commit, dan sejarahnya lurus. Aturan praktisnya: rebase boleh untuk merapikan branch lokalmu sendiri; jangan me-rebase commit yang sudah dipakai orang lain, karena sejarah mereka akan berbeda dengan sejarahmu.
Menyelesaikan konflik
Konflik terjadi bila dua branch mengubah baris yang sama. Git tidak menebak — ia berhenti dan meminta kamu memutuskan:
bash
git merge feature/ongkir-naiktext
Auto-merging Shipping.java
CONFLICT (content): Merge conflict in Shipping.java
Automatic merge failed; fix conflicts and then commit the result.Isi file kini memuat penanda konflik:
text
public class Shipping {
<<<<<<< HEAD
static final long FLAT_FEE = 15_000;
=======
static final long FLAT_FEE = 12_000;
>>>>>>> feature/ongkir-naik
}Bagian atas (HEAD) adalah versi branch-mu saat ini, bagian bawah versi branch yang digabung. Langkahnya:
- Tentukan isi yang benar — bisa salah satu, gabungan keduanya, atau versi baru. Tanyakan ke penulis perubahan lain bila ragu; konflik pada angka bisnis seperti tarif bukan keputusan teknis semata.
- Hapus semua penanda
<<<<<<<,=======,>>>>>>>. - Kompilasi dan jalankan test — kode tanpa penanda konflik belum tentu benar.
git add Shipping.javalalugit commit(ataugit rebase --continuebila sedang rebase).
Bila kacau, git merge --abort atau git rebase --abort mengembalikan keadaan sebelum penggabungan.
Remote: fetch, pull, push
Remote (biasanya bernama origin) adalah salinan repository di server seperti GitHub atau GitLab.
git fetch— mengunduh commit baru dari remote ke branch pelacak (origin/main) tanpa mengubah branch-mu.git pull—fetchlalu menggabungkan ke branch saat ini (merge, atau rebase dengan--rebase).git push— mengirim commit lokal ke remote.
Ketika rekan sudah push duluan, push-mu ditolak:
text
To ../origin.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to '../origin.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.(Di sini remote-nya repository lokal ../origin.git; di tim, URL-nya alamat GitHub/GitLab.)
Solusinya git pull --rebase origin main, selesaikan konflik bila ada, jalankan test, lalu push lagi. Bukangit push --force — itu menimpa commit rekan di server. Jika memang harus memaksa push ke branch milikmu sendiri (misalnya setelah rebase), pakai git push --force-with-lease, yang menolak bila remote berisi commit yang belum kamu lihat.
Pull request dan code review
Pull request (PR, di GitLab disebut merge request) adalah permintaan menggabungkan branch-mu, sekaligus tempat diskusi. PR yang mudah di-review:
- Kecil dan fokus — idealnya beberapa ratus baris; refactor dan fitur baru dipisah.
- Deskripsi menjawab apa, kenapa, dan cara menguji, plus tautan ke tiket atau spec.
- CI hijau (build + test) sebelum minta review.
- Sebagai reviewer, fokus pada kebenaran, test, keamanan, dan keterbacaan — bukan selera format yang bisa diotomasi. Sampaikan saran sebagai pertanyaan atau usulan konkret, bukan vonis.
Conventional Commits
Format pesan <type>(<scope>): <deskripsi> membuat riwayat bisa di-scan dan bisa diolah otomatis menjadi changelog:
text
feat(voucher): validasi kode voucher saat checkout
fix(shipping): ongkir tidak boleh negatif
refactor(order): ekstrak perhitungan diskon ke method sendiri
test(payment): tambah test untuk pembayaran ditolak
chore(deps): naikkan versi JUnitTanda ! setelah type (feat(api)!: ...) atau baris BREAKING CHANGE: di badan pesan menandai perubahan yang tidak kompatibel. Tulis deskripsi dalam bentuk perintah singkat; detail "kenapa" taruh di badan commit.
.gitignore
File .gitignore berisi pola file yang tidak boleh dilacak — hasil build, konfigurasi IDE, dan rahasia lokal:
text
# Hasil build Maven
target/
# IDE
.idea/
*.iml
.vscode/
# Log & rahasia lokal
*.log
.env.gitignore hanya berlaku untuk file yang belum dilacak. File yang terlanjur ter-commit harus dikeluarkan dengan git rm --cached <file>. Bila yang ter-commit adalah password atau token, menghapusnya di commit berikut tidak cukup — nilainya masih ada di riwayat, jadi kredensial itu wajib segera diganti (rotate).
Membatalkan perubahan
| Situasi | Perintah | Risiko |
|---|---|---|
| Buang perubahan di file yang belum stage | git restore <file> | Perubahan itu hilang permanen |
| Keluarkan file dari staging area | git restore --staged <file> | Aman — isi file tetap |
| Batalkan commit yang sudah di-push | git revert <hash> | Aman — menambah commit pembalik |
| Gabungkan ulang commit lokal (belum push) | git reset --soft HEAD~1 | Aman — perubahan kembali ke staging area |
| Buang commit lokal dan perubahannya | git reset --hard HEAD~1 | Working tree ditimpa; berbahaya bila di-push |
Contoh revert — sejarah tidak dihapus, tetapi dibalik dengan commit baru:
bash
git revert --no-edit HEAD
git log --onelinetext
0c1a649 Revert "fix(tax): tarif salah"
38f78a1 fix(tax): tarif salah
89a4a41 feat(tax): PPN 11%Kalau kamu terlanjur reset --hard ke commit yang salah, commit lama biasanya masih bisa ditemukan lewat git reflog — catatan ke mana saja HEAD pernah menunjuk:
text
38f78a1 HEAD@{0}: reset: moving to HEAD~1
0c1a649 HEAD@{1}: reset: moving to HEAD~1
18adeac HEAD@{2}: commit: chore: file sementara
0c1a649 HEAD@{3}: revert: Revert "fix(tax): tarif salah"git reset --hard HEAD@{1} mengembalikan HEAD ke 0c1a649. Reflog hanya ada di lokal dan tidak menyelamatkan perubahan yang tidak pernah di-commit — itu alasan lain untuk sering commit.
Tag dan rilis
Tag menandai commit tertentu sebagai versi rilis, biasanya mengikuti semantic versioning (MAJOR.MINOR.PATCH). Pakai annotated tag karena menyimpan pembuat, tanggal, dan pesan:
bash
git tag -a v1.0.0 -m "Rilis 1.0.0"
git push origin v1.0.0Pipeline CI sering memakai tag sebagai pemicu build artefak rilis, dan tag memudahkan kamu memeriksa ulang kode persis seperti yang berjalan di production (git switch --detach v1.0.0).
Contoh di dunia backend
Alur satu tiket di tim layanan pembayaran — menambah validasi nominal minimum:
bash
# 1. Mulai dari main terbaru
git switch main
git pull --rebase origin main
# 2. Branch untuk tiket ini
git switch -c fix/minimum-payment
# 3. Kerjakan dalam commit kecil, test dulu
git add src/test/java/id/toko/payment/PaymentValidatorTest.java
git commit -m "test(payment): nominal di bawah 1.000 ditolak"
git add src/main/java/id/toko/payment/PaymentValidator.java
git commit -m "fix(payment): tolak pembayaran di bawah nominal minimum"
# 4. Sinkronkan dengan main sebelum PR, jalankan test lagi
git fetch origin
git rebase origin/main
./mvnw verify
# 5. Push branch dan buka pull request
git push -u origin fix/minimum-paymentDeskripsi PR-nya cukup singkat: masalah ("pembayaran Rp1 lolos validasi dan ditolak payment gateway"), solusi ("validasi nominal minimum di PaymentValidator"), dan cara menguji ("PaymentValidatorTest, kasus 999 dan 1.000"). Setelah disetujui dan digabung, branch dihapus. Bila di production ternyata ada masalah, tim cukup git revert commit merge-nya, lalu rilis ulang.
Pertanyaan interview
1. Apa beda git fetch dan git pull?
fetch hanya mengunduh commit baru dari remote dan memperbarui branch pelacak seperti origin/main; branch lokal dan working tree tidak berubah, sehingga kamu bisa memeriksa dulu apa yang datang. pull adalah fetch lalu langsung menggabungkan perubahan itu ke branch saat ini — lewat merge secara default, atau rebase bila memakai --rebase.
2. Kapan kamu memakai revert dan kapan reset?
revert untuk membatalkan commit yang sudah dibagikan (sudah di-push), karena ia menambah commit pembalik tanpa mengubah sejarah. reset memindahkan penunjuk branch ke commit lain sehingga menulis ulang sejarah — cocok untuk merapikan commit lokal yang belum di-push. Memakai reset pada sejarah bersama lalu force push akan merusak repository rekan setim.
3. Bagaimana kamu menangani merge conflict di file yang tidak kamu tulis?
Saya lihat dulu kedua sisi dan konteksnya (git log pada file itu, deskripsi PR terkait), lalu tanyakan ke penulis perubahan lain bila maksudnya tidak jelas — terutama untuk logika bisnis. Setelah memutuskan isi yang benar, saya hapus penanda konflik, kompilasi, dan menjalankan seluruh test sebelum menyelesaikan merge atau rebase.
Jebakan umum
git push --forceke branch bersama — menimpa commit rekan di remote. Gunakan--force-with-lease, dan hanya di branch milikmu sendiri.- Me-rebase commit yang sudah di-push dan dipakai orang lain — hash berubah, sejarah tim bercabang ganda.
- Commit raksasa berpesan "update" — sulit di-review, sulit di-revert sebagian, dan tidak membantu saat mencari asal bug.
- Menganggap
.gitignoremenghapus file yang sudah dilacak — pakaigit rm --cached, dan rotate rahasia yang terlanjur ter-commit. - Menyelesaikan konflik tanpa menjalankan test — file tanpa penanda konflik bisa tetap salah secara logika.
git restoreataureset --hardpada kerja yang belum di-commit — tidak ada reflog untuk perubahan yang tidak pernah di-commit; perubahan itu hilang.
Ringkasan
- Perubahan berpindah dari working tree → staging area (
add) → repository (commit);HEADmenunjuk posisimu saat ini. - Kerjakan di branch berumur pendek dan gabungkan sering;
mergemenjaga sejarah apa adanya,rebasemembuatnya lurus tetapi mengganti hash. - Konflik diselesaikan dengan memutuskan isi yang benar, menghapus penanda, dan menjalankan test.
- Sinkronkan dengan
fetch/pull --rebasesebelumpush; hindari--forcedi branch bersama. - Commit kecil bergaya Conventional Commits + PR yang fokus membuat review cepat dan rilis aman.
revertuntuk sejarah bersama,resetuntuk commit lokal,reflogsebagai jaring pengaman;tagmenandai rilis.