+Thumbs.db
Gitalist-*
*.swp
git-daemon-export-ok
This file documents the revision history for Perl extension Gitalist.
+0.000006 2010-04-10
+
+ - Major frontend redesign, thanks to ranguard and the web designers at
+ Foxtons for making this happen.
+ - Major URI overhaul, Gitalist has gone from old gitweb style CGI
+ parameters to proper URIs, however the old URIs are still supported
+ and will redirect appropriately (Tomas Doran).
+ - With the URI overhaul also came breaking up actions into fragments
+ for use with Catalyst::View::Component::SubInclude.
- BIG BREAKING CHANGE - Gitalist::Model::GitRepos has been renamed
Gitalist::Model::CollectionOfRepos. You need to fix your config and
if you have actually installed Gitalist - remove the old model file.
-
- Bump required version of Git::PurePerl for Encoding fixes.
-
- Fix Makefile.PL to not need release deps when checking out of Git.
0.000005 2010-01-09
requires 'DateTime';
requires 'DateTime::Format::Mail';
requires 'File::Copy::Recursive';
+requires 'File::Type';
+requires 'File::Type::WebImages';
requires 'File::Which';
requires 'HTML::Entities';
requires 'IPC::Run';
cp `perl -Ilib -MGitalist -e'print Gitalist->path_to("gitalist.conf")'` gitalist.conf
- You can then edit this confg, adding a repos_dir path and customising
+ You can then edit this confg, adding a repo_dir path and customising
other settings as desired.
You can then start the Gitalist demo server by setting
Alternatively, if you only want to set a repository directory and are
otherwise happy with the default configuration, then you can set the
- "GITALIST_REPO_DIR" environment variable, or pass the "--repos_dir" flag
+ "GITALIST_REPO_DIR" environment variable, or pass the "--repo_dir" flag
to any of the scripts.
GITALIST_REPO_DIR=/home/myuser/code/git gitalist_server.pl
- gitalist_server.pl --repos_dir home/myuser/code/git
+ gitalist_server.pl --repo_dir home/myuser/code/git
The "GITALIST_REPO_DIR" environment variable will override the
repository directory set in configuration, and will itself be overridden
- by he "--repos_dir" flag.
+ by he "--repo_dir" flag.
RUNNING
Once you have followed the instructions above to install and configure
SubRequest
/;
-our $VERSION = '0.000006_01';
+our $VERSION = '0.000006';
$VERSION = eval $VERSION;
__PACKAGE__->config(
cp `perl -Ilib -MGitalist -e'print Gitalist->path_to("gitalist.conf")'` gitalist.conf
-You can then edit this confg, adding a repos_dir path and customising other settings as desired.
+You can then edit this confg, adding a repo_dir path and customising other settings as desired.
You can then start the Gitalist demo server by setting C<< GITALIST_CONFIG >>. For example:
Alternatively, if you only want to set a repository directory and are otherwise happy with
the default configuration, then you can set the C<< GITALIST_REPO_DIR >> environment
-variable, or pass the C<< --repos_dir >> flag to any of the scripts.
+variable, or pass the C<< --repo_dir >> flag to any of the scripts.
GITALIST_REPO_DIR=/home/myuser/code/git gitalist_server.pl
- gitalist_server.pl --repos_dir home/myuser/code/git
+ gitalist_server.pl --repo_dir home/myuser/code/git
The C<< GITALIST_REPO_DIR >> environment variable will override the repository directory set
-in configuration, and will itself be overridden by he C<< --repos_dir >> flag.
+in configuration, and will itself be overridden by he C<< --repo_dir >> flag.
=head1 RUNNING
Gitalist::URIStructure::Fragment::WithLog
/;
+use File::Type::WebImages ();
+
sub base : Chained('/fragment/repository/find') PathPart('') CaptureArgs(0) {}
sub _diff {
$c->stash(
tree => $tree,
tree_list => [$repository->list_tree($tree->sha1)],
- path => $c->stash->{filename}, # FIXME?
);
};
my ( $self, $c ) = @_;
$c->stash(
# XXX Hack hack hack, see View::SyntaxHighlight
- language => ($c->stash->{filename} =~ /\.p[lm]$/i ? 'Perl' : ''),
+ language => ($c->stash->{filename} =~ /\.p[lm]$/i ? 'Perl' : ''),
+ is_image => File::Type::WebImages::mime_type($c->stash->{blob}),
+ is_binary => Gitalist::Utils::is_binary($c->stash->{blob}),
);
$c->forward('View::SyntaxHighlight')
BEGIN { extends 'Gitalist::Controller' }
with 'Gitalist::URIStructure::Ref';
+use File::Type;
+use File::Type::WebImages ();
+
sub base : Chained('/repository/find') PathPart('') CaptureArgs(0) {}
after commit => sub {
my ($self, $c) = @_;
$c->forward('find_blob');
- $c->response->content_type('text/plain; charset=utf-8');
+ if(!Gitalist::Utils::is_binary($c->stash->{blob})) {
+ $c->response->content_type('text/plain; charset=utf-8');
+ } else {
+ my $ft = File::Type->new();
+ $c->response->content_type(
+ File::Type::WebImages::mime_type($c->stash->{blob})
+ || File::Type->new->mime_type($c->stash->{blob})
+ );
+ }
+
$c->response->body(delete $c->stash->{blob});
}
next;
}
- if (/^index (\w+)\.\.(\w+) (\d+)$/) {
+ if (/^index (\w+)\.\.(\w+)(?: (\d+))?$/) {
@{$ret[-1]}{qw(index src dst mode)} = ($_, $1, $2, $3);
next
}
if !$sha1 || $sha1 !~ $SHA1RE;
my @search_opts;
- if ($search) {
+ if ($search and exists $search->{text}) {
$search->{type} = 'grep'
if $search->{type} eq 'commit';
@search_opts = (
return $age_str;
}
+sub is_binary {
+ my($str) = @_;
+ open my $fh, '<', \$str or return;
+ return -B $fh;
+}
+
1;
__END__
=head2 age_string
-Turns an integer number of seconds into a string..
+Turns an integer number of seconds into a string.
+
+=head2 is_binary
+
+Check whether a string is binary according to C<-B>.
=head1 AUTHORS
[% FOREACH item IN diff %]
<h4 id="diff[% loop.count %]" class='diff-head'>diff --git [%# FIXME %]
- <a href='[% c.uri_for_action("/ref/blob", [Repository.name, Commit.sha1], item.file) %]' title="Blob">[% item.a %]</a>
- <a href='[% c.uri_for_action("/ref/blob", [Repository.name, Commit.sha1], item.file) %]' title="Blob">[% item.b %]</a>
+ [% IF !item.src.match('^0+$') %]
+ <a href='[% c.uri_for_action("/ref/blob", [Repository.name, item.src]) %]' title="Blob">[% item.a %]</a>
+ [% ELSE %]
+ [% item.a %]
+ [% END %]
+ [% IF !item.dst.match('^0+$') %]
+ <a href='[% c.uri_for_action("/ref/blob", [Repository.name, item.dst]) %]' title="Blob">[% item.b %]</a>
+ [% ELSE %]
+ [% item.b %]
+ [% END %]
</h4>
-
-
<div class='diff-patch'>
<pre>[% blobs.${loop.index} %]</pre>
</div>
<tr [% "class='invert'" IF loop.count % 2 %]>
<td>[% loop.count %]</td>
<td><a href="[% repos_link %]/shortlog"><strong>[% p.name %]</strong></a></td>
- <td><span title="[% p.description %]">[% abridged_description(p.description) %]</span></td>
+ <td class="description"><div title="[% p.description %]">[% abridged_description(p.description) IF p.description != "Unnamed repository; edit this file to name it for gitweb." %]</div></td>
<td class="time-since">[% time_since(p.last_change) %]</td>
<td>[% p.owner %]</td>
<td class="action-list">
-[% blob %]
+[%- IF is_image -%]
+<div class='blob'><img src="[% c.uri_for_action('/ref/raw', c.req.captures, filename) %]" title="[% filename %]"></div>
+[%- ELSIF is_binary -%]
+<div class='blob'>This is a binary file which won't render natively on the web, but you can get it here all the same: <a href="[% c.uri_for_action('/ref/raw', c.req.captures, filename) %]" title="[% filename %]">[% filename %]</a></div>
+[%- ELSE -%]
+<pre class='blob'>[% blob | html %]</pre>
+[%- END -%]
<tr>
<td class='sha1'>[% INCLUDE 'inc/chroma_hash.tt2' sha1 = Commit.sha1.substr(0, 7), hide_sha1_output = 1 %] <div class="sha1_label">Commit</div></td>
<td>[% Commit.sha1 %]</td>
- <td class='action-list'></td>
+ <td class='action-list'><a href="[% c.uri_for_action('/ref/diff_fancy', [Repository.name, Commit.sha1]) %]" title="Difference" class="button diff">diff</a></td>
</tr>
<tr>
<td class='sha1'>[% INCLUDE 'inc/chroma_hash.tt2' sha1 = Commit.tree_sha1.substr(0, 7), hide_sha1_output = 1 %] <div class="sha1_label">Tree</div></td>
[% BLOCK history_table_headfoot %]
[% SET cell = type == 'head' ? 'th' : 'td' %]
+[%# FIXME: should c.req.arguments.0 be path instead? %]
<tr>
+<[% cell %] colspan="2"><a href="#" onclick="compareDiffs('[% Repository.name %]','[% c.req.arguments.0 %]');return false;">Compare</a></[% cell %]>
<[% cell %]>sha1</[% cell %]>
<[% cell %]>time</[% cell %]>
<[% cell %]>message</[% cell %]>
<[% cell %]>actions</[% cell %]>
</tr>
[% END %]
+<form name="theform">
<table class='listing'>
<thead>[% PROCESS history_table_headfoot type = 'head' %]</thead
<tbody>
[% FOREACH line IN log_lines %]
<tr [% "class='invert'" IF loop.count % 2 %]>
+ <td><input type="radio" name="sha1_a" value="[% line.sha1 %]" [% "checked" IF loop.count == 2 %] /></td>
+ <td><input type="radio" name="sha1_b" value="[% line.sha1 %]" [% "checked" IF loop.count == 1 %]/></td>
<td class='sha1' title='[% line.sha1 %]'>[% INCLUDE 'inc/chroma_hash.tt2' sha1 = line.sha1.substr(0, 7) %]</td>
<td class='time-since' title='[% line.authored_time %]'>[% time_since(line.authored_time) %]</td>
</td>
</tr>
[% END %]
+ <tr>
+ <td colspan="2"><a href="#" onclick="compareDiffs('[% Repository.name %]');return false;">Compare</a></td>
+ <td colspan="5"></td>
+</tr>
</tbody>
</table>
+</form>
[%
INCLUDE 'inc/log_pager.tt2';
%]
[% BLOCK shortlog_table_headfoot %]
[% SET cell = type == 'head' ? 'th' : 'td' %]
<tr>
+ <[% cell %] colspan="2"><a href="#" onclick="compareDiffs('[% Repository.name %]');return false;">Compare</a></[% cell %]>
<[% cell %]>ID (sha1)</[% cell %]>
<[% cell %]>Last change</[% cell %]>
<[% cell %]>Message</[% cell %]>
</tr>
[% END %]
+<form name="theform">
<table class='listing'>
<thead>[% PROCESS shortlog_table_headfoot type = 'head' %]</thead>
<tbody>
[% FOREACH line IN log_lines %]
<tr [% "class='invert'" IF loop.count % 2 %]>
+ <td><input type="radio" name="sha1_a" value="[% line.sha1 %]" [% "checked" IF loop.count == 2 %] /></td>
+ <td><input type="radio" name="sha1_b" value="[% line.sha1 %]" [% "checked" IF loop.count == 1 %]/></td>
<td class='sha1' title='[% line.sha1 %]'>[% INCLUDE 'inc/chroma_hash.tt2' sha1 = line.sha1.substr(0, 7) %]</td>
<td class='time-since' title='[% line.authored_time %]'>[% time_since(line.authored_time) %]</td>
<td>
</td>
</tr>
[% END %]
+<tr>
+ <td colspan="2"><a href="#" onclick="compareDiffs('[% Repository.name %]');return false;">Compare</a></td>
+ <td colspan="5"></td>
+</tr>
</tbody>
</table>
+</form>
[% INCLUDE 'inc/log_pager.tt2' %]
[% short_cmt(head.comment) %]
</div>
[% END %]
-
- <pre class='blob'>[% subinclude('/fragment/ref/blob', c.req.captures, c.req.args.to_path) | html %]</pre>
+
+[% subinclude('/fragment/ref/blob', c.req.captures, c.req.args.to_path) %]
\ No newline at end of file
- [%
- IF path;
- INCLUDE 'nav/path.tt2' filename = path;
- END;
-
- subinclude('/fragment/ref/tree', c.req.captures, c.req.args.to_path);
- %]
-
+[% subinclude('/fragment/ref/tree', c.req.captures, c.req.args.to_path) %]
/* nav tabs */
#nav_logs{
- width:80%;
+ width:100%;
clear:both;
float:right;
margin:-5px 10px 0 0;
color:#DC143C;
}
+/* sub actions dropdown changer in h1 */
+#actions_nav_link{
+ border:1px solid #666;
+ padding:1px 4px;
+ text-decoration:none;
+ outline:none;
+}
+#actions_nav_link span{
+ margin-right:5px;
+ padding-right:18px;
+ background:transparent url([% c.uri_for('/static/i/arrow_down_white.gif') %]) no-repeat right center;
+}
+#actions_nav_list{
+ display:none;
+ position:absolute;
+ padding:0 0 3px 0;
+ margin:0;
+ background-color:#333;
+ color:#fff;
+ font-size:1em;
+ border:1px solid #666;
+ border-top:none;
+}
+#actions_nav_list li{
+ list-style:none;
+ margin:0;
+ padding:2px 10px 2px 5px;
+}
+#actions_nav_list a{
+ color:#fff;
+ font-size:1.8em;
+}
+#actions_nav_list a:hover{
+ color:#EAF2F5;
+}
+.actions_nav_list_over{
+ display:block !important;
+}
.button{
display:block;
float:left;
th a{
color:#fff;
}
+th a:hover{
+ color:#f0f0f0;
+}
.summary tr{
background-color:#FAFAFA;
border-bottom:1px solid #fff;
.summary td{
vertical-align:middle !important;
}
+tr{
+ background-color:#fff;
+}
+thead tr{
+ background-color:transparent !important;
+}
tr.invert{
background-color:#f0f0f0;
}
vertical-align:top;
padding:9px 5px 9px 10px;
}
+
+/*
+
+puts the repo description on one line which gets truncated if the repo name is too long
+BUT the final width needs to be set with javascript based on the parent element (td) width
+
+.description{
+ white-space:nowrap;
+ overflow:hidden;
+}
+.description div{
+ position:absolute;
+ white-space:nowrap;
+ overflow:hidden;
+ width:200px;
+}
+*/
+
.action-list{
width:120px;
}
padding:10px;
border:1px solid #ddd;
background-color:#f0f0f0;
+ min-height:40px;
}
.diff-head{
background-color:#666;
overflow:auto;
font-size:12px;
}
+div.blob {
+ text-align: center;
+ margin: 30px;
+}
/* /blobdiff etc */
--- /dev/null
+function compareDiffs(repo, path){
+ var f = document.theform;
+ if(!repo){
+ var repo = "";
+ }
+ if(!path){
+ var path = "";
+ }
+ var sha1,sha2;
+ for(var i=0,len=f.length;i<len;i++){
+ if(f[i].name == "sha1_a"){
+ if(f[i].checked){
+ sha1 = f[i].value;
+ }
+ }
+ if(f[i].name == "sha1_b"){
+ if(f[i].checked){
+ sha2 = f[i].value;
+ }
+ }
+ }
+ //document.location.href = [% c.uri_for("/" + repo + "/"+ sha1 + "/diff/" + sha2 + "/" + path) %];
+ document.location.href = "/" + repo + "/"+ sha1 + "/diff/" + sha2 + "/" + path;
+}
<link rel="stylesheet" type="text/css" href="[% c.uri_for('/core.css') %]" />
<link rel="shortcut icon" href="[% c.uri_for('/static/git-favicon.png') %]" type="image/png" />
+ <script type="text/javascript">
+ // FIXME: this should be in an external js file once c.uri_for works in js files
+ function compareDiffs(repo, path){
+ var f = document.theform;
+ if(!repo){
+ var repo = "";
+ }
+ if(!path){
+ var path = "";
+ }
+ var sha1,sha2;
+ for(var i=0,len=f.length;i<len;i++){
+ if(f[i].name == "sha1_a"){
+ if(f[i].checked){
+ sha1 = f[i].value;
+ }
+ }
+ if(f[i].name == "sha1_b"){
+ if(f[i].checked){
+ sha2 = f[i].value;
+ }
+ }
+ }
+ document.location.href = "[% c.uri_for("/") %]" + repo + "/"+ sha1 + "/diff/" + sha2 + "/" + path;
+ }
+ // handles hover sub menus in IE
+ startList = function() {
+ if (document.getElementById && document.getElementById("actions_nav_link")) {
+ var navList = document.getElementById("actions_nav_list");
+ var navLink = document.getElementById("actions_nav_link");
+ // assign event handlers to each element
+ navLink.onmouseover=function() {
+ setNavClass(navList);
+ }
+ navList.onmouseover=function() {
+ setNavClass(navList);
+ }
+ navList.onmouseout=function() {
+ navList.className=navList.className.replace(" actions_nav_list_over", "");
+ }
+ navLink.onmouseout=function() {
+ navList.className=navList.className.replace(" actions_nav_list_over", "");
+ }
+ }
+
+ }
+ function setNavClass(el){
+ var link_el = document.getElementById("actions_nav_link");
+ var offsetAry = findPos(link_el);
+ // set position of list
+ el.style.left = offsetAry[0]+"px";
+ el.style.top = offsetAry[1]+30 +"px";
+ el.className+=" actions_nav_list_over";
+ }
+
+ function addLoadListener(fn){
+ if (typeof window.addEventListener != 'undefined') {
+ window.addEventListener('load', fn, false);
+ }else if (typeof document.addEventListener != 'undefined'){
+ document.addEventListener('load', fn, false);
+ }else if (typeof window.attachEvent != 'undefined'){
+ window.attachEvent('onload', fn);
+ }else{
+ var oldfn = window.onload;
+ if (typeof window.onload != 'function'){
+ window.onload = fn;
+ }else{
+ window.onload = function(){
+ oldfn();
+ fn();
+ };
+ }
+ }
+ }
+ function findPos(obj) {
+ var curleft = curtop = 0;
+ if (obj.offsetParent) {
+ do {
+ curleft += obj.offsetLeft;
+ curtop += obj.offsetTop;
+ }
+ while (obj = obj.offsetParent);
+ return [curleft,curtop];
+ }
+ }
+ addLoadListener(startList);
+ </script>
</head>
<body>
[% IF c.req.captures.size == 1; SET path = 'repository'; ELSE; SET path = 'ref'; END %]
<ul>
- <li[% ' class="selected"' IF c.action.name.match('tree') %]><a href="[% c.uri_for_action('/ref/tree', c.req.captures) %]" id="tree">Tree</a></li>
+ <li[% ' class="selected"' IF c.action.name.match('tree') %]><a href="[% c.uri_for_action('/ref/tree', c.req.captures) || c.uri_for_action('/repository/tree', c.req.captures) %]" id="tree">Tree</a></li>
<li[% ' class="selected"' IF c.action.name.match('longlog') %]><a href="[% c.uri_for_action('/' _ path _ '/longlog', c.req.captures) %]" id="log_full">Long log</a></li>
[% END %]
[%- END %]
+
[%-
- c.action.name
- .replace("_"," ")
- .replace("log", " log")
- .replace("fancy","")
- .replace("index","Repositories")
- FILTER ucfirst
+ SET actions_list = {
+ "blob" => 1,
+ "raw" => 1,
+ "blame" => 1,
+ "history" => 1,
+ };
+
+ SET action_name = c.action.name
+ .replace("_"," ")
+ .replace("log", " log")
+ .replace("fancy","")
+ .replace("index","Repositories");
+ action_name_ucfirst = action_name FILTER ucfirst;
+ IF actions_list.$action_name;
+ '<a href="#" id="actions_nav_link"><span>' _ action_name_ucfirst _ '</span></a>';
+ ELSE;
+ action_name_ucfirst;
+ END;
+
-%]
-
-
-
+
</h1>
+ [%-
+ IF actions_list.$action_name;
+ '<ul id="actions_nav_list">';
+ FOREACH action IN actions_list;
+ action_output = action.key FILTER ucfirst;
+ NEXT IF action_output == action_name_ucfirst;
+ action = action.key;
+ '<li><a href="/' _ c.req.path.replace(action_name, action) _ '">' _ action_output _ '</a></li>';
+ END;
+ '</ul>';
+ END;
+ -%]
+
</div>
</div>
-
+
<div id="content_holder">
<div id="content" class="sub_holder">
<div id="content_inner">
<div id="footer_holder">
<div id="footer" class="sub_holder">
- <p>[% IF Repository; Repository.description | html; END; %]</p>
+ <p>[% IF Repository; Repository.description | html IF Repository.description != "Unnamed repository; edit this file to name it for gitweb." ; END; %]</p>
<a title="git homepage" href="http://git-scm.org"><img src="[% c.uri_for('/logo.png') %]" id="git_logo" alt="git" /></a>