İlk Git Repo'su: git init, Staging Area ve İlk Commit
bookstore repo'sunu git init ile sıfırdan kuruyor, .git klasörüne ilk kez bakıyoruz. Working tree, staging area ve repository üçlüsünü git status, git add ve git commit ile adım adım görüyor, status -s'in iki sütununu ve MM durumunun neden commit'e ilk hâli taşıdığını öğreniyoruz.
Sürüm notu: Bu yazıdaki komutlar Git 2.54 ile denenmiştir.
Önce Tahmin Et
README.md dosyasına bir satır ekledin ve git add README.md dedin. Sonra commit atmadan önce aynı dosyaya bir satır daha ekledin. Şimdi git commit çalıştırırsan commit'e dosyanın hangi hâli girer: iki satırlı son hâli mi, yoksa git add dediğin andaki hâli mi?
Bu sorunun cevabı Git'in en önemli fikrine, üç alana dayanıyor. Bu yazıda bookstore repo'sunu sıfırdan kurup ilk iki commit'i atarken o üç alanı tek tek göreceğiz.
Repo'yu Oluşturmak
Bir önceki yazıdaki ayarları yaptıysan (user.name, user.email, init.defaultBranch main) her şey hazır. Projenin klasörünü Git'e oluşturtalım:
$ git init bookstore
Initialized empty Git repository in /.../bookstore/.git/
$ cd bookstore
$ ls -A
.git
git init, verdiğin isimde bir klasör açtı ve içine .git adında gizli bir klasör koydu. Var olan bir projede çalışıyorsan klasörün içine girip argümansız git init demen yeterli; dosyalarına dokunmaz, sadece .git'i ekler. Çıktıdaki yolun başını ... ile kısalttım; sende tam yol yazar.
Bir klasörü repo yapan şey bu .git klasöründen ibaret. Onu silersen klasör sıradan bir klasöre döner ve bütün geçmiş gider. Kopyalarsan geçmiş de onunla gelir.
.git Klasörüne İlk Bakış
$ ls -1F .git
HEAD
config
description
hooks/
info/
objects/
refs/
$ cat .git/HEAD
ref: refs/heads/main
$ git status
On branch main
No commits yet
nothing to commit (create/copy files and use "git add" to track)
Henüz içi boş sayılır ama iskelet hazır. objects/ bütün içeriğin, yani dosyaların, klasörlerin ve commit'lerin saklandığı yer. refs/ branch'lerin ve tag'lerin hangi commit'i gösterdiğini tutar. config bir önceki yazıda gördüğümüz local ayar dosyası. hooks/ commit gibi olaylarda çalışan script'lerin örneklerini barındırır; hook'lara serinin ilerisinde döneceğiz.
En ilginci HEAD. İçinde tek satır var: ref: refs/heads/main. "Şu an main branch'indeyim" demek bu. Garip olan şu ki main henüz yok, çünkü bir branch bir commit'i gösterir ve ortada hiç commit yok. git status da bunu söylüyor: "No commits yet". İlk commit'le birlikte main doğacak. Bu klasörün içine elle dokunmamak en sağlıklısı; her şeyi Git komutlarıyla yapacağız.
Üç Alan: Working Tree, Staging Area, Repository
Git'te bir dosya üç yerde yaşayabilir. Working tree (working directory de denir), diskte gördüğün ve editörle düzenlediğin dosyalardır. Staging area, bir sonraki commit'e girecek değişikliklerin hazırlandığı ara alandır; Git'in kendi belgelerinde index diye geçer ve .git/index dosyasında durur. Repository ise commit'lerin kalıcı olarak saklandığı .git/objects.
Kargo hazırlamak gibi düşün. Masanın üstündeki eşyalar working tree. Koliye koyduğun eşyalar staging area: neyin gideceğine karar verdin ama koli henüz kapanmadı, bir şey çıkarıp başka bir şey ekleyebilirsin. Koliyi bantlayıp üzerine etiketi yapıştırdığın an commit: kolinin içeriği artık sabit, üstünde kimin ne zaman gönderdiği yazıyor. git add eşyayı koliye koyar, git commit koliyi kapatır.
Bu ara alan neden var? Çünkü bir çalışma oturumunda çoğu zaman birden fazla iş yaparsın. Bir hatayı düzeltirken README'deki bir yazım hatasını da görürsün. Staging area sayesinde ikisini ayrı commit'lere koyabilirsin; her commit tek bir şeyi anlatır.
git status ile Konuşmak
Projeye iki dosya ekleyelim. README.md:
# Bookstore
A tiny bookstore app for the Git & GitHub series.
Ve src/books.js:
export const books = [
{ id: 1, title: "Dune", price: 12.5 },
{ id: 2, title: "Neuromancer", price: 9.99 },
];
Şimdi Git'e ne gördüğünü soralım:
$ git status
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.md
src/
nothing added to commit but untracked files present (use "git add" to track)
İki dosya da "untracked": Git onları görüyor ama takip etmiyor. Klasörü tek satırda src/ olarak göstermesi, içinde takip edilen hiçbir şey olmamasından. Mesajların parantez içindeki kısımlarına dikkat et; git status sana her durumda bir sonraki adımı da söyler. Yeni başlarken bu ipuçlarını okumak, komut ezberlemekten daha çok şey öğretir.
$ git add README.md
$ git status
On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: README.md
Untracked files:
(use "git add <file>..." to include in what will be committed)
src/
README.md artık "Changes to be committed" altında, yani staging area'da. src/ hâlâ dışarıda. İpucu satırı da değişti: staging area'dan geri çıkarmanın yolunu söylüyor. Dosyaları geri almayı ileride ayrı bir yazıda ele alacağız.
İlk Commit
$ git add src/
$ git status -s
A README.md
A src/books.js
$ git commit -m "Add README and book list"
[main (root-commit) 29f27c8] Add README and book list
2 files changed, 7 insertions(+)
create mode 100644 README.md
create mode 100644 src/books.js
$ git status
On branch main
nothing to commit, working tree clean
$ git log --oneline
29f27c8 (HEAD -> main) Add README and book list
git status -s aynı bilgiyi kısa biçimde verir; birazdan iki sütununu ayrıntılı okuyacağız. A, "added", yani staging area'ya yeni eklenmiş dosya.
Commit çıktısının ilk satırı çok şey anlatıyor. main commit'in atıldığı branch. root-commit, bunun öncesi olmayan ilk commit olduğunu söylüyor. 29f27c8 commit'in kısaltılmış hash'i, yani kimliği. Commit'i aynı içerik, aynı yazar ve aynı tarihle atarsan hash de aynı çıkar; bende sabit tarihle atıldığı için sende farklı bir hash görürsün. Altındaki create mode 100644 yeni bir dosyanın oluşturulduğunu ve normal (çalıştırılabilir olmayan) bir dosya olduğunu gösterir.
Commit'ten sonra git status "working tree clean" diyor: diskteki dosyalar, staging area ve son commit birebir aynı. git log --oneline da ilk commit'i ve HEAD -> main etiketini gösteriyor. main artık var ve bu commit'i gösteriyor.
Değişiklik Döngüsü ve status -s'in İki Sütunu
Gündelik çalışma bu döngünün tekrarı: düzenle, add, commit. Bir tur daha atalım. Sepet için yeni bir dosya, src/cart.js:
import { books } from "./books.js";
export function addToCart(cart, bookId) {
const book = books.find((b) => b.id === bookId);
return book ? [...cart, book] : cart;
}
README'nin sonuna da Books live in src/books.js. satırını ekleyelim:
# src/cart.js yeni, README.md'nin sonuna bir satır eklendi
$ git status
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md
Untracked files:
(use "git add <file>..." to include in what will be committed)
src/cart.js
no changes added to commit (use "git add" and/or "git commit -a")
$ git status -s
M README.md
?? src/cart.js
Uzun çıktı iki ayrı grup gösteriyor: daha önce commit'lenmiş ve değişmiş bir dosya ("modified") ile yeni, takip edilmeyen bir dosya. Kısa çıktıda iki sütun var. Soldaki sütun staging area'nın son commit'e göre durumu, sağdaki sütun working tree'nin staging area'ya göre durumu. M sol sütun boş, sağ sütunda M: dosya değişmiş ama değişiklik henüz staging area'da değil. ?? ise untracked. İkisini de ekleyelim:
$ git add README.md src/cart.js
$ git status -s
M README.md
A src/cart.js
M sol sütuna geçti: değişiklik staging area'da, commit'e hazır. Şimdi açılıştaki soruya gelelim. Commit atmadan önce README'ye bir satır daha ekliyoruz:
# README.md'ye bir satır daha ekledikten sonra
$ git status -s
MM README.md
A src/cart.js
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: README.md
new file: src/cart.js
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md
MM: iki sütunda da M var. Uzun çıktı da aynı dosyayı iki grupta birden gösteriyor. Staging area'da README'nin ilk eklemeli hâli duruyor; working tree'de ise iki eklemeli hâli. Bu anda commit atarsan commit'e staging area'daki hâl, yani tek satırlı ekleme girer. İkinci satır working tree'de kalır. Açılıştaki sorunun cevabı bu: commit, working tree'nin değil staging area'nın fotoğrafını çeker.
İkinci satırı da commit'e katmak istediğimiz için dosyayı tekrar ekleyip commit'liyoruz:
$ git add README.md
$ git status -s
M README.md
A src/cart.js
$ git commit -m "Add shopping cart"
[main fae4454] Add shopping cart
2 files changed, 9 insertions(+)
create mode 100644 src/cart.js
$ git log --oneline
fae4454 (HEAD -> main) Add shopping cart
29f27c8 Add README and book list
Artık iki commit'lik bir geçmişimiz var. HEAD -> main etiketi yeni commit'e geçti; main her commit'te bir ileri kayar. .git klasörüne tekrar bakarsan birkaç yeni şey görürsün:
$ ls -1F .git
COMMIT_EDITMSG
HEAD
config
description
hooks/
index
info/
logs/
objects/
refs/
index, staging area'nın kendisi; ilk git add ile oluştu. COMMIT_EDITMSG son commit mesajını, logs/ da branch'lerin hangi commit'ten hangisine geçtiğinin kaydını tutar. objects/ klasörünün içi de artık dolu. Orada tam olarak ne olduğunu bir sonraki yazıda açacağız.
git add'in Biçimleri
git add'e dosya, klasör ya da desen verebilirsin. git add . bulunduğun klasörün altındaki her şeyi ekler, git add -A bütün repo'daki yeni, değişmiş ve silinmiş dosyaları ekler. Bu ikisi rahat ama tehlikeli: ne eklediğini görmeden her şeyi koliye atarsın. git add -p ise bir dosyadaki değişiklikleri parça parça gösterip her parça için "ekleyeyim mi?" diye sorar; aynı dosyadaki iki ayrı işi iki commit'e bölmenin en temiz yolu bu.
git commit -a ise staging adımını atlar: takip edilen ve değişmiş dosyaları otomatik ekleyip commit'ler. "Takip edilen" kısmı önemli; yeni, untracked bir dosyayı -a eklemez. Küçük, tek dosyalık düzeltmelerde işe yarar ama alışkanlık hâline getirirsen staging area'nın sana verdiği kontrolü kaybedersin.
Sık Düşülen Tuzaklar
İlk tuzak, git init'i yanlış klasörde çalıştırmak. Home klasöründe yanlışlıkla git init dersen bütün home klasörün bir repo olur ve altındaki her proje bu repo'nun parçası gibi görünür. git status beklemediğin kadar çok untracked dosya gösteriyorsa önce nerede olduğuna bak; git rev-parse --show-toplevel repo'nun kökünü söyler.
İkincisi, git add'den sonra dosyayı değiştirmeye devam edip son hâlin commit'lendiğini sanmak. MM örneğinde gördüğün gibi commit staging area'yı alır. Commit'ten önce git status'a bir bakmak bu sürprizi önler.
Üçüncüsü, boş klasörlerin commit'lenmesini beklemek. Git dosyaları takip eder, klasörleri değil. İçinde dosya olmayan bir klasör git status'ta görünmez ve commit'e girmez. Klasörün repo'da durması gerekiyorsa içine küçük bir dosya koymak yaygın bir çözüm.
Dördüncüsü, git add . ile şifre dosyalarını, node_modules'u ya da build çıktısını da repo'ya katmak. Bunun önlemi .gitignore; ona iki yazı sonra geleceğiz.
Küçük Bir Deneme
★☆☆ Kendi bilgisayarında bookstore repo'sunu bu yazıdaki adımlarla kur ve iki commit'i at. Her adımdan sonra git status ile git status -s çıktılarını yan yana koyup aynı bilgiyi nasıl gösterdiklerini karşılaştır.
★★☆ src/books.js'e yeni bir kitap ekle ve git add de. Sonra aynı dosyada bir kitabın fiyatını değiştir. git status -s ne gösterir? Commit atmadan önce commit'e hangi değişikliğin gireceğini tahmin et, commit'ten sonra git status ile kontrol et.
★★★ Boş bir docs klasörü oluştur ve git status'ta görünüp görünmediğine bak. Sonra içine bir dosya koy. Ardından yeni bir dosya oluşturup git commit -a -m "..." dene: yeni dosya commit'e girdi mi?
Bu yazıda commit'i dışarıdan gördük. Bir sonraki yazıda içine giriyoruz: commit gerçekten bir diff mi, yoksa bir fotoğraf mı? git cat-file ile blob, tree ve commit object'lerini açacak, hash'in nereden geldiğini görecek, iyi bir commit mesajının nasıl yazıldığını ve git commit --amend'i konuşacağız.