summaryrefslogtreecommitdiff
path: root/crates/git-tracker
diff options
context:
space:
mode:
authorseth <[email protected]>2024-06-16 07:15:13 -0400
committerGitHub <[email protected]>2024-06-16 07:15:13 -0400
commitd25129d829e0ebd70b4e60e399fe91c0d80aa1ad (patch)
tree2a62992f2980f9fed2204ef5ef708a0228998cf1 /crates/git-tracker
parenta0bfcc1587e3cef1b8f6fa0508a280fc48c82231 (diff)
use libgit2 to track PRs (#10)v0.2.0
* nix: don't depend on registry for nixpkgs input * use libgit2 to track PRs * nix: don't use ci devShell as defaul * crates: bump serenity from `9ad74d4` to `0.12.2 * nix: fix cross compiled builds * crates: split more from client * bot-jobs: update remote refs more efficiently * git-tracker: account for HEAD commits * bot-config: use nixpkgs branches from environment * bot-commands: don't display branches prs haven't landed in * git-tracker: return false when commits aren't found this is annoying as a hard error since it turns out github will report garbage merge commit SHAs for PRs that *haven't* been merged yet. yay * bot: improve docs in some places * bot-client: display invite link on start * bot-http: add TeawieClientExt * bot-commands: add /about * docs: update readme todos * nix: enable StateDirectory in module * crates: bump to 0.2.0
Diffstat (limited to 'crates/git-tracker')
-rw-r--r--crates/git-tracker/Cargo.toml27
-rw-r--r--crates/git-tracker/src/lib.rs4
-rw-r--r--crates/git-tracker/src/tracker.rs109
3 files changed, 140 insertions, 0 deletions
diff --git a/crates/git-tracker/Cargo.toml b/crates/git-tracker/Cargo.toml
new file mode 100644
index 0000000..60baa41
--- /dev/null
+++ b/crates/git-tracker/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "git-tracker"
+version = "0.2.0"
+edition = "2021"
+
+authors = ["seth <getchoo at tuta dot io>"]
+description = "A library that helps you track commits and branches in a Git repository"
+repository = "https://github.com/getchoo/nixpkgs-tracker-bot"
+
+publish = false
+
+[dependencies]
+git2 = { workspace = true }
+log = { workspace = true }
+thiserror = "1.0.61"
+
+[lints.rust]
+async_fn_in_trait = "allow"
+unsafe_code = "forbid"
+
+[lints.clippy]
+complexity = "warn"
+correctness = "deny"
+pedantic = "warn"
+perf = "warn"
+style = "warn"
+suspicious = "deny"
diff --git a/crates/git-tracker/src/lib.rs b/crates/git-tracker/src/lib.rs
new file mode 100644
index 0000000..cb0907b
--- /dev/null
+++ b/crates/git-tracker/src/lib.rs
@@ -0,0 +1,4 @@
+//! A library that helps you track commits and branches in a Git repository
+
+mod tracker;
+pub use tracker::{Error, Tracker};
diff --git a/crates/git-tracker/src/tracker.rs b/crates/git-tracker/src/tracker.rs
new file mode 100644
index 0000000..e26f82d
--- /dev/null
+++ b/crates/git-tracker/src/tracker.rs
@@ -0,0 +1,109 @@
+use std::path::Path;
+
+use git2::{Branch, BranchType, Commit, ErrorCode, Oid, Reference, Repository};
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error("libgit2 error")]
+ Git(#[from] git2::Error),
+ #[error("Repository path not found at `{0}`")]
+ RepositoryPathNotFound(String),
+}
+
+/// Helper struct for tracking Git objects
+pub struct Tracker {
+ repository: Repository,
+}
+
+impl Tracker {
+ /// Create a new [`Tracker`] using the repository at [`path`]
+ ///
+ /// # Errors
+ ///
+ /// Will return [`Err`] if the repository can not be opened
+ pub fn from_path(path: &str) -> Result<Self, Error> {
+ let repository_path = Path::new(path);
+ if repository_path.exists() {
+ let repository = Repository::open(repository_path)?;
+ Ok(Self { repository })
+ } else {
+ Err(Error::RepositoryPathNotFound(path.to_string()))
+ }
+ }
+
+ /// Finds a branch of name [`name`]
+ ///
+ /// # Errors
+ ///
+ /// Will return [`Err`] if the branch cannot be found locally
+ pub fn branch_by_name(&self, name: &str) -> Result<Branch, Error> {
+ Ok(self.repository.find_branch(name, BranchType::Remote)?)
+ }
+
+ /// Finds a commit with a SHA match [`sha`]
+ ///
+ /// # Errors
+ ///
+ /// Will return [`Err`] if [`sha`] cannot be converted an [`Oid`] or
+ /// a commit matching it cannot be found
+ pub fn commit_by_sha(&self, sha: &str) -> Result<Commit, Error> {
+ let oid = Oid::from_str(sha)?;
+ let commit = self.repository.find_commit(oid)?;
+
+ Ok(commit)
+ }
+
+ /// Check if [`Reference`] [`ref`] contains [`Commit`] [`commit`]
+ ///
+ /// # Errors
+ ///
+ /// Will return [`Err`] if the reference cannot be resolved to a commit or the descendants
+ /// of the reference cannot be resolved
+ pub fn ref_contains_commit(
+ &self,
+ reference: &Reference,
+ commit: &Commit,
+ ) -> Result<bool, Error> {
+ let head = reference.peel_to_commit()?;
+
+ // NOTE: we have to check this as `Repository::graph_descendant_of()` (like the name says)
+ // only finds *descendants* of it's parent commit, and will not tell us if the parent commit
+ // *is* the child commit. i have no idea why i didn't think of this, but that's why this
+ // comment is here now
+ let is_head = head.id() == commit.id();
+
+ let has_commit = self
+ .repository
+ .graph_descendant_of(head.id(), commit.id())?;
+
+ Ok(has_commit || is_head)
+ }
+
+ /// Check if a [`Branch`] named [`branch_name`] has a commit with the SHA [`commit_sha`]
+ ///
+ /// # Errors
+ ///
+ /// Will return [`Err`] if the commit SHA cannot be resolved to an object id, the branch name cannot
+ /// be resolved to a branch, or the descendants of the resolved branch cannot be resolved
+ pub fn branch_contains_sha(&self, branch_name: &str, commit_sha: &str) -> Result<bool, Error> {
+ let commit = match self.commit_by_sha(commit_sha) {
+ Ok(commit) => commit,
+ Err(why) => {
+ // NOTE: we assume commits not found are just not in the branch *yet*, not an error
+ // this is because github decides to report merge commit shas for unmerged PRs...yeah
+ if let Error::Git(git_error) = &why {
+ if git_error.code() == ErrorCode::NotFound {
+ return Ok(false);
+ }
+ }
+
+ return Err(why);
+ }
+ };
+
+ let branch = self.branch_by_name(branch_name)?;
+ let has_pr = self.ref_contains_commit(&branch.into_reference(), &commit)?;
+
+ Ok(has_pr)
+ }
+}