Skip to main content

pijul_core/
lib.rs

1#[macro_use]
2extern crate log;
3#[macro_use]
4extern crate serde_derive;
5#[macro_use]
6extern crate bitflags;
7#[macro_use]
8extern crate pijul_macros;
9#[macro_use]
10extern crate thiserror;
11#[cfg(test)]
12#[macro_use]
13extern crate quickcheck;
14
15pub mod alive;
16pub mod apply;
17pub mod change;
18pub mod changestore;
19mod diff;
20pub mod fs;
21pub mod missing_context;
22pub mod output;
23pub mod path;
24pub mod pin;
25pub mod pristine;
26pub mod record;
27pub mod small_string;
28mod text_encoding;
29pub mod unrecord;
30mod vector2;
31pub mod vertex_buffer;
32pub mod working_copy;
33
34pub mod key;
35
36#[cfg(test)]
37mod tests;
38
39use std::sync::{LazyLock, Mutex};
40
41pub const DOT_DIR: &str = ".pijul";
42pub const DEFAULT_CHANNEL: &str = "main";
43
44#[derive(Debug, Error)]
45#[error("Parse error: {:?}", s)]
46pub struct ParseError {
47    s: String,
48}
49
50#[derive(Debug, Error, Serialize, Deserialize)]
51pub enum RemoteError {
52    #[error("Repository not found: {}", url)]
53    RepositoryNotFound { url: String },
54    #[error("Channel {} not found for repository {}", channel, url)]
55    ChannelNotFound { channel: String, url: String },
56    #[error("Ambiguous path: {}", path)]
57    AmbiguousPath { path: String },
58    #[error("Path not found: {}", path)]
59    PathNotFound { path: String },
60    #[error("Change not found: {}", change)]
61    ChangeNotFound { change: String },
62}
63
64pub use crate::apply::Workspace as ApplyWorkspace;
65pub use crate::apply::{ApplyError, LocalApplyError, apply_change_arc};
66pub use crate::diff::DEFAULT_SEPARATOR;
67pub use crate::fs::{FsError, WorkingCopyIterator};
68pub use crate::output::{Archive, Conflict};
69pub use crate::pristine::{
70    ArcTxn, Base32, ChangeId, ChannelMutTxnT, ChannelRef, ChannelTxnT, DepsTxnT, EdgeFlags,
71    GraphTxnT, Hash, Inode, Merkle, MutTxnT, OwnedPathId, RemoteRef, ResultOptionalExt, TreeTxnT,
72    TxnT, Vertex,
73};
74pub use crate::record::Builder as RecordBuilder;
75pub use crate::record::{Algorithm, InodeUpdate};
76pub use crate::text_encoding::Encoding;
77pub use crate::unrecord::UnrecordError;
78
79pub type Hasher = std::collections::hash_map::RandomState;
80
81pub type HashMap<K, V> = std::collections::HashMap<K, V, Hasher>;
82pub type HashSet<K> = std::collections::HashSet<K, Hasher>;
83
84impl<
85    T: ::sanakirja::LoadPage<Error = ::sanakirja::Error>
86        + ::sanakirja::RootPageMut
87        + ::sanakirja::Commit
88        + ::sanakirja::AllocPage<Error = ::sanakirja::Error>,
89> MutTxnTExt for pristine::sanakirja::GenericTxn<T>
90{
91}
92impl<T: ::sanakirja::LoadPage<Error = ::sanakirja::Error> + ::sanakirja::RootPage> TxnTExt
93    for pristine::sanakirja::GenericTxn<T>
94{
95}
96
97pub fn commit<T: pristine::MutTxnT>(
98    txn: std::sync::Arc<std::sync::RwLock<T>>,
99) -> Result<(), T::GraphError> {
100    let txn = match std::sync::Arc::try_unwrap(txn) {
101        Ok(txn) => txn.into_inner().unwrap(),
102        _ => {
103            unreachable!()
104        }
105    };
106    txn.commit()
107}
108
109pub trait MutTxnTExt: pristine::MutTxnT {
110    #[allow(clippy::type_complexity)]
111    fn apply_root_change_if_needed<C: changestore::ChangeStore, R: rand::Rng>(
112        &mut self,
113        changes: &C,
114        channel: &ChannelRef<Self>,
115        rng: R,
116    ) -> Result<
117        Option<(pristine::Hash, u64, pristine::Merkle)>,
118        crate::apply::ApplyError<C::Error, Self>,
119    > {
120        crate::apply::apply_root_change(self, channel, changes, rng)
121    }
122
123    fn apply_change_ws<C: changestore::ChangeStore>(
124        &mut self,
125        changes: &C,
126        channel: &mut Self::Channel,
127        hash: &crate::pristine::Hash,
128        workspace: &mut ApplyWorkspace,
129    ) -> Result<(u64, pristine::Merkle), crate::apply::ApplyError<C::Error, Self>> {
130        crate::apply::apply_change_ws(changes, self, channel, hash, workspace)
131    }
132
133    fn apply_change_rec_ws<C: changestore::ChangeStore>(
134        &mut self,
135        changes: &C,
136        channel: &mut Self::Channel,
137        hash: &crate::pristine::Hash,
138        workspace: &mut ApplyWorkspace,
139    ) -> Result<(), crate::apply::ApplyError<C::Error, Self>> {
140        crate::apply::apply_change_rec_ws(changes, self, channel, hash, workspace, false)
141    }
142
143    fn apply_change<C: changestore::ChangeStore>(
144        &mut self,
145        changes: &C,
146        channel: &mut Self::Channel,
147        hash: &pristine::Hash,
148    ) -> Result<(u64, pristine::Merkle), crate::apply::ApplyError<C::Error, Self>> {
149        crate::apply::apply_change(changes, self, channel, hash)
150    }
151
152    fn apply_change_rec<C: changestore::ChangeStore>(
153        &mut self,
154        changes: &C,
155        channel: &mut Self::Channel,
156        hash: &pristine::Hash,
157    ) -> Result<(), crate::apply::ApplyError<C::Error, Self>> {
158        crate::apply::apply_change_rec(changes, self, channel, hash, false)
159    }
160
161    fn apply_deps_rec<C: changestore::ChangeStore>(
162        &mut self,
163        changes: &C,
164        channel: &mut Self::Channel,
165        hash: &pristine::Hash,
166    ) -> Result<(), crate::apply::ApplyError<C::Error, Self>> {
167        crate::apply::apply_change_rec(changes, self, channel, hash, true)
168    }
169
170    fn apply_local_change_ws(
171        &mut self,
172        channel: &pristine::ChannelRef<Self>,
173        change: &change::Change,
174        hash: &pristine::Hash,
175        inode_updates: &HashMap<usize, InodeUpdate>,
176        workspace: &mut ApplyWorkspace,
177    ) -> Result<(u64, pristine::Merkle), crate::apply::LocalApplyError<Self>> {
178        crate::apply::apply_local_change_ws(self, channel, change, hash, inode_updates, workspace)
179    }
180
181    fn apply_local_change(
182        &mut self,
183        channel: &crate::pristine::ChannelRef<Self>,
184        change: &crate::change::Change,
185        hash: &pristine::Hash,
186        inode_updates: &HashMap<usize, InodeUpdate>,
187    ) -> Result<(u64, pristine::Merkle), crate::apply::LocalApplyError<Self>> {
188        crate::apply::apply_local_change(self, channel, change, hash, inode_updates)
189    }
190
191    fn apply_recorded<C: changestore::ChangeStore>(
192        &mut self,
193        channel: &mut pristine::ChannelRef<Self>,
194        recorded: record::Recorded,
195        changestore: &C,
196    ) -> Result<pristine::Hash, crate::apply::ApplyError<C::Error, Self>> {
197        let contents_hash = {
198            let mut hasher = pristine::Hasher::default();
199            hasher.update(&recorded.contents.lock()[..]);
200            hasher.finish()
201        };
202        let mut change = change::LocalChange {
203            offsets: change::Offsets::default(),
204            hashed: change::Hashed {
205                version: change::VERSION,
206                contents_hash,
207                changes: recorded
208                    .actions
209                    .into_iter()
210                    .map(|rec| rec.globalize(self).unwrap())
211                    .collect(),
212                metadata: Vec::new(),
213                dependencies: Vec::new(),
214                extra_known: Vec::new(),
215                header: change::ChangeHeader::default(),
216            },
217            unhashed: None,
218            contents: std::sync::Arc::try_unwrap(recorded.contents)
219                .unwrap()
220                .into_inner(),
221        };
222        let hash = changestore
223            .save_change(&mut change, |_, _| Ok(()))
224            .map_err(apply::ApplyError::Changestore)?;
225        apply::apply_local_change(self, channel, &change, &hash, &recorded.updatables)
226            .map_err(ApplyError::LocalChange)?;
227        Ok(hash)
228    }
229
230    fn unrecord<C: changestore::ChangeStore>(
231        &mut self,
232        changes: &C,
233        channel: &pristine::ChannelRef<Self>,
234        hash: &pristine::Hash,
235        salt: u64,
236        touched: &mut unrecord::TouchedInodes,
237    ) -> Result<bool, unrecord::UnrecordError<C::Error, Self>> {
238        unrecord::unrecord(self, channel, changes, hash, salt, touched)
239    }
240
241    fn touch_inodes<W: working_copy::WorkingCopy>(
242        &mut self,
243        working_copy: &W,
244        touched_inodes: &unrecord::TouchedInodes,
245    ) -> Result<(), unrecord::TouchError<W::Error, Self>> {
246        unrecord::touch_inodes(self, working_copy, touched_inodes)
247    }
248
249    /// Register a file in the working copy, where the file is given by
250    /// its path from the root of the repository, where the components of
251    /// the path are separated by `/` (example path: `a/b/c`).
252    fn add_file(&mut self, path: &str, salt: u64) -> Result<Inode, fs::FsError<Self>> {
253        fs::add_inode(self, None, path, false, salt)
254    }
255
256    /// Register a directory in the working copy, where the directory is
257    /// given by its path from the root of the repository, where the
258    /// components of the path are separated by `/` (example path:
259    /// `a/b/c`).
260    fn add_dir(&mut self, path: &str, salt: u64) -> Result<Inode, fs::FsError<Self>> {
261        fs::add_inode(self, None, path, true, salt)
262    }
263
264    /// Register a file or directory in the working copy, given by its
265    /// path from the root of the repository, where the components of the
266    /// path are separated by `/` (example path: `a/b/c`).
267    fn add(&mut self, path: &str, is_dir: bool, salt: u64) -> Result<Inode, fs::FsError<Self>> {
268        fs::add_inode(self, None, path, is_dir, salt)
269    }
270
271    fn move_file(&mut self, a: &str, b: &str, salt: u64) -> Result<(), fs::FsError<Self>> {
272        fs::move_file(self, a, b, salt)
273    }
274
275    fn remove_file(&mut self, a: &str) -> Result<(), fs::FsError<Self>> {
276        fs::remove_file(self, a)
277    }
278}
279
280pub trait TxnTExt: pristine::TxnT {
281    fn is_directory(&self, inode: pristine::Inode) -> Result<bool, Self::TreeError> {
282        fs::is_directory(self, inode).map_err(|e| e.0)
283    }
284
285    fn is_tracked(&self, path: &str) -> Result<bool, Self::TreeError> {
286        fs::is_tracked(self, path).map_err(|e| e.0)
287    }
288
289    fn iter_working_copy<'a>(&'a self) -> WorkingCopyIterator<'a, Self> {
290        fs::iter_working_copy(self, pristine::Inode::ROOT)
291    }
292
293    fn iter_graph_children<'txn, 'changes, P>(
294        &'txn self,
295        changes: &'changes P,
296        channel: &'txn Self::Channel,
297        key: pristine::Position<ChangeId>,
298    ) -> Result<fs::GraphChildren<'txn, 'changes, Self, P>, Self::GraphError>
299    where
300        P: changestore::ChangeStore,
301    {
302        fs::iter_graph_children(self, changes, self.graph(channel), key)
303    }
304
305    fn has_change(
306        &self,
307        channel: &pristine::ChannelRef<Self>,
308        hash: &pristine::Hash,
309    ) -> Result<Option<u64>, Self::GraphError> {
310        if let Some(cid) = pristine::GraphTxnT::get_internal(self, &hash.into()).map_err(|e| e.0)? {
311            self.get_changeset(self.changes(&channel.read()), cid)
312                .map_err(|e| e.0)
313                .map(|x| x.map(|x| u64::from_le(x.0)))
314        } else {
315            Ok(None)
316        }
317    }
318
319    fn is_alive(
320        &self,
321        channel: &Self::Channel,
322        a: &pristine::Vertex<pristine::ChangeId>,
323    ) -> Result<bool, Self::GraphError> {
324        pristine::is_alive(self, self.graph(channel), a).map_err(|e| e.0)
325    }
326
327    fn current_state(&self, channel: &Self::Channel) -> Result<pristine::Merkle, Self::GraphError> {
328        pristine::current_state(self, channel).map_err(|e| e.0)
329    }
330
331    fn log<'txn>(
332        &'txn self,
333        channel: &Self::Channel,
334        from: u64,
335    ) -> Result<Log<'txn, Self>, Self::GraphError> {
336        Ok(Log {
337            txn: self,
338            iter: pristine::changeid_log(self, channel, pristine::L64(from.to_le()))
339                .map_err(|e| e.0)?,
340        })
341    }
342
343    fn log_for_path<'channel, 'txn>(
344        &'txn self,
345        channel: &'channel Self::Channel,
346        pos: pristine::Position<pristine::ChangeId>,
347        from: u64,
348    ) -> Result<pristine::PathChangeset<'channel, 'txn, Self>, Self::GraphError> {
349        pristine::log_for_path(self, channel, pos, from).map_err(|e| e.0)
350    }
351
352    fn rev_log_for_path<'channel, 'txn>(
353        &'txn self,
354        channel: &'channel Self::Channel,
355        pos: pristine::Position<pristine::ChangeId>,
356        from: u64,
357    ) -> Result<pristine::RevPathChangeset<'channel, 'txn, Self>, Self::DepsError> {
358        pristine::rev_log_for_path(self, channel, pos, from).map_err(|e| e.0)
359    }
360
361    fn reverse_log<'txn>(
362        &'txn self,
363        channel: &Self::Channel,
364        from: Option<u64>,
365    ) -> Result<RevLog<'txn, Self>, Self::GraphError> {
366        Ok(RevLog {
367            txn: self,
368            iter: pristine::changeid_rev_log(self, channel, from.map(|x| pristine::L64(x.to_le())))
369                .map_err(|e| e.0)?,
370        })
371    }
372
373    #[allow(clippy::type_complexity)]
374    fn changeid_reverse_log<'txn>(
375        &'txn self,
376        channel: &Self::Channel,
377        from: Option<pristine::L64>,
378    ) -> Result<
379        pristine::RevCursor<
380            Self,
381            &'txn Self,
382            Self::RevchangesetCursor,
383            pristine::L64,
384            pristine::Pair<pristine::ChangeId, pristine::SerializedMerkle>,
385        >,
386        Self::GraphError,
387    > {
388        pristine::changeid_rev_log(self, channel, from).map_err(|e| e.0)
389    }
390
391    fn get_changes(
392        &self,
393        channel: &pristine::ChannelRef<Self>,
394        n: u64,
395    ) -> Result<Option<(pristine::Hash, pristine::Merkle)>, Self::GraphError> {
396        match self
397            .get_revchangeset(self.rev_changes(&channel.read()), &pristine::L64(n.to_le()))
398            .map_err(|e| e.0)?
399        {
400            Some(p) => Ok(Some((
401                self.get_external(&p.a)
402                    .optional()
403                    .map_err(|e| e.0)?
404                    .unwrap()
405                    .into(),
406                (&p.b).into(),
407            ))),
408            _ => Ok(None),
409        }
410    }
411
412    fn get_revchanges(
413        &self,
414        channel: &pristine::ChannelRef<Self>,
415        h: &pristine::Hash,
416    ) -> Result<Option<u64>, Self::GraphError> {
417        if let Some(h) = pristine::GraphTxnT::get_internal(self, &h.into()).map_err(|e| e.0)? {
418            self.get_changeset(self.changes(&channel.read()), h)
419                .map_err(|e| e.0)
420                .map(|x| x.map(|x| u64::from_le(x.0)))
421        } else {
422            Ok(None)
423        }
424    }
425
426    fn touched_files<'a>(
427        &'a self,
428        h: &pristine::Hash,
429    ) -> Result<Option<Touched<'a, Self>>, Self::DepsError> {
430        if let Some(id) = pristine::GraphTxnT::get_internal(self, &h.into()).map_err(|e| e.0)? {
431            Ok(Some(Touched {
432                txn: self,
433                iter: self.iter_rev_touched_files(id, None).map_err(|e| e.0)?,
434                id: *id,
435            }))
436        } else {
437            Ok(None)
438        }
439    }
440
441    #[allow(clippy::type_complexity)]
442    fn find_oldest_path<C: changestore::ChangeStore>(
443        &self,
444        changes: &C,
445        channel: &pristine::ChannelRef<Self>,
446        position: &pristine::Position<pristine::Hash>,
447    ) -> Result<Option<(String, bool)>, output::FileError<C::Error, Self>> {
448        let position = pristine::Position {
449            change: *pristine::GraphTxnT::get_internal(self, &position.change.into())?.unwrap(),
450            pos: position.pos,
451        };
452        match fs::find_path(changes, self, &channel.read(), false, position)? {
453            Some(a) => Ok(Some((a.path.join("/"), a.all_alive))),
454            _ => Ok(None),
455        }
456    }
457
458    #[allow(clippy::type_complexity)]
459    fn find_youngest_path<C: changestore::ChangeStore>(
460        &self,
461        changes: &C,
462        channel: &pristine::ChannelRef<Self>,
463        position: pristine::Position<pristine::Hash>,
464    ) -> Result<Option<(String, bool)>, output::FileError<C::Error, Self>> {
465        let position = pristine::Position {
466            change: *pristine::GraphTxnT::get_internal(self, &position.change.into())?.unwrap(),
467            pos: position.pos,
468        };
469        match fs::find_path(changes, self, &channel.read(), true, position)? {
470            Some(a) => Ok(Some((a.path.join("/"), a.all_alive))),
471            _ => Ok(None),
472        }
473    }
474
475    fn follow_oldest_path<C: changestore::ChangeStore>(
476        &self,
477        changes: &C,
478        channel: &pristine::ChannelRef<Self>,
479        path: &str,
480    ) -> Result<(pristine::Position<pristine::ChangeId>, bool), fs::FsErrorC<C::Error, Self>> {
481        fs::follow_oldest_path(changes, self, &channel.read(), path)
482    }
483
484    fn iter_adjacent<'txn>(
485        &'txn self,
486        graph: &'txn Self::Channel,
487        key: Vertex<pristine::ChangeId>,
488        min_flag: pristine::EdgeFlags,
489        max_flag: pristine::EdgeFlags,
490    ) -> Result<pristine::AdjacentIterator<'txn, Self>, pristine::TxnErr<Self::GraphError>> {
491        pristine::iter_adjacent(self, self.graph(graph), key, min_flag, max_flag)
492    }
493}
494
495impl<T: ChannelTxnT + TreeTxnT + DepsTxnT<DepsError = <T as GraphTxnT>::GraphError>> ArcTxn<T> {
496    #[allow(clippy::type_complexity)]
497    pub fn archive<C: changestore::ChangeStore, A: Archive>(
498        &self,
499        changes: &C,
500        channel: &pristine::ChannelRef<T>,
501        arch: &mut A,
502    ) -> Result<Vec<output::Conflict>, output::ArchiveError<C::Error, T, A::Error>> {
503        output::archive(changes, self, channel, &mut std::iter::empty(), arch)
504    }
505
506    pub fn archive_prefix<
507        'a,
508        C: changestore::ChangeStore,
509        I: Iterator<Item = &'a str>,
510        A: Archive,
511    >(
512        &self,
513        changes: &C,
514        channel: &pristine::ChannelRef<T>,
515        prefix: &mut I,
516        arch: &mut A,
517    ) -> Result<Vec<output::Conflict>, output::ArchiveError<C::Error, T, A::Error>> {
518        output::archive(changes, self, channel, prefix, arch)
519    }
520}
521
522impl<T: MutTxnT> ArcTxn<T> {
523    #[allow(clippy::type_complexity)]
524    pub fn archive_with_state<P: changestore::ChangeStore, A: Archive>(
525        &self,
526        changes: &P,
527        channel: &pristine::ChannelRef<T>,
528        state: &pristine::Merkle,
529        extra: &[pristine::Hash],
530        arch: &mut A,
531        salt: u64,
532    ) -> Result<Vec<output::Conflict>, output::ArchiveError<P::Error, T, A::Error>> {
533        self.archive_prefix_with_state(
534            changes,
535            channel,
536            state,
537            extra,
538            &mut std::iter::empty(),
539            arch,
540            salt,
541        )
542    }
543
544    /// Warning: this method unrecords changes until finding the
545    /// state. If this is not wanted, please fork the channel before
546    /// calling.
547    #[allow(clippy::too_many_arguments)]
548    pub fn archive_prefix_with_state<
549        'a,
550        P: changestore::ChangeStore,
551        A: Archive,
552        I: Iterator<Item = &'a str>,
553    >(
554        &self,
555        changes: &P,
556        channel: &pristine::ChannelRef<T>,
557        state: &pristine::Merkle,
558        extra: &[pristine::Hash],
559        prefix: &mut I,
560        arch: &mut A,
561        salt: u64,
562    ) -> Result<Vec<output::Conflict>, output::ArchiveError<P::Error, T, A::Error>> {
563        let mut unrecord = Vec::new();
564        let mut found = false;
565        let mut txn = self.write();
566        for x in pristine::changeid_rev_log(&*txn, &channel.read(), None)? {
567            let (_, p) = x?;
568            let m: Merkle = (&p.b).into();
569            if &m == state {
570                found = true;
571                break;
572            } else {
573                unrecord.push(p.a)
574            }
575        }
576        debug!("unrecord = {:?}", unrecord);
577        if found {
578            for h in unrecord.iter() {
579                let h = txn.get_external(h).optional()?.unwrap().into();
580                unrecord::unrecord(&mut *txn, channel, changes, &h, salt, &mut HashSet::new())?;
581            }
582            {
583                let mut channel_ = channel.write();
584                for app in extra.iter() {
585                    crate::apply::apply_change_rec(changes, &mut *txn, &mut channel_, app, false)?
586                }
587            }
588            std::mem::drop(txn);
589            output::archive(changes, self, channel, prefix, arch)
590        } else {
591            Err(output::ArchiveError::StateNotFound {
592                state: Box::new(*state),
593            })
594        }
595    }
596}
597
598pub struct Log<'txn, T: pristine::ChannelTxnT> {
599    txn: &'txn T,
600    iter: pristine::Cursor<
601        T,
602        &'txn T,
603        T::RevchangesetCursor,
604        pristine::L64,
605        pristine::Pair<pristine::ChangeId, pristine::SerializedMerkle>,
606    >,
607}
608
609impl<'txn, T: pristine::ChannelTxnT> Iterator for Log<'txn, T> {
610    type Item = Result<
611        (
612            u64,
613            (
614                &'txn pristine::SerializedHash,
615                &'txn pristine::SerializedMerkle,
616            ),
617        ),
618        T::GraphError,
619    >;
620    fn next(&mut self) -> Option<Self::Item> {
621        match self.iter.next() {
622            Some(Ok((n, p))) => {
623                let ext = match self.txn.get_external(&p.a).optional() {
624                    Err(pristine::TxnErr(e)) => return Some(Err(e)),
625                    Ok(None) => panic!("Unknown change {:?}", p),
626                    Ok(Some(ext)) => ext,
627                };
628                Some(Ok((u64::from_le(n.0), (ext, &p.b))))
629            }
630            None => None,
631            Some(Err(e)) => Some(Err(e.0)),
632        }
633    }
634}
635
636pub struct RevLog<'txn, T: pristine::ChannelTxnT> {
637    txn: &'txn T,
638    iter: pristine::RevCursor<
639        T,
640        &'txn T,
641        T::RevchangesetCursor,
642        pristine::L64,
643        pristine::Pair<pristine::ChangeId, pristine::SerializedMerkle>,
644    >,
645}
646
647impl<'txn, T: pristine::ChannelTxnT> Iterator for RevLog<'txn, T> {
648    type Item = Result<
649        (
650            u64,
651            (
652                &'txn pristine::SerializedHash,
653                &'txn pristine::SerializedMerkle,
654            ),
655        ),
656        T::GraphError,
657    >;
658    fn next(&mut self) -> Option<Self::Item> {
659        match self.iter.next() {
660            Some(Ok((n, p))) => match self.txn.get_external(&p.a).optional() {
661                Ok(Some(ext)) => Some(Ok((u64::from_le(n.0), (ext, &p.b)))),
662                Err(e) => Some(Err(e.0)),
663                Ok(None) => panic!("Unknown change {:?}", p),
664            },
665            None => None,
666            Some(Err(e)) => Some(Err(e.0)),
667        }
668    }
669}
670
671pub struct Touched<'txn, T: pristine::DepsTxnT> {
672    txn: &'txn T,
673    iter: pristine::Cursor<
674        T,
675        &'txn T,
676        T::Rev_touched_filesCursor,
677        pristine::ChangeId,
678        pristine::Position<pristine::ChangeId>,
679    >,
680    id: pristine::ChangeId,
681}
682
683impl<
684    'txn,
685    T: pristine::DepsTxnT + pristine::GraphTxnT<GraphError = <T as pristine::DepsTxnT>::DepsError>,
686> Iterator for Touched<'txn, T>
687{
688    type Item = Result<pristine::Position<pristine::Hash>, T::DepsError>;
689    fn next(&mut self) -> Option<Self::Item> {
690        for x in self.iter.by_ref() {
691            let (cid, file) = match x {
692                Ok(x) => x,
693                Err(e) => return Some(Err(e.0)),
694            };
695            if *cid > self.id {
696                return None;
697            } else if *cid == self.id {
698                let change = match self.txn.get_external(&file.change).optional() {
699                    Ok(Some(ext)) => ext,
700                    Ok(None) => panic!("Unknown change {:?}", file.change),
701                    Err(e) => return Some(Err(e.0)),
702                };
703                return Some(Ok(pristine::Position {
704                    change: change.into(),
705                    pos: file.pos,
706                }));
707            }
708        }
709        None
710    }
711}
712
713#[doc(hidden)]
714#[derive(Debug, Default, Clone)]
715pub struct Timers {
716    pub alive_output: std::time::Duration,
717    pub alive_graph: std::time::Duration,
718    pub alive_retrieve: std::time::Duration,
719    pub alive_contents: std::time::Duration,
720    pub alive_write: std::time::Duration,
721    pub record: std::time::Duration,
722    pub apply: std::time::Duration,
723    pub repair_context: std::time::Duration,
724    pub check_cyclic_paths: std::time::Duration,
725    pub find_alive: std::time::Duration,
726}
727
728pub static TIMERS: LazyLock<Mutex<Timers>> = LazyLock::new(|| {
729    Mutex::new(Timers {
730        alive_output: std::time::Duration::from_secs(0),
731        alive_graph: std::time::Duration::from_secs(0),
732        alive_retrieve: std::time::Duration::from_secs(0),
733        alive_contents: std::time::Duration::from_secs(0),
734        alive_write: std::time::Duration::from_secs(0),
735        record: std::time::Duration::from_secs(0),
736        apply: std::time::Duration::from_secs(0),
737        repair_context: std::time::Duration::from_secs(0),
738        check_cyclic_paths: std::time::Duration::from_secs(0),
739        find_alive: std::time::Duration::from_secs(0),
740    })
741});
742
743#[doc(hidden)]
744pub fn reset_timers() {
745    *TIMERS.lock().unwrap() = Timers::default();
746}
747#[doc(hidden)]
748pub fn get_timers() -> Timers {
749    TIMERS.lock().unwrap().clone()
750}
751
752pub(crate) fn get_valid_encoding(
753    enc: &chardetng::EncodingDetector,
754    tld: Option<&[u8]>,
755    allow_utf8: bool,
756    buffer: &[u8],
757) -> Option<&'static encoding_rs::Encoding> {
758    if let (encoding, true) = enc.guess_assess(tld, allow_utf8)
759        && let (s, e, false) = encoding.decode(buffer)
760    {
761        let reencoded = encoding.encode(&s).0;
762
763        // Special handling for UTF-8 BOM: encoding_rs doesn't preserve BOM during encode,
764        // but the file is still valid UTF-8 text. Check if the only difference is a UTF-8 BOM.
765        if reencoded == buffer {
766            return Some(e);
767        } else if encoding == encoding_rs::UTF_8
768            && buffer.starts_with(b"\xef\xbb\xbf")
769            && buffer[3..] == *reencoded
770        {
771            // The file has a UTF-8 BOM prefix, which is removed during decode/encode.
772            // This is still valid UTF-8 text.
773            return Some(e);
774        }
775    }
776    None
777}