mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-08-16 15:50:37 +03:00
move base branch implementation to branches crate
This commit is contained in:
parent
41f28383a0
commit
9eaab28a48
@ -1,6 +1,6 @@
|
||||
use futures::future::join_all;
|
||||
|
||||
use crate::{VirtualBranch, VirtualBranchCommit};
|
||||
use crate::{base::BaseBranch, VirtualBranch, VirtualBranchCommit};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Proxy {
|
||||
@ -44,4 +44,28 @@ impl Proxy {
|
||||
..commit
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn proxy_base_branch(&self, base_branch: BaseBranch) -> BaseBranch {
|
||||
BaseBranch {
|
||||
recent_commits: join_all(
|
||||
base_branch
|
||||
.clone()
|
||||
.recent_commits
|
||||
.into_iter()
|
||||
.map(|commit| self.core_proxy.proxy_remote_commit(commit))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.await,
|
||||
upstream_commits: join_all(
|
||||
base_branch
|
||||
.clone()
|
||||
.upstream_commits
|
||||
.into_iter()
|
||||
.map(|commit| self.core_proxy.proxy_remote_commit(commit))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.await,
|
||||
..base_branch.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
633
crates/gitbutler-branch/src/base.rs
Normal file
633
crates/gitbutler-branch/src/base.rs
Normal file
@ -0,0 +1,633 @@
|
||||
use std::{path::Path, time};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use git2::Index;
|
||||
use serde::Serialize;
|
||||
|
||||
use gitbutler_core::virtual_branches::integration::{
|
||||
get_workspace_head, update_gitbutler_integration, GITBUTLER_INTEGRATION_REFERENCE,
|
||||
};
|
||||
use gitbutler_core::virtual_branches::{
|
||||
branch, convert_to_real_branch, target, BranchId, RemoteCommit, VirtualBranchHunk,
|
||||
VirtualBranchesHandle,
|
||||
};
|
||||
use gitbutler_core::{error::Marker, git::RepositoryExt, rebase::cherry_rebase};
|
||||
use gitbutler_core::{
|
||||
git::{self, diff},
|
||||
project_repository::{self, LogUntil},
|
||||
projects::FetchResult,
|
||||
virtual_branches::branch::BranchOwnershipClaims,
|
||||
};
|
||||
|
||||
#[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 push_remote_name: Option<String>,
|
||||
pub push_remote_url: String,
|
||||
#[serde(with = "gitbutler_core::serde::oid")]
|
||||
pub base_sha: git2::Oid,
|
||||
#[serde(with = "gitbutler_core::serde::oid")]
|
||||
pub current_sha: git2::Oid,
|
||||
pub behind: usize,
|
||||
pub upstream_commits: Vec<RemoteCommit>,
|
||||
pub recent_commits: Vec<RemoteCommit>,
|
||||
pub last_fetched_ms: Option<u128>,
|
||||
}
|
||||
|
||||
pub fn get_base_branch_data(
|
||||
project_repository: &project_repository::Repository,
|
||||
) -> Result<BaseBranch> {
|
||||
let target = default_target(&project_repository.project().gb_dir())?;
|
||||
let base = target_to_base_branch(project_repository, &target)?;
|
||||
Ok(base)
|
||||
}
|
||||
|
||||
fn go_back_to_integration(
|
||||
project_repository: &project_repository::Repository,
|
||||
default_target: &target::Target,
|
||||
) -> Result<BaseBranch> {
|
||||
let statuses = project_repository
|
||||
.repo()
|
||||
.statuses(Some(
|
||||
git2::StatusOptions::new()
|
||||
.show(git2::StatusShow::IndexAndWorkdir)
|
||||
.include_untracked(true),
|
||||
))
|
||||
.context("failed to get status")?;
|
||||
if !statuses.is_empty() {
|
||||
return Err(anyhow!("current HEAD is dirty")).context(Marker::ProjectConflict);
|
||||
}
|
||||
|
||||
let vb_state = project_repository.project().virtual_branches();
|
||||
let all_virtual_branches = vb_state
|
||||
.list_branches()
|
||||
.context("failed to read virtual branches")?;
|
||||
|
||||
let applied_virtual_branches = all_virtual_branches
|
||||
.iter()
|
||||
.filter(|branch| branch.applied)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let target_commit = project_repository
|
||||
.repo()
|
||||
.find_commit(default_target.sha)
|
||||
.context("failed to find target commit")?;
|
||||
|
||||
let base_tree = target_commit
|
||||
.tree()
|
||||
.context("failed to get base tree from commit")?;
|
||||
let mut final_tree = target_commit
|
||||
.tree()
|
||||
.context("failed to get base tree from commit")?;
|
||||
for branch in &applied_virtual_branches {
|
||||
// merge this branches tree with our tree
|
||||
let branch_head = project_repository
|
||||
.repo()
|
||||
.find_commit(branch.head)
|
||||
.context("failed to find branch head")?;
|
||||
let branch_tree = branch_head
|
||||
.tree()
|
||||
.context("failed to get branch head tree")?;
|
||||
let mut result = project_repository
|
||||
.repo()
|
||||
.merge_trees(&base_tree, &final_tree, &branch_tree, None)
|
||||
.context("failed to merge")?;
|
||||
let final_tree_oid = result
|
||||
.write_tree_to(project_repository.repo())
|
||||
.context("failed to write tree")?;
|
||||
final_tree = project_repository
|
||||
.repo()
|
||||
.find_tree(final_tree_oid)
|
||||
.context("failed to find written tree")?;
|
||||
}
|
||||
|
||||
project_repository
|
||||
.repo()
|
||||
.checkout_tree_builder(&final_tree)
|
||||
.force()
|
||||
.checkout()
|
||||
.context("failed to checkout tree")?;
|
||||
|
||||
let base = target_to_base_branch(project_repository, default_target)?;
|
||||
update_gitbutler_integration(&vb_state, project_repository)?;
|
||||
Ok(base)
|
||||
}
|
||||
|
||||
pub fn set_base_branch(
|
||||
project_repository: &project_repository::Repository,
|
||||
target_branch_ref: &git::RemoteRefname,
|
||||
) -> Result<BaseBranch> {
|
||||
let repo = project_repository.repo();
|
||||
|
||||
// if target exists, and it is the same as the requested branch, we should go back
|
||||
if let Ok(target) = default_target(&project_repository.project().gb_dir()) {
|
||||
if target.branch.eq(target_branch_ref) {
|
||||
return go_back_to_integration(project_repository, &target);
|
||||
}
|
||||
}
|
||||
|
||||
// lookup a branch by name
|
||||
let target_branch = match repo.find_branch_by_refname(&target_branch_ref.clone().into()) {
|
||||
Ok(branch) => branch,
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
.ok_or(anyhow!("remote branch '{}' not found", target_branch_ref))?;
|
||||
|
||||
let remote = repo
|
||||
.find_remote(target_branch_ref.remote())
|
||||
.context(format!(
|
||||
"failed to find remote for branch {}",
|
||||
target_branch.get().name().unwrap()
|
||||
))?;
|
||||
let remote_url = remote.url().context(format!(
|
||||
"failed to get remote url for {}",
|
||||
target_branch_ref.remote()
|
||||
))?;
|
||||
|
||||
let target_branch_head = target_branch.get().peel_to_commit().context(format!(
|
||||
"failed to peel branch {} to commit",
|
||||
target_branch.get().name().unwrap()
|
||||
))?;
|
||||
|
||||
let current_head = repo.head().context("Failed to get HEAD reference")?;
|
||||
let current_head_commit = current_head
|
||||
.peel_to_commit()
|
||||
.context("Failed to peel HEAD reference to commit")?;
|
||||
|
||||
// calculate the commit as the merge-base between HEAD in project_repository and this target commit
|
||||
let target_commit_oid = repo
|
||||
.merge_base(current_head_commit.id(), target_branch_head.id())
|
||||
.context(format!(
|
||||
"Failed to calculate merge base between {} and {}",
|
||||
current_head_commit.id(),
|
||||
target_branch_head.id()
|
||||
))?;
|
||||
|
||||
let target = target::Target {
|
||||
branch: target_branch_ref.clone(),
|
||||
remote_url: remote_url.to_string(),
|
||||
sha: target_commit_oid,
|
||||
push_remote_name: None,
|
||||
};
|
||||
|
||||
let vb_state = project_repository.project().virtual_branches();
|
||||
vb_state.set_default_target(target.clone())?;
|
||||
|
||||
// TODO: make sure this is a real branch
|
||||
let head_name: git::Refname = current_head
|
||||
.name()
|
||||
.map(|name| name.parse().expect("libgit2 provides valid refnames"))
|
||||
.context("Failed to get HEAD reference name")?;
|
||||
if !head_name
|
||||
.to_string()
|
||||
.eq(&GITBUTLER_INTEGRATION_REFERENCE.to_string())
|
||||
{
|
||||
// if there are any commits on the head branch or uncommitted changes in the working directory, we need to
|
||||
// put them into a virtual branch
|
||||
|
||||
let wd_diff = diff::workdir(repo, ¤t_head_commit.id())?;
|
||||
if !wd_diff.is_empty() || current_head_commit.id() != target.sha {
|
||||
// assign ownership to the branch
|
||||
let ownership = wd_diff.iter().fold(
|
||||
BranchOwnershipClaims::default(),
|
||||
|mut ownership, (file_path, diff)| {
|
||||
for hunk in &diff.hunks {
|
||||
ownership.put(
|
||||
format!(
|
||||
"{}:{}",
|
||||
file_path.display(),
|
||||
VirtualBranchHunk::gen_id(hunk.new_start, hunk.new_lines)
|
||||
)
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
ownership
|
||||
},
|
||||
);
|
||||
|
||||
let now_ms = gitbutler_core::time::now_ms();
|
||||
|
||||
let (upstream, upstream_head) = if let git::Refname::Local(head_name) = &head_name {
|
||||
let upstream_name = target_branch_ref.with_branch(head_name.branch());
|
||||
if upstream_name.eq(target_branch_ref) {
|
||||
(None, None)
|
||||
} else {
|
||||
match repo.find_reference(&git::Refname::from(&upstream_name).to_string()) {
|
||||
Ok(upstream) => {
|
||||
let head = upstream
|
||||
.peel_to_commit()
|
||||
.map(|commit| commit.id())
|
||||
.context(format!(
|
||||
"failed to peel upstream {} to commit",
|
||||
upstream.name().unwrap()
|
||||
))?;
|
||||
Ok((Some(upstream_name), Some(head)))
|
||||
}
|
||||
Err(err) if err.code() == git2::ErrorCode::NotFound => Ok((None, None)),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
.context(format!("failed to find upstream for {}", head_name))?
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let branch = branch::Branch {
|
||||
id: BranchId::generate(),
|
||||
name: head_name.to_string().replace("refs/heads/", ""),
|
||||
notes: String::new(),
|
||||
applied: true,
|
||||
upstream,
|
||||
upstream_head,
|
||||
created_timestamp_ms: now_ms,
|
||||
updated_timestamp_ms: now_ms,
|
||||
head: current_head_commit.id(),
|
||||
tree: gitbutler_core::virtual_branches::write_tree_onto_commit(
|
||||
project_repository,
|
||||
current_head_commit.id(),
|
||||
diff::diff_files_into_hunks(wd_diff),
|
||||
)?,
|
||||
ownership,
|
||||
order: 0,
|
||||
selected_for_changes: None,
|
||||
allow_rebasing: project_repository.project().ok_with_force_push.into(),
|
||||
};
|
||||
|
||||
vb_state.set_branch(branch)?;
|
||||
}
|
||||
}
|
||||
|
||||
set_exclude_decoration(project_repository)?;
|
||||
|
||||
update_gitbutler_integration(&vb_state, project_repository)?;
|
||||
|
||||
let base = target_to_base_branch(project_repository, &target)?;
|
||||
Ok(base)
|
||||
}
|
||||
|
||||
pub fn set_target_push_remote(
|
||||
project_repository: &project_repository::Repository,
|
||||
push_remote_name: &str,
|
||||
) -> Result<()> {
|
||||
let remote = project_repository
|
||||
.repo()
|
||||
.find_remote(push_remote_name)
|
||||
.context(format!("failed to find remote {}", push_remote_name))?;
|
||||
|
||||
// if target exists, and it is the same as the requested branch, we should go back
|
||||
let mut target = default_target(&project_repository.project().gb_dir())?;
|
||||
target.push_remote_name = remote
|
||||
.name()
|
||||
.context("failed to get remote name")?
|
||||
.to_string()
|
||||
.into();
|
||||
let vb_state = project_repository.project().virtual_branches();
|
||||
vb_state.set_default_target(target)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_exclude_decoration(project_repository: &project_repository::Repository) -> Result<()> {
|
||||
let repo = project_repository.repo();
|
||||
let mut config = repo.config()?;
|
||||
config
|
||||
.set_multivar("log.excludeDecoration", "refs/gitbutler", "refs/gitbutler")
|
||||
.context("failed to set log.excludeDecoration")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn _print_tree(repo: &git2::Repository, tree: &git2::Tree) -> Result<()> {
|
||||
println!("tree id: {}", tree.id());
|
||||
for entry in tree {
|
||||
println!(
|
||||
" entry: {} {}",
|
||||
entry.name().unwrap_or_default(),
|
||||
entry.id()
|
||||
);
|
||||
// get entry contents
|
||||
let object = entry.to_object(repo).context("failed to get object")?;
|
||||
let blob = object.as_blob().context("failed to get blob")?;
|
||||
// convert content to string
|
||||
if let Ok(content) = std::str::from_utf8(blob.content()) {
|
||||
println!(" blob: {}", content);
|
||||
} else {
|
||||
println!(" blob: BINARY");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// try to update the target branch
|
||||
// this means that we need to:
|
||||
// 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_base_branch(
|
||||
project_repository: &project_repository::Repository,
|
||||
) -> anyhow::Result<Vec<git2::Branch<'_>>> {
|
||||
project_repository.assure_resolved()?;
|
||||
|
||||
// look up the target and see if there is a new oid
|
||||
let target = default_target(&project_repository.project().gb_dir())?;
|
||||
let repo = project_repository.repo();
|
||||
let target_branch = repo
|
||||
.find_branch_by_refname(&target.branch.clone().into())
|
||||
.context(format!("failed to find branch {}", target.branch))?;
|
||||
|
||||
let new_target_commit = target_branch
|
||||
.ok_or(anyhow!("failed to get branch"))?
|
||||
.get()
|
||||
.peel_to_commit()
|
||||
.context(format!("failed to peel branch {} to commit", target.branch))?;
|
||||
|
||||
let mut unapplied_branch_names: Vec<git2::Branch> = Vec::new();
|
||||
|
||||
if new_target_commit.id() == target.sha {
|
||||
return Ok(unapplied_branch_names);
|
||||
}
|
||||
|
||||
let new_target_tree = new_target_commit
|
||||
.tree()
|
||||
.context("failed to get new target commit tree")?;
|
||||
|
||||
let old_target_tree = repo.find_commit(target.sha)?.tree().context(format!(
|
||||
"failed to get old target commit tree {}",
|
||||
target.sha
|
||||
))?;
|
||||
|
||||
let vb_state = project_repository.project().virtual_branches();
|
||||
let integration_commit = get_workspace_head(&vb_state, project_repository)?;
|
||||
|
||||
// try to update every branch
|
||||
let updated_vbranches = gitbutler_core::virtual_branches::get_status_by_branch(
|
||||
project_repository,
|
||||
Some(&integration_commit),
|
||||
)?
|
||||
.0
|
||||
.into_iter()
|
||||
.map(|(branch, _)| branch)
|
||||
.map(
|
||||
|mut branch: branch::Branch| -> Result<Option<branch::Branch>> {
|
||||
let branch_tree = repo.find_tree(branch.tree)?;
|
||||
|
||||
let branch_head_commit = repo.find_commit(branch.head).context(format!(
|
||||
"failed to find commit {} for branch {}",
|
||||
branch.head, branch.id
|
||||
))?;
|
||||
let branch_head_tree = branch_head_commit.tree().context(format!(
|
||||
"failed to find tree for commit {} for branch {}",
|
||||
branch.head, branch.id
|
||||
))?;
|
||||
|
||||
let result_integrated_detected =
|
||||
|mut branch: branch::Branch| -> Result<Option<branch::Branch>> {
|
||||
// branch head tree is the same as the new target tree.
|
||||
// meaning we can safely use the new target commit as the branch head.
|
||||
|
||||
branch.head = new_target_commit.id();
|
||||
|
||||
// it also means that the branch is fully integrated into the target.
|
||||
// disconnect it from the upstream
|
||||
branch.upstream = None;
|
||||
branch.upstream_head = None;
|
||||
|
||||
let non_commited_files =
|
||||
diff::trees(project_repository.repo(), &branch_head_tree, &branch_tree)?;
|
||||
if non_commited_files.is_empty() {
|
||||
// if there are no commited files, then the branch is fully merged
|
||||
// and we can delete it.
|
||||
vb_state.remove_branch(branch.id)?;
|
||||
project_repository.delete_branch_reference(&branch)?;
|
||||
Ok(None)
|
||||
} else {
|
||||
vb_state.set_branch(branch.clone())?;
|
||||
Ok(Some(branch))
|
||||
}
|
||||
};
|
||||
|
||||
if branch_head_tree.id() == new_target_tree.id() {
|
||||
return result_integrated_detected(branch);
|
||||
}
|
||||
|
||||
// try to merge branch head with new target
|
||||
let mut branch_tree_merge_index = repo
|
||||
.merge_trees(&old_target_tree, &branch_tree, &new_target_tree, None)
|
||||
.context(format!("failed to merge trees for branch {}", branch.id))?;
|
||||
|
||||
if branch_tree_merge_index.has_conflicts() {
|
||||
// branch tree conflicts with new target, unapply branch for now. we'll handle it later, when user applies it back.
|
||||
let unapplied_real_branch =
|
||||
convert_to_real_branch(project_repository, branch.id, Default::default())?;
|
||||
|
||||
unapplied_branch_names.push(unapplied_real_branch);
|
||||
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let branch_merge_index_tree_oid =
|
||||
branch_tree_merge_index.write_tree_to(project_repository.repo())?;
|
||||
|
||||
if branch_merge_index_tree_oid == new_target_tree.id() {
|
||||
return result_integrated_detected(branch);
|
||||
}
|
||||
|
||||
if branch.head == target.sha {
|
||||
// there are no commits on the branch, so we can just update the head to the new target and calculate the new tree
|
||||
branch.head = new_target_commit.id();
|
||||
branch.tree = branch_merge_index_tree_oid;
|
||||
vb_state.set_branch(branch.clone())?;
|
||||
return Ok(Some(branch));
|
||||
}
|
||||
|
||||
let mut branch_head_merge_index = repo
|
||||
.merge_trees(&old_target_tree, &branch_head_tree, &new_target_tree, None)
|
||||
.context(format!(
|
||||
"failed to merge head tree for branch {}",
|
||||
branch.id
|
||||
))?;
|
||||
|
||||
if branch_head_merge_index.has_conflicts() {
|
||||
// branch commits conflict with new target, make sure the branch is
|
||||
// unapplied. conflicts witll be dealt with when applying it back.
|
||||
let unapplied_real_branch =
|
||||
convert_to_real_branch(project_repository, branch.id, Default::default())?;
|
||||
unapplied_branch_names.push(unapplied_real_branch);
|
||||
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// branch commits do not conflict with new target, so lets merge them
|
||||
let branch_head_merge_tree_oid = branch_head_merge_index
|
||||
.write_tree_to(project_repository.repo())
|
||||
.context(format!(
|
||||
"failed to write head merge index for {}",
|
||||
branch.id
|
||||
))?;
|
||||
|
||||
let ok_with_force_push = branch.allow_rebasing;
|
||||
|
||||
let result_merge = |mut branch: branch::Branch| -> Result<Option<branch::Branch>> {
|
||||
// branch was pushed to upstream, and user doesn't like force pushing.
|
||||
// create a merge commit to avoid the need of force pushing then.
|
||||
let branch_head_merge_tree = repo
|
||||
.find_tree(branch_head_merge_tree_oid)
|
||||
.context("failed to find tree")?;
|
||||
|
||||
let new_target_head = project_repository
|
||||
.commit(
|
||||
format!(
|
||||
"Merged {}/{} into {}",
|
||||
target.branch.remote(),
|
||||
target.branch.branch(),
|
||||
branch.name,
|
||||
)
|
||||
.as_str(),
|
||||
&branch_head_merge_tree,
|
||||
&[&branch_head_commit, &new_target_commit],
|
||||
None,
|
||||
)
|
||||
.context("failed to commit merge")?;
|
||||
|
||||
branch.head = new_target_head;
|
||||
branch.tree = branch_merge_index_tree_oid;
|
||||
vb_state.set_branch(branch.clone())?;
|
||||
Ok(Some(branch))
|
||||
};
|
||||
|
||||
if branch.upstream.is_some() && !ok_with_force_push {
|
||||
return result_merge(branch);
|
||||
}
|
||||
|
||||
// branch was not pushed to upstream yet. attempt a rebase,
|
||||
let rebased_head_oid = cherry_rebase(
|
||||
project_repository,
|
||||
new_target_commit.id(),
|
||||
new_target_commit.id(),
|
||||
branch.head,
|
||||
);
|
||||
|
||||
// rebase failed, just do the merge
|
||||
if rebased_head_oid.is_err() {
|
||||
return result_merge(branch);
|
||||
}
|
||||
|
||||
if let Some(rebased_head_oid) = rebased_head_oid? {
|
||||
// rebase worked out, rewrite the branch head
|
||||
branch.head = rebased_head_oid;
|
||||
branch.tree = branch_merge_index_tree_oid;
|
||||
vb_state.set_branch(branch.clone())?;
|
||||
return Ok(Some(branch));
|
||||
}
|
||||
|
||||
result_merge(branch)
|
||||
},
|
||||
)
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// ok, now all the problematic branches have been unapplied
|
||||
// now we calculate and checkout new tree for the working directory
|
||||
|
||||
let final_tree = updated_vbranches
|
||||
.iter()
|
||||
.filter(|branch| branch.applied)
|
||||
.fold(new_target_commit.tree(), |final_tree, branch| {
|
||||
let repo: &git2::Repository = repo;
|
||||
let final_tree = final_tree?;
|
||||
let branch_tree = repo.find_tree(branch.tree)?;
|
||||
let mut merge_result: Index =
|
||||
repo.merge_trees(&new_target_tree, &final_tree, &branch_tree, None)?;
|
||||
let final_tree_oid = merge_result.write_tree_to(repo)?;
|
||||
repo.find_tree(final_tree_oid)
|
||||
})
|
||||
.context("failed to calculate final tree")?;
|
||||
|
||||
repo.checkout_tree_builder(&final_tree)
|
||||
.force()
|
||||
.checkout()
|
||||
.context("failed to checkout index, this should not have happened, we should have already detected this")?;
|
||||
|
||||
// write new target oid
|
||||
vb_state.set_default_target(target::Target {
|
||||
sha: new_target_commit.id(),
|
||||
..target
|
||||
})?;
|
||||
|
||||
// Rewriting the integration commit is necessary after changing target sha.
|
||||
gitbutler_core::virtual_branches::integration::update_gitbutler_integration(
|
||||
&vb_state,
|
||||
project_repository,
|
||||
)?;
|
||||
Ok(unapplied_branch_names)
|
||||
}
|
||||
|
||||
pub fn target_to_base_branch(
|
||||
project_repository: &project_repository::Repository,
|
||||
target: &target::Target,
|
||||
) -> Result<BaseBranch> {
|
||||
let repo = project_repository.repo();
|
||||
let branch = repo
|
||||
.find_branch_by_refname(&target.branch.clone().into())?
|
||||
.ok_or(anyhow!("failed to get branch"))?;
|
||||
let commit = branch.get().peel_to_commit()?;
|
||||
let oid = commit.id();
|
||||
|
||||
// gather a list of commits between oid and target.sha
|
||||
let upstream_commits = project_repository
|
||||
.log(oid, project_repository::LogUntil::Commit(target.sha))
|
||||
.context("failed to get upstream commits")?
|
||||
.iter()
|
||||
.map(gitbutler_core::virtual_branches::commit_to_remote_commit)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// get some recent commits
|
||||
let recent_commits = project_repository
|
||||
.log(target.sha, LogUntil::Take(20))
|
||||
.context("failed to get recent commits")?
|
||||
.iter()
|
||||
.map(gitbutler_core::virtual_branches::commit_to_remote_commit)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// there has got to be a better way to do this.
|
||||
let push_remote_url = match target.push_remote_name {
|
||||
Some(ref name) => match repo.find_remote(name) {
|
||||
Ok(remote) => match remote.url() {
|
||||
Some(url) => url.to_string(),
|
||||
None => target.remote_url.clone(),
|
||||
},
|
||||
Err(_err) => target.remote_url.clone(),
|
||||
},
|
||||
None => target.remote_url.clone(),
|
||||
};
|
||||
|
||||
let base = BaseBranch {
|
||||
branch_name: format!("{}/{}", target.branch.remote(), target.branch.branch()),
|
||||
remote_name: target.branch.remote().to_string(),
|
||||
remote_url: target.remote_url.clone(),
|
||||
push_remote_name: target.push_remote_name.clone(),
|
||||
push_remote_url,
|
||||
base_sha: target.sha,
|
||||
current_sha: oid,
|
||||
behind: upstream_commits.len(),
|
||||
upstream_commits,
|
||||
recent_commits,
|
||||
last_fetched_ms: project_repository
|
||||
.project()
|
||||
.project_data_last_fetch
|
||||
.as_ref()
|
||||
.map(FetchResult::timestamp)
|
||||
.copied()
|
||||
.map(|t| t.duration_since(time::UNIX_EPOCH).unwrap().as_millis()),
|
||||
};
|
||||
Ok(base)
|
||||
}
|
||||
|
||||
fn default_target(base_path: &Path) -> Result<target::Target> {
|
||||
VirtualBranchesHandle::new(base_path).get_default_target()
|
||||
}
|
@ -10,12 +10,16 @@ use std::{path::Path, sync::Arc};
|
||||
|
||||
use tokio::sync::Semaphore;
|
||||
|
||||
use crate::base::{
|
||||
get_base_branch_data, set_base_branch, set_target_push_remote, update_base_branch, BaseBranch,
|
||||
};
|
||||
|
||||
use super::r#virtual as branch;
|
||||
use gitbutler_core::virtual_branches;
|
||||
|
||||
use gitbutler_core::virtual_branches::{
|
||||
branch::{BranchId, BranchOwnershipClaims},
|
||||
target, BaseBranch, RemoteBranchFile, VirtualBranchesHandle,
|
||||
target, RemoteBranchFile, VirtualBranchesHandle,
|
||||
};
|
||||
use gitbutler_core::{
|
||||
git,
|
||||
@ -110,7 +114,7 @@ impl Controller {
|
||||
|
||||
pub async fn get_base_branch_data(&self, project: &Project) -> Result<BaseBranch> {
|
||||
let project_repository = Repository::open(project)?;
|
||||
virtual_branches::get_base_branch_data(&project_repository)
|
||||
get_base_branch_data(&project_repository)
|
||||
}
|
||||
|
||||
pub async fn list_remote_commit_files(
|
||||
@ -132,12 +136,12 @@ impl Controller {
|
||||
let _ = project_repository
|
||||
.project()
|
||||
.create_snapshot(SnapshotDetails::new(OperationKind::SetBaseBranch));
|
||||
virtual_branches::set_base_branch(&project_repository, target_branch)
|
||||
set_base_branch(&project_repository, target_branch)
|
||||
}
|
||||
|
||||
pub async fn set_target_push_remote(&self, project: &Project, push_remote: &str) -> Result<()> {
|
||||
let project_repository = Repository::open(project)?;
|
||||
virtual_branches::set_target_push_remote(&project_repository, push_remote)
|
||||
set_target_push_remote(&project_repository, push_remote)
|
||||
}
|
||||
|
||||
pub async fn integrate_upstream_commits(
|
||||
@ -162,7 +166,7 @@ impl Controller {
|
||||
let _ = project_repository
|
||||
.project()
|
||||
.create_snapshot(SnapshotDetails::new(OperationKind::UpdateWorkspaceBase));
|
||||
virtual_branches::update_base_branch(&project_repository)
|
||||
update_base_branch(&project_repository)
|
||||
.map(|unapplied_branches| {
|
||||
unapplied_branches
|
||||
.iter()
|
||||
|
@ -6,3 +6,5 @@ pub mod r#virtual;
|
||||
pub use r#virtual::*;
|
||||
|
||||
pub mod assets;
|
||||
|
||||
pub mod base;
|
||||
|
@ -7,7 +7,7 @@ use url::Url;
|
||||
|
||||
use crate::{
|
||||
users,
|
||||
virtual_branches::{Author, BaseBranch, RemoteBranchData, RemoteCommit},
|
||||
virtual_branches::{Author, RemoteBranchData, RemoteCommit},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -62,37 +62,13 @@ impl Proxy {
|
||||
}
|
||||
}
|
||||
|
||||
async fn proxy_remote_commit(&self, commit: RemoteCommit) -> RemoteCommit {
|
||||
pub async fn proxy_remote_commit(&self, commit: RemoteCommit) -> RemoteCommit {
|
||||
RemoteCommit {
|
||||
author: self.proxy_author(commit.author).await,
|
||||
..commit
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn proxy_base_branch(&self, base_branch: BaseBranch) -> BaseBranch {
|
||||
BaseBranch {
|
||||
recent_commits: join_all(
|
||||
base_branch
|
||||
.clone()
|
||||
.recent_commits
|
||||
.into_iter()
|
||||
.map(|commit| self.proxy_remote_commit(commit))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.await,
|
||||
upstream_commits: join_all(
|
||||
base_branch
|
||||
.clone()
|
||||
.upstream_commits
|
||||
.into_iter()
|
||||
.map(|commit| self.proxy_remote_commit(commit))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.await,
|
||||
..base_branch.clone()
|
||||
}
|
||||
}
|
||||
|
||||
// takes a url of a remote assets, downloads it into cache and returns a url that points to the cached file
|
||||
pub async fn proxy(&self, src: &Url) -> Result<Url> {
|
||||
#[cfg(unix)]
|
||||
|
@ -8,9 +8,6 @@ pub use files::*;
|
||||
pub mod integration;
|
||||
pub use integration::GITBUTLER_INTEGRATION_REFERENCE;
|
||||
|
||||
mod base;
|
||||
pub use base::*;
|
||||
|
||||
mod r#virtual;
|
||||
pub use r#virtual::*;
|
||||
|
||||
|
@ -167,7 +167,7 @@ pub struct VirtualBranchHunk {
|
||||
|
||||
/// Lifecycle
|
||||
impl VirtualBranchHunk {
|
||||
pub(crate) fn gen_id(new_start: u32, new_lines: u32) -> String {
|
||||
pub fn gen_id(new_start: u32, new_lines: u32) -> String {
|
||||
format!("{}-{}", new_start, new_start + new_lines)
|
||||
}
|
||||
fn from_git_hunk(
|
||||
|
@ -1,6 +1,7 @@
|
||||
pub mod commands {
|
||||
use crate::error::Error;
|
||||
use anyhow::{anyhow, Context};
|
||||
use gitbutler_branch::base::BaseBranch;
|
||||
use gitbutler_branch::{Controller, NameConflitResolution, VirtualBranches};
|
||||
use gitbutler_core::{
|
||||
assets,
|
||||
@ -10,7 +11,7 @@ pub mod commands {
|
||||
types::ReferenceName,
|
||||
virtual_branches::{
|
||||
branch::{self, BranchId, BranchOwnershipClaims},
|
||||
BaseBranch, RemoteBranch, RemoteBranchData, RemoteBranchFile,
|
||||
RemoteBranch, RemoteBranchData, RemoteBranchFile,
|
||||
},
|
||||
};
|
||||
use tauri::{AppHandle, Manager};
|
||||
@ -118,7 +119,9 @@ pub mod commands {
|
||||
.get_base_branch_data(&project)
|
||||
.await
|
||||
{
|
||||
let proxy = handle.state::<assets::Proxy>();
|
||||
let proxy = gitbutler_branch::assets::Proxy::new(
|
||||
handle.state::<assets::Proxy>().inner().clone(),
|
||||
);
|
||||
let base_branch = proxy.proxy_base_branch(base_branch).await;
|
||||
return Ok(Some(base_branch));
|
||||
}
|
||||
@ -141,10 +144,10 @@ pub mod commands {
|
||||
.state::<Controller>()
|
||||
.set_base_branch(&project, &branch_name)
|
||||
.await?;
|
||||
let base_branch = handle
|
||||
.state::<assets::Proxy>()
|
||||
.proxy_base_branch(base_branch)
|
||||
.await;
|
||||
|
||||
let proxy =
|
||||
gitbutler_branch::assets::Proxy::new(handle.state::<assets::Proxy>().inner().clone());
|
||||
let base_branch = proxy.proxy_base_branch(base_branch).await;
|
||||
|
||||
// if they also sent a different push remote, set that too
|
||||
if let Some(push_remote) = push_remote {
|
||||
|
Loading…
Reference in New Issue
Block a user