Skip to content

Commit ad92b95

Browse files
committed
feat: unstable SHA256 support
This adds an `unstable-sha256` Cargo feature, as a follow-up of rust-lang#1201 Also adds some smoke tests for affected operations/types. ## Insta-stable * **NEW** `Index::with_object_format` to create with different format ## Behind `unstable-sha256 * **NEW** `ObjectFormat` enum with variants `Sha1` and `Sha256` * **NEW** `RepositoryInitOptions::object_format()` method to set hash algo * **CHANGED** `Diff::from_buffer` to accept a extra object format argument * **CHANGED** `Index::open` to accept a extra object format argument * **CHANGED** `Indexer::new` to accept a extra object format argument * **CHANGED** `Oid::from_str` to accept a extra object format argument * **CHANGED** `Oid::hash_{object,file}` to accept a extra object format argument * **REMOVED** `Index::new` to avoid misuse. * **REMOVED** `impl std::FromStr for Oid` to avoid misuse
1 parent 8d506c4 commit ad92b95

File tree

12 files changed

+977
-51
lines changed

12 files changed

+977
-51
lines changed

Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ url = "2.5.4"
3434

3535
[features]
3636
unstable = []
37+
# Experimental SHA256 OID support,
38+
# reflecting upstream libgit2's GIT_EXPERIMENTAL_SHA256.
39+
#
40+
# This is an ABI-breaking change.
41+
# Future releases with this feature may introduce breakages without notice
42+
# Use at your own risk.
43+
#
44+
# Library authors:
45+
# DO NOT enable this feature by default in your dependencies.
46+
# Due to Cargo's additive features,
47+
# downstream users cannot deactivate it once enabled.
48+
unstable-sha256 = ["libgit2-sys/unstable-sha256"]
3749
default = []
3850
ssh = ["libgit2-sys/ssh", "cred"]
3951
https = ["libgit2-sys/https", "openssl-sys", "openssl-probe", "cred"]

examples/diff.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,13 @@ fn tree_to_treeish<'a>(
319319

320320
fn resolve_blob<'a>(repo: &'a Repository, arg: Option<&String>) -> Result<Option<Blob<'a>>, Error> {
321321
let arg = match arg {
322-
Some(s) => Oid::from_str(s)?,
322+
Some(s) => {
323+
#[cfg(not(feature = "unstable-sha256"))]
324+
let oid = Oid::from_str(s)?;
325+
#[cfg(feature = "unstable-sha256")]
326+
let oid = Oid::from_str(s, repo.object_format())?;
327+
oid
328+
}
323329
None => return Ok(None),
324330
};
325331
repo.find_blob(arg).map(|b| Some(b))

src/commit.rs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -437,10 +437,15 @@ mod tests {
437437
assert_eq!(commit.parents().count(), 0);
438438

439439
let tree_header_bytes = commit.header_field_bytes("tree").unwrap();
440-
assert_eq!(
441-
crate::Oid::from_str(tree_header_bytes.as_str().unwrap()).unwrap(),
442-
commit.tree_id()
443-
);
440+
let tree_oid = {
441+
let str = tree_header_bytes.as_str().unwrap();
442+
#[cfg(not(feature = "unstable-sha256"))]
443+
let oid = crate::Oid::from_str(str).unwrap();
444+
#[cfg(feature = "unstable-sha256")]
445+
let oid = crate::Oid::from_str(str, repo.object_format()).unwrap();
446+
oid
447+
};
448+
assert_eq!(tree_oid, commit.tree_id());
444449
assert_eq!(commit.author().name(), Some("name"));
445450
assert_eq!(commit.author().email(), Some("email"));
446451
assert_eq!(commit.committer().name(), Some("name"));
@@ -467,4 +472,45 @@ mod tests {
467472
.ok()
468473
.unwrap();
469474
}
475+
476+
#[test]
477+
#[cfg(feature = "unstable-sha256")]
478+
fn smoke_sha256() {
479+
let (_td, repo) = crate::test::repo_init_sha256();
480+
let head = repo.head().unwrap();
481+
let target = head.target().unwrap();
482+
let commit = repo.find_commit(target).unwrap();
483+
484+
// Verify SHA256 OID (32 bytes)
485+
assert_eq!(commit.id().as_bytes().len(), 32);
486+
assert_eq!(commit.tree_id().as_bytes().len(), 32);
487+
488+
assert_eq!(commit.message(), Some("initial\n\nbody"));
489+
assert_eq!(commit.body(), Some("body"));
490+
assert_eq!(commit.id(), target);
491+
commit.summary().unwrap();
492+
commit.tree().unwrap();
493+
assert_eq!(commit.parents().count(), 0);
494+
495+
let tree_header_bytes = commit.header_field_bytes("tree").unwrap();
496+
let tree_oid = {
497+
let str = tree_header_bytes.as_str().unwrap();
498+
let oid = crate::Oid::from_str(str, repo.object_format()).unwrap();
499+
oid
500+
};
501+
assert_eq!(tree_oid, commit.tree_id());
502+
503+
// Create child commit with parent
504+
let sig = repo.signature().unwrap();
505+
let tree = repo.find_tree(commit.tree_id()).unwrap();
506+
let id = repo
507+
.commit(Some("HEAD"), &sig, &sig, "bar", &tree, &[&commit])
508+
.unwrap();
509+
let head = repo.find_commit(id).unwrap();
510+
511+
// Verify child commit ID is also SHA256
512+
assert_eq!(head.id().as_bytes().len(), 32);
513+
assert_eq!(head.parent_count(), 1);
514+
assert_eq!(head.parent_id(0).unwrap(), commit.id());
515+
}
470516
}

src/diff.rs

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -310,16 +310,27 @@ impl Diff<'static> {
310310
/// two trees, however there may be subtle differences. For example,
311311
/// a patch file likely contains abbreviated object IDs, so the
312312
/// object IDs parsed by this function will also be abbreviated.
313-
pub fn from_buffer(buffer: &[u8]) -> Result<Diff<'static>, Error> {
313+
pub fn from_buffer(
314+
buffer: &[u8],
315+
#[cfg(feature = "unstable-sha256")] format: crate::ObjectFormat,
316+
) -> Result<Diff<'static>, Error> {
314317
crate::init();
315318
let mut diff: *mut raw::git_diff = std::ptr::null_mut();
319+
let data = buffer.as_ptr() as *const c_char;
320+
let len = buffer.len();
316321
unsafe {
317322
// NOTE: Doesn't depend on repo, so lifetime can be 'static
318-
try_call!(raw::git_diff_from_buffer(
319-
&mut diff,
320-
buffer.as_ptr() as *const c_char,
321-
buffer.len()
322-
));
323+
#[cfg(not(feature = "unstable-sha256"))]
324+
try_call!(raw::git_diff_from_buffer(&mut diff, data, len));
325+
#[cfg(feature = "unstable-sha256")]
326+
{
327+
let mut opts: raw::git_diff_parse_options = std::mem::zeroed();
328+
opts.version = raw::GIT_DIFF_PARSE_OPTIONS_VERSION;
329+
opts.oid_type = format.raw();
330+
try_call!(raw::git_diff_from_buffer_ext(
331+
&mut diff, data, len, &mut opts
332+
));
333+
}
323334
Ok(Diff::from_raw(diff))
324335
}
325336
}
@@ -1552,6 +1563,8 @@ impl DiffPatchidOptions {
15521563

15531564
#[cfg(test)]
15541565
mod tests {
1566+
#[cfg(feature = "unstable-sha256")]
1567+
use crate::Diff;
15551568
use crate::{DiffLineType, DiffOptions, Oid, Signature, Time};
15561569
use std::borrow::Borrow;
15571570
use std::fs::File;
@@ -1858,4 +1871,37 @@ mod tests {
18581871

18591872
assert_eq!(result.unwrap_err().code(), crate::ErrorCode::User);
18601873
}
1874+
1875+
#[test]
1876+
#[cfg(feature = "unstable-sha256")]
1877+
fn diff_sha256() {
1878+
let (_td, repo) = crate::test::repo_init_sha256();
1879+
let diff = repo.diff_tree_to_workdir(None, None).unwrap();
1880+
assert_eq!(diff.deltas().len(), 0);
1881+
let stats = diff.stats().unwrap();
1882+
assert_eq!(stats.insertions(), 0);
1883+
assert_eq!(stats.deletions(), 0);
1884+
assert_eq!(stats.files_changed(), 0);
1885+
let patchid = diff.patchid(None).unwrap();
1886+
1887+
// Verify SHA256 OID (32 bytes)
1888+
assert_eq!(patchid.as_bytes().len(), 32);
1889+
}
1890+
1891+
#[test]
1892+
#[cfg(feature = "unstable-sha256")]
1893+
fn diff_from_buffer_sha256() {
1894+
// Minimal patch with SHA256 OID (64 chars)
1895+
let patch = b"diff --git a/file.txt b/file.txt
1896+
index 0000000000000000000000000000000000000000000000000000000000000000..1111111111111111111111111111111111111111111111111111111111111111 100644
1897+
--- a/file.txt
1898+
+++ b/file.txt
1899+
@@ -1 +1 @@
1900+
-old
1901+
+new
1902+
";
1903+
1904+
let diff = Diff::from_buffer(patch, crate::ObjectFormat::Sha256).unwrap();
1905+
assert_eq!(diff.deltas().len(), 1);
1906+
}
18611907
}

src/index.rs

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,23 @@ impl Index {
9090
///
9191
/// This index object cannot be read/written to the filesystem, but may be
9292
/// used to perform in-memory index operations.
93+
///
94+
/// <div class="warning">
95+
///
96+
/// # SHA1-only limitation
97+
///
98+
/// This method **always** creates a SHA1 index.
99+
///
100+
/// In future releases, this will be removed entirely to avoid misuse.
101+
///
102+
/// Consider these alternatives:
103+
///
104+
/// * [`Index::with_object_format`] if an in-memory index is needed
105+
/// * [`Repository::index`] if you have repository context
106+
///
107+
/// </div>
108+
#[cfg(not(feature = "unstable-sha256"))]
109+
#[deprecated = "this always creates a SHA1 index, consider using `Index::with_object_format`"]
93110
pub fn new() -> Result<Index, Error> {
94111
crate::init();
95112
let mut raw = ptr::null_mut();
@@ -99,6 +116,30 @@ impl Index {
99116
}
100117
}
101118

119+
/// Creates a new in-memory index with the specified object format.
120+
///
121+
/// This index object cannot be read/written to the filesystem, but may be
122+
/// used to perform in-memory index operations.
123+
pub fn with_object_format(format: crate::ObjectFormat) -> Result<Index, Error> {
124+
crate::init();
125+
let mut raw = ptr::null_mut();
126+
unsafe {
127+
#[cfg(not(feature = "unstable-sha256"))]
128+
{
129+
let _ = format;
130+
try_call!(raw::git_index_new(&mut raw));
131+
}
132+
#[cfg(feature = "unstable-sha256")]
133+
{
134+
let mut opts: raw::git_index_options = std::mem::zeroed();
135+
opts.version = raw::GIT_INDEX_OPTIONS_VERSION;
136+
opts.oid_type = format.raw();
137+
try_call!(raw::git_index_new_ext(&mut raw, &opts));
138+
}
139+
Ok(Binding::from_raw(raw))
140+
}
141+
}
142+
102143
/// Create a new bare Git index object as a memory representation of the Git
103144
/// index file in 'index_path', without a repository to back it.
104145
///
@@ -107,13 +148,24 @@ impl Index {
107148
///
108149
/// If you need an index attached to a repository, use the `index()` method
109150
/// on `Repository`.
110-
pub fn open(index_path: &Path) -> Result<Index, Error> {
151+
pub fn open(
152+
index_path: &Path,
153+
#[cfg(feature = "unstable-sha256")] format: crate::ObjectFormat,
154+
) -> Result<Index, Error> {
111155
crate::init();
112156
let mut raw = ptr::null_mut();
113157
// Normal file path OK (does not need Windows conversion).
114158
let index_path = index_path.into_c_string()?;
115159
unsafe {
160+
#[cfg(not(feature = "unstable-sha256"))]
116161
try_call!(raw::git_index_open(&mut raw, index_path));
162+
#[cfg(feature = "unstable-sha256")]
163+
{
164+
let mut opts: raw::git_index_options = std::mem::zeroed();
165+
opts.version = raw::GIT_INDEX_OPTIONS_VERSION;
166+
opts.oid_type = format.raw();
167+
try_call!(raw::git_index_open_ext(&mut raw, index_path, &opts));
168+
}
117169
Ok(Binding::from_raw(raw))
118170
}
119171
}
@@ -846,11 +898,12 @@ mod tests {
846898
use std::path::Path;
847899
use tempfile::TempDir;
848900

901+
use crate::ObjectFormat;
849902
use crate::{ErrorCode, Index, IndexEntry, IndexTime, Oid, Repository, ResetType};
850903

851904
#[test]
852905
fn smoke() {
853-
let mut index = Index::new().unwrap();
906+
let mut index = Index::with_object_format(ObjectFormat::Sha1).unwrap();
854907
assert!(index.add_path(&Path::new(".")).is_err());
855908
index.clear().unwrap();
856909
assert_eq!(index.len(), 0);
@@ -867,7 +920,10 @@ mod tests {
867920
index.path().map(|s| s.to_path_buf()),
868921
Some(repo.path().join("index"))
869922
);
923+
#[cfg(not(feature = "unstable-sha256"))]
870924
Index::open(&repo.path().join("index")).unwrap();
925+
#[cfg(feature = "unstable-sha256")]
926+
Index::open(&repo.path().join("index"), ObjectFormat::Sha1).unwrap();
871927

872928
index.clear().unwrap();
873929
index.read(true).unwrap();
@@ -949,7 +1005,7 @@ mod tests {
9491005

9501006
#[test]
9511007
fn add_then_read() {
952-
let mut index = Index::new().unwrap();
1008+
let mut index = Index::with_object_format(ObjectFormat::Sha1).unwrap();
9531009
let mut e = entry();
9541010
e.path = b"foobar".to_vec();
9551011
index.add(&e).unwrap();
@@ -959,7 +1015,7 @@ mod tests {
9591015

9601016
#[test]
9611017
fn add_then_find() {
962-
let mut index = Index::new().unwrap();
1018+
let mut index = Index::with_object_format(ObjectFormat::Sha1).unwrap();
9631019
let mut e = entry();
9641020
e.path = b"foo/bar".to_vec();
9651021
index.add(&e).unwrap();
@@ -1004,10 +1060,38 @@ mod tests {
10041060
uid: 0,
10051061
gid: 0,
10061062
file_size: 0,
1063+
#[cfg(not(feature = "unstable-sha256"))]
10071064
id: Oid::from_bytes(&[0; 20]).unwrap(),
1065+
#[cfg(feature = "unstable-sha256")]
1066+
id: Oid::from_bytes(&[0; 32]).unwrap(),
10081067
flags: 0,
10091068
flags_extended: 0,
10101069
path: Vec::new(),
10111070
}
10121071
}
1072+
1073+
#[test]
1074+
#[cfg(feature = "unstable-sha256")]
1075+
fn index_sha256() {
1076+
let (_td, repo) = crate::test::repo_init_sha256();
1077+
let mut index = repo.index().unwrap();
1078+
1079+
// Test opening with correct format
1080+
Index::open(&repo.path().join("index"), ObjectFormat::Sha256).unwrap();
1081+
1082+
// Test basic operations with SHA256
1083+
index.clear().unwrap();
1084+
index.read(true).unwrap();
1085+
index.write().unwrap();
1086+
let tree_id = index.write_tree().unwrap();
1087+
1088+
// Verify OID is 32 bytes (SHA256)
1089+
assert_eq!(tree_id.as_bytes().len(), 32);
1090+
}
1091+
1092+
#[test]
1093+
#[cfg(feature = "unstable-sha256")]
1094+
fn smooke_in_memory_index_sha256() {
1095+
let _index = Index::with_object_format(ObjectFormat::Sha256).unwrap();
1096+
}
10131097
}

0 commit comments

Comments
 (0)