radicle_cli/terminal/patch/
common.rs1use anyhow::anyhow;
2
3use radicle::git;
4use radicle::git::raw::Oid;
5use radicle::prelude::*;
6use radicle::storage::git::Repository;
7
8use crate::terminal as term;
9
10#[inline]
12pub fn branch_oid(branch: &git::raw::Branch) -> anyhow::Result<git::Oid> {
13 let oid = branch
14 .get()
15 .target()
16 .ok_or(anyhow!("invalid HEAD ref; aborting"))?;
17 Ok(oid.into())
18}
19
20#[inline]
21fn get_branch(git_ref: git::Qualified) -> git::RefString {
22 let (_, _, head, tail) = git_ref.non_empty_components();
23 std::iter::once(head).chain(tail).collect()
24}
25
26pub fn get_merge_target(
29 storage: &Repository,
30 head_branch: &git::raw::Branch,
31) -> anyhow::Result<(git::RefString, git::Oid)> {
32 let (qualified_ref, target_oid) = storage.canonical_head()?;
33 let head_oid = branch_oid(head_branch)?;
34 let merge_base = storage.raw().merge_base(*head_oid, *target_oid)?;
35
36 if head_oid == merge_base.into() {
37 anyhow::bail!("commits are already included in the target branch; nothing to do");
38 }
39
40 Ok((get_branch(qualified_ref), (*target_oid).into()))
41}
42
43pub fn diff_stats(
46 repo: &git::raw::Repository,
47 old: &Oid,
48 new: &Oid,
49) -> Result<git::raw::DiffStats, git::raw::Error> {
50 let old = repo.find_commit(*old)?;
51 let new = repo.find_commit(*new)?;
52 let old_tree = old.tree()?;
53 let new_tree = new.tree()?;
54 let mut diff = repo.diff_tree_to_tree(Some(&old_tree), Some(&new_tree), None)?;
55 let mut find_opts = git::raw::DiffFindOptions::new();
56
57 diff.find_similar(Some(&mut find_opts))?;
58 diff.stats()
59}
60
61pub fn ahead_behind(
63 repo: &git::raw::Repository,
64 revision_oid: Oid,
65 head_oid: Oid,
66) -> anyhow::Result<term::Line> {
67 let (a, b) = repo.graph_ahead_behind(revision_oid, head_oid)?;
68 if a == 0 && b == 0 {
69 return Ok(term::Line::new(term::format::dim("up to date")));
70 }
71
72 let ahead = term::format::positive(a);
73 let behind = term::format::negative(b);
74
75 Ok(term::Line::default()
76 .item("ahead ")
77 .item(ahead)
78 .item(", behind ")
79 .item(behind))
80}
81
82pub fn branches(target: &Oid, repo: &git::raw::Repository) -> anyhow::Result<Vec<String>> {
84 let mut branches: Vec<String> = vec![];
85
86 for r in repo.references()?.flatten() {
87 if !r.is_branch() {
88 continue;
89 }
90 if let (Some(oid), Some(name)) = (&r.target(), &r.shorthand()) {
91 if oid == target {
92 branches.push(name.to_string());
93 };
94 };
95 }
96 Ok(branches)
97}
98
99#[inline]
100pub fn try_branch(reference: git::raw::Reference<'_>) -> anyhow::Result<git::raw::Branch> {
101 let branch = if reference.is_branch() {
102 git::raw::Branch::wrap(reference)
103 } else {
104 anyhow::bail!("cannot create patch from detached head; aborting")
105 };
106 Ok(branch)
107}