mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-03 15:33:13 +03:00
Merge pull request #1416 from gitbutlerapp/make-vbranch-remote-a-branch
Make vbranch remote a branch
This commit is contained in:
commit
d8a54e5c49
@ -23,13 +23,12 @@ use super::{branch, get_default_target, iterator::BranchIterator as Iterator, Au
|
||||
// an array of them can be requested from the frontend to show in the sidebar
|
||||
// Tray and should only contain branches that have not been converted into
|
||||
// virtual branches yet (ie, we have no `Branch` struct persisted in our data.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RemoteBranch {
|
||||
pub sha: String,
|
||||
pub name: String,
|
||||
pub sha: git::Oid,
|
||||
pub name: git::BranchName,
|
||||
pub behind: u32,
|
||||
pub upstream: Option<git::RemoteBranchName>,
|
||||
pub commits: Vec<RemoteCommit>,
|
||||
}
|
||||
|
||||
@ -166,42 +165,48 @@ pub fn list_remote_branches(
|
||||
.collect();
|
||||
let top_branches = sorted_branches.into_iter().take(20).collect::<Vec<_>>(); // Take the first 20 entries.
|
||||
|
||||
let mut branches: Vec<RemoteBranch> = Vec::new();
|
||||
for branch in &top_branches {
|
||||
if let Some(branch_oid) = branch.target() {
|
||||
let branches = top_branches
|
||||
.into_iter()
|
||||
.map(|branch| branch_to_remote_branch(project_repository, &branch, main_oid))
|
||||
.collect::<Result<Vec<_>>>()
|
||||
.context("failed to convert branches")?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|branch| !branch.commits.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(branches)
|
||||
}
|
||||
|
||||
pub fn branch_to_remote_branch(
|
||||
project_repository: &project_repository::Repository,
|
||||
branch: &git::Branch,
|
||||
base: git::Oid,
|
||||
) -> Result<Option<RemoteBranch>> {
|
||||
branch
|
||||
.target()
|
||||
.map(|sha| {
|
||||
let ahead = project_repository
|
||||
.log(branch_oid, LogUntil::Commit(main_oid))
|
||||
.log(sha, LogUntil::Commit(base))
|
||||
.context("failed to get ahead commits")?;
|
||||
|
||||
if ahead.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let branch_name = branch.refname().context("could not get branch name")?;
|
||||
let name = git::BranchName::try_from(branch).context("could not get branch name")?;
|
||||
|
||||
let count_behind = project_repository
|
||||
.distance(main_oid, branch_oid)
|
||||
.distance(base, sha)
|
||||
.context("failed to get behind count")?;
|
||||
|
||||
let upstream = branch
|
||||
.upstream()
|
||||
.ok()
|
||||
.map(|upstream_branch| git::RemoteBranchName::try_from(&upstream_branch))
|
||||
.transpose()?;
|
||||
|
||||
branches.push(RemoteBranch {
|
||||
sha: branch_oid.to_string(),
|
||||
name: branch_name.to_string(),
|
||||
upstream,
|
||||
Ok(RemoteBranch {
|
||||
sha,
|
||||
name,
|
||||
behind: count_behind,
|
||||
commits: ahead
|
||||
.into_iter()
|
||||
.map(|commit| commit_to_remote_commit(&commit))
|
||||
.collect::<Result<Vec<_>>>()?,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(branches)
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub fn commit_to_remote_commit(commit: &git::Commit) -> Result<RemoteCommit> {
|
||||
|
@ -1186,7 +1186,7 @@ fn test_merge_vbranch_upstream_clean() -> Result<()> {
|
||||
let branch1 = &branches[0];
|
||||
assert_eq!(branch1.files.len(), 1);
|
||||
assert_eq!(branch1.commits.len(), 1);
|
||||
assert_eq!(branch1.upstream_commits.len(), 1);
|
||||
assert_eq!(branch1.upstream.as_ref().unwrap().commits.len(), 1);
|
||||
|
||||
merge_virtual_branch_upstream(
|
||||
&gb_repository,
|
||||
@ -1208,7 +1208,7 @@ fn test_merge_vbranch_upstream_clean() -> Result<()> {
|
||||
assert_eq!("file2\n", String::from_utf8(contents)?);
|
||||
assert_eq!(branch1.files.len(), 0);
|
||||
assert_eq!(branch1.commits.len(), 3);
|
||||
assert_eq!(branch1.upstream_commits.len(), 0);
|
||||
assert_eq!(branch1.upstream.as_ref().unwrap().commits.len(), 0);
|
||||
|
||||
// make sure the last commit was signed
|
||||
let last_id = &branch1.commits[0].id;
|
||||
@ -1315,7 +1315,7 @@ fn test_merge_vbranch_upstream_conflict() -> Result<()> {
|
||||
|
||||
assert_eq!(branch1.files.len(), 1);
|
||||
assert_eq!(branch1.commits.len(), 1);
|
||||
assert_eq!(branch1.upstream_commits.len(), 1);
|
||||
assert_eq!(branch1.upstream.as_ref().unwrap().commits.len(), 1);
|
||||
|
||||
merge_virtual_branch_upstream(&gb_repository, &project_repository, &branch1.id, None, None)?;
|
||||
|
||||
@ -2218,7 +2218,7 @@ fn test_detect_mergeable_branch() -> Result<()> {
|
||||
list_remote_branches(&gb_repository, &project_repository).expect("failed to list remotes");
|
||||
let remote1 = &remotes
|
||||
.iter()
|
||||
.find(|b| b.name == "refs/remotes/origin/remote_branch")
|
||||
.find(|b| b.name.to_string() == "refs/remotes/origin/remote_branch")
|
||||
.unwrap();
|
||||
assert!(!is_remote_branch_mergeable(
|
||||
&gb_repository,
|
||||
@ -2230,7 +2230,7 @@ fn test_detect_mergeable_branch() -> Result<()> {
|
||||
|
||||
let remote2 = &remotes
|
||||
.iter()
|
||||
.find(|b| b.name == "refs/remotes/origin/remote_branch2")
|
||||
.find(|b| b.name.to_string() == "refs/remotes/origin/remote_branch2")
|
||||
.unwrap();
|
||||
assert!(is_remote_branch_mergeable(
|
||||
&gb_repository,
|
||||
|
@ -20,7 +20,7 @@ use crate::{
|
||||
|
||||
use super::{
|
||||
branch::{self, Branch, BranchCreateRequest, BranchId, FileOwnership, Hunk, Ownership},
|
||||
target, Iterator,
|
||||
branch_to_remote_branch, target, Iterator, RemoteBranch,
|
||||
};
|
||||
|
||||
type AppliedStatuses = Vec<(branch::Branch, Vec<VirtualBranchFile>)>;
|
||||
@ -45,10 +45,9 @@ pub struct VirtualBranch {
|
||||
pub requires_force: bool, // does this branch require a force push to the upstream?
|
||||
pub conflicted: bool, // is this branch currently in a conflicted state (only for applied branches)
|
||||
pub order: usize, // the order in which this branch should be displayed in the UI
|
||||
pub upstream: Option<git::RemoteBranchName>, // the name of the upstream branch this branch this pushes to
|
||||
pub upstream: Option<RemoteBranch>, // the upstream branch where this branch pushes to, if any
|
||||
pub base_current: bool, // is this vbranch based on the current base branch? if false, this needs to be manually merged with conflicts
|
||||
pub ownership: Ownership,
|
||||
pub upstream_commits: Vec<VirtualBranchCommit>,
|
||||
}
|
||||
|
||||
// this is the struct that maps to the view `Commit` type in Typescript
|
||||
@ -655,20 +654,20 @@ pub fn list_virtual_branches(
|
||||
|
||||
let repo = &project_repository.git_repository;
|
||||
|
||||
// see if we can identify some upstream
|
||||
let mut upstream_commit = None;
|
||||
if let Some(branch_upstream) = &branch.upstream {
|
||||
if let Ok(upstream_oid) = repo.refname_to_id(&branch_upstream.to_string()) {
|
||||
if let Ok(upstream_commit_obj) = repo.find_commit(upstream_oid) {
|
||||
upstream_commit = Some(upstream_commit_obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
let upstream_branch = branch
|
||||
.upstream
|
||||
.as_ref()
|
||||
.map(|name| repo.find_branch(&git::BranchName::from(name.clone())))
|
||||
.transpose()?;
|
||||
|
||||
let upstram_branch_commit = upstream_branch
|
||||
.as_ref()
|
||||
.map(git::Branch::peel_to_commit)
|
||||
.transpose()?;
|
||||
|
||||
// find upstream commits if we found an upstream reference
|
||||
let mut upstream_commits = vec![];
|
||||
let mut pushed_commits = HashMap::new();
|
||||
if let Some(upstream) = &upstream_commit {
|
||||
if let Some(upstream) = &upstram_branch_commit {
|
||||
let merge_base =
|
||||
repo.merge_base(upstream.id(), default_target.sha)
|
||||
.context(format!(
|
||||
@ -679,13 +678,6 @@ pub fn list_virtual_branches(
|
||||
for oid in project_repository.l(upstream.id(), LogUntil::Commit(merge_base))? {
|
||||
pushed_commits.insert(oid, true);
|
||||
}
|
||||
|
||||
// find any commits on the upstream that aren't in our branch (someone else pushed to our branch)
|
||||
for commit in project_repository.log(upstream.id(), LogUntil::Commit(branch.head))? {
|
||||
let commit =
|
||||
commit_to_vbranch_commit(project_repository, &default_target, &commit, None)?;
|
||||
upstream_commits.push(commit);
|
||||
}
|
||||
}
|
||||
|
||||
// find all commits on head that are not on target.sha
|
||||
@ -713,8 +705,14 @@ pub fn list_virtual_branches(
|
||||
}
|
||||
}
|
||||
|
||||
let requires_force = is_requires_force(project_repository, branch)?;
|
||||
let upstream = upstream_branch
|
||||
.map(|upstream_branch| {
|
||||
branch_to_remote_branch(project_repository, &upstream_branch, branch.head)
|
||||
})
|
||||
.transpose()?
|
||||
.flatten();
|
||||
|
||||
let requires_force = is_requires_force(project_repository, branch)?;
|
||||
let branch = VirtualBranch {
|
||||
id: branch.id,
|
||||
name: branch.name.clone(),
|
||||
@ -724,11 +722,10 @@ pub fn list_virtual_branches(
|
||||
order: branch.order,
|
||||
commits,
|
||||
requires_force,
|
||||
upstream: branch.upstream.clone(),
|
||||
upstream,
|
||||
conflicted: conflicts::is_resolving(project_repository),
|
||||
base_current,
|
||||
ownership: branch.ownership.clone(),
|
||||
upstream_commits,
|
||||
};
|
||||
branches.push(branch);
|
||||
}
|
||||
|
@ -353,8 +353,8 @@ mod references {
|
||||
assert_eq!(branches[0].id, branch1_id);
|
||||
assert_eq!(branches[0].name, "name");
|
||||
assert_eq!(
|
||||
branches[0].upstream,
|
||||
Some("refs/remotes/origin/name".parse().unwrap())
|
||||
branches[0].upstream.as_ref().unwrap().name.to_string(),
|
||||
"refs/remotes/origin/name"
|
||||
);
|
||||
|
||||
let refnames = repository
|
||||
@ -362,7 +362,7 @@ mod references {
|
||||
.into_iter()
|
||||
.filter_map(|reference| reference.name().map(|name| name.to_string()))
|
||||
.collect::<Vec<_>>();
|
||||
assert!(refnames.contains(&branches[0].upstream.clone().unwrap().to_string()));
|
||||
assert!(refnames.contains(&branches[0].upstream.clone().unwrap().name.to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -448,15 +448,15 @@ mod references {
|
||||
assert_eq!(branches[0].id, branch1_id);
|
||||
assert_eq!(branches[0].name, "updated name");
|
||||
assert_eq!(
|
||||
branches[0].upstream,
|
||||
Some("refs/remotes/origin/name".parse().unwrap())
|
||||
branches[0].upstream.as_ref().unwrap().name,
|
||||
"refs/remotes/origin/name".parse().unwrap()
|
||||
);
|
||||
// new branch is pushing to new ref remotely
|
||||
assert_eq!(branches[1].id, branch2_id);
|
||||
assert_eq!(branches[1].name, "name");
|
||||
assert_eq!(
|
||||
branches[1].upstream,
|
||||
Some("refs/remotes/origin/name-1".parse().unwrap())
|
||||
branches[1].upstream.as_ref().unwrap().name,
|
||||
"refs/remotes/origin/name-1".parse().unwrap()
|
||||
);
|
||||
|
||||
let refnames = repository
|
||||
@ -464,8 +464,8 @@ mod references {
|
||||
.into_iter()
|
||||
.filter_map(|reference| reference.name().map(|name| name.to_string()))
|
||||
.collect::<Vec<_>>();
|
||||
assert!(refnames.contains(&branches[0].upstream.clone().unwrap().to_string()));
|
||||
assert!(refnames.contains(&branches[1].upstream.clone().unwrap().to_string()));
|
||||
assert!(refnames.contains(&branches[0].upstream.clone().unwrap().name.to_string()));
|
||||
assert!(refnames.contains(&branches[1].upstream.clone().unwrap().name.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,9 +40,8 @@ export class Branch {
|
||||
requiresForce!: boolean;
|
||||
description!: string;
|
||||
order!: number;
|
||||
upstream?: string;
|
||||
@Type(() => Commit)
|
||||
upstreamCommits!: Commit[];
|
||||
@Type(() => RemoteBranch)
|
||||
upstream?: RemoteBranch;
|
||||
conflicted!: boolean;
|
||||
baseCurrent!: boolean;
|
||||
ownership!: string;
|
||||
|
@ -90,11 +90,11 @@
|
||||
|
||||
$: pullRequestPromise =
|
||||
githubContext && branch.upstream
|
||||
? getPullRequestByBranch(githubContext, branch.upstream.split('/').slice(-1)[0])
|
||||
? getPullRequestByBranch(githubContext, branch.upstream?.name.split('/').slice(-1)[0])
|
||||
: undefined;
|
||||
|
||||
let shouldCreatePr = false;
|
||||
$: branchName = branch.upstream?.split('/').slice(-1)[0];
|
||||
$: branchName = branch.upstream?.name.split('/').slice(-1)[0];
|
||||
$: if (shouldCreatePr && branchName && githubContext) {
|
||||
createPR();
|
||||
shouldCreatePr = false;
|
||||
@ -184,7 +184,7 @@
|
||||
|
||||
let upstreamCommitsShown = false;
|
||||
|
||||
$: if (upstreamCommitsShown && branch.upstreamCommits.length === 0) {
|
||||
$: if (upstreamCommitsShown && branch.upstream?.commits.length === 0) {
|
||||
upstreamCommitsShown = false;
|
||||
}
|
||||
|
||||
@ -405,12 +405,12 @@
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if branch.upstreamCommits.length > 0 && !branch.conflicted}
|
||||
{#if branch.upstream?.commits.length && branch.upstream?.commits.length > 0 && !branch.conflicted}
|
||||
<div class="bg-zinc-300 p-2 dark:bg-zinc-800">
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="p-1 text-purple-700">
|
||||
{branch.upstreamCommits.length}
|
||||
upstream {branch.upstreamCommits.length > 1 ? 'commits' : 'commit'}
|
||||
{branch.upstream.commits.length}
|
||||
upstream {branch.upstream.commits.length > 1 ? 'commits' : 'commit'}
|
||||
</div>
|
||||
<Button
|
||||
class="w-20"
|
||||
@ -435,7 +435,7 @@
|
||||
id="upstreamCommits"
|
||||
>
|
||||
<div class="bg-light-100">
|
||||
{#each branch.upstreamCommits as commit}
|
||||
{#each branch.upstream.commits as commit}
|
||||
<CommitCard {commit} {projectId} />
|
||||
{/each}
|
||||
</div>
|
||||
@ -655,10 +655,10 @@
|
||||
<Link
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={branchUrl(base, branch.upstream)}
|
||||
href={branchUrl(base, branch.upstream?.name)}
|
||||
class="inline-block max-w-full truncate text-sm font-bold"
|
||||
>
|
||||
{branch.upstream.split('refs/remotes/')[1]}
|
||||
{branch.upstream.name.split('refs/remotes/')[1]}
|
||||
</Link>
|
||||
{#await pullRequestPromise then pr}
|
||||
{#if githubContext && pr}
|
||||
|
@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import type { BranchController } from '$lib/vbranches/branchController';
|
||||
import type { RemoteBranch } from '$lib/vbranches/types';
|
||||
|
||||
export let branch: string;
|
||||
export let branch: RemoteBranch;
|
||||
export let branchController: BranchController;
|
||||
export let branchId: string;
|
||||
|
||||
@ -9,7 +10,7 @@
|
||||
let remoteBranchName = '';
|
||||
|
||||
$: if (branch) {
|
||||
let parts = branch.replace('refs/remotes/', '').split('/');
|
||||
let parts = branch.name.replace('refs/remotes/', '').split('/');
|
||||
remoteName = parts[0];
|
||||
// remoteBranchName is the rest
|
||||
let rbn = parts.slice(1).join('/');
|
||||
|
@ -29,7 +29,7 @@
|
||||
<div>
|
||||
<p class="text-lg font-bold" title="name of virtual branch">{branch.name}</p>
|
||||
<p class="text-light-700 dark:text-dark-200" title="upstream target">
|
||||
{branch.upstream?.replace('refs/remotes/', '') || ''}
|
||||
{branch.upstream?.name.replace('refs/remotes/', '') || ''}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
|
Loading…
Reference in New Issue
Block a user