git2-rs icon indicating copy to clipboard operation
git2-rs copied to clipboard

`index.add_frombuffer` not adding index unless flags changed

Open Caleb-T-Owens opened this issue 1 year ago • 0 comments

I was writing some tests for our index resolving code, and noticed that when we're calling index.add_frombuffer if we don't clear the flags of our our IndexEntry, the tree that we write out ends up with that file missing.

Repro:

main.rs

use std::{
    fs,
    path::{Path, PathBuf},
};

use git2::IndexConflict;
use tempfile::tempdir;

/// Commit whatever is in the current working directory
fn commit<'a>(
    repository: &'a git2::Repository,
    parent: Option<&git2::Commit<'a>>,
) -> git2::Commit<'a> {
    let signature = git2::Signature::now("Caleb", "[email protected]").unwrap();

    let mut index = repository.index().unwrap();
    // Make sure we're not having weird cached state
    index.read(true).unwrap();
    index
        .add_all(["*"], git2::IndexAddOption::DEFAULT, None)
        .unwrap();

    let commit = repository
        .commit(
            None,
            &signature,
            &signature,
            "Committee",
            &repository.find_tree(index.write_tree().unwrap()).unwrap(),
            parent.map(|c| vec![c]).unwrap_or_default().as_slice(),
        )
        .unwrap();

    repository.find_commit(commit).unwrap()
}

fn bytes_to_path(path: &[u8]) -> PathBuf {
    let path = std::str::from_utf8(path).unwrap();
    Path::new(path).to_owned()
}

fn in_memory_repository(repository: &git2::Repository) -> git2::Repository {
    let repository = git2::Repository::open(repository.path()).unwrap();
    repository
        .odb()
        .unwrap()
        .add_new_mempack_backend(999)
        .unwrap();
    repository
}

fn main() {
    let tempdir = tempdir().unwrap();

    let repository = git2::Repository::init(tempdir.path()).unwrap();

    // Make some commits
    fs::write(tempdir.path().join("foo.txt"), "a").unwrap();
    let a = commit(&repository, None);
    fs::write(tempdir.path().join("foo.txt"), "b").unwrap();
    let b = commit(&repository, None);
    fs::write(tempdir.path().join("foo.txt"), "c").unwrap();
    let c = commit(&repository, None);

    let in_memory_repository = in_memory_repository(&repository);

    let mut index: git2::Index = repository
        .merge_trees(
            &a.tree().unwrap(), // Base
            &b.tree().unwrap(), // Ours
            &c.tree().unwrap(), // Theirs
            None,
        )
        .unwrap();

    in_memory_repository.set_index(&mut index).unwrap();

    assert!(index.has_conflicts());

    let mut conflicts = index.conflicts().unwrap().flatten().collect::<Vec<_>>();

    assert_eq!(conflicts.len(), 1);
    let conflict = conflicts.first_mut().unwrap();

    let IndexConflict {
        ancestor: Some(ancestor),
        our: Some(our),
        their: Some(their),
    } = conflict
    else {
        panic!("Ahh");
    };

    index.remove_path(&bytes_to_path(&ancestor.path)).unwrap();
    index.remove_path(&bytes_to_path(&their.path)).unwrap();

    let blob = repository.find_blob(our.id).unwrap();
    // our.flags = 0;
    index.add_frombuffer(&our, blob.content()).unwrap();

    let tree = index.write_tree_to(&repository).unwrap();
    repository.find_tree(tree).unwrap();

    assert_eq!(
        tree,
        git2::Oid::from_str("6e4760ce692776132d52ac0787b7dc1ca2ac15f4").unwrap()
    ) // Ends up being 4b825dc642cb6eb9a060e54bf8d69288fbee4904 (empty tree)
}

Cargo.toml

[package]
name = "strange_behaviour"
version = "0.1.0"
edition = "2021"

[dependencies]
git2 = { version = "0.19.0", features = [
    "vendored-openssl",
    "vendored-libgit2",
] }
tempfile = "3.10"

If we set our.flags to 0 ourselves, before we call add_frombuffer, then we end up with the tree that we expected.

It's a bit strange that we're ending up with this empty tree rather than having some form of error

Caleb-T-Owens avatar Sep 24 '24 16:09 Caleb-T-Owens