From 20910252270b4b8813971b5424e08632f746912d Mon Sep 17 00:00:00 2001 From: estib Date: Tue, 24 Sep 2024 17:16:26 +0200 Subject: [PATCH] Resolve upstream integration Add a method to determine which should be the new base branch target commit ID based on what resolution approach is selected --- .../components/IntegrateUpstreamModal.svelte | 49 +++++++++++++++++-- .../vbranches/upstreamIntegrationService.ts | 11 ++++- .../gitbutler-branch-actions/src/actions.rs | 21 +++++++- crates/gitbutler-branch-actions/src/lib.rs | 8 +-- .../src/upstream_integration.rs | 41 ++++++++++++++++ crates/gitbutler-tauri/src/main.rs | 1 + .../gitbutler-tauri/src/virtual_branches.rs | 19 ++++++- 7 files changed, 137 insertions(+), 13 deletions(-) diff --git a/apps/desktop/src/lib/components/IntegrateUpstreamModal.svelte b/apps/desktop/src/lib/components/IntegrateUpstreamModal.svelte index 0a1d82ff3..6de80eb23 100644 --- a/apps/desktop/src/lib/components/IntegrateUpstreamModal.svelte +++ b/apps/desktop/src/lib/components/IntegrateUpstreamModal.svelte @@ -10,6 +10,7 @@ getResolutionApproach, sortStatusInfo, UpstreamIntegrationService, + type BaseBranchResolutionApproach, type BranchStatusesWithBranches, type BranchStatusInfo, type Resolution @@ -39,6 +40,8 @@ let results = $state(new SvelteMap()); let statuses = $state([]); let expanded = $state(false); + let baseResolutionApproach = $state('hardReset'); + let targetId = $state(undefined); $effect(() => { if ($branchStatuses?.type !== 'updatesRequired') { @@ -68,6 +71,17 @@ statuses = statusesTmp; }); + $effect(() => { + if (targetId) { + branchStatuses = upstreamIntegrationService.upstreamStatuses(targetId); + } + }); + + async function handleBaseResolutionSelection(resolution: BaseBranchResolutionApproach) { + baseResolutionApproach = resolution; + targetId = await upstreamIntegrationService.resolveUpstreamIntegration(resolution); + } + async function integrate() { integratingUpstream = 'loading'; await tick(); @@ -144,6 +158,34 @@ {/if} {/if} + + {#if $base?.diverged} +
+
+
{$base.branchName ?? 'Unknown'}
+

Diverged

+
+ +
+ +
+
+ {/if} + {#if statuses.length > 0}
{#each statuses as { branch, status }} @@ -168,11 +210,8 @@ onselect={(value) => { const result = results.get(branch.id)!; - results.set(branch.id, { - ...result, - approach: { type: value as 'rebase' | 'merge' | 'unapply' } - }); - }} + results.set(branch.id, {...result, approach: { type: value }}) + }} options={[ { label: 'Rebase', value: 'rebase' }, { label: 'Merge', value: 'merge' }, diff --git a/apps/desktop/src/lib/vbranches/upstreamIntegrationService.ts b/apps/desktop/src/lib/vbranches/upstreamIntegrationService.ts index 2f8ab47e4..79b50e278 100644 --- a/apps/desktop/src/lib/vbranches/upstreamIntegrationService.ts +++ b/apps/desktop/src/lib/vbranches/upstreamIntegrationService.ts @@ -37,6 +37,8 @@ export type Resolution = { approach: ResolutionApproach; }; +export type BaseBranchResolutionApproach = 'rebase' | 'merge' | 'hardReset'; + export function getResolutionApproach(statusInfo: BranchStatusInfo): ResolutionApproach { if (statusInfo.status.type === 'fullyIntegrated') { return { type: 'delete' }; @@ -79,7 +81,7 @@ export class UpstreamIntegrationService { private virtualBranchService: VirtualBranchService ) {} - upstreamStatuses(): Readable { + upstreamStatuses(_targetId?: string): Readable { const branchStatuses = readable(undefined, (set) => { invoke('upstream_integration_statuses', { projectId: this.project.id @@ -116,4 +118,11 @@ export class UpstreamIntegrationService { async integrateUpstream(resolutions: Resolution[]) { return await invoke('integrate_upstream', { projectId: this.project.id, resolutions }); } + + async resolveUpstreamIntegration(type: BaseBranchResolutionApproach) { + return await invoke('resolve_upstream_integration', { + projectId: this.project.id, + resolutionApproach: { type } + }); + } } diff --git a/crates/gitbutler-branch-actions/src/actions.rs b/crates/gitbutler-branch-actions/src/actions.rs index 568aba5c0..f2bcf243e 100644 --- a/crates/gitbutler-branch-actions/src/actions.rs +++ b/crates/gitbutler-branch-actions/src/actions.rs @@ -1,5 +1,9 @@ use super::r#virtual as vbranch; -use crate::upstream_integration::{self, BranchStatuses, Resolution, UpstreamIntegrationContext}; +use crate::move_commits; +use crate::reorder_commits; +use crate::upstream_integration::{ + self, BaseBranchResolutionApproach, BranchStatuses, Resolution, UpstreamIntegrationContext, +}; use crate::{ base, base::BaseBranch, @@ -9,7 +13,6 @@ use crate::{ remote::{RemoteBranch, RemoteBranchData, RemoteCommit}, VirtualBranchesExt, }; -use crate::{move_commits, reorder_commits}; use anyhow::{Context, Result}; use gitbutler_branch::{BranchCreateRequest, BranchId, BranchOwnershipClaims, BranchUpdateRequest}; use gitbutler_command_context::CommandContext; @@ -544,6 +547,20 @@ pub fn integrate_upstream(project: &Project, resolutions: &[Resolution]) -> Resu ) } +pub fn resolve_upstream_integration( + project: &Project, + resolution_approach: BaseBranchResolutionApproach, +) -> Result { + let command_context = CommandContext::open(project)?; + let mut guard = project.exclusive_worktree_access(); + + upstream_integration::resolve_upstream_integration( + &command_context, + resolution_approach, + guard.write_permission(), + ) +} + pub(crate) fn open_with_verify(project: &Project) -> Result { let ctx = CommandContext::open(project)?; let mut guard = project.exclusive_worktree_access(); diff --git a/crates/gitbutler-branch-actions/src/lib.rs b/crates/gitbutler-branch-actions/src/lib.rs index 417bf1a92..49b8e3cd6 100644 --- a/crates/gitbutler-branch-actions/src/lib.rs +++ b/crates/gitbutler-branch-actions/src/lib.rs @@ -9,10 +9,10 @@ pub use actions::{ integrate_upstream_commits, list_local_branches, list_remote_commit_files, list_virtual_branches, list_virtual_branches_cached, move_commit, move_commit_file, push_base_branch, push_virtual_branch, reorder_commit, reset_files, reset_virtual_branch, - save_and_unapply_virutal_branch, set_base_branch, set_target_push_remote, squash, - unapply_ownership, unapply_without_saving_virtual_branch, undo_commit, update_base_branch, - update_branch_order, update_commit_message, update_virtual_branch, - upstream_integration_statuses, + resolve_upstream_integration, save_and_unapply_virutal_branch, set_base_branch, + set_target_push_remote, squash, unapply_ownership, unapply_without_saving_virtual_branch, + undo_commit, update_base_branch, update_branch_order, update_commit_message, + update_virtual_branch, upstream_integration_statuses, }; mod r#virtual; diff --git a/crates/gitbutler-branch-actions/src/upstream_integration.rs b/crates/gitbutler-branch-actions/src/upstream_integration.rs index 1714cbe81..6055acd20 100644 --- a/crates/gitbutler-branch-actions/src/upstream_integration.rs +++ b/crates/gitbutler-branch-actions/src/upstream_integration.rs @@ -32,6 +32,14 @@ pub enum BranchStatuses { UpdatesRequired(Vec<(BranchId, BranchStatus)>), } +#[derive(Serialize, Deserialize, PartialEq, Debug)] +#[serde(tag = "type", content = "subject", rename_all = "camelCase")] +pub enum BaseBranchResolutionApproach { + Rebase, + Merge, + HardReset, +} + #[derive(Serialize, Deserialize, PartialEq, Debug)] #[serde(tag = "type", content = "subject", rename_all = "camelCase")] enum ResolutionApproach { @@ -313,6 +321,39 @@ pub(crate) fn integrate_upstream( Ok(()) } +pub(crate) fn resolve_upstream_integration( + command_context: &CommandContext, + resolution_approach: BaseBranchResolutionApproach, + permission: &mut WorktreeWritePermission, +) -> Result { + let context = UpstreamIntegrationContext::open(command_context, permission)?; + let repo = command_context.repository(); + let new_target_id = context.new_target.id(); + let old_target_id = context.old_target.id(); + let fork_point = repo.merge_base(old_target_id, new_target_id)?; + + match resolution_approach { + BaseBranchResolutionApproach::HardReset => Ok(new_target_id), + BaseBranchResolutionApproach::Merge => { + let new_head = gitbutler_merge_commits( + repo, + context.old_target, + context.new_target, + &context.target_branch_name, + &context.target_branch_name, + )?; + + Ok(new_head.id()) + } + BaseBranchResolutionApproach::Rebase => { + let commits = repo.l(old_target_id, LogUntil::Commit(fork_point))?; + let new_head = cherry_rebase_group(repo, new_target_id, &commits, true)?; + + Ok(new_head) + } + } +} + fn compute_resolutions( context: &UpstreamIntegrationContext, resolutions: &[Resolution], diff --git a/crates/gitbutler-tauri/src/main.rs b/crates/gitbutler-tauri/src/main.rs index 3de1b2d2d..9dbccb79b 100644 --- a/crates/gitbutler-tauri/src/main.rs +++ b/crates/gitbutler-tauri/src/main.rs @@ -188,6 +188,7 @@ fn main() { virtual_branches::commands::normalize_branch_name, virtual_branches::commands::upstream_integration_statuses, virtual_branches::commands::integrate_upstream, + virtual_branches::commands::resolve_upstream_integration, virtual_branches::commands::find_commit, stack::create_series, stack::remove_series, diff --git a/crates/gitbutler-tauri/src/virtual_branches.rs b/crates/gitbutler-tauri/src/virtual_branches.rs index 928f553f3..5a5a2a950 100644 --- a/crates/gitbutler-tauri/src/virtual_branches.rs +++ b/crates/gitbutler-tauri/src/virtual_branches.rs @@ -4,7 +4,9 @@ pub mod commands { BranchCreateRequest, BranchId, BranchOwnershipClaims, BranchUpdateRequest, }; use gitbutler_branch_actions::internal::PushResult; - use gitbutler_branch_actions::upstream_integration::{BranchStatuses, Resolution}; + use gitbutler_branch_actions::upstream_integration::{ + BaseBranchResolutionApproach, BranchStatuses, Resolution, + }; use gitbutler_branch_actions::{ BaseBranch, BranchListing, BranchListingDetails, BranchListingFilter, RemoteBranch, RemoteBranchData, RemoteBranchFile, RemoteCommit, VirtualBranches, @@ -597,6 +599,21 @@ pub mod commands { Ok(()) } + #[tauri::command(async)] + #[instrument(skip(projects), err(Debug))] + pub fn resolve_upstream_integration( + projects: State<'_, projects::Controller>, + project_id: ProjectId, + resolution_approach: BaseBranchResolutionApproach, + ) -> Result { + let project = projects.get(project_id)?; + + let new_target_id = + gitbutler_branch_actions::resolve_upstream_integration(&project, resolution_approach)?; + let commit_id = git2::Oid::to_string(&new_target_id); + Ok(commit_id) + } + pub(crate) fn emit_vbranches(windows: &WindowState, project_id: projects::ProjectId) { if let Err(error) = windows.post(gitbutler_watcher::Action::CalculateVirtualBranches( project_id,