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 @@
  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
... ...
CHANGELOG
... ... @@ -8,6 +8,16 @@ v 6.9.0
8 8 - Fix syntax highlighting for code comments blocks
9 9 - Improve comments loading logic
10 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 22 v 6.8.0
13 23 - Ability to at mention users that are participating in issue and merge req. discussion
... ...
Gemfile
... ... @@ -152,7 +152,7 @@ gem "rack-attack"
152 152 # Ace editor
153 153 gem 'ace-rails-ap'
154 154  
155   -gem "sass-rails"
  155 +gem "sass-rails", '~> 4.0.2'
156 156 gem "coffee-rails"
157 157 gem "uglifier"
158 158 gem "therubyracer"
... ...
Gemfile.lock
... ... @@ -2,26 +2,26 @@ GEM
2 2 remote: https://rubygems.org/
3 3 specs:
4 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 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 10 builder (~> 3.1.0)
11 11 erubis (~> 2.7.0)
12 12 rack (~> 1.5.2)
13 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 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 19 activerecord-deprecated_finders (~> 1.0.2)
20   - activesupport (= 4.0.3)
  20 + activesupport (= 4.0.5)
21 21 arel (~> 4.0.0)
22 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 25 minitest (~> 4.2)
26 26 multi_json (~> 1.3)
27 27 thread_safe (~> 0.1)
... ... @@ -162,7 +162,7 @@ GEM
162 162 multi_json
163 163 gitlab-grack (2.0.0.pre)
164 164 rack (~> 1.5.1)
165   - gitlab-grit (2.6.5)
  165 + gitlab-grit (2.6.7)
166 166 charlock_holmes (~> 0.6)
167 167 diff-lcs (~> 1.1)
168 168 mime-types (~> 1.15)
... ... @@ -279,7 +279,7 @@ GEM
279 279 mime-types (1.25.1)
280 280 mini_portile (0.5.3)
281 281 minitest (4.7.5)
282   - multi_json (1.9.3)
  282 + multi_json (1.10.0)
283 283 multi_xml (0.5.5)
284 284 multipart-post (1.2.0)
285 285 mysql2 (0.3.11)
... ... @@ -349,13 +349,13 @@ GEM
349 349 rack
350 350 rack-test (0.6.2)
351 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 357 bundler (>= 1.3.0, < 2.0)
358   - railties (= 4.0.3)
  358 + railties (= 4.0.5)
359 359 sprockets-rails (~> 2.0.0)
360 360 rails-observers (0.1.2)
361 361 activemodel (~> 4.0)
... ... @@ -368,9 +368,9 @@ GEM
368 368 i18n
369 369 require_all
370 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 374 rake (>= 0.8.7)
375 375 thor (>= 0.18.1, < 2.0)
376 376 raindrops (0.12.0)
... ... @@ -427,11 +427,12 @@ GEM
427 427 safe_yaml (0.9.7)
428 428 sanitize (2.1.0)
429 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 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 436 sdoc (0.3.20)
436 437 json (>= 1.1.3)
437 438 rdoc (~> 3.10)
... ... @@ -478,7 +479,7 @@ GEM
478 479 spring (>= 0.9.1)
479 480 spring-commands-spinach (1.0.0)
480 481 spring (>= 0.9.1)
481   - sprockets (2.10.1)
  482 + sprockets (2.11.0)
482 483 hike (~> 1.2)
483 484 multi_json (~> 1.0)
484 485 rack (~> 1.0)
... ... @@ -636,7 +637,7 @@ DEPENDENCIES
636 637 redis-rails
637 638 rspec-rails
638 639 sanitize (~> 2.0)
639   - sass-rails
  640 + sass-rails (~> 4.0.2)
640 641 sdoc
641 642 seed-fu
642 643 select2-rails
... ...
README.md
... ... @@ -15,7 +15,7 @@
15 15  
16 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 20 ### Code status
21 21  
... ... @@ -25,6 +25,8 @@
25 25  
26 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 30 ### Resources
29 31  
30 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 35  
34 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 40 ### Requirements
39 41  
... ... @@ -51,7 +53,7 @@
51 53  
52 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 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 63  
62 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 68 #### Unofficial installation methods
65 69  
66 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.
... ...
VERSION
1   -6.9.0.pre
  1 +6.9.0.rc1
... ...
app/assets/javascripts/notes.js.coffee
... ... @@ -53,6 +53,12 @@ class Notes
53 53 # fetch notes when tab becomes visible
54 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 62 cleanBinding: ->
57 63 $(document).off "ajax:success", ".js-main-target-form"
58 64 $(document).off "ajax:success", ".js-discussion-note-form"
... ... @@ -67,6 +73,7 @@ class Notes
67 73 $(document).off "click", ".js-discussion-reply-button"
68 74 $(document).off "click", ".js-add-diff-note-button"
69 75 $(document).off "visibilitychange"
  76 + $(document).off "keypress", @notes_forms
70 77  
71 78  
72 79 initRefresh: ->
... ...
app/assets/javascripts/project_users_select.js.coffee
1 1 @projectUsersSelect =
2 2 init: ->
3 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 6 $(select).select2
7 7 placeholder: $(select).data('placeholder') || "Search for a user"
... ...
app/assets/stylesheets/generic/files.scss
... ... @@ -11,14 +11,11 @@
11 11 }
12 12  
13 13 .file-title {
14   - background: #DDD;
  14 + background: #EEE;
15 15 border-bottom: 1px solid #CCC;
16 16 text-shadow: 0 1px 1px #fff;
17 17 margin: 0;
18   - font-weight: normal;
19   - font-weight: bold;
20 18 text-align: left;
21   - color: $style_color;
22 19 padding: 9px 10px;
23 20  
24 21 .options {
... ... @@ -31,12 +28,15 @@
31 28 }
32 29  
33 30 .file_name {
34   - color: $style_color;
  31 + font-weight: bold;
  32 + padding-left: 3px;
35 33 font-size: 14px;
36   - text-shadow: 0 1px 1px #fff;
  34 +
37 35 small {
38   - color: #999;
  36 + color: #888;
39 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 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 12 margin:20px 0;
13 13 background: #FFF;
14 14 border: 1px solid #EEE;
  15 + @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05));
15 16  
16 17 &.issue-box-closed {
17   - border-color: #DA4E49;
  18 + border-color: $border_danger;
18 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 23 .state-label {
23   - background: #DA4E49;
  24 + background-color: $bg_danger;
24 25 color: #FFF;
25 26 }
26 27 }
27 28 }
28 29  
29 30 &.issue-box-merged {
30   - border-color: #31708f;
  31 + border-color: $border_primary;
31 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 36 .state-label {
36   - background: #31708f;
  37 + background-color: $bg_primary;
37 38 color: #FFF;
38 39 }
39 40 }
40 41 }
41 42  
42 43 &.issue-box-open {
43   - border-color: #4A4;
  44 + border-color: $border_success;
44 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 49 .state-label {
49   - background: #4A4;
  50 + background-color: $bg_success;
50 51 color: #FFF;
51 52 }
52 53 }
... ... @@ -70,7 +71,6 @@
70 71 }
71 72  
72 73 .state {
73   - height: 34px;
74 74 border-bottom: 1px solid #DDD;
75 75 line-height: 32px;
76 76 }
... ... @@ -89,6 +89,18 @@
89 89 border: none;
90 90 border-top: 1px solid #eee;
91 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 106 .description {
... ... @@ -106,7 +118,11 @@
106 118 padding: 1px 25px;
107 119 text-align: center;
108 120 text-shadow: none;
109   - margin-right: 20px;
110 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 8 width: 270px;
9 9  
10 10 .ui-datepicker-header {
11   - background: #EEE;
  11 + background: #FFF;
12 12 border-color: #DDD;
13 13 }
14 14  
... ... @@ -19,20 +19,37 @@
19 19 }
20 20  
21 21 &.ui-autocomplete {
22   - @include border-radius(0px);
23 22 border-color: #DDD;
24 23 padding: 0;
  24 + margin-top: 2px;
  25 + z-index: 1001;
25 26  
26 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 47 text-decoration: underline;
48 48 }
49 49  
50   - &.dark {
  50 + &.darken {
51 51 color: $style_color;
52 52 }
53 53  
... ...
app/assets/stylesheets/main/mixins.scss
... ... @@ -41,31 +41,6 @@
41 41 * Prefilled mixins
42 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 45 @mixin shade {
71 46 @include box-shadow(0 0 3px #ddd);
... ... @@ -77,7 +52,6 @@
77 52  
78 53 @mixin header-font {
79 54 color: $style_color;
80   - text-shadow: 0 1px 1px #FFF;
81 55 font-size: 16px;
82 56 line-height: 44px;
83 57 font-weight: normal;
... ...
app/assets/stylesheets/main/variables.scss
... ... @@ -8,6 +8,31 @@ $bg_style_color: #2299BB;
8 8 $list-group-active-bg: $bg_style_color;
9 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 37 * Commit Diff Colors
13 38 */
... ...
app/assets/stylesheets/sections/diff.scss
... ... @@ -4,7 +4,7 @@
4 4  
5 5 .diff-header {
6 6 @extend .clearfix;
7   - background: #DDD;
  7 + background: #EEE;
8 8 border-bottom: 1px solid #CCC;
9 9 padding: 5px 5px 5px 10px;
10 10 color: #555;
... ... @@ -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 76 .old_line, .new_line, .diff_line {
86 77 margin: 0px;
87 78 padding: 0px;
88 79 border: none;
89   - background: #EEE;
  80 + background: #F5F5F5;
90 81 color: #666;
91 82 padding: 0px 5px;
92 83 border-right: 1px solid #ccc;
... ... @@ -304,15 +295,9 @@
304 295 } //.view.onion-skin
305 296 }
306 297 .view-modes{
307   -
308 298 padding: 10px;
309 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 302 ul, li{
318 303 list-style: none;
... ...
app/assets/stylesheets/sections/graph.scss
1 1 .project-network {
2   - border: 1px solid #aaa;
3   - padding: 1px;
  2 + border: 1px solid #CCC;
4 3  
5 4 .tip {
6 5 color: #888;
7 6 font-size: 14px;
8 7 padding: 10px;
9 8 border-bottom: 1px solid #bbb;
10   - @include bg-gray-gradient;
  9 + background: #EEE;
11 10 }
12 11  
13 12 .network-graph {
14   - background: #f1f1f1;
  13 + background: #FFF;
15 14 height: 500px;
16 15 overflow-y: scroll;
17 16 overflow-x: hidden;
... ...
app/assets/stylesheets/sections/header.scss
... ... @@ -14,7 +14,6 @@ header {
14 14  
15 15 .nav > li > a {
16 16 color: $style_color;
17   - text-shadow: 0 1px 0 #fff;
18 17 font-size: 14px;
19 18 line-height: 32px;
20 19 padding: 6px 10px;
... ... @@ -190,7 +189,6 @@ header {
190 189  
191 190 .nav > li > a {
192 191 color: #AAA;
193   - text-shadow: 0 1px 0 #444;
194 192  
195 193 &:hover, &:focus, &:active {
196 194 background: none;
... ... @@ -224,7 +222,6 @@ header {
224 222 background: image-url('logo-white.png') no-repeat center center;
225 223 background-size: 32px;
226 224 color: #fff;
227   - text-shadow: 0 1px 1px #444;
228 225 }
229 226 }
230 227 }
... ... @@ -236,7 +233,6 @@ header {
236 233 }
237 234 }
238 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 45 padding: 6px 10px;
46 46 border: 1px solid #ccc;
47 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 50 .issues_content {
... ... @@ -143,3 +135,36 @@ form.edit-issue {
143 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 31  
32 32 .mr_source_commit,
33 33 .mr_target_commit {
34   - margin-top: 10px;
35 34 .commit {
36 35 margin: 0;
37 36 padding: 2px 0;
... ... @@ -74,6 +73,10 @@
74 73  
75 74 .merge-request-info {
76 75 color: #999;
  76 +
  77 + .merge-request-labels {
  78 + display: inline-block;
  79 + }
77 80 }
78 81 }
79 82 }
... ... @@ -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 139 background-color: #fff;
140 140 border-width: 1px 0;
141 141 padding-top: 0;
  142 + vertical-align: top;
142 143  
143 144 li {
144 145 padding: 5px;
... ...
app/assets/stylesheets/sections/profile.scss
... ... @@ -76,7 +76,7 @@
76 76 }
77 77  
78 78 &.modern {
79   - background: #345;
  79 + background: #009871;
80 80 }
81 81  
82 82 &.gray {
... ... @@ -84,7 +84,7 @@
84 84 }
85 85  
86 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 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 40 .votes-holder {
41 41 float: right;
42 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 16 @extend .header-dark;
17 17 &.navbar-gitlab {
18 18 .navbar-inner {
19   - background: #547;
20   - border-bottom: 1px solid #435;
  19 + background: #548;
  20 + border-bottom: 1px solid #436;
21 21 .app_logo, .navbar-toggle {
22 22 &:hover {
23   - background-color: #435;
  23 + background-color: #436;
24 24 }
25 25 }
26 26 .separator {
27   - background: #435;
28   - border-left: 1px solid #658;
  27 + background: #436;
  28 + border-left: 1px solid #659;
29 29 }
30 30 .nav > li > a {
31   - color: #98B;
  31 + color: #98C;
32 32 }
33 33 .search-input {
34   - border-color: #98B;
  34 + border-color: #98C;
35 35 }
36 36 }
37 37 }
38 38 }
39 39  
40 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 16 @extend .header-dark;
17 17 &.navbar-gitlab {
18 18 .navbar-inner {
19   - background: #345;
20   - border-bottom: 1px solid #234;
  19 + background: #00AC7E;
  20 + border-bottom: 1px solid #00AC7E;
21 21 .app_logo, .navbar-toggle {
22 22 &:hover {
23   - background-color: #234;
  23 + background-color: #009C6E;
24 24 }
25 25 }
26 26 .separator {
27   - background: #234;
28   - border-left: 1px solid #456;
  27 + background: #009C6F;
  28 + border-left: 1px solid #10BC8E;
29 29 }
30 30 .nav > li > a {
31   - color: #89A;
  31 + color: #ADC;
32 32 }
33 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 117 return access_denied! unless can?(current_user, :push_code, project)
118 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 125 def access_denied!
121 126 render "errors/access_denied", layout: "errors", status: 404
122 127 end
... ...
app/controllers/groups_controller.rb
... ... @@ -68,7 +68,7 @@ class GroupsController &lt; ApplicationController
68 68 @members = group.users_groups
69 69  
70 70 if params[:search].present?
71   - users = group.users.search(params[:search])
  71 + users = group.users.search(params[:search]).to_a
72 72 @members = @members.where(user_id: users)
73 73 end
74 74  
... ...
app/controllers/projects/labels_controller.rb
1 1 class Projects::LabelsController < Projects::ApplicationController
2 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 6 respond_to :js, :html
8 7  
... ... @@ -13,12 +12,18 @@ class Projects::LabelsController &lt; Projects::ApplicationController
13 12 def generate
14 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 20 end
18 21  
19 22 protected
20 23  
21 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 28 end
24 29 end
... ...
app/controllers/projects/merge_requests_controller.rb
... ... @@ -62,11 +62,27 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController
62 62 @merge_request.source_project = @project unless @merge_request.source_project
63 63 @merge_request.target_project ||= (@project.forked_from_project || @project)
64 64 @target_branches = @merge_request.target_project.nil? ? [] : @merge_request.target_project.repository.branch_names
65   -
66 65 @merge_request.target_branch ||= @merge_request.target_project.default_branch
67   -
68 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 86 end
71 87  
72 88 def edit
... ... @@ -80,7 +96,7 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController
80 96 @merge_request = MergeRequests::CreateService.new(project, current_user, params[:merge_request]).execute
81 97  
82 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 100 else
85 101 @source_project = @merge_request.source_project
86 102 @target_project = @merge_request.target_project
... ...
app/controllers/projects/wikis_controller.rb
... ... @@ -12,9 +12,22 @@ class Projects::WikisController &lt; Projects::ApplicationController
12 12  
13 13 def show
14 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 18 if @page
17 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 31 else
19 32 return render('empty') unless can?(current_user, :write_wiki, @project)
20 33 @page = WikiPage.new(@project_wiki)
... ...
app/helpers/commits_helper.rb
... ... @@ -117,7 +117,7 @@ module CommitsHelper
117 117 added_lines[line_new] = { line_code: line_code, type: type, line: line }
118 118 end
119 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 122 offset1 = 0
123 123 offset2 = 0
... ...
app/helpers/issues_helper.rb
... ... @@ -82,7 +82,7 @@ module IssuesHelper
82 82 end
83 83  
84 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 86 end
87 87  
88 88 def issue_box_class(item)
... ...
app/helpers/selects_helper.rb
... ... @@ -14,7 +14,7 @@ module SelectsHelper
14 14 css_class << (opts[:class] || '')
15 15 value = opts[:selected] || ''
16 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 19 end
20 20 end
... ...
app/mailers/emails/issues.rb
... ... @@ -4,6 +4,7 @@ module Emails
4 4 @issue = Issue.find(issue_id)
5 5 @project = @issue.project
6 6 @target_url = project_issue_url(@project, @issue)
  7 + set_message_id("issue_#{issue_id}")
7 8 mail(from: sender(@issue.author_id),
8 9 to: recipient(recipient_id),
9 10 subject: subject("#{@issue.title} (##{@issue.iid})"))
... ... @@ -14,6 +15,7 @@ module Emails
14 15 @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
15 16 @project = @issue.project
16 17 @target_url = project_issue_url(@project, @issue)
  18 + set_reference("issue_#{issue_id}")
17 19 mail(from: sender(updated_by_user_id),
18 20 to: recipient(recipient_id),
19 21 subject: subject("#{@issue.title} (##{@issue.iid})"))
... ... @@ -24,6 +26,7 @@ module Emails
24 26 @project = @issue.project
25 27 @updated_by = User.find updated_by_user_id
26 28 @target_url = project_issue_url(@project, @issue)
  29 + set_reference("issue_#{issue_id}")
27 30 mail(from: sender(updated_by_user_id),
28 31 to: recipient(recipient_id),
29 32 subject: subject("#{@issue.title} (##{@issue.iid})"))
... ... @@ -35,6 +38,7 @@ module Emails
35 38 @project = @issue.project
36 39 @updated_by = User.find updated_by_user_id
37 40 @target_url = project_issue_url(@project, @issue)
  41 + set_reference("issue_#{issue_id}")
38 42 mail(from: sender(updated_by_user_id),
39 43 to: recipient(recipient_id),
40 44 subject: subject("#{@issue.title} (##{@issue.iid})"))
... ...
app/mailers/emails/merge_requests.rb
... ... @@ -4,9 +4,10 @@ module Emails
4 4 @merge_request = MergeRequest.find(merge_request_id)
5 5 @project = @merge_request.project
6 6 @target_url = project_merge_request_url(@project, @merge_request)
  7 + set_message_id("merge_request_#{merge_request_id}")
7 8 mail(from: sender(@merge_request.author_id),
8 9 to: recipient(recipient_id),
9   - subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
  10 + subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
10 11 end
11 12  
12 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 15 @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
15 16 @project = @merge_request.project
16 17 @target_url = project_merge_request_url(@project, @merge_request)
  18 + set_reference("merge_request_#{merge_request_id}")
17 19 mail(from: sender(updated_by_user_id),
18 20 to: recipient(recipient_id),
19   - subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
  21 + subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
20 22 end
21 23  
22 24 def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
... ... @@ -24,18 +26,20 @@ module Emails
24 26 @updated_by = User.find updated_by_user_id
25 27 @project = @merge_request.project
26 28 @target_url = project_merge_request_url(@project, @merge_request)
  29 + set_reference("merge_request_#{merge_request_id}")
27 30 mail(from: sender(updated_by_user_id),
28 31 to: recipient(recipient_id),
29   - subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
  32 + subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
30 33 end
31 34  
32 35 def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
33 36 @merge_request = MergeRequest.find(merge_request_id)
34 37 @project = @merge_request.project
35 38 @target_url = project_merge_request_url(@project, @merge_request)
  39 + set_reference("merge_request_#{merge_request_id}")
36 40 mail(from: sender(updated_by_user_id),
37 41 to: recipient(recipient_id),
38   - subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
  42 + subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
39 43 end
40 44 end
41 45  
... ...
app/mailers/emails/notes.rb
... ... @@ -15,6 +15,7 @@ module Emails
15 15 @issue = @note.noteable
16 16 @project = @note.project
17 17 @target_url = project_issue_url(@project, @issue, anchor: "note_#{@note.id}")
  18 + set_reference("issue_#{@issue.id}")
18 19 mail(from: sender(@note.author_id),
19 20 to: recipient(recipient_id),
20 21 subject: subject("#{@issue.title} (##{@issue.iid})"))
... ... @@ -25,9 +26,10 @@ module Emails
25 26 @merge_request = @note.noteable
26 27 @project = @note.project
27 28 @target_url = project_merge_request_url(@project, @merge_request, anchor: "note_#{@note.id}")
  29 + set_reference("merge_request_#{@merge_request.id}")
28 30 mail(from: sender(@note.author_id),
29 31 to: recipient(recipient_id),
30   - subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
  32 + subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
31 33 end
32 34  
33 35 def note_wall_email(recipient_id, note_id)
... ...
app/mailers/notify.rb
... ... @@ -53,6 +53,22 @@ class Notify &lt; ActionMailer::Base
53 53 end
54 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 72 # Formats arguments into a String suitable for use as an email subject
57 73 #
58 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 36  
37 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 43 attr_accessor :should_remove_source_branch
42 44  
... ... @@ -44,6 +46,9 @@ class MergeRequest &lt; ActiveRecord::Base
44 46 # It allows us to close or modify broken merge requests
45 47 attr_accessor :allow_broken
46 48  
  49 + ActsAsTaggableOn.strict_case_match = true
  50 + acts_as_taggable_on :labels
  51 +
47 52 state_machine :state, initial: :opened do
48 53 event :close do
49 54 transition [:reopened, :opened] => :closed
... ... @@ -253,6 +258,14 @@ class MergeRequest &lt; ActiveRecord::Base
253 258 end
254 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 269 def source_branch_exists?
257 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 86 # between target and source branches
87 87 def unmerged_commits
88 88 commits = if merge_request.for_fork?
89   - Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).commits_between
  89 + compare_action.commits
90 90 else
91 91 repository.commits_between(target_branch, source_branch)
92 92 end
... ... @@ -150,7 +150,7 @@ class MergeRequestDiff &lt; ActiveRecord::Base
150 150 # between target and source branches
151 151 def unmerged_diffs
152 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 154 else
155 155 Gitlab::Git::Diff.between(repository, source_branch, target_branch)
156 156 end
... ... @@ -165,4 +165,16 @@ class MergeRequestDiff &lt; ActiveRecord::Base
165 165 def repository
166 166 merge_request.target_project.repository
167 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 180 end
... ...
app/models/project.rb
... ... @@ -281,8 +281,11 @@ class Project &lt; ActiveRecord::Base
281 281 self.id
282 282 end
283 283  
  284 + # Tags are shared by issues and merge requests
284 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 289 end
287 290  
288 291 def issue_exists?(issue_id)
... ...
app/models/project_wiki.rb
... ... @@ -64,7 +64,8 @@ class ProjectWiki
64 64 #
65 65 # Returns an initialized WikiPage instance or nil
66 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 69 WikiPage.new(self, page, true)
69 70 else
70 71 nil
... ... @@ -90,6 +91,12 @@ class ProjectWiki
90 91 wiki.delete_page(page, commit_details(:deleted, message, page.title))
91 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 100 private
94 101  
95 102 def create_repo!
... ...
app/models/wiki_page.rb
... ... @@ -175,14 +175,24 @@ class WikiPage
175 175 end
176 176  
177 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 191 set_attributes
182 192  
183 193 @persisted = true
184 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 196 @persisted = false
187 197 end
188 198 @persisted
... ...
app/services/system_hooks_service.rb
... ... @@ -31,7 +31,8 @@ class SystemHooksService
31 31 path_with_namespace: model.path_with_namespace,
32 32 project_id: model.id,
33 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 37 when User
37 38 data.merge!({
... ... @@ -46,7 +47,8 @@ class SystemHooksService
46 47 project_id: model.project_id,
47 48 user_name: model.user.name,
48 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 53 end
52 54 end
... ...
app/views/admin/users/_form.html.haml
... ... @@ -2,9 +2,9 @@
2 2 = form_for [:admin, @user], html: { class: 'form-horizontal' } do |f|
3 3 -if @user.errors.any?
4 4 #error_explanation
5   - %ul.unstyled.alert.alert-danger
  5 + .alert.alert-danger
6 6 - @user.errors.full_messages.each do |msg|
7   - %li= msg
  7 + %p= msg
8 8  
9 9 %fieldset
10 10 %legend Account
... ...
app/views/events/event/_note.html.haml
... ... @@ -14,8 +14,8 @@
14 14 - note = event.target
15 15 - if note.attachment.url
16 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 19 - else
20 20 = link_to note.attachment.secure_url, target: "_blank", class: 'note-file-attach' do
21 21 %i.icon-paper-clip
... ...
app/views/groups/show.html.haml
1 1 .dashboard
2   - .activities.col-md-8.hidden-sm
  2 + .activities.col-md-8.hidden-sm.hidden-xs
3 3 - if current_user
4 4 = render "events/event_last_push", event: @last_push
5 5 = link_to dashboard_path, class: 'btn btn-tiny' do
... ...
app/views/layouts/_head_panel.html.haml
... ... @@ -43,6 +43,6 @@
43 43 %li
44 44 = link_to destroy_user_session_path, class: "logout", method: :delete, title: "Logout", class: 'has_bottom_tooltip', 'data-original-title' => 'Logout' do
45 45 %i.icon-signout
46   - %li
  46 + %li.hidden-xs
47 47 = link_to current_user, class: "profile-pic", id: 'profile-pic' do
48 48 = image_tag avatar_icon(current_user.email, 26), alt: 'User activity'
... ...
app/views/notify/closed_merge_request_email.html.haml
1 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 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 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 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 15 - else
16 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 19 - blob_commit = @repository.last_commit_for_path(@commit.id, @blob.path)
20 20 = render blob_commit, project: @project
21 21  
22 22 %div#tree-content-holder.tree-content-holder
23 23 .file-holder
24   - .file-title
  24 + .file-title.clearfix
25 25 %i.icon-file
26 26 %span.file_name
27 27 = blob.name
28 28 %small= number_to_human_size blob.size
29   - %span.options= render "actions"
  29 + %span.options.hidden-xs= render "actions"
30 30 - if blob.text?
31 31 = render "text", blob: blob
32 32 - elsif blob.image?
... ...
app/views/projects/blob/_remove.html.haml
... ... @@ -14,7 +14,8 @@
14 14 = label_tag 'commit_message', class: "control-label" do
15 15 Commit message
16 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 19 .form-group
19 20 .col-sm-2
20 21 .col-sm-10
... ...
app/views/projects/commits/_parallel_view.html.haml
... ... @@ -2,54 +2,37 @@
2 2 - old_lines, new_lines = parallel_diff_lines(project, @commit, diff, file)
3 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 23 = label_tag 'commit_message', class: "control-label" do
24 24 Commit message
25 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 28 .form-actions
28 29 = hidden_field_tag 'last_commit', @last_commit
29 30 = hidden_field_tag 'content', '', id: "file-content"
... ...
app/views/projects/issues/_issue_context.html.haml
1 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 1 = render "head"
2 2 .row
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 6 .col-md-9.issues-holder
6 7 = render "issues"
... ...
app/views/projects/issues/show.html.haml
1 1 %h3.page-title
2 2 Issue ##{@issue.iid}
3 3  
4   - %span.pull-right
  4 + %span.pull-right.issue-btn-group
5 5 - if can?(current_user, :write_issue, @project)
6 6 = link_to new_project_issue_path(@project), class: "btn btn-grouped", title: "New Issue", id: "new_issue_link" do
7 7 %i.icon-plus
... ... @@ -16,28 +16,29 @@
16 16 %i.icon-edit
17 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 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 36 - if @issue.closed?
36 37 Closed
37 38 - else
38 39 Open
39 40  
40   - %span.creator
  41 + %span.creator.col-sm-9.col-xs-12
41 42 Created by #{link_to_member(@project, @issue.author)} #{time_ago_with_tooltip(@issue.created_at)}
42 43  
43 44 %h4.title
... ...
app/views/projects/merge_requests/_form.html.haml
... ... @@ -14,33 +14,6 @@
14 14 - @merge_request.errors.full_messages.each do |msg|
15 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 17 .merge-request-form-info
45 18 .form-group
46 19 = f.label :title, class: 'control-label' do
... ... @@ -51,6 +24,32 @@
51 24 .col-sm-10
52 25 = f.text_area :description, class: "form-control js-gfm-input", rows: 14
53 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 54 .form-actions
56 55 - if @merge_request.new_record?
... ... @@ -66,20 +65,36 @@
66 65  
67 66 :javascript
68 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 11 - if merge_request.for_fork?
12 12 %span.light
13 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 17 .merge-request-info
22 18 - if merge_request.author
23 19 authored by #{link_to_member(merge_request.source_project, merge_request.author)}
... ... @@ -35,3 +31,9 @@
35 31  
36 32 .pull-right
37 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 @@
  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 @@
  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 4 = render "projects/merge_requests/show/mr_box"
5 5 = render "projects/merge_requests/show/state_widget"
6 6 = render "projects/merge_requests/show/commits"
  7 + = render "projects/merge_requests/show/participants"
7 8  
8 9 - if @commits.present?
9 10 %ul.nav.nav-tabs
... ...
app/views/projects/merge_requests/branch_from.js.haml
1 1 :plain
2 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 8 %hr
9 9 .row
10 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 13 .col-md-9
13 14 .mr-filters.append-bottom-10
14 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 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 12 - if @show_merge_controls
13 13 .automerge_widget.can_be_merged.hide
14 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 16 %h4
17 17 You can accept this request automatically.
18 18 %div
... ... @@ -21,7 +21,6 @@
21 21 = link_to "click here", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
22 22 for instructions.
23 23  
24   -
25 24 .js-toggle-container
26 25 %p
27 26 If you want to modify merge commit message -
... ... @@ -31,7 +30,8 @@
31 30 .form-group
32 31 = label_tag :merge_commit_message, "Commit message", class: 'control-label'
33 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 35 %p.hint
36 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 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 4 - if @merge_request.merged?
5 5 Merged
6 6 - elsif @merge_request.closed?
... ... @@ -8,7 +8,7 @@
8 8 - else
9 9 Open
10 10  
11   - %span.creator
  11 + %span.creator.col-sm-9.col-xs-12
12 12 Created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)}
13 13  
14 14 %h4.title
... ...
app/views/projects/merge_requests/show/_mr_title.html.haml
1 1 %h3.page-title
2 2 = "Merge Request ##{@merge_request.iid}"
3 3  
4   - %span.pull-right
  4 + %span.pull-right.issue-btn-group
5 5 - if can?(current_user, :modify_merge_request, @merge_request)
6 6 - if @merge_request.open?
7 7 .btn-group.pull-left
... ... @@ -39,4 +39,4 @@
39 39 - else
40 40 %span= @merge_request.source_branch
41 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 @@
  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 21 #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
22 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 24 - unless @commits.any?
33 25 %h4 Nothing to merge
34 26 %p
... ... @@ -38,3 +30,12 @@
38 30 %span.label-branch #{@merge_request.target_branch}
39 31 %br
40 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 1 = render "projects/issues/head"
2 2 %h3.page-title
3 3 Milestone ##{@milestone.iid}
4   - %small
5   - = @milestone.expires_at
6 4 .pull-right
7 5 - if can?(current_user, :admin_milestone, @project)
8 6 = link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-grouped" do
... ... @@ -23,14 +21,16 @@
23 21  
24 22  
25 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 26 - if @milestone.closed?
29 27 Closed
30 28 - elsif @milestone.expired?
31 29 Expired
32 30 - else
33 31 Open
  32 + %span.creator.col-sm-9.col-xs-12
  33 + = @milestone.expires_at
34 34  
35 35 %h4.title
36 36 = gfm escape_once(@milestone.title)
... ... @@ -100,7 +100,7 @@
100 100 %ul.bordered-list
101 101 - @users.each do |user|
102 102 %li
103   - = link_to user, title: user.name, class: "dark" do
  103 + = link_to user, title: user.name, class: "darken" do
104 104 = image_tag avatar_icon(user.email, 32), class: "avatar s32"
105 105 %strong= truncate(user.name, lenght: 40)
106 106 %br
... ...
app/views/projects/new_tree/show.html.haml
... ... @@ -24,7 +24,8 @@
24 24 = label_tag 'commit_message', class: "control-label" do
25 25 Commit message
26 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 30 .file-holder
30 31 .file-title
... ...
app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
1 1 - note1 = notes1.first # example note
2 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 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 6 - if note1
  7 + %td.notes_line
  8 + %span.btn.disabled
  9 + %i.icon-comment
  10 + = notes1.count
7 11 %td.notes_content
8 12 %ul.notes{ rel: note1.discussion_id }
9 13 = render notes1
  14 +
10 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 16 - else
16 17 %td= ""
17 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 20 - if note2
24 21 %td.notes_line
25   - %span.btn.disabled.parallel-comment
  22 + %span.btn.disabled
26 23 %i.icon-comment
27 24 = notes2.count
28 25 %td.notes_content
29 26 %ul.notes{ rel: note2.discussion_id }
30 27 = render notes2
  28 +
31 29 = render "projects/notes/discussion_reply_button", note: note2
32 30 - else
33 31 %td= ""
34 32 %td= ""
  33 +
... ...
app/views/projects/notes/_note.html.haml
... ... @@ -54,8 +54,8 @@
54 54 - if note.attachment.url
55 55 .note-attachment
56 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 59 .attachment.pull-right
60 60 = link_to note.attachment.secure_url, target: "_blank" do
61 61 %i.icon-paper-clip
... ...
app/views/projects/show.html.haml
... ... @@ -6,7 +6,7 @@
6 6 = render 'shared/event_filter'
7 7 .content_list
8 8 = spinner
9   - .col-md-3.project-side.hidden-sm
  9 + .col-md-3.project-side.hidden-sm.hidden-xs
10 10 .clearfix
11 11 - if @project.archived?
12 12 .alert.alert-warning
... ...
app/views/projects/wikis/_form.html.haml
1 1 = form_for [@project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal' } do |f|
2 2 -if @page.errors.any?
3 3 #error_explanation
4   - %h2= "#{pluralize(@page.errors.count, "error")} prohibited this wiki from being saved:"
5   - %ul
  4 + .alert.alert-danger
6 5 - @page.errors.full_messages.each do |msg|
7   - %li= msg
  6 + %p= msg
8 7  
9 8 = f.hidden_field :title, value: @page.title
10 9 .form-group
... ...
app/views/projects/wikis/_new.html.haml
... ... @@ -9,6 +9,6 @@
9 9 %span Page slug
10 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 11 %p.hint
12   - Please don't use spaces and slashes
  12 + Please don't use spaces.
13 13 .modal-footer
14 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 @@
  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 44 .light-well
45 45 Add first label to your issues
46 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 49 %fieldset
50 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 @@
  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 66 # Version of your assets, change this if you want to expire all your assets
67 67 config.assets.version = '1.0'
68 68  
  69 + # Relative url support
69 70 # Uncomment and customize the last line to run in a non-root path
70 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 73 # 1) In your application.rb file: config.relative_url_root = "/gitlab"
73 74 # 2) In your gitlab.yml file: relative_url_root: /gitlab
74 75 # 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
75 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 79 # To update the path, run: sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
77 80 #
78 81 # config.relative_url_root = "/gitlab"
... ...
config/gitlab.yml.example
... ... @@ -24,15 +24,8 @@ production: &amp;base
24 24 # Otherwise, ssh host will be set to the `host:` value above
25 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 29 # relative_url_root: /gitlab
37 30  
38 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 33  
41 34 ## Email settings
42 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 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 41 ## User settings
49 42 default_projects_limit: 10
... ...
config/routes.rb
... ... @@ -206,7 +206,7 @@ Gitlab::Application.routes.draw do
206 206 end
207 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 210 collection do
211 211 get :pages
212 212 put ':id' => 'wikis#update'
... ... @@ -273,7 +273,7 @@ Gitlab::Application.routes.draw do
273 273 resources :merge_requests, constraints: {id: /\d+/}, except: [:destroy] do
274 274 member do
275 275 get :diffs
276   - get :automerge
  276 + post :automerge
277 277 get :automerge_check
278 278 get :ci_status
279 279 end
... ...
config/unicorn.rb.example
... ... @@ -8,14 +8,8 @@
8 8 # See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
9 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 14 # ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
21 15  
... ...
db/fixtures/development/04_project.rb
... ... @@ -40,7 +40,8 @@ Gitlab::Seeder.quiet do
40 40 import_url: url,
41 41 namespace_id: group.id,
42 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 47 project = Projects::CreateService.new(User.first, params).execute
... ...
db/fixtures/development/10_merge_requests.rb
1 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 31 end
32   - print('.')
33 32 end
34 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 21 ## Clients
22 22  
23 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 25 + [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby
25 26 + [python-gitlab](https://github.com/Itxaka/python-gitlab) - Python
26 27 + [java-gitlab-api](https://github.com/timols/java-gitlab-api) - Java
27 28 + [node-gitlab](https://github.com/moul/node-gitlab) - Node.js
  29 ++ [NGitLab](https://github.com/Scooletz/NGitLab) - .NET
28 30  
29 31 ## Introduction
30 32  
... ...
doc/api/deploy_key_multiple_projects.md 0 → 100644
... ... @@ -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 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 240 ## Post comment to MR
193 241  
194 242 Adds a comment to a merge request.
... ...
doc/api/projects.md
... ... @@ -43,7 +43,8 @@ GET /projects
43 43 "owner_id": 1,
44 44 "path": "diaspora",
45 45 "updated_at": "2013-09-30T13: 46: 02Z"
46   - }
  46 + },
  47 + "archived": false
47 48 },
48 49 {
49 50 "id": 6,
... ... @@ -78,7 +79,8 @@ GET /projects
78 79 "owner_id": 1,
79 80 "path": "brightbox",
80 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 159 "access_level": 50,
158 160 "notification_level": 3
159 161 }
160   - }
  162 + },
  163 + "archived": false
161 164 }
162 165 ```
163 166  
... ...
doc/install/installation.md
1 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 3 this should be the highest numbered stable branch (example shown below).
4 4  
5 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 9 # Important notes
10 10  
... ... @@ -86,7 +86,7 @@ Is the system packaged Git too old? Remove it and compile from source.
86 86 mail server. By default, Debian is shipped with exim4 whereas Ubuntu
87 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 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 101 Download Ruby and compile it:
102 102  
103 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 106 ./configure --disable-install-rdoc
107 107 make
108 108 sudo make install
... ... @@ -121,6 +121,7 @@ Create a `git` user for Gitlab:
121 121 # 4. Database
122 122  
123 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 126 # Install the database packages
126 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 130 sudo -u postgres psql -d template1
130 131  
131 132 # Create a user for GitLab.
132   - template1=# CREATE USER git;
  133 + template1=# CREATE USER git CREATEDB;
133 134  
134 135 # Create the GitLab production database & grant all privileges on database
135 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 150 ## Clone the Source
150 151  
151 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 155 # Go to gitlab dir
155 156 cd /home/git/gitlab
156 157  
157 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 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 201 # Configure Git global settings for git user, useful when editing via web
201 202 # Edit user.email according to what is set in gitlab.yml
202 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 205 sudo -u git -H git config --global core.autocrlf input
205 206  
206 207 **Important Note:**
... ... @@ -243,15 +244,6 @@ that were [fixed](https://github.com/bundler/bundler/pull/2817) in 1.5.2.
243 244 # Or if you use MySQL (note, the option says "without ... postgres")
244 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 247 ## Install GitLab shell
256 248  
257 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 252 cd /home/git/gitlab
261 253  
262 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 257 # By default, the gitlab-shell config is generated from your main gitlab config. You can review (and modify) it as follows:
266 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 269 ## Install Init Script
269 270  
270 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 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 306 # 6. Nginx
311 307  
312 308 **Note:**
... ... @@ -413,22 +409,22 @@ GitLab uses [Omniauth](http://www.omniauth.org/) for authentication and already
413 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 411 * Stop GitLab
416   - `sudo service gitlab stop`
  412 + `sudo service gitlab stop`
417 413  
418 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 416 * Add the gem to your [Gemfile](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/Gemfile)
421 417 `gem "omniauth-your-auth-provider"`
422 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 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 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 426 * Start GitLab
431   - `sudo service gitlab start`
  427 + `sudo service gitlab start`
432 428  
433 429  
434 430 ### Examples
... ...
doc/install/requirements.md
... ... @@ -53,7 +53,7 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/)) but GitLab
53 53  
54 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 57 - 1GB supports up to 100 users (with individual repositories under 250MB, otherwise git memory usage necessitates using swap space)
58 58 - **2GB** is the **recommended** memory size and supports up to 500 users
59 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 74  
75 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 81 # Supported webbrowsers
79 82  
80 83 - Chrome (Latest stable version)
81 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 86 - Opera (Latest released version)
84 87 - IE 10+
... ...
doc/integration/README.md
... ... @@ -7,3 +7,5 @@ See the documentation below for details on how to configure these services.
7 7 + [LDAP](ldap.md) Set up sign in via LDAP
8 8 + [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, and Google via OAuth.
9 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 2  
3 3 - the 'Issues' link on the GitLab project pages takes you to the appropriate JIRA issue index;
4 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 7 ![jira screenshot](jira-integration-points.png)
8 8  
9   -You can configure the integration in the gitlab.yml configuration file.
10 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 4  
5 5 #### Public projects
6 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 8 **Any logged in user** will have [Guest](/help/permissions) permissions on the repository.
9 9  
10 10 #### Internal projects
... ...
doc/raketasks/maintenance.md
... ... @@ -24,9 +24,9 @@ Version: 5.1.0.beta2
24 24 Revision: 4da8b37
25 25 Directory: /home/git/gitlab
26 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 30 Using LDAP: no
31 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 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 38  
18 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 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 50  
30 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 55 * https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/nginx/gitlab
35 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 61  
41 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 66 #### 9. Start application
46 67  
47 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 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 80  
59 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 16 "path": "stormcloud",
17 17 "path_with_namespace": "jsmith/stormcloud",
18 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 32 "path": "underscore",
32 33 "path_with_namespace": "jsmith/underscore",
33 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 40  
39 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 56  
54 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 64 # Update the logrotate configuration (keep logs for 90 days instead of 52 weeks)
65 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 71 # Close access to gitlab-satellites for others
68 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 64 sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
65 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 67 # Close access to gitlab-satellites for others
71 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 @@
  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 20 cd mysql-postgresql-converter
6 21 mysqldump --compatible=postgresql --default-character-set=utf8 -r databasename.mysql -u root gitlabhq_production
7 22 python db_converter.py databasename.mysql databasename.psql
8 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 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 5 ### 1. Look for Ruby versions
6 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 36 ```
37 37  
38 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 41 ```bash
42 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 25 "project_id": 15,
26 26 "repository": {
27 27 "name": "Diaspora",
28   - "url": "git@localhost:diaspora.git",
  28 + "url": "git@example.com:diaspora.git",
29 29 "description": "",
30   - "homepage": "http://localhost/diaspora"
  30 + "homepage": "http://example.com/diaspora"
31 31 },
32 32 "commits": [
33 33 {
34 34 "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
35 35 "message": "Update Catalan translation to e38cb41.",
36 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 38 "author": {
39 39 "name": "Jordi Mallach",
40 40 "email": "jordi@softcatala.org"
... ... @@ -44,7 +44,7 @@ Triggered when you push to the repository except when pushing tags.
44 44 "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
45 45 "message": "fixed readme",
46 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 48 "author": {
49 49 "name": "GitLab dev user",
50 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 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 113 Then I should see user "John Doe" in team list
114 114 Then I should see user "Mary Jane" in team list
115 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 30 Given I visit project "Forked Shop" merge requests page
31 31 And I click link "New Merge Request"
32 32 And I fill out an invalid "Merge Request On Forked Project" merge request
33   - And I submit the merge request
34 33 Then I should see validation errors
35 34  
36 35 @javascript
37 36 Scenario: Merge request should target fork repository by default
38 37 Given I visit project "Forked Shop" merge requests page
39 38 And I click link "New Merge Request"
40   - Then the target repository should be the original repository
41 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 45 And I browse to that Wiki page
46 46 And I click on the "Pages" button
47 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 25 find("#merge_request_target_project_id").value.should == @project.id.to_s
26 26 find("#merge_request_source_branch").value.should == "new_design"
27 27 find("#merge_request_target_branch").value.should == "master"
28   - find("#merge_request_title").value.should == "New design"
29 28 end
30 29  
31 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 157 # poltergeist always confirms popups.
158 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 167 protected
161 168  
162 169 def assigned_to_me key
... ...
features/steps/project/forked_merge_requests.rb
... ... @@ -53,6 +53,7 @@ class ProjectForkedMergeRequests &lt; Spinach::FeatureSteps
53 53  
54 54 find(:select, "merge_request_source_branch", {}).value.should == 'master'
55 55 find(:select, "merge_request_target_branch", {}).value.should == 'stable'
  56 + click_button "Compare branches"
56 57  
57 58 fill_in "merge_request_title", with: "Merge Request On Forked Project"
58 59 end
... ... @@ -148,29 +149,19 @@ class ProjectForkedMergeRequests &lt; Spinach::FeatureSteps
148 149 current_path.should == edit_project_merge_request_path(@project, @merge_request)
149 150 page.should have_content "Edit merge request ##{@merge_request.id}"
150 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 152 end
158 153  
159 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 155 select "Select branch", from: "merge_request_target_branch"
164   -
165 156 find(:select, "merge_request_source_project_id", {}).value.should == @forked_project.id.to_s
166 157 find(:select, "merge_request_target_project_id", {}).value.should == project.id.to_s
167 158 find(:select, "merge_request_source_branch", {}).value.should == ""
168 159 find(:select, "merge_request_target_branch", {}).value.should == ""
  160 + click_button "Compare branches"
169 161 end
170 162  
171 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 165 end
175 166  
176 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 61 end
62 62  
63 63 step 'I submit new merge request "Wiki Feature"' do
64   - fill_in "merge_request_title", with: "Wiki Feature"
65 64 select "master", from: "merge_request_source_branch"
66 65 select "notes_refactoring", from: "merge_request_target_branch"
  66 + click_button "Compare branches"
  67 + fill_in "merge_request_title", with: "Wiki Feature"
67 68 click_button "Submit merge request"
68 69 end
69 70  
... ...
features/steps/project/wiki.rb
... ... @@ -86,6 +86,47 @@ class Spinach::Features::ProjectWiki &lt; Spinach::FeatureSteps
86 86 page.should have_content @page.title
87 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 130 def wiki
90 131 @project_wiki = ProjectWiki.new(project, current_user)
91 132 end
... ...
lib/api/branches.rb
... ... @@ -24,7 +24,7 @@ module API
24 24 # branch (required) - The name of the branch
25 25 # Example Request:
26 26 # GET /projects/:id/repository/branches/:branch
27   - get ":id/repository/branches/:branch" do
  27 + get ':id/repository/branches/:branch', requirements: { branch: /.*/ } do
28 28 @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
29 29 not_found!("Branch does not exist") if @branch.nil?
30 30 present @branch, with: Entities::RepoObject, project: user_project
... ... @@ -37,7 +37,9 @@ module API
37 37 # branch (required) - The name of the branch
38 38 # Example Request:
39 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 43 authorize_admin_project
42 44  
43 45 @branch = user_project.repository.find_branch(params[:branch])
... ... @@ -55,7 +57,9 @@ module API
55 57 # branch (required) - The name of the branch
56 58 # Example Request:
57 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 63 authorize_admin_project
60 64  
61 65 @branch = user_project.repository.find_branch(params[:branch])
... ...
lib/api/entities.rb
... ... @@ -43,6 +43,7 @@ module API
43 43 class Project < Grape::Entity
44 44 expose :id, :description, :default_branch
45 45 expose :public?, as: :public
  46 + expose :archived?, as: :archived
46 47 expose :visibility_level, :ssh_url_to_repo, :http_url_to_repo, :web_url
47 48 expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
48 49 expose :name, :name_with_namespace
... ... @@ -135,6 +136,7 @@ module API
135 136 expose :target_branch, :source_branch, :upvotes, :downvotes
136 137 expose :author, :assignee, using: Entities::UserBasic
137 138 expose :source_project_id, :target_project_id
  139 + expose :label_list, as: :labels
138 140 end
139 141  
140 142 class SSHKey < Grape::Entity
... ...
lib/api/helpers.rb
... ... @@ -8,6 +8,11 @@ module API
8 8 def current_user
9 9 private_token = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s
10 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 16 identifier = sudo_identifier()
12 17  
13 18 # If the sudo is the current user do nothing
... ...
lib/api/merge_requests.rb
... ... @@ -34,7 +34,7 @@ module API
34 34 when "closed" then user_project.merge_requests.closed
35 35 when "merged" then user_project.merge_requests.merged
36 36 else user_project.merge_requests
37   - end
  37 + end
38 38  
39 39 present paginate(mrs), with: Entities::MergeRequest
40 40 end
... ... @@ -67,6 +67,7 @@ module API
67 67 # assignee_id - Assignee user ID
68 68 # title (required) - Title of MR
69 69 # description - Description of MR
  70 + # labels (optional) - Labels for MR as a comma-separated list
70 71 #
71 72 # Example:
72 73 # POST /projects/:id/merge_requests
... ... @@ -75,6 +76,7 @@ module API
75 76 authorize! :write_merge_request, user_project
76 77 required_attributes! [:source_branch, :target_branch, :title]
77 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 80 merge_request = ::MergeRequests::CreateService.new(user_project, current_user, attrs).execute
79 81  
80 82 if merge_request.valid?
... ... @@ -95,11 +97,13 @@ module API
95 97 # title - Title of MR
96 98 # state_event - Status of MR. (close|reopen|merge)
97 99 # description - Description of MR
  100 + # labels (optional) - Labels for a MR as a comma-separated list
98 101 # Example:
99 102 # PUT /projects/:id/merge_request/:merge_request_id
100 103 #
101 104 put ":id/merge_request/:merge_request_id" do
102 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 107 merge_request = user_project.merge_requests.find(params[:merge_request_id])
104 108 authorize! :modify_merge_request, merge_request
105 109 merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request)
... ... @@ -111,6 +115,49 @@ module API
111 115 end
112 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 161 # Get a merge request's comments
115 162 #
116 163 # Parameters:
... ...
lib/backup/manager.rb
... ... @@ -101,7 +101,7 @@ module Backup
101 101  
102 102 def tar_version
103 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 105 end
106 106 end
107 107 end
... ...
lib/backup/repository.rb
... ... @@ -10,15 +10,12 @@ module Backup
10 10 Project.find_each(batch_size: 1000) do |project|
11 11 print " * #{project.path_with_namespace} ... "
12 12  
13   - if project.empty_repo?
14   - puts "[SKIPPED]".cyan
15   - next
16   - end
17   -
18 13 # Create namespace dir if missing
19 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 19 puts "[DONE]".green
23 20 else
24 21 puts "[FAILED]".red
... ...
lib/gitlab/git_access.rb
... ... @@ -61,18 +61,7 @@ module Gitlab
61 61 private
62 62  
63 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 65 end
77 66 end
78 67 end
... ...
lib/gitlab/ldap/access.rb
... ... @@ -14,7 +14,11 @@ module Gitlab
14 14 end
15 15  
16 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 22 rescue
19 23 false
20 24 end
... ...
lib/gitlab/ldap/adapter.rb
... ... @@ -64,7 +64,7 @@ module Gitlab
64 64 end
65 65 end
66 66  
67   - entries = ldap.search(options).select do |entry|
  67 + entries = ldap_search(options).select do |entry|
68 68 entry.respond_to? config.uid
69 69 end
70 70  
... ... @@ -77,6 +77,26 @@ module Gitlab
77 77 users(*args).first
78 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 100 private
81 101  
82 102 def config
... ...
lib/gitlab/ldap/person.rb
1 1 module Gitlab
2 2 module LDAP
3 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 9 def self.find_by_uid(uid, adapter=nil)
5 10 adapter ||= Gitlab::LDAP::Adapter.new
6 11 adapter.user(config.uid, uid)
... ... @@ -11,6 +16,11 @@ module Gitlab
11 16 adapter.user('dn', dn)
12 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 24 def initialize(entry)
15 25 Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
16 26 @entry = entry
... ...
lib/gitlab/markdown.rb
... ... @@ -98,6 +98,7 @@ module Gitlab
98 98 (?<prefix>\W)? # Prefix
99 99 ( # Reference
100 100 @(?<user>[a-zA-Z][a-zA-Z0-9_\-\.]*) # User name
  101 + |(?<issue>([A-Z\-]+-)\d+) # JIRA Issue ID
101 102 |\#(?<issue>([a-zA-Z\-]+-)?\d+) # Issue ID
102 103 |!(?<merge_request>\d+) # MR ID
103 104 |\$(?<snippet>\d+) # Snippet ID
... ... @@ -172,11 +173,15 @@ module Gitlab
172 173 end
173 174  
174 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 185 end
181 186 end
182 187  
... ... @@ -197,5 +202,12 @@ module Gitlab
197 202 link_to(identifier, project_commit_url(@project, commit), html_options.merge(title: commit.link_title, class: "gfm gfm-commit #{html_options[:class]}"))
198 203 end
199 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 212 end
201 213 end
... ...
lib/gitlab/oauth/user.rb
... ... @@ -34,9 +34,11 @@ module Gitlab
34 34 # In this case we generate temporary email and force user to fill it later
35 35 if user.email.blank?
36 36 user.generate_tmp_oauth_email
37   - else
  37 + elsif provider != "ldap"
38 38 # Google oauth returns email but dont return nickname
39 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 42 user.username = email.match(/^[^@]*/)[0]
41 43 end
42 44  
... ... @@ -65,7 +67,11 @@ module Gitlab
65 67 end
66 68  
67 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 75 end
70 76  
71 77 def username
... ...
lib/gitlab/satellite/compare_action.rb 0 → 100644
... ... @@ -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 84 # Clear the working directory
85 85 def clear_working_dir!
86 86 repo.git.reset(hard: true)
  87 + repo.git.clean(f: true, d: true, x: true)
87 88 end
88 89  
89 90 # Deletes all branches except the parking branch
... ...
lib/gitlab/user_access.rb 0 → 100644
... ... @@ -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 59 }
60 60  
61 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 65 location ~ ^/(assets)/ {
63 66 root /home/git/gitlab/public;
64 67 gzip_static on; # to serve pre-gzipped version
... ... @@ -67,4 +70,4 @@ server {
67 70 }
68 71  
69 72 error_page 502 /502.html;
70   -}
71 73 \ No newline at end of file
  74 +}
... ...
lib/tasks/gitlab/test.rake
... ... @@ -8,9 +8,9 @@ namespace :gitlab do
8 8 ]
9 9  
10 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 14 end
15 15 end
16 16 end
... ...
spec/helpers/gitlab_markdown_helper_spec.rb
... ... @@ -181,6 +181,52 @@ describe GitlabMarkdownHelper do
181 181 include_examples 'referenced object'
182 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 230 describe "referencing a merge request" do
185 231 let(:object) { merge_request }
186 232 let(:reference) { "!#{merge_request.iid}" }
... ...
spec/lib/gitlab/ldap/ldap_access_spec.rb 0 → 100644
... ... @@ -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 @@
  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 11 subject.issues.should == ["1234"]
12 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 20 it 'extracts merge request references' do
15 21 subject.analyze "and here's !43, a merge request"
16 22 subject.merge_requests.should == ["43"]
... ...
spec/mailers/notify_spec.rb
... ... @@ -161,6 +161,10 @@ describe Notify do
161 161 it 'contains a link to the new issue' do
162 162 should have_body_text /#{project_issue_path project, issue}/
163 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 168 end
165 169  
166 170 describe 'that are new with a description' do
... ... @@ -197,6 +201,10 @@ describe Notify do
197 201 it 'contains a link to the issue' do
198 202 should have_body_text /#{project_issue_path project, issue}/
199 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 208 end
201 209  
202 210 describe 'status changed' do
... ... @@ -224,6 +232,10 @@ describe Notify do
224 232 it 'contains a link to the issue' do
225 233 should have_body_text /#{project_issue_path project, issue}/
226 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 239 end
228 240  
229 241 end
... ... @@ -239,7 +251,7 @@ describe Notify do
239 251 it_behaves_like 'an assignee email'
240 252  
241 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 255 end
244 256  
245 257 it 'contains a link to the new merge request' do
... ... @@ -253,6 +265,10 @@ describe Notify do
253 265 it 'contains the target branch for the merge request' do
254 266 should have_body_text /#{merge_request.target_branch}/
255 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 272 end
257 273  
258 274 describe 'that are new with a description' do
... ... @@ -275,7 +291,7 @@ describe Notify do
275 291 end
276 292  
277 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 295 end
280 296  
281 297 it 'contains the name of the previous assignee' do
... ... @@ -303,7 +319,7 @@ describe Notify do
303 319 end
304 320  
305 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 323 end
308 324  
309 325 it 'contains the new status' do
... ... @@ -313,6 +329,10 @@ describe Notify do
313 329 it 'contains a link to the merge request' do
314 330 should have_body_text /#{project_merge_request_path project, merge_request}/
315 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 336 end
317 337 end
318 338 end
... ... @@ -426,7 +446,7 @@ describe Notify do
426 446 it_behaves_like 'a note email'
427 447  
428 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 450 end
431 451  
432 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 39 end
40 40  
41 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 53 it "should leave user as is when sudo not specified" do
43 54 env[API::APIHelpers::PRIVATE_TOKEN_HEADER] = user.private_token
44 55 current_user.should == user
... ...
spec/requests/api/merge_requests_spec.rb
... ... @@ -183,11 +183,33 @@ describe API::API, api: true do
183 183 end
184 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 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 213 end
192 214 end
193 215  
... ...
spec/requests/api/projects_spec.rb
... ... @@ -14,6 +14,12 @@ describe API::API, api: true do
14 14 let(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) }
15 15 let(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) }
16 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 24 describe "GET /projects" do
19 25 before { project }
... ... @@ -634,15 +640,45 @@ describe API::API, api: true do
634 640 end
635 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 682 end
647 683 end
648 684 end
... ...
spec/routing/project_routing_spec.rb
... ... @@ -213,7 +213,7 @@ describe Projects::RefsController, &quot;routing&quot; do
213 213 end
214 214  
215 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 217 # automerge_check_project_merge_request GET /:project_id/merge_requests/:id/automerge_check(.:format) projects/merge_requests#automerge_check
218 218 # branch_from_project_merge_requests GET /:project_id/merge_requests/branch_from(.:format) projects/merge_requests#branch_from
219 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 230 end
231 231  
232 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 237 end
235 238  
236 239 it "to #automerge_check" do
... ...
spec/services/system_hooks_service_spec.rb
... ... @@ -8,10 +8,10 @@ describe SystemHooksService do
8 8 context 'event data' do
9 9 it { event_data(user, :create).should include(:event_name, :name, :created_at, :email, :user_id) }
10 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 15 end
16 16  
17 17 context 'event names' do
... ...