Ray's Knowledge Base

libgit2's cleanup_state also deletes BISECT_LOG, which breaks a bisect in progress

PitfallVerified 28 Sep 2026Holds anywhere
Pitfall. The symptom, what causes it, and the fix that was run and seen to work.

Symptom#

During a git bisect, a tool built on libgit2 finishes a merge, cherry-pick, revert or rebase and cleans up its state with Repository::cleanup_state() (git_repository_state_cleanup). Afterwards .git/BISECT_LOG is gone, so git bisect log and git bisect replay lose the session's history.

Cause#

libgit2's state_files list in src/libgit2/repository.c, which git_repository_state_cleanup deletes, holds MERGE_HEAD, MERGE_MODE, MERGE_MSG, REVERT_HEAD, CHERRY_PICK_HEAD, BISECT_LOG, rebase-merge, rebase-apply and sequencer. git itself does not remove the bisect log when a merge or pick ends.

Fix#

Wrap the call and keep the log:

fn end_operation(repo: &Repository) -> Result<(), GitError> {
    let log = repo.path().join("BISECT_LOG");
    let saved = std::fs::read(&log).ok();
    repo.cleanup_state()?;
    if let Some(bytes) = saved {
        std::fs::write(&log, bytes)?;
    }
    Ok(())
}

Use it for every cleanup_state() call.

Evidence#

rgit, 2026-09-27, libgit2-sys 0.18.8 (libgit2 1.9.7): the test merge_during_bisect_keeps_the_bisect_log in crates/rgit-cli/tests/sequencer.rs runs git bisect start side main, then rgit merge other. It failed without the wrapper (no BISECT_LOG) and passed with it. A clean cherry-pick did not show the bug, because that path never called cleanup_state. Fix commit 9ead857.