Commit c09d233611e00328f0e8d493a106737f0638d9a2

Authored by Alex Denisov
2 parents e6ce4729 3c132f2e

Merge branch 'master' into fix_project_access_notification

Showing 74 changed files with 1150 additions and 838 deletions   Show diff stats
  1 +v 2.9.1
  2 + - Fixed resque custom config init
  3 +
1 v 2.9.0 4 v 2.9.0
2 - fixed inline notes bugs 5 - fixed inline notes bugs
3 - refactored rspecs 6 - refactored rspecs
@@ -9,8 +12,10 @@ v 2.9.0 @@ -9,8 +12,10 @@ v 2.9.0
9 - scss refactoring. gitlab_bootstrap/ dir 12 - scss refactoring. gitlab_bootstrap/ dir
10 - fix git push http body bigger than 112k problem 13 - fix git push http body bigger than 112k problem
11 - list of labels page under issues tab 14 - list of labels page under issues tab
12 - - API for milestones 15 + - API for milestones, keys
13 - restyled buttons 16 - restyled buttons
  17 + - OAuth
  18 + - Comment order changed
14 19
15 v 2.8.1 20 v 2.8.1
16 - ability to disable gravatars 21 - ability to disable gravatars
@@ -96,6 +96,7 @@ group :assets do @@ -96,6 +96,7 @@ group :assets do
96 gem "therubyracer" 96 gem "therubyracer"
97 97
98 gem 'chosen-rails' 98 gem 'chosen-rails'
  99 + gem 'jquery-atwho-rails', '0.1.6'
99 gem "jquery-rails", "2.0.2" 100 gem "jquery-rails", "2.0.2"
100 gem "jquery-ui-rails", "0.5.0" 101 gem "jquery-ui-rails", "0.5.0"
101 gem "modernizr", "2.5.3" 102 gem "modernizr", "2.5.3"
@@ -199,6 +199,7 @@ GEM @@ -199,6 +199,7 @@ GEM
199 httpauth (0.1) 199 httpauth (0.1)
200 i18n (0.6.1) 200 i18n (0.6.1)
201 journey (1.0.4) 201 journey (1.0.4)
  202 + jquery-atwho-rails (0.1.6)
202 jquery-rails (2.0.2) 203 jquery-rails (2.0.2)
203 railties (>= 3.2.0, < 5.0) 204 railties (>= 3.2.0, < 5.0)
204 thor (~> 0.14) 205 thor (~> 0.14)
@@ -441,6 +442,7 @@ DEPENDENCIES @@ -441,6 +442,7 @@ DEPENDENCIES
441 haml-rails 442 haml-rails
442 headless 443 headless
443 httparty 444 httparty
  445 + jquery-atwho-rails (= 0.1.6)
444 jquery-rails (= 2.0.2) 446 jquery-rails (= 2.0.2)
445 jquery-ui-rails (= 0.5.0) 447 jquery-ui-rails (= 0.5.0)
446 kaminari 448 kaminari
@@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
4 guard 'rspec', :version => 2, :all_on_start => false, :all_after_pass => false do 4 guard 'rspec', :version => 2, :all_on_start => false, :all_after_pass => false do
5 watch(%r{^spec/.+_spec\.rb$}) 5 watch(%r{^spec/.+_spec\.rb$})
6 watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } 6 watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
  7 + watch(%r{^lib/api/(.+)\.rb$}) { |m| "spec/requests/api/#{m[1]}_spec.rb" }
7 watch('spec/spec_helper.rb') { "spec" } 8 watch('spec/spec_helper.rb') { "spec" }
8 9
9 # Rails example 10 # Rails example
1 -2.9.0pre 1 +2.9.1
app/assets/javascripts/application.js
@@ -11,6 +11,7 @@ @@ -11,6 +11,7 @@
11 //= require jquery.endless-scroll 11 //= require jquery.endless-scroll
12 //= require jquery.highlight 12 //= require jquery.highlight
13 //= require jquery.waitforimages 13 //= require jquery.waitforimages
  14 +//= require jquery.atwho
14 //= require bootstrap 15 //= require bootstrap
15 //= require modernizr 16 //= require modernizr
16 //= require chosen-jquery 17 //= require chosen-jquery
app/assets/stylesheets/application.css
@@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
4 * the top of the compiled file, but it's generally better to create a new file per style scope. 4 * the top of the compiled file, but it's generally better to create a new file per style scope.
5 *= require jquery.ui.all 5 *= require jquery.ui.all
6 *= require jquery.ui.aristo 6 *= require jquery.ui.aristo
  7 + *= require jquery.atwho
7 *= require chosen 8 *= require chosen
8 *= require_self 9 *= require_self
9 *= require main 10 *= require main
app/assets/stylesheets/common.scss
@@ -185,36 +185,6 @@ span.update-author { @@ -185,36 +185,6 @@ span.update-author {
185 } 185 }
186 } 186 }
187 187
188 -.event_label {  
189 - @extend .label;  
190 - background-color: #999;  
191 -  
192 - &.pushed {  
193 - background-color: #4A97BD;  
194 - }  
195 -  
196 - &.opened {  
197 - background-color: #469847;  
198 - }  
199 -  
200 - &.closed {  
201 - background-color: #B94A48;  
202 - }  
203 -  
204 - &.merged {  
205 - background-color: #2A2;  
206 - }  
207 -  
208 - &.joined {  
209 - background-color: #1ca9dd;  
210 - }  
211 -  
212 - &.left {  
213 - background-color: #888;  
214 - float:none;  
215 - }  
216 -}  
217 -  
218 form { 188 form {
219 @extend .form-horizontal; 189 @extend .form-horizontal;
220 190
@@ -355,41 +325,6 @@ p.time { @@ -355,41 +325,6 @@ p.time {
355 border:2px solid #ddd; 325 border:2px solid #ddd;
356 } 326 }
357 327
358 -.event_feed {  
359 - min-height:40px;  
360 - border-bottom:1px solid #ddd;  
361 - .avatar {  
362 - width:32px;  
363 - }  
364 - .event_icon {  
365 - float:right;  
366 - margin-right:2px;  
367 - img {  
368 - width:20px;  
369 - }  
370 - }  
371 - ul {  
372 - margin-left:50px;  
373 - margin-bottom:5px;  
374 - .avatar {  
375 - width:24px;  
376 - }  
377 - }  
378 -  
379 - padding: 15px 5px;  
380 - &:last-child { border:none }  
381 - .wll:hover { background:none }  
382 -  
383 - .event_commits {  
384 - margin-top: 5px;  
385 -  
386 - li.commit {  
387 - background: transparent;  
388 - padding:5px;  
389 - border:none;  
390 - }  
391 - }  
392 -}  
393 328
394 .ico { 329 .ico {
395 background: url("images.png") no-repeat -85px -77px; 330 background: url("images.png") no-repeat -85px -77px;
@@ -639,22 +574,6 @@ li.note { @@ -639,22 +574,6 @@ li.note {
639 background:#fff; 574 background:#fff;
640 } 575 }
641 576
642 -/**  
643 - * Push event widget  
644 - *  
645 - */  
646 -.event_lp {  
647 - @extend .ui-box;  
648 - color:#777;  
649 - margin-bottom:20px;  
650 - padding:8px;  
651 - @include border-radius(4px);  
652 - min-height:22px;  
653 -  
654 - .avatar {  
655 - width:24px;  
656 - }  
657 -}  
658 577
659 .supp_diff_link, 578 .supp_diff_link,
660 .mr_show_all_commits { 579 .mr_show_all_commits {
app/assets/stylesheets/gitlab_bootstrap/lists.scss
1 /** LISTS **/ 1 /** LISTS **/
2 2
3 -ul { 3 +ul {
4 /** 4 /**
5 * List li block element #1 5 * List li block element #1
6 * 6 *
@@ -18,7 +18,7 @@ ul { @@ -18,7 +18,7 @@ ul {
18 .author { color: #999; } 18 .author { color: #999; }
19 19
20 p { 20 p {
21 - padding-top:5px; 21 + padding-top:5px;
22 margin:0; 22 margin:0;
23 color:#222; 23 color:#222;
24 img { 24 img {
app/assets/stylesheets/main.scss
@@ -143,6 +143,7 @@ $hover: #fdf5d9; @@ -143,6 +143,7 @@ $hover: #fdf5d9;
143 @import "sections/projects.scss"; 143 @import "sections/projects.scss";
144 @import "sections/merge_requests.scss"; 144 @import "sections/merge_requests.scss";
145 @import "sections/graph.scss"; 145 @import "sections/graph.scss";
  146 +@import "sections/events.scss";
146 147
147 /** 148 /**
148 * This scss file redefine chozen selectbox styles for 149 * This scss file redefine chozen selectbox styles for
app/assets/stylesheets/sections/commits.scss
1 .commit-box { 1 .commit-box {
2 @extend .main_box; 2 @extend .main_box;
3 3
4 - .commit-head { 4 + .commit-head {
5 @extend .top_box_content; 5 @extend .top_box_content;
6 6
7 .commit-title { 7 .commit-title {
@@ -29,11 +29,11 @@ @@ -29,11 +29,11 @@
29 29
30 .sha-block { 30 .sha-block {
31 text-align:right; 31 text-align:right;
32 - &:first-child { 32 + &:first-child {
33 padding-bottom:6px; 33 padding-bottom:6px;
34 } 34 }
35 35
36 - a { 36 + a {
37 border-bottom: 1px solid #aaa; 37 border-bottom: 1px solid #aaa;
38 margin-left: 9px; 38 margin-left: 9px;
39 } 39 }
@@ -54,7 +54,7 @@ @@ -54,7 +54,7 @@
54 } 54 }
55 55
56 /** 56 /**
57 - * 57 + *
58 * COMMIT SHOw 58 * COMMIT SHOw
59 * 59 *
60 */ 60 */
@@ -71,7 +71,7 @@ @@ -71,7 +71,7 @@
71 background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); 71 background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
72 background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); 72 background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
73 73
74 - span { 74 + span {
75 font-size:14px; 75 font-size:14px;
76 } 76 }
77 } 77 }
@@ -111,8 +111,8 @@ @@ -111,8 +111,8 @@
111 } 111 }
112 } 112 }
113 113
114 - &.img_compared {  
115 - img { 114 + &.img_compared {
  115 + img {
116 max-width:300px; 116 max-width:300px;
117 } 117 }
118 } 118 }
@@ -120,12 +120,12 @@ @@ -120,12 +120,12 @@
120 } 120 }
121 121
122 .diff_file_content{ 122 .diff_file_content{
123 - table { 123 + table {
124 border:none; 124 border:none;
125 margin:0px; 125 margin:0px;
126 padding:0px; 126 padding:0px;
127 tr { 127 tr {
128 - td { 128 + td {
129 font-size:12px; 129 font-size:12px;
130 } 130 }
131 } 131 }
@@ -145,29 +145,29 @@ @@ -145,29 +145,29 @@
145 moz-user-select: none; 145 moz-user-select: none;
146 -khtml-user-select: none; 146 -khtml-user-select: none;
147 user-select: none; 147 user-select: none;
148 - a { 148 + a {
149 float:left; 149 float:left;
150 width:35px; 150 width:35px;
151 font-weight:normal; 151 font-weight:normal;
152 color:#666; 152 color:#666;
153 - &:hover { 153 + &:hover {
154 text-decoration:underline; 154 text-decoration:underline;
155 } 155 }
156 } 156 }
157 } 157 }
158 - .line_content {  
159 - white-space:pre; 158 + .line_content {
  159 + white-space:pre;
160 height:14px; 160 height:14px;
161 margin:0px; 161 margin:0px;
162 padding:0px; 162 padding:0px;
163 border:none; 163 border:none;
164 - &.new { 164 + &.new {
165 background: #CFD; 165 background: #CFD;
166 } 166 }
167 - &.old { 167 + &.old {
168 background: #FDD; 168 background: #FDD;
169 } 169 }
170 - &.matched { 170 + &.matched {
171 color:#ccc; 171 color:#ccc;
172 background:#fafafa; 172 background:#fafafa;
173 } 173 }
@@ -182,32 +182,32 @@ @@ -182,32 +182,32 @@
182 182
183 183
184 /** COMMIT ROW **/ 184 /** COMMIT ROW **/
185 -.commit { 185 +.commit {
186 @extend .wll; 186 @extend .wll;
187 187
188 - .browse_code_link_holder { 188 + .browse_code_link_holder {
189 @extend .span2; 189 @extend .span2;
190 float:right; 190 float:right;
191 } 191 }
192 192
193 - .committed_ago { 193 + .committed_ago {
194 float:right; 194 float:right;
195 @extend .cgray; 195 @extend .cgray;
196 } 196 }
197 197
198 - code { 198 + code {
199 background:#FCEEC1; 199 background:#FCEEC1;
200 color:$style_color; 200 color:$style_color;
201 } 201 }
202 202
203 - .commit_short_id { 203 + .commit_short_id {
204 float:left; 204 float:left;
205 @extend .lined; 205 @extend .lined;
206 min-width:65px; 206 min-width:65px;
207 font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; 207 font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace;
208 } 208 }
209 -  
210 - .commit-author-name { 209 +
  210 + .commit-author-name {
211 color: #777; 211 color: #777;
212 } 212 }
213 } 213 }
app/assets/stylesheets/sections/events.scss 0 → 100644
@@ -0,0 +1,118 @@ @@ -0,0 +1,118 @@
  1 +/**
  2 + * Events labels
  3 + *
  4 + */
  5 +.event_label {
  6 + &.pushed {
  7 + padding:0 2px;
  8 + @extend .alert;
  9 + @extend .alert-info;
  10 + }
  11 +
  12 + &.opened {
  13 + padding:0 2px;
  14 + @extend .alert;
  15 + @extend .alert-success;
  16 + }
  17 +
  18 + &.closed {
  19 + padding:0 2px;
  20 + @extend .alert;
  21 + @extend .alert-error;
  22 + }
  23 +
  24 + &.merged {
  25 + padding:0 2px;
  26 + @extend .alert;
  27 + @extend .alert-success;
  28 + }
  29 +
  30 + &.left,
  31 + &.joined {
  32 + padding:0 2px;
  33 + @extend .alert;
  34 + }
  35 +}
  36 +
  37 +/**
  38 + * Dashboard events feed
  39 + *
  40 + */
  41 +.event-item {
  42 + min-height:40px;
  43 + border-bottom:1px solid #eee;
  44 + .event-title {
  45 + color:#333;
  46 + font-weight: bold;
  47 + .author_name {
  48 + color:#333;
  49 + }
  50 + }
  51 + .event-body {
  52 + p {
  53 + color:#555;
  54 + }
  55 + .event-info {
  56 + color:#666;
  57 + }
  58 + }
  59 + .avatar {
  60 + width:32px;
  61 + }
  62 + .event_icon {
  63 + float: right;
  64 + border: 1px solid #EEE;
  65 + padding: 5px;
  66 + @include border-radius(5px);
  67 + background: #F9F9F9;
  68 + img {
  69 + width:20px;
  70 + }
  71 + }
  72 + ul {
  73 + margin-left:50px;
  74 + margin-bottom:5px;
  75 + .avatar {
  76 + width:18px;
  77 + margin-top:3px;
  78 + }
  79 + }
  80 +
  81 + padding: 15px 5px;
  82 + &:last-child { border:none }
  83 + .wll:hover { background:none }
  84 +
  85 + .event_commits {
  86 + margin-top: 5px;
  87 +
  88 + li {
  89 + &.commit {
  90 + background: transparent;
  91 + padding:3px;
  92 + border:none;
  93 + font-size:12px;
  94 + }
  95 + &.commits-stat {
  96 + display: block;
  97 + margin-top: 5px;
  98 + }
  99 + }
  100 + }
  101 +}
  102 +
  103 +/**
  104 + * Push event widget
  105 + *
  106 + */
  107 +.event_lp {
  108 + @extend .ui-box;
  109 + color:#777;
  110 + margin-bottom:20px;
  111 + padding:8px;
  112 + @include border-radius(4px);
  113 + min-height:22px;
  114 +
  115 + .avatar {
  116 + width:24px;
  117 + }
  118 +}
app/assets/stylesheets/sections/notes.scss
@@ -43,7 +43,9 @@ @@ -43,7 +43,9 @@
43 padding: 8px 0; 43 padding: 8px 0;
44 overflow: hidden; 44 overflow: hidden;
45 display: block; 45 display: block;
  46 + position:relative;
46 img {float: left; margin-right: 10px;} 47 img {float: left; margin-right: 10px;}
  48 + img.emoji {float:none;margin:0;}
47 .note-author cite{font-style: italic;} 49 .note-author cite{font-style: italic;}
48 p { color:$style_color; } 50 p { color:$style_color; }
49 .note-author { color: $style_color;} 51 .note-author { color: $style_color;}
@@ -55,7 +57,9 @@ @@ -55,7 +57,9 @@
55 57
56 .delete-note { 58 .delete-note {
57 display:none; 59 display:none;
58 - float:right; 60 + position:absolute;
  61 + right:0;
  62 + top:0;
59 } 63 }
60 64
61 &:hover { 65 &:hover {
app/controllers/admin/users_controller.rb
@@ -30,7 +30,7 @@ class Admin::UsersController &lt; AdminController @@ -30,7 +30,7 @@ class Admin::UsersController &lt; AdminController
30 30
31 31
32 def new 32 def new
33 - @admin_user = User.new(projects_limit: Gitlab.config.default_projects_limit) 33 + @admin_user = User.new({ projects_limit: Gitlab.config.default_projects_limit }, as: :admin)
34 end 34 end
35 35
36 def edit 36 def edit
@@ -60,7 +60,7 @@ class Admin::UsersController &lt; AdminController @@ -60,7 +60,7 @@ class Admin::UsersController &lt; AdminController
60 def create 60 def create
61 admin = params[:user].delete("admin") 61 admin = params[:user].delete("admin")
62 62
63 - @admin_user = User.new(params[:user]) 63 + @admin_user = User.new(params[:user], as: :admin)
64 @admin_user.admin = (admin && admin.to_i > 0) 64 @admin_user.admin = (admin && admin.to_i > 0)
65 65
66 respond_to do |format| 66 respond_to do |format|
@@ -86,7 +86,7 @@ class Admin::UsersController &lt; AdminController @@ -86,7 +86,7 @@ class Admin::UsersController &lt; AdminController
86 @admin_user.admin = (admin && admin.to_i > 0) 86 @admin_user.admin = (admin && admin.to_i > 0)
87 87
88 respond_to do |format| 88 respond_to do |format|
89 - if @admin_user.update_attributes(params[:user]) 89 + if @admin_user.update_attributes(params[:user], as: :admin)
90 format.html { redirect_to [:admin, @admin_user], notice: 'User was successfully updated.' } 90 format.html { redirect_to [:admin, @admin_user], notice: 'User was successfully updated.' }
91 format.json { head :ok } 91 format.json { head :ok }
92 else 92 else
app/controllers/commits_controller.rb
@@ -52,6 +52,7 @@ class CommitsController &lt; ApplicationController @@ -52,6 +52,7 @@ class CommitsController &lt; ApplicationController
52 @commits = result[:commits] 52 @commits = result[:commits]
53 @commit = result[:commit] 53 @commit = result[:commit]
54 @diffs = result[:diffs] 54 @diffs = result[:diffs]
  55 + @refs_are_same = result[:same]
55 @line_notes = [] 56 @line_notes = []
56 57
57 @commits = CommitDecorator.decorate(@commits) 58 @commits = CommitDecorator.decorate(@commits)
app/controllers/hooks_controller.rb
1 class HooksController < ApplicationController 1 class HooksController < ApplicationController
2 - before_filter :authenticate_user!  
3 before_filter :project 2 before_filter :project
4 layout "project" 3 layout "project"
5 4
app/controllers/issues_controller.rb
1 class IssuesController < ApplicationController 1 class IssuesController < ApplicationController
2 - before_filter :authenticate_user!  
3 before_filter :project 2 before_filter :project
4 before_filter :module_enabled 3 before_filter :module_enabled
5 before_filter :issue, only: [:edit, :update, :destroy, :show] 4 before_filter :issue, only: [:edit, :update, :destroy, :show]
app/controllers/labels_controller.rb
1 class LabelsController < ApplicationController 1 class LabelsController < ApplicationController
2 - before_filter :authenticate_user!  
3 before_filter :project 2 before_filter :project
4 before_filter :module_enabled 3 before_filter :module_enabled
5 4
app/controllers/merge_requests_controller.rb
1 class MergeRequestsController < ApplicationController 1 class MergeRequestsController < ApplicationController
2 - before_filter :authenticate_user!  
3 before_filter :project 2 before_filter :project
4 before_filter :module_enabled 3 before_filter :module_enabled
5 before_filter :merge_request, only: [:edit, :update, :destroy, :show, :commits, :diffs, :automerge, :automerge_check, :raw] 4 before_filter :merge_request, only: [:edit, :update, :destroy, :show, :commits, :diffs, :automerge, :automerge_check, :raw]
app/controllers/milestones_controller.rb
1 class MilestonesController < ApplicationController 1 class MilestonesController < ApplicationController
2 - before_filter :authenticate_user!  
3 before_filter :project 2 before_filter :project
4 before_filter :module_enabled 3 before_filter :module_enabled
5 before_filter :milestone, only: [:edit, :update, :destroy, :show] 4 before_filter :milestone, only: [:edit, :update, :destroy, :show]
app/controllers/snippets_controller.rb
1 class SnippetsController < ApplicationController 1 class SnippetsController < ApplicationController
2 - before_filter :authenticate_user!  
3 before_filter :project 2 before_filter :project
4 before_filter :snippet, only: [:show, :edit, :destroy, :update, :raw] 3 before_filter :snippet, only: [:show, :edit, :destroy, :update, :raw]
5 layout "project" 4 layout "project"
app/helpers/tree_helper.rb
@@ -32,7 +32,11 @@ module TreeHelper @@ -32,7 +32,11 @@ module TreeHelper
32 # 32 #
33 # Returns boolean 33 # Returns boolean
34 def markup?(filename) 34 def markup?(filename)
35 - filename.end_with?(*%w(.mdown .md .markdown .textile .rdoc .org .creole  
36 - .mediawiki .rst .asciidoc .pod)) 35 + filename.end_with?(*%w(.textile .rdoc .org .creole
  36 + .mediawiki .rst .asciidoc .pod))
  37 + end
  38 +
  39 + def gitlab_markdown?(filename)
  40 + filename.end_with?(*%w(.mdown .md .markdown))
37 end 41 end
38 end 42 end
app/models/commit.rb
@@ -82,20 +82,24 @@ class Commit @@ -82,20 +82,24 @@ class Commit
82 end 82 end
83 83
84 def compare(project, from, to) 84 def compare(project, from, to)
85 - first = project.commit(to.try(:strip))  
86 - last = project.commit(from.try(:strip))  
87 -  
88 result = { 85 result = {
89 commits: [], 86 commits: [],
90 diffs: [], 87 diffs: [],
91 - commit: nil 88 + commit: nil,
  89 + same: false
92 } 90 }
93 91
  92 + return result unless from && to
  93 +
  94 + first = project.commit(to.try(:strip))
  95 + last = project.commit(from.try(:strip))
  96 +
94 if first && last 97 if first && last
95 commits = [first, last].sort_by(&:created_at) 98 commits = [first, last].sort_by(&:created_at)
96 younger = commits.first 99 younger = commits.first
97 older = commits.last 100 older = commits.last
98 101
  102 + result[:same] = (younger.id == older.id)
99 result[:commits] = project.repo.commits_between(younger.id, older.id).map {|c| Commit.new(c)} 103 result[:commits] = project.repo.commits_between(younger.id, older.id).map {|c| Commit.new(c)}
100 result[:diffs] = project.repo.diff(younger.id, older.id) rescue [] 104 result[:diffs] = project.repo.diff(younger.id, older.id) rescue []
101 result[:commit] = Commit.new(older) 105 result[:commit] = Commit.new(older)
app/models/event.rb
@@ -132,6 +132,7 @@ class Event &lt; ActiveRecord::Base @@ -132,6 +132,7 @@ class Event &lt; ActiveRecord::Base
132 end 132 end
133 end 133 end
134 134
  135 +
135 delegate :name, :email, to: :author, prefix: true, allow_nil: true 136 delegate :name, :email, to: :author, prefix: true, allow_nil: true
136 delegate :title, to: :issue, prefix: true, allow_nil: true 137 delegate :title, to: :issue, prefix: true, allow_nil: true
137 delegate :title, to: :merge_request, prefix: true, allow_nil: true 138 delegate :title, to: :merge_request, prefix: true, allow_nil: true
app/models/user.rb
@@ -6,8 +6,9 @@ class User &lt; ActiveRecord::Base @@ -6,8 +6,9 @@ class User &lt; ActiveRecord::Base
6 :recoverable, :rememberable, :trackable, :validatable, :omniauthable 6 :recoverable, :rememberable, :trackable, :validatable, :omniauthable
7 7
8 attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, 8 attr_accessible :email, :password, :password_confirmation, :remember_me, :bio,
9 - :name, :projects_limit, :skype, :linkedin, :twitter, :dark_scheme,  
10 - :theme_id, :force_random_password, :extern_uid, :provider 9 + :name, :skype, :linkedin, :twitter, :dark_scheme,
  10 + :theme_id, :force_random_password, :extern_uid, :provider, :as => [:default, :admin]
  11 + attr_accessible :projects_limit, :as => :admin
11 12
12 attr_accessor :force_random_password 13 attr_accessor :force_random_password
13 14
app/roles/repository.rb
@@ -79,6 +79,14 @@ module Repository @@ -79,6 +79,14 @@ module Repository
79 @heads ||= repo.heads 79 @heads ||= repo.heads
80 end 80 end
81 81
  82 + def branches_names
  83 + heads.map(&:name)
  84 + end
  85 +
  86 + def ref_names
  87 + [branches_names + tags].flatten
  88 + end
  89 +
82 def tree(fcommit, path = nil) 90 def tree(fcommit, path = nil)
83 fcommit = commit if fcommit == :head 91 fcommit = commit if fcommit == :head
84 tree = fcommit.tree 92 tree = fcommit.tree
app/views/commits/compare.html.haml
1 = render "head" 1 = render "head"
2 2
3 -%h3 3 +%h3.page_title
4 Compare View 4 Compare View
5 %hr 5 %hr
6 6
7 %div 7 %div
8 - %p 8 + %p.slead
9 Fill input field with commit id like 9 Fill input field with commit id like
10 - %code '4eedf23' 10 + %code.label_branch 4eedf23
11 or branch/tag name like 11 or branch/tag name like
12 - %code master  
13 - &amp; press compare button for commits list, code diff. 12 + %code.label_branch master
  13 + and press compare button for commits list, code diff.
14 14
15 %br 15 %br
16 16
@@ -19,22 +19,24 @@ @@ -19,22 +19,24 @@
19 = text_field_tag :from, params[:from], placeholder: "master", class: "xlarge" 19 = text_field_tag :from, params[:from], placeholder: "master", class: "xlarge"
20 = "..." 20 = "..."
21 = text_field_tag :to, params[:to], placeholder: "aa8b4ef", class: "xlarge" 21 = text_field_tag :to, params[:to], placeholder: "aa8b4ef", class: "xlarge"
  22 + - if @refs_are_same
  23 + .alert
  24 + %span Refs are the same
22 .actions 25 .actions
23 - = submit_tag "Compare", class: "btn primary" 26 + = submit_tag "Compare", class: "btn primary wide commits-compare-btn"
24 27
25 -  
26 -- unless @commits.empty? 28 +- if @commits.present?
27 %div.ui-box 29 %div.ui-box
28 %h5.small Commits (#{@commits.count}) 30 %h5.small Commits (#{@commits.count})
29 %ul.unstyled= render @commits 31 %ul.unstyled= render @commits
30 32
31 -- unless @diffs.empty?  
32 - %h4 Diff  
33 - = render "commits/diffs", diffs: @diffs 33 + - unless @diffs.empty?
  34 + %h4 Diff
  35 + = render "commits/diffs", diffs: @diffs
34 36
35 :javascript 37 :javascript
36 $(function() { 38 $(function() {
37 - var availableTags = #{@project.heads.map(&:name).to_json}; 39 + var availableTags = #{@project.ref_names.to_json};
38 40
39 $("#from").autocomplete({ 41 $("#from").autocomplete({
40 source: availableTags, 42 source: availableTags,
@@ -45,5 +47,7 @@ @@ -45,5 +47,7 @@
45 source: availableTags, 47 source: availableTags,
46 minLength: 1 48 minLength: 1
47 }); 49 });
  50 +
  51 + disableButtonIfEmptyField('#to', '.commits-compare-btn');
48 }); 52 });
49 53
app/views/events/_commit.html.haml
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 %li.commit 2 %li.commit
3 %p 3 %p
4 = link_to commit.short_id(8), project_commit_path(project, id: commit.id), class: "commit_short_id" 4 = link_to commit.short_id(8), project_commit_path(project, id: commit.id), class: "commit_short_id"
5 - %strong.cdark= commit.author_name 5 + %span= commit.author_name
6 &ndash; 6 &ndash;
7 = image_tag gravatar_icon(commit.author_email), class: "avatar", width: 16 7 = image_tag gravatar_icon(commit.author_email), class: "avatar", width: 16
8 = gfm escape_once(truncate(commit.title, length: 50)) rescue "--broken encoding" 8 = gfm escape_once(truncate(commit.title, length: 50)) rescue "--broken encoding"
app/views/events/_event.html.haml
1 - if event.allowed? 1 - if event.allowed?
2 - - if event.issue?  
3 - .event_feed 2 + %div.event-item
  3 + - if event.issue?
4 = render "events/event_issue", event: event 4 = render "events/event_issue", event: event
5 5
6 - - elsif event.merge_request?  
7 - .event_feed 6 + - elsif event.merge_request?
8 = render "events/event_merge_request", event: event 7 = render "events/event_merge_request", event: event
9 8
10 - - elsif event.push?  
11 - .event_feed 9 + - elsif event.push?
12 = render "events/event_push", event: event 10 = render "events/event_push", event: event
13 11
14 - - elsif event.membership_changed?  
15 - .event_feed 12 + - elsif event.membership_changed?
16 = render "events/event_membership_changed", event: event 13 = render "events/event_membership_changed", event: event
17 14
  15 + %span.cgray.right
  16 + = time_ago_in_words(event.created_at)
  17 + ago.
  18 + .clearfix
app/views/events/_event_issue.html.haml
1 = image_tag gravatar_icon(event.author_email), class: "avatar" 1 = image_tag gravatar_icon(event.author_email), class: "avatar"
2 -%strong #{event.author_name}  
3 -%span.event_label{class: event.action_name}= event.action_name  
4 -issue  
5 -= link_to project_issue_path(event.project, event.issue) do  
6 - %strong= truncate event.issue_title  
7 -at  
8 -%strong= link_to event.project.name, event.project  
9 -%span.cgray  
10 - = time_ago_in_words(event.created_at)  
11 - ago. 2 +.event-title
  3 + %strong.author_name #{event.author_name}
  4 + %span.event_label{class: event.action_name} #{event.action_name} issue
  5 + = link_to project_issue_path(event.project, event.issue) do
  6 + %strong= truncate event.issue_title
  7 + at
  8 + %strong= link_to event.project.name, event.project
app/views/events/_event_last_push.html.haml
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 .event_lp 2 .event_lp
3 %div 3 %div
4 = image_tag gravatar_icon(event.author_email), class: "avatar" 4 = image_tag gravatar_icon(event.author_email), class: "avatar"
5 - %span Your pushed to 5 + %span You pushed to
6 = event.ref_type 6 = event.ref_type
7 = link_to project_commits_path(event.project, ref: event.ref_name) do 7 = link_to project_commits_path(event.project, ref: event.ref_name) do
8 %strong= truncate(event.ref_name, length: 28) 8 %strong= truncate(event.ref_name, length: 28)
app/views/events/_event_membership_changed.html.haml
1 = image_tag gravatar_icon(event.author_email), class: "avatar" 1 = image_tag gravatar_icon(event.author_email), class: "avatar"
2 -%strong #{event.author_name}  
3 -%span.event_label{class: event.action_name}= event.action_name  
4 -project  
5 -%strong= link_to event.project_name, event.project  
6 -%span.cgray  
7 - = time_ago_in_words(event.created_at)  
8 - ago. 2 +.event-title
  3 + %strong.author_name #{event.author_name}
  4 + %span.event_label{class: event.action_name} #{event.action_name} project
  5 + %strong= link_to event.project_name, event.project
  6 + %span.cgray
  7 + = time_ago_in_words(event.created_at)
  8 + ago.
9 9
app/views/events/_event_merge_request.html.haml
1 - if event.action_name == "merged" 1 - if event.action_name == "merged"
2 .event_icon= image_tag "event_mr_merged.png" 2 .event_icon= image_tag "event_mr_merged.png"
3 = image_tag gravatar_icon(event.author_email), class: "avatar" 3 = image_tag gravatar_icon(event.author_email), class: "avatar"
4 -%strong #{event.author_name}  
5 -%span.event_label{class: event.action_name}= event.action_name  
6 -merge request  
7 -= link_to project_merge_request_path(event.project, event.merge_request) do  
8 - %strong= truncate event.merge_request_title  
9 -at  
10 -%strong= link_to event.project.name, event.project  
11 -%span.cgray  
12 - = time_ago_in_words(event.created_at)  
13 - ago.  
14 -%br  
15 -%span= event.merge_request.source_branch  
16 -&rarr;  
17 -%span= event.merge_request.target_branch 4 +.event-title
  5 + %strong.author_name #{event.author_name}
  6 + %span.event_label{class: event.action_name} #{event.action_name} merge request
  7 + = link_to project_merge_request_path(event.project, event.merge_request) do
  8 + %strong= truncate event.merge_request_title
  9 + at
  10 + %strong= link_to event.project.name, event.project
  11 +.event-body
  12 + .event-info
  13 + %span= event.merge_request.source_branch
  14 + &rarr;
  15 + %span= event.merge_request.target_branch
18 16
app/views/events/_event_push.html.haml
1 %div 1 %div
2 .event_icon= image_tag "event_push.png" 2 .event_icon= image_tag "event_push.png"
3 = image_tag gravatar_icon(event.author_email), class: "avatar" 3 = image_tag gravatar_icon(event.author_email), class: "avatar"
4 - %strong #{event.author_name}  
5 - %span.event_label.pushed= event.push_action_name  
6 - = event.ref_type  
7 - = link_to project_commits_path(event.project, ref: event.ref_name) do  
8 - %strong= event.ref_name  
9 - at  
10 - %strong= link_to event.project.name, event.project  
11 - %span.cgray  
12 - = time_ago_in_words(event.created_at)  
13 - ago. 4 +
  5 + .event-title
  6 + %strong.author_name #{event.author_name}
  7 + %span.event_label.pushed #{event.push_action_name} #{event.ref_type}
  8 + = link_to project_commits_path(event.project, ref: event.ref_name) do
  9 + %strong= event.ref_name
  10 + at
  11 + %strong= link_to event.project.name, event.project
14 12
15 - if event.push_with_commits? 13 - if event.push_with_commits?
16 - - if event.commits_count > 1  
17 - = link_to compare_project_commits_path(event.project, from: event.parent_commit.id, to: event.last_commit.id) do  
18 - %strong #{event.parent_commit.id[0..7]}...#{event.last_commit.id[0..7]}  
19 - project = event.project 14 - project = event.project
20 - %ul.unstyled.event_commits  
21 - - if event.commits_count > 3  
22 - - event.commits[0...2].each do |commit|  
23 - = render "events/commit", commit: commit, project: project  
24 - %li  
25 - %br  
26 - \... and #{event.commits_count - 2} more commits  
27 - - else  
28 - - event.commits.each do |commit| 15 + .event-body
  16 + %ul.unstyled.event_commits
  17 + - few_commits = event.commits[0...2]
  18 + - few_commits.each do |commit|
29 = render "events/commit", commit: commit, project: project 19 = render "events/commit", commit: commit, project: project
30 20
  21 + %li.commits-stat
  22 + - if event.commits_count > 2
  23 + %span ... and #{event.commits_count - 2} more commits.
  24 + = link_to compare_project_commits_path(event.project, from: event.parent_commit.id, to: event.last_commit.id) do
  25 + %strong Compare &rarr; #{event.parent_commit.id[0..7]}...#{event.last_commit.id[0..7]}
  26 + .clearfix
app/views/help/api.html.haml
1 -%h3 API 1 +%h3.page_title API
2 .back_link 2 .back_link
3 = link_to help_path do 3 = link_to help_path do
4 &larr; to index 4 &larr; to index
5 -%hr 5 +%br
6 6
7 -%ol 7 +%ul.nav.nav-tabs.log-tabs
  8 + %li.active
  9 + = link_to "README", "#README", 'data-toggle' => 'tab'
8 %li 10 %li
9 - %a{href: "#README"} README 11 + = link_to "Projects", "#projects", 'data-toggle' => 'tab'
10 %li 12 %li
11 - %a{href: "#projects"} Projects 13 + = link_to "Snippets", "#snippets", 'data-toggle' => 'tab'
12 %li 14 %li
13 - %a{href: "#snippets"} Snippets 15 + = link_to "Repositories", "#repositories", 'data-toggle' => 'tab'
14 %li 16 %li
15 - %a{href: "#users"} Users 17 + = link_to "Users", "#users", 'data-toggle' => 'tab'
16 %li 18 %li
17 - %a{href: "#issues"} Issues 19 + = link_to "Session", "#session", 'data-toggle' => 'tab'
18 %li 20 %li
19 - %a{href: "#milestones"} Milestones  
20 -  
21 -.file_holder#README  
22 - .file_title  
23 - %i.icon-file  
24 - README  
25 - .file_content.wiki  
26 - = preserve do  
27 - = markdown File.read(Rails.root.join("doc", "api", "README.md"))  
28 -  
29 -%br  
30 -  
31 -.file_holder#projects  
32 - .file_title  
33 - %i.icon-file  
34 - Projects  
35 - .file_content.wiki  
36 - = preserve do  
37 - = markdown File.read(Rails.root.join("doc", "api", "projects.md")) 21 + = link_to "Issues", "#issues", 'data-toggle' => 'tab'
  22 + %li
  23 + = link_to "Milestones", "#milestones", 'data-toggle' => 'tab'
38 24
39 -%br 25 +.tab-content
  26 + .tab-pane.active#README
  27 + .file_holder
  28 + .file_title
  29 + %i.icon-file
  30 + README
  31 + .file_content.wiki
  32 + = preserve do
  33 + = markdown File.read(Rails.root.join("doc", "api", "README.md"))
40 34
41 -.file_holder#snippets  
42 - .file_title  
43 - %i.icon-file  
44 - Projects Snippets  
45 - .file_content.wiki  
46 - = preserve do  
47 - = markdown File.read(Rails.root.join("doc", "api", "snippets.md")) 35 + .tab-pane#projects
  36 + .file_holder
  37 + .file_title
  38 + %i.icon-file
  39 + Projects
  40 + .file_content.wiki
  41 + = preserve do
  42 + = markdown File.read(Rails.root.join("doc", "api", "projects.md"))
48 43
49 -%br 44 + .tab-pane#snippets
  45 + .file_holder
  46 + .file_title
  47 + %i.icon-file
  48 + Projects Snippets
  49 + .file_content.wiki
  50 + = preserve do
  51 + = markdown File.read(Rails.root.join("doc", "api", "snippets.md"))
50 52
51 -.file_holder#users  
52 - .file_title  
53 - %i.icon-file  
54 - Users  
55 - .file_content.wiki  
56 - = preserve do  
57 - = markdown File.read(Rails.root.join("doc", "api", "users.md")) 53 + .tab-pane#repositories
  54 + .file_holder
  55 + .file_title
  56 + %i.icon-file
  57 + Projects
  58 + .file_content.wiki
  59 + = preserve do
  60 + = markdown File.read(Rails.root.join("doc", "api", "repositories.md"))
58 61
59 -%br 62 + .tab-pane#users
  63 + .file_holder
  64 + .file_title
  65 + %i.icon-file
  66 + Users
  67 + .file_content.wiki
  68 + = preserve do
  69 + = markdown File.read(Rails.root.join("doc", "api", "users.md"))
60 70
61 -.file_holder#issues  
62 - .file_title  
63 - %i.icon-file  
64 - Issues  
65 - .file_content.wiki  
66 - = preserve do  
67 - = markdown File.read(Rails.root.join("doc", "api", "issues.md")) 71 + .tab-pane#session
  72 + .file_holder
  73 + .file_title
  74 + %i.icon-file
  75 + Session
  76 + .file_content.wiki
  77 + = preserve do
  78 + = markdown File.read(Rails.root.join("doc", "api", "session.md"))
68 79
69 -%br 80 + .tab-pane#issues
  81 + .file_holder
  82 + .file_title
  83 + %i.icon-file
  84 + Issues
  85 + .file_content.wiki
  86 + = preserve do
  87 + = markdown File.read(Rails.root.join("doc", "api", "issues.md"))
70 88
71 -.file_holder#milestones  
72 - .file_title  
73 - %i.icon-file  
74 - Milestones  
75 - .file_content.wiki  
76 - = preserve do  
77 - = markdown File.read(Rails.root.join("doc", "api", "milestones.md")) 89 + .tab-pane#milestones
  90 + .file_holder
  91 + .file_title
  92 + %i.icon-file
  93 + Milestones
  94 + .file_content.wiki
  95 + = preserve do
  96 + = markdown File.read(Rails.root.join("doc", "api", "milestones.md"))
app/views/notes/_common_form.html.haml
@@ -37,3 +37,14 @@ @@ -37,3 +37,14 @@
37 = f.file_field :attachment, class: "input-file" 37 = f.file_field :attachment, class: "input-file"
38 %span.hint Any file less than 10 MB 38 %span.hint Any file less than 10 MB
39 39
  40 +:javascript
  41 + $(function(){
  42 + var names = #{@project.users.pluck(:name)}, emoji = ['+1', '-1'];
  43 + var emoji = $.map(emoji, function(value, i) {return {key:value + ':', name:value}});
  44 + $('#note_note').
  45 + atWho('@', { data: names }).
  46 + atWho(':', {
  47 + data: emoji,
  48 + tpl: "<li data-value='${key}'>${name} #{escape_javascript image_tag('emoji/${name}.png', :size => '20x20')}</li>"
  49 + });
  50 + });
app/views/refs/_tree.html.haml
@@ -43,7 +43,11 @@ @@ -43,7 +43,11 @@
43 %i.icon-file 43 %i.icon-file
44 = content.name 44 = content.name
45 .file_content.wiki 45 .file_content.wiki
46 - = raw GitHub::Markup.render(content.name, content.data) 46 + - if gitlab_markdown?(content.name)
  47 + = preserve do
  48 + = markdown(content.data)
  49 + - else
  50 + = raw GitHub::Markup.render(content.name, content.data)
47 51
48 :javascript 52 :javascript
49 $(function(){ 53 $(function(){
app/views/refs/_tree_file.html.haml
@@ -9,7 +9,11 @@ @@ -9,7 +9,11 @@
9 = link_to "history", project_commits_path(@project, path: params[:path], ref: @ref), class: "btn very_small" 9 = link_to "history", project_commits_path(@project, path: params[:path], ref: @ref), class: "btn very_small"
10 = link_to "blame", blame_file_project_ref_path(@project, @ref, path: params[:path]), class: "btn very_small" 10 = link_to "blame", blame_file_project_ref_path(@project, @ref, path: params[:path]), class: "btn very_small"
11 - if file.text? 11 - if file.text?
12 - - if markup?(name) 12 + - if gitlab_markdown?(name)
  13 + .file_content.wiki
  14 + = preserve do
  15 + = markdown(file.data)
  16 + - elsif markup?(name)
13 .file_content.wiki 17 .file_content.wiki
14 = raw GitHub::Markup.render(name, file.data) 18 = raw GitHub::Markup.render(name, file.data)
15 - else 19 - else
config/initializers/4_resque.rb 0 → 100644
@@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
  1 +# Custom Redis configuration
  2 +rails_root = ENV['RAILS_ROOT'] || File.dirname(__FILE__) + '/../..'
  3 +rails_env = ENV['RAILS_ENV'] || 'development'
  4 +config_file = File.join(rails_root, 'config', 'resque.yml')
  5 +
  6 +if File.exists?(config_file)
  7 + resque_config = YAML.load_file(config_file)
  8 + Resque.redis = resque_config[rails_env]
  9 +end
  10 +
  11 +# Queues
  12 +Resque.watch_queue(PostReceive.instance_variable_get("@queue"))
  13 +
  14 +# Authentication
  15 +require 'resque/server'
  16 +class Authentication
  17 + def initialize(app)
  18 + @app = app
  19 + end
  20 +
  21 + def call(env)
  22 + account = env['warden'].authenticate!(:database_authenticatable, :rememberable, scope: :user)
  23 + raise "Access denied" if !account.admin?
  24 + @app.call(env)
  25 + end
  26 +end
  27 +
  28 +Resque::Server.use Authentication
  29 +
  30 +# Mailer
  31 +Resque::Mailer.excluded_environments = []
config/initializers/4_resque_queues.rb
@@ -1 +0,0 @@ @@ -1 +0,0 @@
1 -Resque.watch_queue(PostReceive.instance_variable_get("@queue"))  
config/initializers/resque.rb
@@ -1,8 +0,0 @@ @@ -1,8 +0,0 @@
1 -rails_root = ENV['RAILS_ROOT'] || File.dirname(__FILE__) + '/../..'  
2 -rails_env = ENV['RAILS_ENV'] || 'development'  
3 -config_file = File.join(rails_root, 'config', 'resque.yml')  
4 -  
5 -if File.exists?(config_file)  
6 - resque_config = YAML.load_file(config_file)  
7 - Resque.redis = resque_config[rails_env]  
8 -end  
config/initializers/resque_authentication.rb
@@ -1,14 +0,0 @@ @@ -1,14 +0,0 @@
1 -require 'resque/server'  
2 -class Authentication  
3 - def initialize(app)  
4 - @app = app  
5 - end  
6 -  
7 - def call(env)  
8 - account = env['warden'].authenticate!(:database_authenticatable, :rememberable, scope: :user)  
9 - raise "Access denied" if !account.admin?  
10 - @app.call(env)  
11 - end  
12 -end  
13 -  
14 -Resque::Server.use Authentication  
15 \ No newline at end of file 0 \ No newline at end of file
config/initializers/resque_mailer.rb
@@ -1 +0,0 @@ @@ -1 +0,0 @@
1 -Resque::Mailer.excluded_environments = []  
config/unicorn.rb.example
@@ -6,7 +6,7 @@ working_directory app_dir @@ -6,7 +6,7 @@ working_directory app_dir
6 # worker spawn times 6 # worker spawn times
7 preload_app true 7 preload_app true
8 8
9 -# nuke workers after 60 seconds (the default) 9 +# nuke workers after 30 seconds (60 is the default)
10 timeout 30 10 timeout 30
11 11
12 # listen on a Unix domain socket and/or a TCP port, 12 # listen on a Unix domain socket and/or a TCP port,
doc/api/README.md
@@ -30,8 +30,9 @@ When listing resources you can pass the following parameters: @@ -30,8 +30,9 @@ When listing resources you can pass the following parameters:
30 ## Contents 30 ## Contents
31 31
32 + [Users](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/users.md) 32 + [Users](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/users.md)
  33 ++ [Session](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/session.md)
33 + [Projects](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/projects.md) 34 + [Projects](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/projects.md)
34 + [Snippets](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/snippets.md) 35 + [Snippets](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/snippets.md)
  36 ++ [Repositories](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/repositories.md)
35 + [Issues](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/issues.md) 37 + [Issues](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/issues.md)
36 + [Milestones](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/milestones.md) 38 + [Milestones](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/milestones.md)
37 -+ [SSH Keys](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/keys.md)  
doc/api/keys.md
@@ -1,79 +0,0 @@ @@ -1,79 +0,0 @@
1 -## List keys  
2 -  
3 -Get a list of currently authenticated user's keys.  
4 -  
5 -```  
6 -GET /keys  
7 -```  
8 -  
9 -```json  
10 -[  
11 - {  
12 - "id": 1,  
13 - "title" : "Public key"  
14 - "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4  
15 - 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4  
16 - soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",  
17 - },  
18 - {  
19 - "id": 3,  
20 - "title" : "Another Public key"  
21 - "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4  
22 - 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4  
23 - soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="  
24 - }  
25 -]  
26 -```  
27 -  
28 -## Single key  
29 -  
30 -Get a single key.  
31 -  
32 -```  
33 -GET /keys/:id  
34 -```  
35 -  
36 -Parameters:  
37 -  
38 -+ `id` (required) - The ID of a key  
39 -  
40 -```json  
41 -{  
42 - "id": 1,  
43 - "title" : "Public key"  
44 - "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4  
45 - 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4  
46 - soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="  
47 -}  
48 -```  
49 -## Add key  
50 -  
51 -Create new key owned by currently authenticated user  
52 -  
53 -```  
54 -POST /keys  
55 -```  
56 -  
57 -Parameters:  
58 -  
59 -+ `title` (required) - new SSH Key's title  
60 -+ `key` (required) - new SSH key  
61 -  
62 -Will return created key with status `201 Created` on success, or `404 Not  
63 -found` on fail.  
64 -  
65 -## Delete key  
66 -  
67 -Delete key owned by currently authenticated user  
68 -  
69 -```  
70 -DELETE /keys/:id  
71 -```  
72 -  
73 -Parameters:  
74 -  
75 -+ `id` (required) - key ID  
76 -  
77 -Will return `200 OK` on success, or `404 Not Found` on fail.  
78 -  
79 -  
doc/api/projects.md
@@ -102,7 +102,7 @@ Parameters: @@ -102,7 +102,7 @@ Parameters:
102 + `name` (required) - new project name 102 + `name` (required) - new project name
103 + `code` (optional) - new project code, uses project name if not set 103 + `code` (optional) - new project code, uses project name if not set
104 + `path` (optional) - new project path, uses project name if not set 104 + `path` (optional) - new project path, uses project name if not set
105 -+ `description (optional) - short project description 105 ++ `description` (optional) - short project description
106 + `default_branch` (optional) - 'master' by default 106 + `default_branch` (optional) - 'master' by default
107 + `issues_enabled` (optional) - enabled by default 107 + `issues_enabled` (optional) - enabled by default
108 + `wall_enabled` (optional) - enabled by default 108 + `wall_enabled` (optional) - enabled by default
@@ -112,66 +112,89 @@ Parameters: @@ -112,66 +112,89 @@ Parameters:
112 Will return created project with status `201 Created` on success, or `404 Not 112 Will return created project with status `201 Created` on success, or `404 Not
113 found` on fail. 113 found` on fail.
114 114
115 -## Get project users 115 +## List project team members
116 116
117 -Get users and access roles for existing project 117 +Get a list of project team members.
118 118
119 ``` 119 ```
120 -GET /projects/:id/users 120 +GET /projects/:id/members
121 ``` 121 ```
122 122
123 Parameters: 123 Parameters:
124 124
125 + `id` (required) - The ID or code name of a project 125 + `id` (required) - The ID or code name of a project
126 126
127 -Will return users and their access roles with status `200 OK` on success, or `404 Not found` on fail. 127 +## Get project team member
128 128
129 -## Add project users 129 +Get a project team member.
130 130
131 -Add users to exiting project 131 +```
  132 +GET /projects/:id/members/:user_id
  133 +```
  134 +
  135 +Parameters:
  136 +
  137 ++ `id` (required) - The ID or code name of a project
  138 ++ `user_id` (required) - The ID of a user
132 139
  140 +```json
  141 +{
  142 +
  143 + "id": 1,
  144 + "email": "john@example.com",
  145 + "name": "John Smith",
  146 + "blocked": false,
  147 + "created_at": "2012-05-23T08:00:58Z",
  148 + "access_level": 40
  149 +}
133 ``` 150 ```
134 -POST /projects/:id/users 151 +
  152 +## Add project team member
  153 +
  154 +Add a user to a project team.
  155 +
  156 +```
  157 +POST /projects/:id/members
135 ``` 158 ```
136 159
137 Parameters: 160 Parameters:
138 161
139 + `id` (required) - The ID or code name of a project 162 + `id` (required) - The ID or code name of a project
140 -+ `user_ids` (required) - The ID list of users to add  
141 -+ `project_access` (required) - Project access level 163 ++ `user_id` (required) - The ID of a user to add
  164 ++ `access_level` (required) - Project access level
142 165
143 Will return status `201 Created` on success, or `404 Not found` on fail. 166 Will return status `201 Created` on success, or `404 Not found` on fail.
144 167
145 -## Update project users access level 168 +## Edit project team member
146 169
147 -Update existing users to specified access level 170 +Update project team member to specified access level.
148 171
149 ``` 172 ```
150 -PUT /projects/:id/users 173 +PUT /projects/:id/members/:user_id
151 ``` 174 ```
152 175
153 Parameters: 176 Parameters:
154 177
155 + `id` (required) - The ID or code name of a project 178 + `id` (required) - The ID or code name of a project
156 -+ `user_ids` (required) - The ID list of users to add  
157 -+ `project_access` (required) - Project access level 179 ++ `user_id` (required) - The ID of a team member
  180 ++ `access_level` (required) - Project access level
158 181
159 Will return status `200 OK` on success, or `404 Not found` on fail. 182 Will return status `200 OK` on success, or `404 Not found` on fail.
160 183
161 -## Delete project users 184 +## Remove project team member
162 185
163 -Delete users from exiting project 186 +Removes user from project team.
164 187
165 ``` 188 ```
166 -DELETE /projects/:id/users 189 +DELETE /projects/:id/members/:user_id
167 ``` 190 ```
168 191
169 Parameters: 192 Parameters:
170 193
171 + `id` (required) - The ID or code name of a project 194 + `id` (required) - The ID or code name of a project
172 -+ `user_ids` (required) - The ID list of users to add 195 ++ `user_id` (required) - The ID of a team member
173 196
174 -Will return status `200 OK` on success, or `404 Not found` on fail. 197 +Status code `200` will be returned on success.
175 198
176 ## Get project hooks 199 ## Get project hooks
177 200
@@ -216,135 +239,3 @@ Parameters: @@ -216,135 +239,3 @@ Parameters:
216 + `hook_id` (required) - The ID of hook to delete 239 + `hook_id` (required) - The ID of hook to delete
217 240
218 Will return status `200 OK` on success, or `404 Not found` on fail. 241 Will return status `200 OK` on success, or `404 Not found` on fail.
219 -  
220 -## Project repository branches  
221 -  
222 -Get a list of repository branches from a project, sorted by name alphabetically.  
223 -  
224 -```  
225 -GET /projects/:id/repository/branches  
226 -```  
227 -  
228 -Parameters:  
229 -  
230 -+ `id` (required) - The ID or code name of a project  
231 -  
232 -```json  
233 -[  
234 - {  
235 - "name": "master",  
236 - "commit": {  
237 - "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",  
238 - "parents": [  
239 - {  
240 - "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"  
241 - }  
242 - ],  
243 - "tree": "46e82de44b1061621357f24c05515327f2795a95",  
244 - "message": "add projects API",  
245 - "author": {  
246 - "name": "John Smith",  
247 - "email": "john@example.com"  
248 - },  
249 - "committer": {  
250 - "name": "John Smith",  
251 - "email": "john@example.com"  
252 - },  
253 - "authored_date": "2012-06-27T05:51:39-07:00",  
254 - "committed_date": "2012-06-28T03:44:20-07:00"  
255 - }  
256 - }  
257 -]  
258 -```  
259 -  
260 -Get a single project repository branch.  
261 -  
262 -```  
263 -GET /projects/:id/repository/branches/:branch  
264 -```  
265 -  
266 -Parameters:  
267 -  
268 -+ `id` (required) - The ID or code name of a project  
269 -+ `branch` (required) - The name of the branch  
270 -  
271 -```json  
272 -{  
273 - "name": "master",  
274 - "commit": {  
275 - "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",  
276 - "parents": [  
277 - {  
278 - "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"  
279 - }  
280 - ],  
281 - "tree": "46e82de44b1061621357f24c05515327f2795a95",  
282 - "message": "add projects API",  
283 - "author": {  
284 - "name": "John Smith",  
285 - "email": "john@example.com"  
286 - },  
287 - "committer": {  
288 - "name": "John Smith",  
289 - "email": "john@example.com"  
290 - },  
291 - "authored_date": "2012-06-27T05:51:39-07:00",  
292 - "committed_date": "2012-06-28T03:44:20-07:00"  
293 - }  
294 -}  
295 -```  
296 -  
297 -## Project repository tags  
298 -  
299 -Get a list of repository tags from a project, sorted by name in reverse alphabetical order.  
300 -  
301 -```  
302 -GET /projects/:id/repository/tags  
303 -```  
304 -  
305 -Parameters:  
306 -  
307 -+ `id` (required) - The ID or code name of a project  
308 -  
309 -```json  
310 -[  
311 - {  
312 - "name": "v1.0.0",  
313 - "commit": {  
314 - "id": "2695effb5807a22ff3d138d593fd856244e155e7",  
315 - "parents": [  
316 -  
317 - ],  
318 - "tree": "38017f2f189336fe4497e9d230c5bb1bf873f08d",  
319 - "message": "Initial commit",  
320 - "author": {  
321 - "name": "John Smith",  
322 - "email": "john@example.com"  
323 - },  
324 - "committer": {  
325 - "name": "Jack Smith",  
326 - "email": "jack@example.com"  
327 - },  
328 - "authored_date": "2012-05-28T04:42:42-07:00",  
329 - "committed_date": "2012-05-28T04:42:42-07:00"  
330 - }  
331 - }  
332 -]  
333 -```  
334 -  
335 -## Raw blob content  
336 -  
337 -Get the raw file contents for a file.  
338 -  
339 -```  
340 -GET /projects/:id/repository/commits/:sha/blob  
341 -```  
342 -  
343 -Parameters:  
344 -  
345 -+ `id` (required) - The ID or code name of a project  
346 -+ `sha` (required) - The commit or branch name  
347 -+ `filepath` (required) - The path the file  
348 -  
349 -Will return the raw file contents.  
350 -  
doc/api/repositories.md 0 → 100644
@@ -0,0 +1,166 @@ @@ -0,0 +1,166 @@
  1 +## Project repository branches
  2 +
  3 +Get a list of repository branches from a project, sorted by name alphabetically.
  4 +
  5 +```
  6 +GET /projects/:id/repository/branches
  7 +```
  8 +
  9 +Parameters:
  10 +
  11 ++ `id` (required) - The ID or code name of a project
  12 +
  13 +```json
  14 +[
  15 + {
  16 + "name": "master",
  17 + "commit": {
  18 + "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
  19 + "parents": [
  20 + {
  21 + "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
  22 + }
  23 + ],
  24 + "tree": "46e82de44b1061621357f24c05515327f2795a95",
  25 + "message": "add projects API",
  26 + "author": {
  27 + "name": "John Smith",
  28 + "email": "john@example.com"
  29 + },
  30 + "committer": {
  31 + "name": "John Smith",
  32 + "email": "john@example.com"
  33 + },
  34 + "authored_date": "2012-06-27T05:51:39-07:00",
  35 + "committed_date": "2012-06-28T03:44:20-07:00"
  36 + }
  37 + }
  38 +]
  39 +```
  40 +
  41 +## Project repository branch
  42 +
  43 +Get a single project repository branch.
  44 +
  45 +```
  46 +GET /projects/:id/repository/branches/:branch
  47 +```
  48 +
  49 +Parameters:
  50 +
  51 ++ `id` (required) - The ID or code name of a project
  52 ++ `branch` (required) - The name of the branch
  53 +
  54 +```json
  55 +{
  56 + "name": "master",
  57 + "commit": {
  58 + "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
  59 + "parents": [
  60 + {
  61 + "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
  62 + }
  63 + ],
  64 + "tree": "46e82de44b1061621357f24c05515327f2795a95",
  65 + "message": "add projects API",
  66 + "author": {
  67 + "name": "John Smith",
  68 + "email": "john@example.com"
  69 + },
  70 + "committer": {
  71 + "name": "John Smith",
  72 + "email": "john@example.com"
  73 + },
  74 + "authored_date": "2012-06-27T05:51:39-07:00",
  75 + "committed_date": "2012-06-28T03:44:20-07:00"
  76 + }
  77 +}
  78 +```
  79 +
  80 +## Project repository tags
  81 +
  82 +Get a list of repository tags from a project, sorted by name in reverse alphabetical order.
  83 +
  84 +```
  85 +GET /projects/:id/repository/tags
  86 +```
  87 +
  88 +Parameters:
  89 +
  90 ++ `id` (required) - The ID or code name of a project
  91 +
  92 +```json
  93 +[
  94 + {
  95 + "name": "v1.0.0",
  96 + "commit": {
  97 + "id": "2695effb5807a22ff3d138d593fd856244e155e7",
  98 + "parents": [
  99 +
  100 + ],
  101 + "tree": "38017f2f189336fe4497e9d230c5bb1bf873f08d",
  102 + "message": "Initial commit",
  103 + "author": {
  104 + "name": "John Smith",
  105 + "email": "john@example.com"
  106 + },
  107 + "committer": {
  108 + "name": "Jack Smith",
  109 + "email": "jack@example.com"
  110 + },
  111 + "authored_date": "2012-05-28T04:42:42-07:00",
  112 + "committed_date": "2012-05-28T04:42:42-07:00"
  113 + }
  114 + }
  115 +]
  116 +```
  117 +
  118 +## Project repository commits
  119 +
  120 +Get a list of repository commits in a project.
  121 +
  122 +```
  123 +GET /projects/:id/repository/commits
  124 +```
  125 +
  126 +Parameters:
  127 +
  128 ++ `id` (required) - The ID or code name of a project
  129 ++ `ref_name` (optional) - The name of a repository branch or tag
  130 +
  131 +```json
  132 +[
  133 + {
  134 + "id": "ed899a2f4b50b4370feeea94676502b42383c746",
  135 + "short_id": "ed899a2f4b5",
  136 + "title": "Replace sanitize with escape once",
  137 + "author_name": "Dmitriy Zaporozhets",
  138 + "author_email": "dzaporozhets@sphereconsultinginc.com",
  139 + "created_at": "2012-09-20T11:50:22+03:00"
  140 + },
  141 + {
  142 + "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6",
  143 + "short_id": "6104942438c",
  144 + "title": "Sanitize for network graph",
  145 + "author_name": "randx",
  146 + "author_email": "dmitriy.zaporozhets@gmail.com",
  147 + "created_at": "2012-09-20T09:06:12+03:00"
  148 + }
  149 +]
  150 +```
  151 +
  152 +## Raw blob content
  153 +
  154 +Get the raw file contents for a file.
  155 +
  156 +```
  157 +GET /projects/:id/repository/commits/:sha/blob
  158 +```
  159 +
  160 +Parameters:
  161 +
  162 ++ `id` (required) - The ID or code name of a project
  163 ++ `sha` (required) - The commit or branch name
  164 ++ `filepath` (required) - The path the file
  165 +
  166 +Will return the raw file contents.
doc/api/session.md 0 → 100644
@@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
  1 +Login to get private token
  2 +
  3 +```
  4 +POST /session
  5 +```
  6 +
  7 +Parameters:
  8 +
  9 ++ `email` (required) - The email of user
  10 ++ `password` (required) - Valid password
  11 +
  12 +
  13 +```json
  14 +{
  15 + "id": 1,
  16 + "email": "john@example.com",
  17 + "name": "John Smith",
  18 + "private_token": "dd34asd13as",
  19 + "created_at": "2012-05-23T08:00:58Z",
  20 + "blocked": true
  21 +}
  22 +```
doc/api/users.md
@@ -88,3 +88,81 @@ GET /user @@ -88,3 +88,81 @@ GET /user
88 "theme_id": 1 88 "theme_id": 1
89 } 89 }
90 ``` 90 ```
  91 +
  92 +## List SSH keys
  93 +
  94 +Get a list of currently authenticated user's SSH keys.
  95 +
  96 +```
  97 +GET /user/keys
  98 +```
  99 +
  100 +```json
  101 +[
  102 + {
  103 + "id": 1,
  104 + "title" : "Public key"
  105 + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
  106 + 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
  107 + soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
  108 + },
  109 + {
  110 + "id": 3,
  111 + "title" : "Another Public key"
  112 + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
  113 + 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
  114 + soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
  115 + }
  116 +]
  117 +```
  118 +
  119 +## Single SSH key
  120 +
  121 +Get a single key.
  122 +
  123 +```
  124 +GET /user/keys/:id
  125 +```
  126 +
  127 +Parameters:
  128 +
  129 ++ `id` (required) - The ID of an SSH key
  130 +
  131 +```json
  132 +{
  133 + "id": 1,
  134 + "title" : "Public key"
  135 + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
  136 + 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
  137 + soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
  138 +}
  139 +```
  140 +## Add SSH key
  141 +
  142 +Create new key owned by currently authenticated user
  143 +
  144 +```
  145 +POST /user/keys
  146 +```
  147 +
  148 +Parameters:
  149 +
  150 ++ `title` (required) - new SSH Key's title
  151 ++ `key` (required) - new SSH key
  152 +
  153 +Will return created key with status `201 Created` on success, or `404 Not
  154 +found` on fail.
  155 +
  156 +## Delete SSH key
  157 +
  158 +Delete key owned by currently authenticated user
  159 +
  160 +```
  161 +DELETE /user/keys/:id
  162 +```
  163 +
  164 +Parameters:
  165 +
  166 ++ `id` (required) - SSH key ID
  167 +
  168 +Will return `200 OK` on success, or `404 Not Found` on fail.
features/steps/dashboard/dashboard.rb
@@ -16,7 +16,7 @@ class Dashboard &lt; Spinach::FeatureSteps @@ -16,7 +16,7 @@ class Dashboard &lt; Spinach::FeatureSteps
16 end 16 end
17 17
18 Then 'I should see last push widget' do 18 Then 'I should see last push widget' do
19 - page.should have_content "Your pushed to branch new_design" 19 + page.should have_content "You pushed to branch new_design"
20 page.should have_link "Create Merge Request" 20 page.should have_link "Create Merge Request"
21 end 21 end
22 22
@@ -17,6 +17,6 @@ module Gitlab @@ -17,6 +17,6 @@ module Gitlab
17 mount Projects 17 mount Projects
18 mount Issues 18 mount Issues
19 mount Milestones 19 mount Milestones
20 - mount Keys 20 + mount Session
21 end 21 end
22 end 22 end
lib/api/entities.rb
@@ -9,6 +9,10 @@ module Gitlab @@ -9,6 +9,10 @@ module Gitlab
9 expose :id, :email, :name, :blocked, :created_at 9 expose :id, :email, :name, :blocked, :created_at
10 end 10 end
11 11
  12 + class UserLogin < UserBasic
  13 + expose :private_token
  14 + end
  15 +
12 class Hook < Grape::Entity 16 class Hook < Grape::Entity
13 expose :id, :url 17 expose :id, :url
14 end 18 end
@@ -20,15 +24,20 @@ module Gitlab @@ -20,15 +24,20 @@ module Gitlab
20 expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at 24 expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at
21 end 25 end
22 26
23 - class UsersProject < Grape::Entity  
24 - expose :user, using: Entities::UserBasic  
25 - expose :project_access 27 + class ProjectMember < UserBasic
  28 + expose :project_access, :as => :access_level do |user, options|
  29 + options[:project].users_projects.find_by_user_id(user.id).project_access
  30 + end
26 end 31 end
27 32
28 class RepoObject < Grape::Entity 33 class RepoObject < Grape::Entity
29 expose :name, :commit 34 expose :name, :commit
30 end 35 end
31 36
  37 + class RepoCommit < Grape::Entity
  38 + expose :id, :short_id, :title, :author_name, :author_email, :created_at
  39 + end
  40 +
32 class ProjectSnippet < Grape::Entity 41 class ProjectSnippet < Grape::Entity
33 expose :id, :title, :file_name 42 expose :id, :title, :file_name
34 expose :author, using: Entities::UserBasic 43 expose :author, using: Entities::UserBasic
@@ -36,7 +45,9 @@ module Gitlab @@ -36,7 +45,9 @@ module Gitlab
36 end 45 end
37 46
38 class Milestone < Grape::Entity 47 class Milestone < Grape::Entity
39 - expose :id, :title, :description, :due_date, :closed, :updated_at, :created_at 48 + expose :id
  49 + expose (:project_id) {|milestone| milestone.project.id}
  50 + expose :title, :description, :due_date, :closed, :updated_at, :created_at
40 end 51 end
41 52
42 class Issue < Grape::Entity 53 class Issue < Grape::Entity
@@ -49,10 +60,8 @@ module Gitlab @@ -49,10 +60,8 @@ module Gitlab
49 expose :closed, :updated_at, :created_at 60 expose :closed, :updated_at, :created_at
50 end 61 end
51 62
52 - class Key < Grape::Entity  
53 - expose :id,  
54 - :title,  
55 - :key 63 + class SSHKey < Grape::Entity
  64 + expose :id, :title, :key
56 end 65 end
57 end 66 end
58 end 67 end
lib/api/helpers.rb
@@ -61,7 +61,7 @@ module Gitlab @@ -61,7 +61,7 @@ module Gitlab
61 error!({'message' => message}, status) 61 error!({'message' => message}, status)
62 end 62 end
63 63
64 - private 64 + private
65 65
66 def abilities 66 def abilities
67 @abilities ||= begin 67 @abilities ||= begin
lib/api/keys.rb
@@ -1,50 +0,0 @@ @@ -1,50 +0,0 @@
1 -module Gitlab  
2 - # Keys API  
3 - class Keys < Grape::API  
4 - before { authenticate! }  
5 - resource :keys do  
6 - # Get currently authenticated user's keys  
7 - #  
8 - # Example Request:  
9 - # GET /keys  
10 - get do  
11 - present current_user.keys, with: Entities::Key  
12 - end  
13 - # Get single key owned by currently authenticated user  
14 - #  
15 - # Example Request:  
16 - # GET /keys/:id  
17 - get "/:id" do  
18 - key = current_user.keys.find params[:id]  
19 - present key, with: Entities::Key  
20 - end  
21 - # Add new ssh key to currently authenticated user  
22 - #  
23 - # Parameters:  
24 - # key (required) - New SSH Key  
25 - # title (required) - New SSH Key's title  
26 - # Example Request:  
27 - # POST /keys  
28 - post do  
29 - attrs = attributes_for_keys [:title, :key]  
30 - key = current_user.keys.new attrs  
31 - if key.save  
32 - present key, with: Entities::Key  
33 - else  
34 - not_found!  
35 - end  
36 - end  
37 - # Delete existed ssh key of currently authenticated user  
38 - #  
39 - # Parameters:  
40 - # id (required) - SSH Key ID  
41 - # Example Request:  
42 - # DELETE /keys/:id  
43 - delete "/:id" do  
44 - key = current_user.keys.find params[:id]  
45 - key.delete  
46 - end  
47 - end  
48 - end  
49 -end  
50 -  
lib/api/milestones.rb
@@ -11,6 +11,8 @@ module Gitlab @@ -11,6 +11,8 @@ module Gitlab
11 # Example Request: 11 # Example Request:
12 # GET /projects/:id/milestones 12 # GET /projects/:id/milestones
13 get ":id/milestones" do 13 get ":id/milestones" do
  14 + authorize! :read_milestone, user_project
  15 +
14 present paginate(user_project.milestones), with: Entities::Milestone 16 present paginate(user_project.milestones), with: Entities::Milestone
15 end 17 end
16 18
@@ -22,6 +24,8 @@ module Gitlab @@ -22,6 +24,8 @@ module Gitlab
22 # Example Request: 24 # Example Request:
23 # GET /projects/:id/milestones/:milestone_id 25 # GET /projects/:id/milestones/:milestone_id
24 get ":id/milestones/:milestone_id" do 26 get ":id/milestones/:milestone_id" do
  27 + authorize! :read_milestone, user_project
  28 +
25 @milestone = user_project.milestones.find(params[:milestone_id]) 29 @milestone = user_project.milestones.find(params[:milestone_id])
26 present @milestone, with: Entities::Milestone 30 present @milestone, with: Entities::Milestone
27 end 31 end
@@ -36,6 +40,8 @@ module Gitlab @@ -36,6 +40,8 @@ module Gitlab
36 # Example Request: 40 # Example Request:
37 # POST /projects/:id/milestones 41 # POST /projects/:id/milestones
38 post ":id/milestones" do 42 post ":id/milestones" do
  43 + authorize! :admin_milestone, user_project
  44 +
39 attrs = attributes_for_keys [:title, :description, :due_date] 45 attrs = attributes_for_keys [:title, :description, :due_date]
40 @milestone = user_project.milestones.new attrs 46 @milestone = user_project.milestones.new attrs
41 if @milestone.save 47 if @milestone.save
lib/api/projects.rb
@@ -40,14 +40,14 @@ module Gitlab @@ -40,14 +40,14 @@ module Gitlab
40 post do 40 post do
41 params[:code] ||= params[:name] 41 params[:code] ||= params[:name]
42 params[:path] ||= params[:name] 42 params[:path] ||= params[:name]
43 - attrs = attributes_for_keys [:code,  
44 - :path,  
45 - :name,  
46 - :description,  
47 - :default_branch,  
48 - :issues_enabled,  
49 - :wall_enabled,  
50 - :merge_requests_enabled, 43 + attrs = attributes_for_keys [:code,
  44 + :path,
  45 + :name,
  46 + :description,
  47 + :default_branch,
  48 + :issues_enabled,
  49 + :wall_enabled,
  50 + :merge_requests_enabled,
51 :wiki_enabled] 51 :wiki_enabled]
52 @project = Project.create_by_user(attrs, current_user) 52 @project = Project.create_by_user(attrs, current_user)
53 if @project.saved? 53 if @project.saved?
@@ -57,56 +57,83 @@ module Gitlab @@ -57,56 +57,83 @@ module Gitlab
57 end 57 end
58 end 58 end
59 59
60 - # Get project users 60 + # Get a project team members
61 # 61 #
62 # Parameters: 62 # Parameters:
63 # id (required) - The ID or code name of a project 63 # id (required) - The ID or code name of a project
64 # Example Request: 64 # Example Request:
65 - # GET /projects/:id/users  
66 - get ":id/users" do  
67 - @users_projects = paginate user_project.users_projects  
68 - present @users_projects, with: Entities::UsersProject 65 + # GET /projects/:id/members
  66 + get ":id/members" do
  67 + @members = paginate user_project.users
  68 + present @members, with: Entities::ProjectMember, project: user_project
69 end 69 end
70 70
71 - # Add users to project with specified access level 71 + # Get a project team members
72 # 72 #
73 # Parameters: 73 # Parameters:
74 # id (required) - The ID or code name of a project 74 # id (required) - The ID or code name of a project
75 - # user_ids (required) - The ID list of users to add  
76 - # project_access (required) - Project access level 75 + # user_id (required) - The ID of a user
77 # Example Request: 76 # Example Request:
78 - # POST /projects/:id/users  
79 - post ":id/users" do 77 + # GET /projects/:id/members/:user_id
  78 + get ":id/members/:user_id" do
  79 + @member = user_project.users.find params[:user_id]
  80 + present @member, with: Entities::ProjectMember, project: user_project
  81 + end
  82 +
  83 + # Add a new project team member
  84 + #
  85 + # Parameters:
  86 + # id (required) - The ID or code name of a project
  87 + # user_id (required) - The ID of a user
  88 + # access_level (required) - Project access level
  89 + # Example Request:
  90 + # POST /projects/:id/members
  91 + post ":id/members" do
80 authorize! :admin_project, user_project 92 authorize! :admin_project, user_project
81 - user_project.add_users_ids_to_team(params[:user_ids].values, params[:project_access])  
82 - nil 93 + users_project = user_project.users_projects.new(
  94 + user_id: params[:user_id],
  95 + project_access: params[:access_level]
  96 + )
  97 +
  98 + if users_project.save
  99 + @member = users_project.user
  100 + present @member, with: Entities::ProjectMember, project: user_project
  101 + else
  102 + not_found!
  103 + end
83 end 104 end
84 105
85 - # Update users to specified access level 106 + # Update project team member
86 # 107 #
87 # Parameters: 108 # Parameters:
88 # id (required) - The ID or code name of a project 109 # id (required) - The ID or code name of a project
89 - # user_ids (required) - The ID list of users to add  
90 - # project_access (required) - New project access level to 110 + # user_id (required) - The ID of a team member
  111 + # access_level (required) - Project access level
91 # Example Request: 112 # Example Request:
92 - # PUT /projects/:id/add_users  
93 - put ":id/users" do 113 + # PUT /projects/:id/members/:user_id
  114 + put ":id/members/:user_id" do
94 authorize! :admin_project, user_project 115 authorize! :admin_project, user_project
95 - user_project.update_users_ids_to_role(params[:user_ids].values, params[:project_access])  
96 - nil 116 + users_project = user_project.users_projects.find_by_user_id params[:user_id]
  117 +
  118 + if users_project.update_attributes(project_access: params[:access_level])
  119 + @member = users_project.user
  120 + present @member, with: Entities::ProjectMember, project: user_project
  121 + else
  122 + not_found!
  123 + end
97 end 124 end
98 125
99 - # Delete project users 126 + # Remove a team member from project
100 # 127 #
101 # Parameters: 128 # Parameters:
102 # id (required) - The ID or code name of a project 129 # id (required) - The ID or code name of a project
103 - # user_ids (required) - The ID list of users to delete 130 + # user_id (required) - The ID of a team member
104 # Example Request: 131 # Example Request:
105 - # DELETE /projects/:id/users  
106 - delete ":id/users" do 132 + # DELETE /projects/:id/members/:user_id
  133 + delete ":id/members/:user_id" do
107 authorize! :admin_project, user_project 134 authorize! :admin_project, user_project
108 - user_project.delete_users_ids_from_team(params[:user_ids].values)  
109 - nil 135 + users_project = user_project.users_projects.find_by_user_id params[:user_id]
  136 + users_project.destroy
110 end 137 end
111 138
112 # Get project hooks 139 # Get project hooks
@@ -184,6 +211,24 @@ module Gitlab @@ -184,6 +211,24 @@ module Gitlab
184 present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject 211 present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject
185 end 212 end
186 213
  214 + # Get a project repository commits
  215 + #
  216 + # Parameters:
  217 + # id (required) - The ID or code name of a project
  218 + # ref_name (optional) - The name of a repository branch or tag
  219 + # Example Request:
  220 + # GET /projects/:id/repository/commits
  221 + get ":id/repository/commits" do
  222 + authorize! :download_code, user_project
  223 +
  224 + page = params[:page] || 0
  225 + per_page = params[:per_page] || 20
  226 + ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
  227 +
  228 + commits = user_project.commits(ref, nil, per_page, page * per_page)
  229 + present CommitDecorator.decorate(commits), with: Entities::RepoCommit
  230 + end
  231 +
187 # Get a project snippet 232 # Get a project snippet
188 # 233 #
189 # Parameters: 234 # Parameters:
@@ -207,6 +252,8 @@ module Gitlab @@ -207,6 +252,8 @@ module Gitlab
207 # Example Request: 252 # Example Request:
208 # POST /projects/:id/snippets 253 # POST /projects/:id/snippets
209 post ":id/snippets" do 254 post ":id/snippets" do
  255 + authorize! :write_snippet, user_project
  256 +
210 attrs = attributes_for_keys [:title, :file_name] 257 attrs = attributes_for_keys [:title, :file_name]
211 attrs[:expires_at] = params[:lifetime] if params[:lifetime].present? 258 attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
212 attrs[:content] = params[:code] if params[:code].present? 259 attrs[:content] = params[:code] if params[:code].present?
@@ -282,6 +329,8 @@ module Gitlab @@ -282,6 +329,8 @@ module Gitlab
282 # Example Request: 329 # Example Request:
283 # GET /projects/:id/repository/commits/:sha/blob 330 # GET /projects/:id/repository/commits/:sha/blob
284 get ":id/repository/commits/:sha/blob" do 331 get ":id/repository/commits/:sha/blob" do
  332 + authorize! :download_code, user_project
  333 +
285 ref = params[:sha] 334 ref = params[:sha]
286 335
287 commit = user_project.commit ref 336 commit = user_project.commit ref
lib/api/session.rb 0 → 100644
@@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
  1 +module Gitlab
  2 + # Users API
  3 + class Session < Grape::API
  4 + # Login to get token
  5 + #
  6 + # Example Request:
  7 + # POST /session
  8 + post "/session" do
  9 + resource = User.find_for_database_authentication(email: params[:email])
  10 +
  11 + return unauthorized! unless resource
  12 +
  13 + if resource.valid_password?(params[:password])
  14 + present resource, with: Entities::UserLogin
  15 + else
  16 + unauthorized!
  17 + end
  18 + end
  19 + end
  20 +end
lib/api/users.rb
@@ -25,12 +25,59 @@ module Gitlab @@ -25,12 +25,59 @@ module Gitlab
25 end 25 end
26 end 26 end
27 27
28 - # Get currently authenticated user  
29 - #  
30 - # Example Request:  
31 - # GET /user  
32 - get "/user" do  
33 - present @current_user, with: Entities::User 28 + resource :user do
  29 + # Get currently authenticated user
  30 + #
  31 + # Example Request:
  32 + # GET /user
  33 + get do
  34 + present @current_user, with: Entities::User
  35 + end
  36 +
  37 + # Get currently authenticated user's keys
  38 + #
  39 + # Example Request:
  40 + # GET /user/keys
  41 + get "keys" do
  42 + present current_user.keys, with: Entities::SSHKey
  43 + end
  44 +
  45 + # Get single key owned by currently authenticated user
  46 + #
  47 + # Example Request:
  48 + # GET /user/keys/:id
  49 + get "keys/:id" do
  50 + key = current_user.keys.find params[:id]
  51 + present key, with: Entities::SSHKey
  52 + end
  53 +
  54 + # Add new ssh key to currently authenticated user
  55 + #
  56 + # Parameters:
  57 + # key (required) - New SSH Key
  58 + # title (required) - New SSH Key's title
  59 + # Example Request:
  60 + # POST /user/keys
  61 + post "keys" do
  62 + attrs = attributes_for_keys [:title, :key]
  63 + key = current_user.keys.new attrs
  64 + if key.save
  65 + present key, with: Entities::SSHKey
  66 + else
  67 + not_found!
  68 + end
  69 + end
  70 +
  71 + # Delete existed ssh key of currently authenticated user
  72 + #
  73 + # Parameters:
  74 + # id (required) - SSH Key ID
  75 + # Example Request:
  76 + # DELETE /user/keys/:id
  77 + delete "keys/:id" do
  78 + key = current_user.keys.find params[:id]
  79 + key.delete
  80 + end
34 end 81 end
35 end 82 end
36 end 83 end
lib/gitlab/graph_commit.rb
@@ -5,7 +5,7 @@ module Gitlab @@ -5,7 +5,7 @@ module Gitlab
5 attr_accessor :time, :space 5 attr_accessor :time, :space
6 attr_accessor :refs 6 attr_accessor :refs
7 7
8 - include ActionView::Helpers::SanitizeHelper 8 + include ActionView::Helpers::TagHelper
9 9
10 def self.to_graph(project) 10 def self.to_graph(project)
11 @repo = project.repo 11 @repo = project.repo
@@ -166,7 +166,7 @@ module Gitlab @@ -166,7 +166,7 @@ module Gitlab
166 h[:refs] = refs.collect{|r|r.name}.join(" ") unless refs.nil? 166 h[:refs] = refs.collect{|r|r.name}.join(" ") unless refs.nil?
167 h[:id] = sha 167 h[:id] = sha
168 h[:date] = date 168 h[:date] = date
169 - h[:message] = sanitize(Gitlab::Encode.utf8(message)) 169 + h[:message] = escape_once(Gitlab::Encode.utf8(message))
170 h[:login] = author.email 170 h[:login] = author.email
171 h 171 h
172 end 172 end
lib/tasks/bulk_add_permission.rake
1 -desc "Add all users to all projects, system administratos are added as masters" 1 +desc "Add all users to all projects (admin users are added as masters)"
2 task :add_users_to_project_teams => :environment do |t, args| 2 task :add_users_to_project_teams => :environment do |t, args|
3 - users = User.find_all_by_admin(false, :select => 'id').map(&:id)  
4 - admins = User.find_all_by_admin(true, :select => 'id').map(&:id) 3 + user_ids = User.where(:admin => false).pluck(:id)
  4 + admin_ids = User.where(:admin => true).pluck(:id)
5 5
6 - users.each do |user|  
7 - puts "#{user}"  
8 - end  
9 -  
10 - Project.all.each do |project|  
11 - puts "Importing #{users.length} users into #{project.path}"  
12 - UsersProject.bulk_import(project, users, UsersProject::DEVELOPER)  
13 - puts "Importing #{admins.length} admins into #{project.path}"  
14 - UsersProject.bulk_import(project, admins, UsersProject::MASTER) 6 + Project.find_each do |project|
  7 + puts "Importing #{user_ids.size} users into #{project.code}"
  8 + UsersProject.bulk_import(project, user_ids, UsersProject::DEVELOPER)
  9 + puts "Importing #{admin_ids.size} admins into #{project.code}"
  10 + UsersProject.bulk_import(project, admin_ids, UsersProject::MASTER)
15 end 11 end
16 end 12 end
17 13
18 desc "Add user to as a developer to all projects" 14 desc "Add user to as a developer to all projects"
19 task :add_user_to_project_teams, [:email] => :environment do |t, args| 15 task :add_user_to_project_teams, [:email] => :environment do |t, args|
20 - user_email = args.email  
21 - user = User.find_by_email(user_email)  
22 -  
23 - project_ids = Project.all.map(&:id) 16 + user = User.find_by_email args.email
  17 + project_ids = Project.pluck(:id)
24 18
25 - UsersProject.user_bulk_import(user,project_ids,UsersProject::DEVELOPER) 19 + UsersProject.user_bulk_import(user, project_ids, UsersProject::DEVELOPER)
26 end 20 end
lib/tasks/bulk_import.rake
1 -  
2 desc "Imports existing Git repos from a directory into new projects in git_base_path" 1 desc "Imports existing Git repos from a directory into new projects in git_base_path"
3 task :import_projects, [:directory,:email] => :environment do |t, args| 2 task :import_projects, [:directory,:email] => :environment do |t, args|
4 - user_email = args.email  
5 - import_directory = args.directory 3 + user_email, import_directory = args.email, args.directory
6 repos_to_import = Dir.glob("#{import_directory}/*") 4 repos_to_import = Dir.glob("#{import_directory}/*")
7 git_base_path = Gitlab.config.git_base_path 5 git_base_path = Gitlab.config.git_base_path
8 - puts "Found #{repos_to_import.length} repos to import" 6 + imported_count, skipped_count, failed_count = 0
  7 +
  8 + puts "Found #{repos_to_import.size} repos to import"
9 9
10 - imported_count = 0  
11 - skipped_count = 0  
12 - failed_count = 0  
13 repos_to_import.each do |repo_path| 10 repos_to_import.each do |repo_path|
14 repo_name = File.basename repo_path 11 repo_name = File.basename repo_path
  12 + clone_path = "#{git_base_path}#{repo_name}.git"
15 13
16 puts " Processing #{repo_name}" 14 puts " Processing #{repo_name}"
17 - clone_path = "#{git_base_path}#{repo_name}.git"  
18 15
19 if Dir.exists? clone_path 16 if Dir.exists? clone_path
20 if Project.find_by_code(repo_name) 17 if Project.find_by_code(repo_name)
@@ -38,7 +35,6 @@ task :import_projects, [:directory,:email] =&gt; :environment do |t, args| @@ -38,7 +35,6 @@ task :import_projects, [:directory,:email] =&gt; :environment do |t, args|
38 else 35 else
39 failed_count += 1 36 failed_count += 1
40 end 37 end
41 -  
42 end 38 end
43 39
44 puts "Finished importing #{imported_count} projects (skipped #{skipped_count}, failed #{failed_count})." 40 puts "Finished importing #{imported_count} projects (skipped #{skipped_count}, failed #{failed_count})."
@@ -49,63 +45,39 @@ def clone_bare_repo_as_git(existing_path, new_path) @@ -49,63 +45,39 @@ def clone_bare_repo_as_git(existing_path, new_path)
49 git_user = Gitlab.config.ssh_user 45 git_user = Gitlab.config.ssh_user
50 begin 46 begin
51 sh "sudo -u #{git_user} -i git clone --bare '#{existing_path}' #{new_path}" 47 sh "sudo -u #{git_user} -i git clone --bare '#{existing_path}' #{new_path}"
52 - true  
53 - rescue Exception=> msg  
54 - puts " ERROR: Faild to clone #{existing_path} to #{new_path}"  
55 - puts " Make sure #{git_user} can reach #{existing_path}"  
56 - puts " Exception-MSG: #{msg}"  
57 - false 48 + rescue Exception => msg
  49 + puts " ERROR: Failed to clone #{existing_path} to #{new_path}"
  50 + puts " Make sure #{git_user} can reach #{existing_path}"
  51 + puts " Exception-MSG: #{msg}"
58 end 52 end
59 end 53 end
60 54
61 -# Creats a project in Gitlag given a @project_name@ to use (for name, web url, and code  
62 -# url) and a @user_email@ that will be assigned as the owner of the project. 55 +# Creates a project in GitLab given a `project_name` to use
  56 +# (for name, web url, and code url) and a `user_email` that will be
  57 +# assigned as the owner of the project.
63 def create_repo_project(project_name, user_email) 58 def create_repo_project(project_name, user_email)
64 - user = User.find_by_email(user_email)  
65 - if user 59 + if user = User.find_by_email(user_email)
66 # Using find_by_code since that's the most important identifer to be unique 60 # Using find_by_code since that's the most important identifer to be unique
67 if Project.find_by_code(project_name) 61 if Project.find_by_code(project_name)
68 puts " INFO: Project #{project_name} already exists in Gitlab, skipping." 62 puts " INFO: Project #{project_name} already exists in Gitlab, skipping."
69 - false  
70 else 63 else
71 - project = nil  
72 - if Project.find_by_code(project_name)  
73 - puts " ERROR: Project already exists #{project_name}"  
74 - return false  
75 - project = Project.find_by_code(project_name)  
76 - else  
77 - project = Project.create(  
78 - name: project_name,  
79 - code: project_name,  
80 - path: project_name,  
81 - owner: user,  
82 - description: "Automatically created from Rake on #{Time.now.to_s}"  
83 - )  
84 - end  
85 -  
86 - unless project.valid?  
87 - puts " ERROR: Failed to create project #{project} because #{project.errors.first}"  
88 - return false  
89 - end  
90 -  
91 - # Add user as admin for project  
92 - project.users_projects.create!(  
93 - :project_access => UsersProject::MASTER,  
94 - :user => user 64 + project = Project.create(
  65 + name: project_name,
  66 + code: project_name,
  67 + path: project_name,
  68 + owner: user,
  69 + description: "Automatically created from 'import_projects' rake task on #{Time.now}"
95 ) 70 )
96 71
97 - # Per projects_controller.rb#37  
98 - project.update_repository  
99 -  
100 if project.valid? 72 if project.valid?
101 - true 73 + # Add user as admin for project
  74 + project.users_projects.create!(:project_access => UsersProject::MASTER, :user => user)
  75 + project.update_repository
102 else 76 else
103 puts " ERROR: Failed to create project #{project} because #{project.errors.first}" 77 puts " ERROR: Failed to create project #{project} because #{project.errors.first}"
104 - false  
105 end 78 end
106 end 79 end
107 else 80 else
108 - puts " ERROR: #{user_email} not found, skipping"  
109 - false 81 + puts " ERROR: user with #{user_email} not found, skipping"
110 end 82 end
111 end 83 end
lib/tasks/gitlab/backup.rake
@@ -2,22 +2,20 @@ require &#39;active_record/fixtures&#39; @@ -2,22 +2,20 @@ require &#39;active_record/fixtures&#39;
2 2
3 namespace :gitlab do 3 namespace :gitlab do
4 namespace :app do 4 namespace :app do
5 -  
6 - # Create backup of gitlab system  
7 - desc "GITLAB | Create a backup of the gitlab system" 5 + # Create backup of GitLab system
  6 + desc "GITLAB | Create a backup of the GitLab system"
8 task :backup_create => :environment do 7 task :backup_create => :environment do
9 -  
10 Rake::Task["gitlab:app:db_dump"].invoke 8 Rake::Task["gitlab:app:db_dump"].invoke
11 Rake::Task["gitlab:app:repo_dump"].invoke 9 Rake::Task["gitlab:app:repo_dump"].invoke
12 10
13 Dir.chdir(Gitlab.config.backup_path) 11 Dir.chdir(Gitlab.config.backup_path)
14 12
15 # saving additional informations 13 # saving additional informations
16 - s = Hash.new  
17 - s["db_version"] = "#{ActiveRecord::Migrator.current_version}"  
18 - s["backup_created_at"] = "#{Time.now}"  
19 - s["gitlab_version"] = %x{git rev-parse HEAD}.gsub(/\n/,"")  
20 - s["tar_version"] = %x{tar --version | head -1}.gsub(/\n/,"") 14 + s = {}
  15 + s[:db_version] = "#{ActiveRecord::Migrator.current_version}"
  16 + s[:backup_created_at] = "#{Time.now}"
  17 + s[:gitlab_version] = %x{git rev-parse HEAD}.gsub(/\n/,"")
  18 + s[:tar_version] = %x{tar --version | head -1}.gsub(/\n/,"")
21 19
22 File.open("#{Gitlab.config.backup_path}/backup_information.yml", "w+") do |file| 20 File.open("#{Gitlab.config.backup_path}/backup_information.yml", "w+") do |file|
23 file << s.to_yaml.gsub(/^---\n/,'') 21 file << s.to_yaml.gsub(/^---\n/,'')
@@ -32,7 +30,7 @@ namespace :gitlab do @@ -32,7 +30,7 @@ namespace :gitlab do
32 end 30 end
33 31
34 # cleanup: remove tmp files 32 # cleanup: remove tmp files
35 - print "Deletion of tmp directories..." 33 + print "Deleting tmp directories..."
36 if Kernel.system("rm -rf repositories/ db/ backup_information.yml") 34 if Kernel.system("rm -rf repositories/ db/ backup_information.yml")
37 puts "[DONE]".green 35 puts "[DONE]".green
38 else 36 else
@@ -52,26 +50,23 @@ namespace :gitlab do @@ -52,26 +50,23 @@ namespace :gitlab do
52 else 50 else
53 puts "[SKIPPING]".yellow 51 puts "[SKIPPING]".yellow
54 end 52 end
55 -  
56 end 53 end
57 54
58 -  
59 - # Restore backup of gitlab system 55 + # Restore backup of GitLab system
60 desc "GITLAB | Restore a previously created backup" 56 desc "GITLAB | Restore a previously created backup"
61 task :backup_restore => :environment do 57 task :backup_restore => :environment do
62 -  
63 Dir.chdir(Gitlab.config.backup_path) 58 Dir.chdir(Gitlab.config.backup_path)
64 59
65 # check for existing backups in the backup dir 60 # check for existing backups in the backup dir
66 file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i } 61 file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i }
67 - puts "no backup found" if file_list.count == 0 62 + puts "no backups found" if file_list.count == 0
68 if file_list.count > 1 && ENV["BACKUP"].nil? 63 if file_list.count > 1 && ENV["BACKUP"].nil?
69 puts "Found more than one backup, please specify which one you want to restore:" 64 puts "Found more than one backup, please specify which one you want to restore:"
70 puts "rake gitlab:app:backup_restore BACKUP=timestamp_of_backup" 65 puts "rake gitlab:app:backup_restore BACKUP=timestamp_of_backup"
71 exit 1; 66 exit 1;
72 end 67 end
73 68
74 - tar_file = ENV["BACKUP"].nil? ? File.join(file_list.first.to_s + "_gitlab_backup.tar") : File.join(ENV["BACKUP"] + "_gitlab_backup.tar") 69 + tar_file = ENV["BACKUP"].nil? ? File.join("#{file_list.first}_gitlab_backup.tar") : File.join(ENV["BACKUP"] + "_gitlab_backup.tar")
75 70
76 unless File.exists?(tar_file) 71 unless File.exists?(tar_file)
77 puts "The specified backup doesn't exist!" 72 puts "The specified backup doesn't exist!"
@@ -102,16 +97,14 @@ namespace :gitlab do @@ -102,16 +97,14 @@ namespace :gitlab do
102 Rake::Task["gitlab:app:repo_restore"].invoke 97 Rake::Task["gitlab:app:repo_restore"].invoke
103 98
104 # cleanup: remove tmp files 99 # cleanup: remove tmp files
105 - print "Deletion of tmp directories..." 100 + print "Deleting tmp directories..."
106 if Kernel.system("rm -rf repositories/ db/ backup_information.yml") 101 if Kernel.system("rm -rf repositories/ db/ backup_information.yml")
107 puts "[DONE]".green 102 puts "[DONE]".green
108 else 103 else
109 puts "[FAILED]".red 104 puts "[FAILED]".red
110 end 105 end
111 -  
112 end 106 end
113 107
114 -  
115 ################################################################################ 108 ################################################################################
116 ################################# invoked tasks ################################ 109 ################################# invoked tasks ################################
117 110
@@ -121,7 +114,7 @@ namespace :gitlab do @@ -121,7 +114,7 @@ namespace :gitlab do
121 backup_path_repo = File.join(Gitlab.config.backup_path, "repositories") 114 backup_path_repo = File.join(Gitlab.config.backup_path, "repositories")
122 FileUtils.mkdir_p(backup_path_repo) until Dir.exists?(backup_path_repo) 115 FileUtils.mkdir_p(backup_path_repo) until Dir.exists?(backup_path_repo)
123 puts "Dumping repositories:" 116 puts "Dumping repositories:"
124 - project = Project.all.map { |n| [n.path,n.path_to_repo] } 117 + project = Project.all.map { |n| [n.path, n.path_to_repo] }
125 project << ["gitolite-admin.git", File.join(File.dirname(project.first.second), "gitolite-admin.git")] 118 project << ["gitolite-admin.git", File.join(File.dirname(project.first.second), "gitolite-admin.git")]
126 project.each do |project| 119 project.each do |project|
127 print "- Dumping repository #{project.first}... " 120 print "- Dumping repository #{project.first}... "
@@ -136,11 +129,11 @@ namespace :gitlab do @@ -136,11 +129,11 @@ namespace :gitlab do
136 task :repo_restore => :environment do 129 task :repo_restore => :environment do
137 backup_path_repo = File.join(Gitlab.config.backup_path, "repositories") 130 backup_path_repo = File.join(Gitlab.config.backup_path, "repositories")
138 puts "Restoring repositories:" 131 puts "Restoring repositories:"
139 - project = Project.all.map { |n| [n.path,n.path_to_repo] } 132 + project = Project.all.map { |n| [n.path, n.path_to_repo] }
140 project << ["gitolite-admin.git", File.join(File.dirname(project.first.second), "gitolite-admin.git")] 133 project << ["gitolite-admin.git", File.join(File.dirname(project.first.second), "gitolite-admin.git")]
141 project.each do |project| 134 project.each do |project|
142 print "- Restoring repository #{project.first}... " 135 print "- Restoring repository #{project.first}... "
143 - FileUtils.rm_rf(project.second) if File.dirname(project.second) # delet old stuff 136 + FileUtils.rm_rf(project.second) if File.dirname(project.second) # delete old stuff
144 if Kernel.system("cd #{File.dirname(project.second)} > /dev/null 2>&1 && git clone --bare #{backup_path_repo}/#{project.first}.bundle #{project.first}.git > /dev/null 2>&1") 137 if Kernel.system("cd #{File.dirname(project.second)} > /dev/null 2>&1 && git clone --bare #{backup_path_repo}/#{project.first}.bundle #{project.first}.git > /dev/null 2>&1")
145 permission_commands = [ 138 permission_commands = [
146 "sudo chmod -R g+rwX #{Gitlab.config.git_base_path}", 139 "sudo chmod -R g+rwX #{Gitlab.config.git_base_path}",
@@ -157,8 +150,9 @@ namespace :gitlab do @@ -157,8 +150,9 @@ namespace :gitlab do
157 ###################################### DB ###################################### 150 ###################################### DB ######################################
158 151
159 task :db_dump => :environment do 152 task :db_dump => :environment do
160 - backup_path_db = File.join(Gitlab.config.backup_path, "db")  
161 - FileUtils.mkdir_p(backup_path_db) until Dir.exists?(backup_path_db) 153 + backup_path_db = File.join(Gitlab.config.backup_path, "db")
  154 + FileUtils.mkdir_p(backup_path_db) unless Dir.exists?(backup_path_db)
  155 +
162 puts "Dumping database tables:" 156 puts "Dumping database tables:"
163 ActiveRecord::Base.connection.tables.each do |tbl| 157 ActiveRecord::Base.connection.tables.each do |tbl|
164 print "- Dumping table #{tbl}... " 158 print "- Dumping table #{tbl}... "
@@ -176,9 +170,11 @@ namespace :gitlab do @@ -176,9 +170,11 @@ namespace :gitlab do
176 end 170 end
177 171
178 task :db_restore=> :environment do 172 task :db_restore=> :environment do
179 - backup_path_db = File.join(Gitlab.config.backup_path, "db") 173 + backup_path_db = File.join(Gitlab.config.backup_path, "db")
  174 +
180 puts "Restoring database tables:" 175 puts "Restoring database tables:"
181 Rake::Task["db:reset"].invoke 176 Rake::Task["db:reset"].invoke
  177 +
182 Dir.glob(File.join(backup_path_db, "*.yml") ).each do |dir| 178 Dir.glob(File.join(backup_path_db, "*.yml") ).each do |dir|
183 fixture_file = File.basename(dir, ".*" ) 179 fixture_file = File.basename(dir, ".*" )
184 print "- Loading fixture #{fixture_file}..." 180 print "- Loading fixture #{fixture_file}..."
lib/tasks/gitlab/enable_automerge.rake
1 namespace :gitlab do 1 namespace :gitlab do
2 namespace :app do 2 namespace :app do
3 desc "GITLAB | Enable auto merge" 3 desc "GITLAB | Enable auto merge"
4 - task :enable_automerge => :environment do 4 + task :enable_automerge => :environment do
5 Gitlab::Gitolite.new.enable_automerge 5 Gitlab::Gitolite.new.enable_automerge
6 6
7 Project.find_each do |project| 7 Project.find_each do |project|
lib/tasks/gitlab/gitolite_rebuild.rake
1 namespace :gitlab do 1 namespace :gitlab do
2 namespace :gitolite do 2 namespace :gitolite do
3 desc "GITLAB | Rebuild each project at gitolite config" 3 desc "GITLAB | Rebuild each project at gitolite config"
4 - task :update_repos => :environment do 4 + task :update_repos => :environment do
5 puts "Starting Projects" 5 puts "Starting Projects"
6 Project.find_each(:batch_size => 100) do |project| 6 Project.find_each(:batch_size => 100) do |project|
7 - puts  
8 - puts "=== #{project.name}" 7 + puts "\n=== #{project.name}"
9 project.update_repository 8 project.update_repository
10 puts 9 puts
11 end 10 end
lib/tasks/gitlab/setup.rake
@@ -4,8 +4,7 @@ namespace :gitlab do @@ -4,8 +4,7 @@ namespace :gitlab do
4 task :setup => [ 4 task :setup => [
5 'db:setup', 5 'db:setup',
6 'db:seed_fu', 6 'db:seed_fu',
7 - 'gitlab:app:enable_automerge' 7 + 'gitlab:app:enable_automerge'
8 ] 8 ]
9 end 9 end
10 end 10 end
11 -  
lib/tasks/gitlab/status.rake
1 namespace :gitlab do 1 namespace :gitlab do
2 namespace :app do 2 namespace :app do
3 - desc "GITLAB | Check gitlab installation status" 3 + desc "GITLAB | Check GitLab installation status"
4 task :status => :environment do 4 task :status => :environment do
5 - puts "Starting diagnostic".yellow 5 + puts "Starting diagnostics".yellow
6 git_base_path = Gitlab.config.git_base_path 6 git_base_path = Gitlab.config.git_base_path
7 7
8 print "config/database.yml............" 8 print "config/database.yml............"
9 - if File.exists?(File.join Rails.root, "config", "database.yml") 9 + if File.exists?(Rails.root.join "config", "database.yml")
10 puts "exists".green 10 puts "exists".green
11 - else 11 + else
12 puts "missing".red 12 puts "missing".red
13 return 13 return
14 end 14 end
15 15
16 print "config/gitlab.yml............" 16 print "config/gitlab.yml............"
17 - if File.exists?(File.join Rails.root, "config", "gitlab.yml")  
18 - puts "exists".green 17 + if File.exists?(Rails.root.join "config", "gitlab.yml")
  18 + puts "exists".green
19 else 19 else
20 puts "missing".red 20 puts "missing".red
21 return 21 return
22 end 22 end
23 23
24 print "#{git_base_path}............" 24 print "#{git_base_path}............"
25 - if File.exists?(git_base_path)  
26 - puts "exists".green  
27 - else 25 + if File.exists?(git_base_path)
  26 + puts "exists".green
  27 + else
28 puts "missing".red 28 puts "missing".red
29 return 29 return
30 end 30 end
31 31
32 print "#{git_base_path} is writable?............" 32 print "#{git_base_path} is writable?............"
33 if File.stat(git_base_path).writable? 33 if File.stat(git_base_path).writable?
34 - puts "YES".green 34 + puts "YES".green
35 else 35 else
36 puts "NO".red 36 puts "NO".red
37 return 37 return
@@ -41,16 +41,16 @@ namespace :gitlab do @@ -41,16 +41,16 @@ namespace :gitlab do
41 `git clone #{Gitlab.config.gitolite_admin_uri} /tmp/gitolite_gitlab_test` 41 `git clone #{Gitlab.config.gitolite_admin_uri} /tmp/gitolite_gitlab_test`
42 FileUtils.rm_rf("/tmp/gitolite_gitlab_test") 42 FileUtils.rm_rf("/tmp/gitolite_gitlab_test")
43 print "Can clone gitolite-admin?............" 43 print "Can clone gitolite-admin?............"
44 - puts "YES".green  
45 - rescue 44 + puts "YES".green
  45 + rescue
46 print "Can clone gitolite-admin?............" 46 print "Can clone gitolite-admin?............"
47 puts "NO".red 47 puts "NO".red
48 return 48 return
49 end 49 end
50 50
51 print "UMASK for .gitolite.rc is 0007? ............" 51 print "UMASK for .gitolite.rc is 0007? ............"
52 - unless open("#{git_base_path}/../.gitolite.rc").grep(/UMASK([ \t]*)=([ \t>]*)0007/).empty?  
53 - puts "YES".green 52 + if open("#{git_base_path}/../.gitolite.rc").grep(/UMASK([ \t]*)=([ \t>]*)0007/).any?
  53 + puts "YES".green
54 else 54 else
55 puts "NO".red 55 puts "NO".red
56 return 56 return
@@ -69,16 +69,15 @@ namespace :gitlab do @@ -69,16 +69,15 @@ namespace :gitlab do
69 end 69 end
70 end 70 end
71 71
72 -  
73 - if Project.count > 0 72 + if Project.count > 0
74 puts "Validating projects repositories:".yellow 73 puts "Validating projects repositories:".yellow
75 Project.find_each(:batch_size => 100) do |project| 74 Project.find_each(:batch_size => 100) do |project|
76 print "#{project.name}....." 75 print "#{project.name}....."
77 - hook_file = File.join(project.path_to_repo, 'hooks','post-receive') 76 + hook_file = File.join(project.path_to_repo, 'hooks', 'post-receive')
78 77
79 unless File.exists?(hook_file) 78 unless File.exists?(hook_file)
80 - puts "post-receive file missing".red  
81 - next 79 + puts "post-receive file missing".red
  80 + return
82 end 81 end
83 82
84 puts "post-receive file ok".green 83 puts "post-receive file ok".green
lib/tasks/gitlab/write_hook.rake
@@ -4,7 +4,6 @@ namespace :gitlab do @@ -4,7 +4,6 @@ namespace :gitlab do
4 task :write_hooks => :environment do 4 task :write_hooks => :environment do
5 gitolite_hooks_path = File.join(Gitlab.config.git_hooks_path, "common") 5 gitolite_hooks_path = File.join(Gitlab.config.git_hooks_path, "common")
6 gitlab_hooks_path = Rails.root.join("lib", "hooks") 6 gitlab_hooks_path = Rails.root.join("lib", "hooks")
7 -  
8 gitlab_hook_files = ['post-receive'] 7 gitlab_hook_files = ['post-receive']
9 8
10 gitlab_hook_files.each do |file_name| 9 gitlab_hook_files.each do |file_name|
@@ -20,4 +19,3 @@ namespace :gitlab do @@ -20,4 +19,3 @@ namespace :gitlab do
20 end 19 end
21 end 20 end
22 end 21 end
23 -  
spec/helpers/tree_helper_spec.rb
@@ -2,7 +2,7 @@ require &#39;spec_helper&#39; @@ -2,7 +2,7 @@ require &#39;spec_helper&#39;
2 2
3 describe TreeHelper do 3 describe TreeHelper do
4 describe '#markup?' do 4 describe '#markup?' do
5 - %w(mdown md markdown textile rdoc org creole mediawiki rst asciidoc pod).each do |type| 5 + %w(textile rdoc org creole mediawiki rst asciidoc pod).each do |type|
6 it "returns true for #{type} files" do 6 it "returns true for #{type} files" do
7 markup?("README.#{type}").should be_true 7 markup?("README.#{type}").should be_true
8 end 8 end
spec/models/user_spec.rb
@@ -73,4 +73,30 @@ describe User do @@ -73,4 +73,30 @@ describe User do
73 user.authentication_token.should_not be_blank 73 user.authentication_token.should_not be_blank
74 end 74 end
75 end 75 end
  76 +
  77 + describe "attributes can be changed by a regular user" do
  78 + before do
  79 + @user = Factory :user
  80 + @user.update_attributes(skype: "testskype", linkedin: "testlinkedin")
  81 + end
  82 + it { @user.skype.should == 'testskype' }
  83 + it { @user.linkedin.should == 'testlinkedin' }
  84 + end
  85 +
  86 + describe "attributes that shouldn't be changed by a regular user" do
  87 + before do
  88 + @user = Factory :user
  89 + @user.update_attributes(projects_limit: 50)
  90 + end
  91 + it { @user.projects_limit.should_not == 50 }
  92 + end
  93 +
  94 + describe "attributes can be changed by an admin user" do
  95 + before do
  96 + @admin_user = Factory :admin
  97 + @admin_user.update_attributes({ skype: "testskype", projects_limit: 50 }, as: :admin)
  98 + end
  99 + it { @admin_user.skype.should == 'testskype' }
  100 + it { @admin_user.projects_limit.should == 50 }
  101 + end
76 end 102 end
spec/requests/api/projects_spec.rb
@@ -111,42 +111,52 @@ describe Gitlab::API do @@ -111,42 +111,52 @@ describe Gitlab::API do
111 end 111 end
112 end 112 end
113 113
114 - describe "GET /projects/:id/users" do  
115 - it "should return project users" do  
116 - get api("/projects/#{project.code}/users", user)  
117 - 114 + describe "GET /projects/:id/members" do
  115 + it "should return project team members" do
  116 + get api("/projects/#{project.code}/members", user)
118 response.status.should == 200 117 response.status.should == 200
119 -  
120 json_response.should be_an Array 118 json_response.should be_an Array
121 json_response.count.should == 2 119 json_response.count.should == 2
122 - json_response.first['user']['id'].should == user.id 120 + json_response.first['email'].should == user.email
123 end 121 end
124 end 122 end
125 123
126 - describe "POST /projects/:id/users" do  
127 - it "should add users to project" do  
128 - expect {  
129 - post api("/projects/#{project.code}/users", user),  
130 - user_ids: {"0" => user2.id}, project_access: UsersProject::DEVELOPER  
131 - }.to change {project.users_projects.where(:project_access => UsersProject::DEVELOPER).count}.by(1) 124 + describe "GET /projects/:id/members/:user_id" do
  125 + it "should return project team member" do
  126 + get api("/projects/#{project.code}/members/#{user.id}", user)
  127 + response.status.should == 200
  128 + json_response['email'].should == user.email
  129 + json_response['access_level'].should == UsersProject::MASTER
132 end 130 end
133 end 131 end
134 132
135 - describe "PUT /projects/:id/users" do  
136 - it "should update users to new access role" do 133 + describe "POST /projects/:id/members" do
  134 + it "should add user to project team" do
137 expect { 135 expect {
138 - put api("/projects/#{project.code}/users", user),  
139 - user_ids: {"0" => user3.id}, project_access: UsersProject::MASTER  
140 - }.to change {project.users_projects.where(:project_access => UsersProject::MASTER).count}.by(1) 136 + post api("/projects/#{project.code}/members", user), user_id: user2.id,
  137 + access_level: UsersProject::DEVELOPER
  138 + }.to change { UsersProject.count }.by(1)
  139 +
  140 + response.status.should == 201
  141 + json_response['email'].should == user2.email
  142 + json_response['access_level'].should == UsersProject::DEVELOPER
  143 + end
  144 + end
  145 +
  146 + describe "PUT /projects/:id/members/:user_id" do
  147 + it "should update project team member" do
  148 + put api("/projects/#{project.code}/members/#{user3.id}", user), access_level: UsersProject::MASTER
  149 + response.status.should == 200
  150 + json_response['email'].should == user3.email
  151 + json_response['access_level'].should == UsersProject::MASTER
141 end 152 end
142 end 153 end
143 154
144 - describe "DELETE /projects/:id/users" do  
145 - it "should delete users from project" do 155 + describe "DELETE /projects/:id/members/:user_id" do
  156 + it "should remove user from project team" do
146 expect { 157 expect {
147 - delete api("/projects/#{project.code}/users", user),  
148 - user_ids: {"0" => user3.id}  
149 - }.to change {project.users_projects.count}.by(-1) 158 + delete api("/projects/#{project.code}/members/#{user3.id}", user)
  159 + }.to change { UsersProject.count }.by(-1)
150 end 160 end
151 end 161 end
152 162
@@ -189,6 +199,27 @@ describe Gitlab::API do @@ -189,6 +199,27 @@ describe Gitlab::API do
189 end 199 end
190 end 200 end
191 201
  202 + describe "GET /projects/:id/repository/commits" do
  203 + context "authorized user" do
  204 + before { project.add_access(user2, :read) }
  205 +
  206 + it "should return project commits" do
  207 + get api("/projects/#{project.code}/repository/commits", user)
  208 + response.status.should == 200
  209 +
  210 + json_response.should be_an Array
  211 + json_response.first['id'].should == project.commit.id
  212 + end
  213 + end
  214 +
  215 + context "unauthorized user" do
  216 + it "should not return project commits" do
  217 + get api("/projects/#{project.code}/repository/commits")
  218 + response.status.should == 401
  219 + end
  220 + end
  221 + end
  222 +
192 describe "GET /projects/:id/snippets/:snippet_id" do 223 describe "GET /projects/:id/snippets/:snippet_id" do
193 it "should return a project snippet" do 224 it "should return a project snippet" do
194 get api("/projects/#{project.code}/snippets/#{snippet.id}", user) 225 get api("/projects/#{project.code}/snippets/#{snippet.id}", user)
spec/requests/api/session_spec.rb 0 → 100644
@@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Gitlab::API do
  4 + include ApiHelpers
  5 +
  6 + let(:user) { Factory :user }
  7 +
  8 + describe "POST /session" do
  9 + context "when valid password" do
  10 + it "should return private token" do
  11 + post api("/session"), email: user.email, password: '123456'
  12 + response.status.should == 201
  13 +
  14 + json_response['email'].should == user.email
  15 + json_response['private_token'].should == user.private_token
  16 + end
  17 + end
  18 +
  19 + context "when invalid password" do
  20 + it "should return authentication error" do
  21 + post api("/session"), email: user.email, password: '123'
  22 + response.status.should == 401
  23 +
  24 + json_response['email'].should be_nil
  25 + json_response['private_token'].should be_nil
  26 + end
  27 + end
  28 +
  29 + context "when empty password" do
  30 + it "should return authentication error" do
  31 + post api("/session"), email: user.email
  32 + response.status.should == 401
  33 +
  34 + json_response['email'].should be_nil
  35 + json_response['private_token'].should be_nil
  36 + end
  37 + end
  38 + end
  39 +end
spec/requests/api/ssh_keys_spec.rb
@@ -1,73 +0,0 @@ @@ -1,73 +0,0 @@
1 -require 'spec_helper'  
2 -  
3 -describe Gitlab::Keys do  
4 - include ApiHelpers  
5 - let(:user) {  
6 - user = Factory.create :user  
7 - user.reset_authentication_token!  
8 - user  
9 - }  
10 - let(:key) { Factory.create :key, { user: user}}  
11 -  
12 - describe "GET /keys" do  
13 - context "when unauthenticated" do  
14 - it "should return authentication error" do  
15 - get api("/keys")  
16 - response.status.should == 401  
17 - end  
18 - end  
19 - context "when authenticated" do  
20 - it "should return array of ssh keys" do  
21 - user.keys << key  
22 - user.save  
23 - get api("/keys", user)  
24 - response.status.should == 200  
25 - json_response.should be_an Array  
26 - json_response.first["title"].should == key.title  
27 - end  
28 - end  
29 - end  
30 -  
31 - describe "GET /keys/:id" do  
32 - it "should returm single key" do  
33 - user.keys << key  
34 - user.save  
35 - get api("/keys/#{key.id}", user)  
36 - response.status.should == 200  
37 - json_response["title"].should == key.title  
38 - end  
39 - it "should return 404 Not Found within invalid ID" do  
40 - get api("/keys/42", user)  
41 - response.status.should == 404  
42 - end  
43 - end  
44 -  
45 - describe "POST /keys" do  
46 - it "should not create invalid ssh key" do  
47 - post api("/keys", user), { title: "invalid key" }  
48 - response.status.should == 404  
49 - end  
50 - it "should create ssh key" do  
51 - key_attrs = Factory.attributes :key  
52 - expect {  
53 - post api("/keys", user), key_attrs  
54 - }.to change{ user.keys.count }.by(1)  
55 - end  
56 - end  
57 -  
58 - describe "DELETE /keys/:id" do  
59 - it "should delete existed key" do  
60 - user.keys << key  
61 - user.save  
62 - expect {  
63 - delete api("/keys/#{key.id}", user)  
64 - }.to change{user.keys.count}.by(-1)  
65 - end  
66 - it "should return 404 Not Found within invalid ID" do  
67 - delete api("/keys/42", user)  
68 - response.status.should == 404  
69 - end  
70 - end  
71 -  
72 -end  
73 -  
spec/requests/api/users_spec.rb
@@ -3,7 +3,8 @@ require &#39;spec_helper&#39; @@ -3,7 +3,8 @@ require &#39;spec_helper&#39;
3 describe Gitlab::API do 3 describe Gitlab::API do
4 include ApiHelpers 4 include ApiHelpers
5 5
6 - let(:user) { Factory :user } 6 + let(:user) { Factory :user }
  7 + let(:key) { Factory :key, user: user }
7 8
8 describe "GET /users" do 9 describe "GET /users" do
9 context "when unauthenticated" do 10 context "when unauthenticated" do
@@ -38,4 +39,68 @@ describe Gitlab::API do @@ -38,4 +39,68 @@ describe Gitlab::API do
38 json_response['email'].should == user.email 39 json_response['email'].should == user.email
39 end 40 end
40 end 41 end
  42 +
  43 + describe "GET /user/keys" do
  44 + context "when unauthenticated" do
  45 + it "should return authentication error" do
  46 + get api("/user/keys")
  47 + response.status.should == 401
  48 + end
  49 + end
  50 +
  51 + context "when authenticated" do
  52 + it "should return array of ssh keys" do
  53 + user.keys << key
  54 + user.save
  55 + get api("/user/keys", user)
  56 + response.status.should == 200
  57 + json_response.should be_an Array
  58 + json_response.first["title"].should == key.title
  59 + end
  60 + end
  61 + end
  62 +
  63 + describe "GET /user/keys/:id" do
  64 + it "should returm single key" do
  65 + user.keys << key
  66 + user.save
  67 + get api("/user/keys/#{key.id}", user)
  68 + response.status.should == 200
  69 + json_response["title"].should == key.title
  70 + end
  71 +
  72 + it "should return 404 Not Found within invalid ID" do
  73 + get api("/user/keys/42", user)
  74 + response.status.should == 404
  75 + end
  76 + end
  77 +
  78 + describe "POST /user/keys" do
  79 + it "should not create invalid ssh key" do
  80 + post api("/user/keys", user), { title: "invalid key" }
  81 + response.status.should == 404
  82 + end
  83 +
  84 + it "should create ssh key" do
  85 + key_attrs = Factory.attributes :key
  86 + expect {
  87 + post api("/user/keys", user), key_attrs
  88 + }.to change{ user.keys.count }.by(1)
  89 + end
  90 + end
  91 +
  92 + describe "DELETE /user/keys/:id" do
  93 + it "should delete existed key" do
  94 + user.keys << key
  95 + user.save
  96 + expect {
  97 + delete api("/user/keys/#{key.id}", user)
  98 + }.to change{user.keys.count}.by(-1)
  99 + end
  100 +
  101 + it "should return 404 Not Found within invalid ID" do
  102 + delete api("/user/keys/42", user)
  103 + response.status.should == 404
  104 + end
  105 + end
41 end 106 end