Skip to content

Commit

Permalink
Merge pull request #6094 from gitbutlerapp/add-author-and-commit-details
Browse files Browse the repository at this point in the history
v3 stack branches: add author and createdAt info
  • Loading branch information
krlvi authored Jan 27, 2025
2 parents 07134f3 + ad48eeb commit 97c332d
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 3 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 40 additions & 2 deletions apps/desktop/src/lib/branches/v3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ export type Commits = {
* In either case this is effectively a list of commits that in the working copy which may or may not have been pushed to the remote.
*/
readonly localAndRemote: Commit[];

/**
* List of commits that exist **only** on the upstream branch. Ordered from newest to oldest.
* Created from the tip of the local tracking branch eg. refs/remotes/origin/my-branch -> refs/heads/my-branch
*
* This does **not** include the commits that are in the commits list (local)
* This is effectively the list of commits that are on the remote branch but are not in the working copy.
*/
readonly upstreamOnly: UpstreamCommit[];
};

/** Commit that is a part of a [`StackBranch`](gitbutler_stack::StackBranch) and, as such, containing state derived in relation to the specific branch.*/
Expand All @@ -75,13 +84,42 @@ export type Commit = {
* GitButler will perform rebasing/reordering etc without interruptions and flag commits as conflicted if needed.
* Conflicts are resolved via the Edit Mode mechanism.
*/
readonly has_conflicts: boolean;
readonly hasConflicts: boolean;
/**
* Represents wether the the commit is considered integrated, local only,
* or local and remote with respect to the branch it belongs to.
* Note that remote only commits in the context of a branch are expressed with the [`UpstreamCommit`] struct instead of this.
*/
readonly state: CommitState;
/** Commit creation time in Epoch milliseconds. */
readonly createdAt: string;
/** The author of the commit. */
readonly author: Author;
};

/**
* Commit that is only at the remote.
* Unlike the `Commit` struct, there is no knowledge of GitButler concepts like conflicted state etc.
*/
export type UpstreamCommit = {
/** The OID of the commit. */
readonly id: string;
/** The message of the commit. */
readonly message: string;
/** Commit creation time in Epoch milliseconds. */
readonly createdAt: number;
/** The author of the commit. */
readonly author: Author;
};

/** Represents the author of a commit. */
export type Author = {
/** The name from the git commit signature */
readonly name: string;
/** The email from the git commit signature */
readonly email: string;
/** A URL to a gravatar image for the email from the commit signature */
readonly gravatarUrl: string;
};

/** Represents the state a commit could be in. */
Expand All @@ -94,7 +132,7 @@ export type CommitState =
* - The commit has been pushed to the remote
* - The commit has been copied from a remote commit (when applying a remote branch)
*
* This variant carries the remote commit id.
* This variant carries the remote commit id in the `subject` field.
* The remote commit id may be the same as the `id` or it may be different if the local commit has been rebased or updated in another way.
*/
| { readonly type: 'LocalAndRemote'; readonly subject: string }
Expand Down
3 changes: 2 additions & 1 deletion crates/but-workspace/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ gix = { workspace = true, features = [
"parallel",
"serde",
"status",
"revision"
] }
gitbutler-stack.workspace = true
gitbutler-command-context.workspace = true
Expand All @@ -29,3 +28,5 @@ gitbutler-repo.workspace = true
serde = { workspace = true, features = ["std"] }
gitbutler-serde.workspace = true
itertools = "0.14"
url = { version = "2.5.4", features = ["serde"] }
md5 = "0.7.0"
51 changes: 51 additions & 0 deletions crates/but-workspace/src/author.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//! This code is a fork of [`gitbutler_branch_actions::author`] to avoid depending on the `gitbutler_branch_actions` crate.
use anyhow::Result;
use bstr::ByteSlice;
use serde::Serialize;

/// Represents the author of a commit.
#[derive(Debug, Serialize, Hash, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Author {
/// The name from the git commit signature
pub name: String,
/// The email from the git commit signature
pub email: String,
/// A URL to a gravatar image for the email from the commit signature
pub gravatar_url: url::Url,
}

impl From<git2::Signature<'_>> for Author {
fn from(value: git2::Signature<'_>) -> Self {
let name = value.name().unwrap_or_default().to_string();
let email = value.email().unwrap_or_default().to_string();

let gravatar_url = gravatar_url_from_email(email.as_str()).unwrap();

Author {
name,
email,
gravatar_url,
}
}
}

impl From<gix::actor::SignatureRef<'_>> for Author {
fn from(value: gix::actor::SignatureRef<'_>) -> Self {
let gravatar_url = gravatar_url_from_email(&value.email.to_str_lossy()).unwrap();

Author {
name: value.name.to_owned().to_string(),
email: value.email.to_owned().to_string(),
gravatar_url,
}
}
}

pub fn gravatar_url_from_email(email: &str) -> Result<url::Url> {
let gravatar_url = format!(
"https://www.gravatar.com/avatar/{:x}?s=100&r=g&d=retro",
md5::compute(email.to_lowercase())
);
url::Url::parse(gravatar_url.as_str()).map_err(Into::into)
}
20 changes: 20 additions & 0 deletions crates/but-workspace/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
//!
use anyhow::{Context, Result};
use author::Author;
use bstr::BString;
use gitbutler_command_context::CommandContext;
use gitbutler_commit::commit_ext::CommitExt;
Expand All @@ -31,6 +32,7 @@ use std::collections::HashMap;
use std::path::Path;
use std::str::FromStr;

mod author;
mod integrated;

/// Represents a lightweight version of a [`gitbutler_stack::Stack`] for listing.
Expand Down Expand Up @@ -86,6 +88,7 @@ pub enum CommitState {

/// Commit that is a part of a [`StackBranch`](gitbutler_stack::StackBranch) and, as such, containing state derived in relation to the specific branch.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Commit {
/// The OID of the commit.
#[serde(with = "gitbutler_serde::object_id")]
Expand All @@ -102,18 +105,27 @@ pub struct Commit {
/// or local and remote with respect to the branch it belongs to.
/// Note that remote only commits in the context of a branch are expressed with the [`UpstreamCommit`] struct instead of this.
pub state: CommitState,
/// Commit creation time in Epoch milliseconds.
pub created_at: u128,
/// The author of the commit.
pub author: Author,
}

/// Commit that is only at the remote.
/// Unlike the `Commit` struct, there is no knowledge of GitButler concepts like conflicted state etc.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UpstreamCommit {
/// The OID of the commit.
#[serde(with = "gitbutler_serde::object_id")]
pub id: gix::ObjectId,
/// The message of the commit.
#[serde(with = "gitbutler_serde::bstring_lossy")]
pub message: BString,
/// Commit creation time in Epoch milliseconds.
pub created_at: u128,
/// The author of the commit.
pub author: Author,
}

/// Represents a branch in a [`Stack`]. It contains commits derived from the local pseudo branch and it's respective remote
Expand All @@ -140,6 +152,7 @@ pub struct Branch {

/// List of commits beloning to this branch. Ordered from newest to oldest (child-most to parent-most).
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Commits {
/// Commits that are currently part of the workspace (applied).
/// Created from the local pseudo branch (head currently stored in the TOML file)
Expand Down Expand Up @@ -252,11 +265,15 @@ fn convert(
}
};

let created_at = u128::try_from(commit.time().seconds())? * 1000;

let api_commit = Commit {
id: commit.id().to_gix(),
message: commit.message_bstr().into(),
has_conflicts: commit.is_conflicted(),
state,
created_at,
author: commit.author().into(),
};
local_and_remote.push(api_commit);
}
Expand All @@ -273,9 +290,12 @@ fn convert(
});
// Ignore commits that strictly speaking are remote only but they match a known local commit (rebase etc)
if !matches_known_commit {
let created_at = u128::try_from(commit.time().seconds())? * 1000;
let upstream_commit = UpstreamCommit {
id: commit.id().to_gix(),
message: commit.message_bstr().into(),
created_at,
author: commit.author().into(),
};
upstream_only.push(upstream_commit);
}
Expand Down

0 comments on commit 97c332d

Please sign in to comment.