get rid of virtual / remote confusion

This commit is contained in:
Nikita Galaiko 2023-09-15 12:46:25 +02:00 committed by GitButler
parent 797942fedd
commit edbbe1f0a8
13 changed files with 189 additions and 116 deletions

View File

@ -366,6 +366,7 @@ impl App {
let diff = Self::diff_hunks_to_string(diff);
Ok(diff)
}
pub fn git_commit_diff(
&self,
project_id: &str,

View File

@ -1,12 +1,13 @@
use std::{collections::HashMap, path};
use anyhow::{Context, Result};
use serde::Serialize;
use crate::git;
use super::Repository;
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct Hunk {
pub old_start: usize,
pub old_lines: usize,

View File

@ -8,7 +8,7 @@ use futures::future::join_all;
use tauri::{generate_context, Manager};
use tracing::instrument;
use gitbutler::{virtual_branches::VirtualBranchCommit, *};
use gitbutler::*;
use crate::{error::Error, git, project_repository::activity};
@ -366,7 +366,7 @@ async fn git_remote_branches_data(
.map(|commit| {
let proxy = proxy.clone();
async move {
VirtualBranchCommit {
virtual_branches::RemoteCommit {
author: virtual_branches::Author {
gravatar_url: proxy
.proxy(&commit.author.gravatar_url)

View File

@ -451,7 +451,7 @@ pub fn target_to_base_branch(
.log(oid, project_repository::LogUntil::Commit(target.sha))
.context("failed to get upstream commits")?
.iter()
.map(|c| super::commit_to_vbranch_commit(project_repository, target, c, None))
.map(|commit| super::commit_to_remote_commit(&project_repository.git_repository, commit))
.collect::<Result<Vec<_>>>()?;
// get some recent commits
@ -459,7 +459,7 @@ pub fn target_to_base_branch(
.log(target.sha, LogUntil::Take(20))
.context("failed to get recent commits")?
.iter()
.map(|c| super::commit_to_vbranch_commit(project_repository, target, c, None))
.map(|commit| super::commit_to_remote_commit(&project_repository.git_repository, commit))
.collect::<Result<Vec<_>>>()?;
let base = super::BaseBranch {
@ -576,7 +576,8 @@ pub fn create_virtual_branch_from_branch(
// do a diff between the head of this branch and the target base
let diff = diff::trees(&project_repository.git_repository, &merge_tree, &tree)
.context("failed to diff trees")?;
let hunks_by_filepath = super::hunks_by_filepath(project_repository, &diff);
let hunks_by_filepath =
super::virtual_hunks_by_filepath(&project_repository.git_repository, &diff);
// assign ownership to the branch
for hunk in hunks_by_filepath.values().flatten() {

View File

@ -349,7 +349,7 @@ impl Controller {
.recent_commits
.into_iter()
.map(|commit| async move {
super::VirtualBranchCommit {
super::RemoteCommit {
author: super::Author {
gravatar_url: self
.assets_proxy
@ -373,7 +373,7 @@ impl Controller {
.upstream_commits
.into_iter()
.map(|commit| async move {
super::VirtualBranchCommit {
super::RemoteCommit {
author: super::Author {
gravatar_url: self
.assets_proxy

View File

@ -64,7 +64,6 @@ pub struct VirtualBranchCommit {
pub created_at: u128,
pub author: Author,
pub is_remote: bool,
// only present if is_remote is false
pub files: Vec<VirtualBranchFile>,
pub is_integrated: bool,
}
@ -88,6 +87,14 @@ pub struct VirtualBranchFile {
pub binary: bool,
}
#[derive(Debug, PartialEq, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RemoteBranchFile {
pub path: path::PathBuf,
pub hunks: Vec<git::diff::Hunk>,
pub binary: bool,
}
// this struct is a mapping to the view `Hunk` type in Typescript
// found in src-tauri/src/routes/repo/[project_id]/types.ts
// it holds a materialized view for presentation purposes of one entry of the
@ -110,6 +117,19 @@ pub struct VirtualBranchHunk {
pub locked: bool,
}
#[derive(Debug, PartialEq, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RemoteBranchHunk {
pub id: String,
pub diff: String,
pub modified_at: u128,
pub file_path: path::PathBuf,
pub hash: String,
pub start: usize,
pub end: usize,
pub binary: bool,
}
// this struct is a mapping to the view `RemoteBranch` type in Typescript
// found in src-tauri/src/routes/repo/[project_id]/types.ts
//
@ -127,7 +147,17 @@ pub struct RemoteBranch {
pub behind: u32,
pub upstream: Option<git::RemoteBranchName>,
pub mergeable: bool,
pub commits: Vec<VirtualBranchCommit>,
pub commits: Vec<RemoteCommit>,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RemoteCommit {
pub id: String,
pub description: String,
pub created_at: u128,
pub author: Author,
pub files: Vec<RemoteBranchFile>,
}
#[derive(Debug, Serialize, PartialEq, Clone)]
@ -139,8 +169,8 @@ pub struct BaseBranch {
pub base_sha: String,
pub current_sha: String,
pub behind: u32,
pub upstream_commits: Vec<VirtualBranchCommit>,
pub recent_commits: Vec<VirtualBranchCommit>,
pub upstream_commits: Vec<RemoteCommit>,
pub recent_commits: Vec<RemoteCommit>,
}
#[derive(Debug, Serialize, Hash, Clone, PartialEq, Eq)]
@ -572,74 +602,91 @@ pub fn list_remote_branches(
let mut branches: Vec<RemoteBranch> = Vec::new();
for branch in &top_branches {
let branch_name = branch.refname().context("could not get branch name")?;
match branch.target() {
Some(branch_oid) => {
// get the branch ref
let branch_commit = repo
.find_commit(branch_oid)
.context("failed to find branch commit")?;
if let Some(branch_oid) = branch.target() {
let ahead = project_repository
.log(branch_oid, LogUntil::Commit(main_oid))
.context("failed to get ahead commits")?;
let count_behind = project_repository
.distance(main_oid, branch_oid)
.context("failed to get behind count")?;
let ahead = project_repository
.log(branch_oid, LogUntil::Commit(main_oid))
.context("failed to get ahead commits")?;
let count_ahead = ahead.len();
let upstream = branch
.upstream()
.ok()
.map(|upstream_branch| git::RemoteBranchName::try_from(&upstream_branch))
.transpose()?;
if count_ahead > 0 {
if let Ok(base_tree) = find_base_tree(repo, &branch_commit, &target_commit) {
// determine if this tree is mergeable
let branch_tree = branch_commit.tree()?;
let mergeable = !repo
.merge_trees(&base_tree, &branch_tree, &wd_tree)?
.has_conflicts();
branches.push(RemoteBranch {
sha: branch_oid.to_string(),
name: branch_name.to_string(),
upstream,
behind: count_behind,
mergeable,
commits: ahead
.into_iter()
.map(|commit| {
commit_to_vbranch_commit(
project_repository,
&default_target,
&commit,
None,
)
})
.collect::<Result<Vec<_>>>()?,
});
};
}
}
None => {
// this is a detached head
branches.push(RemoteBranch {
sha: "".to_string(),
name: branch_name.to_string(),
behind: 0,
upstream: None,
mergeable: false,
commits: vec![],
});
if ahead.is_empty() {
continue;
}
let branch_name = branch.refname().context("could not get branch name")?;
// get the branch ref
let branch_commit = repo
.find_commit(branch_oid)
.context("failed to find branch commit")?;
let count_behind = project_repository
.distance(main_oid, branch_oid)
.context("failed to get behind count")?;
let upstream = branch
.upstream()
.ok()
.map(|upstream_branch| git::RemoteBranchName::try_from(&upstream_branch))
.transpose()?;
let base_tree = find_base_tree(repo, &branch_commit, &target_commit)?;
// determine if this tree is mergeable
let branch_tree = branch_commit.tree()?;
let mergeable = !repo
.merge_trees(&base_tree, &branch_tree, &wd_tree)?
.has_conflicts();
branches.push(RemoteBranch {
sha: branch_oid.to_string(),
name: branch_name.to_string(),
upstream,
behind: count_behind,
mergeable,
commits: ahead
.into_iter()
.map(|commit| commit_to_remote_commit(repo, &commit))
.collect::<Result<Vec<_>>>()?,
});
}
}
Ok(branches)
}
pub fn commit_to_remote_commit(
repository: &git::Repository,
commit: &git::Commit,
) -> Result<RemoteCommit> {
Ok(RemoteCommit {
id: commit.id().to_string(),
description: commit.message().unwrap_or_default().to_string(),
created_at: commit.time().seconds().try_into().unwrap(),
author: commit.author().into(),
files: list_remote_commit_files(repository, commit)?,
})
}
fn list_remote_commit_files(
repository: &git::Repository,
commit: &git::Commit,
) -> Result<Vec<RemoteBranchFile>> {
if commit.parent_count() == 0 {
return Ok(vec![]);
}
let parent = commit.parent(0).context("failed to get parent commit")?;
let commit_tree = commit.tree().context("failed to get commit tree")?;
let parent_tree = parent.tree().context("failed to get parent tree")?;
let diff = diff::trees(repository, &parent_tree, &commit_tree)?;
let files = diff
.into_iter()
.map(|(file_path, hunks)| RemoteBranchFile {
path: file_path.clone(),
hunks: hunks.clone(),
binary: hunks.iter().any(|h| h.binary),
})
.collect::<Vec<_>>();
Ok(files)
}
pub fn get_wd_tree(repo: &git::Repository) -> Result<git::Tree> {
let mut index = repo.index()?;
index.add_all(["*"], git2::IndexAddOption::DEFAULT, None)?;
@ -819,7 +866,8 @@ fn calculate_non_commited_files(
)
.context("failed to diff trees")?;
let non_commited_hunks_by_filepath = super::hunks_by_filepath(project_repository, &diff);
let non_commited_hunks_by_filepath =
super::virtual_hunks_by_filepath(&project_repository.git_repository, &diff);
let file_hunks = files
.iter()
@ -910,7 +958,7 @@ fn calculate_non_commited_files(
Ok(vfiles)
}
fn list_commit_files(
fn list_virtual_commit_files(
project_repository: &project_repository::Repository,
commit: &git::Commit,
) -> Result<Vec<VirtualBranchFile>> {
@ -925,8 +973,8 @@ fn list_commit_files(
&parent_tree,
&commit_tree,
)?;
let hunks_by_filepath = hunks_by_filepath(project_repository, &diff);
Ok(hunks_to_files(
let hunks_by_filepath = virtual_hunks_by_filepath(&project_repository.git_repository, &diff);
Ok(virtual_hunks_to_virtual_files(
project_repository,
&hunks_by_filepath
.values()
@ -952,11 +1000,8 @@ pub fn commit_to_vbranch_commit(
None => true,
};
let files = if is_remote {
vec![]
} else {
list_commit_files(repository, commit).context("failed to list commit files")?
};
let files =
list_virtual_commit_files(repository, commit).context("failed to list commit files")?;
let is_integrated = is_commit_integrated(repository, target, commit)?;
@ -1242,8 +1287,8 @@ fn diff_hash(diff: &str) -> String {
format!("{:x}", md5::compute(addition))
}
pub fn hunks_by_filepath(
project_repository: &project_repository::Repository,
pub fn virtual_hunks_by_filepath(
repository: &git::Repository,
diff: &HashMap<path::PathBuf, Vec<diff::Hunk>>,
) -> HashMap<path::PathBuf, Vec<VirtualBranchHunk>> {
let mut mtimes: HashMap<path::PathBuf, u128> = HashMap::new();
@ -1253,7 +1298,7 @@ pub fn hunks_by_filepath(
.iter()
.map(|hunk| VirtualBranchHunk {
id: format!("{}-{}", hunk.new_start, hunk.new_start + hunk.new_lines),
modified_at: get_mtime(&mut mtimes, &project_repository.path().join(file_path)),
modified_at: get_mtime(&mut mtimes, &repository.path().join(file_path)),
file_path: file_path.clone(),
diff: hunk.diff.clone(),
start: hunk.new_start,
@ -1369,7 +1414,8 @@ fn get_non_applied_status(
.collect::<HashMap<_, _>>()
})
.collect::<HashMap<_, _>>();
let hunks_by_filepath = hunks_by_filepath(project_repository, &diff);
let hunks_by_filepath =
virtual_hunks_by_filepath(&project_repository.git_repository, &diff);
let hunks_by_filepath = hunks_by_filepath
.values()
.flatten()
@ -1409,7 +1455,8 @@ fn get_applied_status(
)
.context("failed to diff")?;
let mut hunks_by_filepath = hunks_by_filepath(project_repository, &diff);
let mut hunks_by_filepath =
virtual_hunks_by_filepath(&project_repository.git_repository, &diff);
// sort by order, so that the default branch is first (left in the ui)
virtual_branches.sort_by(|a, b| a.order.cmp(&b.order));
@ -1584,7 +1631,7 @@ fn get_applied_status(
Ok(files_by_branch)
}
fn hunks_to_files(
fn virtual_hunks_to_virtual_files(
project_repository: &project_repository::Repository,
hunks: &[VirtualBranchHunk],
) -> Vec<VirtualBranchFile> {
@ -1619,7 +1666,7 @@ fn group_virtual_hunks(
hunks_by_branch
.iter()
.map(|(branch, hunks)| {
let mut files = hunks_to_files(project_repository, hunks);
let mut files = virtual_hunks_to_virtual_files(project_repository, hunks);
files.sort_by(|a, b| {
branch
.ownership

View File

@ -1,4 +1,4 @@
import type { Branch, BranchData, BaseBranch, WritableReloadable } from './types';
import type { Branch, RemoteBranch, BaseBranch, WritableReloadable } from './types';
import * as toasts from '$lib/toasts';
import { invoke } from '$lib/ipc';
@ -6,7 +6,7 @@ export class BranchController {
constructor(
readonly projectId: string,
readonly virtualBranchStore: WritableReloadable<Branch[] | undefined>,
readonly remoteBranchStore: WritableReloadable<BranchData[] | undefined>,
readonly remoteBranchStore: WritableReloadable<RemoteBranch[] | undefined>,
readonly targetBranchStore: WritableReloadable<BaseBranch | undefined>
) {}

View File

@ -1,5 +1,5 @@
import { asyncWritable, type Readable } from '@square/svelte-store';
import { BaseBranch, Branch, BranchData, type WritableReloadable } from './types';
import { BaseBranch, Branch, RemoteBranch, type WritableReloadable } from './types';
import { plainToInstance } from 'class-transformer';
import { invoke } from '$lib/ipc';
import { isDelete, isInsert, type Delta } from '$lib/api/ipc/deltas';
@ -36,13 +36,13 @@ export function getRemoteBranchStore(projectId: string, asyncStores: Readable<an
async () => getRemoteBranchesData({ projectId }),
async (newRemotes) => newRemotes,
{ reloadable: true, trackState: true }
) as WritableReloadable<BranchData[] | undefined>;
) as WritableReloadable<RemoteBranch[] | undefined>;
}
export function getBaseBranchStore(projectId: string, asyncStores: Readable<any>[]) {
return asyncWritable(
asyncStores,
async () => getBaseBranchData({ projectId }),
async () => getBaseBranch({ projectId }),
async (newBaseBranch) => newBaseBranch,
{ reloadable: true, trackState: true }
) as WritableReloadable<BaseBranch | undefined>;
@ -56,11 +56,13 @@ export async function listVirtualBranches(params: { projectId: string }): Promis
return result;
}
export async function getRemoteBranchesData(params: { projectId: string }): Promise<BranchData[]> {
return plainToInstance(BranchData, await invoke<any[]>('git_remote_branches_data', params));
export async function getRemoteBranchesData(params: {
projectId: string;
}): Promise<RemoteBranch[]> {
return plainToInstance(RemoteBranch, await invoke<any[]>('git_remote_branches_data', params));
}
export async function getBaseBranchData(params: { projectId: string }): Promise<BaseBranch> {
export async function getBaseBranch(params: { projectId: string }): Promise<BaseBranch> {
const baseBranch = plainToInstance(BaseBranch, await invoke<any>('get_base_branch_data', params));
if (baseBranch) {
// The rust code performs a fetch when get_base_branch_data is invoked

View File

@ -57,6 +57,27 @@ export class Commit {
files!: File[];
}
export class RemoteCommit {
id!: string;
author!: Author;
description!: string;
@Transform((obj) => new Date(obj.value))
createdAt!: Date;
@Type(() => RemoteFile)
files!: RemoteFile[];
}
export class RemoteHunk {
diff!: string;
}
export class RemoteFile {
path!: string;
@Type(() => RemoteHunk)
hunks!: RemoteHunk[];
binary!: boolean;
}
export class Author {
email!: string;
name!: string;
@ -65,14 +86,14 @@ export class Author {
}
// TODO: For consistency change Ts suffix to At, and return milliseconds from back end
export class BranchData {
export class RemoteBranch {
sha!: string;
name!: string;
behind!: number;
upstream?: string;
mergeable!: boolean;
@Type(() => Commit)
commits!: Commit[];
@Type(() => RemoteCommit)
commits!: RemoteCommit[];
ahead(): number {
return this.commits.length;
@ -98,10 +119,10 @@ export class BaseBranch {
baseSha!: string;
currentSha!: string;
behind!: number;
@Type(() => Commit)
upstreamCommits!: Commit[];
@Type(() => Commit)
recentCommits!: Commit[];
@Type(() => RemoteCommit)
upstreamCommits!: RemoteCommit[];
@Type(() => RemoteCommit)
recentCommits!: RemoteCommit[];
fetchedAt!: Date;
get repoBaseUrl(): string {

View File

@ -1,5 +1,5 @@
<script lang="ts">
import type { Commit } from '$lib/vbranches/types';
import type { Commit, RemoteCommit } from '$lib/vbranches/types';
import TimeAgo from '$lib/components/TimeAgo/TimeAgo.svelte';
import Tooltip from '$lib/components/Tooltip/Tooltip.svelte';
import { getCommitDiff } from '$lib/api/git/diffs';
@ -10,7 +10,7 @@
import { IconExpandUpDown, IconExpandUp, IconExpandDown } from '$lib/icons';
import { Button, Modal } from '$lib/components';
export let commit: Commit;
export let commit: Commit | RemoteCommit;
export let isIntegrated = false;
export let url: string | undefined = undefined;
export let projectId: string;

View File

@ -2,7 +2,7 @@
import IconChevronLeft from '$lib/icons/IconChevronLeft.svelte';
import { SETTINGS_CONTEXT, type SettingsStore } from '$lib/userSettings';
import type { BranchController } from '$lib/vbranches/branchController';
import { BaseBranch, Branch, BranchData } from '$lib/vbranches/types';
import { BaseBranch, Branch, RemoteBranch } from '$lib/vbranches/types';
import { getContext } from 'svelte';
import type { Readable } from '@square/svelte-store';
import BaseBranchPeek from './BaseBranchPeek.svelte';
@ -11,7 +11,7 @@
import Lane from './BranchLane.svelte';
import type { getCloudApiClient } from '$lib/api/cloud/api';
export let item: Readable<BranchData | Branch | BaseBranch | undefined> | undefined;
export let item: Readable<RemoteBranch | Branch | BaseBranch | undefined> | undefined;
export let cloud: ReturnType<typeof getCloudApiClient>;
export let base: BaseBranch | undefined;
export let branchController: BranchController;
@ -68,7 +68,7 @@
class="h-full max-h-full w-full flex-grow overflow-y-hidden"
style:width={`${$userSettings.peekTrayWidth}px`}
>
{#if $item instanceof BranchData}
{#if $item instanceof RemoteBranch}
<RemoteBranchPeek {branchController} {base} branch={$item} />
{:else if $item instanceof Branch}
<Lane

View File

@ -1,10 +1,10 @@
<script lang="ts">
import Button from '$lib/components/Button/Button.svelte';
import type { BranchController } from '$lib/vbranches/branchController';
import type { BaseBranch, BranchData } from '$lib/vbranches/types';
import type { BaseBranch, RemoteBranch } from '$lib/vbranches/types';
import CommitCard from './CommitCard.svelte';
export let branch: BranchData | undefined;
export let branch: RemoteBranch | undefined;
export let base: BaseBranch | undefined;
export let branchController: BranchController;
</script>

View File

@ -1,6 +1,6 @@
<script lang="ts">
import { Link } from '$lib/components';
import { Branch, BaseBranch, BranchData } from '$lib/vbranches/types';
import { Branch, BaseBranch, RemoteBranch } from '$lib/vbranches/types';
import { IconBranch, IconGitBranch, IconRemote } from '$lib/icons';
import { IconTriangleDown, IconTriangleUp } from '$lib/icons';
import { accordion } from './accordion';
@ -25,7 +25,7 @@
import { computedAddedRemoved } from '$lib/vbranches/fileStatus';
export let vbranchStore: Loadable<Branch[] | undefined>;
export let remoteBranchStore: Loadable<BranchData[] | undefined>;
export let remoteBranchStore: Loadable<RemoteBranch[] | undefined>;
export let baseBranchStore: Readable<BaseBranch | undefined>;
export let branchController: BranchController;
export let peekTransitionsDisabled = false;
@ -50,11 +50,11 @@
let rbSection: HTMLElement;
let baseContents: HTMLElement;
let selectedItem: Readable<Branch | BranchData | BaseBranch | undefined> | undefined;
let selectedItem: Readable<Branch | RemoteBranch | BaseBranch | undefined> | undefined;
let overlayOffsetTop = 0;
let fetching = false;
function select(detail: Branch | BranchData | BaseBranch | undefined, i: number): void {
function select(detail: Branch | RemoteBranch | BaseBranch | undefined, i: number): void {
if (peekTrayExpanded && selectedItem && detail == get(selectedItem)) {
peekTrayExpanded = false;
return;
@ -65,7 +65,7 @@
);
const element = vbContents.children[i] as HTMLDivElement;
overlayOffsetTop = element.offsetTop + vbViewport.offsetTop - vbViewport.scrollTop;
} else if (detail instanceof BranchData) {
} else if (detail instanceof RemoteBranch) {
selectedItem = derived(remoteBranchStore, (branches) =>
branches?.find((remoteBranch) => remoteBranch.sha == detail.sha)
);