Skip to content
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

Add run hooks tauri command #5896

Closed
wants to merge 4 commits into from
Closed
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
3 changes: 3 additions & 0 deletions Cargo.lock

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

29 changes: 28 additions & 1 deletion apps/desktop/src/lib/commit/CommitDialog.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import { persistedCommitMessage, projectRunCommitHooks } from '$lib/config/config';
import { cloudCommunicationFunctionality } from '$lib/config/uiFeatureFlags';
import { SyncedSnapshotService } from '$lib/history/syncedSnapshotService';
import { showError } from '$lib/notifications/toasts';
import DropDownButton from '$lib/shared/DropDownButton.svelte';
import { intersectionObserver } from '$lib/utils/intersectionObserver';
import { BranchController } from '$lib/vbranches/branchController';
Expand Down Expand Up @@ -35,6 +36,7 @@

let commitMessageInput = $state<CommitMessageInput>();
let isCommitting = $state(false);
let isRunningHooks = $state(false);
let commitMessageValid = $state(false);
let isInViewport = $state(false);

Expand All @@ -44,7 +46,6 @@
try {
await branchController.commitBranch(
$stack.id,
$stack.name,
message.trim(),
$selectedOwnership.toString(),
$runCommitHooks
Expand All @@ -59,6 +60,17 @@
}
}

async function runHooks() {
isRunningHooks = true;
try {
await branchController.runHooks(projectId, $selectedOwnership.toString());
} catch (err: unknown) {
showError('Failed to run hooks', err);
} finally {
isRunningHooks = false;
}
}

function close() {
$expanded = false;
}
Expand Down Expand Up @@ -106,6 +118,20 @@
{#if $expanded && !isCommitting}
<div class="cancel-btn-wrapper" transition:slideFade={{ duration: 200, axis: 'x' }}>
<Button style="ghost" outline id="commit-to-branch" onclick={close}>Cancel</Button>
{#if $expanded}
<Button
onclick={() => {
runHooks();
}}
style="pop"
loading={isRunningHooks}
kind="solid"
disabled={isRunningHooks || $selectedOwnership.nothingSelected()}
id="run-hooks"
>
Run hooks
</Button>
{/if}
</div>
{/if}
{#if $expanded && canShowCommitAndPublish}
Expand Down Expand Up @@ -197,6 +223,7 @@
.cancel-btn-wrapper {
overflow: hidden;
margin-right: 6px;
white-space: nowrap;
}

/* MODIFIERS */
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/lib/shared/InfoMessage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@
color: var(--clr-scale-err-10);
border-radius: var(--radius-s);
font-size: 12px;
white-space: pre;

/* scrollbar */
&::-webkit-scrollbar {
Expand Down
10 changes: 8 additions & 2 deletions apps/desktop/src/lib/vbranches/branchController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,16 @@ export class BranchController {
}
}

async runHooks(stackId: string, ownership: string) {
await invoke<void>('run_hooks', {
projectId: this.projectId,
stackId,
ownership
});
}

async commitBranch(
branchId: string,
branchName: string,
message: string,
ownership: string | undefined = undefined,
runHooks = false
Expand All @@ -73,7 +80,6 @@ export class BranchController {
showSignError(err);
} else {
showError('Failed to commit changes', err);
throw err;
}
this.posthog.capture('Commit Failed', err);
}
Expand Down
7 changes: 7 additions & 0 deletions crates/gitbutler-branch-actions/src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use gitbutler_oplog::{
};
use gitbutler_project::{FetchResult, Project};
use gitbutler_reference::{ReferenceName, Refname, RemoteRefname};
use gitbutler_repo::staging;
use gitbutler_repo::RepositoryExt;
use gitbutler_repo_actions::RepoActionsExt;
use gitbutler_stack::{BranchOwnershipClaims, StackId};
Expand All @@ -47,6 +48,12 @@ pub fn create_commit(
let mut guard = project.exclusive_worktree_access();
let snapshot_tree = ctx.project().prepare_snapshot(guard.read_permission());
let result = vbranch::commit(&ctx, stack_id, message, ownership, run_hooks).map_err(Into::into);

if run_hooks && result.is_err() {
// If commit hooks fail then files will still be staged.
staging::unstage_all(&ctx)?
}

let _ = snapshot_tree.and_then(|snapshot_tree| {
ctx.project().snapshot_commit_creation(
snapshot_tree,
Expand Down
3 changes: 2 additions & 1 deletion crates/gitbutler-branch-actions/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ mod gravatar;
mod status;
use gitbutler_stack::VirtualBranchesHandle;
pub use status::get_applied_status;
trait VirtualBranchesExt {
pub trait VirtualBranchesExt {
fn virtual_branches(&self) -> VirtualBranchesHandle;
}

Expand All @@ -79,4 +79,5 @@ pub use branch::{

pub use integration::GITBUTLER_WORKSPACE_COMMIT_TITLE;

pub mod ownership;
pub mod stack;
36 changes: 36 additions & 0 deletions crates/gitbutler-branch-actions/src/ownership.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use std::path::PathBuf;

use anyhow::{anyhow, Result};
use gitbutler_diff::{DiffByPathMap, GitHunk};
use gitbutler_stack::BranchOwnershipClaims;
use itertools::Itertools;

pub fn filter_hunks_by_ownership(
diffs: &DiffByPathMap,
ownership: &BranchOwnershipClaims,
) -> Result<Vec<(PathBuf, Vec<GitHunk>)>> {
ownership
.claims
.iter()
.map(|claim| {
if let Some(diff) = diffs.get(&claim.file_path) {
let hunks = claim
.hunks
.iter()
.filter_map(|claimed_hunk| {
diff.hunks
.iter()
.find(|diff_hunk| {
claimed_hunk.start == diff_hunk.new_start
&& claimed_hunk.end == diff_hunk.new_start + diff_hunk.new_lines
})
.cloned()
})
.collect_vec();
Ok((claim.file_path.clone(), hunks))
} else {
Err(anyhow!("Claim not found in workspace diff"))
}
})
.collect::<Result<Vec<(_, Vec<_>)>>>()
}
124 changes: 38 additions & 86 deletions crates/gitbutler-branch-actions/src/virtual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ use crate::{
file::VirtualBranchFile,
hunk::VirtualBranchHunk,
integration::get_workspace_head,
ownership::filter_hunks_by_ownership,
remote::branch_to_remote_branch,
stack::stack_series,
status::{get_applied_status, get_applied_status_cached},
Get, RemoteBranchData, VirtualBranchHunkRange, VirtualBranchHunkRangeMap, VirtualBranchesExt,
};
use anyhow::{anyhow, bail, Context, Result};
use bstr::{BString, ByteSlice};
use git2_hooks::HookResult;
use gitbutler_branch::BranchUpdateRequest;
use gitbutler_branch::{dedup, dedup_fmt};
use gitbutler_cherry_pick::RepositoryExt as _;
Expand All @@ -28,9 +28,10 @@ use gitbutler_oxidize::{
use gitbutler_project::access::WorktreeWritePermission;
use gitbutler_reference::{normalize_branch_name, Refname, RemoteRefname};
use gitbutler_repo::{
hooks,
logging::{LogUntil, RepositoryExt as _},
rebase::{cherry_rebase, cherry_rebase_group},
RepositoryExt,
staging, RepositoryExt,
};
use gitbutler_repo_actions::RepoActionsExt;
use gitbutler_stack::{
Expand All @@ -40,7 +41,6 @@ use gitbutler_stack::{
use gitbutler_time::time::now_since_unix_epoch_ms;
use itertools::Itertools;
use serde::Serialize;
use std::borrow::Cow;
use std::{collections::HashMap, path::PathBuf, vec};
use tracing::instrument;

Expand Down Expand Up @@ -732,98 +732,47 @@ pub fn commit(
ownership: Option<&BranchOwnershipClaims>,
run_hooks: bool,
) -> Result<git2::Oid> {
let mut message_buffer = message.to_owned();
let diffs = gitbutler_diff::workdir(ctx.repo(), get_workspace_head(ctx)?)?;

fn join_output<'a>(stdout: &'a str, stderr: &'a str) -> Cow<'a, str> {
let stdout = stdout.trim();
if stdout.is_empty() {
stderr.trim().into()
} else {
stdout.into()
}
if ownership.is_none() {
// If we are committing everything owned by a branch then we
// need to make sure we have first updated ownerships.
get_applied_status_cached(ctx, None, &diffs)?;
}

if run_hooks {
let hook_result =
git2_hooks::hooks_commit_msg(ctx.repo(), Some(&["../.husky"]), &mut message_buffer)
.context("failed to run hook")
.context(Code::CommitHookFailed)?;

if let HookResult::RunNotSuccessful { stdout, stderr, .. } = &hook_result {
return Err(
anyhow!("commit-msg hook rejected: {}", join_output(stdout, stderr))
.context(Code::CommitHookFailed),
);
}
let mut stack = ctx.project().virtual_branches().get_stack(stack_id)?;
let ownership = ownership
.map::<Result<&BranchOwnershipClaims>, _>(Ok)
.unwrap_or_else(|| Ok(&stack.ownership))?;

let hook_result = git2_hooks::hooks_pre_commit(ctx.repo(), Some(&["../.husky"]))
.context("failed to run hook")
.context(Code::CommitHookFailed)?;
let selected_files = filter_hunks_by_ownership(&diffs, ownership)?;

if let HookResult::RunNotSuccessful { stdout, stderr, .. } = &hook_result {
return Err(
anyhow!("commit hook rejected: {}", join_output(stdout, stderr))
.context(Code::CommitHookFailed),
);
let final_message = if run_hooks {
hooks::message(ctx, message.to_owned())?
} else {
message.to_owned()
};

if run_hooks {
staging::stage_files(ctx, &selected_files)?;
let result = hooks::pre_commit(ctx);
if result.is_err() {
staging::unstage_all(ctx)?;
result?;
}
}

let message = &message_buffer;

// get the files to commit
let diffs = gitbutler_diff::workdir(ctx.repo(), get_workspace_head(ctx)?)?;
let statuses = get_applied_status_cached(ctx, None, &diffs)
.context("failed to get status by branch")?
.branches;

let (ref mut branch, files) = statuses
.into_iter()
.find(|(stack, _)| stack.id == stack_id)
.with_context(|| format!("stack {stack_id} not found"))?;

update_conflict_markers(ctx, &diffs).context(Code::CommitMergeConflictFailure)?;

ctx.assure_unconflicted()
.context(Code::CommitMergeConflictFailure)?;

let tree_oid = if let Some(ownership) = ownership {
let files = files.into_iter().filter_map(|file| {
let hunks = file
.hunks
.into_iter()
.filter(|hunk| {
let hunk: GitHunk = hunk.clone().into();
ownership
.claims
.iter()
.find(|f| f.file_path.eq(&file.path))
.map_or(false, |f| {
f.hunks.iter().any(|h| {
h.start == hunk.new_start
&& h.end == hunk.new_start + hunk.new_lines
})
})
})
.collect::<Vec<_>>();
if hunks.is_empty() {
None
} else {
Some((file.path, hunks))
}
});
gitbutler_diff::write::hunks_onto_commit(ctx, branch.head(), files)?
} else {
let files = files
.into_iter()
.map(|file| (file.path, file.hunks))
.collect::<Vec<(PathBuf, Vec<VirtualBranchHunk>)>>();
gitbutler_diff::write::hunks_onto_commit(ctx, branch.head(), files)?
};

let git_repository = ctx.repo();
let parent_commit = git_repository
.find_commit(branch.head())
.context(format!("failed to find commit {:?}", branch.head()))?;
.find_commit(stack.head())
.context(format!("failed to find commit {:?}", stack.head()))?;

let tree_oid = gitbutler_diff::write::hunks_onto_commit(ctx, stack.head(), selected_files)?;
let tree = git_repository
.find_tree(tree_oid)
.context(format!("failed to find tree {:?}", tree_oid))?;
Expand All @@ -838,23 +787,26 @@ pub fn commit(
let merge_parent = git_repository
.find_commit(merge_parent)
.context(format!("failed to find merge parent {:?}", merge_parent))?;
let commit_oid = ctx.commit(message, &tree, &[&parent_commit, &merge_parent], None)?;
let commit_oid = ctx.commit(
&final_message,
&tree,
&[&parent_commit, &merge_parent],
None,
)?;
conflicts::clear(ctx)
.context("failed to clear conflicts")
.context(Code::CommitMergeConflictFailure)?;
commit_oid
}
None => ctx.commit(message, &tree, &[&parent_commit], None)?,
None => ctx.commit(&final_message, &tree, &[&parent_commit], None)?,
};

if run_hooks {
git2_hooks::hooks_post_commit(ctx.repo(), Some(&["../.husky"]))
.context("failed to run hook")
.context(Code::CommitHookFailed)?;
hooks::post_commit(ctx)?;
}

let vb_state = ctx.project().virtual_branches();
branch.set_stack_head(ctx, commit_oid, Some(tree_oid))?;
stack.set_stack_head(ctx, commit_oid, Some(tree_oid))?;

crate::integration::update_workspace_commit(&vb_state, ctx)
.context("failed to update gitbutler workspace")?;
Expand Down
Loading
Loading