fix(jetbrains): prevent duplicate auto-approve exceptions

This commit is contained in:
kirillk
2026-07-20 18:58:33 -04:00
parent e8b8daee55
commit ba18d8433e
22 changed files with 120 additions and 2 deletions
@@ -92,13 +92,17 @@ internal fun setException(draft: PermissionDraft, tool: String, pattern: String,
}
/** Add a new exception pattern for [tool], defaulting its level to allow. */
internal fun addException(draft: PermissionDraft, tool: String, pattern: String): PermissionDraft =
setException(draft, tool, pattern, "allow")
internal fun addException(draft: PermissionDraft, tool: String, pattern: String): PermissionDraft {
val rule = draft.rules[tool] as? PermissionRuleDto.Patterns
if (rule?.map?.get(pattern) != null) return draft
return setException(draft, tool, pattern, "allow")
}
internal fun editException(draft: PermissionDraft, tool: String, from: String, to: String): PermissionDraft {
if (from == to) return draft
val rule = draft.rules[tool] as? PermissionRuleDto.Patterns ?: return draft
val level = rule.map[from] ?: return draft
if (rule.map[to] != null) return draft
val map = rule.map.filterKeys { it != from } + (to to level)
return draft.copy(rules = draft.rules + (tool to PermissionRuleDto.Patterns(map)))
}
@@ -80,6 +80,7 @@ internal class SettingsInlineList(
selectionMode,
showSearch = false,
) {
private var keys = emptySet<String>()
/** Overridable in tests, mirrors `PatternList.input` in ContextSettingsUi.kt. */
internal var input: () -> String? = {
@@ -102,6 +103,7 @@ internal class SettingsInlineList(
}
fun syncRows(rows: List<PermissionListRow>, enabled: Boolean) {
keys = rows.map { it.key }.toSet()
setItems(rows.map(::PermissionItem), enabled)
}
@@ -147,6 +149,10 @@ internal class SettingsInlineList(
val add = onAdd ?: return
val value = input()?.trim().orEmpty()
if (value.isBlank()) return
if (value in keys) {
selectKey(value, scroll = true)
return
}
add(value)
}
@@ -154,6 +160,10 @@ internal class SettingsInlineList(
val edit = onEdit ?: return
val value = editInput(key)?.trim().orEmpty()
if (value.isBlank() || value == key) return
if (value in keys) {
selectKey(value, scroll = true)
return
}
edit(key, value)
}
@@ -430,3 +430,6 @@ settings.context.watcher.remove=إزالة الأنماط المحددة
settings.context.watcher.empty=لم يتم تكوين أنماط تجاهل.
settings.context.watcher.input.title=إضافة نمط تجاهل
settings.context.watcher.input.prompt=أدخل نمط glob لتجاهله:
# Auto-Approve settings
settings.autoApprove.edit=تحرير
@@ -430,3 +430,6 @@ settings.context.watcher.remove=Ukloni odabrane uzorke
settings.context.watcher.empty=Nema konfigurisanih uzoraka za ignoriranje.
settings.context.watcher.input.title=Dodaj uzorak za ignoriranje
settings.context.watcher.input.prompt=Unesite glob uzorak za ignoriranje:
# Auto-Approve settings
settings.autoApprove.edit=Uredi
@@ -430,3 +430,6 @@ settings.context.watcher.remove=Fjern valgte mønstre
settings.context.watcher.empty=Ingen ignormønstre konfigureret.
settings.context.watcher.input.title=Tilføj ignormønster
settings.context.watcher.input.prompt=Indtast et glob-mønster, der skal ignoreres:
# Auto-Approve settings
settings.autoApprove.edit=Rediger
@@ -430,3 +430,6 @@ settings.context.watcher.remove=Ausgewählte Muster entfernen
settings.context.watcher.empty=Keine Ignorierungsmuster konfiguriert.
settings.context.watcher.input.title=Ignorierungsmuster hinzufügen
settings.context.watcher.input.prompt=Glob-Muster zum Ignorieren eingeben:
# Auto-Approve settings
settings.autoApprove.edit=Bearbeiten
@@ -430,3 +430,6 @@ settings.context.watcher.remove=Eliminar patrones seleccionados
settings.context.watcher.empty=No hay patrones de ignorar configurados.
settings.context.watcher.input.title=Agregar patrón de ignorar
settings.context.watcher.input.prompt=Introduce un patrón glob para ignorar:
# Auto-Approve settings
settings.autoApprove.edit=Editar
@@ -430,3 +430,6 @@ settings.context.watcher.remove=Supprimer les motifs sélectionnés
settings.context.watcher.empty=Aucun motif dignorance configuré.
settings.context.watcher.input.title=Ajouter un motif dignorance
settings.context.watcher.input.prompt=Saisissez un motif glob à ignorer :
# Auto-Approve settings
settings.autoApprove.edit=Modifier
@@ -430,3 +430,6 @@ settings.context.watcher.remove=選択したパターンを削除
settings.context.watcher.empty=無視パターンは設定されていません。
settings.context.watcher.input.title=無視パターンを追加
settings.context.watcher.input.prompt=無視するglobパターンを入力してください:
# Auto-Approve settings
settings.autoApprove.edit=編集
@@ -430,3 +430,6 @@ settings.context.watcher.remove=선택한 패턴 제거
settings.context.watcher.empty=구성된 무시 패턴이 없습니다.
settings.context.watcher.input.title=무시 패턴 추가
settings.context.watcher.input.prompt=무시할 glob 패턴을 입력하세요:
# Auto-Approve settings
settings.autoApprove.edit=편집
@@ -430,3 +430,6 @@ settings.context.watcher.remove=Geselecteerde patronen verwijderen
settings.context.watcher.empty=Geen negeerpatronen geconfigureerd.
settings.context.watcher.input.title=Negeerpatroon toevoegen
settings.context.watcher.input.prompt=Voer een glob-patroon in om te negeren:
# Auto-Approve settings
settings.autoApprove.edit=Bewerken
@@ -430,3 +430,6 @@ settings.context.watcher.remove=Fjern valgte mønstre
settings.context.watcher.empty=Ingen ignormønstre konfigurert.
settings.context.watcher.input.title=Legg til ignormønster
settings.context.watcher.input.prompt=Skriv inn et glob-mønster som skal ignoreres:
# Auto-Approve settings
settings.autoApprove.edit=Rediger
@@ -430,3 +430,6 @@ settings.context.watcher.remove=Usuń wybrane wzorce
settings.context.watcher.empty=Nie skonfigurowano wzorców ignorowania.
settings.context.watcher.input.title=Dodaj wzorzec ignorowania
settings.context.watcher.input.prompt=Wpisz wzorzec glob do ignorowania:
# Auto-Approve settings
settings.autoApprove.edit=Edytuj
@@ -430,3 +430,6 @@ settings.context.watcher.remove=Remover padrões selecionados
settings.context.watcher.empty=Nenhum padrão de ignorar configurado.
settings.context.watcher.input.title=Adicionar padrão de ignorar
settings.context.watcher.input.prompt=Digite um padrão glob para ignorar:
# Auto-Approve settings
settings.autoApprove.edit=Editar
@@ -430,3 +430,6 @@ settings.context.watcher.remove=Удалить выбранные шаблоны
settings.context.watcher.empty=Шаблоны игнорирования не настроены.
settings.context.watcher.input.title=Добавить шаблон игнорирования
settings.context.watcher.input.prompt=Введите glob-шаблон для игнорирования:
# Auto-Approve settings
settings.autoApprove.edit=Изменить
@@ -430,3 +430,6 @@ settings.context.watcher.remove=ลบรูปแบบที่เลือก
settings.context.watcher.empty=ยังไม่ได้กำหนดค่ารูปแบบการละเว้น
settings.context.watcher.input.title=เพิ่มรูปแบบการละเว้น
settings.context.watcher.input.prompt=ป้อนรูปแบบ glob ที่จะละเว้น:
# Auto-Approve settings
settings.autoApprove.edit=แก้ไข
@@ -430,3 +430,6 @@ settings.context.watcher.remove=Seçili kalıpları kaldır
settings.context.watcher.empty=Yok sayma kalıbı yapılandırılmadı.
settings.context.watcher.input.title=Yok sayma kalıbı ekle
settings.context.watcher.input.prompt=Yok sayılacak bir glob kalıbı girin:
# Auto-Approve settings
settings.autoApprove.edit=Düzenle
@@ -430,3 +430,6 @@ settings.context.watcher.remove=Видалити вибрані шаблони
settings.context.watcher.empty=Шаблони ігнорування не налаштовано.
settings.context.watcher.input.title=Додати шаблон ігнорування
settings.context.watcher.input.prompt=Введіть glob-шаблон для ігнорування:
# Auto-Approve settings
settings.autoApprove.edit=Редагувати
@@ -430,3 +430,6 @@ settings.context.watcher.remove=移除所选模式
settings.context.watcher.empty=未配置忽略模式。
settings.context.watcher.input.title=添加忽略模式
settings.context.watcher.input.prompt=输入要忽略的 glob 模式:
# Auto-Approve settings
settings.autoApprove.edit=编辑
@@ -430,3 +430,6 @@ settings.context.watcher.remove=移除所選模式
settings.context.watcher.empty=尚未設定忽略模式。
settings.context.watcher.input.title=新增忽略模式
settings.context.watcher.input.prompt=輸入要忽略的 glob 模式:
# Auto-Approve settings
settings.autoApprove.edit=編輯
@@ -107,6 +107,26 @@ class AutoApproveSettingsStateTest {
)
}
@Test
fun `addException ignores an existing pattern`() {
val draft = PermissionDraft(
rules = mapOf("bash" to PermissionRuleDto.Patterns(mapOf("*" to "ask", "git *" to "deny"))),
)
assertEquals(draft, addException(draft, "bash", "git *"))
}
@Test
fun `editException ignores an existing target pattern`() {
val draft = PermissionDraft(
rules = mapOf(
"bash" to PermissionRuleDto.Patterns(mapOf("git *" to "deny", "git status" to "ask")),
),
)
assertEquals(draft, editException(draft, "bash", "git *", "git status"))
}
@Test
fun `setException changes an existing exception level`() {
val from = PermissionDraft(
@@ -69,6 +69,20 @@ class SettingsInlineListTest : BasePlatformTestCase() {
}
}
fun `test toolbar add ignores duplicate input`() {
edt {
val added = mutableListOf<String>()
val list = list(onAdd = { added += it })
list.syncItems(listOf("git *" to "ask"), true)
list.input = { "git *" }
layout(list)
click(button(list, 0))
assertTrue(added.isEmpty())
}
}
fun `test row level action changes through picker selection`() {
edt {
val changed = mutableListOf<String>()
@@ -122,6 +136,22 @@ class SettingsInlineListTest : BasePlatformTestCase() {
}
}
fun `test exception edit ignores duplicate target`() {
edt {
val edits = mutableListOf<Pair<String, String>>()
val list = list(onEdit = { from, to -> edits += from to to })
list.editInput = { "git status" }
list.syncItems(listOf("git *" to "allow", "git status" to "ask"), true)
val jList = jbList(list)
jList.setSize(400, jList.preferredSize.height.coerceAtLeast(50))
jList.doLayout()
doubleClickRow(jList, 0)
assertTrue(edits.isEmpty())
}
}
fun `test syncItems retains the same list view instance across updates`() {
edt {
val list = list()