Skip to content

Better Worktree Support #404

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

Closed
wants to merge 3 commits into from
Closed
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ structopt = "0.3"
time = "0.1.39"
tempfile = "3.1.0"
thread-id = "3.3.0" # remove when we work with minimal-versions without it
paste = "0.1.12"

[features]
unstable = []
Expand Down
70 changes: 70 additions & 0 deletions libgit2-sys/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1758,6 +1758,34 @@ git_enum! {
}
}

#[repr(C)]
pub struct git_worktree_add_options {
pub version: c_uint,
pub lock: c_int,
pub reference: *mut git_reference,
}

pub const GIT_WORKTREE_ADD_OPTIONS_VERSION: u32 = 1;

git_enum! {
pub enum git_worktree_prune_t {
/* Prune working tree even if working tree is valid */
GIT_WORKTREE_PRUNE_VALID = 1 << 0,
/* Prune working tree even if it is locked */
GIT_WORKTREE_PRUNE_LOCKED = 1 << 1,
/* Prune checked out working tree */
GIT_WORKTREE_PRUNE_WORKING_TREE = 1 << 2,
}
}

#[repr(C)]
pub struct git_worktree_prune_options {
pub version: c_uint,
pub flags: u32,
}

pub const GIT_WORKTREE_PRUNE_OPTIONS_VERSION: u32 = 1;

extern "C" {
// threads
pub fn git_libgit2_init() -> c_int;
Expand Down Expand Up @@ -3645,6 +3673,48 @@ extern "C" {
location: git_apply_location_t,
options: *const git_apply_options,
) -> c_int;

// Worktrees
pub fn git_worktree_list(out: *mut git_strarray, repo: *mut git_repository) -> c_int;
pub fn git_worktree_lookup(
out: *mut *mut git_worktree,
repo: *mut git_repository,
name: *const c_char,
) -> c_int;
pub fn git_worktree_open_from_repository(
out: *mut *mut git_worktree,
repo: *mut git_repository,
) -> c_int;
pub fn git_worktree_free(wt: *mut git_worktree);
pub fn git_worktree_validate(wt: *const git_worktree) -> c_int;
pub fn git_worktree_add_options_init(
opts: *mut git_worktree_add_options,
version: c_uint,
) -> c_int;
pub fn git_worktree_add(
out: *mut *mut git_worktree,
repo: *mut git_repository,
name: *const c_char,
path: *const c_char,
opts: *const git_worktree_add_options,
) -> c_int;
pub fn git_worktree_lock(wt: *mut git_worktree, reason: *const c_char) -> c_int;
pub fn git_worktree_unlock(wt: *mut git_worktree) -> c_int;
pub fn git_worktree_is_locked(reason: *mut git_buf, wt: *const git_worktree) -> c_int;
pub fn git_worktree_name(wt: *const git_worktree) -> *const c_char;
pub fn git_worktree_path(wt: *const git_worktree) -> *const c_char;
pub fn git_worktree_prune_options_init(
opts: *mut git_worktree_prune_options,
version: c_uint,
) -> c_int;
pub fn git_worktree_is_prunable(
wt: *mut git_worktree,
opts: *mut git_worktree_prune_options,
) -> c_int;
pub fn git_worktree_prune(
wt: *mut git_worktree,
opts: *mut git_worktree_prune_options,
) -> c_int;
}

pub fn init() {
Expand Down
6 changes: 2 additions & 4 deletions src/blame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,9 +290,7 @@ mod tests {
use std::fs::{self, File};
use std::path::Path;

#[test]
fn smoke() {
let (_td, repo) = crate::test::repo_init();
repo_test!(smoke, Typical, TypicalWorktree, BareWorktree {
let mut index = repo.index().unwrap();

let root = repo.path().parent().unwrap();
Expand Down Expand Up @@ -322,5 +320,5 @@ mod tests {
assert_eq!(hunk.path(), Some(Path::new("foo/bar")));
assert_eq!(hunk.lines_in_hunk(), 0);
assert!(!hunk.is_boundary())
}
});
}
5 changes: 2 additions & 3 deletions src/branch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,7 @@ impl<'repo> Drop for Branches<'repo> {
mod tests {
use crate::BranchType;

#[test]
fn smoke() {
repo_test!(smoke, Typical, TypicalWorktree, BareWorktree {
let (_td, repo) = crate::test::repo_init();
let head = repo.head().unwrap();
let target = head.target().unwrap();
Expand All @@ -174,5 +173,5 @@ mod tests {
b1.set_upstream(None).unwrap();

b1.delete().unwrap();
}
});
}
6 changes: 6 additions & 0 deletions src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ mod impls {
}
}

impl Convert<*mut libc::c_char> for CString {
fn convert(&self) -> *mut libc::c_char {
self.as_ptr() as *mut libc::c_char
}
}

impl<T, U: Convert<*const T>> Convert<*const T> for Option<U> {
fn convert(&self) -> *const T {
self.as_ref().map(|s| s.convert()).unwrap_or(ptr::null())
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ pub use crate::time::{IndexTime, Time};
pub use crate::tree::{Tree, TreeEntry, TreeIter, TreeWalkMode, TreeWalkResult};
pub use crate::treebuilder::TreeBuilder;
pub use crate::util::IntoCString;
pub use crate::worktree::{Worktree, WorktreeAddOptions, WorktreeLockStatus, WorktreePruneOptions};

// Create a convinience method on bitflag struct which checks the given flag
macro_rules! is_bit_set {
Expand Down Expand Up @@ -666,6 +667,7 @@ mod tag;
mod time;
mod tree;
mod treebuilder;
mod worktree;

fn init() {
static INIT: Once = Once::new();
Expand Down
61 changes: 61 additions & 0 deletions src/repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::oid_array::OidArray;
use crate::stash::{stash_cb, StashApplyOptions, StashCbData};
use crate::string_array::StringArray;
use crate::util::{self, path_to_repo_path, Binding};
use crate::worktree::{Worktree, WorktreeAddOptions};
use crate::CherrypickOptions;
use crate::{
init, raw, AttrCheckFlags, Buf, Error, Object, Remote, RepositoryOpenFlags, RepositoryState,
Expand Down Expand Up @@ -2700,6 +2701,66 @@ impl Repository {
Ok(())
}
}

/// Lists all the worktrees for the repository
pub fn worktrees(&self) -> Result<StringArray, Error> {
let mut arr = raw::git_strarray {
strings: 0 as *mut *mut c_char,
count: 0,
};
unsafe {
try_call!(raw::git_worktree_list(&mut arr, self.raw));
Ok(Binding::from_raw(arr))
}
}

/// Opens a worktree by name for the given repository
///
/// This can open any worktree that the worktrees method returns.
pub fn worktree_lookup(&self, name: &str) -> Result<Worktree, Error> {
let mut raw = ptr::null_mut();
let raw_name = CString::new(name)?;
unsafe {
try_call!(raw::git_worktree_lookup(&mut raw, self.raw, raw_name));
Ok(Binding::from_raw(raw))
}
}

/// Open a worktree of a the repository
///
/// If a repository is not the main tree but a worktree, this
/// function will look up the worktree inside the parent
/// repository and create a new `git_worktree` structure.
pub fn worktree_open_from_repository(&self) -> Result<Worktree, Error> {
let mut raw = ptr::null_mut();
unsafe {
try_call!(raw::git_worktree_open_from_repository(&mut raw, self.raw));
Ok(Binding::from_raw(raw))
}
}

/// Creates a new worktree for the repository
pub fn worktree_add(
&self,
name: &str,
path: &Path,
opts: &WorktreeAddOptions<'_>,
) -> Result<Worktree, Error> {
let mut raw = ptr::null_mut();
let raw_name = CString::new(name)?;
let raw_path = path.into_c_string()?;

unsafe {
try_call!(raw::git_worktree_add(
&mut raw,
self.raw,
raw_name,
raw_path,
&opts.raw()
));
Ok(Binding::from_raw(raw))
}
}
}

impl Binding for Repository {
Expand Down
59 changes: 57 additions & 2 deletions src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::ptr;
use tempfile::TempDir;
use url::Url;

use crate::{Oid, Repository};
use crate::{Branch, Oid, Repository};

macro_rules! t {
($e:expr) => {
Expand All @@ -17,7 +17,21 @@ macro_rules! t {
};
}

pub fn repo_init() -> (TempDir, Repository) {
// `repo_test! will
macro_rules! repo_test {
($test_name:ident, $($repo_type:ident),+ $test_body:block) => {
paste::item! {
$(#[test]
fn [<$test_name _ $repo_type:snake>]() {
#[allow(unused_variables)]
let (td, repo) = $crate::test::repo_init2($crate::test::RepoType::$repo_type);
$test_body
})+
}
}
}

pub fn repo_init_typical() -> (TempDir, Repository) {
let td = TempDir::new().unwrap();
let repo = Repository::init(td.path()).unwrap();
{
Expand All @@ -35,6 +49,31 @@ pub fn repo_init() -> (TempDir, Repository) {
(td, repo)
}

pub fn repo_init_bare() -> (TempDir, Repository) {
panic!("unimplemented")
}

pub fn repo_init_bare_worktree() -> (TempDir, Repository) {
panic!("unimplemented")
}

pub fn repo_init_typical_worktree() -> (TempDir, Repository) {
panic!("unimplemented")
}

pub fn repo_init() -> (TempDir, Repository) {
repo_init_typical()
}

pub fn repo_init2(repo_type: RepoType) -> (TempDir, Repository) {
match repo_type {
RepoType::Typical => repo_init_typical(),
RepoType::Bare => repo_init_bare(),
RepoType::BareWorktree => repo_init_bare_worktree(),
RepoType::TypicalWorktree => repo_init_typical_worktree(),
}
}

pub fn commit(repo: &Repository) -> (Oid, Oid) {
let mut index = t!(repo.index());
let root = repo.path().parent().unwrap();
Expand All @@ -54,6 +93,22 @@ pub fn path2url(path: &Path) -> String {
Url::from_file_path(path).unwrap().to_string()
}

pub fn worktrees_env_init(repo: &Repository) -> (TempDir, Branch<'_>) {
let oid = repo.head().unwrap().target().unwrap();
let commit = repo.find_commit(oid).unwrap();
let branch = repo.branch("wt-branch", &commit, true).unwrap();
let wtdir = TempDir::new().unwrap();
(wtdir, branch)
}

#[derive(Debug, Clone, Copy)]
pub enum RepoType {
Typical,
TypicalWorktree,
Bare,
BareWorktree,
}

#[cfg(windows)]
pub fn realpath(original: &Path) -> io::Result<PathBuf> {
Ok(original.to_path_buf())
Expand Down
Loading