mirror of
https://github.com/oven-sh/bun
synced 2026-02-11 03:18:53 +00:00
722 lines
26 KiB
Zig
722 lines
26 KiB
Zig
threadlocal var final_path_buf: bun.PathBuffer = undefined;
|
|
threadlocal var ssh_path_buf: bun.PathBuffer = undefined;
|
|
threadlocal var folder_name_buf: bun.PathBuffer = undefined;
|
|
threadlocal var json_path_buf: bun.PathBuffer = undefined;
|
|
|
|
const SloppyGlobalGitConfig = struct {
|
|
has_askpass: bool = false,
|
|
has_ssh_command: bool = false,
|
|
|
|
var holder: SloppyGlobalGitConfig = .{};
|
|
var load_and_parse_once = std.once(loadAndParse);
|
|
|
|
pub fn get() SloppyGlobalGitConfig {
|
|
load_and_parse_once.call();
|
|
return holder;
|
|
}
|
|
|
|
pub fn loadAndParse() void {
|
|
const home_dir = bun.env_var.HOME.get() orelse return;
|
|
|
|
var config_file_path_buf: bun.PathBuffer = undefined;
|
|
const config_file_path = bun.path.joinAbsStringBufZ(home_dir, &config_file_path_buf, &.{".gitconfig"}, .auto);
|
|
var stack_fallback = std.heap.stackFallback(4096, bun.default_allocator);
|
|
const allocator = stack_fallback.get();
|
|
const source = File.toSource(config_file_path, allocator, .{ .convert_bom = true }).unwrap() catch {
|
|
return;
|
|
};
|
|
defer allocator.free(source.contents);
|
|
|
|
var remaining = bun.strings.split(source.contents, "\n");
|
|
var found_askpass = false;
|
|
var found_ssh_command = false;
|
|
var @"[core]" = false;
|
|
while (remaining.next()) |line_| {
|
|
if (found_askpass and found_ssh_command) break;
|
|
|
|
const line = strings.trim(line_, "\t \r");
|
|
|
|
if (line.len == 0) continue;
|
|
// skip comments
|
|
if (line[0] == '#') continue;
|
|
|
|
if (line[0] == '[') {
|
|
if (strings.indexOfChar(line, ']')) |end_bracket| {
|
|
if (strings.eqlComptime(line[0 .. end_bracket + 1], "[core]")) {
|
|
@"[core]" = true;
|
|
continue;
|
|
}
|
|
}
|
|
@"[core]" = false;
|
|
continue;
|
|
}
|
|
|
|
if (@"[core]") {
|
|
if (!found_askpass) {
|
|
if (line.len > "askpass".len and strings.eqlCaseInsensitiveASCIIIgnoreLength(line[0.."askpass".len], "askpass") and switch (line["askpass".len]) {
|
|
' ', '\t', '=' => true,
|
|
else => false,
|
|
}) {
|
|
found_askpass = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!found_ssh_command) {
|
|
if (line.len > "sshCommand".len and strings.eqlCaseInsensitiveASCIIIgnoreLength(line[0.."sshCommand".len], "sshCommand") and switch (line["sshCommand".len]) {
|
|
' ', '\t', '=' => true,
|
|
else => false,
|
|
}) {
|
|
found_ssh_command = true;
|
|
}
|
|
}
|
|
} else {
|
|
if (!found_askpass) {
|
|
if (line.len > "core.askpass".len and strings.eqlCaseInsensitiveASCIIIgnoreLength(line[0.."core.askpass".len], "core.askpass") and switch (line["core.askpass".len]) {
|
|
' ', '\t', '=' => true,
|
|
else => false,
|
|
}) {
|
|
found_askpass = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!found_ssh_command) {
|
|
if (line.len > "core.sshCommand".len and strings.eqlCaseInsensitiveASCIIIgnoreLength(line[0.."core.sshCommand".len], "core.sshCommand") and switch (line["core.sshCommand".len]) {
|
|
' ', '\t', '=' => true,
|
|
else => false,
|
|
}) {
|
|
found_ssh_command = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
holder = .{
|
|
.has_askpass = found_askpass,
|
|
.has_ssh_command = found_ssh_command,
|
|
};
|
|
}
|
|
};
|
|
|
|
pub const Repository = extern struct {
|
|
owner: String = .{},
|
|
repo: String = .{},
|
|
committish: String = .{},
|
|
resolved: String = .{},
|
|
package_name: String = .{},
|
|
|
|
pub var shared_env: struct {
|
|
env: ?DotEnv.Map = null,
|
|
pub fn get(this: *@This(), allocator: std.mem.Allocator, other: *DotEnv.Loader) DotEnv.Map {
|
|
return this.env orelse brk: {
|
|
// Note: currently if the user sets this to some value that causes
|
|
// a prompt for a password, the stdout of the prompt will be masked
|
|
// by further output of the rest of the install process.
|
|
// A value can still be entered, but we need to find a workaround
|
|
// so the user can see what is being prompted. By default the settings
|
|
// below will cause no prompt and throw instead.
|
|
var cloned = bun.handleOom(other.map.cloneWithAllocator(allocator));
|
|
|
|
if (cloned.get("GIT_ASKPASS") == null) {
|
|
const config = SloppyGlobalGitConfig.get();
|
|
if (!config.has_askpass) {
|
|
bun.handleOom(cloned.put("GIT_ASKPASS", "echo"));
|
|
}
|
|
}
|
|
|
|
if (cloned.get("GIT_SSH_COMMAND") == null) {
|
|
const config = SloppyGlobalGitConfig.get();
|
|
if (!config.has_ssh_command) {
|
|
bun.handleOom(cloned.put("GIT_SSH_COMMAND", "ssh -oStrictHostKeyChecking=accept-new"));
|
|
}
|
|
}
|
|
|
|
this.env = cloned;
|
|
break :brk this.env.?;
|
|
};
|
|
}
|
|
} = .{};
|
|
|
|
pub const Hosts = bun.ComptimeStringMap(string, .{
|
|
.{ "bitbucket", ".org" },
|
|
.{ "github", ".com" },
|
|
.{ "gitlab", ".com" },
|
|
});
|
|
|
|
pub fn parseAppendGit(input: string, buf: *String.Buf) OOM!Repository {
|
|
var remain = input;
|
|
if (strings.hasPrefixComptime(remain, "git+")) {
|
|
remain = remain["git+".len..];
|
|
}
|
|
if (strings.lastIndexOfChar(remain, '#')) |hash| {
|
|
return .{
|
|
.repo = try buf.append(remain[0..hash]),
|
|
.committish = try buf.append(remain[hash + 1 ..]),
|
|
};
|
|
}
|
|
return .{
|
|
.repo = try buf.append(remain),
|
|
};
|
|
}
|
|
|
|
pub fn parseAppendGithub(input: string, buf: *String.Buf) OOM!Repository {
|
|
var remain = input;
|
|
if (strings.hasPrefixComptime(remain, "github:")) {
|
|
remain = remain["github:".len..];
|
|
}
|
|
var hash: usize = 0;
|
|
var slash: usize = 0;
|
|
for (remain, 0..) |c, i| {
|
|
switch (c) {
|
|
'/' => slash = i,
|
|
'#' => hash = i,
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
const repo = if (hash == 0) remain[slash + 1 ..] else remain[slash + 1 .. hash];
|
|
|
|
var result: Repository = .{
|
|
.owner = try buf.append(remain[0..slash]),
|
|
.repo = try buf.append(repo),
|
|
};
|
|
|
|
if (hash != 0) {
|
|
result.committish = try buf.append(remain[hash + 1 ..]);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
pub fn createDependencyNameFromVersionLiteral(
|
|
allocator: std.mem.Allocator,
|
|
repository: *const Repository,
|
|
lockfile: *Install.Lockfile,
|
|
dep_id: Install.DependencyID,
|
|
) []u8 {
|
|
const buf = lockfile.buffers.string_bytes.items;
|
|
const dep = lockfile.buffers.dependencies.items[dep_id];
|
|
const repo_name = repository.repo;
|
|
const repo_name_str = lockfile.str(&repo_name);
|
|
|
|
const name = brk: {
|
|
var remain = repo_name_str;
|
|
|
|
if (strings.indexOfChar(remain, '#')) |hash_index| {
|
|
remain = remain[0..hash_index];
|
|
}
|
|
|
|
if (remain.len == 0) break :brk remain;
|
|
|
|
if (strings.lastIndexOfChar(remain, '/')) |slash_index| {
|
|
remain = remain[slash_index + 1 ..];
|
|
}
|
|
|
|
break :brk remain;
|
|
};
|
|
|
|
if (name.len == 0) {
|
|
const version_literal = dep.version.literal.slice(buf);
|
|
const name_buf = bun.handleOom(allocator.alloc(u8, bun.sha.EVP.SHA1.digest));
|
|
var sha1 = bun.sha.SHA1.init();
|
|
defer sha1.deinit();
|
|
sha1.update(version_literal);
|
|
sha1.final(name_buf[0..bun.sha.SHA1.digest]);
|
|
return name_buf[0..bun.sha.SHA1.digest];
|
|
}
|
|
|
|
return bun.handleOom(allocator.dupe(u8, name));
|
|
}
|
|
|
|
pub fn order(lhs: *const Repository, rhs: *const Repository, lhs_buf: []const u8, rhs_buf: []const u8) std.math.Order {
|
|
const owner_order = lhs.owner.order(&rhs.owner, lhs_buf, rhs_buf);
|
|
if (owner_order != .eq) return owner_order;
|
|
const repo_order = lhs.repo.order(&rhs.repo, lhs_buf, rhs_buf);
|
|
if (repo_order != .eq) return repo_order;
|
|
|
|
return lhs.committish.order(&rhs.committish, lhs_buf, rhs_buf);
|
|
}
|
|
|
|
pub fn count(this: *const Repository, buf: []const u8, comptime StringBuilder: type, builder: StringBuilder) void {
|
|
builder.count(this.owner.slice(buf));
|
|
builder.count(this.repo.slice(buf));
|
|
builder.count(this.committish.slice(buf));
|
|
builder.count(this.resolved.slice(buf));
|
|
builder.count(this.package_name.slice(buf));
|
|
}
|
|
|
|
pub fn clone(this: *const Repository, buf: []const u8, comptime StringBuilder: type, builder: StringBuilder) Repository {
|
|
return .{
|
|
.owner = builder.append(String, this.owner.slice(buf)),
|
|
.repo = builder.append(String, this.repo.slice(buf)),
|
|
.committish = builder.append(String, this.committish.slice(buf)),
|
|
.resolved = builder.append(String, this.resolved.slice(buf)),
|
|
.package_name = builder.append(String, this.package_name.slice(buf)),
|
|
};
|
|
}
|
|
|
|
pub fn eql(lhs: *const Repository, rhs: *const Repository, lhs_buf: []const u8, rhs_buf: []const u8) bool {
|
|
if (!lhs.owner.eql(rhs.owner, lhs_buf, rhs_buf)) return false;
|
|
if (!lhs.repo.eql(rhs.repo, lhs_buf, rhs_buf)) return false;
|
|
if (lhs.resolved.isEmpty() or rhs.resolved.isEmpty()) return lhs.committish.eql(rhs.committish, lhs_buf, rhs_buf);
|
|
return lhs.resolved.eql(rhs.resolved, lhs_buf, rhs_buf);
|
|
}
|
|
|
|
pub fn formatAs(this: *const Repository, label: string, buf: []const u8, writer: *std.Io.Writer) std.Io.Writer.Error!void {
|
|
const formatter = Formatter{ .label = label, .repository = this, .buf = buf };
|
|
return try formatter.format(writer);
|
|
}
|
|
|
|
pub fn fmtStorePath(this: *const Repository, label: string, string_buf: string) StorePathFormatter {
|
|
return .{
|
|
.repo = this,
|
|
.label = label,
|
|
.string_buf = string_buf,
|
|
};
|
|
}
|
|
|
|
pub const StorePathFormatter = struct {
|
|
repo: *const Repository,
|
|
label: string,
|
|
string_buf: string,
|
|
|
|
pub fn format(this: StorePathFormatter, writer: *std.Io.Writer) std.Io.Writer.Error!void {
|
|
try writer.print("{f}", .{Install.fmtStorePath(this.label)});
|
|
|
|
if (!this.repo.owner.isEmpty()) {
|
|
try writer.print("{f}", .{this.repo.owner.fmtStorePath(this.string_buf)});
|
|
// try writer.writeByte(if (this.opts.replace_slashes) '+' else '/');
|
|
try writer.writeByte('+');
|
|
} else if (Dependency.isSCPLikePath(this.repo.repo.slice(this.string_buf))) {
|
|
// try writer.print("ssh:{s}", .{if (this.opts.replace_slashes) "++" else "//"});
|
|
try writer.writeAll("ssh++");
|
|
}
|
|
|
|
try writer.print("{f}", .{this.repo.repo.fmtStorePath(this.string_buf)});
|
|
|
|
if (!this.repo.resolved.isEmpty()) {
|
|
try writer.writeByte('+'); // this would be '#' but it's not valid on windows
|
|
var resolved = this.repo.resolved.slice(this.string_buf);
|
|
if (strings.lastIndexOfChar(resolved, '-')) |i| {
|
|
resolved = resolved[i + 1 ..];
|
|
}
|
|
try writer.print("{f}", .{Install.fmtStorePath(resolved)});
|
|
} else if (!this.repo.committish.isEmpty()) {
|
|
try writer.writeByte('+'); // this would be '#' but it's not valid on windows
|
|
try writer.print("{f}", .{this.repo.committish.fmtStorePath(this.string_buf)});
|
|
}
|
|
}
|
|
};
|
|
|
|
pub fn fmt(this: *const Repository, label: string, buf: []const u8) Formatter {
|
|
return .{
|
|
.repository = this,
|
|
.buf = buf,
|
|
.label = label,
|
|
};
|
|
}
|
|
|
|
pub const Formatter = struct {
|
|
label: []const u8 = "",
|
|
buf: []const u8,
|
|
repository: *const Repository,
|
|
pub fn format(formatter: Formatter, writer: *std.Io.Writer) std.Io.Writer.Error!void {
|
|
if (comptime Environment.allow_assert) bun.assert(formatter.label.len > 0);
|
|
try writer.writeAll(formatter.label);
|
|
|
|
const repo = formatter.repository.repo.slice(formatter.buf);
|
|
if (!formatter.repository.owner.isEmpty()) {
|
|
try writer.writeAll(formatter.repository.owner.slice(formatter.buf));
|
|
try writer.writeAll("/");
|
|
} else if (Dependency.isSCPLikePath(repo)) {
|
|
try writer.writeAll("ssh://");
|
|
}
|
|
try writer.writeAll(repo);
|
|
|
|
if (!formatter.repository.resolved.isEmpty()) {
|
|
try writer.writeAll("#");
|
|
var resolved = formatter.repository.resolved.slice(formatter.buf);
|
|
if (strings.lastIndexOfChar(resolved, '-')) |i| {
|
|
resolved = resolved[i + 1 ..];
|
|
}
|
|
try writer.writeAll(resolved);
|
|
} else if (!formatter.repository.committish.isEmpty()) {
|
|
try writer.writeAll("#");
|
|
try writer.writeAll(formatter.repository.committish.slice(formatter.buf));
|
|
}
|
|
}
|
|
};
|
|
|
|
fn exec(
|
|
allocator: std.mem.Allocator,
|
|
_env: DotEnv.Map,
|
|
argv: []const string,
|
|
) !string {
|
|
var env = _env;
|
|
var std_map = try env.stdEnvMap(allocator);
|
|
|
|
defer std_map.deinit();
|
|
|
|
const result = if (comptime Environment.isWindows)
|
|
try std.process.Child.run(.{
|
|
.allocator = allocator,
|
|
.argv = argv,
|
|
.env_map = std_map.get(),
|
|
})
|
|
else
|
|
try std.process.Child.run(.{
|
|
.allocator = allocator,
|
|
.argv = argv,
|
|
.env_map = std_map.get(),
|
|
});
|
|
|
|
switch (result.term) {
|
|
.Exited => |sig| if (sig == 0) return result.stdout else if (
|
|
// remote: The page could not be found <-- for non git
|
|
// remote: Repository not found. <-- for git
|
|
// remote: fatal repository '<url>' does not exist <-- for git
|
|
(strings.containsComptime(result.stderr, "remote:") and
|
|
strings.containsComptime(result.stderr, "not") and
|
|
strings.containsComptime(result.stderr, "found")) or
|
|
strings.containsComptime(result.stderr, "does not exist"))
|
|
{
|
|
return error.RepositoryNotFound;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
return error.InstallFailed;
|
|
}
|
|
|
|
pub fn trySSH(url: string) ?string {
|
|
// Do not cast explicit http(s) URLs to SSH
|
|
if (strings.hasPrefixComptime(url, "http")) {
|
|
return null;
|
|
}
|
|
|
|
if (strings.hasPrefixComptime(url, "git@")) {
|
|
return url;
|
|
}
|
|
|
|
if (strings.hasPrefixComptime(url, "ssh://")) {
|
|
// TODO(markovejnovic): This is a stop-gap. One of the problems with the implementation
|
|
// here is that we should integrate hosted_git_info more thoroughly into the codebase
|
|
// to avoid the allocation and copy here. For now, the thread-local buffer is a good
|
|
// enough solution to avoid having to handle init/deinit.
|
|
|
|
// Fix malformed ssh:// URLs with colons using hosted_git_info.correctUrl
|
|
// ssh://git@github.com:user/repo -> ssh://git@github.com/user/repo
|
|
var pair = hosted_git_info.UrlProtocolPair{
|
|
.url = .{ .unmanaged = url },
|
|
.protocol = .{ .well_formed = .git_plus_ssh },
|
|
};
|
|
|
|
var corrected = hosted_git_info.correctUrl(&pair, bun.default_allocator) catch {
|
|
return url; // If correction fails, return original
|
|
};
|
|
defer corrected.deinit();
|
|
|
|
// Copy corrected URL to thread-local buffer
|
|
const corrected_str = corrected.urlSlice();
|
|
const result = ssh_path_buf[0..corrected_str.len];
|
|
bun.copy(u8, result, corrected_str);
|
|
return result;
|
|
}
|
|
|
|
if (Dependency.isSCPLikePath(url)) {
|
|
ssh_path_buf[0.."ssh://git@".len].* = "ssh://git@".*;
|
|
var rest = ssh_path_buf["ssh://git@".len..];
|
|
|
|
const colon_index = strings.indexOfChar(url, ':');
|
|
|
|
if (colon_index) |colon| {
|
|
// make sure known hosts have `.com` or `.org`
|
|
if (Hosts.get(url[0..colon])) |tld| {
|
|
bun.copy(u8, rest, url[0..colon]);
|
|
bun.copy(u8, rest[colon..], tld);
|
|
rest[colon + tld.len] = '/';
|
|
bun.copy(u8, rest[colon + tld.len + 1 ..], url[colon + 1 ..]);
|
|
const out = ssh_path_buf[0 .. url.len + "ssh://git@".len + tld.len];
|
|
return out;
|
|
}
|
|
}
|
|
|
|
bun.copy(u8, rest, url);
|
|
if (colon_index) |colon| rest[colon] = '/';
|
|
const final = ssh_path_buf[0 .. url.len + "ssh://".len];
|
|
return final;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
pub fn tryHTTPS(url: string) ?string {
|
|
if (strings.hasPrefixComptime(url, "http")) {
|
|
return url;
|
|
}
|
|
|
|
if (strings.hasPrefixComptime(url, "ssh://")) {
|
|
final_path_buf[0.."https".len].* = "https".*;
|
|
bun.copy(u8, final_path_buf["https".len..], url["ssh".len..]);
|
|
const out = final_path_buf[0 .. url.len - "ssh".len + "https".len];
|
|
return out;
|
|
}
|
|
|
|
if (Dependency.isSCPLikePath(url)) {
|
|
final_path_buf[0.."https://".len].* = "https://".*;
|
|
var rest = final_path_buf["https://".len..];
|
|
|
|
const colon_index = strings.indexOfChar(url, ':');
|
|
|
|
if (colon_index) |colon| {
|
|
// make sure known hosts have `.com` or `.org`
|
|
if (Hosts.get(url[0..colon])) |tld| {
|
|
bun.copy(u8, rest, url[0..colon]);
|
|
bun.copy(u8, rest[colon..], tld);
|
|
rest[colon + tld.len] = '/';
|
|
bun.copy(u8, rest[colon + tld.len + 1 ..], url[colon + 1 ..]);
|
|
const out = final_path_buf[0 .. url.len + "https://".len + tld.len];
|
|
return out;
|
|
}
|
|
}
|
|
|
|
bun.copy(u8, rest, url);
|
|
if (colon_index) |colon| rest[colon] = '/';
|
|
return final_path_buf[0 .. url.len + "https://".len];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
pub fn download(
|
|
allocator: std.mem.Allocator,
|
|
env: DotEnv.Map,
|
|
log: *logger.Log,
|
|
cache_dir: std.fs.Dir,
|
|
task_id: Install.Task.Id,
|
|
name: string,
|
|
url: string,
|
|
attempt: u8,
|
|
) !std.fs.Dir {
|
|
bun.analytics.Features.git_dependencies += 1;
|
|
const folder_name = try std.fmt.bufPrintZ(&folder_name_buf, "{f}.git", .{
|
|
bun.fmt.hexIntLower(task_id.get()),
|
|
});
|
|
|
|
return if (cache_dir.openDirZ(folder_name, .{})) |dir| fetch: {
|
|
const path = Path.joinAbsString(PackageManager.get().cache_directory_path, &.{folder_name}, .auto);
|
|
|
|
_ = exec(
|
|
allocator,
|
|
env,
|
|
&[_]string{ "git", "-C", path, "fetch", "--quiet" },
|
|
) catch |err| {
|
|
log.addErrorFmt(
|
|
null,
|
|
logger.Loc.Empty,
|
|
allocator,
|
|
"\"git fetch\" for \"{s}\" failed",
|
|
.{name},
|
|
) catch unreachable;
|
|
return err;
|
|
};
|
|
break :fetch dir;
|
|
} else |not_found| clone: {
|
|
if (not_found != error.FileNotFound) return not_found;
|
|
|
|
const target = Path.joinAbsString(PackageManager.get().cache_directory_path, &.{folder_name}, .auto);
|
|
|
|
_ = exec(allocator, env, &[_]string{
|
|
"git",
|
|
"clone",
|
|
"-c",
|
|
"core.longpaths=true",
|
|
"--quiet",
|
|
"--bare",
|
|
url,
|
|
target,
|
|
}) catch |err| {
|
|
if (err == error.RepositoryNotFound or attempt > 1) {
|
|
log.addErrorFmt(
|
|
null,
|
|
logger.Loc.Empty,
|
|
allocator,
|
|
"\"git clone\" for \"{s}\" failed",
|
|
.{name},
|
|
) catch unreachable;
|
|
}
|
|
return err;
|
|
};
|
|
|
|
break :clone try cache_dir.openDirZ(folder_name, .{});
|
|
};
|
|
}
|
|
|
|
pub fn findCommit(
|
|
allocator: std.mem.Allocator,
|
|
env: *DotEnv.Loader,
|
|
log: *logger.Log,
|
|
repo_dir: std.fs.Dir,
|
|
name: string,
|
|
committish: string,
|
|
task_id: Install.Task.Id,
|
|
) !string {
|
|
const path = Path.joinAbsString(PackageManager.get().cache_directory_path, &.{try std.fmt.bufPrint(&folder_name_buf, "{f}.git", .{
|
|
bun.fmt.hexIntLower(task_id.get()),
|
|
})}, .auto);
|
|
|
|
_ = repo_dir;
|
|
|
|
return std.mem.trim(u8, exec(
|
|
allocator,
|
|
shared_env.get(allocator, env),
|
|
if (committish.len > 0)
|
|
&[_]string{ "git", "-C", path, "log", "--format=%H", "-1", committish }
|
|
else
|
|
&[_]string{ "git", "-C", path, "log", "--format=%H", "-1" },
|
|
) catch |err| {
|
|
log.addErrorFmt(
|
|
null,
|
|
logger.Loc.Empty,
|
|
allocator,
|
|
"no commit matching \"{s}\" found for \"{s}\" (but repository exists)",
|
|
.{ committish, name },
|
|
) catch unreachable;
|
|
return err;
|
|
}, " \t\r\n");
|
|
}
|
|
|
|
pub fn checkout(
|
|
allocator: std.mem.Allocator,
|
|
env: DotEnv.Map,
|
|
log: *logger.Log,
|
|
cache_dir: std.fs.Dir,
|
|
repo_dir: std.fs.Dir,
|
|
name: string,
|
|
url: string,
|
|
resolved: string,
|
|
) !ExtractData {
|
|
bun.analytics.Features.git_dependencies += 1;
|
|
const folder_name = PackageManager.cachedGitFolderNamePrint(&folder_name_buf, resolved, null);
|
|
|
|
var package_dir = bun.openDir(cache_dir, folder_name) catch |not_found| brk: {
|
|
if (not_found != error.ENOENT) return not_found;
|
|
|
|
const target = Path.joinAbsString(PackageManager.get().cache_directory_path, &.{folder_name}, .auto);
|
|
|
|
_ = exec(allocator, env, &[_]string{
|
|
"git",
|
|
"clone",
|
|
"-c",
|
|
"core.longpaths=true",
|
|
"--quiet",
|
|
"--no-checkout",
|
|
try bun.getFdPath(.fromStdDir(repo_dir), &final_path_buf),
|
|
target,
|
|
}) catch |err| {
|
|
log.addErrorFmt(
|
|
null,
|
|
logger.Loc.Empty,
|
|
allocator,
|
|
"\"git clone\" for \"{s}\" failed",
|
|
.{name},
|
|
) catch unreachable;
|
|
return err;
|
|
};
|
|
|
|
const folder = Path.joinAbsString(PackageManager.get().cache_directory_path, &.{folder_name}, .auto);
|
|
|
|
_ = exec(allocator, env, &[_]string{ "git", "-C", folder, "checkout", "--quiet", resolved }) catch |err| {
|
|
log.addErrorFmt(
|
|
null,
|
|
logger.Loc.Empty,
|
|
allocator,
|
|
"\"git checkout\" for \"{s}\" failed",
|
|
.{name},
|
|
) catch unreachable;
|
|
return err;
|
|
};
|
|
var dir = try bun.openDir(cache_dir, folder_name);
|
|
dir.deleteTree(".git") catch {};
|
|
|
|
if (resolved.len > 0) insert_tag: {
|
|
const git_tag = dir.createFileZ(".bun-tag", .{ .truncate = true }) catch break :insert_tag;
|
|
defer git_tag.close();
|
|
git_tag.writeAll(resolved) catch {
|
|
dir.deleteFileZ(".bun-tag") catch {};
|
|
};
|
|
}
|
|
|
|
break :brk dir;
|
|
};
|
|
defer package_dir.close();
|
|
|
|
const json_file, const json_buf = bun.sys.File.readFileFrom(package_dir, "package.json", allocator).unwrap() catch |err| {
|
|
if (err == error.ENOENT) {
|
|
// allow git dependencies without package.json
|
|
return .{
|
|
.url = url,
|
|
.resolved = resolved,
|
|
};
|
|
}
|
|
|
|
log.addErrorFmt(
|
|
null,
|
|
logger.Loc.Empty,
|
|
allocator,
|
|
"\"package.json\" for \"{s}\" failed to open: {s}",
|
|
.{ name, @errorName(err) },
|
|
) catch unreachable;
|
|
return error.InstallFailed;
|
|
};
|
|
defer json_file.close();
|
|
|
|
const json_path = json_file.getPath(
|
|
&json_path_buf,
|
|
).unwrap() catch |err| {
|
|
log.addErrorFmt(
|
|
null,
|
|
logger.Loc.Empty,
|
|
allocator,
|
|
"\"package.json\" for \"{s}\" failed to resolve: {s}",
|
|
.{ name, @errorName(err) },
|
|
) catch unreachable;
|
|
return error.InstallFailed;
|
|
};
|
|
|
|
const ret_json_path = try FileSystem.instance.dirname_store.append(@TypeOf(json_path), json_path);
|
|
return .{
|
|
.url = url,
|
|
.resolved = resolved,
|
|
.json = .{
|
|
.path = ret_json_path,
|
|
.buf = json_buf,
|
|
},
|
|
};
|
|
}
|
|
};
|
|
|
|
const string = []const u8;
|
|
|
|
const Dependency = @import("./dependency.zig");
|
|
const DotEnv = @import("../env_loader.zig");
|
|
const Environment = @import("../env.zig");
|
|
const hosted_git_info = @import("./hosted_git_info.zig");
|
|
const std = @import("std");
|
|
const FileSystem = @import("../fs.zig").FileSystem;
|
|
|
|
const Install = @import("./install.zig");
|
|
const ExtractData = Install.ExtractData;
|
|
const PackageManager = Install.PackageManager;
|
|
|
|
const bun = @import("bun");
|
|
const OOM = bun.OOM;
|
|
const Path = bun.path;
|
|
const logger = bun.logger;
|
|
const strings = bun.strings;
|
|
const File = bun.sys.File;
|
|
|
|
const Semver = bun.Semver;
|
|
const String = Semver.String;
|