radicle_cli/terminal/patch/
common.rs1use anyhow::anyhow;
2
3use radicle::git;
4use radicle::git::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<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::fmt::Qualified) -> git::fmt::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::fmt::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
35 .raw()
36 .merge_base(head_oid.into(), target_oid.into())?;
37
38 if head_oid == merge_base {
39 anyhow::bail!("commits are already included in the target branch; nothing to do");
40 }
41
42 Ok((get_branch(qualified_ref), (target_oid)))
43}
44
45pub fn diff_stats(
48 repo: &git::raw::Repository,
49 old: &Oid,
50 new: &Oid,
51) -> Result<git::raw::DiffStats, git::raw::Error> {
52 let old = repo.find_commit(old.into())?;
53 let new = repo.find_commit(new.into())?;
54 let old_tree = old.tree()?;
55 let new_tree = new.tree()?;
56 let mut diff = repo.diff_tree_to_tree(Some(&old_tree), Some(&new_tree), None)?;
57 let mut find_opts = git::raw::DiffFindOptions::new();
58
59 diff.find_similar(Some(&mut find_opts))?;
60 diff.stats()
61}
62
63pub fn ahead_behind(
65 repo: &git::raw::Repository,
66 revision_oid: Oid,
67 head_oid: Oid,
68) -> anyhow::Result<term::Line> {
69 let (a, b) = repo.graph_ahead_behind(revision_oid.into(), head_oid.into())?;
70 if a == 0 && b == 0 {
71 return Ok(term::Line::new(term::format::dim("up to date")));
72 }
73
74 let ahead = term::format::positive(a);
75 let behind = term::format::negative(b);
76
77 Ok(term::Line::default()
78 .item("ahead ")
79 .item(ahead)
80 .item(", behind ")
81 .item(behind))
82}
83
84pub fn branches(target: &Oid, repo: &git::raw::Repository) -> anyhow::Result<Vec<String>> {
86 let mut branches: Vec<String> = vec![];
87
88 for r in repo.references()?.flatten() {
89 if !r.is_branch() {
90 continue;
91 }
92 if let (Some(oid), Some(name)) = (&r.target(), &r.shorthand()) {
93 if target == oid {
94 branches.push(name.to_string());
95 };
96 };
97 }
98 Ok(branches)
99}
100
101#[inline]
102pub fn try_branch(reference: git::raw::Reference<'_>) -> anyhow::Result<git::raw::Branch<'_>> {
103 let branch = if reference.is_branch() {
104 git::raw::Branch::wrap(reference)
105 } else {
106 anyhow::bail!("cannot create patch from detached head; aborting")
107 };
108 Ok(branch)
109}