wip: "full" finder
This commit is contained in:
@@ -73,9 +73,7 @@ main :: proc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
thread_count := os.get_processor_core_count()
|
thread_count := os.get_processor_core_count()
|
||||||
for dir in paths {
|
walk(paths[:], &results, opts, thread_count)
|
||||||
walk(dir, &results, opts, thread_count)
|
|
||||||
}
|
|
||||||
|
|
||||||
for r in results {
|
for r in results {
|
||||||
fmt.println(r)
|
fmt.println(r)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package findr
|
package findr
|
||||||
|
|
||||||
import "core:os"
|
import "core:os"
|
||||||
|
import "core:sort"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
import "core:sys/linux"
|
import "core:sys/linux"
|
||||||
import "core:testing"
|
import "core:testing"
|
||||||
@@ -218,6 +219,7 @@ test_multiple_search_dirs :: proc(t: ^testing.T) {
|
|||||||
create_git_repo(env, "dir1/repo")
|
create_git_repo(env, "dir1/repo")
|
||||||
create_file(env, "dir1/repo/.gitignore", "*.env\n")
|
create_file(env, "dir1/repo/.gitignore", "*.env\n")
|
||||||
create_file(env, "dir1/repo/a.env")
|
create_file(env, "dir1/repo/a.env")
|
||||||
|
create_file(env, "dir1/repo/normal.txt")
|
||||||
|
|
||||||
create_git_repo(env, "dir2/repo")
|
create_git_repo(env, "dir2/repo")
|
||||||
create_file(env, "dir2/repo/.gitignore", "*.env\n")
|
create_file(env, "dir2/repo/.gitignore", "*.env\n")
|
||||||
@@ -236,9 +238,31 @@ test_multiple_search_dirs :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
opts := WalkOptions{include_hidden = true, ignore_mode = .Ignored}
|
opts := WalkOptions{include_hidden = true, ignore_mode = .Ignored}
|
||||||
thread_count := os.get_processor_core_count()
|
thread_count := os.get_processor_core_count()
|
||||||
walk(dir1, &results, opts, thread_count)
|
walk({dir1, dir2}, &results, opts, thread_count)
|
||||||
walk(dir2, &results, opts, thread_count)
|
|
||||||
testing.expect_value(t, len(results), 2)
|
testing.expect_value(t, len(results), 2)
|
||||||
|
|
||||||
|
actual := make([dynamic]string, 0, len(results))
|
||||||
|
for r in results {
|
||||||
|
stripped := r
|
||||||
|
if strings.has_prefix(stripped, env.temp_dir) {
|
||||||
|
stripped = stripped[len(env.temp_dir):]
|
||||||
|
if len(stripped) > 0 && stripped[0] == '/' {
|
||||||
|
stripped = stripped[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
append(&actual, stripped)
|
||||||
|
}
|
||||||
|
defer delete(actual)
|
||||||
|
|
||||||
|
expected := []string{"dir1/repo/a.env", "dir2/repo/b.env"}
|
||||||
|
|
||||||
|
sort.quick_sort(actual[:])
|
||||||
|
sort.quick_sort(expected[:])
|
||||||
|
|
||||||
|
for i in 0 ..< len(expected) {
|
||||||
|
testing.expect_value(t, actual[i], expected[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -391,6 +415,27 @@ test_fifo_emitted :: proc(t: ^testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// in_repo propagation tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_repo_without_root_gitignore :: proc(t: ^testing.T) {
|
||||||
|
env := create_test_env()
|
||||||
|
defer destroy_test_env(&env)
|
||||||
|
|
||||||
|
create_git_repo(env, "repo")
|
||||||
|
create_dir(env, "repo/sub")
|
||||||
|
create_file(env, "repo/sub/.gitignore", "*.tmp\n")
|
||||||
|
create_file(env, "repo/sub/file.tmp")
|
||||||
|
create_file(env, "repo/sub/file.txt")
|
||||||
|
|
||||||
|
assert_output(t, env, nil,
|
||||||
|
{include_hidden = true, ignore_mode = .Respected},
|
||||||
|
{"repo/", "repo/sub/", "repo/sub/.gitignore", "repo/sub/file.txt"},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// .ignore file support tests (fd respects .ignore in addition to .gitignore)
|
// .ignore file support tests (fd respects .ignore in addition to .gitignore)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ glob_to_regex :: proc(pattern: string, anchored: bool) -> string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.sbprintf(&sb, "(/.*)?$")
|
fmt.sbprintf(&sb, "$")
|
||||||
|
|
||||||
s := strings.to_string(sb)
|
s := strings.to_string(sb)
|
||||||
result, _ := strings.clone(s)
|
result, _ := strings.clone(s)
|
||||||
|
|||||||
@@ -6,98 +6,98 @@ import "core:testing"
|
|||||||
test_glob_simple :: proc(t: ^testing.T) {
|
test_glob_simple :: proc(t: ^testing.T) {
|
||||||
result := glob_to_regex("foo", false)
|
result := glob_to_regex("foo", false)
|
||||||
defer delete(result)
|
defer delete(result)
|
||||||
testing.expect_value(t, result, "(^|/)foo(/.*)?$")
|
testing.expect_value(t, result, "(^|/)foo$")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_glob_anchored :: proc(t: ^testing.T) {
|
test_glob_anchored :: proc(t: ^testing.T) {
|
||||||
result := glob_to_regex("foo", true)
|
result := glob_to_regex("foo", true)
|
||||||
defer delete(result)
|
defer delete(result)
|
||||||
testing.expect_value(t, result, "^foo(/.*)?$")
|
testing.expect_value(t, result, "^foo$")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_glob_star :: proc(t: ^testing.T) {
|
test_glob_star :: proc(t: ^testing.T) {
|
||||||
result := glob_to_regex("*.log", false)
|
result := glob_to_regex("*.log", false)
|
||||||
defer delete(result)
|
defer delete(result)
|
||||||
testing.expect_value(t, result, "(^|/)[^/]*\\.log(/.*)?$")
|
testing.expect_value(t, result, "(^|/)[^/]*\\.log$")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_glob_question :: proc(t: ^testing.T) {
|
test_glob_question :: proc(t: ^testing.T) {
|
||||||
result := glob_to_regex("?.log", false)
|
result := glob_to_regex("?.log", false)
|
||||||
defer delete(result)
|
defer delete(result)
|
||||||
testing.expect_value(t, result, "(^|/)[^/]\\.log(/.*)?$")
|
testing.expect_value(t, result, "(^|/)[^/]\\.log$")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_glob_char_class :: proc(t: ^testing.T) {
|
test_glob_char_class :: proc(t: ^testing.T) {
|
||||||
result := glob_to_regex("[abc].log", false)
|
result := glob_to_regex("[abc].log", false)
|
||||||
defer delete(result)
|
defer delete(result)
|
||||||
testing.expect_value(t, result, "(^|/)[abc]\\.log(/.*)?$")
|
testing.expect_value(t, result, "(^|/)[abc]\\.log$")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_glob_negated_class :: proc(t: ^testing.T) {
|
test_glob_negated_class :: proc(t: ^testing.T) {
|
||||||
result := glob_to_regex("[!abc].log", false)
|
result := glob_to_regex("[!abc].log", false)
|
||||||
defer delete(result)
|
defer delete(result)
|
||||||
testing.expect_value(t, result, "(^|/)[^abc]\\.log(/.*)?$")
|
testing.expect_value(t, result, "(^|/)[^abc]\\.log$")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_glob_dot_escaped :: proc(t: ^testing.T) {
|
test_glob_dot_escaped :: proc(t: ^testing.T) {
|
||||||
result := glob_to_regex(".env", false)
|
result := glob_to_regex(".env", false)
|
||||||
defer delete(result)
|
defer delete(result)
|
||||||
testing.expect_value(t, result, "(^|/)\\.env(/.*)?$")
|
testing.expect_value(t, result, "(^|/)\\.env$")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_glob_globstar_prefix :: proc(t: ^testing.T) {
|
test_glob_globstar_prefix :: proc(t: ^testing.T) {
|
||||||
result := glob_to_regex("**/foo", false)
|
result := glob_to_regex("**/foo", false)
|
||||||
defer delete(result)
|
defer delete(result)
|
||||||
testing.expect_value(t, result, "(^|/)(.*/)?foo(/.*)?$")
|
testing.expect_value(t, result, "(^|/)(.*/)?foo$")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_glob_globstar_suffix :: proc(t: ^testing.T) {
|
test_glob_globstar_suffix :: proc(t: ^testing.T) {
|
||||||
result := glob_to_regex("abc/**", false)
|
result := glob_to_regex("abc/**", false)
|
||||||
defer delete(result)
|
defer delete(result)
|
||||||
testing.expect_value(t, result, "(^|/)abc/.*(/.*)?$")
|
testing.expect_value(t, result, "(^|/)abc/.*$")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_glob_globstar_middle :: proc(t: ^testing.T) {
|
test_glob_globstar_middle :: proc(t: ^testing.T) {
|
||||||
result := glob_to_regex("foo/**/bar", false)
|
result := glob_to_regex("foo/**/bar", false)
|
||||||
defer delete(result)
|
defer delete(result)
|
||||||
testing.expect_value(t, result, "(^|/)foo/(.*/)?bar(/.*)?$")
|
testing.expect_value(t, result, "(^|/)foo/(.*/)?bar$")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_glob_backslash_escape :: proc(t: ^testing.T) {
|
test_glob_backslash_escape :: proc(t: ^testing.T) {
|
||||||
result := glob_to_regex("\\!foo", false)
|
result := glob_to_regex("\\!foo", false)
|
||||||
defer delete(result)
|
defer delete(result)
|
||||||
testing.expect_value(t, result, "(^|/)!foo(/.*)?$")
|
testing.expect_value(t, result, "(^|/)!foo$")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_glob_hash_escaped :: proc(t: ^testing.T) {
|
test_glob_hash_escaped :: proc(t: ^testing.T) {
|
||||||
result := glob_to_regex("#foo", false)
|
result := glob_to_regex("#foo", false)
|
||||||
defer delete(result)
|
defer delete(result)
|
||||||
testing.expect_value(t, result, "(^|/)\\#foo(/.*)?$")
|
testing.expect_value(t, result, "(^|/)\\#foo$")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_glob_hash_in_pattern :: proc(t: ^testing.T) {
|
test_glob_hash_in_pattern :: proc(t: ^testing.T) {
|
||||||
result := glob_to_regex("#*#", false)
|
result := glob_to_regex("#*#", false)
|
||||||
defer delete(result)
|
defer delete(result)
|
||||||
testing.expect_value(t, result, "(^|/)\\#[^/]*\\#(/.*)?$")
|
testing.expect_value(t, result, "(^|/)\\#[^/]*\\#$")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_glob_empty :: proc(t: ^testing.T) {
|
test_glob_empty :: proc(t: ^testing.T) {
|
||||||
result := glob_to_regex("", false)
|
result := glob_to_regex("", false)
|
||||||
defer delete(result)
|
defer delete(result)
|
||||||
testing.expect_value(t, result, "(^|/)(/.*)?$")
|
testing.expect_value(t, result, "(^|/)$")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
@@ -190,6 +190,18 @@ test_is_ignored_globstar :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, is_ignored(&gi, "foo/bar/cache", false), true)
|
testing.expect_value(t, is_ignored(&gi, "foo/bar/cache", false), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_star_negation_subpath :: proc(t: ^testing.T) {
|
||||||
|
gi := parse("*\n!public/\n")
|
||||||
|
defer destroy(&gi)
|
||||||
|
|
||||||
|
// public dir itself is un-ignored
|
||||||
|
testing.expect_value(t, is_ignored(&gi, "public", true), false)
|
||||||
|
// children of public/ should still be ignored by *
|
||||||
|
testing.expect_value(t, is_ignored(&gi, "public/uuid-dir", true), true)
|
||||||
|
testing.expect_value(t, is_ignored(&gi, "public/uuid-dir/file.txt", false), true)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_is_ignored_hash_pattern :: proc(t: ^testing.T) {
|
test_is_ignored_hash_pattern :: proc(t: ^testing.T) {
|
||||||
gi := parse("\\#*\\#\n")
|
gi := parse("\\#*\\#\n")
|
||||||
|
|||||||
@@ -133,9 +133,7 @@ collect_results :: proc(env: TestEnv, args: []string, opts: WalkOptions) -> [dyn
|
|||||||
for a in args {append(&full_args, a)}
|
for a in args {append(&full_args, a)}
|
||||||
|
|
||||||
thread_count := os.get_processor_core_count()
|
thread_count := os.get_processor_core_count()
|
||||||
for dir in full_args {
|
walk(full_args[:], &results, opts, thread_count)
|
||||||
walk(dir, &results, opts, thread_count)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in 0 ..< len(results) {
|
for i in 0 ..< len(results) {
|
||||||
r := results[i]
|
r := results[i]
|
||||||
|
|||||||
21
walker.odin
21
walker.odin
@@ -36,6 +36,7 @@ WorkItem :: struct {
|
|||||||
path: string, // absolute directory path
|
path: string, // absolute directory path
|
||||||
rel: string, // relative path from repo root ("" = root)
|
rel: string, // relative path from repo root ("" = root)
|
||||||
gi_ctx: ^GIContext, // gitignore chain (nil = outside any repo)
|
gi_ctx: ^GIContext, // gitignore chain (nil = outside any repo)
|
||||||
|
in_repo: bool, // true if inside a git repo
|
||||||
}
|
}
|
||||||
|
|
||||||
WalkerPool :: struct {
|
WalkerPool :: struct {
|
||||||
@@ -55,11 +56,13 @@ WalkerPool :: struct {
|
|||||||
contexts_lock: sync.Mutex,
|
contexts_lock: sync.Mutex,
|
||||||
}
|
}
|
||||||
|
|
||||||
walk :: proc(root: string, results: ^[dynamic]string, opts: WalkOptions, thread_count: int) {
|
walk :: proc(roots: []string, results: ^[dynamic]string, opts: WalkOptions, thread_count: int) {
|
||||||
|
if len(roots) == 0 do return
|
||||||
|
|
||||||
pool := new(WalkerPool)
|
pool := new(WalkerPool)
|
||||||
pool.queue = make([dynamic]WorkItem)
|
pool.queue = make([dynamic]WorkItem)
|
||||||
pool.results = results
|
pool.results = results
|
||||||
pool.active = 1
|
pool.active = i64(len(roots))
|
||||||
pool.threads = make([dynamic]^thread.Thread)
|
pool.threads = make([dynamic]^thread.Thread)
|
||||||
pool.all_contexts = make([dynamic]^GIContext)
|
pool.all_contexts = make([dynamic]^GIContext)
|
||||||
pool.opts = opts
|
pool.opts = opts
|
||||||
@@ -86,9 +89,11 @@ walk :: proc(root: string, results: ^[dynamic]string, opts: WalkOptions, thread_
|
|||||||
strings.builder_destroy(&sb)
|
strings.builder_destroy(&sb)
|
||||||
}
|
}
|
||||||
|
|
||||||
root_clone, _ := strings.clone(root)
|
for root in roots {
|
||||||
append(&pool.queue, WorkItem{path = root_clone})
|
root_clone, _ := strings.clone(root)
|
||||||
sync.atomic_sema_post(&pool.queue_sema)
|
append(&pool.queue, WorkItem{path = root_clone})
|
||||||
|
sync.atomic_sema_post(&pool.queue_sema)
|
||||||
|
}
|
||||||
|
|
||||||
for i in 0 ..< thread_count {
|
for i in 0 ..< thread_count {
|
||||||
t := thread.create(walk_worker)
|
t := thread.create(walk_worker)
|
||||||
@@ -181,7 +186,9 @@ process_dir :: proc(pool: ^WalkerPool, item: WorkItem) {
|
|||||||
rel = ""
|
rel = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
gi := load_ignore_patterns(dir_path, has_git || gi_ctx != nil)
|
child_in_repo := has_git || item.in_repo
|
||||||
|
|
||||||
|
gi := load_ignore_patterns(dir_path, child_in_repo)
|
||||||
if gi != nil {
|
if gi != nil {
|
||||||
new_ctx := new(GIContext)
|
new_ctx := new(GIContext)
|
||||||
new_ctx.gi = gi
|
new_ctx.gi = gi
|
||||||
@@ -237,7 +244,7 @@ process_dir :: proc(pool: ^WalkerPool, item: WorkItem) {
|
|||||||
if !ignored {
|
if !ignored {
|
||||||
child_rel, _ := strings.clone(entry_rel)
|
child_rel, _ := strings.clone(entry_rel)
|
||||||
child_path := join_path(dir_path, entry.name)
|
child_path := join_path(dir_path, entry.name)
|
||||||
push_work(pool, WorkItem{path = child_path, rel = child_rel, gi_ctx = gi_ctx})
|
push_work(pool, WorkItem{path = child_path, rel = child_rel, gi_ctx = gi_ctx, in_repo = child_in_repo})
|
||||||
}
|
}
|
||||||
} else if is_nondir {
|
} else if is_nondir {
|
||||||
if should_emit && matches_pattern(pool, entry.name) {
|
if should_emit && matches_pattern(pool, entry.name) {
|
||||||
|
|||||||
Reference in New Issue
Block a user