Skip to content

Commit 52e8c0c

Browse files
committed
Add TreeUpdateBuidler to support git_tree_create_updated
git_tree_create_updated requires a length and a contiguous array of git_tree_update structures, each of which has a pointer to a C string. Create a builder, `TreeUpdateBuilder`, to wrap that safely without excessive copies.
1 parent 9e064ab commit 52e8c0c

File tree

2 files changed

+99
-3
lines changed

2 files changed

+99
-3
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ url = "2.0"
2020
bitflags = "1.1.0"
2121
libc = "0.2"
2222
log = "0.4.8"
23-
libgit2-sys = { path = "libgit2-sys", version = "0.12.15" }
23+
libgit2-sys = { path = "libgit2-sys", version = "0.12.16" }
2424

2525
[target."cfg(all(unix, not(target_os = \"macos\")))".dependencies]
2626
openssl-sys = { version = "0.9.0", optional = true }

src/build.rs

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::path::Path;
77
use std::ptr;
88

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

1313
/// A builder struct which is used to build configuration for cloning a new git
@@ -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,9 +680,82 @@ 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+
///
720+
/// The mode given must be one of 0o040000, 0o100644, 0o100755, 0o120000 or
721+
/// 0o160000 currently.
722+
pub fn upsert<T: IntoCString>(&mut self, path: T, id: Oid, filemode: i32) -> &mut Self {
723+
let filemode = filemode as raw::git_filemode_t;
724+
let path = util::cstring_to_repo_path(path).unwrap();
725+
let path_ptr = path.as_ptr();
726+
self.paths.push(path);
727+
self.updates.push(raw::git_tree_update {
728+
action: raw::GIT_TREE_UPDATE_UPSERT,
729+
id: unsafe { *id.raw() },
730+
filemode,
731+
path: path_ptr,
732+
});
733+
self
734+
}
735+
736+
/// Create a new tree from the specified baseline and this series of updates.
737+
///
738+
/// The baseline tree must exist in the specified repository.
739+
pub fn create_updated(&mut self, repo: &Repository, baseline: &Tree<'_>) -> Result<Oid, Error> {
740+
let mut ret = raw::git_oid {
741+
id: [0; raw::GIT_OID_RAWSZ],
742+
};
743+
unsafe {
744+
try_call!(raw::git_tree_create_updated(
745+
&mut ret,
746+
repo.raw(),
747+
baseline.raw(),
748+
self.updates.len(),
749+
self.updates.as_ptr()
750+
));
751+
Ok(Binding::from_raw(&ret as *const _))
752+
}
753+
}
754+
}
755+
677756
#[cfg(test)]
678757
mod tests {
679-
use super::{CheckoutBuilder, RepoBuilder};
758+
use super::{CheckoutBuilder, RepoBuilder, TreeUpdateBuilder};
680759
use crate::{CheckoutNotificationType, Repository};
681760
use std::fs;
682761
use std::path::Path;
@@ -707,6 +786,23 @@ mod tests {
707786
assert!(RepoBuilder::new().branch("foo").clone(&url, &dst).is_err());
708787
}
709788

789+
#[test]
790+
fn smoke_tree_create_updated() {
791+
let (_tempdir, repo) = crate::test::repo_init();
792+
let (_, tree_id) = crate::test::commit(&repo);
793+
let tree = t!(repo.find_tree(tree_id));
794+
assert!(tree.get_name("bar").is_none());
795+
let foo_id = tree.get_name("foo").unwrap().id();
796+
let tree2_id = t!(TreeUpdateBuilder::new()
797+
.remove("foo")
798+
.upsert("bar/baz", foo_id, 0o100644)
799+
.create_updated(&repo, &tree));
800+
let tree2 = t!(repo.find_tree(tree2_id));
801+
assert!(tree2.get_name("foo").is_none());
802+
let baz_id = tree2.get_path(Path::new("bar/baz")).unwrap().id();
803+
assert_eq!(foo_id, baz_id);
804+
}
805+
710806
/// Issue regression test #365
711807
#[test]
712808
fn notify_callback() {

0 commit comments

Comments
 (0)