Commit b80dd3d2 authored by Sytse Sijbrandij's avatar Sytse Sijbrandij

Non-interactive AWS install by running a single script.

Merge branch 'master' into non-interactive-aws-install

Conflicts:
	doc/installation.md

Fix merge mess in installation.md
parent eae41ad1
#this code temporarily disables notes for all controllers
# Footnotes::Filter.notes = []
v 2.9.0
- fixed inline notes bugs
- refactored rspecs
- refactored gitolite backend
- added factory_girl
- restyled projects list on dashboard
- ssh keys validation to prevent gitolite crash
- send notifications if changed premission in project
- scss refactoring. gitlab_bootstrap/ dir
- fix git push http body bigger than 112k problem
- list of labels page under issues tab
- API for milestones
- restyled buttons
v 2.8.1
- ability to disable gravatars
- improved MR diff logic
- ssh key help page
v 2.8.0 v 2.8.0
- Gitlab Flavored Markdown - Gitlab Flavored Markdown
- Bulk issues update - Bulk issues update
- Issues API - Issues API
- Cucumber coverage increased - Cucumber coverage increased
- Post-receive files fixed
- UI improved
- Application cleanup
- more cucumber
- capybara-webkit + headless
v 2.7.0 v 2.7.0
- Issue Labels - Issue Labels
......
...@@ -54,7 +54,7 @@ gem "unicorn" ...@@ -54,7 +54,7 @@ gem "unicorn"
gem "acts-as-taggable-on", "2.3.1" gem "acts-as-taggable-on", "2.3.1"
# Decorators # Decorators
gem "drapper" gem "draper"
# Background jobs # Background jobs
gem "resque", "~> 1.20.0" gem "resque", "~> 1.20.0"
...@@ -92,7 +92,6 @@ end ...@@ -92,7 +92,6 @@ end
group :development do group :development do
gem "letter_opener" gem "letter_opener"
gem "rails-footnotes"
gem "annotate", :git => "https://github.com/ctran/annotate_models.git" gem "annotate", :git => "https://github.com/ctran/annotate_models.git"
gem 'rack-mini-profiler' gem 'rack-mini-profiler'
end end
...@@ -108,15 +107,18 @@ group :development, :test do ...@@ -108,15 +107,18 @@ group :development, :test do
gem "awesome_print" gem "awesome_print"
gem "database_cleaner" gem "database_cleaner"
gem "launchy" gem "launchy"
gem 'factory_girl_rails'
end end
group :test do group :test do
gem 'cucumber-rails', :require => false gem 'cucumber-rails', :require => false
gem 'minitest', ">= 2.10"
gem "turn", :require => false
gem "simplecov", :require => false gem "simplecov", :require => false
gem "shoulda-matchers" gem "shoulda-matchers"
gem 'email_spec' gem 'email_spec'
gem 'resque_spec' gem 'resque_spec'
gem "webmock" gem "webmock"
end end
group :production do
gem "gitlab_meta", '2.9'
end
...@@ -99,7 +99,6 @@ GEM ...@@ -99,7 +99,6 @@ GEM
acts-as-taggable-on (2.3.1) acts-as-taggable-on (2.3.1)
rails (~> 3.0) rails (~> 3.0)
addressable (2.2.8) addressable (2.2.8)
ansi (1.4.2)
arel (3.0.2) arel (3.0.2)
autotest (4.4.6) autotest (4.4.6)
ZenTest (>= 4.4.1) ZenTest (>= 4.4.1)
...@@ -156,7 +155,9 @@ GEM ...@@ -156,7 +155,9 @@ GEM
railties (~> 3.1) railties (~> 3.1)
warden (~> 1.2.1) warden (~> 1.2.1)
diff-lcs (1.1.3) diff-lcs (1.1.3)
drapper (0.8.4) draper (0.17.0)
actionpack (~> 3.2)
activesupport (~> 3.2)
email_spec (1.2.1) email_spec (1.2.1)
mail (~> 2.2) mail (~> 2.2)
rspec (~> 2.0) rspec (~> 2.0)
...@@ -165,6 +166,11 @@ GEM ...@@ -165,6 +166,11 @@ GEM
eventmachine (0.12.10) eventmachine (0.12.10)
execjs (1.4.0) execjs (1.4.0)
multi_json (~> 1.0) multi_json (~> 1.0)
factory_girl (4.0.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.0.0)
factory_girl (~> 4.0.0)
railties (>= 3.0.0)
ffaker (1.14.0) ffaker (1.14.0)
ffi (1.0.11) ffi (1.0.11)
foreman (0.47.0) foreman (0.47.0)
...@@ -172,6 +178,7 @@ GEM ...@@ -172,6 +178,7 @@ GEM
gherkin (2.11.0) gherkin (2.11.0)
json (>= 1.4.6) json (>= 1.4.6)
git (1.2.5) git (1.2.5)
gitlab_meta (2.9)
grape (0.2.1) grape (0.2.1)
hashie (~> 1.2) hashie (~> 1.2)
multi_json multi_json
...@@ -218,7 +225,6 @@ GEM ...@@ -218,7 +225,6 @@ GEM
treetop (~> 1.4.8) treetop (~> 1.4.8)
method_source (0.7.1) method_source (0.7.1)
mime-types (1.19) mime-types (1.19)
minitest (3.1.0)
modernizr (2.5.3) modernizr (2.5.3)
sprockets (~> 2.0) sprockets (~> 2.0)
multi_json (1.3.6) multi_json (1.3.6)
...@@ -258,8 +264,6 @@ GEM ...@@ -258,8 +264,6 @@ GEM
activesupport (= 3.2.8) activesupport (= 3.2.8)
bundler (~> 1.0) bundler (~> 1.0)
railties (= 3.2.8) railties (= 3.2.8)
rails-footnotes (3.7.8)
rails (>= 3.0.0)
railties (3.2.8) railties (3.2.8)
actionpack (= 3.2.8) actionpack (= 3.2.8)
activesupport (= 3.2.8) activesupport (= 3.2.8)
...@@ -349,8 +353,6 @@ GEM ...@@ -349,8 +353,6 @@ GEM
treetop (1.4.10) treetop (1.4.10)
polyglot polyglot
polyglot (>= 0.3.1) polyglot (>= 0.3.1)
turn (0.9.5)
ansi
tzinfo (0.3.33) tzinfo (0.3.33)
uglifier (1.0.3) uglifier (1.0.3)
execjs (>= 0.3.0) execjs (>= 0.3.0)
...@@ -389,11 +391,13 @@ DEPENDENCIES ...@@ -389,11 +391,13 @@ DEPENDENCIES
cucumber-rails cucumber-rails
database_cleaner database_cleaner
devise (~> 2.1.0) devise (~> 2.1.0)
drapper draper
email_spec email_spec
factory_girl_rails
ffaker ffaker
foreman foreman
git git
gitlab_meta (= 2.9)
gitolite! gitolite!
grack! grack!
grape (~> 0.2.1) grape (~> 0.2.1)
...@@ -407,7 +411,6 @@ DEPENDENCIES ...@@ -407,7 +411,6 @@ DEPENDENCIES
launchy launchy
letter_opener letter_opener
linguist (~> 1.0.0)! linguist (~> 1.0.0)!
minitest (>= 2.10)
modernizr (= 2.5.3) modernizr (= 2.5.3)
mysql2 mysql2
omniauth-ldap! omniauth-ldap!
...@@ -415,7 +418,6 @@ DEPENDENCIES ...@@ -415,7 +418,6 @@ DEPENDENCIES
pygments.rb! pygments.rb!
rack-mini-profiler rack-mini-profiler
rails (= 3.2.8) rails (= 3.2.8)
rails-footnotes
raphael-rails (= 1.5.2) raphael-rails (= 1.5.2)
redcarpet (~> 2.1.1) redcarpet (~> 2.1.1)
resque (~> 1.20.0) resque (~> 1.20.0)
...@@ -432,7 +434,6 @@ DEPENDENCIES ...@@ -432,7 +434,6 @@ DEPENDENCIES
stamp stamp
therubyracer therubyracer
thin thin
turn
uglifier (= 1.0.3) uglifier (= 1.0.3)
unicorn unicorn
webmock webmock
......
...@@ -39,5 +39,6 @@ Email ...@@ -39,5 +39,6 @@ Email
## Contribute ## Contribute
[Development Tips](https://github.com/gitlabhq/gitlabhq/blob/master/doc/development.md)
Want to help - send a pull request. Want to help - send a pull request.
We'll accept good pull requests. We'll accept good pull requests.
app/assets/images/file_dir.png

517 Bytes | W: | H:

app/assets/images/file_dir.png

1.61 KB | W: | H:

app/assets/images/file_dir.png
app/assets/images/file_dir.png
app/assets/images/file_dir.png
app/assets/images/file_dir.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -72,7 +72,7 @@ $(document).ready(function(){ ...@@ -72,7 +72,7 @@ $(document).ready(function(){
* Note markdown preview * Note markdown preview
* *
*/ */
$('#preview-link').on('click', function(e) { $(document).on('click', '#preview-link', function(e) {
$('#preview-note').text('Loading...'); $('#preview-note').text('Loading...');
var previewLinkText = ($(this).text() == 'Preview' ? 'Edit' : 'Preview'); var previewLinkText = ($(this).text() == 'Preview' ? 'Edit' : 'Preview');
...@@ -128,3 +128,23 @@ function showDiff(link) { ...@@ -128,3 +128,23 @@ function showDiff(link) {
function ajaxGet(url) { function ajaxGet(url) {
$.ajax({type: "GET", url: url, dataType: "script"}); $.ajax({type: "GET", url: url, dataType: "script"});
} }
/**
* Disable button if text field is empty
*/
function disableButtonIfEmtpyField(field_selector, button_selector) {
field = $(field_selector);
if(field.val() == "") {
field.closest("form").find(button_selector).attr("disabled", "disabled").addClass("disabled");
}
field.on('keyup', function(){
var field = $(this);
var closest_submit = field.closest("form").find(button_selector);
if(field.val() == "") {
closest_submit.attr("disabled", "disabled").addClass("disabled");
} else {
closest_submit.removeAttr("disabled").removeClass("disabled");
}
})
}
...@@ -5,6 +5,7 @@ function switchToNewIssue(form){ ...@@ -5,6 +5,7 @@ function switchToNewIssue(form){
$('select#issue_milestone_id').chosen(); $('select#issue_milestone_id').chosen();
$("#new_issue_dialog").show("fade", { direction: "right" }, 150); $("#new_issue_dialog").show("fade", { direction: "right" }, 150);
$('.top-tabs .add_new').hide(); $('.top-tabs .add_new').hide();
disableButtonIfEmtpyField("#issue_title", ".save-btn");
}); });
} }
...@@ -15,6 +16,7 @@ function switchToEditIssue(form){ ...@@ -15,6 +16,7 @@ function switchToEditIssue(form){
$('select#issue_milestone_id').chosen(); $('select#issue_milestone_id').chosen();
$("#edit_issue_dialog").show("fade", { direction: "right" }, 150); $("#edit_issue_dialog").show("fade", { direction: "right" }, 150);
$('.add_new').hide(); $('.add_new').hide();
disableButtonIfEmtpyField("#issue_title", ".save-btn");
}); });
} }
......
var NoteList = { var NoteList = {
notes_path: null, notes_path: null,
target_params: null, target_params: null,
target_id: 0, target_id: 0,
target_type: null, target_type: null,
first_id: 0, first_id: 0,
last_id: 0, last_id: 0,
disable:false, disable:false,
init: init:
function(tid, tt, path) { function(tid, tt, path) {
this.notes_path = path + ".js"; this.notes_path = path + ".js";
this.target_id = tid; this.target_id = tid;
this.target_type = tt; this.target_type = tt;
this.target_params = "&target_type=" + this.target_type + "&target_id=" + this.target_id; this.target_params = "&target_type=" + this.target_type + "&target_id=" + this.target_id;
// get notes // get notes
this.getContent(); this.getContent();
// get new notes every n seconds // get new notes every n seconds
this.initRefresh(); this.initRefresh();
$('.delete-note').live('ajax:success', function() { $('.delete-note').live('ajax:success', function() {
$(this).closest('li').fadeOut(); }); $(this).closest('li').fadeOut(); });
$("#new_note").live("ajax:before", function(){ $(".note-form-holder").live("ajax:before", function(){
$(".submit_note").attr("disabled", "disabled"); $(".submit_note").attr("disabled", "disabled");
}) })
$("#new_note").live("ajax:complete", function(){ $(".note-form-holder").live("ajax:complete", function(){
$(".submit_note").removeAttr("disabled"); $(".submit_note").removeAttr("disabled");
}) })
$("#note_note").live("focus", function(){ disableButtonIfEmtpyField(".note-text", ".submit_note");
$(this).css("height", "80px");
$('.note_advanced_opts').show(); $(".note-text").live("focus", function(){
}); $(this).css("height", "80px");
$('.note_advanced_opts').show();
$("#note_attachment").change(function(e){ });
$("#note_attachment").change(function(e){
var val = $('.input-file').val(); var val = $('.input-file').val();
var filename = val.replace(/^.*[\\\/]/, ''); var filename = val.replace(/^.*[\\\/]/, '');
$(".file_name").text(filename); $(".file_name").text(filename);
}); });
}, },
/** /**
* Load new notes to fresh list called 'new_notes_list': * Load new notes to fresh list called 'new_notes_list':
* - Replace 'new_notes_list' with new list every n seconds * - Replace 'new_notes_list' with new list every n seconds
* - Append new notes to this list after submit * - Append new notes to this list after submit
*/ */
initRefresh: initRefresh:
function() { function() {
// init timer // init timer
var intNew = setInterval("NoteList.getNew()", 10000); var intNew = setInterval("NoteList.getNew()", 10000);
}, },
replace: replace:
function(html) { function(html) {
$("#new_notes_list").html(html); $("#new_notes_list").html(html);
}, },
prepend: prepend:
function(id, html) { function(id, html) {
if(id != this.last_id) { if(id != this.last_id) {
$("#new_notes_list").prepend(html); $("#new_notes_list").prepend(html);
} }
}, },
getNew: getNew:
function() { function() {
// refersh notes list // refersh notes list
$.ajax({ $.ajax({
type: "GET", type: "GET",
url: this.notes_path, url: this.notes_path,
data: "last_id=" + this.last_id + this.target_params, data: "last_id=" + this.last_id + this.target_params,
dataType: "script"}); dataType: "script"});
}, },
refresh: refresh:
function() { function() {
// refersh notes list // refersh notes list
$.ajax({ $.ajax({
type: "GET", type: "GET",
url: this.notes_path, url: this.notes_path,
data: "first_id=" + this.first_id + "&last_id=" + this.last_id + this.target_params, data: "first_id=" + this.first_id + "&last_id=" + this.last_id + this.target_params,
dataType: "script"}); dataType: "script"});
}, },
/** /**
* Init load of notes: * Init load of notes:
* 1. Get content with ajax call * 1. Get content with ajax call
* 2. Set content of notes list with loaded one * 2. Set content of notes list with loaded one
*/ */
getContent: getContent:
function() { function() {
$.ajax({ $.ajax({
type: "GET", type: "GET",
url: this.notes_path, url: this.notes_path,
data: "?" + this.target_params, data: "?" + this.target_params,
complete: function(){ $('.status').removeClass("loading")}, complete: function(){ $('.status').removeClass("loading")},
beforeSend: function() { $('.status').addClass("loading") }, beforeSend: function() { $('.status').addClass("loading") },
dataType: "script"}); dataType: "script"});
}, },
setContent: setContent:
function(fid, lid, html) { function(fid, lid, html) {
this.last_id = lid; this.last_id = lid;
this.first_id = fid; this.first_id = fid;
$("#notes-list").html(html); $("#notes-list").html(html);
// Init infinite scrolling // Init infinite scrolling
this.initLoadMore(); this.initLoadMore();
}, },
/** /**
* Paging for old notes when scroll to bottom: * Paging for old notes when scroll to bottom:
* 1. Init scroll events with 'initLoadMore' * 1. Init scroll events with 'initLoadMore'
* 2. Load onlder notes with 'getOld' method * 2. Load onlder notes with 'getOld' method
* 3. append old notes to bottom of list with 'append' * 3. append old notes to bottom of list with 'append'
* *
*/ */
getOld:
function() {
getOld: $('.loading').show();
function() { $.ajax({
$('.loading').show(); type: "GET",
$.ajax({ url: this.notes_path,
type: "GET", data: "first_id=" + this.first_id + this.target_params,
url: this.notes_path, complete: function(){ $('.status').removeClass("loading")},
data: "first_id=" + this.first_id + this.target_params, beforeSend: function() { $('.status').addClass("loading") },
complete: function(){ $('.status').removeClass("loading")}, dataType: "script"});
beforeSend: function() { $('.status').addClass("loading") }, },
dataType: "script"});
}, append:
function(id, html) {
append: if(this.first_id == id) {
function(id, html) { this.disable = true;
if(this.first_id == id) { } else {
this.disable = true; this.first_id = id;
} else { $("#notes-list").append(html);
this.first_id = id; }
$("#notes-list").append(html); },
}
},
initLoadMore: initLoadMore:
function() { function() {
$(document).endlessScroll({ $(document).endlessScroll({
bottomPixels: 400, bottomPixels: 400,
fireDelay: 1000, fireDelay: 1000,
fireOnce:true, fireOnce:true,
ceaseFire: function() { ceaseFire: function() {
...@@ -164,6 +163,20 @@ initLoadMore: ...@@ -164,6 +163,20 @@ initLoadMore:
callback: function(i) { callback: function(i) {
NoteList.getOld(); NoteList.getOld();
} }
}); });
} }
};
var PerLineNotes = {
init:
function() {
$(".line_note_link, .line_note_reply_link").live("click", function(e) {
var form = $(".per_line_form");
$(this).closest("tr").after(form);
form.find("#note_line_code").val($(this).attr("line_code"));
form.show();
return false;
});
disableButtonIfEmtpyField(".line-note-text", ".submit_inline_note");
}
} }
...@@ -7,8 +7,10 @@ function Projects() { ...@@ -7,8 +7,10 @@ function Projects() {
$('.new_project, .edit_project').live('ajax:before', function() { $('.new_project, .edit_project').live('ajax:before', function() {
$('.project_new_holder, .project_edit_holder').hide(); $('.project_new_holder, .project_edit_holder').hide();
$('.ajax_loader').show(); $('.save-project-loader').show();
}); });
$('form #project_default_branch').chosen(); $('form #project_default_branch').chosen();
disableButtonIfEmtpyField("#project_name", ".project-submit")
} }
.diff_file_header a,
.file_stats a {
color:$style_color;
}
/** LAYOUT **/ /** LAYOUT **/
body {
margin-bottom:20px;
}
.container { .container {
padding-top:0; padding-top:0;
z-index:5; z-index:5;
...@@ -40,30 +38,6 @@ ...@@ -40,30 +38,6 @@
color: $link_color; color: $link_color;
} }
.widget {
@include shade;
padding:20px;
margin-bottom:20px;
border: 1px solid #DDD;
border-radius: 5px;
background:#fafafa;
.link_holder {
background:#eee;
position:relative;
left:-20px;
top:20px;
padding:10px 20px;
width:100%;
border-top:1px solid #ccc;
a {
font-size:14px;
color:#666;
}
}
}
.help li { color:#111 } .help li { color:#111 }
.back_link { .back_link {
...@@ -88,16 +62,6 @@ ...@@ -88,16 +62,6 @@
padding-left:20px; padding-left:20px;
} }
.number {
border-radius: 4px;
text-shadow: none;
background: rgba(0,0,0,.12);
text-align: center;
padding: 2px 4px;
line-height:18px;
margin-left:2px;
}
table a code { table a code {
position: relative; position: relative;
top: -2px; top: -2px;
...@@ -129,26 +93,18 @@ table a code { ...@@ -129,26 +93,18 @@ table a code {
border-bottom:1px solid #ccc; border-bottom:1px solid #ccc;
h4 { h4 {
color:#444; color:#666;
font-size:22px; font-size:18px;
line-height:38px;
padding-top:5px; padding-top:5px;
margin:2px; margin:2px;
font-weight:normal;
} }
} }
.git_url_wrapper { .git_url_wrapper {
margin-right:50px margin-right:50px
} }
.file_stats {
span {
img {
width:14px;
float:left;
margin-right:6px;
padding:2px 0;
}
}
}
.handle:hover { .handle:hover {
cursor:move; cursor:move;
...@@ -172,10 +128,6 @@ span.update-author { ...@@ -172,10 +128,6 @@ span.update-author {
display:block; display:block;
} }
/** END UPDATE ITEM **/ /** END UPDATE ITEM **/
.ajax-tab-loading {
padding:40px;
display:none;
}
.dashboard-loader { .dashboard-loader {
float:left; float:left;
margin:10px; margin:10px;
...@@ -186,15 +138,110 @@ span.update-author { ...@@ -186,15 +138,110 @@ span.update-author {
font-weight:bold; font-weight:bold;
} }
a.project-update.titled { .neib {
position:relative; margin-right:10px;
padding-left:35% !important; }
.title-block {
padding:10px; .label {
width:35%; background-color: #474D57;
position:absolute;
left:0; &.label-issue {
top:0; background-color: #eee;
border: 1px solid #ccc;
padding:4px 6px;
color:#444;
text-shadow:0 0 1px #fff;
&.grouped {
float: left;
margin-right: 6px;
padding: 6px;
}
}
}
.event_label {
@extend .label;
background-color: #999;
&.pushed {
background-color: #4A97BD;
}
&.opened {
background-color: #469847;
}
&.closed {
background-color: #B94A48;
}
&.merged {
background-color: #2A2;
}
}
form {
@extend .form-horizontal;
.actions {
@extend .form-actions;
}
.clearfix {
@extend .control-group;
}
.input {
@extend .controls;
}
label {
@extend .control-label;
}
.xlarge {
@extend .input-xlarge;
}
.xxlarge {
@extend .input-xxlarge;
}
}
.field_with_errors {
display:inline;
}
ul.breadcrumb {
background:white;
border:none;
li {
display: inline;
text-shadow: 0 1px 0 white
}
a {
color:#474D57;
font-weight:bold;
font-size:14px;
}
.arrow {
background: url("images.png") no-repeat -85px -77px;
width: 19px;
height: 16px;
float: left;
position: relative;
left: -10px;
padding:0;
margin:0;
}
}
input[type=text] {
&.large_text {
padding:6px;
font-size:16px;
} }
} }
...@@ -270,40 +317,6 @@ p.time { ...@@ -270,40 +317,6 @@ p.time {
} }
/**
* Dashboard page
*
*/
.dashboard_category {
margin-bottom:30px;
h3 a {
color:#474D57;
&:hover {
text-decoration:underline;
}
}
.dashboard_block {
.dash_project_item {
margin-bottom:10px;
border:none;
padding:0px 5px;
.project_link {
color:#888;
&:hover {
color:#111;
.ico.project {
background-position:-209px -21px;
}
}
}
h4 {
color:#666;
}
}
}
}
.styled_image { .styled_image {
border:2px solid #ddd; border:2px solid #ddd;
} }
...@@ -393,39 +406,6 @@ p.time { ...@@ -393,39 +406,6 @@ p.time {
} }
} }
.btn {
&.very_small {
font-size:11px;
padding:2px 6px;
margin:2px;
}
&.grouped {
margin-right:7px;
float:left;
}
&.padded {
margin-right:3px;
padding:4px 10px 4px;
}
}
.prettyprint {
background-color: #fefbf3;
padding: 9px;
border: 1px solid rgba(0,0,0,.2);
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.1);
-moz-box-shadow: 0 1px 2px rgba(0,0,0,.1);
box-shadow: 0 1px 2px rgba(0,0,0,.1);
}
.hint {
font-style: italic;
color: #999;
}
.upvotes { .upvotes {
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
...@@ -549,14 +529,6 @@ li.note { ...@@ -549,14 +529,6 @@ li.note {
} }
/**
* Milestones list
*
*/
.milestone {
@extend .wll;
}
/** /**
* Admin area * Admin area
...@@ -603,11 +575,10 @@ li.note { ...@@ -603,11 +575,10 @@ li.note {
* *
*/ */
.event_lp { .event_lp {
@extend .alert-info; @extend .ui-box;
color:#777;
margin-bottom:20px; margin-bottom:20px;
padding:8px; padding:8px;
border-style: solid;
border-width: 1px;
@include border-radius(4px); @include border-radius(4px);
min-height:22px; min-height:22px;
...@@ -621,88 +592,19 @@ li.note { ...@@ -621,88 +592,19 @@ li.note {
cursor:pointer; cursor:pointer;
} }
/**
* Issues, MRs legend
*
*/
.list_legend {
float:left;
margin-right:20px;
.icon {
width:12px;
height:12px;
float:left;
margin-right:5px;
margin-top: 2px;
@include border-radius(4px);
&.today{
background: #ADA;
border:1px solid #8B8;
}
&.closed {
background: #DDD;
border:1px solid #BBB;
}
&.yours {
background: #AAD;
border:1px solid #88B;
}
&.merged {
background: #DAD;
border:1px solid #B8B;
}
}
.text {
padding-bottom: 10px;
float:left;
}
}
.merge_request, .merge_request,
.issue { .issue {
.list_legend {
margin-right: 5px;
margin-top: 14px;
.icon {
width:8px;
height:8px;
float:left;
margin-right:5px;
@include border-radius(4px);
border:1px solid #ddd;
}
}
&.today{ &.today{
background: #EFE; background: #EFE;
border-color:#CEC; border-color:#CEC;
.icon {
background: #ADA;
border:1px solid #8B8;
}
} }
&.closed { &.closed {
background: #F5f5f5; background: #F5f5f5;
border-color:#E5E5E5; border-color:#E5E5E5;
.icon {
background: #DDD;
border:1px solid #BBB;
}
}
&.yours {
.icon {
background: #AAD;
border:1px solid #88B;
}
} }
&.merged { &.merged {
background: #F5f5f5; background: #F5f5f5;
border-color:#E5E5E5; border-color:#E5E5E5;
.icon {
background: #DAD;
border:1px solid #B8B;
}
} }
} }
...@@ -735,3 +637,11 @@ li.note { ...@@ -735,3 +637,11 @@ li.note {
font-size: 12px; font-size: 12px;
} }
} }
.error_message {
@extend .cred;
border-bottom: 1px solid #D21;
padding-bottom:20px;
text-align:center;
margin-bottom:10px;
}
body {
margin-bottom:20px;
}
a {
outline: none;
color: $link_color;
&:hover {
text-decoration:none;
color: $blue_link;
}
&.btn {
color: $style_color;
}
&.dark {
color: $style_color;
}
&.lined {
text-decoration:underline;
&:hover { text-decoration:underline; }
}
&.gray {
color:gray;
}
&.supp_diff_link {
text-align:center;
padding:20px 0;
background:#f1f1f1;
width:100%;
float:left;
}
&.neib {
margin-right:15px;
}
}
.neib {
margin-right:10px;
}
.alert-message {
@extend .alert;
&.success {
@extend .alert-success;
}
&.error {
@extend .alert-error;
}
}
.alert {
&.alert-well {
background:#ddd;
border:1px solid #ccc;
background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #ddd), to(#dfdfdf));
background-image: -webkit-linear-gradient(#ddd 6.6%, #dfdfdf);
background-image: -moz-linear-gradient(#ddd 6.6%, #dfdfdf);
background-image: -o-linear-gradient(#ddd 6.6%, #dfdfdf);
color:#111;
}
}
h3, h4, h5, h6 {
line-height: 36px;
}
h5 {
font-size:14px;
}
table {
width:100%;
th {
padding-top: 9px;
font-weight: bold;
vertical-align: middle;
}
th, td {
padding: 10px 10px 9px;
line-height: 18px;
text-align: left;
}
&.bordered-table {
border: 1px solid #DDD;
border-collapse: separate;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
&.zebra-striped {
@extend .table-striped;
}
}
.btn {
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f1f1f1), color-stop(25%, #f1f1f1), to(#e6e6e6));
background-image: -webkit-linear-gradient(#f1f1f1, #f1f1f1 25%, #e6e6e6);
background-image: -moz-linear-gradient(top, #f1f1f1, #f1f1f1 25%, #e6e6e6);
background-image: -ms-linear-gradient(#f1f1f1, #f1f1f1 25%, #e6e6e6);
background-image: -o-linear-gradient(#f1f1f1, #f1f1f1 25%, #e6e6e6);
background-image: linear-gradient(#f1f1f1, #f1f1f1 25%, #e6e6e6);
&:hover {
}
&.btn-primary {
background:$link_color;
border-color: #2A79A3;
&:hover {
background:$blue_link;
}
}
&.primary {
@extend .btn-primary;
}
&.success {
color: #fff;
text-shadow: 0 0 1px #111;
background: #5bb75b;;
font-weight: bold;
&:hover {
background-color: #51a351;
color: #fff;
}
}
&.danger,
&.btn-danger {
color:#fff;
background: #DA4E49;
border-color: #BD362F;
&:hover {
color:#fff;
background: #EE4E49;
}
}
&.danger {
@extend .btn-danger;
}
&.small {
@extend .btn-small;
}
&.active {
border-color:#aaa;
background-color:#ccc;
}
}
a:focus {
outline: none;
}
.nav-pills a:hover {
background-color:#888;
}
.nav-pills .active a {
background-color: $style_color;
}
.label {
background-color: #474D57;
&.label-important {
background-color: #B94A48;
}
&.label-issue {
background-color: #eee;
border: 1px solid #ccc;
padding:4px 6px;
color:#444;
text-shadow:0 0 1px #fff;
&.grouped {
float: left;
margin-right: 6px;
padding: 6px;
}
}
}
.nav-tabs > li > a, .nav-pills > li > a {
color:$style_color;
}
.nav-tabs > .active > a {
font-weight:bold;
}
/** COLORS **/
.cgray { color:gray; }
.cred { color:#D12F19; }
.cgreen { color:#44aa22; }
.cblack { color:#111; }
.cdark { color:#444 }
.cwhite { color:#fff !important }
.bgred { background: #F2DEDE !important}
/** COMMON STYLES **/
.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;
}
.prepend-top-20 {
margin-top:20px;
}
.padded {
padding:20px;
}
.ipadded {
padding:20px !important;
}
.lborder {
border-left:1px solid #eee;
}
.borders {
border: 1px solid #ccc;
@include shade;
}
.no-borders {
border:none;
}
table.no-borders {
border:none;
tr, td { border:none }
}
.no-padding {
padding:0 !important;
}
.underlined {
border-bottom: 1px solid $border_color;
}
.vlink {
color: $link_color !important;
}
.pretty_label {
@include round-borders-all(4px);
padding:2px 4px;
background-image: -webkit-gradient(linear, 0 0, 0 26, color-stop(0.076, #fefefe), to(#F6F7F8));
background-image: -webkit-linear-gradient(#fefefe 7.6%, #F6F7F8);
background-image: -moz-linear-gradient(#fefefe 7.6%, #F6F7F8);
background-image: -o-linear-gradient(#fefefe 7.6%, #F6F7F8);
color: #777;
border: 1px solid #DEDFE1;
&.branch {
border:none;
font-size:13px;
background: #474D57;
color:#fff;
font-weight:bold;
font-family: monospace;
}
}
.event_label {
@extend .label;
background-color: #999;
&.pushed {
background-color: #3A87AD;
}
&.opened {
background-color: #468847;
}
&.closed {
background-color: #B94A48;
}
&.merged {
background-color: #2A2;
}
}
img.avatar {
float:left;
margin-right:15px;
width:40px;
border:2px solid #ddd;
&.s16 {
width:16px;
}
&.s24 {
width:24px;
}
&.s32 {
width:32px;
}
}
img.lil_av {
padding-left: 4px;
padding-right:3px;
}
form {
@extend .form-horizontal;
.actions {
@extend .form-actions;
}
.clearfix {
@extend .control-group;
}
.input {
@extend .controls;
}
label {
@extend .control-label;
}
.xlarge {
@extend .input-xlarge;
}
.xxlarge {
@extend .input-xxlarge;
}
}
/**
* List li block element #1
*
*/
.wll {
background-color: #FFF;
padding: 10px 5px;
min-height: 20px;
border-bottom: 1px solid #eee;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
&.smoke {
background-color:#f5f5f5;
}
&:hover {
background:$hover;
}
&:last-child { border:none }
p { padding-top:5px; margin:0; color:$style_color;}
.author { color: #999; }
p {
color:#222;
margin-bottom: 0;
img {
position:relative;
top:3px;
}
}
}
/**
* Block element #2
*
*/
.entry {
position: relative;
padding: 7px 15px;
margin-bottom: 18px;
color: #404040;
filter:none;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
-moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
background:#F1F1F1;
border: 1px solid #ccc;
p {
color:$style_color;
margin-bottom: 0;
img {
position:relative;
top:3px;
}
}
}
/**
* Big UI Block for show page content
*
*/
.ui-box {
background:#F9F9F9;
margin-bottom: 25px;
@include round-borders-all(4px);
border-color: #CCC;
@include solid_shade;
ul {
margin:0;
}
h5, .title {
padding: 0 10px;
@include round-borders-top(4px);
border-bottom: 1px solid #bbb;
background:#eee;
background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
&.small {
line-height: 28px;
font-size: 14px;
line-height:28px;
text-shadow: 0 1px 1px white;
}
form {
padding:9px 0;
margin:0px;
}
.nav-pills {
li {
padding:3px 0;
&.active a { background-color:$style_color; }
a {
border-radius:7px;
}
}
}
}
.bottom {
background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
@include round-borders-bottom(4px);
border-bottom:none;
border-top: 1px solid #bbb;
}
&.padded {
h5, .title {
margin: -20px;
margin-bottom: 0;
padding: 5px 20px;
}
.middle_title {
background:#f5f5f5;
margin:20px -20px;
padding: 0 20px;
border-top:1px solid #eee;
border-bottom:1px solid #eee;
font-size:14px;
color:#777;
}
}
.row_title {
font-weight:bold;
color:#444;
&:hover {
color:#444;
text-decoration:underline;
}
}
li, .wll {
padding:10px;
&:first-child {
@include round-borders-top(4px);
border-top:none;
}
&:last-child {
@include round-borders-bottom(4px);
border:none;
}
}
}
table.admin-table {
@extend .table-bordered;
@extend .zebra-striped;
@include solid_shade;
th {
border-color: #CCC;
border-bottom: 1px solid #bbb;
background:#eee;
background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
}
}
.field_with_errors {
display:inline;
}
ul.breadcrumb {
background:white;
border:none;
li {
display: inline;
text-shadow: 0 1px 0 white
}
a {
color:#474D57;
font-weight:bold;
font-size:14px;
}
.arrow {
background: url("images.png") no-repeat -85px -77px;
width: 19px;
height: 16px;
float: left;
position: relative;
left: -10px;
padding:0;
margin:0;
}
}
.nothing_here_message {
text-align:center;
padding:20px;
color:#777;
}
/**
* UI box element
* contains top, middle, bottom blocks
*
*/
.main_box {
@extend .borders;
@extend .prepend-top-20;
@extend .append-bottom-20;
border-width:1px;
@include solid_shade;
img { max-width: 100%; }
pre {
code {
background: none !important;
}
}
.top_box_content,
.middle_box_content,
.bottom_box_content {
padding:15px;
pre {
background: none !important;
margin:0;
border:none;
padding:0;
}
}
.middle_box_content {
border-radius:0;
border:none;
font-size:12px;
background-color:#f5f5f5;
border:none;
border-top:1px solid #eee;
}
.bottom_box_content {
border-top:1px solid #eee;
}
}
input[type=text] {
&.large_text {
padding:6px;
font-size:16px;
}
}
p {
&.slead {
color:#456;
font-size:16px;
margin-bottom: 12px;
font-weight: 200;
line-height: 24px;
}
}
h3.page_title {
color:#456;
font-size:20px;
font-weight: normal;
line-height: 28px;
}
/**
* File content holder
*
*/
.file_holder {
border:1px solid #CCC;
margin-bottom:1em;
@include solid_shade;
.file_title {
border-bottom: 1px solid #bbb;
background:#eee;
background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
margin: 0;
font-weight: normal;
font-weight: bold;
text-align: left;
color: #666;
padding: 9px 10px;
height:18px;
.options {
float:right;
margin-top: -5px;
}
.file_name {
color:$style_color;
font-size:14px;
text-shadow: 0 1px 1px #fff;
small {
color:#999;
font-size:13px;
}
}
}
.file_content {
background:#fff;
font-size: 11px;
&.wiki {
font-size: 13px;
code {
padding:0 4px;
}
padding:20px;
h1, h2 {
line-height: 46px;
}
h3, h4 {
line-height: 40px;
}
}
&.image_file {
background:#eee;
text-align:center;
img {
padding:100px;
max-width:300px;
}
}
&.blob_file {
}
/**
* Blame file
*/
&.blame {
tr {
border-bottom: 1px solid #eee;
}
td {
padding:5px;
}
.author,
.blame_commit {
background:#f5f5f5;
vertical-align:top;
}
.lines {
pre {
padding:0;
margin:0;
background:none;
border:none;
}
}
}
&.logs {
background:#eee;
max-height: 700px;
overflow-y: auto;
ol {
margin-left:40px;
padding: 10px 0;
border-left: 1px solid #CCC;
margin-bottom:0;
background: white;
li {
color:#888;
p {
margin:0;
color:#333;
line-height:24px;
padding-left: 10px;
}
&:hover {
background:$hover;
}
}
}
}
/**
* Code file
*/
&.code {
padding:0;
td.code {
width: 100%;
.highlight {
margin-left: 55px;
overflow:auto;
overflow-y:hidden;
}
}
.highlight pre {
white-space: pre;
word-wrap:normal;
}
table.highlighttable {
border: none;
}
body.project-page table.highlighttable td { border: none }
table.highlighttable tr:hover { background:none;}
table.highlighttable pre{
line-height:16px !important;
font-size:12px !important;
}
table.highlighttable .linenodiv pre {
text-align: right;
padding-right: 4px;
color:#666;
}
}
}
}
/**
* ===================================
* Contain 3 main UI block elements:
* .main_box - for show pages
* .ui-box - for simple block & widgets
* ===================================
*/
/**
* UI box element
* contains top, middle, bottom blocks
*
*/
.main_box {
@extend .borders;
@extend .prepend-top-20;
@extend .append-bottom-20;
border-width:1px;
@include solid_shade;
img { max-width: 100%; }
pre {
code {
background: none !important;
}
}
.top_box_content,
.middle_box_content,
.bottom_box_content {
padding:15px;
pre {
background: none !important;
margin:0;
border:none;
padding:0;
}
}
.middle_box_content {
border-radius:0;
border:none;
font-size:12px;
background-color:#f5f5f5;
border:none;
border-top:1px solid #eee;
}
.bottom_box_content {
border-top:1px solid #eee;
}
}
/**
* Big UI Block for show page content
*
*/
.ui-box {
background:#F9F9F9;
margin-bottom: 25px;
@include round-borders-all(4px);
border-color: #CCC;
@include solid_shade;
ul {
margin:0;
}
h5, .title {
padding: 0 10px;
@include round-borders-top(4px);
@include bg-gray-gradient;
border-bottom: 1px solid #bbb;
&.small {
line-height: 28px;
font-size: 14px;
line-height:28px;
text-shadow: 0 1px 1px white;
}
form {
padding:9px 0;
margin:0px;
}
.nav-pills {
li {
padding:3px 0;
&.active a { background-color:$style_color; }
a {
border-radius:7px;
}
}
}
}
.bottom {
@include bg-gray-gradient;
@include round-borders-bottom(4px);
border-bottom:none;
border-top: 1px solid #bbb;
}
&.padded {
h5, .title {
margin: -20px;
margin-bottom: 0;
padding: 5px 20px;
}
.middle_title {
background:#f5f5f5;
margin:20px -20px;
padding: 0 20px;
border-top:1px solid #eee;
border-bottom:1px solid #eee;
font-size:14px;
color:#777;
}
}
.row_title {
font-weight:bold;
color:#444;
&:hover {
color:#444;
text-decoration:underline;
}
}
li, .wll {
padding:10px;
&:first-child {
@include round-borders-top(4px);
border-top:none;
}
&:last-child {
@include round-borders-bottom(4px);
border:none;
}
}
}
.btn {
background-image: -webkit-gradient(linear, 0 0, 0 26, color-stop(0.076, #f7f7f7), to(#d5d5d5));
background-image: -webkit-linear-gradient(#f7f7f7 7.6%, #d5d5d5);
background-image: -moz-linear-gradient(#f7f7f7 7.6%, #d5d5d5);
background-image: -o-linear-gradient(#f7f7f7 7.6%, #d5d5d5);
border-color:#aaa;
&:hover {
@include bg-gray-gradient;
border-color:#bbb;
color:#333;
}
&.primary {
background:#2a79A3;
border-color: #2A79A3;
background-image: -webkit-linear-gradient(#47A7b7 7.6%, #2585b5);
background-image: -moz-linear-gradient(#47A7b7 7.6%, #2585b5);
background-image: -o-linear-gradient(#47A7b7 7.6%, #2585b5);
color:#fff;
text-shadow: 0 1px 1px #268;
&:hover {
background:$blue_link;
color:#fff;
}
&.disabled {
color:#fff;
background:#29B;
}
}
&.success {
border-color: #4A4;
background-image: -webkit-linear-gradient(#82D482 7.6%, #22B442);
background-image: -moz-linear-gradient(#82D482 7.6%, #22B442);
background-image: -o-linear-gradient(#82D482 7.6%, #22B442);
color: #fff;
text-shadow: 0 1px 1px #141;
&:hover {
background: #6C6;
color: #fff;
}
&.disabled {
color:#fff;
background:#2b2;
}
}
&.save-btn {
@extend .wide;
@extend .primary;
}
&.cancel-btn {
float:right;
}
&.wide {
padding-left:30px;
padding-right:30px;
}
&.danger,
&.btn-danger {
color:#fff;
background: #DA4E49;
border-color: #BD362F;
&:hover {
color:#fff;
background: #EE4E49;
}
}
&.danger {
@extend .btn-danger;
}
&.small {
@extend .btn-small;
}
&.active {
border-color:#aaa;
background-color:#ccc;
}
&.very_small {
font-size:11px;
padding:2px 6px;
margin:2px;
}
&.grouped {
margin-right:7px;
float:left;
}
&.padded {
margin-right:3px;
padding:4px 10px 4px;
}
}
/** COLORS **/
.cgray { color:gray }
.cred { color:#D12F19 }
.cgreen { color:#4a2 }
.cblack { color:#111 }
.cdark { color:#444 }
.cwhite { color:#fff!important }
.bgred { background:#F2DEDE!important }
/** 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 }
.prepend-top-20 { margin-top:20px }
.padded { padding:20px }
.ipadded { padding:20px!important }
.lborder { border-left:1px solid #eee }
.no-padding { padding:0 !important; }
.underlined { border-bottom: 1px solid #CCC; }
.no-borders { border:none; }
.vlink { color: $link_color !important; }
.borders { border: 1px solid #ccc; @include shade; }
.hint { font-style: italic; color: #999; }
/** PILLS & TABS**/
.nav-pills a:hover { background-color:#888; }
.nav-pills .active a { background-color: $style_color; }
.nav-tabs > li > a, .nav-pills > li > a { color:$style_color; }
.nav-tabs > .active > a { font-weight:bold; }
/** ALERT MESSAGES **/
.alert-message { @extend .alert; }
.alert-messag.success { @extend .alert-success; }
.alert-message.error { @extend .alert-error; }
/** AVATARS **/
img.avatar { float:left; margin-right:15px; width:40px; border:1px solid #ddd; padding:1px; }
img.avatar.s16 { width:16px; height:16px; }
img.avatar.s24 { width:24px; height:24px; }
img.avatar.s32 { width:32px; height:32px; }
img.lil_av { padding-left: 4px; padding-right:3px; }
/** HELPERS **/
.nothing_here_message { text-align:center; padding:20px; color:#777; }
p.slead { color:#456; font-size:16px; margin-bottom: 12px; font-weight: 200; line-height: 24px; }
/**
* File content holder
*
*/
.file_holder {
border:1px solid #CCC;
margin-bottom:1em;
@include solid_shade;
.file_title {
border-bottom: 1px solid #bbb;
@include bg-gray-gradient;
margin: 0;
font-weight: normal;
font-weight: bold;
text-align: left;
color: #666;
padding: 9px 10px;
height:18px;
.options {
float:right;
margin-top: -5px;
}
.file_name {
color:$style_color;
font-size:14px;
text-shadow: 0 1px 1px #fff;
small {
color:#999;
font-size:13px;
}
}
}
.file_content {
background:#fff;
font-size: 11px;
&.wiki {
font-size: 13px;
code {
padding:0 4px;
}
padding:20px;
h1, h2 {
line-height: 46px;
}
h3, h4 {
line-height: 40px;
}
}
&.image_file {
background:#eee;
text-align:center;
img {
padding:100px;
max-width:300px;
}
}
&.blob_file {
}
/**
* Blame file
*/
&.blame {
tr {
border-bottom: 1px solid #eee;
}
td {
padding:5px;
}
.author,
.blame_commit {
background:#f5f5f5;
vertical-align:top;
}
.lines {
pre {
padding:0;
margin:0;
background:none;
border:none;
}
}
}
&.logs {
background:#eee;
max-height: 700px;
overflow-y: auto;
ol {
margin-left:40px;
padding: 10px 0;
border-left: 1px solid #CCC;
margin-bottom:0;
background: white;
li {
color:#888;
p {
margin:0;
color:#333;
line-height:24px;
padding-left: 10px;
}
&:hover {
background:$hover;
}
}
}
}
/**
* Code file
*/
&.code {
padding:0;
td.code {
width: 100%;
.highlight {
margin-left: 55px;
overflow:auto;
overflow-y:hidden;
}
}
.highlight pre {
white-space: pre;
word-wrap:normal;
}
table.highlighttable {
border: none;
}
body.project-page table.highlighttable td { border: none }
table.highlighttable tr:hover { background:none;}
table.highlighttable pre{
line-height:16px !important;
font-size:12px !important;
}
table.highlighttable .linenodiv pre {
text-align: right;
padding-right: 4px;
color:#666;
}
}
}
}
/** LISTS **/
ul {
/**
* List li block element #1
*
*/
.wll {
background-color: #FFF;
padding: 10px 5px;
min-height: 20px;
border-bottom: 1px solid #eee;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
&.smoke { background-color:#f5f5f5; }
&:hover { background:$hover; }
&:last-child { border:none }
.author { color: #999; }
p {
padding-top:5px;
margin:0;
color:#222;
img {
position:relative;
top:3px;
}
}
}
}
table {
width:100%;
th {
padding-top: 9px;
font-weight: bold;
vertical-align: middle;
}
th, td {
padding: 10px 10px 9px;
line-height: 18px;
text-align: left;
}
&.bordered-table {
border: 1px solid #DDD;
border-collapse: separate;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
&.zebra-striped {
@extend .table-striped;
}
}
table.admin-table {
@extend .table-bordered;
@extend .zebra-striped;
@include solid_shade;
th {
border-color: #CCC;
border-bottom: 1px solid #bbb;
@include bg-gray-gradient;
}
}
table.no-borders {
border:none;
tr, td { border:none }
}
/**
* Headers
*
*/
h3, h4, h5, h6 { line-height: 36px; }
h5 { font-size:14px; }
h3.page_title {
color:#456;
font-size:20px;
font-weight: normal;
line-height: 28px;
}
/** CODE **/
pre {
font-family:'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace;
&.dark {
background: #333;
color:#f5f5f5;
}
}
/**
* Links
*
*/
a {
outline: none;
color: $link_color;
&:hover {
text-decoration:none;
color: $blue_link;
}
&.btn {
color: $style_color;
&:hover {
color: $style_color;
}
}
&.dark {
color: $style_color;
}
&.lined {
text-decoration:underline;
&:hover { text-decoration:underline; }
}
&.gray {
color:gray;
}
&.supp_diff_link {
text-align:center;
padding:20px 0;
background:#f1f1f1;
width:100%;
float:left;
}
&.neib {
margin-right:15px;
}
}
a:focus {
outline: none;
}
...@@ -2,29 +2,13 @@ ...@@ -2,29 +2,13 @@
@import "bootstrap-responsive"; @import "bootstrap-responsive";
/** GITLAB colors **/ /** GITLAB colors **/
$text_color:#222; $link_color:#3A89A3;
$lite_text_color: #666;
$link_color:#2A79A3;
$active_link_color:#2FA0BB;
$active_bg_color:#79C3E0;
$active_bd_color: #2FA0BB;
$border_color:#CCC;
$lite_border_color:#EEE;
$min_app_width:980px;
$max_app_width:980px;
$app_padding:20px;
$bg_color: #FFF;
$styled_border_color: #2FA0BB;
$color: "#4BB8D2";
$blue_link: #2fa0bb; $blue_link: #2fa0bb;
$style_color: #474d57;
$hover: #fdf5d9;
/** Style colors **/
$style_color: #474D57;
$hover: #FDF5D9;
/** GITLAB Fonts **/ /** GITLAB Fonts **/
@font-face { font-family: Korolev; src: url('korolev-medium-compressed.otf'); } @font-face { font-family: Korolev; src: url('korolev-medium-compressed.otf'); }
/** MIXINS **/ /** MIXINS **/
@mixin shade { @mixin shade {
...@@ -72,7 +56,20 @@ $hover: #FDF5D9; ...@@ -72,7 +56,20 @@ $hover: #FDF5D9;
border-radius: $radius; border-radius: $radius;
} }
@mixin bg-gray-gradient {
background:#eee;
background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
}
@mixin bg-dark-gray-gradient {
background:#eee;
background-image: -webkit-linear-gradient(#e9e9e9, #d7d7d7);
background-image: -moz-linear-gradient(#e9e9e9, #d7d7d7);
background-image: -o-linear-gradient(#e9e9e9, #d7d7d7);
}
/** /**
* Header of application. * Header of application.
...@@ -113,7 +110,13 @@ $hover: #FDF5D9; ...@@ -113,7 +110,13 @@ $hover: #FDF5D9;
* Overrides some styles of twitter bootstrap. * Overrides some styles of twitter bootstrap.
* Also give some common classes for gitlab app * Also give some common classes for gitlab app
*/ */
@import "gitlab_bootstrap.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";
/** /**
......
.git_url_wrapper { margin-right:50px }
.sidebar aside a{
display: block;
position: relative;
padding: 15px 10px;
margin: 10px 0 0 0;
font-size:13px;
font-weight:bold;
color:#333;
&.current {
color: white;
background: $active_bg_color;
border: 1px solid $active_bd_color;
border-radius:5px;
-webkit-border-top-right-radius: 0;
-webkit-border-bottom-right-radius: 0;
-moz-border-radius-topright: 0px;
-moz-border-radius-bottomright: 0px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
margin-right: -1px;
}
}
body table .commit a{color: #{$blue_link}}
body table th, body table td{ border-bottom: 1px solid #DEE2E3;}
body .fixed{position: fixed; }
/** File stat **/
.file_stats {
span {
img {
width:14px;
float:left;
margin-right: 6px;
padding:2px 0;
}
}
}
.round-borders {
@include round-borders-all(4px);
padding: 4px 0px;
}
table.round-borders {
float:left;
text-align: left;
}
/** PROJECTS **/
input.ssh_project_url {
padding:5px;
margin:0px;
float:right;
width:400px;
text-align:center;
}
#projects-list .project {
height:50px;
}
#tree-slider .tree-item,
#projects-list .project,
#snippets-table .snippet,
#issues-table .issue{
cursor:pointer;
}
.clear {
clear: both;
}
#user_projects_limit{
width: 60px;
}
.handle:hover{
cursor: move;
}
.project-refs-form {
span {
background: none !important;
position:static !important;
width:auto !important;
height: auto !important;
}
}
.project-refs-select {
width:200px;
}
.filter .left { margin-right:15px; }
body table .commit {
a.tree-commit-link {
color:#444;
&:hover {
text-decoration:underline;
}
}
}
/** NEW PROJECT **/
.new-project-hodler {
.icon span { background-position: -31px -70px; }
td { border-bottom: 1px solid #DEE2E3; }
}
/** Feed entry **/
.commit,
.snippet,
.message {
.title {
color:#666;
a { color:#666 !important; }
p { margin-top:0px; }
}
.author { color: #999 }
}
/** JQuery UI **/
.ui-autocomplete { @include round-borders-all(5px); }
.ui-menu-item { cursor: pointer }
.ui-selectmenu{
@include round-borders-all(4px);
margin-right:10px;
font-size:1.5em;
height:auto;
font-weight:bold;
.ui-selectmenu-status {
padding:3px 10px;
}
}
#holder {
background:#FAFAFA;
border: 1px solid #EEE;
cursor: move;
height: 70%;
overflow: hidden;
}
/* Project Dashboard Page */
html, body { height: 100%; }
.news-feed h2{float: left;}
.news-feed .project-updates {margin-bottom: 20px; display: block; width: 100%;}
.news-feed .project-updates .data{ padding: 0}
.news-feed .project-updates a.project-update {padding: 10px; border-bottom: 1px solid #eee; overflow: hidden; display: block;}
.news-feed .project-updates a.project-update:last-child{border-bottom: 0}
.news-feed .project-updates a.project-update img{float: left; margin-right: 10px;}
.news-feed .project-updates a.project-update span.update-title, .dashboard-page .news-feed .project-updates li a span.update-author{display: block;}
.news-feed .project-updates a.project-update span.update-title{margin-bottom: 10px}
.news-feed .project-updates a.project-update span.update-author{color: #999; font-weight: normal; font-style: italic;}
.news-feed .project-updates a.project-update span.update-author strong{font-weight: bold; font-style: normal;}
/* eo Dashboard Page */
/** Update entry **/
.update-data { padding: 0 }
.update-data { width:100%; }
.update-data.ui-box .data { padding:0; }
a.update-item {padding: 10px; border-bottom: 1px solid #eee; overflow: hidden; display: block;}
a.update-item:last-child{border-bottom: 0}
a.update-item img{float: left; margin-right: 10px;}
a.update-item span.update-title, .dashboard-page .news-feed .project-updates li a span.update-author{display: block;}
a.update-item span.update-title{margin-bottom: 10px}
a.update-item span.update-author{color: #999; font-weight: normal; font-style: italic;}
a.update-item span.update-author strong{font-weight: bold; font-style: normal;}
body .team_member_new .span-6, .team_member_edit .span-6{ padding:10px 0; }
body.projects-page input.text.git-url.project_list_url { width:165px; }
body table.no-borders th {
background:none;
border-bottom:1px solid #CCC;
color:#333;
}
body table.no-borders tr,
body table.no-borders td{
border:none;
}
.ajax-tab-loading {
padding:40px;
display:none;
}
#tree-content-holder { float:left; width:100%; }
#tree-readme-holder {
float:left;
width:100%;
.readme {
@include round-borders-all(4px);
padding: 4px 15px;
background:#F7F7F7;
}
}
/* Commit Page */
.entity-info {float: right;}
.entity-button{
background-image: -webkit-gradient(linear, 0 0, 0 26, color-stop(0.192, #fff), to(#f4f4f4));
background-image: -webkit-linear-gradient(#fff 19.2%, #f4f4f4);
background-image: -moz-linear-gradient(#fff 19.2%, #f4f4f4);
background-image: -o-linear-gradient(#fff 19.2%, #f4f4f4);
box-shadow: 0 -1px 0 white inset;
display: block;
border: 1px solid #eee;
border-radius: 5px;
margin-bottom: 2px;
position: relative;
padding: 4px 10px;
font-size: 11px;
padding-right: 20px;
}
.entity-button i{
background: url('images.png') no-repeat -138px -27px;
width: 6px;
height: 9px;
float: right;
position: absolute;
top: 6px;
right: 5px;
}
.box-arrow{float: right; background: #E3E5EA; padding: 10px; border-radius: 5px; margin-top: 2px; text-shadow: none; color: #999; margin: 1.5em 0;}
h4.dash-tabs {
margin: 0;
border-bottom: 1px solid #ccc;
padding: 10px 10px;
font-size: 11px;
padding-left:20px;
font-weight: bold; text-transform: uppercase;
background: #F7F7F7;
margin-bottom:20px;
height:13px;
}
.dash-button {
border-right: 1px solid #ddd;
background:none;
padding: 10px 15px;
float:left;
position:relative;
top:-10px;
left:0px;
height:13px;
&:first-child {
border-left: 1px solid #ddd;
}
&.active {
background: #eaeaea;
}
}
.dashboard-loader {
float:right;
margin-right:30px;
display:none;
}
.merge-tabs {
margin: 0;
border: 1px solid #ccc;
padding: 5px;
font-size: 12px;
background: #F7F7F7;
margin-bottom:20px;
height:26px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
.tab {
font-weight: bold;
border-right: 1px solid #ddd;
background:none;
padding: 10px;
min-width:60px;
float:left;
position:relative;
top:-5px;
left:-5px;
height:16px;
padding-left:34px;
span {
width: 20px;
height: 20px;
display: inline-block;
position: absolute;
left: 8px;
top: 8px;
}
&.active {
background: #eaeaea;
}
}
}
.merge-tabs.repository .tab span{ background: url("images.png") no-repeat -38px -77px; }
.activities-tab span { background: url("images.png") no-repeat -161px -1px; }
.stat-tab span,
.team-tab span,
.snippets-tab span { background: url("images.png") no-repeat -38px -77px; }
.files-tab span { background: url("images.png") no-repeat -112px -23px; }
.merge-notes-tab span { background: url("images.png") no-repeat -161px -1px; }
.merge-commits-tab span { background: url("images.png") no-repeat -86px 1px; }
.merge-diffs-tab span { background: url("images.png") no-repeat -118px 1px; }
.merge-tabs .dashboard-loader { padding:8px; }
.user-mention {
color: #2FA0BB;
font-weight: bold;
}
.author {
color: #999;
}
.dark_scheme_box {
padding:20px 0;
label {
float:left;
box-shadow: 0 0px 5px rgba(0,0,0,.3);
img {
}
}
}
a.project-update.titled {
position: relative;
padding-left: 235px !important;
.title-block {
padding: 10px;
width: 205px;
position: absolute;
left: 0;
top: 0;
}
}
.add_new {
float: right;
background: #A6B807;
color: white;
padding: 4px 10px;
@include round-borders-all(4px);
font-size:11px;
margin: 10px 0;
}
...@@ -33,9 +33,7 @@ ...@@ -33,9 +33,7 @@
} }
.chzn-single { .chzn-single {
background:#ddd; @include bg-gray-gradient;
//border:none;
//box-shadow:none;
div { div {
background:transparent; background:transparent;
......
...@@ -206,4 +206,24 @@ ...@@ -206,4 +206,24 @@
min-width:65px; min-width:65px;
font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace;
} }
.commit-author-name {
color: #777;
}
}
.diff_file_header a,
.file_stats a {
color:$style_color;
}
.file_stats {
span {
img {
width:14px;
float:left;
margin-right:6px;
padding:2px 0;
}
}
} }
...@@ -6,11 +6,7 @@ ...@@ -6,11 +6,7 @@
h4 { h4 {
padding:0 10px; padding:0 10px;
border-bottom: 1px solid #bbb; border-bottom: 1px solid #bbb;
background:#eee; @include bg-gray-gradient;
background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
} }
.graph { .graph {
......
...@@ -65,6 +65,11 @@ input.check_all_issues { ...@@ -65,6 +65,11 @@ input.check_all_issues {
} }
} }
@media (min-width: 800px) { .issues_filters select { width:160px; } }
@media (min-width: 1000px) { .issues_filters select { width:200px; } }
@media (min-width: 1200px) { .issues_filters select { width:220px; } }
#issues-table-holder { #issues-table-holder {
.issues_filters { .issues_filters {
form { form {
...@@ -99,3 +104,11 @@ input.check_all_issues { ...@@ -99,3 +104,11 @@ input.check_all_issues {
#update_status { #update_status {
width:100px; width:100px;
} }
/**
* Milestones list
*
*/
.milestone {
@extend .wll;
}
...@@ -11,23 +11,6 @@ ...@@ -11,23 +11,6 @@
background:#f1f1f1; background:#f1f1f1;
} }
.commit {
margin:0;
padding:0;
padding: 5px;
margin-bottom: 5px;
.committed_ago {
display:none;
}
.browse_code_link_holder {
display:none;
}
list-style:none;
&:hover {
background:none;
}
}
} }
/** /**
...@@ -55,6 +38,7 @@ ...@@ -55,6 +38,7 @@
background: #CEB; background: #CEB;
.accept_merge_request { .accept_merge_request {
font-size:13px;
float:left; float:left;
} }
.remove_branch_holder { .remove_branch_holder {
...@@ -99,3 +83,42 @@ li.merge_request { ...@@ -99,3 +83,42 @@ li.merge_request {
@extend .padded; @extend .padded;
@extend .append-bottom-10; @extend .append-bottom-10;
} }
.label_branch {
@include round-borders-all(4px);
padding:2px 4px;
border:none;
font-size:13px;
background: #474D57;
color:#fff;
font-weight:bold;
font-family: monospace;
}
.mr_source_commit,
.mr_target_commit {
.commit {
margin:0;
padding:0;
padding: 5px;
margin-bottom: 5px;
.avatar { position:relative }
.row_title {
color:#444;
}
.commit-author-name,
.dash,
.committed_ago,
.browse_code_link_holder {
display:none;
}
list-style:none;
&:hover {
background:none;
}
}
}
.mr_direction_tip {
margin-top:40px
}
...@@ -6,13 +6,9 @@ ul.main_menu { ...@@ -6,13 +6,9 @@ ul.main_menu {
border-radius: 4px; border-radius: 4px;
margin: auto; margin: auto;
margin:30px 0; margin:30px 0;
background:#eee; border:1px solid #AAA;
border:1px solid #bbb;
height:37px; height:37px;
background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); @include bg-gray-gradient;
background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
position:relative; position:relative;
overflow:hidden; overflow:hidden;
@include shade; @include shade;
...@@ -89,7 +85,7 @@ ul.main_menu { ...@@ -89,7 +85,7 @@ ul.main_menu {
line-height:36px; line-height:36px;
color: $style_color; color: $style_color;
text-shadow:0 1px 1px white; text-shadow:0 1px 1px white;
padding:0 10px;
} }
} }
/* /*
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* Notes * Notes
* *
*/ */
#notes-list, #notes-list,
#new_notes_list { #new_notes_list {
display:block; display:block;
list-style:none; list-style:none;
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
padding:0px; padding:0px;
} }
#new_notes_list li:last-child{ #new_notes_list li:last-child{
border-bottom:1px solid #aaa; border-bottom:1px solid #aaa;
} }
...@@ -30,16 +30,24 @@ ...@@ -30,16 +30,24 @@
} }
#new_note { #new_note {
#note_note { .note-text {
height:25px; height:40px;
} }
.attach_holder { .attach_holder {
display:none; display:none;
} }
} }
.note { .preview_note {
padding: 8px 0; margin: 2px;
border: 1px solid #ddd;
padding: 10px;
min-height: 60px;
background:#f5f5f5;
}
.note {
padding: 8px 0;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
overflow: hidden; overflow: hidden;
display: block; display: block;
...@@ -49,16 +57,16 @@ ...@@ -49,16 +57,16 @@
.note-author { color: $style_color;} .note-author { color: $style_color;}
.note-title { margin-left:45px; padding-top: 5px;} .note-title { margin-left:45px; padding-top: 5px;}
.avatar { .avatar {
margin-top:3px; margin-top:3px;
} }
.delete-note { .delete-note {
display:none; display:none;
float:right; float:right;
} }
&:hover { &:hover {
.delete-note { display:block; } .delete-note { display:block; }
} }
} }
...@@ -72,18 +80,18 @@ p.notify_controls span{ ...@@ -72,18 +80,18 @@ p.notify_controls span{
font-weight: 700; font-weight: 700;
} }
tr.line_notes_row { tr.line_notes_row {
border-bottom:1px solid #DDD; border-bottom:1px solid #DDD;
border-left: 7px solid #2A79A3; border-left: 7px solid #2A79A3;
&.reply { &.reply {
background:#eee; background:#eee;
border-left: 7px solid #2A79A3; border-left: 7px solid #2A79A3;
border-top:1px solid #ddd; border-top:1px solid #ddd;
td { td {
padding:7px 10px; padding:7px 10px;
} }
a.line_note_reply_link { a.line_note_reply_link {
@include round-borders-all(4px); @include round-borders-all(4px);
padding: 3px 10px; padding: 3px 10px;
margin-left:5px; margin-left:5px;
...@@ -92,9 +100,9 @@ tr.line_notes_row { ...@@ -92,9 +100,9 @@ tr.line_notes_row {
border-color: #2A79A3; border-color: #2A79A3;
} }
} }
ul { ul {
margin:0; margin:0;
li { li {
padding:0; padding:0;
border:none; border:none;
} }
...@@ -103,26 +111,26 @@ tr.line_notes_row { ...@@ -103,26 +111,26 @@ tr.line_notes_row {
.line_notes_row, .per_line_form { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } .line_notes_row, .per_line_form { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; }
.per_line_form { .per_line_form {
background:#f5f5f5; background:#f5f5f5;
border-top:1px solid #eee; border-top:1px solid #eee;
form { margin: 0; } form { margin: 0; }
td { td {
border-bottom:1px solid #ddd; border-bottom:1px solid #ddd;
} }
.note_actions { .note_actions {
margin:0; margin:0;
padding-top: 10px; padding-top: 10px;
.buttons { .buttons {
float:left; float:left;
width:300px; width:300px;
} }
.options { .options {
.labels { .labels {
float:left; float:left;
padding-left:10px; padding-left:10px;
label { label {
padding: 6px 0; padding: 6px 0;
margin: 0; margin: 0;
width:120px; width:120px;
...@@ -132,7 +140,7 @@ tr.line_notes_row { ...@@ -132,7 +140,7 @@ tr.line_notes_row {
} }
} }
td .line_note_link { td .line_note_link {
position:absolute; position:absolute;
margin-left:-70px; margin-left:-70px;
margin-top:-10px; margin-top:-10px;
...@@ -144,14 +152,14 @@ td .line_note_link { ...@@ -144,14 +152,14 @@ td .line_note_link {
opacity: 0.0; opacity: 0.0;
filter: alpha(opacity=0); filter: alpha(opacity=0);
&:hover { &:hover {
opacity: 1.0; opacity: 1.0;
filter: alpha(opacity=100); filter: alpha(opacity=100);
} }
} }
.diff_file_content tr.line_holder:hover > td { background: $hover !important; } .diff_file_content tr.line_holder:hover > td { background: $hover !important; }
.diff_file_content tr.line_holder:hover > td .line_note_link { .diff_file_content tr.line_holder:hover > td .line_note_link {
opacity: 1.0; opacity: 1.0;
filter: alpha(opacity=100); filter: alpha(opacity=100);
} }
...@@ -169,8 +177,8 @@ td .line_note_link { ...@@ -169,8 +177,8 @@ td .line_note_link {
margin: 0; margin: 0;
} }
.note_advanced_opts { .note_advanced_opts {
h6 { h6 {
line-height: 32px; line-height: 32px;
padding-right: 15px; padding-right: 15px;
} }
...@@ -183,7 +191,7 @@ td .line_note_link { ...@@ -183,7 +191,7 @@ td .line_note_link {
overflow:hidden; overflow:hidden;
margin:0 0 5px !important; margin:0 0 5px !important;
.input_file { .input_file {
.file_upload { .file_upload {
position: absolute; position: absolute;
right:14px; right:14px;
...@@ -196,7 +204,7 @@ td .line_note_link { ...@@ -196,7 +204,7 @@ td .line_note_link {
height:28px; height:28px;
overflow:hidden; overflow:hidden;
} }
.input-file { .input-file {
width: 260px; width: 260px;
height: 41px; height: 41px;
float: right; float: right;
...@@ -204,3 +212,8 @@ td .line_note_link { ...@@ -204,3 +212,8 @@ td .line_note_link {
} }
} }
} }
.note-text {
border: 1px solid #aaa;
box-shadow:none;
}
.projects { .projects {
@extend .row; @extend .row;
.activities { .activities {
} }
.side { .side {
@extend .span4; @extend .span4;
@extend .right; @extend .right;
.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;
padding: 2px 10px;
}
ul {
li {
padding:0;
a {
display:block;
.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;
}
}
}
}
} }
@extend .leftbar; @extend .leftbar;
@extend .ui-box; @extend .ui-box;
...@@ -19,21 +46,47 @@ ...@@ -19,21 +46,47 @@
} }
} }
.new_project, .new_project,
.edit_project { .edit_project {
.project_name_holder { .project_name_holder {
input, input,
label { label {
font-size:16px; font-size:16px;
line-height:20px; line-height:20px;
padding:8px; padding:8px;
} }
label { label {
color:#888; color:#888;
} }
.btn { .btn {
padding:6px; padding:6px 10px;
margin-left:10px; margin-left:10px;
margin-bottom:8px;
} }
} }
.adv_settings {
h6 { margin-left:40px; }
}
}
.project_clone_panel {
@include border-radius(4px);
@include bg-gray-gradient;
padding: 4px 7px;
border: 1px solid #CCC;
margin-bottom:5px;
input[type=text] {
border: 1px solid #BBB;
}
}
.save-project-loader {
img {
margin-top:50px;
margin-bottom:50px;
}
h3 {
@extend .page_title;
}
} }
...@@ -72,11 +72,7 @@ ...@@ -72,11 +72,7 @@
th { th {
border-color: #CCC; border-color: #CCC;
border-bottom: 1px solid #bbb; border-bottom: 1px solid #bbb;
background:#eee; @include bg-gray-gradient;
background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
} }
} }
......
...@@ -20,6 +20,10 @@ ...@@ -20,6 +20,10 @@
.fbtn { .fbtn {
.btn { .btn {
i {
position: relative;
top: 1px;
}
margin-left:8px; margin-left:8px;
background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #595D63), to(#31363E)); background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #595D63), to(#31363E));
background-image: -webkit-linear-gradient(#595D63 6.6%, #31363E); background-image: -webkit-linear-gradient(#595D63 6.6%, #31363E);
...@@ -32,6 +36,10 @@ ...@@ -32,6 +36,10 @@
background-image: -moz-linear-gradient(#595D63 6.6%, #202227); background-image: -moz-linear-gradient(#595D63 6.6%, #202227);
background-image: -o-linear-gradient(#595D63 6.6%, #202227); background-image: -o-linear-gradient(#595D63 6.6%, #202227);
background-position:0 0; background-position:0 0;
color:#fff;
i {
@extend .icon-white;
}
} }
border: 1px solid #31363E; border: 1px solid #31363E;
......
class MergeRequestsLoad < BaseContext class MergeRequestsLoad < BaseContext
def execute def execute
type = params[:f].to_i type = params[:f]
merge_requests = project.merge_requests merge_requests = project.merge_requests
merge_requests = case type merge_requests = case type
when 1 then merge_requests when 'all' then merge_requests
when 2 then merge_requests.closed when 'closed' then merge_requests.closed
when 3 then merge_requests.opened.assigned(current_user) when 'assigned-to-me' then merge_requests.opened.assigned(current_user)
else merge_requests.opened else merge_requests.opened
end.page(params[:page]).per(20) end.page(params[:page]).per(20)
......
...@@ -14,6 +14,10 @@ class ApplicationController < ActionController::Base ...@@ -14,6 +14,10 @@ class ApplicationController < ActionController::Base
render "errors/gitolite", layout: "error" render "errors/gitolite", layout: "error"
end end
rescue_from Gitlab::Gitolite::InvalidKey do |exception|
render "errors/invalid_ssh_key", layout: "error"
end
rescue_from Encoding::CompatibilityError do |exception| rescue_from Encoding::CompatibilityError do |exception|
render "errors/encoding", layout: "error", status: 404 render "errors/encoding", layout: "error", status: 404
end end
......
...@@ -60,7 +60,13 @@ class IssuesController < ApplicationController ...@@ -60,7 +60,13 @@ class IssuesController < ApplicationController
@issue.save @issue.save
respond_to do |format| respond_to do |format|
format.html { redirect_to project_issue_path(@project, @issue) } format.html do
if @issue.valid?
redirect_to project_issue_path(@project, @issue)
else
render :new
end
end
format.js format.js
end end
end end
...@@ -162,10 +168,10 @@ class IssuesController < ApplicationController ...@@ -162,10 +168,10 @@ class IssuesController < ApplicationController
def issues_filter def issues_filter
{ {
all: "1", all: "all",
closed: "2", closed: "closed",
to_me: "3", to_me: "assigned-to-me",
open: "0" open: "open"
} }
end end
end end
class LabelsController < ApplicationController
before_filter :authenticate_user!
before_filter :project
before_filter :module_enabled
layout "project"
# Authorize
before_filter :add_project_abilities
# Allow read any issue
before_filter :authorize_read_issue!
respond_to :js, :html
def index
@labels = @project.issues.tag_counts_on(:labels).order('count DESC')
end
protected
def module_enabled
return render_404 unless @project.issues_enabled
end
end
...@@ -103,10 +103,12 @@ class MergeRequestsController < ApplicationController ...@@ -103,10 +103,12 @@ class MergeRequestsController < ApplicationController
def branch_from def branch_from
@commit = project.commit(params[:ref]) @commit = project.commit(params[:ref])
@commit = CommitDecorator.decorate(@commit)
end end
def branch_to def branch_to
@commit = project.commit(params[:ref]) @commit = project.commit(params[:ref])
@commit = CommitDecorator.decorate(@commit)
end end
protected protected
......
...@@ -17,8 +17,8 @@ class MilestonesController < ApplicationController ...@@ -17,8 +17,8 @@ class MilestonesController < ApplicationController
respond_to :html respond_to :html
def index def index
@milestones = case params[:f].to_i @milestones = case params[:f]
when 1; @project.milestones when 'all'; @project.milestones
else @project.milestones.active else @project.milestones.active
end end
......
...@@ -12,8 +12,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -12,8 +12,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def ldap def ldap
# We only find ourselves here if the authentication to LDAP was successful. # We only find ourselves here if the authentication to LDAP was successful.
info = request.env["omniauth.auth"]["info"] @user = User.find_for_ldap_auth(request.env["omniauth.auth"], current_user)
@user = User.find_for_ldap_auth(info)
if @user.persisted? if @user.persisted?
@user.remember_me = true @user.remember_me = true
end end
......
...@@ -9,6 +9,7 @@ class TeamMembersController < ApplicationController ...@@ -9,6 +9,7 @@ class TeamMembersController < ApplicationController
def show def show
@team_member = project.users_projects.find(params[:id]) @team_member = project.users_projects.find(params[:id])
@events = @team_member.user.recent_events.where(:project_id => @project.id).limit(7)
end end
def new def new
......
class ApplicationDecorator < Drapper::Base class ApplicationDecorator < Draper::Base
# Lazy Helpers # Lazy Helpers
# PRO: Call Rails helpers without the h. proxy # PRO: Call Rails helpers without the h. proxy
# ex: number_to_currency(model.price) # ex: number_to_currency(model.price)
......
...@@ -2,10 +2,13 @@ require 'digest/md5' ...@@ -2,10 +2,13 @@ require 'digest/md5'
module ApplicationHelper module ApplicationHelper
def gravatar_icon(user_email = '', size = 40) def gravatar_icon(user_email = '', size = 40)
return unless user_email if Gitlab.config.disable_gravatar? || user_email.blank?
gravatar_host = request.ssl? ? "https://secure.gravatar.com" : "http://www.gravatar.com" 'no_avatar.png'
user_email.strip! else
"#{gravatar_host}/avatar/#{Digest::MD5.hexdigest(user_email.downcase)}?s=#{size}&d=identicon" gravatar_prefix = request.ssl? ? "https://secure" : "http://www"
user_email.strip!
"#{gravatar_prefix}.gravatar.com/avatar/#{Digest::MD5.hexdigest(user_email.downcase)}?s=#{size}&d=identicon"
end
end end
def request_protocol def request_protocol
...@@ -75,16 +78,16 @@ module ApplicationHelper ...@@ -75,16 +78,16 @@ module ApplicationHelper
end end
def show_last_push_widget?(event) def show_last_push_widget?(event)
event && event &&
event.last_push_to_non_root? && event.last_push_to_non_root? &&
!event.rm_ref? && !event.rm_ref? &&
event.project && event.project &&
event.project.merge_requests_enabled event.project.merge_requests_enabled
end end
def tab_class(tab_key) def tab_class(tab_key)
active = case tab_key active = case tab_key
# Project Area # Project Area
when :wall; wall_tab? when :wall; wall_tab?
when :wiki; controller.controller_name == "wikis" when :wiki; controller.controller_name == "wikis"
...@@ -123,4 +126,13 @@ module ApplicationHelper ...@@ -123,4 +126,13 @@ module ApplicationHelper
def hexdigest(string) def hexdigest(string)
Digest::SHA1.hexdigest string Digest::SHA1.hexdigest string
end end
def project_last_activity project
activity = project.last_activity
if activity && activity.created_at
time_ago_in_words(activity.created_at) + " ago"
else
"Never"
end
end
end end
module GitlabMarkdownHelper module GitlabMarkdownHelper
# Replaces references (i.e. @abc, #123, !456, ...) in the text with links to
# the appropriate items in Gitlab.
#
# text - the source text
# html_options - extra options for the reference links as given to link_to
#
# note: reference links will only be generated if @project is set
#
# see Gitlab::Markdown for details on the supported syntax
def gfm(text, html_options = {}) def gfm(text, html_options = {})
return text if text.nil? return text if text.nil?
return text if @project.nil? return text if @project.nil?
# Extract pre blocks # Extract pre blocks so they are not altered
# from http://github.github.com/github-flavored-markdown/ # from http://github.github.com/github-flavored-markdown/
extractions = {} extractions = {}
text.gsub!(%r{<pre>.*?</pre>|<code>.*?</code>}m) do |match| text.gsub!(%r{<pre>.*?</pre>|<code>.*?</code>}m) do |match|
...@@ -22,10 +31,18 @@ module GitlabMarkdownHelper ...@@ -22,10 +31,18 @@ module GitlabMarkdownHelper
extractions[$1] extractions[$1]
end end
text.html_safe sanitize text.html_safe, attributes: ActionView::Base.sanitized_allowed_attributes + %w(id class )
end end
# circumvents nesting links, which will behave bad in browsers # Use this in places where you would normally use link_to(gfm(...), ...).
#
# It solves a problem occurring with nested links (i.e.
# "<a>outer text <a>gfm ref</a> more outer text</a>"). This will not be
# interpreted as intended. Browsers will parse something like
# "<a>outer text </a><a>gfm ref</a> more outer text" (notice the last part is
# not linked any more). link_to_gfm corrects that. It wraps all parts to
# explicitly produce the correct linking behavior (i.e.
# "<a>outer text </a><a>gfm ref</a><a> more outer text</a>").
def link_to_gfm(body, url, html_options = {}) def link_to_gfm(body, url, html_options = {})
gfm_body = gfm(body, html_options) gfm_body = gfm(body, html_options)
...@@ -37,17 +54,24 @@ module GitlabMarkdownHelper ...@@ -37,17 +54,24 @@ module GitlabMarkdownHelper
end end
def markdown(text) def markdown(text)
@__renderer ||= Redcarpet::Markdown.new(Redcarpet::Render::GitlabHTML.new(self, filter_html: true, with_toc_data: true), { unless @markdown
no_intra_emphasis: true, gitlab_renderer = Redcarpet::Render::GitlabHTML.new(self,
tables: true, # see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch-
fenced_code_blocks: true, filter_html: true,
autolink: true, with_toc_data: true,
strikethrough: true, hard_wrap: true)
lax_html_blocks: true, @markdown ||= Redcarpet::Markdown.new(gitlab_renderer,
space_after_headers: true, # see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
superscript: true no_intra_emphasis: true,
}) tables: true,
fenced_code_blocks: true,
@__renderer.render(text).html_safe autolink: true,
strikethrough: true,
lax_html_blocks: true,
space_after_headers: true,
superscript: true)
end
@markdown.render(text).html_safe
end end
end end
...@@ -12,74 +12,117 @@ class Notify < ActionMailer::Base ...@@ -12,74 +12,117 @@ class Notify < ActionMailer::Base
def new_user_email(user_id, password) def new_user_email(user_id, password)
@user = User.find(user_id) @user = User.find(user_id)
@password = password @password = password
mail(to: @user.email, subject: "gitlab | Account was created for you") mail(to: @user.email, subject: subject("Account was created for you"))
end end
def new_issue_email(issue_id) def new_issue_email(issue_id)
@issue = Issue.find(issue_id) @issue = Issue.find(issue_id)
@project = @issue.project @project = @issue.project
mail(to: @issue.assignee_email, subject: "gitlab | new issue ##{@issue.id} | #{@issue.title} | #{@project.name}") mail(to: @issue.assignee_email, subject: subject("new issue ##{@issue.id}", @issue.title))
end end
def note_wall_email(recipient_id, note_id) def note_wall_email(recipient_id, note_id)
recipient = User.find(recipient_id)
@note = Note.find(note_id) @note = Note.find(note_id)
@project = @note.project @project = @note.project
mail(to: recipient.email, subject: "gitlab | #{@project.name}") mail(to: recipient(recipient_id), subject: subject)
end end
def note_commit_email(recipient_id, note_id) def note_commit_email(recipient_id, note_id)
recipient = User.find(recipient_id)
@note = Note.find(note_id) @note = Note.find(note_id)
@commit = @note.target @commit = @note.target
@commit = CommitDecorator.decorate(@commit) @commit = CommitDecorator.decorate(@commit)
@project = @note.project @project = @note.project
mail(to: recipient.email, subject: "gitlab | note for commit #{@commit.short_id} | #{@commit.title} | #{@project.name}") mail(to: recipient(recipient_id), subject: subject("note for commit #{@commit.short_id}", @commit.title))
end end
def note_merge_request_email(recipient_id, note_id) def note_merge_request_email(recipient_id, note_id)
recipient = User.find(recipient_id)
@note = Note.find(note_id) @note = Note.find(note_id)
@merge_request = @note.noteable @merge_request = @note.noteable
@project = @note.project @project = @note.project
mail(to: recipient.email, subject: "gitlab | note for merge request !#{@merge_request.id} | #{@project.name}") mail(to: recipient(recipient_id), subject: subject("note for merge request !#{@merge_request.id}"))
end end
def note_issue_email(recipient_id, note_id) def note_issue_email(recipient_id, note_id)
recipient = User.find(recipient_id)
@note = Note.find(note_id) @note = Note.find(note_id)
@issue = @note.noteable @issue = @note.noteable
@project = @note.project @project = @note.project
mail(to: recipient.email, subject: "gitlab | note for issue ##{@issue.id} | #{@project.name}") mail(to: recipient(recipient_id), subject: subject("note for issue ##{@issue.id}"))
end end
def note_wiki_email(recipient_id, note_id) def note_wiki_email(recipient_id, note_id)
recipient = User.find(recipient_id)
@note = Note.find(note_id) @note = Note.find(note_id)
@wiki = @note.noteable @wiki = @note.noteable
@project = @note.project @project = @note.project
mail(to: recipient.email, subject: "gitlab | note for wiki | #{@project.name}") mail(to: recipient(recipient_id), subject: subject("note for wiki"))
end end
def new_merge_request_email(merge_request_id) def new_merge_request_email(merge_request_id)
@merge_request = MergeRequest.find(merge_request_id) @merge_request = MergeRequest.find(merge_request_id)
@project = @merge_request.project @project = @merge_request.project
mail(to: @merge_request.assignee_email, subject: "gitlab | new merge request !#{@merge_request.id} | #{@merge_request.title} | #{@project.name}") mail(to: @merge_request.assignee_email, subject: subject("new merge request !#{@merge_request.id}", @merge_request.title))
end end
def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id) def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id)
recipient = User.find(recipient_id)
@merge_request = MergeRequest.find(merge_request_id) @merge_request = MergeRequest.find(merge_request_id)
@previous_assignee ||= User.find(previous_assignee_id) @previous_assignee ||= User.find(previous_assignee_id)
@project = @merge_request.project @project = @merge_request.project
mail(to: recipient.email, subject: "gitlab | changed merge request !#{@merge_request.id} | #{@merge_request.title} | #{@project.name}") mail(to: recipient(recipient_id), subject: subject("changed merge request !#{@merge_request.id}", @merge_request.title))
end end
def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id) def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id)
recipient = User.find(recipient_id)
@issue = Issue.find(issue_id) @issue = Issue.find(issue_id)
@previous_assignee ||= User.find(previous_assignee_id) @previous_assignee ||= User.find(previous_assignee_id)
@project = @issue.project @project = @issue.project
mail(to: recipient.email, subject: "gitlab | changed issue ##{@issue.id} | #{@issue.title} | #{@project.name}") mail(to: recipient(recipient_id), subject: subject("changed issue ##{@issue.id}", @issue.title))
end
def project_access_granted_email(user_project_id)
@users_project = UsersProject.find user_project_id
@project = @users_project.project
mail(to: @users_project.user.email,
subject: subject("access to project was granted"))
end
def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id)
@issue = Issue.find issue_id
@issue_status = status
@updated_by = User.find updated_by_user_id
mail(to: recipient(recipient_id),
subject: subject("changed issue ##{@issue.id}", @issue.title))
end
private
# Look up a User by their ID and return their email address
#
# recipient_id - User ID
#
# Returns a String containing the User's email address.
def recipient(recipient_id)
if recipient = User.find(recipient_id)
recipient.email
end
end
# Formats arguments into a String suitable for use as an email subject
#
# extra - Extra Strings to be inserted into the subject
#
# Examples
#
# >> subject('Lorem ipsum')
# => "gitlab | Lorem ipsum"
#
# # Automatically inserts Project name when @project is set
# >> @project = Project.last
# => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
# >> subject('Lorem ipsum')
# => "gitlab | Lorem ipsum | Ruby on Rails"
#
# # Accepts multiple arguments
# >> subject('Lorem ipsum', 'Dolor sit amet')
# => "gitlab | Lorem ipsum | Dolor sit amet"
def subject(*extra)
"gitlab | " << extra.join(' | ') << (@project ? " | #{@project.name}" : "")
end end
end end
require 'digest/md5' require 'digest/md5'
class Key < ActiveRecord::Base class Key < ActiveRecord::Base
include SshKey
belongs_to :user belongs_to :user
belongs_to :project belongs_to :project
attr_protected :user_id
validates :title, validates :title,
presence: true, presence: true,
length: { within: 0..255 } length: { within: 0..255 }
validates :key, validates :key,
presence: true, presence: true,
format: { :with => /ssh-.{3} / },
length: { within: 0..5000 } length: { within: 0..5000 }
before_save :set_identifier before_save :set_identifier
...@@ -50,6 +52,10 @@ class Key < ActiveRecord::Base ...@@ -50,6 +52,10 @@ class Key < ActiveRecord::Base
user.projects user.projects
end end
end end
def last_deploy?
Key.where(identifier: identifier).count == 0
end
end end
# == Schema Information # == Schema Information
# #
......
...@@ -88,8 +88,11 @@ class MergeRequest < ActiveRecord::Base ...@@ -88,8 +88,11 @@ class MergeRequest < ActiveRecord::Base
end end
def unmerged_diffs def unmerged_diffs
commits = project.repo.commits_between(target_branch, source_branch).map {|c| Commit.new(c)} # Only show what is new in the source branch compared to the target branch, not the other way around.
diffs = project.repo.diff(commits.first.prev_commit.id, commits.last.id) rescue [] # The linex below with merge_base is equivalent to diff with three dots (git diff branch1...branch2)
# From the git documentation: "git diff A...B" is equivalent to "git diff $(git-merge-base A B) B"
common_commit = project.repo.git.native(:merge_base, {}, [target_branch, source_branch]).strip
diffs = project.repo.diff(common_commit, source_branch)
end end
def last_commit def last_commit
......
...@@ -28,17 +28,9 @@ class Milestone < ActiveRecord::Base ...@@ -28,17 +28,9 @@ class Milestone < ActiveRecord::Base
end end
def percent_complete def percent_complete
@percent_complete ||= begin ((self.issues.closed.count * 100) / self.issues.count).abs
total_i = self.issues.count rescue ZeroDivisionError
closed_i = self.issues.closed.count 100
if total_i > 0
(closed_i * 100) / total_i
else
100
end
rescue => ex
0
end
end end
def expires_at def expires_at
......
...@@ -2,13 +2,13 @@ require "grit" ...@@ -2,13 +2,13 @@ require "grit"
class Project < ActiveRecord::Base class Project < ActiveRecord::Base
include Repository include Repository
include ProjectPush include PushObserver
include Authority include Authority
include Team include Team
# #
# Relations # Relations
# #
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
...@@ -25,12 +25,12 @@ class Project < ActiveRecord::Base ...@@ -25,12 +25,12 @@ class Project < ActiveRecord::Base
attr_accessor :error_code attr_accessor :error_code
# #
# Protected attributes # Protected attributes
# #
attr_protected :private_flag, :owner_id attr_protected :private_flag, :owner_id
# #
# Scopes # Scopes
# #
scope :public_only, where(private_flag: false) scope :public_only, where(private_flag: false)
...@@ -158,7 +158,7 @@ class Project < ActiveRecord::Base ...@@ -158,7 +158,7 @@ class Project < ActiveRecord::Base
end end
def last_activity def last_activity
events.last || nil events.order("created_at ASC").last
end end
def last_activity_date def last_activity_date
......
class ProtectedBranch < ActiveRecord::Base class ProtectedBranch < ActiveRecord::Base
include GitHost
belongs_to :project belongs_to :project
validates_presence_of :project_id validates_presence_of :project_id
validates_presence_of :name validates_presence_of :name
...@@ -7,7 +9,7 @@ class ProtectedBranch < ActiveRecord::Base ...@@ -7,7 +9,7 @@ class ProtectedBranch < ActiveRecord::Base
after_destroy :update_repository after_destroy :update_repository
def update_repository def update_repository
Gitlab::GitHost.system.update_project(project.path, project) git_host.update_repository(project)
end end
def commit def commit
......
...@@ -7,7 +7,7 @@ class User < ActiveRecord::Base ...@@ -7,7 +7,7 @@ class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, attr_accessible :email, :password, :password_confirmation, :remember_me, :bio,
:name, :projects_limit, :skype, :linkedin, :twitter, :dark_scheme, :name, :projects_limit, :skype, :linkedin, :twitter, :dark_scheme,
:theme_id, :force_random_password :theme_id, :force_random_password, :extern_uid, :provider
attr_accessor :force_random_password attr_accessor :force_random_password
...@@ -54,6 +54,8 @@ class User < ActiveRecord::Base ...@@ -54,6 +54,8 @@ class User < ActiveRecord::Base
validates :bio, length: { within: 0..255 } validates :bio, length: { within: 0..255 }
validates :extern_uid, :allow_blank => true, :uniqueness => {:scope => :provider}
before_save :ensure_authentication_token before_save :ensure_authentication_token
alias_attribute :private_token, :authentication_token alias_attribute :private_token, :authentication_token
...@@ -84,21 +86,31 @@ class User < ActiveRecord::Base ...@@ -84,21 +86,31 @@ class User < ActiveRecord::Base
where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)') where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)')
end end
def self.find_for_ldap_auth(omniauth_info) def self.find_for_ldap_auth(auth, signed_in_resource=nil)
name = omniauth_info.name.force_encoding("utf-8") uid = auth.info.uid
email = omniauth_info.email.downcase unless omniauth_info.email.nil? provider = auth.provider
raise OmniAuth::Error, "LDAP accounts must provide an email address" if email.nil? name = auth.info.name.force_encoding("utf-8")
email = auth.info.email.downcase unless auth.info.email.nil?
raise OmniAuth::Error, "LDAP accounts must provide an uid and email address" if uid.nil? or email.nil?
if @user = User.find_by_email(email) if @user = User.find_by_extern_uid_and_provider(uid, provider)
@user
# workaround for backward compatibility
elsif @user = User.find_by_email(email)
logger.info "Updating legacy LDAP user #{email} with extern_uid => #{uid}"
@user.update_attributes(:extern_uid => uid, :provider => provider)
@user @user
else else
logger.info "Creating user from LDAP login {uid => #{uid}, name => #{name}, email => #{email}}"
password = Devise.friendly_token[0, 8].downcase password = Devise.friendly_token[0, 8].downcase
@user = User.create( @user = User.create(
name: name, :extern_uid => uid,
email: email, :provider => provider,
password: password, :name => name,
password_confirmation: password, :email => email,
projects_limit: Gitlab.config.default_projects_limit :password => password,
:password_confirmation => password,
:projects_limit => Gitlab.config.default_projects_limit
) )
end end
end end
......
class UsersProject < ActiveRecord::Base class UsersProject < ActiveRecord::Base
include GitHost
GUEST = 10 GUEST = 10
REPORTER = 20 REPORTER = 20
DEVELOPER = 30 DEVELOPER = 30
...@@ -58,9 +60,7 @@ class UsersProject < ActiveRecord::Base ...@@ -58,9 +60,7 @@ class UsersProject < ActiveRecord::Base
end end
def update_repository def update_repository
Gitlab::GitHost.system.new.configure do |c| git_host.update_repository(project)
c.update_project(project.path, project)
end
end end
def project_access_human def project_access_human
......
...@@ -9,8 +9,16 @@ class IssueObserver < ActiveRecord::Observer ...@@ -9,8 +9,16 @@ class IssueObserver < ActiveRecord::Observer
def after_update(issue) def after_update(issue)
send_reassigned_email(issue) if issue.is_being_reassigned? send_reassigned_email(issue) if issue.is_being_reassigned?
Note.create_status_change_note(issue, current_user, 'closed') if issue.is_being_closed?
Note.create_status_change_note(issue, current_user, 'reopened') if issue.is_being_reopened? status = nil
status = 'closed' if issue.is_being_closed?
status = 'reopened' if issue.is_being_reopened?
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)
end
end
end end
protected protected
......
class KeyObserver < ActiveRecord::Observer class KeyObserver < ActiveRecord::Observer
include GitHost
def after_save(key) def after_save(key)
key.update_repository git_host.set_key(key.identifier, key.key, key.projects)
end end
def after_destroy(key) def after_destroy(key)
key.repository_delete_key return if key.is_deploy_key && !key.last_deploy?
git_host.remove_key(key.identifier, key.projects)
end end
end end
class UsersProjectObserver < ActiveRecord::Observer
def after_create(users_project)
Notify.project_access_granted_email(users_project.id).deliver
end
def after_update(users_project)
Notify.project_access_granted_email(users_project.id).deliver
end
end
module GitHost
def git_host
Gitlab::Gitolite.new
end
end
module ProjectPush module PushObserver
def observe_push(oldrev, newrev, ref, user) def observe_push(oldrev, newrev, ref, user)
data = post_receive_data(oldrev, newrev, ref, user) data = post_receive_data(oldrev, newrev, ref, user)
......
module Repository module Repository
include GitHost
def valid_repo? def valid_repo?
repo repo
rescue rescue
...@@ -30,26 +32,10 @@ module Repository ...@@ -30,26 +32,10 @@ module Repository
Commit.commits_between(repo, from, to) Commit.commits_between(repo, from, to)
end end
def write_hooks
%w(post-receive).each do |hook|
write_hook(hook, File.read(File.join(Rails.root, 'lib', "#{hook}-hook")))
end
end
def satellite def satellite
@satellite ||= Gitlab::Satellite.new(self) @satellite ||= Gitlab::Satellite.new(self)
end end
def write_hook(name, content)
hook_file = File.join(path_to_repo, 'hooks', name)
File.open(hook_file, 'w') do |f|
f.write(content)
end
File.chmod(0775, hook_file)
end
def has_post_receive_file? def has_post_receive_file?
hook_file = File.join(path_to_repo, 'hooks', 'post-receive') hook_file = File.join(path_to_repo, 'hooks', 'post-receive')
File.exists?(hook_file) File.exists?(hook_file)
...@@ -64,7 +50,7 @@ module Repository ...@@ -64,7 +50,7 @@ module Repository
end end
def url_to_repo def url_to_repo
Gitlab::GitHost.url_to_repo(path) git_host.url_to_repo(path)
end end
def path_to_repo def path_to_repo
...@@ -72,13 +58,11 @@ module Repository ...@@ -72,13 +58,11 @@ module Repository
end end
def update_repository def update_repository
Gitlab::GitHost.system.update_project(path, self) git_host.update_repository(self)
write_hooks if File.exists?(path_to_repo)
end end
def destroy_repository def destroy_repository
Gitlab::GitHost.system.destroy_project(self) git_host.remove_repository(self)
end end
def repo_exists? def repo_exists?
...@@ -133,10 +117,13 @@ module Repository ...@@ -133,10 +117,13 @@ module Repository
storage_path = File.join(Rails.root, "tmp", "repositories", self.code) storage_path = File.join(Rails.root, "tmp", "repositories", self.code)
file_path = File.join(storage_path, file_name) file_path = File.join(storage_path, file_name)
# Put files into a directory before archiving
prefix = self.code + "/"
# Create file if not exists # Create file if not exists
unless File.exists?(file_path) unless File.exists?(file_path)
FileUtils.mkdir_p storage_path FileUtils.mkdir_p storage_path
file = self.repo.archive_to_file(ref, nil, file_path) file = self.repo.archive_to_file(ref, prefix, file_path)
end end
file_path file_path
......
module SshKey
def update_repository
Gitlab::GitHost.system.new.configure do |c|
c.update_keys(identifier, key)
c.update_projects(projects)
end
end
def repository_delete_key
Gitlab::GitHost.system.new.configure do |c|
#delete key file is there is no identically deploy keys
if !is_deploy_key || Key.where(identifier: identifier).count() == 0
c.delete_key(identifier)
end
c.update_projects(projects)
end
end
end
...@@ -35,11 +35,13 @@ ...@@ -35,11 +35,13 @@
%h3 Latest projects %h3 Latest projects
%hr %hr
- @projects.each do |project| - @projects.each do |project|
%h5 %p
= link_to project.name, [:admin, project] = link_to project.name, [:admin, project]
.span6 .span6
%h3 Latest users %h3 Latest users
%hr %hr
- @users.each do |user| - @users.each do |user|
%h5 %p
= link_to user.name, [:admin, user] = link_to [:admin, user] do
= user.name
%small= user.email
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
Read more about system hooks Read more about system hooks
%strong #{link_to "here", help_system_hooks_path, class: "vlink"} %strong #{link_to "here", help_system_hooks_path, class: "vlink"}
= form_for @hook, as: :hook, url: admin_hooks_path do |f| = form_for @hook, as: :hook, url: admin_hooks_path, html: { class: 'form-inline' } do |f|
-if @hook.errors.any? -if @hook.errors.any?
.alert-message.block-message.error .alert-message.block-message.error
- @hook.errors.full_messages.each do |msg| - @hook.errors.full_messages.each do |msg|
......
...@@ -10,19 +10,17 @@ ...@@ -10,19 +10,17 @@
Project name is Project name is
.input .input
= f.text_field :name, placeholder: "Example Project", class: "xxlarge" = f.text_field :name, placeholder: "Example Project", class: "xxlarge"
= f.submit project.new_record? ? 'Create project' : 'Save Project', class: "btn primary"
%hr %hr
.alert.alert-info .adv_settings
%h5 Advanced settings: %h6 Advanced settings:
.clearfix .clearfix
= f.label :path do = f.label :path do
Git Clone Path
.input .input
.input-prepend .input-prepend
%span.add-on= Gitlab.config.ssh_path %strong
= f.text_field :path, placeholder: "example_project", disabled: !!project.id = text_field_tag :ppath, @admin_project.path_to_repo, class: "xlarge", disabled: true
%span.add-on= ".git"
.clearfix .clearfix
= f.label :code do = f.label :code do
URL URL
...@@ -42,8 +40,9 @@ ...@@ -42,8 +40,9 @@
.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? - unless project.new_record?
.alert.alert-info %hr
%h5 Features: .adv_settings
%h6 Features:
.clearfix .clearfix
= f.label :issues_enabled, "Issues" = f.label :issues_enabled, "Issues"
...@@ -63,7 +62,8 @@ ...@@ -63,7 +62,8 @@
- unless project.new_record? - unless project.new_record?
.actions .actions
= f.submit 'Save Project', class: "btn primary" = f.submit 'Save Project', class: "btn save-btn"
= link_to 'Cancel', admin_projects_path, class: "btn cancel-btn"
......
= form_for [:admin, @admin_project] do |f|
- if @admin_project.errors.any?
.alert-message.block-message.error
%span= @admin_project.errors.full_messages.first
.clearfix.project_name_holder
= f.label :name do
Project name is
.input
= f.text_field :name, placeholder: "Example Project", class: "xxlarge"
= f.submit 'Create project', class: "btn primary project-submit"
%hr
%div.adv_settings
%h6 Advanced settings:
.clearfix
= f.label :path do
Git Clone
.input
.input-prepend
%span.add-on= Gitlab.config.ssh_path
= f.text_field :path, placeholder: "example_project", disabled: !@admin_project.new_record?
%span.add-on= ".git"
.clearfix
= f.label :code do
URL
.input
.input-prepend
%span.add-on= web_app_url
= f.text_field :code, placeholder: "example"
%h3 %h3.page_title
Projects Projects
= link_to 'New Project', new_admin_project_path, class: "btn small right" = link_to 'New Project', new_admin_project_path, class: "btn small right"
%br %br
= form_tag admin_projects_path, method: :get do = form_tag admin_projects_path, method: :get, class: 'form-inline' do
= text_field_tag :name, params[:name], class: "xlarge" = text_field_tag :name, params[:name], class: "xlarge"
= submit_tag "Search", class: "btn submit primary" = submit_tag "Search", class: "btn submit primary"
......
%h3.page_title New project .project_new_holder
%hr %h3.page_title
= render 'form', project: @admin_project New Project
%hr
= render 'new_form'
%div.save-project-loader.hide
%center
= image_tag "ajax_loader.gif"
%h3 Creating project &amp; repository. Please wait a few minutes
:javascript
$(function(){ new Projects(); });
...@@ -2,71 +2,79 @@ ...@@ -2,71 +2,79 @@
= form_for [:admin, @admin_user] do |f| = form_for [:admin, @admin_user] do |f|
-if @admin_user.errors.any? -if @admin_user.errors.any?
#error_explanation #error_explanation
%ul %ul.unstyled.alert.alert-error
- @admin_user.errors.full_messages.each do |msg| - @admin_user.errors.full_messages.each do |msg|
%li= msg %li= msg
.row .row
.span6 .span7
.clearfix .ui-box
= f.label :name %br
.input
= f.text_field :name
%span.help-inline * required
.clearfix
= f.label :email
.input
= f.text_field :email
%span.help-inline * required
%hr
-if f.object.new_record?
.clearfix
= f.label :admin, class: "checkbox" do
= f.check_box :force_random_password, {}, true, nil
%span Generate random password
%div.password-fields
.clearfix .clearfix
= f.label :password = f.label :name
.input= f.password_field :password, disabled: f.object.force_random_password .input
= f.text_field :name
%span.help-inline * required
.clearfix .clearfix
= f.label :password_confirmation = f.label :email
.input= f.password_field :password_confirmation, disabled: f.object.force_random_password .input
%hr = f.text_field :email
.clearfix %span.help-inline * required
= f.label :skype %hr
.input= f.text_field :skype -if f.object.new_record?
.clearfix .clearfix
= f.label :linkedin = f.label :force_random_password do
.input= f.text_field :linkedin %span Generate random password
.clearfix .input= f.check_box :force_random_password, {}, true, nil
= f.label :twitter
.input= f.text_field :twitter %div.password-fields
.span6 .clearfix
.clearfix = f.label :password
= f.label :projects_limit .input= f.password_field :password, disabled: f.object.force_random_password
.input= f.text_field :projects_limit, class: "small_input" .clearfix
= f.label :password_confirmation
.input= f.password_field :password_confirmation, disabled: f.object.force_random_password
%hr
.clearfix
= f.label :skype
.input= f.text_field :skype
.clearfix
= f.label :linkedin
.input= f.text_field :linkedin
.clearfix
= f.label :twitter
.input= f.text_field :twitter
.span5
.ui-box
%br
.clearfix
= f.label :projects_limit
.input= f.number_field :projects_limit
.alert
.clearfix .clearfix
%p Make the user a GitLab administrator. = f.label :admin do
= f.label :admin, class: "checkbox" do %strong.cred Administrator
= f.check_box :admin .input= f.check_box :admin
%span Administrator - unless @admin_user.new_record?
- unless @admin_user.new_record? %hr
.alert.alert-error .padded.cred
- if @admin_user.blocked - if @admin_user.blocked
%span %span
= link_to 'Unblock', unblock_admin_user_path(@admin_user), method: :put, class: "btn small" This user is blocked and is not able to login to GitLab
This user is blocked and is not able to login to GitLab .clearfix
- else = link_to 'Unblock User', unblock_admin_user_path(@admin_user), method: :put, class: "btn small right"
%span - else
= link_to 'Block', block_admin_user_path(@admin_user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small danger" %span
Blocked users will be removed from all projects &amp; will not be able to login to GitLab. Blocked users will be removed from all projects &amp; will not be able to login to GitLab.
.clearfix
= link_to 'Block User', block_admin_user_path(@admin_user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small right danger"
.row
.span6
.span6
.actions .actions
= f.submit 'Save', class: "btn primary" = f.submit 'Save', class: "btn save-btn"
- if @admin_user.new_record? - if @admin_user.new_record?
= link_to 'Cancel', admin_users_path, class: "btn" = link_to 'Cancel', admin_users_path, class: "btn cancel-btn"
- else - else
= link_to 'Cancel', admin_user_path(@admin_user), class: "btn" = link_to 'Cancel', admin_user_path(@admin_user), class: "btn cancel-btn"
%h3= @admin_user.name %h3.page_title #{@admin_user.name} &rarr; Edit user
%hr %hr
= render 'form' = render 'form'
%h3 %h3.page_title
Users Users
= 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
= form_tag admin_users_path, method: :get do = form_tag admin_users_path, method: :get, class: 'form-inline' do
= text_field_tag :name, params[:name], class: "xlarge" = text_field_tag :name, params[:name], class: "xlarge"
= submit_tag "Search", class: "btn submit primary" = submit_tag "Search", class: "btn submit primary"
%ul.nav.nav-pills %ul.nav.nav-pills
......
%h2 New user %h3.page_title New user
%hr %br
= render 'form' = render 'form'
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
%strong= link_to "Browse Code »", tree_project_ref_path(@project, commit.id), class: "right" %strong= link_to "Browse Code »", tree_project_ref_path(@project, commit.id), class: "right"
%p %p
= link_to commit.short_id(8), project_commit_path(@project, id: commit.id), class: "commit_short_id" = link_to commit.short_id(8), project_commit_path(@project, id: commit.id), class: "commit_short_id"
%strong.cgray= commit.author_name %strong.commit-author-name= commit.author_name
&ndash; %span.dash &ndash;
= image_tag gravatar_icon(commit.author_email), class: "avatar", width: 16 = image_tag gravatar_icon(commit.author_email), class: "avatar", width: 16
= link_to_gfm truncate(commit.title, length: 50), project_commit_path(@project, id: commit.id), class: "row_title" = link_to_gfm truncate(commit.title, length: 50), project_commit_path(@project, id: commit.id), class: "row_title"
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
= "..." = "..."
= text_field_tag :to, params[:to], placeholder: "aa8b4ef", class: "xlarge" = text_field_tag :to, params[:to], placeholder: "aa8b4ef", class: "xlarge"
.actions .actions
= submit_tag "Compare", class: "btn btn-primary" = submit_tag "Compare", class: "btn primary"
- unless @commits.empty? - unless @commits.empty?
......
...@@ -5,12 +5,6 @@ ...@@ -5,12 +5,6 @@
:javascript :javascript
$(document).ready(function(){ $(function(){
$(".line_note_link, .line_note_reply_link").live("click", function(e) { PerLineNotes.init();
var form = $(".per_line_form");
$(this).parent().parent().after(form);
form.find("#note_line_code").val($(this).attr("line_code"));
form.show();
return false;
});
}); });
- if @projects.any? - if @projects.any?
.projects .projects
.activities.span8 .activities.span8
- if current_user.require_ssh_key? = render 'shared/no_ssh'
.alert.alert-error.padded
%span
You wont be able to pull/push project code unless you
%strong
= link_to new_key_path, class: "vlink" do
add new key
to your profile
- if @events.any? - if @events.any?
.content_list= render @events .content_list= render @events
- else - else
...@@ -26,13 +19,16 @@ ...@@ -26,13 +19,16 @@
= link_to new_project_path, class: "btn very_small info" do = link_to new_project_path, class: "btn very_small info" do
%i.icon-plus %i.icon-plus
New Project New Project
- @projects.each do |project| %ul.unstyled
= link_to project_path(project), class: dom_class(project) do - @projects.each do |project|
%h4 %li.wll
%span.ico.project = link_to project_path(project), class: dom_class(project) do
= truncate(project.name, length: 25) %strong.project_name= truncate(project.name, length: 25)
%span.right %span.arrow
&rarr; &rarr;
%span.last_activity
%strong Last activity:
%span= project_last_activity(project)
.bottom= paginate @projects, theme: "gitlab" .bottom= paginate @projects, theme: "gitlab"
%hr %hr
...@@ -57,5 +53,5 @@ ...@@ -57,5 +53,5 @@
If you will be added to project - it will be displayed here If you will be added to project - it will be displayed here
:javascript :javascript
$(function(){ Pager.init(20); }); $(function(){ Pager.init(20); });
...@@ -21,7 +21,5 @@ ...@@ -21,7 +21,5 @@
Permissions: Permissions:
%pre %pre
= preserve do = preserve do
sudo chmod -R 770 /home/git/repositories/ sudo chmod -R 770 #{Gitlab.config.git_base_path}
sudo chown -R git:git /home/git/repositories/ sudo chown -R git:git #{Gitlab.config.git_base_path}
sudo chown gitlab:gitlab /home/git/repositories/**/hooks/post-receive
%h1 Git Error
%hr
%p Seems like SSH Key you provided is not a valid SSH key.
...@@ -9,5 +9,5 @@ ...@@ -9,5 +9,5 @@
at at
%strong= link_to event.project.name, event.project %strong= link_to event.project.name, event.project
= link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn very_small primary" do = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn very_small" do
Create Merge Request Create Merge Request
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
%a{href: "#README"} README %a{href: "#README"} README
%li %li
%a{href: "#projects"} Projects %a{href: "#projects"} Projects
%li
%a{href: "#snippets"} Snippets
%li %li
%a{href: "#users"} Users %a{href: "#users"} Users
%li %li
...@@ -34,6 +36,16 @@ ...@@ -34,6 +36,16 @@
%br %br
.file_holder#snippets
.file_title
%i.icon-file
Projects Snippets
.file_content.wiki
= preserve do
= markdown File.read(Rails.root.join("doc", "api", "snippets.md"))
%br
.file_holder#users .file_holder#users
.file_title .file_title
%i.icon-file %i.icon-file
...@@ -51,3 +63,13 @@ ...@@ -51,3 +63,13 @@
.file_content.wiki .file_content.wiki
= preserve do = preserve do
= markdown File.read(Rails.root.join("doc", "api", "issues.md")) = markdown File.read(Rails.root.join("doc", "api", "issues.md"))
%br
.file_holder#milestones
.file_title
%i.icon-file
Milestones
.file_content.wiki
= preserve do
= markdown File.read(Rails.root.join("doc", "api", "milestones.md"))
...@@ -31,3 +31,6 @@ ...@@ -31,3 +31,6 @@
%li %li
%h5= link_to "Gitlab Markdown", help_markdown_path %h5= link_to "Gitlab Markdown", help_markdown_path
%li
%h5= link_to "SSH keys", help_ssh_path
- bash_lexer = Pygments::Lexer[:bash] %h3.page_title Gitlab Flavored Markdown
%h3.page_title Gitlab Markdown
.back_link .back_link
= link_to help_path do = link_to help_path do
&larr; to index &larr; to index
%hr %hr
%p.slead We extend Markdown with some GITLAB specific syntax. It allows you to link to: .row
.span8
%p
For Gitlab we developed something we call "Gitlab Flavored Markdown" (GFM).
It extends the standard Markdown in a few significant ways adds some useful functionality.
%ul %p You can use GFM in:
%li issues (#123) %ul
%li merge request (!123) %li commit messages
%li commits (1234567) %li comments
%li team members (@foo) %li wall posts
%li snippets ($123) %li issues
%li merge requests
%li milestones
%li wiki pages
%p.slead in %h3 Differences from traditional Markdown
%ul %h4 Newlines
%li commit messages
%li notes/comments/wall posts %p
%li issues The biggest difference that GFM introduces is in the handling of linebreaks.
%li merge requests With traditional Markdown you can hard wrap paragraphs of text and they will be combined into a single paragraph. We find this to be the cause of a huge number of unintentional formatting errors.
%li milestones GFM treats newlines in paragraph-like content as real line breaks, which is probably what you intended.
%li wiki pages
%p The next paragraph contains two phrases separated by a single newline character:
%pre= "Roses are red\nViolets are blue"
%p becomes
= markdown "Roses are red\nViolets are blue"
%h4 Multiple underscores in words
%p
It is not reasonable to italicize just <em>part</em> of a word, especially when you're dealing with code and names often appear with multiple underscores.
Therefore, GFM ignores multiple underscores in words.
%pre= "perform_complicated_task\ndo_this_and_do_that_and_another_thing"
%p becomes
= markdown "perform_complicated_task\ndo_this_and_do_that_and_another_thing"
%h4 URL autolinking
%p
GFM will autolink standard URLs you copy and paste into your text.
So if you want to link to a URL (instead of a textual link), you can simply put the URL in verbatim and it will be turned into a link to that URL.
%h4 Fenced code blocks
%p
Markdown converts text with four spaces at the front of each line to code blocks.
GFM supports that, but we also support fenced blocks.
Just wrap your code blocks in <code>```</code> and you won't need to indent manually to trigger a code block.
%pre= %Q{```ruby\nrequire 'redcarpet'\nmarkdown = Redcarpet.new("Hello World!")\nputs markdown.to_html\n```}
%p becomes
= markdown %Q{```ruby\nrequire 'redcarpet'\nmarkdown = Redcarpet.new("Hello World!")\nputs markdown.to_html\n```}
%h4 Special Gitlab references
%p
GFM recognizes special references.
You can easily reference e.g. a team member, an issue or a commit within a project.
GFM will turn that reference into a link so you can navigate between them easily.
%p GFM will recognize the following references:
%ul
%li
%code @foo
for team members
%li
%code #123
for issues
%li
%code !123
for merge request
%li
%code $123
for snippets
%li
%code 1234567
for commits
-# this example will only be shown if the user has a project with at least one issue
- if @project = current_user.projects.first
- if issue = @project.issues.first
%p For example in your #{link_to @project.name, project_path(@project)} project something like
%pre= "This is related to ##{issue.id}. @#{current_user.name} is working on solving it."
%p becomes
= markdown "This is related to ##{issue.id}. @#{current_user.name} is working on solving it."
.span4.right
.alert.alert-info
%p
If you're not already familiar with Markdown, you should spend 15 minutes and go over the excellent
%strong= link_to "Markdown Syntax Guide", "http://daringfireball.net/projects/markdown/syntax"
at Daring Fireball.
%h3 Permissions %h3.page_title Permissions
.back_link .back_link
= link_to help_path do = link_to help_path do
&larr; to index &larr; to index
%hr %hr
......
%h3.page_title SSH Keys
.back_link
= link_to help_path do
&larr; to index
%hr
%p.slead
SSH key allows you to establish a secure connection between your computer and Gitlab
%p.slead
To generate a new SSH key just open your terminal and use code below.
%pre.dark
ssh-keygen -t rsa -C "#{current_user.email}"
\# Creates a new ssh key using the provided email
\# Generating public/private rsa key pair...
%p.slead
Next just use code below to dump your public key and add to GITLAB SSH Keys
%pre.dark
cat ~/.ssh/id_rsa.pub
\# ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6eNtGpNGwstc....
%h3 System hooks %h3 System hooks
.back_link .back_link
= link_to :back do = link_to :back do
&larr; back &larr; back
%hr %hr
%p.slead %p.slead
Your Gitlab instance can perform HTTP POST request on next event: create_project, delete_project, create_user, delete_user, change_team_member. Your Gitlab instance can perform HTTP POST request on next event: create_project, delete_project, create_user, delete_user, change_team_member.
%br %br
System Hooks can be used for logging or change information in LDAP server. System Hooks can be used for logging or change information in LDAP server.
......
%h3 Web hooks %h3.page_title Web hooks
.back_link .back_link
= link_to help_path do = link_to help_path do
&larr; to index &larr; to index
%hr %hr
%p.slead %p.slead
Every Gitlab project can trigger a web server whenever the repo is pushed to. Every Gitlab project can trigger a web server whenever the repo is pushed to.
%br %br
Web Hooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server. Web Hooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server.
%br %br
......
- bash_lexer = Pygments::Lexer[:bash] %h3.page_title Workflow
%h3 Workflow
.back_link .back_link
= link_to help_path do = link_to help_path do
&larr; to index &larr; to index
%hr %hr
...@@ -9,25 +8,25 @@ ...@@ -9,25 +8,25 @@
%li %li
%p Clone project %p Clone project
.bash .bash
%pre %pre.dark
git clone git@example.com:project-name.git git clone git@example.com:project-name.git
%li %li
%p Create branch with your feature %p Create branch with your feature
.bash .bash
%pre %pre.dark
git checkout -b $feature_name git checkout -b $feature_name
%li %li
%p Write code. Commit changes %p Write code. Commit changes
.bash .bash
%pre %pre.dark
git commit -am "My feature is ready" git commit -am "My feature is ready"
%li %li
%p Push your branch to gitlabhq %p Push your branch to gitlabhq
.bash .bash
%pre %pre.dark
git push origin $feature_name git push origin $feature_name
%li %li
......
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
} }
} }
], ],
total_commits_count => 3 total_commits_count => 4
} }
eos eos
%> %>
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
Read more about web hooks Read more about web hooks
%strong #{link_to "here", help_web_hooks_path, class: "vlink"} %strong #{link_to "here", help_web_hooks_path, class: "vlink"}
= form_for [@project, @hook], as: :hook, url: project_hooks_path(@project) do |f| = form_for [@project, @hook], as: :hook, url: project_hooks_path(@project), html: { class: 'form-inline' } do |f|
-if @hook.errors.any? -if @hook.errors.any?
.alert-message.block-message.error .alert-message.block-message.error
- @hook.errors.full_messages.each do |msg| - @hook.errors.full_messages.each do |msg|
......
...@@ -38,19 +38,20 @@ ...@@ -38,19 +38,20 @@
= f.label :description, "Details" = f.label :description, "Details"
.input .input
= f.text_area :description, maxlength: 2000, class: "xxlarge", rows: 14 = f.text_area :description, maxlength: 2000, class: "xxlarge", rows: 14
%p.hint Markdown is enabled. %p.hint Issues are parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}.
.actions .actions
- if @issue.new_record? - if @issue.new_record?
= f.submit 'Submit new issue', class: "primary btn" = f.submit 'Submit new issue', class: "btn save-btn"
-else -else
= f.submit 'Save changes', class: "primary btn" = f.submit 'Save changes', class: "save-btn btn"
- cancel_class = 'btn cancel-btn'
- if request.xhr? - if request.xhr?
= link_to "Cancel", "#back", onclick: "backToIssues();", class: "btn" = link_to "Cancel", "#back", onclick: "backToIssues();", class: cancel_class
- else - else
- if @issue.new_record? - if @issue.new_record?
= link_to "Cancel", project_issues_path(@project), class: "btn" = link_to "Cancel", project_issues_path(@project), class: cancel_class
- else - else
= link_to "Cancel", project_issue_path(@project, @issue), class: "btn" = link_to "Cancel", project_issue_path(@project, @issue), class: cancel_class
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
%li{class: "#{'active' if current_page?(project_milestones_path(@project))}"} %li{class: "#{'active' if current_page?(project_milestones_path(@project))}"}
= link_to project_milestones_path(@project), class: "tab" do = link_to project_milestones_path(@project), class: "tab" do
Milestones Milestones
%li{class: "#{'active' if current_page?(project_labels_path(@project))}"}
= link_to project_labels_path(@project), class: "tab" do
Labels
%li.right %li.right
%span.rss-icon %span.rss-icon
= link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do = link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
.right .right
.span5 .span5
- if can? current_user, :write_issue, @project - if can? current_user, :write_issue, @project
= link_to new_project_issue_path(@project), class: "right btn small", title: "New Issue", remote: true do = link_to new_project_issue_path(@project), class: "right btn", title: "New Issue", remote: true do
%i.icon-plus %i.icon-plus
New Issue New Issue
= form_tag search_project_issues_path(@project), method: :get, remote: true, id: "issue_search_form", class: :right do = form_tag search_project_issues_path(@project), method: :get, remote: true, id: "issue_search_form", class: :right do
......
...@@ -11,8 +11,14 @@ ...@@ -11,8 +11,14 @@
.input= f.text_field :title .input= f.text_field :title
.clearfix .clearfix
= f.label :key = f.label :key
.input= f.text_area :key, class: [:xxlarge, :thin_area] .input
= f.text_area :key, class: [:xxlarge, :thin_area]
%p.hint
Paste your public key here. Read more about how generate it
= link_to "here", help_ssh_path
.actions .actions
= f.submit 'Save', class: "primary btn" = f.submit 'Save', class: "btn save-btn"
= link_to "Cancel", keys_path, class: "btn" = link_to "Cancel", keys_path, class: "btn cancel-btn"
%h3.page_title %h3.page_title
SSH Keys SSH Keys
= link_to "Add new", new_key_path, class: "btn small right" = link_to "Add new", new_key_path, class: "btn right"
%hr %hr
%p.slead %p.slead
......
%h3.page_title New key %h3.page_title Add an SSH Key
%hr %hr
= render 'form' = render 'form'
......
%li.wll
%strong= label.name
.right
%span= pluralize label.count, 'issue'
= render "issues/head"
%h3.page_title
Labels
%br
%div.ui-box
%ul.unstyled.labels-table
- @labels.each do |label|
= render 'label', label: label
- unless @labels.present?
%li
%h3.nothing_here_message Nothing to show here
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
%br %br
.row .row
.span6 .span5
.mr_branch_box .mr_branch_box
%h5 From (Head Branch) %h5 From (Head Branch)
.body .body
...@@ -17,10 +17,11 @@ ...@@ -17,10 +17,11 @@
= f.label :source_branch, "From", class: "control-label" = f.label :source_branch, "From", class: "control-label"
.controls .controls
= f.select(:source_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px") = f.select(:source_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px")
.bottom_commit .mr_source_commit
.mr_source_commit
.span6 .span2
%center= image_tag "merge.png", class: 'mr_direction_tip'
.span5
.mr_branch_box .mr_branch_box
%h5 To (Base Branch) %h5 To (Base Branch)
.body .body
...@@ -28,8 +29,7 @@ ...@@ -28,8 +29,7 @@
= f.label :target_branch, "To", class: "control-label" = f.label :target_branch, "To", class: "control-label"
.controls .controls
= f.select(:target_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px") = f.select(:target_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px")
.bottom_commit .mr_target_commit
.mr_target_commit
%h4.cdark 2. Fill info %h4.cdark 2. Fill info
...@@ -48,18 +48,19 @@ ...@@ -48,18 +48,19 @@
.control-group .control-group
.form-actions .form-actions
= f.submit 'Save', class: "btn-primary btn" = f.submit 'Save', class: "btn save-btn"
- if @merge_request.new_record? - if @merge_request.new_record?
= link_to project_merge_requests_path(@project), class: "btn" do = link_to project_merge_requests_path(@project), class: "btn cancel-btn" do
Cancel Cancel
- else - else
= link_to project_merge_request_path(@project, @merge_request), class: "btn" do = link_to project_merge_request_path(@project, @merge_request), class: "btn cancel-btn" do
Cancel Cancel
:javascript :javascript
$(function(){ $(function(){
disableButtonIfEmtpyField("#merge_request_title", ".save-btn");
$('select#merge_request_assignee_id').chosen(); $('select#merge_request_assignee_id').chosen();
$('select#merge_request_source_branch').chosen(); $('select#merge_request_source_branch').chosen();
$('select#merge_request_target_branch').chosen(); $('select#merge_request_target_branch').chosen();
......
%h3.page_title %h3.page_title
Merge Requests Merge Requests
- if can? current_user, :write_issue, @project - if can? current_user, :write_issue, @project
= link_to new_project_merge_request_path(@project), class: "right btn small", title: "New Merge Request" do = link_to new_project_merge_request_path(@project), class: "right btn", title: "New Merge Request" do
New Merge Request New Merge Request
%br %br
...@@ -10,17 +10,17 @@ ...@@ -10,17 +10,17 @@
.ui-box .ui-box
.title .title
%ul.nav.nav-pills %ul.nav.nav-pills
%li{class: ("active" if (params[:f] == "0" || !params[:f]))} %li{class: ("active" if (params[:f] == 'open' || !params[:f]))}
= link_to project_merge_requests_path(@project, f: 0) do = link_to project_merge_requests_path(@project, f: 'open') do
Open Open
%li{class: ("active" if params[:f] == "2")} %li{class: ("active" if params[:f] == "closed")}
= link_to project_merge_requests_path(@project, f: 2) do = link_to project_merge_requests_path(@project, f: "closed") do
Closed Closed
%li{class: ("active" if params[:f] == "3")} %li{class: ("active" if params[:f] == 'assigned-to-me')}
= link_to project_merge_requests_path(@project, f: 3) do = link_to project_merge_requests_path(@project, f: 'assigned-to-me') do
To Me To Me
%li{class: ("active" if params[:f] == "1")} %li{class: ("active" if params[:f] == 'all')}
= link_to project_merge_requests_path(@project, f: 1) do = link_to project_merge_requests_path(@project, f: 'all') do
All All
%ul.unstyled %ul.unstyled
......
...@@ -3,13 +3,12 @@ ...@@ -3,13 +3,12 @@
%a.close{href: "#"} × %a.close{href: "#"} ×
%h3 How To Merge %h3 How To Merge
.modal-body .modal-body
%pre %pre.dark
= preserve do = preserve do
:erb git checkout #{@merge_request.target_branch}
git checkout <%= @merge_request.target_branch %> git fetch origin
git fetch origin git merge origin/#{@merge_request.source_branch}
git merge origin/<%= @merge_request.source_branch %> git push origin #{@merge_request.target_branch}
git push origin <%= @merge_request.target_branch %>
:javascript :javascript
......
%h3.page_title %h3.page_title
= "Merge Request ##{@merge_request.id}:" = "Merge Request ##{@merge_request.id}:"
&nbsp; &nbsp;
%span.pretty_label.branch= @merge_request.source_branch %span.label_branch= @merge_request.source_branch
&rarr; &rarr;
%span.pretty_label.branch= @merge_request.target_branch %span.label_branch= @merge_request.target_branch
%span.right %span.right
- if @merge_request.merged? - if @merge_request.merged?
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
= f.label :description, "Description", class: "control-label" = f.label :description, "Description", class: "control-label"
.controls .controls
= f.text_area :description, maxlength: 2000, class: "input-xlarge", rows: 10 = f.text_area :description, maxlength: 2000, class: "input-xlarge", rows: 10
%p.hint Markdown is enabled. %p.hint Milestones are parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}.
.span6 .span6
.control-group .control-group
= f.label :due_date, "Due Date", class: "control-label" = f.label :due_date, "Due Date", class: "control-label"
...@@ -32,20 +32,16 @@ ...@@ -32,20 +32,16 @@
.form-actions .form-actions
- if @milestone.new_record? - if @milestone.new_record?
= f.submit 'Create milestone', class: "primary btn" = f.submit 'Create milestone', class: "save-btn btn"
= link_to "Cancel", project_milestones_path(@project), class: "btn cancel-btn"
-else -else
= f.submit 'Save changes', class: "primary btn" = f.submit 'Save changes', class: "save-btn btn"
= link_to "Cancel", project_milestone_path(@project, @milestone), class: "btn cancel-btn"
- if request.xhr?
= link_to "Cancel", "#back", onclick: "backToIssues();", class: "btn"
- else
- if @milestone.new_record?
= link_to "Cancel", project_milestones_path(@project), class: "btn"
- else
= link_to "Cancel", project_milestone_path(@project, @milestone), class: "btn"
:javascript :javascript
$(function() { $(function() {
disableButtonIfEmtpyField("#milestone_title", ".save-btn");
$( ".datepicker" ).datepicker({ $( ".datepicker" ).datepicker({
dateFormat: "yy-mm-dd", dateFormat: "yy-mm-dd",
onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
......
...@@ -8,11 +8,11 @@ ...@@ -8,11 +8,11 @@
%div.ui-box %div.ui-box
.title .title
%ul.nav.nav-pills %ul.nav.nav-pills
%li{class: ("active" if (params[:f] == "0" || !params[:f]))} %li{class: ("active" if (params[:f] == "active" || !params[:f]))}
= link_to project_milestones_path(@project, f: 0) do = link_to project_milestones_path(@project, f: "active") do
Active Active
%li{class: ("active" if params[:f] == "1")} %li{class: ("active" if params[:f] == "all")}
= link_to project_milestones_path(@project, f: 1) do = link_to project_milestones_path(@project, f: "all") do
All All
%ul.unstyled %ul.unstyled
......
- if note.valid? - if note.valid?
:plain :plain
$("#new_note .errors").remove(); $(".note-form-holder .error").remove();
$('#new_note textarea').val(""); $('.note-form-holder textarea').val("");
$('.note-form-holder #preview-link').text('Preview');
$('.note-form-holder #preview-note').hide();
$('.note-form-holder').show();
NoteList.prepend(#{note.id}, "#{escape_javascript(render partial: "notes/show", locals: {note: note})}"); NoteList.prepend(#{note.id}, "#{escape_javascript(render partial: "notes/show", locals: {note: note})}");
- else - else
:plain :plain
$("#new_note").replaceWith("#{escape_javascript(render('form'))}"); $(".note-form-holder").replaceWith("#{escape_javascript(render('form'))}");
- if note.valid? - if note.valid?
:plain :plain
$(".per_line_form").hide(); $(".per_line_form").hide();
$('#new_note textarea').val(""); $('.line-note-form-holder textarea').val("");
$("a.line_note_reply_link[line_code='#{note.line_code}']").closest("tr").remove(); $("a.line_note_reply_link[line_code='#{note.line_code}']").closest("tr").remove();
var trEl = $(".#{note.line_code}").parent(); var trEl = $(".#{note.line_code}").parent();
trEl.after("#{escape_javascript(render partial: "notes/per_line_show", locals: {note: note})}"); trEl.after("#{escape_javascript(render partial: "notes/per_line_show", locals: {note: note})}");
......
= form_for [@project, @note], remote: "true", multipart: true do |f| .note-form-holder
%h3.page_title Leave a comment = form_for [@project, @note], remote: "true", multipart: true do |f|
-if @note.errors.any? %h3.page_title Leave a comment
.alert-message.block-message.error -if @note.errors.any?
- @note.errors.full_messages.each do |msg| .alert-message.block-message.error
%div= msg - @note.errors.full_messages.each do |msg|
%div= msg
= f.hidden_field :noteable_id = f.hidden_field :noteable_id
= f.hidden_field :noteable_type = f.hidden_field :noteable_type
= f.text_area :note, size: 255 = f.text_area :note, size: 255, class: 'note-text'
#preview-note.well.hide #preview-note.preview_note.hide
%p.hint .hint
= link_to "Gitlab Markdown", help_markdown_path, target: '_blank' .right Comments are parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}.
is enabled. .clearfix
= link_to 'Preview', preview_project_notes_path(@project), id: 'preview-link'
.row.note_advanced_opts.hide .row.note_advanced_opts.hide
.span2 .span3
= f.submit 'Add Comment', class: "btn primary submit_note", id: "submit_note" = f.submit 'Add Comment', class: "btn success submit_note grouped", id: "submit_note"
.span4.notify_opts = link_to 'Preview', preview_project_notes_path(@project), class: 'btn grouped', id: 'preview-link'
%h6.left Notify via email: .span4.notify_opts
= label_tag :notify do %h6.left Notify via email:
= check_box_tag :notify, 1, @note.noteable_type != "Commit" = label_tag :notify do
%span Project team = check_box_tag :notify, 1, @note.noteable_type != "Commit"
%span Project team
- if @note.notify_only_author?(current_user) - if @note.notify_only_author?(current_user)
= label_tag :notify_author do = label_tag :notify_author do
= check_box_tag :notify_author, 1 , @note.noteable_type == "Commit" = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
%span Commit author %span Commit author
.span6.attachments .span5.attachments
%h6.left Attachment: %h6.left Attachment:
%span.file_name File name... %span.file_name File name...
.input.input_file .input.input_file
%a.file_upload.btn.small Upload File %a.file_upload.btn.small Upload File
= f.file_field :attachment, class: "input-file" = f.file_field :attachment, class: "input-file"
%span.hint Any file less than 10 MB %span.hint Any file less than 10 MB
%table{style: "display:none;"} %table{style: "display:none;"}
%tr.per_line_form %tr.per_line_form
%td{colspan: 3 } %td{colspan: 3 }
= form_for [@project, @note], remote: "true", multipart: true do |f| .line-note-form-holder
%h3.page_title Leave a note = form_for [@project, @note], remote: "true", multipart: true do |f|
%div.span10 %h3.page_title Leave a note
-if @note.errors.any? %div.span10
.alert-message.block-message.error -if @note.errors.any?
- @note.errors.full_messages.each do |msg| .alert-message.block-message.error
%div= msg - @note.errors.full_messages.each do |msg|
%div= msg
= f.hidden_field :noteable_id = f.hidden_field :noteable_id
= f.hidden_field :noteable_type = f.hidden_field :noteable_type
= f.hidden_field :line_code = f.hidden_field :line_code
= f.text_area :note, size: 255 = f.text_area :note, size: 255, class: 'line-note-text'
.note_actions .note_actions
.buttons .buttons
= f.submit 'Add note', class: "btn primary submit_note", id: "submit_note" = f.submit 'Add note', class: "btn save-btn submit_note submit_inline_note", id: "submit_note"
= link_to "Cancel", "#", class: "btn hide-button" = link_to "Cancel", "#", class: "btn hide-button"
.options .options
%h6.left Notify via email: %h6.left Notify via email:
.labels .labels
= label_tag :notify do = label_tag :notify do
= check_box_tag :notify, 1, @note.noteable_type != "Commit" = check_box_tag :notify, 1, @note.noteable_type != "Commit"
%span Project team %span Project team
- if @note.notify_only_author?(current_user) - if @note.notify_only_author?(current_user)
= label_tag :notify_author do = label_tag :notify_author do
= check_box_tag :notify_author, 1 , @note.noteable_type == "Commit" = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
%span Commit author %span Commit author
:javascript :javascript
$(function(){ $(function(){
......
%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"}
%table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"}
%tr
%td{style: "font-size: 1px; line-height: 1px;", width: "21"}
%td{align: "left", style: "padding: 20px 0 0;"}
%h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
= "Issue was #{@issue_status} by #{@updated_by.name}"
%td{style: "font-size: 1px; line-height: 1px;", width: "21"}
%tr
%td{style: "font-size: 1px; line-height: 1px;", width: "21"}
%td{align: "left", style: "padding: 20px 0 0;"}
%h2{style: "color:#646464 !important; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
= "Issue ##{@issue.id}"
= link_to_gfm truncate(@issue.title, length: 45), project_issue_url(@issue.project, @issue), title: @issue.title
%br
%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"}
%table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"}
%tr
%td{style: "font-size: 1px; line-height: 1px;", width: "21"}
%td{align: "left", style: "padding: 20px 0 0;"}
%h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
= "You got granted #{@users_project.project_access_human} access to project"
%td{style: "font-size: 1px; line-height: 1px;", width: "21"}
%tr
%td{style: "font-size: 1px; line-height: 1px;", width: "21"}
%td{align: "left", style: "padding: 20px 0 0;"}
%h2{style: "color:#646464 !important; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
= link_to_gfm truncate(@project.name, length: 45), project_url(@project), title: @project.name
%br
...@@ -16,4 +16,4 @@ ...@@ -16,4 +16,4 @@
= f.label :password_confirmation = f.label :password_confirmation
.input= f.password_field :password_confirmation .input= f.password_field :password_confirmation
.actions .actions
= f.submit 'Save', class: "btn primary" = f.submit 'Save', class: "btn save-btn"
...@@ -45,9 +45,10 @@ ...@@ -45,9 +45,10 @@
%span.help-block Tell us about yourself in fewer than 250 characters. %span.help-block Tell us about yourself in fewer than 250 characters.
.span5.right .span5.right
%p.alert.alert-info -unless Gitlab.config.disable_gravatar?
%strong Tip: %p.alert.alert-info
You can change your avatar at gravatar.com %strong Tip:
You can change your avatar at gravatar.com
%h4 %h4
Personal projects: Personal projects:
...@@ -66,4 +67,4 @@ ...@@ -66,4 +67,4 @@
= link_to "Add Public Key", new_key_path, class: "btn small right" = link_to "Add Public Key", new_key_path, class: "btn small right"
.form-actions .form-actions
= f.submit 'Save', class: "btn-primary btn" = f.submit 'Save', class: "btn save-btn"
.project_clone_panel
.row
.span7
.form-horizontal
.input-prepend.project_clone_holder
= link_to "SSH", "#", class: "btn small active", :"data-clone" => @project.ssh_url_to_repo
= link_to "HTTP", "#", class: "btn small", :"data-clone" => @project.http_url_to_repo
= text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span5"
.span4.right
.right
- if can? current_user, :download_code, @project
= link_to archive_project_repository_path(@project), class: "btn small grouped" do
%i.icon-download-alt
Download
- if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project)
= link_to new_project_merge_request_path(@project), title: "New Merge Request", class: "btn small grouped" do
Merge Request
- if @project.issues_enabled && can?(current_user, :write_issue, @project)
= link_to new_project_issue_path(@project), title: "New Issue", class: "btn small grouped" do
Issue
...@@ -10,9 +10,9 @@ ...@@ -10,9 +10,9 @@
.input .input
= f.text_field :name, placeholder: "Example Project", class: "xxlarge" = f.text_field :name, placeholder: "Example Project", class: "xxlarge"
%h5.page_title %hr
.alert.alert-info .adv_settings
%h5 Advanced settings: %h6 Advanced settings:
.clearfix .clearfix
= f.label :path do = f.label :path do
Path Path
...@@ -34,8 +34,9 @@ ...@@ -34,8 +34,9 @@
.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? - unless @project.new_record?
.alert.alert-info %hr
%h5 Features: .adv_settings
%h6 Features:
.clearfix .clearfix
= f.label :issues_enabled, "Issues" = f.label :issues_enabled, "Issues"
...@@ -56,7 +57,7 @@ ...@@ -56,7 +57,7 @@
%br %br
.actions .actions
= f.submit 'Save', class: "btn primary" = f.submit 'Save', class: "btn save-btn"
= link_to 'Cancel', @project, class: "btn" = link_to 'Cancel', @project, class: "btn"
- unless @project.new_record? - unless @project.new_record?
.right .right
......
...@@ -7,11 +7,11 @@ ...@@ -7,11 +7,11 @@
Project name is Project name is
.input .input
= f.text_field :name, placeholder: "Example Project", class: "xxlarge" = f.text_field :name, placeholder: "Example Project", class: "xxlarge"
= f.submit 'Create project', class: "btn primary" = f.submit 'Create project', class: "btn primary project-submit"
%hr %hr
.alert.alert-info %div.adv_settings
%h5 Advanced settings: %h6 Advanced settings:
.clearfix .clearfix
= f.label :path do = f.label :path do
Git Clone Git Clone
......
- if current_user.require_ssh_key? = render 'shared/no_ssh'
.alert-message.block-message.error .project_clone_panel
%ul .row
%li You have no ssh keys added to your profile. .span7
%li You wont be able to pull/push repository. .form-horizontal
%li Visit profile &rarr; keys and add public key of every machine you want to use for work with gitlabhq. .input-prepend.project_clone_holder
= link_to "SSH", "#", class: "btn small active", :"data-clone" => @project.ssh_url_to_repo
= link_to "HTTP", "#", class: "btn small", :"data-clone" => @project.http_url_to_repo
= text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span5"
%div.git-empty
%h4 Git global setup:
%pre.dark
= preserve do
git config --global user.name "#{current_user.name}"
git config --global user.email "#{current_user.email}"
.alert-message.block-message.error %h4.prepend-top-20 Create Repository
%ul.unstyled.alert_holder %pre.dark
%li You should push repository to proceed. = preserve do
%li After push you will be able to browse code, commits etc. mkdir #{@project.path}
cd #{@project.path}
git init
touch README
git add README
git commit -m 'first commit'
git remote add origin #{@project.url_to_repo}
git push -u origin master
- bash_lexer = Pygments::Lexer[:bash] %h4.prepend-top-20 Existing Git Repo?
%div.git-empty %pre.dark
%h3 Git global setup: = preserve do
- setup_str = ["git config --global user.name \"#{current_user.name}\"", cd existing_git_repo
"git config --global user.email \"#{current_user.email}\""].join("\n") git remote add origin #{@project.url_to_repo}
= preserve do git push -u origin master
= raw bash_lexer.highlight(setup_str, lexer: 'bash', options: {encoding: 'utf-8'})
%br - if can? current_user, :admin_project, @project
%br .prepend-top-20
%h3 Create Repository = link_to 'Remove project', @project, confirm: 'Are you sure?', method: :delete, class: "btn danger right"
- repo_setup_str = ["mkdir #{@project.path}",
"cd #{@project.path}",
"git init",
"touch README",
"git add README",
"git commit -m 'first commit'",
"git remote add origin #{@project.url_to_repo}",
"git push -u origin master"].join("\n")
= preserve do
= raw bash_lexer.highlight(repo_setup_str)
%br
%br
%h3 Existing Git Repo?
- exist_repo_setup_str = ["cd existing_git_repo",
"git remote add origin #{@project.url_to_repo}",
"git push -u origin master"].join("\n")
= preserve do
= raw bash_lexer.highlight(exist_repo_setup_str)
- if can? current_user, :admin_project, @project :javascript
.alert-message.block-message.error.prepend-top-20 $(function(){
= link_to 'Remove project', @project, confirm: 'Are you sure?', method: :delete, class: "btn danger" var link_sel = ".project_clone_holder a";
$(link_sel).bind("click", function() {
$(link_sel).removeClass("active");
$(this).addClass("active");
$("#project_clone").val($(this).attr("data-clone"));
})
})
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
New Project New Project
%hr %hr
= render 'new_form' = render 'new_form'
%div.ajax_loader.hide %div.save-project-loader.hide
%center %center
%div.padded= image_tag "ajax_loader.gif" = image_tag "ajax_loader.gif"
%h3.prepend-top Creating project &amp; repository. Please wait a few minutes %h3 Creating project &amp; repository. Please wait a few minutes
:javascript :javascript
$(function(){ new Projects(); }); $(function(){ new Projects(); });
= render "project_head" = render "project_head"
= render 'clone_panel'
.entry
.row
.span7
.form-horizontal
.input-prepend.project_clone_holder
%span.add-on git clone
= link_to "SSH", "#", class: "btn small active", :"data-clone" => @project.ssh_url_to_repo
= link_to "HTTP", "#", class: "btn small", :"data-clone" => @project.http_url_to_repo
= text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span5"
.span4.right
.right
- if can? current_user, :download_code, @project
= link_to archive_project_repository_path(@project), class: "btn small grouped" do
%i.icon-download-alt
Download
- if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project)
= link_to new_project_merge_request_path(@project), title: "New Merge Request", class: "btn small grouped" do
Merge Request
- if @project.issues_enabled && can?(current_user, :write_issue, @project)
= link_to new_project_issue_path(@project), title: "New Issue", class: "btn small grouped" do
Issue
= render "events/event_last_push", event: @last_push = render "events/event_last_push", event: @last_push
.content_list= render @events .content_list= render @events
:javascript :javascript
$(function(){ $(function(){
var link_sel = ".project_clone_holder a"; var link_sel = ".project_clone_holder a";
$(link_sel).bind("click", function() { $(link_sel).bind("click", function() {
$(link_sel).removeClass("active"); $(link_sel).removeClass("active");
$(this).addClass("active"); $(this).addClass("active");
$("#project_clone").val($(this).attr("data-clone")); $("#project_clone").val($(this).attr("data-clone"));
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
%tr{ class: "tree-item #{tree_hex_class(content)}", url: tree_file_project_ref_path(@project, @ref, file) } %tr{ class: "tree-item #{tree_hex_class(content)}", url: tree_file_project_ref_path(@project, @ref, file) }
%td.tree-item-file-name %td.tree-item-file-name
= tree_icon(content) = tree_icon(content)
= link_to truncate(content.name, length: 40), tree_file_project_ref_path(@project, @ref || @commit.id, file), remote: :true %strong= link_to truncate(content.name, length: 40), tree_file_project_ref_path(@project, @ref || @commit.id, file), remote: :true
%td.tree_time_ago.cgray %td.tree_time_ago.cgray
- if index == 1 - if index == 1
%span.log_loading %span.log_loading
......
= form_tag search_path, method: :get do |f| = form_tag search_path, method: :get, class: 'form-inline' do |f|
.padded .padded
= label_tag :search do = label_tag :search do
%strong Looking for %strong Looking for
.input .input
= text_field_tag :search, params[:search], placeholder: "issue 143", class: "input-xxlarge", id: "dashboard_search" = text_field_tag :search, params[:search], placeholder: "issue 143", class: "input-xxlarge", id: "dashboard_search"
= submit_tag 'Search', class: "btn btn-primary" = submit_tag 'Search', class: "btn primary"
- if params[:search].present? - if params[:search].present?
%br %br
%h3 %h3
......
- if current_user.require_ssh_key?
%h6.error_message
%span
You wont be able to pull/push project code unless you
%strong
= link_to new_key_path, class: "vlink" do
add SSH key
to your profile
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
%span.label Blocked %span.label Blocked
= link_to project_team_member_path(@project, member), title: user.name, class: "dark" do = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do
= image_tag gravatar_icon(user.email, 40), class: "avatar" = image_tag gravatar_icon(user.email, 40), class: "avatar s32"
= link_to project_team_member_path(@project, member), title: user.name, class: "dark" do = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do
%strong= truncate(user.name, lenght: 40) %strong= truncate(user.name, lenght: 40)
%br %br
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
= form_for(@team_member, as: :team_member, url: project_team_member_path(@project, @team_member)) do |f| = form_for(@team_member, as: :team_member, url: project_team_member_path(@project, @team_member)) do |f|
= f.select :project_access, options_for_select(Project.access_options, @team_member.project_access), {}, class: "project-access-select", disabled: !allow_admin = f.select :project_access, options_for_select(Project.access_options, @team_member.project_access), {}, class: "project-access-select", disabled: !allow_admin
%hr %hr
= render user.recent_events.limit(5) = render @events
:javascript :javascript
$(function(){ $(function(){
$('.repo-access-select, .project-access-select').live("change", function() { $('.repo-access-select, .project-access-select').live("change", function() {
......
...@@ -14,13 +14,14 @@ ...@@ -14,13 +14,14 @@
.middle_box_content .middle_box_content
.input .input
%span.cgray %span.cgray
Wiki content is parsed with #{link_to "Markdown", "http://en.wikipedia.org/wiki/Markdown"}. Wiki content is parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}.
To add link to new page you can just type To link to a (new) page you can just type
%code [Link Title](page-slug) %code [Link Title](page-slug)
\.
.bottom_box_content .bottom_box_content
= f.label :content = f.label :content
.input= f.text_area :content, class: 'span8' .input= f.text_area :content, class: 'span8'
.actions .actions
= f.submit 'Save', class: "primary btn" = f.submit 'Save', class: "save-btn btn"
= link_to "Cancel", project_wiki_path(@project, :index), class: "btn" = link_to "Cancel", project_wiki_path(@project, :index), class: "btn cancel-btn"
...@@ -23,7 +23,7 @@ module Gitlab ...@@ -23,7 +23,7 @@ module Gitlab
# config.plugins = [ :exception_notification, :ssl_requirement, :all ] # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
# Activate observers that should always be running. # Activate observers that should always be running.
config.active_record.observers = :mailer_observer, :activity_observer, :project_observer, :key_observer, :issue_observer, :user_observer, :system_hook_observer config.active_record.observers = :mailer_observer, :activity_observer, :project_observer, :key_observer, :issue_observer, :user_observer, :system_hook_observer, :users_project_observer
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
......
...@@ -3,5 +3,3 @@ require File.expand_path('../application', __FILE__) ...@@ -3,5 +3,3 @@ require File.expand_path('../application', __FILE__)
# Initialize the rails application # Initialize the rails application
Gitlab::Application.initialize! Gitlab::Application.initialize!
require File.join(Rails.root, "lib", "gitlab", "git_host")
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Gitlab application config file # # Gitlab application config file #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
...@@ -19,27 +19,27 @@ email: ...@@ -19,27 +19,27 @@ email:
# Application specific settings # Application specific settings
# Like default project limit for user etc # Like default project limit for user etc
app: app:
default_projects_limit: 10 default_projects_limit: 10
# backup_path: "/vol/backups" # default: Rails.root + backups/ # backup_path: "/vol/backups" # default: Rails.root + backups/
# backup_keep_time: 604800 # default: 0 (forever) (in seconds) # backup_keep_time: 604800 # default: 0 (forever) (in seconds)
# disable_gravatar: true # default: false - Disable user avatars from Gravatar.com
#
# # 2. Advanced settings:
# 2. Advanced settings:
# ========================== # ==========================
# Git Hosting configuration # Git Hosting configuration
git_host: git_host:
admin_uri: git@localhost:gitolite-admin admin_uri: git@localhost:gitolite-admin
base_path: /home/git/repositories/ base_path: /home/git/repositories/
# hooks_path: /var/lib/gitolite/.gitolite/hooks/ # only needed when gitolite is not installed according the manual
# host: localhost # host: localhost
git_user: git git_user: git
upload_pack: true upload_pack: true
receive_pack: true receive_pack: true
# port: 22 # port: 22
# Git settings # Git settings
# Use default values unless you understand it # Use default values unless you understand it
git: git:
......
...@@ -66,6 +66,10 @@ class Settings < Settingslogic ...@@ -66,6 +66,10 @@ class Settings < Settingslogic
git_host['base_path'] || '/home/git/repositories/' git_host['base_path'] || '/home/git/repositories/'
end end
def git_hooks_path
git_host['hooks_path'] || '/home/git/share/gitolite/hooks/'
end
def git_upload_pack def git_upload_pack
if git_host['upload_pack'] != false if git_host['upload_pack'] != false
true true
...@@ -111,5 +115,9 @@ class Settings < Settingslogic ...@@ -111,5 +115,9 @@ class Settings < Settingslogic
def backup_keep_time def backup_keep_time
app['backup_keep_time'] || 0 app['backup_keep_time'] || 0
end end
def disable_gravatar?
app['disable_gravatar'] || false
end
end end
end end
# GIT over HTTP
require Rails.root.join("lib", "gitlab", "backend", "grack_auth")
# GITOLITE backend
require Rails.root.join("lib", "gitlab", "backend", "gitolite")
#if defined?(Footnotes) && Rails.env.development?
#Footnotes.run! # first of all
#end
...@@ -30,6 +30,7 @@ Gitlab::Application.routes.draw do ...@@ -30,6 +30,7 @@ Gitlab::Application.routes.draw do
get 'help/web_hooks' => 'help#web_hooks' get 'help/web_hooks' => 'help#web_hooks'
get 'help/system_hooks' => 'help#system_hooks' get 'help/system_hooks' => 'help#system_hooks'
get 'help/markdown' => 'help#markdown' get 'help/markdown' => 'help#markdown'
get 'help/ssh' => 'help#ssh'
# #
# Admin Area # Admin Area
...@@ -196,7 +197,9 @@ Gitlab::Application.routes.draw do ...@@ -196,7 +197,9 @@ Gitlab::Application.routes.draw do
end end
resources :team_members resources :team_members
resources :milestones resources :milestones
resources :labels, :only => [:index]
resources :issues do resources :issues do
collection do collection do
post :sort post :sort
post :bulk_update post :bulk_update
......
# create tmp dir if not exist require 'fileutils'
tmp_dir = File.join(Rails.root, "tmp")
Dir.mkdir(tmp_dir) unless File.exists?(tmp_dir) print "Unpacking seed repository..."
# Create dir for test repo SEED_REPO = 'seed_project.tar.gz'
repo_dir = File.join(Rails.root, "tmp", "tests") REPO_PATH = File.join(Rails.root, 'tmp', 'repositories')
Dir.mkdir(repo_dir) unless File.exists?(repo_dir)
# Make whatever directories we need to make
`cp spec/seed_project.tar.gz tmp/tests/` FileUtils.mkdir_p(REPO_PATH)
Dir.chdir(repo_dir)
`tar -xf seed_project.tar.gz` # Copy the archive to the repo path
3.times do |i| FileUtils.cp(File.join(Rails.root, 'spec', SEED_REPO), REPO_PATH)
`cp -r gitlabhq/ gitlabhq_#{i}/`
puts "Unpacked seed repo - tmp/tests/gitlabhq_#{i}" # chdir to the repo path
FileUtils.cd(REPO_PATH) do
# Extract the archive
`tar -xf #{SEED_REPO}`
# Remove the copy
FileUtils.rm(SEED_REPO)
end end
puts ' done.'
class AddExternAuthProviderToUsers < ActiveRecord::Migration
def change
add_column :users, :extern_uid, :string
add_column :users, :provider, :string
add_index :users, [:extern_uid, :provider], :unique => true
end
end
AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended to check this file into your version control system. # It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20120712080407) do ActiveRecord::Schema.define(:version => 20120729131232) do
create_table "events", :force => true do |t| create_table "events", :force => true do |t|
t.string "target_type" t.string "target_type"
...@@ -171,9 +171,12 @@ ActiveRecord::Schema.define(:version => 20120712080407) do ...@@ -171,9 +171,12 @@ ActiveRecord::Schema.define(:version => 20120712080407) do
t.boolean "blocked", :default => false, :null => false t.boolean "blocked", :default => false, :null => false
t.integer "failed_attempts", :default => 0 t.integer "failed_attempts", :default => 0
t.datetime "locked_at" t.datetime "locked_at"
t.string "extern_uid"
t.string "provider"
end end
add_index "users", ["email"], :name => "index_users_on_email", :unique => true add_index "users", ["email"], :name => "index_users_on_email", :unique => true
add_index "users", ["extern_uid", "provider"], :name => "index_users_on_extern_uid_and_provider", :unique => true
add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
create_table "users_projects", :force => true do |t| create_table "users_projects", :force => true do |t|
......
...@@ -27,4 +27,6 @@ The API uses JSON to serialize data. You don't need to specify `.json` at the en ...@@ -27,4 +27,6 @@ The API uses JSON to serialize data. You don't need to specify `.json` at the en
+ [Users](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/users.md) + [Users](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/users.md)
+ [Projects](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/projects.md) + [Projects](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/projects.md)
+ [Snippets](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/snippets.md)
+ [Issues](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/issues.md) + [Issues](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/issues.md)
+ [Milestones](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/milestones.md)
## List project milestones
Get a list of project milestones.
```
GET /projects/:id/milestones
```
Parameters:
+ `id` (required) - The ID or code name of a project
## Single milestone
Get a single project milestone.
```
GET /projects/:id/milestones/:milestone_id
```
Parameters:
+ `id` (required) - The ID or code name of a project
+ `milestone_id` (required) - The ID of a project milestone
## New milestone
Create a new project milestone.
```
POST /projects/:id/milestones
```
Parameters:
+ `id` (required) - The ID or code name of a project
+ `milestone_id` (required) - The ID of a project milestone
+ `title` (required) - The title of an milestone
+ `description` (optional) - The description of the milestone
+ `due_date` (optional) - The due date of the milestone
## Edit milestone
Update an existing project milestone.
```
PUT /projects/:id/milestones/:milestone_id
```
Parameters:
+ `id` (required) - The ID or code name of a project
+ `milestone_id` (required) - The ID of a project milestone
+ `title` (optional) - The title of a milestone
+ `description` (optional) - The description of a milestone
+ `due_date` (optional) - The due date of the milestone
+ `closed` (optional) - The status of the milestone
...@@ -204,108 +204,6 @@ Parameters: ...@@ -204,108 +204,6 @@ Parameters:
] ]
``` ```
# Project Snippets
## List snippets
Not implemented.
## Single snippet
Get a project snippet.
```
GET /projects/:id/snippets/:snippet_id
```
Parameters:
+ `id` (required) - The ID or code name of a project
+ `snippet_id` (required) - The ID of a project's snippet
```json
{
"id": 1,
"title": "test",
"file_name": "add.rb",
"author": {
"id": 1,
"email": "john@example.com",
"name": "John Smith",
"blocked": false,
"created_at": "2012-05-23T08:00:58Z"
},
"expires_at": null,
"updated_at": "2012-06-28T10:52:04Z",
"created_at": "2012-06-28T10:52:04Z"
}
```
## Snippet content
Get a raw project snippet.
```
GET /projects/:id/snippets/:snippet_id/raw
```
Parameters:
+ `id` (required) - The ID or code name of a project
+ `snippet_id` (required) - The ID of a project's snippet
## New snippet
Create a new project snippet.
```
POST /projects/:id/snippets
```
Parameters:
+ `id` (required) - The ID or code name of a project
+ `title` (required) - The title of a snippet
+ `file_name` (required) - The name of a snippet file
+ `lifetime` (optional) - The expiration date of a snippet
+ `code` (required) - The content of a snippet
Will return created snippet with status `201 Created` on success, or `404 Not found` on fail.
## Edit snippet
Update an existing project snippet.
```
PUT /projects/:id/snippets/:snippet_id
```
Parameters:
+ `id` (required) - The ID or code name of a project
+ `snippet_id` (required) - The ID of a project's snippet
+ `title` (optional) - The title of a snippet
+ `file_name` (optional) - The name of a snippet file
+ `lifetime` (optional) - The expiration date of a snippet
+ `code` (optional) - The content of a snippet
Will return updated snippet with status `200 OK` on success, or `404 Not found` on fail.
## Delete snippet
Delete existing project snippet.
```
DELETE /projects/:id/snippets/:snippet_id
```
Parameters:
+ `id` (required) - The ID or code name of a project
+ `snippet_id` (required) - The ID of a project's snippet
Status code `200` will be returned on success.
## Raw blob content ## Raw blob content
Get the raw file contents for a file. Get the raw file contents for a file.
......
## List snippets
Not implemented.
## Single snippet
Get a project snippet.
```
GET /projects/:id/snippets/:snippet_id
```
Parameters:
+ `id` (required) - The ID or code name of a project
+ `snippet_id` (required) - The ID of a project's snippet
```json
{
"id": 1,
"title": "test",
"file_name": "add.rb",
"author": {
"id": 1,
"email": "john@example.com",
"name": "John Smith",
"blocked": false,
"created_at": "2012-05-23T08:00:58Z"
},
"expires_at": null,
"updated_at": "2012-06-28T10:52:04Z",
"created_at": "2012-06-28T10:52:04Z"
}
```
## Snippet content
Get a raw project snippet.
```
GET /projects/:id/snippets/:snippet_id/raw
```
Parameters:
+ `id` (required) - The ID or code name of a project
+ `snippet_id` (required) - The ID of a project's snippet
## New snippet
Create a new project snippet.
```
POST /projects/:id/snippets
```
Parameters:
+ `id` (required) - The ID or code name of a project
+ `title` (required) - The title of a snippet
+ `file_name` (required) - The name of a snippet file
+ `lifetime` (optional) - The expiration date of a snippet
+ `code` (required) - The content of a snippet
Will return created snippet with status `201 Created` on success, or `404 Not found` on fail.
## Edit snippet
Update an existing project snippet.
```
PUT /projects/:id/snippets/:snippet_id
```
Parameters:
+ `id` (required) - The ID or code name of a project
+ `snippet_id` (required) - The ID of a project's snippet
+ `title` (optional) - The title of a snippet
+ `file_name` (optional) - The name of a snippet file
+ `lifetime` (optional) - The expiration date of a snippet
+ `code` (optional) - The content of a snippet
Will return updated snippet with status `200 OK` on success, or `404 Not found` on fail.
## Delete snippet
Delete existing project snippet.
```
DELETE /projects/:id/snippets/:snippet_id
```
Parameters:
+ `id` (required) - The ID or code name of a project
+ `snippet_id` (required) - The ID of a project's snippet
Status code `200` will be returned on success.
## Development tips:
### Start application in development mode
#### 1. Via foreman
bundle exec foreman -p 3000
#### 2. Via gitlab cli
./gitlab start
#### 3. Manually
bundle exec rails s
bundle exec rake environment resque:work QUEUE=* VVERBOSE=1
### Run tests:
#### 1. Packages
# ubuntu
sudo apt-get install libqt4-dev libqtwebkit-dev
sudo apt-get install xvfb
# Mac
brew install qt
brew install xvfb
#### 2. DB & seeds
bundle exec rake db:setup RAILS_ENV=test
bundle exec rake db:seed_fu RAILS_ENV=test
### 3. Run Tests
# All in one
bundle exec gitlab:test
# Rspec
bundle exec rake spec
# Cucumber
bundle exec rake cucumber
...@@ -119,7 +119,6 @@ Permissions: ...@@ -119,7 +119,6 @@ Permissions:
sudo chmod -R g+rwX /home/git/repositories/ sudo chmod -R g+rwX /home/git/repositories/
sudo chown -R git:git /home/git/repositories/ sudo chown -R git:git /home/git/repositories/
sudo chown gitlab:gitlab /home/git/repositories/**/hooks/post-receive
#### CHECK: Logout & login again to apply git group to your user #### CHECK: Logout & login again to apply git group to your user
...@@ -178,6 +177,11 @@ Permissions: ...@@ -178,6 +177,11 @@ Permissions:
sudo -u gitlab bundle exec rake gitlab:app:setup RAILS_ENV=production sudo -u gitlab bundle exec rake gitlab:app:setup RAILS_ENV=production
#### Setup gitlab hooks
sudo cp ./lib/hooks/post-receive /home/git/share/gitolite/hooks/common/post-receive
sudo chown git:git /home/git/share/gitolite/hooks/common/post-receive
Checking status: Checking status:
sudo -u gitlab bundle exec rake gitlab:app:status RAILS_ENV=production sudo -u gitlab bundle exec rake gitlab:app:status RAILS_ENV=production
...@@ -196,6 +200,7 @@ Checking status: ...@@ -196,6 +200,7 @@ Checking status:
Resolving deltas: 100% (174/174), done. Resolving deltas: 100% (174/174), done.
Can clone gitolite-admin?............YES Can clone gitolite-admin?............YES
UMASK for .gitolite.rc is 0007? ............YES UMASK for .gitolite.rc is 0007? ............YES
/home/git/share/gitolite/hooks/common/post-receive exists? ............YES
If you got all YES - congrats! You can go to next step. If you got all YES - congrats! You can go to next step.
...@@ -239,42 +244,15 @@ You can login via web using admin generated with setup: ...@@ -239,42 +244,15 @@ You can login via web using admin generated with setup:
sudo -u gitlab cp config/unicorn.rb.orig config/unicorn.rb sudo -u gitlab cp config/unicorn.rb.orig config/unicorn.rb
sudo -u gitlab bundle exec unicorn_rails -c config/unicorn.rb -E production -D sudo -u gitlab bundle exec unicorn_rails -c config/unicorn.rb -E production -D
Edit /etc/nginx/nginx.conf. In the *http* section add the following section of code or replace it completely with https://raw.github.com/dosire/gitlabhq/master/aws/nginx.conf Add gitlab to nginx sites & change with your host specific settings
upstream gitlab {
server unix:/home/gitlab/gitlab/tmp/sockets/gitlab.socket;
}
server {
listen YOUR_SERVER_IP:80; # e.g., listen 192.168.1.1:80;
server_name YOUR_SERVER_FQDN; # e.g., server_name source.example.com;
root /home/gitlab/gitlab/public;
# individual nginx logs for this gitlab vhost
access_log /var/log/nginx/gitlab_access.log;
error_log /var/log/nginx/gitlab_error.log;
location / {
# serve static files from defined root folder;.
# @gitlab is a named location for the upstream fallback, see below
try_files $uri $uri/index.html $uri.html @gitlab;
}
# if a file, which is not found in the root folder is requested,
# then the proxy pass the request to the upsteam (gitlab unicorn)
location @gitlab {
proxy_redirect off;
# you need to change this to "https", if you set "ssl" directive to "on"
proxy_set_header X-FORWARDED_PROTO http;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://gitlab; sudo cp /home/gitlab/gitlab/lib/support/nginx-gitlab /etc/nginx/sites-available/gitlab
} sudo ln -s /etc/nginx/sites-available/gitlab /etc/nginx/sites-enabled/gitlab
}
Change **YOUR_SERVER_IP** and **YOUR_SERVER_FQDN** to the IP address and fully-qualified domain name of the host serving GitLab. # Change **YOUR_SERVER_IP** and **YOUR_SERVER_FQDN**
# to the IP address and fully-qualified domain name
# of the host serving GitLab.
sudo vim /etc/nginx/sites-enabled/gitlab
Restart nginx: Restart nginx:
...@@ -282,60 +260,7 @@ Restart nginx: ...@@ -282,60 +260,7 @@ Restart nginx:
Create init script in /etc/init.d/gitlab: Create init script in /etc/init.d/gitlab:
#! /bin/bash cp /home/gitlab/gitlab/lib/support/init-gitlab /etc/init.d/gitlab
### BEGIN INIT INFO
# Provides: gitlab
# Required-Start: $local_fs $remote_fs $network $syslog redis-server
# Required-Stop: $local_fs $remote_fs $network $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: GitLab git repository management
# Description: GitLab git repository management
### END INIT INFO
DAEMON_OPTS="-c /home/gitlab/gitlab/config/unicorn.rb -E production -D"
NAME=unicorn
DESC="Gitlab service"
PID=/home/gitlab/gitlab/tmp/pids/unicorn.pid
RESQUE_PID=/home/gitlab/gitlab/tmp/pids/resque_worker.pid
case "$1" in
start)
CD_TO_APP_DIR="cd /home/gitlab/gitlab"
START_DAEMON_PROCESS="bundle exec unicorn_rails $DAEMON_OPTS"
START_RESQUE_PROCESS="./resque.sh"
echo -n "Starting $DESC: "
if [ `whoami` = root ]; then
sudo -u gitlab sh -l -c "$CD_TO_APP_DIR > /dev/null 2>&1 && $START_DAEMON_PROCESS && $START_RESQUE_PROCESS"
else
$CD_TO_APP_DIR > /dev/null 2>&1 && $START_DAEMON_PROCESS && $START_RESQUE_PROCESS
fi
echo "$NAME."
;;
stop)
echo -n "Stopping $DESC: "
kill -QUIT `cat $PID`
kill -QUIT `cat $RESQUE_PID`
echo "$NAME."
;;
restart)
echo -n "Restarting $DESC: "
kill -USR2 `cat $PID`
echo "$NAME."
;;
reload)
echo -n "Reloading $DESC configuration: "
kill -HUP `cat $PID`
echo "$NAME."
;;
*)
echo "Usage: $NAME {start|stop|restart|reload}" >&2
exit 1
;;
esac
exit 0
Adding permission: Adding permission:
......
...@@ -3,8 +3,8 @@ Feature: SSH Keys ...@@ -3,8 +3,8 @@ Feature: SSH Keys
Given I signin as a user Given I signin as a user
And I have ssh keys: And I have ssh keys:
| title | | title |
| Work | | ssh-rsa Work |
| Home | | ssh-rsa Home |
And I visit profile keys page And I visit profile keys page
Scenario: I should see SSH keys Scenario: I should see SSH keys
......
Feature: Labels
Background:
Given I signin as a user
And I own project "Shop"
And project "Shop" have issues tags:
| name |
| bug |
| feature |
Given I visit project "Shop" labels page
Scenario: I should see active milestones
Then I should see label "bug"
And I should see label "feature"
...@@ -4,9 +4,7 @@ Feature: Project Network Graph ...@@ -4,9 +4,7 @@ Feature: Project Network Graph
Background: Background:
Given I signin as a user Given I signin as a user
And I own project "Shop" And I own project "Shop"
And I visit project "Shop" network page And I visit project "Shop" network page
Scenario: I should see project network Scenario: I should see project network
Then page should have network graph Then page should have network graph
...@@ -91,36 +91,24 @@ Then /^I should see my merge requests$/ do ...@@ -91,36 +91,24 @@ Then /^I should see my merge requests$/ do
end end
Given /^I have assigned issues$/ do Given /^I have assigned issues$/ do
project1 = Factory :project, project = Factory :project
:path => "project1", project.add_access(@user, :read, :write)
:code => "gitlabhq_1"
project2 = Factory :project,
:path => "project2",
:code => "gitlabhq_2"
project1.add_access(@user, :read, :write)
project2.add_access(@user, :read, :write)
issue1 = Factory :issue, issue1 = Factory :issue,
:author => @user, :author => @user,
:assignee => @user, :assignee => @user,
:project => project1 :project => project
issue2 = Factory :issue, issue2 = Factory :issue,
:author => @user, :author => @user,
:assignee => @user, :assignee => @user,
:project => project2 :project => project
end end
Given /^I have authored merge requests$/ do Given /^I have authored merge requests$/ do
project1 = Factory :project, project1 = Factory :project
:path => "project1",
:code => "gitlabhq_1"
project2 = Factory :project, project2 = Factory :project
:path => "project2",
:code => "gitlabhq_2"
project1.add_access(@user, :read, :write) project1.add_access(@user, :read, :write)
project2.add_access(@user, :read, :write) project2.add_access(@user, :read, :write)
......
...@@ -16,7 +16,7 @@ end ...@@ -16,7 +16,7 @@ end
Given /^I submit new ssh key "(.*?)"$/ do |arg1| Given /^I submit new ssh key "(.*?)"$/ do |arg1|
fill_in "key_title", :with => arg1 fill_in "key_title", :with => arg1
fill_in "key_key", :with => "publickey234=" fill_in "key_key", :with => "ssh-rsa publickey234="
click_button "Save" click_button "Save"
end end
......
...@@ -33,6 +33,25 @@ Given /^I visit issue page "(.*?)"$/ do |arg1| ...@@ -33,6 +33,25 @@ Given /^I visit issue page "(.*?)"$/ do |arg1|
end end
Given /^I submit new issue "(.*?)"$/ do |arg1| Given /^I submit new issue "(.*?)"$/ do |arg1|
fill_in "issue_title", :with => arg1 fill_in "issue_title", with: arg1
click_button "Submit new issue" click_button "Submit new issue"
end end
Given /^project "(.*?)" have issues tags:$/ do |arg1, table|
project = Project.find_by_name(arg1)
table.hashes.each do |hash|
Factory :issue,
project: project,
label_list: [hash[:name]]
end
end
Given /^I visit project "(.*?)" labels page$/ do |arg1|
visit project_labels_path(Project.find_by_name(arg1))
end
Then /^I should see label "(.*?)"$/ do |arg1|
within ".labels-table" do
page.should have_content arg1
end
end
include LoginMacros include LoginHelpers
Given /^I signin as a user$/ do Given /^I signin as a user$/ do
login_as :user login_as :user
...@@ -57,6 +57,11 @@ end ...@@ -57,6 +57,11 @@ end
Given /^I visit project "(.*?)" network page$/ do |arg1| Given /^I visit project "(.*?)" network page$/ do |arg1|
project = Project.find_by_name(arg1) project = Project.find_by_name(arg1)
# Stub out find_all to speed this up (10 commits vs. 650)
commits = Grit::Commit.find_all(project.repo, nil, {max_count: 10})
Grit::Commit.stub(:find_all).and_return(commits)
visit graph_project_path(project) visit graph_project_path(project)
end end
...@@ -67,8 +72,8 @@ end ...@@ -67,8 +72,8 @@ end
Given /^page should have network graph$/ do Given /^page should have network graph$/ do
page.should have_content "Project Network Graph" page.should have_content "Project Network Graph"
within ".graph" do within ".graph" do
page.should have_content "stable" page.should have_content "master"
page.should have_content "notes_refacto..." page.should have_content "scss_refactor..."
end end
end end
......
require 'simplecov' unless ENV['CI']
SimpleCov.start 'rails' require 'simplecov'
SimpleCov.start 'rails'
end
require 'cucumber/rails' require 'cucumber/rails'
require 'webmock/cucumber' require 'webmock/cucumber'
WebMock.allow_net_connect! WebMock.allow_net_connect!
require Rails.root.join 'spec/monkeypatch' require Rails.root.join 'spec/support/gitolite_stub'
require Rails.root.join 'spec/factories' require Rails.root.join 'spec/support/stubbed_repository'
require Rails.root.join 'spec/support/login' require Rails.root.join 'spec/support/login_helpers'
require Rails.root.join 'spec/support/valid_commit' require Rails.root.join 'spec/support/valid_commit'
Capybara.default_selector = :css Capybara.default_selector = :css
...@@ -44,3 +47,13 @@ require 'headless' ...@@ -44,3 +47,13 @@ require 'headless'
headless = Headless.new headless = Headless.new
headless.start headless.start
require 'cucumber/rspec/doubles'
include GitoliteStub
Before do
stub_gitolite!
end
World(FactoryGirl::Syntax::Methods)
#!/usr/bin/env ruby
class GitlabCli
def initialize
@path = File.dirname(__FILE__)
@command = ARGV.shift
@mode = ARGV.shift
end
def execute
case @command
when 'start' then start
when 'stop' then stop
else
puts "-- Usage gitlab start production or gitlab stop development"
end
end
private
def start
case @mode
when 'production';
system(unicorn_start_cmd)
system(resque_start_cmd)
else
system(rails_start_cmd)
system(resque_dev_start_cmd)
end
end
def stop
case @mode
when 'production';
system(unicorn_stop_cmd)
else
system(rails_stop_cmd)
end
system(resque_stop_cmd)
end
def rails_start_cmd
"bundle exec rails s -d"
end
def rails_stop_cmd
pid = File.join(@path, "tmp/pids/server.pid")
"kill -QUIT `cat #{pid}`"
end
def unicorn_start_cmd
unicorn_conf = File.join(@path, "config/unicorn.rb")
"bundle exec unicorn_rails -c #{unicorn_conf} -E production -D"
end
def unicorn_stop_cmd
pid = File.join(@path, "/tmp/pids/unicorn.pid")
"kill -QUIT `cat #{pid}`"
end
def resque_dev_start_cmd
"./resque_dev.sh > /dev/null 2>&1"
end
def resque_start_cmd
"./resque.sh > /dev/null 2>&1"
end
def resque_stop_cmd
pid = File.join(@path, "tmp/pids/resque_worker.pid")
"kill -QUIT `cat #{pid}`"
end
end
GitlabCli.new.execute
...@@ -16,5 +16,6 @@ module Gitlab ...@@ -16,5 +16,6 @@ module Gitlab
mount Users mount Users
mount Projects mount Projects
mount Issues mount Issues
mount Milestones
end end
end end
...@@ -95,7 +95,7 @@ module Gitlab ...@@ -95,7 +95,7 @@ module Gitlab
end end
end end
# Delete a project issue # Delete a project issue (deprecated)
# #
# Parameters: # Parameters:
# id (required) - The ID or code name of a project # id (required) - The ID or code name of a project
...@@ -103,8 +103,7 @@ module Gitlab ...@@ -103,8 +103,7 @@ module Gitlab
# Example Request: # Example Request:
# DELETE /projects/:id/issues/:issue_id # DELETE /projects/:id/issues/:issue_id
delete ":id/issues/:issue_id" do delete ":id/issues/:issue_id" do
@issue = user_project.issues.find(params[:issue_id]) error!({'message' => 'method not allowed'}, 405)
@issue.destroy
end end
end end
end end
......
module Gitlab
# Milestones API
class Milestones < Grape::API
before { authenticate! }
resource :projects do
# Get a list of project milestones
#
# Parameters:
# id (required) - The ID or code name of a project
# Example Request:
# GET /projects/:id/milestones
get ":id/milestones" do
present user_project.milestones, with: Entities::Milestone
end
# Get a single project milestone
#
# Parameters:
# id (required) - The ID or code name of a project
# milestone_id (required) - The ID of a project milestone
# Example Request:
# GET /projects/:id/milestones/:milestone_id
get ":id/milestones/:milestone_id" do
@milestone = user_project.milestones.find(params[:milestone_id])
present @milestone, with: Entities::Milestone
end
# Create a new project milestone
#
# Parameters:
# id (required) - The ID or code name of the project
# title (required) - The title of the milestone
# description (optional) - The description of the milestone
# due_date (optional) - The due date of the milestone
# Example Request:
# POST /projects/:id/milestones
post ":id/milestones" do
@milestone = user_project.milestones.new(
title: params[:title],
description: params[:description],
due_date: params[:due_date]
)
if @milestone.save
present @milestone, with: Entities::Milestone
else
error!({'message' => '404 Not found'}, 404)
end
end
# Update an existing project milestone
#
# Parameters:
# id (required) - The ID or code name of a project
# milestone_id (required) - The ID of a project milestone
# title (optional) - The title of a milestone
# description (optional) - The description of a milestone
# due_date (optional) - The due date of a milestone
# closed (optional) - The status of the milestone
# Example Request:
# PUT /projects/:id/milestones/:milestone_id
put ":id/milestones/:milestone_id" do
@milestone = user_project.milestones.find(params[:milestone_id])
parameters = {
title: (params[:title] || @milestone.title),
description: (params[:description] || @milestone.description),
due_date: (params[:due_date] || @milestone.due_date),
closed: (params[:closed] || @milestone.closed)
}
if @milestone.update_attributes(parameters)
present @milestone, with: Entities::Milestone
else
error!({'message' => '404 Not found'}, 404)
end
end
end
end
end
...@@ -2,54 +2,57 @@ require 'gitolite' ...@@ -2,54 +2,57 @@ require 'gitolite'
require 'timeout' require 'timeout'
require 'fileutils' require 'fileutils'
# TODO: refactor & cleanup
module Gitlab module Gitlab
class Gitolite class Gitolite
class AccessDenied < StandardError; end class AccessDenied < StandardError; end
class InvalidKey < StandardError; end
def self.update_project(path, project) def set_key key_id, key_content, projects
self.new.configure { |git| git.update_project(path, project) } configure do |c|
c.update_keys(key_id, key_content)
c.update_projects(projects)
end
end end
def self.destroy_project(project) def remove_key key_id, projects
self.new.configure { |git| git.destroy_project(project) } configure do |c|
c.delete_key(key_id)
c.update_projects(projects)
end
end end
def pull def update_repository project
# create tmp dir configure do |c|
@local_dir = File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}") c.update_project(project.path, project)
Dir.mkdir @local_dir end
end
alias_method :create_repository, :update_repository
`git clone #{GitHost.admin_uri} #{@local_dir}/gitolite` def remove_repository project
configure do |c|
c.destroy_project(project)
end
end end
def push def url_to_repo path
Dir.chdir(File.join(@local_dir, "gitolite")) Gitlab.config.ssh_path + "#{path}.git"
`git add -A` end
`git commit -am "Gitlab"`
`git push`
Dir.chdir(Rails.root)
FileUtils.rm_rf(@local_dir) def initialize
# create tmp dir
@local_dir = File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}")
end end
def configure def enable_automerge
Timeout::timeout(30) do configure do |git|
File.open(File.join(Rails.root, 'tmp', "gitlabhq-gitolite.lock"), "w+") do |f| git.admin_all_repo
begin
f.flock(File::LOCK_EX)
pull
yield(self)
push
ensure
f.flock(File::LOCK_UN)
end
end
end end
rescue Exception => ex
Gitlab::Logger.error(ex.message)
raise Gitolite::AccessDenied.new("gitolite timeout")
end end
protected
def destroy_project(project) def destroy_project(project)
FileUtils.rm_rf(project.path_to_repo) FileUtils.rm_rf(project.path_to_repo)
...@@ -106,13 +109,13 @@ module Gitlab ...@@ -106,13 +109,13 @@ module Gitlab
name_writers = project.repository_writers name_writers = project.repository_writers
name_masters = project.repository_masters name_masters = project.repository_masters
pr_br = project.protected_branches.map(&:name).join(" ") pr_br = project.protected_branches.map(&:name).join("$ ")
repo.clean_permissions repo.clean_permissions
# Deny access to protected branches for writers # Deny access to protected branches for writers
unless name_writers.blank? || pr_br.blank? unless name_writers.blank? || pr_br.blank?
repo.add_permission("-", pr_br, name_writers) repo.add_permission("-", pr_br.strip + "$ ", name_writers)
end end
# Add read permissions # Add read permissions
...@@ -153,5 +156,47 @@ module Gitlab ...@@ -153,5 +156,47 @@ module Gitlab
conf.add_repo(repo, true) conf.add_repo(repo, true)
ga_repo.save ga_repo.save
end end
private
def pull
# create tmp dir
@local_dir = File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}")
Dir.mkdir @local_dir
`git clone #{Gitlab.config.gitolite_admin_uri} #{@local_dir}/gitolite`
end
def push
Dir.chdir(File.join(@local_dir, "gitolite"))
`git add -A`
`git commit -am "Gitlab"`
`git push`
Dir.chdir(Rails.root)
FileUtils.rm_rf(@local_dir)
end
def configure
Timeout::timeout(30) do
File.open(File.join(Rails.root, 'tmp', "gitlabhq-gitolite.lock"), "w+") do |f|
begin
f.flock(File::LOCK_EX)
pull
yield(self)
push
ensure
f.flock(File::LOCK_UN)
end
end
end
rescue Exception => ex
if ex.message =~ /is not a valid SSH key string/
raise Gitolite::InvalidKey.new("ssh key is not valid")
else
Gitlab::Logger.error(ex.message)
raise Gitolite::AccessDenied.new("gitolite timeout")
end
end
end end
end end
...@@ -42,13 +42,13 @@ module Grack ...@@ -42,13 +42,13 @@ module Grack
def current_ref def current_ref
if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/ if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/
input = Zlib::GzipReader.new(@request.body).string input = Zlib::GzipReader.new(@request.body).read
else else
input = @request.body.string input = @request.body.read
end end
# Need to reset seek point
oldrev, newrev, ref = input.split(' ') @request.body.rewind
/refs\/heads\/([\w-]+)/.match(ref).to_a.last /refs\/heads\/([\w-]+)/.match(input).to_a.first
end end
end# Auth end# Auth
end# Grack end# Grack
require File.join(Rails.root, "lib", "gitlab", "gitolite")
module Gitlab
class GitHost
def self.system
Gitlab::Gitolite
end
def self.admin_uri
Gitlab.config.git_host.admin_uri
end
def self.url_to_repo(path)
Gitlab.config.ssh_path + "#{path}.git"
end
end
end
module Gitlab module Gitlab
# Custom parsing for Gitlab-flavored Markdown # Custom parser for Gitlab-flavored Markdown
#
# It replaces references in the text with links to the appropriate items in Gitlab.
#
# Supported reference formats are:
# * @foo for team members
# * #123 for issues
# * !123 for merge requests
# * $123 for snippets
# * 123456 for commits
# #
# Examples # Examples
# #
...@@ -67,25 +76,25 @@ module Gitlab ...@@ -67,25 +76,25 @@ module Gitlab
def reference_user(identifier) def reference_user(identifier)
if user = @project.users.where(name: identifier).first if user = @project.users.where(name: identifier).first
member = @project.users_projects.where(user_id: user).first member = @project.users_projects.where(user_id: user).first
link_to("@#{user.name}", project_team_member_path(@project, member), html_options.merge(class: "gfm gfm-team_member #{html_options[:class]}")) if member link_to("@#{identifier}", project_team_member_path(@project, member), html_options.merge(class: "gfm gfm-team_member #{html_options[:class]}")) if member
end end
end end
def reference_issue(identifier) def reference_issue(identifier)
if issue = @project.issues.where(id: identifier).first if issue = @project.issues.where(id: identifier).first
link_to("##{issue.id}", project_issue_path(@project, issue), html_options.merge(title: "Issue: #{issue.title}", class: "gfm gfm-issue #{html_options[:class]}")) link_to("##{identifier}", project_issue_path(@project, issue), html_options.merge(title: "Issue: #{issue.title}", class: "gfm gfm-issue #{html_options[:class]}"))
end end
end end
def reference_merge_request(identifier) def reference_merge_request(identifier)
if merge_request = @project.merge_requests.where(id: identifier).first if merge_request = @project.merge_requests.where(id: identifier).first
link_to("!#{merge_request.id}", project_merge_request_path(@project, merge_request), html_options.merge(title: "Merge Request: #{merge_request.title}", class: "gfm gfm-merge_request #{html_options[:class]}")) link_to("!#{identifier}", project_merge_request_path(@project, merge_request), html_options.merge(title: "Merge Request: #{merge_request.title}", class: "gfm gfm-merge_request #{html_options[:class]}"))
end end
end end
def reference_snippet(identifier) def reference_snippet(identifier)
if snippet = @project.snippets.where(id: identifier).first if snippet = @project.snippets.where(id: identifier).first
link_to("$#{snippet.id}", project_snippet_path(@project, snippet), html_options.merge(title: "Snippet: #{snippet.title}", class: "gfm gfm-snippet #{html_options[:class]}")) link_to("$#{identifier}", project_snippet_path(@project, snippet), html_options.merge(title: "Snippet: #{snippet.title}", class: "gfm gfm-snippet #{html_options[:class]}"))
end end
end end
......
#! /bin/bash
### BEGIN INIT INFO
# Provides: gitlab
# Required-Start: $local_fs $remote_fs $network $syslog redis-server
# Required-Stop: $local_fs $remote_fs $network $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: GitLab git repository management
# Description: GitLab git repository management
### END INIT INFO
DAEMON_OPTS="-c /home/gitlab/gitlab/config/unicorn.rb -E production -D"
NAME=unicorn
DESC="Gitlab service"
PID=/home/gitlab/gitlab/tmp/pids/unicorn.pid
RESQUE_PID=/home/gitlab/gitlab/tmp/pids/resque_worker.pid
case "$1" in
start)
CD_TO_APP_DIR="cd /home/gitlab/gitlab"
START_DAEMON_PROCESS="bundle exec unicorn_rails $DAEMON_OPTS"
START_RESQUE_PROCESS="./resque.sh"
echo -n "Starting $DESC: "
if [ `whoami` = root ]; then
sudo -u gitlab sh -l -c "$CD_TO_APP_DIR > /dev/null 2>&1 && $START_DAEMON_PROCESS && $START_RESQUE_PROCESS"
else
$CD_TO_APP_DIR > /dev/null 2>&1 && $START_DAEMON_PROCESS && $START_RESQUE_PROCESS
fi
echo "$NAME."
;;
stop)
echo -n "Stopping $DESC: "
kill -QUIT `cat $PID`
kill -QUIT `cat $RESQUE_PID`
echo "$NAME."
;;
restart)
echo -n "Restarting $DESC: "
kill -USR2 `cat $PID`
echo "$NAME."
;;
reload)
echo -n "Reloading $DESC configuration: "
kill -HUP `cat $PID`
echo "$NAME."
;;
*)
echo "Usage: $NAME {start|stop|restart|reload}" >&2
exit 1
;;
esac
exit 0
upstream gitlab {
server unix:/home/gitlab/gitlab/tmp/sockets/gitlab.socket;
}
server {
listen YOUR_SERVER_IP:80; # e.g., listen 192.168.1.1:80;
server_name YOUR_SERVER_FQDN; # e.g., server_name source.example.com;
root /home/gitlab/gitlab/public;
# individual nginx logs for this gitlab vhost
access_log /var/log/nginx/gitlab_access.log;
error_log /var/log/nginx/gitlab_error.log;
location / {
# serve static files from defined root folder;.
# @gitlab is a named location for the upstream fallback, see below
try_files $uri $uri/index.html $uri.html @gitlab;
}
# if a file, which is not found in the root folder is requested,
# then the proxy pass the request to the upsteam (gitlab unicorn)
location @gitlab {
proxy_redirect off;
# you need to change this to "https", if you set "ssl" directive to "on"
proxy_set_header X-FORWARDED_PROTO http;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://gitlab;
}
}
desc "Add all users to all projects, system administratos are added as masters"
task :add_users_to_project_teams => :environment do |t, args|
users = User.find_all_by_admin(false, :select => 'id').map(&:id)
admins = User.find_all_by_admin(true, :select => 'id').map(&:id)
users.each do |user|
puts "#{user}"
end
Project.all.each do |project|
puts "Importing #{users.length} users into #{project.path}"
UsersProject.bulk_import(project, users, UsersProject::DEVELOPER)
puts "Importing #{admins.length} admins into #{project.path}"
UsersProject.bulk_import(project, admins, UsersProject::MASTER)
end
end
desc "Add user to as a developer to all projects"
task :add_user_to_project_teams, [:email] => :environment do |t, args|
user_email = args.email
user = User.find_by_email(user_email)
project_ids = Project.all.map(&:id)
UsersProject.user_bulk_import(user,project_ids,UsersProject::DEVELOPER)
end
...@@ -144,8 +144,7 @@ namespace :gitlab do ...@@ -144,8 +144,7 @@ namespace :gitlab do
if Kernel.system("cd #{File.dirname(project.second)} > /dev/null 2>&1 && git clone --bare #{backup_path_repo}/#{project.first}.bundle #{project.first}.git > /dev/null 2>&1") if Kernel.system("cd #{File.dirname(project.second)} > /dev/null 2>&1 && git clone --bare #{backup_path_repo}/#{project.first}.bundle #{project.first}.git > /dev/null 2>&1")
permission_commands = [ permission_commands = [
"sudo chmod -R g+rwX #{Gitlab.config.git_base_path}", "sudo chmod -R g+rwX #{Gitlab.config.git_base_path}",
"sudo chown -R #{Gitlab.config.ssh_user}:#{Gitlab.config.ssh_user} #{Gitlab.config.git_base_path}", "sudo chown -R #{Gitlab.config.ssh_user}:#{Gitlab.config.ssh_user} #{Gitlab.config.git_base_path}"
"sudo chown gitlab:gitlab /home/git/repositories/**/hooks/post-receive"
] ]
permission_commands.each { |command| Kernel.system(command) } permission_commands.each { |command| Kernel.system(command) }
puts "[DONE]".green puts "[DONE]".green
......
...@@ -2,9 +2,7 @@ namespace :gitlab do ...@@ -2,9 +2,7 @@ namespace :gitlab do
namespace :app do namespace :app do
desc "GITLAB | Enable auto merge" desc "GITLAB | Enable auto merge"
task :enable_automerge => :environment do task :enable_automerge => :environment do
Gitlab::GitHost.system.new.configure do |git| Gitlab::Gitolite.new.enable_automerge
git.admin_all_repo
end
Project.find_each do |project| Project.find_each do |project|
if project.repo_exists? && !project.satellite.exists? if project.repo_exists? && !project.satellite.exists?
......
...@@ -16,7 +16,7 @@ namespace :gitlab do ...@@ -16,7 +16,7 @@ namespace :gitlab do
task :update_keys => :environment do task :update_keys => :environment do
puts "Starting Key" puts "Starting Key"
Key.find_each(:batch_size => 100) do |key| Key.find_each(:batch_size => 100) do |key|
key.update_repository Gitlab::Gitolite.new.set_key(key.identifier, key.key, key.projects)
print '.' print '.'
end end
puts "Done with keys" puts "Done with keys"
......
namespace :gitlab do namespace :gitlab do
namespace :app do namespace :app do
desc "GITLAB | Setup production application" desc "GITLAB | Setup production application"
task :setup => ['db:setup', 'db:seed_fu', 'gitlab:app:enable_automerge'] task :setup => [
'db:setup',
'db:seed_fu',
'gitlab:app:enable_automerge'
]
end end
end end
...@@ -56,6 +56,20 @@ namespace :gitlab do ...@@ -56,6 +56,20 @@ namespace :gitlab do
return return
end end
gitolite_hooks_path = File.join(Gitlab.config.git_hooks_path, "common")
gitlab_hook_files = ['post-receive']
gitlab_hook_files.each do |file_name|
dest = File.join(gitolite_hooks_path, file_name)
print "#{dest} exists? ............"
if File.exists?(dest)
puts "YES".green
else
puts "NO".red
return
end
end
if Project.count > 0 if Project.count > 0
puts "Validating projects repositories:".yellow puts "Validating projects repositories:".yellow
Project.find_each(:batch_size => 100) do |project| Project.find_each(:batch_size => 100) do |project|
...@@ -67,13 +81,7 @@ namespace :gitlab do ...@@ -67,13 +81,7 @@ namespace :gitlab do
next next
end end
puts "post-receive file ok".green
unless File.owned?(hook_file)
puts "post-receive file is not owner by gitlab".red
next
end
puts "post-reveice file ok".green
end end
end end
......
namespace :gitlab do
namespace :gitolite do
desc "GITLAB | Rewrite hooks for repos"
task :update_hooks => :environment do
puts "Starting Projects"
Project.find_each(:batch_size => 100) do |project|
begin
if project.commit
project.write_hooks
print ".".green
end
rescue Exception => e
print e.message.red
end
end
puts "\nDone with projects"
end
end
end
namespace :gitlab do
namespace :gitolite do
desc "GITLAB | Write GITLAB hook for gitolite"
task :write_hooks => :environment do
gitolite_hooks_path = File.join(Gitlab.config.git_hooks_path, "common")
gitlab_hooks_path = Rails.root.join("lib", "hooks")
gitlab_hook_files = ['post-receive']
gitlab_hook_files.each do |file_name|
source = File.join(gitlab_hooks_path, file_name)
dest = File.join(gitolite_hooks_path, file_name)
puts "sudo -u root cp #{source} #{dest}".yellow
`sudo -u root cp #{source} #{dest}`
puts "sudo -u root chown git:git #{dest}".yellow
`sudo -u root chown git:git #{dest}`
end
end
end
end
bundle exec rake environment resque:work QUEUE=post_receive,mailer,system_hook VVERBOSE=1 mkdir -p tmp/pids
bundle exec rake environment resque:work QUEUE=post_receive,mailer,system_hook VVERBOSE=1 PIDFILE=tmp/pids/resque_worker.pid RAILS_ENV=development BACKGROUND=yes
require File.join(Rails.root, 'spec', 'factory') # Backwards compatibility with the old method
def Factory(type, *args)
Factory.add(:project, Project) do |obj| FactoryGirl.create(type, *args)
obj.name = Faker::Internet.user_name
obj.path = 'gitlabhq'
obj.owner = Factory(:user)
obj.code = 'LGT'
end end
Factory.add(:project_without_owner, Project) do |obj| module Factory
obj.name = Faker::Internet.user_name def self.create(type, *args)
obj.path = 'gitlabhq' FactoryGirl.create(type, *args)
obj.code = 'LGT' end
end
Factory.add(:public_project, Project) do |obj| def self.new(type, *args)
obj.name = Faker::Internet.user_name FactoryGirl.build(type, *args)
obj.path = 'gitlabhq' end
obj.private_flag = false
obj.owner = Factory(:user)
obj.code = 'LGT'
end end
Factory.add(:user, User) do |obj| FactoryGirl.define do
obj.email = Faker::Internet.email sequence :sentence, aliases: [:title, :content] do
obj.password = "123456" Faker::Lorem.sentence
obj.name = Faker::Name.name end
obj.password_confirmation = "123456"
end
Factory.add(:admin, User) do |obj| sequence :name, aliases: [:file_name] do
obj.email = Faker::Internet.email Faker::Name.name
obj.password = "123456" end
obj.name = Faker::Name.name
obj.password_confirmation = "123456"
obj.admin = true
end
Factory.add(:issue, Issue) do |obj| sequence(:url) { Faker::Internet.uri('http') }
obj.title = Faker::Lorem.sentence
obj.author = Factory :user
obj.assignee = Factory :user
end
Factory.add(:merge_request, MergeRequest) do |obj| factory :user, aliases: [:author, :assignee, :owner] do
obj.title = Faker::Lorem.sentence email { Faker::Internet.email }
obj.author = Factory :user name
obj.assignee = Factory :user password "123456"
obj.source_branch = "master" password_confirmation "123456"
obj.target_branch = "stable"
obj.closed = false
end
Factory.add(:snippet, Snippet) do |obj| trait :admin do
obj.title = Faker::Lorem.sentence admin true
obj.file_name = Faker::Lorem.sentence end
obj.content = Faker::Lorem.sentences
end
Factory.add(:note, Note) do |obj| factory :admin, traits: [:admin]
obj.note = Faker::Lorem.sentence end
end
Factory.add(:key, Key) do |obj| factory :project do
obj.title = "Example key" sequence(:name) { |n| "project#{n}" }
obj.key = File.read(File.join(Rails.root, "db", "pkey.example")) path { name }
end code { name }
owner
end
Factory.add(:project_hook, ProjectHook) do |obj| factory :users_project do
obj.url = Faker::Internet.uri("http") user
end project
end
Factory.add(:system_hook, SystemHook) do |obj| factory :issue do
obj.url = Faker::Internet.uri("http") title
end author
project
Factory.add(:wiki, Wiki) do |obj| trait :closed do
obj.title = Faker::Lorem.sentence closed true
obj.content = Faker::Lorem.sentence end
obj.user = Factory(:user)
obj.project = Factory(:project)
end
Factory.add(:event, Event) do |obj| factory :closed_issue, traits: [:closed]
obj.title = Faker::Lorem.sentence end
obj.project = Factory(:project)
end factory :merge_request do
title
author
project
source_branch "master"
target_branch "stable"
end
factory :note do
project
note "Note"
end
factory :event do
end
factory :key do
title
key do
"""
ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=
"""
end
factory :deploy_key do
project
end
factory :personal_key do
user
end
end
factory :milestone do
title
project
end
factory :system_hook do
url
end
factory :project_hook do
url
end
factory :wiki do
title
content
user
end
factory :snippet do
project
author
title
content
file_name
end
Factory.add(:milestone, Milestone) do |obj| factory :protected_branch do
obj.title = Faker::Lorem.sentence name
obj.due_date = Date.today + 1.month project
end
end end
require 'spec_helper'
describe "Factories" do
describe 'User' do
it "builds a valid instance" do
build(:user).should be_valid
end
it "builds a valid admin instance" do
build(:admin).should be_valid
end
end
describe 'Project' do
it "builds a valid instance" do
build(:project).should be_valid
end
end
describe 'Issue' do
it "builds a valid instance" do
build(:issue).should be_valid
end
it "builds a valid closed instance" do
build(:closed_issue).should be_valid
end
end
describe 'MergeRequest' do
it "builds a valid instance" do
build(:merge_request).should be_valid
end
end
describe 'Note' do
it "builds a valid instance" do
build(:note).should be_valid
end
end
describe 'Event' do
it "builds a valid instance" do
build(:event).should be_valid
end
end
describe 'Key' do
it "builds a valid instance" do
build(:key).should be_valid
end
it "builds a valid deploy key instance" do
build(:deploy_key).should be_valid
end
it "builds a valid personal key instance" do
build(:personal_key).should be_valid
end
end
describe 'Milestone' do
it "builds a valid instance" do
build(:milestone).should be_valid
end
end
describe 'SystemHook' do
it "builds a valid instance" do
build(:system_hook).should be_valid
end
end
describe 'ProjectHook' do
it "builds a valid instance" do
build(:project_hook).should be_valid
end
end
describe 'Wiki' do
it "builds a valid instance" do
build(:wiki).should be_valid
end
end
describe 'Snippet' do
it "builds a valid instance" do
build(:snippet).should be_valid
end
end
end
class Factory
@factories = {}
class << self
def add(name, klass, &block)
@factories[name] = [klass, block]
end
def create(name, opts = {})
new(name, opts).tap(&:save!)
end
def new(name, opts = {})
factory= @factories[name]
factory[0].new.tap do |obj|
factory[1].call(obj)
end.tap do |obj|
opts.each do |k, opt|
obj.send("#{k}=", opt)
end
end
end
end
end
def Factory(name, opts={})
Factory.create name, opts
end
require 'spec_helper'
describe ApplicationHelper do
describe "gravatar_icon" do
let(:user_email) { 'user@email.com' }
it "should return a generic avatar path when Gravatar is disabled" do
Gitlab.config.stub(:disable_gravatar?).and_return(true)
gravatar_icon(user_email).should == 'no_avatar.png'
end
it "should return a generic avatar path when email is blank" do
gravatar_icon('').should == 'no_avatar.png'
end
it "should use SSL when appropriate" do
stub!(:request).and_return(double(:ssl? => true))
gravatar_icon(user_email).should match('https://secure.gravatar.com')
end
it "should accept a custom size" do
stub!(:request).and_return(double(:ssl? => false))
gravatar_icon(user_email, 64).should match(/\?s=64/)
end
end
end
...@@ -2,7 +2,7 @@ require "spec_helper" ...@@ -2,7 +2,7 @@ require "spec_helper"
describe GitlabMarkdownHelper do describe GitlabMarkdownHelper do
before do before do
@project = Project.find_by_path("gitlabhq") || Factory(:project) @project = Factory(:project)
@commit = @project.repo.commits.first.parents.first @commit = @project.repo.commits.first.parents.first
@commit = CommitDecorator.decorate(Commit.new(@commit)) @commit = CommitDecorator.decorate(Commit.new(@commit))
@other_project = Factory :project, path: "OtherPath", code: "OtherCode" @other_project = Factory :project, path: "OtherPath", code: "OtherCode"
...@@ -157,7 +157,7 @@ describe GitlabMarkdownHelper do ...@@ -157,7 +157,7 @@ describe GitlabMarkdownHelper do
gfm("Let @#{user.name} fix the *mess* in #{@commit.id}").should == "Let #{link_to "@#{user.name}", project_team_member_path(@project, member), class: "gfm gfm-team_member "} fix the *mess* in #{link_to @commit.id, project_commit_path(@project, id: @commit.id), title: "Commit: #{@commit.author_name} - #{@commit.title}", class: "gfm gfm-commit "}" gfm("Let @#{user.name} fix the *mess* in #{@commit.id}").should == "Let #{link_to "@#{user.name}", project_team_member_path(@project, member), class: "gfm gfm-team_member "} fix the *mess* in #{link_to @commit.id, project_commit_path(@project, id: @commit.id), title: "Commit: #{@commit.author_name} - #{@commit.title}", class: "gfm gfm-commit "}"
end end
it "should not trip over other stuff", focus: true do it "should not trip over other stuff" do
gfm("_Please_ *stop* 'helping' and all the other b*$#%' you do.").should == "_Please_ *stop* 'helping' and all the other b*$#%' you do." gfm("_Please_ *stop* 'helping' and all the other b*$#%' you do.").should == "_Please_ *stop* 'helping' and all the other b*$#%' you do."
end end
......
...@@ -24,7 +24,7 @@ describe Notify do ...@@ -24,7 +24,7 @@ describe Notify do
end end
it 'has the correct subject' do it 'has the correct subject' do
should have_subject /Account was created for you/ should have_subject /^gitlab \| Account was created for you$/
end end
it 'contains the new user\'s login name' do it 'contains the new user\'s login name' do
...@@ -60,7 +60,7 @@ describe Notify do ...@@ -60,7 +60,7 @@ describe Notify do
it_behaves_like 'an assignee email' it_behaves_like 'an assignee email'
it 'has the correct subject' do it 'has the correct subject' do
should have_subject /new issue ##{issue.id}/ should have_subject /new issue ##{issue.id} \| #{issue.title} \| #{project.name}/
end end
it 'contains a link to the new issue' do it 'contains a link to the new issue' do
...@@ -76,7 +76,7 @@ describe Notify do ...@@ -76,7 +76,7 @@ describe Notify do
it_behaves_like 'a multiple recipients email' it_behaves_like 'a multiple recipients email'
it 'has the correct subject' do it 'has the correct subject' do
should have_subject /changed issue/ should have_subject /changed issue ##{issue.id} \| #{issue.title}/
end end
it 'contains the name of the previous assignee' do it 'contains the name of the previous assignee' do
...@@ -91,6 +91,29 @@ describe Notify do ...@@ -91,6 +91,29 @@ describe Notify do
should have_body_text /#{project_issue_path project, issue}/ should have_body_text /#{project_issue_path project, issue}/
end end
end end
describe 'status changed' do
let(:current_user) { Factory.create :user, email: "current@email.com" }
let(:status) { 'closed' }
subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) }
it 'has the correct subject' do
should have_subject /changed issue ##{issue.id} \| #{issue.title}/i
end
it 'contains the new status' do
should have_body_text /#{status}/i
end
it 'contains the user name' do
should have_body_text /#{current_user.name}/i
end
it 'contains a link to the issue' do
should have_body_text /#{project_issue_path project, issue}/
end
end
end end
context 'for merge requests' do context 'for merge requests' do
...@@ -145,6 +168,26 @@ describe Notify do ...@@ -145,6 +168,26 @@ describe Notify do
end end
end end
describe 'project access changed' do
let(:project) { Factory.create(:project,
path: "Fuu",
code: "Fuu") }
let(:user) { Factory.create :user }
let(:users_project) { Factory.create(:users_project,
project: project,
user: user) }
subject { Notify.project_access_granted_email(users_project.id) }
it 'has the correct subject' do
should have_subject /access to project was granted/
end
it 'contains name of project' do
should have_body_text /#{project.name}/
end
it 'contains new user role' do
should have_body_text /#{users_project.project_access_human}/
end
end
context 'items that are noteable, the email for a note' do context 'items that are noteable, the email for a note' do
let(:note_author) { Factory.create(:user, name: 'author_name') } let(:note_author) { Factory.create(:user, name: 'author_name') }
let(:note) { Factory.create(:note, project: project, author: note_author) } let(:note) { Factory.create(:note, project: project, author: note_author) }
......
# == Schema Information
#
# Table name: events
#
# id :integer(4) not null, primary key
# target_type :string(255)
# target_id :integer(4)
# title :string(255)
# data :text
# project_id :integer(4)
# created_at :datetime not null
# updated_at :datetime not null
# action :integer(4)
# author_id :integer(4)
#
require 'spec_helper' require 'spec_helper'
describe Event do describe Event do
describe "Associations" do describe "Associations" do
it { should belong_to(:project) } it { should belong_to(:project) }
it { should belong_to(:target) }
end end
describe "Respond to" do describe "Respond to" do
...@@ -29,16 +14,6 @@ describe Event do ...@@ -29,16 +14,6 @@ describe Event do
it { should respond_to(:commits) } it { should respond_to(:commits) }
end end
describe "Creation" do
before do
@event = Factory :event
end
it "should create a valid event" do
@event.should be_valid
end
end
describe "Push event" do describe "Push event" do
before do before do
project = Factory :project project = Factory :project
......
...@@ -2,28 +2,19 @@ require 'spec_helper' ...@@ -2,28 +2,19 @@ require 'spec_helper'
describe Issue do describe Issue do
describe "Associations" do describe "Associations" do
it { should belong_to(:project) }
it { should belong_to(:author) }
it { should belong_to(:assignee) }
it { should belong_to(:milestone) } it { should belong_to(:milestone) }
end end
describe "Validation" do describe "Validation" do
it { should validate_presence_of(:title) } it { should ensure_length_of(:description).is_within(0..2000) }
it { should validate_presence_of(:author_id) }
it { should validate_presence_of(:project_id) }
end end
describe "Scope" do describe 'modules' do
it { Issue.should respond_to :closed } it { should include_module(IssueCommonality) }
it { Issue.should respond_to :opened } it { should include_module(Upvote) }
end end
subject { Factory.create(:issue, subject { Factory.create(:issue) }
author: Factory(:user),
assignee: Factory(:user),
project: Factory.create(:project)) }
it { should be_valid }
describe '#is_being_reassigned?' do describe '#is_being_reassigned?' do
it 'returns true if the issue assignee has changed' do it 'returns true if the issue assignee has changed' do
...@@ -41,11 +32,7 @@ describe Issue do ...@@ -41,11 +32,7 @@ describe Issue do
subject.is_being_closed?.should be_true subject.is_being_closed?.should be_true
end end
it 'returns false if the closed attribute has changed and is now false' do it 'returns false if the closed attribute has changed and is now false' do
issue = Factory.create(:issue, issue = Factory.create(:closed_issue)
closed: true,
author: Factory(:user),
assignee: Factory(:user),
project: Factory.create(:project))
issue.closed = false issue.closed = false
issue.is_being_closed?.should be_false issue.is_being_closed?.should be_false
end end
...@@ -57,11 +44,7 @@ describe Issue do ...@@ -57,11 +44,7 @@ describe Issue do
describe '#is_being_reopened?' do describe '#is_being_reopened?' do
it 'returns true if the closed attribute has changed and is now false' do it 'returns true if the closed attribute has changed and is now false' do
issue = Factory.create(:issue, issue = Factory.create(:closed_issue)
closed: true,
author: Factory(:user),
assignee: Factory(:user),
project: Factory.create(:project))
issue.closed = false issue.closed = false
issue.is_being_reopened?.should be_true issue.is_being_reopened?.should be_true
end end
...@@ -73,64 +56,4 @@ describe Issue do ...@@ -73,64 +56,4 @@ describe Issue do
subject.is_being_reopened?.should be_false subject.is_being_reopened?.should be_false
end end
end end
describe "plus 1" do
let(:project) { Factory(:project) }
subject {
Factory.create(:issue,
author: Factory(:user),
assignee: Factory(:user),
project: project)
}
it "with no notes has a 0/0 score" do
subject.upvotes.should == 0
end
it "should recognize non-+1 notes" do
subject.notes << Factory(:note, note: "No +1 here", project: Factory(:project, path: 'plusone', code: 'plusone'))
subject.should have(1).note
subject.notes.first.upvote?.should be_false
subject.upvotes.should == 0
end
it "should recognize a single +1 note" do
subject.notes << Factory(:note, note: "+1 This is awesome", project: Factory(:project, path: 'plusone', code: 'plusone'))
subject.upvotes.should == 1
end
it "should recognize a multiple +1 notes" do
subject.notes << Factory(:note, note: "+1 This is awesome", project: Factory(:project, path: 'plusone', code: 'plusone'))
subject.notes << Factory(:note, note: "+1 I want this", project: Factory(:project, path: 'plustwo', code: 'plustwo'))
subject.upvotes.should == 2
end
end
describe ".search" do
let!(:issue) { Factory.create(:issue, title: "Searchable issue",
project: Factory.create(:project)) }
it "matches by title" do
Issue.search('able').all.should == [issue]
end
end
end end
# == Schema Information
#
# Table name: issues
#
# id :integer(4) not null, primary key
# title :string(255)
# assignee_id :integer(4)
# author_id :integer(4)
# project_id :integer(4)
# created_at :datetime not null
# updated_at :datetime not null
# closed :boolean(1) default(FALSE), not null
# position :integer(4) default(0)
# critical :boolean(1) default(FALSE), not null
# branch_name :string(255)
# description :text
# milestone_id :integer(4)
#
...@@ -2,12 +2,15 @@ require 'spec_helper' ...@@ -2,12 +2,15 @@ require 'spec_helper'
describe Key do describe Key do
describe "Associations" do describe "Associations" do
it { should belong_to(:user) or belong_to(:project) } it { should belong_to(:user) }
it { should belong_to(:project) }
end end
describe "Validation" do describe "Validation" do
it { should validate_presence_of(:title) } it { should validate_presence_of(:title) }
it { should validate_presence_of(:key) } it { should validate_presence_of(:key) }
it { should ensure_length_of(:title).is_within(0..255) }
it { should ensure_length_of(:key).is_within(0..5000) }
end end
describe "Methods" do describe "Methods" do
...@@ -17,20 +20,15 @@ describe Key do ...@@ -17,20 +20,15 @@ describe Key do
context "validation of uniqueness" do context "validation of uniqueness" do
context "as a deploy key" do context "as a deploy key" do
let(:project) { Factory.create(:project, path: 'alpha', code: 'alpha') } let!(:deploy_key) { create(:deploy_key) }
let(:another_project) { Factory.create(:project, path: 'beta', code: 'beta') }
before do
deploy_key = Factory.create(:key, project: project)
end
it "does not accept the same key twice for a project" do it "does not accept the same key twice for a project" do
key = Factory.new(:key, project: project) key = build(:key, project: deploy_key.project)
key.should_not be_valid key.should_not be_valid
end end
it "does accept the same key for another project" do it "does accept the same key for another project" do
key = Factory.new(:key, project: another_project) key = build(:key, project_id: 0)
key.should be_valid key.should be_valid
end end
end end
...@@ -39,27 +37,13 @@ describe Key do ...@@ -39,27 +37,13 @@ describe Key do
let(:user) { Factory.create(:user) } let(:user) { Factory.create(:user) }
it "accepts the key once" do it "accepts the key once" do
Factory.new(:key, user: user).should be_valid build(:key, user: user).should be_valid
end end
it "does not accepts the key twice" do it "does not accepts the key twice" do
Factory.create(:key, user: user) create(:key, user: user)
Factory.new(:key, user: user).should_not be_valid build(:key, user: user).should_not be_valid
end end
end end
end end
end end
# == Schema Information
#
# Table name: keys
#
# id :integer(4) not null, primary key
# user_id :integer(4)
# created_at :datetime not null
# updated_at :datetime not null
# key :text
# title :string(255)
# identifier :string(255)
# project_id :integer(4)
#
require 'spec_helper' require 'spec_helper'
describe MergeRequest do describe MergeRequest do
describe "Associations" do
it { should belong_to(:project) }
it { should belong_to(:author) }
it { should belong_to(:assignee) }
end
describe "Validation" do describe "Validation" do
it { should validate_presence_of(:target_branch) } it { should validate_presence_of(:target_branch) }
it { should validate_presence_of(:source_branch) } it { should validate_presence_of(:source_branch) }
it { should validate_presence_of(:title) }
it { should validate_presence_of(:author_id) }
it { should validate_presence_of(:project_id) }
end end
describe "Scope" do describe 'modules' do
it { MergeRequest.should respond_to :closed } it { should include_module(IssueCommonality) }
it { MergeRequest.should respond_to :opened } it { should include_module(Upvote) }
end
it { Factory.create(:merge_request,
author: Factory(:user),
assignee: Factory(:user),
project: Factory.create(:project)).should be_valid }
describe "plus 1" do
let(:project) { Factory(:project) }
subject {
Factory.create(:merge_request,
author: Factory(:user),
assignee: Factory(:user),
project: project)
}
it "with no notes has a 0/0 score" do
subject.upvotes.should == 0
end
it "should recognize non-+1 notes" do
subject.notes << Factory(:note, note: "No +1 here", project: Factory(:project, path: 'plusone', code: 'plusone'))
subject.should have(1).note
subject.notes.first.upvote?.should be_false
subject.upvotes.should == 0
end
it "should recognize a single +1 note" do
subject.notes << Factory(:note, note: "+1 This is awesome", project: Factory(:project, path: 'plusone', code: 'plusone'))
subject.upvotes.should == 1
end
it "should recognize a multiple +1 notes" do
subject.notes << Factory(:note, note: "+1 This is awesome", project: Factory(:project, path: 'plusone', code: 'plusone'))
subject.notes << Factory(:note, note: "+1 I want this", project: Factory(:project, path: 'plustwo', code: 'plustwo'))
subject.upvotes.should == 2
end
end
describe ".search" do
let!(:issue) { Factory.create(:issue, title: "Searchable issue",
project: Factory.create(:project)) }
it "matches by title" do
Issue.search('able').all.should == [issue]
end
end end
end end
# == Schema Information
#
# Table name: merge_requests
#
# id :integer(4) not null, primary key
# target_branch :string(255) not null
# source_branch :string(255) not null
# project_id :integer(4) not null
# author_id :integer(4)
# assignee_id :integer(4)
# title :string(255)
# closed :boolean(1) default(FALSE), not null
# created_at :datetime not null
# updated_at :datetime not null
# st_commits :text(2147483647
# st_diffs :text(2147483647
# merged :boolean(1) default(FALSE), not null
# state :integer(4) default(1), not null
#
# == Schema Information
#
# Table name: milestones
#
# id :integer(4) not null, primary key
# title :string(255) not null
# project_id :integer(4) not null
# description :text
# due_date :date
# closed :boolean(1) default(FALSE), not null
# created_at :datetime not null
# updated_at :datetime not null
#
require 'spec_helper' require 'spec_helper'
describe Milestone do describe Milestone do
...@@ -25,30 +11,36 @@ describe Milestone do ...@@ -25,30 +11,36 @@ describe Milestone do
it { should validate_presence_of(:project_id) } it { should validate_presence_of(:project_id) }
end end
let(:project) { Factory :project } let(:milestone) { Factory :milestone }
let(:milestone) { Factory :milestone, project: project } let(:issue) { Factory :issue }
let(:issue) { Factory :issue, project: project }
it { milestone.should be_valid }
describe "Issues" do describe "#percent_complete" do
before do it "should not count open issues" do
milestone.issues << issue milestone.issues << issue
milestone.percent_complete.should == 0
end end
it { milestone.percent_complete.should == 0 } it "should count closed issues" do
issue.update_attributes(closed: true)
milestone.issues << issue
milestone.percent_complete.should == 100
end
it do it "should recover from dividing by zero" do
issue.update_attributes closed: true milestone.issues.should_receive(:count).and_return(0)
milestone.percent_complete.should == 100 milestone.percent_complete.should == 100
end end
end end
describe :expires_at do describe "#expires_at" do
before do it "should be nil when due_date is unset" do
milestone.update_attributes due_date: Date.today + 1.day milestone.update_attributes(due_date: nil)
milestone.expires_at.should be_nil
end end
it { milestone.expires_at.should_not be_nil } it "should not be nil when due_date is set" do
milestone.update_attributes(due_date: Date.tomorrow)
milestone.expires_at.should be_present
end
end end
end end
require 'spec_helper' require 'spec_helper'
describe Note do describe Note do
let(:project) { Factory :project }
let!(:commit) { project.commit }
describe "Associations" do describe "Associations" do
it { should belong_to(:project) } it { should belong_to(:project) }
it { should belong_to(:noteable) }
it { should belong_to(:author).class_name('User') }
end end
describe "Validation" do describe "Validation" do
...@@ -13,8 +12,6 @@ describe Note do ...@@ -13,8 +12,6 @@ describe Note do
it { should validate_presence_of(:project) } it { should validate_presence_of(:project) }
end end
it { Factory.create(:note,
project: project).should be_valid }
describe "Scopes" do describe "Scopes" do
it "should have a today named scope that returns ..." do it "should have a today named scope that returns ..." do
Note.today.where_values.should == ["created_at >= '#{Date.today}'"] Note.today.where_values.should == ["created_at >= '#{Date.today}'"]
...@@ -25,26 +22,27 @@ describe Note do ...@@ -25,26 +22,27 @@ describe Note do
let(:project) { Factory(:project) } let(:project) { Factory(:project) }
it "recognizes a neutral note" do it "recognizes a neutral note" do
note = Factory(:note, project: project, note: "This is not a +1 note") note = Factory(:note, note: "This is not a +1 note")
note.should_not be_upvote note.should_not be_upvote
end end
it "recognizes a +1 note" do it "recognizes a +1 note" do
note = Factory(:note, project: project, note: "+1 for this") note = Factory(:note, note: "+1 for this")
note.should be_upvote note.should be_upvote
end end
it "recognizes a -1 note as no vote" do it "recognizes a -1 note as no vote" do
note = Factory(:note, project: project, note: "-1 for this") note = Factory(:note, note: "-1 for this")
note.should_not be_upvote note.should_not be_upvote
end end
end end
describe "Commit notes" do let(:project) { create(:project) }
let(:commit) { project.commit }
describe "Commit notes" do
before do before do
@note = Factory :note, @note = Factory :note,
project: project,
noteable_id: commit.id, noteable_id: commit.id,
noteable_type: "Commit" noteable_type: "Commit"
end end
...@@ -58,7 +56,6 @@ describe Note do ...@@ -58,7 +56,6 @@ describe Note do
describe "Pre-line commit notes" do describe "Pre-line commit notes" do
before do before do
@note = Factory :note, @note = Factory :note,
project: project,
noteable_id: commit.id, noteable_id: commit.id,
noteable_type: "Commit", noteable_type: "Commit",
line_code: "0_16_1" line_code: "0_16_1"
...@@ -91,8 +88,8 @@ describe Note do ...@@ -91,8 +88,8 @@ describe Note do
describe :authorization do describe :authorization do
before do before do
@p1 = project @p1 = create(:project)
@p2 = Factory :project, code: "alien", path: "gitlabhq_1" @p2 = Factory :project
@u1 = Factory :user @u1 = Factory :user
@u2 = Factory :user @u2 = Factory :user
@u3 = Factory :user @u3 = Factory :user
...@@ -135,19 +132,3 @@ describe Note do ...@@ -135,19 +132,3 @@ describe Note do
end end
end end
end end
# == Schema Information
#
# Table name: notes
#
# id :integer(4) not null, primary key
# note :text
# noteable_id :string(255)
# noteable_type :string(255)
# author_id :integer(4)
# created_at :datetime not null
# updated_at :datetime not null
# project_id :integer(4)
# attachment :string(255)
# line_code :string(255)
#
...@@ -2,23 +2,52 @@ require 'spec_helper' ...@@ -2,23 +2,52 @@ require 'spec_helper'
describe Project do describe Project do
describe "Associations" do describe "Associations" do
it { should belong_to(:owner).class_name('User') }
it { should have_many(:users) } it { should have_many(:users) }
it { should have_many(:protected_branches).dependent(:destroy) }
it { should have_many(:events).dependent(:destroy) } it { should have_many(:events).dependent(:destroy) }
it { should have_many(:wikis).dependent(:destroy) }
it { should have_many(:merge_requests).dependent(:destroy) } it { should have_many(:merge_requests).dependent(:destroy) }
it { should have_many(:users_projects).dependent(:destroy) }
it { should have_many(:issues).dependent(:destroy) } it { should have_many(:issues).dependent(:destroy) }
it { should have_many(:milestones).dependent(:destroy) }
it { should have_many(:users_projects).dependent(:destroy) }
it { should have_many(:notes).dependent(:destroy) } it { should have_many(:notes).dependent(:destroy) }
it { should have_many(:snippets).dependent(:destroy) } it { should have_many(:snippets).dependent(:destroy) }
it { should have_many(:hooks).dependent(:destroy) }
it { should have_many(:deploy_keys).dependent(:destroy) } it { should have_many(:deploy_keys).dependent(:destroy) }
it { should have_many(:hooks).dependent(:destroy) }
it { should have_many(:wikis).dependent(:destroy) }
it { should have_many(:protected_branches).dependent(:destroy) }
end end
describe "Validation" do describe "Validation" do
let!(:project) { create(:project) }
it { should validate_presence_of(:name) } it { should validate_presence_of(:name) }
it { should validate_uniqueness_of(:name) }
it { should ensure_length_of(:name).is_within(0..255) }
it { should validate_presence_of(:path) } it { should validate_presence_of(:path) }
it { should validate_uniqueness_of(:path) }
it { should ensure_length_of(:path).is_within(0..255) }
# TODO: Formats
it { should ensure_length_of(:description).is_within(0..2000) }
it { should validate_presence_of(:code) } it { should validate_presence_of(:code) }
it { should validate_uniqueness_of(:code) }
it { should ensure_length_of(:code).is_within(1..255) }
# TODO: Formats
it { should validate_presence_of(:owner) }
it "should not allow new projects beyond user limits" do
project.stub(:owner).and_return(double(can_create_project?: false, projects_limit: 1))
project.should_not be_valid
project.errors[:base].first.should match(/Your own projects limit is 1/)
end
it "should not allow 'gitolite-admin' as repo name" do
should allow_value("blah").for(:path)
should_not allow_value("gitolite-admin").for(:path)
end
end end
describe "Respond to" do describe "Respond to" do
...@@ -40,7 +69,6 @@ describe Project do ...@@ -40,7 +69,6 @@ describe Project do
it { should respond_to(:commits_with_refs) } it { should respond_to(:commits_with_refs) }
it { should respond_to(:commits_since) } it { should respond_to(:commits_since) }
it { should respond_to(:commits_between) } it { should respond_to(:commits_between) }
it { should respond_to(:write_hooks) }
it { should respond_to(:satellite) } it { should respond_to(:satellite) }
it { should respond_to(:update_repository) } it { should respond_to(:update_repository) }
it { should respond_to(:destroy_repository) } it { should respond_to(:destroy_repository) }
...@@ -74,9 +102,11 @@ describe Project do ...@@ -74,9 +102,11 @@ describe Project do
it { should respond_to(:trigger_post_receive) } it { should respond_to(:trigger_post_receive) }
end end
it "should not allow 'gitolite-admin' as repo name" do describe 'modules' do
should allow_value("blah").for(:path) it { should include_module(Repository) }
should_not allow_value("gitolite-admin").for(:path) it { should include_module(PushObserver) }
it { should include_module(Authority) }
it { should include_module(Team) }
end end
it "should return valid url to repo" do it "should return valid url to repo" do
...@@ -86,7 +116,7 @@ describe Project do ...@@ -86,7 +116,7 @@ describe Project do
it "should return path to repo" do it "should return path to repo" do
project = Project.new(path: "somewhere") project = Project.new(path: "somewhere")
project.path_to_repo.should == File.join(Rails.root, "tmp", "tests", "somewhere") project.path_to_repo.should == File.join(Rails.root, "tmp", "repositories", "somewhere")
end end
it "returns the full web URL for this repo" do it "returns the full web URL for this repo" do
...@@ -111,7 +141,7 @@ describe Project do ...@@ -111,7 +141,7 @@ describe Project do
let(:last_event) { double } let(:last_event) { double }
before do before do
project.stub(:events).and_return( [ double, double, last_event ] ) project.stub_chain(:events, :order).and_return( [ double, double, last_event ] )
end end
it { project.last_activity.should == last_event } it { project.last_activity.should == last_event }
...@@ -237,23 +267,3 @@ describe Project do ...@@ -237,23 +267,3 @@ describe Project do
end end
end end
end end
# == Schema Information
#
# Table name: projects
#
# id :integer(4) not null, primary key
# name :string(255)
# path :string(255)
# description :text
# created_at :datetime not null
# updated_at :datetime not null
# private_flag :boolean(1) default(TRUE), not null
# code :string(255)
# owner_id :integer(4)
# default_branch :string(255) default("master"), not null
# issues_enabled :boolean(1) default(TRUE), not null
# wall_enabled :boolean(1) default(TRUE), not null
# merge_requests_enabled :boolean(1) default(TRUE), not null
# wiki_enabled :boolean(1) default(TRUE), not null
#
# == Schema Information
#
# Table name: protected_branches
#
# id :integer(4) not null, primary key
# project_id :integer(4) not null
# name :string(255) not null
# created_at :datetime not null
# updated_at :datetime not null
#
require 'spec_helper' require 'spec_helper'
describe ProtectedBranch do describe ProtectedBranch do
let(:project) { Factory(:project) }
describe 'Associations' do describe 'Associations' do
it { should belong_to(:project) } it { should belong_to(:project) }
end end
...@@ -24,26 +11,26 @@ describe ProtectedBranch do ...@@ -24,26 +11,26 @@ describe ProtectedBranch do
end end
describe 'Callbacks' do describe 'Callbacks' do
subject { ProtectedBranch.new(project: project, name: 'branch_name') } let(:branch) { build(:protected_branch) }
it 'call update_repository after save' do it 'call update_repository after save' do
subject.should_receive(:update_repository) branch.should_receive(:update_repository)
subject.save branch.save
end end
it 'call update_repository after destroy' do it 'call update_repository after destroy' do
subject.should_receive(:update_repository) branch.save
subject.destroy branch.should_receive(:update_repository)
branch.destroy
end end
end end
describe '#commit' do describe '#commit' do
subject { ProtectedBranch.new(project: project, name: 'cant_touch_this') } let(:branch) { create(:protected_branch) }
it 'commits itself to its project' do it 'commits itself to its project' do
project.should_receive(:commit).with('cant_touch_this') branch.project.should_receive(:commit).with(branch.name)
branch.commit
subject.commit
end end
end end
end end
...@@ -3,29 +3,21 @@ require 'spec_helper' ...@@ -3,29 +3,21 @@ require 'spec_helper'
describe Snippet do describe Snippet do
describe "Associations" do describe "Associations" do
it { should belong_to(:project) } it { should belong_to(:project) }
it { should belong_to(:author) } it { should belong_to(:author).class_name('User') }
it { should have_many(:notes).dependent(:destroy) }
end end
describe "Validation" do describe "Validation" do
it { should validate_presence_of(:title) }
it { should validate_presence_of(:author_id) } it { should validate_presence_of(:author_id) }
it { should validate_presence_of(:project_id) } it { should validate_presence_of(:project_id) }
it { should validate_presence_of(:title) }
it { should ensure_length_of(:title).is_within(0..255) }
it { should validate_presence_of(:file_name) } it { should validate_presence_of(:file_name) }
it { should ensure_length_of(:title).is_within(0..255) }
it { should validate_presence_of(:content) } it { should validate_presence_of(:content) }
it { should ensure_length_of(:content).is_within(0..10_000) }
end end
end end
# == Schema Information
#
# Table name: snippets
#
# id :integer(4) not null, primary key
# title :string(255)
# content :text
# author_id :integer(4) not null
# project_id :integer(4) not null
# created_at :datetime not null
# updated_at :datetime not null
# file_name :string(255)
# expires_at :datetime
#
...@@ -10,13 +10,12 @@ describe SystemHook do ...@@ -10,13 +10,12 @@ describe SystemHook do
end end
it "project_create hook" do it "project_create hook" do
user = Factory :user
with_resque do with_resque do
project = Factory :project_without_owner, owner: user project = Factory :project
end end
WebMock.should have_requested(:post, @system_hook.url).with(body: /project_create/).once WebMock.should have_requested(:post, @system_hook.url).with(body: /project_create/).once
end end
it "project_destroy hook" do it "project_destroy hook" do
project = Factory :project project = Factory :project
with_resque do with_resque do
...@@ -31,7 +30,7 @@ describe SystemHook do ...@@ -31,7 +30,7 @@ describe SystemHook do
end end
WebMock.should have_requested(:post, @system_hook.url).with(body: /user_create/).once WebMock.should have_requested(:post, @system_hook.url).with(body: /user_create/).once
end end
it "user_destroy hook" do it "user_destroy hook" do
user = Factory :user user = Factory :user
with_resque do with_resque do
...@@ -39,7 +38,7 @@ describe SystemHook do ...@@ -39,7 +38,7 @@ describe SystemHook do
end end
WebMock.should have_requested(:post, @system_hook.url).with(body: /user_destroy/).once WebMock.should have_requested(:post, @system_hook.url).with(body: /user_destroy/).once
end end
it "project_create hook" do it "project_create hook" do
user = Factory :user user = Factory :user
project = Factory :project project = Factory :project
...@@ -48,7 +47,7 @@ describe SystemHook do ...@@ -48,7 +47,7 @@ describe SystemHook do
end end
WebMock.should have_requested(:post, @system_hook.url).with(body: /user_add_to_team/).once WebMock.should have_requested(:post, @system_hook.url).with(body: /user_add_to_team/).once
end end
it "project_destroy hook" do it "project_destroy hook" do
user = Factory :user user = Factory :user
project = Factory :project project = Factory :project
......
...@@ -2,12 +2,26 @@ require 'spec_helper' ...@@ -2,12 +2,26 @@ require 'spec_helper'
describe User do describe User do
describe "Associations" do describe "Associations" do
it { should have_many(:users_projects).dependent(:destroy) }
it { should have_many(:projects) } it { should have_many(:projects) }
it { should have_many(:users_projects) } it { should have_many(:my_own_projects).class_name('Project') }
it { should have_many(:issues) } it { should have_many(:keys).dependent(:destroy) }
it { should have_many(:assigned_issues) } it { should have_many(:events).class_name('Event').dependent(:destroy) }
it { should have_many(:merge_requests) } it { should have_many(:recent_events).class_name('Event') }
it { should have_many(:assigned_merge_requests) } it { should have_many(:issues).dependent(:destroy) }
it { should have_many(:notes).dependent(:destroy) }
it { should have_many(:assigned_issues).dependent(:destroy) }
it { should have_many(:merge_requests).dependent(:destroy) }
it { should have_many(:assigned_merge_requests).dependent(:destroy) }
end
describe 'validations' do
it { should validate_presence_of(:projects_limit) }
it { should validate_numericality_of(:projects_limit) }
it { should allow_value(0).for(:projects_limit) }
it { should_not allow_value(-1).for(:projects_limit) }
it { should ensure_length_of(:bio).is_within(0..255) }
end end
describe "Respond to" do describe "Respond to" do
...@@ -49,49 +63,4 @@ describe User do ...@@ -49,49 +63,4 @@ describe User do
user = Factory(:user) user = Factory(:user)
user.authentication_token.should_not == "" user.authentication_token.should_not == ""
end end
describe "dependent" do
before do
@user = Factory :user
@note = Factory :note,
author: @user,
project: Factory(:project)
end
it "should destroy all notes with user" do
Note.find_by_id(@note.id).should_not be_nil
@user.destroy
Note.find_by_id(@note.id).should be_nil
end
end
end end
# == Schema Information
#
# Table name: users
#
# id :integer(4) not null, primary key
# email :string(255) default(""), not null
# encrypted_password :string(128) default(""), not null
# reset_password_token :string(255)
# reset_password_sent_at :datetime
# remember_created_at :datetime
# sign_in_count :integer(4) default(0)
# current_sign_in_at :datetime
# last_sign_in_at :datetime
# current_sign_in_ip :string(255)
# last_sign_in_ip :string(255)
# created_at :datetime not null
# updated_at :datetime not null
# name :string(255)
# admin :boolean(1) default(FALSE), not null
# projects_limit :integer(4) default(10)
# skype :string(255) default(""), not null
# linkedin :string(255) default(""), not null
# twitter :string(255) default(""), not null
# authentication_token :string(255)
# dark_scheme :boolean(1) default(FALSE), not null
# theme_id :integer(4) default(1), not null
# bio :string(255)
# blocked :boolean(1) default(FALSE), not null
#
...@@ -7,7 +7,11 @@ describe UsersProject do ...@@ -7,7 +7,11 @@ describe UsersProject do
end end
describe "Validation" do describe "Validation" do
let!(:users_project) { create(:users_project) }
it { should validate_presence_of(:user_id) } it { should validate_presence_of(:user_id) }
it { should validate_uniqueness_of(:user_id).scoped_to(:project_id) }
it { should validate_presence_of(:project_id) } it { should validate_presence_of(:project_id) }
end end
...@@ -16,15 +20,3 @@ describe UsersProject do ...@@ -16,15 +20,3 @@ describe UsersProject do
it { should respond_to(:user_email) } it { should respond_to(:user_email) }
end end
end end
# == Schema Information
#
# Table name: users_projects
#
# id :integer(4) not null, primary key
# user_id :integer(4) not null
# project_id :integer(4) not null
# created_at :datetime not null
# updated_at :datetime not null
# project_access :integer(4) default(0), not null
#
...@@ -52,14 +52,3 @@ describe ProjectHook do ...@@ -52,14 +52,3 @@ describe ProjectHook do
end end
end end
end end
# == Schema Information
#
# Table name: web_hooks
#
# id :integer(4) not null, primary key
# url :string(255)
# project_id :integer(4)
# created_at :datetime not null
# updated_at :datetime not null
#
...@@ -4,27 +4,13 @@ describe Wiki do ...@@ -4,27 +4,13 @@ describe Wiki do
describe "Associations" do describe "Associations" do
it { should belong_to(:project) } it { should belong_to(:project) }
it { should belong_to(:user) } it { should belong_to(:user) }
it { should have_many(:notes).dependent(:destroy) }
end end
describe "Validation" do describe "Validation" do
it { should validate_presence_of(:title) } it { should validate_presence_of(:title) }
it { should ensure_length_of(:title).is_within(1..250) }
it { should validate_presence_of(:content) } it { should validate_presence_of(:content) }
it { should validate_presence_of(:user_id) } it { should validate_presence_of(:user_id) }
end end
it { Factory(:wiki).should be_valid }
end end
# == Schema Information
#
# Table name: wikis
#
# id :integer(4) not null, primary key
# title :string(255)
# content :text
# project_id :integer(4)
# created_at :datetime not null
# updated_at :datetime not null
# slug :string(255)
# user_id :integer(4)
#
# Stubbing Project <-> git host path
# create project using Factory only
class Project
def update_repository
true
end
def destroy_repository
true
end
def path_to_repo
File.join(Rails.root, "tmp", "tests", path)
end
def satellite
@satellite ||= FakeSatellite.new
end
end
class Key
def update_repository
true
end
def repository_delete_key
true
end
end
class UsersProject
def update_repository
true
end
end
class FakeSatellite
def exists?
true
end
def create
true
end
end
class ProtectedBranch
def update_repository
true
end
end
...@@ -3,7 +3,8 @@ require 'spec_helper' ...@@ -3,7 +3,8 @@ require 'spec_helper'
describe IssueObserver do describe IssueObserver do
let(:some_user) { double(:user, id: 1) } let(:some_user) { double(:user, id: 1) }
let(:assignee) { double(:user, id: 2) } let(:assignee) { double(:user, id: 2) }
let(:issue) { double(:issue, id: 42, assignee: assignee) } let(:author) { double(:user, id: 3) }
let(:issue) { double(:issue, id: 42, assignee: assignee, author: author) }
before(:each) { subject.stub(:current_user).and_return(some_user) } before(:each) { subject.stub(:current_user).and_return(some_user) }
...@@ -67,36 +68,90 @@ describe IssueObserver do ...@@ -67,36 +68,90 @@ describe IssueObserver do
end end
end end
context 'a status "closed" note' do context 'a status "closed"' do
it 'is created if the issue is being closed' do it 'note is created if the issue is being closed' do
issue.should_receive(:is_being_closed?).and_return(true) issue.should_receive(:is_being_closed?).and_return(true)
Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed') Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed')
subject.after_update(issue) subject.after_update(issue)
end end
it 'is not created if the issue is not being closed' do it 'note is not created if the issue is not being closed' do
issue.should_receive(:is_being_closed?).and_return(false) issue.should_receive(:is_being_closed?).and_return(false)
Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'closed') Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'closed')
subject.after_update(issue) subject.after_update(issue)
end end
it 'notification is delivered if the issue being closed' do
issue.stub(:is_being_closed?).and_return(true)
Notify.should_receive(:issue_status_changed_email).twice
Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed')
subject.after_update(issue)
end
it 'notification is not delivered if the issue not being closed' do
issue.stub(:is_being_closed?).and_return(false)
Notify.should_not_receive(:issue_status_changed_email)
Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'closed')
subject.after_update(issue)
end
it 'notification is delivered only to author if the issue being closed' do
issue_without_assignee = double(:issue, id: 42, author: author, assignee: nil)
issue_without_assignee.stub(:is_being_reassigned?).and_return(false)
issue_without_assignee.stub(:is_being_closed?).and_return(true)
issue_without_assignee.stub(:is_being_reopened?).and_return(false)
Notify.should_receive(:issue_status_changed_email).once
Note.should_receive(:create_status_change_note).with(issue_without_assignee, some_user, 'closed')
subject.after_update(issue_without_assignee)
end
end end
context 'a status "reopened" note' do context 'a status "reopened"' do
it 'is created if the issue is being reopened' do it 'note is created if the issue is being reopened' do
issue.should_receive(:is_being_reopened?).and_return(true) issue.should_receive(:is_being_reopened?).and_return(true)
Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened') Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened')
subject.after_update(issue) subject.after_update(issue)
end end
it 'is not created if the issue is not being reopened' do it 'note is not created if the issue is not being reopened' do
issue.should_receive(:is_being_reopened?).and_return(false) issue.should_receive(:is_being_reopened?).and_return(false)
Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'reopened') Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'reopened')
subject.after_update(issue) subject.after_update(issue)
end end
it 'notification is delivered if the issue being reopened' do
issue.stub(:is_being_reopened?).and_return(true)
Notify.should_receive(:issue_status_changed_email).twice
Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened')
subject.after_update(issue)
end
it 'notification is not delivered if the issue not being reopened' do
issue.stub(:is_being_reopened?).and_return(false)
Notify.should_not_receive(:issue_status_changed_email)
Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'reopened')
subject.after_update(issue)
end
it 'notification is delivered only to author if the issue being reopened' do
issue_without_assignee = double(:issue, id: 42, author: author, assignee: nil)
issue_without_assignee.stub(:is_being_reassigned?).and_return(false)
issue_without_assignee.stub(:is_being_closed?).and_return(false)
issue_without_assignee.stub(:is_being_reopened?).and_return(true)
Notify.should_receive(:issue_status_changed_email).once
Note.should_receive(:create_status_change_note).with(issue_without_assignee, some_user, 'reopened')
subject.after_update(issue_without_assignee)
end
end end
end end
......
require 'spec_helper'
describe KeyObserver do
before do
@key = double('Key',
identifier: 'admin_654654',
key: '== a vaild ssh key',
projects: [],
is_deploy_key: false
)
@gitolite = double('Gitlab::Gitolite',
set_key: true,
remove_key: true
)
@observer = KeyObserver.instance
@observer.stub(:git_host => @gitolite)
end
context :after_save do
it do
@gitolite.should_receive(:set_key).with(@key.identifier, @key.key, @key.projects)
@observer.after_save(@key)
end
end
context :after_destroy do
it do
@gitolite.should_receive(:remove_key).with(@key.identifier, @key.projects)
@observer.after_destroy(@key)
end
end
end
require 'spec_helper'
describe UsersProjectObserver do
let(:user) { Factory.create :user }
let(:project) { Factory.create(:project,
code: "Fuu",
path: "Fuu" ) }
let(:users_project) { Factory.create(:users_project,
project: project,
user: user )}
subject { UsersProjectObserver.instance }
describe "#after_create" do
it "should called when UsersProject created" do
subject.should_receive(:after_create)
UsersProject.observers.enable :users_project_observer do
Factory.create(:users_project,
project: project,
user: user)
end
end
it "should send email to user" do
Notify.should_receive(:project_access_granted_email).with(users_project.id).and_return(double(deliver: true))
subject.after_create(users_project)
end
end
describe "#after_update" do
it "should called when UsersProject updated" do
subject.should_receive(:after_update)
UsersProject.observers.enable :users_project_observer do
users_project.update_attribute(:project_access, 40)
end
end
it "should send email to user" do
Notify.should_receive(:project_access_granted_email).with(users_project.id).and_return(double(deliver: true))
subject.after_update(users_project)
end
end
end
...@@ -87,7 +87,7 @@ describe "Admin::Projects" do ...@@ -87,7 +87,7 @@ describe "Admin::Projects" do
visit new_admin_project_path visit new_admin_project_path
fill_in 'project_name', with: 'NewProject' fill_in 'project_name', with: 'NewProject'
fill_in 'project_code', with: 'NPR' fill_in 'project_code', with: 'NPR'
fill_in 'project_path', with: 'gitlabhq_1' fill_in 'project_path', with: 'newproject'
expect { click_button "Create project" }.to change { Project.count }.by(1) expect { click_button "Create project" }.to change { Project.count }.by(1)
@project = Project.last @project = Project.last
end end
......
...@@ -2,20 +2,26 @@ require 'spec_helper' ...@@ -2,20 +2,26 @@ require 'spec_helper'
describe "Admin::Projects" do describe "Admin::Projects" do
describe "GET /admin/projects" do describe "GET /admin/projects" do
it { admin_projects_path.should be_allowed_for :admin } subject { admin_projects_path }
it { admin_projects_path.should be_denied_for :user }
it { admin_projects_path.should be_denied_for :visitor } it { should be_allowed_for :admin }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end end
describe "GET /admin/users" do describe "GET /admin/users" do
it { admin_users_path.should be_allowed_for :admin } subject { admin_users_path }
it { admin_users_path.should be_denied_for :user }
it { admin_users_path.should be_denied_for :visitor } it { should be_allowed_for :admin }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end end
describe "GET /admin/hooks" do describe "GET /admin/hooks" do
it { admin_hooks_path.should be_allowed_for :admin } subject { admin_hooks_path }
it { admin_hooks_path.should be_denied_for :user }
it { admin_hooks_path.should be_denied_for :visitor } it { should be_allowed_for :admin }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end end
end end
require 'spec_helper' require 'spec_helper'
describe Gitlab::API do describe Gitlab::API do
include ApiHelpers
let(:user) { Factory :user } let(:user) { Factory :user }
let!(:project) { Factory :project, owner: user } let!(:project) { Factory :project, owner: user }
let!(:issue) { Factory :issue, author: user, assignee: user, project: project } let!(:issue) { Factory :issue, author: user, assignee: user, project: project }
...@@ -8,13 +10,13 @@ describe Gitlab::API do ...@@ -8,13 +10,13 @@ describe Gitlab::API do
describe "GET /issues" do describe "GET /issues" do
it "should return authentication error" do it "should return authentication error" do
get "#{api_prefix}/issues" get api("/issues")
response.status.should == 401 response.status.should == 401
end end
describe "authenticated GET /issues" do describe "authenticated GET /issues" do
it "should return an array of issues" do it "should return an array of issues" do
get "#{api_prefix}/issues?private_token=#{user.private_token}" get api("/issues", user)
response.status.should == 200 response.status.should == 200
json_response.should be_an Array json_response.should be_an Array
json_response.first['title'].should == issue.title json_response.first['title'].should == issue.title
...@@ -24,7 +26,7 @@ describe Gitlab::API do ...@@ -24,7 +26,7 @@ describe Gitlab::API do
describe "GET /projects/:id/issues" do describe "GET /projects/:id/issues" do
it "should return project issues" do it "should return project issues" do
get "#{api_prefix}/projects/#{project.code}/issues?private_token=#{user.private_token}" get api("/projects/#{project.code}/issues", user)
response.status.should == 200 response.status.should == 200
json_response.should be_an Array json_response.should be_an Array
json_response.first['title'].should == issue.title json_response.first['title'].should == issue.title
...@@ -33,7 +35,7 @@ describe Gitlab::API do ...@@ -33,7 +35,7 @@ describe Gitlab::API do
describe "GET /projects/:id/issues/:issue_id" do describe "GET /projects/:id/issues/:issue_id" do
it "should return a project issue by id" do it "should return a project issue by id" do
get "#{api_prefix}/projects/#{project.code}/issues/#{issue.id}?private_token=#{user.private_token}" get api("/projects/#{project.code}/issues/#{issue.id}", user)
response.status.should == 200 response.status.should == 200
json_response['title'].should == issue.title json_response['title'].should == issue.title
end end
...@@ -41,7 +43,7 @@ describe Gitlab::API do ...@@ -41,7 +43,7 @@ describe Gitlab::API do
describe "POST /projects/:id/issues" do describe "POST /projects/:id/issues" do
it "should create a new project issue" do it "should create a new project issue" do
post "#{api_prefix}/projects/#{project.code}/issues?private_token=#{user.private_token}", post api("/projects/#{project.code}/issues", user),
title: 'new issue', labels: 'label, label2' title: 'new issue', labels: 'label, label2'
response.status.should == 201 response.status.should == 201
json_response['title'].should == 'new issue' json_response['title'].should == 'new issue'
...@@ -52,7 +54,7 @@ describe Gitlab::API do ...@@ -52,7 +54,7 @@ describe Gitlab::API do
describe "PUT /projects/:id/issues/:issue_id" do describe "PUT /projects/:id/issues/:issue_id" do
it "should update a project issue" do it "should update a project issue" do
put "#{api_prefix}/projects/#{project.code}/issues/#{issue.id}?private_token=#{user.private_token}", put api("/projects/#{project.code}/issues/#{issue.id}", user),
title: 'updated title', labels: 'label2', closed: 1 title: 'updated title', labels: 'label2', closed: 1
response.status.should == 200 response.status.should == 200
json_response['title'].should == 'updated title' json_response['title'].should == 'updated title'
...@@ -63,9 +65,8 @@ describe Gitlab::API do ...@@ -63,9 +65,8 @@ describe Gitlab::API do
describe "DELETE /projects/:id/issues/:issue_id" do describe "DELETE /projects/:id/issues/:issue_id" do
it "should delete a project issue" do it "should delete a project issue" do
expect { delete api("/projects/#{project.code}/issues/#{issue.id}", user)
delete "#{api_prefix}/projects/#{project.code}/issues/#{issue.id}?private_token=#{user.private_token}" response.status.should == 405
}.to change { Issue.count }.by(-1)
end end
end end
end end
require 'spec_helper'
describe Gitlab::API do
include ApiHelpers
let(:user) { Factory :user }
let!(:project) { Factory :project, owner: user }
let!(:milestone) { Factory :milestone, project: project }
before { project.add_access(user, :read) }
describe "GET /projects/:id/milestones" do
it "should return project milestones" do
get api("/projects/#{project.code}/milestones", user)
response.status.should == 200
json_response.should be_an Array
json_response.first['title'].should == milestone.title
end
end
describe "GET /projects/:id/milestones/:milestone_id" do
it "should return a project milestone by id" do
get api("/projects/#{project.code}/milestones/#{milestone.id}", user)
response.status.should == 200
json_response['title'].should == milestone.title
end
end
describe "POST /projects/:id/milestones" do
it "should create a new project milestone" do
post api("/projects/#{project.code}/milestones", user),
title: 'new milestone'
response.status.should == 201
json_response['title'].should == 'new milestone'
json_response['description'].should be_nil
end
end
describe "PUT /projects/:id/milestones/:milestone_id" do
it "should update a project milestone" do
put api("/projects/#{project.code}/milestones/#{milestone.id}", user),
title: 'updated title'
response.status.should == 200
json_response['title'].should == 'updated title'
end
end
end
require 'spec_helper' require 'spec_helper'
describe Gitlab::API do describe Gitlab::API do
include ApiHelpers
let(:user) { Factory :user } let(:user) { Factory :user }
let!(:project) { Factory :project, owner: user } let!(:project) { Factory :project, owner: user }
let!(:snippet) { Factory :snippet, author: user, project: project, title: 'example' } let!(:snippet) { Factory :snippet, author: user, project: project, title: 'example' }
...@@ -8,13 +10,13 @@ describe Gitlab::API do ...@@ -8,13 +10,13 @@ describe Gitlab::API do
describe "GET /projects" do describe "GET /projects" do
it "should return authentication error" do it "should return authentication error" do
get "#{api_prefix}/projects" get api("/projects")
response.status.should == 401 response.status.should == 401
end end
describe "authenticated GET /projects" do describe "authenticated GET /projects" do
it "should return an array of projects" do it "should return an array of projects" do
get "#{api_prefix}/projects?private_token=#{user.private_token}" get api("/projects", user)
response.status.should == 200 response.status.should == 200
json_response.should be_an Array json_response.should be_an Array
json_response.first['name'].should == project.name json_response.first['name'].should == project.name
...@@ -25,20 +27,20 @@ describe Gitlab::API do ...@@ -25,20 +27,20 @@ describe Gitlab::API do
describe "GET /projects/:id" do describe "GET /projects/:id" do
it "should return a project by id" do it "should return a project by id" do
get "#{api_prefix}/projects/#{project.id}?private_token=#{user.private_token}" get api("/projects/#{project.id}", user)
response.status.should == 200 response.status.should == 200
json_response['name'].should == project.name json_response['name'].should == project.name
json_response['owner']['email'].should == user.email json_response['owner']['email'].should == user.email
end end
it "should return a project by code name" do it "should return a project by code name" do
get "#{api_prefix}/projects/#{project.code}?private_token=#{user.private_token}" get api("/projects/#{project.code}", user)
response.status.should == 200 response.status.should == 200
json_response['name'].should == project.name json_response['name'].should == project.name
end end
it "should return a 404 error if not found" do it "should return a 404 error if not found" do
get "#{api_prefix}/projects/42?private_token=#{user.private_token}" get api("/projects/42", user)
response.status.should == 404 response.status.should == 404
json_response['message'].should == '404 Not found' json_response['message'].should == '404 Not found'
end end
...@@ -46,7 +48,7 @@ describe Gitlab::API do ...@@ -46,7 +48,7 @@ describe Gitlab::API do
describe "GET /projects/:id/repository/branches" do describe "GET /projects/:id/repository/branches" do
it "should return an array of project branches" do it "should return an array of project branches" do
get "#{api_prefix}/projects/#{project.code}/repository/branches?private_token=#{user.private_token}" get api("/projects/#{project.code}/repository/branches", user)
response.status.should == 200 response.status.should == 200
json_response.should be_an Array json_response.should be_an Array
json_response.first['name'].should == project.repo.heads.sort_by(&:name).first.name json_response.first['name'].should == project.repo.heads.sort_by(&:name).first.name
...@@ -55,7 +57,7 @@ describe Gitlab::API do ...@@ -55,7 +57,7 @@ describe Gitlab::API do
describe "GET /projects/:id/repository/branches/:branch" do describe "GET /projects/:id/repository/branches/:branch" do
it "should return the branch information for a single branch" do it "should return the branch information for a single branch" do
get "#{api_prefix}/projects/#{project.code}/repository/branches/new_design?private_token=#{user.private_token}" get api("/projects/#{project.code}/repository/branches/new_design", user)
response.status.should == 200 response.status.should == 200
json_response['name'].should == 'new_design' json_response['name'].should == 'new_design'
...@@ -65,7 +67,7 @@ describe Gitlab::API do ...@@ -65,7 +67,7 @@ describe Gitlab::API do
describe "GET /projects/:id/repository/tags" do describe "GET /projects/:id/repository/tags" do
it "should return an array of project tags" do it "should return an array of project tags" do
get "#{api_prefix}/projects/#{project.code}/repository/tags?private_token=#{user.private_token}" get api("/projects/#{project.code}/repository/tags", user)
response.status.should == 200 response.status.should == 200
json_response.should be_an Array json_response.should be_an Array
json_response.first['name'].should == project.repo.tags.sort_by(&:name).reverse.first.name json_response.first['name'].should == project.repo.tags.sort_by(&:name).reverse.first.name
...@@ -74,7 +76,7 @@ describe Gitlab::API do ...@@ -74,7 +76,7 @@ describe Gitlab::API do
describe "GET /projects/:id/snippets/:snippet_id" do describe "GET /projects/:id/snippets/:snippet_id" do
it "should return a project snippet" do it "should return a project snippet" do
get "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}?private_token=#{user.private_token}" get api("/projects/#{project.code}/snippets/#{snippet.id}", user)
response.status.should == 200 response.status.should == 200
json_response['title'].should == snippet.title json_response['title'].should == snippet.title
end end
...@@ -82,7 +84,7 @@ describe Gitlab::API do ...@@ -82,7 +84,7 @@ describe Gitlab::API do
describe "POST /projects/:id/snippets" do describe "POST /projects/:id/snippets" do
it "should create a new project snippet" do it "should create a new project snippet" do
post "#{api_prefix}/projects/#{project.code}/snippets?private_token=#{user.private_token}", post api("/projects/#{project.code}/snippets", user),
title: 'api test', file_name: 'sample.rb', code: 'test' title: 'api test', file_name: 'sample.rb', code: 'test'
response.status.should == 201 response.status.should == 201
json_response['title'].should == 'api test' json_response['title'].should == 'api test'
...@@ -91,7 +93,7 @@ describe Gitlab::API do ...@@ -91,7 +93,7 @@ describe Gitlab::API do
describe "PUT /projects/:id/snippets" do describe "PUT /projects/:id/snippets" do
it "should update an existing project snippet" do it "should update an existing project snippet" do
put "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}?private_token=#{user.private_token}", put api("/projects/#{project.code}/snippets/#{snippet.id}", user),
code: 'updated code' code: 'updated code'
response.status.should == 200 response.status.should == 200
json_response['title'].should == 'example' json_response['title'].should == 'example'
...@@ -102,34 +104,31 @@ describe Gitlab::API do ...@@ -102,34 +104,31 @@ describe Gitlab::API do
describe "DELETE /projects/:id/snippets/:snippet_id" do describe "DELETE /projects/:id/snippets/:snippet_id" do
it "should delete existing project snippet" do it "should delete existing project snippet" do
expect { expect {
delete "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}?private_token=#{user.private_token}" delete api("/projects/#{project.code}/snippets/#{snippet.id}", user)
}.to change { Snippet.count }.by(-1) }.to change { Snippet.count }.by(-1)
end end
end end
describe "GET /projects/:id/snippets/:snippet_id/raw" do describe "GET /projects/:id/snippets/:snippet_id/raw" do
it "should get a raw project snippet" do it "should get a raw project snippet" do
get "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}/raw?private_token=#{user.private_token}" get api("/projects/#{project.code}/snippets/#{snippet.id}/raw", user)
response.status.should == 200 response.status.should == 200
end end
end end
describe "GET /projects/:id/:sha/blob" do describe "GET /projects/:id/:sha/blob" do
it "should get the raw file contents" do it "should get the raw file contents" do
get "#{api_prefix}/projects/#{project.code}/repository/commits/master/blob?filepath=README.md&private_token=#{user.private_token}" get api("/projects/#{project.code}/repository/commits/master/blob?filepath=README.md", user)
response.status.should == 200 response.status.should == 200
end end
it "should return 404 for invalid branch_name" do it "should return 404 for invalid branch_name" do
get "#{api_prefix}/projects/#{project.code}/repository/commits/invalid_branch_name/blob?filepath=README.md&private_token=#{user.private_token}" get api("/projects/#{project.code}/repository/commits/invalid_branch_name/blob?filepath=README.md", user)
response.status.should == 404 response.status.should == 404
end end
it "should return 404 for invalid file" do it "should return 404 for invalid file" do
get "#{api_prefix}/projects/#{project.code}/repository/commits/master/blob?filepath=README.invalid&private_token=#{user.private_token}" get api("/projects/#{project.code}/repository/commits/master/blob?filepath=README.invalid", user)
response.status.should == 404 response.status.should == 404
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::API do describe Gitlab::API do
include ApiHelpers
let(:user) { Factory :user } let(:user) { Factory :user }
describe "GET /users" do describe "GET /users" do
it "should return authentication error" do it "should return authentication error" do
get "#{api_prefix}/users" get api("/users")
response.status.should == 401 response.status.should == 401
end end
describe "authenticated GET /users" do describe "authenticated GET /users" do
it "should return an array of users" do it "should return an array of users" do
get "#{api_prefix}/users?private_token=#{user.private_token}" get api("/users", user)
response.status.should == 200 response.status.should == 200
json_response.should be_an Array json_response.should be_an Array
json_response.first['email'].should == user.email json_response.first['email'].should == user.email
...@@ -21,7 +23,7 @@ describe Gitlab::API do ...@@ -21,7 +23,7 @@ describe Gitlab::API do
describe "GET /users/:id" do describe "GET /users/:id" do
it "should return a user by id" do it "should return a user by id" do
get "#{api_prefix}/users/#{user.id}?private_token=#{user.private_token}" get api("/users/#{user.id}", user)
response.status.should == 200 response.status.should == 200
json_response['email'].should == user.email json_response['email'].should == user.email
end end
...@@ -29,7 +31,7 @@ describe Gitlab::API do ...@@ -29,7 +31,7 @@ describe Gitlab::API do
describe "GET /user" do describe "GET /user" do
it "should return current user" do it "should return current user" do
get "#{api_prefix}/user?private_token=#{user.private_token}" get api("/user", user)
response.status.should == 200 response.status.should == 200
json_response['email'].should == user.email json_response['email'].should == user.email
end end
......
...@@ -6,13 +6,9 @@ describe "User Issues Dashboard" do ...@@ -6,13 +6,9 @@ describe "User Issues Dashboard" do
login_as :user login_as :user
@project1 = Factory :project, @project1 = Factory :project
path: "project1",
code: "TEST1"
@project2 = Factory :project, @project2 = Factory :project
path: "project2",
code: "TEST2"
@project1.add_access(@user, :read, :write) @project1.add_access(@user, :read, :write)
@project2.add_access(@user, :read, :write) @project2.add_access(@user, :read, :write)
......
...@@ -42,7 +42,7 @@ describe "Projects", "DeployKeys" do ...@@ -42,7 +42,7 @@ describe "Projects", "DeployKeys" do
describe "fill in" do describe "fill in" do
before do before do
fill_in "key_title", with: "laptop" fill_in "key_title", with: "laptop"
fill_in "key_key", with: "publickey234=" fill_in "key_key", with: "ssh-rsa publickey234="
end end
it { expect { click_button "Save" }.to change {Key.count}.by(1) } it { expect { click_button "Save" }.to change {Key.count}.by(1) }
...@@ -55,12 +55,12 @@ describe "Projects", "DeployKeys" do ...@@ -55,12 +55,12 @@ describe "Projects", "DeployKeys" do
end end
end end
describe "Show page" do describe "Show page" do
before do before do
@key = Factory :key, project: project @key = Factory :key, project: project
visit project_deploy_key_path(project, @key) visit project_deploy_key_path(project, @key)
end end
it { page.should have_content @key.title } it { page.should have_content @key.title }
it { page.should have_content @key.key[0..10] } it { page.should have_content @key.key[0..10] }
end end
......
...@@ -11,24 +11,30 @@ describe "Users Security" do ...@@ -11,24 +11,30 @@ describe "Users Security" do
end end
describe "GET /keys" do describe "GET /keys" do
it { keys_path.should be_allowed_for @u1 } subject { keys_path }
it { keys_path.should be_allowed_for :admin }
it { keys_path.should be_allowed_for :user } it { should be_allowed_for @u1 }
it { keys_path.should be_denied_for :visitor } it { should be_allowed_for :admin }
it { should be_allowed_for :user }
it { should be_denied_for :visitor }
end end
describe "GET /profile" do describe "GET /profile" do
it { profile_path.should be_allowed_for @u1 } subject { profile_path }
it { profile_path.should be_allowed_for :admin }
it { profile_path.should be_allowed_for :user } it { should be_allowed_for @u1 }
it { profile_path.should be_denied_for :visitor } it { should be_allowed_for :admin }
it { should be_allowed_for :user }
it { should be_denied_for :visitor }
end end
describe "GET /profile/password" do describe "GET /profile/password" do
it { profile_password_path.should be_allowed_for @u1 } subject { profile_password_path }
it { profile_password_path.should be_allowed_for :admin }
it { profile_password_path.should be_allowed_for :user } it { should be_allowed_for @u1 }
it { profile_password_path.should be_denied_for :visitor } it { should be_allowed_for :admin }
it { should be_allowed_for :user }
it { should be_denied_for :visitor }
end end
end end
end end
...@@ -26,64 +26,76 @@ describe "Application access" do ...@@ -26,64 +26,76 @@ describe "Application access" do
end end
describe "GET /project_code" do describe "GET /project_code" do
it { project_path(@project).should be_allowed_for @u1 } subject { project_path(@project) }
it { project_path(@project).should be_allowed_for @u3 }
it { project_path(@project).should be_denied_for :admin } it { should be_allowed_for @u1 }
it { project_path(@project).should be_denied_for @u2 } it { should be_allowed_for @u3 }
it { project_path(@project).should be_denied_for :user } it { should be_denied_for :admin }
it { project_path(@project).should be_denied_for :visitor } it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end end
describe "GET /project_code/master/tree" do describe "GET /project_code/master/tree" do
it { tree_project_ref_path(@project, @project.root_ref).should be_allowed_for @u1 } subject { tree_project_ref_path(@project, @project.root_ref) }
it { tree_project_ref_path(@project, @project.root_ref).should be_allowed_for @u3 }
it { tree_project_ref_path(@project, @project.root_ref).should be_denied_for :admin } it { should be_allowed_for @u1 }
it { tree_project_ref_path(@project, @project.root_ref).should be_denied_for @u2 } it { should be_allowed_for @u3 }
it { tree_project_ref_path(@project, @project.root_ref).should be_denied_for :user } it { should be_denied_for :admin }
it { tree_project_ref_path(@project, @project.root_ref).should be_denied_for :visitor } it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end end
describe "GET /project_code/commits" do describe "GET /project_code/commits" do
it { project_commits_path(@project).should be_allowed_for @u1 } subject { project_commits_path(@project) }
it { project_commits_path(@project).should be_allowed_for @u3 }
it { project_commits_path(@project).should be_denied_for :admin } it { should be_allowed_for @u1 }
it { project_commits_path(@project).should be_denied_for @u2 } it { should be_allowed_for @u3 }
it { project_commits_path(@project).should be_denied_for :user } it { should be_denied_for :admin }
it { project_commits_path(@project).should be_denied_for :visitor } it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end end
describe "GET /project_code/commit" do describe "GET /project_code/commit" do
it { project_commit_path(@project, @project.commit.id).should be_allowed_for @u1 } subject { project_commit_path(@project, @project.commit.id) }
it { project_commit_path(@project, @project.commit.id).should be_allowed_for @u3 }
it { project_commit_path(@project, @project.commit.id).should be_denied_for :admin } it { should be_allowed_for @u1 }
it { project_commit_path(@project, @project.commit.id).should be_denied_for @u2 } it { should be_allowed_for @u3 }
it { project_commit_path(@project, @project.commit.id).should be_denied_for :user } it { should be_denied_for :admin }
it { project_commit_path(@project, @project.commit.id).should be_denied_for :visitor } it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end end
describe "GET /project_code/team" do describe "GET /project_code/team" do
it { team_project_path(@project).should be_allowed_for @u1 } subject { team_project_path(@project) }
it { team_project_path(@project).should be_allowed_for @u3 }
it { team_project_path(@project).should be_denied_for :admin } it { should be_allowed_for @u1 }
it { team_project_path(@project).should be_denied_for @u2 } it { should be_allowed_for @u3 }
it { team_project_path(@project).should be_denied_for :user } it { should be_denied_for :admin }
it { team_project_path(@project).should be_denied_for :visitor } it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end end
describe "GET /project_code/wall" do describe "GET /project_code/wall" do
it { wall_project_path(@project).should be_allowed_for @u1 } subject { wall_project_path(@project) }
it { wall_project_path(@project).should be_allowed_for @u3 }
it { wall_project_path(@project).should be_denied_for :admin } it { should be_allowed_for @u1 }
it { wall_project_path(@project).should be_denied_for @u2 } it { should be_allowed_for @u3 }
it { wall_project_path(@project).should be_denied_for :user } it { should be_denied_for :admin }
it { wall_project_path(@project).should be_denied_for :visitor } it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end end
describe "GET /project_code/blob" do describe "GET /project_code/blob" do
before do before do
@commit = @project.commit commit = @project.commit
@path = @commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name
@blob_path = blob_project_ref_path(@project, @commit.id, path: @path) @blob_path = blob_project_ref_path(@project, commit.id, path: path)
end end
it { @blob_path.should be_allowed_for @u1 } it { @blob_path.should be_allowed_for @u1 }
...@@ -95,93 +107,113 @@ describe "Application access" do ...@@ -95,93 +107,113 @@ describe "Application access" do
end end
describe "GET /project_code/edit" do describe "GET /project_code/edit" do
it { edit_project_path(@project).should be_allowed_for @u1 } subject { edit_project_path(@project) }
it { edit_project_path(@project).should be_denied_for @u3 }
it { edit_project_path(@project).should be_denied_for :admin } it { should be_allowed_for @u1 }
it { edit_project_path(@project).should be_denied_for @u2 } it { should be_denied_for @u3 }
it { edit_project_path(@project).should be_denied_for :user } it { should be_denied_for :admin }
it { edit_project_path(@project).should be_denied_for :visitor } it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end end
describe "GET /project_code/deploy_keys" do describe "GET /project_code/deploy_keys" do
it { project_deploy_keys_path(@project).should be_allowed_for @u1 } subject { project_deploy_keys_path(@project) }
it { project_deploy_keys_path(@project).should be_denied_for @u3 }
it { project_deploy_keys_path(@project).should be_denied_for :admin } it { should be_allowed_for @u1 }
it { project_deploy_keys_path(@project).should be_denied_for @u2 } it { should be_denied_for @u3 }
it { project_deploy_keys_path(@project).should be_denied_for :user } it { should be_denied_for :admin }
it { project_deploy_keys_path(@project).should be_denied_for :visitor } it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end end
describe "GET /project_code/issues" do describe "GET /project_code/issues" do
it { project_issues_path(@project).should be_allowed_for @u1 } subject { project_issues_path(@project) }
it { project_issues_path(@project).should be_allowed_for @u3 }
it { project_issues_path(@project).should be_denied_for :admin } it { should be_allowed_for @u1 }
it { project_issues_path(@project).should be_denied_for @u2 } it { should be_allowed_for @u3 }
it { project_issues_path(@project).should be_denied_for :user } it { should be_denied_for :admin }
it { project_issues_path(@project).should be_denied_for :visitor } it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end end
describe "GET /project_code/snippets" do describe "GET /project_code/snippets" do
it { project_snippets_path(@project).should be_allowed_for @u1 } subject { project_snippets_path(@project) }
it { project_snippets_path(@project).should be_allowed_for @u3 }
it { project_snippets_path(@project).should be_denied_for :admin } it { should be_allowed_for @u1 }
it { project_snippets_path(@project).should be_denied_for @u2 } it { should be_allowed_for @u3 }
it { project_snippets_path(@project).should be_denied_for :user } it { should be_denied_for :admin }
it { project_snippets_path(@project).should be_denied_for :visitor } it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end end
describe "GET /project_code/merge_requests" do describe "GET /project_code/merge_requests" do
it { project_merge_requests_path(@project).should be_allowed_for @u1 } subject { project_merge_requests_path(@project) }
it { project_merge_requests_path(@project).should be_allowed_for @u3 }
it { project_merge_requests_path(@project).should be_denied_for :admin } it { should be_allowed_for @u1 }
it { project_merge_requests_path(@project).should be_denied_for @u2 } it { should be_allowed_for @u3 }
it { project_merge_requests_path(@project).should be_denied_for :user } it { should be_denied_for :admin }
it { project_merge_requests_path(@project).should be_denied_for :visitor } it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end end
describe "GET /project_code/repository" do describe "GET /project_code/repository" do
it { project_repository_path(@project).should be_allowed_for @u1 } subject { project_repository_path(@project) }
it { project_repository_path(@project).should be_allowed_for @u3 }
it { project_repository_path(@project).should be_denied_for :admin } it { should be_allowed_for @u1 }
it { project_repository_path(@project).should be_denied_for @u2 } it { should be_allowed_for @u3 }
it { project_repository_path(@project).should be_denied_for :user } it { should be_denied_for :admin }
it { project_repository_path(@project).should be_denied_for :visitor } it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end end
describe "GET /project_code/repository/branches" do describe "GET /project_code/repository/branches" do
it { branches_project_repository_path(@project).should be_allowed_for @u1 } subject { branches_project_repository_path(@project) }
it { branches_project_repository_path(@project).should be_allowed_for @u3 }
it { branches_project_repository_path(@project).should be_denied_for :admin } it { should be_allowed_for @u1 }
it { branches_project_repository_path(@project).should be_denied_for @u2 } it { should be_allowed_for @u3 }
it { branches_project_repository_path(@project).should be_denied_for :user } it { should be_denied_for :admin }
it { branches_project_repository_path(@project).should be_denied_for :visitor } it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end end
describe "GET /project_code/repository/tags" do describe "GET /project_code/repository/tags" do
it { tags_project_repository_path(@project).should be_allowed_for @u1 } subject { tags_project_repository_path(@project) }
it { tags_project_repository_path(@project).should be_allowed_for @u3 }
it { tags_project_repository_path(@project).should be_denied_for :admin } it { should be_allowed_for @u1 }
it { tags_project_repository_path(@project).should be_denied_for @u2 } it { should be_allowed_for @u3 }
it { tags_project_repository_path(@project).should be_denied_for :user } it { should be_denied_for :admin }
it { tags_project_repository_path(@project).should be_denied_for :visitor } it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end end
describe "GET /project_code/hooks" do describe "GET /project_code/hooks" do
it { project_hooks_path(@project).should be_allowed_for @u1 } subject { project_hooks_path(@project) }
it { project_hooks_path(@project).should be_allowed_for @u3 }
it { project_hooks_path(@project).should be_denied_for :admin } it { should be_allowed_for @u1 }
it { project_hooks_path(@project).should be_denied_for @u2 } it { should be_allowed_for @u3 }
it { project_hooks_path(@project).should be_denied_for :user } it { should be_denied_for :admin }
it { project_hooks_path(@project).should be_denied_for :visitor } it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end end
describe "GET /project_code/files" do describe "GET /project_code/files" do
it { files_project_path(@project).should be_allowed_for @u1 } subject { files_project_path(@project) }
it { files_project_path(@project).should be_allowed_for @u3 }
it { files_project_path(@project).should be_denied_for :admin } it { should be_allowed_for @u1 }
it { files_project_path(@project).should be_denied_for @u2 } it { should be_allowed_for @u3 }
it { files_project_path(@project).should be_denied_for :user } it { should be_denied_for :admin }
it { files_project_path(@project).should be_denied_for :visitor } it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end end
end end
end end
require 'spec_helper'
describe Issue, "IssueCommonality" do
let(:issue) { create(:issue) }
describe "Associations" do
it { should belong_to(:project) }
it { should belong_to(:author) }
it { should belong_to(:assignee) }
it { should have_many(:notes).dependent(:destroy) }
end
describe "Validation" do
it { should validate_presence_of(:project_id) }
it { should validate_presence_of(:author_id) }
it { should validate_presence_of(:title) }
it { should ensure_length_of(:title).is_at_least(0).is_at_most(255) }
end
describe "Scope" do
it { described_class.should respond_to(:opened) }
it { described_class.should respond_to(:closed) }
it { described_class.should respond_to(:assigned) }
end
it "has an :author_id_of_changes accessor" do
issue.should respond_to(:author_id_of_changes)
issue.should respond_to(:author_id_of_changes=)
end
describe ".search" do
let!(:searchable_issue) { create(:issue, title: "Searchable issue") }
it "matches by title" do
described_class.search('able').all.should == [searchable_issue]
end
end
describe "#today?" do
it "returns true when created today" do
# Avoid timezone differences and just return exactly what we want
Date.stub(:today).and_return(issue.created_at.to_date)
issue.today?.should be_true
end
it "returns false when not created today" do
Date.stub(:today).and_return(Date.yesterday)
issue.today?.should be_false
end
end
describe "#new?" do
it "returns true when created today and record hasn't been updated" do
issue.stub(:today?).and_return(true)
issue.new?.should be_true
end
it "returns false when not created today" do
issue.stub(:today?).and_return(false)
issue.new?.should be_false
end
it "returns false when record has been updated" do
issue.stub(:today?).and_return(true)
issue.touch
issue.new?.should be_false
end
end
end
require 'spec_helper'
describe Issue, "Upvote" do
let(:issue) { create(:issue) }
it "with no notes has a 0/0 score" do
issue.upvotes.should == 0
end
it "should recognize non-+1 notes" do
issue.notes << create(:note, note: "No +1 here")
issue.should have(1).note
issue.notes.first.upvote?.should be_false
issue.upvotes.should == 0
end
it "should recognize a single +1 note" do
issue.notes << create(:note, note: "+1 This is awesome")
issue.upvotes.should == 1
end
it "should recognize multiple +1 notes" do
issue.notes << create(:note, note: "+1 This is awesome")
issue.notes << create(:note, note: "+1 I want this")
issue.upvotes.should == 2
end
end
require 'simplecov' unless ENV['CI']
SimpleCov.start 'rails' require 'simplecov'
SimpleCov.start 'rails'
end
# This file is copied to spec/ when you run 'rails generate rspec:install' # This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test' ENV["RAILS_ENV"] ||= 'test'
...@@ -7,10 +9,7 @@ require File.expand_path("../../config/environment", __FILE__) ...@@ -7,10 +9,7 @@ require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails' require 'rspec/rails'
require 'capybara/rails' require 'capybara/rails'
require 'capybara/rspec' require 'capybara/rspec'
require 'capybara/dsl'
require 'webmock/rspec' require 'webmock/rspec'
require 'factories'
require 'monkeypatch'
require 'email_spec' require 'email_spec'
require 'headless' require 'headless'
...@@ -21,10 +20,14 @@ Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} ...@@ -21,10 +20,14 @@ Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
# Use capybara-webkit # Use capybara-webkit
Capybara.javascript_driver = :webkit Capybara.javascript_driver = :webkit
WebMock.disable_net_connect!(allow_localhost: true)
RSpec.configure do |config| RSpec.configure do |config|
config.mock_with :rspec config.mock_with :rspec
config.include LoginMacros config.include LoginHelpers, type: :request
config.include GitoliteStub
config.include FactoryGirl::Syntax::Methods
# If you're not using ActiveRecord, or you'd prefer not to run each of your # If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false # examples within a transaction, remove the following line or assign false
...@@ -36,35 +39,11 @@ RSpec.configure do |config| ...@@ -36,35 +39,11 @@ RSpec.configure do |config|
headless.start headless.start
end end
config.before :each, type: :integration do
DeviseSessionMock.disable
end
config.before do config.before do
if example.metadata[:js] stub_gitolite!
DatabaseCleaner.strategy = :truncation
Capybara::Selenium::Driver::DEFAULT_OPTIONS[:resynchronize] = true
else
DatabaseCleaner.strategy = :transaction
end
DatabaseCleaner.start
WebMock.disable_net_connect!(allow_localhost: true)
# !!! Observers disabled by default in tests # !!! Observers disabled by default in tests
# ActiveRecord::Base.observers.disable(:all)
# Use next code to enable observers # ActiveRecord::Base.observers.enable(:all)
# before(:each) { ActiveRecord::Base.observers.enable(:all) }
#
ActiveRecord::Base.observers.disable :all
end
config.after do
DatabaseCleaner.clean
end end
config.include RSpec::Rails::RequestExampleGroup, type: :request, example_group: {
file_path: /spec\/api/
}
end end
def api_prefix
"/api/#{Gitlab::API::VERSION}"
end
def json_response
JSON.parse(response.body)
end
module ApiHelpers
# Public: Prepend a request path with the path to the API
#
# path - Path to append
# user - User object - If provided, automatically appends private_token query
# string for authenticated requests
#
# Examples
#
# >> api('/issues')
# => "/api/v2/issues"
#
# >> api('/issues', User.last)
# => "/api/v2/issues?private_token=..."
#
# >> api('/issues?foo=bar', User.last)
# => "/api/v2/issues?foo=bar&private_token=..."
#
# Returns the relative path to the requested API resource
def api(path, user = nil)
"/api/#{Gitlab::API::VERSION}#{path}" +
# Normalize query string
(path.index('?') ? '' : '?') +
# Append private_token if given a User object
(user.respond_to?(:private_token) ?
"&private_token=#{user.private_token}" : "")
end
def json_response
JSON.parse(response.body)
end
end
require 'database_cleaner'
RSpec.configure do |config|
config.before do
if example.metadata[:js]
DatabaseCleaner.strategy = :truncation
Capybara::Selenium::Driver::DEFAULT_OPTIONS[:resynchronize] = true
else
DatabaseCleaner.strategy = :transaction
end
DatabaseCleaner.start
end
config.after do
DatabaseCleaner.clean
end
end
module GitoliteStub
def stub_gitolite!
stub_gitlab_gitolite
stub_gitolite_admin
end
def stub_gitolite_admin
gitolite_repo = mock(
clean_permissions: true,
add_permission: true
)
gitolite_config = mock(
add_repo: true,
get_repo: gitolite_repo,
has_repo?: true
)
gitolite_admin = double(
'Gitolite::GitoliteAdmin',
config: gitolite_config,
save: true,
)
Gitolite::GitoliteAdmin.stub(new: gitolite_admin)
end
def stub_gitlab_gitolite
gitlab_gitolite = Gitlab::Gitolite.new
Gitlab::Gitolite.stub(new: gitlab_gitolite)
gitlab_gitolite.stub(configure: ->() { yield(self) })
gitlab_gitolite.stub(update_keys: true)
end
end
module JsPatch
def confirm_js_popup
page.evaluate_script("window.alert = function(msg) { return true; }")
page.evaluate_script("window.confirm = function(msg) { return true; }")
end
end
module LoginMacros module LoginHelpers
def login_as role # Internal: Create and log in as a user of the specified role
@user = User.create(email: "user#{User.count}@mail.com", #
name: "John Smith", # role - User role (e.g., :admin, :user)
password: "123456", def login_as(role)
password_confirmation: "123456", @user = Factory(role)
skype: 'user_skype') login_with(@user)
if role == :admin
@user.admin = true
@user.save!
end
visit new_user_session_path
fill_in "user_email", with: @user.email
fill_in "user_password", with: "123456"
click_button "Sign in"
end end
# Internal: Login as the specified user
#
# user - User instance to login with
def login_with(user) def login_with(user)
visit new_user_session_path visit new_user_session_path
fill_in "user_email", with: user.email fill_in "user_email", with: user.email
......
...@@ -28,6 +28,16 @@ RSpec::Matchers.define :be_404_for do |user| ...@@ -28,6 +28,16 @@ RSpec::Matchers.define :be_404_for do |user|
end end
end end
RSpec::Matchers.define :include_module do |expected|
match do
described_class.included_modules.include?(expected)
end
failure_message_for_should do
"expected #{described_class} to include the #{expected} module"
end
end
module UrlAccess module UrlAccess
def url_allowed?(user, url) def url_allowed?(user, url)
emulate_user(user) emulate_user(user)
...@@ -57,3 +67,17 @@ module UrlAccess ...@@ -57,3 +67,17 @@ module UrlAccess
login_with(user) if user login_with(user) if user
end end
end end
# Extend shoulda-matchers
module Shoulda::Matchers::ActiveModel
class EnsureLengthOfMatcher
# Shortcut for is_at_least and is_at_most
def is_within(range)
if range.exclude_end?
is_at_least(range.first) && is_at_most(range.last - 1)
else
is_at_least(range.first) && is_at_most(range.last)
end
end
end
end
shared_examples_for :project_side_pane do
subject { page }
it { should have_content((@project || project).name) }
it { should have_content("Commits") }
it { should have_content("Files") }
end
shared_examples_for :tree_view do
subject { page }
it "should have Tree View of project" do
should have_content("app")
should have_content("History")
should have_content("Gemfile")
end
end
# Stubs out all Git repository access done by models so that specs can run
# against fake repositories without Grit complaining that they don't exist.
module StubbedRepository
def path_to_repo
if new_record? || path == 'newproject'
# There are a couple Project specs and features that expect the Project's
# path to be in the returned path, so let's patronize them.
File.join(Rails.root, 'tmp', 'repositories', path)
else
# For everything else, just give it the path to one of our real seeded
# repos.
File.join(Rails.root, 'tmp', 'repositories', 'gitlabhq')
end
end
def satellite
FakeSatellite.new
end
class FakeSatellite
def exists?
true
end
def create
true
end
end
end
Project.send(:include, StubbedRepository)
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