Skip to content

Konflik sync tanpa leluhur: alarm, bukan merge

Adityo Guni Waluyo

Classifier status mirror Plane pakai hash sha256 sebagai hakim tunggal, dan konflik dua sisi di jalur satu arah runtuh jadi alarm.

Ringkasan

Gue kira sinkronisasi bakal auto gabungin perubahan, ternyata pagesync sengaja nolak pintar-pintaran. Kalau di jalur satu arah dua sisi sama-sama berubah, dia langsung kasih alarm plane-changed dan git yang menang jadi harus push ulang. Semua keputusan cuma ngandelin hash SHA256 sama data last_sync biar nggak nebak-nebak dan data nggak hilang diam-diam.

Alarm merah di layar, bukan gabungan otomatis

Pemindai status mirror halaman Plane saya selesai jalan, dan satu baris berhenti di mata: alarm plane-changed, lengkap dengan catatan "git wins, pull is refused". Padahal rasanya kedua sisi sama-sama berubah. Refleks pertama saya waktu itu sederhana: alat sinkronisasi harusnya cukup pintar menggabungkan dua perubahan itu otomatis, toh isinya kan mirip.

Tebakan itu salah besar.

Setelah membuka logika classify_page_status di pagesync.py, saya justru menemukan desain yang sengaja menolak jadi pintar. Di jalur satu arah, perubahan di kedua sisi tidak pernah dilaporkan sebagai konflik yang harus damaikan. Ia runtuh jadi satu alarm: repo menang, proses pull ditolak, dan saya sendiri yang harus mendorong ulang biar kedua sisi nyambung lagi.

Keputusan itu awalnya terasa seperti mode setengah jadi. Ternyata justru itu satu-satunya desain yang jujur.

Hash sebagai satu-satunya hakim

Classifier ini punya enam kemungkinan hasil, plus satu di sisi tier:

  • clean: hash kedua sisi cocok dengan catatan terakhir
  • repo-changed: git yang bergerak, tugasnya push
  • plane-changed: Plane yang bergerak, termasuk alarm jalur satu arah
  • both-changed: cuma muncul di jalur dua arah
  • blocked: halaman terkunci, diarsipkan, atau marker tidak cocok
  • missing-page dan orphan-page: halaman hilang, atau file git yang hilang

Semua keputusan di atas bersandar pada satu hal saja: hash SHA256 dari body yang sudah dinormalisasi. Nama halaman dan timestamp pembaruan cuma jadi data diagnostik, bukan penentu.

Kunci lainnya ada di kamus last_sync yang menyimpan repo_sha256 dan plane_sha256 dari sinkronisasi terakhir. Kamus ini adalah pengganti "leluhur", yaitu versi yang diketahui benar di kedua sisi sebelum mereka mulai berubah sendiri-sendiri. Tanpa baseline itu, alat ini memilih menganggap setiap perbedaan sebagai potensi kerusakan, bukan undangan merge.

Bentuk hasilnya juga sengaja datar dan mudah dibaca mesin maupun manusia:

result = classify_page_status(
    relative_path="meeting.md",
    tier="oneway",
    mapped=True,
    file_text=repo_file,
    plane_html=plane_page,
    last_sync=last_sync,
)
# {"status": "plane-changed",
#  "detail": "plane moved; ONE-WAY ALARM: git wins - re-push to converge"}

Git dan rsync sudah menjawab ini lama

Cara berpikir ini sebenarnya ditiru dari Git. Untuk menggabungkan dua cabang dengan aman, Git butuh tiga versi: stage 1 adalah leluhur umum, stage 2 versi kita, stage 3 versi lawan [1]. Kalau hasil ketiganya tidak bisa damaikan otomatis, merge berhenti, bukan menebak [2]. Git sengaja tidak mau jadi pintar di titik yang berisiko.

rsync mengajarkan sisi lain. Pemeriksaan cepat bawaannya cuma melihat ukuran dan waktu modifikasi file, bukan isi. Pembandingan checksum konten yang akurat justru jalur opsional yang harus diaktifkan eksplisit lewat flag -c [3]. Jadi alat transfer setua rsync pun tahu: perbandingan konten itu mahal, dan cuma boleh dipakai kalau memang diminta.

Alarm-first bukan mode degraded

Banyak orang membayangkan sinkronisasi yang bagus itu diam-diam menyelesaikan segalanya di latar belakang. Pendapat saya justru sebaliknya: alat yang menggabungkan perubahan tanpa baseline tercatat bukan alat yang pintar. Dia cuma sedang menebak, dan tebakan di urusan data itu resep kehilangan informasi secara senyap.

Makanya aturan runtuh di sistem saya dibuat sederhana. Jalur satu arah, kedua sisi bergerak? Hasilnya langsung jadi alarm plane-changed. Re-push untuk konvergensi itu deterministik: kondisi akhirnya pasti sama dengan repo. Sementara pull diam-diam itu undangan ketidakpastian, karena dia harus menebak sisi mana yang "benar" tanpa leluhur yang bisa dipakai sebagai hakim.

Ada trade-off yang saya terima sadar-sadar: alat ini jadi lebih "berisik" daripada alat sinkron yang membungkam semua konflik. Tapi noise yang bisa dijelaskan itu jauh lebih murah daripada kehilangan tulisan yang tidak tahu kapan hilangnya.

Jadi sekarang, tiap kali lihat baris alarm di pemindai, saya tidak lagi melihatnya sebagai kegagalan alat. Itu justru tanda alat ini tahu persis di mana batas kemampuannya, lalu menyerahkan keputusan akhir ke satu-satunya pihak yang punya konteks penuh: saya.

Sources: [1] Pro Git, Advanced Merging: stage 1 is the common ancestor, stage 2 is your version, stage 3 is from the MERGE_HEAD, diakses 2026-09-29 [2] Manual git-merge: "A merge stops if there's a conflict that cannot be resolved automatically", diakses 2026-09-29 [3] Man page rsync(1): quick check berdasar ukuran atau mtime; checksum via -c, diakses 2026-09-29

Artikel terkait