From 3a2cf73bcd792fa3f4abd84fda46c0274eb57564 Mon Sep 17 00:00:00 2001 From: estib Date: Thu, 12 Sep 2024 16:30:44 +0200 Subject: [PATCH 01/12] BaseBranch: Determine whether the base diverged Determine whether the local target has diverged from the remote, and return some information about that --- apps/desktop/src/lib/baseBranch/baseBranch.ts | 3 ++ crates/gitbutler-branch-actions/src/base.rs | 36 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/apps/desktop/src/lib/baseBranch/baseBranch.ts b/apps/desktop/src/lib/baseBranch/baseBranch.ts index cf54c6c7f..f66ec5203 100644 --- a/apps/desktop/src/lib/baseBranch/baseBranch.ts +++ b/apps/desktop/src/lib/baseBranch/baseBranch.ts @@ -17,6 +17,9 @@ export class BaseBranch { @Type(() => Commit) recentCommits!: Commit[]; lastFetchedMs?: number; + diverged!: boolean; + divergedAhead!: string[]; + divergedBehind!: string[]; actualPushRemoteName(): string { return this.pushRemoteName || this.remoteName; diff --git a/crates/gitbutler-branch-actions/src/base.rs b/crates/gitbutler-branch-actions/src/base.rs index 18d5c034b..262ccc3b1 100644 --- a/crates/gitbutler-branch-actions/src/base.rs +++ b/crates/gitbutler-branch-actions/src/base.rs @@ -39,6 +39,11 @@ pub struct BaseBranch { pub upstream_commits: Vec, pub recent_commits: Vec, pub last_fetched_ms: Option, + pub diverged: bool, + #[serde(with = "gitbutler_serde::oid_vec")] + pub diverged_ahead: Vec, + #[serde(with = "gitbutler_serde::oid_vec")] + pub diverged_behind: Vec, } pub(crate) fn get_base_branch_data(ctx: &CommandContext) -> Result { @@ -558,6 +563,34 @@ pub(crate) fn target_to_base_branch(ctx: &CommandContext, target: &Target) -> Re let commit = branch.get().peel_to_commit()?; let oid = commit.id(); + // determined if the base branch is behind it's upstream + let fork_point = repo.merge_base(target.sha, oid).context(format!( + "failed to find merge base between {} and {}", + target.sha, oid + ))?; + + let diverged = fork_point != target.sha; + + let diverged_ahead = if diverged { + repo.log(target.sha, LogUntil::Commit(fork_point)) + .context("failed to get diverged ahead commits")? + .iter() + .map(|commit| commit.id()) + .collect::>() + } else { + Vec::new() + }; + + let diverged_behind = if diverged { + repo.log(oid, LogUntil::Commit(fork_point)) + .context("failed to get diverged behind commits")? + .iter() + .map(|commit| commit.id()) + .collect::>() + } else { + Vec::new() + }; + // gather a list of commits between oid and target.sha let upstream_commits = repo .log(oid, LogUntil::Commit(target.sha)) @@ -604,6 +637,9 @@ pub(crate) fn target_to_base_branch(ctx: &CommandContext, target: &Target) -> Re .map(FetchResult::timestamp) .copied() .map(|t| t.duration_since(time::UNIX_EPOCH).unwrap().as_millis()), + diverged, + diverged_ahead, + diverged_behind, }; Ok(base) } From 19acb8f22c09587923eca69e02fa08f4dbd0e06f Mon Sep 17 00:00:00 2001 From: estib Date: Fri, 13 Sep 2024 12:32:03 +0200 Subject: [PATCH 02/12] Array utils: group items by condition --- apps/desktop/src/lib/utils/array.ts | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/lib/utils/array.ts b/apps/desktop/src/lib/utils/array.ts index b6107df6f..70b6b28c3 100644 --- a/apps/desktop/src/lib/utils/array.ts +++ b/apps/desktop/src/lib/utils/array.ts @@ -1,6 +1,8 @@ +type Predicate = (item: T) => boolean; + type ItemsSatisfyResult = 'all' | 'some' | 'none'; -export function itemsSatisfy(arr: T[], predicate: (item: T) => boolean): ItemsSatisfyResult { +export function itemsSatisfy(arr: T[], predicate: Predicate): ItemsSatisfyResult { let satisfyCount = 0; let offenseCount = 0; for (const item of arr) { @@ -29,6 +31,23 @@ export function chunk(arr: T[], size: number) { ); } -export function unique(arr: T[]): T[] { - return Array.from(new Set(arr)); +interface GroupByResult { + satisfied: T[]; + rest: T[]; +} + +export function groupByCondition(arr: T[], predicate: Predicate): GroupByResult { + const satisfied: T[] = []; + const rest: T[] = []; + + for (const item of arr) { + if (predicate(item)) { + satisfied.push(item); + continue; + } + + rest.push(item); + } + + return { satisfied, rest }; } From 2be4aaac2d858a64d046803c10a54a5bfa61fedc Mon Sep 17 00:00:00 2001 From: estib Date: Fri, 13 Sep 2024 14:16:58 +0200 Subject: [PATCH 03/12] Display the divergent state in the app If the local target has diverged from the remote target, display that to the user as a warning Only show either the divergence warning or upstream count BaseBranch: Use runes BaseBranch: Display the branch graph In order to make it a bit clearer what the divergence state of the base branch is. reuse the line graphs to display it BaseBranch divergence: Add a confirmation modal --- .../src/lib/commit/CommitAction.svelte | 9 +- apps/desktop/src/lib/commit/CommitList.svelte | 10 +- .../src/lib/components/BaseBranch.svelte | 269 ++++++++++++++++-- .../lib/components/UpdateBaseButton.svelte | 3 +- .../src/lib/navigation/TargetCard.svelte | 10 +- .../ui/src/lib/commitLines/lineManager.ts | 6 + 6 files changed, 266 insertions(+), 41 deletions(-) diff --git a/apps/desktop/src/lib/commit/CommitAction.svelte b/apps/desktop/src/lib/commit/CommitAction.svelte index 74a49ea73..fdc900b17 100644 --- a/apps/desktop/src/lib/commit/CommitAction.svelte +++ b/apps/desktop/src/lib/commit/CommitAction.svelte @@ -4,11 +4,12 @@ interface Props { bottomBorder?: boolean; + backgroundColor?: boolean; lines: Snippet; action: Snippet; } - const { bottomBorder = true, lines, action }: Props = $props(); + const { bottomBorder = true, backgroundColor = true, lines, action }: Props = $props(); let isNotInViewport = $state(false); @@ -18,6 +19,7 @@ class:not-in-viewport={!isNotInViewport} class:sticky-z-index={!isNotInViewport} class:bottom-border={bottomBorder} + class:background-color={backgroundColor} use:intersectionObserver={{ callback: (entry) => { if (entry?.isIntersecting) { @@ -46,12 +48,15 @@ position: relative; display: flex; - background-color: var(--clr-bg-2); overflow: hidden; transition: border-top var(--transition-fast); } + .background-color { + background-color: var(--clr-bg-2); + } + .action { display: flex; flex-direction: column; diff --git a/apps/desktop/src/lib/commit/CommitList.svelte b/apps/desktop/src/lib/commit/CommitList.svelte index deccdd4b8..6db6deafd 100644 --- a/apps/desktop/src/lib/commit/CommitList.svelte +++ b/apps/desktop/src/lib/commit/CommitList.svelte @@ -18,7 +18,7 @@ import { Commit, DetailedCommit, VirtualBranch } from '$lib/vbranches/types'; import Button from '@gitbutler/ui/Button.svelte'; import LineGroup from '@gitbutler/ui/commitLines/LineGroup.svelte'; - import { LineManagerFactory } from '@gitbutler/ui/commitLines/lineManager'; + import { LineManagerFactory, LineSpacer } from '@gitbutler/ui/commitLines/lineManager'; import type { Snippet } from 'svelte'; import { goto } from '$app/navigation'; @@ -52,14 +52,6 @@ const reorderDropzoneManagerFactory = getContext(ReorderDropzoneManagerFactory); const gitHost = getGitHost(); - // TODO: Why does eslint-svelte-plugin complain about enum? - // eslint-disable-next-line svelte/valid-compile - enum LineSpacer { - Remote = 'remote-spacer', - Local = 'local-spacer', - LocalAndRemote = 'local-and-remote-spacer' - } - const mappedRemoteCommits = $derived( remoteCommits.length > 0 ? [...remoteCommits.map(transformAnyCommit), { id: LineSpacer.Remote }] diff --git a/apps/desktop/src/lib/components/BaseBranch.svelte b/apps/desktop/src/lib/components/BaseBranch.svelte index 1fe91d35d..f5a7bfb79 100644 --- a/apps/desktop/src/lib/components/BaseBranch.svelte +++ b/apps/desktop/src/lib/components/BaseBranch.svelte @@ -1,41 +1,111 @@ -
-
- There {multiple ? 'are' : 'is'} - {base.upstreamCommits.length} unmerged upstream - {multiple ? 'commits' : 'commit'} +{#if base.diverged} +
+ + + Your target branch has diverged from upstream. +
+ Target branch is + + {`ahead by ${base.divergedAhead.length}`} + + commits and + + {`behind by ${base.divergedBehind.length}`} + + commits +
+
+{/if} + +{#if !base.diverged && base.upstreamCommits.length > 0} +
+
+ There {multiple ? 'are' : 'is'} + {base.upstreamCommits.length} unmerged upstream + {multiple ? 'commits' : 'commit'} +
- {#if base.upstreamCommits?.length > 0} +
+{/if} + +
+ + {#if base.upstreamCommits?.length > 0}
{#each base.upstreamCommits as commit, index} + > + {#snippet lines(topHeightPx)} + + {/snippet} + {/each}
- + + {#if base.diverged} + + {#snippet lines()} + + {/snippet} + {#snippet action()} + + {/snippet} + + {/if} {/if} + + {#if commitsAhead.length > 0} +
+ {#each commitsAhead as commit, index} + + {#snippet lines(topHeightPx)} + + {/snippet} + + {/each} +
+ + + {#snippet lines()} + + {/snippet} + {#snippet action()} + + {/snippet} + + {/if} + +
- -

Local

-
- {#each base.recentCommits as commit, index} + {#each localAndRemoteCommits as commit, index} + > + {#snippet lines(topHeightPx)} + + {/snippet} + {/each}
@@ -146,12 +310,61 @@ {/snippet} +{#if resetActionType} + { + if (resetActionType) { + resetBranchTo[resetActionType].action(); + } + close(); + }} + > + + + {#snippet controls(close)} + {#if resetActionType} + + + {/if} + {/snippet} + +{/if} +