Compare commits

..

10 commits

Author SHA1 Message Date
Dmitriy Zaporozhets
8b761574bd fix gollum 2013-03-30 16:37:41 +02:00
Dmitriy Zaporozhets
34136d381f Use latest gitlab-grit fork to prevent gollum override gitlab-grit. Fixes issues related to gpg signed commits and other fixes from gitlab/grit fork 2013-03-30 13:14:19 +02:00
Dmitriy Zaporozhets
65156e3693 Merge pull request #3441 from Finkregh/patch-2
switch gitlab-shell to correct version
2013-03-30 04:06:29 -07:00
Oluf Lorenzen
6a09b21df6 switch gitlab-shell to correct version
a gitlab:check does say that you need v1.1.0 of gitlab-shell...
2013-03-28 18:53:26 +01:00
Saito
4606380316 Merge pull request #3367 from koenpunt/patch-1
Fixed typo
2013-03-25 10:03:41 -07:00
Koen Punt
017c19bbe4 Fixed type 2013-03-25 12:00:57 +01:00
Dmitriy Zaporozhets
0c7f426037 fix xss issue in blame 2013-03-22 18:53:01 +02:00
Dmitriy Zaporozhets
5c49cf6ca1 fix blame view head nav 2013-03-22 15:13:14 +02:00
Dmitriy Zaporozhets
0e4b2395a9 Fix lines and line numbers being squashed in File -> blame 2013-03-22 15:12:42 +02:00
Dmitriy Zaporozhets
ef968fd0c8 fix commit-description css on commit.show 2013-03-22 09:35:14 +02:00
208 changed files with 2229 additions and 2785 deletions

2
.rspec
View file

@ -1 +1 @@
--colour --drb --colour

View file

@ -8,7 +8,7 @@ branches:
only: only:
- 'master' - 'master'
rvm: rvm:
- 1.9.3-p392 - 1.9.3-p327
services: services:
- mysql - mysql
- postgresql - postgresql

View file

@ -1,10 +1,3 @@
v 5.1.0
- You can login with email or username now
- Corrected project transfer rollback when repository cannot be moved
- Move both repo and wiki when project transfer requrested
- Admin area: project editing was removed from admin namespace
- Access: admin user has now access to any project.
v 5.0.0 v 5.0.0
- Replaced gitolite with gitlab-shell - Replaced gitolite with gitlab-shell
- Removed gitolite-related libraries - Removed gitolite-related libraries
@ -31,7 +24,7 @@ v 5.0.0
- Fixed search field on projects page - Fixed search field on projects page
- Added teams to search autocomplete - Added teams to search autocomplete
- Move groups and teams on dashboard sidebar to sub-tabs - Move groups and teams on dashboard sidebar to sub-tabs
- API: improved return codes and docs. (Felix Gilcher, Sebastian Ziebell) - API: improved return codes and docs.
- Redesign wall to be more like chat - Redesign wall to be more like chat
- Snippets, Wall features are disabled by default for new projects - Snippets, Wall features are disabled by default for new projects

View file

@ -1,18 +1,35 @@
# Contribute to GitLab # Contribute to GitLab
This guide details how to use pull requests and the issues to improve GitLab. If you have a question or want to contribute to GitLab this guide show you the appropriate channel to use.
## Closing policy for pull requests and issues ## Ruling out common errors
Pull requests and issues not in line with the guidelines listed in this document will be closed with just a link to this paragraph. GitLab is a popular open source project and the capacity to deal with issues and pull requests is limited. To get support for your problems please use other channels as detailed in [the getting help section of the readme](https://github.com/gitlabhq/gitlabhq#getting-help). Professional [support subscriptions](http://www.gitlab.com/subscription/) and [consulting services](http://www.gitlab.com/consultancy/) are available from [GitLab.com](http://www.gitlab.com/). 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.
## Support forum
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.
### Use the support forum if ...
* You get permission denied errors
* You can't see your repos
* You have issues cloning, pulling or pushing
* You have issues with web_hooks not firing
**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.
## Paid support
Community support in the [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq) is done by volunteers. A support subscription is available from [GitLab.com](http://blog.gitlab.com/subscription/)
## Feature suggestions
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.
## Pull requests ## Pull requests
We welcome pull request with improvements to GitLab code and/or documentation. The issues we would really like a pull request for are listed with the [status 'accepting merge/pull requests' on our feedback forum](http://feedback.gitlab.com/forums/176466-general/status/796455) but other improvements are also welcome. 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:
### Pull request guidelines
If you can please submit a pull request with the fix including tests. The workflow to make a pull request is as follows:
1. Fork the project on GitHub 1. Fork the project on GitHub
1. Create a feature branch 1. Create a feature branch
@ -34,20 +51,26 @@ We will accept pull requests if:
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). 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).
## Issue tracker ## Submitting via GitHub's issue tracker
The [issue tracker](https://github.com/gitlabhq/gitlabhq/issues) is only for obvious bugs or misbehavior in the master branch of GitLab. When submitting an issue please conform to the issue submission guidelines listed below. * For obvious bugs or misbehavior in GitLab in the master branch. Please include the revision id and a reproducible test case.
* For problematic or insufficient documentation. Please give a suggestion on how to improve it.
Please send a pull request with a tested solution or a pull request with a failing test instead of opening an issue if you can. If you're unsure where to post, post to the [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq) first. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there. If you're unsure where to post, post it to the [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq) first.
There are a lot of helpful GitLab users there who may be able to help you quickly.
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).
### Issue tracker guidelines ### When submitting an issue
**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. **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.
* Summarize your issue in one sentence (what goes wrong, what did you expect to happen) Please consider the following points when submitting an **issue**:
* Describe your issue in detail
* How can we reproduce the issue on the [GitLab Vagrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm) (start with: vagrant destroy && vagrant up && vagrant ssh) * Summarize your issue in one sentence (what happened wrong, when you did/expected something else)
* Add the last commit sha1 of the GitLab version you used to replicate the issue * Describe your issue in detail (including steps to reproduce)
* Add logs or screen shots when possible * Add logs or screen shots when possible
* Link to the line of code that might be responsible for the problem
* Describe your setup (use relevant parts from `sudo -u gitlab -H bundle exec rake gitlab:env:info`) * Describe your setup (use relevant parts from `sudo -u gitlab -H bundle exec rake gitlab:env:info`)
## Thank you!
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.

14
Gemfile
View file

@ -23,8 +23,8 @@ gem 'omniauth-github'
# Extracting information from a git repository # Extracting information from a git repository
# Since gollum requires grit we cannot use gitlab-grit gem name any more. Use grit instead # Since gollum requires grit we cannot use gitlab-grit gem name any more. Use grit instead
gem "grit", '~> 2.5.0', git: 'https://github.com/gitlabhq/grit.git', ref: '42297cdcee16284d2e4eff23d41377f52fc28b9d' gem "grit", '~> 2.5.0', git: 'https://github.com/gitlabhq/grit.git', ref: 'c40a32432616a07fa7fc3c32c24ab73ad6a9718f'
gem 'grit_ext', '~> 0.8.1' gem 'grit_ext', '~> 0.6.2'
# Ruby/Rack Git Smart-HTTP Server Handler # Ruby/Rack Git Smart-HTTP Server Handler
gem 'gitlab-grack', '~> 1.0.0', require: 'grack' gem 'gitlab-grack', '~> 1.0.0', require: 'grack'
@ -105,7 +105,7 @@ gem 'settingslogic'
# github-linquist needs pygments 0.4.2 but Gollum 2.4.11 # github-linquist needs pygments 0.4.2 but Gollum 2.4.11
# requires pygments 0.3.2. The latest master Gollum has been updated # requires pygments 0.3.2. The latest master Gollum has been updated
# to use pygments 0.4.2. Change this after next Gollum release. # to use pygments 0.4.2. Change this after next Gollum release.
gem "gollum", "~> 2.4.0", git: "https://github.com/gollum/gollum.git", ref: "5dcd3c8c8f" gem "gollum", "~> 2.4.0", git: "git://github.com/gollum/gollum.git", ref: "5dcd3c8c8f"
# Misc # Misc
gem "foreman" gem "foreman"
@ -154,9 +154,9 @@ end
group :development, :test do group :development, :test do
gem 'coveralls', require: false gem 'coveralls', require: false
gem 'rails-dev-tweaks' gem 'rails-dev-tweaks'
gem 'spinach-rails' gem 'spinach-rails', '0.2.0'
gem "rspec-rails" gem "rspec-rails", '2.12.2'
gem "capybara" gem "capybara", '2.0.2'
gem "pry" gem "pry"
gem "awesome_print" gem "awesome_print"
gem "database_cleaner" gem "database_cleaner"
@ -174,8 +174,6 @@ group :development, :test do
# PhantomJS driver for Capybara # PhantomJS driver for Capybara
gem 'poltergeist', '1.1.0' gem 'poltergeist', '1.1.0'
gem 'spork', '~> 1.0rc'
end end
group :test do group :test do

View file

@ -1,29 +1,5 @@
GIT GIT
remote: https://github.com/ctran/annotate_models.git remote: git://github.com/gollum/gollum.git
revision: be4e26825b521f0b2d86b181e2dff89901aa9b1e
specs:
annotate (2.6.0.beta1)
activerecord (>= 2.3.0)
rake (>= 0.8.7)
GIT
remote: https://github.com/gitlabhq/grit.git
revision: 42297cdcee16284d2e4eff23d41377f52fc28b9d
ref: 42297cdcee16284d2e4eff23d41377f52fc28b9d
specs:
grit (2.5.0)
diff-lcs (~> 1.1)
mime-types (~> 1.15)
posix-spawn (~> 0.3.6)
GIT
remote: https://github.com/gitlabhq/raphael-rails.git
revision: cb2c92a040b9b941a5f1aa1ea866cc26e944fe58
specs:
raphael-rails (2.1.0)
GIT
remote: https://github.com/gollum/gollum.git
revision: 5dcd3c8c8f68158e43ff79861279088ee56d0ebe revision: 5dcd3c8c8f68158e43ff79861279088ee56d0ebe
ref: 5dcd3c8c8f ref: 5dcd3c8c8f
specs: specs:
@ -39,6 +15,30 @@ GIT
stringex (~> 1.5.1) stringex (~> 1.5.1)
useragent (~> 0.4.16) useragent (~> 0.4.16)
GIT
remote: https://github.com/ctran/annotate_models.git
revision: be4e26825b521f0b2d86b181e2dff89901aa9b1e
specs:
annotate (2.6.0.beta1)
activerecord (>= 2.3.0)
rake (>= 0.8.7)
GIT
remote: https://github.com/gitlabhq/grit.git
revision: c40a32432616a07fa7fc3c32c24ab73ad6a9718f
ref: c40a32432616a07fa7fc3c32c24ab73ad6a9718f
specs:
grit (2.5.0)
diff-lcs (~> 1.1)
mime-types (~> 1.15)
posix-spawn (~> 0.3.6)
GIT
remote: https://github.com/gitlabhq/raphael-rails.git
revision: cb2c92a040b9b941a5f1aa1ea866cc26e944fe58
specs:
raphael-rails (2.1.0)
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
@ -105,7 +105,7 @@ GEM
thor (~> 0.14) thor (~> 0.14)
code_analyzer (0.3.1) code_analyzer (0.3.1)
sexp_processor sexp_processor
coderay (1.0.9) coderay (1.0.8)
coffee-rails (3.2.2) coffee-rails (3.2.2)
coffee-script (>= 2.2.0) coffee-script (>= 2.2.0)
railties (~> 3.2.0) railties (~> 3.2.0)
@ -122,7 +122,7 @@ GEM
rest-client rest-client
simplecov (>= 0.7) simplecov (>= 0.7)
thor thor
crack (0.3.2) crack (0.3.1)
daemons (1.1.9) daemons (1.1.9)
database_cleaner (0.9.1) database_cleaner (0.9.1)
debug_inspector (0.0.2) debug_inspector (0.0.2)
@ -132,7 +132,7 @@ GEM
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
railties (~> 3.1) railties (~> 3.1)
warden (~> 1.2.1) warden (~> 1.2.1)
diff-lcs (1.2.1) diff-lcs (1.1.3)
draper (1.1.0) draper (1.1.0)
actionpack (>= 3.0) actionpack (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
@ -147,7 +147,7 @@ GEM
eventmachine (1.0.0) eventmachine (1.0.0)
execjs (1.4.0) execjs (1.4.0)
multi_json (~> 1.0) multi_json (~> 1.0)
facter (1.6.18) facter (1.6.17)
factory_girl (4.1.0) factory_girl (4.1.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
factory_girl_rails (4.1.0) factory_girl_rails (4.1.0)
@ -200,16 +200,15 @@ GEM
grape-entity (0.2.0) grape-entity (0.2.0)
activesupport activesupport
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
grit_ext (0.8.1) grit_ext (0.6.2)
charlock_holmes (~> 0.6.9) charlock_holmes (~> 0.6.9)
growl (1.0.3) growl (1.0.3)
guard (1.6.2) guard (1.5.4)
listen (>= 0.6.0) listen (>= 0.4.2)
lumberjack (>= 1.0.2) lumberjack (>= 1.0.2)
pry (>= 0.9.10) pry (>= 0.9.10)
terminal-table (>= 1.4.3)
thor (>= 0.14.6) thor (>= 0.14.6)
guard-rspec (2.5.1) guard-rspec (2.1.2)
guard (>= 1.1) guard (>= 1.1)
rspec (~> 2.11) rspec (~> 2.11)
guard-spinach (0.0.2) guard-spinach (0.0.2)
@ -249,9 +248,9 @@ GEM
addressable (~> 2.3) addressable (~> 2.3)
letter_opener (1.0.0) letter_opener (1.0.0)
launchy (>= 2.0.4) launchy (>= 2.0.4)
libv8 (3.11.8.17) libv8 (3.3.10.4)
listen (0.7.3) listen (0.5.3)
lumberjack (1.0.3) lumberjack (1.0.2)
mail (2.5.3) mail (2.5.3)
i18n (>= 0.4.0) i18n (>= 0.4.0)
mime-types (~> 1.16) mime-types (~> 1.16)
@ -260,7 +259,7 @@ GEM
mime-types (1.21) mime-types (1.21)
modernizr (2.6.2) modernizr (2.6.2)
sprockets (~> 2.0) sprockets (~> 2.0)
multi_json (1.7.2) multi_json (1.7.1)
multi_xml (0.5.3) multi_xml (0.5.3)
multipart-post (1.1.5) multipart-post (1.1.5)
mustache (0.99.4) mustache (0.99.4)
@ -300,10 +299,11 @@ GEM
http_parser.rb (~> 0.5.3) http_parser.rb (~> 0.5.3)
polyglot (0.3.3) polyglot (0.3.3)
posix-spawn (0.3.6) posix-spawn (0.3.6)
pry (0.9.12) progressbar (0.12.0)
pry (0.9.10)
coderay (~> 1.0.5) coderay (~> 1.0.5)
method_source (~> 0.8) method_source (~> 0.8)
slop (~> 3.4) slop (~> 3.3.1)
pygments.rb (0.4.2) pygments.rb (0.4.2)
posix-spawn (~> 0.3.6) posix-spawn (~> 0.3.6)
yajl-ruby (~> 1.1.0) yajl-ruby (~> 1.1.0)
@ -336,14 +336,14 @@ GEM
rails-dev-tweaks (0.6.1) rails-dev-tweaks (0.6.1)
actionpack (~> 3.1) actionpack (~> 3.1)
railties (~> 3.1) railties (~> 3.1)
rails_best_practices (1.13.4) rails_best_practices (1.13.2)
activesupport activesupport
awesome_print awesome_print
code_analyzer code_analyzer
colored colored
erubis erubis
i18n i18n
ruby-progressbar progressbar
railties (3.2.13) railties (3.2.13)
actionpack (= 3.2.13) actionpack (= 3.2.13)
activesupport (= 3.2.13) activesupport (= 3.2.13)
@ -352,14 +352,14 @@ GEM
rdoc (~> 3.4) rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0) thor (>= 0.14.6, < 2.0)
raindrops (0.10.0) raindrops (0.10.0)
rake (10.0.4) rake (10.0.3)
rb-fsevent (0.9.2) rb-fsevent (0.9.2)
rb-inotify (0.8.8) rb-inotify (0.8.8)
ffi (>= 0.5.0) ffi (>= 0.5.0)
rdoc (3.12.2) rdoc (3.12.2)
json (~> 1.4) json (~> 1.4)
redcarpet (2.2.2) redcarpet (2.2.2)
redis (3.0.3) redis (3.0.2)
redis-actionpack (3.2.3) redis-actionpack (3.2.3)
actionpack (~> 3.2.3) actionpack (~> 3.2.3)
redis-rack (~> 1.4.0) redis-rack (~> 1.4.0)
@ -378,32 +378,30 @@ GEM
redis-store (~> 1.1.0) redis-store (~> 1.1.0)
redis-store (1.1.3) redis-store (1.1.3)
redis (>= 2.2.0) redis (>= 2.2.0)
ref (1.0.4)
request_store (1.0.5) request_store (1.0.5)
rest-client (1.6.7) rest-client (1.6.7)
mime-types (>= 1.16) mime-types (>= 1.16)
rspec (2.13.0) rspec (2.12.0)
rspec-core (~> 2.13.0) rspec-core (~> 2.12.0)
rspec-expectations (~> 2.13.0) rspec-expectations (~> 2.12.0)
rspec-mocks (~> 2.13.0) rspec-mocks (~> 2.12.0)
rspec-core (2.13.1) rspec-core (2.12.0)
rspec-expectations (2.13.0) rspec-expectations (2.12.0)
diff-lcs (>= 1.1.3, < 2.0) diff-lcs (~> 1.1.3)
rspec-mocks (2.13.0) rspec-mocks (2.12.0)
rspec-rails (2.13.0) rspec-rails (2.12.2)
actionpack (>= 3.0) actionpack (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
railties (>= 3.0) railties (>= 3.0)
rspec-core (~> 2.13.0) rspec-core (~> 2.12.0)
rspec-expectations (~> 2.13.0) rspec-expectations (~> 2.12.0)
rspec-mocks (~> 2.13.0) rspec-mocks (~> 2.12.0)
ruby-progressbar (1.0.2)
rubyntlm (0.1.1) rubyntlm (0.1.1)
rubyzip (0.9.9) rubyzip (0.9.9)
sanitize (2.0.3) sanitize (2.0.3)
nokogiri (>= 1.4.4, < 1.6) nokogiri (>= 1.4.4, < 1.6)
sass (3.2.7) sass (3.2.5)
sass-rails (3.2.6) sass-rails (3.2.5)
railties (~> 3.2.0) railties (~> 3.2.0)
sass (>= 3.1.10) sass (>= 3.1.10)
tilt (~> 1.3) tilt (~> 1.3)
@ -422,10 +420,10 @@ GEM
rubyzip rubyzip
websocket (~> 1.0.4) websocket (~> 1.0.4)
settingslogic (2.0.9) settingslogic (2.0.9)
sexp_processor (4.2.0) sexp_processor (4.1.3)
shoulda-matchers (1.3.0) shoulda-matchers (1.3.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
sidekiq (2.8.0) sidekiq (2.7.5)
celluloid (~> 0.12.0) celluloid (~> 0.12.0)
connection_pool (~> 1.0) connection_pool (~> 1.0)
multi_json (~> 1) multi_json (~> 1)
@ -443,7 +441,7 @@ GEM
slim (1.3.6) slim (1.3.6)
temple (~> 0.5.5) temple (~> 0.5.5)
tilt (~> 1.3.3) tilt (~> 1.3.3)
slop (3.4.4) slop (3.3.3)
spinach (0.7.0) spinach (0.7.0)
colorize colorize
gherkin-ruby (~> 0.2.0) gherkin-ruby (~> 0.2.0)
@ -451,7 +449,6 @@ GEM
capybara (~> 2.0.0) capybara (~> 2.0.0)
railties (>= 3) railties (>= 3)
spinach (>= 0.4) spinach (>= 0.4)
spork (1.0.0rc3)
sprockets (2.2.2) sprockets (2.2.2)
hike (~> 1.2) hike (~> 1.2)
multi_json (~> 1.0) multi_json (~> 1.0)
@ -461,16 +458,14 @@ GEM
state_machine (1.1.2) state_machine (1.1.2)
stringex (1.5.1) stringex (1.5.1)
temple (0.5.5) temple (0.5.5)
terminal-table (1.4.5)
test_after_commit (0.0.1) test_after_commit (0.0.1)
therubyracer (0.11.4) therubyracer (0.10.2)
libv8 (~> 3.11.8.12) libv8 (~> 3.3.10)
ref
thin (1.5.0) thin (1.5.0)
daemons (>= 1.0.9) daemons (>= 1.0.9)
eventmachine (>= 0.12.6) eventmachine (>= 0.12.6)
rack (>= 1.0.0) rack (>= 1.0.0)
thor (0.18.0) thor (0.17.0)
tilt (1.3.6) tilt (1.3.6)
timers (1.1.0) timers (1.1.0)
treetop (1.4.12) treetop (1.4.12)
@ -508,7 +503,7 @@ DEPENDENCIES
better_errors better_errors
binding_of_caller binding_of_caller
bootstrap-sass (= 2.2.1.1) bootstrap-sass (= 2.2.1.1)
capybara capybara (= 2.0.2)
carrierwave carrierwave
chosen-rails (= 0.9.8) chosen-rails (= 0.9.8)
coffee-rails (~> 3.2.2) coffee-rails (~> 3.2.2)
@ -537,7 +532,7 @@ DEPENDENCIES
grape (~> 0.3.1) grape (~> 0.3.1)
grape-entity (~> 0.2.0) grape-entity (~> 0.2.0)
grit (~> 2.5.0)! grit (~> 2.5.0)!
grit_ext (~> 0.8.1) grit_ext (~> 0.6.2)
growl growl
guard-rspec guard-rspec
guard-spinach guard-spinach
@ -568,7 +563,7 @@ DEPENDENCIES
rb-inotify rb-inotify
redcarpet (~> 2.2.2) redcarpet (~> 2.2.2)
redis-rails redis-rails
rspec-rails rspec-rails (= 2.12.2)
sass-rails (~> 3.2.5) sass-rails (~> 3.2.5)
sdoc sdoc
seed-fu seed-fu
@ -580,8 +575,7 @@ DEPENDENCIES
sinatra sinatra
six six
slim slim
spinach-rails spinach-rails (= 0.2.0)
spork (~> 1.0rc)
stamp stamp
state_machine state_machine
test_after_commit test_after_commit

View file

@ -12,7 +12,7 @@
* powered by Ruby on Rails * powered by Ruby on Rails
* completely free and open source (MIT license) * completely free and open source (MIT license)
* used by more than 10.000 organizations to keep their code secure * used by 10.000 organizations to keep their code secure
### Code status ### Code status
@ -30,7 +30,7 @@
* 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) * 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)
* GitLab.com commercial services: [Homepage](http://www.gitlab.com/) [Subscription](http://www.gitlab.com/subscription/) [Consultancy](http://www.gitlab.com/consultancy/) [GitLab Cloud](http://www.gitlab.com/cloud/) [Blog](http://blog.gitlab.com/) * GitLab.com commercial services: [Homepage](http://blog.gitlab.com/) [GitLab Cloud](http://blog.gitlab.com/cloud/) [Subscription](http://blog.gitlab.com/subscription/) [Consultancy](http://blog.gitlab.com/consultancy/) [Blog](http://blog.gitlab.com/blog/)
* GitLab CI: [Readme](https://github.com/gitlabhq/gitlab-ci/blob/master/README.md) of the GitLab open-source continuous integration server * GitLab CI: [Readme](https://github.com/gitlabhq/gitlab-ci/blob/master/README.md) of the GitLab open-source continuous integration server
@ -47,45 +47,22 @@
### Installation ### Installation
#### Official production installation #### For production
Follow the installation guide for production server. Follow the installation guide for production server.
* [Installation guide for latest stable release (5.0)](https://github.com/gitlabhq/gitlabhq/blob/5-0-stable/doc/install/installation.md) - **Recommended** * [Installation guide for latest stable release (4.2)](https://github.com/gitlabhq/gitlabhq/blob/4-2-stable/doc/install/installation.md) - **Recommended**
* [Installation guide for the current master branch (5.1)](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md) * [Installation guide for the current master branch (5.0)](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md)
#### Official development installation #### For development
If you want to contribute, please first read our [Contributing Guidelines](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) and then we suggest you to use the Vagrant virtual machine project to get an environment working with all dependencies. If you want to contribute, please first read our [Contributing Guidelines](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) and then we suggest you to use the Vagrant virtual machine project to get an environment working sandboxed and with all dependencies.
* [Vagrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm) * [Vagrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm)
### Starting
#### Unsupported production installation
* [GitLab recipes](https://github.com/gitlabhq/gitlab-recipes) for setup on different platforms
* [Unofficial installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides)
* [BitNami one-click installers](http://bitnami.com/stack/gitlab)
* [TurnKey Linux virtual appliance](http://www.turnkeylinux.org/gitlab)
### New versions and upgrading
Each month on the 22th a new version is released together with an upgrade guide.
* [Upgrade guides](https://github.com/gitlabhq/gitlabhq/wiki)
* [Changelog](https://github.com/gitlabhq/gitlabhq/blob/master/CHANGELOG)
* [Roadmap](https://github.com/gitlabhq/gitlabhq/blob/master/ROADMAP.md)
### Getting started
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 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
@ -123,35 +100,38 @@ Each month on the 22th a new version is released together with an upgrade guide.
bundle exec rake spinach bundle exec rake spinach
### Getting help
### GitLab interfaces * [Troubleshooting guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide)
* [Support forum](https://groups.google.com/forum/#!forum/gitlabhq)
* [Feedback and suggestions forum](http://gitlab.uservoice.com/forums/176466-general)
* [Support subscription](http://blog.gitlab.com/subscription/)
* [Consultancy](http://blog.gitlab.com/consultancy/)
### New versions and the API
Each month on the 22th a new version is released together with an upgrade guide.
* [Upgrade guides](https://github.com/gitlabhq/gitlabhq/wiki)
* [Roadmap](https://github.com/gitlabhq/gitlabhq/blob/master/ROADMAP.md)
### Other documentation
* [GitLab API](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/README.md) * [GitLab API](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/README.md)
* [Rake tasks](https://github.com/gitlabhq/gitlabhq/tree/master/doc/raketasks) * [Rake tasks](https://github.com/gitlabhq/gitlabhq/tree/master/doc/raketasks)
* [Directory structure](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/structure.md) * [GitLab recipes](https://github.com/gitlabhq/gitlab-recipes)
* [Databases](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/databases.md)
### Getting help
* [Troubleshooting guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide) contains solutions to common problems.
* [Support forum](https://groups.google.com/forum/#!forum/gitlabhq) is the best place to ask questions. For example you can use it if you have questions about: permission denied errors, invisible repos, can't clone/pull/push or with web hooks that don't fire. Please 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. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there to a fix.
* [Feedback and suggestions forum](http://gitlab.uservoice.com/forums/176466-general) is the place to propose and discuss new features for GitLab.
* [Support subscription](http://www.gitlab.com/subscription/) connect you to the knowledge of GitLab experts that will resolve your issues and answer your questions.
* [Consultancy](http://www.gitlab.com/consultancy/) allows you hire GitLab exports for installations, upgrades and customizations.
* [Contributing guide](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) describes how to submit pull requests and issues. Pull requests and issues not in line with the guidelines in this document will be closed without comment.
### Getting in touch ### Getting in touch
* [Contributing guide](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md)
* [Core team](https://github.com/gitlabhq?tab=members) * [Core team](https://github.com/gitlabhq?tab=members)
* [Contributors](https://github.com/gitlabhq/gitlabhq/graphs/contributors) * [Contributors](https://github.com/gitlabhq/gitlabhq/graphs/contributors)

View file

@ -1,5 +1,7 @@
## GitLab Roadmap ## GitLab Roadmap
### v5.1 April 22 ### v5.0 March 22
* Not decided yet. * Replace gitolite with gitlab-shell
* Usability improvements
* Notification improvements

View file

@ -1 +1 @@
5.1.0pre 5.0.0

View file

@ -3,11 +3,3 @@ $ ->
container = $(@).closest(".js-toggler-container") container = $(@).closest(".js-toggler-container")
container.toggleClass("on") container.toggleClass("on")
$("body").on "click", ".js-toggle-visibility-link", (e) ->
$(@).find('i').
toggleClass('icon-chevron-down').
toggleClass('icon-chevron-up')
container = $(".js-toggle-visibility-container")
container.toggleClass("hide")
e.preventDefault()

View file

@ -5,10 +5,6 @@ class BranchGraph
@mspace = 0 @mspace = 0
@parents = {} @parents = {}
@colors = ["#000"] @colors = ["#000"]
@offsetX = 120
@offsetY = 20
@unitTime = 30
@unitSpace = 10
@load() @load()
load: -> load: ->
@ -24,6 +20,8 @@ class BranchGraph
prepareData: (@days, @commits) -> prepareData: (@days, @commits) ->
@collectParents() @collectParents()
@mtime += 4
@mspace += 10
for c in @commits for c in @commits
c.isParent = true if c.id of @parents c.isParent = true if c.id of @parents
@ -37,7 +35,6 @@ class BranchGraph
@mspace = Math.max(@mspace, c.space) @mspace = Math.max(@mspace, c.space)
for p in c.parents for p in c.parents
@parents[p[0]] = true @parents[p[0]] = true
@mspace = Math.max(@mspace, p[1])
collectColors: -> collectColors: ->
k = 0 k = 0
@ -49,23 +46,25 @@ class BranchGraph
k++ k++
buildGraph: -> buildGraph: ->
graphHeight = $(@element).height()
graphWidth = $(@element).width() graphWidth = $(@element).width()
ch = Math.max(graphHeight, @offsetY + @unitTime * @mtime + 150) ch = @mspace * 20 + 100
cw = Math.max(graphWidth, @offsetX + @unitSpace * @mspace + 300) cw = Math.max(graphWidth, @mtime * 20 + 260)
@r = r = Raphael(@element.get(0), cw, ch) r = Raphael(@element.get(0), cw, ch)
top = r.set() top = r.set()
cuday = 0 cuday = 0
cumonth = "" cumonth = ""
barHeight = Math.max(graphHeight, @unitTime * @days.length + 320) offsetX = 20
offsetY = 60
r.rect(0, 0, 26, barHeight).attr fill: "#222" barWidth = Math.max(graphWidth, @days.length * 20 + 320)
r.rect(26, 0, 20, barHeight).attr fill: "#444" scrollLeft = cw
@raphael = r
r.rect(0, 0, barWidth, 20).attr fill: "#222"
r.rect(0, 20, barWidth, 20).attr fill: "#444"
for day, mm in @days for day, mm in @days
if cuday isnt day[0] if cuday isnt day[0]
# Dates # Dates
r.text(36, @offsetY + @unitTime * mm, day[0]) r.text(offsetX + mm * 20, 31, day[0])
.attr( .attr(
font: "12px Monaco, monospace" font: "12px Monaco, monospace"
fill: "#DDD" fill: "#DDD"
@ -74,7 +73,7 @@ class BranchGraph
if cumonth isnt day[1] if cumonth isnt day[1]
# Months # Months
r.text(13, @offsetY + @unitTime * mm, day[1]) r.text(offsetX + mm * 20, 11, day[1])
.attr( .attr(
font: "12px Monaco, monospace" font: "12px Monaco, monospace"
fill: "#EEE" fill: "#EEE"
@ -82,20 +81,61 @@ class BranchGraph
cumonth = day[1] cumonth = day[1]
for commit in @commits for commit in @commits
x = @offsetX + @unitSpace * (@mspace - commit.space) x = offsetX + 20 * commit.time
y = @offsetY + @unitTime * commit.time y = offsetY + 10 * commit.space
# Draw dot
r.circle(x, y, 3).attr(
fill: @colors[commit.space]
stroke: "none"
)
@drawDot(x, y, commit) # Draw lines
for parent in commit.parents
parentCommit = @preparedCommits[parent[0]]
parentX = offsetX + 20 * parentCommit.time
parentY1 = offsetY + 10 * parentCommit.space
parentY2 = offsetY + 10 * parent[1]
if parentCommit.space is commit.space and parentCommit.space is parent[1]
r.path(["M", x, y, "L", parentX, parentY1]).attr(
stroke: @colors[parentCommit.space]
"stroke-width": 2
)
@drawLines(x, y, commit) else if parentCommit.space < commit.space
if y is parentY2
r.path(["M", x - 5, y, "l-5,-2,0,4,5,-2", "L", x - 10, y, "L", x - 15, parentY2, "L", parentX + 5, parentY2, "L", parentX, parentY1]).attr(
stroke: @colors[commit.space]
"stroke-width": 2
)
@appendLabel(x, y, commit.refs) if commit.refs else
r.path(["M", x - 3, y - 6, "l-4,-3,4,-2,0,5", "L", x - 5, y - 10, "L", x - 10, parentY2, "L", parentX + 5, parentY2, "L", parentX, parentY1]).attr(
stroke: @colors[commit.space]
"stroke-width": 2
)
@appendAnchor(top, commit, x, y) else
r.path(["M", x - 3, y + 6, "l-4,3,4,2,0,-5", "L", x - 5, y + 10, "L", x - 10, parentY2, "L", parentX + 5, parentY2, "L", parentX, parentY1]).attr(
stroke: @colors[parentCommit.space]
"stroke-width": 2
)
@markCommit(x, y, commit, graphHeight) @appendLabel x, y, commit.refs if commit.refs
# Mark commit and displayed in the center
if commit.id is @options.commit_id
r.path(["M", x, y - 5, "L", x + 4, y - 15, "L", x - 4, y - 15, "Z"]).attr(
fill: "#000"
"fill-opacity": .7
stroke: "none"
)
scrollLeft = x - graphWidth / 2
@appendAnchor top, commit, x, y
top.toFront() top.toFront()
@element.scrollLeft scrollLeft
@bindEvents() @bindEvents()
bindEvents: -> bindEvents: ->
@ -127,37 +167,35 @@ class BranchGraph
element.scrollTop element.scrollTop() + 50 if event.keyCode is 40 element.scrollTop element.scrollTop() + 50 if event.keyCode is 40
appendLabel: (x, y, refs) -> appendLabel: (x, y, refs) ->
r = @r r = @raphael
shortrefs = refs shortrefs = refs
# Truncate if longer than 15 chars # Truncate if longer than 15 chars
shortrefs = shortrefs.substr(0, 15) + "" if shortrefs.length > 17 shortrefs = shortrefs.substr(0, 15) + "" if shortrefs.length > 17
text = r.text(x + 4, y, shortrefs).attr( text = r.text(x + 5, y + 8 + 10, shortrefs).attr(
"text-anchor": "start"
font: "10px Monaco, monospace" font: "10px Monaco, monospace"
fill: "#FFF" fill: "#FFF"
title: refs title: refs
) )
textbox = text.getBBox() textbox = text.getBBox()
text.transform ["t", textbox.height / -4, textbox.width / 2 + 5, "r90"]
# Create rectangle based on the size of the textbox # Create rectangle based on the size of the textbox
rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr( rect = r.rect(x, y, textbox.width + 15, textbox.height + 5, 4).attr(
fill: "#000" fill: "#000"
"fill-opacity": .5 "fill-opacity": .7
stroke: "none" stroke: "none"
) )
triangle = r.path(["M", x - 5, y, "L", x - 15, y - 4, "L", x - 15, y + 4, "Z"]).attr( triangle = r.path(["M", x, y + 5, "L", x + 4, y + 15, "L", x - 4, y + 15, "Z"]).attr(
fill: "#000" fill: "#000"
"fill-opacity": .5 "fill-opacity": .7
stroke: "none" stroke: "none"
) )
# Rotate and reposition rectangle over text
label = r.set(rect, text) rect.transform ["r", 90, x, y, "t", 15, -9]
label.transform(["t", -rect.getBBox().width - 15, 0])
# Set text to front # Set text to front
text.toFront() text.toFront()
appendAnchor: (top, commit, x, y) -> appendAnchor: (top, commit, x, y) ->
r = @r r = @raphael
options = @options options = @options
anchor = r.circle(x, y, 10).attr( anchor = r.circle(x, y, 10).attr(
fill: "#000" fill: "#000"
@ -166,92 +204,18 @@ class BranchGraph
).click(-> ).click(->
window.open options.commit_url.replace("%s", commit.id), "_blank" window.open options.commit_url.replace("%s", commit.id), "_blank"
).hover(-> ).hover(->
@tooltip = r.commitTooltip(x + 5, y, commit) @tooltip = r.commitTooltip(x, y + 5, commit)
top.push @tooltip.insertBefore(this) top.push @tooltip.insertBefore(this)
, -> , ->
@tooltip and @tooltip.remove() and delete @tooltip @tooltip and @tooltip.remove() and delete @tooltip
) )
top.push anchor top.push anchor
drawDot: (x, y, commit) ->
r = @r
r.circle(x, y, 3).attr(
fill: @colors[commit.space]
stroke: "none"
)
r.rect(@offsetX + @unitSpace * @mspace + 10, y - 10, 20, 20).attr(
fill: "url(#{commit.author.icon})"
stroke: @colors[commit.space]
"stroke-width": 2
)
r.text(@offsetX + @unitSpace * @mspace + 35, y, commit.message.split("\n")[0]).attr(
"text-anchor": "start"
font: "14px Monaco, monospace"
)
drawLines: (x, y, commit) ->
r = @r
for parent, i in commit.parents
parentCommit = @preparedCommits[parent[0]]
parentY = @offsetY + @unitTime * parentCommit.time
parentX1 = @offsetX + @unitSpace * (@mspace - parentCommit.space)
parentX2 = @offsetX + @unitSpace * (@mspace - parent[1])
# Set line color
if parentCommit.space <= commit.space
color = @colors[commit.space]
else
color = @colors[parentCommit.space]
# Build line shape
if parent[1] is commit.space
offset = [0, 5]
arrow = "l-2,5,4,0,-2,-5,0,5"
else if parent[1] < commit.space
offset = [3, 3]
arrow = "l5,0,-2,4,-3,-4,4,2"
else
offset = [-3, 3]
arrow = "l-5,0,2,4,3,-4,-4,2"
# Start point
route = ["M", x + offset[0], y + offset[1]]
# Add arrow if not first parent
if i > 0
route.push(arrow)
# Circumvent if overlap
if commit.space isnt parentCommit.space or commit.space isnt parent[1]
route.push(
"L", parentX2, y + 10,
"L", parentX2, parentY - 5,
)
# End point
route.push("L", parentX1, parentY)
r
.path(route)
.attr(
stroke: color
"stroke-width": 2)
markCommit: (x, y, commit, graphHeight) ->
if commit.id is @options.commit_id
r = @r
r.path(["M", x + 5, y, "L", x + 15, y + 4, "L", x + 15, y - 4, "Z"]).attr(
fill: "#000"
"fill-opacity": .5
stroke: "none"
)
# Displayed in the center
@element.scrollTop(y - graphHeight / 2)
Raphael::commitTooltip = (x, y, commit) -> Raphael::commitTooltip = (x, y, commit) ->
icon = undefined
nameText = undefined
idText = undefined
messageText = undefined
boxWidth = 300 boxWidth = 300
boxHeight = 200 boxHeight = 200
icon = @image(commit.author.icon, x, y, 20, 20) icon = @image(commit.author.icon, x, y, 20, 20)

View file

@ -1,9 +0,0 @@
$.fn.showAndHide = ->
$(@).show().
delay(3000).
fadeOut()
$.fn.enableButton = ->
$(@).removeAttr('disabled').
removeClass('disabled')

View file

@ -7,8 +7,6 @@ window.slugify = (text) ->
window.ajaxGet = (url) -> window.ajaxGet = (url) ->
$.ajax({type: "GET", url: url, dataType: "script"}) $.ajax({type: "GET", url: url, dataType: "script"})
window.showAndHide = (selector) ->
window.errorMessage = (message) -> window.errorMessage = (message) ->
ehtml = $("<p>") ehtml = $("<p>")
ehtml.addClass("error_message") ehtml.addClass("error_message")

View file

@ -15,8 +15,6 @@ $ ->
$(this).find('.update-failed').hide() $(this).find('.update-failed').hide()
$('.update-username form').on 'ajax:complete', -> $('.update-username form').on 'ajax:complete', ->
$(this).find('.btn-save').enableButton() $(this).find('.save-btn').removeAttr('disabled')
$(this).find('.save-btn').removeClass('disabled')
$(this).find('.loading-gif').hide() $(this).find('.loading-gif').hide()
$('.update-notifications').on 'ajax:complete', ->
$(this).find('.btn-save').enableButton()

View file

@ -6,7 +6,7 @@ $ ->
$('span.log_loading:first').removeClass('hide') $('span.log_loading:first').removeClass('hide')
$('#tree-slider .tree-item-file-name a, .breadcrumb li > a').live "click", -> $('#tree-slider .tree-item-file-name a, .breadcrumb li > a').live "click", ->
$("#tree-content-holder").hide("slide", { direction: "left" }, 400) $("#tree-content-holder").hide("slide", { direction: "left" }, 150)
# Make the entire tree-item row clickable, but not if clicking another link (like a commit message) # Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
$("#tree-slider .tree-item").live 'click', (e) -> $("#tree-slider .tree-item").live 'click', (e) ->

View file

@ -58,26 +58,14 @@
form.show() form.show()
renderNote: (note) -> renderNote: (note) ->
template = Wall.noteTemplate() author = '<strong class="wall-author">' + note.author.name + '</strong>'
template = template.replace('{{author_name}}', note.author.name) body = '<span class="wall-text">' + linkify(sanitize(note.body)) + '</span>'
template = template.replace('{{created_at}}', note.created_at) file = ''
template = template.replace('{{text}}', linkify(sanitize(note.body))) time = '<abbr class="timeago" title="' + note.created_at + '">' + note.created_at + '</time>'
if note.attachment if note.attachment
file = '<i class="icon-paper-clip"/><a href="/files/note/' + note.id + '/' + note.attachment + '">' + note.attachment + '</a>' file = '<span class="wall-file"><a href="/files/note/' + note.id + '/' + note.attachment + '">' + note.attachment + '</a></span>'
else
file = ''
template = template.replace('{{file}}', file)
html = '<li>' + author + body + file + time + '</li>'
$('ul.notes').append(template) $('ul.notes').append(html)
noteTemplate: ->
return '<li>
<strong class="wall-author">{{author_name}}</strong>
<span class="wall-text">
{{text}}
<span class="wall-file">{{file}}</span>
</span>
<abbr class="timeago" title="{{created_at}}">{{created_at}}</abbr>
</li>'

View file

@ -100,9 +100,8 @@
margin-top: 0; margin-top: 0;
} }
.btn { .btn-tiny {
position: relative; @include box-shadow(0 0px 0px 1px #f1f1f1);
top: -2px;
} }
.nav-pills { .nav-pills {

View file

@ -12,7 +12,7 @@
.graph { .graph {
background: #f1f1f1; background: #f1f1f1;
cursor: move; cursor: move;
height: 500px; height: 70%;
overflow: hidden; overflow: hidden;
} }
} }

View file

@ -1,13 +1,13 @@
.main-nav { /*
* Main Menu of Application
*
*/
ul.main_menu {
margin: auto;
margin: 30px 0; margin: 30px 0;
margin-top: 10px; margin-top: 10px;
border-bottom: 1px solid #E1E1E1; height: 38px;
ul {
margin: auto;
height: 39px;
position: relative; position: relative;
top: 3px;
overflow: hidden; overflow: hidden;
.count { .count {
position: relative; position: relative;
@ -32,11 +32,11 @@
margin: 0; margin: 0;
display: table-cell; display: table-cell;
width: 1%; width: 1%;
border-bottom: 2px solid #EEE;
&.active { &.active {
border-bottom: 3px solid #777; border-bottom: 2px solid #474D57;
a { a {
color: $style_color; color: $style_color;
font-weight: bolder;
} }
} }
@ -55,10 +55,14 @@
text-align: center; text-align: center;
font-weight: normal; font-weight: normal;
height: 36px; height: 36px;
line-height: 34px; line-height: 36px;
color: #777; color: #777;
text-shadow: 0 1px 1px white; text-shadow: 0 1px 1px white;
padding: 0 10px; padding: 0 10px;
} }
} }
} /*
* End of Main Menu
*
*/

View file

@ -274,15 +274,6 @@ ul.notes {
} }
.common-note-form {
margin: 0;
height: 140px;
background: #F9F9F9;
padding: 3px;
padding-bottom: 25px;
border: 1px solid #DDD;
}
.note-form-actions { .note-form-actions {
background: #F9F9F9; background: #F9F9F9;
@ -290,8 +281,8 @@ ul.notes {
padding: 0 5px; padding: 0 5px;
.note-form-option { .note-form-option {
margin-top: 10px; margin-top: 8px;
margin-left: 30px; margin-left: 15px;
@extend .pull-left; @extend .pull-left;
} }

View file

@ -14,31 +14,12 @@
.notes { .notes {
margin-bottom: 160px; margin-bottom: 160px;
background: #FFE;
border: 1px solid #EED;
> li {
@extend .clearfix;
border-bottom: 1px solid #EED;
padding: 10px;
}
.wall-author { .wall-author {
color: #666; color: #666;
float: left; margin-right: 10px;
font-size: 12px; border-right: 1px solid #CCC;
width: 120px; padding-right: 5px
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.wall-text {
border-left: 1px solid #CCC;
margin-left: 10px;
padding-left: 10px;
float: left;
width: 75%;
} }
.wall-file { .wall-file {

View file

@ -12,6 +12,7 @@ class CommitLoadContext < BaseContext
commit = project.repository.commit(params[:id]) commit = project.repository.commit(params[:id])
if commit if commit
commit = CommitDecorator.decorate(commit)
line_notes = project.notes.for_commit_id(commit.id).inline line_notes = project.notes.for_commit_id(commit.id).inline
result[:commit] = commit result[:commit] = commit

View file

@ -3,6 +3,8 @@ module Notes
def execute def execute
note = project.notes.new(params[:note]) note = project.notes.new(params[:note])
note.author = current_user note.author = current_user
note.notify = params[:notify].present?
note.notify_author = params[:notify_author].present?
note.save note.save
note note
end end

View file

@ -1,27 +0,0 @@
module Projects
class TransferContext < BaseContext
def execute(role = :default)
namespace_id = params[:project].delete(:namespace_id)
allowed_transfer = can?(current_user, :change_namespace, project) || role == :admin
if allowed_transfer && namespace_id.present?
if namespace_id == Namespace.global_id
if project.namespace.present?
# Transfer to global namespace from anyone
project.transfer(nil)
end
elsif namespace_id.to_i != project.namespace_id
# Transfer to someone namespace
namespace = Namespace.find(namespace_id)
project.transfer(namespace)
end
end
rescue ProjectTransferService::TransferError => ex
project.reload
project.errors.add(:namespace_id, ex.message)
false
end
end
end

View file

@ -1,8 +1,24 @@
module Projects module Projects
class UpdateContext < BaseContext class UpdateContext < BaseContext
def execute(role = :default) def execute(role = :default)
params[:project].delete(:namespace_id) namespace_id = params[:project].delete(:namespace_id)
params[:project].delete(:public) unless can?(current_user, :change_public_mode, project) params[:project].delete(:public) unless can?(current_user, :change_public_mode, project)
allowed_transfer = can?(current_user, :change_namespace, project) || role == :admin
if allowed_transfer && namespace_id.present?
if namespace_id == Namespace.global_id
if project.namespace.present?
# Transfer to global namespace from anyone
project.transfer(nil)
end
elsif namespace_id.to_i != project.namespace_id
# Transfer to someone namespace
namespace = Namespace.find(namespace_id)
project.transfer(namespace)
end
end
project.update_attributes(params[:project], as: role) project.update_attributes(params[:project], as: role)
end end
end end

View file

@ -19,6 +19,34 @@ class Admin::ProjectsController < Admin::ApplicationController
@users = @users.all @users = @users.all
end end
def edit
end
def team_update
@project.team.add_users_ids(params[:user_ids], params[:project_access])
redirect_to [:admin, @project], notice: 'Project was successfully updated.'
end
def update
project.creator = current_user unless project.creator
status = ::Projects::UpdateContext.new(project, current_user, params).execute(:admin)
if status
redirect_to [:admin, @project], notice: 'Project was successfully updated.'
else
render action: "edit"
end
end
def destroy
@project.team.truncate
@project.destroy
redirect_to admin_projects_path, notice: 'Project was successfully deleted.'
end
protected protected
def project def project

View file

@ -84,8 +84,6 @@ class Admin::UsersController < Admin::ApplicationController
format.html { redirect_to [:admin, admin_user], notice: 'User was successfully updated.' } format.html { redirect_to [:admin, admin_user], notice: 'User was successfully updated.' }
format.json { head :ok } format.json { head :ok }
else else
# restore username to keep form action url.
admin_user.username = params[:id]
format.html { render action: "edit" } format.html { render action: "edit" }
format.json { render json: admin_user.errors, status: :unprocessable_entity } format.json { render json: admin_user.errors, status: :unprocessable_entity }
end end

View file

@ -7,7 +7,10 @@ class BlameController < ProjectResourceController
before_filter :authorize_code_access! before_filter :authorize_code_access!
before_filter :require_non_empty_project before_filter :require_non_empty_project
before_filter :assign_ref_vars
def show def show
@blame = Gitlab::Git::Blame.new(project.repository, @commit.id, @path) @repo = @project.repo
@blame = Grit::Blob.blame(@repo, @commit.id, @path)
end end
end end

View file

@ -7,6 +7,8 @@ class BlobController < ProjectResourceController
before_filter :authorize_code_access! before_filter :authorize_code_access!
before_filter :require_non_empty_project before_filter :require_non_empty_project
before_filter :assign_ref_vars
def show def show
if @tree.is_blob? if @tree.is_blob?
send_data( send_data(

View file

@ -13,6 +13,7 @@ class CommitsController < ProjectResourceController
@limit, @offset = (params[:limit] || 40), (params[:offset] || 0) @limit, @offset = (params[:limit] || 40), (params[:offset] || 0)
@commits = @repo.commits(@ref, @path, @limit, @offset) @commits = @repo.commits(@ref, @path, @limit, @offset)
@commits = CommitDecorator.decorate_collection(@commits)
respond_to do |format| respond_to do |format|
format.html # index.html.erb format.html # index.html.erb

View file

@ -8,13 +8,15 @@ class CompareController < ProjectResourceController
end end
def show def show
compare = Gitlab::Git::Compare.new(project.repository, params[:from], params[:to]) result = Commit.compare(project, params[:from], params[:to])
@commits = compare.commits @commits = result[:commits]
@commit = compare.commit @commit = result[:commit]
@diffs = compare.diffs @diffs = result[:diffs]
@refs_are_same = compare.same @refs_are_same = result[:same]
@line_notes = [] @line_notes = []
@commits = CommitDecorator.decorate_collection(@commits)
end end
def create def create

View file

@ -1,47 +0,0 @@
# Controller for edit a repository's file
class EditTreeController < ProjectResourceController
include ExtractsPath
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :require_non_empty_project
before_filter :edit_requirements, only: [:edit, :update]
def show
@last_commit = @project.repository.last_commit_for(@ref, @path).sha
end
def update
edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, @project, @ref, @path)
updated_successfully = edit_file_action.commit!(
params[:content],
params[:commit_message],
params[:last_commit]
)
if updated_successfully
redirect_to project_tree_path(@project, @id), notice: "Your changes have been successfully commited"
else
flash[:notice] = "Your changes could not be commited, because the file has been changed"
render :edit
end
end
private
def edit_requirements
unless @tree.is_blob? && @tree.text?
redirect_to project_tree_path(@project, @id), notice: "You can only edit text files"
end
allowed = if project.protected_branch? @ref
can?(current_user, :push_code_to_protected_branches, project)
else
can?(current_user, :push_code, project)
end
return access_denied! unless allowed
end
end

View file

@ -94,10 +94,12 @@ class MergeRequestsController < ProjectResourceController
def branch_from def branch_from
@commit = @repository.commit(params[:ref]) @commit = @repository.commit(params[:ref])
@commit = CommitDecorator.decorate(@commit)
end end
def branch_to def branch_to
@commit = @repository.commit(params[:ref]) @commit = @repository.commit(params[:ref])
@commit = CommitDecorator.decorate(@commit)
end end
def ci_status def ci_status
@ -127,11 +129,11 @@ class MergeRequestsController < ProjectResourceController
def validates_merge_request def validates_merge_request
# Show git not found page if target branch doesn't exist # Show git not found page if target branch doesn't exist
return invalid_mr unless @project.repository.branch_names.include?(@merge_request.target_branch) return invalid_mr unless @project.repo.heads.map(&:name).include?(@merge_request.target_branch)
# Show git not found page if source branch doesn't exist # Show git not found page if source branch doesn't exist
# and there is no saved commits between source & target branch # and there is no saved commits between source & target branch
return invalid_mr if !@project.repository.branch_names.include?(@merge_request.source_branch) && @merge_request.commits.blank? return invalid_mr if !@project.repo.heads.map(&:name).include?(@merge_request.source_branch) && @merge_request.commits.blank?
end end
def define_show_vars def define_show_vars
@ -141,6 +143,7 @@ class MergeRequestsController < ProjectResourceController
# Get commits from repository # Get commits from repository
# or from cache if already merged # or from cache if already merged
@commits = @merge_request.commits @commits = @merge_request.commits
@commits = CommitDecorator.decorate_collection(@commits)
@allowed_to_merge = allowed_to_merge? @allowed_to_merge = allowed_to_merge?
@show_merge_controls = @merge_request.opened? && @commits.any? && @allowed_to_merge @show_merge_controls = @merge_request.opened? && @commits.any? && @allowed_to_merge

View file

@ -1,13 +0,0 @@
class NotificationsController < ApplicationController
layout 'profile'
def show
@notification = current_user.notification
@projects = current_user.authorized_projects
end
def update
current_user.notification_level = params[:notification_level]
@saved = current_user.save
end
end

View file

@ -4,7 +4,7 @@ class ProjectsController < ProjectResourceController
# Authorize # Authorize
before_filter :authorize_read_project!, except: [:index, :new, :create] before_filter :authorize_read_project!, except: [:index, :new, :create]
before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer] before_filter :authorize_admin_project!, only: [:edit, :update, :destroy]
before_filter :require_non_empty_project, only: [:blob, :tree, :graph] before_filter :require_non_empty_project, only: [:blob, :tree, :graph]
layout 'application', only: [:new, :create] layout 'application', only: [:new, :create]
@ -45,10 +45,10 @@ class ProjectsController < ProjectResourceController
format.js format.js
end end
end end
end
def transfer rescue Project::TransferError => ex
::Projects::TransferContext.new(project, current_user, params).execute @error = ex
render :update_failed
end end
def show def show
@ -57,11 +57,11 @@ class ProjectsController < ProjectResourceController
respond_to do |format| respond_to do |format|
format.html do format.html do
if @project.empty_repo? if @project.repository && !@project.repository.empty?
render "projects/empty"
else
@last_push = current_user.recent_push(@project.id) @last_push = current_user.recent_push(@project.id)
render :show render :show
else
render "projects/empty"
end end
end end
format.js format.js

View file

@ -34,6 +34,7 @@ class RefsController < ProjectResourceController
@logs = contents.map do |content| @logs = contents.map do |content|
file = params[:path] ? File.join(params[:path], content.name) : content.name file = params[:path] ? File.join(params[:path], content.name) : content.name
last_commit = @repo.commits(@commit.id, file, 1).last last_commit = @repo.commits(@commit.id, file, 1).last
last_commit = CommitDecorator.decorate(last_commit)
{ {
file_name: content.name, file_name: content.name,
commit: last_commit commit: last_commit
@ -48,7 +49,9 @@ class RefsController < ProjectResourceController
@repo = project.repository @repo = project.repository
@commit = @repo.commit(@ref) @commit = @repo.commit(@ref)
@commit = CommitDecorator.decorate(@commit)
@tree = Tree.new(@commit.tree, @ref, params[:path]) @tree = Tree.new(@commit.tree, @ref, params[:path])
@tree = TreeDecorator.new(@tree)
@hex_path = Digest::SHA1.hexdigest(params[:path] || "") @hex_path = Digest::SHA1.hexdigest(params[:path] || "")
if params[:path] if params[:path]

View file

@ -7,6 +7,9 @@ class TreeController < ProjectResourceController
before_filter :authorize_code_access! before_filter :authorize_code_access!
before_filter :require_non_empty_project before_filter :require_non_empty_project
before_filter :assign_ref_vars
before_filter :edit_requirements, only: [:edit, :update]
def show def show
@hex_path = Digest::SHA1.hexdigest(@path) @hex_path = Digest::SHA1.hexdigest(@path)
@logs_path = logs_file_project_ref_path(@project, @ref, @path) @logs_path = logs_file_project_ref_path(@project, @ref, @path)
@ -17,4 +20,40 @@ class TreeController < ProjectResourceController
format.js { no_cache_headers } format.js { no_cache_headers }
end end
end end
def edit
@last_commit = @project.repository.last_commit_for(@ref, @path).sha
end
def update
edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, @project, @ref, @path)
updated_successfully = edit_file_action.commit!(
params[:content],
params[:commit_message],
params[:last_commit]
)
if updated_successfully
redirect_to project_tree_path(@project, @id), notice: "Your changes have been successfully commited"
else
flash[:notice] = "Your changes could not be commited, because the file has been changed"
render :edit
end
end
private
def edit_requirements
unless @tree.is_blob? && @tree.text?
redirect_to project_tree_path(@project, @id), notice: "You can only edit text files"
end
allowed = if project.protected_branch? @ref
can?(current_user, :push_code_to_protected_branches, project)
else
can?(current_user, :push_code, project)
end
return access_denied! unless allowed
end
end end

View file

@ -0,0 +1,93 @@
class CommitDecorator < ApplicationDecorator
decorates :commit
# Returns a string describing the commit for use in a link title
#
# Example
#
# "Commit: Alex Denisov - Project git clone panel"
def link_title
"Commit: #{author_name} - #{title}"
end
# Returns the commits title.
#
# Usually, the commit title is the first line of the commit message.
# In case this first line is longer than 80 characters, it is cut off
# after 70 characters and ellipses (`&hellp;`) are appended.
def title
title = safe_message
return no_commit_message if title.blank?
title_end = title.index(/\n/)
if (!title_end && title.length > 80) || (title_end && title_end > 80)
title[0..69] << "&hellip;".html_safe
else
title.split(/\n/, 2).first
end
end
# Returns the commits description
#
# cut off, ellipses (`&hellp;`) are prepended to the commit message.
def description
description = safe_message
title_end = description.index(/\n/)
if (!title_end && description.length > 80) || (title_end && title_end > 80)
"&hellip;".html_safe << description[70..-1]
else
description.split(/\n/, 2)[1].try(:chomp)
end
end
# Returns a link to the commit author. If the author has a matching user and
# is a member of the current @project it will link to the team member page.
# Otherwise it will link to the author email as specified in the commit.
#
# options:
# avatar: true will prepend the avatar image
# size: size of the avatar image in px
def author_link(options = {})
person_link(options.merge source: :author)
end
# Just like #author_link but for the committer.
def committer_link(options = {})
person_link(options.merge source: :committer)
end
protected
def no_commit_message
"--no commit message"
end
# Private: Returns a link to a person. If the person has a matching user and
# is a member of the current @project it will link to the team member page.
# Otherwise it will link to the person email as specified in the commit.
#
# options:
# source: one of :author or :committer
# avatar: true will prepend the avatar image
# size: size of the avatar image in px
def person_link(options = {})
source_name = send "#{options[:source]}_name".to_sym
source_email = send "#{options[:source]}_email".to_sym
text = if options[:avatar]
avatar = h.image_tag h.gravatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: ""
%Q{#{avatar} <span class="commit-#{options[:source]}-name">#{source_name}</span>}
else
source_name
end
user = User.where('name like ? or email like ?', source_name, source_email).first
if user.nil?
h.mail_to(source_email, text.html_safe, class: "commit-#{options[:source]}-link")
else
h.link_to(text.html_safe, h.user_path(user), class: "commit-#{options[:source]}-link")
end
end
end

View file

@ -0,0 +1,33 @@
class TreeDecorator < ApplicationDecorator
decorates :tree
def breadcrumbs(max_links = 2)
if path
part_path = ""
parts = path.split("\/")
yield('..', nil) if parts.count > max_links
parts.each do |part|
part_path = File.join(part_path, part) unless part_path.empty?
part_path = part if part_path.empty?
next unless parts.last(2).include?(part) if parts.count > max_links
yield(part, h.tree_join(ref, part_path))
end
end
end
def up_dir?
path.present?
end
def up_dir_path
file = File.join(path, "..")
h.tree_join(ref, file)
end
def readme
@readme ||= contents.find { |c| c.is_a?(Grit::Blob) and c.name =~ /^readme/i }
end
end

View file

@ -96,7 +96,7 @@ module ApplicationHelper
] ]
project_nav = [] project_nav = []
if @project && @project.repository.exists? && @project.repository.root_ref if @project && @project.repository && @project.repository.root_ref
project_nav = [ project_nav = [
{ label: "#{simple_sanitize(@project.name_with_namespace)} - Issues", url: project_issues_path(@project) }, { label: "#{simple_sanitize(@project.name_with_namespace)} - Issues", url: project_issues_path(@project) },
{ label: "#{simple_sanitize(@project.name_with_namespace)} - Commits", url: project_commits_path(@project, @ref || @project.repository.root_ref) }, { label: "#{simple_sanitize(@project.name_with_namespace)} - Commits", url: project_commits_path(@project, @ref || @project.repository.root_ref) },

View file

@ -1,20 +1,4 @@
module CommitsHelper module CommitsHelper
# Returns a link to the commit author. If the author has a matching user and
# is a member of the current @project it will link to the team member page.
# Otherwise it will link to the author email as specified in the commit.
#
# options:
# avatar: true will prepend the avatar image
# size: size of the avatar image in px
def commit_author_link(commit, options = {})
commit_person_link(commit, options.merge(source: :author))
end
# Just like #author_link but for the committer.
def commit_committer_link(commit, options = {})
commit_person_link(commit, options.merge(source: :committer))
end
def identification_type(line) def identification_type(line)
if line[0] == "+" if line[0] == "+"
"new" "new"
@ -109,8 +93,10 @@ module CommitsHelper
end end
def commit_to_html commit def commit_to_html commit
if commit.model
escape_javascript(render 'commits/commit', commit: commit) escape_javascript(render 'commits/commit', commit: commit)
end end
end
def diff_line_content(line) def diff_line_content(line)
if line.blank? if line.blank?
@ -119,58 +105,4 @@ module CommitsHelper
line line
end end
end end
# Breadcrumb links for a Project and, if applicable, a tree path
def commits_breadcrumbs
return unless @project && @ref
# Add the root project link and the arrow icon
crumbs = content_tag(:li) do
content_tag(:span, nil, class: 'arrow') +
link_to(@project.name, project_commits_path(@project, @ref))
end
if @path
parts = @path.split('/')
parts.each_with_index do |part, i|
crumbs += content_tag(:span, '/', class: 'divider')
crumbs += content_tag(:li) do
# The text is just the individual part, but the link needs all the parts before it
link_to part, project_commits_path(@project, tree_join(@ref, parts[0..i].join('/')))
end
end
end
crumbs.html_safe
end
protected
# Private: Returns a link to a person. If the person has a matching user and
# is a member of the current @project it will link to the team member page.
# Otherwise it will link to the person email as specified in the commit.
#
# options:
# source: one of :author or :committer
# avatar: true will prepend the avatar image
# size: size of the avatar image in px
def commit_person_link(commit, options = {})
source_name = commit.send "#{options[:source]}_name".to_sym
source_email = commit.send "#{options[:source]}_email".to_sym
text = if options[:avatar]
avatar = image_tag(gravatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "")
%Q{#{avatar} <span class="commit-#{options[:source]}-name">#{source_name}</span>}
else
source_name
end
user = User.where('name like ? or email like ?', source_name, source_email).first
if user.nil?
mail_to(source_email, text.html_safe, class: "commit-#{options[:source]}-link")
else
link_to(text.html_safe, user_path(user), class: "commit-#{options[:source]}-link")
end
end
end end

View file

@ -1,13 +1,6 @@
module GraphHelper module GraphHelper
def get_refs(commit) def join_with_space(ary)
refs = "" ary.collect{|r|r.name}.join(" ") unless ary.nil?
refs += commit.refs.collect{|r|r.name}.join(" ") if commit.refs
# append note count
notes = @project.notes.for_commit_id(commit.id)
refs += "[#{notes.count}]" if notes.any?
refs
end end
def parents_zip_spaces(parents, parent_spaces) def parents_zip_spaces(parents, parent_spaces)

View file

@ -48,19 +48,7 @@ module IssuesHelper
if @project.used_default_issues_tracker? if @project.used_default_issues_tracker?
project_issues_filter_path(@project) project_issues_filter_path(@project)
else else
url = Gitlab.config.issues_tracker[@project.issues_tracker]["project_url"] url = Settings[:issues_tracker][@project.issues_tracker]["project_url"]
url.gsub(':project_id', @project.id.to_s)
.gsub(':issues_tracker_id', @project.issues_tracker_id.to_s)
end
end
def url_for_new_issue
return "" if @project.nil?
if @project.used_default_issues_tracker?
url = new_project_issue_path project_id: @project
else
url = Gitlab.config.issues_tracker[@project.issues_tracker]["new_issue_url"]
url.gsub(':project_id', @project.id.to_s) url.gsub(':project_id', @project.id.to_s)
.gsub(':issues_tracker_id', @project.issues_tracker_id.to_s) .gsub(':issues_tracker_id', @project.issues_tracker_id.to_s)
end end
@ -72,7 +60,7 @@ module IssuesHelper
if @project.used_default_issues_tracker? if @project.used_default_issues_tracker?
url = project_issue_url project_id: @project, id: issue_id url = project_issue_url project_id: @project, id: issue_id
else else
url = Gitlab.config.issues_tracker[@project.issues_tracker]["issues_url"] url = Settings[:issues_tracker][@project.issues_tracker]["issues_url"]
url.gsub(':id', issue_id.to_s) url.gsub(':id', issue_id.to_s)
.gsub(':project_id', @project.id.to_s) .gsub(':project_id', @project.id.to_s)
.gsub(':issues_tracker_id', @project.issues_tracker_id.to_s) .gsub(':issues_tracker_id', @project.issues_tracker_id.to_s)

View file

@ -1,2 +0,0 @@
module NotificationsHelper
end

View file

@ -70,26 +70,28 @@ module TreeHelper
end end
end end
def tree_breadcrumbs(tree, max_links = 2) # Breadcrumb links for a Project and, if applicable, a tree path
if tree.path def breadcrumbs
part_path = "" return unless @project && @ref
parts = tree.path.split("\/")
yield('..', nil) if parts.count > max_links # Add the root project link and the arrow icon
crumbs = content_tag(:li) do
content_tag(:span, nil, class: 'arrow') +
link_to(@project.name, project_commits_path(@project, @ref))
end
parts.each do |part| if @path
part_path = File.join(part_path, part) unless part_path.empty? parts = @path.split('/')
part_path = part if part_path.empty?
next unless parts.last(2).include?(part) if parts.count > max_links parts.each_with_index do |part, i|
yield(part, tree_join(tree.ref, part_path)) crumbs += content_tag(:span, '/', class: 'divider')
crumbs += content_tag(:li) do
# The text is just the individual part, but the link needs all the parts before it
link_to part, project_commits_path(@project, tree_join(@ref, parts[0..i].join('/')))
end end
end end
end end
def up_dir_path tree crumbs.html_safe
file = File.join(tree.path, "..")
tree_join(tree.ref, file)
end end
end end

View file

@ -1,9 +1,9 @@
module Emails module Emails
module Issues module Issues
def new_issue_email(recipient_id, issue_id) def new_issue_email(issue_id)
@issue = Issue.find(issue_id) @issue = Issue.find(issue_id)
@project = @issue.project @project = @issue.project
mail(to: recipient(recipient_id), subject: subject("new issue ##{@issue.id}", @issue.title)) mail(to: @issue.assignee_email, subject: subject("new issue ##{@issue.id}", @issue.title))
end end
def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id) def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id)
@ -13,14 +13,6 @@ module Emails
mail(to: recipient(recipient_id), subject: subject("changed issue ##{@issue.id}", @issue.title)) mail(to: recipient(recipient_id), subject: subject("changed issue ##{@issue.id}", @issue.title))
end end
def closed_issue_email(recipient_id, issue_id, updated_by_user_id)
@issue = Issue.find issue_id
@project = @issue.project
@updated_by = User.find updated_by_user_id
mail(to: recipient(recipient_id),
subject: subject("Closed issue ##{@issue.id}", @issue.title))
end
def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id) def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id)
@issue = Issue.find issue_id @issue = Issue.find issue_id
@issue_status = status @issue_status = status

View file

@ -1,9 +1,9 @@
module Emails module Emails
module MergeRequests module MergeRequests
def new_merge_request_email(recipient_id, merge_request_id) def new_merge_request_email(merge_request_id)
@merge_request = MergeRequest.find(merge_request_id) @merge_request = MergeRequest.find(merge_request_id)
@project = @merge_request.project @project = @merge_request.project
mail(to: recipient(recipient_id), subject: subject("new merge request !#{@merge_request.id}", @merge_request.title)) mail(to: @merge_request.assignee_email, subject: subject("new merge request !#{@merge_request.id}", @merge_request.title))
end end
def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id) def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id)
@ -12,18 +12,5 @@ module Emails
@project = @merge_request.project @project = @merge_request.project
mail(to: recipient(recipient_id), subject: subject("changed merge request !#{@merge_request.id}", @merge_request.title)) mail(to: recipient(recipient_id), subject: subject("changed merge request !#{@merge_request.id}", @merge_request.title))
end end
def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
@merge_request = MergeRequest.find(merge_request_id)
@project = @merge_request.project
@updated_by = User.find updated_by_user_id
mail(to: recipient(recipient_id), subject: subject("Closed merge request !#{@merge_request.id}", @merge_request.title))
end
def merged_merge_request_email(recipient_id, merge_request_id)
@merge_request = MergeRequest.find(merge_request_id)
@project = @merge_request.project
mail(to: recipient(recipient_id), subject: subject("Accepted merge request !#{@merge_request.id}", @merge_request.title))
end
end end
end end

View file

@ -3,6 +3,7 @@ module Emails
def note_commit_email(recipient_id, note_id) def note_commit_email(recipient_id, note_id)
@note = Note.find(note_id) @note = Note.find(note_id)
@commit = @note.noteable @commit = @note.noteable
@commit = CommitDecorator.decorate(@commit)
@project = @note.project @project = @note.project
mail(to: recipient(recipient_id), subject: subject("note for commit #{@commit.short_id}", @commit.title)) mail(to: recipient(recipient_id), subject: subject("note for commit #{@commit.short_id}", @commit.title))
end end

View file

@ -41,7 +41,7 @@ class Ability
rules << project_guest_rules rules << project_guest_rules
end end
if project.owner == user || user.admin? if project.owner == user
rules << project_admin_rules rules << project_admin_rules
end end

View file

@ -8,70 +8,168 @@ class Commit
# #
DIFF_SAFE_SIZE = 100 DIFF_SAFE_SIZE = 100
def self.decorate(commits) attr_accessor :commit, :head, :refs
commits.map { |c| self.new(c) }
delegate :message, :authored_date, :committed_date, :parents, :sha,
:date, :committer, :author, :diffs, :tree, :id, :stats,
:to_patch, to: :commit
class << self
def find_or_first(repo, commit_id = nil, root_ref)
commit = if commit_id
repo.commit(commit_id)
else
repo.commits(root_ref).first
end end
attr_accessor :raw Commit.new(commit) if commit
end
def initialize(raw_commit) def fresh_commits(repo, n = 10)
commits = repo.heads.map do |h|
repo.commits(h.name, n).map { |c| Commit.new(c, h) }
end.flatten.uniq { |c| c.id }
commits.sort! do |x, y|
y.committed_date <=> x.committed_date
end
commits[0...n]
end
def commits_with_refs(repo, n = 20)
commits = repo.branches.map { |ref| Commit.new(ref.commit, ref) }
commits.sort! do |x, y|
y.committed_date <=> x.committed_date
end
commits[0..n]
end
def commits_since(repo, date)
commits = repo.heads.map do |h|
repo.log(h.name, nil, since: date).each { |c| Commit.new(c, h) }
end.flatten.uniq { |c| c.id }
commits.sort! do |x, y|
y.committed_date <=> x.committed_date
end
commits
end
def commits(repo, ref, path = nil, limit = nil, offset = nil)
if path
repo.log(ref, path, max_count: limit, skip: offset)
elsif limit && offset
repo.commits(ref, limit, offset)
else
repo.commits(ref)
end.map{ |c| Commit.new(c) }
end
def commits_between(repo, from, to)
repo.commits_between(from, to).map { |c| Commit.new(c) }
end
def compare(project, from, to)
result = {
commits: [],
diffs: [],
commit: nil,
same: false
}
return result unless from && to
first = project.repository.commit(to.try(:strip))
last = project.repository.commit(from.try(:strip))
if first && last
result[:same] = (first.id == last.id)
result[:commits] = project.repo.commits_between(last.id, first.id).map {|c| Commit.new(c)}
# Dont load diff for 100+ commits
result[:diffs] = if result[:commits].size > 100
[]
else
project.repo.diff(last.id, first.id) rescue []
end
result[:commit] = Commit.new(first)
end
result
end
end
def initialize(raw_commit, head = nil)
raise "Nil as raw commit passed" unless raw_commit raise "Nil as raw commit passed" unless raw_commit
@raw = raw_commit @commit = raw_commit
@head = head
end end
def id def short_id(length = 10)
@raw.id id.to_s[0..length]
end end
# Returns a string describing the commit for use in a link title def safe_message
# @safe_message ||= message
# Example
#
# "Commit: Alex Denisov - Project git clone panel"
def link_title
"Commit: #{author_name} - #{title}"
end end
# Returns the commits title. def created_at
# committed_date
# Usually, the commit title is the first line of the commit message. end
# In case this first line is longer than 80 characters, it is cut off
# after 70 characters and ellipses (`&hellp;`) are appended.
def title
title = safe_message
return no_commit_message if title.blank? def author_email
author.email
end
title_end = title.index(/\n/) def author_name
if (!title_end && title.length > 80) || (title_end && title_end > 80) author.name
title[0..69] << "&hellip;".html_safe end
# Was this commit committed by a different person than the original author?
def different_committer?
author_name != committer_name || author_email != committer_email
end
def committer_name
committer.name
end
def committer_email
committer.email
end
def prev_commit
@prev_commit ||= if parents.present?
Commit.new(parents.first)
else else
title.split(/\n/, 2).first nil
end end
end end
# Returns the commits description def prev_commit_id
prev_commit.try :id
end
# Shows the diff between the commit's parent and the commit.
# #
# cut off, ellipses (`&hellp;`) are prepended to the commit message. # Cuts out the header and stats from #to_patch and returns only the diff.
def description def to_diff
description = safe_message # see Grit::Commit#show
patch = to_patch
title_end = description.index(/\n/) # discard lines before the diff
if (!title_end && description.length > 80) || (title_end && title_end > 80) lines = patch.split("\n")
"&hellip;".html_safe << description[70..-1] while !lines.first.start_with?("diff --git") do
else lines.shift
description.split(/\n/, 2)[1].try(:chomp) end
end lines.pop if lines.last =~ /^[\d.]+$/ # Git version
end lines.pop if lines.last == "-- " # end of diff
lines.join("\n")
def method_missing(m, *args, &block)
@raw.send(m, *args, &block)
end
def respond_to?(method)
return true if @raw.respond_to?(method)
super
end end
end end

View file

@ -50,7 +50,7 @@ class GollumWiki
# Returns the last 30 Commit objects across the entire # Returns the last 30 Commit objects across the entire
# repository. # repository.
def recent_history def recent_history
Gitlab::Git::Commit.fresh_commits(wiki.repo, 30) Commit.fresh_commits(wiki.repo, 30)
end end
# Finds a page within the repository based on a tile # Finds a page within the repository based on a tile
@ -90,17 +90,13 @@ class GollumWiki
private private
def create_repo! def create_repo!
if init_repo(path_with_namespace) if gitlab_shell.add_repository(path_with_namespace)
Gollum::Wiki.new(path_to_repo) Gollum::Wiki.new(path_to_repo)
else else
raise CouldNotCreateWikiError raise CouldNotCreateWikiError
end end
end end
def init_repo(path_with_namespace)
gitlab_shell.add_repository(path_with_namespace)
end
def commit_details(action, message = nil, title = nil) def commit_details(action, message = nil, title = nil)
commit_message = message || default_message(action, title) commit_message = message || default_message(action, title)
@ -118,4 +114,5 @@ class GollumWiki
def path_to_repo def path_to_repo
@path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git") @path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git")
end end
end end

View file

@ -152,17 +152,7 @@ class MergeRequest < ActiveRecord::Base
end end
def commits def commits
if st_commits.present? st_commits || []
# check if merge request commits are valid
if st_commits.first.respond_to?(:short_id)
st_commits
else
# if commits are invalid - simply reload it from repo
reloaded_commits
end
else
[]
end
end end
def probably_merged? def probably_merged?
@ -172,12 +162,6 @@ class MergeRequest < ActiveRecord::Base
def reloaded_commits def reloaded_commits
if opened? && unmerged_commits.any? if opened? && unmerged_commits.any?
# we need to reset st_commits field first
# in order to prevent internal rails comparison
self.st_commits = []
save
# Then we can safely write unmerged commits
self.st_commits = unmerged_commits self.st_commits = unmerged_commits
save save
end end
@ -185,8 +169,9 @@ class MergeRequest < ActiveRecord::Base
end end
def unmerged_commits def unmerged_commits
self.project.repository. self.project.repo.
commits_between(self.target_branch, self.source_branch). commits_between(self.target_branch, self.source_branch).
map {|c| Commit.new(c)}.
sort_by(&:created_at). sort_by(&:created_at).
reverse reverse
end end

View file

@ -13,8 +13,6 @@
# #
class Namespace < ActiveRecord::Base class Namespace < ActiveRecord::Base
include Gitlab::ShellAdapter
attr_accessible :name, :description, :path attr_accessible :name, :description, :path
has_many :projects, dependent: :destroy has_many :projects, dependent: :destroy
@ -33,7 +31,7 @@ class Namespace < ActiveRecord::Base
delegate :name, to: :owner, allow_nil: true, prefix: true delegate :name, to: :owner, allow_nil: true, prefix: true
after_create :ensure_dir_exist after_create :ensure_dir_exist
after_update :move_dir, if: :path_changed? after_update :move_dir
after_destroy :rm_dir after_destroy :rm_dir
scope :root, -> { where('type IS NULL') } scope :root, -> { where('type IS NULL') }
@ -55,34 +53,48 @@ class Namespace < ActiveRecord::Base
end end
def ensure_dir_exist def ensure_dir_exist
gitlab_shell.add_namespace(path) unless dir_exists?
FileUtils.mkdir( namespace_full_path, mode: 0770 )
end
end end
def rm_dir def dir_exists?
gitlab_shell.rm_namespace(path) File.exists?(namespace_full_path)
end
def namespace_full_path
@namespace_full_path ||= File.join(Gitlab.config.gitlab_shell.repos_path, path)
end end
def move_dir def move_dir
if gitlab_shell.mv_namespace(path_was, path) if path_changed?
# If repositories moved successfully we need to remove old satellites old_path = File.join(Gitlab.config.gitlab_shell.repos_path, path_was)
# and send update instructions to users. new_path = File.join(Gitlab.config.gitlab_shell.repos_path, path)
# However we cannot allow rollback since we moved namespace dir if File.exists?(new_path)
# So we basically we mute exceptions in next actions raise "Already exists"
end
begin begin
gitlab_shell.rm_satellites(path_was) # Remove satellite when moving repo
if path_was.present?
satellites_path = File.join(Gitlab.config.satellites.path, path_was)
FileUtils.rm_r( satellites_path, force: true )
end
FileUtils.mv( old_path, new_path )
send_update_instructions send_update_instructions
rescue rescue Exception => e
# Returning false does not rolback after_* transaction but gives raise "Namespace move error #{old_path} #{new_path}"
# us information about failing some of tasks
false
end end
else
# if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs
raise Exception.new('namespace directory cannot be moved')
end end
end end
def rm_dir
dir_path = File.join(Gitlab.config.gitlab_shell.repos_path, path)
FileUtils.rm_r( dir_path, force: true )
end
def send_update_instructions def send_update_instructions
projects.each(&:send_move_instructions) projects.each(&:send_move_instructions)
end end

View file

@ -8,7 +8,7 @@ module Network
attr_accessor :time, :spaces, :parent_spaces attr_accessor :time, :spaces, :parent_spaces
def initialize(raw_commit, refs) def initialize(raw_commit, refs)
@commit = Gitlab::Git::Commit.new(raw_commit) @commit = ::Commit.new(raw_commit)
@time = -1 @time = -1
@spaces = [] @spaces = []
@parent_spaces = [] @parent_spaces = []

View file

@ -40,12 +40,15 @@ module Network
def index_commits def index_commits
days = [] days = []
@map = {} @map = {}
@reserved = {}
@commits.each_with_index do |c,i| @commits.reverse.each_with_index do |c,i|
c.time = i c.time = i
days[i] = c.committed_date days[i] = c.committed_date
@map[c.id] = c @map[c.id] = c
end
@reserved = {}
days.each_index do |i|
@reserved[i] = [] @reserved[i] = []
end end
@ -132,7 +135,11 @@ module Network
spaces = [] spaces = []
commit.parents(@map).each do |parent| commit.parents(@map).each do |parent|
range = commit.time..parent.time range = if commit.time < parent.time then
commit.time..parent.time
else
parent.time..commit.time
end
space = if commit.space >= parent.space then space = if commit.space >= parent.space then
find_free_parent_space(range, parent.space, -1, commit.space) find_free_parent_space(range, parent.space, -1, commit.space)
@ -159,7 +166,7 @@ module Network
range.each do |i| range.each do |i|
if i != range.first && if i != range.first &&
i != range.last && i != range.last &&
@commits[i].spaces.include?(overlap_space) then @commits[reversed_index(i)].spaces.include?(overlap_space) then
return true; return true;
end end
@ -177,7 +184,7 @@ module Network
return return
end end
time_range = leaves.first.time..leaves.last.time time_range = leaves.last.time..leaves.first.time
space_base = get_space_base(leaves) space_base = get_space_base(leaves)
space = find_free_space(time_range, 2, space_base) space = find_free_space(time_range, 2, space_base)
leaves.each do |l| leaves.each do |l|
@ -191,17 +198,17 @@ module Network
end end
# and mark it as reserved # and mark it as reserved
if parent_time.nil? min_time = leaves.last.time
min_time = leaves.first.time leaves.last.parents(@map).each do |parent|
else if parent.time < min_time
min_time = parent_time + 1 min_time = parent.time
end
end end
max_time = leaves.last.time if parent_time.nil?
leaves.last.parents(@map).each do |parent| max_time = leaves.first.time
if max_time < parent.time else
max_time = parent.time max_time = parent_time - 1
end
end end
mark_reserved(min_time..max_time, space) mark_reserved(min_time..max_time, space)
@ -282,5 +289,9 @@ module Network
end end
refs_cache refs_cache
end end
def reversed_index(index)
-index - 1
end
end end
end end

View file

@ -22,6 +22,9 @@ class Note < ActiveRecord::Base
attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id, attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id,
:attachment, :line_code, :commit_id :attachment, :line_code, :commit_id
attr_accessor :notify
attr_accessor :notify_author
belongs_to :project belongs_to :project
belongs_to :noteable, polymorphic: true belongs_to :noteable, polymorphic: true
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
@ -140,6 +143,14 @@ class Note < ActiveRecord::Base
nil nil
end end
def notify
@notify ||= false
end
def notify_author
@notify_author ||= false
end
# Returns true if this is an upvote note, # Returns true if this is an upvote note,
# otherwise false is returned # otherwise false is returned
def upvote? def upvote?

View file

@ -1,30 +0,0 @@
class Notification
#
# Notification levels
#
N_DISABLED = 0
N_PARTICIPATING = 1
N_WATCH = 2
attr_accessor :user
def self.notification_levels
[N_DISABLED, N_PARTICIPATING, N_WATCH]
end
def initialize(user)
@user = user
end
def disabled?
user.notification_level == N_DISABLED
end
def participating?
user.notification_level == N_PARTICIPATING
end
def watch?
user.notification_level == N_WATCH
end
end

View file

@ -18,15 +18,16 @@
# public :boolean default(FALSE), not null # public :boolean default(FALSE), not null
# issues_tracker :string(255) default("gitlab"), not null # issues_tracker :string(255) default("gitlab"), not null
# issues_tracker_id :string(255) # issues_tracker_id :string(255)
# snippets_enabled :boolean default(TRUE), not null
# #
require "grit" require "grit"
class Project < ActiveRecord::Base class Project < ActiveRecord::Base
include Gitlab::ShellAdapter include Gitolited
extend Enumerize extend Enumerize
class TransferError < StandardError; end
attr_accessible :name, :path, :description, :default_branch, :issues_tracker, attr_accessible :name, :path, :description, :default_branch, :issues_tracker,
:issues_enabled, :wall_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :issues_enabled, :wall_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id,
:wiki_enabled, :public, :import_url, as: [:default, :admin] :wiki_enabled, :public, :import_url, as: [:default, :admin]
@ -141,7 +142,13 @@ class Project < ActiveRecord::Base
end end
def repository def repository
if path
@repository ||= Repository.new(path_with_namespace, default_branch) @repository ||= Repository.new(path_with_namespace, default_branch)
else
nil
end
rescue Grit::NoSuchPathError
nil
end end
def saved? def saved?
@ -326,14 +333,14 @@ class Project < ActiveRecord::Base
end end
def valid_repo? def valid_repo?
repository.exists? repo
rescue rescue
errors.add(:path, "Invalid repository path") errors.add(:path, "Invalid repository path")
false false
end end
def empty_repo? def empty_repo?
!repository.exists? || repository.empty? !repository || repository.empty?
end end
def ensure_satellite_exists def ensure_satellite_exists
@ -357,25 +364,18 @@ class Project < ActiveRecord::Base
end end
def repo_exists? def repo_exists?
@repo_exists ||= repository.exists? @repo_exists ||= (repository && repository.branches.present?)
rescue rescue
@repo_exists = false @repo_exists = false
end end
def open_branches def open_branches
all_branches = repository.branches if protected_branches.empty?
self.repo.heads
if protected_branches.present? else
all_branches.reject! do |branch| pnames = protected_branches.map(&:name)
protected_branches_names.include?(branch.name) self.repo.heads.reject { |h| pnames.include?(h.name) }
end end.sort_by(&:name)
end
all_branches
end
def protected_branches_names
@protected_branches_names ||= protected_branches.map(&:name)
end end
def root_ref?(branch) def root_ref?(branch)
@ -397,6 +397,6 @@ class Project < ActiveRecord::Base
# Check if current branch name is marked as protected in the system # Check if current branch name is marked as protected in the system
def protected_branch? branch_name def protected_branch? branch_name
protected_branches_names.include?(branch_name) protected_branches.map(&:name).include?(branch_name)
end end
end end

View file

@ -10,7 +10,7 @@
# #
class ProtectedBranch < ActiveRecord::Base class ProtectedBranch < ActiveRecord::Base
include Gitlab::ShellAdapter include Gitolited
attr_accessible :name attr_accessible :name

View file

@ -1,45 +1,169 @@
class Repository class Repository
attr_accessor :raw_repository include Gitlab::Popen
def initialize(path_with_namespace, default_branch) # Repository directory name with namespace direcotry
@raw_repository = Gitlab::Git::Repository.new(path_with_namespace, default_branch) # Examples:
rescue Gitlab::Git::Repository::NoRepository # gitlab/gitolite
nil # diaspora
#
attr_accessor :path_with_namespace
# Grit repo object
attr_accessor :repo
# Default branch in the repository
attr_accessor :root_ref
def initialize(path_with_namespace, root_ref = 'master')
@root_ref = root_ref || "master"
@path_with_namespace = path_with_namespace
# Init grit repo object
repo
end end
def exists? def raw
raw_repository repo
end end
def empty? def path_to_repo
raw_repository.empty? @path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git")
end end
def commit(id = nil) def repo
commit = raw_repository.commit(id) @repo ||= Grit::Repo.new(path_to_repo)
commit = Commit.new(commit) if commit end
commit
def commit(commit_id = nil)
Commit.find_or_first(repo, commit_id, root_ref)
end
def fresh_commits(n = 10)
Commit.fresh_commits(repo, n)
end
def commits_with_refs(n = 20)
Commit.commits_with_refs(repo, n)
end
def commits_since(date)
Commit.commits_since(repo, date)
end end
def commits(ref, path = nil, limit = nil, offset = nil) def commits(ref, path = nil, limit = nil, offset = nil)
commits = raw_repository.commits(ref, path, limit, offset) Commit.commits(repo, ref, path, limit, offset)
commits = Commit.decorate(commits) if commits.present?
commits
end end
def commits_between(target, source) def last_commit_for(ref, path = nil)
commits = raw_repository.commits_between(target, source) commits(ref, path, 1).first
commits = Commit.decorate(commits) if commits.present?
commits
end end
def method_missing(m, *args, &block) def commits_between(from, to)
raw_repository.send(m, *args, &block) Commit.commits_between(repo, from, to)
end end
def respond_to?(method) # Returns an Array of branch names
return true if raw_repository.respond_to?(method) def branch_names
repo.branches.collect(&:name).sort
end
super # Returns an Array of Branches
def branches
repo.branches.sort_by(&:name)
end
# Returns an Array of tag names
def tag_names
repo.tags.collect(&:name).sort.reverse
end
# Returns an Array of Tags
def tags
repo.tags.sort_by(&:name).reverse
end
# Returns an Array of branch and tag names
def ref_names
[branch_names + tag_names].flatten
end
def heads
@heads ||= repo.heads
end
def tree(fcommit, path = nil)
fcommit = commit if fcommit == :head
tree = fcommit.tree
path ? (tree / path) : tree
end
def has_commits?
!!commit
rescue Grit::NoSuchPathError
false
end
def empty?
!has_commits?
end
# Discovers the default branch based on the repository's available branches
#
# - If no branches are present, returns nil
# - If one branch is present, returns its name
# - If two or more branches are present, returns the one that has a name
# matching root_ref (default_branch or 'master' if default_branch is nil)
def discover_default_branch
if branch_names.length == 0
nil
elsif branch_names.length == 1
branch_names.first
else
branch_names.select { |v| v == root_ref }.first
end
end
# Archive Project to .tar.gz
#
# Already packed repo archives stored at
# app_root/tmp/repositories/project_name/project_name-commit-id.tag.gz
#
def archive_repo(ref)
ref = ref || self.root_ref
commit = self.commit(ref)
return nil unless commit
# Build file path
file_name = self.path_with_namespace.gsub("/","_") + "-" + commit.id.to_s + ".tar.gz"
storage_path = Rails.root.join("tmp", "repositories")
file_path = File.join(storage_path, self.path_with_namespace, file_name)
# Put files into a directory before archiving
prefix = File.basename(self.path_with_namespace) + "/"
# Create file if not exists
unless File.exists?(file_path)
FileUtils.mkdir_p File.dirname(file_path)
file = self.repo.archive_to_file(ref, prefix, file_path)
end
file_path
end
# Return repo size in megabytes
# Cached in redis
def size
Rails.cache.fetch(cache_key(:size)) do
size = popen('du -s', path_to_repo).first.strip.to_i
(size.to_f / 1024).round(2)
end
end
def expire_cache
Rails.cache.delete(cache_key(:size))
end
def cache_key(type)
"#{type}:#{path_with_namespace}"
end end
end end

View file

@ -26,12 +26,4 @@ class Tree
def empty? def empty?
data.blank? data.blank?
end end
def up_dir?
path.present?
end
def readme
@readme ||= contents.find { |c| c.is_a?(Grit::Blob) and c.name =~ /^readme/i }
end
end end

View file

@ -33,7 +33,6 @@
# can_create_team :boolean default(TRUE), not null # can_create_team :boolean default(TRUE), not null
# state :string(255) # state :string(255)
# color_scheme_id :integer default(1), not null # color_scheme_id :integer default(1), not null
# notification_level :integer default(1), not null
# #
class User < ActiveRecord::Base class User < ActiveRecord::Base
@ -47,13 +46,6 @@ class User < ActiveRecord::Base
attr_accessor :force_random_password attr_accessor :force_random_password
# Virtual attribute for authenticating by either username or email
attr_accessor :login
# Add login to attr_accessible
attr_accessible :login
# #
# Relations # Relations
# #
@ -109,9 +101,6 @@ class User < ActiveRecord::Base
format: { with: Gitlab::Regex.username_regex, format: { with: Gitlab::Regex.username_regex,
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
validates :notification_level,
inclusion: { in: Notification.notification_levels },
presence: true
validate :namespace_uniq, if: ->(user) { user.username_changed? } validate :namespace_uniq, if: ->(user) { user.username_changed? }
@ -151,16 +140,6 @@ class User < ActiveRecord::Base
# Class methods # Class methods
# #
class << self class << self
# Devise method overriden to allow sing in with email or username
def find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
where(conditions).where(["lower(username) = :value OR lower(email) = :value", { value: login.downcase }]).first
else
where(conditions).first
end
end
def filter filter_name def filter filter_name
case filter_name case filter_name
when "admins"; self.admins when "admins"; self.admins
@ -212,10 +191,6 @@ class User < ActiveRecord::Base
username username
end end
def notification
@notification ||= Notification.new(self)
end
def generate_password def generate_password
if self.force_random_password if self.force_random_password
self.password = self.password_confirmation = Devise.friendly_token.first(8) self.password = self.password_confirmation = Devise.friendly_token.first(8)

View file

@ -11,7 +11,7 @@
# #
class UsersProject < ActiveRecord::Base class UsersProject < ActiveRecord::Base
include Gitlab::ShellAdapter include Gitolited
GUEST = 10 GUEST = 10
REPORTER = 20 REPORTER = 20
@ -38,7 +38,7 @@ class UsersProject < ActiveRecord::Base
scope :masters, -> { where(project_access: MASTER) } scope :masters, -> { where(project_access: MASTER) }
scope :in_project, ->(project) { where(project_id: project.id) } scope :in_project, ->(project) { where(project_id: project.id) }
scope :in_projects, ->(projects) { where(project_id: projects.map { |p| p.id }) } scope :in_projects, ->(projects) { where(project_id: project_ids) }
scope :with_user, ->(user) { where(user_id: user.id) } scope :with_user, ->(user) { where(user_id: user.id) }
class << self class << self

View file

@ -79,14 +79,14 @@ class WikiPage
def version def version
return nil unless persisted? return nil unless persisted?
@version ||= Commit.new(Gitlab::Git::Commit.new(@page.version)) @version ||= Commit.new(@page.version)
end end
# Returns an array of Gitlab Commit instances. # Returns an array of Gitlab Commit instances.
def versions def versions
return [] unless persisted? return [] unless persisted?
@page.versions.map { |v| Commit.new(Gitlab::Git::Commit.new(v)) } @page.versions.map { |v| Commit.new(v) }
end end
# Returns the Date that this latest version was # Returns the Date that this latest version was

View file

@ -1,4 +1,4 @@
class ActivityObserver < BaseObserver class ActivityObserver < ActiveRecord::Observer
observe :issue, :merge_request, :note, :milestone observe :issue, :merge_request, :note, :milestone
def after_create(record) def after_create(record)

View file

@ -1,9 +0,0 @@
class BaseObserver < ActiveRecord::Observer
def notification
NotificationService.new
end
def log_info message
Gitlab::AppLogger.info message
end
end

View file

@ -1,30 +1,42 @@
class IssueObserver < BaseObserver class IssueObserver < ActiveRecord::Observer
cattr_accessor :current_user cattr_accessor :current_user
def after_create(issue) def after_create(issue)
notification.new_issue(issue, current_user) if issue.assignee && issue.assignee != current_user
Notify.delay.new_issue_email(issue.id)
end
end end
def after_close(issue, transition) def after_close(issue, transition)
notification.close_issue(issue, current_user) send_reassigned_email(issue) if issue.is_being_reassigned?
create_note(issue) create_note(issue)
end end
def after_reopen(issue, transition) def after_reopen(issue, transition)
send_reassigned_email(issue) if issue.is_being_reassigned?
create_note(issue) create_note(issue)
end end
def after_update(issue) def after_update(issue)
if issue.is_being_reassigned? send_reassigned_email(issue) if issue.is_being_reassigned?
notification.reassigned_issue(issue, current_user)
end
end end
protected protected
# Create issue note with service comment like 'Status changed to closed'
def create_note(issue) def create_note(issue)
Note.create_status_change_note(issue, current_user, issue.state) Note.create_status_change_note(issue, current_user, issue.state)
[issue.author, issue.assignee].compact.uniq.each do |recipient|
Notify.delay.issue_status_changed_email(recipient.id, issue.id, issue.state, current_user.id)
end
end
def send_reassigned_email(issue)
recipient_ids = [issue.assignee_id, issue.assignee_id_was].keep_if {|id| id && id != current_user.id }
recipient_ids.each do |recipient_id|
Notify.delay.reassigned_issue_email(recipient_id, issue.id, issue.assignee_id_was)
end
end end
end end

View file

@ -1,4 +1,6 @@
class KeyObserver < BaseObserver class KeyObserver < ActiveRecord::Observer
include Gitolited
def after_save(key) def after_save(key)
GitlabShellWorker.perform_async( GitlabShellWorker.perform_async(
:add_key, :add_key,
@ -6,7 +8,8 @@ class KeyObserver < BaseObserver
key.key key.key
) )
notification.new_key(key) # Notify about ssh key being added
Notify.delay.new_ssh_key_email(key.id) if key.user
end end
def after_destroy(key) def after_destroy(key)

View file

@ -1,25 +1,36 @@
class MergeRequestObserver < BaseObserver class MergeRequestObserver < ActiveRecord::Observer
cattr_accessor :current_user cattr_accessor :current_user
def after_create(merge_request) def after_create(merge_request)
notification.new_merge_request(merge_request, current_user) if merge_request.assignee && merge_request.assignee != current_user
Notify.delay.new_merge_request_email(merge_request.id)
end
end end
def after_close(merge_request, transition) def after_close(merge_request, transition)
send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
Note.create_status_change_note(merge_request, current_user, merge_request.state) Note.create_status_change_note(merge_request, current_user, merge_request.state)
notification.close_mr(merge_request, current_user)
end
def after_merge(merge_request, transition)
notification.merge_mr(merge_request)
end end
def after_reopen(merge_request, transition) def after_reopen(merge_request, transition)
send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
Note.create_status_change_note(merge_request, current_user, merge_request.state) Note.create_status_change_note(merge_request, current_user, merge_request.state)
end end
def after_update(merge_request) def after_update(merge_request)
notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned? send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
end
protected
def send_reassigned_email(merge_request)
recipients_ids = merge_request.assignee_id_was, merge_request.assignee_id
recipients_ids.delete current_user.id
recipients_ids.each do |recipient_id|
Notify.delay.reassigned_merge_request_email(recipient_id, merge_request.id, merge_request.assignee_id_was)
end
end end
end end

View file

@ -1,5 +1,38 @@
class NoteObserver < BaseObserver class NoteObserver < ActiveRecord::Observer
def after_create(note) def after_create(note)
notification.new_note(note) send_notify_mails(note)
end
protected
def send_notify_mails(note)
if note.notify
notify_team(note)
elsif note.notify_author
# Notify only author of resource
if note.commit_author
Notify.delay.note_commit_email(note.commit_author.id, note.id)
end
else
# Otherwise ignore it
nil
end
end
# Notifies the whole team except the author of note
def notify_team(note)
# Note: wall posts are not "attached" to anything, so fall back to "Wall"
noteable_type = note.noteable_type.presence || "Wall"
notify_method = "note_#{noteable_type.underscore}_email".to_sym
if Notify.respond_to? notify_method
team_without_note_author(note).map do |u|
Notify.delay.send(notify_method, u.id, note.id)
end
end
end
def team_without_note_author(note)
note.project.users.reject { |u| u.id == note.author.id }
end end
end end

View file

@ -1,4 +1,4 @@
class ProjectObserver < BaseObserver class ProjectObserver < ActiveRecord::Observer
def after_create(project) def after_create(project)
GitlabShellWorker.perform_async( GitlabShellWorker.perform_async(
:add_repository, :add_repository,
@ -27,4 +27,10 @@ class ProjectObserver < BaseObserver
log_info("Project \"#{project.name}\" was removed") log_info("Project \"#{project.name}\" was removed")
end end
protected
def log_info message
Gitlab::AppLogger.info message
end
end end

View file

@ -1,4 +1,4 @@
class SystemHookObserver < BaseObserver class SystemHookObserver < ActiveRecord::Observer
observe :user, :project, :users_project observe :user, :project, :users_project
def after_create(model) def after_create(model)

View file

@ -1,8 +1,9 @@
class UserObserver < BaseObserver class UserObserver < ActiveRecord::Observer
def after_create(user) def after_create(user)
log_info("User \"#{user.name}\" (#{user.email}) was created") log_info("User \"#{user.name}\" (#{user.email}) was created")
notification.new_user(user) # Dont email omniauth created users
Notify.delay.new_user_email(user.id, user.password) unless user.extern_uid?
end end
def after_destroy user def after_destroy user
@ -18,4 +19,10 @@ class UserObserver < BaseObserver
end end
end end
end end
protected
def log_info message
Gitlab::AppLogger.info message
end
end end

View file

@ -1,6 +1,7 @@
class UsersProjectObserver < BaseObserver class UsersProjectObserver < ActiveRecord::Observer
def after_commit(users_project) def after_commit(users_project)
return if users_project.destroyed? return if users_project.destroyed?
Notify.delay.project_access_granted_email(users_project.id)
end end
def after_create(users_project) def after_create(users_project)
@ -9,12 +10,6 @@ class UsersProjectObserver < BaseObserver
action: Event::JOINED, action: Event::JOINED,
author_id: users_project.user.id author_id: users_project.user.id
) )
notification.new_team_member(users_project)
end
def after_update(users_project)
notification.update_team_member(users_project)
end end
def after_destroy(users_project) def after_destroy(users_project)

View file

@ -1,197 +0,0 @@
# NotificationService class
#
# Used for notifing users with emails about different events
#
# Ex.
# NotificationService.new.new_issue(issue, current_user)
#
class NotificationService
# Always notify user about ssh key added
# only if ssh key is not deploy key
#
# This is security email so it will be sent
# even if user disabled notifications
def new_key(key)
if key.user
Notify.delay.new_ssh_key_email(key.id)
end
end
# When create an issue we should send next emails:
#
# * issue assignee if his notification level is not Disabled
# * project team members with notification level higher then Participating
#
def new_issue(issue, current_user)
new_resource_email(issue, 'new_issue_email')
end
# When we close an issue we should send next emails:
#
# * issue author if his notification level is not Disabled
# * issue assignee if his notification level is not Disabled
# * project team members with notification level higher then Participating
#
def close_issue(issue, current_user)
close_resource_email(issue, current_user, 'closed_issue_email')
end
# When we reassign an issue we should send next emails:
#
# * issue old assignee if his notification level is not Disabled
# * issue new assignee if his notification level is not Disabled
#
def reassigned_issue(issue, current_user)
reassign_resource_email(issue, current_user, 'reassigned_issue_email')
end
# When create a merge request we should send next emails:
#
# * mr assignee if his notification level is not Disabled
#
def new_merge_request(merge_request, current_user)
new_resource_email(merge_request, 'new_merge_request_email')
end
# When we reassign a merge_request we should send next emails:
#
# * merge_request old assignee if his notification level is not Disabled
# * merge_request assignee if his notification level is not Disabled
#
def reassigned_merge_request(merge_request, current_user)
reassign_resource_email(merge_request, current_user, 'reassigned_merge_request_email')
end
# When we close a merge request we should send next emails:
#
# * merge_request author if his notification level is not Disabled
# * merge_request assignee if his notification level is not Disabled
# * project team members with notification level higher then Participating
#
def close_mr(merge_request, current_user)
close_resource_email(merge_request, current_user, 'closed_merge_request_email')
end
# When we merge a merge request we should send next emails:
#
# * merge_request author if his notification level is not Disabled
# * merge_request assignee if his notification level is not Disabled
# * project team members with notification level higher then Participating
#
def merge_mr(merge_request)
recipients = reject_muted_users([merge_request.author, merge_request.assignee])
recipients = recipients.concat(project_watchers(merge_request.project)).uniq
recipients.each do |recipient|
Notify.delay.merged_merge_request_email(recipient.id, merge_request.id)
end
end
# Notify new user with email after creation
def new_user(user)
# Dont email omniauth created users
Notify.delay.new_user_email(user.id, user.password) unless user.extern_uid?
end
# Notify users on new note in system
#
# TODO: split on methods and refactor
#
def new_note(note)
# ignore wall messages
return true unless note.noteable_type.present?
opts = { noteable_type: note.noteable_type, project_id: note.project_id }
if note.commit_id.present?
opts.merge!(commit_id: note.commit_id)
recipients = [note.commit_author]
else
opts.merge!(noteable_id: note.noteable_id)
target = note.noteable
recipients = []
recipients << target.assignee if target.respond_to?(:assignee)
recipients << target.author if target.respond_to?(:author)
end
# Get users who left comment in thread
recipients = recipients.concat(User.where(id: Note.where(opts).pluck(:author_id)))
# Merge project watchers
recipients = recipients.concat(project_watchers(note.project)).compact.uniq
# Reject mutes users
recipients = reject_muted_users(recipients)
# Reject author
recipients.delete(note.author)
# build notify method like 'note_commit_email'
notify_method = "note_#{note.noteable_type.underscore}_email".to_sym
recipients.each do |recipient|
Notify.delay.send(notify_method, recipient.id, note.id)
end
end
def new_team_member(users_project)
Notify.delay.project_access_granted_email(users_project.id)
end
def update_team_member(users_project)
Notify.delay.project_access_granted_email(users_project.id)
end
protected
# Get project users with WATCH notification level
def project_watchers(project)
project.users.where(notification_level: Notification::N_WATCH)
end
# Remove users with disabled notifications from array
# Also remove duplications and nil recipients
def reject_muted_users(users)
users.compact.uniq.reject do |user|
user.notification.disabled?
end
end
def new_resource_email(target, method)
recipients = reject_muted_users([target.assignee])
recipients = recipients.concat(project_watchers(target.project)).uniq
recipients.delete(target.author)
recipients.each do |recipient|
Notify.delay.send(method, recipient.id, target.id)
end
end
def close_resource_email(target, current_user, method)
recipients = reject_muted_users([target.author, target.assignee])
recipients = recipients.concat(project_watchers(target.project)).uniq
recipients.delete(current_user)
recipients.each do |recipient|
Notify.delay.send(method, recipient.id, target.id, current_user.id)
end
end
def reassign_resource_email(target, current_user, method)
recipients = User.where(id: [target.assignee_id, target.assignee_id_was])
# Add watchers to email list
recipients = recipients.concat(project_watchers(target.project))
# reject users with disabled notifications
recipients = reject_muted_users(recipients)
# Reject me from recipients if I reassign an item
recipients.delete(current_user)
recipients.each do |recipient|
Notify.delay.send(method, recipient.id, target.id, target.assignee_id_was)
end
end
end

View file

@ -3,9 +3,7 @@
# Used for transfer project to another namespace # Used for transfer project to another namespace
# #
class ProjectTransferService class ProjectTransferService
include Gitlab::ShellAdapter include Gitolited
class TransferError < StandardError; end
attr_accessor :project attr_accessor :project
@ -21,16 +19,14 @@ class ProjectTransferService
project.namespace = new_namespace project.namespace = new_namespace
project.save! project.save!
# Move main repository
unless gitlab_shell.mv_repository(old_path, new_path) unless gitlab_shell.mv_repository(old_path, new_path)
raise TransferError.new('Cannot move project') raise TransferError.new('Cannot move project')
end end
# Move wiki repo also if present
gitlab_shell.mv_repository("#{old_path}.wiki", "#{new_path}.wiki")
true true
end end
rescue => ex
raise Project::TransferError.new(ex.message)
end end
end end

View file

@ -0,0 +1,86 @@
= form_for [:admin, project] do |f|
-if project.errors.any?
.alert.alert-error
%ul
- project.errors.full_messages.each do |msg|
%li= msg
.clearfix.project_name_holder
= f.label :name do
Project name is
.input
= f.text_field :name, placeholder: "Example Project", class: "xxlarge"
- if project.repo_exists?
%fieldset.adv_settings
%legend Advanced settings:
.clearfix
= f.label :path do
Path
.input
= text_field_tag :ppath, @project.repository.path_to_repo, class: "xlarge", disabled: true
.clearfix
= f.label :default_branch, "Default Branch"
.input= f.select(:default_branch, @project.repository.heads.map(&:name), {}, style: "width:210px;")
%fieldset.adv_settings
%legend Features:
.clearfix
= f.label :issues_enabled, "Issues"
.input= f.check_box :issues_enabled
- if Project.issues_tracker.values.count > 1
.clearfix
= f.label :issues_tracker, "Issues tracker", class: 'control-label'
.input= f.select(:issues_tracker, Project.issues_tracker.values, {}, { disabled: !@project.issues_enabled })
.clearfix
= f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label'
.input= f.text_field :issues_tracker_id, class: "xxlarge", disabled: !@project.can_have_issues_tracker_id?
.clearfix
= f.label :merge_requests_enabled, "Merge Requests"
.input= f.check_box :merge_requests_enabled
.clearfix
= f.label :wall_enabled, "Wall"
.input= f.check_box :wall_enabled
.clearfix
= f.label :wiki_enabled, "Wiki"
.input= f.check_box :wiki_enabled
%fieldset.features
%legend Public mode:
.clearfix
= f.label :public do
%span Allow public http clone
.input= f.check_box :public
%fieldset.features
%legend Transfer:
.control-group
= f.label :namespace_id do
%span Namespace
.controls
= f.select :namespace_id, namespaces_options(@project.namespace_id, :all), {}, {class: 'chosen'}
%br
%ul.prepend-top-10.cred
%li Be careful. Changing project namespace can have unintended side effects
%li You can transfer project only to namespaces you can manage
%li You will need to update your local repositories to point to the new location.
.actions
= f.submit 'Save Project', class: "btn btn-save"
= link_to 'Cancel', admin_projects_path, class: "btn btn-cancel"
:javascript
$(function(){
new Projects();
})

View file

@ -0,0 +1,3 @@
%h3.page_title #{@project.name} &rarr; Edit project
%hr
= render 'form', project: @project

View file

@ -52,8 +52,8 @@
%i.icon-lock.cgreen %i.icon-lock.cgreen
= link_to project.name_with_namespace, [:admin, project] = link_to project.name_with_namespace, [:admin, project]
.pull-right .pull-right
= link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" = link_to 'Edit', edit_admin_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
= link_to 'Destroy', [project], confirm: "REMOVE #{project.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove" = link_to 'Destroy', [:admin, project], confirm: "REMOVE #{project.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
- if @projects.blank? - if @projects.blank?
%p.nothing_here_message 0 projects matches %p.nothing_here_message 0 projects matches
- else - else

View file

@ -1,90 +1,129 @@
%h3.page_title %h3.page_title
Project: #{@project.name_with_namespace} Project: #{@project.name_with_namespace}
= link_to edit_project_path(@project), class: "btn pull-right" do = link_to edit_admin_project_path(@project), class: "btn pull-right" do
%i.icon-edit %i.icon-edit
Edit Edit
%hr
.row
.span6 %br
.ui-box %table.zebra-striped
%h5.title %thead
Project info: %tr
%ul.well-list %th Project
%li %th
%span.light Name: %tr
%strong= @project.name %td
%li %b
%span.light Namespace: Name:
%strong %td
= @project.name
%tr
%td
%b
Namespace:
%td
- if @project.namespace - if @project.namespace
= link_to @project.namespace.human_name, [:admin, @project.group || @project.owner] = @project.namespace.human_name
- else - else
Global Global
%li %tr
%span.light Owned by: %td
%strong %b
Owned by:
%td
- if @project.owner - if @project.owner
= link_to @project.owner_name, admin_user_path(@project.owner) = link_to @project.owner_name, admin_user_path(@project.owner)
- else - else
(deleted) (deleted)
%tr
%li %td
%span.light Created by: %b
%strong Created by:
%td
= @project.creator.try(:name) || '(deleted)' = @project.creator.try(:name) || '(deleted)'
%tr
%li %td
%span.light Created at: %b
%strong Created at:
%td
= @project.created_at.stamp("March 1, 1999") = @project.created_at.stamp("March 1, 1999")
%tr
%li %td
%span.light http: %b
%strong Smart HTTP:
%td
= link_to @project.http_url_to_repo = link_to @project.http_url_to_repo
%li %tr
%span.light ssh: %td
%strong %b
SSH:
%td
= link_to @project.ssh_url_to_repo = link_to @project.ssh_url_to_repo
- if @project.repository.exists?
%li
%span.light fs:
%strong
= @repository.path_to_repo
%li
%span.light last commit:
%strong
= last_commit(@project)
- else
%li
%span.light repository:
%strong.cred
does not exist
%li
%span.light access:
%strong
- if @project.public - if @project.public
%span.cblue %tr.bgred
%i.icon-share %td
Public %b
- else Public Read-Only Code access:
%span.cgreen %td
%i.icon-lock = check_box_tag 'public', nil, @project.public
Private
.span6 - if @repository
.ui-box %table.zebra-striped
%h5.title %thead
%tr
%th Repository
%th
%tr
%td
%b
FS Path:
%td
%code= @repository.path_to_repo
%tr
%td
%b
Last commit at:
%td
= last_commit(@project)
%br
%h5
Team Team
%small %small
(#{@project.users.count}) (#{@project.users.count})
= link_to project_team_index_path(@project), class: "btn btn-tiny" do %br
%i.icon-edit %table.zebra-striped.team_members
Edit Team %thead
%ul.well-list.team_members %tr
%th Name
%th Project Access
%th Repository Access
%th
- @project.users.each do |tm| - @project.users.each do |tm|
%li %tr
%strong %td
= link_to tm.name, admin_user_path(tm) = link_to tm.name, admin_user_path(tm)
%span.pull-right.light= @project.project_access_human(tm) %td= @project.project_access_human(tm)
%td= link_to 'Edit Access', edit_admin_project_member_path(@project, tm), class: "btn btn-small"
%td= link_to 'Remove from team', admin_project_member_path(@project, tm), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove small"
%br
%h5 Add new team member
%br
= form_tag team_update_admin_project_path(@project), class: "bulk_import", method: :put do
%table.zebra-striped
%thead
%tr
%th Users
%th Project Access:
%tr
%td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
%td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"}
%tr
%td= submit_tag 'Add', class: "btn btn-primary"
%td
Read more about project permissions
%strong= link_to "here", help_permissions_path, class: "vlink"

View file

View file

@ -6,7 +6,7 @@
%i.icon-angle-right %i.icon-angle-right
= link_to project_tree_path(@project, @ref) do = link_to project_tree_path(@project, @ref) do
= @project.name = @project.name
- tree_breadcrumbs(@tree, 6) do |link| - @tree.breadcrumbs(6) do |link|
\/ \/
%li= link %li= link
.clear .clear
@ -22,13 +22,13 @@
%table %table
- current_line = 1 - current_line = 1
- @blame.each do |commit, lines| - @blame.each do |commit, lines|
- commit = Commit.new(commit) - commit = CommitDecorator.decorate(Commit.new(commit))
%tr %tr
%td.blame-commit %td.blame-commit
%span.commit %span.commit
= link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id" = link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id"
&nbsp; &nbsp;
= commit_author_link(commit, avatar: true, size: 16) = commit.author_link avatar: true, size: 16
&nbsp; &nbsp;
= link_to_gfm truncate(commit.title, length: 20), project_commit_path(@project, commit.id), class: "row_title" = link_to_gfm truncate(commit.title, length: 20), project_commit_path(@project, commit.id), class: "row_title"
%td.lines.blame-numbers %td.lines.blame-numbers

View file

@ -1,6 +1,5 @@
= render "commit_box" = render "commits/commit_box"
- unless @commit.has_zero_stats?
%p.pull-right.cgray %p.pull-right.cgray
This commit has This commit has
%span.cgreen #{@commit.stats.additions} additions %span.cgreen #{@commit.stats.additions} additions

View file

@ -4,7 +4,7 @@
%strong= link_to "Browse Code »", project_tree_path(@project, commit), class: "right" %strong= link_to "Browse Code »", project_tree_path(@project, commit), class: "right"
%p %p
= link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id" = link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id"
= commit_author_link(commit, avatar: true, size: 24) = commit.author_link avatar: true, size: 24
&nbsp; &nbsp;
= link_to_gfm truncate(commit.title, length: 70), project_commit_path(@project, commit.id), class: "row_title" = link_to_gfm truncate(commit.title, length: 70), project_commit_path(@project, commit.id), class: "row_title"

View file

@ -24,14 +24,14 @@
.row .row
.span5 .span5
.author .author
= commit_author_link(@commit, avatar: true, size: 32) = @commit.author_link avatar: true, size: 32
authored authored
%time{title: @commit.authored_date.stamp("Aug 21, 2011 9:23pm")} %time{title: @commit.authored_date.stamp("Aug 21, 2011 9:23pm")}
#{time_ago_in_words(@commit.authored_date)} ago #{time_ago_in_words(@commit.authored_date)} ago
- if @commit.different_committer? - if @commit.different_committer?
.committer .committer
&rarr; &rarr;
= commit_committer_link(@commit) = @commit.committer_link
committed committed
%time{title: @commit.committed_date.stamp("Aug 21, 2011 9:23pm")} %time{title: @commit.committed_date.stamp("Aug 21, 2011 9:23pm")}
#{time_ago_in_words(@commit.committed_date)} ago #{time_ago_in_words(@commit.committed_date)} ago

View file

@ -2,7 +2,7 @@
- if @path.present? - if @path.present?
%ul.breadcrumb %ul.breadcrumb
= commits_breadcrumbs = breadcrumbs
%div{id: dom_id(@project)} %div{id: dom_id(@project)}
#commits-list= render "commits" #commits-list= render "commits"

View file

@ -16,7 +16,7 @@
%div.ui-box %div.ui-box
%h5.title %h5.title
Commits (#{@commits.count}) Commits (#{@commits.count})
%ul.well-list= render Commit.decorate(@commits) %ul.well-list= render @commits
- unless @diffs.empty? - unless @diffs.empty?
%h4 Diff %h4 Diff

View file

@ -1,11 +1,11 @@
.ui-box .ui-box
%h5.title %h5.title
Groups Groups
%span.light %small
(#{groups.count}) (#{groups.count})
- if current_user.can_create_group? - if current_user.can_create_group?
%span.pull-right %span.pull-right
= link_to new_group_path, class: "btn btn-small" do = link_to new_group_path, class: "btn btn-tiny info" do
%i.icon-plus %i.icon-plus
New Group New Group
%ul.well-list %ul.well-list

View file

@ -1,11 +1,11 @@
.ui-box .ui-box
%h5.title %h5.title
Projects Projects
%span.light %small
(#{@projects_count}) (#{@projects_count})
- if current_user.can_create_project? - if current_user.can_create_project?
%span.pull-right %span.pull-right
= link_to new_project_path, class: "btn btn-small" do = link_to new_project_path, class: "btn btn-tiny info" do
%i.icon-plus %i.icon-plus
New Project New Project

View file

@ -1,10 +1,10 @@
.ui-box.teams-box .ui-box.teams-box
%h5.title %h5.title
Teams Teams
%span.light %small
(#{teams.count}) (#{teams.count})
%span.pull-right %span.pull-right
= link_to new_team_path, class: "btn btn-small" do = link_to new_team_path, class: "btn btn-tiny info" do
%i.icon-plus %i.icon-plus
New Team New Team
%ul.well-list %ul.well-list

View file

@ -1,19 +1,19 @@
- if ldap_enable? - if ldap_enable?
= render partial: 'devise/sessions/new_ldap' = render :partial => 'devise/sessions/new_ldap'
- else - else
= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: "login-box" }) do |f| = form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f|
= image_tag "login-logo.png", width: "304", height: "66", class: "login-logo", alt: "Login Logo" = image_tag "login-logo.png", :width => "304", :height => "66", :class => "login-logo", :alt => "Login Logo"
= f.text_field :login, class: "text top", placeholder: "Username or Email", autofocus: "autofocus" = f.email_field :email, :class => "text top", :placeholder => "Email", :autofocus => "autofocus"
= f.password_field :password, class: "text bottom", placeholder: "Password" = f.password_field :password, :class => "text bottom", :placeholder => "Password"
- if devise_mapping.rememberable? - if devise_mapping.rememberable?
.clearfix.inputs-list .clearfix.inputs-list
%label.checkbox.remember_me{for: "user_remember_me"} %label.checkbox.remember_me{:for => "user_remember_me"}
= f.check_box :remember_me = f.check_box :remember_me
%span Remember me %span Remember me
%br/ %br/
= f.submit "Sign in", class: "btn-create btn" = f.submit "Sign in", :class => "btn-create btn"
.pull-right .pull-right
= link_to "Forgot your password?", new_password_path(resource_name), class: "btn" = link_to "Forgot your password?", new_password_path(resource_name), :class => "btn"
%br/ %br/
- if Gitlab.config.gitlab.signup_enabled - if Gitlab.config.gitlab.signup_enabled
%hr/ %hr/

View file

@ -1,3 +1,4 @@
- commit = CommitDecorator.decorate(commit)
%li.commit %li.commit
%p %p
= link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id" = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id"

View file

@ -13,7 +13,7 @@
}, },
time: c.time, time: c.time,
space: c.spaces.first, space: c.spaces.first,
refs: get_refs(c), refs: join_with_space(c.refs),
id: c.sha, id: c.sha,
date: c.date, date: c.date,
message: c.message, message: c.message,

View file

@ -4,8 +4,24 @@
%body{class: "#{app_theme} admin"} %body{class: "#{app_theme} admin"}
= render "layouts/head_panel", title: "Admin area" = render "layouts/head_panel", title: "Admin area"
= render "layouts/flash" = render "layouts/flash"
%nav.main-nav
.container= render 'layouts/nav/admin'
.container .container
%ul.main_menu
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
= link_to admin_root_path, title: "Stats" do
%i.icon-home
= nav_link(controller: :projects) do
= link_to "Projects", admin_projects_path
= nav_link(controller: :teams) do
= link_to "Teams", admin_teams_path
= nav_link(controller: :groups) do
= link_to "Groups", admin_groups_path
= nav_link(controller: :users) do
= link_to "Users", admin_users_path
= nav_link(controller: :logs) do
= link_to "Logs", admin_logs_path
= nav_link(controller: :hooks) do
= link_to "Hooks", admin_hooks_path
= nav_link(controller: :resque) do
= link_to "Background Jobs", admin_resque_path
.content= yield .content= yield

View file

@ -4,8 +4,25 @@
%body{class: "#{app_theme} application"} %body{class: "#{app_theme} application"}
= render "layouts/head_panel", title: "Dashboard" = render "layouts/head_panel", title: "Dashboard"
= render "layouts/flash" = render "layouts/flash"
%nav.main-nav
.container= render 'layouts/nav/dashboard'
.container .container
%ul.main_menu
= nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do
= link_to root_path, title: "Home" do
%i.icon-home
= nav_link(path: 'dashboard#projects') do
= link_to projects_dashboard_path do
Projects
= nav_link(path: 'dashboard#issues') do
= link_to issues_dashboard_path do
Issues
%span.count= current_user.assigned_issues.opened.count
= nav_link(path: 'dashboard#merge_requests') do
= link_to merge_requests_dashboard_path do
Merge Requests
%span.count= current_user.cared_merge_requests.opened.count
= nav_link(path: 'search#show') do
= link_to "Search", search_path
= nav_link(controller: :help) do
= link_to "Help", help_path
.content= yield .content= yield

View file

@ -4,8 +4,25 @@
%body{class: "#{app_theme} application"} %body{class: "#{app_theme} application"}
= render "layouts/head_panel", title: "group: #{@group.name}" = render "layouts/head_panel", title: "group: #{@group.name}"
= render "layouts/flash" = render "layouts/flash"
%nav.main-nav
.container= render 'layouts/nav/group'
.container .container
%ul.main_menu
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: "Home" do
%i.icon-home
= nav_link(path: 'groups#issues') do
= link_to issues_group_path(@group) do
Issues
%span.count= current_user.assigned_issues.opened.of_group(@group).count
= nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group) do
Merge Requests
%span.count= current_user.cared_merge_requests.opened.of_group(@group).count
= nav_link(path: 'groups#people') do
= link_to "People", people_group_path(@group)
- if can?(current_user, :manage_group, @group)
= nav_link(path: 'groups#edit') do
= link_to edit_group_path(@group), class: "tab " do
Settings
.content= yield .content= yield

View file

@ -1,19 +0,0 @@
%ul
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
= link_to admin_root_path, title: "Stats" do
%i.icon-home
= nav_link(controller: :projects) do
= link_to "Projects", admin_projects_path
= nav_link(controller: :teams) do
= link_to "Teams", admin_teams_path
= nav_link(controller: :groups) do
= link_to "Groups", admin_groups_path
= nav_link(controller: :users) do
= link_to "Users", admin_users_path
= nav_link(controller: :logs) do
= link_to "Logs", admin_logs_path
= nav_link(controller: :hooks) do
= link_to "Hooks", admin_hooks_path
= nav_link(controller: :resque) do
= link_to "Background Jobs", admin_resque_path

View file

@ -1,20 +0,0 @@
%ul
= nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do
= link_to root_path, title: "Home" do
%i.icon-home
= nav_link(path: 'dashboard#projects') do
= link_to projects_dashboard_path do
Projects
= nav_link(path: 'dashboard#issues') do
= link_to issues_dashboard_path do
Issues
%span.count= current_user.assigned_issues.opened.count
= nav_link(path: 'dashboard#merge_requests') do
= link_to merge_requests_dashboard_path do
Merge Requests
%span.count= current_user.cared_merge_requests.opened.count
= nav_link(path: 'search#show') do
= link_to "Search", search_path
= nav_link(controller: :help) do
= link_to "Help", help_path

View file

@ -1,20 +0,0 @@
%ul
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: "Home" do
%i.icon-home
= nav_link(path: 'groups#issues') do
= link_to issues_group_path(@group) do
Issues
%span.count= current_user.assigned_issues.opened.of_group(@group).count
= nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group) do
Merge Requests
%span.count= current_user.cared_merge_requests.opened.of_group(@group).count
= nav_link(path: 'groups#people') do
= link_to "People", people_group_path(@group)
- if can?(current_user, :manage_group, @group)
= nav_link(path: 'groups#edit') do
= link_to edit_group_path(@group), class: "tab " do
Settings

View file

@ -1,17 +0,0 @@
%ul
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: "Profile" do
%i.icon-home
= nav_link(path: 'profiles#account') do
= link_to "Account", account_profile_path
= nav_link(controller: :notifications) do
= link_to "Notifications", profile_notifications_path
= nav_link(controller: :keys) do
= link_to keys_path do
SSH Keys
%span.count= current_user.keys.count
= nav_link(path: 'profiles#design') do
= link_to "Design", design_profile_path
= nav_link(path: 'profiles#history') do
= link_to "History", history_profile_path

Some files were not shown because too many files have changed in this diff Show more