-# Contact & support
+# Contribute to GitLab
-If you want quick help, head over to our [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq).
-Otherwise you can follow our [Issue Submission Guide](https://github.com/gitlabhq/gitlabhq/wiki/Issue-Submission-Guide) for a more systematic and thorough guide to solving your issues.
+If you have a question or want to contribute to GitLab this guide show you the appropriate channel to use.
+## Ruling out common errors
+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.
-# Contribute to GitLab
+## 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 ...
-## Recipes
+* 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
-We collect user submitted installation scripts and config file templates for platforms we don't support officially.
-We believe there is merit in allowing a certain amount of diversity.
-You can get and submit your solution to running/configuring GitLab with your favorite OS/distro, database, web server, cloud hoster, configuration management tool, etc.
+**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.
-Help us improve the collection of [GitLab Recipes](https://github.com/gitlabhq/gitlab-recipes/)
+## Paid support
+Community support in the [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq) is done by volunteers. Paid support is available from [GitLab.com](http://blog.gitlab.com/services/)
## Feature suggestions
-Follow the [Issue Submission Guide](https://github.com/gitlabhq/gitlabhq/wiki/Issue-Submission-Guide) and support other peoples ideas or propose your own.
+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
+
+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:
+
+1. Fork the project on GitHub
+1. Create a feature branch
+1. Write tests and code
+1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
+1. Push the commit to your fork
+1. Submit a pull request
+
+We will accept pull requests if:
+
+* The code has proper tests and all tests pass
+* It can be merged without problems (if not please use: git rebase master)
+* It doesn't break any existing functionality
+* It's quality code that conforms to the [Rails style guide](https://github.com/bbatsov/rails-style-guide) and best practices
+* The description includes a motive for your change and the method you used to achieve it
+* It keeps the GitLab code base clean and well structured
+* We think other users will need the same functionality
+* If it makes changes to the UI the pull request should include screenshots
+
+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).
+
+## Submitting via GitHub's issue tracker
+
+* 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.
+
+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).
+
+### 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.
+
+Please consider the following points when submitting an **issue**:
+* Summarize your issue in one sentence (what happened wrong, when you did/expected something else)
+* Describe your issue in detail (including steps to reproduce)
+* Add logs or screen shots when possible
+* Describe your setup (use relevant parts from `sudo -u gitlab -H bundle exec rake gitlab:env:info`)
-## Code
+## Thank you!
-Follow our [Developer Guide](https://github.com/gitlabhq/gitlabhq/wiki/Developer-Guide) to set you up for hacking on GitLab.
+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.
gem "pg", group: :postgres
# Auth
-gem "devise", "~> 2.1.0"
-gem 'omniauth', "~> 1.1.1"
+gem "devise"
+gem 'omniauth', "~> 1.1.3"
gem 'omniauth-google-oauth2'
gem 'omniauth-twitter'
gem 'omniauth-github'
-# GITLAB patched libs
-gem "grit", git: "https://github.com/gitlabhq/grit.git", ref: '9e98418ce2d654485b967003726aa2706a10060b'
-gem 'grack', git: "https://github.com/gitlabhq/grack.git", ref: 'ba46f3b0845c6a09d488ae6abdce6ede37e227e8'
-gem 'grit_ext', git: "https://github.com/gitlabhq/grit_ext.git", ref: '8e6afc2da821354774aa4d1ee8a1aa2082f84a3e'
+# Extracting information from a git repository
+gem "gitlab-grit", '~> 1.0.0', require: 'grit'
+gem 'grit_ext', '~> 0.6.2'
+
+# Ruby/Rack Git Smart-HTTP Server Handler
+gem 'gitlab-grack', '~> 1.0.0', require: 'grack'
# LDAP Auth
gem 'gitlab_omniauth-ldap', '1.0.2', require: "omniauth-ldap"
gem 'gitlab_yaml_db', '1.0.0', require: "yaml_db"
# Syntax highlighter
-gem "pygments.rb", git: "https://github.com/gitlabhq/pygments.rb.git", branch: "master"
+gem "gitlab-pygments.rb", '~> 0.3.2', require: 'pygments.rb'
# Language detection
gem "github-linguist", "~> 2.3.4" , require: "linguist"
# based on human-friendly examples
gem "stamp"
+# Enumeration fields
+gem 'enumerize'
+
# Pagination
gem "kaminari", "~> 0.14.1"
# HAML
-gem "haml-rails", "~> 0.3.5"
+gem "haml-rails"
# Files attachments
-gem "carrierwave", "~> 0.7.1"
+gem "carrierwave"
# Authorization
gem "six"
gem "github-markup", "~> 0.7.4", require: 'github/markup'
# Servers
-gem "unicorn", "~> 4.4.0"
+gem "unicorn"
# State machine
gem "state_machine"
gem "acts-as-taggable-on", "2.3.3"
# Decorators
-gem "draper", "~> 0.18.0"
+gem "draper"
# Background jobs
gem 'slim'
gem 'sinatra', require: nil
-gem 'sidekiq', '2.7.3'
+gem 'sidekiq'
# HTTP requests
gem "httparty"
gem 'bootstrap-sass', "2.2.1.1"
gem "font-awesome-sass-rails", "~> 3.0.0"
gem "gemoji", "~> 1.2.1", require: 'emoji/railtie'
+ gem "gon"
end
group :development do
gem "capybara", '2.0.2'
gem "pry"
gem "awesome_print"
- gem "database_cleaner", ref: "9f898fc50d87a5d51760f9dcf374bf5ffda21baf", git: "https://github.com/bmabey/database_cleaner.git"
+ gem "database_cleaner"
gem "launchy"
gem 'factory_girl_rails'
GIT
- remote: https://github.com/bmabey/database_cleaner.git
- revision: 9f898fc50d87a5d51760f9dcf374bf5ffda21baf
- ref: 9f898fc50d87a5d51760f9dcf374bf5ffda21baf
- specs:
- database_cleaner (0.9.1)
-
-GIT
remote: https://github.com/ctran/annotate_models.git
revision: be4e26825b521f0b2d86b181e2dff89901aa9b1e
specs:
rake (>= 0.8.7)
GIT
- remote: https://github.com/gitlabhq/grack.git
- revision: ba46f3b0845c6a09d488ae6abdce6ede37e227e8
- ref: ba46f3b0845c6a09d488ae6abdce6ede37e227e8
- specs:
- grack (1.0.0)
- rack (~> 1.4.1)
-
-GIT
- remote: https://github.com/gitlabhq/grit.git
- revision: 9e98418ce2d654485b967003726aa2706a10060b
- ref: 9e98418ce2d654485b967003726aa2706a10060b
- specs:
- grit (2.5.0)
- diff-lcs (~> 1.1)
- mime-types (~> 1.15)
- posix-spawn (~> 0.3.6)
-
-GIT
- remote: https://github.com/gitlabhq/grit_ext.git
- revision: 8e6afc2da821354774aa4d1ee8a1aa2082f84a3e
- ref: 8e6afc2da821354774aa4d1ee8a1aa2082f84a3e
- specs:
- grit_ext (0.6.1)
- charlock_holmes (~> 0.6.9)
-
-GIT
- remote: https://github.com/gitlabhq/pygments.rb.git
- revision: db1da0343adf86b49bdc3add04d02d2e80438d38
- branch: master
- specs:
- pygments.rb (0.3.2)
- posix-spawn (~> 0.3.6)
- yajl-ruby (~> 1.1.0)
-
-GIT
remote: https://github.com/gitlabhq/raphael-rails.git
revision: cb2c92a040b9b941a5f1aa1ea866cc26e944fe58
specs:
addressable (2.3.2)
arel (3.0.2)
awesome_print (1.1.0)
- backports (2.6.5)
+ backports (2.6.7)
bcrypt-ruby (3.0.1)
better_errors (0.3.2)
coderay (>= 1.0.0)
erubis (>= 2.7.0)
- binding_of_caller (0.6.8)
+ binding_of_caller (0.7.1)
+ debug_inspector (>= 0.0.1)
bootstrap-sass (2.2.1.1)
sass (~> 3.2)
builder (3.0.4)
rack-test (>= 0.5.4)
selenium-webdriver (~> 2.0)
xpath (~> 1.0.0)
- carrierwave (0.7.1)
+ carrierwave (0.8.0)
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
celluloid (0.12.4)
connection_pool (1.0.0)
crack (0.3.1)
daemons (1.1.9)
- devise (2.1.2)
+ database_cleaner (0.9.1)
+ debug_inspector (0.0.2)
+ descendants_tracker (0.0.1)
+ devise (2.2.3)
bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.1)
railties (~> 3.1)
warden (~> 1.2.1)
diff-lcs (1.1.3)
- draper (0.18.0)
- actionpack (~> 3.2)
- activesupport (~> 3.2)
+ draper (1.1.0)
+ actionpack (>= 3.0)
+ activesupport (>= 3.0)
+ request_store (~> 1.0.3)
email_spec (1.4.0)
launchy (~> 2.1)
mail (~> 2.2)
+ enumerize (0.5.1)
+ activesupport (>= 3.2)
erubis (2.7.0)
escape_utils (0.2.4)
eventmachine (1.0.0)
factory_girl_rails (4.1.0)
factory_girl (~> 4.1.0)
railties (>= 3.0.0)
- faraday (0.8.4)
+ faraday (0.8.6)
multipart-post (~> 1.1)
faye-websocket (0.4.7)
eventmachine (>= 0.12.0)
font-awesome-sass-rails (3.0.0.1)
railties (>= 3.1.1)
sass-rails (>= 3.1.1)
- foreman (0.60.2)
+ foreman (0.61.0)
thor (>= 0.13.6)
gemoji (1.2.1)
gherkin-ruby (0.2.1)
escape_utils (~> 0.2.3)
mime-types (~> 1.19)
pygments.rb (>= 0.2.13)
- github-markup (0.7.4)
+ github-markup (0.7.5)
+ gitlab-grack (1.0.0)
+ rack (~> 1.4.1)
+ gitlab-grit (1.0.0)
+ diff-lcs (~> 1.1)
+ mime-types (~> 1.15)
+ posix-spawn (~> 0.3.6)
+ gitlab-pygments.rb (0.3.2)
+ posix-spawn (~> 0.3.6)
+ yajl-ruby (~> 1.1.0)
gitlab_meta (5.0)
gitlab_omniauth-ldap (1.0.2)
net-ldap (~> 0.2.2)
pyu-ruby-sasl (~> 0.0.3.1)
rubyntlm (~> 0.1.1)
gitlab_yaml_db (1.0.0)
- grape (0.3.1)
+ gon (4.0.2)
+ grape (0.3.2)
activesupport
- grape-entity (~> 0.2.0)
- hashie (~> 1.2)
+ builder
+ hashie (>= 1.2.0)
multi_json (>= 1.3.2)
- multi_xml
+ multi_xml (>= 0.5.2)
rack
rack-accept
rack-mount
virtus
grape-entity (0.2.0)
+ activesupport
+ multi_json (>= 1.3.2)
+ grit_ext (0.6.2)
+ charlock_holmes (~> 0.6.9)
growl (1.0.3)
guard (1.5.4)
listen (>= 0.4.2)
guard-spinach (0.0.2)
guard (>= 1.1)
spinach
- haml (3.1.7)
- haml-rails (0.3.5)
+ haml (4.0.0)
+ tilt
+ haml-rails (0.4)
actionpack (>= 3.1, < 4.1)
activesupport (>= 3.1, < 4.1)
- haml (~> 3.1)
+ haml (>= 3.1, < 4.1)
railties (>= 3.1, < 4.1)
hashie (1.2.0)
hike (1.2.1)
http_parser.rb (0.5.3)
- httparty (0.9.0)
+ httparty (0.10.2)
multi_json (~> 1.0)
- multi_xml
+ multi_xml (>= 0.5.2)
httpauth (0.2.0)
- i18n (0.6.1)
+ i18n (0.6.4)
journey (1.0.4)
jquery-atwho-rails (0.1.7)
jquery-rails (2.1.3)
kaminari (0.14.1)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
- kgio (2.7.4)
+ kgio (2.8.0)
launchy (2.1.2)
addressable (~> 2.3)
letter_opener (1.0.0)
modernizr (2.6.2)
sprockets (~> 2.0)
multi_json (1.6.1)
- multi_xml (0.5.1)
- multipart-post (1.1.5)
+ multi_xml (0.5.3)
+ multipart-post (1.2.0)
mysql2 (0.3.11)
net-ldap (0.2.2)
nokogiri (1.5.6)
oauth (0.4.7)
- oauth2 (0.8.0)
+ oauth2 (0.8.1)
faraday (~> 0.8)
httpauth (~> 0.1)
jwt (~> 0.1.4)
multi_json (~> 1.0)
rack (~> 1.2)
- omniauth (1.1.1)
+ omniauth (1.1.3)
hashie (~> 1.2)
rack
- omniauth-github (1.0.3)
+ omniauth-github (1.1.0)
omniauth (~> 1.0)
omniauth-oauth2 (~> 1.1)
omniauth-google-oauth2 (0.1.13)
coderay (~> 1.0.5)
method_source (~> 0.8)
slop (~> 3.3.1)
+ pygments.rb (0.4.2)
+ posix-spawn (~> 0.3.6)
+ yajl-ruby (~> 1.1.0)
pyu-ruby-sasl (0.0.3.3)
quiet_assets (1.0.1)
railties (~> 3.1)
rack (>= 1.1.3)
rack-mount (0.8.3)
rack (>= 1.0.0)
- rack-protection (1.3.2)
+ rack-protection (1.4.0)
rack
rack-ssl (1.3.3)
rack
rb-fsevent (0.9.2)
rb-inotify (0.8.8)
ffi (>= 0.5.0)
- rdoc (3.12.1)
+ rdoc (3.12.2)
json (~> 1.4)
redcarpet (2.2.2)
redis (3.0.2)
redis-namespace (1.2.1)
redis (~> 3.0.0)
+ request_store (1.0.5)
rspec (2.12.0)
rspec-core (~> 2.12.0)
rspec-expectations (~> 2.12.0)
multi_json (~> 1.0)
rubyzip
websocket (~> 1.0.4)
- settingslogic (2.0.8)
+ settingslogic (2.0.9)
sexp_processor (4.1.3)
shoulda-matchers (1.3.0)
activesupport (>= 3.0.0)
- sidekiq (2.7.3)
+ sidekiq (2.7.5)
celluloid (~> 0.12.0)
connection_pool (~> 1.0)
multi_json (~> 1)
multi_json (~> 1.0)
simplecov-html (~> 0.7.1)
simplecov-html (0.7.1)
- sinatra (1.3.3)
- rack (~> 1.3, >= 1.3.6)
- rack-protection (~> 1.2)
+ sinatra (1.3.5)
+ rack (~> 1.4)
+ rack-protection (~> 1.3)
tilt (~> 1.3, >= 1.3.3)
six (0.2.0)
slim (1.3.6)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
- stamp (0.3.0)
+ stamp (0.5.0)
state_machine (1.1.2)
temple (0.5.5)
test_after_commit (0.0.1)
eventmachine (>= 0.12.6)
rack (>= 1.0.0)
thor (0.17.0)
- tilt (1.3.3)
+ tilt (1.3.4)
timers (1.1.0)
treetop (1.4.12)
polyglot
uglifier (1.3.0)
execjs (>= 0.3.0)
multi_json (~> 1.0, >= 1.0.2)
- unicorn (4.4.0)
+ unicorn (4.6.2)
kgio (~> 2.6)
rack
raindrops (~> 0.7)
- virtus (0.5.2)
+ virtus (0.5.4)
backports (~> 2.6.1)
+ descendants_tracker (~> 0.0.1)
warden (1.2.1)
rack (>= 1.0)
webmock (1.9.0)
binding_of_caller
bootstrap-sass (= 2.2.1.1)
capybara (= 2.0.2)
- carrierwave (~> 0.7.1)
+ carrierwave
chosen-rails (= 0.9.8)
coffee-rails (~> 3.2.2)
colored
- database_cleaner!
- devise (~> 2.1.0)
- draper (~> 0.18.0)
+ database_cleaner
+ devise
+ draper
email_spec
+ enumerize
factory_girl_rails
ffaker
font-awesome-sass-rails (~> 3.0.0)
git
github-linguist (~> 2.3.4)
github-markup (~> 0.7.4)
+ gitlab-grack (~> 1.0.0)
+ gitlab-grit (~> 1.0.0)
+ gitlab-pygments.rb (~> 0.3.2)
gitlab_meta (= 5.0)
gitlab_omniauth-ldap (= 1.0.2)
gitlab_yaml_db (= 1.0.0)
- grack!
+ gon
grape (~> 0.3.1)
grape-entity (~> 0.2.0)
- grit!
- grit_ext!
+ grit_ext (~> 0.6.2)
growl
guard-rspec
guard-spinach
- haml-rails (~> 0.3.5)
+ haml-rails
httparty
jquery-atwho-rails (= 0.1.7)
jquery-rails (= 2.1.3)
letter_opener
modernizr (= 2.6.2)
mysql2
- omniauth (~> 1.1.1)
+ omniauth (~> 1.1.3)
omniauth-github
omniauth-google-oauth2
omniauth-twitter
pg
poltergeist (= 1.1.0)
pry
- pygments.rb!
quiet_assets (~> 1.0.1)
rack-mini-profiler
rails (= 3.2.12)
seed-fu
settingslogic
shoulda-matchers (= 1.3.0)
- sidekiq (= 2.7.3)
+ sidekiq
simplecov
sinatra
six
therubyracer
thin
uglifier (~> 1.3.0)
- unicorn (~> 4.4.0)
+ unicorn
webmock
-# Welcome to GitLab! Self hosted Git management software
+## GitLab: self hosted Git management software
+![logo](https://raw.github.com/gitlabhq/gitlabhq/master/public/gitlab_logo.png)
-## Badges:
+### GitLab allows you to
+ * keep your code secure on your own server
+ * manage repositories, users and access permissions
+ * communicate though issues, line-comments and wiki's
+ * perform code reviews with merge requests
+
+### GitLab is
+
+* powered by Ruby on Rails
+* completely free and open source (MIT license)
+* used by 10.000 organization to keep their code secure
+
+### Code status
+
+* [![build status](http://ci.gitlab.org/projects/1/status?ref=master)](http://ci.gitlab.org/projects/1?ref=master) ci.gitlab.org (master branch)
+
+* [![build status](https://secure.travis-ci.org/gitlabhq/gitlabhq.png)](https://travis-ci.org/gitlabhq/gitlabhq) travis-ci.org (master branch)
+
+* [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.png)](https://codeclimate.com/github/gitlabhq/gitlabhq)
-* master: travis-ci.org [![build status](https://secure.travis-ci.org/gitlabhq/gitlabhq.png)](https://travis-ci.org/gitlabhq/gitlabhq)a
-* master: ci.gitlab.org [![CI](http://ci.gitlab.org/projects/1/status?ref=master)](http://ci.gitlab.org/projects/1?ref=master)
-* [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/gitlabhq/gitlabhq)
* [![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.png)](https://gemnasium.com/gitlabhq/gitlabhq)
-GitLab is a free project and repository management application
+### Resources
+* 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)
-## Application details
+* GitLab.com: [Homepage](http://blog.gitlab.com/) [Hosted pricing](http://blog.gitlab.com/pricing/) [Services](http://blog.gitlab.com/services/) [Blog](http://blog.gitlab.com/blog/)
-* powered by Ruby on Rails
-* its completely free and open source
-* distributed under the MIT License
+* GitLab CI: [Readme](https://github.com/gitlabhq/gitlab-ci/blob/master/README.md) of the GitLab open-source continuous integration server
-## Requirements
+### Requirements
-* Ubuntu/Debian
+* Ubuntu/Debian*
* ruby 1.9.3+
* MySQL
* git
* gitlab-shell
* redis
-## Install
+* More details are in the [requirements doc](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/requirements.md)
+
+### Installation
+
+You can either follow the "ordinary" Installation guide to install it on a machine or use the Vagrant virtual machine. The Installation guide is recommended to set up a production server. The Vargrant virtual machine is recommended for development since it makes it much easier to set up all the dependencies for integration testing.
+
+* [Installation guide for latest stable release](https://github.com/gitlabhq/gitlabhq/blob/4-2-stable/doc/install/installation.md)
+
+* [Installation guide for the current master branch](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md)
+
+* [Vagrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm)
+
+### Starting
+
+1. The Installation guide contains instructions to download an init script and run that on boot. With the init script you can also start GitLab with:
+
+ sudo service gitlab start
+
+ or
+
+ sudo /etc/init.d/gitlab restart
+
+2. Start it with [Foreman](https://github.com/ddollar/foreman) in development model
+
+ bundle exec foreman start -p 3000
+
+3. Start it manually in development mode
+
+ bundle exec rails s
+ bundle exec rake sidekiq:start
+
+### Running the tests
+
+* Seed the database with
+
+ bundle exec rake db:setup RAILS_ENV=test
+ bundle exec rake db:seed_fu RAILS_ENV=test
+
+* Run all tests
+
+ bundle exec rake gitlab:test
+
+* Rspec unit and functional tests
+
+ bundle exec rake spec
+
+* Spinach integration tests
+
+ bundle exec rake spinach
+
+### Getting help
+
+* [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)
+
+* [Paid support](http://blog.gitlab.com/support/)
+
+* [Paid services](http://blog.gitlab.com/services/)
+
+### 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)
+
+* [Rake tasks](https://github.com/gitlabhq/gitlabhq/tree/master/doc/raketasks)
+
+* [GitLab recipes](https://github.com/gitlabhq/gitlab-recipes)
+
+### Getting in touch
-Checkout [wiki](https://github.com/gitlabhq/gitlabhq/wiki) pages for installation information, migration, etc.
+* [Contributing guide](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md)
-## [Community](http://gitlab.org/community/)
+* [Core team](https://github.com/gitlabhq?tab=members)
-## [Contact](http://gitlab.org/contact/)
+* [Contributors](https://github.com/gitlabhq/gitlabhq/graphs/contributors)
-## Contribute
+* [Leader](https://github.com/randx)
-[Developer Guide](https://github.com/gitlabhq/gitlabhq/wiki/Developer-Guide)
-Want to help - send a pull request.
-We'll accept good pull requests.
+* [Contact page](http://gitlab.org/contact/)
* Replace gitolite with gitlab-shell
* Usability improvements
-* Notification improvements
-
-### v4.2 February 22
-
-* Teams
-
+* Notification improvements
\ No newline at end of file
+++ /dev/null
-Copyright (c) 2010, Jan Gerner (post@yanone.de)\r
-This Font Software is licensed under the SIL Open Font License, Version 1.1.\r
-This license is copied below, and is also available with a FAQ at:\r
-http://scripts.sil.org/OFL\r
-\r
-\r
------------------------------------------------------------\r
-SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\r
------------------------------------------------------------\r
-\r
-PREAMBLE\r
-The goals of the Open Font License (OFL) are to stimulate worldwide\r
-development of collaborative font projects, to support the font creation\r
-efforts of academic and linguistic communities, and to provide a free and\r
-open framework in which fonts may be shared and improved in partnership\r
-with others.\r
-\r
-The OFL allows the licensed fonts to be used, studied, modified and\r
-redistributed freely as long as they are not sold by themselves. The\r
-fonts, including any derivative works, can be bundled, embedded, \r
-redistributed and/or sold with any software provided that any reserved\r
-names are not used by derivative works. The fonts and derivatives,\r
-however, cannot be released under any other type of license. The\r
-requirement for fonts to remain under this license does not apply\r
-to any document created using the fonts or their derivatives.\r
-\r
-DEFINITIONS\r
-"Font Software" refers to the set of files released by the Copyright\r
-Holder(s) under this license and clearly marked as such. This may\r
-include source files, build scripts and documentation.\r
-\r
-"Reserved Font Name" refers to any names specified as such after the\r
-copyright statement(s).\r
-\r
-"Original Version" refers to the collection of Font Software components as\r
-distributed by the Copyright Holder(s).\r
-\r
-"Modified Version" refers to any derivative made by adding to, deleting,\r
-or substituting -- in part or in whole -- any of the components of the\r
-Original Version, by changing formats or by porting the Font Software to a\r
-new environment.\r
-\r
-"Author" refers to any designer, engineer, programmer, technical\r
-writer or other person who contributed to the Font Software.\r
-\r
-PERMISSION & CONDITIONS\r
-Permission is hereby granted, free of charge, to any person obtaining\r
-a copy of the Font Software, to use, study, copy, merge, embed, modify,\r
-redistribute, and sell modified and unmodified copies of the Font\r
-Software, subject to the following conditions:\r
-\r
-1) Neither the Font Software nor any of its individual components,\r
-in Original or Modified Versions, may be sold by itself.\r
-\r
-2) Original or Modified Versions of the Font Software may be bundled,\r
-redistributed and/or sold with any software, provided that each copy\r
-contains the above copyright notice and this license. These can be\r
-included either as stand-alone text files, human-readable headers or\r
-in the appropriate machine-readable metadata fields within text or\r
-binary files as long as those fields can be easily viewed by the user.\r
-\r
-3) No Modified Version of the Font Software may use the Reserved Font\r
-Name(s) unless explicit written permission is granted by the corresponding\r
-Copyright Holder. This restriction only applies to the primary font name as\r
-presented to the users.\r
-\r
-4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font\r
-Software shall not be used to promote, endorse or advertise any\r
-Modified Version, except to acknowledge the contribution(s) of the\r
-Copyright Holder(s) and the Author(s) or with their explicit written\r
-permission.\r
-\r
-5) The Font Software, modified or unmodified, in part or in whole,\r
-must be distributed entirely under this license, and must not be\r
-distributed under any other license. The requirement for fonts to\r
-remain under this license does not apply to any document created\r
-using the Font Software.\r
-\r
-TERMINATION\r
-This license becomes null and void if any of the above conditions are\r
-not met.\r
-\r
-DISCLAIMER\r
-THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\r
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\r
-OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\r
-COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r
-INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\r
-DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r
-FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\r
-OTHER DEALINGS IN THE FONT SOFTWARE.\r
});
} else if (c.space < this.commits[i].space) {
- r.path([
- "M", x - 5, y,
- "l-5-2,0,4,5,-2",
- "L", x - 10, y,
- "L", x - 15, psy,
- "L", cx + 5, psy,
- "L", cx, cy])
- .attr({
- stroke: this.colors[this.commits[i].space],
- "stroke-width": 2
- });
+ if (y == psy) {
+ r.path([
+ "M", x - 5, y,
+ "l-5,-2,0,4,5,-2",
+ "L", x - 10, y,
+ "L", x - 15, psy,
+ "L", cx + 5, psy,
+ "L", cx, cy])
+ .attr({
+ stroke: this.colors[this.commits[i].space],
+ "stroke-width": 2
+ });
+ } else {
+ r.path([
+ "M", x - 3, y - 6,
+ "l-4,-3,4,-2,0,5",
+ "L", x - 5, y - 10,
+ "L", x - 10, psy,
+ "L", cx + 5, psy,
+ "L", cx, cy])
+ .attr({
+ stroke: this.colors[this.commits[i].space],
+ "stroke-width": 2
+ });
+ }
} else {
r.path([
"M", x - 3, y + 6,
}(this);
Raphael.fn.commitTooltip = function(x, y, commit){
- var nameText, idText, messageText
+ var icon, nameText, idText, messageText
, boxWidth = 300
, boxHeight = 200;
- nameText = this.text(x, y + 10, commit.author.name);
+ icon = this.image(commit.author.icon, x, y, 20, 20);
+ nameText = this.text(x + 25, y + 10, commit.author.name);
idText = this.text(x, y + 35, commit.id);
messageText = this.text(x, y + 50, commit.message);
- textSet = this.set(nameText, idText, messageText).attr({
+ textSet = this.set(icon, nameText, idText, messageText).attr({
"text-anchor": "start",
"font": "12px Monaco, monospace"
});
$(@).parents('form').submit()
# Flash
- if (flash = $("#flash-container")).length > 0
- flash.click -> $(@).slideUp("slow")
- flash.slideDown "slow"
- setTimeout (-> flash.slideUp("slow")), 3000
+ if (flash = $(".flash-container")).length > 0
+ flash.click -> $(@).fadeOut()
+ flash.show()
+ setTimeout (-> flash.fadeOut()), 3000
# Disable form buttons while a form is submitting
$('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
# Ref switcher
$('.project-refs-select').on 'change', ->
$(@).parents('form').submit()
+
+ $('#project_issues_enabled').change ->
+ if ($(this).is(':checked') == true)
+ $('#project_issues_tracker').removeAttr('disabled')
+ else
+ $('#project_issues_tracker').attr('disabled', 'disabled')
+
+ $('#project_issues_tracker').change()
+
+ $('#project_issues_tracker').change ->
+ if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled'))
+ $('#project_issues_tracker_id').attr('disabled', 'disabled')
+ else
+ $('#project_issues_tracker_id').removeAttr('disabled')
+
}
/** FLASH message **/
-#flash-container {
- height: 50px;
- position: fixed;
- z-index: 10001;
- top: 0px;
- width: 100%;
- margin-bottom: 15px;
- overflow: hidden;
- background: white;
- cursor: pointer;
- border-bottom: 1px solid #ccc;
- text-align: center;
+.flash-container {
display: none;
+ .alert {
+ cursor: pointer;
+ margin: 0;
+ text-align: center;
+ border-radius: 0;
- h4 {
- color: #666;
- font-size: 18px;
- line-height: 38px;
- padding-top: 5px;
- margin: 2px;
- font-weight: normal;
+ span {
+ font-size: 14px;
+ }
}
}
}
}
-input.git_clone_url {
- width: 325px;
-}
-
.merge-request-form-holder {
select {
width: 300px;
border-color: #DDD;
}
+.well { padding: 15px; }
+
/** HELPERS **/
.nothing_here_message {
text-align: center;
-@font-face{
- font-family: Yanone;
- src: font-url('YanoneKaffeesatz-Light.ttf');
-}
-
/** Typo **/
$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
@mixin header-font {
color: $style_color;
text-shadow: 0 1px 1px #FFF;
- font-family: 'Yanone', sans-serif;
- font-size: 24px;
- line-height: 36px;
+ font-size: 18px;
+ line-height: 42px;
font-weight: normal;
+ letter-spacing: -1px;
+}
+
+@mixin md-typography {
+ code { padding: 0 4px; }
+ p { font-size: 13px; }
+ h1 { font-size: 26px; line-height: 40px; margin: 10px 0;}
+ h2 { font-size: 22px; line-height: 40px; margin: 10px 0;}
+ h3 { font-size: 18px; line-height: 40px; margin: 10px 0;}
+ h4 { font-size: 16px; line-height: 20px; margin: 10px 0;}
+ h5 { font-size: 14px; line-height: 20px; margin: 10px 0;}
+ h6 { font-size: 12px; line-height: 20px; margin: 10px 0;}
}
*
*/
.wiki {
+ @include md-typography;
+
font-size: 13px;
+ line-height: 20px;
- code { padding: 0 4px; }
- p { font-size: 13px; }
- h1 { font-size: 32px; line-height: 40px; margin: 10px 0;}
- h2 { font-size: 26px; line-height: 40px; margin: 10px 0;}
- h3 { font-size: 22px; line-height: 40px; margin: 10px 0;}
- h4 { font-size: 18px; line-height: 20px; margin: 10px 0;}
- h5 { font-size: 14px; line-height: 20px; margin: 10px 0;}
- h6 { font-size: 12px; line-height: 20px; margin: 10px 0;}
.white .highlight pre { background: #f5f5f5; }
ul { margin: 0 0 9px 25px !important; }
}
+
+.md {
+ @include md-typography;
+}
.black .highlight {
- background-color: #333;
pre {
+ background-color: #333;
color: #eee;
- background: inherit;
}
.hll { display: block; background-color: darken($hover, 65%) }
}
}
.line_content {
+ display: block;
white-space: pre;
- height: 14px;
+ height: 18px;
margin: 0px;
padding: 0px;
border: none;
color: #666;
}
.event-note {
- padding-top: 5px;
- padding-left: 5px;
- display: inline-block;
color: #555;
+ margin-top: 5px;
+ margin-left: 40px;
.note-file-attach {
- margin-left: -25px;
- float: left;
.note-image-attach {
+ margin-top: 4px;
margin-left: 0px;
max-width: 200px;
}
color: #777;
float: left;
font-size: 16px;
- line-height: 18px;
- margin: 5px;
+ line-height: 16px;
+ margin-right: 5px;
}
}
.avatar {
position: relative;
float: left;
margin: 0;
- margin-left: 15px;
+ margin-left: 10px;
@include header-font;
}
/* Login Page */
body.login-page{
- padding-top: 7%;
- background: #666;
+ background: #EEE;
+ .container .content { padding-top: 5%; }
}
.login-box{
margin-top: -20px;
}
.note-body {
+ @include md-typography;
margin-left: 45px;
}
.note-header {
border: 1px solid #BBB;
box-shadow: none;
margin-left: -1px;
+ background: #FFF;
}
}
@issues = case params[:status]
when issues_filter[:all] then @project.issues
when issues_filter[:closed] then @project.issues.closed
- when issues_filter[:to_me] then @project.issues.opened.assigned(current_user)
+ when issues_filter[:to_me] then @project.issues.assigned(current_user)
+ when issues_filter[:by_me] then @project.issues.authored(current_user)
else @project.issues.opened
end
@issues = @issues.tagged_with(params[:label_name]) if params[:label_name].present?
- @issues = @issues.includes(:author, :project).order("updated_at")
+ @issues = @issues.includes(:author, :project)
# Filter by specific assignee_id (or lack thereof)?
if params[:assignee_id].present?
class Admin::Teams::MembersController < Admin::Teams::ApplicationController
def new
@users = User.potential_team_members(user_team)
- @users = UserDecorator.decorate @users
+ @users = UserDecorator.decorate_collection @users
end
def create
end
def unblock
- if admin_user.update_attribute(:blocked, false)
+ if admin_user.activate
redirect_to :back, alert: "Successfully unblocked"
else
redirect_to :back, alert: "Error occured. User was not unblocked"
before_filter :add_abilities
before_filter :dev_tools if Rails.env == 'development'
before_filter :default_headers
+ before_filter :add_gon_variables
protect_from_forgery
end
def reject_blocked!
- if current_user && current_user.blocked
+ if current_user && current_user.blocked?
sign_out current_user
flash[:alert] = "Your account is blocked. Retry when an admin unblock it."
redirect_to new_user_session_path
end
def after_sign_in_path_for resource
- if resource.is_a?(User) && resource.respond_to?(:blocked) && resource.blocked
+ if resource.is_a?(User) && resource.respond_to?(:blocked?) && resource.blocked?
sign_out resource
flash[:alert] = "Your account is blocked. Retry when an admin unblock it."
new_user_session_path
headers['X-Frame-Options'] = 'DENY'
headers['X-XSS-Protection'] = '1; mode=block'
end
+
+ def add_gon_variables
+ gon.default_issues_tracker = Project.issues_tracker.default_value
+ end
end
@limit, @offset = (params[:limit] || 40), (params[:offset] || 0)
@commits = @repo.commits(@ref, @path, @limit, @offset)
- @commits = CommitDecorator.decorate(@commits)
+ @commits = CommitDecorator.decorate_collection(@commits)
respond_to do |format|
format.html # index.html.erb
@refs_are_same = result[:same]
@line_notes = []
- @commits = CommitDecorator.decorate(@commits)
+ @commits = CommitDecorator.decorate_collection(@commits)
end
def create
class GraphController < ProjectResourceController
include ExtractsPath
+ include ApplicationHelper
# Authorize
before_filter :authorize_read_project!
respond_to do |format|
format.html
format.json do
- graph = Gitlab::Graph::JsonBuilder.new(project, @ref, @commit)
+ graph = Graph::JsonBuilder.new(project, @ref, @commit)
+ graph.commits.each do |c|
+ c.icon = gravatar_icon(c.author.email)
+ end
render :json => graph.to_json
end
end
end
def automerge
- return access_denied! unless can?(current_user, :accept_mr, @project)
+ return access_denied! unless allowed_to_merge?
+
if @merge_request.opened? && @merge_request.can_be_merged?
@merge_request.should_remove_source_branch = params[:should_remove_source_branch]
@merge_request.automerge!(current_user)
# Get commits from repository
# or from cache if already merged
@commits = @merge_request.commits
- @commits = CommitDecorator.decorate(@commits)
+ @commits = CommitDecorator.decorate_collection(@commits)
+
+ @allowed_to_merge = allowed_to_merge?
+ @show_merge_controls = @merge_request.opened? && @commits.any? && @allowed_to_merge
+ end
+
+ def allowed_to_merge?
+ action = if project.protected_branch?(@merge_request.target_branch)
+ :push_code_to_protected_branches
+ else
+ :push_code
+ end
+
+ can?(current_user, action, @project)
end
end
def show
@issues = @milestone.issues
- @users = UserDecorator.decorate(@milestone.participants)
+ @users = UserDecorator.decorate_collection(@milestone.participants)
@merge_requests = @milestone.merge_requests
respond_to do |format|
-require Rails.root.join('lib', 'gitlab', 'graph', 'json_builder')
-
class ProjectsController < ProjectResourceController
skip_before_filter :project, only: [:new, :create]
skip_before_filter :repository, only: [:new, :create]
def new
@users = User.potential_team_members(user_team)
- @users = UserDecorator.decorate @users
+ @users = UserDecorator.decorate_collection @users
end
def create
-class ApplicationDecorator < Draper::Base
+class ApplicationDecorator < Draper::Decorator
+ delegate_all
# Lazy Helpers
# PRO: Call Rails helpers without the h. proxy
# ex: number_to_currency(model.price)
# CON: Add a bazillion methods into your decorator's namespace
# and probably sacrifice performance/memory
- #
+ #
# Enable them by uncommenting this line:
# lazy_helpers
# Shared Decorations
# Consider defining shared methods common to all your models.
- #
+ #
# Example: standardize the formatting of timestamps
#
# def formatted_timestamp(time)
- # h.content_tag :span, time.strftime("%a %m/%d/%y"),
- # class: 'timestamp'
+ # h.content_tag :span, time.strftime("%a %m/%d/%y"),
+ # class: 'timestamp'
# end
- #
+ #
# def created_at
# formatted_timestamp(model.created_at)
# end
- #
+ #
# def updated_at
# formatted_timestamp(model.updated_at)
# end
end
def image_url(source)
- root_url + path_to_image(source)
+ # prevent relative_root_path being added twice (it's part of root_url and path_to_image)
+ root_url.sub(/#{root_path}$/, path_to_image(source))
end
alias_method :url_to_image, :image_url
all: "all",
closed: "closed",
to_me: "assigned-to-me",
+ by_me: "created-by-me",
open: "open"
}
end
def issues_active_milestones
@project.milestones.active.order("id desc").all
end
+
+ def url_for_project_issues
+ return "" if @project.nil?
+
+ if @project.used_default_issues_tracker?
+ project_issues_filter_path(@project)
+ else
+ 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_issue(issue_id)
+ return "" if @project.nil?
+
+ if @project.used_default_issues_tracker?
+ url = project_issue_url project_id: @project, id: issue_id
+ else
+ url = Settings[:issues_tracker][@project.issues_tracker]["issues_url"]
+ url.gsub(':id', issue_id.to_s)
+ .gsub(':project_id', @project.id.to_s)
+ .gsub(':issues_tracker_id', @project.issues_tracker_id.to_s)
+ end
+ end
+
+ def title_for_issue(issue_id)
+ return "" if @project.nil?
+
+ if @project.used_default_issues_tracker? && issue = @project.issues.where(id: issue_id).first
+ issue.title
+ else
+ ""
+ end
+ end
end
tree += render partial: 'tree/tree_item', collection: folders, locals: {type: 'folder'} if folders.present?
files.each do |f|
- if f.respond_to?(:url)
- # Object is a Submodule
- tree += render partial: 'tree/submodule_item', object: f
- else
- # Object is a Blob
- tree += render partial: 'tree/tree_item', object: f, locals: {type: 'file'}
- end
+ html = if f.respond_to?(:url)
+ # Object is a Submodule
+ render partial: 'tree/submodule_item', object: f
+ else
+ # Object is a Blob
+ render partial: 'tree/tree_item', object: f, locals: {type: 'file'}
+ end
+
+ tree += html if html.present?
end
tree.html_safe
:admin_team_member,
:admin_merge_request,
:admin_note,
- :accept_mr,
:admin_wiki,
:admin_project
]
--- /dev/null
+require "grit"
+
+module Graph
+ class Commit
+ include ActionView::Helpers::TagHelper
+
+ attr_accessor :time, :spaces, :refs, :parent_spaces, :icon
+
+ def initialize(commit)
+ @_commit = commit
+ @time = -1
+ @spaces = []
+ @parent_spaces = []
+ end
+
+ def method_missing(m, *args, &block)
+ @_commit.send(m, *args, &block)
+ end
+
+ def to_graph_hash
+ h = {}
+ h[:parents] = self.parents.collect do |p|
+ [p.id,0,0]
+ end
+ h[:author] = {
+ name: author.name,
+ email: author.email,
+ icon: icon
+ }
+ h[:time] = time
+ h[:space] = spaces.first
+ h[:parent_spaces] = parent_spaces
+ h[:refs] = refs.collect{|r|r.name}.join(" ") unless refs.nil?
+ h[:id] = sha
+ h[:date] = date
+ h[:message] = message
+ h
+ end
+
+ def add_refs(ref_cache, repo)
+ if ref_cache.empty?
+ repo.refs.each do |ref|
+ ref_cache[ref.commit.id] ||= []
+ ref_cache[ref.commit.id] << ref
+ end
+ end
+ @refs = ref_cache[@_commit.id] if ref_cache.include?(@_commit.id)
+ @refs ||= []
+ end
+
+ def space
+ if @spaces.size > 0
+ @spaces.first
+ else
+ 0
+ end
+ end
+ end
+end
--- /dev/null
+require "grit"
+
+module Graph
+ class JsonBuilder
+ attr_accessor :days, :commits, :ref_cache, :repo
+
+ def self.max_count
+ @max_count ||= 650
+ end
+
+ def initialize project, ref, commit
+ @project = project
+ @ref = ref
+ @commit = commit
+ @repo = project.repo
+ @ref_cache = {}
+
+ @commits = collect_commits
+ @days = index_commits
+ end
+
+ def to_json(*args)
+ {
+ days: @days.compact.map { |d| [d.day, d.strftime("%b")] },
+ commits: @commits.map(&:to_graph_hash)
+ }.to_json(*args)
+ end
+
+ protected
+
+ # Get commits from repository
+ #
+ def collect_commits
+
+ @commits = Grit::Commit.find_all(repo, nil, {date_order: true, max_count: self.class.max_count, skip: to_commit}).dup
+
+ # Decorate with app/models/commit.rb
+ @commits.map! { |commit| Commit.new(commit) }
+
+ # Decorate with lib/gitlab/graph/commit.rb
+ @commits.map! { |commit| Graph::Commit.new(commit) }
+
+ # add refs to each commit
+ @commits.each { |commit| commit.add_refs(ref_cache, repo) }
+
+ @commits
+ end
+
+ # Method is adding time and space on the
+ # list of commits. As well as returns date list
+ # corelated with time set on commits.
+ #
+ # @param [Array<Graph::Commit>] commits to index
+ #
+ # @return [Array<TimeDate>] list of commit dates corelated with time on commits
+ def index_commits
+ days, times = [], []
+ map = {}
+
+ commits.reverse.each_with_index do |c,i|
+ c.time = i
+ days[i] = c.committed_date
+ map[c.id] = c
+ times[i] = c
+ end
+
+ @_reserved = {}
+ days.each_index do |i|
+ @_reserved[i] = []
+ end
+
+ commits_sort_by_ref.each do |commit|
+ if map.include? commit.id then
+ place_chain(map[commit.id], map)
+ end
+ end
+
+ # find parent spaces for not overlap lines
+ times.each do |c|
+ c.parent_spaces.concat(find_free_parent_spaces(c, map, times))
+ end
+
+ days
+ end
+
+ # Skip count that the target commit is displayed in center.
+ def to_commit
+ commits = Grit::Commit.find_all(repo, nil, {date_order: true})
+ commit_index = commits.index do |c|
+ c.id == @commit.id
+ end
+
+ if commit_index && (self.class.max_count / 2 < commit_index) then
+ # get max index that commit is displayed in the center.
+ commit_index - self.class.max_count / 2
+ else
+ 0
+ end
+ end
+
+ def commits_sort_by_ref
+ commits.sort do |a,b|
+ if include_ref?(a)
+ -1
+ elsif include_ref?(b)
+ 1
+ else
+ b.committed_date <=> a.committed_date
+ end
+ end
+ end
+
+ def include_ref?(commit)
+ heads = commit.refs.select do |ref|
+ ref.is_a?(Grit::Head) or ref.is_a?(Grit::Remote) or ref.is_a?(Grit::Tag)
+ end
+
+ heads.map! do |head|
+ head.name
+ end
+
+ heads.include?(@ref)
+ end
+
+ def find_free_parent_spaces(commit, map, times)
+ spaces = []
+
+ commit.parents.each do |p|
+ if map.include?(p.id) then
+ parent = map[p.id]
+
+ range = if commit.time < parent.time then
+ commit.time..parent.time
+ else
+ parent.time..commit.time
+ end
+
+ space = if commit.space >= parent.space then
+ find_free_parent_space(range, parent.space, -1, commit.space, times)
+ else
+ find_free_parent_space(range, commit.space, -1, parent.space, times)
+ end
+
+ mark_reserved(range, space)
+ spaces << space
+ end
+ end
+
+ spaces
+ end
+
+ def find_free_parent_space(range, space_base, space_step, space_default, times)
+ if is_overlap?(range, times, space_default) then
+ find_free_space(range, space_step, space_base, space_default)
+ else
+ space_default
+ end
+ end
+
+ def is_overlap?(range, times, overlap_space)
+ range.each do |i|
+ if i != range.first &&
+ i != range.last &&
+ times[i].spaces.include?(overlap_space) then
+
+ return true;
+ end
+ end
+
+ false
+ end
+
+ # Add space mark on commit and its parents
+ #
+ # @param [Graph::Commit] the commit object.
+ # @param [Hash<String,Graph::Commit>] map of commits
+ def place_chain(commit, map, parent_time = nil)
+ leaves = take_left_leaves(commit, map)
+ if leaves.empty?
+ return
+ end
+
+ time_range = leaves.last.time..leaves.first.time
+ space_base = get_space_base(leaves, map)
+ space = find_free_space(time_range, 2, space_base)
+ leaves.each do |l|
+ l.spaces << space
+ # Also add space to parent
+ l.parents.each do |p|
+ if map.include?(p.id)
+ parent = map[p.id]
+ if parent.space > 0
+ parent.spaces << space
+ end
+ end
+ end
+ end
+
+ # and mark it as reserved
+ min_time = leaves.last.time
+ parents = leaves.last.parents.collect
+ parents.each do |p|
+ if map.include? p.id
+ parent = map[p.id]
+ if parent.time < min_time
+ min_time = parent.time
+ end
+ end
+ end
+
+ if parent_time.nil?
+ max_time = leaves.first.time
+ else
+ max_time = parent_time - 1
+ end
+ mark_reserved(min_time..max_time, space)
+
+ # Visit branching chains
+ leaves.each do |l|
+ parents = l.parents.collect.select{|p| map.include? p.id and map[p.id].space.zero?}
+ for p in parents
+ place_chain(map[p.id], map, l.time)
+ end
+ end
+ end
+
+ def get_space_base(leaves, map)
+ space_base = 1
+ if leaves.last.parents.size > 0
+ first_parent = leaves.last.parents.first
+ if map.include?(first_parent.id)
+ first_p = map[first_parent.id]
+ if first_p.space > 0
+ space_base = first_p.space
+ end
+ end
+ end
+ space_base
+ end
+
+ def mark_reserved(time_range, space)
+ for day in time_range
+ @_reserved[day].push(space)
+ end
+ end
+
+ def find_free_space(time_range, space_step, space_base = 1, space_default = nil)
+ space_default ||= space_base
+
+ reserved = []
+ for day in time_range
+ reserved += @_reserved[day]
+ end
+ reserved.uniq!
+
+ space = space_default
+ while reserved.include?(space) do
+ space += space_step
+ if space < space_base then
+ space_step *= -1
+ space = space_base + space_step
+ end
+ end
+
+ space
+ end
+
+ # Takes most left subtree branch of commits
+ # which don't have space mark yet.
+ #
+ # @param [Graph::Commit] the commit object.
+ # @param [Hash<String,Graph::Commit>] map of commits
+ #
+ # @return [Array<Graph::Commit>] list of branch commits
+ def take_left_leaves(commit, map)
+ leaves = []
+ leaves.push(commit) if commit.space.zero?
+
+ while true
+ return leaves if commit.parents.count.zero?
+ return leaves unless map.include? commit.parents.first.id
+
+ commit = map[commit.parents.first.id]
+
+ return leaves unless commit.space.zero?
+
+ leaves.push(commit)
+ end
+ end
+ end
+end
#
# Table name: namespaces
#
-# id :integer not null, primary key
-# name :string(255) not null
-# path :string(255) not null
-# owner_id :integer not null
-# created_at :datetime not null
-# updated_at :datetime not null
-# type :string(255)
+# id :integer not null, primary key
+# name :string(255) not null
+# description :string(255) not null
+# path :string(255) not null
+# owner_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# type :string(255)
#
class Group < Namespace
where('assignee_id = :user', user: user.id)
end
+ def authored(user)
+ where('author_id = :user', user: user.id)
+ end
+
def open_for(user)
opened.assigned(user)
end
#
# Table name: namespaces
#
-# id :integer not null, primary key
-# name :string(255) not null
-# path :string(255) not null
-# owner_id :integer not null
-# created_at :datetime not null
-# updated_at :datetime not null
-# type :string(255)
+# id :integer not null, primary key
+# name :string(255) not null
+# description :string(255) not null
+# path :string(255) not null
+# owner_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# type :string(255)
#
class Namespace < ActiveRecord::Base
- attr_accessible :name, :path
+ attr_accessible :name, :description, :path
has_many :projects, dependent: :destroy
belongs_to :owner, class_name: "User"
length: { within: 0..255 },
format: { with: Gitlab::Regex.name_regex,
message: "only letters, digits, spaces & '_' '-' '.' allowed." }
-
+ validates :description, length: { within: 0..255 }
validates :path, uniqueness: true, presence: true, length: { within: 1..255 },
format: { with: Gitlab::Regex.path_regex,
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
# creator_id :integer
# default_branch :string(255)
# issues_enabled :boolean default(TRUE), not null
+# issues_tracker :string not null
# wall_enabled :boolean default(TRUE), not null
# merge_requests_enabled :boolean default(TRUE), not null
# wiki_enabled :boolean default(TRUE), not null
class Project < ActiveRecord::Base
include Gitolited
+ extend Enumerize
class TransferError < StandardError; end
- attr_accessible :name, :path, :description, :default_branch,
- :issues_enabled, :wall_enabled, :merge_requests_enabled,
+ attr_accessible :name, :path, :description, :default_branch, :issues_tracker,
+ :issues_enabled, :wall_enabled, :merge_requests_enabled, :issues_tracker_id,
:wiki_enabled, :public, :import_url, as: [:default, :admin]
attr_accessible :namespace_id, :creator_id, as: :admin
has_many :events, dependent: :destroy
has_many :merge_requests, dependent: :destroy
- has_many :issues, dependent: :destroy, order: "state, created_at DESC"
+ has_many :issues, dependent: :destroy, order: "state DESC, created_at DESC"
has_many :milestones, dependent: :destroy
has_many :users_projects, dependent: :destroy
has_many :notes, dependent: :destroy
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
validates :issues_enabled, :wall_enabled, :merge_requests_enabled,
:wiki_enabled, inclusion: { in: [true, false] }
+ validates :issues_tracker_id, length: { within: 0..255 }
validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id
scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) }
scope :public_only, -> { where(public: true) }
+ enumerize :issues_tracker, :in => (Gitlab.config.issues_tracker.keys).append(:gitlab), :default => :gitlab
+
class << self
def abandoned
project_ids = Event.select('max(created_at) as latest_date, project_id').
issues.tag_counts_on(:labels)
end
+ def issue_exists?(issue_id)
+ if used_default_issues_tracker?
+ self.issues.where(id: issue_id).first.present?
+ else
+ true
+ end
+ end
+
+ def used_default_issues_tracker?
+ self.issues_tracker == Project.issues_tracker.default_value
+ end
+
+ def can_have_issues_tracker_id?
+ self.issues_enabled && !self.used_default_issues_tracker?
+ end
+
def services
[gitlab_ci_service].compact
end
file_path = File.join(storage_path, self.path_with_namespace, file_name)
# Put files into a directory before archiving
- prefix = self.path_with_namespace + "/"
+ prefix = File.basename(self.path_with_namespace) + "/"
# Create file if not exists
unless File.exists?(file_path)
# dark_scheme :boolean default(FALSE), not null
# theme_id :integer default(1), not null
# bio :string(255)
-# blocked :boolean default(FALSE), not null
+# state :string(255)
# failed_attempts :integer default(0)
# locked_at :datetime
# extern_uid :string(255)
attr_accessor :force_random_password
+ #
+ # Relations
+ #
+
# Namespace for personal projects
- has_one :namespace, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace", conditions: 'type IS NULL'
+ has_one :namespace,
+ dependent: :destroy,
+ foreign_key: :owner_id,
+ class_name: "Namespace",
+ conditions: 'type IS NULL'
+
+ # Profile
+ has_many :keys, dependent: :destroy
+
+ # Groups
+ has_many :groups, class_name: "Group", foreign_key: :owner_id
+
+ # Teams
+ has_many :own_teams,
+ class_name: "UserTeam",
+ foreign_key: :owner_id,
+ dependent: :destroy
+
+ has_many :user_team_user_relationships, dependent: :destroy
+ has_many :user_teams, through: :user_team_user_relationships
+ has_many :user_team_project_relationships, through: :user_teams
+ has_many :team_projects, through: :user_team_project_relationships
- has_many :keys, dependent: :destroy
+ # Projects
has_many :users_projects, dependent: :destroy
has_many :issues, dependent: :destroy, foreign_key: :author_id
has_many :notes, dependent: :destroy, foreign_key: :author_id
has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event"
has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue"
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
+ has_many :projects, through: :users_projects
- has_many :groups, class_name: "Group", foreign_key: :owner_id
- has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC"
-
- has_many :projects, through: :users_projects
-
- has_many :user_team_user_relationships, dependent: :destroy
-
- has_many :user_teams, through: :user_team_user_relationships
- has_many :user_team_project_relationships, through: :user_teams
- has_many :team_projects, through: :user_team_project_relationships
+ has_many :recent_events,
+ class_name: "Event",
+ foreign_key: :author_id,
+ order: "id DESC"
+ #
+ # Validations
+ #
validates :name, presence: true
validates :email, presence: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/ }
validates :bio, length: { within: 0..255 }
delegate :path, to: :namespace, allow_nil: true, prefix: true
+ state_machine :state, initial: :active do
+ after_transition any => :blocked do |user, transition|
+ # Remove user from all projects and
+ user.users_projects.find_each do |membership|
+ return false unless membership.destroy
+ end
+ end
+
+ event :block do
+ transition active: :blocked
+ end
+
+ event :activate do
+ transition blocked: :active
+ end
+ end
+
# Scopes
scope :admins, -> { where(admin: true) }
- scope :blocked, -> { where(blocked: true) }
- scope :active, -> { where(blocked: false) }
+ scope :blocked, -> { with_state(:blocked) }
+ scope :active, -> { with_state(:active) }
scope :alphabetically, -> { order('name ASC') }
scope :in_team, ->(team){ where(id: team.member_ids) }
scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
MergeRequest.cared(self)
end
- # Remove user from all projects and
- # set blocked attribute to true
- def block
- users_projects.find_each do |membership|
- return false unless membership.destroy
- end
-
- self.blocked = true
- save
- end
-
def projects_limit_percent
return 100 if projects_limit.zero?
(personal_projects.count.to_f / projects_limit) * 100
#
class UserTeam < ActiveRecord::Base
- attr_accessible :name, :owner_id, :path
+ attr_accessible :name, :description, :owner_id, :path
belongs_to :owner, class_name: User
length: { within: 0..255 },
format: { with: Gitlab::Regex.name_regex,
message: "only letters, digits, spaces & '_' '-' '.' allowed." }
+ validates :description, length: { within: 0..255 }
validates :path, uniqueness: true, presence: true, length: { within: 1..255 },
format: { with: Gitlab::Regex.path_regex,
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
def create_note(issue)
Note.create_status_change_note(issue, current_user, issue.state)
- [issue.author, issue.assignee].compact.each do |recipient|
+ [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 after_create(user)
log_info("User \"#{user.name}\" (#{user.email}) was created")
- Notify.delay.new_user_email(user.id, user.password)
+ # Dont email omniauth created users
+ Notify.delay.new_user_email(user.id, user.password) unless user.extern_uid?
end
def after_destroy user
# Collect data for this git push
@push_data = post_receive_data(oldrev, newrev, ref)
+ create_push_event
+
project.ensure_satellite_exists
project.discover_default_branch
project.execute_hooks(@push_data.dup)
project.execute_services(@push_data.dup)
end
-
- create_push_event
end
# This method provide a sample data
Gitlab::ProjectMover.new(project, old_dir, new_dir).execute
- save!
+ project.save!
end
rescue Gitlab::ProjectMover::ProjectMoveError => ex
raise Project::TransferError.new(ex.message)
-%h3.page_title Rename Group
+%h3.page_title Edit Group
%hr
= form_for [:admin, @group] do |f|
- if @group.errors.any?
.input
= f.text_field :name, placeholder: "Example Group", class: "xxlarge"
-
+ .clearfix.group-description-holder
+ = f.label :description, "Details"
+ .input
+ = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4
.clearfix.group_name_holder
= f.label :path do
%li It will change the git path to repositories under this group.
.form-actions
- = f.submit 'Rename group', class: "btn btn-remove"
+ = f.submit 'Edit group', class: "btn btn-remove"
= link_to 'Cancel', admin_groups_path, class: "btn btn-cancel"
Name
%i.icon-sort-down
%th Path
+ %th Description
%th Projects
%th Owner
%th.cred Danger Zone!
%tr
%td
%strong= link_to group.name, [:admin, group]
+ %td= truncate group.description
%td= group.path
%td= group.projects.count
%td
= link_to group.owner_name, admin_user_path(group.owner)
%td.bgred
- = link_to 'Rename', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-small"
+ = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-small"
= link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
= paginate @groups, theme: "admin"
Group name is
.input
= f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
-
- = f.submit 'Create group', class: "btn btn-primary"
+ .clearfix.group-description-holder
+ = f.label :description, "Details"
+ .input
+ = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4
+
+ .form-actions
+ = f.submit 'Create group', class: "btn btn-primary"
+
%hr
.padded
%ul
= link_to edit_admin_group_path(@group), class: "btn btn-small pull-right" do
%i.icon-edit
- Rename
+ Edit
+ %tr
+ %td
+ %b
+ Description:
+ %td
+ = @group.description
%tr
%td
%b
= 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
-%h3.page_title Rename Team
+%h3.page_title Edit Team
%hr
= form_for @team, url: admin_team_path(@team), method: :put do |f|
- if @team.errors.any?
.input
= f.text_field :name, placeholder: "Example Team", class: "xxlarge"
+ .clearfix.team-description-holder
+ = f.label :description, "Details"
+ .input
+ = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4
+
.clearfix.team_name_holder
= f.label :path do
%span.cred Team path is
%li It will change web url for access team and team projects.
.form-actions
- = f.submit 'Rename team', class: "btn btn-remove"
+ = f.submit 'Edit team', class: "btn btn-remove"
= link_to 'Cancel', admin_teams_path, class: "btn btn-cancel"
%th
Name
%i.icon-sort-down
+ %th Description
%th Path
%th Projects
%th Members
%tr
%td
%strong= link_to team.name, admin_team_path(team)
+ %td= truncate team.description
%td= team.path
%td= team.projects.count
%td= team.members.count
%td
- = link_to team.owner.name, admin_user_path(team.owner)
+ - if team.owner
+ = link_to team.owner.name, admin_user_path(team.owner)
+ - else
+ (deleted)
%td.bgred
- = link_to 'Rename', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn btn-small"
+ = link_to 'Edit', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn btn-small"
= link_to 'Destroy', admin_team_path(team), confirm: "REMOVE #{team.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
= paginate @teams, theme: "admin"
Team name is
.input
= f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
-
- = f.submit 'Create team', class: "btn btn-primary"
+
+ .clearfix.team-description-holder
+ = f.label :description, "Details"
+ .input
+ = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4
+
+ .form-actions
+ = f.submit 'Create team', class: "btn btn-primary"
+
%hr
.padded
%ul
= link_to edit_admin_team_path(@team), class: "btn btn-small pull-right" do
%i.icon-edit
- Rename
+ Edit
+ %tr
+ %td
+ %b
+ Description:
+ %td
+ = @team.description
%tr
%td
%b
.span4
- unless @admin_user.new_record?
.alert.alert-error
- - if @admin_user.blocked
+ - if @admin_user.blocked?
%p This user is blocked and is not able to login to GitLab
= link_to 'Unblock User', unblock_admin_user_path(@admin_user), method: :put, class: "btn btn-small"
- else
= link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn btn-small"
- unless user == current_user
- - if user.blocked
+ - if user.blocked?
= link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn btn-small success"
- else
= link_to 'Block', block_admin_user_path(user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-small btn-remove"
%h3.page_title
= image_tag gravatar_icon(@admin_user.email, 90), class: "avatar s90"
= @admin_user.name
- - if @admin_user.blocked
+ - if @admin_user.blocked?
%span.cred (Blocked)
- if @admin_user.admin
%span.cred (Admin)
-- @commits.group_by { |c| c.committed_date.to_date }.each do |day, commits|
+- @commits.group_by { |c| c.committed_date.to_date }.sort.reverse.each do |day, commits|
%div.ui-box
%h5.title
%i.icon-calendar
= event.project_name
.event-body
- %i.icon-comment-alt.event-note-icon
- %span.event-note
- = markdown truncate(event.target.note, length: 70)
+ .event-note
+ .md
+ %i.icon-comment-alt.event-note-icon
+ = sanitize(markdown(truncate(event.target.note, length: 150)), tags: %w(a img b pre p))
- note = event.target
- if note.attachment.url
= link_to note.attachment.secure_url, target: "_blank", class: 'note-file-attach' do
Group name is
.input
= f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
-
- = f.submit 'Save group', class: "btn btn-save"
+
+ .clearfix.group-description-holder
+ = f.label :description, "Details"
+ .input
+ = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4
+
+ .form-actions
+ = f.submit 'Save group', class: "btn btn-save"
+
%hr
Group name is
.input
= f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
-
- = f.submit 'Create group', class: "btn btn-create"
- %hr
+
+ .clearfix.group-description-holder
+ = f.label :description, "Details"
+ .input
+ = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4
+
+ .form-actions
+ = f.submit 'Create group', class: "btn btn-create"
+
+
.padded
%ul
%li Group is kind of directory for several projects
%p.nothing_here_message Project activity will be displayed here
.loading.hide
.side.span4
+ - if @group.description.present?
+ .description.well.light
+ = @group.description
= render "projects", projects: @projects
%div
%span.rss-icon
Open
%li{class: ("active" if params[:status] == 'assigned-to-me')}
= link_to project_issues_path(@project, status: 'assigned-to-me') do
- Assigned To Me
+ Assigned to me
+ %li{class: ("active" if params[:status] == 'created-by-me')}
+ = link_to project_issues_path(@project, status: 'created-by-me') do
+ Created by me
%li{class: ("active" if params[:status] == 'closed')}
= link_to project_issues_path(@project, status: 'closed') do
Closed
-- if text = alert || notice
- #flash-container
- %h4= text
+.flash-container
+ - if alert
+ .alert
+ %span= alert
+
+ - elsif notice
+ .alert.alert-info
+ %span= notice
= stylesheet_link_tag "application"
= javascript_include_tag "application"
= csrf_meta_tags
+ = include_gon
-# Atom feed
- if current_user
%span.separator
%h1.project_name= title
%ul.nav
+ %li
+ = link_to public_root_path, title: "Public area", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do
+ %i.icon-globe
- if current_user.is_admin?
%li
= link_to admin_root_path, title: "Admin area", class: 'has_bottom_tooltip', 'data-original-title' => 'Admin area' do
%html{ lang: "en"}
= render "layouts/head", title: "Admin area"
%body{class: "#{app_theme} admin"}
- = render "layouts/flash"
= render "layouts/head_panel", title: "Admin area"
+ = render "layouts/flash"
.container
%ul.main_menu
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
%html{ lang: "en"}
= render "layouts/head", title: "Dashboard"
%body{class: "#{app_theme} application"}
- = render "layouts/flash"
= render "layouts/head_panel", title: "Dashboard"
+ = render "layouts/flash"
.container
%ul.main_menu
= nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do
= render "layouts/head"
%body.ui_basic.login-page
= render "layouts/flash"
- .container= yield
+ .container
+ .content
+ = yield
%html{ lang: "en"}
= render "layouts/head", title: "Error"
%body{class: "#{app_theme} application"}
- = render "layouts/flash"
= render "layouts/head_panel", title: ""
+ = render "layouts/flash"
.container
.content
%center.padded.prepend-top-20
%html{ lang: "en"}
= render "layouts/head", title: "#{@group.name}"
%body{class: "#{app_theme} application"}
- = render "layouts/flash"
= render "layouts/head_panel", title: "group: #{@group.name}"
+ = render "layouts/flash"
.container
%ul.main_menu
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
%html{ lang: "en"}
= render "layouts/head", title: "Profile"
%body{class: "#{app_theme} profile"}
- = render "layouts/flash"
= render "layouts/head_panel", title: "Profile"
+ = render "layouts/flash"
.container
%ul.main_menu
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
%html{ lang: "en"}
= render "layouts/head", title: @project.name_with_namespace
%body{class: "#{app_theme} project"}
- = render "layouts/flash"
= render "layouts/head_panel", title: project_title(@project)
+ = render "layouts/flash"
- if can?(current_user, :download_code, @project)
= render 'shared/no_ssh'
= nav_link(controller: %w(graph)) do
= link_to "Network", project_graph_path(@project, @ref || @repository.root_ref)
- - if @project.issues_enabled
+ - if @project.issues_enabled
= nav_link(controller: %w(issues milestones labels)) do
- = link_to project_issues_filter_path(@project) do
+ = link_to url_for_project_issues do
Issues
- %span.count.issue_counter= @project.issues.opened.count
+ - if @project.used_default_issues_tracker?
+ %span.count.issue_counter= @project.issues.opened.count
- if @project.repo_exists? && @project.merge_requests_enabled
= nav_link(controller: :merge_requests) do
%html{ lang: "en"}
= render "layouts/head", title: "#{@team.name}"
%body{class: "#{app_theme} application"}
- = render "layouts/flash"
= render "layouts/head_panel", title: "team: #{@team.name}"
+ = render "layouts/flash"
.container
%ul.main_menu
= nav_link(path: 'teams#show', html_options: {class: 'home'}) do
-- unless can?(current_user, :accept_mr, @project)
+- unless @allowed_to_merge
.alert
- %strong Only masters can accept MR
+ %strong You don't have enough permissions to merge this MR
-- if @merge_request.opened? && @commits.any? && can?(current_user, :accept_mr, @project)
+- if @show_merge_controls
.automerge_widget.can_be_merged{style: "display:none"}
.alert.alert-success
%span
--- /dev/null
+Issue was <%= @issue_status %> by <%= @updated_by.name %>
+
+Issue <%= @issue.id %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
+
--- /dev/null
+New Issue was created and assigned to you.
+
+
+Issue <%= @issue.id %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
--- /dev/null
+New Merge Request <%= @merge_request.id %>
+
+<%= url_for(project_merge_request_url(@merge_request.project, @merge_request)) %>
+
+
+Branches: <%= @merge_request.source_branch %> to <%= @merge_request.target_branch %>
+Author: <%= @merge_request.author_name %>
+Asignee: <%= @merge_request.assignee_name %>
+
--- /dev/null
+Hi <%= @user.name %>!
+
+Administrator created account for you. Now you are a member of company GitLab application.
+
+login.................. <%= @user.email %>
+<% unless Gitlab.config.gitlab.signup_enabled %>
+ password............... <%= @password %>
+<% end %>
+
+Click here to login: <%= url_for(root_url) %>
--- /dev/null
+New comment for Commit <%= @commit.short_id %>
+
+<%= url_for(project_commit_url(@note.project, id: @commit.id, anchor: "note_#{@note.id}")) %>
+
+
+Author: <%= @note.author_name %>
+
+<%= @note.note %>
+
--- /dev/null
+New comment for Issue <%= @issue.id %>
+
+<%= url_for(project_issue_url(@issue.project, @issue, anchor: "note_#{@note.id}")) %>
+
+
+Author: <%= @note.author_name %>
+
+<%= @note.note %>
+
--- /dev/null
+New comment for Merge Request <%= @merge_request.id %>
+
+<%= url_for(project_merge_request_url(@merge_request.project, @merge_request, anchor: "note_#{@note.id}")) %>
+
+
+<%= @note.author_name %>
+
+<%= @note.note %>
+
--- /dev/null
+New message on the project wall <%= @note.project %>
+
+<%= url_for(wall_project_url(@note.project, anchor: "note_#{@note.id}")) %>
+
+
+<%= @note.author_name %>
+
+<%= @note.note %>
+
--- /dev/null
+
+You have been granted <%= @users_project.project_access_human %> access to project <%= @project.name_with_namespace %>
+
+<%= url_for(project_url(@project)) %>
--- /dev/null
+Project was moved to another location
+
+The project is now located under
+<%= url_for(link_to project_url(@project)) %>
+
+
+To update the remote url in your local repository run:
+ git remote set-url origin <%= @project.ssh_url_to_repo %>
--- /dev/null
+Reassigned Issue <%= @issue.id %>
+
+<%= url_for(project_issue_url(@issue.project, @issue)) %>
+
+
+Assignee changed from <%= @previous_assignee.name %> to <%= @issue.assignee_name %>
+
--- /dev/null
+Reassigned Merge Request <%= @merge_request.id %>
+
+<%= url_for(project_merge_request_url(@merge_request.project, @merge_request)) %>
+
+
+Assignee changed from <%= @previous_assignee.name %> to <%= @merge_request.assignee_name %>
+
= f.check_box :issues_enabled
%span.descr Lightweight issue tracking system for this project
+ - if Project.issues_tracker.values.count > 1
+ .control-group
+ = 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?
+
.control-group
= f.label :merge_requests_enabled, "Merge Requests", class: 'control-label'
.controls
%fieldset
%legend Git global setup:
%pre.dark
- = preserve do
+ :preserve
git config --global user.name "#{current_user.name}"
git config --global user.email "#{current_user.email}"
%fieldset
%legend Create Repository
%pre.dark
- = preserve do
+ :preserve
mkdir #{@project.path}
cd #{@project.path}
git init
%fieldset
%legend Existing Git Repo?
%pre.dark
- = preserve do
+ :preserve
cd existing_git_repo
git remote add origin #{@project.url_to_repo}
git push -u origin master
.pull-right
%pre.dark.tiny git clone #{project.http_url_to_repo}
+ - unless @projects.present?
+ %h3.nothing_here_message No public projects
= paginate @projects, theme: "admin"
.input-prepend.project_clone_holder
%button{class: "btn active", :"data-clone" => @project.ssh_url_to_repo} SSH
%button{class: "btn", :"data-clone" => @project.http_url_to_repo}= Gitlab.config.gitlab.protocol.upcase
-
- = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select input-xxlarge"
+ = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select input-xxlarge", readonly: true
%span.label This is you!
- if @project.namespace_owner == user
%span.label Owner
- - elsif user.blocked
+ - elsif user.blocked?
%span.label Blocked
- elsif allow_admin
= link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "btn-tiny btn btn-remove" do
.input
= f.text_field :name, placeholder: "Ex. OpenSource", class: "xlarge left"
+ .clearfix.team-description-holder
+ = f.label :description, "Details"
+ .input
+ = f.text_area :description, maxlength: 250, class: "xlarge js-gfm-input", rows: 4
+
.clearfix
= f.label :path do
Team path is
.input
= f.text_field :path, placeholder: "opensource", class: "xlarge left"
+
.form-actions
- = f.submit 'Save team changes', class: "btn btn-save"
+ = f.submit 'Save team changes', class: "btn btn-primary"
.span5
.ui-box
%h5.title Remove team
%p
Removed team can not be restored!
= link_to 'Remove team', team_path(@team), method: :delete, confirm: "You are sure?", class: "btn btn-remove btn-small"
-
%span.btn.disabled This is you!
- if @team.owner == user
%span.btn.disabled Owner
- - elsif user.blocked
+ - elsif user.blocked?
%span.btn.disabled.blocked Blocked
- elsif allow_admin
= link_to team_member_path(@team, user), confirm: remove_from_user_team_message(@team, user), method: :delete, class: "btn-tiny btn btn-remove", title: "Remove from team" do
Team name is
.input
= f.text_field :name, placeholder: "Ex. Ruby Developers", class: "xxlarge left"
-
- = f.submit 'Create team', class: "btn btn-create"
- %hr
+
+ .clearfix.team-description-holder
+ = f.label :description, "Details"
+ .input
+ = f.text_area :description, maxlength: 250, class: "xxlarge js-gfm-input", rows: 4
+
+ .form-actions
+ = f.submit 'Create team', class: "btn btn-create"
+
.padded
%ul
%li All created teams are public (users can view who enter into team and which project are assigned for this team)
%p.nothing_here_message Projects activity will be displayed here
.loading.hide
.side.span4
+ - if @team.description.present?
+ .description.well.light
+ = @team.description
= render "projects", projects: @projects
%div
%span.rss-icon
# # # # # # # # # # # # # # # # # #
-# Gitlab application config file #
+# GitLab application config file #
# # # # # # # # # # # # # # # # # #
#
# How to use:
# signup_enabled: true # default: false - Account passwords are not sent via the email if signup is enabled.
# username_changing_enabled: false # default: true - User can change her username/namespace
+
+ ## External issues trackers
+ issues_tracker:
+ redmine:
+ ## If not nil, link 'Issues' on project page will be replaced with this
+ ## Use placeholders:
+ ## :project_id - GitLab project identifier
+ ## :issues_tracker_id - Project Name or Id in external issue tracker
+ project_url: "http://redmine.sample/projects/:issues_tracker_id"
+ ## If not nil, links from /#\d/ entities from commit messages will replaced with this
+ ## Use placeholders:
+ ## :project_id - GitLab project identifier
+ ## :issues_tracker_id - Project Name or Id in external issue tracker
+ ## :id - Issue id (from commit messages)
+ issues_url: "http://redmine.sample/issues/:id"
+
## Gravatar
gravatar:
- enabled: true # Use user avatar images from Gravatar.com (default: true)
+ enabled: true # Use user avatar image from Gravatar.com (default: true)
# plain_url: "http://..." # default: http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=mm
# ssl_url: "https://..." # default: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=mm
bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
password: '_the_password_of_the_bind_user'
- ## Omniauth settings
+ ## OmniAuth settings
omniauth:
- # Enable ability for users
- # Allow logging in via Twitter, Google, etc. using Omniauth providers
+ # Allow login via Twitter, Google, etc. using OmniAuth providers
enabled: false
# CAUTION!
- # This allows users to login without having a user account first (default: false)
+ # This allows users to login without having a user account first (default: false).
# User accounts will be created automatically when authentication was successful.
allow_single_sign_on: false
- # Locks down those users until they have been cleared by the admin (default: true)
+ # Locks down those users until they have been cleared by the admin (default: true).
block_auto_created_users: true
## Auth providers
- # Uncomment the lines and fill in the data of the auth provider you want to use
- # If your favorite auth provider is not listed you can user others:
+ # Uncomment the following lines and fill in the data of the auth provider you want to use
+ # If your favorite auth provider is not listed you can use others:
# see https://github.com/gitlabhq/gitlabhq/wiki/Using-Custom-Omniauth-Providers
# The 'app_id' and 'app_secret' parameters are always passed as the first two
# arguments, followed by optional 'args' which can be either a hash or an array.
upload_pack: true
receive_pack: true
- # If you use non-standart ssh port you need to specify it
+ # If you use non-standard ssh port you need to specify it
# ssh_port: 22
## Git settings
# Use the default values unless you really know what you are doing
git:
bin_path: /usr/bin/git
- # Max size of git object like commit, in bytes
- # This value can be increased if you have a very large commits
+ # Max size of a git object (e.g. a commit), in bytes
+ # This value can be increased if you have very large commits
max_size: 5242880 # 5.megabytes
- # Git timeout to read commit, in seconds
+ # Git timeout to read a commit, in seconds
timeout: 10
development:
test:
<<: *base
+ issues_tracker:
+ redmine:
+ project_url: "http://redmine/projects/:issues_tracker_id"
+ issues_url: "http://redmine/:project_id/:issues_tracker_id/:id"
staging:
<<: *base
Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil?
Settings.omniauth['providers'] ||= []
+Settings['issues_tracker'] ||= {}
+
#
# GitLab
#
Settings.gitlab['host'] ||= 'localhost'
Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80
-Settings.gitlab['relative_url_root'] ||= ''
+Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || ''
Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http"
Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}"
Settings.gitlab['support_email'] ||= Settings.gitlab.email_from
# ==> Configuration for :validatable
# Range for password length. Default is 6..128.
- # config.password_length = 6..128
+ config.password_length = 6..128
# Email regex used to validate email formats. It simply asserts that
# an one (and only one) @ exists in the given string. This is mainly
unauthenticated: 'You need to sign in before continuing.'
unconfirmed: 'You have to confirm your account before continuing.'
locked: 'Your account is locked.'
+ not_found_in_database: 'Invalid email or password.'
invalid: 'Invalid email or password.'
invalid_token: 'Invalid authentication token.'
timeout: 'Your session expired, please sign in again to continue.'
#
# Attachments serving
#
- get 'files/:type/:id/:filename' => 'files#download', constraints: { id: /\d+/, type: /[a-z]+/, filename: /[a-zA-Z.0-9_\-\+]+/ }
+ get 'files/:type/:id/:filename' => 'files#download', constraints: { id: /\d+/, type: /[a-z]+/, filename: /.+/ }
#
# Admin Area
--- /dev/null
+class AddIssuesTrackerToProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :issues_tracker, :string, default: :gitlab, null: false
+ end
+end
--- /dev/null
+class AddDescriptionToNamsespace < ActiveRecord::Migration
+ def change
+ add_column :namespaces, :description, :string, default: '', null: false
+ end
+end
--- /dev/null
+class AddDescriptionToTeams < ActiveRecord::Migration
+ def change
+ add_column :user_teams, :description, :string, default: '', null: false
+ end
+end
--- /dev/null
+class AddIssuesTrackerIdToProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :issues_tracker_id, :string
+ end
+end
class ConvertClosedToStateInIssue < ActiveRecord::Migration
def up
Issue.transaction do
- Issue.where(closed: true).update_all("state = 'closed'")
- Issue.where(closed: false).update_all("state = 'opened'")
+ Issue.where(closed: true).update_all(state: :closed)
+ Issue.where(closed: false).update_all(state: :opened)
end
end
def down
Issue.transaction do
- Issue.where(state: :closed).update_all("closed = 1")
+ Issue.where(state: :closed).update_all(closed: true)
end
end
end
class ConvertClosedToStateInMergeRequest < ActiveRecord::Migration
def up
MergeRequest.transaction do
- MergeRequest.where(closed: true, merged: true).update_all("state = 'merged'")
- MergeRequest.where(closed: true, merged: true).update_all("state = 'closed'")
- MergeRequest.where(closed: false).update_all("state = 'opened'")
+ MergeRequest.where(closed: true, merged: true).update_all(state: :merged)
+ MergeRequest.where(closed: true, merged: false).update_all(state: :closed)
+ MergeRequest.where(closed: false).update_all(state: :opened)
end
end
class ConvertClosedToStateInMilestone < ActiveRecord::Migration
def up
Milestone.transaction do
- Milestone.where(closed: false).update_all("state = 'opened'")
- Milestone.where(closed: false).update_all("state = 'active'")
+ Milestone.where(closed: true).update_all(state: :closed)
+ Milestone.where(closed: false).update_all(state: :active)
end
end
def down
Milestone.transaction do
- Milestone.where(state: :closed).update_all("closed = 1")
+ Milestone.where(state: :closed).update_all(closed: true)
end
end
end
--- /dev/null
+class AddStateToUser < ActiveRecord::Migration
+ def change
+ add_column :users, :state, :string
+ end
+end
--- /dev/null
+class ConvertBlockedToState < ActiveRecord::Migration
+ def up
+ User.transaction do
+ User.where(blocked: true).update_all(state: :blocked)
+ User.where(blocked: false).update_all(state: :active)
+ end
+ end
+
+ def down
+ User.transaction do
+ User.where(state: :blocked).update_all(blocked: :true)
+ end
+ end
+end
--- /dev/null
+class RemoveBlockedFromUser < ActiveRecord::Migration
+ def up
+ remove_column :users, :blocked
+ end
+
+ def down
+ add_column :users, :blocked, :boolean
+ end
+end
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20130220133245) do
+ActiveRecord::Schema.define(:version => 20130304105317) do
create_table "events", :force => true do |t|
t.string "target_type"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "type"
+ t.string "description", :default => "", :null => false
end
add_index "namespaces", ["name"], :name => "index_namespaces_on_name"
t.boolean "wiki_enabled", :default => true, :null => false
t.integer "namespace_id"
t.boolean "public", :default => false, :null => false
+ t.string "issues_tracker", :default => "gitlab", :null => false
+ t.string "issues_tracker_id"
end
add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id"
t.integer "owner_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
+ t.string "description", :default => "", :null => false
end
create_table "users", :force => true do |t|
t.boolean "dark_scheme", :default => false, :null => false
t.integer "theme_id", :default => 1, :null => false
t.string "bio"
- t.boolean "blocked", :default => false, :null => false
t.integer "failed_attempts", :default => 0
t.datetime "locked_at"
t.string "extern_uid"
t.string "username"
t.boolean "can_create_group", :default => true, :null => false
t.boolean "can_create_team", :default => true, :null => false
+ t.string "state"
end
add_index "users", ["admin"], :name => "index_users_on_admin"
- add_index "users", ["blocked"], :name => "index_users_on_blocked"
add_index "users", ["email"], :name => "index_users_on_email", :unique => true
add_index "users", ["extern_uid", "provider"], :name => "index_users_on_extern_uid_and_provider", :unique => true
add_index "users", ["name"], :name => "index_users_on_name"
sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
# Login to MySQL
- $ mysql -u root -p
+ mysql -u root -p
# Create a user for GitLab. (change $password to a real password)
mysql> CREATE USER 'gitlab'@'localhost' IDENTIFIED BY '$password';
-This installation guide was created for Debian/Ubuntu and tested on it.
-
-Please read [`doc/install/requirements.md`](./requirements.md) for hardware and platform requirements.
+This installation guide was created for Debian/Ubuntu and tested on it. Please read [`doc/install/requirements.md`](./requirements.md) for hardware and platform requirements.
+This installation guide is recommended to set up a production server. If you want a development environment please use the [Vargrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm) since it makes it much easier to set up all the dependencies for integration testing.
**Important Note:**
The following steps have been known to work.
sudo adduser --disabled-login --gecos 'GitLab' git
+
# 4. GitLab shell
- # Login as git
+GitLab Shell is a ssh access and repository management software developed specially for GitLab.
+
+ # Login as git
sudo su git
- # Go to home directory
+ # Go to home directory
cd /home/git
# Clone gitlab shell
git clone https://github.com/gitlabhq/gitlab-shell.git
- # Setup
cd gitlab-shell
cp config.yml.example config.yml
- ./bin/install
+
+ # Edit config and replace gitlab_url
+ # with something like 'http://domain.com/'
+ vim config.yml
+
+ # Do setup
+ ./bin/install
# 5. Database
# Clone GitLab repository
sudo -u git -H git clone https://github.com/gitlabhq/gitlabhq.git gitlab
- # Go to gitlab dir
+ # Go to gitlab dir
cd /home/git/gitlab
-
+
# Checkout to stable release
sudo -u git -H git checkout 5-0-stable
# Create directory for pids and make sure GitLab can write to it
sudo -u git -H mkdir tmp/pids/
sudo chmod -R u+rwX tmp/pids/
-
+
# Copy the example Unicorn config
sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb
## Initialise Database and Activate Advanced Features
-
+
sudo -u git -H bundle exec rake db:setup RAILS_ENV=production
sudo -u git -H bundle exec rake db:seed_fu RAILS_ENV=production
sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
## Custom SSH Connection
If you are running SSH on a non-standard port, you must change the gitlab user's SSH config.
-
+
# Add to /home/git/.ssh/config
host localhost # Give your setup a name (here: override localhost)
user git # Your remote git user
@javascript
Scenario: I should see project network
Then page should have network graph
+ And page should select "master" in select box
+ And page should have "master" on graph
+
+ @javascript
+ Scenario: I should switch ref to "stable"
+ When I switch ref to "stable"
+ Then page should have network graph
+ And page should select "stable" in select box
+ And page should have "stable" on graph
+
+ @javascript
+ Scenario: I should looking for a commit by SHA of "v2.1.0"
+ When I looking for a commit by SHA of "v2.1.0"
+ Then page should have network graph
+ And page should select "master" in select box
+ And page should have "v2.1.0" on graph
And 'submit form with new group info' do
fill_in 'group_name', :with => 'gitlab'
+ fill_in 'group_description', :with => 'Group description'
click_button "Create group"
end
Then 'I should see newly created group' do
page.should have_content "Group: gitlab"
+ page.should have_content "Group description"
end
Then 'I should be redirected to group page' do
And 'submit form with new team info' do
fill_in 'user_team_name', with: 'gitlab'
+ fill_in 'user_team_description', with: 'description'
click_button 'Create team'
end
And 'I should see newly created team' do
page.should have_content "Team: gitlab"
+ page.should have_content "description"
end
When 'I visit admin teams page' do
Then 'I should see merge requests from this group assigned to me' do
assigned_to_me(:merge_requests).each do |issue|
- page.should have_content issue.title
+ page.should have_content issue.title[0..80]
end
end
end
And 'submit form with new group info' do
- fill_in 'group_name', :with => 'Samurai'
+ fill_in 'group_name', with: 'Samurai'
+ fill_in 'group_description', with: 'Tokugawa Shogunate'
click_button "Create group"
end
Then 'I should see newly created group' do
page.should have_content "Samurai"
+ page.should have_content "Tokugawa Shogunate"
page.should have_content "You will only see events from projects in this group"
end
end
Then 'I should see closed merge request "Bug NS-04"' do
- mr = MergeRequest.find_by_title("Bug NS-04")
- mr.closed?.should be_true
+ merge_request = MergeRequest.find_by_title!("Bug NS-04")
+ merge_request.closed?.should be_true
page.should have_content "Closed by"
end
end
And 'project "Shop" have "Bug NS-04" open merge request' do
- project = Project.find_by_name("Shop")
create(:merge_request,
title: "Bug NS-04",
project: project,
end
And 'project "Shop" have "Bug NS-05" open merge request with diffs inside' do
- project = Project.find_by_name("Shop")
create(:merge_request_with_diffs,
title: "Bug NS-05",
project: project,
end
And 'project "Shop" have "Feature NS-03" closed merge request' do
- project = Project.find_by_name("Shop")
create(:closed_merge_request,
title: "Feature NS-03",
project: project,
end
And 'I switch to the diff tab' do
- mr = MergeRequest.find_by_title("Bug NS-05")
- visit diffs_project_merge_request_path(mr.project, mr)
+ visit diffs_project_merge_request_path(project, merge_request)
end
And 'I switch to the merge request\'s comments tab' do
- mr = MergeRequest.find_by_title("Bug NS-05")
- visit project_merge_request_path(mr.project, mr)
+ visit project_merge_request_path(project, merge_request)
end
And 'I click on the first commit in the merge request' do
- mr = MergeRequest.find_by_title("Bug NS-05")
- click_link mr.commits.first.short_id(8)
+
+ click_link merge_request.commits.first.short_id(8)
end
And 'I leave a comment on the diff page' do
end
Then 'I should see a discussion has started on line 185' do
- mr = MergeRequest.find_by_title("Bug NS-05")
- first_commit = mr.commits.first
+ first_commit = merge_request.commits.first
first_diff = first_commit.diffs.first
page.should have_content "#{current_user.name} started a discussion on this merge request diff"
page.should have_content "#{first_diff.b_path}:L185"
end
Then 'I should see a discussion has started on commit bcf03b5de6c:L185' do
- mr = MergeRequest.find_by_title("Bug NS-05")
- first_commit = mr.commits.first
+ first_commit = merge_request.commits.first
first_diff = first_commit.diffs.first
page.should have_content "#{current_user.name} started a discussion on commit"
page.should have_content first_commit.short_id(8)
end
Then 'I should see a discussion has started on commit bcf03b5de6c' do
- mr = MergeRequest.find_by_title("Bug NS-05")
- first_commit = mr.st_commits.first
+ first_commit = merge_request.st_commits.first
first_diff = first_commit.diffs.first
page.should have_content "#{current_user.name} started a discussion on commit bcf03b5de6c"
page.should have_content first_commit.short_id(8)
page.should have_content "One comment to rule them all"
page.should have_content "#{first_diff.b_path}:L185"
end
+
+ def project
+ @project ||= Project.find_by_name!("Shop")
+ end
+
+ def merge_request
+ @merge_request ||= MergeRequest.find_by_title!("Bug NS-05")
+ end
end
Then 'page should have network graph' do
page.should have_content "Project Network Graph"
- within ".graph" do
- page.should have_content "master"
- end
+ page.should have_selector ".graph"
end
- And 'I visit project "Shop" network page' do
+ When 'I visit project "Shop" network page' do
# Stub Graph::JsonBuilder max_size to speed up test (10 commits vs. 650)
- Gitlab::Graph::JsonBuilder.stub(max_count: 10)
+ Graph::JsonBuilder.stub(max_count: 10)
project = Project.find_by_name("Shop")
visit project_graph_path(project, "master")
end
+
+ And 'page should select "master" in select box' do
+ page.should have_selector '#ref_chzn span', :text => "master"
+ end
+
+ And 'page should have "master" on graph' do
+ within '.graph' do
+ page.should have_content 'master'
+ end
+ end
+
+ And 'I switch ref to "stable"' do
+ page.select 'stable', :from => 'ref'
+ end
+
+ And 'page should select "stable" in select box' do
+ page.should have_selector '#ref_chzn span', :text => "stable"
+ end
+
+ And 'page should have "stable" on graph' do
+ within '.graph' do
+ page.should have_content 'stable'
+ end
+ end
+
+ And 'I looking for a commit by SHA of "v2.1.0"' do
+ within ".content .search" do
+ fill_in 'q', :with => '98d6492'
+ find('button').click
+ end
+ end
+
+ And 'page should have "v2.1.0" on graph' do
+ within '.graph' do
+ page.should have_content 'v2.1.0'
+ end
+ end
end
Given "I visit my project's network page" do
# Stub Graph::JsonBuilder max_size to speed up test (10 commits vs. 650)
- Gitlab::Graph::JsonBuilder.stub(max_count: 10)
+ Graph::JsonBuilder.stub(max_count: 10)
visit project_graph_path(@project, root_ref)
end
And 'I submit form with new team info' do
fill_in 'name', with: 'gitlab'
+
+ fill_in 'user_team_description', with: 'team description'
click_button 'Create team'
end
+ And 'I should see newly created team' do
+ page.should have_content "gitlab"
+ page.should have_content "team description"
+ end
+
Then 'I should be redirected to new team page' do
team = UserTeam.last
current_path.should == team_path(team)
Gitlab.config.gitlab_shell.stub(repos_path: Rails.root.join('tmp', 'test-git-base-path'))
FileUtils.rm_rf Gitlab.config.gitlab_shell.repos_path
FileUtils.mkdir_p Gitlab.config.gitlab_shell.repos_path
+ DatabaseCleaner.start
end
Spinach.hooks.after_scenario do
When I click to "New team" link
And I submit form with new team info
Then I should be redirected to new team page
+ Then I should see newly created team
Scenario: I should see team dashboard list
When I have teams with projects and members
module Entities
class User < Grape::Entity
expose :id, :username, :email, :name, :bio, :skype, :linkedin, :twitter,
- :dark_scheme, :theme_id, :blocked, :created_at, :extern_uid, :provider
+ :dark_scheme, :theme_id, :state, :created_at, :extern_uid, :provider
end
class UserBasic < Grape::Entity
- expose :id, :username, :email, :name, :blocked, :created_at
+ expose :id, :username, :email, :name, :state, :created_at
end
class UserLogin < UserBasic
:issues_enabled,
:wall_enabled,
:merge_requests_enabled,
- :wiki_enabled]
-
+ :wiki_enabled,
+ :namespace_id]
@project = ::Projects::CreateContext.new(current_user, attrs).execute
if @project.saved?
present @project, with: Entities::Project
@tree = TreeDecorator.new(@tree)
raise InvalidPathError if @tree.invalid?
- rescue NoMethodError, InvalidPathError
+ rescue RuntimeError, NoMethodError, InvalidPathError
not_found!
end
end
password_confirmation: password,
projects_limit: Gitlab.config.gitlab.default_projects_limit,
}, as: :admin)
+ @user.save!
+
if Gitlab.config.omniauth['block_auto_created_users'] && !ldap
- @user.blocked = true
+ @user.block
end
- @user.save!
+
@user
end
+++ /dev/null
-require "grit"
-
-module Gitlab
- module Graph
- class Commit
- include ActionView::Helpers::TagHelper
-
- attr_accessor :time, :space, :refs, :parent_spaces
-
- def initialize(commit)
- @_commit = commit
- @time = -1
- @space = 0
- @parent_spaces = []
- end
-
- def method_missing(m, *args, &block)
- @_commit.send(m, *args, &block)
- end
-
- def to_graph_hash
- h = {}
- h[:parents] = self.parents.collect do |p|
- [p.id,0,0]
- end
- h[:author] = {
- name: author.name,
- email: author.email
- }
- h[:time] = time
- h[:space] = space
- h[:parent_spaces] = parent_spaces
- h[:refs] = refs.collect{|r|r.name}.join(" ") unless refs.nil?
- h[:id] = sha
- h[:date] = date
- h[:message] = message
- h
- end
-
- def add_refs(ref_cache, repo)
- if ref_cache.empty?
- repo.refs.each do |ref|
- ref_cache[ref.commit.id] ||= []
- ref_cache[ref.commit.id] << ref
- end
- end
- @refs = ref_cache[@_commit.id] if ref_cache.include?(@_commit.id)
- @refs ||= []
- end
- end
- end
-end
+++ /dev/null
-require "grit"
-
-module Gitlab
- module Graph
- class JsonBuilder
- attr_accessor :days, :commits, :ref_cache, :repo
-
- def self.max_count
- @max_count ||= 650
- end
-
- def initialize project, ref, commit
- @project = project
- @ref = ref
- @commit = commit
- @repo = project.repo
- @ref_cache = {}
-
- @commits = collect_commits
- @days = index_commits
- end
-
- def to_json(*args)
- {
- days: @days.compact.map { |d| [d.day, d.strftime("%b")] },
- commits: @commits.map(&:to_graph_hash)
- }.to_json(*args)
- end
-
- protected
-
- # Get commits from repository
- #
- def collect_commits
-
- @commits = Grit::Commit.find_all(repo, nil, {topo_order: true, max_count: self.class.max_count, skip: to_commit}).dup
-
- # Decorate with app/models/commit.rb
- @commits.map! { |commit| ::Commit.new(commit) }
-
- # Decorate with lib/gitlab/graph/commit.rb
- @commits.map! { |commit| Gitlab::Graph::Commit.new(commit) }
-
- # add refs to each commit
- @commits.each { |commit| commit.add_refs(ref_cache, repo) }
-
- @commits
- end
-
- # Method is adding time and space on the
- # list of commits. As well as returns date list
- # corelated with time set on commits.
- #
- # @param [Array<Graph::Commit>] commits to index
- #
- # @return [Array<TimeDate>] list of commit dates corelated with time on commits
- def index_commits
- days, times = [], []
- map = {}
-
- commits.reverse.each_with_index do |c,i|
- c.time = i
- days[i] = c.committed_date
- map[c.id] = c
- times[i] = c
- end
-
- @_reserved = {}
- days.each_index do |i|
- @_reserved[i] = []
- end
-
- commits_sort_by_ref.each do |commit|
- if map.include? commit.id then
- place_chain(map[commit.id], map)
- end
- end
-
- # find parent spaces for not overlap lines
- times.each do |c|
- c.parent_spaces.concat(find_free_parent_spaces(c, map, times))
- end
-
- days
- end
-
- # Skip count that the target commit is displayed in center.
- def to_commit
- commits = Grit::Commit.find_all(repo, nil, {topo_order: true})
- commit_index = commits.index do |c|
- c.id == @commit.id
- end
-
- if commit_index && (self.class.max_count / 2 < commit_index) then
- # get max index that commit is displayed in the center.
- commit_index - self.class.max_count / 2
- else
- 0
- end
- end
-
- def commits_sort_by_ref
- commits.sort do |a,b|
- if include_ref?(a)
- -1
- elsif include_ref?(b)
- 1
- else
- b.committed_date <=> a.committed_date
- end
- end
- end
-
- def include_ref?(commit)
- heads = commit.refs.select do |ref|
- ref.is_a?(Grit::Head) or ref.is_a?(Grit::Remote) or ref.is_a?(Grit::Tag)
- end
-
- heads.map! do |head|
- head.name
- end
-
- heads.include?(@ref)
- end
-
- def find_free_parent_spaces(commit, map, times)
- spaces = []
-
- commit.parents.each do |p|
- if map.include?(p.id) then
- parent = map[p.id]
-
- range = if commit.time < parent.time then
- commit.time..parent.time
- else
- parent.time..commit.time
- end
-
- space = if commit.space >= parent.space then
- find_free_parent_space(range, parent.space, 1, commit.space, times)
- else
- find_free_parent_space(range, parent.space, -1, parent.space, times)
- end
-
- mark_reserved(range, space)
- spaces << space
- end
- end
-
- spaces
- end
-
- def find_free_parent_space(range, space_base, space_step, space_default, times)
- if is_overlap?(range, times, space_default) then
- find_free_space(range, space_base, space_step)
- else
- space_default
- end
- end
-
- def is_overlap?(range, times, overlap_space)
- range.each do |i|
- if i != range.first &&
- i != range.last &&
- times[i].space == overlap_space then
-
- return true;
- end
- end
-
- false
- end
-
- # Add space mark on commit and its parents
- #
- # @param [Graph::Commit] the commit object.
- # @param [Hash<String,Graph::Commit>] map of commits
- def place_chain(commit, map, parent_time = nil)
- leaves = take_left_leaves(commit, map)
- if leaves.empty?
- return
- end
- # and mark it as reserved
- min_time = leaves.last.time
- max_space = 1
- parents = leaves.last.parents.collect
- parents.each do |p|
- if map.include? p.id
- parent = map[p.id]
- if parent.time < min_time
- min_time = parent.time
- end
- if max_space < parent.space then
- max_space = parent.space
- end
- end
- end
- if parent_time.nil?
- max_time = leaves.first.time
- else
- max_time = parent_time - 1
- end
-
- time_range = leaves.last.time..leaves.first.time
- space = find_free_space(time_range, max_space, 2)
- leaves.each{|l| l.space = space}
-
- mark_reserved(min_time..max_time, space)
-
- # Visit branching chains
- leaves.each do |l|
- parents = l.parents.collect.select{|p| map.include? p.id and map[p.id].space.zero?}
- for p in parents
- place_chain(map[p.id], map, l.time)
- end
- end
- end
-
- def mark_reserved(time_range, space)
- for day in time_range
- @_reserved[day].push(space)
- end
- end
-
- def find_free_space(time_range, space_base, space_step)
- reserved = []
- for day in time_range
- reserved += @_reserved[day]
- end
- reserved.uniq!
-
- space = space_base
- while reserved.include?(space) do
- space += space_step
- if space <= 0 then
- space_step *= -1
- space = space_base + space_step
- end
- end
-
- space
- end
-
- # Takes most left subtree branch of commits
- # which don't have space mark yet.
- #
- # @param [Graph::Commit] the commit object.
- # @param [Hash<String,Graph::Commit>] map of commits
- #
- # @return [Array<Graph::Commit>] list of branch commits
- def take_left_leaves(commit, map)
- leaves = []
- leaves.push(commit) if commit.space.zero?
-
- while true
- return leaves if commit.parents.count.zero?
- return leaves unless map.include? commit.parents.first.id
-
- commit = map[commit.parents.first.id]
-
- return leaves unless commit.space.zero?
-
- leaves.push(commit)
- end
- end
- end
- end
-end
# >> gfm(":trollface:")
# => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":trollface:\" />
module Markdown
+ include IssuesHelper
+
attr_reader :html_options
# Public: Parse the provided text with GitLab-Flavored Markdown
end
def reference_issue(identifier)
- if issue = @project.issues.where(id: identifier).first
- link_to("##{identifier}", project_issue_url(@project, issue), html_options.merge(title: "Issue: #{issue.title}", class: "gfm gfm-issue #{html_options[:class]}"))
+ if @project.issue_exists? identifier
+ url = url_for_issue(identifier)
+ title = title_for_issue(identifier)
+
+ link_to("##{identifier}", url, html_options.merge(title: "Issue: #{title}", class: "gfm gfm-issue #{html_options[:class]}"))
end
end
namespace :sidekiq do
desc "GITLAB | Stop sidekiq"
task :stop do
- run "bundle exec sidekiqctl stop #{pidfile}"
+ system "bundle exec sidekiqctl stop #{pidfile}"
end
desc "GITLAB | Start sidekiq"
task :start do
- run "nohup bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1 &"
+ system "nohup bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1 &"
end
-
+
desc "GITLAB | Start sidekiq with launchd on Mac OS X"
task :launchd do
- run "bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1"
+ system "bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1"
end
-
+
def pidfile
Rails.root.join("tmp", "pids", "sidekiq.pid")
end
creator
end
+ factory :redmine_project, parent: :project do
+ issues_tracker { "redmine" }
+ issues_tracker_id { "project_name_in_redmine" }
+ end
+
factory :group do
sequence(:name) { |n| "group#{n}" }
path { name.downcase.gsub(/\s/, '_') }
FactoryGirl.define do
factory :user_team do
sequence(:name) { |n| "team#{n}" }
+ sequence(:description) { |n| "team_description#{n}" }
path { name.downcase.gsub(/\s/, '_') }
owner
end
user = User.last
email = ActionMailer::Base.deliveries.last
email.subject.should have_content("Account was created")
- email.body.should have_content(user.email)
- email.body.should have_content(@password)
+ email.text_part.body.should have_content(user.email)
+ email.text_part.body.should have_content(@password)
end
end
user = User.last
email = ActionMailer::Base.deliveries.last
email.subject.should have_content("Account was created")
- email.body.should have_content(user.email)
- email.body.should_not have_content(@password)
+ email.text_part.body.should have_content(user.email)
+ email.text_part.body.should_not have_content(@password)
end
end
end
describe GitlabMarkdownHelper do
include ApplicationHelper
+ include IssuesHelper
let!(:project) { create(:project) }
--- /dev/null
+require "spec_helper"
+
+describe IssuesHelper do
+ let(:project) { create :project }
+ let(:issue) { create :issue, project: project }
+ let(:ext_project) { create :redmine_project }
+
+ describe :title_for_issue do
+ it "should return issue title if used internal tracker" do
+ @project = project
+ title_for_issue(issue.id).should eq issue.title
+ end
+
+ it "should always return empty string if used external tracker" do
+ @project = ext_project
+ title_for_issue(rand(100)).should eq ""
+ end
+
+ it "should always return empty string if project nil" do
+ @project = nil
+
+ title_for_issue(rand(100)).should eq ""
+ end
+ end
+
+ describe :url_for_project_issues do
+ let(:project_url) { Gitlab.config.issues_tracker.redmine.project_url}
+ let(:ext_expected) do
+ project_url.gsub(':project_id', ext_project.id.to_s)
+ .gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
+ end
+ let(:int_expected) { polymorphic_path([project]) }
+
+ it "should return internal path if used internal tracker" do
+ @project = project
+ url_for_project_issues.should match(int_expected)
+ end
+
+ it "should return path to external tracker" do
+ @project = ext_project
+
+ url_for_project_issues.should match(ext_expected)
+ end
+
+ it "should return empty string if project nil" do
+ @project = nil
+
+ url_for_project_issues.should eq ""
+ end
+ end
+
+ describe :url_for_issue do
+ let(:issue_id) { 3 }
+ let(:issues_url) { Gitlab.config.issues_tracker.redmine.issues_url}
+ let(:ext_expected) do
+ issues_url.gsub(':id', issue_id.to_s)
+ .gsub(':project_id', ext_project.id.to_s)
+ .gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
+ end
+ let(:int_expected) { polymorphic_path([project, issue]) }
+
+ it "should return internal path if used internal tracker" do
+ @project = project
+ url_for_issue(issue.id).should match(int_expected)
+ end
+
+ it "should return path to external tracker" do
+ @project = ext_project
+
+ url_for_issue(issue_id).should match(ext_expected)
+ end
+
+ it "should return empty string if project nil" do
+ @project = nil
+
+ url_for_issue(issue.id).should eq ""
+ end
+ end
+end
it { should ensure_inclusion_of(:wall_enabled).in_array([true, false]) }
it { should ensure_inclusion_of(:merge_requests_enabled).in_array([true, false]) }
it { should ensure_inclusion_of(:wiki_enabled).in_array([true, false]) }
+ it { should ensure_length_of(:issues_tracker_id).is_within(0..255) }
it "should not allow new projects beyond user limits" do
project.stub(:creator).and_return(double(can_create_project?: false, projects_limit: 1))
Project.new(path: "empty").repository.should be_nil
end
end
+
+ describe :issue_exists? do
+ let(:project) { create(:project) }
+ let(:existed_issue) { create(:issue, project: project) }
+ let(:not_existed_issue) { create(:issue) }
+ let(:ext_project) { create(:redmine_project) }
+
+ it "should be true or if used internal tracker and issue exists" do
+ project.issue_exists?(existed_issue.id).should be_true
+ end
+
+ it "should be false or if used internal tracker and issue not exists" do
+ project.issue_exists?(not_existed_issue.id).should be_false
+ end
+
+ it "should always be true if used other tracker" do
+ ext_project.issue_exists?(rand(100)).should be_true
+ end
+ end
+
+ describe :used_default_issues_tracker? do
+ let(:project) { create(:project) }
+ let(:ext_project) { create(:redmine_project) }
+
+ it "should be true if used internal tracker" do
+ project.used_default_issues_tracker?.should be_true
+ end
+
+ it "should be false if used other tracker" do
+ ext_project.used_default_issues_tracker?.should be_false
+ end
+ end
+
+ describe :can_have_issues_tracker_id? do
+ let(:project) { create(:project) }
+ let(:ext_project) { create(:redmine_project) }
+
+ it "should be true for projects with external issues tracker if issues enabled" do
+ ext_project.can_have_issues_tracker_id?.should be_true
+ end
+
+ it "should be false for projects with internal issue tracker if issues enabled" do
+ project.can_have_issues_tracker_id?.should be_false
+ end
+
+ it "should be always false if issues disbled" do
+ project.issues_enabled = false
+ ext_project.issues_enabled = false
+
+ project.can_have_issues_tracker_id?.should be_false
+ ext_project.can_have_issues_tracker_id?.should be_false
+ end
+ end
end
# dark_scheme :boolean default(FALSE), not null
# theme_id :integer default(1), not null
# bio :string(255)
-# blocked :boolean default(FALSE), not null
+# state :string(255) default(FALSE), not null
# failed_attempts :integer default(0)
# locked_at :datetime
# extern_uid :string(255)
it "should block user" do
user.block
- user.blocked.should be_true
+ user.blocked?.should be_true
end
end
User.delete_all
@user = create :user
@admin = create :user, admin: true
- @blocked = create :user, blocked: true
+ @blocked = create :user, state: :blocked
end
it { User.filter("admins").should == [@admin] }
create(:user)
end
+ it 'no email for external' do
+ Notify.should_not_receive(:new_user_email)
+ create(:user, extern_uid: '32442eEfsafada')
+ end
+
it 'trigger logger' do
+ user = double(:user, id: 42, password: 'P@ssword!', name: 'John', email: 'u@mail.local', extern_uid?: false)
Gitlab::AppLogger.should_receive(:info)
create(:user)
end