A src/git/blame.rs +30 -0
@@ 0,0 1,30 @@
+use crate::config::CONFIG;
+use std::path::Path;
+use crate::git::commits::{Commits, get_commits};
+use serde_derive::Serialize;
+use std::ops::Range;
+
+pub fn blame<'a>(repo: String, branch: String, path: String) -> Option<Vec<Blame>> {
+ if branch.contains(":") {
+ return None;
+ }
+ let mut repo_path = CONFIG.git_location.clone();
+ repo_path.push(format!("{}.git", repo));
+ let repo_clone = repo.clone();
+ let repo = git2::Repository::open(repo_path).ok()?;
+ let blame = repo.blame_file(Path::new(&path), None).ok()?;
+ let mut blames = Vec::new();
+ for i in blame.iter() {
+ blames.push( Blame {
+ commit: (*get_commits(repo_clone.clone(), 1, Some(i.final_commit_id().to_string()), None)?.first()?).clone(),
+ lines: (0..i.lines_in_hunk()-1).collect()
+ });
+ }
+ Some(blames)
+}
+
+#[derive(Serialize, Clone)]
+pub struct Blame {
+ commit: Commits,
+ lines: Vec<usize>
+}
M src/git/file.rs => src/git/file.rs +0 -18
@@ 9,15 9,6 @@ pub fn files(repo: String, branch: String, path: String) -> Option<Vec<File>> {
if branch.contains(":") {
return None;
}
- let mut location = CONFIG.git_location.clone();
- location.push(path.clone());
- if location
- .components()
- .into_iter()
- .any(|x| x == Component::ParentDir)
- {
- return None;
- }
let mut repo_path = CONFIG.git_location.clone();
repo_path.push(format!("{}.git", repo));
let repo = git2::Repository::open(repo_path).ok()?;
@@ 56,15 47,6 @@ pub fn file(repo: String, branch: String, path: String) -> Option<(File, Option<
if branch.contains(":") {
return None;
}
- let mut location = CONFIG.git_location.clone();
- location.push(path.clone());
- if location
- .components()
- .into_iter()
- .any(|x| x == Component::ParentDir)
- {
- return None;
- }
let mut repo_path = CONFIG.git_location.clone();
repo_path.push(format!("{}.git", repo));
let repo = git2::Repository::open(repo_path).ok()?;
M src/git/mod.rs => src/git/mod.rs +1 -0
@@ 4,3 4,4 @@ pub mod file;
pub mod main_branch;
pub mod repos;
pub mod tag;
+pub mod blame;
M src/main.rs => src/main.rs +2 -0
@@ 15,6 15,7 @@ use crate::repository::log;
use crate::repository::raw;
use crate::repository::summary;
use crate::repository::tree;
+use crate::repository::blame;
use crate::utils::own_pathbuf::PathBufWithDotfiles;
use rocket_dyn_templates::Template;
@@ 34,6 35,7 @@ fn rocket() -> _ {
raw::raw,
log::log_main,
log::log,
+ blame::blames,
],
)
.attach(Template::fairing())
A src/repository/blame.rs +69 -0
@@ 0,0 1,69 @@
+use crate::config::CONFIG;
+use crate::git::file::files;
+use rocket_dyn_templates::{context, Template};
+use crate::git::commits::get_commits;
+use crate::utils::repo_config::repo_config;
+use syntect::html::highlighted_html_for_string;
+use std::path::Path;
+use syntect::highlighting::ThemeSet;
+use syntect::parsing::SyntaxSet;
+use crate::git::file::file;
+use crate::git::blame::blame;
+use crate::PathBufWithDotfiles;
+use std::ffi::OsStr;
+
+#[get("/<repo>/blame/<branch>/<location..>", rank = 2)]
+pub fn blames(repo: String, branch: String, location: PathBufWithDotfiles) -> Option<Template> {
+ let blames = blame(repo.clone(), branch.clone(), location.get().display().to_string());
+ let file = file(
+ repo.clone(),
+ branch.clone(),
+ location.get().display().to_string(),
+ )?;
+ let mut content = "".to_string();
+ let mut lines: Vec<usize> = (1..1).collect();
+ if file.1.is_some() {
+ let ps = SyntaxSet::load_defaults_newlines();
+ let ts = ThemeSet::load_defaults();
+ let syntax = match ps.find_syntax_by_extension(
+ Path::new(&file.0.name)
+ .extension()
+ .and_then(OsStr::to_str)
+ .unwrap_or("txt"),
+ ) {
+ Some(x) => x,
+ None => ps.find_syntax_by_extension("txt").unwrap(),
+ };
+ let s = file.1.as_ref().unwrap();
+ lines = (1..s.lines().count() + 1).collect();
+ content =
+ highlighted_html_for_string(s, &ps, syntax, &ts.themes["Solarized (light)"])
+ .ok()?
+ }
+ Some(Template::render(
+ "repository/blame",
+ context! {
+ title: format!("/{} :: {}", location.get().display(), repo.clone()),
+ repo: repo.clone(),
+ config: repo_config(repo.clone()),
+ domain: CONFIG.domain.to_string(),
+ active: "tree",
+ commit: match get_commits(repo.clone(), 1, None, Some(format!("{}", location.get().display()).replace("//", "/"))) {
+ Some(x) => match x.clone().get(0) {
+ Some(x) => Some(x.clone()),
+ None => None
+ }
+ None => None
+ },
+ files: file,
+ content,
+ lines,
+ fluid: "true",
+ blame: blames,
+ branch: branch.clone(),
+ current_dir_file: format!("/{}/", location.get().display()).replace("//", "/"),
+ current_dir: format!("/{}", location.get().display()),
+ payment: CONFIG.payment_link.clone()
+ },
+ ))
+}
M src/repository/mod.rs => src/repository/mod.rs +1 -0
@@ 2,3 2,4 @@ pub mod log;
pub mod raw;
pub mod summary;
pub mod tree;
+pub mod blame;
A templates/repository/blame.html.hbs +93 -0
@@ 0,0 1,93 @@
+{{#*inline "page"}}
+{{> navbar}}
+<div class="header-extension" style="margin-bottom: 0;">
+ <div class="blob container-fluid">
+ <span>
+ {{repo}}{{current_dir}}
+ <span class="text-muted" style="margin-left: 1rem">
+ <span title="{{files.0.properties_int}}">
+ {{files.0.properties}}
+ </span>
+ <span title="{{files.0.size}}">
+ {{files.0.size}}
+ </span>
+ </span>
+ <div class="blob-nav" style="margin-left: 1rem">
+ <ul class="nav nav-tabs">
+ <li class="nav-item">
+ <a class="nav-link" href="/{{repo}}/tree/{{branch}}{{current_dir}}">View</a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="/{{repo}}/log/{{branch}}{{current_dir}}">Log</a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link active" href="/{{repo}}/blame/{{branch}}{{current_dir}}">Blame</a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="/{{repo}}/blob/{{branch}}{{current_dir}}">View Raw</a>
+ </li>
+ </ul>
+ </div>
+ </span>
+ {{#if commit}}
+ <div class="commit">
+ <a href="/{{repo}}/commit/{{commit.commit_hash}}">{{commit.commit_hash_short}}</a> —
+ {{commit.commitie}}
+ {{commit.commit}}
+ <span class="text-muted">
+ <span title="{{commit.time_utc}}">{{commit.time_relitive}}</span>
+ </span>
+ </div>
+ <div class="clearfix">
+ </div>
+ {{/if}}
+ </div>
+</div>
+<div class="container-fluid code-viewport">
+ <div class="row" style="margin-right: 0;">
+ <div class="col-md-12 code-view">
+ <pre class="ruler"><span>
+ </span></pre>
+ <pre class="blame-user">
+{{#each blame}}
+<div class="hunk"><a href="/{{../repo}}/commit/{{this.commit.commit_hash}}">{{this.commit.commit_hash_short}}</a> {{this.commit.commitie}}</div>{{#each this.lines}}
+{{/each}}
+{{/each}}
+</pre>
+ <pre class="blame-time">
+{{#each blame}}
+<div class="hunk"><a href="/{{../repo}}/blame/{{this.commit.commit_hash}}{{../current_dir}}"><span title="{{this.commit.time_utc}}">{{this.commit.time_relitive}}</span></a></div>{{#each this.lines}}
+{{/each}}
+{{/each}}
+</pre>
+ <pre class="lines">
+{{#each lines}}
+<a href="#L{{this}}" id="L{{this}}">{{this}}</a>
+{{/each}}
+</pre>
+ <div class="highlight">
+ {{#if files.1}}
+ {{{content}}}
+ {{else}}
+ <div style="padding: 1rem">
+ <p>
+ {{files.0.size}} Binary file not shown.
+ </p>
+ <p>
+ <a class="btn btn-primary" href="/{{repo}}/blob/{{branch}}{{current_dir}}" rel="nofollow">
+ Download
+ <span aria-hidden="true" class="icon icon-caret-right">
+ <svg viewBox="0 0 192 512" xmlns="http://www.w3.org/2000/svg">
+ <path d="M0 384.662V127.338c0-17.818 21.543-26.741 34.142-14.142l128.662 128.662c7.81 7.81 7.81 20.474 0 28.284L34.142 398.804C21.543 411.404 0 402.48 0 384.662z"></path>
+ </svg>
+ </span>
+ </a>
+ </p>
+ </div>
+ {{/if}}
+ </div>
+ </div>
+ </div>
+</div>
+{{/inline}}
+{{> layout}}
M templates/repository/file.html.hbs => templates/repository/file.html.hbs +1 -1
@@ 21,7 21,7 @@
<a class="nav-link" href="/{{repo}}/log/{{branch}}{{current_dir}}">Log</a>
</li>
<li class="nav-item">
- <a class="nav-link" href="/{{repo}}/raw/{{branch}}{{current_dir}}">Blame</a>
+ <a class="nav-link" href="/{{repo}}/blame/{{branch}}{{current_dir}}">Blame</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/{{repo}}/blob/{{branch}}{{current_dir}}">View Raw</a>