Skip to content

Add TreeUpdateBuilder to support git_tree_create_updated #648

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions libgit2-sys/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,21 @@ pub type git_treebuilder_filter_cb =

pub type git_revwalk_hide_cb = Option<extern "C" fn(*const git_oid, *mut c_void) -> c_int>;

git_enum! {
pub enum git_tree_update_t {
GIT_TREE_UPDATE_UPSERT = 0,
GIT_TREE_UPDATE_REMOVE = 1,
}
}

#[repr(C)]
pub struct git_tree_update {
pub action: git_tree_update_t,
pub id: git_oid,
pub filemode: git_filemode_t,
pub path: *const c_char,
}

#[repr(C)]
#[derive(Copy, Clone)]
pub struct git_buf {
Expand Down Expand Up @@ -2531,6 +2546,13 @@ extern "C" {
callback: git_treewalk_cb,
payload: *mut c_void,
) -> c_int;
pub fn git_tree_create_updated(
out: *mut git_oid,
repo: *mut git_repository,
baseline: *mut git_tree,
nupdates: usize,
updates: *const git_tree_update,
) -> c_int;

// treebuilder
pub fn git_treebuilder_new(
Expand Down
100 changes: 96 additions & 4 deletions src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use std::path::Path;
use std::ptr;

use crate::util::{self, Binding};
use crate::{panic, raw, Error, FetchOptions, IntoCString, Repository};
use crate::{CheckoutNotificationType, DiffFile, Remote};
use crate::{panic, raw, Error, FetchOptions, IntoCString, Oid, Repository, Tree};
use crate::{CheckoutNotificationType, DiffFile, FileMode, Remote};

/// A builder struct which is used to build configuration for cloning a new git
/// repository.
Expand Down Expand Up @@ -64,6 +64,12 @@ pub struct RepoBuilder<'cb> {
pub type RemoteCreate<'cb> =
dyn for<'a> FnMut(&'a Repository, &str, &str) -> Result<Remote<'a>, Error> + 'cb;

/// A builder struct for git tree updates, for use with `git_tree_create_updated`.
pub struct TreeUpdateBuilder {
updates: Vec<raw::git_tree_update>,
paths: Vec<CString>,
}

/// A builder struct for configuring checkouts of a repository.
pub struct CheckoutBuilder<'cb> {
their_label: Option<CString>,
Expand Down Expand Up @@ -674,10 +680,79 @@ extern "C" fn notify_cb(
.unwrap_or(2)
}

impl Default for TreeUpdateBuilder {
fn default() -> Self {
Self::new()
}
}

impl TreeUpdateBuilder {
/// Create a new empty series of updates.
pub fn new() -> Self {
Self {
updates: Vec::new(),
paths: Vec::new(),
}
}

/// Add an update removing the specified `path` from a tree.
pub fn remove<T: IntoCString>(&mut self, path: T) -> &mut Self {
let path = util::cstring_to_repo_path(path).unwrap();
let path_ptr = path.as_ptr();
self.paths.push(path);
self.updates.push(raw::git_tree_update {
action: raw::GIT_TREE_UPDATE_REMOVE,
id: raw::git_oid {
id: [0; raw::GIT_OID_RAWSZ],
},
filemode: raw::GIT_FILEMODE_UNREADABLE,
path: path_ptr,
});
self
}

/// Add an update setting the specified `path` to a specific Oid, whether it currently exists
/// or not.
///
/// Note that libgit2 does not support an upsert of a previously removed path, or an upsert
/// that changes the type of an object (such as from tree to blob or vice versa).
pub fn upsert<T: IntoCString>(&mut self, path: T, id: Oid, filemode: FileMode) -> &mut Self {
let path = util::cstring_to_repo_path(path).unwrap();
let path_ptr = path.as_ptr();
self.paths.push(path);
self.updates.push(raw::git_tree_update {
action: raw::GIT_TREE_UPDATE_UPSERT,
id: unsafe { *id.raw() },
filemode: u32::from(filemode) as raw::git_filemode_t,
path: path_ptr,
});
self
}

/// Create a new tree from the specified baseline and this series of updates.
///
/// The baseline tree must exist in the specified repository.
pub fn create_updated(&mut self, repo: &Repository, baseline: &Tree<'_>) -> Result<Oid, Error> {
let mut ret = raw::git_oid {
id: [0; raw::GIT_OID_RAWSZ],
};
unsafe {
try_call!(raw::git_tree_create_updated(
&mut ret,
repo.raw(),
baseline.raw(),
self.updates.len(),
self.updates.as_ptr()
));
Ok(Binding::from_raw(&ret as *const _))
}
}
}

#[cfg(test)]
mod tests {
use super::{CheckoutBuilder, RepoBuilder};
use crate::{CheckoutNotificationType, Repository};
use super::{CheckoutBuilder, RepoBuilder, TreeUpdateBuilder};
use crate::{CheckoutNotificationType, FileMode, Repository};
use std::fs;
use std::path::Path;
use tempfile::TempDir;
Expand Down Expand Up @@ -707,6 +782,23 @@ mod tests {
assert!(RepoBuilder::new().branch("foo").clone(&url, &dst).is_err());
}

#[test]
fn smoke_tree_create_updated() {
let (_tempdir, repo) = crate::test::repo_init();
let (_, tree_id) = crate::test::commit(&repo);
let tree = t!(repo.find_tree(tree_id));
assert!(tree.get_name("bar").is_none());
let foo_id = tree.get_name("foo").unwrap().id();
let tree2_id = t!(TreeUpdateBuilder::new()
.remove("foo")
.upsert("bar/baz", foo_id, FileMode::Blob)
.create_updated(&repo, &tree));
let tree2 = t!(repo.find_tree(tree2_id));
assert!(tree2.get_name("foo").is_none());
let baz_id = tree2.get_path(Path::new("bar/baz")).unwrap().id();
assert_eq!(foo_id, baz_id);
}

/// Issue regression test #365
#[test]
fn notify_callback() {
Expand Down