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: env:
- DB=postgresql
- DB=mysql - DB=mysql
before_install: before_install:
- sudo apt-get install libicu-dev -y - sudo apt-get install libicu-dev -y
...@@ -18,8 +19,7 @@ services: ...@@ -18,8 +19,7 @@ services:
before_script: before_script:
- "cp config/database.yml.$DB config/database.yml" - "cp config/database.yml.$DB config/database.yml"
- "cp config/gitlab.yml.example config/gitlab.yml" - "cp config/gitlab.yml.example config/gitlab.yml"
- "bundle exec rake db:create RAILS_ENV=test" - "bundle exec rake db:setup RAILS_ENV=test"
- "bundle exec rake db:migrate RAILS_ENV=test"
- "bundle exec rake db:seed_fu RAILS_ENV=test" - "bundle exec rake db:seed_fu RAILS_ENV=test"
- "sh -e /etc/init.d/xvfb start" - "sh -e /etc/init.d/xvfb start"
script: "bundle exec rake travis --trace" script: "bundle exec rake travis --trace"
v 4.0.0 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] expose created date for hooks and SSH keys
- [API] list, create issue notes - [API] list, create issue notes
- [API] list, create snippet 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 # Contribute to GitLab
* Your code can be merged w/o problems
* It won't break existing functionality
* It's quality code
* We like it :)
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: ...@@ -32,7 +32,7 @@ gem 'grit_ext', git: "https://github.com/gitlabhq/grit_ext.git", ref:
gem "gitolite", '1.1.0' gem "gitolite", '1.1.0'
# Syntax highlighter # 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 # Language detection
gem "github-linguist", "~> 2.3.4" , require: "linguist" gem "github-linguist", "~> 2.3.4" , require: "linguist"
...@@ -100,7 +100,7 @@ group :assets do ...@@ -100,7 +100,7 @@ group :assets do
gem "therubyracer" gem "therubyracer"
gem 'chosen-rails', "0.9.8" 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-rails", "2.1.3"
gem "jquery-ui-rails", "2.0.2" gem "jquery-ui-rails", "2.0.2"
gem "modernizr", "2.6.2" gem "modernizr", "2.6.2"
...@@ -124,7 +124,7 @@ group :development, :test do ...@@ -124,7 +124,7 @@ group :development, :test do
gem "capybara" gem "capybara"
gem "pry" gem "pry"
gem "awesome_print" gem "awesome_print"
gem "database_cleaner" gem "database_cleaner", ref: "f89c34300e114be99532f14c115b2799a3380ac6", git: "https://github.com/bmabey/database_cleaner.git"
gem "launchy" gem "launchy"
gem 'factory_girl_rails' gem 'factory_girl_rails'
......
GIT
remote: https://github.com/bmabey/database_cleaner.git
revision: f89c34300e114be99532f14c115b2799a3380ac6
ref: f89c34300e114be99532f14c115b2799a3380ac6
specs:
database_cleaner (0.9.1)
GIT GIT
remote: https://github.com/ctran/annotate_models.git remote: https://github.com/ctran/annotate_models.git
revision: be4e26825b521f0b2d86b181e2dff89901aa9b1e revision: be4e26825b521f0b2d86b181e2dff89901aa9b1e
...@@ -45,8 +52,8 @@ GIT ...@@ -45,8 +52,8 @@ GIT
GIT GIT
remote: https://github.com/gitlabhq/pygments.rb.git remote: https://github.com/gitlabhq/pygments.rb.git
revision: 4db80c599067e2d5f23c5c243bf85b8ca0368ad4 revision: db1da0343adf86b49bdc3add04d02d2e80438d38
ref: 4db80c599067e2d5f23c5c243bf85b8ca0368ad4 branch: master
specs: specs:
pygments.rb (0.3.2) pygments.rb (0.3.2)
posix-spawn (~> 0.3.6) posix-spawn (~> 0.3.6)
...@@ -140,7 +147,6 @@ GEM ...@@ -140,7 +147,6 @@ GEM
colorize (0.5.8) colorize (0.5.8)
crack (0.3.1) crack (0.3.1)
daemons (1.1.9) daemons (1.1.9)
database_cleaner (0.9.1)
devise (2.1.2) devise (2.1.2)
bcrypt-ruby (~> 3.0) bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
...@@ -227,7 +233,7 @@ GEM ...@@ -227,7 +233,7 @@ GEM
httpauth (0.2.0) httpauth (0.2.0)
i18n (0.6.1) i18n (0.6.1)
journey (1.0.4) journey (1.0.4)
jquery-atwho-rails (0.1.6) jquery-atwho-rails (0.1.7)
jquery-rails (2.1.3) jquery-rails (2.1.3)
railties (>= 3.1.0, < 5.0) railties (>= 3.1.0, < 5.0)
thor (~> 0.14) thor (~> 0.14)
...@@ -458,7 +464,7 @@ DEPENDENCIES ...@@ -458,7 +464,7 @@ DEPENDENCIES
chosen-rails (= 0.9.8) chosen-rails (= 0.9.8)
coffee-rails (~> 3.2.2) coffee-rails (~> 3.2.2)
colored colored
database_cleaner database_cleaner!
devise (~> 2.1.0) devise (~> 2.1.0)
draper (~> 0.18.0) draper (~> 0.18.0)
email_spec email_spec
...@@ -481,7 +487,7 @@ DEPENDENCIES ...@@ -481,7 +487,7 @@ DEPENDENCIES
guard-spinach guard-spinach
haml-rails (~> 0.3.5) haml-rails (~> 0.3.5)
httparty httparty
jquery-atwho-rails (= 0.1.6) jquery-atwho-rails (= 0.1.7)
jquery-rails (= 2.1.3) jquery-rails (= 2.1.3)
jquery-ui-rails (= 2.0.2) jquery-ui-rails (= 2.0.2)
kaminari (~> 0.14.1) 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 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 # Creates the variables for setting up GFM auto-completion
window.GitLab ?= {} window.GitLab ?= {}
GitLab.GfmAutoComplete ?= {} GitLab.GfmAutoComplete =
# Emoji
# Emoji Emoji:
data = [] data: []
template = "<li data-value='${insert}'>${name} <img alt='${name}' height='20' src='${image}' width='20' /></li>" 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. # Team Members
GitLab.GfmAutoComplete.setup = -> 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') input = $('.js-gfm-input')
# Emoji # Emoji
input.atWho ':', input.atWho ':',
data: GitLab.GfmAutoComplete.Emoji.data, data: @Emoji.data
tpl: GitLab.GfmAutoComplete.Emoji.template tpl: @Emoji.template
# Team Members # 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 '@', input.atWho '@',
data: GitLab.GfmAutoComplete.Members.data tpl: @Members.template
callback: null callback: (query, callback) =>
else request_params = $.extend({}, @Members.params, query: query)
# get next page $.getJSON(@Members.url, request_params).done (members) =>
getMoreMembers() 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() { function initIssuesSearch() {
var href = $('#issue_search_form').attr('action'); var href = $('#issue_search_form').attr('action');
var last_terms = ''; var last_terms = '';
...@@ -76,23 +36,15 @@ function issuesPage(){ ...@@ -76,23 +36,15 @@ function issuesPage(){
$(this).closest("form").submit(); $(this).closest("form").submit();
}); });
$("#new_issue_link").click(function(){ $('body').on('ajax:success', '.close_issue, .reopen_issue', function(){
updateNewIssueURL();
});
$('body').on('ajax:success', '.close_issue, .reopen_issue, #new_issue', function(){
var t = $(this), var t = $(this),
totalIssues, totalIssues,
reopen = t.hasClass('reopen_issue'), reopen = t.hasClass('reopen_issue');
newIssue = false; $('.issue_counter').each(function(){
if( this.id == 'new_issue' ){
newIssue = true;
}
$('.issue_counter, #new_issue').each(function(){
var issue = $(this); var issue = $(this);
totalIssues = parseInt( $(this).html(), 10 ); totalIssues = parseInt( $(this).html(), 10 );
if( newIssue || ( reopen && issue.closest('.main_menu').length ) ){ if( reopen && issue.closest('.main_menu').length ){
$(this).html( totalIssues+1 ); $(this).html( totalIssues+1 );
}else { }else {
$(this).html( totalIssues-1 ); $(this).html( totalIssues-1 );
...@@ -126,20 +78,3 @@ function issuesCheckChanged() { ...@@ -126,20 +78,3 @@ function issuesCheckChanged() {
$('.issues_filters').show(); $('.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) -> ...@@ -7,6 +7,18 @@ window.slugify = (text) ->
window.ajaxGet = (url) -> window.ajaxGet = (url) ->
$.ajax({type: "GET", url: url, dataType: "script"}) $.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 # Disable button if text field is empty
window.disableButtonIfEmptyField = (field_selector, button_selector) -> window.disableButtonIfEmptyField = (field_selector, button_selector) ->
field = $(field_selector) field = $(field_selector)
......
...@@ -26,6 +26,12 @@ var MergeRequest = { ...@@ -26,6 +26,12 @@ var MergeRequest = {
self.showState(data.state); self.showState(data.state);
}, "json"); }, "json");
} }
if(self.opts.ci_enable){
$.get(self.opts.url_to_ci_check, function(data){
self.showCiState(data.status);
}, "json");
}
}, },
initTabs: initTabs:
...@@ -79,6 +85,11 @@ var MergeRequest = { ...@@ -79,6 +85,11 @@ var MergeRequest = {
$(".automerge_widget." + state).show(); $(".automerge_widget." + state).show();
}, },
showCiState:
function(state){
$(".ci_widget").hide();
$(".ci_widget.ci-" + state).show();
},
loadDiff: loadDiff:
function() { function() {
......
...@@ -18,10 +18,3 @@ $ -> ...@@ -18,10 +18,3 @@ $ ->
# Ref switcher # Ref switcher
$('.project-refs-select').on 'change', -> $('.project-refs-select').on 'change', ->
$(@).parents('form').submit() $(@).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 @@ ...@@ -6,5 +6,47 @@
*= require jquery.atwho *= require jquery.atwho
*= require chosen *= require chosen
*= require_self *= 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 { ...@@ -13,20 +13,12 @@ body {
margin: 0 0; margin: 0 0;
} }
.container .sidebar {
width: 200px;
height: 100%;
min-height: 450px;
float: right;
}
.visible_link, .visible_link,
.author_link { .author_link {
color: $link_color; color: $link_color;
} }
.help li { color:#111 } .help li { color:$style_color; }
.back_link { .back_link {
text-decoration: underline; text-decoration: underline;
...@@ -65,6 +57,9 @@ table a code { ...@@ -65,6 +57,9 @@ table a code {
background: url(ajax_loader.gif) no-repeat center center; background: url(ajax_loader.gif) no-repeat center center;
width: 40px; width: 40px;
height: 40px; height: 40px;
&.loading-gray {
background: url(ajax_loader_gray.gif) no-repeat center center;
}
} }
/** FLASH message **/ /** FLASH message **/
...@@ -96,28 +91,17 @@ table a code { ...@@ -96,28 +91,17 @@ table a code {
margin-right:50px margin-right:50px
} }
.handle:hover {
cursor: move;
}
span.update-author { span.update-author {
display: block; display: block;
}
span.update-author {
color: #999; color: #999;
font-weight: normal; font-weight: normal;
font-style: italic; font-style: italic;
} strong {
span.update-author strong {
font-weight: bold; font-weight: bold;
font-style: normal; font-style: normal;
}
} }
/** UPDATE ITEM **/
span.update-author {
display: block;
}
/** END UPDATE ITEM **/
.dashboard-loader { .dashboard-loader {
float: left; float: left;
margin: 10px; margin: 10px;
...@@ -264,21 +248,6 @@ input.git_clone_url { ...@@ -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 { .line_holder {
&:hover { &:hover {
td { td {
...@@ -316,98 +285,6 @@ p.time { ...@@ -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) */ /* Fix for readme code (stopped it from being yellow) */
.readme { .readme {
...@@ -420,7 +297,6 @@ p.time { ...@@ -420,7 +297,6 @@ p.time {
} }
} }
.highlight_word { .highlight_word {
background: #EEDC94; background: #EEDC94;
} }
...@@ -428,23 +304,16 @@ p.time { ...@@ -428,23 +304,16 @@ p.time {
.status_info { .status_info {
font-size: 14px; font-size: 14px;
padding: 5px 15px; padding: 5px 15px;
line-height: 24px; line-height: 26px;
width: 60px;
text-align: center; text-align: center;
float: left; float: right;
margin-right: 20px; position: relative;
top: -5px;
@include border-radius(4px);
&.success {
background: #5BB75B;
color: white;
text-shadow: 0 1px #111;
border-color: #9A9;
}
&.error { &.error {
background: #DA4E49; background: #DA4E49;
border-color: #BD362F; color: #FFF;
color: white;
text-shadow: 0 1px #111;
} }
} }
...@@ -463,16 +332,6 @@ p.time { ...@@ -463,16 +332,6 @@ p.time {
height: 150px; 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. // Fixes alignment on notes.
.new_note { .new_note {
label { label {
...@@ -647,9 +506,14 @@ pre { ...@@ -647,9 +506,14 @@ pre {
} }
} }
.milestone .progress { .milestone {
&.milestone-closed {
background: #eee;
}
.progress {
margin-bottom: 0; margin-bottom: 0;
margin-top: 4px; margin-top: 4px;
}
} }
.float-link { .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 @@ ...@@ -31,6 +31,7 @@
.middle_box_content, .middle_box_content,
.bottom_box_content { .bottom_box_content {
padding: 15px; padding: 15px;
word-wrap: break-word;
pre { pre {
background: none !important; background: none !important;
...@@ -40,6 +41,15 @@ ...@@ -40,6 +41,15 @@
} }
} }
.top_box_content {
.box-title {
color: $style_color;
font-size: 18px;
font-weight: normal;
line-height: 28px;
}
}
.middle_box_content { .middle_box_content {
@include border-radius(0); @include border-radius(0);
border: none; border: none;
...@@ -83,6 +93,10 @@ ...@@ -83,6 +93,10 @@
border-top: 1px solid #eaeaea; border-top: 1px solid #eaeaea;
border-bottom: 1px solid #bbb; border-bottom: 1px solid #bbb;
> a {
text-shadow: 0 1px 1px #fff;
}
&.small { &.small {
line-height: 28px; line-height: 28px;
font-size: 14px; font-size: 14px;
...@@ -138,19 +152,6 @@ ...@@ -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 { .ui-box-body {
padding: 10px; padding: 10px;
} }
......
...@@ -10,11 +10,6 @@ ...@@ -10,11 +10,6 @@
/** COMMON CLASSES **/ /** COMMON CLASSES **/
.left { float:left } .left { float:left }
.right { float:right!important } .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-10 { margin-bottom:10px }
.append-bottom-20 { margin-bottom:20px } .append-bottom-20 { margin-bottom:20px }
.prepend-top-10 { margin-top:10px } .prepend-top-10 { margin-top:10px }
...@@ -30,6 +25,7 @@ ...@@ -30,6 +25,7 @@
.borders { border: 1px solid #ccc; @include shade; } .borders { border: 1px solid #ccc; @include shade; }
.hint { font-style: italic; color: #999; } .hint { font-style: italic; color: #999; }
.light { color: #888 } .light { color: #888 }
.tiny { font-weight: normal }
/** PILLS & TABS**/ /** PILLS & TABS**/
.nav-pills a:hover { background-color: #888; } .nav-pills a:hover { background-color: #888; }
...@@ -99,18 +95,21 @@ input[type='search'].search-text-input { ...@@ -99,18 +95,21 @@ input[type='search'].search-text-input {
border: 1px solid #ccc; border: 1px solid #ccc;
} }
fieldset legend { font-size: 17px; } input[type='text'].danger {
background: #F2DEDE!important;
ul.nav.nav-projects-tabs { border-color: #D66;
@extend .nav-tabs; text-shadow: 0 1px 1px #fff
}
padding-left: 8px; fieldset legend { font-size: 17px; }
li { /** PAGINATION **/
a { .gitlab_pagination {
padding: 4px 20px; span a { color: $link_color; }
margin-top: 2px; .prev, .next, .current, .page a {
border-color: #DDD; padding: 10px;
} }
.current {
border-bottom: 2px solid $style_color;
} }
} }
...@@ -43,11 +43,15 @@ ...@@ -43,11 +43,15 @@
padding: 0 4px; padding: 0 4px;
} }
padding: 20px; padding: 20px;
h1, h2 {
line-height: 46px; h1 { font-size: 26px; line-height: 46px; }
} h2 { font-size: 22px; line-height: 42px; }
h3, h4 { h3 { font-size: 20px; line-height: 40px; }
line-height: 40px; h4 { font-size: 18px; line-height: 32px; }
h5 { font-size: 16px; line-height: 26px; }
.white .highlight pre {
background: #f5f5f5;
} }
} }
......
/** LISTS **/ /**
* Well styled list
ul {
/**
* List li block element #1
* *
*/ */
.wll { .well-list {
margin: 0;
list-style: none;
li {
background-color: #FFF; background-color: #FFF;
padding: 10px 5px; padding: 10px;
min-height: 20px; min-height: 20px;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
border-bottom: 1px solid rgba(0, 0, 0, 0.05); border-bottom: 1px solid rgba(0, 0, 0, 0.05);
&.disabled {
color: #888;
}
&.smoke { background-color: #f5f5f5; } &.smoke { background-color: #f5f5f5; }
&:hover { &:hover {
background: $hover; background: $hover;
border-bottom: 1px solid #ADF; 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; } .author { color: #999; }
p { p {
...@@ -29,6 +44,11 @@ ul { ...@@ -29,6 +44,11 @@ ul {
top: 3px; top: 3px;
} }
} }
.well-title {
font-size: 14px;
line-height: 18px;
}
} }
} }
...@@ -39,3 +59,17 @@ ol, ul { ...@@ -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 }
}
}
...@@ -58,3 +58,12 @@ ...@@ -58,3 +58,12 @@
@mixin solid-shade { @mixin solid-shade {
@include box-shadow(0 0 0 3px #f1f1f1); @include box-shadow(0 0 0 3px #f1f1f1);
} }
@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;
}
.black .highlight { .black .highlight {
pre {
background-color: #333; background-color: #333;
pre {
color: #eee; color: #eee;
background: inherit;
} }
.hll { display: block; background-color: darken($hover, 65%) } .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 /* Interaction Cues
----------------------------------*/ ----------------------------------*/
.ui-state-disabled { cursor: default !important; } .ui-state-disabled { cursor: default !important; }
...@@ -140,26 +116,6 @@ ...@@ -140,26 +116,6 @@
/* Overlays */ /* Overlays */
.ui-widget-overlay { background: #262b33; opacity: .70;filter:Alpha(Opacity=70); } .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; } .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 * jQuery UI Selectable 1.8.7
* *
...@@ -240,34 +196,7 @@ ...@@ -240,34 +196,7 @@
cursor: pointer; cursor: pointer;
font-weight: bold; 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 * jQuery UI Datepicker 1.8.7
* *
...@@ -326,45 +255,3 @@ ...@@ -326,45 +255,3 @@
.ui-datepicker table .ui-state-highlight { border-color: #ADE; } .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-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; } .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 @@ ...@@ -232,8 +232,6 @@
/** COMMIT ROW **/ /** COMMIT ROW **/
.commit { .commit {
@extend .wll;
.browse_code_link_holder { .browse_code_link_holder {
@extend .span2; @extend .span2;
float: right; float: right;
...@@ -305,3 +303,17 @@ ...@@ -305,3 +303,17 @@
color: #fff; color: #fff;
font-family: $monospace; 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 @@ ...@@ -31,7 +31,6 @@
* *
*/ */
.event-item { .event-item {
min-height: 40px;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
.event-title { .event-title {
color: #333; color: #333;
...@@ -50,14 +49,18 @@ ...@@ -50,14 +49,18 @@
} }
} }
.avatar { .avatar {
width: 32px; position: relative;
top: -3px;
} }
.event_icon { .event_icon {
position: relative;
float: right; float: right;
border: 1px solid #EEE; border: 1px solid #EEE;
padding: 5px; padding: 5px;
@include border-radius(5px); @include border-radius(5px);
background: #F9F9F9; background: #F9F9F9;
margin-left: 10px;
top: -6px;
img { img {
width: 20px; width: 20px;
} }
...@@ -71,9 +74,8 @@ ...@@ -71,9 +74,8 @@
} }
} }
padding: 15px 5px; padding: 16px 5px;
&:last-child { border:none } &:last-child { border:none }
.wll:hover { background:none }
.event_commits { .event_commits {
margin-top: 5px; margin-top: 5px;
......
...@@ -44,14 +44,9 @@ header { ...@@ -44,14 +44,9 @@ header {
background: url('logo_dark.png') no-repeat 0px 2px; background: url('logo_dark.png') no-repeat 0px 2px;
float: left; float: left;
margin-left: 2px; margin-left: 2px;
font-size: 30px;
line-height: 48px;
font-weight: normal;
color: $style_color;
text-shadow: 0 1px 1px #FFF;
padding-left: 45px; padding-left: 45px;
height: 40px; height: 40px;
font-family: 'Korolev', sans-serif; @include header-font;
} }
} }
} }
...@@ -66,12 +61,7 @@ header { ...@@ -66,12 +61,7 @@ header {
float: left; float: left;
margin: 0; margin: 0;
margin-right: 30px; margin-right: 30px;
font-size: 30px; @include header-font;
line-height: 48px;
font-weight: normal;
color: $style_color;
text-shadow: 0 1px 1px #FFF;
font-family: 'Korolev', sans-serif;
} }
/** /**
...@@ -172,7 +162,7 @@ header { ...@@ -172,7 +162,7 @@ header {
display: none; display: none;
z-index: 100000; z-index: 100000;
@include border-radius(4px); @include border-radius(4px);
width: 100px; width: 130px;
position: absolute; position: absolute;
right: 5px; right: 5px;
top: 38px; top: 38px;
...@@ -181,7 +171,7 @@ header { ...@@ -181,7 +171,7 @@ header {
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
a { a {
color: #fff; color: #fff;
padding: 7px 10px; padding: 12px 15px;
display: block; display: block;
text-shadow: none; text-shadow: none;
border-bottom: 1px solid #666; border-bottom: 1px solid #666;
......
...@@ -121,12 +121,3 @@ input.check_all_issues { ...@@ -121,12 +121,3 @@ input.check_all_issues {
#update_status { #update_status {
width: 100px; width: 100px;
} }
/**
* Milestones list
*
*/
.milestone {
@extend .wll;
}
...@@ -136,9 +136,3 @@ li.merge_request { ...@@ -136,9 +136,3 @@ li.merge_request {
} }
} }
} }
.status-badge {
height: 32px;
width: 100%;
@include border-radius(5px);
}
...@@ -3,15 +3,13 @@ ...@@ -3,15 +3,13 @@
* *
*/ */
ul.main_menu { ul.main_menu {
@include border-radius(4px);
margin: auto; margin: auto;
margin: 30px 0; margin: 30px 0;
border: 1px solid #BBB; margin-top: 10px;
border-bottom: 1px solid #DDD;
height: 37px; height: 37px;
@include bg-gray-gradient;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
@include shade;
.count { .count {
position: relative; position: relative;
top: -1px; top: -1px;
...@@ -24,9 +22,6 @@ ul.main_menu { ...@@ -24,9 +22,6 @@ ul.main_menu {
line-height: 14px; line-height: 14px;
text-align: center; text-align: center;
color: #777; color: #777;
background: #f2f2f2;
border-top: 1px solid #CCC;
@include border-radius(8px);
} }
.label { .label {
background: $hover; background: $hover;
...@@ -38,23 +33,10 @@ ul.main_menu { ...@@ -38,23 +33,10 @@ ul.main_menu {
margin: 0; margin: 0;
display: table-cell; display: table-cell;
width: 1%; 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 { &.active {
background-color: #D5D5D5; border-bottom: 2px solid #474D57;
border-right: 1px solid #BBB; a {
border-left: 1px solid #BBB; color: $style_color;
@include border-radius(0 0 1px 1px);
&:first-child{
border-bottom: none;
border-left: none;
} }
} }
...@@ -73,10 +55,10 @@ ul.main_menu { ...@@ -73,10 +55,10 @@ ul.main_menu {
a { a {
display: block; display: block;
text-align: center; text-align: center;
font-weight: bold; font-weight: normal;
height: 35px; height: 35px;
line-height: 36px; line-height: 36px;
color: $style_color; color: #777;
text-shadow: 0 1px 1px white; text-shadow: 0 1px 1px white;
padding: 0 10px; padding: 0 10px;
} }
......
...@@ -4,12 +4,11 @@ ...@@ -4,12 +4,11 @@
} }
.side { .side {
@extend .span4;
@extend .right; @extend .right;
.groups_box, .groups_box,
.projects_box { .projects_box {
h5 { > h5 {
color: $style_color; color: $style_color;
font-size: 16px; font-size: 16px;
text-shadow: 0 1px 1px #fff; text-shadow: 0 1px 1px #fff;
...@@ -17,20 +16,8 @@ ...@@ -17,20 +16,8 @@
line-height: 32px; line-height: 32px;
font-size: 14px; font-size: 14px;
} }
ul { .nav-projects-tabs li { padding: 0; }
li { .well-list {
padding: 0;
a {
display: block;
.group_name {
font-size: 14px;
line-height: 18px;
}
.project_name {
color: #4fa2bd;
font-size: 14px;
line-height: 18px;
}
.arrow { .arrow {
float: right; float: right;
padding: 10px; padding: 10px;
...@@ -45,9 +32,6 @@ ...@@ -45,9 +32,6 @@
} }
} }
} }
}
}
@extend .leftbar;
@extend .ui-box; @extend .ui-box;
} }
} }
...@@ -117,3 +101,25 @@ ...@@ -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 @@ ...@@ -4,18 +4,6 @@
* *
*/ */
.ui_basic { .ui_basic {
/*
* Common styles
*
*/
a {
color: $link_color;
&:hover {
text-decoration: none;
color: $primary_color;
}
}
.app_logo { .app_logo {
.separator { .separator {
margin-left: 0; margin-left: 0;
......
...@@ -2,7 +2,9 @@ class ProjectUpdateContext < BaseContext ...@@ -2,7 +2,9 @@ class ProjectUpdateContext < BaseContext
def execute(role = :default) def execute(role = :default)
namespace_id = params[:project].delete(:namespace_id) 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 namespace_id == Namespace.global_id
if project.namespace.present? if project.namespace.present?
# Transfer to global namespace from anyone # Transfer to global namespace from anyone
......
...@@ -2,7 +2,7 @@ class Admin::GroupsController < AdminController ...@@ -2,7 +2,7 @@ class Admin::GroupsController < AdminController
before_filter :group, only: [:edit, :show, :update, :destroy, :project_update] before_filter :group, only: [:edit, :show, :update, :destroy, :project_update]
def index def index
@groups = Group.scoped @groups = Group.order('name ASC')
@groups = @groups.search(params[:name]) if params[:name].present? @groups = @groups.search(params[:name]) if params[:name].present?
@groups = @groups.page(params[:page]).per(20) @groups = @groups.page(params[:page]).per(20)
end end
...@@ -11,6 +11,7 @@ class Admin::GroupsController < AdminController ...@@ -11,6 +11,7 @@ class Admin::GroupsController < AdminController
@projects = Project.scoped @projects = Project.scoped
@projects = @projects.not_in_group(@group) if @group.projects.present? @projects = @projects.not_in_group(@group) if @group.projects.present?
@projects = @projects.all @projects = @projects.all
@projects.reject!(&:empty_repo?)
end end
def new def new
......
...@@ -4,12 +4,13 @@ class Admin::ProjectsController < AdminController ...@@ -4,12 +4,13 @@ class Admin::ProjectsController < AdminController
def index def index
@projects = Project.scoped @projects = Project.scoped
@projects = @projects.where(namespace_id: params[:namespace_id]) if params[:namespace_id].present? @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.search(params[:name]) if params[:name].present?
@projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20) @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20)
end end
def show def show
@users = User.scoped @users = User.active
@users = @users.not_in_project(@project) if @project.users.present? @users = @users.not_in_project(@project) if @project.users.present?
@users = @users.all @users = @users.all
end end
......
...@@ -3,7 +3,7 @@ class Admin::UsersController < AdminController ...@@ -3,7 +3,7 @@ class Admin::UsersController < AdminController
@admin_users = User.scoped @admin_users = User.scoped
@admin_users = @admin_users.filter(params[:filter]) @admin_users = @admin_users.filter(params[:filter])
@admin_users = @admin_users.search(params[:name]) if params[:name].present? @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 end
def show def show
...@@ -30,7 +30,7 @@ class Admin::UsersController < AdminController ...@@ -30,7 +30,7 @@ class Admin::UsersController < AdminController
def new 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 end
def edit def edit
......
...@@ -112,6 +112,10 @@ class ApplicationController < ActionController::Base ...@@ -112,6 +112,10 @@ class ApplicationController < ActionController::Base
render file: Rails.root.join("public", "404"), layout: false, status: "404" render file: Rails.root.join("public", "404"), layout: false, status: "404"
end end
def render_403
render file: Rails.root.join("public", "403"), layout: false, status: "403"
end
def require_non_empty_project def require_non_empty_project
redirect_to @project if @project.empty_repo? redirect_to @project if @project.empty_repo?
end end
......
...@@ -7,6 +7,8 @@ class DashboardController < ApplicationController ...@@ -7,6 +7,8 @@ class DashboardController < ApplicationController
def index def index
@groups = current_user.authorized_groups @groups = current_user.authorized_groups
@has_authorized_projects = @projects.count > 0
@projects = case params[:scope] @projects = case params[:scope]
when 'personal' then when 'personal' then
@projects.personal(current_user) @projects.personal(current_user)
......
...@@ -21,7 +21,7 @@ class GroupsController < ApplicationController ...@@ -21,7 +21,7 @@ class GroupsController < ApplicationController
# Get authored or assigned open merge requests # Get authored or assigned open merge requests
def 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) @merge_requests = @merge_requests.of_group(@group).recent.page(params[:page]).per(20)
end end
...@@ -49,6 +49,7 @@ class GroupsController < ApplicationController ...@@ -49,6 +49,7 @@ class GroupsController < ApplicationController
def people def people
@project = group.projects.find(params[:project_id]) if params[:project_id] @project = group.projects.find(params[:project_id]) if params[:project_id]
@users = @project ? @project.users : group.users @users = @project ? @project.users : group.users
@users.sort_by!(&:name)
if @project if @project
@team_member = @project.users_projects.new @team_member = @project.users_projects.new
......
class IssuesController < ProjectResourceController class IssuesController < ProjectResourceController
before_filter :module_enabled before_filter :module_enabled
before_filter :issue, only: [:edit, :update, :destroy, :show] before_filter :issue, only: [:edit, :update, :show]
# Allow read any issue # Allow read any issue
before_filter :authorize_read_issue! before_filter :authorize_read_issue!
...@@ -11,9 +11,6 @@ class IssuesController < ProjectResourceController ...@@ -11,9 +11,6 @@ class IssuesController < ProjectResourceController
# Allow modify issue # Allow modify issue
before_filter :authorize_modify_issue!, only: [:edit, :update] before_filter :authorize_modify_issue!, only: [:edit, :update]
# Allow destroy issue
before_filter :authorize_admin_issue!, only: [:destroy]
respond_to :js, :html respond_to :js, :html
def index def index
...@@ -79,15 +76,6 @@ class IssuesController < ProjectResourceController ...@@ -79,15 +76,6 @@ class IssuesController < ProjectResourceController
end end
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 def sort
return render_404 unless can?(current_user, :admin_issue, @project) return render_404 unless can?(current_user, :admin_issue, @project)
......
class MergeRequestsController < ProjectResourceController class MergeRequestsController < ProjectResourceController
before_filter :module_enabled 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 :validates_merge_request, only: [:show, :diffs]
before_filter :define_show_vars, only: [:show, :diffs] before_filter :define_show_vars, only: [:show, :diffs]
...@@ -13,9 +13,6 @@ class MergeRequestsController < ProjectResourceController ...@@ -13,9 +13,6 @@ class MergeRequestsController < ProjectResourceController
# Allow modify merge_request # Allow modify merge_request
before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort] 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 = MergeRequestsLoadContext.new(project, current_user, params).execute @merge_requests = MergeRequestsLoadContext.new(project, current_user, params).execute
end end
...@@ -90,14 +87,6 @@ class MergeRequestsController < ProjectResourceController ...@@ -90,14 +87,6 @@ class MergeRequestsController < ProjectResourceController
end end
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 def branch_from
@commit = project.commit(params[:ref]) @commit = project.commit(params[:ref])
@commit = CommitDecorator.decorate(@commit) @commit = CommitDecorator.decorate(@commit)
...@@ -108,6 +97,13 @@ class MergeRequestsController < ProjectResourceController ...@@ -108,6 +97,13 @@ class MergeRequestsController < ProjectResourceController
@commit = CommitDecorator.decorate(@commit) @commit = CommitDecorator.decorate(@commit)
end end
def ci_status
status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha)
response = { status: status }
render json: response
end
protected protected
def merge_request def merge_request
......
...@@ -12,11 +12,12 @@ class MilestonesController < ProjectResourceController ...@@ -12,11 +12,12 @@ class MilestonesController < ProjectResourceController
def index def index
@milestones = case params[:f] @milestones = case params[:f]
when 'all'; @project.milestones when 'all'; @project.milestones.order("closed, due_date DESC")
else @project.milestones.active when 'closed'; @project.milestones.closed.order("due_date DESC")
else @project.milestones.active.order("due_date ASC")
end end
@milestones = @milestones.includes(:project).order("due_date") @milestones = @milestones.includes(:project)
@milestones = @milestones.page(params[:page]).per(20) @milestones = @milestones.page(params[:page]).per(20)
end end
...@@ -42,6 +43,7 @@ class MilestonesController < ProjectResourceController ...@@ -42,6 +43,7 @@ class MilestonesController < ProjectResourceController
def create def create
@milestone = @project.milestones.new(params[:milestone]) @milestone = @project.milestones.new(params[:milestone])
@milestone.author_id_of_changes = current_user.id
if @milestone.save if @milestone.save
redirect_to project_milestone_path(@project, @milestone) redirect_to project_milestone_path(@project, @milestone)
...@@ -51,7 +53,7 @@ class MilestonesController < ProjectResourceController ...@@ -51,7 +53,7 @@ class MilestonesController < ProjectResourceController
end end
def update def update
@milestone.update_attributes(params[:milestone]) @milestone.update_attributes(params[:milestone].merge(author_id_of_changes: current_user.id))
respond_to do |format| respond_to do |format|
format.js format.js
......
class OmniauthCallbacksController < Devise::OmniauthCallbacksController class OmniauthCallbacksController < Devise::OmniauthCallbacksController
Gitlab.config.omniauth_providers.each do |provider| Gitlab.config.omniauth.providers.each do |provider|
define_method provider['name'] do define_method provider['name'] do
handle_omniauth handle_omniauth
end end
......
...@@ -46,6 +46,10 @@ class ProjectsController < ProjectResourceController ...@@ -46,6 +46,10 @@ class ProjectsController < ProjectResourceController
format.js format.js
end end
end end
rescue Project::TransferError => ex
@error = ex
render :update_failed
end end
def show def show
...@@ -86,12 +90,18 @@ class ProjectsController < ProjectResourceController ...@@ -86,12 +90,18 @@ class ProjectsController < ProjectResourceController
end end
def graph def graph
respond_to do |format|
format.html
format.json do
graph = Gitlab::Graph::JsonBuilder.new(project) graph = Gitlab::Graph::JsonBuilder.new(project)
render :json => graph.to_json
@days_json, @commits_json = graph.days_json, graph.commits_json end
end
end end
def destroy def destroy
return access_denied! unless can?(current_user, :remove_project, project)
# Disable the UsersProject update_repository call, otherwise it will be # Disable the UsersProject update_repository call, otherwise it will be
# called once for every person removed from the project # called once for every person removed from the project
UsersProject.skip_callback(:destroy, :after, :update_repository) UsersProject.skip_callback(:destroy, :after, :update_repository)
......
...@@ -16,7 +16,7 @@ class SnippetsController < ProjectResourceController ...@@ -16,7 +16,7 @@ class SnippetsController < ProjectResourceController
respond_to :html respond_to :html
def index def index
@snippets = @project.snippets @snippets = @project.snippets.fresh
end end
def new def new
......
...@@ -76,7 +76,7 @@ class CommitDecorator < ApplicationDecorator ...@@ -76,7 +76,7 @@ class CommitDecorator < ApplicationDecorator
source_name = send "#{options[:source]}_name".to_sym source_name = send "#{options[:source]}_name".to_sym
source_email = send "#{options[:source]}_email".to_sym source_email = send "#{options[:source]}_email".to_sym
text = if options[:avatar] 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>} %Q{#{avatar} <span class="commit-#{options[:source]}-name">#{source_name}</span>}
else else
source_name source_name
......
require 'digest/md5' require 'digest/md5'
require 'uri'
module ApplicationHelper module ApplicationHelper
...@@ -30,13 +31,15 @@ module ApplicationHelper ...@@ -30,13 +31,15 @@ module ApplicationHelper
args.any? { |v| v.to_s.downcase == action_name } args.any? { |v| v.to_s.downcase == action_name }
end end
def gravatar_icon(user_email = '', size = 40) def gravatar_icon(user_email = '', size = nil)
if Gitlab.config.disable_gravatar? || user_email.blank? size = 40 if size.nil? || size <= 0
if !Gitlab.config.gravatar.enabled || user_email.blank?
'no_avatar.png' 'no_avatar.png'
else 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! 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
end end
...@@ -45,7 +48,7 @@ module ApplicationHelper ...@@ -45,7 +48,7 @@ module ApplicationHelper
end end
def web_app_url def web_app_url
"#{request_protocol}://#{Gitlab.config.web_host}/" "#{request_protocol}://#{Gitlab.config.gitlab.host}/"
end end
def last_commit(project) def last_commit(project)
...@@ -92,6 +95,7 @@ module ApplicationHelper ...@@ -92,6 +95,7 @@ module ApplicationHelper
{ label: "API Help", url: help_api_path }, { label: "API Help", url: help_api_path },
{ label: "Markdown Help", url: help_markdown_path }, { label: "Markdown Help", url: help_markdown_path },
{ label: "SSH Keys Help", url: help_ssh_path }, { label: "SSH Keys Help", url: help_ssh_path },
{ label: "Gitlab Rake Tasks Help", url: help_raketasks_path },
] ]
project_nav = [] project_nav = []
......
...@@ -4,28 +4,6 @@ module IssuesHelper ...@@ -4,28 +4,6 @@ module IssuesHelper
project_issues_path project, params project_issues_path project, params
end 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 def issue_css_classes issue
classes = "issue" classes = "issue"
classes << " closed" if issue.closed classes << " closed" if issue.closed
...@@ -52,4 +30,14 @@ module IssuesHelper ...@@ -52,4 +30,14 @@ module IssuesHelper
open: "open" open: "open"
} }
end 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 end
module MergeRequestsHelper 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) def new_mr_path_from_push_event(event)
new_project_merge_request_path( new_project_merge_request_path(
event.project, event.project,
...@@ -39,7 +17,7 @@ module MergeRequestsHelper ...@@ -39,7 +17,7 @@ module MergeRequestsHelper
classes classes
end end
def ci_status_path def ci_build_details_path merge_request
@project.gitlab_ci_service.commit_badge_path(@merge_request.last_commit.sha) merge_request.project.gitlab_ci_service.build_page(merge_request.last_commit.sha)
end end
end end
...@@ -8,11 +8,49 @@ module ProjectsHelper ...@@ -8,11 +8,49 @@ module ProjectsHelper
end end
def link_to_project project 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 end
def tm_path team_member def tm_path team_member
project_team_member_path(@project, team_member) project_team_member_path(@project, team_member)
end 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 ...@@ -72,7 +72,7 @@ module TabHelper
return "active" if current_page?(controller: "projects", action: action, id: @project) return "active" if current_page?(controller: "projects", action: action, id: @project)
end 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" "active"
end end
end end
......
...@@ -3,11 +3,11 @@ class Notify < ActionMailer::Base ...@@ -3,11 +3,11 @@ class Notify < ActionMailer::Base
add_template_helper ApplicationHelper add_template_helper ApplicationHelper
add_template_helper GitlabMarkdownHelper add_template_helper GitlabMarkdownHelper
default_url_options[:host] = Gitlab.config.web_host default_url_options[:host] = Gitlab.config.gitlab.host
default_url_options[:protocol] = Gitlab.config.web_protocol default_url_options[:protocol] = Gitlab.config.gitlab.protocol
default_url_options[:port] = Gitlab.config.web_port if Gitlab.config.web_custom_port? 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 ...@@ -31,6 +31,7 @@ class Notify < ActionMailer::Base
def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id) def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id)
@issue = Issue.find issue_id @issue = Issue.find issue_id
@issue_status = status @issue_status = status
@project = @issue.project
@updated_by = User.find updated_by_user_id @updated_by = User.find updated_by_user_id
mail(to: recipient(recipient_id), mail(to: recipient(recipient_id),
subject: subject("changed issue ##{@issue.id}", @issue.title)) subject: subject("changed issue ##{@issue.id}", @issue.title))
...@@ -102,6 +103,12 @@ class Notify < ActionMailer::Base ...@@ -102,6 +103,12 @@ class Notify < ActionMailer::Base
end 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 # User
......
...@@ -17,9 +17,7 @@ class Ability ...@@ -17,9 +17,7 @@ class Ability
# Rules based on role in project # Rules based on role in project
if project.master_access_for?(user) if project.master_access_for?(user)
# TODO: replace with master rules. rules << project_master_rules
# Only allow project administration for namespace owners
rules << project_admin_rules
elsif project.dev_access_for?(user) elsif project.dev_access_for?(user)
rules << project_dev_rules rules << project_dev_rules
...@@ -93,13 +91,16 @@ class Ability ...@@ -93,13 +91,16 @@ class Ability
:admin_merge_request, :admin_merge_request,
:admin_note, :admin_note,
:accept_mr, :accept_mr,
:admin_wiki :admin_wiki,
:admin_project
] ]
end end
def project_admin_rules def project_admin_rules
project_master_rules + [ project_master_rules + [
:admin_project :change_namespace,
:rename_project,
:remove_project
] ]
end end
......
...@@ -87,14 +87,10 @@ class Commit ...@@ -87,14 +87,10 @@ class Commit
last = project.commit(from.try(:strip)) last = project.commit(from.try(:strip))
if first && last if first && last
commits = [first, last].sort_by(&:created_at) result[:same] = (first.id == last.id)
younger = commits.first result[:commits] = project.repo.commits_between(last.id, first.id).map {|c| Commit.new(c)}
older = commits.last result[:diffs] = project.repo.diff(last.id, first.id) rescue []
result[:commit] = Commit.new(first)
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)
end end
result result
...@@ -163,6 +159,8 @@ class Commit ...@@ -163,6 +159,8 @@ class Commit
while !lines.first.start_with?("diff --git") do while !lines.first.start_with?("diff --git") do
lines.shift lines.shift
end end
lines.pop if lines.last =~ /^[\d.]+$/ # Git version
lines.pop if lines.last == "-- " # end of diff
lines.join("\n") lines.join("\n")
end end
end end
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
# #
class Event < ActiveRecord::Base class Event < ActiveRecord::Base
include NoteEvent
include PushEvent include PushEvent
attr_accessible :project, :action, :data, :author_id, :project_id, attr_accessible :project, :action, :data, :author_id, :project_id,
...@@ -58,12 +59,14 @@ class Event < ActiveRecord::Base ...@@ -58,12 +59,14 @@ class Event < ActiveRecord::Base
end end
end end
# Next events currently enabled for system def proper?
# - push if push?
# - new issue true
# - merge request elsif membership_changed?
def allowed? true
push? || issue? || merge_request? || membership_changed? else
(issue? || merge_request? || note? || milestone?) && target
end
end end
def project_name def project_name
...@@ -94,6 +97,14 @@ class Event < ActiveRecord::Base ...@@ -94,6 +97,14 @@ class Event < ActiveRecord::Base
action == self.class::Reopened action == self.class::Reopened
end end
def milestone?
target_type == "Milestone"
end
def note?
target_type == "Note"
end
def issue? def issue?
target_type == "Issue" target_type == "Issue"
end end
......
...@@ -36,4 +36,22 @@ class GitlabCiService < Service ...@@ -36,4 +36,22 @@ class GitlabCiService < Service
def commit_badge_path sha def commit_badge_path sha
project_url + "/status?sha=#{sha}" project_url + "/status?sha=#{sha}"
end 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 end
...@@ -204,7 +204,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -204,7 +204,7 @@ class MergeRequest < ActiveRecord::Base
def mr_and_commit_notes def mr_and_commit_notes
commit_ids = commits.map(&:id) 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 end
# Returns the raw diff for this merge request # Returns the raw diff for this merge request
...@@ -220,4 +220,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -220,4 +220,8 @@ class MergeRequest < ActiveRecord::Base
def to_patch def to_patch
project.repo.git.format_patch({timeout: 30, raise: true, stdout: true}, "#{target_branch}..#{source_branch}") project.repo.git.format_patch({timeout: 30, raise: true, stdout: true}, "#{target_branch}..#{source_branch}")
end end
def last_commit_short_sha
@last_commit_short_sha ||= last_commit.sha[0..10]
end
end end
...@@ -13,18 +13,26 @@ ...@@ -13,18 +13,26 @@
# #
class Milestone < ActiveRecord::Base 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 belongs_to :project
has_many :issues has_many :issues
has_many :merge_requests has_many :merge_requests
scope :active, where(closed: false)
scope :closed, where(closed: true)
validates :title, presence: true validates :title, presence: true
validates :project, presence: true validates :project, presence: true
validates :closed, inclusion: { in: [true, false] } validates :closed, inclusion: { in: [true, false] }
def self.active def expired?
where("due_date > ? OR due_date IS NULL", Date.today) if due_date
due_date < Date.today
else
false
end
end end
def participants def participants
...@@ -52,4 +60,20 @@ class Milestone < ActiveRecord::Base ...@@ -52,4 +60,20 @@ class Milestone < ActiveRecord::Base
def expires_at def expires_at
"expires at #{due_date.stamp("Aug 21, 2011")}" if due_date "expires at #{due_date.stamp("Aug 21, 2011")}" if due_date
end 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 end
...@@ -48,23 +48,30 @@ class Namespace < ActiveRecord::Base ...@@ -48,23 +48,30 @@ class Namespace < ActiveRecord::Base
end end
def ensure_dir_exist 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) system("mkdir -m 770 #{namespace_dir_path}") unless File.exists?(namespace_dir_path)
end end
def move_dir def move_dir
if path_changed? if path_changed?
old_path = File.join(Gitlab.config.git_base_path, path_was) old_path = File.join(Gitlab.config.gitolite.repos_path, path_was)
new_path = File.join(Gitlab.config.git_base_path, path) new_path = File.join(Gitlab.config.gitolite.repos_path, path)
if File.exists?(new_path) if File.exists?(new_path)
raise "Already exists" raise "Already exists"
end end
system("mv #{old_path} #{new_path}")
if system("mv #{old_path} #{new_path}")
send_update_instructions
end
end end
end end
def rm_dir 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}") system("rm -rf #{dir_path}")
end end
def send_update_instructions
projects.each(&:send_move_instructions)
end
end end
...@@ -19,7 +19,7 @@ require 'file_size_validator' ...@@ -19,7 +19,7 @@ require 'file_size_validator'
class Note < ActiveRecord::Base class Note < ActiveRecord::Base
attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id, attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id,
:attachment, :line_code :attachment, :line_code, :commit_id
attr_accessor :notify attr_accessor :notify
attr_accessor :notify_author attr_accessor :notify_author
...@@ -35,10 +35,14 @@ class Note < ActiveRecord::Base ...@@ -35,10 +35,14 @@ class Note < ActiveRecord::Base
validates :line_code, format: { with: /\A\d+_\d+_\d+\Z/ }, allow_blank: true validates :line_code, format: { with: /\A\d+_\d+_\d+\Z/ }, allow_blank: true
validates :attachment, file_size: { maximum: 10.megabytes.to_i } 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 mount_uploader :attachment, AttachmentUploader
# Scopes # 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 :today, ->{ where("created_at >= :date", date: Date.today) }
scope :last_week, ->{ where("created_at >= :date", date: (Date.today - 7.days)) } scope :last_week, ->{ where("created_at >= :date", date: (Date.today - 7.days)) }
scope :since, ->(day) { where("created_at >= :date", date: (day)) } scope :since, ->(day) { where("created_at >= :date", date: (day)) }
...@@ -122,7 +126,7 @@ class Note < ActiveRecord::Base ...@@ -122,7 +126,7 @@ class Note < ActiveRecord::Base
# override to return commits, which are not active record # override to return commits, which are not active record
def noteable def noteable
if for_commit? if for_commit?
project.commit(noteable_id) project.commit(commit_id)
else else
super super
end end
...@@ -151,4 +155,12 @@ class Note < ActiveRecord::Base ...@@ -151,4 +155,12 @@ class Note < ActiveRecord::Base
def votable? def votable?
for_issue? || (for_merge_request? && !for_diff_line?) for_issue? || (for_merge_request? && !for_diff_line?)
end end
def noteable_type_name
if noteable_type.present?
noteable_type.downcase
else
"wall"
end
end
end end
...@@ -25,6 +25,9 @@ class Project < ActiveRecord::Base ...@@ -25,6 +25,9 @@ class Project < ActiveRecord::Base
include PushObserver include PushObserver
include Authority include Authority
include Team include Team
include NamespacedProject
class TransferError < StandardError; end
attr_accessible :name, :path, :description, :default_branch, :issues_enabled, attr_accessible :name, :path, :description, :default_branch, :issues_enabled,
:wall_enabled, :merge_requests_enabled, :wiki_enabled, as: [:default, :admin] :wall_enabled, :merge_requests_enabled, :wiki_enabled, as: [:default, :admin]
...@@ -36,6 +39,10 @@ class Project < ActiveRecord::Base ...@@ -36,6 +39,10 @@ class Project < ActiveRecord::Base
# Relations # Relations
belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'" belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'"
belongs_to :namespace 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" belongs_to :owner, class_name: "User"
has_many :users, through: :users_projects has_many :users, through: :users_projects
has_many :events, dependent: :destroy has_many :events, dependent: :destroy
...@@ -97,7 +104,7 @@ class Project < ActiveRecord::Base ...@@ -97,7 +104,7 @@ class Project < ActiveRecord::Base
namespace_id = Namespace.find_by_path(id.first).id namespace_id = Namespace.find_by_path(id.first).id
where(namespace_id: namespace_id).find_by_path(id.last) where(namespace_id: namespace_id).find_by_path(id.last)
else else
find_by_path(id) where(path: id, namespace_id: nil).last
end end
end end
...@@ -172,7 +179,7 @@ class Project < ActiveRecord::Base ...@@ -172,7 +179,7 @@ class Project < ActiveRecord::Base
end end
def repo_name 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) if denied_paths.include?(path)
errors.add(:path, "like #{path} is not allowed") errors.add(:path, "like #{path} is not allowed")
...@@ -188,7 +195,7 @@ class Project < ActiveRecord::Base ...@@ -188,7 +195,7 @@ class Project < ActiveRecord::Base
end end
def web_url def web_url
[Gitlab.config.url, path].join("/") [Gitlab.config.gitlab.url, path_with_namespace].join("/")
end end
def common_notes def common_notes
...@@ -196,15 +203,15 @@ class Project < ActiveRecord::Base ...@@ -196,15 +203,15 @@ class Project < ActiveRecord::Base
end end
def build_commit_note(commit) def build_commit_note(commit)
notes.new(noteable_id: commit.id, noteable_type: "Commit") notes.new(commit_id: commit.id, noteable_type: "Commit")
end end
def commit_notes(commit) 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 end
def commit_line_notes(commit) 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 end
def public? def public?
...@@ -239,51 +246,11 @@ class Project < ActiveRecord::Base ...@@ -239,51 +246,11 @@ class Project < ActiveRecord::Base
gitlab_ci_service && gitlab_ci_service.active gitlab_ci_service && gitlab_ci_service.active
end end
def path_with_namespace
if namespace
namespace.path + '/' + path
else
path
end
end
# For compatibility with old code # For compatibility with old code
def code def code
path path
end 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 def items_for entity
case entity case entity
when 'issue' then when 'issue' then
...@@ -293,7 +260,9 @@ class Project < ActiveRecord::Base ...@@ -293,7 +260,9 @@ class Project < ActiveRecord::Base
end end
end end
def namespace_owner def send_move_instructions
namespace.try(:owner) self.users_projects.each do |member|
Notify.project_was_moved_email(member.id).deliver
end
end end
end end
...@@ -22,7 +22,7 @@ class Snippet < ActiveRecord::Base ...@@ -22,7 +22,7 @@ class Snippet < ActiveRecord::Base
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
has_many :notes, as: :noteable, dependent: :destroy 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 :author, presence: true
validates :project, presence: true validates :project, presence: true
......
...@@ -56,12 +56,12 @@ class User < ActiveRecord::Base ...@@ -56,12 +56,12 @@ class User < ActiveRecord::Base
has_many :issues, foreign_key: :author_id, dependent: :destroy has_many :issues, foreign_key: :author_id, dependent: :destroy
has_many :notes, 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 :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 :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 :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_issues, class_name: "Issue", foreign_key: :assignee_id, dependent: :destroy
has_many :assigned_merge_requests, class_name: "MergeRequest", 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 :bio, length: { within: 0..255 }
validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider} validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider}
validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0} validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0}
...@@ -123,16 +123,4 @@ class User < ActiveRecord::Base ...@@ -123,16 +123,4 @@ class User < ActiveRecord::Base
self.password = self.password_confirmation = Devise.friendly_token.first(8) self.password = self.password_confirmation = Devise.friendly_token.first(8)
end end
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 end
...@@ -28,6 +28,7 @@ class UsersProject < ActiveRecord::Base ...@@ -28,6 +28,7 @@ class UsersProject < ActiveRecord::Base
validates :user, presence: true validates :user, presence: true
validates :user_id, uniqueness: { :scope => [:project_id], message: "already exists in project" } 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 validates :project, presence: true
delegate :name, :email, to: :user, prefix: true delegate :name, :email, to: :user, prefix: true
......
class ActivityObserver < ActiveRecord::Observer class ActivityObserver < ActiveRecord::Observer
observe :issue, :merge_request observe :issue, :merge_request, :note, :milestone
def after_create(record) def after_create(record)
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( Event.create(
project: record.project, project: record.project,
target_id: record.id, target_id: record.id,
target_type: record.class.name, target_type: record.class.name,
action: Event.determine_action(record), action: Event.determine_action(record),
author_id: record.author_id author_id: event_author_id
) )
end end
end
def after_save(record) def after_save(record)
if record.changed.include?("closed") if record.changed.include?("closed") && record.author_id_of_changes
Event.create( Event.create(
project: record.project, project: record.project,
target_id: record.id, target_id: record.id,
......
...@@ -16,7 +16,7 @@ class IssueObserver < ActiveRecord::Observer ...@@ -16,7 +16,7 @@ class IssueObserver < ActiveRecord::Observer
if status if status
Note.create_status_change_note(issue, current_user, status) Note.create_status_change_note(issue, current_user, status)
[issue.author, issue.assignee].compact.each do |recipient| [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 end
end end
......
...@@ -21,7 +21,7 @@ class NoteObserver < ActiveRecord::Observer ...@@ -21,7 +21,7 @@ class NoteObserver < ActiveRecord::Observer
# Notifies the whole team except the author of note # Notifies the whole team except the author of note
def notify_team(note) def notify_team(note)
# Note: wall posts are not "attached" to anything, so fall back to "Wall" # 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 notify_method = "note_#{noteable_type.underscore}_email".to_sym
if Notify.respond_to? notify_method if Notify.respond_to? notify_method
......
...@@ -3,7 +3,8 @@ class ProjectObserver < ActiveRecord::Observer ...@@ -3,7 +3,8 @@ class ProjectObserver < ActiveRecord::Observer
project.update_repository project.update_repository
end end
def after_save(project) def after_update(project)
project.send_move_instructions if project.namespace_id_changed?
end end
def after_destroy(project) def after_destroy(project)
......
...@@ -47,7 +47,7 @@ module Account ...@@ -47,7 +47,7 @@ module Account
end end
def cared_merge_requests 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 end
def project_ids def project_ids
...@@ -105,4 +105,20 @@ module Account ...@@ -105,4 +105,20 @@ module Account
def namespace_id def namespace_id
namespace.try :id namespace.try :id
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
def my_own_projects
Project.personal(self)
end
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 ...@@ -114,7 +114,7 @@ module PushObserver
id: commit.id, id: commit.id,
message: commit.safe_message, message: commit.safe_message,
timestamp: commit.date.xmlschema, timestamp: commit.date.xmlschema,
url: "#{Gitlab.config.url}/#{path}/commits/#{commit.id}", url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{commit.id}",
author: { author: {
name: commit.author_name, name: commit.author_name,
email: commit.author_email email: commit.author_email
......
...@@ -45,8 +45,22 @@ module Repository ...@@ -45,8 +45,22 @@ module Repository
end end
def has_post_receive_file? def has_post_receive_file?
hook_file = File.join(path_to_repo, 'hooks', 'post-receive') !!hook_file
File.exists?(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 end
# Returns an Array of branch names # Returns an Array of branch names
...@@ -83,7 +97,7 @@ module Repository ...@@ -83,7 +97,7 @@ module Repository
end end
def path_to_repo 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 end
def namespace_dir def namespace_dir
...@@ -185,7 +199,7 @@ module Repository ...@@ -185,7 +199,7 @@ module Repository
end end
def http_url_to_repo 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 end
# Check if current branch name is marked as protected in the system # 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 %h3.page_title Rename Group
%br %hr
= render 'form' = 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 @@ ...@@ -12,17 +12,24 @@
%table %table
%thead %thead
%th Name %tr
%th
Name
%i.icon-sort-down
%th Path %th Path
%th Projects %th Projects
%th Edit %th Owner
%th.cred Danger Zone! %th.cred Danger Zone!
- @groups.each do |group| - @groups.each do |group|
%tr %tr
%td= link_to group.name, [:admin, group] %td
%strong= link_to group.name, [:admin, group]
%td= group.path %td= group.path
%td= group.projects.count %td= group.projects.count
%td= link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn small" %td
%td.bgred= link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn small danger" = 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" = paginate @groups, theme: "admin"
%h3.page_title %h3.page_title
Group: #{@group.name} Group: #{@group.name}
= link_to edit_admin_group_path(@group), class: "btn right" do
%i.icon-edit
Edit
%br %br
%table.zebra-striped %table.zebra-striped
...@@ -16,36 +13,64 @@ ...@@ -16,36 +13,64 @@
Name: Name:
%td %td
= @group.name = @group.name
&nbsp;
= link_to edit_admin_group_path(@group), class: "btn btn-small right" do
%i.icon-edit
Rename
%tr %tr
%td %td
%b %b
Path: Path:
%td %td
%span.monospace= File.join(Gitlab.config.git_base_path, @group.path) %span.monospace= File.join(Gitlab.config.gitolite.repos_path, @group.path)
%tr %tr
%td %td
%b %b
Owner: Owner:
%td %td
= @group.owner_name = @group.owner_name
.ui-box
%h5
Projects
%small
(#{@group.projects.count})
%ul.unstyled
- @group.projects.each do |project|
%li.wll
%strong
= link_to project.name, [:admin, project]
.right .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" = link_to "#", class: "btn btn-small change-owner-link" do
.clearfix %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|
%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 = form_tag project_update_admin_group_path(@group), class: "bulk_import", method: :put do
%fieldset %fieldset
%legend Move projects to group %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 .clearfix
= label_tag :project_ids do = label_tag :project_ids do
Projects Projects
...@@ -53,3 +78,17 @@ ...@@ -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' = 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 .form-actions
= submit_tag 'Add', class: "btn primary" = 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 @@ ...@@ -3,6 +3,8 @@
= link_to "githost.log", "#githost", 'data-toggle' => 'tab' = link_to "githost.log", "#githost", 'data-toggle' => 'tab'
%li %li
= link_to "application.log", "#application", 'data-toggle' => 'tab' = 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 %p.light To prevent perfomance issues admin logs output the last 2000 lines
.tab-content .tab-content
...@@ -34,3 +36,17 @@ ...@@ -34,3 +36,17 @@
- Gitlab::AppLogger.read_latest.each do |line| - Gitlab::AppLogger.read_latest.each do |line|
%li %li
%p= line %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,17 +19,11 @@ ...@@ -19,17 +19,11 @@
.input .input
= text_field_tag :ppath, @project.path_to_repo, class: "xlarge", disabled: true = text_field_tag :ppath, @project.path_to_repo, class: "xlarge", disabled: true
- unless project.new_record?
.clearfix
= f.label :namespace_id
.input= f.select :namespace_id, namespaces_options(@project.namespace_id), {}, {class: 'chosen'}
- if project.repo_exists? - if project.repo_exists?
.clearfix .clearfix
= f.label :default_branch, "Default Branch" = f.label :default_branch, "Default Branch"
.input= f.select(:default_branch, project.heads.map(&:name), {}, style: "width:210px;") .input= f.select(:default_branch, project.heads.map(&:name), {}, style: "width:210px;")
- unless project.new_record?
%fieldset.adv_settings %fieldset.adv_settings
%legend Features: %legend Features:
...@@ -49,7 +43,20 @@ ...@@ -49,7 +43,20 @@
= f.label :wiki_enabled, "Wiki" = f.label :wiki_enabled, "Wiki"
.input= f.check_box :wiki_enabled .input= f.check_box :wiki_enabled
- unless project.new_record? %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.
.actions .actions
= f.submit 'Save Project', class: "btn save-btn" = f.submit 'Save Project', class: "btn save-btn"
= link_to 'Cancel', admin_projects_path, class: "btn cancel-btn" = link_to 'Cancel', admin_projects_path, class: "btn cancel-btn"
......
%h3.page_title %h3.page_title
Projects Projects (#{@projects.count})
= link_to 'New Project', new_project_path, class: "btn small right" = link_to 'New Project', new_project_path, class: "btn small right"
%br %br
= form_tag admin_projects_path, method: :get, class: 'form-inline' do = form_tag admin_projects_path, method: :get, class: 'form-inline' do
...@@ -9,7 +9,10 @@ ...@@ -9,7 +9,10 @@
%table %table
%thead %thead
%th Name %tr
%th
Name
%i.icon-sort-down
%th Path %th Path
%th Team Members %th Team Members
%th Last Commit %th Last Commit
......
...@@ -4,14 +4,24 @@ ...@@ -4,14 +4,24 @@
%i.icon-edit %i.icon-edit
Edit Edit
- if !@project.has_post_receive_file? && @project.has_commits? - if @project.has_commits?
- if !@project.has_post_receive_file?
%br %br
.alert.alert-error .alert.alert-error
%span %span
%strong Important! %strong Project has commits but missing post-receive file.
Project has commits but missing post-receive file.
%br %br
If you exported project manually - copy post-receive hook to bare repository 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 %br
%table.zebra-striped %table.zebra-striped
...@@ -37,23 +47,63 @@ ...@@ -37,23 +47,63 @@
%tr %tr
%td %td
%b %b
Path: Owned by:
%td %td
%code= @project.path_to_repo - if @project.chief
= link_to @project.chief.name, admin_user_path(@project.chief)
- else
(deleted)
%tr %tr
%td %td
%b %b
Created by: Created by:
%td %td
= @project.owner_name || '(deleted)' = @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 %tr
%td %td
%b %b
Post Receive File: Post Receive File:
%td %td
= check_box_tag :post_receive_file, 1, @project.has_post_receive_file?, disabled: true = check_box_tag :post_receive_file, 1, @project.has_post_receive_file?, disabled: true
%br %br
%h3 %h5
Team Team
%small %small
(#{@project.users_projects.count}) (#{@project.users_projects.count})
...@@ -75,7 +125,7 @@ ...@@ -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" %td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn danger small"
%br %br
%h3 Add new team member %h5 Add new team member
%br %br
= form_tag team_update_admin_project_path(@project), class: "bulk_import", method: :put do = form_tag team_update_admin_project_path(@project), class: "bulk_import", method: :put do
%table.zebra-striped %table.zebra-striped
......
%h3.page_title %h3.page_title
Users Users (#{@admin_users.count})
= link_to 'New User', new_admin_user_path, class: "btn small right" = link_to 'New User', new_admin_user_path, class: "btn small right"
%br %br
...@@ -21,8 +21,11 @@ ...@@ -21,8 +21,11 @@
%table %table
%thead %thead
%tr
%th Admin %th Admin
%th Name %th
Name
%i.icon-sort-down
%th Username %th Username
%th Email %th Email
%th Projects %th Projects
...@@ -38,6 +41,9 @@ ...@@ -38,6 +41,9 @@
%td= user.users_projects.count %td= user.users_projects.count
%td= link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn small" %td= link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn small"
%td.bgred %td.bgred
- if user == current_user
%span.cred It's you!
- else
- if user.blocked - if user.blocked
= link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn small success" = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn small success"
- else - else
......
...@@ -37,6 +37,12 @@ ...@@ -37,6 +37,12 @@
%b %b
Blocked: Blocked:
%td= check_box_tag "blocked", 1, @admin_user.blocked, disabled: :disabled %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 %tr
%td %td
%b %b
...@@ -66,7 +72,7 @@ ...@@ -66,7 +72,7 @@
= @admin_user.twitter = @admin_user.twitter
%br %br
%h3 Add User to Projects %h5 Add User to Projects
%br %br
= form_tag team_update_admin_user_path(@admin_user), class: "bulk_import", method: :put do = form_tag team_update_admin_user_path(@admin_user), class: "bulk_import", method: :put do
%table %table
...@@ -76,7 +82,7 @@ ...@@ -76,7 +82,7 @@
%th Project Access: %th Project Access:
%tr %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" %td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select chosen span3"
%tr %tr
...@@ -86,8 +92,22 @@ ...@@ -86,8 +92,22 @@
%strong= link_to "here", help_permissions_path, class: "vlink" %strong= link_to "here", help_permissions_path, class: "vlink"
%br %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? - if @admin_user.projects.present?
%h3 Projects %h5 Projects:
%br %br
%table.zebra-striped %table.zebra-striped
...@@ -101,7 +121,7 @@ ...@@ -101,7 +121,7 @@
- @admin_user.users_projects.each do |tm| - @admin_user.users_projects.each do |tm|
- project = tm.project - project = tm.project
%tr %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= tm.project_access_human
%td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small" %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" %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 @@ ...@@ -3,4 +3,4 @@
%h5.small %h5.small
%i.icon-calendar %i.icon-calendar
= day.stamp("28 Aug, 2010") = day.stamp("28 Aug, 2010")
%ul.unstyled= render commits %ul.well-list= render commits
%div %div
- unless params[:to]
%p.slead %p.slead
Fill input field with commit id like Fill input field with commit id like
%code.label_branch 4eedf23 %code.label_branch 4eedf23
...@@ -10,14 +11,20 @@ ...@@ -10,14 +11,20 @@
= form_tag project_compare_index_path(@project), method: :post do = form_tag project_compare_index_path(@project), method: :post do
.clearfix .clearfix
.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 :from, params[:from], placeholder: "master", class: "xlarge"
= "..." = "..."
= text_field_tag :to, params[:to], placeholder: "aa8b4ef", 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 - if @refs_are_same
.alert .alert
%span Refs are the same %span Refs are the same
.actions
= submit_tag "Compare", class: "btn primary wide commits-compare-btn"
:javascript :javascript
$(function() { $(function() {
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
- if @commits.present? - if @commits.present?
%div.ui-box %div.ui-box
%h5.small Commits (#{@commits.count}) %h5.small Commits (#{@commits.count})
%ul.unstyled= render @commits %ul.well-list= render @commits
- unless @diffs.empty? - unless @diffs.empty?
%h4 Diff %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.
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