feat(bun:git): Initial implementation of bun:git module with libgit2

- Add libgit2 CMake build target
- Register bun:git native module
- Implement JSGitRepository class with:
  - Constructor and static find/init methods
  - Properties: path, gitDir, isBare, isClean, head, branch
  - Methods: getCommit, status, add, commit
- Implement JSGitCommit class with:
  - Properties: sha, shortSha, message, summary, author, committer, tree, parents
  - Methods: parent, isAncestorOf
- Implement JSGitBranch class with:
  - Properties: name, fullName, isRemote, isHead, commit, upstream, ahead, behind
  - Methods: delete, rename
- Implement JSGitSignature class with:
  - Properties: name, email, date, timezone
  - Methods: toString

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Bot
2026-01-16 07:36:01 +00:00
parent c9c2a560b0
commit efeadc42b1
9 changed files with 2289 additions and 0 deletions

View File

@@ -54,6 +54,7 @@ set(BUN_DEPENDENCIES
Cares
Highway
LibDeflate
Libgit2
LolHtml
Lshpack
Mimalloc

View File

@@ -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
)

523
src/bun.js/bindings/JSGit.h Normal file
View File

@@ -0,0 +1,523 @@
#pragma once
#include "root.h"
#include <JavaScriptCore/JSDestructibleObject.h>
#include <JavaScriptCore/JSNonFinalObject.h>
#include <JavaScriptCore/InternalFunction.h>
#include <git2.h>
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<typename, JSC::SubspaceAccess mode>
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<JSGitRepository>(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<JSGitRepository*>(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<JSGitRepositoryPrototype>(vm)) JSGitRepositoryPrototype(vm, structure);
ptr->finishCreation(vm, globalObject);
return ptr;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
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<typename, JSC::SubspaceAccess mode>
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<JSGitCommit>(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<JSGitCommit*>(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<JSGitCommitPrototype>(vm)) JSGitCommitPrototype(vm, structure);
ptr->finishCreation(vm, globalObject);
return ptr;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
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<typename, JSC::SubspaceAccess mode>
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<JSGitBranch>(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<JSGitBranch*>(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<JSGitBranchPrototype>(vm)) JSGitBranchPrototype(vm, structure);
ptr->finishCreation(vm, globalObject);
return ptr;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
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<typename CellType, JSC::SubspaceAccess>
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<JSGitSignature>(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<JSGitSignaturePrototype>(vm)) JSGitSignaturePrototype(vm, structure);
ptr->finishCreation(vm, globalObject);
return ptr;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
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

View File

@@ -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 <git2.h>
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<typename Visitor>
void JSGitBranch::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
auto* thisObject = jsCast<JSGitBranch*>(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<JSGitBranch, WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForJSGitBranch.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSGitBranch = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForJSGitBranch.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForJSGitBranch = std::forward<decltype(space)>(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<JSGitBranch*>(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<JSGitBranch*>(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<JSGitBranch*>(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<JSGitBranch*>(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<Zig::GlobalObject*>(lexicalGlobalObject);
auto* thisObject = jsDynamicCast<JSGitBranch*>(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<Zig::GlobalObject*>(lexicalGlobalObject);
auto* thisObject = jsDynamicCast<JSGitBranch*>(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<JSGitBranch*>(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<JSGitBranch*>(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<JSGitBranch*>(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<JSGitBranch*>(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<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitBranchGetter_name, 0 } },
{ "fullName"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitBranchGetter_fullName, 0 } },
{ "isRemote"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitBranchGetter_isRemote, 0 } },
{ "isHead"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitBranchGetter_isHead, 0 } },
{ "commit"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitBranchGetter_commit, 0 } },
{ "upstream"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitBranchGetter_upstream, 0 } },
{ "ahead"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitBranchGetter_ahead, 0 } },
{ "behind"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitBranchGetter_behind, 0 } },
{ "delete"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsGitBranchProtoFunc_delete, 0 } },
{ "rename"_s, static_cast<unsigned>(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<JSGitBranchConstructor>(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

View File

@@ -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 <git2.h>
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<typename Visitor>
void JSGitCommit::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
auto* thisObject = jsCast<JSGitCommit*>(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<JSGitCommit, WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForJSGitCommit.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSGitCommit = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForJSGitCommit.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForJSGitCommit = std::forward<decltype(space)>(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<JSGitCommit*>(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<JSGitCommit*>(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<JSGitCommit*>(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<JSGitCommit*>(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<Zig::GlobalObject*>(lexicalGlobalObject);
auto* thisObject = jsDynamicCast<JSGitCommit*>(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<Zig::GlobalObject*>(lexicalGlobalObject);
auto* thisObject = jsDynamicCast<JSGitCommit*>(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<JSGitCommit*>(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<Zig::GlobalObject*>(lexicalGlobalObject);
auto* thisObject = jsDynamicCast<JSGitCommit*>(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<Zig::GlobalObject*>(lexicalGlobalObject);
auto* thisObject = jsDynamicCast<JSGitCommit*>(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<JSGitCommit*>(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<JSGitCommit*>(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<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitCommitGetter_sha, 0 } },
{ "shortSha"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitCommitGetter_shortSha, 0 } },
{ "message"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitCommitGetter_message, 0 } },
{ "summary"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitCommitGetter_summary, 0 } },
{ "author"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitCommitGetter_author, 0 } },
{ "committer"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitCommitGetter_committer, 0 } },
{ "tree"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitCommitGetter_tree, 0 } },
{ "parents"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitCommitGetter_parents, 0 } },
{ "parent"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsGitCommitProtoFunc_parent, 0 } },
{ "isAncestorOf"_s, static_cast<unsigned>(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<JSGitCommitConstructor>(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

View File

@@ -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 <git2.h>
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<JSGitRepository, WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForJSGitRepository.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSGitRepository = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForJSGitRepository.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForJSGitRepository = std::forward<decltype(space)>(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<JSGitRepository*>(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<JSGitRepository*>(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<JSGitRepository*>(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<JSGitRepository*>(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<Zig::GlobalObject*>(lexicalGlobalObject);
auto* thisObject = jsDynamicCast<JSGitRepository*>(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<Zig::GlobalObject*>(lexicalGlobalObject);
auto* thisObject = jsDynamicCast<JSGitRepository*>(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<Zig::GlobalObject*>(lexicalGlobalObject);
auto* thisObject = jsDynamicCast<JSGitRepository*>(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<JSGitRepository*>(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<JSGitRepository*>(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<JSArray*>(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<Zig::GlobalObject*>(lexicalGlobalObject);
auto* thisObject = jsDynamicCast<JSGitRepository*>(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<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitRepositoryGetter_path, 0 } },
{ "gitDir"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitRepositoryGetter_gitDir, 0 } },
{ "isBare"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitRepositoryGetter_isBare, 0 } },
{ "isClean"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitRepositoryGetter_isClean, 0 } },
{ "head"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitRepositoryGetter_head, 0 } },
{ "branch"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitRepositoryGetter_branch, 0 } },
{ "getCommit"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsGitRepositoryProtoFunc_getCommit, 1 } },
{ "status"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsGitRepositoryProtoFunc_status, 0 } },
{ "add"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsGitRepositoryProtoFunc_add, 1 } },
{ "commit"_s, static_cast<unsigned>(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<JSGitRepositoryConstructor>(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<Zig::GlobalObject*>(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<Zig::GlobalObject*>(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<Zig::GlobalObject*>(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<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsGitRepositoryConstructorFunc_find, 0 } },
{ "init"_s, static_cast<unsigned>(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

View File

@@ -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 <git2.h>
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<JSGitSignature*>(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<JSGitSignature*>(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<JSGitSignature*>(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<double>(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<JSGitSignature*>(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 <email>"
JSC_DEFINE_HOST_FUNCTION(jsGitSignatureProtoFunc_toString, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSGitSignature*>(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<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitSignatureGetter_name, 0 } },
{ "email"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitSignatureGetter_email, 0 } },
{ "date"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitSignatureGetter_date, 0 } },
{ "timezone"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsGitSignatureGetter_timezone, 0 } },
{ "toString"_s, static_cast<unsigned>(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<JSGitSignatureConstructor>(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

View File

@@ -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<Zig::GlobalObject*>(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

View File

@@ -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) \