Skip to content
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

WIP: Add wrappers for git_index_conflict_{add,remove,get,cleanup} #780

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ ssh_key_from_memory = ["libgit2-sys/ssh_key_from_memory"]
zlib-ng-compat = ["libgit2-sys/zlib-ng-compat"]

[workspace]
members = ["systest", "git2-curl"]
members = ["systest", "git2-curl", "libgit2-sys"]
1 change: 1 addition & 0 deletions libgit2-sys/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2923,6 +2923,7 @@ extern "C" {
our_entry: *const git_index_entry,
their_entry: *const git_index_entry,
) -> c_int;
pub fn git_index_conflict_cleanup(index: *mut git_index) -> c_int;
pub fn git_index_conflict_remove(index: *mut git_index, path: *const c_char) -> c_int;
pub fn git_index_conflict_get(
ancestor_out: *mut *const git_index_entry,
Expand Down
235 changes: 166 additions & 69 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,43 @@ pub struct IndexEntry {
pub path: Vec<u8>,
}

impl IndexEntry {
// Expecting c_path to contain a CString with the contents of self.path
fn to_raw(&self, c_path: &CString) -> raw::git_index_entry {
// libgit2 encodes the length of the path in the lower bits of the
// `flags` entry, so mask those out and recalculate here to ensure we
// don't corrupt anything.
let mut flags = self.flags & !raw::GIT_INDEX_ENTRY_NAMEMASK;

if self.path.len() < raw::GIT_INDEX_ENTRY_NAMEMASK as usize {
flags |= self.path.len() as u16;
} else {
flags |= raw::GIT_INDEX_ENTRY_NAMEMASK;
}

raw::git_index_entry {
dev: self.dev,
ino: self.ino,
mode: self.mode,
uid: self.uid,
gid: self.gid,
file_size: self.file_size,
id: unsafe { *self.id.raw() },
flags,
flags_extended: self.flags_extended,
path: c_path.as_ptr(),
mtime: raw::git_index_time {
seconds: self.mtime.seconds(),
nanoseconds: self.mtime.nanoseconds(),
},
ctime: raw::git_index_time {
seconds: self.ctime.seconds(),
nanoseconds: self.ctime.nanoseconds(),
},
}
}
}

impl Index {
/// Creates a new in-memory index.
///
Expand Down Expand Up @@ -145,43 +182,13 @@ impl Index {
/// given 'source_entry', it will be replaced. Otherwise, the 'source_entry'
/// will be added.
pub fn add(&mut self, entry: &IndexEntry) -> Result<(), Error> {
let path = CString::new(&entry.path[..])?;

// libgit2 encodes the length of the path in the lower bits of the
// `flags` entry, so mask those out and recalculate here to ensure we
// don't corrupt anything.
let mut flags = entry.flags & !raw::GIT_INDEX_ENTRY_NAMEMASK;

if entry.path.len() < raw::GIT_INDEX_ENTRY_NAMEMASK as usize {
flags |= entry.path.len() as u16;
} else {
flags |= raw::GIT_INDEX_ENTRY_NAMEMASK;
}
let c_path = CString::new(&entry.path[..])?;
let raw_entry = entry.to_raw(&c_path);

unsafe {
let raw = raw::git_index_entry {
dev: entry.dev,
ino: entry.ino,
mode: entry.mode,
uid: entry.uid,
gid: entry.gid,
file_size: entry.file_size,
id: *entry.id.raw(),
flags,
flags_extended: entry.flags_extended,
path: path.as_ptr(),
mtime: raw::git_index_time {
seconds: entry.mtime.seconds(),
nanoseconds: entry.mtime.nanoseconds(),
},
ctime: raw::git_index_time {
seconds: entry.ctime.seconds(),
nanoseconds: entry.ctime.nanoseconds(),
},
};
try_call!(raw::git_index_add(self.raw, &raw));
Ok(())
try_call!(raw::git_index_add(self.raw, &raw_entry));
}
Ok(())
}

/// Add or update an index entry from a buffer in memory
Expand All @@ -202,46 +209,17 @@ impl Index {
/// no longer be marked as conflicting. The data about the conflict will be
/// moved to the "resolve undo" (REUC) section.
pub fn add_frombuffer(&mut self, entry: &IndexEntry, data: &[u8]) -> Result<(), Error> {
let path = CString::new(&entry.path[..])?;

// libgit2 encodes the length of the path in the lower bits of the
// `flags` entry, so mask those out and recalculate here to ensure we
// don't corrupt anything.
let mut flags = entry.flags & !raw::GIT_INDEX_ENTRY_NAMEMASK;

if entry.path.len() < raw::GIT_INDEX_ENTRY_NAMEMASK as usize {
flags |= entry.path.len() as u16;
} else {
flags |= raw::GIT_INDEX_ENTRY_NAMEMASK;
}
let c_path = CString::new(&entry.path[..])?;
let raw_entry = entry.to_raw(&c_path);

unsafe {
let raw = raw::git_index_entry {
dev: entry.dev,
ino: entry.ino,
mode: entry.mode,
uid: entry.uid,
gid: entry.gid,
file_size: entry.file_size,
id: *entry.id.raw(),
flags,
flags_extended: entry.flags_extended,
path: path.as_ptr(),
mtime: raw::git_index_time {
seconds: entry.mtime.seconds(),
nanoseconds: entry.mtime.nanoseconds(),
},
ctime: raw::git_index_time {
seconds: entry.ctime.seconds(),
nanoseconds: entry.ctime.nanoseconds(),
},
};

let ptr = data.as_ptr() as *const c_void;
let len = data.len() as size_t;
try_call!(raw::git_index_add_frombuffer(self.raw, &raw, ptr, len));
Ok(())
try_call!(raw::git_index_add_frombuffer(
self.raw, &raw_entry, ptr, len
));
}
Ok(())
}

/// Add or update an index entry from a file on disk
Expand Down Expand Up @@ -412,6 +390,125 @@ impl Index {
unsafe { raw::git_index_has_conflicts(self.raw) == 1 }
}

/// Add or update index entries to represent a conflict. Any staged entries
/// that exist at the given paths will be removed.
///
/// The entries are the entries from the tree included in the merge. Any entry
/// may be `None` to indicate that that file was not present in the trees during
/// the merge. For example, ancestor_entry may be `None` to indicate that a file
/// was added in both branches and must be resolved.
pub fn conflict_add(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The general intention of this crate is to be a lightweight binding over libgit2 itself, but this is a pretty heavyweight method. Would it be possible to avoid the movement around of all the data internally?

Copy link
Author

@fin-ger fin-ger Nov 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I based this implementation on all the other methods taking a IndexEntry (e.g. Index::add()). This one is particularly long because it takes three IndexEntry parameters. I don't know if the handling of IndexEntrys can be more concise, my intention was more to handle them exactly the same as in the existing methods.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe std::mem::transmute can be used here, but I have no clue if git_index_entry is even remotely binary compatible with an IndexEntry especially regarding the path: Vec<u8>. Also, this would be incredibly easy to mess up and hard to debug 🙈

Maybe someone has a better idea 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, well in that case can the conversion into a git_index_entry be refactored into a helper function instead of being duplicated?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, will do so. Should I also refactor all conversions in other methods using IndexEntry?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess? Sort of depends, I'm not intimately familiar with all this code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, my solution is to use a helper function which supplies pointers to raw::git_index_entry instances via a callback. This is needed as the construction of a git_index_entry requires the construction of a CString to which the git_index_entry points to. I went for a callback solution as it is (in my opinion) the easiest approach to make sure that the CString is available while using the raw git_index_entry. The procedure is implemented in try_raw_entries()@src/index.rs:92. The implementation is a bit messy and requires the construction of three Vecs. Suggestions welcome!

&self,
ancestor_entry: Option<&IndexEntry>,
our_entry: Option<&IndexEntry>,
their_entry: Option<&IndexEntry>,
) -> Result<(), Error> {
let ancestor_c_path = if let Some(entry) = ancestor_entry {
Some(CString::new(&entry.path[..])?)
} else {
None
};
let our_c_path = if let Some(entry) = our_entry {
Some(CString::new(&entry.path[..])?)
} else {
None
};
let their_c_path = if let Some(entry) = their_entry {
Some(CString::new(&entry.path[..])?)
} else {
None
};

let mut raw_ancestor_entry = None;
let mut raw_our_entry = None;
let mut raw_their_entry = None;

if let Some(entry) = ancestor_entry {
let c_path = ancestor_c_path.as_ref().unwrap();
raw_ancestor_entry = Some(entry.to_raw(&c_path));
}
if let Some(entry) = our_entry {
let c_path = our_c_path.as_ref().unwrap();
raw_our_entry = Some(entry.to_raw(&c_path));
}
if let Some(entry) = their_entry {
let c_path = their_c_path.as_ref().unwrap();
raw_their_entry = Some(entry.to_raw(&c_path));
}

let raw_ancestor_entry_ptr = raw_ancestor_entry
.as_ref()
.map_or_else(std::ptr::null, |ptr| ptr);
let raw_our_entry_ptr = raw_our_entry
.as_ref()
.map_or_else(std::ptr::null, |ptr| ptr);
let raw_their_entry_ptr = raw_their_entry
.as_ref()
.map_or_else(std::ptr::null, |ptr| ptr);

unsafe {
try_call!(raw::git_index_conflict_add(
self.raw,
raw_ancestor_entry_ptr,
raw_our_entry_ptr,
raw_their_entry_ptr
));
}
Ok(())
}

/// Remove all conflicts in the index (entries with a stage greater than 0).
pub fn conflict_cleanup(&self) -> Result<(), Error> {
unsafe {
try_call!(raw::git_index_conflict_cleanup(self.raw));
Ok(())
}
}

/// Get the index entries that represent a conflict of a single file.
///
/// The entries are not modifiable.
pub fn conflict_get(&self, path: &Path) -> Result<IndexConflict, Error> {
let path = path_to_repo_path(path)?;
let mut ancestor = ptr::null();
let mut our = ptr::null();
let mut their = ptr::null();

unsafe {
try_call!(raw::git_index_conflict_get(
&mut ancestor,
&mut our,
&mut their,
self.raw,
path
));
Ok(IndexConflict {
ancestor: match ancestor.is_null() {
false => Some(IndexEntry::from_raw(*ancestor)),
true => None,
},
our: match our.is_null() {
false => Some(IndexEntry::from_raw(*our)),
true => None,
},
their: match their.is_null() {
false => Some(IndexEntry::from_raw(*their)),
true => None,
},
})
}
}

/// Removes the index entries that represent a conflict of a single file.
pub fn conflict_remove(&self, path: &Path) -> Result<(), Error> {
let path = path_to_repo_path(path)?;

unsafe {
try_call!(raw::git_index_conflict_remove(self.raw, path));
Ok(())
}
}

/// Get the full path to the index file on disk.
///
/// Returns `None` if this is an in-memory index.
Expand Down