1 use std::path::PathBuf; 2 3 use anyhow::bail; 4 use clap::Parser; 5 use libpijul::changestore::ChangeStore; 6 use libpijul::{DepsTxnT, GraphTxnT, MutTxnTExt, TxnT}; 7 use libpijul::{HashMap, HashSet}; 8 use log::*; 9 10 use crate::progress::PROGRESS; 11 use crate::repository::Repository; 12 13 #[derive(Parser, Debug)] 14 pub struct Apply { 15 /// Set the repository where this command should run. Defaults to the first ancestor of the current directory that contains a `.pijul` directory. 16 #[clap(long = "repository")] 17 repo_path: Option<PathBuf>, 18 /// Apply change to this channel 19 #[clap(long = "channel")] 20 channel: Option<String>, 21 /// Only apply the dependencies of the change, not the change itself. Only applicable for a single change. 22 #[clap(long = "deps-only")] 23 deps_only: bool, 24 /// The change that need to be applied. If this value is missing, read the change in text format on the standard input. 25 change: Vec<String>, 26 } 27 28 impl Apply { run(self) -> Result<(), anyhow::Error>29 pub fn run(self) -> Result<(), anyhow::Error> { 30 let repo = Repository::find_root(self.repo_path)?; 31 let txn = repo.pristine.arc_txn_begin()?; 32 let cur = txn 33 .read() 34 .current_channel() 35 .unwrap_or(crate::DEFAULT_CHANNEL) 36 .to_string(); 37 let channel_name = if let Some(ref c) = self.channel { 38 c 39 } else { 40 cur.as_str() 41 }; 42 let is_current_channel = channel_name == cur; 43 let channel = if let Some(channel) = txn.read().load_channel(&channel_name)? { 44 channel 45 } else { 46 bail!("Channel {:?} not found", channel_name) 47 }; 48 let mut hashes = Vec::new(); 49 for ch in self.change.iter() { 50 hashes.push(if let Ok(h) = txn.read().hash_from_prefix(ch) { 51 h.0 52 } else { 53 let change = libpijul::change::Change::deserialize(&ch, None); 54 match change { 55 Ok(mut change) => repo 56 .changes 57 .save_change(&mut change, |_, _| Ok::<_, anyhow::Error>(()))?, 58 Err(libpijul::change::ChangeError::Io(e)) => { 59 if let std::io::ErrorKind::NotFound = e.kind() { 60 let mut changes = repo.changes_dir.clone(); 61 super::find_hash(&mut changes, &ch)? 62 } else { 63 return Err(e.into()); 64 } 65 } 66 Err(e) => return Err(e.into()), 67 } 68 }) 69 } 70 if hashes.is_empty() { 71 let mut change = std::io::BufReader::new(std::io::stdin()); 72 let mut change = libpijul::change::Change::read(&mut change, &mut HashMap::default())?; 73 hashes.push( 74 repo.changes 75 .save_change(&mut change, |_, _| Ok::<_, anyhow::Error>(()))?, 76 ) 77 } 78 if self.deps_only { 79 if hashes.len() > 1 { 80 bail!("--deps-only is only applicable to a single change") 81 } 82 let mut channel = channel.write(); 83 txn.write() 84 .apply_deps_rec(&repo.changes, &mut channel, hashes.last().unwrap())?; 85 } else { 86 let mut channel = channel.write(); 87 let mut txn = txn.write(); 88 for hash in hashes.iter() { 89 txn.apply_change_rec(&repo.changes, &mut channel, hash)? 90 } 91 } 92 93 let mut touched = HashSet::default(); 94 let txn_ = txn.read(); 95 for d in hashes.iter() { 96 if let Some(int) = txn_.get_internal(&d.into())? { 97 debug!("int = {:?}", int); 98 for inode in txn_.iter_rev_touched(int)? { 99 debug!("{:?}", inode); 100 let (int_, inode) = inode?; 101 if int_ < int { 102 continue; 103 } else if int_ > int { 104 break; 105 } 106 touched.insert(*inode); 107 } 108 } 109 } 110 std::mem::drop(txn_); 111 112 if is_current_channel { 113 let mut touched_files = Vec::with_capacity(touched.len()); 114 let txn_ = txn.read(); 115 for i in touched { 116 if let Some((path, _)) = 117 libpijul::fs::find_path(&repo.changes, &*txn_, &*channel.read(), false, i)? 118 { 119 touched_files.push(path) 120 } else { 121 touched_files.clear(); 122 break; 123 } 124 } 125 std::mem::drop(txn_); 126 PROGRESS 127 .borrow_mut() 128 .unwrap() 129 .push(crate::progress::Cursor::Spin { 130 i: 0, 131 pre: "Outputting repository".into(), 132 }); 133 let mut conflicts = Vec::new(); 134 for path in touched_files.iter() { 135 conflicts.extend( 136 libpijul::output::output_repository_no_pending( 137 &repo.working_copy, 138 &repo.changes, 139 &txn, 140 &channel, 141 &path, 142 true, 143 None, 144 num_cpus::get(), 145 0, 146 )? 147 .into_iter(), 148 ); 149 } 150 if !touched_files.is_empty() { 151 conflicts.extend( 152 libpijul::output::output_repository_no_pending( 153 &repo.working_copy, 154 &repo.changes, 155 &txn, 156 &channel, 157 "", 158 true, 159 None, 160 num_cpus::get(), 161 0, 162 )? 163 .into_iter(), 164 ); 165 } 166 PROGRESS.join(); 167 super::print_conflicts(&conflicts)?; 168 } 169 txn.commit()?; 170 Ok(()) 171 } 172 } 173