GitHub Hesabına Bağlanmak: HTTPS, Personal Access Token ve SSH
GitHub'da repo açmaktan ilk push'a: 2021'den beri parolanın neden çalışmadığı, fine-grained ve classic personal access token farkı, credential helper'ın nasıl çalıştığı, ed25519 SSH anahtarı, gh auth login ve token'ı URL'ye gömmeyi yakalayan transfer.credentialsInUrl.
Sürüm notu: Bu yazıdaki komutlar Git 2.54 ile denenmiştir; git config get/set biçimi Git 2.46'dan beri var.
Önce Tahmin Et
bookstore repo'su bilgisayarında hazır, üç commit'i var. GitHub'da boş bir repo açtın ve yerel repo'yu ona bağladın:
$ git log --oneline
7f7eeec Add cart total
61419c1 Add book list
5102cb8 Add README and .gitignore
$ git remote add origin https://github.com/ada/bookstore.git
$ git remote -v
origin https://github.com/ada/bookstore.git (fetch)
origin https://github.com/ada/bookstore.git (push)
Sıra git push -u origin main komutunda. Terminal önce kullanıcı adını, sonra parolayı soruyor. GitHub hesabının parolasını yazıp Enter'a basıyorsun. Push gider mi?
Gitmez. GitHub, 13 Ağustos 2021'den beri Git işlemlerinde hesap parolasını kabul etmiyor. Değişikliği Aralık 2020'de duyurdu, iki kısa kesintiyle uyardı ve o tarihte parolayı kapattı. Terminaldeki "Password" sorusu Git'in genel sorusudur; GitHub oraya parolanı değil, bir token bekler. Bu yazı o boşluğu dolduruyor: GitHub'da repo açmak, HTTPS (HTTP Secure) ile personal access token kullanmak, token'ı her seferinde yazmamak için credential helper, SSH (Secure Shell) anahtarı ve GitHub CLI (Command-Line Interface).
GitHub'da Repo Açmak
Repo'yu web arayüzünden açarsın. Sağ üstteki artı menüsünden yeni repository seçeneğine gidersin. Karşına çıkan formda sahibi (kendi hesabın ya da üyesi olduğun bir organization), repo adını, isteğe bağlı bir açıklamayı ve görünürlüğü (public ya da private) seçersin. Formun altında repo'yu bir README, bir .gitignore template'i ve bir lisansla başlatma seçenekleri var. 1 Ekim 2020'den beri GitHub'da yeni açılan repo'ların varsayılan branch'i main; ikinci yazıda init.defaultBranch ayarını bu yüzden main yapmıştık.
Elinde zaten yerel bir repo varsa o üç seçeneği boş bırak. Boş açılan repo'da GitHub sana mevcut bir repo'yu nasıl push edeceğini gösteren birkaç satır komut verir; yukarıdaki remote add ve push -u satırlarının aynısı. Seçenekleri işaretlersen ne olur? GitHub repo'yu kendi ilk commit'iyle başlatır ve senin geçmişinle ortak hiçbir noktası olmayan ikinci bir geçmiş doğar. Bunu yerelde, içinde tek bir README commit'i olan bir bare repo ile canlandırdım:
$ git remote add origin ../github-bookstore.git
$ git push -u origin main
To ../github-bookstore.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to '../github-bookstore.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.
$ git pull --no-rebase origin main
From ../github-bookstore
* branch main -> FETCH_HEAD
* [new branch] main -> origin/main
fatal: refusing to merge unrelated histories
$ git pull --no-rebase --allow-unrelated-histories origin main
From ../github-bookstore
* branch main -> FETCH_HEAD
Auto-merging README.md
CONFLICT (add/add): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.
Push reddedildi, pull iki geçmişi merge etmeyi reddetti, zorladığında da README iki tarafta ayrı ayrı yaratıldığı için add/add conflict çıktı. Çözülmez bir durum değil ama hiç yaşamamak çok daha kolay: yerel repo'n varsa GitHub'daki repo'yu boş aç.
HTTPS mi, SSH mi?
GitHub'daki bir repo'ya iki adresle ulaşırsın. HTTPS adresi https://github.com/ada/bookstore.git biçimindedir, SSH adresi git@github.com:ada/bookstore.git. İkisi aynı repo'yu gösterir; fark yalnızca kimliğini nasıl kanıtladığında. Remote'u istediğin zaman öbürüne çevirebilirsin:
$ git remote set-url origin git@github.com:ada/bookstore.git
$ git remote -v
origin git@github.com:ada/bookstore.git (fetch)
origin git@github.com:ada/bookstore.git (push)
Seçim için iki seçenek ve kısa tradeoff. HTTPS her ağda çalışır, çünkü web trafiğiyle aynı portu kullanır; kurumsal proxy'lerin arkasında da sorun çıkarmaz. Karşılığında bir token yönetirsin ve token'ın süresi dolar. SSH'ta anahtarı bir kez üretip GitHub'a eklersin, sonra yıllarca dokunmazsın; ama bazı şirket ağları SSH'ın kullandığı 22 numaralı portu kapatır. Kendi makinende SSH, CI (Continuous Integration) ortamlarında ve kısıtlı ağlarda HTTPS çoğu zaman en az sürtünmeli yol.
Personal Access Token: Parolanın Yerine
HTTPS ile push ederken parola sorusuna yazdığın şey bir personal access token (PAT). Token'ı GitHub'da profil menüsünden ayarlara, oradan geliştirici ayarlarına girip personal access token bölümünden üretirsin. İki tür var.
Fine-grained token tek bir sahibe bağlıdır: ya senin hesabın ya da bir organization. Hangi repo'lara erişeceğini tek tek seçersin, her izni de ayrı ayrı verirsin; bookstore'a yalnızca içerik okuma ve yazma izni gibi. Organization'lar bu token'lar için onay şartı koyabilir. 2022 Ekim'inde beta olarak geldi, 18 Mart 2025'te genel kullanıma açıldı ve GitHub mümkün olan her yerde bunu öneriyor. Classic token ise geniş scope'larla çalışır ve erişebildiğin bütün repo'lara açılır. Fine-grained token'ların hâlâ yapamadığı birkaç şey var: üyesi olmadığın public repo'lara katkı, dışarıdan collaborator olarak çalışmak, aynı anda birden çok organization'a erişmek, Packages gibi. Bu durumlarda classic token'a mecbur kalabilirsin.
Token'ı üretirken bir süre seç ve en dar izni ver. Token'ı yalnızca bir kez görürsün; sayfayı kapatınca bir daha gösterilmez. Bir yıl boyunca hiç kullanılmayan token'ları GitHub kendisi siler.
Credential Helper: Token'ı Bir Kez Yaz
Her push'ta token yapıştırmak istemezsin. Git bunu credential helper'a bırakır: Git bir sunucuya kimlik bilgisi göndermesi gerektiğinde önce helper'a sorar, helper bir şey bilmiyorsa sana sorar ve başarılı olan bilgiyi helper'a saklatır. Mekanizmayı görmek için en basit helper olan store'u deneme amaçlı, bir dosyaya yazacak şekilde ayarladım:
$ git config set credential.helper "store --file=../demo-credentials"
$ printf 'protocol=https\nhost=github.com\nusername=ada\npassword=example-token\n' | git credential approve
$ cat ../demo-credentials
https://ada:example-token@github.com
$ printf 'protocol=https\nhost=github.com\n' | git credential fill
protocol=https
host=github.com
username=ada
password=example-token
$ git config unset credential.helper
git credential approve, bir push başarılı olduğunda Git'in helper'a söylediği şeyin aynısı: "bu bilgi işe yaradı, sakla". git credential fill ise bir sonraki push'ta sorulan soru: "github.com için elinde ne var?" Cevap protokol, host, kullanıcı adı ve token. Ama cat çıktısına bak: store token'ı düz metin olarak diske yazıyor. Bu yüzden onu gerçek işte kullanmıyoruz.
Gerçek işte işletim sisteminin kasasını kullanan helper'ı seçersin. macOS'ta bu osxkeychain; token Keychain'de durur. Apple'ın Xcode ile gelen Git'i bunu kendi yerleşik ayarında zaten açık getiriyor, git config get --show-origin credential.helper ile nereden geldiğine bakabilirsin. Windows'ta Git for Windows kurulumu Git Credential Manager'ı (GCM) getirir; GCM ilk push'ta tarayıcıda GitHub girişi açar ve token'la sen uğraşmazsın. Linux'ta GCM ya da libsecret tabanlı bir helper kurabilirsin; hiçbir şey kurmak istemiyorsan cache helper'ı bilgiyi yalnızca bellekte, varsayılan olarak 900 saniye tutar.
Üçüncü bir yol GitHub CLI. gh yerelde kurulu olmadığı için komutları çıktısız veriyorum:
# tarayıcıda GitHub girişi yapar, HTTPS mi SSH mi kullanacağını sorar
$ gh auth login
# hangi hesapla bağlı olduğunu gösterir
$ gh auth status
# gh'yi Git'in credential helper'ı olarak ayarlar
$ gh auth setup-git
gh auth login girişi tarayıcıda tamamlar ve token'ı kendisi saklar. gh auth setup-git ile Git, HTTPS üzerinden GitHub'a giderken kimlik bilgisini gh'den ister. Böylece token üretme, kopyalama, süresini takip etme işlerinin hepsi tek bir araçta toplanır.
SSH Anahtarı Kurmak
SSH tarafında parola ya da token yok, bir anahtar çifti var. Özel anahtar bilgisayarında kalır, açık anahtarı GitHub'a verirsin. Kapı kilidi ile anahtar gibi düşün: kilidi (açık anahtar) istediğin kapıya takabilirsin, anahtar (özel anahtar) cebinden hiç çıkmaz. Bu komutlar kendi ~/.ssh klasörüne yazdığı için çıktılarını göstermiyorum:
# ed25519 anahtar çifti üretir, dosya yolu ve passphrase sorar
$ ssh-keygen -t ed25519 -C "ada@example.com"
# ssh-agent'ı başlatır
$ eval "$(ssh-agent -s)"
# macOS'ta anahtarı agent'a ekler, passphrase'i Keychain'e kaydeder
$ ssh-add --apple-use-keychain ~/.ssh/id_ed25519
ssh-keygen önce dosya yolunu sorar; Enter'a basarsan ~/.ssh/id_ed25519 ve ~/.ssh/id_ed25519.pub oluşur. Sonra passphrase ister. Boş bırakma: dizüstün çalınırsa özel anahtarı koruyan tek şey odur. ed25519'u desteklemeyen eski bir sistemle uğraşıyorsan GitHub'ın önerdiği yedek ssh-keygen -t rsa -b 4096. macOS Monterey'den eski sürümlerde --apple-use-keychain yerine -K kullanılır. macOS'ta her yeniden başlatmada anahtarı tekrar eklememek için ~/.ssh/config dosyasına şunu yaz:
Host github.com
AddKeysToAgent yes
UseKeychain yes
IdentityFile ~/.ssh/id_ed25519
Sonra ~/.ssh/id_ed25519.pub dosyasının içeriğini kopyalayıp GitHub'da ayarlar altındaki SSH ve GPG (GNU Privacy Guard) anahtarları sayfasına yeni anahtar olarak eklersin. .pub uzantılı olanı. Uzantısız dosya özel anahtarındır ve hiçbir yere yapıştırılmaz. Bağlantıyı ssh -T git@github.com ile test edersin; ilk bağlantıda sunucunun parmak izini onaylamanı ister, ardından GitHub seni kullanıcı adınla selamlar ve shell erişimi vermediğini söyler. Bu cevabı aldıysan remote'u SSH adresine çevirip push edebilirsin.
Token'ı URL'ye Gömme
Eski yazılarda ve CI script'lerinde şu kısa yolu görürsün: token'ı doğrudan remote adresine yazmak. Çalışır, ama token artık .git/config dosyasında düz metin olarak durur, komut satırı argümanlarında görünür ve yedeklerle başka makinelere taşınır. Git 2.37'den beri bunu yakalayan bir ayar var: transfer.credentialsInUrl. Değeri allow (varsayılan), warn ya da die olabilir:
$ git remote set-url origin https://ada:example-token@github.com/ada/bookstore.git
$ git config set --global transfer.credentialsInUrl die
$ git fetch origin
fatal: URL 'https://ada:<redacted>@github.com/ada/bookstore.git' uses plaintext credentials
$ git remote set-url origin https://github.com/ada/bookstore.git
fatal: URL 'https://ada:<redacted>@github.com/ada/bookstore.git' uses plaintext credentials
$ git config set remote.origin.url https://github.com/ada/bookstore.git
$ git remote -v
origin https://github.com/ada/bookstore.git (fetch)
origin https://github.com/ada/bookstore.git (push)
die modunda fetch, ağa hiç çıkmadan durdu ve hata mesajında token'ı <redacted> ile gizledi. Asıl ilginç olan dördüncü komut: adresi temizlemek için çalıştırdığın git remote set-url da aynı hatayla durdu, çünkü önce mevcut remote'u okuyor. Çıkış yolu adresi doğrudan config üzerinden yazmak: git config set remote.origin.url. Bu ayarı global olarak die yapmak iyi bir alışkanlık; token'ı URL'ye (Uniform Resource Locator) gömen bir script'i ilk çalıştığı anda yakalarsın.
Örneklerde GitHub'ın Yerine Bare Repo
Bu serideki örnekler GitHub'a gerçekten push etmiyor. GitHub'daki repo'nun yerine yerel bir bare repo kullanıyorum: working tree'si olmayan, yalnızca Git verisini tutan bir repo. GitHub sunucusunda senin repo'n da tam olarak böyle saklanır. Push, fetch ve pull komutları bire bir aynı çalışır; değişen tek şey adres:
$ git init --bare ../bookstore.git
Initialized empty Git repository in .../bookstore.git/
$ git remote set-url origin ../bookstore.git
$ git push -u origin main
To ../bookstore.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.
$ git branch -vv
* main 7f7eeec [origin/main] Add cart total
GitHub'a push ettiğinde bu çıktının altında remote: ile başlayan, GitHub'ın kendi yazdığı satırlar da görebilirsin. Onlar sunucunun mesajları; Git'in kendi çıktısı yukarıdakiyle aynıdır.
Sık Düşülen Tuzaklar
İlk tuzak parola sorusuna hesap parolasını yazmak. İnsan parolayı yanlış yazdığını sanıp üç kez daha dener. Sorun parolada değil; oraya token girer.
İkincisi süresi dolan token. Push bir gün aniden kimlik hatası vermeye başlar, çünkü helper eski token'ı göndermeye devam ediyordur. Yeni token üretip helper'daki kaydı güncellemen gerekir: macOS'ta Keychain'deki github.com kaydını silersin, bir sonraki push'ta Git yeni token'ı sorar ve onu saklar.
Üçüncüsü yanlış anahtarı paylaşmak. GitHub'a eklenen dosya her zaman .pub uzantılı olandır. Özel anahtarı bir yere yapıştırdıysan onu ifşa olmuş say, yeni bir çift üret ve eskisini GitHub'dan kaldır.
Dördüncüsü SSH'ta Permission denied (publickey). Genelde ya anahtar agent'a eklenmemiştir ya da GitHub'a başka bir anahtarın açık kısmı eklenmiştir. ssh-add -l agent'taki anahtarların parmak izlerini listeler; bunu GitHub'daki anahtar listesinde görünen parmak iziyle karşılaştır.
Beşincisi token'ı repo'nun içine yazmak: URL'de, bir .env dosyasında ya da bir script'te. Token commit'lendiyse silmek yetmez, geçmişte durmaya devam eder. Token'ı hemen iptal et ve yenisini üret.
Küçük Bir Deneme
★☆☆ Kendi makinende git config get --show-origin --all credential.helper komutunu çalıştır. Hangi helper'lar tanımlı, her biri hangi dosyadan geliyor? Bir repo'da birden çok helper tanımlıysa Git onları sırayla dener.
★★☆ Boş bir klasörde iki bare repo aç; birine başka bir klondan tek bir README commit'i push et. Kendi yerel repo'nu önce boş olana, sonra dolu olana push etmeyi dene ve iki çıktıyı karşılaştır. Dolu olanda --allow-unrelated-histories ile merge edip add/add conflict'ini çöz.
★★★ Deneme bir repo'da transfer.credentialsInUrl değerini warn yap. Remote adresini bare repo'yu gösteren ve içine sahte bir token gömülü file://ada:example-token@/tam/yol/bookstore.git biçiminde yaz, sonra git fetch çalıştır. Uyarıdan sonra fetch devam ediyor mu? Ardından aynı adresi remote.origin.pushurl olarak yazıp git push dene. Uyarı neden çıkmıyor? Cevabı git help config içinde ara.
Artık GitHub'la konuşabiliyorsun. Bir sonraki yazıda bu bağlantıyı ekip işine çeviriyoruz: bir branch açıp commit'leri push etmek, pull request açmak, review'a yeni push'larla cevap vermek, GitHub'ın üç merge yöntemi arasında seçim yapmak ve açık kaynakta fork ile upstream'i senkron tutmak.