Show remote activity on iPhone Home
This commit is contained in:
1 parent
daf122aff3
commit
8e883f5ccf
9 files changed
+3377
-35
No files matched your search
+188
-25
@@ -180,6 +180,53 @@ pub struct GitSnapshot {
|
||||
recent: Vec<GitLogEntry>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct GitCommitActivity {
|
||||
commit: GitLogEntry,
|
||||
changes: Vec<GitChange>,
|
||||
}
|
||||
|
||||
impl GitCommitActivity {
|
||||
pub fn commit(&self) -> &GitLogEntry {
|
||||
&self.commit
|
||||
}
|
||||
|
||||
pub fn changes(&self) -> &[GitChange] {
|
||||
&self.changes
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct GitDivergence {
|
||||
branch: String,
|
||||
status: GitStatus,
|
||||
remote: GitRemoteStatus,
|
||||
incoming: Vec<GitCommitActivity>,
|
||||
outgoing: Vec<GitCommitActivity>,
|
||||
}
|
||||
|
||||
impl GitDivergence {
|
||||
pub fn branch(&self) -> &str {
|
||||
&self.branch
|
||||
}
|
||||
|
||||
pub fn status(&self) -> &GitStatus {
|
||||
&self.status
|
||||
}
|
||||
|
||||
pub fn remote(&self) -> &GitRemoteStatus {
|
||||
&self.remote
|
||||
}
|
||||
|
||||
pub fn incoming(&self) -> &[GitCommitActivity] {
|
||||
&self.incoming
|
||||
}
|
||||
|
||||
pub fn outgoing(&self) -> &[GitCommitActivity] {
|
||||
&self.outgoing
|
||||
}
|
||||
}
|
||||
|
||||
impl GitSnapshot {
|
||||
pub fn root(&self) -> &Path {
|
||||
&self.root
|
||||
@@ -1698,32 +1745,144 @@ impl GitRepository {
|
||||
})
|
||||
}
|
||||
|
||||
/// Return commits and typed path changes on each side of the configured
|
||||
/// remote-tracking branch without performing network access.
|
||||
pub fn divergence(
|
||||
&self,
|
||||
configured: &GitRemote,
|
||||
activity_limit: usize,
|
||||
) -> Result<GitDivergence, GitError> {
|
||||
let branch = self.current_branch()?;
|
||||
let status = self.status()?;
|
||||
let name = configured.name().as_str();
|
||||
let actual_url = self.remote_url(name)?;
|
||||
if actual_url != configured.url().as_str() {
|
||||
return Err(GitError::ForbiddenRemoteUrl);
|
||||
}
|
||||
let local_id = self
|
||||
.repository
|
||||
.head_id()
|
||||
.map_err(|_| GitError::UnbornHead)?
|
||||
.detach();
|
||||
let remote_ref = format!("refs/remotes/{name}/{branch}");
|
||||
let remote_id = self
|
||||
.repository
|
||||
.find_reference(&remote_ref)
|
||||
.map_err(|_| GitError::RemoteNotFound {
|
||||
name: remote_ref.clone(),
|
||||
})?
|
||||
.into_fully_peeled_id()
|
||||
.map_err(invalid)?
|
||||
.detach();
|
||||
let local_ids = self.ancestor_ids(local_id)?;
|
||||
let remote_ids = self.ancestor_ids(remote_id)?;
|
||||
let ahead = local_ids.difference(&remote_ids).count();
|
||||
let behind = remote_ids.difference(&local_ids).count();
|
||||
let incoming = self.commit_activities(remote_id, &local_ids, activity_limit)?;
|
||||
let outgoing = self.commit_activities(local_id, &remote_ids, activity_limit)?;
|
||||
Ok(GitDivergence {
|
||||
branch,
|
||||
status,
|
||||
remote: GitRemoteStatus {
|
||||
name: name.to_owned(),
|
||||
url: actual_url,
|
||||
ahead,
|
||||
behind,
|
||||
},
|
||||
incoming,
|
||||
outgoing,
|
||||
})
|
||||
}
|
||||
|
||||
fn commit_activities(
|
||||
&self,
|
||||
tip: gix::hash::ObjectId,
|
||||
excluded: &BTreeSet<gix::hash::ObjectId>,
|
||||
limit: usize,
|
||||
) -> Result<Vec<GitCommitActivity>, GitError> {
|
||||
if limit == 0 {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let commit = self
|
||||
.repository
|
||||
.find_object(tip)
|
||||
.map_err(invalid)?
|
||||
.peel_to_commit()
|
||||
.map_err(invalid)?;
|
||||
let mut output = Vec::new();
|
||||
for info in commit.ancestors().all().map_err(invalid)? {
|
||||
let info = info.map_err(invalid)?;
|
||||
if excluded.contains(&info.id) {
|
||||
continue;
|
||||
}
|
||||
output.push(self.commit_activity(info.id)?);
|
||||
if output.len() >= limit {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn commit_activity(&self, id: gix::hash::ObjectId) -> Result<GitCommitActivity, GitError> {
|
||||
let commit = self
|
||||
.repository
|
||||
.find_object(id)
|
||||
.map_err(invalid)?
|
||||
.peel_to_commit()
|
||||
.map_err(invalid)?;
|
||||
let new_tree = commit.tree_id().map_err(invalid)?.detach();
|
||||
let old_tree = commit
|
||||
.parent_ids()
|
||||
.next()
|
||||
.map(|parent| {
|
||||
self.repository
|
||||
.find_object(parent.detach())
|
||||
.map_err(invalid)?
|
||||
.peel_to_commit()
|
||||
.map_err(invalid)?
|
||||
.tree_id()
|
||||
.map_err(invalid)
|
||||
.map(|tree| tree.detach())
|
||||
})
|
||||
.transpose()?;
|
||||
let old = tree_map_by_id(&self.repository, old_tree)?;
|
||||
let new = tree_map_by_id(&self.repository, Some(new_tree))?;
|
||||
Ok(GitCommitActivity {
|
||||
commit: git_log_entry(&commit)?,
|
||||
changes: compare_maps(&old, &new),
|
||||
})
|
||||
}
|
||||
|
||||
fn ahead_behind(
|
||||
&self,
|
||||
local: gix::hash::ObjectId,
|
||||
remote: gix::hash::ObjectId,
|
||||
) -> Result<(usize, usize), GitError> {
|
||||
let ancestors = |id| -> Result<BTreeSet<gix::hash::ObjectId>, GitError> {
|
||||
let commit = self
|
||||
.repository
|
||||
.find_object(id)
|
||||
.map_err(invalid)?
|
||||
.peel_to_commit()
|
||||
.map_err(invalid)?;
|
||||
let mut ids = BTreeSet::from([id]);
|
||||
for info in commit.ancestors().all().map_err(invalid)? {
|
||||
ids.insert(info.map_err(invalid)?.id);
|
||||
}
|
||||
Ok(ids)
|
||||
};
|
||||
let local_ids = ancestors(local)?;
|
||||
let remote_ids = ancestors(remote)?;
|
||||
let local_ids = self.ancestor_ids(local)?;
|
||||
let remote_ids = self.ancestor_ids(remote)?;
|
||||
Ok((
|
||||
local_ids.difference(&remote_ids).count(),
|
||||
remote_ids.difference(&local_ids).count(),
|
||||
))
|
||||
}
|
||||
|
||||
fn ancestor_ids(
|
||||
&self,
|
||||
id: gix::hash::ObjectId,
|
||||
) -> Result<BTreeSet<gix::hash::ObjectId>, GitError> {
|
||||
let commit = self
|
||||
.repository
|
||||
.find_object(id)
|
||||
.map_err(invalid)?
|
||||
.peel_to_commit()
|
||||
.map_err(invalid)?;
|
||||
let mut ids = BTreeSet::from([id]);
|
||||
for info in commit.ancestors().all().map_err(invalid)? {
|
||||
ids.insert(info.map_err(invalid)?.id);
|
||||
}
|
||||
Ok(ids)
|
||||
}
|
||||
|
||||
fn update_remote_tracking(
|
||||
&self,
|
||||
remote: &str,
|
||||
@@ -2329,16 +2488,7 @@ impl GitRepository {
|
||||
}
|
||||
let info = info.map_err(invalid)?;
|
||||
let commit = info.object().map_err(invalid)?;
|
||||
let decoded = commit.decode().map_err(invalid)?;
|
||||
let author = decoded.author().map_err(invalid)?;
|
||||
output.push(GitLogEntry {
|
||||
id: commit.id.to_string(),
|
||||
parents: decoded.parents().map(|id| id.to_string()).collect(),
|
||||
author_name: author.name.to_str_lossy().into_owned(),
|
||||
author_email: author.email.to_str_lossy().into_owned(),
|
||||
message: decoded.message.to_str_lossy().into_owned(),
|
||||
timestamp: author.time().map_err(invalid)?.seconds,
|
||||
});
|
||||
output.push(git_log_entry(&commit)?);
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
@@ -2516,6 +2666,19 @@ fn append_diff_lines(output: &mut Vec<u8>, prefix: u8, contents: &[u8]) {
|
||||
}
|
||||
}
|
||||
|
||||
fn git_log_entry(commit: &gix::Commit<'_>) -> Result<GitLogEntry, GitError> {
|
||||
let decoded = commit.decode().map_err(invalid)?;
|
||||
let author = decoded.author().map_err(invalid)?;
|
||||
Ok(GitLogEntry {
|
||||
id: commit.id.to_string(),
|
||||
parents: decoded.parents().map(|id| id.to_string()).collect(),
|
||||
author_name: author.name.to_str_lossy().into_owned(),
|
||||
author_email: author.email.to_str_lossy().into_owned(),
|
||||
message: decoded.message.to_str_lossy().into_owned(),
|
||||
timestamp: author.time().map_err(invalid)?.seconds,
|
||||
})
|
||||
}
|
||||
|
||||
impl EntryCommitter for GitRepository {
|
||||
fn commit(&mut self, change: &EntryCommit) -> Result<(), EntryCommitError> {
|
||||
self.stage_and_commit(&[change.path().encrypted_relative_path()], change.message())
|
||||
|
||||
Reference in new issue
Block a user