use crate::permissions::Perm;
use crate::Config;
use axum::{
debug_handler,
extract::{Path, State},
response::{IntoResponse, Response},
Json,
};
use axum_extra::extract::SignedCookieJar;
use diesel_async::AsyncPgConnection;
use libpijul::Hash;
use libpijul::{changestore::ChangeStore, Base32, EdgeFlags, TxnT};
use serde_derive::*;
use std::collections::HashMap;
use tracing::*;
pub struct Record<'a, L> {
pub rec: &'a libpijul::change::Hunk<Option<Hash>, L>,
pub hashes: &'a mut HashMap<Hash, usize>,
pub hash: &'a libpijul::Hash,
pub changes: &'a crate::repository::changestore::FileSystem,
pub hunk: usize,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct TreePath {
hash: String,
}
#[debug_handler]
pub async fn change(
State(config): State<Config>,
jar: SignedCookieJar,
repo: crate::repository::RepoPath,
token: axum_csrf::CsrfToken,
Path(tree): Path<TreePath>,
) -> Result<Response, crate::Error> {
let (uid, login) = crate::get_user_login_(&jar, &config).await?;
let mut db = config.db.get().await.unwrap();
let (id, _) =
crate::repository::repository_id(&mut db, &repo.owner, &repo.repo, uid, Perm::READ).await?;
let repo_locks = config.repo_locks.clone();
let repo_ = repo_locks.get(&id).await.unwrap();
let pristine = repo_.pristine.read().await;
let txn = pristine.txn_begin().unwrap();
let r = render(
&mut db,
&txn,
&token,
&repo_.changes,
repo.owner,
repo.repo,
libpijul::Hash::from_base32(tree.hash.as_bytes()).unwrap(),
login,
)
.await?;
debug!("r = {:#?}", r);
Ok((token, Json(r)).into_response())
}
async fn render<T: TxnT>(
db: &mut AsyncPgConnection,
txn: &T,
token: &axum_csrf::CsrfToken,
changes: &crate::repository::changestore::FileSystem,
owner: String,
repo: String,
hash: libpijul::Hash,
login: Option<String>,
) -> Result<Patch, crate::Error> {
use libpijul::changestore::ChangeStore;
let mut header = changes.get_header(&hash).unwrap();
let hunks = changes.get_changes(&hash).unwrap();
let dependencies = changes.get_dependencies(&hash).unwrap();
let extra_known = changes.get_extra_known(&hash).unwrap();
let mut deps = Vec::new();
let mut hashes = HashMap::new();
hashes.insert(hash, 0);
hashes.insert(libpijul::Hash::None, 1);
for h in dependencies.iter() {
hashes.insert(*h, deps.len() + 2);
deps.push(h.to_base32())
}
use std::collections::hash_map::Entry;
let mut known = Vec::new();
for hash in extra_known.iter() {
if let Entry::Vacant(e) = hashes.entry(*hash) {
e.insert(deps.len() + 2);
known.push(hash.to_base32())
}
}
let mut body = Vec::with_capacity(hunks.len());
for (hunk, c) in hunks.iter().enumerate() {
body.push(
(Record {
rec: c,
hashes: &mut hashes,
hash: &hash,
changes: &changes,
hunk,
})
.render(txn),
);
}
let mut hashes_: Vec<_> = hashes.into_iter().collect();
hashes_.sort_by(|a, b| a.1.cmp(&b.1));
let hashes: Vec<_> = hashes_.into_iter().map(|x| x.0.to_base32()).collect();
Ok(Patch {
authors: super::get_authors(db, &mut header.authors).await.unwrap(),
header,
hash: hash.to_base32(),
owner,
repo,
token: token.authenticity_token()?,
deps: Deps {
hashes,
deps,
known,
},
hunks: body,
login,
})
}
#[derive(Debug, Serialize, Clone)]
pub struct Metadata {
basename: String,
metadata: libpijul::pristine::InodeMetadata,
}
impl<'a> From<libpijul::changestore::FileMetadata<'a>> for Metadata {
fn from(f: libpijul::changestore::FileMetadata<'a>) -> Metadata {
Metadata {
basename: f.basename.to_string(),
metadata: f.metadata,
}
}
}
#[derive(Debug, Serialize, Clone)]
pub enum Contents {
Add(String),
Del(String),
}
type EdgeMap = libpijul::change::EdgeMap<usize>;
type NewVertex = libpijul::change::NewVertex<usize>;
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
struct Pos {
hunk: usize,
sub: usize,
l1: usize,
}
pub type Position = libpijul::pristine::Position<usize>;
#[derive(Debug, Serialize, Clone)]
pub struct Inode(Position);
pub type Atom = libpijul::change::Atom<usize>;
#[derive(Debug, Serialize, Clone)]
pub enum Hunk {
FileAdd {
meta: Metadata,
context: Vec<Position>,
contents: Vec<Contents>,
},
FileMove {
meta: Metadata,
old: Vec<Metadata>,
up: Vec<Position>,
down: Vec<Position>,
show_perms: bool,
},
FileDel {
deleted_names: Vec<Metadata>,
del: Atom,
content_edges: Option<EdgeMap>,
content: Vec<Contents>,
},
FileUndel {
undel_names: Vec<Metadata>,
undel: Atom,
content_edges: Option<EdgeMap>,
content: Vec<Contents>,
},
SolveNameConflict {
old: Vec<Metadata>,
name: Atom,
},
UnsolveNameConflict {
old: Vec<Metadata>,
name: Atom,
},
Edit {
inode: Inode,
path: String,
line: usize,
atom: Atom,
contents: Vec<Contents>,
},
Replacement {
inode: Inode,
path: String,
line: usize,
ins: NewVertex,
del: EdgeMap,
ins_contents: Vec<Contents>,
del_contents: Vec<Contents>,
},
SolveOrderConflict {
inode: Inode,
path: String,
line: usize,
atom: Atom,
contents: Vec<Contents>,
},
UnsolveOrderConflict {
inode: Inode,
path: String,
line: usize,
atom: Atom,
contents: Vec<Contents>,
},
ResurrectZombie {
inode: Inode,
path: String,
line: usize,
atom: Atom,
contents: Vec<Contents>,
},
AddRoot {
inode: Inode,
atom: Atom,
name: Atom,
},
DelRoot {
inode: Inode,
atom: Atom,
name: Atom,
},
}
impl<'a> Record<'a, libpijul::change::Local> {
pub fn render<T: TxnT>(&'a mut self, txn: &T) -> Hunk {
match self.rec {
libpijul::change::Hunk::FileAdd {
add_name: libpijul::change::Atom::NewVertex(ref n),
ref contents,
..
} => self.render_file_add(txn, n, contents),
libpijul::change::Hunk::FileAdd { .. } => unreachable!(),
libpijul::change::Hunk::FileMove {
add: libpijul::change::Atom::NewVertex(ref add),
del,
..
} => self.render_file_move(txn, add, &del),
libpijul::change::Hunk::FileMove { .. } => unreachable!(),
libpijul::change::Hunk::FileDel { del, contents, .. } => {
self.render_file_del(txn, del, contents)
}
libpijul::change::Hunk::FileUndel {
undel, contents, ..
} => self.render_file_undel(txn, undel, contents),
libpijul::change::Hunk::SolveNameConflict { name, .. } => {
self.render_solve_name_conflict(txn, name)
}
libpijul::change::Hunk::UnsolveNameConflict { name, .. } => {
self.render_unsolve_name_conflict(txn, name)
}
libpijul::change::Hunk::Edit { change, local, .. } => {
self.render_edit(txn, change, local)
}
libpijul::change::Hunk::Replacement {
change: libpijul::change::Atom::EdgeMap(ref change),
replacement: libpijul::change::Atom::NewVertex(ref replacement),
local,
..
} => self.render_replacement(txn, change, replacement, local),
libpijul::change::Hunk::Replacement { .. } => unreachable!(),
libpijul::change::Hunk::SolveOrderConflict { change, local } => {
self.render_solve_order_conflict(txn, change, local)
}
libpijul::change::Hunk::UnsolveOrderConflict { change, local } => {
self.render_unsolve_order_conflict(txn, change, local)
}
libpijul::change::Hunk::ResurrectZombies { change, local, .. } => {
self.render_resurrect_zombies(txn, change, local)
}
libpijul::change::Hunk::AddRoot { inode, name } => {
self.render_add_root(txn, inode, name)
}
libpijul::change::Hunk::DelRoot { inode, name } => {
self.render_del_root(txn, inode, name)
}
}
}
}
impl<'a, L> Record<'a, L> {
fn inode<T: TxnT>(&mut self, txn: &T, id: &libpijul::change::Atom<Option<Hash>>) -> Inode {
Inode(position(txn, self.hashes, &id.inode()))
}
fn inode_edgemap<T: TxnT>(
&mut self,
txn: &T,
id: &libpijul::change::EdgeMap<Option<Hash>>,
) -> Inode {
Inode(position(txn, self.hashes, &id.inode))
}
fn hash(&mut self, hash: &Option<Hash>) -> usize {
if let Some(h) = hash {
let len = self.hashes.len();
*self.hashes.entry(h.clone()).or_insert(len + 1)
} else {
0
}
}
fn map_pos(
&mut self,
pos: &libpijul::pristine::Position<Option<Hash>>,
) -> libpijul::pristine::Position<usize> {
libpijul::pristine::Position {
change: self.hash(&pos.change),
pos: pos.pos,
}
}
fn map_vertex(&mut self, pos: &libpijul::Vertex<Option<Hash>>) -> libpijul::Vertex<usize> {
libpijul::Vertex {
change: self.hash(&pos.change),
start: pos.start,
end: pos.end,
}
}
fn atom(
&mut self,
atom: &libpijul::change::Atom<Option<Hash>>,
) -> libpijul::change::Atom<usize> {
use libpijul::change::Atom;
match atom {
Atom::NewVertex(v) => Atom::NewVertex(self.new_vertex(v)),
Atom::EdgeMap(e) => Atom::EdgeMap(self.edge_map(e)),
}
}
fn edge_map(
&mut self,
e: &libpijul::change::EdgeMap<Option<Hash>>,
) -> libpijul::change::EdgeMap<usize> {
libpijul::change::EdgeMap {
edges: e
.edges
.iter()
.map(|e| libpijul::change::NewEdge {
flag: e.flag,
previous: e.previous,
from: self.map_pos(&e.from),
to: self.map_vertex(&e.to),
introduced_by: self.hash(&e.introduced_by),
})
.collect(),
inode: self.map_pos(&e.inode),
}
}
fn new_vertex(
&mut self,
v: &libpijul::change::NewVertex<Option<Hash>>,
) -> libpijul::change::NewVertex<usize> {
libpijul::change::NewVertex {
up_context: v.up_context.iter().map(|x| self.map_pos(x)).collect(),
down_context: v.down_context.iter().map(|x| self.map_pos(x)).collect(),
start: v.start,
end: v.end,
flag: v.flag,
inode: self.map_pos(&v.inode),
}
}
}
#[derive(Debug, Serialize, Clone)]
pub struct Patch {
header: libpijul::change::ChangeHeader,
hash: String,
authors: Vec<super::Author>,
deps: Deps,
hunks: Vec<Hunk>,
owner: String,
repo: String,
token: String,
login: Option<String>,
}
#[derive(Debug, Serialize, Clone)]
struct Deps {
hashes: Vec<String>,
deps: Vec<String>,
known: Vec<String>,
}
impl<'a> Record<'a, libpijul::change::Local> {
fn render_contents<T: TxnT>(
&self,
txn: &T,
v: &libpijul::change::Atom<Option<Hash>>,
lang: Option<&str>,
pos: Pos,
) -> Vec<Contents> {
match v {
libpijul::change::Atom::NewVertex(ref n) => {
self.render_newvertex_contents(txn, n, lang, pos)
}
libpijul::change::Atom::EdgeMap(ref e) => {
self.render_edgemap_contents(txn, e, lang, pos)
}
}
}
fn render_newvertex_contents<H, T: TxnT>(
&self,
_txn: &T,
v: &libpijul::change::NewVertex<H>,
_lang: Option<&str>,
_pos: Pos,
) -> Vec<Contents> {
let v_start: u64 = v.start.0.into();
let v_end: u64 = v.end.0.into();
let mut b = vec![0; v_end as usize - v_start as usize];
self.changes
.get_contents_ext(
libpijul::pristine::Vertex {
change: Some(*self.hash),
start: v.start,
end: v.end,
},
&mut b,
)
.unwrap();
let mut v = Vec::new();
if let Ok(l) = std::str::from_utf8(&b) {
for l in l.lines() {
v.push(Contents::Add(l.to_string()))
}
}
v
}
fn render_edgemap_contents<T: TxnT>(
&self,
_txn: &T,
contents: &libpijul::change::EdgeMap<Option<Hash>>,
_lang: Option<&str>,
_pos: Pos,
) -> Vec<Contents> {
let mut buf = Vec::new();
let mut current_edge = None;
let mut v = Vec::new();
for e in contents.edges.iter() {
if Some(e.to) == current_edge {
continue;
}
current_edge = Some(e.to);
buf.resize(e.to.end - e.to.start, 0);
if self.changes.get_contents_ext(e.to, &mut buf).is_err() {
continue;
}
let buf = if let Ok(buf) = std::str::from_utf8(&buf) {
buf
} else {
continue;
};
v.push(if e.flag.contains(EdgeFlags::DELETED) {
Contents::Del(buf.to_string())
} else {
Contents::Add(buf.to_string())
});
}
v
}
fn render_file_add<T: TxnT>(
&mut self,
txn: &T,
n: &libpijul::change::NewVertex<Option<libpijul::pristine::Hash>>,
contents: &Option<libpijul::change::Atom<Option<libpijul::pristine::Hash>>>,
) -> Hunk {
let n_start: u64 = n.start.0.into();
let n_end: u64 = n.end.0.into();
let mut buf = vec![0; (n_end - n_start) as usize];
self.changes
.get_contents_ext(
libpijul::pristine::Vertex {
change: Some(*self.hash),
start: n.start,
end: n.end,
},
&mut buf,
)
.unwrap();
let meta: Metadata = libpijul::changestore::FileMetadata::read(&buf).into();
let context = render_context(txn, self.hashes, &n.up_context[..]);
let contents = if let Some(ref contents) = contents {
let lang = if let Some(p) = meta.basename.rfind('.') {
Some(meta.basename.split_at(p + 1).1)
} else {
None
};
let pos = Pos {
hunk: self.hunk,
sub: 1,
l1: 0,
};
self.render_contents(txn, contents, lang, pos)
} else {
Vec::new()
};
Hunk::FileAdd {
meta,
context,
contents,
}
}
fn render_file_move<T: TxnT>(
&'a mut self,
txn: &T,
add: &'a libpijul::change::NewVertex<Option<libpijul::pristine::Hash>>,
del: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
) -> Hunk {
let add_start: u64 = add.start.0.into();
let add_end: u64 = add.end.0.into();
let mut buf = vec![0; (add_end - add_start) as usize];
self.changes
.get_contents_ext(
libpijul::pristine::Vertex {
change: Some(*self.hash),
start: add.start,
end: add.end,
},
&mut buf,
)
.unwrap();
let meta: Metadata = libpijul::changestore::FileMetadata::read(&buf).into();
let (old, show_perms) = if let libpijul::change::Atom::EdgeMap(ref del) = del {
self.render_deleted_names(txn, del, Some(meta.metadata))
} else {
(Vec::new(), true)
};
Hunk::FileMove {
meta,
old,
show_perms,
up: render_context(txn, self.hashes, &add.up_context[..]),
down: render_context(txn, self.hashes, &add.down_context[..]),
}
}
fn render_file_del<T: TxnT>(
&'a mut self,
txn: &T,
del: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
contents: &'a Option<libpijul::change::Atom<Option<libpijul::pristine::Hash>>>,
) -> Hunk {
let (deleted_names, _) = if let libpijul::change::Atom::EdgeMap(ref del) = del {
self.render_deleted_names(txn, del, None)
} else {
(Vec::new(), true)
};
Hunk::FileDel {
deleted_names,
del: self.atom(del),
content_edges: if let Some(libpijul::change::Atom::EdgeMap(ref contents)) = contents {
Some(self.edge_map(contents))
} else {
None
},
content: if let Some(libpijul::change::Atom::EdgeMap(ref contents)) = contents {
self.render_edgemap_contents(
txn,
contents,
None,
Pos {
hunk: self.hunk,
sub: 1,
l1: 0,
},
)
} else {
Vec::new()
},
}
}
fn render_file_undel<T: TxnT>(
&'a mut self,
txn: &T,
undel: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
contents: &'a Option<libpijul::change::Atom<Option<libpijul::pristine::Hash>>>,
) -> Hunk {
let (undel_names, _) = if let libpijul::change::Atom::EdgeMap(ref undel) = undel {
self.render_deleted_names(txn, undel, None)
} else {
(Vec::new(), true)
};
Hunk::FileUndel {
undel_names,
undel: self.atom(undel),
content_edges: if let Some(libpijul::change::Atom::EdgeMap(ref contents)) = contents {
Some(self.edge_map(contents))
} else {
None
},
content: if let Some(libpijul::change::Atom::EdgeMap(ref contents)) = contents {
self.render_edgemap_contents(
txn,
contents,
None,
Pos {
hunk: self.hunk,
sub: 0,
l1: 0,
},
)
} else {
Vec::new()
},
}
}
fn render_solve_name_conflict<T: TxnT>(
&'a mut self,
txn: &T,
name: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
) -> Hunk {
let (old, _) = if let libpijul::change::Atom::EdgeMap(ref name) = name {
self.render_deleted_names(txn, name, None)
} else {
(Vec::new(), true)
};
Hunk::SolveNameConflict {
old,
name: self.atom(name),
}
}
fn render_unsolve_name_conflict<T: TxnT>(
&'a mut self,
txn: &T,
name: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
) -> Hunk {
let (old, _) = if let libpijul::change::Atom::EdgeMap(ref name) = name {
self.render_deleted_names(txn, name, None)
} else {
(Vec::new(), true)
};
Hunk::UnsolveNameConflict {
old,
name: self.atom(name),
}
}
fn render_edit<T: TxnT>(
&'a mut self,
txn: &T,
change: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
local: &'a libpijul::change::Local,
) -> Hunk {
Hunk::Edit {
inode: self.inode(txn, change),
path: local.path.to_string(),
line: local.line,
atom: self.atom(&change),
contents: self.render_contents(
txn,
change,
None,
Pos {
hunk: self.hunk,
sub: 1,
l1: 0,
},
),
}
}
fn render_replacement<T: TxnT>(
&'a mut self,
txn: &T,
change: &'a libpijul::change::EdgeMap<Option<libpijul::pristine::Hash>>,
replacement: &'a libpijul::change::NewVertex<Option<libpijul::pristine::Hash>>,
local: &'a libpijul::change::Local,
) -> Hunk {
Hunk::Replacement {
inode: self.inode_edgemap(txn, change),
path: local.path.to_string(),
line: local.line,
del: self.edge_map(change),
del_contents: self.render_edgemap_contents(
txn,
change,
None,
Pos {
hunk: self.hunk,
sub: 0,
l1: 0,
},
),
ins: self.new_vertex(replacement),
ins_contents: self.render_newvertex_contents(
txn,
replacement,
None,
Pos {
hunk: self.hunk,
sub: 1,
l1: 0,
},
),
}
}
fn render_solve_order_conflict<T: TxnT>(
&'a mut self,
txn: &T,
change: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
local: &'a libpijul::change::Local,
) -> Hunk {
Hunk::SolveOrderConflict {
inode: self.inode(txn, change),
path: local.path.to_string(),
line: local.line,
atom: self.atom(change),
contents: self.render_contents(
txn,
change,
None,
Pos {
hunk: self.hunk,
sub: 0,
l1: 0,
},
),
}
}
fn render_unsolve_order_conflict<T: TxnT>(
&'a mut self,
txn: &T,
change: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
local: &'a libpijul::change::Local,
) -> Hunk {
Hunk::UnsolveOrderConflict {
inode: self.inode(txn, change),
path: local.path.to_string(),
line: local.line,
atom: self.atom(change),
contents: self.render_contents(
txn,
change,
None,
Pos {
hunk: self.hunk,
sub: 0,
l1: 0,
},
),
}
}
fn render_resurrect_zombies<T: TxnT>(
&'a mut self,
txn: &T,
change: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
local: &'a libpijul::change::Local,
) -> Hunk {
Hunk::ResurrectZombie {
inode: self.inode(txn, change),
path: local.path.to_string(),
line: local.line,
atom: self.atom(change),
contents: self.render_contents(
txn,
change,
None,
Pos {
hunk: self.hunk,
sub: 0,
l1: 0,
},
),
}
}
fn render_add_root<T: TxnT>(
&'a mut self,
txn: &T,
change: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
name: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
) -> Hunk {
Hunk::AddRoot {
inode: self.inode(txn, change),
atom: self.atom(change),
name: self.atom(name),
}
}
fn render_del_root<T: TxnT>(
&'a mut self,
txn: &T,
change: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
name: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
) -> Hunk {
Hunk::DelRoot {
inode: self.inode(txn, change),
atom: self.atom(change),
name: self.atom(name),
}
}
fn render_deleted_names<T: TxnT>(
&self,
_txn: &T,
del: &libpijul::change::EdgeMap<Option<Hash>>,
perm: Option<libpijul::pristine::InodeMetadata>,
) -> (Vec<Metadata>, bool) {
let mut buf = Vec::new();
let mut show_perms = false;
let mut result = Vec::new();
for d in del.edges.iter() {
buf.resize(d.to.end - d.to.start, 0);
if self.changes.get_contents_ext(d.to, &mut buf).is_err() {
continue;
}
if !buf.is_empty() {
let meta: Metadata = libpijul::changestore::FileMetadata::read(&buf).into();
if let Some(perm) = perm {
if meta.metadata != perm {
show_perms = true;
}
}
result.push(meta);
}
}
(result, show_perms)
}
}
fn position<T: TxnT>(
_txn: &T,
hashes: &mut HashMap<Hash, usize>,
c: &libpijul::pristine::Position<Option<Hash>>,
) -> Position {
let n = if let Some(ref h) = c.change {
let len = hashes.len();
*hashes.entry(h.clone()).or_insert(len + 1)
} else {
0
};
Position {
change: n,
pos: c.pos,
}
}
fn render_context<T: TxnT>(
txn: &T,
hashes: &mut HashMap<Hash, usize>,
context: &[libpijul::pristine::Position<Option<Hash>>],
) -> Vec<Position> {
context.iter().map(|c| position(txn, hashes, c)).collect()
}