mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2025-01-06 01:27:24 +03:00
Merge remote-tracking branch 'origin/master' into sc-signing-2
This commit is contained in:
commit
c3adce8238
@ -1,6 +1,7 @@
|
||||
use std::time;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
@ -10,7 +11,20 @@ use crate::{
|
||||
reader, sessions,
|
||||
};
|
||||
|
||||
use super::{branch, delete_branch, iterator, target};
|
||||
use super::{branch, delete_branch, iterator, target, RemoteCommit};
|
||||
|
||||
#[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<RemoteCommit>,
|
||||
pub recent_commits: Vec<RemoteCommit>,
|
||||
}
|
||||
|
||||
pub fn get_base_branch_data(
|
||||
gb_repository: &gb_repository::Repository,
|
||||
|
38
packages/tauri/src/virtual_branches/files.rs
Normal file
38
packages/tauri/src/virtual_branches/files.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use std::path;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::git::{self, diff};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RemoteBranchFile {
|
||||
pub path: path::PathBuf,
|
||||
pub hunks: Vec<diff::Hunk>,
|
||||
pub binary: bool,
|
||||
}
|
||||
|
||||
pub fn list_remote_commit_files(
|
||||
repository: &git::Repository,
|
||||
commit: &git::Commit,
|
||||
) -> Result<Vec<RemoteBranchFile>> {
|
||||
if commit.parent_count() == 0 {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
let parent = commit.parent(0).context("failed to get parent commit")?;
|
||||
let commit_tree = commit.tree().context("failed to get commit tree")?;
|
||||
let parent_tree = parent.tree().context("failed to get parent tree")?;
|
||||
let diff = diff::trees(repository, &parent_tree, &commit_tree)?;
|
||||
|
||||
let files = diff
|
||||
.into_iter()
|
||||
.map(|(file_path, hunks)| RemoteBranchFile {
|
||||
path: file_path.clone(),
|
||||
hunks: hunks.clone(),
|
||||
binary: hunks.iter().any(|h| h.binary),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(files)
|
||||
}
|
@ -251,7 +251,7 @@ fn verify_head_is_clean(
|
||||
)
|
||||
.context("failed to reset to integration commit")?;
|
||||
|
||||
let new_branch = super::vbranch::create_virtual_branch(
|
||||
let new_branch = super::create_virtual_branch(
|
||||
gb_repository,
|
||||
&BranchCreateRequest {
|
||||
name: extra_commits
|
||||
@ -322,11 +322,11 @@ fn verify_head_is_set(
|
||||
{
|
||||
Some(GITBUTLER_INTEGRATION_REFERENCE) => Ok(()),
|
||||
None => {
|
||||
super::vbranch::mark_all_unapplied(gb_repository).map_err(VerifyError::Other)?;
|
||||
super::mark_all_unapplied(gb_repository).map_err(VerifyError::Other)?;
|
||||
Err(VerifyError::DetachedHead)
|
||||
}
|
||||
Some(head_name) => {
|
||||
super::vbranch::mark_all_unapplied(gb_repository).map_err(VerifyError::Other)?;
|
||||
super::mark_all_unapplied(gb_repository).map_err(VerifyError::Other)?;
|
||||
Err(VerifyError::InvalidHead(head_name.to_string()))
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,9 @@ pub mod branch;
|
||||
pub use branch::Branch;
|
||||
pub mod target;
|
||||
|
||||
mod files;
|
||||
pub use files::*;
|
||||
|
||||
mod integration;
|
||||
pub use integration::GITBUTLER_INTEGRATION_BRANCH_NAME;
|
||||
|
||||
@ -18,5 +21,8 @@ pub use iterator::BranchIterator as Iterator;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
mod vbranch;
|
||||
pub use vbranch::*;
|
||||
mod r#virtual;
|
||||
pub use r#virtual::*;
|
||||
|
||||
mod remote;
|
||||
pub use remote::*;
|
||||
|
214
packages/tauri/src/virtual_branches/remote.rs
Normal file
214
packages/tauri/src/virtual_branches/remote.rs
Normal file
@ -0,0 +1,214 @@
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
time,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
gb_repository, git,
|
||||
project_repository::{self, LogUntil},
|
||||
reader, sessions,
|
||||
};
|
||||
|
||||
use super::{branch, get_default_target, iterator::BranchIterator as Iterator, Author};
|
||||
|
||||
// this struct is a mapping to the view `RemoteBranch` type in Typescript
|
||||
// found in src-tauri/src/routes/repo/[project_id]/types.ts
|
||||
//
|
||||
// it holds data calculated for presentation purposes of one Git branch
|
||||
// with comparison data to the Target commit, determining if it is mergeable,
|
||||
// and how far ahead or behind the Target it is.
|
||||
// an array of them can be requested from the frontend to show in the sidebar
|
||||
// Tray and should only contain branches that have not been converted into
|
||||
// virtual branches yet (ie, we have no `Branch` struct persisted in our data.
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RemoteBranch {
|
||||
pub sha: String,
|
||||
pub name: String,
|
||||
pub behind: u32,
|
||||
pub upstream: Option<git::RemoteBranchName>,
|
||||
pub commits: Vec<RemoteCommit>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RemoteCommit {
|
||||
pub id: String,
|
||||
pub description: String,
|
||||
pub created_at: u128,
|
||||
pub author: Author,
|
||||
}
|
||||
|
||||
pub fn list_remote_branches(
|
||||
gb_repository: &gb_repository::Repository,
|
||||
project_repository: &project_repository::Repository,
|
||||
) -> Result<Vec<RemoteBranch>> {
|
||||
// get the current target
|
||||
let current_session = gb_repository
|
||||
.get_or_create_current_session()
|
||||
.context("failed to get or create currnt session")?;
|
||||
let current_session_reader = sessions::Reader::open(gb_repository, ¤t_session)
|
||||
.context("failed to open current session")?;
|
||||
|
||||
let default_target = match get_default_target(¤t_session_reader)
|
||||
.context("failed to get default target")?
|
||||
{
|
||||
Some(target) => target,
|
||||
None => return Ok(vec![]),
|
||||
};
|
||||
|
||||
let current_time = time::SystemTime::now();
|
||||
let too_old = time::Duration::from_secs(86_400 * 90); // 90 days (3 months) is too old
|
||||
|
||||
let repo = &project_repository.git_repository;
|
||||
|
||||
let main_oid = default_target.sha;
|
||||
|
||||
let virtual_branches_names = Iterator::new(¤t_session_reader)
|
||||
.context("failed to create branch iterator")?
|
||||
.collect::<Result<Vec<branch::Branch>, reader::Error>>()
|
||||
.context("failed to read virtual branches")?
|
||||
.into_iter()
|
||||
.filter_map(|branch| branch.upstream)
|
||||
.map(|upstream| upstream.branch().to_string())
|
||||
.collect::<HashSet<_>>();
|
||||
let mut most_recent_branches_by_hash: HashMap<git::Oid, (git::Branch, u64)> = HashMap::new();
|
||||
|
||||
for (branch, _) in repo.branches(None)?.flatten() {
|
||||
if let Some(branch_oid) = branch.target() {
|
||||
// get the branch ref
|
||||
let branch_commit = repo
|
||||
.find_commit(branch_oid)
|
||||
.context("failed to find branch commit")?;
|
||||
let branch_time = branch_commit.time();
|
||||
let seconds = branch_time
|
||||
.seconds()
|
||||
.try_into()
|
||||
.context("failed to convert seconds")?;
|
||||
let branch_time = time::UNIX_EPOCH + time::Duration::from_secs(seconds);
|
||||
let duration = current_time
|
||||
.duration_since(branch_time)
|
||||
.context("failed to get duration")?;
|
||||
if duration > too_old {
|
||||
continue;
|
||||
}
|
||||
|
||||
let branch_name =
|
||||
git::BranchName::try_from(&branch).context("could not get branch name")?;
|
||||
|
||||
// skip the default target branch (both local and remote)
|
||||
match branch_name {
|
||||
git::BranchName::Remote(ref remote_branch_name) => {
|
||||
if *remote_branch_name == default_target.branch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
git::BranchName::Local(ref local_branch_name) => {
|
||||
if let Some(upstream_branch_name) = local_branch_name.remote() {
|
||||
if *upstream_branch_name == default_target.branch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if virtual_branches_names.contains(branch_name.branch()) {
|
||||
continue;
|
||||
}
|
||||
if branch_name.branch().eq("HEAD") {
|
||||
continue;
|
||||
}
|
||||
if branch_name
|
||||
.branch()
|
||||
.eq(super::integration::GITBUTLER_INTEGRATION_BRANCH_NAME)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
match most_recent_branches_by_hash.get(&branch_oid) {
|
||||
Some((_, existing_seconds)) => {
|
||||
let branch_name = branch.refname().context("could not get branch name")?;
|
||||
if seconds < *existing_seconds {
|
||||
// this branch is older than the one we already have
|
||||
continue;
|
||||
}
|
||||
if seconds > *existing_seconds {
|
||||
most_recent_branches_by_hash.insert(branch_oid, (branch, seconds));
|
||||
continue;
|
||||
}
|
||||
if branch_name.starts_with("refs/remotes") {
|
||||
// this branch is a remote branch
|
||||
// we always prefer the remote branch if it is the same age as the local branch
|
||||
most_recent_branches_by_hash.insert(branch_oid, (branch, seconds));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// this is the first time we've seen this branch
|
||||
// so we should add it to the list
|
||||
most_recent_branches_by_hash.insert(branch_oid, (branch, seconds));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut most_recent_branches: Vec<(git::Branch, u64)> =
|
||||
most_recent_branches_by_hash.into_values().collect();
|
||||
|
||||
// take the most recent 20 branches
|
||||
most_recent_branches.sort_by(|a, b| b.1.cmp(&a.1)); // Sort by timestamp in descending order.
|
||||
let sorted_branches: Vec<git::Branch> = most_recent_branches
|
||||
.into_iter()
|
||||
.map(|(branch, _)| branch)
|
||||
.collect();
|
||||
let top_branches = sorted_branches.into_iter().take(20).collect::<Vec<_>>(); // Take the first 20 entries.
|
||||
|
||||
let mut branches: Vec<RemoteBranch> = Vec::new();
|
||||
for branch in &top_branches {
|
||||
if let Some(branch_oid) = branch.target() {
|
||||
let ahead = project_repository
|
||||
.log(branch_oid, LogUntil::Commit(main_oid))
|
||||
.context("failed to get ahead commits")?;
|
||||
|
||||
if ahead.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let branch_name = branch.refname().context("could not get branch name")?;
|
||||
|
||||
let count_behind = project_repository
|
||||
.distance(main_oid, branch_oid)
|
||||
.context("failed to get behind count")?;
|
||||
|
||||
let upstream = branch
|
||||
.upstream()
|
||||
.ok()
|
||||
.map(|upstream_branch| git::RemoteBranchName::try_from(&upstream_branch))
|
||||
.transpose()?;
|
||||
|
||||
branches.push(RemoteBranch {
|
||||
sha: branch_oid.to_string(),
|
||||
name: branch_name.to_string(),
|
||||
upstream,
|
||||
behind: count_behind,
|
||||
commits: ahead
|
||||
.into_iter()
|
||||
.map(|commit| commit_to_remote_commit(&commit))
|
||||
.collect::<Result<Vec<_>>>()?,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(branches)
|
||||
}
|
||||
|
||||
pub fn commit_to_remote_commit(commit: &git::Commit) -> Result<RemoteCommit> {
|
||||
Ok(RemoteCommit {
|
||||
id: commit.id().to_string(),
|
||||
description: commit.message().unwrap_or_default().to_string(),
|
||||
created_at: commit.time().seconds().try_into().unwrap(),
|
||||
author: commit.author().into(),
|
||||
})
|
||||
}
|
@ -86,14 +86,6 @@ pub struct VirtualBranchFile {
|
||||
pub binary: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RemoteBranchFile {
|
||||
pub path: path::PathBuf,
|
||||
pub hunks: Vec<git::diff::Hunk>,
|
||||
pub binary: bool,
|
||||
}
|
||||
|
||||
// this struct is a mapping to the view `Hunk` type in Typescript
|
||||
// found in src-tauri/src/routes/repo/[project_id]/types.ts
|
||||
// it holds a materialized view for presentation purposes of one entry of the
|
||||
@ -116,60 +108,6 @@ pub struct VirtualBranchHunk {
|
||||
pub locked: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RemoteBranchHunk {
|
||||
pub id: String,
|
||||
pub diff: String,
|
||||
pub modified_at: u128,
|
||||
pub file_path: path::PathBuf,
|
||||
pub hash: String,
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub binary: bool,
|
||||
}
|
||||
|
||||
// this struct is a mapping to the view `RemoteBranch` type in Typescript
|
||||
// found in src-tauri/src/routes/repo/[project_id]/types.ts
|
||||
//
|
||||
// it holds data calculated for presentation purposes of one Git branch
|
||||
// with comparison data to the Target commit, determining if it is mergeable,
|
||||
// and how far ahead or behind the Target it is.
|
||||
// an array of them can be requested from the frontend to show in the sidebar
|
||||
// Tray and should only contain branches that have not been converted into
|
||||
// virtual branches yet (ie, we have no `Branch` struct persisted in our data.
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RemoteBranch {
|
||||
pub sha: String,
|
||||
pub name: String,
|
||||
pub behind: u32,
|
||||
pub upstream: Option<git::RemoteBranchName>,
|
||||
pub commits: Vec<RemoteCommit>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RemoteCommit {
|
||||
pub id: String,
|
||||
pub description: String,
|
||||
pub created_at: u128,
|
||||
pub author: Author,
|
||||
}
|
||||
|
||||
#[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<RemoteCommit>,
|
||||
pub recent_commits: Vec<RemoteCommit>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Hash, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Author {
|
||||
@ -468,201 +406,6 @@ fn unapply_all_branches(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn list_remote_branches(
|
||||
gb_repository: &gb_repository::Repository,
|
||||
project_repository: &project_repository::Repository,
|
||||
) -> Result<Vec<RemoteBranch>> {
|
||||
// get the current target
|
||||
let current_session = gb_repository
|
||||
.get_or_create_current_session()
|
||||
.context("failed to get or create currnt session")?;
|
||||
let current_session_reader = sessions::Reader::open(gb_repository, ¤t_session)
|
||||
.context("failed to open current session")?;
|
||||
|
||||
let default_target = match get_default_target(¤t_session_reader)
|
||||
.context("failed to get default target")?
|
||||
{
|
||||
Some(target) => target,
|
||||
None => return Ok(vec![]),
|
||||
};
|
||||
|
||||
let current_time = time::SystemTime::now();
|
||||
let too_old = time::Duration::from_secs(86_400 * 90); // 90 days (3 months) is too old
|
||||
|
||||
let repo = &project_repository.git_repository;
|
||||
|
||||
let main_oid = default_target.sha;
|
||||
|
||||
let virtual_branches_names = Iterator::new(¤t_session_reader)
|
||||
.context("failed to create branch iterator")?
|
||||
.collect::<Result<Vec<branch::Branch>, reader::Error>>()
|
||||
.context("failed to read virtual branches")?
|
||||
.into_iter()
|
||||
.filter_map(|branch| branch.upstream)
|
||||
.map(|upstream| upstream.branch().to_string())
|
||||
.collect::<HashSet<_>>();
|
||||
let mut most_recent_branches_by_hash: HashMap<git::Oid, (git::Branch, u64)> = HashMap::new();
|
||||
|
||||
for (branch, _) in repo.branches(None)?.flatten() {
|
||||
if let Some(branch_oid) = branch.target() {
|
||||
// get the branch ref
|
||||
let branch_commit = repo
|
||||
.find_commit(branch_oid)
|
||||
.context("failed to find branch commit")?;
|
||||
let branch_time = branch_commit.time();
|
||||
let seconds = branch_time
|
||||
.seconds()
|
||||
.try_into()
|
||||
.context("failed to convert seconds")?;
|
||||
let branch_time = time::UNIX_EPOCH + time::Duration::from_secs(seconds);
|
||||
let duration = current_time
|
||||
.duration_since(branch_time)
|
||||
.context("failed to get duration")?;
|
||||
if duration > too_old {
|
||||
continue;
|
||||
}
|
||||
|
||||
let branch_name =
|
||||
git::BranchName::try_from(&branch).context("could not get branch name")?;
|
||||
|
||||
// skip the default target branch (both local and remote)
|
||||
match branch_name {
|
||||
git::BranchName::Remote(ref remote_branch_name) => {
|
||||
if *remote_branch_name == default_target.branch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
git::BranchName::Local(ref local_branch_name) => {
|
||||
if let Some(upstream_branch_name) = local_branch_name.remote() {
|
||||
if *upstream_branch_name == default_target.branch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if virtual_branches_names.contains(branch_name.branch()) {
|
||||
continue;
|
||||
}
|
||||
if branch_name.branch().eq("HEAD") {
|
||||
continue;
|
||||
}
|
||||
if branch_name
|
||||
.branch()
|
||||
.eq(super::integration::GITBUTLER_INTEGRATION_BRANCH_NAME)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
match most_recent_branches_by_hash.get(&branch_oid) {
|
||||
Some((_, existing_seconds)) => {
|
||||
let branch_name = branch.refname().context("could not get branch name")?;
|
||||
if seconds < *existing_seconds {
|
||||
// this branch is older than the one we already have
|
||||
continue;
|
||||
}
|
||||
if seconds > *existing_seconds {
|
||||
most_recent_branches_by_hash.insert(branch_oid, (branch, seconds));
|
||||
continue;
|
||||
}
|
||||
if branch_name.starts_with("refs/remotes") {
|
||||
// this branch is a remote branch
|
||||
// we always prefer the remote branch if it is the same age as the local branch
|
||||
most_recent_branches_by_hash.insert(branch_oid, (branch, seconds));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// this is the first time we've seen this branch
|
||||
// so we should add it to the list
|
||||
most_recent_branches_by_hash.insert(branch_oid, (branch, seconds));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut most_recent_branches: Vec<(git::Branch, u64)> =
|
||||
most_recent_branches_by_hash.into_values().collect();
|
||||
|
||||
// take the most recent 20 branches
|
||||
most_recent_branches.sort_by(|a, b| b.1.cmp(&a.1)); // Sort by timestamp in descending order.
|
||||
let sorted_branches: Vec<git::Branch> = most_recent_branches
|
||||
.into_iter()
|
||||
.map(|(branch, _)| branch)
|
||||
.collect();
|
||||
let top_branches = sorted_branches.into_iter().take(20).collect::<Vec<_>>(); // Take the first 20 entries.
|
||||
|
||||
let mut branches: Vec<RemoteBranch> = Vec::new();
|
||||
for branch in &top_branches {
|
||||
if let Some(branch_oid) = branch.target() {
|
||||
let ahead = project_repository
|
||||
.log(branch_oid, LogUntil::Commit(main_oid))
|
||||
.context("failed to get ahead commits")?;
|
||||
|
||||
if ahead.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let branch_name = branch.refname().context("could not get branch name")?;
|
||||
|
||||
let count_behind = project_repository
|
||||
.distance(main_oid, branch_oid)
|
||||
.context("failed to get behind count")?;
|
||||
|
||||
let upstream = branch
|
||||
.upstream()
|
||||
.ok()
|
||||
.map(|upstream_branch| git::RemoteBranchName::try_from(&upstream_branch))
|
||||
.transpose()?;
|
||||
|
||||
branches.push(RemoteBranch {
|
||||
sha: branch_oid.to_string(),
|
||||
name: branch_name.to_string(),
|
||||
upstream,
|
||||
behind: count_behind,
|
||||
commits: ahead
|
||||
.into_iter()
|
||||
.map(|commit| commit_to_remote_commit(&commit))
|
||||
.collect::<Result<Vec<_>>>()?,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(branches)
|
||||
}
|
||||
|
||||
pub fn commit_to_remote_commit(commit: &git::Commit) -> Result<RemoteCommit> {
|
||||
Ok(RemoteCommit {
|
||||
id: commit.id().to_string(),
|
||||
description: commit.message().unwrap_or_default().to_string(),
|
||||
created_at: commit.time().seconds().try_into().unwrap(),
|
||||
author: commit.author().into(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn list_remote_commit_files(
|
||||
repository: &git::Repository,
|
||||
commit: &git::Commit,
|
||||
) -> Result<Vec<RemoteBranchFile>> {
|
||||
if commit.parent_count() == 0 {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
let parent = commit.parent(0).context("failed to get parent commit")?;
|
||||
let commit_tree = commit.tree().context("failed to get commit tree")?;
|
||||
let parent_tree = parent.tree().context("failed to get parent tree")?;
|
||||
let diff = diff::trees(repository, &parent_tree, &commit_tree)?;
|
||||
|
||||
let files = diff
|
||||
.into_iter()
|
||||
.map(|(file_path, hunks)| RemoteBranchFile {
|
||||
path: file_path.clone(),
|
||||
hunks: hunks.clone(),
|
||||
binary: hunks.iter().any(|h| h.binary),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
fn find_base_tree<'a>(
|
||||
repo: &'a git::Repository,
|
||||
branch_commit: &'a git::Commit<'a>,
|
Loading…
Reference in New Issue
Block a user