summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG14
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--README.md1
-rw-r--r--VERSION2
-rw-r--r--app/assets/images/merge.pngbin0 -> 593 bytes
-rw-r--r--app/assets/javascripts/application.js20
-rw-r--r--app/assets/javascripts/issues.js2
-rw-r--r--app/assets/javascripts/note.js261
-rw-r--r--app/assets/javascripts/projects.js4
-rw-r--r--app/assets/stylesheets/common.scss320
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap.scss817
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/blocks.scss145
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/buttons.scss105
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/common.scss52
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/files.scss156
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/lists.scss30
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/tables.scss41
-rw-r--r--app/assets/stylesheets/gitlab_bootstrap/typography.scss71
-rw-r--r--app/assets/stylesheets/main.scss41
-rw-r--r--app/assets/stylesheets/projects.css.scss385
-rw-r--r--app/assets/stylesheets/sections/commits.scss20
-rw-r--r--app/assets/stylesheets/sections/issues.scss13
-rw-r--r--app/assets/stylesheets/sections/merge_requests.scss57
-rw-r--r--app/assets/stylesheets/sections/nav.scss4
-rw-r--r--app/assets/stylesheets/sections/notes.scss73
-rw-r--r--app/assets/stylesheets/sections/projects.scss42
-rw-r--r--app/contexts/merge_requests_load.rb8
-rw-r--r--app/controllers/issues_controller.rb16
-rw-r--r--app/controllers/milestones_controller.rb4
-rw-r--r--app/decorators/commit_decorator.rb9
-rw-r--r--app/helpers/application_helper.rb15
-rw-r--r--app/helpers/gitlab_markdown_helper.rb2
-rw-r--r--app/mailers/notify.rb8
-rw-r--r--app/models/commit.rb12
-rw-r--r--app/models/project.rb8
-rw-r--r--app/observers/issue_observer.rb12
-rw-r--r--app/roles/static_model.rb35
-rw-r--r--app/views/admin/projects/_form.html.haml20
-rw-r--r--app/views/admin/projects/_new_form.html.haml29
-rw-r--r--app/views/admin/projects/new.html.haml15
-rw-r--r--app/views/admin/resque/show.html.haml2
-rw-r--r--app/views/admin/users/_form.html.haml6
-rw-r--r--app/views/commits/_commit.html.haml4
-rw-r--r--app/views/commits/compare.html.haml2
-rw-r--r--app/views/commits/show.html.haml10
-rw-r--r--app/views/dashboard/index.html.haml17
-rw-r--r--app/views/events/_event_last_push.html.haml2
-rw-r--r--app/views/help/api.html.haml10
-rw-r--r--app/views/issues/_form.html.haml11
-rw-r--r--app/views/issues/index.html.haml2
-rw-r--r--app/views/keys/_form.html.haml4
-rw-r--r--app/views/keys/index.html.haml2
-rw-r--r--app/views/merge_requests/_form.html.haml19
-rw-r--r--app/views/merge_requests/index.html.haml18
-rw-r--r--app/views/merge_requests/show/_mr_title.html.haml4
-rw-r--r--app/views/milestones/_form.html.haml14
-rw-r--r--app/views/milestones/index.html.haml8
-rw-r--r--app/views/notes/_create_common.js.haml11
-rw-r--r--app/views/notes/_create_line.js.haml2
-rw-r--r--app/views/notes/_form.html.haml68
-rw-r--r--app/views/notes/_per_line_form.html.haml51
-rw-r--r--app/views/notify/issue_status_changed_email.html.haml16
-rw-r--r--app/views/profile/password.html.haml2
-rw-r--r--app/views/profile/show.html.haml2
-rw-r--r--app/views/projects/_clone_panel.html.haml21
-rw-r--r--app/views/projects/_form.html.haml13
-rw-r--r--app/views/projects/_new_form.html.haml6
-rw-r--r--app/views/projects/empty.html.haml21
-rw-r--r--app/views/projects/new.html.haml6
-rw-r--r--app/views/projects/show.html.haml27
-rw-r--r--app/views/refs/_tree_item.html.haml2
-rw-r--r--app/views/search/show.html.haml2
-rw-r--r--app/views/wikis/_form.html.haml4
-rw-r--r--config/routes.rb2
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/milestones.md57
-rw-r--r--doc/debian_ubuntu.sh3
-rw-r--r--doc/development.md45
-rw-r--r--doc/installation.md57
-rwxr-xr-xgitlab75
-rw-r--r--lib/api.rb1
-rw-r--r--lib/api/issues.rb5
-rw-r--r--lib/api/milestones.rb80
-rw-r--r--lib/gitlab/markdown.rb2
-rw-r--r--lib/support/aws/debian_ubuntu_aws.sh125
-rwxr-xr-xresque_dev.sh3
-rw-r--r--spec/factories.rb13
-rw-r--r--spec/helpers/gitlab_flavored_markdown_spec.rb232
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb288
-rw-r--r--spec/mailers/notify_spec.rb23
-rw-r--r--spec/models/event_spec.rb27
-rw-r--r--spec/models/issue_spec.rb66
-rw-r--r--spec/models/key_spec.rb19
-rw-r--r--spec/models/merge_request_spec.rb69
-rw-r--r--spec/models/milestone_spec.rb14
-rw-r--r--spec/models/note_spec.rb18
-rw-r--r--spec/models/project_spec.rb67
-rw-r--r--spec/models/protected_branch_spec.rb31
-rw-r--r--spec/models/snippet_spec.rb26
-rw-r--r--spec/models/user_spec.rb47
-rw-r--r--spec/models/users_project_spec.rb16
-rw-r--r--spec/models/web_hook_spec.rb11
-rw-r--r--spec/models/wiki_spec.rb18
-rw-r--r--spec/observers/issue_observer_spec.rb69
-rw-r--r--spec/requests/api/issues_spec.rb5
-rw-r--r--spec/requests/api/milestones_spec.rb47
-rw-r--r--spec/roles/issue_commonality_spec.rb69
-rw-r--r--spec/roles/upvote_spec.rb27
-rw-r--r--spec/support/matchers.rb24
110 files changed, 2502 insertions, 2444 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 6868c07ba52..7322efbf228 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,17 @@
+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
diff --git a/Gemfile b/Gemfile
index 4905169f65e..b0724fadf5b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -120,5 +120,5 @@ group :test do
end
group :production do
- gem "gitlab_meta", '2.8'
+ gem "gitlab_meta", '2.9'
end
diff --git a/Gemfile.lock b/Gemfile.lock
index b8e97aa0204..f226c931bff 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -178,7 +178,7 @@ GEM
gherkin (2.11.0)
json (>= 1.4.6)
git (1.2.5)
- gitlab_meta (2.8)
+ gitlab_meta (2.9)
grape (0.2.1)
hashie (~> 1.2)
multi_json
@@ -397,7 +397,7 @@ DEPENDENCIES
ffaker
foreman
git
- gitlab_meta (= 2.8)
+ gitlab_meta (= 2.9)
gitolite!
grack!
grape (~> 0.2.1)
diff --git a/README.md b/README.md
index 26ed209ef87..122cd984b4a 100644
--- a/README.md
+++ b/README.md
@@ -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.
diff --git a/VERSION b/VERSION
index 1817afea416..a564e65383b 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.8.2
+2.9.0pre
diff --git a/app/assets/images/merge.png b/app/assets/images/merge.png
new file mode 100644
index 00000000000..4a6bb2e154d
--- /dev/null
+++ b/app/assets/images/merge.png
Binary files differ
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 24d99a62ca5..f69fd6f9a44 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -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");
+ }
+ })
+}
diff --git a/app/assets/javascripts/issues.js b/app/assets/javascripts/issues.js
index bc0569654e1..aae818deefc 100644
--- a/app/assets/javascripts/issues.js
+++ b/app/assets/javascripts/issues.js
@@ -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");
});
}
diff --git a/app/assets/javascripts/note.js b/app/assets/javascripts/note.js
index d9ae45d93c7..9cd3e36e87b 100644
--- a/app/assets/javascripts/note.js
+++ b/app/assets/javascripts/note.js
@@ -1,161 +1,160 @@
var NoteList = {
-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;
- this.target_type = tt;
- this.target_params = "&target_type=" + this.target_type + "&target_id=" + this.target_id;
-
- // get notes
- this.getContent();
-
- // get new notes every n seconds
- this.initRefresh();
-
- $('.delete-note').live('ajax:success', function() {
- $(this).closest('li').fadeOut(); });
-
- $("#new_note").live("ajax:before", function(){
- $(".submit_note").attr("disabled", "disabled");
- })
-
- $("#new_note").live("ajax:complete", function(){
- $(".submit_note").removeAttr("disabled");
- })
-
- $("#note_note").live("focus", function(){
- $(this).css("height", "80px");
- $('.note_advanced_opts').show();
- });
-
- $("#note_attachment").change(function(e){
+ 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;
+ this.target_type = tt;
+ this.target_params = "&target_type=" + this.target_type + "&target_id=" + this.target_id;
+
+ // get notes
+ this.getContent();
+
+ // get new notes every n seconds
+ this.initRefresh();
+
+ $('.delete-note').live('ajax:success', function() {
+ $(this).closest('li').fadeOut(); });
+
+ $(".note-form-holder").live("ajax:before", function(){
+ $(".submit_note").attr("disabled", "disabled");
+ })
+
+ $(".note-form-holder").live("ajax:complete", function(){
+ $(".submit_note").removeAttr("disabled");
+ })
+
+ disableButtonIfEmtpyField(".note-text", ".submit_note");
+
+ $(".note-text").live("focus", function(){
+ $(this).css("height", "80px");
+ $('.note_advanced_opts').show();
+ });
+
+ $("#note_attachment").change(function(e){
var val = $('.input-file').val();
var filename = val.replace(/^.*[\\\/]/, '');
$(".file_name").text(filename);
- });
+ });
- },
+ },
-/**
- * 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
- */
+ /**
+ * 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:
- function() {
- // init timer
- var intNew = setInterval("NoteList.getNew()", 10000);
- },
+ initRefresh:
+ function() {
+ // init timer
+ var intNew = setInterval("NoteList.getNew()", 10000);
+ },
-replace:
- function(html) {
- $("#new_notes_list").html(html);
- },
+ replace:
+ function(html) {
+ $("#new_notes_list").html(html);
+ },
-prepend:
- function(id, html) {
- if(id != this.last_id) {
- $("#new_notes_list").prepend(html);
- }
- },
+ prepend:
+ function(id, html) {
+ if(id != this.last_id) {
+ $("#new_notes_list").prepend(html);
+ }
+ },
-getNew:
- function() {
- // refersh notes list
- $.ajax({
- type: "GET",
+ getNew:
+ function() {
+ // refersh notes list
+ $.ajax({
+ type: "GET",
url: this.notes_path,
data: "last_id=" + this.last_id + this.target_params,
dataType: "script"});
- },
+ },
-refresh:
- function() {
- // refersh notes list
- $.ajax({
- type: "GET",
+ refresh:
+ function() {
+ // refersh notes list
+ $.ajax({
+ type: "GET",
url: this.notes_path,
data: "first_id=" + this.first_id + "&last_id=" + this.last_id + this.target_params,
dataType: "script"});
- },
+ },
-/**
- * Init load of notes:
- * 1. Get content with ajax call
- * 2. Set content of notes list with loaded one
- */
+ /**
+ * Init load of notes:
+ * 1. Get content with ajax call
+ * 2. Set content of notes list with loaded one
+ */
-getContent:
- function() {
- $.ajax({
- type: "GET",
+ getContent:
+ function() {
+ $.ajax({
+ type: "GET",
url: this.notes_path,
data: "?" + this.target_params,
complete: function(){ $('.status').removeClass("loading")},
beforeSend: function() { $('.status').addClass("loading") },
dataType: "script"});
- },
+ },
-setContent:
- function(fid, lid, html) {
+ setContent:
+ function(fid, lid, html) {
this.last_id = lid;
this.first_id = fid;
$("#notes-list").html(html);
// Init infinite scrolling
this.initLoadMore();
- },
-
-
-/**
- * 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:
- function() {
- $('.loading').show();
- $.ajax({
- type: "GET",
- url: this.notes_path,
- data: "first_id=" + this.first_id + this.target_params,
- complete: function(){ $('.status').removeClass("loading")},
- beforeSend: function() { $('.status').addClass("loading") },
- dataType: "script"});
- },
-
-append:
- function(id, html) {
- if(this.first_id == id) {
- this.disable = true;
- } else {
- this.first_id = id;
- $("#notes-list").append(html);
- }
- },
-
+ },
+
+
+ /**
+ * 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:
+ function() {
+ $('.loading').show();
+ $.ajax({
+ type: "GET",
+ url: this.notes_path,
+ data: "first_id=" + this.first_id + this.target_params,
+ complete: function(){ $('.status').removeClass("loading")},
+ beforeSend: function() { $('.status').addClass("loading") },
+ dataType: "script"});
+ },
+
+ append:
+ function(id, html) {
+ if(this.first_id == id) {
+ this.disable = true;
+ } else {
+ this.first_id = id;
+ $("#notes-list").append(html);
+ }
+ },
-initLoadMore:
- function() {
- $(document).endlessScroll({
- bottomPixels: 400,
+ initLoadMore:
+ function() {
+ $(document).endlessScroll({
+ bottomPixels: 400,
fireDelay: 1000,
fireOnce:true,
ceaseFire: function() {
@@ -164,6 +163,20 @@ initLoadMore:
callback: function(i) {
NoteList.getOld();
}
- });
- }
+ });
+ }
+};
+
+var PerLineNotes = {
+ init:
+ function() {
+ $(".line_note_link, .line_note_reply_link").live("click", function(e) {
+ var form = $(".per_line_form");
+ $(this).closest("tr").after(form);
+ form.find("#note_line_code").val($(this).attr("line_code"));
+ form.show();
+ return false;
+ });
+ disableButtonIfEmtpyField(".line-note-text", ".submit_inline_note");
+ }
}
diff --git a/app/assets/javascripts/projects.js b/app/assets/javascripts/projects.js
index 842726981d3..be1b75b90a7 100644
--- a/app/assets/javascripts/projects.js
+++ b/app/assets/javascripts/projects.js
@@ -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 --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss
index 68f862b8d2a..aa27a280a18 100644
--- a/app/assets/stylesheets/common.scss
+++ b/app/assets/stylesheets/common.scss
@@ -1,11 +1,9 @@
-.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;
@@ -141,16 +105,6 @@ table a code {
.git_url_wrapper {
margin-right:50px
}
-.file_stats {
- span {
- img {
- width:14px;
- float:left;
- margin-right:6px;
- padding:2px 0;
- }
- }
-}
.handle:hover {
cursor:move;
@@ -174,10 +128,6 @@ span.update-author {
display:block;
}
/** END UPDATE ITEM **/
-.ajax-tab-loading {
- padding:40px;
- display:none;
-}
.dashboard-loader {
float:left;
margin:10px;
@@ -188,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;
}
}
@@ -272,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;
}
@@ -395,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;
@@ -551,14 +529,6 @@ li.note {
}
-/**
- * Milestones list
- *
- */
-
-.milestone {
- @extend .wll;
-}
/**
* Admin area
@@ -605,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;
@@ -623,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;
- }
}
}
diff --git a/app/assets/stylesheets/gitlab_bootstrap.scss b/app/assets/stylesheets/gitlab_bootstrap.scss
deleted file mode 100644
index a1faf0601cb..00000000000
--- a/app/assets/stylesheets/gitlab_bootstrap.scss
+++ /dev/null
@@ -1,817 +0,0 @@
-body {
- margin-bottom:20px;
-}
-
-pre {
- font-family:'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace;
-
- &.dark {
- background: #333;
- color:#f5f5f5;
- }
-}
-
-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:1px solid #ddd;
- padding:1px;
-
- &.s16 {
- width:16px;
- height:16px;
- }
- &.s24 {
- width:24px;
- height:24px;
- }
- &.s32 {
- width:32px;
- height: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);
- @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;
- }
- }
-
-}
-
-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;
- }
-}
-
-.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;
- @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;
- }
- }
- }
-}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss
new file mode 100644
index 00000000000..894cb3044fa
--- /dev/null
+++ b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss
@@ -0,0 +1,145 @@
+/**
+ * ===================================
+ * 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;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/buttons.scss b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss
new file mode 100644
index 00000000000..c838f3b2368
--- /dev/null
+++ b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss
@@ -0,0 +1,105 @@
+.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;
+ }
+}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/common.scss b/app/assets/stylesheets/gitlab_bootstrap/common.scss
new file mode 100644
index 00000000000..cd7145c99c3
--- /dev/null
+++ b/app/assets/stylesheets/gitlab_bootstrap/common.scss
@@ -0,0 +1,52 @@
+/** 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; }
diff --git a/app/assets/stylesheets/gitlab_bootstrap/files.scss b/app/assets/stylesheets/gitlab_bootstrap/files.scss
new file mode 100644
index 00000000000..4ea5ed02ada
--- /dev/null
+++ b/app/assets/stylesheets/gitlab_bootstrap/files.scss
@@ -0,0 +1,156 @@
+/**
+ * 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;
+ }
+ }
+ }
+}
+
diff --git a/app/assets/stylesheets/gitlab_bootstrap/lists.scss b/app/assets/stylesheets/gitlab_bootstrap/lists.scss
new file mode 100644
index 00000000000..402ba04bd00
--- /dev/null
+++ b/app/assets/stylesheets/gitlab_bootstrap/lists.scss
@@ -0,0 +1,30 @@
+/** 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;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/tables.scss b/app/assets/stylesheets/gitlab_bootstrap/tables.scss
new file mode 100644
index 00000000000..f78b1dee4fa
--- /dev/null
+++ b/app/assets/stylesheets/gitlab_bootstrap/tables.scss
@@ -0,0 +1,41 @@
+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 }
+}
diff --git a/app/assets/stylesheets/gitlab_bootstrap/typography.scss b/app/assets/stylesheets/gitlab_bootstrap/typography.scss
new file mode 100644
index 00000000000..97e85492875
--- /dev/null
+++ b/app/assets/stylesheets/gitlab_bootstrap/typography.scss
@@ -0,0 +1,71 @@
+/**
+ * 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;
+}
diff --git a/app/assets/stylesheets/main.scss b/app/assets/stylesheets/main.scss
index b4f0ebf8ba0..be27d754dfa 100644
--- a/app/assets/stylesheets/main.scss
+++ b/app/assets/stylesheets/main.scss
@@ -2,29 +2,13 @@
@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'); }
+@font-face { font-family: Korolev; src: url('korolev-medium-compressed.otf'); }
/** MIXINS **/
@mixin shade {
@@ -72,7 +56,7 @@ $hover: #FDF5D9;
border-radius: $radius;
}
-@mixin bg-gray-gradient {
+@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);
@@ -80,6 +64,13 @@ $hover: #FDF5D9;
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.
* Contain application logo, search panel, profile icon
@@ -119,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";
/**
diff --git a/app/assets/stylesheets/projects.css.scss b/app/assets/stylesheets/projects.css.scss
deleted file mode 100644
index 4bdf5dee2f5..00000000000
--- a/app/assets/stylesheets/projects.css.scss
+++ /dev/null
@@ -1,385 +0,0 @@
-.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;
-}
diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss
index e2db701db71..75e38aeea93 100644
--- a/app/assets/stylesheets/sections/commits.scss
+++ b/app/assets/stylesheets/sections/commits.scss
@@ -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;
+ }
+ }
}
diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss
index 1b61ec3fe85..230a7aea1da 100644
--- a/app/assets/stylesheets/sections/issues.scss
+++ b/app/assets/stylesheets/sections/issues.scss
@@ -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;
+}
diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss
index ec84a64e23a..73171915fcc 100644
--- a/app/assets/stylesheets/sections/merge_requests.scss
+++ b/app/assets/stylesheets/sections/merge_requests.scss
@@ -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
+}
diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss
index fc7293b2864..097e819732d 100644
--- a/app/assets/stylesheets/sections/nav.scss
+++ b/app/assets/stylesheets/sections/nav.scss
@@ -6,7 +6,7 @@ ul.main_menu {
border-radius: 4px;
margin: auto;
margin:30px 0;
- border:1px solid #bbb;
+ border:1px solid #AAA;
height:37px;
@include bg-gray-gradient;
position:relative;
@@ -85,7 +85,7 @@ ul.main_menu {
line-height:36px;
color: $style_color;
text-shadow:0 1px 1px white;
-
+ padding:0 10px;
}
}
/*
diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss
index afa5095267d..6a965fa47b9 100644
--- a/app/assets/stylesheets/sections/notes.scss
+++ b/app/assets/stylesheets/sections/notes.scss
@@ -2,7 +2,7 @@
* Notes
*
*/
-#notes-list,
+#notes-list,
#new_notes_list {
display:block;
list-style:none;
@@ -10,7 +10,7 @@
padding:0px;
}
-#new_notes_list li:last-child{
+#new_notes_list li:last-child{
border-bottom:1px solid #aaa;
}
@@ -30,20 +30,24 @@
}
#new_note {
- #note_note {
- height:25px;
+ .note-text {
+ height:40px;
}
.attach_holder {
display:none;
}
}
-#preview-note {
- margin-bottom: 0;
+.preview_note {
+ margin: 2px;
+ border: 1px solid #ddd;
+ padding: 10px;
+ min-height: 60px;
+ background:#f5f5f5;
}
-.note {
- padding: 8px 0;
+.note {
+ padding: 8px 0;
border-bottom: 1px solid #eee;
overflow: hidden;
display: block;
@@ -53,16 +57,16 @@
.note-author { color: $style_color;}
.note-title { margin-left:45px; padding-top: 5px;}
- .avatar {
+ .avatar {
margin-top:3px;
}
- .delete-note {
- display:none;
+ .delete-note {
+ display:none;
float:right;
}
- &:hover {
+ &:hover {
.delete-note { display:block; }
}
}
@@ -76,18 +80,18 @@ p.notify_controls span{
font-weight: 700;
}
-tr.line_notes_row {
+tr.line_notes_row {
border-bottom:1px solid #DDD;
border-left: 7px solid #2A79A3;
- &.reply {
+ &.reply {
background:#eee;
border-left: 7px solid #2A79A3;
border-top:1px solid #ddd;
- td {
+ td {
padding:7px 10px;
}
- a.line_note_reply_link {
+ a.line_note_reply_link {
@include round-borders-all(4px);
padding: 3px 10px;
margin-left:5px;
@@ -96,9 +100,9 @@ tr.line_notes_row {
border-color: #2A79A3;
}
}
- ul {
+ ul {
margin:0;
- li {
+ li {
padding:0;
border:none;
}
@@ -107,26 +111,26 @@ tr.line_notes_row {
.line_notes_row, .per_line_form { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; }
-.per_line_form {
+.per_line_form {
background:#f5f5f5;
border-top:1px solid #eee;
form { margin: 0; }
- td {
+ td {
border-bottom:1px solid #ddd;
}
- .note_actions {
+ .note_actions {
margin:0;
padding-top: 10px;
- .buttons {
+ .buttons {
float:left;
width:300px;
}
- .options {
- .labels {
+ .options {
+ .labels {
float:left;
padding-left:10px;
- label {
+ label {
padding: 6px 0;
margin: 0;
width:120px;
@@ -136,7 +140,7 @@ tr.line_notes_row {
}
}
-td .line_note_link {
+td .line_note_link {
position:absolute;
margin-left:-70px;
margin-top:-10px;
@@ -148,14 +152,14 @@ td .line_note_link {
opacity: 0.0;
filter: alpha(opacity=0);
- &:hover {
+ &:hover {
opacity: 1.0;
filter: alpha(opacity=100);
}
}
.diff_file_content tr.line_holder:hover > td { background: $hover !important; }
-.diff_file_content tr.line_holder:hover > td .line_note_link {
+.diff_file_content tr.line_holder:hover > td .line_note_link {
opacity: 1.0;
filter: alpha(opacity=100);
}
@@ -173,8 +177,8 @@ td .line_note_link {
margin: 0;
}
- .note_advanced_opts {
- h6 {
+ .note_advanced_opts {
+ h6 {
line-height: 32px;
padding-right: 15px;
}
@@ -187,7 +191,7 @@ td .line_note_link {
overflow:hidden;
margin:0 0 5px !important;
- .input_file {
+ .input_file {
.file_upload {
position: absolute;
right:14px;
@@ -200,7 +204,7 @@ td .line_note_link {
height:28px;
overflow:hidden;
}
- .input-file {
+ .input-file {
width: 260px;
height: 41px;
float: right;
@@ -208,3 +212,8 @@ td .line_note_link {
}
}
}
+
+.note-text {
+ border: 1px solid #aaa;
+ box-shadow:none;
+}
diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss
index 20fd2cbfe42..721b569d446 100644
--- a/app/assets/stylesheets/sections/projects.scss
+++ b/app/assets/stylesheets/sections/projects.scss
@@ -14,6 +14,32 @@
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;
}
@@ -33,11 +59,14 @@
color:#888;
}
.btn {
- padding:6px;
+ padding:6px 10px;
margin-left:10px;
margin-bottom:8px;
}
}
+ .adv_settings {
+ h6 { margin-left:40px; }
+ }
}
.project_clone_panel {
@@ -50,3 +79,14 @@
border: 1px solid #BBB;
}
}
+
+.save-project-loader {
+ img {
+ margin-top:50px;
+ margin-bottom:50px;
+ }
+ h3 {
+ @extend .page_title;
+ }
+
+}
diff --git a/app/contexts/merge_requests_load.rb b/app/contexts/merge_requests_load.rb
index 6778db3bce5..e2f68e3805b 100644
--- a/app/contexts/merge_requests_load.rb
+++ b/app/contexts/merge_requests_load.rb
@@ -1,13 +1,13 @@
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)
diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb
index 889a7d98033..a47b38435f2 100644
--- a/app/controllers/issues_controller.rb
+++ b/app/controllers/issues_controller.rb
@@ -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
diff --git a/app/controllers/milestones_controller.rb b/app/controllers/milestones_controller.rb
index 7acb3781fbb..10f089f138b 100644
--- a/app/controllers/milestones_controller.rb
+++ b/app/controllers/milestones_controller.rb
@@ -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
diff --git a/app/decorators/commit_decorator.rb b/app/decorators/commit_decorator.rb
index cc8fa97587b..074176ae802 100644
--- a/app/decorators/commit_decorator.rb
+++ b/app/decorators/commit_decorator.rb
@@ -1,6 +1,15 @@
class CommitDecorator < ApplicationDecorator
decorates :commit
+ # Returns a string describing the commit for use in a link title
+ #
+ # Example
+ #
+ # "Commit: Alex Denisov - Project git clone panel"
+ def link_title
+ "Commit: #{author_name} - #{title}"
+ end
+
# Returns the commits title.
#
# Usually, the commit title is the first line of the commit message.
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 38191f55571..3dafb7534c3 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -78,16 +78,16 @@ module ApplicationHelper
end
def show_last_push_widget?(event)
- event &&
+ event &&
event.last_push_to_non_root? &&
!event.rm_ref? &&
- event.project &&
+ event.project &&
event.project.merge_requests_enabled
end
def tab_class(tab_key)
active = case tab_key
-
+
# Project Area
when :wall; wall_tab?
when :wiki; controller.controller_name == "wikis"
@@ -126,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
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index b4e3b9626e0..9da695b5d23 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -31,7 +31,7 @@ module GitlabMarkdownHelper
extractions[$1]
end
- text.html_safe
+ sanitize text.html_safe, attributes: ActionView::Base.sanitized_allowed_attributes + %w(id class )
end
# Use this in places where you would normally use link_to(gfm(...), ...).
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 1d222eb1a53..91136fee95e 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -83,6 +83,14 @@ class Notify < ActionMailer::Base
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
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 5c6b4d88d96..15afedcb101 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -1,6 +1,7 @@
class Commit
include ActiveModel::Conversion
include Gitlab::Encode
+ include StaticModel
extend ActiveModel::Naming
attr_accessor :commit
@@ -22,8 +23,7 @@ class Commit
:to_patch,
to: :commit
-
- class << self
+ class << self
def find_or_first(repo, commit_id = nil, root_ref)
commit = if commit_id
repo.commit(commit_id)
@@ -85,7 +85,7 @@ class Commit
first = project.commit(to.try(:strip))
last = project.commit(from.try(:strip))
- result = {
+ result = {
commits: [],
diffs: [],
commit: nil
@@ -105,10 +105,6 @@ class Commit
end
end
- def persisted?
- false
- end
-
def initialize(raw_commit, head = nil)
@commit = raw_commit
@head = head
@@ -155,7 +151,7 @@ class Commit
prev_commit.try :id
end
- def parents_count
+ def parents_count
parents && parents.count || 0
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 3fe11916504..a7735a42137 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -8,7 +8,7 @@ class Project < ActiveRecord::Base
#
# Relations
- #
+ #
belongs_to :owner, class_name: "User"
has_many :users, through: :users_projects
has_many :events, dependent: :destroy
@@ -25,12 +25,12 @@ class Project < ActiveRecord::Base
attr_accessor :error_code
- #
+ #
# Protected attributes
#
attr_protected :private_flag, :owner_id
- #
+ #
# Scopes
#
scope :public_only, where(private_flag: false)
@@ -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
diff --git a/app/observers/issue_observer.rb b/app/observers/issue_observer.rb
index 92b0f8368cb..62fd9bf8ac9 100644
--- a/app/observers/issue_observer.rb
+++ b/app/observers/issue_observer.rb
@@ -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
diff --git a/app/roles/static_model.rb b/app/roles/static_model.rb
new file mode 100644
index 00000000000..d26c8f47501
--- /dev/null
+++ b/app/roles/static_model.rb
@@ -0,0 +1,35 @@
+# Provides an ActiveRecord-like interface to a model whose data is not persisted to a database.
+module StaticModel
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ # Used by ActiveRecord's polymorphic association to set object_id
+ def primary_key
+ 'id'
+ end
+
+ # Used by ActiveRecord's polymorphic association to set object_type
+ def base_class
+ self
+ end
+ end
+
+ # Used by AR for fetching attributes
+ #
+ # Pass it along if we respond to it.
+ def [](key)
+ send(key) if respond_to?(key)
+ end
+
+ def to_param
+ id
+ end
+
+ def persisted?
+ false
+ end
+
+ def destroyed?
+ false
+ end
+end
diff --git a/app/views/admin/projects/_form.html.haml b/app/views/admin/projects/_form.html.haml
index 7cebddf2890..87d212e5710 100644
--- a/app/views/admin/projects/_form.html.haml
+++ b/app/views/admin/projects/_form.html.haml
@@ -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"
diff --git a/app/views/admin/projects/_new_form.html.haml b/app/views/admin/projects/_new_form.html.haml
new file mode 100644
index 00000000000..d793b6f3aff
--- /dev/null
+++ b/app/views/admin/projects/_new_form.html.haml
@@ -0,0 +1,29 @@
+= 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"
diff --git a/app/views/admin/projects/new.html.haml b/app/views/admin/projects/new.html.haml
index ac6526bfa4b..933cb671142 100644
--- a/app/views/admin/projects/new.html.haml
+++ b/app/views/admin/projects/new.html.haml
@@ -1,3 +1,12 @@
-%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(); });
diff --git a/app/views/admin/resque/show.html.haml b/app/views/admin/resque/show.html.haml
index 0375d94bc9d..d889a5d0817 100644
--- a/app/views/admin/resque/show.html.haml
+++ b/app/views/admin/resque/show.html.haml
@@ -1,2 +1,2 @@
%h3 Resque
-%iframe{src: "/info/resque", width: 1168, height: 600, style: "border: none"} \ No newline at end of file
+%iframe{src: resque_url, width: 1168, height: 600, style: "border: none"}
diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml
index 2014472375b..7010c2727c6 100644
--- a/app/views/admin/users/_form.html.haml
+++ b/app/views/admin/users/_form.html.haml
@@ -73,8 +73,8 @@
.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"
diff --git a/app/views/commits/_commit.html.haml b/app/views/commits/_commit.html.haml
index 686a4337b3a..61371d14c5f 100644
--- a/app/views/commits/_commit.html.haml
+++ b/app/views/commits/_commit.html.haml
@@ -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"
diff --git a/app/views/commits/compare.html.haml b/app/views/commits/compare.html.haml
index be915cd1038..7dab1f5c0fa 100644
--- a/app/views/commits/compare.html.haml
+++ b/app/views/commits/compare.html.haml
@@ -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?
diff --git a/app/views/commits/show.html.haml b/app/views/commits/show.html.haml
index 9a483aa2a9a..e01f8ea5878 100644
--- a/app/views/commits/show.html.haml
+++ b/app/views/commits/show.html.haml
@@ -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();
});
diff --git a/app/views/dashboard/index.html.haml b/app/views/dashboard/index.html.haml
index ba7d019cb63..e13640fb835 100644
--- a/app/views/dashboard/index.html.haml
+++ b/app/views/dashboard/index.html.haml
@@ -19,13 +19,16 @@
= link_to new_project_path, class: "btn very_small info" do
%i.icon-plus
New Project
- - @projects.each do |project|
- = link_to project_path(project), class: dom_class(project) do
- %h4
- %span.ico.project
- = truncate(project.name, length: 25)
- %span.right
- &rarr;
+ %ul.unstyled
+ - @projects.each do |project|
+ %li.wll
+ = link_to project_path(project), class: dom_class(project) do
+ %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
diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml
index 4ef927495ae..66e149365f7 100644
--- a/app/views/events/_event_last_push.html.haml
+++ b/app/views/events/_event_last_push.html.haml
@@ -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
diff --git a/app/views/help/api.html.haml b/app/views/help/api.html.haml
index 4f7af193741..d7d7aed4232 100644
--- a/app/views/help/api.html.haml
+++ b/app/views/help/api.html.haml
@@ -63,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"))
diff --git a/app/views/issues/_form.html.haml b/app/views/issues/_form.html.haml
index 65bf605a038..db7920b993d 100644
--- a/app/views/issues/_form.html.haml
+++ b/app/views/issues/_form.html.haml
@@ -43,14 +43,15 @@
.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
diff --git a/app/views/issues/index.html.haml b/app/views/issues/index.html.haml
index a6836fd4fd1..010b8856d65 100644
--- a/app/views/issues/index.html.haml
+++ b/app/views/issues/index.html.haml
@@ -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
diff --git a/app/views/keys/_form.html.haml b/app/views/keys/_form.html.haml
index 9c6e229bf49..26700803e61 100644
--- a/app/views/keys/_form.html.haml
+++ b/app/views/keys/_form.html.haml
@@ -19,6 +19,6 @@
.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"
diff --git a/app/views/keys/index.html.haml b/app/views/keys/index.html.haml
index 04e9e4cbc33..9b5663ed93e 100644
--- a/app/views/keys/index.html.haml
+++ b/app/views/keys/index.html.haml
@@ -1,6 +1,6 @@
%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
diff --git a/app/views/merge_requests/_form.html.haml b/app/views/merge_requests/_form.html.haml
index b6c12397d6c..b554c051964 100644
--- a/app/views/merge_requests/_form.html.haml
+++ b/app/views/merge_requests/_form.html.haml
@@ -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
+ .mr_source_commit
- .span6
+ .span2
+ %center= image_tag "merge.png", class: 'mr_direction_tip'
+ .span5
.mr_branch_box
%h5 To (Base Branch)
.body
@@ -28,8 +29,7 @@
= 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
+ .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();
diff --git a/app/views/merge_requests/index.html.haml b/app/views/merge_requests/index.html.haml
index 4ad6e5c18ce..bbf35dc72d2 100644
--- a/app/views/merge_requests/index.html.haml
+++ b/app/views/merge_requests/index.html.haml
@@ -1,7 +1,7 @@
%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
diff --git a/app/views/merge_requests/show/_mr_title.html.haml b/app/views/merge_requests/show/_mr_title.html.haml
index 31fa0779fd2..3ae1050d169 100644
--- a/app/views/merge_requests/show/_mr_title.html.haml
+++ b/app/views/merge_requests/show/_mr_title.html.haml
@@ -1,9 +1,9 @@
%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?
diff --git a/app/views/milestones/_form.html.haml b/app/views/milestones/_form.html.haml
index 49200c6705f..41cbd6abcad 100644
--- a/app/views/milestones/_form.html.haml
+++ b/app/views/milestones/_form.html.haml
@@ -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) }
diff --git a/app/views/milestones/index.html.haml b/app/views/milestones/index.html.haml
index ecb008dc144..c5333b08fdc 100644
--- a/app/views/milestones/index.html.haml
+++ b/app/views/milestones/index.html.haml
@@ -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
diff --git a/app/views/notes/_create_common.js.haml b/app/views/notes/_create_common.js.haml
index e9538902754..e80eccb1b4c 100644
--- a/app/views/notes/_create_common.js.haml
+++ b/app/views/notes/_create_common.js.haml
@@ -1,11 +1,12 @@
- if note.valid?
:plain
- $("#new_note .error").remove();
- $('#new_note textarea').val("");
- $('#preview-link').text('Preview');
- $('#preview-note').hide(); $('#note_note').show();
+ $(".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'))}");
diff --git a/app/views/notes/_create_line.js.haml b/app/views/notes/_create_line.js.haml
index 13809bec1b9..662909f7967 100644
--- a/app/views/notes/_create_line.js.haml
+++ b/app/views/notes/_create_line.js.haml
@@ -1,7 +1,7 @@
- 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})}");
diff --git a/app/views/notes/_form.html.haml b/app/views/notes/_form.html.haml
index 326f1add485..7211a0ae471 100644
--- a/app/views/notes/_form.html.haml
+++ b/app/views/notes/_form.html.haml
@@ -1,37 +1,39 @@
-= 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
- - @note.errors.full_messages.each do |msg|
- %div= msg
+.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
+ - @note.errors.full_messages.each do |msg|
+ %div= msg
- = f.hidden_field :noteable_id
- = f.hidden_field :noteable_type
- = f.text_area :note, size: 255
- #preview-note.well.hide
- .hint
- = link_to 'Preview', preview_project_notes_path(@project), id: 'preview-link'
- .right Comments are parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}.
+ = f.hidden_field :noteable_id
+ = f.hidden_field :noteable_type
+ = 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"
- .span4.notify_opts
- %h6.left Notify via email:
- = label_tag :notify do
- = check_box_tag :notify, 1, @note.noteable_type != "Commit"
- %span Project team
+ .row.note_advanced_opts.hide
+ .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
+ = check_box_tag :notify, 1, @note.noteable_type != "Commit"
+ %span Project team
- - if @note.notify_only_author?(current_user)
- = label_tag :notify_author do
- = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
- %span Commit author
- .span6.attachments
- %h6.left Attachment:
- %span.file_name File name...
-
- .input.input_file
- %a.file_upload.btn.small Upload File
- = f.file_field :attachment, class: "input-file"
- %span.hint Any file less than 10 MB
+ - if @note.notify_only_author?(current_user)
+ = label_tag :notify_author do
+ = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
+ %span Commit author
+ .span5.attachments
+ %h6.left Attachment:
+ %span.file_name File name...
+
+ .input.input_file
+ %a.file_upload.btn.small Upload File
+ = f.file_field :attachment, class: "input-file"
+ %span.hint Any file less than 10 MB
diff --git a/app/views/notes/_per_line_form.html.haml b/app/views/notes/_per_line_form.html.haml
index afb0b30dca3..8e31b59e9f0 100644
--- a/app/views/notes/_per_line_form.html.haml
+++ b/app/views/notes/_per_line_form.html.haml
@@ -1,33 +1,34 @@
%table{style: "display:none;"}
%tr.per_line_form
%td{colspan: 3 }
- = form_for [@project, @note], remote: "true", multipart: true do |f|
- %h3.page_title Leave a note
- %div.span10
- -if @note.errors.any?
- .alert-message.block-message.error
- - @note.errors.full_messages.each do |msg|
- %div= msg
+ .line-note-form-holder
+ = form_for [@project, @note], remote: "true", multipart: true do |f|
+ %h3.page_title Leave a note
+ %div.span10
+ -if @note.errors.any?
+ .alert-message.block-message.error
+ - @note.errors.full_messages.each do |msg|
+ %div= msg
- = f.hidden_field :noteable_id
- = f.hidden_field :noteable_type
- = f.hidden_field :line_code
- = f.text_area :note, size: 255
- .note_actions
- .buttons
- = f.submit 'Add note', class: "btn primary submit_note", id: "submit_note"
- = link_to "Cancel", "#", class: "btn hide-button"
- .options
- %h6.left Notify via email:
- .labels
- = label_tag :notify do
- = check_box_tag :notify, 1, @note.noteable_type != "Commit"
- %span Project team
+ = f.hidden_field :noteable_id
+ = f.hidden_field :noteable_type
+ = f.hidden_field :line_code
+ = f.text_area :note, size: 255, class: 'line-note-text'
+ .note_actions
+ .buttons
+ = 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:
+ .labels
+ = label_tag :notify do
+ = check_box_tag :notify, 1, @note.noteable_type != "Commit"
+ %span Project team
- - if @note.notify_only_author?(current_user)
- = label_tag :notify_author do
- = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
- %span Commit author
+ - if @note.notify_only_author?(current_user)
+ = label_tag :notify_author do
+ = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
+ %span Commit author
:javascript
$(function(){
diff --git a/app/views/notify/issue_status_changed_email.html.haml b/app/views/notify/issue_status_changed_email.html.haml
new file mode 100644
index 00000000000..59130f79d6c
--- /dev/null
+++ b/app/views/notify/issue_status_changed_email.html.haml
@@ -0,0 +1,16 @@
+%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
+
diff --git a/app/views/profile/password.html.haml b/app/views/profile/password.html.haml
index 257dacb1ad3..d0aee7ac6b3 100644
--- a/app/views/profile/password.html.haml
+++ b/app/views/profile/password.html.haml
@@ -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"
diff --git a/app/views/profile/show.html.haml b/app/views/profile/show.html.haml
index a7b6a18ad2d..22e840a089f 100644
--- a/app/views/profile/show.html.haml
+++ b/app/views/profile/show.html.haml
@@ -67,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"
diff --git a/app/views/projects/_clone_panel.html.haml b/app/views/projects/_clone_panel.html.haml
new file mode 100644
index 00000000000..839a98a0d79
--- /dev/null
+++ b/app/views/projects/_clone_panel.html.haml
@@ -0,0 +1,21 @@
+.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
+
diff --git a/app/views/projects/_form.html.haml b/app/views/projects/_form.html.haml
index ce66b2cf930..8bdeda1cafe 100644
--- a/app/views/projects/_form.html.haml
+++ b/app/views/projects/_form.html.haml
@@ -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
diff --git a/app/views/projects/_new_form.html.haml b/app/views/projects/_new_form.html.haml
index 5104df83a2c..e6d5e93fca7 100644
--- a/app/views/projects/_new_form.html.haml
+++ b/app/views/projects/_new_form.html.haml
@@ -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
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 907d5ef4666..d408c0a64ae 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,4 +1,12 @@
= 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
%h4 Git global setup:
%pre.dark
@@ -28,3 +36,16 @@
- if can? current_user, :admin_project, @project
.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"));
+ })
+ })
+
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 703e558ae41..933cb671142 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -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(); });
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 628dc53d01b..77a0ef1ac4d 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,33 +1,12 @@
= render "project_head"
-
-.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
-
+= render 'clone_panel'
= render "events/event_last_push", event: @last_push
.content_list= render @events
-:javascript
+:javascript
$(function(){
var link_sel = ".project_clone_holder a";
- $(link_sel).bind("click", function() {
+ $(link_sel).bind("click", function() {
$(link_sel).removeClass("active");
$(this).addClass("active");
$("#project_clone").val($(this).attr("data-clone"));
diff --git a/app/views/refs/_tree_item.html.haml b/app/views/refs/_tree_item.html.haml
index 2e6bbf6221b..d4c4ee8de49 100644
--- a/app/views/refs/_tree_item.html.haml
+++ b/app/views/refs/_tree_item.html.haml
@@ -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
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index 9a0b4789588..d37ef670318 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -4,7 +4,7 @@
%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
diff --git a/app/views/wikis/_form.html.haml b/app/views/wikis/_form.html.haml
index 305607d4118..12b57e032a4 100644
--- a/app/views/wikis/_form.html.haml
+++ b/app/views/wikis/_form.html.haml
@@ -23,5 +23,5 @@
= 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"
diff --git a/config/routes.rb b/config/routes.rb
index f895478fb12..51e65721880 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -10,7 +10,7 @@ Gitlab::Application.routes.draw do
# Optionally, enable Resque here
require 'resque/server'
- mount Resque::Server.new, at: '/info/resque'
+ mount Resque::Server.new, at: '/info/resque', as: 'resque'
# Enable Grack support
mount Grack::Bundle.new({
diff --git a/doc/api/README.md b/doc/api/README.md
index d32573aaa6b..53b4983ef47 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -29,3 +29,4 @@ The API uses JSON to serialize data. You don't need to specify `.json` at the en
+ [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)
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
new file mode 100644
index 00000000000..f68d8eb7d58
--- /dev/null
+++ b/doc/api/milestones.md
@@ -0,0 +1,57 @@
+## 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
diff --git a/doc/debian_ubuntu.sh b/doc/debian_ubuntu.sh
index a0b4710b5eb..5ed1e2a2f1f 100644
--- a/doc/debian_ubuntu.sh
+++ b/doc/debian_ubuntu.sh
@@ -3,7 +3,8 @@
sudo apt-get update
sudo apt-get upgrade
-sudo apt-get install -y git git-core wget curl gcc checkinstall libxml2-dev libxslt-dev sqlite3 libsqlite3-dev libcurl4-openssl-dev libreadline-gplv2-dev libc6-dev libssl-dev libmysql++-dev make build-essential zlib1g-dev libicu-dev redis-server openssh-server python-dev python-pip libyaml-dev postfix
+sudo DEBIAN_FRONTEND='noninteractive' apt-get install -y postfix-policyd-spf-python # Install postfix without prompting.
+sudo apt-get install -y git git-core wget curl gcc checkinstall libxml2-dev libxslt-dev sqlite3 libsqlite3-dev libcurl4-openssl-dev libreadline-gplv2-dev libc6-dev libssl-dev libmysql++-dev make build-essential zlib1g-dev libicu-dev redis-server openssh-server python-dev python-pip libyaml-dev
wget http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.3-p194.tar.gz
tar xfvz ruby-1.9.3-p194.tar.gz
diff --git a/doc/development.md b/doc/development.md
new file mode 100644
index 00000000000..55be2bc32f2
--- /dev/null
+++ b/doc/development.md
@@ -0,0 +1,45 @@
+## 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
diff --git a/doc/installation.md b/doc/installation.md
index df106bc10a6..efed21f0b28 100644
--- a/doc/installation.md
+++ b/doc/installation.md
@@ -1,4 +1,4 @@
-## Platform requirements:
+## Platform requirements:
**The project is designed for the Linux operating system.**
@@ -22,7 +22,7 @@ You might have some luck using these, but no guarantees:
Gitlab does **not** run on Windows and we have no plans of making Gitlab compatible.
-## This installation guide created for Debian/Ubuntu and properly tested.
+## This installation guide created for Debian/Ubuntu and properly tested.
The installation consists of 6 steps:
@@ -43,13 +43,13 @@ Also read the [Read this before you submit an issue](https://github.com/gitlabhq
> - - -
> First 3 steps can be easily skipped with simply install script:
->
-> # Install curl and sudo
+>
+> # Install curl and sudo
> apt-get install curl sudo
->
+>
> # 3 steps in 1 command :)
> curl https://raw.github.com/gitlabhq/gitlabhq/master/doc/debian_ubuntu.sh | sh
->
+>
> Now you can go to step 4"
> - - -
@@ -61,7 +61,7 @@ Also read the [Read this before you submit an issue](https://github.com/gitlabhq
sudo apt-get upgrade
sudo apt-get install -y wget curl gcc checkinstall libxml2-dev libxslt-dev sqlite3 libsqlite3-dev libcurl4-openssl-dev libreadline6-dev libc6-dev libssl-dev libmysql++-dev make build-essential zlib1g-dev libicu-dev redis-server openssh-server git-core python-dev python-pip libyaml-dev postfix
-
+
# If you want to use MySQL:
sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
@@ -77,7 +77,7 @@ Also read the [Read this before you submit an issue](https://github.com/gitlabhq
# 3. Install gitolite
Create user for git:
-
+
sudo adduser \
--system \
--shell /bin/sh \
@@ -90,7 +90,7 @@ Create user for git:
Create user for gitlab:
# ubuntu/debian
- sudo adduser --disabled-login --gecos 'gitlab system' gitlab
+ sudo adduser --disabled-login --gecos 'gitlab system' gitlab
Add your user to git group:
@@ -103,7 +103,7 @@ Generate key:
Get gitolite source code:
cd /home/git
- sudo -H -u git git clone git://github.com/gitlabhq/gitolite /home/git/gitolite
+ sudo -H -u git git clone git://github.com/gitlabhq/gitolite /home/git/gitolite
Setup:
@@ -114,20 +114,20 @@ Setup:
sudo -u git -H sed -i 's/0077/0007/g' /home/git/share/gitolite/conf/example.gitolite.rc
sudo -u git -H sh -c "PATH=/home/git/bin:$PATH; gl-setup -q /home/git/gitlab.pub"
-
+
Permissions:
sudo chmod -R g+rwX /home/git/repositories/
sudo chown -R git:git /home/git/repositories/
#### CHECK: Logout & login again to apply git group to your user
-
+
# clone admin repo to add localhost to known_hosts
# & be sure your user has access to gitolite
- sudo -u gitlab -H git clone git@localhost:gitolite-admin.git /tmp/gitolite-admin
+ sudo -u gitlab -H git clone git@localhost:gitolite-admin.git /tmp/gitolite-admin
# if succeed you can remove it
- sudo rm -rf /tmp/gitolite-admin
+ sudo rm -rf /tmp/gitolite-admin
**IMPORTANT! If you cant clone `gitolite-admin` repository - DONT PROCEED INSTALLATION**
@@ -139,7 +139,7 @@ Permissions:
cd /home/gitlab
sudo -H -u gitlab git clone -b stable git://github.com/gitlabhq/gitlabhq.git gitlab
cd gitlab
-
+
sudo -u gitlab mkdir tmp
# Rename config files
@@ -150,22 +150,22 @@ Permissions:
# SQLite
sudo -u gitlab cp config/database.yml.sqlite config/database.yml
- # Or
+ # Or
# Mysql
# Install MySQL as directed in Step #1
-
+
# Login to MySQL
- $ mysql -u root -p
-
+ $ mysql -u root -p
+
# Create the gitlabhq production database
mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`;
-
+
# Create the MySQL User change $password to a real password
- mysql> CREATE USER 'gitlab'@'localhost' IDENTIFIED BY '$password';
-
+ mysql> CREATE USER 'gitlab'@'localhost' IDENTIFIED BY '$password';
+
# Grant proper permissions to the MySQL User
mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON `gitlabhq_production`.* TO 'gitlab'@'localhost';
-
+
# Exit MySQL Server and copy the example config, make sure to update username/password in config/database.yml
sudo -u gitlab cp config/database.yml.example config/database.yml
@@ -181,7 +181,7 @@ Permissions:
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
@@ -202,13 +202,13 @@ Checking status:
UMASK for .gitolite.rc is 0007? ............YES
/home/git/share/gitolite/hooks/common/post-receive exists? ............YES
-If you got all YES - congrats! You can go to next step.
+If you got all YES - congrats! You can go to next step.
# 5. Server up
Application can be started with next command:
- # For test purposes
+ # For test purposes
sudo -u gitlab bundle exec rails s -e production
# As daemon
@@ -246,12 +246,13 @@ You can login via web using admin generated with setup:
Add gitlab to nginx sites & change with your host specific settings
- cp /home/gitlab/gitlab/lib/support/nginx-gitlab /etc/nginx/sites-enabled/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.
- vim /etc/nginx/sites-enabled/nginx-gitlab
+ sudo vim /etc/nginx/sites-enabled/gitlab
Restart nginx:
diff --git a/gitlab b/gitlab
new file mode 100755
index 00000000000..acafb3f10c4
--- /dev/null
+++ b/gitlab
@@ -0,0 +1,75 @@
+#!/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
diff --git a/lib/api.rb b/lib/api.rb
index 3ff3b3836f4..be04701c25d 100644
--- a/lib/api.rb
+++ b/lib/api.rb
@@ -16,5 +16,6 @@ module Gitlab
mount Users
mount Projects
mount Issues
+ mount Milestones
end
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 2abc20ad34e..836c2818544 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -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
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
new file mode 100644
index 00000000000..f537b8e5bf2
--- /dev/null
+++ b/lib/api/milestones.rb
@@ -0,0 +1,80 @@
+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
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index 75fa835d502..9a07133d0b3 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -100,7 +100,7 @@ module Gitlab
def reference_commit(identifier)
if commit = @project.commit(identifier)
- link_to(identifier, project_commit_path(@project, id: commit.id), html_options.merge(title: "Commit: #{commit.author_name} - #{CommitDecorator.new(commit).title}", class: "gfm gfm-commit #{html_options[:class]}"))
+ link_to(identifier, project_commit_path(@project, id: commit.id), html_options.merge(title: CommitDecorator.new(commit).link_title, class: "gfm gfm-commit #{html_options[:class]}"))
end
end
end
diff --git a/lib/support/aws/debian_ubuntu_aws.sh b/lib/support/aws/debian_ubuntu_aws.sh
new file mode 100644
index 00000000000..962ee008082
--- /dev/null
+++ b/lib/support/aws/debian_ubuntu_aws.sh
@@ -0,0 +1,125 @@
+#!/bin/sh
+
+# ABOUT
+# This script performs a complete installation of Gitlab (master branch).
+# Is can be run with one command without needing _any_ user input after that.
+# This script only works on Amazon Web Services (AWS).
+# The operating system used is Ubuntu 12.04 64bit.
+
+# TODO
+# @dosire will send a pull request after this is merged in to change dosire/gitlabhq/non-interactive-aws-install links to gitlabhq/gitlabhq/master and reference this script from installation.md
+
+# HOWTO
+# Signup for AWS, free tier are available at http://aws.amazon.com/free/
+# Go to EC2 tab in the AWS console EC2 https://console.aws.amazon.com/ec2/home
+# Click the 'Launch Instance' button
+# Select: 'Quick launch wizard' and continue
+# Choose a key pair => Create New => Name it => Download it
+# Choose a Launch Configuration => Select 'More Amazon Marketplace Images'
+# Press 'Continue'
+# Enter 'ubuntu/images/ubuntu-precise-12.04-amd64-server-20120424' and press 'Search'
+# Select the only result (ami-3c994355) and press 'Continue'
+# Press 'Edit details' if you want to modify something, for example make the type 'c1.medium' to make the install faster.
+# Press the 'Launch' button
+# Press 'Close'
+# Click 'Security Groups' under the left hand menu 'NETWORK & SECURITY'
+# Select the newly create seciruty group, probably named 'quicklaunch-1'
+# Click on the Inbound tab
+# In the 'Create a new rule' dropdown select 'HTTP'
+# Press 'Add Rule'
+# In the 'Create a new rule' dropdown select 'HTTPS'
+# Press 'Add Rule'
+# Press 'Apply Rule Changes'
+# Give the following command in your local terminal while suptituting the UPPERCASE items
+# 'ssh -i LOCATION_OF_AWS_KEY_PAIR_PRIVATE_KEY PUBLIC_DNS_OF_THE_NEW_SERVER'
+# Execute the curl command below and when its ready follow the printed 'Log in instuctions'
+# curl https://raw.github.com/dosire/gitlabhq/non-interactive-aws-install/lib/support/aws/debian_ubuntu_aws.sh | sh
+
+# Prevent fingerprint prompt for localhost in step 1 to 3.
+echo "Host localhost
+ StrictHostKeyChecking no
+ UserKnownHostsFile=/dev/null" | sudo tee -a /etc/ssh/ssh_config
+
+# Existing script for Step 1 to 3
+curl https://raw.github.com/dosire/gitlabhq/non-interactive-aws-install/doc/debian_ubuntu.sh | sh
+
+# Install MySQL
+sudo apt-get install -y makepasswd # Needed to create a unique password non-interactively.
+userPassword=$(makepasswd --char=10) # Generate a random MySQL password
+# Note that the lines below creates a cleartext copy of the random password in /var/cache/debconf/passwords.dat
+# This file is normally only readable by root and the password will be deleted by the package management system after install.
+echo mysql-server mysql-server/root_password password $userPassword | sudo debconf-set-selections
+echo mysql-server mysql-server/root_password_again password $userPassword | sudo debconf-set-selections
+sudo apt-get install -y mysql-server
+
+# Gitlab install
+sudo gem install charlock_holmes --version '0.6.8'
+sudo pip install pygments
+sudo gem install bundler
+sudo su -l gitlab -c "git clone git://github.com/gitlabhq/gitlabhq.git gitlab" # Using master everywhere.
+sudo su -l gitlab -c "cd gitlab && mkdir tmp"
+sudo su -l gitlab -c "cd gitlab/config && cp gitlab.yml.example gitlab.yml"
+sudo su -l gitlab -c "cd gitlab/config && cp database.yml.example database.yml"
+sudo sed -i 's/"secure password"/"'$userPassword'"/' /home/gitlab/gitlab/config/database.yml # Insert the mysql root password.
+sudo su -l gitlab -c "cd gitlab && bundle install --without development test --deployment"
+sudo su -l gitlab -c "cd gitlab && bundle exec rake gitlab:app:setup RAILS_ENV=production"
+
+# Setup gitlab hooks
+sudo cp /home/gitlab/gitlab/lib/hooks/post-receive /home/git/share/gitolite/hooks/common/post-receive
+sudo chown git:git /home/git/share/gitolite/hooks/common/post-receive
+
+# Set the first occurrence of host in the Gitlab config to the publicly available domain name
+sudo sed -i '0,/host/s/localhost/'`wget -qO- http://instance-data/latest/meta-data/public-hostname`'/' /home/gitlab/gitlab/config/gitlab.yml
+
+# Gitlab installation test (optional)
+# sudo -u gitlab bundle exec rake gitlab:app:status RAILS_ENV=production
+# sudo -u gitlab bundle exec rails s -e production
+# sudo -u gitlab bundle exec rake environment resque:work QUEUE=* RAILS_ENV=production BACKGROUND=no
+
+# Install and configure Nginx
+sudo apt-get install -y nginx
+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
+sudo sed -i 's/YOUR_SERVER_IP/'`wget -qO- http://instance-data/latest/meta-data/local-ipv4`'/' /etc/nginx/sites-available/gitlab # Set private ip address (public won't work).
+sudo sed -i 's/YOUR_SERVER_FQDN/'`wget -qO- http://instance-data/latest/meta-data/public-hostname`'/' /etc/nginx/sites-available/gitlab # Set public dns domain name.
+
+# Configure Unicorn
+sudo -u gitlab cp /home/gitlab/gitlab/config/unicorn.rb.orig /home/gitlab/gitlab/config/unicorn.rb
+
+# Create a Gitlab service
+sudo cp /home/gitlab/gitlab/lib/support/init-gitlab /etc/init.d/gitlab
+sudo chmod +x /etc/init.d/gitlab && sudo update-rc.d gitlab defaults
+
+## Gitlab service commands (unicorn and resque)
+## restart doesn't restart resque, only start/stop effect it.
+sudo -u gitlab service gitlab start
+# sudo -u gitlab service gitlab restart
+# sudo -u gitlab service gitlab stop
+
+# nginx Service commands
+# sudo service nginx start
+sudo service nginx restart
+# sudo service nginx stop
+
+# Manual startup commands for troubleshooting when the service commands do not work
+# sudo -u gitlab bundle exec unicorn_rails -c config/unicorn.rb -E production -D
+# sudo su -l gitlab -c "cd gitlab && ./resque.sh"
+
+# Monitoring commands
+# sudo tail -f /var/log/nginx/access.log;
+# sudo tail -f /var/log/nginx/error.log;
+
+# Go to gitlab directory by default on next login.
+echo 'cd /home/gitlab/gitlab' >> /home/ubuntu/.bashrc
+
+echo ''
+echo '###########################################'
+echo '# Log in instuctions #'
+echo '###########################################'
+echo ''
+echo "Surf to this Gitlab installation in your browser:"
+echo "http://`wget -qO- http://instance-data/latest/meta-data/public-hostname`/"
+echo ''
+echo 'and login with the following Email and Password:'
+echo 'admin@local.host'
+echo '5iveL!fe' \ No newline at end of file
diff --git a/resque_dev.sh b/resque_dev.sh
index b09cfd9e383..0f1d6edb41f 100755
--- a/resque_dev.sh
+++ b/resque_dev.sh
@@ -1 +1,2 @@
-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
diff --git a/spec/factories.rb b/spec/factories.rb
index 3f9673b499e..2e4acf39461 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -18,11 +18,15 @@ FactoryGirl.define do
Faker::Lorem.sentence
end
+ sequence :name, aliases: [:file_name] do
+ Faker::Name.name
+ end
+
sequence(:url) { Faker::Internet.uri('http') }
factory :user, aliases: [:author, :assignee, :owner] do
email { Faker::Internet.email }
- name { Faker::Name.name }
+ name
password "123456"
password_confirmation "123456"
@@ -116,6 +120,11 @@ FactoryGirl.define do
author
title
content
- file_name { Faker::Lorem.sentence }
+ file_name
+ end
+
+ factory :protected_branch do
+ name
+ project
end
end
diff --git a/spec/helpers/gitlab_flavored_markdown_spec.rb b/spec/helpers/gitlab_flavored_markdown_spec.rb
deleted file mode 100644
index 28bd46ecb99..00000000000
--- a/spec/helpers/gitlab_flavored_markdown_spec.rb
+++ /dev/null
@@ -1,232 +0,0 @@
-require "spec_helper"
-
-describe GitlabMarkdownHelper do
- before do
- @project = Factory(:project)
- @commit = @project.repo.commits.first.parents.first
- @commit = CommitDecorator.decorate(Commit.new(@commit))
- @other_project = Factory :project, path: "OtherPath", code: "OtherCode"
- @fake_user = Factory :user, name: "fred"
- end
-
- describe "#gfm" do
- it "should return text if @project is not set" do
- @project = nil
-
- gfm("foo").should == "foo"
- end
-
- describe "referencing a commit" do
- it "should link using a full id" do
- gfm("Reverts changes from #{@commit.id}").should == "Reverts changes from #{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 link using a short id" do
- gfm("Backported from #{@commit.id[0, 6]}").should == "Backported from #{link_to @commit.id[0, 6], project_commit_path(@project, id: @commit.id), title: "Commit: #{@commit.author_name} - #{@commit.title}", class: "gfm gfm-commit "}"
- end
-
- it "should link with adjecent text" do
- gfm("Reverted (see #{@commit.id})").should == "Reverted (see #{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 link with an invalid id" do
- gfm("What happened in 12345678?").should == "What happened in 12345678?"
- end
- end
-
- describe "referencing a team member" do
- it "should link using a simple name" do
- user = Factory :user, name: "barry"
- @project.users << user
- member = @project.users_projects.where(user_id: user).first
-
- gfm("@#{user.name} you are right").should == "#{link_to "@#{user.name}", project_team_member_path(@project, member), class: "gfm gfm-team_member "} you are right"
- end
-
- it "should link using a name with dots" do
- user = Factory :user, name: "alphA.Beta"
- @project.users << user
- member = @project.users_projects.where(user_id: user).first
-
- gfm("@#{user.name} you are right").should == "#{link_to "@#{user.name}", project_team_member_path(@project, member), class: "gfm gfm-team_member "} you are right"
- end
-
- it "should link using name with underscores" do
- user = Factory :user, name: "ping_pong_king"
- @project.users << user
- member = @project.users_projects.where(user_id: user).first
-
- gfm("@#{user.name} you are right").should == "#{link_to "@#{user.name}", project_team_member_path(@project, member), class: "gfm gfm-team_member "} you are right"
- end
-
- it "should link with adjecent text" do
- user = Factory.create(:user, name: "ace")
- @project.users << user
- member = @project.users_projects.where(user_id: user).first
-
- gfm("Mail the Admin (@#{user.name})").should == "Mail the Admin (#{link_to "@#{user.name}", project_team_member_path(@project, member), class: "gfm gfm-team_member "})"
- end
-
- it "should add styles" do
- user = Factory :user, name: "barry"
- @project.users << user
- gfm("@#{user.name} you are right").should have_selector(".gfm.gfm-team_member")
- end
-
- it "should not link using a bogus name" do
- gfm("What hapened to @foo?").should == "What hapened to @foo?"
- end
- end
-
- describe "referencing an issue" do
- before do
- @issue = Factory :issue, assignee: @fake_user, author: @fake_user, project: @project
- @invalid_issue = Factory :issue, assignee: @fake_user, author: @fake_user, project: @other_project
- end
-
- it "should link using a correct id" do
- gfm("Fixes ##{@issue.id}").should == "Fixes #{link_to "##{@issue.id}", project_issue_path(@project, @issue), title: "Issue: #{@issue.title}", class: "gfm gfm-issue "}"
- end
-
- it "should link with adjecent text" do
- gfm("This has already been discussed (see ##{@issue.id})").should == "This has already been discussed (see #{link_to "##{@issue.id}", project_issue_path(@project, @issue), title: "Issue: #{@issue.title}", class: "gfm gfm-issue "})"
- end
-
- it "should add styles" do
- gfm("Fixes ##{@issue.id}").should have_selector(".gfm.gfm-issue")
- end
-
- it "should not link using an invalid id" do
- gfm("##{@invalid_issue.id} has been marked duplicate of this").should == "##{@invalid_issue.id} has been marked duplicate of this"
- end
- end
-
- describe "referencing a merge request" do
- before do
- @merge_request = Factory :merge_request, assignee: @fake_user, author: @fake_user, project: @project
- @invalid_merge_request = Factory :merge_request, assignee: @fake_user, author: @fake_user, project: @other_project
- end
-
- it "should link using a correct id" do
- gfm("Fixed in !#{@merge_request.id}").should == "Fixed in #{link_to "!#{@merge_request.id}", project_merge_request_path(@project, @merge_request), title: "Merge Request: #{@merge_request.title}", class: "gfm gfm-merge_request "}"
- end
-
- it "should link with adjecent text" do
- gfm("This has been fixed already (see !#{@merge_request.id})").should == "This has been fixed already (see #{link_to "!#{@merge_request.id}", project_merge_request_path(@project, @merge_request), title: "Merge Request: #{@merge_request.title}", class: "gfm gfm-merge_request "})"
- end
-
- it "should add styles" do
- gfm("Fixed in !#{@merge_request.id}").should have_selector(".gfm.gfm-merge_request")
- end
-
- it "should not link using an invalid id" do
- gfm("!#{@invalid_merge_request.id} violates our coding guidelines")
- end
- end
-
- describe "referencing a snippet" do
- before do
- @snippet = Factory.create(:snippet,
- title: "Render asset to string",
- author: @fake_user,
- project: @project)
- end
-
- it "should link using a correct id" do
- gfm("Check out $#{@snippet.id}").should == "Check out #{link_to "$#{@snippet.id}", project_snippet_path(@project, @snippet), title: "Snippet: #{@snippet.title}", class: "gfm gfm-snippet "}"
- end
-
- it "should link with adjecent text" do
- gfm("I have created a snippet for that ($#{@snippet.id})").should == "I have created a snippet for that (#{link_to "$#{@snippet.id}", project_snippet_path(@project, @snippet), title: "Snippet: #{@snippet.title}", class: "gfm gfm-snippet "})"
- end
-
- it "should add styles" do
- gfm("Check out $#{@snippet.id}").should have_selector(".gfm.gfm-snippet")
- end
-
- it "should not link using an invalid id" do
- gfm("Don't use $1234").should == "Don't use $1234"
- end
- end
-
- it "should link to multiple things" do
- user = Factory :user, name: "barry"
- @project.users << user
- member = @project.users_projects.where(user_id: user).first
-
- 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" do
- gfm("_Please_ *stop* 'helping' and all the other b*$#%' you do.").should == "_Please_ *stop* 'helping' and all the other b*$#%' you do."
- end
-
- it "should not touch HTML entities" do
- gfm("We&#39;ll accept good pull requests.").should == "We&#39;ll accept good pull requests."
- end
-
- it "should forward HTML options to links" do
- gfm("fixed in #{@commit.id}", class: "foo").should have_selector("a.foo")
- end
- end
-
- describe "#link_to_gfm" do
- let(:issue1) { Factory :issue, assignee: @fake_user, author: @fake_user, project: @project }
- let(:issue2) { Factory :issue, assignee: @fake_user, author: @fake_user, project: @project }
-
- it "should handle references nested in links with all the text" do
- link_to_gfm("This should finally fix ##{issue1.id} and ##{issue2.id} for real", project_commit_path(@project, id: @commit.id)).should == "#{link_to "This should finally fix ", project_commit_path(@project, id: @commit.id)}#{link_to "##{issue1.id}", project_issue_path(@project, issue1), title: "Issue: #{issue1.title}", class: "gfm gfm-issue "}#{link_to " and ", project_commit_path(@project, id: @commit.id)}#{link_to "##{issue2.id}", project_issue_path(@project, issue2), title: "Issue: #{issue2.title}", class: "gfm gfm-issue "}#{link_to " for real", project_commit_path(@project, id: @commit.id)}"
- end
-
- it "should forward HTML options" do
- link_to_gfm("This should finally fix ##{issue1.id} for real", project_commit_path(@project, id: @commit.id), class: "foo").should have_selector(".foo")
- end
- end
-
- describe "#markdown" do
- before do
- @issue = Factory :issue, assignee: @fake_user, author: @fake_user, project: @project
- @merge_request = Factory :merge_request, assignee: @fake_user, author: @fake_user, project: @project
- @note = Factory.create(:note,
- note: "Screenshot of the new feature",
- project: @project,
- noteable_id: @commit.id,
- noteable_type: "Commit",
- attachment: "screenshot123.jpg")
- @snippet = Factory.create(:snippet,
- title: "Render asset to string",
- author: @fake_user,
- project: @project)
-
- @other_user = Factory :user, name: "bill"
- @project.users << @other_user
- @member = @project.users_projects.where(user_id: @other_user).first
- end
-
- it "should handle references in paragraphs" do
- markdown("\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. #{@commit.id} Nam pulvinar sapien eget odio adipiscing at faucibus orci vestibulum.\n").should == "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. #{link_to @commit.id, project_commit_path(@project, id: @commit.id), title: "Commit: #{@commit.author_name} - #{@commit.title}", class: "gfm gfm-commit "} Nam pulvinar sapien eget odio adipiscing at faucibus orci vestibulum.</p>\n"
- end
-
- it "should handle references in headers" do
- markdown("\n# Working around ##{@issue.id} for now\n## Apply !#{@merge_request.id}").should == "<h1 id=\"toc_0\">Working around #{link_to "##{@issue.id}", project_issue_path(@project, @issue), title: "Issue: #{@issue.title}", class: "gfm gfm-issue "} for now</h1>\n\n<h2 id=\"toc_1\">Apply #{link_to "!#{@merge_request.id}", project_merge_request_path(@project, @merge_request), title: "Merge Request: #{@merge_request.title}", class: "gfm gfm-merge_request "}</h2>\n"
- end
-
- it "should handle references in lists" do
- markdown("\n* dark: ##{@issue.id}\n* light by @#{@other_user.name}\n").should == "<ul>\n<li>dark: #{link_to "##{@issue.id}", project_issue_path(@project, @issue), title: "Issue: #{@issue.title}", class: "gfm gfm-issue "}</li>\n<li>light by #{link_to "@#{@other_user.name}", project_team_member_path(@project, @member), class: "gfm gfm-team_member "}</li>\n</ul>\n"
- end
-
- it "should handle references in <em>" do
- markdown("Apply _!#{@merge_request.id}_ ASAP").should == "<p>Apply <em>#{link_to "!#{@merge_request.id}", project_merge_request_path(@project, @merge_request), title: "Merge Request: #{@merge_request.title}", class: "gfm gfm-merge_request "}</em> ASAP</p>\n"
- end
-
- it "should leave code blocks untouched" do
- markdown("\n some code from $#{@snippet.id}\n here too\n").should == "<div class=\"highlight\"><pre><span class=\"n\">some</span> <span class=\"n\">code</span> <span class=\"n\">from</span> $#{@snippet.id}\n<span class=\"n\">here</span> <span class=\"n\">too</span>\n</pre>\n</div>\n"
-
- markdown("\n```\nsome code from $#{@snippet.id}\nhere too\n```\n").should == "<div class=\"highlight\"><pre><span class=\"n\">some</span> <span class=\"n\">code</span> <span class=\"n\">from</span> $#{@snippet.id}\n<span class=\"n\">here</span> <span class=\"n\">too</span>\n</pre>\n</div>\n"
- end
-
- it "should leave inline code untouched" do
- markdown("\nDon't use `$#{@snippet.id}` here.\n").should == "<p>Don&#39;t use <code>$#{@snippet.id}</code> here.</p>\n"
- end
- end
-end
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
new file mode 100644
index 00000000000..00164e0cdd7
--- /dev/null
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -0,0 +1,288 @@
+require "spec_helper"
+
+describe GitlabMarkdownHelper do
+ let!(:project) { create(:project) }
+
+ let(:user) { create(:user, name: 'gfm') }
+ let(:commit) { CommitDecorator.decorate(project.commit) }
+ let(:issue) { create(:issue, project: project) }
+ let(:merge_request) { create(:merge_request, project: project) }
+ let(:snippet) { create(:snippet, project: project) }
+ let(:member) { project.users_projects.where(user_id: user).first }
+
+ before do
+ # Helper expects a @project instance variable
+ @project = project
+ end
+
+ describe "#gfm" do
+ it "should return unaltered text if project is nil" do
+ actual = "Testing references: ##{issue.id}"
+
+ gfm(actual).should_not == actual
+
+ @project = nil
+ gfm(actual).should == actual
+ end
+
+ it "should not alter non-references" do
+ actual = expected = "_Please_ *stop* 'helping' and all the other b*$#%' you do."
+ gfm(actual).should == expected
+ end
+
+ it "should not touch HTML entities" do
+ actual = expected = "We&#39;ll accept good pull requests."
+ gfm(actual).should == expected
+ end
+
+ it "should forward HTML options to links" do
+ gfm("Fixed in #{commit.id}", class: "foo").should have_selector("a.gfm.foo")
+ end
+
+ describe "referencing a commit" do
+ let(:expected) { project_commit_path(project, commit) }
+
+ it "should link using a full id" do
+ actual = "Reverts #{commit.id}"
+ gfm(actual).should match(expected)
+ end
+
+ it "should link using a short id" do
+ actual = "Backported from #{commit.short_id(6)}"
+ gfm(actual).should match(expected)
+ end
+
+ it "should link with adjacent text" do
+ actual = "Reverted (see #{commit.id})"
+ gfm(actual).should match(expected)
+ end
+
+ it "should keep whitespace intact" do
+ actual = "Changes #{commit.id} dramatically"
+ expected = /Changes <a.+>#{commit.id}<\/a> dramatically/
+ gfm(actual).should match(expected)
+ end
+
+ it "should not link with an invalid id" do
+ actual = expected = "What happened in #{commit.id.reverse}"
+ gfm(actual).should == expected
+ end
+
+ it "should include a title attribute" do
+ actual = "Reverts #{commit.id}"
+ gfm(actual).should match(/title="#{commit.link_title}"/)
+ end
+
+ it "should include standard gfm classes" do
+ actual = "Reverts #{commit.id}"
+ gfm(actual).should match(/class="\s?gfm gfm-commit\s?"/)
+ end
+ end
+
+ describe "referencing a team member" do
+ let(:actual) { "@#{user.name} you are right." }
+ let(:expected) { project_team_member_path(project, member) }
+
+ before do
+ project.users << user
+ end
+
+ it "should link using a simple name" do
+ gfm(actual).should match(expected)
+ end
+
+ it "should link using a name with dots" do
+ user.update_attributes(name: "alphA.Beta")
+ gfm(actual).should match(expected)
+ end
+
+ it "should link using name with underscores" do
+ user.update_attributes(name: "ping_pong_king")
+ gfm(actual).should match(expected)
+ end
+
+ it "should link with adjacent text" do
+ actual = "Mail the admin (@gfm)"
+ gfm(actual).should match(expected)
+ end
+
+ it "should keep whitespace intact" do
+ actual = "Yes, @#{user.name} is right."
+ expected = /Yes, <a.+>@#{user.name}<\/a> is right/
+ gfm(actual).should match(expected)
+ end
+
+ it "should not link with an invalid id" do
+ actual = expected = "@#{user.name.reverse} you are right."
+ gfm(actual).should == expected
+ end
+
+ it "should include standard gfm classes" do
+ gfm(actual).should match(/class="\s?gfm gfm-team_member\s?"/)
+ end
+ end
+
+ # Shared examples for referencing an object
+ #
+ # Expects the following attributes to be available in the example group:
+ #
+ # - object - The object itself
+ # - reference - The object reference string (e.g., #1234, $1234, !1234)
+ #
+ # Currently limited to Snippets, Issues and MergeRequests
+ shared_examples 'referenced object' do
+ let(:actual) { "Reference to #{reference}" }
+ let(:expected) { polymorphic_path([project, object]) }
+
+ it "should link using a valid id" do
+ gfm(actual).should match(expected)
+ end
+
+ it "should link with adjacent text" do
+ # Wrap the reference in parenthesis
+ gfm(actual.gsub(reference, "(#{reference})")).should match(expected)
+
+ # Append some text to the end of the reference
+ gfm(actual.gsub(reference, "#{reference}, right?")).should match(expected)
+ end
+
+ it "should keep whitespace intact" do
+ actual = "Referenced #{reference} already."
+ expected = /Referenced <a.+>[^\s]+<\/a> already/
+ gfm(actual).should match(expected)
+ end
+
+ it "should not link with an invalid id" do
+ # Modify the reference string so it's still parsed, but is invalid
+ reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
+ gfm(actual).should == actual
+ end
+
+ it "should include a title attribute" do
+ title = "#{object.class.to_s.titlecase}: #{object.title}"
+ gfm(actual).should match(/title="#{title}"/)
+ end
+
+ it "should include standard gfm classes" do
+ css = object.class.to_s.underscore
+ gfm(actual).should match(/class="\s?gfm gfm-#{css}\s?"/)
+ end
+ end
+
+ describe "referencing an issue" do
+ let(:object) { issue }
+ let(:reference) { "##{issue.id}" }
+
+ include_examples 'referenced object'
+ end
+
+ describe "referencing a merge request" do
+ let(:object) { merge_request }
+ let(:reference) { "!#{merge_request.id}" }
+
+ include_examples 'referenced object'
+ end
+
+ describe "referencing a snippet" do
+ let(:object) { snippet }
+ let(:reference) { "$#{snippet.id}" }
+
+ include_examples 'referenced object'
+ end
+
+ describe "referencing multiple objects" do
+ let(:actual) { "!#{merge_request.id} -> #{commit.id} -> ##{issue.id}" }
+
+ it "should link to the merge request" do
+ expected = project_merge_request_path(project, merge_request)
+ gfm(actual).should match(expected)
+ end
+
+ it "should link to the commit" do
+ expected = project_commit_path(project, commit)
+ gfm(actual).should match(expected)
+ end
+
+ it "should link to the issue" do
+ expected = project_issue_path(project, issue)
+ gfm(actual).should match(expected)
+ end
+ end
+ end
+
+ describe "#link_to_gfm" do
+ let(:commit_path) { project_commit_path(project, commit) }
+ let(:issues) { create_list(:issue, 2, project: project) }
+
+ it "should handle references nested in links with all the text" do
+ actual = link_to_gfm("This should finally fix ##{issues[0].id} and ##{issues[1].id} for real", commit_path)
+
+ # Break the result into groups of links with their content, without
+ # closing tags
+ groups = actual.split("</a>")
+
+ # Leading commit link
+ groups[0].should match(/href="#{commit_path}"/)
+ groups[0].should match(/This should finally fix $/)
+
+ # First issue link
+ groups[1].should match(/href="#{project_issue_path(project, issues[0])}"/)
+ groups[1].should match(/##{issues[0].id}$/)
+
+ # Internal commit link
+ groups[2].should match(/href="#{commit_path}"/)
+ groups[2].should match(/ and /)
+
+ # Second issue link
+ groups[3].should match(/href="#{project_issue_path(project, issues[1])}"/)
+ groups[3].should match(/##{issues[1].id}$/)
+
+ # Trailing commit link
+ groups[4].should match(/href="#{commit_path}"/)
+ groups[4].should match(/ for real$/)
+ end
+
+ it "should forward HTML options" do
+ actual = link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo')
+ actual.should have_selector 'a.gfm.gfm-commit.foo'
+ end
+ end
+
+ describe "#markdown" do
+ it "should handle references in paragraphs" do
+ markdown("\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. #{commit.id} Nam pulvinar sapien eget odio adipiscing at faucibus orci vestibulum.\n").should == "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. #{link_to commit.id, project_commit_path(project, commit), title: commit.link_title, class: "gfm gfm-commit "} Nam pulvinar sapien eget odio adipiscing at faucibus orci vestibulum.</p>\n"
+ end
+
+ it "should handle references in headers" do
+ actual = "\n# Working around ##{issue.id}\n## Apply !#{merge_request.id}"
+
+ markdown(actual).should match(%r{<h1[^<]*>Working around <a.+>##{issue.id}</a></h1>})
+ markdown(actual).should match(%r{<h2[^<]*>Apply <a.+>!#{merge_request.id}</a></h2>})
+ end
+
+ it "should handle references in lists" do
+ project.users << user
+
+ actual = "\n* dark: ##{issue.id}\n* light by @#{member.user_name}"
+
+ markdown(actual).should match(%r{<li>dark: <a.+>##{issue.id}</a></li>})
+ markdown(actual).should match(%r{<li>light by <a.+>@#{member.user_name}</a></li>})
+ end
+
+ it "should handle references in <em>" do
+ actual = "Apply _!#{merge_request.id}_ ASAP"
+
+ markdown(actual).should match(%r{Apply <em><a.+>!#{merge_request.id}</a></em>})
+ end
+
+ it "should leave code blocks untouched" do
+ markdown("\n some code from $#{snippet.id}\n here too\n").should == "<div class=\"highlight\"><pre><span class=\"n\">some</span> <span class=\"n\">code</span> <span class=\"n\">from</span> $#{snippet.id}\n<span class=\"n\">here</span> <span class=\"n\">too</span>\n</pre>\n</div>\n"
+
+ markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n").should == "<div class=\"highlight\"><pre><span class=\"n\">some</span> <span class=\"n\">code</span> <span class=\"n\">from</span> $#{snippet.id}\n<span class=\"n\">here</span> <span class=\"n\">too</span>\n</pre>\n</div>\n"
+ end
+
+ it "should leave inline code untouched" do
+ markdown("\nDon't use `$#{snippet.id}` here.\n").should == "<p>Don&#39;t use <code>$#{snippet.id}</code> here.</p>\n"
+ end
+ end
+end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 60f3231ce91..cf50b429f23 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -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
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 188f09978a7..aaffda3199e 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -1,24 +1,9 @@
-# == 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
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 133f073451f..69829a4d13d 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -2,21 +2,16 @@ 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) }
@@ -61,57 +56,4 @@ describe Issue do
subject.is_being_reopened?.should be_false
end
end
-
- describe "plus 1" do
- subject { Factory.create(:issue) }
-
- 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")
- 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")
- subject.upvotes.should == 1
- end
-
- it "should recognize a multiple +1 notes" do
- subject.notes << Factory(:note, note: "+1 This is awesome")
- subject.notes << Factory(:note, note: "+1 I want this")
- subject.upvotes.should == 2
- end
- end
-
- describe ".search" do
- let!(:issue) { Factory.create(:issue, title: "Searchable issue") }
-
- 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)
-#
-
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index ea58fbd291e..85cd291d681 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -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
@@ -44,17 +47,3 @@ describe Key do
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)
-#
-
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index f4b93eea966..d1253b35ae5 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1,76 +1,13 @@
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
-
- describe "plus 1" do
- subject { Factory.create(:merge_request) }
-
- 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")
- 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")
- subject.upvotes.should == 1
- end
-
- it "should recognize a multiple +1 notes" do
- subject.notes << Factory(:note, note: "+1 This is awesome")
- subject.notes << Factory(:note, note: "+1 I want this")
- subject.upvotes.should == 2
- end
end
- describe ".search" do
- let!(:issue) { Factory.create(:issue, title: "Searchable issue") }
-
- 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
-#
-
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index ed805a243b5..fa15fc8f560 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -1,17 +1,3 @@
-# == 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
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 89e50479762..ffaf442d9a4 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -3,6 +3,8 @@ require 'spec_helper'
describe Note do
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
@@ -130,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)
-#
-
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index faaa9a917f4..5add7ff88a9 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -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
@@ -73,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
@@ -110,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 }
@@ -236,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
-#
-
diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb
index 1654e3b6f56..9180bc3bca6 100644
--- a/spec/models/protected_branch_spec.rb
+++ b/spec/models/protected_branch_spec.rb
@@ -1,19 +1,6 @@
-# == 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
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index 9b4aaa13f74..ffb861c4910 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -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
-#
-
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index ebc45fa4710..ca34f07df7f 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2,13 +2,26 @@ require 'spec_helper'
describe User do
describe "Associations" do
- it { should have_many(:projects) }
it { should have_many(:users_projects).dependent(:destroy) }
+ it { should have_many(:projects) }
+ 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) }
- it { should have_many(:notes).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
@@ -51,33 +64,3 @@ describe User do
user.authentication_token.should_not == ""
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
-#
-
diff --git a/spec/models/users_project_spec.rb b/spec/models/users_project_spec.rb
index 87fbfbf2a8c..3197ba6eb6b 100644
--- a/spec/models/users_project_spec.rb
+++ b/spec/models/users_project_spec.rb
@@ -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
-#
-
diff --git a/spec/models/web_hook_spec.rb b/spec/models/web_hook_spec.rb
index 885947614d7..3cba5b64ff0 100644
--- a/spec/models/web_hook_spec.rb
+++ b/spec/models/web_hook_spec.rb
@@ -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
-#
-
diff --git a/spec/models/wiki_spec.rb b/spec/models/wiki_spec.rb
index 892d0e8fe17..de6ce426331 100644
--- a/spec/models/wiki_spec.rb
+++ b/spec/models/wiki_spec.rb
@@ -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)
-#
-
diff --git a/spec/observers/issue_observer_spec.rb b/spec/observers/issue_observer_spec.rb
index c6a405f1c1b..b5943f2c539 100644
--- a/spec/observers/issue_observer_spec.rb
+++ b/spec/observers/issue_observer_spec.rb
@@ -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
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index c00a056d079..293ea83ae6c 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -65,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("/projects/#{project.code}/issues/#{issue.id}", user)
- }.to change { Issue.count }.by(-1)
+ delete api("/projects/#{project.code}/issues/#{issue.id}", user)
+ response.status.should == 405
end
end
end
diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb
new file mode 100644
index 00000000000..cf5f65f068c
--- /dev/null
+++ b/spec/requests/api/milestones_spec.rb
@@ -0,0 +1,47 @@
+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
diff --git a/spec/roles/issue_commonality_spec.rb b/spec/roles/issue_commonality_spec.rb
new file mode 100644
index 00000000000..77b98b46ed9
--- /dev/null
+++ b/spec/roles/issue_commonality_spec.rb
@@ -0,0 +1,69 @@
+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
diff --git a/spec/roles/upvote_spec.rb b/spec/roles/upvote_spec.rb
new file mode 100644
index 00000000000..24288ada0fe
--- /dev/null
+++ b/spec/roles/upvote_spec.rb
@@ -0,0 +1,27 @@
+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
diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb
index e0672166e92..cb1dcba3dd8 100644
--- a/spec/support/matchers.rb
+++ b/spec/support/matchers.rb
@@ -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