Compare commits
10 commits
master
...
5-0-stable
Author | SHA1 | Date | |
---|---|---|---|
|
8b761574bd | ||
|
34136d381f | ||
|
65156e3693 | ||
|
6a09b21df6 | ||
|
4606380316 | ||
|
017c19bbe4 | ||
|
0c7f426037 | ||
|
5c49cf6ca1 | ||
|
0e4b2395a9 | ||
|
ef968fd0c8 |
208 changed files with 2229 additions and 2785 deletions
2
.rspec
2
.rspec
|
@ -1 +1 @@
|
|||
--colour --drb
|
||||
--colour
|
||||
|
|
|
@ -8,7 +8,7 @@ branches:
|
|||
only:
|
||||
- 'master'
|
||||
rvm:
|
||||
- 1.9.3-p392
|
||||
- 1.9.3-p327
|
||||
services:
|
||||
- mysql
|
||||
- postgresql
|
||||
|
|
15
CHANGELOG
15
CHANGELOG
|
@ -1,10 +1,3 @@
|
|||
v 5.1.0
|
||||
- You can login with email or username now
|
||||
- Corrected project transfer rollback when repository cannot be moved
|
||||
- Move both repo and wiki when project transfer requrested
|
||||
- Admin area: project editing was removed from admin namespace
|
||||
- Access: admin user has now access to any project.
|
||||
|
||||
v 5.0.0
|
||||
- Replaced gitolite with gitlab-shell
|
||||
- Removed gitolite-related libraries
|
||||
|
@ -21,7 +14,7 @@ v 5.0.0
|
|||
- Add validations for Group and Team names
|
||||
- Restyle team page for project
|
||||
- Update capybara, rspec-rails, poltergeist to recent versions
|
||||
- Wiki on git using Gollum
|
||||
- Wiki on git using Gollum
|
||||
- Added Solarized Dark theme for code review
|
||||
- Dont show user emails in autocomplete lists, profile pages
|
||||
- Added settings tab for group, team, project
|
||||
|
@ -31,7 +24,7 @@ v 5.0.0
|
|||
- Fixed search field on projects page
|
||||
- Added teams to search autocomplete
|
||||
- Move groups and teams on dashboard sidebar to sub-tabs
|
||||
- API: improved return codes and docs. (Felix Gilcher, Sebastian Ziebell)
|
||||
- API: improved return codes and docs.
|
||||
- Redesign wall to be more like chat
|
||||
- Snippets, Wall features are disabled by default for new projects
|
||||
|
||||
|
@ -63,7 +56,7 @@ v 4.1.0
|
|||
- cleanup rake tasks
|
||||
- fix backup/restore
|
||||
- scss cleanup
|
||||
- show preview for note images
|
||||
- show preview for note images
|
||||
- improved network-graph
|
||||
- get rid of app/roles/
|
||||
- added new classes Team, Repository
|
||||
|
@ -77,7 +70,7 @@ v 4.1.0
|
|||
v 4.0.0
|
||||
- Remove project code and path from API. Use id instead
|
||||
- Return valid clonable url to repo for web hook
|
||||
- Fixed backup issue
|
||||
- Fixed backup issue
|
||||
- Reorganized settings
|
||||
- Fixed commits compare
|
||||
- Refactored scss
|
||||
|
|
|
@ -1,18 +1,35 @@
|
|||
# Contribute to GitLab
|
||||
|
||||
This guide details how to use pull requests and the issues to improve GitLab.
|
||||
If you have a question or want to contribute to GitLab this guide show you the appropriate channel to use.
|
||||
|
||||
## Closing policy for pull requests and issues
|
||||
## Ruling out common errors
|
||||
|
||||
Pull requests and issues not in line with the guidelines listed in this document will be closed with just a link to this paragraph. GitLab is a popular open source project and the capacity to deal with issues and pull requests is limited. To get support for your problems please use other channels as detailed in [the getting help section of the readme](https://github.com/gitlabhq/gitlabhq#getting-help). Professional [support subscriptions](http://www.gitlab.com/subscription/) and [consulting services](http://www.gitlab.com/consultancy/) are available from [GitLab.com](http://www.gitlab.com/).
|
||||
Some errors are common and it may so happen, that you are not the only one who stumbled over a particular issue. We have [collected several of those and documented quick solutions](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide) for them.
|
||||
|
||||
## Support forum
|
||||
|
||||
Please visit our [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq) for any kind of question regarding the usage or adiministration/configuration of GitLab.
|
||||
|
||||
### Use the support forum if ...
|
||||
|
||||
* You get permission denied errors
|
||||
* You can't see your repos
|
||||
* You have issues cloning, pulling or pushing
|
||||
* You have issues with web_hooks not firing
|
||||
|
||||
**Search** for similar issues before posting your own, there's a good chance somebody else had the same issue you have now and had it resolved.
|
||||
|
||||
## Paid support
|
||||
|
||||
Community support in the [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq) is done by volunteers. A support subscription is available from [GitLab.com](http://blog.gitlab.com/subscription/)
|
||||
|
||||
## Feature suggestions
|
||||
|
||||
Feature suggestions don't belong in issues but can go to [Feedback forum](http://gitlab.uservoice.com/forums/176466-general) where they can be voted on.
|
||||
|
||||
## Pull requests
|
||||
|
||||
We welcome pull request with improvements to GitLab code and/or documentation. The issues we would really like a pull request for are listed with the [status 'accepting merge/pull requests' on our feedback forum](http://feedback.gitlab.com/forums/176466-general/status/796455) but other improvements are also welcome.
|
||||
|
||||
### Pull request guidelines
|
||||
|
||||
If you can please submit a pull request with the fix including tests. The workflow to make a pull request is as follows:
|
||||
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
|
||||
|
@ -34,20 +51,26 @@ We will accept pull requests if:
|
|||
|
||||
For examples of feedback on pull requests please look at already [closed pull requests](https://github.com/gitlabhq/gitlabhq/pulls?direction=desc&page=1&sort=created&state=closed).
|
||||
|
||||
## Issue tracker
|
||||
## Submitting via GitHub's issue tracker
|
||||
|
||||
The [issue tracker](https://github.com/gitlabhq/gitlabhq/issues) is only for obvious bugs or misbehavior in the master branch of GitLab. When submitting an issue please conform to the issue submission guidelines listed below.
|
||||
* For obvious bugs or misbehavior in GitLab in the master branch. Please include the revision id and a reproducible test case.
|
||||
* For problematic or insufficient documentation. Please give a suggestion on how to improve it.
|
||||
|
||||
Please send a pull request with a tested solution or a pull request with a failing test instead of opening an issue if you can. If you're unsure where to post, post to the [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq) first. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there.
|
||||
If you're unsure where to post, post it to the [Support Forum](https://groups.google.com/forum/#!forum/gitlabhq) first.
|
||||
There are a lot of helpful GitLab users there who may be able to help you quickly.
|
||||
If your particular issue turns out to be a bug, it will find its way from there to the [issue tracker on GitHub](https://github.com/gitlabhq/gitlabhq/issues).
|
||||
|
||||
### Issue tracker guidelines
|
||||
### When submitting an issue
|
||||
|
||||
**Search** for similar entries before submitting your own, there's a good chance somebody else had the same issue or idea. Show your support with `:+1:` and/or join the discussion.
|
||||
|
||||
* Summarize your issue in one sentence (what goes wrong, what did you expect to happen)
|
||||
* Describe your issue in detail
|
||||
* How can we reproduce the issue on the [GitLab Vagrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm) (start with: vagrant destroy && vagrant up && vagrant ssh)
|
||||
* Add the last commit sha1 of the GitLab version you used to replicate the issue
|
||||
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
|
||||
* Link to the line of code that might be responsible for the problem
|
||||
* Describe your setup (use relevant parts from `sudo -u gitlab -H bundle exec rake gitlab:env:info`)
|
||||
|
||||
## Thank you!
|
||||
|
||||
By taking the time to use the right channel, you help the development team to organize and prioritize issues and suggestions in order to make GitLab a better product for us all.
|
||||
|
|
14
Gemfile
14
Gemfile
|
@ -23,8 +23,8 @@ gem 'omniauth-github'
|
|||
|
||||
# Extracting information from a git repository
|
||||
# Since gollum requires grit we cannot use gitlab-grit gem name any more. Use grit instead
|
||||
gem "grit", '~> 2.5.0', git: 'https://github.com/gitlabhq/grit.git', ref: '42297cdcee16284d2e4eff23d41377f52fc28b9d'
|
||||
gem 'grit_ext', '~> 0.8.1'
|
||||
gem "grit", '~> 2.5.0', git: 'https://github.com/gitlabhq/grit.git', ref: 'c40a32432616a07fa7fc3c32c24ab73ad6a9718f'
|
||||
gem 'grit_ext', '~> 0.6.2'
|
||||
|
||||
# Ruby/Rack Git Smart-HTTP Server Handler
|
||||
gem 'gitlab-grack', '~> 1.0.0', require: 'grack'
|
||||
|
@ -105,7 +105,7 @@ gem 'settingslogic'
|
|||
# github-linquist needs pygments 0.4.2 but Gollum 2.4.11
|
||||
# requires pygments 0.3.2. The latest master Gollum has been updated
|
||||
# to use pygments 0.4.2. Change this after next Gollum release.
|
||||
gem "gollum", "~> 2.4.0", git: "https://github.com/gollum/gollum.git", ref: "5dcd3c8c8f"
|
||||
gem "gollum", "~> 2.4.0", git: "git://github.com/gollum/gollum.git", ref: "5dcd3c8c8f"
|
||||
|
||||
# Misc
|
||||
gem "foreman"
|
||||
|
@ -154,9 +154,9 @@ end
|
|||
group :development, :test do
|
||||
gem 'coveralls', require: false
|
||||
gem 'rails-dev-tweaks'
|
||||
gem 'spinach-rails'
|
||||
gem "rspec-rails"
|
||||
gem "capybara"
|
||||
gem 'spinach-rails', '0.2.0'
|
||||
gem "rspec-rails", '2.12.2'
|
||||
gem "capybara", '2.0.2'
|
||||
gem "pry"
|
||||
gem "awesome_print"
|
||||
gem "database_cleaner"
|
||||
|
@ -174,8 +174,6 @@ group :development, :test do
|
|||
|
||||
# PhantomJS driver for Capybara
|
||||
gem 'poltergeist', '1.1.0'
|
||||
|
||||
gem 'spork', '~> 1.0rc'
|
||||
end
|
||||
|
||||
group :test do
|
||||
|
|
142
Gemfile.lock
142
Gemfile.lock
|
@ -1,29 +1,5 @@
|
|||
GIT
|
||||
remote: https://github.com/ctran/annotate_models.git
|
||||
revision: be4e26825b521f0b2d86b181e2dff89901aa9b1e
|
||||
specs:
|
||||
annotate (2.6.0.beta1)
|
||||
activerecord (>= 2.3.0)
|
||||
rake (>= 0.8.7)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/gitlabhq/grit.git
|
||||
revision: 42297cdcee16284d2e4eff23d41377f52fc28b9d
|
||||
ref: 42297cdcee16284d2e4eff23d41377f52fc28b9d
|
||||
specs:
|
||||
grit (2.5.0)
|
||||
diff-lcs (~> 1.1)
|
||||
mime-types (~> 1.15)
|
||||
posix-spawn (~> 0.3.6)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/gitlabhq/raphael-rails.git
|
||||
revision: cb2c92a040b9b941a5f1aa1ea866cc26e944fe58
|
||||
specs:
|
||||
raphael-rails (2.1.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/gollum/gollum.git
|
||||
remote: git://github.com/gollum/gollum.git
|
||||
revision: 5dcd3c8c8f68158e43ff79861279088ee56d0ebe
|
||||
ref: 5dcd3c8c8f
|
||||
specs:
|
||||
|
@ -39,6 +15,30 @@ GIT
|
|||
stringex (~> 1.5.1)
|
||||
useragent (~> 0.4.16)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/ctran/annotate_models.git
|
||||
revision: be4e26825b521f0b2d86b181e2dff89901aa9b1e
|
||||
specs:
|
||||
annotate (2.6.0.beta1)
|
||||
activerecord (>= 2.3.0)
|
||||
rake (>= 0.8.7)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/gitlabhq/grit.git
|
||||
revision: c40a32432616a07fa7fc3c32c24ab73ad6a9718f
|
||||
ref: c40a32432616a07fa7fc3c32c24ab73ad6a9718f
|
||||
specs:
|
||||
grit (2.5.0)
|
||||
diff-lcs (~> 1.1)
|
||||
mime-types (~> 1.15)
|
||||
posix-spawn (~> 0.3.6)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/gitlabhq/raphael-rails.git
|
||||
revision: cb2c92a040b9b941a5f1aa1ea866cc26e944fe58
|
||||
specs:
|
||||
raphael-rails (2.1.0)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
|
@ -105,7 +105,7 @@ GEM
|
|||
thor (~> 0.14)
|
||||
code_analyzer (0.3.1)
|
||||
sexp_processor
|
||||
coderay (1.0.9)
|
||||
coderay (1.0.8)
|
||||
coffee-rails (3.2.2)
|
||||
coffee-script (>= 2.2.0)
|
||||
railties (~> 3.2.0)
|
||||
|
@ -122,7 +122,7 @@ GEM
|
|||
rest-client
|
||||
simplecov (>= 0.7)
|
||||
thor
|
||||
crack (0.3.2)
|
||||
crack (0.3.1)
|
||||
daemons (1.1.9)
|
||||
database_cleaner (0.9.1)
|
||||
debug_inspector (0.0.2)
|
||||
|
@ -132,7 +132,7 @@ GEM
|
|||
orm_adapter (~> 0.1)
|
||||
railties (~> 3.1)
|
||||
warden (~> 1.2.1)
|
||||
diff-lcs (1.2.1)
|
||||
diff-lcs (1.1.3)
|
||||
draper (1.1.0)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
|
@ -147,7 +147,7 @@ GEM
|
|||
eventmachine (1.0.0)
|
||||
execjs (1.4.0)
|
||||
multi_json (~> 1.0)
|
||||
facter (1.6.18)
|
||||
facter (1.6.17)
|
||||
factory_girl (4.1.0)
|
||||
activesupport (>= 3.0.0)
|
||||
factory_girl_rails (4.1.0)
|
||||
|
@ -200,16 +200,15 @@ GEM
|
|||
grape-entity (0.2.0)
|
||||
activesupport
|
||||
multi_json (>= 1.3.2)
|
||||
grit_ext (0.8.1)
|
||||
grit_ext (0.6.2)
|
||||
charlock_holmes (~> 0.6.9)
|
||||
growl (1.0.3)
|
||||
guard (1.6.2)
|
||||
listen (>= 0.6.0)
|
||||
guard (1.5.4)
|
||||
listen (>= 0.4.2)
|
||||
lumberjack (>= 1.0.2)
|
||||
pry (>= 0.9.10)
|
||||
terminal-table (>= 1.4.3)
|
||||
thor (>= 0.14.6)
|
||||
guard-rspec (2.5.1)
|
||||
guard-rspec (2.1.2)
|
||||
guard (>= 1.1)
|
||||
rspec (~> 2.11)
|
||||
guard-spinach (0.0.2)
|
||||
|
@ -249,9 +248,9 @@ GEM
|
|||
addressable (~> 2.3)
|
||||
letter_opener (1.0.0)
|
||||
launchy (>= 2.0.4)
|
||||
libv8 (3.11.8.17)
|
||||
listen (0.7.3)
|
||||
lumberjack (1.0.3)
|
||||
libv8 (3.3.10.4)
|
||||
listen (0.5.3)
|
||||
lumberjack (1.0.2)
|
||||
mail (2.5.3)
|
||||
i18n (>= 0.4.0)
|
||||
mime-types (~> 1.16)
|
||||
|
@ -260,7 +259,7 @@ GEM
|
|||
mime-types (1.21)
|
||||
modernizr (2.6.2)
|
||||
sprockets (~> 2.0)
|
||||
multi_json (1.7.2)
|
||||
multi_json (1.7.1)
|
||||
multi_xml (0.5.3)
|
||||
multipart-post (1.1.5)
|
||||
mustache (0.99.4)
|
||||
|
@ -300,10 +299,11 @@ GEM
|
|||
http_parser.rb (~> 0.5.3)
|
||||
polyglot (0.3.3)
|
||||
posix-spawn (0.3.6)
|
||||
pry (0.9.12)
|
||||
progressbar (0.12.0)
|
||||
pry (0.9.10)
|
||||
coderay (~> 1.0.5)
|
||||
method_source (~> 0.8)
|
||||
slop (~> 3.4)
|
||||
slop (~> 3.3.1)
|
||||
pygments.rb (0.4.2)
|
||||
posix-spawn (~> 0.3.6)
|
||||
yajl-ruby (~> 1.1.0)
|
||||
|
@ -336,14 +336,14 @@ GEM
|
|||
rails-dev-tweaks (0.6.1)
|
||||
actionpack (~> 3.1)
|
||||
railties (~> 3.1)
|
||||
rails_best_practices (1.13.4)
|
||||
rails_best_practices (1.13.2)
|
||||
activesupport
|
||||
awesome_print
|
||||
code_analyzer
|
||||
colored
|
||||
erubis
|
||||
i18n
|
||||
ruby-progressbar
|
||||
progressbar
|
||||
railties (3.2.13)
|
||||
actionpack (= 3.2.13)
|
||||
activesupport (= 3.2.13)
|
||||
|
@ -352,14 +352,14 @@ GEM
|
|||
rdoc (~> 3.4)
|
||||
thor (>= 0.14.6, < 2.0)
|
||||
raindrops (0.10.0)
|
||||
rake (10.0.4)
|
||||
rake (10.0.3)
|
||||
rb-fsevent (0.9.2)
|
||||
rb-inotify (0.8.8)
|
||||
ffi (>= 0.5.0)
|
||||
rdoc (3.12.2)
|
||||
json (~> 1.4)
|
||||
redcarpet (2.2.2)
|
||||
redis (3.0.3)
|
||||
redis (3.0.2)
|
||||
redis-actionpack (3.2.3)
|
||||
actionpack (~> 3.2.3)
|
||||
redis-rack (~> 1.4.0)
|
||||
|
@ -378,32 +378,30 @@ GEM
|
|||
redis-store (~> 1.1.0)
|
||||
redis-store (1.1.3)
|
||||
redis (>= 2.2.0)
|
||||
ref (1.0.4)
|
||||
request_store (1.0.5)
|
||||
rest-client (1.6.7)
|
||||
mime-types (>= 1.16)
|
||||
rspec (2.13.0)
|
||||
rspec-core (~> 2.13.0)
|
||||
rspec-expectations (~> 2.13.0)
|
||||
rspec-mocks (~> 2.13.0)
|
||||
rspec-core (2.13.1)
|
||||
rspec-expectations (2.13.0)
|
||||
diff-lcs (>= 1.1.3, < 2.0)
|
||||
rspec-mocks (2.13.0)
|
||||
rspec-rails (2.13.0)
|
||||
rspec (2.12.0)
|
||||
rspec-core (~> 2.12.0)
|
||||
rspec-expectations (~> 2.12.0)
|
||||
rspec-mocks (~> 2.12.0)
|
||||
rspec-core (2.12.0)
|
||||
rspec-expectations (2.12.0)
|
||||
diff-lcs (~> 1.1.3)
|
||||
rspec-mocks (2.12.0)
|
||||
rspec-rails (2.12.2)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec-core (~> 2.13.0)
|
||||
rspec-expectations (~> 2.13.0)
|
||||
rspec-mocks (~> 2.13.0)
|
||||
ruby-progressbar (1.0.2)
|
||||
rspec-core (~> 2.12.0)
|
||||
rspec-expectations (~> 2.12.0)
|
||||
rspec-mocks (~> 2.12.0)
|
||||
rubyntlm (0.1.1)
|
||||
rubyzip (0.9.9)
|
||||
sanitize (2.0.3)
|
||||
nokogiri (>= 1.4.4, < 1.6)
|
||||
sass (3.2.7)
|
||||
sass-rails (3.2.6)
|
||||
sass (3.2.5)
|
||||
sass-rails (3.2.5)
|
||||
railties (~> 3.2.0)
|
||||
sass (>= 3.1.10)
|
||||
tilt (~> 1.3)
|
||||
|
@ -422,10 +420,10 @@ GEM
|
|||
rubyzip
|
||||
websocket (~> 1.0.4)
|
||||
settingslogic (2.0.9)
|
||||
sexp_processor (4.2.0)
|
||||
sexp_processor (4.1.3)
|
||||
shoulda-matchers (1.3.0)
|
||||
activesupport (>= 3.0.0)
|
||||
sidekiq (2.8.0)
|
||||
sidekiq (2.7.5)
|
||||
celluloid (~> 0.12.0)
|
||||
connection_pool (~> 1.0)
|
||||
multi_json (~> 1)
|
||||
|
@ -443,7 +441,7 @@ GEM
|
|||
slim (1.3.6)
|
||||
temple (~> 0.5.5)
|
||||
tilt (~> 1.3.3)
|
||||
slop (3.4.4)
|
||||
slop (3.3.3)
|
||||
spinach (0.7.0)
|
||||
colorize
|
||||
gherkin-ruby (~> 0.2.0)
|
||||
|
@ -451,7 +449,6 @@ GEM
|
|||
capybara (~> 2.0.0)
|
||||
railties (>= 3)
|
||||
spinach (>= 0.4)
|
||||
spork (1.0.0rc3)
|
||||
sprockets (2.2.2)
|
||||
hike (~> 1.2)
|
||||
multi_json (~> 1.0)
|
||||
|
@ -461,16 +458,14 @@ GEM
|
|||
state_machine (1.1.2)
|
||||
stringex (1.5.1)
|
||||
temple (0.5.5)
|
||||
terminal-table (1.4.5)
|
||||
test_after_commit (0.0.1)
|
||||
therubyracer (0.11.4)
|
||||
libv8 (~> 3.11.8.12)
|
||||
ref
|
||||
therubyracer (0.10.2)
|
||||
libv8 (~> 3.3.10)
|
||||
thin (1.5.0)
|
||||
daemons (>= 1.0.9)
|
||||
eventmachine (>= 0.12.6)
|
||||
rack (>= 1.0.0)
|
||||
thor (0.18.0)
|
||||
thor (0.17.0)
|
||||
tilt (1.3.6)
|
||||
timers (1.1.0)
|
||||
treetop (1.4.12)
|
||||
|
@ -508,7 +503,7 @@ DEPENDENCIES
|
|||
better_errors
|
||||
binding_of_caller
|
||||
bootstrap-sass (= 2.2.1.1)
|
||||
capybara
|
||||
capybara (= 2.0.2)
|
||||
carrierwave
|
||||
chosen-rails (= 0.9.8)
|
||||
coffee-rails (~> 3.2.2)
|
||||
|
@ -537,7 +532,7 @@ DEPENDENCIES
|
|||
grape (~> 0.3.1)
|
||||
grape-entity (~> 0.2.0)
|
||||
grit (~> 2.5.0)!
|
||||
grit_ext (~> 0.8.1)
|
||||
grit_ext (~> 0.6.2)
|
||||
growl
|
||||
guard-rspec
|
||||
guard-spinach
|
||||
|
@ -568,7 +563,7 @@ DEPENDENCIES
|
|||
rb-inotify
|
||||
redcarpet (~> 2.2.2)
|
||||
redis-rails
|
||||
rspec-rails
|
||||
rspec-rails (= 2.12.2)
|
||||
sass-rails (~> 3.2.5)
|
||||
sdoc
|
||||
seed-fu
|
||||
|
@ -580,8 +575,7 @@ DEPENDENCIES
|
|||
sinatra
|
||||
six
|
||||
slim
|
||||
spinach-rails
|
||||
spork (~> 1.0rc)
|
||||
spinach-rails (= 0.2.0)
|
||||
stamp
|
||||
state_machine
|
||||
test_after_commit
|
||||
|
|
82
README.md
82
README.md
|
@ -12,7 +12,7 @@
|
|||
|
||||
* powered by Ruby on Rails
|
||||
* completely free and open source (MIT license)
|
||||
* used by more than 10.000 organizations to keep their code secure
|
||||
* used by 10.000 organizations to keep their code secure
|
||||
|
||||
### Code status
|
||||
|
||||
|
@ -30,7 +30,7 @@
|
|||
|
||||
* GitLab.org community site: [Homepage](http://gitlab.org) [Screenshots](http://gitlab.org/screenshots/) [Blog](http://blog.gitlab.org/) [Demo](http://demo.gitlabhq.com/users/sign_in)
|
||||
|
||||
* GitLab.com commercial services: [Homepage](http://www.gitlab.com/) [Subscription](http://www.gitlab.com/subscription/) [Consultancy](http://www.gitlab.com/consultancy/) [GitLab Cloud](http://www.gitlab.com/cloud/) [Blog](http://blog.gitlab.com/)
|
||||
* GitLab.com commercial services: [Homepage](http://blog.gitlab.com/) [GitLab Cloud](http://blog.gitlab.com/cloud/) [Subscription](http://blog.gitlab.com/subscription/) [Consultancy](http://blog.gitlab.com/consultancy/) [Blog](http://blog.gitlab.com/blog/)
|
||||
|
||||
* GitLab CI: [Readme](https://github.com/gitlabhq/gitlab-ci/blob/master/README.md) of the GitLab open-source continuous integration server
|
||||
|
||||
|
@ -47,45 +47,22 @@
|
|||
|
||||
### Installation
|
||||
|
||||
#### Official production installation
|
||||
#### For production
|
||||
|
||||
Follow the installation guide for production server.
|
||||
|
||||
* [Installation guide for latest stable release (5.0)](https://github.com/gitlabhq/gitlabhq/blob/5-0-stable/doc/install/installation.md) - **Recommended**
|
||||
* [Installation guide for latest stable release (4.2)](https://github.com/gitlabhq/gitlabhq/blob/4-2-stable/doc/install/installation.md) - **Recommended**
|
||||
|
||||
* [Installation guide for the current master branch (5.1)](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md)
|
||||
* [Installation guide for the current master branch (5.0)](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md)
|
||||
|
||||
|
||||
#### Official development installation
|
||||
#### For development
|
||||
|
||||
If you want to contribute, please first read our [Contributing Guidelines](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) and then we suggest you to use the Vagrant virtual machine project to get an environment working with all dependencies.
|
||||
If you want to contribute, please first read our [Contributing Guidelines](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) and then we suggest you to use the Vagrant virtual machine project to get an environment working sandboxed and with all dependencies.
|
||||
|
||||
* [Vagrant virtual machine](https://github.com/gitlabhq/gitlab-vagrant-vm)
|
||||
|
||||
|
||||
#### Unsupported production installation
|
||||
|
||||
* [GitLab recipes](https://github.com/gitlabhq/gitlab-recipes) for setup on different platforms
|
||||
|
||||
* [Unofficial installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides)
|
||||
|
||||
* [BitNami one-click installers](http://bitnami.com/stack/gitlab)
|
||||
|
||||
* [TurnKey Linux virtual appliance](http://www.turnkeylinux.org/gitlab)
|
||||
|
||||
|
||||
### New versions and upgrading
|
||||
|
||||
Each month on the 22th a new version is released together with an upgrade guide.
|
||||
|
||||
* [Upgrade guides](https://github.com/gitlabhq/gitlabhq/wiki)
|
||||
|
||||
* [Changelog](https://github.com/gitlabhq/gitlabhq/blob/master/CHANGELOG)
|
||||
|
||||
* [Roadmap](https://github.com/gitlabhq/gitlabhq/blob/master/ROADMAP.md)
|
||||
|
||||
|
||||
### Getting started
|
||||
### 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
|
||||
|
||||
|
@ -123,35 +100,38 @@ Each month on the 22th a new version is released together with an upgrade guide.
|
|||
|
||||
bundle exec rake spinach
|
||||
|
||||
### Getting help
|
||||
|
||||
### GitLab interfaces
|
||||
* [Troubleshooting guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide)
|
||||
|
||||
* [Support forum](https://groups.google.com/forum/#!forum/gitlabhq)
|
||||
|
||||
* [Feedback and suggestions forum](http://gitlab.uservoice.com/forums/176466-general)
|
||||
|
||||
* [Support subscription](http://blog.gitlab.com/subscription/)
|
||||
|
||||
* [Consultancy](http://blog.gitlab.com/consultancy/)
|
||||
|
||||
### New versions and the API
|
||||
|
||||
Each month on the 22th a new version is released together with an upgrade guide.
|
||||
|
||||
* [Upgrade guides](https://github.com/gitlabhq/gitlabhq/wiki)
|
||||
|
||||
* [Roadmap](https://github.com/gitlabhq/gitlabhq/blob/master/ROADMAP.md)
|
||||
|
||||
### Other documentation
|
||||
|
||||
* [GitLab API](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/README.md)
|
||||
|
||||
* [Rake tasks](https://github.com/gitlabhq/gitlabhq/tree/master/doc/raketasks)
|
||||
|
||||
* [Directory structure](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/structure.md)
|
||||
|
||||
* [Databases](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/databases.md)
|
||||
|
||||
|
||||
### Getting help
|
||||
|
||||
* [Troubleshooting guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide) contains solutions to common problems.
|
||||
|
||||
* [Support forum](https://groups.google.com/forum/#!forum/gitlabhq) is the best place to ask questions. For example you can use it if you have questions about: permission denied errors, invisible repos, can't clone/pull/push or with web hooks that don't fire. Please search for similar issues before posting your own, there's a good chance somebody else had the same issue you have now and had it resolved. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there to a fix.
|
||||
|
||||
* [Feedback and suggestions forum](http://gitlab.uservoice.com/forums/176466-general) is the place to propose and discuss new features for GitLab.
|
||||
|
||||
* [Support subscription](http://www.gitlab.com/subscription/) connect you to the knowledge of GitLab experts that will resolve your issues and answer your questions.
|
||||
|
||||
* [Consultancy](http://www.gitlab.com/consultancy/) allows you hire GitLab exports for installations, upgrades and customizations.
|
||||
|
||||
* [Contributing guide](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md) describes how to submit pull requests and issues. Pull requests and issues not in line with the guidelines in this document will be closed without comment.
|
||||
|
||||
* [GitLab recipes](https://github.com/gitlabhq/gitlab-recipes)
|
||||
|
||||
### Getting in touch
|
||||
|
||||
* [Contributing guide](https://github.com/gitlabhq/gitlabhq/blob/master/CONTRIBUTING.md)
|
||||
|
||||
* [Core team](https://github.com/gitlabhq?tab=members)
|
||||
|
||||
* [Contributors](https://github.com/gitlabhq/gitlabhq/graphs/contributors)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
## GitLab Roadmap
|
||||
|
||||
### v5.1 April 22
|
||||
### v5.0 March 22
|
||||
|
||||
* Not decided yet.
|
||||
* Replace gitolite with gitlab-shell
|
||||
* Usability improvements
|
||||
* Notification improvements
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
5.1.0pre
|
||||
5.0.0
|
||||
|
|
|
@ -3,11 +3,3 @@ $ ->
|
|||
container = $(@).closest(".js-toggler-container")
|
||||
|
||||
container.toggleClass("on")
|
||||
|
||||
$("body").on "click", ".js-toggle-visibility-link", (e) ->
|
||||
$(@).find('i').
|
||||
toggleClass('icon-chevron-down').
|
||||
toggleClass('icon-chevron-up')
|
||||
container = $(".js-toggle-visibility-container")
|
||||
container.toggleClass("hide")
|
||||
e.preventDefault()
|
||||
|
|
|
@ -5,10 +5,6 @@ class BranchGraph
|
|||
@mspace = 0
|
||||
@parents = {}
|
||||
@colors = ["#000"]
|
||||
@offsetX = 120
|
||||
@offsetY = 20
|
||||
@unitTime = 30
|
||||
@unitSpace = 10
|
||||
@load()
|
||||
|
||||
load: ->
|
||||
|
@ -24,6 +20,8 @@ class BranchGraph
|
|||
|
||||
prepareData: (@days, @commits) ->
|
||||
@collectParents()
|
||||
@mtime += 4
|
||||
@mspace += 10
|
||||
|
||||
for c in @commits
|
||||
c.isParent = true if c.id of @parents
|
||||
|
@ -37,7 +35,6 @@ class BranchGraph
|
|||
@mspace = Math.max(@mspace, c.space)
|
||||
for p in c.parents
|
||||
@parents[p[0]] = true
|
||||
@mspace = Math.max(@mspace, p[1])
|
||||
|
||||
collectColors: ->
|
||||
k = 0
|
||||
|
@ -49,23 +46,25 @@ class BranchGraph
|
|||
k++
|
||||
|
||||
buildGraph: ->
|
||||
graphHeight = $(@element).height()
|
||||
graphWidth = $(@element).width()
|
||||
ch = Math.max(graphHeight, @offsetY + @unitTime * @mtime + 150)
|
||||
cw = Math.max(graphWidth, @offsetX + @unitSpace * @mspace + 300)
|
||||
@r = r = Raphael(@element.get(0), cw, ch)
|
||||
ch = @mspace * 20 + 100
|
||||
cw = Math.max(graphWidth, @mtime * 20 + 260)
|
||||
r = Raphael(@element.get(0), cw, ch)
|
||||
top = r.set()
|
||||
cuday = 0
|
||||
cumonth = ""
|
||||
barHeight = Math.max(graphHeight, @unitTime * @days.length + 320)
|
||||
|
||||
r.rect(0, 0, 26, barHeight).attr fill: "#222"
|
||||
r.rect(26, 0, 20, barHeight).attr fill: "#444"
|
||||
offsetX = 20
|
||||
offsetY = 60
|
||||
barWidth = Math.max(graphWidth, @days.length * 20 + 320)
|
||||
scrollLeft = cw
|
||||
@raphael = r
|
||||
r.rect(0, 0, barWidth, 20).attr fill: "#222"
|
||||
r.rect(0, 20, barWidth, 20).attr fill: "#444"
|
||||
|
||||
for day, mm in @days
|
||||
if cuday isnt day[0]
|
||||
# Dates
|
||||
r.text(36, @offsetY + @unitTime * mm, day[0])
|
||||
r.text(offsetX + mm * 20, 31, day[0])
|
||||
.attr(
|
||||
font: "12px Monaco, monospace"
|
||||
fill: "#DDD"
|
||||
|
@ -74,7 +73,7 @@ class BranchGraph
|
|||
|
||||
if cumonth isnt day[1]
|
||||
# Months
|
||||
r.text(13, @offsetY + @unitTime * mm, day[1])
|
||||
r.text(offsetX + mm * 20, 11, day[1])
|
||||
.attr(
|
||||
font: "12px Monaco, monospace"
|
||||
fill: "#EEE"
|
||||
|
@ -82,20 +81,61 @@ class BranchGraph
|
|||
cumonth = day[1]
|
||||
|
||||
for commit in @commits
|
||||
x = @offsetX + @unitSpace * (@mspace - commit.space)
|
||||
y = @offsetY + @unitTime * commit.time
|
||||
x = offsetX + 20 * commit.time
|
||||
y = offsetY + 10 * commit.space
|
||||
# Draw dot
|
||||
r.circle(x, y, 3).attr(
|
||||
fill: @colors[commit.space]
|
||||
stroke: "none"
|
||||
)
|
||||
|
||||
@drawDot(x, y, commit)
|
||||
# Draw lines
|
||||
for parent in commit.parents
|
||||
parentCommit = @preparedCommits[parent[0]]
|
||||
parentX = offsetX + 20 * parentCommit.time
|
||||
parentY1 = offsetY + 10 * parentCommit.space
|
||||
parentY2 = offsetY + 10 * parent[1]
|
||||
if parentCommit.space is commit.space and parentCommit.space is parent[1]
|
||||
r.path(["M", x, y, "L", parentX, parentY1]).attr(
|
||||
stroke: @colors[parentCommit.space]
|
||||
"stroke-width": 2
|
||||
)
|
||||
|
||||
@drawLines(x, y, commit)
|
||||
else if parentCommit.space < commit.space
|
||||
if y is parentY2
|
||||
r.path(["M", x - 5, y, "l-5,-2,0,4,5,-2", "L", x - 10, y, "L", x - 15, parentY2, "L", parentX + 5, parentY2, "L", parentX, parentY1]).attr(
|
||||
stroke: @colors[commit.space]
|
||||
"stroke-width": 2
|
||||
)
|
||||
|
||||
@appendLabel(x, y, commit.refs) if commit.refs
|
||||
else
|
||||
r.path(["M", x - 3, y - 6, "l-4,-3,4,-2,0,5", "L", x - 5, y - 10, "L", x - 10, parentY2, "L", parentX + 5, parentY2, "L", parentX, parentY1]).attr(
|
||||
stroke: @colors[commit.space]
|
||||
"stroke-width": 2
|
||||
)
|
||||
|
||||
@appendAnchor(top, commit, x, y)
|
||||
else
|
||||
r.path(["M", x - 3, y + 6, "l-4,3,4,2,0,-5", "L", x - 5, y + 10, "L", x - 10, parentY2, "L", parentX + 5, parentY2, "L", parentX, parentY1]).attr(
|
||||
stroke: @colors[parentCommit.space]
|
||||
"stroke-width": 2
|
||||
)
|
||||
|
||||
@markCommit(x, y, commit, graphHeight)
|
||||
@appendLabel x, y, commit.refs if commit.refs
|
||||
|
||||
# Mark commit and displayed in the center
|
||||
if commit.id is @options.commit_id
|
||||
r.path(["M", x, y - 5, "L", x + 4, y - 15, "L", x - 4, y - 15, "Z"]).attr(
|
||||
fill: "#000"
|
||||
"fill-opacity": .7
|
||||
stroke: "none"
|
||||
)
|
||||
|
||||
scrollLeft = x - graphWidth / 2
|
||||
|
||||
@appendAnchor top, commit, x, y
|
||||
|
||||
top.toFront()
|
||||
@element.scrollLeft scrollLeft
|
||||
@bindEvents()
|
||||
|
||||
bindEvents: ->
|
||||
|
@ -127,37 +167,35 @@ class BranchGraph
|
|||
element.scrollTop element.scrollTop() + 50 if event.keyCode is 40
|
||||
|
||||
appendLabel: (x, y, refs) ->
|
||||
r = @r
|
||||
r = @raphael
|
||||
shortrefs = refs
|
||||
# Truncate if longer than 15 chars
|
||||
shortrefs = shortrefs.substr(0, 15) + "…" if shortrefs.length > 17
|
||||
text = r.text(x + 4, y, shortrefs).attr(
|
||||
"text-anchor": "start"
|
||||
text = r.text(x + 5, y + 8 + 10, shortrefs).attr(
|
||||
font: "10px Monaco, monospace"
|
||||
fill: "#FFF"
|
||||
title: refs
|
||||
)
|
||||
textbox = text.getBBox()
|
||||
text.transform ["t", textbox.height / -4, textbox.width / 2 + 5, "r90"]
|
||||
# Create rectangle based on the size of the textbox
|
||||
rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr(
|
||||
rect = r.rect(x, y, textbox.width + 15, textbox.height + 5, 4).attr(
|
||||
fill: "#000"
|
||||
"fill-opacity": .5
|
||||
"fill-opacity": .7
|
||||
stroke: "none"
|
||||
)
|
||||
triangle = r.path(["M", x - 5, y, "L", x - 15, y - 4, "L", x - 15, y + 4, "Z"]).attr(
|
||||
triangle = r.path(["M", x, y + 5, "L", x + 4, y + 15, "L", x - 4, y + 15, "Z"]).attr(
|
||||
fill: "#000"
|
||||
"fill-opacity": .5
|
||||
"fill-opacity": .7
|
||||
stroke: "none"
|
||||
)
|
||||
|
||||
label = r.set(rect, text)
|
||||
label.transform(["t", -rect.getBBox().width - 15, 0])
|
||||
|
||||
# Rotate and reposition rectangle over text
|
||||
rect.transform ["r", 90, x, y, "t", 15, -9]
|
||||
# Set text to front
|
||||
text.toFront()
|
||||
|
||||
appendAnchor: (top, commit, x, y) ->
|
||||
r = @r
|
||||
r = @raphael
|
||||
options = @options
|
||||
anchor = r.circle(x, y, 10).attr(
|
||||
fill: "#000"
|
||||
|
@ -166,92 +204,18 @@ class BranchGraph
|
|||
).click(->
|
||||
window.open options.commit_url.replace("%s", commit.id), "_blank"
|
||||
).hover(->
|
||||
@tooltip = r.commitTooltip(x + 5, y, commit)
|
||||
@tooltip = r.commitTooltip(x, y + 5, commit)
|
||||
top.push @tooltip.insertBefore(this)
|
||||
, ->
|
||||
@tooltip and @tooltip.remove() and delete @tooltip
|
||||
)
|
||||
top.push anchor
|
||||
|
||||
drawDot: (x, y, commit) ->
|
||||
r = @r
|
||||
r.circle(x, y, 3).attr(
|
||||
fill: @colors[commit.space]
|
||||
stroke: "none"
|
||||
)
|
||||
r.rect(@offsetX + @unitSpace * @mspace + 10, y - 10, 20, 20).attr(
|
||||
fill: "url(#{commit.author.icon})"
|
||||
stroke: @colors[commit.space]
|
||||
"stroke-width": 2
|
||||
)
|
||||
r.text(@offsetX + @unitSpace * @mspace + 35, y, commit.message.split("\n")[0]).attr(
|
||||
"text-anchor": "start"
|
||||
font: "14px Monaco, monospace"
|
||||
)
|
||||
|
||||
drawLines: (x, y, commit) ->
|
||||
r = @r
|
||||
for parent, i in commit.parents
|
||||
parentCommit = @preparedCommits[parent[0]]
|
||||
parentY = @offsetY + @unitTime * parentCommit.time
|
||||
parentX1 = @offsetX + @unitSpace * (@mspace - parentCommit.space)
|
||||
parentX2 = @offsetX + @unitSpace * (@mspace - parent[1])
|
||||
|
||||
# Set line color
|
||||
if parentCommit.space <= commit.space
|
||||
color = @colors[commit.space]
|
||||
|
||||
else
|
||||
color = @colors[parentCommit.space]
|
||||
|
||||
# Build line shape
|
||||
if parent[1] is commit.space
|
||||
offset = [0, 5]
|
||||
arrow = "l-2,5,4,0,-2,-5,0,5"
|
||||
|
||||
else if parent[1] < commit.space
|
||||
offset = [3, 3]
|
||||
arrow = "l5,0,-2,4,-3,-4,4,2"
|
||||
|
||||
else
|
||||
offset = [-3, 3]
|
||||
arrow = "l-5,0,2,4,3,-4,-4,2"
|
||||
|
||||
# Start point
|
||||
route = ["M", x + offset[0], y + offset[1]]
|
||||
|
||||
# Add arrow if not first parent
|
||||
if i > 0
|
||||
route.push(arrow)
|
||||
|
||||
# Circumvent if overlap
|
||||
if commit.space isnt parentCommit.space or commit.space isnt parent[1]
|
||||
route.push(
|
||||
"L", parentX2, y + 10,
|
||||
"L", parentX2, parentY - 5,
|
||||
)
|
||||
|
||||
# End point
|
||||
route.push("L", parentX1, parentY)
|
||||
|
||||
r
|
||||
.path(route)
|
||||
.attr(
|
||||
stroke: color
|
||||
"stroke-width": 2)
|
||||
|
||||
markCommit: (x, y, commit, graphHeight) ->
|
||||
if commit.id is @options.commit_id
|
||||
r = @r
|
||||
r.path(["M", x + 5, y, "L", x + 15, y + 4, "L", x + 15, y - 4, "Z"]).attr(
|
||||
fill: "#000"
|
||||
"fill-opacity": .5
|
||||
stroke: "none"
|
||||
)
|
||||
# Displayed in the center
|
||||
@element.scrollTop(y - graphHeight / 2)
|
||||
|
||||
Raphael::commitTooltip = (x, y, commit) ->
|
||||
icon = undefined
|
||||
nameText = undefined
|
||||
idText = undefined
|
||||
messageText = undefined
|
||||
boxWidth = 300
|
||||
boxHeight = 200
|
||||
icon = @image(commit.author.icon, x, y, 20, 20)
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
$.fn.showAndHide = ->
|
||||
$(@).show().
|
||||
delay(3000).
|
||||
fadeOut()
|
||||
|
||||
$.fn.enableButton = ->
|
||||
$(@).removeAttr('disabled').
|
||||
removeClass('disabled')
|
||||
|
|
@ -7,8 +7,6 @@ window.slugify = (text) ->
|
|||
window.ajaxGet = (url) ->
|
||||
$.ajax({type: "GET", url: url, dataType: "script"})
|
||||
|
||||
window.showAndHide = (selector) ->
|
||||
|
||||
window.errorMessage = (message) ->
|
||||
ehtml = $("<p>")
|
||||
ehtml.addClass("error_message")
|
||||
|
|
|
@ -15,8 +15,6 @@ $ ->
|
|||
$(this).find('.update-failed').hide()
|
||||
|
||||
$('.update-username form').on 'ajax:complete', ->
|
||||
$(this).find('.btn-save').enableButton()
|
||||
$(this).find('.save-btn').removeAttr('disabled')
|
||||
$(this).find('.save-btn').removeClass('disabled')
|
||||
$(this).find('.loading-gif').hide()
|
||||
|
||||
$('.update-notifications').on 'ajax:complete', ->
|
||||
$(this).find('.btn-save').enableButton()
|
||||
|
|
|
@ -6,7 +6,7 @@ $ ->
|
|||
$('span.log_loading:first').removeClass('hide')
|
||||
|
||||
$('#tree-slider .tree-item-file-name a, .breadcrumb li > a').live "click", ->
|
||||
$("#tree-content-holder").hide("slide", { direction: "left" }, 400)
|
||||
$("#tree-content-holder").hide("slide", { direction: "left" }, 150)
|
||||
|
||||
# Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
|
||||
$("#tree-slider .tree-item").live 'click', (e) ->
|
||||
|
|
|
@ -58,26 +58,14 @@
|
|||
form.show()
|
||||
|
||||
renderNote: (note) ->
|
||||
template = Wall.noteTemplate()
|
||||
template = template.replace('{{author_name}}', note.author.name)
|
||||
template = template.replace('{{created_at}}', note.created_at)
|
||||
template = template.replace('{{text}}', linkify(sanitize(note.body)))
|
||||
author = '<strong class="wall-author">' + note.author.name + '</strong>'
|
||||
body = '<span class="wall-text">' + linkify(sanitize(note.body)) + '</span>'
|
||||
file = ''
|
||||
time = '<abbr class="timeago" title="' + note.created_at + '">' + note.created_at + '</time>'
|
||||
|
||||
if note.attachment
|
||||
file = '<i class="icon-paper-clip"/><a href="/files/note/' + note.id + '/' + note.attachment + '">' + note.attachment + '</a>'
|
||||
else
|
||||
file = ''
|
||||
template = template.replace('{{file}}', file)
|
||||
file = '<span class="wall-file"><a href="/files/note/' + note.id + '/' + note.attachment + '">' + note.attachment + '</a></span>'
|
||||
|
||||
html = '<li>' + author + body + file + time + '</li>'
|
||||
|
||||
|
||||
$('ul.notes').append(template)
|
||||
|
||||
noteTemplate: ->
|
||||
return '<li>
|
||||
<strong class="wall-author">{{author_name}}</strong>
|
||||
<span class="wall-text">
|
||||
{{text}}
|
||||
<span class="wall-file">{{file}}</span>
|
||||
</span>
|
||||
<abbr class="timeago" title="{{created_at}}">{{created_at}}</abbr>
|
||||
</li>'
|
||||
$('ul.notes').append(html)
|
||||
|
|
|
@ -100,9 +100,8 @@
|
|||
margin-top: 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
.btn-tiny {
|
||||
@include box-shadow(0 0px 0px 1px #f1f1f1);
|
||||
}
|
||||
|
||||
.nav-pills {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
.graph {
|
||||
background: #f1f1f1;
|
||||
cursor: move;
|
||||
height: 500px;
|
||||
height: 70%;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,64 +1,68 @@
|
|||
.main-nav {
|
||||
/*
|
||||
* Main Menu of Application
|
||||
*
|
||||
*/
|
||||
ul.main_menu {
|
||||
margin: auto;
|
||||
margin: 30px 0;
|
||||
margin-top: 10px;
|
||||
border-bottom: 1px solid #E1E1E1;
|
||||
|
||||
ul {
|
||||
margin: auto;
|
||||
height: 39px;
|
||||
height: 38px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
.count {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
overflow: hidden;
|
||||
.count {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
display: inline-block;
|
||||
height: 15px;
|
||||
margin: 0 0 0 5px;
|
||||
padding: 0 8px 1px 8px;
|
||||
height: auto;
|
||||
font-size: 0.82em;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
color: #777;
|
||||
}
|
||||
.label {
|
||||
background: $hover;
|
||||
text-shadow: none;
|
||||
color: $style_color;
|
||||
}
|
||||
li {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
display: table-cell;
|
||||
width: 1%;
|
||||
&.active {
|
||||
border-bottom: 3px solid #777;
|
||||
a {
|
||||
color: $style_color;
|
||||
font-weight: bolder;
|
||||
}
|
||||
top: -1px;
|
||||
display: inline-block;
|
||||
height: 15px;
|
||||
margin: 0 0 0 5px;
|
||||
padding: 0 8px 1px 8px;
|
||||
height: auto;
|
||||
font-size: 0.82em;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
color: #777;
|
||||
}
|
||||
.label {
|
||||
background: $hover;
|
||||
text-shadow: none;
|
||||
color: $style_color;
|
||||
}
|
||||
li {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
display: table-cell;
|
||||
width: 1%;
|
||||
border-bottom: 2px solid #EEE;
|
||||
&.active {
|
||||
border-bottom: 2px solid #474D57;
|
||||
a {
|
||||
color: $style_color;
|
||||
}
|
||||
}
|
||||
|
||||
&.home {
|
||||
a {
|
||||
i {
|
||||
font-size: 20px;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
}
|
||||
&.home {
|
||||
a {
|
||||
i {
|
||||
font-size: 20px;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
a {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
height: 36px;
|
||||
line-height: 34px;
|
||||
color: #777;
|
||||
text-shadow: 0 1px 1px white;
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
a {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
color: #777;
|
||||
text-shadow: 0 1px 1px white;
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* End of Main Menu
|
||||
*
|
||||
*/
|
||||
|
||||
|
|
|
@ -274,15 +274,6 @@ ul.notes {
|
|||
}
|
||||
|
||||
|
||||
.common-note-form {
|
||||
margin: 0;
|
||||
height: 140px;
|
||||
background: #F9F9F9;
|
||||
padding: 3px;
|
||||
padding-bottom: 25px;
|
||||
border: 1px solid #DDD;
|
||||
}
|
||||
|
||||
|
||||
.note-form-actions {
|
||||
background: #F9F9F9;
|
||||
|
@ -290,8 +281,8 @@ ul.notes {
|
|||
padding: 0 5px;
|
||||
|
||||
.note-form-option {
|
||||
margin-top: 10px;
|
||||
margin-left: 30px;
|
||||
margin-top: 8px;
|
||||
margin-left: 15px;
|
||||
@extend .pull-left;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,31 +14,12 @@
|
|||
|
||||
.notes {
|
||||
margin-bottom: 160px;
|
||||
background: #FFE;
|
||||
border: 1px solid #EED;
|
||||
|
||||
> li {
|
||||
@extend .clearfix;
|
||||
border-bottom: 1px solid #EED;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.wall-author {
|
||||
color: #666;
|
||||
float: left;
|
||||
font-size: 12px;
|
||||
width: 120px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wall-text {
|
||||
border-left: 1px solid #CCC;
|
||||
margin-left: 10px;
|
||||
padding-left: 10px;
|
||||
float: left;
|
||||
width: 75%;
|
||||
margin-right: 10px;
|
||||
border-right: 1px solid #CCC;
|
||||
padding-right: 5px
|
||||
}
|
||||
|
||||
.wall-file {
|
||||
|
|
|
@ -12,6 +12,7 @@ class CommitLoadContext < BaseContext
|
|||
commit = project.repository.commit(params[:id])
|
||||
|
||||
if commit
|
||||
commit = CommitDecorator.decorate(commit)
|
||||
line_notes = project.notes.for_commit_id(commit.id).inline
|
||||
|
||||
result[:commit] = commit
|
||||
|
|
|
@ -3,6 +3,8 @@ module Notes
|
|||
def execute
|
||||
note = project.notes.new(params[:note])
|
||||
note.author = current_user
|
||||
note.notify = params[:notify].present?
|
||||
note.notify_author = params[:notify_author].present?
|
||||
note.save
|
||||
note
|
||||
end
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
module Projects
|
||||
class TransferContext < BaseContext
|
||||
def execute(role = :default)
|
||||
namespace_id = params[:project].delete(:namespace_id)
|
||||
allowed_transfer = can?(current_user, :change_namespace, project) || role == :admin
|
||||
|
||||
if allowed_transfer && namespace_id.present?
|
||||
if namespace_id == Namespace.global_id
|
||||
if project.namespace.present?
|
||||
# Transfer to global namespace from anyone
|
||||
project.transfer(nil)
|
||||
end
|
||||
elsif namespace_id.to_i != project.namespace_id
|
||||
# Transfer to someone namespace
|
||||
namespace = Namespace.find(namespace_id)
|
||||
project.transfer(namespace)
|
||||
end
|
||||
end
|
||||
|
||||
rescue ProjectTransferService::TransferError => ex
|
||||
project.reload
|
||||
project.errors.add(:namespace_id, ex.message)
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,8 +1,24 @@
|
|||
module Projects
|
||||
class UpdateContext < BaseContext
|
||||
def execute(role = :default)
|
||||
params[:project].delete(:namespace_id)
|
||||
namespace_id = params[:project].delete(:namespace_id)
|
||||
params[:project].delete(:public) unless can?(current_user, :change_public_mode, project)
|
||||
|
||||
allowed_transfer = can?(current_user, :change_namespace, project) || role == :admin
|
||||
|
||||
if allowed_transfer && namespace_id.present?
|
||||
if namespace_id == Namespace.global_id
|
||||
if project.namespace.present?
|
||||
# Transfer to global namespace from anyone
|
||||
project.transfer(nil)
|
||||
end
|
||||
elsif namespace_id.to_i != project.namespace_id
|
||||
# Transfer to someone namespace
|
||||
namespace = Namespace.find(namespace_id)
|
||||
project.transfer(namespace)
|
||||
end
|
||||
end
|
||||
|
||||
project.update_attributes(params[:project], as: role)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,6 +19,34 @@ class Admin::ProjectsController < Admin::ApplicationController
|
|||
@users = @users.all
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def team_update
|
||||
@project.team.add_users_ids(params[:user_ids], params[:project_access])
|
||||
|
||||
redirect_to [:admin, @project], notice: 'Project was successfully updated.'
|
||||
end
|
||||
|
||||
def update
|
||||
project.creator = current_user unless project.creator
|
||||
|
||||
status = ::Projects::UpdateContext.new(project, current_user, params).execute(:admin)
|
||||
|
||||
if status
|
||||
redirect_to [:admin, @project], notice: 'Project was successfully updated.'
|
||||
else
|
||||
render action: "edit"
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@project.team.truncate
|
||||
@project.destroy
|
||||
|
||||
redirect_to admin_projects_path, notice: 'Project was successfully deleted.'
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def project
|
||||
|
|
|
@ -84,8 +84,6 @@ class Admin::UsersController < Admin::ApplicationController
|
|||
format.html { redirect_to [:admin, admin_user], notice: 'User was successfully updated.' }
|
||||
format.json { head :ok }
|
||||
else
|
||||
# restore username to keep form action url.
|
||||
admin_user.username = params[:id]
|
||||
format.html { render action: "edit" }
|
||||
format.json { render json: admin_user.errors, status: :unprocessable_entity }
|
||||
end
|
||||
|
|
|
@ -7,7 +7,10 @@ class BlameController < ProjectResourceController
|
|||
before_filter :authorize_code_access!
|
||||
before_filter :require_non_empty_project
|
||||
|
||||
before_filter :assign_ref_vars
|
||||
|
||||
def show
|
||||
@blame = Gitlab::Git::Blame.new(project.repository, @commit.id, @path)
|
||||
@repo = @project.repo
|
||||
@blame = Grit::Blob.blame(@repo, @commit.id, @path)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,6 +7,8 @@ class BlobController < ProjectResourceController
|
|||
before_filter :authorize_code_access!
|
||||
before_filter :require_non_empty_project
|
||||
|
||||
before_filter :assign_ref_vars
|
||||
|
||||
def show
|
||||
if @tree.is_blob?
|
||||
send_data(
|
||||
|
|
|
@ -13,6 +13,7 @@ class CommitsController < ProjectResourceController
|
|||
@limit, @offset = (params[:limit] || 40), (params[:offset] || 0)
|
||||
|
||||
@commits = @repo.commits(@ref, @path, @limit, @offset)
|
||||
@commits = CommitDecorator.decorate_collection(@commits)
|
||||
|
||||
respond_to do |format|
|
||||
format.html # index.html.erb
|
||||
|
|
|
@ -8,13 +8,15 @@ class CompareController < ProjectResourceController
|
|||
end
|
||||
|
||||
def show
|
||||
compare = Gitlab::Git::Compare.new(project.repository, params[:from], params[:to])
|
||||
result = Commit.compare(project, params[:from], params[:to])
|
||||
|
||||
@commits = compare.commits
|
||||
@commit = compare.commit
|
||||
@diffs = compare.diffs
|
||||
@refs_are_same = compare.same
|
||||
@commits = result[:commits]
|
||||
@commit = result[:commit]
|
||||
@diffs = result[:diffs]
|
||||
@refs_are_same = result[:same]
|
||||
@line_notes = []
|
||||
|
||||
@commits = CommitDecorator.decorate_collection(@commits)
|
||||
end
|
||||
|
||||
def create
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
# Controller for edit a repository's file
|
||||
class EditTreeController < ProjectResourceController
|
||||
include ExtractsPath
|
||||
|
||||
# Authorize
|
||||
before_filter :authorize_read_project!
|
||||
before_filter :authorize_code_access!
|
||||
before_filter :require_non_empty_project
|
||||
|
||||
before_filter :edit_requirements, only: [:edit, :update]
|
||||
|
||||
def show
|
||||
@last_commit = @project.repository.last_commit_for(@ref, @path).sha
|
||||
end
|
||||
|
||||
def update
|
||||
edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, @project, @ref, @path)
|
||||
updated_successfully = edit_file_action.commit!(
|
||||
params[:content],
|
||||
params[:commit_message],
|
||||
params[:last_commit]
|
||||
)
|
||||
|
||||
if updated_successfully
|
||||
redirect_to project_tree_path(@project, @id), notice: "Your changes have been successfully commited"
|
||||
else
|
||||
flash[:notice] = "Your changes could not be commited, because the file has been changed"
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def edit_requirements
|
||||
unless @tree.is_blob? && @tree.text?
|
||||
redirect_to project_tree_path(@project, @id), notice: "You can only edit text files"
|
||||
end
|
||||
|
||||
allowed = if project.protected_branch? @ref
|
||||
can?(current_user, :push_code_to_protected_branches, project)
|
||||
else
|
||||
can?(current_user, :push_code, project)
|
||||
end
|
||||
|
||||
return access_denied! unless allowed
|
||||
end
|
||||
end
|
|
@ -94,10 +94,12 @@ class MergeRequestsController < ProjectResourceController
|
|||
|
||||
def branch_from
|
||||
@commit = @repository.commit(params[:ref])
|
||||
@commit = CommitDecorator.decorate(@commit)
|
||||
end
|
||||
|
||||
def branch_to
|
||||
@commit = @repository.commit(params[:ref])
|
||||
@commit = CommitDecorator.decorate(@commit)
|
||||
end
|
||||
|
||||
def ci_status
|
||||
|
@ -127,11 +129,11 @@ class MergeRequestsController < ProjectResourceController
|
|||
|
||||
def validates_merge_request
|
||||
# Show git not found page if target branch doesn't exist
|
||||
return invalid_mr unless @project.repository.branch_names.include?(@merge_request.target_branch)
|
||||
return invalid_mr unless @project.repo.heads.map(&:name).include?(@merge_request.target_branch)
|
||||
|
||||
# Show git not found page if source branch doesn't exist
|
||||
# and there is no saved commits between source & target branch
|
||||
return invalid_mr if !@project.repository.branch_names.include?(@merge_request.source_branch) && @merge_request.commits.blank?
|
||||
return invalid_mr if !@project.repo.heads.map(&:name).include?(@merge_request.source_branch) && @merge_request.commits.blank?
|
||||
end
|
||||
|
||||
def define_show_vars
|
||||
|
@ -141,6 +143,7 @@ class MergeRequestsController < ProjectResourceController
|
|||
# Get commits from repository
|
||||
# or from cache if already merged
|
||||
@commits = @merge_request.commits
|
||||
@commits = CommitDecorator.decorate_collection(@commits)
|
||||
|
||||
@allowed_to_merge = allowed_to_merge?
|
||||
@show_merge_controls = @merge_request.opened? && @commits.any? && @allowed_to_merge
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
class NotificationsController < ApplicationController
|
||||
layout 'profile'
|
||||
|
||||
def show
|
||||
@notification = current_user.notification
|
||||
@projects = current_user.authorized_projects
|
||||
end
|
||||
|
||||
def update
|
||||
current_user.notification_level = params[:notification_level]
|
||||
@saved = current_user.save
|
||||
end
|
||||
end
|
|
@ -4,7 +4,7 @@ class ProjectsController < ProjectResourceController
|
|||
|
||||
# Authorize
|
||||
before_filter :authorize_read_project!, except: [:index, :new, :create]
|
||||
before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer]
|
||||
before_filter :authorize_admin_project!, only: [:edit, :update, :destroy]
|
||||
before_filter :require_non_empty_project, only: [:blob, :tree, :graph]
|
||||
|
||||
layout 'application', only: [:new, :create]
|
||||
|
@ -45,10 +45,10 @@ class ProjectsController < ProjectResourceController
|
|||
format.js
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def transfer
|
||||
::Projects::TransferContext.new(project, current_user, params).execute
|
||||
rescue Project::TransferError => ex
|
||||
@error = ex
|
||||
render :update_failed
|
||||
end
|
||||
|
||||
def show
|
||||
|
@ -57,11 +57,11 @@ class ProjectsController < ProjectResourceController
|
|||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
if @project.empty_repo?
|
||||
render "projects/empty"
|
||||
else
|
||||
if @project.repository && !@project.repository.empty?
|
||||
@last_push = current_user.recent_push(@project.id)
|
||||
render :show
|
||||
else
|
||||
render "projects/empty"
|
||||
end
|
||||
end
|
||||
format.js
|
||||
|
|
|
@ -34,6 +34,7 @@ class RefsController < ProjectResourceController
|
|||
@logs = contents.map do |content|
|
||||
file = params[:path] ? File.join(params[:path], content.name) : content.name
|
||||
last_commit = @repo.commits(@commit.id, file, 1).last
|
||||
last_commit = CommitDecorator.decorate(last_commit)
|
||||
{
|
||||
file_name: content.name,
|
||||
commit: last_commit
|
||||
|
@ -48,7 +49,9 @@ class RefsController < ProjectResourceController
|
|||
|
||||
@repo = project.repository
|
||||
@commit = @repo.commit(@ref)
|
||||
@commit = CommitDecorator.decorate(@commit)
|
||||
@tree = Tree.new(@commit.tree, @ref, params[:path])
|
||||
@tree = TreeDecorator.new(@tree)
|
||||
@hex_path = Digest::SHA1.hexdigest(params[:path] || "")
|
||||
|
||||
if params[:path]
|
||||
|
|
|
@ -7,6 +7,9 @@ class TreeController < ProjectResourceController
|
|||
before_filter :authorize_code_access!
|
||||
before_filter :require_non_empty_project
|
||||
|
||||
before_filter :assign_ref_vars
|
||||
before_filter :edit_requirements, only: [:edit, :update]
|
||||
|
||||
def show
|
||||
@hex_path = Digest::SHA1.hexdigest(@path)
|
||||
@logs_path = logs_file_project_ref_path(@project, @ref, @path)
|
||||
|
@ -17,4 +20,40 @@ class TreeController < ProjectResourceController
|
|||
format.js { no_cache_headers }
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@last_commit = @project.repository.last_commit_for(@ref, @path).sha
|
||||
end
|
||||
|
||||
def update
|
||||
edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, @project, @ref, @path)
|
||||
updated_successfully = edit_file_action.commit!(
|
||||
params[:content],
|
||||
params[:commit_message],
|
||||
params[:last_commit]
|
||||
)
|
||||
|
||||
if updated_successfully
|
||||
redirect_to project_tree_path(@project, @id), notice: "Your changes have been successfully commited"
|
||||
else
|
||||
flash[:notice] = "Your changes could not be commited, because the file has been changed"
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def edit_requirements
|
||||
unless @tree.is_blob? && @tree.text?
|
||||
redirect_to project_tree_path(@project, @id), notice: "You can only edit text files"
|
||||
end
|
||||
|
||||
allowed = if project.protected_branch? @ref
|
||||
can?(current_user, :push_code_to_protected_branches, project)
|
||||
else
|
||||
can?(current_user, :push_code, project)
|
||||
end
|
||||
|
||||
return access_denied! unless allowed
|
||||
end
|
||||
end
|
||||
|
|
93
app/decorators/commit_decorator.rb
Normal file
93
app/decorators/commit_decorator.rb
Normal file
|
@ -0,0 +1,93 @@
|
|||
class CommitDecorator < ApplicationDecorator
|
||||
decorates :commit
|
||||
|
||||
# Returns a string describing the commit for use in a link title
|
||||
#
|
||||
# Example
|
||||
#
|
||||
# "Commit: Alex Denisov - Project git clone panel"
|
||||
def link_title
|
||||
"Commit: #{author_name} - #{title}"
|
||||
end
|
||||
|
||||
# Returns the commits title.
|
||||
#
|
||||
# Usually, the commit title is the first line of the commit message.
|
||||
# In case this first line is longer than 80 characters, it is cut off
|
||||
# after 70 characters and ellipses (`&hellp;`) are appended.
|
||||
def title
|
||||
title = safe_message
|
||||
|
||||
return no_commit_message if title.blank?
|
||||
|
||||
title_end = title.index(/\n/)
|
||||
if (!title_end && title.length > 80) || (title_end && title_end > 80)
|
||||
title[0..69] << "…".html_safe
|
||||
else
|
||||
title.split(/\n/, 2).first
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the commits description
|
||||
#
|
||||
# cut off, ellipses (`&hellp;`) are prepended to the commit message.
|
||||
def description
|
||||
description = safe_message
|
||||
|
||||
title_end = description.index(/\n/)
|
||||
if (!title_end && description.length > 80) || (title_end && title_end > 80)
|
||||
"…".html_safe << description[70..-1]
|
||||
else
|
||||
description.split(/\n/, 2)[1].try(:chomp)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a link to the commit author. If the author has a matching user and
|
||||
# is a member of the current @project it will link to the team member page.
|
||||
# Otherwise it will link to the author email as specified in the commit.
|
||||
#
|
||||
# options:
|
||||
# avatar: true will prepend the avatar image
|
||||
# size: size of the avatar image in px
|
||||
def author_link(options = {})
|
||||
person_link(options.merge source: :author)
|
||||
end
|
||||
|
||||
# Just like #author_link but for the committer.
|
||||
def committer_link(options = {})
|
||||
person_link(options.merge source: :committer)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def no_commit_message
|
||||
"--no commit message"
|
||||
end
|
||||
|
||||
# Private: Returns a link to a person. If the person has a matching user and
|
||||
# is a member of the current @project it will link to the team member page.
|
||||
# Otherwise it will link to the person email as specified in the commit.
|
||||
#
|
||||
# options:
|
||||
# source: one of :author or :committer
|
||||
# avatar: true will prepend the avatar image
|
||||
# size: size of the avatar image in px
|
||||
def person_link(options = {})
|
||||
source_name = send "#{options[:source]}_name".to_sym
|
||||
source_email = send "#{options[:source]}_email".to_sym
|
||||
text = if options[:avatar]
|
||||
avatar = h.image_tag h.gravatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: ""
|
||||
%Q{#{avatar} <span class="commit-#{options[:source]}-name">#{source_name}</span>}
|
||||
else
|
||||
source_name
|
||||
end
|
||||
|
||||
user = User.where('name like ? or email like ?', source_name, source_email).first
|
||||
|
||||
if user.nil?
|
||||
h.mail_to(source_email, text.html_safe, class: "commit-#{options[:source]}-link")
|
||||
else
|
||||
h.link_to(text.html_safe, h.user_path(user), class: "commit-#{options[:source]}-link")
|
||||
end
|
||||
end
|
||||
end
|
33
app/decorators/tree_decorator.rb
Normal file
33
app/decorators/tree_decorator.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
class TreeDecorator < ApplicationDecorator
|
||||
decorates :tree
|
||||
|
||||
def breadcrumbs(max_links = 2)
|
||||
if path
|
||||
part_path = ""
|
||||
parts = path.split("\/")
|
||||
|
||||
yield('..', nil) if parts.count > max_links
|
||||
|
||||
parts.each do |part|
|
||||
part_path = File.join(part_path, part) unless part_path.empty?
|
||||
part_path = part if part_path.empty?
|
||||
|
||||
next unless parts.last(2).include?(part) if parts.count > max_links
|
||||
yield(part, h.tree_join(ref, part_path))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def up_dir?
|
||||
path.present?
|
||||
end
|
||||
|
||||
def up_dir_path
|
||||
file = File.join(path, "..")
|
||||
h.tree_join(ref, file)
|
||||
end
|
||||
|
||||
def readme
|
||||
@readme ||= contents.find { |c| c.is_a?(Grit::Blob) and c.name =~ /^readme/i }
|
||||
end
|
||||
end
|
|
@ -96,7 +96,7 @@ module ApplicationHelper
|
|||
]
|
||||
|
||||
project_nav = []
|
||||
if @project && @project.repository.exists? && @project.repository.root_ref
|
||||
if @project && @project.repository && @project.repository.root_ref
|
||||
project_nav = [
|
||||
{ label: "#{simple_sanitize(@project.name_with_namespace)} - Issues", url: project_issues_path(@project) },
|
||||
{ label: "#{simple_sanitize(@project.name_with_namespace)} - Commits", url: project_commits_path(@project, @ref || @project.repository.root_ref) },
|
||||
|
|
|
@ -1,20 +1,4 @@
|
|||
module CommitsHelper
|
||||
# Returns a link to the commit author. If the author has a matching user and
|
||||
# is a member of the current @project it will link to the team member page.
|
||||
# Otherwise it will link to the author email as specified in the commit.
|
||||
#
|
||||
# options:
|
||||
# avatar: true will prepend the avatar image
|
||||
# size: size of the avatar image in px
|
||||
def commit_author_link(commit, options = {})
|
||||
commit_person_link(commit, options.merge(source: :author))
|
||||
end
|
||||
|
||||
# Just like #author_link but for the committer.
|
||||
def commit_committer_link(commit, options = {})
|
||||
commit_person_link(commit, options.merge(source: :committer))
|
||||
end
|
||||
|
||||
def identification_type(line)
|
||||
if line[0] == "+"
|
||||
"new"
|
||||
|
@ -109,7 +93,9 @@ module CommitsHelper
|
|||
end
|
||||
|
||||
def commit_to_html commit
|
||||
escape_javascript(render 'commits/commit', commit: commit)
|
||||
if commit.model
|
||||
escape_javascript(render 'commits/commit', commit: commit)
|
||||
end
|
||||
end
|
||||
|
||||
def diff_line_content(line)
|
||||
|
@ -119,58 +105,4 @@ module CommitsHelper
|
|||
line
|
||||
end
|
||||
end
|
||||
|
||||
# Breadcrumb links for a Project and, if applicable, a tree path
|
||||
def commits_breadcrumbs
|
||||
return unless @project && @ref
|
||||
|
||||
# Add the root project link and the arrow icon
|
||||
crumbs = content_tag(:li) do
|
||||
content_tag(:span, nil, class: 'arrow') +
|
||||
link_to(@project.name, project_commits_path(@project, @ref))
|
||||
end
|
||||
|
||||
if @path
|
||||
parts = @path.split('/')
|
||||
|
||||
parts.each_with_index do |part, i|
|
||||
crumbs += content_tag(:span, '/', class: 'divider')
|
||||
crumbs += content_tag(:li) do
|
||||
# The text is just the individual part, but the link needs all the parts before it
|
||||
link_to part, project_commits_path(@project, tree_join(@ref, parts[0..i].join('/')))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
crumbs.html_safe
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Private: Returns a link to a person. If the person has a matching user and
|
||||
# is a member of the current @project it will link to the team member page.
|
||||
# Otherwise it will link to the person email as specified in the commit.
|
||||
#
|
||||
# options:
|
||||
# source: one of :author or :committer
|
||||
# avatar: true will prepend the avatar image
|
||||
# size: size of the avatar image in px
|
||||
def commit_person_link(commit, options = {})
|
||||
source_name = commit.send "#{options[:source]}_name".to_sym
|
||||
source_email = commit.send "#{options[:source]}_email".to_sym
|
||||
text = if options[:avatar]
|
||||
avatar = image_tag(gravatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "")
|
||||
%Q{#{avatar} <span class="commit-#{options[:source]}-name">#{source_name}</span>}
|
||||
else
|
||||
source_name
|
||||
end
|
||||
|
||||
user = User.where('name like ? or email like ?', source_name, source_email).first
|
||||
|
||||
if user.nil?
|
||||
mail_to(source_email, text.html_safe, class: "commit-#{options[:source]}-link")
|
||||
else
|
||||
link_to(text.html_safe, user_path(user), class: "commit-#{options[:source]}-link")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
module GraphHelper
|
||||
def get_refs(commit)
|
||||
refs = ""
|
||||
refs += commit.refs.collect{|r|r.name}.join(" ") if commit.refs
|
||||
|
||||
# append note count
|
||||
notes = @project.notes.for_commit_id(commit.id)
|
||||
refs += "[#{notes.count}]" if notes.any?
|
||||
|
||||
refs
|
||||
def join_with_space(ary)
|
||||
ary.collect{|r|r.name}.join(" ") unless ary.nil?
|
||||
end
|
||||
|
||||
def parents_zip_spaces(parents, parent_spaces)
|
||||
|
|
|
@ -48,31 +48,19 @@ module IssuesHelper
|
|||
if @project.used_default_issues_tracker?
|
||||
project_issues_filter_path(@project)
|
||||
else
|
||||
url = Gitlab.config.issues_tracker[@project.issues_tracker]["project_url"]
|
||||
url = Settings[:issues_tracker][@project.issues_tracker]["project_url"]
|
||||
url.gsub(':project_id', @project.id.to_s)
|
||||
.gsub(':issues_tracker_id', @project.issues_tracker_id.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
def url_for_new_issue
|
||||
return "" if @project.nil?
|
||||
|
||||
if @project.used_default_issues_tracker?
|
||||
url = new_project_issue_path project_id: @project
|
||||
else
|
||||
url = Gitlab.config.issues_tracker[@project.issues_tracker]["new_issue_url"]
|
||||
url.gsub(':project_id', @project.id.to_s)
|
||||
.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 = Gitlab.config.issues_tracker[@project.issues_tracker]["issues_url"]
|
||||
url = Settings[:issues_tracker][@project.issues_tracker]["issues_url"]
|
||||
url.gsub(':id', issue_id.to_s)
|
||||
.gsub(':project_id', @project.id.to_s)
|
||||
.gsub(':issues_tracker_id', @project.issues_tracker_id.to_s)
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
module NotificationsHelper
|
||||
end
|
|
@ -70,26 +70,28 @@ module TreeHelper
|
|||
end
|
||||
end
|
||||
|
||||
def tree_breadcrumbs(tree, max_links = 2)
|
||||
if tree.path
|
||||
part_path = ""
|
||||
parts = tree.path.split("\/")
|
||||
# Breadcrumb links for a Project and, if applicable, a tree path
|
||||
def breadcrumbs
|
||||
return unless @project && @ref
|
||||
|
||||
yield('..', nil) if parts.count > max_links
|
||||
# Add the root project link and the arrow icon
|
||||
crumbs = content_tag(:li) do
|
||||
content_tag(:span, nil, class: 'arrow') +
|
||||
link_to(@project.name, project_commits_path(@project, @ref))
|
||||
end
|
||||
|
||||
parts.each do |part|
|
||||
part_path = File.join(part_path, part) unless part_path.empty?
|
||||
part_path = part if part_path.empty?
|
||||
if @path
|
||||
parts = @path.split('/')
|
||||
|
||||
next unless parts.last(2).include?(part) if parts.count > max_links
|
||||
yield(part, tree_join(tree.ref, part_path))
|
||||
parts.each_with_index do |part, i|
|
||||
crumbs += content_tag(:span, '/', class: 'divider')
|
||||
crumbs += content_tag(:li) do
|
||||
# The text is just the individual part, but the link needs all the parts before it
|
||||
link_to part, project_commits_path(@project, tree_join(@ref, parts[0..i].join('/')))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def up_dir_path tree
|
||||
file = File.join(tree.path, "..")
|
||||
tree_join(tree.ref, file)
|
||||
crumbs.html_safe
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
module Emails
|
||||
module Issues
|
||||
def new_issue_email(recipient_id, issue_id)
|
||||
def new_issue_email(issue_id)
|
||||
@issue = Issue.find(issue_id)
|
||||
@project = @issue.project
|
||||
mail(to: recipient(recipient_id), subject: subject("new issue ##{@issue.id}", @issue.title))
|
||||
mail(to: @issue.assignee_email, subject: subject("new issue ##{@issue.id}", @issue.title))
|
||||
end
|
||||
|
||||
def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id)
|
||||
|
@ -13,14 +13,6 @@ module Emails
|
|||
mail(to: recipient(recipient_id), subject: subject("changed issue ##{@issue.id}", @issue.title))
|
||||
end
|
||||
|
||||
def closed_issue_email(recipient_id, issue_id, updated_by_user_id)
|
||||
@issue = Issue.find issue_id
|
||||
@project = @issue.project
|
||||
@updated_by = User.find updated_by_user_id
|
||||
mail(to: recipient(recipient_id),
|
||||
subject: subject("Closed issue ##{@issue.id}", @issue.title))
|
||||
end
|
||||
|
||||
def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id)
|
||||
@issue = Issue.find issue_id
|
||||
@issue_status = status
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
module Emails
|
||||
module MergeRequests
|
||||
def new_merge_request_email(recipient_id, merge_request_id)
|
||||
def new_merge_request_email(merge_request_id)
|
||||
@merge_request = MergeRequest.find(merge_request_id)
|
||||
@project = @merge_request.project
|
||||
mail(to: recipient(recipient_id), subject: subject("new merge request !#{@merge_request.id}", @merge_request.title))
|
||||
mail(to: @merge_request.assignee_email, subject: subject("new merge request !#{@merge_request.id}", @merge_request.title))
|
||||
end
|
||||
|
||||
def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id)
|
||||
|
@ -12,18 +12,5 @@ module Emails
|
|||
@project = @merge_request.project
|
||||
mail(to: recipient(recipient_id), subject: subject("changed merge request !#{@merge_request.id}", @merge_request.title))
|
||||
end
|
||||
|
||||
def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
|
||||
@merge_request = MergeRequest.find(merge_request_id)
|
||||
@project = @merge_request.project
|
||||
@updated_by = User.find updated_by_user_id
|
||||
mail(to: recipient(recipient_id), subject: subject("Closed merge request !#{@merge_request.id}", @merge_request.title))
|
||||
end
|
||||
|
||||
def merged_merge_request_email(recipient_id, merge_request_id)
|
||||
@merge_request = MergeRequest.find(merge_request_id)
|
||||
@project = @merge_request.project
|
||||
mail(to: recipient(recipient_id), subject: subject("Accepted merge request !#{@merge_request.id}", @merge_request.title))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,7 @@ module Emails
|
|||
def note_commit_email(recipient_id, note_id)
|
||||
@note = Note.find(note_id)
|
||||
@commit = @note.noteable
|
||||
@commit = CommitDecorator.decorate(@commit)
|
||||
@project = @note.project
|
||||
mail(to: recipient(recipient_id), subject: subject("note for commit #{@commit.short_id}", @commit.title))
|
||||
end
|
||||
|
|
|
@ -41,7 +41,7 @@ class Ability
|
|||
rules << project_guest_rules
|
||||
end
|
||||
|
||||
if project.owner == user || user.admin?
|
||||
if project.owner == user
|
||||
rules << project_admin_rules
|
||||
end
|
||||
|
||||
|
|
|
@ -8,70 +8,168 @@ class Commit
|
|||
#
|
||||
DIFF_SAFE_SIZE = 100
|
||||
|
||||
def self.decorate(commits)
|
||||
commits.map { |c| self.new(c) }
|
||||
attr_accessor :commit, :head, :refs
|
||||
|
||||
delegate :message, :authored_date, :committed_date, :parents, :sha,
|
||||
:date, :committer, :author, :diffs, :tree, :id, :stats,
|
||||
:to_patch, to: :commit
|
||||
|
||||
class << self
|
||||
def find_or_first(repo, commit_id = nil, root_ref)
|
||||
commit = if commit_id
|
||||
repo.commit(commit_id)
|
||||
else
|
||||
repo.commits(root_ref).first
|
||||
end
|
||||
|
||||
Commit.new(commit) if commit
|
||||
end
|
||||
|
||||
def fresh_commits(repo, n = 10)
|
||||
commits = repo.heads.map do |h|
|
||||
repo.commits(h.name, n).map { |c| Commit.new(c, h) }
|
||||
end.flatten.uniq { |c| c.id }
|
||||
|
||||
commits.sort! do |x, y|
|
||||
y.committed_date <=> x.committed_date
|
||||
end
|
||||
|
||||
commits[0...n]
|
||||
end
|
||||
|
||||
def commits_with_refs(repo, n = 20)
|
||||
commits = repo.branches.map { |ref| Commit.new(ref.commit, ref) }
|
||||
|
||||
commits.sort! do |x, y|
|
||||
y.committed_date <=> x.committed_date
|
||||
end
|
||||
|
||||
commits[0..n]
|
||||
end
|
||||
|
||||
def commits_since(repo, date)
|
||||
commits = repo.heads.map do |h|
|
||||
repo.log(h.name, nil, since: date).each { |c| Commit.new(c, h) }
|
||||
end.flatten.uniq { |c| c.id }
|
||||
|
||||
commits.sort! do |x, y|
|
||||
y.committed_date <=> x.committed_date
|
||||
end
|
||||
|
||||
commits
|
||||
end
|
||||
|
||||
def commits(repo, ref, path = nil, limit = nil, offset = nil)
|
||||
if path
|
||||
repo.log(ref, path, max_count: limit, skip: offset)
|
||||
elsif limit && offset
|
||||
repo.commits(ref, limit, offset)
|
||||
else
|
||||
repo.commits(ref)
|
||||
end.map{ |c| Commit.new(c) }
|
||||
end
|
||||
|
||||
def commits_between(repo, from, to)
|
||||
repo.commits_between(from, to).map { |c| Commit.new(c) }
|
||||
end
|
||||
|
||||
def compare(project, from, to)
|
||||
result = {
|
||||
commits: [],
|
||||
diffs: [],
|
||||
commit: nil,
|
||||
same: false
|
||||
}
|
||||
|
||||
return result unless from && to
|
||||
|
||||
first = project.repository.commit(to.try(:strip))
|
||||
last = project.repository.commit(from.try(:strip))
|
||||
|
||||
if first && last
|
||||
result[:same] = (first.id == last.id)
|
||||
result[:commits] = project.repo.commits_between(last.id, first.id).map {|c| Commit.new(c)}
|
||||
|
||||
# Dont load diff for 100+ commits
|
||||
result[:diffs] = if result[:commits].size > 100
|
||||
[]
|
||||
else
|
||||
project.repo.diff(last.id, first.id) rescue []
|
||||
end
|
||||
|
||||
result[:commit] = Commit.new(first)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
attr_accessor :raw
|
||||
|
||||
def initialize(raw_commit)
|
||||
def initialize(raw_commit, head = nil)
|
||||
raise "Nil as raw commit passed" unless raw_commit
|
||||
|
||||
@raw = raw_commit
|
||||
@commit = raw_commit
|
||||
@head = head
|
||||
end
|
||||
|
||||
def id
|
||||
@raw.id
|
||||
def short_id(length = 10)
|
||||
id.to_s[0..length]
|
||||
end
|
||||
|
||||
# Returns a string describing the commit for use in a link title
|
||||
#
|
||||
# Example
|
||||
#
|
||||
# "Commit: Alex Denisov - Project git clone panel"
|
||||
def link_title
|
||||
"Commit: #{author_name} - #{title}"
|
||||
def safe_message
|
||||
@safe_message ||= message
|
||||
end
|
||||
|
||||
# Returns the commits title.
|
||||
def created_at
|
||||
committed_date
|
||||
end
|
||||
|
||||
def author_email
|
||||
author.email
|
||||
end
|
||||
|
||||
def author_name
|
||||
author.name
|
||||
end
|
||||
|
||||
# Was this commit committed by a different person than the original author?
|
||||
def different_committer?
|
||||
author_name != committer_name || author_email != committer_email
|
||||
end
|
||||
|
||||
def committer_name
|
||||
committer.name
|
||||
end
|
||||
|
||||
def committer_email
|
||||
committer.email
|
||||
end
|
||||
|
||||
def prev_commit
|
||||
@prev_commit ||= if parents.present?
|
||||
Commit.new(parents.first)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def prev_commit_id
|
||||
prev_commit.try :id
|
||||
end
|
||||
|
||||
# Shows the diff between the commit's parent and the commit.
|
||||
#
|
||||
# Usually, the commit title is the first line of the commit message.
|
||||
# In case this first line is longer than 80 characters, it is cut off
|
||||
# after 70 characters and ellipses (`&hellp;`) are appended.
|
||||
def title
|
||||
title = safe_message
|
||||
# Cuts out the header and stats from #to_patch and returns only the diff.
|
||||
def to_diff
|
||||
# see Grit::Commit#show
|
||||
patch = to_patch
|
||||
|
||||
return no_commit_message if title.blank?
|
||||
|
||||
title_end = title.index(/\n/)
|
||||
if (!title_end && title.length > 80) || (title_end && title_end > 80)
|
||||
title[0..69] << "…".html_safe
|
||||
else
|
||||
title.split(/\n/, 2).first
|
||||
# discard lines before the diff
|
||||
lines = patch.split("\n")
|
||||
while !lines.first.start_with?("diff --git") do
|
||||
lines.shift
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the commits description
|
||||
#
|
||||
# cut off, ellipses (`&hellp;`) are prepended to the commit message.
|
||||
def description
|
||||
description = safe_message
|
||||
|
||||
title_end = description.index(/\n/)
|
||||
if (!title_end && description.length > 80) || (title_end && title_end > 80)
|
||||
"…".html_safe << description[70..-1]
|
||||
else
|
||||
description.split(/\n/, 2)[1].try(:chomp)
|
||||
end
|
||||
end
|
||||
|
||||
def method_missing(m, *args, &block)
|
||||
@raw.send(m, *args, &block)
|
||||
end
|
||||
|
||||
def respond_to?(method)
|
||||
return true if @raw.respond_to?(method)
|
||||
|
||||
super
|
||||
lines.pop if lines.last =~ /^[\d.]+$/ # Git version
|
||||
lines.pop if lines.last == "-- " # end of diff
|
||||
lines.join("\n")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -50,7 +50,7 @@ class GollumWiki
|
|||
# Returns the last 30 Commit objects across the entire
|
||||
# repository.
|
||||
def recent_history
|
||||
Gitlab::Git::Commit.fresh_commits(wiki.repo, 30)
|
||||
Commit.fresh_commits(wiki.repo, 30)
|
||||
end
|
||||
|
||||
# Finds a page within the repository based on a tile
|
||||
|
@ -90,17 +90,13 @@ class GollumWiki
|
|||
private
|
||||
|
||||
def create_repo!
|
||||
if init_repo(path_with_namespace)
|
||||
if gitlab_shell.add_repository(path_with_namespace)
|
||||
Gollum::Wiki.new(path_to_repo)
|
||||
else
|
||||
raise CouldNotCreateWikiError
|
||||
end
|
||||
end
|
||||
|
||||
def init_repo(path_with_namespace)
|
||||
gitlab_shell.add_repository(path_with_namespace)
|
||||
end
|
||||
|
||||
def commit_details(action, message = nil, title = nil)
|
||||
commit_message = message || default_message(action, title)
|
||||
|
||||
|
@ -118,4 +114,5 @@ class GollumWiki
|
|||
def path_to_repo
|
||||
@path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git")
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -152,17 +152,7 @@ class MergeRequest < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def commits
|
||||
if st_commits.present?
|
||||
# check if merge request commits are valid
|
||||
if st_commits.first.respond_to?(:short_id)
|
||||
st_commits
|
||||
else
|
||||
# if commits are invalid - simply reload it from repo
|
||||
reloaded_commits
|
||||
end
|
||||
else
|
||||
[]
|
||||
end
|
||||
st_commits || []
|
||||
end
|
||||
|
||||
def probably_merged?
|
||||
|
@ -172,12 +162,6 @@ class MergeRequest < ActiveRecord::Base
|
|||
|
||||
def reloaded_commits
|
||||
if opened? && unmerged_commits.any?
|
||||
# we need to reset st_commits field first
|
||||
# in order to prevent internal rails comparison
|
||||
self.st_commits = []
|
||||
save
|
||||
|
||||
# Then we can safely write unmerged commits
|
||||
self.st_commits = unmerged_commits
|
||||
save
|
||||
end
|
||||
|
@ -185,8 +169,9 @@ class MergeRequest < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def unmerged_commits
|
||||
self.project.repository.
|
||||
self.project.repo.
|
||||
commits_between(self.target_branch, self.source_branch).
|
||||
map {|c| Commit.new(c)}.
|
||||
sort_by(&:created_at).
|
||||
reverse
|
||||
end
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
#
|
||||
|
||||
class Namespace < ActiveRecord::Base
|
||||
include Gitlab::ShellAdapter
|
||||
|
||||
attr_accessible :name, :description, :path
|
||||
|
||||
has_many :projects, dependent: :destroy
|
||||
|
@ -33,7 +31,7 @@ class Namespace < ActiveRecord::Base
|
|||
delegate :name, to: :owner, allow_nil: true, prefix: true
|
||||
|
||||
after_create :ensure_dir_exist
|
||||
after_update :move_dir, if: :path_changed?
|
||||
after_update :move_dir
|
||||
after_destroy :rm_dir
|
||||
|
||||
scope :root, -> { where('type IS NULL') }
|
||||
|
@ -55,34 +53,48 @@ class Namespace < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def ensure_dir_exist
|
||||
gitlab_shell.add_namespace(path)
|
||||
unless dir_exists?
|
||||
FileUtils.mkdir( namespace_full_path, mode: 0770 )
|
||||
end
|
||||
end
|
||||
|
||||
def rm_dir
|
||||
gitlab_shell.rm_namespace(path)
|
||||
def dir_exists?
|
||||
File.exists?(namespace_full_path)
|
||||
end
|
||||
|
||||
def namespace_full_path
|
||||
@namespace_full_path ||= File.join(Gitlab.config.gitlab_shell.repos_path, path)
|
||||
end
|
||||
|
||||
def move_dir
|
||||
if gitlab_shell.mv_namespace(path_was, path)
|
||||
# If repositories moved successfully we need to remove old satellites
|
||||
# and send update instructions to users.
|
||||
# However we cannot allow rollback since we moved namespace dir
|
||||
# So we basically we mute exceptions in next actions
|
||||
begin
|
||||
gitlab_shell.rm_satellites(path_was)
|
||||
send_update_instructions
|
||||
rescue
|
||||
# Returning false does not rolback after_* transaction but gives
|
||||
# us information about failing some of tasks
|
||||
false
|
||||
if path_changed?
|
||||
old_path = File.join(Gitlab.config.gitlab_shell.repos_path, path_was)
|
||||
new_path = File.join(Gitlab.config.gitlab_shell.repos_path, path)
|
||||
if File.exists?(new_path)
|
||||
raise "Already exists"
|
||||
end
|
||||
|
||||
|
||||
begin
|
||||
# Remove satellite when moving repo
|
||||
if path_was.present?
|
||||
satellites_path = File.join(Gitlab.config.satellites.path, path_was)
|
||||
FileUtils.rm_r( satellites_path, force: true )
|
||||
end
|
||||
|
||||
FileUtils.mv( old_path, new_path )
|
||||
send_update_instructions
|
||||
rescue Exception => e
|
||||
raise "Namespace move error #{old_path} #{new_path}"
|
||||
end
|
||||
else
|
||||
# if we cannot move namespace directory we should rollback
|
||||
# db changes in order to prevent out of sync between db and fs
|
||||
raise Exception.new('namespace directory cannot be moved')
|
||||
end
|
||||
end
|
||||
|
||||
def rm_dir
|
||||
dir_path = File.join(Gitlab.config.gitlab_shell.repos_path, path)
|
||||
FileUtils.rm_r( dir_path, force: true )
|
||||
end
|
||||
|
||||
def send_update_instructions
|
||||
projects.each(&:send_move_instructions)
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ module Network
|
|||
attr_accessor :time, :spaces, :parent_spaces
|
||||
|
||||
def initialize(raw_commit, refs)
|
||||
@commit = Gitlab::Git::Commit.new(raw_commit)
|
||||
@commit = ::Commit.new(raw_commit)
|
||||
@time = -1
|
||||
@spaces = []
|
||||
@parent_spaces = []
|
||||
|
|
|
@ -40,12 +40,15 @@ module Network
|
|||
def index_commits
|
||||
days = []
|
||||
@map = {}
|
||||
@reserved = {}
|
||||
|
||||
@commits.each_with_index do |c,i|
|
||||
@commits.reverse.each_with_index do |c,i|
|
||||
c.time = i
|
||||
days[i] = c.committed_date
|
||||
@map[c.id] = c
|
||||
end
|
||||
|
||||
@reserved = {}
|
||||
days.each_index do |i|
|
||||
@reserved[i] = []
|
||||
end
|
||||
|
||||
|
@ -132,7 +135,11 @@ module Network
|
|||
spaces = []
|
||||
|
||||
commit.parents(@map).each do |parent|
|
||||
range = commit.time..parent.time
|
||||
range = if commit.time < parent.time then
|
||||
commit.time..parent.time
|
||||
else
|
||||
parent.time..commit.time
|
||||
end
|
||||
|
||||
space = if commit.space >= parent.space then
|
||||
find_free_parent_space(range, parent.space, -1, commit.space)
|
||||
|
@ -159,7 +166,7 @@ module Network
|
|||
range.each do |i|
|
||||
if i != range.first &&
|
||||
i != range.last &&
|
||||
@commits[i].spaces.include?(overlap_space) then
|
||||
@commits[reversed_index(i)].spaces.include?(overlap_space) then
|
||||
|
||||
return true;
|
||||
end
|
||||
|
@ -177,7 +184,7 @@ module Network
|
|||
return
|
||||
end
|
||||
|
||||
time_range = leaves.first.time..leaves.last.time
|
||||
time_range = leaves.last.time..leaves.first.time
|
||||
space_base = get_space_base(leaves)
|
||||
space = find_free_space(time_range, 2, space_base)
|
||||
leaves.each do |l|
|
||||
|
@ -191,17 +198,17 @@ module Network
|
|||
end
|
||||
|
||||
# and mark it as reserved
|
||||
if parent_time.nil?
|
||||
min_time = leaves.first.time
|
||||
else
|
||||
min_time = parent_time + 1
|
||||
min_time = leaves.last.time
|
||||
leaves.last.parents(@map).each do |parent|
|
||||
if parent.time < min_time
|
||||
min_time = parent.time
|
||||
end
|
||||
end
|
||||
|
||||
max_time = leaves.last.time
|
||||
leaves.last.parents(@map).each do |parent|
|
||||
if max_time < parent.time
|
||||
max_time = parent.time
|
||||
end
|
||||
if parent_time.nil?
|
||||
max_time = leaves.first.time
|
||||
else
|
||||
max_time = parent_time - 1
|
||||
end
|
||||
mark_reserved(min_time..max_time, space)
|
||||
|
||||
|
@ -282,5 +289,9 @@ module Network
|
|||
end
|
||||
refs_cache
|
||||
end
|
||||
|
||||
def reversed_index(index)
|
||||
-index - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,6 +22,9 @@ class Note < ActiveRecord::Base
|
|||
attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id,
|
||||
:attachment, :line_code, :commit_id
|
||||
|
||||
attr_accessor :notify
|
||||
attr_accessor :notify_author
|
||||
|
||||
belongs_to :project
|
||||
belongs_to :noteable, polymorphic: true
|
||||
belongs_to :author, class_name: "User"
|
||||
|
@ -140,6 +143,14 @@ class Note < ActiveRecord::Base
|
|||
nil
|
||||
end
|
||||
|
||||
def notify
|
||||
@notify ||= false
|
||||
end
|
||||
|
||||
def notify_author
|
||||
@notify_author ||= false
|
||||
end
|
||||
|
||||
# Returns true if this is an upvote note,
|
||||
# otherwise false is returned
|
||||
def upvote?
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
class Notification
|
||||
#
|
||||
# Notification levels
|
||||
#
|
||||
N_DISABLED = 0
|
||||
N_PARTICIPATING = 1
|
||||
N_WATCH = 2
|
||||
|
||||
attr_accessor :user
|
||||
|
||||
def self.notification_levels
|
||||
[N_DISABLED, N_PARTICIPATING, N_WATCH]
|
||||
end
|
||||
|
||||
def initialize(user)
|
||||
@user = user
|
||||
end
|
||||
|
||||
def disabled?
|
||||
user.notification_level == N_DISABLED
|
||||
end
|
||||
|
||||
def participating?
|
||||
user.notification_level == N_PARTICIPATING
|
||||
end
|
||||
|
||||
def watch?
|
||||
user.notification_level == N_WATCH
|
||||
end
|
||||
end
|
|
@ -18,15 +18,16 @@
|
|||
# public :boolean default(FALSE), not null
|
||||
# issues_tracker :string(255) default("gitlab"), not null
|
||||
# issues_tracker_id :string(255)
|
||||
# snippets_enabled :boolean default(TRUE), not null
|
||||
#
|
||||
|
||||
require "grit"
|
||||
|
||||
class Project < ActiveRecord::Base
|
||||
include Gitlab::ShellAdapter
|
||||
include Gitolited
|
||||
extend Enumerize
|
||||
|
||||
class TransferError < StandardError; end
|
||||
|
||||
attr_accessible :name, :path, :description, :default_branch, :issues_tracker,
|
||||
:issues_enabled, :wall_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id,
|
||||
:wiki_enabled, :public, :import_url, as: [:default, :admin]
|
||||
|
@ -141,7 +142,13 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def repository
|
||||
@repository ||= Repository.new(path_with_namespace, default_branch)
|
||||
if path
|
||||
@repository ||= Repository.new(path_with_namespace, default_branch)
|
||||
else
|
||||
nil
|
||||
end
|
||||
rescue Grit::NoSuchPathError
|
||||
nil
|
||||
end
|
||||
|
||||
def saved?
|
||||
|
@ -326,14 +333,14 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def valid_repo?
|
||||
repository.exists?
|
||||
repo
|
||||
rescue
|
||||
errors.add(:path, "Invalid repository path")
|
||||
false
|
||||
end
|
||||
|
||||
def empty_repo?
|
||||
!repository.exists? || repository.empty?
|
||||
!repository || repository.empty?
|
||||
end
|
||||
|
||||
def ensure_satellite_exists
|
||||
|
@ -357,25 +364,18 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def repo_exists?
|
||||
@repo_exists ||= repository.exists?
|
||||
@repo_exists ||= (repository && repository.branches.present?)
|
||||
rescue
|
||||
@repo_exists = false
|
||||
end
|
||||
|
||||
def open_branches
|
||||
all_branches = repository.branches
|
||||
|
||||
if protected_branches.present?
|
||||
all_branches.reject! do |branch|
|
||||
protected_branches_names.include?(branch.name)
|
||||
end
|
||||
end
|
||||
|
||||
all_branches
|
||||
end
|
||||
|
||||
def protected_branches_names
|
||||
@protected_branches_names ||= protected_branches.map(&:name)
|
||||
if protected_branches.empty?
|
||||
self.repo.heads
|
||||
else
|
||||
pnames = protected_branches.map(&:name)
|
||||
self.repo.heads.reject { |h| pnames.include?(h.name) }
|
||||
end.sort_by(&:name)
|
||||
end
|
||||
|
||||
def root_ref?(branch)
|
||||
|
@ -397,6 +397,6 @@ class Project < ActiveRecord::Base
|
|||
|
||||
# Check if current branch name is marked as protected in the system
|
||||
def protected_branch? branch_name
|
||||
protected_branches_names.include?(branch_name)
|
||||
protected_branches.map(&:name).include?(branch_name)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
|
||||
class ProtectedBranch < ActiveRecord::Base
|
||||
include Gitlab::ShellAdapter
|
||||
include Gitolited
|
||||
|
||||
attr_accessible :name
|
||||
|
||||
|
|
|
@ -1,45 +1,169 @@
|
|||
class Repository
|
||||
attr_accessor :raw_repository
|
||||
include Gitlab::Popen
|
||||
|
||||
def initialize(path_with_namespace, default_branch)
|
||||
@raw_repository = Gitlab::Git::Repository.new(path_with_namespace, default_branch)
|
||||
rescue Gitlab::Git::Repository::NoRepository
|
||||
nil
|
||||
# Repository directory name with namespace direcotry
|
||||
# Examples:
|
||||
# gitlab/gitolite
|
||||
# diaspora
|
||||
#
|
||||
attr_accessor :path_with_namespace
|
||||
|
||||
# Grit repo object
|
||||
attr_accessor :repo
|
||||
|
||||
# Default branch in the repository
|
||||
attr_accessor :root_ref
|
||||
|
||||
def initialize(path_with_namespace, root_ref = 'master')
|
||||
@root_ref = root_ref || "master"
|
||||
@path_with_namespace = path_with_namespace
|
||||
|
||||
# Init grit repo object
|
||||
repo
|
||||
end
|
||||
|
||||
def exists?
|
||||
raw_repository
|
||||
def raw
|
||||
repo
|
||||
end
|
||||
|
||||
def empty?
|
||||
raw_repository.empty?
|
||||
def path_to_repo
|
||||
@path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git")
|
||||
end
|
||||
|
||||
def commit(id = nil)
|
||||
commit = raw_repository.commit(id)
|
||||
commit = Commit.new(commit) if commit
|
||||
commit
|
||||
def repo
|
||||
@repo ||= Grit::Repo.new(path_to_repo)
|
||||
end
|
||||
|
||||
def commit(commit_id = nil)
|
||||
Commit.find_or_first(repo, commit_id, root_ref)
|
||||
end
|
||||
|
||||
def fresh_commits(n = 10)
|
||||
Commit.fresh_commits(repo, n)
|
||||
end
|
||||
|
||||
def commits_with_refs(n = 20)
|
||||
Commit.commits_with_refs(repo, n)
|
||||
end
|
||||
|
||||
def commits_since(date)
|
||||
Commit.commits_since(repo, date)
|
||||
end
|
||||
|
||||
def commits(ref, path = nil, limit = nil, offset = nil)
|
||||
commits = raw_repository.commits(ref, path, limit, offset)
|
||||
commits = Commit.decorate(commits) if commits.present?
|
||||
commits
|
||||
Commit.commits(repo, ref, path, limit, offset)
|
||||
end
|
||||
|
||||
def commits_between(target, source)
|
||||
commits = raw_repository.commits_between(target, source)
|
||||
commits = Commit.decorate(commits) if commits.present?
|
||||
commits
|
||||
def last_commit_for(ref, path = nil)
|
||||
commits(ref, path, 1).first
|
||||
end
|
||||
|
||||
def method_missing(m, *args, &block)
|
||||
raw_repository.send(m, *args, &block)
|
||||
def commits_between(from, to)
|
||||
Commit.commits_between(repo, from, to)
|
||||
end
|
||||
|
||||
def respond_to?(method)
|
||||
return true if raw_repository.respond_to?(method)
|
||||
# Returns an Array of branch names
|
||||
def branch_names
|
||||
repo.branches.collect(&:name).sort
|
||||
end
|
||||
|
||||
super
|
||||
# Returns an Array of Branches
|
||||
def branches
|
||||
repo.branches.sort_by(&:name)
|
||||
end
|
||||
|
||||
# Returns an Array of tag names
|
||||
def tag_names
|
||||
repo.tags.collect(&:name).sort.reverse
|
||||
end
|
||||
|
||||
# Returns an Array of Tags
|
||||
def tags
|
||||
repo.tags.sort_by(&:name).reverse
|
||||
end
|
||||
|
||||
# Returns an Array of branch and tag names
|
||||
def ref_names
|
||||
[branch_names + tag_names].flatten
|
||||
end
|
||||
|
||||
def heads
|
||||
@heads ||= repo.heads
|
||||
end
|
||||
|
||||
def tree(fcommit, path = nil)
|
||||
fcommit = commit if fcommit == :head
|
||||
tree = fcommit.tree
|
||||
path ? (tree / path) : tree
|
||||
end
|
||||
|
||||
def has_commits?
|
||||
!!commit
|
||||
rescue Grit::NoSuchPathError
|
||||
false
|
||||
end
|
||||
|
||||
def empty?
|
||||
!has_commits?
|
||||
end
|
||||
|
||||
# Discovers the default branch based on the repository's available branches
|
||||
#
|
||||
# - If no branches are present, returns nil
|
||||
# - If one branch is present, returns its name
|
||||
# - If two or more branches are present, returns the one that has a name
|
||||
# matching root_ref (default_branch or 'master' if default_branch is nil)
|
||||
def discover_default_branch
|
||||
if branch_names.length == 0
|
||||
nil
|
||||
elsif branch_names.length == 1
|
||||
branch_names.first
|
||||
else
|
||||
branch_names.select { |v| v == root_ref }.first
|
||||
end
|
||||
end
|
||||
|
||||
# Archive Project to .tar.gz
|
||||
#
|
||||
# Already packed repo archives stored at
|
||||
# app_root/tmp/repositories/project_name/project_name-commit-id.tag.gz
|
||||
#
|
||||
def archive_repo(ref)
|
||||
ref = ref || self.root_ref
|
||||
commit = self.commit(ref)
|
||||
return nil unless commit
|
||||
|
||||
# Build file path
|
||||
file_name = self.path_with_namespace.gsub("/","_") + "-" + commit.id.to_s + ".tar.gz"
|
||||
storage_path = Rails.root.join("tmp", "repositories")
|
||||
file_path = File.join(storage_path, self.path_with_namespace, file_name)
|
||||
|
||||
# Put files into a directory before archiving
|
||||
prefix = File.basename(self.path_with_namespace) + "/"
|
||||
|
||||
# Create file if not exists
|
||||
unless File.exists?(file_path)
|
||||
FileUtils.mkdir_p File.dirname(file_path)
|
||||
file = self.repo.archive_to_file(ref, prefix, file_path)
|
||||
end
|
||||
|
||||
file_path
|
||||
end
|
||||
|
||||
# Return repo size in megabytes
|
||||
# Cached in redis
|
||||
def size
|
||||
Rails.cache.fetch(cache_key(:size)) do
|
||||
size = popen('du -s', path_to_repo).first.strip.to_i
|
||||
(size.to_f / 1024).round(2)
|
||||
end
|
||||
end
|
||||
|
||||
def expire_cache
|
||||
Rails.cache.delete(cache_key(:size))
|
||||
end
|
||||
|
||||
def cache_key(type)
|
||||
"#{type}:#{path_with_namespace}"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,12 +26,4 @@ class Tree
|
|||
def empty?
|
||||
data.blank?
|
||||
end
|
||||
|
||||
def up_dir?
|
||||
path.present?
|
||||
end
|
||||
|
||||
def readme
|
||||
@readme ||= contents.find { |c| c.is_a?(Grit::Blob) and c.name =~ /^readme/i }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
# can_create_team :boolean default(TRUE), not null
|
||||
# state :string(255)
|
||||
# color_scheme_id :integer default(1), not null
|
||||
# notification_level :integer default(1), not null
|
||||
#
|
||||
|
||||
class User < ActiveRecord::Base
|
||||
|
@ -47,13 +46,6 @@ class User < ActiveRecord::Base
|
|||
|
||||
attr_accessor :force_random_password
|
||||
|
||||
# Virtual attribute for authenticating by either username or email
|
||||
attr_accessor :login
|
||||
|
||||
# Add login to attr_accessible
|
||||
attr_accessible :login
|
||||
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
@ -109,9 +101,6 @@ class User < ActiveRecord::Base
|
|||
format: { with: Gitlab::Regex.username_regex,
|
||||
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
|
||||
|
||||
validates :notification_level,
|
||||
inclusion: { in: Notification.notification_levels },
|
||||
presence: true
|
||||
|
||||
validate :namespace_uniq, if: ->(user) { user.username_changed? }
|
||||
|
||||
|
@ -151,16 +140,6 @@ class User < ActiveRecord::Base
|
|||
# Class methods
|
||||
#
|
||||
class << self
|
||||
# Devise method overriden to allow sing in with email or username
|
||||
def find_for_database_authentication(warden_conditions)
|
||||
conditions = warden_conditions.dup
|
||||
if login = conditions.delete(:login)
|
||||
where(conditions).where(["lower(username) = :value OR lower(email) = :value", { value: login.downcase }]).first
|
||||
else
|
||||
where(conditions).first
|
||||
end
|
||||
end
|
||||
|
||||
def filter filter_name
|
||||
case filter_name
|
||||
when "admins"; self.admins
|
||||
|
@ -212,10 +191,6 @@ class User < ActiveRecord::Base
|
|||
username
|
||||
end
|
||||
|
||||
def notification
|
||||
@notification ||= Notification.new(self)
|
||||
end
|
||||
|
||||
def generate_password
|
||||
if self.force_random_password
|
||||
self.password = self.password_confirmation = Devise.friendly_token.first(8)
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
|
||||
class UsersProject < ActiveRecord::Base
|
||||
include Gitlab::ShellAdapter
|
||||
include Gitolited
|
||||
|
||||
GUEST = 10
|
||||
REPORTER = 20
|
||||
|
@ -38,7 +38,7 @@ class UsersProject < ActiveRecord::Base
|
|||
scope :masters, -> { where(project_access: MASTER) }
|
||||
|
||||
scope :in_project, ->(project) { where(project_id: project.id) }
|
||||
scope :in_projects, ->(projects) { where(project_id: projects.map { |p| p.id }) }
|
||||
scope :in_projects, ->(projects) { where(project_id: project_ids) }
|
||||
scope :with_user, ->(user) { where(user_id: user.id) }
|
||||
|
||||
class << self
|
||||
|
|
|
@ -79,14 +79,14 @@ class WikiPage
|
|||
def version
|
||||
return nil unless persisted?
|
||||
|
||||
@version ||= Commit.new(Gitlab::Git::Commit.new(@page.version))
|
||||
@version ||= Commit.new(@page.version)
|
||||
end
|
||||
|
||||
# Returns an array of Gitlab Commit instances.
|
||||
def versions
|
||||
return [] unless persisted?
|
||||
|
||||
@page.versions.map { |v| Commit.new(Gitlab::Git::Commit.new(v)) }
|
||||
@page.versions.map { |v| Commit.new(v) }
|
||||
end
|
||||
|
||||
# Returns the Date that this latest version was
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
class ActivityObserver < BaseObserver
|
||||
class ActivityObserver < ActiveRecord::Observer
|
||||
observe :issue, :merge_request, :note, :milestone
|
||||
|
||||
def after_create(record)
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
class BaseObserver < ActiveRecord::Observer
|
||||
def notification
|
||||
NotificationService.new
|
||||
end
|
||||
|
||||
def log_info message
|
||||
Gitlab::AppLogger.info message
|
||||
end
|
||||
end
|
|
@ -1,30 +1,42 @@
|
|||
class IssueObserver < BaseObserver
|
||||
class IssueObserver < ActiveRecord::Observer
|
||||
cattr_accessor :current_user
|
||||
|
||||
def after_create(issue)
|
||||
notification.new_issue(issue, current_user)
|
||||
if issue.assignee && issue.assignee != current_user
|
||||
Notify.delay.new_issue_email(issue.id)
|
||||
end
|
||||
end
|
||||
|
||||
def after_close(issue, transition)
|
||||
notification.close_issue(issue, current_user)
|
||||
send_reassigned_email(issue) if issue.is_being_reassigned?
|
||||
|
||||
create_note(issue)
|
||||
end
|
||||
|
||||
def after_reopen(issue, transition)
|
||||
send_reassigned_email(issue) if issue.is_being_reassigned?
|
||||
|
||||
create_note(issue)
|
||||
end
|
||||
|
||||
def after_update(issue)
|
||||
if issue.is_being_reassigned?
|
||||
notification.reassigned_issue(issue, current_user)
|
||||
end
|
||||
send_reassigned_email(issue) if issue.is_being_reassigned?
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Create issue note with service comment like 'Status changed to closed'
|
||||
def create_note(issue)
|
||||
Note.create_status_change_note(issue, current_user, issue.state)
|
||||
[issue.author, issue.assignee].compact.uniq.each do |recipient|
|
||||
Notify.delay.issue_status_changed_email(recipient.id, issue.id, issue.state, current_user.id)
|
||||
end
|
||||
end
|
||||
|
||||
def send_reassigned_email(issue)
|
||||
recipient_ids = [issue.assignee_id, issue.assignee_id_was].keep_if {|id| id && id != current_user.id }
|
||||
|
||||
recipient_ids.each do |recipient_id|
|
||||
Notify.delay.reassigned_issue_email(recipient_id, issue.id, issue.assignee_id_was)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class KeyObserver < BaseObserver
|
||||
class KeyObserver < ActiveRecord::Observer
|
||||
include Gitolited
|
||||
|
||||
def after_save(key)
|
||||
GitlabShellWorker.perform_async(
|
||||
:add_key,
|
||||
|
@ -6,7 +8,8 @@ class KeyObserver < BaseObserver
|
|||
key.key
|
||||
)
|
||||
|
||||
notification.new_key(key)
|
||||
# Notify about ssh key being added
|
||||
Notify.delay.new_ssh_key_email(key.id) if key.user
|
||||
end
|
||||
|
||||
def after_destroy(key)
|
||||
|
|
|
@ -1,25 +1,36 @@
|
|||
class MergeRequestObserver < BaseObserver
|
||||
class MergeRequestObserver < ActiveRecord::Observer
|
||||
cattr_accessor :current_user
|
||||
|
||||
def after_create(merge_request)
|
||||
notification.new_merge_request(merge_request, current_user)
|
||||
if merge_request.assignee && merge_request.assignee != current_user
|
||||
Notify.delay.new_merge_request_email(merge_request.id)
|
||||
end
|
||||
end
|
||||
|
||||
def after_close(merge_request, transition)
|
||||
send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
|
||||
|
||||
Note.create_status_change_note(merge_request, current_user, merge_request.state)
|
||||
|
||||
notification.close_mr(merge_request, current_user)
|
||||
end
|
||||
|
||||
def after_merge(merge_request, transition)
|
||||
notification.merge_mr(merge_request)
|
||||
end
|
||||
|
||||
def after_reopen(merge_request, transition)
|
||||
send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
|
||||
|
||||
Note.create_status_change_note(merge_request, current_user, merge_request.state)
|
||||
end
|
||||
|
||||
def after_update(merge_request)
|
||||
notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned?
|
||||
send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def send_reassigned_email(merge_request)
|
||||
recipients_ids = merge_request.assignee_id_was, merge_request.assignee_id
|
||||
recipients_ids.delete current_user.id
|
||||
|
||||
recipients_ids.each do |recipient_id|
|
||||
Notify.delay.reassigned_merge_request_email(recipient_id, merge_request.id, merge_request.assignee_id_was)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,38 @@
|
|||
class NoteObserver < BaseObserver
|
||||
class NoteObserver < ActiveRecord::Observer
|
||||
def after_create(note)
|
||||
notification.new_note(note)
|
||||
send_notify_mails(note)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def send_notify_mails(note)
|
||||
if note.notify
|
||||
notify_team(note)
|
||||
elsif note.notify_author
|
||||
# Notify only author of resource
|
||||
if note.commit_author
|
||||
Notify.delay.note_commit_email(note.commit_author.id, note.id)
|
||||
end
|
||||
else
|
||||
# Otherwise ignore it
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Notifies the whole team except the author of note
|
||||
def notify_team(note)
|
||||
# Note: wall posts are not "attached" to anything, so fall back to "Wall"
|
||||
noteable_type = note.noteable_type.presence || "Wall"
|
||||
notify_method = "note_#{noteable_type.underscore}_email".to_sym
|
||||
|
||||
if Notify.respond_to? notify_method
|
||||
team_without_note_author(note).map do |u|
|
||||
Notify.delay.send(notify_method, u.id, note.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def team_without_note_author(note)
|
||||
note.project.users.reject { |u| u.id == note.author.id }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
class ProjectObserver < BaseObserver
|
||||
class ProjectObserver < ActiveRecord::Observer
|
||||
def after_create(project)
|
||||
GitlabShellWorker.perform_async(
|
||||
:add_repository,
|
||||
|
@ -27,4 +27,10 @@ class ProjectObserver < BaseObserver
|
|||
|
||||
log_info("Project \"#{project.name}\" was removed")
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def log_info message
|
||||
Gitlab::AppLogger.info message
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
class SystemHookObserver < BaseObserver
|
||||
class SystemHookObserver < ActiveRecord::Observer
|
||||
observe :user, :project, :users_project
|
||||
|
||||
def after_create(model)
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
class UserObserver < BaseObserver
|
||||
class UserObserver < ActiveRecord::Observer
|
||||
def after_create(user)
|
||||
log_info("User \"#{user.name}\" (#{user.email}) was created")
|
||||
|
||||
notification.new_user(user)
|
||||
# Dont email omniauth created users
|
||||
Notify.delay.new_user_email(user.id, user.password) unless user.extern_uid?
|
||||
end
|
||||
|
||||
def after_destroy user
|
||||
|
@ -18,4 +19,10 @@ class UserObserver < BaseObserver
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def log_info message
|
||||
Gitlab::AppLogger.info message
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
class UsersProjectObserver < BaseObserver
|
||||
class UsersProjectObserver < ActiveRecord::Observer
|
||||
def after_commit(users_project)
|
||||
return if users_project.destroyed?
|
||||
Notify.delay.project_access_granted_email(users_project.id)
|
||||
end
|
||||
|
||||
def after_create(users_project)
|
||||
|
@ -9,12 +10,6 @@ class UsersProjectObserver < BaseObserver
|
|||
action: Event::JOINED,
|
||||
author_id: users_project.user.id
|
||||
)
|
||||
|
||||
notification.new_team_member(users_project)
|
||||
end
|
||||
|
||||
def after_update(users_project)
|
||||
notification.update_team_member(users_project)
|
||||
end
|
||||
|
||||
def after_destroy(users_project)
|
||||
|
|
|
@ -1,197 +0,0 @@
|
|||
# NotificationService class
|
||||
#
|
||||
# Used for notifing users with emails about different events
|
||||
#
|
||||
# Ex.
|
||||
# NotificationService.new.new_issue(issue, current_user)
|
||||
#
|
||||
class NotificationService
|
||||
# Always notify user about ssh key added
|
||||
# only if ssh key is not deploy key
|
||||
#
|
||||
# This is security email so it will be sent
|
||||
# even if user disabled notifications
|
||||
def new_key(key)
|
||||
if key.user
|
||||
Notify.delay.new_ssh_key_email(key.id)
|
||||
end
|
||||
end
|
||||
|
||||
# When create an issue we should send next emails:
|
||||
#
|
||||
# * issue assignee if his notification level is not Disabled
|
||||
# * project team members with notification level higher then Participating
|
||||
#
|
||||
def new_issue(issue, current_user)
|
||||
new_resource_email(issue, 'new_issue_email')
|
||||
end
|
||||
|
||||
# When we close an issue we should send next emails:
|
||||
#
|
||||
# * issue author if his notification level is not Disabled
|
||||
# * issue assignee if his notification level is not Disabled
|
||||
# * project team members with notification level higher then Participating
|
||||
#
|
||||
def close_issue(issue, current_user)
|
||||
close_resource_email(issue, current_user, 'closed_issue_email')
|
||||
end
|
||||
|
||||
# When we reassign an issue we should send next emails:
|
||||
#
|
||||
# * issue old assignee if his notification level is not Disabled
|
||||
# * issue new assignee if his notification level is not Disabled
|
||||
#
|
||||
def reassigned_issue(issue, current_user)
|
||||
reassign_resource_email(issue, current_user, 'reassigned_issue_email')
|
||||
end
|
||||
|
||||
|
||||
# When create a merge request we should send next emails:
|
||||
#
|
||||
# * mr assignee if his notification level is not Disabled
|
||||
#
|
||||
def new_merge_request(merge_request, current_user)
|
||||
new_resource_email(merge_request, 'new_merge_request_email')
|
||||
end
|
||||
|
||||
# When we reassign a merge_request we should send next emails:
|
||||
#
|
||||
# * merge_request old assignee if his notification level is not Disabled
|
||||
# * merge_request assignee if his notification level is not Disabled
|
||||
#
|
||||
def reassigned_merge_request(merge_request, current_user)
|
||||
reassign_resource_email(merge_request, current_user, 'reassigned_merge_request_email')
|
||||
end
|
||||
|
||||
# When we close a merge request we should send next emails:
|
||||
#
|
||||
# * merge_request author if his notification level is not Disabled
|
||||
# * merge_request assignee if his notification level is not Disabled
|
||||
# * project team members with notification level higher then Participating
|
||||
#
|
||||
def close_mr(merge_request, current_user)
|
||||
close_resource_email(merge_request, current_user, 'closed_merge_request_email')
|
||||
end
|
||||
|
||||
# When we merge a merge request we should send next emails:
|
||||
#
|
||||
# * merge_request author if his notification level is not Disabled
|
||||
# * merge_request assignee if his notification level is not Disabled
|
||||
# * project team members with notification level higher then Participating
|
||||
#
|
||||
def merge_mr(merge_request)
|
||||
recipients = reject_muted_users([merge_request.author, merge_request.assignee])
|
||||
recipients = recipients.concat(project_watchers(merge_request.project)).uniq
|
||||
|
||||
recipients.each do |recipient|
|
||||
Notify.delay.merged_merge_request_email(recipient.id, merge_request.id)
|
||||
end
|
||||
end
|
||||
|
||||
# Notify new user with email after creation
|
||||
def new_user(user)
|
||||
# Dont email omniauth created users
|
||||
Notify.delay.new_user_email(user.id, user.password) unless user.extern_uid?
|
||||
end
|
||||
|
||||
# Notify users on new note in system
|
||||
#
|
||||
# TODO: split on methods and refactor
|
||||
#
|
||||
def new_note(note)
|
||||
# ignore wall messages
|
||||
return true unless note.noteable_type.present?
|
||||
|
||||
opts = { noteable_type: note.noteable_type, project_id: note.project_id }
|
||||
|
||||
if note.commit_id.present?
|
||||
opts.merge!(commit_id: note.commit_id)
|
||||
recipients = [note.commit_author]
|
||||
else
|
||||
opts.merge!(noteable_id: note.noteable_id)
|
||||
target = note.noteable
|
||||
recipients = []
|
||||
recipients << target.assignee if target.respond_to?(:assignee)
|
||||
recipients << target.author if target.respond_to?(:author)
|
||||
end
|
||||
|
||||
# Get users who left comment in thread
|
||||
recipients = recipients.concat(User.where(id: Note.where(opts).pluck(:author_id)))
|
||||
|
||||
# Merge project watchers
|
||||
recipients = recipients.concat(project_watchers(note.project)).compact.uniq
|
||||
|
||||
# Reject mutes users
|
||||
recipients = reject_muted_users(recipients)
|
||||
|
||||
# Reject author
|
||||
recipients.delete(note.author)
|
||||
|
||||
# build notify method like 'note_commit_email'
|
||||
notify_method = "note_#{note.noteable_type.underscore}_email".to_sym
|
||||
|
||||
recipients.each do |recipient|
|
||||
Notify.delay.send(notify_method, recipient.id, note.id)
|
||||
end
|
||||
end
|
||||
|
||||
def new_team_member(users_project)
|
||||
Notify.delay.project_access_granted_email(users_project.id)
|
||||
end
|
||||
|
||||
def update_team_member(users_project)
|
||||
Notify.delay.project_access_granted_email(users_project.id)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Get project users with WATCH notification level
|
||||
def project_watchers(project)
|
||||
project.users.where(notification_level: Notification::N_WATCH)
|
||||
end
|
||||
|
||||
# Remove users with disabled notifications from array
|
||||
# Also remove duplications and nil recipients
|
||||
def reject_muted_users(users)
|
||||
users.compact.uniq.reject do |user|
|
||||
user.notification.disabled?
|
||||
end
|
||||
end
|
||||
|
||||
def new_resource_email(target, method)
|
||||
recipients = reject_muted_users([target.assignee])
|
||||
recipients = recipients.concat(project_watchers(target.project)).uniq
|
||||
recipients.delete(target.author)
|
||||
|
||||
recipients.each do |recipient|
|
||||
Notify.delay.send(method, recipient.id, target.id)
|
||||
end
|
||||
end
|
||||
|
||||
def close_resource_email(target, current_user, method)
|
||||
recipients = reject_muted_users([target.author, target.assignee])
|
||||
recipients = recipients.concat(project_watchers(target.project)).uniq
|
||||
recipients.delete(current_user)
|
||||
|
||||
recipients.each do |recipient|
|
||||
Notify.delay.send(method, recipient.id, target.id, current_user.id)
|
||||
end
|
||||
end
|
||||
|
||||
def reassign_resource_email(target, current_user, method)
|
||||
recipients = User.where(id: [target.assignee_id, target.assignee_id_was])
|
||||
|
||||
# Add watchers to email list
|
||||
recipients = recipients.concat(project_watchers(target.project))
|
||||
|
||||
# reject users with disabled notifications
|
||||
recipients = reject_muted_users(recipients)
|
||||
|
||||
# Reject me from recipients if I reassign an item
|
||||
recipients.delete(current_user)
|
||||
|
||||
recipients.each do |recipient|
|
||||
Notify.delay.send(method, recipient.id, target.id, target.assignee_id_was)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,9 +3,7 @@
|
|||
# Used for transfer project to another namespace
|
||||
#
|
||||
class ProjectTransferService
|
||||
include Gitlab::ShellAdapter
|
||||
|
||||
class TransferError < StandardError; end
|
||||
include Gitolited
|
||||
|
||||
attr_accessor :project
|
||||
|
||||
|
@ -21,16 +19,14 @@ class ProjectTransferService
|
|||
project.namespace = new_namespace
|
||||
project.save!
|
||||
|
||||
# Move main repository
|
||||
unless gitlab_shell.mv_repository(old_path, new_path)
|
||||
raise TransferError.new('Cannot move project')
|
||||
end
|
||||
|
||||
# Move wiki repo also if present
|
||||
gitlab_shell.mv_repository("#{old_path}.wiki", "#{new_path}.wiki")
|
||||
|
||||
true
|
||||
end
|
||||
rescue => ex
|
||||
raise Project::TransferError.new(ex.message)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
86
app/views/admin/projects/_form.html.haml
Normal file
86
app/views/admin/projects/_form.html.haml
Normal file
|
@ -0,0 +1,86 @@
|
|||
= form_for [:admin, project] do |f|
|
||||
-if project.errors.any?
|
||||
.alert.alert-error
|
||||
%ul
|
||||
- project.errors.full_messages.each do |msg|
|
||||
%li= msg
|
||||
|
||||
.clearfix.project_name_holder
|
||||
= f.label :name do
|
||||
Project name is
|
||||
.input
|
||||
= f.text_field :name, placeholder: "Example Project", class: "xxlarge"
|
||||
|
||||
- if project.repo_exists?
|
||||
%fieldset.adv_settings
|
||||
%legend Advanced settings:
|
||||
.clearfix
|
||||
= f.label :path do
|
||||
Path
|
||||
.input
|
||||
= text_field_tag :ppath, @project.repository.path_to_repo, class: "xlarge", disabled: true
|
||||
|
||||
.clearfix
|
||||
= f.label :default_branch, "Default Branch"
|
||||
.input= f.select(:default_branch, @project.repository.heads.map(&:name), {}, style: "width:210px;")
|
||||
|
||||
%fieldset.adv_settings
|
||||
%legend Features:
|
||||
|
||||
.clearfix
|
||||
= f.label :issues_enabled, "Issues"
|
||||
.input= f.check_box :issues_enabled
|
||||
|
||||
- if Project.issues_tracker.values.count > 1
|
||||
.clearfix
|
||||
= f.label :issues_tracker, "Issues tracker", class: 'control-label'
|
||||
.input= f.select(:issues_tracker, Project.issues_tracker.values, {}, { disabled: !@project.issues_enabled })
|
||||
|
||||
.clearfix
|
||||
= f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label'
|
||||
.input= f.text_field :issues_tracker_id, class: "xxlarge", disabled: !@project.can_have_issues_tracker_id?
|
||||
|
||||
.clearfix
|
||||
= f.label :merge_requests_enabled, "Merge Requests"
|
||||
.input= f.check_box :merge_requests_enabled
|
||||
|
||||
.clearfix
|
||||
= f.label :wall_enabled, "Wall"
|
||||
.input= f.check_box :wall_enabled
|
||||
|
||||
.clearfix
|
||||
= f.label :wiki_enabled, "Wiki"
|
||||
.input= f.check_box :wiki_enabled
|
||||
|
||||
%fieldset.features
|
||||
%legend Public mode:
|
||||
.clearfix
|
||||
= f.label :public do
|
||||
%span Allow public http clone
|
||||
.input= f.check_box :public
|
||||
|
||||
%fieldset.features
|
||||
%legend Transfer:
|
||||
.control-group
|
||||
= f.label :namespace_id do
|
||||
%span Namespace
|
||||
.controls
|
||||
= f.select :namespace_id, namespaces_options(@project.namespace_id, :all), {}, {class: 'chosen'}
|
||||
%br
|
||||
%ul.prepend-top-10.cred
|
||||
%li Be careful. Changing project namespace can have unintended side effects
|
||||
%li You can transfer project only to namespaces you can manage
|
||||
%li You will need to update your local repositories to point to the new location.
|
||||
|
||||
|
||||
.actions
|
||||
= f.submit 'Save Project', class: "btn btn-save"
|
||||
= link_to 'Cancel', admin_projects_path, class: "btn btn-cancel"
|
||||
|
||||
|
||||
|
||||
:javascript
|
||||
$(function(){
|
||||
new Projects();
|
||||
})
|
||||
|
3
app/views/admin/projects/edit.html.haml
Normal file
3
app/views/admin/projects/edit.html.haml
Normal file
|
@ -0,0 +1,3 @@
|
|||
%h3.page_title #{@project.name} → Edit project
|
||||
%hr
|
||||
= render 'form', project: @project
|
|
@ -52,8 +52,8 @@
|
|||
%i.icon-lock.cgreen
|
||||
= link_to project.name_with_namespace, [:admin, project]
|
||||
.pull-right
|
||||
= link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
|
||||
= link_to 'Destroy', [project], confirm: "REMOVE #{project.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
|
||||
= link_to 'Edit', edit_admin_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
|
||||
= link_to 'Destroy', [:admin, project], confirm: "REMOVE #{project.name}? Are you sure?", method: :delete, class: "btn btn-small btn-remove"
|
||||
- if @projects.blank?
|
||||
%p.nothing_here_message 0 projects matches
|
||||
- else
|
||||
|
|
|
@ -1,90 +1,129 @@
|
|||
%h3.page_title
|
||||
Project: #{@project.name_with_namespace}
|
||||
= link_to edit_project_path(@project), class: "btn pull-right" do
|
||||
= link_to edit_admin_project_path(@project), class: "btn pull-right" do
|
||||
%i.icon-edit
|
||||
Edit
|
||||
%hr
|
||||
.row
|
||||
.span6
|
||||
.ui-box
|
||||
%h5.title
|
||||
Project info:
|
||||
%ul.well-list
|
||||
%li
|
||||
%span.light Name:
|
||||
%strong= @project.name
|
||||
%li
|
||||
%span.light Namespace:
|
||||
%strong
|
||||
- if @project.namespace
|
||||
= link_to @project.namespace.human_name, [:admin, @project.group || @project.owner]
|
||||
- else
|
||||
Global
|
||||
%li
|
||||
%span.light Owned by:
|
||||
%strong
|
||||
- if @project.owner
|
||||
= link_to @project.owner_name, admin_user_path(@project.owner)
|
||||
- else
|
||||
(deleted)
|
||||
|
||||
%li
|
||||
%span.light Created by:
|
||||
%strong
|
||||
= @project.creator.try(:name) || '(deleted)'
|
||||
|
||||
%li
|
||||
%span.light Created at:
|
||||
%strong
|
||||
= @project.created_at.stamp("March 1, 1999")
|
||||
%br
|
||||
%table.zebra-striped
|
||||
%thead
|
||||
%tr
|
||||
%th Project
|
||||
%th
|
||||
%tr
|
||||
%td
|
||||
%b
|
||||
Name:
|
||||
%td
|
||||
= @project.name
|
||||
%tr
|
||||
%td
|
||||
%b
|
||||
Namespace:
|
||||
%td
|
||||
- if @project.namespace
|
||||
= @project.namespace.human_name
|
||||
- else
|
||||
Global
|
||||
%tr
|
||||
%td
|
||||
%b
|
||||
Owned by:
|
||||
%td
|
||||
- if @project.owner
|
||||
= link_to @project.owner_name, admin_user_path(@project.owner)
|
||||
- else
|
||||
(deleted)
|
||||
%tr
|
||||
%td
|
||||
%b
|
||||
Created by:
|
||||
%td
|
||||
= @project.creator.try(:name) || '(deleted)'
|
||||
%tr
|
||||
%td
|
||||
%b
|
||||
Created at:
|
||||
%td
|
||||
= @project.created_at.stamp("March 1, 1999")
|
||||
%tr
|
||||
%td
|
||||
%b
|
||||
Smart HTTP:
|
||||
%td
|
||||
= link_to @project.http_url_to_repo
|
||||
%tr
|
||||
%td
|
||||
%b
|
||||
SSH:
|
||||
%td
|
||||
= link_to @project.ssh_url_to_repo
|
||||
- if @project.public
|
||||
%tr.bgred
|
||||
%td
|
||||
%b
|
||||
Public Read-Only Code access:
|
||||
%td
|
||||
= check_box_tag 'public', nil, @project.public
|
||||
|
||||
%li
|
||||
%span.light http:
|
||||
%strong
|
||||
= link_to @project.http_url_to_repo
|
||||
%li
|
||||
%span.light ssh:
|
||||
%strong
|
||||
= link_to @project.ssh_url_to_repo
|
||||
- if @project.repository.exists?
|
||||
%li
|
||||
%span.light fs:
|
||||
%strong
|
||||
= @repository.path_to_repo
|
||||
- if @repository
|
||||
%table.zebra-striped
|
||||
%thead
|
||||
%tr
|
||||
%th Repository
|
||||
%th
|
||||
%tr
|
||||
%td
|
||||
%b
|
||||
FS Path:
|
||||
%td
|
||||
%code= @repository.path_to_repo
|
||||
%tr
|
||||
%td
|
||||
%b
|
||||
Last commit at:
|
||||
%td
|
||||
= last_commit(@project)
|
||||
|
||||
%li
|
||||
%span.light last commit:
|
||||
%strong
|
||||
= last_commit(@project)
|
||||
- else
|
||||
%li
|
||||
%span.light repository:
|
||||
%strong.cred
|
||||
does not exist
|
||||
%br
|
||||
%h5
|
||||
Team
|
||||
%small
|
||||
(#{@project.users.count})
|
||||
%br
|
||||
%table.zebra-striped.team_members
|
||||
%thead
|
||||
%tr
|
||||
%th Name
|
||||
%th Project Access
|
||||
%th Repository Access
|
||||
%th
|
||||
|
||||
%li
|
||||
%span.light access:
|
||||
%strong
|
||||
- if @project.public
|
||||
%span.cblue
|
||||
%i.icon-share
|
||||
Public
|
||||
- else
|
||||
%span.cgreen
|
||||
%i.icon-lock
|
||||
Private
|
||||
.span6
|
||||
.ui-box
|
||||
%h5.title
|
||||
Team
|
||||
%small
|
||||
(#{@project.users.count})
|
||||
= link_to project_team_index_path(@project), class: "btn btn-tiny" do
|
||||
%i.icon-edit
|
||||
Edit Team
|
||||
%ul.well-list.team_members
|
||||
- @project.users.each do |tm|
|
||||
%li
|
||||
%strong
|
||||
= link_to tm.name, admin_user_path(tm)
|
||||
%span.pull-right.light= @project.project_access_human(tm)
|
||||
- @project.users.each do |tm|
|
||||
%tr
|
||||
%td
|
||||
= link_to tm.name, admin_user_path(tm)
|
||||
%td= @project.project_access_human(tm)
|
||||
%td= link_to 'Edit Access', edit_admin_project_member_path(@project, tm), class: "btn btn-small"
|
||||
%td= link_to 'Remove from team', admin_project_member_path(@project, tm), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove small"
|
||||
|
||||
%br
|
||||
%h5 Add new team member
|
||||
%br
|
||||
= form_tag team_update_admin_project_path(@project), class: "bulk_import", method: :put do
|
||||
%table.zebra-striped
|
||||
%thead
|
||||
%tr
|
||||
%th Users
|
||||
%th Project Access:
|
||||
|
||||
%tr
|
||||
%td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
|
||||
%td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"}
|
||||
|
||||
%tr
|
||||
%td= submit_tag 'Add', class: "btn btn-primary"
|
||||
%td
|
||||
Read more about project permissions
|
||||
%strong= link_to "here", help_permissions_path, class: "vlink"
|
||||
|
|
0
app/views/admin/projects/team.html.haml
Normal file
0
app/views/admin/projects/team.html.haml
Normal file
|
@ -6,7 +6,7 @@
|
|||
%i.icon-angle-right
|
||||
= link_to project_tree_path(@project, @ref) do
|
||||
= @project.name
|
||||
- tree_breadcrumbs(@tree, 6) do |link|
|
||||
- @tree.breadcrumbs(6) do |link|
|
||||
\/
|
||||
%li= link
|
||||
.clear
|
||||
|
@ -22,13 +22,13 @@
|
|||
%table
|
||||
- current_line = 1
|
||||
- @blame.each do |commit, lines|
|
||||
- commit = Commit.new(commit)
|
||||
- commit = CommitDecorator.decorate(Commit.new(commit))
|
||||
%tr
|
||||
%td.blame-commit
|
||||
%span.commit
|
||||
= link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id"
|
||||
|
||||
= commit_author_link(commit, avatar: true, size: 16)
|
||||
= commit.author_link avatar: true, size: 16
|
||||
|
||||
= link_to_gfm truncate(commit.title, length: 20), project_commit_path(@project, commit.id), class: "row_title"
|
||||
%td.lines.blame-numbers
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
= render "commit_box"
|
||||
= render "commits/commit_box"
|
||||
|
||||
- unless @commit.has_zero_stats?
|
||||
%p.pull-right.cgray
|
||||
This commit has
|
||||
%span.cgreen #{@commit.stats.additions} additions
|
||||
and
|
||||
%span.cred #{@commit.stats.deletions} deletions
|
||||
%p.pull-right.cgray
|
||||
This commit has
|
||||
%span.cgreen #{@commit.stats.additions} additions
|
||||
and
|
||||
%span.cred #{@commit.stats.deletions} deletions
|
||||
|
||||
= render "commits/diffs", diffs: @commit.diffs
|
||||
= render "notes/notes_with_form"
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
%strong= link_to "Browse Code »", project_tree_path(@project, commit), class: "right"
|
||||
%p
|
||||
= link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id"
|
||||
= commit_author_link(commit, avatar: true, size: 24)
|
||||
= commit.author_link avatar: true, size: 24
|
||||
|
||||
= link_to_gfm truncate(commit.title, length: 70), project_commit_path(@project, commit.id), class: "row_title"
|
||||
|
||||
|
|
|
@ -24,14 +24,14 @@
|
|||
.row
|
||||
.span5
|
||||
.author
|
||||
= commit_author_link(@commit, avatar: true, size: 32)
|
||||
= @commit.author_link avatar: true, size: 32
|
||||
authored
|
||||
%time{title: @commit.authored_date.stamp("Aug 21, 2011 9:23pm")}
|
||||
#{time_ago_in_words(@commit.authored_date)} ago
|
||||
- if @commit.different_committer?
|
||||
.committer
|
||||
→
|
||||
= commit_committer_link(@commit)
|
||||
= @commit.committer_link
|
||||
committed
|
||||
%time{title: @commit.committed_date.stamp("Aug 21, 2011 9:23pm")}
|
||||
#{time_ago_in_words(@commit.committed_date)} ago
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
- if @path.present?
|
||||
%ul.breadcrumb
|
||||
= commits_breadcrumbs
|
||||
= breadcrumbs
|
||||
|
||||
%div{id: dom_id(@project)}
|
||||
#commits-list= render "commits"
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
%div.ui-box
|
||||
%h5.title
|
||||
Commits (#{@commits.count})
|
||||
%ul.well-list= render Commit.decorate(@commits)
|
||||
%ul.well-list= render @commits
|
||||
|
||||
- unless @diffs.empty?
|
||||
%h4 Diff
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
.ui-box
|
||||
%h5.title
|
||||
Groups
|
||||
%span.light
|
||||
%small
|
||||
(#{groups.count})
|
||||
- if current_user.can_create_group?
|
||||
%span.pull-right
|
||||
= link_to new_group_path, class: "btn btn-small" do
|
||||
= link_to new_group_path, class: "btn btn-tiny info" do
|
||||
%i.icon-plus
|
||||
New Group
|
||||
%ul.well-list
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
.ui-box
|
||||
%h5.title
|
||||
Projects
|
||||
%span.light
|
||||
%small
|
||||
(#{@projects_count})
|
||||
- if current_user.can_create_project?
|
||||
%span.pull-right
|
||||
= link_to new_project_path, class: "btn btn-small" do
|
||||
= link_to new_project_path, class: "btn btn-tiny info" do
|
||||
%i.icon-plus
|
||||
New Project
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
.ui-box.teams-box
|
||||
%h5.title
|
||||
Teams
|
||||
%span.light
|
||||
%small
|
||||
(#{teams.count})
|
||||
%span.pull-right
|
||||
= link_to new_team_path, class: "btn btn-small" do
|
||||
= link_to new_team_path, class: "btn btn-tiny info" do
|
||||
%i.icon-plus
|
||||
New Team
|
||||
%ul.well-list
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
- if ldap_enable?
|
||||
= render partial: 'devise/sessions/new_ldap'
|
||||
= render :partial => 'devise/sessions/new_ldap'
|
||||
- else
|
||||
= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: "login-box" }) do |f|
|
||||
= image_tag "login-logo.png", width: "304", height: "66", class: "login-logo", alt: "Login Logo"
|
||||
= f.text_field :login, class: "text top", placeholder: "Username or Email", autofocus: "autofocus"
|
||||
= f.password_field :password, class: "text bottom", placeholder: "Password"
|
||||
= form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f|
|
||||
= image_tag "login-logo.png", :width => "304", :height => "66", :class => "login-logo", :alt => "Login Logo"
|
||||
= f.email_field :email, :class => "text top", :placeholder => "Email", :autofocus => "autofocus"
|
||||
= f.password_field :password, :class => "text bottom", :placeholder => "Password"
|
||||
- if devise_mapping.rememberable?
|
||||
.clearfix.inputs-list
|
||||
%label.checkbox.remember_me{for: "user_remember_me"}
|
||||
%label.checkbox.remember_me{:for => "user_remember_me"}
|
||||
= f.check_box :remember_me
|
||||
%span Remember me
|
||||
%br/
|
||||
= f.submit "Sign in", class: "btn-create btn"
|
||||
= f.submit "Sign in", :class => "btn-create btn"
|
||||
.pull-right
|
||||
= link_to "Forgot your password?", new_password_path(resource_name), class: "btn"
|
||||
= link_to "Forgot your password?", new_password_path(resource_name), :class => "btn"
|
||||
%br/
|
||||
- if Gitlab.config.gitlab.signup_enabled
|
||||
%hr/
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
- commit = CommitDecorator.decorate(commit)
|
||||
%li.commit
|
||||
%p
|
||||
= link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id"
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
},
|
||||
time: c.time,
|
||||
space: c.spaces.first,
|
||||
refs: get_refs(c),
|
||||
refs: join_with_space(c.refs),
|
||||
id: c.sha,
|
||||
date: c.date,
|
||||
message: c.message,
|
||||
|
|
|
@ -4,8 +4,24 @@
|
|||
%body{class: "#{app_theme} admin"}
|
||||
= render "layouts/head_panel", title: "Admin area"
|
||||
= render "layouts/flash"
|
||||
%nav.main-nav
|
||||
.container= render 'layouts/nav/admin'
|
||||
|
||||
.container
|
||||
%ul.main_menu
|
||||
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
|
||||
= link_to admin_root_path, title: "Stats" do
|
||||
%i.icon-home
|
||||
= nav_link(controller: :projects) do
|
||||
= link_to "Projects", admin_projects_path
|
||||
= nav_link(controller: :teams) do
|
||||
= link_to "Teams", admin_teams_path
|
||||
= nav_link(controller: :groups) do
|
||||
= link_to "Groups", admin_groups_path
|
||||
= nav_link(controller: :users) do
|
||||
= link_to "Users", admin_users_path
|
||||
= nav_link(controller: :logs) do
|
||||
= link_to "Logs", admin_logs_path
|
||||
= nav_link(controller: :hooks) do
|
||||
= link_to "Hooks", admin_hooks_path
|
||||
= nav_link(controller: :resque) do
|
||||
= link_to "Background Jobs", admin_resque_path
|
||||
|
||||
.content= yield
|
||||
|
|
|
@ -4,8 +4,25 @@
|
|||
%body{class: "#{app_theme} application"}
|
||||
= render "layouts/head_panel", title: "Dashboard"
|
||||
= render "layouts/flash"
|
||||
%nav.main-nav
|
||||
.container= render 'layouts/nav/dashboard'
|
||||
|
||||
.container
|
||||
%ul.main_menu
|
||||
= nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do
|
||||
= link_to root_path, title: "Home" do
|
||||
%i.icon-home
|
||||
= nav_link(path: 'dashboard#projects') do
|
||||
= link_to projects_dashboard_path do
|
||||
Projects
|
||||
= nav_link(path: 'dashboard#issues') do
|
||||
= link_to issues_dashboard_path do
|
||||
Issues
|
||||
%span.count= current_user.assigned_issues.opened.count
|
||||
= nav_link(path: 'dashboard#merge_requests') do
|
||||
= link_to merge_requests_dashboard_path do
|
||||
Merge Requests
|
||||
%span.count= current_user.cared_merge_requests.opened.count
|
||||
= nav_link(path: 'search#show') do
|
||||
= link_to "Search", search_path
|
||||
= nav_link(controller: :help) do
|
||||
= link_to "Help", help_path
|
||||
|
||||
.content= yield
|
||||
|
|
|
@ -4,8 +4,25 @@
|
|||
%body{class: "#{app_theme} application"}
|
||||
= render "layouts/head_panel", title: "group: #{@group.name}"
|
||||
= render "layouts/flash"
|
||||
%nav.main-nav
|
||||
.container= render 'layouts/nav/group'
|
||||
|
||||
.container
|
||||
%ul.main_menu
|
||||
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
|
||||
= link_to group_path(@group), title: "Home" do
|
||||
%i.icon-home
|
||||
= nav_link(path: 'groups#issues') do
|
||||
= link_to issues_group_path(@group) do
|
||||
Issues
|
||||
%span.count= current_user.assigned_issues.opened.of_group(@group).count
|
||||
= nav_link(path: 'groups#merge_requests') do
|
||||
= link_to merge_requests_group_path(@group) do
|
||||
Merge Requests
|
||||
%span.count= current_user.cared_merge_requests.opened.of_group(@group).count
|
||||
= nav_link(path: 'groups#people') do
|
||||
= link_to "People", people_group_path(@group)
|
||||
|
||||
- if can?(current_user, :manage_group, @group)
|
||||
= nav_link(path: 'groups#edit') do
|
||||
= link_to edit_group_path(@group), class: "tab " do
|
||||
Settings
|
||||
|
||||
.content= yield
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
%ul
|
||||
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
|
||||
= link_to admin_root_path, title: "Stats" do
|
||||
%i.icon-home
|
||||
= nav_link(controller: :projects) do
|
||||
= link_to "Projects", admin_projects_path
|
||||
= nav_link(controller: :teams) do
|
||||
= link_to "Teams", admin_teams_path
|
||||
= nav_link(controller: :groups) do
|
||||
= link_to "Groups", admin_groups_path
|
||||
= nav_link(controller: :users) do
|
||||
= link_to "Users", admin_users_path
|
||||
= nav_link(controller: :logs) do
|
||||
= link_to "Logs", admin_logs_path
|
||||
= nav_link(controller: :hooks) do
|
||||
= link_to "Hooks", admin_hooks_path
|
||||
= nav_link(controller: :resque) do
|
||||
= link_to "Background Jobs", admin_resque_path
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
%ul
|
||||
= nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do
|
||||
= link_to root_path, title: "Home" do
|
||||
%i.icon-home
|
||||
= nav_link(path: 'dashboard#projects') do
|
||||
= link_to projects_dashboard_path do
|
||||
Projects
|
||||
= nav_link(path: 'dashboard#issues') do
|
||||
= link_to issues_dashboard_path do
|
||||
Issues
|
||||
%span.count= current_user.assigned_issues.opened.count
|
||||
= nav_link(path: 'dashboard#merge_requests') do
|
||||
= link_to merge_requests_dashboard_path do
|
||||
Merge Requests
|
||||
%span.count= current_user.cared_merge_requests.opened.count
|
||||
= nav_link(path: 'search#show') do
|
||||
= link_to "Search", search_path
|
||||
= nav_link(controller: :help) do
|
||||
= link_to "Help", help_path
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
%ul
|
||||
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
|
||||
= link_to group_path(@group), title: "Home" do
|
||||
%i.icon-home
|
||||
= nav_link(path: 'groups#issues') do
|
||||
= link_to issues_group_path(@group) do
|
||||
Issues
|
||||
%span.count= current_user.assigned_issues.opened.of_group(@group).count
|
||||
= nav_link(path: 'groups#merge_requests') do
|
||||
= link_to merge_requests_group_path(@group) do
|
||||
Merge Requests
|
||||
%span.count= current_user.cared_merge_requests.opened.of_group(@group).count
|
||||
= nav_link(path: 'groups#people') do
|
||||
= link_to "People", people_group_path(@group)
|
||||
|
||||
- if can?(current_user, :manage_group, @group)
|
||||
= nav_link(path: 'groups#edit') do
|
||||
= link_to edit_group_path(@group), class: "tab " do
|
||||
Settings
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
%ul
|
||||
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
|
||||
= link_to profile_path, title: "Profile" do
|
||||
%i.icon-home
|
||||
= nav_link(path: 'profiles#account') do
|
||||
= link_to "Account", account_profile_path
|
||||
= nav_link(controller: :notifications) do
|
||||
= link_to "Notifications", profile_notifications_path
|
||||
= nav_link(controller: :keys) do
|
||||
= link_to keys_path do
|
||||
SSH Keys
|
||||
%span.count= current_user.keys.count
|
||||
= nav_link(path: 'profiles#design') do
|
||||
= link_to "Design", design_profile_path
|
||||
= nav_link(path: 'profiles#history') do
|
||||
= link_to "History", history_profile_path
|
||||
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue