diff --git a/cmake/targets/BuildBun.cmake b/cmake/targets/BuildBun.cmake index ff72f37527..e4f83c9c86 100644 --- a/cmake/targets/BuildBun.cmake +++ b/cmake/targets/BuildBun.cmake @@ -54,6 +54,7 @@ set(BUN_DEPENDENCIES Cares Highway LibDeflate + Libgit2 LolHtml Lshpack Mimalloc diff --git a/cmake/targets/BuildLibgit2.cmake b/cmake/targets/BuildLibgit2.cmake new file mode 100644 index 0000000000..0ad22974b5 --- /dev/null +++ b/cmake/targets/BuildLibgit2.cmake @@ -0,0 +1,42 @@ +register_repository( + NAME + libgit2 + REPOSITORY + libgit2/libgit2 + TAG + v1.9.0 +) + +set(LIBGIT2_CMAKE_ARGS + -DBUILD_SHARED_LIBS=OFF + -DBUILD_TESTS=OFF + -DBUILD_CLI=OFF + -DUSE_SSH=OFF + -DUSE_HTTPS=OFF + -DUSE_BUNDLED_ZLIB=OFF + -DUSE_ICONV=OFF + -DREGEX_BACKEND=builtin + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DCMAKE_INSTALL_LIBDIR=lib +) + +if(WIN32) + list(APPEND LIBGIT2_CMAKE_ARGS + -DWIN32_LEAKCHECK=OFF + ) +endif() + +register_cmake_command( + TARGET + libgit2 + TARGETS + git2 + ARGS + ${LIBGIT2_CMAKE_ARGS} + LIB_PATH + lib + LIBRARIES + git2 + INCLUDES + include +) diff --git a/src/bun.js/bindings/JSGit.h b/src/bun.js/bindings/JSGit.h new file mode 100644 index 0000000000..61063ed59f --- /dev/null +++ b/src/bun.js/bindings/JSGit.h @@ -0,0 +1,523 @@ +#pragma once + +#include "root.h" +#include +#include +#include +#include + +namespace Bun { +using namespace JSC; + +// Forward declarations +class JSGitRepository; +class JSGitCommit; +class JSGitBranch; +class JSGitRemote; +class JSGitDiff; +class JSGitStatusEntry; +class JSGitIndex; +class JSGitConfig; +class JSGitStash; +class JSGitWorktree; +class JSGitBlob; +class JSGitSignature; + +// Initialize libgit2 (call once at startup) +void initializeLibgit2(); +void shutdownLibgit2(); + +// ============================================================================ +// JSGitRepository - Core repository class +// ============================================================================ + +class JSGitRepository : public JSC::JSDestructibleObject { + using Base = JSC::JSDestructibleObject; + +public: + JSGitRepository(JSC::VM& vm, JSC::Structure* structure, git_repository* repo) + : Base(vm, structure) + , m_repo(repo) + { + } + + DECLARE_INFO; + + static constexpr unsigned StructureFlags = Base::StructureFlags; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return subspaceForImpl(vm); + } + + static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm); + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + + static JSGitRepository* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, git_repository* repo) + { + JSGitRepository* object = new (NotNull, JSC::allocateCell(vm)) JSGitRepository(vm, structure, repo); + object->finishCreation(vm, globalObject); + return object; + } + + void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject); + + static void destroy(JSCell* thisObject) + { + static_cast(thisObject)->~JSGitRepository(); + } + + ~JSGitRepository(); + + git_repository* repo() const { return m_repo; } + +private: + git_repository* m_repo; +}; + +class JSGitRepositoryPrototype : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSGitRepositoryPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSGitRepositoryPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSGitRepositoryPrototype(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSGitRepositoryPrototype, Base); + return &vm.plainObjectSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + structure->setMayBePrototype(true); + return structure; + } + +private: + JSGitRepositoryPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +class JSGitRepositoryConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + + static JSGitRepositoryConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSGitRepositoryPrototype* prototype); + + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); + } + + void initializeProperties(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSGitRepositoryPrototype* prototype); + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + + DECLARE_EXPORT_INFO; + +private: + JSGitRepositoryConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, call, construct) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject* globalObject, JSGitRepositoryPrototype* prototype); +}; + +// ============================================================================ +// JSGitCommit - Commit class +// ============================================================================ + +class JSGitCommit : public JSC::JSDestructibleObject { + using Base = JSC::JSDestructibleObject; + +public: + JSGitCommit(JSC::VM& vm, JSC::Structure* structure, git_commit* commit, JSGitRepository* repo) + : Base(vm, structure) + , m_commit(commit) + , m_repo(repo) + { + } + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + static constexpr unsigned StructureFlags = Base::StructureFlags; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return subspaceForImpl(vm); + } + + static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm); + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + + static JSGitCommit* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, git_commit* commit, JSGitRepository* repo) + { + JSGitCommit* object = new (NotNull, JSC::allocateCell(vm)) JSGitCommit(vm, structure, commit, repo); + object->finishCreation(vm, globalObject); + return object; + } + + void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject); + + static void destroy(JSCell* thisObject) + { + static_cast(thisObject)->~JSGitCommit(); + } + + ~JSGitCommit(); + + git_commit* commit() const { return m_commit; } + JSGitRepository* repository() const { return m_repo; } + +private: + git_commit* m_commit; + JSGitRepository* m_repo; +}; + +class JSGitCommitPrototype : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSGitCommitPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSGitCommitPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSGitCommitPrototype(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSGitCommitPrototype, Base); + return &vm.plainObjectSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + structure->setMayBePrototype(true); + return structure; + } + +private: + JSGitCommitPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +class JSGitCommitConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + + static JSGitCommitConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSGitCommitPrototype* prototype); + + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); + } + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + + DECLARE_EXPORT_INFO; + +private: + JSGitCommitConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, call, construct) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject* globalObject, JSGitCommitPrototype* prototype); +}; + +// ============================================================================ +// JSGitBranch - Branch class +// ============================================================================ + +class JSGitBranch : public JSC::JSDestructibleObject { + using Base = JSC::JSDestructibleObject; + +public: + JSGitBranch(JSC::VM& vm, JSC::Structure* structure, git_reference* ref, JSGitRepository* repo, bool isRemote) + : Base(vm, structure) + , m_ref(ref) + , m_repo(repo) + , m_isRemote(isRemote) + { + } + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + static constexpr unsigned StructureFlags = Base::StructureFlags; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return subspaceForImpl(vm); + } + + static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm); + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + + static JSGitBranch* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, git_reference* ref, JSGitRepository* repo, bool isRemote) + { + JSGitBranch* object = new (NotNull, JSC::allocateCell(vm)) JSGitBranch(vm, structure, ref, repo, isRemote); + object->finishCreation(vm, globalObject); + return object; + } + + void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject); + + static void destroy(JSCell* thisObject) + { + static_cast(thisObject)->~JSGitBranch(); + } + + ~JSGitBranch(); + + git_reference* ref() const { return m_ref; } + JSGitRepository* repository() const { return m_repo; } + bool isRemote() const { return m_isRemote; } + +private: + git_reference* m_ref; + JSGitRepository* m_repo; + bool m_isRemote; +}; + +class JSGitBranchPrototype : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSGitBranchPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSGitBranchPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSGitBranchPrototype(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSGitBranchPrototype, Base); + return &vm.plainObjectSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + structure->setMayBePrototype(true); + return structure; + } + +private: + JSGitBranchPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +class JSGitBranchConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + + static JSGitBranchConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSGitBranchPrototype* prototype); + + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); + } + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + + DECLARE_EXPORT_INFO; + +private: + JSGitBranchConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, call, construct) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject* globalObject, JSGitBranchPrototype* prototype); +}; + +// ============================================================================ +// JSGitSignature - Signature class (author/committer info) +// ============================================================================ + +class JSGitSignature : public JSC::JSNonFinalObject { + using Base = JSC::JSNonFinalObject; + +public: + JSGitSignature(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + , m_name() + , m_email() + , m_time(0) + , m_offset(0) + { + } + + DECLARE_INFO; + + static constexpr unsigned StructureFlags = Base::StructureFlags; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSGitSignature, Base); + return &vm.plainObjectSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + + static JSGitSignature* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, const git_signature* sig) + { + JSGitSignature* object = new (NotNull, JSC::allocateCell(vm)) JSGitSignature(vm, structure); + object->finishCreation(vm, globalObject, sig); + return object; + } + + void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, const git_signature* sig); + + const String& name() const { return m_name; } + const String& email() const { return m_email; } + git_time_t time() const { return m_time; } + int offset() const { return m_offset; } + +private: + String m_name; + String m_email; + git_time_t m_time; + int m_offset; +}; + +class JSGitSignaturePrototype : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSGitSignaturePrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSGitSignaturePrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSGitSignaturePrototype(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSGitSignaturePrototype, Base); + return &vm.plainObjectSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + structure->setMayBePrototype(true); + return structure; + } + +private: + JSGitSignaturePrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +class JSGitSignatureConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + + static JSGitSignatureConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSGitSignaturePrototype* prototype); + + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); + } + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + + DECLARE_EXPORT_INFO; + +private: + JSGitSignatureConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, call, construct) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject* globalObject, JSGitSignaturePrototype* prototype); +}; + +// ============================================================================ +// Helper functions for class structure initialization +// ============================================================================ + +void initJSGitRepositoryClassStructure(JSC::LazyClassStructure::Initializer& init); +void initJSGitCommitClassStructure(JSC::LazyClassStructure::Initializer& init); +void initJSGitBranchClassStructure(JSC::LazyClassStructure::Initializer& init); +void initJSGitSignatureClassStructure(JSC::LazyClassStructure::Initializer& init); + +} // namespace Bun diff --git a/src/bun.js/bindings/JSGitBranch.cpp b/src/bun.js/bindings/JSGitBranch.cpp new file mode 100644 index 0000000000..cf76b039de --- /dev/null +++ b/src/bun.js/bindings/JSGitBranch.cpp @@ -0,0 +1,379 @@ +#include "root.h" +#include "JSGit.h" +#include "ZigGlobalObject.h" +#include "JavaScriptCore/JSCJSValueInlines.h" +#include "JavaScriptCore/ObjectConstructor.h" +#include "wtf/text/WTFString.h" +#include "helpers.h" +#include + +namespace Bun { +using namespace JSC; + +// ============================================================================ +// JSGitBranch Implementation +// ============================================================================ + +const ClassInfo JSGitBranch::s_info = { "Branch"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSGitBranch) }; + +JSGitBranch::~JSGitBranch() +{ + if (m_ref) { + git_reference_free(m_ref); + m_ref = nullptr; + } +} + +void JSGitBranch::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); +} + +template +void JSGitBranch::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + visitor.append(thisObject->m_repo); +} + +DEFINE_VISIT_CHILDREN(JSGitBranch); + +JSC::GCClient::IsoSubspace* JSGitBranch::subspaceForImpl(VM& vm) +{ + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForJSGitBranch.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSGitBranch = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForJSGitBranch.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForJSGitBranch = std::forward(space); }); +} + +// Getter: name +JSC_DEFINE_CUSTOM_GETTER(jsGitBranchGetter_name, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Branch"_s, "name"_s); + return {}; + } + + const char* name = nullptr; + int error = git_branch_name(&name, thisObject->ref()); + if (error < 0 || !name) { + return JSValue::encode(jsNull()); + } + return JSValue::encode(jsString(vm, WTF::String::fromUTF8(name))); +} + +// Getter: fullName +JSC_DEFINE_CUSTOM_GETTER(jsGitBranchGetter_fullName, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Branch"_s, "fullName"_s); + return {}; + } + + const char* name = git_reference_name(thisObject->ref()); + return JSValue::encode(jsString(vm, WTF::String::fromUTF8(name ? name : ""))); +} + +// Getter: isRemote +JSC_DEFINE_CUSTOM_GETTER(jsGitBranchGetter_isRemote, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Branch"_s, "isRemote"_s); + return {}; + } + + return JSValue::encode(jsBoolean(thisObject->isRemote())); +} + +// Getter: isHead +JSC_DEFINE_CUSTOM_GETTER(jsGitBranchGetter_isHead, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Branch"_s, "isHead"_s); + return {}; + } + + return JSValue::encode(jsBoolean(git_branch_is_head(thisObject->ref()))); +} + +// Getter: commit +JSC_DEFINE_CUSTOM_GETTER(jsGitBranchGetter_commit, (JSGlobalObject* lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = jsCast(lexicalGlobalObject); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*lexicalGlobalObject, scope, "Branch"_s, "commit"_s); + return {}; + } + + const git_oid* oid = git_reference_target(thisObject->ref()); + if (!oid) { + // Symbolic reference, need to resolve + git_reference* resolved = nullptr; + int error = git_reference_resolve(&resolved, thisObject->ref()); + if (error < 0) { + const git_error* err = git_error_last(); + throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(err ? err->message : "Failed to resolve branch"))); + return {}; + } + oid = git_reference_target(resolved); + git_reference_free(resolved); + } + + git_commit* commit = nullptr; + int error = git_commit_lookup(&commit, thisObject->repository()->repo(), oid); + if (error < 0) { + const git_error* err = git_error_last(); + throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(err ? err->message : "Failed to get commit"))); + return {}; + } + + auto* structure = globalObject->JSGitCommitStructure(); + return JSValue::encode(JSGitCommit::create(vm, lexicalGlobalObject, structure, commit, thisObject->repository())); +} + +// Getter: upstream +JSC_DEFINE_CUSTOM_GETTER(jsGitBranchGetter_upstream, (JSGlobalObject* lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = jsCast(lexicalGlobalObject); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*lexicalGlobalObject, scope, "Branch"_s, "upstream"_s); + return {}; + } + + git_reference* upstream = nullptr; + int error = git_branch_upstream(&upstream, thisObject->ref()); + if (error < 0) { + if (error == GIT_ENOTFOUND) { + return JSValue::encode(jsNull()); + } + const git_error* err = git_error_last(); + throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(err ? err->message : "Failed to get upstream"))); + return {}; + } + + auto* structure = globalObject->JSGitBranchStructure(); + return JSValue::encode(JSGitBranch::create(vm, lexicalGlobalObject, structure, upstream, thisObject->repository(), true)); +} + +// Getter: ahead +JSC_DEFINE_CUSTOM_GETTER(jsGitBranchGetter_ahead, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Branch"_s, "ahead"_s); + return {}; + } + + git_reference* upstream = nullptr; + int error = git_branch_upstream(&upstream, thisObject->ref()); + if (error < 0) { + return JSValue::encode(jsNumber(0)); + } + + size_t ahead = 0, behind = 0; + const git_oid* localOid = git_reference_target(thisObject->ref()); + const git_oid* upstreamOid = git_reference_target(upstream); + + if (localOid && upstreamOid) { + git_graph_ahead_behind(&ahead, &behind, thisObject->repository()->repo(), localOid, upstreamOid); + } + + git_reference_free(upstream); + return JSValue::encode(jsNumber(ahead)); +} + +// Getter: behind +JSC_DEFINE_CUSTOM_GETTER(jsGitBranchGetter_behind, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Branch"_s, "behind"_s); + return {}; + } + + git_reference* upstream = nullptr; + int error = git_branch_upstream(&upstream, thisObject->ref()); + if (error < 0) { + return JSValue::encode(jsNumber(0)); + } + + size_t ahead = 0, behind = 0; + const git_oid* localOid = git_reference_target(thisObject->ref()); + const git_oid* upstreamOid = git_reference_target(upstream); + + if (localOid && upstreamOid) { + git_graph_ahead_behind(&ahead, &behind, thisObject->repository()->repo(), localOid, upstreamOid); + } + + git_reference_free(upstream); + return JSValue::encode(jsNumber(behind)); +} + +// Method: delete(force?) +JSC_DEFINE_HOST_FUNCTION(jsGitBranchProtoFunc_delete, (JSGlobalObject* globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(callFrame->thisValue()); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Branch"_s, "delete"_s); + return {}; + } + + int error = git_branch_delete(thisObject->ref()); + if (error < 0) { + const git_error* err = git_error_last(); + throwException(globalObject, scope, createError(globalObject, WTF::String::fromUTF8(err ? err->message : "Failed to delete branch"))); + return {}; + } + + return JSValue::encode(jsUndefined()); +} + +// Method: rename(newName: string) +JSC_DEFINE_HOST_FUNCTION(jsGitBranchProtoFunc_rename, (JSGlobalObject* globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(callFrame->thisValue()); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Branch"_s, "rename"_s); + return {}; + } + + if (callFrame->argumentCount() < 1) { + throwException(globalObject, scope, createError(globalObject, "rename requires a newName argument"_s)); + return {}; + } + + auto newName = callFrame->argument(0).toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + git_reference* newRef = nullptr; + int error = git_branch_move(&newRef, thisObject->ref(), newName.utf8().data(), 0); + if (error < 0) { + const git_error* err = git_error_last(); + throwException(globalObject, scope, createError(globalObject, WTF::String::fromUTF8(err ? err->message : "Failed to rename branch"))); + return {}; + } + + git_reference_free(newRef); + return JSValue::encode(jsUndefined()); +} + +// ============================================================================ +// JSGitBranch Prototype Table +// ============================================================================ + +static const HashTableValue JSGitBranchPrototypeTableValues[] = { + { "name"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitBranchGetter_name, 0 } }, + { "fullName"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitBranchGetter_fullName, 0 } }, + { "isRemote"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitBranchGetter_isRemote, 0 } }, + { "isHead"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitBranchGetter_isHead, 0 } }, + { "commit"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitBranchGetter_commit, 0 } }, + { "upstream"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitBranchGetter_upstream, 0 } }, + { "ahead"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitBranchGetter_ahead, 0 } }, + { "behind"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitBranchGetter_behind, 0 } }, + { "delete"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsGitBranchProtoFunc_delete, 0 } }, + { "rename"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsGitBranchProtoFunc_rename, 1 } }, +}; + +// ============================================================================ +// JSGitBranchPrototype Implementation +// ============================================================================ + +const ClassInfo JSGitBranchPrototype::s_info = { "Branch"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSGitBranchPrototype) }; + +void JSGitBranchPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSGitBranch::info(), JSGitBranchPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +// ============================================================================ +// JSGitBranchConstructor Implementation +// ============================================================================ + +const ClassInfo JSGitBranchConstructor::s_info = { "Branch"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSGitBranchConstructor) }; + +JSGitBranchConstructor* JSGitBranchConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSGitBranchPrototype* prototype) +{ + JSGitBranchConstructor* constructor = new (NotNull, allocateCell(vm)) JSGitBranchConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; +} + +void JSGitBranchConstructor::finishCreation(VM& vm, JSGlobalObject* globalObject, JSGitBranchPrototype* prototype) +{ + Base::finishCreation(vm, 0, "Branch"_s, PropertyAdditionMode::WithoutStructureTransition); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); +} + +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSGitBranchConstructor::construct(JSGlobalObject* globalObject, CallFrame*) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + throwException(globalObject, scope, createTypeError(globalObject, "Branch cannot be directly constructed"_s)); + return {}; +} + +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSGitBranchConstructor::call(JSGlobalObject* globalObject, CallFrame*) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + throwException(globalObject, scope, createTypeError(globalObject, "Branch cannot be called as a function"_s)); + return {}; +} + +// ============================================================================ +// Class Structure Initialization +// ============================================================================ + +void initJSGitBranchClassStructure(LazyClassStructure::Initializer& init) +{ + auto* prototype = JSGitBranchPrototype::create(init.vm, init.global, JSGitBranchPrototype::createStructure(init.vm, init.global, init.global->objectPrototype())); + auto* structure = JSGitBranch::createStructure(init.vm, init.global, prototype); + auto* constructor = JSGitBranchConstructor::create(init.vm, init.global, JSGitBranchConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()), prototype); + init.setPrototype(prototype); + init.setStructure(structure); + init.setConstructor(constructor); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/JSGitCommit.cpp b/src/bun.js/bindings/JSGitCommit.cpp new file mode 100644 index 0000000000..b52f473674 --- /dev/null +++ b/src/bun.js/bindings/JSGitCommit.cpp @@ -0,0 +1,374 @@ +#include "root.h" +#include "JSGit.h" +#include "ZigGlobalObject.h" +#include "JavaScriptCore/JSCJSValueInlines.h" +#include "JavaScriptCore/ObjectConstructor.h" +#include "wtf/text/WTFString.h" +#include "helpers.h" +#include + +namespace Bun { +using namespace JSC; + +// ============================================================================ +// JSGitCommit Implementation +// ============================================================================ + +const ClassInfo JSGitCommit::s_info = { "Commit"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSGitCommit) }; + +JSGitCommit::~JSGitCommit() +{ + if (m_commit) { + git_commit_free(m_commit); + m_commit = nullptr; + } +} + +void JSGitCommit::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); +} + +template +void JSGitCommit::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + visitor.append(thisObject->m_repo); +} + +DEFINE_VISIT_CHILDREN(JSGitCommit); + +JSC::GCClient::IsoSubspace* JSGitCommit::subspaceForImpl(VM& vm) +{ + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForJSGitCommit.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSGitCommit = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForJSGitCommit.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForJSGitCommit = std::forward(space); }); +} + +// Helper to format OID as hex string +static WTF::String oidToString(const git_oid* oid) +{ + char buf[GIT_OID_SHA1_HEXSIZE + 1]; + git_oid_tostr(buf, sizeof(buf), oid); + return WTF::String::fromUTF8(buf); +} + +// Getter: sha +JSC_DEFINE_CUSTOM_GETTER(jsGitCommitGetter_sha, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Commit"_s, "sha"_s); + return {}; + } + + const git_oid* oid = git_commit_id(thisObject->commit()); + return JSValue::encode(jsString(vm, oidToString(oid))); +} + +// Getter: shortSha +JSC_DEFINE_CUSTOM_GETTER(jsGitCommitGetter_shortSha, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Commit"_s, "shortSha"_s); + return {}; + } + + const git_oid* oid = git_commit_id(thisObject->commit()); + char buf[8]; + git_oid_tostr(buf, sizeof(buf), oid); + return JSValue::encode(jsString(vm, WTF::String::fromUTF8(buf))); +} + +// Getter: message +JSC_DEFINE_CUSTOM_GETTER(jsGitCommitGetter_message, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Commit"_s, "message"_s); + return {}; + } + + const char* message = git_commit_message(thisObject->commit()); + return JSValue::encode(jsString(vm, WTF::String::fromUTF8(message ? message : ""))); +} + +// Getter: summary +JSC_DEFINE_CUSTOM_GETTER(jsGitCommitGetter_summary, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Commit"_s, "summary"_s); + return {}; + } + + const char* summary = git_commit_summary(thisObject->commit()); + return JSValue::encode(jsString(vm, WTF::String::fromUTF8(summary ? summary : ""))); +} + +// Getter: author +JSC_DEFINE_CUSTOM_GETTER(jsGitCommitGetter_author, (JSGlobalObject* lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = jsCast(lexicalGlobalObject); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*lexicalGlobalObject, scope, "Commit"_s, "author"_s); + return {}; + } + + const git_signature* author = git_commit_author(thisObject->commit()); + auto* structure = globalObject->JSGitSignatureStructure(); + return JSValue::encode(JSGitSignature::create(vm, lexicalGlobalObject, structure, author)); +} + +// Getter: committer +JSC_DEFINE_CUSTOM_GETTER(jsGitCommitGetter_committer, (JSGlobalObject* lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = jsCast(lexicalGlobalObject); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*lexicalGlobalObject, scope, "Commit"_s, "committer"_s); + return {}; + } + + const git_signature* committer = git_commit_committer(thisObject->commit()); + auto* structure = globalObject->JSGitSignatureStructure(); + return JSValue::encode(JSGitSignature::create(vm, lexicalGlobalObject, structure, committer)); +} + +// Getter: tree +JSC_DEFINE_CUSTOM_GETTER(jsGitCommitGetter_tree, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Commit"_s, "tree"_s); + return {}; + } + + const git_oid* treeId = git_commit_tree_id(thisObject->commit()); + return JSValue::encode(jsString(vm, oidToString(treeId))); +} + +// Getter: parents +JSC_DEFINE_CUSTOM_GETTER(jsGitCommitGetter_parents, (JSGlobalObject* lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = jsCast(lexicalGlobalObject); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*lexicalGlobalObject, scope, "Commit"_s, "parents"_s); + return {}; + } + + unsigned int parentCount = git_commit_parentcount(thisObject->commit()); + JSArray* result = constructEmptyArray(lexicalGlobalObject, nullptr, parentCount); + RETURN_IF_EXCEPTION(scope, {}); + + for (unsigned int i = 0; i < parentCount; i++) { + git_commit* parent = nullptr; + int error = git_commit_parent(&parent, thisObject->commit(), i); + if (error < 0) { + const git_error* err = git_error_last(); + throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(err ? err->message : "Failed to get parent commit"))); + return {}; + } + + auto* structure = globalObject->JSGitCommitStructure(); + result->putDirectIndex(lexicalGlobalObject, i, JSGitCommit::create(vm, lexicalGlobalObject, structure, parent, thisObject->repository())); + RETURN_IF_EXCEPTION(scope, {}); + } + + return JSValue::encode(result); +} + +// Method: parent(n?) -> Commit | null +JSC_DEFINE_HOST_FUNCTION(jsGitCommitProtoFunc_parent, (JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)) +{ + VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = jsCast(lexicalGlobalObject); + + auto* thisObject = jsDynamicCast(callFrame->thisValue()); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*lexicalGlobalObject, scope, "Commit"_s, "parent"_s); + return {}; + } + + unsigned int n = 0; + if (callFrame->argumentCount() > 0 && !callFrame->argument(0).isUndefined()) { + n = callFrame->argument(0).toUInt32(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + } + + git_commit* parent = nullptr; + int error = git_commit_parent(&parent, thisObject->commit(), n); + if (error < 0) { + if (error == GIT_ENOTFOUND) { + return JSValue::encode(jsNull()); + } + const git_error* err = git_error_last(); + throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(err ? err->message : "Failed to get parent commit"))); + return {}; + } + + auto* structure = globalObject->JSGitCommitStructure(); + return JSValue::encode(JSGitCommit::create(vm, lexicalGlobalObject, structure, parent, thisObject->repository())); +} + +// Method: isAncestorOf(other: Commit | string) -> boolean +JSC_DEFINE_HOST_FUNCTION(jsGitCommitProtoFunc_isAncestorOf, (JSGlobalObject* globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(callFrame->thisValue()); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Commit"_s, "isAncestorOf"_s); + return {}; + } + + if (callFrame->argumentCount() < 1) { + throwException(globalObject, scope, createError(globalObject, "isAncestorOf requires a commit argument"_s)); + return {}; + } + + const git_oid* ancestorOid = git_commit_id(thisObject->commit()); + git_oid descendantOid; + + JSValue otherArg = callFrame->argument(0); + if (auto* otherCommit = jsDynamicCast(otherArg)) { + git_oid_cpy(&descendantOid, git_commit_id(otherCommit->commit())); + } else { + auto refString = otherArg.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + git_object* obj = nullptr; + int error = git_revparse_single(&obj, thisObject->repository()->repo(), refString.utf8().data()); + if (error < 0) { + const git_error* err = git_error_last(); + throwException(globalObject, scope, createError(globalObject, WTF::String::fromUTF8(err ? err->message : "Invalid ref"))); + return {}; + } + git_oid_cpy(&descendantOid, git_object_id(obj)); + git_object_free(obj); + } + + int result = git_graph_descendant_of(thisObject->repository()->repo(), &descendantOid, ancestorOid); + if (result < 0) { + const git_error* err = git_error_last(); + throwException(globalObject, scope, createError(globalObject, WTF::String::fromUTF8(err ? err->message : "Failed to check ancestry"))); + return {}; + } + + return JSValue::encode(jsBoolean(result == 1)); +} + +// ============================================================================ +// JSGitCommit Prototype Table +// ============================================================================ + +static const HashTableValue JSGitCommitPrototypeTableValues[] = { + { "sha"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitCommitGetter_sha, 0 } }, + { "shortSha"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitCommitGetter_shortSha, 0 } }, + { "message"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitCommitGetter_message, 0 } }, + { "summary"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitCommitGetter_summary, 0 } }, + { "author"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitCommitGetter_author, 0 } }, + { "committer"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitCommitGetter_committer, 0 } }, + { "tree"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitCommitGetter_tree, 0 } }, + { "parents"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitCommitGetter_parents, 0 } }, + { "parent"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsGitCommitProtoFunc_parent, 0 } }, + { "isAncestorOf"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsGitCommitProtoFunc_isAncestorOf, 1 } }, +}; + +// ============================================================================ +// JSGitCommitPrototype Implementation +// ============================================================================ + +const ClassInfo JSGitCommitPrototype::s_info = { "Commit"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSGitCommitPrototype) }; + +void JSGitCommitPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSGitCommit::info(), JSGitCommitPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +// ============================================================================ +// JSGitCommitConstructor Implementation +// ============================================================================ + +const ClassInfo JSGitCommitConstructor::s_info = { "Commit"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSGitCommitConstructor) }; + +JSGitCommitConstructor* JSGitCommitConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSGitCommitPrototype* prototype) +{ + JSGitCommitConstructor* constructor = new (NotNull, allocateCell(vm)) JSGitCommitConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; +} + +void JSGitCommitConstructor::finishCreation(VM& vm, JSGlobalObject* globalObject, JSGitCommitPrototype* prototype) +{ + Base::finishCreation(vm, 0, "Commit"_s, PropertyAdditionMode::WithoutStructureTransition); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); +} + +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSGitCommitConstructor::construct(JSGlobalObject* globalObject, CallFrame*) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + throwException(globalObject, scope, createTypeError(globalObject, "Commit cannot be directly constructed"_s)); + return {}; +} + +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSGitCommitConstructor::call(JSGlobalObject* globalObject, CallFrame*) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + throwException(globalObject, scope, createTypeError(globalObject, "Commit cannot be called as a function"_s)); + return {}; +} + +// ============================================================================ +// Class Structure Initialization +// ============================================================================ + +void initJSGitCommitClassStructure(LazyClassStructure::Initializer& init) +{ + auto* prototype = JSGitCommitPrototype::create(init.vm, init.global, JSGitCommitPrototype::createStructure(init.vm, init.global, init.global->objectPrototype())); + auto* structure = JSGitCommit::createStructure(init.vm, init.global, prototype); + auto* constructor = JSGitCommitConstructor::create(init.vm, init.global, JSGitCommitConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()), prototype); + init.setPrototype(prototype); + init.setStructure(structure); + init.setConstructor(constructor); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/JSGitRepository.cpp b/src/bun.js/bindings/JSGitRepository.cpp new file mode 100644 index 0000000000..992a3f4ebe --- /dev/null +++ b/src/bun.js/bindings/JSGitRepository.cpp @@ -0,0 +1,730 @@ +#include "root.h" +#include "JSGit.h" +#include "ZigGlobalObject.h" +#include "JavaScriptCore/JSCJSValueInlines.h" +#include "JavaScriptCore/ObjectConstructor.h" +#include "wtf/text/WTFString.h" +#include "helpers.h" +#include "BunClientData.h" +#include + +namespace Bun { +using namespace JSC; + +// libgit2 initialization +static std::once_flag s_libgit2InitFlag; + +void initializeLibgit2() +{ + std::call_once(s_libgit2InitFlag, []() { + git_libgit2_init(); + }); +} + +void shutdownLibgit2() +{ + git_libgit2_shutdown(); +} + +// Helper to throw git errors +static void throwGitError(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, int errorCode) +{ + const git_error* err = git_error_last(); + WTF::String message; + if (err && err->message) { + message = WTF::String::fromUTF8(err->message); + } else { + message = makeString("Git error: "_s, errorCode); + } + throwException(globalObject, scope, createError(globalObject, message)); +} + +// ============================================================================ +// JSGitRepository Implementation +// ============================================================================ + +const ClassInfo JSGitRepository::s_info = { "Repository"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSGitRepository) }; + +JSGitRepository::~JSGitRepository() +{ + if (m_repo) { + git_repository_free(m_repo); + m_repo = nullptr; + } +} + +void JSGitRepository::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); +} + +JSC::GCClient::IsoSubspace* JSGitRepository::subspaceForImpl(VM& vm) +{ + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForJSGitRepository.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSGitRepository = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForJSGitRepository.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForJSGitRepository = std::forward(space); }); +} + +// ============================================================================ +// JSGitRepository Prototype Methods and Getters +// ============================================================================ + +// Getter: path +JSC_DEFINE_CUSTOM_GETTER(jsGitRepositoryGetter_path, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Repository"_s, "path"_s); + return {}; + } + + const char* path = git_repository_workdir(thisObject->repo()); + if (!path) { + path = git_repository_path(thisObject->repo()); + } + return JSValue::encode(jsString(vm, WTF::String::fromUTF8(path))); +} + +// Getter: gitDir +JSC_DEFINE_CUSTOM_GETTER(jsGitRepositoryGetter_gitDir, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Repository"_s, "gitDir"_s); + return {}; + } + + const char* path = git_repository_path(thisObject->repo()); + return JSValue::encode(jsString(vm, WTF::String::fromUTF8(path))); +} + +// Getter: isBare +JSC_DEFINE_CUSTOM_GETTER(jsGitRepositoryGetter_isBare, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Repository"_s, "isBare"_s); + return {}; + } + + return JSValue::encode(jsBoolean(git_repository_is_bare(thisObject->repo()))); +} + +// Getter: isClean +JSC_DEFINE_CUSTOM_GETTER(jsGitRepositoryGetter_isClean, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Repository"_s, "isClean"_s); + return {}; + } + + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED; + + git_status_list* statusList = nullptr; + int error = git_status_list_new(&statusList, thisObject->repo(), &opts); + if (error < 0) { + throwGitError(globalObject, scope, error); + return {}; + } + + size_t count = git_status_list_entrycount(statusList); + git_status_list_free(statusList); + + return JSValue::encode(jsBoolean(count == 0)); +} + +// Getter: head (returns the HEAD commit) +JSC_DEFINE_CUSTOM_GETTER(jsGitRepositoryGetter_head, (JSGlobalObject* lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = jsCast(lexicalGlobalObject); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*lexicalGlobalObject, scope, "Repository"_s, "head"_s); + return {}; + } + + git_reference* headRef = nullptr; + int error = git_repository_head(&headRef, thisObject->repo()); + if (error < 0) { + if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND) { + return JSValue::encode(jsNull()); + } + throwGitError(lexicalGlobalObject, scope, error); + return {}; + } + + const git_oid* oid = git_reference_target(headRef); + git_commit* commit = nullptr; + error = git_commit_lookup(&commit, thisObject->repo(), oid); + git_reference_free(headRef); + + if (error < 0) { + throwGitError(lexicalGlobalObject, scope, error); + return {}; + } + + auto* structure = globalObject->JSGitCommitStructure(); + return JSValue::encode(JSGitCommit::create(vm, lexicalGlobalObject, structure, commit, thisObject)); +} + +// Getter: branch (returns the current branch or null if detached) +JSC_DEFINE_CUSTOM_GETTER(jsGitRepositoryGetter_branch, (JSGlobalObject* lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = jsCast(lexicalGlobalObject); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*lexicalGlobalObject, scope, "Repository"_s, "branch"_s); + return {}; + } + + git_reference* headRef = nullptr; + int error = git_repository_head(&headRef, thisObject->repo()); + if (error < 0) { + if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND) { + return JSValue::encode(jsNull()); + } + throwGitError(lexicalGlobalObject, scope, error); + return {}; + } + + if (git_reference_is_branch(headRef)) { + auto* structure = globalObject->JSGitBranchStructure(); + return JSValue::encode(JSGitBranch::create(vm, lexicalGlobalObject, structure, headRef, thisObject, false)); + } + + git_reference_free(headRef); + return JSValue::encode(jsNull()); +} + +// Method: getCommit(ref: string) -> Commit | null +JSC_DEFINE_HOST_FUNCTION(jsGitRepositoryProtoFunc_getCommit, (JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)) +{ + VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = jsCast(lexicalGlobalObject); + + auto* thisObject = jsDynamicCast(callFrame->thisValue()); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*lexicalGlobalObject, scope, "Repository"_s, "getCommit"_s); + return {}; + } + + if (callFrame->argumentCount() < 1) { + throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "getCommit requires a ref argument"_s)); + return {}; + } + + auto refString = callFrame->argument(0).toWTFString(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + + git_object* obj = nullptr; + int error = git_revparse_single(&obj, thisObject->repo(), refString.utf8().data()); + if (error < 0) { + if (error == GIT_ENOTFOUND) { + return JSValue::encode(jsNull()); + } + throwGitError(lexicalGlobalObject, scope, error); + return {}; + } + + git_commit* commit = nullptr; + error = git_commit_lookup(&commit, thisObject->repo(), git_object_id(obj)); + git_object_free(obj); + + if (error < 0) { + throwGitError(lexicalGlobalObject, scope, error); + return {}; + } + + auto* structure = globalObject->JSGitCommitStructure(); + return JSValue::encode(JSGitCommit::create(vm, lexicalGlobalObject, structure, commit, thisObject)); +} + +// Method: status(options?) -> StatusEntry[] +JSC_DEFINE_HOST_FUNCTION(jsGitRepositoryProtoFunc_status, (JSGlobalObject* globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(callFrame->thisValue()); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Repository"_s, "status"_s); + return {}; + } + + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + // Parse options if provided + if (callFrame->argumentCount() > 0 && !callFrame->argument(0).isUndefinedOrNull()) { + JSObject* options = callFrame->argument(0).toObject(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + JSValue includeUntracked = options->get(globalObject, Identifier::fromString(vm, "includeUntracked"_s)); + RETURN_IF_EXCEPTION(scope, {}); + if (!includeUntracked.isUndefined() && !includeUntracked.toBoolean(globalObject)) { + opts.flags &= ~(GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS); + } + + JSValue includeIgnored = options->get(globalObject, Identifier::fromString(vm, "includeIgnored"_s)); + RETURN_IF_EXCEPTION(scope, {}); + if (!includeIgnored.isUndefined() && includeIgnored.toBoolean(globalObject)) { + opts.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; + } + } + + git_status_list* statusList = nullptr; + int error = git_status_list_new(&statusList, thisObject->repo(), &opts); + if (error < 0) { + throwGitError(globalObject, scope, error); + return {}; + } + + size_t count = git_status_list_entrycount(statusList); + JSArray* result = constructEmptyArray(globalObject, nullptr, count); + RETURN_IF_EXCEPTION(scope, {}); + + for (size_t i = 0; i < count; i++) { + const git_status_entry* entry = git_status_byindex(statusList, i); + JSObject* entryObj = constructEmptyObject(globalObject); + + const char* path = entry->head_to_index ? entry->head_to_index->new_file.path + : entry->index_to_workdir ? entry->index_to_workdir->new_file.path + : nullptr; + if (path) { + entryObj->putDirect(vm, Identifier::fromString(vm, "path"_s), jsString(vm, WTF::String::fromUTF8(path))); + } + + // Index status + WTF::String indexStatus = "unmodified"_s; + if (entry->status & GIT_STATUS_INDEX_NEW) indexStatus = "added"_s; + else if (entry->status & GIT_STATUS_INDEX_MODIFIED) indexStatus = "modified"_s; + else if (entry->status & GIT_STATUS_INDEX_DELETED) indexStatus = "deleted"_s; + else if (entry->status & GIT_STATUS_INDEX_RENAMED) indexStatus = "renamed"_s; + else if (entry->status & GIT_STATUS_INDEX_TYPECHANGE) indexStatus = "typechange"_s; + entryObj->putDirect(vm, Identifier::fromString(vm, "indexStatus"_s), jsString(vm, indexStatus)); + + // Worktree status + WTF::String wtStatus = "unmodified"_s; + if (entry->status & GIT_STATUS_WT_NEW) wtStatus = "untracked"_s; + else if (entry->status & GIT_STATUS_WT_MODIFIED) wtStatus = "modified"_s; + else if (entry->status & GIT_STATUS_WT_DELETED) wtStatus = "deleted"_s; + else if (entry->status & GIT_STATUS_WT_RENAMED) wtStatus = "renamed"_s; + else if (entry->status & GIT_STATUS_WT_TYPECHANGE) wtStatus = "typechange"_s; + else if (entry->status & GIT_STATUS_IGNORED) wtStatus = "ignored"_s; + else if (entry->status & GIT_STATUS_CONFLICTED) wtStatus = "unmerged"_s; + entryObj->putDirect(vm, Identifier::fromString(vm, "workTreeStatus"_s), jsString(vm, wtStatus)); + + // Original path for renames + const char* origPath = entry->head_to_index ? entry->head_to_index->old_file.path + : entry->index_to_workdir ? entry->index_to_workdir->old_file.path + : nullptr; + if (origPath && path && strcmp(origPath, path) != 0) { + entryObj->putDirect(vm, Identifier::fromString(vm, "origPath"_s), jsString(vm, WTF::String::fromUTF8(origPath))); + } else { + entryObj->putDirect(vm, Identifier::fromString(vm, "origPath"_s), jsNull()); + } + + result->putDirectIndex(globalObject, i, entryObj); + RETURN_IF_EXCEPTION(scope, {}); + } + + git_status_list_free(statusList); + return JSValue::encode(result); +} + +// Method: add(paths: string | string[]) +JSC_DEFINE_HOST_FUNCTION(jsGitRepositoryProtoFunc_add, (JSGlobalObject* globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(callFrame->thisValue()); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Repository"_s, "add"_s); + return {}; + } + + if (callFrame->argumentCount() < 1) { + throwException(globalObject, scope, createError(globalObject, "add requires a path argument"_s)); + return {}; + } + + git_index* index = nullptr; + int error = git_repository_index(&index, thisObject->repo()); + if (error < 0) { + throwGitError(globalObject, scope, error); + return {}; + } + + JSValue pathsArg = callFrame->argument(0); + if (pathsArg.isString()) { + auto pathStr = pathsArg.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + error = git_index_add_bypath(index, pathStr.utf8().data()); + } else if (isArray(globalObject, pathsArg)) { + JSArray* paths = jsCast(pathsArg); + uint32_t length = paths->length(); + for (uint32_t i = 0; i < length; i++) { + JSValue pathValue = paths->get(globalObject, i); + RETURN_IF_EXCEPTION(scope, {}); + auto pathStr = pathValue.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + error = git_index_add_bypath(index, pathStr.utf8().data()); + if (error < 0) break; + } + } else { + git_index_free(index); + throwException(globalObject, scope, createTypeError(globalObject, "paths must be a string or array of strings"_s)); + return {}; + } + + if (error < 0) { + git_index_free(index); + throwGitError(globalObject, scope, error); + return {}; + } + + error = git_index_write(index); + git_index_free(index); + + if (error < 0) { + throwGitError(globalObject, scope, error); + return {}; + } + + return JSValue::encode(jsUndefined()); +} + +// Method: commit(message: string, options?) -> Commit +JSC_DEFINE_HOST_FUNCTION(jsGitRepositoryProtoFunc_commit, (JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)) +{ + VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = jsCast(lexicalGlobalObject); + + auto* thisObject = jsDynamicCast(callFrame->thisValue()); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*lexicalGlobalObject, scope, "Repository"_s, "commit"_s); + return {}; + } + + if (callFrame->argumentCount() < 1) { + throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "commit requires a message argument"_s)); + return {}; + } + + auto message = callFrame->argument(0).toWTFString(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + + // Get the index + git_index* index = nullptr; + int error = git_repository_index(&index, thisObject->repo()); + if (error < 0) { + throwGitError(lexicalGlobalObject, scope, error); + return {}; + } + + // Write the index as a tree + git_oid treeId; + error = git_index_write_tree(&treeId, index); + git_index_free(index); + if (error < 0) { + throwGitError(lexicalGlobalObject, scope, error); + return {}; + } + + // Lookup the tree + git_tree* tree = nullptr; + error = git_tree_lookup(&tree, thisObject->repo(), &treeId); + if (error < 0) { + throwGitError(lexicalGlobalObject, scope, error); + return {}; + } + + // Get the default signature + git_signature* sig = nullptr; + error = git_signature_default(&sig, thisObject->repo()); + if (error < 0) { + git_tree_free(tree); + throwGitError(lexicalGlobalObject, scope, error); + return {}; + } + + // Get the parent commit (HEAD) + git_commit* parent = nullptr; + git_reference* headRef = nullptr; + error = git_repository_head(&headRef, thisObject->repo()); + if (error == 0) { + const git_oid* parentId = git_reference_target(headRef); + error = git_commit_lookup(&parent, thisObject->repo(), parentId); + git_reference_free(headRef); + if (error < 0) { + git_signature_free(sig); + git_tree_free(tree); + throwGitError(lexicalGlobalObject, scope, error); + return {}; + } + } else if (error != GIT_EUNBORNBRANCH && error != GIT_ENOTFOUND) { + git_signature_free(sig); + git_tree_free(tree); + throwGitError(lexicalGlobalObject, scope, error); + return {}; + } + + // Create the commit + git_oid commitId; + const git_commit* parents[] = { parent }; + size_t parentCount = parent ? 1 : 0; + + error = git_commit_create( + &commitId, + thisObject->repo(), + "HEAD", + sig, + sig, + nullptr, + message.utf8().data(), + tree, + parentCount, + parents + ); + + git_signature_free(sig); + git_tree_free(tree); + if (parent) git_commit_free(parent); + + if (error < 0) { + throwGitError(lexicalGlobalObject, scope, error); + return {}; + } + + // Return the new commit + git_commit* newCommit = nullptr; + error = git_commit_lookup(&newCommit, thisObject->repo(), &commitId); + if (error < 0) { + throwGitError(lexicalGlobalObject, scope, error); + return {}; + } + + auto* structure = globalObject->JSGitCommitStructure(); + return JSValue::encode(JSGitCommit::create(vm, lexicalGlobalObject, structure, newCommit, thisObject)); +} + +// ============================================================================ +// JSGitRepository Prototype Table +// ============================================================================ + +static const HashTableValue JSGitRepositoryPrototypeTableValues[] = { + { "path"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitRepositoryGetter_path, 0 } }, + { "gitDir"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitRepositoryGetter_gitDir, 0 } }, + { "isBare"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitRepositoryGetter_isBare, 0 } }, + { "isClean"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitRepositoryGetter_isClean, 0 } }, + { "head"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitRepositoryGetter_head, 0 } }, + { "branch"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitRepositoryGetter_branch, 0 } }, + { "getCommit"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsGitRepositoryProtoFunc_getCommit, 1 } }, + { "status"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsGitRepositoryProtoFunc_status, 0 } }, + { "add"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsGitRepositoryProtoFunc_add, 1 } }, + { "commit"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsGitRepositoryProtoFunc_commit, 1 } }, +}; + +// ============================================================================ +// JSGitRepositoryPrototype Implementation +// ============================================================================ + +const ClassInfo JSGitRepositoryPrototype::s_info = { "Repository"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSGitRepositoryPrototype) }; + +void JSGitRepositoryPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSGitRepository::info(), JSGitRepositoryPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +// ============================================================================ +// JSGitRepositoryConstructor Implementation +// ============================================================================ + +const ClassInfo JSGitRepositoryConstructor::s_info = { "Repository"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSGitRepositoryConstructor) }; + +JSGitRepositoryConstructor* JSGitRepositoryConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSGitRepositoryPrototype* prototype) +{ + JSGitRepositoryConstructor* constructor = new (NotNull, allocateCell(vm)) JSGitRepositoryConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; +} + +void JSGitRepositoryConstructor::finishCreation(VM& vm, JSGlobalObject* globalObject, JSGitRepositoryPrototype* prototype) +{ + Base::finishCreation(vm, 1, "Repository"_s, PropertyAdditionMode::WithoutStructureTransition); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); +} + +// Constructor: new Repository(path?) +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSGitRepositoryConstructor::construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame) +{ + VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = jsCast(lexicalGlobalObject); + + initializeLibgit2(); + + WTF::String pathStr = "."_s; + if (callFrame->argumentCount() > 0 && !callFrame->argument(0).isUndefinedOrNull()) { + pathStr = callFrame->argument(0).toWTFString(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + } + + // Discover the repository + git_buf repoPath = GIT_BUF_INIT; + int error = git_repository_discover(&repoPath, pathStr.utf8().data(), 0, nullptr); + if (error < 0) { + git_buf_dispose(&repoPath); + throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Not a git repository"_s)); + return {}; + } + + git_repository* repo = nullptr; + error = git_repository_open(&repo, repoPath.ptr); + git_buf_dispose(&repoPath); + + if (error < 0) { + throwGitError(lexicalGlobalObject, scope, error); + return {}; + } + + auto* structure = globalObject->JSGitRepositoryStructure(); + return JSValue::encode(JSGitRepository::create(vm, lexicalGlobalObject, structure, repo)); +} + +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSGitRepositoryConstructor::call(JSGlobalObject* globalObject, CallFrame*) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + throwException(globalObject, scope, createTypeError(globalObject, "Repository constructor cannot be called as a function"_s)); + return {}; +} + +// Static method: Repository.find(startPath?) -> Repository | null +JSC_DEFINE_HOST_FUNCTION(jsGitRepositoryConstructorFunc_find, (JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)) +{ + VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = jsCast(lexicalGlobalObject); + + initializeLibgit2(); + + WTF::String pathStr = "."_s; + if (callFrame->argumentCount() > 0 && !callFrame->argument(0).isUndefinedOrNull()) { + pathStr = callFrame->argument(0).toWTFString(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + } + + git_buf repoPath = GIT_BUF_INIT; + int error = git_repository_discover(&repoPath, pathStr.utf8().data(), 0, nullptr); + if (error < 0) { + git_buf_dispose(&repoPath); + return JSValue::encode(jsNull()); + } + + git_repository* repo = nullptr; + error = git_repository_open(&repo, repoPath.ptr); + git_buf_dispose(&repoPath); + + if (error < 0) { + return JSValue::encode(jsNull()); + } + + auto* structure = globalObject->JSGitRepositoryStructure(); + return JSValue::encode(JSGitRepository::create(vm, lexicalGlobalObject, structure, repo)); +} + +// Static method: Repository.init(path, options?) -> Repository +JSC_DEFINE_HOST_FUNCTION(jsGitRepositoryConstructorFunc_init, (JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)) +{ + VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = jsCast(lexicalGlobalObject); + + initializeLibgit2(); + + if (callFrame->argumentCount() < 1) { + throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "init requires a path argument"_s)); + return {}; + } + + auto pathStr = callFrame->argument(0).toWTFString(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + + bool isBare = false; + if (callFrame->argumentCount() > 1 && !callFrame->argument(1).isUndefinedOrNull()) { + JSObject* options = callFrame->argument(1).toObject(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + + JSValue bareValue = options->get(lexicalGlobalObject, Identifier::fromString(vm, "bare"_s)); + RETURN_IF_EXCEPTION(scope, {}); + isBare = bareValue.toBoolean(lexicalGlobalObject); + } + + git_repository* repo = nullptr; + int error = git_repository_init(&repo, pathStr.utf8().data(), isBare ? 1 : 0); + if (error < 0) { + throwGitError(lexicalGlobalObject, scope, error); + return {}; + } + + auto* structure = globalObject->JSGitRepositoryStructure(); + return JSValue::encode(JSGitRepository::create(vm, lexicalGlobalObject, structure, repo)); +} + +static const HashTableValue JSGitRepositoryConstructorTableValues[] = { + { "find"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsGitRepositoryConstructorFunc_find, 0 } }, + { "init"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsGitRepositoryConstructorFunc_init, 1 } }, +}; + +void JSGitRepositoryConstructor::initializeProperties(VM& vm, JSGlobalObject* globalObject, JSGitRepositoryPrototype* prototype) +{ + reifyStaticProperties(vm, info(), JSGitRepositoryConstructorTableValues, *this); +} + +// ============================================================================ +// Class Structure Initialization +// ============================================================================ + +void initJSGitRepositoryClassStructure(LazyClassStructure::Initializer& init) +{ + auto* prototype = JSGitRepositoryPrototype::create(init.vm, init.global, JSGitRepositoryPrototype::createStructure(init.vm, init.global, init.global->objectPrototype())); + auto* structure = JSGitRepository::createStructure(init.vm, init.global, prototype); + auto* constructor = JSGitRepositoryConstructor::create(init.vm, init.global, JSGitRepositoryConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()), prototype); + constructor->initializeProperties(init.vm, init.global, prototype); + init.setPrototype(prototype); + init.setStructure(structure); + init.setConstructor(constructor); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/JSGitSignature.cpp b/src/bun.js/bindings/JSGitSignature.cpp new file mode 100644 index 0000000000..aecc4b0f23 --- /dev/null +++ b/src/bun.js/bindings/JSGitSignature.cpp @@ -0,0 +1,190 @@ +#include "root.h" +#include "JSGit.h" +#include "ZigGlobalObject.h" +#include "JavaScriptCore/JSCJSValueInlines.h" +#include "JavaScriptCore/ObjectConstructor.h" +#include "JavaScriptCore/DateInstance.h" +#include "wtf/text/WTFString.h" +#include "helpers.h" +#include + +namespace Bun { +using namespace JSC; + +// ============================================================================ +// JSGitSignature Implementation +// ============================================================================ + +const ClassInfo JSGitSignature::s_info = { "Signature"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSGitSignature) }; + +void JSGitSignature::finishCreation(VM& vm, JSGlobalObject* globalObject, const git_signature* sig) +{ + Base::finishCreation(vm); + if (sig) { + m_name = WTF::String::fromUTF8(sig->name ? sig->name : ""); + m_email = WTF::String::fromUTF8(sig->email ? sig->email : ""); + m_time = sig->when.time; + m_offset = sig->when.offset; + } +} + +// Getter: name +JSC_DEFINE_CUSTOM_GETTER(jsGitSignatureGetter_name, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Signature"_s, "name"_s); + return {}; + } + + return JSValue::encode(jsString(vm, thisObject->name())); +} + +// Getter: email +JSC_DEFINE_CUSTOM_GETTER(jsGitSignatureGetter_email, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Signature"_s, "email"_s); + return {}; + } + + return JSValue::encode(jsString(vm, thisObject->email())); +} + +// Getter: date +JSC_DEFINE_CUSTOM_GETTER(jsGitSignatureGetter_date, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Signature"_s, "date"_s); + return {}; + } + + // Convert git_time_t (seconds since epoch) to JavaScript Date (milliseconds) + double ms = static_cast(thisObject->time()) * 1000.0; + return JSValue::encode(DateInstance::create(vm, globalObject->dateStructure(), ms)); +} + +// Getter: timezone +JSC_DEFINE_CUSTOM_GETTER(jsGitSignatureGetter_timezone, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Signature"_s, "timezone"_s); + return {}; + } + + int offset = thisObject->offset(); + int hours = offset / 60; + int minutes = offset % 60; + if (minutes < 0) minutes = -minutes; + + char buf[16]; + snprintf(buf, sizeof(buf), "%+03d:%02d", hours, minutes); + return JSValue::encode(jsString(vm, WTF::String::fromUTF8(buf))); +} + +// Method: toString() -> "Name " +JSC_DEFINE_HOST_FUNCTION(jsGitSignatureProtoFunc_toString, (JSGlobalObject* globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(callFrame->thisValue()); + if (UNLIKELY(!thisObject)) { + throwThisTypeError(*globalObject, scope, "Signature"_s, "toString"_s); + return {}; + } + + WTF::String result = makeString(thisObject->name(), " <"_s, thisObject->email(), ">"_s); + return JSValue::encode(jsString(vm, result)); +} + +// ============================================================================ +// JSGitSignature Prototype Table +// ============================================================================ + +static const HashTableValue JSGitSignaturePrototypeTableValues[] = { + { "name"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitSignatureGetter_name, 0 } }, + { "email"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitSignatureGetter_email, 0 } }, + { "date"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitSignatureGetter_date, 0 } }, + { "timezone"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitSignatureGetter_timezone, 0 } }, + { "toString"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsGitSignatureProtoFunc_toString, 0 } }, +}; + +// ============================================================================ +// JSGitSignaturePrototype Implementation +// ============================================================================ + +const ClassInfo JSGitSignaturePrototype::s_info = { "Signature"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSGitSignaturePrototype) }; + +void JSGitSignaturePrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSGitSignature::info(), JSGitSignaturePrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +// ============================================================================ +// JSGitSignatureConstructor Implementation +// ============================================================================ + +const ClassInfo JSGitSignatureConstructor::s_info = { "Signature"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSGitSignatureConstructor) }; + +JSGitSignatureConstructor* JSGitSignatureConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSGitSignaturePrototype* prototype) +{ + JSGitSignatureConstructor* constructor = new (NotNull, allocateCell(vm)) JSGitSignatureConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; +} + +void JSGitSignatureConstructor::finishCreation(VM& vm, JSGlobalObject* globalObject, JSGitSignaturePrototype* prototype) +{ + Base::finishCreation(vm, 0, "Signature"_s, PropertyAdditionMode::WithoutStructureTransition); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); +} + +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSGitSignatureConstructor::construct(JSGlobalObject* globalObject, CallFrame*) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + throwException(globalObject, scope, createTypeError(globalObject, "Signature cannot be directly constructed"_s)); + return {}; +} + +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSGitSignatureConstructor::call(JSGlobalObject* globalObject, CallFrame*) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + throwException(globalObject, scope, createTypeError(globalObject, "Signature cannot be called as a function"_s)); + return {}; +} + +// ============================================================================ +// Class Structure Initialization +// ============================================================================ + +void initJSGitSignatureClassStructure(LazyClassStructure::Initializer& init) +{ + auto* prototype = JSGitSignaturePrototype::create(init.vm, init.global, JSGitSignaturePrototype::createStructure(init.vm, init.global, init.global->objectPrototype())); + auto* structure = JSGitSignature::createStructure(init.vm, init.global, prototype); + auto* constructor = JSGitSignatureConstructor::create(init.vm, init.global, JSGitSignatureConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()), prototype); + init.setPrototype(prototype); + init.setStructure(structure); + init.setConstructor(constructor); +} + +} // namespace Bun diff --git a/src/bun.js/modules/BunGitModule.h b/src/bun.js/modules/BunGitModule.h new file mode 100644 index 0000000000..213bac4d7f --- /dev/null +++ b/src/bun.js/modules/BunGitModule.h @@ -0,0 +1,49 @@ +#pragma once + +#include "root.h" +#include "_NativeModule.h" + +namespace Zig { +using namespace WebCore; +using namespace JSC; + +// Forward declarations for the git classes +JSC::JSValue createGitRepositoryConstructor(JSC::JSGlobalObject* globalObject); +JSC::JSValue createGitCommitConstructor(JSC::JSGlobalObject* globalObject); +JSC::JSValue createGitBranchConstructor(JSC::JSGlobalObject* globalObject); +JSC::JSValue createGitRemoteConstructor(JSC::JSGlobalObject* globalObject); +JSC::JSValue createGitDiffConstructor(JSC::JSGlobalObject* globalObject); +JSC::JSValue createGitStatusEntryConstructor(JSC::JSGlobalObject* globalObject); +JSC::JSValue createGitIndexConstructor(JSC::JSGlobalObject* globalObject); +JSC::JSValue createGitConfigConstructor(JSC::JSGlobalObject* globalObject); +JSC::JSValue createGitStashConstructor(JSC::JSGlobalObject* globalObject); +JSC::JSValue createGitWorktreeConstructor(JSC::JSGlobalObject* globalObject); +JSC::JSValue createGitBlobConstructor(JSC::JSGlobalObject* globalObject); +JSC::JSValue createGitSignatureConstructor(JSC::JSGlobalObject* globalObject); +JSC::JSValue createGitErrorConstructor(JSC::JSGlobalObject* globalObject); + +DEFINE_NATIVE_MODULE(BunGit) +{ + INIT_NATIVE_MODULE(13); + + auto* zigGlobalObject = jsCast(globalObject); + + // Main classes + put(JSC::Identifier::fromString(vm, "Repository"_s), zigGlobalObject->JSGitRepositoryConstructor()); + put(JSC::Identifier::fromString(vm, "Commit"_s), zigGlobalObject->JSGitCommitConstructor()); + put(JSC::Identifier::fromString(vm, "Branch"_s), zigGlobalObject->JSGitBranchConstructor()); + put(JSC::Identifier::fromString(vm, "Remote"_s), zigGlobalObject->JSGitRemoteConstructor()); + put(JSC::Identifier::fromString(vm, "Diff"_s), zigGlobalObject->JSGitDiffConstructor()); + put(JSC::Identifier::fromString(vm, "StatusEntry"_s), zigGlobalObject->JSGitStatusEntryConstructor()); + put(JSC::Identifier::fromString(vm, "Index"_s), zigGlobalObject->JSGitIndexConstructor()); + put(JSC::Identifier::fromString(vm, "Config"_s), zigGlobalObject->JSGitConfigConstructor()); + put(JSC::Identifier::fromString(vm, "Stash"_s), zigGlobalObject->JSGitStashConstructor()); + put(JSC::Identifier::fromString(vm, "Worktree"_s), zigGlobalObject->JSGitWorktreeConstructor()); + put(JSC::Identifier::fromString(vm, "Blob"_s), zigGlobalObject->JSGitBlobConstructor()); + put(JSC::Identifier::fromString(vm, "Signature"_s), zigGlobalObject->JSGitSignatureConstructor()); + put(JSC::Identifier::fromString(vm, "GitError"_s), zigGlobalObject->JSGitErrorConstructor()); + + RETURN_NATIVE_MODULE(); +} + +} // namespace Zig diff --git a/src/bun.js/modules/_NativeModule.h b/src/bun.js/modules/_NativeModule.h index 70b19153ba..3196650f02 100644 --- a/src/bun.js/modules/_NativeModule.h +++ b/src/bun.js/modules/_NativeModule.h @@ -28,6 +28,7 @@ macro("bun:test"_s, BunTest) \ macro("bun:jsc"_s, BunJSC) \ macro("bun:app"_s, BunApp) \ + macro("bun:git"_s, BunGit) \ macro("node:buffer"_s, NodeBuffer) \ macro("node:constants"_s, NodeConstants) \ macro("node:string_decoder"_s, NodeStringDecoder) \