diff ve patch Komutları ile Metin Tabanlı Değişiklikleri Yönetme ve Uygulama
Sürüm kontrol sistemleri hayatımıza girmeden önce, iki dosya arasındaki farkı takip etmek ve bu farkı başka bir sisteme uygulamak gerçek bir sorundu. Bugün Git kullanıyoruz, evet. Ama diff ve patch komutlarını bilmeden Git’in arka planda ne yaptığını anlamak mümkün değil. Üstelik Git’in olmadığı senaryolar hala var: legacy sistemler, konfigürasyon dosyaları, kernel patch’leri, sistem yöneticisinin kendi yazdığı scriptler… Bu iki komutu iyi bilmek, sizi “dosyayı elle düzenleyeyim” modundan çıkarıp gerçek anlamda kontrollü değişiklik yönetimine taşır.
diff Komutunun Temelleri
diff komutu, iki metin dosyası arasındaki satır bazlı farkları bulur. Çıktısı hem insan tarafından okunabilir hem de patch komutu tarafından işlenebilir bir formattadır. Bu ikili kombinasyon, Linux dünyasında onlarca yıldır değişmeyen klasik bir iş akışını oluşturur.
En basit kullanımı şu şekilde:
diff dosya1.txt dosya2.txt
Ama bu çıktıyı ilk gördüğünüzde biraz kafa karıştırıcı gelebilir. < işareti ilk dosyada olan satırları, > işareti ikinci dosyada olan satırları gösterir. Ortadaki --- iki bloğu birbirinden ayırır. 2c2 gibi ifadeler ise “2. satır değişti” anlamına gelir. c change, a add, d delete demek.
Gerçek dünyada neredeyse her zaman unified format kullanırsınız:
diff -u orijinal.conf guncel.conf
Bu format, değişikliğin bağlamını gösterir ve patch komutunun tercih ettiği standarttır. + ile başlayan satırlar eklenenleri, - ile başlayanlar silinenleri gösterir.
Önemli diff Parametreleri
-u veya –unified: Unified diff formatı üretir, bağlam satırlarını da gösterir. Patch oluşturmak için altın standarttır.
-c: Context format, eski stil ama bazı araçlarla hala uyumlu.
-r veya –recursive: Dizinleri özyinelemeli olarak karşılaştırır, büyük proje farklarını bulmak için kritik.
-i: Büyük/küçük harf farklarını yoksayar, konfigürasyon dosyalarını karşılaştırırken bazen işe yarar.
-w: Tüm boşluk farklarını yoksayar.
-B: Boş satır farklarını yoksayar.
-N veya –new-file: Karşılaştırmada eksik dosyaları boş dosya gibi ele alır, yeni eklenen dosyaları patch’e dahil etmek için gerekli.
–color: Terminalde renkli çıktı verir, elle inceleme yaparken göz yormuyor.
-q veya –brief: Sadece dosyaların farklı olup olmadığını söyler, içeriği değil. Script’lerde kontrol amacıyla kullanışlı.
Gerçek Senaryo: Nginx Konfigürasyonu Değişikliği
Diyelim ki production sunucunuzdaki Nginx konfigürasyonunu güncellemek istiyorsunuz. Yedek almak ve değişikliği belgelemek için diff kullanmak iyi bir pratik:
# Önce yedeği al
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup
# Değişiklikleri yap
vim /etc/nginx/nginx.conf
# Farkı göster ve kaydet
diff -u /etc/nginx/nginx.conf.backup /etc/nginx/nginx.conf > nginx_degisiklik.patch
# Patch dosyasını incele
cat nginx_degisiklik.patch
Bu patch dosyasını Git reponuza commit edebilir, ticketing sisteminize ekleyebilir ya da takım arkadaşlarınıza mail ile iletebilirsiniz. Değişikliğin tam olarak ne olduğu kayıt altına girmiş olur.
Dizin Bazlı Karşılaştırma
Bir yazılımın veya bir konfigürasyon setinin tüm dizinini karşılaştırmak istediğinizde -r parametresi devreye girer:
diff -ruN /etc/myapp/config.d.old/ /etc/myapp/config.d/ > myapp_full.patch
Burada -N parametresi önemli. Bir dosya eski dizinde yokken yeni dizinde varsa veya tam tersi, bu parametre olmadan diff o dosyayı atlar. -N ile birlikte yeni eklenen dosyalar da patch’e dahil edilir.
Hangi dosyaların farklı olduğunu hızlıca görmek istiyorsanız:
diff -rq /srv/app/v1.0/ /srv/app/v2.0/
Bu komut sadece farklı olan dosyaların isimlerini listeler, içeriklerine girmez. Yüzlerce dosyalı bir projede önce genel görünümü almak için idealdir.
patch Komutu ile Değişiklikleri Uygulamak
patch komutu, diff tarafından üretilen bir patch dosyasını alır ve hedef dosyaya veya dizine uygular. Temel kullanımı:
patch dosya.txt < degisiklik.patch
Eğer patch unified formatta üretildiyse, genellikle hangi dosyaya uygulanacağını zaten bilir. Ama bazen dizin yapısı değişmiş olabilir veya patch’i farklı bir konumdan uygulamak zorunda kalırsınız. Bu durumda -p parametresi kritik öneme sahip olur.
patch’in -p Parametresini Anlamak
Bu parametre, patch dosyasındaki yol bilgisinden kaç dizin seviyesinin çıkarılacağını belirtir. Kafa karıştırıcı gelir ama örnek üzerinden anlaşılır:
Patch dosyasının içindeki yol şuysa:
--- a/etc/nginx/nginx.conf
+++ b/etc/nginx/nginx.conf
-p0 diyorsanız a/etc/nginx/nginx.conf dosyasına uygulamaya çalışır. -p1 diyorsanız etc/nginx/nginx.conf dosyasına uygulamaya çalışır. -p2 diyorsanız nginx/nginx.conf dosyasına uygulamaya çalışır.
Standart Git formatında üretilen patch’ler için genellikle -p1 kullanılır.
# Git formatındaki bir patch'i uygulamak
patch -p1 < feature_branch.patch
# Belirli bir dizin içinde uygulamak
cd /srv/myapp
patch -p1 < /tmp/update.patch
Önemli patch Parametreleri
-p NUM: Yol önekini belirtilen sayıda dizin atlar.
-R veya –reverse: Patch’i tersine uygular, yani değişikliği geri alır. Bir şeylerin ters gittiği durumlarda can kurtarıcı.
-b veya –backup: Patch uygulamadan önce orijinal dosyaların yedeğini alır. Production’da mutlaka kullanın.
–dry-run: Patch’i gerçekten uygulamaz, sadece ne olacağını simüle eder. Büyük patch’leri uygulamadan önce test etmek için vazgeçilmez.
-i DOSYA: Patch içeriğini stdin yerine dosyadan okur.
–verbose: Detaylı çıktı verir, hata ayıklamak için kullanışlı.
-f veya –force: Hatalı patch olsa da uygulamayı zorla, dikkatli kullanın.
Dry-Run ile Güvenli Test
Bir patch’i production sistemine uygulamadan önce her zaman dry-run yapın:
# Önce simüle et
patch --dry-run -p1 < kritik_guncelleme.patch
# Hata yoksa gerçekten uygula
patch -p1 -b < kritik_guncelleme.patch
-b parametresi ile yedekleme de aldık. Orijinal dosya .orig uzantısıyla saklanır.
Gerçek Senaryo: Kernel Modülü Yaması
Kernel development camiasında patch workflow’u hala aktif olarak kullanılıyor. Bir kernel modülüne hata düzeltmesi uygulamak istediğinizde:
# Kernel kaynak dizinine git
cd /usr/src/linux-5.15.0
# Upstream'den gelen patch dosyasını uygula
patch -p1 --dry-run < fix_network_driver.patch
# Dry-run başarılıysa gerçekten uygula
patch -p1 < fix_network_driver.patch
# Başarıyla uygulanan veya reddedilen hunkleri gör
# .rej uzantılı dosyalar reddedilen değişiklikleri içerir
find . -name "*.rej"
.rej uzantılı dosyalar, patch’in uygulanamadığı kısımları içerir. Bu dosyalar varsa o hunkleri elle uygulamanız gerekir. Kaynak kod çok değişmişse bu kaçınılmaz olabilir.
Çakışmaları Yönetmek
Patch uygulanırken “hunk FAILED” hatası alırsanız iki şey olabilir: ya hedef dosya patch beklenenden farklı bir versiyondadır ya da aynı satırlar başka bir değişiklikle çakışmaktadır.
# Hata aldığınızda hangi hunklerin başarısız olduğunu görmek için verbose kullanın
patch -p1 --verbose < degisiklik.patch
# Reddedilen hunkleri kontrol et
ls -la *.rej
cat dosya.c.rej
.rej dosyasını açıp hangi satırların uygulanamadığını görün, sonra hedef dosyada o bölgeyi elle düzenleyin. Bu elle müdahale gerektiren bir iş olsa da diff çıktısını okuyabiliyorsanız ne yapmanız gerektiği net olarak görünür.
Birden Fazla Patch’i Sırayla Uygulamak
Sistemlerde sıralı yama uygulaması yaygın bir ihtiyaçtır. Özellikle bir yazılımı belirli bir versiyondan başka bir versiyona adım adım taşırken:
# Patch dosyalarını sıralı şekilde uygula
for patch_file in patches/0001-*.patch patches/0002-*.patch patches/0003-*.patch; do
echo "Uygulanıyor: $patch_file"
patch -p1 --dry-run < "$patch_file" && patch -p1 < "$patch_file"
if [ $? -ne 0 ]; then
echo "HATA: $patch_file uygulanamadı, durduruluyor."
exit 1
fi
done
echo "Tüm patch'ler başarıyla uygulandı."
Bu script, her patch’i önce test eder, başarısızlık olursa durur. Böylece yarım yamalak bir durumda kalmamış olursunuz.
diff3: Üç Yönlü Karşılaştırma
İki dosyayı değil, ortak bir tabanı olan iki farklı versiyonu karşılaştırmak istediğinizde diff3 devreye girer. Git’in merge işlemi de bu prensipte çalışır:
# Temel dosya, benim değişikliğim, onların değişikliği
diff3 benim_versiyon.conf orijinal.conf onlarin_versiyon.conf
Çakışan satırlar işaretlenerek gösterilir. Standart diff‘e göre daha az kullanılsa da iki farklı sysadmin’in aynı konfigürasyona dokunduğu durumlarda değerli olabilir.
Git ile diff ve patch Entegrasyonu
Git kullansanız bile diff ve patch komutlarını Git workflow’una entegre edebilirsiniz. Bir branch’teki değişiklikleri patch formatında dışa aktarmak:
# Belirli commit'leri patch olarak kaydet
git format-patch HEAD~3..HEAD -o /tmp/patches/
# Bu patch'leri başka bir repoya uygulamak
cd /path/to/other/repo
git am /tmp/patches/*.patch
Ya da daha basit bir senaryo: git diff çıktısını alıp başka bir sisteme uygulamak:
# Değişiklikleri dışa aktar
git diff main feature-branch > ozellik.patch
# Başka makinede uygula (git olmayan bir sistemde)
patch -p1 < ozellik.patch
Bu, CI/CD pipeline’larında veya air-gapped sistemlerde sıkça karşılaşılan bir ihtiyaçtır.
Pratik: Konfigürasyon Yönetimi için diff Otomasyonu
Büyük altyapılarda konfigürasyon değişikliklerini takip etmek için basit bir bash scripti:
#!/bin/bash
# konfig_takip.sh - Konfigürasyon değişikliklerini otomatik kaydet
KONFIG_DIR="/etc/myapp"
YEDEK_DIR="/var/backup/konfig/$(date +%Y%m%d_%H%M%S)"
DEGISIKLIK_LOG="/var/log/konfig_degisiklikleri.log"
mkdir -p "$YEDEK_DIR"
# Mevcut konfigürasyonla son yedeği karşılaştır
SON_YEDEK=$(ls -td /var/backup/konfig/*/ 2>/dev/null | head -2 | tail -1)
if [ -n "$SON_YEDEK" ]; then
diff -ruN "$SON_YEDEK" "$KONFIG_DIR" > "$YEDEK_DIR/degisiklikler.patch"
if [ -s "$YEDEK_DIR/degisiklikler.patch" ]; then
echo "$(date): Konfigürasyon değişikliği tespit edildi." >> "$DEGISIKLIK_LOG"
echo "Patch: $YEDEK_DIR/degisiklikler.patch" >> "$DEGISIKLIK_LOG"
else
echo "$(date): Değişiklik yok." >> "$DEGISIKLIK_LOG"
fi
fi
# Güncel konfigürasyonun yedeğini al
cp -r "$KONFIG_DIR" "$YEDEK_DIR/snapshot"
echo "Yedek alındı: $YEDEK_DIR"
Bu script’i cron ile çalıştırırsanız, konfigürasyonlarınızda ne zaman ne değişti, bunu her zaman geri alabilirsiniz ve minimum araçla çalışır. Ansible veya Puppet olmayan ortamlar için hafif ve etkili bir çözüm.
Patch Güvenliği ve Dikkat Edilmesi Gerekenler
Güvenmediğiniz kaynaklardan gelen patch dosyalarını körü körüne uygulamayın. Bir patch dosyası aslında dosya sisteminizde herhangi bir konuma dosya yazabilir çünkü yol bilgisi manipüle edilebilir. Özellikle .. içeren path’lere dikkat edin:
# Patch dosyasının içeriğini önce inceleyin
grep "^---|^+++" suphe_li_patch.patch
# Mutlak yol veya üst dizin referansı var mı?
grep ".." suphe_li_patch.patch
grep "^+++ /" suphe_li_patch.patch
Güvenilir kaynaklardan gelse bile production’da mutlaka dry-run yapın ve yedek alın. Bu söylemesi kolay, yapması bazen unutulan basit bir kuraldır.
Sonuç
diff ve patch, Linux sistem yönetiminin temel taşlarından ikisidir. Git’in varlığına rağmen bu komutlar hala doğrudan ihtiyaç duyulan araçlardır: kernel yamaları, legacy sistemler, Git erişimi olmayan ortamlar, basit konfigürasyon takibi… Bunları bilmek sizi salt araç kullanıcısı olmaktan çıkarıp işin mantığını anlayan biri yapar.
Özetle şunu söyleyebilirim: diff -u ile fark oluşturun, patch --dry-run ile test edin, -b ile yedek alarak uygulayın. Bu üç adım sizi çoğu sorundan korur. Geri kalanı deneyimle gelir.
