Search code examples
libgit2git2-rs

git-rs leaving index in odd state


I'm using libgit2 via git-rs. I'm trying to commit things which is working, however, old files are showing up as deleted and/or staged even after the commit. How can I clean this up?

let repo: Repository = ...

let head_commit = match repo.head() {
    Ok(head) => head.peel_to_commit().ok(),
    Err(_) => None,
};

let head_tree = match head_commit {
    Some(ref commit) => commit.tree().ok(),
    None => None,
};

let mut diff_options = DiffOptions::new();
diff_options
    .include_untracked(true)
    .recurse_untracked_dirs(true);

let diff_result = self
    .repo
    .diff_tree_to_workdir_with_index(head_tree.as_ref(), Some(&mut diff_options));

let diff_deltas: Vec<_> = match diff_result {
    Ok(ref diff) => diff.deltas().collect(),
    Err(_) => Vec::new(),
};

if diff_deltas.is_empty() {
    info!("no files changed");
    return Ok(());
}

let mut index = .repo.index()?;

for diff_delta in diff_deltas {
    let delta = diff_delta.status();

    match delta {
        Delta::Added
        | Delta::Copied
        | Delta::Modified
        | Delta::Renamed
        | Delta::Untracked
        | Delta::Unmodified => {
            let path = diff_delta.new_file().path().unwrap();
            debug!("Staging {:?} file: {:?}", delta, path);
            index.add_path(path)?;
        }
        Delta::Deleted => {
            let path = diff_delta.old_file().path().unwrap();
            debug!("Unstaging {:?} file: {:?}", delta, path);
            index.remove_path(path)?;
        }
        _ => debug!("skipping {:?} file", delta),
    }
}

let index_oid = index.write_tree()?;
let index_tree = self.repo.find_tree(index_oid)?;

let sig = Signature::new(&self.committer.name, &self.committer.email, &time)?;

let parents: Vec<_> = [&head_commit].iter().flat_map(|c| c.as_ref()).collect();

repo.commit(Some("HEAD"), &sig, &sig, message, &index_tree, &parents)?;

index.clear().unwrap();

Solution

  • As @user2722968 pointed out, you must call both index.write() and index.write_tree().

    index.write_tree() will create a tree (or trees) from the index, returning the root tree object. This can be used to create a commit.

    The trouble here is that you have updated HEAD with the commit that you've created. When you run git status, then that will compare the working directory to the index to the HEAD commit.

    In this case, you have made changes to the index, you've updated the index in memory and used that to update HEAD. But you haven't actually written the index itself to disk, so you will see that the working directory does not match the index for any modified files, nor does the index match HEAD. (The working directory and HEAD contents will match, though that is never actually tested by git.)

    Once you call index.write() then the working directory, the index and the HEAD commit will all be identical, and thus you'll see the expected (empty) status.