mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-09 09:59:45 -10:00
fix(snapshot): repair a history whose HEAD names a missing commit, and say when undo is degraded
When the snapshot side repo's HEAD pointed at a commit that no longer
exists (an interrupted gc/prune, a copied or partly deleted snapshots
dir), `rev-parse --verify HEAD` still succeeded, so every snapshot passed
the missing id to `commit-tree -p` and failed ("is not a valid object").
/undo died with nothing but a WARN in the log (sweep B2).
- SnapshotRepo::repair_broken_head: when HEAD names something that is not
a commit, delete the broken ref so the next snapshot starts a fresh
history. snapshot_with_session repairs on its own and resolves its
parent with `HEAD^{commit}`.
- The turn snapshot path reports through the existing once-per-session
snapshot notice (TUI toast, /status, headless stderr): a repaired
history says earlier restore points are gone and new turns are
protected again; a snapshot that fails for a real error (not a gate)
says snapshots are failing, with the error. A later successful snapshot
clears the status.
- Two new localized messages (SnapshotsHistoryRepaired, SnapshotsFailing)
in all 15 packs.
Tests: codewhale-tui focused run (snapshot, core::turn::, runtime_threads::,
command_catalog, hooks::, tools::file, tools::apply_patch,
plugins::marketplace, commands::groups::plugins, task_manager, status::,
undo) 1099 passed / 0 failed / 4 ignored, including new
snapshot::repo::tests::broken_head_is_repaired_and_snapshots_resume,
core::turn::snapshot_notice_tests::broken_snapshot_history_is_repaired_and_the_user_is_told
and unrelated_snapshot_errors_say_snapshots_are_failing (replaces
unrelated_snapshot_errors_are_not_gated_notices). codewhale-localization
50 passed / 0 failed.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01R9rJEoMuUSRWznU6QjkE7h
This commit is contained in:
1 parent
1bce3e0368
commit
ab428ba1a7
18 files changed
+254
-19
No files matched your search
@@ -1580,6 +1580,8 @@
|
||||
"SnapshotsDisabledTooLarge": "Les instantànies i /undo estan desactivats per a {workspace}: conté més de {limit} de contingut per instantaniar. Augmenta `{config_key}` a config.toml (o posa-ho a 0 per treure el límit) per activar-los.",
|
||||
"SnapshotsDisabledTooManyFiles": "Les instantànies i /undo estan desactivats per a {workspace}: conté més de {limit} fitxers per instantaniar. Aquí el límit de mida no hi té res a veure: ignora els directoris generats al `.gitignore`, o comença en un subdirectori més petit, per activar-los.",
|
||||
"SnapshotsDisabledUnsafeLocation": "Les instantànies i /undo estan desactivats per a {workspace}: un directori d'inici o l'arrel és massa ampli per fer-ne una instantània segura. Comença en un directori de projecte per activar-los.",
|
||||
"SnapshotsHistoryRepaired": "L'historial de desfer de {workspace} estava malmès (faltava el darrer punt de restauració) i s'ha reiniciat. Els punts de restauració anteriors ja no es poden restaurar; els torns nous tornen a estar protegits.",
|
||||
"SnapshotsFailing": "Les instantànies fallen per a {workspace}, així que /undo no pot restaurar els torns recents: {limit}",
|
||||
"SessionIdDivergedNotice": "La identitat de la sessió ha canviat sota aquesta finestra: el motor ara desa els punts de control com a {next} (abans era {previous}). Els punts anteriors queden sota l'id antic; `codewhale sessions` els llista tots dos i `codewhale resume {previous}` reobre l'anterior.",
|
||||
"RuntimeStoreUnreadableNotice": "Magatzem d'execució de la sessió: no s'ha pogut llegir {record} a {path} ({reason}). Aparta aquest fitxer (o suprimeix-lo) i torna-ho a provar; la resta de registres del fil es conserven.",
|
||||
"RuntimeStoreUnwritableNotice": "Magatzem d'execució de la sessió: no s'ha pogut escriure {record} a {path} ({reason}). Comprova l'espai lliure i els permisos allà i torna-ho a provar; no s'ha sobreescrit res.",
|
||||
|
||||
@@ -1580,6 +1580,8 @@
|
||||
"SnapshotsDisabledTooLarge": "Snapshots und /undo sind für {workspace} aus: mehr als {limit} an snapshot-fähigem Inhalt. Erhöhe `{config_key}` in config.toml (oder setze es auf 0, um die Grenze aufzuheben), um sie einzuschalten.",
|
||||
"SnapshotsDisabledTooManyFiles": "Snapshots und /undo sind für {workspace} aus: mehr als {limit} snapshot-fähige Dateien. Die Größengrenze spielt hier keine Rolle – ignoriere generierte Verzeichnisse in `.gitignore` oder starte in einem kleineren Unterverzeichnis, um sie einzuschalten.",
|
||||
"SnapshotsDisabledUnsafeLocation": "Snapshots und /undo sind für {workspace} aus: ein Home- oder Wurzelverzeichnis ist zu weit gefasst für einen sicheren Snapshot. Starte in einem Projektverzeichnis, um sie einzuschalten.",
|
||||
"SnapshotsHistoryRepaired": "Der Undo-Verlauf für {workspace} war beschädigt (der letzte Wiederherstellungspunkt fehlte) und wurde neu gestartet. Frühere Wiederherstellungspunkte lassen sich nicht mehr wiederherstellen; neue Züge sind wieder geschützt.",
|
||||
"SnapshotsFailing": "Snapshots für {workspace} schlagen fehl, daher kann /undo die letzten Züge nicht wiederherstellen: {limit}",
|
||||
"SessionIdDivergedNotice": "Die Sitzungsidentität unter diesem Fenster hat sich geändert: Die Engine speichert Checkpoints jetzt als {next} (vorher {previous}). Frühere Checkpoints bleiben unter der alten ID; `codewhale sessions` listet beide und `codewhale resume {previous}` öffnet die frühere wieder.",
|
||||
"RuntimeStoreUnreadableNotice": "Laufzeitspeicher der Sitzung: {record} unter {path} konnte nicht gelesen werden ({reason}). Verschiebe diese Datei beiseite (oder lösche sie) und versuche es erneut; die übrigen Datensätze des Threads bleiben erhalten.",
|
||||
"RuntimeStoreUnwritableNotice": "Laufzeitspeicher der Sitzung: {record} unter {path} konnte nicht geschrieben werden ({reason}). Prüfe dort freien Speicherplatz und Berechtigungen und versuche es erneut; nichts wurde überschrieben.",
|
||||
|
||||
@@ -1603,6 +1603,8 @@
|
||||
"SnapshotsDisabledTooLarge": "Snapshots and /undo are off for {workspace}: it holds more than {limit} of snapshot-eligible content. Raise `{config_key}` in config.toml (or set it to 0 to remove the cap) to turn them on.",
|
||||
"SnapshotsDisabledTooManyFiles": "Snapshots and /undo are off for {workspace}: it holds more than {limit} snapshot-eligible files. The size cap does not apply here — ignore generated directories in `.gitignore`, or start in a smaller subdirectory, to turn them on.",
|
||||
"SnapshotsDisabledUnsafeLocation": "Snapshots and /undo are off for {workspace}: a home or root directory is too broad to snapshot safely. Start in a project directory to turn them on.",
|
||||
"SnapshotsHistoryRepaired": "Undo history for {workspace} was damaged (its latest restore point was missing) and has been restarted. Earlier restore points can no longer be restored; new turns are protected again.",
|
||||
"SnapshotsFailing": "Snapshots are failing for {workspace}, so /undo cannot restore recent turns: {limit}",
|
||||
"SessionIdDivergedNotice": "Session identity changed under this window: the engine now saves checkpoints as {next} (it was {previous}). Earlier checkpoints stay under the old id; `codewhale sessions` lists both and `codewhale resume {previous}` reopens the earlier one.",
|
||||
"RuntimeStoreUnreadableNotice": "Session runtime store: {record} at {path} could not be read ({reason}). Move that file aside (or delete it) and retry; the thread's other records stay in place.",
|
||||
"RuntimeStoreUnwritableNotice": "Session runtime store: {record} at {path} could not be written ({reason}). Check free space and permissions there, then retry; nothing was overwritten.",
|
||||
|
||||
@@ -1603,6 +1603,8 @@
|
||||
"SnapshotsDisabledTooLarge": "Las instantáneas y /undo están desactivados para {workspace}: tiene más de {limit} de contenido apto para instantáneas. Sube `{config_key}` en config.toml (o ponlo en 0 para quitar el límite) para activarlos.",
|
||||
"SnapshotsDisabledTooManyFiles": "Las instantáneas y /undo están desactivados para {workspace}: tiene más de {limit} archivos aptos para instantáneas. El límite de tamaño no aplica aquí: ignora los directorios generados en `.gitignore`, o empieza en un subdirectorio más chico, para activarlos.",
|
||||
"SnapshotsDisabledUnsafeLocation": "Las instantáneas y /undo están desactivados para {workspace}: un directorio raíz o personal es demasiado amplio para una instantánea segura. Empieza en un directorio de proyecto para activarlos.",
|
||||
"SnapshotsHistoryRepaired": "El historial de deshacer de {workspace} estaba dañado (faltaba su último punto de restauración) y se reinició. Los puntos de restauración anteriores ya no se pueden restaurar; los turnos nuevos vuelven a estar protegidos.",
|
||||
"SnapshotsFailing": "Las instantáneas están fallando para {workspace}, así que /undo no puede restaurar los turnos recientes: {limit}",
|
||||
"SessionIdDivergedNotice": "La identidad de la sesión cambió bajo esta ventana: el motor ahora guarda checkpoints como {next} (antes era {previous}). Los checkpoints anteriores quedan bajo el id anterior; `codewhale sessions` lista ambos y `codewhale resume {previous}` reabre el anterior.",
|
||||
"RuntimeStoreUnreadableNotice": "Almacén de ejecución de la sesión: no se pudo leer {record} en {path} ({reason}). Aparta ese archivo (o elimínalo) y vuelve a intentar; los demás registros del hilo se conservan.",
|
||||
"RuntimeStoreUnwritableNotice": "Almacén de ejecución de la sesión: no se pudo escribir {record} en {path} ({reason}). Revisa el espacio libre y los permisos allí y vuelve a intentar; no se sobrescribió nada.",
|
||||
|
||||
@@ -1580,6 +1580,8 @@
|
||||
"SnapshotsDisabledTooLarge": "Les instantanés et /undo sont désactivés pour {workspace} : il contient plus de {limit} de contenu à instantanéier. Augmentez `{config_key}` dans config.toml (ou mettez 0 pour lever la limite) pour les activer.",
|
||||
"SnapshotsDisabledTooManyFiles": "Les instantanés et /undo sont désactivés pour {workspace} : il contient plus de {limit} fichiers à instantanéier. La limite de taille n'entre pas en jeu ici — ignorez les répertoires générés dans `.gitignore`, ou démarrez dans un sous-répertoire plus petit, pour les activer.",
|
||||
"SnapshotsDisabledUnsafeLocation": "Les instantanés et /undo sont désactivés pour {workspace} : un répertoire personnel ou racine est trop vaste pour un instantané sûr. Démarrez dans un répertoire de projet pour les activer.",
|
||||
"SnapshotsHistoryRepaired": "L'historique d'annulation de {workspace} était endommagé (son dernier point de restauration manquait) et a été redémarré. Les points de restauration antérieurs ne peuvent plus être restaurés ; les nouveaux tours sont de nouveau protégés.",
|
||||
"SnapshotsFailing": "Les instantanés échouent pour {workspace}, donc /undo ne peut pas restaurer les tours récents : {limit}",
|
||||
"SessionIdDivergedNotice": "L'identité de session a changé sous cette fenêtre : le moteur enregistre désormais les points de reprise sous {next} (c'était {previous}). Les points antérieurs restent sous l'ancien identifiant ; `codewhale sessions` liste les deux et `codewhale resume {previous}` rouvre l'ancien.",
|
||||
"RuntimeStoreUnreadableNotice": "Magasin d'exécution de la session : impossible de lire {record} dans {path} ({reason}). Mettez ce fichier de côté (ou supprimez-le) et réessayez ; les autres enregistrements du fil restent en place.",
|
||||
"RuntimeStoreUnwritableNotice": "Magasin d'exécution de la session : impossible d'écrire {record} dans {path} ({reason}). Vérifiez l'espace libre et les permissions à cet endroit, puis réessayez ; rien n'a été écrasé.",
|
||||
|
||||
@@ -1580,6 +1580,8 @@
|
||||
"SnapshotsDisabledTooLarge": "{workspace} के लिए स्नैपशॉट और /undo बंद हैं: इसमें स्नैपशॉट-योग्य सामग्री {limit} से अधिक है। इन्हें चालू करने के लिए config.toml में `{config_key}` बढ़ाएँ (या सीमा हटाने के लिए 0 रखें)।",
|
||||
"SnapshotsDisabledTooManyFiles": "{workspace} के लिए स्नैपशॉट और /undo बंद हैं: इसमें स्नैपशॉट-योग्य फ़ाइलें {limit} से अधिक हैं। यहाँ आकार-सीमा लागू नहीं होती — जनरेट की गई निर्देशिकाओं को `.gitignore` में डालें, या किसी छोटी उप-निर्देशिका में शुरू करें, तब ये चालू होंगे।",
|
||||
"SnapshotsDisabledUnsafeLocation": "{workspace} के लिए स्नैपशॉट और /undo बंद हैं: होम या रूट निर्देशिका सुरक्षित स्नैपशॉट के लिए बहुत बड़ी है। इन्हें चालू करने के लिए किसी प्रोजेक्ट निर्देशिका में शुरू करें।",
|
||||
"SnapshotsHistoryRepaired": "{workspace} का undo इतिहास क्षतिग्रस्त था (उसका नवीनतम पुनर्स्थापना बिंदु गायब था) और इसे फिर से शुरू किया गया है। पहले के पुनर्स्थापना बिंदु अब पुनर्स्थापित नहीं किए जा सकते; नए टर्न फिर से सुरक्षित हैं।",
|
||||
"SnapshotsFailing": "{workspace} के लिए स्नैपशॉट विफल हो रहे हैं, इसलिए /undo हाल के टर्न पुनर्स्थापित नहीं कर सकता: {limit}",
|
||||
"SessionIdDivergedNotice": "इस विंडो की सत्र पहचान बदल गई है: इंजन अब चेकपॉइंट {next} के रूप में सहेजता है (पहले {previous} था)। पुराने चेकपॉइंट पुराने id के तहत ही रहेंगे; `codewhale sessions` दोनों दिखाता है और `codewhale resume {previous}` पुराना सत्र फिर खोलता है।",
|
||||
"RuntimeStoreUnreadableNotice": "सत्र रनटाइम स्टोर: {path} पर {record} पढ़ा नहीं जा सका ({reason})। उस फ़ाइल को अलग रख दें (या हटा दें) और फिर से कोशिश करें; थ्रेड के बाकी रिकॉर्ड यथावत रहते हैं।",
|
||||
"RuntimeStoreUnwritableNotice": "सत्र रनटाइम स्टोर: {path} पर {record} लिखा नहीं जा सका ({reason})। वहाँ खाली जगह और अनुमतियाँ जाँचें, फिर दोबारा कोशिश करें; कुछ भी अधिलेखित नहीं हुआ।",
|
||||
|
||||
@@ -1580,6 +1580,8 @@
|
||||
"SnapshotsDisabledTooLarge": "Snapshot dan /undo nonaktif untuk {workspace}: isinya lebih dari {limit} konten yang bisa di-snapshot. Naikkan `{config_key}` di config.toml (atau setel 0 untuk menghapus batas) untuk mengaktifkannya.",
|
||||
"SnapshotsDisabledTooManyFiles": "Snapshot dan /undo nonaktif untuk {workspace}: isinya lebih dari {limit} berkas yang bisa di-snapshot. Batas ukuran tidak berlaku di sini — abaikan direktori hasil build di `.gitignore`, atau mulai di subdirektori yang lebih kecil, untuk mengaktifkannya.",
|
||||
"SnapshotsDisabledUnsafeLocation": "Snapshot dan /undo nonaktif untuk {workspace}: direktori home atau root terlalu luas untuk di-snapshot dengan aman. Mulailah di direktori proyek untuk mengaktifkannya.",
|
||||
"SnapshotsHistoryRepaired": "Riwayat undo untuk {workspace} rusak (titik pemulihan terakhirnya hilang) dan telah dimulai ulang. Titik pemulihan sebelumnya tidak dapat dipulihkan lagi; giliran baru kembali terlindungi.",
|
||||
"SnapshotsFailing": "Snapshot gagal untuk {workspace}, jadi /undo tidak dapat memulihkan giliran terbaru: {limit}",
|
||||
"SessionIdDivergedNotice": "Identitas sesi di jendela ini berubah: engine kini menyimpan checkpoint sebagai {next} (sebelumnya {previous}). Checkpoint lama tetap di id lama; `codewhale sessions` menampilkan keduanya dan `codewhale resume {previous}` membuka kembali yang lama.",
|
||||
"RuntimeStoreUnreadableNotice": "Penyimpanan runtime sesi: {record} di {path} tidak dapat dibaca ({reason}). Pindahkan berkas itu (atau hapus) lalu coba lagi; rekaman lain di thread ini tetap utuh.",
|
||||
"RuntimeStoreUnwritableNotice": "Penyimpanan runtime sesi: {record} di {path} tidak dapat ditulis ({reason}). Periksa ruang kosong dan izin di sana, lalu coba lagi; tidak ada yang ditimpa.",
|
||||
|
||||
@@ -1603,6 +1603,8 @@
|
||||
"SnapshotsDisabledTooLarge": "{workspace} ではスナップショットと /undo が無効です: スナップショット対象の内容が {limit} を超えています。config.toml の `{config_key}` を上げる(または 0 にして上限を外す)と有効になります。",
|
||||
"SnapshotsDisabledTooManyFiles": "{workspace} ではスナップショットと /undo が無効です: スナップショット対象のファイルが {limit} 件を超えています。ここではサイズ上限は関係ありません。生成物のディレクトリを `.gitignore` で除外するか、より小さいサブディレクトリで開始すると有効になります。",
|
||||
"SnapshotsDisabledUnsafeLocation": "{workspace} ではスナップショットと /undo が無効です: ホームディレクトリやルートディレクトリは範囲が広すぎて安全にスナップショットできません。プロジェクトのディレクトリで開始すると有効になります。",
|
||||
"SnapshotsHistoryRepaired": "{workspace} の undo 履歴が破損していたため(最新の復元ポイントが見つかりませんでした)、履歴を新しく開始しました。以前の復元ポイントは復元できなくなりましたが、新しいターンは再び保護されます。",
|
||||
"SnapshotsFailing": "{workspace} のスナップショットが失敗しているため、/undo で最近のターンを復元できません: {limit}",
|
||||
"SessionIdDivergedNotice": "このウィンドウのセッション ID が変わりました。エンジンは今後 {next} としてチェックポイントを保存します(以前は {previous})。以前のチェックポイントは旧 ID のまま残ります。`codewhale sessions` で両方を確認でき、`codewhale resume {previous}` で以前のセッションを再開できます。",
|
||||
"RuntimeStoreUnreadableNotice": "セッションのランタイムストア: {path} の {record} を読み取れませんでした({reason})。そのファイルを退避(または削除)して再試行してください。スレッドの他のレコードはそのまま残ります。",
|
||||
"RuntimeStoreUnwritableNotice": "セッションのランタイムストア: {path} の {record} を書き込めませんでした({reason})。その場所の空き容量と権限を確認して再試行してください。何も上書きされていません。",
|
||||
|
||||
@@ -1603,6 +1603,8 @@
|
||||
"SnapshotsDisabledTooLarge": "{workspace}에서 스냅샷과 /undo가 꺼져 있습니다: 스냅샷 대상 콘텐츠가 {limit}을 넘습니다. config.toml의 `{config_key}` 값을 높이면(또는 0으로 설정해 상한을 없애면) 켜집니다.",
|
||||
"SnapshotsDisabledTooManyFiles": "{workspace}에서 스냅샷과 /undo가 꺼져 있습니다: 스냅샷 대상 파일이 {limit}개를 넘습니다. 여기서는 크기 상한이 적용되지 않습니다 — 생성된 디렉터리를 `.gitignore`에 넣거나 더 작은 하위 디렉터리에서 시작하면 켜집니다.",
|
||||
"SnapshotsDisabledUnsafeLocation": "{workspace}에서 스냅샷과 /undo가 꺼져 있습니다: 홈 디렉터리나 루트 디렉터리는 범위가 너무 넓어 안전하게 스냅샷할 수 없습니다. 프로젝트 디렉터리에서 시작하면 켜집니다.",
|
||||
"SnapshotsHistoryRepaired": "{workspace}의 실행 취소 기록이 손상되어(최신 복원 지점이 없음) 새로 시작했습니다. 이전 복원 지점은 더 이상 복원할 수 없지만, 새 턴은 다시 보호됩니다.",
|
||||
"SnapshotsFailing": "{workspace}의 스냅샷이 실패하고 있어 /undo로 최근 턴을 복원할 수 없습니다: {limit}",
|
||||
"SessionIdDivergedNotice": "이 창의 세션 ID가 바뀌었습니다. 엔진은 이제 {next}로 체크포인트를 저장합니다(이전: {previous}). 이전 체크포인트는 옛 ID 아래에 남습니다. `codewhale sessions`로 둘 다 확인하고 `codewhale resume {previous}`로 이전 세션을 다시 열 수 있습니다.",
|
||||
"RuntimeStoreUnreadableNotice": "세션 런타임 저장소: {path}의 {record}을(를) 읽을 수 없습니다({reason}). 해당 파일을 옮기거나 삭제한 뒤 다시 시도하세요. 스레드의 다른 레코드는 그대로 유지됩니다.",
|
||||
"RuntimeStoreUnwritableNotice": "세션 런타임 저장소: {path}의 {record}을(를) 쓸 수 없습니다({reason}). 해당 위치의 여유 공간과 권한을 확인한 뒤 다시 시도하세요. 아무것도 덮어쓰지 않았습니다.",
|
||||
|
||||
@@ -1603,6 +1603,8 @@
|
||||
"SnapshotsDisabledTooLarge": "Snapshots e /undo estão desligados para {workspace}: ele tem mais de {limit} de conteúdo elegível para snapshot. Aumente `{config_key}` no config.toml (ou defina 0 para remover o limite) para ligá-los.",
|
||||
"SnapshotsDisabledTooManyFiles": "Snapshots e /undo estão desligados para {workspace}: ele tem mais de {limit} arquivos elegíveis para snapshot. O limite de tamanho não se aplica aqui — ignore diretórios gerados no `.gitignore`, ou comece em um subdiretório menor, para ligá-los.",
|
||||
"SnapshotsDisabledUnsafeLocation": "Snapshots e /undo estão desligados para {workspace}: um diretório raiz ou pessoal é amplo demais para um snapshot seguro. Comece em um diretório de projeto para ligá-los.",
|
||||
"SnapshotsHistoryRepaired": "O histórico de desfazer de {workspace} estava danificado (faltava o ponto de restauração mais recente) e foi reiniciado. Os pontos de restauração anteriores não podem mais ser restaurados; os novos turnos voltam a ficar protegidos.",
|
||||
"SnapshotsFailing": "Os snapshots estão falhando em {workspace}, então /undo não consegue restaurar os turnos recentes: {limit}",
|
||||
"SessionIdDivergedNotice": "A identidade da sessão mudou nesta janela: o motor agora salva checkpoints como {next} (antes era {previous}). Os checkpoints anteriores ficam sob o id antigo; `codewhale sessions` lista os dois e `codewhale resume {previous}` reabre o anterior.",
|
||||
"RuntimeStoreUnreadableNotice": "Armazenamento de execução da sessão: não foi possível ler {record} em {path} ({reason}). Afaste esse arquivo (ou exclua-o) e tente de novo; os demais registros da thread permanecem.",
|
||||
"RuntimeStoreUnwritableNotice": "Armazenamento de execução da sessão: não foi possível gravar {record} em {path} ({reason}). Verifique o espaço livre e as permissões nesse local e tente de novo; nada foi sobrescrito.",
|
||||
|
||||
@@ -1580,6 +1580,8 @@
|
||||
"SnapshotsDisabledTooLarge": "Снимки и /undo отключены для {workspace}: в нём больше {limit} содержимого, попадающего в снимок. Увеличьте `{config_key}` в config.toml (или задайте 0, чтобы снять ограничение), чтобы включить их.",
|
||||
"SnapshotsDisabledTooManyFiles": "Снимки и /undo отключены для {workspace}: в нём больше {limit} файлов, попадающих в снимок. Ограничение по размеру здесь ни при чём — исключите сгенерированные каталоги в `.gitignore` или начните в подкаталоге поменьше, чтобы включить их.",
|
||||
"SnapshotsDisabledUnsafeLocation": "Снимки и /undo отключены для {workspace}: домашний или корневой каталог слишком широк для безопасного снимка. Начните в каталоге проекта, чтобы включить их.",
|
||||
"SnapshotsHistoryRepaired": "История отмены для {workspace} была повреждена (не хватало последней точки восстановления) и начата заново. Более ранние точки восстановления больше нельзя восстановить; новые ходы снова защищены.",
|
||||
"SnapshotsFailing": "Снимки для {workspace} не создаются, поэтому /undo не может восстановить последние ходы: {limit}",
|
||||
"SessionIdDivergedNotice": "Идентификатор сеанса в этом окне изменился: движок теперь сохраняет контрольные точки как {next} (было {previous}). Прежние точки остаются под старым id; `codewhale sessions` показывает оба, а `codewhale resume {previous}` открывает прежний сеанс.",
|
||||
"RuntimeStoreUnreadableNotice": "Хранилище среды выполнения сессии: не удалось прочитать {record} в {path} ({reason}). Отложите этот файл (или удалите его) и повторите попытку; остальные записи потока сохраняются.",
|
||||
"RuntimeStoreUnwritableNotice": "Хранилище среды выполнения сессии: не удалось записать {record} в {path} ({reason}). Проверьте свободное место и права доступа там и повторите попытку; ничего не было перезаписано.",
|
||||
|
||||
@@ -1580,6 +1580,8 @@
|
||||
"SnapshotsDisabledTooLarge": "Знімки та /undo вимкнено для {workspace}: у ньому понад {limit} вмісту, що потрапляє у знімок. Збільште `{config_key}` у config.toml (або встановіть 0, щоб зняти обмеження), щоб увімкнути їх.",
|
||||
"SnapshotsDisabledTooManyFiles": "Знімки та /undo вимкнено для {workspace}: у ньому понад {limit} файлів, що потрапляють у знімок. Обмеження за розміром тут ні до чого — виключіть згенеровані каталоги в `.gitignore` або почніть у меншому підкаталозі, щоб увімкнути їх.",
|
||||
"SnapshotsDisabledUnsafeLocation": "Знімки та /undo вимкнено для {workspace}: домашній або кореневий каталог надто широкий для безпечного знімка. Почніть у каталозі проєкту, щоб увімкнути їх.",
|
||||
"SnapshotsHistoryRepaired": "Історію скасування для {workspace} було пошкоджено (бракувало останньої точки відновлення), тож її розпочато заново. Попередні точки відновлення більше не можна відновити; нові ходи знову захищені.",
|
||||
"SnapshotsFailing": "Знімки для {workspace} не створюються, тому /undo не може відновити останні ходи: {limit}",
|
||||
"SessionIdDivergedNotice": "Ідентифікатор сеансу в цьому вікні змінився: рушій тепер зберігає контрольні точки як {next} (було {previous}). Попередні точки лишаються під старим id; `codewhale sessions` показує обидва, а `codewhale resume {previous}` відкриває попередній сеанс.",
|
||||
"RuntimeStoreUnreadableNotice": "Сховище середовища виконання сесії: не вдалося прочитати {record} у {path} ({reason}). Відкладіть цей файл (або видаліть його) і повторіть спробу; інші записи потоку залишаються.",
|
||||
"RuntimeStoreUnwritableNotice": "Сховище середовища виконання сесії: не вдалося записати {record} у {path} ({reason}). Перевірте вільне місце та права доступу там і повторіть спробу; нічого не було перезаписано.",
|
||||
|
||||
@@ -1603,6 +1603,8 @@
|
||||
"SnapshotsDisabledTooLarge": "Ảnh chụp nhanh và /undo đang tắt cho {workspace}: nội dung được chụp vượt quá {limit}. Tăng `{config_key}` trong config.toml (hoặc đặt 0 để bỏ giới hạn) để bật lại.",
|
||||
"SnapshotsDisabledTooManyFiles": "Ảnh chụp nhanh và /undo đang tắt cho {workspace}: có hơn {limit} tệp được chụp. Giới hạn dung lượng không áp dụng ở đây — bỏ qua các thư mục sinh ra trong `.gitignore`, hoặc bắt đầu ở thư mục con nhỏ hơn, để bật lại.",
|
||||
"SnapshotsDisabledUnsafeLocation": "Ảnh chụp nhanh và /undo đang tắt cho {workspace}: thư mục nhà hoặc thư mục gốc quá rộng để chụp an toàn. Hãy bắt đầu trong một thư mục dự án để bật lại.",
|
||||
"SnapshotsHistoryRepaired": "Lịch sử hoàn tác của {workspace} bị hỏng (thiếu điểm khôi phục mới nhất) và đã được bắt đầu lại. Các điểm khôi phục trước đó không thể khôi phục nữa; các lượt mới lại được bảo vệ.",
|
||||
"SnapshotsFailing": "Snapshot đang lỗi cho {workspace}, nên /undo không thể khôi phục các lượt gần đây: {limit}",
|
||||
"SessionIdDivergedNotice": "Danh tính phiên trong cửa sổ này đã thay đổi: engine hiện lưu checkpoint dưới {next} (trước đó là {previous}). Các checkpoint cũ vẫn nằm dưới id cũ; `codewhale sessions` liệt kê cả hai và `codewhale resume {previous}` mở lại phiên cũ.",
|
||||
"RuntimeStoreUnreadableNotice": "Kho lưu trữ runtime của phiên: không đọc được {record} tại {path} ({reason}). Hãy dời tệp đó sang chỗ khác (hoặc xóa) rồi thử lại; các bản ghi khác của luồng vẫn được giữ nguyên.",
|
||||
"RuntimeStoreUnwritableNotice": "Kho lưu trữ runtime của phiên: không ghi được {record} tại {path} ({reason}). Hãy kiểm tra dung lượng trống và quyền truy cập ở đó rồi thử lại; không có gì bị ghi đè.",
|
||||
|
||||
@@ -1603,6 +1603,8 @@
|
||||
"SnapshotsDisabledTooLarge": "此工作区 {workspace} 的快照与 /undo 已关闭:可快照的内容超过 {limit}。在 config.toml 中提高 `{config_key}`(或设为 0 取消上限)即可开启。",
|
||||
"SnapshotsDisabledTooManyFiles": "此工作区 {workspace} 的快照与 /undo 已关闭:可快照的文件超过 {limit} 个。此处与大小上限无关——在 `.gitignore` 中排除生成目录,或改在更小的子目录中启动,即可开启。",
|
||||
"SnapshotsDisabledUnsafeLocation": "此工作区 {workspace} 的快照与 /undo 已关闭:主目录或根目录范围过大,无法安全快照。请在项目目录中启动即可开启。",
|
||||
"SnapshotsHistoryRepaired": "{workspace} 的撤销历史已损坏(缺少最新的恢复点),已重新开始记录。之前的恢复点无法再恢复;新的轮次重新受到保护。",
|
||||
"SnapshotsFailing": "{workspace} 的快照持续失败,因此 /undo 无法恢复最近的轮次:{limit}",
|
||||
"SessionIdDivergedNotice": "此窗口下的会话身份已更改:引擎现在以 {next} 保存检查点(之前为 {previous})。较早的检查点仍保留在旧 ID 下;`codewhale sessions` 会列出两者,`codewhale resume {previous}` 可重新打开较早的会话。",
|
||||
"RuntimeStoreUnreadableNotice": "会话运行时存储:无法读取 {path} 处的 {record}({reason})。请将该文件移开(或删除)后重试;线程的其他记录保持不变。",
|
||||
"RuntimeStoreUnwritableNotice": "会话运行时存储:无法写入 {path} 处的 {record}({reason})。请检查该位置的可用空间和权限后重试;没有任何内容被覆盖。",
|
||||
|
||||
@@ -1414,6 +1414,8 @@
|
||||
"SnapshotsDisabledTooLarge": "此工作區 {workspace} 的快照與 /undo 已關閉:可快照的內容超過 {limit}。在 config.toml 中提高 `{config_key}`(或設為 0 取消上限)即可開啟。",
|
||||
"SnapshotsDisabledTooManyFiles": "此工作區 {workspace} 的快照與 /undo 已關閉:可快照的檔案超過 {limit} 個。此處與大小上限無關——在 `.gitignore` 中排除產生的目錄,或改在更小的子目錄中啟動,即可開啟。",
|
||||
"SnapshotsDisabledUnsafeLocation": "此工作區 {workspace} 的快照與 /undo 已關閉:主目錄或根目錄範圍過大,無法安全快照。請在專案目錄中啟動即可開啟。",
|
||||
"SnapshotsHistoryRepaired": "{workspace} 的復原歷史已損壞(缺少最新的還原點),已重新開始記錄。先前的還原點無法再還原;新的輪次重新受到保護。",
|
||||
"SnapshotsFailing": "{workspace} 的快照持續失敗,因此 /undo 無法還原最近的輪次:{limit}",
|
||||
"SessionIdDivergedNotice": "此視窗下的工作階段身分已更改:引擎現在以 {next} 儲存檢查點(之前為 {previous})。較早的檢查點仍保留在舊 ID 下;`codewhale sessions` 會列出兩者,`codewhale resume {previous}` 可重新開啟較早的工作階段。",
|
||||
"RuntimeStoreUnreadableNotice": "工作階段執行期儲存區:無法讀取 {path} 的 {record}({reason})。請將該檔案移開(或刪除)後重試;執行緒的其他記錄保持不變。",
|
||||
"RuntimeStoreUnwritableNotice": "工作階段執行期儲存區:無法寫入 {path} 的 {record}({reason})。請檢查該位置的可用空間與權限後重試;沒有任何內容被覆寫。",
|
||||
|
||||
@@ -1988,6 +1988,8 @@ pub enum MessageId {
|
||||
SnapshotsDisabledTooLarge,
|
||||
SnapshotsDisabledTooManyFiles,
|
||||
SnapshotsDisabledUnsafeLocation,
|
||||
SnapshotsHistoryRepaired,
|
||||
SnapshotsFailing,
|
||||
SessionIdDivergedNotice,
|
||||
RuntimeStoreUnreadableNotice,
|
||||
RuntimeStoreUnwritableNotice,
|
||||
@@ -4374,6 +4376,8 @@ pub const ALL_MESSAGE_IDS: &[MessageId] = &[
|
||||
MessageId::SnapshotsDisabledTooLarge,
|
||||
MessageId::SnapshotsDisabledTooManyFiles,
|
||||
MessageId::SnapshotsDisabledUnsafeLocation,
|
||||
MessageId::SnapshotsHistoryRepaired,
|
||||
MessageId::SnapshotsFailing,
|
||||
MessageId::SessionIdDivergedNotice,
|
||||
MessageId::RuntimeStoreUnreadableNotice,
|
||||
MessageId::RuntimeStoreUnwritableNotice,
|
||||
|
||||
+128
-18
@@ -659,14 +659,35 @@ fn snapshot_with_label(
|
||||
) -> Option<String> {
|
||||
match SnapshotRepo::open_or_init_with_cap(workspace, cap_bytes) {
|
||||
Ok(repo) => {
|
||||
clear_snapshots_disabled_status(workspace, session_id);
|
||||
let id = match repo.snapshot_with_session(label, session_id) {
|
||||
Ok(id) => Some(id.into_string()),
|
||||
// Undo that silently stops working is the failure this guards
|
||||
// (B2): a repaired history and a failing snapshot both reach the
|
||||
// user through the same notice as the gates, never only a log.
|
||||
let taken = repo.repair_broken_head().and_then(|repaired| {
|
||||
repo.snapshot_with_session(label, session_id)
|
||||
.map(|id| (id, repaired))
|
||||
});
|
||||
let (id, repaired) = match taken {
|
||||
Ok((id, repaired)) => (Some(id.into_string()), repaired),
|
||||
Err(e) => {
|
||||
tracing::warn!(target: "snapshot", "snapshot '{label}' failed: {e}");
|
||||
record_snapshot_notice(
|
||||
workspace,
|
||||
session_id,
|
||||
SnapshotsDisabledScope::Failing,
|
||||
snapshot_failure_detail(&e),
|
||||
);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
clear_snapshots_disabled_status(workspace, session_id);
|
||||
if repaired {
|
||||
record_snapshot_notice(
|
||||
workspace,
|
||||
session_id,
|
||||
SnapshotsDisabledScope::HistoryRepaired,
|
||||
String::new(),
|
||||
);
|
||||
}
|
||||
// Prune oldest snapshots to cap disk usage (#1112).
|
||||
if let Err(e) = repo.prune_keep_last_n(crate::snapshot::DEFAULT_MAX_SNAPSHOTS) {
|
||||
tracing::warn!(target: "snapshot", "snapshot prune failed: {e}");
|
||||
@@ -699,6 +720,12 @@ pub enum SnapshotsDisabledScope {
|
||||
/// Home, filesystem root, or a top-level home folder: refused for safety,
|
||||
/// and no config value changes that.
|
||||
UnsafeLocation,
|
||||
/// The side repo's HEAD named a missing commit; history was restarted, so
|
||||
/// earlier restore points are gone although new turns are protected.
|
||||
HistoryRepaired,
|
||||
/// Snapshots open but fail (a real git or disk error, not a gate): undo
|
||||
/// cannot restore the turns taken since. `limit` carries the error.
|
||||
Failing,
|
||||
}
|
||||
|
||||
/// Snapshot availability observed for a session and its workspace. Delivering
|
||||
@@ -712,8 +739,9 @@ pub enum SnapshotsDisabledScope {
|
||||
pub struct SnapshotsDisabledNotice {
|
||||
pub workspace: String,
|
||||
pub scope: SnapshotsDisabledScope,
|
||||
/// Preformatted limit for the scope that names one (`2.0 GB`, `200000`).
|
||||
/// Empty for scopes whose message names no limit.
|
||||
/// Preformatted limit for the scope that names one (`2.0 GB`, `200000`),
|
||||
/// or the failure detail for [`SnapshotsDisabledScope::Failing`]. Empty
|
||||
/// for scopes whose message names neither.
|
||||
pub limit: String,
|
||||
}
|
||||
|
||||
@@ -724,6 +752,8 @@ impl SnapshotsDisabledNotice {
|
||||
SnapshotsDisabledScope::WorkspaceTooLarge => MessageId::SnapshotsDisabledTooLarge,
|
||||
SnapshotsDisabledScope::TooManyFiles => MessageId::SnapshotsDisabledTooManyFiles,
|
||||
SnapshotsDisabledScope::UnsafeLocation => MessageId::SnapshotsDisabledUnsafeLocation,
|
||||
SnapshotsDisabledScope::HistoryRepaired => MessageId::SnapshotsHistoryRepaired,
|
||||
SnapshotsDisabledScope::Failing => MessageId::SnapshotsFailing,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -840,20 +870,50 @@ fn maybe_notify_snapshots_disabled_once(
|
||||
} else if message.contains(crate::snapshot::GATE_UNSAFE_LOCATION_MARKER) {
|
||||
SnapshotsDisabledScope::UnsafeLocation
|
||||
} else {
|
||||
// A real snapshot/data-loss error, not a gate: leave it to the caller's
|
||||
// WARN so it is never softened into a "snapshots are off" notice.
|
||||
return true;
|
||||
// A real snapshot/data-loss error, not a gate: say snapshots are
|
||||
// failing and why, never a "snapshots are off" gate notice.
|
||||
return record_snapshot_notice(
|
||||
workspace,
|
||||
session_id,
|
||||
SnapshotsDisabledScope::Failing,
|
||||
snapshot_failure_detail(error),
|
||||
);
|
||||
};
|
||||
let limit = match scope {
|
||||
SnapshotsDisabledScope::WorkspaceTooLarge => format_cap_bytes(cap_bytes),
|
||||
SnapshotsDisabledScope::TooManyFiles => crate::snapshot::SIZE_WALK_MAX_ENTRIES.to_string(),
|
||||
_ => String::new(),
|
||||
};
|
||||
record_snapshot_notice(workspace, session_id, scope, limit)
|
||||
}
|
||||
|
||||
/// One line of a snapshot error for the notice: the first line, bounded.
|
||||
fn snapshot_failure_detail(error: &std::io::Error) -> String {
|
||||
const MAX_CHARS: usize = 200;
|
||||
let text = error.to_string();
|
||||
let line = text.lines().next().unwrap_or_default().trim();
|
||||
if line.chars().count() > MAX_CHARS {
|
||||
let cut: String = line.chars().take(MAX_CHARS).collect();
|
||||
format!("{cut}…")
|
||||
} else {
|
||||
line.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Record the session's snapshot availability and, the first time in this
|
||||
/// session, queue its notice for the TUI and print it for headless runs.
|
||||
/// Returns whether this call delivered the notice.
|
||||
#[allow(clippy::print_stderr)]
|
||||
fn record_snapshot_notice(
|
||||
workspace: &Path,
|
||||
session_id: Option<&str>,
|
||||
scope: SnapshotsDisabledScope,
|
||||
limit: String,
|
||||
) -> bool {
|
||||
let notice = SnapshotsDisabledNotice {
|
||||
workspace: workspace.to_string_lossy().into_owned(),
|
||||
scope,
|
||||
limit: match scope {
|
||||
SnapshotsDisabledScope::WorkspaceTooLarge => format_cap_bytes(cap_bytes),
|
||||
SnapshotsDisabledScope::TooManyFiles => {
|
||||
crate::snapshot::SIZE_WALK_MAX_ENTRIES.to_string()
|
||||
}
|
||||
SnapshotsDisabledScope::UnsafeLocation => String::new(),
|
||||
},
|
||||
limit,
|
||||
};
|
||||
let mut states = snapshot_notices()
|
||||
.lock()
|
||||
@@ -992,8 +1052,10 @@ mod snapshot_notice_tests {
|
||||
assert!(take_snapshots_disabled_notices(&workspace, Some("session")).is_empty());
|
||||
}
|
||||
|
||||
/// A real error is not a gate: the user is told snapshots are failing
|
||||
/// and why, never that they are "off" behind a limit.
|
||||
#[test]
|
||||
fn unrelated_snapshot_errors_are_not_gated_notices() {
|
||||
fn unrelated_snapshot_errors_say_snapshots_are_failing() {
|
||||
let workspace = tempfile::tempdir().unwrap();
|
||||
let error = std::io::Error::other("disk full");
|
||||
assert!(maybe_notify_snapshots_disabled_once(
|
||||
@@ -1002,8 +1064,56 @@ mod snapshot_notice_tests {
|
||||
1024,
|
||||
&error
|
||||
));
|
||||
assert!(take_snapshots_disabled_notices(workspace.path(), Some("session")).is_empty());
|
||||
assert!(snapshots_disabled_status(workspace.path(), Some("session")).is_none());
|
||||
let notices = take_snapshots_disabled_notices(workspace.path(), Some("session"));
|
||||
assert_eq!(notices.len(), 1);
|
||||
assert_eq!(notices[0].scope, SnapshotsDisabledScope::Failing);
|
||||
let line = notices[0].localize(codewhale_localization::Locale::En);
|
||||
assert!(
|
||||
line.contains("failing") && line.contains("disk full"),
|
||||
"{line}"
|
||||
);
|
||||
assert!(!line.contains(SNAPSHOTS_CAP_CONFIG_KEY), "{line}");
|
||||
assert!(snapshots_disabled_status(workspace.path(), Some("session")).is_some());
|
||||
assert!(
|
||||
!maybe_notify_snapshots_disabled_once(workspace.path(), Some("session"), 1024, &error),
|
||||
"told once per session"
|
||||
);
|
||||
}
|
||||
|
||||
/// B2: a side repo whose HEAD names a missing commit is repaired on the
|
||||
/// next turn snapshot, and the user is told earlier restore points are
|
||||
/// gone instead of /undo silently dying.
|
||||
#[test]
|
||||
fn broken_snapshot_history_is_repaired_and_the_user_is_told() {
|
||||
let _env = crate::test_support::lock_test_env();
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let _home = crate::test_support::EnvVarGuard::set("CODEWHALE_HOME", root.path());
|
||||
let _user_home = crate::test_support::EnvVarGuard::set("HOME", root.path());
|
||||
let _user_profile = crate::test_support::EnvVarGuard::set("USERPROFILE", root.path());
|
||||
let workspace = root.path().join("workspace");
|
||||
std::fs::create_dir(&workspace).unwrap();
|
||||
std::fs::write(workspace.join("a.txt"), b"alpha").unwrap();
|
||||
assert!(pre_turn_snapshot(&workspace, 1, 0, None, Some("session")).is_some());
|
||||
SnapshotRepo::open_or_init(&workspace)
|
||||
.unwrap()
|
||||
.point_head_at_missing_commit_for_test();
|
||||
|
||||
std::fs::write(workspace.join("a.txt"), b"beta").unwrap();
|
||||
assert!(
|
||||
post_turn_snapshot(&workspace, 1, 0, None, Some("session")).is_some(),
|
||||
"the snapshot succeeds after the repair"
|
||||
);
|
||||
let notices = take_snapshots_disabled_notices(&workspace, Some("session"));
|
||||
assert_eq!(notices.len(), 1, "{notices:?}");
|
||||
assert_eq!(notices[0].scope, SnapshotsDisabledScope::HistoryRepaired);
|
||||
let line = notices[0].localize(codewhale_localization::Locale::En);
|
||||
assert!(line.contains("restarted"), "{line}");
|
||||
assert!(line.contains(&workspace.display().to_string()), "{line}");
|
||||
assert!(pre_turn_snapshot(&workspace, 2, 0, None, Some("session")).is_some());
|
||||
assert!(
|
||||
take_snapshots_disabled_notices(&workspace, Some("session")).is_empty(),
|
||||
"a healthy history says nothing more"
|
||||
);
|
||||
}
|
||||
|
||||
/// Every gate must state a recovery that actually lifts *that* gate. The
|
||||
|
||||
@@ -487,10 +487,13 @@ impl SnapshotRepo {
|
||||
}
|
||||
let tree = String::from_utf8_lossy(&tree.stdout).trim().to_string();
|
||||
|
||||
// A HEAD that names a missing commit would make every `commit-tree
|
||||
// -p` below fail ("is not a valid object"), silently ending undo.
|
||||
self.repair_broken_head()?;
|
||||
let parent = run_git(
|
||||
&self.git_dir,
|
||||
&self.work_tree,
|
||||
&["rev-parse", "--verify", "HEAD"],
|
||||
&["rev-parse", "--verify", "--quiet", "HEAD^{commit}"],
|
||||
)?;
|
||||
let parent = parent
|
||||
.status
|
||||
@@ -537,6 +540,62 @@ impl SnapshotRepo {
|
||||
})
|
||||
}
|
||||
|
||||
/// Repair a side repo whose HEAD names a commit that no longer exists
|
||||
/// (an interrupted gc or prune, a copied or partially deleted
|
||||
/// `~/.codewhale/snapshots` directory). Left alone, every later snapshot
|
||||
/// fails on `commit-tree -p <missing>` and /undo is dead without a word.
|
||||
///
|
||||
/// The broken ref is deleted so the next snapshot starts a fresh history.
|
||||
/// Restore points before the break cannot be recovered; the caller tells
|
||||
/// the user. Returns `true` when a repair happened.
|
||||
pub fn repair_broken_head(&self) -> io::Result<bool> {
|
||||
let commit = run_git(
|
||||
&self.git_dir,
|
||||
&self.work_tree,
|
||||
&["rev-parse", "--verify", "--quiet", "HEAD^{commit}"],
|
||||
)?;
|
||||
if commit.status.success() {
|
||||
return Ok(false);
|
||||
}
|
||||
// An unborn branch (fresh repo) names nothing: nothing to repair.
|
||||
let named = run_git(
|
||||
&self.git_dir,
|
||||
&self.work_tree,
|
||||
&["rev-parse", "--verify", "--quiet", "HEAD"],
|
||||
)?;
|
||||
if !named.status.success() {
|
||||
return Ok(false);
|
||||
}
|
||||
let missing = String::from_utf8_lossy(&named.stdout).trim().to_string();
|
||||
let delete = run_git(
|
||||
&self.git_dir,
|
||||
&self.work_tree,
|
||||
&["update-ref", "-d", "HEAD"],
|
||||
)?;
|
||||
if !delete.status.success() {
|
||||
return Err(io_other(format!(
|
||||
"snapshot history HEAD points at missing commit {missing} and could not be reset: {}",
|
||||
String::from_utf8_lossy(&delete.stderr).trim()
|
||||
)));
|
||||
}
|
||||
tracing::warn!(
|
||||
target: "snapshot",
|
||||
"snapshot history HEAD pointed at missing commit {missing}; started a fresh history"
|
||||
);
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Point the side repo's HEAD branch at a commit id that does not exist.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn point_head_at_missing_commit_for_test(&self) {
|
||||
let branch = run_git(&self.git_dir, &self.work_tree, &["symbolic-ref", "HEAD"])
|
||||
.expect("symbolic-ref");
|
||||
let branch = String::from_utf8_lossy(&branch.stdout).trim().to_string();
|
||||
let path = self.git_dir.join(&branch);
|
||||
std::fs::create_dir_all(path.parent().expect("ref parent")).expect("ref dir");
|
||||
std::fs::write(path, "1111111111111111111111111111111111111111\n").expect("write ref");
|
||||
}
|
||||
|
||||
/// Prefix a snapshot label with its owning session id, if any.
|
||||
fn encode_session_label(label: &str, session_id: Option<&str>) -> String {
|
||||
match session_id {
|
||||
@@ -1670,6 +1729,38 @@ mod tests {
|
||||
assert!(!repo.work_tree().join(".git").exists());
|
||||
}
|
||||
|
||||
/// B2: a side repo whose HEAD names a missing commit made every snapshot
|
||||
/// fail on `commit-tree -p` ("is not a valid object"), so /undo died
|
||||
/// silently. The broken ref is reset and snapshots resume.
|
||||
#[test]
|
||||
fn broken_head_is_repaired_and_snapshots_resume() {
|
||||
let tmp = tempdir().unwrap();
|
||||
let (repo, _home) = make_repo(tmp.path());
|
||||
std::fs::write(repo.work_tree().join("a.txt"), b"alpha").unwrap();
|
||||
repo.snapshot("pre-turn:1").expect("first snapshot");
|
||||
assert!(!repo.repair_broken_head().expect("healthy head"));
|
||||
|
||||
repo.point_head_at_missing_commit_for_test();
|
||||
assert!(
|
||||
repo.list(10).is_err() || repo.list(10).unwrap().is_empty(),
|
||||
"a broken head cannot list its history"
|
||||
);
|
||||
|
||||
assert!(repo.repair_broken_head().expect("repair"), "repaired once");
|
||||
assert!(!repo.repair_broken_head().expect("idempotent"));
|
||||
std::fs::write(repo.work_tree().join("a.txt"), b"beta").unwrap();
|
||||
repo.snapshot("pre-turn:2").expect("snapshots resume");
|
||||
let list = repo.list(10).expect("list after repair");
|
||||
assert_eq!(list.len(), 1, "history restarts at the repair: {list:?}");
|
||||
assert_eq!(list[0].label, "pre-turn:2");
|
||||
|
||||
// The snapshot path repairs on its own too, for callers that never
|
||||
// asked.
|
||||
repo.point_head_at_missing_commit_for_test();
|
||||
repo.snapshot("pre-turn:3")
|
||||
.expect("snapshot repairs on its own");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_existing_is_read_only_and_does_not_initialize() {
|
||||
let tmp = tempdir().unwrap();
|
||||
|
||||
Reference in new issue
Block a user