rust-lang / rust-lang/git2-rs

Pathspec support for magic words

Open
#1,132 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

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

Description

Hi all, I'm using git2-rs's diff API interface and I'm trying to filter out some paths using pathspec.

From Git's user manual, pathspecs should support "magic words", which would allow me to write something like this :(exclude)*.toml to exclude all toml files when computing the diff. This is important to me because it allows to save a lot of computation time wasted on files I do not care about (and have to filter out manually after the diff).

When using Git, this works as expected, however, I can't replicate the same behaviour using git2-rs from my Rust application.

Any idea on how to workaround on this issue? Am I using the APIs wrongly? Or even pointers on how to implement this in the library (I'm a newbie to Rust, but I might give it a try...).

Here's an example Rust application that showcases the problem.

use std::{env::args, error::Error};

use git2::{DiffFindOptions, DiffOptions, ObjectType, Repository};

pub fn diff(
    repo: &Repository,
    parent: &str,
    child: &str,
    pathspecs: &[String],
) -> Result<(), git2::Error> {
    let parent = repo.revparse_single(parent)?;
    let child = repo.revparse_single(child)?;
    let p_obj = parent.peel(ObjectType::Tree)?;
    let c_obj = child.peel(ObjectType::Tree)?;
    let p_tree = p_obj.as_tree().unwrap();
    let c_tree = c_obj.as_tree().unwrap();
    let mut rename_opts = DiffFindOptions::new();
    rename_opts.renames(true);
    rename_opts.rename_limit(200);
    rename_opts.renames_from_rewrites(true);
    let mut opts = DiffOptions::new();
    opts.update_index(false);
    opts.context_lines(0);
    for p in pathspecs {
        opts.pathspec(p);
    }
    let mut diff = repo.diff_tree_to_tree(Some(p_tree), Some(c_tree), Some(&mut opts))?;
    diff.find_similar(Some(&mut rename_opts))?;
    for delta in diff.deltas() {
        println!("{}", delta.old_file().path().unwrap().to_str().unwrap());
    }
    Ok(())
}

fn main() -> Result<(), Box<dyn Error>> {
    let mut args = args();
    if args.len() <= 3 {
        println!("Usage: <repo> <sha> <sha> [pathspec...]");
        return Err("Wrong number of arguments".into());
    }
    args.next();
    let repo = args.next().expect("Repository must be provided");
    let sha_parent = args.next().expect("Parent sha must be provided");
    let sha_child = args.next().expect("Child sha must be provided");
    let pathspecs = args.collect::<Vec<String>>();
    let repo = Repository::open(repo)?;
    diff(&repo, &sha_parent, &sha_child, pathspecs.as_slice())?;
    Ok(())
}

To illustrate the problem, you can follow these steps (after setting up the sample cargo app above):

  1. Run Git on an example repository
git clone https://github.com/rust-lang/git2-rs /tmp/git2-rs
git -C /tmp/git2-rs/ diff --name-only d1ae3b6c 7b7be439 ":(exclude)*.toml"

Which outputs

libgit2-sys/lib.rs
src/repo.rs

Note that also Cargo.toml and git2-curl/Cargo.toml have changed, but they are not part of the diff.

  1. Run sample app
cargo run -- /tmp/git2-rs/ d1ae3b6c 7b7be439 ":(exclude)*.toml"

No output is display at all if you provide a pathspec with any magic word. In this case, the expected output is the two .rs files seen above.

Morover, combinations like these seem to fail too:

cargo run -- /tmp/git2-rs/ d1ae3b6c 7b7be439 "*.toml" ":(exclude)*.toml"
# Outputs
Cargo.toml
git2-curl/Cargo.toml

Git correctly prints out nothing.

Thank you! Looking forward to your answers :)

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 provided sample against git2-rs and compare DiffOptions::pathspec with repo.diff_tree_to_tree for magic-word pathspecs. Trace how the Rust API passes pathspecs to libgit2, then verify the documented exclude example and mixed include/exclude example produce the expected file lists.

Written by the indexing model from the issue text.

Assessment

Tech stack
git, rust
Domain
tooling
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.