Repository remote baru saja lahir di platform — tetapi repository lokal kita tidak otomatis mengenalnya. Pertemuan keduanya punya dua arah yang berbeda nasib: arah pertama, repository lokal berisi commit bertemu remote yang masih kosong, dan perjodohannya tinggal dua perintah. Arah kedua, repository lokal bertemu remote yang sudah berisi commit awal (misalnya dibuat dengan kotak centang README di Membuat Repository Remote ) — dan di sinilah masalah paling terkenal di dunia push pertama menunggu: dua riwayat tanpa leluhur bersama. Chapter ini menuntaskan keduanya dengan demo nyata, termasuk error yang sesungguhnya dan obatnya.
Arah Pertama: Lokal Bertemu Remote Kosong #
Lab kita memakai pola yang akrab: repository lokal Budi dengan dua commit, dan remote baru yang masih kosong — di sandbox kita wakili dengan bare repo toko-kosong.git, padanan persis repository yang baru dibuat di platform tanpa berkas awal:
git init -b main --bare toko-kosong.git
git init -b main budi && cd budi
git config user.name "Budi Santoso"
git config user.email "[email protected]"
printf '# Toko Budi\n' > README.md
git add README.md
git commit -m "Buat README toko"
printf 'kontak\n' > kontak.md
git add kontak.md
git commit -m "Tambah halaman kontak"Pendaftaran remote kini tinggal satu baris — mekanisme lengkapnya (nama origin, verifikasi buku alamat, fetch pertama) sudah dibedah di Menambahkan Remote
:
git remote add origin ../toko-kosong.git
git remote -vOutput yang Dihasilkan:
origin ../toko-kosong.git (fetch)
origin ../toko-kosong.git (push)Karena remote kosong, push pertama tidak menemui hambatan sedikit pun — tinggal pastikan flag -u ikut terbawa agar branch main lokal terikat ke origin/main (peran flag ini dibedah di git push
):
git push -u origin mainOutput yang Dihasilkan:
To ../toko-kosong.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.git statusOutput yang Dihasilkan:
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree cleanBaris * [new branch] menandai branch main lahir di remote, dan baris terakhir memasang ikatan pelacakan. Sejak status melaporkan up to date with 'origin/main', seluruh siklus yang kita latih di section Remote Repository
— pull, push, sinkronisasi — berlaku penuh. Inilah arah yang sebaiknya kita pilih: remote kosong, lokal berkomitmen, dua perintah selesai.
Arah Kedua: Remote Sudah Berisi #
Skenario keduanya umum: repository lokal sudah berjalan, lalu tim membuat remote di platform dan — tanpa sadar akibatnya — mencentang “Initialize this repository with a README”. Server pun berisi satu commit Initial commit. Di sandbox, komitmen server itu kita simulasikan dengan repo penyemai yang mendorong README ke bare toko-bersih.git sebelum repository lokal dikenalkan:
git init -b main --bare toko-bersih.git
git init -b main penyemai && cd penyemai
git config user.name "Budi Santoso"
git config user.email "[email protected]"
printf '# Toko Budi\n' > README.md
git add README.md
git commit -m "Initial commit"
git remote add origin ../toko-bersih.git
git push -u origin main
cd ..
git init -b main lokal && cd lokal
git config user.name "Budi Santoso"
git config user.email "[email protected]"
printf 'beranda\n' > situs.md
git add situs.md
git commit -m "Buat kerangka situs"
printf 'kontak\n' > kontak.md
git add kontak.md
git commit -m "Tambah halaman kontak"Semua commit lab ini dibuat dengan tanggal deterministik sehingga keluaran konsisten antar-jalankan; hash-nya berganti di mesin lain. Hubungkan seperti biasa, lalu push:
git remote add origin ../toko-bersih.git
git push -u origin mainOutput yang Dihasilkan:
To ../toko-bersih.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to '../toko-bersih.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.Push ditolak: server memuat commit yang tidak kita punya, dan keduanya tidak berbagi leluhur. Petunjuknya menyarankan git pull — mari ikuti, karena penolakan berikutnya justru inti pembelajaran kita:
git pull origin mainOutput yang Dihasilkan:
From ../toko-bersih
* branch main -> FETCH_HEAD
* [new branch] main -> origin/main
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint: git config pull.rebase false # merge
hint: git config pull.rebase true # rebase
hint: git config pull.ff only # fast-forward only
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
fatal: Need to specify how to reconcile divergent branches.Lapis penolakan pertama ini bukan tentang riwayat asing, melainkan penjaga keamanan git pull modern yang menuntut kita memilih strategi pendamaian — perilaku yang sudah kita kenal dan tangani di git pull
. Kita pilih merge:
git config pull.rebase false
git pull origin mainOutput yang Dihasilkan:
From ../toko-bersih
* branch main -> FETCH_HEAD
fatal: refusing to merge unrelated historiesInilah pesan yang menjadi inti masalah ini: refusing to merge unrelated histories. Git menolak merge dua riwayat tanpa satu pun commit leluhur bersama karena hampir selalu itu tanda kesalahan — salah remote, salah repository, atau repo yang tertukar. Di kasus kita, kecurigaan Git keliru: dua riwayat ini memang dua sisi sah dari repository yang sama, hasil formulir platform yang tercentang.
Solusi: –allow-unrelated-histories #
Obatnya satu flag: --allow-unrelated-histories memberi tahu merge bahwa ketiadaan leluhur bersama bukan kecelakaan, melainkan kondisi yang kita sadari dan setujui:
git pull origin main --allow-unrelated-historiesOutput yang Dihasilkan:
From ../toko-bersih
* branch main -> FETCH_HEAD
Merge made by the 'ort' strategy.
README.md | 1 +
1 file changed, 1 insertion(+)
create mode 100644 README.mdDiffstat-nya ramah: satu-satunya berkas yang berpindah adalah README.md milik server — situs.md dan kontak.md kita tidak tersentuh karena tidak ada yang memperebutkannya. Sebuah commit merge lahir, menyatukan dua akar riwayat menjadi satu graf. Push yang tadinya ditolak kini lolos:
git push -u origin mainOutput yang Dihasilkan:
To ../toko-bersih.git
297a7d5..5bef93f main -> main
branch 'main' set up to track 'origin/main'.Hasil akhirnya terbaca utuh di graf:
git log --oneline --graph --allOutput yang Dihasilkan:
* 5bef93f Merge branch 'main' of ../toko-bersih
|\
| * 297a7d5 Initial commit
* 43e14f6 Tambah halaman kontak
* 2f490dd Buat kerangka situsDua akar terlihat jelas: Initial commit milik platform di kanan, kerangka situs kita di kiri, dan merge commit 5bef93f menjembataninya. Hash di mesin pembaca berbeda; komposisinya tidak.
Peta Skenario #
| Kondisi awal | Langkah yang tepat |
|---|---|
| Lokal berkomitmen, remote kosong | git remote add origin <url> lalu git push -u origin main |
| Belum ada lokal, remote berisi | git clone <url> — origin terdaftar otomatis, langsung kerja |
| Lokal berkomitmen, remote berisi | git push -u (ditolak) → git pull origin main --allow-unrelated-histories → git push -u origin main |
| Ingin mulai bersih di remote lama yang berisi | buat ulang repository kosong, atau push --force sekali setelah yakin isi remote memang dibuang |
Baris terakhir tabel menyebut --force; versi amannya (--force-with-lease) dibahas di push rejected
, dan untuk kasus repository baru membuat ulang jauh lebih bersih daripada memaksa.
Koneksi kini hidup: push dan pull mengalir. Pertanyaan berikutnya menyangkut pintu yang kita lalui — alamat remote bisa ditulis dengan HTTPS atau SSH, dan pilihan itu menentukan kredensial apa yang dimintakan setiap kali bertransaksi. Kita bedah keduanya di SSH vs HTTPS .