Commit f5147780ff2290332d8979d399cfd55bf4a5fd37

Authored by Marc Radulescu
2 parents d1980adf 9f80ab8e

Merge branch 'master' of dev.gitlab.org:gitlab/gitlabhq into grammar_fixes

Showing 142 changed files with 1879 additions and 655 deletions   Show diff stats
.pkgr.yml 0 → 100644
@@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
  1 +user: git
  2 +group: git
  3 +before_precompile: ./bin/pkgr_before_precompile.sh
  4 +targets:
  5 + debian-7: &wheezy
  6 + build_dependencies:
  7 + - libicu-dev
  8 + dependencies:
  9 + - libicu48
  10 + - libpcre3
  11 + - git
  12 + ubuntu-12.04: *wheezy
  13 + ubuntu-14.04:
  14 + build_dependencies:
  15 + - libicu-dev
  16 + dependencies:
  17 + - libicu52
  18 + - libpcre3
  19 + - git
@@ -8,6 +8,16 @@ v 6.9.0 @@ -8,6 +8,16 @@ v 6.9.0
8 - Fix syntax highlighting for code comments blocks 8 - Fix syntax highlighting for code comments blocks
9 - Improve comments loading logic 9 - Improve comments loading logic
10 - Stop refreshing comments when the tab is hidden 10 - Stop refreshing comments when the tab is hidden
  11 + - Improve issue and merge request mobile UI (Drew Blessing)
  12 + - Document how to convert a backup to PostgreSQL
  13 + - Fix locale bug in backup manager
  14 + - Fix can not automerge when MR description is too long
  15 + - Fix wiki backup skip bug
  16 + - Two Step MR creation process
  17 + - Remove unwanted files from satellite working directory with git clean -fdx
  18 + - Accept merge request via API (sponsored by O'Reilly Media)
  19 + - Add more access checks during API calls
  20 + - Block SSH access for 'disabled' Active Directory users
11 21
12 v 6.8.0 22 v 6.8.0
13 - Ability to at mention users that are participating in issue and merge req. discussion 23 - Ability to at mention users that are participating in issue and merge req. discussion
@@ -152,7 +152,7 @@ gem "rack-attack" @@ -152,7 +152,7 @@ gem "rack-attack"
152 # Ace editor 152 # Ace editor
153 gem 'ace-rails-ap' 153 gem 'ace-rails-ap'
154 154
155 -gem "sass-rails" 155 +gem "sass-rails", '~> 4.0.2'
156 gem "coffee-rails" 156 gem "coffee-rails"
157 gem "uglifier" 157 gem "uglifier"
158 gem "therubyracer" 158 gem "therubyracer"
@@ -2,26 +2,26 @@ GEM @@ -2,26 +2,26 @@ GEM
2 remote: https://rubygems.org/ 2 remote: https://rubygems.org/
3 specs: 3 specs:
4 ace-rails-ap (2.0.1) 4 ace-rails-ap (2.0.1)
5 - actionmailer (4.0.3)  
6 - actionpack (= 4.0.3) 5 + actionmailer (4.0.5)
  6 + actionpack (= 4.0.5)
7 mail (~> 2.5.4) 7 mail (~> 2.5.4)
8 - actionpack (4.0.3)  
9 - activesupport (= 4.0.3) 8 + actionpack (4.0.5)
  9 + activesupport (= 4.0.5)
10 builder (~> 3.1.0) 10 builder (~> 3.1.0)
11 erubis (~> 2.7.0) 11 erubis (~> 2.7.0)
12 rack (~> 1.5.2) 12 rack (~> 1.5.2)
13 rack-test (~> 0.6.2) 13 rack-test (~> 0.6.2)
14 - activemodel (4.0.3)  
15 - activesupport (= 4.0.3) 14 + activemodel (4.0.5)
  15 + activesupport (= 4.0.5)
16 builder (~> 3.1.0) 16 builder (~> 3.1.0)
17 - activerecord (4.0.3)  
18 - activemodel (= 4.0.3) 17 + activerecord (4.0.5)
  18 + activemodel (= 4.0.5)
19 activerecord-deprecated_finders (~> 1.0.2) 19 activerecord-deprecated_finders (~> 1.0.2)
20 - activesupport (= 4.0.3) 20 + activesupport (= 4.0.5)
21 arel (~> 4.0.0) 21 arel (~> 4.0.0)
22 activerecord-deprecated_finders (1.0.3) 22 activerecord-deprecated_finders (1.0.3)
23 - activesupport (4.0.3)  
24 - i18n (~> 0.6, >= 0.6.4) 23 + activesupport (4.0.5)
  24 + i18n (~> 0.6, >= 0.6.9)
25 minitest (~> 4.2) 25 minitest (~> 4.2)
26 multi_json (~> 1.3) 26 multi_json (~> 1.3)
27 thread_safe (~> 0.1) 27 thread_safe (~> 0.1)
@@ -162,7 +162,7 @@ GEM @@ -162,7 +162,7 @@ GEM
162 multi_json 162 multi_json
163 gitlab-grack (2.0.0.pre) 163 gitlab-grack (2.0.0.pre)
164 rack (~> 1.5.1) 164 rack (~> 1.5.1)
165 - gitlab-grit (2.6.5) 165 + gitlab-grit (2.6.7)
166 charlock_holmes (~> 0.6) 166 charlock_holmes (~> 0.6)
167 diff-lcs (~> 1.1) 167 diff-lcs (~> 1.1)
168 mime-types (~> 1.15) 168 mime-types (~> 1.15)
@@ -279,7 +279,7 @@ GEM @@ -279,7 +279,7 @@ GEM
279 mime-types (1.25.1) 279 mime-types (1.25.1)
280 mini_portile (0.5.3) 280 mini_portile (0.5.3)
281 minitest (4.7.5) 281 minitest (4.7.5)
282 - multi_json (1.9.3) 282 + multi_json (1.10.0)
283 multi_xml (0.5.5) 283 multi_xml (0.5.5)
284 multipart-post (1.2.0) 284 multipart-post (1.2.0)
285 mysql2 (0.3.11) 285 mysql2 (0.3.11)
@@ -349,13 +349,13 @@ GEM @@ -349,13 +349,13 @@ GEM
349 rack 349 rack
350 rack-test (0.6.2) 350 rack-test (0.6.2)
351 rack (>= 1.0) 351 rack (>= 1.0)
352 - rails (4.0.3)  
353 - actionmailer (= 4.0.3)  
354 - actionpack (= 4.0.3)  
355 - activerecord (= 4.0.3)  
356 - activesupport (= 4.0.3) 352 + rails (4.0.5)
  353 + actionmailer (= 4.0.5)
  354 + actionpack (= 4.0.5)
  355 + activerecord (= 4.0.5)
  356 + activesupport (= 4.0.5)
357 bundler (>= 1.3.0, < 2.0) 357 bundler (>= 1.3.0, < 2.0)
358 - railties (= 4.0.3) 358 + railties (= 4.0.5)
359 sprockets-rails (~> 2.0.0) 359 sprockets-rails (~> 2.0.0)
360 rails-observers (0.1.2) 360 rails-observers (0.1.2)
361 activemodel (~> 4.0) 361 activemodel (~> 4.0)
@@ -368,9 +368,9 @@ GEM @@ -368,9 +368,9 @@ GEM
368 i18n 368 i18n
369 require_all 369 require_all
370 ruby-progressbar 370 ruby-progressbar
371 - railties (4.0.3)  
372 - actionpack (= 4.0.3)  
373 - activesupport (= 4.0.3) 371 + railties (4.0.5)
  372 + actionpack (= 4.0.5)
  373 + activesupport (= 4.0.5)
374 rake (>= 0.8.7) 374 rake (>= 0.8.7)
375 thor (>= 0.18.1, < 2.0) 375 thor (>= 0.18.1, < 2.0)
376 raindrops (0.12.0) 376 raindrops (0.12.0)
@@ -427,11 +427,12 @@ GEM @@ -427,11 +427,12 @@ GEM
427 safe_yaml (0.9.7) 427 safe_yaml (0.9.7)
428 sanitize (2.1.0) 428 sanitize (2.1.0)
429 nokogiri (>= 1.4.4) 429 nokogiri (>= 1.4.4)
430 - sass (3.2.12)  
431 - sass-rails (4.0.1) 430 + sass (3.2.19)
  431 + sass-rails (4.0.3)
432 railties (>= 4.0.0, < 5.0) 432 railties (>= 4.0.0, < 5.0)
433 - sass (>= 3.1.10)  
434 - sprockets-rails (~> 2.0.0) 433 + sass (~> 3.2.0)
  434 + sprockets (~> 2.8, <= 2.11.0)
  435 + sprockets-rails (~> 2.0)
435 sdoc (0.3.20) 436 sdoc (0.3.20)
436 json (>= 1.1.3) 437 json (>= 1.1.3)
437 rdoc (~> 3.10) 438 rdoc (~> 3.10)
@@ -478,7 +479,7 @@ GEM @@ -478,7 +479,7 @@ GEM
478 spring (>= 0.9.1) 479 spring (>= 0.9.1)
479 spring-commands-spinach (1.0.0) 480 spring-commands-spinach (1.0.0)
480 spring (>= 0.9.1) 481 spring (>= 0.9.1)
481 - sprockets (2.10.1) 482 + sprockets (2.11.0)
482 hike (~> 1.2) 483 hike (~> 1.2)
483 multi_json (~> 1.0) 484 multi_json (~> 1.0)
484 rack (~> 1.0) 485 rack (~> 1.0)
@@ -636,7 +637,7 @@ DEPENDENCIES @@ -636,7 +637,7 @@ DEPENDENCIES
636 redis-rails 637 redis-rails
637 rspec-rails 638 rspec-rails
638 sanitize (~> 2.0) 639 sanitize (~> 2.0)
639 - sass-rails 640 + sass-rails (~> 4.0.2)
640 sdoc 641 sdoc
641 seed-fu 642 seed-fu
642 select2-rails 643 select2-rails
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 15
16 ### Canonical source 16 ### Canonical source
17 17
18 -* The source of GitLab Communinity Edition is [hosted on GitLab Cloud](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible. 18 +* The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible.
19 19
20 ### Code status 20 ### Code status
21 21
@@ -25,6 +25,8 @@ @@ -25,6 +25,8 @@
25 25
26 * [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq) 26 * [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq)
27 27
  28 +* [![PullReview stats](https://www.pullreview.com/gitlab/gitlab-org/gitlab-ce/badges/master.svg?)](https://www.pullreview.com/gitlab.gitlab.com/gitlab-org/gitlab-ce/reviews/master)
  29 +
28 ### Resources 30 ### Resources
29 31
30 * [GitLab.com](https://www.gitlab.com/) includes information about [subscriptions](https://www.gitlab.com/subscription/), [consultancy](https://www.gitlab.com/consultancy/), the [community](https://www.gitlab.com/community/) and the [hosted GitLab Cloud](https://www.gitlab.com/cloud/). 32 * [GitLab.com](https://www.gitlab.com/) includes information about [subscriptions](https://www.gitlab.com/subscription/), [consultancy](https://www.gitlab.com/consultancy/), the [community](https://www.gitlab.com/community/) and the [hosted GitLab Cloud](https://www.gitlab.com/cloud/).
@@ -33,7 +35,7 @@ @@ -33,7 +35,7 @@
33 35
34 * [GitLab CI](https://www.gitlab.com/gitlab-ci/) is a continuous integration (CI) server that is easy to integrate with GitLab. 36 * [GitLab CI](https://www.gitlab.com/gitlab-ci/) is a continuous integration (CI) server that is easy to integrate with GitLab.
35 37
36 -* Unofficial third-party [iPhone app](http://gitlabcontrol.com/)m [Android app](https://play.google.com/store/apps/details?id=com.bd.gitlab&hl=en) and [command line client](https://github.com/drewblessing/gitlab-cli) for GitLab. 38 +* Unofficial third-party [iPhone app](http://gitlabcontrol.com/), [Android app](https://play.google.com/store/apps/details?id=com.bd.gitlab&hl=en) and [command line client](https://github.com/drewblessing/gitlab-cli) and [Ruby API wrapper](https://github.com/NARKOZ/gitlab) for GitLab.
37 39
38 ### Requirements 40 ### Requirements
39 41
@@ -51,7 +53,7 @@ @@ -51,7 +53,7 @@
51 53
52 * [GitLab packages](https://www.gitlab.com/downloads/) **recommended** These packages contain GitLab and all its depencies (Ruby, PostgreSQL, Redis, Nginx, Unicorn, etc.). They are made with [omnibus-gitlab](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md) that also contains the installation instructions. 54 * [GitLab packages](https://www.gitlab.com/downloads/) **recommended** These packages contain GitLab and all its depencies (Ruby, PostgreSQL, Redis, Nginx, Unicorn, etc.). They are made with [omnibus-gitlab](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md) that also contains the installation instructions.
53 55
54 -* [GitLab Chef Cookbook](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/README.md) This cookbook can be used both for development installations and production installations. If you want to [contribute](CONTRIBUTE.md) to GitLab we suggest you follow the [development installation on a virtual machine with Vagrant](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/doc/development.md) instructions to install all testing dependencies. 56 +* [GitLab Chef Cookbook](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/README.md) This cookbook can be used both for development installations and production installations. If you want to [contribute](CONTRIBUTE.md) to GitLab we suggest you follow the [development installation](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/doc/development.md) instructions to install all testing dependencies.
55 57
56 * [Manual installation guide](doc/install/installation.md) This guide to set up a production server on Ubuntu offers detailed and complete step-by-step instructions. 58 * [Manual installation guide](doc/install/installation.md) This guide to set up a production server on Ubuntu offers detailed and complete step-by-step instructions.
57 59
@@ -61,6 +63,8 @@ @@ -61,6 +63,8 @@
61 63
62 * [BitNami one-click installers](http://bitnami.com/stack/gitlab) This package contains both GitLab and GitLab CI. It is available as installer, virtual machine or for cloud hosting providers (Amazon Web Services/Azure/etc.). 64 * [BitNami one-click installers](http://bitnami.com/stack/gitlab) This package contains both GitLab and GitLab CI. It is available as installer, virtual machine or for cloud hosting providers (Amazon Web Services/Azure/etc.).
63 65
  66 +* [Cloud 66 deployment and management](http://blog.cloud66.com/installing-gitlab-ubuntu/) Use Cloud 66 to deploy GitLab to your own server or any cloud (eg. DigitalOcean, AWS, Rackspace, GCE) and then manage it with database backups, scaling and more.
  67 +
64 #### Unofficial installation methods 68 #### Unofficial installation methods
65 69
66 * [GitLab recipes](https://gitlab.com/gitlab-org/gitlab-recipes/) repository with unofficial guides for using GitLab with different software (operating systems, webservers, etc.) than the official version. 70 * [GitLab recipes](https://gitlab.com/gitlab-org/gitlab-recipes/) repository with unofficial guides for using GitLab with different software (operating systems, webservers, etc.) than the official version.
1 -6.9.0.pre 1 +6.9.0.rc1
app/assets/javascripts/notes.js.coffee
@@ -53,6 +53,12 @@ class Notes @@ -53,6 +53,12 @@ class Notes
53 # fetch notes when tab becomes visible 53 # fetch notes when tab becomes visible
54 $(document).on "visibilitychange", @visibilityChange 54 $(document).on "visibilitychange", @visibilityChange
55 55
  56 + @notes_forms = '.js-main-target-form textarea, .js-discussion-note-form textarea'
  57 + $(document).on('keypress', @notes_forms, (e)->
  58 + if e.keyCode == 10 || (e.ctrlKey && e.keyCode == 13)
  59 + $(@).parents('form').submit()
  60 + )
  61 +
56 cleanBinding: -> 62 cleanBinding: ->
57 $(document).off "ajax:success", ".js-main-target-form" 63 $(document).off "ajax:success", ".js-main-target-form"
58 $(document).off "ajax:success", ".js-discussion-note-form" 64 $(document).off "ajax:success", ".js-discussion-note-form"
@@ -67,6 +73,7 @@ class Notes @@ -67,6 +73,7 @@ class Notes
67 $(document).off "click", ".js-discussion-reply-button" 73 $(document).off "click", ".js-discussion-reply-button"
68 $(document).off "click", ".js-add-diff-note-button" 74 $(document).off "click", ".js-add-diff-note-button"
69 $(document).off "visibilitychange" 75 $(document).off "visibilitychange"
  76 + $(document).off "keypress", @notes_forms
70 77
71 78
72 initRefresh: -> 79 initRefresh: ->
app/assets/javascripts/project_users_select.js.coffee
1 @projectUsersSelect = 1 @projectUsersSelect =
2 init: -> 2 init: ->
3 $('.ajax-project-users-select').each (i, select) -> 3 $('.ajax-project-users-select').each (i, select) ->
4 - project_id = $('body').data('project-id') 4 + project_id = $(select).data('project-id') || $('body').data('project-id')
5 5
6 $(select).select2 6 $(select).select2
7 placeholder: $(select).data('placeholder') || "Search for a user" 7 placeholder: $(select).data('placeholder') || "Search for a user"
app/assets/stylesheets/generic/files.scss
@@ -11,14 +11,11 @@ @@ -11,14 +11,11 @@
11 } 11 }
12 12
13 .file-title { 13 .file-title {
14 - background: #DDD; 14 + background: #EEE;
15 border-bottom: 1px solid #CCC; 15 border-bottom: 1px solid #CCC;
16 text-shadow: 0 1px 1px #fff; 16 text-shadow: 0 1px 1px #fff;
17 margin: 0; 17 margin: 0;
18 - font-weight: normal;  
19 - font-weight: bold;  
20 text-align: left; 18 text-align: left;
21 - color: $style_color;  
22 padding: 9px 10px; 19 padding: 9px 10px;
23 20
24 .options { 21 .options {
@@ -31,12 +28,15 @@ @@ -31,12 +28,15 @@
31 } 28 }
32 29
33 .file_name { 30 .file_name {
34 - color: $style_color; 31 + font-weight: bold;
  32 + padding-left: 3px;
35 font-size: 14px; 33 font-size: 14px;
36 - text-shadow: 0 1px 1px #fff; 34 +
37 small { 35 small {
38 - color: #999; 36 + color: #888;
39 font-size: 13px; 37 font-size: 13px;
  38 + font-weight: normal;
  39 + padding-left: 10px;
40 } 40 }
41 } 41 }
42 } 42 }
app/assets/stylesheets/generic/forms.scss
@@ -75,3 +75,26 @@ label { @@ -75,3 +75,26 @@ label {
75 width: 200px; 75 width: 200px;
76 } 76 }
77 } 77 }
  78 +
  79 +.commit-message-container {
  80 + background-color: $body-bg;
  81 + position: relative;
  82 + font-family: $monospace_font;
  83 + $left: 12px;
  84 + .max-width-marker {
  85 + color: rgba(0, 0, 0, 0.0);
  86 + font-family: inherit;
  87 + left: $left;
  88 + height: 100%;
  89 + border-right: 1px solid mix($input-border, white);
  90 + position: absolute;
  91 + z-index: 1;
  92 + }
  93 + > textarea {
  94 + background-color: rgba(0, 0, 0, 0.0);
  95 + font-family: inherit;
  96 + padding-left: $left;
  97 + position: relative;
  98 + z-index: 2;
  99 + }
  100 +}
app/assets/stylesheets/generic/issue_box.scss
@@ -12,41 +12,42 @@ @@ -12,41 +12,42 @@
12 margin:20px 0; 12 margin:20px 0;
13 background: #FFF; 13 background: #FFF;
14 border: 1px solid #EEE; 14 border: 1px solid #EEE;
  15 + @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05));
15 16
16 &.issue-box-closed { 17 &.issue-box-closed {
17 - border-color: #DA4E49; 18 + border-color: $border_danger;
18 .state { 19 .state {
19 - background-color: #f2dede;  
20 - border-color: #ebccd1;  
21 - color: #a94442; 20 + background-color: $bg_light_danger;
  21 + border-color: $border_danger;
  22 + color: $color_danger;
22 .state-label { 23 .state-label {
23 - background: #DA4E49; 24 + background-color: $bg_danger;
24 color: #FFF; 25 color: #FFF;
25 } 26 }
26 } 27 }
27 } 28 }
28 29
29 &.issue-box-merged { 30 &.issue-box-merged {
30 - border-color: #31708f; 31 + border-color: $border_primary;
31 .state { 32 .state {
32 - background-color: #d9edf7;  
33 - border-color: #bce8f1;  
34 - color: #31708f; 33 + background-color: $bg_light_primary;
  34 + border-color: $border_primary;
  35 + color: $color_primary;
35 .state-label { 36 .state-label {
36 - background: #31708f; 37 + background-color: $bg_primary;
37 color: #FFF; 38 color: #FFF;
38 } 39 }
39 } 40 }
40 } 41 }
41 42
42 &.issue-box-open { 43 &.issue-box-open {
43 - border-color: #4A4; 44 + border-color: $border_success;
44 .state { 45 .state {
45 - background-color: #dff0d8;  
46 - border-color: #d6e9c6;  
47 - color: #3c763d; 46 + background-color: $bg_light_success;
  47 + border-color: $border_success;
  48 + color: $color_success;
48 .state-label { 49 .state-label {
49 - background: #4A4; 50 + background-color: $bg_success;
50 color: #FFF; 51 color: #FFF;
51 } 52 }
52 } 53 }
@@ -70,7 +71,6 @@ @@ -70,7 +71,6 @@
70 } 71 }
71 72
72 .state { 73 .state {
73 - height: 34px;  
74 border-bottom: 1px solid #DDD; 74 border-bottom: 1px solid #DDD;
75 line-height: 32px; 75 line-height: 32px;
76 } 76 }
@@ -89,6 +89,18 @@ @@ -89,6 +89,18 @@
89 border: none; 89 border: none;
90 border-top: 1px solid #eee; 90 border-top: 1px solid #eee;
91 padding: 15px 25px; 91 padding: 15px 25px;
  92 +
  93 + // Reset text align for children
  94 + .text-right > * { text-align: left; }
  95 +
  96 + @media (max-width: $screen-xs-max) {
  97 + // Don't right align on mobile
  98 + .text-right { text-align: left; }
  99 +
  100 + .row .col-md-6 {
  101 + padding-top: 5px;
  102 + }
  103 + }
92 } 104 }
93 105
94 .description { 106 .description {
@@ -106,7 +118,11 @@ @@ -106,7 +118,11 @@
106 padding: 1px 25px; 118 padding: 1px 25px;
107 text-align: center; 119 text-align: center;
108 text-shadow: none; 120 text-shadow: none;
109 - margin-right: 20px;  
110 display: inline-block; 121 display: inline-block;
  122 + line-height: 34px;
  123 + }
  124 +
  125 + .creator {
  126 + padding: 2px 15px;
111 } 127 }
112 } 128 }
app/assets/stylesheets/generic/jquery.scss
@@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
8 width: 270px; 8 width: 270px;
9 9
10 .ui-datepicker-header { 10 .ui-datepicker-header {
11 - background: #EEE; 11 + background: #FFF;
12 border-color: #DDD; 12 border-color: #DDD;
13 } 13 }
14 14
@@ -19,20 +19,37 @@ @@ -19,20 +19,37 @@
19 } 19 }
20 20
21 &.ui-autocomplete { 21 &.ui-autocomplete {
22 - @include border-radius(0px);  
23 border-color: #DDD; 22 border-color: #DDD;
24 padding: 0; 23 padding: 0;
  24 + margin-top: 2px;
  25 + z-index: 1001;
25 26
26 .ui-menu-item a { 27 .ui-menu-item a {
27 - color: #777;  
28 -  
29 - &:hover {  
30 - background: $hover;  
31 - border-color: $primary_color;  
32 - @include border-radius(0px);  
33 - color: #333;  
34 - } 28 + padding: 4px 10px;
35 } 29 }
36 } 30 }
37 -}  
38 31
  32 + .ui-state-default {
  33 + border: 1px solid #FFF;
  34 + background: #FFF;
  35 + color: #777;
  36 + }
  37 +
  38 + .ui-state-highlight {
  39 + border: 1px solid #EEE;
  40 + background: #EEE;
  41 + }
  42 +
  43 + .ui-state-active {
  44 + border: 1px solid $bg_style_color;
  45 + background: $bg_style_color;
  46 + color: #FFF;
  47 + }
  48 +
  49 + .ui-state-hover,
  50 + .ui-state-focus {
  51 + border: 1px solid $hover;
  52 + background: $hover;
  53 + color: #333;
  54 + }
  55 +}
app/assets/stylesheets/generic/typography.scss
@@ -47,7 +47,7 @@ a { @@ -47,7 +47,7 @@ a {
47 text-decoration: underline; 47 text-decoration: underline;
48 } 48 }
49 49
50 - &.dark { 50 + &.darken {
51 color: $style_color; 51 color: $style_color;
52 } 52 }
53 53
app/assets/stylesheets/main/mixins.scss
@@ -41,31 +41,6 @@ @@ -41,31 +41,6 @@
41 * Prefilled mixins 41 * Prefilled mixins
42 * Mixins with fixed values 42 * Mixins with fixed values
43 */ 43 */
44 -@mixin bg-light-gray-gradient {  
45 - background: #f1f1f1;  
46 - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #f5f5f5), to(#e1e1e1));  
47 - background-image: -webkit-linear-gradient(#f5f5f5 6.6%, #e1e1e1);  
48 - background-image: -moz-linear-gradient(#f5f5f5 6.6%, #e1e1e1);  
49 - background-image: -ms-linear-gradient(#f5f5f5 6.6%, #e1e1e1);  
50 - background-image: -o-linear-gradient(#f5f5f5 6.6%, #e1e1e1);  
51 -}  
52 -  
53 -@mixin bg-gray-gradient {  
54 - background: #eee;  
55 - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));  
56 - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);  
57 - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);  
58 - background-image: -ms-linear-gradient(#eee 6.6%, #dfdfdf);  
59 - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);  
60 -}  
61 -  
62 -@mixin bg-dark-gray-gradient {  
63 - background: #eee;  
64 - background-image: -webkit-linear-gradient(#e9e9e9, #d7d7d7);  
65 - background-image: -moz-linear-gradient(#e9e9e9, #d7d7d7);  
66 - background-image: -ms-linear-gradient(#e9e9e9, #d7d7d7);  
67 - background-image: -o-linear-gradient(#e9e9e9, #d7d7d7);  
68 -}  
69 44
70 @mixin shade { 45 @mixin shade {
71 @include box-shadow(0 0 3px #ddd); 46 @include box-shadow(0 0 3px #ddd);
@@ -77,7 +52,6 @@ @@ -77,7 +52,6 @@
77 52
78 @mixin header-font { 53 @mixin header-font {
79 color: $style_color; 54 color: $style_color;
80 - text-shadow: 0 1px 1px #FFF;  
81 font-size: 16px; 55 font-size: 16px;
82 line-height: 44px; 56 line-height: 44px;
83 font-weight: normal; 57 font-weight: normal;
app/assets/stylesheets/main/variables.scss
@@ -8,6 +8,31 @@ $bg_style_color: #2299BB; @@ -8,6 +8,31 @@ $bg_style_color: #2299BB;
8 $list-group-active-bg: $bg_style_color; 8 $list-group-active-bg: $bg_style_color;
9 $hover: #D9EDF7; 9 $hover: #D9EDF7;
10 10
  11 +/*
  12 + * Success colors (green)
  13 + */
  14 +$border_success: #4cae4c;
  15 +$bg_success: #5cb85c;
  16 +$bg_light_success: #dff0d8;
  17 +$color_success: #3c763d;
  18 +
  19 +/*
  20 + * Danger colors (red)
  21 + */
  22 +$border_danger: #d43f3a;
  23 +$bg_danger: #d9534f;
  24 +$bg_light_danger: #f2dede;
  25 +$color_danger: #a94442;
  26 +
  27 +/*
  28 + * Primary colors (blue)
  29 + */
  30 +$border_primary: #358ebd;
  31 +$bg_primary: #429bca;
  32 +$bg_light_primary: #d9edf7;
  33 +$color_primary: #31708f;
  34 +
  35 +
11 /** 36 /**
12 * Commit Diff Colors 37 * Commit Diff Colors
13 */ 38 */
app/assets/stylesheets/sections/diff.scss
@@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
4 4
5 .diff-header { 5 .diff-header {
6 @extend .clearfix; 6 @extend .clearfix;
7 - background: #DDD; 7 + background: #EEE;
8 border-bottom: 1px solid #CCC; 8 border-bottom: 1px solid #CCC;
9 padding: 5px 5px 5px 10px; 9 padding: 5px 5px 5px 10px;
10 color: #555; 10 color: #555;
@@ -63,30 +63,21 @@ @@ -63,30 +63,21 @@
63 } 63 }
64 } 64 }
65 65
66 - .text-file-parallel div {  
67 - display: inline-block;  
68 - padding-bottom: 16px;  
69 - }  
70 - .diff-side {  
71 - overflow-x: scroll;  
72 - width: 508px;  
73 - }  
74 - .diff-side.diff-side-left{  
75 - overflow-y:hidden;  
76 - }  
77 - .diff-side table, td.diff-middle table {  
78 - }  
79 - .diff-middle {  
80 - width: 114px;  
81 - vertical-align: top;  
82 - overflow: hidden 66 + tr.line_holder.parallel{
  67 + .old_line, .new_line, .diff_line {
  68 + min-width: 50px;
  69 + }
  70 +
  71 + td.line_content.parallel{
  72 + width: 50%;
  73 + }
83 } 74 }
84 75
85 .old_line, .new_line, .diff_line { 76 .old_line, .new_line, .diff_line {
86 margin: 0px; 77 margin: 0px;
87 padding: 0px; 78 padding: 0px;
88 border: none; 79 border: none;
89 - background: #EEE; 80 + background: #F5F5F5;
90 color: #666; 81 color: #666;
91 padding: 0px 5px; 82 padding: 0px 5px;
92 border-right: 1px solid #ccc; 83 border-right: 1px solid #ccc;
@@ -304,15 +295,9 @@ @@ -304,15 +295,9 @@
304 } //.view.onion-skin 295 } //.view.onion-skin
305 } 296 }
306 .view-modes{ 297 .view-modes{
307 -  
308 padding: 10px; 298 padding: 10px;
309 text-align: center; 299 text-align: center;
310 -  
311 - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));  
312 - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);  
313 - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);  
314 - background-image: -ms-linear-gradient(#eee 6.6%, #dfdfdf);  
315 - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); 300 + background: #EEE;
316 301
317 ul, li{ 302 ul, li{
318 list-style: none; 303 list-style: none;
app/assets/stylesheets/sections/graph.scss
1 .project-network { 1 .project-network {
2 - border: 1px solid #aaa;  
3 - padding: 1px; 2 + border: 1px solid #CCC;
4 3
5 .tip { 4 .tip {
6 color: #888; 5 color: #888;
7 font-size: 14px; 6 font-size: 14px;
8 padding: 10px; 7 padding: 10px;
9 border-bottom: 1px solid #bbb; 8 border-bottom: 1px solid #bbb;
10 - @include bg-gray-gradient; 9 + background: #EEE;
11 } 10 }
12 11
13 .network-graph { 12 .network-graph {
14 - background: #f1f1f1; 13 + background: #FFF;
15 height: 500px; 14 height: 500px;
16 overflow-y: scroll; 15 overflow-y: scroll;
17 overflow-x: hidden; 16 overflow-x: hidden;
app/assets/stylesheets/sections/header.scss
@@ -14,7 +14,6 @@ header { @@ -14,7 +14,6 @@ header {
14 14
15 .nav > li > a { 15 .nav > li > a {
16 color: $style_color; 16 color: $style_color;
17 - text-shadow: 0 1px 0 #fff;  
18 font-size: 14px; 17 font-size: 14px;
19 line-height: 32px; 18 line-height: 32px;
20 padding: 6px 10px; 19 padding: 6px 10px;
@@ -190,7 +189,6 @@ header { @@ -190,7 +189,6 @@ header {
190 189
191 .nav > li > a { 190 .nav > li > a {
192 color: #AAA; 191 color: #AAA;
193 - text-shadow: 0 1px 0 #444;  
194 192
195 &:hover, &:focus, &:active { 193 &:hover, &:focus, &:active {
196 background: none; 194 background: none;
@@ -224,7 +222,6 @@ header { @@ -224,7 +222,6 @@ header {
224 background: image-url('logo-white.png') no-repeat center center; 222 background: image-url('logo-white.png') no-repeat center center;
225 background-size: 32px; 223 background-size: 32px;
226 color: #fff; 224 color: #fff;
227 - text-shadow: 0 1px 1px #444;  
228 } 225 }
229 } 226 }
230 } 227 }
@@ -236,7 +233,6 @@ header { @@ -236,7 +233,6 @@ header {
236 } 233 }
237 } 234 }
238 color: #fff; 235 color: #fff;
239 - text-shadow: 0 1px 1px #444;  
240 } 236 }
241 } 237 }
242 238
app/assets/stylesheets/sections/issues.scss
@@ -45,14 +45,6 @@ @@ -45,14 +45,6 @@
45 padding: 6px 10px; 45 padding: 6px 10px;
46 border: 1px solid #ccc; 46 border: 1px solid #ccc;
47 @include border-radius(4px); 47 @include border-radius(4px);
48 -  
49 -  
50 - input.check_all_issues {  
51 - padding: 0;  
52 - margin: 0;  
53 - position: relative;  
54 - top: 3px;  
55 - }  
56 } 48 }
57 49
58 .issues_content { 50 .issues_content {
@@ -143,3 +135,36 @@ form.edit-issue { @@ -143,3 +135,36 @@ form.edit-issue {
143 border-color: #E5E5E5; 135 border-color: #E5E5E5;
144 } 136 }
145 } 137 }
  138 +
  139 +@media (max-width: $screen-xs-max) {
  140 + .issue-btn-group {
  141 + width: 100%;
  142 + margin-top: 5px;
  143 +
  144 + .btn-group {
  145 + width: 100%;
  146 +
  147 + ul {
  148 + width: 100%;
  149 + text-align: center;
  150 + }
  151 + }
  152 +
  153 + .btn {
  154 + width: 100%;
  155 + margin-top: -1px;
  156 +
  157 + &:first-child:not(:last-child) {
  158 + border-radius: 4px 4px 0 0;
  159 + }
  160 +
  161 + &:not(:first-child):not(:last-child) {
  162 + border-radius: 0;
  163 + }
  164 +
  165 + &:last-child:not(:first-child) {
  166 + border-radius: 0 0 4px 4px;
  167 + }
  168 + }
  169 + }
  170 +}
app/assets/stylesheets/sections/merge_requests.scss
@@ -31,7 +31,6 @@ @@ -31,7 +31,6 @@
31 31
32 .mr_source_commit, 32 .mr_source_commit,
33 .mr_target_commit { 33 .mr_target_commit {
34 - margin-top: 10px;  
35 .commit { 34 .commit {
36 margin: 0; 35 margin: 0;
37 padding: 2px 0; 36 padding: 2px 0;
@@ -74,6 +73,10 @@ @@ -74,6 +73,10 @@
74 73
75 .merge-request-info { 74 .merge-request-info {
76 color: #999; 75 color: #999;
  76 +
  77 + .merge-request-labels {
  78 + display: inline-block;
  79 + }
77 } 80 }
78 } 81 }
79 } 82 }
@@ -112,3 +115,7 @@ @@ -112,3 +115,7 @@
112 } 115 }
113 } 116 }
114 } 117 }
  118 +
  119 +.merge-request-show-labels .label {
  120 + padding: 6px 10px;
  121 +}
app/assets/stylesheets/sections/notes.scss
@@ -139,6 +139,7 @@ ul.notes { @@ -139,6 +139,7 @@ ul.notes {
139 background-color: #fff; 139 background-color: #fff;
140 border-width: 1px 0; 140 border-width: 1px 0;
141 padding-top: 0; 141 padding-top: 0;
  142 + vertical-align: top;
142 143
143 li { 144 li {
144 padding: 5px; 145 padding: 5px;
app/assets/stylesheets/sections/profile.scss
@@ -76,7 +76,7 @@ @@ -76,7 +76,7 @@
76 } 76 }
77 77
78 &.modern { 78 &.modern {
79 - background: #345; 79 + background: #009871;
80 } 80 }
81 81
82 &.gray { 82 &.gray {
@@ -84,7 +84,7 @@ @@ -84,7 +84,7 @@
84 } 84 }
85 85
86 &.violet { 86 &.violet {
87 - background: #547; 87 + background: #548;
88 } 88 }
89 } 89 }
90 } 90 }
app/assets/stylesheets/sections/tree.scss
@@ -151,3 +151,5 @@ @@ -151,3 +151,5 @@
151 } 151 }
152 } 152 }
153 } 153 }
  154 +
  155 +#modal-remove-blob > .modal-dialog { width: 850px; }
app/assets/stylesheets/sections/votes.scss
@@ -40,4 +40,10 @@ @@ -40,4 +40,10 @@
40 .votes-holder { 40 .votes-holder {
41 float: right; 41 float: right;
42 width: 250px; 42 width: 250px;
  43 +
  44 + @media (max-width: $screen-xs-max) {
  45 + width: 100%;
  46 + margin-top: 5px;
  47 + margin-bottom: 10px;
  48 + }
43 } 49 }
app/assets/stylesheets/themes/ui_color.scss
@@ -16,28 +16,28 @@ @@ -16,28 +16,28 @@
16 @extend .header-dark; 16 @extend .header-dark;
17 &.navbar-gitlab { 17 &.navbar-gitlab {
18 .navbar-inner { 18 .navbar-inner {
19 - background: #547;  
20 - border-bottom: 1px solid #435; 19 + background: #548;
  20 + border-bottom: 1px solid #436;
21 .app_logo, .navbar-toggle { 21 .app_logo, .navbar-toggle {
22 &:hover { 22 &:hover {
23 - background-color: #435; 23 + background-color: #436;
24 } 24 }
25 } 25 }
26 .separator { 26 .separator {
27 - background: #435;  
28 - border-left: 1px solid #658; 27 + background: #436;
  28 + border-left: 1px solid #659;
29 } 29 }
30 .nav > li > a { 30 .nav > li > a {
31 - color: #98B; 31 + color: #98C;
32 } 32 }
33 .search-input { 33 .search-input {
34 - border-color: #98B; 34 + border-color: #98C;
35 } 35 }
36 } 36 }
37 } 37 }
38 } 38 }
39 39
40 .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus { 40 .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus {
41 - background: #769; 41 + background: #659;
42 } 42 }
43 } 43 }
app/assets/stylesheets/themes/ui_modern.scss
@@ -16,24 +16,28 @@ @@ -16,24 +16,28 @@
16 @extend .header-dark; 16 @extend .header-dark;
17 &.navbar-gitlab { 17 &.navbar-gitlab {
18 .navbar-inner { 18 .navbar-inner {
19 - background: #345;  
20 - border-bottom: 1px solid #234; 19 + background: #00AC7E;
  20 + border-bottom: 1px solid #00AC7E;
21 .app_logo, .navbar-toggle { 21 .app_logo, .navbar-toggle {
22 &:hover { 22 &:hover {
23 - background-color: #234; 23 + background-color: #009C6E;
24 } 24 }
25 } 25 }
26 .separator { 26 .separator {
27 - background: #234;  
28 - border-left: 1px solid #456; 27 + background: #009C6F;
  28 + border-left: 1px solid #10BC8E;
29 } 29 }
30 .nav > li > a { 30 .nav > li > a {
31 - color: #89A; 31 + color: #ADC;
32 } 32 }
33 .search-input { 33 .search-input {
34 - border-color: #89A; 34 + border-color: #7fd5be;
35 } 35 }
36 } 36 }
37 } 37 }
38 } 38 }
  39 +
  40 + .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus {
  41 + background: #00AC7E;
  42 + }
39 } 43 }
app/controllers/application_controller.rb
@@ -117,6 +117,11 @@ class ApplicationController &lt; ActionController::Base @@ -117,6 +117,11 @@ class ApplicationController &lt; ActionController::Base
117 return access_denied! unless can?(current_user, :push_code, project) 117 return access_denied! unless can?(current_user, :push_code, project)
118 end 118 end
119 119
  120 + def authorize_labels!
  121 + # Labels should be accessible for issues and/or merge requests
  122 + authorize_read_issue! || authorize_read_merge_request!
  123 + end
  124 +
120 def access_denied! 125 def access_denied!
121 render "errors/access_denied", layout: "errors", status: 404 126 render "errors/access_denied", layout: "errors", status: 404
122 end 127 end
app/controllers/groups_controller.rb
@@ -68,7 +68,7 @@ class GroupsController &lt; ApplicationController @@ -68,7 +68,7 @@ class GroupsController &lt; ApplicationController
68 @members = group.users_groups 68 @members = group.users_groups
69 69
70 if params[:search].present? 70 if params[:search].present?
71 - users = group.users.search(params[:search]) 71 + users = group.users.search(params[:search]).to_a
72 @members = @members.where(user_id: users) 72 @members = @members.where(user_id: users)
73 end 73 end
74 74
app/controllers/projects/labels_controller.rb
1 class Projects::LabelsController < Projects::ApplicationController 1 class Projects::LabelsController < Projects::ApplicationController
2 before_filter :module_enabled 2 before_filter :module_enabled
3 3
4 - # Allow read any issue  
5 - before_filter :authorize_read_issue! 4 + before_filter :authorize_labels!
6 5
7 respond_to :js, :html 6 respond_to :js, :html
8 7
@@ -13,12 +12,18 @@ class Projects::LabelsController &lt; Projects::ApplicationController @@ -13,12 +12,18 @@ class Projects::LabelsController &lt; Projects::ApplicationController
13 def generate 12 def generate
14 Gitlab::IssuesLabels.generate(@project) 13 Gitlab::IssuesLabels.generate(@project)
15 14
16 - redirect_to project_issues_path(@project) 15 + if params[:redirect] == 'issues'
  16 + redirect_to project_issues_path(@project)
  17 + elsif params[:redirect] == 'merge_requests'
  18 + redirect_to project_merge_requests_path(@project)
  19 + end
17 end 20 end
18 21
19 protected 22 protected
20 23
21 def module_enabled 24 def module_enabled
22 - return render_404 unless @project.issues_enabled 25 + unless @project.issues_enabled || @project.merge_requests_enabled
  26 + return render_404
  27 + end
23 end 28 end
24 end 29 end
app/controllers/projects/merge_requests_controller.rb
@@ -62,11 +62,27 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController @@ -62,11 +62,27 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController
62 @merge_request.source_project = @project unless @merge_request.source_project 62 @merge_request.source_project = @project unless @merge_request.source_project
63 @merge_request.target_project ||= (@project.forked_from_project || @project) 63 @merge_request.target_project ||= (@project.forked_from_project || @project)
64 @target_branches = @merge_request.target_project.nil? ? [] : @merge_request.target_project.repository.branch_names 64 @target_branches = @merge_request.target_project.nil? ? [] : @merge_request.target_project.repository.branch_names
65 -  
66 @merge_request.target_branch ||= @merge_request.target_project.default_branch 65 @merge_request.target_branch ||= @merge_request.target_project.default_branch
67 -  
68 @source_project = @merge_request.source_project 66 @source_project = @merge_request.source_project
69 - @merge_request 67 +
  68 + if @merge_request.target_branch && @merge_request.source_branch
  69 + compare_action = Gitlab::Satellite::CompareAction.new(
  70 + current_user,
  71 + @merge_request.target_project,
  72 + @merge_request.target_branch,
  73 + @merge_request.source_project,
  74 + @merge_request.source_branch
  75 + )
  76 +
  77 + @commits = compare_action.commits
  78 + @commits.map! { |commit| Commit.new(commit) }
  79 + @commit = @commits.first
  80 +
  81 + @diffs = compare_action.diffs
  82 + @merge_request.title = @merge_request.source_branch.titleize.humanize
  83 + @target_project = @merge_request.target_project
  84 + @target_repo = @target_project.repository
  85 + end
70 end 86 end
71 87
72 def edit 88 def edit
@@ -80,7 +96,7 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController @@ -80,7 +96,7 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController
80 @merge_request = MergeRequests::CreateService.new(project, current_user, params[:merge_request]).execute 96 @merge_request = MergeRequests::CreateService.new(project, current_user, params[:merge_request]).execute
81 97
82 if @merge_request.valid? 98 if @merge_request.valid?
83 - redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully created.' 99 + redirect_to project_merge_request_path(@merge_request.target_project, @merge_request), notice: 'Merge request was successfully created.'
84 else 100 else
85 @source_project = @merge_request.source_project 101 @source_project = @merge_request.source_project
86 @target_project = @merge_request.target_project 102 @target_project = @merge_request.target_project
app/controllers/projects/wikis_controller.rb
@@ -12,9 +12,22 @@ class Projects::WikisController &lt; Projects::ApplicationController @@ -12,9 +12,22 @@ class Projects::WikisController &lt; Projects::ApplicationController
12 12
13 def show 13 def show
14 @page = @project_wiki.find_page(params[:id], params[:version_id]) 14 @page = @project_wiki.find_page(params[:id], params[:version_id])
  15 + gollum_wiki = @project_wiki.wiki
  16 + file = gollum_wiki.file(params[:id], gollum_wiki.ref, true)
15 17
16 if @page 18 if @page
17 render 'show' 19 render 'show'
  20 + elsif file
  21 + if file.on_disk?
  22 + send_file file.on_disk_path, disposition: 'inline'
  23 + else
  24 + send_data(
  25 + file.raw_data,
  26 + type: file.mime_type,
  27 + disposition: 'inline',
  28 + filename: file.name
  29 + )
  30 + end
18 else 31 else
19 return render('empty') unless can?(current_user, :write_wiki, @project) 32 return render('empty') unless can?(current_user, :write_wiki, @project)
20 @page = WikiPage.new(@project_wiki) 33 @page = WikiPage.new(@project_wiki)
app/helpers/commits_helper.rb
@@ -117,7 +117,7 @@ module CommitsHelper @@ -117,7 +117,7 @@ module CommitsHelper
117 added_lines[line_new] = { line_code: line_code, type: type, line: line } 117 added_lines[line_new] = { line_code: line_code, type: type, line: line }
118 end 118 end
119 end 119 end
120 - max_length = old_file ? old_file.sloc + added_lines.length : file.sloc 120 + max_length = old_file ? [old_file.loc, file.loc].max : file.loc
121 121
122 offset1 = 0 122 offset1 = 0
123 offset2 = 0 123 offset2 = 0
app/helpers/issues_helper.rb
@@ -82,7 +82,7 @@ module IssuesHelper @@ -82,7 +82,7 @@ module IssuesHelper
82 end 82 end
83 83
84 def milestone_options object 84 def milestone_options object
85 - options_from_collection_for_select(@project.milestones.active, 'id', 'title', object.milestone_id) 85 + options_from_collection_for_select(object.project.milestones.active, 'id', 'title', object.milestone_id)
86 end 86 end
87 87
88 def issue_box_class(item) 88 def issue_box_class(item)
app/helpers/selects_helper.rb
@@ -14,7 +14,7 @@ module SelectsHelper @@ -14,7 +14,7 @@ module SelectsHelper
14 css_class << (opts[:class] || '') 14 css_class << (opts[:class] || '')
15 value = opts[:selected] || '' 15 value = opts[:selected] || ''
16 placeholder = opts[:placeholder] || 'Select user' 16 placeholder = opts[:placeholder] || 'Select user'
17 -  
18 - hidden_field_tag(id, value, class: css_class, 'data-placeholder' => placeholder) 17 + project_id = opts[:project_id] || @project.id
  18 + hidden_field_tag(id, value, class: css_class, 'data-placeholder' => placeholder, 'data-project-id' => project_id)
19 end 19 end
20 end 20 end
app/mailers/emails/issues.rb
@@ -4,6 +4,7 @@ module Emails @@ -4,6 +4,7 @@ module Emails
4 @issue = Issue.find(issue_id) 4 @issue = Issue.find(issue_id)
5 @project = @issue.project 5 @project = @issue.project
6 @target_url = project_issue_url(@project, @issue) 6 @target_url = project_issue_url(@project, @issue)
  7 + set_message_id("issue_#{issue_id}")
7 mail(from: sender(@issue.author_id), 8 mail(from: sender(@issue.author_id),
8 to: recipient(recipient_id), 9 to: recipient(recipient_id),
9 subject: subject("#{@issue.title} (##{@issue.iid})")) 10 subject: subject("#{@issue.title} (##{@issue.iid})"))
@@ -14,6 +15,7 @@ module Emails @@ -14,6 +15,7 @@ module Emails
14 @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id 15 @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
15 @project = @issue.project 16 @project = @issue.project
16 @target_url = project_issue_url(@project, @issue) 17 @target_url = project_issue_url(@project, @issue)
  18 + set_reference("issue_#{issue_id}")
17 mail(from: sender(updated_by_user_id), 19 mail(from: sender(updated_by_user_id),
18 to: recipient(recipient_id), 20 to: recipient(recipient_id),
19 subject: subject("#{@issue.title} (##{@issue.iid})")) 21 subject: subject("#{@issue.title} (##{@issue.iid})"))
@@ -24,6 +26,7 @@ module Emails @@ -24,6 +26,7 @@ module Emails
24 @project = @issue.project 26 @project = @issue.project
25 @updated_by = User.find updated_by_user_id 27 @updated_by = User.find updated_by_user_id
26 @target_url = project_issue_url(@project, @issue) 28 @target_url = project_issue_url(@project, @issue)
  29 + set_reference("issue_#{issue_id}")
27 mail(from: sender(updated_by_user_id), 30 mail(from: sender(updated_by_user_id),
28 to: recipient(recipient_id), 31 to: recipient(recipient_id),
29 subject: subject("#{@issue.title} (##{@issue.iid})")) 32 subject: subject("#{@issue.title} (##{@issue.iid})"))
@@ -35,6 +38,7 @@ module Emails @@ -35,6 +38,7 @@ module Emails
35 @project = @issue.project 38 @project = @issue.project
36 @updated_by = User.find updated_by_user_id 39 @updated_by = User.find updated_by_user_id
37 @target_url = project_issue_url(@project, @issue) 40 @target_url = project_issue_url(@project, @issue)
  41 + set_reference("issue_#{issue_id}")
38 mail(from: sender(updated_by_user_id), 42 mail(from: sender(updated_by_user_id),
39 to: recipient(recipient_id), 43 to: recipient(recipient_id),
40 subject: subject("#{@issue.title} (##{@issue.iid})")) 44 subject: subject("#{@issue.title} (##{@issue.iid})"))
app/mailers/emails/merge_requests.rb
@@ -4,9 +4,10 @@ module Emails @@ -4,9 +4,10 @@ module Emails
4 @merge_request = MergeRequest.find(merge_request_id) 4 @merge_request = MergeRequest.find(merge_request_id)
5 @project = @merge_request.project 5 @project = @merge_request.project
6 @target_url = project_merge_request_url(@project, @merge_request) 6 @target_url = project_merge_request_url(@project, @merge_request)
  7 + set_message_id("merge_request_#{merge_request_id}")
7 mail(from: sender(@merge_request.author_id), 8 mail(from: sender(@merge_request.author_id),
8 to: recipient(recipient_id), 9 to: recipient(recipient_id),
9 - subject: subject("#{@merge_request.title} (!#{@merge_request.iid})")) 10 + subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
10 end 11 end
11 12
12 def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id) 13 def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id)
@@ -14,9 +15,10 @@ module Emails @@ -14,9 +15,10 @@ module Emails
14 @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id 15 @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
15 @project = @merge_request.project 16 @project = @merge_request.project
16 @target_url = project_merge_request_url(@project, @merge_request) 17 @target_url = project_merge_request_url(@project, @merge_request)
  18 + set_reference("merge_request_#{merge_request_id}")
17 mail(from: sender(updated_by_user_id), 19 mail(from: sender(updated_by_user_id),
18 to: recipient(recipient_id), 20 to: recipient(recipient_id),
19 - subject: subject("#{@merge_request.title} (!#{@merge_request.iid})")) 21 + subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
20 end 22 end
21 23
22 def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) 24 def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
@@ -24,18 +26,20 @@ module Emails @@ -24,18 +26,20 @@ module Emails
24 @updated_by = User.find updated_by_user_id 26 @updated_by = User.find updated_by_user_id
25 @project = @merge_request.project 27 @project = @merge_request.project
26 @target_url = project_merge_request_url(@project, @merge_request) 28 @target_url = project_merge_request_url(@project, @merge_request)
  29 + set_reference("merge_request_#{merge_request_id}")
27 mail(from: sender(updated_by_user_id), 30 mail(from: sender(updated_by_user_id),
28 to: recipient(recipient_id), 31 to: recipient(recipient_id),
29 - subject: subject("#{@merge_request.title} (!#{@merge_request.iid})")) 32 + subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
30 end 33 end
31 34
32 def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) 35 def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
33 @merge_request = MergeRequest.find(merge_request_id) 36 @merge_request = MergeRequest.find(merge_request_id)
34 @project = @merge_request.project 37 @project = @merge_request.project
35 @target_url = project_merge_request_url(@project, @merge_request) 38 @target_url = project_merge_request_url(@project, @merge_request)
  39 + set_reference("merge_request_#{merge_request_id}")
36 mail(from: sender(updated_by_user_id), 40 mail(from: sender(updated_by_user_id),
37 to: recipient(recipient_id), 41 to: recipient(recipient_id),
38 - subject: subject("#{@merge_request.title} (!#{@merge_request.iid})")) 42 + subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
39 end 43 end
40 end 44 end
41 45
app/mailers/emails/notes.rb
@@ -15,6 +15,7 @@ module Emails @@ -15,6 +15,7 @@ module Emails
15 @issue = @note.noteable 15 @issue = @note.noteable
16 @project = @note.project 16 @project = @note.project
17 @target_url = project_issue_url(@project, @issue, anchor: "note_#{@note.id}") 17 @target_url = project_issue_url(@project, @issue, anchor: "note_#{@note.id}")
  18 + set_reference("issue_#{@issue.id}")
18 mail(from: sender(@note.author_id), 19 mail(from: sender(@note.author_id),
19 to: recipient(recipient_id), 20 to: recipient(recipient_id),
20 subject: subject("#{@issue.title} (##{@issue.iid})")) 21 subject: subject("#{@issue.title} (##{@issue.iid})"))
@@ -25,9 +26,10 @@ module Emails @@ -25,9 +26,10 @@ module Emails
25 @merge_request = @note.noteable 26 @merge_request = @note.noteable
26 @project = @note.project 27 @project = @note.project
27 @target_url = project_merge_request_url(@project, @merge_request, anchor: "note_#{@note.id}") 28 @target_url = project_merge_request_url(@project, @merge_request, anchor: "note_#{@note.id}")
  29 + set_reference("merge_request_#{@merge_request.id}")
28 mail(from: sender(@note.author_id), 30 mail(from: sender(@note.author_id),
29 to: recipient(recipient_id), 31 to: recipient(recipient_id),
30 - subject: subject("#{@merge_request.title} (!#{@merge_request.iid})")) 32 + subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
31 end 33 end
32 34
33 def note_wall_email(recipient_id, note_id) 35 def note_wall_email(recipient_id, note_id)
app/mailers/notify.rb
@@ -53,6 +53,22 @@ class Notify &lt; ActionMailer::Base @@ -53,6 +53,22 @@ class Notify &lt; ActionMailer::Base
53 end 53 end
54 end 54 end
55 55
  56 + # Set the Message-ID header field
  57 + #
  58 + # local_part - The local part of the message ID
  59 + #
  60 + def set_message_id(local_part)
  61 + headers["Message-ID"] = "<#{local_part}@#{Gitlab.config.gitlab.host}>"
  62 + end
  63 +
  64 + # Set the References header field
  65 + #
  66 + # local_part - The local part of the referenced message ID
  67 + #
  68 + def set_reference(local_part)
  69 + headers["References"] = "<#{local_part}@#{Gitlab.config.gitlab.host}>"
  70 + end
  71 +
56 # Formats arguments into a String suitable for use as an email subject 72 # Formats arguments into a String suitable for use as an email subject
57 # 73 #
58 # extra - Extra Strings to be inserted into the subject 74 # extra - Extra Strings to be inserted into the subject
app/models/merge_request.rb
@@ -36,7 +36,9 @@ class MergeRequest &lt; ActiveRecord::Base @@ -36,7 +36,9 @@ class MergeRequest &lt; ActiveRecord::Base
36 36
37 delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil 37 delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil
38 38
39 - attr_accessible :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, :state_event, :description 39 + attr_accessible :title, :assignee_id, :source_project_id, :source_branch,
  40 + :target_project_id, :target_branch, :milestone_id,
  41 + :state_event, :description, :label_list
40 42
41 attr_accessor :should_remove_source_branch 43 attr_accessor :should_remove_source_branch
42 44
@@ -44,6 +46,9 @@ class MergeRequest &lt; ActiveRecord::Base @@ -44,6 +46,9 @@ class MergeRequest &lt; ActiveRecord::Base
44 # It allows us to close or modify broken merge requests 46 # It allows us to close or modify broken merge requests
45 attr_accessor :allow_broken 47 attr_accessor :allow_broken
46 48
  49 + ActsAsTaggableOn.strict_case_match = true
  50 + acts_as_taggable_on :labels
  51 +
47 state_machine :state, initial: :opened do 52 state_machine :state, initial: :opened do
48 event :close do 53 event :close do
49 transition [:reopened, :opened] => :closed 54 transition [:reopened, :opened] => :closed
@@ -253,6 +258,14 @@ class MergeRequest &lt; ActiveRecord::Base @@ -253,6 +258,14 @@ class MergeRequest &lt; ActiveRecord::Base
253 end 258 end
254 end 259 end
255 260
  261 + def target_project_namespace
  262 + if target_project && target_project.namespace
  263 + target_project.namespace.path
  264 + else
  265 + "(removed)"
  266 + end
  267 + end
  268 +
256 def source_branch_exists? 269 def source_branch_exists?
257 return false unless self.source_project 270 return false unless self.source_project
258 271
app/models/merge_request_diff.rb
@@ -86,7 +86,7 @@ class MergeRequestDiff &lt; ActiveRecord::Base @@ -86,7 +86,7 @@ class MergeRequestDiff &lt; ActiveRecord::Base
86 # between target and source branches 86 # between target and source branches
87 def unmerged_commits 87 def unmerged_commits
88 commits = if merge_request.for_fork? 88 commits = if merge_request.for_fork?
89 - Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).commits_between 89 + compare_action.commits
90 else 90 else
91 repository.commits_between(target_branch, source_branch) 91 repository.commits_between(target_branch, source_branch)
92 end 92 end
@@ -150,7 +150,7 @@ class MergeRequestDiff &lt; ActiveRecord::Base @@ -150,7 +150,7 @@ class MergeRequestDiff &lt; ActiveRecord::Base
150 # between target and source branches 150 # between target and source branches
151 def unmerged_diffs 151 def unmerged_diffs
152 diffs = if merge_request.for_fork? 152 diffs = if merge_request.for_fork?
153 - Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).diffs_between_satellite 153 + compare_action.diffs
154 else 154 else
155 Gitlab::Git::Diff.between(repository, source_branch, target_branch) 155 Gitlab::Git::Diff.between(repository, source_branch, target_branch)
156 end 156 end
@@ -165,4 +165,16 @@ class MergeRequestDiff &lt; ActiveRecord::Base @@ -165,4 +165,16 @@ class MergeRequestDiff &lt; ActiveRecord::Base
165 def repository 165 def repository
166 merge_request.target_project.repository 166 merge_request.target_project.repository
167 end 167 end
  168 +
  169 + private
  170 +
  171 + def compare_action
  172 + Gitlab::Satellite::CompareAction.new(
  173 + merge_request.author,
  174 + merge_request.target_project,
  175 + merge_request.target_branch,
  176 + merge_request.source_project,
  177 + merge_request.source_branch
  178 + )
  179 + end
168 end 180 end
app/models/project.rb
@@ -281,8 +281,11 @@ class Project &lt; ActiveRecord::Base @@ -281,8 +281,11 @@ class Project &lt; ActiveRecord::Base
281 self.id 281 self.id
282 end 282 end
283 283
  284 + # Tags are shared by issues and merge requests
284 def issues_labels 285 def issues_labels
285 - @issues_labels ||= (issues_default_labels + issues.tags_on(:labels)).uniq.sort_by(&:name) 286 + @issues_labels ||= (issues_default_labels +
  287 + merge_requests.tags_on(:labels) +
  288 + issues.tags_on(:labels)).uniq.sort_by(&:name)
286 end 289 end
287 290
288 def issue_exists?(issue_id) 291 def issue_exists?(issue_id)
app/models/project_wiki.rb
@@ -64,7 +64,8 @@ class ProjectWiki @@ -64,7 +64,8 @@ class ProjectWiki
64 # 64 #
65 # Returns an initialized WikiPage instance or nil 65 # Returns an initialized WikiPage instance or nil
66 def find_page(title, version = nil) 66 def find_page(title, version = nil)
67 - if page = wiki.page(title, version) 67 + page_title, page_dir = page_title_and_dir(title)
  68 + if page = wiki.page(page_title, version, page_dir)
68 WikiPage.new(self, page, true) 69 WikiPage.new(self, page, true)
69 else 70 else
70 nil 71 nil
@@ -90,6 +91,12 @@ class ProjectWiki @@ -90,6 +91,12 @@ class ProjectWiki
90 wiki.delete_page(page, commit_details(:deleted, message, page.title)) 91 wiki.delete_page(page, commit_details(:deleted, message, page.title))
91 end 92 end
92 93
  94 + def page_title_and_dir(title)
  95 + title_array = title.split("/")
  96 + title = title_array.pop
  97 + [title.gsub(/\.[^.]*$/, ""), title_array.join("/")]
  98 + end
  99 +
93 private 100 private
94 101
95 def create_repo! 102 def create_repo!
app/models/wiki_page.rb
@@ -175,14 +175,24 @@ class WikiPage @@ -175,14 +175,24 @@ class WikiPage
175 end 175 end
176 176
177 def save(method, *args) 177 def save(method, *args)
178 - if valid? && wiki.send(method, *args)  
179 - @page = wiki.wiki.paged(title) 178 + project_wiki = wiki
  179 + if valid? && project_wiki.send(method, *args)
  180 +
  181 + page_details = if method == :update_page
  182 + @page.path
  183 + else
  184 + title
  185 + end
  186 +
  187 + page_title, page_dir = project_wiki.page_title_and_dir(page_details)
  188 + gollum_wiki = project_wiki.wiki
  189 + @page = gollum_wiki.paged(page_title, page_dir)
180 190
181 set_attributes 191 set_attributes
182 192
183 @persisted = true 193 @persisted = true
184 else 194 else
185 - errors.add(:base, wiki.error_message) if wiki.error_message 195 + errors.add(:base, project_wiki.error_message) if project_wiki.error_message
186 @persisted = false 196 @persisted = false
187 end 197 end
188 @persisted 198 @persisted
app/services/system_hooks_service.rb
@@ -31,7 +31,8 @@ class SystemHooksService @@ -31,7 +31,8 @@ class SystemHooksService
31 path_with_namespace: model.path_with_namespace, 31 path_with_namespace: model.path_with_namespace,
32 project_id: model.id, 32 project_id: model.id,
33 owner_name: owner.name, 33 owner_name: owner.name,
34 - owner_email: owner.respond_to?(:email) ? owner.email : nil 34 + owner_email: owner.respond_to?(:email) ? owner.email : nil,
  35 + project_visibility: Project.visibility_levels.key(model.visibility_level_field).downcase
35 }) 36 })
36 when User 37 when User
37 data.merge!({ 38 data.merge!({
@@ -46,7 +47,8 @@ class SystemHooksService @@ -46,7 +47,8 @@ class SystemHooksService
46 project_id: model.project_id, 47 project_id: model.project_id,
47 user_name: model.user.name, 48 user_name: model.user.name,
48 user_email: model.user.email, 49 user_email: model.user.email,
49 - project_access: model.human_access 50 + project_access: model.human_access,
  51 + project_visibility: Project.visibility_levels.key(model.project.visibility_level_field).downcase
50 }) 52 })
51 end 53 end
52 end 54 end
app/views/admin/users/_form.html.haml
@@ -2,9 +2,9 @@ @@ -2,9 +2,9 @@
2 = form_for [:admin, @user], html: { class: 'form-horizontal' } do |f| 2 = form_for [:admin, @user], html: { class: 'form-horizontal' } do |f|
3 -if @user.errors.any? 3 -if @user.errors.any?
4 #error_explanation 4 #error_explanation
5 - %ul.unstyled.alert.alert-danger 5 + .alert.alert-danger
6 - @user.errors.full_messages.each do |msg| 6 - @user.errors.full_messages.each do |msg|
7 - %li= msg 7 + %p= msg
8 8
9 %fieldset 9 %fieldset
10 %legend Account 10 %legend Account
app/views/events/event/_note.html.haml
@@ -14,8 +14,8 @@ @@ -14,8 +14,8 @@
14 - note = event.target 14 - note = event.target
15 - if note.attachment.url 15 - if note.attachment.url
16 - if note.attachment.image? 16 - if note.attachment.image?
17 - = link_to note.attachment.url, target: '_blank' do  
18 - = image_tag note.attachment.url, class: 'note-image-attach' 17 + = link_to note.attachment.secure_url, target: '_blank' do
  18 + = image_tag note.attachment.secure_url, class: 'note-image-attach'
19 - else 19 - else
20 = link_to note.attachment.secure_url, target: "_blank", class: 'note-file-attach' do 20 = link_to note.attachment.secure_url, target: "_blank", class: 'note-file-attach' do
21 %i.icon-paper-clip 21 %i.icon-paper-clip
app/views/groups/show.html.haml
1 .dashboard 1 .dashboard
2 - .activities.col-md-8.hidden-sm 2 + .activities.col-md-8.hidden-sm.hidden-xs
3 - if current_user 3 - if current_user
4 = render "events/event_last_push", event: @last_push 4 = render "events/event_last_push", event: @last_push
5 = link_to dashboard_path, class: 'btn btn-tiny' do 5 = link_to dashboard_path, class: 'btn btn-tiny' do
app/views/layouts/_head_panel.html.haml
@@ -43,6 +43,6 @@ @@ -43,6 +43,6 @@
43 %li 43 %li
44 = link_to destroy_user_session_path, class: "logout", method: :delete, title: "Logout", class: 'has_bottom_tooltip', 'data-original-title' => 'Logout' do 44 = link_to destroy_user_session_path, class: "logout", method: :delete, title: "Logout", class: 'has_bottom_tooltip', 'data-original-title' => 'Logout' do
45 %i.icon-signout 45 %i.icon-signout
46 - %li 46 + %li.hidden-xs
47 = link_to current_user, class: "profile-pic", id: 'profile-pic' do 47 = link_to current_user, class: "profile-pic", id: 'profile-pic' do
48 = image_tag avatar_icon(current_user.email, 26), alt: 'User activity' 48 = image_tag avatar_icon(current_user.email, 26), alt: 'User activity'
app/views/notify/closed_merge_request_email.html.haml
1 %p 1 %p
2 - = "Merge Request !#{@merge_request.iid} was closed by #{@updated_by.name}" 2 + = "Merge Request ##{@merge_request.iid} was closed by #{@updated_by.name}"
app/views/notify/closed_merge_request_email.text.haml
1 -= "Merge Request #{@merge_request.iid} was closed by #{@updated_by.name}" 1 += "Merge Request ##{@merge_request.iid} was closed by #{@updated_by.name}"
2 2
3 Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)} 3 Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
4 4
app/views/notify/merged_merge_request_email.html.haml
1 %p 1 %p
2 - = "Merge Request !#{@merge_request.iid} was merged" 2 + = "Merge Request ##{@merge_request.iid} was merged"
app/views/notify/merged_merge_request_email.text.haml
1 -= "Merge Request #{@merge_request.iid} was merged" 1 += "Merge Request ##{@merge_request.iid} was merged"
2 2
3 Merge Request Url: #{project_merge_request_url(@merge_request.target_project, @merge_request)} 3 Merge Request Url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
4 4
app/views/projects/blob/_blob.html.haml
@@ -15,18 +15,18 @@ @@ -15,18 +15,18 @@
15 - else 15 - else
16 = link_to title, '#' 16 = link_to title, '#'
17 17
18 -%ul.blob-commit-info.bs-callout.bs-callout-info 18 +%ul.blob-commit-info.bs-callout.bs-callout-info.hidden-xs
19 - blob_commit = @repository.last_commit_for_path(@commit.id, @blob.path) 19 - blob_commit = @repository.last_commit_for_path(@commit.id, @blob.path)
20 = render blob_commit, project: @project 20 = render blob_commit, project: @project
21 21
22 %div#tree-content-holder.tree-content-holder 22 %div#tree-content-holder.tree-content-holder
23 .file-holder 23 .file-holder
24 - .file-title 24 + .file-title.clearfix
25 %i.icon-file 25 %i.icon-file
26 %span.file_name 26 %span.file_name
27 = blob.name 27 = blob.name
28 %small= number_to_human_size blob.size 28 %small= number_to_human_size blob.size
29 - %span.options= render "actions" 29 + %span.options.hidden-xs= render "actions"
30 - if blob.text? 30 - if blob.text?
31 = render "text", blob: blob 31 = render "text", blob: blob
32 - elsif blob.image? 32 - elsif blob.image?
app/views/projects/blob/_remove.html.haml
@@ -14,7 +14,8 @@ @@ -14,7 +14,8 @@
14 = label_tag 'commit_message', class: "control-label" do 14 = label_tag 'commit_message', class: "control-label" do
15 Commit message 15 Commit message
16 .col-sm-10 16 .col-sm-10
17 - = text_area_tag 'commit_message', params[:commit_message], placeholder: "Removed this file because...", required: true, rows: 3, class: 'form-control' 17 + = render 'shared/commit_message_container', {textarea: text_area_tag('commit_message',
  18 + params[:commit_message], placeholder: "Removed this file because...", required: true, rows: 3, class: 'form-control')}
18 .form-group 19 .form-group
19 .col-sm-2 20 .col-sm-2
20 .col-sm-10 21 .col-sm-10
app/views/projects/commits/_parallel_view.html.haml
@@ -2,54 +2,37 @@ @@ -2,54 +2,37 @@
2 - old_lines, new_lines = parallel_diff_lines(project, @commit, diff, file) 2 - old_lines, new_lines = parallel_diff_lines(project, @commit, diff, file)
3 - num_lines = old_lines.length 3 - num_lines = old_lines.length
4 4
5 -%div.text-file-parallel  
6 - %div.diff-side.diff-side-left  
7 - %table  
8 - - old_lines.each do |line| 5 +%div.text-file
  6 + %table
  7 + - num_lines.times do |index|
  8 + - new_line = new_lines[index]
  9 + - old_line = old_lines[index]
  10 + %tr.line_holder.parallel
  11 + -# For old line
  12 + - if old_line.type == :file_created
  13 + %td.old_line= old_line.num
  14 + %td.line_content.parallel= "File was created"
  15 + - elsif old_line.type == :deleted
  16 + %td.old_line.old= old_line.num
  17 + %td.line_content{class: "parallel noteable_line old #{old_line.code}", "line_code" => old_line.code}= old_line.content
  18 + - else old_line.type == :no_change
  19 + %td.old_line= old_line.num
  20 + %td.line_content.parallel= old_line.content
  21 +
  22 + -# For new line
  23 + - if new_line.type == :file_deleted
  24 + %td.new_line= new_line.num
  25 + %td.line_content.parallel= "File was deleted"
  26 + - elsif new_line.type == :added
  27 + %td.new_line.new= new_line.num
  28 + %td.line_content{class: "parallel noteable_line new #{new_line.code}", "line_code" => new_line.code}= new_line.content
  29 + - else new_line.type == :no_change
  30 + %td.new_line= new_line.num
  31 + %td.line_content.parallel= new_line.content
  32 +
  33 + - if @reply_allowed
  34 + - comments1 = @line_notes.select { |n| n.line_code == old_line.code }.sort_by(&:created_at)
  35 + - comments2 = @line_notes.select { |n| n.line_code == new_line.code }.sort_by(&:created_at)
  36 + - unless comments1.empty? and comments2.empty?
  37 + = render "projects/notes/diff_notes_with_reply_parallel", notes1: comments1, notes2: comments2
9 38
10 - %tr.line_holder.parallel  
11 - - if line.type == :file_created  
12 - %td.line_content.parallel= "File was created"  
13 - - elsif line.type == :deleted  
14 - %td.line_content{class: "parallel noteable_line old #{line.code}", "line_code" => line.code }= line.content  
15 - - else line.type == :no_change  
16 - %td.line_content.parallel= line.content  
17 -  
18 - %div.diff-middle  
19 - %table  
20 - - num_lines.times do |index|  
21 - %tr  
22 - - if old_lines[index].type == :deleted  
23 - %td.old_line.old= old_lines[index].num  
24 - - else  
25 - %td.old_line= old_lines[index].num  
26 -  
27 - %td.diff_line=""  
28 -  
29 - - if new_lines[index].type == :added  
30 - %td.new_line.new= new_lines[index].num  
31 - - else  
32 - %td.new_line= new_lines[index].num  
33 -  
34 - %div.diff-side.diff-side-right  
35 - %table  
36 - - new_lines.each do |line|  
37 -  
38 - %tr.line_holder.parallel  
39 - - if line.type == :file_deleted  
40 - %td.line_content.parallel= "File was deleted"  
41 - - elsif line.type == :added  
42 - %td.line_content{class: "parallel noteable_line new #{line.code}", "line_code" => line.code }= line.content  
43 - - else line.type == :no_change  
44 - %td.line_content.parallel= line.content  
45 -  
46 -:javascript  
47 - $('.diff-side-right').on('scroll', function(){  
48 - $('.diff-side-left, .diff-middle').scrollTop($(this).scrollTop());  
49 - $('.diff-side-left').scrollLeft($(this).scrollLeft());  
50 - });  
51 -  
52 - $('.diff-side-left').on('scroll', function(){  
53 - $('.diff-side-right, .diff-middle').scrollTop($(this).scrollTop()); // might never be relevant  
54 - $('.diff-side-right').scrollLeft($(this).scrollLeft());  
55 - });  
app/views/projects/edit_tree/show.html.haml
@@ -23,7 +23,8 @@ @@ -23,7 +23,8 @@
23 = label_tag 'commit_message', class: "control-label" do 23 = label_tag 'commit_message', class: "control-label" do
24 Commit message 24 Commit message
25 .col-sm-10 25 .col-sm-10
26 - = text_area_tag 'commit_message', '', placeholder: "Update #{@blob.name}", required: true, rows: 3, class: 'form-control' 26 + = render 'shared/commit_message_container', {textarea: text_area_tag('commit_message', '',
  27 + placeholder: "Update #{@blob.name}", required: true, rows: 3, class: 'form-control')}
27 .form-actions 28 .form-actions
28 = hidden_field_tag 'last_commit', @last_commit 29 = hidden_field_tag 'last_commit', @last_commit
29 = hidden_field_tag 'content', '', id: "file-content" 30 = hidden_field_tag 'content', '', id: "file-content"
app/views/projects/issues/_issue_context.html.haml
1 = form_for [@project, @issue], remote: true, html: {class: 'edit-issue inline-update'} do |f| 1 = form_for [@project, @issue], remote: true, html: {class: 'edit-issue inline-update'} do |f|
2 - %strong.append-right-10  
3 - Assignee: 2 + .row
  3 + .col-md-6
  4 + %strong.append-right-10
  5 + Assignee:
4 6
5 - - if can?(current_user, :modify_issue, @issue)  
6 - = project_users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control', selected: @issue.assignee_id)  
7 - - elsif issue.assignee  
8 - = link_to_member(@project, @issue.assignee)  
9 - - else  
10 - None 7 + - if can?(current_user, :modify_issue, @issue)
  8 + = project_users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control', selected: @issue.assignee_id)
  9 + - elsif issue.assignee
  10 + = link_to_member(@project, @issue.assignee)
  11 + - else
  12 + None
11 13
12 - .pull-right  
13 - %strong.append-right-10  
14 - Milestone:  
15 - - if can?(current_user, :modify_issue, @issue)  
16 - = f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone (none):" }, {class: 'select2 select2-compact'})  
17 - = hidden_field_tag :issue_context  
18 - = f.submit class: 'btn'  
19 - - elsif issue.milestone  
20 - = link_to issue.milestone.title, project_milestone_path  
21 - - else  
22 - None 14 + .col-md-6.text-right
  15 + %strong.append-right-10
  16 + Milestone:
  17 + - if can?(current_user, :modify_issue, @issue)
  18 + = f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone" }, {class: 'select2 select2-compact'})
  19 + = hidden_field_tag :issue_context
  20 + = f.submit class: 'btn'
  21 + - elsif issue.milestone
  22 + = link_to issue.milestone.title, project_milestone_path
  23 + - else
  24 + None
app/views/projects/issues/index.html.haml
1 = render "head" 1 = render "head"
2 .row 2 .row
3 .col-md-3 3 .col-md-3
4 - = render 'shared/project_filter', project_entities_path: project_issues_path(@project), labels: true 4 + = render 'shared/project_filter', project_entities_path: project_issues_path(@project),
  5 + labels: true, redirect: 'issues'
5 .col-md-9.issues-holder 6 .col-md-9.issues-holder
6 = render "issues" 7 = render "issues"
app/views/projects/issues/show.html.haml
1 %h3.page-title 1 %h3.page-title
2 Issue ##{@issue.iid} 2 Issue ##{@issue.iid}
3 3
4 - %span.pull-right 4 + %span.pull-right.issue-btn-group
5 - if can?(current_user, :write_issue, @project) 5 - if can?(current_user, :write_issue, @project)
6 = link_to new_project_issue_path(@project), class: "btn btn-grouped", title: "New Issue", id: "new_issue_link" do 6 = link_to new_project_issue_path(@project), class: "btn btn-grouped", title: "New Issue", id: "new_issue_link" do
7 %i.icon-plus 7 %i.icon-plus
@@ -16,28 +16,29 @@ @@ -16,28 +16,29 @@
16 %i.icon-edit 16 %i.icon-edit
17 Edit 17 Edit
18 18
19 -.votes-holder  
20 - #votes= render 'votes/votes_block', votable: @issue 19 +.clearfix
  20 + .votes-holder
  21 + #votes= render 'votes/votes_block', votable: @issue
21 22
22 -.back-link  
23 - = link_to project_issues_path(@project) do  
24 - &larr; To issues list  
25 - %span.milestone-nav-link  
26 - - if @issue.milestone  
27 - |  
28 - %span.light Milestone  
29 - = link_to project_milestone_path(@project, @issue.milestone) do  
30 - = @issue.milestone.title 23 + .back-link
  24 + = link_to project_issues_path(@project) do
  25 + &larr; To issues list
  26 + %span.milestone-nav-link
  27 + - if @issue.milestone
  28 + |
  29 + %span.light Milestone
  30 + = link_to project_milestone_path(@project, @issue.milestone) do
  31 + = @issue.milestone.title
31 32
32 .issue-box{ class: issue_box_class(@issue) } 33 .issue-box{ class: issue_box_class(@issue) }
33 - .state  
34 - %span.state-label 34 + .state.clearfix
  35 + .state-label.col-sm-2.col-xs-12
35 - if @issue.closed? 36 - if @issue.closed?
36 Closed 37 Closed
37 - else 38 - else
38 Open 39 Open
39 40
40 - %span.creator 41 + %span.creator.col-sm-9.col-xs-12
41 Created by #{link_to_member(@project, @issue.author)} #{time_ago_with_tooltip(@issue.created_at)} 42 Created by #{link_to_member(@project, @issue.author)} #{time_ago_with_tooltip(@issue.created_at)}
42 43
43 %h4.title 44 %h4.title
app/views/projects/merge_requests/_form.html.haml
@@ -14,33 +14,6 @@ @@ -14,33 +14,6 @@
14 - @merge_request.errors.full_messages.each do |msg| 14 - @merge_request.errors.full_messages.each do |msg|
15 %div= msg 15 %div= msg
16 16
17 - .merge-request-branches  
18 - .form-group  
19 - = label_tag nil, class: 'control-label' do  
20 - From  
21 - .col-sm-10  
22 - .clearfix  
23 - .pull-left  
24 - = f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted? })  
25 - .pull-left  
26 - &nbsp;  
27 - = f.select(:source_branch, @merge_request.source_branches, { include_blank: "Select branch" }, {class: 'source_branch select2 span2'})  
28 - .mr_source_commit  
29 - %br  
30 - .form-group  
31 - = label_tag nil, class: 'control-label' do  
32 - To  
33 - .col-sm-10  
34 - .clearfix  
35 - .pull-left  
36 - - projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project]  
37 - = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted? })  
38 - .pull-left  
39 - &nbsp;  
40 - = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, {class: 'target_branch select2 span2'})  
41 - .mr_target_commit  
42 -  
43 - %hr  
44 .merge-request-form-info 17 .merge-request-form-info
45 .form-group 18 .form-group
46 = f.label :title, class: 'control-label' do 19 = f.label :title, class: 'control-label' do
@@ -51,6 +24,32 @@ @@ -51,6 +24,32 @@
51 .col-sm-10 24 .col-sm-10
52 = f.text_area :description, class: "form-control js-gfm-input", rows: 14 25 = f.text_area :description, class: "form-control js-gfm-input", rows: 14
53 %p.hint Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. 26 %p.hint Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
  27 + %hr
  28 + .form-group
  29 + .issue-assignee
  30 + = f.label :assignee_id, class: 'control-label' do
  31 + %i.icon-user
  32 + Assign to
  33 + .col-sm-10
  34 + = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id)
  35 + &nbsp;
  36 + = link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link'
  37 + .form-group
  38 + .issue-milestone
  39 + = f.label :milestone_id, class: 'control-label' do
  40 + %i.icon-time
  41 + Milestone
  42 + .col-sm-10= f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2'})
  43 +
  44 +
  45 + - if @merge_request.persisted? # Only allow labels on edit to avoid fork vs upstream repo labels issue
  46 + .form-group
  47 + = f.label :label_list, class: 'control-label' do
  48 + %i.icon-tag
  49 + Labels
  50 + .col-sm-10
  51 + = f.text_field :label_list, maxlength: 2000, class: "form-control"
  52 + %p.hint Separate labels with commas.
54 53
55 .form-actions 54 .form-actions
56 - if @merge_request.new_record? 55 - if @merge_request.new_record?
@@ -66,20 +65,36 @@ @@ -66,20 +65,36 @@
66 65
67 :javascript 66 :javascript
68 disableButtonIfEmptyField("#merge_request_title", ".btn-save"); 67 disableButtonIfEmptyField("#merge_request_title", ".btn-save");
69 -  
70 - var source_branch = $("#merge_request_source_branch")  
71 - , target_branch = $("#merge_request_target_branch")  
72 - , target_project = $("#merge_request_target_project_id");  
73 -  
74 - $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: source_branch.val() });  
75 - $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: target_branch.val() });  
76 -  
77 - target_project.on("change", function() {  
78 - $.get("#{update_branches_project_merge_requests_path(@source_project)}", {target_project_id: $(this).val() });  
79 - });  
80 - source_branch.on("change", function() {  
81 - $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: $(this).val() });  
82 - });  
83 - target_branch.on("change", function() {  
84 - $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: $(this).val() }); 68 + $('.assign-to-me-link').on('click', function(e){
  69 + $('#merge_request_assignee_id').val("#{current_user.id}").trigger("change");
  70 + e.preventDefault();
85 }); 71 });
  72 +
  73 + $("#merge_request_label_list")
  74 + .bind( "keydown", function( event ) {
  75 + if ( event.keyCode === $.ui.keyCode.TAB &&
  76 + $( this ).data( "autocomplete" ).menu.active ) {
  77 + event.preventDefault();
  78 + }
  79 + })
  80 + .bind("click", function(event) {
  81 + $(this).autocomplete("search", "");
  82 + })
  83 + .autocomplete({
  84 + minLength: 0,
  85 + source: function( request, response ) {
  86 + response( $.ui.autocomplete.filter(
  87 + #{raw labels_autocomplete_source}, extractLast( request.term ) ) );
  88 + },
  89 + focus: function() {
  90 + return false;
  91 + },
  92 + select: function(event, ui) {
  93 + var terms = split( this.value );
  94 + terms.pop();
  95 + terms.push( ui.item.value );
  96 + terms.push( "" );
  97 + this.value = terms.join( ", " );
  98 + return false;
  99 + }
  100 + });
app/views/projects/merge_requests/_merge_request.html.haml
@@ -11,13 +11,9 @@ @@ -11,13 +11,9 @@
11 - if merge_request.for_fork? 11 - if merge_request.for_fork?
12 %span.light 12 %span.light
13 #{merge_request.source_project_namespace}: 13 #{merge_request.source_project_namespace}:
14 - = merge_request.source_branch  
15 - %i.icon-angle-right.light  
16 - = merge_request.target_branch  
17 - - else  
18 - = merge_request.source_branch  
19 - %i.icon-angle-right.light  
20 - = merge_request.target_branch 14 + = truncate merge_request.source_branch, length: 25
  15 + %i.icon-angle-right.light
  16 + = merge_request.target_branch
21 .merge-request-info 17 .merge-request-info
22 - if merge_request.author 18 - if merge_request.author
23 authored by #{link_to_member(merge_request.source_project, merge_request.author)} 19 authored by #{link_to_member(merge_request.source_project, merge_request.author)}
@@ -35,3 +31,9 @@ @@ -35,3 +31,9 @@
35 31
36 .pull-right 32 .pull-right
37 %small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')} 33 %small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')}
  34 +
  35 + .merge-request-labels
  36 + - merge_request.labels.each do |label|
  37 + %span{class: "label #{label_css_class(label.name)}"}
  38 + %i.icon-tag
  39 + = label.name
app/views/projects/merge_requests/_new_compare.html.haml 0 → 100644
@@ -0,0 +1,84 @@ @@ -0,0 +1,84 @@
  1 +%h3.page-title Compare branches for new Merge Request
  2 +%hr
  3 +
  4 += form_for [@project, @merge_request], url: new_project_merge_request_path(@project), method: :get, html: { class: "merge-request-form form-inline" } do |f|
  5 + .hide.alert.alert-danger.mr-compare-errors
  6 + .merge-request-branches.row
  7 + .col-md-6
  8 + .panel.panel-default
  9 + .panel-heading
  10 + %strong Source branch
  11 + .panel-body
  12 + = f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted? })
  13 + &nbsp;
  14 + = f.select(:source_branch, @merge_request.source_branches, { include_blank: "Select branch" }, {class: 'source_branch select2 span2'})
  15 + .panel-footer
  16 + .mr_source_commit
  17 +
  18 + .col-md-6
  19 + .panel.panel-default
  20 + .panel-heading
  21 + %strong Target branch
  22 + .panel-body
  23 + - projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project]
  24 + = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted? })
  25 + &nbsp;
  26 + = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, {class: 'target_branch select2 span2'})
  27 + .panel-footer
  28 + .mr_target_commit
  29 +
  30 + -if @merge_request.errors.any?
  31 + .alert.alert-danger
  32 + - @merge_request.errors.full_messages.each do |msg|
  33 + %div= msg
  34 +
  35 + - if @merge_request.source_branch.present? && @merge_request.target_branch.present?
  36 + .light-well
  37 + %center
  38 + %h4
  39 + There isn't anything to merge.
  40 + %p.slead
  41 + - if @merge_request.source_branch == @merge_request.target_branch
  42 + You'll need to use different branch names to get a valid comparison.
  43 + - else
  44 + %span.label-branch #{@merge_request.source_branch}
  45 + and
  46 + %span.label-branch #{@merge_request.target_branch}
  47 + are the same.
  48 +
  49 +
  50 + %hr
  51 + = f.submit 'Compare branches', class: "btn btn-primary mr-compare-btn"
  52 +
  53 +:javascript
  54 + var source_branch = $("#merge_request_source_branch")
  55 + , target_branch = $("#merge_request_target_branch")
  56 + , target_project = $("#merge_request_target_project_id");
  57 +
  58 + $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: source_branch.val() });
  59 + $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: target_branch.val() });
  60 +
  61 + target_project.on("change", function() {
  62 + $.get("#{update_branches_project_merge_requests_path(@source_project)}", {target_project_id: $(this).val() });
  63 + });
  64 + source_branch.on("change", function() {
  65 + $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: $(this).val() });
  66 + $(".mr-compare-errors").fadeOut();
  67 + $(".mr-compare-btn").enable();
  68 + });
  69 + target_branch.on("change", function() {
  70 + $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: $(this).val() });
  71 + $(".mr-compare-errors").fadeOut();
  72 + $(".mr-compare-btn").enable();
  73 + });
  74 +
  75 +
  76 +:coffeescript
  77 +
  78 + $(".merge-request-form").on 'submit', ->
  79 + if $("#merge_request_source_branch").val() is "" or $('#merge_request_target_branch').val() is ""
  80 + $(".mr-compare-errors").html("You must select source and target branch to proceed")
  81 + $(".mr-compare-errors").fadeIn()
  82 + event.preventDefault()
  83 + return
  84 +
app/views/projects/merge_requests/_new_submit.html.haml 0 → 100644
@@ -0,0 +1,82 @@ @@ -0,0 +1,82 @@
  1 +%h3.page-title
  2 + New merge request
  3 +%p.slead
  4 + From
  5 + %strong.monospace
  6 + #{@merge_request.source_project_namespace}:#{@merge_request.source_branch}
  7 + into
  8 + %strong.monospace
  9 + #{@merge_request.target_project_namespace}:#{@merge_request.target_branch}
  10 +
  11 + %span.pull-right
  12 + = link_to 'Change branches', new_project_merge_request_path(@project)
  13 +
  14 += form_for [@project, @merge_request], html: { class: "merge-request-form" } do |f|
  15 + .panel.panel-default
  16 +
  17 + .panel-body
  18 + .form-group
  19 + .light
  20 + = f.label :title do
  21 + = "Title *"
  22 + = f.text_field :title, class: "form-control input-lg js-gfm-input", maxlength: 255, rows: 5, required: true
  23 + .form-group
  24 + .light
  25 + = f.label :description, "Description"
  26 + = f.text_area :description, class: "form-control js-gfm-input", rows: 10
  27 + %p.hint Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
  28 + .form-group
  29 + .issue-assignee
  30 + = f.label :assignee_id do
  31 + %i.icon-user
  32 + Assign to
  33 + %div
  34 + = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id, project_id: @merge_request.target_project_id)
  35 + &nbsp;
  36 + = link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link'
  37 + .form-group
  38 + .issue-milestone
  39 + = f.label :milestone_id do
  40 + %i.icon-time
  41 + Milestone
  42 + %div= f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2'})
  43 + .panel-footer
  44 + - if @target_repo.contribution_guide
  45 + - contribution_guide_url = project_blob_path(@target_project, tree_join(@target_repo.root_ref, @target_repo.contribution_guide.name))
  46 + %p
  47 + Please review the
  48 + %strong #{link_to "guidelines for contribution", contribution_guide_url}
  49 + to this repository.
  50 + = f.hidden_field :source_project_id
  51 + = f.hidden_field :target_project_id
  52 + = f.hidden_field :target_branch
  53 + = f.hidden_field :source_branch
  54 + = f.submit 'Submit merge request', class: "btn btn-create"
  55 +
  56 +.mr-compare
  57 + %div.ui-box
  58 + .title
  59 + Commits (#{@commits.count})
  60 + - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
  61 + %ul.well-list
  62 + - Commit.decorate(@commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE)).each do |commit|
  63 + = render "projects/commits/inline_commit", commit: commit, project: @project
  64 + %li.warning-row.unstyled
  65 + other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues.
  66 + - else
  67 + %ul.well-list= render Commit.decorate(@commits), project: @project
  68 +
  69 + %h4 Changes
  70 + - if @diffs.present?
  71 + = render "projects/commits/diffs", diffs: @diffs, project: @project
  72 + - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
  73 + .bs-callout.bs-callout-danger
  74 + %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
  75 + %p To preserve performance the line changes are not shown.
  76 +
  77 +
  78 +:javascript
  79 + $('.assign-to-me-link').on('click', function(e){
  80 + $('#merge_request_assignee_id').val("#{current_user.id}").trigger("change");
  81 + e.preventDefault();
  82 + });
app/views/projects/merge_requests/_show.html.haml
@@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
4 = render "projects/merge_requests/show/mr_box" 4 = render "projects/merge_requests/show/mr_box"
5 = render "projects/merge_requests/show/state_widget" 5 = render "projects/merge_requests/show/state_widget"
6 = render "projects/merge_requests/show/commits" 6 = render "projects/merge_requests/show/commits"
  7 + = render "projects/merge_requests/show/participants"
7 8
8 - if @commits.present? 9 - if @commits.present?
9 %ul.nav.nav-tabs 10 %ul.nav.nav-tabs
app/views/projects/merge_requests/branch_from.js.haml
1 :plain 1 :plain
2 $(".mr_source_commit").html("#{commit_to_html(@commit, @source_project, false)}"); 2 $(".mr_source_commit").html("#{commit_to_html(@commit, @source_project, false)}");
3 - var mrTitle = $('#merge_request_title');  
4 -  
5 - if(mrTitle.val().length == 0) {  
6 - mrTitle.val("#{params[:ref].titleize.humanize}");  
7 - }  
app/views/projects/merge_requests/index.html.haml
@@ -8,7 +8,8 @@ @@ -8,7 +8,8 @@
8 %hr 8 %hr
9 .row 9 .row
10 .col-md-3 10 .col-md-3
11 - = render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project) 11 + = render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project),
  12 + labels: true, redirect: 'merge_requests'
12 .col-md-9 13 .col-md-9
13 .mr-filters.append-bottom-10 14 .mr-filters.append-bottom-10
14 .dropdown.inline 15 .dropdown.inline
app/views/projects/merge_requests/new.html.haml
1 -%h3.page-title New Merge Request  
2 -%hr  
3 -= render 'form' 1 +- if @commits.present?
  2 + = render 'new_submit'
  3 +- else
  4 + = render 'new_compare'
app/views/projects/merge_requests/show/_context.html.haml
1 = form_for [@project, @merge_request], remote: true, html: {class: 'edit-merge_request inline-update'} do |f| 1 = form_for [@project, @merge_request], remote: true, html: {class: 'edit-merge_request inline-update'} do |f|
2 - %strong.append-right-10  
3 - Assignee: 2 + .row
  3 + .col-md-6
  4 + %strong.append-right-10
  5 + Assignee:
4 6
5 - - if can?(current_user, :modify_merge_request, @merge_request)  
6 - = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control', selected: @merge_request.assignee_id)  
7 - - elsif merge_request.assignee  
8 - = link_to_member(@project, @merge_request.assignee)  
9 - - else  
10 - None 7 + - if can?(current_user, :modify_merge_request, @merge_request)
  8 + = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control', selected: @merge_request.assignee_id)
  9 + - elsif merge_request.assignee
  10 + = link_to_member(@project, @merge_request.assignee)
  11 + - else
  12 + None
11 13
12 - .pull-right  
13 - %strong.append-right-10  
14 - Milestone:  
15 - - if can?(current_user, :modify_merge_request, @merge_request)  
16 - = f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone (none):" }, {class: 'select2 select2-compact'})  
17 - = hidden_field_tag :merge_request_context  
18 - = f.submit class: 'btn'  
19 - - elsif merge_request.milestone  
20 - = link_to merge_request.milestone.title, project_milestone_path  
21 - - else  
22 - None 14 + .col-md-6.text-right
  15 + %strong.append-right-10
  16 + Milestone:
  17 + - if can?(current_user, :modify_merge_request, @merge_request)
  18 + = f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2 select2-compact'})
  19 + = hidden_field_tag :merge_request_context
  20 + = f.submit class: 'btn'
  21 + - elsif merge_request.milestone
  22 + = link_to merge_request.milestone.title, project_milestone_path
  23 + - else
  24 + None
app/views/projects/merge_requests/show/_mr_accept.html.haml
@@ -12,7 +12,7 @@ @@ -12,7 +12,7 @@
12 - if @show_merge_controls 12 - if @show_merge_controls
13 .automerge_widget.can_be_merged.hide 13 .automerge_widget.can_be_merged.hide
14 .clearfix 14 .clearfix
15 - = form_for [:automerge, @project, @merge_request], remote: true, method: :get do |f| 15 + = form_for [:automerge, @project, @merge_request], remote: true, method: :post do |f|
16 %h4 16 %h4
17 You can accept this request automatically. 17 You can accept this request automatically.
18 %div 18 %div
@@ -21,7 +21,6 @@ @@ -21,7 +21,6 @@
21 = link_to "click here", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" 21 = link_to "click here", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
22 for instructions. 22 for instructions.
23 23
24 -  
25 .js-toggle-container 24 .js-toggle-container
26 %p 25 %p
27 If you want to modify merge commit message - 26 If you want to modify merge commit message -
@@ -31,7 +30,8 @@ @@ -31,7 +30,8 @@
31 .form-group 30 .form-group
32 = label_tag :merge_commit_message, "Commit message", class: 'control-label' 31 = label_tag :merge_commit_message, "Commit message", class: 'control-label'
33 .col-sm-10 32 .col-sm-10
34 - = text_area_tag :merge_commit_message, @merge_request.merge_commit_message, class: "form-control js-gfm-input", rows: 14, required: true 33 + = render 'shared/commit_message_container', {textarea: text_area_tag(:merge_commit_message,
  34 + @merge_request.merge_commit_message, class: "form-control js-gfm-input", rows: 14, required: true)}
35 %p.hint 35 %p.hint
36 The recommended maximum line length is 52 characters for the first line and 72 characters for all following lines. 36 The recommended maximum line length is 52 characters for the first line and 72 characters for all following lines.
37 37
app/views/projects/merge_requests/show/_mr_box.html.haml
1 .issue-box{ class: issue_box_class(@merge_request) } 1 .issue-box{ class: issue_box_class(@merge_request) }
2 - .state  
3 - %span.state-label 2 + .state.clearfix
  3 + %span.state-label.col-sm-2.col-xs-12
4 - if @merge_request.merged? 4 - if @merge_request.merged?
5 Merged 5 Merged
6 - elsif @merge_request.closed? 6 - elsif @merge_request.closed?
@@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
8 - else 8 - else
9 Open 9 Open
10 10
11 - %span.creator 11 + %span.creator.col-sm-9.col-xs-12
12 Created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)} 12 Created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)}
13 13
14 %h4.title 14 %h4.title
app/views/projects/merge_requests/show/_mr_title.html.haml
1 %h3.page-title 1 %h3.page-title
2 = "Merge Request ##{@merge_request.iid}" 2 = "Merge Request ##{@merge_request.iid}"
3 3
4 - %span.pull-right 4 + %span.pull-right.issue-btn-group
5 - if can?(current_user, :modify_merge_request, @merge_request) 5 - if can?(current_user, :modify_merge_request, @merge_request)
6 - if @merge_request.open? 6 - if @merge_request.open?
7 .btn-group.pull-left 7 .btn-group.pull-left
@@ -39,4 +39,4 @@ @@ -39,4 +39,4 @@
39 - else 39 - else
40 %span= @merge_request.source_branch 40 %span= @merge_request.source_branch
41 &rarr; 41 &rarr;
42 - %spanh= @merge_request.target_branch 42 + %span= @merge_request.target_branch
app/views/projects/merge_requests/show/_participants.html.haml 0 → 100644
@@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
  1 +.participants
  2 + %cite.cgray #{@merge_request.participants.count} participants
  3 + - @merge_request.participants.each do |participant|
  4 + = link_to_member(@project, participant, name: false, size: 24)
  5 +
  6 + .merge-request-show-labels.pull-right
  7 + - @merge_request.labels.each do |label|
  8 + %span{class: "label #{label_css_class(label.name)}"}
  9 + %i.icon-tag
  10 + = label.name
  11 + &nbsp;
app/views/projects/merge_requests/show/_state_widget.html.haml
@@ -21,14 +21,6 @@ @@ -21,14 +21,6 @@
21 #{time_ago_with_tooltip(@merge_request.merge_event.created_at)} 21 #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
22 = render "projects/merge_requests/show/remove_source_branch" 22 = render "projects/merge_requests/show/remove_source_branch"
23 23
24 - - if !@closes_issues.empty? && @merge_request.open?  
25 - .alert.alert-info.alert-info  
26 - %span  
27 - %i.icon-ok  
28 - Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'}  
29 - = succeed '.' do  
30 - != gfm(@closes_issues.map { |i| "##{i.iid}" }.to_sentence)  
31 -  
32 - unless @commits.any? 24 - unless @commits.any?
33 %h4 Nothing to merge 25 %h4 Nothing to merge
34 %p 26 %p
@@ -38,3 +30,12 @@ @@ -38,3 +30,12 @@
38 %span.label-branch #{@merge_request.target_branch} 30 %span.label-branch #{@merge_request.target_branch}
39 %br 31 %br
40 Try to use different branches or push new code. 32 Try to use different branches or push new code.
  33 +
  34 + - if !@closes_issues.empty? && @merge_request.open?
  35 + .panel-footer
  36 + %span
  37 + %i.icon-ok
  38 + Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'}
  39 + = succeed '.' do
  40 + != gfm(@closes_issues.map { |i| "##{i.iid}" }.to_sentence)
  41 +
app/views/projects/milestones/show.html.haml
1 = render "projects/issues/head" 1 = render "projects/issues/head"
2 %h3.page-title 2 %h3.page-title
3 Milestone ##{@milestone.iid} 3 Milestone ##{@milestone.iid}
4 - %small  
5 - = @milestone.expires_at  
6 .pull-right 4 .pull-right
7 - if can?(current_user, :admin_milestone, @project) 5 - if can?(current_user, :admin_milestone, @project)
8 = link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-grouped" do 6 = link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-grouped" do
@@ -23,14 +21,16 @@ @@ -23,14 +21,16 @@
23 21
24 22
25 .issue-box{ class: issue_box_class(@milestone) } 23 .issue-box{ class: issue_box_class(@milestone) }
26 - .state  
27 - %span.state-label 24 + .state.clearfix
  25 + .state-label.col-sm-2.col-xs-12
28 - if @milestone.closed? 26 - if @milestone.closed?
29 Closed 27 Closed
30 - elsif @milestone.expired? 28 - elsif @milestone.expired?
31 Expired 29 Expired
32 - else 30 - else
33 Open 31 Open
  32 + %span.creator.col-sm-9.col-xs-12
  33 + = @milestone.expires_at
34 34
35 %h4.title 35 %h4.title
36 = gfm escape_once(@milestone.title) 36 = gfm escape_once(@milestone.title)
@@ -100,7 +100,7 @@ @@ -100,7 +100,7 @@
100 %ul.bordered-list 100 %ul.bordered-list
101 - @users.each do |user| 101 - @users.each do |user|
102 %li 102 %li
103 - = link_to user, title: user.name, class: "dark" do 103 + = link_to user, title: user.name, class: "darken" do
104 = image_tag avatar_icon(user.email, 32), class: "avatar s32" 104 = image_tag avatar_icon(user.email, 32), class: "avatar s32"
105 %strong= truncate(user.name, lenght: 40) 105 %strong= truncate(user.name, lenght: 40)
106 %br 106 %br
app/views/projects/new_tree/show.html.haml
@@ -24,7 +24,8 @@ @@ -24,7 +24,8 @@
24 = label_tag 'commit_message', class: "control-label" do 24 = label_tag 'commit_message', class: "control-label" do
25 Commit message 25 Commit message
26 .col-sm-10 26 .col-sm-10
27 - = text_area_tag 'commit_message', params[:commit_message], placeholder: "Added new file", required: true, rows: 3, class: 'form-control' 27 + = render 'shared/commit_message_container', {textarea: text_area_tag('commit_message',
  28 + params[:commit_message], placeholder: "Added new file", required: true, rows: 3, class: 'form-control')}
28 29
29 .file-holder 30 .file-holder
30 .file-title 31 .file-title
app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
1 - note1 = notes1.first # example note 1 - note1 = notes1.first # example note
2 - note2 = notes2.first # example note 2 - note2 = notes2.first # example note
  3 +-# Check if line want not changed since comment was left
  4 +/- if !defined?(line) || line == note.diff_line
3 %tr.notes_holder.js-toggle-content 5 %tr.notes_holder.js-toggle-content
4 - -# Check if line want not changed since comment was left  
5 - /- if !defined?(line1) || line1 == note1.diff_line  
6 - if note1 6 - if note1
  7 + %td.notes_line
  8 + %span.btn.disabled
  9 + %i.icon-comment
  10 + = notes1.count
7 %td.notes_content 11 %td.notes_content
8 %ul.notes{ rel: note1.discussion_id } 12 %ul.notes{ rel: note1.discussion_id }
9 = render notes1 13 = render notes1
  14 +
10 = render "projects/notes/discussion_reply_button", note: note1 15 = render "projects/notes/discussion_reply_button", note: note1
11 - %td.notes_line2  
12 - %span.btn.disabled.parallel-comment  
13 - %i.icon-comment  
14 - = notes1.count  
15 - else 16 - else
16 %td= "" 17 %td= ""
17 %td= "" 18 %td= ""
18 19
19 - %td= ""  
20 -  
21 - -# Check if line want not changed since comment was left  
22 - /- if !defined?(line2) || line2 == note2.diff_line  
23 - if note2 20 - if note2
24 %td.notes_line 21 %td.notes_line
25 - %span.btn.disabled.parallel-comment 22 + %span.btn.disabled
26 %i.icon-comment 23 %i.icon-comment
27 = notes2.count 24 = notes2.count
28 %td.notes_content 25 %td.notes_content
29 %ul.notes{ rel: note2.discussion_id } 26 %ul.notes{ rel: note2.discussion_id }
30 = render notes2 27 = render notes2
  28 +
31 = render "projects/notes/discussion_reply_button", note: note2 29 = render "projects/notes/discussion_reply_button", note: note2
32 - else 30 - else
33 %td= "" 31 %td= ""
34 %td= "" 32 %td= ""
  33 +
app/views/projects/notes/_note.html.haml
@@ -54,8 +54,8 @@ @@ -54,8 +54,8 @@
54 - if note.attachment.url 54 - if note.attachment.url
55 .note-attachment 55 .note-attachment
56 - if note.attachment.image? 56 - if note.attachment.image?
57 - = link_to note.attachment.url, target: '_blank' do  
58 - = image_tag note.attachment.url, class: 'note-image-attach' 57 + = link_to note.attachment.secure_url, target: '_blank' do
  58 + = image_tag note.attachment.secure_url, class: 'note-image-attach'
59 .attachment.pull-right 59 .attachment.pull-right
60 = link_to note.attachment.secure_url, target: "_blank" do 60 = link_to note.attachment.secure_url, target: "_blank" do
61 %i.icon-paper-clip 61 %i.icon-paper-clip
app/views/projects/show.html.haml
@@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
6 = render 'shared/event_filter' 6 = render 'shared/event_filter'
7 .content_list 7 .content_list
8 = spinner 8 = spinner
9 - .col-md-3.project-side.hidden-sm 9 + .col-md-3.project-side.hidden-sm.hidden-xs
10 .clearfix 10 .clearfix
11 - if @project.archived? 11 - if @project.archived?
12 .alert.alert-warning 12 .alert.alert-warning
app/views/projects/wikis/_form.html.haml
1 = form_for [@project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal' } do |f| 1 = form_for [@project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal' } do |f|
2 -if @page.errors.any? 2 -if @page.errors.any?
3 #error_explanation 3 #error_explanation
4 - %h2= "#{pluralize(@page.errors.count, "error")} prohibited this wiki from being saved:"  
5 - %ul 4 + .alert.alert-danger
6 - @page.errors.full_messages.each do |msg| 5 - @page.errors.full_messages.each do |msg|
7 - %li= msg 6 + %p= msg
8 7
9 = f.hidden_field :title, value: @page.title 8 = f.hidden_field :title, value: @page.title
10 .form-group 9 .form-group
app/views/projects/wikis/_new.html.haml
@@ -9,6 +9,6 @@ @@ -9,6 +9,6 @@
9 %span Page slug 9 %span Page slug
10 = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => project_wikis_path(@project) 10 = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => project_wikis_path(@project)
11 %p.hint 11 %p.hint
12 - Please don't use spaces and slashes 12 + Please don't use spaces.
13 .modal-footer 13 .modal-footer
14 = link_to 'Build', '#', class: 'build-new-wiki btn btn-create' 14 = link_to 'Build', '#', class: 'build-new-wiki btn btn-create'
app/views/shared/_commit_message_container.html.haml 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +.commit-message-container
  2 + .max-width-marker
  3 + -# When the `ch` CSS length unit becomes widely supported `http://www.quirksmode.org/css/units-values` remove this workaround.
  4 + = 'a' * 72
  5 + = textarea
app/views/shared/_project_filter.html.haml
@@ -44,7 +44,7 @@ @@ -44,7 +44,7 @@
44 .light-well 44 .light-well
45 Add first label to your issues 45 Add first label to your issues
46 %br 46 %br
47 - or #{link_to 'generate', generate_project_labels_path(@project), method: :post} default set of labels 47 + or #{link_to 'generate', generate_project_labels_path(@project, redirect: redirect), method: :post} default set of labels
48 48
49 %fieldset 49 %fieldset
50 - if %w(state scope milestone_id assignee_id label_name).select { |k| params[k].present? }.any? 50 - if %w(state scope milestone_id assignee_id label_name).select { |k| params[k].present? }.any?
bin/pkgr_before_precompile.sh 0 → 100755
@@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
  1 +#!/bin/sh
  2 +
  3 +set -e
  4 +
  5 +for file in config/*.yml.example; do
  6 + cp ${file} config/$(basename ${file} .example)
  7 +done
  8 +
  9 +# Allow to override the Gitlab URL from an environment variable, as this will avoid having to change the configuration file for simple deployments.
  10 +config=$(echo '<% gitlab_url = URI(ENV["GITLAB_URL"] || "http://localhost:80") %>' | cat - config/gitlab.yml)
  11 +echo "$config" > config/gitlab.yml
  12 +sed -i "s/host: localhost/host: <%= gitlab_url.host %>/" config/gitlab.yml
  13 +sed -i "s/port: 80/port: <%= gitlab_url.port %>/" config/gitlab.yml
  14 +sed -i "s/https: false/https: <%= gitlab_url.scheme == 'https' %>/" config/gitlab.yml
  15 +
  16 +# No need for config file. Will be taken care of by REDIS_URL env variable
  17 +rm config/resque.yml
  18 +
  19 +# Set default unicorn.rb file
  20 +echo "" > config/unicorn.rb
  21 +
  22 +# Required for assets precompilation
  23 +sudo service postgresql start
config/application.rb
@@ -66,13 +66,16 @@ module Gitlab @@ -66,13 +66,16 @@ module Gitlab
66 # Version of your assets, change this if you want to expire all your assets 66 # Version of your assets, change this if you want to expire all your assets
67 config.assets.version = '1.0' 67 config.assets.version = '1.0'
68 68
  69 + # Relative url support
69 # Uncomment and customize the last line to run in a non-root path 70 # Uncomment and customize the last line to run in a non-root path
70 # WARNING: We recommend creating a FQDN to host GitLab in a root path instead of this. 71 # WARNING: We recommend creating a FQDN to host GitLab in a root path instead of this.
71 - # Note that four settings need to be changed for this to work. 72 + # Note that following settings need to be changed for this to work.
72 # 1) In your application.rb file: config.relative_url_root = "/gitlab" 73 # 1) In your application.rb file: config.relative_url_root = "/gitlab"
73 # 2) In your gitlab.yml file: relative_url_root: /gitlab 74 # 2) In your gitlab.yml file: relative_url_root: /gitlab
74 # 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab" 75 # 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
75 # 4) In ../gitlab-shell/config.yml: gitlab_url: "http://127.0.0.1/gitlab" 76 # 4) In ../gitlab-shell/config.yml: gitlab_url: "http://127.0.0.1/gitlab"
  77 + # 5) In lib/support/nginx/gitlab : do not use asset gzipping, remove block starting with "location ~ ^/(assets)/"
  78 + #
76 # To update the path, run: sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production 79 # To update the path, run: sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
77 # 80 #
78 # config.relative_url_root = "/gitlab" 81 # config.relative_url_root = "/gitlab"
config/gitlab.yml.example
@@ -24,15 +24,8 @@ production: &amp;base @@ -24,15 +24,8 @@ production: &amp;base
24 # Otherwise, ssh host will be set to the `host:` value above 24 # Otherwise, ssh host will be set to the `host:` value above
25 # ssh_host: ssh.host_example.com 25 # ssh_host: ssh.host_example.com
26 26
27 - # Uncomment and customize the last line to run in a non-root path  
28 - # WARNING: We recommend creating a FQDN to host GitLab in a root path instead of this.  
29 - # Note that four settings need to be changed for this to work.  
30 - # 1) In your application.rb file: config.relative_url_root = "/gitlab"  
31 - # 2) In your gitlab.yml file: relative_url_root: /gitlab  
32 - # 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"  
33 - # 4) In ../gitlab-shell/config.yml: gitlab_url: "http://127.0.0.1/gitlab"  
34 - # To update the path, run: sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production  
35 - # 27 + # WARNING: See config/application.rb under "Relative url support" for the list of
  28 + # other files that need to be changed for relative url support
36 # relative_url_root: /gitlab 29 # relative_url_root: /gitlab
37 30
38 # Uncomment and customize if you can't use the default user to run GitLab (default: 'git') 31 # Uncomment and customize if you can't use the default user to run GitLab (default: 'git')
@@ -40,10 +33,10 @@ production: &amp;base @@ -40,10 +33,10 @@ production: &amp;base
40 33
41 ## Email settings 34 ## Email settings
42 # Email address used in the "From" field in mails sent by GitLab 35 # Email address used in the "From" field in mails sent by GitLab
43 - email_from: gitlab@localhost 36 + email_from: example@example.com
44 37
45 # Email address of your support contact (default: same as email_from) 38 # Email address of your support contact (default: same as email_from)
46 - support_email: support@localhost 39 + support_email: support@example.com
47 40
48 ## User settings 41 ## User settings
49 default_projects_limit: 10 42 default_projects_limit: 10
config/routes.rb
@@ -206,7 +206,7 @@ Gitlab::Application.routes.draw do @@ -206,7 +206,7 @@ Gitlab::Application.routes.draw do
206 end 206 end
207 end 207 end
208 208
209 - resources :wikis, only: [:show, :edit, :destroy, :create], constraints: {id: /[a-zA-Z.0-9_\-]+/} do 209 + resources :wikis, only: [:show, :edit, :destroy, :create], constraints: {id: /[a-zA-Z.0-9_\-\/]+/} do
210 collection do 210 collection do
211 get :pages 211 get :pages
212 put ':id' => 'wikis#update' 212 put ':id' => 'wikis#update'
@@ -273,7 +273,7 @@ Gitlab::Application.routes.draw do @@ -273,7 +273,7 @@ Gitlab::Application.routes.draw do
273 resources :merge_requests, constraints: {id: /\d+/}, except: [:destroy] do 273 resources :merge_requests, constraints: {id: /\d+/}, except: [:destroy] do
274 member do 274 member do
275 get :diffs 275 get :diffs
276 - get :automerge 276 + post :automerge
277 get :automerge_check 277 get :automerge_check
278 get :ci_status 278 get :ci_status
279 end 279 end
config/unicorn.rb.example
@@ -8,14 +8,8 @@ @@ -8,14 +8,8 @@
8 # See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete 8 # See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
9 # documentation. 9 # documentation.
10 10
11 -# Uncomment and customize the last line to run in a non-root path  
12 -# WARNING: We recommend creating a FQDN to host GitLab in a root path instead of this.  
13 -# Note that four settings need to be changed for this to work.  
14 -# 1) In your application.rb file: config.relative_url_root = "/gitlab"  
15 -# 2) In your gitlab.yml file: relative_url_root: /gitlab  
16 -# 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"  
17 -# 4) In ../gitlab-shell/config.yml: gitlab_url: "http://127.0.0.1/gitlab"  
18 -# To update the path, run: sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production 11 +# WARNING: See config/application.rb under "Relative url support" for the list of
  12 +# other files that need to be changed for relative url support
19 # 13 #
20 # ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab" 14 # ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
21 15
db/fixtures/development/04_project.rb
@@ -40,7 +40,8 @@ Gitlab::Seeder.quiet do @@ -40,7 +40,8 @@ Gitlab::Seeder.quiet do
40 import_url: url, 40 import_url: url,
41 namespace_id: group.id, 41 namespace_id: group.id,
42 name: project_path.titleize, 42 name: project_path.titleize,
43 - description: Faker::Lorem.sentence 43 + description: Faker::Lorem.sentence,
  44 + visibility_level: Gitlab::VisibilityLevel.values.sample
44 } 45 }
45 46
46 project = Projects::CreateService.new(User.first, params).execute 47 project = Projects::CreateService.new(User.first, params).execute
db/fixtures/development/10_merge_requests.rb
1 Gitlab::Seeder.quiet do 1 Gitlab::Seeder.quiet do
2 - (1..100).each do |i|  
3 - # Random Project  
4 - project = Project.all.sample  
5 -  
6 - # Random user  
7 - user = project.team.users.sample  
8 -  
9 - next unless user  
10 -  
11 - next if project.empty_repo?  
12 -  
13 - branches = project.repository.branch_names.sample(2)  
14 -  
15 - next if branches.uniq.size < 2  
16 -  
17 - user_id = user.id  
18 -  
19 - Gitlab::Seeder.by_user(user) do  
20 - MergeRequest.seed(:id, [{  
21 - id: i,  
22 - source_branch: branches.first,  
23 - target_branch: branches.last,  
24 - source_project_id: project.id,  
25 - target_project_id: project.id,  
26 - author_id: user_id,  
27 - assignee_id: user_id,  
28 - milestone: project.milestones.sample,  
29 - title: Faker::Lorem.sentence(6)  
30 - }]) 2 + Project.all.reject(&:empty_repo?).each do |project|
  3 + branches = project.repository.branch_names
  4 +
  5 + branches.each do |branch_name|
  6 + break if branches.size < 2
  7 + source_branch = branches.pop
  8 + target_branch = branches.pop
  9 +
  10 + # Random user
  11 + user = project.team.users.sample
  12 + next unless user
  13 +
  14 + params = {
  15 + source_branch: source_branch,
  16 + target_branch: target_branch,
  17 + title: Faker::Lorem.sentence(6),
  18 + description: Faker::Lorem.sentences(3).join(" ")
  19 + }
  20 +
  21 + merge_request = MergeRequests::CreateService.new(project, user, params).execute
  22 +
  23 + if merge_request.valid?
  24 + merge_request.assignee = user
  25 + merge_request.milestone = project.milestones.sample
  26 + merge_request.save
  27 + print '.'
  28 + else
  29 + print 'F'
  30 + end
31 end 31 end
32 - print('.')  
33 end 32 end
34 end 33 end
35 -  
36 -MergeRequest.all.map do |mr|  
37 - mr.set_iid  
38 - mr.save  
39 -end  
40 -  
41 -puts 'Load diffs for Merge Requests (it will take some time)...'  
42 -MergeRequest.all.each do |mr|  
43 - mr.reload_code  
44 - print '.'  
45 -end  
doc/api/README.md
@@ -21,10 +21,12 @@ @@ -21,10 +21,12 @@
21 ## Clients 21 ## Clients
22 22
23 + [php-gitlab-api](https://github.com/m4tthumphrey/php-gitlab-api) - PHP 23 + [php-gitlab-api](https://github.com/m4tthumphrey/php-gitlab-api) - PHP
  24 ++ [Laravel API Wrapper for GitLab CE](https://github.com/adamgoose/gitlab) - PHP / [Laravel](http://laravel.com)
24 + [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby 25 + [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby
25 + [python-gitlab](https://github.com/Itxaka/python-gitlab) - Python 26 + [python-gitlab](https://github.com/Itxaka/python-gitlab) - Python
26 + [java-gitlab-api](https://github.com/timols/java-gitlab-api) - Java 27 + [java-gitlab-api](https://github.com/timols/java-gitlab-api) - Java
27 + [node-gitlab](https://github.com/moul/node-gitlab) - Node.js 28 + [node-gitlab](https://github.com/moul/node-gitlab) - Node.js
  29 ++ [NGitLab](https://github.com/Scooletz/NGitLab) - .NET
28 30
29 ## Introduction 31 ## Introduction
30 32
doc/api/deploy_key_multiple_projects.md 0 → 100644
@@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
  1 +# Adding deploy keys to multiple projects
  2 +
  3 +If you want to easily add the same deploy key to multiple projects in the same group, this can be achieved quite easily with the API.
  4 +
  5 +First, find the ID of the projects you're interested in, by either listing all projects:
  6 +
  7 +```
  8 +curl --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/projects
  9 +```
  10 +
  11 +Or finding the id of a group and then listing all projects in that group:
  12 +
  13 +```
  14 +curl --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/groups
  15 +
  16 +# For group 1234:
  17 +curl --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/groups/1234
  18 +```
  19 +
  20 +With those IDs, add the same deploy key to all:
  21 +```
  22 +for project_id in 321 456 987; do
  23 + curl -X POST --data '{"title": "my key", "key": "ssh-rsa AAAA..."}' --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/projects/${project_id}/keys
  24 +done
  25 +```
doc/api/merge_requests.md
@@ -189,6 +189,54 @@ Parameters: @@ -189,6 +189,54 @@ Parameters:
189 ``` 189 ```
190 190
191 191
  192 +## Accept MR
  193 +
  194 +Merge changes submitted with MR usign this API.
  195 +If merge success you get 200 OK.
  196 +If it has some conflicts and can not be merged - you get 405 and error message 'Branch cannot be merged'
  197 +If merge request is already merged or closed - you get 405 and error message 'Method Not Allowed'
  198 +If you dont have permissions to accept this merge request - you get 401
  199 +
  200 +```
  201 +PUT /projects/:id/merge_request/:merge_request_id/merge
  202 +```
  203 +
  204 +Parameters:
  205 +
  206 ++ `id` (required) - The ID of a project
  207 ++ `merge_request_id` (required) - ID of MR
  208 ++ `merge_commit_message` (optional) - Custom merge commit message
  209 +
  210 +```json
  211 +{
  212 + "id": 1,
  213 + "target_branch": "master",
  214 + "source_branch": "test1",
  215 + "project_id": 3,
  216 + "title": "test1",
  217 + "state": "merged",
  218 + "upvotes": 0,
  219 + "downvotes": 0,
  220 + "author": {
  221 + "id": 1,
  222 + "username": "admin",
  223 + "email": "admin@local.host",
  224 + "name": "Administrator",
  225 + "state": "active",
  226 + "created_at": "2012-04-29T08:46:00Z"
  227 + },
  228 + "assignee": {
  229 + "id": 1,
  230 + "username": "admin",
  231 + "email": "admin@local.host",
  232 + "name": "Administrator",
  233 + "state": "active",
  234 + "created_at": "2012-04-29T08:46:00Z"
  235 + }
  236 +}
  237 +```
  238 +
  239 +
192 ## Post comment to MR 240 ## Post comment to MR
193 241
194 Adds a comment to a merge request. 242 Adds a comment to a merge request.
doc/api/projects.md
@@ -43,7 +43,8 @@ GET /projects @@ -43,7 +43,8 @@ GET /projects
43 "owner_id": 1, 43 "owner_id": 1,
44 "path": "diaspora", 44 "path": "diaspora",
45 "updated_at": "2013-09-30T13: 46: 02Z" 45 "updated_at": "2013-09-30T13: 46: 02Z"
46 - } 46 + },
  47 + "archived": false
47 }, 48 },
48 { 49 {
49 "id": 6, 50 "id": 6,
@@ -78,7 +79,8 @@ GET /projects @@ -78,7 +79,8 @@ GET /projects
78 "owner_id": 1, 79 "owner_id": 1,
79 "path": "brightbox", 80 "path": "brightbox",
80 "updated_at": "2013-09-30T13:46:02Z" 81 "updated_at": "2013-09-30T13:46:02Z"
81 - } 82 + },
  83 + "archived": false
82 } 84 }
83 ] 85 ]
84 ``` 86 ```
@@ -157,7 +159,8 @@ Parameters: @@ -157,7 +159,8 @@ Parameters:
157 "access_level": 50, 159 "access_level": 50,
158 "notification_level": 3 160 "notification_level": 3
159 } 161 }
160 - } 162 + },
  163 + "archived": false
161 } 164 }
162 ``` 165 ```
163 166
doc/install/installation.md
1 # Select Version to Install 1 # Select Version to Install
2 -Make sure you view this installation guide from the branch (version) of GitLab you would like to install. In most cases 2 +Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install. In most cases
3 this should be the highest numbered stable branch (example shown below). 3 this should be the highest numbered stable branch (example shown below).
4 4
5 ![capture](http://i.imgur.com/d2AlIVj.png) 5 ![capture](http://i.imgur.com/d2AlIVj.png)
6 6
7 -If this is unclear check the [GitLab Blog](https://www.gitlab.com/blog/) for installation guide links by version. 7 +If the highest number stable branch is unclear please check the [GitLab Blog](https://www.gitlab.com/blog/) for installation guide links by version.
8 8
9 # Important notes 9 # Important notes
10 10
@@ -86,7 +86,7 @@ Is the system packaged Git too old? Remove it and compile from source. @@ -86,7 +86,7 @@ Is the system packaged Git too old? Remove it and compile from source.
86 mail server. By default, Debian is shipped with exim4 whereas Ubuntu 86 mail server. By default, Debian is shipped with exim4 whereas Ubuntu
87 does not ship with one. The recommended mail server is postfix and you can install it with: 87 does not ship with one. The recommended mail server is postfix and you can install it with:
88 88
89 - sudo apt-get install -y postfix 89 + sudo apt-get install -y postfix
90 90
91 Then select 'Internet Site' and press enter to confirm the hostname. 91 Then select 'Internet Site' and press enter to confirm the hostname.
92 92
@@ -101,8 +101,8 @@ Remove the old Ruby 1.8 if present @@ -101,8 +101,8 @@ Remove the old Ruby 1.8 if present
101 Download Ruby and compile it: 101 Download Ruby and compile it:
102 102
103 mkdir /tmp/ruby && cd /tmp/ruby 103 mkdir /tmp/ruby && cd /tmp/ruby
104 - curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.0/ruby-2.0.0-p353.tar.gz | tar xz  
105 - cd ruby-2.0.0-p353 104 + curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.0/ruby-2.0.0-p481.tar.gz | tar xz
  105 + cd ruby-2.0.0-p481
106 ./configure --disable-install-rdoc 106 ./configure --disable-install-rdoc
107 make 107 make
108 sudo make install 108 sudo make install
@@ -121,6 +121,7 @@ Create a `git` user for Gitlab: @@ -121,6 +121,7 @@ Create a `git` user for Gitlab:
121 # 4. Database 121 # 4. Database
122 122
123 We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](database_mysql.md). 123 We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](database_mysql.md).
  124 +NOTE: because we need to make use of extensions you need at least pgsql 9.1.
124 125
125 # Install the database packages 126 # Install the database packages
126 sudo apt-get install -y postgresql-9.1 postgresql-client libpq-dev 127 sudo apt-get install -y postgresql-9.1 postgresql-client libpq-dev
@@ -129,7 +130,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da @@ -129,7 +130,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
129 sudo -u postgres psql -d template1 130 sudo -u postgres psql -d template1
130 131
131 # Create a user for GitLab. 132 # Create a user for GitLab.
132 - template1=# CREATE USER git; 133 + template1=# CREATE USER git CREATEDB;
133 134
134 # Create the GitLab production database & grant all privileges on database 135 # Create the GitLab production database & grant all privileges on database
135 template1=# CREATE DATABASE gitlabhq_production OWNER git; 136 template1=# CREATE DATABASE gitlabhq_production OWNER git;
@@ -149,13 +150,13 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da @@ -149,13 +150,13 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
149 ## Clone the Source 150 ## Clone the Source
150 151
151 # Clone GitLab repository 152 # Clone GitLab repository
152 - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 6-8-stable gitlab 153 + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 6-9-stable gitlab
153 154
154 # Go to gitlab dir 155 # Go to gitlab dir
155 cd /home/git/gitlab 156 cd /home/git/gitlab
156 157
157 **Note:** 158 **Note:**
158 -You can change `6-8-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! 159 +You can change `6-9-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
159 160
160 ## Configure it 161 ## Configure it
161 162
@@ -200,7 +201,7 @@ You can change `6-8-stable` to `master` if you want the *bleeding edge* version, @@ -200,7 +201,7 @@ You can change `6-8-stable` to `master` if you want the *bleeding edge* version,
200 # Configure Git global settings for git user, useful when editing via web 201 # Configure Git global settings for git user, useful when editing via web
201 # Edit user.email according to what is set in gitlab.yml 202 # Edit user.email according to what is set in gitlab.yml
202 sudo -u git -H git config --global user.name "GitLab" 203 sudo -u git -H git config --global user.name "GitLab"
203 - sudo -u git -H git config --global user.email "gitlab@localhost" 204 + sudo -u git -H git config --global user.email "example@example.com"
204 sudo -u git -H git config --global core.autocrlf input 205 sudo -u git -H git config --global core.autocrlf input
205 206
206 **Important Note:** 207 **Important Note:**
@@ -243,15 +244,6 @@ that were [fixed](https://github.com/bundler/bundler/pull/2817) in 1.5.2. @@ -243,15 +244,6 @@ that were [fixed](https://github.com/bundler/bundler/pull/2817) in 1.5.2.
243 # Or if you use MySQL (note, the option says "without ... postgres") 244 # Or if you use MySQL (note, the option says "without ... postgres")
244 sudo -u git -H bundle install --deployment --without development test postgres aws 245 sudo -u git -H bundle install --deployment --without development test postgres aws
245 246
246 -  
247 -## Initialize Database and Activate Advanced Features  
248 -  
249 - sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production  
250 -  
251 - # Type 'yes' to create the database tables.  
252 -  
253 - # When done you see 'Administrator account created:'  
254 -  
255 ## Install GitLab shell 247 ## Install GitLab shell
256 248
257 GitLab Shell is an ssh access and repository management software developed specially for GitLab. 249 GitLab Shell is an ssh access and repository management software developed specially for GitLab.
@@ -260,11 +252,20 @@ GitLab Shell is an ssh access and repository management software developed speci @@ -260,11 +252,20 @@ GitLab Shell is an ssh access and repository management software developed speci
260 cd /home/git/gitlab 252 cd /home/git/gitlab
261 253
262 # Run the installation task for gitlab-shell (replace `REDIS_URL` if needed): 254 # Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
263 - sudo -u git -H bundle exec rake gitlab:shell:install[v1.9.3] REDIS_URL=redis://localhost:6379 255 + sudo -u git -H bundle exec rake gitlab:shell:install[v1.9.4] REDIS_URL=redis://localhost:6379 RAILS_ENV=production
264 256
265 # By default, the gitlab-shell config is generated from your main gitlab config. You can review (and modify) it as follows: 257 # By default, the gitlab-shell config is generated from your main gitlab config. You can review (and modify) it as follows:
266 sudo -u git -H editor /home/git/gitlab-shell/config.yml 258 sudo -u git -H editor /home/git/gitlab-shell/config.yml
267 259
  260 +
  261 +## Initialize Database and Activate Advanced Features
  262 +
  263 + sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
  264 +
  265 + # Type 'yes' to create the database tables.
  266 +
  267 + # When done you see 'Administrator account created:'
  268 +
268 ## Install Init Script 269 ## Install Init Script
269 270
270 Download the init script (will be /etc/init.d/gitlab): 271 Download the init script (will be /etc/init.d/gitlab):
@@ -302,11 +303,6 @@ Check if GitLab and its environment are configured correctly: @@ -302,11 +303,6 @@ Check if GitLab and its environment are configured correctly:
302 sudo /etc/init.d/gitlab restart 303 sudo /etc/init.d/gitlab restart
303 304
304 305
305 -## Compile assets  
306 -  
307 - sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production  
308 -  
309 -  
310 # 6. Nginx 306 # 6. Nginx
311 307
312 **Note:** 308 **Note:**
@@ -413,22 +409,22 @@ GitLab uses [Omniauth](http://www.omniauth.org/) for authentication and already @@ -413,22 +409,22 @@ GitLab uses [Omniauth](http://www.omniauth.org/) for authentication and already
413 These steps are fairly general and you will need to figure out the exact details from the Omniauth provider's documentation. 409 These steps are fairly general and you will need to figure out the exact details from the Omniauth provider's documentation.
414 410
415 * Stop GitLab 411 * Stop GitLab
416 - `sudo service gitlab stop` 412 + `sudo service gitlab stop`
417 413
418 * Add provider specific configuration options to your `config/gitlab.yml` (you can use the [auth providers section of the example config](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example) as a reference) 414 * Add provider specific configuration options to your `config/gitlab.yml` (you can use the [auth providers section of the example config](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example) as a reference)
419 415
420 * Add the gem to your [Gemfile](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/Gemfile) 416 * Add the gem to your [Gemfile](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/Gemfile)
421 `gem "omniauth-your-auth-provider"` 417 `gem "omniauth-your-auth-provider"`
422 * If you're using MySQL, install the new Omniauth provider gem by running the following command: 418 * If you're using MySQL, install the new Omniauth provider gem by running the following command:
423 - `sudo -u git -H bundle install --without development test postgres --path vendor/bundle --no-deployment` 419 + `sudo -u git -H bundle install --without development test postgres --path vendor/bundle --no-deployment`
424 420
425 * If you're using PostgreSQL, install the new Omniauth provider gem by running the following command: 421 * If you're using PostgreSQL, install the new Omniauth provider gem by running the following command:
426 - `sudo -u git -H bundle install --without development test mysql --path vendor/bundle --no-deployment` 422 + `sudo -u git -H bundle install --without development test mysql --path vendor/bundle --no-deployment`
427 423
428 > These are the same commands you used in the [Install Gems section](#install-gems) with `--path vendor/bundle --no-deployment` instead of `--deployment`. 424 > These are the same commands you used in the [Install Gems section](#install-gems) with `--path vendor/bundle --no-deployment` instead of `--deployment`.
429 425
430 * Start GitLab 426 * Start GitLab
431 - `sudo service gitlab start` 427 + `sudo service gitlab start`
432 428
433 429
434 ### Examples 430 ### Examples
doc/install/requirements.md
@@ -53,7 +53,7 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/)) but GitLab @@ -53,7 +53,7 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/)) but GitLab
53 53
54 ## Memory 54 ## Memory
55 55
56 -- 512MB is the abolute minimum, you need 256MB of swap, you can configure only one slow unicorn worker, only ssh access will work, we do not recommend this 56 +- 512MB is the absolute minimum, you need 256MB of swap, you can configure only one slow unicorn worker, only ssh access will work, we do not recommend this
57 - 1GB supports up to 100 users (with individual repositories under 250MB, otherwise git memory usage necessitates using swap space) 57 - 1GB supports up to 100 users (with individual repositories under 250MB, otherwise git memory usage necessitates using swap space)
58 - **2GB** is the **recommended** memory size and supports up to 500 users 58 - **2GB** is the **recommended** memory size and supports up to 500 users
59 - 4GB supports up to 2,000 users 59 - 4GB supports up to 2,000 users
@@ -74,11 +74,14 @@ Apart from a local hard drive you can also mount a volume that supports the netw @@ -74,11 +74,14 @@ Apart from a local hard drive you can also mount a volume that supports the netw
74 74
75 If you have enough RAM memory and a recent CPU the speed of GitLab is mainly limited by hard drive seek times. Having a fast drive (7200 RPM and up) or a solid state drive (SSD) will improve the responsiveness of GitLab. 75 If you have enough RAM memory and a recent CPU the speed of GitLab is mainly limited by hard drive seek times. Having a fast drive (7200 RPM and up) or a solid state drive (SSD) will improve the responsiveness of GitLab.
76 76
  77 +## Database
  78 +
  79 +If you want to run the database separately, the **recommended** database size is **1 MB per user**
77 80
78 # Supported webbrowsers 81 # Supported webbrowsers
79 82
80 - Chrome (Latest stable version) 83 - Chrome (Latest stable version)
81 - Firefox (Latest released version) 84 - Firefox (Latest released version)
82 -- Safari 7+ (Know problem: required fields in html5 do not work) 85 +- Safari 7+ (known problem: required fields in html5 do not work)
83 - Opera (Latest released version) 86 - Opera (Latest released version)
84 - IE 10+ 87 - IE 10+
doc/integration/README.md
@@ -7,3 +7,5 @@ See the documentation below for details on how to configure these services. @@ -7,3 +7,5 @@ See the documentation below for details on how to configure these services.
7 + [LDAP](ldap.md) Set up sign in via LDAP 7 + [LDAP](ldap.md) Set up sign in via LDAP
8 + [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, and Google via OAuth. 8 + [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, and Google via OAuth.
9 + [Slack](slack.md) Integrate with the Slack chat service 9 + [Slack](slack.md) Integrate with the Slack chat service
  10 +
  11 +Jenkins support is [available in GitLab EE](http://doc.gitlab.com/ee/integration/jenkins.html).
doc/integration/external-issue-tracker.md
@@ -2,8 +2,10 @@ GitLab has a great issue tracker but you can also use an external issue tracker @@ -2,8 +2,10 @@ GitLab has a great issue tracker but you can also use an external issue tracker
2 2
3 - the 'Issues' link on the GitLab project pages takes you to the appropriate JIRA issue index; 3 - the 'Issues' link on the GitLab project pages takes you to the appropriate JIRA issue index;
4 - clicking 'New issue' on the project dashboard creates a new JIRA issue; 4 - clicking 'New issue' on the project dashboard creates a new JIRA issue;
5 -- To reference JIRA issue PROJECT-1234 in comments, use syntax #PROJECT-1234. Commit messages get turned into HTML links to the corresponding JIRA issue. 5 +- To reference JIRA issue PROJECT-1234 in comments, use syntax PROJECT-1234. Commit messages get turned into HTML links to the corresponding JIRA issue.
6 6
7 ![jira screenshot](jira-integration-points.png) 7 ![jira screenshot](jira-integration-points.png)
8 8
9 -You can configure the integration in the gitlab.yml configuration file.  
10 \ No newline at end of file 9 \ No newline at end of file
  10 +You can configure the integration in the gitlab.yml configuration file.
  11 +
  12 +Support to add your commits to the Jira ticket automatically is [available in GitLab EE](http://doc.gitlab.com/ee/integration/jira.html).
doc/public_access/public_access.md
@@ -4,7 +4,7 @@ Internal projects will only be available to authenticated users. @@ -4,7 +4,7 @@ Internal projects will only be available to authenticated users.
4 4
5 #### Public projects 5 #### Public projects
6 Public projects can be cloned **without any** authentication. 6 Public projects can be cloned **without any** authentication.
7 -It will also be listen on the [public access directory](/public). 7 +It will also be listed on the [public access directory](/public).
8 **Any logged in user** will have [Guest](/help/permissions) permissions on the repository. 8 **Any logged in user** will have [Guest](/help/permissions) permissions on the repository.
9 9
10 #### Internal projects 10 #### Internal projects
doc/raketasks/maintenance.md
@@ -24,9 +24,9 @@ Version: 5.1.0.beta2 @@ -24,9 +24,9 @@ Version: 5.1.0.beta2
24 Revision: 4da8b37 24 Revision: 4da8b37
25 Directory: /home/git/gitlab 25 Directory: /home/git/gitlab
26 DB Adapter: mysql2 26 DB Adapter: mysql2
27 -URL: http://localhost  
28 -HTTP Clone URL: http://localhost/some-project.git  
29 -SSH Clone URL: git@localhost:some-project.git 27 +URL: http://example.com
  28 +HTTP Clone URL: http://example.com/some-project.git
  29 +SSH Clone URL: git@example.com:some-project.git
30 Using LDAP: no 30 Using LDAP: no
31 Using Omniauth: no 31 Using Omniauth: no
32 32
doc/release/monthly.md
1 -# Things to do when creating new monthly minor or major release  
2 -NOTE: This is a guide for GitLab developers. If you are trying to install GitLab see the latest stable [installation guide](install/installation.md) and if you are trying to upgrade, see the [upgrade guides](update). 1 +# Monthly Release
  2 +NOTE: This is a guide for GitLab developers.
3 3
4 -## Install guide up to date? 4 +# **15th - Code Freeze & Release Manager**
5 5
6 -* References correct GitLab branch `x-x-stable` and correct GitLab shell tag? 6 +### **1. Stop merging in code, except for important bugfixes**
7 7
8 -## Make upgrade guide 8 +### **2. Release Manager**
9 9
10 -### From x.x to x.x 10 +A release manager is selected that coordinates the entire release of this version. The release manager has to make sure all the steps below are done and delegated where necessary. This person should also make sure this document is kept up to date and issues are created and updated.
11 11
12 -#### 0. Any major changes? Database updates? Web server change? File structure changes? 12 +# **18th - Releasing RC1**
  13 +
  14 +The RC1 release comes with the task to update the installation and upgrade docs. Be mindful that there might already be merge requests for this on GitLab or GitHub.
  15 +
  16 +### **1. Create an issue for RC1 release**
  17 +
  18 +### **2. Update the installation guide**
  19 +
  20 +1. Check if it references the correct branch `x-x-stable` (doesn't exist yet, but that is okay)
  21 +2. Check the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782)
  22 +3. Check the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794)
  23 +4. There might be other changes. Ask around.
  24 +
  25 +### **3. Create an update guide**
  26 +
  27 +It's best to copy paste the previous guide and make changes where necessary. The typical steps are listed below with any points you should specifically look at.
  28 +
  29 +#### 0. Any major changes?
  30 +List any major changes here, so the user is aware of them before starting to upgrade. For instance:
  31 +- Database updates
  32 +- Web server changes
  33 +- File structure changes
13 34
14 #### 1. Make backup 35 #### 1. Make backup
15 36
@@ -17,9 +38,9 @@ NOTE: This is a guide for GitLab developers. If you are trying to install GitLab @@ -17,9 +38,9 @@ NOTE: This is a guide for GitLab developers. If you are trying to install GitLab
17 38
18 #### 3. Do users need to update dependencies like `git`? 39 #### 3. Do users need to update dependencies like `git`?
19 40
20 -- Check the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782) 41 +- Check if the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782) changed since the last release.
21 42
22 -- Check the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794) 43 +- Check if the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794) changed since the last release.
23 44
24 #### 4. Get latest code 45 #### 4. Get latest code
25 46
@@ -29,7 +50,7 @@ NOTE: This is a guide for GitLab developers. If you are trying to install GitLab @@ -29,7 +50,7 @@ NOTE: This is a guide for GitLab developers. If you are trying to install GitLab
29 50
30 #### 7. Any config files updated since last release? 51 #### 7. Any config files updated since last release?
31 52
32 -Check if any of these changed since last release (~22nd of last month depending on when last release branch was created): 53 +Check if any of these changed since last release:
33 54
34 * https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/nginx/gitlab 55 * https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/nginx/gitlab
35 * https://gitlab.com/gitlab-org/gitlab-shell/commits/master/config.yml.example 56 * https://gitlab.com/gitlab-org/gitlab-shell/commits/master/config.yml.example
@@ -40,13 +61,14 @@ Check if any of these changed since last release (~22nd of last month depending @@ -40,13 +61,14 @@ Check if any of these changed since last release (~22nd of last month depending
40 61
41 #### 8. Need to update init script? 62 #### 8. Need to update init script?
42 63
43 -Check if changed since last release (~22nd of last month depending on when last release branch was created): https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/init.d/gitlab 64 +Check if the init.d/gitlab script changed since last release: https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/init.d/gitlab
44 65
45 #### 9. Start application 66 #### 9. Start application
46 67
47 #### 10. Check application status 68 #### 10. Check application status
48 69
49 -## Make sure the code quality indicatiors are good 70 +### **4. Code quality indicatiors**
  71 +Make sure the code quality indicators are green / good.
50 72
51 * [![build status](http://ci.gitlab.org/projects/1/status.png?ref=master)](http://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch) 73 * [![build status](http://ci.gitlab.org/projects/1/status.png?ref=master)](http://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch)
52 74
@@ -58,49 +80,88 @@ Check if changed since last release (~22nd of last month depending on when last @@ -58,49 +80,88 @@ Check if changed since last release (~22nd of last month depending on when last
58 80
59 * [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq) 81 * [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq)
60 82
61 -## Release Schedule  
62 -  
63 -After making the release branch new commits are cherry-picked from master. When the release gets closer we get more selective what is cherry-picked. The days of the month are approximately as follows:  
64 -  
65 -* 1-7th: Official merge window (see contributing guide).  
66 -* 8-14th: Work on bugfixes, sponsored features and GitLab EE.  
67 -* 15th: Code freeze  
68 - - Stop merging into master, except essential bugfixes  
69 - - Select a Release Manager  
70 -* 18th: Release Candidate 1  
71 - - Set VERSION to x.x.0.rc1  
72 - - Create annotated tag x.x.0.rc1  
73 - - Push the changes to GitLab.com, dev.gitlab.com, GitHub  
74 - - Tweet about the release  
75 - - Create a new branch on cloud for rc1  
76 - - Deploy the new branch on Cloud after tests pass  
77 -* 20st: Optional release candidate 2 (x.x.0.rc2, only if rc1 had problems)  
78 -* 22nd: Release  
79 - - Create x-x-stable branch and push to the repositories  
80 - - QA  
81 - - Fix anything coming out of the QA  
82 - - Set VERSION to x.x.0  
83 - - Create annotated tag x.x.0  
84 - - Push VERSION + Tag to master, merge into x-x-stable  
85 - - Publish blog for new release  
86 - - Tweet to blog (see below)  
87 -* 22th: release GitLab EE  
88 -* 23nd: optional patch releases (x.x.1, x.x.2, etc., only if there are serious problems)  
89 -* 25th: release GitLab CI  
90 -  
91 -# Write a blog post 83 +### **5. Set VERSION**
  84 +
  85 +Set VERSION tot x.x.0.rc1
  86 +
  87 +
  88 +### **6. Tag**
  89 +
  90 +Create an annotated tag that points to the version change commit.
  91 +```
  92 +git tag -a vx.x.0.rc1 -m 'Version x.x.0.rc1'
  93 +```
  94 +
  95 +### **7. Tweet**
  96 +
  97 +Tweet about the RC release:
  98 +
  99 +> GitLab x.x.x.rc1 is out. This is a release candidate intended for testing only. Please let us know if you find regressions.
  100 +
  101 +### **8. Update Cloud**
  102 +
  103 +Merge the RC1 code into Cloud. Once the build is green, deploy in the morning.
  104 +
  105 +It is important to do this as soon as possible, so we can catch any errors before we release the full version.
92 106
93 -* Mention what GitLab is on the second line: GitLab is open source software to collaborate on code.  
94 -* Select and thank the the Most Valuable Person (MVP) of this release.  
95 -* Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible.  
96 107
97 -# Tweet 108 +# **22nd - Release CE and EE**
98 109
99 -Send out a tweet to share the good news with the world. For a major/minor release, list the features in short and link to the blog post. 110 +For GitLab EE, append -ee to the branches and tags.
100 111
101 -For a RC, make sure to explain what a RC is. 112 +`x-x-stable-ee`
  113 +
  114 +`v.x.x.0-ee`
  115 +
  116 +### **1. Create x-x-stable branch and push to the repositories**
  117 +
  118 +```
  119 +git checkout master
  120 +git pull
  121 +git checkout -b x-x-stable
  122 +git push <remote> x-x-stable
  123 +```
  124 +
  125 +### **2. Build the Omnibus packages**
  126 +[Follow this guide](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md)
  127 +
  128 +### **3. QA**
  129 +Use the omnibus packages to test using [this guide](https://dev.gitlab.org/gitlab/gitlab-ee/blob/master/doc/release/manual_testing.md)
  130 +
  131 +
  132 +### **4. Fix anything coming out of the QA**
  133 +
  134 +### **5. Set VERSION to x.x.0**
  135 +
  136 +### **6. Create annotated tag vx.x.0**
  137 +```
  138 +git tag -a vx.x.0 -m 'Version x.x.0'
  139 +```
  140 +
  141 +### **7. Push VERSION + Tag to master, merge into x-x-stable**
  142 +```
  143 +git push origin master
  144 +```
  145 +
  146 +Next, merge the VERSION into the x-x-stable branch.
  147 +
  148 +### **8. Push to remotes**
  149 +
  150 +For GitLab CE, push to dev, GitLab.com and GitHub.
  151 +
  152 +For GitLab EE, push to the subscribers repo.
  153 +
  154 +NOTE: You might not have the rights to push to master on dev. Ask Dmitriy.
  155 +
  156 +### **9. Publish blog for new release**
  157 +* Mention what GitLab is on the second line: GitLab is open source software to collaborate on code.
  158 +* Select and thank the the Most Valuable Person (MVP) of this release.
  159 +* Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible.
102 160
103 -A patch release tweet should specify the fixes it brings and link to the corresponding blog post. 161 +### **10. Tweet to blog**
104 162
  163 +Send out a tweet to share the good news with the world. List the features in short and link to the blog post.
105 164
  165 +# **23rd - Optional Patch Release**
106 166
  167 +# **25th - Release GitLab CI**
doc/system_hooks/system_hooks.md
@@ -16,6 +16,7 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser @@ -16,6 +16,7 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser
16 "path": "stormcloud", 16 "path": "stormcloud",
17 "path_with_namespace": "jsmith/stormcloud", 17 "path_with_namespace": "jsmith/stormcloud",
18 "project_id": 74, 18 "project_id": 74,
  19 + "project_visibility": "private",
19 } 20 }
20 ``` 21 ```
21 22
@@ -31,6 +32,7 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser @@ -31,6 +32,7 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser
31 "path": "underscore", 32 "path": "underscore",
32 "path_with_namespace": "jsmith/underscore", 33 "path_with_namespace": "jsmith/underscore",
33 "project_id": 73, 34 "project_id": 73,
  35 + "project_visibility": "internal",
34 } 36 }
35 ``` 37 ```
36 38
@@ -38,14 +40,15 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser @@ -38,14 +40,15 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser
38 40
39 ```json 41 ```json
40 { 42 {
41 - "created_at": "2012-07-21T07:30:56Z",  
42 - "event_name": "user_add_to_team",  
43 - "project_access": "Master",  
44 - "project_id": 74,  
45 - "project_name": "StoreCloud",  
46 - "project_path": "storecloud",  
47 - "user_email": "johnsmith@gmail.com",  
48 - "user_name": "John Smith", 43 + "created_at": "2012-07-21T07:30:56Z",
  44 + "event_name": "user_add_to_team",
  45 + "project_access": "Master",
  46 + "project_id": 74,
  47 + "project_name": "StoreCloud",
  48 + "project_path": "storecloud",
  49 + "user_email": "johnsmith@gmail.com",
  50 + "user_name": "John Smith",
  51 + "project_visibility": "private",
49 } 52 }
50 ``` 53 ```
51 54
@@ -53,14 +56,15 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser @@ -53,14 +56,15 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser
53 56
54 ```json 57 ```json
55 { 58 {
56 - "created_at": "2012-07-21T07:30:56Z",  
57 - "event_name": "user_remove_from_team",  
58 - "project_access": "Master",  
59 - "project_id": 74,  
60 - "project_name": "StoreCloud",  
61 - "project_path": "storecloud",  
62 - "user_email": "johnsmith@gmail.com",  
63 - "user_name": "John Smith", 59 + "created_at": "2012-07-21T07:30:56Z",
  60 + "event_name": "user_remove_from_team",
  61 + "project_access": "Master",
  62 + "project_id": 74,
  63 + "project_name": "StoreCloud",
  64 + "project_path": "storecloud",
  65 + "user_email": "johnsmith@gmail.com",
  66 + "user_name": "John Smith",
  67 + "project_visibility": "private",
64 } 68 }
65 ``` 69 ```
66 70
doc/update/6.6-to-6.7.md
@@ -64,6 +64,10 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab @@ -64,6 +64,10 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
64 # Update the logrotate configuration (keep logs for 90 days instead of 52 weeks) 64 # Update the logrotate configuration (keep logs for 90 days instead of 52 weeks)
65 sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab 65 sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
66 66
  67 +# Compress existing .log.1 files because we turned off delaycompress in logrotate
  68 +sudo -u git -H gzip /home/git/gitlab/log/*.log.1
  69 +sudo -u git -H gzip /home/git/gitlab-shell/gitlab-shell.log.1
  70 +
67 # Close access to gitlab-satellites for others 71 # Close access to gitlab-satellites for others
68 sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites 72 sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites
69 ``` 73 ```
doc/update/6.7-to-6.8.md
@@ -64,9 +64,6 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS @@ -64,9 +64,6 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
64 sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab 64 sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
65 sudo chmod +x /etc/init.d/gitlab 65 sudo chmod +x /etc/init.d/gitlab
66 66
67 -# Update the logrotate configuration (keep logs for 90 days instead of 52 weeks)  
68 -sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab  
69 -  
70 # Close access to gitlab-satellites for others 67 # Close access to gitlab-satellites for others
71 sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites 68 sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites
72 ``` 69 ```
doc/update/6.8-to-6.9.md 0 → 100644
@@ -0,0 +1,96 @@ @@ -0,0 +1,96 @@
  1 +# From 6.8 to 6.9
  2 +
  3 +### 0. Backup
  4 +
  5 +```bash
  6 +cd /home/git/gitlab
  7 +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
  8 +```
  9 +
  10 +### 1. Stop server
  11 +
  12 +```bash
  13 +sudo service gitlab stop
  14 +```
  15 +
  16 +### 2. Get latest code
  17 +
  18 +```bash
  19 +cd /home/git/gitlab
  20 +sudo -u git -H git fetch --all
  21 +```
  22 +
  23 +For Gitlab Community Edition:
  24 +
  25 +```bash
  26 +sudo -u git -H git checkout 6-9-stable
  27 +```
  28 +
  29 +OR
  30 +
  31 +For GitLab Enterprise Edition:
  32 +
  33 +```bash
  34 +sudo -u git -H git checkout 6-9-stable-ee
  35 +```
  36 +
  37 +### 3. Update gitlab-shell (and its config)
  38 +
  39 +```bash
  40 +cd /home/git/gitlab-shell
  41 +sudo -u git -H git fetch
  42 +sudo -u git -H git checkout v1.9.4
  43 +```
  44 +
  45 +### 4. Install libs, migrations, etc.
  46 +
  47 +```bash
  48 +cd /home/git/gitlab
  49 +
  50 +# MySQL installations (note: the line below states '--without ... postgres')
  51 +sudo -u git -H bundle install --without development test postgres --deployment
  52 +
  53 +# PostgreSQL installations (note: the line below states '--without ... mysql')
  54 +sudo -u git -H bundle install --without development test mysql --deployment
  55 +```
  56 +
  57 +### 5. Update config files
  58 +
  59 +#### New configuration options for gitlab.yml
  60 +
  61 +There are new configuration options available for gitlab.yml. View them with the command below and apply them to your current gitlab.yml if desired.
  62 +
  63 +```
  64 +git diff 6-8-stable:config/gitlab.yml.example 6-9-stable:config/gitlab.yml.example
  65 +```
  66 +
  67 +### 6. Start application
  68 +
  69 + sudo service gitlab start
  70 + sudo service nginx restart
  71 +
  72 +### 7. Check application status
  73 +
  74 +Check if GitLab and its environment are configured correctly:
  75 +
  76 + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
  77 +
  78 +To make sure you didn't miss anything run a more thorough check with:
  79 +
  80 + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
  81 +
  82 +If all items are green, then congratulations upgrade is complete!
  83 +
  84 +## Things went south? Revert to previous version (6.8)
  85 +
  86 +### 1. Revert the code to the previous version
  87 +Follow the [`upgrade guide from 6.7 to 6.8`](6.7-to-6.8.md), except for the database migration
  88 +(The backup is already migrated to the previous version)
  89 +
  90 +### 2. Restore from the backup:
  91 +
  92 +```bash
  93 +cd /home/git/gitlab
  94 +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
  95 +```
  96 +If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
doc/update/mysql_to_postgresql.md
1 -# Use the shell commands below to convert a MySQL GitLab database to a PostgreSQL one. 1 +# Migrating GitLab from MySQL to Postgres
  2 +
  3 +If you are replacing MySQL with Postgres while keeping GitLab on the same
  4 +server all you need to do is to export from MySQL, import into Postgres and
  5 +rebuild the indexes as described below. If you are also moving GitLab to
  6 +another server, or if you are switching to omnibus-gitlab, you may want to use
  7 +a GitLab backup file. The second part of this documents explains the procedure
  8 +to do this.
  9 +
  10 +## Export from MySQL and import into Postgres
  11 +
  12 +Use this if you are keeping GitLab on the same server.
2 13
3 ``` 14 ```
4 -git clone https://github.com/lanyrd/mysql-postgresql-converter.git 15 +sudo service gitlab stop
  16 +
  17 +# Update /home/git/gitlab/config/database.yml
  18 +
  19 +git clone https://github.com/gitlabhq/mysql-postgresql-converter.git
5 cd mysql-postgresql-converter 20 cd mysql-postgresql-converter
6 mysqldump --compatible=postgresql --default-character-set=utf8 -r databasename.mysql -u root gitlabhq_production 21 mysqldump --compatible=postgresql --default-character-set=utf8 -r databasename.mysql -u root gitlabhq_production
7 python db_converter.py databasename.mysql databasename.psql 22 python db_converter.py databasename.mysql databasename.psql
8 psql -f databasename.psql -d gitlabhq_production 23 psql -f databasename.psql -d gitlabhq_production
  24 +
  25 +# Rebuild indexes (see below)
  26 +
  27 +sudo service gitlab start
  28 +```
  29 +
  30 +
  31 +## Rebuild indexes
  32 +
  33 +The lanyrd database converter script does not preserve all indexes, so we have
  34 +to recreate them ourselves after migrating from MySQL. It is not necessary to
  35 +shut down GitLab for this process.
  36 +
  37 +```
  38 +# Clone the database converter on your Postgres-backed GitLab server
  39 +cd /tmp
  40 +git clone https://github.com/gitlabhq/mysql-postgresql-converter.git
  41 +
  42 +# Stash changes to db/schema.rb to make sure we can find the right index statements
  43 +cd /home/git/gitlab
  44 +sudo -u git -H git stash
  45 +
  46 +# Generate the `CREATE INDEX CONCURRENTLY` statements based on schema.rb
  47 +cd /tmp/mysql-to-postgresql-converter
  48 +ruby index_create_statements.rb /home/git/gitlab/db/schema.rb > index_create_statements.psql
  49 +
  50 +# Execute the SQL statements against the GitLab database
  51 +sudo -u git psql -f index_create_statements.psql -d gitlabhq_production
  52 +```
  53 +
  54 +## Converting a GitLab backup file from MySQL to Postgres
  55 +
  56 +GitLab backup files (<timestamp>_gitlab_backup.tar) contain a SQL dump. Using
  57 +the lanyrd database converter we can replace a MySQL database dump inside the
  58 +tar file with a Postgres database dump. This can be useful if you are moving to
  59 +another server.
  60 +
  61 +```
  62 +# Stop GitLab
  63 +sudo service gitlab stop
  64 +
  65 +# Create the backup
  66 +cd /home/git/gitlab
  67 +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
  68 +
  69 +# Note the filename of the backup that was created. We will call it
  70 +# TIMESTAMP_gitlab_backup.tar below.
  71 +
  72 +# Move the backup file we will convert to its own directory
  73 +sudo -u git -H mkdir -p tmp/backups/postgresql
  74 +sudo -u git -H mv tmp/backups/TIMESTAMP_gitlab_backup.tar tmp/backups/postgresql/
  75 +
  76 +# Create a separate database dump with PostgreSQL compatibility
  77 +cd tmp/backups/postgresql
  78 +sudo -u git -H mysqldump --compatible=postgresql --default-character-set=utf8 -r gitlabhq_production.mysql -u root gitlabhq_production
  79 +
  80 +# Clone the database converter
  81 +sudo -u git -H git clone https://github.com/lanyrd/mysql-postgresql-converter.git
  82 +
  83 +# Convert gitlabhq_production.mysql
  84 +sudo -u git -H mkdir db
  85 +sudo -u git -H python mysql-postgresql-converter/db_converter.py gitlabhq_production.mysql db/database.sql
  86 +
  87 +# Replace the MySQL dump in TIMESTAMP_gitlab_backup.tar.
  88 +
  89 +# Warning: if you forget to replace TIMESTAMP below, tar will create a new file
  90 +# 'TIMESTAMP_gitlab_backup.tar' without giving an error.
  91 +
  92 +sudo -u git -H tar rf TIMESTAMP_gitlab_backup.tar db/database.sql
  93 +
  94 +# Done! TIMESTAMP_gitlab_backup.tar can now be restored into a Postgres GitLab
  95 +# installation. Remember to recreate the indexes after the import.
9 ``` 96 ```
doc/update/ruby.md
1 # Updating Ruby from source 1 # Updating Ruby from source
2 2
3 -This guide explains how to update Ruby in case you installed it from source according to the instructions in https://gitlab.com/gitlab-org/gitlab-ce/blob/masterdoc/install/installation.md#2-ruby . 3 +This guide explains how to update Ruby in case you installed it from source according to the [instructions](../install/installation.md#2-ruby).
4 4
5 ### 1. Look for Ruby versions 5 ### 1. Look for Ruby versions
6 This guide will only update `/usr/local/bin/ruby`. You can see which Ruby binaries are installed on your system by running: 6 This guide will only update `/usr/local/bin/ruby`. You can see which Ruby binaries are installed on your system by running:
@@ -36,7 +36,7 @@ sudo gem install bundler @@ -36,7 +36,7 @@ sudo gem install bundler
36 ``` 36 ```
37 37
38 ### 5. Reinstall GitLab gem bundle 38 ### 5. Reinstall GitLab gem bundle
39 -Just to be sure we will reinstall the gems used by GitLab. Note that the `bundle install` command [depends on your choice of database](https://gitlab.com/gitlab-org/gitlab-ce/blob/masterdoc/install/installation.md#install-gems). 39 +Just to be sure we will reinstall the gems used by GitLab. Note that the `bundle install` command [depends on your choice of database](../install/installation.md#install-gems).
40 40
41 ```bash 41 ```bash
42 cd /home/git/gitlab 42 cd /home/git/gitlab
doc/web_hooks/web_hooks.md
@@ -25,16 +25,16 @@ Triggered when you push to the repository except when pushing tags. @@ -25,16 +25,16 @@ Triggered when you push to the repository except when pushing tags.
25 "project_id": 15, 25 "project_id": 15,
26 "repository": { 26 "repository": {
27 "name": "Diaspora", 27 "name": "Diaspora",
28 - "url": "git@localhost:diaspora.git", 28 + "url": "git@example.com:diaspora.git",
29 "description": "", 29 "description": "",
30 - "homepage": "http://localhost/diaspora" 30 + "homepage": "http://example.com/diaspora"
31 }, 31 },
32 "commits": [ 32 "commits": [
33 { 33 {
34 "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", 34 "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
35 "message": "Update Catalan translation to e38cb41.", 35 "message": "Update Catalan translation to e38cb41.",
36 "timestamp": "2011-12-12T14:27:31+02:00", 36 "timestamp": "2011-12-12T14:27:31+02:00",
37 - "url": "http://localhost/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", 37 + "url": "http://example.com/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
38 "author": { 38 "author": {
39 "name": "Jordi Mallach", 39 "name": "Jordi Mallach",
40 "email": "jordi@softcatala.org" 40 "email": "jordi@softcatala.org"
@@ -44,7 +44,7 @@ Triggered when you push to the repository except when pushing tags. @@ -44,7 +44,7 @@ Triggered when you push to the repository except when pushing tags.
44 "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", 44 "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
45 "message": "fixed readme", 45 "message": "fixed readme",
46 "timestamp": "2012-01-03T23:36:29+02:00", 46 "timestamp": "2012-01-03T23:36:29+02:00",
47 - "url": "http://localhost/diaspora/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7", 47 + "url": "http://example.com/diaspora/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
48 "author": { 48 "author": {
49 "name": "GitLab dev user", 49 "name": "GitLab dev user",
50 "email": "gitlabdev@dv6700.(none)" 50 "email": "gitlabdev@dv6700.(none)"
@@ -112,3 +112,34 @@ Triggered when a new merge request is created or an existing merge request was u @@ -112,3 +112,34 @@ Triggered when a new merge request is created or an existing merge request was u
112 } 112 }
113 } 113 }
114 ``` 114 ```
  115 +
  116 +#### Example webhook receiver
  117 +
  118 +If you want to see GitLab's webhooks in action for testing purposes you can use
  119 +a simple echo script running in a console session.
  120 +
  121 +Save the following file as `print_http_body.rb`.
  122 +
  123 +```ruby
  124 +require 'webrick'
  125 +
  126 +server = WEBrick::HTTPServer.new(Port: ARGV.first)
  127 +server.mount_proc '/' do |req, res|
  128 + puts req.body
  129 +end
  130 +
  131 +trap 'INT' do server.shutdown end
  132 +server.start
  133 +```
  134 +
  135 +Pick an unused port (e.g. 8000) and start the script: `ruby print_http_body.rb
  136 +8000`. Then add your server as a webhook receiver in GitLab as
  137 +`http://my.host:8000/`.
  138 +
  139 +When you press 'Test Hook' in GitLab, you should see something like this in the console.
  140 +
  141 +```
  142 +{"before":"077a85dd266e6f3573ef7e9ef8ce3343ad659c4e","after":"95cd4a99e93bc4bbabacfa2cd10e6725b1403c60",<SNIP>}
  143 +example.com - - [14/May/2014:07:45:26 EDT] "POST / HTTP/1.1" 200 0
  144 +- -> /
  145 +```
features/group.feature
@@ -113,3 +113,10 @@ Feature: Groups @@ -113,3 +113,10 @@ Feature: Groups
113 Then I should see user "John Doe" in team list 113 Then I should see user "John Doe" in team list
114 Then I should see user "Mary Jane" in team list 114 Then I should see user "Mary Jane" in team list
115 Then I should not see the "Remove User From Group" button for "Mary Jane" 115 Then I should not see the "Remove User From Group" button for "Mary Jane"
  116 +
  117 + Scenario: Search member by name
  118 + Given "Mary Jane" is guest of group "Guest"
  119 + And I visit group "Guest" members page
  120 + When I search for 'Mary' member
  121 + Then I should see user "Mary Jane" in team list
  122 + Then I should not see user "John Doe" in team list
features/project/forked_merge_requests.feature
@@ -30,11 +30,10 @@ Feature: Project Forked Merge Requests @@ -30,11 +30,10 @@ Feature: Project Forked Merge Requests
30 Given I visit project "Forked Shop" merge requests page 30 Given I visit project "Forked Shop" merge requests page
31 And I click link "New Merge Request" 31 And I click link "New Merge Request"
32 And I fill out an invalid "Merge Request On Forked Project" merge request 32 And I fill out an invalid "Merge Request On Forked Project" merge request
33 - And I submit the merge request  
34 Then I should see validation errors 33 Then I should see validation errors
35 34
36 @javascript 35 @javascript
37 Scenario: Merge request should target fork repository by default 36 Scenario: Merge request should target fork repository by default
38 Given I visit project "Forked Shop" merge requests page 37 Given I visit project "Forked Shop" merge requests page
39 And I click link "New Merge Request" 38 And I click link "New Merge Request"
40 - Then the target repository should be the original repository  
41 \ No newline at end of file 39 \ No newline at end of file
  40 + Then the target repository should be the original repository
features/project/wiki.feature
@@ -45,3 +45,20 @@ Feature: Project Wiki @@ -45,3 +45,20 @@ Feature: Project Wiki
45 And I browse to that Wiki page 45 And I browse to that Wiki page
46 And I click on the "Pages" button 46 And I click on the "Pages" button
47 Then I should see the existing page in the pages list 47 Then I should see the existing page in the pages list
  48 +
  49 + Scenario: File exists in wiki repo
  50 + Given I have an existing Wiki page with images linked on page
  51 + And I browse to wiki page with images
  52 + And I click on existing image link
  53 + Then I should see the image from wiki repo
  54 +
  55 + Scenario: Image in wiki repo shown on the page
  56 + Given I have an existing Wiki page with images linked on page
  57 + And I browse to wiki page with images
  58 + Then Image should be shown on the page
  59 +
  60 + Scenario: File does not exist in wiki repo
  61 + Given I have an existing Wiki page with images linked on page
  62 + And I browse to wiki page with images
  63 + And I click on image link
  64 + Then I should see the new wiki page form
features/steps/dashboard/dashboard.rb
@@ -25,7 +25,6 @@ class Dashboard &lt; Spinach::FeatureSteps @@ -25,7 +25,6 @@ class Dashboard &lt; Spinach::FeatureSteps
25 find("#merge_request_target_project_id").value.should == @project.id.to_s 25 find("#merge_request_target_project_id").value.should == @project.id.to_s
26 find("#merge_request_source_branch").value.should == "new_design" 26 find("#merge_request_source_branch").value.should == "new_design"
27 find("#merge_request_target_branch").value.should == "master" 27 find("#merge_request_target_branch").value.should == "master"
28 - find("#merge_request_title").value.should == "New design"  
29 end 28 end
30 29
31 Given 'user with name "John Doe" joined project "Shop"' do 30 Given 'user with name "John Doe" joined project "Shop"' do
features/steps/group/group.rb
@@ -157,6 +157,13 @@ class Groups &lt; Spinach::FeatureSteps @@ -157,6 +157,13 @@ class Groups &lt; Spinach::FeatureSteps
157 # poltergeist always confirms popups. 157 # poltergeist always confirms popups.
158 end 158 end
159 159
  160 + step 'I search for \'Mary\' member' do
  161 + within '.member-search-form' do
  162 + fill_in 'search', with: 'Mary'
  163 + click_button 'Search'
  164 + end
  165 + end
  166 +
160 protected 167 protected
161 168
162 def assigned_to_me key 169 def assigned_to_me key
features/steps/project/forked_merge_requests.rb
@@ -53,6 +53,7 @@ class ProjectForkedMergeRequests &lt; Spinach::FeatureSteps @@ -53,6 +53,7 @@ class ProjectForkedMergeRequests &lt; Spinach::FeatureSteps
53 53
54 find(:select, "merge_request_source_branch", {}).value.should == 'master' 54 find(:select, "merge_request_source_branch", {}).value.should == 'master'
55 find(:select, "merge_request_target_branch", {}).value.should == 'stable' 55 find(:select, "merge_request_target_branch", {}).value.should == 'stable'
  56 + click_button "Compare branches"
56 57
57 fill_in "merge_request_title", with: "Merge Request On Forked Project" 58 fill_in "merge_request_title", with: "Merge Request On Forked Project"
58 end 59 end
@@ -148,29 +149,19 @@ class ProjectForkedMergeRequests &lt; Spinach::FeatureSteps @@ -148,29 +149,19 @@ class ProjectForkedMergeRequests &lt; Spinach::FeatureSteps
148 current_path.should == edit_project_merge_request_path(@project, @merge_request) 149 current_path.should == edit_project_merge_request_path(@project, @merge_request)
149 page.should have_content "Edit merge request ##{@merge_request.id}" 150 page.should have_content "Edit merge request ##{@merge_request.id}"
150 find("#merge_request_title").value.should == "Merge Request On Forked Project" 151 find("#merge_request_title").value.should == "Merge Request On Forked Project"
151 - find("#merge_request_source_project_id").value.should == @forked_project.id.to_s  
152 - find("#merge_request_target_project_id").value.should == @project.id.to_s  
153 - find("#merge_request_source_branch").value.should have_content "master"  
154 - verify_commit_link(".mr_source_commit",@forked_project)  
155 - find("#merge_request_target_branch").value.should have_content "stable"  
156 - verify_commit_link(".mr_target_commit",@project)  
157 end 152 end
158 153
159 step 'I fill out an invalid "Merge Request On Forked Project" merge request' do 154 step 'I fill out an invalid "Merge Request On Forked Project" merge request' do
160 - #If this isn't filled in the rest of the validations won't be triggered  
161 - fill_in "merge_request_title", with: "Merge Request On Forked Project"  
162 -  
163 select "Select branch", from: "merge_request_target_branch" 155 select "Select branch", from: "merge_request_target_branch"
164 -  
165 find(:select, "merge_request_source_project_id", {}).value.should == @forked_project.id.to_s 156 find(:select, "merge_request_source_project_id", {}).value.should == @forked_project.id.to_s
166 find(:select, "merge_request_target_project_id", {}).value.should == project.id.to_s 157 find(:select, "merge_request_target_project_id", {}).value.should == project.id.to_s
167 find(:select, "merge_request_source_branch", {}).value.should == "" 158 find(:select, "merge_request_source_branch", {}).value.should == ""
168 find(:select, "merge_request_target_branch", {}).value.should == "" 159 find(:select, "merge_request_target_branch", {}).value.should == ""
  160 + click_button "Compare branches"
169 end 161 end
170 162
171 step 'I should see validation errors' do 163 step 'I should see validation errors' do
172 - page.should have_content "Source branch can't be blank"  
173 - page.should have_content "Target branch can't be blank" 164 + page.should have_content "You must select source and target branch"
174 end 165 end
175 166
176 step 'the target repository should be the original repository' do 167 step 'the target repository should be the original repository' do
features/steps/project/merge_requests.rb
@@ -61,9 +61,10 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps @@ -61,9 +61,10 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
61 end 61 end
62 62
63 step 'I submit new merge request "Wiki Feature"' do 63 step 'I submit new merge request "Wiki Feature"' do
64 - fill_in "merge_request_title", with: "Wiki Feature"  
65 select "master", from: "merge_request_source_branch" 64 select "master", from: "merge_request_source_branch"
66 select "notes_refactoring", from: "merge_request_target_branch" 65 select "notes_refactoring", from: "merge_request_target_branch"
  66 + click_button "Compare branches"
  67 + fill_in "merge_request_title", with: "Wiki Feature"
67 click_button "Submit merge request" 68 click_button "Submit merge request"
68 end 69 end
69 70
features/steps/project/wiki.rb
@@ -86,6 +86,47 @@ class Spinach::Features::ProjectWiki &lt; Spinach::FeatureSteps @@ -86,6 +86,47 @@ class Spinach::Features::ProjectWiki &lt; Spinach::FeatureSteps
86 page.should have_content @page.title 86 page.should have_content @page.title
87 end 87 end
88 88
  89 + Given 'I have an existing Wiki page with images linked on page' do
  90 + wiki.create_page("pictures", "Look at this [image](image.jpg)\n\n ![image](image.jpg)", :markdown, "first commit")
  91 + @wiki_page = wiki.find_page("pictures")
  92 + end
  93 +
  94 + And 'I browse to wiki page with images' do
  95 + visit project_wiki_path(project, @wiki_page)
  96 + end
  97 +
  98 + And 'I click on existing image link' do
  99 + file = Gollum::File.new(wiki.wiki)
  100 + Gollum::Wiki.any_instance.stub(:file).with("image.jpg", "master", true).and_return(file)
  101 + Gollum::File.any_instance.stub(:mime_type).and_return("image/jpeg")
  102 + page.should have_link('image', href: "image.jpg")
  103 + click_on "image"
  104 + end
  105 +
  106 + Then 'I should see the image from wiki repo' do
  107 + url = URI.parse(current_url)
  108 + url.path.should match("wikis/image.jpg")
  109 + page.should_not have_xpath('/html') # Page should render the image which means there is no html involved
  110 + Gollum::Wiki.any_instance.unstub(:file)
  111 + Gollum::File.any_instance.unstub(:mime_type)
  112 + end
  113 +
  114 + Then 'Image should be shown on the page' do
  115 + page.should have_xpath("//img[@src=\"image.jpg\"]")
  116 + end
  117 +
  118 + And 'I click on image link' do
  119 + page.should have_link('image', href: "image.jpg")
  120 + click_on "image"
  121 + end
  122 +
  123 + Then 'I should see the new wiki page form' do
  124 + url = URI.parse(current_url)
  125 + url.path.should match("wikis/image.jpg")
  126 + page.should have_content('New Wiki Page')
  127 + page.should have_content('Editing - image.jpg')
  128 + end
  129 +
89 def wiki 130 def wiki
90 @project_wiki = ProjectWiki.new(project, current_user) 131 @project_wiki = ProjectWiki.new(project, current_user)
91 end 132 end
lib/api/branches.rb
@@ -24,7 +24,7 @@ module API @@ -24,7 +24,7 @@ module API
24 # branch (required) - The name of the branch 24 # branch (required) - The name of the branch
25 # Example Request: 25 # Example Request:
26 # GET /projects/:id/repository/branches/:branch 26 # GET /projects/:id/repository/branches/:branch
27 - get ":id/repository/branches/:branch" do 27 + get ':id/repository/branches/:branch', requirements: { branch: /.*/ } do
28 @branch = user_project.repo.heads.find { |item| item.name == params[:branch] } 28 @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
29 not_found!("Branch does not exist") if @branch.nil? 29 not_found!("Branch does not exist") if @branch.nil?
30 present @branch, with: Entities::RepoObject, project: user_project 30 present @branch, with: Entities::RepoObject, project: user_project
@@ -37,7 +37,9 @@ module API @@ -37,7 +37,9 @@ module API
37 # branch (required) - The name of the branch 37 # branch (required) - The name of the branch
38 # Example Request: 38 # Example Request:
39 # PUT /projects/:id/repository/branches/:branch/protect 39 # PUT /projects/:id/repository/branches/:branch/protect
40 - put ":id/repository/branches/:branch/protect" do 40 + put ':id/repository/branches/:branch/protect',
  41 + requirements: { branch: /.*/ } do
  42 +
41 authorize_admin_project 43 authorize_admin_project
42 44
43 @branch = user_project.repository.find_branch(params[:branch]) 45 @branch = user_project.repository.find_branch(params[:branch])
@@ -55,7 +57,9 @@ module API @@ -55,7 +57,9 @@ module API
55 # branch (required) - The name of the branch 57 # branch (required) - The name of the branch
56 # Example Request: 58 # Example Request:
57 # PUT /projects/:id/repository/branches/:branch/unprotect 59 # PUT /projects/:id/repository/branches/:branch/unprotect
58 - put ":id/repository/branches/:branch/unprotect" do 60 + put ':id/repository/branches/:branch/unprotect',
  61 + requirements: { branch: /.*/ } do
  62 +
59 authorize_admin_project 63 authorize_admin_project
60 64
61 @branch = user_project.repository.find_branch(params[:branch]) 65 @branch = user_project.repository.find_branch(params[:branch])
lib/api/entities.rb
@@ -43,6 +43,7 @@ module API @@ -43,6 +43,7 @@ module API
43 class Project < Grape::Entity 43 class Project < Grape::Entity
44 expose :id, :description, :default_branch 44 expose :id, :description, :default_branch
45 expose :public?, as: :public 45 expose :public?, as: :public
  46 + expose :archived?, as: :archived
46 expose :visibility_level, :ssh_url_to_repo, :http_url_to_repo, :web_url 47 expose :visibility_level, :ssh_url_to_repo, :http_url_to_repo, :web_url
47 expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group } 48 expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
48 expose :name, :name_with_namespace 49 expose :name, :name_with_namespace
@@ -135,6 +136,7 @@ module API @@ -135,6 +136,7 @@ module API
135 expose :target_branch, :source_branch, :upvotes, :downvotes 136 expose :target_branch, :source_branch, :upvotes, :downvotes
136 expose :author, :assignee, using: Entities::UserBasic 137 expose :author, :assignee, using: Entities::UserBasic
137 expose :source_project_id, :target_project_id 138 expose :source_project_id, :target_project_id
  139 + expose :label_list, as: :labels
138 end 140 end
139 141
140 class SSHKey < Grape::Entity 142 class SSHKey < Grape::Entity
lib/api/helpers.rb
@@ -8,6 +8,11 @@ module API @@ -8,6 +8,11 @@ module API
8 def current_user 8 def current_user
9 private_token = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s 9 private_token = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s
10 @current_user ||= User.find_by(authentication_token: private_token) 10 @current_user ||= User.find_by(authentication_token: private_token)
  11 +
  12 + unless @current_user && Gitlab::UserAccess.allowed?(@current_user)
  13 + return nil
  14 + end
  15 +
11 identifier = sudo_identifier() 16 identifier = sudo_identifier()
12 17
13 # If the sudo is the current user do nothing 18 # If the sudo is the current user do nothing
lib/api/merge_requests.rb
@@ -34,7 +34,7 @@ module API @@ -34,7 +34,7 @@ module API
34 when "closed" then user_project.merge_requests.closed 34 when "closed" then user_project.merge_requests.closed
35 when "merged" then user_project.merge_requests.merged 35 when "merged" then user_project.merge_requests.merged
36 else user_project.merge_requests 36 else user_project.merge_requests
37 - end 37 + end
38 38
39 present paginate(mrs), with: Entities::MergeRequest 39 present paginate(mrs), with: Entities::MergeRequest
40 end 40 end
@@ -67,6 +67,7 @@ module API @@ -67,6 +67,7 @@ module API
67 # assignee_id - Assignee user ID 67 # assignee_id - Assignee user ID
68 # title (required) - Title of MR 68 # title (required) - Title of MR
69 # description - Description of MR 69 # description - Description of MR
  70 + # labels (optional) - Labels for MR as a comma-separated list
70 # 71 #
71 # Example: 72 # Example:
72 # POST /projects/:id/merge_requests 73 # POST /projects/:id/merge_requests
@@ -75,6 +76,7 @@ module API @@ -75,6 +76,7 @@ module API
75 authorize! :write_merge_request, user_project 76 authorize! :write_merge_request, user_project
76 required_attributes! [:source_branch, :target_branch, :title] 77 required_attributes! [:source_branch, :target_branch, :title]
77 attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description] 78 attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description]
  79 + attrs[:label_list] = params[:labels] if params[:labels].present?
78 merge_request = ::MergeRequests::CreateService.new(user_project, current_user, attrs).execute 80 merge_request = ::MergeRequests::CreateService.new(user_project, current_user, attrs).execute
79 81
80 if merge_request.valid? 82 if merge_request.valid?
@@ -95,11 +97,13 @@ module API @@ -95,11 +97,13 @@ module API
95 # title - Title of MR 97 # title - Title of MR
96 # state_event - Status of MR. (close|reopen|merge) 98 # state_event - Status of MR. (close|reopen|merge)
97 # description - Description of MR 99 # description - Description of MR
  100 + # labels (optional) - Labels for a MR as a comma-separated list
98 # Example: 101 # Example:
99 # PUT /projects/:id/merge_request/:merge_request_id 102 # PUT /projects/:id/merge_request/:merge_request_id
100 # 103 #
101 put ":id/merge_request/:merge_request_id" do 104 put ":id/merge_request/:merge_request_id" do
102 attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event, :description] 105 attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event, :description]
  106 + attrs[:label_list] = params[:labels] if params[:labels].present?
103 merge_request = user_project.merge_requests.find(params[:merge_request_id]) 107 merge_request = user_project.merge_requests.find(params[:merge_request_id])
104 authorize! :modify_merge_request, merge_request 108 authorize! :modify_merge_request, merge_request
105 merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request) 109 merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request)
@@ -111,6 +115,49 @@ module API @@ -111,6 +115,49 @@ module API
111 end 115 end
112 end 116 end
113 117
  118 + # Merge MR
  119 + #
  120 + # Parameters:
  121 + # id (required) - The ID of a project
  122 + # merge_request_id (required) - ID of MR
  123 + # merge_commit_message (optional) - Custom merge commit message
  124 + # Example:
  125 + # PUT /projects/:id/merge_request/:merge_request_id/merge
  126 + #
  127 + put ":id/merge_request/:merge_request_id/merge" do
  128 + merge_request = user_project.merge_requests.find(params[:merge_request_id])
  129 +
  130 + action = if user_project.protected_branch?(merge_request.target_branch)
  131 + :push_code_to_protected_branches
  132 + else
  133 + :push_code
  134 + end
  135 +
  136 + if can?(current_user, action, user_project)
  137 + if merge_request.unchecked?
  138 + merge_request.check_if_can_be_merged
  139 + end
  140 +
  141 + if merge_request.open?
  142 + if merge_request.can_be_merged?
  143 + merge_request.automerge!(current_user, params[:merge_commit_message] || merge_request.merge_commit_message)
  144 + present merge_request, with: Entities::MergeRequest
  145 + else
  146 + render_api_error!('Branch cannot be merged', 405)
  147 + end
  148 + else
  149 + # Merge request can not be merged
  150 + # because it is already closed/merged
  151 + not_allowed!
  152 + end
  153 + else
  154 + # Merge request can not be merged
  155 + # because user dont have permissions to push into target branch
  156 + unauthorized!
  157 + end
  158 + end
  159 +
  160 +
114 # Get a merge request's comments 161 # Get a merge request's comments
115 # 162 #
116 # Parameters: 163 # Parameters:
lib/backup/manager.rb
@@ -101,7 +101,7 @@ module Backup @@ -101,7 +101,7 @@ module Backup
101 101
102 def tar_version 102 def tar_version
103 tar_version, _ = Gitlab::Popen.popen(%W(tar --version)) 103 tar_version, _ = Gitlab::Popen.popen(%W(tar --version))
104 - tar_version.split("\n").first 104 + tar_version.force_encoding('locale').split("\n").first
105 end 105 end
106 end 106 end
107 end 107 end
lib/backup/repository.rb
@@ -10,15 +10,12 @@ module Backup @@ -10,15 +10,12 @@ module Backup
10 Project.find_each(batch_size: 1000) do |project| 10 Project.find_each(batch_size: 1000) do |project|
11 print " * #{project.path_with_namespace} ... " 11 print " * #{project.path_with_namespace} ... "
12 12
13 - if project.empty_repo?  
14 - puts "[SKIPPED]".cyan  
15 - next  
16 - end  
17 -  
18 # Create namespace dir if missing 13 # Create namespace dir if missing
19 FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.path)) if project.namespace 14 FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.path)) if project.namespace
20 15
21 - if system(*%W(git --git-dir=#{path_to_repo(project)} bundle create #{path_to_bundle(project)} --all), silent) 16 + if project.empty_repo?
  17 + puts "[SKIPPED]".cyan
  18 + elsif system(*%W(git --git-dir=#{path_to_repo(project)} bundle create #{path_to_bundle(project)} --all), silent)
22 puts "[DONE]".green 19 puts "[DONE]".green
23 else 20 else
24 puts "[FAILED]".red 21 puts "[FAILED]".red
lib/gitlab/git_access.rb
@@ -61,18 +61,7 @@ module Gitlab @@ -61,18 +61,7 @@ module Gitlab
61 private 61 private
62 62
63 def user_allowed?(user) 63 def user_allowed?(user)
64 - return false if user.blocked?  
65 -  
66 - if Gitlab.config.ldap.enabled  
67 - if user.ldap_user?  
68 - # Check if LDAP user exists and match LDAP user_filter  
69 - unless Gitlab::LDAP::Access.new.allowed?(user)  
70 - return false  
71 - end  
72 - end  
73 - end  
74 -  
75 - true 64 + Gitlab::UserAccess.allowed?(user)
76 end 65 end
77 end 66 end
78 end 67 end
lib/gitlab/ldap/access.rb
@@ -14,7 +14,11 @@ module Gitlab @@ -14,7 +14,11 @@ module Gitlab
14 end 14 end
15 15
16 def allowed?(user) 16 def allowed?(user)
17 - !!Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter) 17 + if Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter)
  18 + !Gitlab::LDAP::Person.active_directory_disabled?(user.extern_uid, adapter)
  19 + else
  20 + false
  21 + end
18 rescue 22 rescue
19 false 23 false
20 end 24 end
lib/gitlab/ldap/adapter.rb
@@ -64,7 +64,7 @@ module Gitlab @@ -64,7 +64,7 @@ module Gitlab
64 end 64 end
65 end 65 end
66 66
67 - entries = ldap.search(options).select do |entry| 67 + entries = ldap_search(options).select do |entry|
68 entry.respond_to? config.uid 68 entry.respond_to? config.uid
69 end 69 end
70 70
@@ -77,6 +77,26 @@ module Gitlab @@ -77,6 +77,26 @@ module Gitlab
77 users(*args).first 77 users(*args).first
78 end 78 end
79 79
  80 + def dn_matches_filter?(dn, filter)
  81 + ldap_search(base: dn, filter: filter, scope: Net::LDAP::SearchScope_BaseObject, attributes: %w{dn}).any?
  82 + end
  83 +
  84 + def ldap_search(*args)
  85 + results = ldap.search(*args)
  86 +
  87 + if results.nil?
  88 + response = ldap.get_operation_result
  89 +
  90 + unless response.code.zero?
  91 + Rails.logger.warn("LDAP search error: #{response.message}")
  92 + end
  93 +
  94 + []
  95 + else
  96 + results
  97 + end
  98 + end
  99 +
80 private 100 private
81 101
82 def config 102 def config
lib/gitlab/ldap/person.rb
1 module Gitlab 1 module Gitlab
2 module LDAP 2 module LDAP
3 class Person 3 class Person
  4 + # Active Directory-specific LDAP filter that checks if bit 2 of the
  5 + # userAccountControl attribute is set.
  6 + # Source: http://ctogonewild.com/2009/09/03/bitmask-searches-in-ldap/
  7 + AD_USER_DISABLED = Net::LDAP::Filter.ex("userAccountControl:1.2.840.113556.1.4.803", "2")
  8 +
4 def self.find_by_uid(uid, adapter=nil) 9 def self.find_by_uid(uid, adapter=nil)
5 adapter ||= Gitlab::LDAP::Adapter.new 10 adapter ||= Gitlab::LDAP::Adapter.new
6 adapter.user(config.uid, uid) 11 adapter.user(config.uid, uid)
@@ -11,6 +16,11 @@ module Gitlab @@ -11,6 +16,11 @@ module Gitlab
11 adapter.user('dn', dn) 16 adapter.user('dn', dn)
12 end 17 end
13 18
  19 + def self.active_directory_disabled?(dn, adapter=nil)
  20 + adapter ||= Gitlab::LDAP::Adapter.new
  21 + adapter.dn_matches_filter?(dn, AD_USER_DISABLED)
  22 + end
  23 +
14 def initialize(entry) 24 def initialize(entry)
15 Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" } 25 Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
16 @entry = entry 26 @entry = entry
lib/gitlab/markdown.rb
@@ -98,6 +98,7 @@ module Gitlab @@ -98,6 +98,7 @@ module Gitlab
98 (?<prefix>\W)? # Prefix 98 (?<prefix>\W)? # Prefix
99 ( # Reference 99 ( # Reference
100 @(?<user>[a-zA-Z][a-zA-Z0-9_\-\.]*) # User name 100 @(?<user>[a-zA-Z][a-zA-Z0-9_\-\.]*) # User name
  101 + |(?<issue>([A-Z\-]+-)\d+) # JIRA Issue ID
101 |\#(?<issue>([a-zA-Z\-]+-)?\d+) # Issue ID 102 |\#(?<issue>([a-zA-Z\-]+-)?\d+) # Issue ID
102 |!(?<merge_request>\d+) # MR ID 103 |!(?<merge_request>\d+) # MR ID
103 |\$(?<snippet>\d+) # Snippet ID 104 |\$(?<snippet>\d+) # Snippet ID
@@ -172,11 +173,15 @@ module Gitlab @@ -172,11 +173,15 @@ module Gitlab
172 end 173 end
173 174
174 def reference_issue(identifier) 175 def reference_issue(identifier)
175 - if @project.issue_exists? identifier  
176 - url = url_for_issue(identifier)  
177 - title = title_for_issue(identifier) 176 + if @project.used_default_issues_tracker? || !external_issues_tracker_enabled?
  177 + if @project.issue_exists? identifier
  178 + url = url_for_issue(identifier)
  179 + title = title_for_issue(identifier)
178 180
179 - link_to("##{identifier}", url, html_options.merge(title: "Issue: #{title}", class: "gfm gfm-issue #{html_options[:class]}")) 181 + link_to("##{identifier}", url, html_options.merge(title: "Issue: #{title}", class: "gfm gfm-issue #{html_options[:class]}"))
  182 + end
  183 + else
  184 + reference_jira_issue(identifier) if @project.issues_tracker == "jira"
180 end 185 end
181 end 186 end
182 187
@@ -197,5 +202,12 @@ module Gitlab @@ -197,5 +202,12 @@ module Gitlab
197 link_to(identifier, project_commit_url(@project, commit), html_options.merge(title: commit.link_title, class: "gfm gfm-commit #{html_options[:class]}")) 202 link_to(identifier, project_commit_url(@project, commit), html_options.merge(title: commit.link_title, class: "gfm gfm-commit #{html_options[:class]}"))
198 end 203 end
199 end 204 end
  205 +
  206 + def reference_jira_issue(identifier)
  207 + url = url_for_issue(identifier)
  208 + title = Gitlab.config.issues_tracker[@project.issues_tracker]["title"]
  209 +
  210 + link_to("#{identifier}", url, html_options.merge(title: "Issue in #{title}", class: "gfm gfm-issue #{html_options[:class]}"))
  211 + end
200 end 212 end
201 end 213 end
lib/gitlab/oauth/user.rb
@@ -34,9 +34,11 @@ module Gitlab @@ -34,9 +34,11 @@ module Gitlab
34 # In this case we generate temporary email and force user to fill it later 34 # In this case we generate temporary email and force user to fill it later
35 if user.email.blank? 35 if user.email.blank?
36 user.generate_tmp_oauth_email 36 user.generate_tmp_oauth_email
37 - else 37 + elsif provider != "ldap"
38 # Google oauth returns email but dont return nickname 38 # Google oauth returns email but dont return nickname
39 # So we use part of email as username for new user 39 # So we use part of email as username for new user
  40 + # For LDAP, username is already set to the user's
  41 + # uid/userid/sAMAccountName.
40 user.username = email.match(/^[^@]*/)[0] 42 user.username = email.match(/^[^@]*/)[0]
41 end 43 end
42 44
@@ -65,7 +67,11 @@ module Gitlab @@ -65,7 +67,11 @@ module Gitlab
65 end 67 end
66 68
67 def name 69 def name
68 - auth.info.name.to_s.force_encoding("utf-8") 70 + if auth.info.name.nil?
  71 + "#{auth.info.first_name} #{auth.info.last_name}".force_encoding('utf-8')
  72 + else
  73 + auth.info.name.to_s.force_encoding('utf-8')
  74 + end
69 end 75 end
70 76
71 def username 77 def username
lib/gitlab/satellite/compare_action.rb 0 → 100644
@@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
  1 +module Gitlab
  2 + module Satellite
  3 + class CompareAction < Action
  4 + def initialize(user, target_project, target_branch, source_project, source_branch)
  5 + super user, target_project
  6 +
  7 + @target_project, @target_branch = target_project, target_branch
  8 + @source_project, @source_branch = source_project, source_branch
  9 + end
  10 +
  11 + # Only show what is new in the source branch compared to the target branch, not the other way around.
  12 + # The line below with merge_base is equivalent to diff with three dots (git diff branch1...branch2)
  13 + # From the git documentation: "git diff A...B" is equivalent to "git diff $(git-merge-base A B) B"
  14 + def diffs
  15 + in_locked_and_timed_satellite do |target_repo|
  16 + prepare_satellite!(target_repo)
  17 + update_satellite_source_and_target!(target_repo)
  18 + common_commit = target_repo.git.native(:merge_base, default_options, ["origin/#{@target_branch}", "source/#{@source_branch}"]).strip
  19 + #this method doesn't take default options
  20 + diffs = target_repo.diff(common_commit, "source/#{@source_branch}")
  21 + diffs = diffs.map { |diff| Gitlab::Git::Diff.new(diff) }
  22 + diffs
  23 + end
  24 + rescue Grit::Git::CommandFailed => ex
  25 + handle_exception(ex)
  26 + end
  27 +
  28 + # Retrieve an array of commits between the source and the target
  29 + def commits
  30 + in_locked_and_timed_satellite do |target_repo|
  31 + prepare_satellite!(target_repo)
  32 + update_satellite_source_and_target!(target_repo)
  33 + commits = target_repo.commits_between("origin/#{@target_branch}", "source/#{@source_branch}")
  34 + commits = commits.map { |commit| Gitlab::Git::Commit.new(commit, nil) }
  35 + commits
  36 + end
  37 + rescue Grit::Git::CommandFailed => ex
  38 + handle_exception(ex)
  39 + end
  40 +
  41 + private
  42 +
  43 + # Assumes a satellite exists that is a fresh clone of the projects repo, prepares satellite for diffs
  44 + def update_satellite_source_and_target!(target_repo)
  45 + target_repo.remote_add('source', @source_project.repository.path_to_repo)
  46 + target_repo.remote_fetch('source')
  47 + target_repo.git.checkout(default_options({b: true}), @target_branch, "origin/#{@target_branch}")
  48 + rescue Grit::Git::CommandFailed => ex
  49 + handle_exception(ex)
  50 + end
  51 + end
  52 + end
  53 +end
lib/gitlab/satellite/satellite.rb
@@ -84,6 +84,7 @@ module Gitlab @@ -84,6 +84,7 @@ module Gitlab
84 # Clear the working directory 84 # Clear the working directory
85 def clear_working_dir! 85 def clear_working_dir!
86 repo.git.reset(hard: true) 86 repo.git.reset(hard: true)
  87 + repo.git.clean(f: true, d: true, x: true)
87 end 88 end
88 89
89 # Deletes all branches except the parking branch 90 # Deletes all branches except the parking branch
lib/gitlab/user_access.rb 0 → 100644
@@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
  1 +module Gitlab
  2 + module UserAccess
  3 + def self.allowed?(user)
  4 + return false if user.blocked?
  5 +
  6 + if Gitlab.config.ldap.enabled
  7 + if user.ldap_user?
  8 + # Check if LDAP user exists and match LDAP user_filter
  9 + Gitlab::LDAP::Access.open do |adapter|
  10 + return false unless adapter.allowed?(user)
  11 + end
  12 + end
  13 + end
  14 +
  15 + true
  16 + end
  17 + end
  18 +end
lib/support/nginx/gitlab
@@ -59,6 +59,9 @@ server { @@ -59,6 +59,9 @@ server {
59 } 59 }
60 60
61 # Enable gzip compression as per rails guide: http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression 61 # Enable gzip compression as per rails guide: http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression
  62 + # WARNING: If you are using relative urls do remove the block below
  63 + # See config/application.rb under "Relative url support" for the list of
  64 + # other files that need to be changed for relative url support
62 location ~ ^/(assets)/ { 65 location ~ ^/(assets)/ {
63 root /home/git/gitlab/public; 66 root /home/git/gitlab/public;
64 gzip_static on; # to serve pre-gzipped version 67 gzip_static on; # to serve pre-gzipped version
@@ -67,4 +70,4 @@ server { @@ -67,4 +70,4 @@ server {
67 } 70 }
68 71
69 error_page 502 /502.html; 72 error_page 502 /502.html;
70 -}  
71 \ No newline at end of file 73 \ No newline at end of file
  74 +}
lib/tasks/gitlab/test.rake
@@ -8,9 +8,9 @@ namespace :gitlab do @@ -8,9 +8,9 @@ namespace :gitlab do
8 ] 8 ]
9 9
10 cmds.each do |cmd| 10 cmds.each do |cmd|
11 - system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd) 11 + result = system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd)
12 12
13 - raise "#{cmd} failed!" unless $?.exitstatus.zero? 13 + raise "#{cmd} failed!" unless result
14 end 14 end
15 end 15 end
16 end 16 end
spec/helpers/gitlab_markdown_helper_spec.rb
@@ -181,6 +181,52 @@ describe GitlabMarkdownHelper do @@ -181,6 +181,52 @@ describe GitlabMarkdownHelper do
181 include_examples 'referenced object' 181 include_examples 'referenced object'
182 end 182 end
183 183
  184 + describe "referencing a Jira issue" do
  185 + let(:actual) { "Reference to JIRA-#{issue.iid}" }
  186 + let(:expected) { "http://jira.example/browse/JIRA-#{issue.iid}" }
  187 + let(:reference) { "JIRA-#{issue.iid}" }
  188 +
  189 + before do
  190 + issue_tracker_config = { "jira" => { "title" => "JIRA tracker", "issues_url" => "http://jira.example/browse/:id" } }
  191 + Gitlab.config.stub(:issues_tracker).and_return(issue_tracker_config)
  192 + @project.stub(:issues_tracker).and_return("jira")
  193 + @project.stub(:issues_tracker_id).and_return("JIRA")
  194 + end
  195 +
  196 + it "should link using a valid id" do
  197 + gfm(actual).should match(expected)
  198 + end
  199 +
  200 + it "should link with adjacent text" do
  201 + # Wrap the reference in parenthesis
  202 + gfm(actual.gsub(reference, "(#{reference})")).should match(expected)
  203 +
  204 + # Append some text to the end of the reference
  205 + gfm(actual.gsub(reference, "#{reference}, right?")).should match(expected)
  206 + end
  207 +
  208 + it "should keep whitespace intact" do
  209 + actual = "Referenced #{reference} already."
  210 + expected = /Referenced <a.+>[^\s]+<\/a> already/
  211 + gfm(actual).should match(expected)
  212 + end
  213 +
  214 + it "should not link with an invalid id" do
  215 + # Modify the reference string so it's still parsed, but is invalid
  216 + invalid_reference = actual.gsub(/(\d+)$/, "r45")
  217 + gfm(invalid_reference).should == invalid_reference
  218 + end
  219 +
  220 + it "should include a title attribute" do
  221 + title = "Issue in JIRA tracker"
  222 + gfm(actual).should match(/title="#{title}"/)
  223 + end
  224 +
  225 + it "should include standard gfm classes" do
  226 + gfm(actual).should match(/class="\s?gfm gfm-issue\s?"/)
  227 + end
  228 + end
  229 +
184 describe "referencing a merge request" do 230 describe "referencing a merge request" do
185 let(:object) { merge_request } 231 let(:object) { merge_request }
186 let(:reference) { "!#{merge_request.iid}" } 232 let(:reference) { "!#{merge_request.iid}" }
spec/lib/gitlab/ldap/ldap_access_spec.rb 0 → 100644
@@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Gitlab::LDAP::Access do
  4 + let(:access) { Gitlab::LDAP::Access.new }
  5 + let(:user) { create(:user) }
  6 +
  7 + describe :allowed? do
  8 + subject { access.allowed?(user) }
  9 +
  10 + context 'when the user cannot be found' do
  11 + before { Gitlab::LDAP::Person.stub(find_by_dn: nil) }
  12 +
  13 + it { should be_false }
  14 + end
  15 +
  16 + context 'when the user is found' do
  17 + before { Gitlab::LDAP::Person.stub(find_by_dn: :ldap_user) }
  18 +
  19 + context 'and the Active Directory disabled flag is set' do
  20 + before { Gitlab::LDAP::Person.stub(active_directory_disabled?: true) }
  21 +
  22 + it { should be_false }
  23 + end
  24 +
  25 + context 'and the Active Directory disabled flag is not set' do
  26 + before { Gitlab::LDAP::Person.stub(active_directory_disabled?: false) }
  27 +
  28 + it { should be_true }
  29 + end
  30 + end
  31 + end
  32 +end
spec/lib/gitlab/ldap/ldap_adapter_spec.rb 0 → 100644
@@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Gitlab::LDAP::Adapter do
  4 + let(:adapter) { Gitlab::LDAP::Adapter.new }
  5 +
  6 + describe :dn_matches_filter? do
  7 + let(:ldap) { double(:ldap) }
  8 + subject { adapter.dn_matches_filter?(:dn, :filter) }
  9 + before { adapter.stub(ldap: ldap) }
  10 +
  11 + context "when the search is successful" do
  12 + context "and the result is non-empty" do
  13 + before { ldap.stub(search: [:foo]) }
  14 +
  15 + it { should be_true }
  16 + end
  17 +
  18 + context "and the result is empty" do
  19 + before { ldap.stub(search: []) }
  20 +
  21 + it { should be_false }
  22 + end
  23 + end
  24 +
  25 + context "when the search encounters an error" do
  26 + before { ldap.stub(search: nil, get_operation_result: double(code: 1, message: 'some error')) }
  27 +
  28 + it { should be_false }
  29 + end
  30 + end
  31 +end
spec/lib/gitlab/reference_extractor_spec.rb
@@ -11,6 +11,12 @@ describe Gitlab::ReferenceExtractor do @@ -11,6 +11,12 @@ describe Gitlab::ReferenceExtractor do
11 subject.issues.should == ["1234"] 11 subject.issues.should == ["1234"]
12 end 12 end
13 13
  14 + it 'extracts JIRA issue references' do
  15 + Gitlab.config.gitlab.stub(:issues_tracker).and_return("jira")
  16 + subject.analyze "this one talks about issue JIRA-1234"
  17 + subject.issues.should == ["JIRA-1234"]
  18 + end
  19 +
14 it 'extracts merge request references' do 20 it 'extracts merge request references' do
15 subject.analyze "and here's !43, a merge request" 21 subject.analyze "and here's !43, a merge request"
16 subject.merge_requests.should == ["43"] 22 subject.merge_requests.should == ["43"]
spec/mailers/notify_spec.rb
@@ -161,6 +161,10 @@ describe Notify do @@ -161,6 +161,10 @@ describe Notify do
161 it 'contains a link to the new issue' do 161 it 'contains a link to the new issue' do
162 should have_body_text /#{project_issue_path project, issue}/ 162 should have_body_text /#{project_issue_path project, issue}/
163 end 163 end
  164 +
  165 + it 'has the correct message-id set' do
  166 + should have_header 'Message-ID', "<issue_#{issue.id}@#{Gitlab.config.gitlab.host}>"
  167 + end
164 end 168 end
165 169
166 describe 'that are new with a description' do 170 describe 'that are new with a description' do
@@ -197,6 +201,10 @@ describe Notify do @@ -197,6 +201,10 @@ describe Notify do
197 it 'contains a link to the issue' do 201 it 'contains a link to the issue' do
198 should have_body_text /#{project_issue_path project, issue}/ 202 should have_body_text /#{project_issue_path project, issue}/
199 end 203 end
  204 +
  205 + it 'has the correct reference set' do
  206 + should have_header 'References', "<issue_#{issue.id}@#{Gitlab.config.gitlab.host}>"
  207 + end
200 end 208 end
201 209
202 describe 'status changed' do 210 describe 'status changed' do
@@ -224,6 +232,10 @@ describe Notify do @@ -224,6 +232,10 @@ describe Notify do
224 it 'contains a link to the issue' do 232 it 'contains a link to the issue' do
225 should have_body_text /#{project_issue_path project, issue}/ 233 should have_body_text /#{project_issue_path project, issue}/
226 end 234 end
  235 +
  236 + it 'has the correct reference set' do
  237 + should have_header 'References', "<issue_#{issue.id}@#{Gitlab.config.gitlab.host}>"
  238 + end
227 end 239 end
228 240
229 end 241 end
@@ -239,7 +251,7 @@ describe Notify do @@ -239,7 +251,7 @@ describe Notify do
239 it_behaves_like 'an assignee email' 251 it_behaves_like 'an assignee email'
240 252
241 it 'has the correct subject' do 253 it 'has the correct subject' do
242 - should have_subject /#{merge_request.title} \(!#{merge_request.iid}\)/ 254 + should have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
243 end 255 end
244 256
245 it 'contains a link to the new merge request' do 257 it 'contains a link to the new merge request' do
@@ -253,6 +265,10 @@ describe Notify do @@ -253,6 +265,10 @@ describe Notify do
253 it 'contains the target branch for the merge request' do 265 it 'contains the target branch for the merge request' do
254 should have_body_text /#{merge_request.target_branch}/ 266 should have_body_text /#{merge_request.target_branch}/
255 end 267 end
  268 +
  269 + it 'has the correct message-id set' do
  270 + should have_header 'Message-ID', "<merge_request_#{merge_request.id}@#{Gitlab.config.gitlab.host}>"
  271 + end
256 end 272 end
257 273
258 describe 'that are new with a description' do 274 describe 'that are new with a description' do
@@ -275,7 +291,7 @@ describe Notify do @@ -275,7 +291,7 @@ describe Notify do
275 end 291 end
276 292
277 it 'has the correct subject' do 293 it 'has the correct subject' do
278 - should have_subject /#{merge_request.title} \(!#{merge_request.iid}\)/ 294 + should have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
279 end 295 end
280 296
281 it 'contains the name of the previous assignee' do 297 it 'contains the name of the previous assignee' do
@@ -303,7 +319,7 @@ describe Notify do @@ -303,7 +319,7 @@ describe Notify do
303 end 319 end
304 320
305 it 'has the correct subject' do 321 it 'has the correct subject' do
306 - should have_subject /#{merge_request.title} \(!#{merge_request.iid}\)/ 322 + should have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
307 end 323 end
308 324
309 it 'contains the new status' do 325 it 'contains the new status' do
@@ -313,6 +329,10 @@ describe Notify do @@ -313,6 +329,10 @@ describe Notify do
313 it 'contains a link to the merge request' do 329 it 'contains a link to the merge request' do
314 should have_body_text /#{project_merge_request_path project, merge_request}/ 330 should have_body_text /#{project_merge_request_path project, merge_request}/
315 end 331 end
  332 +
  333 + it 'has the correct reference set' do
  334 + should have_header 'References', "<merge_request_#{merge_request.id}@#{Gitlab.config.gitlab.host}>"
  335 + end
316 end 336 end
317 end 337 end
318 end 338 end
@@ -426,7 +446,7 @@ describe Notify do @@ -426,7 +446,7 @@ describe Notify do
426 it_behaves_like 'a note email' 446 it_behaves_like 'a note email'
427 447
428 it 'has the correct subject' do 448 it 'has the correct subject' do
429 - should have_subject /#{merge_request.title} \(!#{merge_request.iid}\)/ 449 + should have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
430 end 450 end
431 451
432 it 'contains a link to the merge request note' do 452 it 'contains a link to the merge request note' do
spec/requests/api/api_helpers_spec.rb
@@ -39,6 +39,17 @@ describe API, api: true do @@ -39,6 +39,17 @@ describe API, api: true do
39 end 39 end
40 40
41 describe ".current_user" do 41 describe ".current_user" do
  42 + it "should return nil for an invalid token" do
  43 + env[API::APIHelpers::PRIVATE_TOKEN_HEADER] = 'invalid token'
  44 + current_user.should be_nil
  45 + end
  46 +
  47 + it "should return nil for a user without access" do
  48 + env[API::APIHelpers::PRIVATE_TOKEN_HEADER] = user.private_token
  49 + Gitlab::UserAccess.stub(allowed?: false)
  50 + current_user.should be_nil
  51 + end
  52 +
42 it "should leave user as is when sudo not specified" do 53 it "should leave user as is when sudo not specified" do
43 env[API::APIHelpers::PRIVATE_TOKEN_HEADER] = user.private_token 54 env[API::APIHelpers::PRIVATE_TOKEN_HEADER] = user.private_token
44 current_user.should == user 55 current_user.should == user
spec/requests/api/merge_requests_spec.rb
@@ -183,11 +183,33 @@ describe API::API, api: true do @@ -183,11 +183,33 @@ describe API::API, api: true do
183 end 183 end
184 end 184 end
185 185
186 - describe "PUT /projects/:id/merge_request/:merge_request_id to merge MR" do  
187 - it "should return merge_request" do  
188 - put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), state_event: "merge" 186 + describe "PUT /projects/:id/merge_request/:merge_request_id/merge" do
  187 + it "should return merge_request in case of success" do
  188 + MergeRequest.any_instance.stub(can_be_merged?: true, automerge!: true)
  189 + put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
189 response.status.should == 200 190 response.status.should == 200
190 - json_response['state'].should == 'merged' 191 + end
  192 +
  193 + it "should return 405 if branch can't be merged" do
  194 + MergeRequest.any_instance.stub(can_be_merged?: false)
  195 + put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
  196 + response.status.should == 405
  197 + json_response['message'].should == 'Branch cannot be merged'
  198 + end
  199 +
  200 + it "should return 405 if merge_request is not open" do
  201 + merge_request.close
  202 + put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
  203 + response.status.should == 405
  204 + json_response['message'].should == 'Method Not Allowed'
  205 + end
  206 +
  207 + it "should return 401 if user has no permissions to merge" do
  208 + user2 = create(:user)
  209 + project.team << [user2, :reporter]
  210 + put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user2)
  211 + response.status.should == 401
  212 + json_response['message'].should == '401 Unauthorized'
191 end 213 end
192 end 214 end
193 215
spec/requests/api/projects_spec.rb
@@ -14,6 +14,12 @@ describe API::API, api: true do @@ -14,6 +14,12 @@ describe API::API, api: true do
14 let(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) } 14 let(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) }
15 let(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) } 15 let(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) }
16 let(:issue_with_labels) { create(:issue, author: user, assignee: user, project: project, :label_list => "label1, label2") } 16 let(:issue_with_labels) { create(:issue, author: user, assignee: user, project: project, :label_list => "label1, label2") }
  17 + let(:merge_request_with_labels) do
  18 + create(:merge_request, :simple, author: user, assignee: user,
  19 + source_project: project, target_project: project, title: 'Test',
  20 + label_list: 'label3, label4')
  21 + end
  22 +
17 23
18 describe "GET /projects" do 24 describe "GET /projects" do
19 before { project } 25 before { project }
@@ -634,15 +640,45 @@ describe API::API, api: true do @@ -634,15 +640,45 @@ describe API::API, api: true do
634 end 640 end
635 end 641 end
636 642
637 - describe "GET /projects/:id/labels" do  
638 - before { issue_with_labels } 643 + describe 'GET /projects/:id/labels' do
  644 + context 'with an issue' do
  645 + before { issue_with_labels }
639 646
640 - it "should return project labels" do  
641 - get api("/projects/#{project.id}/labels", user)  
642 - response.status.should == 200  
643 - json_response.should be_an Array  
644 - json_response.first['name'].should == issue_with_labels.labels.first.name  
645 - json_response.last['name'].should == issue_with_labels.labels.last.name 647 + it 'should return project labels' do
  648 + get api("/projects/#{project.id}/labels", user)
  649 + response.status.should == 200
  650 + json_response.should be_an Array
  651 + json_response.first['name'].should == issue_with_labels.labels.first.name
  652 + json_response.last['name'].should == issue_with_labels.labels.last.name
  653 + end
  654 + end
  655 +
  656 + context 'with a merge request' do
  657 + before { merge_request_with_labels }
  658 +
  659 + it 'should return project labels' do
  660 + get api("/projects/#{project.id}/labels", user)
  661 + response.status.should == 200
  662 + json_response.should be_an Array
  663 + json_response.first['name'].should == merge_request_with_labels.labels.first.name
  664 + json_response.last['name'].should == merge_request_with_labels.labels.last.name
  665 + end
  666 + end
  667 +
  668 + context 'with an issue and a merge request' do
  669 + before do
  670 + issue_with_labels
  671 + merge_request_with_labels
  672 + end
  673 +
  674 + it 'should return project labels from both' do
  675 + get api("/projects/#{project.id}/labels", user)
  676 + response.status.should == 200
  677 + json_response.should be_an Array
  678 + all_labels = issue_with_labels.labels.map(&:name).to_a
  679 + .concat(merge_request_with_labels.labels.map(&:name).to_a)
  680 + json_response.map { |e| e['name'] }.should =~ all_labels
  681 + end
646 end 682 end
647 end 683 end
648 end 684 end
spec/routing/project_routing_spec.rb
@@ -213,7 +213,7 @@ describe Projects::RefsController, &quot;routing&quot; do @@ -213,7 +213,7 @@ describe Projects::RefsController, &quot;routing&quot; do
213 end 213 end
214 214
215 # diffs_project_merge_request GET /:project_id/merge_requests/:id/diffs(.:format) projects/merge_requests#diffs 215 # diffs_project_merge_request GET /:project_id/merge_requests/:id/diffs(.:format) projects/merge_requests#diffs
216 -# automerge_project_merge_request GET /:project_id/merge_requests/:id/automerge(.:format) projects/merge_requests#automerge 216 +# automerge_project_merge_request POST /:project_id/merge_requests/:id/automerge(.:format) projects/merge_requests#automerge
217 # automerge_check_project_merge_request GET /:project_id/merge_requests/:id/automerge_check(.:format) projects/merge_requests#automerge_check 217 # automerge_check_project_merge_request GET /:project_id/merge_requests/:id/automerge_check(.:format) projects/merge_requests#automerge_check
218 # branch_from_project_merge_requests GET /:project_id/merge_requests/branch_from(.:format) projects/merge_requests#branch_from 218 # branch_from_project_merge_requests GET /:project_id/merge_requests/branch_from(.:format) projects/merge_requests#branch_from
219 # branch_to_project_merge_requests GET /:project_id/merge_requests/branch_to(.:format) projects/merge_requests#branch_to 219 # branch_to_project_merge_requests GET /:project_id/merge_requests/branch_to(.:format) projects/merge_requests#branch_to
@@ -230,7 +230,10 @@ describe Projects::MergeRequestsController, &quot;routing&quot; do @@ -230,7 +230,10 @@ describe Projects::MergeRequestsController, &quot;routing&quot; do
230 end 230 end
231 231
232 it "to #automerge" do 232 it "to #automerge" do
233 - get("/gitlab/gitlabhq/merge_requests/1/automerge").should route_to('projects/merge_requests#automerge', project_id: 'gitlab/gitlabhq', id: '1') 233 + post('/gitlab/gitlabhq/merge_requests/1/automerge').should route_to(
  234 + 'projects/merge_requests#automerge',
  235 + project_id: 'gitlab/gitlabhq', id: '1'
  236 + )
234 end 237 end
235 238
236 it "to #automerge_check" do 239 it "to #automerge_check" do
spec/services/system_hooks_service_spec.rb
@@ -8,10 +8,10 @@ describe SystemHooksService do @@ -8,10 +8,10 @@ describe SystemHooksService do
8 context 'event data' do 8 context 'event data' do
9 it { event_data(user, :create).should include(:event_name, :name, :created_at, :email, :user_id) } 9 it { event_data(user, :create).should include(:event_name, :name, :created_at, :email, :user_id) }
10 it { event_data(user, :destroy).should include(:event_name, :name, :created_at, :email, :user_id) } 10 it { event_data(user, :destroy).should include(:event_name, :name, :created_at, :email, :user_id) }
11 - it { event_data(project, :create).should include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email) }  
12 - it { event_data(project, :destroy).should include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email) }  
13 - it { event_data(users_project, :create).should include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :project_access) }  
14 - it { event_data(users_project, :destroy).should include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :project_access) } 11 + it { event_data(project, :create).should include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
  12 + it { event_data(project, :destroy).should include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
  13 + it { event_data(users_project, :create).should include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :project_access, :project_visibility) }
  14 + it { event_data(users_project, :destroy).should include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :project_access, :project_visibility) }
15 end 15 end
16 16
17 context 'event names' do 17 context 'event names' do