use crate::error::Result;
use crate::notes::{
note_object_name, read_blob_bytes, read_notes_snapshot, read_notes_tree, remove_note_entry,
upsert_note_entry, write_notes_commit_idents, NotesRef, NotesSnapshot, NotesTreeEntry,
};
use crate::objects::{ObjectId, ObjectKind};
use crate::repo::Repository;
#[derive(Debug, Clone)]
pub struct NotesWriteOptions {
pub message: String,
pub committer: String,
pub author: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NoteListing {
pub object: ObjectId,
pub note_blob: ObjectId,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Note {
pub object: ObjectId,
pub note_blob: ObjectId,
pub body: Vec<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NoteAddOutcome {
Added,
Replaced,
}
fn listings(entries: Vec<NotesTreeEntry>) -> impl Iterator<Item = NoteListing> {
entries.into_iter().filter_map(|e| {
let object = note_object_name(&e.path)?.parse().ok()?;
Some(NoteListing {
object,
note_blob: e.oid,
})
})
}
#[derive(Debug, Copy, Clone)]
pub struct RepositoryNotes<'repo> {
repo: &'repo Repository,
}
impl<'repo> RepositoryNotes<'repo> {
pub(crate) fn new(repo: &'repo Repository) -> Self {
Self { repo }
}
#[must_use]
pub fn repository(&self) -> &'repo Repository {
self.repo
}
pub fn list(&self, notes_ref: &NotesRef) -> Result<Vec<NoteListing>> {
let entries = read_notes_tree(self.repo, notes_ref.as_str())?;
Ok(listings(entries).collect())
}
pub fn list_with_bodies(&self, notes_ref: &NotesRef) -> Result<Vec<Note>> {
let entries = read_notes_tree(self.repo, notes_ref.as_str())?;
let mut notes = Vec::with_capacity(entries.len());
for listing in listings(entries) {
notes.push(Note {
body: read_blob_bytes(self.repo, &listing.note_blob)?,
object: listing.object,
note_blob: listing.note_blob,
});
}
notes.sort_by_key(|note| note.object);
Ok(notes)
}
pub fn read(&self, notes_ref: &NotesRef, object: &ObjectId) -> Result<Option<Vec<u8>>> {
let hex = object.to_hex();
let entries = read_notes_tree(self.repo, notes_ref.as_str())?;
let Some(entry) = entries
.iter()
.find(|e| note_object_name(&e.path).as_deref() == Some(hex.as_str()))
else {
return Ok(None);
};
read_blob_bytes(self.repo, &entry.oid).map(Some)
}
pub fn add(
&self,
notes_ref: &NotesRef,
object: &ObjectId,
content: &[u8],
options: &NotesWriteOptions,
force: bool,
) -> Result<NoteAddOutcome> {
let hex = object.to_hex();
let NotesSnapshot { tip, mut entries } =
read_notes_snapshot(self.repo, notes_ref.as_str())?;
let exists = entries
.iter()
.any(|e| note_object_name(&e.path).as_deref() == Some(hex.as_str()));
if exists && !force {
return Err(crate::error::Error::NoteAlreadyExists { object: *object });
}
let blob = self.repo.odb().write(ObjectKind::Blob, content)?;
upsert_note_entry(&mut entries, &hex, blob);
write_notes_commit_idents(
self.repo,
notes_ref.as_str(),
tip,
&entries,
&options.message,
options.author.as_deref().unwrap_or(&options.committer),
&options.committer,
)?;
Ok(if exists {
NoteAddOutcome::Replaced
} else {
NoteAddOutcome::Added
})
}
pub fn remove(
&self,
notes_ref: &NotesRef,
object: &ObjectId,
options: &NotesWriteOptions,
) -> Result<()> {
self.remove_many(notes_ref, std::slice::from_ref(object), options)?;
Ok(())
}
pub fn remove_many(
&self,
notes_ref: &NotesRef,
objects: &[ObjectId],
options: &NotesWriteOptions,
) -> Result<Vec<ObjectId>> {
let NotesSnapshot { tip, mut entries } =
read_notes_snapshot(self.repo, notes_ref.as_str())?;
let mut removed = Vec::new();
for object in objects {
if remove_note_entry(&mut entries, &object.to_hex()) {
removed.push(*object);
}
}
if removed.is_empty() {
return Ok(removed);
}
write_notes_commit_idents(
self.repo,
notes_ref.as_str(),
tip,
&entries,
&options.message,
options.author.as_deref().unwrap_or(&options.committer),
&options.committer,
)?;
Ok(removed)
}
}