Branching, Merge
& Konflik
Kekuatan sejati Git muncul saat banyak orang bekerja sekaligus tanpa saling menimpa. Chapter ini adalah satu busur utuh: memisah jalur dengan branch, menyatukannya dengan merge, lalu menyelesaikan konflik yang muncul saat dua jalur bertabrakan.
Sampai Chapter 2, history kita satu garis lurus. Tapi tim nyata tidak bekerja berbaris satu per satu, mereka menggarap beberapa fitur sekaligus. Chapter ini menutup busur “bekerja paralel lalu menyatukan”: branch membuka jalur terpisah, merge menjahitnya kembali, dan ketika dua jalur menyentuh baris yang sama, konflik adalah momen Git memintamu memutuskan. Tiga section ini sengaja berurutan karena konflik hanya masuk akal setelah kamu paham bagaimana branch dan merge bekerja.
Branch: Ruang Kerja Paralel
Branch adalah pointer ringan ke commit
Branch bukan salinan kode, melainkan pointer ringan yang menunjuk ke satu commit, dan itulah kenapa Git memberanikan kita membuatnya sesering apa pun.
Di Chapter 2 kita melihat histori sebagai rantai commit yang saling menunjuk ke parent-nya. Branch adalah lapisan di atas rantai itu: sebuah nama yang menyimpan satu hash commit. Tidak ada folder baru, tidak ada penggandaan file. Saat kamu menjalankan git branch feature/login, Git hanya menulis satu baris berisi hash ke dalam .git/refs/heads/. Itu sebabnya membuat branch terasa instan, bahkan pada repo dengan ratusan ribu commit.
Yang membuat branch terasa “hidup” adalah HEAD. HEAD adalah pointer ke branch yang sedang aktif, bukan langsung ke commit. Saat kamu commit, Git menambah commit baru lalu menggeser pointer branch yang ditunjuk HEAD untuk menunjuk commit baru itu. Branch lain tidak bergerak. Inilah mekanisme isolasi: pekerjaan di feature/login tidak menyentuh main sampai kamu memutuskan menggabungkannya.
flowchart LR C1["c1"] --> C2["c2"] C2 --> C3["c3 main"] C2 --> C4["c4 feature"] HEAD(["HEAD"]) -.-> C4
Branch sebagai pointer. main menunjuk c3, feature menunjuk c4, dan HEAD menandai feature sebagai branch aktif.
Di proyek skincare-backend, alur ini sangat praktis. Misalnya main memuat kode yang sudah jalan di produksi, sementara kamu sedang menggarap endpoint checkout. Kamu buat branch feature/checkout, bekerja bebas di sana, dan main tetap utuh siap dirilis kapan saja tanpa terkontaminasi kode setengah jadi.
Branch seperti cabang sungai yang memisah dari aliran utama, mengalir sendiri sebentar, lalu nanti bisa bertemu kembali ke aliran induk lewat merge.
Karena branch sangat murah, tim mengandalkan konvensi penamaan agar jalur kerja tetap terbaca. Nama branch yang konsisten membuat git branch dan daftar PR di hosting langsung bercerita tentang jenis pekerjaannya.
feature/<ringkas>
Fitur baru, mis. feature/checkout. Lahir dari main, hidup pendek.
fix/<gejala>
Perbaikan bug, mis. fix/negative-price. Fokus satu masalah.
hotfix/<versi>
Patch darurat dari tag rilis, mis. hotfix/1.2.1. Dibahas di Chapter 6.
Untuk berpindah branch, perintah modern adalah git switch. Versi lama memakai git checkout yang memikul peran ganda (pindah branch sekaligus memulihkan file), sehingga Git memisahnya menjadi git switch dan git restore. git checkout masih ada dan berfungsi, tapi untuk berpindah branch sebaiknya pakai git switch agar niat kode lebih jelas.
Terminal# membuat dan langsung pindah ke branch baru git switch -c feature/login # ... edit file, lalu: git add . git commit -m "feat: tambah handler login" # kembali ke main; commit feature/login tidak terbawa ke sini git switch main git log --oneline # commit login tidak terlihat di main
git switch -c feature/login setara dengan git branch feature/login lalu git switch feature/login dalam satu langkah, jadi kamu langsung berada di ruang kerja baru.
Kebiasaan menyalin folder project jadi project-eksperimen lalu bingung mana yang terbaru, digantikan branch resmi: satu repo, banyak garis kerja, dan Git yang menjaga mana yang aktif.
Sebuah ref branch hanya menyimpan satu hash (sekitar 41 byte di disk), jadi jangan ragu membuat branch untuk tiap fitur, percobaan, atau perbaikan kecil. Sebagai imbalannya, jaga umurnya pendek, makin lama branch hidup, makin besar jurang dengan main dan makin sering konflik.
Merge: Menggabungkan Branch
Fast-forward versus three-way merge
Merge adalah cara Git menyatukan pekerjaan dari satu branch ke branch lain, dan hasilnya bergantung pada apakah base sudah bergerak sejak branch dibuat.
Setelah pekerjaan di feature/login selesai dan teruji, kamu ingin perubahannya masuk ke main. Caranya: pindah ke branch tujuan, lalu jalankan git merge. Git punya dua strategi yang dipilih otomatis tergantung bentuk histori, dan memahami keduanya membuat histori proyekmu lebih mudah dibaca.
Kasus pertama adalah fast-forward. Ini terjadi bila main tidak menerima commit baru sejak feature/login dipisah, sehingga commit main masih merupakan leluhur (ancestor) dari ujung feature. Karena tidak ada yang perlu didamaikan, Git cukup menggeser pointer main maju ke commit terakhir feature. Tidak ada commit baru yang dibuat; histori tetap satu garis lurus.
Kasus kedua adalah three-way merge. Ini terjadi bila kedua branch sama-sama maju: ada commit baru di main dan ada commit baru di feature. Git tidak bisa sekadar menggeser pointer karena keduanya menyimpang. Git mengambil tiga titik (ujung kedua branch dan commit leluhur bersama), menggabungkannya, lalu membuat merge commit yang istimewa karena punya dua parent.
gitGraph commit id: "c1" commit id: "c2" branch feature/login checkout feature/login commit id: "f1" commit id: "f2" checkout main commit id: "m1" merge feature/login id: "merge"
Three-way merge. Karena main maju dengan m1 dan feature dengan f1, f2, Git membuat merge commit berisi dua parent.
Bayangkan dua orang mengedit salinan dokumen yang sama dari titik awal yang identik. Three-way merge adalah proses menyatukan kedua revisi dengan melihat naskah asli sebagai acuan, bukan menimpa salah satunya.
Terminal# pindah ke branch tujuan dulu git switch main # fast-forward bila main belum bergerak sejak feature dibuat git merge feature/login # Updating a1b2c3d..e4f5g6h # Fast-forward # memaksa merge commit walau sebenarnya bisa fast-forward git merge --no-ff feature/login
Kapan memilih --no-ff? Flag ini memaksa Git membuat merge commit meski fast-forward sebenarnya mungkin. Banyak tim memakai ini agar serangkaian commit satu fitur tetap tampak sebagai satu gugus di histori.
- Tanpa merge commit, histori tetap lurus.
- Asal feature tidak menonjol sebagai grup.
- Cocok untuk perbaikan kecil satu commit.
- Selalu membuat merge commit penanda fitur.
git log —graphmenunjukkan gugus commit per fitur.- Banyak tim memakainya saat merge feature ke main.
Pakai git merge —no-ff untuk feature branch agar grup commit-nya tetap tampak utuh di histori; git log —oneline —graph akan menunjukkan struktur cabangnya dengan jelas.
Saat kedua sisi mengubah baris yang sama, three-way merge tidak bisa otomatis dan Git menandainya sebagai conflict. Cara membaca dan menyelesaikannya dibahas tuntas di section berikut.
Merge Conflict
Saat Git tidak bisa menggabungkan otomatis
Merge conflict bukan tanda ada yang rusak, melainkan momen Git jujur bahwa ia tidak punya cukup informasi untuk memilih gabungan yang benar, dan menyerahkan keputusan itu kepadamu.
Konflik muncul ketika dua branch mengubah baris yang sama pada file yang sama, atau satu sisi mengubah file sementara sisi lain menghapusnya. Untuk perubahan yang menyentuh baris berbeda, Git menggabungkan otomatis tanpa kamu sadari. Hanya saat dua sisi bertabrakan di baris yang persis sama, Git berhenti, menandai file, dan meminta penyelesaian manual.
Sama seperti dua rekan yang menulis ulang paragraf yang identik di dokumen bersama lalu harus menentukan versi final, dua branch yang mengubah baris yang sama memaksamu memilih atau memadukan keduanya secara sadar.
Saat konflik terjadi, Git menyisipkan conflict marker ke dalam file. Ada tiga penanda: kepala <<<<<<< menandai awal versi branch saat ini (current), pemisah ======= membatasi kedua versi, dan ekor >>>>>>> menutup versi yang masuk (incoming). Tugasmu mengganti seluruh blok bertanda ini dengan versi final yang benar.
internal/order/service.go (saat konflik)func calcTotal(items []Item) int64 { <<<<<<< HEAD var total PriceRupiah for _, it := range items { total += it.Price * int64(it.Qty) } ======= var total int64 for _, it := range items { total += it.Subtotal } >>>>>>> feature/discount return int64(total) }
Setelah membaca kedua sisi, kamu menulis ulang blok itu menjadi satu versi yang menggabungkan maksud keduanya, lalu menghapus semua marker. Hasil resolusi bisa berupa gabungan ide dari kedua branch, bukan sekadar memilih salah satu mentah-mentah.
internal/order/service.go (setelah resolve)func calcTotal(items []Item) PriceRupiah { var total PriceRupiah for _, it := range items { total += it.Price*int64(it.Qty) - it.Discount } return total }
Begitu file beres, kamu menandainya selesai dengan git add <file>, lalu menuntaskan merge dengan git commit. Bila ternyata situasinya terlalu rumit dan kamu ingin mundur, git merge --abort mengembalikan working tree ke kondisi sebelum merge dimulai, seolah merge tak pernah terjadi.
Di main ubah satu baris fungsi, commit. Buat branch dari commit sebelumnya, ubah baris yang sama secara berbeda, commit juga.
Kembali ke main lalu jalankan git merge nama-branch; Git melaporkan CONFLICT dan menyisipkan marker ke file.
Buka file, pahami current (HEAD) dan incoming, lalu tulis ulang blok menjadi versi final tanpa menyisakan marker.
Jalankan git add file untuk menyatakan konflik teratasi, lalu git commit untuk membuat merge commit.
Jalankan test dan build (go test ./…) untuk memastikan hasil gabungan benar-benar berjalan, bukan hanya bebas marker.
Memilih satu sisi tanpa membaca konteks bisa membuang logika penting dari branch lain. Selalu baca kedua versi, dan setelah resolve jalankan test, karena file yang lolos kompilasi belum tentu benar secara logika.
Cara paling ampuh menghindari konflik besar bukan jago resolve, melainkan jarang menumpuknya: tarik perubahan main ke branch-mu secara rutin dan jaga branch tetap pendek. Konflik kecil yang sering jauh lebih murah daripada satu konflik raksasa di akhir.
Ringkasan
Dari satu garis lurus ke kerja paralel yang aman
Branch, merge, dan konflik adalah satu busur: memisah jalur, menyatukannya, dan menengahi saat keduanya bertabrakan.
Kamu kini bisa bekerja paralel tanpa takut. Branch adalah pointer murah yang mengisolasi pekerjaan, jaga umurnya pendek dan namai dengan konvensi. Merge menyatukannya, fast-forward saat linear, merge commit saat menyimpang, dengan --no-ff untuk menjaga jejak fitur. Dan konflik bukan kegagalan, melainkan undangan untuk memutuskan dengan membaca kedua sisi lalu memverifikasi dengan test. Sejauh ini semua masih lokal di mesinmu. Di Chapter 4 kita buka pintu ke tim: remote, Pull Request, dan branch protection.
Yang Wajib Menempel
- Branch adalah pointer ringan ke satu commit; HEAD menandai branch aktif, isolasi terjadi otomatis.
- Pakai
git switchuntuk berpindah; namai branch dengan konvensi (feature/,fix/) dan jaga umurnya pendek. - Fast-forward saat main belum bergerak; three-way merge membuat merge commit dua parent saat menyimpang.
—no-ffmenjaga gugus commit fitur tetap terlihat di histori.- Konflik diselesaikan dengan membaca kedua sisi, menghapus marker, lalu
git add+ test;git merge —abortuntuk mundur.