mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2025-01-06 01:27:24 +03:00
fix merge conflict
This commit is contained in:
commit
bb56719d1e
45
Cargo.lock
generated
45
Cargo.lock
generated
@ -86,9 +86,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.5.0"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
|
||||
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
@ -124,9 +124,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "2.1.0"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
|
||||
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys 0.48.0",
|
||||
@ -704,9 +704,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.2"
|
||||
version = "4.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6"
|
||||
checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@ -714,9 +714,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.2"
|
||||
version = "4.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
|
||||
checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@ -1164,6 +1164,21 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deunicode"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71dbf1bf89c23e9cd1baf5e654f622872655f195b36588dc9dc38f7eda30758c"
|
||||
dependencies = [
|
||||
"deunicode 1.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deunicode"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a1abaf4d861455be59f64fd2b55606cb151fce304ede7165f410243ce96bde6"
|
||||
|
||||
[[package]]
|
||||
name = "dialoguer"
|
||||
version = "0.11.0"
|
||||
@ -1950,6 +1965,7 @@ dependencies = [
|
||||
"sha1",
|
||||
"sha2",
|
||||
"similar",
|
||||
"slug",
|
||||
"ssh-key",
|
||||
"tantivy",
|
||||
"tauri",
|
||||
@ -4686,9 +4702,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.7"
|
||||
version = "0.10.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8"
|
||||
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
@ -4785,6 +4801,15 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slug"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373"
|
||||
dependencies = [
|
||||
"deunicode 0.4.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.0"
|
||||
|
@ -10,7 +10,7 @@ rust-version = "1.57"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.71"
|
||||
clap = { version = "4.0", features = ["derive"] }
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
colored = "2.0.0"
|
||||
dialoguer = "0.11.0"
|
||||
dirs = "5.0.1"
|
||||
|
@ -81,6 +81,7 @@ impl super::RunCommand for Move {
|
||||
|
||||
virtual_branches::update_branch(
|
||||
&gb_repository,
|
||||
&app.project_repository(),
|
||||
virtual_branches::branch::BranchUpdateRequest {
|
||||
id: target_branch.id,
|
||||
ownership: Some(ownership),
|
||||
|
@ -20,6 +20,7 @@ impl super::RunCommand for New {
|
||||
|
||||
virtual_branches::create_virtual_branch(
|
||||
&app.gb_repository(),
|
||||
&app.project_repository(),
|
||||
&virtual_branches::branch::BranchCreateRequest {
|
||||
name: Some(input),
|
||||
..Default::default()
|
||||
|
@ -49,8 +49,9 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
serde-jsonlines = "0.4.0"
|
||||
serde_json = { version = "1.0", features = [ "std", "arbitrary_precision" ] }
|
||||
sha1 = "0.10.6"
|
||||
sha2 = "0.10.7"
|
||||
sha2 = "0.10.8"
|
||||
similar = { version = "2.2.1", features = ["unicode"] }
|
||||
slug = "0.1.4"
|
||||
ssh-key = { version = "0.6.1", features = [ "alloc", "ed25519" ] }
|
||||
tantivy = "0.20.2"
|
||||
tauri = { version = "1.5.2", features = ["dialog-open", "fs-read-file", "path-all", "process-relaunch", "protocol-asset", "shell-open", "system-tray", "window-maximize", "window-start-dragging", "window-unmaximize"] }
|
||||
|
@ -386,7 +386,6 @@ impl Repository {
|
||||
self.0.remote(name, url).map(Into::into).map_err(Into::into)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn references(&self) -> Result<impl Iterator<Item = Result<Reference>>> {
|
||||
self.0
|
||||
.references()
|
||||
|
@ -213,9 +213,7 @@ impl Repository {
|
||||
|
||||
if let Some(url) = remote_url {
|
||||
match url.scheme {
|
||||
#[cfg(test)]
|
||||
git::Scheme::File => Ok(remote),
|
||||
git::Scheme::Https => Ok(remote),
|
||||
git::Scheme::Https | git::Scheme::File => Ok(remote),
|
||||
_ => {
|
||||
let https_url = url
|
||||
.as_https()
|
||||
@ -260,9 +258,7 @@ impl Repository {
|
||||
|
||||
if let Some(url) = remote_url {
|
||||
match url.scheme {
|
||||
#[cfg(test)]
|
||||
git::Scheme::File => Ok(remote),
|
||||
git::Scheme::Ssh => Ok(remote),
|
||||
git::Scheme::Ssh | git::Scheme::File => Ok(remote),
|
||||
_ => {
|
||||
let ssh_url = url
|
||||
.as_ssh()
|
||||
|
@ -18,7 +18,7 @@ pub struct BaseBranch {
|
||||
pub branch_name: String,
|
||||
pub remote_name: String,
|
||||
pub remote_url: String,
|
||||
pub base_sha: String,
|
||||
pub base_sha: git::Oid,
|
||||
pub current_sha: String,
|
||||
pub behind: usize,
|
||||
pub upstream_commits: Vec<RemoteCommit>,
|
||||
@ -483,7 +483,7 @@ pub fn target_to_base_branch(
|
||||
branch_name: format!("{}/{}", target.branch.remote(), target.branch.branch()),
|
||||
remote_name: target.branch.remote().to_string(),
|
||||
remote_url: target.remote_url.clone(),
|
||||
base_sha: target.sha.to_string(),
|
||||
base_sha: target.sha,
|
||||
current_sha: oid.to_string(),
|
||||
behind: upstream_commits.len(),
|
||||
upstream_commits,
|
||||
|
@ -8,6 +8,7 @@ pub use file_ownership::FileOwnership;
|
||||
pub use hunk::Hunk;
|
||||
pub use ownership::Ownership;
|
||||
pub use reader::BranchReader as Reader;
|
||||
use slug::slugify;
|
||||
pub use writer::BranchWriter as Writer;
|
||||
|
||||
use std::path;
|
||||
@ -43,6 +44,12 @@ pub struct Branch {
|
||||
pub order: usize,
|
||||
}
|
||||
|
||||
impl Branch {
|
||||
pub fn refname(&self) -> String {
|
||||
format!("refs/gitbutler/{}", slugify(&self.name))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct BranchUpdateRequest {
|
||||
pub id: BranchId,
|
||||
|
@ -186,9 +186,10 @@ impl Controller {
|
||||
if conflicts::is_resolving(project_repository) {
|
||||
return Err(Error::Conflicting);
|
||||
}
|
||||
let branch_id = super::create_virtual_branch(gb_repository, create)
|
||||
.map_err(Error::Other)?
|
||||
.id;
|
||||
let branch_id =
|
||||
super::create_virtual_branch(gb_repository, project_repository, create)
|
||||
.map_err(Error::Other)?
|
||||
.id;
|
||||
Ok(branch_id)
|
||||
})
|
||||
})
|
||||
@ -356,8 +357,8 @@ impl Controller {
|
||||
branch_update: super::branch::BranchUpdateRequest,
|
||||
) -> Result<(), Error> {
|
||||
self.with_lock(project_id, || {
|
||||
self.with_verify_branch(project_id, |gb_repository, _, _| {
|
||||
super::update_branch(gb_repository, branch_update)?;
|
||||
self.with_verify_branch(project_id, |gb_repository, project_repository, _| {
|
||||
super::update_branch(gb_repository, project_repository, branch_update)?;
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
|
@ -113,8 +113,7 @@ pub fn update_gitbutler_integration(
|
||||
for branch in &applied_virtual_branches {
|
||||
message.push_str(" - ");
|
||||
message.push_str(branch.name.as_str());
|
||||
let branch_name = super::name_to_branch(branch.name.as_str());
|
||||
message.push_str(format!(" (gitbutler/{})", &branch_name).as_str());
|
||||
message.push_str(format!(" ({})", &branch.refname()).as_str());
|
||||
message.push('\n');
|
||||
|
||||
if branch.head != target.sha {
|
||||
@ -183,9 +182,12 @@ pub fn update_gitbutler_integration(
|
||||
branch_head = repo.find_commit(branch_head_oid)?;
|
||||
}
|
||||
|
||||
let branch_name = super::name_to_branch(branch.name.as_str());
|
||||
let branch_ref = format!("refs/gitbutler/{}", branch_name);
|
||||
repo.reference(&branch_ref, branch_head.id(), true, "update virtual branch")?;
|
||||
repo.reference(
|
||||
&branch.refname(),
|
||||
branch_head.id(),
|
||||
true,
|
||||
"update virtual branch",
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -282,6 +284,7 @@ fn verify_head_is_clean(
|
||||
|
||||
let new_branch = super::create_virtual_branch(
|
||||
gb_repository,
|
||||
project_repository,
|
||||
&BranchCreateRequest {
|
||||
name: extra_commits
|
||||
.last()
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,7 @@ use std::{
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use diffy::{apply_bytes, Patch};
|
||||
use serde::Serialize;
|
||||
use slug::slugify;
|
||||
|
||||
use crate::{
|
||||
dedup::{dedup, dedup_fmt},
|
||||
@ -945,6 +946,7 @@ pub fn commit_to_vbranch_commit(
|
||||
|
||||
pub fn create_virtual_branch(
|
||||
gb_repository: &gb_repository::Repository,
|
||||
project_repository: &project_repository::Repository,
|
||||
create: &BranchCreateRequest,
|
||||
) -> Result<branch::Branch> {
|
||||
let current_session = gb_repository
|
||||
@ -996,17 +998,15 @@ pub fn create_virtual_branch(
|
||||
.context("failed to get elapsed time")?
|
||||
.as_millis();
|
||||
|
||||
let name: String = create.name.as_ref().map_or_else(
|
||||
|| {
|
||||
dedup(
|
||||
&all_virtual_branches
|
||||
.iter()
|
||||
.map(|b| b.name.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
"Virtual branch",
|
||||
)
|
||||
},
|
||||
ToString::to_string,
|
||||
let name = dedup(
|
||||
&all_virtual_branches
|
||||
.iter()
|
||||
.map(|b| b.name.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
create
|
||||
.name
|
||||
.as_ref()
|
||||
.unwrap_or(&"Virtual branch".to_string()),
|
||||
);
|
||||
|
||||
let mut branch = Branch {
|
||||
@ -1034,6 +1034,11 @@ pub fn create_virtual_branch(
|
||||
.write(&branch)
|
||||
.context("failed to write branch")?;
|
||||
|
||||
project_repository
|
||||
.git_repository
|
||||
.reference(&branch.refname(), branch.head, false, "new vbranch")
|
||||
.context("failed to create branch reference")?;
|
||||
|
||||
Ok(branch)
|
||||
}
|
||||
|
||||
@ -1189,6 +1194,7 @@ pub fn merge_virtual_branch_upstream(
|
||||
|
||||
pub fn update_branch(
|
||||
gb_repository: &gb_repository::Repository,
|
||||
project_repository: &project_repository::Repository,
|
||||
branch_update: branch::BranchUpdateRequest,
|
||||
) -> Result<branch::Branch> {
|
||||
let current_session = gb_repository
|
||||
@ -1209,7 +1215,32 @@ pub fn update_branch(
|
||||
}
|
||||
|
||||
if let Some(name) = branch_update.name {
|
||||
branch.name = name;
|
||||
let all_virtual_branches = Iterator::new(¤t_session_reader)
|
||||
.context("failed to create branch iterator")?
|
||||
.collect::<Result<Vec<branch::Branch>, reader::Error>>()
|
||||
.context("failed to read virtual branches")?;
|
||||
|
||||
if let Ok(ref mut reference) = project_repository
|
||||
.git_repository
|
||||
.find_reference(&branch.refname())
|
||||
{
|
||||
reference
|
||||
.delete()
|
||||
.context("failed to delete old branch reference")?;
|
||||
}
|
||||
|
||||
branch.name = dedup(
|
||||
&all_virtual_branches
|
||||
.iter()
|
||||
.map(|b| b.name.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
&name,
|
||||
);
|
||||
|
||||
project_repository
|
||||
.git_repository
|
||||
.reference(&branch.refname(), branch.head, false, "new vbranch")
|
||||
.context("failed to create branch reference")?;
|
||||
};
|
||||
|
||||
if let Some(upstream_branch_name) = branch_update.upstream {
|
||||
@ -1217,7 +1248,7 @@ pub fn update_branch(
|
||||
Some(target) => format!(
|
||||
"refs/remotes/{}/{}",
|
||||
target.branch.remote(),
|
||||
name_to_branch(&upstream_branch_name)
|
||||
slugify(upstream_branch_name)
|
||||
)
|
||||
.parse::<git::RemoteBranchName>()
|
||||
.unwrap(),
|
||||
@ -1264,14 +1295,11 @@ pub fn delete_branch(
|
||||
|
||||
// remove refs/butler reference
|
||||
let repo = &project_repository.git_repository;
|
||||
let branch_name = name_to_branch(&branch.name);
|
||||
let ref_name = format!("refs/gitbutler/{}", branch_name);
|
||||
println!("deleting ref: {}", ref_name);
|
||||
if let Ok(mut reference) = repo.find_reference(&ref_name) {
|
||||
println!("FOUND {}", ref_name);
|
||||
let refname = branch.refname();
|
||||
if let Ok(mut reference) = repo.find_reference(&refname) {
|
||||
reference
|
||||
.delete()
|
||||
.context(format!("failed to delete {}", ref_name))?;
|
||||
.context(format!("failed to delete {}", refname))?;
|
||||
}
|
||||
|
||||
Ok(branch)
|
||||
@ -1523,11 +1551,12 @@ fn get_applied_status(
|
||||
|
||||
if virtual_branches.is_empty() && !hunks_by_filepath.is_empty() {
|
||||
// no virtual branches, but hunks: create default branch
|
||||
virtual_branches =
|
||||
vec![
|
||||
create_virtual_branch(gb_repository, &BranchCreateRequest::default())
|
||||
.context("failed to default branch")?,
|
||||
];
|
||||
virtual_branches = vec![create_virtual_branch(
|
||||
gb_repository,
|
||||
project_repository,
|
||||
&BranchCreateRequest::default(),
|
||||
)
|
||||
.context("failed to default branch")?];
|
||||
}
|
||||
|
||||
// align branch ownership to the real hunks:
|
||||
@ -2047,12 +2076,6 @@ pub fn commit(
|
||||
Ok(commit_oid)
|
||||
}
|
||||
|
||||
pub fn name_to_branch(name: &str) -> String {
|
||||
name.chars()
|
||||
.map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
|
||||
.collect::<String>()
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum CommitError {
|
||||
#[error("will not commit conflicted files")]
|
||||
@ -2099,7 +2122,7 @@ pub fn push(
|
||||
Some(target) => format!(
|
||||
"refs/remotes/{}/{}",
|
||||
target.branch.remote(),
|
||||
name_to_branch(&vbranch.name)
|
||||
slugify(&vbranch.name)
|
||||
)
|
||||
.parse::<git::RemoteBranchName>()
|
||||
.unwrap(),
|
||||
@ -2111,11 +2134,8 @@ pub fn push(
|
||||
.iter()
|
||||
.map(RemoteBranchName::branch)
|
||||
.collect::<Vec<_>>();
|
||||
remote_branch.with_branch(&name_to_branch(&dedup_fmt(
|
||||
&existing_branches,
|
||||
remote_branch.branch(),
|
||||
"-",
|
||||
)))
|
||||
|
||||
remote_branch.with_branch(&dedup_fmt(&existing_branches, remote_branch.branch(), "-"))
|
||||
};
|
||||
|
||||
project_repository.push(&vbranch.head, &remote_branch, with_force, credentials)?;
|
||||
|
@ -111,4 +111,12 @@ impl TestProject {
|
||||
)
|
||||
.expect("failed to commit")
|
||||
}
|
||||
|
||||
pub fn references(&self) -> Vec<git::Reference> {
|
||||
self.local_repository
|
||||
.references()
|
||||
.expect("failed to get references")
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.expect("failed to read references")
|
||||
}
|
||||
}
|
||||
|
@ -35,10 +35,10 @@ impl Default for Test {
|
||||
}
|
||||
}
|
||||
|
||||
mod create_virtual_branch {
|
||||
mod references {
|
||||
use super::*;
|
||||
|
||||
mod name {
|
||||
mod create_virtual_branch {
|
||||
|
||||
use super::*;
|
||||
|
||||
@ -47,6 +47,7 @@ mod create_virtual_branch {
|
||||
let Test {
|
||||
project_id,
|
||||
controller,
|
||||
repository,
|
||||
..
|
||||
} = Test::default();
|
||||
|
||||
@ -65,6 +66,14 @@ mod create_virtual_branch {
|
||||
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
|
||||
assert_eq!(branches.len(), 1);
|
||||
assert_eq!(branches[0].id, branch_id);
|
||||
assert_eq!(branches[0].name, "Virtual branch");
|
||||
|
||||
let refnames = repository
|
||||
.references()
|
||||
.into_iter()
|
||||
.filter_map(|reference| reference.name().map(|name| name.to_string()))
|
||||
.collect::<Vec<_>>();
|
||||
assert!(refnames.contains(&"refs/gitbutler/virtual-branch".to_string()))
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -72,6 +81,7 @@ mod create_virtual_branch {
|
||||
let Test {
|
||||
project_id,
|
||||
controller,
|
||||
repository,
|
||||
..
|
||||
} = Test::default();
|
||||
|
||||
@ -110,15 +120,19 @@ mod create_virtual_branch {
|
||||
assert_eq!(branches[0].id, branch1_id);
|
||||
assert_eq!(branches[0].name, "name");
|
||||
assert_eq!(branches[1].id, branch2_id);
|
||||
assert_eq!(branches[1].name, "name");
|
||||
assert_eq!(branches[1].name, "name 1");
|
||||
|
||||
let refnames = repository
|
||||
.references()
|
||||
.into_iter()
|
||||
.filter_map(|reference| reference.name().map(|name| name.to_string()))
|
||||
.collect::<Vec<_>>();
|
||||
assert!(refnames.contains(&"refs/gitbutler/name".to_string()));
|
||||
assert!(refnames.contains(&"refs/gitbutler/name-1".to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod update_virtual_branch {
|
||||
use super::*;
|
||||
|
||||
mod name {
|
||||
mod update_virtual_branch {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
@ -126,6 +140,7 @@ mod update_virtual_branch {
|
||||
let Test {
|
||||
project_id,
|
||||
controller,
|
||||
repository,
|
||||
..
|
||||
} = Test::default();
|
||||
|
||||
@ -137,7 +152,13 @@ mod update_virtual_branch {
|
||||
.unwrap();
|
||||
|
||||
let branch_id = controller
|
||||
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
|
||||
.create_virtual_branch(
|
||||
&project_id,
|
||||
&gitbutler::virtual_branches::branch::BranchCreateRequest {
|
||||
name: Some("name".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -157,6 +178,14 @@ mod update_virtual_branch {
|
||||
assert_eq!(branches.len(), 1);
|
||||
assert_eq!(branches[0].id, branch_id);
|
||||
assert_eq!(branches[0].name, "new name");
|
||||
|
||||
let refnames = repository
|
||||
.references()
|
||||
.into_iter()
|
||||
.filter_map(|reference| reference.name().map(|name| name.to_string()))
|
||||
.collect::<Vec<_>>();
|
||||
assert!(!refnames.contains(&"refs/gitbutler/name".to_string()));
|
||||
assert!(refnames.contains(&"refs/gitbutler/new-name".to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -164,6 +193,7 @@ mod update_virtual_branch {
|
||||
let Test {
|
||||
project_id,
|
||||
controller,
|
||||
repository,
|
||||
..
|
||||
} = Test::default();
|
||||
|
||||
@ -212,7 +242,226 @@ mod update_virtual_branch {
|
||||
assert_eq!(branches[0].id, branch1_id);
|
||||
assert_eq!(branches[0].name, "name");
|
||||
assert_eq!(branches[1].id, branch2_id);
|
||||
assert_eq!(branches[1].name, "name 1");
|
||||
|
||||
let refnames = repository
|
||||
.references()
|
||||
.into_iter()
|
||||
.filter_map(|reference| reference.name().map(|name| name.to_string()))
|
||||
.collect::<Vec<_>>();
|
||||
assert!(refnames.contains(&"refs/gitbutler/name".to_string()));
|
||||
assert!(refnames.contains(&"refs/gitbutler/name-1".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
mod delete_virtual_branch {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn simple() {
|
||||
let Test {
|
||||
project_id,
|
||||
controller,
|
||||
repository,
|
||||
..
|
||||
} = Test::default();
|
||||
|
||||
controller
|
||||
.set_base_branch(
|
||||
&project_id,
|
||||
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let id = controller
|
||||
.create_virtual_branch(
|
||||
&project_id,
|
||||
&gitbutler::virtual_branches::branch::BranchCreateRequest {
|
||||
name: Some("name".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
controller
|
||||
.delete_virtual_branch(&project_id, &id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
|
||||
assert_eq!(branches.len(), 0);
|
||||
|
||||
let refnames = repository
|
||||
.references()
|
||||
.into_iter()
|
||||
.filter_map(|reference| reference.name().map(|name| name.to_string()))
|
||||
.collect::<Vec<_>>();
|
||||
assert!(!refnames.contains(&"refs/gitbutler/name".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
mod push_virtual_branch {
|
||||
use gitbutler::virtual_branches::branch::BranchUpdateRequest;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn simple() {
|
||||
let Test {
|
||||
project_id,
|
||||
controller,
|
||||
repository,
|
||||
..
|
||||
} = Test::default();
|
||||
|
||||
controller
|
||||
.set_base_branch(
|
||||
&project_id,
|
||||
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let branch1_id = controller
|
||||
.create_virtual_branch(
|
||||
&project_id,
|
||||
&gitbutler::virtual_branches::branch::BranchCreateRequest {
|
||||
name: Some("name".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
fs::write(repository.path().join("file.txt"), "content").unwrap();
|
||||
|
||||
controller
|
||||
.create_commit(&project_id, &branch1_id, "test", None)
|
||||
.await
|
||||
.unwrap();
|
||||
controller
|
||||
.push_virtual_branch(&project_id, &branch1_id, false)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
|
||||
assert_eq!(branches.len(), 1);
|
||||
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())
|
||||
);
|
||||
|
||||
let refnames = repository
|
||||
.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()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn duplicate_names() {
|
||||
let Test {
|
||||
project_id,
|
||||
controller,
|
||||
repository,
|
||||
..
|
||||
} = Test::default();
|
||||
|
||||
controller
|
||||
.set_base_branch(
|
||||
&project_id,
|
||||
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let branch1_id = {
|
||||
// create and push branch with some work
|
||||
let branch1_id = controller
|
||||
.create_virtual_branch(
|
||||
&project_id,
|
||||
&gitbutler::virtual_branches::branch::BranchCreateRequest {
|
||||
name: Some("name".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
fs::write(repository.path().join("file.txt"), "content").unwrap();
|
||||
controller
|
||||
.create_commit(&project_id, &branch1_id, "test", None)
|
||||
.await
|
||||
.unwrap();
|
||||
controller
|
||||
.push_virtual_branch(&project_id, &branch1_id, false)
|
||||
.await
|
||||
.unwrap();
|
||||
branch1_id
|
||||
};
|
||||
|
||||
// rename first branch
|
||||
controller
|
||||
.update_virtual_branch(
|
||||
&project_id,
|
||||
BranchUpdateRequest {
|
||||
id: branch1_id,
|
||||
name: Some("updated name".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let branch2_id = {
|
||||
// create another branch with first branch's old name and push it
|
||||
let branch2_id = controller
|
||||
.create_virtual_branch(
|
||||
&project_id,
|
||||
&gitbutler::virtual_branches::branch::BranchCreateRequest {
|
||||
name: Some("name".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
fs::write(repository.path().join("file.txt"), "updated content").unwrap();
|
||||
controller
|
||||
.create_commit(&project_id, &branch2_id, "test", None)
|
||||
.await
|
||||
.unwrap();
|
||||
controller
|
||||
.push_virtual_branch(&project_id, &branch2_id, false)
|
||||
.await
|
||||
.unwrap();
|
||||
branch2_id
|
||||
};
|
||||
|
||||
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
|
||||
assert_eq!(branches.len(), 2);
|
||||
// first branch is pushing to old ref remotely
|
||||
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())
|
||||
);
|
||||
// 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())
|
||||
);
|
||||
|
||||
let refnames = repository
|
||||
.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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -650,3 +899,271 @@ mod conflicts {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod reset {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn to_head() {
|
||||
let Test {
|
||||
repository,
|
||||
project_id,
|
||||
controller,
|
||||
} = Test::default();
|
||||
|
||||
controller
|
||||
.set_base_branch(
|
||||
&project_id,
|
||||
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let branch1_id = controller
|
||||
.create_virtual_branch(&project_id, &Default::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let oid = {
|
||||
fs::write(repository.path().join("file.txt"), "content").unwrap();
|
||||
|
||||
// commit changes
|
||||
let oid = controller
|
||||
.create_commit(&project_id, &branch1_id, "commit", None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
|
||||
assert_eq!(branches.len(), 1);
|
||||
assert_eq!(branches[0].id, branch1_id);
|
||||
assert_eq!(branches[0].commits.len(), 1);
|
||||
assert_eq!(branches[0].commits[0].id, oid);
|
||||
assert_eq!(branches[0].files.len(), 0);
|
||||
assert_eq!(
|
||||
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
|
||||
"content"
|
||||
);
|
||||
|
||||
oid
|
||||
};
|
||||
|
||||
{
|
||||
// reset changes to head
|
||||
controller
|
||||
.reset_virtual_branch(&project_id, &branch1_id, oid)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
|
||||
assert_eq!(branches.len(), 1);
|
||||
assert_eq!(branches[0].id, branch1_id);
|
||||
assert_eq!(branches[0].commits.len(), 1);
|
||||
assert_eq!(branches[0].commits[0].id, oid);
|
||||
assert_eq!(branches[0].files.len(), 0);
|
||||
assert_eq!(
|
||||
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
|
||||
"content"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn to_target() {
|
||||
let Test {
|
||||
repository,
|
||||
project_id,
|
||||
controller,
|
||||
} = Test::default();
|
||||
|
||||
let base_branch = controller
|
||||
.set_base_branch(
|
||||
&project_id,
|
||||
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let branch1_id = controller
|
||||
.create_virtual_branch(&project_id, &Default::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
{
|
||||
fs::write(repository.path().join("file.txt"), "content").unwrap();
|
||||
|
||||
// commit changes
|
||||
let oid = controller
|
||||
.create_commit(&project_id, &branch1_id, "commit", None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
|
||||
assert_eq!(branches.len(), 1);
|
||||
assert_eq!(branches[0].id, branch1_id);
|
||||
assert_eq!(branches[0].commits.len(), 1);
|
||||
assert_eq!(branches[0].commits[0].id, oid);
|
||||
assert_eq!(branches[0].files.len(), 0);
|
||||
assert_eq!(
|
||||
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
|
||||
"content"
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
// reset changes to head
|
||||
controller
|
||||
.reset_virtual_branch(&project_id, &branch1_id, base_branch.base_sha)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
|
||||
assert_eq!(branches.len(), 1);
|
||||
assert_eq!(branches[0].id, branch1_id);
|
||||
assert_eq!(branches[0].commits.len(), 0);
|
||||
assert_eq!(branches[0].files.len(), 1);
|
||||
assert_eq!(
|
||||
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
|
||||
"content"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn to_commit() {
|
||||
let Test {
|
||||
repository,
|
||||
project_id,
|
||||
controller,
|
||||
} = Test::default();
|
||||
|
||||
controller
|
||||
.set_base_branch(
|
||||
&project_id,
|
||||
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let branch1_id = controller
|
||||
.create_virtual_branch(&project_id, &Default::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let first_commit_oid = {
|
||||
// commit some changes
|
||||
|
||||
fs::write(repository.path().join("file.txt"), "content").unwrap();
|
||||
|
||||
let oid = controller
|
||||
.create_commit(&project_id, &branch1_id, "commit", None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
|
||||
assert_eq!(branches.len(), 1);
|
||||
assert_eq!(branches[0].id, branch1_id);
|
||||
assert_eq!(branches[0].commits.len(), 1);
|
||||
assert_eq!(branches[0].commits[0].id, oid);
|
||||
assert_eq!(branches[0].files.len(), 0);
|
||||
assert_eq!(
|
||||
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
|
||||
"content"
|
||||
);
|
||||
|
||||
oid
|
||||
};
|
||||
|
||||
{
|
||||
// commit some more
|
||||
fs::write(repository.path().join("file.txt"), "more content").unwrap();
|
||||
|
||||
let second_commit_oid = controller
|
||||
.create_commit(&project_id, &branch1_id, "commit", None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
|
||||
assert_eq!(branches.len(), 1);
|
||||
assert_eq!(branches[0].id, branch1_id);
|
||||
assert_eq!(branches[0].commits.len(), 2);
|
||||
assert_eq!(branches[0].commits[0].id, second_commit_oid);
|
||||
assert_eq!(branches[0].commits[1].id, first_commit_oid);
|
||||
assert_eq!(branches[0].files.len(), 0);
|
||||
assert_eq!(
|
||||
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
|
||||
"more content"
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
// reset changes to the first commit
|
||||
controller
|
||||
.reset_virtual_branch(&project_id, &branch1_id, first_commit_oid)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
|
||||
assert_eq!(branches.len(), 1);
|
||||
assert_eq!(branches[0].id, branch1_id);
|
||||
assert_eq!(branches[0].commits.len(), 1);
|
||||
assert_eq!(branches[0].commits[0].id, first_commit_oid);
|
||||
assert_eq!(branches[0].files.len(), 1);
|
||||
assert_eq!(
|
||||
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
|
||||
"more content"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn to_non_existing() {
|
||||
let Test {
|
||||
repository,
|
||||
project_id,
|
||||
controller,
|
||||
} = Test::default();
|
||||
|
||||
controller
|
||||
.set_base_branch(
|
||||
&project_id,
|
||||
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let branch1_id = controller
|
||||
.create_virtual_branch(&project_id, &Default::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
{
|
||||
fs::write(repository.path().join("file.txt"), "content").unwrap();
|
||||
|
||||
// commit changes
|
||||
let oid = controller
|
||||
.create_commit(&project_id, &branch1_id, "commit", None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
|
||||
assert_eq!(branches.len(), 1);
|
||||
assert_eq!(branches[0].id, branch1_id);
|
||||
assert_eq!(branches[0].commits.len(), 1);
|
||||
assert_eq!(branches[0].commits[0].id, oid);
|
||||
assert_eq!(branches[0].files.len(), 0);
|
||||
assert_eq!(
|
||||
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
|
||||
"content"
|
||||
);
|
||||
|
||||
oid
|
||||
};
|
||||
|
||||
assert!(matches!(
|
||||
controller
|
||||
.reset_virtual_branch(
|
||||
&project_id,
|
||||
&branch1_id,
|
||||
"fe14df8c66b73c6276f7bb26102ad91da680afcb".parse().unwrap()
|
||||
)
|
||||
.await,
|
||||
Err(ControllerError::Other(_))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,8 @@
|
||||
import BaseBranchSelect from './BaseBranchSelect.svelte';
|
||||
import { unsubscribe } from '$lib/utils';
|
||||
import * as hotkeys from '$lib/hotkeys';
|
||||
import { userStore } from '$lib/stores/user';
|
||||
import type { GitHubIntegrationContext } from '$lib/github/types';
|
||||
|
||||
export let data: PageData;
|
||||
let { projectId, project, cloud } = data;
|
||||
@ -81,6 +83,24 @@
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
function getIntegrationContext(
|
||||
remoteUrl: string,
|
||||
githubAuthToken: string
|
||||
): GitHubIntegrationContext | undefined {
|
||||
if (!remoteUrl.includes('github')) return undefined;
|
||||
const [owner, repo] = remoteUrl.split('.git')[0].split(/\/|:/).slice(-2);
|
||||
return {
|
||||
authToken: githubAuthToken,
|
||||
owner,
|
||||
repo
|
||||
};
|
||||
}
|
||||
|
||||
$: githubContext =
|
||||
$baseBranchStore?.remoteUrl && $userStore?.github_access_token
|
||||
? getIntegrationContext($baseBranchStore?.remoteUrl, $userStore?.github_access_token)
|
||||
: undefined;
|
||||
</script>
|
||||
|
||||
{#if $baseBranchesState.isLoading}
|
||||
@ -98,6 +118,7 @@
|
||||
bind:peekTrayExpanded
|
||||
{cloud}
|
||||
{projectId}
|
||||
{githubContext}
|
||||
/>
|
||||
</div>
|
||||
<Resizer
|
||||
@ -151,6 +172,7 @@
|
||||
baseBranchState={$baseBranchesState}
|
||||
cloudEnabled={$project?.api?.sync || false}
|
||||
{cloud}
|
||||
{githubContext}
|
||||
/>
|
||||
</div>
|
||||
<!-- <BottomPanel base={$baseBranchStore} {userSettings} /> -->
|
||||
|
@ -8,6 +8,7 @@
|
||||
import type { LoadState } from '@square/svelte-store';
|
||||
import { open } from '@tauri-apps/api/shell';
|
||||
import { IconFile, IconTerminal, IconExternalLink } from '$lib/icons';
|
||||
import type { GitHubIntegrationContext } from '$lib/github/types';
|
||||
|
||||
export let projectId: string;
|
||||
export let projectPath: string;
|
||||
@ -22,6 +23,8 @@
|
||||
export let cloud: ReturnType<typeof getCloudApiClient>;
|
||||
export let branchController: BranchController;
|
||||
|
||||
export let githubContext: GitHubIntegrationContext | undefined
|
||||
|
||||
let dragged: any;
|
||||
let dropZone: HTMLDivElement;
|
||||
let priorPosition = 0;
|
||||
@ -100,6 +103,7 @@
|
||||
{cloud}
|
||||
{branchController}
|
||||
branchCount={branches.filter((c) => c.active).length}
|
||||
{githubContext}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
|
@ -65,6 +65,7 @@
|
||||
export let branchController: BranchController;
|
||||
export let maximized = false;
|
||||
export let branchCount = 1;
|
||||
export let githubContext: GitHubIntegrationContext | undefined;
|
||||
|
||||
const user = userStore;
|
||||
const userSettings = getContext<SettingsStore>(SETTINGS_CONTEXT);
|
||||
@ -87,20 +88,6 @@
|
||||
const dzType = 'text/hunk';
|
||||
const laneWidthKey = 'laneWidth:';
|
||||
|
||||
function getIntegrationContext(): GitHubIntegrationContext | undefined {
|
||||
const remoteUrl = base?.remoteUrl;
|
||||
if (!remoteUrl) return undefined;
|
||||
const [owner, repo] = remoteUrl.split('.git')[0].split(/\/|:/).slice(-2);
|
||||
const authToken = $user?.github_access_token;
|
||||
if (!authToken) return undefined;
|
||||
return {
|
||||
authToken,
|
||||
owner,
|
||||
repo
|
||||
};
|
||||
}
|
||||
|
||||
$: githubContext = getIntegrationContext();
|
||||
$: pullRequestPromise =
|
||||
githubContext && branch.upstream
|
||||
? getPullRequestByBranch(githubContext, branch.upstream.split('/').slice(-1)[0])
|
||||
|
@ -9,6 +9,7 @@
|
||||
import RemoteBranchPeek from './RemoteBranchPeek.svelte';
|
||||
import Resizer from '$lib/components/Resizer.svelte';
|
||||
import Lane from './BranchLane.svelte';
|
||||
import type { GitHubIntegrationContext } from '$lib/github/types';
|
||||
import type { getCloudApiClient } from '$lib/api/cloud/api';
|
||||
|
||||
export let item: Readable<RemoteBranch | Branch | BaseBranch | undefined> | undefined;
|
||||
@ -20,6 +21,7 @@
|
||||
export let projectId: string;
|
||||
export let fullHeight = false;
|
||||
export let disabled = false;
|
||||
export let githubContext: GitHubIntegrationContext | undefined;
|
||||
|
||||
let viewport: HTMLElement;
|
||||
|
||||
@ -81,6 +83,7 @@
|
||||
cloudEnabled={false}
|
||||
projectPath=""
|
||||
readonly={true}
|
||||
{githubContext}
|
||||
/>
|
||||
{:else if $item instanceof BaseBranch}
|
||||
<BaseBranchPeek {projectId} base={$item} {branchController} />
|
||||
|
159
packages/ui/src/routes/repo/[projectId]/RemoteBranches.svelte
Normal file
159
packages/ui/src/routes/repo/[projectId]/RemoteBranches.svelte
Normal file
@ -0,0 +1,159 @@
|
||||
<script lang="ts">
|
||||
import { Link } from '$lib/components';
|
||||
import { IconGitBranch, IconRemote } from '$lib/icons';
|
||||
import IconHelp from '$lib/icons/IconHelp.svelte';
|
||||
import Scrollbar from '$lib/components/Scrollbar.svelte';
|
||||
import Tooltip from '$lib/components/Tooltip/Tooltip.svelte';
|
||||
import { IconTriangleDown, IconTriangleUp } from '$lib/icons';
|
||||
import TimeAgo from '$lib/components/TimeAgo/TimeAgo.svelte';
|
||||
import { accordion } from './accordion';
|
||||
import type { CustomStore, RemoteBranch, BaseBranch, Branch } from '$lib/vbranches/types';
|
||||
import type { Readable } from '@square/svelte-store';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
selection: {
|
||||
branch: RemoteBranch;
|
||||
i: number;
|
||||
offset: number;
|
||||
};
|
||||
}>();
|
||||
|
||||
export let remoteBranchStore: CustomStore<RemoteBranch[] | undefined>;
|
||||
let rbViewport: HTMLElement;
|
||||
let rbContents: HTMLElement;
|
||||
let rbSection: HTMLElement;
|
||||
export let peekTrayExpanded = false;
|
||||
export let selectedItem: Readable<Branch | RemoteBranch | BaseBranch | undefined> | undefined;
|
||||
|
||||
$: remoteBranchesState = remoteBranchStore?.state;
|
||||
|
||||
let open = false;
|
||||
|
||||
function select(branch: RemoteBranch, i: number) {
|
||||
const element = rbContents.children[i] as HTMLDivElement;
|
||||
const offset = element.offsetTop + rbSection.offsetTop - rbViewport.scrollTop;
|
||||
dispatch('selection', { branch, i, offset });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="bg-color-4 border-color-4 flex items-center justify-between border-b border-t px-2 py-1 pr-1"
|
||||
>
|
||||
<div class="flex flex-row place-items-center space-x-2">
|
||||
<div class="text-color-2 font-bold">Remote Branches</div>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://docs.gitbutler.com/features/virtual-branches/remote-branches"
|
||||
>
|
||||
<IconHelp class="text-color-3 h-3 w-3" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex h-4 w-4 justify-around">
|
||||
<button class="h-full w-full" on:click={() => (open = !open)}>
|
||||
{#if open}
|
||||
<IconTriangleUp />
|
||||
{:else}
|
||||
<IconTriangleDown />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div bind:this={rbSection} use:accordion={open} class="border-color-5 relative flex-grow border-b">
|
||||
<div
|
||||
bind:this={rbViewport}
|
||||
on:scroll
|
||||
class="hide-native-scrollbar flex max-h-full flex-grow flex-col overflow-y-scroll overscroll-none"
|
||||
>
|
||||
<div bind:this={rbContents}>
|
||||
{#if $remoteBranchesState.isLoading}
|
||||
<div class="px-2 py-1">loading...</div>
|
||||
{:else if $remoteBranchesState.isError}
|
||||
<div class="px-2 py-1">Something went wrong</div>
|
||||
{:else if !$remoteBranchStore || $remoteBranchStore.length == 0}
|
||||
<div class="p-4">
|
||||
<p class="text-color-3 mb-2">
|
||||
There are no local or remote Git branches that can be imported as virtual branches
|
||||
</p>
|
||||
<Link
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://docs.gitbutler.com/features/virtual-branches/remote-branches"
|
||||
>
|
||||
Learn more
|
||||
</Link>
|
||||
</div>
|
||||
{:else if $remoteBranchStore}
|
||||
{#each $remoteBranchStore as branch, i}
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
on:click={() => select(branch, i)}
|
||||
on:keypress={() => select(branch, i)}
|
||||
class:bg-color-4={$selectedItem == branch && peekTrayExpanded}
|
||||
class="border-color-4 flex flex-col justify-between gap-1 border-b px-2 py-1 pt-2 -outline-offset-2 outline-blue-200 last:border-b focus:outline-2"
|
||||
>
|
||||
<div class="flex flex-row items-center gap-x-2 pr-1">
|
||||
<div class="text-color-3">
|
||||
{#if branch.name.match('refs/remotes')}
|
||||
<Tooltip
|
||||
label="This is a remote branch that you don't have a virtual branch tracking yet"
|
||||
>
|
||||
<IconRemote class="h-4 w-4" />
|
||||
</Tooltip>
|
||||
{:else}
|
||||
<Tooltip label="This is a local branch that is not a virtual branch yet">
|
||||
<IconGitBranch class="h-4 w-4" />
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="text-color-2 flex-grow truncate" title={branch.name}>
|
||||
{branch.name
|
||||
.replace('refs/remotes/', '')
|
||||
.replace('origin/', '')
|
||||
.replace('refs/heads/', '')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row justify-between space-x-2 rounded p-1 pr-1">
|
||||
<div class="text-color-4 flex-grow-0 text-sm">
|
||||
<TimeAgo date={branch.lastCommitTs()} />
|
||||
</div>
|
||||
<div class="flex flex-grow-0 flex-row space-x-2">
|
||||
<Tooltip
|
||||
label="This branch has {branch.ahead()} commits not on your base branch and your base has {branch.behind} commits not on this branch yet"
|
||||
>
|
||||
<div class="bg-color-3 text-color-3 rounded-lg px-2 text-sm">
|
||||
{branch.ahead()} / {branch.behind}
|
||||
</div>
|
||||
</Tooltip>
|
||||
{#await branch.isMergeable then isMergeable}
|
||||
{#if !isMergeable}
|
||||
<div class="font-bold text-red-500" title="Can't be merged">!</div>
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
<div
|
||||
class="isolate flex flex-grow justify-end -space-x-2 overflow-hidden transition duration-300 ease-in-out hover:space-x-1 hover:transition hover:ease-in"
|
||||
>
|
||||
{#each branch.authors() as author}
|
||||
<img
|
||||
class="relative z-30 inline-block h-4 w-4 rounded-full ring-1 ring-white dark:ring-black"
|
||||
title="Gravatar for {author.email}"
|
||||
alt="Gravatar for {author.email}"
|
||||
srcset="{author.gravatarUrl} 2x"
|
||||
width="100"
|
||||
height="100"
|
||||
on:error
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<Scrollbar viewport={rbViewport} contents={rbContents} width="0.5rem" />
|
||||
</div>
|
@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Link } from '$lib/components';
|
||||
import { Branch, BaseBranch, RemoteBranch, type CustomStore } from '$lib/vbranches/types';
|
||||
import { IconBranch, IconGitBranch, IconRemote } from '$lib/icons';
|
||||
import { IconBranch } from '$lib/icons';
|
||||
import { IconTriangleDown, IconTriangleUp } from '$lib/icons';
|
||||
import { accordion } from './accordion';
|
||||
import { SETTINGS_CONTEXT, type SettingsStore } from '$lib/userSettings';
|
||||
@ -9,7 +8,6 @@
|
||||
import type { BranchController } from '$lib/vbranches/branchController';
|
||||
import Tooltip from '$lib/components/Tooltip/Tooltip.svelte';
|
||||
import Scrollbar from '$lib/components/Scrollbar.svelte';
|
||||
import IconHelp from '$lib/icons/IconHelp.svelte';
|
||||
import { derived, get, type Readable } from '@square/svelte-store';
|
||||
import PeekTray from './PeekTray.svelte';
|
||||
import IconRefresh from '$lib/icons/IconRefresh.svelte';
|
||||
@ -23,6 +21,8 @@
|
||||
import IconChevronRightSmall from '$lib/icons/IconChevronRightSmall.svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
import { computedAddedRemoved } from '$lib/vbranches/fileStatus';
|
||||
import RemoteBranches from './RemoteBranches.svelte';
|
||||
import type { GitHubIntegrationContext } from '$lib/github/types';
|
||||
|
||||
export let branchesWithContentStore: CustomStore<Branch[] | undefined>;
|
||||
export let remoteBranchStore: CustomStore<RemoteBranch[] | undefined>;
|
||||
@ -32,29 +32,29 @@
|
||||
export let projectId: string;
|
||||
export let cloud: ReturnType<typeof getCloudApiClient>;
|
||||
export let peekTrayExpanded = false;
|
||||
export let githubContext: GitHubIntegrationContext | undefined;
|
||||
|
||||
$: branchesState = branchesWithContentStore?.state;
|
||||
$: remoteBranchesState = remoteBranchStore?.state;
|
||||
|
||||
const userSettings = getContext<SettingsStore>(SETTINGS_CONTEXT);
|
||||
|
||||
let yourBranchesOpen = true;
|
||||
let remoteBranchesOpen = true;
|
||||
|
||||
let applyConflictedModal: Modal;
|
||||
|
||||
let vbViewport: HTMLElement;
|
||||
let vbContents: HTMLElement;
|
||||
let rbViewport: HTMLElement;
|
||||
let rbContents: HTMLElement;
|
||||
let rbSection: HTMLElement;
|
||||
let baseContents: HTMLElement;
|
||||
|
||||
let selectedItem: Readable<Branch | RemoteBranch | BaseBranch | undefined> | undefined;
|
||||
let overlayOffsetTop = 0;
|
||||
let fetching = false;
|
||||
|
||||
function select(detail: Branch | RemoteBranch | BaseBranch | undefined, i: number): void {
|
||||
function select(
|
||||
detail: Branch | RemoteBranch | BaseBranch | undefined,
|
||||
i: number,
|
||||
offset?: number
|
||||
): void {
|
||||
if (peekTrayExpanded && selectedItem && detail == get(selectedItem)) {
|
||||
peekTrayExpanded = false;
|
||||
return;
|
||||
@ -69,8 +69,7 @@
|
||||
selectedItem = derived(remoteBranchStore, (branches) =>
|
||||
branches?.find((remoteBranch) => remoteBranch.sha == detail.sha)
|
||||
);
|
||||
const element = rbContents.children[i] as HTMLDivElement;
|
||||
overlayOffsetTop = element.offsetTop + rbSection.offsetTop - rbViewport.scrollTop;
|
||||
overlayOffsetTop = offset || overlayOffsetTop;
|
||||
} else if (detail instanceof BaseBranch) {
|
||||
selectedItem = baseBranchStore;
|
||||
overlayOffsetTop = baseContents.offsetTop;
|
||||
@ -118,6 +117,7 @@
|
||||
disabled={peekTransitionsDisabled}
|
||||
{cloud}
|
||||
{projectId}
|
||||
{githubContext}
|
||||
/>
|
||||
<div
|
||||
class="bg-color-5 border-color-4 z-30 flex w-80 shrink-0 flex-col border-r"
|
||||
@ -308,130 +308,13 @@
|
||||
/>
|
||||
|
||||
<!-- Remote branches -->
|
||||
<div
|
||||
class="bg-color-4 border-color-4 flex items-center justify-between border-b border-t px-2 py-1 pr-1"
|
||||
>
|
||||
<div class="flex flex-row place-items-center space-x-2">
|
||||
<div class="text-color-2 font-bold">Remote Branches</div>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://docs.gitbutler.com/features/virtual-branches/remote-branches"
|
||||
>
|
||||
<IconHelp class="text-color-3 h-3 w-3" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex h-4 w-4 justify-around">
|
||||
<button class="h-full w-full" on:click={() => (remoteBranchesOpen = !remoteBranchesOpen)}>
|
||||
{#if remoteBranchesOpen}
|
||||
<IconTriangleUp />
|
||||
{:else}
|
||||
<IconTriangleDown />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
bind:this={rbSection}
|
||||
use:accordion={remoteBranchesOpen}
|
||||
class="border-color-5 relative flex-grow border-b"
|
||||
>
|
||||
<div
|
||||
bind:this={rbViewport}
|
||||
on:scroll={onScroll}
|
||||
class="hide-native-scrollbar flex max-h-full flex-grow flex-col overflow-y-scroll overscroll-none"
|
||||
>
|
||||
<div bind:this={rbContents}>
|
||||
{#if $remoteBranchesState.isLoading}
|
||||
<div class="px-2 py-1">loading...</div>
|
||||
{:else if $remoteBranchesState.isError}
|
||||
<div class="px-2 py-1">Something went wrong</div>
|
||||
{:else if !$remoteBranchStore || $remoteBranchStore.length == 0}
|
||||
<div class="p-4">
|
||||
<p class="text-color-3 mb-2">
|
||||
There are no local or remote Git branches that can be imported as virtual branches
|
||||
</p>
|
||||
<Link
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://docs.gitbutler.com/features/virtual-branches/remote-branches"
|
||||
>
|
||||
Learn more
|
||||
</Link>
|
||||
</div>
|
||||
{:else if $remoteBranchStore}
|
||||
{#each $remoteBranchStore as branch, i}
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
on:click={() => select(branch, i)}
|
||||
on:keypress={() => select(branch, i)}
|
||||
class:bg-color-4={$selectedItem == branch && peekTrayExpanded}
|
||||
class="border-color-4 flex flex-col justify-between gap-1 border-b px-2 py-1 pt-2 -outline-offset-2 outline-blue-200 last:border-b focus:outline-2"
|
||||
>
|
||||
<div class="flex flex-row items-center gap-x-2 pr-1">
|
||||
<div class="text-color-3">
|
||||
{#if branch.name.match('refs/remotes')}
|
||||
<Tooltip
|
||||
label="This is a remote branch that you don't have a virtual branch tracking yet"
|
||||
>
|
||||
<IconRemote class="h-4 w-4" />
|
||||
</Tooltip>
|
||||
{:else}
|
||||
<Tooltip label="This is a local branch that is not a virtual branch yet">
|
||||
<IconGitBranch class="h-4 w-4" />
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="text-color-2 flex-grow truncate" title={branch.name}>
|
||||
{branch.name
|
||||
.replace('refs/remotes/', '')
|
||||
.replace('origin/', '')
|
||||
.replace('refs/heads/', '')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row justify-between space-x-2 rounded p-1 pr-1">
|
||||
<div class="text-color-4 flex-grow-0 text-sm">
|
||||
<TimeAgo date={branch.lastCommitTs()} />
|
||||
</div>
|
||||
<div class="flex flex-grow-0 flex-row space-x-2">
|
||||
<Tooltip
|
||||
label="This branch has {branch.ahead()} commits not on your base branch and your base has {branch.behind} commits not on this branch yet"
|
||||
>
|
||||
<div class="bg-color-3 text-color-3 rounded-lg px-2 text-sm">
|
||||
{branch.ahead()} / {branch.behind}
|
||||
</div>
|
||||
</Tooltip>
|
||||
{#await branch.isMergeable then isMergeable}
|
||||
{#if !isMergeable}
|
||||
<div class="font-bold text-red-500" title="Can't be merged">!</div>
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
<div
|
||||
class="isolate flex flex-grow justify-end -space-x-2 overflow-hidden transition duration-300 ease-in-out hover:space-x-1 hover:transition hover:ease-in"
|
||||
>
|
||||
{#each branch.authors() as author}
|
||||
<img
|
||||
class="relative z-30 inline-block h-4 w-4 rounded-full ring-1 ring-white dark:ring-black"
|
||||
title="Gravatar for {author.email}"
|
||||
alt="Gravatar for {author.email}"
|
||||
srcset="{author.gravatarUrl} 2x"
|
||||
width="100"
|
||||
height="100"
|
||||
on:error
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<Scrollbar viewport={rbViewport} contents={rbContents} width="0.5rem" />
|
||||
</div>
|
||||
<RemoteBranches
|
||||
on:scroll={onScroll}
|
||||
on:selection={(e) => select(e.detail.branch, e.detail.i, e.detail.offset)}
|
||||
{remoteBranchStore}
|
||||
{peekTrayExpanded}
|
||||
{selectedItem}
|
||||
></RemoteBranches>
|
||||
</div>
|
||||
|
||||
<Modal width="small" bind:this={applyConflictedModal}>
|
||||
|
Loading…
Reference in New Issue
Block a user