Skip to content

Commit d8d0717

Browse files
authored
Merge pull request #648 from joshtriplett/git_tree_create_updated
Add TreeUpdateBuilder to support git_tree_create_updated
2 parents eca10db + d23a94f commit d8d0717

File tree

2 files changed

+118
-4
lines changed

2 files changed

+118
-4
lines changed

libgit2-sys/lib.rs

+22
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,21 @@ pub type git_treebuilder_filter_cb =
704704

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

707+
git_enum! {
708+
pub enum git_tree_update_t {
709+
GIT_TREE_UPDATE_UPSERT = 0,
710+
GIT_TREE_UPDATE_REMOVE = 1,
711+
}
712+
}
713+
714+
#[repr(C)]
715+
pub struct git_tree_update {
716+
pub action: git_tree_update_t,
717+
pub id: git_oid,
718+
pub filemode: git_filemode_t,
719+
pub path: *const c_char,
720+
}
721+
707722
#[repr(C)]
708723
#[derive(Copy, Clone)]
709724
pub struct git_buf {
@@ -2531,6 +2546,13 @@ extern "C" {
25312546
callback: git_treewalk_cb,
25322547
payload: *mut c_void,
25332548
) -> c_int;
2549+
pub fn git_tree_create_updated(
2550+
out: *mut git_oid,
2551+
repo: *mut git_repository,
2552+
baseline: *mut git_tree,
2553+
nupdates: usize,
2554+
updates: *const git_tree_update,
2555+
) -> c_int;
25342556

25352557
// treebuilder
25362558
pub fn git_treebuilder_new(

src/build.rs

+96-4
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use std::path::Path;
77
use std::ptr;
88

99
use crate::util::{self, Binding};
10-
use crate::{panic, raw, Error, FetchOptions, IntoCString, Repository};
11-
use crate::{CheckoutNotificationType, DiffFile, Remote};
10+
use crate::{panic, raw, Error, FetchOptions, IntoCString, Oid, Repository, Tree};
11+
use crate::{CheckoutNotificationType, DiffFile, FileMode, Remote};
1212

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

67+
/// A builder struct for git tree updates, for use with `git_tree_create_updated`.
68+
pub struct TreeUpdateBuilder {
69+
updates: Vec<raw::git_tree_update>,
70+
paths: Vec<CString>,
71+
}
72+
6773
/// A builder struct for configuring checkouts of a repository.
6874
pub struct CheckoutBuilder<'cb> {
6975
their_label: Option<CString>,
@@ -674,10 +680,79 @@ extern "C" fn notify_cb(
674680
.unwrap_or(2)
675681
}
676682

683+
impl Default for TreeUpdateBuilder {
684+
fn default() -> Self {
685+
Self::new()
686+
}
687+
}
688+
689+
impl TreeUpdateBuilder {
690+
/// Create a new empty series of updates.
691+
pub fn new() -> Self {
692+
Self {
693+
updates: Vec::new(),
694+
paths: Vec::new(),
695+
}
696+
}
697+
698+
/// Add an update removing the specified `path` from a tree.
699+
pub fn remove<T: IntoCString>(&mut self, path: T) -> &mut Self {
700+
let path = util::cstring_to_repo_path(path).unwrap();
701+
let path_ptr = path.as_ptr();
702+
self.paths.push(path);
703+
self.updates.push(raw::git_tree_update {
704+
action: raw::GIT_TREE_UPDATE_REMOVE,
705+
id: raw::git_oid {
706+
id: [0; raw::GIT_OID_RAWSZ],
707+
},
708+
filemode: raw::GIT_FILEMODE_UNREADABLE,
709+
path: path_ptr,
710+
});
711+
self
712+
}
713+
714+
/// Add an update setting the specified `path` to a specific Oid, whether it currently exists
715+
/// or not.
716+
///
717+
/// Note that libgit2 does not support an upsert of a previously removed path, or an upsert
718+
/// that changes the type of an object (such as from tree to blob or vice versa).
719+
pub fn upsert<T: IntoCString>(&mut self, path: T, id: Oid, filemode: FileMode) -> &mut Self {
720+
let path = util::cstring_to_repo_path(path).unwrap();
721+
let path_ptr = path.as_ptr();
722+
self.paths.push(path);
723+
self.updates.push(raw::git_tree_update {
724+
action: raw::GIT_TREE_UPDATE_UPSERT,
725+
id: unsafe { *id.raw() },
726+
filemode: u32::from(filemode) as raw::git_filemode_t,
727+
path: path_ptr,
728+
});
729+
self
730+
}
731+
732+
/// Create a new tree from the specified baseline and this series of updates.
733+
///
734+
/// The baseline tree must exist in the specified repository.
735+
pub fn create_updated(&mut self, repo: &Repository, baseline: &Tree<'_>) -> Result<Oid, Error> {
736+
let mut ret = raw::git_oid {
737+
id: [0; raw::GIT_OID_RAWSZ],
738+
};
739+
unsafe {
740+
try_call!(raw::git_tree_create_updated(
741+
&mut ret,
742+
repo.raw(),
743+
baseline.raw(),
744+
self.updates.len(),
745+
self.updates.as_ptr()
746+
));
747+
Ok(Binding::from_raw(&ret as *const _))
748+
}
749+
}
750+
}
751+
677752
#[cfg(test)]
678753
mod tests {
679-
use super::{CheckoutBuilder, RepoBuilder};
680-
use crate::{CheckoutNotificationType, Repository};
754+
use super::{CheckoutBuilder, RepoBuilder, TreeUpdateBuilder};
755+
use crate::{CheckoutNotificationType, FileMode, Repository};
681756
use std::fs;
682757
use std::path::Path;
683758
use tempfile::TempDir;
@@ -707,6 +782,23 @@ mod tests {
707782
assert!(RepoBuilder::new().branch("foo").clone(&url, &dst).is_err());
708783
}
709784

785+
#[test]
786+
fn smoke_tree_create_updated() {
787+
let (_tempdir, repo) = crate::test::repo_init();
788+
let (_, tree_id) = crate::test::commit(&repo);
789+
let tree = t!(repo.find_tree(tree_id));
790+
assert!(tree.get_name("bar").is_none());
791+
let foo_id = tree.get_name("foo").unwrap().id();
792+
let tree2_id = t!(TreeUpdateBuilder::new()
793+
.remove("foo")
794+
.upsert("bar/baz", foo_id, FileMode::Blob)
795+
.create_updated(&repo, &tree));
796+
let tree2 = t!(repo.find_tree(tree2_id));
797+
assert!(tree2.get_name("foo").is_none());
798+
let baz_id = tree2.get_path(Path::new("bar/baz")).unwrap().id();
799+
assert_eq!(foo_id, baz_id);
800+
}
801+
710802
/// Issue regression test #365
711803
#[test]
712804
fn notify_callback() {

0 commit comments

Comments
 (0)