Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions src/Server/RefreshScheduler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -214,14 +214,16 @@ let resolveArchivedPaths (archivedBranchSets: Map<RepoId, Set<string>>) (repos:
|> Option.map (fun _ -> wt.Path))
|> Set.ofList)

let isWorktreeIgnored (ignorePredicate: string -> bool) (wt: GitWorktree.WorktreeInfo) =
(wt.Branch |> Option.exists ignorePredicate)
|| (wt.Path |> Path.GetFileName |> ignorePredicate)

let resolveIgnoredPaths (ignorePredicate: string -> bool) (repos: Map<RepoId, PerRepoState>) =
repos
|> Map.map (fun _ repo ->
repo.WorktreeList
|> List.choose (fun wt ->
wt.Branch
|> Option.filter ignorePredicate
|> Option.map (fun _ -> wt.Path))
|> List.filter (isWorktreeIgnored ignorePredicate)
|> List.map _.Path
|> Set.ofList)

type PathFilters =
Expand Down Expand Up @@ -428,7 +430,7 @@ let runInitialBurst (agent: MailboxProcessor<StateMsg>) (rootPaths: Map<RepoId,
let! state = agent.PostAndAsyncReply(GetState)
let archivedBranchSets = readArchivedBranchSets rootPaths
let archivedPaths = resolveArchivedPaths archivedBranchSets state.Repos
let ignorePredicate = TreemonConfig.readIgnoreBranchPatterns () |> TreemonConfig.buildIgnorePredicate
let ignorePredicate = TreemonConfig.readIgnoreWorktreePatterns () |> TreemonConfig.buildIgnorePredicate
let ignoredPaths = resolveIgnoredPaths ignorePredicate state.Repos
let filters = { Archived = archivedPaths; Ignored = ignoredPaths }
Log.log "Scheduler" "Starting initial burst — Phase 2 (local data + fetch)"
Expand Down Expand Up @@ -488,7 +490,7 @@ let start (agent: MailboxProcessor<StateMsg>) (worktreeRoots: string list) (ct:

let archivedBranchSets = readArchivedBranchSets rootPaths
let archivedPaths = resolveArchivedPaths archivedBranchSets repos
let ignorePredicate = TreemonConfig.readIgnoreBranchPatterns () |> TreemonConfig.buildIgnorePredicate
let ignorePredicate = TreemonConfig.readIgnoreWorktreePatterns () |> TreemonConfig.buildIgnorePredicate
let ignoredPaths = resolveIgnoredPaths ignorePredicate repos
let tasks = buildTaskList { Archived = archivedPaths; Ignored = ignoredPaths } repos
let now = DateTimeOffset.UtcNow
Expand Down
8 changes: 4 additions & 4 deletions src/Server/TreemonConfig.fs
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ let private withGlobalConfig (defaultValue: 'a) (f: JsonElement -> 'a) : 'a =
Log.log "TreemonConfig" $"Failed to read global config: {ex.Message}"
defaultValue

let readIgnoreBranchPatterns () : string list =
let readIgnoreWorktreePatterns () : string list =
withGlobalConfig [] (fun root ->
match root.TryGetProperty("ignoreBranchPatterns") with
match root.TryGetProperty("ignoreWorktreePatterns") with
| true, prop when prop.ValueKind = JsonValueKind.Array ->
prop.EnumerateArray()
|> Seq.choose (fun el ->
Expand All @@ -129,8 +129,8 @@ let buildIgnorePredicate (patterns: string list) : string -> bool =
|> List.choose (fun pattern ->
try Some (Regex($"^(?:{pattern})$", RegexOptions.Compiled))
with :? ArgumentException ->
Log.log "TreemonConfig" $"Invalid ignore branch pattern: '{pattern}'"
Log.log "TreemonConfig" $"Invalid ignore worktree pattern: '{pattern}'"
None)
match regexes with
| [] -> fun _ -> false
| _ -> fun branch -> regexes |> List.exists _.IsMatch(branch)
| _ -> fun value -> regexes |> List.exists _.IsMatch(value)
10 changes: 2 additions & 8 deletions src/Server/WorktreeApi.fs
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,6 @@ let private readCollapsedRepos () : Set<RepoId> =
|> Set.ofSeq
| _ -> Set.empty)

let private readIgnoreBranchPatterns () : string list =
TreemonConfig.readIgnoreBranchPatterns ()

let internal buildIgnorePredicate (patterns: string list) : string -> bool =
TreemonConfig.buildIgnorePredicate patterns

let private writeCollapsedRepos (repos: RepoId list) =
let configPath = globalConfigPath ()
try
Expand Down Expand Up @@ -208,7 +202,7 @@ let getWorktrees
let! activeSessions = SessionManager.getActiveSessions sessionAgent

let activeSessionPaths = activeSessions |> Map.keys |> Set.ofSeq
let ignoreBranch = readIgnoreBranchPatterns () |> buildIgnorePredicate
let ignorePredicate = TreemonConfig.readIgnoreWorktreePatterns () |> TreemonConfig.buildIgnorePredicate

let repos =
state.Repos
Expand All @@ -221,7 +215,7 @@ let getWorktrees

let statuses =
repo.WorktreeList
|> List.filter (fun wt -> wt.Branch |> Option.exists ignoreBranch |> not)
|> List.filter (RefreshScheduler.isWorktreeIgnored ignorePredicate >> not)
|> List.map (fun wt ->
let hasLog = SyncEngine.testFailureLogPath wt.Path |> System.IO.File.Exists
assembleFromState activeSessionPaths archivedBranches hasLog repo wt)
Expand Down
35 changes: 31 additions & 4 deletions src/Tests/SchedulerTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1049,18 +1049,45 @@ type ResolveIgnoredPathsTests() =
Assert.That(ignored |> Set.contains "/r1/main", Is.False, "main should not be ignored")

[<Test>]
member _.``Worktree with no branch is not ignored``() =
member _.``Matches worktrees by folder name``() =
let repos =
[ RepoId "Repo1", makeRepo [ makeWorktree "/r1/main" "main"; makeWorktree "/r1/archive-foo" "feature/abc" ] ]
|> Map.ofList

let predicate = Server.TreemonConfig.buildIgnorePredicate [ "archive-.*" ]
let result = resolveIgnoredPaths predicate repos

let ignored = result |> Map.find (RepoId "Repo1")
Assert.That(ignored |> Set.contains "/r1/archive-foo", Is.True, "archive-foo folder should be ignored")
Assert.That(ignored |> Set.contains "/r1/main", Is.False, "main should not be ignored")

[<Test>]
member _.``Worktree with no branch is ignored when folder matches``() =
let repo =
{ PerRepoState.empty with
WorktreeList = [ { Path = "/r1/detached"; Head = "abc123"; Branch = None } ]
KnownPaths = Set.ofList [ "/r1/detached" ] }
let repos = [ RepoId "Repo1", repo ] |> Map.ofList

let predicate = Server.TreemonConfig.buildIgnorePredicate [ "detached" ]
let result = resolveIgnoredPaths predicate repos

let ignored = result |> Map.find (RepoId "Repo1")
Assert.That(ignored |> Set.contains "/r1/detached", Is.True, "detached folder should be ignored")

[<Test>]
member _.``Worktree with no branch is not ignored when folder does not match``() =
let repo =
{ PerRepoState.empty with
WorktreeList = [ { Path = "/r1/detached"; Head = "abc123"; Branch = None } ]
KnownPaths = Set.ofList [ "/r1/detached" ] }
let repos = [ RepoId "Repo1", repo ] |> Map.ofList

let predicate = Server.TreemonConfig.buildIgnorePredicate [ ".*" ]
let predicate = Server.TreemonConfig.buildIgnorePredicate [ "archive-.*" ]
let result = resolveIgnoredPaths predicate repos

let ignored = result |> Map.find (RepoId "Repo1")
Assert.That(ignored, Is.Empty, "Worktree with no branch should never be ignored")
Assert.That(ignored, Is.Empty, "Worktree with no branch should not be ignored when folder does not match")

[<Test>]
member _.``No patterns produces empty ignored sets``() =
Expand All @@ -1087,7 +1114,7 @@ type BuildIgnorePredicateTests() =
Assert.That(predicate "feature/abc", Is.False)

[<Test>]
member _.``Regex pattern matches branch names``() =
member _.``Regex pattern matches values``() =
let predicate = Server.TreemonConfig.buildIgnorePredicate [ "feature/.*"; "hotfix/.*" ]
Assert.That(predicate "feature/abc", Is.True)
Assert.That(predicate "hotfix/urgent", Is.True)
Expand Down
Loading