native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #199 from supabitapp/sbertix/global-worktree-settings

Add global defaults for worktree copy flags and merge strategy

authored by

Stefano Bertagno and committed by
GitHub
c29ee5a5 7ade80a1

+242 -48
+8 -1
supacode/Features/App/Reducer/AppFeature.swift
··· 243 243 return .none 244 244 } 245 245 @Shared(.repositorySettings(repository.rootURL)) var repositorySettings 246 - state.settings.repositorySettings = RepositorySettingsFeature.State( 246 + var repoSettingsState = RepositorySettingsFeature.State( 247 247 rootURL: repository.rootURL, 248 248 settings: repositorySettings 249 249 ) 250 + repoSettingsState.globalCopyIgnoredOnWorktreeCreate = 251 + state.settings.copyIgnoredOnWorktreeCreate 252 + repoSettingsState.globalCopyUntrackedOnWorktreeCreate = 253 + state.settings.copyUntrackedOnWorktreeCreate 254 + repoSettingsState.globalPullRequestMergeStrategy = 255 + state.settings.pullRequestMergeStrategy 256 + state.settings.repositorySettings = repoSettingsState 250 257 case .general, .notifications, .worktree, .shortcuts, .updates, .github: 251 258 state.settings.repositorySettings = nil 252 259 }
+10 -5
supacode/Features/Repositories/Reducer/RepositoriesFeature.swift
··· 879 879 repositoryOverridePath: repositorySettings.worktreeBaseDirectoryPath 880 880 ) 881 881 let selectedBaseRef = repositorySettings.worktreeBaseRef 882 - let copyIgnoredOnWorktreeCreate = repositorySettings.copyIgnoredOnWorktreeCreate 883 - let copyUntrackedOnWorktreeCreate = repositorySettings.copyUntrackedOnWorktreeCreate 882 + let globalSettings = settingsFile.global 883 + let copyIgnoredOnWorktreeCreate = 884 + repositorySettings.copyIgnoredOnWorktreeCreate ?? globalSettings.copyIgnoredOnWorktreeCreate 885 + let copyUntrackedOnWorktreeCreate = 886 + repositorySettings.copyUntrackedOnWorktreeCreate ?? globalSettings.copyUntrackedOnWorktreeCreate 884 887 state.pendingWorktrees.append( 885 888 PendingWorktree( 886 889 id: pendingID, ··· 1987 1990 var effects: [Effect<Action>] = [ 1988 1991 .run { _ in 1989 1992 await repositoryPersistence.savePinnedWorktreeIDs(pinnedWorktreeIDs) 1990 - }, 1993 + } 1991 1994 ] 1992 1995 if didUpdateWorktreeOrder { 1993 1996 let worktreeOrderByRepository = state.worktreeOrderByRepository ··· 2014 2017 var effects: [Effect<Action>] = [ 2015 2018 .run { _ in 2016 2019 await repositoryPersistence.savePinnedWorktreeIDs(pinnedWorktreeIDs) 2017 - }, 2020 + } 2018 2021 ] 2019 2022 if didUpdateWorktreeOrder { 2020 2023 let worktreeOrderByRepository = state.worktreeOrderByRepository ··· 2442 2445 return 2443 2446 } 2444 2447 @Shared(.repositorySettings(repoRoot)) var repositorySettings 2445 - let strategy = repositorySettings.pullRequestMergeStrategy 2448 + @Shared(.settingsFile) var settingsFile 2449 + let strategy = 2450 + repositorySettings.pullRequestMergeStrategy ?? settingsFile.global.pullRequestMergeStrategy 2446 2451 await send(.showToast(.inProgress("Merging pull request…"))) 2447 2452 do { 2448 2453 try await githubCLI.mergePullRequest(worktreeRoot, pullRequest.number, strategy)
+31 -9
supacode/Features/RepositorySettings/Reducer/RepositorySettingsFeature.swift
··· 8 8 var rootURL: URL 9 9 var settings: RepositorySettings 10 10 var globalDefaultWorktreeBaseDirectoryPath: String? 11 + var globalCopyIgnoredOnWorktreeCreate: Bool = false 12 + var globalCopyUntrackedOnWorktreeCreate: Bool = false 13 + var globalPullRequestMergeStrategy: PullRequestMergeStrategy = .merge 11 14 var isBareRepository = false 12 15 var branchOptions: [String] = [] 13 16 var defaultWorktreeBaseRef = "origin/main" ··· 28 31 case settingsLoaded( 29 32 RepositorySettings, 30 33 isBareRepository: Bool, 31 - globalDefaultWorktreeBaseDirectoryPath: String? 34 + globalDefaultWorktreeBaseDirectoryPath: String?, 35 + globalCopyIgnoredOnWorktreeCreate: Bool, 36 + globalCopyUntrackedOnWorktreeCreate: Bool, 37 + globalPullRequestMergeStrategy: PullRequestMergeStrategy 32 38 ) 33 39 case branchDataLoaded([String], defaultBaseRef: String) 34 40 case delegate(Delegate) ··· 51 57 @Shared(.repositorySettings(rootURL)) var repositorySettings 52 58 @Shared(.settingsFile) var settingsFile 53 59 let settings = repositorySettings 54 - let globalDefaultWorktreeBaseDirectoryPath = 55 - settingsFile.global.defaultWorktreeBaseDirectoryPath 60 + let global = settingsFile.global 61 + let globalDefaultWorktreeBaseDirectoryPath = global.defaultWorktreeBaseDirectoryPath 62 + let globalCopyIgnored = global.copyIgnoredOnWorktreeCreate 63 + let globalCopyUntracked = global.copyUntrackedOnWorktreeCreate 64 + let globalMergeStrategy = global.pullRequestMergeStrategy 56 65 let gitClient = gitClient 57 66 return .run { send in 58 67 let isBareRepository = (try? await gitClient.isBareRepository(rootURL)) ?? false ··· 60 69 .settingsLoaded( 61 70 settings, 62 71 isBareRepository: isBareRepository, 63 - globalDefaultWorktreeBaseDirectoryPath: globalDefaultWorktreeBaseDirectoryPath 72 + globalDefaultWorktreeBaseDirectoryPath: globalDefaultWorktreeBaseDirectoryPath, 73 + globalCopyIgnoredOnWorktreeCreate: globalCopyIgnored, 74 + globalCopyUntrackedOnWorktreeCreate: globalCopyUntracked, 75 + globalPullRequestMergeStrategy: globalMergeStrategy 64 76 ) 65 77 ) 66 78 let branches: [String] ··· 77 89 await send(.branchDataLoaded(branches, defaultBaseRef: defaultBaseRef)) 78 90 } 79 91 80 - case .settingsLoaded(let settings, let isBareRepository, let globalDefaultWorktreeBaseDirectoryPath): 92 + case .settingsLoaded( 93 + let settings, 94 + let isBareRepository, 95 + let globalDefaultWorktreeBaseDirectoryPath, 96 + let globalCopyIgnoredOnWorktreeCreate, 97 + let globalCopyUntrackedOnWorktreeCreate, 98 + let globalPullRequestMergeStrategy 99 + ): 81 100 var updatedSettings = settings 82 101 updatedSettings.worktreeBaseDirectoryPath = SupacodePaths.normalizedWorktreeBaseDirectoryPath( 83 102 updatedSettings.worktreeBaseDirectoryPath, 84 103 repositoryRootURL: state.rootURL 85 104 ) 86 105 if isBareRepository { 87 - updatedSettings.copyIgnoredOnWorktreeCreate = false 88 - updatedSettings.copyUntrackedOnWorktreeCreate = false 106 + updatedSettings.copyIgnoredOnWorktreeCreate = nil 107 + updatedSettings.copyUntrackedOnWorktreeCreate = nil 89 108 } 90 109 state.settings = updatedSettings 91 110 state.globalDefaultWorktreeBaseDirectoryPath = 92 111 SupacodePaths.normalizedWorktreeBaseDirectoryPath(globalDefaultWorktreeBaseDirectoryPath) 112 + state.globalCopyIgnoredOnWorktreeCreate = globalCopyIgnoredOnWorktreeCreate 113 + state.globalCopyUntrackedOnWorktreeCreate = globalCopyUntrackedOnWorktreeCreate 114 + state.globalPullRequestMergeStrategy = globalPullRequestMergeStrategy 93 115 state.isBareRepository = isBareRepository 94 116 guard updatedSettings != settings else { return .none } 95 117 let rootURL = state.rootURL ··· 112 134 113 135 case .binding: 114 136 if state.isBareRepository { 115 - state.settings.copyIgnoredOnWorktreeCreate = false 116 - state.settings.copyUntrackedOnWorktreeCreate = false 137 + state.settings.copyIgnoredOnWorktreeCreate = nil 138 + state.settings.copyUntrackedOnWorktreeCreate = nil 117 139 } 118 140 let rootURL = state.rootURL 119 141 var normalizedSettings = state.settings
+21
supacode/Features/Settings/Models/GlobalSettings.swift
··· 17 17 var promptForWorktreeCreation: Bool 18 18 var fetchOriginBeforeWorktreeCreation: Bool 19 19 var defaultWorktreeBaseDirectoryPath: String? 20 + var copyIgnoredOnWorktreeCreate: Bool 21 + var copyUntrackedOnWorktreeCreate: Bool 22 + var pullRequestMergeStrategy: PullRequestMergeStrategy 20 23 var terminalThemeSyncEnabled: Bool 21 24 var shortcutOverrides: [AppShortcutID: AppShortcutOverride] 22 25 ··· 38 41 automaticallyArchiveMergedWorktrees: false, 39 42 promptForWorktreeCreation: true, 40 43 fetchOriginBeforeWorktreeCreation: true, 44 + copyIgnoredOnWorktreeCreate: false, 45 + copyUntrackedOnWorktreeCreate: false, 46 + pullRequestMergeStrategy: .merge, 41 47 terminalThemeSyncEnabled: false, 42 48 defaultWorktreeBaseDirectoryPath: nil, 43 49 shortcutOverrides: [:] ··· 61 67 automaticallyArchiveMergedWorktrees: Bool, 62 68 promptForWorktreeCreation: Bool, 63 69 fetchOriginBeforeWorktreeCreation: Bool = true, 70 + copyIgnoredOnWorktreeCreate: Bool = false, 71 + copyUntrackedOnWorktreeCreate: Bool = false, 72 + pullRequestMergeStrategy: PullRequestMergeStrategy = .merge, 64 73 terminalThemeSyncEnabled: Bool = false, 65 74 defaultWorktreeBaseDirectoryPath: String? = nil, 66 75 shortcutOverrides: [AppShortcutID: AppShortcutOverride] = [:] ··· 82 91 self.automaticallyArchiveMergedWorktrees = automaticallyArchiveMergedWorktrees 83 92 self.promptForWorktreeCreation = promptForWorktreeCreation 84 93 self.fetchOriginBeforeWorktreeCreation = fetchOriginBeforeWorktreeCreation 94 + self.copyIgnoredOnWorktreeCreate = copyIgnoredOnWorktreeCreate 95 + self.copyUntrackedOnWorktreeCreate = copyUntrackedOnWorktreeCreate 96 + self.pullRequestMergeStrategy = pullRequestMergeStrategy 85 97 self.terminalThemeSyncEnabled = terminalThemeSyncEnabled 86 98 self.defaultWorktreeBaseDirectoryPath = defaultWorktreeBaseDirectoryPath 87 99 self.shortcutOverrides = shortcutOverrides ··· 134 146 fetchOriginBeforeWorktreeCreation = 135 147 try container.decodeIfPresent(Bool.self, forKey: .fetchOriginBeforeWorktreeCreation) 136 148 ?? Self.default.fetchOriginBeforeWorktreeCreation 149 + copyIgnoredOnWorktreeCreate = 150 + try container.decodeIfPresent(Bool.self, forKey: .copyIgnoredOnWorktreeCreate) 151 + ?? Self.default.copyIgnoredOnWorktreeCreate 152 + copyUntrackedOnWorktreeCreate = 153 + try container.decodeIfPresent(Bool.self, forKey: .copyUntrackedOnWorktreeCreate) 154 + ?? Self.default.copyUntrackedOnWorktreeCreate 155 + pullRequestMergeStrategy = 156 + try container.decodeIfPresent(PullRequestMergeStrategy.self, forKey: .pullRequestMergeStrategy) 157 + ?? Self.default.pullRequestMergeStrategy 137 158 terminalThemeSyncEnabled = 138 159 try container.decodeIfPresent(Bool.self, forKey: .terminalThemeSyncEnabled) 139 160 ?? Self.default.terminalThemeSyncEnabled
+15 -21
supacode/Features/Settings/Models/RepositorySettings.swift
··· 8 8 var openActionID: String 9 9 var worktreeBaseRef: String? 10 10 var worktreeBaseDirectoryPath: String? 11 - var copyIgnoredOnWorktreeCreate: Bool 12 - var copyUntrackedOnWorktreeCreate: Bool 13 - var pullRequestMergeStrategy: PullRequestMergeStrategy 11 + var copyIgnoredOnWorktreeCreate: Bool? 12 + var copyUntrackedOnWorktreeCreate: Bool? 13 + var pullRequestMergeStrategy: PullRequestMergeStrategy? 14 14 15 15 private enum CodingKeys: String, CodingKey { 16 16 case setupScript ··· 33 33 openActionID: OpenWorktreeAction.automaticSettingsID, 34 34 worktreeBaseRef: nil, 35 35 worktreeBaseDirectoryPath: nil, 36 - copyIgnoredOnWorktreeCreate: false, 37 - copyUntrackedOnWorktreeCreate: false, 38 - pullRequestMergeStrategy: .merge 36 + copyIgnoredOnWorktreeCreate: nil, 37 + copyUntrackedOnWorktreeCreate: nil, 38 + pullRequestMergeStrategy: nil 39 39 ) 40 40 41 41 init( ··· 46 46 openActionID: String, 47 47 worktreeBaseRef: String?, 48 48 worktreeBaseDirectoryPath: String? = nil, 49 - copyIgnoredOnWorktreeCreate: Bool, 50 - copyUntrackedOnWorktreeCreate: Bool, 51 - pullRequestMergeStrategy: PullRequestMergeStrategy 49 + copyIgnoredOnWorktreeCreate: Bool? = nil, 50 + copyUntrackedOnWorktreeCreate: Bool? = nil, 51 + pullRequestMergeStrategy: PullRequestMergeStrategy? = nil 52 52 ) { 53 53 self.setupScript = setupScript 54 54 self.archiveScript = archiveScript ··· 84 84 worktreeBaseDirectoryPath = 85 85 try container.decodeIfPresent(String.self, forKey: .worktreeBaseDirectoryPath) 86 86 copyIgnoredOnWorktreeCreate = 87 - try container.decodeIfPresent( 88 - Bool.self, 89 - forKey: .copyIgnoredOnWorktreeCreate 90 - ) ?? Self.default.copyIgnoredOnWorktreeCreate 87 + try container.decodeIfPresent(Bool.self, forKey: .copyIgnoredOnWorktreeCreate) 88 + ?? Self.default.copyIgnoredOnWorktreeCreate 91 89 copyUntrackedOnWorktreeCreate = 92 - try container.decodeIfPresent( 93 - Bool.self, 94 - forKey: .copyUntrackedOnWorktreeCreate 95 - ) ?? Self.default.copyUntrackedOnWorktreeCreate 90 + try container.decodeIfPresent(Bool.self, forKey: .copyUntrackedOnWorktreeCreate) 91 + ?? Self.default.copyUntrackedOnWorktreeCreate 96 92 pullRequestMergeStrategy = 97 - try container.decodeIfPresent( 98 - PullRequestMergeStrategy.self, 99 - forKey: .pullRequestMergeStrategy 100 - ) ?? Self.default.pullRequestMergeStrategy 93 + try container.decodeIfPresent(PullRequestMergeStrategy.self, forKey: .pullRequestMergeStrategy) 94 + ?? Self.default.pullRequestMergeStrategy 101 95 } 102 96 }
+28 -8
supacode/Features/Settings/Reducer/SettingsFeature.swift
··· 23 23 var automaticallyArchiveMergedWorktrees: Bool 24 24 var promptForWorktreeCreation: Bool 25 25 var fetchOriginBeforeWorktreeCreation: Bool 26 + var copyIgnoredOnWorktreeCreate: Bool 27 + var copyUntrackedOnWorktreeCreate: Bool 28 + var pullRequestMergeStrategy: PullRequestMergeStrategy 26 29 var terminalThemeSyncEnabled: Bool 27 30 var defaultWorktreeBaseDirectoryPath: String 28 31 var shortcutOverrides: [AppShortcutID: AppShortcutOverride] ··· 52 55 automaticallyArchiveMergedWorktrees = settings.automaticallyArchiveMergedWorktrees 53 56 promptForWorktreeCreation = settings.promptForWorktreeCreation 54 57 fetchOriginBeforeWorktreeCreation = settings.fetchOriginBeforeWorktreeCreation 58 + copyIgnoredOnWorktreeCreate = settings.copyIgnoredOnWorktreeCreate 59 + copyUntrackedOnWorktreeCreate = settings.copyUntrackedOnWorktreeCreate 60 + pullRequestMergeStrategy = settings.pullRequestMergeStrategy 55 61 terminalThemeSyncEnabled = settings.terminalThemeSyncEnabled 56 62 shortcutOverrides = settings.shortcutOverrides 57 63 defaultWorktreeBaseDirectoryPath = ··· 77 83 automaticallyArchiveMergedWorktrees: automaticallyArchiveMergedWorktrees, 78 84 promptForWorktreeCreation: promptForWorktreeCreation, 79 85 fetchOriginBeforeWorktreeCreation: fetchOriginBeforeWorktreeCreation, 86 + copyIgnoredOnWorktreeCreate: copyIgnoredOnWorktreeCreate, 87 + copyUntrackedOnWorktreeCreate: copyUntrackedOnWorktreeCreate, 88 + pullRequestMergeStrategy: pullRequestMergeStrategy, 80 89 terminalThemeSyncEnabled: terminalThemeSyncEnabled, 81 90 defaultWorktreeBaseDirectoryPath: SupacodePaths.normalizedWorktreeBaseDirectoryPath( 82 91 defaultWorktreeBaseDirectoryPath ··· 157 166 state.automaticallyArchiveMergedWorktrees = normalizedSettings.automaticallyArchiveMergedWorktrees 158 167 state.promptForWorktreeCreation = normalizedSettings.promptForWorktreeCreation 159 168 state.fetchOriginBeforeWorktreeCreation = normalizedSettings.fetchOriginBeforeWorktreeCreation 169 + state.copyIgnoredOnWorktreeCreate = normalizedSettings.copyIgnoredOnWorktreeCreate 170 + state.copyUntrackedOnWorktreeCreate = normalizedSettings.copyUntrackedOnWorktreeCreate 171 + state.pullRequestMergeStrategy = normalizedSettings.pullRequestMergeStrategy 160 172 state.terminalThemeSyncEnabled = normalizedSettings.terminalThemeSyncEnabled 161 173 state.shortcutOverrides = normalizedSettings.shortcutOverrides 162 174 state.defaultWorktreeBaseDirectoryPath = normalizedSettings.defaultWorktreeBaseDirectoryPath ?? "" 163 - state.repositorySettings?.globalDefaultWorktreeBaseDirectoryPath = 164 - normalizedSettings.defaultWorktreeBaseDirectoryPath 175 + state.syncGlobalDefaults(from: normalizedSettings) 165 176 return .send(.delegate(.settingsChanged(normalizedSettings))) 166 177 167 178 case .binding: 168 - let defaultWorktreeBaseDirectoryPath = state.globalSettings.defaultWorktreeBaseDirectoryPath 169 - state.repositorySettings?.globalDefaultWorktreeBaseDirectoryPath = 170 - defaultWorktreeBaseDirectoryPath 179 + state.syncGlobalDefaults(from: state.globalSettings) 171 180 return persist(state) 172 181 173 182 case .setSystemNotificationsEnabled(let isEnabled): 174 183 state.systemNotificationsEnabled = isEnabled 175 - let defaultWorktreeBaseDirectoryPath = state.globalSettings.defaultWorktreeBaseDirectoryPath 176 - state.repositorySettings?.globalDefaultWorktreeBaseDirectoryPath = 177 - defaultWorktreeBaseDirectoryPath 184 + state.syncGlobalDefaults(from: state.globalSettings) 178 185 return persist(state) 179 186 180 187 case .showNotificationPermissionAlert(let errorMessage): ··· 280 287 return .send(.delegate(.settingsChanged(settings))) 281 288 } 282 289 } 290 + 291 + extension SettingsFeature.State { 292 + mutating func syncGlobalDefaults(from settings: GlobalSettings) { 293 + repositorySettings?.globalDefaultWorktreeBaseDirectoryPath = 294 + settings.defaultWorktreeBaseDirectoryPath 295 + repositorySettings?.globalCopyIgnoredOnWorktreeCreate = 296 + settings.copyIgnoredOnWorktreeCreate 297 + repositorySettings?.globalCopyUntrackedOnWorktreeCreate = 298 + settings.copyUntrackedOnWorktreeCreate 299 + repositorySettings?.globalPullRequestMergeStrategy = 300 + settings.pullRequestMergeStrategy 301 + } 302 + }
+11
supacode/Features/Settings/Views/GithubSettingsView.swift
··· 146 146 EmptyView() 147 147 } 148 148 } 149 + Section("Pull Requests") { 150 + Picker(selection: $store.pullRequestMergeStrategy) { 151 + ForEach(PullRequestMergeStrategy.allCases) { strategy in 152 + Text(strategy.title) 153 + .tag(strategy) 154 + } 155 + } label: { 156 + Text("Merge strategy") 157 + Text("Default strategy when merging PRs from the command palette.") 158 + } 159 + } 149 160 } 150 161 .formStyle(.grouped) 151 162 .padding(.top, -20)
+22 -4
supacode/Features/Settings/Views/RepositorySettingsView.swift
··· 37 37 } 38 38 } 39 39 Section { 40 - Toggle(isOn: settings.copyIgnoredOnWorktreeCreate) { 40 + Picker(selection: settings.copyIgnoredOnWorktreeCreate) { 41 + Text("Global \(Text(store.globalCopyIgnoredOnWorktreeCreate ? "Yes" : "No").foregroundStyle(.secondary))") 42 + .tag(Bool?.none) 43 + Text("Yes").tag(Bool?.some(true)) 44 + Text("No").tag(Bool?.some(false)) 45 + } label: { 41 46 Text("Copy ignored files to new worktrees") 42 47 Text("Copies gitignored files from the main worktree.") 43 48 } 44 49 .disabled(store.isBareRepository) 45 - Toggle(isOn: settings.copyUntrackedOnWorktreeCreate) { 50 + Picker(selection: settings.copyUntrackedOnWorktreeCreate) { 51 + Text("Global \(Text(store.globalCopyUntrackedOnWorktreeCreate ? "Yes" : "No").foregroundStyle(.secondary))") 52 + .tag(Bool?.none) 53 + Text("Yes").tag(Bool?.some(true)) 54 + Text("No").tag(Bool?.some(false)) 55 + } label: { 46 56 Text("Copy untracked files to new worktrees") 47 57 Text("Copies untracked files from the main worktree.") 48 58 } ··· 54 64 } 55 65 TextField( 56 66 text: worktreeBaseDirectoryPath, 57 - prompt: Text(SupacodePaths.reposDirectory.path(percentEncoded: false)) 67 + prompt: Text( 68 + SupacodePaths.worktreeBaseDirectory( 69 + for: store.rootURL, 70 + globalDefaultPath: store.globalDefaultWorktreeBaseDirectoryPath, 71 + repositoryOverridePath: nil 72 + ).path(percentEncoded: false) 73 + ) 58 74 ) { 59 75 Text("Default directory").monospaced(false) 60 76 Text("Parent path for new worktrees.").monospaced(false) ··· 66 82 } 67 83 Section("Pull Requests") { 68 84 Picker(selection: settings.pullRequestMergeStrategy) { 85 + Text("Global \(Text(store.globalPullRequestMergeStrategy.title).foregroundStyle(.secondary))") 86 + .tag(PullRequestMergeStrategy?.none) 69 87 ForEach(PullRequestMergeStrategy.allCases) { strategy in 70 88 Text(strategy.title) 71 - .tag(strategy) 89 + .tag(PullRequestMergeStrategy?.some(strategy)) 72 90 } 73 91 } label: { 74 92 Text("Merge strategy")
+10
supacode/Features/Settings/Views/WorktreeSettingsView.swift
··· 31 31 } footer: { 32 32 Text("e.g., `\(examplePath)`") 33 33 } 34 + Section { 35 + Toggle(isOn: $store.copyIgnoredOnWorktreeCreate) { 36 + Text("Copy ignored files to new worktrees") 37 + Text("Copies gitignored files from the main worktree.") 38 + } 39 + Toggle(isOn: $store.copyUntrackedOnWorktreeCreate) { 40 + Text("Copy untracked files to new worktrees") 41 + Text("Copies untracked files from the main worktree.") 42 + } 43 + } 34 44 Section("Clean-up") { 35 45 Toggle(isOn: $store.automaticallyArchiveMergedWorktrees) { 36 46 Text("Automatically archive merged worktrees")
+42
supacodeTests/RepositorySettingsKeyTests.swift
··· 13 13 14 14 #expect(!json.contains("worktreeBaseRef")) 15 15 #expect(!json.contains("worktreeBaseDirectoryPath")) 16 + #expect(!json.contains("copyIgnoredOnWorktreeCreate")) 17 + #expect(!json.contains("copyUntrackedOnWorktreeCreate")) 18 + #expect(!json.contains("pullRequestMergeStrategy")) 16 19 } 17 20 18 21 @Test(.dependencies) func loadCreatesDefaultAndPersists() throws { ··· 64 67 } 65 68 66 69 #expect(reloaded.repositories[rootURL.path(percentEncoded: false)] == updated) 70 + } 71 + 72 + @Test func decodeOldFormatPreservesExplicitOverrides() throws { 73 + let data = Data( 74 + """ 75 + { 76 + "setupScript": "", 77 + "archiveScript": "", 78 + "deleteScript": "", 79 + "runScript": "", 80 + "openActionID": "automatic", 81 + "copyIgnoredOnWorktreeCreate": false, 82 + "copyUntrackedOnWorktreeCreate": true, 83 + "pullRequestMergeStrategy": "squash" 84 + } 85 + """.utf8 86 + ) 87 + let settings = try JSONDecoder().decode(RepositorySettings.self, from: data) 88 + 89 + #expect(settings.copyIgnoredOnWorktreeCreate == false) 90 + #expect(settings.copyUntrackedOnWorktreeCreate == true) 91 + #expect(settings.pullRequestMergeStrategy == .squash) 92 + } 93 + 94 + @Test func decodeMissingOptionalFieldsDefaultsToNil() throws { 95 + let data = Data( 96 + """ 97 + { 98 + "setupScript": "", 99 + "runScript": "", 100 + "openActionID": "automatic" 101 + } 102 + """.utf8 103 + ) 104 + let settings = try JSONDecoder().decode(RepositorySettings.self, from: data) 105 + 106 + #expect(settings.copyIgnoredOnWorktreeCreate == nil) 107 + #expect(settings.copyUntrackedOnWorktreeCreate == nil) 108 + #expect(settings.pullRequestMergeStrategy == nil) 67 109 } 68 110 69 111 @Test func decodeMissingDeleteScriptDefaultsToEmpty() throws {
+44
supacodeTests/SettingsFeatureTests.swift
··· 264 264 #expect(settingsFile.global.defaultWorktreeBaseDirectoryPath == expectedPath) 265 265 } 266 266 267 + @Test(.dependencies) func changingGlobalCopyIgnoredUpdatesRepositorySettingsState() async { 268 + let rootURL = URL(fileURLWithPath: "/tmp/repo") 269 + @Shared(.settingsFile) var settingsFile 270 + $settingsFile.withLock { $0.global = .default } 271 + var state = SettingsFeature.State() 272 + state.repositorySettings = RepositorySettingsFeature.State( 273 + rootURL: rootURL, 274 + settings: .default 275 + ) 276 + let store = TestStore(initialState: state) { 277 + SettingsFeature() 278 + } 279 + 280 + await store.send(.binding(.set(\.copyIgnoredOnWorktreeCreate, true))) { 281 + $0.copyIgnoredOnWorktreeCreate = true 282 + $0.repositorySettings?.globalCopyIgnoredOnWorktreeCreate = true 283 + } 284 + await store.receive(\.delegate.settingsChanged) 285 + #expect(store.state.repositorySettings?.globalCopyIgnoredOnWorktreeCreate == true) 286 + #expect(settingsFile.global.copyIgnoredOnWorktreeCreate == true) 287 + } 288 + 289 + @Test(.dependencies) func changingGlobalMergeStrategyUpdatesRepositorySettingsState() async { 290 + let rootURL = URL(fileURLWithPath: "/tmp/repo") 291 + @Shared(.settingsFile) var settingsFile 292 + $settingsFile.withLock { $0.global = .default } 293 + var state = SettingsFeature.State() 294 + state.repositorySettings = RepositorySettingsFeature.State( 295 + rootURL: rootURL, 296 + settings: .default 297 + ) 298 + let store = TestStore(initialState: state) { 299 + SettingsFeature() 300 + } 301 + 302 + await store.send(.binding(.set(\.pullRequestMergeStrategy, .squash))) { 303 + $0.pullRequestMergeStrategy = .squash 304 + $0.repositorySettings?.globalPullRequestMergeStrategy = .squash 305 + } 306 + await store.receive(\.delegate.settingsChanged) 307 + #expect(store.state.repositorySettings?.globalPullRequestMergeStrategy == .squash) 308 + #expect(settingsFile.global.pullRequestMergeStrategy == .squash) 309 + } 310 + 267 311 // MARK: - Sorted repositories. 268 312 269 313 @Test(.dependencies) func repositoriesChangedSortsByNameCaseInsensitive() async {