I24UEJQLCH2SOXA4UHIYWTRDCHSOPU7AFTRUOTX7HZIAV4AZKYEQC
O4DNWMPDUWI6SKYOZTQKCSX6MSR73CTGCUSM65TSQYVOUSAAS6KAC
IYJZVLETBAQDDELENH3FX7ZTOC3HY4UJ3AMC3MACW6O7ZCWZTR6AC
NZIK34IMY3L5YMFISBLHUL5ATENBL35VOZJW4EHPINZEL6IQE4UQC
YWUZQU3TBJJMUVMX2JJW2D2JLTN57YJA36YJXKHBF7UJ6GEBTWUAC
VL7ZYKHBPKLNY5SA5QBW56SJ7LBBCKCGV5UAYLVF75KY6PPBOD4AC
OU243LABJJZ3MQHYW2A2MYKW7KZLTZGHJJXDR2BIIIHXT5BSYN3AC
PKIHBUGT3N4BUZ2QP2UWJI4ICOIF6EZVXBFKG753SOTYBAKSVTFAC
NF4O25IELPL2JJBVM3UXMOWP2VIFWXII7PQD3PX5SUW3RYHKP5XQC
I6DVZEFUMGH6BFOLGBPM6J4PL5I4PAAODJYG7REXYPDHPKPBLDTAC
ADPAFSMYUBTKSK63EPCY5WQGROJQWFCWO4UFPWY3ZXD5ZNH26P2QC
YTQS4ES362EJ27OE45CE5HLY7ZU57YLVKRMDRJ2OXT623VM5WOBQC
VE2UWMW42VWMOYZWJCJZBX4NYJO37E6WNISFBUXZFLQ5HWN3B4VQC
YDTN6BGI5TFRJFM3N3Y2J463GKFHU5L3DPBQXBDIQJDWXU5ELKMQC
QV66H4YAO5ASPDY72R3FBFMSFE2DXTKU54W4OSNBY5KPLXNIFPIAC
2D7P2VKJASU7QDQZHGCLBIT6G2V5WUFYLWTCEVVEI2EZHGM6XYRAC
X7OHUPL5VYT6ECER2KNGRNFLRX7SBZOM5QWSQ4PBO2UPIE7XM6MAC
MFTN7GBWZNQAFHKER57MLZAJGVEAHV2GYAQN2QTDHTPCEURDVIGQC
EEOOHGQQK43J36LQJDSS3UK56M54DXPYE4VB3K4A2XAYGOYDJAXAC
SXEYMYF7P4RZMZ46WPL4IZUTSQ2ATBWYZX7QNVMS3SGOYXYOHAGQC
JL4WKA5PBKXRNAMETYO4I52QKASQ3COYHH2JKGA7W5YLIRZZH53AC
YN63NUZO4LVJ7XPMURDULTXBVJKW5MVCTZ24R7Z52QMHO3HPDUVQC
CCLLB7OIFNFYJZTG3UCI7536TOCWSCSXR67VELSB466R24WLJSDAC
TKEVOH7HXON7SOBGXTUDHAHO2U2GPTQRNESP6ERKUQAS526OZIRAC
UDHP4ZVBQZT2VBURB2MDCU2IZDNMCAFSIUKWRBDQ5BWMFKSN2LYQC
L4JXJHWXYNCL4QGJXNKKTOKKTAXKKXBJUUY7HFZGEUZ5A2V5H34QC
76PCXGML77EZWTRI5E6KHLVRAFTJ2AB5YRN5EKOYNAPKTWY2KCGAC
MU5GSJAW65PEG3BRYUKZ7O37BPHW3MOX3S5E2RFOXKGUOJEEDQ5AC
VMPAOJS2ZFOLNXALHWSVM5AFENWX6ZUACB45EJV3HXI7DQNAZPHQC
LGEJSLTYI7Y2CYC3AN6ECMT3D3MTWCAKZPVQEG5MPM2OBW5FQ46AC
62XVBWPYCBULZ2IUWF36JVHAPMKCGQC53PQQ53AAGZGC2KT7VQRQC
Y6EVFMTA6FOH3OQH6QCSWMI3F6SYZT2FSHO6GF4M3ICENDCWFM4QC
ZBNKSYA6PW4DSPC3NCRUZLVHW2GNXMCSDSAGEIKHGHDGGZRBH7ZQC
5DVRL6MFXQOCPOZMYSKBERMRRVUTYRL2SRGRTU2MH4IEOFCDKM3QC
YAJAXIV5VL263Z6FYLKFPROB3MQPRPH22P44GRGRVGEP56HOMBOAC
EGSVRZJVIBSPYAI65A25CH5RYAGL4PUP3B24VSRUS3M4WIUCZWHAC
SLJ3OHD4F6GJGZ3SV2D7DMR3PXYHPSI64X77KZ3RJ24EGEX6ZNQAC
I52XSRUH5RVHQBFWVMAQPTUSPAJ4KNVID2RMI3UGCVKFLYUO6WZAC
3AMEP2Y5J6GA4AWQONF4JVA3XSR3ASLHHKMYG44R72SOUY3UQCDAC
GHO6DWPILBBTL6CVZKERJBTFL3EY6ZT4YM4E5R4S6YPGVFKFHCVAC
MHQBEHJDJ7MUW46HIS24AZFBC4DZDKZNBVBOBOBPML6GGFIS4LQAC
3KRGVQFUWFHPOGZOXVTJYNCM4XBRVYITAEOVPKBSAZ5GZIUO5KVQC
XA23FMQM2AI7RMR36AYN7UNP2D5JWVJMJPHURWZO7URM7H46PU6AC
SPA2OL5ITFMLB5P2WL342QAU2FXPKSFS4XHAMW6HYWOGSGLO2MJAC
VNBLGT6GAN2AHKRFKTKED7WNDDRGNULY5H343ZYV3ETSDZZKGBTAC
QMTANHVNRPQ5IX66FYQBFRBDCTN6YKMNCO6OHTQ6QCUASPWWXJKAC
Y7YAFMFFJY3SQ3GYN3SS4V3FZWMH3B5L65AXQBXOR5XARSMF5JJQC
WZVCLZKY34KQBQU6YBGJLQCDADBQ67LQVDNRVCMQVY3O3C3EIWSQC
VQPAUKBQ2POZKL7CZFAZK5ZQKEBYL27XZYZWYUSH5AH25KK6DWKAC
MDADYULS5AWVMTJDGYCGNQTN6T7XJDRUBDTFILDY5MLF6I2PE5NAC
KWAGWB73AMLJFK2Z7SBKHHKKHFRX7AQKXCWDN2MBX72RYCNMB36QC
ZHABNS3S6FSINO74FOI5KHYXYDTBPO4FQTTYTUS7NNKEVVNLYC4AC
DJYHARZ7CSRMX6ZFM6P52SM2EC57VTSHWAIMFSD7Q3EL7UYZGLXQC
MF3WAHBIH6Q2F7ZOKWPEJF6VGSKJITWLR3Z64GTD6YQZNA5EATWQC
G6S6PWZEFJK7ARWBIFKDU6VYC5DCJ2YFJMWZOLLWWKU52R2QPXZAC
HSVGP2G4D2F56DS3YKZLSYPS4A5BNGH4NTAXAOZ57OCXFM3E5AYAC
44BN7FWSIXKG75IJUTCXLJE7VANNQFPRHQXTPLQHFU7AKGLSPQRAC
IIV3EL2XYI2X7HZWKXEXQFAE3R3KC2Q7SGOT3Q332HSENMYVF32QC
OJZWJUF2TCGZ7RFVY6FPKBS5P3C4BGHZDPVH775OHVNVFMJICKNQC
4VWXL6KQGYGDUQRCVJCEVIV6CKJSEIYDX4YF33OX6EDNKJNEGD2AC
ZRUPLBBTT4S6S7A3LOAHG4ONYEGPA5CFO4L2XBCNFKK45MWX3BDAC
HMMMKONLCRAXVT7SO2ITTFDOJIQKKVSRIZPXYVPDC34RCBHWMHVAC
5BRU2RRWOQBMS2V3RQM7PRFR5UILYZ73GISHAKJA6KIZGC5M2MFAC
BD5PC25AB5MKVIYDFSDGRZ4YGX4PKW4SMZ3YAYAPNA5HLDVJUR3QC
6DOXSHWGKJIMIPFCNLASGKBAJCJMJULW5HFRZAZ67EYSMXXGJ3KAC
VIHXB7SGRETFPHPYZFSRGOFRXEO4RZY57WDZSU6IAUEJRU3HPKQAC
2K7JLB4Z7BS5VFNWD4DO3MKYU7VNPA5MTVHVSDI3FQZ5ICM6XM6QC
SQVWP4LU7AAJSEIHK5CNNUK3XBUVT3FGIJIOPTKMR53PO2P4ARNQC
HR3WK6A7KKILCHI2CD2BZSZQJUS44MJIT2K3WPKJBHUXGRKAQDRQC
LYTVEPH3W5UHF7MAYFWBT6NVNC42HEVKJGGMFDKUDZDNDOI33YJQC
B3QWIGDERNMB3M6P5WTWP5CN2DB3KCS7MVTEPC2XVJ237ZXVQGMAC
Q45QHPO4HDTEZF2W4UDZSYYQ46BPEIWSW4GJILZR5HTJNLKXJABQC
QE64ATLZWMKHYABCD3VA547PYXCK6YN3K7RE2TX3SCQNKG7XLVAQC
VLPIKNFSMJXOG37QYRGUJC6YFMZXZUFDADQV4PYASKKDQOJ24MZAC
A6R6SGCPLFM45QNWJLISFBR3EEXVITYHCWEUOPNH4UIGIWJRTZAQC
R3H7D42UZ446V5TO2574BMAQQAYYJPEIMSZVDPAGVIYU2COJSWBAC
JRENVH5DF2F4SOV7UNJENFA7VDI3H63XK76R3LFZK6QCW7JIBLSQC
ENKQ3QZGH2QW246C7GSZRKYLODJOQHKZZSYV7QHB7VPOFP5PASVQC
TPEH2XNBS5RO4IEVKENVF6P65AH7IX64KK2JAYMSJT3J5GXO67EAC
VO5OQW4W2656DIYYRNZ3PO7TQ4JOKQ3GVWE5ALUTYVMX3WMXJOYQC
7FFFKQZU3TFXWL45TILYNX5A7AC7HBK526SD5DZGYCELN76YE7TAC
4OCC6D42GZYRDLH3NSKXMJTRKXP7UZ6Z3YNGCNUT7NT6WBDBCBIAC
JP3BYVXXWFBVQ23MEHJ3LE36AN26P6OCZALKUXMNLHS2TSTM3NKAC
X243Z3Y54ULINQMMRIKLHRV5T237B7VUOAHVJ7DMPOQ6A6GQXY2AC
7UPL3Y2A5QOBU6VIUHNWEFGSGQ5CMWGDGOVLRZ53UARXG3TDGLMAC
NO2QPRFLGCYUDXYJTOY3S3NZJCCLAFOQUHITKDZ7LSZLRLOV5W3QC
BZSC7VMYSFRXDHDDAMCDR6X67FN5VWIBOSE76BQLX7OCVOJFUA3AC
3M7WBE24JTPTHWQOU5PO2ZJYKPKEH2F6R4M6RWIRFG334PQEA55QC
ZAEUSICJC3YOWGF6NZEQCQ34PHPRSBCJEP7FIWE6VIWJGVU734HQC
CXM5CBS27BL35Z6TRCI7OS4AHWVJ4VFND7HECGAUC74ZQ5KFZXLAC
EQLDTLXVCARE36EJE3S6SNEVTW2JJY4EYD36EX7WSIFLG2XMKKQAC
CCFJ7VO3I73FE3MZRS5RSDRYNZVW7AXC345P4BXS7JIL2TU3LQJQC
VYHHOEYHO67JNJEODX5L3CQFIV3DAXZBBIQUOMCWJDYF3VWICDNQC
LLT3GY6ULCVHMO3VUSVI5H4O244Z3ULOWLTW2IGJXIA2TWIHJDSQC
WKX5S4Z4DOB5S6A6X5V6ECZFCHQUMWRGX5XT4FBOG57P6HPWK7CAC
VXZNQQHCDC6MBLUMNDJVPM4I7XWTDYBPZZNCPZCA6EJ3GS5WAQGQC
6YMDOZIB5LVYLFIDGN2WNT5JTHEAMS4TFPVDEZ3OWXWOKJOC5QDAC
7ZFRYVVQQGJYG3POPWJWL3CDW37YDXZYZQC3OSWFHWEUSEMYQ4EQC
TZ42DX3BML5C3O5Z6OBVNBCHSIIHT6AOJPD6ICOLOP4LPYFXQN2QC
DNQHXWRZF6EWII3RGQ6HPGFD47BOOFH4FCQHSCACBS3RLQO3DNIQC
BXD3IQYNMKMI5BTANCF6NZHZP4CKPWADGJMBT2R3WTMKVKONT5QAC
IM6UFPOZHZTBMESRXGBALOAWJWUUDGLP2TVLSZ3RZPSJITKB3R7QC
ATZ3BWSEFJBLVGDUZFESRNHVCIO6ZRZ3ALPANZSVGVO7A5BUAFQQC
NA5I4WYNE2O3LPSHXGWXW7XL4YNYFDREEGDOP6LJ5HJXTQDXM7BAC
KVBLRDOUFRYB6BPOQJDD7OVBYMTTPDAUX7CJ5DC3U7WFRI5OLWRAC
JRSBH6HTYXSIZKHW6SGWAF3JCEPMFTUG4JZUSUSF73ODEGFLAAJAC
ZXTHL45OYLOICXTXXEQ6AMNSLMQFZ6BFUJWMO6LZOSDK7ERROC5AC
7PM25EXLNQ6JUUIZNTAOQYNNIZNG6TJREEBUSAIC3FIOE7FHETSAC
KDF6FJRVF72L274BEUJCTUKRFMNL6BDZMTVKDPEYGFX4TC3YOVSQC
BZCGXVS77ZS3N4QPLIHNWZ3YFVV7H4PXQD3U6RN5ZFVOC7QL7MBQC
HQ56ADNSNBCCEBNK5PE5ZVBKKBGWY3ATGFWEYPXJKJBJDUJ2XKQQC
43AJ37IXON6PMMYRZM6OB2MJXPYCNIWW2XBDVNBXIRH5DD7JDTVQC
NMX52UOGRCY2O7HT7Q45KWISOHNV4PEEMLDYDBJ4QPDIMTVKKJ6AC
CZX6TRWR53F2BRLKSUTBPEONKS65IYNBO2FYB4HSWF6OK7DOEJGAC
G734WNM64AR5BLAZMN5MDPKSFTYXTUQR6MAGB32NRBC5FXFRWSJAC
HDGRZISM2SS4TK5BMNGDIYG22SOXAZRTTC6YFIOPY4LSO53QDWZQC
RXNT67OTDNFTBYXS6ECDAZ26PRTDROASNYTR6IEXYQUO4K5YNXYQC
SAGSYAPXQ2T6GC3B3TNRPNFTZMS7UMME6YQGSF5MOIM66S5NKB2QC
HKEOO4QJ5EACX37IJG76GEUMNSZMFW4VRKA4IVBCGR52ZQSYTN6QC
3WIQYEISUMGOL5FY4LCWOJS55CTTQA7WMJZXAE3Q4GQUTBFLAE7QC
OUWD436ATBTZJR53B6XDSI5SXRRNZV7YGRUEA5ACHZC2RUDP7G5QC
23LVKATNTT74YKHG7KJM6SBO2IVZEV24TQ46ZJIHQ2IXONWNVXJAC
UFCZKKLXVYQYQBYENCAFHY3ZPPSDAJJAIREZSYNQM4QNXV6G6RXAC
33SQMZYXPV2A3F7P6WBFKFO2BTQV6THSQXOLV3PCZASC6OTKVHTQC
txn.output_repository_no_pending(&mut repo, &store, &mut channel, "", true, None)?;
let txn = Arc::new(RwLock::new(txn));
let wc = Arc::new(repo);
libpijul::output::output_repository_no_pending(
wc,
&store,
txn.clone(),
channel.clone(),
"",
true,
None,
num_cpus::get(),
)?;
let txn = if let Ok(txn) = Arc::try_unwrap(txn) {
txn.into_inner().unwrap()
} else {
unreachable!()
};
let mut txn = repo.pristine.mut_txn_begin()?;
if let Some(mut channel) = txn.load_channel(channel_name)? {
let pending_hash = if self.reset {
super::pending(&mut txn, &mut channel, &mut repo)?
} else {
None
};
let txn = repo.pristine.mut_txn_begin()?;
let channel = if let Some(channel) = txn.load_channel(channel_name)? {
channel
} else {
bail!("No such channel: {:?}", channel_name);
};
let txn = Arc::new(RwLock::new(txn));
let pending_hash = if self.reset {
super::pending(txn.clone(), &channel, &mut repo)?
} else {
None
};
let mut txn = if let Ok(txn) = Arc::try_unwrap(txn) {
txn.into_inner().unwrap()
} else {
unreachable!()
};
if self.change_id.is_empty() {
// No change ids were given, present a list for choosing
// The number can be set in the global config or passed as a command-line option
let number_of_changes = if let Some(n) = self.show_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 hashes_ = txn
.reverse_log(&channel.borrow(), None)?
.map(|h| (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[..])?).iter() {
hashes.push((*h, *txn.get_internal(&h.into())?.unwrap()))
}
if self.change_id.is_empty() {
// No change ids were given, present a list for choosing
// The number can be set in the global config or passed as a command-line option
let number_of_changes = if let Some(n) = self.show_changes {
n
for c in self.change_id.iter() {
let (hash, cid) = txn.hash_from_prefix(c)?;
hashes.push((hash, cid))
}
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 channel_ = channel.borrow();
let mut changes: Vec<(Hash, ChangeId, Option<u64>)> = Vec::new();
for (hash, change_id) in hashes {
let n = txn
.get_changeset(txn.changes(&channel_), &change_id)
.unwrap();
changes.push((hash, change_id, n.map(|&x| x.into())));
let hashes_ = txn
.reverse_log(&*channel.read()?, None)?
.map(|h| (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[..])?).iter() {
hashes.push((*h, *txn.get_internal(&h.into())?.unwrap()))
}
} else {
for c in self.change_id.iter() {
let (hash, cid) = txn.hash_from_prefix(c)?;
hashes.push((hash, cid))
std::mem::drop(channel_);
changes.sort_by(|a, b| b.2.cmp(&a.2));
for (hash, change_id, _) in changes {
let channel_ = channel.borrow();
for p in txn.iter_revdep(&change_id)? {
let (p, d) = p?;
if p < &change_id {
continue;
} else if p > &change_id {
break;
};
let channel_ = channel.read()?;
let mut changes: Vec<(Hash, ChangeId, Option<u64>)> = Vec::new();
for (hash, change_id) in hashes {
let n = txn
.get_changeset(txn.changes(&channel_), &change_id)
.unwrap();
changes.push((hash, change_id, n.map(|&x| x.into())));
}
std::mem::drop(channel_);
changes.sort_by(|a, b| b.2.cmp(&a.2));
for (hash, change_id, _) in changes {
let channel_ = channel.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()
);
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()
);
}
}
if self.reset && is_current_channel {
txn.output_repository_no_pending(
&mut repo.working_copy,
&repo.changes,
&mut channel,
"",
true,
None,
)?;
std::mem::drop(channel_);
txn.unrecord(&repo.changes, &channel, &hash)?;
}
let txn = Arc::new(RwLock::new(txn));
if self.reset && is_current_channel {
libpijul::output::output_repository_no_pending(
repo.working_copy.clone(),
&repo.changes,
txn.clone(),
channel.clone(),
"",
true,
None,
num_cpus::get(),
)?;
}
if let Some(h) = pending_hash {
txn.write().unwrap().unrecord(&repo.changes, &channel, &h)?;
if cfg!(feature = "keep-changes") {
repo.changes.del_change(&h)?;
if let Some(h) = pending_hash {
txn.unrecord(&repo.changes, &mut channel, &h)?;
if cfg!(feature = "keep-changes") {
repo.changes.del_change(&h)?;
}
}
use libpijul::{ChannelTxnT, DepsTxnT, MutTxnT, MutTxnTExt, TxnT, TxnTExt};
use log::debug;
use libpijul::{ChannelTxnT, DepsTxnT, MutTxnT, TxnT, TxnTExt};
use log::*;
let mut repo = Repository::find_root(self.repo_path).await?;
let mut txn = repo.pristine.mut_txn_begin()?;
let (channel_name, _) = repo.config.get_current_channel(self.channel.as_deref());
let repo = Repository::find_root(self.repo_path).await?;
let txn = repo.pristine.mut_txn_begin()?;
let config_path = repo.config_path();
let mut config = repo.config;
let (channel_name, _) = config.get_current_channel(self.channel.as_deref());
} else {
let (current_channel, _) = repo.config.get_current_channel(None);
if self.channel.as_deref() == Some(current_channel) {
if !overwrite_changes {
return Ok(());
}
} else if self.channel.is_some() {
if !self.files.is_empty() {
bail!("Cannot use --channel with individual paths. Did you mean --dry-run?")
return Ok(());
}
let txn = Arc::new(RwLock::new(txn));
let (current_channel, _) = config.get_current_channel(None);
if self.channel.as_deref() == Some(current_channel) {
if !overwrite_changes {
return Ok(());
}
} else if self.channel.is_some() {
if !self.files.is_empty() {
bail!("Cannot use --channel with individual paths. Did you mean --dry-run?")
}
let channel = {
let txn = txn.read().unwrap();
txn.load_channel(current_channel)?
};
if let Some(channel) = channel {
let mut state = libpijul::RecordBuilder::new();
state.record(
txn.clone(),
libpijul::Algorithm::default(),
channel.clone(),
repo.working_copy.clone(),
&repo.changes,
"",
num_cpus::get(),
)?;
let rec = state.finish();
debug!("actions = {:?}", rec.actions);
if !rec.actions.is_empty() {
bail!("Cannot change channel, as there are unrecorded changes.")
if let Some(mut channel) = txn.load_channel(current_channel)? {
let mut state = libpijul::RecordBuilder::new();
txn.record(
&mut state,
libpijul::Algorithm::default(),
&mut channel,
&mut repo.working_copy,
&repo.changes,
"",
)?;
let rec = state.finish();
debug!("actions = {:?}", rec.actions);
if !rec.actions.is_empty() {
bail!("Cannot change channel, as there are unrecorded changes.")
}
}
let now = std::time::Instant::now();
if self.files.is_empty() {
if self.channel.is_none() || self.channel.as_deref() == Some(current_channel) {
let last_modified = last_modified(&txn, &channel.borrow());
txn.output_repository_no_pending(
&mut repo.working_copy,
&repo.changes,
&mut channel,
"",
true,
Some(last_modified),
)?;
txn.touch_channel(&mut channel.borrow_mut(), None);
txn.commit()?;
return Ok(());
}
let mut inodes = HashSet::new();
if let Some(cur) = txn.load_channel(current_channel)? {
let mut changediff = HashSet::new();
let (a, b, s) = libpijul::pristine::last_common_state(
&txn,
&cur.borrow(),
&channel.borrow(),
)?;
debug!("last common state {:?}", s);
changes_after(&txn, &cur.borrow(), a, &mut changediff, &mut inodes)?;
changes_after(&txn, &channel.borrow(), b, &mut changediff, &mut inodes)?;
}
let now = std::time::Instant::now();
if self.files.is_empty() {
if self.channel.is_none() || self.channel.as_deref() == Some(current_channel) {
let last_modified = last_modified(&*txn.read().unwrap(), &*channel.read()?);
libpijul::output::output_repository_no_pending(
repo.working_copy.clone(),
&repo.changes,
txn.clone(),
channel.clone(),
"",
true,
Some(last_modified),
num_cpus::get(),
)?;
let mut txn = if let Ok(txn) = Arc::try_unwrap(txn) {
txn.into_inner().unwrap()
} else {
unreachable!()
};
txn.touch_channel(&mut *channel.write()?, None);
txn.commit()?;
return Ok(());
}
let mut inodes = HashSet::new();
let txn_ = txn.read().unwrap();
if let Some(cur) = txn_.load_channel(current_channel)? {
let mut changediff = HashSet::new();
let (a, b, s) = libpijul::pristine::last_common_state(
&*txn_,
&*cur.read()?,
&*channel.read()?,
)?;
debug!("last common state {:?}", s);
changes_after(&*txn_, &*cur.read()?, a, &mut changediff, &mut inodes)?;
changes_after(&*txn_, &*channel.read()?, b, &mut changediff, &mut inodes)?;
}
if self.channel.is_some() {
repo.config.current_channel = self.channel;
repo.save_config()?;
if self.channel.is_some() {
config.current_channel = self.channel;
config.save(&config_path)?;
}
let mut paths = Vec::with_capacity(inodes.len());
for pos in inodes.iter() {
if let Some((path, _)) =
libpijul::fs::find_path(&repo.changes, &*txn_, &*channel.read()?, false, *pos)?
{
paths.push(path)
} else {
paths.clear();
break;
let mut paths = Vec::with_capacity(inodes.len());
for pos in inodes.iter() {
if let Some((path, _)) = libpijul::fs::find_path(
&repo.changes,
&txn,
&channel.borrow(),
false,
*pos,
)? {
paths.push(path)
} else {
paths.clear();
break;
}
}
PROGRESS
.borrow_mut()
.unwrap()
.push(crate::progress::Cursor::Spin {
i: 0,
pre: "Outputting repository".into(),
});
for path in paths.iter() {
debug!("resetting {:?}", path);
txn.output_repository_no_pending(
&mut repo.working_copy,
&repo.changes,
&mut channel,
&path,
true,
None,
)?;
}
if paths.is_empty() {
txn.output_repository_no_pending(
&mut repo.working_copy,
&repo.changes,
&mut channel,
"",
true,
None,
)?;
}
PROGRESS.join();
} else {
PROGRESS
.borrow_mut()
.unwrap()
.push(crate::progress::Cursor::Spin {
i: 0,
pre: "Outputting repository".into(),
});
for root in self.files.iter() {
let root = std::fs::canonicalize(&root)?;
let path = root.strip_prefix(&repo_path)?.to_str().unwrap();
txn.output_repository_no_pending(
&mut repo.working_copy,
&repo.changes,
&mut channel,
&path,
true,
None,
)?;
}
PROGRESS.join();
}
PROGRESS
.borrow_mut()
.unwrap()
.push(crate::progress::Cursor::Spin {
i: 0,
pre: "Outputting repository".into(),
});
std::mem::drop(txn_);
for path in paths.iter() {
debug!("resetting {:?}", path);
libpijul::output::output_repository_no_pending(
repo.working_copy.clone(),
&repo.changes,
txn.clone(),
channel.clone(),
&path,
true,
None,
num_cpus::get(),
)?;
}
if paths.is_empty() {
libpijul::output::output_repository_no_pending(
repo.working_copy,
&repo.changes,
txn.clone(),
channel,
"",
true,
None,
num_cpus::get(),
)?;
}
PROGRESS.join();
} else {
PROGRESS
.borrow_mut()
.unwrap()
.push(crate::progress::Cursor::Spin {
i: 0,
pre: "Outputting repository".into(),
});
for root in self.files.iter() {
let root = std::fs::canonicalize(&root)?;
let path = root.strip_prefix(&repo_path)?.to_str().unwrap();
libpijul::output::output_repository_no_pending(
repo.working_copy.clone(),
&repo.changes,
txn.clone(),
channel.clone(),
&path,
true,
None,
num_cpus::get(),
)?;
use libpijul::pristine::ChannelMutTxnT;
use libpijul::{Base32, ChannelRef, ChannelTxnT, MutTxnT, MutTxnTExt, TxnT, TxnTExt};
use libpijul::{
Base32, ChannelMutTxnT, ChannelRef, ChannelTxnT, MutTxnT, MutTxnTExt, TxnT, TxnTExt,
};
use libpijul::{HashMap, HashSet};
mut txn: T,
channel: &mut ChannelRef<T>,
working_copy: &mut libpijul::working_copy::FileSystem,
txn: Arc<RwLock<T>>,
channel: ChannelRef<T>,
working_copy: Arc<libpijul::working_copy::FileSystem>,
let hash = super::pending(&mut txn, &mut channel, &mut repo)?;
let txn = Arc::new(RwLock::new(txn));
let hash = super::pending(txn.clone(), &mut channel, &mut repo)?;
let mut txn = if let Ok(txn) = Arc::try_unwrap(txn) {
txn.into_inner().unwrap()
} else {
unreachable!()
};
txn.output_repository_no_pending(
&mut repo.working_copy,
&repo.changes,
&mut channel,
path,
true,
None,
)?;
conflicts.extend(
libpijul::output::output_repository_no_pending(
repo.working_copy.clone(),
&repo.changes,
txn.clone(),
channel.clone(),
path,
true,
None,
num_cpus::get(),
)?
.into_iter(),
);
txn.output_repository_no_pending(
&mut repo.working_copy,
&repo.changes,
&mut channel,
"",
true,
None,
)?;
conflicts.extend(
libpijul::output::output_repository_no_pending(
repo.working_copy.clone(),
&repo.changes,
txn.clone(),
channel.clone(),
"",
true,
None,
num_cpus::get(),
)?
.into_iter(),
);
fn pending<T: libpijul::MutTxnTExt + libpijul::TxnT>(
txn: &mut T,
channel: &mut libpijul::ChannelRef<T>,
fn pending<T: libpijul::MutTxnTExt + libpijul::TxnT + Send + Sync + 'static>(
txn: std::sync::Arc<std::sync::RwLock<T>>,
channel: &libpijul::ChannelRef<T>,
if let Some(root) = self.root {
let (pos, _) = txn
.follow_oldest_path(&repo.changes, &channel, &root)
.unwrap();
libpijul::pristine::debug_root(
&txn,
&channel.borrow().graph,
pos.inode_vertex(),
std::io::stdout(),
true,
)?;
} else {
let channel = channel.borrow();
libpijul::pristine::debug(&txn, &channel.graph, std::io::stdout())?;
if !self.sanakirja_only {
libpijul::pristine::debug_inodes(&txn);
libpijul::pristine::debug_revinodes(&txn);
libpijul::pristine::debug_tree_print(&txn);
libpijul::pristine::debug_revtree_print(&txn);
if let Some(root) = self.root {
let (pos, _) = txn
.follow_oldest_path(&repo.changes, &channel, &root)
.unwrap();
libpijul::pristine::debug_root(
&txn,
&channel.read()?.graph,
pos.inode_vertex(),
std::io::stdout(),
true,
)?;
} else {
let channel = channel.read()?;
libpijul::pristine::debug(&txn, &channel.graph, std::io::stdout())?;
}
libpijul::pristine::check_alive_debug(&repo.changes, &txn, &*channel.read()?, 0)?;
txn.output_repository_no_pending(
&mut repo.working_copy,
let config_path = repo.config_path();
let mut config = repo.config;
let txn = Arc::new(RwLock::new(txn));
libpijul::output::output_repository_no_pending(
repo.working_copy.clone(),
let channel = channel?;
let channel = channel.borrow();
let name = txn.name(&channel);
let (_, channel) = channel?;
let channel = channel.read()?;
let name = txn.name(&*channel);
fn remove_path(&mut self, name: &str) -> Result<(), Self::Error>;
fn rename(&mut self, former: &str, new: &str) -> Result<(), Self::Error>;
fn set_permissions(&mut self, name: &str, permissions: u16) -> Result<(), Self::Error>;
fn write_file<A, E: std::error::Error, F: FnOnce(&mut dyn std::io::Write) -> Result<A, E>>(
&mut self,
file: &str,
writer: F,
) -> Result<A, WriteError<E>>;
fn remove_path(&self, name: &str) -> Result<(), Self::Error>;
fn rename(&self, former: &str, new: &str) -> Result<(), Self::Error>;
fn set_permissions(&self, name: &str, permissions: u16) -> Result<(), Self::Error>;
type Writer: std::io::Write;
fn write_file(&self, file: &str) -> Result<Self::Writer, Self::Error>;
fn create_dir_all(&mut self, file: &str) -> Result<(), Self::Error> {
if self.get_file(file).is_none() {
fn create_dir_all(&self, file: &str) -> Result<(), Self::Error> {
let m = self.0.lock().unwrap();
if m.get_file(file).is_none() {
fn remove_path(&mut self, path: &str) -> Result<(), Self::Error> {
self.remove_path_(path);
fn remove_path(&self, path: &str) -> Result<(), Self::Error> {
self.0.lock().unwrap().remove_path_(path);
fn write_file<A, E: std::error::Error, F: FnOnce(&mut dyn std::io::Write) -> Result<A, E>>(
&mut self,
file: &str,
writer: F,
) -> Result<A, WriteError<E>> {
match self.get_file_mut(file) {
type Writer = Writer;
fn write_file(&self, file: &str) -> Result<Self::Writer, Self::Error> {
let mut m = self.0.lock().unwrap();
match m.get_file_mut(file) {
pub struct Writer {
w: Arc<Mutex<Vec<u8>>>,
}
impl std::io::Write for Writer {
fn write(&mut self, b: &[u8]) -> Result<usize, std::io::Error> {
self.w.lock().unwrap().write(b)
}
fn flush(&mut self) -> Result<(), std::io::Error> {
Ok(())
}
}
fn write_file<A, E: std::error::Error, F: FnOnce(&mut dyn std::io::Write) -> Result<A, E>>(
&mut self,
file: &str,
writer: F,
) -> Result<A, WriteError<E>> {
type Writer = std::io::BufWriter<std::fs::File>;
fn write_file(&self, file: &str) -> Result<Self::Writer, Self::Error> {
let br = br?;
let br = br.borrow();
if txn.name(&br) != txn.name(&channel)
&& txn.get_changeset(txn.changes(&br), &change_id)?.is_some()
{
let (name, br) = br?;
if name.as_str() == txn.name(&channel) {
continue;
}
let br = br.read().unwrap();
if txn.get_changeset(txn.changes(&br), &change_id)?.is_some() {
let mut txn = env.mut_txn_begin();
let mut txn2 = env2.mut_txn_begin();
let mut txn3 = env3.mut_txn_begin();
let mut txn = env.mut_txn_begin().unwrap();
let mut txn2 = env2.mut_txn_begin().unwrap();
let mut txn3 = env3.mut_txn_begin().unwrap();
fn record_all_output<T: MutTxnT, R: WorkingCopy, P: ChangeStore + Clone + Send + 'static>(
repo: &mut R,
changes: &P,
txn: &mut T,
channel: &mut ChannelRef<T>,
fn record_all_output<
T: MutTxnT + Send + Sync + 'static,
R: WorkingCopy + Send + Sync + 'static,
P: ChangeStore + Clone + Send + Sync + 'static,
>(
repo: Arc<R>,
changes: Arc<P>,
txn: Arc<RwLock<T>>,
channel: &ChannelRef<T>,
let hash = record_all(repo, changes, txn, channel, prefix)?;
output::output_repository_no_pending(repo, changes, txn, channel, "", true, None).unwrap();
let hash = record_all(
repo.as_ref(),
changes.as_ref(),
&mut *txn.write().unwrap(),
channel,
prefix,
)?;
output::output_repository_no_pending(repo, changes, txn, channel.clone(), "", true, None, 1)
.unwrap();
let mut txn = env.mut_txn_begin();
txn.add_file("dir/file").unwrap();
let mut channel = txn.open_or_create_channel("main").unwrap();
let mut txn = Arc::new(RwLock::new(env.mut_txn_begin().unwrap()));
txn.write().unwrap().add_file("dir/file").unwrap();
let mut channel = txn.write().unwrap().open_or_create_channel("main").unwrap();
record_all_output(&mut repo, &changes, &mut txn, &mut channel, "").unwrap();
debug_to_file(&txn, &channel.borrow(), "debug0").unwrap();
let files: Vec<_> = crate::fs::iter_working_copy(&txn, Inode::ROOT)
record_all_output(
repo.clone(),
changes.clone(),
txn.clone(),
&channel.clone(),
"",
)
.unwrap();
debug_to_file(&*txn.read().unwrap(), &channel, "debug0").unwrap();
let files: Vec<_> = crate::fs::iter_working_copy(&*txn.read().unwrap(), Inode::ROOT)
record_all_output(&mut repo, &changes, &mut txn, &mut channel, "").unwrap();
debug_to_file(&txn, &channel.borrow(), "debug").unwrap();
record_all_output(repo, changes, txn, &channel, "").unwrap();
debug_to_file(&*txn.read().unwrap(), &channel, "debug").unwrap();
txn.add_file("dir2/file").unwrap();
txn.remove_file("dir2").unwrap();
assert!(crate::fs::iter_working_copy(&txn, Inode::ROOT).all(|f| f.unwrap().1 != "dir2"));
assert!(txn.remove_file("dir2").is_err());
txn.commit().unwrap();
txn.write().unwrap().add_file("dir2/file").unwrap();
txn.write().unwrap().remove_file("dir2").unwrap();
assert!(
crate::fs::iter_working_copy(&*txn.read().unwrap(), Inode::ROOT)
.all(|f| f.unwrap().1 != "dir2")
);
assert!(txn.write().unwrap().remove_file("dir2").is_err());
txn.write().unwrap().commit().unwrap();
let mut txn = env.mut_txn_begin();
txn.add_file("a/b/c/d/e")?;
let mut channel = txn.open_or_create_channel("main")?;
let mut txn = Arc::new(RwLock::new(env.mut_txn_begin().unwrap()));
txn.write().unwrap().add_file("a/b/c/d/e")?;
let mut channel = txn.write().unwrap().open_or_create_channel("main")?;
record_all_output(&mut repo, &changes, &mut txn, &mut channel, "")?;
debug_to_file(&txn, &channel.borrow(), "debug0").unwrap();
let files: Vec<_> = crate::fs::iter_working_copy(&txn, Inode::ROOT)
record_all_output(repo.clone(), changes.clone(), txn.clone(), &channel, "")?;
debug_to_file(&*txn.read().unwrap(), &channel, "debug0").unwrap();
let files: Vec<_> = crate::fs::iter_working_copy(&*txn.read().unwrap(), Inode::ROOT)
record_all_output(&mut repo, &changes, &mut txn, &mut channel, "")?;
debug_to_file(&txn, &channel.borrow(), "debug").unwrap();
record_all_output(repo.clone(), changes.clone(), txn.clone(), &channel, "")?;
debug_to_file(&*txn.read().unwrap(), &channel, "debug").unwrap();
let mut txn = env.mut_txn_begin();
txn.add_file("dir/file")?;
let mut channel = txn.open_or_create_channel("main")?;
let mut txn = Arc::new(RwLock::new(env.mut_txn_begin().unwrap()));
txn.write().unwrap().add_file("dir/file")?;
let mut channel = txn.write().unwrap().open_or_create_channel("main")?;
record_all_output(&mut repo, &changes, &mut txn, &mut channel, "").unwrap();
debug_to_file(&txn, &channel.borrow(), "debug").unwrap();
record_all_output(repo.clone(), changes.clone(), txn.clone(), &channel, "").unwrap();
debug_to_file(&*txn.read().unwrap(), &channel, "debug").unwrap();
repo.write_file::<_, std::io::Error, _>("dir/file", |w| {
w.write_all(b"a\nb\nc\n")?;
Ok(())
})?;
record_all_output(&mut repo, &changes, &mut txn, &mut channel, "").unwrap();
repo.write_file("dir/file")
.unwrap()
.write_all(b"a\nb\nc\n")
.unwrap();
record_all_output(repo.clone(), changes.clone(), txn.clone(), &channel, "").unwrap();
let mut txn = env.mut_txn_begin();
txn.add_file("dir/file")?;
let mut channel = txn.open_or_create_channel("main")?;
let mut txn = env.mut_txn_begin().unwrap();
txn.write().unwrap().add_file("dir/file")?;
let mut channel = txn.write().unwrap().open_or_create_channel("main")?;
let mut txn = env.mut_txn_begin();
txn.add_file("dir/file")?;
let mut channel = txn.open_or_create_channel("main")?;
let mut txn = env.mut_txn_begin().unwrap();
txn.write().unwrap().add_file("dir/file")?;
let mut channel = txn.write().unwrap().open_or_create_channel("main")?;
pub(crate) rec: Recorded,
pub(crate) redundant: Vec<(Vertex<ChangeId>, SerializedEdge)>,
recorded_inodes: HashMap<Inode, Position<Option<ChangeId>>>,
deleted_vertices: HashSet<Position<ChangeId>>,
former_parents: Vec<Parent>,
pub(crate) rec: Vec<Arc<Mutex<Recorded>>>,
recorded_inodes: Arc<Mutex<HashMap<Inode, Position<Option<ChangeId>>>>>,
deleted_vertices: Arc<Mutex<HashSet<Position<ChangeId>>>>,
rec: Recorded {
contents: Vec::new(),
actions: Vec::new(),
updatables: HashMap::new(),
largest_file: 0,
has_binary_files: false,
oldest_change: std::time::SystemTime::UNIX_EPOCH,
},
redundant: Vec::new(),
recorded_inodes: HashMap::new(),
deleted_vertices: HashSet::new(),
former_parents: Vec::new(),
rec: Vec::new(),
recorded_inodes: Arc::new(Mutex::new(HashMap::default())),
pub fn recorded(&mut self) -> Arc<Mutex<Recorded>> {
let m = Arc::new(Mutex::new(self.recorded_()));
self.rec.push(m.clone());
m
}
fn recorded_(&self) -> Recorded {
Recorded {
contents: self.contents.clone(),
actions: Vec::new(),
updatables: HashMap::default(),
largest_file: 0,
has_binary_files: false,
oldest_change: std::time::SystemTime::UNIX_EPOCH,
redundant: Vec::new(),
force_rediff: self.force_rediff,
deleted_vertices: self.deleted_vertices.clone(),
recorded_inodes: self.recorded_inodes.clone(),
}
}
pub fn finish(self) -> Recorded {
self.rec
pub fn finish(mut self) -> Recorded {
if self.rec.is_empty() {
self.recorded();
}
let mut it = self.rec.into_iter();
let mut result = if let Ok(rec) = Arc::try_unwrap(it.next().unwrap()) {
rec.into_inner().unwrap()
} else {
unreachable!()
};
for rec in it {
let rec = if let Ok(rec) = Arc::try_unwrap(rec) {
rec.into_inner().unwrap()
} else {
unreachable!()
};
let off = result.actions.len();
result.actions.extend(rec.actions.into_iter());
for (a, b) in rec.updatables {
result.updatables.insert(a + off, b);
}
result.largest_file = result.largest_file.max(rec.largest_file);
result.has_binary_files |= rec.has_binary_files;
result.oldest_change = result.oldest_change.min(rec.oldest_change);
result.redundant.extend(rec.redundant.into_iter())
}
debug!(
"result = {:?}, updatables = {:?}",
result.actions, result.updatables
);
result
fn get_inodes_<T: ChannelTxnT + TreeTxnT<TreeError = <T as GraphTxnT>::GraphError>>(
txn: Arc<RwLock<T>>,
channel: ChannelRef<T>,
inode: &Inode,
) -> Result<Option<Position<ChangeId>>, TxnErr<T::GraphError>> {
let txn = txn.read().unwrap();
let channel = channel.r.read().unwrap();
Ok(get_inodes(&*txn, &*channel, inode)?.map(|x| *x))
}
if txn
.get_changeset(txn.changes(channel), &vertex.change)?
.is_some()
if let Some(e) = iter_adjacent(
txn,
txn.graph(channel),
vertex.inode_vertex(),
EdgeFlags::PARENT,
EdgeFlags::all(),
)?
.next()
let work = Arc::new(Mutex::new(Tasks {
t: VecDeque::new(),
stop: false,
}));
let mut workers: Vec<std::thread::JoinHandle<()>> = Vec::new();
for t in 0..0 {
// n_workers - 1 {
let working_copy = working_copy.clone();
let changes = changes.clone();
let channel = channel.clone();
let work = work.clone();
let txn = txn.clone();
workers.push(std::thread::spawn(move || loop {
let (w, stop) = {
let mut work = work.lock().unwrap();
(work.t.pop_front(), work.stop)
};
if let Some((item, vertex, rec, new_papa)) = w {
// This parent has changed.
info!("record existing file {:?} on thread {:?}", item, t);
rec.lock()
.unwrap()
.record_existing_file(
txn.clone(),
diff_algorithm,
channel.clone(),
working_copy.clone(),
&changes,
&item,
new_papa,
vertex,
)
.unwrap();
} else if stop {
info!("stop {:?}", t);
break;
} else {
info!("yield {:?}", t);
std::thread::park_timeout(std::time::Duration::from_secs(1));
}
}))
}
let vertex = if let Some(vertex) = self.recorded_inodes.get(&item.inode) {
*vertex
let vertex: Option<Position<Option<ChangeId>>> = self
.recorded_inodes
.lock()
.unwrap()
.get(&item.inode)
.cloned();
let vertex = if let Some(vertex) = vertex {
vertex
txn,
txn.graph(channel),
working_copy,
&*txn,
txn.graph(&channel),
working_copy.as_ref(),
} else if let Some(&vertex) = get_inodes(txn, &channel, &item.inode)? {
self.delete_obsolete_children(
txn,
txn.graph(channel),
working_copy,
changes,
&item.full_path,
vertex,
)?;
self.record_existing_file(
txn,
diff_algorithm,
&channel,
working_copy,
changes,
&item,
vertex,
)?;
self.recorded_inodes.insert(item.inode, vertex.to_option());
} else if let Some(vertex) = get_inodes_(txn.clone(), channel.clone(), &item.inode)? {
{
let mut txn = txn.write().unwrap();
let mut channel = channel.r.write().unwrap();
let mut graph = txn.graph(&mut *channel);
self.delete_obsolete_children(
&mut *txn,
&mut graph,
working_copy.as_ref(),
changes,
&item.full_path,
vertex,
)?;
}
let rec = self.recorded();
let new_papa = {
let mut recorded = self.recorded_inodes.lock().unwrap();
recorded.insert(item.inode, vertex.to_option());
recorded.get(&item.papa).cloned()
};
let mut work = work.lock().unwrap();
work.t.push_back((item.clone(), vertex, rec, new_papa));
std::mem::drop(work);
for t in workers.iter() {
t.thread().unpark()
}
}
info!("stop work");
work.lock().unwrap().stop = true;
for t in workers.iter() {
t.thread().unpark()
}
loop {
let w = {
let mut work = work.lock().unwrap();
debug!("waiting, stop = {:?}", work.stop);
work.t.pop_front()
};
if let Some((item, vertex, rec, new_papa)) = w {
// This parent has changed.
info!("record existing file {:?}", item);
rec.lock()
.unwrap()
.record_existing_file(
txn.clone(),
diff_algorithm,
channel.clone(),
working_copy.clone(),
changes,
&item,
new_papa,
vertex,
)
.unwrap();
} else {
break;
}
}
for (n, t) in workers.into_iter().enumerate() {
debug!("WAITING {:?}", n);
t.join().unwrap()
fn delete_obsolete_children<
T: GraphTxnT + TreeTxnT<TreeError = <T as GraphTxnT>::GraphError>,
W: WorkingCopy,
C: ChangeStore,
>(
&mut self,
txn: &T,
channel: &T::Graph,
working_copy: &W,
changes: &C,
full_path: &str,
v: Position<ChangeId>,
) -> Result<(), RecordError<C::Error, W::Error, T::GraphError>>
where
<W as WorkingCopy>::Error: 'static,
{
if self.ignore_missing {
return Ok(());
}
let f0 = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
let f1 = f0 | EdgeFlags::PSEUDO;
debug!("delete_obsolete_children, v = {:?}", v);
for child in iter_adjacent(txn, channel, v.inode_vertex(), f0, f1)? {
let child = child?;
let child = txn.find_block(channel, child.dest()).unwrap();
for grandchild in iter_adjacent(txn, channel, *child, f0, f1)? {
let grandchild = grandchild?;
debug!("grandchild {:?}", grandchild);
let needs_deletion =
if let Some(inode) = txn.get_revinodes(&grandchild.dest(), None)? {
debug!("inode = {:?} {:?}", inode, txn.get_revtree(inode, None));
if let Some(path) = crate::fs::inode_filename(txn, *inode)? {
working_copy.file_metadata(&path).is_err()
} else {
true
}
} else {
true
};
if needs_deletion {
let mut name = Vec::new();
changes
.get_contents(
|p| txn.get_external(&p).unwrap().map(From::from),
*child,
&mut name,
)
.map_err(RecordError::Changestore)?;
let mut full_path = full_path.to_string();
if name.len() > 2 {
if let Ok(name) = std::str::from_utf8(&name[2..]) {
if !full_path.is_empty() {
full_path.push('/');
}
full_path.push_str(name);
}
}
// delete recursively.
let rec = self.recorded();
let mut rec = rec.lock().unwrap();
rec.record_deleted_file(
txn,
&channel,
working_copy,
&full_path,
grandchild.dest(),
)?
}
}
}
Ok(())
}
fn push_children<
'a,
T: ChannelTxnT + TreeTxnT<TreeError = <T as GraphTxnT>::GraphError>,
W: WorkingCopy,
C: ChangeStore,
>(
&mut self,
txn: &T,
channel: &T::Channel,
working_copy: &W,
item: &mut RecordItem,
components: &mut Components<'a>,
vertex: Position<Option<ChangeId>>,
stack: &mut Vec<(RecordItem, Components<'a>)>,
prefix: &str,
) -> Result<(), RecordError<C::Error, W::Error, T::GraphError>>
where
<W as crate::working_copy::WorkingCopy>::Error: 'static,
{
debug!("push_children, item = {:?}", item);
let comp = components.next();
let full_path = item.full_path.clone();
let fileid = OwnedPathId {
parent_inode: item.inode,
basename: SmallString::new(),
};
let mut has_matching_children = false;
for x in txn.iter_tree(&fileid, None)? {
let (fileid_, child_inode) = x?;
debug!("push_children {:?} {:?}", fileid_, child_inode);
if fileid_.parent_inode < fileid.parent_inode || fileid_.basename.is_empty() {
continue;
} else if fileid_.parent_inode > fileid.parent_inode {
break;
}
if let Some(comp) = comp {
if comp != fileid_.basename.as_str() {
continue;
}
}
has_matching_children = true;
let basename = fileid_.basename.as_str().to_string();
let full_path = if full_path.is_empty() {
basename.clone()
} else {
full_path.clone() + "/" + &basename
};
debug!("fileid_ {:?} child_inode {:?}", fileid_, child_inode);
if let Ok(meta) = working_copy.file_metadata(&full_path) {
stack.push((
RecordItem {
papa: item.inode,
inode: *child_inode,
v_papa: vertex,
basename,
full_path,
metadata: meta,
},
components.clone(),
));
} else if let Some(vertex) = get_inodes(txn, &channel, child_inode)? {
let rec = self.recorded();
let mut rec = rec.lock().unwrap();
rec.record_deleted_file(txn, txn.graph(channel), working_copy, &full_path, *vertex)?
}
}
if comp.is_some() && !has_matching_children {
debug!("comp = {:?}", comp);
return Err(RecordError::PathNotInRepo(prefix.to_string()));
}
Ok(())
}
}
fn modified_since_last_commit<T: ChannelTxnT, W: WorkingCopy>(
txn: &T,
channel: &T::Channel,
working_copy: &W,
prefix: &str,
) -> Result<bool, std::time::SystemTimeError> {
if let Ok(last_modified) = working_copy.modified_time(prefix) {
debug!(
"last_modified = {:?}, channel.last = {:?}",
last_modified
.duration_since(std::time::UNIX_EPOCH)?
.as_secs(),
txn.last_modified(channel)
);
Ok(last_modified
.duration_since(std::time::UNIX_EPOCH)?
.as_secs()
>= txn.last_modified(channel))
} else {
Ok(true)
}
}
impl Recorded {
let name_start = ChangePosition(self.rec.contents.len().into());
meta.write(&mut self.rec.contents).unwrap();
self.rec.contents.extend(item.basename.as_bytes());
let name_end = ChangePosition(self.rec.contents.len().into());
self.rec.contents.push(0);
let inode_pos = ChangePosition(self.rec.contents.len().into());
self.rec.contents.push(0);
let mut contents = self.contents.lock().unwrap();
let name_start = ChangePosition(contents.len().into());
// Push the metadata, big-endian.
contents.push((meta.0 >> 8) as u8);
contents.push((meta.0 & 0xff) as u8);
//
contents.extend(item.basename.as_bytes());
let name_end = ChangePosition(contents.len().into());
contents.push(0);
let inode_pos = ChangePosition(contents.len().into());
contents.push(0);
let start = ChangePosition(self.rec.contents.len().into());
working_copy.read_file(&item.full_path, &mut self.rec.contents)?;
let end = ChangePosition(self.rec.contents.len().into());
self.rec.largest_file = self.rec.largest_file.max(end.0.as_u64() - start.0.as_u64());
self.rec.has_binary_files |= {
let start = ChangePosition(contents.len().into());
working_copy.read_file(&item.full_path, &mut contents)?;
let end = ChangePosition(contents.len().into());
self.largest_file = self.largest_file.max(end.0.as_u64() - start.0.as_u64());
self.has_binary_files |= {
for name_ in iter_adjacent(txn, txn.graph(channel), vertex.inode_vertex(), f0, f1)? {
let txn_ = txn.read().unwrap();
let channel_ = channel.read().unwrap();
for name_ in iter_adjacent(
&*txn_,
txn_.graph(&*channel_),
vertex.inode_vertex(),
f0,
f1,
)? {
if self.former_parents.len() > 1
|| self.former_parents[0].basename != item.basename
|| self.former_parents[0].metadata != item.metadata
|| self.former_parents[0].parent != item.v_papa
if former_parents.len() > 1
|| former_parents[0].basename != item.basename
|| former_parents[0].metadata != item.metadata
|| former_parents[0].parent != item.v_papa
fn delete_obsolete_children<
T: GraphTxnT + TreeTxnT<TreeError = <T as GraphTxnT>::GraphError>,
W: WorkingCopy,
C: ChangeStore,
>(
&mut self,
txn: &T,
channel: &T::Graph,
working_copy: &W,
changes: &C,
full_path: &str,
v: Position<ChangeId>,
) -> Result<(), RecordError<C::Error, W::Error, T::GraphError>>
where
<W as WorkingCopy>::Error: 'static,
{
if self.ignore_missing {
return Ok(());
}
let f0 = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
let f1 = f0 | EdgeFlags::PSEUDO;
debug!("delete_obsolete_children, v = {:?}", v);
for child in iter_adjacent(txn, channel, v.inode_vertex(), f0, f1)? {
let child = child?;
let child = txn.find_block(channel, child.dest()).unwrap();
for grandchild in iter_adjacent(txn, channel, *child, f0, f1)? {
let grandchild = grandchild?;
debug!("grandchild {:?}", grandchild);
let needs_deletion =
if let Some(inode) = txn.get_revinodes(&grandchild.dest(), None)? {
debug!("inode = {:?} {:?}", inode, txn.get_revtree(inode, None));
if let Some(path) = crate::fs::inode_filename(txn, *inode)? {
working_copy.file_metadata(&path).is_err()
} else {
true
}
} else {
true
};
if needs_deletion {
let mut name = Vec::new();
changes
.get_contents(
|p| txn.get_external(&p).unwrap().map(From::from),
*child,
&mut name,
)
.map_err(RecordError::Changestore)?;
let mut full_path = full_path.to_string();
if name.len() > 2 {
if let Ok(name) = std::str::from_utf8(&name[2..]) {
if !full_path.is_empty() {
full_path.push('/');
}
full_path.push_str(name);
}
}
// delete recursively.
self.record_deleted_file(
txn,
&channel,
working_copy,
&full_path,
grandchild.dest(),
)?
}
}
}
Ok(())
}
fn push_children<
'a,
T: ChannelTxnT + TreeTxnT<TreeError = <T as GraphTxnT>::GraphError>,
W: WorkingCopy,
C: ChangeStore,
>(
&mut self,
txn: &T,
channel: &T::Channel,
working_copy: &W,
item: &mut RecordItem<'a>,
vertex: Position<Option<ChangeId>>,
stack: &mut Vec<RecordItem<'a>>,
prefix: &str,
) -> Result<(), RecordError<C::Error, W::Error, T::GraphError>>
where
<W as crate::working_copy::WorkingCopy>::Error: 'static,
{
debug!("push_children, item = {:?}", item);
let comp = item.components.next();
let full_path = item.full_path.clone();
let fileid = OwnedPathId {
parent_inode: item.inode,
basename: SmallString::new(),
};
let mut has_matching_children = false;
for x in txn.iter_tree(&fileid, None)? {
let (fileid_, child_inode) = x?;
debug!("push_children {:?} {:?}", fileid_, child_inode);
if fileid_.parent_inode < fileid.parent_inode || fileid_.basename.is_empty() {
continue;
} else if fileid_.parent_inode > fileid.parent_inode {
break;
}
if let Some(comp) = comp {
if comp != fileid_.basename.as_str() {
continue;
}
}
has_matching_children = true;
let basename = fileid_.basename.as_str().to_string();
let full_path = if full_path.is_empty() {
basename.clone()
} else {
full_path.clone() + "/" + &basename
};
debug!("fileid_ {:?} child_inode {:?}", fileid_, child_inode);
if let Ok(meta) = working_copy.file_metadata(&full_path) {
stack.push(RecordItem {
papa: item.inode,
inode: *child_inode,
v_papa: vertex,
basename,
full_path,
metadata: meta,
components: item.components.clone(),
})
} else if let Some(vertex) = get_inodes(txn, &channel, child_inode)? {
self.record_deleted_file(
txn,
txn.graph(channel),
working_copy,
&full_path,
*vertex,
)?
}
}
if comp.is_some() && !has_matching_children {
debug!("comp = {:?}", comp);
return Err(RecordError::PathNotInRepo(prefix.to_string()));
}
Ok(())
}
fn modified_since_last_commit<T: ChannelTxnT, W: WorkingCopy>(
&mut self,
txn: &T,
channel: &T::Channel,
working_copy: &W,
prefix: &str,
) -> Result<bool, std::time::SystemTimeError> {
if let Ok(last_modified) = working_copy.modified_time(prefix) {
debug!(
"last_modified = {:?}, channel.last = {:?}",
last_modified
.duration_since(std::time::UNIX_EPOCH)?
.as_secs(),
txn.last_modified(channel)
);
Ok(last_modified
.duration_since(std::time::UNIX_EPOCH)?
.as_secs()
>= txn.last_modified(channel))
} else {
Ok(true)
}
}
}
impl Builder {
let name_start = ChangePosition(self.rec.contents.len().into());
item.metadata.write(&mut self.rec.contents).unwrap();
self.rec.contents.extend(item.basename.as_bytes());
let name_end = ChangePosition(self.rec.contents.len().into());
self.rec.contents.push(0);
let name = &self.rec.contents[name_start.0.as_usize()..name_end.0.as_usize()];
let mut contents = self.contents.lock().unwrap();
let name_start = ChangePosition(contents.len().into());
// Push the metadata, big-endian.
contents.push((item.metadata.0 >> 8) as u8);
contents.push((item.metadata.0 & 0xff) as u8);
//
contents.extend(item.basename.as_bytes());
let name_end = ChangePosition(contents.len().into());
contents.push(0);
let name = &contents[name_start.0.as_usize()..name_end.0.as_usize()];
open_channels: RefCell::new(HashMap::new()),
open_remotes: RefCell::new(HashMap::new()),
open_channels: Mutex::new(HashMap::default()),
open_remotes: Mutex::new(HashMap::default()),
pub(crate) open_channels: RefCell<HashMap<SmallString, ChannelRef<Self>>>,
open_remotes: RefCell<HashMap<SmallString, RemoteRef<Self>>>,
pub(crate) open_channels: Mutex<HashMap<SmallString, ChannelRef<Self>>>,
open_remotes: Mutex<HashMap<SmallString, RemoteRef<Self>>>,
counter: usize,
let mut k = if let Some((k, _)) = cursor
.set(txn, &key, None)
.map_err(|x| BlockError::Txn(x.into()))?
{
k
} else if let Some((k, _)) = cursor.prev(txn).map_err(|x| BlockError::Txn(x.into()))? {
k
} else {
return Err(BlockError::Block { block: p });
let mut k = match cursor.set(txn, &key, None) {
Ok(Some((k, _))) => k,
Ok(None) => {
if let Some((k, _)) = cursor.prev(txn).map_err(|x| BlockError::Txn(x.into()))? {
k
} else {
debug!("find_block_end, no prev");
return Err(BlockError::Block { block: p });
}
}
Err(e) => {
debug!("find_block_end: BLOCK ERROR");
return Err(BlockError::Txn(e.into()));
}
self.counter += 1;
debug!("put_graph {:?} {:?}, counter = {:?}", k, e, self.counter);
/*
if self.counter >= 12965 {
let mut x = None;
let mut cursor = btree::cursor::Cursor::new(&self.txn, graph)?;
let mut panic = None;
while let Some((k, v)) = cursor.next(&self.txn)? {
if let Some((ref k_, ref v_)) = x {
if k_ > k || (k_== k && v_ > v) {
panic = Some((*k, *v));
break
}
}
x = Some((*k, *v))
}
self.debug(graph, ".put");
if panic.is_some() {
panic!("{:?}", x);
}
}
*/
Ok(btree::del(&mut self.txn, graph, k, e)?)
self.counter += 1;
debug!("del_graph {:?} {:?}, counter = {:?}", k, e, self.counter);
/*
if self.counter >= 12965 {
let mut x = None;
let mut cursor = btree::cursor::Cursor::new(&self.txn, graph)?;
let mut panic = None;
while let Some((k, v)) = cursor.next(&self.txn)? {
if let Some((ref k_, ref v_)) = x {
if k_ > k || (k_== k && v_ > v) {
panic = Some((*k, *v));
break
}
}
x = Some((*k, *v))
}
self.debug(graph, ".del");
if panic.is_some() {
panic!("{:?}", x);
}
}
let change = ChangeId::from_base32(b"MM6XEY5S32WRA").unwrap();
let mm6v = Vertex {
change,
start: ChangePosition(L64(1478218)),
end: ChangePosition(L64(1478229)),
};
let mm6e = (Edge {
flag: EdgeFlags::BLOCK | EdgeFlags::FOLDER | EdgeFlags::PARENT,
dest: Position {
change,
pos: ChangePosition(L64(1466149)),
},
introduced_by: change,
}).into();
let has_mm6 = if let Some((v, e)) = btree::get(&self.txn, graph, &mm6v, Some(&mm6e)).unwrap() {
v == &mm6v && e == &mm6e
} else {
false
};
*/
let result = Ok(btree::del(&mut self.txn, graph, k, e)?);
/*
if has_mm6 && (k != &mm6v || e != Some(&mm6e)) {
if let Some((v, e)) = btree::get(&self.txn, graph, &mm6v, Some(&mm6e)).unwrap() {
assert_eq!(v, &mm6v);
assert_eq!(e, &mm6e)
} else {
panic!("Not found")
}
}
*/
result
}
fn debug(&mut self, graph: &mut Self::Graph, extra: &str) {
::sanakirja::debug::debug(
&self.txn,
&[graph],
format!("debug{}{}", self.counter, extra),
true,
);
pub(crate) r: Rc<RefCell<T::Channel>>,
pub(crate) r: Arc<RwLock<T::Channel>>,
}
#[derive(Debug, Error)]
#[error("Mutex poison error")]
pub struct PoisonError {}
impl<T: ChannelTxnT> ChannelRef<T> {
pub fn read(&self) -> Result<std::sync::RwLockReadGuard<T::Channel>, PoisonError> {
self.r.read().map_err(|_| PoisonError {})
}
pub fn write(&self) -> Result<std::sync::RwLockWriteGuard<T::Channel>, PoisonError> {
self.r.write().map_err(|_| PoisonError {})
}
impl<T: ChannelTxnT> ChannelRef<T> {
pub fn borrow(&self) -> std::cell::Ref<T::Channel> {
self.r.borrow()
pub fn lock(&self) -> Result<std::sync::MutexGuard<Remote<T>>, PoisonError> {
self.db.lock().map_err(|_| PoisonError {})
let a = txn.del_graph(
graph,
&k0,
Some(&SerializedEdge::new(
flag,
k1.change,
k1.start,
introduced_by,
)),
)?;
let b = txn.del_graph(
graph,
&k1,
Some(&SerializedEdge::new(
flag | EdgeFlags::PARENT,
k0.change,
k0.end,
introduced_by,
)),
)?;
if (a && !b) || (!a && b) {
let v0 = SerializedEdge::new(flag, k1.change, k1.start, introduced_by);
let a = txn.del_graph(graph, &k0, Some(&v0))?;
{
if let Some(&ee) = txn.get_graph(graph, &k0, Some(&v0))? {
if ee == v0 {
txn.debug(graph, ".3");
panic!("Not deleted: {:?} {:?}", k0, v0);
}
}
}
let v1 = SerializedEdge::new(flag | EdgeFlags::PARENT, k0.change, k0.end, introduced_by);
let b = txn.del_graph(graph, &k1, Some(&v1))?;
{
if let Some(&ee) = txn.get_graph(graph, &k1, Some(&v1))? {
if ee == v1 {
txn.debug(graph, ".3");
panic!("Not deleted: {:?} {:?}", k1, v1);
}
}
}
if a != b {
txn.debug(graph, ".2");
let gaeul = ChangeId::from_base32(b"GAEULYDRSLJSC").unwrap();
let has_gaeul1 = {
let v = Vertex {
change: gaeul,
start: ChangePosition(L64(1677892)),
end: ChangePosition(L64(1677893)),
};
let e = SerializedEdge::new(
EdgeFlags::PSEUDO | EdgeFlags::PARENT,
gaeul,
ChangePosition(L64(1677837)),
ChangeId::ROOT,
);
if let Some(&ee) = txn.get_graph(graph, &v, Some(&e))? {
ee == e
} else {
false
}
};
let has_gaeul1_ = {
let v = Vertex {
change: gaeul,
start: ChangePosition(L64(1677892)),
end: ChangePosition(L64(1677893)),
};
let e = SerializedEdge::new(
EdgeFlags::PSEUDO | EdgeFlags::PARENT,
gaeul,
ChangePosition(L64(1677837)),
ChangeId::ROOT,
);
if let Some(&ee) = txn.get_graph(graph, &v, Some(&e))? {
ee == e
} else {
false
}
};
if has_gaeul1 && !has_gaeul1_ {
txn.debug(graph, ".GAEUL");
panic!("GAEULT");
}
impl SerializedHash {
pub fn size(b: &[u8]) -> usize {
if b[0] == HashAlgorithm::Blake3 as u8 {
1 + BLAKE3_BYTES
} else if b[0] == HashAlgorithm::None as u8 {
1
} else {
panic!("Unknown hash algorithm {:?}", b[0])
}
}
pub unsafe fn size_from_ptr(b: *const u8) -> usize {
if *b == HashAlgorithm::Blake3 as u8 {
1 + BLAKE3_BYTES
} else if *b == HashAlgorithm::None as u8 {
1
} else {
panic!("Unknown hash algorithm {:?}", *b)
}
}
}
pub fn output_repository_no_pending<T: MutTxnT, R: WorkingCopy, P: ChangeStore>(
repo: &mut R,
pub fn output_repository_no_pending<
T: MutTxnT + Send + Sync + 'static,
R: WorkingCopy + Send + Sync + 'static,
P: ChangeStore + Send + Clone + 'static,
>(
repo: Arc<R>,
fn output_loop<
T: TreeMutTxnT + ChannelMutTxnT + GraphMutTxnT<GraphError = <T as TreeTxnT>::TreeError>,
R: WorkingCopy + 'static,
P: ChangeStore + Clone + Send,
>(
repo: Arc<R>,
changes: &P,
txn: Arc<RwLock<T>>,
channel: ChannelRef<T>,
work: Arc<crossbeam_deque::Injector<(OutputItem, String)>>,
stop: Arc<std::sync::atomic::AtomicBool>,
t: usize,
) -> Result<(), OutputError<P::Error, T::GraphError, R::Error>> {
use crossbeam_deque::*;
// let backoff = crossbeam_utils::Backoff::new();
// let w: Worker<(OutputItem, String)> = Worker::new_fifo();
loop {
match work.steal() {
Steal::Success((item, path)) => {
let mut conflicts = Vec::new();
info!("Outputting {:?}, on thread {}", path, t);
output_item::<_, _, R>(
txn.clone(),
channel.clone(),
changes,
&item,
&mut conflicts,
&repo,
&path,
)?;
debug!("setting permissions for {:?}", path);
repo.set_permissions(&path, item.meta.permissions())
.map_err(OutputError::WorkingCopy)?;
debug!("output {:?}", path);
}
Steal::Retry => {}
Steal::Empty => {
if stop.load(std::sync::atomic::Ordering::Relaxed) {
break;
}
}
}
}
Ok(())
}
T: TreeMutTxnT + ChannelMutTxnT + GraphMutTxnT<GraphError = <T as TreeTxnT>::TreeError>,
R: WorkingCopy,
P: ChangeStore,
T: TreeMutTxnT
+ ChannelMutTxnT
+ GraphMutTxnT<GraphError = <T as TreeTxnT>::TreeError>
+ Send
+ Sync
+ 'static,
R: WorkingCopy + Send + Sync + 'static,
P: ChangeStore + Send + Clone + 'static,
) -> Result<Vec<Conflict>, OutputError<P::Error, T::TreeError, R::Error>> {
n_workers: usize,
) -> Result<Vec<Conflict>, OutputError<P::Error, T::TreeError, R::Error>>
where
T::Channel: Send + Sync + 'static,
{
let work = Arc::new(crossbeam_deque::Injector::new());
let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
let mut threads = Vec::new();
for t in 0..n_workers - 1 {
let repo = repo.clone();
let work = work.clone();
let stop = stop.clone();
let txn = txn.clone();
let channel = channel.clone();
let changes = changes.clone();
threads.push(std::thread::spawn(move || {
output_loop(repo, &changes, txn, channel, work, stop, t + 1)
}))
}
let dead = collect_dead_files(txn, txn.graph(channel), pending_change_id, Inode::ROOT)?;
let dead = {
let txn_ = txn.read().unwrap();
let channel = channel.read().unwrap();
let graph = txn_.graph(&*channel);
collect_dead_files(&*txn_, graph, pending_change_id, Inode::ROOT)?
};
collect_children(
txn,
changes,
txn.graph(channel),
Position::ROOT,
Inode::ROOT,
"",
next_prefix_basename,
&mut files,
)?;
{
let txn = txn.read().unwrap();
let channel = channel.read().unwrap();
collect_children(
&*txn,
&*changes,
txn.graph(&*channel),
Position::ROOT,
Inode::ROOT,
"",
next_prefix_basename,
&mut files,
)?;
}
b.sort_unstable_by(|u, v| {
txn.get_changeset(txn.changes(&channel), &u.0.change)
.unwrap()
.cmp(
&txn.get_changeset(txn.changes(&channel), &v.0.change)
.unwrap(),
)
});
{
let txn = txn.read().unwrap();
let channel = channel.read().unwrap();
b.sort_unstable_by(|u, v| {
txn.get_changeset(txn.changes(&channel), &u.0.change)
.unwrap()
.cmp(
&txn.get_changeset(txn.changes(&channel), &v.0.change)
.unwrap(),
)
});
}
let dead =
collect_dead_files(txn, txn.graph(channel), pending_change_id, inode)?;
let dead = {
let txn_ = txn.read().unwrap();
let channel = channel.read().unwrap();
collect_dead_files(&*txn_, txn_.graph(&*channel), pending_change_id, inode)?
};
collect_children(
txn,
changes,
txn.graph(channel),
output_item.pos,
inode,
&path,
next_prefix_basename,
&mut next_files,
)?;
{
let txn = txn.read().unwrap();
let channel = channel.read().unwrap();
collect_children(
&*txn,
&*changes,
txn.graph(&*channel),
output_item.pos,
inode,
&path,
next_prefix_basename,
&mut next_files,
)?;
}
debug!("setting permissions for {:?}", path);
repo.set_permissions(&path, output_item.meta.permissions())
.map_err(OutputError::WorkingCopy)?;
if needs_output(repo, if_modified_after, &path) {
repo.write_file(&path, |w: &mut dyn std::io::Write| {
output_file::<_, _, R>(
txn,
channel,
changes,
&output_item,
&mut conflicts,
w,
)
})
.map_err(OutputError::from)?
if needs_output(repo.as_ref(), if_modified_after, &path) {
work.push((output_item.clone(), path.clone()));
if let Some(&inode) = txn.get_tree(&file_id, None)? {
crate::fs::rec_delete(txn, &file_id, inode, true)
std::mem::drop(txn_);
let mut txn_ = txn.write().unwrap();
if let Some(&inode) = txn_.get_tree(&file_id, None)? {
crate::fs::rec_delete(&mut *txn_, &file_id, inode, true)
if let Some(&inode) = txn.get_tree(&file_id, None)? {
crate::fs::rec_delete(txn, &file_id, inode, true).map_err(PristineOutputError::Fs)?;
let mut txn_ = txn.write().unwrap();
if let Some(&inode) = txn_.get_tree(&file_id, None)? {
crate::fs::rec_delete(&mut *txn_, &file_id, inode, true)
.map_err(PristineOutputError::Fs)?;
fn output_file<T: ChannelMutTxnT + GraphMutTxnT, P: ChangeStore, W: WorkingCopy>(
txn: &mut T,
channel: &mut T::Channel,
fn output_item<T: ChannelMutTxnT + GraphMutTxnT, P: ChangeStore, W: WorkingCopy>(
txn: Arc<RwLock<T>>,
channel: ChannelRef<T>,
let mut l = retrieve(txn, txn.graph(channel), output_item.pos)?;
let mut f = vertex_buffer::ConflictsWriter::new(w, &output_item.path, conflicts);
alive::output_graph(changes, txn, channel, &mut f, &mut l, &mut forward)
.map_err(PristineOutputError::from)?;
{
let txn = txn.read().unwrap();
let channel = channel.read().unwrap();
let mut l = retrieve(&*txn, txn.graph(&*channel), output_item.pos)?;
let w = repo.write_file(&path).map_err(OutputError::WorkingCopy)?;
let mut f = vertex_buffer::ConflictsWriter::new(w, &output_item.path, conflicts);
alive::output_graph(changes, &*txn, &*channel, &mut f, &mut l, &mut forward)
.map_err(PristineOutputError::from)?;
}
if forward.is_empty() {
return Ok(());
}
let mut txn = txn.write().unwrap();
let mut channel = channel.write().unwrap();
fn collect_dead_files<T: TreeMutTxnT + GraphTxnT<GraphError = <T as TreeTxnT>::TreeError>>(
txn: &mut T,
fn collect_dead_files<T: TreeTxnT + GraphTxnT<GraphError = <T as TreeTxnT>::TreeError>>(
txn: &T,
}
use crate::working_copy::WriteError;
impl<C: std::error::Error, T: std::error::Error + 'static, W: std::error::Error>
OutputError<C, T, W>
{
fn from(e: WriteError<Self>) -> Self {
match e {
WriteError::Io(e) => OutputError::Pristine(PristineOutputError::Io(e)),
WriteError::E(e) => e,
}
}
impl<C: std::error::Error, T: std::error::Error + 'static, W: std::error::Error> From<TxnErr<T>>
for OutputError<C, T, W>
impl<C: std::error::Error, T: std::error::Error + 'static, W: std::error::Error + Send>
From<TxnErr<T>> for OutputError<C, T, W>
}
pub fn output_file<
T: TreeTxnT + ChannelTxnT,
C: crate::changestore::ChangeStore,
V: crate::vertex_buffer::VertexBuffer,
>(
changes: &C,
txn: &T,
channel: &T::Channel,
v0: Position<ChangeId>,
out: &mut V,
) -> Result<(), FileError<C::Error, T::GraphError>> {
let mut forward = Vec::new();
let mut graph = crate::alive::retrieve(&*txn, txn.graph(&*channel), v0)?;
crate::alive::output_graph(changes, &*txn, &*channel, out, &mut graph, &mut forward)?;
Ok(())
}
}
}
}
Ok(())
}
pub(crate) fn collect_zombie_context<T: GraphMutTxnT, K>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut Workspace,
inode: Position<Option<Hash>>,
n: &NewEdge<Option<Hash>>,
change_id: ChangeId,
mut known: K,
) -> Result<(), MissingError<T::GraphError>>
where
K: FnMut(Hash) -> bool,
{
if n.flag.contains(EdgeFlags::FOLDER) {
return Ok(());
}
let mut pos = internal_pos(txn, &n.to.start_pos(), change_id)?;
let end_pos = internal_pos(txn, &n.to.end_pos(), change_id)?;
let mut unknown_parents = Vec::new();
while let Ok(&dest_vertex) = txn.find_block(&channel, pos) {
debug!("collect zombie context: {:?}", dest_vertex);
for v in iter_adjacent(
txn,
channel,
dest_vertex,
EdgeFlags::empty(),
EdgeFlags::all() - EdgeFlags::DELETED,
)? {
let v = v?;
if v.introduced_by() == change_id || v.dest().change.is_root() {
continue;
}
if v.introduced_by().is_root() {
ws.pseudo.push((dest_vertex, *v));
continue;
}
if v.flag().contains(EdgeFlags::PARENT) {
// Unwrap ok, since `v` is in the channel.
let intro = txn.get_external(&v.introduced_by())?.unwrap().into();
if !known(intro) {
debug!("unknown: {:?}", v);
unknown_parents.push((dest_vertex, *v))
}
}
zombify(txn, channel, ws, change_id, inode, n.flag, &unknown_parents)?;
if dest_vertex.end < end_pos.pos {
pos.pos = dest_vertex.end
} else {
break;
}
}
Ok(())
}
fn zombify<T: GraphMutTxnT>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut Workspace,
change_id: ChangeId,
inode: Position<Option<Hash>>,
flag: EdgeFlags,
unknown: &[(Vertex<ChangeId>, SerializedEdge)],
) -> Result<(), MissingError<T::GraphError>> {
for &(dest_vertex, edge) in unknown.iter() {
let p = *txn.find_block_end(channel, edge.dest())?;
ws.unknown_parents
.push((dest_vertex, p, inode, edge.flag()));
let fold = flag & EdgeFlags::FOLDER;
debug!("zombify p {:?}, dest_vertex {:?}", p, dest_vertex);
let mut v = p;
while let Ok(&u) = txn.find_block_end(channel, v.start_pos()) {
if u != v {
debug!("u = {:?}, v = {:?}", u, v);
put_graph_with_rev(
txn,
channel,
EdgeFlags::DELETED | EdgeFlags::BLOCK | fold,
u,
v,
change_id,
)?;
v = u
} else {
break;
// Zombify the first chunk of the split.
for parent in iter_adjacent(
txn,
channel,
v,
EdgeFlags::PARENT,
EdgeFlags::all() - EdgeFlags::DELETED,
)? {
let parent = parent?;
if !parent.flag().contains(EdgeFlags::PSEUDO) {
ws.parents.insert(*parent);
}
}
debug!("ws.parents = {:?}", ws.parents);
for parent in ws.parents.drain() {
let parent_dest = *txn.find_block_end(channel, parent.dest())?;
let mut flag = EdgeFlags::DELETED | EdgeFlags::BLOCK;
if parent.flag().contains(EdgeFlags::FOLDER) {
flag |= EdgeFlags::FOLDER
}
put_graph_with_rev(txn, channel, flag, parent_dest, v, change_id)?;
}
use std::collections::HashMap;
// Making hashmaps deterministic (for testing)
pub type HashMap<K, V> =
std::collections::HashMap<K, V, std::hash::BuildHasherDefault<twox_hash::XxHash64>>;
pub type HashSet<K> =
std::collections::HashSet<K, std::hash::BuildHasherDefault<twox_hash::XxHash64>>;
// pub type HashMap<K, V> = std::collections::HashMap<K, V, std::collections::hash_map::RandomState>;
// pub type HashSet<K> = std::collections::HashSet<K, std::collections::hash_map::RandomState>;
}
/*
#[cfg(feature = "dump")]
fn channel_from_dump<'a>(
&'a mut self,
name: &str,
) -> Result<
pristine::channel_dump::ChannelFromDump<'a, Self>,
pristine::channel_dump::ChannelDumpError<Self::GraphError>,
> {
use pristine::channel_dump::*;
if self.load_channel(name)?.is_none() {
let channel = pristine::MutTxnT::open_or_create_channel(self, name)
.map_err(ChannelDumpError::Txn)?;
Ok(ChannelFromDump::new(self, channel))
} else {
Err(ChannelDumpError::ChannelNameExists(name.to_string()))
}
fs::follow_oldest_path(changes, self, &channel.borrow(), path)
}
fn output_file<C: changestore::ChangeStore, V: vertex_buffer::VertexBuffer>(
&self,
changes: &C,
channel: &pristine::ChannelRef<Self>,
v0: pristine::Position<pristine::ChangeId>,
out: &mut V,
) -> Result<(), output::FileError<C::Error, Self::GraphError>> {
let mut forward = Vec::new();
let channel = channel.borrow();
let mut graph = alive::retrieve(self, self.graph(&channel), v0)?;
alive::output_graph(changes, self, &channel, out, &mut graph, &mut forward)?;
Ok(())
fs::follow_oldest_path(changes, self, &channel.read().unwrap(), path)
let pos = self.rec.contents.len();
self.rec.contents.extend_from_slice(&b[..]);
let pos_end = self.rec.contents.len();
self.rec.contents.push(0);
let mut contents = self.contents.lock().unwrap();
let pos = contents.len();
contents.extend_from_slice(&b[..]);
let pos_end = contents.len();
contents.push(0);
std::mem::drop(contents);
self.rec.contents.push(0);
let pos = ChangePosition(self.rec.contents.len().into());
self.rec.contents.push(0);
let mut contents = self.contents.lock().unwrap();
contents.push(0);
let pos = ChangePosition(contents.len().into());
contents.push(0);
let contents_len = contents.len();
std::mem::drop(contents);
#[derive(Clone)]
pub struct FileSystem(Arc<FileSystem_>);
struct FileSystem_ {
change_cache: Mutex<lru_cache::LruCache<ChangeId, Arc<Mutex<ChangeFile<'static>>>>>,
pub struct FileSystem {
change_cache: RefCell<lru_cache::LruCache<ChangeId, ChangeFile<'static>>>,
let mut cache = self.0.change_cache.lock().unwrap();
let mut poisoned = false;
if let Some(c) = cache.get_mut(change_id) {
if let Ok(l) = c.lock() {
return l.has_contents();
} else {
poisoned = true
}
}
if poisoned {
cache.remove(change_id);
if let Some(l) = self.change_cache.borrow_mut().get_mut(change_id) {
return l.has_contents();
crate::missing_context::collect_zombie_context(
txn,
T::graph_mut(channel),
&mut ws.missing_context,
n.inode,
edge,
change_id,
|h| change.knows(&h),
)
.map_err(LocalApplyError::from_missing)?
}
}
}
Ok(())
}
fn put_newvertex<T: GraphMutTxnT>(
txn: &mut T,
graph: &mut T::Graph,
ch: &Change,
ws: &mut Workspace,
change: ChangeId,
n: &NewVertex<Option<Hash>>,
) -> Result<(), LocalApplyError<T::GraphError>> {
let vertex = Vertex {
change,
start: n.start,
end: n.end,
};
if txn.find_block_end(graph, vertex.end_pos()).is_ok()
|| txn.find_block(graph, vertex.start_pos()).is_ok()
{
error!("Invalid change: {:?}", vertex);
return Err(LocalApplyError::InvalidChange);
}
debug!(
"put_newvertex {:?} {:?} {:?} {:?} {:?}",
vertex, n.up_context, n.down_context, n.flag, change
);
assert!(ws.deleted_by.is_empty());
for up in n.up_context.iter() {
let up = internal_pos(txn, up, change)?;
if put_up_context(txn, graph, ch, ws, up)? && n.flag.contains(EdgeFlags::FOLDER) {
return Err(LocalApplyError::InvalidChange);
}
}
for down in n.down_context.iter() {
let down = internal_pos(txn, down, change)?;
if down.change == change {
return Err(LocalApplyError::InvalidChange);
}
if put_down_context(txn, graph, ch, ws, down)? && !n.flag.contains(EdgeFlags::FOLDER) {
return Err(LocalApplyError::InvalidChange);
}
}
debug!("deleted by: {:?}", ws.deleted_by);
let up_flag = n.flag | EdgeFlags::BLOCK | EdgeFlags::DELETED;
for up in ws.up_context.drain(..) {
assert_ne!(up, vertex);
if !n.flag.contains(EdgeFlags::FOLDER) {
for change in ws.deleted_by.iter() {
put_graph_with_rev(txn, graph, up_flag, up, vertex, *change)?;
debug!("down_context {:?}", ws.down_context);
let mut down_flag = n.flag;
if !n.flag.is_folder() {
down_flag -= EdgeFlags::BLOCK
}
for down in ws.down_context.drain(..) {
assert_ne!(down, vertex);
put_graph_with_rev(txn, graph, down_flag, vertex, down, change)?;
if n.flag.is_folder() {
ws.missing_context.files.insert(down);
}
}
ws.deleted_by.clear();
}
fn put_up_context<T: GraphMutTxnT>(
txn: &mut T,
graph: &mut T::Graph,
ch: &Change,
ws: &mut Workspace,
up: Position<ChangeId>,
) -> Result<bool, LocalApplyError<T::GraphError>> {
let up_vertex = if up.change.is_root() {
Vertex::ROOT
} else {
debug!("put_up_context {:?}", up);
let k = *txn.find_block_end(graph, up)?;
assert_eq!(k.change, up.change);
assert!(k.start <= up.pos);
debug!("k = {:?}", k);
if k.start < up.pos && k.end > up.pos {
// The missing context "graphs" are only used at the
// DELETION stage, check that:
assert!(ws.missing_context.graphs.0.is_empty());
txn.split_block(graph, &k, up.pos, &mut ws.adjbuf)?
}
Vertex {
change: k.change,
start: k.start,
end: up.pos,
}
};
debug!("up_vertex {:?}", up_vertex);
let flag0 = EdgeFlags::PARENT | EdgeFlags::BLOCK;
let flag1 = flag0 | EdgeFlags::DELETED | EdgeFlags::FOLDER;
let mut is_non_folder = false;
for parent in iter_adjacent(txn, graph, up_vertex, flag0, flag1)? {
let parent = parent?;
is_non_folder |=
parent.flag() & (EdgeFlags::PARENT | EdgeFlags::FOLDER) == EdgeFlags::PARENT;
if parent
.flag()
.contains(EdgeFlags::PARENT | EdgeFlags::DELETED | EdgeFlags::BLOCK)
{
let introduced_by = txn.get_external(&parent.introduced_by())?.unwrap().into();
if !ch.knows(&introduced_by) {
ws.deleted_by.insert(parent.introduced_by());
}
}
}
ws.up_context.push(up_vertex);
Ok(is_non_folder)
}
fn put_down_context<T: GraphMutTxnT>(
txn: &mut T,
graph: &mut T::Graph,
ch: &Change,
ws: &mut Workspace,
down: Position<ChangeId>,
) -> Result<bool, LocalApplyError<T::GraphError>> {
let k = *txn.find_block(&graph, down)?;
assert_eq!(k.change, down.change);
assert!(k.end >= down.pos);
if k.start < down.pos && k.end > down.pos {
// The missing context "graphs" are only used at the
// DELETION stage, check that:
assert!(ws.missing_context.graphs.0.is_empty());
txn.split_block(graph, &k, down.pos, &mut ws.adjbuf)?
}
let down_vertex = Vertex {
change: k.change,
start: down.pos,
end: k.end,
};
debug!("down_vertex {:?}", down_vertex);
let flag0 = EdgeFlags::PARENT;
let flag1 = flag0 | EdgeFlags::FOLDER | EdgeFlags::BLOCK | EdgeFlags::DELETED;
let mut is_folder = false;
for parent in iter_adjacent(txn, &graph, down_vertex, flag0, flag1)? {
let parent = parent?;
is_folder |= parent
.flag()
.contains(EdgeFlags::PARENT | EdgeFlags::FOLDER);
if parent.flag().contains(EdgeFlags::PARENT | EdgeFlags::BLOCK) {
if parent.flag().contains(EdgeFlags::DELETED) {
let introduced_by = txn.get_external(&parent.introduced_by())?.unwrap().into();
if !ch.knows(&introduced_by) {
ws.deleted_by.insert(parent.introduced_by());
}
}
}
}
ws.down_context.push(down_vertex);
Ok(is_folder)
pub(crate) fn put_newedge<T, E, F>(
txn: &mut T,
graph: &mut T::Graph,
ws: &mut Workspace,
change: ChangeId,
inode: Position<Option<Hash>>,
n: &NewEdge<Option<Hash>>,
apply_check: F,
) -> Result<(), E>
where
T: GraphMutTxnT,
E: From<LocalApplyError<T::GraphError>> + From<TxnErr<T::GraphError>>,
F: Fn(&mut T, &mut T::Graph, Vertex<ChangeId>, Vertex<ChangeId>) -> Result<bool, E>,
{
debug!("put_newedge {:?} {:?}", n, change);
if n.flag.contains(EdgeFlags::DELETED) {
ws.missing_context
.load_graph(txn, graph, inode)
.map_err(|_| LocalApplyError::InvalidChange)?;
}
if (n.previous.is_block() && !n.flag.is_block())
|| (n.previous.is_folder() != n.flag.is_folder())
{
return Err(LocalApplyError::InvalidChange.into());
}
debug_assert!(ws.children.is_empty());
debug_assert!(ws.parents.is_empty());
let n_introduced_by = if let Some(n) = internal(txn, &n.introduced_by, change)? {
n
} else {
return Err(LocalApplyError::InvalidChange.into());
};
let mut source = find_source_vertex(txn, graph, &n.from, change, inode, n.flag, ws)?;
let mut target = find_target_vertex(txn, graph, &n.to, change, inode, n.flag, ws)?;
if n.flag.contains(EdgeFlags::FOLDER) {
ws.missing_context.files.insert(target);
}
loop {
if target.end > n.to.end {
assert!(!n.flag.contains(EdgeFlags::FOLDER));
ws.missing_context.graphs.split(inode, target, n.to.end);
txn.split_block(graph, &target, n.to.end, &mut ws.adjbuf)?;
target.end = n.to.end
}
if n.flag.contains(EdgeFlags::DELETED) {
debug_assert!(ws.children.is_empty());
debug_assert!(ws.parents.is_empty());
collect_pseudo_edges(txn, graph, ws, inode, target)?;
if !n.flag.contains(EdgeFlags::FOLDER) {
reconnect_pseudo_edges(txn, graph, inode, ws, target)?;
}
ws.children.clear();
ws.parents.clear();
}
del_graph_with_rev(txn, graph, n.previous, source, target, n_introduced_by)?;
if apply_check(txn, graph, source, target)? {
put_graph_with_rev(txn, graph, n.flag, source, target, change)?;
}
if target.end >= n.to.end {
debug!("{:?} {:?}", target, n.to);
debug_assert_eq!(target.end, n.to.end);
break;
}
source = target;
target = *txn
.find_block(graph, target.end_pos())
.map_err(LocalApplyError::from)?;
assert_ne!(source, target);
}
Ok(())
}
fn find_source_vertex<T: GraphMutTxnT>(
txn: &mut T,
channel: &mut T::Graph,
from: &Position<Option<Hash>>,
change: ChangeId,
inode: Position<Option<Hash>>,
flag: EdgeFlags,
ws: &mut Workspace,
) -> Result<Vertex<ChangeId>, LocalApplyError<T::GraphError>> {
debug!("find_source_vertex");
let mut source = *txn.find_block_end(&channel, internal_pos(txn, &from, change)?)?;
debug!("source = {:?}", source);
if source.start < from.pos && source.end > from.pos {
assert!(!flag.contains(EdgeFlags::FOLDER));
ws.missing_context.graphs.split(inode, source, from.pos);
txn.split_block(channel, &source, from.pos, &mut ws.adjbuf)?;
source.end = from.pos;
}
Ok(source)
}
fn find_target_vertex<T: GraphMutTxnT>(
txn: &mut T,
channel: &mut T::Graph,
to: &Vertex<Option<Hash>>,
change: ChangeId,
inode: Position<Option<Hash>>,
flag: EdgeFlags,
ws: &mut Workspace,
) -> Result<Vertex<ChangeId>, LocalApplyError<T::GraphError>> {
let to_pos = internal_pos(txn, &to.start_pos(), change)?;
debug!("find_target_vertex, to = {:?}", to);
let mut target = *txn.find_block(channel, to_pos)?;
debug!("target = {:?}", target);
if target.start < to.start {
assert!(!flag.contains(EdgeFlags::FOLDER));
ws.missing_context.graphs.split(inode, target, to.start);
txn.split_block(channel, &target, to.start, &mut ws.adjbuf)?;
target.start = to.start;
}
Ok(target)
}
fn collect_pseudo_edges<T: GraphMutTxnT>(
txn: &mut T,
channel: &mut T::Graph,
apply: &mut Workspace,
inode: Position<Option<Hash>>,
v: Vertex<ChangeId>,
) -> Result<(), LocalApplyError<T::GraphError>> {
for e in iter_adjacent(
txn,
&channel,
v,
EdgeFlags::empty(),
EdgeFlags::all() - EdgeFlags::DELETED,
)? {
let e = e?;
debug!("collect_pseudo_edges {:?} {:?}", v, e);
if !e.flag().contains(EdgeFlags::FOLDER) {
if e.flag().contains(EdgeFlags::PARENT) {
let p = txn.find_block_end(channel, e.dest())?;
if is_alive(txn, channel, p)? {
apply.parents.insert(*p);
}
} else {
let p = txn.find_block(channel, e.dest())?;
if e.flag().contains(EdgeFlags::BLOCK)
|| p.is_empty()
|| is_alive(txn, channel, p).unwrap()
{
apply.children.insert(*p);
}
}
}
if e.flag().contains(EdgeFlags::PSEUDO) {
apply.pseudo.push((v, *e, inode));
}
}
Ok(())
}
fn reconnect_pseudo_edges<T: GraphMutTxnT>(
txn: &mut T,
channel: &mut T::Graph,
inode: Position<Option<Hash>>,
ws: &mut Workspace,
target: Vertex<ChangeId>,
) -> Result<(), LocalApplyError<T::GraphError>> {
if ws.parents.is_empty() || ws.children.is_empty() {
return Ok(());
}
let (graph, vids) = if let Some(x) = ws.missing_context.graphs.get(inode) {
x
} else {
return Err(LocalApplyError::InvalidChange.into());
};
crate::alive::remove_redundant_parents(
&graph,
&vids,
&mut ws.parents,
&mut ws.missing_context.covered_parents,
target,
);
for &p in ws.parents.iter() {
ws.missing_context.covered_parents.insert((p, target));
}
crate::alive::remove_redundant_children(&graph, &vids, &mut ws.children, target);
for &p in ws.parents.iter() {
debug_assert!(is_alive(txn, channel, &p).unwrap());
for &c in ws.children.iter() {
if p != c {
debug_assert!(is_alive(txn, channel, &c).unwrap());
put_graph_with_rev(txn, channel, EdgeFlags::PSEUDO, p, c, ChangeId::ROOT)?;
}
}
}
Ok(())
}
use super::{LocalApplyError, Workspace};
use crate::change::{Change, NewVertex};
use crate::pristine::*;
use crate::{ChangeId, EdgeFlags, Hash, Vertex};
pub fn put_newvertex<T: GraphMutTxnT>(
txn: &mut T,
graph: &mut T::Graph,
ch: &Change,
ws: &mut Workspace,
change: ChangeId,
n: &NewVertex<Option<Hash>>,
) -> Result<(), LocalApplyError<T::GraphError>> {
let vertex = Vertex {
change,
start: n.start,
end: n.end,
};
if txn.find_block_end(graph, vertex.end_pos()).is_ok()
|| txn.find_block(graph, vertex.start_pos()).is_ok()
{
error!("Invalid change: {:?}", vertex);
return Err(LocalApplyError::InvalidChange);
}
debug!(
"put_newvertex {:?} {:?} {:?} {:?} {:?}",
vertex, n.up_context, n.down_context, n.flag, change
);
assert!(ws.deleted_by.is_empty());
for up in n.up_context.iter() {
let up = internal_pos(txn, up, change)?;
if put_up_context(txn, graph, ch, ws, up)? && n.flag.contains(EdgeFlags::FOLDER) {
return Err(LocalApplyError::InvalidChange);
}
}
for down in n.down_context.iter() {
let down = internal_pos(txn, down, change)?;
if down.change == change {
return Err(LocalApplyError::InvalidChange);
}
if put_down_context(txn, graph, ch, ws, down)? && !n.flag.contains(EdgeFlags::FOLDER) {
return Err(LocalApplyError::InvalidChange);
}
}
debug!("deleted by: {:?}", ws.deleted_by);
let up_flag = n.flag | EdgeFlags::BLOCK | EdgeFlags::DELETED;
for up in ws.up_context.drain(..) {
assert_ne!(up, vertex);
if !n.flag.contains(EdgeFlags::FOLDER) {
for change in ws.deleted_by.iter() {
put_graph_with_rev(txn, graph, up_flag, up, vertex, *change)?;
}
}
put_graph_with_rev(txn, graph, n.flag | EdgeFlags::BLOCK, up, vertex, change)?;
}
debug!("down_context {:?}", ws.down_context);
let mut down_flag = n.flag;
if !n.flag.is_folder() {
down_flag -= EdgeFlags::BLOCK
}
for down in ws.down_context.drain(..) {
assert_ne!(down, vertex);
put_graph_with_rev(txn, graph, down_flag, vertex, down, change)?;
if n.flag.is_folder() {
ws.missing_context.files.insert(down);
}
}
ws.deleted_by.clear();
Ok(())
}
fn put_up_context<T: GraphMutTxnT>(
txn: &mut T,
graph: &mut T::Graph,
ch: &Change,
ws: &mut Workspace,
up: Position<ChangeId>,
) -> Result<bool, LocalApplyError<T::GraphError>> {
let up_vertex = if up.change.is_root() {
Vertex::ROOT
} else {
debug!("put_up_context {:?}", up);
let k = *txn.find_block_end(graph, up)?;
assert_eq!(k.change, up.change);
assert!(k.start <= up.pos);
debug!("k = {:?}", k);
if k.start < up.pos && k.end > up.pos {
// The missing context "graphs" are only used at the
// DELETION stage, check that:
assert!(ws.missing_context.graphs.0.is_empty());
txn.split_block(graph, &k, up.pos, &mut ws.adjbuf)?
}
Vertex {
change: k.change,
start: k.start,
end: up.pos,
}
};
debug!("up_vertex {:?}", up_vertex);
let flag0 = EdgeFlags::PARENT | EdgeFlags::BLOCK;
let flag1 = flag0 | EdgeFlags::DELETED | EdgeFlags::FOLDER;
let mut is_non_folder = false;
for parent in iter_adjacent(txn, graph, up_vertex, flag0, flag1)? {
let parent = parent?;
is_non_folder |=
parent.flag() & (EdgeFlags::PARENT | EdgeFlags::FOLDER) == EdgeFlags::PARENT;
if parent
.flag()
.contains(EdgeFlags::PARENT | EdgeFlags::DELETED | EdgeFlags::BLOCK)
{
let introduced_by = txn.get_external(&parent.introduced_by())?.unwrap().into();
if !ch.knows(&introduced_by) {
ws.deleted_by.insert(parent.introduced_by());
}
}
}
ws.up_context.push(up_vertex);
Ok(is_non_folder)
}
fn put_down_context<T: GraphMutTxnT>(
txn: &mut T,
graph: &mut T::Graph,
ch: &Change,
ws: &mut Workspace,
down: Position<ChangeId>,
) -> Result<bool, LocalApplyError<T::GraphError>> {
let k = *txn.find_block(&graph, down)?;
assert_eq!(k.change, down.change);
assert!(k.end >= down.pos);
if k.start < down.pos && k.end > down.pos {
// The missing context "graphs" are only used at the
// DELETION stage, check that:
assert!(ws.missing_context.graphs.0.is_empty());
txn.split_block(graph, &k, down.pos, &mut ws.adjbuf)?
}
let down_vertex = Vertex {
change: k.change,
start: down.pos,
end: k.end,
};
debug!("down_vertex {:?}", down_vertex);
let flag0 = EdgeFlags::PARENT;
let flag1 = flag0 | EdgeFlags::FOLDER | EdgeFlags::BLOCK | EdgeFlags::DELETED;
let mut is_folder = false;
for parent in iter_adjacent(txn, &graph, down_vertex, flag0, flag1)? {
let parent = parent?;
is_folder |= parent
.flag()
.contains(EdgeFlags::PARENT | EdgeFlags::FOLDER);
if parent.flag().contains(EdgeFlags::PARENT | EdgeFlags::BLOCK) {
if parent.flag().contains(EdgeFlags::DELETED) {
let introduced_by = txn.get_external(&parent.introduced_by())?.unwrap().into();
if !ch.knows(&introduced_by) {
ws.deleted_by.insert(parent.introduced_by());
}
}
}
}
ws.down_context.push(down_vertex);
Ok(is_folder)
}
use super::LocalApplyError;
use crate::change::NewEdge;
use crate::missing_context::*;
use crate::pristine::*;
pub fn put_newedge<T, E, F, K>(
txn: &mut T,
graph: &mut T::Graph,
ws: &mut super::Workspace,
change: ChangeId,
inode: Position<Option<Hash>>,
n: &NewEdge<Option<Hash>>,
apply_check: F,
mut known: K,
) -> Result<(), E>
where
T: GraphMutTxnT,
E: From<LocalApplyError<T::GraphError>> + From<TxnErr<T::GraphError>>,
F: Fn(&mut T, &mut T::Graph, Vertex<ChangeId>, Vertex<ChangeId>) -> Result<bool, E>,
K: FnMut(&Hash) -> bool,
{
debug!("put_newedge {:?} {:?}", n, change);
check_valid(txn, graph, inode, n, ws)?;
let n_introduced_by = if let Some(n) = internal(txn, &n.introduced_by, change)? {
n
} else {
return Err(LocalApplyError::InvalidChange.into());
};
let mut source = find_source_vertex(txn, graph, &n.from, change, inode, n.flag, ws)?;
let mut target = find_target_vertex(txn, graph, &n.to, change, inode, n.flag, ws)?;
if n.flag.contains(EdgeFlags::FOLDER) {
ws.missing_context.files.insert(target);
}
let mut zombies = Vec::new();
loop {
if !n.flag.contains(EdgeFlags::DELETED) {
collect_nondeleted_zombies::<_, E, _>(
txn,
graph,
&mut known,
source,
target,
&mut zombies,
)?;
}
if target.end > n.to.end {
assert!(!n.flag.contains(EdgeFlags::FOLDER));
ws.missing_context.graphs.split(inode, target, n.to.end);
txn.split_block(graph, &target, n.to.end, &mut ws.adjbuf)?;
target.end = n.to.end
}
if n.flag.contains(EdgeFlags::DELETED) {
debug_assert!(ws.children.is_empty());
debug_assert!(ws.parents.is_empty());
collect_pseudo_edges(txn, graph, ws, inode, target)?;
if !n.flag.contains(EdgeFlags::FOLDER) {
reconnect_pseudo_edges(txn, graph, inode, ws, target)?;
}
ws.children.clear();
ws.parents.clear();
}
del_graph_with_rev(txn, graph, n.previous, source, target, n_introduced_by)?;
if apply_check(txn, graph, source, target)? {
put_graph_with_rev(txn, graph, n.flag, source, target, change)?;
for intro in zombies.drain(..) {
put_graph_with_rev(txn, graph, EdgeFlags::DELETED, source, target, intro)?;
}
}
if target.end >= n.to.end {
debug!("{:?} {:?}", target, n.to);
debug_assert_eq!(target.end, n.to.end);
break;
}
source = target;
target = *txn
.find_block(graph, target.end_pos())
.map_err(LocalApplyError::from)?;
assert_ne!(source, target);
}
if n.flag.contains(EdgeFlags::DELETED) {
collect_zombie_context(txn, graph, &mut ws.missing_context, inode, n, change, known)
.map_err(LocalApplyError::from_missing)?;
}
Ok(())
}
fn collect_nondeleted_zombies<T, E, K>(
txn: &mut T,
graph: &mut T::Graph,
mut known: K,
source: Vertex<ChangeId>,
target: Vertex<ChangeId>,
zombies: &mut Vec<ChangeId>,
) -> Result<(), E>
where
T: GraphMutTxnT,
E: From<LocalApplyError<T::GraphError>> + From<TxnErr<T::GraphError>>,
K: FnMut(&Hash) -> bool,
{
for v in iter_deleted_parents(txn, graph, source)? {
let v = v?;
let intro = v.introduced_by();
if !known(&txn.get_external(&intro)?.unwrap().into()) {
zombies.push(intro)
}
}
for v in iter_adjacent(txn, graph, target, EdgeFlags::empty(), EdgeFlags::all())? {
let v = v?;
if v.flag().contains(EdgeFlags::PARENT) {
continue;
}
for v in iter_deleted_parents(txn, graph, target)? {
let v = v?;
let intro = v.introduced_by();
if !known(&txn.get_external(&intro)?.unwrap().into()) {
zombies.push(intro)
}
}
}
Ok(())
}
fn check_valid<T: GraphMutTxnT>(
txn: &mut T,
graph: &mut T::Graph,
inode: Position<Option<Hash>>,
n: &NewEdge<Option<Hash>>,
ws: &mut super::Workspace,
) -> Result<(), LocalApplyError<T::GraphError>> {
if n.flag.contains(EdgeFlags::DELETED) {
ws.missing_context
.load_graph(txn, graph, inode)
.map_err(|_| LocalApplyError::InvalidChange)?;
}
if (n.previous.is_block() && !n.flag.is_block())
|| (n.previous.is_folder() != n.flag.is_folder())
{
return Err(LocalApplyError::InvalidChange.into());
}
debug_assert!(ws.children.is_empty());
debug_assert!(ws.parents.is_empty());
Ok(())
}
fn find_source_vertex<T: GraphMutTxnT>(
txn: &mut T,
channel: &mut T::Graph,
from: &Position<Option<Hash>>,
change: ChangeId,
inode: Position<Option<Hash>>,
flag: EdgeFlags,
ws: &mut super::Workspace,
) -> Result<Vertex<ChangeId>, LocalApplyError<T::GraphError>> {
debug!("find_source_vertex");
let mut source = *txn.find_block_end(&channel, internal_pos(txn, &from, change)?)?;
debug!("source = {:?}", source);
if source.start < from.pos && source.end > from.pos {
assert!(!flag.contains(EdgeFlags::FOLDER));
ws.missing_context.graphs.split(inode, source, from.pos);
txn.split_block(channel, &source, from.pos, &mut ws.adjbuf)?;
source.end = from.pos;
}
Ok(source)
}
fn find_target_vertex<T: GraphMutTxnT>(
txn: &mut T,
channel: &mut T::Graph,
to: &Vertex<Option<Hash>>,
change: ChangeId,
inode: Position<Option<Hash>>,
flag: EdgeFlags,
ws: &mut super::Workspace,
) -> Result<Vertex<ChangeId>, LocalApplyError<T::GraphError>> {
let to_pos = internal_pos(txn, &to.start_pos(), change)?;
debug!("find_target_vertex, to = {:?}", to);
let mut target = *txn.find_block(channel, to_pos)?;
debug!("target = {:?}", target);
if target.start < to.start {
assert!(!flag.contains(EdgeFlags::FOLDER));
ws.missing_context.graphs.split(inode, target, to.start);
txn.split_block(channel, &target, to.start, &mut ws.adjbuf)?;
target.start = to.start;
}
Ok(target)
}
fn collect_pseudo_edges<T: GraphMutTxnT>(
txn: &mut T,
channel: &mut T::Graph,
apply: &mut super::Workspace,
inode: Position<Option<Hash>>,
v: Vertex<ChangeId>,
) -> Result<(), LocalApplyError<T::GraphError>> {
for e in iter_adjacent(
txn,
&channel,
v,
EdgeFlags::empty(),
EdgeFlags::all() - EdgeFlags::DELETED,
)? {
let e = e?;
debug!("collect_pseudo_edges {:?} {:?}", v, e);
if !e.flag().contains(EdgeFlags::FOLDER) {
if e.flag().contains(EdgeFlags::PARENT) {
let p = txn.find_block_end(channel, e.dest())?;
if is_alive(txn, channel, p)? {
apply.parents.insert(*p);
}
} else {
let p = txn.find_block(channel, e.dest())?;
if e.flag().contains(EdgeFlags::BLOCK)
|| p.is_empty()
|| is_alive(txn, channel, p).unwrap()
{
apply.children.insert(*p);
}
}
}
if e.flag().contains(EdgeFlags::PSEUDO) {
apply.pseudo.push((v, *e, inode));
}
}
Ok(())
}
fn reconnect_pseudo_edges<T: GraphMutTxnT>(
txn: &mut T,
channel: &mut T::Graph,
inode: Position<Option<Hash>>,
ws: &mut super::Workspace,
target: Vertex<ChangeId>,
) -> Result<(), LocalApplyError<T::GraphError>> {
if ws.parents.is_empty() || ws.children.is_empty() {
return Ok(());
}
let (graph, vids) = if let Some(x) = ws.missing_context.graphs.get(inode) {
x
} else {
return Err(LocalApplyError::InvalidChange.into());
};
crate::alive::remove_redundant_parents(
&graph,
&vids,
&mut ws.parents,
&mut ws.missing_context.covered_parents,
target,
);
for &p in ws.parents.iter() {
ws.missing_context.covered_parents.insert((p, target));
}
crate::alive::remove_redundant_children(&graph, &vids, &mut ws.children, target);
for &p in ws.parents.iter() {
debug_assert!(is_alive(txn, channel, &p).unwrap());
for &c in ws.children.iter() {
if p != c {
debug_assert!(is_alive(txn, channel, &c).unwrap());
put_graph_with_rev(txn, channel, EdgeFlags::PSEUDO, p, c, ChangeId::ROOT)?;
}
}
}
Ok(())
}
fn collect_zombie_context<T: GraphMutTxnT, K>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut crate::missing_context::Workspace,
inode: Position<Option<Hash>>,
n: &NewEdge<Option<Hash>>,
change_id: ChangeId,
mut known: K,
) -> Result<(), MissingError<T::GraphError>>
where
K: FnMut(&Hash) -> bool,
{
if n.flag.contains(EdgeFlags::FOLDER) {
return Ok(());
}
let mut pos = internal_pos(txn, &n.to.start_pos(), change_id)?;
let end_pos = internal_pos(txn, &n.to.end_pos(), change_id)?;
let mut unknown_parents = Vec::new();
while let Ok(&dest_vertex) = txn.find_block(&channel, pos) {
debug!("collect zombie context: {:?}", dest_vertex);
for v in iter_adjacent(
txn,
channel,
dest_vertex,
EdgeFlags::empty(),
EdgeFlags::all() - EdgeFlags::DELETED,
)? {
let v = v?;
if v.introduced_by() == change_id || v.dest().change.is_root() {
continue;
}
if v.introduced_by().is_root() {
ws.pseudo.push((dest_vertex, *v));
continue;
}
if v.flag().contains(EdgeFlags::PARENT) {
// Unwrap ok, since `v` is in the channel.
let intro = txn.get_external(&v.introduced_by())?.unwrap().into();
if !known(&intro) {
debug!("unknown: {:?}", v);
unknown_parents.push((dest_vertex, *v))
}
}
}
zombify(txn, channel, ws, change_id, inode, n.flag, &unknown_parents)?;
if dest_vertex.end < end_pos.pos {
pos.pos = dest_vertex.end
} else {
break;
}
}
Ok(())
}
fn zombify<T: GraphMutTxnT>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut crate::missing_context::Workspace,
change_id: ChangeId,
inode: Position<Option<Hash>>,
flag: EdgeFlags,
unknown: &[(Vertex<ChangeId>, SerializedEdge)],
) -> Result<(), MissingError<T::GraphError>> {
for &(dest_vertex, edge) in unknown.iter() {
let p = *txn.find_block_end(channel, edge.dest())?;
ws.unknown_parents
.push((dest_vertex, p, inode, edge.flag()));
let fold = flag & EdgeFlags::FOLDER;
debug!("zombify p {:?}, dest_vertex {:?}", p, dest_vertex);
let mut v = p;
while let Ok(&u) = txn.find_block_end(channel, v.start_pos()) {
if u != v {
debug!("u = {:?}, v = {:?}", u, v);
put_graph_with_rev(
txn,
channel,
EdgeFlags::DELETED | EdgeFlags::BLOCK | fold,
u,
v,
change_id,
)?;
v = u
} else {
break;
}
}
// Zombify the first chunk of the split.
for parent in iter_adjacent(
txn,
channel,
v,
EdgeFlags::PARENT,
EdgeFlags::all() - EdgeFlags::DELETED,
)? {
let parent = parent?;
if !parent.flag().contains(EdgeFlags::PSEUDO) {
ws.parents.insert(*parent);
}
}
debug!("ws.parents = {:?}", ws.parents);
for parent in ws.parents.drain() {
let parent_dest = *txn.find_block_end(channel, parent.dest())?;
let mut flag = EdgeFlags::DELETED | EdgeFlags::BLOCK;
if parent.flag().contains(EdgeFlags::FOLDER) {
flag |= EdgeFlags::FOLDER
}
put_graph_with_rev(txn, channel, flag, parent_dest, v, change_id)?;
}
}
Ok(())
}
};
"crossbeam-deque" = rec {
crateName = "crossbeam-deque";
version = "0.8.0";
edition = "2018";
sha256 = "1ad995vzq74k7jd1pgn9zxbacyzj9ii6l0svhlb2dxzy8vxnxbwl";
authors = [
"The Crossbeam Project Developers"
];
dependencies = [
{
name = "cfg-if";
packageId = "cfg-if 1.0.0";
}
{
name = "crossbeam-epoch";
packageId = "crossbeam-epoch";
optional = true;
usesDefaultFeatures = false;
}
{
name = "crossbeam-utils";
packageId = "crossbeam-utils";
optional = true;
usesDefaultFeatures = false;
}
];
features = {
"default" = [ "std" ];
"std" = [ "crossbeam-epoch/std" "crossbeam-utils/std" ];
};
resolvedDefaultFeatures = [ "crossbeam-epoch" "crossbeam-utils" "default" "std" ];
};
"crossbeam-epoch" = rec {
crateName = "crossbeam-epoch";
version = "0.9.3";
edition = "2018";
sha256 = "04jbrwrm6ibmd83anc3difsvk0fgjyr1aqs9k33sizlmxcwzd115";
authors = [
"The Crossbeam Project Developers"
];
dependencies = [
{
name = "cfg-if";
packageId = "cfg-if 1.0.0";
}
{
name = "crossbeam-utils";
packageId = "crossbeam-utils";
usesDefaultFeatures = false;
}
{
name = "lazy_static";
packageId = "lazy_static";
optional = true;
}
{
name = "memoffset";
packageId = "memoffset";
}
{
name = "scopeguard";
packageId = "scopeguard";
usesDefaultFeatures = false;
}
];
features = {
"default" = [ "std" ];
"loom" = [ "loom-crate" "crossbeam-utils/loom" ];
"nightly" = [ "crossbeam-utils/nightly" "const_fn" ];
"std" = [ "alloc" "crossbeam-utils/std" "lazy_static" ];
};
resolvedDefaultFeatures = [ "alloc" "lazy_static" "std" ];
};
"memoffset" = rec {
crateName = "memoffset";
version = "0.6.1";
edition = "2015";
sha256 = "11yxgw330cf8g4wy0fnb20ag8gg1b33fsnfmg2g8z6h5wc444yqm";
authors = [
"Gilad Naaman <gilad.naaman@gmail.com>"
];
buildDependencies = [
{
name = "autocfg";
packageId = "autocfg";
}
];
features = {
};
resolvedDefaultFeatures = [ "default" ];
"rand" = rec {
"rand 0.7.3" = rec {
crateName = "rand";
version = "0.7.3";
edition = "2018";
sha256 = "00sdaimkbz491qgi6qxkv582yivl32m2jd401kzbn94vsiwicsva";
authors = [
"The Rand Project Developers"
"The Rust Project Developers"
];
dependencies = [
{
name = "getrandom";
packageId = "getrandom 0.1.16";
rename = "getrandom_package";
optional = true;
}
{
name = "libc";
packageId = "libc";
optional = true;
usesDefaultFeatures = false;
target = { target, features }: (target."unix" or false);
}
{
name = "rand_chacha";
packageId = "rand_chacha 0.2.2";
usesDefaultFeatures = false;
target = { target, features }: (!(target."os" == "emscripten"));
}
{
name = "rand_core";
packageId = "rand_core 0.5.1";
}
{
name = "rand_hc";
packageId = "rand_hc 0.2.0";
target = { target, features }: (target."os" == "emscripten");
}
];
devDependencies = [
{
name = "rand_hc";
packageId = "rand_hc 0.2.0";
}
];
features = {
"alloc" = [ "rand_core/alloc" ];
"default" = [ "std" ];
"getrandom" = [ "getrandom_package" "rand_core/getrandom" ];
"nightly" = [ "simd_support" ];
"simd_support" = [ "packed_simd" ];
"small_rng" = [ "rand_pcg" ];
"std" = [ "rand_core/std" "rand_chacha/std" "alloc" "getrandom" "libc" ];
"stdweb" = [ "getrandom_package/stdweb" ];
"wasm-bindgen" = [ "getrandom_package/wasm-bindgen" ];
};
resolvedDefaultFeatures = [ "alloc" "default" "getrandom" "getrandom_package" "libc" "std" ];
};
"rand 0.8.3" = rec {
version = "0.2.2";
edition = "2018";
sha256 = "00il36fkdbsmpr99p9ksmmp6dn1md7rmnwmz0rr77jbrca2yvj7l";
authors = [
"The Rand Project Developers"
"The Rust Project Developers"
"The CryptoCorrosion Contributors"
];
dependencies = [
{
name = "ppv-lite86";
packageId = "ppv-lite86";
usesDefaultFeatures = false;
features = [ "simd" ];
}
{
name = "rand_core";
packageId = "rand_core 0.5.1";
}
];
features = {
"default" = [ "std" "simd" ];
"std" = [ "ppv-lite86/std" ];
};
resolvedDefaultFeatures = [ "std" ];
};
"rand_chacha 0.3.0" = rec {
crateName = "rand_chacha";
"rand_hc" = rec {
"rand_hc 0.2.0" = rec {
crateName = "rand_hc";
version = "0.2.0";
edition = "2018";
sha256 = "0g31sqwpmsirdlwr0svnacr4dbqyz339im4ssl9738cjgfpjjcfa";
authors = [
"The Rand Project Developers"
];
dependencies = [
{
name = "rand_core";
packageId = "rand_core 0.5.1";
}
];
};
"rand_hc 0.3.0" = rec {
};
"twox-hash" = rec {
crateName = "twox-hash";
version = "1.6.0";
edition = "2018";
crateBin = [];
sha256 = "0ndb4pil758kn0av83jjgq8kkfkwc5lhi5ii7fk5yw96h1wapy04";
authors = [
"Jake Goulding <jake.goulding@gmail.com>"
];
dependencies = [
{
name = "cfg-if";
packageId = "cfg-if 0.1.10";
usesDefaultFeatures = false;
}
{
name = "rand";
packageId = "rand 0.7.3";
optional = true;
}
{
name = "static_assertions";
packageId = "static_assertions";
usesDefaultFeatures = false;
}
];
features = {
"default" = [ "std" ];
"serialize" = [ "serde" ];
"std" = [ "rand" ];
};
resolvedDefaultFeatures = [ "default" "rand" "std" ];
name = "crossbeam-epoch"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils",
"lazy_static",
"memoffset",
"scopeguard",
]
[[package]]
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom 0.1.16",
"libc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc 0.2.0",
]
[[package]]
name = "rand"
"rand_hc",
"rand_hc 0.3.0",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core 0.5.1",