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
- Gitlab Flavored Markdown
- Bulk issues update
- Issues API
- Cucumber coverage increased
- Post-receive files fixed
- UI improved
- Application cleanup
- more cucumber
- capybara-webkit + headless
v 2.7.0
- Issue Labels
......
......@@ -54,7 +54,7 @@ gem "unicorn"
gem "acts-as-taggable-on", "2.3.1"
# Decorators
gem "drapper"
gem "draper"
# Background jobs
gem "resque", "~> 1.20.0"
......@@ -92,7 +92,6 @@ end
group :development do
gem "letter_opener"
gem "rails-footnotes"
gem "annotate", :git => "https://github.com/ctran/annotate_models.git"
gem 'rack-mini-profiler'
end
......@@ -108,15 +107,18 @@ group :development, :test do
gem "awesome_print"
gem "database_cleaner"
gem "launchy"
gem 'factory_girl_rails'
end
group :test do
gem 'cucumber-rails', :require => false
gem 'minitest', ">= 2.10"
gem "turn", :require => false
gem "simplecov", :require => false
gem "shoulda-matchers"
gem 'email_spec'
gem 'resque_spec'
gem "webmock"
end
group :production do
gem "gitlab_meta", '2.9'
end
......@@ -99,7 +99,6 @@ GEM
acts-as-taggable-on (2.3.1)
rails (~> 3.0)
addressable (2.2.8)
ansi (1.4.2)
arel (3.0.2)
autotest (4.4.6)
ZenTest (>= 4.4.1)
......@@ -156,7 +155,9 @@ GEM
railties (~> 3.1)
warden (~> 1.2.1)
diff-lcs (1.1.3)
drapper (0.8.4)
draper (0.17.0)
actionpack (~> 3.2)
activesupport (~> 3.2)
email_spec (1.2.1)
mail (~> 2.2)
rspec (~> 2.0)
......@@ -165,6 +166,11 @@ GEM
eventmachine (0.12.10)
execjs (1.4.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)
ffi (1.0.11)
foreman (0.47.0)
......@@ -172,6 +178,7 @@ GEM
gherkin (2.11.0)
json (>= 1.4.6)
git (1.2.5)
gitlab_meta (2.9)
grape (0.2.1)
hashie (~> 1.2)
multi_json
......@@ -218,7 +225,6 @@ GEM
treetop (~> 1.4.8)
method_source (0.7.1)
mime-types (1.19)
minitest (3.1.0)
modernizr (2.5.3)
sprockets (~> 2.0)
multi_json (1.3.6)
......@@ -258,8 +264,6 @@ GEM
activesupport (= 3.2.8)
bundler (~> 1.0)
railties (= 3.2.8)
rails-footnotes (3.7.8)
rails (>= 3.0.0)
railties (3.2.8)
actionpack (= 3.2.8)
activesupport (= 3.2.8)
......@@ -349,8 +353,6 @@ GEM
treetop (1.4.10)
polyglot
polyglot (>= 0.3.1)
turn (0.9.5)
ansi
tzinfo (0.3.33)
uglifier (1.0.3)
execjs (>= 0.3.0)
......@@ -389,11 +391,13 @@ DEPENDENCIES
cucumber-rails
database_cleaner
devise (~> 2.1.0)
drapper
draper
email_spec
factory_girl_rails
ffaker
foreman
git
gitlab_meta (= 2.9)
gitolite!
grack!
grape (~> 0.2.1)
......@@ -407,7 +411,6 @@ DEPENDENCIES
launchy
letter_opener
linguist (~> 1.0.0)!
minitest (>= 2.10)
modernizr (= 2.5.3)
mysql2
omniauth-ldap!
......@@ -415,7 +418,6 @@ DEPENDENCIES
pygments.rb!
rack-mini-profiler
rails (= 3.2.8)
rails-footnotes
raphael-rails (= 1.5.2)
redcarpet (~> 2.1.1)
resque (~> 1.20.0)
......@@ -432,7 +434,6 @@ DEPENDENCIES
stamp
therubyracer
thin
turn
uglifier (= 1.0.3)
unicorn
webmock
......
......@@ -39,5 +39,6 @@ Email
## Contribute
[Development Tips](https://github.com/gitlabhq/gitlabhq/blob/master/doc/development.md)
Want to help - send a pull request.
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(){
* Note markdown preview
*
*/
$('#preview-link').on('click', function(e) {
$(document).on('click', '#preview-link', function(e) {
$('#preview-note').text('Loading...');
var previewLinkText = ($(this).text() == 'Preview' ? 'Edit' : 'Preview');
......@@ -128,3 +128,23 @@ function showDiff(link) {
function ajaxGet(url) {
$.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){
$('select#issue_milestone_id').chosen();
$("#new_issue_dialog").show("fade", { direction: "right" }, 150);
$('.top-tabs .add_new').hide();
disableButtonIfEmtpyField("#issue_title", ".save-btn");
});
}
......@@ -15,6 +16,7 @@ function switchToEditIssue(form){
$('select#issue_milestone_id').chosen();
$("#edit_issue_dialog").show("fade", { direction: "right" }, 150);
$('.add_new').hide();
disableButtonIfEmtpyField("#issue_title", ".save-btn");
});
}
......
var NoteList = {
notes_path: null,
target_params: null,
target_id: 0,
target_type: null,
first_id: 0,
last_id: 0,
disable:false,
init:
notes_path: null,
target_params: null,
target_id: 0,
target_type: null,
first_id: 0,
last_id: 0,
disable:false,
init:
function(tid, tt, path) {
this.notes_path = path + ".js";
this.target_id = tid;
......@@ -24,15 +24,17 @@ init:
$('.delete-note').live('ajax:success', function() {
$(this).closest('li').fadeOut(); });
$("#new_note").live("ajax:before", function(){
$(".note-form-holder").live("ajax:before", function(){
$(".submit_note").attr("disabled", "disabled");
})
$("#new_note").live("ajax:complete", function(){
$(".note-form-holder").live("ajax:complete", function(){
$(".submit_note").removeAttr("disabled");
})
$("#note_note").live("focus", function(){
disableButtonIfEmtpyField(".note-text", ".submit_note");
$(".note-text").live("focus", function(){
$(this).css("height", "80px");
$('.note_advanced_opts').show();
});
......@@ -46,31 +48,31 @@ init:
},
/**
/**
* Load new notes to fresh list called 'new_notes_list':
* - Replace 'new_notes_list' with new list every n seconds
* - Append new notes to this list after submit
*/
initRefresh:
initRefresh:
function() {
// init timer
var intNew = setInterval("NoteList.getNew()", 10000);
},
replace:
replace:
function(html) {
$("#new_notes_list").html(html);
},
prepend:
prepend:
function(id, html) {
if(id != this.last_id) {
$("#new_notes_list").prepend(html);
}
},
getNew:
getNew:
function() {
// refersh notes list
$.ajax({
......@@ -80,7 +82,7 @@ getNew:
dataType: "script"});
},
refresh:
refresh:
function() {
// refersh notes list
$.ajax({
......@@ -91,14 +93,14 @@ refresh:
},
/**
/**
* Init load of notes:
* 1. Get content with ajax call
* 2. Set content of notes list with loaded one
*/
getContent:
getContent:
function() {
$.ajax({
type: "GET",
......@@ -109,7 +111,7 @@ getContent:
dataType: "script"});
},
setContent:
setContent:
function(fid, lid, html) {
this.last_id = lid;
this.first_id = fid;
......@@ -120,16 +122,14 @@ setContent:
},
/**
/**
* Paging for old notes when scroll to bottom:
* 1. Init scroll events with 'initLoadMore'
* 2. Load onlder notes with 'getOld' method
* 3. append old notes to bottom of list with 'append'
*
*/
getOld:
getOld:
function() {
$('.loading').show();
$.ajax({
......@@ -141,7 +141,7 @@ getOld:
dataType: "script"});
},
append:
append:
function(id, html) {
if(this.first_id == id) {
this.disable = true;
......@@ -151,8 +151,7 @@ append:
}
},
initLoadMore:
initLoadMore:
function() {
$(document).endlessScroll({
bottomPixels: 400,
......@@ -166,4 +165,18 @@ initLoadMore:
}
});
}
};
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() {
$('.new_project, .edit_project').live('ajax:before', function() {
$('.project_new_holder, .project_edit_holder').hide();
$('.ajax_loader').show();
$('.save-project-loader').show();
});
$('form #project_default_branch').chosen();
disableButtonIfEmtpyField("#project_name", ".project-submit")
}
.diff_file_header a,
.file_stats a {
color:$style_color;
}
/** LAYOUT **/
body {
margin-bottom:20px;
}
.container {
padding-top:0;
z-index:5;
......@@ -40,30 +38,6 @@
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 }
.back_link {
......@@ -88,16 +62,6 @@
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 {
position: relative;
top: -2px;
......@@ -129,26 +93,18 @@ table a code {
border-bottom:1px solid #ccc;
h4 {
color:#444;
font-size:22px;
color:#666;
font-size:18px;
line-height:38px;
padding-top:5px;
margin:2px;
font-weight:normal;
}
}
.git_url_wrapper {
margin-right:50px
}
.file_stats {
span {
img {
width:14px;
float:left;
margin-right:6px;
padding:2px 0;
}
}
}
.handle:hover {
cursor:move;
......@@ -172,10 +128,6 @@ span.update-author {
display:block;
}
/** END UPDATE ITEM **/
.ajax-tab-loading {
padding:40px;
display:none;
}
.dashboard-loader {
float:left;
margin:10px;
......@@ -186,15 +138,110 @@ span.update-author {
font-weight:bold;
}
a.project-update.titled {
position:relative;
padding-left:35% !important;
.title-block {
padding:10px;
width:35%;
position:absolute;
left:0;
top:0;
.neib {
margin-right:10px;
}
.label {
background-color: #474D57;
&.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;
}
}
}
.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 {
}
/**
* 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 {
border:2px solid #ddd;
}
......@@ -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 {
font-size: 14px;
font-weight: bold;
......@@ -549,14 +529,6 @@ li.note {
}
/**
* Milestones list
*
*/
.milestone {
@extend .wll;
}
/**
* Admin area
......@@ -603,11 +575,10 @@ li.note {
*
*/
.event_lp {
@extend .alert-info;
@extend .ui-box;
color:#777;
margin-bottom:20px;
padding:8px;
border-style: solid;
border-width: 1px;
@include border-radius(4px);
min-height:22px;
......@@ -621,88 +592,19 @@ li.note {
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,
.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{
background: #EFE;
border-color:#CEC;
.icon {
background: #ADA;
border:1px solid #8B8;
}
}
&.closed {
background: #F5f5f5;
border-color:#E5E5E5;
.icon {
background: #DDD;
border:1px solid #BBB;
}
}
&.yours {
.icon {
background: #AAD;
border:1px solid #88B;
}
}
&.merged {
background: #F5f5f5;
border-color:#E5E5E5;
.icon {
background: #DAD;
border:1px solid #B8B;
}
}
}
......@@ -735,3 +637,11 @@ li.note {
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,26 +2,10 @@
@import "bootstrap-responsive";
/** GITLAB colors **/
$text_color:#222;
$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";
$link_color:#3A89A3;
$blue_link: #2fa0bb;
/** Style colors **/
$style_color: #474D57;
$hover: #FDF5D9;
$style_color: #474d57;
$hover: #fdf5d9;
/** GITLAB Fonts **/
@font-face { font-family: Korolev; src: url('korolev-medium-compressed.otf'); }
......@@ -72,7 +56,20 @@ $hover: #FDF5D9;
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.
......@@ -113,7 +110,13 @@ $hover: #FDF5D9;
* Overrides some styles of twitter bootstrap.
* 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 @@
}
.chzn-single {
background:#ddd;
//border:none;
//box-shadow:none;
@include bg-gray-gradient;
div {
background:transparent;
......
......@@ -206,4 +206,24 @@
min-width:65px;
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 @@
h4 {
padding:0 10px;
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);
@include bg-gray-gradient;
}
.graph {
......
......@@ -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_filters {
form {
......@@ -99,3 +104,11 @@ input.check_all_issues {
#update_status {
width:100px;
}
/**
* Milestones list
*
*/
.milestone {
@extend .wll;
}
......@@ -11,23 +11,6 @@
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 @@
background: #CEB;
.accept_merge_request {
font-size:13px;
float:left;
}
.remove_branch_holder {
......@@ -99,3 +83,42 @@ li.merge_request {
@extend .padded;
@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 {
border-radius: 4px;
margin: auto;
margin:30px 0;
background:#eee;
border:1px solid #bbb;
border:1px solid #AAA;
height:37px;
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 bg-gray-gradient;
position:relative;
overflow:hidden;
@include shade;
......@@ -89,7 +85,7 @@ ul.main_menu {
line-height:36px;
color: $style_color;
text-shadow:0 1px 1px white;
padding:0 10px;
}
}
/*
......
......@@ -30,14 +30,22 @@
}
#new_note {
#note_note {
height:25px;
.note-text {
height:40px;
}
.attach_holder {
display:none;
}
}
.preview_note {
margin: 2px;
border: 1px solid #ddd;
padding: 10px;
min-height: 60px;
background:#f5f5f5;
}
.note {
padding: 8px 0;
border-bottom: 1px solid #eee;
......@@ -204,3 +212,8 @@ td .line_note_link {
}
}
}
.note-text {
border: 1px solid #aaa;
box-shadow:none;
}
......@@ -12,6 +12,33 @@
color:$style_color;
font-size:16px;
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 .ui-box;
......@@ -32,8 +59,34 @@
color:#888;
}
.btn {
padding:6px;
padding:6px 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 @@
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);
@include bg-gray-gradient;
}
}
......
......@@ -20,6 +20,10 @@
.fbtn {
.btn {
i {
position: relative;
top: 1px;
}
margin-left:8px;
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);
......@@ -32,6 +36,10 @@
background-image: -moz-linear-gradient(#595D63 6.6%, #202227);
background-image: -o-linear-gradient(#595D63 6.6%, #202227);
background-position:0 0;
color:#fff;
i {
@extend .icon-white;
}
}
border: 1px solid #31363E;
......
class MergeRequestsLoad < BaseContext
def execute
type = params[:f].to_i
type = params[:f]
merge_requests = project.merge_requests
merge_requests = case type
when 1 then merge_requests
when 2 then merge_requests.closed
when 3 then merge_requests.opened.assigned(current_user)
when 'all' then merge_requests
when 'closed' then merge_requests.closed
when 'assigned-to-me' then merge_requests.opened.assigned(current_user)
else merge_requests.opened
end.page(params[:page]).per(20)
......
......@@ -14,6 +14,10 @@ class ApplicationController < ActionController::Base
render "errors/gitolite", layout: "error"
end
rescue_from Gitlab::Gitolite::InvalidKey do |exception|
render "errors/invalid_ssh_key", layout: "error"
end
rescue_from Encoding::CompatibilityError do |exception|
render "errors/encoding", layout: "error", status: 404
end
......
......@@ -60,7 +60,13 @@ class IssuesController < ApplicationController
@issue.save
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
end
end
......@@ -162,10 +168,10 @@ class IssuesController < ApplicationController
def issues_filter
{
all: "1",
closed: "2",
to_me: "3",
open: "0"
all: "all",
closed: "closed",
to_me: "assigned-to-me",
open: "open"
}
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
def branch_from
@commit = project.commit(params[:ref])
@commit = CommitDecorator.decorate(@commit)
end
def branch_to
@commit = project.commit(params[:ref])
@commit = CommitDecorator.decorate(@commit)
end
protected
......
......@@ -17,8 +17,8 @@ class MilestonesController < ApplicationController
respond_to :html
def index
@milestones = case params[:f].to_i
when 1; @project.milestones
@milestones = case params[:f]
when 'all'; @project.milestones
else @project.milestones.active
end
......
......@@ -12,8 +12,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def ldap
# We only find ourselves here if the authentication to LDAP was successful.
info = request.env["omniauth.auth"]["info"]
@user = User.find_for_ldap_auth(info)
@user = User.find_for_ldap_auth(request.env["omniauth.auth"], current_user)
if @user.persisted?
@user.remember_me = true
end
......
......@@ -9,6 +9,7 @@ class TeamMembersController < ApplicationController
def show
@team_member = project.users_projects.find(params[:id])
@events = @team_member.user.recent_events.where(:project_id => @project.id).limit(7)
end
def new
......
class ApplicationDecorator < Drapper::Base
class ApplicationDecorator < Draper::Base
# Lazy Helpers
# PRO: Call Rails helpers without the h. proxy
# ex: number_to_currency(model.price)
......
......@@ -2,10 +2,13 @@ require 'digest/md5'
module ApplicationHelper
def gravatar_icon(user_email = '', size = 40)
return unless user_email
gravatar_host = request.ssl? ? "https://secure.gravatar.com" : "http://www.gravatar.com"
if Gitlab.config.disable_gravatar? || user_email.blank?
'no_avatar.png'
else
gravatar_prefix = request.ssl? ? "https://secure" : "http://www"
user_email.strip!
"#{gravatar_host}/avatar/#{Digest::MD5.hexdigest(user_email.downcase)}?s=#{size}&d=identicon"
"#{gravatar_prefix}.gravatar.com/avatar/#{Digest::MD5.hexdigest(user_email.downcase)}?s=#{size}&d=identicon"
end
end
def request_protocol
......@@ -123,4 +126,13 @@ module ApplicationHelper
def hexdigest(string)
Digest::SHA1.hexdigest string
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
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 = {})
return text if text.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/
extractions = {}
text.gsub!(%r{<pre>.*?</pre>|<code>.*?</code>}m) do |match|
......@@ -22,10 +31,18 @@ module GitlabMarkdownHelper
extractions[$1]
end
text.html_safe
sanitize text.html_safe, attributes: ActionView::Base.sanitized_allowed_attributes + %w(id class )
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 = {})
gfm_body = gfm(body, html_options)
......@@ -37,7 +54,14 @@ module GitlabMarkdownHelper
end
def markdown(text)
@__renderer ||= Redcarpet::Markdown.new(Redcarpet::Render::GitlabHTML.new(self, filter_html: true, with_toc_data: true), {
unless @markdown
gitlab_renderer = Redcarpet::Render::GitlabHTML.new(self,
# see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch-
filter_html: true,
with_toc_data: true,
hard_wrap: true)
@markdown ||= Redcarpet::Markdown.new(gitlab_renderer,
# see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
no_intra_emphasis: true,
tables: true,
fenced_code_blocks: true,
......@@ -45,9 +69,9 @@ module GitlabMarkdownHelper
strikethrough: true,
lax_html_blocks: true,
space_after_headers: true,
superscript: true
})
superscript: true)
end
@__renderer.render(text).html_safe
@markdown.render(text).html_safe
end
end
......@@ -12,74 +12,117 @@ class Notify < ActionMailer::Base
def new_user_email(user_id, password)
@user = User.find(user_id)
@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
def new_issue_email(issue_id)
@issue = Issue.find(issue_id)
@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
def note_wall_email(recipient_id, note_id)
recipient = User.find(recipient_id)
@note = Note.find(note_id)
@project = @note.project
mail(to: recipient.email, subject: "gitlab | #{@project.name}")
mail(to: recipient(recipient_id), subject: subject)
end
def note_commit_email(recipient_id, note_id)
recipient = User.find(recipient_id)
@note = Note.find(note_id)
@commit = @note.target
@commit = CommitDecorator.decorate(@commit)
@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
def note_merge_request_email(recipient_id, note_id)
recipient = User.find(recipient_id)
@note = Note.find(note_id)
@merge_request = @note.noteable
@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
def note_issue_email(recipient_id, note_id)
recipient = User.find(recipient_id)
@note = Note.find(note_id)
@issue = @note.noteable
@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
def note_wiki_email(recipient_id, note_id)
recipient = User.find(recipient_id)
@note = Note.find(note_id)
@wiki = @note.noteable
@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
def new_merge_request_email(merge_request_id)
@merge_request = MergeRequest.find(merge_request_id)
@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
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)
@previous_assignee ||= User.find(previous_assignee_id)
@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
def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id)
recipient = User.find(recipient_id)
@issue = Issue.find(issue_id)
@previous_assignee ||= User.find(previous_assignee_id)
@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
require 'digest/md5'
class Key < ActiveRecord::Base
include SshKey
belongs_to :user
belongs_to :project
attr_protected :user_id
validates :title,
presence: true,
length: { within: 0..255 }
validates :key,
presence: true,
format: { :with => /ssh-.{3} / },
length: { within: 0..5000 }
before_save :set_identifier
......@@ -50,6 +52,10 @@ class Key < ActiveRecord::Base
user.projects
end
end
def last_deploy?
Key.where(identifier: identifier).count == 0
end
end
# == Schema Information
#
......
......@@ -88,8 +88,11 @@ class MergeRequest < ActiveRecord::Base
end
def unmerged_diffs
commits = project.repo.commits_between(target_branch, source_branch).map {|c| Commit.new(c)}
diffs = project.repo.diff(commits.first.prev_commit.id, commits.last.id) rescue []
# Only show what is new in the source branch compared to the target branch, not the other way around.
# 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
def last_commit
......
......@@ -28,18 +28,10 @@ class Milestone < ActiveRecord::Base
end
def percent_complete
@percent_complete ||= begin
total_i = self.issues.count
closed_i = self.issues.closed.count
if total_i > 0
(closed_i * 100) / total_i
else
((self.issues.closed.count * 100) / self.issues.count).abs
rescue ZeroDivisionError
100
end
rescue => ex
0
end
end
def expires_at
"expires at #{due_date.stamp("Aug 21, 2011")}" if due_date
......
......@@ -2,7 +2,7 @@ require "grit"
class Project < ActiveRecord::Base
include Repository
include ProjectPush
include PushObserver
include Authority
include Team
......@@ -158,7 +158,7 @@ class Project < ActiveRecord::Base
end
def last_activity
events.last || nil
events.order("created_at ASC").last
end
def last_activity_date
......
class ProtectedBranch < ActiveRecord::Base
include GitHost
belongs_to :project
validates_presence_of :project_id
validates_presence_of :name
......@@ -7,7 +9,7 @@ class ProtectedBranch < ActiveRecord::Base
after_destroy :update_repository
def update_repository
Gitlab::GitHost.system.update_project(project.path, project)
git_host.update_repository(project)
end
def commit
......
......@@ -7,7 +7,7 @@ class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation, :remember_me, :bio,
: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
......@@ -54,6 +54,8 @@ class User < ActiveRecord::Base
validates :bio, length: { within: 0..255 }
validates :extern_uid, :allow_blank => true, :uniqueness => {:scope => :provider}
before_save :ensure_authentication_token
alias_attribute :private_token, :authentication_token
......@@ -84,21 +86,31 @@ class User < ActiveRecord::Base
where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)')
end
def self.find_for_ldap_auth(omniauth_info)
name = omniauth_info.name.force_encoding("utf-8")
email = omniauth_info.email.downcase unless omniauth_info.email.nil?
raise OmniAuth::Error, "LDAP accounts must provide an email address" if email.nil?
def self.find_for_ldap_auth(auth, signed_in_resource=nil)
uid = auth.info.uid
provider = auth.provider
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
else
logger.info "Creating user from LDAP login {uid => #{uid}, name => #{name}, email => #{email}}"
password = Devise.friendly_token[0, 8].downcase
@user = User.create(
name: name,
email: email,
password: password,
password_confirmation: password,
projects_limit: Gitlab.config.default_projects_limit
:extern_uid => uid,
:provider => provider,
:name => name,
:email => email,
:password => password,
:password_confirmation => password,
:projects_limit => Gitlab.config.default_projects_limit
)
end
end
......
class UsersProject < ActiveRecord::Base
include GitHost
GUEST = 10
REPORTER = 20
DEVELOPER = 30
......@@ -58,9 +60,7 @@ class UsersProject < ActiveRecord::Base
end
def update_repository
Gitlab::GitHost.system.new.configure do |c|
c.update_project(project.path, project)
end
git_host.update_repository(project)
end
def project_access_human
......
......@@ -9,8 +9,16 @@ class IssueObserver < ActiveRecord::Observer
def after_update(issue)
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
protected
......
class KeyObserver < ActiveRecord::Observer
include GitHost
def after_save(key)
key.update_repository
git_host.set_key(key.identifier, key.key, key.projects)
end
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
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)
data = post_receive_data(oldrev, newrev, ref, user)
......
module Repository
include GitHost
def valid_repo?
repo
rescue
......@@ -30,26 +32,10 @@ module Repository
Commit.commits_between(repo, from, to)
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
@satellite ||= Gitlab::Satellite.new(self)
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?
hook_file = File.join(path_to_repo, 'hooks', 'post-receive')
File.exists?(hook_file)
......@@ -64,7 +50,7 @@ module Repository
end
def url_to_repo
Gitlab::GitHost.url_to_repo(path)
git_host.url_to_repo(path)
end
def path_to_repo
......@@ -72,13 +58,11 @@ module Repository
end
def update_repository
Gitlab::GitHost.system.update_project(path, self)
write_hooks if File.exists?(path_to_repo)
git_host.update_repository(self)
end
def destroy_repository
Gitlab::GitHost.system.destroy_project(self)
git_host.remove_repository(self)
end
def repo_exists?
......@@ -133,10 +117,13 @@ module Repository
storage_path = File.join(Rails.root, "tmp", "repositories", self.code)
file_path = File.join(storage_path, file_name)
# Put files into a directory before archiving
prefix = self.code + "/"
# Create file if not exists
unless File.exists?(file_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
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 @@
%h3 Latest projects
%hr
- @projects.each do |project|
%h5
%p
= link_to project.name, [:admin, project]
.span6
%h3 Latest users
%hr
- @users.each do |user|
%h5
= link_to user.name, [:admin, user]
%p
= link_to [:admin, user] do
= user.name
%small= user.email
......@@ -5,7 +5,7 @@
Read more about system hooks
%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?
.alert-message.block-message.error
- @hook.errors.full_messages.each do |msg|
......
......@@ -10,19 +10,17 @@
Project name is
.input
= f.text_field :name, placeholder: "Example Project", class: "xxlarge"
= f.submit project.new_record? ? 'Create project' : 'Save Project', class: "btn primary"
%hr
.alert.alert-info
%h5 Advanced settings:
.adv_settings
%h6 Advanced settings:
.clearfix
= f.label :path do
Git Clone
Path
.input
.input-prepend
%span.add-on= Gitlab.config.ssh_path
= f.text_field :path, placeholder: "example_project", disabled: !!project.id
%span.add-on= ".git"
%strong
= text_field_tag :ppath, @admin_project.path_to_repo, class: "xlarge", disabled: true
.clearfix
= f.label :code do
URL
......@@ -42,8 +40,9 @@
.input= f.select(:default_branch, project.heads.map(&:name), {}, style: "width:210px;")
- unless project.new_record?
.alert.alert-info
%h5 Features:
%hr
.adv_settings
%h6 Features:
.clearfix
= f.label :issues_enabled, "Issues"
......@@ -63,7 +62,8 @@
- unless project.new_record?
.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
= link_to 'New Project', new_admin_project_path, class: "btn small right"
%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"
= submit_tag "Search", class: "btn submit primary"
......
%h3.page_title New project
%hr
= render 'form', project: @admin_project
.project_new_holder
%h3.page_title
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,12 +2,14 @@
= form_for [:admin, @admin_user] do |f|
-if @admin_user.errors.any?
#error_explanation
%ul
%ul.unstyled.alert.alert-error
- @admin_user.errors.full_messages.each do |msg|
%li= msg
.row
.span6
.span7
.ui-box
%br
.clearfix
= f.label :name
.input
......@@ -19,12 +21,11 @@
= 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
= f.label :force_random_password do
%span Generate random password
.input= f.check_box :force_random_password, {}, true, nil
%div.password-fields
.clearfix
......@@ -43,30 +44,37 @@
.clearfix
= f.label :twitter
.input= f.text_field :twitter
.span6
.span5
.ui-box
%br
.clearfix
= f.label :projects_limit
.input= f.text_field :projects_limit, class: "small_input"
.input= f.number_field :projects_limit
.alert
.clearfix
%p Make the user a GitLab administrator.
= f.label :admin, class: "checkbox" do
= f.check_box :admin
%span Administrator
= f.label :admin do
%strong.cred Administrator
.input= f.check_box :admin
- unless @admin_user.new_record?
.alert.alert-error
%hr
.padded.cred
- if @admin_user.blocked
%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
.clearfix
= link_to 'Unblock User', unblock_admin_user_path(@admin_user), method: :put, class: "btn small right"
- else
%span
= link_to 'Block', block_admin_user_path(@admin_user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn small danger"
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
= f.submit 'Save', class: "btn primary"
= f.submit 'Save', class: "btn save-btn"
- if @admin_user.new_record?
= link_to 'Cancel', admin_users_path, class: "btn"
= link_to 'Cancel', admin_users_path, class: "btn cancel-btn"
- 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
= render 'form'
%h3
%h3.page_title
Users
= link_to 'New User', new_admin_user_path, class: "btn small right"
%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"
= submit_tag "Search", class: "btn submit primary"
%ul.nav.nav-pills
......
%h2 New user
%hr
%h3.page_title New user
%br
= render 'form'
......@@ -4,8 +4,8 @@
%strong= link_to "Browse Code »", tree_project_ref_path(@project, commit.id), class: "right"
%p
= link_to commit.short_id(8), project_commit_path(@project, id: commit.id), class: "commit_short_id"
%strong.cgray= commit.author_name
&ndash;
%strong.commit-author-name= commit.author_name
%span.dash &ndash;
= 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"
......
......@@ -20,7 +20,7 @@
= "..."
= text_field_tag :to, params[:to], placeholder: "aa8b4ef", class: "xlarge"
.actions
= submit_tag "Compare", class: "btn btn-primary"
= submit_tag "Compare", class: "btn primary"
- unless @commits.empty?
......
......@@ -5,12 +5,6 @@
:javascript
$(document).ready(function(){
$(".line_note_link, .line_note_reply_link").live("click", function(e) {
var form = $(".per_line_form");
$(this).parent().parent().after(form);
form.find("#note_line_code").val($(this).attr("line_code"));
form.show();
return false;
});
$(function(){
PerLineNotes.init();
});
- if @projects.any?
.projects
.activities.span8
- if current_user.require_ssh_key?
.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
= render 'shared/no_ssh'
- if @events.any?
.content_list= render @events
- else
......@@ -26,13 +19,16 @@
= link_to new_project_path, class: "btn very_small info" do
%i.icon-plus
New Project
%ul.unstyled
- @projects.each do |project|
%li.wll
= link_to project_path(project), class: dom_class(project) do
%h4
%span.ico.project
= truncate(project.name, length: 25)
%span.right
%strong.project_name= truncate(project.name, length: 25)
%span.arrow
&rarr;
%span.last_activity
%strong Last activity:
%span= project_last_activity(project)
.bottom= paginate @projects, theme: "gitlab"
%hr
......
......@@ -21,7 +21,5 @@
Permissions:
%pre
= preserve do
sudo chmod -R 770 /home/git/repositories/
sudo chown -R git:git /home/git/repositories/
sudo chown gitlab:gitlab /home/git/repositories/**/hooks/post-receive
sudo chmod -R 770 #{Gitlab.config.git_base_path}
sudo chown -R git:git #{Gitlab.config.git_base_path}
%h1 Git Error
%hr
%p Seems like SSH Key you provided is not a valid SSH key.
......@@ -9,5 +9,5 @@
at
%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
......@@ -9,6 +9,8 @@
%a{href: "#README"} README
%li
%a{href: "#projects"} Projects
%li
%a{href: "#snippets"} Snippets
%li
%a{href: "#users"} Users
%li
......@@ -34,6 +36,16 @@
%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_title
%i.icon-file
......@@ -51,3 +63,13 @@
.file_content.wiki
= preserve do
= 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 @@
%li
%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 Markdown
%h3.page_title Gitlab Flavored Markdown
.back_link
= link_to help_path do
&larr; to index
%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
%li issues (#123)
%li merge request (!123)
%li commits (1234567)
%li team members (@foo)
%li snippets ($123)
%p.slead in
%ul
%p You can use GFM in:
%ul
%li commit messages
%li notes/comments/wall posts
%li comments
%li wall posts
%li issues
%li merge requests
%li milestones
%li wiki pages
%h3 Differences from traditional Markdown
%h4 Newlines
%p
The biggest difference that GFM introduces is in the handling of linebreaks.
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.
GFM treats newlines in paragraph-like content as real line breaks, which is probably what you intended.
%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
= link_to help_path do
&larr; to index
......
%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 Web hooks
%h3.page_title Web hooks
.back_link
= link_to help_path do
&larr; to index
......
- bash_lexer = Pygments::Lexer[:bash]
%h3 Workflow
%h3.page_title Workflow
.back_link
= link_to help_path do
&larr; to index
......@@ -9,25 +8,25 @@
%li
%p Clone project
.bash
%pre
%pre.dark
git clone git@example.com:project-name.git
%li
%p Create branch with your feature
.bash
%pre
%pre.dark
git checkout -b $feature_name
%li
%p Write code. Commit changes
.bash
%pre
%pre.dark
git commit -am "My feature is ready"
%li
%p Push your branch to gitlabhq
.bash
%pre
%pre.dark
git push origin $feature_name
%li
......
......@@ -37,7 +37,7 @@
}
}
],
total_commits_count => 3
total_commits_count => 4
}
eos
%>
......
......@@ -8,7 +8,7 @@
Read more about web hooks
%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?
.alert-message.block-message.error
- @hook.errors.full_messages.each do |msg|
......
......@@ -38,19 +38,20 @@
= f.label :description, "Details"
.input
= 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
- if @issue.new_record?
= f.submit 'Submit new issue', class: "primary btn"
= f.submit 'Submit new issue', class: "btn save-btn"
-else
= f.submit 'Save changes', class: "primary btn"
= f.submit 'Save changes', class: "save-btn btn"
- cancel_class = 'btn cancel-btn'
- if request.xhr?
= link_to "Cancel", "#back", onclick: "backToIssues();", class: "btn"
= link_to "Cancel", "#back", onclick: "backToIssues();", class: cancel_class
- else
- if @issue.new_record?
= link_to "Cancel", project_issues_path(@project), class: "btn"
= link_to "Cancel", project_issues_path(@project), class: cancel_class
- 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 @@
%li{class: "#{'active' if current_page?(project_milestones_path(@project))}"}
= link_to project_milestones_path(@project), class: "tab" do
Milestones
%li{class: "#{'active' if current_page?(project_labels_path(@project))}"}
= link_to project_labels_path(@project), class: "tab" do
Labels
%li.right
%span.rss-icon
= link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do
......
......@@ -6,7 +6,7 @@
.right
.span5
- 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
New Issue
= form_tag search_project_issues_path(@project), method: :get, remote: true, id: "issue_search_form", class: :right do
......
......@@ -11,8 +11,14 @@
.input= f.text_field :title
.clearfix
= 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
= f.submit 'Save', class: "primary btn"
= link_to "Cancel", keys_path, class: "btn"
= f.submit 'Save', class: "btn save-btn"
= link_to "Cancel", keys_path, class: "btn cancel-btn"
%h3.page_title
SSH Keys
= link_to "Add new", new_key_path, class: "btn small right"
= link_to "Add new", new_key_path, class: "btn right"
%hr
%p.slead
......
%h3.page_title New key
%h3.page_title Add an SSH Key
%hr
= 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 @@
%br
.row
.span6
.span5
.mr_branch_box
%h5 From (Head Branch)
.body
......@@ -17,10 +17,11 @@
= f.label :source_branch, "From", class: "control-label"
.controls
= f.select(:source_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px")
.bottom_commit
.mr_source_commit
.span6
.span2
%center= image_tag "merge.png", class: 'mr_direction_tip'
.span5
.mr_branch_box
%h5 To (Base Branch)
.body
......@@ -28,7 +29,6 @@
= f.label :target_branch, "To", class: "control-label"
.controls
= f.select(:target_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px")
.bottom_commit
.mr_target_commit
%h4.cdark 2. Fill info
......@@ -48,18 +48,19 @@
.control-group
.form-actions
= f.submit 'Save', class: "btn-primary btn"
= f.submit 'Save', class: "btn save-btn"
- 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
- 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
:javascript
$(function(){
disableButtonIfEmtpyField("#merge_request_title", ".save-btn");
$('select#merge_request_assignee_id').chosen();
$('select#merge_request_source_branch').chosen();
$('select#merge_request_target_branch').chosen();
......
%h3.page_title
Merge Requests
- 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
%br
......@@ -10,17 +10,17 @@
.ui-box
.title
%ul.nav.nav-pills
%li{class: ("active" if (params[:f] == "0" || !params[:f]))}
= link_to project_merge_requests_path(@project, f: 0) do
%li{class: ("active" if (params[:f] == 'open' || !params[:f]))}
= link_to project_merge_requests_path(@project, f: 'open') do
Open
%li{class: ("active" if params[:f] == "2")}
= link_to project_merge_requests_path(@project, f: 2) do
%li{class: ("active" if params[:f] == "closed")}
= link_to project_merge_requests_path(@project, f: "closed") do
Closed
%li{class: ("active" if params[:f] == "3")}
= link_to project_merge_requests_path(@project, f: 3) do
%li{class: ("active" if params[:f] == 'assigned-to-me')}
= link_to project_merge_requests_path(@project, f: 'assigned-to-me') do
To Me
%li{class: ("active" if params[:f] == "1")}
= link_to project_merge_requests_path(@project, f: 1) do
%li{class: ("active" if params[:f] == 'all')}
= link_to project_merge_requests_path(@project, f: 'all') do
All
%ul.unstyled
......
......@@ -3,13 +3,12 @@
%a.close{href: "#"} ×
%h3 How To Merge
.modal-body
%pre
%pre.dark
= preserve do
:erb
git checkout <%= @merge_request.target_branch %>
git checkout #{@merge_request.target_branch}
git fetch origin
git merge origin/<%= @merge_request.source_branch %>
git push origin <%= @merge_request.target_branch %>
git merge origin/#{@merge_request.source_branch}
git push origin #{@merge_request.target_branch}
:javascript
......
%h3.page_title
= "Merge Request ##{@merge_request.id}:"
&nbsp;
%span.pretty_label.branch= @merge_request.source_branch
%span.label_branch= @merge_request.source_branch
&rarr;
%span.pretty_label.branch= @merge_request.target_branch
%span.label_branch= @merge_request.target_branch
%span.right
- if @merge_request.merged?
......
......@@ -22,7 +22,7 @@
= f.label :description, "Description", class: "control-label"
.controls
= 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
.control-group
= f.label :due_date, "Due Date", class: "control-label"
......@@ -32,20 +32,16 @@
.form-actions
- 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
= 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
$(function() {
disableButtonIfEmtpyField("#milestone_title", ".save-btn");
$( ".datepicker" ).datepicker({
dateFormat: "yy-mm-dd",
onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
......
......@@ -8,11 +8,11 @@
%div.ui-box
.title
%ul.nav.nav-pills
%li{class: ("active" if (params[:f] == "0" || !params[:f]))}
= link_to project_milestones_path(@project, f: 0) do
%li{class: ("active" if (params[:f] == "active" || !params[:f]))}
= link_to project_milestones_path(@project, f: "active") do
Active
%li{class: ("active" if params[:f] == "1")}
= link_to project_milestones_path(@project, f: 1) do
%li{class: ("active" if params[:f] == "all")}
= link_to project_milestones_path(@project, f: "all") do
All
%ul.unstyled
......
- if note.valid?
:plain
$("#new_note .errors").remove();
$('#new_note textarea').val("");
$(".note-form-holder .error").remove();
$('.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})}");
- else
:plain
$("#new_note").replaceWith("#{escape_javascript(render('form'))}");
$(".note-form-holder").replaceWith("#{escape_javascript(render('form'))}");
- if note.valid?
:plain
$(".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();
var trEl = $(".#{note.line_code}").parent();
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
= form_for [@project, @note], remote: "true", multipart: true do |f|
%h3.page_title Leave a comment
-if @note.errors.any?
.alert-message.block-message.error
......@@ -7,16 +8,16 @@
= f.hidden_field :noteable_id
= f.hidden_field :noteable_type
= f.text_area :note, size: 255
#preview-note.well.hide
%p.hint
= link_to "Gitlab Markdown", help_markdown_path, target: '_blank'
is enabled.
= link_to 'Preview', preview_project_notes_path(@project), id: 'preview-link'
= f.text_area :note, size: 255, class: 'note-text'
#preview-note.preview_note.hide
.hint
.right Comments are parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}.
.clearfix
.row.note_advanced_opts.hide
.span2
= f.submit 'Add Comment', class: "btn primary submit_note", id: "submit_note"
.span3
= f.submit 'Add Comment', class: "btn success submit_note grouped", id: "submit_note"
= link_to 'Preview', preview_project_notes_path(@project), class: 'btn grouped', id: 'preview-link'
.span4.notify_opts
%h6.left Notify via email:
= label_tag :notify do
......@@ -27,7 +28,7 @@
= label_tag :notify_author do
= check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
%span Commit author
.span6.attachments
.span5.attachments
%h6.left Attachment:
%span.file_name File name...
......
%table{style: "display:none;"}
%tr.per_line_form
%td{colspan: 3 }
.line-note-form-holder
= form_for [@project, @note], remote: "true", multipart: true do |f|
%h3.page_title Leave a note
%div.span10
......@@ -12,10 +13,10 @@
= f.hidden_field :noteable_id
= f.hidden_field :noteable_type
= f.hidden_field :line_code
= f.text_area :note, size: 255
= f.text_area :note, size: 255, class: 'line-note-text'
.note_actions
.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"
.options
%h6.left Notify via email:
......
%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 @@
= f.label :password_confirmation
.input= f.password_field :password_confirmation
.actions
= f.submit 'Save', class: "btn primary"
= f.submit 'Save', class: "btn save-btn"
......@@ -45,6 +45,7 @@
%span.help-block Tell us about yourself in fewer than 250 characters.
.span5.right
-unless Gitlab.config.disable_gravatar?
%p.alert.alert-info
%strong Tip:
You can change your avatar at gravatar.com
......@@ -66,4 +67,4 @@
= link_to "Add Public Key", new_key_path, class: "btn small right"
.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 @@
.input
= f.text_field :name, placeholder: "Example Project", class: "xxlarge"
%h5.page_title
.alert.alert-info
%h5 Advanced settings:
%hr
.adv_settings
%h6 Advanced settings:
.clearfix
= f.label :path do
Path
......@@ -34,8 +34,9 @@
.input= f.select(:default_branch, @project.heads.map(&:name), {}, style: "width:210px;")
- unless @project.new_record?
.alert.alert-info
%h5 Features:
%hr
.adv_settings
%h6 Features:
.clearfix
= f.label :issues_enabled, "Issues"
......@@ -56,7 +57,7 @@
%br
.actions
= f.submit 'Save', class: "btn primary"
= f.submit 'Save', class: "btn save-btn"
= link_to 'Cancel', @project, class: "btn"
- unless @project.new_record?
.right
......
......@@ -7,11 +7,11 @@
Project name is
.input
= 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
.alert.alert-info
%h5 Advanced settings:
%div.adv_settings
%h6 Advanced settings:
.clearfix
= f.label :path do
Git Clone
......
- if current_user.require_ssh_key?
.alert-message.block-message.error
%ul
%li You have no ssh keys added to your profile.
%li You wont be able to pull/push repository.
%li Visit profile &rarr; keys and add public key of every machine you want to use for work with gitlabhq.
.alert-message.block-message.error
%ul.unstyled.alert_holder
%li You should push repository to proceed.
%li After push you will be able to browse code, commits etc.
- bash_lexer = Pygments::Lexer[:bash]
= render 'shared/no_ssh'
.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"
%div.git-empty
%h3 Git global setup:
- setup_str = ["git config --global user.name \"#{current_user.name}\"",
"git config --global user.email \"#{current_user.email}\""].join("\n")
%h4 Git global setup:
%pre.dark
= preserve do
= raw bash_lexer.highlight(setup_str, lexer: 'bash', options: {encoding: 'utf-8'})
%br
%br
%h3 Create Repository
- 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")
git config --global user.name "#{current_user.name}"
git config --global user.email "#{current_user.email}"
%h4.prepend-top-20 Create Repository
%pre.dark
= preserve do
= raw bash_lexer.highlight(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
%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")
%h4.prepend-top-20 Existing Git Repo?
%pre.dark
= preserve do
= raw bash_lexer.highlight(exist_repo_setup_str)
cd existing_git_repo
git remote add origin #{@project.url_to_repo}
git push -u origin master
- if can? current_user, :admin_project, @project
.alert-message.block-message.error.prepend-top-20
= link_to 'Remove project', @project, confirm: 'Are you sure?', method: :delete, class: "btn danger"
.prepend-top-20
= link_to 'Remove project', @project, confirm: 'Are you sure?', method: :delete, class: "btn danger right"
:javascript
$(function(){
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 @@
New Project
%hr
= render 'new_form'
%div.ajax_loader.hide
%div.save-project-loader.hide
%center
%div.padded= image_tag "ajax_loader.gif"
%h3.prepend-top Creating project &amp; repository. Please wait a few minutes
= image_tag "ajax_loader.gif"
%h3 Creating project &amp; repository. Please wait a few minutes
:javascript
$(function(){ new Projects(); });
= render "project_head"
.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 'clone_panel'
= render "events/event_last_push", event: @last_push
.content_list= render @events
......
......@@ -2,7 +2,7 @@
%tr{ class: "tree-item #{tree_hex_class(content)}", url: tree_file_project_ref_path(@project, @ref, file) }
%td.tree-item-file-name
= 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
- if index == 1
%span.log_loading
......
= form_tag search_path, method: :get do |f|
= form_tag search_path, method: :get, class: 'form-inline' do |f|
.padded
= label_tag :search do
%strong Looking for
.input
= 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?
%br
%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 @@
%span.label Blocked
= 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
%strong= truncate(user.name, lenght: 40)
%br
......
......@@ -51,7 +51,7 @@
= 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
%hr
= render user.recent_events.limit(5)
= render @events
:javascript
$(function(){
$('.repo-access-select, .project-access-select').live("change", function() {
......
......@@ -14,13 +14,14 @@
.middle_box_content
.input
%span.cgray
Wiki content is parsed with #{link_to "Markdown", "http://en.wikipedia.org/wiki/Markdown"}.
To add link to new page you can just type
Wiki content is parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}.
To link to a (new) page you can just type
%code [Link Title](page-slug)
\.
.bottom_box_content
= f.label :content
.input= f.text_area :content, class: 'span8'
.actions
= f.submit 'Save', class: "primary btn"
= link_to "Cancel", project_wiki_path(@project, :index), class: "btn"
= f.submit 'Save', class: "save-btn btn"
= link_to "Cancel", project_wiki_path(@project, :index), class: "btn cancel-btn"
......@@ -23,7 +23,7 @@ module Gitlab
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
# 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.
# 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__)
# Initialize the rails application
Gitlab::Application.initialize!
require File.join(Rails.root, "lib", "gitlab", "git_host")
......@@ -23,7 +23,7 @@ app:
default_projects_limit: 10
# backup_path: "/vol/backups" # default: Rails.root + backups/
# backup_keep_time: 604800 # default: 0 (forever) (in seconds)
# disable_gravatar: true # default: false - Disable user avatars from Gravatar.com
#
# 2. Advanced settings:
......@@ -33,13 +33,13 @@ app:
git_host:
admin_uri: git@localhost:gitolite-admin
base_path: /home/git/repositories/
# hooks_path: /var/lib/gitolite/.gitolite/hooks/ # only needed when gitolite is not installed according the manual
# host: localhost
git_user: git
upload_pack: true
receive_pack: true
# port: 22
# Git settings
# Use default values unless you understand it
git:
......
......@@ -66,6 +66,10 @@ class Settings < Settingslogic
git_host['base_path'] || '/home/git/repositories/'
end
def git_hooks_path
git_host['hooks_path'] || '/home/git/share/gitolite/hooks/'
end
def git_upload_pack
if git_host['upload_pack'] != false
true
......@@ -111,5 +115,9 @@ class Settings < Settingslogic
def backup_keep_time
app['backup_keep_time'] || 0
end
def disable_gravatar?
app['disable_gravatar'] || false
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
get 'help/web_hooks' => 'help#web_hooks'
get 'help/system_hooks' => 'help#system_hooks'
get 'help/markdown' => 'help#markdown'
get 'help/ssh' => 'help#ssh'
#
# Admin Area
......@@ -196,7 +197,9 @@ Gitlab::Application.routes.draw do
end
resources :team_members
resources :milestones
resources :labels, :only => [:index]
resources :issues do
collection do
post :sort
post :bulk_update
......
# create tmp dir if not exist
tmp_dir = File.join(Rails.root, "tmp")
Dir.mkdir(tmp_dir) unless File.exists?(tmp_dir)
# Create dir for test repo
repo_dir = File.join(Rails.root, "tmp", "tests")
Dir.mkdir(repo_dir) unless File.exists?(repo_dir)
`cp spec/seed_project.tar.gz tmp/tests/`
Dir.chdir(repo_dir)
`tar -xf seed_project.tar.gz`
3.times do |i|
`cp -r gitlabhq/ gitlabhq_#{i}/`
puts "Unpacked seed repo - tmp/tests/gitlabhq_#{i}"
require 'fileutils'
print "Unpacking seed repository..."
SEED_REPO = 'seed_project.tar.gz'
REPO_PATH = File.join(Rails.root, 'tmp', 'repositories')
# Make whatever directories we need to make
FileUtils.mkdir_p(REPO_PATH)
# Copy the archive to the repo path
FileUtils.cp(File.join(Rails.root, 'spec', SEED_REPO), REPO_PATH)
# 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
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 @@
#
# 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|
t.string "target_type"
......@@ -171,9 +171,12 @@ ActiveRecord::Schema.define(:version => 20120712080407) do
t.boolean "blocked", :default => false, :null => false
t.integer "failed_attempts", :default => 0
t.datetime "locked_at"
t.string "extern_uid"
t.string "provider"
end
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
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
+ [Users](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/users.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)
+ [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:
]
```
# 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
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:
sudo chmod -R g+rwX /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
......@@ -178,6 +177,11 @@ Permissions:
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:
sudo -u gitlab bundle exec rake gitlab:app:status RAILS_ENV=production
......@@ -196,6 +200,7 @@ Checking status:
Resolving deltas: 100% (174/174), done.
Can clone gitolite-admin?............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.
......@@ -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 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
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;
Add gitlab to nginx sites & change with your host specific settings
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:
......@@ -282,60 +260,7 @@ Restart nginx:
Create init script in /etc/init.d/gitlab:
#! /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
cp /home/gitlab/gitlab/lib/support/init-gitlab /etc/init.d/gitlab
Adding permission:
......
......@@ -3,8 +3,8 @@ Feature: SSH Keys
Given I signin as a user
And I have ssh keys:
| title |
| Work |
| Home |
| ssh-rsa Work |
| ssh-rsa Home |
And I visit profile keys page
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"
......@@ -8,5 +8,3 @@ Feature: Project Network Graph
Scenario: I should see project network
Then page should have network graph
......@@ -91,36 +91,24 @@ Then /^I should see my merge requests$/ do
end
Given /^I have assigned issues$/ do
project1 = Factory :project,
:path => "project1",
:code => "gitlabhq_1"
project2 = Factory :project,
:path => "project2",
:code => "gitlabhq_2"
project1.add_access(@user, :read, :write)
project2.add_access(@user, :read, :write)
project = Factory :project
project.add_access(@user, :read, :write)
issue1 = Factory :issue,
:author => @user,
:assignee => @user,
:project => project1
:project => project
issue2 = Factory :issue,
:author => @user,
:assignee => @user,
:project => project2
:project => project
end
Given /^I have authored merge requests$/ do
project1 = Factory :project,
:path => "project1",
:code => "gitlabhq_1"
project1 = Factory :project
project2 = Factory :project,
:path => "project2",
:code => "gitlabhq_2"
project2 = Factory :project
project1.add_access(@user, :read, :write)
project2.add_access(@user, :read, :write)
......
......@@ -16,7 +16,7 @@ end
Given /^I submit new ssh key "(.*?)"$/ do |arg1|
fill_in "key_title", :with => arg1
fill_in "key_key", :with => "publickey234="
fill_in "key_key", :with => "ssh-rsa publickey234="
click_button "Save"
end
......
......@@ -33,6 +33,25 @@ Given /^I visit issue page "(.*?)"$/ do |arg1|
end
Given /^I submit new issue "(.*?)"$/ do |arg1|
fill_in "issue_title", :with => arg1
fill_in "issue_title", with: arg1
click_button "Submit new issue"
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
login_as :user
......@@ -57,6 +57,11 @@ end
Given /^I visit project "(.*?)" network page$/ do |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)
end
......@@ -67,8 +72,8 @@ end
Given /^page should have network graph$/ do
page.should have_content "Project Network Graph"
within ".graph" do
page.should have_content "stable"
page.should have_content "notes_refacto..."
page.should have_content "master"
page.should have_content "scss_refactor..."
end
end
......
require 'simplecov'
SimpleCov.start 'rails'
unless ENV['CI']
require 'simplecov'
SimpleCov.start 'rails'
end
require 'cucumber/rails'
require 'webmock/cucumber'
WebMock.allow_net_connect!
require Rails.root.join 'spec/monkeypatch'
require Rails.root.join 'spec/factories'
require Rails.root.join 'spec/support/login'
require Rails.root.join 'spec/support/gitolite_stub'
require Rails.root.join 'spec/support/stubbed_repository'
require Rails.root.join 'spec/support/login_helpers'
require Rails.root.join 'spec/support/valid_commit'
Capybara.default_selector = :css
......@@ -44,3 +47,13 @@ require 'headless'
headless = Headless.new
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
mount Users
mount Projects
mount Issues
mount Milestones
end
end
......@@ -95,7 +95,7 @@ module Gitlab
end
end
# Delete a project issue
# Delete a project issue (deprecated)
#
# Parameters:
# id (required) - The ID or code name of a project
......@@ -103,8 +103,7 @@ module Gitlab
# Example Request:
# DELETE /projects/:id/issues/:issue_id
delete ":id/issues/:issue_id" do
@issue = user_project.issues.find(params[:issue_id])
@issue.destroy
error!({'message' => 'method not allowed'}, 405)
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'
require 'timeout'
require 'fileutils'
# TODO: refactor & cleanup
module Gitlab
class Gitolite
class AccessDenied < StandardError; end
class InvalidKey < StandardError; end
def self.update_project(path, project)
self.new.configure { |git| git.update_project(path, project) }
def set_key key_id, key_content, projects
configure do |c|
c.update_keys(key_id, key_content)
c.update_projects(projects)
end
def self.destroy_project(project)
self.new.configure { |git| git.destroy_project(project) }
end
def pull
# create tmp dir
@local_dir = File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}")
Dir.mkdir @local_dir
def remove_key key_id, projects
configure do |c|
c.delete_key(key_id)
c.update_projects(projects)
end
end
`git clone #{GitHost.admin_uri} #{@local_dir}/gitolite`
def update_repository project
configure do |c|
c.update_project(project.path, project)
end
end
def push
Dir.chdir(File.join(@local_dir, "gitolite"))
`git add -A`
`git commit -am "Gitlab"`
`git push`
Dir.chdir(Rails.root)
alias_method :create_repository, :update_repository
FileUtils.rm_rf(@local_dir)
def remove_repository project
configure do |c|
c.destroy_project(project)
end
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)
def url_to_repo path
Gitlab.config.ssh_path + "#{path}.git"
end
def initialize
# create tmp dir
@local_dir = File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}")
end
def enable_automerge
configure do |git|
git.admin_all_repo
end
rescue Exception => ex
Gitlab::Logger.error(ex.message)
raise Gitolite::AccessDenied.new("gitolite timeout")
end
protected
def destroy_project(project)
FileUtils.rm_rf(project.path_to_repo)
......@@ -106,13 +109,13 @@ module Gitlab
name_writers = project.repository_writers
name_masters = project.repository_masters
pr_br = project.protected_branches.map(&:name).join(" ")
pr_br = project.protected_branches.map(&:name).join("$ ")
repo.clean_permissions
# Deny access to protected branches for writers
unless name_writers.blank? || pr_br.blank?
repo.add_permission("-", pr_br, name_writers)
repo.add_permission("-", pr_br.strip + "$ ", name_writers)
end
# Add read permissions
......@@ -153,5 +156,47 @@ module Gitlab
conf.add_repo(repo, true)
ga_repo.save
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
......@@ -42,13 +42,13 @@ module Grack
def current_ref
if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/
input = Zlib::GzipReader.new(@request.body).string
input = Zlib::GzipReader.new(@request.body).read
else
input = @request.body.string
input = @request.body.read
end
oldrev, newrev, ref = input.split(' ')
/refs\/heads\/([\w-]+)/.match(ref).to_a.last
# Need to reset seek point
@request.body.rewind
/refs\/heads\/([\w-]+)/.match(input).to_a.first
end
end# Auth
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
# 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
#
......@@ -67,25 +76,25 @@ module Gitlab
def reference_user(identifier)
if user = @project.users.where(name: identifier).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
def reference_issue(identifier)
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
def reference_merge_request(identifier)
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
def reference_snippet(identifier)
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
......
#! /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
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 = [
"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 gitlab:gitlab /home/git/repositories/**/hooks/post-receive"
"sudo chown -R #{Gitlab.config.ssh_user}:#{Gitlab.config.ssh_user} #{Gitlab.config.git_base_path}"
]
permission_commands.each { |command| Kernel.system(command) }
puts "[DONE]".green
......
......@@ -2,9 +2,7 @@ namespace :gitlab do
namespace :app do
desc "GITLAB | Enable auto merge"
task :enable_automerge => :environment do
Gitlab::GitHost.system.new.configure do |git|
git.admin_all_repo
end
Gitlab::Gitolite.new.enable_automerge
Project.find_each do |project|
if project.repo_exists? && !project.satellite.exists?
......
......@@ -16,7 +16,7 @@ namespace :gitlab do
task :update_keys => :environment do
puts "Starting Key"
Key.find_each(:batch_size => 100) do |key|
key.update_repository
Gitlab::Gitolite.new.set_key(key.identifier, key.key, key.projects)
print '.'
end
puts "Done with keys"
......
namespace :gitlab do
namespace :app do
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
......@@ -56,6 +56,20 @@ namespace :gitlab do
return
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
puts "Validating projects repositories:".yellow
Project.find_each(:batch_size => 100) do |project|
......@@ -67,13 +81,7 @@ namespace :gitlab do
next
end
unless File.owned?(hook_file)
puts "post-receive file is not owner by gitlab".red
next
end
puts "post-reveice file ok".green
puts "post-receive file ok".green
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')
Factory.add(:project, Project) do |obj|
obj.name = Faker::Internet.user_name
obj.path = 'gitlabhq'
obj.owner = Factory(:user)
obj.code = 'LGT'
# Backwards compatibility with the old method
def Factory(type, *args)
FactoryGirl.create(type, *args)
end
Factory.add(:project_without_owner, Project) do |obj|
obj.name = Faker::Internet.user_name
obj.path = 'gitlabhq'
obj.code = 'LGT'
end
module Factory
def self.create(type, *args)
FactoryGirl.create(type, *args)
end
Factory.add(:public_project, Project) do |obj|
obj.name = Faker::Internet.user_name
obj.path = 'gitlabhq'
obj.private_flag = false
obj.owner = Factory(:user)
obj.code = 'LGT'
def self.new(type, *args)
FactoryGirl.build(type, *args)
end
end
Factory.add(:user, User) do |obj|
obj.email = Faker::Internet.email
obj.password = "123456"
obj.name = Faker::Name.name
obj.password_confirmation = "123456"
end
FactoryGirl.define do
sequence :sentence, aliases: [:title, :content] do
Faker::Lorem.sentence
end
Factory.add(:admin, User) do |obj|
obj.email = Faker::Internet.email
obj.password = "123456"
obj.name = Faker::Name.name
obj.password_confirmation = "123456"
obj.admin = true
end
sequence :name, aliases: [:file_name] do
Faker::Name.name
end
Factory.add(:issue, Issue) do |obj|
obj.title = Faker::Lorem.sentence
obj.author = Factory :user
obj.assignee = Factory :user
end
sequence(:url) { Faker::Internet.uri('http') }
Factory.add(:merge_request, MergeRequest) do |obj|
obj.title = Faker::Lorem.sentence
obj.author = Factory :user
obj.assignee = Factory :user
obj.source_branch = "master"
obj.target_branch = "stable"
obj.closed = false
end
factory :user, aliases: [:author, :assignee, :owner] do
email { Faker::Internet.email }
name
password "123456"
password_confirmation "123456"
Factory.add(:snippet, Snippet) do |obj|
obj.title = Faker::Lorem.sentence
obj.file_name = Faker::Lorem.sentence
obj.content = Faker::Lorem.sentences
end
trait :admin do
admin true
end
Factory.add(:note, Note) do |obj|
obj.note = Faker::Lorem.sentence
end
factory :admin, traits: [:admin]
end
Factory.add(:key, Key) do |obj|
obj.title = "Example key"
obj.key = File.read(File.join(Rails.root, "db", "pkey.example"))
end
factory :project do
sequence(:name) { |n| "project#{n}" }
path { name }
code { name }
owner
end
Factory.add(:project_hook, ProjectHook) do |obj|
obj.url = Faker::Internet.uri("http")
end
factory :users_project do
user
project
end
Factory.add(:system_hook, SystemHook) do |obj|
obj.url = Faker::Internet.uri("http")
end
factory :issue do
title
author
project
Factory.add(:wiki, Wiki) do |obj|
obj.title = Faker::Lorem.sentence
obj.content = Faker::Lorem.sentence
obj.user = Factory(:user)
obj.project = Factory(:project)
end
trait :closed do
closed true
end
Factory.add(:event, Event) do |obj|
obj.title = Faker::Lorem.sentence
obj.project = Factory(:project)
end
factory :closed_issue, traits: [:closed]
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|
obj.title = Faker::Lorem.sentence
obj.due_date = Date.today + 1.month
factory :protected_branch do
name
project
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"
describe GitlabMarkdownHelper do
before do
@project = Project.find_by_path("gitlabhq") || Factory(:project)
@project = Factory(:project)
@commit = @project.repo.commits.first.parents.first
@commit = CommitDecorator.decorate(Commit.new(@commit))
@other_project = Factory :project, path: "OtherPath", code: "OtherCode"
......@@ -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 "}"
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."
end
......
......@@ -24,7 +24,7 @@ describe Notify do
end
it 'has the correct subject' do
should have_subject /Account was created for you/
should have_subject /^gitlab \| Account was created for you$/
end
it 'contains the new user\'s login name' do
......@@ -60,7 +60,7 @@ describe Notify do
it_behaves_like 'an assignee email'
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
it 'contains a link to the new issue' do
......@@ -76,7 +76,7 @@ describe Notify do
it_behaves_like 'a multiple recipients email'
it 'has the correct subject' do
should have_subject /changed issue/
should have_subject /changed issue ##{issue.id} \| #{issue.title}/
end
it 'contains the name of the previous assignee' do
......@@ -91,6 +91,29 @@ describe Notify do
should have_body_text /#{project_issue_path project, issue}/
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
context 'for merge requests' do
......@@ -145,6 +168,26 @@ describe Notify do
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
let(:note_author) { Factory.create(:user, name: 'author_name') }
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'
describe Event do
describe "Associations" do
it { should belong_to(:project) }
it { should belong_to(:target) }
end
describe "Respond to" do
......@@ -29,16 +14,6 @@ describe Event do
it { should respond_to(:commits) }
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
before do
project = Factory :project
......
......@@ -2,28 +2,19 @@ require 'spec_helper'
describe Issue do
describe "Associations" do
it { should belong_to(:project) }
it { should belong_to(:author) }
it { should belong_to(:assignee) }
it { should belong_to(:milestone) }
end
describe "Validation" do
it { should validate_presence_of(:title) }
it { should validate_presence_of(:author_id) }
it { should validate_presence_of(:project_id) }
it { should ensure_length_of(:description).is_within(0..2000) }
end
describe "Scope" do
it { Issue.should respond_to :closed }
it { Issue.should respond_to :opened }
describe 'modules' do
it { should include_module(IssueCommonality) }
it { should include_module(Upvote) }
end
subject { Factory.create(:issue,
author: Factory(:user),
assignee: Factory(:user),
project: Factory.create(:project)) }
it { should be_valid }
subject { Factory.create(:issue) }
describe '#is_being_reassigned?' do
it 'returns true if the issue assignee has changed' do
......@@ -41,11 +32,7 @@ describe Issue do
subject.is_being_closed?.should be_true
end
it 'returns false if the closed attribute has changed and is now false' do
issue = Factory.create(:issue,
closed: true,
author: Factory(:user),
assignee: Factory(:user),
project: Factory.create(:project))
issue = Factory.create(:closed_issue)
issue.closed = false
issue.is_being_closed?.should be_false
end
......@@ -57,11 +44,7 @@ describe Issue do
describe '#is_being_reopened?' do
it 'returns true if the closed attribute has changed and is now false' do
issue = Factory.create(:issue,
closed: true,
author: Factory(:user),
assignee: Factory(:user),
project: Factory.create(:project))
issue = Factory.create(:closed_issue)
issue.closed = false
issue.is_being_reopened?.should be_true
end
......@@ -73,64 +56,4 @@ describe Issue do
subject.is_being_reopened?.should be_false
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
# == 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'
describe Key do
describe "Associations" do
it { should belong_to(:user) or belong_to(:project) }
it { should belong_to(:user) }
it { should belong_to(:project) }
end
describe "Validation" do
it { should validate_presence_of(:title) }
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
describe "Methods" do
......@@ -17,20 +20,15 @@ describe Key do
context "validation of uniqueness" do
context "as a deploy key" do
let(:project) { Factory.create(:project, path: 'alpha', code: 'alpha') }
let(:another_project) { Factory.create(:project, path: 'beta', code: 'beta') }
before do
deploy_key = Factory.create(:key, project: project)
end
let!(:deploy_key) { create(:deploy_key) }
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
end
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
end
end
......@@ -39,27 +37,13 @@ describe Key do
let(:user) { Factory.create(:user) }
it "accepts the key once" do
Factory.new(:key, user: user).should be_valid
build(:key, user: user).should be_valid
end
it "does not accepts the key twice" do
Factory.create(:key, user: user)
Factory.new(:key, user: user).should_not be_valid
create(:key, user: user)
build(:key, user: user).should_not be_valid
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'
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
it { should validate_presence_of(:target_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
describe "Scope" do
it { MergeRequest.should respond_to :closed }
it { MergeRequest.should respond_to :opened }
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
describe 'modules' do
it { should include_module(IssueCommonality) }
it { should include_module(Upvote) }
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'
describe Milestone do
......@@ -25,30 +11,36 @@ describe Milestone do
it { should validate_presence_of(:project_id) }
end
let(:project) { Factory :project }
let(:milestone) { Factory :milestone, project: project }
let(:issue) { Factory :issue, project: project }
it { milestone.should be_valid }
let(:milestone) { Factory :milestone }
let(:issue) { Factory :issue }
describe "Issues" do
before do
describe "#percent_complete" do
it "should not count open issues" do
milestone.issues << issue
milestone.percent_complete.should == 0
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
issue.update_attributes closed: true
it "should recover from dividing by zero" do
milestone.issues.should_receive(:count).and_return(0)
milestone.percent_complete.should == 100
end
end
describe :expires_at do
before do
milestone.update_attributes due_date: Date.today + 1.day
describe "#expires_at" do
it "should be nil when due_date is unset" do
milestone.update_attributes(due_date: nil)
milestone.expires_at.should be_nil
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
require 'spec_helper'
describe Note do
let(:project) { Factory :project }
let!(:commit) { project.commit }
describe "Associations" do
it { should belong_to(:project) }
it { should belong_to(:noteable) }
it { should belong_to(:author).class_name('User') }
end
describe "Validation" do
......@@ -13,8 +12,6 @@ describe Note do
it { should validate_presence_of(:project) }
end
it { Factory.create(:note,
project: project).should be_valid }
describe "Scopes" do
it "should have a today named scope that returns ..." do
Note.today.where_values.should == ["created_at >= '#{Date.today}'"]
......@@ -25,26 +22,27 @@ describe Note do
let(:project) { Factory(:project) }
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
end
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
end
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
end
end
describe "Commit notes" do
let(:project) { create(:project) }
let(:commit) { project.commit }
describe "Commit notes" do
before do
@note = Factory :note,
project: project,
noteable_id: commit.id,
noteable_type: "Commit"
end
......@@ -58,7 +56,6 @@ describe Note do
describe "Pre-line commit notes" do
before do
@note = Factory :note,
project: project,
noteable_id: commit.id,
noteable_type: "Commit",
line_code: "0_16_1"
......@@ -91,8 +88,8 @@ describe Note do
describe :authorization do
before do
@p1 = project
@p2 = Factory :project, code: "alien", path: "gitlabhq_1"
@p1 = create(:project)
@p2 = Factory :project
@u1 = Factory :user
@u2 = Factory :user
@u3 = Factory :user
......@@ -135,19 +132,3 @@ describe Note do
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'
describe Project do
describe "Associations" do
it { should belong_to(:owner).class_name('User') }
it { should have_many(:users) }
it { should have_many(:protected_branches).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(:users_projects).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(:snippets).dependent(:destroy) }
it { should have_many(:hooks).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
describe "Validation" do
let!(:project) { create(:project) }
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_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_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
describe "Respond to" do
......@@ -40,7 +69,6 @@ describe Project do
it { should respond_to(:commits_with_refs) }
it { should respond_to(:commits_since) }
it { should respond_to(:commits_between) }
it { should respond_to(:write_hooks) }
it { should respond_to(:satellite) }
it { should respond_to(:update_repository) }
it { should respond_to(:destroy_repository) }
......@@ -74,9 +102,11 @@ describe Project do
it { should respond_to(:trigger_post_receive) }
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)
describe 'modules' do
it { should include_module(Repository) }
it { should include_module(PushObserver) }
it { should include_module(Authority) }
it { should include_module(Team) }
end
it "should return valid url to repo" do
......@@ -86,7 +116,7 @@ describe Project do
it "should return path to repo" do
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
it "returns the full web URL for this repo" do
......@@ -111,7 +141,7 @@ describe Project do
let(:last_event) { double }
before do
project.stub(:events).and_return( [ double, double, last_event ] )
project.stub_chain(:events, :order).and_return( [ double, double, last_event ] )
end
it { project.last_activity.should == last_event }
......@@ -237,23 +267,3 @@ describe Project do
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'
describe ProtectedBranch do
let(:project) { Factory(:project) }
describe 'Associations' do
it { should belong_to(:project) }
end
......@@ -24,26 +11,26 @@ describe ProtectedBranch do
end
describe 'Callbacks' do
subject { ProtectedBranch.new(project: project, name: 'branch_name') }
let(:branch) { build(:protected_branch) }
it 'call update_repository after save' do
subject.should_receive(:update_repository)
subject.save
branch.should_receive(:update_repository)
branch.save
end
it 'call update_repository after destroy' do
subject.should_receive(:update_repository)
subject.destroy
branch.save
branch.should_receive(:update_repository)
branch.destroy
end
end
describe '#commit' do
subject { ProtectedBranch.new(project: project, name: 'cant_touch_this') }
let(:branch) { create(:protected_branch) }
it 'commits itself to its project' do
project.should_receive(:commit).with('cant_touch_this')
subject.commit
branch.project.should_receive(:commit).with(branch.name)
branch.commit
end
end
end
......@@ -3,29 +3,21 @@ require 'spec_helper'
describe Snippet do
describe "Associations" do
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
describe "Validation" do
it { should validate_presence_of(:title) }
it { should validate_presence_of(:author_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 ensure_length_of(:title).is_within(0..255) }
it { should validate_presence_of(:content) }
it { should ensure_length_of(:content).is_within(0..10_000) }
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,9 +10,8 @@ describe SystemHook do
end
it "project_create hook" do
user = Factory :user
with_resque do
project = Factory :project_without_owner, owner: user
project = Factory :project
end
WebMock.should have_requested(:post, @system_hook.url).with(body: /project_create/).once
end
......
......@@ -2,12 +2,26 @@ require 'spec_helper'
describe User do
describe "Associations" do
it { should have_many(:users_projects).dependent(:destroy) }
it { should have_many(:projects) }
it { should have_many(:users_projects) }
it { should have_many(:issues) }
it { should have_many(:assigned_issues) }
it { should have_many(:merge_requests) }
it { should have_many(:assigned_merge_requests) }
it { should have_many(:my_own_projects).class_name('Project') }
it { should have_many(:keys).dependent(:destroy) }
it { should have_many(:events).class_name('Event').dependent(:destroy) }
it { should have_many(:recent_events).class_name('Event') }
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
describe "Respond to" do
......@@ -49,49 +63,4 @@ describe User do
user = Factory(:user)
user.authentication_token.should_not == ""
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
# == 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
end
describe "Validation" do
let!(:users_project) { create(:users_project) }
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) }
end
......@@ -16,15 +20,3 @@ describe UsersProject do
it { should respond_to(:user_email) }
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
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
describe "Associations" do
it { should belong_to(:project) }
it { should belong_to(:user) }
it { should have_many(:notes).dependent(:destroy) }
end
describe "Validation" do
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(:user_id) }
end
it { Factory(:wiki).should be_valid }
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'
describe IssueObserver do
let(:some_user) { double(:user, id: 1) }
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) }
......@@ -67,36 +68,90 @@ describe IssueObserver do
end
end
context 'a status "closed" note' do
it 'is created if the issue is being closed' do
context 'a status "closed"' do
it 'note is created if the issue is being closed' do
issue.should_receive(:is_being_closed?).and_return(true)
Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed')
subject.after_update(issue)
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)
Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'closed')
subject.after_update(issue)
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
context 'a status "reopened" note' do
it 'is created if the issue is being reopened' do
context 'a status "reopened"' do
it 'note is created if the issue is being reopened' do
issue.should_receive(:is_being_reopened?).and_return(true)
Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened')
subject.after_update(issue)
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)
Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'reopened')
subject.after_update(issue)
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
......
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
visit new_admin_project_path
fill_in 'project_name', with: 'NewProject'
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)
@project = Project.last
end
......
......@@ -2,20 +2,26 @@ require 'spec_helper'
describe "Admin::Projects" do
describe "GET /admin/projects" do
it { admin_projects_path.should be_allowed_for :admin }
it { admin_projects_path.should be_denied_for :user }
it { admin_projects_path.should be_denied_for :visitor }
subject { admin_projects_path }
it { should be_allowed_for :admin }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end
describe "GET /admin/users" do
it { admin_users_path.should be_allowed_for :admin }
it { admin_users_path.should be_denied_for :user }
it { admin_users_path.should be_denied_for :visitor }
subject { admin_users_path }
it { should be_allowed_for :admin }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end
describe "GET /admin/hooks" do
it { admin_hooks_path.should be_allowed_for :admin }
it { admin_hooks_path.should be_denied_for :user }
it { admin_hooks_path.should be_denied_for :visitor }
subject { admin_hooks_path }
it { should be_allowed_for :admin }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end
end
require 'spec_helper'
describe Gitlab::API do
include ApiHelpers
let(:user) { Factory :user }
let!(:project) { Factory :project, owner: user }
let!(:issue) { Factory :issue, author: user, assignee: user, project: project }
......@@ -8,13 +10,13 @@ describe Gitlab::API do
describe "GET /issues" do
it "should return authentication error" do
get "#{api_prefix}/issues"
get api("/issues")
response.status.should == 401
end
describe "authenticated GET /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
json_response.should be_an Array
json_response.first['title'].should == issue.title
......@@ -24,7 +26,7 @@ describe Gitlab::API do
describe "GET /projects/:id/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
json_response.should be_an Array
json_response.first['title'].should == issue.title
......@@ -33,7 +35,7 @@ describe Gitlab::API do
describe "GET /projects/:id/issues/:issue_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
json_response['title'].should == issue.title
end
......@@ -41,7 +43,7 @@ describe Gitlab::API do
describe "POST /projects/:id/issues" 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'
response.status.should == 201
json_response['title'].should == 'new issue'
......@@ -52,7 +54,7 @@ describe Gitlab::API do
describe "PUT /projects/:id/issues/:issue_id" 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
response.status.should == 200
json_response['title'].should == 'updated title'
......@@ -63,9 +65,8 @@ describe Gitlab::API do
describe "DELETE /projects/:id/issues/:issue_id" do
it "should delete a project issue" do
expect {
delete "#{api_prefix}/projects/#{project.code}/issues/#{issue.id}?private_token=#{user.private_token}"
}.to change { Issue.count }.by(-1)
delete api("/projects/#{project.code}/issues/#{issue.id}", user)
response.status.should == 405
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'
describe Gitlab::API do
include ApiHelpers
let(:user) { Factory :user }
let!(:project) { Factory :project, owner: user }
let!(:snippet) { Factory :snippet, author: user, project: project, title: 'example' }
......@@ -8,13 +10,13 @@ describe Gitlab::API do
describe "GET /projects" do
it "should return authentication error" do
get "#{api_prefix}/projects"
get api("/projects")
response.status.should == 401
end
describe "authenticated GET /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
json_response.should be_an Array
json_response.first['name'].should == project.name
......@@ -25,20 +27,20 @@ describe Gitlab::API do
describe "GET /projects/: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
json_response['name'].should == project.name
json_response['owner']['email'].should == user.email
end
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
json_response['name'].should == project.name
end
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
json_response['message'].should == '404 Not found'
end
......@@ -46,7 +48,7 @@ describe Gitlab::API do
describe "GET /projects/:id/repository/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
json_response.should be_an Array
json_response.first['name'].should == project.repo.heads.sort_by(&:name).first.name
......@@ -55,7 +57,7 @@ describe Gitlab::API do
describe "GET /projects/:id/repository/branches/: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
json_response['name'].should == 'new_design'
......@@ -65,7 +67,7 @@ describe Gitlab::API do
describe "GET /projects/:id/repository/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
json_response.should be_an Array
json_response.first['name'].should == project.repo.tags.sort_by(&:name).reverse.first.name
......@@ -74,7 +76,7 @@ describe Gitlab::API do
describe "GET /projects/:id/snippets/:snippet_id" 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
json_response['title'].should == snippet.title
end
......@@ -82,7 +84,7 @@ describe Gitlab::API do
describe "POST /projects/:id/snippets" 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'
response.status.should == 201
json_response['title'].should == 'api test'
......@@ -91,7 +93,7 @@ describe Gitlab::API do
describe "PUT /projects/:id/snippets" 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'
response.status.should == 200
json_response['title'].should == 'example'
......@@ -102,34 +104,31 @@ describe Gitlab::API do
describe "DELETE /projects/:id/snippets/:snippet_id" do
it "should delete existing project snippet" do
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)
end
end
describe "GET /projects/:id/snippets/:snippet_id/raw" 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
end
end
describe "GET /projects/:id/:sha/blob" 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
end
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
end
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
end
end
......
require 'spec_helper'
describe Gitlab::API do
include ApiHelpers
let(:user) { Factory :user }
describe "GET /users" do
it "should return authentication error" do
get "#{api_prefix}/users"
get api("/users")
response.status.should == 401
end
describe "authenticated GET /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
json_response.should be_an Array
json_response.first['email'].should == user.email
......@@ -21,7 +23,7 @@ describe Gitlab::API do
describe "GET /users/: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
json_response['email'].should == user.email
end
......@@ -29,7 +31,7 @@ describe Gitlab::API do
describe "GET /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
json_response['email'].should == user.email
end
......
......@@ -6,13 +6,9 @@ describe "User Issues Dashboard" do
login_as :user
@project1 = Factory :project,
path: "project1",
code: "TEST1"
@project1 = Factory :project
@project2 = Factory :project,
path: "project2",
code: "TEST2"
@project2 = Factory :project
@project1.add_access(@user, :read, :write)
@project2.add_access(@user, :read, :write)
......
......@@ -42,7 +42,7 @@ describe "Projects", "DeployKeys" do
describe "fill in" do
before do
fill_in "key_title", with: "laptop"
fill_in "key_key", with: "publickey234="
fill_in "key_key", with: "ssh-rsa publickey234="
end
it { expect { click_button "Save" }.to change {Key.count}.by(1) }
......
......@@ -11,24 +11,30 @@ describe "Users Security" do
end
describe "GET /keys" do
it { keys_path.should be_allowed_for @u1 }
it { keys_path.should be_allowed_for :admin }
it { keys_path.should be_allowed_for :user }
it { keys_path.should be_denied_for :visitor }
subject { keys_path }
it { should be_allowed_for @u1 }
it { should be_allowed_for :admin }
it { should be_allowed_for :user }
it { should be_denied_for :visitor }
end
describe "GET /profile" do
it { profile_path.should be_allowed_for @u1 }
it { profile_path.should be_allowed_for :admin }
it { profile_path.should be_allowed_for :user }
it { profile_path.should be_denied_for :visitor }
subject { profile_path }
it { should be_allowed_for @u1 }
it { should be_allowed_for :admin }
it { should be_allowed_for :user }
it { should be_denied_for :visitor }
end
describe "GET /profile/password" do
it { profile_password_path.should be_allowed_for @u1 }
it { profile_password_path.should be_allowed_for :admin }
it { profile_password_path.should be_allowed_for :user }
it { profile_password_path.should be_denied_for :visitor }
subject { profile_password_path }
it { should be_allowed_for @u1 }
it { should be_allowed_for :admin }
it { should be_allowed_for :user }
it { should be_denied_for :visitor }
end
end
end
......@@ -26,64 +26,76 @@ describe "Application access" do
end
describe "GET /project_code" do
it { project_path(@project).should be_allowed_for @u1 }
it { project_path(@project).should be_allowed_for @u3 }
it { project_path(@project).should be_denied_for :admin }
it { project_path(@project).should be_denied_for @u2 }
it { project_path(@project).should be_denied_for :user }
it { project_path(@project).should be_denied_for :visitor }
subject { project_path(@project) }
it { should be_allowed_for @u1 }
it { should be_allowed_for @u3 }
it { should be_denied_for :admin }
it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end
describe "GET /project_code/master/tree" do
it { tree_project_ref_path(@project, @project.root_ref).should be_allowed_for @u1 }
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 { tree_project_ref_path(@project, @project.root_ref).should be_denied_for @u2 }
it { tree_project_ref_path(@project, @project.root_ref).should be_denied_for :user }
it { tree_project_ref_path(@project, @project.root_ref).should be_denied_for :visitor }
subject { tree_project_ref_path(@project, @project.root_ref) }
it { should be_allowed_for @u1 }
it { should be_allowed_for @u3 }
it { should be_denied_for :admin }
it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end
describe "GET /project_code/commits" do
it { project_commits_path(@project).should be_allowed_for @u1 }
it { project_commits_path(@project).should be_allowed_for @u3 }
it { project_commits_path(@project).should be_denied_for :admin }
it { project_commits_path(@project).should be_denied_for @u2 }
it { project_commits_path(@project).should be_denied_for :user }
it { project_commits_path(@project).should be_denied_for :visitor }
subject { project_commits_path(@project) }
it { should be_allowed_for @u1 }
it { should be_allowed_for @u3 }
it { should be_denied_for :admin }
it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end
describe "GET /project_code/commit" do
it { project_commit_path(@project, @project.commit.id).should be_allowed_for @u1 }
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 { project_commit_path(@project, @project.commit.id).should be_denied_for @u2 }
it { project_commit_path(@project, @project.commit.id).should be_denied_for :user }
it { project_commit_path(@project, @project.commit.id).should be_denied_for :visitor }
subject { project_commit_path(@project, @project.commit.id) }
it { should be_allowed_for @u1 }
it { should be_allowed_for @u3 }
it { should be_denied_for :admin }
it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end
describe "GET /project_code/team" do
it { team_project_path(@project).should be_allowed_for @u1 }
it { team_project_path(@project).should be_allowed_for @u3 }
it { team_project_path(@project).should be_denied_for :admin }
it { team_project_path(@project).should be_denied_for @u2 }
it { team_project_path(@project).should be_denied_for :user }
it { team_project_path(@project).should be_denied_for :visitor }
subject { team_project_path(@project) }
it { should be_allowed_for @u1 }
it { should be_allowed_for @u3 }
it { should be_denied_for :admin }
it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end
describe "GET /project_code/wall" do
it { wall_project_path(@project).should be_allowed_for @u1 }
it { wall_project_path(@project).should be_allowed_for @u3 }
it { wall_project_path(@project).should be_denied_for :admin }
it { wall_project_path(@project).should be_denied_for @u2 }
it { wall_project_path(@project).should be_denied_for :user }
it { wall_project_path(@project).should be_denied_for :visitor }
subject { wall_project_path(@project) }
it { should be_allowed_for @u1 }
it { should be_allowed_for @u3 }
it { should be_denied_for :admin }
it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end
describe "GET /project_code/blob" do
before do
@commit = @project.commit
@path = @commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name
@blob_path = blob_project_ref_path(@project, @commit.id, path: @path)
commit = @project.commit
path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name
@blob_path = blob_project_ref_path(@project, commit.id, path: path)
end
it { @blob_path.should be_allowed_for @u1 }
......@@ -95,93 +107,113 @@ describe "Application access" do
end
describe "GET /project_code/edit" do
it { edit_project_path(@project).should be_allowed_for @u1 }
it { edit_project_path(@project).should be_denied_for @u3 }
it { edit_project_path(@project).should be_denied_for :admin }
it { edit_project_path(@project).should be_denied_for @u2 }
it { edit_project_path(@project).should be_denied_for :user }
it { edit_project_path(@project).should be_denied_for :visitor }
subject { edit_project_path(@project) }
it { should be_allowed_for @u1 }
it { should be_denied_for @u3 }
it { should be_denied_for :admin }
it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end
describe "GET /project_code/deploy_keys" do
it { project_deploy_keys_path(@project).should be_allowed_for @u1 }
it { project_deploy_keys_path(@project).should be_denied_for @u3 }
it { project_deploy_keys_path(@project).should be_denied_for :admin }
it { project_deploy_keys_path(@project).should be_denied_for @u2 }
it { project_deploy_keys_path(@project).should be_denied_for :user }
it { project_deploy_keys_path(@project).should be_denied_for :visitor }
subject { project_deploy_keys_path(@project) }
it { should be_allowed_for @u1 }
it { should be_denied_for @u3 }
it { should be_denied_for :admin }
it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end
describe "GET /project_code/issues" do
it { project_issues_path(@project).should be_allowed_for @u1 }
it { project_issues_path(@project).should be_allowed_for @u3 }
it { project_issues_path(@project).should be_denied_for :admin }
it { project_issues_path(@project).should be_denied_for @u2 }
it { project_issues_path(@project).should be_denied_for :user }
it { project_issues_path(@project).should be_denied_for :visitor }
subject { project_issues_path(@project) }
it { should be_allowed_for @u1 }
it { should be_allowed_for @u3 }
it { should be_denied_for :admin }
it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end
describe "GET /project_code/snippets" do
it { project_snippets_path(@project).should be_allowed_for @u1 }
it { project_snippets_path(@project).should be_allowed_for @u3 }
it { project_snippets_path(@project).should be_denied_for :admin }
it { project_snippets_path(@project).should be_denied_for @u2 }
it { project_snippets_path(@project).should be_denied_for :user }
it { project_snippets_path(@project).should be_denied_for :visitor }
subject { project_snippets_path(@project) }
it { should be_allowed_for @u1 }
it { should be_allowed_for @u3 }
it { should be_denied_for :admin }
it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end
describe "GET /project_code/merge_requests" do
it { project_merge_requests_path(@project).should be_allowed_for @u1 }
it { project_merge_requests_path(@project).should be_allowed_for @u3 }
it { project_merge_requests_path(@project).should be_denied_for :admin }
it { project_merge_requests_path(@project).should be_denied_for @u2 }
it { project_merge_requests_path(@project).should be_denied_for :user }
it { project_merge_requests_path(@project).should be_denied_for :visitor }
subject { project_merge_requests_path(@project) }
it { should be_allowed_for @u1 }
it { should be_allowed_for @u3 }
it { should be_denied_for :admin }
it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end
describe "GET /project_code/repository" do
it { project_repository_path(@project).should be_allowed_for @u1 }
it { project_repository_path(@project).should be_allowed_for @u3 }
it { project_repository_path(@project).should be_denied_for :admin }
it { project_repository_path(@project).should be_denied_for @u2 }
it { project_repository_path(@project).should be_denied_for :user }
it { project_repository_path(@project).should be_denied_for :visitor }
subject { project_repository_path(@project) }
it { should be_allowed_for @u1 }
it { should be_allowed_for @u3 }
it { should be_denied_for :admin }
it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end
describe "GET /project_code/repository/branches" do
it { branches_project_repository_path(@project).should be_allowed_for @u1 }
it { branches_project_repository_path(@project).should be_allowed_for @u3 }
it { branches_project_repository_path(@project).should be_denied_for :admin }
it { branches_project_repository_path(@project).should be_denied_for @u2 }
it { branches_project_repository_path(@project).should be_denied_for :user }
it { branches_project_repository_path(@project).should be_denied_for :visitor }
subject { branches_project_repository_path(@project) }
it { should be_allowed_for @u1 }
it { should be_allowed_for @u3 }
it { should be_denied_for :admin }
it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end
describe "GET /project_code/repository/tags" do
it { tags_project_repository_path(@project).should be_allowed_for @u1 }
it { tags_project_repository_path(@project).should be_allowed_for @u3 }
it { tags_project_repository_path(@project).should be_denied_for :admin }
it { tags_project_repository_path(@project).should be_denied_for @u2 }
it { tags_project_repository_path(@project).should be_denied_for :user }
it { tags_project_repository_path(@project).should be_denied_for :visitor }
subject { tags_project_repository_path(@project) }
it { should be_allowed_for @u1 }
it { should be_allowed_for @u3 }
it { should be_denied_for :admin }
it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end
describe "GET /project_code/hooks" do
it { project_hooks_path(@project).should be_allowed_for @u1 }
it { project_hooks_path(@project).should be_allowed_for @u3 }
it { project_hooks_path(@project).should be_denied_for :admin }
it { project_hooks_path(@project).should be_denied_for @u2 }
it { project_hooks_path(@project).should be_denied_for :user }
it { project_hooks_path(@project).should be_denied_for :visitor }
subject { project_hooks_path(@project) }
it { should be_allowed_for @u1 }
it { should be_allowed_for @u3 }
it { should be_denied_for :admin }
it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
end
describe "GET /project_code/files" do
it { files_project_path(@project).should be_allowed_for @u1 }
it { files_project_path(@project).should be_allowed_for @u3 }
it { files_project_path(@project).should be_denied_for :admin }
it { files_project_path(@project).should be_denied_for @u2 }
it { files_project_path(@project).should be_denied_for :user }
it { files_project_path(@project).should be_denied_for :visitor }
subject { files_project_path(@project) }
it { should be_allowed_for @u1 }
it { should be_allowed_for @u3 }
it { should be_denied_for :admin }
it { should be_denied_for @u2 }
it { should be_denied_for :user }
it { should be_denied_for :visitor }
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'
SimpleCov.start 'rails'
unless ENV['CI']
require 'simplecov'
SimpleCov.start 'rails'
end
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
......@@ -7,10 +9,7 @@ require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'capybara/rails'
require 'capybara/rspec'
require 'capybara/dsl'
require 'webmock/rspec'
require 'factories'
require 'monkeypatch'
require 'email_spec'
require 'headless'
......@@ -21,10 +20,14 @@ Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
# Use capybara-webkit
Capybara.javascript_driver = :webkit
WebMock.disable_net_connect!(allow_localhost: true)
RSpec.configure do |config|
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
# examples within a transaction, remove the following line or assign false
......@@ -36,35 +39,11 @@ RSpec.configure do |config|
headless.start
end
config.before :each, type: :integration do
DeviseSessionMock.disable
end
config.before do
if example.metadata[:js]
DatabaseCleaner.strategy = :truncation
Capybara::Selenium::Driver::DEFAULT_OPTIONS[:resynchronize] = true
else
DatabaseCleaner.strategy = :transaction
end
DatabaseCleaner.start
WebMock.disable_net_connect!(allow_localhost: true)
stub_gitolite!
# !!! Observers disabled by default in tests
#
# Use next code to enable observers
# before(:each) { ActiveRecord::Base.observers.enable(:all) }
#
ActiveRecord::Base.observers.disable :all
ActiveRecord::Base.observers.disable(:all)
# ActiveRecord::Base.observers.enable(:all)
end
config.after do
DatabaseCleaner.clean
end
config.include RSpec::Rails::RequestExampleGroup, type: :request, example_group: {
file_path: /spec\/api/
}
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
def login_as role
@user = User.create(email: "user#{User.count}@mail.com",
name: "John Smith",
password: "123456",
password_confirmation: "123456",
skype: 'user_skype')
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"
module LoginHelpers
# Internal: Create and log in as a user of the specified role
#
# role - User role (e.g., :admin, :user)
def login_as(role)
@user = Factory(role)
login_with(@user)
end
# Internal: Login as the specified user
#
# user - User instance to login with
def login_with(user)
visit new_user_session_path
fill_in "user_email", with: user.email
......
......@@ -28,6 +28,16 @@ RSpec::Matchers.define :be_404_for do |user|
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
def url_allowed?(user, url)
emulate_user(user)
......@@ -57,3 +67,17 @@ module UrlAccess
login_with(user) if user
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