|
|
… |
710 unchanged lines |
| 711 |
711 |
|
md.len() == e.size |
| 712 |
712 |
|
&& fs::read(&path).is_ok_and(|b| *blake3::hash(&b).as_bytes() == e.hash) |
| 713 |
713 |
|
} |
|
714 |
+ |
// A subrepo: its pin is compared on its own (`subrepo_moved`), |
|
715 |
+ |
// its directory's time says nothing (oxygen#75). |
|
716 |
+ |
Ok(md) if md.is_dir() => { |
|
717 |
+ |
path.join(OXY_DIR).exists() || path.join(".git").exists() |
|
718 |
+ |
} |
| 714 |
719 |
|
_ => false, |
| 715 |
720 |
|
}; |
| 716 |
721 |
|
!same |
|
|
… |
117 unchanged lines |
| 834 |
839 |
|
self.disk_differs_listing(manifest, keep) |
| 835 |
840 |
|
} |
| 836 |
841 |
|
|
|
842 |
+ |
/// The manifest's entries that are subrepos (nested repositories it |
|
843 |
+ |
/// tracks): a directory, compared by its pin, not its stat. |
|
844 |
+ |
/// Known from the walk's nested repositories, and from `.oxymodules`: |
|
845 |
+ |
/// a commit's manifest does not list the nested repositories. |
|
846 |
+ |
fn subrepo_entries<'m>(&self, manifest: &'m Manifest) -> Vec<(&'m String, &'m ManifestEntry)> { |
|
847 |
+ |
let modules = self.modules_on_disk(); |
|
848 |
+ |
let mut out: Vec<(&'m String, &'m ManifestEntry)> = manifest |
|
849 |
+ |
.nested |
|
850 |
+ |
.iter() |
|
851 |
+ |
.map(String::as_str) |
|
852 |
+ |
.chain(modules.entries.keys().map(String::as_str)) |
|
853 |
+ |
.filter_map(|n| manifest.entries.get_key_value(n)) |
|
854 |
+ |
.collect(); |
|
855 |
+ |
out.sort_by(|a, b| a.0.cmp(b.0)); |
|
856 |
+ |
out.dedup_by(|a, b| a.0 == b.0); |
|
857 |
+ |
out |
|
858 |
+ |
} |
|
859 |
+ |
|
|
860 |
+ |
/// Whether a subrepo is at another pin than the manifest recorded: its |
|
861 |
+ |
/// pin now (the nested repository's base, or Git's `HEAD`), encoded as |
|
862 |
+ |
/// a snapshot records it, against the recorded hash. Absent, it keeps |
|
863 |
+ |
/// its recorded pin, as a snapshot does; unreadable, it counts as moved. |
|
864 |
+ |
fn subrepo_moved(&self, rel: &str, me: &ManifestEntry) -> bool { |
|
865 |
+ |
let dir = self.root.join(rel); |
|
866 |
+ |
if !(dir.join(OXY_DIR).exists() || dir.join(".git").exists()) { |
|
867 |
+ |
return false; |
|
868 |
+ |
} |
|
869 |
+ |
let modules = self.modules_on_disk(); |
|
870 |
+ |
let hint = modules.entries.get(rel).and_then(|m| m.bookmark.clone()); |
|
871 |
+ |
match super::subrepos::read_pin(&dir, hint.as_deref()) { |
|
872 |
+ |
// A commit's manifest names the pin by its blob (its hash is of |
|
873 |
+ |
// no bytes); a snapshot's by the bytes' hash. |
|
874 |
+ |
Ok(pin) => match me.blob { |
|
875 |
+ |
Some(b) => Repo::blob_ref(&pin.encode()) != b, |
|
876 |
+ |
None => *blake3::hash(&pin.encode()).as_bytes() != me.hash, |
|
877 |
+ |
}, |
|
878 |
+ |
Err(_) => true, |
|
879 |
+ |
} |
|
880 |
+ |
} |
|
881 |
+ |
|
| 837 |
882 |
|
/// [`Repo::disk_differs`] without listing the tree, when the manifest |
| 838 |
883 |
|
/// knows its directories: a directory's time moves when an entry is |
| 839 |
884 |
|
/// added, removed or renamed in it, so with every directory's time as |
|
|
… |
28 unchanged lines |
| 868 |
913 |
|
if !dirs_same { |
| 869 |
914 |
|
return None; |
| 870 |
915 |
|
} |
| 871 |
|
- |
let rels: Vec<&String> = manifest.entries.keys().collect(); |
|
916 |
+ |
// Subrepos are directories, compared by their pins below. |
|
917 |
+ |
let subrepos = self.subrepo_entries(manifest); |
|
918 |
+ |
let is_subrepo = |rel: &str| subrepos.iter().any(|(r, _)| r.as_str() == rel); |
|
919 |
+ |
let tracked: Vec<(&String, &ManifestEntry)> = manifest |
|
920 |
+ |
.entries |
|
921 |
+ |
.iter() |
|
922 |
+ |
.filter(|(rel, _)| !is_subrepo(rel)) |
|
923 |
+ |
.collect(); |
|
924 |
+ |
let rels: Vec<&String> = tracked.iter().map(|(r, _)| *r).collect(); |
| 872 |
925 |
|
let now = stat_tracked(root, &rels); |
| 873 |
926 |
|
let differs_from = |me: &ManifestEntry, rel: &str, now: &Tracked| match now { |
| 874 |
927 |
|
Tracked::Link(t) => *blake3::hash(t).as_bytes() != me.hash, |
|
|
… |
11 unchanged lines |
| 886 |
939 |
|
return None; |
| 887 |
940 |
|
} |
| 888 |
941 |
|
// The entries in the order `rels` was taken in: no lookup by path. |
| 889 |
|
- |
let entries: Vec<&ManifestEntry> = manifest.entries.values().collect(); |
|
942 |
+ |
let entries: Vec<&ManifestEntry> = tracked.iter().map(|(_, me)| *me).collect(); |
| 890 |
943 |
|
let differs = rels |
| 891 |
944 |
|
.par_iter() |
| 892 |
945 |
|
.zip(entries.par_iter()) |
| 893 |
946 |
|
.zip(now.par_iter()) |
| 894 |
|
- |
.any(|((rel, me), n)| differs_from(me, rel, n.as_ref().unwrap())); |
|
947 |
+ |
.any(|((rel, me), n)| differs_from(me, rel, n.as_ref().unwrap())) |
|
948 |
+ |
|| subrepos.iter().any(|(rel, me)| self.subrepo_moved(rel, me)); |
| 895 |
949 |
|
if keep && (differs || racy) { |
| 896 |
950 |
|
let mut listing = super::walk::Listing { |
| 897 |
951 |
|
dirs: manifest.dirs.clone(), |
|
|
… |
88 unchanged lines |
| 986 |
1040 |
|
}) |
| 987 |
1041 |
|
.collect(); |
| 988 |
1042 |
|
let root = &self.root; |
|
1043 |
+ |
// Subrepos are not listed as files: compared by their pins. |
|
1044 |
+ |
let subrepos = self.subrepo_entries(manifest); |
|
1045 |
+ |
if subrepos.iter().any(|(rel, me)| self.subrepo_moved(rel, me)) { |
|
1046 |
+ |
return Ok(true); |
|
1047 |
+ |
} |
|
1048 |
+ |
let skipped: Vec<_> = skipped |
|
1049 |
+ |
.into_iter() |
|
1050 |
+ |
.filter(|(rel, _)| !subrepos.iter().any(|(r, _)| r == rel)) |
|
1051 |
+ |
.collect(); |
| 989 |
1052 |
|
Ok(skipped.par_iter().any(|(rel, me)| { |
| 990 |
1053 |
|
let path = root.join(rel.as_str()); |
| 991 |
1054 |
|
let Ok(m) = fs::symlink_metadata(&path) else { |
|
|
… |
114 unchanged lines |