1 contributor
package Gitprep::Main;
use Mojo::Base 'Mojolicious::Controller';
use File::Basename 'dirname';
use Carp 'croak';
sub blob {
my $self = shift;
# Parameters
my $project_ns = $self->param('project');
my $project = "/$project_ns";
my $home_ns = dirname $project_ns;
my $home = "/$home_ns";
my $id_file = $self->param('id_file');
# Id and file
my ($id, $file) = $self->_parse_id_path($project, $id_file);
# Git
my $git = $self->app->git;
# Blob content
my $bid = $git->id_by_path($project, $id, $file, 'blob')
or croak 'Cannot find file';
my @cmd = ($git->cmd($project), 'cat-file', 'blob', $bid);
open my $fh, '-|', @cmd
or croak qq/Couldn't cat "$file", "$bid"/;
# Blob plain
if ($self->stash('plain')) {
# Content type
my $type = $git->blob_contenttype($fh, $file);
# Convert text/* content type to text/plain
if ($self->config('prevent_xss') &&
($type =~ m#^text/[a-z]+\b(.*)$# ||
($type =~ m#^[a-z]+/[a-z]\+xml\b(.*)$# && -T $fh)))
{
my $rest = $1;
$rest = defined $rest ? $rest : '';
$type = "text/plain$rest";
}
# File name
my $file_name = $id;
if (defined $file) { $file_name = $file }
elsif ($type =~ m/^text\//) { $file_name .= '.txt' }
# Content
my $content = do { local $/; <$fh> };
my $sandbox = $self->config('prevent_xss') &&
$type !~ m#^(?:text/[a-z]+|image/(?:gif|png|jpeg))(?:[ ;]|$)#;
my $content_disposition = $sandbox ? 'attachment' : 'inline';
$content_disposition .= "; filename=$file_name";
# Render
$self->res->headers->content_disposition($content_disposition);
$self->res->headers->content_type($type);
$self->render_data($content);
}
# Blob
else {
# MIME type
my $mimetype = $git->blob_mimetype($fh, $file);
# Redirect to blob-plain if no display MIME type
if ($mimetype !~ m#^(?:text/|image/(?:gif|png|jpeg)$)# && -B $fh) {
close $fh;
my $url = $self->url_for('blob_plain',
project => $project_ns, id_file => "$id/$file");
return $self->redirect_to($url);
}
# Commit
my $commit = $git->parse_commit($project, $id);
# Parse line
my @lines;
while (my $line = $git->dec(scalar <$fh>)) {
chomp $line;
$line = $git->_tab_to_space($line);
push @lines, $line;
}
# Render
$self->render(
home => $home,
home_ns => $home_ns,
project => $project,
project_ns => $project_ns,
commit => $commit,
id => $id,
file => $file,
bid => $bid,
lines => \@lines,
mimetype => $mimetype
);
}
}
sub blobdiff {
my $self = shift;
# Parameters
my $project_ns = $self->param('project');
my $project = "/$project_ns";
my $home_ns = dirname $project_ns;
my $home = "/$home_ns";
my $diff = $self->param('diff');
my $file = $self->param('file');
my $from_file = $self->param('from-file');
$from_file = $file unless defined $from_file;
my $plain = $self->param('plain');
my $from_id;
my $id;
if ($diff =~ /\.\./) { ($from_id, $id) = $diff =~ /(.+)\.\.(.+)/ }
else { $id = $diff }
# Git
my $git = $self->app->git;
# Get blob diff (command "git diff")
open my $fh, '-|', $git->cmd($project), 'diff', '-r', '-M', '-p',
$from_id, $id, '--', $from_file, $file
or croak "Open git-diff-tree failed";
# Blob diff plain
if ($plain) {
# Content
my $content = do { local $/; <$fh> };
close $fh;
# Render
my $content_disposition .= "inline; filename=$file";
$self->res->headers->content_disposition($content_disposition);
$self->res->headers->content_type("text/plain; charset=" . $git->encoding);
$self->render(data => $content);
}
# Blob diff
else {
# Lines
my @lines = map { $git->dec($_) } <$fh>;
close $fh;
my $lines = $self->_parse_blobdiff_lines(\@lines);
# Commit
my $commit = $git->parse_commit($project, $id);
# Render
$self->render(
'/blobdiff',
home => $home,
home_ns => $home_ns,
project => $project,
project_ns => $project_ns,
id => $id,
from_id => $from_id,
file => $file,
from_file => $from_file,
commit => $commit,
lines => $lines
);
}
}
sub commit {
my $self = shift;
# Parameters
my $project_ns = $self->param('project');
my $project = "/$project_ns";
my $home_ns = dirname $project_ns;
my $home = "/$home_ns";
my $id = $self->param('id');
# Git
my $git = $self->app->git;
# Commit
my $commit = $git->parse_commit($project, $id);
my $committer_date
= $git->parse_date($commit->{committer_epoch}, $commit->{committer_tz});
my $author_date
= $git->parse_date($commit->{author_epoch}, $commit->{author_tz});
$commit->{author_date} = $git->timestamp($author_date);
$commit->{committer_date} = $git->timestamp($committer_date);
# References
my $refs = $git->references($project);
# Diff tree
my $parent = $commit->{parent};
my $parents = $commit->{parents};
my $difftrees = $git->difftree($project, $commit->{id}, $parent, $parents);
# Render
$self->render(
home => $home,
home_ns => $home_ns,
project => $project,
project_ns => $project_ns,
id => $id,
commit => $commit,
refs => $refs,
difftrees => $difftrees,
);
}
sub commitdiff {
my $self = shift;
# Paramters
my $project_ns = $self->param('project');
my $project = "/$project_ns";
my $home_ns = dirname $project_ns;
my $home = "/$home_ns";
my $diff = $self->param('diff');
my ($from_id, $id) = $diff =~ /(.+)\.\.(.+)/;
$id = $diff unless defined $id;
# Git
my $git = $self->app->git;
# Commit
my $commit = $git->parse_commit($project, $id)
or croak 'Unknown commit object';
my $author_date
= $git->parse_date($commit->{author_epoch}, $commit->{author_tz});
my $committer_date
= $git->parse_date($commit->{committer_epoch}, $commit->{committer_tz});
$commit->{author_date} = $git->timestamp($author_date);
$commit->{committer_date} = $git->timestamp($committer_date);
$from_id = $commit->{parent} unless defined $from_id;
# Plain text
if ($self->param('plain')) {
# Get blob diffs (command "git diff-tree")
my @cmd = ($git->cmd($project), 'diff-tree', '-r', '-M',
'-p', $from_id, $id, '--');
open my $fh, '-|', @cmd
or croak 'Open git-diff-tree failed';
# Content
my $content = do { local $/; <$fh> };
my $content_disposition .= "inline; filename=$id";
# Render
$self->res->headers->content_disposition($content_disposition);
$self->res->headers->content_type("text/plain;charset=" . $git->encoding);
$self->render_data($content);
}
# HTML
else {
# Diff tree
my $difftrees = $git->difftree($project,
$id, $commit->{parent}, $commit->{parents});
# Get blob diffs (command "git diff-tree")
my @cmd = ($git->cmd($project), 'diff-tree', '-r', '-M',
'--no-commit-id', '--patch-with-raw', $from_id, $id, '--');
open my $fh, '-|', @cmd
or croak 'Open git-diff-tree failed';
# Parse output
my @blobdiffs;
while (my $line = $git->dec(scalar <$fh>)) {
# Parse line
chomp $line;
my $diffinfo = $git->parse_difftree_raw_line($line);
my $from_file = $diffinfo->{from_file};
my $file = $diffinfo->{to_file};
# Get blobdiff (command "git diff-tree")
my @cmd = ($git->cmd($project), 'diff-tree', '-r', '-M', '-p',
$from_id, $id, '--', (defined $from_file ? $from_file : ()), $file);
open my $fh_blobdiff, '-|', @cmd
or croak 'Open git-diff-tree failed';
my @lines = map { $git->dec($_) } <$fh>;
close $fh_blobdiff;
my $blobdiff = {
file => $file,
from_file => $from_file,
lines => $self->_parse_blobdiff_lines(\@lines)
};
# Status
for my $difftree (@$difftrees) {
if ($difftree->{to_file} eq $file) {
$blobdiff->{status} = $difftree->{status};
last;
}
}
push @blobdiffs, $blobdiff;
}
# References
my $refs = $git->references($project);
# Render
$self->render(
'commitdiff',
home => $home,
home_ns => $home_ns,
project => $project,
project_ns => $project_ns,
from_id => $from_id,
id => $id,
commit => $commit,
difftrees => $difftrees,
blobdiffs => \@blobdiffs,
refs => $refs
);
}
}
sub home {
my $self = shift;
my $rep_home = '/gitpub';
my @users = qw/kimoto ken/;
$self->render(users => \@users);
}
sub heads {
my $self = shift;
# Parameters
my $project_ns = $self->param('project');
my $project = "/$project_ns";
my $home_ns = dirname $project_ns;
my $home = "/$home_ns";
# Git
my $git = $self->app->git;
# Ref names
my $heads = $git->heads($project);
# Render
$self->render(
home => $home,
home_ns => $home_ns,
project => $project,
project_ns => $project_ns,
heads => $heads,
);
}
sub log {
my ($self, %opt) = @_;
# Parameters
my $project_ns = $self->param('project');
my $project = "/$project_ns";
my $home_ns = dirname $project_ns;
my $home = "/$home_ns";
my $id = $self->param('id');
my $page = $self->param('page');
$page = 0 if !defined $page;
my $short = $self->param('short');
# Git
my $git = $self->app->git;
# Commit
my $commit = $git->parse_commit($project, $id);
# Commits
my $page_count = $short ? 50 : 20;
my $commits = $git->parse_commits(
$project, $commit->{id},$page_count, $page_count * $page);
for my $commit (@$commits) {
my $author_date
= $git->parse_date($commit->{author_epoch}, $commit->{author_tz});
$commit->{author_date} = $git->timestamp($author_date);
}
# References
my $refs = $git->references($project);
# Render
$self->stash->{action} = 'shortlog' if $short;
$self->render(
home => $home,
home_ns => $home_ns,
project => $project,
project_ns => $project_ns,
id => $id,
commits => $commits,
refs => $refs,
page => $page,
page_count => $page_count
);
};
has 'root' => '/gitpub';
sub _root_ns {
my $self = shift;
my $root = $self->root;
$root =~ s/^\///;
return $root;
}
sub repository {
my $self = shift;
# Parameters
my $user = $self->param('user');
my $repository = $self->param('repository');
my $root_ns = $self->_root_ns;
my $project_ns = "$root_ns/$user/$repository.git";
my $project = "/$project_ns";
my $home_ns = dirname $project_ns;
my $home = "/$home_ns";
my $id_dir = $self->param('id_dir') || 'master/';
# Id and directory
my ($id, $dir) = $self->_parse_id_path($project, $id_dir);
# Git
my $git = $self->app->git;
# Tree id
my $tid;
my $commit = $git->parse_commit($project, $id);
unless (defined $tid) {
if (defined $dir && $dir ne '') {
$tid = $git->id_by_path($project, $id, $dir, 'tree');
}
else { $tid = $commit->{tree} }
}
$self->render_not_found unless defined $tid;
# Get tree (command "git ls-tree")
my @entries = ();
my $show_sizes = 0;
open my $fh, '-|', $git->cmd($project), 'ls-tree', '-z',
($show_sizes ? '-l' : ()), $tid
or croak 'Open git-ls-tree failed';
local $/ = "\0";
@entries = map { chomp; $git->dec($_) } <$fh>;
close $fh
or croak 404, "Reading tree failed";
# Parse tree
my @trees;
for my $line (@entries) {
my $tree = $git->parse_ls_tree_line($line, -z => 1, -l => $show_sizes);
$tree->{mode_str} = $git->_mode_str($tree->{mode});
push @trees, $tree;
}
# References
my $refs = $git->references($project);
# Render
$self->render(
home => $home,
home_ns => $home_ns,
project => $project,
project_ns => $project_ns,
dir => $dir,
id => $id,
tid => $tid,
commit => $commit,
trees => \@trees,
refs => $refs
);
}
sub repositories {
my $self = shift;
my $root = $self->root;
my $user = $self->param('user');
# Repositories
my $reps = $self->app->git->repositories("/$root/$user");
# Render
$self->render(reps => $reps);
}
sub snapshot {
my $self = shift;
# Parameter
my $project_ns = $self->param('project');
my $project = "/$project_ns";
my $home_ns = dirname $project_ns;
my $home = "/$home_ns";
my $id = $self->param('id');
# Git
my $git = $self->app->git;
# Object type
my $type = $git->object_type($project, "$id^{}");
if (!$type) { croak 404, 'Object does not exist' }
elsif ($type eq 'blob') { croak 400, 'Object is not a tree-ish' }
my ($name, $prefix) = $git->snapshot_name($project, $id);
my $file = "$name.tar.gz";
my $cmd = $self->_quote_command(
$git->cmd($project), 'archive', "--format=tar", "--prefix=$prefix/", $id
);
$cmd .= ' | ' . $self->_quote_command('gzip', '-n');
$file =~ s/(["\\])/\\$1/g;
open my $fh, '-|', $cmd
or croak 'Execute git-archive failed';
# Write chunk
$self->res->headers->content_type('application/x-tar');
$self->res->headers->content_disposition(qq/attachment; filename="$file"/);
my $cb;
$cb = sub {
my $c = shift;
my $size = 500 * 1024;
my $length = sysread($fh, my $buffer, $size);
unless (defined $length) {
close $fh;
undef $cb;
return;
}
$c->write_chunk($buffer, $cb);
};
$self->$cb;
}
sub summary {
my $self = shift;
# Parameters
my $project_ns = $self->param('project');
my $project = "/$project_ns";
my $home_ns = dirname $project_ns;
my $home = "/$home_ns";
# Git
my $git = $self->app->git;
# HEAd commit
my $project_description = $git->project_description($project);
my $project_owner = $git->project_owner($project);
my $head_commit = $git->parse_commit($project, 'HEAD');
my $committer_date
= $git->parse_date($head_commit->{committer_epoch}, $head_commit->{committer_tz});
my $last_change = $git->timestamp($committer_date);
my $head_id = $head_commit->{id};
my $urls = $git->project_urls($project);
# Commits
my $commit_count = 20;
my $commits = $head_id ? $git->parse_commits($project, $head_id, $commit_count) : ();
# References
my $refs = $git->references($project);
# Tags
my $tag_count = 20;
my $tags = $git->tags($project, $tag_count - 1);
# Heads
my $head_count = 20;
my $heads = $git->heads($project, $head_count - 1);
# Render
$self->render(
home => $home,
home_ns => $home_ns,
project => $project,
project_ns => $project_ns,
project_description => $project_description,
project_owner => $project_owner,
last_change => $last_change,
urls => $urls,
commits => $commits,
tags => $tags,
head_id => $head_id,
heads => $heads,
refs => $refs,
commit_count => $commit_count,
tag_count => $tag_count,
head_count => $head_count
);
}
sub tag {
my $self = shift;
# Parameters
my $project_ns = $self->param('project');
my $project = "/$project_ns";
my $home_ns = dirname $project_ns;
my $home = "/$home_ns";
my $id = $self->param('id');
# Git
my $git = $self->app->git;
# Ref names
my $tag = $git->parse_tag($project, $id);
my $author_date
= $git->parse_date($tag->{author_epoch}, $tag->{author_tz});
$tag->{author_date} = $git->timestamp($author_date);
# Render
$self->render(
home => $home,
home_ns => $home_ns,
project => $project,
project_ns => $project_ns,
tag => $tag,
);
}
sub tags {
my $self = shift;
# Parameters
my $project_ns = $self->param('project');
my $project = "/$project_ns";
my $home_ns = dirname $project_ns;
my $home = "/$home_ns";
# Git
my $git = $self->app->git;
# Ref names
my $tags = $git->tags($project);
# Render
$self->render(
home => $home,
home_ns => $home_ns,
project => $project,
project_ns => $project_ns,
tags => $tags,
);
}
sub tree {
my $self = shift;
# Parameters
my $project_ns = $self->param('project');
my $project = "/$project_ns";
my $home_ns = dirname $project_ns;
my $home = "/$home_ns";
my $id_dir = $self->param('id_dir');
# Id and directory
my ($id, $dir) = $self->_parse_id_path($project, $id_dir);
# Git
my $git = $self->app->git;
# Tree id
my $tid;
my $commit = $git->parse_commit($project, $id);
unless (defined $tid) {
if (defined $dir && $dir ne '') {
$tid = $git->id_by_path($project, $id, $dir, 'tree');
}
else { $tid = $commit->{tree} }
}
$self->render_not_found unless defined $tid;
# Get tree (command "git ls-tree")
my @entries = ();
my $show_sizes = 0;
open my $fh, '-|', $git->cmd($project), 'ls-tree', '-z',
($show_sizes ? '-l' : ()), $tid
or croak 'Open git-ls-tree failed';
local $/ = "\0";
@entries = map { chomp; $git->dec($_) } <$fh>;
close $fh
or croak 404, "Reading tree failed";
# Parse tree
my @trees;
for my $line (@entries) {
my $tree = $git->parse_ls_tree_line($line, -z => 1, -l => $show_sizes);
$tree->{mode_str} = $git->_mode_str($tree->{mode});
push @trees, $tree;
}
# References
my $refs = $git->references($project);
# Render
$self->render(
home => $home,
home_ns => $home_ns,
project => $project,
project_ns => $project_ns,
dir => $dir,
id => $id,
tid => $tid,
commit => $commit,
trees => \@trees,
refs => $refs
);
}
sub _parse_blobdiff_lines {
my ($self, $lines_raw) = @_;
# Git
my $git = $self->app->git;
# Parse
my @lines;
for my $line (@$lines_raw) {
$line = $git->dec($line);
chomp $line;
my $class;
if ($line =~ /^diff \-\-git /) { $class = 'diff header' }
elsif ($line =~ /^index /) { $class = 'diff extended_header' }
elsif ($line =~ /^\+/) { $class = 'diff to_file' }
elsif ($line =~ /^\-/) { $class = 'diff from_file' }
elsif ($line =~ /^\@\@/) { $class = 'diff chunk_header' }
elsif ($line =~ /^Binary files/) { $class = 'diff binary_file' }
else { $class = 'diff' }
push @lines, {value => $line, class => $class};
}
return \@lines;
}
sub _parse_id_path {
my ($self, $project, $id_path) = @_;
# Git
my $git = $self->app->git;
# Parse id and path
my $refs = $git->references($project);
my $id;
my $path;
for my $rs (values %$refs) {
for my $ref (@$rs) {
$ref =~ s#^heads/##;
$ref =~ s#^tags/##;
if ($id_path =~ s#^\Q$ref(/|$)##) {
$id = $ref;
$path = $id_path;
last;
}
}
}
unless (defined $id) {
if ($id_path =~ s#(^[^/]+)(/|$)##) {
$id = $1;
$path = $id_path;
}
}
return ($id, $path);
}
sub _quote_command {
my $self = shift;
return join(' ',
map { my $a = $_; $a =~ s/(['!])/'\\$1'/g; "'$a'" } @_ );
}
1;