-
Notifications
You must be signed in to change notification settings - Fork 8
Sync → Ignore patterns #70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
kwame-Owusu
wants to merge
13
commits into
8thpark:main
Choose a base branch
from
kwame-Owusu:feat/ignore-patterns
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
044b010
feat: add ignore patterns for files and folders we do not want to sync
kwame-Owusu e5caed9
feat: add margin on top of heading
kwame-Owusu 52a37fa
chore: biome linting and formatting
kwame-Owusu 2308afa
refactor: pre compile the regex and use that in shouldIgnore call
kwame-Owusu 55ea66c
fix: fix desc string
kwame-Owusu 27d0014
Merge branch 'main' into feat/ignore-patterns
kwame-Owusu 13e0160
Merge branch 'main' into feat/ignore-patterns
revett 40e6d24
Merge branch 'main' into feat/ignore-patterns
kwame-Owusu 3312dd1
chore: resolve merge conflicts
kwame-Owusu 32a8dd5
fix: add ignore settings to createObsidianReader arguments in sync itest
kwame-Owusu 2bd950f
chore: biome formatting
kwame-Owusu 673f26e
merge main into feat/ignore-patterns and fix merge conflicts
kwame-Owusu f4b102a
Merge branch 'main' into feat/ignore-patterns
kwame-Owusu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,181 @@ | ||
| import assert from "node:assert/strict"; | ||
| import { test } from "node:test"; | ||
| import { hasLocalPrefix, matchesGlob, shouldIgnore } from "./ignore.ts"; | ||
|
|
||
| const hasLocalPrefixCases: { name: string; path: string; want: boolean }[] = [ | ||
| { name: "top-level local_ folder", path: "local_notes/note.md", want: true }, | ||
| { name: "nested local_ folder", path: "a/b/local_notes/note.md", want: true }, | ||
| { name: "local_ file at root", path: "local_draft.md", want: true }, | ||
| { name: "local_ file nested", path: "docs/local_draft.md", want: true }, | ||
| { name: "no match", path: "notes/note.md", want: false }, | ||
| { name: "partial prefix match", path: "local_not/stuff.md", want: true }, | ||
| { | ||
| name: "local_ in middle of segment", | ||
| path: "not_local_/file.md", | ||
| want: false, | ||
| }, | ||
| { name: "empty path", path: "", want: false }, | ||
| ]; | ||
|
|
||
| for (const { name, path, want } of hasLocalPrefixCases) { | ||
| test(`hasLocalPrefix: ${name}`, () => { | ||
| assert.strictEqual(hasLocalPrefix(path), want); | ||
| }); | ||
| } | ||
|
|
||
| const matchesGlobCases: { | ||
| name: string; | ||
| path: string; | ||
| pattern: string; | ||
| want: boolean; | ||
| }[] = [ | ||
| { | ||
| name: "exact match", | ||
| path: "notes/note.md", | ||
| pattern: "notes/note.md", | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "exact mismatch", | ||
| path: "notes/note.md", | ||
| pattern: "other/note.md", | ||
| want: false, | ||
| }, | ||
| { | ||
| name: "star matches within segment", | ||
| path: "docs/file.md", | ||
| pattern: "docs/*.md", | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "star does not cross segments", | ||
| path: "a/b/file.md", | ||
| pattern: "a/*.md", | ||
| want: false, | ||
| }, | ||
| { | ||
| name: "star matches any extension", | ||
| path: "docs/file.txt", | ||
| pattern: "docs/*", | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "double star at start", | ||
| path: "a/b/c.md", | ||
| pattern: "**/*.md", | ||
| want: true, | ||
| }, | ||
| { name: "double star at end", path: "a/b/c.md", pattern: "a/**", want: true }, | ||
| { | ||
| name: "double star matches empty segments", | ||
| path: "a.md", | ||
| pattern: "**/a.md", | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "double star in middle", | ||
| path: "a/b/c/d.md", | ||
| pattern: "a/**/d.md", | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "question mark matches one char", | ||
| path: "file1.md", | ||
| pattern: "file?.md", | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "question mark does not match slash", | ||
| path: "a/b", | ||
| pattern: "a?b", | ||
| want: false, | ||
| }, | ||
| { | ||
| name: "question mark fails on two chars", | ||
| path: "file12.md", | ||
| pattern: "file?.md", | ||
| want: false, | ||
| }, | ||
| { | ||
| name: "leading slash ignored", | ||
| path: "notes/note.md", | ||
| pattern: "/notes/note.md", | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "trailing slash pattern", | ||
| path: "docs", | ||
| pattern: "docs/", | ||
| want: false, | ||
| }, | ||
| { | ||
| name: "complex pattern", | ||
| path: "src/components/Button.tsx", | ||
| pattern: "src/**/*.tsx", | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "no match", | ||
| path: "notes/note.md", | ||
| pattern: "docs/*.md", | ||
| want: false, | ||
| }, | ||
| { name: "empty pattern", path: "notes/note.md", pattern: "", want: false }, | ||
| { name: "empty path with star pattern", path: "", pattern: "*", want: true }, | ||
| ]; | ||
|
|
||
| for (const { name, path, pattern, want } of matchesGlobCases) { | ||
| test(`matchesGlob: ${name}`, () => { | ||
| assert.strictEqual(matchesGlob(path, pattern), want); | ||
| }); | ||
| } | ||
|
|
||
| const shouldIgnoreCases: { | ||
| name: string; | ||
| path: string; | ||
| patterns: string[]; | ||
| want: boolean; | ||
| }[] = [ | ||
| { | ||
| name: "local_ prefix ignored", | ||
| path: "local_notes/note.md", | ||
| patterns: [], | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "glob pattern ignored", | ||
| path: "private/secret.md", | ||
| patterns: ["private/**"], | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "no match with patterns", | ||
| path: "notes/note.md", | ||
| patterns: ["private/**"], | ||
| want: false, | ||
| }, | ||
| { | ||
| name: "empty patterns no match", | ||
| path: "notes/note.md", | ||
| patterns: [], | ||
| want: false, | ||
| }, | ||
| { | ||
| name: "multiple patterns second match", | ||
| path: "temp/file.md", | ||
| patterns: ["private/**", "temp/*"], | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "local_ takes precedence", | ||
| path: "local_x/file.md", | ||
| patterns: [], | ||
| want: true, | ||
| }, | ||
| ]; | ||
|
|
||
| for (const { name, path, patterns, want } of shouldIgnoreCases) { | ||
| test(`shouldIgnore: ${name}`, () => { | ||
| assert.strictEqual(shouldIgnore(path, patterns), want); | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| // hasLocalPrefix reports whether any segment of path starts with "local_", the built-in | ||
| // convention for vault content that should never be synced. | ||
| export function hasLocalPrefix(path: string): boolean { | ||
| const segments = path.split("/"); | ||
| for (const segment of segments) { | ||
| if (segment.startsWith("local_")) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| // globToRegex converts a simplified glob pattern to a regular expression. Supported syntax: | ||
| // * — matches any characters within one path segment (stops at /) | ||
| // ** — matches zero or more path segments | ||
| // ? — matches exactly one character (not /) | ||
| // All other characters match literally. A leading / in the pattern is stripped. | ||
| function globToRegex(pattern: string): RegExp { | ||
| const pat = pattern.startsWith("/") ? pattern.slice(1) : pattern; | ||
| const body = pat.replace(/\*\*\/|\*\*|\*|\?|[.+^${}()|[\]\\]/g, (token) => { | ||
| switch (token) { | ||
| case "**/": | ||
| return "(.*/)?"; | ||
| case "**": | ||
| return ".*"; | ||
| case "*": | ||
| return "[^/]*"; | ||
| case "?": | ||
| return "[^/]"; | ||
| default: | ||
| return `\\${token}`; // escaped regex metachar | ||
| } | ||
| }); | ||
| return new RegExp(`^${body}$`); | ||
| } | ||
|
|
||
| // matchesGlob reports whether path matches a simplified glob pattern. | ||
| export function matchesGlob(path: string, pattern: string): boolean { | ||
| return globToRegex(pattern).test(path); | ||
| } | ||
|
|
||
| // compilePatterns converts a list of glob patterns to regular expressions once | ||
| // so the compiled forms can be reused across many shouldIgnoreCompiled calls. | ||
| export function compilePatterns(patterns: string[]): RegExp[] { | ||
| const compiled: RegExp[] = []; | ||
| for (const pattern of patterns) { | ||
| compiled.push(globToRegex(pattern)); | ||
| } | ||
| return compiled; | ||
| } | ||
|
|
||
| // shouldIgnoreCompiled is like shouldIgnore but accepts pre-compiled patterns | ||
| // for callers that filter many paths against a fixed pattern set. | ||
| export function shouldIgnoreCompiled(path: string, compiled: RegExp[]): boolean { | ||
| if (hasLocalPrefix(path)) { | ||
| return true; | ||
| } | ||
| for (const re of compiled) { | ||
| if (re.test(path)) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| // shouldIgnore reports whether path should be excluded from sync, checking the built-in local_ | ||
| // prefix convention first, then any user-configured glob patterns. | ||
| export function shouldIgnore(path: string, patterns: string[]): boolean { | ||
| return shouldIgnoreCompiled(path, compilePatterns(patterns)); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.