rust-lang/git2-rs

unstage file

harilet opened this issue · 6 comments

I was trying to unstage files. I can add file fine by using

let mut index = repo.index().unwrap();
index.add_path(Path::new(&path)).unwrap();

I saw we can use the remove_path like this

let mut index = repo.index().unwrap();
index.remove_path(Path::new(&path)).unwrap();
index.write().unwrap();

but this deletes file not remove changes
Image

what can I do?
and I saw this remove but the 2th parameter stage is an int but have no more detail
https://docs.rs/git2/latest/git2/struct.Index.html#method.remove

ehuss commented

The 2nd parameter is usually 0 for the main index. Other numbers are used when dealing with merge conflicts and such.

@ehuss ok I understand that now.

so what can I do to just unstage changes from a file, any ideas?

ehuss commented

It's index.remove(path, 0)

@ehuss
similar to remove_path remove also just deletes the file from the index not unstage the changes

let mut index = repo.index().unwrap();
index.remove(Path::new(&path),0).unwrap();
index.write().unwrap();

output
Image

I want to unstage changes not delete the file from the index

ehuss commented

Oh, sorry, I misremembered how that worked. There are a few different approaches to emulate restore --staged, which is generally a pretty complicated command. One is to use reset_default, and another is to rewrite the index based on the head tree.

For example:

use git2::*;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let repo = Repository::init("repo")?;
    // Add a file and commit it.
    std::fs::write("repo/foo.txt", "")?;
    let mut index = repo.index()?;
    index.add_all(["*"].iter(), git2::IndexAddOption::DEFAULT, None)?;
    index.write()?;
    let tree_id = index.write_tree()?;
    let sig = repo.signature()?;
    repo.commit(
        Some("HEAD"),
        &sig,
        &sig,
        "test",
        &repo.find_tree(tree_id)?,
        &[],
    )?;

    // Modify the file and stage it.
    std::fs::write("repo/foo.txt", "this is a test")?;
    let mut index = repo.index()?;
    index.add_all(["*"].iter(), git2::IndexAddOption::DEFAULT, None)?;
    index.write()?;

    std::process::Command::new("git")
        .arg("status")
        .current_dir("repo")
        .status()?;

    // Unstage the file
    let head = repo.head()?;
    let commit = head.peel_to_commit()?;
    repo.reset_default(Some(commit.as_object()), &["foo.txt"])?;

    std::process::Command::new("git")
        .arg("status")
        .current_dir("repo")
        .status()?;
    Ok(())
}

The other approach writing the index is a bit more complicated, and I may not recommend it:

// Unstage the file
let mut index = repo.index()?;
let head = repo.head()?;
let commit = head.peel_to_commit()?;
let head_tree = commit.tree()?;
let tree_entry = head_tree.get_path(std::path::Path::new("foo.txt"))?;
let mut index_entry = index.get_path(std::path::Path::new("foo.txt"), 0).expect("is staged");
// May need to investigate if other index entry fields should be updated...
index_entry.id = tree_entry.id();
index_entry.mode = tree_entry.filemode() as u32;
index.add(&index_entry)?;
index.write()?;

@ehuss I got it, the first method worked thanks