Skip to content

Add gitoxide backend #1

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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
19 changes: 17 additions & 2 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,21 @@ jobs:
with:
command: clippy
args: --all --tests -- -D warnings
- name: Clippy with all features
- name: Clippy with all features (git2)
uses: actions-rs/cargo@v1
with:
command: clippy
args: --features=chrono,git2,semver --all --tests -- -D warnings
- name: Clippy with all features (gitoxide)
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-features --all --tests -- -D warnings
- name: Clippy with all features (gitoxide only)
uses: actions-rs/cargo@v1
with:
command: clippy
args: --features=gix --all --tests -- -D warnings
- name: Clippy example_project
uses: actions-rs/cargo@v1
with:
Expand All @@ -105,7 +115,12 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: test
- name: Test with all features
- name: Test with all features (git2)
uses: actions-rs/cargo@v1
with:
command: test
args: --features=chrono,git2,semver
- name: Test with all features (gitoxide)
uses: actions-rs/cargo@v1
with:
command: test
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ cargo-lock = { version = "9.0", optional = true, default-features = false }
semver = { version = "1.0", optional = true }
chrono = { version = "0.4", optional = true, default-features = false, features = ["clock"] }
git2 = { version = "0.17", optional = true, default-features = false, features = [] }
gix = { version = "0.51.0", optional = true, default-features = false, features = ["max-performance-safe"] }

[dev-dependencies]
tempfile = "3"

[package.metadata.docs.rs]
features = [ "chrono", "git2", "semver" ]
features = [ "chrono", "git2", "gitoxide", "semver" ]
64 changes: 11 additions & 53 deletions src/git.rs
Original file line number Diff line number Diff line change
@@ -1,71 +1,29 @@
use crate::{fmt_option_str, write_variable};
use crate::git_shared::RepoInfo;
use std::{fs, io, path};

pub fn write_git_version(manifest_location: &path::Path, mut w: &fs::File) -> io::Result<()> {
use io::Write;

pub fn write_git_version(manifest_location: &path::Path, w: &fs::File) -> io::Result<()> {
// CIs will do shallow clones of repositories, causing libgit2 to error
// out. We try to detect if we are running on a CI and ignore the
// error.
let (tag, dirty) = match get_repo_description(manifest_location) {
Ok(Some((tag, dirty))) => (Some(tag), Some(dirty)),
_ => (None, None),
};
write_variable!(
w,
"GIT_VERSION",
"Option<&str>",
fmt_option_str(tag),
"If the crate was compiled from within a git-repository, \
`GIT_VERSION` contains HEAD's tag. The short commit id is used if HEAD is not tagged."
);
write_variable!(
w,
"GIT_DIRTY",
"Option<bool>",
match dirty {
Some(true) => "Some(true)",
Some(false) => "Some(false)",
None => "None",
},
"If the repository had dirty/staged files."
);

let (branch, commit, commit_short) = match get_repo_head(manifest_location) {
Ok(Some((b, c, cs))) => (b, Some(c), Some(cs)),
_ => (None, None, None),
};

let doc = "If the crate was compiled from within a git-repository, `GIT_HEAD_REF` \
contains full name to the reference pointed to by HEAD \
(e.g.: `refs/heads/master`). If HEAD is detached or the branch name is not \
valid UTF-8 `None` will be stored.\n";
write_variable!(
crate::git_shared::write_variables(
w,
"GIT_HEAD_REF",
"Option<&str>",
fmt_option_str(branch),
doc
);

write_variable!(
w,
"GIT_COMMIT_HASH",
"Option<&str>",
fmt_option_str(commit),
"If the crate was compiled from within a git-repository, `GIT_COMMIT_HASH` \
contains HEAD's full commit SHA-1 hash."
);

write_variable!(
w,
"GIT_COMMIT_HASH_SHORT",
"Option<&str>",
fmt_option_str(commit_short),
"If the crate was compiled from within a git-repository, `GIT_COMMIT_HASH_SHORT` \
contains HEAD's short commit SHA-1 hash."
);

RepoInfo {
branch,
tag,
dirty,
commit_id: commit,
commit_id_short: commit_short,
},
)?;
Ok(())
}

Expand Down
67 changes: 67 additions & 0 deletions src/git_shared.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use crate::{fmt_option_str, write_variable};
use std::{fs, io};

#[derive(Debug, Default, PartialEq)]
pub(crate) struct RepoInfo {
pub branch: Option<String>,
pub tag: Option<String>,
pub dirty: Option<bool>,
pub commit_id: Option<String>,
pub commit_id_short: Option<String>,
}

pub(crate) fn write_variables(mut w: &fs::File, info: RepoInfo) -> io::Result<()> {
use io::Write;

write_variable!(
w,
"GIT_VERSION",
"Option<&str>",
fmt_option_str(info.tag),
"If the crate was compiled from within a git-repository, \
`GIT_VERSION` contains HEAD's tag. The short commit id is used if HEAD is not tagged."
);
write_variable!(
w,
"GIT_DIRTY",
"Option<bool>",
match info.dirty {
Some(true) => "Some(true)",
Some(false) => "Some(false)",
None => "None",
},
"If the repository had dirty/staged files."
);

let doc = "If the crate was compiled from within a git-repository, `GIT_HEAD_REF` \
contains full name to the reference pointed to by HEAD \
(e.g.: `refs/heads/master`). If HEAD is detached or the branch name is not \
valid UTF-8 `None` will be stored.\n";
write_variable!(
w,
"GIT_HEAD_REF",
"Option<&str>",
fmt_option_str(info.branch),
doc
);

write_variable!(
w,
"GIT_COMMIT_HASH",
"Option<&str>",
fmt_option_str(info.commit_id),
"If the crate was compiled from within a git-repository, `GIT_COMMIT_HASH` \
contains HEAD's full commit SHA-1 hash."
);

write_variable!(
w,
"GIT_COMMIT_HASH_SHORT",
"Option<&str>",
fmt_option_str(info.commit_id_short),
"If the crate was compiled from within a git-repository, `GIT_COMMIT_HASH_SHORT` \
contains HEAD's short commit SHA-1 hash."
);

Ok(())
}
137 changes: 137 additions & 0 deletions src/gix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use crate::git_shared::RepoInfo;
use std::{fs, io, path};

fn get_repo_info(manifest_location: &path::Path) -> Option<RepoInfo> {
let repo = gix::discover(manifest_location).ok()?;

let branch = repo.head_name().ok()?.map(|n| n.to_string());

let repo_info = if let Ok(commit) = repo.head_commit() {
RepoInfo {
branch,
tag: commit.describe().format().ok().map(|f| f.to_string()),
dirty: is_dirty(manifest_location),
commit_id: Some(commit.id().to_string()),
commit_id_short: commit.id().shorten().ok().map(|p| p.to_string()),
}
} else {
RepoInfo {
branch,
..Default::default()
}
};

Some(repo_info)
}

// TODO: implement this with `gix`
fn is_dirty(_manifest_location: &path::Path) -> Option<bool> {
None
}

pub(crate) fn write_git_version(
manifest_location: &path::Path,
w: &mut fs::File,
) -> io::Result<()> {
let info = get_repo_info(manifest_location).unwrap_or_default();
crate::git_shared::write_variables(w, info)
}

// NOTE: Copy-pasted test from `git2` with adaptation to `gix`

#[cfg(test)]
#[cfg(feature = "git2")]
mod tests {
// TODO: unify tests
#[test]
fn parse_git_repo() {
use std::fs;
use std::path;

let repo_root = tempfile::tempdir().unwrap();
assert_eq!(super::get_repo_info(repo_root.as_ref()), None);

let repo = git2::Repository::init_opts(
&repo_root,
git2::RepositoryInitOptions::new()
.external_template(false)
.mkdir(false)
.no_reinit(true)
.mkpath(false),
)
.unwrap();

let cruft_file = repo_root.path().join("cruftfile");
std::fs::write(&cruft_file, "Who? Me?").unwrap();

let project_root = repo_root.path().join("project_root");
fs::create_dir(&project_root).unwrap();

let sig = git2::Signature::now("foo", "bar").unwrap();
let mut idx = repo.index().unwrap();
idx.add_path(path::Path::new("cruftfile")).unwrap();
idx.write().unwrap();
let commit_oid = repo
.commit(
Some("HEAD"),
&sig,
&sig,
"Testing testing 1 2 3",
&repo.find_tree(idx.write_tree().unwrap()).unwrap(),
&[],
)
.unwrap();

let binding = repo
.find_commit(commit_oid)
.unwrap()
.into_object()
.short_id()
.unwrap();

let commit_oid_short = binding.as_str().unwrap();

let commit_hash = format!("{}", commit_oid);
let commit_hash_short = commit_oid_short.to_string();

assert!(commit_hash.starts_with(&commit_hash_short));

// The commit, the commit-id is something and the repo is not dirty
let repo_info = super::get_repo_info(&project_root).unwrap();
assert!(!repo_info.tag.unwrap().is_empty());
assert_eq!(repo_info.dirty, None, "Needs implementation");

// Tag the commit, it should be retrieved
repo.tag(
"foobar",
&repo
.find_object(commit_oid, Some(git2::ObjectType::Commit))
.unwrap(),
&sig,
"Tagged foobar",
false,
)
.unwrap();

let repo_info = super::get_repo_info(&project_root).unwrap();
assert_eq!(repo_info.tag, Some(String::from("foobar")));
assert_eq!(repo_info.dirty, None, "needs implementation");

// Make some dirt
std::fs::write(cruft_file, "now dirty").unwrap();
let repo_info = super::get_repo_info(&project_root).unwrap();
assert_eq!(repo_info.tag, Some(String::from("foobar")));
assert_eq!(repo_info.dirty, None, "needs implementation");

let branch_short_name = "baz";
let branch_name = "refs/heads/baz";
let commit = repo.find_commit(commit_oid).unwrap();
repo.branch(branch_short_name, &commit, true).unwrap();
repo.set_head(branch_name).unwrap();

let repo_info = super::get_repo_info(&project_root).unwrap();
assert_eq!(repo_info.branch, Some(branch_name.to_owned()));
assert_eq!(repo_info.commit_id, Some(commit_hash));
assert_eq!(repo_info.commit_id_short, Some(commit_hash_short));
}
}
20 changes: 17 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,12 @@ mod dependencies;
mod environment;
#[cfg(feature = "git2")]
mod git;
#[cfg(feature = "gix")]
mod gix;

#[cfg(any(feature = "git2", feature = "gix"))]
mod git_shared;

#[cfg(feature = "chrono")]
mod krono;
pub mod util;
Expand Down Expand Up @@ -339,7 +345,7 @@ impl Options {
/// result. `GIT_VERSION` and `GIT_DIRTY` will therefor always be `None` if
/// a CI-platform is detected.
///
#[cfg(feature = "git2")]
#[cfg(any(feature = "git2", feature = "gix"))]
pub fn set_git(&mut self, enabled: bool) -> &mut Self {
self.git = enabled;
self
Expand Down Expand Up @@ -502,7 +508,8 @@ impl Options {
/// `OUR_DIR`.
pub fn write_built_file_with_opts(
options: &Options,
#[cfg(any(feature = "cargo-lock", feature = "git2"))] manifest_location: &path::Path,
#[cfg(any(feature = "cargo-lock", feature = "git2", feature = "gix"))]
manifest_location: &path::Path,
dst: &path::Path,
) -> io::Result<()> {
let mut built_file = fs::File::create(dst)?;
Expand Down Expand Up @@ -532,6 +539,13 @@ pub fn write_built_file_with_opts(
{
o!(git, git::write_git_version(manifest_location, &built_file)?);
}
#[cfg(feature = "gix")]
{
o!(
git,
gix::write_git_version(manifest_location, &mut built_file)?
);
}
}
#[cfg(feature = "cargo-lock")]
o!(
Expand Down Expand Up @@ -564,7 +578,7 @@ pub fn write_built_file() -> io::Result<()> {
let dst = path::Path::new(&env::var("OUT_DIR").unwrap()).join("built.rs");
write_built_file_with_opts(
&Options::default(),
#[cfg(any(feature = "cargo-lock", feature = "git2"))]
#[cfg(any(feature = "cargo-lock", feature = "git2", feature = "gix"))]
env::var("CARGO_MANIFEST_DIR").unwrap().as_ref(),
&dst,
)?;
Expand Down