mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-25 02:26:14 +03:00
Merged origin/master into Collapsable lane
This commit is contained in:
commit
3427a3e8c5
@ -214,6 +214,7 @@ fn main() {
|
|||||||
virtual_branches::commands::cherry_pick_onto_virtual_branch,
|
virtual_branches::commands::cherry_pick_onto_virtual_branch,
|
||||||
virtual_branches::commands::amend_virtual_branch,
|
virtual_branches::commands::amend_virtual_branch,
|
||||||
virtual_branches::commands::list_remote_branches,
|
virtual_branches::commands::list_remote_branches,
|
||||||
|
virtual_branches::commands::get_remote_branch_data,
|
||||||
virtual_branches::commands::squash_branch_commit,
|
virtual_branches::commands::squash_branch_commit,
|
||||||
virtual_branches::commands::fetch_from_target,
|
virtual_branches::commands::fetch_from_target,
|
||||||
menu::menu_item_set_enabled,
|
menu::menu_item_set_enabled,
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::watcher;
|
use crate::watcher;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use tauri::{AppHandle, Manager};
|
use tauri::{AppHandle, Manager};
|
||||||
@ -519,6 +521,28 @@ pub async fn list_remote_branches(
|
|||||||
Ok(branches)
|
Ok(branches)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command(async)]
|
||||||
|
#[instrument(skip(handle))]
|
||||||
|
pub async fn get_remote_branch_data(
|
||||||
|
handle: tauri::AppHandle,
|
||||||
|
project_id: &str,
|
||||||
|
refname: &str,
|
||||||
|
) -> Result<super::RemoteBranchData, Error> {
|
||||||
|
let project_id = project_id.parse().map_err(|_| Error::UserError {
|
||||||
|
code: Code::Validation,
|
||||||
|
message: "Malformed project id".to_string(),
|
||||||
|
})?;
|
||||||
|
let refname = git::Refname::from_str(refname).map_err(|_| Error::UserError {
|
||||||
|
code: Code::Validation,
|
||||||
|
message: "Malformed refname".to_string(),
|
||||||
|
})?;
|
||||||
|
let branch_data = handle
|
||||||
|
.state::<Controller>()
|
||||||
|
.get_remote_branch_data(&project_id, &refname)
|
||||||
|
.await?;
|
||||||
|
Ok(branch_data)
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command(async)]
|
#[tauri::command(async)]
|
||||||
#[instrument(skip(handle))]
|
#[instrument(skip(handle))]
|
||||||
pub async fn squash_branch_commit(
|
pub async fn squash_branch_commit(
|
||||||
|
@ -14,8 +14,8 @@ use crate::{
|
|||||||
use super::{
|
use super::{
|
||||||
branch::{BranchId, Ownership},
|
branch::{BranchId, Ownership},
|
||||||
errors::{
|
errors::{
|
||||||
self, FetchFromTargetError, GetBaseBranchDataError, IsRemoteBranchMergableError,
|
self, FetchFromTargetError, GetBaseBranchDataError, GetRemoteBranchDataError,
|
||||||
ListRemoteBranchesError,
|
IsRemoteBranchMergableError, ListRemoteBranchesError,
|
||||||
},
|
},
|
||||||
target_to_base_branch, BaseBranch, RemoteBranchFile,
|
target_to_base_branch, BaseBranch, RemoteBranchFile,
|
||||||
};
|
};
|
||||||
@ -313,6 +313,16 @@ impl Controller {
|
|||||||
.list_remote_branches(project_id)
|
.list_remote_branches(project_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_remote_branch_data(
|
||||||
|
&self,
|
||||||
|
project_id: &ProjectId,
|
||||||
|
refname: &git::Refname,
|
||||||
|
) -> Result<super::RemoteBranchData, ControllerError<GetRemoteBranchDataError>> {
|
||||||
|
self.inner(project_id)
|
||||||
|
.await
|
||||||
|
.get_remote_branch_data(project_id, refname)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn squash(
|
pub async fn squash(
|
||||||
&self,
|
&self,
|
||||||
project_id: &ProjectId,
|
project_id: &ProjectId,
|
||||||
@ -808,6 +818,25 @@ impl ControllerInner {
|
|||||||
.map_err(ControllerError::Action)
|
.map_err(ControllerError::Action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_remote_branch_data(
|
||||||
|
&self,
|
||||||
|
project_id: &ProjectId,
|
||||||
|
refname: &git::Refname,
|
||||||
|
) -> Result<super::RemoteBranchData, ControllerError<GetRemoteBranchDataError>> {
|
||||||
|
let project = self.projects.get(project_id).map_err(Error::from)?;
|
||||||
|
let project_repository =
|
||||||
|
project_repository::Repository::open(&project).map_err(Error::from)?;
|
||||||
|
let user = self.users.get_user().map_err(Error::from)?;
|
||||||
|
let gb_repository = gb_repository::Repository::open(
|
||||||
|
&self.local_data_dir,
|
||||||
|
&project_repository,
|
||||||
|
user.as_ref(),
|
||||||
|
)
|
||||||
|
.context("failed to open gitbutler repository")?;
|
||||||
|
super::get_branch_data(&gb_repository, &project_repository, refname)
|
||||||
|
.map_err(ControllerError::Action)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn squash(
|
pub async fn squash(
|
||||||
&self,
|
&self,
|
||||||
project_id: &ProjectId,
|
project_id: &ProjectId,
|
||||||
|
@ -751,6 +751,26 @@ pub enum ListRemoteBranchesError {
|
|||||||
Other(#[from] anyhow::Error),
|
Other(#[from] anyhow::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum GetRemoteBranchDataError {
|
||||||
|
#[error("default target not set")]
|
||||||
|
DefaultTargetNotSet(DefaultTargetNotSetError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Other(#[from] anyhow::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<GetRemoteBranchDataError> for Error {
|
||||||
|
fn from(value: GetRemoteBranchDataError) -> Self {
|
||||||
|
match value {
|
||||||
|
GetRemoteBranchDataError::DefaultTargetNotSet(error) => error.into(),
|
||||||
|
GetRemoteBranchDataError::Other(error) => {
|
||||||
|
tracing::error!(?error, "get remote branch data error");
|
||||||
|
Error::Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<ListRemoteBranchesError> for Error {
|
impl From<ListRemoteBranchesError> for Error {
|
||||||
fn from(value: ListRemoteBranchesError) -> Self {
|
fn from(value: ListRemoteBranchesError) -> Self {
|
||||||
match value {
|
match value {
|
||||||
|
@ -27,6 +27,16 @@ pub struct RemoteBranch {
|
|||||||
pub commits: Vec<RemoteCommit>,
|
pub commits: Vec<RemoteCommit>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, PartialEq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RemoteBranchData {
|
||||||
|
pub sha: git::Oid,
|
||||||
|
pub name: git::Refname,
|
||||||
|
pub upstream: Option<git::RemoteRefname>,
|
||||||
|
pub behind: u32,
|
||||||
|
pub commits: Vec<RemoteCommit>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RemoteCommit {
|
pub struct RemoteCommit {
|
||||||
@ -66,6 +76,43 @@ pub fn list_remote_branches(
|
|||||||
Ok(remote_branches)
|
Ok(remote_branches)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_branch_data(
|
||||||
|
gb_repository: &gb_repository::Repository,
|
||||||
|
project_repository: &project_repository::Repository,
|
||||||
|
refname: &git::Refname,
|
||||||
|
) -> Result<super::RemoteBranchData, errors::GetRemoteBranchDataError> {
|
||||||
|
let default_target = gb_repository
|
||||||
|
.default_target()
|
||||||
|
.context("failed to get default target")?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
errors::GetRemoteBranchDataError::DefaultTargetNotSet(
|
||||||
|
errors::DefaultTargetNotSetError {
|
||||||
|
project_id: project_repository.project().id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let branch = project_repository
|
||||||
|
.git_repository
|
||||||
|
.find_branch(refname)
|
||||||
|
.context(format!("failed to find branch with refname {refname}"))?;
|
||||||
|
|
||||||
|
let branch_data = branch_to_remote_branch(project_repository, &branch, default_target.sha)
|
||||||
|
.context("failed to get branch data")?;
|
||||||
|
|
||||||
|
branch_data
|
||||||
|
.ok_or_else(|| {
|
||||||
|
errors::GetRemoteBranchDataError::Other(anyhow::anyhow!("no data found for branch"))
|
||||||
|
})
|
||||||
|
.map(|branch_data| RemoteBranchData {
|
||||||
|
sha: branch_data.sha,
|
||||||
|
name: branch_data.name,
|
||||||
|
upstream: branch_data.upstream,
|
||||||
|
behind: branch_data.behind,
|
||||||
|
commits: branch_data.commits,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn branch_to_remote_branch(
|
pub fn branch_to_remote_branch(
|
||||||
project_repository: &project_repository::Repository,
|
project_repository: &project_repository::Repository,
|
||||||
branch: &git::Branch,
|
branch: &git::Branch,
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import ScrollableContainer from './ScrollableContainer.svelte';
|
import ScrollableContainer from './ScrollableContainer.svelte';
|
||||||
import CommitCard from '$lib/components/CommitCard.svelte';
|
import CommitCard from '$lib/components/CommitCard.svelte';
|
||||||
import { type SettingsStore, SETTINGS_CONTEXT } from '$lib/settings/userSettings';
|
import { type SettingsStore, SETTINGS_CONTEXT } from '$lib/settings/userSettings';
|
||||||
|
import { getRemoteBranchData } from '$lib/stores/remoteBranches';
|
||||||
import { Ownership } from '$lib/vbranches/ownership';
|
import { Ownership } from '$lib/vbranches/ownership';
|
||||||
import lscache from 'lscache';
|
import lscache from 'lscache';
|
||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
@ -59,18 +60,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if branch.commits && branch.commits.length > 0}
|
{#await getRemoteBranchData({ projectId, refname: branch.name }) then branchData}
|
||||||
<div class="flex w-full flex-col gap-y-2">
|
{#if branchData.commits && branchData.commits.length > 0}
|
||||||
{#each branch.commits as commit (commit.id)}
|
<div class="flex w-full flex-col gap-y-2">
|
||||||
<CommitCard
|
{#each branchData.commits as commit (commit.id)}
|
||||||
{commit}
|
<CommitCard
|
||||||
{projectId}
|
{commit}
|
||||||
{selectedFiles}
|
{projectId}
|
||||||
commitUrl={base?.commitUrl(commit.id)}
|
{selectedFiles}
|
||||||
/>
|
commitUrl={base?.commitUrl(commit.id)}
|
||||||
{/each}
|
/>
|
||||||
</div>
|
{/each}
|
||||||
{/if}
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
</ScrollableContainer>
|
</ScrollableContainer>
|
||||||
<Resizer
|
<Resizer
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { invoke } from '$lib/backend/ipc';
|
import { invoke } from '$lib/backend/ipc';
|
||||||
import * as toasts from '$lib/utils/toasts';
|
import * as toasts from '$lib/utils/toasts';
|
||||||
import { RemoteBranch } from '$lib/vbranches/types';
|
import { RemoteBranch, RemoteBranchData } from '$lib/vbranches/types';
|
||||||
import { plainToInstance } from 'class-transformer';
|
import { plainToInstance } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
@ -25,7 +25,7 @@ export class RemoteBranchService {
|
|||||||
baseBranch$: Observable<any>
|
baseBranch$: Observable<any>
|
||||||
) {
|
) {
|
||||||
this.branches$ = combineLatest([baseBranch$, this.reload$, head$, fetches$]).pipe(
|
this.branches$ = combineLatest([baseBranch$, this.reload$, head$, fetches$]).pipe(
|
||||||
switchMap(() => getRemoteBranchesData({ projectId })),
|
switchMap(() => listRemoteBranches({ projectId })),
|
||||||
map((branches) => branches.filter((b) => b.ahead != 0)),
|
map((branches) => branches.filter((b) => b.ahead != 0)),
|
||||||
shareReplay(1),
|
shareReplay(1),
|
||||||
catchError((e) => {
|
catchError((e) => {
|
||||||
@ -42,9 +42,7 @@ export class RemoteBranchService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRemoteBranchesData(params: {
|
async function listRemoteBranches(params: { projectId: string }): Promise<RemoteBranch[]> {
|
||||||
projectId: string;
|
|
||||||
}): Promise<RemoteBranch[]> {
|
|
||||||
const branches = plainToInstance(
|
const branches = plainToInstance(
|
||||||
RemoteBranch,
|
RemoteBranch,
|
||||||
await invoke<any[]>('list_remote_branches', params)
|
await invoke<any[]>('list_remote_branches', params)
|
||||||
@ -52,3 +50,15 @@ export async function getRemoteBranchesData(params: {
|
|||||||
|
|
||||||
return branches;
|
return branches;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getRemoteBranchData(params: {
|
||||||
|
projectId: string;
|
||||||
|
refname: string;
|
||||||
|
}): Promise<RemoteBranchData> {
|
||||||
|
const branchData = plainToInstance(
|
||||||
|
RemoteBranchData,
|
||||||
|
await invoke<any>('get_remote_branch_data', params)
|
||||||
|
);
|
||||||
|
|
||||||
|
return branchData;
|
||||||
|
}
|
||||||
|
@ -216,6 +216,40 @@ export class RemoteBranch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class RemoteBranchData {
|
||||||
|
sha!: string;
|
||||||
|
name!: string;
|
||||||
|
upstream?: string;
|
||||||
|
behind!: number;
|
||||||
|
@Type(() => RemoteCommit)
|
||||||
|
commits!: RemoteCommit[];
|
||||||
|
isMergeable!: boolean | undefined;
|
||||||
|
|
||||||
|
get ahead(): number {
|
||||||
|
return this.commits.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
get lastCommitTs(): Date | undefined {
|
||||||
|
return this.commits[0]?.createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
get firstCommitAt(): Date {
|
||||||
|
return this.commits[this.commits.length - 1].createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
get authors(): Author[] {
|
||||||
|
const allAuthors = this.commits.map((commit) => commit.author);
|
||||||
|
const uniqueAuthors = allAuthors.filter(
|
||||||
|
(author, index) => allAuthors.findIndex((a) => a.email == author.email) == index
|
||||||
|
);
|
||||||
|
return uniqueAuthors;
|
||||||
|
}
|
||||||
|
|
||||||
|
get displayName(): string {
|
||||||
|
return this.name.replace('refs/remotes/', '').replace('origin/', '').replace('refs/heads/', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class BaseBranch {
|
export class BaseBranch {
|
||||||
branchName!: string;
|
branchName!: string;
|
||||||
remoteName!: string;
|
remoteName!: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user