Skip to content

Commit 2ba1852

Browse files
foriequal0mkeeler
andauthored
More Better Worktree Support (#603)
* Intial support for working with worktrees The systest changes were necessary due to the actual field name used in the C library being “ref” and not being able to name the field the same in the Rust struct because “ref” is a reserved keyword. Co-authored-by: Matt Keeler <mjkeeler7@gmail.com>
1 parent 7d755ae commit 2ba1852

File tree

8 files changed

+477
-3
lines changed

8 files changed

+477
-3
lines changed

Diff for: Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ structopt = "0.3"
3131
time = "0.1.39"
3232
tempfile = "3.1.0"
3333
thread-id = "3.3.0" # remove when we work with minimal-versions without it
34+
paste = "0.1.12"
3435

3536
[features]
3637
unstable = []

Diff for: libgit2-sys/lib.rs

+70
Original file line numberDiff line numberDiff line change
@@ -1825,6 +1825,34 @@ git_enum! {
18251825
}
18261826
}
18271827

1828+
#[repr(C)]
1829+
pub struct git_worktree_add_options {
1830+
pub version: c_uint,
1831+
pub lock: c_int,
1832+
pub reference: *mut git_reference,
1833+
}
1834+
1835+
pub const GIT_WORKTREE_ADD_OPTIONS_VERSION: c_uint = 1;
1836+
1837+
git_enum! {
1838+
pub enum git_worktree_prune_t {
1839+
/* Prune working tree even if working tree is valid */
1840+
GIT_WORKTREE_PRUNE_VALID = 1 << 0,
1841+
/* Prune working tree even if it is locked */
1842+
GIT_WORKTREE_PRUNE_LOCKED = 1 << 1,
1843+
/* Prune checked out working tree */
1844+
GIT_WORKTREE_PRUNE_WORKING_TREE = 1 << 2,
1845+
}
1846+
}
1847+
1848+
#[repr(C)]
1849+
pub struct git_worktree_prune_options {
1850+
pub version: c_uint,
1851+
pub flags: u32,
1852+
}
1853+
1854+
pub const GIT_WORKTREE_PRUNE_OPTIONS_VERSION: c_uint = 1;
1855+
18281856
extern "C" {
18291857
// threads
18301858
pub fn git_libgit2_init() -> c_int;
@@ -3782,6 +3810,48 @@ extern "C" {
37823810
) -> c_int;
37833811

37843812
pub fn git_libgit2_opts(option: c_int, ...) -> c_int;
3813+
3814+
// Worktrees
3815+
pub fn git_worktree_list(out: *mut git_strarray, repo: *mut git_repository) -> c_int;
3816+
pub fn git_worktree_lookup(
3817+
out: *mut *mut git_worktree,
3818+
repo: *mut git_repository,
3819+
name: *const c_char,
3820+
) -> c_int;
3821+
pub fn git_worktree_open_from_repository(
3822+
out: *mut *mut git_worktree,
3823+
repo: *mut git_repository,
3824+
) -> c_int;
3825+
pub fn git_worktree_free(wt: *mut git_worktree);
3826+
pub fn git_worktree_validate(wt: *const git_worktree) -> c_int;
3827+
pub fn git_worktree_add_options_init(
3828+
opts: *mut git_worktree_add_options,
3829+
version: c_uint,
3830+
) -> c_int;
3831+
pub fn git_worktree_add(
3832+
out: *mut *mut git_worktree,
3833+
repo: *mut git_repository,
3834+
name: *const c_char,
3835+
path: *const c_char,
3836+
opts: *const git_worktree_add_options,
3837+
) -> c_int;
3838+
pub fn git_worktree_lock(wt: *mut git_worktree, reason: *const c_char) -> c_int;
3839+
pub fn git_worktree_unlock(wt: *mut git_worktree) -> c_int;
3840+
pub fn git_worktree_is_locked(reason: *mut git_buf, wt: *const git_worktree) -> c_int;
3841+
pub fn git_worktree_name(wt: *const git_worktree) -> *const c_char;
3842+
pub fn git_worktree_path(wt: *const git_worktree) -> *const c_char;
3843+
pub fn git_worktree_prune_options_init(
3844+
opts: *mut git_worktree_prune_options,
3845+
version: c_uint,
3846+
) -> c_int;
3847+
pub fn git_worktree_is_prunable(
3848+
wt: *mut git_worktree,
3849+
opts: *mut git_worktree_prune_options,
3850+
) -> c_int;
3851+
pub fn git_worktree_prune(
3852+
wt: *mut git_worktree,
3853+
opts: *mut git_worktree_prune_options,
3854+
) -> c_int;
37853855
}
37863856

37873857
pub fn init() {

Diff for: src/blame.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ mod tests {
307307
let (_td, repo) = crate::test::repo_init();
308308
let mut index = repo.index().unwrap();
309309

310-
let root = repo.path().parent().unwrap();
310+
let root = repo.workdir().unwrap();
311311
fs::create_dir(&root.join("foo")).unwrap();
312312
File::create(&root.join("foo/bar")).unwrap();
313313
index.add_path(Path::new("foo/bar")).unwrap();

Diff for: src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ pub use crate::time::{IndexTime, Time};
131131
pub use crate::tree::{Tree, TreeEntry, TreeIter, TreeWalkMode, TreeWalkResult};
132132
pub use crate::treebuilder::TreeBuilder;
133133
pub use crate::util::IntoCString;
134+
pub use crate::worktree::{Worktree, WorktreeAddOptions, WorktreeLockStatus, WorktreePruneOptions};
134135

135136
// Create a convinience method on bitflag struct which checks the given flag
136137
macro_rules! is_bit_set {
@@ -686,6 +687,7 @@ mod tagforeach;
686687
mod time;
687688
mod tree;
688689
mod treebuilder;
690+
mod worktree;
689691

690692
fn init() {
691693
static INIT: Once = Once::new();

Diff for: src/repo.rs

+60
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::stash::{stash_cb, StashApplyOptions, StashCbData};
1616
use crate::string_array::StringArray;
1717
use crate::tagforeach::{tag_foreach_cb, TagForeachCB, TagForeachData};
1818
use crate::util::{self, path_to_repo_path, Binding};
19+
use crate::worktree::{Worktree, WorktreeAddOptions};
1920
use crate::CherrypickOptions;
2021
use crate::RevertOptions;
2122
use crate::{
@@ -162,6 +163,18 @@ impl Repository {
162163
}
163164
}
164165

166+
/// Attempt to open an already-existing repository from a worktree.
167+
pub fn open_from_worktree(worktree: &Worktree) -> Result<Repository, Error> {
168+
let mut ret = ptr::null_mut();
169+
unsafe {
170+
try_call!(raw::git_repository_open_from_worktree(
171+
&mut ret,
172+
worktree.raw()
173+
));
174+
Ok(Binding::from_raw(ret))
175+
}
176+
}
177+
165178
/// Attempt to open an already-existing repository at or above `path`
166179
///
167180
/// This starts at `path` and looks up the filesystem hierarchy
@@ -2791,6 +2804,53 @@ impl Repository {
27912804
Ok(Binding::from_raw(ret))
27922805
}
27932806
}
2807+
2808+
/// Lists all the worktrees for the repository
2809+
pub fn worktrees(&self) -> Result<StringArray, Error> {
2810+
let mut arr = raw::git_strarray {
2811+
strings: ptr::null_mut(),
2812+
count: 0,
2813+
};
2814+
unsafe {
2815+
try_call!(raw::git_worktree_list(&mut arr, self.raw));
2816+
Ok(Binding::from_raw(arr))
2817+
}
2818+
}
2819+
2820+
/// Opens a worktree by name for the given repository
2821+
///
2822+
/// This can open any worktree that the worktrees method returns.
2823+
pub fn find_worktree(&self, name: &str) -> Result<Worktree, Error> {
2824+
let mut raw = ptr::null_mut();
2825+
let raw_name = CString::new(name)?;
2826+
unsafe {
2827+
try_call!(raw::git_worktree_lookup(&mut raw, self.raw, raw_name));
2828+
Ok(Binding::from_raw(raw))
2829+
}
2830+
}
2831+
2832+
/// Creates a new worktree for the repository
2833+
pub fn worktree<'a>(
2834+
&'a self,
2835+
name: &str,
2836+
path: &Path,
2837+
opts: Option<&WorktreeAddOptions<'a>>,
2838+
) -> Result<Worktree, Error> {
2839+
let mut raw = ptr::null_mut();
2840+
let raw_name = CString::new(name)?;
2841+
let raw_path = path.into_c_string()?;
2842+
2843+
unsafe {
2844+
try_call!(raw::git_worktree_add(
2845+
&mut raw,
2846+
self.raw,
2847+
raw_name,
2848+
raw_path,
2849+
opts.map(|o| o.raw())
2850+
));
2851+
Ok(Binding::from_raw(raw))
2852+
}
2853+
}
27942854
}
27952855

27962856
impl Binding for Repository {

Diff for: src/test.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::ptr;
66
use tempfile::TempDir;
77
use url::Url;
88

9-
use crate::{Oid, Repository, RepositoryInitOptions};
9+
use crate::{Branch, Oid, Repository, RepositoryInitOptions};
1010

1111
macro_rules! t {
1212
($e:expr) => {
@@ -56,6 +56,14 @@ pub fn path2url(path: &Path) -> String {
5656
Url::from_file_path(path).unwrap().to_string()
5757
}
5858

59+
pub fn worktrees_env_init(repo: &Repository) -> (TempDir, Branch<'_>) {
60+
let oid = repo.head().unwrap().target().unwrap();
61+
let commit = repo.find_commit(oid).unwrap();
62+
let branch = repo.branch("wt-branch", &commit, true).unwrap();
63+
let wtdir = TempDir::new().unwrap();
64+
(wtdir, branch)
65+
}
66+
5967
#[cfg(windows)]
6068
pub fn realpath(original: &Path) -> io::Result<PathBuf> {
6169
Ok(original.to_path_buf())

0 commit comments

Comments
 (0)