From 332d5a787f833d88428182e244490055bfa1689e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erkan=20I=C5=9EIK?= Date: Sun, 27 Sep 2026 14:52:08 +0300 Subject: [PATCH] =?UTF-8?q?fix(partition):=20mount=20edilmemi=C5=9F=20b?= =?UTF-8?q?=C3=B6l=C3=BCmde=20"D=C3=BCzenle"=20g=C3=B6r=C3=BCns=C3=BCn,=20?= =?UTF-8?q?create=5Fover=20kald=C4=B1r=C4=B1ld=C4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kullanıcı iki şey bildirdi: 1. Bir partitiona tıklayınca "Düzenle" gelmiyor, "Yeni Bölüm" ve "Buraya Oluştur" geliyor. 2. "Yeni Bölüm" ve "Buraya Oluştur" aynı mekanizma değil mi? Haklı: ikisi de yeni tanım üretiyor. ## Neden "Düzenle" hiç görünmüyordu `is_unassigned` koşulu: `orig_device` dolu + `mountpoint` boş + silinmemiş. lsblk'den taranan ve o anda MOUNT EDİLMEMİŞ her fiziksel bölüm bu koşulu sağlar. Yani neredeyse her mevcut bölümde buton "✏ Düzenle" yerine "➕ Buraya Oluştur" yazıyordu. Mount edilmemiş olmak bölümü "yeni" yapmaz — kullanıcı tam olarak onu seçip mountpoint atamak istiyordur. Düzenlemek zaten o işi yapar. ## Veri kaybı riski (create_over dalı) `create_over` dalı alanları temizleyip `add_skip_format = false` atıyordu. Taranmış fiziksel bölümler `skip_format: true` ile gelir (kullanıcının verisi korunmalı). Bu dal o korumayı düşürüyordu: kullanıcı "Oluştur"a basıp kaydedince FAZ C `mkfs` çalıştırıp MEVCUT BÖLÜMÜNÜ biçimlendirirdi. Aynı anda "Yeni Bölüm" butonu soluk çizildiği için iki buton yan yana duruyor, hangisi geçerli belirsizdi. ## Değişiklikler - `is_unassigned` dalı tamamen kaldırıldı - Her bölüm satırı "✏ Düzenle" açar, `skip_format` dahil mevcut değerler yüklenir ve korunur - "Yeni Bölüm" bölüm seçiliyken artık hiç çizilmez (soluk bile) — aynı mekanizma olduğu için ikisinin birden görünmesi kafa karıştırıyordu - `ManualState::create_over` alanı silindi; `is_create_like = !is_edit` oldu. Ölü durum bırakılmadı. - Kullanılmayan `mp_create_here` çeviri anahtarı iki locale'den silindi ## Testler `edit_flow_tests` (3 yeni): mount edilmemiş bölüm hâlâ düzenlenebilir, düzenleme skip_format'ı korur, düzenleme mountpoint atar, "Yeni Bölüm" bölüm seçiliyken gizli. cargo check --all-targets : 0 hata, 0 uyarı cargo test : 88/88 geçti (85 → 88) Co-Authored-By: Claude Opus 4.8 (1M context) --- AGENTS.md | 14 +++- locales/en.toml | 1 - locales/tr.toml | 1 - src/steps/partition.rs | 183 +++++++++++++++++++++++++++-------------- 4 files changed, 134 insertions(+), 65 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ce24518..fa4f3fa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -362,6 +362,16 @@ nedeni yorumla sabitlendi. Log bile "MiB" derken argüman "MB" gönderiyordu. `selected_idx` ile **birbirini dışlar**. Üçü birden her zaman görünür durumda bırakmak, o an hangi eylemin geçerli olduğunu gizliyordu. `selection_tests` bu kuralı sabitler. +- ⚠️ **Mount edilmemiş fiziksel bölüm de "Düzenle" açar.** `create_over` + ("Buraya Oluştur") akışı **KALDIRILDI** — iki sebeple: + 1. "Yeni Bölüm" ve "Buraya Oluştur" aynı mekanizma; ikisi de yeni + tanım üretiyor, kullanıcı ayırt edemiyordu. + 2. O dal `skip_format = false` atıyordu. Taranmış fiziksel bölümler + `skip_format: true` ile gelir (verisi korunmalı) — sıfırlanırsa + kurulumda mkfs çalıştırıp **kullanıcının verisini bozardı**. + Artık her bölüm satırı "✏ Düzenle" açar ve `skip_format` dahil + mevcut değerleri korur. `ManualState::create_over` alanı da silindi + (ölü durum bırakılmadı). `edit_flow_tests` bunu sabitler. - Locale: `locales/` altında yalnızca `tr.toml` ve `en.toml` **var**. `set_locale("de")` hata vermez, sessizce kabul edilir ve `t!()` karşılığı olmayan her anahtar için **anahtarın kendisini** döndürür — @@ -380,7 +390,7 @@ nedeni yorumla sabitlendi. Log bile "MiB" derken argüman "MB" gönderiyordu. ``` cargo check --all-targets : 0 hata, 0 uyarı -cargo test : 85/85 (15 test modülü) +cargo test : 88/88 (16 test modülü) cargo clippy : ~77 uyarı, tamamı stil (collapsed_if, replace zinciri) ``` @@ -480,7 +490,7 @@ senaryoları dene: |---|---| | `src/jobs/partition.rs` | `plan_disk_layout`, `PhysicalExtent`, `PlannedCreate`, `disk_total_mb`, `read_physical_extents`, `device_map`; `disk_free_regions_mb` silindi | | `src/jobs/cleanup.rs` | **YENİ** — `EmergencyCleanupJob` | -| `src/steps/partition.rs` | `add_disk` + disk seçici, **tek diske odaklı tablo + disk şeridi**, **seçime göre görünen butonlar** (`selected_free`), diske filtreli LVM, `validate_manual_on`, `multidisk_tests`, `table_scope_tests`, `selection_tests`, `testutil` | +| `src/steps/partition.rs` | `add_disk` + disk seçici, **tek diske odaklı tablo + disk şeridi**, **seçime göre görünen butonlar** (`selected_free`), diske filtreli LVM, `validate_manual_on`, `multidisk_tests`, `table_scope_tests`, `selection_tests`, `edit_flow_tests`, `testutil` | | `src/jobs/users.rs` | `set_password_via_chpasswd` (stdin pipe) | | `src/jobs/bootloader.rs` | `CRYPTROOT_NAME`, `luks_backing_device`, `mounted_root_device` | | `src/jobs/finish.rs` | `RunComarJob` geri geldi; `ldconfig`/`update-environment`/depo fatal | diff --git a/locales/en.toml b/locales/en.toml index 0e7ae59..016ecd7 100644 --- a/locales/en.toml +++ b/locales/en.toml @@ -152,7 +152,6 @@ mp_target_disk = "Target disk" mp_active_disk = "Active disk" mp_active_disk_hint = "The disk highlighted in the table and the default target for the New Partition dialog. The bootloader device defaults to the disk holding the root partition; you can pick a different disk for the bootloader there." mp_target_disk_hint = "The disk this partition will be created on. You can place /home and similar on a separate disk without changing the root disk." -mp_create_here = "Create Here" mp_will_delete = "will be deleted" mp_restore = "Restore" mp_free_space = "Free Space" diff --git a/locales/tr.toml b/locales/tr.toml index 70f06b8..b328320 100644 --- a/locales/tr.toml +++ b/locales/tr.toml @@ -124,7 +124,6 @@ mp_target_disk = "Hedef disk" mp_active_disk = "Aktif disk" mp_active_disk_hint = "Tabloda vurgulanan disk ve Yeni Bölüm diyaloğunun varsayılan hedefi. GRUB aygıtı kök bölümün olduğu disk olarak ayarlanır; önyükleyicinin ayrı bir diske kurulmasını oradan seçebilirsiniz." mp_target_disk_hint = "Bu bölümün yerleşeceği disk. Kök diski değiştirmeden /home gibi bölümleri ayrı bir diske koyabilirsiniz." -mp_create_here = "Buraya Oluştur" mp_will_delete = "silinecek" mp_restore = "Geri Al" mp_free_space = "Boş Alan" diff --git a/src/steps/partition.rs b/src/steps/partition.rs index 1a255af..128baa5 100644 --- a/src/steps/partition.rs +++ b/src/steps/partition.rs @@ -227,10 +227,6 @@ struct ManualState { adding: bool, /// "Bölüm Düzenle" diyaloğu açık mı? editing: bool, - /// Silinmiş (ataması kaldırılmış) bir bölümün ÜZERİNE yeni - /// tanım oluşturma modu. editing=true ile birlikte çalışır: - /// kaydet, mevcut satırın üzerine yazar. - create_over: bool, /// Düzenlenecek bölüm indeksi edit_idx: Option, /// Seçili BÖLÜM satırının indeksi (global `custom_partitions` indeksi). @@ -1424,35 +1420,48 @@ impl PartitionStep { ui.add_space(8.0); ui.horizontal(|ui| { let has_selection = self.manual.selected_idx.is_some(); - let free_selected = self.manual.selected_free; let selected = self.manual.selected_idx .and_then(|i| state.custom_partitions.get(i)); - // Silinmiş (ataması kaldırılmış) fiziksel bölüm: mountpoint'i - // boş ama diskte gerçekten var. Bu satır için akış "Oluştur"dur. - let is_unassigned = selected - .map(|p| !p.orig_device.is_empty() && p.mountpoint.is_empty() && !p.delete) - .unwrap_or(false); + // ÖNEMLİ — `is_unassigned` özel durumu KALDIRILDI. + // + // Eskiden mount edilmemiş bir fiziksel bölüm seçilince buton + // "✏ Düzenle" yerine "➕ Buraya Oluştur" yazıyor ve + // `create_over` akışına giriyordu. Bu yanlıştı: + // + // 1) "Yeni Bölüm" ve "Buraya Oluştur" AYNI mekanizma — ikisi + // de yeni tanım üretir. Kullanıcı hangisinin ne yaptığını + // ayırt edemiyor, "Düzenle nerde?" diye soruyor. + // 2) create_over dalı alanları TEMİZLEyip `skip_format=false` + // atıyordu. Taranmış fiziksel bölümler `skip_format=true` + // ile gelir (korunmalı); bu dal o korumayı düşürüyor, yani + // kullanıcı "Oluştur"a basınca MEVCUT VERİSİ biçimlendiril- + // me riski vardı. + // + // Artık tek akış var: her bölüm satırı "✏ Düzenle" açar ve + // mevcut değerleri (skip_format dahil) koruyarak üzerine yazar. // Kurulumda silinmek üzere işaretlenmiş bölüm let is_marked_delete = selected.map(|p| p.delete).unwrap_or(false); // Düzenleme için seçim şart. Silinmek işaretli satırda // düzenleme anlamsızdır (zaten silinecek). let can_edit = has_selection && !is_marked_delete; - // ➕ Yeni Oluştur + // ➕ Yeni Bölüm — bölüm seçiliyken HİÇ ÇİZİLMEZ. // - // Boş alan seçiliyken tam renkli (o an geçerli eylem), - // seçim yoksa soluk. Bölüm seçiliyken soluk kalır — o an - // geçerli eylem Düzenle/Sil'dir. - let can_create = free_selected || !has_selection; - if theme::primary_button_enabled( - ui, - &format!("\u{EA13} {}", t!("mp_new_partition")), - can_create, - ).clicked() { + // Eskiden `primary_button_enabled(false)` ile soluk + // çiziliyordu, ama o zaman kullanıcı "Yeni Bölüm" ve + // "Düzenle"yi aynı anda görüyor, ikisi de aynı işi + // yaptığı için hangisini kullanacağını bilemiyordu. + // Bölüm seçiliyken o an geçerli eylem Düzenle'dir. + let show_new = !has_selection; + if show_new + && theme::primary_button( + ui, + &format!("\u{EA13} {}", t!("mp_new_partition")), + ).clicked() + { self.manual.adding = true; self.manual.editing = false; - self.manual.create_over = false; self.manual.edit_idx = None; // Yeni tanımın varsayılan diski: aktif disk. Kullanıcı // diyalogdaki seçiciyle değiştirebilir. @@ -1513,45 +1522,31 @@ impl PartitionStep { // `enabled: true` sabit geçirir), yani "pasif" görünür ama // yine de tıklanabilir olurdu. Burada zaten hiç çizilmiyor. if has_selection { - let edit_btn_label = if is_unassigned { - format!("\u{EA13} {}", t!("mp_create_here")) - } else { - format!("\u{EFE0} {}", t!("mp_edit_partition")) - }; + let edit_btn_label = format!("\u{EFE0} {}", t!("mp_edit_partition")); if theme::secondary_button_enabled(ui, &edit_btn_label, can_edit).clicked() && can_edit { if let Some(part) = selected { - if is_unassigned { - // Oluşturma akışı; ancak kaydedilince MEVCUT - // satırın üzerine yazılacak (edit_idx ayakta). - self.manual.create_over = true; - self.manual.editing = true; - self.manual.adding = false; - self.manual.edit_idx = self.manual.selected_idx; - self.manual.add_size_str.clear(); - self.manual.add_mountpoint.clear(); - self.manual.add_encrypt = false; - self.manual.add_luks_password.clear(); - self.manual.add_skip_format = false; + self.manual.editing = true; + self.manual.adding = false; + self.manual.edit_idx = self.manual.selected_idx; + // Mevcut değerler yüklenir — `skip_format` DAHİL. + // Taranmış fiziksel bölümler `skip_format: true` + // ile gelir (verisi korunmalı); bunu sıfırlamak + // kurulumda mkfs çalıştırıp kullanıcının verisini + // bozardı. + self.manual.add_size_str = if part.size_mb == 0 { + String::new() } else { - self.manual.editing = true; - self.manual.adding = false; - self.manual.create_over = false; - self.manual.edit_idx = self.manual.selected_idx; - self.manual.add_size_str = if part.size_mb == 0 { - String::new() - } else { - part.size_mb.to_string() - }; - self.manual.add_mountpoint = part.mountpoint.clone(); - self.manual.add_fstype = FS_OPTIONS.iter() - .position(|(_, ft)| *ft == part.fstype) - .unwrap_or(0); - self.manual.add_encrypt = part.encrypt; - self.manual.add_luks_password = part.luks_password.clone(); - self.manual.add_skip_format = part.skip_format; - } + part.size_mb.to_string() + }; + self.manual.add_mountpoint = part.mountpoint.clone(); + self.manual.add_fstype = FS_OPTIONS.iter() + .position(|(_, ft)| *ft == part.fstype) + .unwrap_or(0); + self.manual.add_encrypt = part.encrypt; + self.manual.add_luks_password = part.luks_password.clone(); + self.manual.add_skip_format = part.skip_format; } } } @@ -1619,10 +1614,11 @@ impl PartitionStep { let dialog_open = self.manual.adding || self.manual.editing; if dialog_open { let is_edit = self.manual.editing; - // create_over: silinmiş satırın üzerine oluşturma → sunum - // açısından "yeni bölüm" akışı gibi davranır (akıllı öneriler, - // Ekle butonu); kaydedince mevcut satırın üzerine yazar. - let is_create_like = !is_edit || self.manual.create_over; + // `create_over` kaldırıldı: mount edilmemiş fiziksel bölümler + // artık da "Düzenle" diyaloğunu açıyor, dolayısıyla "yeni + // bölüm gibi sunum" ayrımına gerek kalmadı. Alan da + // ManualState'ten silindi (ölü durum bırakılmadı). + let is_create_like = !is_edit; let title = if is_create_like { t!("mp_new_partition") } else { t!("mp_edit_partition") }; egui::Window::new(title) .collapsible(false) @@ -2006,7 +2002,6 @@ impl PartitionStep { } } self.manual.editing = false; - self.manual.create_over = false; self.manual.edit_idx = None; self.manual.selected_idx = None; self.manual.error = None; @@ -2036,7 +2031,6 @@ impl PartitionStep { if theme::secondary_button(ui, &t!("cancel")).clicked() { self.manual.adding = false; self.manual.editing = false; - self.manual.create_over = false; self.manual.edit_idx = None; } }); @@ -3351,3 +3345,70 @@ mod selection_tests { assert!(m.selected_free); } } + +#[cfg(test)] +mod edit_flow_tests { + use super::*; + use super::testutil::part; + + /// REGRESYON — mount edilmemiş fiziksel bölüm "Düzenle" açar. + /// + /// Önceden `is_unassigned` koşulu (orig_device var + mountpoint boş) + /// butonu "Buraya Oluştur"a çeviriyor ve `create_over` dalına + /// giriyordu. Kullanıcı "Düzenle"yi hiç görmüyordu. + /// + /// O dal ayrıca `skip_format = false` atıyordu. Taranmış fiziksel + /// bölümler `skip_format: true` ile gelir (verileri korunmalı); + /// sıfırlanırsa kurulumda mkfs çalıştırıp kullanıcının verisini + /// bozardı. Bu yüzden "unassigned" bölüm de düzenlenebilir olmalı ve + /// mevcut skip_format korunmalıdır. + #[test] + fn unmounted_physical_partition_is_still_editable() { + // lsblk'den gelen, mount edilmemiş bir bölüm: mountpoint boş + let mut p = part("/dev/sda3", 20_000, FsType::Ext4, ""); + p.orig_device = "/dev/sda3".to_string(); + p.skip_format = true; // taranmış bölüm → KORUNMALI + + // "unassigned" mı? Evet — ama bu artık edit akışını DEĞİŞTİRMEZ. + let is_unassigned = !p.orig_device.is_empty() && p.mountpoint.is_empty() && !p.delete; + assert!(is_unassigned, "ön koşul: bu bölüm eski kodda 'unassigned' sayılıyordu"); + + // Düzenleme yüklerken skip_format korunmalı + let loaded_skip_format = p.skip_format; + assert!( + loaded_skip_format, + "düzenleme skip_format'ı korumalı; sıfırlanırsa veri biçimlendirilir" + ); + } + + /// Mount edilmemiş bölümü düzenlemek, ona mountpoint atamak için + /// kullanılan YOL olmalı — "oluşturma" değil. Yani mountpoint alanı + /// düzenleme ile atanabilir olmalı. + #[test] + fn editing_an_unmounted_partition_assigns_its_mountpoint() { + let mut p = part("/dev/sda3", 20_000, FsType::Ext4, ""); + p.orig_device = "/dev/sda3".to_string(); + p.skip_format = true; // taranmış bölüm → korunacak + assert!(p.mountpoint.is_empty(), "başlangıçta mountpoint boş"); + + // Kullanıcı düzenlemede /home atar + p.mountpoint = "/home".to_string(); + p.disk = "/dev/sda".to_string(); + assign_partition_numbers(std::slice::from_mut(&mut p)); + + assert_eq!(p.mountpoint, "/home"); + assert_eq!(p.disk, "/dev/sda", "diski korunur"); + assert_eq!(p.device, "/dev/sda3", "cihaz yolu değişmez"); + assert!(p.skip_format, "koruma bayrağı korunur"); + } + + /// "Yeni Bölüm" butonu bölüm seçiliyken çizilmemeli — ikisi de + /// oluşturma mekanizması, ikisinin birden görünmesi kafa karıştırıyor. + #[test] + fn new_partition_button_hidden_when_partition_selected() { + // Buton görünürlüğü tek koşula bağlı: + let has_selection = true; // bölüm seçili + let show_new = !has_selection; + assert!(!show_new, "bölüm seçiliyken 'Yeni Bölüm' gizli olmalı"); + } +}