use std::path::PathBuf;
use super::{make_changelist, parse_changelist};
use crate::remote::CS;
use crate::repository::Repository;
use anyhow::{anyhow, bail};
use clap::Parser;
use libpijul::changestore::ChangeStore;
use libpijul::*;
use log::debug;
#[derive(Parser, Debug)]
pub struct Unrecord {
#[clap(long = "repository")]
repo_path: Option<PathBuf>,
#[clap(long = "channel")]
channel: Option<String>,
#[clap(long = "reset")]
reset: bool,
#[clap(long = "show-changes", value_name = "N", conflicts_with("change-id"))]
show_changes: Option<usize>,
change_id: Vec<String>,
}
impl Unrecord {
pub fn run(self) -> Result<(), anyhow::Error> {
let mut repo = Repository::find_root(self.repo_path)?;
debug!("{:?}", repo.config);
let txn = repo.pristine.arc_txn_begin()?;
let cur = txn
.read()
.current_channel()
.unwrap_or(crate::DEFAULT_CHANNEL)
.to_string();
let channel_name = if let Some(ref c) = self.channel {
c
} else {
cur.as_str()
};
let is_current_channel = cur == channel_name;
let channel = if let Some(channel) = txn.read().load_channel(&channel_name)? {
channel
} else {
bail!("No such channel: {:?}", channel_name);
};
let mut hashes = Vec::new();
if self.change_id.is_empty() {
let number_of_changes = if let Some(n) = self.show_changes {
n
} else if let Some(n) = repo.config.unrecord_changes {
n
} else {
let (cfg, _) = crate::config::Global::load()?;
cfg.unrecord_changes.ok_or_else(|| {
anyhow!(
"Can't determine how many changes to show. \
Please set the `unrecord_changes` option in \
your global config or run `pijul unrecord` \
with the `--show-changes` option."
)
})?
};
let txn = txn.read();
let hashes_ = txn
.reverse_log(&*channel.read(), None)?
.map(|h| CS::Change((h.unwrap().1).0.into()))
.take(number_of_changes)
.collect::<Vec<_>>();
let o = make_changelist(&repo.changes, &hashes_, "unrecord")?;
for h in parse_changelist(&edit::edit_bytes(&o[..])?, &hashes_).iter() {
if let CS::Change(h) = h {
hashes.push((*h, *txn.get_internal(&h.into())?.unwrap()))
}
}
} else {
let txn = txn.read();
for c in self.change_id.iter() {
let (hash, cid) = txn.hash_from_prefix(c)?;
hashes.push((hash, cid))
}
};
let channel_ = channel.read();
let mut changes: Vec<(Hash, ChangeId, Option<u64>)> = Vec::new();
{
let txn = txn.read();
for (hash, change_id) in hashes {
let n = txn
.get_changeset(txn.changes(&channel_), &change_id)
.unwrap();
if n.is_none() {
bail!("Change not in channel: {:?}", hash)
}
changes.push((hash, change_id, n.map(|&x| x.into())));
}
}
debug!("changes: {:?}", changes);
std::mem::drop(channel_);
let pending_hash = if self.reset {
super::pending(txn.clone(), &channel, &mut repo)?
} else {
None
};
changes.sort_by(|a, b| b.2.cmp(&a.2));
for (hash, change_id, _) in changes {
let channel_ = channel.read();
let txn_ = txn.read();
for p in txn_.iter_revdep(&change_id)? {
let (p, d) = p?;
if p < &change_id {
continue;
} else if p > &change_id {
break;
}
if txn_.get_changeset(txn_.changes(&channel_), d)?.is_some() {
let dep: Hash = txn_.get_external(d)?.unwrap().into();
if Some(dep) == pending_hash {
bail!(
"Cannot unrecord change {} because unrecorded changes depend on it",
hash.to_base32()
);
} else {
bail!(
"Cannot unrecord change {} because {} depend on it",
hash.to_base32(),
dep.to_base32()
);
}
}
}
std::mem::drop(channel_);
std::mem::drop(txn_);
txn.write().unrecord(&repo.changes, &channel, &hash, 0)?;
}
if self.reset && is_current_channel {
libpijul::output::output_repository_no_pending(
&repo.working_copy,
&repo.changes,
&txn,
&channel,
"",
true,
None,
num_cpus::get(),
0,
)?;
}
if let Some(h) = pending_hash {
txn.write().unrecord(&repo.changes, &channel, &h, 0)?;
if !cfg!(feature = "keep-changes") {
repo.changes.del_change(&h)?;
}
}
txn.commit()?;
Ok(())
}
}