Commit 4587ab6f authored by Ariejan de Vroom's avatar Ariejan de Vroom

Merge remote-tracking branch 'upstream/master'

parents 2677bc3a 98d64925
port: 3000
.bundle
.rbx/
db/*.sqlite3
db/*.sqlite3-journal
log/*.log
tmp/
.sass-cache/
coverage/*
*.swp
public/uploads/
.rvmrc
.directory
nohup.out
rvm use 1.9.2-p290
before_install: sudo apt-get install libicu-dev -y
branches:
only:
- 'master'
......
v 2.1.0
- Project tab r1
- Repository tab r1
v 2.0.0
- gitolite as main git host system
- merge requests
- project/repo access
- link to commit/issue feed
- design tab
- improved email notifications
- restyled dashboard
- bugfix
v 1.2.2
......
......@@ -3,9 +3,11 @@ source "http://rubygems.org"
gem "rails", "3.1.1"
gem "sqlite3"
gem "rake", "0.9.2.2"
gem "devise", "1.5.0"
gem "stamp"
gem "kaminari"
gem "haml", "3.1.4"
gem "haml-rails"
gem "jquery-rails"
gem "grit", :git => "https://github.com/gitlabhq/grit.git"
......@@ -15,14 +17,17 @@ gem "six"
gem "therubyracer"
gem "faker"
gem "seed-fu", "~> 2.1.0"
gem "pygments.rb", "0.2.3"
gem "pygments.rb", "0.2.4"
gem "thin"
gem "git"
gem "acts_as_list"
gem "rdiscount"
gem "acts-as-taggable-on", "~> 2.1.0"
gem "drapper"
gem "rchardet19", "~> 1.3.5"
gem "resque"
gem "httparty"
gem "charlock_holmes"
gem "foreman"
group :assets do
gem "sass-rails", "~> 3.1.0"
......@@ -47,6 +52,7 @@ group :development, :test do
gem "awesome_print"
gem "database_cleaner"
gem "launchy"
gem "webmock"
end
group :test do
......
......@@ -77,6 +77,7 @@ GEM
xpath (~> 0.1.4)
carrierwave (0.5.8)
activesupport (~> 3.0)
charlock_holmes (0.6.8)
childprocess (0.2.2)
ffi (~> 1.0.6)
coffee-rails (3.1.1)
......@@ -87,6 +88,7 @@ GEM
execjs
coffee-script-source (1.1.3)
columnize (0.3.4)
crack (0.3.1)
daemons (1.1.4)
database_cleaner (0.7.0)
devise (1.5.0)
......@@ -102,8 +104,11 @@ GEM
faker (1.0.1)
i18n (~> 0.4)
ffi (1.0.11)
foreman (0.27.0)
term-ansicolor (~> 1.0.5)
thor (>= 0.13.6)
git (1.2.5)
haml (3.1.3)
haml (3.1.4)
haml-rails (0.3.4)
actionpack (~> 3.0)
activesupport (~> 3.0)
......@@ -111,6 +116,9 @@ GEM
railties (~> 3.0)
hashery (1.4.0)
hike (1.2.1)
httparty (0.8.1)
multi_json
multi_xml
i18n (0.6.0)
jquery-rails (1.0.17)
railties (~> 3.0)
......@@ -132,17 +140,20 @@ GEM
treetop (~> 1.4.8)
mime-types (1.17.2)
multi_json (1.0.3)
multi_xml (0.4.1)
nokogiri (1.5.0)
orm_adapter (0.0.5)
polyglot (0.3.3)
posix-spawn (0.3.6)
pygments.rb (0.2.3)
rubypython (>= 0.5.1)
pygments.rb (0.2.4)
rubypython (~> 0.5.3)
rack (1.3.5)
rack-cache (1.1)
rack (>= 0.4)
rack-mount (0.8.3)
rack (>= 1.0.0)
rack-protection (1.1.4)
rack
rack-ssl (1.3.2)
rack
rack-test (0.6.1)
......@@ -165,10 +176,17 @@ GEM
rdoc (~> 3.4)
thor (~> 0.14.6)
rake (0.9.2.2)
rchardet19 (1.3.5)
rdiscount (1.6.8)
rdoc (3.11)
json (~> 1.4)
redis (2.2.2)
redis-namespace (1.0.3)
redis (< 3.0.0)
resque (1.19.0)
multi_json (~> 1.0)
redis-namespace (~> 1.0.2)
sinatra (>= 0.9.2)
vegas (~> 0.1.2)
rspec (2.7.0)
rspec-core (~> 2.7.0)
rspec-expectations (~> 2.7.0)
......@@ -220,6 +238,10 @@ GEM
multi_json (~> 1.0.3)
simplecov-html (~> 0.5.3)
simplecov-html (0.5.3)
sinatra (1.3.1)
rack (~> 1.3, >= 1.3.4)
rack-protection (~> 1.1, >= 1.1.2)
tilt (~> 1.3, >= 1.3.3)
six (0.2.0)
sprockets (2.0.3)
hike (~> 1.2)
......@@ -227,6 +249,7 @@ GEM
tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.4)
stamp (0.1.6)
term-ansicolor (1.0.7)
therubyracer (0.9.9)
libv8 (~> 3.3.10)
thin (1.3.1)
......@@ -244,8 +267,13 @@ GEM
uglifier (1.1.0)
execjs (>= 0.3.0)
multi_json (>= 1.0.2)
vegas (0.1.8)
rack (>= 1.0.0)
warden (1.1.0)
rack (>= 1.0)
webmock (1.7.8)
addressable (~> 2.2, > 2.2.5)
crack (>= 0.1.7)
xpath (0.1.4)
nokogiri (~> 1.3)
......@@ -261,24 +289,29 @@ DEPENDENCIES
awesome_print
capybara
carrierwave
charlock_holmes
coffee-rails (~> 3.1.0)
database_cleaner
devise (= 1.5.0)
drapper
faker
foreman
git
gitolite!
grit!
haml (= 3.1.4)
haml-rails
httparty
jquery-rails
kaminari
launchy
letter_opener
pygments.rb (= 0.2.3)
pygments.rb (= 0.2.4)
rails (= 3.1.1)
rails-footnotes (~> 3.7.5)
rchardet19 (~> 1.3.5)
rake (= 0.9.2.2)
rdiscount
resque
rspec-rails
ruby-debug19
sass-rails (~> 3.1.0)
......@@ -292,3 +325,4 @@ DEPENDENCIES
thin
turn
uglifier
webmock
web: bundle exec rails s -p $PORT
worker: bundle exec rake environment resque:work QUEUE=* VVERBOSE=1
web: bundle exec rails s -p $PORT -e production
worker: bundle exec rake environment resque:work RAILS_ENV=production QUEUE=* VVERBOSE=1
# Welcome to GitLab [![build status](https://secure.travis-ci.org/gitlabhq/gitlabhq.png)](https://secure.travis-ci.org/gitlabhq/gitlabhq)
GitLab is a free Project/Repository management application
<img src="http://gitlabhq.com/front.png" width="900" height="471">
GitLab is a free project and repository management application
## Application details
rails 3.1
works only with gitolite
sqlite as default a database
* rails 3.1
* works only with gitolite
* sqlite as default a database
## Requirements
......@@ -18,7 +15,7 @@ sqlite as default a database
* sqlite
* git
* gitolite
* pygments lib - `sudo easy_install pygments`
* redis
## Install
......@@ -28,13 +25,11 @@ Checkout wiki pages for installation information, migration, etc.
[Google Group](https://groups.google.com/group/gitlabhq)
IRC freenode: #gitlabhq
## Contacts
Twitter:
* @gitalbhq
* @gitlabhq
* @dzaporozhets
Email
......@@ -43,7 +38,5 @@ Email
## Contribute
We are on our way to full open source.
Want to help - create an issue on github and notify us that you are ready to start it.
If approved - fork, code, cover with tests & make pull request.
Want to help - send a pull request.
We'll accept good pull requests.
[Dolphin]
ShowPreview=true
Timestamp=2011,10,28,13,16,25
Version=2
......@@ -16,7 +16,7 @@
//= require branch-graph
//= require_tree .
$(function(){
$(document).ready(function(){
$(".one_click_select").live("click", function(){
$(this).select();
});
......@@ -27,8 +27,50 @@ $(function(){
$(".account-box").mouseenter(showMenu);
$(".account-box").mouseleave(resetMenu);
$("#projects-list .project").live('click', function(e){
if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") {
location.href = $(this).attr("url");
e.stopPropagation();
return false;
}
});
$("#issues-table .issue").live('click', function(e){
if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") {
location.href = $(this).attr("url");
e.stopPropagation();
return false;
}
});
$(document).keypress(function(e) {
if( $(e.target).is(":input") ) return;
switch(e.which) {
case 115: focusSearch();
e.preventDefault();
}
});
});
function focusSearch() {
$("#search").focus();
}
function taggifyForm(){
var tag_field = $('#tag_field').tagify();
tag_field.tagify('inputField').autocomplete({
source: '/tags.json'
});
$('form').submit( function() {
var tag_field = $('#tag_field')
tag_field.val( tag_field.tagify('serialize') );
return true;
});
}
function updatePage(data){
$.ajax({type: "GET", url: location.href, data: data, dataType: "script"});
}
......@@ -40,3 +82,5 @@ function showMenu() {
function resetMenu() {
$(this).removeClass("hover");
}
$(document).ready(function(){
$(".day-commits-table li.commit").live('click', function(e){
if(e.target.nodeName != "A") {
location.href = $(this).attr("url");
e.stopPropagation();
return false;
}
});
});
var CommitsList = {
ref:null,
limit:0,
offset:0,
ref:null,
limit:0,
offset:0,
init:
function(ref, limit) {
this.ref=ref;
this.limit=limit;
this.offset=limit;
this.initLoadMore();
$('.loading').show();
},
getOld:
function() {
$('.loading').show();
$.ajax({
type: "GET",
url: location.href,
data: "limit=" + this.limit + "&offset=" + this.offset + "&ref=" + this.ref,
complete: function(){ $('.loading').hide()},
dataType: "script"});
},
init:
function(ref, limit) {
$(".day-commits-table li.commit").live('click', function(e){
if(e.target.nodeName != "A") {
location.href = $(this).attr("url");
e.stopPropagation();
return false;
}
});
append:
function(count, html) {
$("#commits_list").append(html);
if(count > 0) {
this.offset += count;
this.ref=ref;
this.limit=limit;
this.offset=limit;
this.initLoadMore();
}
},
$('.loading').show();
},
getOld:
function() {
$('.loading').show();
$.ajax({
type: "GET",
url: location.href,
data: "limit=" + this.limit + "&offset=" + this.offset + "&ref=" + this.ref,
complete: function(){ $('.loading').hide()},
dataType: "script"});
},
initLoadMore:
function() {
$(window).bind('scroll', function(){
if($(window).scrollTop() == $(document).height() - $(window).height()){
$(window).unbind('scroll');
CommitsList.getOld();
append:
function(count, html) {
$("#commits_list").append(html);
if(count > 0) {
this.offset += count;
this.initLoadMore();
}
});
}
},
initLoadMore:
function() {
$(window).bind('scroll', function(){
if($(window).scrollTop() == $(document).height() - $(window).height()){
$(window).unbind('scroll');
CommitsList.getOld();
}
});
}
}
var Loader = {
img_src: "/assets/ajax-loader.gif",
html:
function(width) {
img = $("<img>");
img.attr("width", width);
img.attr("src", this.img_src);
return img;
}
}
var MergeRequest = {
diffs_loaded: false,
commits_loaded: false,
init:
function() {
$(".merge-tabs a").live("click", function() {
$(".merge-tabs a").removeClass("active");
$(this).addClass("active");
});
$(".merge-tabs a.merge-notes-tab").live("click", function() {
$(".merge-request-commits, .merge-request-diffs").hide();
$(".merge-request-notes").show();
});
$(".merge-tabs a.merge-commits-tab").live("click", function() {
if(!MergeRequest.commits_loaded) {
MergeRequest.loadCommits();
}
$(".merge-request-notes, .merge-request-diffs").hide();
$(".merge-request-commits").show();
});
$(".merge-tabs a.merge-diffs-tab").live("click", function() {
if(!MergeRequest.diffs_loaded) {
MergeRequest.loadDiff();
}
$(".merge-request-notes, .merge-request-commits").hide();
$(".merge-request-diffs").show();
});
},
loadCommits:
function() {
$(".dashboard-loader").show();
$.ajax({
type: "GET",
url: $(".merge-commits-tab").attr("data-url"),
complete: function(){
MergeRequest.commits_loaded = true;
$(".merge-request-notes, .merge-request-diffs").hide();
$(".dashboard-loader").hide()},
dataType: "script"});
},
loadDiff:
function() {
$(".dashboard-loader").show();
$.ajax({
type: "GET",
url: $(".merge-diffs-tab").attr("data-url"),
complete: function(){
MergeRequest.diffs_loaded = true;
$(".merge-request-notes, .merge-request-commits").hide();
$(".dashboard-loader").hide()},
dataType: "script"});
}
}
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
$(document).ready(function(){
$('#tree-slider td.tree-item-file-name a, #tree-breadcrumbs a').live("click", function() {
history.pushState({ path: this.path }, '', this.href)
})
$("#tree-slider tr.tree-item").live('click', function(e){
if(e.target.nodeName != "A") {
e.stopPropagation();
link = $(this).find("td.tree-item-file-name a")
link.click();
return false;
}
});
$("#projects-list .project").live('click', function(e){
if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") {
location.href = $(this).attr("url");
e.stopPropagation();
return false;
}
});
$("#issues-table .issue").live('click', function(e){
if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") {
location.href = $(this).attr("url");
e.stopPropagation();
return false;
}
});
$(document).keypress(function(e) {
if( $(e.target).is(":input") ) return;
switch(e.which) {
case 115: focusSearch();
e.preventDefault();
var ProjectsList = {
limit:0,
offset:0,
init:
function(limit) {
this.limit=limit;
this.offset=limit;
this.initLoadMore();
},
getOld:
function() {
$('.loading').show();
$.ajax({
type: "GET",
url: location.href,
data: "limit=" + this.limit + "&offset=" + this.offset,
complete: function(){ $('.loading').hide()},
dataType: "script"});
},
append:
function(count, html) {
$(".tile").append(html);
if(count > 0) {
this.offset += count;
this.initLoadMore();
}
},
initLoadMore:
function() {
$(window).bind('scroll', function(){
if($(window).scrollTop() == $(document).height() - $(window).height()){
$(window).unbind('scroll');
$('.loading').show();
ProjectsList.getOld();
}
});
}
});
});
function focusSearch() {
$("#search").focus();
}
function taggifyForm(){
var tag_field = $('#tag_field').tagify();
tag_field.tagify('inputField').autocomplete({
source: '/tags.json'
});
$('form').submit( function() {
var tag_field = $('#tag_field')
tag_field.val( tag_field.tagify('serialize') );
return true;
});
}
function backToMembers(){
$("#team_member_new").hide("slide", { direction: "right" }, 150, function(){
$("#team-table").show("slide", { direction: "left" }, 150, function() {
$("#team_member_new").remove();
$(".add_new").show();
});
});
}
/**
* Tree slider for code browse
*
*/
var Tree = {
init:
function() {
(new Image).src = "ajax-loader-facebook.gif";
$('#tree-slider td.tree-item-file-name a, #tree-breadcrumbs a').live("click", function() {
history.pushState({ path: this.path }, '', this.href)
$("#tree-content-holder").hide("slide", { direction: "left" }, 150)
})
$("#tree-slider tr.tree-item").live('click', function(e){
if(e.target.nodeName != "A") {
link = $(this).find("td.tree-item-file-name a");
link.trigger("click");
}
});
$('#tree-slider td.tree-item-file-name a, #tree-breadcrumbs a').live({
"ajax:beforeSend": function() { $('.tree_progress').addClass("loading"); },
"ajax:complete": function() { $('.tree_progress').removeClass("loading"); }
});
}
}
......@@ -7,45 +7,5 @@
*= require jquery-ui/jquery.tagify
*= require chosen
*= require_self
*= require_tree .
*= require common
*/
/** COLORS **/
.cgray { color:gray; }
.cred { color:#D12F19; }
.cgreen { color:#44aa22; }
/** COMMON STYLES **/
.left {
float:left;
}
.right {
float:right;
}
.width-50p{
width:50%;
}
.width-49p{
width:49%;
}
.width-30p{
width:30%;
}
.width-65p{
width:65%;
}
.width-100p{
width:100%;
}
.append-bottom-10 {
margin-bottom:10px;
}
.prepend-top-10 {
margin-top:10px;
}
.no-borders {
border:none;
}
.no-padding {
padding:0 !important;
}
/* Commit Page */
body.project-page.commits-page .commit-info{float: right;}
body.project-page.commits-page .commit-info data{
padding: 4px 10px;
font-size: 11px;
}
body.project-page.commits-page .commit-info data.commit-button{
background-image: -webkit-gradient(linear, 0 0, 0 26, color-stop(0.192, #fff), to(#f4f4f4));
background-image: -webkit-linear-gradient(#fff 19.2%, #f4f4f4);
background-image: -moz-linear-gradient(#fff 19.2%, #f4f4f4);
background-image: -o-linear-gradient(#fff 19.2%, #f4f4f4);
box-shadow: 0 -1px 0 white inset;
display: block;
border: 1px solid #eee;
border-radius: 5px;
margin-bottom: 2px;
position: relative;
padding-right: 20px;
}
body.project-page.commits-page .commit-button i{
background: url('images.png') no-repeat -138px -27px;
width: 6px;
height: 9px;
float: right;
position: absolute;
top: 6px;
right: 5px;
}
body.project-page.commits-page .commits-date {display: block; width: 100%; margin-bottom: 20px}
body.project-page.commits-page .commits-date .data {padding: 0}
body.project-page.commits-page a.commit{padding: 10px; border-bottom: 1px solid #eee; overflow: hidden; display: block;}
body.project-page.commits-page .commits-date a.commit {padding: 10px; border-bottom: none; overflow: hidden; display: block;}
body.project-page.commits-page .commits-date a.commit:last-child{border-bottom: 0}
body.project-page.commits-page .commits-date a.commit img{float: left; margin-right: 10px;}
body.project-page.commits-page .commits-date a.commit span.commit-title{display: block;}
body.project-page.commits-page .commits-date a.commit span.commit-title{margin-bottom: 10px}
body.project-page.commits-page .commits-date a.commit span.commit-author{color: #999; font-weight: normal; font-style: italic;}
body.project-page.commits-page .commits-date a.commit span.commit-author strong{font-weight: bold; font-style: normal;}
/* eo Commit Page */
/** Commit diff view **/
.diff_file {
border:1px solid #CCC;
......@@ -37,7 +78,7 @@
padding:0px;
border:none;
background:#F7F7F7;
color:#333;
color:#aaa;
padding: 0px 5px;
border-right: 1px solid #ccc;
text-align:right;
......@@ -48,6 +89,7 @@
float:left;
width:35px;
font-weight:normal;
color:#aaa;
&:hover {
text-decoration:underline;
}
......@@ -96,3 +138,54 @@ ul.bordered-list {
}
ul.bordered-list li:last-child { border:none }
.line_holder {
&:hover {
td {
background: #FFFFCF !important;
}
}
}
.per_line_form {
font-family: "Helvetica", sans-serif;
background: #2FA0BB;
td {
padding:0;
}
form {
margin:5px;
width: 756px;
border: 1px solid #CCC;
padding: 20px;
background: white;
}
}
tr.line_notes_row {
font-family: "Helvetica", sans-serif;
&:hover {
background:none;
}
td {
margin:0px;
padding:0px;
border-bottom:1px solid #DEE2E3;
ul {
display:block;
list-style:none;
margin:0px;
padding:0px;
li {
border-top:1px solid #DEE2E3;
padding:10px;
}
}
}
}
$text_color:#222;
$lite_text_color: #666;
$link_color:#111;
$active_link_color:#2FA0BB;
$active_bg_color:#79C3E0;
$active_bd_color: #2FA0BB;
$border_color:#CCC;
$lite_border_color:#EEE;
$app_width:980px;
$app_padding:20px;
$bg_color: #FFF;
$styled_border_color: #2FA0BB;
/** MIXINS **/
@mixin round-borders-bottom($radius) {
border-top: 1px solid #eaeaea;
-moz-border-radius-bottomright: $radius;
-moz-border-radius-bottomleft: $radius;
border-bottom-right-radius: $radius;
border-bottom-left-radius: $radius;
-webkit-border-bottom-left-radius: $radius;
-webkit-border-bottom-right-radius: $radius;
}
@mixin round-borders-top($radius) {
border-top: 1px solid #eaeaea;
-moz-border-radius-topright: $radius;
-moz-border-radius-topleft: $radius;
border-top-right-radius: $radius;
border-top-left-radius: $radius;
-webkit-border-top-left-radius: $radius;
-webkit-border-top-right-radius: $radius;
}
@mixin round-borders-all($radius) {
border: 1px solid #eaeaea;
-moz-border-radius: $radius;
-webkit-border-radius: $radius;
border-radius: $radius;
}
/** COLORS **/
.cgray { color:gray; }
.cred { color:#D12F19; }
.cgreen { color:#44aa22; }
/** COMMON STYLES **/
.left {
float:left;
}
.right {
float:right;
}
.width-50p{
width:50%;
}
.width-49p{
width:49%;
}
.width-30p{
width:30%;
}
.width-65p{
width:65%;
}
.width-100p{
width:100%;
}
.append-bottom-10 {
margin-bottom:10px;
}
.append-bottom-20 {
margin-bottom:20px;
}
.prepend-top-10 {
margin-top:10px;
}
.no-borders {
border:none;
}
.no-padding {
padding:0 !important;
}
/* General */
body.collapsed {
background-color: $bg_color;
#container{
margin: auto;
margin-top:51px;
width: $app_width;
border-top: 0;
background-color: $bg_color;
}
}
a {
color: $link_color;
}
@import "style.scss";
@import "projects.css.scss";
@import "commits.css.scss";
@import "notes.css.scss";
@import "merge_requests.css.scss";
@import "highlight.css.scss";
@import "highlight.black.css.scss";
@import "issues.css.scss";
@import "commits.css.scss";
@import "top_panel.scss";
@import "dashboard.scss";
@import "tree.scss";
body.dashboard-page h2.icon span{ background-position: 9px -69px; }
body.dashboard-page header{margin-bottom: 0}
body.dashboard-page .news-feed{margin-left: 285px; min-height: 600px; margin-top: 20px; margin-right:2px; padding:20px;}
body.dashboard-page .dashboard-content{ position: relative; float: left; width: 100%; height: 100%; }
body.dashboard-page .news-feed h2{float: left;}
body.dashboard-page aside{
min-height: 820px; position: relative; top: 0; bottom: 0; right: 0; width: 260px; float: left; border-right: 1px solid $border_color; padding:20px; padding-right:0;
h4{margin: 0; border-bottom: 1px solid #ccc; padding: 20px 20px 20px 0px; font-size: 11px; font-weight: bold; text-transform: uppercase;}
h4 a.button-small{float: right; text-transform: none; border-radius: 4px; margin-right: 2%; margin-top: -4px; display: block;}
.project-list {list-style: none; margin: 0; padding: 0;}
.project-list li a {background: white; color: #{$blue_link}; display: block; border-bottom: 1px solid $lite_border_color; padding: 14px 6% 14px 0px;}
.project-list li a span.project-name{font-size: 14px; display: block; margin-bottom: 8px}
.project-list li a span.time{color: #666; font-weight: normal; font-size: 11px}
.project-list li a span.arrow{float: right; background: #E3E5EA; padding: 10px; border-radius: 5px; margin-top: 2px; text-shadow: none; color: #999}
}
body.dashboard-page .news-feed .project-updates {
margin-bottom: 20px; display: block; width: 100%;
.data{ padding: 0}
a.project-update {padding: 10px; overflow: hidden; display: block;}
a.project-update:last-child{border-bottom: 0}
a.project-update img{float: left; margin-right: 10px;}
a.project-update span.update-title, .dashboard-page .news-feed .project-updates li a span.update-author{display: block;}
a.project-update span.update-title{margin-bottom: 10px}
a.project-update span.update-author{color: #999; font-weight: normal; font-style: italic;}
a.project-update span.update-author strong{font-weight: bold; font-style: normal;}
}
/* eo Dashboard Page */
......@@ -11,8 +11,8 @@
}
.issues_filter {
margin-top:10px;
.left {
margin:10px 0;
.left {
margin-right:15px;
}
}
......@@ -72,3 +72,13 @@ body.project-page .edit_snippet table td
}
}
#issues-table {
tr {
border-top: 1px solid $lite_border_color;
&:first-child {
border:none;
}
}
}
......@@ -42,3 +42,11 @@ body.project-page #notes-list .note span.note-author strong{font-weight: bold; f
.note .note-title { margin-left:55px; }
p.notify_controls input{
margin: 5px;
}
p.notify_controls span{
font-weight: 700;
}
This diff is collapsed.
This diff is collapsed.
.main_links {
width:130px;
float:left;
a {
float:left;
}
}
.dashboard_links {
padding:7px;
float:left;
a {
margin: 0 14px;
float: left;
font-size: 14px;
&.active {
color:$active_link_color;
}
&:hover {
color:$active_link_color;
}
}
}
.top-tabs {
margin: 0;
padding: 5px;
font-size: 14px;
padding-bottom:10px;
margin-bottom:20px;
height:26px;
border-bottom:1px solid #ccc;
.tab {
font-weight: bold;
background:none;
padding: 10px;
float:left;
padding-left:0px;
padding-right:40px;
&.active {
color: $active_link_color;
}
}
}
body header {
position:absolute;
width:100%;
padding:0;
margin:0;
top:0;
left:0;
background: #999; /* for non-css3 browsers */
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFF', endColorstr='#EAEAEA'); /* for IE */
background: -webkit-gradient(linear, left top, left bottom, from(#FFFFFF), to(#EAEAEA)); /* for webkit browsers */
background: -moz-linear-gradient(top, #FFFFFF, #EAEAEA); /* for firefox 3.6+ */
background: -o-linear-gradient(top, #FFFFFF, #EAEAEA); /* for firefox 3.6+ */
border-bottom: 1px solid #ccc;
height:50px;
.wrapper {
margin:auto;
width:$app_width;
position:relative;
.top_panel_content {
padding:10px $app_padding;
}
}
.project_name {
float:left;
width:235px;
margin-right:30px;
font-size:16px;
font-weight:bold;
padding:8px;
color:#333;
}
.git_url_wrapper {
padding:0px;
margin:0px;
float:left;
.git-url {
padding:0px;
margin:0px;
font-size: 12px;
margin-right:10px;
border-radius: 4px;
-moz-border-radius: 4px;
color: #666;
border: 1px solid #AAA;
padding: 0 10px 0 30px;
background: transparent url('images.png') no-repeat 8px -42px;
width: 160px;
height:26px;
}
}
}
.top_panel_holder .chzn-container {
position:relative;
.chzn-drop {
margin:7px 0;
border: 1px solid #CCC;
min-width: 300px;
.chzn-results {
max-height:300px;
}
}
.chzn-single {
background:transparent;
-moz-border-radius: 4px;
border-radius: 4px;
div {
background:transparent;
border-left:none;
}
span {
font-weight: normal;
}
}
}
.rss-icon {
margin:0 15px;
padding:3px;
border:1px solid #AAA;
border-radius:3px;
float:left;
}
#tree-breadcrumbs {
div {
margin:0;
margin-bottom:20px;
float:left;
font-size:14px;
}
}
.tree_progress {
float:left;
width:16px;
height:16px;
margin:2px 6px;
&.loading {
background-position: 0px 0px;
background: url("ajax-loader-facebook.gif") no-repeat;
}
}
/** FILE CONTENT VIEW **/
.view_file_content{
.old_line, .new_line {
background:#ECECEC;
color:#777;
width:15px;
float:left;
padding: 0px 10px;
border-right: 1px solid #ccc;
}
.old_line{
display:none;
}
}
.view_file .view_file_header,
.diff_file .diff_file_header {
background-image: -webkit-gradient(linear, 0 0, 0 26, color-stop(0.076, #fefefe), to(#F6F7F8));
background-image: -webkit-linear-gradient(#fefefe 7.6%, #F6F7F8);
background-image: -moz-linear-gradient(#fefefe 7.6%, #F6F7F8);
background-image: -o-linear-gradient(#fefefe 7.6%, #F6F7F8);
margin: 0;
font-weight: normal;
font-weight: bold;
text-align: left;
color: #666;
border-bottom: 1px solid #DEE2E3;
padding: 7px 10px;
.mode_text,
.file_icon {
margin-right:15px;
padding-right:15px;
border-right:1px solid $lite_border_color;
float:left;
color:#aaa;
}
.file_icon {
padding-left:15px;
}
}
.view_file {
border:1px solid #CCC;
margin-bottom:1em;
.view_file_content {
background:#fff;
color:#514721;
font-size: 11px;
}
.view_file_content_image {
background:#eee;
text-align:center;
img {
padding:100px;
max-width:300px;
}
}
}
td.code {
width: 100%;
.highlight {
margin-left: 55px;
overflow:auto;
overflow-y:hidden;
border-left: 1px solid #DEE2E3;
background: white;
}
}
.highlight pre {
white-space: pre;
word-wrap:normal;
}
table.highlighttable {
border: none;
background: #F7F7F7;
}
body.project-page table.highlighttable td { border: none }
table.highlighttable tr:hover { background:none;}
table.highlighttable pre{
line-height:16px !important;
font-size:12px !important;
}
table.highlighttable .linenodiv pre {
text-align: right;
padding-right: 4px;
color:#888;
}
.tree-item {
&:hover {
background: #FFFFCF;
}
}
......@@ -9,6 +9,12 @@ class Admin::ProjectsController < ApplicationController
def show
@admin_project = Project.find_by_code(params[:id])
@users = if @admin_project.users.empty?
User
else
User.not_in_project(@admin_project)
end.all
end
def new
......@@ -19,6 +25,19 @@ class Admin::ProjectsController < ApplicationController
@admin_project = Project.find_by_code(params[:id])
end
def team_update
@admin_project = Project.find_by_code(params[:id])
UsersProject.bulk_import(
@admin_project,
params[:user_ids],
params[:project_access],
params[:repo_access]
)
redirect_to [:admin, @admin_project], notice: 'Project was successfully updated.'
end
def create
@admin_project = Project.new(params[:project])
@admin_project.owner = current_user
......
......@@ -27,7 +27,6 @@ class Admin::UsersController < ApplicationController
respond_to do |format|
if @admin_user.save
Notify.new_user_email(@admin_user, params[:user][:password]).deliver
format.html { redirect_to [:admin, @admin_user], notice: 'User was successfully created.' }
format.json { render json: @admin_user, status: :created, location: @admin_user }
else
......@@ -39,7 +38,7 @@ class Admin::UsersController < ApplicationController
def update
admin = params[:user].delete("admin")
if params[:user][:password].empty?
if params[:user][:password].blank?
params[:user].delete(:password)
params[:user].delete(:password_confirmation)
end
......
class ApplicationController < ActionController::Base
before_filter :authenticate_user!
before_filter :set_current_user_for_mailer
protect_from_forgery
helper_method :abilities, :can?
......@@ -19,6 +20,10 @@ class ApplicationController < ActionController::Base
end
end
def set_current_user_for_mailer
MailerObserver.current_user = current_user
end
def abilities
@abilities ||= Six.new
end
......
......@@ -27,6 +27,8 @@ class CommitsController < ApplicationController
@notes = project.commit_notes(@commit).fresh.limit(20)
@note = @project.build_commit_note(@commit)
@line_notes = project.commit_line_notes(@commit)
respond_to do |format|
format.html
format.js { respond_with_notes }
......
class DashboardController < ApplicationController
respond_to :html
def index
@projects = current_user.projects.all
@active_projects = @projects.select(&:last_activity_date).sort_by(&:last_activity_date).reverse
@active_projects = @projects.select(&:repo_exists?).select(&:last_activity_date_cached).sort_by(&:last_activity_date_cached).reverse
end
# Get authored or assigned open merge requests
def merge_requests
@projects = current_user.projects.all
@merge_requests = MergeRequest.where("author_id = :id or assignee_id = :id", :id => current_user.id).opened.order("created_at DESC").limit(40)
end
# Get only assigned issues
def issues
@projects = current_user.projects.all
@user = current_user
@issues = current_user.assigned_issues.opened.order("created_at DESC").limit(40)
@issues = @issues.includes(:author, :project)
respond_to do |format|
format.html
format.atom { render :layout => false }
end
end
end
class DeployKeysController < ApplicationController
respond_to :html
layout "project"
before_filter :project
# Authorize
before_filter :add_project_abilities
before_filter :authorize_admin_project!
def project
@project ||= Project.find_by_code(params[:project_id])
end
def index
@keys = @project.deploy_keys.all
end
def show
@key = @project.deploy_keys.find(params[:id])
end
def new
@key = @project.deploy_keys.new
respond_with(@key)
end
def create
@key = @project.deploy_keys.new(params[:key])
if @key.save
redirect_to project_deploy_keys_path(@project)
else
render "new"
end
end
def destroy
@key = @project.deploy_keys.find(params[:id])
@key.destroy
respond_to do |format|
format.html { redirect_to project_deploy_keys_url }
format.js { render :nothing => true }
end
end
end
class HelpController < ApplicationController
def index
end
end
class HooksController < ApplicationController
before_filter :authenticate_user!
before_filter :project
layout "project"
# Authorize
before_filter :add_project_abilities
before_filter :authorize_read_project!
before_filter :authorize_admin_project!, :only => [:new, :create, :destroy]
respond_to :html
def index
@hooks = @project.web_hooks
end
def new
@hook = @project.web_hooks.new
end
def create
@hook = @project.web_hooks.new(params[:hook])
@hook.save
if @hook.valid?
redirect_to project_hook_path(@project, @hook)
else
render :new
end
end
def test
@hook = @project.web_hooks.find(params[:id])
commits = @project.commits(@project.default_branch, nil, 3)
data = @project.web_hook_data(commits.last.id, commits.first.id, "refs/heads/#{@project.default_branch}")
@hook.execute(data)
redirect_to :back
end
def show
@hook = @project.web_hooks.find(params[:id])
end
def destroy
@hook = @project.web_hooks.find(params[:id])
@hook.destroy
redirect_to project_hooks_path(@project)
end
end
......@@ -6,8 +6,18 @@ class IssuesController < ApplicationController
# Authorize
before_filter :add_project_abilities
# Allow read any issue
before_filter :authorize_read_issue!
before_filter :authorize_write_issue!, :only => [:new, :create, :close, :edit, :update, :sort]
# Allow write(create) issue
before_filter :authorize_write_issue!, :only => [:new, :create]
# Allow modify issue
before_filter :authorize_modify_issue!, :only => [:close, :edit, :update, :sort]
# Allow destroy issue
before_filter :authorize_admin_issue!, :only => [:destroy]
respond_to :js, :html
......@@ -57,10 +67,7 @@ class IssuesController < ApplicationController
def create
@issue = @project.issues.new(params[:issue])
@issue.author = current_user
if @issue.save && @issue.assignee != current_user
Notify.new_issue_email(@issue).deliver
end
@issue.save
respond_with(@issue)
end
......@@ -80,6 +87,7 @@ class IssuesController < ApplicationController
@issue.destroy
respond_to do |format|
format.html { redirect_to project_issues_path }
format.js { render :nothing => true }
end
end
......@@ -115,4 +123,13 @@ class IssuesController < ApplicationController
def issue
@issue ||= @project.issues.find(params[:id])
end
def authorize_modify_issue!
can?(current_user, :modify_issue, @issue) ||
@issue.assignee == current_user
end
def authorize_admin_issue!
can?(current_user, :admin_issue, @issue)
end
end
......@@ -6,6 +6,10 @@ class KeysController < ApplicationController
@keys = current_user.keys.all
end
def show
@key = current_user.keys.find(params[:id])
end
def new
@key = current_user.keys.new
......
......@@ -6,11 +6,28 @@ class MergeRequestsController < ApplicationController
# Authorize
before_filter :add_project_abilities
before_filter :authorize_read_project!
before_filter :authorize_write_project!, :only => [:new, :create, :edit, :update]
# Allow read any merge_request
before_filter :authorize_read_merge_request!
# Allow write(create) merge_request
before_filter :authorize_write_merge_request!, :only => [:new, :create]
# Allow modify merge_request
before_filter :authorize_modify_merge_request!, :only => [:close, :edit, :update, :sort]
# Allow destroy merge_request
before_filter :authorize_admin_merge_request!, :only => [:destroy]
def index
@merge_requests = @project.merge_requests
@merge_requests = case params[:f].to_i
when 2 then @merge_requests.closed
else @merge_requests.opened
end
@merge_requests = @merge_requests.includes(:author, :project)
end
def show
......@@ -30,14 +47,12 @@ class MergeRequestsController < ApplicationController
def commits
@commits = @project.repo.commits_between(@merge_request.target_branch, @merge_request.source_branch).map {|c| Commit.new(c)}
render :template => "merge_requests/_commits", :layout => false
end
def diffs
@diffs = @merge_request.diffs
@commit = @merge_request.last_commit
render :template => "merge_requests/_diffs", :layout => false
@line_notes = []
end
def new
......@@ -88,4 +103,13 @@ class MergeRequestsController < ApplicationController
def merge_request
@merge_request ||= @project.merge_requests.find(params[:id])
end
def authorize_modify_merge_request!
can?(current_user, :modify_merge_request, @merge_request) ||
@merge_request.assignee == current_user
end
def authorize_admin_merge_request!
can?(current_user, :admin_merge_request, @merge_request)
end
end
......@@ -3,6 +3,8 @@ class NotesController < ApplicationController
# Authorize
before_filter :add_project_abilities
before_filter :authorize_read_note!
before_filter :authorize_write_note!, :only => [:create]
respond_to :js
......@@ -10,10 +12,9 @@ class NotesController < ApplicationController
def create
@note = @project.notes.new(params[:note])
@note.author = current_user
if @note.save
notify if params[:notify] == '1'
end
@note.notify = true if params[:notify] == '1'
@note.notify_author = true if params[:notify_author] == '1'
@note.save
respond_to do |format|
format.html {redirect_to :back}
......@@ -33,22 +34,4 @@ class NotesController < ApplicationController
end
end
protected
def notify
@project.users.reject { |u| u.id == current_user.id } .each do |u|
case @note.noteable_type
when "Commit" then
Notify.note_commit_email(u, @note).deliver
when "Issue" then
Notify.note_issue_email(u, @note).deliver
when "MergeRequest"
true # someone should write email notification
when "Snippet"
true
else
Notify.note_wall_email(u, @note).deliver
end
end
end
end
......@@ -4,10 +4,14 @@ class ProfileController < ApplicationController
@user = current_user
end
def social_update
def design
@user = current_user
end
def update
@user = current_user
@user.update_attributes(params[:user])
redirect_to [:profile]
redirect_to :back
end
def password
......
......@@ -9,12 +9,10 @@ class ProjectsController < ApplicationController
before_filter :authorize_read_project!, :except => [:index, :new, :create]
before_filter :authorize_admin_project!, :only => [:edit, :update, :destroy]
before_filter :require_non_empty_project, :only => [:blob, :tree, :graph]
before_filter :load_refs, :only => :tree # load @branch, @tag & @ref
def index
source = current_user.projects
source = source.tagged_with(params[:tag]) unless params[:tag].blank?
@projects = source.all
@limit, @offset = (params[:limit] || 16), (params[:offset] || 0)
@projects = current_user.projects.limit(@limit).offset(@offset)
end
def new
......@@ -59,7 +57,7 @@ class ProjectsController < ApplicationController
def update
respond_to do |format|
if project.update_attributes(params[:project])
format.html { redirect_to project, :notice => 'Project was successfully updated.' }
format.html { redirect_to info_project_path(project), :notice => 'Project was successfully updated.' }
format.js
else
format.html { render action: "edit" }
......@@ -71,7 +69,14 @@ class ProjectsController < ApplicationController
def show
return render "projects/empty" unless @project.repo_exists? && @project.has_commits?
limit = (params[:limit] || 20).to_i
@activities = @project.cached_updates(limit)
@activities = @project.activities(limit)#updates_wo_repo(limit)
end
def files
@notes = @project.notes.where("attachment != 'NULL'").order("created_at DESC").limit(100)
end
def info
end
#
......@@ -94,7 +99,11 @@ class ProjectsController < ApplicationController
end
def destroy
# Disable the UsersProject update_repository call, otherwise it will be
# called once for every person removed from the project
UsersProject.skip_callback(:destroy, :after, :update_repository)
project.destroy
UsersProject.set_callback(:destroy, :after, :update_repository)
respond_to do |format|
format.html { redirect_to projects_url }
......
class RepositoriesController < ApplicationController
before_filter :project
# Authorize
before_filter :add_project_abilities
before_filter :authorize_read_project!
before_filter :require_non_empty_project
layout "project"
def show
@activities = @project.commits_with_refs(20)
end
def branches
@branches = @project.repo.heads.sort_by(&:name)
end
def tags
@tags = @project.repo.tags.sort_by(&:name).reverse
end
end
......@@ -5,8 +5,18 @@ class SnippetsController < ApplicationController
# Authorize
before_filter :add_project_abilities
# Allow read any snippet
before_filter :authorize_read_snippet!
before_filter :authorize_write_snippet!, :only => [:new, :create, :close, :edit, :update, :sort]
# Allow write(create) snippet
before_filter :authorize_write_snippet!, :only => [:new, :create]
# Allow modify snippet
before_filter :authorize_modify_snippet!, :only => [:edit, :update]
# Allow destroy snippet
before_filter :authorize_admin_snippet!, :only => [:destroy]
respond_to :html
......@@ -60,4 +70,14 @@ class SnippetsController < ApplicationController
redirect_to project_snippets_path(@project)
end
protected
def authorize_modify_snippet!
can?(current_user, :modify_snippet, @snippet)
end
def authorize_admin_snippet!
can?(current_user, :admin_snippet, @snippet)
end
end
......@@ -5,7 +5,7 @@ class TeamMembersController < ApplicationController
# Authorize
before_filter :add_project_abilities
before_filter :authorize_read_project!
before_filter :authorize_admin_project!, :only => [:new, :create, :destroy, :update]
before_filter :authorize_admin_project!, :except => [:show]
def show
@team_member = project.users_projects.find(params[:id])
......@@ -18,7 +18,11 @@ class TeamMembersController < ApplicationController
def create
@team_member = UsersProject.new(params[:team_member])
@team_member.project = project
@team_member.save
if @team_member.save
redirect_to team_project_path(@project)
else
render "new"
end
end
def update
......
......@@ -6,7 +6,7 @@ class TreeDecorator < ApplicationDecorator
part_path = ""
parts = path.split("\/")
parts = parts[0...-1] if is_blob?
#parts = parts[0...-1] if is_blob?
yield(h.link_to("..", "#", :remote => :true)) if parts.count > max_links
......@@ -32,4 +32,13 @@ class TreeDecorator < ApplicationDecorator
def history_path
h.project_commits_path(project, :path => path, :ref => ref)
end
def mb_size
size = (tree.size / 1024)
if size < 1024
"#{size} KB"
else
"#{size/1024} MB"
end
end
end
require 'digest/md5'
module ApplicationHelper
def gravatar_icon(user_email)
def gravatar_icon(user_email, size = 40)
gravatar_host = request.ssl? ? "https://secure.gravatar.com" : "http://www.gravatar.com"
"#{gravatar_host}/avatar/#{Digest::MD5.hexdigest(user_email)}?s=40&d=identicon"
"#{gravatar_host}/avatar/#{Digest::MD5.hexdigest(user_email)}?s=#{size}&d=identicon"
end
def fixed_mode?
......@@ -48,11 +48,11 @@ module ApplicationHelper
def grouped_options_refs(destination = :tree)
options = [
["Branch", @repo.heads.map(&:name) ],
["Branch", @project.repo.heads.map(&:name) ],
[ "Tag", @project.tags ]
]
grouped_options_for_select(options, @ref)
grouped_options_for_select(options, @ref || @project.default_branch)
end
def markdown(text)
......@@ -82,4 +82,15 @@ module ApplicationHelper
[projects, default_nav, project_nav].flatten.to_json
end
def project_layout
@project && !@project.new_record?
end
def profile_layout
controller.controller_name == "dashboard" || current_page?(projects_path) || controller.controller_name == "profile" || controller.controller_name == "keys"
end
def help_layout
controller.controller_name == "help"
end
end
module CommitsHelper
include Utils::CharEncode
def old_line_number(line, i)
end
......@@ -25,4 +23,30 @@ module CommitsHelper
link_to "More", project_commits_path(@project, :offset => offset.to_i + limit.to_i, :limit => limit),
:remote => true, :class => "lite_button vm", :style => "text-align:center; width:930px; ", :id => "more-commits-link"
end
def commit_msg_with_link_to_issues(project, message)
return '' unless message
out = ''
message.split(/(#[0-9]+)/m).each do |m|
if m =~ /(#([0-9]+))/m
begin
issue = project.issues.find($2)
out += link_to($1, project_issue_path(project, $2))
rescue
out += $1
end
else
out += m
end
end
preserve out
end
def build_line_code(line, index, line_new, line_old)
if diff_line_class(line) == "new"
"NEW_#{index}_#{line_new}"
else
"OLD_#{index}_#{line_old}"
end
end
end
......@@ -10,6 +10,7 @@ module DashboardHelper
when "Issue" then project_issue_path(project, note.noteable_id)
when "Snippet" then project_snippet_path(project, note.noteable_id)
when "Commit" then project_commit_path(project, :id => note.noteable_id)
when "MergeRequest" then project_merge_request_path(project, note.noteable_id)
else wall_project_path(project)
end
else wall_project_path(project)
......
......@@ -16,12 +16,26 @@ module ProjectsHelper
nil
end
# expires in 360 days
def switch_colorscheme_link(opts)
if cookies[:colorschema].blank?
link_to_function "paint it black!", "$.cookie('colorschema','black', {expires:360}); window.location.reload()", opts
else
link_to_function "paint it white!", "$.cookie('colorschema','', {expires:360}); window.location.reload()", opts
def project_tab_class
[:show, :files, :team, :edit, :update, :info].each do |action|
return "current" if current_page?(:controller => "projects", :action => action, :id => @project)
end
if controller.controller_name == "snippets" ||
controller.controller_name == "team_members"
"current"
end
end
def tree_tab_class
controller.controller_name == "refs" ?
"current" : nil
end
def repository_tab_class
if controller.controller_name == "repositories" ||
controller.controller_name == "hooks"
"current"
end
end
end
module UserIssuesHelper
end
module UserMergeRequestsHelper
end
......@@ -28,7 +28,16 @@ class Notify < ActionMailer::Base
@note = note
@project = note.project
@commit = @project.repo.commits(note.noteable_id).first
mail(:to => @user.email, :subject => "gitlab | #{@note.project.name} ")
return unless ( note.notify or ( note.notify_author and @commit.author.email == @user.email ) )
mail(:to => @user.email, :subject => "gitlab | note for commit | #{@note.project.name} ")
end
def note_merge_request_email(user, note)
@user = user
@note = note
@project = note.project
@merge_request = note.noteable
mail(:to => @user.email, :subject => "gitlab | note for merge request | #{@note.project.name} ")
end
def note_issue_email(user, note)
......@@ -36,6 +45,29 @@ class Notify < ActionMailer::Base
@note = note
@project = note.project
@issue = note.noteable
mail(:to => @user.email, :subject => "gitlab | #{@note.project.name} ")
mail(:to => @user.email, :subject => "gitlab | note for issue #{@issue.id} | #{@note.project.name} ")
end
def new_merge_request_email(merge_request)
@user = merge_request.assignee
@merge_request = merge_request
@project = merge_request.project
mail(:to => @user.email, :subject => "gitlab | new merge request | #{@merge_request.title} ")
end
def changed_merge_request_email(user, merge_request)
@user = user
@assignee_was ||= User.find(merge_request.assignee_id_was)
@merge_request = merge_request
@project = merge_request.project
mail(:to => @user.email, :subject => "gitlab | merge request changed | #{@merge_request.title} ")
end
def changed_issue_email(user, issue)
@user = user
@assignee_was ||= User.find(issue.assignee_id_was)
@issue = issue
@project = issue.project
mail(:to => @user.email, :subject => "gitlab | changed issue | #{@issue.title} ")
end
end
......@@ -19,7 +19,7 @@ class Ability
:read_team_member,
:read_merge_request,
:read_note
] if project.readers.include?(user)
] if project.allow_read_for?(user)
rules << [
:write_project,
......@@ -27,16 +27,18 @@ class Ability
:write_snippet,
:write_merge_request,
:write_note
] if project.writers.include?(user)
] if project.allow_write_for?(user)
rules << [
:modify_issue,
:modify_snippet,
:admin_project,
:admin_issue,
:admin_snippet,
:admin_team_member,
:admin_merge_request,
:admin_note
] if project.admins.include?(user)
] if project.allow_admin_for?(user)
rules.flatten
end
......@@ -48,6 +50,7 @@ class Ability
[
:"read_#{name}",
:"write_#{name}",
:"modify_#{name}",
:"admin_#{name}"
]
else
......
class Commit
include Utils::CharEncode
attr_accessor :commit
attr_accessor :head
attr_accessor :refs
delegate :message,
:committed_date,
......@@ -22,7 +22,7 @@ class Commit
end
def safe_message
encode(message)
message
end
def created_at
......@@ -30,11 +30,11 @@ class Commit
end
def author_email
encode(author.email)
author.email
end
def author_name
encode(author.name)
author.name
end
def prev_commit
......
......@@ -2,7 +2,7 @@ class Issue < ActiveRecord::Base
belongs_to :project
belongs_to :author, :class_name => "User"
belongs_to :assignee, :class_name => "User"
has_many :notes, :as => :noteable
has_many :notes, :as => :noteable, :dependent => :destroy
attr_protected :author, :author_id, :project, :project_id
......@@ -59,5 +59,6 @@ end
# closed :boolean default(FALSE), not null
# position :integer default(0)
# critical :boolean default(FALSE), not null
# branch_name :string(255)
#
class Key < ActiveRecord::Base
belongs_to :user
belongs_to :project
validates :title,
:presence => true,
......@@ -15,32 +16,38 @@ class Key < ActiveRecord::Base
after_destroy :repository_delete_key
def set_identifier
self.identifier = "#{user.identifier}_#{Time.now.to_i}"
if is_deploy_key
self.identifier = "deploy_#{project.code}_#{Time.now.to_i}"
else
self.identifier = "#{user.identifier}_#{Time.now.to_i}"
end
end
def update_repository
Gitlabhq::GitHost.system.new.configure do |c|
c.update_keys(identifier, key)
projects.each do |project|
c.update_project(project.path, project)
end
c.update_projects(projects)
end
end
def repository_delete_key
Gitlabhq::GitHost.system.new.configure do |c|
c.delete_key(identifier)
projects.each do |project|
c.update_project(project.path, project)
end
c.update_projects(projects)
end
end
def is_deploy_key
true if project_id
end
#projects that has this key
def projects
user.projects
if is_deploy_key
[project]
else
user.projects
end
end
end
# == Schema Information
......@@ -48,11 +55,12 @@ end
# Table name: keys
#
# id :integer not null, primary key
# user_id :integer not null
# user_id :integer
# created_at :datetime
# updated_at :datetime
# key :text
# title :string(255)
# identifier :string(255)
# project_id :integer
#
class MailerObserver < ActiveRecord::Observer
observe :issue, :user, :note, :merge_request
cattr_accessor :current_user
def after_create(model)
new_issue(model) if model.kind_of?(Issue)
new_user(model) if model.kind_of?(User)
new_note(model) if model.kind_of?(Note)
new_merge_request(model) if model.kind_of?(MergeRequest)
end
def after_update(model)
changed_merge_request(model) if model.kind_of?(MergeRequest)
changed_issue(model) if model.kind_of?(Issue)
end
protected
def new_issue(issue)
if issue.assignee != current_user
Notify.new_issue_email(issue).deliver
end
end
def new_user(user)
Notify.new_user_email(user, user.password).deliver
end
def new_note(note)
return unless note.notify or note.notify_author
note.project.users.reject { |u| u.id == current_user.id } .each do |u|
case note.noteable_type
when "Commit" then
Notify.note_commit_email(u, note).deliver
when "Issue" then
Notify.note_issue_email(u, note).deliver
when "MergeRequest" then
Notify.note_merge_request_email(u, note).deliver
when "Snippet"
true
else
Notify.note_wall_email(u, note).deliver
end
end
end
def new_merge_request(merge_request)
if merge_request.assignee != current_user
Notify.new_merge_request_email(merge_request).deliver
end
end
def changed_merge_request(merge_request)
if merge_request.assignee_id_changed?
recipients_ids = merge_request.assignee_id_was, merge_request.assignee_id
recipients_ids.delete current_user.id
User.find(recipients_ids).each do |user|
Notify.changed_merge_request_email(user, merge_request).deliver
end
end
if merge_request.closed_changed?
note = Note.new(:noteable => merge_request, :project => merge_request.project)
note.author = current_user
note.note = "_Status changed to #{merge_request.closed ? 'closed' : 'reopened'}_"
note.save()
end
end
def changed_issue(issue)
if issue.assignee_id_changed?
recipients_ids = issue.assignee_id_was, issue.assignee_id
recipients_ids.delete current_user.id
User.find(recipients_ids).each do |user|
Notify.changed_issue_email(user, issue).deliver
end
end
if issue.closed_changed?
note = Note.new(:noteable => issue, :project => issue.project)
note.author = current_user
note.note = "_Status changed to #{issue.closed ? 'closed' : 'reopened'}_"
note.save()
end
end
end
......@@ -2,7 +2,7 @@ class MergeRequest < ActiveRecord::Base
belongs_to :project
belongs_to :author, :class_name => "User"
belongs_to :assignee, :class_name => "User"
has_many :notes, :as => :noteable
has_many :notes, :as => :noteable, :dependent => :destroy
attr_protected :author, :author_id, :project, :project_id
......@@ -35,12 +35,27 @@ class MergeRequest < ActiveRecord::Base
end
def diffs
commit = project.commit(source_branch)
commits = project.repo.commits_between(target_branch, source_branch).map {|c| Commit.new(c)}
diffs = project.repo.diff(commits.first.prev_commit.id, commits.last.id)
diffs = project.repo.diff(commits.first.prev_commit.id, commits.last.id) rescue []
end
def last_commit
project.commit(source_branch)
end
end
# == Schema Information
#
# Table name: merge_requests
#
# id :integer not null, primary key
# target_branch :string(255) not null
# source_branch :string(255) not null
# project_id :integer not null
# author_id :integer
# assignee_id :integer
# title :string(255)
# closed :boolean default(FALSE), not null
# created_at :datetime
# updated_at :datetime
#
......@@ -13,6 +13,8 @@ class Note < ActiveRecord::Base
:prefix => true
attr_protected :author, :author_id
attr_accessor :notify
attr_accessor :notify_author
validates_presence_of :project
......@@ -35,6 +37,43 @@ class Note < ActiveRecord::Base
scope :inc_author, includes(:author)
mount_uploader :attachment, AttachmentUploader
def notify
@notify ||= false
end
def notify_author
@notify_author ||= false
end
def target
if noteable_type == "Commit"
project.commit(noteable_id)
else
noteable
end
# Temp fix to prevent app crash
# if note commit id doesnt exist
rescue
nil
end
def line_file_id
@line_file_id ||= line_code.split("_")[1].to_i if line_code
end
def line_type_id
@line_type_id ||= line_code.split("_").first if line_code
end
def line_number
@line_number ||= line_code.split("_").last.to_i if line_code
end
def for_line?(file_id, old_line, new_line)
line_file_id == file_id &&
((line_type_id == "NEW" && line_number == new_line) || (line_type_id == "OLD" && line_number == old_line ))
end
end
# == Schema Information
#
......@@ -49,5 +88,6 @@ end
# updated_at :datetime
# project_id :integer
# attachment :string(255)
# line_code :string(255)
#
......@@ -14,6 +14,8 @@ class Project < ActiveRecord::Base
has_many :users, :through => :users_projects
has_many :notes, :dependent => :destroy
has_many :snippets, :dependent => :destroy
has_many :deploy_keys, :dependent => :destroy, :foreign_key => "project_id", :class_name => "Key"
has_many :web_hooks, :dependent => :destroy
acts_as_taggable
......@@ -25,8 +27,8 @@ class Project < ActiveRecord::Base
validates :path,
:uniqueness => true,
:presence => true,
:format => { :with => /^[a-zA-Z0-9_\-]*$/,
:message => "only letters, digits & '_' '-' allowed" },
:format => { :with => /^[a-zA-Z0-9_\-\.]*$/,
:message => "only letters, digits & '_' '-' '.' allowed" },
:length => { :within => 0..255 }
validates :description,
......@@ -35,8 +37,8 @@ class Project < ActiveRecord::Base
validates :code,
:presence => true,
:uniqueness => true,
:format => { :with => /^[a-zA-Z0-9_\-]*$/,
:message => "only letters, digits & '_' '-' allowed" },
:format => { :with => /^[a-zA-Z0-9_\-\.]*$/,
:message => "only letters, digits & '_' '-' '.' allowed" },
:length => { :within => 3..255 }
validates :owner,
......@@ -52,6 +54,9 @@ class Project < ActiveRecord::Base
scope :public_only, where(:private_flag => false)
def self.active
joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC")
end
def self.access_options
{
......@@ -75,21 +80,76 @@ class Project < ActiveRecord::Base
:repo_exists?,
:commit,
:commits,
:commits_with_refs,
:tree,
:heads,
:commits_since,
:fresh_commits,
:commits_between,
:to => :repository, :prefix => nil
def to_param
code
end
def web_url
[GIT_HOST['host'], code].join("/")
end
def execute_web_hooks(oldrev, newrev, ref)
ref_parts = ref.split('/')
# Return if this is not a push to a branch (e.g. new commits)
return if ref_parts[1] !~ /heads/ || oldrev == "00000000000000000000000000000000"
data = web_hook_data(oldrev, newrev, ref)
web_hooks.each { |web_hook| web_hook.execute(data) }
end
def web_hook_data(oldrev, newrev, ref)
data = {
before: oldrev,
after: newrev,
ref: ref,
repository: {
name: name,
url: web_url,
description: description,
homepage: web_url,
private: private?
},
commits: []
}
commits_between(oldrev, newrev).each do |commit|
data[:commits] << {
id: commit.id,
message: commit.safe_message,
timestamp: commit.date.xmlschema,
url: "http://#{GIT_HOST['host']}/#{code}/commits/#{commit.id}",
author: {
name: commit.author_name,
email: commit.author_email
}
}
end
data
end
def team_member_by_name_or_email(email = nil, name = nil)
user = users.where("email like ? or name like ?", email, name).first
users_projects.find_by_user_id(user.id) if user
end
def team_member_by_id(user_id)
users_projects.find_by_user_id(user_id)
end
def fresh_merge_requests(n)
merge_requests.includes(:project, :author).order("created_at desc").first(n)
end
def fresh_issues(n)
issues.includes(:project, :author).order("created_at desc").first(n)
end
......@@ -107,7 +167,11 @@ class Project < ActiveRecord::Base
end
def commit_notes(commit)
notes.where(:noteable_id => commit.id, :noteable_type => "Commit")
notes.where(:noteable_id => commit.id, :noteable_type => "Commit", :line_code => nil)
end
def commit_line_notes(commit)
notes.where(:noteable_id => commit.id, :noteable_type => "Commit").where("line_code is not null")
end
def has_commits?
......@@ -136,7 +200,7 @@ class Project < ActiveRecord::Base
def repository_readers
keys = Key.joins({:user => :users_projects}).
where("users_projects.project_id = ? AND users_projects.repo_access = ?", id, Repository::REPO_R)
keys.map(&:identifier)
keys.map(&:identifier) + deploy_keys.map(&:identifier)
end
def repository_writers
......@@ -157,6 +221,18 @@ class Project < ActiveRecord::Base
@admins ||= users_projects.includes(:user).where(:project_access => PROJECT_RWA).map(&:user)
end
def allow_read_for?(user)
!users_projects.where(:user_id => user.id, :project_access => [PROJECT_R, PROJECT_RW, PROJECT_RWA]).empty?
end
def allow_write_for?(user)
!users_projects.where(:user_id => user.id, :project_access => [PROJECT_RW, PROJECT_RWA]).empty?
end
def allow_admin_for?(user)
!users_projects.where(:user_id => user.id, :project_access => [PROJECT_RWA]).empty? || owner_id == user.id
end
def root_ref
default_branch || "master"
end
......@@ -179,6 +255,24 @@ class Project < ActiveRecord::Base
last_activity.try(:created_at)
end
def last_activity_date_cached(expire = 1.hour)
activity_date_key = "project_#{id}_activity_date"
cached_activities = Rails.cache.read(activity_date_key)
if cached_activities
activity_date = if cached_activities == "Never"
nil
else
cached_activities
end
else
activity_date = last_activity_date
Rails.cache.write(activity_date_key, activity_date || "Never", :expires_in => expire)
end
activity_date
end
# Get project updates from cache
# or calculate.
def cached_updates(limit, expire = 2.minutes)
......@@ -188,7 +282,7 @@ class Project < ActiveRecord::Base
activities = cached_activities
else
activities = updates(limit)
Rails.cache.write(activities_key, activities, :expires_in => 60.seconds)
Rails.cache.write(activities_key, activities, :expires_in => expire)
end
activities
......@@ -206,6 +300,16 @@ class Project < ActiveRecord::Base
end[0...n]
end
def activities(n=3)
[
fresh_issues(n),
fresh_merge_requests(n),
notes.inc_author_project.where("noteable_type is not null").order("created_at desc").first(n)
].compact.flatten.sort do |x, y|
y.created_at <=> x.created_at
end[0...n]
end
def check_limit
unless owner.can_create_project?
errors[:base] << ("Your own projects limit is #{owner.projects_limit}! Please contact administrator to increase it")
......@@ -231,14 +335,15 @@ end
#
# Table name: projects
#
# id :integer not null, primary key
# name :string(255)
# path :string(255)
# description :text
# created_at :datetime
# updated_at :datetime
# private_flag :boolean default(TRUE), not null
# code :string(255)
# owner_id :integer
# id :integer not null, primary key
# name :string(255)
# path :string(255)
# description :text
# created_at :datetime
# updated_at :datetime
# private_flag :boolean default(TRUE), not null
# code :string(255)
# owner_id :integer
# default_branch :string(255) default("master"), not null
#
......@@ -31,6 +31,22 @@ class Repository
project.id
end
def write_hooks
%w(post-receive).each do |hook|
write_hook(hook, File.read(File.join(Rails.root, 'lib', "#{hook}-hook")))
end
end
def write_hook(name, content)
hook_file = File.join(project.path_to_repo, 'hooks', name)
File.open(hook_file, 'w') do |f|
f.write(content)
end
File.chmod(0775, hook_file)
end
def repo
@repo ||= Grit::Repo.new(project.path_to_repo)
end
......@@ -47,6 +63,8 @@ class Repository
Gitlabhq::GitHost.system.new.configure do |c|
c.update_project(path, project)
end
write_hooks if File.exists?(project.path_to_repo)
end
def destroy_repository
......@@ -56,7 +74,9 @@ class Repository
end
def repo_exists?
repo rescue false
@repo_exists ||= (repo && !repo.branches.empty?)
rescue
@repo_exists = false
end
def tags
......@@ -94,6 +114,16 @@ class Repository
commits[0...n]
end
def commits_with_refs(n = 20)
commits = repo.branches.map { |ref| Commit.new(ref.commit, ref) }
commits.sort! do |x, y|
y.committed_date <=> x.committed_date
end
commits[0..n]
end
def commits_since(date)
commits = heads.map do |h|
repo.log(h.name, nil, :since => date).each { |c| Commit.new(c, h) }
......@@ -115,4 +145,8 @@ class Repository
repo.commits(ref)
end.map{ |c| Commit.new(c) }
end
def commits_between(from, to)
repo.commits_between(from, to).map { |c| Commit.new(c) }
end
end
......@@ -3,7 +3,7 @@ class Snippet < ActiveRecord::Base
belongs_to :project
belongs_to :author, :class_name => "User"
has_many :notes, :as => :noteable
has_many :notes, :as => :noteable, :dependent => :destroy
delegate :name,
:email,
......@@ -28,6 +28,7 @@ class Snippet < ActiveRecord::Base
scope :fresh, order("created_at DESC")
scope :non_expired, where(["expires_at IS NULL OR expires_at > ?", Time.current])
scope :expired, where(["expires_at IS NOT NULL AND expires_at < ?", Time.current])
def self.content_types
[
......
......@@ -7,6 +7,8 @@ class Tree
:name,
:data,
:mime_type,
:mode,
:size,
:text?,
:colorize,
:to => :tree
......
......@@ -6,7 +6,7 @@ class User < ActiveRecord::Base
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me,
:name, :projects_limit, :skype, :linkedin, :twitter
:name, :projects_limit, :skype, :linkedin, :twitter, :dark_scheme
has_many :users_projects, :dependent => :destroy
has_many :projects, :through => :users_projects
......@@ -25,6 +25,20 @@ class User < ActiveRecord::Base
:foreign_key => :assignee_id,
:dependent => :destroy
has_many :merge_requests,
:foreign_key => :author_id,
:dependent => :destroy
has_many :assigned_merge_requests,
:class_name => "MergeRequest",
:foreign_key => :assignee_id,
:dependent => :destroy
validates :projects_limit,
:presence => true,
:numericality => {:greater_than_or_equal_to => 0}
before_create :ensure_authentication_token
alias_attribute :private_token, :authentication_token
scope :not_in_project, lambda { |project| where("id not in (:ids)", :ids => project.users.map(&:id) ) }
......@@ -37,8 +51,12 @@ class User < ActiveRecord::Base
admin
end
def require_ssh_key?
keys.count == 0
end
def can_create_project?
projects_limit >= my_own_projects.count
projects_limit > my_own_projects.count
end
def last_activity_project
......@@ -69,5 +87,6 @@ end
# linkedin :string(255) default(""), not null
# twitter :string(255) default(""), not null
# authentication_token :string(255)
# dark_scheme :boolean default(FALSE), not null
#
......@@ -13,6 +13,20 @@ class UsersProject < ActiveRecord::Base
delegate :name, :email, :to => :user, :prefix => true
def self.bulk_import(project, user_ids, project_access, repo_access)
UsersProject.transaction do
user_ids.each do |user_id|
users_project = UsersProject.new(
:repo_access => repo_access,
:project_access => project_access,
:user_id => user_id
)
users_project.project = project
users_project.save
end
end
end
def update_repository
Gitlabhq::GitHost.system.new.configure do |c|
c.update_project(project.path, project)
......@@ -23,13 +37,12 @@ end
#
# Table name: users_projects
#
# id :integer not null, primary key
# user_id :integer not null
# project_id :integer not null
# read :boolean default(FALSE)
# write :boolean default(FALSE)
# admin :boolean default(FALSE)
# created_at :datetime
# updated_at :datetime
# id :integer not null, primary key
# user_id :integer not null
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# repo_access :integer default(0), not null
# project_access :integer default(0), not null
#
class WebHook < ActiveRecord::Base
include HTTParty
# HTTParty timeout
default_timeout 10
belongs_to :project
validates :url,
presence: true,
format: {
with: URI::regexp(%w(http https)),
message: "should be a valid url" }
def execute(data)
WebHook.post(url, body: data.to_json)
rescue
# There was a problem calling this web hook, let's forget about it.
end
end
# == Schema Information
#
# Table name: web_hooks
#
# id :integer not null, primary key
# url :string(255)
# project_id :integer
# created_at :datetime
# updated_at :datetime
#
......@@ -38,6 +38,23 @@
%h2 Team
= form_tag team_update_admin_project_path(@admin_project), :class => "bulk_import", :method => :put do
%table
%thead
%tr
%th Users
%th Project Access:
%th Repo Access:
%tr
%td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), :multiple => true
%td= select_tag :project_access, options_for_select(Project.access_options), :class => "project-access-select"
%td= select_tag :repo_access, options_for_select(Repository.access_options), :class => "repo-access-select"
%tr
%td{ :colspan => 3 }
= submit_tag 'Add', :class => "positive-button"
%table.round-borders
%thead
%tr
......@@ -52,8 +69,22 @@
%td
= link_to tm.user_name, admin_team_member_path(tm)
%td= time_ago_in_words(tm.updated_at) + " ago"
%td= select_tag :project_access, options_for_select(Project.access_options, tm.project_access), :class => "project-access-select", :disabled => :disabled
%td= select_tag :repo_access, options_for_select(Repository.access_options, tm.repo_access), :class => "repo-access-select", :disabled => :disabled
%td= select_tag :tm_project_access, options_for_select(Project.access_options, tm.project_access), :class => "project-access-select", :disabled => :disabled
%td= select_tag :tm_repo_access, options_for_select(Repository.access_options, tm.repo_access), :class => "repo-access-select", :disabled => :disabled
%td= link_to 'Destroy', admin_team_member_path(tm), :confirm => 'Are you sure?', :method => :delete
= link_to 'New Team Member', new_admin_team_member_path(:team_member => {:project_id => @admin_project.id}), :class => "grey-button"
:css
form select {
width:150px;
}
#user_ids {
width:300px;
}
:javascript
$('select#user_ids').chosen();
$('select#repo_access').chosen();
$('select#project_access').chosen();
......@@ -17,7 +17,7 @@
= image_tag "no_avatar.png", :class => "left", :width => 40, :style => "padding-right:5px;"
%span.commit-title
%strong
= truncate(commit.safe_message, :length => 60)
= truncate(commit.safe_message, :length => 70)
%span.commit-author
%strong= commit.author_name
= time_ago_in_words(commit.committed_date)
......
%table
- line_old = 0
- line_new = 0
- diff_str = encode(diff.diff)
- diff_str = diff.diff
- lines_arr = diff_str.lines.to_a
- lines_arr.each do |line|
- next if line.match(/^--- \/dev\/null/)
- next if line.match(/^--- a/)
- next if line.match(/^\+\+\+ b/)
- if line.match(/^@@ -/)
- unless line_old.zero? && line_new.zero?
%tr.line_holder
%td.old_line= "..."
%td.new_line= "..."
%td.line_content &nbsp;
- line_old = line.match(/\-[0-9]*/)[0].to_i.abs rescue 0
- line_new = line.match(/\+[0-9]*/)[0].to_i.abs rescue 0
- next
......@@ -18,7 +24,11 @@
= link_to raw(diff_line_class(line) == "new" ? "&nbsp;" : line_old), "#OLD#{index}-#{line_old}", :id => "OLD#{index}-#{line_old}"
%td.new_line
= link_to raw(diff_line_class(line) == "old" ? "&nbsp;" : line_new) , "#NEW#{index}-#{line_new}", :id => "NEW#{index}-#{line_new}"
%td.line_content{:class => diff_line_class(full_line)}= raw "#{full_line} &nbsp;"
%td.line_content{:class => "#{diff_line_class(full_line)} #{build_line_code(line, index, line_new, line_old)}", "line_code" => build_line_code(line, index, line_new, line_old)}= raw "#{full_line} &nbsp;"
- comments = @line_notes.select { |n| n.for_line?(index, line_old, line_new) }.sort_by(&:created_at).reverse
- unless comments.empty?
- comments.each do |note|
= render "notes/per_line_show", :note => note
- if line[0] == "+"
- line_new += 1
- elsif line[0] == "-"
......
- content_for(:body_class, "project-page commits-page")
- if current_user.private_token
= content_for :rss_icon do
.rss-icon
= link_to project_commits_path(@project, :atom, { :private_token => current_user.private_token, :ref => @ref }) do
= image_tag "Rss-UI.PNG", :width => 22, :title => "feed"
-#%a.right.button{:href => "#"} Download
-#-if can? current_user, :admin_project, @project
%a.right.button.blue{:href => "#"} EDIT
%h2.icon
%span
%d
- if params[:path]
%h2
= link_to project_commits_path(@project) do
= @project.name
- if params[:path]
\/
%a{:href => "#"}= params[:path].split("/").join(" / ")
.right= render :partial => "projects/refs", :locals => { :destination => :commits }
= @project.code
\/
%a{:href => "#"}= params[:path].split("/").join(" / ")
%div{:id => dom_id(@project)}
#commits_list= render "commits"
......
......@@ -18,10 +18,21 @@
%hr
%pre.commit_message
= preserve @commit.safe_message
= commit_msg_with_link_to_issues(@project, @commit.safe_message)
.clear
%br
= render "commits/diff"
= render "notes/notes"
= render "notes/per_line_form"
:javascript
$(document).ready(function(){
$(".line_content").live("dblclick", function(e) {
var form = $(".per_line_form");
$(this).parent().after(form);
form.find("#note_line_code").val($(this).attr("line_code"));
form.show();
});
});
#feeds_content_holder
- unless @issues.empty?
.project-box.project-updates.ui-box.ui-box-small.ui-box-big
.data
- @issues.each do |update|
%a.project-update{:href => dashboard_feed_path(update.project, update)}
%strong.issue-number= "##{update.id}"
%span.update-title
= truncate update.title, :length => 35
.right= truncate update.project.name
%span.update-author
%strong= update.author_name
authored
= time_ago_in_words(update.created_at)
ago
.right
- if update.critical
%span.tag.high critical
- if update.today?
%span.tag.today today
- else
%h2
No assigned
%span.tag.open open
issues
-#%h4.dash-tabs
= link_to "Activities", dashboard_path, :remote => true, :class => "dash-button #{"active" if current_page?(dashboard_path) || current_page?(root_path) }", :id => "activities_slide"
= link_to "Issues", dashboard_issues_path, :remote => true, :class => "dash-button #{"active" if current_page?(dashboard_issues_path)}", :id => "issues_slide"
= link_to "Merge Requests", dashboard_merge_requests_path, :remote => true, :class => "dash-button #{"active" if current_page?(dashboard_merge_requests_path)}", :id => "merge_requests_slide"
= image_tag "ajax-loader-facebook.gif", :class => "dashboard-loader"
:javascript
$(function(){
$(".dash-button").live("click", function() {
$(".dash-button").removeClass("active");
$(this).addClass("active");
});
$(".dash-button").live("ajax:before", function() {
$(".dashboard-loader").show();
});
$(".dash-button").live("ajax:complete", function() {
$(".dashboard-loader").hide();
});
});
#feeds_content_holder
- unless @merge_requests.empty?
.project-box.project-updates.ui-box.ui-box-small.ui-box-big
.data
- @merge_requests.each do |update|
%a.project-update{:href => project_merge_request_path(update.project, update)}
= image_tag gravatar_icon(update.author_email), :class => "left", :width => 40
%span.update-title
= truncate update.title, :length => 35
.right= truncate update.project.name
%span.update-author
%strong= update.author_name
authored
= time_ago_in_words(update.created_at)
ago
.right
%span.tag.commit= update.source_branch
&rarr;
%span.tag.commit= update.target_branch
- else
%h2
No authored or assigned
%span.tag.open open
merge requests
#feeds_content_holder
- @active_projects.first(3).each do |project|
.project-box.project-updates.ui-box.ui-box-small.ui-box-big
= link_to project do
%h3= project.name
.data
- project.updates(3).each do |update|
%a.project-update{:href => dashboard_feed_path(project, update)}
= image_tag gravatar_icon(update.author_email), :class => "left", :width => 40
%span.update-title
= dashboard_feed_title(update)
%span.update-author
%strong= update.author_name
authored
= time_ago_in_words(update.created_at)
ago
.right
- klass = update.class.to_s.split("::").last.downcase
%span.tag{ :class => klass }= klass
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment