1use std::fmt::Write;
12
13use eyre::Context;
14use tracing::instrument;
15
16use crate::core::effects::Effects;
17use crate::core::eventlog::{
18 is_gc_ref, CommitActivityStatus, EventCursor, EventLogDb, EventReplayer,
19};
20use crate::core::formatting::Pluralize;
21use crate::git::{NonZeroOid, Reference, Repo};
22
23pub fn find_dangling_references<'repo>(
26 repo: &'repo Repo,
27 event_replayer: &EventReplayer,
28 event_cursor: EventCursor,
29) -> eyre::Result<Vec<Reference<'repo>>> {
30 let mut result = Vec::new();
31 for reference in repo.get_all_references()? {
32 let reference_name = reference.get_name()?;
33 if !is_gc_ref(&reference_name) {
34 continue;
35 }
36
37 let commit = match reference.peel_to_commit()? {
41 Some(commit) => commit,
42 None => continue,
43 };
44
45 match event_replayer.get_cursor_commit_activity_status(event_cursor, commit.get_oid()) {
46 CommitActivityStatus::Active => {
47 }
49 CommitActivityStatus::Inactive => {
50 }
53 CommitActivityStatus::Obsolete => {
54 result.push(reference)
56 }
57 }
58 }
59 Ok(result)
60}
61
62#[instrument]
74pub fn mark_commit_reachable(repo: &Repo, commit_oid: NonZeroOid) -> eyre::Result<()> {
75 let ref_name = format!("refs/branchless/{commit_oid}");
76 eyre::ensure!(
77 Reference::is_valid_name(&ref_name),
78 format!("Invalid ref name to mark commit as reachable: {ref_name}")
79 );
80
81 if repo.find_commit(commit_oid)?.is_some() {
85 repo.create_reference(
86 &ref_name.into(),
87 commit_oid,
88 true,
89 "branchless: marking commit as reachable",
90 )
91 .wrap_err("Creating reference")?;
92 }
93
94 Ok(())
95}
96
97#[instrument]
101pub fn gc(effects: &Effects) -> eyre::Result<()> {
102 let repo = Repo::from_current_dir()?;
103 let conn = repo.get_db_conn()?;
104 let event_log_db = EventLogDb::new(&conn)?;
105 let event_replayer = EventReplayer::from_event_log_db(effects, &repo, &event_log_db)?;
106 let event_cursor = event_replayer.make_default_cursor();
107
108 writeln!(
109 effects.get_output_stream(),
110 "branchless: collecting garbage"
111 )?;
112 let dangling_references = find_dangling_references(&repo, &event_replayer, event_cursor)?;
113 let num_dangling_references = Pluralize {
114 determiner: None,
115 amount: dangling_references.len(),
116 unit: ("dangling reference", "dangling references"),
117 }
118 .to_string();
119 for mut reference in dangling_references.into_iter() {
120 reference.delete()?;
121 }
122
123 writeln!(
124 effects.get_output_stream(),
125 "branchless: {num_dangling_references} deleted",
126 )?;
127 Ok(())
128}