rust-lang / rust-lang/git2-rs

`index.add_frombuffer` not adding index unless flags changed

Open
#1,086 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
2.1k
Forks
450
Avg merge
11m
Merged PRs (30d)
1

Description

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", "caleb@gitbutler.com").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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the reproduction in main.rs with the dependencies in Cargo.toml, then trace the Index::add_frombuffer entry point and how IndexEntry flags are handled. Done means the resolved entry is included in the written tree without manually clearing our.flags, with a regression test covering the reported case.

Written by the indexing model from the issue text.

Assessment

Tech stack
git, rust
Domain
devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.