fix(partition): silinen alandan açılan "/", boş alan satırını gizlemiyordu

Kullanıcı raporu: "boş alan sorunu geri gelmiş bir alanı sildim boş
alan olarak doğru oku, sonrasında o boş alanı yeni bölüm diyerek /
dizin yaptım biçimlendir seçtim kaydettim ama boş alan duruyor
yerinde, yeni oluşturduğumda duruyor."

## Sebep — koşul YER YER yazılmıştı

Bu tablodaki bir hata üçüncü kez yaşanıyor ve üçü de aynı sebepten:
`has_symbolic` kontrolü satırın YALNIZCA BİR basış yerine eklenmişti.

  1. `else if` dalı vardı → tanım varsa satır hiç basılmıyordu
  2. `else if` düzeltildi, DÖNGÜ İÇİ basış unutuldu
  3. döngü içi de ayrı ayrı yazıldı → yine kaçırılabilir

Senaryo: sdb2 silindi → boş alan DOĞRU göründü → o alandan "Yeni
Bölüm" ile `/` açıldı. `/` "Kalan Alan" olduğu için SEMBOLİKTİR ve
alanın tamamını alır. Ama silinen bölümün yerine basılan satır
sembolik tanımı görmüyordu ve diskin tamamını "boş" göstermeye
devam etti — kullanıcının o satırın sağında gördüğü.

## Düzeltme — koşul tek yerde

    let show_free = free_total_mb > 0 && !has_symbolic;

`show_free` artık ÜÇ basış yerinin de ortak koşulu:
döngü içi (silinen grubun sonu), döngü sonu (sonda işaretli), sondaki
`else if` (disk sonu).

Koşulu üç yere ayrı ayrı yazmayı bırakmak bu sınıf hatayı
kaçınılmaz kılıyordu; aynı hatanın üçüncü kez yaşanması bunun
kanıtı.

## Testler (143 → 145)

- ⭐ kullanıcının senaryosu: silinen bölümün yerine sembolik `/`
  açıldı → boş alan satırı KAYBOLUR, 2 satır kalır
- aynı senaryo ama SABİT boyutlu bölüm → boş alan satırı kalır ve
  miktar doğru düşer (500000 − 50000 = 450000)

İkinci test önemli: satırın kaybolmaması da bir hata olurdu —
sabit boyutlu bölüm gerçekten yer bırakıyor.

cargo check --all-targets : 0 hata, 0 uyarı
cargo test              : 145/145

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erkan IŞIK
2026-09-27 22:17:43 +03:00
parent 5063d9d9dd
commit 4f12334dd6
2 changed files with 107 additions and 13 deletions
+25 -3
View File
@@ -319,7 +319,7 @@ boş alandan düşmedi"* — 20 GB'lik disk 39 GB kullanıyor gibi görünüyord
| Yer | Kural |
|---|---|
| `has_symbolic_remaining(parts, disk)` | **TEK KAYNAK.** `!delete && size_mb == 0 && orig_device.is_empty()` |
| tablo (`build_table_rows`) | sembolik varsa boş alan satırı **çizilmez** — o alan o tanıma ait |
| tablo (`build_table_rows`) | sembolik varsa boş alan satırı **çizilmez** — o alan o tanıma ait. Koşul **`show_free`** adıyla TEK yerde tanımlı ve **üç basış yerinin de** ortak koşulu (aşağıdaki REGRESYON 3'e bak) |
| çubuk (`manual_dual_bars`) | sembolik varsa serbest segment **eklenmez** |
| `manual_capacity_error` | sembolik varsa `fixed_mb >= cap_mb` reddedilir |
@@ -331,6 +331,28 @@ kalanın tamamını alır, ikincisine hiç yer kalmaz.
`orig_device` dolu (taranmış) 0 boyutlu bölüm sembolik **DEĞİLDİR**:
sıfır boyut "kalanın tamamı" anlamına gelmez.
#### ⭐ Koşul YER YER yazılırsa bu sınıf hata tekrarlar
Bu tabloda bir hata **üç kez** yaşandı ve üçü de aynı sebepten:
`has_symbolic` kontrolü satırın **yalnızca bir** basış yerine
eklenmişti.
1. `else if` dalı vardı, diskte tanım yokken çalışıyordu →
tanım varsa satır hiç basılmıyordu (`sda1` + 15 GB boş)
2. `else if` düzeltildi, **döngü içindeki** basış unutuldu →
`19.8 GB` bölüm + `19.8 GB` boş alan
3. döngü içi de düzeltildi ama **koşul üç yere ayrı ayrı yazıldı**
**Ders:** aynı koşulu birden çok yerde yazma. Tek bir
`show_free = free_total_mb > 0 && !has_symbolic` değişkeni hesaplanıp
**üç basış yerinin de** koşulu olarak kullanılıyor. Kural tek
yerde; onu atlamak ya da çoğaltmak imkânsız.
> Bu, "sabit bir sayıyı 3 yere yazma" değil — "bir *koşulu* 3
> yere yazma". Sabit sayıyı çoğaltmak hataya açıktır ama en azından
> yanlışsa sayı yanlış görünür; koşulu çoğaltmak bazı basış
> yerlerinde **sessizce yanlış** davranış üretir.
### ⭐ Bağlanmayan dosya sistemleri — mountpoint isteğe bağlı
`mountpoint_required(editing, fstype)` üç dosya sisteminde `false`
@@ -669,11 +691,11 @@ nedeni yorumla sabitlendi. Log bile "MiB" derken argüman "MB" gönderiyordu.
```
cargo check --all-targets : 0 hata, 0 uyarı
cargo test : 143/143 (test modülü sayısı arttıkça güncel tut)
cargo test : 145/145 (test modülü sayısı arttıkça güncel tut)
cargo clippy : ~77 uyarı, tamamı stil (collapsed_if, replace zinciri)
```
Bu oturumda 1 → 143 test. Test modülü olmayan dosyalar (`users.rs`,
Bu oturumda 1 → 145 test. Test modülü olmayan dosyalar (`users.rs`,
`location.rs`, `rescue.rs`, `funct.rs`, `autoinstall/mod.rs`, `main.rs`)
içine modüller eklendi. Çoklu test modülü paylaşılan yardımcıları
`steps/partition.rs::testutil` altındadır (`disk()`, `part()`).
+82 -10
View File
@@ -762,8 +762,12 @@ fn build_table_rows<'a>(
.map(|p| p.size_mb)
.sum();
let free_total_mb = disk_mb.saturating_sub(used_total);
// "Kalan Alan" tanımı kalanın tamamını alır → boş alan satırı çizilmez.
// "Kalan Alan" tanımı kalanın tamamını alır → boş alan satırı
// çizilmez. ⚠️ Bu koşul ÜÇ basış yerinin de ORTAK koşuludur.
// Sadece sondakine uygulanırsa diğer ikisi sembolik tanımı
// görmez (aşağıdaki REGRESYON 3).
let has_symbolic = has_symbolic_remaining(parts, active_disk);
let show_free = free_total_mb > 0 && !has_symbolic;
let mut fs_first: Option<usize> = None;
// Boş alan satırı disk başına TEK'tir (GParted tarzı: silinenler
@@ -779,7 +783,7 @@ fn build_table_rows<'a>(
}
// İşaret bloğunun bittiği yerde boş-alan satırını bas
if let Some(first) = fs_first.take() {
if free_total_mb > 0 {
if show_free {
visible_rows.push(TableRow::FreeSpace {
disk: active_disk.to_string(),
total_mb: free_total_mb,
@@ -794,14 +798,14 @@ fn build_table_rows<'a>(
if let Some(first) = fs_first.take() {
// Son ifade olduğu için `free_row_shown` burada güncellenmez;
// zaten sonrasında okunacak bir dal yok.
if free_total_mb > 0 {
if show_free {
visible_rows.push(TableRow::FreeSpace {
disk: active_disk.to_string(),
total_mb: free_total_mb,
first_idx: Some(first),
});
}
} else if free_total_mb > 0 && !free_row_shown && !has_symbolic {
} else if show_free && !free_row_shown {
// Disk SONUNDAKİ boş alan — diskte tanım olsun ya da olmasın.
//
// REGRESYON: koşul `all(|p| p.disk != active_disk)` idi, yani
@@ -809,14 +813,27 @@ fn build_table_rows<'a>(
// Diskte tanım var ama hiçbiri silinmek işaretli değilse
// (`sda1` + 15 GB boş) satır hiç basılmıyordu.
//
// `has_symbolic` kuralı: "Kalan Alan" tanımı varsa boş alan o
// tanıma aittir. REGRESYON 2: sembolik tanım (`size_mb == 0`)
// hiçbir hesaba katılmadığı için hem bölüm satırı hem boş alan
// satırı kalanın tamamını gösteriyordu — 20 GB'lik disk
// 39 GB kullanıyormuş gibi görünüyordu. Kullanıcı raporu:
// "eklenen bölümün 19.8 gb demesine rağmen bunu boş alandan
// REGRESYON 2: sembolik tanım (`size_mb == 0`) hiçbir hesaba
// katılmadığı için hem bölüm satırı hem boş alan satırı
// kalanın tamamını gösteriyordu — 20 GB'lik disk 39 GB
// kullanıyor gibi görünüyordu. Kullanıcı raporu:
// düşmedi".
//
// düşmedi".
//
// REGRESYON 3 (kullanıcı: "bir alanı sildim boş alan olarak
// doğru okudu, sonrasında o boş alanı yeni bölüm diyerek /
// dizin yaptım… ama boş alan duruyor yerinde, yeni oluşturduğumda
// duruyor"): REGRESYON 2'nin düzeltmesi YALNIZCA bu `else if`
// dalına uygulanmıştı. Silinen bölümün yerine **döngü içinde**
// basılan satır `has_symbolic`'e BAKMIYORDU. `/` "Kalan Alan"
// olduğu için semboliktir ve alanın tamamını alır; buna rağmen
// satır diskin tamamını "boş" gösteriyordu.
//
// Çözüm: `show_free` TEK koşul ve üç basış yerinin de ortak
// koşulu. Koşulu yer yer yazmak bu sınıf hatayı kaçınılmaz
// kılıyordu — üçüncü kez düzeltmek zorunda kaldık.
//
// `first_idx: None` burada "seçilemez" DEĞİL demektir; yalnızca
// temsil ettiği silinmiş bir tanım yoktur. Tıklanabilirdir
// (`global_idx == None` dalı `selected_free` tutar ve
@@ -4027,6 +4044,61 @@ mod free_space_tests {
// ── "Kalan Alan" (sembolik) tanımlar ───────────────────────
/// ⭐ Kullanıcının senaryosu: bir alanı sildi → boş alan DOĞRU
/// göründü → o boş alandan "Yeni Bölüm" ile `/` yaptı → boş alan
/// **yerinde durdu**, hem de yeni bölümün yanında.
///
/// REGRESYON: sembolik tanım kuralı (`has_symbolic`) yalnızca
/// sondaki `else if` dalına uygulanmıştı. Silinen bölümün yerine
/// **döngü içinde** basılan satır kuralı görmüyordu. `/` "Kalan
/// Alan" olduğu için semboliktir (alanın tamamını alır) ama satır
/// yine de diskin tamamını "boş" gösteriyordu.
#[test]
fn creating_root_in_a_freed_area_consumes_the_free_row() {
let mut dead = part("/dev/sdb2", 100_000, FsType::Ext4, "/");
dead.delete = true; // kullanıcı sildi
let mut root = part("/dev/sdb3", 0, FsType::Ext4, "/");
root.orig_device = String::new(); // "Kalan Alan" → sembolik
let parts = vec![
part("/dev/sdb1", 1_024, FsType::Fat32, "/boot/efi"),
dead,
root,
];
let rows = build_table_rows(&parts, "/dev/sdb", 500_000);
assert!(
free_mb_of(&rows).is_empty(),
"sembolik / alanın tamamını alıyor, boş alan satırı kalmamalı: {:?}",
free_mb_of(&rows)
);
assert_eq!(
rows.len(),
2,
"sdb1 + yeni / olmalı, boş alan satırı olmamalı: {}",
rows.len()
);
}
/// Aynı senaryo ama yerine SABİT boyutlu bölüm konursa boş alan
/// **kalır** ve miktar doğru düşer — sembolik değil, gerçek yer
/// vardır.
#[test]
fn fixed_size_partition_in_a_freed_area_keeps_a_correct_free_row() {
let mut dead = part("/dev/sdb2", 100_000, FsType::Ext4, "/");
dead.delete = true;
let mut data = part("/dev/sdb3", 50_000, FsType::Ntfs, "");
data.orig_device = String::new();
let parts = vec![dead, data];
let rows = build_table_rows(&parts, "/dev/sdb", 500_000);
// 500_000 − 50_000 = 450_000 boş kalmalı
assert_eq!(
free_mb_of(&rows),
vec![450_000],
"sabit boyutlu bölüm boş alanı düşürmeli, satır kalmalı"
);
}
/// Kullanıcı raporu: "eklenen bölümün 19.8 gb demesine rağmen
/// bunu boş alandan düşmedi".
///