Commit db2c1536 authored by Riyad Preukschas's avatar Riyad Preukschas

Merge branch 'master' into discussions

Conflicts:
	app/assets/stylesheets/main.scss
	app/models/project.rb
	app/views/notes/_common_form.html.haml
	app/views/notes/_per_line_form.html.haml
	lib/gitlab/markdown.rb
	spec/models/note_spec.rb
parents b47173da 68c43d59
env:
- DB=postgresql
- DB=mysql
before_install:
- sudo apt-get install libicu-dev -y
......@@ -18,8 +19,7 @@ services:
before_script:
- "cp config/database.yml.$DB config/database.yml"
- "cp config/gitlab.yml.example config/gitlab.yml"
- "bundle exec rake db:create RAILS_ENV=test"
- "bundle exec rake db:migrate RAILS_ENV=test"
- "bundle exec rake db:setup RAILS_ENV=test"
- "bundle exec rake db:seed_fu RAILS_ENV=test"
- "sh -e /etc/init.d/xvfb start"
script: "bundle exec rake travis --trace"
v 4.0.0
- Reorganized settings
- Fixed commits compare
- Refactored scss
- Improve status checks
- Validates presence of User#name
- Fixed postgres support
- Removed sqlite support
- Modified post-receive hook
- Milestones can be closed now
- Show comment events on dashboard
- Quick add team members via group#people page
- [API] expose created date for hooks and SSH keys
- [API] list, create issue notes
- [API] list, create snippet notes
......
## Contribute to GitLab
# Contact & support
If you want to contribute to GitLab, follow this process:
If you want quick help, head over to our [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq).
Otherwise you can follow our [Issue Submission Guide](https://github.com/gitlabhq/gitlabhq/wiki/Issue-Submission-Guide) for a more systematic and thorough guide to solving your issues.
1. Fork the project
2. Create a feature branch
3. Code
4. Create a pull request
We will only accept pull requests if:
* Your code has proper tests and all tests pass
* Your code can be merged w/o problems
* It won't break existing functionality
* It's quality code
* We like it :)
# Contribute to GitLab
For examples of feedback on pull requests please look at the [closed pull requests](https://github.com/gitlabhq/gitlabhq/pulls?direction=desc&page=1&sort=created&state=closed).
## Recipes
## Installation
We collect user submitted installation scripts and config file templates for platforms we don't support officially.
We believe there is merit in allowing a certain amount of diversity.
You can get and submit your solution to running/configuring GitLab with your favorite OS/distro, database, web server, cloud hoster, configuration management tool, etc.
Install the Gitlab development in a virtual machine with the [Gitlab Vagrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm). Installing it in a virtual machine makes it much easier to set up all the dependencies for integration testing.
Help us improve the collection of [GitLab Recipes](https://github.com/gitlabhq/gitlab-recipes/)
## Running tests
For more information on running the tests please read the [development tips](https://github.com/gitlabhq/gitlabhq/blob/master/doc/development.md)
## Feature suggestions
Follow the [Issue Submission Guide](https://github.com/gitlabhq/gitlabhq/wiki/Issue-Submission-Guide) and support other peoples ideas or propose your own.
## Code
Follow our [Developer Guide](https://github.com/gitlabhq/gitlabhq/wiki/Developer-Guide) to set you up for hacking on GitLab.
......@@ -32,7 +32,7 @@ gem 'grit_ext', git: "https://github.com/gitlabhq/grit_ext.git", ref:
gem "gitolite", '1.1.0'
# Syntax highlighter
gem "pygments.rb", git: "https://github.com/gitlabhq/pygments.rb.git", ref: '4db80c599067e2d5f23c5c243bf85b8ca0368ad4'
gem "pygments.rb", git: "https://github.com/gitlabhq/pygments.rb.git", branch: "master"
# Language detection
gem "github-linguist", "~> 2.3.4" , require: "linguist"
......@@ -100,7 +100,7 @@ group :assets do
gem "therubyracer"
gem 'chosen-rails', "0.9.8"
gem 'jquery-atwho-rails', "0.1.6"
gem 'jquery-atwho-rails', "0.1.7"
gem "jquery-rails", "2.1.3"
gem "jquery-ui-rails", "2.0.2"
gem "modernizr", "2.6.2"
......@@ -124,7 +124,7 @@ group :development, :test do
gem "capybara"
gem "pry"
gem "awesome_print"
gem "database_cleaner"
gem "database_cleaner", ref: "f89c34300e114be99532f14c115b2799a3380ac6", git: "https://github.com/bmabey/database_cleaner.git"
gem "launchy"
gem 'factory_girl_rails'
......
GIT
remote: https://github.com/bmabey/database_cleaner.git
revision: f89c34300e114be99532f14c115b2799a3380ac6
ref: f89c34300e114be99532f14c115b2799a3380ac6
specs:
database_cleaner (0.9.1)
GIT
remote: https://github.com/ctran/annotate_models.git
revision: be4e26825b521f0b2d86b181e2dff89901aa9b1e
......@@ -45,8 +52,8 @@ GIT
GIT
remote: https://github.com/gitlabhq/pygments.rb.git
revision: 4db80c599067e2d5f23c5c243bf85b8ca0368ad4
ref: 4db80c599067e2d5f23c5c243bf85b8ca0368ad4
revision: db1da0343adf86b49bdc3add04d02d2e80438d38
branch: master
specs:
pygments.rb (0.3.2)
posix-spawn (~> 0.3.6)
......@@ -140,7 +147,6 @@ GEM
colorize (0.5.8)
crack (0.3.1)
daemons (1.1.9)
database_cleaner (0.9.1)
devise (2.1.2)
bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.1)
......@@ -227,7 +233,7 @@ GEM
httpauth (0.2.0)
i18n (0.6.1)
journey (1.0.4)
jquery-atwho-rails (0.1.6)
jquery-atwho-rails (0.1.7)
jquery-rails (2.1.3)
railties (>= 3.1.0, < 5.0)
thor (~> 0.14)
......@@ -458,7 +464,7 @@ DEPENDENCIES
chosen-rails (= 0.9.8)
coffee-rails (~> 3.2.2)
colored
database_cleaner
database_cleaner!
devise (~> 2.1.0)
draper (~> 0.18.0)
email_spec
......@@ -481,7 +487,7 @@ DEPENDENCIES
guard-spinach
haml-rails (~> 0.3.5)
httparty
jquery-atwho-rails (= 0.1.6)
jquery-atwho-rails (= 0.1.7)
jquery-rails (= 2.1.3)
jquery-ui-rails (= 2.0.2)
kaminari (~> 0.14.1)
......
# Welcome to GitLab [![build status](https://secure.travis-ci.org/gitlabhq/gitlabhq.png)](https://secure.travis-ci.org/gitlabhq/gitlabhq) [![build status](https://secure.travis-ci.org/gitlabhq/grit.png)](https://secure.travis-ci.org/gitlabhq/grit) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/gitlabhq/gitlabhq)
# Welcome to GitLab [![build status](https://secure.travis-ci.org/gitlabhq/gitlabhq.png)](https://travis-ci.org/gitlabhq/gitlabhq) [![build status](https://secure.travis-ci.org/gitlabhq/grit.png)](https://travis-ci.org/gitlabhq/grit) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.png)](https://gemnasium.com/gitlabhq/gitlabhq)
GitLab is a free project and repository management application
......
app/assets/images/logo_white.png

1.48 KB | W: | H:

app/assets/images/logo_white.png

1.88 KB | W: | H:

app/assets/images/logo_white.png
app/assets/images/logo_white.png
app/assets/images/logo_white.png
app/assets/images/logo_white.png
  • 2-up
  • Swipe
  • Onion skin
# Creates the variables for setting up GFM auto-completion
window.GitLab ?= {}
GitLab.GfmAutoComplete ?= {}
# Emoji
data = []
template = "<li data-value='${insert}'>${name} <img alt='${name}' height='20' src='${image}' width='20' /></li>"
GitLab.GfmAutoComplete.Emoji = {data, template}
# Team Members
data = []
url = '';
params = {private_token: '', page: 1}
GitLab.GfmAutoComplete.Members = {data, url, params}
# Add GFM auto-completion to all input fields, that accept GFM input.
GitLab.GfmAutoComplete.setup = ->
input = $('.js-gfm-input')
GitLab.GfmAutoComplete =
# Emoji
input.atWho ':',
data: GitLab.GfmAutoComplete.Emoji.data,
tpl: GitLab.GfmAutoComplete.Emoji.template
Emoji:
data: []
template: '<li data-value="${insert}">${name} <img alt="${name}" height="20" src="${image}" width="20" /></li>'
# Team Members
input.atWho '@', (query, callback) ->
(getMoreMembers = ->
$.getJSON(GitLab.GfmAutoComplete.Members.url, GitLab.GfmAutoComplete.Members.params)
.success (members) ->
# pick the data we need
newMembersData = $.map(members, (m) -> m.name )
# add the new page of data to the rest
$.merge(GitLab.GfmAutoComplete.Members.data, newMembersData)
# show the pop-up with a copy of the current data
callback(GitLab.GfmAutoComplete.Members.data[..])
# are we past the last page?
if newMembersData.length is 0
# set static data and stop callbacks
input.atWho '@',
data: GitLab.GfmAutoComplete.Members.data
callback: null
else
# get next page
getMoreMembers()
Members:
data: []
url: ''
params:
private_token: ''
template: '<li data-value="${username}">${username} <small>${name}</small></li>'
# Add GFM auto-completion to all input fields, that accept GFM input.
setup: ->
input = $('.js-gfm-input')
# Emoji
input.atWho ':',
data: @Emoji.data
tpl: @Emoji.template
# Team Members
input.atWho '@',
tpl: @Members.template
callback: (query, callback) =>
request_params = $.extend({}, @Members.params, query: query)
$.getJSON(@Members.url, request_params).done (members) =>
new_members_data = $.map(members, (m) ->
username: m.username,
name: m.name
)
callback(new_members_data)
# so the next request gets the next page
GitLab.GfmAutoComplete.Members.params.page += 1
).call()
function switchToNewIssue(){
$(".issues_content").hide("fade", { direction: "left" }, 150, function(){
$('select#issue_assignee_id').chosen();
$('select#issue_milestone_id').chosen();
$("#new_issue_dialog").show("fade", { direction: "right" }, 150);
$('.top-tabs .add_new').hide();
disableButtonIfEmptyField("#issue_title", ".save-btn");
GitLab.GfmAutoComplete.setup();
});
}
function switchToEditIssue(){
$(".issues_content").hide("fade", { direction: "left" }, 150, function(){
$('select#issue_assignee_id').chosen();
$('select#issue_milestone_id').chosen();
$("#edit_issue_dialog").show("fade", { direction: "right" }, 150);
$('.add_new').hide();
disableButtonIfEmptyField("#issue_title", ".save-btn");
GitLab.GfmAutoComplete.setup();
});
}
function switchFromNewIssue(){
backToIssues();
}
function switchFromEditIssue(){
backToIssues();
}
function backToIssues(){
$("#edit_issue_dialog, #new_issue_dialog").hide("fade", { direction: "right" }, 150, function(){
$(".issues_content").show("fade", { direction: "left" }, 150, function() {
$("#edit_issue_dialog").html("");
$("#new_issue_dialog").html("");
$('.add_new').show();
});
});
}
function initIssuesSearch() {
var href = $('#issue_search_form').attr('action');
var last_terms = '';
......@@ -76,23 +36,15 @@ function issuesPage(){
$(this).closest("form").submit();
});
$("#new_issue_link").click(function(){
updateNewIssueURL();
});
$('body').on('ajax:success', '.close_issue, .reopen_issue, #new_issue', function(){
$('body').on('ajax:success', '.close_issue, .reopen_issue', function(){
var t = $(this),
totalIssues,
reopen = t.hasClass('reopen_issue'),
newIssue = false;
if( this.id == 'new_issue' ){
newIssue = true;
}
$('.issue_counter, #new_issue').each(function(){
reopen = t.hasClass('reopen_issue');
$('.issue_counter').each(function(){
var issue = $(this);
totalIssues = parseInt( $(this).html(), 10 );
if( newIssue || ( reopen && issue.closest('.main_menu').length ) ){
if( reopen && issue.closest('.main_menu').length ){
$(this).html( totalIssues+1 );
}else {
$(this).html( totalIssues-1 );
......@@ -126,20 +78,3 @@ function issuesCheckChanged() {
$('.issues_filters').show();
}
}
function updateNewIssueURL(){
var new_issue_link = $("#new_issue_link");
var milestone_id = $("#milestone_id").val();
var assignee_id = $("#assignee_id").val();
var new_href = "";
if(milestone_id){
new_href = "issue[milestone_id]=" + milestone_id + "&";
}
if(assignee_id){
new_href = new_href + "issue[assignee_id]=" + assignee_id;
}
if(new_href.length){
new_href = new_issue_link.attr("href") + "?" + new_href;
new_issue_link.attr("href", new_href);
}
};
......@@ -7,6 +7,18 @@ window.slugify = (text) ->
window.ajaxGet = (url) ->
$.ajax({type: "GET", url: url, dataType: "script"})
window.errorMessage = (message) ->
ehtml = $("<p>")
ehtml.addClass("error_message")
ehtml.html(message)
ehtml
window.split = (val) ->
return val.split( /,\s*/ )
window.extractLast = (term) ->
return split( term ).pop()
# Disable button if text field is empty
window.disableButtonIfEmptyField = (field_selector, button_selector) ->
field = $(field_selector)
......
......@@ -26,6 +26,12 @@ var MergeRequest = {
self.showState(data.state);
}, "json");
}
if(self.opts.ci_enable){
$.get(self.opts.url_to_ci_check, function(data){
self.showCiState(data.status);
}, "json");
}
},
initTabs:
......@@ -79,6 +85,11 @@ var MergeRequest = {
$(".automerge_widget." + state).show();
},
showCiState:
function(state){
$(".ci_widget").hide();
$(".ci_widget.ci-" + state).show();
},
loadDiff:
function() {
......
......@@ -18,10 +18,3 @@ $ ->
# Ref switcher
$('.project-refs-select').on 'change', ->
$(@).parents('form').submit()
class @GraphNav
@init: ->
$('.graph svg').css 'position', 'relative'
$('body').bind 'keyup', (e) ->
$('.graph svg').animate(left: '+=400') if e.keyCode is 37 # left
$('.graph svg').animate(left: '-=400') if e.keyCode is 39 # right
......@@ -6,5 +6,47 @@
*= require jquery.atwho
*= require chosen
*= require_self
*= require main
*/
/**
* GitLab bootstrap:
*/
@import "gitlab_bootstrap.scss";
@import "common.scss";
@import "ref_select.scss";
@import "sections/header.scss";
@import "sections/nav.scss";
@import "sections/commits.scss";
@import "sections/issues.scss";
@import "sections/projects.scss";
@import "sections/snippets.scss";
@import "sections/votes.scss";
@import "sections/merge_requests.scss";
@import "sections/graph.scss";
@import "sections/events.scss";
@import "sections/themes.scss";
@import "sections/tree.scss";
@import "sections/notes.scss";
@import "sections/profile.scss";
@import "sections/login.scss";
@import "sections/editor.scss";
@import "highlight/white.scss";
@import "highlight/dark.scss";
/**
* UI themes:
*/
@import "themes/ui_basic.scss";
@import "themes/ui_mars.scss";
@import "themes/ui_modern.scss";
@import "themes/ui_gray.scss";
@import "themes/ui_color.scss";
/**
* Styles for JS behaviors.
*/
@import "behaviors.scss";
......@@ -13,20 +13,12 @@ body {
margin: 0 0;
}
.container .sidebar {
width: 200px;
height: 100%;
min-height: 450px;
float: right;
}
.visible_link,
.author_link {
color: $link_color;
}
.help li { color:#111 }
.help li { color:$style_color; }
.back_link {
text-decoration: underline;
......@@ -65,6 +57,9 @@ table a code {
background: url(ajax_loader.gif) no-repeat center center;
width: 40px;
height: 40px;
&.loading-gray {
background: url(ajax_loader_gray.gif) no-repeat center center;
}
}
/** FLASH message **/
......@@ -96,28 +91,17 @@ table a code {
margin-right:50px
}
.handle:hover {
cursor: move;
}
span.update-author {
display: block;
}
span.update-author {
color: #999;
font-weight: normal;
font-style: italic;
}
span.update-author strong {
font-weight: bold;
font-style: normal;
strong {
font-weight: bold;
font-style: normal;
}
}
/** UPDATE ITEM **/
span.update-author {
display: block;
}
/** END UPDATE ITEM **/
.dashboard-loader {
float: left;
margin: 10px;
......@@ -264,21 +248,6 @@ input.git_clone_url {
}
/** bordered list **/
ul.bordered-list {
margin: 5px 0px;
padding: 0px;
li {
padding: 5px 0;
border-bottom: 1px solid #EEE;
overflow: hidden;
display: block;
margin: 0px;
}
}
ul.bordered-list li:last-child { border:none }
.line_holder {
&:hover {
td {
......@@ -316,98 +285,6 @@ p.time {
}
.ico {
background: url("images.png") no-repeat -85px -77px;
width: 19px;
height: 16px;
float: left;
position: relative;
margin-right: 10px;
top: 8px;
&.project {
background-position: -37px -77px;
}
&.activities {
background-position:-162px -22px;
}
&.projects {
background-position:-209px -21px;
}
}
.leftbar {
h5, .title {
padding: 5px 10px;
}
h4 {
font-size: 14px;
padding: 2px 10px;
color: #666;
border-bottom: 1px solid #f1f1f1;
}
a:last-child h4 { border: none; }
a:hover {
h4 {
color: #111;
background: $hover;
border-color: #CCC;
.ico.project {
background-position:-209px -21px;
}
}
}
.bottom {
padding: 10px;
}
}
.votes {
font-size: 13px;
line-height: 15px;
.progress {
height: 4px;
margin: 0;
.bar {
float: left;
height: 100%;
}
.bar-success {
@include linear-gradient(#62C462, #51A351);
background-color: #468847;
}
.bar-danger {
@include linear-gradient(#EE5F5B, #BD362F);
background-color: #B94A48;
}
}
.upvotes {
display: inline-block;
color: #468847;
}
.downvotes {
display: inline-block;
color: #B94A48;
}
}
.votes-block {
margin: 14px 6px 6px 0;
.downvotes {
float: right;
}
}
.votes-inline {
display: inline-block;
margin: 0 8px;
.progress {
display: inline-block;
padding: 0 0 2px;
width: 45px;
}
}
/* Fix for readme code (stopped it from being yellow) */
.readme {
......@@ -420,7 +297,6 @@ p.time {
}
}
.highlight_word {
background: #EEDC94;
}
......@@ -428,23 +304,16 @@ p.time {
.status_info {
font-size: 14px;
padding: 5px 15px;
line-height: 24px;
width: 60px;
line-height: 26px;
text-align: center;
float: left;
margin-right: 20px;
float: right;
position: relative;
top: -5px;
@include border-radius(4px);
&.success {
background: #5BB75B;
color: white;
text-shadow: 0 1px #111;
border-color: #9A9;
}
&.error {
background: #DA4E49;
border-color: #BD362F;
color: white;
text-shadow: 0 1px #111;
color: #FFF;
}
}
......@@ -463,16 +332,6 @@ p.time {
height: 150px;
}
.gitlab_pagination {
span a { color: $link_color; }
.prev, .next, .current, .page a {
padding: 10px;
}
.current {
border-bottom: 2px solid $style_color;
}
}
// Fixes alignment on notes.
.new_note {
label {
......@@ -647,9 +506,14 @@ pre {
}
}
.milestone .progress {
margin-bottom: 0;
margin-top: 4px;
.milestone {
&.milestone-closed {
background: #eee;
}
.progress {
margin-bottom: 0;
margin-top: 4px;
}
}
.float-link {
......
/** Override bootstrap variables **/
$baseFontSize: 13px !default;
$baseLineHeight: 18px !default;
// BOOTSTRAP
@import "bootstrap";
@import "bootstrap/responsive-utilities";
@import "bootstrap/responsive-1200px-min";
@import "font-awesome";
/**
* GitLab bootstrap.
* Overrides some styles of twitter bootstrap.
* Also give some common classes for GitLab app
*/
@import "gitlab_bootstrap/variables.scss";
@import "gitlab_bootstrap/fonts.scss";
@import "gitlab_bootstrap/mixins.scss";
@import "gitlab_bootstrap/common.scss";
@import "gitlab_bootstrap/typography.scss";
@import "gitlab_bootstrap/buttons.scss";
@import "gitlab_bootstrap/blocks.scss";
@import "gitlab_bootstrap/files.scss";
@import "gitlab_bootstrap/tables.scss";
@import "gitlab_bootstrap/lists.scss";
......@@ -31,6 +31,7 @@
.middle_box_content,
.bottom_box_content {
padding: 15px;
word-wrap: break-word;
pre {
background: none !important;
......@@ -40,6 +41,15 @@
}
}
.top_box_content {
.box-title {
color: $style_color;
font-size: 18px;
font-weight: normal;
line-height: 28px;
}
}
.middle_box_content {
@include border-radius(0);
border: none;
......@@ -64,7 +74,7 @@
border: 1px solid #eaeaea;
@include border-radius(4px);
border-color: #CCC;
@include solid-shade;
......@@ -83,6 +93,10 @@
border-top: 1px solid #eaeaea;
border-bottom: 1px solid #bbb;
> a {
text-shadow: 0 1px 1px #fff;
}
&.small {
line-height: 28px;
font-size: 14px;
......@@ -138,19 +152,6 @@
}
}
li, .wll {
padding: 10px;
&:first-child {
@include border-radius(4px 4px 0 0);
border-top: none;
}
&:last-child {
@include border-radius(0 0 4px 4px);
border: none;
}
}
.ui-box-body {
padding: 10px;
}
......
......@@ -10,11 +10,6 @@
/** COMMON CLASSES **/
.left { float:left }
.right { float:right!important }
.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 }
......@@ -30,6 +25,7 @@
.borders { border: 1px solid #ccc; @include shade; }
.hint { font-style: italic; color: #999; }
.light { color: #888 }
.tiny { font-weight: normal }
/** PILLS & TABS**/
.nav-pills a:hover { background-color: #888; }
......@@ -99,18 +95,21 @@ input[type='search'].search-text-input {
border: 1px solid #ccc;
}
fieldset legend { font-size: 17px; }
ul.nav.nav-projects-tabs {
@extend .nav-tabs;
input[type='text'].danger {
background: #F2DEDE!important;
border-color: #D66;
text-shadow: 0 1px 1px #fff
}
padding-left: 8px;
fieldset legend { font-size: 17px; }
li {
a {
padding: 4px 20px;
margin-top: 2px;
border-color: #DDD;
}
/** PAGINATION **/
.gitlab_pagination {
span a { color: $link_color; }
.prev, .next, .current, .page a {
padding: 10px;
}
.current {
border-bottom: 2px solid $style_color;
}
}
......@@ -43,11 +43,15 @@
padding: 0 4px;
}
padding: 20px;
h1, h2 {
line-height: 46px;
}
h3, h4 {
line-height: 40px;
h1 { font-size: 26px; line-height: 46px; }
h2 { font-size: 22px; line-height: 42px; }
h3 { font-size: 20px; line-height: 40px; }
h4 { font-size: 18px; line-height: 32px; }
h5 { font-size: 16px; line-height: 26px; }
.white .highlight pre {
background: #f5f5f5;
}
}
......
/** LISTS **/
ul {
/**
* List li block element #1
*
*/
.wll {
/**
* Well styled list
*
*/
.well-list {
margin: 0;
list-style: none;
li {
background-color: #FFF;
padding: 10px 5px;
padding: 10px;
min-height: 20px;
border-bottom: 1px solid #eee;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
&.disabled {
color: #888;
}
&.smoke { background-color: #f5f5f5; }
&:hover {
background: $hover;
border-bottom: 1px solid #ADF;
}
&:last-child { border:none }
&:first-child {
@include border-radius(4px 4px 0 0);
border-top: none;
}
&:last-child {
@include border-radius(0 0 4px 4px);
border: none;
}
.author { color: #999; }
p {
......@@ -29,6 +44,11 @@ ul {
top: 3px;
}
}
.well-title {
font-size: 14px;
line-height: 18px;
}
}
}
......@@ -39,3 +59,17 @@ ol, ul {
}
}
}
/** light list with border-bottom between li **/
ul.bordered-list {
margin: 5px 0px;
padding: 0px;
li {
padding: 5px 0;
border-bottom: 1px solid #EEE;
overflow: hidden;
display: block;
margin: 0px;
&:last-child { border:none }
}
}
......@@ -57,4 +57,13 @@
@mixin solid-shade {
@include box-shadow(0 0 0 3px #f1f1f1);
}
\ No newline at end of file
}
@mixin header-font {
color: $style_color;
text-shadow: 0 1px 1px #FFF;
font-family: 'Korolev', sans-serif;
font-size: 28px;
line-height: 48px;
font-weight: normal;
}
......@@ -2,4 +2,4 @@
$primary_color: #2FA0BB;
$link_color: #3A89A3;
$style_color: #474D57;
$hover: #D9EDF7;
\ No newline at end of file
$hover: #D9EDF7;
.black .highlight {
background-color: #333;
pre {
background-color: #333;
color: #eee;
background: inherit;
}
.hll { display: block; background-color: darken($hover, 65%) }
......
/*
* jQuery UI CSS Framework 1.8.7
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Theming/API
*/
/* Layout helpers
----------------------------------*/
.ui-helper-hidden { display: none; }
.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
.ui-helper-clearfix { display: inline-block; }
/* required comment for clearfix to work in Opera \*/
* html .ui-helper-clearfix { height:1%; }
.ui-helper-clearfix { display:block; }
/* end clearfix */
.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
/* Interaction Cues
----------------------------------*/
.ui-state-disabled { cursor: default !important; }
......@@ -140,26 +116,6 @@
/* Overlays */
.ui-widget-overlay { background: #262b33; opacity: .70;filter:Alpha(Opacity=70); }
.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #000000; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }
/*
* jQuery UI Resizable 1.8.7
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Resizable#theming
*/
.ui-resizable { position: relative;}
.ui-resizable-handle { position: absolute; font-size: 0.1px; z-index: 999; display: block;}
.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}
/*
* jQuery UI Selectable 1.8.7
*
......@@ -240,34 +196,7 @@
cursor: pointer;
font-weight: bold;
}
/*
* jQuery UI Slider 1.8.7
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Slider#theming
*/
.ui-slider { position: relative; text-align: left; background: #d7d7d7; z-index: 1; }
.ui-slider { -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.5) inset; -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.5) inset; box-shadow: 0 1px 2px rgba(0,0,0,0.5) inset; }
.ui-slider .ui-slider-handle { background: url(slider_handles.png) 0px -23px no-repeat; position: absolute; z-index: 2; width: 23px; height: 23px; cursor: default; border: none; outline: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; }
.ui-slider .ui-state-hover, .ui-slider .ui-state-active { background-position: 0 0; }
.ui-slider .ui-slider-range { background: #a3cae0; position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
.ui-slider .ui-slider-range { -moz-box-shadow: 0 1px 2px rgba(17,35,45,0.6) inset; -webkit-box-shadow: 0 1px 2px rgba(17,35,45,0.6) inset; box-shadow: 0 1px 2px rgba(17,35,45,0.6) inset; }
.ui-slider-horizontal { height: 5px; }
.ui-slider-horizontal .ui-slider-handle { top: -8px; margin-left: -13px; }
.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
.ui-slider-horizontal .ui-slider-range-min { left: 0; }
.ui-slider-horizontal .ui-slider-range-max { right: 0; }
.ui-slider-vertical { width: 5px; height: 100px; }
.ui-slider-vertical .ui-slider-handle { left: -8px; margin-left: 0; margin-bottom: -13px; }
.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
.ui-slider-vertical .ui-slider-range-max { top: 0; }
/*
* jQuery UI Datepicker 1.8.7
*
......@@ -326,45 +255,3 @@
.ui-datepicker table .ui-state-highlight { border-color: #ADE; }
.ui-datepicker-calendar .ui-state-default { background: transparent; border-color: #FFF; }
.ui-datepicker-calendar .ui-state-active { background: #D9EDF7; border-color: #ADE; color: #3A89A3; font-weight: bold; text-shadow: 0 1px 1px #fff; }
/* with multiple calendars */
.ui-datepicker.ui-datepicker-multi { width:auto; }
.ui-datepicker-multi .ui-datepicker-group { float:left; }
.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
.ui-datepicker-row-break { clear:both; width:100%; }
/* Extra Input Field Styling */
.ui-form textarea, .ui-form input:not([type="submit"]):not([type="button"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]) {
padding: 3px;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
border: 1px solid #cecece;
outline: none;
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.1) inset, 0 1px 0 rgba(255,255,255,0.2);
-moz-box-shadow: 0 1px 3px rgba(0,0,0,0.1) inset, 0 1px 0 rgba(255,255,255,0.2);
box-shadow: 0 1px 3px rgba(0,0,0,0.1) inset, 0 1px 0 rgba(255,255,255,0.2);
-webkit-transition: all 250ms ease-in-out;
-moz-transition: all 250ms ease-in-out;
-o-transition: all 250ms ease-in-out;
transition: all 250ms ease-in-out;
}
.ui-form textarea:hover, .ui-form input:not([type="submit"]):not([type="button"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):hover {
border: 1px solid #bdbdbd;
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.2) inset, 0 1px 0 rgba(255,255,255,0.2);
-moz-box-shadow: 0 1px 3px rgba(0,0,0,0.2) inset, 0 1px 0 rgba(255,255,255,0.2);
box-shadow: 0 1px 3px rgba(0,0,0,0.2) inset, 0 1px 0 rgba(255,255,255,0.2);
}
.ui-form textarea:focus, .ui-form input:not([type="submit"]):not([type="button"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):focus {
border: 1px solid #95bdd4;
-webkit-box-shadow: 0 2px 3px rgba(161,202,226,0.5) inset, 0 1px 0 rgba(255,255,255,0.2);
-moz-box-shadow: 0 2px 3px rgba(161,202,226,0.5) inset, 0 1px 0 rgba(255,255,255,0.2);
box-shadow: 0 2px 3px rgba(161,202,226,0.5) inset, 0 1px 0 rgba(255,255,255,0.2);
}
/** Override bootstrap variables **/
$baseFontSize: 13px !default;
$baseLineHeight: 18px !default;
// BOOTSTRAP
@import "bootstrap";
@import "bootstrap/responsive-utilities";
@import "bootstrap/responsive-1200px-min";
// FONT AWESOME
@import "font-awesome";
/**
* Variables
* Contains colors
*/
@import "variables.scss";
/**
* Custom fonts
* Contains @font-face font Korolev and default $monotype
*/
@import "fonts.scss";
/**
* General mixins.
* Contains rounded borders, gradients and shades
*/
@import "mixins.scss";
/**
* Header of application.
* Contain application logo, search panel, profile icon
*/
@import "sections/header.scss";
/**
* Navigation menu of application.
* Panel with links to pages depends on project, profile or admin area
*/
@import "sections/nav.scss";
/**
* This file represent some UI that can be changed
* during web app restyle or theme select.
*
* Next items should be placed there
* - link, button colors
* - header restyles
* - main menu restyles
*
*/
@import "themes/ui_basic.scss";
/**
* UI themes:
*/
@import "themes/ui_mars.scss";
@import "themes/ui_modern.scss";
@import "themes/ui_gray.scss";
@import "themes/ui_color.scss";
/**
* GitLab bootstrap.
* Overrides some styles of twitter bootstrap.
* Also give some common classes for GitLab app
*/
@import "gitlab_bootstrap/common.scss";
@import "gitlab_bootstrap/typography.scss";
@import "gitlab_bootstrap/buttons.scss";
@import "gitlab_bootstrap/blocks.scss";
@import "gitlab_bootstrap/files.scss";
@import "gitlab_bootstrap/tables.scss";
@import "gitlab_bootstrap/lists.scss";
/**
* Most of application styles placed here.
* This file represent common UI that should not be changed between themes
* or project restyling like form width or user avatar class or commit title
*
* TODO: clean it
*/
@import "common.scss";
/**
* Styles necessary to support JS behaviours.
*/
@import "behaviors.scss";
/**
* Styles related to specific part of app
*/
@import "sections/commits.scss";
@import "sections/issues.scss";
@import "sections/projects.scss";
@import "sections/merge_requests.scss";
@import "sections/graph.scss";
@import "sections/events.scss";
@import "sections/themes.scss";
/**
* This scss file redefine chozen selectbox styles for
* project Branch/Tag select element
*/
@import "ref_select.scss";
/**
* Code (files list) styles. Browsing project files there
*/
@import "sections/tree.scss";
/**
* This file represent notes(comments) styles
*/
@import "sections/notes.scss";
/**
* This file represent profile styles
*/
@import "sections/profile.scss";
/**
* Devise styles
*/
@import "sections/login.scss";
/**
* CODE HIGHTLIGHT BASE
*
*/
@import "highlight/white.scss";
/**
* CODE HIGHTLIGHT DARK schema
*
*/
@import "highlight/dark.scss";
/**
* File Editor styles
*
*/
@import "sections/editor.scss";
......@@ -232,8 +232,6 @@
/** COMMIT ROW **/
.commit {
@extend .wll;
.browse_code_link_holder {
@extend .span2;
float: right;
......@@ -305,3 +303,17 @@
color: #fff;
font-family: $monospace;
}
.commits-compare-switch{
background: url("switch_icon.png") no-repeat center center;
width: 16px;
height: 18px;
text-indent: -9999px;
float: left;
margin-right: 9px;
border: 1px solid #DDD;
@include border-radius(4px);
padding: 4px;
background-color: #EEE;
}
......@@ -31,7 +31,6 @@
*
*/
.event-item {
min-height: 40px;
border-bottom: 1px solid #eee;
.event-title {
color: #333;
......@@ -50,14 +49,18 @@
}
}
.avatar {
width: 32px;
position: relative;
top: -3px;
}
.event_icon {
position: relative;
float: right;
border: 1px solid #EEE;
padding: 5px;
@include border-radius(5px);
background: #F9F9F9;
margin-left: 10px;
top: -6px;
img {
width: 20px;
}
......@@ -71,9 +74,8 @@
}
}
padding: 15px 5px;
padding: 16px 5px;
&:last-child { border:none }
.wll:hover { background:none }
.event_commits {
margin-top: 5px;
......
......@@ -44,14 +44,9 @@ header {
background: url('logo_dark.png') no-repeat 0px 2px;
float: left;
margin-left: 2px;
font-size: 30px;
line-height: 48px;
font-weight: normal;
color: $style_color;
text-shadow: 0 1px 1px #FFF;
padding-left: 45px;
height: 40px;
font-family: 'Korolev', sans-serif;
@include header-font;
}
}
}
......@@ -66,12 +61,7 @@ header {
float: left;
margin: 0;
margin-right: 30px;
font-size: 30px;
line-height: 48px;
font-weight: normal;
color: $style_color;
text-shadow: 0 1px 1px #FFF;
font-family: 'Korolev', sans-serif;
@include header-font;
}
/**
......@@ -172,7 +162,7 @@ header {
display: none;
z-index: 100000;
@include border-radius(4px);
width: 100px;
width: 130px;
position: absolute;
right: 5px;
top: 38px;
......@@ -181,7 +171,7 @@ header {
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
a {
color: #fff;
padding: 7px 10px;
padding: 12px 15px;
display: block;
text-shadow: none;
border-bottom: 1px solid #666;
......@@ -204,8 +194,8 @@ header {
}
&:last-child {
@include border-radius(0 0 5px 5px);
border-bottom: 0;
}
border-bottom: 0;
}
}
......
......@@ -121,12 +121,3 @@ input.check_all_issues {
#update_status {
width: 100px;
}
/**
* Milestones list
*
*/
.milestone {
@extend .wll;
}
......@@ -136,9 +136,3 @@ li.merge_request {
}
}
}
.status-badge {
height: 32px;
width: 100%;
@include border-radius(5px);
}
......@@ -3,15 +3,13 @@
*
*/
ul.main_menu {
@include border-radius(4px);
margin: auto;
margin: 30px 0;
border: 1px solid #BBB;
margin-top: 10px;
border-bottom: 1px solid #DDD;
height: 37px;
@include bg-gray-gradient;
position: relative;
overflow: hidden;
@include shade;
.count {
position: relative;
top: -1px;
......@@ -24,9 +22,6 @@ ul.main_menu {
line-height: 14px;
text-align: center;
color: #777;
background: #f2f2f2;
border-top: 1px solid #CCC;
@include border-radius(8px);
}
.label {
background: $hover;
......@@ -38,23 +33,10 @@ ul.main_menu {
margin: 0;
display: table-cell;
width: 1%;
border-right: 1px solid #DDD;
border-left: 1px solid #EEE;
border-bottom: 2px solid #CFCFCF;
&:first-child{
@include border-radius(5px 0 0 5px);
border-left: 0;
}
&.active {
background-color: #D5D5D5;
border-right: 1px solid #BBB;
border-left: 1px solid #BBB;
@include border-radius(0 0 1px 1px);
&:first-child{
border-bottom: none;
border-left: none;
border-bottom: 2px solid #474D57;
a {
color: $style_color;
}
}
......@@ -73,10 +55,10 @@ ul.main_menu {
a {
display: block;
text-align: center;
font-weight: bold;
font-weight: normal;
height: 35px;
line-height: 36px;
color: $style_color;
color: #777;
text-shadow: 0 1px 1px white;
padding: 0 10px;
}
......
......@@ -4,12 +4,11 @@
}
.side {
@extend .span4;
@extend .right;
.groups_box,
.projects_box {
h5 {
> h5 {
color: $style_color;
font-size: 16px;
text-shadow: 0 1px 1px #fff;
......@@ -17,37 +16,22 @@
line-height: 32px;
font-size: 14px;
}
ul {
li {
padding: 0;
a {
display: block;
.group_name {
font-size: 14px;
line-height: 18px;
}
.project_name {
color: #4fa2bd;
font-size: 14px;
line-height: 18px;
}
.arrow {
float: right;
padding: 10px;
margin: 0;
}
.last_activity {
padding-top: 5px;
display: block;
span, strong {
font-size: 12px;
color: #666;
}
}
.nav-projects-tabs li { padding: 0; }
.well-list {
.arrow {
float: right;
padding: 10px;
margin: 0;
}
.last_activity {
padding-top: 5px;
display: block;
span, strong {
font-size: 12px;
color: #666;
}
}
}
@extend .leftbar;
@extend .ui-box;
}
}
......@@ -117,3 +101,25 @@
}
}
ul.nav.nav-projects-tabs {
@extend .nav-tabs;
padding-left: 8px;
li {
a {
padding: 4px 20px;
margin-top: 2px;
border-color: #DDD;
background-color: #EEE;
text-shadow: 0 1px 1px white;
color: #555;
}
&.active {
a {
font-weight: bold;
}
}
}
}
.snippet.file_holder {
.file_title {
.snippet-file-name {
position: relative;
top: -4px;
left: -4px;
}
}
}
.votes {
font-size: 13px;
line-height: 15px;
.progress {
height: 4px;
margin: 0;
.bar {
float: left;
height: 100%;
}
.bar-success {
@include linear-gradient(#62C462, #51A351);
background-color: #468847;
}
.bar-danger {
@include linear-gradient(#EE5F5B, #BD362F);
background-color: #B94A48;
}
}
.upvotes {
display: inline-block;
color: #468847;
}
.downvotes {
display: inline-block;
color: #B94A48;
}
}
.votes-block {
margin: 14px 6px 6px 0;
.downvotes {
float: right;
}
}
.votes-inline {
display: inline-block;
margin: 0 8px;
.progress {
display: inline-block;
padding: 0 0 2px;
width: 45px;
}
}
......@@ -4,18 +4,6 @@
*
*/
.ui_basic {
/*
* Common styles
*
*/
a {
color: $link_color;
&:hover {
text-decoration: none;
color: $primary_color;
}
}
.app_logo {
.separator {
margin-left: 0;
......
......@@ -2,7 +2,9 @@ class ProjectUpdateContext < BaseContext
def execute(role = :default)
namespace_id = params[:project].delete(:namespace_id)
if namespace_id.present?
allowed_transfer = can?(current_user, :change_namespace, project) || role == :admin
if allowed_transfer && namespace_id.present?
if namespace_id == Namespace.global_id
if project.namespace.present?
# Transfer to global namespace from anyone
......
......@@ -2,7 +2,7 @@ class Admin::GroupsController < AdminController
before_filter :group, only: [:edit, :show, :update, :destroy, :project_update]
def index
@groups = Group.scoped
@groups = Group.order('name ASC')
@groups = @groups.search(params[:name]) if params[:name].present?
@groups = @groups.page(params[:page]).per(20)
end
......@@ -11,6 +11,7 @@ class Admin::GroupsController < AdminController
@projects = Project.scoped
@projects = @projects.not_in_group(@group) if @group.projects.present?
@projects = @projects.all
@projects.reject!(&:empty_repo?)
end
def new
......
......@@ -4,12 +4,13 @@ class Admin::ProjectsController < AdminController
def index
@projects = Project.scoped
@projects = @projects.where(namespace_id: params[:namespace_id]) if params[:namespace_id].present?
@projects = @projects.where(namespace_id: nil) if params[:namespace_id] == Namespace.global_id
@projects = @projects.search(params[:name]) if params[:name].present?
@projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20)
end
def show
@users = User.scoped
@users = User.active
@users = @users.not_in_project(@project) if @project.users.present?
@users = @users.all
end
......
......@@ -3,7 +3,7 @@ class Admin::UsersController < AdminController
@admin_users = User.scoped
@admin_users = @admin_users.filter(params[:filter])
@admin_users = @admin_users.search(params[:name]) if params[:name].present?
@admin_users = @admin_users.order("updated_at DESC").page(params[:page])
@admin_users = @admin_users.order("name ASC").page(params[:page])
end
def show
......@@ -30,7 +30,7 @@ class Admin::UsersController < AdminController
def new
@admin_user = User.new({ projects_limit: Gitlab.config.default_projects_limit }, as: :admin)
@admin_user = User.new({ projects_limit: Gitlab.config.gitlab.default_projects_limit }, as: :admin)
end
def edit
......
......@@ -112,6 +112,10 @@ class ApplicationController < ActionController::Base
render file: Rails.root.join("public", "404"), layout: false, status: "404"
end
def render_403
render file: Rails.root.join("public", "403"), layout: false, status: "403"
end
def require_non_empty_project
redirect_to @project if @project.empty_repo?
end
......
......@@ -7,6 +7,8 @@ class DashboardController < ApplicationController
def index
@groups = current_user.authorized_groups
@has_authorized_projects = @projects.count > 0
@projects = case params[:scope]
when 'personal' then
@projects.personal(current_user)
......
......@@ -21,7 +21,7 @@ class GroupsController < ApplicationController
# Get authored or assigned open merge requests
def merge_requests
@merge_requests = current_user.cared_merge_requests
@merge_requests = current_user.cared_merge_requests.opened
@merge_requests = @merge_requests.of_group(@group).recent.page(params[:page]).per(20)
end
......@@ -49,6 +49,7 @@ class GroupsController < ApplicationController
def people
@project = group.projects.find(params[:project_id]) if params[:project_id]
@users = @project ? @project.users : group.users
@users.sort_by!(&:name)
if @project
@team_member = @project.users_projects.new
......
class IssuesController < ProjectResourceController
before_filter :module_enabled
before_filter :issue, only: [:edit, :update, :destroy, :show]
before_filter :issue, only: [:edit, :update, :show]
# Allow read any issue
before_filter :authorize_read_issue!
......@@ -11,9 +11,6 @@ class IssuesController < ProjectResourceController
# Allow modify issue
before_filter :authorize_modify_issue!, only: [:edit, :update]
# Allow destroy issue
before_filter :authorize_admin_issue!, only: [:destroy]
respond_to :js, :html
def index
......@@ -79,15 +76,6 @@ class IssuesController < ProjectResourceController
end
end
def destroy
@issue.destroy
respond_to do |format|
format.html { redirect_to project_issues_path }
format.js { render nothing: true }
end
end
def sort
return render_404 unless can?(current_user, :admin_issue, @project)
......
class MergeRequestsController < ProjectResourceController
before_filter :module_enabled
before_filter :merge_request, only: [:edit, :update, :destroy, :show, :commits, :diffs, :automerge, :automerge_check]
before_filter :merge_request, only: [:edit, :update, :show, :commits, :diffs, :automerge, :automerge_check, :ci_status]
before_filter :validates_merge_request, only: [:show, :diffs]
before_filter :define_show_vars, only: [:show, :diffs]
......@@ -13,9 +13,6 @@ class MergeRequestsController < ProjectResourceController
# Allow modify merge_request
before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort]
# Allow destroy merge_request
before_filter :authorize_admin_merge_request!, only: [:destroy]
def index
@merge_requests = MergeRequestsLoadContext.new(project, current_user, params).execute
end
......@@ -90,14 +87,6 @@ class MergeRequestsController < ProjectResourceController
end
end
def destroy
@merge_request.destroy
respond_to do |format|
format.html { redirect_to project_merge_requests_url(@project) }
end
end
def branch_from
@commit = project.commit(params[:ref])
@commit = CommitDecorator.decorate(@commit)
......@@ -108,6 +97,13 @@ class MergeRequestsController < ProjectResourceController
@commit = CommitDecorator.decorate(@commit)
end
def ci_status
status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha)
response = { status: status }
render json: response
end
protected
def merge_request
......
......@@ -12,11 +12,12 @@ class MilestonesController < ProjectResourceController
def index
@milestones = case params[:f]
when 'all'; @project.milestones
else @project.milestones.active
when 'all'; @project.milestones.order("closed, due_date DESC")
when 'closed'; @project.milestones.closed.order("due_date DESC")
else @project.milestones.active.order("due_date ASC")
end
@milestones = @milestones.includes(:project).order("due_date")
@milestones = @milestones.includes(:project)
@milestones = @milestones.page(params[:page]).per(20)
end
......@@ -42,6 +43,7 @@ class MilestonesController < ProjectResourceController
def create
@milestone = @project.milestones.new(params[:milestone])
@milestone.author_id_of_changes = current_user.id
if @milestone.save
redirect_to project_milestone_path(@project, @milestone)
......@@ -51,7 +53,7 @@ class MilestonesController < ProjectResourceController
end
def update
@milestone.update_attributes(params[:milestone])
@milestone.update_attributes(params[:milestone].merge(author_id_of_changes: current_user.id))
respond_to do |format|
format.js
......
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
Gitlab.config.omniauth_providers.each do |provider|
Gitlab.config.omniauth.providers.each do |provider|
define_method provider['name'] do
handle_omniauth
end
......
......@@ -46,6 +46,10 @@ class ProjectsController < ProjectResourceController
format.js
end
end
rescue Project::TransferError => ex
@error = ex
render :update_failed
end
def show
......@@ -54,12 +58,12 @@ class ProjectsController < ProjectResourceController
respond_to do |format|
format.html do
unless @project.empty_repo?
@last_push = current_user.recent_push(@project.id)
render :show
else
render "projects/empty"
end
unless @project.empty_repo?
@last_push = current_user.recent_push(@project.id)
render :show
else
render "projects/empty"
end
end
format.js
end
......@@ -86,12 +90,18 @@ class ProjectsController < ProjectResourceController
end
def graph
graph = Gitlab::Graph::JsonBuilder.new(project)
@days_json, @commits_json = graph.days_json, graph.commits_json
respond_to do |format|
format.html
format.json do
graph = Gitlab::Graph::JsonBuilder.new(project)
render :json => graph.to_json
end
end
end
def destroy
return access_denied! unless can?(current_user, :remove_project, project)
# 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)
......
......@@ -16,7 +16,7 @@ class SnippetsController < ProjectResourceController
respond_to :html
def index
@snippets = @project.snippets
@snippets = @project.snippets.fresh
end
def new
......@@ -62,7 +62,7 @@ class SnippetsController < ProjectResourceController
redirect_to project_snippets_path(@project)
end
def raw
def raw
send_data(
@snippet.content,
type: "text/plain",
......
......@@ -76,7 +76,7 @@ class CommitDecorator < ApplicationDecorator
source_name = send "#{options[:source]}_name".to_sym
source_email = send "#{options[:source]}_email".to_sym
text = if options[:avatar]
avatar = h.image_tag h.gravatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size]
avatar = h.image_tag h.gravatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: ""
%Q{#{avatar} <span class="commit-#{options[:source]}-name">#{source_name}</span>}
else
source_name
......
require 'digest/md5'
require 'uri'
module ApplicationHelper
......@@ -30,13 +31,15 @@ module ApplicationHelper
args.any? { |v| v.to_s.downcase == action_name }
end
def gravatar_icon(user_email = '', size = 40)
if Gitlab.config.disable_gravatar? || user_email.blank?
def gravatar_icon(user_email = '', size = nil)
size = 40 if size.nil? || size <= 0
if !Gitlab.config.gravatar.enabled || user_email.blank?
'no_avatar.png'
else
gravatar_prefix = request.ssl? ? "https://secure" : "http://www"
gravatar_url = request.ssl? ? Gitlab.config.gravatar.ssl_url : Gitlab.config.gravatar.plain_url
user_email.strip!
"#{gravatar_prefix}.gravatar.com/avatar/#{Digest::MD5.hexdigest(user_email.downcase)}?s=#{size}&d=mm"
sprintf(gravatar_url, {:hash => Digest::MD5.hexdigest(user_email.downcase), :email => URI.escape(user_email), :size => size})
end
end
......@@ -45,7 +48,7 @@ module ApplicationHelper
end
def web_app_url
"#{request_protocol}://#{Gitlab.config.web_host}/"
"#{request_protocol}://#{Gitlab.config.gitlab.host}/"
end
def last_commit(project)
......@@ -92,6 +95,7 @@ module ApplicationHelper
{ label: "API Help", url: help_api_path },
{ label: "Markdown Help", url: help_markdown_path },
{ label: "SSH Keys Help", url: help_ssh_path },
{ label: "Gitlab Rake Tasks Help", url: help_raketasks_path },
]
project_nav = []
......
......@@ -4,28 +4,6 @@ module IssuesHelper
project_issues_path project, params
end
def link_to_issue_assignee(issue)
project = issue.project
tm = project.team_member_by_id(issue.assignee_id)
if tm
link_to issue.assignee_name, project_team_member_path(project, tm), class: "author_link"
else
issue.assignee_name
end
end
def link_to_issue_author(issue)
project = issue.project
tm = project.team_member_by_id(issue.author_id)
if tm
link_to issue.author_name, project_team_member_path(project, tm), class: "author_link"
else
issue.author_name
end
end
def issue_css_classes issue
classes = "issue"
classes << " closed" if issue.closed
......@@ -52,4 +30,14 @@ module IssuesHelper
open: "open"
}
end
def labels_autocomplete_source
labels = @project.issues_labels.order('count DESC')
labels = labels.map{ |l| { label: l.name, value: l.name } }
labels.to_json
end
def issues_active_milestones
@project.milestones.active.order("id desc").all
end
end
module MergeRequestsHelper
def link_to_merge_request_assignee(merge_request)
project = merge_request.project
tm = project.team_member_by_id(merge_request.assignee_id)
if tm
link_to merge_request.assignee_name, project_team_member_path(project, tm), class: "author_link"
else
merge_request.assignee_name
end
end
def link_to_merge_request_author(merge_request)
project = merge_request.project
tm = project.team_member_by_id(merge_request.author_id)
if tm
link_to merge_request.author_name, project_team_member_path(project, tm), class: "author_link"
else
merge_request.author_name
end
end
def new_mr_path_from_push_event(event)
new_project_merge_request_path(
event.project,
......@@ -39,7 +17,7 @@ module MergeRequestsHelper
classes
end
def ci_status_path
@project.gitlab_ci_service.commit_badge_path(@merge_request.last_commit.sha)
def ci_build_details_path merge_request
merge_request.project.gitlab_ci_service.build_page(merge_request.last_commit.sha)
end
end
......@@ -8,11 +8,49 @@ module ProjectsHelper
end
def link_to_project project
link_to project.name, project
link_to project do
title = content_tag(:strong, project.name)
if project.namespace
namespace = content_tag(:span, "#{project.namespace.human_name} / ", class: 'tiny')
title = namespace + title
end
title
end
end
def link_to_member(project, author)
return "(deleted)" unless author
# Build avatar image tag
avatar = image_tag(gravatar_icon(author.try(:email)), width: 16, class: "lil_av")
# Build name strong tag
name = content_tag :strong, author.name, class: 'author'
author_html = avatar + name
tm = project.team_member_by_id(author)
content_tag :span, class: 'member-link' do
if tm
link_to author_html, project_team_member_path(project, tm), class: "author_link"
else
author_html
end
end
end
def tm_path team_member
project_team_member_path(@project, team_member)
end
end
def project_title project
if project.group
project.name_with_namespace
else
project.name
end
end
end
......@@ -72,7 +72,7 @@ module TabHelper
return "active" if current_page?(controller: "projects", action: action, id: @project)
end
if ['snippets', 'hooks', 'deploy_keys', 'team_members'].include? controller.controller_name
if ['snippets', 'services', 'hooks', 'deploy_keys', 'team_members'].include? controller.controller_name
"active"
end
end
......
......@@ -3,11 +3,11 @@ class Notify < ActionMailer::Base
add_template_helper ApplicationHelper
add_template_helper GitlabMarkdownHelper
default_url_options[:host] = Gitlab.config.web_host
default_url_options[:protocol] = Gitlab.config.web_protocol
default_url_options[:port] = Gitlab.config.web_port if Gitlab.config.web_custom_port?
default_url_options[:host] = Gitlab.config.gitlab.host
default_url_options[:protocol] = Gitlab.config.gitlab.protocol
default_url_options[:port] = Gitlab.config.gitlab.port if Gitlab.config.gitlab_on_non_standard_port?
default from: Gitlab.config.email_from
default from: Gitlab.config.gitlab.email_from
......@@ -31,6 +31,7 @@ class Notify < ActionMailer::Base
def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id)
@issue = Issue.find issue_id
@issue_status = status
@project = @issue.project
@updated_by = User.find updated_by_user_id
mail(to: recipient(recipient_id),
subject: subject("changed issue ##{@issue.id}", @issue.title))
......@@ -102,6 +103,12 @@ class Notify < ActionMailer::Base
end
def project_was_moved_email(user_project_id)
@users_project = UsersProject.find user_project_id
@project = @users_project.project
mail(to: @users_project.user.email,
subject: subject("project was moved"))
end
#
# User
......
......@@ -17,9 +17,7 @@ class Ability
# Rules based on role in project
if project.master_access_for?(user)
# TODO: replace with master rules.
# Only allow project administration for namespace owners
rules << project_admin_rules
rules << project_master_rules
elsif project.dev_access_for?(user)
rules << project_dev_rules
......@@ -93,13 +91,16 @@ class Ability
:admin_merge_request,
:admin_note,
:accept_mr,
:admin_wiki
:admin_wiki,
:admin_project
]
end
def project_admin_rules
project_master_rules + [
:admin_project
:change_namespace,
:rename_project,
:remove_project
]
end
......
......@@ -87,14 +87,10 @@ class Commit
last = project.commit(from.try(:strip))
if first && last
commits = [first, last].sort_by(&:created_at)
younger = commits.first
older = commits.last
result[:same] = (younger.id == older.id)
result[:commits] = project.repo.commits_between(younger.id, older.id).map {|c| Commit.new(c)}
result[:diffs] = project.repo.diff(younger.id, older.id) rescue []
result[:commit] = Commit.new(older)
result[:same] = (first.id == last.id)
result[:commits] = project.repo.commits_between(last.id, first.id).map {|c| Commit.new(c)}
result[:diffs] = project.repo.diff(last.id, first.id) rescue []
result[:commit] = Commit.new(first)
end
result
......@@ -163,6 +159,8 @@ class Commit
while !lines.first.start_with?("diff --git") do
lines.shift
end
lines.pop if lines.last =~ /^[\d.]+$/ # Git version
lines.pop if lines.last == "-- " # end of diff
lines.join("\n")
end
end
......@@ -15,6 +15,7 @@
#
class Event < ActiveRecord::Base
include NoteEvent
include PushEvent
attr_accessible :project, :action, :data, :author_id, :project_id,
......@@ -58,12 +59,14 @@ class Event < ActiveRecord::Base
end
end
# Next events currently enabled for system
# - push
# - new issue
# - merge request
def allowed?
push? || issue? || merge_request? || membership_changed?
def proper?
if push?
true
elsif membership_changed?
true
else
(issue? || merge_request? || note? || milestone?) && target
end
end
def project_name
......@@ -94,6 +97,14 @@ class Event < ActiveRecord::Base
action == self.class::Reopened
end
def milestone?
target_type == "Milestone"
end
def note?
target_type == "Note"
end
def issue?
target_type == "Issue"
end
......
......@@ -36,4 +36,22 @@ class GitlabCiService < Service
def commit_badge_path sha
project_url + "/status?sha=#{sha}"
end
def commit_status_path sha
project_url + "/builds/#{sha}/status.json?token=#{token}"
end
def commit_status sha
response = HTTParty.get(commit_status_path(sha))
if response.code == 200 and response["status"]
response["status"]
else
:error
end
end
def build_page sha
project_url + "/builds/#{sha}"
end
end
......@@ -204,7 +204,7 @@ class MergeRequest < ActiveRecord::Base
def mr_and_commit_notes
commit_ids = commits.map(&:id)
Note.where("(noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR (noteable_type = 'Commit' AND noteable_id IN (:commit_ids))", mr_id: id, commit_ids: commit_ids)
Note.where("(noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR (noteable_type = 'Commit' AND commit_id IN (:commit_ids))", mr_id: id, commit_ids: commit_ids)
end
# Returns the raw diff for this merge request
......@@ -220,4 +220,8 @@ class MergeRequest < ActiveRecord::Base
def to_patch
project.repo.git.format_patch({timeout: 30, raise: true, stdout: true}, "#{target_branch}..#{source_branch}")
end
def last_commit_short_sha
@last_commit_short_sha ||= last_commit.sha[0..10]
end
end
......@@ -13,18 +13,26 @@
#
class Milestone < ActiveRecord::Base
attr_accessible :title, :description, :due_date, :closed
attr_accessible :title, :description, :due_date, :closed, :author_id_of_changes
attr_accessor :author_id_of_changes
belongs_to :project
has_many :issues
has_many :merge_requests
scope :active, where(closed: false)
scope :closed, where(closed: true)
validates :title, presence: true
validates :project, presence: true
validates :closed, inclusion: { in: [true, false] }
def self.active
where("due_date > ? OR due_date IS NULL", Date.today)
def expired?
if due_date
due_date < Date.today
else
false
end
end
def participants
......@@ -52,4 +60,20 @@ class Milestone < ActiveRecord::Base
def expires_at
"expires at #{due_date.stamp("Aug 21, 2011")}" if due_date
end
def can_be_closed?
open? && issues.opened.count.zero?
end
def is_empty?
total_items_count.zero?
end
def open?
!closed
end
def author_id
author_id_of_changes
end
end
......@@ -48,23 +48,30 @@ class Namespace < ActiveRecord::Base
end
def ensure_dir_exist
namespace_dir_path = File.join(Gitlab.config.git_base_path, path)
namespace_dir_path = File.join(Gitlab.config.gitolite.repos_path, path)
system("mkdir -m 770 #{namespace_dir_path}") unless File.exists?(namespace_dir_path)
end
def move_dir
if path_changed?
old_path = File.join(Gitlab.config.git_base_path, path_was)
new_path = File.join(Gitlab.config.git_base_path, path)
old_path = File.join(Gitlab.config.gitolite.repos_path, path_was)
new_path = File.join(Gitlab.config.gitolite.repos_path, path)
if File.exists?(new_path)
raise "Already exists"
end
system("mv #{old_path} #{new_path}")
if system("mv #{old_path} #{new_path}")
send_update_instructions
end
end
end
def rm_dir
dir_path = File.join(Gitlab.config.git_base_path, path)
dir_path = File.join(Gitlab.config.gitolite.repos_path, path)
system("rm -rf #{dir_path}")
end
def send_update_instructions
projects.each(&:send_move_instructions)
end
end
......@@ -19,7 +19,7 @@ require 'file_size_validator'
class Note < ActiveRecord::Base
attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id,
:attachment, :line_code
:attachment, :line_code, :commit_id
attr_accessor :notify
attr_accessor :notify_author
......@@ -35,10 +35,14 @@ class Note < ActiveRecord::Base
validates :line_code, format: { with: /\A\d+_\d+_\d+\Z/ }, allow_blank: true
validates :attachment, file_size: { maximum: 10.megabytes.to_i }
validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' }
validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' }
mount_uploader :attachment, AttachmentUploader
# Scopes
scope :common, ->{ where(noteable_id: nil) }
scope :for_commits, ->{ where(noteable_type: "Commit") }
scope :common, ->{ where(noteable_id: nil, commit_id: nil) }
scope :today, ->{ where("created_at >= :date", date: Date.today) }
scope :last_week, ->{ where("created_at >= :date", date: (Date.today - 7.days)) }
scope :since, ->(day) { where("created_at >= :date", date: (day)) }
......@@ -122,7 +126,7 @@ class Note < ActiveRecord::Base
# override to return commits, which are not active record
def noteable
if for_commit?
project.commit(noteable_id)
project.commit(commit_id)
else
super
end
......@@ -151,4 +155,12 @@ class Note < ActiveRecord::Base
def votable?
for_issue? || (for_merge_request? && !for_diff_line?)
end
def noteable_type_name
if noteable_type.present?
noteable_type.downcase
else
"wall"
end
end
end
......@@ -25,6 +25,9 @@ class Project < ActiveRecord::Base
include PushObserver
include Authority
include Team
include NamespacedProject
class TransferError < StandardError; end
attr_accessible :name, :path, :description, :default_branch, :issues_enabled,
:wall_enabled, :merge_requests_enabled, :wiki_enabled, as: [:default, :admin]
......@@ -36,6 +39,10 @@ class Project < ActiveRecord::Base
# Relations
belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'"
belongs_to :namespace
# TODO: replace owner with creator.
# With namespaces a project owner will be a namespace owner
# so this field makes sense only for global projects
belongs_to :owner, class_name: "User"
has_many :users, through: :users_projects
has_many :events, dependent: :destroy
......@@ -97,7 +104,7 @@ class Project < ActiveRecord::Base
namespace_id = Namespace.find_by_path(id.first).id
where(namespace_id: namespace_id).find_by_path(id.last)
else
find_by_path(id)
where(path: id, namespace_id: nil).last
end
end
......@@ -172,7 +179,7 @@ class Project < ActiveRecord::Base
end
def repo_name
denied_paths = %w(gitolite-admin groups projects dashboard)
denied_paths = %w(gitolite-admin admin dashboard groups help profile projects search)
if denied_paths.include?(path)
errors.add(:path, "like #{path} is not allowed")
......@@ -188,7 +195,7 @@ class Project < ActiveRecord::Base
end
def web_url
[Gitlab.config.url, path].join("/")
[Gitlab.config.gitlab.url, path_with_namespace].join("/")
end
def common_notes
......@@ -196,15 +203,15 @@ class Project < ActiveRecord::Base
end
def build_commit_note(commit)
notes.new(noteable_id: commit.id, noteable_type: "Commit")
notes.new(commit_id: commit.id, noteable_type: "Commit")
end
def commit_notes(commit)
notes.where(noteable_id: commit.id, noteable_type: "Commit").where('line_code IS NULL OR line_code = ""')
notes.where(commit_id: commit.id, noteable_type: "Commit").where('line_code IS NULL OR line_code = ""')
end
def commit_line_notes(commit)
notes.where(noteable_id: commit.id, noteable_type: "Commit").where("line_code IS NOT NULL")
notes.where(commit_id: commit.id, noteable_type: "Commit").where("line_code IS NOT NULL")
end
def public?
......@@ -239,51 +246,11 @@ class Project < ActiveRecord::Base
gitlab_ci_service && gitlab_ci_service.active
end
def path_with_namespace
if namespace
namespace.path + '/' + path
else
path
end
end
# For compatibility with old code
def code
path
end
def transfer(new_namespace)
Project.transaction do
old_namespace = namespace
self.namespace = new_namespace
old_dir = old_namespace.try(:path) || ''
new_dir = new_namespace.try(:path) || ''
old_repo = if old_dir.present?
File.join(old_dir, self.path)
else
self.path
end
Gitlab::ProjectMover.new(self, old_dir, new_dir).execute
git_host.move_repository(old_repo, self)
save!
end
end
def name_with_namespace
@name_with_namespace ||= begin
if namespace
namespace.human_name + " / " + name
else
name
end
end
end
def items_for entity
case entity
when 'issue' then
......@@ -293,7 +260,9 @@ class Project < ActiveRecord::Base
end
end
def namespace_owner
namespace.try(:owner)
def send_move_instructions
self.users_projects.each do |member|
Notify.project_was_moved_email(member.id).deliver
end
end
end
......@@ -22,7 +22,7 @@ class Snippet < ActiveRecord::Base
belongs_to :author, class_name: "User"
has_many :notes, as: :noteable, dependent: :destroy
delegate :name, :email, to: :author, prefix: true
delegate :name, :email, to: :author, prefix: true, allow_nil: true
validates :author, presence: true
validates :project, presence: true
......
......@@ -56,12 +56,12 @@ class User < ActiveRecord::Base
has_many :issues, foreign_key: :author_id, dependent: :destroy
has_many :notes, foreign_key: :author_id, dependent: :destroy
has_many :merge_requests, foreign_key: :author_id, dependent: :destroy
has_many :my_own_projects, class_name: "Project", foreign_key: :owner_id
has_many :events, class_name: "Event", foreign_key: :author_id, dependent: :destroy
has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC"
has_many :assigned_issues, class_name: "Issue", foreign_key: :assignee_id, dependent: :destroy
has_many :assigned_merge_requests, class_name: "MergeRequest", foreign_key: :assignee_id, dependent: :destroy
validates :name, presence: true
validates :bio, length: { within: 0..255 }
validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider}
validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0}
......@@ -123,16 +123,4 @@ class User < ActiveRecord::Base
self.password = self.password_confirmation = Devise.friendly_token.first(8)
end
end
def authorized_groups
@authorized_groups ||= begin
groups = Group.where(id: self.projects.pluck(:namespace_id)).all
groups = groups + self.groups
groups.uniq
end
end
def authorized_projects
Project.authorized_for(self)
end
end
......@@ -28,6 +28,7 @@ class UsersProject < ActiveRecord::Base
validates :user, presence: true
validates :user_id, uniqueness: { :scope => [:project_id], message: "already exists in project" }
validates :project_access, inclusion: { in: [GUEST, REPORTER, DEVELOPER, MASTER] }, presence: true
validates :project, presence: true
delegate :name, :email, to: :user, prefix: true
......
class ActivityObserver < ActiveRecord::Observer
observe :issue, :merge_request
observe :issue, :merge_request, :note, :milestone
def after_create(record)
Event.create(
project: record.project,
target_id: record.id,
target_type: record.class.name,
action: Event.determine_action(record),
author_id: record.author_id
)
event_author_id = record.author_id
# Skip status notes
if record.kind_of?(Note) && record.note.include?("_Status changed to ")
return true
end
if event_author_id
Event.create(
project: record.project,
target_id: record.id,
target_type: record.class.name,
action: Event.determine_action(record),
author_id: event_author_id
)
end
end
def after_save(record)
if record.changed.include?("closed")
if record.changed.include?("closed") && record.author_id_of_changes
Event.create(
project: record.project,
target_id: record.id,
......
......@@ -16,7 +16,7 @@ class IssueObserver < ActiveRecord::Observer
if status
Note.create_status_change_note(issue, current_user, status)
[issue.author, issue.assignee].compact.each do |recipient|
Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user)
Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user.id).deliver
end
end
end
......
......@@ -21,7 +21,7 @@ class NoteObserver < ActiveRecord::Observer
# Notifies the whole team except the author of note
def notify_team(note)
# Note: wall posts are not "attached" to anything, so fall back to "Wall"
noteable_type = note.noteable_type || "Wall"
noteable_type = note.noteable_type.presence || "Wall"
notify_method = "note_#{noteable_type.underscore}_email".to_sym
if Notify.respond_to? notify_method
......
......@@ -3,7 +3,8 @@ class ProjectObserver < ActiveRecord::Observer
project.update_repository
end
def after_save(project)
def after_update(project)
project.send_move_instructions if project.namespace_id_changed?
end
def after_destroy(project)
......
......@@ -47,7 +47,7 @@ module Account
end
def cared_merge_requests
MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id).opened
MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id)
end
def project_ids
......@@ -105,4 +105,20 @@ module Account
def namespace_id
namespace.try :id
end
def authorized_groups
@authorized_groups ||= begin
groups = Group.where(id: self.projects.pluck(:namespace_id)).all
groups = groups + self.groups
groups.uniq
end
end
def authorized_projects
Project.authorized_for(self)
end
def my_own_projects
Project.personal(self)
end
end
module NamespacedProject
def transfer(new_namespace)
Project.transaction do
old_namespace = namespace
self.namespace = new_namespace
old_dir = old_namespace.try(:path) || ''
new_dir = new_namespace.try(:path) || ''
old_repo = if old_dir.present?
File.join(old_dir, self.path)
else
self.path
end
if Project.where(path: self.path, namespace_id: new_namespace.try(:id)).present?
raise TransferError.new("Project with same path in target namespace already exists")
end
Gitlab::ProjectMover.new(self, old_dir, new_dir).execute
git_host.move_repository(old_repo, self)
save!
end
rescue Gitlab::ProjectMover::ProjectMoveError => ex
raise TransferError.new(ex.message)
end
def name_with_namespace
@name_with_namespace ||= begin
if namespace
namespace.human_name + " / " + name
else
name
end
end
end
def namespace_owner
namespace.try(:owner)
end
def chief
if namespace
namespace_owner
else
owner
end
end
def path_with_namespace
if namespace
namespace.path + '/' + path
else
path
end
end
end
module NoteEvent
def note_commit_id
target.commit_id
end
def note_short_commit_id
note_commit_id[0..8]
end
def note_commit?
target.noteable_type == "Commit"
end
def note_target
target.noteable
end
def note_target_id
if note_commit?
target.commit_id
else
target.noteable_id.to_s
end
end
def wall_note?
target.noteable_type.blank?
end
def note_target_type
if target.noteable_type.present?
target.noteable_type.titleize
else
"Wall"
end.downcase
end
end
......@@ -114,7 +114,7 @@ module PushObserver
id: commit.id,
message: commit.safe_message,
timestamp: commit.date.xmlschema,
url: "#{Gitlab.config.url}/#{path}/commits/#{commit.id}",
url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{commit.id}",
author: {
name: commit.author_name,
email: commit.author_email
......
......@@ -45,8 +45,22 @@ module Repository
end
def has_post_receive_file?
hook_file = File.join(path_to_repo, 'hooks', 'post-receive')
File.exists?(hook_file)
!!hook_file
end
def valid_post_receive_file?
valid_hook_file == hook_file
end
def valid_hook_file
@valid_hook_file ||= File.read(Rails.root.join('lib', 'hooks', 'post-receive'))
end
def hook_file
@hook_file ||= begin
hook_path = File.join(path_to_repo, 'hooks', 'post-receive')
File.read(hook_path) if File.exists?(hook_path)
end
end
# Returns an Array of branch names
......@@ -83,7 +97,7 @@ module Repository
end
def path_to_repo
File.join(Gitlab.config.git_base_path, "#{path_with_namespace}.git")
File.join(Gitlab.config.gitolite.repos_path, "#{path_with_namespace}.git")
end
def namespace_dir
......@@ -185,7 +199,7 @@ module Repository
end
def http_url_to_repo
http_url = [Gitlab.config.url, "/", path_with_namespace, ".git"].join('')
http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
end
# Check if current branch name is marked as protected in the system
......
= form_for [:admin, @group] do |f|
- if @group.errors.any?
.alert-message.block-message.error
%span= @group.errors.full_messages.first
.clearfix.group_name_holder
= f.label :name do
Group name is
.input
= f.text_field :name, placeholder: "Example Group", class: "xxlarge"
.form-actions
= f.submit 'Save group', class: "btn save-btn"
%h3.page_title Edit Group
%br
= render 'form'
%h3.page_title Rename Group
%hr
= form_for [:admin, @group] do |f|
- if @group.errors.any?
.alert-message.block-message.error
%span= @group.errors.full_messages.first
.clearfix.group_name_holder
= f.label :name do
Group name is
.input
= f.text_field :name, placeholder: "Example Group", class: "xxlarge"
.clearfix.group_name_holder
= f.label :path do
%span.cred Group path is
.input
= f.text_field :path, placeholder: "example-group", class: "xxlarge danger"
%ul.cred
%li Changing group path can have unintended side effects.
%li Renaming group path will rename directory for all related projects
%li It will change web url for access group and group projects.
%li It will change the git path to repositories under this group.
.form-actions
= f.submit 'Rename group', class: "btn danger"
= link_to 'Cancel', admin_groups_path, class: "btn cancel-btn"
......@@ -12,17 +12,24 @@
%table
%thead
%th Name
%th Path
%th Projects
%th Edit
%th.cred Danger Zone!
%tr
%th
Name
%i.icon-sort-down
%th Path
%th Projects
%th Owner
%th.cred Danger Zone!
- @groups.each do |group|
%tr
%td= link_to group.name, [:admin, group]
%td
%strong= link_to group.name, [:admin, group]
%td= group.path
%td= group.projects.count
%td= link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn small"
%td.bgred= link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn small danger"
%td
= link_to group.owner_name, admin_user_path(group.owner_id)
%td.bgred
= link_to 'Rename', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn small"
= link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn small danger"
= paginate @groups, theme: "admin"
%h3.page_title
Group: #{@group.name}
= link_to edit_admin_group_path(@group), class: "btn right" do
%i.icon-edit
Edit
%br
%table.zebra-striped
......@@ -16,36 +13,64 @@
Name:
%td
= @group.name
&nbsp;
= link_to edit_admin_group_path(@group), class: "btn btn-small right" do
%i.icon-edit
Rename
%tr
%td
%b
Path:
%td
%span.monospace= File.join(Gitlab.config.git_base_path, @group.path)
%span.monospace= File.join(Gitlab.config.gitolite.repos_path, @group.path)
%tr
%td
%b
Owner:
%td
= @group.owner_name
.ui-box
%h5
Projects
%small
(#{@group.projects.count})
%ul.unstyled
.right
= link_to "#", class: "btn btn-small change-owner-link" do
%i.icon-edit
Change owner
%tr.change-owner-holder.hide
%td.bgred
%b.cred
New Owner:
%td.bgred
= form_for [:admin, @group] do |f|
= f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'}
%div
= f.submit 'Change Owner', class: "btn danger"
= link_to "Cancel", "#", class: "btn change-owner-cancel-link"
%fieldset
%legend Projects (#{@group.projects.count})
%table
%thead
%tr
%th Project name
%th Path
%th Users
%th.cred Danger Zone!
- @group.projects.each do |project|
%li.wll
%strong
= link_to project.name, [:admin, project]
.right
= link_to 'Remove from group', remove_project_admin_group_path(@group, project_id: project.id), confirm: 'Are you sure?', method: :delete, class: "btn danger small"
.clearfix
%tr
%td
= link_to project.name_with_namespace, [:admin, project]
%td
%span.monospace= project.path_with_namespace + ".git"
%td= project.users.count
%td.bgred
= link_to 'Transfer project to global namespace', remove_project_admin_group_path(@group, project_id: project.id), confirm: 'Remove project from group and move to global namespace. Are you sure?', method: :delete, class: "btn danger small"
= form_tag project_update_admin_group_path(@group), class: "bulk_import", method: :put do
%fieldset
%legend Move projects to group
.alert
You can move only projects with existing repos
%br
Group projects will be moved in group directory and will not be accessible by old path
.clearfix
= label_tag :project_ids do
Projects
......@@ -53,3 +78,17 @@
= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
.form-actions
= submit_tag 'Add', class: "btn primary"
:javascript
$(function(){
var modal = $('.change-owner-holder');
$('.change-owner-link').bind("click", function(){
$(this).hide();
modal.show();
});
$('.change-owner-cancel-link').bind("click", function(){
modal.hide();
$('.change-owner-link').show();
})
})
......@@ -3,6 +3,8 @@
= link_to "githost.log", "#githost", 'data-toggle' => 'tab'
%li
= link_to "application.log", "#application", 'data-toggle' => 'tab'
%li
= link_to "production.log", "#production", 'data-toggle' => 'tab'
%p.light To prevent perfomance issues admin logs output the last 2000 lines
.tab-content
......@@ -34,3 +36,17 @@
- Gitlab::AppLogger.read_latest.each do |line|
%li
%p= line
.tab-pane#production
.file_holder#README
.file_title
%i.icon-file
production.log
.right
= link_to '#', class: 'log-bottom' do
%i.icon-arrow-down
Scroll down
.file_content.logs
%ol
- Gitlab::Logger.read_latest_for('production.log').each do |line|
%li
%p= line
......@@ -19,40 +19,47 @@
.input
= text_field_tag :ppath, @project.path_to_repo, class: "xlarge", disabled: true
- unless project.new_record?
- if project.repo_exists?
.clearfix
= f.label :namespace_id
.input= f.select :namespace_id, namespaces_options(@project.namespace_id), {}, {class: 'chosen'}
= f.label :default_branch, "Default Branch"
.input= f.select(:default_branch, project.heads.map(&:name), {}, style: "width:210px;")
- if project.repo_exists?
.clearfix
= f.label :default_branch, "Default Branch"
.input= f.select(:default_branch, project.heads.map(&:name), {}, style: "width:210px;")
%fieldset.adv_settings
%legend Features:
- unless project.new_record?
%fieldset.adv_settings
%legend Features:
.clearfix
= f.label :issues_enabled, "Issues"
.input= f.check_box :issues_enabled
.clearfix
= f.label :issues_enabled, "Issues"
.input= f.check_box :issues_enabled
.clearfix
= f.label :merge_requests_enabled, "Merge Requests"
.input= f.check_box :merge_requests_enabled
.clearfix
= f.label :merge_requests_enabled, "Merge Requests"
.input= f.check_box :merge_requests_enabled
.clearfix
= f.label :wall_enabled, "Wall"
.input= f.check_box :wall_enabled
.clearfix
= f.label :wall_enabled, "Wall"
.input= f.check_box :wall_enabled
.clearfix
= f.label :wiki_enabled, "Wiki"
.input= f.check_box :wiki_enabled
%fieldset.features
%legend Transfer:
.control-group
= f.label :namespace_id do
%span Namespace
.controls
= f.select :namespace_id, namespaces_options(@project.namespace_id, :all), {}, {class: 'chosen'}
%br
%ul.prepend-top-10.cred
%li Be careful. Changing project namespace can have unintended side effects
%li You can transfer project only to namespaces you can manage
%li You will need to update your local repositories to point to the new location.
.clearfix
= f.label :wiki_enabled, "Wiki"
.input= f.check_box :wiki_enabled
- unless project.new_record?
.actions
= f.submit 'Save Project', class: "btn save-btn"
= link_to 'Cancel', admin_projects_path, class: "btn cancel-btn"
.actions
= f.submit 'Save Project', class: "btn save-btn"
= link_to 'Cancel', admin_projects_path, class: "btn cancel-btn"
......
%h3.page_title
Projects
Projects (#{@projects.count})
= link_to 'New Project', new_project_path, class: "btn small right"
%br
= form_tag admin_projects_path, method: :get, class: 'form-inline' do
......@@ -9,12 +9,15 @@
%table
%thead
%th Name
%th Path
%th Team Members
%th Last Commit
%th Edit
%th.cred Danger Zone!
%tr
%th
Name
%i.icon-sort-down
%th Path
%th Team Members
%th Last Commit
%th Edit
%th.cred Danger Zone!
- @projects.each do |project|
%tr
......
......@@ -4,14 +4,24 @@
%i.icon-edit
Edit
- if !@project.has_post_receive_file? && @project.has_commits?
%br
.alert.alert-error
%span
%strong Important!
Project has commits but missing post-receive file.
%br
If you exported project manually - copy post-receive hook to bare repository
- if @project.has_commits?
- if !@project.has_post_receive_file?
%br
.alert.alert-error
%span
%strong Project has commits but missing post-receive file.
%br
If you exported project manually - make a link of post-receive hook file from gitolite to project repository
- elsif !@project.valid_post_receive_file?
%br
.alert.alert-error
%span
%strong Project has invalid post-receive file.
%br
1. Make sure your gitolite instace has latest post-receive file.
%br
2. Make a link of post-receive hook file from gitolite to project repository
%br
%table.zebra-striped
......@@ -37,23 +47,63 @@
%tr
%td
%b
Path:
Owned by:
%td
%code= @project.path_to_repo
- if @project.chief
= link_to @project.chief.name, admin_user_path(@project.chief)
- else
(deleted)
%tr
%td
%b
Created by:
%td
= @project.owner_name || '(deleted)'
%tr
%td
%b
Created at:
%td
= @project.created_at.stamp("March 1, 1999")
%table.zebra-striped
%thead
%tr
%th Repository
%th
%tr
%td
%b
FS Path:
%td
%code= @project.path_to_repo
%tr
%td
%b
Smart HTTP:
%td
= link_to @project.http_url_to_repo
%tr
%td
%b
SSH:
%td
= link_to @project.ssh_url_to_repo
%tr
%td
%b
Last commit at:
%td
= last_commit(@project)
%tr
%td
%b
Post Receive File:
%td
= check_box_tag :post_receive_file, 1, @project.has_post_receive_file?, disabled: true
%br
%h3
%h5
Team
%small
(#{@project.users_projects.count})
......@@ -75,7 +125,7 @@
%td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn danger small"
%br
%h3 Add new team member
%h5 Add new team member
%br
= form_tag team_update_admin_project_path(@project), class: "bulk_import", method: :put do
%table.zebra-striped
......
%h3.page_title
Users
Users (#{@admin_users.count})
= link_to 'New User', new_admin_user_path, class: "btn small right"
%br
......@@ -21,13 +21,16 @@
%table
%thead
%th Admin
%th Name
%th Username
%th Email
%th Projects
%th Edit
%th.cred Danger Zone!
%tr
%th Admin
%th
Name
%i.icon-sort-down
%th Username
%th Email
%th Projects
%th Edit
%th.cred Danger Zone!
- @admin_users.each do |user|
%tr
......@@ -38,10 +41,13 @@
%td= user.users_projects.count
%td= link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn small"
%td.bgred
- if user.blocked
= link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn small success"
- if user == current_user
%span.cred It's you!
- else
= link_to 'Block', block_admin_user_path(user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small danger"
= link_to 'Destroy', [:admin, user], confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn small danger"
- if user.blocked
= link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn small success"
- else
= link_to 'Block', block_admin_user_path(user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small danger"
= link_to 'Destroy', [:admin, user], confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn small danger"
= paginate @admin_users, theme: "admin"
......@@ -37,6 +37,12 @@
%b
Blocked:
%td= check_box_tag "blocked", 1, @admin_user.blocked, disabled: :disabled
%tr
%td
%b
Created at:
%td
= @admin_user.created_at.stamp("March 1, 1999")
%tr
%td
%b
......@@ -66,7 +72,7 @@
= @admin_user.twitter
%br
%h3 Add User to Projects
%h5 Add User to Projects
%br
= form_tag team_update_admin_user_path(@admin_user), class: "bulk_import", method: :put do
%table
......@@ -76,7 +82,7 @@
%th Project Access:
%tr
%td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
%td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
%td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select chosen span3"
%tr
......@@ -86,8 +92,22 @@
%strong= link_to "here", help_permissions_path, class: "vlink"
%br
- if @admin_user.groups.present?
%h5 Owner of groups:
%br
%table.zebra-striped
%thead
%tr
%th Name
- @admin_user.groups.each do |group|
%tr
%td= link_to group.name, admin_group_path(group)
- if @admin_user.projects.present?
%h3 Projects
%h5 Projects:
%br
%table.zebra-striped
......@@ -101,7 +121,7 @@
- @admin_user.users_projects.each do |tm|
- project = tm.project
%tr
%td= link_to project.name, admin_project_path(project)
%td= link_to project.name_with_namespace, admin_project_path(project)
%td= tm.project_access_human
%td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small"
%td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn small danger"
......@@ -3,4 +3,4 @@
%h5.small
%i.icon-calendar
= day.stamp("28 Aug, 2010")
%ul.unstyled= render commits
%ul.well-list= render commits
%div
%p.slead
Fill input field with commit id like
%code.label_branch 4eedf23
or branch/tag name like
%code.label_branch master
and press compare button for commits list, code diff.
- unless params[:to]
%p.slead
Fill input field with commit id like
%code.label_branch 4eedf23
or branch/tag name like
%code.label_branch master
and press compare button for commits list, code diff.
%br
%br
= form_tag project_compare_index_path(@project), method: :post do
.clearfix
= text_field_tag :from, params[:from], placeholder: "master", class: "xlarge"
= "..."
= text_field_tag :to, params[:to], placeholder: "aa8b4ef", class: "xlarge"
.pull-left
- if params[:to] && params[:from]
= link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has_tooltip', title: 'Switch base of comparison'}
= text_field_tag :from, params[:from], placeholder: "master", class: "xlarge"
= "..."
= text_field_tag :to, params[:to], placeholder: "aa8b4ef", class: "xlarge"
.pull-left
&nbsp;
= submit_tag "Compare", class: "btn primary wide commits-compare-btn"
- if @refs_are_same
.alert
%span Refs are the same
.actions
= submit_tag "Compare", class: "btn primary wide commits-compare-btn"
:javascript
$(function() {
......
......@@ -9,7 +9,7 @@
- if @commits.present?
%div.ui-box
%h5.small Commits (#{@commits.count})
%ul.unstyled= render @commits
%ul.well-list= render @commits
- unless @diffs.empty?
%h4 Diff
......
= render "events/event_last_push", event: @last_push
.event_filter
= event_filter_link EventFilter.push, 'Push events'
= event_filter_link EventFilter.merged, 'Merge events'
= event_filter_link EventFilter.comments, 'Comments'
= event_filter_link EventFilter.team, 'Team'
- if @events.any?
.content_list= render @events
- else
%p.nothing_here_message Projects activity will be displayed here
.loading.hide
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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