let integration commit also respect author and committer times.

This commit is contained in:
Sebastian Thiel 2024-07-30 15:32:38 +02:00
parent 76d687b55c
commit a5bc727705
No known key found for this signature in database
GPG Key ID: 9CB5EE7895E8268B
9 changed files with 86 additions and 101 deletions

1
Cargo.lock generated
View File

@ -1997,6 +1997,7 @@ dependencies = [
"gitbutler-id",
"gitbutler-reference",
"gitbutler-serde",
"gix",
"hex",
"itertools 0.13.0",
"lazy_static",

View File

@ -2,7 +2,7 @@ use std::path::PathBuf;
use anyhow::{Context, Result};
use git2::Commit;
use gitbutler_branch::{Branch, BranchExt, BranchId};
use gitbutler_branch::{Branch, BranchExt, BranchId, SignaturePurpose};
use gitbutler_commit::commit_headers::CommitHeadersV2;
use gitbutler_oplog::SnapshotExt;
use gitbutler_project::access::WorktreeWritePermission;
@ -14,7 +14,6 @@ use crate::{
conflicts::{self},
ensure_selected_for_changes, get_applied_status,
hunk::VirtualBranchHunk,
integration::get_integration_commiter,
VirtualBranchesExt,
};
@ -168,14 +167,15 @@ impl BranchManager<'_> {
message.push_str("\n\n");
// Commit wip commit
let committer = get_integration_commiter()?;
let committer = gitbutler_branch::signature(SignaturePurpose::Committer)?;
let author = gitbutler_branch::signature(SignaturePurpose::Author)?;
let parent = branch.get().peel_to_commit()?;
let commit_headers = CommitHeadersV2::new();
let commit_oid = repo.commit_with_signature(
Some(&branch.try_into()?),
&committer,
&author,
&committer,
&message,
&tree,

View File

@ -3,8 +3,7 @@ use std::{path::PathBuf, vec};
use anyhow::{anyhow, Context, Result};
use bstr::ByteSlice;
use gitbutler_branch::{
self, Branch, BranchCreateRequest, VirtualBranchesHandle,
GITBUTLER_INTEGRATION_COMMIT_AUTHOR_EMAIL, GITBUTLER_INTEGRATION_COMMIT_AUTHOR_NAME,
self, Branch, BranchCreateRequest, SignaturePurpose, VirtualBranchesHandle,
GITBUTLER_INTEGRATION_REFERENCE,
};
use gitbutler_command_context::CommandContext;
@ -18,13 +17,6 @@ use crate::{branch_manager::BranchManagerExt, conflicts, VirtualBranchesExt};
const WORKSPACE_HEAD: &str = "Workspace Head";
const GITBUTLER_INTEGRATION_COMMIT_TITLE: &str = "GitButler Integration Commit";
pub(crate) fn get_integration_commiter<'a>() -> Result<git2::Signature<'a>> {
Ok(git2::Signature::now(
GITBUTLER_INTEGRATION_COMMIT_AUTHOR_NAME,
GITBUTLER_INTEGRATION_COMMIT_AUTHOR_EMAIL,
)?)
}
// Creates and returns a merge commit of all active branch heads.
//
// This is the base against which we diff the working directory to understand
@ -65,9 +57,8 @@ pub(crate) fn get_workspace_head(ctx: &CommandContext) -> Result<git2::Oid> {
}
}
// TODO(mg): Can we make this a constant?
let committer = get_integration_commiter()?;
let committer = gitbutler_branch::signature(SignaturePurpose::Committer)?;
let author = gitbutler_branch::signature(SignaturePurpose::Author)?;
let mut heads: Vec<git2::Commit<'_>> = virtual_branches
.iter()
.filter(|b| b.head != target.sha)
@ -85,7 +76,7 @@ pub(crate) fn get_workspace_head(ctx: &CommandContext) -> Result<git2::Oid> {
let workspace_head_id = repo.commit(
None,
&committer,
&author,
&committer,
WORKSPACE_HEAD,
&workspace_tree,
@ -199,7 +190,8 @@ pub fn update_gitbutler_integration(
message.push_str("For more information about what we're doing here, check out our docs:\n");
message.push_str("https://docs.gitbutler.com/features/virtual-branches/integration-branch\n");
let committer = get_integration_commiter()?;
let committer = gitbutler_branch::signature(SignaturePurpose::Committer)?;
let author = gitbutler_branch::signature(SignaturePurpose::Author)?;
// It would be nice if we could pass an `update_ref` parameter to this function, but that
// requires committing to the tip of the branch, and we're mostly replacing the tip.
@ -209,7 +201,7 @@ pub fn update_gitbutler_integration(
let final_commit = repo.commit(
None,
&committer,
&author,
&committer,
&message,
&workspace_tree,

View File

@ -21,19 +21,6 @@ git init remote
git add . && git commit -m "init"
)
git clone remote single-branch-no-vbranch
git clone remote single-branch-no-vbranch-one-commit
(cd single-branch-no-vbranch-one-commit
echo change >> file && git add . && git commit -m "local change"
)
git clone remote single-branch-no-vbranch-multi-remote
(cd single-branch-no-vbranch-multi-remote
git remote add other-origin ../remote
git fetch other-origin
)
export GITBUTLER_CLI_DATA_DIR=./user/gitbutler/app-data
git clone remote one-vbranch-on-integration
(cd one-vbranch-on-integration
@ -47,6 +34,7 @@ git clone remote one-vbranch-on-integration-one-commit
$CLI branch create virtual
echo change >> file
echo in-index > new && git add new
tick
$CLI branch commit virtual -m "virtual branch change in index and worktree"
)

View File

@ -8,6 +8,7 @@ publish = false
[dependencies]
anyhow = "1.0.86"
git2.workspace = true
gix.workspace = true
gitbutler-reference.workspace = true
gitbutler-serde.workspace = true
gitbutler-id.workspace = true

View File

@ -1,5 +1,8 @@
mod branch;
use anyhow::Context;
pub use branch::{Branch, BranchCreateRequest, BranchId, BranchUpdateRequest};
use bstr::ByteSlice;
mod branch_ext;
pub use branch_ext::BranchExt;
mod reference_ext;
@ -22,5 +25,53 @@ lazy_static! {
gitbutler_reference::LocalRefname::new("gitbutler/integration", None);
}
pub const GITBUTLER_INTEGRATION_COMMIT_AUTHOR_NAME: &str = "GitButler";
pub const GITBUTLER_INTEGRATION_COMMIT_AUTHOR_EMAIL: &str = "gitbutler@gitbutler.com";
pub const GITBUTLER_COMMIT_AUTHOR_NAME: &str = "GitButler";
pub const GITBUTLER_COMMIT_AUTHOR_EMAIL: &str = "gitbutler@gitbutler.com";
pub enum SignaturePurpose {
Author,
Committer,
}
/// Provide a signature with the GitButler author, and the current time or the time overridden
/// depending on the value for `purpose`.
pub fn signature(purpose: SignaturePurpose) -> anyhow::Result<git2::Signature<'static>> {
let signature = gix::actor::SignatureRef {
name: GITBUTLER_COMMIT_AUTHOR_NAME.into(),
email: GITBUTLER_COMMIT_AUTHOR_EMAIL.into(),
time: commit_time(match purpose {
SignaturePurpose::Author => "GIT_AUTHOR_DATE",
SignaturePurpose::Committer => "GIT_COMMITTER_DATE",
}),
};
gix_to_git2_signature(signature)
}
/// Convert `actor` to a `git2` representation or fail if that's not possible.
/// Note that the current time as provided by `gix` is also used as it.
pub fn gix_to_git2_signature(
actor: gix::actor::SignatureRef<'_>,
) -> anyhow::Result<git2::Signature<'static>> {
let offset_in_minutes = actor.time.offset / 60;
let time = git2::Time::new(actor.time.seconds, offset_in_minutes);
Ok(git2::Signature::new(
actor
.name
.to_str()
.with_context(|| format!("Could not process actor name: {}", actor.name))?,
actor
.email
.to_str()
.with_context(|| format!("Could not process actor email: {}", actor.email))?,
&time,
)?)
}
/// Return the time of a commit as `now` unless the `overriding_variable_name` contains a parseable date,
/// which is used instead.
fn commit_time(overriding_variable_name: &str) -> gix::date::Time {
std::env::var(overriding_variable_name)
.ok()
.and_then(|time| gix::date::parse(&time, Some(std::time::SystemTime::now())).ok())
.unwrap_or_else(gix::date::Time::now_local_or_utc)
}

View File

@ -8,10 +8,7 @@ use std::{
use anyhow::{anyhow, bail, Context, Result};
use git2::{DiffOptions, FileMode};
use gitbutler_branch::{
Branch, VirtualBranchesHandle, VirtualBranchesState, GITBUTLER_INTEGRATION_COMMIT_AUTHOR_EMAIL,
GITBUTLER_INTEGRATION_COMMIT_AUTHOR_NAME,
};
use gitbutler_branch::{Branch, SignaturePurpose, VirtualBranchesHandle, VirtualBranchesState};
use gitbutler_diff::{hunks_by_filepath, FileDiff};
use gitbutler_project::{
access::{WorktreeReadPermission, WorktreeWritePermission},
@ -463,19 +460,16 @@ fn commit_snapshot(
.and_then(|head_id| repo.find_commit(head_id).ok());
// Construct a new commit
let signature = git2::Signature::now(
GITBUTLER_INTEGRATION_COMMIT_AUTHOR_NAME,
GITBUTLER_INTEGRATION_COMMIT_AUTHOR_EMAIL,
)
.unwrap();
let committer = gitbutler_branch::signature(SignaturePurpose::Committer)?;
let author = gitbutler_branch::signature(SignaturePurpose::Author)?;
let parents = oplog_head_commit
.as_ref()
.map(|head| vec![head])
.unwrap_or_default();
let snapshot_commit_id = repo.commit(
None,
&signature,
&signature,
&author,
&committer,
&details.to_string(),
&snapshot_tree,
parents.as_slice(),

View File

@ -1,9 +1,7 @@
use std::path::Path;
use anyhow::{Context, Result};
use gitbutler_branch::{
GITBUTLER_INTEGRATION_COMMIT_AUTHOR_EMAIL, GITBUTLER_INTEGRATION_COMMIT_AUTHOR_NAME,
};
use gitbutler_branch::{GITBUTLER_COMMIT_AUTHOR_EMAIL, GITBUTLER_COMMIT_AUTHOR_NAME};
use gitbutler_fs::write;
use gix::config::tree::Key;
@ -83,8 +81,8 @@ fn branch_creation_message(commit_id_hex: &str) -> String {
fn standard_signature() -> gix::actor::SignatureRef<'static> {
gix::actor::SignatureRef {
name: GITBUTLER_INTEGRATION_COMMIT_AUTHOR_NAME.into(),
email: GITBUTLER_INTEGRATION_COMMIT_AUTHOR_EMAIL.into(),
name: GITBUTLER_COMMIT_AUTHOR_NAME.into(),
email: GITBUTLER_COMMIT_AUTHOR_EMAIL.into(),
time: gix::date::Time::now_local_or_utc(),
}
}
@ -155,8 +153,7 @@ mod set_target_ref {
use tempfile::tempdir;
use super::{
set_reference_to_oplog, GITBUTLER_INTEGRATION_COMMIT_AUTHOR_EMAIL,
GITBUTLER_INTEGRATION_COMMIT_AUTHOR_NAME,
set_reference_to_oplog, GITBUTLER_COMMIT_AUTHOR_EMAIL, GITBUTLER_COMMIT_AUTHOR_NAME,
};
#[test]
@ -352,8 +349,8 @@ mod set_target_ref {
}
fn assert_signature(sig: gix::actor::SignatureRef<'_>) {
assert_eq!(sig.name, GITBUTLER_INTEGRATION_COMMIT_AUTHOR_NAME);
assert_eq!(sig.email, GITBUTLER_INTEGRATION_COMMIT_AUTHOR_EMAIL);
assert_eq!(sig.name, GITBUTLER_COMMIT_AUTHOR_NAME);
assert_eq!(sig.email, GITBUTLER_COMMIT_AUTHOR_EMAIL);
assert_ne!(
sig.time.seconds, 0,
"we don't accidentally use the default time as it would caues GC as well"

View File

@ -1,11 +1,7 @@
use std::str::FromStr;
use anyhow::{anyhow, Context, Result};
use bstr::ByteSlice;
use gitbutler_branch::{
Branch, BranchId, GITBUTLER_INTEGRATION_COMMIT_AUTHOR_EMAIL,
GITBUTLER_INTEGRATION_COMMIT_AUTHOR_NAME,
};
use gitbutler_branch::{gix_to_git2_signature, Branch, BranchId, SignaturePurpose};
use gitbutler_command_context::CommandContext;
use gitbutler_commit::commit_headers::CommitHeadersV2;
use gitbutler_error::error::Code;
@ -450,58 +446,23 @@ impl RepoActionsExt for CommandContext {
let author = repo
.author()
.transpose()?
.unwrap_or_else(|| default_actor_with_commit_time("GIT_AUTHOR_DATE"));
.map(gitbutler_branch::gix_to_git2_signature)
.unwrap_or_else(|| gitbutler_branch::signature(SignaturePurpose::Author))?;
let config: Config = self.repository().into();
let committer = if config.user_real_comitter()? {
repo.committer()
.transpose()?
.unwrap_or_else(|| default_actor_with_commit_time("GIT_COMMITTER_DATE"))
.map(gix_to_git2_signature)
.unwrap_or_else(|| gitbutler_branch::signature(SignaturePurpose::Committer))
} else {
default_actor_with_commit_time("GIT_COMMITTER_DATE")
};
gitbutler_branch::signature(SignaturePurpose::Committer)
}?;
Ok((
actor_to_git2_signature(author)?,
actor_to_git2_signature(committer)?,
))
Ok((author, committer))
}
}
fn default_actor_with_commit_time(
overriding_variable_name: &str,
) -> gix::actor::SignatureRef<'static> {
gix::actor::SignatureRef {
name: GITBUTLER_INTEGRATION_COMMIT_AUTHOR_NAME.into(),
email: GITBUTLER_INTEGRATION_COMMIT_AUTHOR_EMAIL.into(),
time: std::env::var(overriding_variable_name)
.ok()
.and_then(|time| gix::date::parse(&time, Some(std::time::SystemTime::now())).ok())
.unwrap_or_else(|| gix::date::Time::now_local_or_utc()),
}
}
/// Convert `actor` to a `git2` representation or fail if that's not possible.
/// Note that the current time as provided by `gix` is also use as it
/// respects the `GIT_AUTHOR|COMMITTER_DATE` environment variables.
fn actor_to_git2_signature(
actor: gix::actor::SignatureRef<'_>,
) -> Result<git2::Signature<'static>> {
let offset_in_minutes = actor.time.offset / 60;
let time = git2::Time::new(actor.time.seconds, offset_in_minutes);
Ok(git2::Signature::new(
actor
.name
.to_str()
.with_context(|| format!("Could not process actor name: {}", actor.name))?,
actor
.email
.to_str()
.with_context(|| format!("Could not process actor email: {}", actor.email))?,
&time,
)?)
}
type OidFilter = dyn Fn(&git2::Commit) -> Result<bool>;
pub enum LogUntil {