diff --git a/src/build.rs b/src/build.rs index c1ef2567a4..c2392fb786 100644 --- a/src/build.rs +++ b/src/build.rs @@ -7,7 +7,7 @@ use std::path::Path; use std::ptr; use crate::util::{self, Binding}; -use crate::{panic, raw, Error, FetchOptions, IntoCString, Oid, Repository, Tree}; +use crate::{panic, raw, Error, FetchOptions, Index, IntoCString, Oid, Repository, Tree}; use crate::{CheckoutNotificationType, DiffFile, FileMode, Remote}; /// A builder struct which is used to build configuration for cloning a new git @@ -83,6 +83,8 @@ pub struct CheckoutBuilder<'cb> { their_label: Option, our_label: Option, ancestor_label: Option, + baseline: Option<&'cb Tree<'cb>>, + baseline_index: Option<&'cb Index>, target_dir: Option, paths: Vec, path_ptrs: Vec<*const c_char>, @@ -332,6 +334,8 @@ impl<'cb> CheckoutBuilder<'cb> { file_perm: None, path_ptrs: Vec::new(), paths: Vec::new(), + baseline: None, + baseline_index: None, target_dir: None, ancestor_label: None, our_label: None, @@ -533,6 +537,18 @@ impl<'cb> CheckoutBuilder<'cb> { self } + /// The expected content of the working directory. Overridden by [Self::baseline_index]. + pub fn baseline(&mut self, tree: &'cb Tree<'cb>) -> &mut CheckoutBuilder<'cb> { + self.baseline = Some(tree); + self + } + + /// The expected content of the working directory. Overrides [Self::baseline]. + pub fn baseline_index(&mut self, index: &'cb Index) -> &mut CheckoutBuilder<'cb> { + self.baseline_index = Some(index); + self + } + /// Set the directory to check out to pub fn target_dir(&mut self, dst: &Path) -> &mut CheckoutBuilder<'cb> { // Normal file path OK (does not need Windows conversion). @@ -610,6 +626,12 @@ impl<'cb> CheckoutBuilder<'cb> { if let Some(ref c) = self.target_dir { opts.target_directory = c.as_ptr(); } + if let Some(ref c) = self.baseline { + opts.baseline = c.raw(); + } + if let Some(ref c) = self.baseline_index { + opts.baseline_index = c.raw(); + } if let Some(ref c) = self.ancestor_label { opts.ancestor_label = c.as_ptr(); } diff --git a/src/repo.rs b/src/repo.rs index 00d2aadb6f..4e29a261b9 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -3675,9 +3675,11 @@ mod tests { use crate::build::CheckoutBuilder; use crate::AttrCheckFlags; use crate::Error; + use crate::Index; use crate::ObjectFormat; #[cfg(feature = "unstable-sha256")] use crate::RepositoryInitOptions; + use crate::Tree; use crate::{CherrypickOptions, MergeFileOptions}; use crate::{ Config, ObjectType, Oid, Repository, ResetType, Signature, SubmoduleIgnore, SubmoduleUpdate, @@ -3686,6 +3688,7 @@ mod tests { use std::ffi::OsStr; use std::fs; use std::path::Path; + use std::path::PathBuf; use libgit2_sys as raw; use tempfile::TempDir; @@ -3892,6 +3895,92 @@ mod tests { assert_eq!(format, "sha256"); } + fn setup_checkout_with_baseline<'repo>( + repo: &'repo Repository, + ) -> (PathBuf, PathBuf, PathBuf, Tree<'repo>, Tree<'repo>) { + let blank_commit = repo.revparse_single("HEAD").unwrap(); + + let (tree_a, path_a) = { + let mut index = repo.index().unwrap(); + let path = Path::new(repo.workdir().unwrap()).join("file_a"); + fs::write(&path, "a").unwrap(); + index.add_path(Path::new("file_a")).unwrap(); + let id = index.write_tree().unwrap(); + (repo.find_tree(id).unwrap(), path) + }; + + t!(repo.reset(&blank_commit, ResetType::Hard, None)); + + let (tree_b, path_b) = { + let mut index = repo.index().unwrap(); + let path = Path::new(repo.workdir().unwrap()).join("file_b"); + fs::write(&path, "b").unwrap(); + index.add_path(Path::new("file_b")).unwrap(); + let id = index.write_tree().unwrap(); + (repo.find_tree(id).unwrap(), path) + }; + + // Leave file_b in index. + + let path_c = { + let mut index = repo.index().unwrap(); + let path = Path::new(repo.workdir().unwrap()).join("file_c"); + fs::write(&path, "c").unwrap(); + index.add_path(Path::new("file_c")).unwrap(); + path + }; + + (path_a, path_b, path_c, tree_a, tree_b) + } + + #[test] + fn checkout_with_baseline() { + let (_td, repo) = crate::test::repo_init(); + let (path_a, path_b, path_c, tree_a, tree_b) = setup_checkout_with_baseline(&repo); + + let mut opts = CheckoutBuilder::new(); + opts.baseline(&tree_b); + t!(repo.checkout_tree(&tree_a.into_object(), Some(&mut opts))); + + assert!( + fs::exists(&path_a).unwrap(), + "we are checking out a tree that has a" + ); + assert!( + !fs::exists(&path_b).unwrap(), + "we are checking out from a baseline that has b to a tree that does not have b" + ); + assert!( + fs::exists(&path_c).unwrap(), + "c is related to neither baseline nor tree and is preserved" + ); + } + + #[test] + fn checkout_with_baseline_index() { + let (_td, repo) = crate::test::repo_init(); + let (path_a, path_b, path_c, tree_a, tree_b) = setup_checkout_with_baseline(&repo); + + let mut index = Index::new().unwrap(); + index.read_tree(&tree_b).unwrap(); + let mut opts = CheckoutBuilder::new(); + opts.baseline_index(&index); + t!(repo.checkout_tree(&tree_a.into_object(), Some(&mut opts))); + + assert!( + fs::exists(&path_a).unwrap(), + "we are checking out a tree that has a" + ); + assert!( + !fs::exists(&path_b).unwrap(), + "we are checking out from a baseline that has b to a tree that does not have b" + ); + assert!( + fs::exists(&path_c).unwrap(), + "c is related to neither baseline nor tree and is preserved" + ); + } + #[test] fn makes_dirs() { let td = TempDir::new().unwrap();