Merge pull request #772 from gitbutlerapp/return-commits-with-base-branch

Return commit list with base branch data
This commit is contained in:
Scott Chacon 2023-07-21 11:57:38 +02:00 committed by GitHub
commit 5732a1804f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 223 additions and 205 deletions

View File

@ -36,7 +36,7 @@ impl super::RunCommand for Setup {
Some(index) => {
println!("Setting target to: {}", items[index].red());
app.gb_repository()
.set_target_branch(&app.project_repository(), &items[index])
.set_base_branch(&app.project_repository(), &items[index])
.context("failed to set target branch")?;
}
None => println!("User did not select anything"),

View File

@ -261,22 +261,22 @@ impl App {
.list_by_project_id_session_id(project_id, session_id, paths)
}
pub fn get_target_data(
pub fn get_base_branch_data(
&self,
project_id: &str,
) -> Result<Option<virtual_branches::target::Target>> {
) -> Result<Option<virtual_branches::BaseBranch>> {
let gb_repository = self.gb_repository(project_id)?;
let project = self.gb_project(project_id)?;
let project_repository = project_repository::Repository::open(&project)
.context("failed to open project repository")?;
virtual_branches::get_target_data(&gb_repository, &project_repository)
virtual_branches::get_base_branch_data(&gb_repository, &project_repository)
}
pub async fn set_target_branch(
pub async fn set_base_branch(
&self,
project_id: &str,
target_branch: &str,
) -> Result<Option<virtual_branches::target::Target>> {
) -> Result<Option<virtual_branches::BaseBranch>> {
let gb_repository = self.gb_repository(project_id)?;
let project = self.gb_project(project_id)?;
let project_repository = project_repository::Repository::open(&project)
@ -288,11 +288,11 @@ impl App {
.or_insert_with(|| Semaphore::new(1));
let _permit = semaphore.acquire().await?;
let target = gb_repository.set_target_branch(&project_repository, target_branch)?;
let target = gb_repository.set_base_branch(&project_repository, target_branch)?;
Ok(Some(target))
}
pub async fn update_branch_target(&self, project_id: &str) -> Result<()> {
pub async fn update_base_branch(&self, project_id: &str) -> Result<()> {
let gb_repository = self.gb_repository(project_id)?;
let project = self.gb_project(project_id)?;
let project_repository = project_repository::Repository::open(&project)
@ -304,7 +304,7 @@ impl App {
.or_insert_with(|| Semaphore::new(1));
let _permit = semaphore.acquire().await?;
virtual_branches::update_branch_target(&gb_repository, &project_repository)?;
virtual_branches::update_base_branch(&gb_repository, &project_repository)?;
Ok(())
}

View File

@ -11,7 +11,10 @@ use filetime::FileTime;
use sha2::{Digest, Sha256};
use uuid::Uuid;
use crate::{fs, projects, users, virtual_branches};
use crate::{
fs, projects, users,
virtual_branches::{self},
};
use crate::{
project_repository,
@ -549,11 +552,11 @@ impl Repository {
self.session_path().join("wd")
}
pub fn set_target_branch(
pub fn set_base_branch(
&self,
project_repository: &project_repository::Repository,
target_branch: &str,
) -> Result<virtual_branches::target::Target> {
) -> Result<virtual_branches::BaseBranch> {
let repo = &project_repository.git_repository;
// lookup a branch by name
@ -604,7 +607,6 @@ impl Repository {
remote_name: remote.name().unwrap().to_string(),
remote_url: remote_url.to_string(),
sha: commit_oid,
behind: 0,
};
let target_writer = virtual_branches::target::Writer::new(self);
@ -635,8 +637,8 @@ impl Repository {
}
virtual_branches::update_gitbutler_integration(self, project_repository)?;
Ok(target)
let base = virtual_branches::target_to_base_branch(project_repository, &target)?;
Ok(base)
}
pub fn git_signatures(&self) -> Result<(git2::Signature<'_>, git2::Signature<'_>)> {

View File

@ -681,25 +681,25 @@ async fn fetch_from_target(handle: tauri::AppHandle, project_id: &str) -> Result
#[timed(duration(printer = "debug!"))]
#[tauri::command(async)]
async fn get_target_data(
async fn get_base_branch_data(
handle: tauri::AppHandle,
project_id: &str,
) -> Result<Option<virtual_branches::target::Target>, Error> {
) -> Result<Option<virtual_branches::BaseBranch>, Error> {
let app = handle.state::<app::App>();
let target = app.get_target_data(project_id)?;
let target = app.get_base_branch_data(project_id)?;
Ok(target)
}
#[timed(duration(printer = "debug!"))]
#[tauri::command(async)]
async fn set_target_branch(
async fn set_base_branch(
handle: tauri::AppHandle,
project_id: &str,
branch: &str,
) -> Result<virtual_branches::target::Target, Error> {
) -> Result<virtual_branches::BaseBranch, Error> {
let app = handle.state::<app::App>();
let target = app
.set_target_branch(project_id, branch)
.set_base_branch(project_id, branch)
.await?
.context("failed to get target data")?;
Ok(target)
@ -707,9 +707,9 @@ async fn set_target_branch(
#[timed(duration(printer = "debug!"))]
#[tauri::command(async)]
async fn update_branch_target(handle: tauri::AppHandle, project_id: &str) -> Result<(), Error> {
async fn update_base_branch(handle: tauri::AppHandle, project_id: &str) -> Result<(), Error> {
let app = handle.state::<app::App>();
app.update_branch_target(project_id).await?;
app.update_base_branch(project_id).await?;
Ok(())
}
@ -944,9 +944,9 @@ fn main() {
list_virtual_branches,
create_virtual_branch,
commit_virtual_branch,
get_target_data,
set_target_branch,
update_branch_target,
get_base_branch_data,
set_base_branch,
update_base_branch,
update_virtual_branch,
delete_virtual_branch,
apply_branch,

View File

@ -114,7 +114,6 @@ mod tests {
unsafe { TEST_TARGET_INDEX }
))
.unwrap(),
behind: 0,
}
}

View File

@ -63,8 +63,7 @@ pub struct VirtualBranchCommit {
pub id: String,
pub description: String,
pub created_at: u128,
pub author_name: String,
pub author_email: String,
pub author: Author,
pub is_remote: bool,
}
@ -132,7 +131,19 @@ pub struct RemoteBranch {
pub merge_conflicts: Vec<String>,
}
#[derive(Debug, Serialize, Hash, PartialEq, Eq)]
#[derive(Debug, Serialize, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct BaseBranch {
pub branch_name: String,
pub remote_name: String,
pub remote_url: String,
pub base_sha: String,
pub current_sha: String,
pub behind: u32,
pub upstream_commits: Vec<VirtualBranchCommit>,
}
#[derive(Debug, Serialize, Hash, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Author {
pub name: String,
@ -825,22 +836,7 @@ pub fn list_virtual_branches(
.log(branch.head, default_target.sha)
.context(format!("failed to get log for branch {}", branch.name))?
{
let timestamp = commit.time().seconds() as u128;
let signature = commit.author();
let name = signature.name().unwrap().to_string();
let email = signature.email().unwrap().to_string();
let message = commit.message().unwrap().to_string();
let sha = commit.id().to_string();
let is_remote = upstream_commits.contains_key(&commit.id());
let commit = VirtualBranchCommit {
id: sha,
created_at: timestamp * 1000,
author_name: name,
author_email: email,
description: message,
is_remote,
};
let commit = commit_to_vbranch_commit(&commit, Some(&upstream_commits))?;
commits.push(commit);
}
@ -894,6 +890,31 @@ pub fn list_virtual_branches(
Ok(branches)
}
pub fn commit_to_vbranch_commit(
commit: &git2::Commit,
upstream_commits: Option<&HashMap<git2::Oid, bool>>,
) -> Result<VirtualBranchCommit> {
let timestamp = commit.time().seconds() as u128;
let signature = commit.author();
let message = commit.message().unwrap().to_string();
let sha = commit.id().to_string();
let is_remote = match upstream_commits {
Some(commits) => commits.contains_key(&commit.id()),
None => true,
};
let commit = VirtualBranchCommit {
id: sha,
created_at: timestamp * 1000,
author: Author::from(signature),
description: message,
is_remote,
};
Ok(commit)
}
pub fn create_virtual_branch_from_branch(
gb_repository: &gb_repository::Repository,
project_repository: &project_repository::Repository,
@ -1499,7 +1520,7 @@ pub fn get_status_by_branch(
// determine if what the target branch is now pointing to is mergeable with our current working directory
// merge the target branch into our current working directory
// update the target sha
pub fn update_branch_target(
pub fn update_base_branch(
gb_repository: &gb_repository::Repository,
project_repository: &project_repository::Repository,
) -> Result<()> {
@ -2154,10 +2175,10 @@ pub fn push(
.map_err(Error::Other)
}
pub fn get_target_data(
pub fn get_base_branch_data(
gb_repository: &gb_repository::Repository,
project_repository: &project_repository::Repository,
) -> Result<Option<target::Target>> {
) -> Result<Option<BaseBranch>> {
let current_session = gb_repository
.get_or_create_current_session()
.context("failed to get or create current session")?;
@ -2165,15 +2186,39 @@ pub fn get_target_data(
.context("failed to open current session")?;
match get_default_target(&current_session_reader)? {
None => Ok(None),
Some(mut target) => {
let repo = &project_repository.git_repository;
let branch = repo.find_branch(&target.branch_name, git2::BranchType::Remote)?;
let commit = branch.get().peel_to_commit()?;
let oid = commit.id();
target.behind = project_repository
.distance(oid, target.sha)
.context(format!("failed to get behind for {}", target.branch_name))?;
Ok(Some(target))
Some(target) => {
let base = target_to_base_branch(&project_repository, &target)?;
Ok(Some(base))
}
}
}
pub fn target_to_base_branch(
project_repository: &project_repository::Repository,
target: &target::Target,
) -> Result<BaseBranch> {
let repo = &project_repository.git_repository;
let branch = repo.find_branch(&target.branch_name, git2::BranchType::Remote)?;
let commit = branch.get().peel_to_commit()?;
let oid = commit.id();
// gather a list of commits between oid and target.sha
let mut upstream_commits = vec![];
for commit in project_repository
.log(oid, target.sha)
.context(format!("failed to get log for branch {:?}", branch.name()?))?
{
let commit = commit_to_vbranch_commit(&commit, None)?;
upstream_commits.push(commit);
}
let base = BaseBranch {
branch_name: target.branch_name.clone(),
remote_name: target.remote_name.clone(),
remote_url: target.remote_url.clone(),
base_sha: target.sha.to_string(),
current_sha: oid.to_string(),
behind: upstream_commits.len() as u32,
upstream_commits,
};
Ok(base)
}

View File

@ -13,7 +13,6 @@ pub struct Target {
pub remote_name: String,
pub remote_url: String,
pub sha: git2::Oid,
pub behind: u32,
}
impl Serialize for Target {
@ -25,7 +24,6 @@ impl Serialize for Target {
state.serialize_field("branchName", &self.branch_name)?;
state.serialize_field("remoteName", &self.remote_name)?;
state.serialize_field("remoteUrl", &self.remote_url)?;
state.serialize_field("behind", &self.behind)?;
state.serialize_field("sha", &self.sha.to_string())?;
state.end()
}
@ -97,7 +95,6 @@ impl TryFrom<&dyn crate::reader::Reader> for Target {
remote_name,
remote_url,
sha,
behind: 0,
})
}
}

View File

@ -189,7 +189,6 @@ mod tests {
branch_name: "branch".to_string(),
remote_url: "remote url".to_string(),
sha: git2::Oid::from_str("fedcba9876543210fedcba9876543210fedcba98").unwrap(),
behind: 0,
};
let default_target = Target {
@ -197,7 +196,6 @@ mod tests {
branch_name: "default branch".to_string(),
remote_url: "default remote url".to_string(),
sha: git2::Oid::from_str("0123456789abcdef0123456789abcdef01234567").unwrap(),
behind: 0,
};
let branch_writer = branch::Writer::new(&gb_repo);

View File

@ -167,7 +167,6 @@ mod tests {
remote_name: "remote name".to_string(),
remote_url: "remote url".to_string(),
sha: git2::Oid::from_str("0123456789abcdef0123456789abcdef01234567").unwrap(),
behind: 0,
};
let branch_writer = branch::Writer::new(&gb_repo);
@ -260,7 +259,6 @@ mod tests {
branch_name: "branch name".to_string(),
remote_url: "remote url".to_string(),
sha: git2::Oid::from_str("0123456789abcdef0123456789abcdef01234567").unwrap(),
behind: 0,
};
let branch_writer = branch::Writer::new(&gb_repo);
@ -273,7 +271,6 @@ mod tests {
branch_name: "updated branch name".to_string(),
remote_url: "updated remote url".to_string(),
sha: git2::Oid::from_str("fedcba9876543210fedcba9876543210fedcba98").unwrap(),
behind: 0,
};
target_writer.write(&branch.id, &updated_target)?;

View File

@ -79,7 +79,6 @@ fn test_commit_on_branch_then_change_file_then_get_status() -> Result<()> {
branch_name: "master".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -163,7 +162,6 @@ fn test_track_binary_files() -> Result<()> {
branch_name: "master".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -260,7 +258,6 @@ fn test_create_branch_with_ownership() -> Result<()> {
branch_name: "master".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -327,7 +324,6 @@ fn test_create_branch_in_the_middle() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -381,7 +377,6 @@ fn test_create_branch_no_arguments() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -425,7 +420,6 @@ fn test_hunk_expantion() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -514,7 +508,6 @@ fn test_get_status_files_by_branch_no_hunks_no_branches() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -548,7 +541,6 @@ fn test_get_status_files_by_branch() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -609,7 +601,6 @@ fn test_updated_ownership_should_bubble_up() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -732,7 +723,6 @@ fn test_move_hunks_multiple_sources() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -849,7 +839,6 @@ fn test_move_hunks_partial_explicitly() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -946,7 +935,6 @@ fn test_add_new_hunk_to_the_end() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -978,7 +966,7 @@ fn test_add_new_hunk_to_the_end() -> Result<()> {
}
#[test]
fn test_update_branch_target_base() -> Result<()> {
fn test_update_base_branch_base() -> Result<()> {
let repository = test_repository()?;
let project = projects::Project::try_from(&repository)?;
let gb_repo_path = tempdir()?.path().to_str().unwrap().to_string();
@ -1013,7 +1001,6 @@ fn test_update_branch_target_base() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
repository.set_head("refs/heads/master")?;
@ -1072,7 +1059,7 @@ fn test_update_branch_target_base() -> Result<()> {
// update the target branch
// this should leave the work on file2, but update the contents of file1
// and the branch diff should only be on file2
update_branch_target(&gb_repo, &project_repository)?;
update_base_branch(&gb_repo, &project_repository)?;
let contents = std::fs::read(std::path::Path::new(&project.path).join(file_path))?;
assert_eq!(
@ -1090,7 +1077,7 @@ fn test_update_branch_target_base() -> Result<()> {
}
#[test]
fn test_update_branch_target_detect_integrated_branches() -> Result<()> {
fn test_update_base_branch_detect_integrated_branches() -> Result<()> {
let repository = test_repository()?;
let project = projects::Project::try_from(&repository)?;
let gb_repo_path = tempdir()?.path().to_str().unwrap().to_string();
@ -1120,7 +1107,6 @@ fn test_update_branch_target_detect_integrated_branches() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -1166,7 +1152,7 @@ fn test_update_branch_target_detect_integrated_branches() -> Result<()> {
// update the target branch
// this should notice that the trees are the same after the merge, so it should unapply the branch
update_branch_target(&gb_repo, &project_repository)?;
update_base_branch(&gb_repo, &project_repository)?;
// integrated branch should be deleted
let branches = list_virtual_branches(&gb_repo, &project_repository, true)?;
@ -1176,7 +1162,7 @@ fn test_update_branch_target_detect_integrated_branches() -> Result<()> {
}
#[test]
fn test_update_branch_target_detect_integrated_branches_with_more_work() -> Result<()> {
fn test_update_base_branch_detect_integrated_branches_with_more_work() -> Result<()> {
let repository = test_repository()?;
let project = projects::Project::try_from(&repository)?;
let gb_repo_path = tempdir()?.path().to_str().unwrap().to_string();
@ -1206,7 +1192,6 @@ fn test_update_branch_target_detect_integrated_branches_with_more_work() -> Resu
remote_name: "origin".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -1246,7 +1231,7 @@ fn test_update_branch_target_detect_integrated_branches_with_more_work() -> Resu
// update the target branch
// this should notice that the trees are the same after the merge, but there are files on the branch, so do a merge and then leave the files there
update_branch_target(&gb_repo, &project_repository)?;
update_base_branch(&gb_repo, &project_repository)?;
// there should be a new vbranch created, but nothing is on it
let branches = list_virtual_branches(&gb_repo, &project_repository, true)?;
@ -1302,7 +1287,6 @@ fn test_update_target_with_conflicts_in_vbranches() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -1546,7 +1530,7 @@ fn test_update_target_with_conflicts_in_vbranches() -> Result<()> {
)?;
// update the target branch
update_branch_target(&gb_repo, &project_repository)?;
update_base_branch(&gb_repo, &project_repository)?;
let branches = list_virtual_branches(&gb_repo, &project_repository, true)?;
@ -1623,7 +1607,6 @@ fn test_apply_unapply_branch() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -1730,7 +1713,6 @@ fn test_apply_unapply_added_deleted_files() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -1821,7 +1803,6 @@ fn test_detect_mergeable_branch() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -2000,7 +1981,6 @@ fn test_detect_remote_commits() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "http://origin.com/project".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -2101,7 +2081,6 @@ fn test_create_vbranch_from_remote_branch() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "http://origin.com/project".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
repository.remote("origin", "http://origin.com/project")?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -2257,7 +2236,6 @@ fn test_create_vbranch_from_behind_remote_branch() -> Result<()> {
branch_name: "master".to_string(),
remote_url: "http://origin.com/project".to_string(),
sha: upstream_commit,
behind: 0,
})?;
repository.remote("origin", "http://origin.com/project")?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -2363,7 +2341,6 @@ fn test_partial_commit() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "origin".to_string(),
sha: repository.head().unwrap().target().unwrap(),
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -2506,7 +2483,6 @@ fn test_commit_add_and_delete_files() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "origin".to_string(),
sha: commit1_oid,
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -2583,7 +2559,6 @@ fn test_commit_executable_and_symlinks() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "origin".to_string(),
sha: commit1_oid,
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -2723,7 +2698,6 @@ fn test_apply_out_of_date_vbranch() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "http://origin.com/project".to_string(),
sha: base_commit,
behind: 0,
})?;
repository.remote("origin", "http://origin.com/project")?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -2801,7 +2775,7 @@ fn test_apply_out_of_date_vbranch() -> Result<()> {
assert_eq!(contents, "line1\nline2\nline3\nline4\n");
// update target, this will update the wd and add an empty default branch
update_branch_target(&gb_repo, &project_repository)?;
update_base_branch(&gb_repo, &project_repository)?;
// updated the file
let contents = std::fs::read_to_string(std::path::Path::new(&project.path).join(file_path))?;
@ -2866,7 +2840,6 @@ fn test_apply_out_of_date_conflicting_vbranch() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "http://origin.com/project".to_string(),
sha: base_commit,
behind: 0,
})?;
repository.remote("origin", "http://origin.com/project")?;
update_gitbutler_integration(&gb_repo, &project_repository)?;
@ -2945,7 +2918,7 @@ fn test_apply_out_of_date_conflicting_vbranch() -> Result<()> {
assert_eq!(contents, "line1\nline2\nline3\nline4\n");
// update target, this will update the wd and add an empty default branch
update_branch_target(&gb_repo, &project_repository)?;
update_base_branch(&gb_repo, &project_repository)?;
// updated the file
let contents = std::fs::read_to_string(std::path::Path::new(&project.path).join(file_path))?;
@ -3044,7 +3017,6 @@ fn test_apply_conflicting_vbranch() -> Result<()> {
remote_name: "origin".to_string(),
remote_url: "http://origin.com/project".to_string(),
sha: base_commit,
behind: 0,
})?;
update_gitbutler_integration(&gb_repo, &project_repository)?;

View File

@ -221,7 +221,6 @@ mod test {
unsafe { TEST_TARGET_INDEX }
))
.unwrap(),
behind: 0,
}
}

View File

@ -1,7 +1,7 @@
import type { Refreshable } from './branchStoresCache';
import type { Readable } from '@square/svelte-store';
import type { Loadable } from 'svelte-loadable-store';
import type { Branch, BranchData, Target } from './types';
import type { Branch, BranchData, BaseBranch } from './types';
import { toasts } from '$lib';
import { invoke } from '$lib/ipc';
@ -12,12 +12,12 @@ export class BranchController {
readonly projectId: string,
readonly virtualBranchStore: Refreshable & Readable<Loadable<Branch[]>>,
readonly remoteBranchStore: Refreshable & Readable<Loadable<BranchData[]>>,
readonly targetBranchStore: Refreshable & Readable<Loadable<Target | null>>
readonly targetBranchStore: Refreshable & Readable<Loadable<BaseBranch | null>>
) {}
async setTarget(branch: string) {
try {
await invoke<Target>('set_target_branch', { projectId: this.projectId, branch });
await invoke<BaseBranch>('set_base_branch', { projectId: this.projectId, branch });
await Promise.all([
this.virtualBranchStore.refresh(),
this.remoteBranchStore.refresh(),
@ -119,9 +119,9 @@ export class BranchController {
}
}
async updateBranchTarget() {
async updateBaseBranch() {
try {
await invoke<object>('update_branch_target', { projectId: this.projectId });
await invoke<object>('update_base_branch', { projectId: this.projectId });
await Promise.all([
this.remoteBranchStore.refresh(),
this.virtualBranchStore.refresh(),

View File

@ -4,7 +4,7 @@ import { Operation } from '$lib/api';
import type { Readable } from '@square/svelte-store';
import { deltas, git } from '$lib/api/ipc';
import { stores } from '$lib';
import { Target, Branch, BranchData } from './types';
import { BaseBranch, Branch, BranchData } from './types';
import { plainToInstance } from 'class-transformer';
import { invoke } from '$lib/ipc';
@ -15,7 +15,7 @@ export interface Refreshable {
export class BranchStoresCache {
virtualBranchStores: Map<string, Refreshable & Readable<Loadable<Branch[]>>> = new Map();
remoteBranchStores: Map<string, Refreshable & Readable<Loadable<BranchData[]>>> = new Map();
targetBranchStores: Map<string, Refreshable & Readable<Loadable<Target | null>>> = new Map();
targetBranchStores: Map<string, Refreshable & Readable<Loadable<BaseBranch | null>>> = new Map();
getVirtualBranchStore(projectId: string) {
const cachedStore = this.virtualBranchStores.get(projectId);
@ -86,21 +86,21 @@ export class BranchStoresCache {
return refreshableStore;
}
getTargetBranchStore(projectId: string) {
getBaseBranchStore(projectId: string) {
const cachedStore = this.targetBranchStores.get(projectId);
if (cachedStore) {
return cachedStore;
}
const writableStore = writable(getTargetData({ projectId }), (set) => {
const writableStore = writable(getBaseBranchData({ projectId }), (set) => {
git.fetches.subscribe({ projectId }, () => {
getTargetData({ projectId }).then(set);
getBaseBranchData({ projectId }).then(set);
});
});
const refreshableStore = {
subscribe: writableStore.subscribe,
refresh: async () => {
const newTarget = await getTargetData({ projectId });
return writableStore.set({ isLoading: false, value: newTarget });
const newBaseBranch = await getBaseBranchData({ projectId });
return writableStore.set({ isLoading: false, value: newBaseBranch });
}
};
this.targetBranchStores.set(projectId, refreshableStore);
@ -116,8 +116,8 @@ export async function getRemoteBranchesData(params: { projectId: string }): Prom
return plainToInstance(BranchData, await invoke<any[]>('git_remote_branches_data', params));
}
export async function getTargetData(params: { projectId: string }): Promise<Target> {
return plainToInstance(Target, invoke<any>('get_target_data', params));
export async function getBaseBranchData(params: { projectId: string }): Promise<BaseBranch> {
return plainToInstance(BaseBranch, invoke<any>('get_base_branch_data', params));
}
async function branchesWithFileContent(projectId: string, sessionId: string, branches: Branch[]) {

View File

@ -4,12 +4,12 @@
* There are three interesting data types coming from the rust IPC api:
* - Branch, representing a virtual branch
* - BranchData, representing a remote branch
* - Target, representing a target remote branch
* - BaseBranch, representing a target base remote branch
*
* The three types are obtained as reactive stores from the BranchStoresCache's methods:
* - getVirtualBranchStore - List of Branch (virtual branches)
* - getRemoteBranchStore - List of BranchData (remote branches)
* - getTargetBranchStore - Target (single target branch)
* - getBaseBranchStore - BaseBranch (single target branch)
*
* BranchController is a class where all virtual branch operations are performed
* This class gets the three stores injected at construction so that any related updates can be peformed
@ -20,6 +20,6 @@
* so that it can take advantage of caching, making project navigation quicker.
* - Create the BranchController at the level of a specific project and inject it to components that need it.
*/
export { Branch, File, Hunk, Commit, BranchData, Target } from './types';
export { Branch, File, Hunk, Commit, BranchData, BaseBranch } from './types';
export { BranchStoresCache } from './branchStoresCache';
export { BranchController } from './branchController';

View File

@ -42,8 +42,7 @@ export class Branch {
export class Commit {
id!: string;
authorEmail!: string;
authorName!: string;
author!: Author;
description!: string;
@Transform((obj) => new Date(obj.value))
createdAt!: Date;
@ -70,10 +69,12 @@ export class BranchData {
mergeConflicts!: string[];
}
export class Target {
sha!: string;
export class BaseBranch {
branchName!: string;
remoteName!: string;
remoteUrl!: string;
baseSha!: string;
currentSha!: string;
behind!: number;
upstreamCommits!: Commit[];
}

View File

@ -46,7 +46,7 @@
{#if target}
<div class="flex w-full max-w-full" role="group" on:dragover|preventDefault>
<Tray {branches} {target} {remoteBranches} />
<Tray {branches} {remoteBranches} />
<div
class="z-50 -ml-[0.250rem] w-[0.250rem] shrink-0 cursor-col-resize hover:bg-orange-200 dark:bg-dark-1000 dark:hover:bg-orange-700"
draggable="true"
@ -60,7 +60,7 @@
/>
<div class="flex w-full flex-col overflow-x-hidden">
<Board {branches} {projectId} projectPath={project.path} {target} />
<BottomPanel />
<BottomPanel {target} />
</div>
</div>
{:else}

View File

@ -14,7 +14,7 @@ export async function load({ parent, params }: PageLoadEvent) {
const { branchStoresCache } = await parent();
const vbranchStore = branchStoresCache.getVirtualBranchStore(projectId);
const remoteBranchStore = branchStoresCache.getRemoteBranchStore(projectId);
const targetBranchStore = branchStoresCache.getTargetBranchStore(projectId);
const targetBranchStore = branchStoresCache.getBaseBranchStore(projectId);
return {
projectId,

View File

@ -1,7 +1,7 @@
<script lang="ts" async="true">
import Lane from './BranchLane.svelte';
import NewBranchDropZone from './NewBranchDropZone.svelte';
import type { Branch, Target } from '$lib/vbranches';
import type { Branch, BaseBranch } from '$lib/vbranches';
import { dzHighlight } from './dropZone';
import type { BranchController } from '$lib/vbranches';
import { getContext } from 'svelte';
@ -10,7 +10,7 @@
export let projectId: string;
export let projectPath: string;
export let branches: Branch[];
export let target: Target;
export let target: BaseBranch;
const branchController = getContext<BranchController>(BRANCH_CONTROLLER_KEY);

View File

@ -1,10 +1,25 @@
<script lang="ts">
import { Button, Modal } from '$lib/components';
import { slide } from 'svelte/transition';
import { IconTriangleUp, IconTriangleDown } from '$lib/icons';
import type { BaseBranch } from '$lib/vbranches';
import type { BranchController } from '$lib/vbranches';
import { IconRefresh } from '$lib/icons';
import { BRANCH_CONTROLLER_KEY } from '$lib/vbranches/branchController';
import { getContext } from 'svelte';
export let target: BaseBranch;
let updateTargetModal: Modal;
const branchController = getContext<BranchController>(BRANCH_CONTROLLER_KEY);
$: console.log(target);
let shown = false;
$: behindMessage = target.behind > 0 ? `behind ${target.behind}` : 'up-to-date';
</script>
<div class="flex border-t border-light-400 dark:border-dark-600">
<div class="flex border-t border-light-400 p-2 dark:border-dark-600">
<div class="ml-4 flex flex-col">
<div
role="button"
@ -18,24 +33,72 @@
{:else}
<IconTriangleUp />
{/if}
<span class="text-sm font-bold uppercase"> Common base </span>
<div class="flex flex-row justify-between space-x-2">
<div class="font-bold uppercase">Common base</div>
<div class="flex-grow pb-1 font-bold" title={behindMessage}>{target.branchName}</div>
<div class="pb-1">{target.behind > 0 ? `behind ${target.behind}` : 'up-to-date'}</div>
<div class="flex-shrink-0 text-light-700 dark:text-dark-100" title={behindMessage}>
{#if target.behind == 0}
<button
class="p-0 hover:bg-light-200 disabled:text-light-300 dark:hover:bg-dark-800 disabled:dark:text-dark-300"
on:click={() => branchController.fetchFromTarget()}
title="click to fetch"
>
<IconRefresh />
</button>
{:else}
<button
class="p-0 disabled:text-light-300 disabled:dark:text-dark-300"
on:click={updateTargetModal.show}
disabled={target.behind == 0}
title={target.behind > 0 ? 'click to update target' : 'already up-to-date'}
>
<IconRefresh />
</button>
{/if}
</div>
</div>
</div>
{#if shown}
<div class="h-64 py-2" transition:slide={{ duration: 150 }}>
<div class="">
<div>We're no strangers to love You know the rules and so do I</div>
<div>
A full commitment's what I'm thinking of You wouldn't get this from any other guy
</div>
<div>I just wanna tell you how I'm feeling Gotta make you understand</div>
<div>Never gonna give you up</div>
<div>Never gonna let you down</div>
<div>Never gonna run around and desert you</div>
<div>Never gonna make you cry</div>
<div>Never gonna say goodbye</div>
<div>Never gonna tell a lie and hurt you</div>
<div>Upstream Commits:</div>
<div class="flex flex-col">
{#each target.upstreamCommits as commit}
<div class="flex flex-row space-x-2">
<img
class="relative z-30 inline-block h-4 w-4 rounded-full ring-1 ring-white dark:ring-black"
title="Gravatar for {commit.author.email}"
alt="Gravatar for {commit.author.email}"
srcset="{commit.author.gravatarUrl} 2x"
width="100"
height="100"
on:error
/>
<div>{commit.description.substring(0, 50)}</div>
</div>
{/each}
</div>
</div>
{/if}
</div>
</div>
<!-- Confirm target update modal -->
<Modal width="small" bind:this={updateTargetModal}>
<svelte:fragment slot="title">Update target</svelte:fragment>
<p>You are about to update the base branch.</p>
<svelte:fragment slot="controls" let:close>
<Button height="small" kind="outlined" on:click={close}>Cancel</Button>
<Button
height="small"
color="purple"
on:click={() => {
branchController.updateBaseBranch();
close();
}}
>
Update
</Button>
</svelte:fragment>
</Modal>

View File

@ -1,6 +1,6 @@
<script lang="ts">
import { toasts } from '$lib';
import type { Commit, File, Target } from '$lib/vbranches';
import type { Commit, File, BaseBranch } from '$lib/vbranches';
import { getContext, onMount } from 'svelte';
import { IconAISparkles, IconBranch } from '$lib/icons';
import { Button, Link, Modal } from '$lib/components';
@ -25,7 +25,7 @@
export let projectId: string;
export let order: number;
export let conflicted: boolean;
export let target: Target;
export let target: BaseBranch;
const branchController = getContext<BranchController>(BRANCH_CONTROLLER_KEY);
@ -110,7 +110,7 @@
.join('');
}
function url(target: Target, branchName: string) {
function url(target: BaseBranch, branchName: string) {
const baseBranchName = target.branchName.split('/')[1];
const repoBaseUrl = target.remoteUrl
.replace(':', '/')

View File

@ -12,7 +12,7 @@
{commit.description}
</div>
<div class="flex text-light-700">
<div class="flex-grow">{commit.authorName}</div>
<div class="flex-grow">{commit.author.name}</div>
<div>{formatDistanceToNow(commit.createdAt)} ago</div>
</div>
</div>

View File

@ -1,8 +1,8 @@
<script lang="ts">
import { Button, Checkbox, Modal } from '$lib/components';
import type { Branch, BranchData, Target } from '$lib/vbranches';
import type { Branch, BranchData, BaseBranch } from '$lib/vbranches';
import { formatDistanceToNow } from 'date-fns';
import { IconGitBranch, IconRemote, IconRefresh } from '$lib/icons';
import { IconGitBranch, IconRemote } from '$lib/icons';
import { IconTriangleDown, IconTriangleUp } from '$lib/icons';
import { accordion } from './accordion';
import PopupMenu from '$lib/components/PopupMenu/PopupMenu.svelte';
@ -12,7 +12,6 @@
import { getContext } from 'svelte';
import { BRANCH_CONTROLLER_KEY } from '$lib/vbranches/branchController';
export let target: Target;
export let branches: Branch[];
export let remoteBranches: BranchData[];
@ -24,12 +23,9 @@
let yourBranchContextMenu: PopupMenu;
let remoteBranchContextMenu: PopupMenu;
let updateTargetModal: Modal;
let applyConflictedModal: Modal;
let deleteBranchModal: Modal;
$: behindMessage = target.behind > 0 ? `behind ${target.behind}` : 'up-to-date';
function toggleBranch(branch: Branch) {
if (branch.active) {
branchController.unapplyBranch(branch.id);
@ -64,37 +60,6 @@
class="tray-scroll w-80 min-w-[216px] shrink-0 cursor-default resize-x overflow-y-scroll overscroll-y-none border-r border-light-400 bg-white text-light-800 dark:border-dark-600 dark:bg-dark-900 dark:text-dark-100"
style:width={$userSettings.trayWidth ? `${$userSettings.trayWidth}px` : null}
>
<!-- Target branch -->
<div class="pl-2 pr-4 pt-2 text-light-700 dark:bg-dark-700 dark:text-dark-200">Base branch</div>
<div
class="flex w-full flex-row items-center justify-between border-b border-light-400 pb-1 pl-2 pr-1 text-light-900 dark:border-dark-500 dark:bg-dark-700 dark:text-dark-100"
>
<div class="flex-grow pb-1 font-bold" title={behindMessage}>{target.branchName}</div>
<div class="flex items-center gap-1">
<div class="pb-1">{target.behind > 0 ? `behind ${target.behind}` : 'up-to-date'}</div>
<div class="flex-shrink-0 text-light-700 dark:text-dark-100" title={behindMessage}>
{#if target.behind == 0}
<button
class="p-0 hover:bg-light-200 disabled:text-light-300 dark:hover:bg-dark-800 disabled:dark:text-dark-300"
on:click={() => branchController.fetchFromTarget()}
title="click to fetch"
>
<IconRefresh />
</button>
{:else}
<button
class="p-0 disabled:text-light-300 disabled:dark:text-dark-300"
on:click={updateTargetModal.show}
disabled={target.behind == 0}
title={target.behind > 0 ? 'click to update target' : 'already up-to-date'}
>
<IconRefresh />
</button>
{/if}
</div>
</div>
</div>
<!-- Your branches -->
<div
class="flex items-center justify-between border-b border-light-400 bg-light-100 px-2 py-1 pr-1 dark:border-dark-600 dark:bg-dark-800"
@ -253,26 +218,6 @@
</svelte:fragment>
</Modal>
<!-- Confirm target update modal -->
<Modal width="small" bind:this={updateTargetModal}>
<svelte:fragment slot="title">Update target</svelte:fragment>
<p>You are about to update the base branch.</p>
<svelte:fragment slot="controls" let:close>
<Button height="small" kind="outlined" on:click={close}>Cancel</Button>
<Button
height="small"
color="purple"
on:click={() => {
branchController.updateBranchTarget();
close();
}}
>
Update
</Button>
</svelte:fragment>
</Modal>
<!-- Delete branch confirmation modal -->
<Modal width="small" bind:this={deleteBranchModal} let:item>