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
This commit is contained in:
estib 2024-09-24 17:16:26 +02:00
parent e4cd357b8a
commit 2091025227
7 changed files with 137 additions and 13 deletions

View File

@ -10,6 +10,7 @@
getResolutionApproach,
sortStatusInfo,
UpstreamIntegrationService,
type BaseBranchResolutionApproach,
type BranchStatusesWithBranches,
type BranchStatusInfo,
type Resolution
@ -39,6 +40,8 @@
let results = $state(new SvelteMap<string, Resolution>());
let statuses = $state<BranchStatusInfo[]>([]);
let expanded = $state<boolean>(false);
let baseResolutionApproach = $state<BaseBranchResolutionApproach>('hardReset');
let targetId = $state<string | undefined>(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}
</div>
{/if}
{#if $base?.diverged}
<div class="branch-status">
<div class="description">
<h5 class="text-16">{$base.branchName ?? 'Unknown'}</h5>
<p>Diverged</p>
</div>
<div class="action">
<Select
value={baseResolutionApproach}
onselect={handleBaseResolutionSelection}
options={[
{ label: 'Rebase', value: 'rebase' },
{ label: 'Merge', value: 'merge' },
{ label: 'Hard reset', value: 'hardReset' }
]}
>
{#snippet itemSnippet({ item, highlighted })}
<SelectItem selected={highlighted} {highlighted}>
{item.label}
</SelectItem>
{/snippet}
</Select>
</div>
</div>
{/if}
{#if statuses.length > 0}
<div class="statuses">
{#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' },

View File

@ -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<BranchStatusesWithBranches | undefined> {
upstreamStatuses(_targetId?: string): Readable<BranchStatusesWithBranches | undefined> {
const branchStatuses = readable<BranchStatusesResponse | undefined>(undefined, (set) => {
invoke<BranchStatusesResponse>('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<string>('resolve_upstream_integration', {
projectId: this.project.id,
resolutionApproach: { type }
});
}
}

View File

@ -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<git2::Oid> {
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<CommandContext> {
let ctx = CommandContext::open(project)?;
let mut guard = project.exclusive_worktree_access();

View File

@ -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;

View File

@ -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<git2::Oid> {
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],

View File

@ -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,

View File

@ -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<String, Error> {
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,