Skip to content
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

Fix absolute patterns with glob #10121

Merged
merged 12 commits into from
Apr 23, 2024
123 changes: 86 additions & 37 deletions src/glob.zig
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ pub fn GlobWalker_(
/// If the pattern contains "./" or "../"
has_relative_components: bool = false,

end_byte_of_basename_excluding_special_syntax: u32 = 0,
basename_excluding_special_syntax_component_idx: u32 = 0,

patternComponents: ArrayList(Component) = .{},
matchedPaths: ArrayList(BunString) = .{},
i: u32 = 0,
Expand Down Expand Up @@ -201,8 +204,35 @@ pub fn GlobWalker_(

pub fn init(this: *Iterator) !Maybe(void) {
log("Iterator init pattern={s}", .{this.walker.pattern});
var was_absolute = false;
const root_work_item = brk: {
var use_posix = bun.Environment.isPosix;
const is_absolute = if (bun.Environment.isPosix) std.fs.path.isAbsolute(this.walker.pattern) else std.fs.path.isAbsolute(this.walker.pattern) or is_absolute: {
use_posix = true;
break :is_absolute std.fs.path.isAbsolutePosix(this.walker.pattern);
};

if (!is_absolute) break :brk WorkItem.new(this.walker.cwd, 0, .directory);

was_absolute = true;

const component_idx = this.walker.basename_excluding_special_syntax_component_idx;
var path_without_special_syntax = this.walker.pattern[0..this.walker.end_byte_of_basename_excluding_special_syntax];
if (this.walker.pattern.len == this.walker.end_byte_of_basename_excluding_special_syntax) {
path_without_special_syntax = (if (use_posix) std.fs.path.dirnamePosix(path_without_special_syntax) else std.fs.path.dirnameWindows(path_without_special_syntax)) orelse p: {
break :p path_without_special_syntax;
};
}

break :brk WorkItem.new(
path_without_special_syntax,
component_idx,
.directory,
);
};

var path_buf: *[bun.MAX_PATH_BYTES]u8 = &this.walker.pathBuf;
const root_path = this.walker.cwd;
const root_path = root_work_item.path;
@memcpy(path_buf[0..root_path.len], root_path[0..root_path.len]);
path_buf[root_path.len] = 0;
const root_path_z = path_buf[0..root_path.len :0];
Expand All @@ -217,8 +247,13 @@ pub fn GlobWalker_(

this.cwd_fd = cwd_fd;

const root_work_item = WorkItem.new(this.walker.cwd, 0, .directory);
switch (try this.transitionToDirIterState(root_work_item, true)) {
switch (if (was_absolute) try this.transitionToDirIterState(
root_work_item,
false,
) else try this.transitionToDirIterState(
root_work_item,
true,
)) {
.err => |err| return .{ .err = err },
else => {},
}
Expand Down Expand Up @@ -609,6 +644,13 @@ pub fn GlobWalker_(
Dot,
/// ../
DotBack,

fn isSpecialSyntax(this: SyntaxHint) bool {
return switch (this) {
.Literal => false,
else => true,
};
}
};
};

Expand Down Expand Up @@ -646,9 +688,6 @@ pub fn GlobWalker_(
const ptr = @intFromPtr(this);
log("GlobWalker(0x{x}) components:", .{ptr});
for (components.items) |cmp| {
if (cmp.syntax_hint == .None) {
continue;
}
switch (cmp.syntax_hint) {
.Single => log(" *", .{}),
.Double => log(" **", .{}),
Expand All @@ -673,13 +712,17 @@ pub fn GlobWalker_(
only_files: bool,
) !Maybe(void) {
var patternComponents = ArrayList(Component){};
this.basename_excluding_special_syntax_component_idx = 0;
this.end_byte_of_basename_excluding_special_syntax = @intCast(pattern.len);
try GlobWalker.buildPatternComponents(
arena,
&patternComponents,
pattern,
&this.cp_len,
&this.pattern_codepoints,
&this.has_relative_components,
&this.end_byte_of_basename_excluding_special_syntax,
&this.basename_excluding_special_syntax_component_idx,
);

this.cwd = cwd;
Expand Down Expand Up @@ -999,19 +1042,6 @@ pub fn GlobWalker_(
return name;
}

fn appendMatchedPath(
this: *GlobWalker,
entry_name: []const u8,
dir_name: [:0]const u8,
) !void {
const subdir_parts: []const []const u8 = &[_][]const u8{
dir_name[0..dir_name.len],
entry_name,
};
const name = try this.join(subdir_parts);
try this.matchedPaths.append(this.arena.allocator(), BunString.fromBytes(name));
}

fn appendMatchedPathSymlink(this: *GlobWalker, symlink_full_path: []const u8) !void {
const name = try this.arena.allocator().dupe(u8, symlink_full_path);
try this.matchedPaths.append(this.arena.allocator(), BunString.fromBytes(name));
Expand Down Expand Up @@ -1073,23 +1103,21 @@ pub fn GlobWalker_(
return false;
}

fn addComponent(
allocator: Allocator,
fn makeComponent(
pattern: []const u8,
patternComponents: *ArrayList(Component),
start_cp: u32,
end_cp: u32,
start_byte: u32,
end_byte: u32,
has_relative_patterns: *bool,
) !void {
) ?Component {
var component: Component = .{
.start = start_byte,
.len = end_byte - start_byte,
.start_cp = start_cp,
.end_cp = end_cp,
};
if (component.len == 0) return;
if (component.len == 0) return null;

out: {
if (component.len == 1 and pattern[component.start] == '.') {
Expand Down Expand Up @@ -1163,7 +1191,7 @@ pub fn GlobWalker_(
component.is_ascii = true;
}

try patternComponents.append(allocator, component);
return component;
}

fn buildPatternComponents(
Expand All @@ -1173,6 +1201,8 @@ pub fn GlobWalker_(
out_cp_len: *u32,
out_pattern_cp: *[]u32,
has_relative_patterns: *bool,
end_byte_of_basename_excluding_special_syntax: *u32,
basename_excluding_special_syntax_component_idx: *u32,
) !void {
var start_cp: u32 = 0;
var start_byte: u32 = 0;
Expand All @@ -1182,23 +1212,29 @@ pub fn GlobWalker_(

var cp_len: u32 = 0;
var prevIsBackslash = false;
var saw_special = false;
while (iter.next(&cursor)) : (cp_len += 1) {
const c = cursor.c;

switch (c) {
'\\' => {
if (comptime isWindows) {
const end_cp = cp_len;
try addComponent(
arena.allocator(),
if (makeComponent(
pattern,
patternComponents,
start_cp,
end_cp,
start_byte,
cursor.i,
has_relative_patterns,
);
)) |component| {
if (!saw_special and component.syntax_hint.isSpecialSyntax()) {
saw_special = true;
basename_excluding_special_syntax_component_idx.* = @intCast(patternComponents.items.len);
end_byte_of_basename_excluding_special_syntax.* = cursor.i + cursor.width;
}
try patternComponents.append(arena.allocator(), component);
}
start_cp = cp_len + 1;
start_byte = cursor.i + cursor.width;
continue;
Expand All @@ -1219,16 +1255,21 @@ pub fn GlobWalker_(
end_cp += 1;
end_byte += cursor.width;
}
try addComponent(
arena.allocator(),
if (makeComponent(
pattern,
patternComponents,
start_cp,
end_cp,
start_byte,
end_byte,
has_relative_patterns,
);
)) |component| {
if (!saw_special and component.syntax_hint.isSpecialSyntax()) {
saw_special = true;
basename_excluding_special_syntax_component_idx.* = @intCast(patternComponents.items.len);
end_byte_of_basename_excluding_special_syntax.* = cursor.i;
}
try patternComponents.append(arena.allocator(), component);
}
start_cp = cp_len + 1;
start_byte = cursor.i + cursor.width;
},
Expand All @@ -1247,16 +1288,24 @@ pub fn GlobWalker_(
out_pattern_cp.* = codepoints;

const end_cp = cp_len;
try addComponent(
arena.allocator(),
if (makeComponent(
pattern,
patternComponents,
start_cp,
end_cp,
start_byte,
@intCast(pattern.len),
has_relative_patterns,
);
)) |component| {
if (!saw_special and component.syntax_hint.isSpecialSyntax()) {
saw_special = true;
basename_excluding_special_syntax_component_idx.* = @intCast(patternComponents.items.len);
end_byte_of_basename_excluding_special_syntax.* = cursor.i;
}
try patternComponents.append(arena.allocator(), component);
}

if (!saw_special)
end_byte_of_basename_excluding_special_syntax.* = @intCast(patternComponents.items.len);
}
};
}
Expand Down
10 changes: 10 additions & 0 deletions test/js/bun/glob/scan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import fg from "fast-glob";
import * as path from "path";
import { tempFixturesDir, createTempDirectoryWithBrokenSymlinks, prepareEntries } from "./util";
import { tempDirWithFiles } from "harness";
import { TestBuilder } from "../shell/test_builder";

let origAggressiveGC = Bun.unsafe.gcAggressionLevel();
let tempBrokenSymlinksDir: string;
Expand Down Expand Up @@ -445,6 +446,15 @@ test("glob.scan('.')", async () => {
expect(entries).toContain("README.md");
});

test("absolute path pattern should ignore cwd and start at the proper path", async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we get more tests for this? is there a way to run all the tests that run against relative paths to also run against absolute paths?

const tmpdir = TestBuilder.tmpdir();
const expected = [`${tmpdir}/bunx-foo`, `${tmpdir}/bunx-bar`, `${tmpdir}/bunx-baz`];
await Bun.$`touch ${expected[0]}; touch ${expected[1]}; touch ${expected[2]}`;
const glob = new Glob(`${path.join(tmpdir, "bunx-*")}`);
const entries = await Array.fromAsync(glob.scan());
expect(entries.sort()).toEqual(expected.sort());
});

describe("glob.scan wildcard fast path", async () => {
test("works", async () => {
const tempdir = tempDirWithFiles("glob-scan-wildcard-fast-path", {
Expand Down
Loading