Skip to content

Commit 2e055d7

Browse files
committed
chore: drop optional libgit2 dependency, keep git log fallback
libgit2 is licensed GPLv2-with-linking-exception, and this project is deliberately GPL-free (see scripts/license-policy.json). libgit2 was only an OPTIONAL, faster git-history code path guarded by HAVE_LIBGIT2 and auto-detected via pkg-config; it always shipped with a popen("git log ...") fallback that release binaries already used (they were built without libgit2). Making that popen fallback the SOLE git-history implementation means there is NO change to shipped behavior. Changes: - pass_githistory.c: collapse the HAVE_LIBGIT2 #ifdef so only the popen parse_git_log remains; drop the now-unused <git2.h>/<time.h> includes. - cbm.c / cbm.h / main.c / subprocess.c / index_supervisor.h: remove the libgit2 mimalloc allocator bind and its >=1.7.0 version guards; keep the tree-sitter + sqlite3 binds; correct the comments. - Makefile.cbm: remove the optional-libgit2 pkg-config detection block, the REQUIRE_LIBGIT2 error gate, and all LIBGIT2 CFLAGS/LIBS/FLAGS. - .github/workflows/_test.yml: drop the REQUIRE_LIBGIT2 test-matrix leg plus its libgit2-dev/pkg-config apt install (lower CI cost, no new gating, no trigger change). - flake.nix: drop libgit2/pkg-config from the dev shell (now-dead dep). HAVE_LIBGIT2 no longer exists anywhere in the tree. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
1 parent 610531a commit 2e055d7

10 files changed

Lines changed: 15 additions & 214 deletions

File tree

.github/workflows/_test.yml

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
BROAD: ${{ inputs.broad_platforms }}
3434
run: |
3535
CORE_UNIX='[
36-
{"os":"ubuntu-latest","cc":"gcc","cxx":"g++","libgit2":true},
36+
{"os":"ubuntu-latest","cc":"gcc","cxx":"g++"},
3737
{"os":"ubuntu-24.04-arm","cc":"gcc","cxx":"g++"},
3838
{"os":"macos-14","cc":"cc","cxx":"c++"},
3939
{"os":"macos-15-intel","cc":"cc","cxx":"c++"}
@@ -79,15 +79,10 @@ jobs:
7979

8080
- name: Install deps (Ubuntu)
8181
if: startsWith(matrix.os, 'ubuntu')
82-
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev ${{ matrix.libgit2 == true && 'libgit2-dev pkg-config' || '' }}
82+
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev
8383

8484
- name: Test
85-
run: |
86-
if [ "${{ matrix.libgit2 == true }}" = "true" ]; then
87-
echo "REQUIRE_LIBGIT2=1"
88-
pkg-config --modversion libgit2
89-
fi
90-
scripts/test.sh CC=${{ matrix.cc }} CXX=${{ matrix.cxx }} ${{ matrix.libgit2 == true && 'REQUIRE_LIBGIT2=1' || '' }}
85+
run: scripts/test.sh CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
9186
env:
9287
CBM_SKIP_PERF: ${{ inputs.skip_perf && '1' || '' }}
9388

Makefile.cbm

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -34,30 +34,6 @@ TS_INCLUDE = $(CBM_DIR)/vendored/ts_runtime/include
3434
# This ensures we use our vendored copies instead of requiring system libicu-dev.
3535
TS_SRC = $(CBM_DIR)/vendored/ts_runtime/src
3636

37-
# ── Optional libgit2 (faster git history parsing) ────────────────
38-
# Auto-detected via pkg-config. Falls back to popen("git log ...") if absent
39-
# or too old for the git_allocator ABI used by the production allocator bind.
40-
PKG_CONFIG ?= pkg-config
41-
LIBGIT2_MIN_VERSION := 1.7.0
42-
LIBGIT2_AVAILABLE := $(shell $(PKG_CONFIG) --atleast-version=$(LIBGIT2_MIN_VERSION) libgit2 >/dev/null 2>&1 && echo yes || echo no)
43-
44-
ifeq ($(REQUIRE_LIBGIT2),1)
45-
ifneq ($(LIBGIT2_AVAILABLE),yes)
46-
LIBGIT2_VERSION := $(shell $(PKG_CONFIG) --modversion libgit2 2>/dev/null)
47-
$(error libgit2 >= $(LIBGIT2_MIN_VERSION) is required when REQUIRE_LIBGIT2=1; pkg-config found $(if $(LIBGIT2_VERSION),$(LIBGIT2_VERSION),none))
48-
endif
49-
endif
50-
51-
ifeq ($(LIBGIT2_AVAILABLE),yes)
52-
LIBGIT2_CFLAGS := $(shell $(PKG_CONFIG) --cflags libgit2 2>/dev/null)
53-
LIBGIT2_LIBS := $(shell $(PKG_CONFIG) --libs libgit2 2>/dev/null)
54-
LIBGIT2_FLAGS = -DHAVE_LIBGIT2 $(LIBGIT2_CFLAGS)
55-
else
56-
LIBGIT2_CFLAGS =
57-
LIBGIT2_FLAGS =
58-
LIBGIT2_LIBS =
59-
endif
60-
6137
# GCC-only warning suppressions (Clang rejects unknown -Wno-* with -Werror).
6238
# Detect GCC by checking for __GNUC__ without __clang__ — handles all versions.
6339
IS_GCC := $(shell echo | $(CC) -dM -E - 2>/dev/null | grep -q '__GNUC__' && ! echo | $(CC) -dM -E - 2>/dev/null | grep -q '__clang__' && echo yes || echo no)
@@ -72,8 +48,7 @@ CFLAGS_COMMON = -std=c11 -D_DEFAULT_SOURCE -D_GNU_SOURCE -Wall -Wextra -Werror \
7248
$(GCC_ONLY_FLAGS) \
7349
-Isrc -Ivendored -Ivendored/sqlite3 \
7450
-Ivendored/mimalloc/include \
75-
-I$(CBM_DIR) -I$(TS_INCLUDE) \
76-
$(LIBGIT2_FLAGS)
51+
-I$(CBM_DIR) -I$(TS_INCLUDE)
7752

7853
CXXFLAGS_COMMON = -std=c++14 -Wall -Wextra -Werror \
7954
-Wno-unused-parameter \
@@ -111,9 +86,9 @@ ifeq ($(STATIC),1)
11186
STATIC_FLAGS := -static
11287
endif
11388

114-
LDFLAGS = -lm -lstdc++ -lpthread -lz $(LIBGIT2_LIBS) $(WIN32_LIBS) $(STATIC_FLAGS)
115-
LDFLAGS_TEST = -lm -lstdc++ -lpthread -lz $(SANITIZE) $(LIBGIT2_LIBS) $(WIN32_LIBS)
116-
LDFLAGS_TSAN = -lm -lstdc++ -lpthread -lz -fsanitize=thread $(LIBGIT2_LIBS) $(WIN32_LIBS)
89+
LDFLAGS = -lm -lstdc++ -lpthread -lz $(WIN32_LIBS) $(STATIC_FLAGS)
90+
LDFLAGS_TEST = -lm -lstdc++ -lpthread -lz $(SANITIZE) $(WIN32_LIBS)
91+
LDFLAGS_TSAN = -lm -lstdc++ -lpthread -lz -fsanitize=thread $(WIN32_LIBS)
11792

11893
# ── Source files ─────────────────────────────────────────────────
11994

flake.nix

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,6 @@
4444
devShells = forAllSystems (pkgs: {
4545
default = pkgs.mkShell {
4646
inputsFrom = [ self.packages.${pkgs.system}.default ];
47-
# libgit2 is an optional dependency auto-detected via pkg-config at
48-
# build time. When present it accelerates git history parsing;
49-
# otherwise the build falls back to shelling out to `git log`.
50-
packages = [ pkgs.pkg-config pkgs.libgit2 ];
5147
};
5248
});
5349
};

internal/cbm/cbm.c

Lines changed: 1 addition & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,6 @@
2121
#include "mimalloc.h" // mi_malloc/mi_calloc/mi_realloc/mi_free/mi_usable_size — bind 3rd-party allocators (#424)
2222
#if defined(CBM_BIND_TS_ALLOCATOR) && CBM_BIND_TS_ALLOCATOR
2323
#include "sqlite3.h" // sqlite3_mem_methods, sqlite3_config, SQLITE_CONFIG_MALLOC — bind sqlite to mimalloc
24-
#if defined(HAVE_LIBGIT2)
25-
#include <git2/version.h>
26-
#if defined(LIBGIT2_VERSION_CHECK)
27-
#if !LIBGIT2_VERSION_CHECK(1, 7, 0)
28-
#error "HAVE_LIBGIT2 requires libgit2 >= 1.7.0 for git_allocator"
29-
#endif
30-
#elif defined(LIBGIT2_VER_MAJOR) && defined(LIBGIT2_VER_MINOR) && defined(LIBGIT2_VER_REVISION)
31-
#if ((LIBGIT2_VER_MAJOR * 1000000) + (LIBGIT2_VER_MINOR * 10000) + (LIBGIT2_VER_REVISION * 100)) < \
32-
1070000
33-
#error "HAVE_LIBGIT2 requires libgit2 >= 1.7.0 for git_allocator"
34-
#endif
35-
#else
36-
#error "HAVE_LIBGIT2 requires known libgit2 version macros for the >= 1.7.0 git_allocator guard"
37-
#endif
38-
#include <git2.h> // git_libgit2_opts, GIT_OPT_SET_ALLOCATOR — bind libgit2 to mimalloc
39-
#include <git2/sys/alloc.h> // git_allocator — not pulled in by <git2.h> (it's a sys/ header)
40-
#endif
4124
#endif
4225
#include <stdint.h> // uint32_t, uint64_t, int64_t
4326
#include <stdlib.h>
@@ -256,7 +239,7 @@ static TSParser *get_thread_parser(const TSLanguage *ts_lang, CBMLanguage lang)
256239

257240
// --- Allocator binding (defense-in-depth, #424) ---
258241

259-
/* Bind tree-sitter, sqlite3, and libgit2 to mimalloc explicitly so a correct
242+
/* Bind tree-sitter and sqlite3 to mimalloc explicitly so a correct
260243
* binary does NOT depend on the fragile MI_OVERRIDE symbol override. Under
261244
* MI_OVERRIDE=1 — particularly the Windows static-MinGW link with
262245
* --allow-multiple-definition — `malloc`/`free` can resolve to DIFFERENT
@@ -300,25 +283,6 @@ static int cbm_sqlite_meminit(void *appdata) {
300283
static void cbm_sqlite_memshutdown(void *appdata) {
301284
(void)appdata;
302285
}
303-
304-
#if defined(HAVE_LIBGIT2)
305-
/* libgit2 >= 1.7 git_allocator backed by mimalloc. The struct has exactly
306-
* three members: gmalloc(size_t,file,line), grealloc(ptr,size,file,line),
307-
* gfree(ptr). The file/line args are ignored. */
308-
static void *cbm_git_malloc(size_t n, const char *file, int line) {
309-
(void)file;
310-
(void)line;
311-
return mi_malloc(n);
312-
}
313-
static void *cbm_git_realloc(void *ptr, size_t size, const char *file, int line) {
314-
(void)file;
315-
(void)line;
316-
return mi_realloc(ptr, size);
317-
}
318-
static void cbm_git_free(void *ptr) {
319-
mi_free(ptr);
320-
}
321-
#endif /* HAVE_LIBGIT2 */
322286
#endif /* CBM_BIND_TS_ALLOCATOR */
323287

324288
void cbm_alloc_init(void) {
@@ -349,24 +313,6 @@ void cbm_alloc_init(void) {
349313
int sqlite_rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &cbm_sqlite_mem);
350314
assert(sqlite_rc == SQLITE_OK && "SQLITE_CONFIG_MALLOC must run before sqlite3_initialize");
351315
(void)sqlite_rc;
352-
353-
#if defined(HAVE_LIBGIT2)
354-
/* libgit2. GIT_OPT_SET_ALLOCATOR MUST be set BEFORE git_libgit2_init():
355-
* libgit2's git_allocator_global_init (run during init) installs the default
356-
* stdalloc only if no custom allocator is set yet
357-
* (`if (git__allocator.gmalloc != git_failalloc_malloc) return 0;`), and the
358-
* pre-init global is the fail-allocator. There is no allocator-reset on
359-
* git_libgit2_shutdown, so binding once here — before pass_githistory's
360-
* per-call git_libgit2_init/shutdown pairs ever run — persists for the whole
361-
* process. git_libgit2_opts(GIT_OPT_SET_ALLOCATOR,...) itself does not
362-
* allocate, so calling it before init is safe. */
363-
static git_allocator cbm_git_alloc = {
364-
cbm_git_malloc, /* gmalloc */
365-
cbm_git_realloc, /* grealloc */
366-
cbm_git_free, /* gfree */
367-
};
368-
git_libgit2_opts(GIT_OPT_SET_ALLOCATOR, &cbm_git_alloc);
369-
#endif /* HAVE_LIBGIT2 */
370316
#endif /* CBM_BIND_TS_ALLOCATOR */
371317
}
372318

internal/cbm/cbm.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -505,11 +505,11 @@ typedef struct {
505505

506506
// --- Public API ---
507507

508-
// Bind third-party allocators (tree-sitter, sqlite3, libgit2) to mimalloc as
508+
// Bind third-party allocators (tree-sitter, sqlite3) to mimalloc as
509509
// defense-in-depth, so they never depend on the fragile MI_OVERRIDE symbol
510510
// override (#424). MUST be called as the very first statement of main(), before
511511
// any sqlite3_open*/sqlite3_initialize (SQLITE_CONFIG_MALLOC returns
512-
// SQLITE_MISUSE once sqlite has initialized) and before any git_libgit2_init.
512+
// SQLITE_MISUSE once sqlite has initialized).
513513
// Idempotent (static guard); intended for single-threaded startup. cbm_init()
514514
// also calls it so non-main entry points (pipeline passes) still get the binds.
515515
// In the test build (no CBM_BIND_TS_ALLOCATOR) this is a no-op.

scripts/security-allowlist.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ src/mcp/mcp.c:cbm_popen:git ls-files count for auto-index (session_root validate
3434
src/mcp/mcp.c:cbm_popen:update check to api.github.com (hardcoded URL)
3535
src/mcp/mcp.c:popen:via cbm_popen wrapper calls
3636

37-
# ── Pipeline: git history parsing (fallback when libgit2 not available) ────
37+
# ── Pipeline: git history parsing (git log) ────────────────────────────────
3838
src/pipeline/pass_githistory.c:cbm_popen:git log for file history (path validated)
3939
src/pipeline/pass_githistory.c:popen:via cbm_popen wrapper call
4040

src/foundation/subprocess.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ static int cbm_run_posix(const cbm_proc_opts_t *opts, cbm_proc_result_t *out) {
225225
/* Child: redirect stdout+stderr to the log (or discard), then exec.
226226
* Use open()+dup2() (async-signal-safe, no malloc) rather than freopen():
227227
* the parent may be multithreaded (the MCP server holds worker/watcher/http
228-
* threads plus mimalloc/sqlite/libgit2 global state), and a fork() copies
228+
* threads plus mimalloc/sqlite global state), and a fork() copies
229229
* only the calling thread — a malloc between fork and exec could deadlock on
230230
* a lock another thread held at fork time. open/dup2/execv touch no heap. */
231231
const char *bin = opts->bin;

src/main.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ static void setup_signal_handlers(void) {
594594
}
595595

596596
int main(int argc, char **argv) {
597-
/* Defense-in-depth: bind tree-sitter, sqlite3, and libgit2 to mimalloc so a
597+
/* Defense-in-depth: bind tree-sitter and sqlite3 to mimalloc so a
598598
* correct binary does not rely on the fragile MI_OVERRIDE symbol override
599599
* (#424). MUST be the VERY FIRST statement: SQLITE_CONFIG_MALLOC has to run
600600
* before the first sqlite3_open* (cbm_mcp_server_new → cbm_store_open_memory

src/mcp/index_supervisor.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* module has no dependency on the response format.
1414
*
1515
* fork+exec only (never fork-and-run-in-child): the server holds persistent
16-
* threads plus mimalloc/sqlite/libgit2 global state with no pthread_atfork, so a
16+
* threads plus mimalloc/sqlite global state with no pthread_atfork, so a
1717
* fork without exec would be a latent deadlock. Recursion is prevented by an argv
1818
* flag (`--index-worker`), never an ambient env var.
1919
*/

src/pipeline/pass_githistory.c

Lines changed: 1 addition & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -105,116 +105,7 @@ static void commit_free(commit_t *c) {
105105
free(c->files);
106106
}
107107

108-
/* ── libgit2-based git log parsing (preferred) ────────────────────── */
109-
110-
#ifdef HAVE_LIBGIT2
111-
#include <git2.h>
112-
#include <time.h>
113-
114-
static int parse_git_log(const char *repo_path, commit_t **out, int *out_count) {
115-
*out = NULL;
116-
*out_count = 0;
117-
118-
git_libgit2_init();
119-
120-
git_repository *repo = NULL;
121-
if (git_repository_open(&repo, repo_path) != 0) {
122-
git_libgit2_shutdown();
123-
return CBM_NOT_FOUND;
124-
}
125-
126-
/* Walk from HEAD, sorted chronologically */
127-
git_revwalk *walker = NULL;
128-
if (git_revwalk_new(&walker, repo) != 0) {
129-
git_repository_free(repo);
130-
git_libgit2_shutdown();
131-
return CBM_NOT_FOUND;
132-
}
133-
git_revwalk_sorting(walker, GIT_SORT_TIME);
134-
git_revwalk_push_head(walker);
135-
136-
/* 1 year cutoff, max 10k commits */
137-
time_t cutoff = time(NULL) - (365L * 24 * 3600);
138-
int max_commits = 10000;
139-
140-
int cap = CBM_SZ_64;
141-
commit_t *commits = malloc(cap * sizeof(commit_t));
142-
int count = 0;
143-
144-
git_oid oid;
145-
while (git_revwalk_next(&oid, walker) == 0 && count < max_commits) {
146-
git_commit *commit = NULL;
147-
if (git_commit_lookup(&commit, repo, &oid) != 0) {
148-
continue;
149-
}
150-
151-
/* Check if commit is within the 6-month window */
152-
git_time_t ct = git_commit_time(commit);
153-
if ((time_t)ct < cutoff) {
154-
git_commit_free(commit);
155-
break; /* sorted by time — all subsequent commits are older */
156-
}
157-
158-
/* Get commit tree and parent tree for diff */
159-
git_tree *tree = NULL;
160-
git_tree *parent_tree = NULL;
161-
git_commit_tree(&tree, commit);
162-
163-
unsigned int nparents = git_commit_parentcount(commit);
164-
if (nparents > 0) {
165-
git_commit *parent = NULL;
166-
if (git_commit_parent(&parent, commit, 0) == 0) {
167-
git_commit_tree(&parent_tree, parent);
168-
git_commit_free(parent);
169-
}
170-
}
171-
172-
/* Diff parent_tree → tree to find changed files */
173-
git_diff *diff = NULL;
174-
git_diff_options diff_opts;
175-
git_diff_options_init(&diff_opts, GIT_DIFF_OPTIONS_VERSION);
176-
if (git_diff_tree_to_tree(&diff, repo, parent_tree, tree, &diff_opts) == 0) {
177-
commit_t current = {0};
178-
179-
size_t ndeltas = git_diff_num_deltas(diff);
180-
for (size_t d = 0; d < ndeltas; d++) {
181-
const git_diff_delta *delta = git_diff_get_delta(diff, d);
182-
const char *path = delta->new_file.path;
183-
if (path && cbm_is_trackable_file(path)) {
184-
commit_add_file(&current, path);
185-
}
186-
}
187-
188-
if (current.count > 0) {
189-
if (count >= cap) {
190-
cap *= PAIR_LEN;
191-
commits = safe_realloc(commits, cap * sizeof(commit_t));
192-
}
193-
current.timestamp = (long long)ct;
194-
commits[count++] = current;
195-
} else {
196-
commit_free(&current);
197-
}
198-
git_diff_free(diff);
199-
}
200-
201-
if (parent_tree) {
202-
git_tree_free(parent_tree);
203-
}
204-
git_tree_free(tree);
205-
git_commit_free(commit);
206-
}
207-
208-
git_revwalk_free(walker);
209-
git_repository_free(repo);
210-
git_libgit2_shutdown();
211-
212-
*out = commits;
213-
*out_count = count;
214-
return 0;
215-
}
216-
217-
#else /* !HAVE_LIBGIT2 — popen fallback */
108+
/* ── git log parsing (popen "git log") ────────────────────────────── */
218109

219110
static int parse_git_log(const char *repo_path, commit_t **out, int *out_count) {
220111
*out = NULL;
@@ -297,8 +188,6 @@ static int parse_git_log(const char *repo_path, commit_t **out, int *out_count)
297188
return 0;
298189
}
299190

300-
#endif /* HAVE_LIBGIT2 */
301-
302191
/* Callback to free hash table entries. */
303192
static void free_counter(const char *key, void *val, void *ud) {
304193
(void)ud;

0 commit comments

Comments
 (0)