Commit eefb27f5ae0edf0c005eb8ce6da56cbd17c9aa8a

Authored by Sebastian Ziebell
2 parents 1b97a2ee b7ac654b

Merge branch 'master' into fixes/api

Conflicts:
	spec/requests/api/projects_spec.rb
Showing 135 changed files with 1141 additions and 884 deletions   Show diff stats
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 .rbx/ 2 .rbx/
3 db/*.sqlite3 3 db/*.sqlite3
4 db/*.sqlite3-journal 4 db/*.sqlite3-journal
5 -log/*.log 5 +log/*.log*
6 tmp/ 6 tmp/
7 .sass-cache/ 7 .sass-cache/
8 coverage/* 8 coverage/*
@@ -20,6 +20,7 @@ config/database.yml @@ -20,6 +20,7 @@ config/database.yml
20 config/initializers/omniauth.rb 20 config/initializers/omniauth.rb
21 config/unicorn.rb 21 config/unicorn.rb
22 config/resque.yml 22 config/resque.yml
  23 +config/aws.yml
23 db/data.yml 24 db/data.yml
24 .idea 25 .idea
25 .DS_Store 26 .DS_Store
@@ -70,6 +70,9 @@ gem "github-markup", "~> 0.7.4", require: 'github/markup' @@ -70,6 +70,9 @@ gem "github-markup", "~> 0.7.4", require: 'github/markup'
70 # Servers 70 # Servers
71 gem "unicorn", "~> 4.4.0" 71 gem "unicorn", "~> 4.4.0"
72 72
  73 +# State machine
  74 +gem "state_machine"
  75 +
73 # Issue tags 76 # Issue tags
74 gem "acts-as-taggable-on", "2.3.3" 77 gem "acts-as-taggable-on", "2.3.3"
75 78
@@ -425,6 +425,7 @@ GEM @@ -425,6 +425,7 @@ GEM
425 rack (~> 1.0) 425 rack (~> 1.0)
426 tilt (~> 1.1, != 1.3.0) 426 tilt (~> 1.1, != 1.3.0)
427 stamp (0.3.0) 427 stamp (0.3.0)
  428 + state_machine (1.1.2)
428 temple (0.5.5) 429 temple (0.5.5)
429 test_after_commit (0.0.1) 430 test_after_commit (0.0.1)
430 therubyracer (0.10.2) 431 therubyracer (0.10.2)
@@ -536,6 +537,7 @@ DEPENDENCIES @@ -536,6 +537,7 @@ DEPENDENCIES
536 slim 537 slim
537 spinach-rails 538 spinach-rails
538 stamp 539 stamp
  540 + state_machine
539 test_after_commit 541 test_after_commit
540 therubyracer 542 therubyracer
541 thin 543 thin
app/assets/javascripts/main.js.coffee
@@ -49,6 +49,10 @@ $ -> @@ -49,6 +49,10 @@ $ ->
49 # Bottom tooltip 49 # Bottom tooltip
50 $('.has_bottom_tooltip').tooltip(placement: 'bottom') 50 $('.has_bottom_tooltip').tooltip(placement: 'bottom')
51 51
  52 + # Form submitter
  53 + $('.trigger-submit').on 'change', ->
  54 + $(@).parents('form').submit()
  55 +
52 # Flash 56 # Flash
53 if (flash = $("#flash-container")).length > 0 57 if (flash = $("#flash-container")).length > 0
54 flash.click -> $(@).slideUp("slow") 58 flash.click -> $(@).slideUp("slow")
app/assets/javascripts/merge_requests.js.coffee
@@ -27,7 +27,7 @@ class MergeRequest @@ -27,7 +27,7 @@ class MergeRequest
27 this.$el.find(selector) 27 this.$el.find(selector)
28 28
29 initMergeWidget: -> 29 initMergeWidget: ->
30 - this.showState( @opts.current_state ) 30 + this.showState( @opts.current_status )
31 31
32 if this.$('.automerge_widget').length and @opts.check_enable 32 if this.$('.automerge_widget').length and @opts.check_enable
33 $.get @opts.url_to_automerge_check, (data) => 33 $.get @opts.url_to_automerge_check, (data) =>
app/assets/stylesheets/gitlab_bootstrap/mixins.scss
@@ -63,7 +63,7 @@ @@ -63,7 +63,7 @@
63 color: $style_color; 63 color: $style_color;
64 text-shadow: 0 1px 1px #FFF; 64 text-shadow: 0 1px 1px #FFF;
65 font-family: 'Yanone', sans-serif; 65 font-family: 'Yanone', sans-serif;
66 - font-size: 26px;  
67 - line-height: 42px; 66 + font-size: 24px;
  67 + line-height: 36px;
68 font-weight: normal; 68 font-weight: normal;
69 } 69 }
app/assets/stylesheets/sections/commits.scss
@@ -29,7 +29,7 @@ @@ -29,7 +29,7 @@
29 a{ 29 a{
30 color: $style_color; 30 color: $style_color;
31 } 31 }
32 - 32 +
33 > span { 33 > span {
34 font-family: $monospace_font; 34 font-family: $monospace_font;
35 font-size: 14px; 35 font-size: 14px;
@@ -124,7 +124,7 @@ @@ -124,7 +124,7 @@
124 .wrap{ 124 .wrap{
125 display: inline-block; 125 display: inline-block;
126 } 126 }
127 - 127 +
128 .frame { 128 .frame {
129 display: inline-block; 129 display: inline-block;
130 background-color: #fff; 130 background-color: #fff;
@@ -149,7 +149,7 @@ @@ -149,7 +149,7 @@
149 149
150 .view.swipe{ 150 .view.swipe{
151 position: relative; 151 position: relative;
152 - 152 +
153 .swipe-frame{ 153 .swipe-frame{
154 display: block; 154 display: block;
155 margin: auto; 155 margin: auto;
@@ -228,7 +228,7 @@ @@ -228,7 +228,7 @@
228 bottom: 0px; 228 bottom: 0px;
229 left: 50%; 229 left: 50%;
230 margin-left: -150px; 230 margin-left: -150px;
231 - 231 +
232 .drag-track{ 232 .drag-track{
233 display: block; 233 display: block;
234 position: absolute; 234 position: absolute;
@@ -237,7 +237,7 @@ @@ -237,7 +237,7 @@
237 width: 276px; 237 width: 276px;
238 background: url('onion_skin_sprites.gif') -4px -20px repeat-x; 238 background: url('onion_skin_sprites.gif') -4px -20px repeat-x;
239 } 239 }
240 - 240 +
241 .dragger { 241 .dragger {
242 display: block; 242 display: block;
243 position: absolute; 243 position: absolute;
@@ -248,7 +248,7 @@ @@ -248,7 +248,7 @@
248 background: url('onion_skin_sprites.gif') 0px -34px repeat-x; 248 background: url('onion_skin_sprites.gif') 0px -34px repeat-x;
249 cursor: pointer; 249 cursor: pointer;
250 } 250 }
251 - 251 +
252 .transparent { 252 .transparent {
253 display: block; 253 display: block;
254 position: absolute; 254 position: absolute;
@@ -258,7 +258,7 @@ @@ -258,7 +258,7 @@
258 width: 10px; 258 width: 10px;
259 background: url('onion_skin_sprites.gif') -2px 0px no-repeat; 259 background: url('onion_skin_sprites.gif') -2px 0px no-repeat;
260 } 260 }
261 - 261 +
262 .opaque { 262 .opaque {
263 display: block; 263 display: block;
264 position: absolute; 264 position: absolute;
@@ -275,19 +275,19 @@ @@ -275,19 +275,19 @@
275 275
276 padding: 10px; 276 padding: 10px;
277 text-align: center; 277 text-align: center;
278 - 278 +
279 background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); 279 background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
280 background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); 280 background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
281 background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); 281 background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
282 background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); 282 background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
283 - 283 +
284 ul, li{ 284 ul, li{
285 list-style: none; 285 list-style: none;
286 margin: 0; 286 margin: 0;
287 padding: 0; 287 padding: 0;
288 display: inline-block; 288 display: inline-block;
289 } 289 }
290 - 290 +
291 li{ 291 li{
292 color: grey; 292 color: grey;
293 border-left: 1px solid #c1c1c1; 293 border-left: 1px solid #c1c1c1;
@@ -322,12 +322,12 @@ @@ -322,12 +322,12 @@
322 } 322 }
323 .commit-author, .commit-committer{ 323 .commit-author, .commit-committer{
324 display: block; 324 display: block;
325 - color: #999;  
326 - font-weight: normal; 325 + color: #999;
  326 + font-weight: normal;
327 font-style: italic; 327 font-style: italic;
328 } 328 }
329 .commit-author strong, .commit-committer strong{ 329 .commit-author strong, .commit-committer strong{
330 - font-weight: bold; 330 + font-weight: bold;
331 font-style: normal; 331 font-style: normal;
332 } 332 }
333 333
@@ -337,7 +337,6 @@ @@ -337,7 +337,6 @@
337 */ 337 */
338 .commit { 338 .commit {
339 .browse_code_link_holder { 339 .browse_code_link_holder {
340 - @extend .span2;  
341 float: right; 340 float: right;
342 } 341 }
343 342
app/assets/stylesheets/sections/header.scss
@@ -5,15 +5,16 @@ @@ -5,15 +5,16 @@
5 header { 5 header {
6 &.navbar-gitlab { 6 &.navbar-gitlab {
7 .navbar-inner { 7 .navbar-inner {
8 - height: 45px;  
9 - padding: 5px; 8 + height: 40px;
  9 + padding: 3px;
10 background: #F1F1F1; 10 background: #F1F1F1;
  11 + filter: none;
11 12
12 .nav > li > a { 13 .nav > li > a {
13 color: $style_color; 14 color: $style_color;
14 text-shadow: 0 1px 0 #fff; 15 text-shadow: 0 1px 0 #fff;
15 - font-size: 18px;  
16 - padding: 12px; 16 + font-size: 16px;
  17 + padding: 10px;
17 } 18 }
18 19
19 /** NAV block with links and profile **/ 20 /** NAV block with links and profile **/
@@ -25,7 +26,6 @@ header { @@ -25,7 +26,6 @@ header {
25 } 26 }
26 27
27 z-index: 10; 28 z-index: 10;
28 - /*height: 60px;*/  
29 29
30 /** 30 /**
31 * 31 *
@@ -34,7 +34,7 @@ header { @@ -34,7 +34,7 @@ header {
34 */ 34 */
35 .app_logo { 35 .app_logo {
36 float: left; 36 float: left;
37 - margin-right: 15px; 37 + margin-right: 9px;
38 position: relative; 38 position: relative;
39 top: -5px; 39 top: -5px;
40 padding-top: 5px; 40 padding-top: 5px;
@@ -42,10 +42,10 @@ header { @@ -42,10 +42,10 @@ header {
42 a { 42 a {
43 float: left; 43 float: left;
44 padding: 0px; 44 padding: 0px;
45 - margin: 0 10px; 45 + margin: 0 6px;
46 46
47 h1 { 47 h1 {
48 - background: url('logo_dark.png') no-repeat 0px 2px; 48 + background: url('logo_dark.png') no-repeat center 1px;
49 float: left; 49 float: left;
50 height: 40px; 50 height: 40px;
51 width: 40px; 51 width: 40px;
@@ -79,7 +79,6 @@ header { @@ -79,7 +79,6 @@ header {
79 .search { 79 .search {
80 margin-right: 45px; 80 margin-right: 45px;
81 margin-left: 10px; 81 margin-left: 10px;
82 - margin-top: 2px;  
83 82
84 .search-input { 83 .search-input {
85 @extend .span2; 84 @extend .span2;
@@ -105,7 +104,7 @@ header { @@ -105,7 +104,7 @@ header {
105 .account-box { 104 .account-box {
106 position: absolute; 105 position: absolute;
107 right: 0; 106 right: 0;
108 - top: 6px; 107 + top: 4px;
109 z-index: 10000; 108 z-index: 10000;
110 width: 128px; 109 width: 128px;
111 font-size: 11px; 110 font-size: 11px;
@@ -228,6 +227,7 @@ header { @@ -228,6 +227,7 @@ header {
228 .search-input { 227 .search-input {
229 background-color: #D2D5DA; 228 background-color: #D2D5DA;
230 background-color: rgba(255, 255, 255, 0.5); 229 background-color: rgba(255, 255, 255, 0.5);
  230 + border: 1px solid #AAA;
231 231
232 &:focus { 232 &:focus {
233 background-color: white; 233 background-color: white;
@@ -240,13 +240,16 @@ header { @@ -240,13 +240,16 @@ header {
240 .app_logo { 240 .app_logo {
241 a { 241 a {
242 h1 { 242 h1 {
243 - background: url('logo_white.png') no-repeat center center; 243 + background: url('logo_white.png') no-repeat center 1px;
244 color: #fff; 244 color: #fff;
245 text-shadow: 0 1px 1px #111; 245 text-shadow: 0 1px 1px #111;
246 } 246 }
247 } 247 }
248 } 248 }
249 .project_name { 249 .project_name {
  250 + a {
  251 + color: #FFF;
  252 + }
250 color: #fff; 253 color: #fff;
251 text-shadow: 0 1px 1px #111; 254 text-shadow: 0 1px 1px #111;
252 } 255 }
@@ -261,11 +264,11 @@ header { @@ -261,11 +264,11 @@ header {
261 264
262 .separator { 265 .separator {
263 float: left; 266 float: left;
264 - height: 60px; 267 + height: 46px;
265 width: 1px; 268 width: 1px;
266 background: white; 269 background: white;
267 border-left: 1px solid #DDD; 270 border-left: 1px solid #DDD;
268 - margin-top: -10px; 271 + margin-top: -3px;
269 margin-left: 10px; 272 margin-left: 10px;
270 margin-right: 10px; 273 margin-right: 10px;
271 } 274 }
app/assets/stylesheets/sections/projects.scss
@@ -115,3 +115,7 @@ ul.nav.nav-projects-tabs { @@ -115,3 +115,7 @@ ul.nav.nav-projects-tabs {
115 } 115 }
116 } 116 }
117 } 117 }
  118 +
  119 +.team_member_row form {
  120 + margin: 0px;
  121 +}
app/assets/stylesheets/themes/ui_mars.scss
@@ -8,66 +8,27 @@ @@ -8,66 +8,27 @@
8 * 8 *
9 */ 9 */
10 .ui_mars { 10 .ui_mars {
11 -  
12 /* 11 /*
13 * Application Header 12 * Application Header
14 * 13 *
15 */ 14 */
16 header { 15 header {
17 - 16 + @extend .header-dark;
18 &.navbar-gitlab { 17 &.navbar-gitlab {
19 .navbar-inner { 18 .navbar-inner {
20 - background: #474D57 url('bg-header.png') repeat-x bottom;  
21 - border-bottom: 1px solid #444;  
22 -  
23 - .nav > li > a {  
24 - color: #eee;  
25 - text-shadow: 0 1px 0 #444; 19 + background: #474D57;
  20 + border-bottom: 1px solid #373D47;
  21 + .app_logo {
  22 + &:hover {
  23 + background-color: #373D47;
  24 + }
26 } 25 }
27 } 26 }
28 } 27 }
29 28
30 - .search {  
31 - float: right;  
32 - margin-right: 45px;  
33 - .search-input {  
34 - border: 1px solid rgba(0, 0, 0, 0.7);  
35 - background-color: #D2D5DA;  
36 - background-color: rgba(255, 255, 255, 0.5);  
37 -  
38 - &:focus {  
39 - background-color: white;  
40 - }  
41 - }  
42 - }  
43 - .search-input::-webkit-input-placeholder {  
44 - color: #666;  
45 - }  
46 - .app_logo {  
47 - a {  
48 - h1 {  
49 - background: url('logo_white.png') no-repeat center center;  
50 - color: #eee;  
51 - text-shadow: 0 1px 1px #111;  
52 - }  
53 - }  
54 - &:hover {  
55 - background-color: #41464e;  
56 - }  
57 - }  
58 - .project_name {  
59 - color: #eee;  
60 - text-shadow: 0 1px 1px #111; 29 + .separator {
  30 + background: #31363E;
  31 + border-left: 1px solid #666;
61 } 32 }
62 } 33 }
63 -  
64 - .separator {  
65 - background: #31363E;  
66 - border-left: 1px solid #666;  
67 - }  
68 -  
69 - /*  
70 - * End of Application Header  
71 - *  
72 - */  
73 } 34 }
app/contexts/merge_requests_load_context.rb
@@ -14,7 +14,7 @@ class MergeRequestsLoadContext < BaseContext @@ -14,7 +14,7 @@ class MergeRequestsLoadContext < BaseContext
14 end 14 end
15 15
16 merge_requests = merge_requests.page(params[:page]).per(20) 16 merge_requests = merge_requests.page(params[:page]).per(20)
17 - merge_requests = merge_requests.includes(:author, :project).order("closed, created_at desc") 17 + merge_requests = merge_requests.includes(:author, :project).order("state, created_at desc")
18 18
19 # Filter by specific assignee_id (or lack thereof)? 19 # Filter by specific assignee_id (or lack thereof)?
20 if params[:assignee_id].present? 20 if params[:assignee_id].present?
app/contexts/projects/create_context.rb
@@ -38,6 +38,8 @@ module Projects @@ -38,6 +38,8 @@ module Projects
38 if @project.valid? && @project.import_url.present? 38 if @project.valid? && @project.import_url.present?
39 shell = Gitlab::Shell.new 39 shell = Gitlab::Shell.new
40 if shell.import_repository(@project.path_with_namespace, @project.import_url) 40 if shell.import_repository(@project.path_with_namespace, @project.import_url)
  41 + # We should create satellite for imported repo
  42 + @project.satellite.create unless @project.satellite.exists?
41 true 43 true
42 else 44 else
43 @project.errors.add(:import_url, 'cannot clone repo') 45 @project.errors.add(:import_url, 'cannot clone repo')
app/controllers/dashboard_controller.rb
@@ -5,7 +5,7 @@ class DashboardController < ApplicationController @@ -5,7 +5,7 @@ class DashboardController < ApplicationController
5 before_filter :event_filter, only: :show 5 before_filter :event_filter, only: :show
6 6
7 def show 7 def show
8 - @groups = current_user.authorized_groups 8 + @groups = current_user.authorized_groups.sort_by(&:human_name)
9 @has_authorized_projects = @projects.count > 0 9 @has_authorized_projects = @projects.count > 0
10 @teams = current_user.authorized_teams 10 @teams = current_user.authorized_teams
11 @projects_count = @projects.count 11 @projects_count = @projects.count
app/controllers/files_controller.rb 0 → 100644
@@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
  1 +class FilesController < ApplicationController
  2 + def download
  3 + note = Note.find(params[:id])
  4 +
  5 + if can?(current_user, :read_project, note.project)
  6 + uploader = note.attachment
  7 + send_file uploader.file.path, disposition: 'attachment'
  8 + else
  9 + not_found!
  10 + end
  11 + end
  12 +end
  13 +
app/controllers/merge_requests_controller.rb
@@ -73,14 +73,14 @@ class MergeRequestsController &lt; ProjectResourceController @@ -73,14 +73,14 @@ class MergeRequestsController &lt; ProjectResourceController
73 if @merge_request.unchecked? 73 if @merge_request.unchecked?
74 @merge_request.check_if_can_be_merged 74 @merge_request.check_if_can_be_merged
75 end 75 end
76 - render json: {state: @merge_request.human_state} 76 + render json: {merge_status: @merge_request.human_merge_status}
77 rescue Gitlab::SatelliteNotExistError 77 rescue Gitlab::SatelliteNotExistError
78 - render json: {state: :no_satellite} 78 + render json: {merge_status: :no_satellite}
79 end 79 end
80 80
81 def automerge 81 def automerge
82 return access_denied! unless can?(current_user, :accept_mr, @project) 82 return access_denied! unless can?(current_user, :accept_mr, @project)
83 - if @merge_request.open? && @merge_request.can_be_merged? 83 + if @merge_request.opened? && @merge_request.can_be_merged?
84 @merge_request.should_remove_source_branch = params[:should_remove_source_branch] 84 @merge_request.should_remove_source_branch = params[:should_remove_source_branch]
85 @merge_request.automerge!(current_user) 85 @merge_request.automerge!(current_user)
86 @status = true 86 @status = true
app/controllers/milestones_controller.rb
@@ -12,7 +12,7 @@ class MilestonesController &lt; ProjectResourceController @@ -12,7 +12,7 @@ class MilestonesController &lt; ProjectResourceController
12 12
13 def index 13 def index
14 @milestones = case params[:f] 14 @milestones = case params[:f]
15 - when 'all'; @project.milestones.order("closed, due_date DESC") 15 + when 'all'; @project.milestones.order("state, due_date DESC")
16 when 'closed'; @project.milestones.closed.order("due_date DESC") 16 when 'closed'; @project.milestones.closed.order("due_date DESC")
17 else @project.milestones.active.order("due_date ASC") 17 else @project.milestones.active.order("due_date ASC")
18 end 18 end
app/controllers/profiles_controller.rb
@@ -51,7 +51,9 @@ class ProfilesController &lt; ApplicationController @@ -51,7 +51,9 @@ class ProfilesController &lt; ApplicationController
51 end 51 end
52 52
53 def update_username 53 def update_username
54 - @user.update_attributes(username: params[:user][:username]) 54 + if @user.can_change_username?
  55 + @user.update_attributes(username: params[:user][:username])
  56 + end
55 57
56 respond_to do |format| 58 respond_to do |format|
57 format.js 59 format.js
app/controllers/team_members_controller.rb
@@ -4,7 +4,11 @@ class TeamMembersController &lt; ProjectResourceController @@ -4,7 +4,11 @@ class TeamMembersController &lt; ProjectResourceController
4 before_filter :authorize_admin_project!, except: [:index, :show] 4 before_filter :authorize_admin_project!, except: [:index, :show]
5 5
6 def index 6 def index
7 - @teams = UserTeam.scoped 7 + @team = @project.users_projects.scoped
  8 + @team = @team.send(params[:type]) if %w(masters developers reporters guests).include?(params[:type])
  9 + @team = @team.sort_by(&:project_access).reverse.group_by(&:project_access)
  10 +
  11 + @assigned_teams = @project.user_team_project_relationships
8 end 12 end
9 13
10 def show 14 def show
app/controllers/teams/members_controller.rb
@@ -27,7 +27,13 @@ class Teams::MembersController &lt; Teams::ApplicationController @@ -27,7 +27,13 @@ class Teams::MembersController &lt; Teams::ApplicationController
27 end 27 end
28 28
29 def update 29 def update
30 - options = {default_projects_access: params[:default_project_access], group_admin: params[:group_admin]} 30 + member_params = params[:team_member]
  31 +
  32 + options = {
  33 + default_projects_access: member_params[:permission],
  34 + group_admin: member_params[:group_admin]
  35 + }
  36 +
31 if user_team.update_membership(team_member, options) 37 if user_team.update_membership(team_member, options)
32 redirect_to team_members_path(user_team), notice: "Membership for #{team_member.name} was successfully updated in Team of users." 38 redirect_to team_members_path(user_team), notice: "Membership for #{team_member.name} was successfully updated in Team of users."
33 else 39 else
@@ -45,5 +51,4 @@ class Teams::MembersController &lt; Teams::ApplicationController @@ -45,5 +51,4 @@ class Teams::MembersController &lt; Teams::ApplicationController
45 def team_member 51 def team_member
46 @member ||= user_team.members.find_by_username(params[:id]) 52 @member ||= user_team.members.find_by_username(params[:id])
47 end 53 end
48 -  
49 end 54 end
app/controllers/teams_controller.rb
@@ -9,13 +9,11 @@ class TeamsController &lt; ApplicationController @@ -9,13 +9,11 @@ class TeamsController &lt; ApplicationController
9 layout 'user_team', except: [:new, :create] 9 layout 'user_team', except: [:new, :create]
10 10
11 def show 11 def show
12 - user_team  
13 projects 12 projects
14 @events = Event.in_projects(user_team.project_ids).limit(20).offset(params[:offset] || 0) 13 @events = Event.in_projects(user_team.project_ids).limit(20).offset(params[:offset] || 0)
15 end 14 end
16 15
17 def edit 16 def edit
18 - user_team  
19 end 17 end
20 18
21 def update 19 def update
@@ -41,6 +39,9 @@ class TeamsController &lt; ApplicationController @@ -41,6 +39,9 @@ class TeamsController &lt; ApplicationController
41 @team.path = @team.name.dup.parameterize if @team.name 39 @team.path = @team.name.dup.parameterize if @team.name
42 40
43 if @team.save 41 if @team.save
  42 + # Add current user as Master to the team
  43 + @team.add_members([current_user.id], UsersProject::MASTER, true)
  44 +
44 redirect_to team_path(@team) 45 redirect_to team_path(@team)
45 else 46 else
46 render action: :new 47 render action: :new
app/helpers/application_helper.rb
@@ -73,8 +73,8 @@ module ApplicationHelper @@ -73,8 +73,8 @@ module ApplicationHelper
73 73
74 def search_autocomplete_source 74 def search_autocomplete_source
75 projects = current_user.authorized_projects.map { |p| { label: "project: #{p.name_with_namespace}", url: project_path(p) } } 75 projects = current_user.authorized_projects.map { |p| { label: "project: #{p.name_with_namespace}", url: project_path(p) } }
76 - groups = current_user.authorized_groups.map { |group| { label: "group: #{group.name}", url: group_path(group) } }  
77 - teams = current_user.authorized_teams.map { |team| { label: "team: #{team.name}", url: team_path(team) } } 76 + groups = current_user.authorized_groups.map { |group| { label: "group: #{simple_sanitize(group.name)}", url: group_path(group) } }
  77 + teams = current_user.authorized_teams.map { |team| { label: "team: #{simple_sanitize(team.name)}", url: team_path(team) } }
78 78
79 default_nav = [ 79 default_nav = [
80 { label: "My Profile", url: profile_path }, 80 { label: "My Profile", url: profile_path },
@@ -159,8 +159,13 @@ module ApplicationHelper @@ -159,8 +159,13 @@ module ApplicationHelper
159 alt: "Sign in with #{provider.to_s.titleize}") 159 alt: "Sign in with #{provider.to_s.titleize}")
160 end 160 end
161 161
  162 + def simple_sanitize str
  163 + sanitize(str, tags: %w(a span))
  164 + end
  165 +
162 def image_url(source) 166 def image_url(source)
163 root_url + path_to_image(source) 167 root_url + path_to_image(source)
164 end 168 end
  169 +
165 alias_method :url_to_image, :image_url 170 alias_method :url_to_image, :image_url
166 end 171 end
app/helpers/dashboard_helper.rb
@@ -27,6 +27,6 @@ module DashboardHelper @@ -27,6 +27,6 @@ module DashboardHelper
27 items.opened 27 items.opened
28 end 28 end
29 29
30 - items.where(assignee_id: current_user.id).count 30 + items.cared(current_user).count
31 end 31 end
32 end 32 end
app/helpers/issues_helper.rb
@@ -6,7 +6,7 @@ module IssuesHelper @@ -6,7 +6,7 @@ module IssuesHelper
6 6
7 def issue_css_classes issue 7 def issue_css_classes issue
8 classes = "issue" 8 classes = "issue"
9 - classes << " closed" if issue.closed 9 + classes << " closed" if issue.closed?
10 classes << " today" if issue.today? 10 classes << " today" if issue.today?
11 classes 11 classes
12 end 12 end
app/helpers/merge_requests_helper.rb
@@ -12,7 +12,7 @@ module MergeRequestsHelper @@ -12,7 +12,7 @@ module MergeRequestsHelper
12 12
13 def mr_css_classes mr 13 def mr_css_classes mr
14 classes = "merge_request" 14 classes = "merge_request"
15 - classes << " closed" if mr.closed 15 + classes << " closed" if mr.closed?
16 classes << " merged" if mr.merged? 16 classes << " merged" if mr.merged?
17 classes 17 classes
18 end 18 end
app/helpers/namespaces_helper.rb
@@ -10,8 +10,8 @@ module NamespacesHelper @@ -10,8 +10,8 @@ module NamespacesHelper
10 10
11 11
12 global_opts = ["Global", [['/', Namespace.global_id]] ] 12 global_opts = ["Global", [['/', Namespace.global_id]] ]
13 - group_opts = ["Groups", groups.map {|g| [g.human_name, g.id]} ]  
14 - users_opts = [ "Users", users.map {|u| [u.human_name, u.id]} ] 13 + group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [g.human_name, g.id]} ]
  14 + users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [u.human_name, u.id]} ]
15 15
16 options = [] 16 options = []
17 options << global_opts if current_user.admin 17 options << global_opts if current_user.admin
app/helpers/projects_helper.rb
1 module ProjectsHelper 1 module ProjectsHelper
2 - def grouper_project_members(project)  
3 - @project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access)  
4 - end  
5 -  
6 - def grouper_project_teams(project)  
7 - @project.user_team_project_relationships.sort_by(&:greatest_access).reverse.group_by(&:greatest_access)  
8 - end  
9 -  
10 def remove_from_project_team_message(project, user) 2 def remove_from_project_team_message(project, user)
11 "You are going to remove #{user.name} from #{project.name} project team. Are you sure?" 3 "You are going to remove #{user.name} from #{project.name} project team. Are you sure?"
12 end 4 end
@@ -56,7 +48,7 @@ module ProjectsHelper @@ -56,7 +48,7 @@ module ProjectsHelper
56 def project_title project 48 def project_title project
57 if project.group 49 if project.group
58 content_tag :span do 50 content_tag :span do
59 - link_to(project.group.name, group_path(project.group)) + " / " + project.name 51 + link_to(simple_sanitize(project.group.name), group_path(project.group)) + " / " + project.name
60 end 52 end
61 else 53 else
62 project.name 54 project.name
app/models/ability.rb
@@ -123,7 +123,7 @@ class Ability @@ -123,7 +123,7 @@ class Ability
123 def user_team_abilities user, team 123 def user_team_abilities user, team
124 rules = [] 124 rules = []
125 125
126 - # Only group owner and administrators can manage group 126 + # Only group owner and administrators can manage team
127 if team.owner == user || team.admin?(user) || user.admin? 127 if team.owner == user || team.admin?(user) || user.admin?
128 rules << [ :manage_user_team ] 128 rules << [ :manage_user_team ]
129 end 129 end
app/models/concerns/issuable.rb
@@ -17,10 +17,9 @@ module Issuable @@ -17,10 +17,9 @@ module Issuable
17 validates :project, presence: true 17 validates :project, presence: true
18 validates :author, presence: true 18 validates :author, presence: true
19 validates :title, presence: true, length: { within: 0..255 } 19 validates :title, presence: true, length: { within: 0..255 }
20 - validates :closed, inclusion: { in: [true, false] }  
21 20
22 - scope :opened, -> { where(closed: false) }  
23 - scope :closed, -> { where(closed: true) } 21 + scope :opened, -> { with_state(:opened) }
  22 + scope :closed, -> { with_state(:closed) }
24 scope :of_group, ->(group) { where(project_id: group.project_ids) } 23 scope :of_group, ->(group) { where(project_id: group.project_ids) }
25 scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) } 24 scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
26 scope :assigned, ->(u) { where(assignee_id: u.id)} 25 scope :assigned, ->(u) { where(assignee_id: u.id)}
@@ -62,14 +61,6 @@ module Issuable @@ -62,14 +61,6 @@ module Issuable
62 assignee_id_changed? 61 assignee_id_changed?
63 end 62 end
64 63
65 - def is_being_closed?  
66 - closed_changed? && closed  
67 - end  
68 -  
69 - def is_being_reopened?  
70 - closed_changed? && !closed  
71 - end  
72 -  
73 # 64 #
74 # Votes 65 # Votes
75 # 66 #
app/models/event.rb
@@ -130,10 +130,6 @@ class Event &lt; ActiveRecord::Base @@ -130,10 +130,6 @@ class Event &lt; ActiveRecord::Base
130 target if target_type == "MergeRequest" 130 target if target_type == "MergeRequest"
131 end 131 end
132 132
133 - def author  
134 - @author ||= User.find(author_id)  
135 - end  
136 -  
137 def action_name 133 def action_name
138 if closed? 134 if closed?
139 "closed" 135 "closed"
app/models/issue.rb
@@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
9 # project_id :integer 9 # project_id :integer
10 # created_at :datetime not null 10 # created_at :datetime not null
11 # updated_at :datetime not null 11 # updated_at :datetime not null
12 -# closed :boolean default(FALSE), not null 12 +# state :string default(FALSE), not null
13 # position :integer default(0) 13 # position :integer default(0)
14 # branch_name :string(255) 14 # branch_name :string(255)
15 # description :text 15 # description :text
@@ -19,12 +19,35 @@ @@ -19,12 +19,35 @@
19 class Issue < ActiveRecord::Base 19 class Issue < ActiveRecord::Base
20 include Issuable 20 include Issuable
21 21
22 - attr_accessible :title, :assignee_id, :closed, :position, :description,  
23 - :milestone_id, :label_list, :author_id_of_changes 22 + attr_accessible :title, :assignee_id, :position, :description,
  23 + :milestone_id, :label_list, :author_id_of_changes,
  24 + :state_event
24 25
25 acts_as_taggable_on :labels 26 acts_as_taggable_on :labels
26 27
27 - def self.open_for(user)  
28 - opened.assigned(user) 28 + class << self
  29 + def cared(user)
  30 + where('assignee_id = :user', user: user.id)
  31 + end
  32 +
  33 + def open_for(user)
  34 + opened.assigned(user)
  35 + end
  36 + end
  37 +
  38 + state_machine :state, initial: :opened do
  39 + event :close do
  40 + transition [:reopened, :opened] => :closed
  41 + end
  42 +
  43 + event :reopen do
  44 + transition closed: :reopened
  45 + end
  46 +
  47 + state :opened
  48 +
  49 + state :reopened
  50 +
  51 + state :closed
29 end 52 end
30 end 53 end
app/models/key.rb
@@ -35,7 +35,7 @@ class Key &lt; ActiveRecord::Base @@ -35,7 +35,7 @@ class Key &lt; ActiveRecord::Base
35 35
36 def fingerprintable_key 36 def fingerprintable_key
37 return true unless key # Don't test if there is no key. 37 return true unless key # Don't test if there is no key.
38 - # `ssh-keygen -lf /dev/stdin <<< "#{key}"` errors with: redirection unexpected 38 +
39 file = Tempfile.new('key_file') 39 file = Tempfile.new('key_file')
40 begin 40 begin
41 file.puts key 41 file.puts key
@@ -45,7 +45,7 @@ class Key &lt; ActiveRecord::Base @@ -45,7 +45,7 @@ class Key &lt; ActiveRecord::Base
45 file.close 45 file.close
46 file.unlink # deletes the temp file 46 file.unlink # deletes the temp file
47 end 47 end
48 - errors.add(:key, "can't be fingerprinted") if fingerprint_output.match("failed") 48 + errors.add(:key, "can't be fingerprinted") if $?.exitstatus != 0
49 end 49 end
50 50
51 def set_identifier 51 def set_identifier
app/models/merge_request.rb
@@ -9,15 +9,14 @@ @@ -9,15 +9,14 @@
9 # author_id :integer 9 # author_id :integer
10 # assignee_id :integer 10 # assignee_id :integer
11 # title :string(255) 11 # title :string(255)
12 -# closed :boolean default(FALSE), not null 12 +# state :string(255) not null
13 # created_at :datetime not null 13 # created_at :datetime not null
14 # updated_at :datetime not null 14 # updated_at :datetime not null
15 # st_commits :text(2147483647) 15 # st_commits :text(2147483647)
16 # st_diffs :text(2147483647) 16 # st_diffs :text(2147483647)
17 -# merged :boolean default(FALSE), not null  
18 -# state :integer default(1), not null  
19 -# milestone_id :integer 17 +# merge_status :integer default(1), not null
20 # 18 #
  19 +# milestone_id :integer
21 20
22 require Rails.root.join("app/models/commit") 21 require Rails.root.join("app/models/commit")
23 require Rails.root.join("lib/static_model") 22 require Rails.root.join("lib/static_model")
@@ -25,11 +24,33 @@ require Rails.root.join(&quot;lib/static_model&quot;) @@ -25,11 +24,33 @@ require Rails.root.join(&quot;lib/static_model&quot;)
25 class MergeRequest < ActiveRecord::Base 24 class MergeRequest < ActiveRecord::Base
26 include Issuable 25 include Issuable
27 26
28 - attr_accessible :title, :assignee_id, :closed, :target_branch, :source_branch, :milestone_id,  
29 - :author_id_of_changes 27 + attr_accessible :title, :assignee_id, :target_branch, :source_branch, :milestone_id,
  28 + :author_id_of_changes, :state_event
30 29
31 attr_accessor :should_remove_source_branch 30 attr_accessor :should_remove_source_branch
32 31
  32 + state_machine :state, initial: :opened do
  33 + event :close do
  34 + transition [:reopened, :opened] => :closed
  35 + end
  36 +
  37 + event :merge do
  38 + transition [:reopened, :opened] => :merged
  39 + end
  40 +
  41 + event :reopen do
  42 + transition closed: :reopened
  43 + end
  44 +
  45 + state :opened
  46 +
  47 + state :reopened
  48 +
  49 + state :closed
  50 +
  51 + state :merged
  52 + end
  53 +
33 BROKEN_DIFF = "--broken-diff" 54 BROKEN_DIFF = "--broken-diff"
34 55
35 UNCHECKED = 1 56 UNCHECKED = 1
@@ -43,21 +64,33 @@ class MergeRequest &lt; ActiveRecord::Base @@ -43,21 +64,33 @@ class MergeRequest &lt; ActiveRecord::Base
43 validates :target_branch, presence: true 64 validates :target_branch, presence: true
44 validate :validate_branches 65 validate :validate_branches
45 66
46 - def self.find_all_by_branch(branch_name)  
47 - where("source_branch LIKE :branch OR target_branch LIKE :branch", branch: branch_name)  
48 - end 67 + scope :merged, -> { with_state(:merged) }
49 68
50 - def self.find_all_by_milestone(milestone)  
51 - where("milestone_id = :milestone_id", milestone_id: milestone) 69 + class << self
  70 + def find_all_by_branch(branch_name)
  71 + where("source_branch LIKE :branch OR target_branch LIKE :branch", branch: branch_name)
  72 + end
  73 +
  74 + def cared(user)
  75 + where('assignee_id = :user OR author_id = :user', user: user.id)
  76 + end
  77 +
  78 + def find_all_by_branch(branch_name)
  79 + where("source_branch LIKE :branch OR target_branch LIKE :branch", branch: branch_name)
  80 + end
  81 +
  82 + def find_all_by_milestone(milestone)
  83 + where("milestone_id = :milestone_id", milestone_id: milestone)
  84 + end
52 end 85 end
53 86
54 - def human_state  
55 - states = { 87 + def human_merge_status
  88 + merge_statuses = {
56 CAN_BE_MERGED => "can_be_merged", 89 CAN_BE_MERGED => "can_be_merged",
57 CANNOT_BE_MERGED => "cannot_be_merged", 90 CANNOT_BE_MERGED => "cannot_be_merged",
58 UNCHECKED => "unchecked" 91 UNCHECKED => "unchecked"
59 } 92 }
60 - states[self.state] 93 + merge_statuses[self.merge_status]
61 end 94 end
62 95
63 def validate_branches 96 def validate_branches
@@ -72,20 +105,20 @@ class MergeRequest &lt; ActiveRecord::Base @@ -72,20 +105,20 @@ class MergeRequest &lt; ActiveRecord::Base
72 end 105 end
73 106
74 def unchecked? 107 def unchecked?
75 - state == UNCHECKED 108 + merge_status == UNCHECKED
76 end 109 end
77 110
78 def mark_as_unchecked 111 def mark_as_unchecked
79 - self.state = UNCHECKED 112 + self.merge_status = UNCHECKED
80 self.save 113 self.save
81 end 114 end
82 115
83 def can_be_merged? 116 def can_be_merged?
84 - state == CAN_BE_MERGED 117 + merge_status == CAN_BE_MERGED
85 end 118 end
86 119
87 def check_if_can_be_merged 120 def check_if_can_be_merged
88 - self.state = if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged? 121 + self.merge_status = if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
89 CAN_BE_MERGED 122 CAN_BE_MERGED
90 else 123 else
91 CANNOT_BE_MERGED 124 CANNOT_BE_MERGED
@@ -98,7 +131,7 @@ class MergeRequest &lt; ActiveRecord::Base @@ -98,7 +131,7 @@ class MergeRequest &lt; ActiveRecord::Base
98 end 131 end
99 132
100 def reloaded_diffs 133 def reloaded_diffs
101 - if open? && unmerged_diffs.any? 134 + if opened? && unmerged_diffs.any?
102 self.st_diffs = unmerged_diffs 135 self.st_diffs = unmerged_diffs
103 self.save 136 self.save
104 end 137 end
@@ -128,10 +161,6 @@ class MergeRequest &lt; ActiveRecord::Base @@ -128,10 +161,6 @@ class MergeRequest &lt; ActiveRecord::Base
128 commits.first 161 commits.first
129 end 162 end
130 163
131 - def merged?  
132 - merged && merge_event  
133 - end  
134 -  
135 def merge_event 164 def merge_event
136 self.project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last 165 self.project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last
137 end 166 end
@@ -146,26 +175,16 @@ class MergeRequest &lt; ActiveRecord::Base @@ -146,26 +175,16 @@ class MergeRequest &lt; ActiveRecord::Base
146 175
147 def probably_merged? 176 def probably_merged?
148 unmerged_commits.empty? && 177 unmerged_commits.empty? &&
149 - commits.any? && open?  
150 - end  
151 -  
152 - def open?  
153 - !closed  
154 - end  
155 -  
156 - def mark_as_merged!  
157 - self.merged = true  
158 - self.closed = true  
159 - save 178 + commits.any? && opened?
160 end 179 end
161 180
162 def mark_as_unmergable 181 def mark_as_unmergable
163 - self.state = CANNOT_BE_MERGED 182 + self.merge_status = CANNOT_BE_MERGED
164 self.save 183 self.save
165 end 184 end
166 185
167 def reloaded_commits 186 def reloaded_commits
168 - if open? && unmerged_commits.any? 187 + if opened? && unmerged_commits.any?
169 self.st_commits = unmerged_commits 188 self.st_commits = unmerged_commits
170 save 189 save
171 end 190 end
@@ -181,7 +200,8 @@ class MergeRequest &lt; ActiveRecord::Base @@ -181,7 +200,8 @@ class MergeRequest &lt; ActiveRecord::Base
181 end 200 end
182 201
183 def merge!(user_id) 202 def merge!(user_id)
184 - self.mark_as_merged! 203 + self.merge
  204 +
185 Event.create( 205 Event.create(
186 project: self.project, 206 project: self.project,
187 action: Event::MERGED, 207 action: Event::MERGED,
app/models/milestone.rb
@@ -13,19 +13,32 @@ @@ -13,19 +13,32 @@
13 # 13 #
14 14
15 class Milestone < ActiveRecord::Base 15 class Milestone < ActiveRecord::Base
16 - attr_accessible :title, :description, :due_date, :closed, :author_id_of_changes 16 + attr_accessible :title, :description, :due_date, :state_event, :author_id_of_changes
17 attr_accessor :author_id_of_changes 17 attr_accessor :author_id_of_changes
18 18
19 belongs_to :project 19 belongs_to :project
20 has_many :issues 20 has_many :issues
21 has_many :merge_requests 21 has_many :merge_requests
22 22
23 - scope :active, -> { where(closed: false) }  
24 - scope :closed, -> { where(closed: true) } 23 + scope :active, -> { with_state(:active) }
  24 + scope :closed, -> { with_state(:closed) }
25 25
26 validates :title, presence: true 26 validates :title, presence: true
27 validates :project, presence: true 27 validates :project, presence: true
28 - validates :closed, inclusion: { in: [true, false] } 28 +
  29 + state_machine :state, initial: :active do
  30 + event :close do
  31 + transition active: :closed
  32 + end
  33 +
  34 + event :activate do
  35 + transition closed: :active
  36 + end
  37 +
  38 + state :closed
  39 +
  40 + state :active
  41 + end
29 42
30 def expired? 43 def expired?
31 if due_date 44 if due_date
@@ -68,17 +81,13 @@ class Milestone &lt; ActiveRecord::Base @@ -68,17 +81,13 @@ class Milestone &lt; ActiveRecord::Base
68 end 81 end
69 82
70 def can_be_closed? 83 def can_be_closed?
71 - open? && issues.opened.count.zero? 84 + active? && issues.opened.count.zero?
72 end 85 end
73 86
74 def is_empty? 87 def is_empty?
75 total_items_count.zero? 88 total_items_count.zero?
76 end 89 end
77 90
78 - def open?  
79 - !closed  
80 - end  
81 -  
82 def author_id 91 def author_id
83 author_id_of_changes 92 author_id_of_changes
84 end 93 end
app/models/namespace.rb
@@ -17,11 +17,15 @@ class Namespace &lt; ActiveRecord::Base @@ -17,11 +17,15 @@ class Namespace &lt; ActiveRecord::Base
17 has_many :projects, dependent: :destroy 17 has_many :projects, dependent: :destroy
18 belongs_to :owner, class_name: "User" 18 belongs_to :owner, class_name: "User"
19 19
20 - validates :name, presence: true, uniqueness: true 20 + validates :owner, presence: true
  21 + validates :name, presence: true, uniqueness: true,
  22 + length: { within: 0..255 },
  23 + format: { with: Gitlab::Regex.name_regex,
  24 + message: "only letters, digits, spaces & '_' '-' '.' allowed." }
  25 +
21 validates :path, uniqueness: true, presence: true, length: { within: 1..255 }, 26 validates :path, uniqueness: true, presence: true, length: { within: 1..255 },
22 format: { with: Gitlab::Regex.path_regex, 27 format: { with: Gitlab::Regex.path_regex,
23 message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } 28 message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
24 - validates :owner, presence: true  
25 29
26 delegate :name, to: :owner, allow_nil: true, prefix: true 30 delegate :name, to: :owner, allow_nil: true, prefix: true
27 31
app/models/project.rb
@@ -43,7 +43,7 @@ class Project &lt; ActiveRecord::Base @@ -43,7 +43,7 @@ class Project &lt; ActiveRecord::Base
43 43
44 has_many :events, dependent: :destroy 44 has_many :events, dependent: :destroy
45 has_many :merge_requests, dependent: :destroy 45 has_many :merge_requests, dependent: :destroy
46 - has_many :issues, dependent: :destroy, order: "closed, created_at DESC" 46 + has_many :issues, dependent: :destroy, order: "state, created_at DESC"
47 has_many :milestones, dependent: :destroy 47 has_many :milestones, dependent: :destroy
48 has_many :users_projects, dependent: :destroy 48 has_many :users_projects, dependent: :destroy
49 has_many :notes, dependent: :destroy 49 has_many :notes, dependent: :destroy
@@ -146,7 +146,7 @@ class Project &lt; ActiveRecord::Base @@ -146,7 +146,7 @@ class Project &lt; ActiveRecord::Base
146 end 146 end
147 147
148 def saved? 148 def saved?
149 - id && valid? 149 + id && persisted?
150 end 150 end
151 151
152 def import? 152 def import?
app/models/repository.rb
@@ -132,16 +132,16 @@ class Repository @@ -132,16 +132,16 @@ class Repository
132 return nil unless commit 132 return nil unless commit
133 133
134 # Build file path 134 # Build file path
135 - file_name = self.path_with_namespace + "-" + commit.id.to_s + ".tar.gz" 135 + file_name = self.path_with_namespace.gsub("/","_") + "-" + commit.id.to_s + ".tar.gz"
136 storage_path = Rails.root.join("tmp", "repositories") 136 storage_path = Rails.root.join("tmp", "repositories")
137 - file_path = File.join(storage_path, file_name) 137 + file_path = File.join(storage_path, self.path_with_namespace, file_name)
138 138
139 # Put files into a directory before archiving 139 # Put files into a directory before archiving
140 prefix = self.path_with_namespace + "/" 140 prefix = self.path_with_namespace + "/"
141 141
142 # Create file if not exists 142 # Create file if not exists
143 unless File.exists?(file_path) 143 unless File.exists?(file_path)
144 - FileUtils.mkdir_p storage_path 144 + FileUtils.mkdir_p File.dirname(file_path)
145 file = self.repo.archive_to_file(ref, prefix, file_path) 145 file = self.repo.archive_to_file(ref, prefix, file_path)
146 end 146 end
147 147
app/models/user.rb
@@ -234,8 +234,12 @@ class User &lt; ActiveRecord::Base @@ -234,8 +234,12 @@ class User &lt; ActiveRecord::Base
234 keys.count == 0 234 keys.count == 0
235 end 235 end
236 236
  237 + def can_change_username?
  238 + Gitlab.config.gitlab.username_changing_enabled
  239 + end
  240 +
237 def can_create_project? 241 def can_create_project?
238 - projects_limit > personal_projects.count 242 + projects_limit > owned_projects.count
239 end 243 end
240 244
241 def can_create_group? 245 def can_create_group?
@@ -263,7 +267,7 @@ class User &lt; ActiveRecord::Base @@ -263,7 +267,7 @@ class User &lt; ActiveRecord::Base
263 end 267 end
264 268
265 def cared_merge_requests 269 def cared_merge_requests
266 - MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id) 270 + MergeRequest.cared(self)
267 end 271 end
268 272
269 # Remove user from all projects and 273 # Remove user from all projects and
app/models/user_team.rb
@@ -21,8 +21,11 @@ class UserTeam &lt; ActiveRecord::Base @@ -21,8 +21,11 @@ class UserTeam &lt; ActiveRecord::Base
21 has_many :projects, through: :user_team_project_relationships 21 has_many :projects, through: :user_team_project_relationships
22 has_many :members, through: :user_team_user_relationships, source: :user 22 has_many :members, through: :user_team_user_relationships, source: :user
23 23
24 - validates :name, presence: true, uniqueness: true  
25 validates :owner, presence: true 24 validates :owner, presence: true
  25 + validates :name, presence: true, uniqueness: true,
  26 + length: { within: 0..255 },
  27 + format: { with: Gitlab::Regex.name_regex,
  28 + message: "only letters, digits, spaces & '_' '-' '.' allowed." }
26 validates :path, uniqueness: true, presence: true, length: { within: 1..255 }, 29 validates :path, uniqueness: true, presence: true, length: { within: 1..255 },
27 format: { with: Gitlab::Regex.path_regex, 30 format: { with: Gitlab::Regex.path_regex,
28 message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } 31 message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
app/models/user_team_project_relationship.rb
@@ -26,6 +26,10 @@ class UserTeamProjectRelationship &lt; ActiveRecord::Base @@ -26,6 +26,10 @@ class UserTeamProjectRelationship &lt; ActiveRecord::Base
26 user_team.name 26 user_team.name
27 end 27 end
28 28
  29 + def human_max_access
  30 + UserTeam.access_roles.key(greatest_access)
  31 + end
  32 +
29 private 33 private
30 34
31 def check_greatest_access 35 def check_greatest_access
app/observers/activity_observer.rb
@@ -20,15 +20,23 @@ class ActivityObserver &lt; ActiveRecord::Observer @@ -20,15 +20,23 @@ class ActivityObserver &lt; ActiveRecord::Observer
20 end 20 end
21 end 21 end
22 22
23 - def after_save(record)  
24 - if record.changed.include?("closed") && record.author_id_of_changes 23 + def after_close(record, transition)
25 Event.create( 24 Event.create(
26 project: record.project, 25 project: record.project,
27 target_id: record.id, 26 target_id: record.id,
28 target_type: record.class.name, 27 target_type: record.class.name,
29 - action: (record.closed ? Event::CLOSED : Event::REOPENED), 28 + action: Event::CLOSED,
  29 + author_id: record.author_id_of_changes
  30 + )
  31 + end
  32 +
  33 + def after_reopen(record, transition)
  34 + Event.create(
  35 + project: record.project,
  36 + target_id: record.id,
  37 + target_type: record.class.name,
  38 + action: Event::REOPENED,
30 author_id: record.author_id_of_changes 39 author_id: record.author_id_of_changes
31 ) 40 )
32 - end  
33 end 41 end
34 end 42 end
app/observers/issue_observer.rb
@@ -7,22 +7,31 @@ class IssueObserver &lt; ActiveRecord::Observer @@ -7,22 +7,31 @@ class IssueObserver &lt; ActiveRecord::Observer
7 end 7 end
8 end 8 end
9 9
10 - def after_update(issue) 10 + def after_close(issue, transition)
11 send_reassigned_email(issue) if issue.is_being_reassigned? 11 send_reassigned_email(issue) if issue.is_being_reassigned?
12 12
13 - status = nil  
14 - status = 'closed' if issue.is_being_closed?  
15 - status = 'reopened' if issue.is_being_reopened?  
16 - if status  
17 - Note.create_status_change_note(issue, current_user, status)  
18 - [issue.author, issue.assignee].compact.each do |recipient|  
19 - Notify.delay.issue_status_changed_email(recipient.id, issue.id, status, current_user.id)  
20 - end  
21 - end 13 + create_note(issue)
  14 + end
  15 +
  16 + def after_reopen(issue, transition)
  17 + send_reassigned_email(issue) if issue.is_being_reassigned?
  18 +
  19 + create_note(issue)
  20 + end
  21 +
  22 + def after_update(issue)
  23 + send_reassigned_email(issue) if issue.is_being_reassigned?
22 end 24 end
23 25
24 protected 26 protected
25 27
  28 + def create_note(issue)
  29 + Note.create_status_change_note(issue, current_user, issue.state)
  30 + [issue.author, issue.assignee].compact.each do |recipient|
  31 + Notify.delay.issue_status_changed_email(recipient.id, issue.id, issue.state, current_user.id)
  32 + end
  33 + end
  34 +
26 def send_reassigned_email(issue) 35 def send_reassigned_email(issue)
27 recipient_ids = [issue.assignee_id, issue.assignee_id_was].keep_if {|id| id && id != current_user.id } 36 recipient_ids = [issue.assignee_id, issue.assignee_id_was].keep_if {|id| id && id != current_user.id }
28 37
app/observers/merge_request_observer.rb
@@ -7,15 +7,20 @@ class MergeRequestObserver &lt; ActiveRecord::Observer @@ -7,15 +7,20 @@ class MergeRequestObserver &lt; ActiveRecord::Observer
7 end 7 end
8 end 8 end
9 9
10 - def after_update(merge_request) 10 + def after_close(merge_request, transition)
11 send_reassigned_email(merge_request) if merge_request.is_being_reassigned? 11 send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
12 12
13 - status = nil  
14 - status = 'closed' if merge_request.is_being_closed?  
15 - status = 'reopened' if merge_request.is_being_reopened?  
16 - if status  
17 - Note.create_status_change_note(merge_request, current_user, status)  
18 - end 13 + Note.create_status_change_note(merge_request, current_user, merge_request.state)
  14 + end
  15 +
  16 + def after_reopen(merge_request, transition)
  17 + send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
  18 +
  19 + Note.create_status_change_note(merge_request, current_user, merge_request.state)
  20 + end
  21 +
  22 + def after_update(merge_request)
  23 + send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
19 end 24 end
20 25
21 protected 26 protected
app/uploaders/attachment_uploader.rb
@@ -19,4 +19,12 @@ class AttachmentUploader &lt; CarrierWave::Uploader::Base @@ -19,4 +19,12 @@ class AttachmentUploader &lt; CarrierWave::Uploader::Base
19 rescue 19 rescue
20 false 20 false
21 end 21 end
  22 +
  23 + def secure_url
  24 + if self.class.storage == CarrierWave::Storage::File
  25 + "/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}"
  26 + else
  27 + url
  28 + end
  29 + end
22 end 30 end
app/views/admin/groups/index.html.haml
@@ -28,7 +28,7 @@ @@ -28,7 +28,7 @@
28 %td= group.path 28 %td= group.path
29 %td= group.projects.count 29 %td= group.projects.count
30 %td 30 %td
31 - = link_to group.owner_name, admin_user_path(group.owner_id) 31 + = link_to group.owner_name, admin_user_path(group.owner)
32 %td.bgred 32 %td.bgred
33 = link_to 'Rename', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-small" 33 = link_to 'Rename', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-small"
34 = link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove" 34 = link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
app/views/admin/teams/index.html.haml
@@ -30,7 +30,7 @@ @@ -30,7 +30,7 @@
30 %td= team.projects.count 30 %td= team.projects.count
31 %td= team.members.count 31 %td= team.members.count
32 %td 32 %td
33 - = link_to team.owner.name, admin_user_path(team.owner_id) 33 + = link_to team.owner.name, admin_user_path(team.owner)
34 %td.bgred 34 %td.bgred
35 = link_to 'Rename', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn btn-small" 35 = link_to 'Rename', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn btn-small"
36 = link_to 'Destroy', admin_team_path(team), confirm: "REMOVE #{team.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove" 36 = link_to 'Destroy', admin_team_path(team), confirm: "REMOVE #{team.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
app/views/commits/_commit.html.haml
@@ -6,9 +6,9 @@ @@ -6,9 +6,9 @@
6 = link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id" 6 = link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id"
7 = commit.author_link avatar: true, size: 24 7 = commit.author_link avatar: true, size: 24
8 &nbsp; 8 &nbsp;
9 - = link_to_gfm truncate(commit.title, length: 50), project_commit_path(@project, commit.id), class: "row_title" 9 + = link_to_gfm truncate(commit.title, length: 70), project_commit_path(@project, commit.id), class: "row_title"
10 10
11 - %span.committed_ago 11 + %time.committed_ago{ datetime: commit.committed_date, title: commit.committed_date.stamp("Aug 21, 2011 9:23pm") }
12 = time_ago_in_words(commit.committed_date) 12 = time_ago_in_words(commit.committed_date)
13 ago 13 ago
14 &nbsp; 14 &nbsp;
app/views/events/event/_note.html.haml
@@ -26,7 +26,7 @@ @@ -26,7 +26,7 @@
26 = markdown truncate(event.target.note, length: 70) 26 = markdown truncate(event.target.note, length: 70)
27 - note = event.target 27 - note = event.target
28 - if note.attachment.url 28 - if note.attachment.url
29 - = link_to note.attachment.url, target: "_blank", class: 'note-file-attach' do 29 + = link_to note.attachment.secure_url, target: "_blank", class: 'note-file-attach' do
30 - if note.attachment.image? 30 - if note.attachment.image?
31 = image_tag note.attachment.url, class: 'note-image-attach' 31 = image_tag note.attachment.url, class: 'note-image-attach'
32 - else 32 - else
app/views/groups/_filter.html.haml
@@ -26,6 +26,8 @@ @@ -26,6 +26,8 @@
26 = link_to group_filter_path(entity, project_id: project.id) do 26 = link_to group_filter_path(entity, project_id: project.id) do
27 = project.name_with_namespace 27 = project.name_with_namespace
28 %small.pull-right= entities_per_project(project, entity) 28 %small.pull-right= entities_per_project(project, entity)
  29 + - if @projects.blank?
  30 + %p.nothing_here_message This group has no projects yet
29 31
30 %fieldset 32 %fieldset
31 %hr 33 %hr
app/views/groups/_people_filter.html.haml
@@ -7,6 +7,8 @@ @@ -7,6 +7,8 @@
7 = link_to people_group_path(@group, project_id: project.id) do 7 = link_to people_group_path(@group, project_id: project.id) do
8 = project.name_with_namespace 8 = project.name_with_namespace
9 %small.pull-right= project.users.count 9 %small.pull-right= project.users.count
  10 + - if @projects.blank?
  11 + %p.nothing_here_message This group has no projects yet
10 12
11 %fieldset 13 %fieldset
12 %hr 14 %hr
app/views/groups/edit.html.haml
@@ -30,6 +30,8 @@ @@ -30,6 +30,8 @@
30 = link_to 'Team', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" 30 = link_to 'Team', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
31 = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" 31 = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
32 = link_to 'Remove', project, confirm: "REMOVE #{project.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove" 32 = link_to 'Remove', project, confirm: "REMOVE #{project.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
  33 + - if @group.projects.blank?
  34 + %p.nothing_here_message This group has no projects yet
33 35
34 .span5 36 .span5
35 .ui-box 37 .ui-box
app/views/help/index.html.haml
1 %h3.page_title 1 %h3.page_title
2 GITLAB 2 GITLAB
3 .pull-right 3 .pull-right
4 - %span= Gitlab::Version  
5 - %small= Gitlab::Revision 4 + %span= Gitlab::VERSION
  5 + %small= Gitlab::REVISION
6 %hr 6 %hr
7 %p.lead 7 %p.lead
8 Self Hosted Git Management 8 Self Hosted Git Management
app/views/issues/_show.html.haml
@@ -8,10 +8,10 @@ @@ -8,10 +8,10 @@
8 %i.icon-comment 8 %i.icon-comment
9 = issue.notes.count 9 = issue.notes.count
10 - if can? current_user, :modify_issue, issue 10 - if can? current_user, :modify_issue, issue
11 - - if issue.closed  
12 - = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {closed: false }, status_only: true), method: :put, class: "btn btn-small grouped reopen_issue", remote: true 11 + - if issue.closed?
  12 + = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small grouped reopen_issue", remote: true
13 - else 13 - else
14 - = link_to 'Close', project_issue_path(issue.project, issue, issue: {closed: true }, status_only: true), method: :put, class: "btn btn-small grouped close_issue", remote: true 14 + = link_to 'Close', project_issue_path(issue.project, issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small grouped close_issue", remote: true
15 = link_to edit_project_issue_path(issue.project, issue), class: "btn btn-small edit-issue-link grouped" do 15 = link_to edit_project_issue_path(issue.project, issue), class: "btn btn-small edit-issue-link grouped" do
16 %i.icon-edit 16 %i.icon-edit
17 Edit 17 Edit
app/views/issues/show.html.haml
@@ -7,10 +7,10 @@ @@ -7,10 +7,10 @@
7 7
8 %span.pull-right 8 %span.pull-right
9 - if can?(current_user, :admin_project, @project) || @issue.author == current_user 9 - if can?(current_user, :admin_project, @project) || @issue.author == current_user
10 - - if @issue.closed  
11 - = link_to 'Reopen', project_issue_path(@project, @issue, issue: {closed: false }, status_only: true), method: :put, class: "btn grouped reopen_issue" 10 + - if @issue.closed?
  11 + = link_to 'Reopen', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn grouped reopen_issue"
12 - else 12 - else
13 - = link_to 'Close', project_issue_path(@project, @issue, issue: {closed: true }, status_only: true), method: :put, class: "btn grouped close_issue", title: "Close Issue" 13 + = link_to 'Close', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn grouped close_issue", title: "Close Issue"
14 - if can?(current_user, :admin_project, @project) || @issue.author == current_user 14 - if can?(current_user, :admin_project, @project) || @issue.author == current_user
15 = link_to edit_project_issue_path(@project, @issue), class: "btn grouped" do 15 = link_to edit_project_issue_path(@project, @issue), class: "btn grouped" do
16 %i.icon-edit 16 %i.icon-edit
@@ -27,7 +27,7 @@ @@ -27,7 +27,7 @@
27 .ui-box.ui-box-show 27 .ui-box.ui-box-show
28 .ui-box-head 28 .ui-box-head
29 %h4.box-title 29 %h4.box-title
30 - - if @issue.closed 30 + - if @issue.closed?
31 .error.status_info Closed 31 .error.status_info Closed
32 = gfm escape_once(@issue.title) 32 = gfm escape_once(@issue.title)
33 33
app/views/merge_requests/_show.html.haml
@@ -29,10 +29,10 @@ @@ -29,10 +29,10 @@
29 $(function(){ 29 $(function(){
30 merge_request = new MergeRequest({ 30 merge_request = new MergeRequest({
31 url_to_automerge_check: "#{automerge_check_project_merge_request_path(@project, @merge_request)}", 31 url_to_automerge_check: "#{automerge_check_project_merge_request_path(@project, @merge_request)}",
32 - check_enable: #{@merge_request.state == MergeRequest::UNCHECKED ? "true" : "false"}, 32 + check_enable: #{@merge_request.merge_status == MergeRequest::UNCHECKED ? "true" : "false"},
33 url_to_ci_check: "#{ci_status_project_merge_request_path(@project, @merge_request)}", 33 url_to_ci_check: "#{ci_status_project_merge_request_path(@project, @merge_request)}",
34 ci_enable: #{@project.gitlab_ci? ? "true" : "false"}, 34 ci_enable: #{@project.gitlab_ci? ? "true" : "false"},
35 - current_state: "#{@merge_request.human_state}", 35 + current_status: "#{@merge_request.human_merge_status}",
36 action: "#{controller.action_name}" 36 action: "#{controller.action_name}"
37 }); 37 });
38 }); 38 });
app/views/merge_requests/show/_mr_accept.html.haml
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 %strong Only masters can accept MR 3 %strong Only masters can accept MR
4 4
5 5
6 -- if @merge_request.open? && @commits.any? && can?(current_user, :accept_mr, @project) 6 +- if @merge_request.opened? && @commits.any? && can?(current_user, :accept_mr, @project)
7 .automerge_widget.can_be_merged{style: "display:none"} 7 .automerge_widget.can_be_merged{style: "display:none"}
8 .alert.alert-success 8 .alert.alert-success
9 %span 9 %span
app/views/merge_requests/show/_mr_box.html.haml
1 .ui-box.ui-box-show 1 .ui-box.ui-box-show
2 .ui-box-head 2 .ui-box-head
3 %h4.box-title 3 %h4.box-title
4 - - if @merge_request.merged 4 + - if @merge_request.merged?
5 .error.status_info 5 .error.status_info
6 %i.icon-ok 6 %i.icon-ok
7 Merged 7 Merged
8 - - elsif @merge_request.closed 8 + - elsif @merge_request.closed?
9 .error.status_info Closed 9 .error.status_info Closed
10 = gfm escape_once(@merge_request.title) 10 = gfm escape_once(@merge_request.title)
11 11
@@ -21,14 +21,14 @@ @@ -21,14 +21,14 @@
21 %strong= link_to_gfm truncate(milestone.title, length: 20), project_milestone_path(milestone.project, milestone) 21 %strong= link_to_gfm truncate(milestone.title, length: 20), project_milestone_path(milestone.project, milestone)
22 22
23 23
24 - - if @merge_request.closed 24 + - if @merge_request.closed?
25 .ui-box-bottom 25 .ui-box-bottom
26 - - if @merge_request.merged?  
27 - %span  
28 - Merged by #{link_to_member(@project, @merge_request.merge_event.author)}  
29 - %small #{time_ago_in_words(@merge_request.merge_event.created_at)} ago.  
30 - - elsif @merge_request.closed_event  
31 - %span  
32 - Closed by #{link_to_member(@project, @merge_request.closed_event.author)}  
33 - %small #{time_ago_in_words(@merge_request.closed_event.created_at)} ago. 26 + %span
  27 + Closed by #{link_to_member(@project, @merge_request.closed_event.author)}
  28 + %small #{time_ago_in_words(@merge_request.closed_event.created_at)} ago.
  29 + - if @merge_request.merged?
  30 + .ui-box-bottom
  31 + %span
  32 + Merged by #{link_to_member(@project, @merge_request.merge_event.author)}
  33 + %small #{time_ago_in_words(@merge_request.merge_event.created_at)} ago.
34 34
app/views/merge_requests/show/_mr_ci.html.haml
1 -- if @merge_request.open? && @commits.any? 1 +- if @merge_request.opened? && @commits.any?
2 .ci_widget.ci-success{style: "display:none"} 2 .ci_widget.ci-success{style: "display:none"}
3 .alert.alert-success 3 .alert.alert-success
4 %i.icon-ok 4 %i.icon-ok
app/views/merge_requests/show/_mr_title.html.haml
@@ -7,7 +7,7 @@ @@ -7,7 +7,7 @@
7 7
8 %span.pull-right 8 %span.pull-right
9 - if can?(current_user, :modify_merge_request, @merge_request) 9 - if can?(current_user, :modify_merge_request, @merge_request)
10 - - if @merge_request.open? 10 + - if @merge_request.opened?
11 .left.btn-group 11 .left.btn-group
12 %a.btn.grouped.dropdown-toggle{ data: {toggle: :dropdown} } 12 %a.btn.grouped.dropdown-toggle{ data: {toggle: :dropdown} }
13 %i.icon-download-alt 13 %i.icon-download-alt
@@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
17 %li= link_to "Email Patches", project_merge_request_path(@project, @merge_request, format: :patch) 17 %li= link_to "Email Patches", project_merge_request_path(@project, @merge_request, format: :patch)
18 %li= link_to "Plain Diff", project_merge_request_path(@project, @merge_request, format: :diff) 18 %li= link_to "Plain Diff", project_merge_request_path(@project, @merge_request, format: :diff)
19 19
20 - = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {closed: true }, status_only: true), method: :put, class: "btn grouped btn-close", title: "Close merge request" 20 + = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn grouped btn-close", title: "Close merge request"
21 21
22 = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn grouped" do 22 = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn grouped" do
23 %i.icon-edit 23 %i.icon-edit
app/views/milestones/_milestone.html.haml
1 -%li{class: "milestone milestone-#{milestone.closed ? 'closed' : 'open'}", id: dom_id(milestone) } 1 +%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) }
2 .pull-right 2 .pull-right
3 - - if can?(current_user, :admin_milestone, milestone.project) and milestone.open? 3 + - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
4 = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link grouped" do 4 = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link grouped" do
5 %i.icon-edit 5 %i.icon-edit
6 Edit 6 Edit
7 %h4 7 %h4
8 = link_to_gfm truncate(milestone.title, length: 100), project_milestone_path(milestone.project, milestone) 8 = link_to_gfm truncate(milestone.title, length: 100), project_milestone_path(milestone.project, milestone)
9 - - if milestone.expired? and not milestone.closed 9 + - if milestone.expired? and not milestone.closed?
10 %span.cred (Expired) 10 %span.cred (Expired)
11 %small 11 %small
12 = milestone.expires_at 12 = milestone.expires_at
app/views/milestones/show.html.haml
@@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
9 &larr; To milestones list 9 &larr; To milestones list
10 .span6 10 .span6
11 .pull-right 11 .pull-right
12 - - unless @milestone.closed 12 + - unless @milestone.closed?
13 = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small grouped", title: "New Issue" do 13 = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small grouped", title: "New Issue" do
14 %i.icon-plus 14 %i.icon-plus
15 New Issue 15 New Issue
@@ -25,12 +25,12 @@ @@ -25,12 +25,12 @@
25 %hr 25 %hr
26 %p 26 %p
27 %span All issues for this milestone are closed. You may close milestone now. 27 %span All issues for this milestone are closed. You may close milestone now.
28 - = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {closed: true }), method: :put, class: "btn btn-small btn-remove" 28 + = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-small btn-remove"
29 29
30 .ui-box.ui-box-show 30 .ui-box.ui-box-show
31 .ui-box-head 31 .ui-box-head
32 %h4.box-title 32 %h4.box-title
33 - - if @milestone.closed 33 + - if @milestone.closed?
34 .error.status_info Closed 34 .error.status_info Closed
35 - elsif @milestone.expired? 35 - elsif @milestone.expired?
36 .error.status_info Expired 36 .error.status_info Expired
@@ -63,7 +63,7 @@ @@ -63,7 +63,7 @@
63 %li=link_to('All Issues', '#') 63 %li=link_to('All Issues', '#')
64 %ul.well-list 64 %ul.well-list
65 - @issues.each do |issue| 65 - @issues.each do |issue|
66 - %li{data: {closed: issue.closed}} 66 + %li{data: {closed: issue.closed?}}
67 = link_to [@project, issue] do 67 = link_to [@project, issue] do
68 %span.badge.badge-info ##{issue.id} 68 %span.badge.badge-info ##{issue.id}
69 &ndash; 69 &ndash;
@@ -77,7 +77,7 @@ @@ -77,7 +77,7 @@
77 %li=link_to('All Merge Requests', '#') 77 %li=link_to('All Merge Requests', '#')
78 %ul.well-list 78 %ul.well-list
79 - @merge_requests.each do |merge_request| 79 - @merge_requests.each do |merge_request|
80 - %li{data: {closed: merge_request.closed}} 80 + %li{data: {closed: merge_request.closed?}}
81 = link_to [@project, merge_request] do 81 = link_to [@project, merge_request] do
82 %span.badge.badge-info ##{merge_request.id} 82 %span.badge.badge-info ##{merge_request.id}
83 &ndash; 83 &ndash;
app/views/notes/_note.html.haml
@@ -31,7 +31,7 @@ @@ -31,7 +31,7 @@
31 - if note.attachment.image? 31 - if note.attachment.image?
32 = image_tag note.attachment.url, class: 'note-image-attach' 32 = image_tag note.attachment.url, class: 'note-image-attach'
33 .attachment.pull-right 33 .attachment.pull-right
34 - = link_to note.attachment.url, target: "_blank" do 34 + = link_to note.attachment.secure_url, target: "_blank" do
35 %i.icon-paper-clip 35 %i.icon-paper-clip
36 = note.attachment_identifier 36 = note.attachment_identifier
37 .clear 37 .clear
app/views/profiles/account.html.haml
@@ -53,29 +53,30 @@ @@ -53,29 +53,30 @@
53 53
54 54
55 55
56 -%fieldset.update-username  
57 - %legend  
58 - Username  
59 - %small.cred.pull-right  
60 - Changing your username can have unintended side effects!  
61 - = form_for @user, url: update_username_profile_path, method: :put, remote: true do |f|  
62 - .padded  
63 - = f.label :username  
64 - .input  
65 - = f.text_field :username, required: true  
66 - &nbsp;  
67 - %span.loading-gif.hide= image_tag "ajax_loader.gif"  
68 - %span.update-success.cgreen.hide  
69 - %i.icon-ok  
70 - Saved  
71 - %span.update-failed.cred.hide  
72 - %i.icon-remove  
73 - Failed  
74 - %ul.cred  
75 - %li It will change web url for personal projects.  
76 - %li It will change the git path to repositories for personal projects.  
77 - .input  
78 - = f.submit 'Save username', class: "btn btn-save" 56 +- if current_user.can_change_username?
  57 + %fieldset.update-username
  58 + %legend
  59 + Username
  60 + %small.cred.pull-right
  61 + Changing your username can have unintended side effects!
  62 + = form_for @user, url: update_username_profile_path, method: :put, remote: true do |f|
  63 + .padded
  64 + = f.label :username
  65 + .input
  66 + = f.text_field :username, required: true
  67 + &nbsp;
  68 + %span.loading-gif.hide= image_tag "ajax_loader.gif"
  69 + %span.update-success.cgreen.hide
  70 + %i.icon-ok
  71 + Saved
  72 + %span.update-failed.cred.hide
  73 + %i.icon-remove
  74 + Failed
  75 + %ul.cred
  76 + %li It will change web url for personal projects.
  77 + %li It will change the git path to repositories for personal projects.
  78 + .input
  79 + = f.submit 'Save username', class: "btn btn-save"
79 80
80 - if Gitlab.config.gitlab.signup_enabled 81 - if Gitlab.config.gitlab.signup_enabled
81 %fieldset.remove-account 82 %fieldset.remove-account
@@ -83,4 +84,4 @@ @@ -83,4 +84,4 @@
83 Remove account 84 Remove account
84 %small.cred.pull-right 85 %small.cred.pull-right
85 Before removing the account you must remove all projects! 86 Before removing the account you must remove all projects!
86 - = link_to 'Delete account', user_registration_path, confirm: "REMOVE #{current_user.name}? Are you sure?", method: :delete, class: "btn btn-remove delete-key btn-small pull-right"  
87 \ No newline at end of file 87 \ No newline at end of file
  88 + = link_to 'Delete account', user_registration_path, confirm: "REMOVE #{current_user.name}? Are you sure?", method: :delete, class: "btn btn-remove delete-key btn-small pull-right"
app/views/profiles/show.html.haml
@@ -77,7 +77,7 @@ @@ -77,7 +77,7 @@
77 %legend 77 %legend
78 Personal projects: 78 Personal projects:
79 %small.pull-right 79 %small.pull-right
80 - %span= current_user.personal_projects.count 80 + %span= current_user.owned_projects.count
81 of 81 of
82 %span= current_user.projects_limit 82 %span= current_user.projects_limit
83 .padded 83 .padded
app/views/projects/_new_form.html.haml
@@ -28,7 +28,7 @@ @@ -28,7 +28,7 @@
28 .input 28 .input
29 = f.text_field :import_url, class: 'xlarge', placeholder: 'https://github.com/randx/six.git' 29 = f.text_field :import_url, class: 'xlarge', placeholder: 'https://github.com/randx/six.git'
30 .light 30 .light
31 - URL should be clonable 31 + URL must be clonable
32 32
33 %p.padded 33 %p.padded
34 New projects are private by default. You choose who can see the project and commit to repository. 34 New projects are private by default. You choose who can see the project and commit to repository.
app/views/projects/files.html.haml
@@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
9 - @notes.each do |note| 9 - @notes.each do |note|
10 %tr 10 %tr
11 %td 11 %td
12 - %a{href: note.attachment.url} 12 + = link_to note.attachment.secure_url, target: "_blank" do
13 = image_tag gravatar_icon(note.author_email), class: "avatar s24" 13 = image_tag gravatar_icon(note.author_email), class: "avatar s24"
14 = note.attachment_identifier 14 = note.attachment_identifier
15 %td 15 %td
app/views/team_members/_assigned_team.html.haml 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +%li{id: dom_id(team), class: "user_team_row team_#{team.id}"}
  2 + .pull-right
  3 + - if can?(current_user, :admin_team_member, @project)
  4 + = link_to resign_project_team_path(@project, team), method: :delete, confirm: "Are you shure?", class: "btn btn-remove btn-tiny" do
  5 + %i.icon-minus.icon-white
  6 +
  7 + %strong= link_to team.name, team_path(team), title: team.name, class: "dark"
  8 + %br
  9 + %small.cgray Members: #{team.members.count}
  10 + %small.cgray Max access: #{team_relation.human_max_access}
app/views/team_members/_assigned_teams.html.haml 0 → 100644
@@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
  1 +.ui-box
  2 + %ul.well-list
  3 + - assigned_teams.sort_by(&:team_name).each do |team_relation|
  4 + = render "team_members/assigned_team", team_relation: team_relation, team: team_relation.user_team
app/views/team_members/_show.html.haml
@@ -1,28 +0,0 @@ @@ -1,28 +0,0 @@
1 -- user = member.user  
2 -- allow_admin = can? current_user, :admin_project, @project  
3 -%li{id: dom_id(user), class: "team_member_row user_#{user.id}"}  
4 - .row  
5 - .span6  
6 - = link_to project_team_member_path(@project, user), title: user.name, class: "dark" do  
7 - = image_tag gravatar_icon(user.email, 40), class: "avatar s32"  
8 - = link_to project_team_member_path(@project, user), title: user.name, class: "dark" do  
9 - %strong= truncate(user.name, lenght: 40)  
10 - %br  
11 - %small.cgray= user.email  
12 -  
13 - .span5.pull-right  
14 - - if allow_admin  
15 - .left  
16 - = form_for(member, as: :team_member, url: project_team_member_path(@project, member.user)) do |f|  
17 - = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2"  
18 - .pull-right  
19 - - if current_user == user  
20 - %span.btn.disabled This is you!  
21 - - if @project.namespace_owner == user  
22 - %span.btn.disabled Owner  
23 - - elsif user.blocked  
24 - %span.btn.disabled.blocked Blocked  
25 - - elsif allow_admin  
26 - = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "btn-tiny btn btn-remove" do  
27 - %i.icon-minus.icon-white  
28 -  
app/views/team_members/_show_team.html.haml
@@ -1,15 +0,0 @@ @@ -1,15 +0,0 @@
1 -- team = team_rel.user_team  
2 -- allow_admin = can? current_user, :admin_team_member, @project  
3 -%li{id: dom_id(team), class: "user_team_row team_#{team.id}"}  
4 - .row  
5 - .span6  
6 - %strong= link_to team.name, team_path(team), title: team.name, class: "dark"  
7 - %br  
8 - %small.cgray Members: #{team.members.count}  
9 -  
10 - .span5.pull-right  
11 - .pull-right  
12 - - if allow_admin  
13 - .left  
14 - = link_to resign_project_team_path(@project, team), method: :delete, confirm: "Are you shure?", class: "btn btn-remove small" do  
15 - %i.icon-minus.icon-white  
app/views/team_members/_team.html.haml
1 -- grouper_project_members(@project).each do |access, members| 1 +- team.each do |access, members|
2 .ui-box 2 .ui-box
3 %h5.title 3 %h5.title
4 = Project.access_options.key(access).pluralize 4 = Project.access_options.key(access).pluralize
5 %small= members.size 5 %small= members.size
6 %ul.well-list 6 %ul.well-list
7 - - members.sort_by(&:user_name).each do |up|  
8 - = render(partial: 'team_members/show', locals: {member: up})  
9 -  
10 -  
11 -:javascript  
12 - $(function(){  
13 - $('.repo-access-select, .project-access-select').live("change", function() {  
14 - $(this.form).submit();  
15 - });  
16 - }) 7 + - members.sort_by(&:user_name).each do |team_member|
  8 + = render 'team_members/team_member', member: team_member
app/views/team_members/_team_member.html.haml 0 → 100644
@@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
  1 +- user = member.user
  2 +- allow_admin = can? current_user, :admin_project, @project
  3 +%li{id: dom_id(user), class: "team_member_row user_#{user.id}"}
  4 + .row
  5 + .span4
  6 + = link_to project_team_member_path(@project, user), title: user.name, class: "dark" do
  7 + = image_tag gravatar_icon(user.email, 40), class: "avatar s32"
  8 + = link_to project_team_member_path(@project, user), title: user.name, class: "dark" do
  9 + %strong= truncate(user.name, lenght: 40)
  10 + %br
  11 + %small.cgray= user.email
  12 +
  13 + .span4.pull-right
  14 + - if allow_admin
  15 + .left
  16 + = form_for(member, as: :team_member, url: project_team_member_path(@project, member.user)) do |f|
  17 + = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2 trigger-submit"
  18 + .pull-right
  19 + - if current_user == user
  20 + %span.label This is you!
  21 + - if @project.namespace_owner == user
  22 + %span.label Owner
  23 + - elsif user.blocked
  24 + %span.label Blocked
  25 + - elsif allow_admin
  26 + = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "btn-tiny btn btn-remove" do
  27 + %i.icon-minus.icon-white
  28 +
app/views/team_members/_teams.html.haml
@@ -1,16 +0,0 @@ @@ -1,16 +0,0 @@
1 -- grouper_project_teams(@project).each do |access, teams|  
2 - .ui-box  
3 - %h5.title  
4 - = UserTeam.access_roles.key(access).pluralize  
5 - %small= teams.size  
6 - %ul.well-list  
7 - - teams.sort_by(&:team_name).each do |tofr|  
8 - = render(partial: 'team_members/show_team', locals: {team_rel: tofr})  
9 -  
10 -  
11 -:javascript  
12 - $(function(){  
13 - $('.repo-access-select, .project-access-select').live("change", function() {  
14 - $(this.form).submit();  
15 - });  
16 - })  
app/views/team_members/index.html.haml
@@ -18,16 +18,39 @@ @@ -18,16 +18,39 @@
18 %hr 18 %hr
19 19
20 .clearfix 20 .clearfix
21 -%div.team-table  
22 - = render partial: "team_members/team", locals: {project: @project} 21 +.row
  22 + .span3
  23 + %ul.nav.nav-pills.nav-stacked
  24 + %li{class: ("active" if !params[:type])}
  25 + = link_to project_team_members_path(type: nil) do
  26 + All
  27 + %li{class: ("active" if params[:type] == 'masters')}
  28 + = link_to project_team_members_path(type: 'masters') do
  29 + Masters
  30 + %span.pull-right= @project.users_projects.masters.count
  31 + %li{class: ("active" if params[:type] == 'developers')}
  32 + = link_to project_team_members_path(type: 'developers') do
  33 + Developers
  34 + %span.pull-right= @project.users_projects.developers.count
  35 + %li{class: ("active" if params[:type] == 'reporters')}
  36 + = link_to project_team_members_path(type: 'reporters') do
  37 + Reporters
  38 + %span.pull-right= @project.users_projects.reporters.count
  39 + %li{class: ("active" if params[:type] == 'guests')}
  40 + = link_to project_team_members_path(type: 'guests') do
  41 + Guests
  42 + %span.pull-right= @project.users_projects.guests.count
23 43
  44 + - if @assigned_teams.present?
  45 + %h5
  46 + Assigned teams
  47 + (#{@project.user_teams.count})
  48 + %div
  49 + = render "team_members/assigned_teams", assigned_teams: @assigned_teams
  50 +
  51 + .span9
  52 + %div.team-table
  53 + = render "team_members/team", team: @team
24 54
25 -%h3.page_title  
26 - Assigned teams  
27 - (#{@project.user_teams.count})  
28 55
29 -%hr  
30 56
31 -.clearfix  
32 -%div.team-table  
33 - = render partial: "team_members/teams", locals: {project: @project}  
app/views/teams/edit.html.haml
1 %h3.page_title= "Edit Team #{@team.name}" 1 %h3.page_title= "Edit Team #{@team.name}"
2 %hr 2 %hr
3 -= form_for @team, url: team_path(@team) do |f|  
4 - - if @team.errors.any?  
5 - .alert.alert-error  
6 - %span= @team.errors.full_messages.first  
7 - .clearfix  
8 - = f.label :name do  
9 - Team name is  
10 - .input  
11 - = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left" 3 +.row
  4 + .span7
  5 + = form_for @team, url: team_path(@team) do |f|
  6 + - if @team.errors.any?
  7 + .alert.alert-error
  8 + %span= @team.errors.full_messages.first
  9 + .clearfix
  10 + = f.label :name do
  11 + Team name is
  12 + .input
  13 + = f.text_field :name, placeholder: "Ex. OpenSource", class: "xlarge left"
  14 +
  15 + .clearfix
  16 + = f.label :path do
  17 + Team path is
  18 + .input
  19 + = f.text_field :path, placeholder: "opensource", class: "xlarge left"
  20 + .form-actions
  21 + = f.submit 'Save team changes', class: "btn btn-save"
  22 + .span5
  23 + .ui-box
  24 + %h5.title Remove team
  25 + .padded.bgred
  26 + %p
  27 + Removed team can not be restored!
  28 + = link_to 'Remove team', team_path(@team), method: :delete, confirm: "You are sure?", class: "btn btn-remove btn-small"
12 29
13 - .clearfix  
14 - = f.label :path do  
15 - Team path is  
16 - .input  
17 - = f.text_field :path, placeholder: "opensource", class: "xxlarge left"  
18 - .form-actions  
19 - = f.submit 'Save team changes', class: "btn btn-primary"  
20 - = link_to 'Delete team', team_path(@team), method: :delete, confirm: "You are shure?", class: "btn btn-remove pull-right"  
app/views/teams/members/_show.html.haml
@@ -10,22 +10,21 @@ @@ -10,22 +10,21 @@
10 %br 10 %br
11 %small.cgray= user.email 11 %small.cgray= user.email
12 12
13 - .span6.pull-right 13 + .span4
14 - if allow_admin 14 - if allow_admin
15 - .left.span2  
16 - = form_for(member, as: :team_member, url: team_member_path(@team, user)) do |f|  
17 - = f.select :permission, options_for_select(UsersProject.access_roles, @team.default_projects_access(user)), {}, class: "medium project-access-select span2"  
18 - .left.span2  
19 - %span  
20 - = check_box_tag :group_admin, true, @team.admin?(user)  
21 - Admin access  
22 - .pull-right  
23 - - if current_user == user  
24 - %span.btn.disabled This is you!  
25 - - if @team.owner == user  
26 - %span.btn.disabled.btn-success Owner  
27 - - elsif user.blocked  
28 - %span.btn.disabled.blocked Blocked  
29 - - elsif allow_admin  
30 - = link_to team_member_path(@team, user), confirm: remove_from_user_team_message(@team, user), method: :delete, class: "btn-tiny btn btn-remove" do  
31 - %i.icon-minus.icon-white 15 + = form_for(member, as: :team_member, url: team_member_path(@team, user)) do |f|
  16 + = f.select :permission, options_for_select(UsersProject.access_roles, @team.default_projects_access(user)), {}, class: "medium trigger-submit"
  17 + %br
  18 + = label_tag do
  19 + = f.check_box :group_admin, class: 'trigger-submit'
  20 + %span Admin access
  21 + .pull-right
  22 + - if current_user == user
  23 + %span.btn.disabled This is you!
  24 + - if @team.owner == user
  25 + %span.btn.disabled Owner
  26 + - elsif user.blocked
  27 + %span.btn.disabled.blocked Blocked
  28 + - elsif allow_admin
  29 + = link_to team_member_path(@team, user), confirm: remove_from_user_team_message(@team, user), method: :delete, class: "btn-tiny btn btn-remove" do
  30 + %i.icon-minus.icon-white
app/views/teams/new.html.haml
@@ -17,3 +17,17 @@ @@ -17,3 +17,17 @@
17 %li All created teams are public (users can view who enter into team and which project are assigned for this team) 17 %li All created teams are public (users can view who enter into team and which project are assigned for this team)
18 %li People within a team see only projects they have access to 18 %li People within a team see only projects they have access to
19 %li You will be able to assign existing projects for team 19 %li You will be able to assign existing projects for team
  20 + %hr
  21 +
  22 + - if current_user.can_create_group?
  23 + .clearfix
  24 + .input.light
  25 + Need a group for several dependent projects?
  26 + = link_to new_group_path, class: "btn btn-tiny" do
  27 + Create a group
  28 + - if current_user.can_create_project?
  29 + .clearfix
  30 + .input.light
  31 + Want to create a project?
  32 + = link_to new_project_path, class: "btn btn-tiny" do
  33 + Create a project
app/workers/post_receive.rb
@@ -21,14 +21,18 @@ class PostReceive @@ -21,14 +21,18 @@ class PostReceive
21 return false 21 return false
22 end 22 end
23 23
24 - # Ignore push from non-gitlab users  
25 - user = if identifier.nil?  
26 - raise identifier.inspect 24 + user = if identifier.blank?
  25 + # Local push from gitlab
27 email = project.repository.commit(newrev).author.email rescue nil 26 email = project.repository.commit(newrev).author.email rescue nil
28 User.find_by_email(email) if email 27 User.find_by_email(email) if email
29 - elsif /^[A-Z0-9._%a-z\-]+@(?:[A-Z0-9a-z\-]+\.)+[A-Za-z]{2,4}$/.match(identifier)  
30 - User.find_by_email(identifier)  
31 - elsif identifier =~ /key/ 28 +
  29 + elsif identifier =~ /\Auser-\d+\Z/
  30 + # git push over http
  31 + user_id = identifier.gsub("user-", "")
  32 + User.find_by_id(user_id)
  33 +
  34 + elsif identifier =~ /\Akey-\d+\Z/
  35 + # git push over ssh
32 key_id = identifier.gsub("key-", "") 36 key_id = identifier.gsub("key-", "")
33 Key.find_by_id(key_id).try(:user) 37 Key.find_by_id(key_id).try(:user)
34 end 38 end
config/gitlab.yml.example
@@ -7,121 +7,132 @@ @@ -7,121 +7,132 @@
7 # 2. Replace gitlab -> host with your domain 7 # 2. Replace gitlab -> host with your domain
8 # 3. Replace gitlab -> email_from 8 # 3. Replace gitlab -> email_from
9 9
10 -#  
11 -# 1. GitLab app settings  
12 -# ==========================  
13 -  
14 -## GitLab settings  
15 -gitlab:  
16 - ## Web server settings  
17 - host: localhost  
18 - port: 80  
19 - https: false  
20 - # Uncomment and customize to run in non-root path  
21 - # Note that ENV['RAILS_RELATIVE_URL_ROOT'] in config/unicorn.rb may need to be changed  
22 - # relative_url_root: /gitlab  
23 -  
24 - # Uncomment and customize if you can't use the default user to run GitLab (default: 'git')  
25 - # user: git  
26 -  
27 - ## Email settings  
28 - # Email address used in the "From" field in mails sent by GitLab  
29 - email_from: gitlab@localhost  
30 -  
31 - # Email address of your support contact (default: same as email_from)  
32 - support_email: support@localhost  
33 -  
34 - ## Project settings  
35 - default_projects_limit: 10  
36 - # signup_enabled: true # default: false - Account passwords are not sent via the email if signup is enabled.  
37 -  
38 -## Gravatar  
39 -gravatar:  
40 - enabled: true # Use user avatar images from Gravatar.com (default: true)  
41 - # plain_url: "http://..." # default: http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=mm  
42 - # ssl_url: "https://..." # default: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=mm  
43 -  
44 -  
45 -  
46 -#  
47 -# 2. Auth settings  
48 -# ==========================  
49 -  
50 -## LDAP settings  
51 -ldap:  
52 - enabled: false  
53 - host: '_your_ldap_server'  
54 - base: '_the_base_where_you_search_for_users'  
55 - port: 636  
56 - uid: 'sAMAccountName'  
57 - method: 'ssl' # "ssl" or "plain"  
58 - bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'  
59 - password: '_the_password_of_the_bind_user'  
60 -  
61 -## Omniauth settings  
62 -omniauth:  
63 - # Enable ability for users  
64 - # Allow logging in via Twitter, Google, etc. using Omniauth providers  
65 - enabled: false  
66 - 10 +production: &base
  11 + #
  12 + # 1. GitLab app settings
  13 + # ==========================
  14 +
  15 + ## GitLab settings
  16 + gitlab:
  17 + ## Web server settings
  18 + host: localhost
  19 + port: 80
  20 + https: false
  21 + # Uncomment and customize to run in non-root path
  22 + # Note that ENV['RAILS_RELATIVE_URL_ROOT'] in config/unicorn.rb may need to be changed
  23 + # relative_url_root: /gitlab
  24 +
  25 + # Uncomment and customize if you can't use the default user to run GitLab (default: 'git')
  26 + # user: git
  27 +
  28 + ## Email settings
  29 + # Email address used in the "From" field in mails sent by GitLab
  30 + email_from: gitlab@localhost
  31 +
  32 + # Email address of your support contact (default: same as email_from)
  33 + support_email: support@localhost
  34 +
  35 + ## Project settings
  36 + default_projects_limit: 10
  37 + # signup_enabled: true # default: false - Account passwords are not sent via the email if signup is enabled.
  38 + # username_changing_enabled: false # default: true - User can change her username/namespace
  39 +
  40 + ## Gravatar
  41 + gravatar:
  42 + enabled: true # Use user avatar images from Gravatar.com (default: true)
  43 + # plain_url: "http://..." # default: http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=mm
  44 + # ssl_url: "https://..." # default: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=mm
  45 +
  46 +
  47 +
  48 + #
  49 + # 2. Auth settings
  50 + # ==========================
  51 +
  52 + ## LDAP settings
  53 + ldap:
  54 + enabled: false
  55 + host: '_your_ldap_server'
  56 + base: '_the_base_where_you_search_for_users'
  57 + port: 636
  58 + uid: 'sAMAccountName'
  59 + method: 'ssl' # "ssl" or "plain"
  60 + bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
  61 + password: '_the_password_of_the_bind_user'
  62 +
  63 + ## Omniauth settings
  64 + omniauth:
  65 + # Enable ability for users
  66 + # Allow logging in via Twitter, Google, etc. using Omniauth providers
  67 + enabled: false
  68 +
  69 + # CAUTION!
  70 + # This allows users to login without having a user account first (default: false)
  71 + # User accounts will be created automatically when authentication was successful.
  72 + allow_single_sign_on: false
  73 + # Locks down those users until they have been cleared by the admin (default: true)
  74 + block_auto_created_users: true
  75 +
  76 + ## Auth providers
  77 + # Uncomment the lines and fill in the data of the auth provider you want to use
  78 + # If your favorite auth provider is not listed you can user others:
  79 + # see https://github.com/gitlabhq/gitlabhq/wiki/Using-Custom-Omniauth-Providers
  80 + # The 'app_id' and 'app_secret' parameters are always passed as the first two
  81 + # arguments, followed by optional 'args' which can be either a hash or an array.
  82 + providers:
  83 + # - { name: 'google_oauth2', app_id: 'YOUR APP ID',
  84 + # app_secret: 'YOUR APP SECRET',
  85 + # args: { access_type: 'offline', approval_prompt: '' } }
  86 + # - { name: 'twitter', app_id: 'YOUR APP ID',
  87 + # app_secret: 'YOUR APP SECRET'}
  88 + # - { name: 'github', app_id: 'YOUR APP ID',
  89 + # app_secret: 'YOUR APP SECRET' }
  90 +
  91 +
  92 +
  93 + #
  94 + # 3. Advanced settings
  95 + # ==========================
  96 +
  97 + # GitLab Satellites
  98 + satellites:
  99 + # Relative paths are relative to Rails.root (default: tmp/repo_satellites/)
  100 + path: /home/git/gitlab-satellites/
  101 +
  102 + ## Backup settings
  103 + backup:
  104 + path: "tmp/backups" # Relative paths are relative to Rails.root (default: tmp/backups/)
  105 + # keep_time: 604800 # default: 0 (forever) (in seconds)
  106 +
  107 + ## GitLab Shell settings
  108 + gitlab_shell:
  109 + # REPOS_PATH MUST NOT BE A SYMLINK!!!
  110 + repos_path: /home/git/repositories/
  111 + hooks_path: /home/git/gitlab-shell/hooks/
  112 +
  113 + # Git over HTTP
  114 + upload_pack: true
  115 + receive_pack: true
  116 +
  117 + # If you use non-standart ssh port you need to specify it
  118 + # ssh_port: 22
  119 +
  120 + ## Git settings
67 # CAUTION! 121 # CAUTION!
68 - # This allows users to login without having a user account first (default: false)  
69 - # User accounts will be created automatically when authentication was successful.  
70 - allow_single_sign_on: false  
71 - # Locks down those users until they have been cleared by the admin (default: true)  
72 - block_auto_created_users: true  
73 -  
74 - ## Auth providers  
75 - # Uncomment the lines and fill in the data of the auth provider you want to use  
76 - # If your favorite auth provider is not listed you can user others:  
77 - # see https://github.com/gitlabhq/gitlabhq/wiki/Using-Custom-Omniauth-Providers  
78 - # The 'app_id' and 'app_secret' parameters are always passed as the first two  
79 - # arguments, followed by optional 'args' which can be either a hash or an array.  
80 - providers:  
81 - # - { name: 'google_oauth2', app_id: 'YOUR APP ID',  
82 - # app_secret: 'YOUR APP SECRET',  
83 - # args: { access_type: 'offline', approval_prompt: '' } }  
84 - # - { name: 'twitter', app_id: 'YOUR APP ID',  
85 - # app_secret: 'YOUR APP SECRET'}  
86 - # - { name: 'github', app_id: 'YOUR APP ID',  
87 - # app_secret: 'YOUR APP SECRET' }  
88 -  
89 -  
90 -  
91 -#  
92 -# 3. Advanced settings  
93 -# ==========================  
94 -  
95 -# GitLab Satellites  
96 -satellites:  
97 - # Relative paths are relative to Rails.root (default: tmp/repo_satellites/)  
98 - path: /home/git/gitlab-satellites/  
99 -  
100 -## Backup settings  
101 -backup:  
102 - path: "tmp/backups" # Relative paths are relative to Rails.root (default: tmp/backups/)  
103 - # keep_time: 604800 # default: 0 (forever) (in seconds)  
104 -  
105 -## GitLab Shell settings  
106 -gitlab_shell:  
107 - # REPOS_PATH MUST NOT BE A SYMLINK!!!  
108 - repos_path: /home/git/repositories/  
109 - hooks_path: /home/git/gitlab-shell/hooks/  
110 -  
111 - # Git over HTTP  
112 - upload_pack: true  
113 - receive_pack: true  
114 -  
115 - # If you use non-standart ssh port you need to specify it  
116 - # ssh_port: 22  
117 -  
118 -## Git settings  
119 -# CAUTION!  
120 -# Use the default values unless you really know what you are doing  
121 -git:  
122 - bin_path: /usr/bin/git  
123 - # Max size of git object like commit, in bytes  
124 - # This value can be increased if you have a very large commits  
125 - max_size: 5242880 # 5.megabytes  
126 - # Git timeout to read commit, in seconds  
127 - timeout: 10 122 + # Use the default values unless you really know what you are doing
  123 + git:
  124 + bin_path: /usr/bin/git
  125 + # Max size of git object like commit, in bytes
  126 + # This value can be increased if you have a very large commits
  127 + max_size: 5242880 # 5.megabytes
  128 + # Git timeout to read commit, in seconds
  129 + timeout: 10
  130 +
  131 +development:
  132 + <<: *base
  133 +
  134 +test:
  135 + <<: *base
  136 +
  137 +staging:
  138 + <<: *base
config/initializers/1_settings.rb
1 class Settings < Settingslogic 1 class Settings < Settingslogic
2 source "#{Rails.root}/config/gitlab.yml" 2 source "#{Rails.root}/config/gitlab.yml"
  3 + namespace Rails.env
3 4
4 class << self 5 class << self
5 def gitlab_on_non_standard_port? 6 def gitlab_on_non_standard_port?
@@ -56,6 +57,7 @@ Settings.gitlab[&#39;support_email&#39;] ||= Settings.gitlab.email_from @@ -56,6 +57,7 @@ Settings.gitlab[&#39;support_email&#39;] ||= Settings.gitlab.email_from
56 Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url) 57 Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url)
57 Settings.gitlab['user'] ||= 'git' 58 Settings.gitlab['user'] ||= 'git'
58 Settings.gitlab['signup_enabled'] ||= false 59 Settings.gitlab['signup_enabled'] ||= false
  60 +Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
59 61
60 # 62 #
61 # Gravatar 63 # Gravatar
config/initializers/2_app.rb
1 module Gitlab 1 module Gitlab
2 - Version = File.read(Rails.root.join("VERSION"))  
3 - Revision = `git log --pretty=format:'%h' -n 1` 2 + VERSION = File.read(Rails.root.join("VERSION")).strip
  3 + REVISION = `git log --pretty=format:'%h' -n 1`
4 4
5 def self.config 5 def self.config
6 Settings 6 Settings
config/routes.rb
@@ -47,6 +47,11 @@ Gitlab::Application.routes.draw do @@ -47,6 +47,11 @@ Gitlab::Application.routes.draw do
47 end 47 end
48 48
49 # 49 #
  50 + # Attachments serving
  51 + #
  52 + get 'files/:type/:id/:filename' => 'files#download', constraints: { id: /\d+/, type: /[a-z]+/, filename: /[a-zA-Z.0-9_\-\+]+/ }
  53 +
  54 + #
50 # Admin Area 55 # Admin Area
51 # 56 #
52 namespace :admin do 57 namespace :admin do
db/fixtures/development/02_source_code.rb
1 -root = Gitlab.config.gitolite.repos_path 1 +root = Gitlab.config.gitlab_shell.repos_path
2 2
3 projects = [ 3 projects = [
4 { path: 'underscore.git', git: 'https://github.com/documentcloud/underscore.git' }, 4 { path: 'underscore.git', git: 'https://github.com/documentcloud/underscore.git' },
db/fixtures/development/09_issues.rb
@@ -16,7 +16,7 @@ Gitlab::Seeder.quiet do @@ -16,7 +16,7 @@ Gitlab::Seeder.quiet do
16 project_id: project.id, 16 project_id: project.id,
17 author_id: user_id, 17 author_id: user_id,
18 assignee_id: user_id, 18 assignee_id: user_id,
19 - closed: [true, false].sample, 19 + state: ['opened', 'closed'].sample,
20 milestone: project.milestones.sample, 20 milestone: project.milestones.sample,
21 title: Faker::Lorem.sentence(6) 21 title: Faker::Lorem.sentence(6)
22 }]) 22 }])
db/fixtures/development/10_merge_requests.rb
@@ -17,7 +17,7 @@ Gitlab::Seeder.quiet do @@ -17,7 +17,7 @@ Gitlab::Seeder.quiet do
17 project_id: project.id, 17 project_id: project.id,
18 author_id: user_id, 18 author_id: user_id,
19 assignee_id: user_id, 19 assignee_id: user_id,
20 - closed: [true, false].sample, 20 + state: ['opened', 'closed'].sample,
21 milestone: project.milestones.sample, 21 milestone: project.milestones.sample,
22 title: Faker::Lorem.sentence(6) 22 title: Faker::Lorem.sentence(6)
23 }]) 23 }])
db/migrate/20130214154045_rename_state_to_merge_status_in_milestone.rb 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +class RenameStateToMergeStatusInMilestone < ActiveRecord::Migration
  2 + def change
  3 + rename_column :merge_requests, :state, :merge_status
  4 + end
  5 +end
db/migrate/20130218140952_add_state_to_issue.rb 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +class AddStateToIssue < ActiveRecord::Migration
  2 + def change
  3 + add_column :issues, :state, :string
  4 + end
  5 +end
db/migrate/20130218141038_add_state_to_merge_request.rb 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +class AddStateToMergeRequest < ActiveRecord::Migration
  2 + def change
  3 + add_column :merge_requests, :state, :string
  4 + end
  5 +end
db/migrate/20130218141117_add_state_to_milestone.rb 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +class AddStateToMilestone < ActiveRecord::Migration
  2 + def change
  3 + add_column :milestones, :state, :string
  4 + end
  5 +end
db/migrate/20130218141258_convert_closed_to_state_in_issue.rb 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +class ConvertClosedToStateInIssue < ActiveRecord::Migration
  2 + def up
  3 + Issue.transaction do
  4 + Issue.where(closed: true).update_all("state = 'closed'")
  5 + Issue.where(closed: false).update_all("state = 'opened'")
  6 + end
  7 + end
  8 +
  9 + def down
  10 + Issue.transaction do
  11 + Issue.where(state: :closed).update_all("closed = 1")
  12 + end
  13 + end
  14 +end
db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb 0 → 100644
@@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
  1 +class ConvertClosedToStateInMergeRequest < ActiveRecord::Migration
  2 + def up
  3 + MergeRequest.transaction do
  4 + MergeRequest.where("closed = 1 AND merged = 1").update_all("state = 'merged'")
  5 + MergeRequest.where("closed = 1 AND merged = 0").update_all("state = 'closed'")
  6 + MergeRequest.where("closed = 0").update_all("state = 'opened'")
  7 + end
  8 + end
  9 +
  10 + def down
  11 + MergeRequest.transaction do
  12 + MergeRequest.where(state: :closed).update_all("closed = 1")
  13 + MergeRequest.where(state: :merged).update_all("closed = 1, merged = 1")
  14 + end
  15 + end
  16 +end
db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +class ConvertClosedToStateInMilestone < ActiveRecord::Migration
  2 + def up
  3 + Milestone.transaction do
  4 + Milestone.where(closed: false).update_all("state = 'opened'")
  5 + Milestone.where(closed: false).update_all("state = 'active'")
  6 + end
  7 + end
  8 +
  9 + def down
  10 + Milestone.transaction do
  11 + Milestone.where(state: :closed).update_all("closed = 1")
  12 + end
  13 + end
  14 +end
db/migrate/20130218141444_remove_merged_from_merge_request.rb 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +class RemoveMergedFromMergeRequest < ActiveRecord::Migration
  2 + def up
  3 + remove_column :merge_requests, :merged
  4 + end
  5 +
  6 + def down
  7 + add_column :merge_requests, :merged, :boolean, default: true, null: false
  8 + end
  9 +end
db/migrate/20130218141507_remove_closed_from_issue.rb 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +class RemoveClosedFromIssue < ActiveRecord::Migration
  2 + def up
  3 + remove_column :issues, :closed
  4 + end
  5 +
  6 + def down
  7 + add_column :issues, :closed, :boolean
  8 + end
  9 +end
db/migrate/20130218141536_remove_closed_from_merge_request.rb 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +class RemoveClosedFromMergeRequest < ActiveRecord::Migration
  2 + def up
  3 + remove_column :merge_requests, :closed
  4 + end
  5 +
  6 + def down
  7 + add_column :merge_requests, :closed, :boolean
  8 + end
  9 +end
db/migrate/20130218141554_remove_closed_from_milestone.rb 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +class RemoveClosedFromMilestone < ActiveRecord::Migration
  2 + def up
  3 + remove_column :milestones, :closed
  4 + end
  5 +
  6 + def down
  7 + add_column :milestones, :closed, :boolean
  8 + end
  9 +end
@@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
11 # 11 #
12 # It's strongly recommended to check this file into your version control system. 12 # It's strongly recommended to check this file into your version control system.
13 13
14 -ActiveRecord::Schema.define(:version => 20130131070232) do 14 +ActiveRecord::Schema.define(:version => 20130218141554) do
15 15
16 create_table "events", :force => true do |t| 16 create_table "events", :force => true do |t|
17 t.string "target_type" 17 t.string "target_type"
@@ -37,18 +37,17 @@ ActiveRecord::Schema.define(:version =&gt; 20130131070232) do @@ -37,18 +37,17 @@ ActiveRecord::Schema.define(:version =&gt; 20130131070232) do
37 t.integer "assignee_id" 37 t.integer "assignee_id"
38 t.integer "author_id" 38 t.integer "author_id"
39 t.integer "project_id" 39 t.integer "project_id"
40 - t.datetime "created_at", :null => false  
41 - t.datetime "updated_at", :null => false  
42 - t.boolean "closed", :default => false, :null => false 40 + t.datetime "created_at", :null => false
  41 + t.datetime "updated_at", :null => false
43 t.integer "position", :default => 0 42 t.integer "position", :default => 0
44 t.string "branch_name" 43 t.string "branch_name"
45 t.text "description" 44 t.text "description"
46 t.integer "milestone_id" 45 t.integer "milestone_id"
  46 + t.string "state"
47 end 47 end
48 48
49 add_index "issues", ["assignee_id"], :name => "index_issues_on_assignee_id" 49 add_index "issues", ["assignee_id"], :name => "index_issues_on_assignee_id"
50 add_index "issues", ["author_id"], :name => "index_issues_on_author_id" 50 add_index "issues", ["author_id"], :name => "index_issues_on_author_id"
51 - add_index "issues", ["closed"], :name => "index_issues_on_closed"  
52 add_index "issues", ["created_at"], :name => "index_issues_on_created_at" 51 add_index "issues", ["created_at"], :name => "index_issues_on_created_at"
53 add_index "issues", ["milestone_id"], :name => "index_issues_on_milestone_id" 52 add_index "issues", ["milestone_id"], :name => "index_issues_on_milestone_id"
54 add_index "issues", ["project_id"], :name => "index_issues_on_project_id" 53 add_index "issues", ["project_id"], :name => "index_issues_on_project_id"
@@ -69,25 +68,23 @@ ActiveRecord::Schema.define(:version =&gt; 20130131070232) do @@ -69,25 +68,23 @@ ActiveRecord::Schema.define(:version =&gt; 20130131070232) do
69 add_index "keys", ["user_id"], :name => "index_keys_on_user_id" 68 add_index "keys", ["user_id"], :name => "index_keys_on_user_id"
70 69
71 create_table "merge_requests", :force => true do |t| 70 create_table "merge_requests", :force => true do |t|
72 - t.string "target_branch", :null => false  
73 - t.string "source_branch", :null => false  
74 - t.integer "project_id", :null => false 71 + t.string "target_branch", :null => false
  72 + t.string "source_branch", :null => false
  73 + t.integer "project_id", :null => false
75 t.integer "author_id" 74 t.integer "author_id"
76 t.integer "assignee_id" 75 t.integer "assignee_id"
77 t.string "title" 76 t.string "title"
78 - t.boolean "closed", :default => false, :null => false  
79 - t.datetime "created_at", :null => false  
80 - t.datetime "updated_at", :null => false 77 + t.datetime "created_at", :null => false
  78 + t.datetime "updated_at", :null => false
81 t.text "st_commits", :limit => 2147483647 79 t.text "st_commits", :limit => 2147483647
82 t.text "st_diffs", :limit => 2147483647 80 t.text "st_diffs", :limit => 2147483647
83 - t.boolean "merged", :default => false, :null => false  
84 - t.integer "state", :default => 1, :null => false 81 + t.integer "merge_status", :default => 1, :null => false
85 t.integer "milestone_id" 82 t.integer "milestone_id"
  83 + t.string "state"
86 end 84 end
87 85
88 add_index "merge_requests", ["assignee_id"], :name => "index_merge_requests_on_assignee_id" 86 add_index "merge_requests", ["assignee_id"], :name => "index_merge_requests_on_assignee_id"
89 add_index "merge_requests", ["author_id"], :name => "index_merge_requests_on_author_id" 87 add_index "merge_requests", ["author_id"], :name => "index_merge_requests_on_author_id"
90 - add_index "merge_requests", ["closed"], :name => "index_merge_requests_on_closed"  
91 add_index "merge_requests", ["created_at"], :name => "index_merge_requests_on_created_at" 88 add_index "merge_requests", ["created_at"], :name => "index_merge_requests_on_created_at"
92 add_index "merge_requests", ["milestone_id"], :name => "index_merge_requests_on_milestone_id" 89 add_index "merge_requests", ["milestone_id"], :name => "index_merge_requests_on_milestone_id"
93 add_index "merge_requests", ["project_id"], :name => "index_merge_requests_on_project_id" 90 add_index "merge_requests", ["project_id"], :name => "index_merge_requests_on_project_id"
@@ -96,13 +93,13 @@ ActiveRecord::Schema.define(:version =&gt; 20130131070232) do @@ -96,13 +93,13 @@ ActiveRecord::Schema.define(:version =&gt; 20130131070232) do
96 add_index "merge_requests", ["title"], :name => "index_merge_requests_on_title" 93 add_index "merge_requests", ["title"], :name => "index_merge_requests_on_title"
97 94
98 create_table "milestones", :force => true do |t| 95 create_table "milestones", :force => true do |t|
99 - t.string "title", :null => false  
100 - t.integer "project_id", :null => false 96 + t.string "title", :null => false
  97 + t.integer "project_id", :null => false
101 t.text "description" 98 t.text "description"
102 t.date "due_date" 99 t.date "due_date"
103 - t.boolean "closed", :default => false, :null => false  
104 - t.datetime "created_at", :null => false  
105 - t.datetime "updated_at", :null => false 100 + t.datetime "created_at", :null => false
  101 + t.datetime "updated_at", :null => false
  102 + t.string "state"
106 end 103 end
107 104
108 add_index "milestones", ["due_date"], :name => "index_milestones_on_due_date" 105 add_index "milestones", ["due_date"], :name => "index_milestones_on_due_date"
doc/api/milestones.md
@@ -34,7 +34,6 @@ POST /projects/:id/milestones @@ -34,7 +34,6 @@ POST /projects/:id/milestones
34 Parameters: 34 Parameters:
35 35
36 + `id` (required) - The ID of a project 36 + `id` (required) - The ID of a project
37 -+ `milestone_id` (required) - The ID of a project milestone  
38 + `title` (required) - The title of an milestone 37 + `title` (required) - The title of an milestone
39 + `description` (optional) - The description of the milestone 38 + `description` (optional) - The description of the milestone
40 + `due_date` (optional) - The due date of the milestone 39 + `due_date` (optional) - The due date of the milestone
doc/api/projects.md
@@ -23,7 +23,7 @@ GET /projects @@ -23,7 +23,7 @@ GET /projects
23 "blocked": false, 23 "blocked": false,
24 "created_at": "2012-05-23T08:00:58Z" 24 "created_at": "2012-05-23T08:00:58Z"
25 }, 25 },
26 - "private": true, 26 + "public": true,
27 "path": "rails", 27 "path": "rails",
28 "path_with_namespace": "rails/rails", 28 "path_with_namespace": "rails/rails",
29 "issues_enabled": false, 29 "issues_enabled": false,
@@ -45,7 +45,7 @@ GET /projects @@ -45,7 +45,7 @@ GET /projects
45 "blocked": false, 45 "blocked": false,
46 "created_at": "2012-05-23T08:00:58Z" 46 "created_at": "2012-05-23T08:00:58Z"
47 }, 47 },
48 - "private": true, 48 + "public": true,
49 "path": "gitlab", 49 "path": "gitlab",
50 "path_with_namespace": "randx/gitlab", 50 "path_with_namespace": "randx/gitlab",
51 "issues_enabled": true, 51 "issues_enabled": true,
@@ -89,7 +89,7 @@ Parameters: @@ -89,7 +89,7 @@ Parameters:
89 "blocked": false, 89 "blocked": false,
90 "created_at": "2012-05-23T08:00:58Z" 90 "created_at": "2012-05-23T08:00:58Z"
91 }, 91 },
92 - "private": true, 92 + "public": true,
93 "path": "gitlab", 93 "path": "gitlab",
94 "path_with_namespace": "randx/gitlab", 94 "path_with_namespace": "randx/gitlab",
95 "issues_enabled": true, 95 "issues_enabled": true,
doc/install/databases.md
@@ -27,7 +27,7 @@ GitLab supports the following databases: @@ -27,7 +27,7 @@ GitLab supports the following databases:
27 mysql> \q 27 mysql> \q
28 28
29 # Try connecting to the new database with the new user 29 # Try connecting to the new database with the new user
30 - sudo -u gitlab -H mysql -u gitlab -p -D gitlabhq_production 30 + sudo -u git -H mysql -u gitlab -p -D gitlabhq_production
31 31
32 ## PostgreSQL 32 ## PostgreSQL
33 33
@@ -47,5 +47,5 @@ GitLab supports the following databases: @@ -47,5 +47,5 @@ GitLab supports the following databases:
47 template1=# \q 47 template1=# \q
48 48
49 # Try connecting to the new database with the new user 49 # Try connecting to the new database with the new user
50 - sudo -u gitlab -H psql -d gitlabhq_production 50 + sudo -u git -H psql -d gitlabhq_production
51 51
doc/install/installation.md
1 This installation guide was created for Debian/Ubuntu and tested on it. 1 This installation guide was created for Debian/Ubuntu and tested on it.
2 2
3 -Please read `doc/install/requirements.md` for hardware and platform requirements. 3 +Please read [`doc/install/requirements.md`](./requirements.md) for hardware and platform requirements.
4 4
5 5
6 **Important Note:** 6 **Important Note:**
@@ -8,12 +8,13 @@ The following steps have been known to work. @@ -8,12 +8,13 @@ The following steps have been known to work.
8 If you deviate from this guide, do it with caution and make sure you don't 8 If you deviate from this guide, do it with caution and make sure you don't
9 violate any assumptions GitLab makes about its environment. 9 violate any assumptions GitLab makes about its environment.
10 For things like AWS installation scripts, init scripts or config files for 10 For things like AWS installation scripts, init scripts or config files for
11 -alternative web server have a look at the "Advanced Setup Tips" section. 11 +alternative web server have a look at the [`Advanced Setup
  12 +Tips`](./installation.md#advanced-setup-tips) section.
12 13
13 14
14 **Important Note:** 15 **Important Note:**
15 If you find a bug/error in this guide please submit an issue or pull request 16 If you find a bug/error in this guide please submit an issue or pull request
16 -following the contribution guide (see `CONTRIBUTING.md`). 17 +following the [`contribution guide`](../../CONTRIBUTING.md).
17 18
18 - - - 19 - - -
19 20
@@ -24,7 +25,7 @@ The GitLab installation consists of setting up the following components: @@ -24,7 +25,7 @@ The GitLab installation consists of setting up the following components:
24 1. Packages / Dependencies 25 1. Packages / Dependencies
25 2. Ruby 26 2. Ruby
26 3. System Users 27 3. System Users
27 -4. Gitolite 28 +4. GitLab shell
28 5. Database 29 5. Database
29 6. GitLab 30 6. GitLab
30 7. Nginx 31 7. Nginx
@@ -32,16 +33,13 @@ The GitLab installation consists of setting up the following components: @@ -32,16 +33,13 @@ The GitLab installation consists of setting up the following components:
32 33
33 # 1. Packages / Dependencies 34 # 1. Packages / Dependencies
34 35
35 -`sudo` is not installed on Debian by default. If you don't have it you'll need  
36 -to install it first. 36 +`sudo` is not installed on Debian by default. Make sure your system is
  37 +up-to-date and install it.
37 38
38 # run as root 39 # run as root
39 - apt-get update && apt-get upgrade && apt-get install sudo  
40 -  
41 -Make sure your system is up-to-date:  
42 -  
43 - sudo apt-get update  
44 - sudo apt-get upgrade 40 + apt-get update
  41 + apt-get upgrade
  42 + apt-get install sudo
45 43
46 **Note:** 44 **Note:**
47 Vim is an editor that is used here whenever there are files that need to be 45 Vim is an editor that is used here whenever there are files that need to be
@@ -96,25 +94,24 @@ Create a `git` user for Gitlab: @@ -96,25 +94,24 @@ Create a `git` user for Gitlab:
96 94
97 # 4. GitLab shell 95 # 4. GitLab shell
98 96
99 - # login as git 97 + # Login as git
100 sudo su git 98 sudo su git
101 99
102 - # go to home directory 100 + # Go to home directory
103 cd /home/git 101 cd /home/git
104 102
105 - # clone gitlab shell 103 + # Clone gitlab shell
106 git clone https://github.com/gitlabhq/gitlab-shell.git 104 git clone https://github.com/gitlabhq/gitlab-shell.git
107 105
108 - # setup 106 + # Setup
109 cd gitlab-shell 107 cd gitlab-shell
110 cp config.yml.example config.yml 108 cp config.yml.example config.yml
111 ./bin/install 109 ./bin/install
112 110
113 111
114 -  
115 # 5. Database 112 # 5. Database
116 113
117 -To setup the MySQL/PostgreSQL database and dependencies please see [`doc/install/databases.md`](./databases.md) . 114 +To setup the MySQL/PostgreSQL database and dependencies please see [`doc/install/databases.md`](./databases.md).
118 115
119 116
120 # 6. GitLab 117 # 6. GitLab
@@ -154,9 +151,13 @@ do so with caution! @@ -154,9 +151,13 @@ do so with caution!
154 sudo chmod -R u+rwX log/ 151 sudo chmod -R u+rwX log/
155 sudo chmod -R u+rwX tmp/ 152 sudo chmod -R u+rwX tmp/
156 153
157 - # Make directory for satellites 154 + # Create directory for satellites
158 sudo -u git -H mkdir /home/git/gitlab-satellites 155 sudo -u git -H mkdir /home/git/gitlab-satellites
159 156
  157 + # Create directory for pids and make sure GitLab can write to it
  158 + sudo -u git -H mkdir tmp/pids/
  159 + sudo chmod -R u+rwX tmp/pids/
  160 +
160 # Copy the example Unicorn config 161 # Copy the example Unicorn config
161 sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb 162 sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb
162 163
@@ -187,7 +188,9 @@ Make sure to update username/password in config/database.yml. @@ -187,7 +188,9 @@ Make sure to update username/password in config/database.yml.
187 188
188 189
189 ## Initialise Database and Activate Advanced Features 190 ## Initialise Database and Activate Advanced Features
190 - 191 +
  192 + sudo -u git -H bundle exec rake db:setup RAILS_ENV=production
  193 + sudo -u git -H bundle exec rake db:seed_fu RAILS_ENV=production
191 sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production 194 sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
192 195
193 196
@@ -205,7 +208,7 @@ Make GitLab start on boot: @@ -205,7 +208,7 @@ Make GitLab start on boot:
205 208
206 ## Check Application Status 209 ## Check Application Status
207 210
208 -Check if GitLab and its environment is configured correctly: 211 +Check if GitLab and its environment are configured correctly:
209 212
210 sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production 213 sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
211 214
@@ -227,7 +230,7 @@ However there are still a few steps left. @@ -227,7 +230,7 @@ However there are still a few steps left.
227 230
228 **Note:** 231 **Note:**
229 If you can't or don't want to use Nginx as your web server, have a look at the 232 If you can't or don't want to use Nginx as your web server, have a look at the
230 -"Advanced Setup Tips" section. 233 +[`Advanced Setup Tips`](./installation.md#advanced-setup-tips) section.
231 234
232 ## Installation 235 ## Installation
233 sudo apt-get install nginx 236 sudo apt-get install nginx
@@ -244,11 +247,11 @@ Make sure to edit the config file to match your setup: @@ -244,11 +247,11 @@ Make sure to edit the config file to match your setup:
244 # Change **YOUR_SERVER_IP** and **YOUR_SERVER_FQDN** 247 # Change **YOUR_SERVER_IP** and **YOUR_SERVER_FQDN**
245 # to the IP address and fully-qualified domain name 248 # to the IP address and fully-qualified domain name
246 # of your host serving GitLab 249 # of your host serving GitLab
247 - sudo vim /etc/nginx/sites-enabled/gitlab 250 + sudo vim /etc/nginx/sites-available/gitlab
248 251
249 ## Restart 252 ## Restart
250 253
251 - sudo /etc/init.d/nginx restart 254 + sudo service nginx restart
252 255
253 256
254 # Done! 257 # Done!
@@ -282,7 +285,7 @@ a different host, you can configure its connection string via the @@ -282,7 +285,7 @@ a different host, you can configure its connection string via the
282 285
283 ## Custom SSH Connection 286 ## Custom SSH Connection
284 287
285 -If you are running SSH on a non-standard port, you must change the gitlab user'S SSH config. 288 +If you are running SSH on a non-standard port, you must change the gitlab user's SSH config.
286 289
287 # Add to /home/git/.ssh/config 290 # Add to /home/git/.ssh/config
288 host localhost # Give your setup a name (here: override localhost) 291 host localhost # Give your setup a name (here: override localhost)
features/steps/profile/profile_ssh_keys.rb
@@ -43,6 +43,6 @@ class ProfileSshKeys &lt; Spinach::FeatureSteps @@ -43,6 +43,6 @@ class ProfileSshKeys &lt; Spinach::FeatureSteps
43 end 43 end
44 44
45 And 'I have ssh key "ssh-rsa Work"' do 45 And 'I have ssh key "ssh-rsa Work"' do
46 - create(:key, :user => @user, :title => "ssh-rsa Work", :key => "jfKLJDFKSFJSHFJssh-rsa Work") 46 + create(:key, :user => @user, :title => "ssh-rsa Work", :key => "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+L3TbFegm3k8QjejSwemk4HhlRh+DuN679Pc5ckqE/MPhVtE/+kZQDYCTB284GiT2aIoGzmZ8ee9TkaoejAsBwlA+Wz2Q3vhz65X6sMgalRwpdJx8kSEUYV8ZPV3MZvPo8KdNg993o4jL6G36GDW4BPIyO6FPZhfsawdf6liVD0Xo5kibIK7B9VoE178cdLQtLpS2YolRwf5yy6XR6hbbBGQR+6xrGOdP16eGZDb1CE2bMvvJijjloFqPscGktWOqW+nfh5txwFfBzlfARDTBsS8WZtg3Yoj1kn33kPsWRlgHfNutFRAIynDuDdQzQq8tTtVwm+Yi75RfcPHW8y3P Work")
47 end 47 end
48 end 48 end
features/steps/project/project_issues.rb
@@ -122,10 +122,9 @@ class ProjectIssues &lt; Spinach::FeatureSteps @@ -122,10 +122,9 @@ class ProjectIssues &lt; Spinach::FeatureSteps
122 122
123 And 'project "Shop" have "Release 0.3" closed issue' do 123 And 'project "Shop" have "Release 0.3" closed issue' do
124 project = Project.find_by_name("Shop") 124 project = Project.find_by_name("Shop")
125 - create(:issue, 125 + create(:closed_issue,
126 :title => "Release 0.3", 126 :title => "Release 0.3",
127 :project => project, 127 :project => project,
128 - :author => project.users.first,  
129 - :closed => true) 128 + :author => project.users.first)
130 end 129 end
131 end 130 end
features/steps/project/project_merge_requests.rb
@@ -26,7 +26,7 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps @@ -26,7 +26,7 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
26 26
27 Then 'I should see closed merge request "Bug NS-04"' do 27 Then 'I should see closed merge request "Bug NS-04"' do
28 mr = MergeRequest.find_by_title("Bug NS-04") 28 mr = MergeRequest.find_by_title("Bug NS-04")
29 - mr.closed.should be_true 29 + mr.closed?.should be_true
30 page.should have_content "Closed by" 30 page.should have_content "Closed by"
31 end 31 end
32 32
@@ -80,11 +80,10 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps @@ -80,11 +80,10 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
80 80
81 And 'project "Shop" have "Feature NS-03" closed merge request' do 81 And 'project "Shop" have "Feature NS-03" closed merge request' do
82 project = Project.find_by_name("Shop") 82 project = Project.find_by_name("Shop")
83 - create(:merge_request, 83 + create(:closed_merge_request,
84 title: "Feature NS-03", 84 title: "Feature NS-03",
85 project: project, 85 project: project,
86 - author: project.users.first,  
87 - closed: true) 86 + author: project.users.first)
88 end 87 end
89 88
90 And 'I switch to the diff tab' do 89 And 'I switch to the diff tab' do
lib/api/entities.rb
@@ -20,7 +20,7 @@ module Gitlab @@ -20,7 +20,7 @@ module Gitlab
20 class Project < Grape::Entity 20 class Project < Grape::Entity
21 expose :id, :name, :description, :default_branch 21 expose :id, :name, :description, :default_branch
22 expose :owner, using: Entities::UserBasic 22 expose :owner, using: Entities::UserBasic
23 - expose :private_flag, as: :private 23 + expose :public
24 expose :path, :path_with_namespace 24 expose :path, :path_with_namespace
25 expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at 25 expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at
26 expose :namespace 26 expose :namespace
@@ -35,12 +35,11 @@ module Gitlab @@ -35,12 +35,11 @@ module Gitlab
35 class Group < Grape::Entity 35 class Group < Grape::Entity
36 expose :id, :name, :path, :owner_id 36 expose :id, :name, :path, :owner_id
37 end 37 end
38 - 38 +
39 class GroupDetail < Group 39 class GroupDetail < Group
40 expose :projects, using: Entities::Project 40 expose :projects, using: Entities::Project
41 end 41 end
42 42
43 -  
44 class RepoObject < Grape::Entity 43 class RepoObject < Grape::Entity
45 expose :name, :commit 44 expose :name, :commit
46 expose :protected do |repo, options| 45 expose :protected do |repo, options|
@@ -63,7 +62,7 @@ module Gitlab @@ -63,7 +62,7 @@ module Gitlab
63 class Milestone < Grape::Entity 62 class Milestone < Grape::Entity
64 expose :id 63 expose :id
65 expose (:project_id) {|milestone| milestone.project.id} 64 expose (:project_id) {|milestone| milestone.project.id}
66 - expose :title, :description, :due_date, :closed, :updated_at, :created_at 65 + expose :title, :description, :due_date, :state, :updated_at, :created_at
67 end 66 end
68 67
69 class Issue < Grape::Entity 68 class Issue < Grape::Entity
@@ -73,7 +72,7 @@ module Gitlab @@ -73,7 +72,7 @@ module Gitlab
73 expose :label_list, as: :labels 72 expose :label_list, as: :labels
74 expose :milestone, using: Entities::Milestone 73 expose :milestone, using: Entities::Milestone
75 expose :assignee, :author, using: Entities::UserBasic 74 expose :assignee, :author, using: Entities::UserBasic
76 - expose :closed, :updated_at, :created_at 75 + expose :state, :updated_at, :created_at
77 end 76 end
78 77
79 class SSHKey < Grape::Entity 78 class SSHKey < Grape::Entity
@@ -81,7 +80,7 @@ module Gitlab @@ -81,7 +80,7 @@ module Gitlab
81 end 80 end
82 81
83 class MergeRequest < Grape::Entity 82 class MergeRequest < Grape::Entity
84 - expose :id, :target_branch, :source_branch, :project_id, :title, :closed, :merged 83 + expose :id, :target_branch, :source_branch, :project_id, :title, :state
85 expose :author, :assignee, using: Entities::UserBasic 84 expose :author, :assignee, using: Entities::UserBasic
86 end 85 end
87 86
lib/api/internal.rb
@@ -40,7 +40,9 @@ module Gitlab @@ -40,7 +40,9 @@ module Gitlab
40 40
41 get "/check" do 41 get "/check" do
42 { 42 {
43 - api_version: '3' 43 + api_version: Gitlab::API.version,
  44 + gitlab_version: Gitlab::VERSION,
  45 + gitlab_rev: Gitlab::REVISION,
44 } 46 }
45 end 47 end
46 end 48 end
lib/api/issues.rb
@@ -69,14 +69,14 @@ module Gitlab @@ -69,14 +69,14 @@ module Gitlab
69 # assignee_id (optional) - The ID of a user to assign issue 69 # assignee_id (optional) - The ID of a user to assign issue
70 # milestone_id (optional) - The ID of a milestone to assign issue 70 # milestone_id (optional) - The ID of a milestone to assign issue
71 # labels (optional) - The labels of an issue 71 # labels (optional) - The labels of an issue
72 - # closed (optional) - The state of an issue (0 = false, 1 = true) 72 + # state (optional) - The state of an issue (close|reopen)
73 # Example Request: 73 # Example Request:
74 # PUT /projects/:id/issues/:issue_id 74 # PUT /projects/:id/issues/:issue_id
75 put ":id/issues/:issue_id" do 75 put ":id/issues/:issue_id" do
76 @issue = user_project.issues.find(params[:issue_id]) 76 @issue = user_project.issues.find(params[:issue_id])
77 authorize! :modify_issue, @issue 77 authorize! :modify_issue, @issue
78 78
79 - attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :closed] 79 + attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event]
80 attrs[:label_list] = params[:labels] if params[:labels].present? 80 attrs[:label_list] = params[:labels] if params[:labels].present?
81 IssueObserver.current_user = current_user 81 IssueObserver.current_user = current_user
82 if @issue.update_attributes attrs 82 if @issue.update_attributes attrs
lib/api/merge_requests.rb
@@ -91,12 +91,12 @@ module Gitlab @@ -91,12 +91,12 @@ module Gitlab
91 # target_branch - The target branch 91 # target_branch - The target branch
92 # assignee_id - Assignee user ID 92 # assignee_id - Assignee user ID
93 # title - Title of MR 93 # title - Title of MR
94 - # closed - Status of MR. true - closed 94 + # state_event - Status of MR. (close|reopen|merge)
95 # Example: 95 # Example:
96 # PUT /projects/:id/merge_request/:merge_request_id 96 # PUT /projects/:id/merge_request/:merge_request_id
97 # 97 #
98 put ":id/merge_request/:merge_request_id" do 98 put ":id/merge_request/:merge_request_id" do
99 - attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :closed] 99 + attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event]
100 merge_request = user_project.merge_requests.find(params[:merge_request_id]) 100 merge_request = user_project.merge_requests.find(params[:merge_request_id])
101 101
102 authorize! :modify_merge_request, merge_request 102 authorize! :modify_merge_request, merge_request
lib/api/milestones.rb
@@ -74,14 +74,14 @@ module Gitlab @@ -74,14 +74,14 @@ module Gitlab
74 # title (optional) - The title of a milestone 74 # title (optional) - The title of a milestone
75 # description (optional) - The description of a milestone 75 # description (optional) - The description of a milestone
76 # due_date (optional) - The due date of a milestone 76 # due_date (optional) - The due date of a milestone
77 - # closed (optional) - The status of the milestone 77 + # state (optional) - The status of the milestone (close|activate)
78 # Example Request: 78 # Example Request:
79 # PUT /projects/:id/milestones/:milestone_id 79 # PUT /projects/:id/milestones/:milestone_id
80 put ":id/milestones/:milestone_id" do 80 put ":id/milestones/:milestone_id" do
81 authorize! :admin_milestone, user_project 81 authorize! :admin_milestone, user_project
82 82
83 @milestone = user_project.milestones.find(params[:milestone_id]) 83 @milestone = user_project.milestones.find(params[:milestone_id])
84 - attrs = attributes_for_keys [:title, :description, :due_date, :closed] 84 + attrs = attributes_for_keys [:title, :description, :due_date, :state_event]
85 if @milestone.update_attributes attrs 85 if @milestone.update_attributes attrs
86 present @milestone, with: Entities::Milestone 86 present @milestone, with: Entities::Milestone
87 else 87 else
lib/api/projects.rb
@@ -184,6 +184,7 @@ module Gitlab @@ -184,6 +184,7 @@ module Gitlab
184 # Example Request: 184 # Example Request:
185 # GET /projects/:id/hooks/:hook_id 185 # GET /projects/:id/hooks/:hook_id
186 get ":id/hooks/:hook_id" do 186 get ":id/hooks/:hook_id" do
  187 + authorize! :admin_project, user_project
187 @hook = user_project.hooks.find(params[:hook_id]) 188 @hook = user_project.hooks.find(params[:hook_id])
188 present @hook, with: Entities::Hook 189 present @hook, with: Entities::Hook
189 end 190 end
lib/gitlab/backend/grack_auth.rb
  1 +require_relative 'shell_env'
  2 +
1 module Grack 3 module Grack
2 class Auth < Rack::Auth::Basic 4 class Auth < Rack::Auth::Basic
3 attr_accessor :user, :project 5 attr_accessor :user, :project
@@ -7,9 +9,6 @@ module Grack @@ -7,9 +9,6 @@ module Grack
7 @request = Rack::Request.new(env) 9 @request = Rack::Request.new(env)
8 @auth = Request.new(env) 10 @auth = Request.new(env)
9 11
10 - # Pass Gitolite update hook  
11 - ENV['GL_BYPASS_UPDATE_HOOK'] = "true"  
12 -  
13 # Need this patch due to the rails mount 12 # Need this patch due to the rails mount
14 @env['PATH_INFO'] = @request.path 13 @env['PATH_INFO'] = @request.path
15 @env['SCRIPT_NAME'] = "" 14 @env['SCRIPT_NAME'] = ""
@@ -35,8 +34,7 @@ module Grack @@ -35,8 +34,7 @@ module Grack
35 self.user = User.find_by_email(login) || User.find_by_username(login) 34 self.user = User.find_by_email(login) || User.find_by_username(login)
36 return false unless user.try(:valid_password?, password) 35 return false unless user.try(:valid_password?, password)
37 36
38 - # Set GL_USER env variable  
39 - ENV['GL_USER'] = user.email 37 + Gitlab::ShellEnv.set_env(user)
40 end 38 end
41 39
42 # Git upload and receive 40 # Git upload and receive
lib/gitlab/backend/shell.rb
@@ -10,7 +10,7 @@ module Gitlab @@ -10,7 +10,7 @@ module Gitlab
10 # add_repository("gitlab/gitlab-ci") 10 # add_repository("gitlab/gitlab-ci")
11 # 11 #
12 def add_repository(name) 12 def add_repository(name)
13 - system("/home/git/gitlab-shell/bin/gitlab-projects add-project #{name}.git") 13 + system("#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects add-project #{name}.git")
14 end 14 end
15 15
16 # Import repository 16 # Import repository
@@ -21,7 +21,7 @@ module Gitlab @@ -21,7 +21,7 @@ module Gitlab
21 # import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git") 21 # import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git")
22 # 22 #
23 def import_repository(name, url) 23 def import_repository(name, url)
24 - system("/home/git/gitlab-shell/bin/gitlab-projects import-project #{name}.git #{url}") 24 + system("#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects import-project #{name}.git #{url}")
25 end 25 end
26 26
27 # Remove repository from file system 27 # Remove repository from file system
@@ -32,7 +32,7 @@ module Gitlab @@ -32,7 +32,7 @@ module Gitlab
32 # remove_repository("gitlab/gitlab-ci") 32 # remove_repository("gitlab/gitlab-ci")
33 # 33 #
34 def remove_repository(name) 34 def remove_repository(name)
35 - system("/home/git/gitlab-shell/bin/gitlab-projects rm-project #{name}.git") 35 + system("#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects rm-project #{name}.git")
36 end 36 end
37 37
38 # Add new key to gitlab-shell 38 # Add new key to gitlab-shell
@@ -41,7 +41,7 @@ module Gitlab @@ -41,7 +41,7 @@ module Gitlab
41 # add_key("key-42", "sha-rsa ...") 41 # add_key("key-42", "sha-rsa ...")
42 # 42 #
43 def add_key(key_id, key_content) 43 def add_key(key_id, key_content)
44 - system("/home/git/gitlab-shell/bin/gitlab-keys add-key #{key_id} \"#{key_content}\"") 44 + system("#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-keys add-key #{key_id} \"#{key_content}\"")
45 end 45 end
46 46
47 # Remove ssh key from gitlab shell 47 # Remove ssh key from gitlab shell
@@ -50,12 +50,16 @@ module Gitlab @@ -50,12 +50,16 @@ module Gitlab
50 # remove_key("key-342", "sha-rsa ...") 50 # remove_key("key-342", "sha-rsa ...")
51 # 51 #
52 def remove_key(key_id, key_content) 52 def remove_key(key_id, key_content)
53 - system("/home/git/gitlab-shell/bin/gitlab-keys rm-key #{key_id} \"#{key_content}\"") 53 + system("#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-keys rm-key #{key_id} \"#{key_content}\"")
54 end 54 end
55 55
56 -  
57 def url_to_repo path 56 def url_to_repo path
58 Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git" 57 Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git"
59 end 58 end
  59 +
  60 + def gitlab_shell_user_home
  61 + File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}")
  62 + end
  63 +
60 end 64 end
61 end 65 end
lib/gitlab/backend/shell_env.rb 0 → 100644
@@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
  1 +module Gitlab
  2 + # This module provide 2 methods
  3 + # to set specific ENV variabled for GitLab Shell
  4 + module ShellEnv
  5 + extend self
  6 +
  7 + def set_env(user)
  8 + # Set GL_ID env variable
  9 + ENV['GL_ID'] = "user-#{user.id}"
  10 + end
  11 +
  12 + def reset_env
  13 + # Reset GL_ID env variable
  14 + ENV['GL_ID'] = nil
  15 + end
  16 + end
  17 +end
lib/gitlab/regex.rb
@@ -10,6 +10,10 @@ module Gitlab @@ -10,6 +10,10 @@ module Gitlab
10 /\A[a-zA-Z][a-zA-Z0-9_\-\. ]*\z/ 10 /\A[a-zA-Z][a-zA-Z0-9_\-\. ]*\z/
11 end 11 end
12 12
  13 + def name_regex
  14 + /\A[a-zA-Z0-9_\-\. ]*\z/
  15 + end
  16 +
13 def path_regex 17 def path_regex
14 default_regex 18 default_regex
15 end 19 end
lib/gitlab/satellite/action.rb
@@ -17,6 +17,8 @@ module Gitlab @@ -17,6 +17,8 @@ module Gitlab
17 # * Locks the satellite repo 17 # * Locks the satellite repo
18 # * Yields the prepared satellite repo 18 # * Yields the prepared satellite repo
19 def in_locked_and_timed_satellite 19 def in_locked_and_timed_satellite
  20 + Gitlab::ShellEnv.set_env(user)
  21 +
20 Grit::Git.with_timeout(options[:git_timeout]) do 22 Grit::Git.with_timeout(options[:git_timeout]) do
21 project.satellite.lock do 23 project.satellite.lock do
22 return yield project.satellite.repo 24 return yield project.satellite.repo
@@ -28,6 +30,8 @@ module Gitlab @@ -28,6 +30,8 @@ module Gitlab
28 rescue Grit::Git::GitTimeout => ex 30 rescue Grit::Git::GitTimeout => ex
29 Gitlab::GitLogger.error(ex.message) 31 Gitlab::GitLogger.error(ex.message)
30 return false 32 return false
  33 + ensure
  34 + Gitlab::ShellEnv.reset_env
31 end 35 end
32 36
33 # * Clears the satellite 37 # * Clears the satellite
lib/tasks/gitlab/info.rake
@@ -40,8 +40,8 @@ namespace :gitlab do @@ -40,8 +40,8 @@ namespace :gitlab do
40 40
41 puts "" 41 puts ""
42 puts "GitLab information".yellow 42 puts "GitLab information".yellow
43 - puts "Version:\t#{Gitlab::Version}"  
44 - puts "Revision:\t#{Gitlab::Revision}" 43 + puts "Version:\t#{Gitlab::VERSION}"
  44 + puts "Revision:\t#{Gitlab::REVISION}"
45 puts "Directory:\t#{Rails.root}" 45 puts "Directory:\t#{Rails.root}"
46 puts "DB Adapter:\t#{database_adapter}" 46 puts "DB Adapter:\t#{database_adapter}"
47 puts "URL:\t\t#{Gitlab.config.gitlab.url}" 47 puts "URL:\t\t#{Gitlab.config.gitlab.url}"
lib/tasks/gitlab/setup.rake
1 namespace :gitlab do 1 namespace :gitlab do
2 desc "GITLAB | Setup production application" 2 desc "GITLAB | Setup production application"
3 task :setup => :environment do 3 task :setup => :environment do
4 - setup 4 + setup_db
5 end 5 end
6 6
7 - def setup 7 + def setup_db
8 warn_user_is_not_gitlab 8 warn_user_is_not_gitlab
9 9
10 puts "This will create the necessary database tables and seed the database." 10 puts "This will create the necessary database tables and seed the database."
lib/tasks/gitlab/shell.rake
@@ -25,12 +25,13 @@ namespace :gitlab do @@ -25,12 +25,13 @@ namespace :gitlab do
25 def setup 25 def setup
26 warn_user_is_not_gitlab 26 warn_user_is_not_gitlab
27 27
  28 + gitlab_shell_authorized_keys = File.join(File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}"),'.ssh/authorized_keys')
28 puts "This will rebuild an authorized_keys file." 29 puts "This will rebuild an authorized_keys file."
29 - puts "You will lose any data stored in /home/git/.ssh/authorized_keys." 30 + puts "You will lose any data stored in #{gitlab_shell_authorized_keys}."
30 ask_to_continue 31 ask_to_continue
31 puts "" 32 puts ""
32 33
33 - system("echo '# Managed by gitlab-shell' > /home/git/.ssh/authorized_keys") 34 + system("echo '# Managed by gitlab-shell' > #{gitlab_shell_authorized_keys}")
34 35
35 Key.find_each(batch_size: 1000) do |key| 36 Key.find_each(batch_size: 1000) do |key|
36 if Gitlab::Shell.new.add_key(key.shell_id, key.key) 37 if Gitlab::Shell.new.add_key(key.shell_id, key.key)
lib/tasks/gitlab/task_helpers.rake
@@ -77,8 +77,7 @@ namespace :gitlab do @@ -77,8 +77,7 @@ namespace :gitlab do
77 end 77 end
78 78
79 def gid_for(group_name) 79 def gid_for(group_name)
80 - group_line = File.read("/etc/group").lines.select{|l| l.start_with?("#{group_name}:")}.first  
81 - group_line.split(":")[2].to_i 80 + Etc.getgrnam(group_name).gid
82 end 81 end
83 82
84 def warn_user_is_not_gitlab 83 def warn_user_is_not_gitlab
public/deploy.html
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 <link href="/static.css" media="screen" rel="stylesheet" type="text/css" /> 5 <link href="/static.css" media="screen" rel="stylesheet" type="text/css" />
6 </head> 6 </head>
7 <body> 7 <body>
8 - <h1>Deploy in progress</h1> 8 + <h1><center><img src="/gitlab_logo.png"/></center>Deploy in progress</h1>
9 <h3>Please try again in few minutes or contact your administrator.</h3> 9 <h3>Please try again in few minutes or contact your administrator.</h3>
10 </body> 10 </body>
11 </html> 11 </html>
public/gitlab_logo.png 0 → 100644

17 KB

spec/factories.rb
@@ -54,10 +54,15 @@ FactoryGirl.define do @@ -54,10 +54,15 @@ FactoryGirl.define do
54 project 54 project
55 55
56 trait :closed do 56 trait :closed do
57 - closed true 57 + state :closed
  58 + end
  59 +
  60 + trait :reopened do
  61 + state :reopened
58 end 62 end
59 63
60 factory :closed_issue, traits: [:closed] 64 factory :closed_issue, traits: [:closed]
  65 + factory :reopened_issue, traits: [:reopened]
61 end 66 end
62 67
63 factory :merge_request do 68 factory :merge_request do
@@ -67,10 +72,6 @@ FactoryGirl.define do @@ -67,10 +72,6 @@ FactoryGirl.define do
67 source_branch "master" 72 source_branch "master"
68 target_branch "stable" 73 target_branch "stable"
69 74
70 - trait :closed do  
71 - closed true  
72 - end  
73 -  
74 # pick 3 commits "at random" (from bcf03b5d~3 to bcf03b5d) 75 # pick 3 commits "at random" (from bcf03b5d~3 to bcf03b5d)
75 trait :with_diffs do 76 trait :with_diffs do
76 target_branch "master" # pretend bcf03b5d~3 77 target_branch "master" # pretend bcf03b5d~3
@@ -85,7 +86,16 @@ FactoryGirl.define do @@ -85,7 +86,16 @@ FactoryGirl.define do
85 end 86 end
86 end 87 end
87 88
  89 + trait :closed do
  90 + state :closed
  91 + end
  92 +
  93 + trait :reopened do
  94 + state :reopened
  95 + end
  96 +
88 factory :closed_merge_request, traits: [:closed] 97 factory :closed_merge_request, traits: [:closed]
  98 + factory :reopened_merge_request, traits: [:reopened]
89 factory :merge_request_with_diffs, traits: [:with_diffs] 99 factory :merge_request_with_diffs, traits: [:with_diffs]
90 end 100 end
91 101
@@ -148,11 +158,23 @@ FactoryGirl.define do @@ -148,11 +158,23 @@ FactoryGirl.define do
148 "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa ++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" 158 "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa ++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
149 end 159 end
150 end 160 end
  161 +
  162 + factory :invalid_key do
  163 + key do
  164 + "ssh-rsa this_is_invalid_key=="
  165 + end
  166 + end
151 end 167 end
152 168
153 factory :milestone do 169 factory :milestone do
154 title 170 title
155 project 171 project
  172 +
  173 + trait :closed do
  174 + state :closed
  175 + end
  176 +
  177 + factory :closed_milestone, traits: [:closed]
156 end 178 end
157 179
158 factory :system_hook do 180 factory :system_hook do
spec/factories_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 -INVALID_FACTORIES = [:key_with_a_space_in_the_middle] 3 +INVALID_FACTORIES = [
  4 + :key_with_a_space_in_the_middle,
  5 + :invalid_key,
  6 +]
4 7
5 FactoryGirl.factories.map(&:name).each do |factory_name| 8 FactoryGirl.factories.map(&:name).each do |factory_name|
6 next if INVALID_FACTORIES.include?(factory_name) 9 next if INVALID_FACTORIES.include?(factory_name)
spec/models/concerns/issuable_spec.rb
@@ -15,7 +15,6 @@ describe Issue, &quot;Issuable&quot; do @@ -15,7 +15,6 @@ describe Issue, &quot;Issuable&quot; do
15 it { should validate_presence_of(:author) } 15 it { should validate_presence_of(:author) }
16 it { should validate_presence_of(:title) } 16 it { should validate_presence_of(:title) }
17 it { should ensure_length_of(:title).is_at_least(0).is_at_most(255) } 17 it { should ensure_length_of(:title).is_at_least(0).is_at_most(255) }
18 - it { should ensure_inclusion_of(:closed).in_array([true, false]) }  
19 end 18 end
20 19
21 describe "Scope" do 20 describe "Scope" do
spec/models/issue_spec.rb
@@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
9 # project_id :integer 9 # project_id :integer
10 # created_at :datetime not null 10 # created_at :datetime not null
11 # updated_at :datetime not null 11 # updated_at :datetime not null
12 -# closed :boolean default(FALSE), not null 12 +# state :string default(FALSE), not null
13 # position :integer default(0) 13 # position :integer default(0)
14 # branch_name :string(255) 14 # branch_name :string(255)
15 # description :text 15 # description :text
@@ -44,34 +44,15 @@ describe Issue do @@ -44,34 +44,15 @@ describe Issue do
44 end 44 end
45 end 45 end
46 46
47 - describe '#is_being_closed?' do  
48 - it 'returns true if the closed attribute has changed and is now true' do  
49 - subject.closed = true  
50 - subject.is_being_closed?.should be_true  
51 - end  
52 - it 'returns false if the closed attribute has changed and is now false' do  
53 - issue = create(:closed_issue)  
54 - issue.closed = false  
55 - issue.is_being_closed?.should be_false  
56 - end  
57 - it 'returns false if the closed attribute has not changed' do  
58 - subject.is_being_closed?.should be_false  
59 - end  
60 - end 47 + describe '#is_being_reassigned?' do
  48 + it 'returnes issues assigned to user' do
  49 + user = create :user
61 50
  51 + 2.times do
  52 + issue = create :issue, assignee: user
  53 + end
62 54
63 - describe '#is_being_reopened?' do  
64 - it 'returns true if the closed attribute has changed and is now false' do  
65 - issue = create(:closed_issue)  
66 - issue.closed = false  
67 - issue.is_being_reopened?.should be_true  
68 - end  
69 - it 'returns false if the closed attribute has changed and is now true' do  
70 - subject.closed = true  
71 - subject.is_being_reopened?.should be_false  
72 - end  
73 - it 'returns false if the closed attribute has not changed' do  
74 - subject.is_being_reopened?.should be_false 55 + Issue.open_for(user).count.should eq 2
75 end 56 end
76 end 57 end
77 end 58 end
spec/models/key_spec.rb
@@ -73,8 +73,12 @@ describe Key do @@ -73,8 +73,12 @@ describe Key do
73 build(:key, user: user).should be_valid 73 build(:key, user: user).should be_valid
74 end 74 end
75 75
76 - it "rejects the unfingerprintable key" do 76 + it "rejects the unfingerprintable key (contains space in middle)" do
77 build(:key_with_a_space_in_the_middle).should_not be_valid 77 build(:key_with_a_space_in_the_middle).should_not be_valid
78 end 78 end
  79 +
  80 + it "rejects the unfingerprintable key (not a key)" do
  81 + build(:invalid_key).should_not be_valid
  82 + end
79 end 83 end
80 end 84 end
spec/models/merge_request_spec.rb
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 # st_commits :text(2147483647) 15 # st_commits :text(2147483647)
16 # st_diffs :text(2147483647) 16 # st_diffs :text(2147483647)
17 # merged :boolean default(FALSE), not null 17 # merged :boolean default(FALSE), not null
18 -# state :integer default(1), not null 18 +# merge_status :integer default(1), not null
19 # milestone_id :integer 19 # milestone_id :integer
20 # 20 #
21 21
@@ -37,6 +37,10 @@ describe MergeRequest do @@ -37,6 +37,10 @@ describe MergeRequest do
37 end 37 end
38 38
39 describe "#mr_and_commit_notes" do 39 describe "#mr_and_commit_notes" do
  40 +
  41 + end
  42 +
  43 + describe "#mr_and_commit_notes" do
40 let!(:merge_request) { create(:merge_request) } 44 let!(:merge_request) { create(:merge_request) }
41 45
42 before do 46 before do
@@ -62,35 +66,4 @@ describe MergeRequest do @@ -62,35 +66,4 @@ describe MergeRequest do
62 subject.is_being_reassigned?.should be_false 66 subject.is_being_reassigned?.should be_false
63 end 67 end
64 end 68 end
65 -  
66 - describe '#is_being_closed?' do  
67 - it 'returns true if the closed attribute has changed and is now true' do  
68 - subject.closed = true  
69 - subject.is_being_closed?.should be_true  
70 - end  
71 - it 'returns false if the closed attribute has changed and is now false' do  
72 - merge_request = create(:closed_merge_request)  
73 - merge_request.closed = false  
74 - merge_request.is_being_closed?.should be_false  
75 - end  
76 - it 'returns false if the closed attribute has not changed' do  
77 - subject.is_being_closed?.should be_false  
78 - end  
79 - end  
80 -  
81 -  
82 - describe '#is_being_reopened?' do  
83 - it 'returns true if the closed attribute has changed and is now false' do  
84 - merge_request = create(:closed_merge_request)  
85 - merge_request.closed = false  
86 - merge_request.is_being_reopened?.should be_true  
87 - end  
88 - it 'returns false if the closed attribute has changed and is now true' do  
89 - subject.closed = true  
90 - subject.is_being_reopened?.should be_false  
91 - end  
92 - it 'returns false if the closed attribute has not changed' do  
93 - subject.is_being_reopened?.should be_false  
94 - end  
95 - end  
96 end 69 end
spec/models/milestone_spec.rb
@@ -7,7 +7,7 @@ @@ -7,7 +7,7 @@
7 # project_id :integer not null 7 # project_id :integer not null
8 # description :text 8 # description :text
9 # due_date :date 9 # due_date :date
10 -# closed :boolean default(FALSE), not null 10 +# state :string default(FALSE), not null
11 # created_at :datetime not null 11 # created_at :datetime not null
12 # updated_at :datetime not null 12 # updated_at :datetime not null
13 # 13 #
@@ -27,7 +27,6 @@ describe Milestone do @@ -27,7 +27,6 @@ describe Milestone do
27 describe "Validation" do 27 describe "Validation" do
28 it { should validate_presence_of(:title) } 28 it { should validate_presence_of(:title) }
29 it { should validate_presence_of(:project) } 29 it { should validate_presence_of(:project) }
30 - it { should ensure_inclusion_of(:closed).in_array([true, false]) }  
31 end 30 end
32 31
33 let(:milestone) { create(:milestone) } 32 let(:milestone) { create(:milestone) }
@@ -41,7 +40,7 @@ describe Milestone do @@ -41,7 +40,7 @@ describe Milestone do
41 40
42 it "should count closed issues" do 41 it "should count closed issues" do
43 IssueObserver.current_user = issue.author 42 IssueObserver.current_user = issue.author
44 - issue.update_attributes(closed: true) 43 + issue.close
45 milestone.issues << issue 44 milestone.issues << issue
46 milestone.percent_complete.should == 100 45 milestone.percent_complete.should == 100
47 end 46 end
@@ -96,7 +95,7 @@ describe Milestone do @@ -96,7 +95,7 @@ describe Milestone do
96 describe :items_count do 95 describe :items_count do
97 before do 96 before do
98 milestone.issues << create(:issue) 97 milestone.issues << create(:issue)
99 - milestone.issues << create(:issue, closed: true) 98 + milestone.issues << create(:closed_issue)
100 milestone.merge_requests << create(:merge_request) 99 milestone.merge_requests << create(:merge_request)
101 end 100 end
102 101
@@ -110,7 +109,35 @@ describe Milestone do @@ -110,7 +109,35 @@ describe Milestone do
110 it { milestone.can_be_closed?.should be_true } 109 it { milestone.can_be_closed?.should be_true }
111 end 110 end
112 111
113 - describe :open? do  
114 - it { milestone.open?.should be_true } 112 + describe :is_empty? do
  113 + before do
  114 + issue = create :closed_issue, milestone: milestone
  115 + merge_request = create :merge_request, milestone: milestone
  116 + end
  117 +
  118 + it 'Should return total count of issues and merge requests assigned to milestone' do
  119 + milestone.total_items_count.should eq 2
  120 + end
  121 + end
  122 +
  123 + describe :can_be_closed? do
  124 + before do
  125 + milestone = create :milestone
  126 + create :closed_issue, milestone: milestone
  127 +
  128 + issue = create :issue
  129 + end
  130 +
  131 + it 'should be true if milestone active and all nestied issues closed' do
  132 + milestone.can_be_closed?.should be_true
  133 + end
  134 +
  135 + it 'should be false if milestone active and not all nestied issues closed' do
  136 + issue.milestone = milestone
  137 + issue.save
  138 +
  139 + milestone.can_be_closed?.should be_false
  140 + end
115 end 141 end
  142 +
116 end 143 end
spec/models/project_spec.rb
@@ -121,10 +121,7 @@ describe Project do @@ -121,10 +121,7 @@ describe Project do
121 let(:project) { create(:project) } 121 let(:project) { create(:project) }
122 122
123 before do 123 before do
124 - @merge_request = create(:merge_request,  
125 - project: project,  
126 - merged: false,  
127 - closed: false) 124 + @merge_request = create(:merge_request, project: project)
128 @key = create(:key, user_id: project.owner.id) 125 @key = create(:key, user_id: project.owner.id)
129 end 126 end
130 127
@@ -133,8 +130,7 @@ describe Project do @@ -133,8 +130,7 @@ describe Project do
133 @merge_request.last_commit.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" 130 @merge_request.last_commit.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"
134 project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a", "refs/heads/stable", @key.user) 131 project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a", "refs/heads/stable", @key.user)
135 @merge_request.reload 132 @merge_request.reload
136 - @merge_request.merged.should be_true  
137 - @merge_request.closed.should be_true 133 + @merge_request.merged?.should be_true
138 end 134 end
139 135
140 it "should update merge request commits with new one if pushed to source branch" do 136 it "should update merge request commits with new one if pushed to source branch" do
spec/observers/issue_observer_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 describe IssueObserver do 3 describe IssueObserver do
4 - let(:some_user) { double(:user, id: 1) }  
5 - let(:assignee) { double(:user, id: 2) }  
6 - let(:author) { double(:user, id: 3) }  
7 - let(:issue) { double(:issue, id: 42, assignee: assignee, author: author) } 4 + let(:some_user) { create :user }
  5 + let(:assignee) { create :user }
  6 + let(:author) { create :user }
  7 + let(:mock_issue) { double(:issue, id: 42, assignee: assignee, author: author) }
  8 + let(:assigned_issue) { create(:issue, assignee: assignee, author: author) }
  9 + let(:unassigned_issue) { create(:issue, author: author) }
  10 + let(:closed_assigned_issue) { create(:closed_issue, assignee: assignee, author: author) }
  11 + let(:closed_unassigned_issue) { create(:closed_issue, author: author) }
  12 +
8 13
9 before(:each) { subject.stub(:current_user).and_return(some_user) } 14 before(:each) { subject.stub(:current_user).and_return(some_user) }
10 15
@@ -21,137 +26,91 @@ describe IssueObserver do @@ -21,137 +26,91 @@ describe IssueObserver do
21 end 26 end
22 27
23 it 'sends an email to the assignee' do 28 it 'sends an email to the assignee' do
24 - Notify.should_receive(:new_issue_email).with(issue.id) 29 + Notify.should_receive(:new_issue_email).with(mock_issue.id)
25 30
26 - subject.after_create(issue) 31 + subject.after_create(mock_issue)
27 end 32 end
28 33
29 it 'does not send an email to the assignee if assignee created the issue' do 34 it 'does not send an email to the assignee if assignee created the issue' do
30 subject.stub(:current_user).and_return(assignee) 35 subject.stub(:current_user).and_return(assignee)
31 Notify.should_not_receive(:new_issue_email) 36 Notify.should_not_receive(:new_issue_email)
32 37
33 - subject.after_create(issue) 38 + subject.after_create(mock_issue)
34 end 39 end
35 end 40 end
36 41
37 - context '#after_update' do  
38 - before(:each) do  
39 - issue.stub(:is_being_reassigned?).and_return(false)  
40 - issue.stub(:is_being_closed?).and_return(false)  
41 - issue.stub(:is_being_reopened?).and_return(false)  
42 - end  
43 -  
44 - it 'is called when an issue is changed' do  
45 - changed = create(:issue, project: create(:project))  
46 - subject.should_receive(:after_update)  
47 -  
48 - Issue.observers.enable :issue_observer do  
49 - changed.description = 'I changed'  
50 - changed.save  
51 - end  
52 - end  
53 -  
54 - context 'a reassigned email' do  
55 - it 'is sent if the issue is being reassigned' do  
56 - issue.should_receive(:is_being_reassigned?).and_return(true)  
57 - subject.should_receive(:send_reassigned_email).with(issue)  
58 -  
59 - subject.after_update(issue)  
60 - end  
61 -  
62 - it 'is not sent if the issue is not being reassigned' do  
63 - issue.should_receive(:is_being_reassigned?).and_return(false)  
64 - subject.should_not_receive(:send_reassigned_email)  
65 -  
66 - subject.after_update(issue)  
67 - end  
68 - end  
69 - 42 + context '#after_close' do
70 context 'a status "closed"' do 43 context 'a status "closed"' do
71 it 'note is created if the issue is being closed' do 44 it 'note is created if the issue is being closed' do
72 - issue.should_receive(:is_being_closed?).and_return(true)  
73 - Notify.should_receive(:issue_status_changed_email).twice  
74 - Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed')  
75 -  
76 - subject.after_update(issue)  
77 - end  
78 -  
79 - it 'note is not created if the issue is not being closed' do  
80 - issue.should_receive(:is_being_closed?).and_return(false)  
81 - Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'closed') 45 + Note.should_receive(:create_status_change_note).with(assigned_issue, some_user, 'closed')
82 46
83 - subject.after_update(issue) 47 + assigned_issue.close
84 end 48 end
85 49
86 it 'notification is delivered if the issue being closed' do 50 it 'notification is delivered if the issue being closed' do
87 - issue.stub(:is_being_closed?).and_return(true)  
88 Notify.should_receive(:issue_status_changed_email).twice 51 Notify.should_receive(:issue_status_changed_email).twice
89 - Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed')  
90 52
91 - subject.after_update(issue)  
92 - end  
93 -  
94 - it 'notification is not delivered if the issue not being closed' do  
95 - issue.stub(:is_being_closed?).and_return(false)  
96 - Notify.should_not_receive(:issue_status_changed_email)  
97 - Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'closed')  
98 -  
99 - subject.after_update(issue) 53 + assigned_issue.close
100 end 54 end
101 55
102 it 'notification is delivered only to author if the issue being closed' do 56 it 'notification is delivered only to author if the issue being closed' do
103 - issue_without_assignee = double(:issue, id: 42, author: author, assignee: nil)  
104 - issue_without_assignee.stub(:is_being_reassigned?).and_return(false)  
105 - issue_without_assignee.stub(:is_being_closed?).and_return(true)  
106 - issue_without_assignee.stub(:is_being_reopened?).and_return(false)  
107 Notify.should_receive(:issue_status_changed_email).once 57 Notify.should_receive(:issue_status_changed_email).once
108 - Note.should_receive(:create_status_change_note).with(issue_without_assignee, some_user, 'closed') 58 + Note.should_receive(:create_status_change_note).with(unassigned_issue, some_user, 'closed')
109 59
110 - subject.after_update(issue_without_assignee) 60 + unassigned_issue.close
111 end 61 end
112 end 62 end
113 63
114 context 'a status "reopened"' do 64 context 'a status "reopened"' do
115 it 'note is created if the issue is being reopened' do 65 it 'note is created if the issue is being reopened' do
  66 + Note.should_receive(:create_status_change_note).with(closed_assigned_issue, some_user, 'reopened')
  67 +
  68 + closed_assigned_issue.reopen
  69 + end
  70 +
  71 + it 'notification is delivered if the issue being reopened' do
116 Notify.should_receive(:issue_status_changed_email).twice 72 Notify.should_receive(:issue_status_changed_email).twice
117 - issue.should_receive(:is_being_reopened?).and_return(true)  
118 - Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened')  
119 73
120 - subject.after_update(issue) 74 + closed_assigned_issue.reopen
121 end 75 end
122 76
123 - it 'note is not created if the issue is not being reopened' do  
124 - issue.should_receive(:is_being_reopened?).and_return(false)  
125 - Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'reopened') 77 + it 'notification is delivered only to author if the issue being reopened' do
  78 + Notify.should_receive(:issue_status_changed_email).once
  79 + Note.should_receive(:create_status_change_note).with(closed_unassigned_issue, some_user, 'reopened')
126 80
127 - subject.after_update(issue) 81 + closed_unassigned_issue.reopen
128 end 82 end
  83 + end
  84 + end
129 85
130 - it 'notification is delivered if the issue being reopened' do  
131 - issue.stub(:is_being_reopened?).and_return(true)  
132 - Notify.should_receive(:issue_status_changed_email).twice  
133 - Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened') 86 + context '#after_update' do
  87 + before(:each) do
  88 + mock_issue.stub(:is_being_reassigned?).and_return(false)
  89 + end
  90 +
  91 + it 'is called when an issue is changed' do
  92 + changed = create(:issue, project: create(:project))
  93 + subject.should_receive(:after_update)
134 94
135 - subject.after_update(issue) 95 + Issue.observers.enable :issue_observer do
  96 + changed.description = 'I changed'
  97 + changed.save
136 end 98 end
  99 + end
137 100
138 - it 'notification is not delivered if the issue not being reopened' do  
139 - issue.stub(:is_being_reopened?).and_return(false)  
140 - Notify.should_not_receive(:issue_status_changed_email)  
141 - Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'reopened') 101 + context 'a reassigned email' do
  102 + it 'is sent if the issue is being reassigned' do
  103 + mock_issue.should_receive(:is_being_reassigned?).and_return(true)
  104 + subject.should_receive(:send_reassigned_email).with(mock_issue)
142 105
143 - subject.after_update(issue) 106 + subject.after_update(mock_issue)
144 end 107 end
145 108
146 - it 'notification is delivered only to author if the issue being reopened' do  
147 - issue_without_assignee = double(:issue, id: 42, author: author, assignee: nil)  
148 - issue_without_assignee.stub(:is_being_reassigned?).and_return(false)  
149 - issue_without_assignee.stub(:is_being_closed?).and_return(false)  
150 - issue_without_assignee.stub(:is_being_reopened?).and_return(true)  
151 - Notify.should_receive(:issue_status_changed_email).once  
152 - Note.should_receive(:create_status_change_note).with(issue_without_assignee, some_user, 'reopened') 109 + it 'is not sent if the issue is not being reassigned' do
  110 + mock_issue.should_receive(:is_being_reassigned?).and_return(false)
  111 + subject.should_not_receive(:send_reassigned_email)
153 112
154 - subject.after_update(issue_without_assignee) 113 + subject.after_update(mock_issue)
155 end 114 end
156 end 115 end
157 end 116 end
@@ -160,23 +119,23 @@ describe IssueObserver do @@ -160,23 +119,23 @@ describe IssueObserver do
160 let(:previous_assignee) { double(:user, id: 3) } 119 let(:previous_assignee) { double(:user, id: 3) }
161 120
162 before(:each) do 121 before(:each) do
163 - issue.stub(:assignee_id).and_return(assignee.id)  
164 - issue.stub(:assignee_id_was).and_return(previous_assignee.id) 122 + mock_issue.stub(:assignee_id).and_return(assignee.id)
  123 + mock_issue.stub(:assignee_id_was).and_return(previous_assignee.id)
165 end 124 end
166 125
167 def it_sends_a_reassigned_email_to(recipient) 126 def it_sends_a_reassigned_email_to(recipient)
168 - Notify.should_receive(:reassigned_issue_email).with(recipient, issue.id, previous_assignee.id) 127 + Notify.should_receive(:reassigned_issue_email).with(recipient, mock_issue.id, previous_assignee.id)
169 end 128 end
170 129
171 def it_does_not_send_a_reassigned_email_to(recipient) 130 def it_does_not_send_a_reassigned_email_to(recipient)
172 - Notify.should_not_receive(:reassigned_issue_email).with(recipient, issue.id, previous_assignee.id) 131 + Notify.should_not_receive(:reassigned_issue_email).with(recipient, mock_issue.id, previous_assignee.id)
173 end 132 end
174 133
175 it 'sends a reassigned email to the previous and current assignees' do 134 it 'sends a reassigned email to the previous and current assignees' do
176 it_sends_a_reassigned_email_to assignee.id 135 it_sends_a_reassigned_email_to assignee.id
177 it_sends_a_reassigned_email_to previous_assignee.id 136 it_sends_a_reassigned_email_to previous_assignee.id
178 137
179 - subject.send(:send_reassigned_email, issue) 138 + subject.send(:send_reassigned_email, mock_issue)
180 end 139 end
181 140
182 context 'does not send an email to the user who made the reassignment' do 141 context 'does not send an email to the user who made the reassignment' do
@@ -185,14 +144,14 @@ describe IssueObserver do @@ -185,14 +144,14 @@ describe IssueObserver do
185 it_sends_a_reassigned_email_to previous_assignee.id 144 it_sends_a_reassigned_email_to previous_assignee.id
186 it_does_not_send_a_reassigned_email_to assignee.id 145 it_does_not_send_a_reassigned_email_to assignee.id
187 146
188 - subject.send(:send_reassigned_email, issue) 147 + subject.send(:send_reassigned_email, mock_issue)
189 end 148 end
190 it 'if the user is the previous assignee' do 149 it 'if the user is the previous assignee' do
191 subject.stub(:current_user).and_return(previous_assignee) 150 subject.stub(:current_user).and_return(previous_assignee)
192 it_sends_a_reassigned_email_to assignee.id 151 it_sends_a_reassigned_email_to assignee.id
193 it_does_not_send_a_reassigned_email_to previous_assignee.id 152 it_does_not_send_a_reassigned_email_to previous_assignee.id
194 153
195 - subject.send(:send_reassigned_email, issue) 154 + subject.send(:send_reassigned_email, mock_issue)
196 end 155 end
197 end 156 end
198 end 157 end
spec/observers/merge_request_observer_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 describe MergeRequestObserver do 3 describe MergeRequestObserver do
4 - let(:some_user) { double(:user, id: 1) }  
5 - let(:assignee) { double(:user, id: 2) }  
6 - let(:author) { double(:user, id: 3) }  
7 - let(:mr) { double(:merge_request, id: 42, assignee: assignee, author: author) } 4 + let(:some_user) { create :user }
  5 + let(:assignee) { create :user }
  6 + let(:author) { create :user }
  7 + let(:mr_mock) { double(:merge_request, id: 42, assignee: assignee, author: author) }
  8 + let(:assigned_mr) { create(:merge_request, assignee: assignee, author: author) }
  9 + let(:unassigned_mr) { create(:merge_request, author: author) }
  10 + let(:closed_assigned_mr) { create(:closed_merge_request, assignee: assignee, author: author) }
  11 + let(:closed_unassigned_mr) { create(:closed_merge_request, author: author) }
8 12
9 before(:each) { subject.stub(:current_user).and_return(some_user) } 13 before(:each) { subject.stub(:current_user).and_return(some_user) }
10 14
@@ -21,23 +25,21 @@ describe MergeRequestObserver do @@ -21,23 +25,21 @@ describe MergeRequestObserver do
21 end 25 end
22 26
23 it 'sends an email to the assignee' do 27 it 'sends an email to the assignee' do
24 - Notify.should_receive(:new_merge_request_email).with(mr.id)  
25 - subject.after_create(mr) 28 + Notify.should_receive(:new_merge_request_email).with(mr_mock.id)
  29 + subject.after_create(mr_mock)
26 end 30 end
27 31
28 it 'does not send an email to the assignee if assignee created the merge request' do 32 it 'does not send an email to the assignee if assignee created the merge request' do
29 subject.stub(:current_user).and_return(assignee) 33 subject.stub(:current_user).and_return(assignee)
30 Notify.should_not_receive(:new_merge_request_email) 34 Notify.should_not_receive(:new_merge_request_email)
31 35
32 - subject.after_create(mr) 36 + subject.after_create(mr_mock)
33 end 37 end
34 end 38 end
35 39
36 context '#after_update' do 40 context '#after_update' do
37 before(:each) do 41 before(:each) do
38 - mr.stub(:is_being_reassigned?).and_return(false)  
39 - mr.stub(:is_being_closed?).and_return(false)  
40 - mr.stub(:is_being_reopened?).and_return(false) 42 + mr_mock.stub(:is_being_reassigned?).and_return(false)
41 end 43 end
42 44
43 it 'is called when a merge request is changed' do 45 it 'is called when a merge request is changed' do
@@ -52,97 +54,50 @@ describe MergeRequestObserver do @@ -52,97 +54,50 @@ describe MergeRequestObserver do
52 54
53 context 'a reassigned email' do 55 context 'a reassigned email' do
54 it 'is sent if the merge request is being reassigned' do 56 it 'is sent if the merge request is being reassigned' do
55 - mr.should_receive(:is_being_reassigned?).and_return(true)  
56 - subject.should_receive(:send_reassigned_email).with(mr) 57 + mr_mock.should_receive(:is_being_reassigned?).and_return(true)
  58 + subject.should_receive(:send_reassigned_email).with(mr_mock)
57 59
58 - subject.after_update(mr) 60 + subject.after_update(mr_mock)
59 end 61 end
60 62
61 it 'is not sent if the merge request is not being reassigned' do 63 it 'is not sent if the merge request is not being reassigned' do
62 - mr.should_receive(:is_being_reassigned?).and_return(false) 64 + mr_mock.should_receive(:is_being_reassigned?).and_return(false)
63 subject.should_not_receive(:send_reassigned_email) 65 subject.should_not_receive(:send_reassigned_email)
64 66
65 - subject.after_update(mr) 67 + subject.after_update(mr_mock)
66 end 68 end
67 end 69 end
68 70
  71 + end
  72 +
  73 + context '#after_close' do
69 context 'a status "closed"' do 74 context 'a status "closed"' do
70 it 'note is created if the merge request is being closed' do 75 it 'note is created if the merge request is being closed' do
71 - mr.should_receive(:is_being_closed?).and_return(true)  
72 - Note.should_receive(:create_status_change_note).with(mr, some_user, 'closed')  
73 -  
74 - subject.after_update(mr)  
75 - end  
76 -  
77 - it 'note is not created if the merge request is not being closed' do  
78 - mr.should_receive(:is_being_closed?).and_return(false)  
79 - Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'closed')  
80 -  
81 - subject.after_update(mr)  
82 - end  
83 -  
84 - it 'notification is delivered if the merge request being closed' do  
85 - mr.stub(:is_being_closed?).and_return(true)  
86 - Note.should_receive(:create_status_change_note).with(mr, some_user, 'closed')  
87 -  
88 - subject.after_update(mr)  
89 - end  
90 -  
91 - it 'notification is not delivered if the merge request not being closed' do  
92 - mr.stub(:is_being_closed?).and_return(false)  
93 - Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'closed') 76 + Note.should_receive(:create_status_change_note).with(assigned_mr, some_user, 'closed')
94 77
95 - subject.after_update(mr) 78 + assigned_mr.close
96 end 79 end
97 80
98 it 'notification is delivered only to author if the merge request is being closed' do 81 it 'notification is delivered only to author if the merge request is being closed' do
99 - mr_without_assignee = double(:merge_request, id: 42, author: author, assignee: nil)  
100 - mr_without_assignee.stub(:is_being_reassigned?).and_return(false)  
101 - mr_without_assignee.stub(:is_being_closed?).and_return(true)  
102 - mr_without_assignee.stub(:is_being_reopened?).and_return(false)  
103 - Note.should_receive(:create_status_change_note).with(mr_without_assignee, some_user, 'closed') 82 + Note.should_receive(:create_status_change_note).with(unassigned_mr, some_user, 'closed')
104 83
105 - subject.after_update(mr_without_assignee) 84 + unassigned_mr.close
106 end 85 end
107 end 86 end
  87 + end
108 88
  89 + context '#after_reopen' do
109 context 'a status "reopened"' do 90 context 'a status "reopened"' do
110 it 'note is created if the merge request is being reopened' do 91 it 'note is created if the merge request is being reopened' do
111 - mr.should_receive(:is_being_reopened?).and_return(true)  
112 - Note.should_receive(:create_status_change_note).with(mr, some_user, 'reopened')  
113 -  
114 - subject.after_update(mr)  
115 - end  
116 -  
117 - it 'note is not created if the merge request is not being reopened' do  
118 - mr.should_receive(:is_being_reopened?).and_return(false)  
119 - Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'reopened')  
120 -  
121 - subject.after_update(mr)  
122 - end  
123 -  
124 - it 'notification is delivered if the merge request being reopened' do  
125 - mr.stub(:is_being_reopened?).and_return(true)  
126 - Note.should_receive(:create_status_change_note).with(mr, some_user, 'reopened')  
127 -  
128 - subject.after_update(mr)  
129 - end  
130 -  
131 - it 'notification is not delivered if the merge request is not being reopened' do  
132 - mr.stub(:is_being_reopened?).and_return(false)  
133 - Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'reopened') 92 + Note.should_receive(:create_status_change_note).with(closed_assigned_mr, some_user, 'reopened')
134 93
135 - subject.after_update(mr) 94 + closed_assigned_mr.reopen
136 end 95 end
137 96
138 it 'notification is delivered only to author if the merge request is being reopened' do 97 it 'notification is delivered only to author if the merge request is being reopened' do
139 - mr_without_assignee = double(:merge_request, id: 42, author: author, assignee: nil)  
140 - mr_without_assignee.stub(:is_being_reassigned?).and_return(false)  
141 - mr_without_assignee.stub(:is_being_closed?).and_return(false)  
142 - mr_without_assignee.stub(:is_being_reopened?).and_return(true)  
143 - Note.should_receive(:create_status_change_note).with(mr_without_assignee, some_user, 'reopened') 98 + Note.should_receive(:create_status_change_note).with(closed_unassigned_mr, some_user, 'reopened')
144 99
145 - subject.after_update(mr_without_assignee) 100 + closed_unassigned_mr.reopen
146 end 101 end
147 end 102 end
148 end 103 end
@@ -151,23 +106,23 @@ describe MergeRequestObserver do @@ -151,23 +106,23 @@ describe MergeRequestObserver do
151 let(:previous_assignee) { double(:user, id: 3) } 106 let(:previous_assignee) { double(:user, id: 3) }
152 107
153 before(:each) do 108 before(:each) do
154 - mr.stub(:assignee_id).and_return(assignee.id)  
155 - mr.stub(:assignee_id_was).and_return(previous_assignee.id) 109 + mr_mock.stub(:assignee_id).and_return(assignee.id)
  110 + mr_mock.stub(:assignee_id_was).and_return(previous_assignee.id)
156 end 111 end
157 112
158 def it_sends_a_reassigned_email_to(recipient) 113 def it_sends_a_reassigned_email_to(recipient)
159 - Notify.should_receive(:reassigned_merge_request_email).with(recipient, mr.id, previous_assignee.id) 114 + Notify.should_receive(:reassigned_merge_request_email).with(recipient, mr_mock.id, previous_assignee.id)
160 end 115 end
161 116
162 def it_does_not_send_a_reassigned_email_to(recipient) 117 def it_does_not_send_a_reassigned_email_to(recipient)
163 - Notify.should_not_receive(:reassigned_merge_request_email).with(recipient, mr.id, previous_assignee.id) 118 + Notify.should_not_receive(:reassigned_merge_request_email).with(recipient, mr_mock.id, previous_assignee.id)
164 end 119 end
165 120
166 it 'sends a reassigned email to the previous and current assignees' do 121 it 'sends a reassigned email to the previous and current assignees' do
167 it_sends_a_reassigned_email_to assignee.id 122 it_sends_a_reassigned_email_to assignee.id
168 it_sends_a_reassigned_email_to previous_assignee.id 123 it_sends_a_reassigned_email_to previous_assignee.id
169 124
170 - subject.send(:send_reassigned_email, mr) 125 + subject.send(:send_reassigned_email, mr_mock)
171 end 126 end
172 127
173 context 'does not send an email to the user who made the reassignment' do 128 context 'does not send an email to the user who made the reassignment' do
@@ -176,14 +131,14 @@ describe MergeRequestObserver do @@ -176,14 +131,14 @@ describe MergeRequestObserver do
176 it_sends_a_reassigned_email_to previous_assignee.id 131 it_sends_a_reassigned_email_to previous_assignee.id
177 it_does_not_send_a_reassigned_email_to assignee.id 132 it_does_not_send_a_reassigned_email_to assignee.id
178 133
179 - subject.send(:send_reassigned_email, mr) 134 + subject.send(:send_reassigned_email, mr_mock)
180 end 135 end
181 it 'if the user is the previous assignee' do 136 it 'if the user is the previous assignee' do
182 subject.stub(:current_user).and_return(previous_assignee) 137 subject.stub(:current_user).and_return(previous_assignee)
183 it_sends_a_reassigned_email_to assignee.id 138 it_sends_a_reassigned_email_to assignee.id
184 it_does_not_send_a_reassigned_email_to previous_assignee.id 139 it_does_not_send_a_reassigned_email_to previous_assignee.id
185 140
186 - subject.send(:send_reassigned_email, mr) 141 + subject.send(:send_reassigned_email, mr_mock)
187 end 142 end
188 end 143 end
189 end 144 end
spec/requests/api/issues_spec.rb
@@ -54,14 +54,24 @@ describe Gitlab::API do @@ -54,14 +54,24 @@ describe Gitlab::API do
54 end 54 end
55 end 55 end
56 56
57 - describe "PUT /projects/:id/issues/:issue_id" do 57 + describe "PUT /projects/:id/issues/:issue_id to update only title" do
58 it "should update a project issue" do 58 it "should update a project issue" do
59 put api("/projects/#{project.id}/issues/#{issue.id}", user), 59 put api("/projects/#{project.id}/issues/#{issue.id}", user),
60 - title: 'updated title', labels: 'label2', closed: 1 60 + title: 'updated title'
61 response.status.should == 200 61 response.status.should == 200
  62 +
62 json_response['title'].should == 'updated title' 63 json_response['title'].should == 'updated title'
  64 + end
  65 + end
  66 +
  67 + describe "PUT /projects/:id/issues/:issue_id to update state and label" do
  68 + it "should update a project issue" do
  69 + put api("/projects/#{project.id}/issues/#{issue.id}", user),
  70 + labels: 'label2', state_event: "close"
  71 + response.status.should == 200
  72 +
63 json_response['labels'].should == ['label2'] 73 json_response['labels'].should == ['label2']
64 - json_response['closed'].should be_true 74 + json_response['state'].should eq "closed"
65 end 75 end
66 end 76 end
67 77
spec/requests/api/merge_requests_spec.rb
@@ -66,6 +66,23 @@ describe Gitlab::API do @@ -66,6 +66,23 @@ describe Gitlab::API do
66 end 66 end
67 end 67 end
68 68
  69 + describe "PUT /projects/:id/merge_request/:merge_request_id to close MR" do
  70 + it "should return merge_request" do
  71 + put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), state_event: "close"
  72 + response.status.should == 200
  73 + json_response['state'].should == 'closed'
  74 + end
  75 + end
  76 +
  77 + describe "PUT /projects/:id/merge_request/:merge_request_id to merge MR" do
  78 + it "should return merge_request" do
  79 + put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), state_event: "merge"
  80 + response.status.should == 200
  81 + json_response['state'].should == 'merged'
  82 + end
  83 + end
  84 +
  85 +
69 describe "PUT /projects/:id/merge_request/:merge_request_id" do 86 describe "PUT /projects/:id/merge_request/:merge_request_id" do
70 it "should return merge_request" do 87 it "should return merge_request" do
71 put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), title: "New title" 88 put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), title: "New title"
spec/requests/api/milestones_spec.rb
@@ -68,4 +68,14 @@ describe Gitlab::API do @@ -68,4 +68,14 @@ describe Gitlab::API do
68 response.status.should == 404 68 response.status.should == 404
69 end 69 end
70 end 70 end
  71 +
  72 + describe "PUT /projects/:id/milestones/:milestone_id to close milestone" do
  73 + it "should update a project milestone" do
  74 + put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
  75 + state_event: 'close'
  76 + response.status.should == 200
  77 +
  78 + json_response['state'].should == 'closed'
  79 + end
  80 + end
71 end 81 end
spec/requests/api/projects_spec.rb
@@ -33,6 +33,20 @@ describe Gitlab::API do @@ -33,6 +33,20 @@ describe Gitlab::API do
33 end 33 end
34 34
35 describe "POST /projects" do 35 describe "POST /projects" do
  36 + context "maximum number of projects reached" do
  37 + before do
  38 + (1..user2.projects_limit).each do |project|
  39 + post api("/projects", user2), name: "foo#{project}"
  40 + end
  41 + end
  42 +
  43 + it "should not create new project" do
  44 + expect {
  45 + post api("/projects", user2), name: 'foo'
  46 + }.to change {Project.count}.by(0)
  47 + end
  48 + end
  49 +
36 it "should create new project without path" do 50 it "should create new project without path" do
37 expect { post api("/projects", user), name: 'foo' }.to change {Project.count}.by(1) 51 expect { post api("/projects", user), name: 'foo' }.to change {Project.count}.by(1)
38 end 52 end
@@ -46,6 +60,12 @@ describe Gitlab::API do @@ -46,6 +60,12 @@ describe Gitlab::API do
46 response.status.should == 400 60 response.status.should == 400
47 end 61 end
48 62
  63 + it "should create last project before reaching project limit" do
  64 + (1..user2.projects_limit-1).each { |p| post api("/projects", user2), name: "foo#{p}" }
  65 + post api("/projects", user2), name: "foo"
  66 + response.status.should == 201
  67 + end
  68 +
49 it "should respond with 201 on success" do 69 it "should respond with 201 on success" do
50 post api("/projects", user), name: 'foo' 70 post api("/projects", user), name: 'foo'
51 response.status.should == 201 71 response.status.should == 201
@@ -314,22 +334,44 @@ describe Gitlab::API do @@ -314,22 +334,44 @@ describe Gitlab::API do
314 end 334 end
315 335
316 describe "GET /projects/:id/hooks" do 336 describe "GET /projects/:id/hooks" do
317 - it "should return project hooks" do  
318 - get api("/projects/#{project.id}/hooks", user) 337 + context "authorized user" do
  338 + it "should return project hooks" do
  339 + get api("/projects/#{project.id}/hooks", user)
  340 + response.status.should == 200
319 341
320 - response.status.should == 200 342 + json_response.should be_an Array
  343 + json_response.count.should == 1
  344 + json_response.first['url'].should == "http://example.com"
  345 + end
  346 + end
321 347
322 - json_response.should be_an Array  
323 - json_response.count.should == 1  
324 - json_response.first['url'].should == "http://example.com" 348 + context "unauthorized user" do
  349 + it "should not access project hooks" do
  350 + get api("/projects/#{project.id}/hooks", user3)
  351 + response.status.should == 403
  352 + end
325 end 353 end
326 end 354 end
327 355
328 describe "GET /projects/:id/hooks/:hook_id" do 356 describe "GET /projects/:id/hooks/:hook_id" do
329 - it "should return a project hook" do  
330 - get api("/projects/#{project.id}/hooks/#{hook.id}", user)  
331 - response.status.should == 200  
332 - json_response['url'].should == hook.url 357 + context "authorized user" do
  358 + it "should return a project hook" do
  359 + get api("/projects/#{project.id}/hooks/#{hook.id}", user)
  360 + response.status.should == 200
  361 + json_response['url'].should == hook.url
  362 + end
  363 +
  364 + it "should return a 404 error if hook id is not available" do
  365 + get api("/projects/#{project.id}/hooks/1234", user)
  366 + response.status.should == 404
  367 + end
  368 + end
  369 +
  370 + context "unauthorized user" do
  371 + it "should not access an existing hook" do
  372 + get api("/projects/#{project.id}/hooks/#{hook.id}", user3)
  373 + response.status.should == 403
  374 + end
333 end 375 end
334 376
335 it "should return a 404 error if hook id is not available" do 377 it "should return a 404 error if hook id is not available" do
spec/requests/issues_spec.rb
@@ -58,8 +58,7 @@ describe &quot;Issues&quot; do @@ -58,8 +58,7 @@ describe &quot;Issues&quot; do
58 58
59 it "should be able to search on different statuses" do 59 it "should be able to search on different statuses" do
60 issue = Issue.first # with title 'foobar' 60 issue = Issue.first # with title 'foobar'
61 - issue.closed = true  
62 - issue.save 61 + issue.close
63 62
64 visit project_issues_path(project) 63 visit project_issues_path(project)
65 click_link 'Closed' 64 click_link 'Closed'