lib/ginatra.rb
require 'sinatra/base'
require 'sinatra/partials'
require 'rouge'
require 'ginatra/config'
require 'ginatra/errors'
require 'ginatra/logger'
require 'ginatra/helpers'
require 'ginatra/repo'
require 'ginatra/repo_list'
require 'ginatra/repo_stats'
module Ginatra
# The main application class.
# Contains all the core application logic and mounted in +config.ru+ file.
class App < Sinatra::Base
include Logger
helpers Helpers, Sinatra::Partials
configure do
set :host, Ginatra.config.host
set :port, Ginatra.config.port
set :public_folder, "#{settings.root}/../public"
set :views, "#{settings.root}/../views"
enable :dump_errors, :logging, :static
end
configure :development do
# Use better errors in development
require 'better_errors'
use BetterErrors::Middleware
BetterErrors.application_root = settings.root
# Reload modified files in development
require 'sinatra/reloader'
register Sinatra::Reloader
Dir["#{settings.root}/ginatra/*.rb"].each { |file| also_reload file }
end
def cache(obj)
etag obj if settings.production?
end
not_found do
erb :'404', layout: false
end
error Ginatra::RepoNotFound, Ginatra::InvalidRef,
Rugged::OdbError, Rugged::ObjectError, Rugged::InvalidError do
halt 404, erb(:'404', layout: false)
end
error 500 do
erb :'500', layout: false
end
# The root route
get '/' do
@repositories = Ginatra::RepoList.list
erb :index
end
# The atom feed of recent commits to a +repo+.
#
# This only returns commits to the +master+ branch.
#
# @param [String] repo the repository url-sanitised-name
get '/:repo.atom' do
@repo = RepoList.find(params[:repo])
@commits = @repo.commits
if @commits.empty?
return ''
else
cache "#{@commits.first.oid}/atom"
content_type 'application/xml'
erb :atom, layout: false
end
end
# The html page for a +repo+.
#
# Shows the most recent commits in a log format.
#
# @param [String] repo the repository url-sanitised-name
get '/:repo/?' do
@repo = RepoList.find(params[:repo])
if @repo.branches.none?
erb :empty_repo
else
params[:page] = 1
params[:ref] = @repo.branch_exists?('master') ? 'master' : @repo.branches.first.name
@commits = @repo.commits(params[:ref])
cache "#{@commits.first.oid}/log"
@next_commits = !@repo.commits(params[:ref], 10, 10).nil?
erb :log
end
end
# The atom feed of recent commits to a certain branch of a +repo+.
#
# @param [String] repo the repository url-sanitised-name
# @param [String] ref the repository ref
get '/:repo/:ref.atom' do
@repo = RepoList.find(params[:repo])
@commits = @repo.commits(params[:ref])
if @commits.empty?
return ''
else
cache "#{@commits.first.oid}/atom/ref"
content_type 'application/xml'
erb :atom, layout: false
end
end
# The html page for a given +ref+ of a +repo+.
#
# Shows the most recent commits in a log format.
#
# @param [String] repo the repository url-sanitised-name
# @param [String] ref the repository ref
get '/:repo/:ref' do
@repo = RepoList.find(params[:repo])
@commits = @repo.commits(params[:ref])
cache "#{@commits.first.oid}/ref" if @commits.any?
params[:page] = 1
@next_commits = !@repo.commits(params[:ref], 10, 10).nil?
erb :log
end
# The html page for a +repo+ stats.
#
# Shows information about repository branch.
#
# @param [String] repo the repository url-sanitised-name
# @param [String] ref the repository ref
get '/:repo/stats/:ref' do
@repo = RepoList.find(params[:repo])
@stats = RepoStats.new(@repo, params[:ref])
erb :stats
end
# The patch file for a given commit to a +repo+.
#
# @param [String] repo the repository url-sanitised-name
# @param [String] commit the repository commit
get '/:repo/commit/:commit.patch' do
content_type :txt
repo = RepoList.find(params[:repo])
commit = repo.commit(params[:commit])
cache "#{commit.oid}/patch"
diff = commit.parents.first.diff(commit)
diff.patch
end
# The html representation of a commit.
#
# @param [String] repo the repository url-sanitised-name
# @param [String] commit the repository commit
get '/:repo/commit/:commit' do
@repo = RepoList.find(params[:repo])
@commit = @repo.commit(params[:commit])
cache @commit.oid
erb :commit
end
# The html representation of a tag.
#
# @param [String] repo the repository url-sanitised-name
# @param [String] tag the repository tag
get '/:repo/tag/:tag' do
@repo = RepoList.find(params[:repo])
@commit = @repo.commit_by_tag(params[:tag])
cache "#{@commit.oid}/tag"
erb :commit
end
# HTML page for a given tree in a given +repo+
#
# @param [String] repo the repository url-sanitised-name
# @param [String] tree the repository tree
get '/:repo/tree/:tree' do
@repo = RepoList.find(params[:repo])
@tree = @repo.find_tree(params[:tree])
cache @tree.oid
@path = {
blob: "#{params[:repo]}/blob/#{params[:tree]}",
tree: "#{params[:repo]}/tree/#{params[:tree]}"
}
erb :tree, layout: !is_pjax?
end
# HTML page for a given tree in a given +repo+.
#
# This one supports a splat parameter so you can specify a path.
#
# @param [String] repo the repository url-sanitised-name
# @param [String] tree the repository tree
get '/:repo/tree/:tree/*' do
@repo = RepoList.find(params[:repo])
@tree = @repo.find_tree(params[:tree])
cache "#{@tree.oid}/#{params[:splat].first}"
@tree.walk(:postorder) do |root, entry|
@tree = @repo.lookup entry[:oid] if "#{root}#{entry[:name]}" == params[:splat].first
end
@path = {
blob: "#{params[:repo]}/blob/#{params[:tree]}/#{params[:splat].first}",
tree: "#{params[:repo]}/tree/#{params[:tree]}/#{params[:splat].first}"
}
erb :tree, layout: !is_pjax?
end
# HTML page for a given blob in a given +repo+
#
# @param [String] repo the repository url-sanitised-name
# @param [String] tree the repository tree
get '/:repo/blob/:blob' do
@repo = RepoList.find(params[:repo])
@tree = @repo.lookup(params[:tree])
@tree.walk(:postorder) do |root, entry|
@blob = entry if "#{root}#{entry[:name]}" == params[:splat].first
end
cache @blob[:oid]
erb :blob, layout: !is_pjax?
end
# HTML page for a given blob in a given repo.
#
# Uses a splat param to specify a blob path.
#
# @param [String] repo the repository url-sanitised-name
# @param [String] tree the repository tree
get '/:repo/blob/:tree/*' do
@repo = RepoList.find(params[:repo])
@tree = @repo.find_tree(params[:tree])
@tree.walk(:postorder) do |root, entry|
@blob = entry if "#{root}#{entry[:name]}" == params[:splat].first
end
cache "#{@blob[:oid]}/#{@tree.oid}"
erb :blob, layout: !is_pjax?
end
# HTML page for a raw blob contents in a given repo.
#
# Uses a splat param to specify a blob path.
#
# @param [String] repo the repository url-sanitised-name
# @param [String] tree the repository tree
get '/:repo/raw/:tree/*' do
@repo = RepoList.find(params[:repo])
@tree = @repo.find_tree(params[:tree])
@tree.walk(:postorder) do |root, entry|
@blob = entry if "#{root}#{entry[:name]}" == params[:splat].first
end
cache "#{@blob[:oid]}/#{@tree.oid}/raw"
blob = @repo.find_blob @blob[:oid]
if blob.binary?
content_type 'application/octet-stream'
blob.text
else
content_type :txt
blob.text
end
end
# Pagination route for the commits to a given ref in a +repo+.
#
# @param [String] repo the repository url-sanitised-name
# @param [String] ref the repository ref
get '/:repo/:ref/page/:page' do
pass unless params[:page] =~ /\A\d+\z/
params[:page] = params[:page].to_i
@repo = RepoList.find(params[:repo])
@commits = @repo.commits(params[:ref], 10, (params[:page] - 1) * 10)
cache "#{@commits.first.oid}/page/#{params[:page]}/ref/#{params[:ref]}" if @commits.any?
@next_commits = !@repo.commits(params[:ref], 10, params[:page] * 10).nil?
if params[:page] - 1 > 0
@previous_commits = !@repo.commits(params[:ref], 10, (params[:page] - 1) * 10).empty?
end
erb :log
end
end # App
end # Ginatra