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

Merge remote-tracking branch 'upstream/master'

parents 2677bc3a 98d64925
port: 3000
.bundle .bundle
.rbx/ .rbx/
db/*.sqlite3 db/*.sqlite3
db/*.sqlite3-journal
log/*.log log/*.log
tmp/ tmp/
.sass-cache/ .sass-cache/
coverage/* coverage/*
*.swp *.swp
public/uploads/ public/uploads/
.rvmrc
.directory
nohup.out
rvm use 1.9.2-p290
before_install: sudo apt-get install libicu-dev -y
branches: branches:
only: only:
- 'master' - 'master'
......
v 2.1.0
- Project tab r1
- Repository tab r1
v 2.0.0 v 2.0.0
- gitolite as main git host system - gitolite as main git host system
- merge requests - merge requests
- project/repo access
- link to commit/issue feed
- design tab
- improved email notifications
- restyled dashboard
- bugfix - bugfix
v 1.2.2 v 1.2.2
......
...@@ -3,9 +3,11 @@ source "http://rubygems.org" ...@@ -3,9 +3,11 @@ source "http://rubygems.org"
gem "rails", "3.1.1" gem "rails", "3.1.1"
gem "sqlite3" gem "sqlite3"
gem "rake", "0.9.2.2"
gem "devise", "1.5.0" gem "devise", "1.5.0"
gem "stamp" gem "stamp"
gem "kaminari" gem "kaminari"
gem "haml", "3.1.4"
gem "haml-rails" gem "haml-rails"
gem "jquery-rails" gem "jquery-rails"
gem "grit", :git => "https://github.com/gitlabhq/grit.git" gem "grit", :git => "https://github.com/gitlabhq/grit.git"
...@@ -15,14 +17,17 @@ gem "six" ...@@ -15,14 +17,17 @@ gem "six"
gem "therubyracer" gem "therubyracer"
gem "faker" gem "faker"
gem "seed-fu", "~> 2.1.0" gem "seed-fu", "~> 2.1.0"
gem "pygments.rb", "0.2.3" gem "pygments.rb", "0.2.4"
gem "thin" gem "thin"
gem "git" gem "git"
gem "acts_as_list" gem "acts_as_list"
gem "rdiscount" gem "rdiscount"
gem "acts-as-taggable-on", "~> 2.1.0" gem "acts-as-taggable-on", "~> 2.1.0"
gem "drapper" gem "drapper"
gem "rchardet19", "~> 1.3.5" gem "resque"
gem "httparty"
gem "charlock_holmes"
gem "foreman"
group :assets do group :assets do
gem "sass-rails", "~> 3.1.0" gem "sass-rails", "~> 3.1.0"
...@@ -47,6 +52,7 @@ group :development, :test do ...@@ -47,6 +52,7 @@ group :development, :test do
gem "awesome_print" gem "awesome_print"
gem "database_cleaner" gem "database_cleaner"
gem "launchy" gem "launchy"
gem "webmock"
end end
group :test do group :test do
......
...@@ -77,6 +77,7 @@ GEM ...@@ -77,6 +77,7 @@ GEM
xpath (~> 0.1.4) xpath (~> 0.1.4)
carrierwave (0.5.8) carrierwave (0.5.8)
activesupport (~> 3.0) activesupport (~> 3.0)
charlock_holmes (0.6.8)
childprocess (0.2.2) childprocess (0.2.2)
ffi (~> 1.0.6) ffi (~> 1.0.6)
coffee-rails (3.1.1) coffee-rails (3.1.1)
...@@ -87,6 +88,7 @@ GEM ...@@ -87,6 +88,7 @@ GEM
execjs execjs
coffee-script-source (1.1.3) coffee-script-source (1.1.3)
columnize (0.3.4) columnize (0.3.4)
crack (0.3.1)
daemons (1.1.4) daemons (1.1.4)
database_cleaner (0.7.0) database_cleaner (0.7.0)
devise (1.5.0) devise (1.5.0)
...@@ -102,8 +104,11 @@ GEM ...@@ -102,8 +104,11 @@ GEM
faker (1.0.1) faker (1.0.1)
i18n (~> 0.4) i18n (~> 0.4)
ffi (1.0.11) ffi (1.0.11)
foreman (0.27.0)
term-ansicolor (~> 1.0.5)
thor (>= 0.13.6)
git (1.2.5) git (1.2.5)
haml (3.1.3) haml (3.1.4)
haml-rails (0.3.4) haml-rails (0.3.4)
actionpack (~> 3.0) actionpack (~> 3.0)
activesupport (~> 3.0) activesupport (~> 3.0)
...@@ -111,6 +116,9 @@ GEM ...@@ -111,6 +116,9 @@ GEM
railties (~> 3.0) railties (~> 3.0)
hashery (1.4.0) hashery (1.4.0)
hike (1.2.1) hike (1.2.1)
httparty (0.8.1)
multi_json
multi_xml
i18n (0.6.0) i18n (0.6.0)
jquery-rails (1.0.17) jquery-rails (1.0.17)
railties (~> 3.0) railties (~> 3.0)
...@@ -132,17 +140,20 @@ GEM ...@@ -132,17 +140,20 @@ GEM
treetop (~> 1.4.8) treetop (~> 1.4.8)
mime-types (1.17.2) mime-types (1.17.2)
multi_json (1.0.3) multi_json (1.0.3)
multi_xml (0.4.1)
nokogiri (1.5.0) nokogiri (1.5.0)
orm_adapter (0.0.5) orm_adapter (0.0.5)
polyglot (0.3.3) polyglot (0.3.3)
posix-spawn (0.3.6) posix-spawn (0.3.6)
pygments.rb (0.2.3) pygments.rb (0.2.4)
rubypython (>= 0.5.1) rubypython (~> 0.5.3)
rack (1.3.5) rack (1.3.5)
rack-cache (1.1) rack-cache (1.1)
rack (>= 0.4) rack (>= 0.4)
rack-mount (0.8.3) rack-mount (0.8.3)
rack (>= 1.0.0) rack (>= 1.0.0)
rack-protection (1.1.4)
rack
rack-ssl (1.3.2) rack-ssl (1.3.2)
rack rack
rack-test (0.6.1) rack-test (0.6.1)
...@@ -165,10 +176,17 @@ GEM ...@@ -165,10 +176,17 @@ GEM
rdoc (~> 3.4) rdoc (~> 3.4)
thor (~> 0.14.6) thor (~> 0.14.6)
rake (0.9.2.2) rake (0.9.2.2)
rchardet19 (1.3.5)
rdiscount (1.6.8) rdiscount (1.6.8)
rdoc (3.11) rdoc (3.11)
json (~> 1.4) 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 (2.7.0)
rspec-core (~> 2.7.0) rspec-core (~> 2.7.0)
rspec-expectations (~> 2.7.0) rspec-expectations (~> 2.7.0)
...@@ -220,6 +238,10 @@ GEM ...@@ -220,6 +238,10 @@ GEM
multi_json (~> 1.0.3) multi_json (~> 1.0.3)
simplecov-html (~> 0.5.3) simplecov-html (~> 0.5.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) six (0.2.0)
sprockets (2.0.3) sprockets (2.0.3)
hike (~> 1.2) hike (~> 1.2)
...@@ -227,6 +249,7 @@ GEM ...@@ -227,6 +249,7 @@ GEM
tilt (~> 1.1, != 1.3.0) tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.4) sqlite3 (1.3.4)
stamp (0.1.6) stamp (0.1.6)
term-ansicolor (1.0.7)
therubyracer (0.9.9) therubyracer (0.9.9)
libv8 (~> 3.3.10) libv8 (~> 3.3.10)
thin (1.3.1) thin (1.3.1)
...@@ -244,8 +267,13 @@ GEM ...@@ -244,8 +267,13 @@ GEM
uglifier (1.1.0) uglifier (1.1.0)
execjs (>= 0.3.0) execjs (>= 0.3.0)
multi_json (>= 1.0.2) multi_json (>= 1.0.2)
vegas (0.1.8)
rack (>= 1.0.0)
warden (1.1.0) warden (1.1.0)
rack (>= 1.0) rack (>= 1.0)
webmock (1.7.8)
addressable (~> 2.2, > 2.2.5)
crack (>= 0.1.7)
xpath (0.1.4) xpath (0.1.4)
nokogiri (~> 1.3) nokogiri (~> 1.3)
...@@ -261,24 +289,29 @@ DEPENDENCIES ...@@ -261,24 +289,29 @@ DEPENDENCIES
awesome_print awesome_print
capybara capybara
carrierwave carrierwave
charlock_holmes
coffee-rails (~> 3.1.0) coffee-rails (~> 3.1.0)
database_cleaner database_cleaner
devise (= 1.5.0) devise (= 1.5.0)
drapper drapper
faker faker
foreman
git git
gitolite! gitolite!
grit! grit!
haml (= 3.1.4)
haml-rails haml-rails
httparty
jquery-rails jquery-rails
kaminari kaminari
launchy launchy
letter_opener letter_opener
pygments.rb (= 0.2.3) pygments.rb (= 0.2.4)
rails (= 3.1.1) rails (= 3.1.1)
rails-footnotes (~> 3.7.5) rails-footnotes (~> 3.7.5)
rchardet19 (~> 1.3.5) rake (= 0.9.2.2)
rdiscount rdiscount
resque
rspec-rails rspec-rails
ruby-debug19 ruby-debug19
sass-rails (~> 3.1.0) sass-rails (~> 3.1.0)
...@@ -292,3 +325,4 @@ DEPENDENCIES ...@@ -292,3 +325,4 @@ DEPENDENCIES
thin thin
turn turn
uglifier 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) # 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 GitLab is a free project and repository management application
<img src="http://gitlabhq.com/front.png" width="900" height="471">
## Application details ## Application details
rails 3.1 * rails 3.1
works only with gitolite * works only with gitolite
sqlite as default a database * sqlite as default a database
## Requirements ## Requirements
...@@ -18,7 +15,7 @@ sqlite as default a database ...@@ -18,7 +15,7 @@ sqlite as default a database
* sqlite * sqlite
* git * git
* gitolite * gitolite
* pygments lib - `sudo easy_install pygments` * redis
## Install ## Install
...@@ -28,13 +25,11 @@ Checkout wiki pages for installation information, migration, etc. ...@@ -28,13 +25,11 @@ Checkout wiki pages for installation information, migration, etc.
[Google Group](https://groups.google.com/group/gitlabhq) [Google Group](https://groups.google.com/group/gitlabhq)
IRC freenode: #gitlabhq
## Contacts ## Contacts
Twitter: Twitter:
* @gitalbhq * @gitlabhq
* @dzaporozhets * @dzaporozhets
Email Email
...@@ -43,7 +38,5 @@ Email ...@@ -43,7 +38,5 @@ Email
## Contribute ## Contribute
We are on our way to full open source. Want to help - send a pull request.
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.
We'll accept good pull requests. We'll accept good pull requests.
[Dolphin]
ShowPreview=true
Timestamp=2011,10,28,13,16,25
Version=2
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
//= require branch-graph //= require branch-graph
//= require_tree . //= require_tree .
$(function(){ $(document).ready(function(){
$(".one_click_select").live("click", function(){ $(".one_click_select").live("click", function(){
$(this).select(); $(this).select();
}); });
...@@ -27,8 +27,50 @@ $(function(){ ...@@ -27,8 +27,50 @@ $(function(){
$(".account-box").mouseenter(showMenu); $(".account-box").mouseenter(showMenu);
$(".account-box").mouseleave(resetMenu); $(".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){ function updatePage(data){
$.ajax({type: "GET", url: location.href, data: data, dataType: "script"}); $.ajax({type: "GET", url: location.href, data: data, dataType: "script"});
} }
...@@ -40,3 +82,5 @@ function showMenu() { ...@@ -40,3 +82,5 @@ function showMenu() {
function resetMenu() { function resetMenu() {
$(this).removeClass("hover"); $(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 = { var CommitsList = {
ref:null,
limit:0,
offset:0,
ref:null, init:
limit:0, function(ref, limit) {
offset:0, $(".day-commits-table li.commit").live('click', function(e){
if(e.target.nodeName != "A") {
init: location.href = $(this).attr("url");
function(ref, limit) { e.stopPropagation();
this.ref=ref; return false;
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"});
},
append: this.ref=ref;
function(count, html) { this.limit=limit;
$("#commits_list").append(html); this.offset=limit;
if(count > 0) {
this.offset += count;
this.initLoadMore(); 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: append:
function() { function(count, html) {
$(window).bind('scroll', function(){ $("#commits_list").append(html);
if($(window).scrollTop() == $(document).height() - $(window).height()){ if(count > 0) {
$(window).unbind('scroll'); this.offset += count;
CommitsList.getOld(); 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(){ var ProjectsList = {
$('#tree-slider td.tree-item-file-name a, #tree-breadcrumbs a').live("click", function() { limit:0,
history.pushState({ path: this.path }, '', this.href) offset:0,
})
init:
$("#tree-slider tr.tree-item").live('click', function(e){ function(limit) {
if(e.target.nodeName != "A") { this.limit=limit;
e.stopPropagation(); this.offset=limit;
link = $(this).find("td.tree-item-file-name a") this.initLoadMore();
link.click(); },
return false;
} getOld:
}); function() {
$('.loading').show();
$("#projects-list .project").live('click', function(e){ $.ajax({
if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") { type: "GET",
location.href = $(this).attr("url"); url: location.href,
e.stopPropagation(); data: "limit=" + this.limit + "&offset=" + this.offset,
return false; complete: function(){ $('.loading').hide()},
} dataType: "script"});
}); },
$("#issues-table .issue").live('click', function(e){ append:
if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") { function(count, html) {
location.href = $(this).attr("url"); $(".tile").append(html);
e.stopPropagation(); if(count > 0) {
return false; this.offset += count;
} this.initLoadMore();
}); }
},
$(document).keypress(function(e) {
if( $(e.target).is(":input") ) return; initLoadMore:
switch(e.which) { function() {
case 115: focusSearch(); $(window).bind('scroll', function(){
e.preventDefault(); 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 @@ ...@@ -7,45 +7,5 @@
*= require jquery-ui/jquery.tagify *= require jquery-ui/jquery.tagify
*= require chosen *= require chosen
*= require_self *= 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 **/ /** Commit diff view **/
.diff_file { .diff_file {
border:1px solid #CCC; border:1px solid #CCC;
...@@ -37,7 +78,7 @@ ...@@ -37,7 +78,7 @@
padding:0px; padding:0px;
border:none; border:none;
background:#F7F7F7; background:#F7F7F7;
color:#333; color:#aaa;
padding: 0px 5px; padding: 0px 5px;
border-right: 1px solid #ccc; border-right: 1px solid #ccc;
text-align:right; text-align:right;
...@@ -48,6 +89,7 @@ ...@@ -48,6 +89,7 @@
float:left; float:left;
width:35px; width:35px;
font-weight:normal; font-weight:normal;
color:#aaa;
&:hover { &:hover {
text-decoration:underline; text-decoration:underline;
} }
...@@ -96,3 +138,54 @@ ul.bordered-list { ...@@ -96,3 +138,54 @@ ul.bordered-list {
} }
ul.bordered-list li:last-child { border:none } 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 @@ ...@@ -11,8 +11,8 @@
} }
.issues_filter { .issues_filter {
margin-top:10px; margin:10px 0;
.left { .left {
margin-right:15px; margin-right:15px;
} }
} }
...@@ -72,3 +72,13 @@ body.project-page .edit_snippet table td ...@@ -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 ...@@ -42,3 +42,11 @@ body.project-page #notes-list .note span.note-author strong{font-weight: bold; f
.note .note-title { margin-left:55px; } .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 ...@@ -9,6 +9,12 @@ class Admin::ProjectsController < ApplicationController
def show def show
@admin_project = Project.find_by_code(params[:id]) @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 end
def new def new
...@@ -19,6 +25,19 @@ class Admin::ProjectsController < ApplicationController ...@@ -19,6 +25,19 @@ class Admin::ProjectsController < ApplicationController
@admin_project = Project.find_by_code(params[:id]) @admin_project = Project.find_by_code(params[:id])
end 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 def create
@admin_project = Project.new(params[:project]) @admin_project = Project.new(params[:project])
@admin_project.owner = current_user @admin_project.owner = current_user
......
...@@ -27,7 +27,6 @@ class Admin::UsersController < ApplicationController ...@@ -27,7 +27,6 @@ class Admin::UsersController < ApplicationController
respond_to do |format| respond_to do |format|
if @admin_user.save 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.html { redirect_to [:admin, @admin_user], notice: 'User was successfully created.' }
format.json { render json: @admin_user, status: :created, location: @admin_user } format.json { render json: @admin_user, status: :created, location: @admin_user }
else else
...@@ -39,7 +38,7 @@ class Admin::UsersController < ApplicationController ...@@ -39,7 +38,7 @@ class Admin::UsersController < ApplicationController
def update def update
admin = params[:user].delete("admin") admin = params[:user].delete("admin")
if params[:user][:password].empty? if params[:user][:password].blank?
params[:user].delete(:password) params[:user].delete(:password)
params[:user].delete(:password_confirmation) params[:user].delete(:password_confirmation)
end end
......
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
before_filter :authenticate_user! before_filter :authenticate_user!
before_filter :set_current_user_for_mailer
protect_from_forgery protect_from_forgery
helper_method :abilities, :can? helper_method :abilities, :can?
...@@ -19,6 +20,10 @@ class ApplicationController < ActionController::Base ...@@ -19,6 +20,10 @@ class ApplicationController < ActionController::Base
end end
end end
def set_current_user_for_mailer
MailerObserver.current_user = current_user
end
def abilities def abilities
@abilities ||= Six.new @abilities ||= Six.new
end end
......
...@@ -27,6 +27,8 @@ class CommitsController < ApplicationController ...@@ -27,6 +27,8 @@ class CommitsController < ApplicationController
@notes = project.commit_notes(@commit).fresh.limit(20) @notes = project.commit_notes(@commit).fresh.limit(20)
@note = @project.build_commit_note(@commit) @note = @project.build_commit_note(@commit)
@line_notes = project.commit_line_notes(@commit)
respond_to do |format| respond_to do |format|
format.html format.html
format.js { respond_with_notes } format.js { respond_with_notes }
......
class DashboardController < ApplicationController class DashboardController < ApplicationController
respond_to :html
def index def index
@projects = current_user.projects.all @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
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 ...@@ -6,8 +6,18 @@ class IssuesController < ApplicationController
# Authorize # Authorize
before_filter :add_project_abilities before_filter :add_project_abilities
# Allow read any issue
before_filter :authorize_read_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 respond_to :js, :html
...@@ -57,10 +67,7 @@ class IssuesController < ApplicationController ...@@ -57,10 +67,7 @@ class IssuesController < ApplicationController
def create def create
@issue = @project.issues.new(params[:issue]) @issue = @project.issues.new(params[:issue])
@issue.author = current_user @issue.author = current_user
@issue.save
if @issue.save && @issue.assignee != current_user
Notify.new_issue_email(@issue).deliver
end
respond_with(@issue) respond_with(@issue)
end end
...@@ -80,6 +87,7 @@ class IssuesController < ApplicationController ...@@ -80,6 +87,7 @@ class IssuesController < ApplicationController
@issue.destroy @issue.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to project_issues_path }
format.js { render :nothing => true } format.js { render :nothing => true }
end end
end end
...@@ -115,4 +123,13 @@ class IssuesController < ApplicationController ...@@ -115,4 +123,13 @@ class IssuesController < ApplicationController
def issue def issue
@issue ||= @project.issues.find(params[:id]) @issue ||= @project.issues.find(params[:id])
end 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 end
...@@ -6,6 +6,10 @@ class KeysController < ApplicationController ...@@ -6,6 +6,10 @@ class KeysController < ApplicationController
@keys = current_user.keys.all @keys = current_user.keys.all
end end
def show
@key = current_user.keys.find(params[:id])
end
def new def new
@key = current_user.keys.new @key = current_user.keys.new
......
...@@ -6,11 +6,28 @@ class MergeRequestsController < ApplicationController ...@@ -6,11 +6,28 @@ class MergeRequestsController < ApplicationController
# Authorize # Authorize
before_filter :add_project_abilities 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 def index
@merge_requests = @project.merge_requests @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 end
def show def show
...@@ -30,14 +47,12 @@ class MergeRequestsController < ApplicationController ...@@ -30,14 +47,12 @@ class MergeRequestsController < ApplicationController
def commits def commits
@commits = @project.repo.commits_between(@merge_request.target_branch, @merge_request.source_branch).map {|c| Commit.new(c)} @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 end
def diffs def diffs
@diffs = @merge_request.diffs @diffs = @merge_request.diffs
@commit = @merge_request.last_commit @commit = @merge_request.last_commit
@line_notes = []
render :template => "merge_requests/_diffs", :layout => false
end end
def new def new
...@@ -88,4 +103,13 @@ class MergeRequestsController < ApplicationController ...@@ -88,4 +103,13 @@ class MergeRequestsController < ApplicationController
def merge_request def merge_request
@merge_request ||= @project.merge_requests.find(params[:id]) @merge_request ||= @project.merge_requests.find(params[:id])
end 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 end
...@@ -3,6 +3,8 @@ class NotesController < ApplicationController ...@@ -3,6 +3,8 @@ class NotesController < ApplicationController
# Authorize # Authorize
before_filter :add_project_abilities before_filter :add_project_abilities
before_filter :authorize_read_note!
before_filter :authorize_write_note!, :only => [:create] before_filter :authorize_write_note!, :only => [:create]
respond_to :js respond_to :js
...@@ -10,10 +12,9 @@ class NotesController < ApplicationController ...@@ -10,10 +12,9 @@ class NotesController < ApplicationController
def create def create
@note = @project.notes.new(params[:note]) @note = @project.notes.new(params[:note])
@note.author = current_user @note.author = current_user
@note.notify = true if params[:notify] == '1'
if @note.save @note.notify_author = true if params[:notify_author] == '1'
notify if params[:notify] == '1' @note.save
end
respond_to do |format| respond_to do |format|
format.html {redirect_to :back} format.html {redirect_to :back}
...@@ -33,22 +34,4 @@ class NotesController < ApplicationController ...@@ -33,22 +34,4 @@ class NotesController < ApplicationController
end end
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 end
...@@ -4,10 +4,14 @@ class ProfileController < ApplicationController ...@@ -4,10 +4,14 @@ class ProfileController < ApplicationController
@user = current_user @user = current_user
end end
def social_update def design
@user = current_user
end
def update
@user = current_user @user = current_user
@user.update_attributes(params[:user]) @user.update_attributes(params[:user])
redirect_to [:profile] redirect_to :back
end end
def password def password
......
...@@ -9,12 +9,10 @@ class ProjectsController < ApplicationController ...@@ -9,12 +9,10 @@ class ProjectsController < ApplicationController
before_filter :authorize_read_project!, :except => [:index, :new, :create] before_filter :authorize_read_project!, :except => [:index, :new, :create]
before_filter :authorize_admin_project!, :only => [:edit, :update, :destroy] before_filter :authorize_admin_project!, :only => [:edit, :update, :destroy]
before_filter :require_non_empty_project, :only => [:blob, :tree, :graph] before_filter :require_non_empty_project, :only => [:blob, :tree, :graph]
before_filter :load_refs, :only => :tree # load @branch, @tag & @ref
def index def index
source = current_user.projects @limit, @offset = (params[:limit] || 16), (params[:offset] || 0)
source = source.tagged_with(params[:tag]) unless params[:tag].blank? @projects = current_user.projects.limit(@limit).offset(@offset)
@projects = source.all
end end
def new def new
...@@ -59,7 +57,7 @@ class ProjectsController < ApplicationController ...@@ -59,7 +57,7 @@ class ProjectsController < ApplicationController
def update def update
respond_to do |format| respond_to do |format|
if project.update_attributes(params[:project]) 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 format.js
else else
format.html { render action: "edit" } format.html { render action: "edit" }
...@@ -71,7 +69,14 @@ class ProjectsController < ApplicationController ...@@ -71,7 +69,14 @@ class ProjectsController < ApplicationController
def show def show
return render "projects/empty" unless @project.repo_exists? && @project.has_commits? return render "projects/empty" unless @project.repo_exists? && @project.has_commits?
limit = (params[:limit] || 20).to_i 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 end
# #
...@@ -94,7 +99,11 @@ class ProjectsController < ApplicationController ...@@ -94,7 +99,11 @@ class ProjectsController < ApplicationController
end end
def destroy 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 project.destroy
UsersProject.set_callback(:destroy, :after, :update_repository)
respond_to do |format| respond_to do |format|
format.html { redirect_to projects_url } 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 ...@@ -5,8 +5,18 @@ class SnippetsController < ApplicationController
# Authorize # Authorize
before_filter :add_project_abilities before_filter :add_project_abilities
# Allow read any snippet
before_filter :authorize_read_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 respond_to :html
...@@ -60,4 +70,14 @@ class SnippetsController < ApplicationController ...@@ -60,4 +70,14 @@ class SnippetsController < ApplicationController
redirect_to project_snippets_path(@project) redirect_to project_snippets_path(@project)
end 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 end
...@@ -5,7 +5,7 @@ class TeamMembersController < ApplicationController ...@@ -5,7 +5,7 @@ class TeamMembersController < ApplicationController
# Authorize # Authorize
before_filter :add_project_abilities before_filter :add_project_abilities
before_filter :authorize_read_project! before_filter :authorize_read_project!
before_filter :authorize_admin_project!, :only => [:new, :create, :destroy, :update] before_filter :authorize_admin_project!, :except => [:show]
def show def show
@team_member = project.users_projects.find(params[:id]) @team_member = project.users_projects.find(params[:id])
...@@ -18,7 +18,11 @@ class TeamMembersController < ApplicationController ...@@ -18,7 +18,11 @@ class TeamMembersController < ApplicationController
def create def create
@team_member = UsersProject.new(params[:team_member]) @team_member = UsersProject.new(params[:team_member])
@team_member.project = project @team_member.project = project
@team_member.save if @team_member.save
redirect_to team_project_path(@project)
else
render "new"
end
end end
def update def update
......
...@@ -6,7 +6,7 @@ class TreeDecorator < ApplicationDecorator ...@@ -6,7 +6,7 @@ class TreeDecorator < ApplicationDecorator
part_path = "" part_path = ""
parts = path.split("\/") 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 yield(h.link_to("..", "#", :remote => :true)) if parts.count > max_links
...@@ -32,4 +32,13 @@ class TreeDecorator < ApplicationDecorator ...@@ -32,4 +32,13 @@ class TreeDecorator < ApplicationDecorator
def history_path def history_path
h.project_commits_path(project, :path => path, :ref => ref) h.project_commits_path(project, :path => path, :ref => ref)
end end
def mb_size
size = (tree.size / 1024)
if size < 1024
"#{size} KB"
else
"#{size/1024} MB"
end
end
end end
require 'digest/md5' require 'digest/md5'
module ApplicationHelper 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 = 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 end
def fixed_mode? def fixed_mode?
...@@ -48,11 +48,11 @@ module ApplicationHelper ...@@ -48,11 +48,11 @@ module ApplicationHelper
def grouped_options_refs(destination = :tree) def grouped_options_refs(destination = :tree)
options = [ options = [
["Branch", @repo.heads.map(&:name) ], ["Branch", @project.repo.heads.map(&:name) ],
[ "Tag", @project.tags ] [ "Tag", @project.tags ]
] ]
grouped_options_for_select(options, @ref) grouped_options_for_select(options, @ref || @project.default_branch)
end end
def markdown(text) def markdown(text)
...@@ -82,4 +82,15 @@ module ApplicationHelper ...@@ -82,4 +82,15 @@ module ApplicationHelper
[projects, default_nav, project_nav].flatten.to_json [projects, default_nav, project_nav].flatten.to_json
end 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 end
module CommitsHelper module CommitsHelper
include Utils::CharEncode
def old_line_number(line, i) def old_line_number(line, i)
end end
...@@ -25,4 +23,30 @@ module CommitsHelper ...@@ -25,4 +23,30 @@ module CommitsHelper
link_to "More", project_commits_path(@project, :offset => offset.to_i + limit.to_i, :limit => limit), 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" :remote => true, :class => "lite_button vm", :style => "text-align:center; width:930px; ", :id => "more-commits-link"
end 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 end
...@@ -10,6 +10,7 @@ module DashboardHelper ...@@ -10,6 +10,7 @@ module DashboardHelper
when "Issue" then project_issue_path(project, note.noteable_id) when "Issue" then project_issue_path(project, note.noteable_id)
when "Snippet" then project_snippet_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 "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) else wall_project_path(project)
end end
else wall_project_path(project) else wall_project_path(project)
......
...@@ -16,12 +16,26 @@ module ProjectsHelper ...@@ -16,12 +16,26 @@ module ProjectsHelper
nil nil
end end
# expires in 360 days def project_tab_class
def switch_colorscheme_link(opts) [:show, :files, :team, :edit, :update, :info].each do |action|
if cookies[:colorschema].blank? return "current" if current_page?(:controller => "projects", :action => action, :id => @project)
link_to_function "paint it black!", "$.cookie('colorschema','black', {expires:360}); window.location.reload()", opts end
else
link_to_function "paint it white!", "$.cookie('colorschema','', {expires:360}); window.location.reload()", opts 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 end
end end
module UserIssuesHelper
end
module UserMergeRequestsHelper
end
...@@ -28,7 +28,16 @@ class Notify < ActionMailer::Base ...@@ -28,7 +28,16 @@ class Notify < ActionMailer::Base
@note = note @note = note
@project = note.project @project = note.project
@commit = @project.repo.commits(note.noteable_id).first @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 end
def note_issue_email(user, note) def note_issue_email(user, note)
...@@ -36,6 +45,29 @@ class Notify < ActionMailer::Base ...@@ -36,6 +45,29 @@ class Notify < ActionMailer::Base
@note = note @note = note
@project = note.project @project = note.project
@issue = note.noteable @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
end end
...@@ -19,7 +19,7 @@ class Ability ...@@ -19,7 +19,7 @@ class Ability
:read_team_member, :read_team_member,
:read_merge_request, :read_merge_request,
:read_note :read_note
] if project.readers.include?(user) ] if project.allow_read_for?(user)
rules << [ rules << [
:write_project, :write_project,
...@@ -27,16 +27,18 @@ class Ability ...@@ -27,16 +27,18 @@ class Ability
:write_snippet, :write_snippet,
:write_merge_request, :write_merge_request,
:write_note :write_note
] if project.writers.include?(user) ] if project.allow_write_for?(user)
rules << [ rules << [
:modify_issue,
:modify_snippet,
:admin_project, :admin_project,
:admin_issue, :admin_issue,
:admin_snippet, :admin_snippet,
:admin_team_member, :admin_team_member,
:admin_merge_request, :admin_merge_request,
:admin_note :admin_note
] if project.admins.include?(user) ] if project.allow_admin_for?(user)
rules.flatten rules.flatten
end end
...@@ -48,6 +50,7 @@ class Ability ...@@ -48,6 +50,7 @@ class Ability
[ [
:"read_#{name}", :"read_#{name}",
:"write_#{name}", :"write_#{name}",
:"modify_#{name}",
:"admin_#{name}" :"admin_#{name}"
] ]
else else
......
class Commit class Commit
include Utils::CharEncode
attr_accessor :commit attr_accessor :commit
attr_accessor :head attr_accessor :head
attr_accessor :refs
delegate :message, delegate :message,
:committed_date, :committed_date,
...@@ -22,7 +22,7 @@ class Commit ...@@ -22,7 +22,7 @@ class Commit
end end
def safe_message def safe_message
encode(message) message
end end
def created_at def created_at
...@@ -30,11 +30,11 @@ class Commit ...@@ -30,11 +30,11 @@ class Commit
end end
def author_email def author_email
encode(author.email) author.email
end end
def author_name def author_name
encode(author.name) author.name
end end
def prev_commit def prev_commit
......
...@@ -2,7 +2,7 @@ class Issue < ActiveRecord::Base ...@@ -2,7 +2,7 @@ class Issue < ActiveRecord::Base
belongs_to :project belongs_to :project
belongs_to :author, :class_name => "User" belongs_to :author, :class_name => "User"
belongs_to :assignee, :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 attr_protected :author, :author_id, :project, :project_id
...@@ -59,5 +59,6 @@ end ...@@ -59,5 +59,6 @@ end
# closed :boolean default(FALSE), not null # closed :boolean default(FALSE), not null
# position :integer default(0) # position :integer default(0)
# critical :boolean default(FALSE), not null # critical :boolean default(FALSE), not null
# branch_name :string(255)
# #
class Key < ActiveRecord::Base class Key < ActiveRecord::Base
belongs_to :user belongs_to :user
belongs_to :project
validates :title, validates :title,
:presence => true, :presence => true,
...@@ -15,32 +16,38 @@ class Key < ActiveRecord::Base ...@@ -15,32 +16,38 @@ class Key < ActiveRecord::Base
after_destroy :repository_delete_key after_destroy :repository_delete_key
def set_identifier 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 end
def update_repository def update_repository
Gitlabhq::GitHost.system.new.configure do |c| Gitlabhq::GitHost.system.new.configure do |c|
c.update_keys(identifier, key) c.update_keys(identifier, key)
c.update_projects(projects)
projects.each do |project|
c.update_project(project.path, project)
end
end end
end end
def repository_delete_key def repository_delete_key
Gitlabhq::GitHost.system.new.configure do |c| Gitlabhq::GitHost.system.new.configure do |c|
c.delete_key(identifier) c.delete_key(identifier)
c.update_projects(projects)
projects.each do |project|
c.update_project(project.path, project)
end
end end
end end
def is_deploy_key
true if project_id
end
#projects that has this key #projects that has this key
def projects def projects
user.projects if is_deploy_key
[project]
else
user.projects
end
end end
end end
# == Schema Information # == Schema Information
...@@ -48,11 +55,12 @@ end ...@@ -48,11 +55,12 @@ end
# Table name: keys # Table name: keys
# #
# id :integer not null, primary key # id :integer not null, primary key
# user_id :integer not null # user_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# key :text # key :text
# title :string(255) # title :string(255)
# identifier :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 ...@@ -2,7 +2,7 @@ class MergeRequest < ActiveRecord::Base
belongs_to :project belongs_to :project
belongs_to :author, :class_name => "User" belongs_to :author, :class_name => "User"
belongs_to :assignee, :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 attr_protected :author, :author_id, :project, :project_id
...@@ -35,12 +35,27 @@ class MergeRequest < ActiveRecord::Base ...@@ -35,12 +35,27 @@ class MergeRequest < ActiveRecord::Base
end end
def diffs def diffs
commit = project.commit(source_branch)
commits = project.repo.commits_between(target_branch, source_branch).map {|c| Commit.new(c)} 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 end
def last_commit def last_commit
project.commit(source_branch) project.commit(source_branch)
end end
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 ...@@ -13,6 +13,8 @@ class Note < ActiveRecord::Base
:prefix => true :prefix => true
attr_protected :author, :author_id attr_protected :author, :author_id
attr_accessor :notify
attr_accessor :notify_author
validates_presence_of :project validates_presence_of :project
...@@ -35,6 +37,43 @@ class Note < ActiveRecord::Base ...@@ -35,6 +37,43 @@ class Note < ActiveRecord::Base
scope :inc_author, includes(:author) scope :inc_author, includes(:author)
mount_uploader :attachment, AttachmentUploader 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 end
# == Schema Information # == Schema Information
# #
...@@ -49,5 +88,6 @@ end ...@@ -49,5 +88,6 @@ end
# updated_at :datetime # updated_at :datetime
# project_id :integer # project_id :integer
# attachment :string(255) # attachment :string(255)
# line_code :string(255)
# #
...@@ -14,6 +14,8 @@ class Project < ActiveRecord::Base ...@@ -14,6 +14,8 @@ class Project < ActiveRecord::Base
has_many :users, :through => :users_projects has_many :users, :through => :users_projects
has_many :notes, :dependent => :destroy has_many :notes, :dependent => :destroy
has_many :snippets, :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 acts_as_taggable
...@@ -25,8 +27,8 @@ class Project < ActiveRecord::Base ...@@ -25,8 +27,8 @@ class Project < ActiveRecord::Base
validates :path, validates :path,
:uniqueness => true, :uniqueness => true,
:presence => true, :presence => true,
:format => { :with => /^[a-zA-Z0-9_\-]*$/, :format => { :with => /^[a-zA-Z0-9_\-\.]*$/,
:message => "only letters, digits & '_' '-' allowed" }, :message => "only letters, digits & '_' '-' '.' allowed" },
:length => { :within => 0..255 } :length => { :within => 0..255 }
validates :description, validates :description,
...@@ -35,8 +37,8 @@ class Project < ActiveRecord::Base ...@@ -35,8 +37,8 @@ class Project < ActiveRecord::Base
validates :code, validates :code,
:presence => true, :presence => true,
:uniqueness => true, :uniqueness => true,
:format => { :with => /^[a-zA-Z0-9_\-]*$/, :format => { :with => /^[a-zA-Z0-9_\-\.]*$/,
:message => "only letters, digits & '_' '-' allowed" }, :message => "only letters, digits & '_' '-' '.' allowed" },
:length => { :within => 3..255 } :length => { :within => 3..255 }
validates :owner, validates :owner,
...@@ -52,6 +54,9 @@ class Project < ActiveRecord::Base ...@@ -52,6 +54,9 @@ class Project < ActiveRecord::Base
scope :public_only, where(:private_flag => false) 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 def self.access_options
{ {
...@@ -75,21 +80,76 @@ class Project < ActiveRecord::Base ...@@ -75,21 +80,76 @@ class Project < ActiveRecord::Base
:repo_exists?, :repo_exists?,
:commit, :commit,
:commits, :commits,
:commits_with_refs,
:tree, :tree,
:heads, :heads,
:commits_since, :commits_since,
:fresh_commits, :fresh_commits,
:commits_between,
:to => :repository, :prefix => nil :to => :repository, :prefix => nil
def to_param def to_param
code code
end 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) def team_member_by_name_or_email(email = nil, name = nil)
user = users.where("email like ? or name like ?", email, name).first user = users.where("email like ? or name like ?", email, name).first
users_projects.find_by_user_id(user.id) if user users_projects.find_by_user_id(user.id) if user
end 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) def fresh_issues(n)
issues.includes(:project, :author).order("created_at desc").first(n) issues.includes(:project, :author).order("created_at desc").first(n)
end end
...@@ -107,7 +167,11 @@ class Project < ActiveRecord::Base ...@@ -107,7 +167,11 @@ class Project < ActiveRecord::Base
end end
def commit_notes(commit) 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 end
def has_commits? def has_commits?
...@@ -136,7 +200,7 @@ class Project < ActiveRecord::Base ...@@ -136,7 +200,7 @@ class Project < ActiveRecord::Base
def repository_readers def repository_readers
keys = Key.joins({:user => :users_projects}). keys = Key.joins({:user => :users_projects}).
where("users_projects.project_id = ? AND users_projects.repo_access = ?", id, Repository::REPO_R) 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 end
def repository_writers def repository_writers
...@@ -157,6 +221,18 @@ class Project < ActiveRecord::Base ...@@ -157,6 +221,18 @@ class Project < ActiveRecord::Base
@admins ||= users_projects.includes(:user).where(:project_access => PROJECT_RWA).map(&:user) @admins ||= users_projects.includes(:user).where(:project_access => PROJECT_RWA).map(&:user)
end 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 def root_ref
default_branch || "master" default_branch || "master"
end end
...@@ -179,6 +255,24 @@ class Project < ActiveRecord::Base ...@@ -179,6 +255,24 @@ class Project < ActiveRecord::Base
last_activity.try(:created_at) last_activity.try(:created_at)
end 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 # Get project updates from cache
# or calculate. # or calculate.
def cached_updates(limit, expire = 2.minutes) def cached_updates(limit, expire = 2.minutes)
...@@ -188,7 +282,7 @@ class Project < ActiveRecord::Base ...@@ -188,7 +282,7 @@ class Project < ActiveRecord::Base
activities = cached_activities activities = cached_activities
else else
activities = updates(limit) activities = updates(limit)
Rails.cache.write(activities_key, activities, :expires_in => 60.seconds) Rails.cache.write(activities_key, activities, :expires_in => expire)
end end
activities activities
...@@ -206,6 +300,16 @@ class Project < ActiveRecord::Base ...@@ -206,6 +300,16 @@ class Project < ActiveRecord::Base
end[0...n] end[0...n]
end 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 def check_limit
unless owner.can_create_project? unless owner.can_create_project?
errors[:base] << ("Your own projects limit is #{owner.projects_limit}! Please contact administrator to increase it") errors[:base] << ("Your own projects limit is #{owner.projects_limit}! Please contact administrator to increase it")
...@@ -231,14 +335,15 @@ end ...@@ -231,14 +335,15 @@ end
# #
# Table name: projects # Table name: projects
# #
# id :integer not null, primary key # id :integer not null, primary key
# name :string(255) # name :string(255)
# path :string(255) # path :string(255)
# description :text # description :text
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# private_flag :boolean default(TRUE), not null # private_flag :boolean default(TRUE), not null
# code :string(255) # code :string(255)
# owner_id :integer # owner_id :integer
# default_branch :string(255) default("master"), not null
# #
...@@ -31,6 +31,22 @@ class Repository ...@@ -31,6 +31,22 @@ class Repository
project.id project.id
end 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 def repo
@repo ||= Grit::Repo.new(project.path_to_repo) @repo ||= Grit::Repo.new(project.path_to_repo)
end end
...@@ -47,6 +63,8 @@ class Repository ...@@ -47,6 +63,8 @@ class Repository
Gitlabhq::GitHost.system.new.configure do |c| Gitlabhq::GitHost.system.new.configure do |c|
c.update_project(path, project) c.update_project(path, project)
end end
write_hooks if File.exists?(project.path_to_repo)
end end
def destroy_repository def destroy_repository
...@@ -56,7 +74,9 @@ class Repository ...@@ -56,7 +74,9 @@ class Repository
end end
def repo_exists? def repo_exists?
repo rescue false @repo_exists ||= (repo && !repo.branches.empty?)
rescue
@repo_exists = false
end end
def tags def tags
...@@ -94,6 +114,16 @@ class Repository ...@@ -94,6 +114,16 @@ class Repository
commits[0...n] commits[0...n]
end 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) def commits_since(date)
commits = heads.map do |h| commits = heads.map do |h|
repo.log(h.name, nil, :since => date).each { |c| Commit.new(c, h) } repo.log(h.name, nil, :since => date).each { |c| Commit.new(c, h) }
...@@ -115,4 +145,8 @@ class Repository ...@@ -115,4 +145,8 @@ class Repository
repo.commits(ref) repo.commits(ref)
end.map{ |c| Commit.new(c) } end.map{ |c| Commit.new(c) }
end end
def commits_between(from, to)
repo.commits_between(from, to).map { |c| Commit.new(c) }
end
end end
...@@ -3,7 +3,7 @@ class Snippet < ActiveRecord::Base ...@@ -3,7 +3,7 @@ class Snippet < ActiveRecord::Base
belongs_to :project belongs_to :project
belongs_to :author, :class_name => "User" belongs_to :author, :class_name => "User"
has_many :notes, :as => :noteable has_many :notes, :as => :noteable, :dependent => :destroy
delegate :name, delegate :name,
:email, :email,
...@@ -28,6 +28,7 @@ class Snippet < ActiveRecord::Base ...@@ -28,6 +28,7 @@ class Snippet < ActiveRecord::Base
scope :fresh, order("created_at DESC") scope :fresh, order("created_at DESC")
scope :non_expired, where(["expires_at IS NULL OR expires_at > ?", Time.current]) 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 def self.content_types
[ [
......
...@@ -7,6 +7,8 @@ class Tree ...@@ -7,6 +7,8 @@ class Tree
:name, :name,
:data, :data,
:mime_type, :mime_type,
:mode,
:size,
:text?, :text?,
:colorize, :colorize,
:to => :tree :to => :tree
......
...@@ -6,7 +6,7 @@ class User < ActiveRecord::Base ...@@ -6,7 +6,7 @@ class User < ActiveRecord::Base
# Setup accessible (or protected) attributes for your model # Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, 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 :users_projects, :dependent => :destroy
has_many :projects, :through => :users_projects has_many :projects, :through => :users_projects
...@@ -25,6 +25,20 @@ class User < ActiveRecord::Base ...@@ -25,6 +25,20 @@ class User < ActiveRecord::Base
:foreign_key => :assignee_id, :foreign_key => :assignee_id,
:dependent => :destroy :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 before_create :ensure_authentication_token
alias_attribute :private_token, :authentication_token alias_attribute :private_token, :authentication_token
scope :not_in_project, lambda { |project| where("id not in (:ids)", :ids => project.users.map(&:id) ) } scope :not_in_project, lambda { |project| where("id not in (:ids)", :ids => project.users.map(&:id) ) }
...@@ -37,8 +51,12 @@ class User < ActiveRecord::Base ...@@ -37,8 +51,12 @@ class User < ActiveRecord::Base
admin admin
end end
def require_ssh_key?
keys.count == 0
end
def can_create_project? def can_create_project?
projects_limit >= my_own_projects.count projects_limit > my_own_projects.count
end end
def last_activity_project def last_activity_project
...@@ -69,5 +87,6 @@ end ...@@ -69,5 +87,6 @@ end
# linkedin :string(255) default(""), not null # linkedin :string(255) default(""), not null
# twitter :string(255) default(""), not null # twitter :string(255) default(""), not null
# authentication_token :string(255) # authentication_token :string(255)
# dark_scheme :boolean default(FALSE), not null
# #
...@@ -13,6 +13,20 @@ class UsersProject < ActiveRecord::Base ...@@ -13,6 +13,20 @@ class UsersProject < ActiveRecord::Base
delegate :name, :email, :to => :user, :prefix => true 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 def update_repository
Gitlabhq::GitHost.system.new.configure do |c| Gitlabhq::GitHost.system.new.configure do |c|
c.update_project(project.path, project) c.update_project(project.path, project)
...@@ -23,13 +37,12 @@ end ...@@ -23,13 +37,12 @@ end
# #
# Table name: users_projects # Table name: users_projects
# #
# id :integer not null, primary key # id :integer not null, primary key
# user_id :integer not null # user_id :integer not null
# project_id :integer not null # project_id :integer not null
# read :boolean default(FALSE) # created_at :datetime
# write :boolean default(FALSE) # updated_at :datetime
# admin :boolean default(FALSE) # repo_access :integer default(0), not null
# created_at :datetime # project_access :integer default(0), not null
# updated_at :datetime
# #
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 @@ ...@@ -38,6 +38,23 @@
%h2 Team %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 %table.round-borders
%thead %thead
%tr %tr
...@@ -52,8 +69,22 @@ ...@@ -52,8 +69,22 @@
%td %td
= link_to tm.user_name, admin_team_member_path(tm) = link_to tm.user_name, admin_team_member_path(tm)
%td= time_ago_in_words(tm.updated_at) + " ago" %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 :tm_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_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 %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 @@ ...@@ -17,7 +17,7 @@
= image_tag "no_avatar.png", :class => "left", :width => 40, :style => "padding-right:5px;" = image_tag "no_avatar.png", :class => "left", :width => 40, :style => "padding-right:5px;"
%span.commit-title %span.commit-title
%strong %strong
= truncate(commit.safe_message, :length => 60) = truncate(commit.safe_message, :length => 70)
%span.commit-author %span.commit-author
%strong= commit.author_name %strong= commit.author_name
= time_ago_in_words(commit.committed_date) = time_ago_in_words(commit.committed_date)
......
%table %table
- line_old = 0 - line_old = 0
- line_new = 0 - line_new = 0
- diff_str = encode(diff.diff) - diff_str = diff.diff
- lines_arr = diff_str.lines.to_a - lines_arr = diff_str.lines.to_a
- lines_arr.each do |line| - lines_arr.each do |line|
- next if line.match(/^--- \/dev\/null/) - next if line.match(/^--- \/dev\/null/)
- next if line.match(/^--- a/) - next if line.match(/^--- a/)
- next if line.match(/^\+\+\+ b/) - next if line.match(/^\+\+\+ b/)
- if line.match(/^@@ -/) - 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_old = line.match(/\-[0-9]*/)[0].to_i.abs rescue 0
- line_new = line.match(/\+[0-9]*/)[0].to_i.abs rescue 0 - line_new = line.match(/\+[0-9]*/)[0].to_i.abs rescue 0
- next - next
...@@ -18,7 +24,11 @@ ...@@ -18,7 +24,11 @@
= link_to raw(diff_line_class(line) == "new" ? "&nbsp;" : line_old), "#OLD#{index}-#{line_old}", :id => "OLD#{index}-#{line_old}" = link_to raw(diff_line_class(line) == "new" ? "&nbsp;" : line_old), "#OLD#{index}-#{line_old}", :id => "OLD#{index}-#{line_old}"
%td.new_line %td.new_line
= link_to raw(diff_line_class(line) == "old" ? "&nbsp;" : line_new) , "#NEW#{index}-#{line_new}", :id => "NEW#{index}-#{line_new}" = 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] == "+" - if line[0] == "+"
- line_new += 1 - line_new += 1
- elsif line[0] == "-" - elsif line[0] == "-"
......
- content_for(:body_class, "project-page commits-page") - 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 params[:path]
-#-if can? current_user, :admin_project, @project %h2
%a.right.button.blue{:href => "#"} EDIT
%h2.icon
%span
%d
= link_to project_commits_path(@project) do = link_to project_commits_path(@project) do
= @project.name = @project.code
- if params[:path] \/
\/ %a{:href => "#"}= params[:path].split("/").join(" / ")
%a{:href => "#"}= params[:path].split("/").join(" / ")
.right= render :partial => "projects/refs", :locals => { :destination => :commits }
%div{:id => dom_id(@project)} %div{:id => dom_id(@project)}
#commits_list= render "commits" #commits_list= render "commits"
......
...@@ -18,10 +18,21 @@ ...@@ -18,10 +18,21 @@
%hr %hr
%pre.commit_message %pre.commit_message
= preserve @commit.safe_message = commit_msg_with_link_to_issues(@project, @commit.safe_message)
.clear .clear
%br %br
= render "commits/diff" = render "commits/diff"
= render "notes/notes" = 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