Skip to content
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
4 changes: 3 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ jobs:
shell: bash
- run: cargo test --locked
- run: cargo test --features https,ssh
- run: cargo test --features unstable-sha256
- run: cargo test --features https,ssh,unstable-sha256
- run: cargo test -p git2-curl
- run: cargo run -p systest
- run: cargo run -p systest --features unstable-sha256
- run: cargo test -p git2-curl

rustfmt:
name: Rustfmt
Expand Down
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ url = "2.5.4"

[features]
unstable = []
# Experimental SHA256 OID support,
# reflecting upstream libgit2's GIT_EXPERIMENTAL_SHA256.
#
# This is an ABI-breaking change.
# Future releases with this feature may introduce breakages without notice
# Use at your own risk.
#
# Library authors:
# DO NOT enable this feature by default in your dependencies.
# Due to Cargo's additive features,
# downstream users cannot deactivate it once enabled.
unstable-sha256 = ["libgit2-sys/unstable-sha256"]
default = []
ssh = ["libgit2-sys/ssh", "cred"]
https = ["libgit2-sys/https", "openssl-sys", "openssl-probe", "cred"]
Expand Down
2 changes: 1 addition & 1 deletion examples/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ fn tree_to_treeish<'a>(

fn resolve_blob<'a>(repo: &'a Repository, arg: Option<&String>) -> Result<Option<Blob<'a>>, Error> {
let arg = match arg {
Some(s) => Oid::from_str(s)?,
Some(s) => Oid::from_str_ext(s, repo.object_format())?,
None => return Ok(None),
};
repo.find_blob(arg).map(|b| Some(b))
Expand Down
20 changes: 20 additions & 0 deletions examples/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#![deny(warnings)]

use clap::Parser;
use git2::ObjectFormat;
use git2::{Error, Repository, RepositoryInitMode, RepositoryInitOptions};
use std::path::{Path, PathBuf};

Expand All @@ -40,6 +41,9 @@ struct Args {
#[structopt(name = "perms", long = "shared")]
/// permissions to create the repository with
flag_shared: Option<String>,
#[structopt(name = "object-format", long, value_parser = parse_object_format)]
/// object format to use (sha1 or sha256, requires unstable-sha256 feature to use the sha256 format)
flag_object_format: Option<ObjectFormat>,
}

fn run(args: &Args) -> Result<(), Error> {
Expand All @@ -48,6 +52,7 @@ fn run(args: &Args) -> Result<(), Error> {
&& args.flag_template.is_none()
&& args.flag_shared.is_none()
&& args.flag_separate_git_dir.is_none()
&& args.flag_object_format.is_none()
{
Repository::init(&path)?
} else {
Expand All @@ -68,6 +73,12 @@ fn run(args: &Args) -> Result<(), Error> {
if let Some(ref s) = args.flag_shared {
opts.mode(parse_shared(s)?);
}

#[cfg(feature = "unstable-sha256")]
if let Some(format) = args.flag_object_format {
opts.object_format(format);
}

Repository::init_opts(&path, &opts)?
};

Expand Down Expand Up @@ -136,6 +147,15 @@ fn parse_shared(shared: &str) -> Result<RepositoryInitMode, Error> {
}
}

fn parse_object_format(format: &str) -> Result<ObjectFormat, Error> {
match format {
"sha1" => Ok(ObjectFormat::Sha1),
#[cfg(feature = "unstable-sha256")]
"sha256" => Ok(ObjectFormat::Sha256),
_ => Err(Error::from_str("object format must be 'sha1' or 'sha256'")),
}
}

fn main() {
let args = Args::parse();
match run(&args) {
Expand Down
50 changes: 46 additions & 4 deletions src/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -437,10 +437,11 @@ mod tests {
assert_eq!(commit.parents().count(), 0);

let tree_header_bytes = commit.header_field_bytes("tree").unwrap();
assert_eq!(
crate::Oid::from_str(tree_header_bytes.as_str().unwrap()).unwrap(),
commit.tree_id()
);
let tree_oid = {
let str = tree_header_bytes.as_str().unwrap();
crate::Oid::from_str_ext(str, repo.object_format()).unwrap()
};
assert_eq!(tree_oid, commit.tree_id());
assert_eq!(commit.author().name(), Some("name"));
assert_eq!(commit.author().email(), Some("email"));
assert_eq!(commit.committer().name(), Some("name"));
Expand All @@ -467,4 +468,45 @@ mod tests {
.ok()
.unwrap();
}

#[test]
#[cfg(feature = "unstable-sha256")]
fn smoke_sha256() {
let (_td, repo) = crate::test::repo_init_sha256();
let head = repo.head().unwrap();
let target = head.target().unwrap();
let commit = repo.find_commit(target).unwrap();

// Verify SHA256 OID (32 bytes)
assert_eq!(commit.id().as_bytes().len(), 32);
assert_eq!(commit.tree_id().as_bytes().len(), 32);

assert_eq!(commit.message(), Some("initial\n\nbody"));
assert_eq!(commit.body(), Some("body"));
assert_eq!(commit.id(), target);
commit.summary().unwrap();
commit.tree().unwrap();
assert_eq!(commit.parents().count(), 0);

let tree_header_bytes = commit.header_field_bytes("tree").unwrap();
let tree_oid = {
let str = tree_header_bytes.as_str().unwrap();
let oid = crate::Oid::from_str_ext(str, repo.object_format()).unwrap();
oid
};
assert_eq!(tree_oid, commit.tree_id());

// Create child commit with parent
let sig = repo.signature().unwrap();
let tree = repo.find_tree(commit.tree_id()).unwrap();
let id = repo
.commit(Some("HEAD"), &sig, &sig, "bar", &tree, &[&commit])
.unwrap();
let head = repo.find_commit(id).unwrap();

// Verify child commit ID is also SHA256
assert_eq!(head.id().as_bytes().len(), 32);
assert_eq!(head.parent_count(), 1);
assert_eq!(head.parent_id(0).unwrap(), commit.id());
}
}
70 changes: 63 additions & 7 deletions src/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::ptr;
use std::slice;

use crate::util::{self, Binding};
use crate::ObjectFormat;
use crate::{panic, raw, Buf, Delta, DiffFormat, Error, FileMode, Oid, Repository};
use crate::{DiffFlags, DiffStatsFormat, IntoCString};

Expand Down Expand Up @@ -310,16 +311,36 @@ impl Diff<'static> {
/// two trees, however there may be subtle differences. For example,
/// a patch file likely contains abbreviated object IDs, so the
/// object IDs parsed by this function will also be abbreviated.
///
/// This parses the diff assuming SHA1 object IDs. Use
/// [`Diff::from_buffer_ext`] to specify a different format.
pub fn from_buffer(buffer: &[u8]) -> Result<Diff<'static>, Error> {
Self::from_buffer_ext(buffer, ObjectFormat::Sha1)
}

/// Reads the contents of a git patch file into a `git_diff` object,
/// with a specific object format.
///
/// See [`Diff::from_buffer`] for more details.
pub fn from_buffer_ext(buffer: &[u8], format: ObjectFormat) -> Result<Diff<'static>, Error> {
crate::init();
let mut diff: *mut raw::git_diff = std::ptr::null_mut();
let data = buffer.as_ptr() as *const c_char;
let len = buffer.len();
// NOTE: Doesn't depend on repo, so lifetime can be 'static
unsafe {
// NOTE: Doesn't depend on repo, so lifetime can be 'static
try_call!(raw::git_diff_from_buffer(
&mut diff,
buffer.as_ptr() as *const c_char,
buffer.len()
));
#[cfg(not(feature = "unstable-sha256"))]
{
let _ = format;
try_call!(raw::git_diff_from_buffer(&mut diff, data, len));
}
#[cfg(feature = "unstable-sha256")]
{
let mut opts: raw::git_diff_parse_options = std::mem::zeroed();
opts.version = raw::GIT_DIFF_PARSE_OPTIONS_VERSION;
opts.oid_type = format.raw();
try_call!(raw::git_diff_from_buffer(&mut diff, data, len, &mut opts));
}
Ok(Diff::from_raw(diff))
}
}
Expand Down Expand Up @@ -1552,6 +1573,8 @@ impl DiffPatchidOptions {

#[cfg(test)]
mod tests {
#[cfg(feature = "unstable-sha256")]
use crate::Diff;
use crate::{DiffLineType, DiffOptions, Oid, Signature, Time};
use std::borrow::Borrow;
use std::fs::File;
Expand All @@ -1568,7 +1591,7 @@ mod tests {
assert_eq!(stats.deletions(), 0);
assert_eq!(stats.files_changed(), 0);
let patchid = diff.patchid(None).unwrap();
assert_ne!(patchid, Oid::zero());
assert_ne!(patchid, Oid::ZERO_SHA1);
}

#[test]
Expand Down Expand Up @@ -1858,4 +1881,37 @@ mod tests {

assert_eq!(result.unwrap_err().code(), crate::ErrorCode::User);
}

#[test]
#[cfg(feature = "unstable-sha256")]
fn diff_sha256() {
let (_td, repo) = crate::test::repo_init_sha256();
let diff = repo.diff_tree_to_workdir(None, None).unwrap();
assert_eq!(diff.deltas().len(), 0);
let stats = diff.stats().unwrap();
assert_eq!(stats.insertions(), 0);
assert_eq!(stats.deletions(), 0);
assert_eq!(stats.files_changed(), 0);
let patchid = diff.patchid(None).unwrap();

// Verify SHA256 OID (32 bytes)
assert_eq!(patchid.as_bytes().len(), 32);
}

#[test]
#[cfg(feature = "unstable-sha256")]
fn diff_from_buffer_sha256() {
// Minimal patch with SHA256 OID (64 chars)
let patch = b"diff --git a/file.txt b/file.txt
index 0000000000000000000000000000000000000000000000000000000000000000..1111111111111111111111111111111111111111111111111111111111111111 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1 @@
-old
+new
";

let diff = Diff::from_buffer_ext(patch, crate::ObjectFormat::Sha256).unwrap();
assert_eq!(diff.deltas().len(), 1);
}
}
Loading
Loading