1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
use std::path::Path;
use anyhow::bail;
use clap::Parser;
use log::*;
use crate::commands::common_opts::RepoAndChannel;
use crate::commands::load_channel;
use pijul_core::HashMap;
use pijul_core::changestore::ChangeStore;
use pijul_core::{MutTxnTExt, TxnT};
use pijul_interaction::{OUTPUT_MESSAGE, Spinner};
#[derive(Parser, Debug)]
pub struct Apply {
#[clap(flatten)]
base: RepoAndChannel,
/// Only apply the dependencies of the change, not the change itself. Only applicable for a single change.
#[clap(long = "deps-only")]
deps_only: bool,
/// The change that need to be applied. If this value is missing, read the change in text format on the standard input.
change: Vec<String>,
}
impl Apply {
pub fn repository_path(&mut self) -> Option<&Path> {
self.base.repo_path()
}
pub fn run(mut self) -> Result<(), anyhow::Error> {
let repo = self.base.find_root()?;
let txn = repo.pristine.arc_txn_begin()?;
let (channel, is_current_channel) = load_channel(self.base.channel(), &*txn.read())?;
let mut hashes = Vec::new();
if self.change.is_empty() {
let mut change = std::io::BufReader::new(std::io::stdin());
let mut change =
pijul_core::change::Change::read(&mut change, &mut HashMap::default())?;
hashes.push(
repo.changes
.save_change(&mut change, |_, _| Ok::<_, anyhow::Error>(()))?,
)
}
// The working copy reflects the channel as it is now: once the changes
// are applied, unrecorded changes are recorded against this fork of it.
let base = if is_current_channel {
Some(pijul_remote::fork_pending_base(
&mut *txn.write(),
&channel,
)?)
} else {
None
};
for ch in self.change.iter() {
hashes.push(if let Ok(h) = txn.read().hash_from_prefix(ch) {
h.0
} else {
let change = pijul_core::change::Change::deserialize(&ch, None);
match change {
Ok(mut change) => repo
.changes
.save_change(&mut change, |_, _| Ok::<_, anyhow::Error>(()))?,
Err(pijul_core::change::ChangeError::Io(e)) => {
if let std::io::ErrorKind::NotFound = e.kind() {
let mut changes = repo.changes_dir.clone();
super::find_hash(&mut changes, &ch)?
} else {
return Err(e.into());
}
}
Err(e) => return Err(e.into()),
}
})
}
if self.deps_only {
if hashes.len() > 1 {
bail!("--deps-only is only applicable to a single change")
}
let mut channel = channel.write();
let hash = hashes.last().unwrap();
txn.write()
.apply_deps_rec(&repo.changes, &mut channel, hash)?;
} else {
let mut txnw = txn.write();
for hash in hashes.iter() {
// Honor `replaces`: supersede an amended predecessor on the
// channel before applying — in this same txn, so a failure
// aborts the whole thing. Shared with pull/push via the core
// `unrecord_superseded` helper. (`unrecord` locks the channel
// itself, so this runs with no write-guard held.)
txnw.unrecord_superseded(&repo.changes, &channel, hash)?;
{
let mut channelw = channel.write();
txnw.apply_change_rec(&repo.changes, &mut channelw, hash)?;
}
}
}
if let Some(base) = base {
let applied: Vec<_> = hashes
.iter()
.map(|h| pijul_remote::CS::Change(*h))
.collect();
let touched = pijul_remote::touched_inodes(&*txn.read(), &channel, &applied)?;
// Where the output can overwrite unrecorded changes, as the working
// copy still is: the files these changes create have no such place.
let prefixes = pijul_remote::touched_prefixes(
&*txn.read(),
&repo.changes,
base.channel(),
&touched,
)?;
debug!("touched prefixes {:?}", prefixes);
let _output_spinner = Spinner::new(OUTPUT_MESSAGE)?;
{
let mut state = pijul_core::RecordBuilder::new();
prefixes.record(
&mut state,
&txn,
base.channel(),
&repo.working_copy,
&repo.changes,
)?;
let rec = state.finish();
if !rec.actions.is_empty() {
debug!("actions {:#?}", rec.actions);
bail!("Applying this patch would delete unrecorded changes, aborting")
}
}
// Nothing is unrecorded there, but any output drops the files added
// and never recorded from the tree, unless a pending patch holds them.
let (hash, mut covered) = pijul_remote::pending_touched(
txn.clone(),
Some(base),
&channel,
&repo.working_copy,
&repo.changes,
&Default::default(),
&Default::default(),
)?;
covered.extend(prefixes);
let conflicts = pijul_remote::output_touched(
&repo.working_copy,
&repo.changes,
&txn,
&channel,
&touched,
&Default::default(),
&covered,
true,
)?;
super::print_conflicts(&conflicts)?;
if let Some(h) = hash {
let mut touched_inodes = pijul_core::unrecord::TouchedInodes::new();
txn.write()
.unrecord(&repo.changes, &channel, &h, 0, &mut touched_inodes)?;
// The pending patch is ephemeral: drop its change file.
repo.changes.del_change(&h)?;
txn.write()
.touch_inodes(&repo.working_copy, &touched_inodes)?;
}
}
txn.commit()?;
Ok(())
}
}