use std::collections::HashMap;
use std::fmt::Write;
use std::time::SystemTime;
use git_branchless_opts::{ResolveRevsetOptions, Revset};
use lib::core::dag::{union_all, Dag};
use lib::core::effects::Effects;
use lib::core::eventlog::{CommitActivityStatus, Event};
use lib::core::eventlog::{EventLogDb, EventReplayer};
use lib::core::formatting::{Glyphs, Pluralize};
use lib::core::repo_ext::RepoExt;
use lib::core::rewrite::move_branches;
use lib::git::{CategorizedReferenceName, GitRunInfo, MaybeZeroOid, NonZeroOid, Repo};
use lib::util::{ExitCode, EyreExitOr};
use tracing::instrument;
use git_branchless_revset::resolve_commits;
#[instrument]
pub fn hide(
effects: &Effects,
git_run_info: &GitRunInfo,
revsets: Vec<Revset>,
resolve_revset_options: &ResolveRevsetOptions,
no_delete_branches: bool,
recursive: bool,
) -> EyreExitOr<()> {
let now = SystemTime::now();
let glyphs = Glyphs::detect();
let repo = Repo::from_current_dir()?;
let references_snapshot = repo.get_references_snapshot()?;
let conn = repo.get_db_conn()?;
let event_log_db = EventLogDb::new(&conn)?;
let event_replayer = EventReplayer::from_event_log_db(effects, &repo, &event_log_db)?;
let event_cursor = event_replayer.make_default_cursor();
let mut dag = Dag::open_and_sync(
effects,
&repo,
&event_replayer,
event_cursor,
&references_snapshot,
)?;
let delete_branches = !no_delete_branches;
let commit_sets =
match resolve_commits(effects, &repo, &mut dag, &revsets, resolve_revset_options) {
Ok(commit_sets) => commit_sets,
Err(err) => {
err.describe(effects)?;
return Ok(Err(ExitCode(1)));
}
};
let commits = union_all(&commit_sets);
let commits = if recursive {
dag.filter_visible_commits(dag.query_descendants(commits)?)?
} else {
commits
};
let commits = dag.sort(&commits)?;
let commits = commits
.into_iter()
.map(|commit_oid| repo.find_commit_or_fail(commit_oid))
.collect::<Result<Vec<_>, _>>()?;
let timestamp = now.duration_since(SystemTime::UNIX_EPOCH)?.as_secs_f64();
let event_tx_id = event_log_db.make_transaction_id(now, "hide")?;
let events = commits
.iter()
.map(|commit| Event::ObsoleteEvent {
timestamp,
event_tx_id,
commit_oid: commit.get_oid(),
})
.collect();
event_log_db.add_events(events)?;
let cursor = event_replayer.make_default_cursor();
let num_commits = commits.len();
for commit in commits.iter() {
writeln!(
effects.get_output_stream(),
"Hid commit: {}",
glyphs.render(commit.friendly_describe(&glyphs)?)?,
)?;
if let CommitActivityStatus::Obsolete =
event_replayer.get_cursor_commit_activity_status(cursor, commit.get_oid())
{
writeln!(
effects.get_output_stream(),
"(It was already hidden, so this operation had no effect.)"
)?;
}
}
if delete_branches {
let head_info = repo.get_head_info()?;
let abandoned_branches: HashMap<NonZeroOid, MaybeZeroOid> = commits
.iter()
.map(|commit| (commit.get_oid(), MaybeZeroOid::Zero))
.collect();
if let Some(head_oid) = head_info.oid {
if abandoned_branches.contains_key(&head_oid) {
repo.detach_head(&head_info)?;
}
}
move_branches(
effects,
git_run_info,
&repo,
event_tx_id,
&abandoned_branches,
)?;
}
let mut abandoned_branches: Vec<String> = commits
.iter()
.filter_map(|commit| {
references_snapshot
.branch_oid_to_names
.get(&commit.get_oid())
})
.flatten()
.map(|branch_name| CategorizedReferenceName::new(branch_name).render_suffix())
.collect();
if !abandoned_branches.is_empty() {
abandoned_branches.sort_unstable();
writeln!(
effects.get_output_stream(),
"{} {}: {}",
if delete_branches {
"Deleted"
} else {
"Abandoned"
},
Pluralize {
determiner: None,
amount: abandoned_branches.len(),
unit: ("branch", "branches"),
},
abandoned_branches.join(", ")
)?;
}
let delete_branches_message = if delete_branches && !abandoned_branches.is_empty() {
format!(
" and restore {}",
Pluralize {
determiner: None,
amount: abandoned_branches.len(),
unit: ("branch", "branches"),
}
)
} else {
String::new()
};
writeln!(
effects.get_output_stream(),
"To unhide {}{}, run: git undo",
Pluralize {
determiner: Some(("this", "these")),
amount: num_commits,
unit: ("commit", "commits"),
},
delete_branches_message
)?;
Ok(Ok(()))
}
#[instrument]
pub fn unhide(
effects: &Effects,
revsets: Vec<Revset>,
resolve_revset_options: &ResolveRevsetOptions,
recursive: bool,
) -> EyreExitOr<()> {
let now = SystemTime::now();
let glyphs = Glyphs::detect();
let repo = Repo::from_current_dir()?;
let references_snapshot = repo.get_references_snapshot()?;
let conn = repo.get_db_conn()?;
let event_log_db = EventLogDb::new(&conn)?;
let event_replayer = EventReplayer::from_event_log_db(effects, &repo, &event_log_db)?;
let event_cursor = event_replayer.make_default_cursor();
let mut dag = Dag::open_and_sync(
effects,
&repo,
&event_replayer,
event_cursor,
&references_snapshot,
)?;
let commit_sets =
match resolve_commits(effects, &repo, &mut dag, &revsets, resolve_revset_options) {
Ok(commit_sets) => commit_sets,
Err(err) => {
err.describe(effects)?;
return Ok(Err(ExitCode(1)));
}
};
let commits = union_all(&commit_sets);
let commits = if recursive {
dag.query_descendants(commits)?
.intersection(&dag.query_obsolete_commits())
} else {
commits
};
let commits = dag.sort(&commits)?;
let commits = commits
.into_iter()
.map(|commit_oid| repo.find_commit_or_fail(commit_oid))
.collect::<Result<Vec<_>, _>>()?;
let timestamp = now.duration_since(SystemTime::UNIX_EPOCH)?.as_secs_f64();
let event_tx_id = event_log_db.make_transaction_id(now, "unhide")?;
let events = commits
.iter()
.map(|commit| Event::UnobsoleteEvent {
timestamp,
event_tx_id,
commit_oid: commit.get_oid(),
})
.collect();
event_log_db.add_events(events)?;
let cursor = event_replayer.make_default_cursor();
let num_commits = commits.len();
for commit in commits {
writeln!(
effects.get_output_stream(),
"Unhid commit: {}",
glyphs.render(commit.friendly_describe(&glyphs)?)?,
)?;
if let CommitActivityStatus::Active =
event_replayer.get_cursor_commit_activity_status(cursor, commit.get_oid())
{
writeln!(
effects.get_output_stream(),
"(It was not hidden, so this operation had no effect.)"
)?;
}
}
writeln!(
effects.get_output_stream(),
"To hide {}, run: git undo",
Pluralize {
determiner: Some(("this", "these")),
amount: num_commits,
unit: ("commit", "commits"),
},
)?;
Ok(Ok(()))
}