Commit 61cfa2a7a6e1d4557b69e17c537656e8a0201ac8

Authored by Sebastian Ziebell
2 parents d269d107 6beae84e

Merge branch 'master' into fixes/api

Conflicts:
	lib/api/projects.rb
Showing 147 changed files with 1924 additions and 1221 deletions   Show diff stats
CONTRIBUTING.md
1   -# Contact & support
  1 +# Contribute to GitLab
2 2  
3   -If you want quick help, head over to our [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq).
4   -Otherwise you can follow our [Issue Submission Guide](https://github.com/gitlabhq/gitlabhq/wiki/Issue-Submission-Guide) for a more systematic and thorough guide to solving your issues.
  3 +If you have a question or want to contribute to GitLab this guide show you the appropriate channel to use.
5 4  
  5 +## Ruling out common errors
6 6  
  7 +Some errors are common and it may so happen, that you are not the only one who stumbled over a particular issue. We have [collected several of those and documented quick solutions](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide) for them.
7 8  
8   -# Contribute to GitLab
  9 +## Support forum
  10 +
  11 +Please visit our [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq) for any kind of question regarding the usage or adiministration/configuration of GitLab.
  12 +
  13 +### Use the support forum if ...
9 14  
10   -## Recipes
  15 +* You get permission denied errors
  16 +* You can't see your repos
  17 +* You have issues cloning, pulling or pushing
  18 +* You have issues with web_hooks not firing
11 19  
12   -We collect user submitted installation scripts and config file templates for platforms we don't support officially.
13   -We believe there is merit in allowing a certain amount of diversity.
14   -You can get and submit your solution to running/configuring GitLab with your favorite OS/distro, database, web server, cloud hoster, configuration management tool, etc.
  20 +**Search** for similar issues before posting your own, there's a good chance somebody else had the same issue you have now and had it resolved.
15 21  
16   -Help us improve the collection of [GitLab Recipes](https://github.com/gitlabhq/gitlab-recipes/)
  22 +## Paid support
17 23  
  24 +Community support in the [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq) is done by volunteers. Paid support is available from [GitLab.com](http://blog.gitlab.com/services/)
18 25  
19 26 ## Feature suggestions
20 27  
21   -Follow the [Issue Submission Guide](https://github.com/gitlabhq/gitlabhq/wiki/Issue-Submission-Guide) and support other peoples ideas or propose your own.
  28 +Feature suggestions don't belong in issues but can go to [Feedback forum](http://gitlab.uservoice.com/forums/176466-general) where they can be voted on.
  29 +
  30 +## Pull requests
  31 +
  32 +Code speaks louder than words. If you can please submit a pull request with the fix including tests. The workflow to make a pull request is as follows:
  33 +
  34 +1. Fork the project on GitHub
  35 +1. Create a feature branch
  36 +1. Write tests and code
  37 +1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
  38 +1. Push the commit to your fork
  39 +1. Submit a pull request
  40 +
  41 +We will accept pull requests if:
  42 +
  43 +* The code has proper tests and all tests pass
  44 +* It can be merged without problems (if not please use: git rebase master)
  45 +* It doesn't break any existing functionality
  46 +* It's quality code that conforms to the [Rails style guide](https://github.com/bbatsov/rails-style-guide) and best practices
  47 +* The description includes a motive for your change and the method you used to achieve it
  48 +* It keeps the GitLab code base clean and well structured
  49 +* We think other users will need the same functionality
  50 +* If it makes changes to the UI the pull request should include screenshots
  51 +
  52 +For examples of feedback on pull requests please look at already [closed pull requests](https://github.com/gitlabhq/gitlabhq/pulls?direction=desc&page=1&sort=created&state=closed).
  53 +
  54 +## Submitting via GitHub's issue tracker
  55 +
  56 +* For obvious bugs or misbehavior in GitLab in the master branch. Please include the revision id and a reproducible test case.
  57 +* For problematic or insufficient documentation. Please give a suggestion on how to improve it.
  58 +
  59 +If you're unsure where to post, post it to the [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq) first.
  60 +There are a lot of helpful GitLab users there who may be able to help you quickly.
  61 +If your particular issue turns out to be a bug, it will find its way from there to the [issue tracker on GitHub](https://github.com/gitlabhq/gitlabhq/issues).
  62 +
  63 +### When submitting an issue
  64 +
  65 +**Search** for similar entries before submitting your own, there's a good chance somebody else had the same issue or idea. Show your support with `:+1:` and/or join the discussion.
  66 +
  67 +Please consider the following points when submitting an **issue**:
22 68  
  69 +* Summarize your issue in one sentence (what happened wrong, when you did/expected something else)
  70 +* Describe your issue in detail (including steps to reproduce)
  71 +* Add logs or screen shots when possible
  72 +* Describe your setup (use relevant parts from `sudo -u gitlab -H bundle exec rake gitlab:env:info`)
23 73  
24   -## Code
  74 +## Thank you!
25 75  
26   -Follow our [Developer Guide](https://github.com/gitlabhq/gitlabhq/wiki/Developer-Guide) to set you up for hacking on GitLab.
  76 +By taking the time to use the right channel, you help the development team to organize and prioritize issues and suggestions in order to make GitLab a better product for us all.
... ...
Gemfile
... ... @@ -15,16 +15,18 @@ gem "mysql2", group: :mysql
15 15 gem "pg", group: :postgres
16 16  
17 17 # Auth
18   -gem "devise", "~> 2.1.0"
19   -gem 'omniauth', "~> 1.1.1"
  18 +gem "devise"
  19 +gem 'omniauth', "~> 1.1.3"
20 20 gem 'omniauth-google-oauth2'
21 21 gem 'omniauth-twitter'
22 22 gem 'omniauth-github'
23 23  
24   -# GITLAB patched libs
25   -gem "grit", git: "https://github.com/gitlabhq/grit.git", ref: '9e98418ce2d654485b967003726aa2706a10060b'
26   -gem 'grack', git: "https://github.com/gitlabhq/grack.git", ref: 'ba46f3b0845c6a09d488ae6abdce6ede37e227e8'
27   -gem 'grit_ext', git: "https://github.com/gitlabhq/grit_ext.git", ref: '8e6afc2da821354774aa4d1ee8a1aa2082f84a3e'
  24 +# Extracting information from a git repository
  25 +gem "gitlab-grit", '~> 1.0.0', require: 'grit'
  26 +gem 'grit_ext', '~> 0.6.2'
  27 +
  28 +# Ruby/Rack Git Smart-HTTP Server Handler
  29 +gem 'gitlab-grack', '~> 1.0.0', require: 'grack'
28 30  
29 31 # LDAP Auth
30 32 gem 'gitlab_omniauth-ldap', '1.0.2', require: "omniauth-ldap"
... ... @@ -33,7 +35,7 @@ gem 'gitlab_omniauth-ldap', '1.0.2', require: "omniauth-ldap"
33 35 gem 'gitlab_yaml_db', '1.0.0', require: "yaml_db"
34 36  
35 37 # Syntax highlighter
36   -gem "pygments.rb", git: "https://github.com/gitlabhq/pygments.rb.git", branch: "master"
  38 +gem "gitlab-pygments.rb", '~> 0.3.2', require: 'pygments.rb'
37 39  
38 40 # Language detection
39 41 gem "github-linguist", "~> 2.3.4" , require: "linguist"
... ... @@ -46,14 +48,17 @@ gem "grape-entity", "~> 0.2.0"
46 48 # based on human-friendly examples
47 49 gem "stamp"
48 50  
  51 +# Enumeration fields
  52 +gem 'enumerize'
  53 +
49 54 # Pagination
50 55 gem "kaminari", "~> 0.14.1"
51 56  
52 57 # HAML
53   -gem "haml-rails", "~> 0.3.5"
  58 +gem "haml-rails"
54 59  
55 60 # Files attachments
56   -gem "carrierwave", "~> 0.7.1"
  61 +gem "carrierwave"
57 62  
58 63 # Authorization
59 64 gem "six"
... ... @@ -69,7 +74,7 @@ gem "redcarpet", "~> 2.2.2"
69 74 gem "github-markup", "~> 0.7.4", require: 'github/markup'
70 75  
71 76 # Servers
72   -gem "unicorn", "~> 4.4.0"
  77 +gem "unicorn"
73 78  
74 79 # State machine
75 80 gem "state_machine"
... ... @@ -78,12 +83,12 @@ gem "state_machine"
78 83 gem "acts-as-taggable-on", "2.3.3"
79 84  
80 85 # Decorators
81   -gem "draper", "~> 0.18.0"
  86 +gem "draper"
82 87  
83 88 # Background jobs
84 89 gem 'slim'
85 90 gem 'sinatra', require: nil
86   -gem 'sidekiq', '2.7.3'
  91 +gem 'sidekiq'
87 92  
88 93 # HTTP requests
89 94 gem "httparty"
... ... @@ -113,6 +118,7 @@ group :assets do
113 118 gem 'bootstrap-sass', "2.2.1.1"
114 119 gem "font-awesome-sass-rails", "~> 3.0.0"
115 120 gem "gemoji", "~> 1.2.1", require: 'emoji/railtie'
  121 + gem "gon"
116 122 end
117 123  
118 124 group :development do
... ... @@ -140,7 +146,7 @@ group :development, :test do
140 146 gem "capybara", '2.0.2'
141 147 gem "pry"
142 148 gem "awesome_print"
143   - gem "database_cleaner", ref: "9f898fc50d87a5d51760f9dcf374bf5ffda21baf", git: "https://github.com/bmabey/database_cleaner.git"
  149 + gem "database_cleaner"
144 150 gem "launchy"
145 151 gem 'factory_girl_rails'
146 152  
... ...
Gemfile.lock
1 1 GIT
2   - remote: https://github.com/bmabey/database_cleaner.git
3   - revision: 9f898fc50d87a5d51760f9dcf374bf5ffda21baf
4   - ref: 9f898fc50d87a5d51760f9dcf374bf5ffda21baf
5   - specs:
6   - database_cleaner (0.9.1)
7   -
8   -GIT
9 2 remote: https://github.com/ctran/annotate_models.git
10 3 revision: be4e26825b521f0b2d86b181e2dff89901aa9b1e
11 4 specs:
... ... @@ -14,41 +7,6 @@ GIT
14 7 rake (>= 0.8.7)
15 8  
16 9 GIT
17   - remote: https://github.com/gitlabhq/grack.git
18   - revision: ba46f3b0845c6a09d488ae6abdce6ede37e227e8
19   - ref: ba46f3b0845c6a09d488ae6abdce6ede37e227e8
20   - specs:
21   - grack (1.0.0)
22   - rack (~> 1.4.1)
23   -
24   -GIT
25   - remote: https://github.com/gitlabhq/grit.git
26   - revision: 9e98418ce2d654485b967003726aa2706a10060b
27   - ref: 9e98418ce2d654485b967003726aa2706a10060b
28   - specs:
29   - grit (2.5.0)
30   - diff-lcs (~> 1.1)
31   - mime-types (~> 1.15)
32   - posix-spawn (~> 0.3.6)
33   -
34   -GIT
35   - remote: https://github.com/gitlabhq/grit_ext.git
36   - revision: 8e6afc2da821354774aa4d1ee8a1aa2082f84a3e
37   - ref: 8e6afc2da821354774aa4d1ee8a1aa2082f84a3e
38   - specs:
39   - grit_ext (0.6.1)
40   - charlock_holmes (~> 0.6.9)
41   -
42   -GIT
43   - remote: https://github.com/gitlabhq/pygments.rb.git
44   - revision: db1da0343adf86b49bdc3add04d02d2e80438d38
45   - branch: master
46   - specs:
47   - pygments.rb (0.3.2)
48   - posix-spawn (~> 0.3.6)
49   - yajl-ruby (~> 1.1.0)
50   -
51   -GIT
52 10 remote: https://github.com/gitlabhq/raphael-rails.git
53 11 revision: cb2c92a040b9b941a5f1aa1ea866cc26e944fe58
54 12 specs:
... ... @@ -89,12 +47,13 @@ GEM
89 47 addressable (2.3.2)
90 48 arel (3.0.2)
91 49 awesome_print (1.1.0)
92   - backports (2.6.5)
  50 + backports (2.6.7)
93 51 bcrypt-ruby (3.0.1)
94 52 better_errors (0.3.2)
95 53 coderay (>= 1.0.0)
96 54 erubis (>= 2.7.0)
97   - binding_of_caller (0.6.8)
  55 + binding_of_caller (0.7.1)
  56 + debug_inspector (>= 0.0.1)
98 57 bootstrap-sass (2.2.1.1)
99 58 sass (~> 3.2)
100 59 builder (3.0.4)
... ... @@ -105,7 +64,7 @@ GEM
105 64 rack-test (>= 0.5.4)
106 65 selenium-webdriver (~> 2.0)
107 66 xpath (~> 1.0.0)
108   - carrierwave (0.7.1)
  67 + carrierwave (0.8.0)
109 68 activemodel (>= 3.2.0)
110 69 activesupport (>= 3.2.0)
111 70 celluloid (0.12.4)
... ... @@ -132,18 +91,24 @@ GEM
132 91 connection_pool (1.0.0)
133 92 crack (0.3.1)
134 93 daemons (1.1.9)
135   - devise (2.1.2)
  94 + database_cleaner (0.9.1)
  95 + debug_inspector (0.0.2)
  96 + descendants_tracker (0.0.1)
  97 + devise (2.2.3)
136 98 bcrypt-ruby (~> 3.0)
137 99 orm_adapter (~> 0.1)
138 100 railties (~> 3.1)
139 101 warden (~> 1.2.1)
140 102 diff-lcs (1.1.3)
141   - draper (0.18.0)
142   - actionpack (~> 3.2)
143   - activesupport (~> 3.2)
  103 + draper (1.1.0)
  104 + actionpack (>= 3.0)
  105 + activesupport (>= 3.0)
  106 + request_store (~> 1.0.3)
144 107 email_spec (1.4.0)
145 108 launchy (~> 2.1)
146 109 mail (~> 2.2)
  110 + enumerize (0.5.1)
  111 + activesupport (>= 3.2)
147 112 erubis (2.7.0)
148 113 escape_utils (0.2.4)
149 114 eventmachine (1.0.0)
... ... @@ -155,7 +120,7 @@ GEM
155 120 factory_girl_rails (4.1.0)
156 121 factory_girl (~> 4.1.0)
157 122 railties (>= 3.0.0)
158   - faraday (0.8.4)
  123 + faraday (0.8.6)
159 124 multipart-post (~> 1.1)
160 125 faye-websocket (0.4.7)
161 126 eventmachine (>= 0.12.0)
... ... @@ -164,7 +129,7 @@ GEM
164 129 font-awesome-sass-rails (3.0.0.1)
165 130 railties (>= 3.1.1)
166 131 sass-rails (>= 3.1.1)
167   - foreman (0.60.2)
  132 + foreman (0.61.0)
168 133 thor (>= 0.13.6)
169 134 gemoji (1.2.1)
170 135 gherkin-ruby (0.2.1)
... ... @@ -174,7 +139,16 @@ GEM
174 139 escape_utils (~> 0.2.3)
175 140 mime-types (~> 1.19)
176 141 pygments.rb (>= 0.2.13)
177   - github-markup (0.7.4)
  142 + github-markup (0.7.5)
  143 + gitlab-grack (1.0.0)
  144 + rack (~> 1.4.1)
  145 + gitlab-grit (1.0.0)
  146 + diff-lcs (~> 1.1)
  147 + mime-types (~> 1.15)
  148 + posix-spawn (~> 0.3.6)
  149 + gitlab-pygments.rb (0.3.2)
  150 + posix-spawn (~> 0.3.6)
  151 + yajl-ruby (~> 1.1.0)
178 152 gitlab_meta (5.0)
179 153 gitlab_omniauth-ldap (1.0.2)
180 154 net-ldap (~> 0.2.2)
... ... @@ -182,17 +156,22 @@ GEM
182 156 pyu-ruby-sasl (~> 0.0.3.1)
183 157 rubyntlm (~> 0.1.1)
184 158 gitlab_yaml_db (1.0.0)
185   - grape (0.3.1)
  159 + gon (4.0.2)
  160 + grape (0.3.2)
186 161 activesupport
187   - grape-entity (~> 0.2.0)
188   - hashie (~> 1.2)
  162 + builder
  163 + hashie (>= 1.2.0)
189 164 multi_json (>= 1.3.2)
190   - multi_xml
  165 + multi_xml (>= 0.5.2)
191 166 rack
192 167 rack-accept
193 168 rack-mount
194 169 virtus
195 170 grape-entity (0.2.0)
  171 + activesupport
  172 + multi_json (>= 1.3.2)
  173 + grit_ext (0.6.2)
  174 + charlock_holmes (~> 0.6.9)
196 175 growl (1.0.3)
197 176 guard (1.5.4)
198 177 listen (>= 0.4.2)
... ... @@ -205,20 +184,21 @@ GEM
205 184 guard-spinach (0.0.2)
206 185 guard (>= 1.1)
207 186 spinach
208   - haml (3.1.7)
209   - haml-rails (0.3.5)
  187 + haml (4.0.0)
  188 + tilt
  189 + haml-rails (0.4)
210 190 actionpack (>= 3.1, < 4.1)
211 191 activesupport (>= 3.1, < 4.1)
212   - haml (~> 3.1)
  192 + haml (>= 3.1, < 4.1)
213 193 railties (>= 3.1, < 4.1)
214 194 hashie (1.2.0)
215 195 hike (1.2.1)
216 196 http_parser.rb (0.5.3)
217   - httparty (0.9.0)
  197 + httparty (0.10.2)
218 198 multi_json (~> 1.0)
219   - multi_xml
  199 + multi_xml (>= 0.5.2)
220 200 httpauth (0.2.0)
221   - i18n (0.6.1)
  201 + i18n (0.6.4)
222 202 journey (1.0.4)
223 203 jquery-atwho-rails (0.1.7)
224 204 jquery-rails (2.1.3)
... ... @@ -233,7 +213,7 @@ GEM
233 213 kaminari (0.14.1)
234 214 actionpack (>= 3.0.0)
235 215 activesupport (>= 3.0.0)
236   - kgio (2.7.4)
  216 + kgio (2.8.0)
237 217 launchy (2.1.2)
238 218 addressable (~> 2.3)
239 219 letter_opener (1.0.0)
... ... @@ -250,22 +230,22 @@ GEM
250 230 modernizr (2.6.2)
251 231 sprockets (~> 2.0)
252 232 multi_json (1.6.1)
253   - multi_xml (0.5.1)
254   - multipart-post (1.1.5)
  233 + multi_xml (0.5.3)
  234 + multipart-post (1.2.0)
255 235 mysql2 (0.3.11)
256 236 net-ldap (0.2.2)
257 237 nokogiri (1.5.6)
258 238 oauth (0.4.7)
259   - oauth2 (0.8.0)
  239 + oauth2 (0.8.1)
260 240 faraday (~> 0.8)
261 241 httpauth (~> 0.1)
262 242 jwt (~> 0.1.4)
263 243 multi_json (~> 1.0)
264 244 rack (~> 1.2)
265   - omniauth (1.1.1)
  245 + omniauth (1.1.3)
266 246 hashie (~> 1.2)
267 247 rack
268   - omniauth-github (1.0.3)
  248 + omniauth-github (1.1.0)
269 249 omniauth (~> 1.0)
270 250 omniauth-oauth2 (~> 1.1)
271 251 omniauth-google-oauth2 (0.1.13)
... ... @@ -293,6 +273,9 @@ GEM
293 273 coderay (~> 1.0.5)
294 274 method_source (~> 0.8)
295 275 slop (~> 3.3.1)
  276 + pygments.rb (0.4.2)
  277 + posix-spawn (~> 0.3.6)
  278 + yajl-ruby (~> 1.1.0)
296 279 pyu-ruby-sasl (0.0.3.3)
297 280 quiet_assets (1.0.1)
298 281 railties (~> 3.1)
... ... @@ -305,7 +288,7 @@ GEM
305 288 rack (>= 1.1.3)
306 289 rack-mount (0.8.3)
307 290 rack (>= 1.0.0)
308   - rack-protection (1.3.2)
  291 + rack-protection (1.4.0)
309 292 rack
310 293 rack-ssl (1.3.3)
311 294 rack
... ... @@ -342,12 +325,13 @@ GEM
342 325 rb-fsevent (0.9.2)
343 326 rb-inotify (0.8.8)
344 327 ffi (>= 0.5.0)
345   - rdoc (3.12.1)
  328 + rdoc (3.12.2)
346 329 json (~> 1.4)
347 330 redcarpet (2.2.2)
348 331 redis (3.0.2)
349 332 redis-namespace (1.2.1)
350 333 redis (~> 3.0.0)
  334 + request_store (1.0.5)
351 335 rspec (2.12.0)
352 336 rspec-core (~> 2.12.0)
353 337 rspec-expectations (~> 2.12.0)
... ... @@ -381,11 +365,11 @@ GEM
381 365 multi_json (~> 1.0)
382 366 rubyzip
383 367 websocket (~> 1.0.4)
384   - settingslogic (2.0.8)
  368 + settingslogic (2.0.9)
385 369 sexp_processor (4.1.3)
386 370 shoulda-matchers (1.3.0)
387 371 activesupport (>= 3.0.0)
388   - sidekiq (2.7.3)
  372 + sidekiq (2.7.5)
389 373 celluloid (~> 0.12.0)
390 374 connection_pool (~> 1.0)
391 375 multi_json (~> 1)
... ... @@ -395,9 +379,9 @@ GEM
395 379 multi_json (~> 1.0)
396 380 simplecov-html (~> 0.7.1)
397 381 simplecov-html (0.7.1)
398   - sinatra (1.3.3)
399   - rack (~> 1.3, >= 1.3.6)
400   - rack-protection (~> 1.2)
  382 + sinatra (1.3.5)
  383 + rack (~> 1.4)
  384 + rack-protection (~> 1.3)
401 385 tilt (~> 1.3, >= 1.3.3)
402 386 six (0.2.0)
403 387 slim (1.3.6)
... ... @@ -416,7 +400,7 @@ GEM
416 400 multi_json (~> 1.0)
417 401 rack (~> 1.0)
418 402 tilt (~> 1.1, != 1.3.0)
419   - stamp (0.3.0)
  403 + stamp (0.5.0)
420 404 state_machine (1.1.2)
421 405 temple (0.5.5)
422 406 test_after_commit (0.0.1)
... ... @@ -427,7 +411,7 @@ GEM
427 411 eventmachine (>= 0.12.6)
428 412 rack (>= 1.0.0)
429 413 thor (0.17.0)
430   - tilt (1.3.3)
  414 + tilt (1.3.4)
431 415 timers (1.1.0)
432 416 treetop (1.4.12)
433 417 polyglot
... ... @@ -436,12 +420,13 @@ GEM
436 420 uglifier (1.3.0)
437 421 execjs (>= 0.3.0)
438 422 multi_json (~> 1.0, >= 1.0.2)
439   - unicorn (4.4.0)
  423 + unicorn (4.6.2)
440 424 kgio (~> 2.6)
441 425 rack
442 426 raindrops (~> 0.7)
443   - virtus (0.5.2)
  427 + virtus (0.5.4)
444 428 backports (~> 2.6.1)
  429 + descendants_tracker (~> 0.0.1)
445 430 warden (1.2.1)
446 431 rack (>= 1.0)
447 432 webmock (1.9.0)
... ... @@ -463,14 +448,15 @@ DEPENDENCIES
463 448 binding_of_caller
464 449 bootstrap-sass (= 2.2.1.1)
465 450 capybara (= 2.0.2)
466   - carrierwave (~> 0.7.1)
  451 + carrierwave
467 452 chosen-rails (= 0.9.8)
468 453 coffee-rails (~> 3.2.2)
469 454 colored
470   - database_cleaner!
471   - devise (~> 2.1.0)
472   - draper (~> 0.18.0)
  455 + database_cleaner
  456 + devise
  457 + draper
473 458 email_spec
  459 + enumerize
474 460 factory_girl_rails
475 461 ffaker
476 462 font-awesome-sass-rails (~> 3.0.0)
... ... @@ -479,18 +465,20 @@ DEPENDENCIES
479 465 git
480 466 github-linguist (~> 2.3.4)
481 467 github-markup (~> 0.7.4)
  468 + gitlab-grack (~> 1.0.0)
  469 + gitlab-grit (~> 1.0.0)
  470 + gitlab-pygments.rb (~> 0.3.2)
482 471 gitlab_meta (= 5.0)
483 472 gitlab_omniauth-ldap (= 1.0.2)
484 473 gitlab_yaml_db (= 1.0.0)
485   - grack!
  474 + gon
486 475 grape (~> 0.3.1)
487 476 grape-entity (~> 0.2.0)
488   - grit!
489   - grit_ext!
  477 + grit_ext (~> 0.6.2)
490 478 growl
491 479 guard-rspec
492 480 guard-spinach
493   - haml-rails (~> 0.3.5)
  481 + haml-rails
494 482 httparty
495 483 jquery-atwho-rails (= 0.1.7)
496 484 jquery-rails (= 2.1.3)
... ... @@ -500,14 +488,13 @@ DEPENDENCIES
500 488 letter_opener
501 489 modernizr (= 2.6.2)
502 490 mysql2
503   - omniauth (~> 1.1.1)
  491 + omniauth (~> 1.1.3)
504 492 omniauth-github
505 493 omniauth-google-oauth2
506 494 omniauth-twitter
507 495 pg
508 496 poltergeist (= 1.1.0)
509 497 pry
510   - pygments.rb!
511 498 quiet_assets (~> 1.0.1)
512 499 rack-mini-profiler
513 500 rails (= 3.2.12)
... ... @@ -523,7 +510,7 @@ DEPENDENCIES
523 510 seed-fu
524 511 settingslogic
525 512 shoulda-matchers (= 1.3.0)
526   - sidekiq (= 2.7.3)
  513 + sidekiq
527 514 simplecov
528 515 sinatra
529 516 six
... ... @@ -535,5 +522,5 @@ DEPENDENCIES
535 522 therubyracer
536 523 thin
537 524 uglifier (~> 1.3.0)
538   - unicorn (~> 4.4.0)
  525 + unicorn
539 526 webmock
... ...
README.md
1   -# Welcome to GitLab! Self hosted Git management software
  1 +## GitLab: self hosted Git management software
2 2  
  3 +![logo](https://raw.github.com/gitlabhq/gitlabhq/master/public/gitlab_logo.png)
3 4  
4   -## Badges:
  5 +### GitLab allows you to
  6 + * keep your code secure on your own server
  7 + * manage repositories, users and access permissions
  8 + * communicate though issues, line-comments and wiki's
  9 + * perform code reviews with merge requests
  10 +
  11 +### GitLab is
  12 +
  13 +* powered by Ruby on Rails
  14 +* completely free and open source (MIT license)
  15 +* used by 10.000 organization to keep their code secure
  16 +
  17 +### Code status
  18 +
  19 +* [![build status](http://ci.gitlab.org/projects/1/status?ref=master)](http://ci.gitlab.org/projects/1?ref=master) ci.gitlab.org (master branch)
  20 +
  21 +* [![build status](https://secure.travis-ci.org/gitlabhq/gitlabhq.png)](https://travis-ci.org/gitlabhq/gitlabhq) travis-ci.org (master branch)
  22 +
  23 +* [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.png)](https://codeclimate.com/github/gitlabhq/gitlabhq)
5 24  
6   -* master: travis-ci.org [![build status](https://secure.travis-ci.org/gitlabhq/gitlabhq.png)](https://travis-ci.org/gitlabhq/gitlabhq)a
7   -* master: ci.gitlab.org [![CI](http://ci.gitlab.org/projects/1/status?ref=master)](http://ci.gitlab.org/projects/1?ref=master)
8   -* [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/gitlabhq/gitlabhq)
9 25 * [![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.png)](https://gemnasium.com/gitlabhq/gitlabhq)
10 26  
11   -GitLab is a free project and repository management application
  27 +### Resources
12 28  
  29 +* GitLab.org community site: [Homepage](http://gitlab.org) [Screenshots](http://gitlab.org/screenshots/) [Blog](http://blog.gitlab.org/) [Demo](http://demo.gitlabhq.com/users/sign_in)
13 30  
14   -## Application details
  31 +* GitLab.com: [Homepage](http://blog.gitlab.com/) [Hosted pricing](http://blog.gitlab.com/pricing/) [Services](http://blog.gitlab.com/services/) [Blog](http://blog.gitlab.com/blog/)
15 32  
16   -* powered by Ruby on Rails
17   -* its completely free and open source
18   -* distributed under the MIT License
  33 +* GitLab CI: [Readme](https://github.com/gitlabhq/gitlab-ci/blob/master/README.md) of the GitLab open-source continuous integration server
19 34  
20   -## Requirements
  35 +### Requirements
21 36  
22   -* Ubuntu/Debian
  37 +* Ubuntu/Debian*
23 38 * ruby 1.9.3+
24 39 * MySQL
25 40 * git
26 41 * gitlab-shell
27 42 * redis
28 43  
29   -## Install
  44 +* More details are in the [requirements doc](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/requirements.md)
  45 +
  46 +### Installation
  47 +
  48 +You can either follow the "ordinary" Installation guide to install it on a machine or use the Vagrant virtual machine. The Installation guide is recommended to set up a production server. The Vargrant virtual machine is recommended for development since it makes it much easier to set up all the dependencies for integration testing.
  49 +
  50 +* [Installation guide for latest stable release](https://github.com/gitlabhq/gitlabhq/blob/4-2-stable/doc/install/installation.md)
  51 +
  52 +* [Installation guide for the current master branch](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md)
  53 +
  54 +* [Vagrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm)
  55 +
  56 +### Starting
  57 +
  58 +1. The Installation guide contains instructions to download an init script and run that on boot. With the init script you can also start GitLab with:
  59 +
  60 + sudo service gitlab start
  61 +
  62 + or
  63 +
  64 + sudo /etc/init.d/gitlab restart
  65 +
  66 +2. Start it with [Foreman](https://github.com/ddollar/foreman) in development model
  67 +
  68 + bundle exec foreman start -p 3000
  69 +
  70 +3. Start it manually in development mode
  71 +
  72 + bundle exec rails s
  73 + bundle exec rake sidekiq:start
  74 +
  75 +### Running the tests
  76 +
  77 +* Seed the database with
  78 +
  79 + bundle exec rake db:setup RAILS_ENV=test
  80 + bundle exec rake db:seed_fu RAILS_ENV=test
  81 +
  82 +* Run all tests
  83 +
  84 + bundle exec rake gitlab:test
  85 +
  86 +* Rspec unit and functional tests
  87 +
  88 + bundle exec rake spec
  89 +
  90 +* Spinach integration tests
  91 +
  92 + bundle exec rake spinach
  93 +
  94 +### Getting help
  95 +
  96 +* [Troubleshooting guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide)
  97 +
  98 +* [Support forum](https://groups.google.com/forum/#!forum/gitlabhq)
  99 +
  100 +* [Feedback and suggestions forum](http://gitlab.uservoice.com/forums/176466-general)
  101 +
  102 +* [Paid support](http://blog.gitlab.com/support/)
  103 +
  104 +* [Paid services](http://blog.gitlab.com/services/)
  105 +
  106 +### New versions and the API
  107 +
  108 +Each month on the 22th a new version is released together with an upgrade guide.
  109 +
  110 +* [Upgrade guides](https://github.com/gitlabhq/gitlabhq/wiki)
  111 +
  112 +* [Roadmap](https://github.com/gitlabhq/gitlabhq/blob/master/ROADMAP.md)
  113 +
  114 +### Other documentation
  115 +
  116 +* [GitLab API](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/README.md)
  117 +
  118 +* [Rake tasks](https://github.com/gitlabhq/gitlabhq/tree/master/doc/raketasks)
  119 +
  120 +* [GitLab recipes](https://github.com/gitlabhq/gitlab-recipes)
  121 +
  122 +### Getting in touch
30 123  
31   -Checkout [wiki](https://github.com/gitlabhq/gitlabhq/wiki) pages for installation information, migration, etc.
  124 +* [Contributing guide](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md)
32 125  
33   -## [Community](http://gitlab.org/community/)
  126 +* [Core team](https://github.com/gitlabhq?tab=members)
34 127  
35   -## [Contact](http://gitlab.org/contact/)
  128 +* [Contributors](https://github.com/gitlabhq/gitlabhq/graphs/contributors)
36 129  
37   -## Contribute
  130 +* [Leader](https://github.com/randx)
38 131  
39   -[Developer Guide](https://github.com/gitlabhq/gitlabhq/wiki/Developer-Guide)
40   -Want to help - send a pull request.
41   -We'll accept good pull requests.
  132 +* [Contact page](http://gitlab.org/contact/)
... ...
ROADMAP.md
... ... @@ -4,9 +4,4 @@
4 4  
5 5 * Replace gitolite with gitlab-shell
6 6 * Usability improvements
7   -* Notification improvements
8   -
9   -### v4.2 February 22
10   -
11   -* Teams
12   -
  7 +* Notification improvements
13 8 \ No newline at end of file
... ...
app/assets/fonts/OFL.txt
... ... @@ -1,92 +0,0 @@
1   -Copyright (c) 2010, Jan Gerner (post@yanone.de)
2   -This Font Software is licensed under the SIL Open Font License, Version 1.1.
3   -This license is copied below, and is also available with a FAQ at:
4   -http://scripts.sil.org/OFL
5   -
6   -
7   ------------------------------------------------------------
8   -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
9   ------------------------------------------------------------
10   -
11   -PREAMBLE
12   -The goals of the Open Font License (OFL) are to stimulate worldwide
13   -development of collaborative font projects, to support the font creation
14   -efforts of academic and linguistic communities, and to provide a free and
15   -open framework in which fonts may be shared and improved in partnership
16   -with others.
17   -
18   -The OFL allows the licensed fonts to be used, studied, modified and
19   -redistributed freely as long as they are not sold by themselves. The
20   -fonts, including any derivative works, can be bundled, embedded,
21   -redistributed and/or sold with any software provided that any reserved
22   -names are not used by derivative works. The fonts and derivatives,
23   -however, cannot be released under any other type of license. The
24   -requirement for fonts to remain under this license does not apply
25   -to any document created using the fonts or their derivatives.
26   -
27   -DEFINITIONS
28   -"Font Software" refers to the set of files released by the Copyright
29   -Holder(s) under this license and clearly marked as such. This may
30   -include source files, build scripts and documentation.
31   -
32   -"Reserved Font Name" refers to any names specified as such after the
33   -copyright statement(s).
34   -
35   -"Original Version" refers to the collection of Font Software components as
36   -distributed by the Copyright Holder(s).
37   -
38   -"Modified Version" refers to any derivative made by adding to, deleting,
39   -or substituting -- in part or in whole -- any of the components of the
40   -Original Version, by changing formats or by porting the Font Software to a
41   -new environment.
42   -
43   -"Author" refers to any designer, engineer, programmer, technical
44   -writer or other person who contributed to the Font Software.
45   -
46   -PERMISSION & CONDITIONS
47   -Permission is hereby granted, free of charge, to any person obtaining
48   -a copy of the Font Software, to use, study, copy, merge, embed, modify,
49   -redistribute, and sell modified and unmodified copies of the Font
50   -Software, subject to the following conditions:
51   -
52   -1) Neither the Font Software nor any of its individual components,
53   -in Original or Modified Versions, may be sold by itself.
54   -
55   -2) Original or Modified Versions of the Font Software may be bundled,
56   -redistributed and/or sold with any software, provided that each copy
57   -contains the above copyright notice and this license. These can be
58   -included either as stand-alone text files, human-readable headers or
59   -in the appropriate machine-readable metadata fields within text or
60   -binary files as long as those fields can be easily viewed by the user.
61   -
62   -3) No Modified Version of the Font Software may use the Reserved Font
63   -Name(s) unless explicit written permission is granted by the corresponding
64   -Copyright Holder. This restriction only applies to the primary font name as
65   -presented to the users.
66   -
67   -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
68   -Software shall not be used to promote, endorse or advertise any
69   -Modified Version, except to acknowledge the contribution(s) of the
70   -Copyright Holder(s) and the Author(s) or with their explicit written
71   -permission.
72   -
73   -5) The Font Software, modified or unmodified, in part or in whole,
74   -must be distributed entirely under this license, and must not be
75   -distributed under any other license. The requirement for fonts to
76   -remain under this license does not apply to any document created
77   -using the Font Software.
78   -
79   -TERMINATION
80   -This license becomes null and void if any of the above conditions are
81   -not met.
82   -
83   -DISCLAIMER
84   -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
85   -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
86   -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
87   -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
88   -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
89   -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
90   -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
91   -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
92   -OTHER DEALINGS IN THE FONT SOFTWARE.
app/assets/fonts/YanoneKaffeesatz-Light.ttf
No preview for this file type
app/assets/javascripts/branch-graph.js 0 → 100644
... ... @@ -0,0 +1,400 @@
  1 +!function(){
  2 +
  3 + var BranchGraph = function(element, options){
  4 + this.element = element;
  5 + this.options = options;
  6 +
  7 + this.preparedCommits = {};
  8 + this.mtime = 0;
  9 + this.mspace = 0;
  10 + this.parents = {};
  11 + this.colors = ["#000"];
  12 +
  13 + this.load();
  14 + };
  15 +
  16 + BranchGraph.prototype.load = function(){
  17 + $.ajax({
  18 + url: this.options.url,
  19 + method: 'get',
  20 + dataType: 'json',
  21 + success: $.proxy(function(data){
  22 + $('.loading', this.element).hide();
  23 + this.prepareData(data.days, data.commits);
  24 + this.buildGraph();
  25 + }, this)
  26 + });
  27 + };
  28 +
  29 + BranchGraph.prototype.prepareData = function(days, commits){
  30 + this.days = days;
  31 + this.dayCount = days.length;
  32 + this.commits = commits;
  33 + this.commitCount = commits.length;
  34 +
  35 + this.collectParents();
  36 +
  37 + this.mtime += 4;
  38 + this.mspace += 10;
  39 + for (var i = 0; i < this.commitCount; i++) {
  40 + if (this.commits[i].id in this.parents) {
  41 + this.commits[i].isParent = true;
  42 + }
  43 + this.preparedCommits[this.commits[i].id] = this.commits[i];
  44 + }
  45 + this.collectColors();
  46 + };
  47 +
  48 + BranchGraph.prototype.collectParents = function(){
  49 + for (var i = 0; i < this.commitCount; i++) {
  50 + for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) {
  51 + this.parents[this.commits[i].parents[j][0]] = true;
  52 + }
  53 + this.mtime = Math.max(this.mtime, this.commits[i].time);
  54 + this.mspace = Math.max(this.mspace, this.commits[i].space);
  55 + }
  56 + };
  57 +
  58 + BranchGraph.prototype.collectColors = function(){
  59 + for (var k = 0; k < this.mspace; k++) {
  60 + this.colors.push(Raphael.getColor(.8));
  61 + // Skipping a few colors in the spectrum to get more contrast between colors
  62 + Raphael.getColor();Raphael.getColor();
  63 + }
  64 + };
  65 +
  66 + BranchGraph.prototype.buildGraph = function(){
  67 + var graphWidth = $(this.element).width()
  68 + , ch = this.mspace * 20 + 100
  69 + , cw = Math.max(graphWidth, this.mtime * 20 + 260)
  70 + , r = Raphael(this.element.get(0), cw, ch)
  71 + , top = r.set()
  72 + , cuday = 0
  73 + , cumonth = ""
  74 + , offsetX = 20
  75 + , offsetY = 60
  76 + , barWidth = Math.max(graphWidth, this.dayCount * 20 + 320)
  77 + , scrollLeft = cw;
  78 +
  79 + this.raphael = r;
  80 +
  81 + r.rect(0, 0, barWidth, 20).attr({fill: "#222"});
  82 + r.rect(0, 20, barWidth, 20).attr({fill: "#444"});
  83 +
  84 + for (mm = 0; mm < this.dayCount; mm++) {
  85 + if(this.days[mm] != null){
  86 + if(cuday != this.days[mm][0]){
  87 + // Dates
  88 + r.text(offsetX + mm * 20, 31, this.days[mm][0]).attr({
  89 + font: "12px Monaco, monospace",
  90 + fill: "#DDD"
  91 + });
  92 + cuday = this.days[mm][0];
  93 + }
  94 + if(cumonth != this.days[mm][1]){
  95 + // Months
  96 + r.text(offsetX + mm * 20, 11, this.days[mm][1]).attr({
  97 + font: "12px Monaco, monospace",
  98 + fill: "#EEE"
  99 + });
  100 + cumonth = this.days[mm][1];
  101 + }
  102 + }
  103 + }
  104 +
  105 + for (i = 0; i < this.commitCount; i++) {
  106 + var x = offsetX + 20 * this.commits[i].time
  107 + , y = offsetY + 10 * this.commits[i].space
  108 + , c
  109 + , ps;
  110 +
  111 + // Draw dot
  112 + r.circle(x, y, 3).attr({
  113 + fill: this.colors[this.commits[i].space],
  114 + stroke: "none"
  115 + });
  116 +
  117 + // Draw lines
  118 + for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) {
  119 + c = this.preparedCommits[this.commits[i].parents[j][0]];
  120 + ps = this.commits[i].parent_spaces[j];
  121 + if (c) {
  122 + var cx = offsetX + 20 * c.time
  123 + , cy = offsetY + 10 * c.space
  124 + , psy = offsetY + 10 * ps;
  125 + if (c.space == this.commits[i].space && c.space == ps) {
  126 + r.path([
  127 + "M", x, y,
  128 + "L", cx, cy
  129 + ]).attr({
  130 + stroke: this.colors[c.space],
  131 + "stroke-width": 2
  132 + });
  133 +
  134 + } else if (c.space < this.commits[i].space) {
  135 + if (y == psy) {
  136 + r.path([
  137 + "M", x - 5, y,
  138 + "l-5,-2,0,4,5,-2",
  139 + "L", x - 10, y,
  140 + "L", x - 15, psy,
  141 + "L", cx + 5, psy,
  142 + "L", cx, cy])
  143 + .attr({
  144 + stroke: this.colors[this.commits[i].space],
  145 + "stroke-width": 2
  146 + });
  147 + } else {
  148 + r.path([
  149 + "M", x - 3, y - 6,
  150 + "l-4,-3,4,-2,0,5",
  151 + "L", x - 5, y - 10,
  152 + "L", x - 10, psy,
  153 + "L", cx + 5, psy,
  154 + "L", cx, cy])
  155 + .attr({
  156 + stroke: this.colors[this.commits[i].space],
  157 + "stroke-width": 2
  158 + });
  159 + }
  160 + } else {
  161 + r.path([
  162 + "M", x - 3, y + 6,
  163 + "l-4,3,4,2,0,-5",
  164 + "L", x - 5, y + 10,
  165 + "L", x - 10, psy,
  166 + "L", cx + 5, psy,
  167 + "L", cx, cy])
  168 + .attr({
  169 + stroke: this.colors[c.space],
  170 + "stroke-width": 2
  171 + });
  172 + }
  173 + }
  174 + }
  175 +
  176 + if (this.commits[i].refs) {
  177 + this.appendLabel(x, y, this.commits[i].refs);
  178 + }
  179 +
  180 + // mark commit and displayed in the center
  181 + if (this.commits[i].id == this.options.commit_id) {
  182 + r.path([
  183 + 'M', x, y - 5,
  184 + 'L', x + 4, y - 15,
  185 + 'L', x - 4, y - 15,
  186 + 'Z'
  187 + ]).attr({
  188 + "fill": "#000",
  189 + "fill-opacity": .7,
  190 + "stroke": "none"
  191 + });
  192 + scrollLeft = x - graphWidth / 2;
  193 + }
  194 +
  195 + this.appendAnchor(top, this.commits[i], x, y);
  196 + }
  197 + top.toFront();
  198 + this.element.scrollLeft(scrollLeft);
  199 + this.bindEvents();
  200 + };
  201 +
  202 + BranchGraph.prototype.bindEvents = function(){
  203 + var drag = {}
  204 + , element = this.element;
  205 +
  206 + var dragger = function(event){
  207 + element.scrollLeft(drag.sl - (event.clientX - drag.x));
  208 + element.scrollTop(drag.st - (event.clientY - drag.y));
  209 + };
  210 +
  211 + element.on({
  212 + mousedown: function (event) {
  213 + drag = {
  214 + x: event.clientX,
  215 + y: event.clientY,
  216 + st: element.scrollTop(),
  217 + sl: element.scrollLeft()
  218 + };
  219 + $(window).on('mousemove', dragger);
  220 + }
  221 + });
  222 + $(window).on({
  223 + mouseup: function(){
  224 + //bars.animate({opacity: 0}, 300);
  225 + $(window).off('mousemove', dragger);
  226 + },
  227 + keydown: function(event){
  228 + if(event.keyCode == 37){
  229 + // left
  230 + element.scrollLeft( element.scrollLeft() - 50);
  231 + }
  232 + if(event.keyCode == 38){
  233 + // top
  234 + element.scrollTop( element.scrollTop() - 50);
  235 + }
  236 + if(event.keyCode == 39){
  237 + // right
  238 + element.scrollLeft( element.scrollLeft() + 50);
  239 + }
  240 + if(event.keyCode == 40){
  241 + // bottom
  242 + element.scrollTop( element.scrollTop() + 50);
  243 + }
  244 + }
  245 + });
  246 + };
  247 +
  248 + BranchGraph.prototype.appendLabel = function(x, y, refs){
  249 + var r = this.raphael
  250 + , shortrefs = refs
  251 + , text, textbox, rect;
  252 +
  253 + if (shortrefs.length > 17){
  254 + // Truncate if longer than 15 chars
  255 + shortrefs = shortrefs.substr(0,15) + "…";
  256 + }
  257 +
  258 + text = r.text(x+5, y+8 + 10, shortrefs).attr({
  259 + font: "10px Monaco, monospace",
  260 + fill: "#FFF",
  261 + title: refs
  262 + });
  263 +
  264 + textbox = text.getBBox();
  265 + text.transform([
  266 + 't', textbox.height/-4, textbox.width/2 + 5,
  267 + 'r90'
  268 + ]);
  269 +
  270 + // Create rectangle based on the size of the textbox
  271 + rect = r.rect(x, y, textbox.width + 15, textbox.height + 5, 4).attr({
  272 + "fill": "#000",
  273 + "fill-opacity": .7,
  274 + "stroke": "none"
  275 + });
  276 +
  277 + triangle = r.path([
  278 + 'M', x, y + 5,
  279 + 'L', x + 4, y + 15,
  280 + 'L', x - 4, y + 15,
  281 + 'Z'
  282 + ]).attr({
  283 + "fill": "#000",
  284 + "fill-opacity": .7,
  285 + "stroke": "none"
  286 + });
  287 +
  288 + // Rotate and reposition rectangle over text
  289 + rect.transform([
  290 + 'r', 90, x, y,
  291 + 't', 15, -9
  292 + ]);
  293 +
  294 + // Set text to front
  295 + text.toFront();
  296 + };
  297 +
  298 + BranchGraph.prototype.appendAnchor = function(top, commit, x, y) {
  299 + var r = this.raphael
  300 + , options = this.options
  301 + , anchor;
  302 + anchor = r.circle(x, y, 10).attr({
  303 + fill: "#000",
  304 + opacity: 0,
  305 + cursor: "pointer"
  306 + })
  307 + .click(function(){
  308 + window.open(options.commit_url.replace('%s', commit.id), '_blank');
  309 + })
  310 + .hover(function(){
  311 + this.tooltip = r.commitTooltip(x, y + 5, commit);
  312 + top.push(this.tooltip.insertBefore(this));
  313 + }, function(){
  314 + this.tooltip && this.tooltip.remove() && delete this.tooltip;
  315 + });
  316 + top.push(anchor);
  317 + };
  318 +
  319 + this.BranchGraph = BranchGraph;
  320 +
  321 +}(this);
  322 +Raphael.fn.commitTooltip = function(x, y, commit){
  323 + var icon, nameText, idText, messageText
  324 + , boxWidth = 300
  325 + , boxHeight = 200;
  326 +
  327 + icon = this.image(commit.author.icon, x, y, 20, 20);
  328 + nameText = this.text(x + 25, y + 10, commit.author.name);
  329 + idText = this.text(x, y + 35, commit.id);
  330 + messageText = this.text(x, y + 50, commit.message);
  331 +
  332 + textSet = this.set(icon, nameText, idText, messageText).attr({
  333 + "text-anchor": "start",
  334 + "font": "12px Monaco, monospace"
  335 + });
  336 +
  337 + nameText.attr({
  338 + "font": "14px Arial",
  339 + "font-weight": "bold"
  340 + });
  341 +
  342 + idText.attr({
  343 + "fill": "#AAA"
  344 + });
  345 +
  346 + textWrap(messageText, boxWidth - 50);
  347 +
  348 + var rect = this.rect(x - 10, y - 10, boxWidth, 100, 4).attr({
  349 + "fill": "#FFF",
  350 + "stroke": "#000",
  351 + "stroke-linecap": "round",
  352 + "stroke-width": 2
  353 + });
  354 + var tooltip = this.set(rect, textSet);
  355 +
  356 + rect.attr({
  357 + "height" : tooltip.getBBox().height + 10,
  358 + "width" : tooltip.getBBox().width + 10
  359 + });
  360 +
  361 + tooltip.transform([
  362 + 't', 20, 20
  363 + ]);
  364 +
  365 + return tooltip;
  366 +};
  367 +
  368 +function textWrap(t, width) {
  369 + var content = t.attr("text");
  370 + var abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  371 + t.attr({
  372 + "text" : abc
  373 + });
  374 + var letterWidth = t.getBBox().width / abc.length;
  375 +
  376 + t.attr({
  377 + "text" : content
  378 + });
  379 +
  380 + var words = content.split(" ");
  381 + var x = 0, s = [];
  382 + for ( var i = 0; i < words.length; i++) {
  383 +
  384 + var l = words[i].length;
  385 + if (x + (l * letterWidth) > width) {
  386 + s.push("\n");
  387 + x = 0;
  388 + }
  389 + x += l * letterWidth;
  390 + s.push(words[i] + " ");
  391 + }
  392 + t.attr({
  393 + "text" : s.join("")
  394 + });
  395 + var b = t.getBBox()
  396 + , h = Math.abs(b.y2) - Math.abs(b.y) + 1;
  397 + t.attr({
  398 + "y": b.y + h
  399 + });
  400 +}
... ...
app/assets/javascripts/main.js.coffee
... ... @@ -54,10 +54,10 @@ $ -&gt;
54 54 $(@).parents('form').submit()
55 55  
56 56 # Flash
57   - if (flash = $("#flash-container")).length > 0
58   - flash.click -> $(@).slideUp("slow")
59   - flash.slideDown "slow"
60   - setTimeout (-> flash.slideUp("slow")), 3000
  57 + if (flash = $(".flash-container")).length > 0
  58 + flash.click -> $(@).fadeOut()
  59 + flash.show()
  60 + setTimeout (-> flash.fadeOut()), 3000
61 61  
62 62 # Disable form buttons while a form is submitting
63 63 $('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
... ...
app/assets/javascripts/projects.js.coffee
... ... @@ -18,3 +18,18 @@ $ -&gt;
18 18 # Ref switcher
19 19 $('.project-refs-select').on 'change', ->
20 20 $(@).parents('form').submit()
  21 +
  22 + $('#project_issues_enabled').change ->
  23 + if ($(this).is(':checked') == true)
  24 + $('#project_issues_tracker').removeAttr('disabled')
  25 + else
  26 + $('#project_issues_tracker').attr('disabled', 'disabled')
  27 +
  28 + $('#project_issues_tracker').change()
  29 +
  30 + $('#project_issues_tracker').change ->
  31 + if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled'))
  32 + $('#project_issues_tracker_id').attr('disabled', 'disabled')
  33 + else
  34 + $('#project_issues_tracker_id').removeAttr('disabled')
  35 +
... ...
app/assets/stylesheets/common.scss
... ... @@ -67,27 +67,17 @@ table a code {
67 67 }
68 68  
69 69 /** FLASH message **/
70   -#flash-container {
71   - height: 50px;
72   - position: fixed;
73   - z-index: 10001;
74   - top: 0px;
75   - width: 100%;
76   - margin-bottom: 15px;
77   - overflow: hidden;
78   - background: white;
79   - cursor: pointer;
80   - border-bottom: 1px solid #ccc;
81   - text-align: center;
  70 +.flash-container {
82 71 display: none;
  72 + .alert {
  73 + cursor: pointer;
  74 + margin: 0;
  75 + text-align: center;
  76 + border-radius: 0;
83 77  
84   - h4 {
85   - color: #666;
86   - font-size: 18px;
87   - line-height: 38px;
88   - padding-top: 5px;
89   - margin: 2px;
90   - font-weight: normal;
  78 + span {
  79 + font-size: 14px;
  80 + }
91 81 }
92 82 }
93 83  
... ... @@ -203,10 +193,6 @@ input[type=text] {
203 193 }
204 194 }
205 195  
206   -input.git_clone_url {
207   - width: 325px;
208   -}
209   -
210 196 .merge-request-form-holder {
211 197 select {
212 198 width: 300px;
... ...
app/assets/stylesheets/gitlab_bootstrap/common.scss
... ... @@ -30,6 +30,8 @@
30 30 border-color: #DDD;
31 31 }
32 32  
  33 +.well { padding: 15px; }
  34 +
33 35 /** HELPERS **/
34 36 .nothing_here_message {
35 37 text-align: center;
... ...
app/assets/stylesheets/gitlab_bootstrap/fonts.scss
1   -@font-face{
2   - font-family: Yanone;
3   - src: font-url('YanoneKaffeesatz-Light.ttf');
4   -}
5   -
6 1 /** Typo **/
7 2 $monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
... ...
app/assets/stylesheets/gitlab_bootstrap/mixins.scss
... ... @@ -70,8 +70,19 @@
70 70 @mixin header-font {
71 71 color: $style_color;
72 72 text-shadow: 0 1px 1px #FFF;
73   - font-family: 'Yanone', sans-serif;
74   - font-size: 24px;
75   - line-height: 36px;
  73 + font-size: 18px;
  74 + line-height: 42px;
76 75 font-weight: normal;
  76 + letter-spacing: -1px;
  77 +}
  78 +
  79 +@mixin md-typography {
  80 + code { padding: 0 4px; }
  81 + p { font-size: 13px; }
  82 + h1 { font-size: 26px; line-height: 40px; margin: 10px 0;}
  83 + h2 { font-size: 22px; line-height: 40px; margin: 10px 0;}
  84 + h3 { font-size: 18px; line-height: 40px; margin: 10px 0;}
  85 + h4 { font-size: 16px; line-height: 20px; margin: 10px 0;}
  86 + h5 { font-size: 14px; line-height: 20px; margin: 10px 0;}
  87 + h6 { font-size: 12px; line-height: 20px; margin: 10px 0;}
77 88 }
... ...
app/assets/stylesheets/gitlab_bootstrap/typography.scss
... ... @@ -87,16 +87,15 @@ a:focus {
87 87 *
88 88 */
89 89 .wiki {
  90 + @include md-typography;
  91 +
90 92 font-size: 13px;
  93 + line-height: 20px;
91 94  
92   - code { padding: 0 4px; }
93   - p { font-size: 13px; }
94   - h1 { font-size: 32px; line-height: 40px; margin: 10px 0;}
95   - h2 { font-size: 26px; line-height: 40px; margin: 10px 0;}
96   - h3 { font-size: 22px; line-height: 40px; margin: 10px 0;}
97   - h4 { font-size: 18px; line-height: 20px; margin: 10px 0;}
98   - h5 { font-size: 14px; line-height: 20px; margin: 10px 0;}
99   - h6 { font-size: 12px; line-height: 20px; margin: 10px 0;}
100 95 .white .highlight pre { background: #f5f5f5; }
101 96 ul { margin: 0 0 9px 25px !important; }
102 97 }
  98 +
  99 +.md {
  100 + @include md-typography;
  101 +}
... ...
app/assets/stylesheets/highlight/dark.scss
1 1 .black .highlight {
2   - background-color: #333;
3 2 pre {
  3 + background-color: #333;
4 4 color: #eee;
5   - background: inherit;
6 5 }
7 6  
8 7 .hll { display: block; background-color: darken($hover, 65%) }
... ...
app/assets/stylesheets/sections/commits.scss
... ... @@ -100,8 +100,9 @@
100 100 }
101 101 }
102 102 .line_content {
  103 + display: block;
103 104 white-space: pre;
104   - height: 14px;
  105 + height: 18px;
105 106 margin: 0px;
106 107 padding: 0px;
107 108 border: none;
... ...
app/assets/stylesheets/sections/events.scss
... ... @@ -48,15 +48,13 @@
48 48 color: #666;
49 49 }
50 50 .event-note {
51   - padding-top: 5px;
52   - padding-left: 5px;
53   - display: inline-block;
54 51 color: #555;
  52 + margin-top: 5px;
  53 + margin-left: 40px;
55 54  
56 55 .note-file-attach {
57   - margin-left: -25px;
58   - float: left;
59 56 .note-image-attach {
  57 + margin-top: 4px;
60 58 margin-left: 0px;
61 59 max-width: 200px;
62 60 }
... ... @@ -66,8 +64,8 @@
66 64 color: #777;
67 65 float: left;
68 66 font-size: 16px;
69   - line-height: 18px;
70   - margin: 5px;
  67 + line-height: 16px;
  68 + margin-right: 5px;
71 69 }
72 70 }
73 71 .avatar {
... ...
app/assets/stylesheets/sections/header.scss
... ... @@ -67,7 +67,7 @@ header {
67 67 position: relative;
68 68 float: left;
69 69 margin: 0;
70   - margin-left: 15px;
  70 + margin-left: 10px;
71 71 @include header-font;
72 72 }
73 73  
... ...
app/assets/stylesheets/sections/login.scss
1 1 /* Login Page */
2 2 body.login-page{
3   - padding-top: 7%;
4   - background: #666;
  3 + background: #EEE;
  4 + .container .content { padding-top: 5%; }
5 5 }
6 6  
7 7 .login-box{
... ...
app/assets/stylesheets/sections/notes.scss
... ... @@ -83,6 +83,7 @@ ul.notes {
83 83 margin-top: -20px;
84 84 }
85 85 .note-body {
  86 + @include md-typography;
86 87 margin-left: 45px;
87 88 }
88 89 .note-header {
... ...
app/assets/stylesheets/sections/projects.scss
... ... @@ -80,6 +80,7 @@
80 80 border: 1px solid #BBB;
81 81 box-shadow: none;
82 82 margin-left: -1px;
  83 + background: #FFF;
83 84 }
84 85 }
85 86  
... ...
app/contexts/issues_list_context.rb
... ... @@ -7,12 +7,13 @@ class IssuesListContext &lt; BaseContext
7 7 @issues = case params[:status]
8 8 when issues_filter[:all] then @project.issues
9 9 when issues_filter[:closed] then @project.issues.closed
10   - when issues_filter[:to_me] then @project.issues.opened.assigned(current_user)
  10 + when issues_filter[:to_me] then @project.issues.assigned(current_user)
  11 + when issues_filter[:by_me] then @project.issues.authored(current_user)
11 12 else @project.issues.opened
12 13 end
13 14  
14 15 @issues = @issues.tagged_with(params[:label_name]) if params[:label_name].present?
15   - @issues = @issues.includes(:author, :project).order("updated_at")
  16 + @issues = @issues.includes(:author, :project)
16 17  
17 18 # Filter by specific assignee_id (or lack thereof)?
18 19 if params[:assignee_id].present?
... ...
app/controllers/admin/teams/members_controller.rb
1 1 class Admin::Teams::MembersController < Admin::Teams::ApplicationController
2 2 def new
3 3 @users = User.potential_team_members(user_team)
4   - @users = UserDecorator.decorate @users
  4 + @users = UserDecorator.decorate_collection @users
5 5 end
6 6  
7 7 def create
... ...
app/controllers/admin/users_controller.rb
... ... @@ -45,7 +45,7 @@ class Admin::UsersController &lt; Admin::ApplicationController
45 45 end
46 46  
47 47 def unblock
48   - if admin_user.update_attribute(:blocked, false)
  48 + if admin_user.activate
49 49 redirect_to :back, alert: "Successfully unblocked"
50 50 else
51 51 redirect_to :back, alert: "Error occured. User was not unblocked"
... ...
app/controllers/application_controller.rb
... ... @@ -5,6 +5,7 @@ class ApplicationController &lt; ActionController::Base
5 5 before_filter :add_abilities
6 6 before_filter :dev_tools if Rails.env == 'development'
7 7 before_filter :default_headers
  8 + before_filter :add_gon_variables
8 9  
9 10 protect_from_forgery
10 11  
... ... @@ -29,7 +30,7 @@ class ApplicationController &lt; ActionController::Base
29 30 end
30 31  
31 32 def reject_blocked!
32   - if current_user && current_user.blocked
  33 + if current_user && current_user.blocked?
33 34 sign_out current_user
34 35 flash[:alert] = "Your account is blocked. Retry when an admin unblock it."
35 36 redirect_to new_user_session_path
... ... @@ -37,7 +38,7 @@ class ApplicationController &lt; ActionController::Base
37 38 end
38 39  
39 40 def after_sign_in_path_for resource
40   - if resource.is_a?(User) && resource.respond_to?(:blocked) && resource.blocked
  41 + if resource.is_a?(User) && resource.respond_to?(:blocked?) && resource.blocked?
41 42 sign_out resource
42 43 flash[:alert] = "Your account is blocked. Retry when an admin unblock it."
43 44 new_user_session_path
... ... @@ -148,4 +149,8 @@ class ApplicationController &lt; ActionController::Base
148 149 headers['X-Frame-Options'] = 'DENY'
149 150 headers['X-XSS-Protection'] = '1; mode=block'
150 151 end
  152 +
  153 + def add_gon_variables
  154 + gon.default_issues_tracker = Project.issues_tracker.default_value
  155 + end
151 156 end
... ...
app/controllers/commits_controller.rb
... ... @@ -13,7 +13,7 @@ class CommitsController &lt; ProjectResourceController
13 13 @limit, @offset = (params[:limit] || 40), (params[:offset] || 0)
14 14  
15 15 @commits = @repo.commits(@ref, @path, @limit, @offset)
16   - @commits = CommitDecorator.decorate(@commits)
  16 + @commits = CommitDecorator.decorate_collection(@commits)
17 17  
18 18 respond_to do |format|
19 19 format.html # index.html.erb
... ...
app/controllers/compare_controller.rb
... ... @@ -16,7 +16,7 @@ class CompareController &lt; ProjectResourceController
16 16 @refs_are_same = result[:same]
17 17 @line_notes = []
18 18  
19   - @commits = CommitDecorator.decorate(@commits)
  19 + @commits = CommitDecorator.decorate_collection(@commits)
20 20 end
21 21  
22 22 def create
... ...
app/controllers/graph_controller.rb
1 1 class GraphController < ProjectResourceController
2 2 include ExtractsPath
  3 + include ApplicationHelper
3 4  
4 5 # Authorize
5 6 before_filter :authorize_read_project!
... ... @@ -20,7 +21,10 @@ class GraphController &lt; ProjectResourceController
20 21 respond_to do |format|
21 22 format.html
22 23 format.json do
23   - graph = Gitlab::Graph::JsonBuilder.new(project, @ref, @commit)
  24 + graph = Graph::JsonBuilder.new(project, @ref, @commit)
  25 + graph.commits.each do |c|
  26 + c.icon = gravatar_icon(c.author.email)
  27 + end
24 28 render :json => graph.to_json
25 29 end
26 30 end
... ...
app/controllers/merge_requests_controller.rb
... ... @@ -81,7 +81,8 @@ class MergeRequestsController &lt; ProjectResourceController
81 81 end
82 82  
83 83 def automerge
84   - return access_denied! unless can?(current_user, :accept_mr, @project)
  84 + return access_denied! unless allowed_to_merge?
  85 +
85 86 if @merge_request.opened? && @merge_request.can_be_merged?
86 87 @merge_request.should_remove_source_branch = params[:should_remove_source_branch]
87 88 @merge_request.automerge!(current_user)
... ... @@ -142,6 +143,19 @@ class MergeRequestsController &lt; ProjectResourceController
142 143 # Get commits from repository
143 144 # or from cache if already merged
144 145 @commits = @merge_request.commits
145   - @commits = CommitDecorator.decorate(@commits)
  146 + @commits = CommitDecorator.decorate_collection(@commits)
  147 +
  148 + @allowed_to_merge = allowed_to_merge?
  149 + @show_merge_controls = @merge_request.opened? && @commits.any? && @allowed_to_merge
  150 + end
  151 +
  152 + def allowed_to_merge?
  153 + action = if project.protected_branch?(@merge_request.target_branch)
  154 + :push_code_to_protected_branches
  155 + else
  156 + :push_code
  157 + end
  158 +
  159 + can?(current_user, action, @project)
146 160 end
147 161 end
... ...
app/controllers/milestones_controller.rb
... ... @@ -32,7 +32,7 @@ class MilestonesController &lt; ProjectResourceController
32 32  
33 33 def show
34 34 @issues = @milestone.issues
35   - @users = UserDecorator.decorate(@milestone.participants)
  35 + @users = UserDecorator.decorate_collection(@milestone.participants)
36 36 @merge_requests = @milestone.merge_requests
37 37  
38 38 respond_to do |format|
... ...
app/controllers/projects_controller.rb
1   -require Rails.root.join('lib', 'gitlab', 'graph', 'json_builder')
2   -
3 1 class ProjectsController < ProjectResourceController
4 2 skip_before_filter :project, only: [:new, :create]
5 3 skip_before_filter :repository, only: [:new, :create]
... ...
app/controllers/teams/members_controller.rb
... ... @@ -8,7 +8,7 @@ class Teams::MembersController &lt; Teams::ApplicationController
8 8  
9 9 def new
10 10 @users = User.potential_team_members(user_team)
11   - @users = UserDecorator.decorate @users
  11 + @users = UserDecorator.decorate_collection @users
12 12 end
13 13  
14 14 def create
... ...
app/decorators/application_decorator.rb
1   -class ApplicationDecorator < Draper::Base
  1 +class ApplicationDecorator < Draper::Decorator
  2 + delegate_all
2 3 # Lazy Helpers
3 4 # PRO: Call Rails helpers without the h. proxy
4 5 # ex: number_to_currency(model.price)
5 6 # CON: Add a bazillion methods into your decorator's namespace
6 7 # and probably sacrifice performance/memory
7   - #
  8 + #
8 9 # Enable them by uncommenting this line:
9 10 # lazy_helpers
10 11  
11 12 # Shared Decorations
12 13 # Consider defining shared methods common to all your models.
13   - #
  14 + #
14 15 # Example: standardize the formatting of timestamps
15 16 #
16 17 # def formatted_timestamp(time)
17   - # h.content_tag :span, time.strftime("%a %m/%d/%y"),
18   - # class: 'timestamp'
  18 + # h.content_tag :span, time.strftime("%a %m/%d/%y"),
  19 + # class: 'timestamp'
19 20 # end
20   - #
  21 + #
21 22 # def created_at
22 23 # formatted_timestamp(model.created_at)
23 24 # end
24   - #
  25 + #
25 26 # def updated_at
26 27 # formatted_timestamp(model.updated_at)
27 28 # end
... ...
app/helpers/application_helper.rb
... ... @@ -164,7 +164,8 @@ module ApplicationHelper
164 164 end
165 165  
166 166 def image_url(source)
167   - root_url + path_to_image(source)
  167 + # prevent relative_root_path being added twice (it's part of root_url and path_to_image)
  168 + root_url.sub(/#{root_path}$/, path_to_image(source))
168 169 end
169 170  
170 171 alias_method :url_to_image, :image_url
... ...
app/helpers/issues_helper.rb
... ... @@ -27,6 +27,7 @@ module IssuesHelper
27 27 all: "all",
28 28 closed: "closed",
29 29 to_me: "assigned-to-me",
  30 + by_me: "created-by-me",
30 31 open: "open"
31 32 }
32 33 end
... ... @@ -40,4 +41,39 @@ module IssuesHelper
40 41 def issues_active_milestones
41 42 @project.milestones.active.order("id desc").all
42 43 end
  44 +
  45 + def url_for_project_issues
  46 + return "" if @project.nil?
  47 +
  48 + if @project.used_default_issues_tracker?
  49 + project_issues_filter_path(@project)
  50 + else
  51 + url = Settings[:issues_tracker][@project.issues_tracker]["project_url"]
  52 + url.gsub(':project_id', @project.id.to_s)
  53 + .gsub(':issues_tracker_id', @project.issues_tracker_id.to_s)
  54 + end
  55 + end
  56 +
  57 + def url_for_issue(issue_id)
  58 + return "" if @project.nil?
  59 +
  60 + if @project.used_default_issues_tracker?
  61 + url = project_issue_url project_id: @project, id: issue_id
  62 + else
  63 + url = Settings[:issues_tracker][@project.issues_tracker]["issues_url"]
  64 + url.gsub(':id', issue_id.to_s)
  65 + .gsub(':project_id', @project.id.to_s)
  66 + .gsub(':issues_tracker_id', @project.issues_tracker_id.to_s)
  67 + end
  68 + end
  69 +
  70 + def title_for_issue(issue_id)
  71 + return "" if @project.nil?
  72 +
  73 + if @project.used_default_issues_tracker? && issue = @project.issues.where(id: issue_id).first
  74 + issue.title
  75 + else
  76 + ""
  77 + end
  78 + end
43 79 end
... ...
app/helpers/tree_helper.rb
... ... @@ -13,13 +13,15 @@ module TreeHelper
13 13 tree += render partial: 'tree/tree_item', collection: folders, locals: {type: 'folder'} if folders.present?
14 14  
15 15 files.each do |f|
16   - if f.respond_to?(:url)
17   - # Object is a Submodule
18   - tree += render partial: 'tree/submodule_item', object: f
19   - else
20   - # Object is a Blob
21   - tree += render partial: 'tree/tree_item', object: f, locals: {type: 'file'}
22   - end
  16 + html = if f.respond_to?(:url)
  17 + # Object is a Submodule
  18 + render partial: 'tree/submodule_item', object: f
  19 + else
  20 + # Object is a Blob
  21 + render partial: 'tree/tree_item', object: f, locals: {type: 'file'}
  22 + end
  23 +
  24 + tree += html if html.present?
23 25 end
24 26  
25 27 tree.html_safe
... ...
app/models/ability.rb
... ... @@ -91,7 +91,6 @@ class Ability
91 91 :admin_team_member,
92 92 :admin_merge_request,
93 93 :admin_note,
94   - :accept_mr,
95 94 :admin_wiki,
96 95 :admin_project
97 96 ]
... ...
app/models/graph/commit.rb 0 → 100644
... ... @@ -0,0 +1,59 @@
  1 +require "grit"
  2 +
  3 +module Graph
  4 + class Commit
  5 + include ActionView::Helpers::TagHelper
  6 +
  7 + attr_accessor :time, :spaces, :refs, :parent_spaces, :icon
  8 +
  9 + def initialize(commit)
  10 + @_commit = commit
  11 + @time = -1
  12 + @spaces = []
  13 + @parent_spaces = []
  14 + end
  15 +
  16 + def method_missing(m, *args, &block)
  17 + @_commit.send(m, *args, &block)
  18 + end
  19 +
  20 + def to_graph_hash
  21 + h = {}
  22 + h[:parents] = self.parents.collect do |p|
  23 + [p.id,0,0]
  24 + end
  25 + h[:author] = {
  26 + name: author.name,
  27 + email: author.email,
  28 + icon: icon
  29 + }
  30 + h[:time] = time
  31 + h[:space] = spaces.first
  32 + h[:parent_spaces] = parent_spaces
  33 + h[:refs] = refs.collect{|r|r.name}.join(" ") unless refs.nil?
  34 + h[:id] = sha
  35 + h[:date] = date
  36 + h[:message] = message
  37 + h
  38 + end
  39 +
  40 + def add_refs(ref_cache, repo)
  41 + if ref_cache.empty?
  42 + repo.refs.each do |ref|
  43 + ref_cache[ref.commit.id] ||= []
  44 + ref_cache[ref.commit.id] << ref
  45 + end
  46 + end
  47 + @refs = ref_cache[@_commit.id] if ref_cache.include?(@_commit.id)
  48 + @refs ||= []
  49 + end
  50 +
  51 + def space
  52 + if @spaces.size > 0
  53 + @spaces.first
  54 + else
  55 + 0
  56 + end
  57 + end
  58 + end
  59 +end
... ...
app/models/graph/json_builder.rb 0 → 100644
... ... @@ -0,0 +1,291 @@
  1 +require "grit"
  2 +
  3 +module Graph
  4 + class JsonBuilder
  5 + attr_accessor :days, :commits, :ref_cache, :repo
  6 +
  7 + def self.max_count
  8 + @max_count ||= 650
  9 + end
  10 +
  11 + def initialize project, ref, commit
  12 + @project = project
  13 + @ref = ref
  14 + @commit = commit
  15 + @repo = project.repo
  16 + @ref_cache = {}
  17 +
  18 + @commits = collect_commits
  19 + @days = index_commits
  20 + end
  21 +
  22 + def to_json(*args)
  23 + {
  24 + days: @days.compact.map { |d| [d.day, d.strftime("%b")] },
  25 + commits: @commits.map(&:to_graph_hash)
  26 + }.to_json(*args)
  27 + end
  28 +
  29 + protected
  30 +
  31 + # Get commits from repository
  32 + #
  33 + def collect_commits
  34 +
  35 + @commits = Grit::Commit.find_all(repo, nil, {date_order: true, max_count: self.class.max_count, skip: to_commit}).dup
  36 +
  37 + # Decorate with app/models/commit.rb
  38 + @commits.map! { |commit| Commit.new(commit) }
  39 +
  40 + # Decorate with lib/gitlab/graph/commit.rb
  41 + @commits.map! { |commit| Graph::Commit.new(commit) }
  42 +
  43 + # add refs to each commit
  44 + @commits.each { |commit| commit.add_refs(ref_cache, repo) }
  45 +
  46 + @commits
  47 + end
  48 +
  49 + # Method is adding time and space on the
  50 + # list of commits. As well as returns date list
  51 + # corelated with time set on commits.
  52 + #
  53 + # @param [Array<Graph::Commit>] commits to index
  54 + #
  55 + # @return [Array<TimeDate>] list of commit dates corelated with time on commits
  56 + def index_commits
  57 + days, times = [], []
  58 + map = {}
  59 +
  60 + commits.reverse.each_with_index do |c,i|
  61 + c.time = i
  62 + days[i] = c.committed_date
  63 + map[c.id] = c
  64 + times[i] = c
  65 + end
  66 +
  67 + @_reserved = {}
  68 + days.each_index do |i|
  69 + @_reserved[i] = []
  70 + end
  71 +
  72 + commits_sort_by_ref.each do |commit|
  73 + if map.include? commit.id then
  74 + place_chain(map[commit.id], map)
  75 + end
  76 + end
  77 +
  78 + # find parent spaces for not overlap lines
  79 + times.each do |c|
  80 + c.parent_spaces.concat(find_free_parent_spaces(c, map, times))
  81 + end
  82 +
  83 + days
  84 + end
  85 +
  86 + # Skip count that the target commit is displayed in center.
  87 + def to_commit
  88 + commits = Grit::Commit.find_all(repo, nil, {date_order: true})
  89 + commit_index = commits.index do |c|
  90 + c.id == @commit.id
  91 + end
  92 +
  93 + if commit_index && (self.class.max_count / 2 < commit_index) then
  94 + # get max index that commit is displayed in the center.
  95 + commit_index - self.class.max_count / 2
  96 + else
  97 + 0
  98 + end
  99 + end
  100 +
  101 + def commits_sort_by_ref
  102 + commits.sort do |a,b|
  103 + if include_ref?(a)
  104 + -1
  105 + elsif include_ref?(b)
  106 + 1
  107 + else
  108 + b.committed_date <=> a.committed_date
  109 + end
  110 + end
  111 + end
  112 +
  113 + def include_ref?(commit)
  114 + heads = commit.refs.select do |ref|
  115 + ref.is_a?(Grit::Head) or ref.is_a?(Grit::Remote) or ref.is_a?(Grit::Tag)
  116 + end
  117 +
  118 + heads.map! do |head|
  119 + head.name
  120 + end
  121 +
  122 + heads.include?(@ref)
  123 + end
  124 +
  125 + def find_free_parent_spaces(commit, map, times)
  126 + spaces = []
  127 +
  128 + commit.parents.each do |p|
  129 + if map.include?(p.id) then
  130 + parent = map[p.id]
  131 +
  132 + range = if commit.time < parent.time then
  133 + commit.time..parent.time
  134 + else
  135 + parent.time..commit.time
  136 + end
  137 +
  138 + space = if commit.space >= parent.space then
  139 + find_free_parent_space(range, parent.space, -1, commit.space, times)
  140 + else
  141 + find_free_parent_space(range, commit.space, -1, parent.space, times)
  142 + end
  143 +
  144 + mark_reserved(range, space)
  145 + spaces << space
  146 + end
  147 + end
  148 +
  149 + spaces
  150 + end
  151 +
  152 + def find_free_parent_space(range, space_base, space_step, space_default, times)
  153 + if is_overlap?(range, times, space_default) then
  154 + find_free_space(range, space_step, space_base, space_default)
  155 + else
  156 + space_default
  157 + end
  158 + end
  159 +
  160 + def is_overlap?(range, times, overlap_space)
  161 + range.each do |i|
  162 + if i != range.first &&
  163 + i != range.last &&
  164 + times[i].spaces.include?(overlap_space) then
  165 +
  166 + return true;
  167 + end
  168 + end
  169 +
  170 + false
  171 + end
  172 +
  173 + # Add space mark on commit and its parents
  174 + #
  175 + # @param [Graph::Commit] the commit object.
  176 + # @param [Hash<String,Graph::Commit>] map of commits
  177 + def place_chain(commit, map, parent_time = nil)
  178 + leaves = take_left_leaves(commit, map)
  179 + if leaves.empty?
  180 + return
  181 + end
  182 +
  183 + time_range = leaves.last.time..leaves.first.time
  184 + space_base = get_space_base(leaves, map)
  185 + space = find_free_space(time_range, 2, space_base)
  186 + leaves.each do |l|
  187 + l.spaces << space
  188 + # Also add space to parent
  189 + l.parents.each do |p|
  190 + if map.include?(p.id)
  191 + parent = map[p.id]
  192 + if parent.space > 0
  193 + parent.spaces << space
  194 + end
  195 + end
  196 + end
  197 + end
  198 +
  199 + # and mark it as reserved
  200 + min_time = leaves.last.time
  201 + parents = leaves.last.parents.collect
  202 + parents.each do |p|
  203 + if map.include? p.id
  204 + parent = map[p.id]
  205 + if parent.time < min_time
  206 + min_time = parent.time
  207 + end
  208 + end
  209 + end
  210 +
  211 + if parent_time.nil?
  212 + max_time = leaves.first.time
  213 + else
  214 + max_time = parent_time - 1
  215 + end
  216 + mark_reserved(min_time..max_time, space)
  217 +
  218 + # Visit branching chains
  219 + leaves.each do |l|
  220 + parents = l.parents.collect.select{|p| map.include? p.id and map[p.id].space.zero?}
  221 + for p in parents
  222 + place_chain(map[p.id], map, l.time)
  223 + end
  224 + end
  225 + end
  226 +
  227 + def get_space_base(leaves, map)
  228 + space_base = 1
  229 + if leaves.last.parents.size > 0
  230 + first_parent = leaves.last.parents.first
  231 + if map.include?(first_parent.id)
  232 + first_p = map[first_parent.id]
  233 + if first_p.space > 0
  234 + space_base = first_p.space
  235 + end
  236 + end
  237 + end
  238 + space_base
  239 + end
  240 +
  241 + def mark_reserved(time_range, space)
  242 + for day in time_range
  243 + @_reserved[day].push(space)
  244 + end
  245 + end
  246 +
  247 + def find_free_space(time_range, space_step, space_base = 1, space_default = nil)
  248 + space_default ||= space_base
  249 +
  250 + reserved = []
  251 + for day in time_range
  252 + reserved += @_reserved[day]
  253 + end
  254 + reserved.uniq!
  255 +
  256 + space = space_default
  257 + while reserved.include?(space) do
  258 + space += space_step
  259 + if space < space_base then
  260 + space_step *= -1
  261 + space = space_base + space_step
  262 + end
  263 + end
  264 +
  265 + space
  266 + end
  267 +
  268 + # Takes most left subtree branch of commits
  269 + # which don't have space mark yet.
  270 + #
  271 + # @param [Graph::Commit] the commit object.
  272 + # @param [Hash<String,Graph::Commit>] map of commits
  273 + #
  274 + # @return [Array<Graph::Commit>] list of branch commits
  275 + def take_left_leaves(commit, map)
  276 + leaves = []
  277 + leaves.push(commit) if commit.space.zero?
  278 +
  279 + while true
  280 + return leaves if commit.parents.count.zero?
  281 + return leaves unless map.include? commit.parents.first.id
  282 +
  283 + commit = map[commit.parents.first.id]
  284 +
  285 + return leaves unless commit.space.zero?
  286 +
  287 + leaves.push(commit)
  288 + end
  289 + end
  290 + end
  291 +end
... ...
app/models/group.rb
... ... @@ -2,13 +2,14 @@
2 2 #
3 3 # Table name: namespaces
4 4 #
5   -# id :integer not null, primary key
6   -# name :string(255) not null
7   -# path :string(255) not null
8   -# owner_id :integer not null
9   -# created_at :datetime not null
10   -# updated_at :datetime not null
11   -# type :string(255)
  5 +# id :integer not null, primary key
  6 +# name :string(255) not null
  7 +# description :string(255) not null
  8 +# path :string(255) not null
  9 +# owner_id :integer not null
  10 +# created_at :datetime not null
  11 +# updated_at :datetime not null
  12 +# type :string(255)
12 13 #
13 14  
14 15 class Group < Namespace
... ...
app/models/issue.rb
... ... @@ -30,6 +30,10 @@ class Issue &lt; ActiveRecord::Base
30 30 where('assignee_id = :user', user: user.id)
31 31 end
32 32  
  33 + def authored(user)
  34 + where('author_id = :user', user: user.id)
  35 + end
  36 +
33 37 def open_for(user)
34 38 opened.assigned(user)
35 39 end
... ...
app/models/namespace.rb
... ... @@ -2,17 +2,18 @@
2 2 #
3 3 # Table name: namespaces
4 4 #
5   -# id :integer not null, primary key
6   -# name :string(255) not null
7   -# path :string(255) not null
8   -# owner_id :integer not null
9   -# created_at :datetime not null
10   -# updated_at :datetime not null
11   -# type :string(255)
  5 +# id :integer not null, primary key
  6 +# name :string(255) not null
  7 +# description :string(255) not null
  8 +# path :string(255) not null
  9 +# owner_id :integer not null
  10 +# created_at :datetime not null
  11 +# updated_at :datetime not null
  12 +# type :string(255)
12 13 #
13 14  
14 15 class Namespace < ActiveRecord::Base
15   - attr_accessible :name, :path
  16 + attr_accessible :name, :description, :path
16 17  
17 18 has_many :projects, dependent: :destroy
18 19 belongs_to :owner, class_name: "User"
... ... @@ -22,7 +23,7 @@ class Namespace &lt; ActiveRecord::Base
22 23 length: { within: 0..255 },
23 24 format: { with: Gitlab::Regex.name_regex,
24 25 message: "only letters, digits, spaces & '_' '-' '.' allowed." }
25   -
  26 + validates :description, length: { within: 0..255 }
26 27 validates :path, uniqueness: true, presence: true, length: { within: 1..255 },
27 28 format: { with: Gitlab::Regex.path_regex,
28 29 message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
... ...
app/models/project.rb
... ... @@ -11,6 +11,7 @@
11 11 # creator_id :integer
12 12 # default_branch :string(255)
13 13 # issues_enabled :boolean default(TRUE), not null
  14 +# issues_tracker :string not null
14 15 # wall_enabled :boolean default(TRUE), not null
15 16 # merge_requests_enabled :boolean default(TRUE), not null
16 17 # wiki_enabled :boolean default(TRUE), not null
... ... @@ -22,11 +23,12 @@ require &quot;grit&quot;
22 23  
23 24 class Project < ActiveRecord::Base
24 25 include Gitolited
  26 + extend Enumerize
25 27  
26 28 class TransferError < StandardError; end
27 29  
28   - attr_accessible :name, :path, :description, :default_branch,
29   - :issues_enabled, :wall_enabled, :merge_requests_enabled,
  30 + attr_accessible :name, :path, :description, :default_branch, :issues_tracker,
  31 + :issues_enabled, :wall_enabled, :merge_requests_enabled, :issues_tracker_id,
30 32 :wiki_enabled, :public, :import_url, as: [:default, :admin]
31 33  
32 34 attr_accessible :namespace_id, :creator_id, as: :admin
... ... @@ -43,7 +45,7 @@ class Project &lt; ActiveRecord::Base
43 45  
44 46 has_many :events, dependent: :destroy
45 47 has_many :merge_requests, dependent: :destroy
46   - has_many :issues, dependent: :destroy, order: "state, created_at DESC"
  48 + has_many :issues, dependent: :destroy, order: "state DESC, created_at DESC"
47 49 has_many :milestones, dependent: :destroy
48 50 has_many :users_projects, dependent: :destroy
49 51 has_many :notes, dependent: :destroy
... ... @@ -72,6 +74,7 @@ class Project &lt; ActiveRecord::Base
72 74 message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
73 75 validates :issues_enabled, :wall_enabled, :merge_requests_enabled,
74 76 :wiki_enabled, inclusion: { in: [true, false] }
  77 + validates :issues_tracker_id, length: { within: 0..255 }
75 78  
76 79 validates_uniqueness_of :name, scope: :namespace_id
77 80 validates_uniqueness_of :path, scope: :namespace_id
... ... @@ -93,6 +96,8 @@ class Project &lt; ActiveRecord::Base
93 96 scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) }
94 97 scope :public_only, -> { where(public: true) }
95 98  
  99 + enumerize :issues_tracker, :in => (Gitlab.config.issues_tracker.keys).append(:gitlab), :default => :gitlab
  100 +
96 101 class << self
97 102 def abandoned
98 103 project_ids = Event.select('max(created_at) as latest_date, project_id').
... ... @@ -201,6 +206,22 @@ class Project &lt; ActiveRecord::Base
201 206 issues.tag_counts_on(:labels)
202 207 end
203 208  
  209 + def issue_exists?(issue_id)
  210 + if used_default_issues_tracker?
  211 + self.issues.where(id: issue_id).first.present?
  212 + else
  213 + true
  214 + end
  215 + end
  216 +
  217 + def used_default_issues_tracker?
  218 + self.issues_tracker == Project.issues_tracker.default_value
  219 + end
  220 +
  221 + def can_have_issues_tracker_id?
  222 + self.issues_enabled && !self.used_default_issues_tracker?
  223 + end
  224 +
204 225 def services
205 226 [gitlab_ci_service].compact
206 227 end
... ...
app/models/repository.rb
... ... @@ -137,7 +137,7 @@ class Repository
137 137 file_path = File.join(storage_path, self.path_with_namespace, file_name)
138 138  
139 139 # Put files into a directory before archiving
140   - prefix = self.path_with_namespace + "/"
  140 + prefix = File.basename(self.path_with_namespace) + "/"
141 141  
142 142 # Create file if not exists
143 143 unless File.exists?(file_path)
... ...
app/models/user.rb
... ... @@ -25,7 +25,7 @@
25 25 # dark_scheme :boolean default(FALSE), not null
26 26 # theme_id :integer default(1), not null
27 27 # bio :string(255)
28   -# blocked :boolean default(FALSE), not null
  28 +# state :string(255)
29 29 # failed_attempts :integer default(0)
30 30 # locked_at :datetime
31 31 # extern_uid :string(255)
... ... @@ -46,10 +46,35 @@ class User &lt; ActiveRecord::Base
46 46  
47 47 attr_accessor :force_random_password
48 48  
  49 + #
  50 + # Relations
  51 + #
  52 +
49 53 # Namespace for personal projects
50   - has_one :namespace, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace", conditions: 'type IS NULL'
  54 + has_one :namespace,
  55 + dependent: :destroy,
  56 + foreign_key: :owner_id,
  57 + class_name: "Namespace",
  58 + conditions: 'type IS NULL'
  59 +
  60 + # Profile
  61 + has_many :keys, dependent: :destroy
  62 +
  63 + # Groups
  64 + has_many :groups, class_name: "Group", foreign_key: :owner_id
  65 +
  66 + # Teams
  67 + has_many :own_teams,
  68 + class_name: "UserTeam",
  69 + foreign_key: :owner_id,
  70 + dependent: :destroy
  71 +
  72 + has_many :user_team_user_relationships, dependent: :destroy
  73 + has_many :user_teams, through: :user_team_user_relationships
  74 + has_many :user_team_project_relationships, through: :user_teams
  75 + has_many :team_projects, through: :user_team_project_relationships
51 76  
52   - has_many :keys, dependent: :destroy
  77 + # Projects
53 78 has_many :users_projects, dependent: :destroy
54 79 has_many :issues, dependent: :destroy, foreign_key: :author_id
55 80 has_many :notes, dependent: :destroy, foreign_key: :author_id
... ... @@ -57,18 +82,16 @@ class User &lt; ActiveRecord::Base
57 82 has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event"
58 83 has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue"
59 84 has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
  85 + has_many :projects, through: :users_projects
60 86  
61   - has_many :groups, class_name: "Group", foreign_key: :owner_id
62   - has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC"
63   -
64   - has_many :projects, through: :users_projects
65   -
66   - has_many :user_team_user_relationships, dependent: :destroy
67   -
68   - has_many :user_teams, through: :user_team_user_relationships
69   - has_many :user_team_project_relationships, through: :user_teams
70   - has_many :team_projects, through: :user_team_project_relationships
  87 + has_many :recent_events,
  88 + class_name: "Event",
  89 + foreign_key: :author_id,
  90 + order: "id DESC"
71 91  
  92 + #
  93 + # Validations
  94 + #
72 95 validates :name, presence: true
73 96 validates :email, presence: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/ }
74 97 validates :bio, length: { within: 0..255 }
... ... @@ -87,10 +110,27 @@ class User &lt; ActiveRecord::Base
87 110  
88 111 delegate :path, to: :namespace, allow_nil: true, prefix: true
89 112  
  113 + state_machine :state, initial: :active do
  114 + after_transition any => :blocked do |user, transition|
  115 + # Remove user from all projects and
  116 + user.users_projects.find_each do |membership|
  117 + return false unless membership.destroy
  118 + end
  119 + end
  120 +
  121 + event :block do
  122 + transition active: :blocked
  123 + end
  124 +
  125 + event :activate do
  126 + transition blocked: :active
  127 + end
  128 + end
  129 +
90 130 # Scopes
91 131 scope :admins, -> { where(admin: true) }
92   - scope :blocked, -> { where(blocked: true) }
93   - scope :active, -> { where(blocked: false) }
  132 + scope :blocked, -> { with_state(:blocked) }
  133 + scope :active, -> { with_state(:active) }
94 134 scope :alphabetically, -> { order('name ASC') }
95 135 scope :in_team, ->(team){ where(id: team.member_ids) }
96 136 scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
... ... @@ -260,17 +300,6 @@ class User &lt; ActiveRecord::Base
260 300 MergeRequest.cared(self)
261 301 end
262 302  
263   - # Remove user from all projects and
264   - # set blocked attribute to true
265   - def block
266   - users_projects.find_each do |membership|
267   - return false unless membership.destroy
268   - end
269   -
270   - self.blocked = true
271   - save
272   - end
273   -
274 303 def projects_limit_percent
275 304 return 100 if projects_limit.zero?
276 305 (personal_projects.count.to_f / projects_limit) * 100
... ...
app/models/user_team.rb
... ... @@ -11,7 +11,7 @@
11 11 #
12 12  
13 13 class UserTeam < ActiveRecord::Base
14   - attr_accessible :name, :owner_id, :path
  14 + attr_accessible :name, :description, :owner_id, :path
15 15  
16 16 belongs_to :owner, class_name: User
17 17  
... ... @@ -26,6 +26,7 @@ class UserTeam &lt; ActiveRecord::Base
26 26 length: { within: 0..255 },
27 27 format: { with: Gitlab::Regex.name_regex,
28 28 message: "only letters, digits, spaces & '_' '-' '.' allowed." }
  29 + validates :description, length: { within: 0..255 }
29 30 validates :path, uniqueness: true, presence: true, length: { within: 1..255 },
30 31 format: { with: Gitlab::Regex.path_regex,
31 32 message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
... ...
app/observers/issue_observer.rb
... ... @@ -27,7 +27,7 @@ class IssueObserver &lt; ActiveRecord::Observer
27 27  
28 28 def create_note(issue)
29 29 Note.create_status_change_note(issue, current_user, issue.state)
30   - [issue.author, issue.assignee].compact.each do |recipient|
  30 + [issue.author, issue.assignee].compact.uniq.each do |recipient|
31 31 Notify.delay.issue_status_changed_email(recipient.id, issue.id, issue.state, current_user.id)
32 32 end
33 33 end
... ...
app/observers/user_observer.rb
... ... @@ -2,7 +2,8 @@ class UserObserver &lt; ActiveRecord::Observer
2 2 def after_create(user)
3 3 log_info("User \"#{user.name}\" (#{user.email}) was created")
4 4  
5   - Notify.delay.new_user_email(user.id, user.password)
  5 + # Dont email omniauth created users
  6 + Notify.delay.new_user_email(user.id, user.password) unless user.extern_uid?
6 7 end
7 8  
8 9 def after_destroy user
... ...
app/services/git_push_service.rb
... ... @@ -19,6 +19,8 @@ class GitPushService
19 19 # Collect data for this git push
20 20 @push_data = post_receive_data(oldrev, newrev, ref)
21 21  
  22 + create_push_event
  23 +
22 24 project.ensure_satellite_exists
23 25 project.discover_default_branch
24 26  
... ... @@ -27,8 +29,6 @@ class GitPushService
27 29 project.execute_hooks(@push_data.dup)
28 30 project.execute_services(@push_data.dup)
29 31 end
30   -
31   - create_push_event
32 32 end
33 33  
34 34 # This method provide a sample data
... ...
app/services/project_transfer_service.rb
... ... @@ -25,7 +25,7 @@ class ProjectTransferService
25 25  
26 26 Gitlab::ProjectMover.new(project, old_dir, new_dir).execute
27 27  
28   - save!
  28 + project.save!
29 29 end
30 30 rescue Gitlab::ProjectMover::ProjectMoveError => ex
31 31 raise Project::TransferError.new(ex.message)
... ...
app/views/admin/groups/edit.html.haml
1   -%h3.page_title Rename Group
  1 +%h3.page_title Edit Group
2 2 %hr
3 3 = form_for [:admin, @group] do |f|
4 4 - if @group.errors.any?
... ... @@ -10,7 +10,10 @@
10 10 .input
11 11 = f.text_field :name, placeholder: "Example Group", class: "xxlarge"
12 12  
13   -
  13 + .clearfix.group-description-holder
  14 + = f.label :description, "Details"
  15 + .input
  16 + = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4
14 17  
15 18 .clearfix.group_name_holder
16 19 = f.label :path do
... ... @@ -24,5 +27,5 @@
24 27 %li It will change the git path to repositories under this group.
25 28  
26 29 .form-actions
27   - = f.submit 'Rename group', class: "btn btn-remove"
  30 + = f.submit 'Edit group', class: "btn btn-remove"
28 31 = link_to 'Cancel', admin_groups_path, class: "btn btn-cancel"
... ...
app/views/admin/groups/index.html.haml
... ... @@ -17,6 +17,7 @@
17 17 Name
18 18 %i.icon-sort-down
19 19 %th Path
  20 + %th Description
20 21 %th Projects
21 22 %th Owner
22 23 %th.cred Danger Zone!
... ... @@ -25,11 +26,12 @@
25 26 %tr
26 27 %td
27 28 %strong= link_to group.name, [:admin, group]
  29 + %td= truncate group.description
28 30 %td= group.path
29 31 %td= group.projects.count
30 32 %td
31 33 = link_to group.owner_name, admin_user_path(group.owner)
32 34 %td.bgred
33   - = link_to 'Rename', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-small"
  35 + = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-small"
34 36 = link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
35 37 = paginate @groups, theme: "admin"
... ...
app/views/admin/groups/new.html.haml
... ... @@ -9,8 +9,14 @@
9 9 Group name is
10 10 .input
11 11 = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
12   - &nbsp;
13   - = f.submit 'Create group', class: "btn btn-primary"
  12 + .clearfix.group-description-holder
  13 + = f.label :description, "Details"
  14 + .input
  15 + = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4
  16 +
  17 + .form-actions
  18 + = f.submit 'Create group', class: "btn btn-primary"
  19 +
14 20 %hr
15 21 .padded
16 22 %ul
... ...
app/views/admin/groups/show.html.haml
... ... @@ -16,7 +16,13 @@
16 16 &nbsp;
17 17 = link_to edit_admin_group_path(@group), class: "btn btn-small pull-right" do
18 18 %i.icon-edit
19   - Rename
  19 + Edit
  20 + %tr
  21 + %td
  22 + %b
  23 + Description:
  24 + %td
  25 + = @group.description
20 26 %tr
21 27 %td
22 28 %b
... ...
app/views/admin/projects/_form.html.haml
... ... @@ -31,6 +31,15 @@
31 31 = f.label :issues_enabled, "Issues"
32 32 .input= f.check_box :issues_enabled
33 33  
  34 + - if Project.issues_tracker.values.count > 1
  35 + .clearfix
  36 + = f.label :issues_tracker, "Issues tracker", class: 'control-label'
  37 + .input= f.select(:issues_tracker, Project.issues_tracker.values, {}, { disabled: !@project.issues_enabled })
  38 +
  39 + .clearfix
  40 + = f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label'
  41 + .input= f.text_field :issues_tracker_id, class: "xxlarge", disabled: !@project.can_have_issues_tracker_id?
  42 +
34 43 .clearfix
35 44 = f.label :merge_requests_enabled, "Merge Requests"
36 45 .input= f.check_box :merge_requests_enabled
... ...
app/views/admin/teams/edit.html.haml
1   -%h3.page_title Rename Team
  1 +%h3.page_title Edit Team
2 2 %hr
3 3 = form_for @team, url: admin_team_path(@team), method: :put do |f|
4 4 - if @team.errors.any?
... ... @@ -10,6 +10,11 @@
10 10 .input
11 11 = f.text_field :name, placeholder: "Example Team", class: "xxlarge"
12 12  
  13 + .clearfix.team-description-holder
  14 + = f.label :description, "Details"
  15 + .input
  16 + = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4
  17 +
13 18 .clearfix.team_name_holder
14 19 = f.label :path do
15 20 %span.cred Team path is
... ... @@ -19,5 +24,5 @@
19 24 %li It will change web url for access team and team projects.
20 25  
21 26 .form-actions
22   - = f.submit 'Rename team', class: "btn btn-remove"
  27 + = f.submit 'Edit team', class: "btn btn-remove"
23 28 = link_to 'Cancel', admin_teams_path, class: "btn btn-cancel"
... ...
app/views/admin/teams/index.html.haml
... ... @@ -16,6 +16,7 @@
16 16 %th
17 17 Name
18 18 %i.icon-sort-down
  19 + %th Description
19 20 %th Path
20 21 %th Projects
21 22 %th Members
... ... @@ -26,13 +27,17 @@
26 27 %tr
27 28 %td
28 29 %strong= link_to team.name, admin_team_path(team)
  30 + %td= truncate team.description
29 31 %td= team.path
30 32 %td= team.projects.count
31 33 %td= team.members.count
32 34 %td
33   - = link_to team.owner.name, admin_user_path(team.owner)
  35 + - if team.owner
  36 + = link_to team.owner.name, admin_user_path(team.owner)
  37 + - else
  38 + (deleted)
34 39 %td.bgred
35   - = link_to 'Rename', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn btn-small"
  40 + = link_to 'Edit', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn btn-small"
36 41 = link_to 'Destroy', admin_team_path(team), confirm: "REMOVE #{team.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
37 42  
38 43 = paginate @teams, theme: "admin"
... ...
app/views/admin/teams/new.html.haml
... ... @@ -9,8 +9,15 @@
9 9 Team name is
10 10 .input
11 11 = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
12   - &nbsp;
13   - = f.submit 'Create team', class: "btn btn-primary"
  12 +
  13 + .clearfix.team-description-holder
  14 + = f.label :description, "Details"
  15 + .input
  16 + = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4
  17 +
  18 + .form-actions
  19 + = f.submit 'Create team', class: "btn btn-primary"
  20 +
14 21 %hr
15 22 .padded
16 23 %ul
... ...
app/views/admin/teams/show.html.haml
... ... @@ -16,7 +16,13 @@
16 16 &nbsp;
17 17 = link_to edit_admin_team_path(@team), class: "btn btn-small pull-right" do
18 18 %i.icon-edit
19   - Rename
  19 + Edit
  20 + %tr
  21 + %td
  22 + %b
  23 + Description:
  24 + %td
  25 + = @team.description
20 26 %tr
21 27 %td
22 28 %b
... ...
app/views/admin/users/_form.html.haml
... ... @@ -61,7 +61,7 @@
61 61 .span4
62 62 - unless @admin_user.new_record?
63 63 .alert.alert-error
64   - - if @admin_user.blocked
  64 + - if @admin_user.blocked?
65 65 %p This user is blocked and is not able to login to GitLab
66 66 = link_to 'Unblock User', unblock_admin_user_path(@admin_user), method: :put, class: "btn btn-small"
67 67 - else
... ...
app/views/admin/users/index.html.haml
... ... @@ -53,7 +53,7 @@
53 53 &nbsp;
54 54 = link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn btn-small"
55 55 - unless user == current_user
56   - - if user.blocked
  56 + - if user.blocked?
57 57 = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn btn-small success"
58 58 - else
59 59 = link_to 'Block', block_admin_user_path(user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-small btn-remove"
... ...
app/views/admin/users/show.html.haml
... ... @@ -3,7 +3,7 @@
3 3 %h3.page_title
4 4 = image_tag gravatar_icon(@admin_user.email, 90), class: "avatar s90"
5 5 = @admin_user.name
6   - - if @admin_user.blocked
  6 + - if @admin_user.blocked?
7 7 %span.cred (Blocked)
8 8 - if @admin_user.admin
9 9 %span.cred (Admin)
... ...
app/views/commits/_commits.html.haml
1   -- @commits.group_by { |c| c.committed_date.to_date }.each do |day, commits|
  1 +- @commits.group_by { |c| c.committed_date.to_date }.sort.reverse.each do |day, commits|
2 2 %div.ui-box
3 3 %h5.title
4 4 %i.icon-calendar
... ...
app/views/events/event/_note.html.haml
... ... @@ -21,9 +21,10 @@
21 21 = event.project_name
22 22  
23 23 .event-body
24   - %i.icon-comment-alt.event-note-icon
25   - %span.event-note
26   - = markdown truncate(event.target.note, length: 70)
  24 + .event-note
  25 + .md
  26 + %i.icon-comment-alt.event-note-icon
  27 + = sanitize(markdown(truncate(event.target.note, length: 150)), tags: %w(a img b pre p))
27 28 - note = event.target
28 29 - if note.attachment.url
29 30 = link_to note.attachment.secure_url, target: "_blank", class: 'note-file-attach' do
... ...
app/views/groups/edit.html.haml
... ... @@ -9,8 +9,15 @@
9 9 Group name is
10 10 .input
11 11 = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
12   - &nbsp;
13   - = f.submit 'Save group', class: "btn btn-save"
  12 +
  13 + .clearfix.group-description-holder
  14 + = f.label :description, "Details"
  15 + .input
  16 + = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4
  17 +
  18 + .form-actions
  19 + = f.submit 'Save group', class: "btn btn-save"
  20 +
14 21 %hr
15 22  
16 23  
... ...
app/views/groups/new.html.haml
... ... @@ -9,9 +9,16 @@
9 9 Group name is
10 10 .input
11 11 = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
12   - &nbsp;
13   - = f.submit 'Create group', class: "btn btn-create"
14   - %hr
  12 +
  13 + .clearfix.group-description-holder
  14 + = f.label :description, "Details"
  15 + .input
  16 + = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4
  17 +
  18 + .form-actions
  19 + = f.submit 'Create group', class: "btn btn-create"
  20 +
  21 +
15 22 .padded
16 23 %ul
17 24 %li Group is kind of directory for several projects
... ...
app/views/groups/show.html.haml
... ... @@ -12,6 +12,9 @@
12 12 %p.nothing_here_message Project activity will be displayed here
13 13 .loading.hide
14 14 .side.span4
  15 + - if @group.description.present?
  16 + .description.well.light
  17 + = @group.description
15 18 = render "projects", projects: @projects
16 19 %div
17 20 %span.rss-icon
... ...
app/views/issues/_filter.html.haml
... ... @@ -6,7 +6,10 @@
6 6 Open
7 7 %li{class: ("active" if params[:status] == 'assigned-to-me')}
8 8 = link_to project_issues_path(@project, status: 'assigned-to-me') do
9   - Assigned To Me
  9 + Assigned to me
  10 + %li{class: ("active" if params[:status] == 'created-by-me')}
  11 + = link_to project_issues_path(@project, status: 'created-by-me') do
  12 + Created by me
10 13 %li{class: ("active" if params[:status] == 'closed')}
11 14 = link_to project_issues_path(@project, status: 'closed') do
12 15 Closed
... ...
app/views/layouts/_flash.html.haml
1   -- if text = alert || notice
2   - #flash-container
3   - %h4= text
  1 +.flash-container
  2 + - if alert
  3 + .alert
  4 + %span= alert
  5 +
  6 + - elsif notice
  7 + .alert.alert-info
  8 + %span= notice
... ...
app/views/layouts/_head.html.haml
... ... @@ -7,6 +7,7 @@
7 7 = stylesheet_link_tag "application"
8 8 = javascript_include_tag "application"
9 9 = csrf_meta_tags
  10 + = include_gon
10 11  
11 12 -# Atom feed
12 13 - if current_user
... ...
app/views/layouts/_head_panel.html.haml
... ... @@ -8,6 +8,9 @@
8 8 %span.separator
9 9 %h1.project_name= title
10 10 %ul.nav
  11 + %li
  12 + = link_to public_root_path, title: "Public area", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do
  13 + %i.icon-globe
11 14 - if current_user.is_admin?
12 15 %li
13 16 = link_to admin_root_path, title: "Admin area", class: 'has_bottom_tooltip', 'data-original-title' => 'Admin area' do
... ...
app/views/layouts/admin.html.haml
... ... @@ -2,8 +2,8 @@
2 2 %html{ lang: "en"}
3 3 = render "layouts/head", title: "Admin area"
4 4 %body{class: "#{app_theme} admin"}
5   - = render "layouts/flash"
6 5 = render "layouts/head_panel", title: "Admin area"
  6 + = render "layouts/flash"
7 7 .container
8 8 %ul.main_menu
9 9 = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
... ...
app/views/layouts/application.html.haml
... ... @@ -2,8 +2,8 @@
2 2 %html{ lang: "en"}
3 3 = render "layouts/head", title: "Dashboard"
4 4 %body{class: "#{app_theme} application"}
5   - = render "layouts/flash"
6 5 = render "layouts/head_panel", title: "Dashboard"
  6 + = render "layouts/flash"
7 7 .container
8 8 %ul.main_menu
9 9 = nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do
... ...
app/views/layouts/devise.html.haml
... ... @@ -3,4 +3,6 @@
3 3 = render "layouts/head"
4 4 %body.ui_basic.login-page
5 5 = render "layouts/flash"
6   - .container= yield
  6 + .container
  7 + .content
  8 + = yield
... ...
app/views/layouts/errors.html.haml
... ... @@ -2,8 +2,8 @@
2 2 %html{ lang: "en"}
3 3 = render "layouts/head", title: "Error"
4 4 %body{class: "#{app_theme} application"}
5   - = render "layouts/flash"
6 5 = render "layouts/head_panel", title: ""
  6 + = render "layouts/flash"
7 7 .container
8 8 .content
9 9 %center.padded.prepend-top-20
... ...
app/views/layouts/group.html.haml
... ... @@ -2,8 +2,8 @@
2 2 %html{ lang: "en"}
3 3 = render "layouts/head", title: "#{@group.name}"
4 4 %body{class: "#{app_theme} application"}
5   - = render "layouts/flash"
6 5 = render "layouts/head_panel", title: "group: #{@group.name}"
  6 + = render "layouts/flash"
7 7 .container
8 8 %ul.main_menu
9 9 = nav_link(path: 'groups#show', html_options: {class: 'home'}) do
... ...
app/views/layouts/profile.html.haml
... ... @@ -2,8 +2,8 @@
2 2 %html{ lang: "en"}
3 3 = render "layouts/head", title: "Profile"
4 4 %body{class: "#{app_theme} profile"}
5   - = render "layouts/flash"
6 5 = render "layouts/head_panel", title: "Profile"
  6 + = render "layouts/flash"
7 7 .container
8 8 %ul.main_menu
9 9 = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
... ...
app/views/layouts/project_resource.html.haml
... ... @@ -2,8 +2,8 @@
2 2 %html{ lang: "en"}
3 3 = render "layouts/head", title: @project.name_with_namespace
4 4 %body{class: "#{app_theme} project"}
5   - = render "layouts/flash"
6 5 = render "layouts/head_panel", title: project_title(@project)
  6 + = render "layouts/flash"
7 7 - if can?(current_user, :download_code, @project)
8 8 = render 'shared/no_ssh'
9 9  
... ... @@ -22,11 +22,12 @@
22 22 = nav_link(controller: %w(graph)) do
23 23 = link_to "Network", project_graph_path(@project, @ref || @repository.root_ref)
24 24  
25   - - if @project.issues_enabled
  25 + - if @project.issues_enabled
26 26 = nav_link(controller: %w(issues milestones labels)) do
27   - = link_to project_issues_filter_path(@project) do
  27 + = link_to url_for_project_issues do
28 28 Issues
29   - %span.count.issue_counter= @project.issues.opened.count
  29 + - if @project.used_default_issues_tracker?
  30 + %span.count.issue_counter= @project.issues.opened.count
30 31  
31 32 - if @project.repo_exists? && @project.merge_requests_enabled
32 33 = nav_link(controller: :merge_requests) do
... ...
app/views/layouts/user_team.html.haml
... ... @@ -2,8 +2,8 @@
2 2 %html{ lang: "en"}
3 3 = render "layouts/head", title: "#{@team.name}"
4 4 %body{class: "#{app_theme} application"}
5   - = render "layouts/flash"
6 5 = render "layouts/head_panel", title: "team: #{@team.name}"
  6 + = render "layouts/flash"
7 7 .container
8 8 %ul.main_menu
9 9 = nav_link(path: 'teams#show', html_options: {class: 'home'}) do
... ...
app/views/merge_requests/show/_mr_accept.html.haml
1   -- unless can?(current_user, :accept_mr, @project)
  1 +- unless @allowed_to_merge
2 2 .alert
3   - %strong Only masters can accept MR
  3 + %strong You don't have enough permissions to merge this MR
4 4  
5 5  
6   -- if @merge_request.opened? && @commits.any? && can?(current_user, :accept_mr, @project)
  6 +- if @show_merge_controls
7 7 .automerge_widget.can_be_merged{style: "display:none"}
8 8 .alert.alert-success
9 9 %span
... ...
app/views/notify/issue_status_changed_email.text.erb 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +Issue was <%= @issue_status %> by <%= @updated_by.name %>
  2 +
  3 +Issue <%= @issue.id %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
  4 +
... ...
app/views/notify/new_issue_email.text.erb 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +New Issue was created and assigned to you.
  2 +
  3 +
  4 +Issue <%= @issue.id %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
... ...
app/views/notify/new_merge_request_email.text.erb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +New Merge Request <%= @merge_request.id %>
  2 +
  3 +<%= url_for(project_merge_request_url(@merge_request.project, @merge_request)) %>
  4 +
  5 +
  6 +Branches: <%= @merge_request.source_branch %> to <%= @merge_request.target_branch %>
  7 +Author: <%= @merge_request.author_name %>
  8 +Asignee: <%= @merge_request.assignee_name %>
  9 +
... ...
app/views/notify/new_user_email.text.erb 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +Hi <%= @user.name %>!
  2 +
  3 +Administrator created account for you. Now you are a member of company GitLab application.
  4 +
  5 +login.................. <%= @user.email %>
  6 +<% unless Gitlab.config.gitlab.signup_enabled %>
  7 + password............... <%= @password %>
  8 +<% end %>
  9 +
  10 +Click here to login: <%= url_for(root_url) %>
... ...
app/views/notify/note_commit_email.text.erb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +New comment for Commit <%= @commit.short_id %>
  2 +
  3 +<%= url_for(project_commit_url(@note.project, id: @commit.id, anchor: "note_#{@note.id}")) %>
  4 +
  5 +
  6 +Author: <%= @note.author_name %>
  7 +
  8 +<%= @note.note %>
  9 +
... ...
app/views/notify/note_issue_email.text.erb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +New comment for Issue <%= @issue.id %>
  2 +
  3 +<%= url_for(project_issue_url(@issue.project, @issue, anchor: "note_#{@note.id}")) %>
  4 +
  5 +
  6 +Author: <%= @note.author_name %>
  7 +
  8 +<%= @note.note %>
  9 +
... ...
app/views/notify/note_merge_request_email.text.erb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +New comment for Merge Request <%= @merge_request.id %>
  2 +
  3 +<%= url_for(project_merge_request_url(@merge_request.project, @merge_request, anchor: "note_#{@note.id}")) %>
  4 +
  5 +
  6 +<%= @note.author_name %>
  7 +
  8 +<%= @note.note %>
  9 +
... ...
app/views/notify/note_wall_email.text.erb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +New message on the project wall <%= @note.project %>
  2 +
  3 +<%= url_for(wall_project_url(@note.project, anchor: "note_#{@note.id}")) %>
  4 +
  5 +
  6 +<%= @note.author_name %>
  7 +
  8 +<%= @note.note %>
  9 +
... ...
app/views/notify/project_access_granted_email.text.erb 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +
  2 +You have been granted <%= @users_project.project_access_human %> access to project <%= @project.name_with_namespace %>
  3 +
  4 +<%= url_for(project_url(@project)) %>
... ...
app/views/notify/project_was_moved_email.text.erb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +Project was moved to another location
  2 +
  3 +The project is now located under
  4 +<%= url_for(link_to project_url(@project)) %>
  5 +
  6 +
  7 +To update the remote url in your local repository run:
  8 + git remote set-url origin <%= @project.ssh_url_to_repo %>
... ...
app/views/notify/reassigned_issue_email.text.erb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +Reassigned Issue <%= @issue.id %>
  2 +
  3 +<%= url_for(project_issue_url(@issue.project, @issue)) %>
  4 +
  5 +
  6 +Assignee changed from <%= @previous_assignee.name %> to <%= @issue.assignee_name %>
  7 +
... ...
app/views/notify/reassigned_merge_request_email.text.erb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +Reassigned Merge Request <%= @merge_request.id %>
  2 +
  3 +<%= url_for(project_merge_request_url(@merge_request.project, @merge_request)) %>
  4 +
  5 +
  6 +Assignee changed from <%= @previous_assignee.name %> to <%= @merge_request.assignee_name %>
  7 +
... ...
app/views/projects/_form.html.haml
... ... @@ -24,6 +24,15 @@
24 24 = f.check_box :issues_enabled
25 25 %span.descr Lightweight issue tracking system for this project
26 26  
  27 + - if Project.issues_tracker.values.count > 1
  28 + .control-group
  29 + = f.label :issues_tracker, "Issues tracker", class: 'control-label'
  30 + .input= f.select(:issues_tracker, Project.issues_tracker.values, {}, { disabled: !@project.issues_enabled })
  31 +
  32 + .clearfix
  33 + = f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label'
  34 + .input= f.text_field :issues_tracker_id, class: "xxlarge", disabled: !@project.can_have_issues_tracker_id?
  35 +
27 36 .control-group
28 37 = f.label :merge_requests_enabled, "Merge Requests", class: 'control-label'
29 38 .controls
... ...
app/views/projects/empty.html.haml
... ... @@ -5,14 +5,14 @@
5 5 %fieldset
6 6 %legend Git global setup:
7 7 %pre.dark
8   - = preserve do
  8 + :preserve
9 9 git config --global user.name "#{current_user.name}"
10 10 git config --global user.email "#{current_user.email}"
11 11  
12 12 %fieldset
13 13 %legend Create Repository
14 14 %pre.dark
15   - = preserve do
  15 + :preserve
16 16 mkdir #{@project.path}
17 17 cd #{@project.path}
18 18 git init
... ... @@ -25,7 +25,7 @@
25 25 %fieldset
26 26 %legend Existing Git Repo?
27 27 %pre.dark
28   - = preserve do
  28 + :preserve
29 29 cd existing_git_repo
30 30 git remote add origin #{@project.url_to_repo}
31 31 git push -u origin master
... ...
app/views/public/projects/index.html.haml
... ... @@ -12,5 +12,7 @@
12 12 .pull-right
13 13 %pre.dark.tiny git clone #{project.http_url_to_repo}
14 14  
  15 + - unless @projects.present?
  16 + %h3.nothing_here_message No public projects
15 17  
16 18 = paginate @projects, theme: "admin"
... ...
app/views/shared/_clone_panel.html.haml
1 1 .input-prepend.project_clone_holder
2 2 %button{class: "btn active", :"data-clone" => @project.ssh_url_to_repo} SSH
3 3 %button{class: "btn", :"data-clone" => @project.http_url_to_repo}= Gitlab.config.gitlab.protocol.upcase
4   -
5   - = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select input-xxlarge"
  4 + = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select input-xxlarge", readonly: true
... ...
app/views/team_members/_team_member.html.haml
... ... @@ -20,7 +20,7 @@
20 20 %span.label This is you!
21 21 - if @project.namespace_owner == user
22 22 %span.label Owner
23   - - elsif user.blocked
  23 + - elsif user.blocked?
24 24 %span.label Blocked
25 25 - elsif allow_admin
26 26 = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "btn-tiny btn btn-remove" do
... ...
app/views/teams/edit.html.haml
... ... @@ -12,13 +12,19 @@
12 12 .input
13 13 = f.text_field :name, placeholder: "Ex. OpenSource", class: "xlarge left"
14 14  
  15 + .clearfix.team-description-holder
  16 + = f.label :description, "Details"
  17 + .input
  18 + = f.text_area :description, maxlength: 250, class: "xlarge js-gfm-input", rows: 4
  19 +
15 20 .clearfix
16 21 = f.label :path do
17 22 Team path is
18 23 .input
19 24 = f.text_field :path, placeholder: "opensource", class: "xlarge left"
  25 +
20 26 .form-actions
21   - = f.submit 'Save team changes', class: "btn btn-save"
  27 + = f.submit 'Save team changes', class: "btn btn-primary"
22 28 .span5
23 29 .ui-box
24 30 %h5.title Remove team
... ... @@ -26,4 +32,3 @@
26 32 %p
27 33 Removed team can not be restored!
28 34 = link_to 'Remove team', team_path(@team), method: :delete, confirm: "You are sure?", class: "btn btn-remove btn-small"
29   -
... ...
app/views/teams/members/_show.html.haml
... ... @@ -23,7 +23,7 @@
23 23 %span.btn.disabled This is you!
24 24 - if @team.owner == user
25 25 %span.btn.disabled Owner
26   - - elsif user.blocked
  26 + - elsif user.blocked?
27 27 %span.btn.disabled.blocked Blocked
28 28 - elsif allow_admin
29 29 = link_to team_member_path(@team, user), confirm: remove_from_user_team_message(@team, user), method: :delete, class: "btn-tiny btn btn-remove", title: "Remove from team" do
... ...
app/views/teams/new.html.haml
... ... @@ -9,9 +9,15 @@
9 9 Team name is
10 10 .input
11 11 = f.text_field :name, placeholder: "Ex. Ruby Developers", class: "xxlarge left"
12   - &nbsp;
13   - = f.submit 'Create team', class: "btn btn-create"
14   - %hr
  12 +
  13 + .clearfix.team-description-holder
  14 + = f.label :description, "Details"
  15 + .input
  16 + = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4
  17 +
  18 + .form-actions
  19 + = f.submit 'Create team', class: "btn btn-create"
  20 +
15 21 .padded
16 22 %ul
17 23 %li All created teams are public (users can view who enter into team and which project are assigned for this team)
... ...
app/views/teams/show.html.haml
... ... @@ -11,6 +11,9 @@
11 11 %p.nothing_here_message Projects activity will be displayed here
12 12 .loading.hide
13 13 .side.span4
  14 + - if @team.description.present?
  15 + .description.well.light
  16 + = @team.description
14 17 = render "projects", projects: @projects
15 18 %div
16 19 %span.rss-icon
... ...
config/gitlab.yml.example
1 1 # # # # # # # # # # # # # # # # # #
2   -# Gitlab application config file #
  2 +# GitLab application config file #
3 3 # # # # # # # # # # # # # # # # # #
4 4 #
5 5 # How to use:
... ... @@ -37,9 +37,25 @@ production: &amp;base
37 37 # signup_enabled: true # default: false - Account passwords are not sent via the email if signup is enabled.
38 38 # username_changing_enabled: false # default: true - User can change her username/namespace
39 39  
  40 +
  41 + ## External issues trackers
  42 + issues_tracker:
  43 + redmine:
  44 + ## If not nil, link 'Issues' on project page will be replaced with this
  45 + ## Use placeholders:
  46 + ## :project_id - GitLab project identifier
  47 + ## :issues_tracker_id - Project Name or Id in external issue tracker
  48 + project_url: "http://redmine.sample/projects/:issues_tracker_id"
  49 + ## If not nil, links from /#\d/ entities from commit messages will replaced with this
  50 + ## Use placeholders:
  51 + ## :project_id - GitLab project identifier
  52 + ## :issues_tracker_id - Project Name or Id in external issue tracker
  53 + ## :id - Issue id (from commit messages)
  54 + issues_url: "http://redmine.sample/issues/:id"
  55 +
40 56 ## Gravatar
41 57 gravatar:
42   - enabled: true # Use user avatar images from Gravatar.com (default: true)
  58 + enabled: true # Use user avatar image from Gravatar.com (default: true)
43 59 # plain_url: "http://..." # default: http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=mm
44 60 # ssl_url: "https://..." # default: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=mm
45 61  
... ... @@ -60,22 +76,21 @@ production: &amp;base
60 76 bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
61 77 password: '_the_password_of_the_bind_user'
62 78  
63   - ## Omniauth settings
  79 + ## OmniAuth settings
64 80 omniauth:
65   - # Enable ability for users
66   - # Allow logging in via Twitter, Google, etc. using Omniauth providers
  81 + # Allow login via Twitter, Google, etc. using OmniAuth providers
67 82 enabled: false
68 83  
69 84 # CAUTION!
70   - # This allows users to login without having a user account first (default: false)
  85 + # This allows users to login without having a user account first (default: false).
71 86 # User accounts will be created automatically when authentication was successful.
72 87 allow_single_sign_on: false
73   - # Locks down those users until they have been cleared by the admin (default: true)
  88 + # Locks down those users until they have been cleared by the admin (default: true).
74 89 block_auto_created_users: true
75 90  
76 91 ## Auth providers
77   - # Uncomment the lines and fill in the data of the auth provider you want to use
78   - # If your favorite auth provider is not listed you can user others:
  92 + # Uncomment the following lines and fill in the data of the auth provider you want to use
  93 + # If your favorite auth provider is not listed you can use others:
79 94 # see https://github.com/gitlabhq/gitlabhq/wiki/Using-Custom-Omniauth-Providers
80 95 # The 'app_id' and 'app_secret' parameters are always passed as the first two
81 96 # arguments, followed by optional 'args' which can be either a hash or an array.
... ... @@ -114,7 +129,7 @@ production: &amp;base
114 129 upload_pack: true
115 130 receive_pack: true
116 131  
117   - # If you use non-standart ssh port you need to specify it
  132 + # If you use non-standard ssh port you need to specify it
118 133 # ssh_port: 22
119 134  
120 135 ## Git settings
... ... @@ -122,10 +137,10 @@ production: &amp;base
122 137 # Use the default values unless you really know what you are doing
123 138 git:
124 139 bin_path: /usr/bin/git
125   - # Max size of git object like commit, in bytes
126   - # This value can be increased if you have a very large commits
  140 + # Max size of a git object (e.g. a commit), in bytes
  141 + # This value can be increased if you have very large commits
127 142 max_size: 5242880 # 5.megabytes
128   - # Git timeout to read commit, in seconds
  143 + # Git timeout to read a commit, in seconds
129 144 timeout: 10
130 145  
131 146 development:
... ... @@ -133,6 +148,10 @@ development:
133 148  
134 149 test:
135 150 <<: *base
  151 + issues_tracker:
  152 + redmine:
  153 + project_url: "http://redmine/projects/:issues_tracker_id"
  154 + issues_url: "http://redmine/:project_id/:issues_tracker_id/:id"
136 155  
137 156 staging:
138 157 <<: *base
... ...
config/initializers/1_settings.rb
... ... @@ -42,6 +42,8 @@ Settings[&#39;omniauth&#39;] ||= Settingslogic.new({})
42 42 Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil?
43 43 Settings.omniauth['providers'] ||= []
44 44  
  45 +Settings['issues_tracker'] ||= {}
  46 +
45 47 #
46 48 # GitLab
47 49 #
... ... @@ -50,7 +52,7 @@ Settings.gitlab[&#39;default_projects_limit&#39;] ||= 10
50 52 Settings.gitlab['host'] ||= 'localhost'
51 53 Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
52 54 Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80
53   -Settings.gitlab['relative_url_root'] ||= ''
  55 +Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || ''
54 56 Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http"
55 57 Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}"
56 58 Settings.gitlab['support_email'] ||= Settings.gitlab.email_from
... ...
config/initializers/devise.rb
... ... @@ -99,7 +99,7 @@ Devise.setup do |config|
99 99  
100 100 # ==> Configuration for :validatable
101 101 # Range for password length. Default is 6..128.
102   - # config.password_length = 6..128
  102 + config.password_length = 6..128
103 103  
104 104 # Email regex used to validate email formats. It simply asserts that
105 105 # an one (and only one) @ exists in the given string. This is mainly
... ...
config/locales/devise.en.yml
... ... @@ -17,6 +17,7 @@ en:
17 17 unauthenticated: 'You need to sign in before continuing.'
18 18 unconfirmed: 'You have to confirm your account before continuing.'
19 19 locked: 'Your account is locked.'
  20 + not_found_in_database: 'Invalid email or password.'
20 21 invalid: 'Invalid email or password.'
21 22 invalid_token: 'Invalid authentication token.'
22 23 timeout: 'Your session expired, please sign in again to continue.'
... ...
config/routes.rb
... ... @@ -49,7 +49,7 @@ Gitlab::Application.routes.draw do
49 49 #
50 50 # Attachments serving
51 51 #
52   - get 'files/:type/:id/:filename' => 'files#download', constraints: { id: /\d+/, type: /[a-z]+/, filename: /[a-zA-Z.0-9_\-\+]+/ }
  52 + get 'files/:type/:id/:filename' => 'files#download', constraints: { id: /\d+/, type: /[a-z]+/, filename: /.+/ }
53 53  
54 54 #
55 55 # Admin Area
... ...
db/migrate/20130123114545_add_issues_tracker_to_project.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class AddIssuesTrackerToProject < ActiveRecord::Migration
  2 + def change
  3 + add_column :projects, :issues_tracker, :string, default: :gitlab, null: false
  4 + end
  5 +end
... ...
db/migrate/20130206084024_add_description_to_namsespace.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class AddDescriptionToNamsespace < ActiveRecord::Migration
  2 + def change
  3 + add_column :namespaces, :description, :string, default: '', null: false
  4 + end
  5 +end
... ...
db/migrate/20130207104426_add_description_to_teams.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class AddDescriptionToTeams < ActiveRecord::Migration
  2 + def change
  3 + add_column :user_teams, :description, :string, default: '', null: false
  4 + end
  5 +end
... ...
db/migrate/20130211085435_add_issues_tracker_id_to_project.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class AddIssuesTrackerIdToProject < ActiveRecord::Migration
  2 + def change
  3 + add_column :projects, :issues_tracker_id, :string
  4 + end
  5 +end
... ...
db/migrate/20130218141258_convert_closed_to_state_in_issue.rb
1 1 class ConvertClosedToStateInIssue < ActiveRecord::Migration
2 2 def up
3 3 Issue.transaction do
4   - Issue.where(closed: true).update_all("state = 'closed'")
5   - Issue.where(closed: false).update_all("state = 'opened'")
  4 + Issue.where(closed: true).update_all(state: :closed)
  5 + Issue.where(closed: false).update_all(state: :opened)
6 6 end
7 7 end
8 8  
9 9 def down
10 10 Issue.transaction do
11   - Issue.where(state: :closed).update_all("closed = 1")
  11 + Issue.where(state: :closed).update_all(closed: true)
12 12 end
13 13 end
14 14 end
... ...
db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb
1 1 class ConvertClosedToStateInMergeRequest < ActiveRecord::Migration
2 2 def up
3 3 MergeRequest.transaction do
4   - MergeRequest.where(closed: true, merged: true).update_all("state = 'merged'")
5   - MergeRequest.where(closed: true, merged: true).update_all("state = 'closed'")
6   - MergeRequest.where(closed: false).update_all("state = 'opened'")
  4 + MergeRequest.where(closed: true, merged: true).update_all(state: :merged)
  5 + MergeRequest.where(closed: true, merged: false).update_all(state: :closed)
  6 + MergeRequest.where(closed: false).update_all(state: :opened)
7 7 end
8 8 end
9 9  
... ...
db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb
1 1 class ConvertClosedToStateInMilestone < ActiveRecord::Migration
2 2 def up
3 3 Milestone.transaction do
4   - Milestone.where(closed: false).update_all("state = 'opened'")
5   - Milestone.where(closed: false).update_all("state = 'active'")
  4 + Milestone.where(closed: true).update_all(state: :closed)
  5 + Milestone.where(closed: false).update_all(state: :active)
6 6 end
7 7 end
8 8  
9 9 def down
10 10 Milestone.transaction do
11   - Milestone.where(state: :closed).update_all("closed = 1")
  11 + Milestone.where(state: :closed).update_all(closed: true)
12 12 end
13 13 end
14 14 end
... ...
db/migrate/20130304104623_add_state_to_user.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class AddStateToUser < ActiveRecord::Migration
  2 + def change
  3 + add_column :users, :state, :string
  4 + end
  5 +end
... ...
db/migrate/20130304104740_convert_blocked_to_state.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +class ConvertBlockedToState < ActiveRecord::Migration
  2 + def up
  3 + User.transaction do
  4 + User.where(blocked: true).update_all(state: :blocked)
  5 + User.where(blocked: false).update_all(state: :active)
  6 + end
  7 + end
  8 +
  9 + def down
  10 + User.transaction do
  11 + User.where(state: :blocked).update_all(blocked: :true)
  12 + end
  13 + end
  14 +end
... ...
db/migrate/20130304105317_remove_blocked_from_user.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +class RemoveBlockedFromUser < ActiveRecord::Migration
  2 + def up
  3 + remove_column :users, :blocked
  4 + end
  5 +
  6 + def down
  7 + add_column :users, :blocked, :boolean
  8 + end
  9 +end
... ...
db/schema.rb
... ... @@ -11,7 +11,7 @@
11 11 #
12 12 # It's strongly recommended to check this file into your version control system.
13 13  
14   -ActiveRecord::Schema.define(:version => 20130220133245) do
  14 +ActiveRecord::Schema.define(:version => 20130304105317) do
15 15  
16 16 create_table "events", :force => true do |t|
17 17 t.string "target_type"
... ... @@ -112,6 +112,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130220133245) do
112 112 t.datetime "created_at", :null => false
113 113 t.datetime "updated_at", :null => false
114 114 t.string "type"
  115 + t.string "description", :default => "", :null => false
115 116 end
116 117  
117 118 add_index "namespaces", ["name"], :name => "index_namespaces_on_name"
... ... @@ -152,6 +153,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130220133245) do
152 153 t.boolean "wiki_enabled", :default => true, :null => false
153 154 t.integer "namespace_id"
154 155 t.boolean "public", :default => false, :null => false
  156 + t.string "issues_tracker", :default => "gitlab", :null => false
  157 + t.string "issues_tracker_id"
155 158 end
156 159  
157 160 add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id"
... ... @@ -232,6 +235,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130220133245) do
232 235 t.integer "owner_id"
233 236 t.datetime "created_at", :null => false
234 237 t.datetime "updated_at", :null => false
  238 + t.string "description", :default => "", :null => false
235 239 end
236 240  
237 241 create_table "users", :force => true do |t|
... ... @@ -257,7 +261,6 @@ ActiveRecord::Schema.define(:version =&gt; 20130220133245) do
257 261 t.boolean "dark_scheme", :default => false, :null => false
258 262 t.integer "theme_id", :default => 1, :null => false
259 263 t.string "bio"
260   - t.boolean "blocked", :default => false, :null => false
261 264 t.integer "failed_attempts", :default => 0
262 265 t.datetime "locked_at"
263 266 t.string "extern_uid"
... ... @@ -265,10 +268,10 @@ ActiveRecord::Schema.define(:version =&gt; 20130220133245) do
265 268 t.string "username"
266 269 t.boolean "can_create_group", :default => true, :null => false
267 270 t.boolean "can_create_team", :default => true, :null => false
  271 + t.string "state"
268 272 end
269 273  
270 274 add_index "users", ["admin"], :name => "index_users_on_admin"
271   - add_index "users", ["blocked"], :name => "index_users_on_blocked"
272 275 add_index "users", ["email"], :name => "index_users_on_email", :unique => true
273 276 add_index "users", ["extern_uid", "provider"], :name => "index_users_on_extern_uid_and_provider", :unique => true
274 277 add_index "users", ["name"], :name => "index_users_on_name"
... ...
doc/install/databases.md
... ... @@ -12,7 +12,7 @@ GitLab supports the following databases:
12 12 sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
13 13  
14 14 # Login to MySQL
15   - $ mysql -u root -p
  15 + mysql -u root -p
16 16  
17 17 # Create a user for GitLab. (change $password to a real password)
18 18 mysql> CREATE USER 'gitlab'@'localhost' IDENTIFIED BY '$password';
... ...
doc/install/installation.md
1   -This installation guide was created for Debian/Ubuntu and tested on it.
2   -
3   -Please read [`doc/install/requirements.md`](./requirements.md) for hardware and platform requirements.
  1 +This installation guide was created for Debian/Ubuntu and tested on it. Please read [`doc/install/requirements.md`](./requirements.md) for hardware and platform requirements.
4 2  
  3 +This installation guide is recommended to set up a production server. If you want a development environment please use the [Vargrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm) since it makes it much easier to set up all the dependencies for integration testing.
5 4  
6 5 **Important Note:**
7 6 The following steps have been known to work.
... ... @@ -92,21 +91,29 @@ Create a `git` user for Gitlab:
92 91  
93 92 sudo adduser --disabled-login --gecos 'GitLab' git
94 93  
  94 +
95 95 # 4. GitLab shell
96 96  
97   - # Login as git
  97 +GitLab Shell is a ssh access and repository management software developed specially for GitLab.
  98 +
  99 + # Login as git
98 100 sudo su git
99 101  
100   - # Go to home directory
  102 + # Go to home directory
101 103 cd /home/git
102 104  
103 105 # Clone gitlab shell
104 106 git clone https://github.com/gitlabhq/gitlab-shell.git
105 107  
106   - # Setup
107 108 cd gitlab-shell
108 109 cp config.yml.example config.yml
109   - ./bin/install
  110 +
  111 + # Edit config and replace gitlab_url
  112 + # with something like 'http://domain.com/'
  113 + vim config.yml
  114 +
  115 + # Do setup
  116 + ./bin/install
110 117  
111 118  
112 119 # 5. Database
... ... @@ -124,9 +131,9 @@ To setup the MySQL/PostgreSQL database and dependencies please see [`doc/install
124 131 # Clone GitLab repository
125 132 sudo -u git -H git clone https://github.com/gitlabhq/gitlabhq.git gitlab
126 133  
127   - # Go to gitlab dir
  134 + # Go to gitlab dir
128 135 cd /home/git/gitlab
129   -
  136 +
130 137 # Checkout to stable release
131 138 sudo -u git -H git checkout 5-0-stable
132 139  
... ... @@ -157,7 +164,7 @@ do so with caution!
157 164 # Create directory for pids and make sure GitLab can write to it
158 165 sudo -u git -H mkdir tmp/pids/
159 166 sudo chmod -R u+rwX tmp/pids/
160   -
  167 +
161 168 # Copy the example Unicorn config
162 169 sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb
163 170  
... ... @@ -188,7 +195,7 @@ Make sure to update username/password in config/database.yml.
188 195  
189 196  
190 197 ## Initialise Database and Activate Advanced Features
191   -
  198 +
192 199 sudo -u git -H bundle exec rake db:setup RAILS_ENV=production
193 200 sudo -u git -H bundle exec rake db:seed_fu RAILS_ENV=production
194 201 sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
... ... @@ -286,7 +293,7 @@ a different host, you can configure its connection string via the
286 293 ## Custom SSH Connection
287 294  
288 295 If you are running SSH on a non-standard port, you must change the gitlab user's SSH config.
289   -
  296 +
290 297 # Add to /home/git/.ssh/config
291 298 host localhost # Give your setup a name (here: override localhost)
292 299 user git # Your remote git user
... ...
features/project/network.feature
... ... @@ -7,3 +7,19 @@ Feature: Project Network Graph
7 7 @javascript
8 8 Scenario: I should see project network
9 9 Then page should have network graph
  10 + And page should select "master" in select box
  11 + And page should have "master" on graph
  12 +
  13 + @javascript
  14 + Scenario: I should switch ref to "stable"
  15 + When I switch ref to "stable"
  16 + Then page should have network graph
  17 + And page should select "stable" in select box
  18 + And page should have "stable" on graph
  19 +
  20 + @javascript
  21 + Scenario: I should looking for a commit by SHA of "v2.1.0"
  22 + When I looking for a commit by SHA of "v2.1.0"
  23 + Then page should have network graph
  24 + And page should select "master" in select box
  25 + And page should have "v2.1.0" on graph
... ...
features/steps/admin/admin_groups.rb
... ... @@ -25,11 +25,13 @@ class AdminGroups &lt; Spinach::FeatureSteps
25 25  
26 26 And 'submit form with new group info' do
27 27 fill_in 'group_name', :with => 'gitlab'
  28 + fill_in 'group_description', :with => 'Group description'
28 29 click_button "Create group"
29 30 end
30 31  
31 32 Then 'I should see newly created group' do
32 33 page.should have_content "Group: gitlab"
  34 + page.should have_content "Group description"
33 35 end
34 36  
35 37 Then 'I should be redirected to group page' do
... ...
features/steps/admin/admin_teams.rb
... ... @@ -18,6 +18,7 @@ class AdminTeams &lt; Spinach::FeatureSteps
18 18  
19 19 And 'submit form with new team info' do
20 20 fill_in 'user_team_name', with: 'gitlab'
  21 + fill_in 'user_team_description', with: 'description'
21 22 click_button 'Create team'
22 23 end
23 24  
... ... @@ -27,6 +28,7 @@ class AdminTeams &lt; Spinach::FeatureSteps
27 28  
28 29 And 'I should see newly created team' do
29 30 page.should have_content "Team: gitlab"
  31 + page.should have_content "description"
30 32 end
31 33  
32 34 When 'I visit admin teams page' do
... ...
features/steps/group/group.rb
... ... @@ -28,7 +28,7 @@ class Groups &lt; Spinach::FeatureSteps
28 28  
29 29 Then 'I should see merge requests from this group assigned to me' do
30 30 assigned_to_me(:merge_requests).each do |issue|
31   - page.should have_content issue.title
  31 + page.should have_content issue.title[0..80]
32 32 end
33 33 end
34 34  
... ... @@ -69,12 +69,14 @@ class Groups &lt; Spinach::FeatureSteps
69 69 end
70 70  
71 71 And 'submit form with new group info' do
72   - fill_in 'group_name', :with => 'Samurai'
  72 + fill_in 'group_name', with: 'Samurai'
  73 + fill_in 'group_description', with: 'Tokugawa Shogunate'
73 74 click_button "Create group"
74 75 end
75 76  
76 77 Then 'I should see newly created group' do
77 78 page.should have_content "Samurai"
  79 + page.should have_content "Tokugawa Shogunate"
78 80 page.should have_content "You will only see events from projects in this group"
79 81 end
80 82  
... ...
features/steps/project/project_merge_requests.rb
... ... @@ -25,8 +25,8 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
25 25 end
26 26  
27 27 Then 'I should see closed merge request "Bug NS-04"' do
28   - mr = MergeRequest.find_by_title("Bug NS-04")
29   - mr.closed?.should be_true
  28 + merge_request = MergeRequest.find_by_title!("Bug NS-04")
  29 + merge_request.closed?.should be_true
30 30 page.should have_content "Closed by"
31 31 end
32 32  
... ... @@ -63,7 +63,6 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
63 63 end
64 64  
65 65 And 'project "Shop" have "Bug NS-04" open merge request' do
66   - project = Project.find_by_name("Shop")
67 66 create(:merge_request,
68 67 title: "Bug NS-04",
69 68 project: project,
... ... @@ -71,7 +70,6 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
71 70 end
72 71  
73 72 And 'project "Shop" have "Bug NS-05" open merge request with diffs inside' do
74   - project = Project.find_by_name("Shop")
75 73 create(:merge_request_with_diffs,
76 74 title: "Bug NS-05",
77 75 project: project,
... ... @@ -79,7 +77,6 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
79 77 end
80 78  
81 79 And 'project "Shop" have "Feature NS-03" closed merge request' do
82   - project = Project.find_by_name("Shop")
83 80 create(:closed_merge_request,
84 81 title: "Feature NS-03",
85 82 project: project,
... ... @@ -87,18 +84,16 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
87 84 end
88 85  
89 86 And 'I switch to the diff tab' do
90   - mr = MergeRequest.find_by_title("Bug NS-05")
91   - visit diffs_project_merge_request_path(mr.project, mr)
  87 + visit diffs_project_merge_request_path(project, merge_request)
92 88 end
93 89  
94 90 And 'I switch to the merge request\'s comments tab' do
95   - mr = MergeRequest.find_by_title("Bug NS-05")
96   - visit project_merge_request_path(mr.project, mr)
  91 + visit project_merge_request_path(project, merge_request)
97 92 end
98 93  
99 94 And 'I click on the first commit in the merge request' do
100   - mr = MergeRequest.find_by_title("Bug NS-05")
101   - click_link mr.commits.first.short_id(8)
  95 +
  96 + click_link merge_request.commits.first.short_id(8)
102 97 end
103 98  
104 99 And 'I leave a comment on the diff page' do
... ... @@ -121,8 +116,7 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
121 116 end
122 117  
123 118 Then 'I should see a discussion has started on line 185' do
124   - mr = MergeRequest.find_by_title("Bug NS-05")
125   - first_commit = mr.commits.first
  119 + first_commit = merge_request.commits.first
126 120 first_diff = first_commit.diffs.first
127 121 page.should have_content "#{current_user.name} started a discussion on this merge request diff"
128 122 page.should have_content "#{first_diff.b_path}:L185"
... ... @@ -130,8 +124,7 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
130 124 end
131 125  
132 126 Then 'I should see a discussion has started on commit bcf03b5de6c:L185' do
133   - mr = MergeRequest.find_by_title("Bug NS-05")
134   - first_commit = mr.commits.first
  127 + first_commit = merge_request.commits.first
135 128 first_diff = first_commit.diffs.first
136 129 page.should have_content "#{current_user.name} started a discussion on commit"
137 130 page.should have_content first_commit.short_id(8)
... ... @@ -140,12 +133,19 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
140 133 end
141 134  
142 135 Then 'I should see a discussion has started on commit bcf03b5de6c' do
143   - mr = MergeRequest.find_by_title("Bug NS-05")
144   - first_commit = mr.st_commits.first
  136 + first_commit = merge_request.st_commits.first
145 137 first_diff = first_commit.diffs.first
146 138 page.should have_content "#{current_user.name} started a discussion on commit bcf03b5de6c"
147 139 page.should have_content first_commit.short_id(8)
148 140 page.should have_content "One comment to rule them all"
149 141 page.should have_content "#{first_diff.b_path}:L185"
150 142 end
  143 +
  144 + def project
  145 + @project ||= Project.find_by_name!("Shop")
  146 + end
  147 +
  148 + def merge_request
  149 + @merge_request ||= MergeRequest.find_by_title!("Bug NS-05")
  150 + end
151 151 end
... ...
features/steps/project/project_network_graph.rb
... ... @@ -4,16 +4,51 @@ class ProjectNetworkGraph &lt; Spinach::FeatureSteps
4 4  
5 5 Then 'page should have network graph' do
6 6 page.should have_content "Project Network Graph"
7   - within ".graph" do
8   - page.should have_content "master"
9   - end
  7 + page.should have_selector ".graph"
10 8 end
11 9  
12   - And 'I visit project "Shop" network page' do
  10 + When 'I visit project "Shop" network page' do
13 11 # Stub Graph::JsonBuilder max_size to speed up test (10 commits vs. 650)
14   - Gitlab::Graph::JsonBuilder.stub(max_count: 10)
  12 + Graph::JsonBuilder.stub(max_count: 10)
15 13  
16 14 project = Project.find_by_name("Shop")
17 15 visit project_graph_path(project, "master")
18 16 end
  17 +
  18 + And 'page should select "master" in select box' do
  19 + page.should have_selector '#ref_chzn span', :text => "master"
  20 + end
  21 +
  22 + And 'page should have "master" on graph' do
  23 + within '.graph' do
  24 + page.should have_content 'master'
  25 + end
  26 + end
  27 +
  28 + And 'I switch ref to "stable"' do
  29 + page.select 'stable', :from => 'ref'
  30 + end
  31 +
  32 + And 'page should select "stable" in select box' do
  33 + page.should have_selector '#ref_chzn span', :text => "stable"
  34 + end
  35 +
  36 + And 'page should have "stable" on graph' do
  37 + within '.graph' do
  38 + page.should have_content 'stable'
  39 + end
  40 + end
  41 +
  42 + And 'I looking for a commit by SHA of "v2.1.0"' do
  43 + within ".content .search" do
  44 + fill_in 'q', :with => '98d6492'
  45 + find('button').click
  46 + end
  47 + end
  48 +
  49 + And 'page should have "v2.1.0" on graph' do
  50 + within '.graph' do
  51 + page.should have_content 'v2.1.0'
  52 + end
  53 + end
19 54 end
... ...
features/steps/shared/paths.rb
... ... @@ -143,7 +143,7 @@ module SharedPaths
143 143  
144 144 Given "I visit my project's network page" do
145 145 # Stub Graph::JsonBuilder max_size to speed up test (10 commits vs. 650)
146   - Gitlab::Graph::JsonBuilder.stub(max_count: 10)
  146 + Graph::JsonBuilder.stub(max_count: 10)
147 147  
148 148 visit project_graph_path(@project, root_ref)
149 149 end
... ...
features/steps/userteams/userteams.rb
... ... @@ -44,9 +44,16 @@ class Userteams &lt; Spinach::FeatureSteps
44 44  
45 45 And 'I submit form with new team info' do
46 46 fill_in 'name', with: 'gitlab'
  47 +
  48 + fill_in 'user_team_description', with: 'team description'
47 49 click_button 'Create team'
48 50 end
49 51  
  52 + And 'I should see newly created team' do
  53 + page.should have_content "gitlab"
  54 + page.should have_content "team description"
  55 + end
  56 +
50 57 Then 'I should be redirected to new team page' do
51 58 team = UserTeam.last
52 59 current_path.should == team_path(team)
... ...
features/support/env.rb
... ... @@ -34,6 +34,7 @@ Spinach.hooks.before_scenario do
34 34 Gitlab.config.gitlab_shell.stub(repos_path: Rails.root.join('tmp', 'test-git-base-path'))
35 35 FileUtils.rm_rf Gitlab.config.gitlab_shell.repos_path
36 36 FileUtils.mkdir_p Gitlab.config.gitlab_shell.repos_path
  37 + DatabaseCleaner.start
37 38 end
38 39  
39 40 Spinach.hooks.after_scenario do
... ...
features/teams/team.feature
... ... @@ -20,6 +20,7 @@ Feature: UserTeams
20 20 When I click to "New team" link
21 21 And I submit form with new team info
22 22 Then I should be redirected to new team page
  23 + Then I should see newly created team
23 24  
24 25 Scenario: I should see team dashboard list
25 26 When I have teams with projects and members
... ...
lib/api/entities.rb
... ... @@ -2,11 +2,11 @@ module Gitlab
2 2 module Entities
3 3 class User < Grape::Entity
4 4 expose :id, :username, :email, :name, :bio, :skype, :linkedin, :twitter,
5   - :dark_scheme, :theme_id, :blocked, :created_at, :extern_uid, :provider
  5 + :dark_scheme, :theme_id, :state, :created_at, :extern_uid, :provider
6 6 end
7 7  
8 8 class UserBasic < Grape::Entity
9   - expose :id, :username, :email, :name, :blocked, :created_at
  9 + expose :id, :username, :email, :name, :state, :created_at
10 10 end
11 11  
12 12 class UserLogin < UserBasic
... ...
lib/api/projects.rb
... ... @@ -52,8 +52,8 @@ module Gitlab
52 52 :issues_enabled,
53 53 :wall_enabled,
54 54 :merge_requests_enabled,
55   - :wiki_enabled]
56   -
  55 + :wiki_enabled,
  56 + :namespace_id]
57 57 @project = ::Projects::CreateContext.new(current_user, attrs).execute
58 58 if @project.saved?
59 59 present @project, with: Entities::Project
... ...
lib/extracts_path.rb
... ... @@ -126,7 +126,7 @@ module ExtractsPath
126 126 @tree = TreeDecorator.new(@tree)
127 127  
128 128 raise InvalidPathError if @tree.invalid?
129   - rescue NoMethodError, InvalidPathError
  129 + rescue RuntimeError, NoMethodError, InvalidPathError
130 130 not_found!
131 131 end
132 132 end
... ...
lib/gitlab/auth.rb
... ... @@ -41,10 +41,12 @@ module Gitlab
41 41 password_confirmation: password,
42 42 projects_limit: Gitlab.config.gitlab.default_projects_limit,
43 43 }, as: :admin)
  44 + @user.save!
  45 +
44 46 if Gitlab.config.omniauth['block_auto_created_users'] && !ldap
45   - @user.blocked = true
  47 + @user.block
46 48 end
47   - @user.save!
  49 +
48 50 @user
49 51 end
50 52  
... ...
lib/gitlab/graph/commit.rb
... ... @@ -1,52 +0,0 @@
1   -require "grit"
2   -
3   -module Gitlab
4   - module Graph
5   - class Commit
6   - include ActionView::Helpers::TagHelper
7   -
8   - attr_accessor :time, :space, :refs, :parent_spaces
9   -
10   - def initialize(commit)
11   - @_commit = commit
12   - @time = -1
13   - @space = 0
14   - @parent_spaces = []
15   - end
16   -
17   - def method_missing(m, *args, &block)
18   - @_commit.send(m, *args, &block)
19   - end
20   -
21   - def to_graph_hash
22   - h = {}
23   - h[:parents] = self.parents.collect do |p|
24   - [p.id,0,0]
25   - end
26   - h[:author] = {
27   - name: author.name,
28   - email: author.email
29   - }
30   - h[:time] = time
31   - h[:space] = space
32   - h[:parent_spaces] = parent_spaces
33   - h[:refs] = refs.collect{|r|r.name}.join(" ") unless refs.nil?
34   - h[:id] = sha
35   - h[:date] = date
36   - h[:message] = message
37   - h
38   - end
39   -
40   - def add_refs(ref_cache, repo)
41   - if ref_cache.empty?
42   - repo.refs.each do |ref|
43   - ref_cache[ref.commit.id] ||= []
44   - ref_cache[ref.commit.id] << ref
45   - end
46   - end
47   - @refs = ref_cache[@_commit.id] if ref_cache.include?(@_commit.id)
48   - @refs ||= []
49   - end
50   - end
51   - end
52   -end
lib/gitlab/graph/json_builder.rb
... ... @@ -1,268 +0,0 @@
1   -require "grit"
2   -
3   -module Gitlab
4   - module Graph
5   - class JsonBuilder
6   - attr_accessor :days, :commits, :ref_cache, :repo
7   -
8   - def self.max_count
9   - @max_count ||= 650
10   - end
11   -
12   - def initialize project, ref, commit
13   - @project = project
14   - @ref = ref
15   - @commit = commit
16   - @repo = project.repo
17   - @ref_cache = {}
18   -
19   - @commits = collect_commits
20   - @days = index_commits
21   - end
22   -
23   - def to_json(*args)
24   - {
25   - days: @days.compact.map { |d| [d.day, d.strftime("%b")] },
26   - commits: @commits.map(&:to_graph_hash)
27   - }.to_json(*args)
28   - end
29   -
30   - protected
31   -
32   - # Get commits from repository
33   - #
34   - def collect_commits
35   -
36   - @commits = Grit::Commit.find_all(repo, nil, {topo_order: true, max_count: self.class.max_count, skip: to_commit}).dup
37   -
38   - # Decorate with app/models/commit.rb
39   - @commits.map! { |commit| ::Commit.new(commit) }
40   -
41   - # Decorate with lib/gitlab/graph/commit.rb
42   - @commits.map! { |commit| Gitlab::Graph::Commit.new(commit) }
43   -
44   - # add refs to each commit
45   - @commits.each { |commit| commit.add_refs(ref_cache, repo) }
46   -
47   - @commits
48   - end
49   -
50   - # Method is adding time and space on the
51   - # list of commits. As well as returns date list
52   - # corelated with time set on commits.
53   - #
54   - # @param [Array<Graph::Commit>] commits to index
55   - #
56   - # @return [Array<TimeDate>] list of commit dates corelated with time on commits
57   - def index_commits
58   - days, times = [], []
59   - map = {}
60   -
61   - commits.reverse.each_with_index do |c,i|
62   - c.time = i
63   - days[i] = c.committed_date
64   - map[c.id] = c
65   - times[i] = c
66   - end
67   -
68   - @_reserved = {}
69   - days.each_index do |i|
70   - @_reserved[i] = []
71   - end
72   -
73   - commits_sort_by_ref.each do |commit|
74   - if map.include? commit.id then
75   - place_chain(map[commit.id], map)
76   - end
77   - end
78   -
79   - # find parent spaces for not overlap lines
80   - times.each do |c|
81   - c.parent_spaces.concat(find_free_parent_spaces(c, map, times))
82   - end
83   -
84   - days
85   - end
86   -
87   - # Skip count that the target commit is displayed in center.
88   - def to_commit
89   - commits = Grit::Commit.find_all(repo, nil, {topo_order: true})
90   - commit_index = commits.index do |c|
91   - c.id == @commit.id
92   - end
93   -
94   - if commit_index && (self.class.max_count / 2 < commit_index) then
95   - # get max index that commit is displayed in the center.
96   - commit_index - self.class.max_count / 2
97   - else
98   - 0
99   - end
100   - end
101   -
102   - def commits_sort_by_ref
103   - commits.sort do |a,b|
104   - if include_ref?(a)
105   - -1
106   - elsif include_ref?(b)
107   - 1
108   - else
109   - b.committed_date <=> a.committed_date
110   - end
111   - end
112   - end
113   -
114   - def include_ref?(commit)
115   - heads = commit.refs.select do |ref|
116   - ref.is_a?(Grit::Head) or ref.is_a?(Grit::Remote) or ref.is_a?(Grit::Tag)
117   - end
118   -
119   - heads.map! do |head|
120   - head.name
121   - end
122   -
123   - heads.include?(@ref)
124   - end
125   -
126   - def find_free_parent_spaces(commit, map, times)
127   - spaces = []
128   -
129   - commit.parents.each do |p|
130   - if map.include?(p.id) then
131   - parent = map[p.id]
132   -
133   - range = if commit.time < parent.time then
134   - commit.time..parent.time
135   - else
136   - parent.time..commit.time
137   - end
138   -
139   - space = if commit.space >= parent.space then
140   - find_free_parent_space(range, parent.space, 1, commit.space, times)
141   - else
142   - find_free_parent_space(range, parent.space, -1, parent.space, times)
143   - end
144   -
145   - mark_reserved(range, space)
146   - spaces << space
147   - end
148   - end
149   -
150   - spaces
151   - end
152   -
153   - def find_free_parent_space(range, space_base, space_step, space_default, times)
154   - if is_overlap?(range, times, space_default) then
155   - find_free_space(range, space_base, space_step)
156   - else
157   - space_default
158   - end
159   - end
160   -
161   - def is_overlap?(range, times, overlap_space)
162   - range.each do |i|
163   - if i != range.first &&
164   - i != range.last &&
165   - times[i].space == overlap_space then
166   -
167   - return true;
168   - end
169   - end
170   -
171   - false
172   - end
173   -
174   - # Add space mark on commit and its parents
175   - #
176   - # @param [Graph::Commit] the commit object.
177   - # @param [Hash<String,Graph::Commit>] map of commits
178   - def place_chain(commit, map, parent_time = nil)
179   - leaves = take_left_leaves(commit, map)
180   - if leaves.empty?
181   - return
182   - end
183   - # and mark it as reserved
184   - min_time = leaves.last.time
185   - max_space = 1
186   - parents = leaves.last.parents.collect
187   - parents.each do |p|
188   - if map.include? p.id
189   - parent = map[p.id]
190   - if parent.time < min_time
191   - min_time = parent.time
192   - end
193   - if max_space < parent.space then
194   - max_space = parent.space
195   - end
196   - end
197   - end
198   - if parent_time.nil?
199   - max_time = leaves.first.time
200   - else
201   - max_time = parent_time - 1
202   - end
203   -
204   - time_range = leaves.last.time..leaves.first.time
205   - space = find_free_space(time_range, max_space, 2)
206   - leaves.each{|l| l.space = space}
207   -
208   - mark_reserved(min_time..max_time, space)
209   -
210   - # Visit branching chains
211   - leaves.each do |l|
212   - parents = l.parents.collect.select{|p| map.include? p.id and map[p.id].space.zero?}
213   - for p in parents
214   - place_chain(map[p.id], map, l.time)
215   - end
216   - end
217   - end
218   -
219   - def mark_reserved(time_range, space)
220   - for day in time_range
221   - @_reserved[day].push(space)
222   - end
223   - end
224   -
225   - def find_free_space(time_range, space_base, space_step)
226   - reserved = []
227   - for day in time_range
228   - reserved += @_reserved[day]
229   - end
230   - reserved.uniq!
231   -
232   - space = space_base
233   - while reserved.include?(space) do
234   - space += space_step
235   - if space <= 0 then
236   - space_step *= -1
237   - space = space_base + space_step
238   - end
239   - end
240   -
241   - space
242   - end
243   -
244   - # Takes most left subtree branch of commits
245   - # which don't have space mark yet.
246   - #
247   - # @param [Graph::Commit] the commit object.
248   - # @param [Hash<String,Graph::Commit>] map of commits
249   - #
250   - # @return [Array<Graph::Commit>] list of branch commits
251   - def take_left_leaves(commit, map)
252   - leaves = []
253   - leaves.push(commit) if commit.space.zero?
254   -
255   - while true
256   - return leaves if commit.parents.count.zero?
257   - return leaves unless map.include? commit.parents.first.id
258   -
259   - commit = map[commit.parents.first.id]
260   -
261   - return leaves unless commit.space.zero?
262   -
263   - leaves.push(commit)
264   - end
265   - end
266   - end
267   - end
268   -end
lib/gitlab/markdown.rb
... ... @@ -25,6 +25,8 @@ module Gitlab
25 25 # >> gfm(":trollface:")
26 26 # => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":trollface:\" />
27 27 module Markdown
  28 + include IssuesHelper
  29 +
28 30 attr_reader :html_options
29 31  
30 32 # Public: Parse the provided text with GitLab-Flavored Markdown
... ... @@ -163,8 +165,11 @@ module Gitlab
163 165 end
164 166  
165 167 def reference_issue(identifier)
166   - if issue = @project.issues.where(id: identifier).first
167   - link_to("##{identifier}", project_issue_url(@project, issue), html_options.merge(title: "Issue: #{issue.title}", class: "gfm gfm-issue #{html_options[:class]}"))
  168 + if @project.issue_exists? identifier
  169 + url = url_for_issue(identifier)
  170 + title = title_for_issue(identifier)
  171 +
  172 + link_to("##{identifier}", url, html_options.merge(title: "Issue: #{title}", class: "gfm gfm-issue #{html_options[:class]}"))
168 173 end
169 174 end
170 175  
... ...
lib/tasks/sidekiq.rake
1 1 namespace :sidekiq do
2 2 desc "GITLAB | Stop sidekiq"
3 3 task :stop do
4   - run "bundle exec sidekiqctl stop #{pidfile}"
  4 + system "bundle exec sidekiqctl stop #{pidfile}"
5 5 end
6 6  
7 7 desc "GITLAB | Start sidekiq"
8 8 task :start do
9   - run "nohup bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1 &"
  9 + system "nohup bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1 &"
10 10 end
11   -
  11 +
12 12 desc "GITLAB | Start sidekiq with launchd on Mac OS X"
13 13 task :launchd do
14   - run "bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1"
  14 + system "bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1"
15 15 end
16   -
  16 +
17 17 def pidfile
18 18 Rails.root.join("tmp", "pids", "sidekiq.pid")
19 19 end
... ...
spec/factories.rb
... ... @@ -29,6 +29,11 @@ FactoryGirl.define do
29 29 creator
30 30 end
31 31  
  32 + factory :redmine_project, parent: :project do
  33 + issues_tracker { "redmine" }
  34 + issues_tracker_id { "project_name_in_redmine" }
  35 + end
  36 +
32 37 factory :group do
33 38 sequence(:name) { |n| "group#{n}" }
34 39 path { name.downcase.gsub(/\s/, '_') }
... ...
spec/factories/user_teams.rb
... ... @@ -15,6 +15,7 @@
15 15 FactoryGirl.define do
16 16 factory :user_team do
17 17 sequence(:name) { |n| "team#{n}" }
  18 + sequence(:description) { |n| "team_description#{n}" }
18 19 path { name.downcase.gsub(/\s/, '_') }
19 20 owner
20 21 end
... ...
spec/features/admin/admin_users_spec.rb
... ... @@ -55,8 +55,8 @@ describe &quot;Admin::Users&quot; do
55 55 user = User.last
56 56 email = ActionMailer::Base.deliveries.last
57 57 email.subject.should have_content("Account was created")
58   - email.body.should have_content(user.email)
59   - email.body.should have_content(@password)
  58 + email.text_part.body.should have_content(user.email)
  59 + email.text_part.body.should have_content(@password)
60 60 end
61 61 end
62 62  
... ... @@ -67,8 +67,8 @@ describe &quot;Admin::Users&quot; do
67 67 user = User.last
68 68 email = ActionMailer::Base.deliveries.last
69 69 email.subject.should have_content("Account was created")
70   - email.body.should have_content(user.email)
71   - email.body.should_not have_content(@password)
  70 + email.text_part.body.should have_content(user.email)
  71 + email.text_part.body.should_not have_content(@password)
72 72 end
73 73 end
74 74 end
... ...
spec/helpers/gitlab_markdown_helper_spec.rb
... ... @@ -2,6 +2,7 @@ require &quot;spec_helper&quot;
2 2  
3 3 describe GitlabMarkdownHelper do
4 4 include ApplicationHelper
  5 + include IssuesHelper
5 6  
6 7 let!(:project) { create(:project) }
7 8  
... ...
spec/helpers/issues_helper_spec.rb 0 → 100644
... ... @@ -0,0 +1,79 @@
  1 +require "spec_helper"
  2 +
  3 +describe IssuesHelper do
  4 + let(:project) { create :project }
  5 + let(:issue) { create :issue, project: project }
  6 + let(:ext_project) { create :redmine_project }
  7 +
  8 + describe :title_for_issue do
  9 + it "should return issue title if used internal tracker" do
  10 + @project = project
  11 + title_for_issue(issue.id).should eq issue.title
  12 + end
  13 +
  14 + it "should always return empty string if used external tracker" do
  15 + @project = ext_project
  16 + title_for_issue(rand(100)).should eq ""
  17 + end
  18 +
  19 + it "should always return empty string if project nil" do
  20 + @project = nil
  21 +
  22 + title_for_issue(rand(100)).should eq ""
  23 + end
  24 + end
  25 +
  26 + describe :url_for_project_issues do
  27 + let(:project_url) { Gitlab.config.issues_tracker.redmine.project_url}
  28 + let(:ext_expected) do
  29 + project_url.gsub(':project_id', ext_project.id.to_s)
  30 + .gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
  31 + end
  32 + let(:int_expected) { polymorphic_path([project]) }
  33 +
  34 + it "should return internal path if used internal tracker" do
  35 + @project = project
  36 + url_for_project_issues.should match(int_expected)
  37 + end
  38 +
  39 + it "should return path to external tracker" do
  40 + @project = ext_project
  41 +
  42 + url_for_project_issues.should match(ext_expected)
  43 + end
  44 +
  45 + it "should return empty string if project nil" do
  46 + @project = nil
  47 +
  48 + url_for_project_issues.should eq ""
  49 + end
  50 + end
  51 +
  52 + describe :url_for_issue do
  53 + let(:issue_id) { 3 }
  54 + let(:issues_url) { Gitlab.config.issues_tracker.redmine.issues_url}
  55 + let(:ext_expected) do
  56 + issues_url.gsub(':id', issue_id.to_s)
  57 + .gsub(':project_id', ext_project.id.to_s)
  58 + .gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
  59 + end
  60 + let(:int_expected) { polymorphic_path([project, issue]) }
  61 +
  62 + it "should return internal path if used internal tracker" do
  63 + @project = project
  64 + url_for_issue(issue.id).should match(int_expected)
  65 + end
  66 +
  67 + it "should return path to external tracker" do
  68 + @project = ext_project
  69 +
  70 + url_for_issue(issue_id).should match(ext_expected)
  71 + end
  72 +
  73 + it "should return empty string if project nil" do
  74 + @project = nil
  75 +
  76 + url_for_issue(issue.id).should eq ""
  77 + end
  78 + end
  79 +end
... ...
spec/models/project_spec.rb
... ... @@ -60,6 +60,7 @@ describe Project do
60 60 it { should ensure_inclusion_of(:wall_enabled).in_array([true, false]) }
61 61 it { should ensure_inclusion_of(:merge_requests_enabled).in_array([true, false]) }
62 62 it { should ensure_inclusion_of(:wiki_enabled).in_array([true, false]) }
  63 + it { should ensure_length_of(:issues_tracker_id).is_within(0..255) }
63 64  
64 65 it "should not allow new projects beyond user limits" do
65 66 project.stub(:creator).and_return(double(can_create_project?: false, projects_limit: 1))
... ... @@ -190,4 +191,57 @@ describe Project do
190 191 Project.new(path: "empty").repository.should be_nil
191 192 end
192 193 end
  194 +
  195 + describe :issue_exists? do
  196 + let(:project) { create(:project) }
  197 + let(:existed_issue) { create(:issue, project: project) }
  198 + let(:not_existed_issue) { create(:issue) }
  199 + let(:ext_project) { create(:redmine_project) }
  200 +
  201 + it "should be true or if used internal tracker and issue exists" do
  202 + project.issue_exists?(existed_issue.id).should be_true
  203 + end
  204 +
  205 + it "should be false or if used internal tracker and issue not exists" do
  206 + project.issue_exists?(not_existed_issue.id).should be_false
  207 + end
  208 +
  209 + it "should always be true if used other tracker" do
  210 + ext_project.issue_exists?(rand(100)).should be_true
  211 + end
  212 + end
  213 +
  214 + describe :used_default_issues_tracker? do
  215 + let(:project) { create(:project) }
  216 + let(:ext_project) { create(:redmine_project) }
  217 +
  218 + it "should be true if used internal tracker" do
  219 + project.used_default_issues_tracker?.should be_true
  220 + end
  221 +
  222 + it "should be false if used other tracker" do
  223 + ext_project.used_default_issues_tracker?.should be_false
  224 + end
  225 + end
  226 +
  227 + describe :can_have_issues_tracker_id? do
  228 + let(:project) { create(:project) }
  229 + let(:ext_project) { create(:redmine_project) }
  230 +
  231 + it "should be true for projects with external issues tracker if issues enabled" do
  232 + ext_project.can_have_issues_tracker_id?.should be_true
  233 + end
  234 +
  235 + it "should be false for projects with internal issue tracker if issues enabled" do
  236 + project.can_have_issues_tracker_id?.should be_false
  237 + end
  238 +
  239 + it "should be always false if issues disbled" do
  240 + project.issues_enabled = false
  241 + ext_project.issues_enabled = false
  242 +
  243 + project.can_have_issues_tracker_id?.should be_false
  244 + ext_project.can_have_issues_tracker_id?.should be_false
  245 + end
  246 + end
193 247 end
... ...
spec/models/user_spec.rb
... ... @@ -25,7 +25,7 @@
25 25 # dark_scheme :boolean default(FALSE), not null
26 26 # theme_id :integer default(1), not null
27 27 # bio :string(255)
28   -# blocked :boolean default(FALSE), not null
  28 +# state :string(255) default(FALSE), not null
29 29 # failed_attempts :integer default(0)
30 30 # locked_at :datetime
31 31 # extern_uid :string(255)
... ... @@ -140,7 +140,7 @@ describe User do
140 140  
141 141 it "should block user" do
142 142 user.block
143   - user.blocked.should be_true
  143 + user.blocked?.should be_true
144 144 end
145 145 end
146 146  
... ... @@ -149,7 +149,7 @@ describe User do
149 149 User.delete_all
150 150 @user = create :user
151 151 @admin = create :user, admin: true
152   - @blocked = create :user, blocked: true
  152 + @blocked = create :user, state: :blocked
153 153 end
154 154  
155 155 it { User.filter("admins").should == [@admin] }
... ...
spec/observers/user_observer_spec.rb
... ... @@ -15,7 +15,13 @@ describe UserObserver do
15 15 create(:user)
16 16 end
17 17  
  18 + it 'no email for external' do
  19 + Notify.should_not_receive(:new_user_email)
  20 + create(:user, extern_uid: '32442eEfsafada')
  21 + end
  22 +
18 23 it 'trigger logger' do
  24 + user = double(:user, id: 42, password: 'P@ssword!', name: 'John', email: 'u@mail.local', extern_uid?: false)
19 25 Gitlab::AppLogger.should_receive(:info)
20 26 create(:user)
21 27 end
... ...
vendor/assets/javascripts/branch-graph.js
... ... @@ -1,385 +0,0 @@
1   -!function(){
2   -
3   - var BranchGraph = function(element, options){
4   - this.element = element;
5   - this.options = options;
6   -
7   - this.preparedCommits = {};
8   - this.mtime = 0;
9   - this.mspace = 0;
10   - this.parents = {};
11   - this.colors = ["#000"];
12   -
13   - this.load();
14   - };
15   -
16   - BranchGraph.prototype.load = function(){
17   - $.ajax({
18   - url: this.options.url,
19   - method: 'get',
20   - dataType: 'json',
21   - success: $.proxy(function(data){
22   - $('.loading', this.element).hide();
23   - this.prepareData(data.days, data.commits);
24   - this.buildGraph();
25   - }, this)
26   - });
27   - };
28   -
29   - BranchGraph.prototype.prepareData = function(days, commits){
30   - this.days = days;
31   - this.dayCount = days.length;
32   - this.commits = commits;
33   - this.commitCount = commits.length;
34   -
35   - this.collectParents();
36   -
37   - this.mtime += 4;
38   - this.mspace += 10;
39   - for (var i = 0; i < this.commitCount; i++) {
40   - if (this.commits[i].id in this.parents) {
41   - this.commits[i].isParent = true;
42   - }
43   - this.preparedCommits[this.commits[i].id] = this.commits[i];
44   - }
45   - this.collectColors();
46   - };
47   -
48   - BranchGraph.prototype.collectParents = function(){
49   - for (var i = 0; i < this.commitCount; i++) {
50   - for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) {
51   - this.parents[this.commits[i].parents[j][0]] = true;
52   - }
53   - this.mtime = Math.max(this.mtime, this.commits[i].time);
54   - this.mspace = Math.max(this.mspace, this.commits[i].space);
55   - }
56   - };
57   -
58   - BranchGraph.prototype.collectColors = function(){
59   - for (var k = 0; k < this.mspace; k++) {
60   - this.colors.push(Raphael.getColor(.8));
61   - // Skipping a few colors in the spectrum to get more contrast between colors
62   - Raphael.getColor();Raphael.getColor();
63   - }
64   - };
65   -
66   - BranchGraph.prototype.buildGraph = function(){
67   - var graphWidth = $(this.element).width()
68   - , ch = this.mspace * 20 + 100
69   - , cw = Math.max(graphWidth, this.mtime * 20 + 260)
70   - , r = Raphael(this.element.get(0), cw, ch)
71   - , top = r.set()
72   - , cuday = 0
73   - , cumonth = ""
74   - , offsetX = 20
75   - , offsetY = 60
76   - , barWidth = Math.max(graphWidth, this.dayCount * 20 + 320)
77   - , scrollLeft = cw;
78   -
79   - this.raphael = r;
80   -
81   - r.rect(0, 0, barWidth, 20).attr({fill: "#222"});
82   - r.rect(0, 20, barWidth, 20).attr({fill: "#444"});
83   -
84   - for (mm = 0; mm < this.dayCount; mm++) {
85   - if(this.days[mm] != null){
86   - if(cuday != this.days[mm][0]){
87   - // Dates
88   - r.text(offsetX + mm * 20, 31, this.days[mm][0]).attr({
89   - font: "12px Monaco, monospace",
90   - fill: "#DDD"
91   - });
92   - cuday = this.days[mm][0];
93   - }
94   - if(cumonth != this.days[mm][1]){
95   - // Months
96   - r.text(offsetX + mm * 20, 11, this.days[mm][1]).attr({
97   - font: "12px Monaco, monospace",
98   - fill: "#EEE"
99   - });
100   - cumonth = this.days[mm][1];
101   - }
102   - }
103   - }
104   -
105   - for (i = 0; i < this.commitCount; i++) {
106   - var x = offsetX + 20 * this.commits[i].time
107   - , y = offsetY + 10 * this.commits[i].space
108   - , c
109   - , ps;
110   -
111   - // Draw dot
112   - r.circle(x, y, 3).attr({
113   - fill: this.colors[this.commits[i].space],
114   - stroke: "none"
115   - });
116   -
117   - // Draw lines
118   - for (var j = 0, jj = this.commits[i].parents.length; j < jj; j++) {
119   - c = this.preparedCommits[this.commits[i].parents[j][0]];
120   - ps = this.commits[i].parent_spaces[j];
121   - if (c) {
122   - var cx = offsetX + 20 * c.time
123   - , cy = offsetY + 10 * c.space
124   - , psy = offsetY + 10 * ps;
125   - if (c.space == this.commits[i].space && c.space == ps) {
126   - r.path([
127   - "M", x, y,
128   - "L", cx, cy
129   - ]).attr({
130   - stroke: this.colors[c.space],
131   - "stroke-width": 2
132   - });
133   -
134   - } else if (c.space < this.commits[i].space) {
135   - r.path([
136   - "M", x - 5, y,
137   - "l-5-2,0,4,5,-2",
138   - "L", x - 10, y,
139   - "L", x - 15, psy,
140   - "L", cx + 5, psy,
141   - "L", cx, cy])
142   - .attr({
143   - stroke: this.colors[this.commits[i].space],
144   - "stroke-width": 2
145   - });
146   - } else {
147   - r.path([
148   - "M", x - 3, y + 6,
149   - "l-4,3,4,2,0,-5",
150   - "L", x - 5, y + 10,
151   - "L", x - 10, psy,
152   - "L", cx + 5, psy,
153   - "L", cx, cy])
154   - .attr({
155   - stroke: this.colors[c.space],
156   - "stroke-width": 2
157   - });
158   - }
159   - }
160   - }
161   -
162   - if (this.commits[i].refs) {
163   - this.appendLabel(x, y, this.commits[i].refs);
164   - }
165   -
166   - // mark commit and displayed in the center
167   - if (this.commits[i].id == this.options.commit_id) {
168   - r.path([
169   - 'M', x, y - 5,
170   - 'L', x + 4, y - 15,
171   - 'L', x - 4, y - 15,
172   - 'Z'
173   - ]).attr({
174   - "fill": "#000",
175   - "fill-opacity": .7,
176   - "stroke": "none"
177   - });
178   - scrollLeft = x - graphWidth / 2;
179   - }
180   -
181   - this.appendAnchor(top, this.commits[i], x, y);
182   - }
183   - top.toFront();
184   - this.element.scrollLeft(scrollLeft);
185   - this.bindEvents();
186   - };
187   -
188   - BranchGraph.prototype.bindEvents = function(){
189   - var drag = {}
190   - , element = this.element;
191   -
192   - var dragger = function(event){
193   - element.scrollLeft(drag.sl - (event.clientX - drag.x));
194   - element.scrollTop(drag.st - (event.clientY - drag.y));
195   - };
196   -
197   - element.on({
198   - mousedown: function (event) {
199   - drag = {
200   - x: event.clientX,
201   - y: event.clientY,
202   - st: element.scrollTop(),
203   - sl: element.scrollLeft()
204   - };
205   - $(window).on('mousemove', dragger);
206   - }
207   - });
208   - $(window).on({
209   - mouseup: function(){
210   - //bars.animate({opacity: 0}, 300);
211   - $(window).off('mousemove', dragger);
212   - },
213   - keydown: function(event){
214   - if(event.keyCode == 37){
215   - // left
216   - element.scrollLeft( element.scrollLeft() - 50);
217   - }
218   - if(event.keyCode == 38){
219   - // top
220   - element.scrollTop( element.scrollTop() - 50);
221   - }
222   - if(event.keyCode == 39){
223   - // right
224   - element.scrollLeft( element.scrollLeft() + 50);
225   - }
226   - if(event.keyCode == 40){
227   - // bottom
228   - element.scrollTop( element.scrollTop() + 50);
229   - }
230   - }
231   - });
232   - };
233   -
234   - BranchGraph.prototype.appendLabel = function(x, y, refs){
235   - var r = this.raphael
236   - , shortrefs = refs
237   - , text, textbox, rect;
238   -
239   - if (shortrefs.length > 17){
240   - // Truncate if longer than 15 chars
241   - shortrefs = shortrefs.substr(0,15) + "…";
242   - }
243   -
244   - text = r.text(x+5, y+8 + 10, shortrefs).attr({
245   - font: "10px Monaco, monospace",
246   - fill: "#FFF",
247   - title: refs
248   - });
249   -
250   - textbox = text.getBBox();
251   - text.transform([
252   - 't', textbox.height/-4, textbox.width/2 + 5,
253   - 'r90'
254   - ]);
255   -
256   - // Create rectangle based on the size of the textbox
257   - rect = r.rect(x, y, textbox.width + 15, textbox.height + 5, 4).attr({
258   - "fill": "#000",
259   - "fill-opacity": .7,
260   - "stroke": "none"
261   - });
262   -
263   - triangle = r.path([
264   - 'M', x, y + 5,
265   - 'L', x + 4, y + 15,
266   - 'L', x - 4, y + 15,
267   - 'Z'
268   - ]).attr({
269   - "fill": "#000",
270   - "fill-opacity": .7,
271   - "stroke": "none"
272   - });
273   -
274   - // Rotate and reposition rectangle over text
275   - rect.transform([
276   - 'r', 90, x, y,
277   - 't', 15, -9
278   - ]);
279   -
280   - // Set text to front
281   - text.toFront();
282   - };
283   -
284   - BranchGraph.prototype.appendAnchor = function(top, commit, x, y) {
285   - var r = this.raphael
286   - , options = this.options
287   - , anchor;
288   - anchor = r.circle(x, y, 10).attr({
289   - fill: "#000",
290   - opacity: 0,
291   - cursor: "pointer"
292   - })
293   - .click(function(){
294   - window.open(options.commit_url.replace('%s', commit.id), '_blank');
295   - })
296   - .hover(function(){
297   - this.tooltip = r.commitTooltip(x, y + 5, commit);
298   - top.push(this.tooltip.insertBefore(this));
299   - }, function(){
300   - this.tooltip && this.tooltip.remove() && delete this.tooltip;
301   - });
302   - top.push(anchor);
303   - };
304   -
305   - this.BranchGraph = BranchGraph;
306   -
307   -}(this);
308   -Raphael.fn.commitTooltip = function(x, y, commit){
309   - var nameText, idText, messageText
310   - , boxWidth = 300
311   - , boxHeight = 200;
312   -
313   - nameText = this.text(x, y + 10, commit.author.name);
314   - idText = this.text(x, y + 35, commit.id);
315   - messageText = this.text(x, y + 50, commit.message);
316   -
317   - textSet = this.set(nameText, idText, messageText).attr({
318   - "text-anchor": "start",
319   - "font": "12px Monaco, monospace"
320   - });
321   -
322   - nameText.attr({
323   - "font": "14px Arial",
324   - "font-weight": "bold"
325   - });
326   -
327   - idText.attr({
328   - "fill": "#AAA"
329   - });
330   -
331   - textWrap(messageText, boxWidth - 50);
332   -
333   - var rect = this.rect(x - 10, y - 10, boxWidth, 100, 4).attr({
334   - "fill": "#FFF",
335   - "stroke": "#000",
336   - "stroke-linecap": "round",
337   - "stroke-width": 2
338   - });
339   - var tooltip = this.set(rect, textSet);
340   -
341   - rect.attr({
342   - "height" : tooltip.getBBox().height + 10,
343   - "width" : tooltip.getBBox().width + 10
344   - });
345   -
346   - tooltip.transform([
347   - 't', 20, 20
348   - ]);
349   -
350   - return tooltip;
351   -};
352   -
353   -function textWrap(t, width) {
354   - var content = t.attr("text");
355   - var abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
356   - t.attr({
357   - "text" : abc
358   - });
359   - var letterWidth = t.getBBox().width / abc.length;
360   -
361   - t.attr({
362   - "text" : content
363   - });
364   -
365   - var words = content.split(" ");
366   - var x = 0, s = [];
367   - for ( var i = 0; i < words.length; i++) {
368   -
369   - var l = words[i].length;
370   - if (x + (l * letterWidth) > width) {
371   - s.push("\n");
372   - x = 0;
373   - }
374   - x += l * letterWidth;
375   - s.push(words[i] + " ");
376   - }
377   - t.attr({
378   - "text" : s.join("")
379   - });
380   - var b = t.getBBox()
381   - , h = Math.abs(b.y2) - Math.abs(b.y) + 1;
382   - t.attr({
383   - "y": b.y + h
384   - });
385   -}