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
4-1-stable
Riyad Preukschas 2012-12-23 01:03:57 +01:00
commit db2c15369c
276 changed files with 4466 additions and 2603 deletions

View File

@ -1,4 +1,5 @@
env:
- DB=postgresql
- DB=mysql
before_install:
- sudo apt-get install libicu-dev -y
@ -18,8 +19,7 @@ services:
before_script:
- "cp config/database.yml.$DB config/database.yml"
- "cp config/gitlab.yml.example config/gitlab.yml"
- "bundle exec rake db:create RAILS_ENV=test"
- "bundle exec rake db:migrate RAILS_ENV=test"
- "bundle exec rake db:setup RAILS_ENV=test"
- "bundle exec rake db:seed_fu RAILS_ENV=test"
- "sh -e /etc/init.d/xvfb start"
script: "bundle exec rake travis --trace"

View File

@ -1,4 +1,15 @@
v 4.0.0
- Reorganized settings
- Fixed commits compare
- Refactored scss
- Improve status checks
- Validates presence of User#name
- Fixed postgres support
- Removed sqlite support
- Modified post-receive hook
- Milestones can be closed now
- Show comment events on dashboard
- Quick add team members via group#people page
- [API] expose created date for hooks and SSH keys
- [API] list, create issue notes
- [API] list, create snippet notes

View File

@ -1,26 +1,26 @@
## Contribute to GitLab
# Contact & support
If you want to contribute to GitLab, follow this process:
If you want quick help, head over to our [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq).
Otherwise you can follow our [Issue Submission Guide](https://github.com/gitlabhq/gitlabhq/wiki/Issue-Submission-Guide) for a more systematic and thorough guide to solving your issues.
1. Fork the project
2. Create a feature branch
3. Code
4. Create a pull request
We will only accept pull requests if:
* Your code has proper tests and all tests pass
* Your code can be merged w/o problems
* It won't break existing functionality
* It's quality code
* We like it :)
# Contribute to GitLab
For examples of feedback on pull requests please look at the [closed pull requests](https://github.com/gitlabhq/gitlabhq/pulls?direction=desc&page=1&sort=created&state=closed).
## Recipes
## Installation
We collect user submitted installation scripts and config file templates for platforms we don't support officially.
We believe there is merit in allowing a certain amount of diversity.
You can get and submit your solution to running/configuring GitLab with your favorite OS/distro, database, web server, cloud hoster, configuration management tool, etc.
Install the Gitlab development in a virtual machine with the [Gitlab Vagrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm). Installing it in a virtual machine makes it much easier to set up all the dependencies for integration testing.
Help us improve the collection of [GitLab Recipes](https://github.com/gitlabhq/gitlab-recipes/)
## Running tests
For more information on running the tests please read the [development tips](https://github.com/gitlabhq/gitlabhq/blob/master/doc/development.md)
## Feature suggestions
Follow the [Issue Submission Guide](https://github.com/gitlabhq/gitlabhq/wiki/Issue-Submission-Guide) and support other peoples ideas or propose your own.
## Code
Follow our [Developer Guide](https://github.com/gitlabhq/gitlabhq/wiki/Developer-Guide) to set you up for hacking on GitLab.

View File

@ -32,7 +32,7 @@ gem 'grit_ext', git: "https://github.com/gitlabhq/grit_ext.git", ref:
gem "gitolite", '1.1.0'
# Syntax highlighter
gem "pygments.rb", git: "https://github.com/gitlabhq/pygments.rb.git", ref: '4db80c599067e2d5f23c5c243bf85b8ca0368ad4'
gem "pygments.rb", git: "https://github.com/gitlabhq/pygments.rb.git", branch: "master"
# Language detection
gem "github-linguist", "~> 2.3.4" , require: "linguist"
@ -100,7 +100,7 @@ group :assets do
gem "therubyracer"
gem 'chosen-rails', "0.9.8"
gem 'jquery-atwho-rails', "0.1.6"
gem 'jquery-atwho-rails', "0.1.7"
gem "jquery-rails", "2.1.3"
gem "jquery-ui-rails", "2.0.2"
gem "modernizr", "2.6.2"
@ -124,7 +124,7 @@ group :development, :test do
gem "capybara"
gem "pry"
gem "awesome_print"
gem "database_cleaner"
gem "database_cleaner", ref: "f89c34300e114be99532f14c115b2799a3380ac6", git: "https://github.com/bmabey/database_cleaner.git"
gem "launchy"
gem 'factory_girl_rails'

View File

@ -1,3 +1,10 @@
GIT
remote: https://github.com/bmabey/database_cleaner.git
revision: f89c34300e114be99532f14c115b2799a3380ac6
ref: f89c34300e114be99532f14c115b2799a3380ac6
specs:
database_cleaner (0.9.1)
GIT
remote: https://github.com/ctran/annotate_models.git
revision: be4e26825b521f0b2d86b181e2dff89901aa9b1e
@ -45,8 +52,8 @@ GIT
GIT
remote: https://github.com/gitlabhq/pygments.rb.git
revision: 4db80c599067e2d5f23c5c243bf85b8ca0368ad4
ref: 4db80c599067e2d5f23c5c243bf85b8ca0368ad4
revision: db1da0343adf86b49bdc3add04d02d2e80438d38
branch: master
specs:
pygments.rb (0.3.2)
posix-spawn (~> 0.3.6)
@ -140,7 +147,6 @@ GEM
colorize (0.5.8)
crack (0.3.1)
daemons (1.1.9)
database_cleaner (0.9.1)
devise (2.1.2)
bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.1)
@ -227,7 +233,7 @@ GEM
httpauth (0.2.0)
i18n (0.6.1)
journey (1.0.4)
jquery-atwho-rails (0.1.6)
jquery-atwho-rails (0.1.7)
jquery-rails (2.1.3)
railties (>= 3.1.0, < 5.0)
thor (~> 0.14)
@ -458,7 +464,7 @@ DEPENDENCIES
chosen-rails (= 0.9.8)
coffee-rails (~> 3.2.2)
colored
database_cleaner
database_cleaner!
devise (~> 2.1.0)
draper (~> 0.18.0)
email_spec
@ -481,7 +487,7 @@ DEPENDENCIES
guard-spinach
haml-rails (~> 0.3.5)
httparty
jquery-atwho-rails (= 0.1.6)
jquery-atwho-rails (= 0.1.7)
jquery-rails (= 2.1.3)
jquery-ui-rails (= 2.0.2)
kaminari (~> 0.14.1)

View File

@ -1,4 +1,4 @@
# 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

View File

@ -1 +1 @@
4.0.0pre
4.0.0rc1

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,52 +1,38 @@
# Creates the variables for setting up GFM auto-completion
window.GitLab ?= {}
GitLab.GfmAutoComplete ?= {}
# Emoji
data = []
template = "<li data-value='${insert}'>${name} <img alt='${name}' height='20' src='${image}' width='20' /></li>"
GitLab.GfmAutoComplete.Emoji = {data, template}
# Team Members
data = []
url = '';
params = {private_token: '', page: 1}
GitLab.GfmAutoComplete.Members = {data, url, params}
# Add GFM auto-completion to all input fields, that accept GFM input.
GitLab.GfmAutoComplete.setup = ->
input = $('.js-gfm-input')
GitLab.GfmAutoComplete =
# Emoji
input.atWho ':',
data: GitLab.GfmAutoComplete.Emoji.data,
tpl: GitLab.GfmAutoComplete.Emoji.template
Emoji:
data: []
template: '<li data-value="${insert}">${name} <img alt="${name}" height="20" src="${image}" width="20" /></li>'
# Team Members
input.atWho '@', (query, callback) ->
(getMoreMembers = ->
$.getJSON(GitLab.GfmAutoComplete.Members.url, GitLab.GfmAutoComplete.Members.params)
.success (members) ->
# pick the data we need
newMembersData = $.map(members, (m) -> m.name )
Members:
data: []
url: ''
params:
private_token: ''
template: '<li data-value="${username}">${username} <small>${name}</small></li>'
# add the new page of data to the rest
$.merge(GitLab.GfmAutoComplete.Members.data, newMembersData)
# Add GFM auto-completion to all input fields, that accept GFM input.
setup: ->
input = $('.js-gfm-input')
# show the pop-up with a copy of the current data
callback(GitLab.GfmAutoComplete.Members.data[..])
# Emoji
input.atWho ':',
data: @Emoji.data
tpl: @Emoji.template
# are we past the last page?
if newMembersData.length is 0
# set static data and stop callbacks
input.atWho '@',
data: GitLab.GfmAutoComplete.Members.data
callback: null
else
# get next page
getMoreMembers()
# Team Members
input.atWho '@',
tpl: @Members.template
callback: (query, callback) =>
request_params = $.extend({}, @Members.params, query: query)
$.getJSON(@Members.url, request_params).done (members) =>
new_members_data = $.map(members, (m) ->
username: m.username,
name: m.name
)
callback(new_members_data)
# so the next request gets the next page
GitLab.GfmAutoComplete.Members.params.page += 1
).call()

View File

@ -1,43 +1,3 @@
function switchToNewIssue(){
$(".issues_content").hide("fade", { direction: "left" }, 150, function(){
$('select#issue_assignee_id').chosen();
$('select#issue_milestone_id').chosen();
$("#new_issue_dialog").show("fade", { direction: "right" }, 150);
$('.top-tabs .add_new').hide();
disableButtonIfEmptyField("#issue_title", ".save-btn");
GitLab.GfmAutoComplete.setup();
});
}
function switchToEditIssue(){
$(".issues_content").hide("fade", { direction: "left" }, 150, function(){
$('select#issue_assignee_id').chosen();
$('select#issue_milestone_id').chosen();
$("#edit_issue_dialog").show("fade", { direction: "right" }, 150);
$('.add_new').hide();
disableButtonIfEmptyField("#issue_title", ".save-btn");
GitLab.GfmAutoComplete.setup();
});
}
function switchFromNewIssue(){
backToIssues();
}
function switchFromEditIssue(){
backToIssues();
}
function backToIssues(){
$("#edit_issue_dialog, #new_issue_dialog").hide("fade", { direction: "right" }, 150, function(){
$(".issues_content").show("fade", { direction: "left" }, 150, function() {
$("#edit_issue_dialog").html("");
$("#new_issue_dialog").html("");
$('.add_new').show();
});
});
}
function initIssuesSearch() {
var href = $('#issue_search_form').attr('action');
var last_terms = '';
@ -76,23 +36,15 @@ function issuesPage(){
$(this).closest("form").submit();
});
$("#new_issue_link").click(function(){
updateNewIssueURL();
});
$('body').on('ajax:success', '.close_issue, .reopen_issue, #new_issue', function(){
$('body').on('ajax:success', '.close_issue, .reopen_issue', function(){
var t = $(this),
totalIssues,
reopen = t.hasClass('reopen_issue'),
newIssue = false;
if( this.id == 'new_issue' ){
newIssue = true;
}
$('.issue_counter, #new_issue').each(function(){
reopen = t.hasClass('reopen_issue');
$('.issue_counter').each(function(){
var issue = $(this);
totalIssues = parseInt( $(this).html(), 10 );
if( newIssue || ( reopen && issue.closest('.main_menu').length ) ){
if( reopen && issue.closest('.main_menu').length ){
$(this).html( totalIssues+1 );
}else {
$(this).html( totalIssues-1 );
@ -126,20 +78,3 @@ function issuesCheckChanged() {
$('.issues_filters').show();
}
}
function updateNewIssueURL(){
var new_issue_link = $("#new_issue_link");
var milestone_id = $("#milestone_id").val();
var assignee_id = $("#assignee_id").val();
var new_href = "";
if(milestone_id){
new_href = "issue[milestone_id]=" + milestone_id + "&";
}
if(assignee_id){
new_href = new_href + "issue[assignee_id]=" + assignee_id;
}
if(new_href.length){
new_href = new_issue_link.attr("href") + "?" + new_href;
new_issue_link.attr("href", new_href);
}
};

View File

@ -7,6 +7,18 @@ window.slugify = (text) ->
window.ajaxGet = (url) ->
$.ajax({type: "GET", url: url, dataType: "script"})
window.errorMessage = (message) ->
ehtml = $("<p>")
ehtml.addClass("error_message")
ehtml.html(message)
ehtml
window.split = (val) ->
return val.split( /,\s*/ )
window.extractLast = (term) ->
return split( term ).pop()
# Disable button if text field is empty
window.disableButtonIfEmptyField = (field_selector, button_selector) ->
field = $(field_selector)

View File

@ -26,6 +26,12 @@ var MergeRequest = {
self.showState(data.state);
}, "json");
}
if(self.opts.ci_enable){
$.get(self.opts.url_to_ci_check, function(data){
self.showCiState(data.status);
}, "json");
}
},
initTabs:
@ -79,6 +85,11 @@ var MergeRequest = {
$(".automerge_widget." + state).show();
},
showCiState:
function(state){
$(".ci_widget").hide();
$(".ci_widget.ci-" + state).show();
},
loadDiff:
function() {

View File

@ -18,10 +18,3 @@ $ ->
# Ref switcher
$('.project-refs-select').on 'change', ->
$(@).parents('form').submit()
class @GraphNav
@init: ->
$('.graph svg').css 'position', 'relative'
$('body').bind 'keyup', (e) ->
$('.graph svg').animate(left: '+=400') if e.keyCode is 37 # left
$('.graph svg').animate(left: '-=400') if e.keyCode is 39 # right

View File

@ -1,10 +0,0 @@
/*
* This is a manifest file that'll automatically include all the stylesheets available in this directory
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope.
*= require jquery.ui.gitlab
*= require jquery.atwho
*= require chosen
*= require_self
*= require main
*/

View File

@ -0,0 +1,52 @@
/*
* This is a manifest file that'll automatically include all the stylesheets available in this directory
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope.
*= require jquery.ui.gitlab
*= require jquery.atwho
*= require chosen
*= require_self
*/
/**
* 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";

View File

@ -13,20 +13,12 @@ body {
margin: 0 0;
}
.container .sidebar {
width: 200px;
height: 100%;
min-height: 450px;
float: right;
}
.visible_link,
.author_link {
color: $link_color;
}
.help li { color:#111 }
.help li { color:$style_color; }
.back_link {
text-decoration: underline;
@ -65,6 +57,9 @@ table a code {
background: url(ajax_loader.gif) no-repeat center center;
width: 40px;
height: 40px;
&.loading-gray {
background: url(ajax_loader_gray.gif) no-repeat center center;
}
}
/** FLASH message **/
@ -96,28 +91,17 @@ table a code {
margin-right:50px
}
.handle:hover {
cursor: move;
}
span.update-author {
display: block;
}
span.update-author {
color: #999;
font-weight: normal;
font-style: italic;
}
span.update-author strong {
font-weight: bold;
font-style: normal;
strong {
font-weight: bold;
font-style: normal;
}
}
/** UPDATE ITEM **/
span.update-author {
display: block;
}
/** END UPDATE ITEM **/
.dashboard-loader {
float: left;
margin: 10px;
@ -264,21 +248,6 @@ input.git_clone_url {
}
/** bordered list **/
ul.bordered-list {
margin: 5px 0px;
padding: 0px;
li {
padding: 5px 0;
border-bottom: 1px solid #EEE;
overflow: hidden;
display: block;
margin: 0px;
}
}
ul.bordered-list li:last-child { border:none }
.line_holder {
&:hover {
td {
@ -316,98 +285,6 @@ p.time {
}
.ico {
background: url("images.png") no-repeat -85px -77px;
width: 19px;
height: 16px;
float: left;
position: relative;
margin-right: 10px;
top: 8px;
&.project {
background-position: -37px -77px;
}
&.activities {
background-position:-162px -22px;
}
&.projects {
background-position:-209px -21px;
}
}
.leftbar {
h5, .title {
padding: 5px 10px;
}
h4 {
font-size: 14px;
padding: 2px 10px;
color: #666;
border-bottom: 1px solid #f1f1f1;
}
a:last-child h4 { border: none; }
a:hover {
h4 {
color: #111;
background: $hover;
border-color: #CCC;
.ico.project {
background-position:-209px -21px;
}
}
}
.bottom {
padding: 10px;
}
}
.votes {
font-size: 13px;
line-height: 15px;
.progress {
height: 4px;
margin: 0;
.bar {
float: left;
height: 100%;
}
.bar-success {
@include linear-gradient(#62C462, #51A351);
background-color: #468847;
}
.bar-danger {
@include linear-gradient(#EE5F5B, #BD362F);
background-color: #B94A48;
}
}
.upvotes {
display: inline-block;
color: #468847;
}
.downvotes {
display: inline-block;
color: #B94A48;
}
}
.votes-block {
margin: 14px 6px 6px 0;
.downvotes {
float: right;
}
}
.votes-inline {
display: inline-block;
margin: 0 8px;
.progress {
display: inline-block;
padding: 0 0 2px;
width: 45px;
}
}
/* Fix for readme code (stopped it from being yellow) */
.readme {
@ -420,7 +297,6 @@ p.time {
}
}
.highlight_word {
background: #EEDC94;
}
@ -428,23 +304,16 @@ p.time {
.status_info {
font-size: 14px;
padding: 5px 15px;
line-height: 24px;
width: 60px;
line-height: 26px;
text-align: center;
float: left;
margin-right: 20px;
float: right;
position: relative;
top: -5px;
@include border-radius(4px);
&.success {
background: #5BB75B;
color: white;
text-shadow: 0 1px #111;
border-color: #9A9;
}
&.error {
background: #DA4E49;
border-color: #BD362F;
color: white;
text-shadow: 0 1px #111;
color: #FFF;
}
}
@ -463,16 +332,6 @@ p.time {
height: 150px;
}
.gitlab_pagination {
span a { color: $link_color; }
.prev, .next, .current, .page a {
padding: 10px;
}
.current {
border-bottom: 2px solid $style_color;
}
}
// Fixes alignment on notes.
.new_note {
label {
@ -647,9 +506,14 @@ pre {
}
}
.milestone .progress {
margin-bottom: 0;
margin-top: 4px;
.milestone {
&.milestone-closed {
background: #eee;
}
.progress {
margin-bottom: 0;
margin-top: 4px;
}
}
.float-link {

View File

@ -0,0 +1,26 @@
/** 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";

View File

@ -31,6 +31,7 @@
.middle_box_content,
.bottom_box_content {
padding: 15px;
word-wrap: break-word;
pre {
background: none !important;
@ -40,6 +41,15 @@
}
}
.top_box_content {
.box-title {
color: $style_color;
font-size: 18px;
font-weight: normal;
line-height: 28px;
}
}
.middle_box_content {
@include border-radius(0);
border: none;
@ -64,7 +74,7 @@
border: 1px solid #eaeaea;
@include border-radius(4px);
border-color: #CCC;
@include solid-shade;
@ -83,6 +93,10 @@
border-top: 1px solid #eaeaea;
border-bottom: 1px solid #bbb;
> a {
text-shadow: 0 1px 1px #fff;
}
&.small {
line-height: 28px;
font-size: 14px;
@ -138,19 +152,6 @@
}
}
li, .wll {
padding: 10px;
&:first-child {
@include border-radius(4px 4px 0 0);
border-top: none;
}
&:last-child {
@include border-radius(0 0 4px 4px);
border: none;
}
}
.ui-box-body {
padding: 10px;
}

View File

@ -10,11 +10,6 @@
/** COMMON CLASSES **/
.left { float:left }
.right { float:right!important }
.width-50p { width:50% }
.width-49p { width:49% }
.width-30p { width:30% }
.width-65p { width:65% }
.width-100p { width:100% }
.append-bottom-10 { margin-bottom:10px }
.append-bottom-20 { margin-bottom:20px }
.prepend-top-10 { margin-top:10px }
@ -30,6 +25,7 @@
.borders { border: 1px solid #ccc; @include shade; }
.hint { font-style: italic; color: #999; }
.light { color: #888 }
.tiny { font-weight: normal }
/** PILLS & TABS**/
.nav-pills a:hover { background-color: #888; }
@ -99,18 +95,21 @@ input[type='search'].search-text-input {
border: 1px solid #ccc;
}
input[type='text'].danger {
background: #F2DEDE!important;
border-color: #D66;
text-shadow: 0 1px 1px #fff
}
fieldset legend { font-size: 17px; }
ul.nav.nav-projects-tabs {
@extend .nav-tabs;
padding-left: 8px;
li {
a {
padding: 4px 20px;
margin-top: 2px;
border-color: #DDD;
}
/** PAGINATION **/
.gitlab_pagination {
span a { color: $link_color; }
.prev, .next, .current, .page a {
padding: 10px;
}
.current {
border-bottom: 2px solid $style_color;
}
}

View File

@ -43,11 +43,15 @@
padding: 0 4px;
}
padding: 20px;
h1, h2 {
line-height: 46px;
}
h3, h4 {
line-height: 40px;
h1 { font-size: 26px; line-height: 46px; }
h2 { font-size: 22px; line-height: 42px; }
h3 { font-size: 20px; line-height: 40px; }
h4 { font-size: 18px; line-height: 32px; }
h5 { font-size: 16px; line-height: 26px; }
.white .highlight pre {
background: #f5f5f5;
}
}

View File

@ -1,23 +1,38 @@
/** LISTS **/
ul {
/**
* List li block element #1
*
*/
.wll {
/**
* Well styled list
*
*/
.well-list {
margin: 0;
list-style: none;
li {
background-color: #FFF;
padding: 10px 5px;
padding: 10px;
min-height: 20px;
border-bottom: 1px solid #eee;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
&.disabled {
color: #888;
}
&.smoke { background-color: #f5f5f5; }
&:hover {
background: $hover;
border-bottom: 1px solid #ADF;
}
&:last-child { border:none }
&:first-child {
@include border-radius(4px 4px 0 0);
border-top: none;
}
&:last-child {
@include border-radius(0 0 4px 4px);
border: none;
}
.author { color: #999; }
p {
@ -29,6 +44,11 @@ ul {
top: 3px;
}
}
.well-title {
font-size: 14px;
line-height: 18px;
}
}
}
@ -39,3 +59,17 @@ ol, ul {
}
}
}
/** light list with border-bottom between li **/
ul.bordered-list {
margin: 5px 0px;
padding: 0px;
li {
padding: 5px 0;
border-bottom: 1px solid #EEE;
overflow: hidden;
display: block;
margin: 0px;
&:last-child { border:none }
}
}

View File

@ -57,4 +57,13 @@
@mixin solid-shade {
@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;
}

View File

@ -2,4 +2,4 @@
$primary_color: #2FA0BB;
$link_color: #3A89A3;
$style_color: #474D57;
$hover: #D9EDF7;
$hover: #D9EDF7;

View File

@ -1,7 +1,8 @@
.black .highlight {
background-color: #333;
pre {
background-color: #333;
color: #eee;
background: inherit;
}
.hll { display: block; background-color: darken($hover, 65%) }

View File

@ -1,27 +1,3 @@
/*
* jQuery UI CSS Framework 1.8.7
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Theming/API
*/
/* Layout helpers
----------------------------------*/
.ui-helper-hidden { display: none; }
.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
.ui-helper-clearfix { display: inline-block; }
/* required comment for clearfix to work in Opera \*/
* html .ui-helper-clearfix { height:1%; }
.ui-helper-clearfix { display:block; }
/* end clearfix */
.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
/* Interaction Cues
----------------------------------*/
.ui-state-disabled { cursor: default !important; }
@ -140,26 +116,6 @@
/* Overlays */
.ui-widget-overlay { background: #262b33; opacity: .70;filter:Alpha(Opacity=70); }
.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #000000; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }
/*
* jQuery UI Resizable 1.8.7
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Resizable#theming
*/
.ui-resizable { position: relative;}
.ui-resizable-handle { position: absolute; font-size: 0.1px; z-index: 999; display: block;}
.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}
/*
* jQuery UI Selectable 1.8.7
*
@ -240,34 +196,7 @@
cursor: pointer;
font-weight: bold;
}
/*
* jQuery UI Slider 1.8.7
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Slider#theming
*/
.ui-slider { position: relative; text-align: left; background: #d7d7d7; z-index: 1; }
.ui-slider { -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.5) inset; -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.5) inset; box-shadow: 0 1px 2px rgba(0,0,0,0.5) inset; }
.ui-slider .ui-slider-handle { background: url(slider_handles.png) 0px -23px no-repeat; position: absolute; z-index: 2; width: 23px; height: 23px; cursor: default; border: none; outline: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; }
.ui-slider .ui-state-hover, .ui-slider .ui-state-active { background-position: 0 0; }
.ui-slider .ui-slider-range { background: #a3cae0; position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
.ui-slider .ui-slider-range { -moz-box-shadow: 0 1px 2px rgba(17,35,45,0.6) inset; -webkit-box-shadow: 0 1px 2px rgba(17,35,45,0.6) inset; box-shadow: 0 1px 2px rgba(17,35,45,0.6) inset; }
.ui-slider-horizontal { height: 5px; }
.ui-slider-horizontal .ui-slider-handle { top: -8px; margin-left: -13px; }
.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
.ui-slider-horizontal .ui-slider-range-min { left: 0; }
.ui-slider-horizontal .ui-slider-range-max { right: 0; }
.ui-slider-vertical { width: 5px; height: 100px; }
.ui-slider-vertical .ui-slider-handle { left: -8px; margin-left: 0; margin-bottom: -13px; }
.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
.ui-slider-vertical .ui-slider-range-max { top: 0; }
/*
* jQuery UI Datepicker 1.8.7
*
@ -326,45 +255,3 @@
.ui-datepicker table .ui-state-highlight { border-color: #ADE; }
.ui-datepicker-calendar .ui-state-default { background: transparent; border-color: #FFF; }
.ui-datepicker-calendar .ui-state-active { background: #D9EDF7; border-color: #ADE; color: #3A89A3; font-weight: bold; text-shadow: 0 1px 1px #fff; }
/* with multiple calendars */
.ui-datepicker.ui-datepicker-multi { width:auto; }
.ui-datepicker-multi .ui-datepicker-group { float:left; }
.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
.ui-datepicker-row-break { clear:both; width:100%; }
/* Extra Input Field Styling */
.ui-form textarea, .ui-form input:not([type="submit"]):not([type="button"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]) {
padding: 3px;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
border: 1px solid #cecece;
outline: none;
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.1) inset, 0 1px 0 rgba(255,255,255,0.2);
-moz-box-shadow: 0 1px 3px rgba(0,0,0,0.1) inset, 0 1px 0 rgba(255,255,255,0.2);
box-shadow: 0 1px 3px rgba(0,0,0,0.1) inset, 0 1px 0 rgba(255,255,255,0.2);
-webkit-transition: all 250ms ease-in-out;
-moz-transition: all 250ms ease-in-out;
-o-transition: all 250ms ease-in-out;
transition: all 250ms ease-in-out;
}
.ui-form textarea:hover, .ui-form input:not([type="submit"]):not([type="button"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):hover {
border: 1px solid #bdbdbd;
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.2) inset, 0 1px 0 rgba(255,255,255,0.2);
-moz-box-shadow: 0 1px 3px rgba(0,0,0,0.2) inset, 0 1px 0 rgba(255,255,255,0.2);
box-shadow: 0 1px 3px rgba(0,0,0,0.2) inset, 0 1px 0 rgba(255,255,255,0.2);
}
.ui-form textarea:focus, .ui-form input:not([type="submit"]):not([type="button"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):focus {
border: 1px solid #95bdd4;
-webkit-box-shadow: 0 2px 3px rgba(161,202,226,0.5) inset, 0 1px 0 rgba(255,255,255,0.2);
-moz-box-shadow: 0 2px 3px rgba(161,202,226,0.5) inset, 0 1px 0 rgba(255,255,255,0.2);
box-shadow: 0 2px 3px rgba(161,202,226,0.5) inset, 0 1px 0 rgba(255,255,255,0.2);
}

View File

@ -1,144 +0,0 @@
/** 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";

View File

@ -232,8 +232,6 @@
/** COMMIT ROW **/
.commit {
@extend .wll;
.browse_code_link_holder {
@extend .span2;
float: right;
@ -305,3 +303,17 @@
color: #fff;
font-family: $monospace;
}
.commits-compare-switch{
background: url("switch_icon.png") no-repeat center center;
width: 16px;
height: 18px;
text-indent: -9999px;
float: left;
margin-right: 9px;
border: 1px solid #DDD;
@include border-radius(4px);
padding: 4px;
background-color: #EEE;
}

View File

@ -31,7 +31,6 @@
*
*/
.event-item {
min-height: 40px;
border-bottom: 1px solid #eee;
.event-title {
color: #333;
@ -50,14 +49,18 @@
}
}
.avatar {
width: 32px;
position: relative;
top: -3px;
}
.event_icon {
position: relative;
float: right;
border: 1px solid #EEE;
padding: 5px;
@include border-radius(5px);
background: #F9F9F9;
margin-left: 10px;
top: -6px;
img {
width: 20px;
}
@ -71,9 +74,8 @@
}
}
padding: 15px 5px;
padding: 16px 5px;
&:last-child { border:none }
.wll:hover { background:none }
.event_commits {
margin-top: 5px;

View File

@ -44,14 +44,9 @@ header {
background: url('logo_dark.png') no-repeat 0px 2px;
float: left;
margin-left: 2px;
font-size: 30px;
line-height: 48px;
font-weight: normal;
color: $style_color;
text-shadow: 0 1px 1px #FFF;
padding-left: 45px;
height: 40px;
font-family: 'Korolev', sans-serif;
@include header-font;
}
}
}
@ -66,12 +61,7 @@ header {
float: left;
margin: 0;
margin-right: 30px;
font-size: 30px;
line-height: 48px;
font-weight: normal;
color: $style_color;
text-shadow: 0 1px 1px #FFF;
font-family: 'Korolev', sans-serif;
@include header-font;
}
/**
@ -172,7 +162,7 @@ header {
display: none;
z-index: 100000;
@include border-radius(4px);
width: 100px;
width: 130px;
position: absolute;
right: 5px;
top: 38px;
@ -181,7 +171,7 @@ header {
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
a {
color: #fff;
padding: 7px 10px;
padding: 12px 15px;
display: block;
text-shadow: none;
border-bottom: 1px solid #666;
@ -204,8 +194,8 @@ header {
}
&:last-child {
@include border-radius(0 0 5px 5px);
border-bottom: 0;
}
border-bottom: 0;
}
}

View File

@ -121,12 +121,3 @@ input.check_all_issues {
#update_status {
width: 100px;
}
/**
* Milestones list
*
*/
.milestone {
@extend .wll;
}

View File

@ -136,9 +136,3 @@ li.merge_request {
}
}
}
.status-badge {
height: 32px;
width: 100%;
@include border-radius(5px);
}

View File

@ -3,15 +3,13 @@
*
*/
ul.main_menu {
@include border-radius(4px);
margin: auto;
margin: 30px 0;
border: 1px solid #BBB;
margin-top: 10px;
border-bottom: 1px solid #DDD;
height: 37px;
@include bg-gray-gradient;
position: relative;
overflow: hidden;
@include shade;
.count {
position: relative;
top: -1px;
@ -24,9 +22,6 @@ ul.main_menu {
line-height: 14px;
text-align: center;
color: #777;
background: #f2f2f2;
border-top: 1px solid #CCC;
@include border-radius(8px);
}
.label {
background: $hover;
@ -38,23 +33,10 @@ ul.main_menu {
margin: 0;
display: table-cell;
width: 1%;
border-right: 1px solid #DDD;
border-left: 1px solid #EEE;
border-bottom: 2px solid #CFCFCF;
&:first-child{
@include border-radius(5px 0 0 5px);
border-left: 0;
}
&.active {
background-color: #D5D5D5;
border-right: 1px solid #BBB;
border-left: 1px solid #BBB;
@include border-radius(0 0 1px 1px);
&:first-child{
border-bottom: none;
border-left: none;
border-bottom: 2px solid #474D57;
a {
color: $style_color;
}
}
@ -73,10 +55,10 @@ ul.main_menu {
a {
display: block;
text-align: center;
font-weight: bold;
font-weight: normal;
height: 35px;
line-height: 36px;
color: $style_color;
color: #777;
text-shadow: 0 1px 1px white;
padding: 0 10px;
}

View File

@ -4,12 +4,11 @@
}
.side {
@extend .span4;
@extend .right;
.groups_box,
.projects_box {
h5 {
> h5 {
color: $style_color;
font-size: 16px;
text-shadow: 0 1px 1px #fff;
@ -17,37 +16,22 @@
line-height: 32px;
font-size: 14px;
}
ul {
li {
padding: 0;
a {
display: block;
.group_name {
font-size: 14px;
line-height: 18px;
}
.project_name {
color: #4fa2bd;
font-size: 14px;
line-height: 18px;
}
.arrow {
float: right;
padding: 10px;
margin: 0;
}
.last_activity {
padding-top: 5px;
display: block;
span, strong {
font-size: 12px;
color: #666;
}
}
.nav-projects-tabs li { padding: 0; }
.well-list {
.arrow {
float: right;
padding: 10px;
margin: 0;
}
.last_activity {
padding-top: 5px;
display: block;
span, strong {
font-size: 12px;
color: #666;
}
}
}
@extend .leftbar;
@extend .ui-box;
}
}
@ -117,3 +101,25 @@
}
}
ul.nav.nav-projects-tabs {
@extend .nav-tabs;
padding-left: 8px;
li {
a {
padding: 4px 20px;
margin-top: 2px;
border-color: #DDD;
background-color: #EEE;
text-shadow: 0 1px 1px white;
color: #555;
}
&.active {
a {
font-weight: bold;
}
}
}
}

View File

@ -0,0 +1,9 @@
.snippet.file_holder {
.file_title {
.snippet-file-name {
position: relative;
top: -4px;
left: -4px;
}
}
}

View File

@ -0,0 +1,43 @@
.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;
}
}

View File

@ -4,18 +4,6 @@
*
*/
.ui_basic {
/*
* Common styles
*
*/
a {
color: $link_color;
&:hover {
text-decoration: none;
color: $primary_color;
}
}
.app_logo {
.separator {
margin-left: 0;

View File

@ -2,7 +2,9 @@ class ProjectUpdateContext < BaseContext
def execute(role = :default)
namespace_id = params[:project].delete(:namespace_id)
if namespace_id.present?
allowed_transfer = can?(current_user, :change_namespace, project) || role == :admin
if allowed_transfer && namespace_id.present?
if namespace_id == Namespace.global_id
if project.namespace.present?
# Transfer to global namespace from anyone

View File

@ -2,7 +2,7 @@ class Admin::GroupsController < AdminController
before_filter :group, only: [:edit, :show, :update, :destroy, :project_update]
def index
@groups = Group.scoped
@groups = Group.order('name ASC')
@groups = @groups.search(params[:name]) if params[:name].present?
@groups = @groups.page(params[:page]).per(20)
end
@ -11,6 +11,7 @@ class Admin::GroupsController < AdminController
@projects = Project.scoped
@projects = @projects.not_in_group(@group) if @group.projects.present?
@projects = @projects.all
@projects.reject!(&:empty_repo?)
end
def new

View File

@ -4,12 +4,13 @@ class Admin::ProjectsController < AdminController
def index
@projects = Project.scoped
@projects = @projects.where(namespace_id: params[:namespace_id]) if params[:namespace_id].present?
@projects = @projects.where(namespace_id: nil) if params[:namespace_id] == Namespace.global_id
@projects = @projects.search(params[:name]) if params[:name].present?
@projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20)
end
def show
@users = User.scoped
@users = User.active
@users = @users.not_in_project(@project) if @project.users.present?
@users = @users.all
end

View File

@ -3,7 +3,7 @@ class Admin::UsersController < AdminController
@admin_users = User.scoped
@admin_users = @admin_users.filter(params[:filter])
@admin_users = @admin_users.search(params[:name]) if params[:name].present?
@admin_users = @admin_users.order("updated_at DESC").page(params[:page])
@admin_users = @admin_users.order("name ASC").page(params[:page])
end
def show
@ -30,7 +30,7 @@ class Admin::UsersController < AdminController
def new
@admin_user = User.new({ projects_limit: Gitlab.config.default_projects_limit }, as: :admin)
@admin_user = User.new({ projects_limit: Gitlab.config.gitlab.default_projects_limit }, as: :admin)
end
def edit

View File

@ -112,6 +112,10 @@ class ApplicationController < ActionController::Base
render file: Rails.root.join("public", "404"), layout: false, status: "404"
end
def render_403
render file: Rails.root.join("public", "403"), layout: false, status: "403"
end
def require_non_empty_project
redirect_to @project if @project.empty_repo?
end

View File

@ -7,6 +7,8 @@ class DashboardController < ApplicationController
def index
@groups = current_user.authorized_groups
@has_authorized_projects = @projects.count > 0
@projects = case params[:scope]
when 'personal' then
@projects.personal(current_user)

View File

@ -21,7 +21,7 @@ class GroupsController < ApplicationController
# Get authored or assigned open merge requests
def merge_requests
@merge_requests = current_user.cared_merge_requests
@merge_requests = current_user.cared_merge_requests.opened
@merge_requests = @merge_requests.of_group(@group).recent.page(params[:page]).per(20)
end
@ -49,6 +49,7 @@ class GroupsController < ApplicationController
def people
@project = group.projects.find(params[:project_id]) if params[:project_id]
@users = @project ? @project.users : group.users
@users.sort_by!(&:name)
if @project
@team_member = @project.users_projects.new

View File

@ -1,6 +1,6 @@
class IssuesController < ProjectResourceController
before_filter :module_enabled
before_filter :issue, only: [:edit, :update, :destroy, :show]
before_filter :issue, only: [:edit, :update, :show]
# Allow read any issue
before_filter :authorize_read_issue!
@ -11,9 +11,6 @@ class IssuesController < ProjectResourceController
# Allow modify issue
before_filter :authorize_modify_issue!, only: [:edit, :update]
# Allow destroy issue
before_filter :authorize_admin_issue!, only: [:destroy]
respond_to :js, :html
def index
@ -79,15 +76,6 @@ class IssuesController < ProjectResourceController
end
end
def destroy
@issue.destroy
respond_to do |format|
format.html { redirect_to project_issues_path }
format.js { render nothing: true }
end
end
def sort
return render_404 unless can?(current_user, :admin_issue, @project)

View File

@ -1,6 +1,6 @@
class MergeRequestsController < ProjectResourceController
before_filter :module_enabled
before_filter :merge_request, only: [:edit, :update, :destroy, :show, :commits, :diffs, :automerge, :automerge_check]
before_filter :merge_request, only: [:edit, :update, :show, :commits, :diffs, :automerge, :automerge_check, :ci_status]
before_filter :validates_merge_request, only: [:show, :diffs]
before_filter :define_show_vars, only: [:show, :diffs]
@ -13,9 +13,6 @@ class MergeRequestsController < ProjectResourceController
# Allow modify merge_request
before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort]
# Allow destroy merge_request
before_filter :authorize_admin_merge_request!, only: [:destroy]
def index
@merge_requests = MergeRequestsLoadContext.new(project, current_user, params).execute
end
@ -90,14 +87,6 @@ class MergeRequestsController < ProjectResourceController
end
end
def destroy
@merge_request.destroy
respond_to do |format|
format.html { redirect_to project_merge_requests_url(@project) }
end
end
def branch_from
@commit = project.commit(params[:ref])
@commit = CommitDecorator.decorate(@commit)
@ -108,6 +97,13 @@ class MergeRequestsController < ProjectResourceController
@commit = CommitDecorator.decorate(@commit)
end
def ci_status
status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha)
response = { status: status }
render json: response
end
protected
def merge_request

View File

@ -12,11 +12,12 @@ class MilestonesController < ProjectResourceController
def index
@milestones = case params[:f]
when 'all'; @project.milestones
else @project.milestones.active
when 'all'; @project.milestones.order("closed, due_date DESC")
when 'closed'; @project.milestones.closed.order("due_date DESC")
else @project.milestones.active.order("due_date ASC")
end
@milestones = @milestones.includes(:project).order("due_date")
@milestones = @milestones.includes(:project)
@milestones = @milestones.page(params[:page]).per(20)
end
@ -42,6 +43,7 @@ class MilestonesController < ProjectResourceController
def create
@milestone = @project.milestones.new(params[:milestone])
@milestone.author_id_of_changes = current_user.id
if @milestone.save
redirect_to project_milestone_path(@project, @milestone)
@ -51,7 +53,7 @@ class MilestonesController < ProjectResourceController
end
def update
@milestone.update_attributes(params[:milestone])
@milestone.update_attributes(params[:milestone].merge(author_id_of_changes: current_user.id))
respond_to do |format|
format.js

View File

@ -1,5 +1,5 @@
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
Gitlab.config.omniauth_providers.each do |provider|
Gitlab.config.omniauth.providers.each do |provider|
define_method provider['name'] do
handle_omniauth
end

View File

@ -46,6 +46,10 @@ class ProjectsController < ProjectResourceController
format.js
end
end
rescue Project::TransferError => ex
@error = ex
render :update_failed
end
def show
@ -54,12 +58,12 @@ class ProjectsController < ProjectResourceController
respond_to do |format|
format.html do
unless @project.empty_repo?
@last_push = current_user.recent_push(@project.id)
render :show
else
render "projects/empty"
end
unless @project.empty_repo?
@last_push = current_user.recent_push(@project.id)
render :show
else
render "projects/empty"
end
end
format.js
end
@ -86,12 +90,18 @@ class ProjectsController < ProjectResourceController
end
def graph
graph = Gitlab::Graph::JsonBuilder.new(project)
@days_json, @commits_json = graph.days_json, graph.commits_json
respond_to do |format|
format.html
format.json do
graph = Gitlab::Graph::JsonBuilder.new(project)
render :json => graph.to_json
end
end
end
def destroy
return access_denied! unless can?(current_user, :remove_project, project)
# Disable the UsersProject update_repository call, otherwise it will be
# called once for every person removed from the project
UsersProject.skip_callback(:destroy, :after, :update_repository)

View File

@ -16,7 +16,7 @@ class SnippetsController < ProjectResourceController
respond_to :html
def index
@snippets = @project.snippets
@snippets = @project.snippets.fresh
end
def new
@ -62,7 +62,7 @@ class SnippetsController < ProjectResourceController
redirect_to project_snippets_path(@project)
end
def raw
def raw
send_data(
@snippet.content,
type: "text/plain",

View File

@ -76,7 +76,7 @@ class CommitDecorator < ApplicationDecorator
source_name = send "#{options[:source]}_name".to_sym
source_email = send "#{options[:source]}_email".to_sym
text = if options[:avatar]
avatar = h.image_tag h.gravatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size]
avatar = h.image_tag h.gravatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: ""
%Q{#{avatar} <span class="commit-#{options[:source]}-name">#{source_name}</span>}
else
source_name

View File

@ -1,4 +1,5 @@
require 'digest/md5'
require 'uri'
module ApplicationHelper
@ -30,13 +31,15 @@ module ApplicationHelper
args.any? { |v| v.to_s.downcase == action_name }
end
def gravatar_icon(user_email = '', size = 40)
if Gitlab.config.disable_gravatar? || user_email.blank?
def gravatar_icon(user_email = '', size = nil)
size = 40 if size.nil? || size <= 0
if !Gitlab.config.gravatar.enabled || user_email.blank?
'no_avatar.png'
else
gravatar_prefix = request.ssl? ? "https://secure" : "http://www"
gravatar_url = request.ssl? ? Gitlab.config.gravatar.ssl_url : Gitlab.config.gravatar.plain_url
user_email.strip!
"#{gravatar_prefix}.gravatar.com/avatar/#{Digest::MD5.hexdigest(user_email.downcase)}?s=#{size}&d=mm"
sprintf(gravatar_url, {:hash => Digest::MD5.hexdigest(user_email.downcase), :email => URI.escape(user_email), :size => size})
end
end
@ -45,7 +48,7 @@ module ApplicationHelper
end
def web_app_url
"#{request_protocol}://#{Gitlab.config.web_host}/"
"#{request_protocol}://#{Gitlab.config.gitlab.host}/"
end
def last_commit(project)
@ -92,6 +95,7 @@ module ApplicationHelper
{ label: "API Help", url: help_api_path },
{ label: "Markdown Help", url: help_markdown_path },
{ label: "SSH Keys Help", url: help_ssh_path },
{ label: "Gitlab Rake Tasks Help", url: help_raketasks_path },
]
project_nav = []

View File

@ -4,28 +4,6 @@ module IssuesHelper
project_issues_path project, params
end
def link_to_issue_assignee(issue)
project = issue.project
tm = project.team_member_by_id(issue.assignee_id)
if tm
link_to issue.assignee_name, project_team_member_path(project, tm), class: "author_link"
else
issue.assignee_name
end
end
def link_to_issue_author(issue)
project = issue.project
tm = project.team_member_by_id(issue.author_id)
if tm
link_to issue.author_name, project_team_member_path(project, tm), class: "author_link"
else
issue.author_name
end
end
def issue_css_classes issue
classes = "issue"
classes << " closed" if issue.closed
@ -52,4 +30,14 @@ module IssuesHelper
open: "open"
}
end
def labels_autocomplete_source
labels = @project.issues_labels.order('count DESC')
labels = labels.map{ |l| { label: l.name, value: l.name } }
labels.to_json
end
def issues_active_milestones
@project.milestones.active.order("id desc").all
end
end

View File

@ -1,26 +1,4 @@
module MergeRequestsHelper
def link_to_merge_request_assignee(merge_request)
project = merge_request.project
tm = project.team_member_by_id(merge_request.assignee_id)
if tm
link_to merge_request.assignee_name, project_team_member_path(project, tm), class: "author_link"
else
merge_request.assignee_name
end
end
def link_to_merge_request_author(merge_request)
project = merge_request.project
tm = project.team_member_by_id(merge_request.author_id)
if tm
link_to merge_request.author_name, project_team_member_path(project, tm), class: "author_link"
else
merge_request.author_name
end
end
def new_mr_path_from_push_event(event)
new_project_merge_request_path(
event.project,
@ -39,7 +17,7 @@ module MergeRequestsHelper
classes
end
def ci_status_path
@project.gitlab_ci_service.commit_badge_path(@merge_request.last_commit.sha)
def ci_build_details_path merge_request
merge_request.project.gitlab_ci_service.build_page(merge_request.last_commit.sha)
end
end

View File

@ -8,11 +8,49 @@ module ProjectsHelper
end
def link_to_project project
link_to project.name, project
link_to project do
title = content_tag(:strong, project.name)
if project.namespace
namespace = content_tag(:span, "#{project.namespace.human_name} / ", class: 'tiny')
title = namespace + title
end
title
end
end
def link_to_member(project, author)
return "(deleted)" unless author
# Build avatar image tag
avatar = image_tag(gravatar_icon(author.try(:email)), width: 16, class: "lil_av")
# Build name strong tag
name = content_tag :strong, author.name, class: 'author'
author_html = avatar + name
tm = project.team_member_by_id(author)
content_tag :span, class: 'member-link' do
if tm
link_to author_html, project_team_member_path(project, tm), class: "author_link"
else
author_html
end
end
end
def tm_path team_member
project_team_member_path(@project, team_member)
end
end
def project_title project
if project.group
project.name_with_namespace
else
project.name
end
end
end

View File

@ -72,7 +72,7 @@ module TabHelper
return "active" if current_page?(controller: "projects", action: action, id: @project)
end
if ['snippets', 'hooks', 'deploy_keys', 'team_members'].include? controller.controller_name
if ['snippets', 'services', 'hooks', 'deploy_keys', 'team_members'].include? controller.controller_name
"active"
end
end

View File

@ -3,11 +3,11 @@ class Notify < ActionMailer::Base
add_template_helper ApplicationHelper
add_template_helper GitlabMarkdownHelper
default_url_options[:host] = Gitlab.config.web_host
default_url_options[:protocol] = Gitlab.config.web_protocol
default_url_options[:port] = Gitlab.config.web_port if Gitlab.config.web_custom_port?
default_url_options[:host] = Gitlab.config.gitlab.host
default_url_options[:protocol] = Gitlab.config.gitlab.protocol
default_url_options[:port] = Gitlab.config.gitlab.port if Gitlab.config.gitlab_on_non_standard_port?
default from: Gitlab.config.email_from
default from: Gitlab.config.gitlab.email_from
@ -31,6 +31,7 @@ class Notify < ActionMailer::Base
def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id)
@issue = Issue.find issue_id
@issue_status = status
@project = @issue.project
@updated_by = User.find updated_by_user_id
mail(to: recipient(recipient_id),
subject: subject("changed issue ##{@issue.id}", @issue.title))
@ -102,6 +103,12 @@ class Notify < ActionMailer::Base
end
def project_was_moved_email(user_project_id)
@users_project = UsersProject.find user_project_id
@project = @users_project.project
mail(to: @users_project.user.email,
subject: subject("project was moved"))
end
#
# User

View File

@ -17,9 +17,7 @@ class Ability
# Rules based on role in project
if project.master_access_for?(user)
# TODO: replace with master rules.
# Only allow project administration for namespace owners
rules << project_admin_rules
rules << project_master_rules
elsif project.dev_access_for?(user)
rules << project_dev_rules
@ -93,13 +91,16 @@ class Ability
:admin_merge_request,
:admin_note,
:accept_mr,
:admin_wiki
:admin_wiki,
:admin_project
]
end
def project_admin_rules
project_master_rules + [
:admin_project
:change_namespace,
:rename_project,
:remove_project
]
end

View File

@ -87,14 +87,10 @@ class Commit
last = project.commit(from.try(:strip))
if first && last
commits = [first, last].sort_by(&:created_at)
younger = commits.first
older = commits.last
result[:same] = (younger.id == older.id)
result[:commits] = project.repo.commits_between(younger.id, older.id).map {|c| Commit.new(c)}
result[:diffs] = project.repo.diff(younger.id, older.id) rescue []
result[:commit] = Commit.new(older)
result[:same] = (first.id == last.id)
result[:commits] = project.repo.commits_between(last.id, first.id).map {|c| Commit.new(c)}
result[:diffs] = project.repo.diff(last.id, first.id) rescue []
result[:commit] = Commit.new(first)
end
result
@ -163,6 +159,8 @@ class Commit
while !lines.first.start_with?("diff --git") do
lines.shift
end
lines.pop if lines.last =~ /^[\d.]+$/ # Git version
lines.pop if lines.last == "-- " # end of diff
lines.join("\n")
end
end

View File

@ -15,6 +15,7 @@
#
class Event < ActiveRecord::Base
include NoteEvent
include PushEvent
attr_accessible :project, :action, :data, :author_id, :project_id,
@ -58,12 +59,14 @@ class Event < ActiveRecord::Base
end
end
# Next events currently enabled for system
# - push
# - new issue
# - merge request
def allowed?
push? || issue? || merge_request? || membership_changed?
def proper?
if push?
true
elsif membership_changed?
true
else
(issue? || merge_request? || note? || milestone?) && target
end
end
def project_name
@ -94,6 +97,14 @@ class Event < ActiveRecord::Base
action == self.class::Reopened
end
def milestone?
target_type == "Milestone"
end
def note?
target_type == "Note"
end
def issue?
target_type == "Issue"
end

View File

@ -36,4 +36,22 @@ class GitlabCiService < Service
def commit_badge_path sha
project_url + "/status?sha=#{sha}"
end
def commit_status_path sha
project_url + "/builds/#{sha}/status.json?token=#{token}"
end
def commit_status sha
response = HTTParty.get(commit_status_path(sha))
if response.code == 200 and response["status"]
response["status"]
else
:error
end
end
def build_page sha
project_url + "/builds/#{sha}"
end
end

View File

@ -204,7 +204,7 @@ class MergeRequest < ActiveRecord::Base
def mr_and_commit_notes
commit_ids = commits.map(&:id)
Note.where("(noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR (noteable_type = 'Commit' AND noteable_id IN (:commit_ids))", mr_id: id, commit_ids: commit_ids)
Note.where("(noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR (noteable_type = 'Commit' AND commit_id IN (:commit_ids))", mr_id: id, commit_ids: commit_ids)
end
# Returns the raw diff for this merge request
@ -220,4 +220,8 @@ class MergeRequest < ActiveRecord::Base
def to_patch
project.repo.git.format_patch({timeout: 30, raise: true, stdout: true}, "#{target_branch}..#{source_branch}")
end
def last_commit_short_sha
@last_commit_short_sha ||= last_commit.sha[0..10]
end
end

View File

@ -13,18 +13,26 @@
#
class Milestone < ActiveRecord::Base
attr_accessible :title, :description, :due_date, :closed
attr_accessible :title, :description, :due_date, :closed, :author_id_of_changes
attr_accessor :author_id_of_changes
belongs_to :project
has_many :issues
has_many :merge_requests
scope :active, where(closed: false)
scope :closed, where(closed: true)
validates :title, presence: true
validates :project, presence: true
validates :closed, inclusion: { in: [true, false] }
def self.active
where("due_date > ? OR due_date IS NULL", Date.today)
def expired?
if due_date
due_date < Date.today
else
false
end
end
def participants
@ -52,4 +60,20 @@ class Milestone < ActiveRecord::Base
def expires_at
"expires at #{due_date.stamp("Aug 21, 2011")}" if due_date
end
def can_be_closed?
open? && issues.opened.count.zero?
end
def is_empty?
total_items_count.zero?
end
def open?
!closed
end
def author_id
author_id_of_changes
end
end

View File

@ -48,23 +48,30 @@ class Namespace < ActiveRecord::Base
end
def ensure_dir_exist
namespace_dir_path = File.join(Gitlab.config.git_base_path, path)
namespace_dir_path = File.join(Gitlab.config.gitolite.repos_path, path)
system("mkdir -m 770 #{namespace_dir_path}") unless File.exists?(namespace_dir_path)
end
def move_dir
if path_changed?
old_path = File.join(Gitlab.config.git_base_path, path_was)
new_path = File.join(Gitlab.config.git_base_path, path)
old_path = File.join(Gitlab.config.gitolite.repos_path, path_was)
new_path = File.join(Gitlab.config.gitolite.repos_path, path)
if File.exists?(new_path)
raise "Already exists"
end
system("mv #{old_path} #{new_path}")
if system("mv #{old_path} #{new_path}")
send_update_instructions
end
end
end
def rm_dir
dir_path = File.join(Gitlab.config.git_base_path, path)
dir_path = File.join(Gitlab.config.gitolite.repos_path, path)
system("rm -rf #{dir_path}")
end
def send_update_instructions
projects.each(&:send_move_instructions)
end
end

View File

@ -19,7 +19,7 @@ require 'file_size_validator'
class Note < ActiveRecord::Base
attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id,
:attachment, :line_code
:attachment, :line_code, :commit_id
attr_accessor :notify
attr_accessor :notify_author
@ -35,10 +35,14 @@ class Note < ActiveRecord::Base
validates :line_code, format: { with: /\A\d+_\d+_\d+\Z/ }, allow_blank: true
validates :attachment, file_size: { maximum: 10.megabytes.to_i }
validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' }
validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' }
mount_uploader :attachment, AttachmentUploader
# Scopes
scope :common, ->{ where(noteable_id: nil) }
scope :for_commits, ->{ where(noteable_type: "Commit") }
scope :common, ->{ where(noteable_id: nil, commit_id: nil) }
scope :today, ->{ where("created_at >= :date", date: Date.today) }
scope :last_week, ->{ where("created_at >= :date", date: (Date.today - 7.days)) }
scope :since, ->(day) { where("created_at >= :date", date: (day)) }
@ -122,7 +126,7 @@ class Note < ActiveRecord::Base
# override to return commits, which are not active record
def noteable
if for_commit?
project.commit(noteable_id)
project.commit(commit_id)
else
super
end
@ -151,4 +155,12 @@ class Note < ActiveRecord::Base
def votable?
for_issue? || (for_merge_request? && !for_diff_line?)
end
def noteable_type_name
if noteable_type.present?
noteable_type.downcase
else
"wall"
end
end
end

View File

@ -25,6 +25,9 @@ class Project < ActiveRecord::Base
include PushObserver
include Authority
include Team
include NamespacedProject
class TransferError < StandardError; end
attr_accessible :name, :path, :description, :default_branch, :issues_enabled,
:wall_enabled, :merge_requests_enabled, :wiki_enabled, as: [:default, :admin]
@ -36,6 +39,10 @@ class Project < ActiveRecord::Base
# Relations
belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'"
belongs_to :namespace
# TODO: replace owner with creator.
# With namespaces a project owner will be a namespace owner
# so this field makes sense only for global projects
belongs_to :owner, class_name: "User"
has_many :users, through: :users_projects
has_many :events, dependent: :destroy
@ -97,7 +104,7 @@ class Project < ActiveRecord::Base
namespace_id = Namespace.find_by_path(id.first).id
where(namespace_id: namespace_id).find_by_path(id.last)
else
find_by_path(id)
where(path: id, namespace_id: nil).last
end
end
@ -172,7 +179,7 @@ class Project < ActiveRecord::Base
end
def repo_name
denied_paths = %w(gitolite-admin groups projects dashboard)
denied_paths = %w(gitolite-admin admin dashboard groups help profile projects search)
if denied_paths.include?(path)
errors.add(:path, "like #{path} is not allowed")
@ -188,7 +195,7 @@ class Project < ActiveRecord::Base
end
def web_url
[Gitlab.config.url, path].join("/")
[Gitlab.config.gitlab.url, path_with_namespace].join("/")
end
def common_notes
@ -196,15 +203,15 @@ class Project < ActiveRecord::Base
end
def build_commit_note(commit)
notes.new(noteable_id: commit.id, noteable_type: "Commit")
notes.new(commit_id: commit.id, noteable_type: "Commit")
end
def commit_notes(commit)
notes.where(noteable_id: commit.id, noteable_type: "Commit").where('line_code IS NULL OR line_code = ""')
notes.where(commit_id: commit.id, noteable_type: "Commit").where('line_code IS NULL OR line_code = ""')
end
def commit_line_notes(commit)
notes.where(noteable_id: commit.id, noteable_type: "Commit").where("line_code IS NOT NULL")
notes.where(commit_id: commit.id, noteable_type: "Commit").where("line_code IS NOT NULL")
end
def public?
@ -239,51 +246,11 @@ class Project < ActiveRecord::Base
gitlab_ci_service && gitlab_ci_service.active
end
def path_with_namespace
if namespace
namespace.path + '/' + path
else
path
end
end
# For compatibility with old code
def code
path
end
def transfer(new_namespace)
Project.transaction do
old_namespace = namespace
self.namespace = new_namespace
old_dir = old_namespace.try(:path) || ''
new_dir = new_namespace.try(:path) || ''
old_repo = if old_dir.present?
File.join(old_dir, self.path)
else
self.path
end
Gitlab::ProjectMover.new(self, old_dir, new_dir).execute
git_host.move_repository(old_repo, self)
save!
end
end
def name_with_namespace
@name_with_namespace ||= begin
if namespace
namespace.human_name + " / " + name
else
name
end
end
end
def items_for entity
case entity
when 'issue' then
@ -293,7 +260,9 @@ class Project < ActiveRecord::Base
end
end
def namespace_owner
namespace.try(:owner)
def send_move_instructions
self.users_projects.each do |member|
Notify.project_was_moved_email(member.id).deliver
end
end
end

View File

@ -22,7 +22,7 @@ class Snippet < ActiveRecord::Base
belongs_to :author, class_name: "User"
has_many :notes, as: :noteable, dependent: :destroy
delegate :name, :email, to: :author, prefix: true
delegate :name, :email, to: :author, prefix: true, allow_nil: true
validates :author, presence: true
validates :project, presence: true

View File

@ -56,12 +56,12 @@ class User < ActiveRecord::Base
has_many :issues, foreign_key: :author_id, dependent: :destroy
has_many :notes, foreign_key: :author_id, dependent: :destroy
has_many :merge_requests, foreign_key: :author_id, dependent: :destroy
has_many :my_own_projects, class_name: "Project", foreign_key: :owner_id
has_many :events, class_name: "Event", foreign_key: :author_id, dependent: :destroy
has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC"
has_many :assigned_issues, class_name: "Issue", foreign_key: :assignee_id, dependent: :destroy
has_many :assigned_merge_requests, class_name: "MergeRequest", foreign_key: :assignee_id, dependent: :destroy
validates :name, presence: true
validates :bio, length: { within: 0..255 }
validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider}
validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0}
@ -123,16 +123,4 @@ class User < ActiveRecord::Base
self.password = self.password_confirmation = Devise.friendly_token.first(8)
end
end
def authorized_groups
@authorized_groups ||= begin
groups = Group.where(id: self.projects.pluck(:namespace_id)).all
groups = groups + self.groups
groups.uniq
end
end
def authorized_projects
Project.authorized_for(self)
end
end

View File

@ -28,6 +28,7 @@ class UsersProject < ActiveRecord::Base
validates :user, presence: true
validates :user_id, uniqueness: { :scope => [:project_id], message: "already exists in project" }
validates :project_access, inclusion: { in: [GUEST, REPORTER, DEVELOPER, MASTER] }, presence: true
validates :project, presence: true
delegate :name, :email, to: :user, prefix: true

View File

@ -1,18 +1,27 @@
class ActivityObserver < ActiveRecord::Observer
observe :issue, :merge_request
observe :issue, :merge_request, :note, :milestone
def after_create(record)
Event.create(
project: record.project,
target_id: record.id,
target_type: record.class.name,
action: Event.determine_action(record),
author_id: record.author_id
)
event_author_id = record.author_id
# Skip status notes
if record.kind_of?(Note) && record.note.include?("_Status changed to ")
return true
end
if event_author_id
Event.create(
project: record.project,
target_id: record.id,
target_type: record.class.name,
action: Event.determine_action(record),
author_id: event_author_id
)
end
end
def after_save(record)
if record.changed.include?("closed")
if record.changed.include?("closed") && record.author_id_of_changes
Event.create(
project: record.project,
target_id: record.id,

View File

@ -16,7 +16,7 @@ class IssueObserver < ActiveRecord::Observer
if status
Note.create_status_change_note(issue, current_user, status)
[issue.author, issue.assignee].compact.each do |recipient|
Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user)
Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user.id).deliver
end
end
end

View File

@ -21,7 +21,7 @@ class NoteObserver < ActiveRecord::Observer
# Notifies the whole team except the author of note
def notify_team(note)
# Note: wall posts are not "attached" to anything, so fall back to "Wall"
noteable_type = note.noteable_type || "Wall"
noteable_type = note.noteable_type.presence || "Wall"
notify_method = "note_#{noteable_type.underscore}_email".to_sym
if Notify.respond_to? notify_method

View File

@ -3,7 +3,8 @@ class ProjectObserver < ActiveRecord::Observer
project.update_repository
end
def after_save(project)
def after_update(project)
project.send_move_instructions if project.namespace_id_changed?
end
def after_destroy(project)

View File

@ -47,7 +47,7 @@ module Account
end
def cared_merge_requests
MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id).opened
MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id)
end
def project_ids
@ -105,4 +105,20 @@ module Account
def namespace_id
namespace.try :id
end
def authorized_groups
@authorized_groups ||= begin
groups = Group.where(id: self.projects.pluck(:namespace_id)).all
groups = groups + self.groups
groups.uniq
end
end
def authorized_projects
Project.authorized_for(self)
end
def my_own_projects
Project.personal(self)
end
end

View File

@ -0,0 +1,59 @@
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

37
app/roles/note_event.rb Normal file
View File

@ -0,0 +1,37 @@
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

View File

@ -114,7 +114,7 @@ module PushObserver
id: commit.id,
message: commit.safe_message,
timestamp: commit.date.xmlschema,
url: "#{Gitlab.config.url}/#{path}/commits/#{commit.id}",
url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{commit.id}",
author: {
name: commit.author_name,
email: commit.author_email

View File

@ -45,8 +45,22 @@ module Repository
end
def has_post_receive_file?
hook_file = File.join(path_to_repo, 'hooks', 'post-receive')
File.exists?(hook_file)
!!hook_file
end
def valid_post_receive_file?
valid_hook_file == hook_file
end
def valid_hook_file
@valid_hook_file ||= File.read(Rails.root.join('lib', 'hooks', 'post-receive'))
end
def hook_file
@hook_file ||= begin
hook_path = File.join(path_to_repo, 'hooks', 'post-receive')
File.read(hook_path) if File.exists?(hook_path)
end
end
# Returns an Array of branch names
@ -83,7 +97,7 @@ module Repository
end
def path_to_repo
File.join(Gitlab.config.git_base_path, "#{path_with_namespace}.git")
File.join(Gitlab.config.gitolite.repos_path, "#{path_with_namespace}.git")
end
def namespace_dir
@ -185,7 +199,7 @@ module Repository
end
def http_url_to_repo
http_url = [Gitlab.config.url, "/", path_with_namespace, ".git"].join('')
http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
end
# Check if current branch name is marked as protected in the system

View File

@ -1,12 +0,0 @@
= 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"

View File

@ -1,3 +1,28 @@
%h3.page_title Edit Group
%br
= render 'form'
%h3.page_title Rename Group
%hr
= form_for [:admin, @group] do |f|
- if @group.errors.any?
.alert-message.block-message.error
%span= @group.errors.full_messages.first
.clearfix.group_name_holder
= f.label :name do
Group name is
.input
= f.text_field :name, placeholder: "Example Group", class: "xxlarge"
.clearfix.group_name_holder
= f.label :path do
%span.cred Group path is
.input
= f.text_field :path, placeholder: "example-group", class: "xxlarge danger"
%ul.cred
%li Changing group path can have unintended side effects.
%li Renaming group path will rename directory for all related projects
%li It will change web url for access group and group projects.
%li It will change the git path to repositories under this group.
.form-actions
= f.submit 'Rename group', class: "btn danger"
= link_to 'Cancel', admin_groups_path, class: "btn cancel-btn"

View File

@ -12,17 +12,24 @@
%table
%thead
%th Name
%th Path
%th Projects
%th Edit
%th.cred Danger Zone!
%tr
%th
Name
%i.icon-sort-down
%th Path
%th Projects
%th Owner
%th.cred Danger Zone!
- @groups.each do |group|
%tr
%td= link_to group.name, [:admin, group]
%td
%strong= link_to group.name, [:admin, group]
%td= group.path
%td= group.projects.count
%td= link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn small"
%td.bgred= link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn small danger"
%td
= link_to group.owner_name, admin_user_path(group.owner_id)
%td.bgred
= link_to 'Rename', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn small"
= link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn small danger"
= paginate @groups, theme: "admin"

View File

@ -1,8 +1,5 @@
%h3.page_title
Group: #{@group.name}
= link_to edit_admin_group_path(@group), class: "btn right" do
%i.icon-edit
Edit
%br
%table.zebra-striped
@ -16,36 +13,64 @@
Name:
%td
= @group.name
&nbsp;
= link_to edit_admin_group_path(@group), class: "btn btn-small right" do
%i.icon-edit
Rename
%tr
%td
%b
Path:
%td
%span.monospace= File.join(Gitlab.config.git_base_path, @group.path)
%span.monospace= File.join(Gitlab.config.gitolite.repos_path, @group.path)
%tr
%td
%b
Owner:
%td
= @group.owner_name
.ui-box
%h5
Projects
%small
(#{@group.projects.count})
%ul.unstyled
.right
= link_to "#", class: "btn btn-small change-owner-link" do
%i.icon-edit
Change owner
%tr.change-owner-holder.hide
%td.bgred
%b.cred
New Owner:
%td.bgred
= form_for [:admin, @group] do |f|
= f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'}
%div
= f.submit 'Change Owner', class: "btn danger"
= link_to "Cancel", "#", class: "btn change-owner-cancel-link"
%fieldset
%legend Projects (#{@group.projects.count})
%table
%thead
%tr
%th Project name
%th Path
%th Users
%th.cred Danger Zone!
- @group.projects.each do |project|
%li.wll
%strong
= link_to project.name, [:admin, project]
.right
= link_to 'Remove from group', remove_project_admin_group_path(@group, project_id: project.id), confirm: 'Are you sure?', method: :delete, class: "btn danger small"
.clearfix
%tr
%td
= link_to project.name_with_namespace, [:admin, project]
%td
%span.monospace= project.path_with_namespace + ".git"
%td= project.users.count
%td.bgred
= link_to 'Transfer project to global namespace', remove_project_admin_group_path(@group, project_id: project.id), confirm: 'Remove project from group and move to global namespace. Are you sure?', method: :delete, class: "btn danger small"
= form_tag project_update_admin_group_path(@group), class: "bulk_import", method: :put do
%fieldset
%legend Move projects to group
.alert
You can move only projects with existing repos
%br
Group projects will be moved in group directory and will not be accessible by old path
.clearfix
= label_tag :project_ids do
Projects
@ -53,3 +78,17 @@
= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
.form-actions
= submit_tag 'Add', class: "btn primary"
:javascript
$(function(){
var modal = $('.change-owner-holder');
$('.change-owner-link').bind("click", function(){
$(this).hide();
modal.show();
});
$('.change-owner-cancel-link').bind("click", function(){
modal.hide();
$('.change-owner-link').show();
})
})

View File

@ -3,6 +3,8 @@
= link_to "githost.log", "#githost", 'data-toggle' => 'tab'
%li
= link_to "application.log", "#application", 'data-toggle' => 'tab'
%li
= link_to "production.log", "#production", 'data-toggle' => 'tab'
%p.light To prevent perfomance issues admin logs output the last 2000 lines
.tab-content
@ -34,3 +36,17 @@
- Gitlab::AppLogger.read_latest.each do |line|
%li
%p= line
.tab-pane#production
.file_holder#README
.file_title
%i.icon-file
production.log
.right
= link_to '#', class: 'log-bottom' do
%i.icon-arrow-down
Scroll down
.file_content.logs
%ol
- Gitlab::Logger.read_latest_for('production.log').each do |line|
%li
%p= line

View File

@ -19,40 +19,47 @@
.input
= text_field_tag :ppath, @project.path_to_repo, class: "xlarge", disabled: true
- unless project.new_record?
- if project.repo_exists?
.clearfix
= f.label :namespace_id
.input= f.select :namespace_id, namespaces_options(@project.namespace_id), {}, {class: 'chosen'}
= f.label :default_branch, "Default Branch"
.input= f.select(:default_branch, project.heads.map(&:name), {}, style: "width:210px;")
- if project.repo_exists?
.clearfix
= f.label :default_branch, "Default Branch"
.input= f.select(:default_branch, project.heads.map(&:name), {}, style: "width:210px;")
%fieldset.adv_settings
%legend Features:
- unless project.new_record?
%fieldset.adv_settings
%legend Features:
.clearfix
= f.label :issues_enabled, "Issues"
.input= f.check_box :issues_enabled
.clearfix
= f.label :issues_enabled, "Issues"
.input= f.check_box :issues_enabled
.clearfix
= f.label :merge_requests_enabled, "Merge Requests"
.input= f.check_box :merge_requests_enabled
.clearfix
= f.label :merge_requests_enabled, "Merge Requests"
.input= f.check_box :merge_requests_enabled
.clearfix
= f.label :wall_enabled, "Wall"
.input= f.check_box :wall_enabled
.clearfix
= f.label :wall_enabled, "Wall"
.input= f.check_box :wall_enabled
.clearfix
= f.label :wiki_enabled, "Wiki"
.input= f.check_box :wiki_enabled
.clearfix
= f.label :wiki_enabled, "Wiki"
.input= f.check_box :wiki_enabled
%fieldset.features
%legend Transfer:
.control-group
= f.label :namespace_id do
%span Namespace
.controls
= f.select :namespace_id, namespaces_options(@project.namespace_id, :all), {}, {class: 'chosen'}
%br
%ul.prepend-top-10.cred
%li Be careful. Changing project namespace can have unintended side effects
%li You can transfer project only to namespaces you can manage
%li You will need to update your local repositories to point to the new location.
- unless project.new_record?
.actions
= f.submit 'Save Project', class: "btn save-btn"
= link_to 'Cancel', admin_projects_path, class: "btn cancel-btn"
.actions
= f.submit 'Save Project', class: "btn save-btn"
= link_to 'Cancel', admin_projects_path, class: "btn cancel-btn"

View File

@ -1,5 +1,5 @@
%h3.page_title
Projects
Projects (#{@projects.count})
= link_to 'New Project', new_project_path, class: "btn small right"
%br
= form_tag admin_projects_path, method: :get, class: 'form-inline' do
@ -9,12 +9,15 @@
%table
%thead
%th Name
%th Path
%th Team Members
%th Last Commit
%th Edit
%th.cred Danger Zone!
%tr
%th
Name
%i.icon-sort-down
%th Path
%th Team Members
%th Last Commit
%th Edit
%th.cred Danger Zone!
- @projects.each do |project|
%tr

View File

@ -4,14 +4,24 @@
%i.icon-edit
Edit
- if !@project.has_post_receive_file? && @project.has_commits?
%br
.alert.alert-error
%span
%strong Important!
Project has commits but missing post-receive file.
%br
If you exported project manually - copy post-receive hook to bare repository
- if @project.has_commits?
- if !@project.has_post_receive_file?
%br
.alert.alert-error
%span
%strong Project has commits but missing post-receive file.
%br
If you exported project manually - make a link of post-receive hook file from gitolite to project repository
- elsif !@project.valid_post_receive_file?
%br
.alert.alert-error
%span
%strong Project has invalid post-receive file.
%br
1. Make sure your gitolite instace has latest post-receive file.
%br
2. Make a link of post-receive hook file from gitolite to project repository
%br
%table.zebra-striped
@ -37,23 +47,63 @@
%tr
%td
%b
Path:
Owned by:
%td
%code= @project.path_to_repo
- if @project.chief
= link_to @project.chief.name, admin_user_path(@project.chief)
- else
(deleted)
%tr
%td
%b
Created by:
%td
= @project.owner_name || '(deleted)'
%tr
%td
%b
Created at:
%td
= @project.created_at.stamp("March 1, 1999")
%table.zebra-striped
%thead
%tr
%th Repository
%th
%tr
%td
%b
FS Path:
%td
%code= @project.path_to_repo
%tr
%td
%b
Smart HTTP:
%td
= link_to @project.http_url_to_repo
%tr
%td
%b
SSH:
%td
= link_to @project.ssh_url_to_repo
%tr
%td
%b
Last commit at:
%td
= last_commit(@project)
%tr
%td
%b
Post Receive File:
%td
= check_box_tag :post_receive_file, 1, @project.has_post_receive_file?, disabled: true
%br
%h3
%h5
Team
%small
(#{@project.users_projects.count})
@ -75,7 +125,7 @@
%td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn danger small"
%br
%h3 Add new team member
%h5 Add new team member
%br
= form_tag team_update_admin_project_path(@project), class: "bulk_import", method: :put do
%table.zebra-striped

View File

@ -1,5 +1,5 @@
%h3.page_title
Users
Users (#{@admin_users.count})
= link_to 'New User', new_admin_user_path, class: "btn small right"
%br
@ -21,13 +21,16 @@
%table
%thead
%th Admin
%th Name
%th Username
%th Email
%th Projects
%th Edit
%th.cred Danger Zone!
%tr
%th Admin
%th
Name
%i.icon-sort-down
%th Username
%th Email
%th Projects
%th Edit
%th.cred Danger Zone!
- @admin_users.each do |user|
%tr
@ -38,10 +41,13 @@
%td= user.users_projects.count
%td= link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn small"
%td.bgred
- if user.blocked
= link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn small success"
- if user == current_user
%span.cred It's you!
- else
= link_to 'Block', block_admin_user_path(user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small danger"
= link_to 'Destroy', [:admin, user], confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn small danger"
- if user.blocked
= link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn small success"
- else
= link_to 'Block', block_admin_user_path(user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small danger"
= link_to 'Destroy', [:admin, user], confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn small danger"
= paginate @admin_users, theme: "admin"

View File

@ -37,6 +37,12 @@
%b
Blocked:
%td= check_box_tag "blocked", 1, @admin_user.blocked, disabled: :disabled
%tr
%td
%b
Created at:
%td
= @admin_user.created_at.stamp("March 1, 1999")
%tr
%td
%b
@ -66,7 +72,7 @@
= @admin_user.twitter
%br
%h3 Add User to Projects
%h5 Add User to Projects
%br
= form_tag team_update_admin_user_path(@admin_user), class: "bulk_import", method: :put do
%table
@ -76,7 +82,7 @@
%th Project Access:
%tr
%td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
%td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
%td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select chosen span3"
%tr
@ -86,8 +92,22 @@
%strong= link_to "here", help_permissions_path, class: "vlink"
%br
- if @admin_user.groups.present?
%h5 Owner of groups:
%br
%table.zebra-striped
%thead
%tr
%th Name
- @admin_user.groups.each do |group|
%tr
%td= link_to group.name, admin_group_path(group)
- if @admin_user.projects.present?
%h3 Projects
%h5 Projects:
%br
%table.zebra-striped
@ -101,7 +121,7 @@
- @admin_user.users_projects.each do |tm|
- project = tm.project
%tr
%td= link_to project.name, admin_project_path(project)
%td= link_to project.name_with_namespace, admin_project_path(project)
%td= tm.project_access_human
%td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small"
%td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn small danger"

View File

@ -3,4 +3,4 @@
%h5.small
%i.icon-calendar
= day.stamp("28 Aug, 2010")
%ul.unstyled= render commits
%ul.well-list= render commits

View File

@ -1,23 +1,30 @@
%div
%p.slead
Fill input field with commit id like
%code.label_branch 4eedf23
or branch/tag name like
%code.label_branch master
and press compare button for commits list, code diff.
- unless params[:to]
%p.slead
Fill input field with commit id like
%code.label_branch 4eedf23
or branch/tag name like
%code.label_branch master
and press compare button for commits list, code diff.
%br
%br
= form_tag project_compare_index_path(@project), method: :post do
.clearfix
= text_field_tag :from, params[:from], placeholder: "master", class: "xlarge"
= "..."
= text_field_tag :to, params[:to], placeholder: "aa8b4ef", class: "xlarge"
.pull-left
- if params[:to] && params[:from]
= link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has_tooltip', title: 'Switch base of comparison'}
= text_field_tag :from, params[:from], placeholder: "master", class: "xlarge"
= "..."
= text_field_tag :to, params[:to], placeholder: "aa8b4ef", class: "xlarge"
.pull-left
&nbsp;
= submit_tag "Compare", class: "btn primary wide commits-compare-btn"
- if @refs_are_same
.alert
%span Refs are the same
.actions
= submit_tag "Compare", class: "btn primary wide commits-compare-btn"
:javascript
$(function() {

View File

@ -9,7 +9,7 @@
- if @commits.present?
%div.ui-box
%h5.small Commits (#{@commits.count})
%ul.unstyled= render @commits
%ul.well-list= render @commits
- unless @diffs.empty?
%h4 Diff

Some files were not shown because too many files have changed in this diff Show More