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