Skip to main content

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