|
|
… |
610 unchanged lines |
| 611 |
611 |
|
FileBody::Text(String::from_utf8_lossy(&bytes).into_owned()) |
| 612 |
612 |
|
}; |
| 613 |
613 |
|
let labels = head_labels(repo)?; |
| 614 |
|
- |
let last_change = repo |
| 615 |
|
- |
.log_for_path_in(path, &target.heads) |
| 616 |
|
- |
.ok() |
| 617 |
|
- |
.and_then(|l| l.into_iter().next()) |
| 618 |
|
- |
.map(|e| change_view(repo, &e.hash, &e.change, e.is_head, &labels)); |
|
614 |
+ |
let last_change = last_change_of(repo, &target.heads, path) |
|
615 |
+ |
.and_then(|h| loaded.state.get(&h).map(|meta| change_view(repo, &h, meta, loaded.state.heads().contains(&h), &labels))); |
| 619 |
616 |
|
let name = path.rsplit('/').next().unwrap_or(path).to_string(); |
| 620 |
617 |
|
Ok(FileView { |
| 621 |
618 |
|
target, |
|
|
… |
25 unchanged lines |
| 647 |
644 |
|
}, |
| 648 |
645 |
|
}; |
| 649 |
646 |
|
Ok((path.rsplit('/').next().unwrap_or(path).to_string(), bytes)) |
|
647 |
+ |
} |
|
648 |
+ |
|
|
649 |
+ |
/// Facts about a path in a state never change, so they are cached by the |
|
650 |
+ |
/// state's heads and the path (hub spec §16.3). |
|
651 |
+ |
fn state_path_key(tag: &[u8], heads: &[ChangeHash], path: &str) -> [u8; 32] { |
|
652 |
+ |
let mut sorted: Vec<&ChangeHash> = heads.iter().collect(); |
|
653 |
+ |
sorted.sort(); |
|
654 |
+ |
let joined: Vec<u8> = sorted.iter().flat_map(|h| h.to_hex().into_bytes()).collect(); |
|
655 |
+ |
crate::render::cache::key(&[tag, &joined, path.as_bytes()]) |
| 650 |
656 |
|
} |
| 651 |
657 |
|
|
|
658 |
+ |
struct Last(Option<ChangeHash>); |
|
659 |
+ |
|
|
660 |
+ |
impl crate::render::cache::Weigh for Last { |
|
661 |
+ |
fn weight(&self) -> usize { |
|
662 |
+ |
64 |
|
663 |
+ |
} |
|
664 |
+ |
} |
|
665 |
+ |
|
|
666 |
+ |
/// The newest change touching `path` in the state, from a cache of 4 MiB. |
|
667 |
+ |
pub fn last_change_of(repo: &Repo, heads: &[ChangeHash], path: &str) -> Option<ChangeHash> { |
|
668 |
+ |
static C: std::sync::OnceLock<crate::render::cache::Lru<Last>> = std::sync::OnceLock::new(); |
|
669 |
+ |
let cache = C.get_or_init(|| crate::render::cache::Lru::new(4 << 20)); |
|
670 |
+ |
let key = state_path_key(b"last1", heads, path); |
|
671 |
+ |
cache.get_or(key, || Last(repo.log_for_path_in(path, heads).ok().and_then(|l| l.into_iter().next()).map(|e| e.hash))).0 |
|
672 |
+ |
} |
|
673 |
+ |
|
|
674 |
+ |
struct BlameLines(Vec<BlameLine>); |
|
675 |
+ |
|
|
676 |
+ |
impl crate::render::cache::Weigh for BlameLines { |
|
677 |
+ |
fn weight(&self) -> usize { |
|
678 |
+ |
self.0.iter().map(|l| l.text.len() + l.author.len() + 128).sum::<usize>() + 32 |
|
679 |
+ |
} |
|
680 |
+ |
} |
|
681 |
+ |
|
|
682 |
+ |
/// `Repo::blame_in`, cached by state and path (64 MiB). |
|
683 |
+ |
fn blame_cached(repo: &Repo, heads: &[ChangeHash], path: &str) -> Result<Vec<BlameLine>> { |
|
684 |
+ |
static C: std::sync::OnceLock<crate::render::cache::Lru<BlameLines>> = std::sync::OnceLock::new(); |
|
685 |
+ |
let cache = C.get_or_init(|| crate::render::cache::Lru::new(64 << 20)); |
|
686 |
+ |
let key = state_path_key(b"blame1", heads, path); |
|
687 |
+ |
let mut failure = None; |
|
688 |
+ |
let lines = cache.get_or(key, || match repo.blame_in(path, heads) { |
|
689 |
+ |
Ok(l) => BlameLines(l), |
|
690 |
+ |
Err(e) => { |
|
691 |
+ |
failure = Some(e); |
|
692 |
+ |
BlameLines(Vec::new()) |
|
693 |
+ |
} |
|
694 |
+ |
}); |
|
695 |
+ |
if let Some(e) = failure { |
|
696 |
+ |
return Err(RepoError::Oxy(e)); |
|
697 |
+ |
} |
|
698 |
+ |
Ok(lines.0.clone()) |
|
699 |
+ |
} |
|
700 |
+ |
|
| 652 |
701 |
|
pub fn is_binary(bytes: &[u8]) -> bool { |
| 653 |
702 |
|
let head = &bytes[..bytes.len().min(8192)]; |
| 654 |
703 |
|
head.contains(&0) |
|
|
… |
57 unchanged lines |
| 712 |
761 |
|
pub fn blame(repo: &Repo, cache: &StateCache, target: RefTarget, path: &str, render_limit: u64) -> Result<Blame> { |
| 713 |
762 |
|
let heads = target.heads.clone(); |
| 714 |
763 |
|
let file = file(repo, cache, target, path, render_limit)?; |
| 715 |
|
- |
let lines = repo.blame_in(path, &heads)?; |
|
764 |
+ |
let lines = blame_cached(repo, &heads, path)?; |
| 716 |
765 |
|
let mut before = HashMap::new(); |
| 717 |
766 |
|
for l in &lines { |
| 718 |
767 |
|
before.entry(l.hash).or_insert_with(|| repo.store().get_change(&l.hash).ok().and_then(|c| state_spec(&c.deps))); |
|
|
… |
415 unchanged lines |