1 use std::path::PathBuf; 2 3 use super::{make_changelist, parse_changelist}; 4 use anyhow::{anyhow, bail}; 5 use clap::Parser; 6 use libpijul::changestore::ChangeStore; 7 use libpijul::*; 8 use log::debug; 9 10 use crate::repository::Repository; 11 12 #[derive(Parser, Debug)] 13 pub struct Unrecord { 14 /// Set the repository where this command should run. Defaults to the first ancestor of the current directory that contains a `.pijul` directory. 15 #[clap(long = "repository")] 16 repo_path: Option<PathBuf>, 17 /// Unrecord changes from this channel instead of the current channel 18 #[clap(long = "channel")] 19 channel: Option<String>, 20 /// Also undo the changes in the working copy (preserving unrecorded changes if there are any) 21 #[clap(long = "reset")] 22 reset: bool, 23 /// Show N changes in a text editor if no <change-id>s were given. 24 /// Defaults to the value 25 /// of `unrecord_changes` in your global configuration. 26 #[clap(long = "show-changes", value_name = "N", conflicts_with("change-id"))] 27 show_changes: Option<usize>, 28 /// The hash of a change (unambiguous prefixes are accepted) 29 change_id: Vec<String>, 30 } 31 32 impl Unrecord { run(self) -> Result<(), anyhow::Error>33 pub fn run(self) -> Result<(), anyhow::Error> { 34 let mut repo = Repository::find_root(self.repo_path)?; 35 debug!("{:?}", repo.config); 36 let txn = repo.pristine.arc_txn_begin()?; 37 let cur = txn 38 .read() 39 .current_channel() 40 .unwrap_or(crate::DEFAULT_CHANNEL) 41 .to_string(); 42 let channel_name = if let Some(ref c) = self.channel { 43 c 44 } else { 45 cur.as_str() 46 }; 47 let is_current_channel = cur == channel_name; 48 let channel = if let Some(channel) = txn.read().load_channel(&channel_name)? { 49 channel 50 } else { 51 bail!("No such channel: {:?}", channel_name); 52 }; 53 let mut hashes = Vec::new(); 54 55 if self.change_id.is_empty() { 56 // No change ids were given, present a list for choosing 57 // The number can be set in the global config or passed as a command-line option 58 let number_of_changes = if let Some(n) = self.show_changes { 59 n 60 } else { 61 let (cfg, _) = crate::config::Global::load()?; 62 cfg.unrecord_changes.ok_or_else(|| { 63 anyhow!( 64 "Can't determine how many changes to show. \ 65 Please set the `unrecord_changes` option in \ 66 your global config or run `pijul unrecord` \ 67 with the `--show-changes` option." 68 ) 69 })? 70 }; 71 let txn = txn.read(); 72 let hashes_ = txn 73 .reverse_log(&*channel.read(), None)? 74 .map(|h| (h.unwrap().1).0.into()) 75 .take(number_of_changes) 76 .collect::<Vec<_>>(); 77 let o = make_changelist(&repo.changes, &hashes_, "unrecord")?; 78 for h in parse_changelist(&edit::edit_bytes(&o[..])?).iter() { 79 hashes.push((*h, *txn.get_internal(&h.into())?.unwrap())) 80 } 81 } else { 82 let txn = txn.read(); 83 for c in self.change_id.iter() { 84 let (hash, cid) = txn.hash_from_prefix(c)?; 85 hashes.push((hash, cid)) 86 } 87 }; 88 let channel_ = channel.read(); 89 let mut changes: Vec<(Hash, ChangeId, Option<u64>)> = Vec::new(); 90 { 91 let txn = txn.read(); 92 for (hash, change_id) in hashes { 93 let n = txn 94 .get_changeset(txn.changes(&channel_), &change_id) 95 .unwrap(); 96 if n.is_none() { 97 bail!("Change not in channel: {:?}", hash) 98 } 99 changes.push((hash, change_id, n.map(|&x| x.into()))); 100 } 101 } 102 debug!("changes: {:?}", changes); 103 std::mem::drop(channel_); 104 let pending_hash = if self.reset { 105 super::pending(txn.clone(), &channel, &mut repo)? 106 } else { 107 None 108 }; 109 changes.sort_by(|a, b| b.2.cmp(&a.2)); 110 for (hash, change_id, _) in changes { 111 let channel_ = channel.read(); 112 let txn_ = txn.read(); 113 for p in txn_.iter_revdep(&change_id)? { 114 let (p, d) = p?; 115 if p < &change_id { 116 continue; 117 } else if p > &change_id { 118 break; 119 } 120 if txn_.get_changeset(txn_.changes(&channel_), d)?.is_some() { 121 let dep: Hash = txn_.get_external(d)?.unwrap().into(); 122 if Some(dep) == pending_hash { 123 bail!( 124 "Cannot unrecord change {} because unrecorded changes depend on it", 125 hash.to_base32() 126 ); 127 } else { 128 bail!( 129 "Cannot unrecord change {} because {} depend on it", 130 hash.to_base32(), 131 dep.to_base32() 132 ); 133 } 134 } 135 } 136 std::mem::drop(channel_); 137 std::mem::drop(txn_); 138 txn.write().unrecord(&repo.changes, &channel, &hash, 0)?; 139 } 140 141 if self.reset && is_current_channel { 142 libpijul::output::output_repository_no_pending( 143 &repo.working_copy, 144 &repo.changes, 145 &txn, 146 &channel, 147 "", 148 true, 149 None, 150 num_cpus::get(), 151 0, 152 )?; 153 } 154 if let Some(h) = pending_hash { 155 txn.write().unrecord(&repo.changes, &channel, &h, 0)?; 156 if cfg!(feature = "keep-changes") { 157 repo.changes.del_change(&h)?; 158 } 159 } 160 txn.commit()?; 161 Ok(()) 162 } 163 } 164