// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie // SPDX-License-Identifier: MIT //! Pathspecs on the command line: read as git reads them, with the //! `GIT_*_PATHSPECS` settings from the environment, the current directory //! as the prefix, and git's words when one cannot be read. const std = @import("std"); const Allocator = std.mem.Allocator; const Io = std.Io; const git = @import("git"); const Pathspec = git.Pathspec; const tool = @import("zig_git.zig"); const Context = tool.Context; /// Reads `args` as a pathspec for `repo`, run from its prefix. `command` /// names the command in a refusal. pub fn parse(ctx: Context, repo: *const git.Repository, args: []const []const u8, command: []const u8, stderr: *Io.Writer) !Pathspec { var diag: git.Diagnostics = .{}; const env = ctx.env; const settings = Pathspec.settingsFromEnvironment( env.get("GIT_LITERAL_PATHSPECS"), env.get("GIT_GLOB_PATHSPECS"), env.get("GIT_NOGLOB_PATHSPECS"), env.get("GIT_ICASE_PATHSPECS"), &diag, ) catch return tool.fatalMessage(stderr, "{s}", .{diag.detail()}); const work_tree = repo.location.work_tree orelse repo.location.work_dir; return Pathspec.parse(ctx.gpa, args, .{ .prefix = repo.location.prefix orelse "", .work_tree = work_tree, .settings = settings, }, &diag) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.UnsupportedMagic => { stderr.print("zig-git: {s}: the '{s}' pathspec magic is not supported\n", .{ command, diag.detail() }) catch {}; return error.Fatal; }, else => return tool.fatalMessage(stderr, "{s}", .{diag.detail()}), }; } /// Whether git takes `arg`, given without `--` where no revision could be /// read, for a path: one with a wildcard or in the long magic form, which /// need not exist; `:/`, from the top, and `:!` or /// `:^`, from the current directory, which must; or a path that is /// there. pub fn takenForPath(ctx: Context, repo: *const git.Repository, arg: []const u8) !bool { if (std.mem.findAny(u8, arg, "*?[") != null) return true; if (std.mem.startsWith(u8, arg, ":(")) return true; if (std.mem.startsWith(u8, arg, ":/")) { const rest = arg[2..]; if (rest.len == 0) return true; const top = repo.location.work_tree orelse return false; return exists(ctx, top, rest); } var path = arg; if (std.mem.startsWith(u8, arg, ":!") or std.mem.startsWith(u8, arg, ":^")) path = arg[2..]; if (std.fs.path.isAbsolute(path)) return exists(ctx, "/", path); return exists(ctx, ctx.cwd, path); } fn exists(ctx: Context, dir: []const u8, path: []const u8) !bool { const full = try std.fs.path.join(ctx.gpa, &.{ dir, path }); defer ctx.gpa.free(full); _ = Io.Dir.cwd().statFile(ctx.io, full, .{ .follow_symlinks = false }) catch return false; return true; }